fix(atlas): gate final-wave approval on real plan state
Ignore nested plan checkboxes and track parallel final-wave approvals so Atlas only pauses for user approval when the real top-level review wave is complete. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,47 +1,47 @@
|
||||
import { existsSync, readFileSync } from "node:fs"
|
||||
import type { SessionState } from "./types"
|
||||
import { readFinalWavePlanState } from "./final-wave-plan-state"
|
||||
|
||||
const APPROVE_VERDICT_PATTERN = /\bVERDICT:\s*APPROVE\b/i
|
||||
const FINAL_VERIFICATION_HEADING_PATTERN = /^##\s+Final Verification Wave\b/i
|
||||
const UNCHECKED_TASK_PATTERN = /^\s*[-*]\s*\[\s*\]\s*(.+)$/
|
||||
const FINAL_WAVE_TASK_PATTERN = /^F\d+\./i
|
||||
|
||||
function clearFinalWaveApprovalTracking(sessionState: SessionState): void {
|
||||
sessionState.pendingFinalWaveTaskCount = undefined
|
||||
sessionState.approvedFinalWaveTaskCount = undefined
|
||||
}
|
||||
|
||||
export function shouldPauseForFinalWaveApproval(input: {
|
||||
planPath: string
|
||||
taskOutput: string
|
||||
sessionState: SessionState
|
||||
}): boolean {
|
||||
const planState = readFinalWavePlanState(input.planPath)
|
||||
if (!planState) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (planState.pendingImplementationTaskCount > 0 || planState.pendingFinalWaveTaskCount === 0) {
|
||||
clearFinalWaveApprovalTracking(input.sessionState)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!APPROVE_VERDICT_PATTERN.test(input.taskOutput)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!existsSync(input.planPath)) {
|
||||
return false
|
||||
if (planState.pendingFinalWaveTaskCount === 1) {
|
||||
clearFinalWaveApprovalTracking(input.sessionState)
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
const content = readFileSync(input.planPath, "utf-8")
|
||||
const lines = content.split(/\r?\n/)
|
||||
let inFinalVerificationWave = false
|
||||
let uncheckedTaskCount = 0
|
||||
let uncheckedFinalWaveTaskCount = 0
|
||||
|
||||
for (const line of lines) {
|
||||
if (/^##\s+/.test(line)) {
|
||||
inFinalVerificationWave = FINAL_VERIFICATION_HEADING_PATTERN.test(line)
|
||||
}
|
||||
|
||||
const uncheckedTaskMatch = line.match(UNCHECKED_TASK_PATTERN)
|
||||
if (!uncheckedTaskMatch) {
|
||||
continue
|
||||
}
|
||||
|
||||
uncheckedTaskCount += 1
|
||||
if (inFinalVerificationWave && FINAL_WAVE_TASK_PATTERN.test(uncheckedTaskMatch[1].trim())) {
|
||||
uncheckedFinalWaveTaskCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
return uncheckedTaskCount === 1 && uncheckedFinalWaveTaskCount === 1
|
||||
} catch {
|
||||
return false
|
||||
if (input.sessionState.pendingFinalWaveTaskCount !== planState.pendingFinalWaveTaskCount) {
|
||||
input.sessionState.pendingFinalWaveTaskCount = planState.pendingFinalWaveTaskCount
|
||||
input.sessionState.approvedFinalWaveTaskCount = 0
|
||||
}
|
||||
|
||||
input.sessionState.approvedFinalWaveTaskCount = (input.sessionState.approvedFinalWaveTaskCount ?? 0) + 1
|
||||
const shouldPause = input.sessionState.approvedFinalWaveTaskCount >= planState.pendingFinalWaveTaskCount
|
||||
if (shouldPause) {
|
||||
clearFinalWaveApprovalTracking(input.sessionState)
|
||||
}
|
||||
|
||||
return shouldPause
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user