/** Result of a convergence check. */
interface ConvergenceResult {
- /** Best SHA to restore (empty string = no update). */
- bestSha: string
+ /** Best SHA to restore (null = no update). */
+ bestSha: null | string
/** Updated last findings. */
lastFindings: Finding[]
/** New loop status. */
const ctx: LoopContext = { baseBranch, sandbox, signal, spec, strategy }
const seenKeys = new Set<string>()
+ let failureReason: string | undefined
let lastFindings: Finding[] = []
let status: LoopStatus = 'exhausted'
let totalCommits = 0
let roundsCompleted = 0
let previousFindingsCount = Infinity
- let bestSha = ''
+ let bestSha: null | string = null
let bestFindingsCount = Infinity
for (let round = 1; round <= maxRounds; round++) {
if (earlyExit !== null) {
totalCommits = earlyExit.totalCommits
status = earlyExit.status
+ if (earlyExit.status === 'failed') {
+ failureReason = result.commits === 0 ? 'actor_error' : 'critic_parse_failed'
+ }
break
}
previousFindingsCount
)
) {
+ failureReason = 'quality_regression'
status = 'exhausted'
break
}
totalCommits = await resetToBestState(sandbox.worktreePath, bestSha, totalCommits, baseBranch)
}
- return { baseBranch, lastFindings, roundsCompleted, status, totalCommits }
+ return { baseBranch, failureReason, lastFindings, roundsCompleted, status, totalCommits }
}
/**
* @param cwd - Working directory for git operations.
* @returns The HEAD SHA or empty string.
*/
-async function captureHeadSha (cwd: string): Promise<string> {
+async function captureHeadSha (cwd: string): Promise<null | string> {
try {
const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd })
return stdout.trim()
} catch {
- return ''
+ return null
}
}
}
return {
- bestSha: '',
+ bestSha: null,
lastFindings: nonLowFindings.length > 0 ? nonLowFindings : [],
status: 'converged',
}
.digest('hex')
.slice(0, HASH_PREFIX_LENGTH)
} catch {
+ console.debug(` hashContextLines: fallback for ${file}:${String(line)}`)
return crypto
.createHash('sha256')
.update(`${file}:${String(line)}:fallback`)
*/
async function resetToBestState (
cwd: string,
- bestSha: string,
+ bestSha: null | string,
currentCommits: number,
baseBranch: string
): Promise<number> {
+ if (bestSha === null) return currentCommits
if (!/^[0-9a-f]{40}$/.test(bestSha)) return currentCommits
try {
await execFileAsync('git', ['reset', '--hard', bestSha], { cwd })
* @param bestSha - Best intermediate SHA (empty string if none captured).
* @returns True if reset should be applied.
*/
-function shouldResetToBest (status: LoopStatus, bestSha: string): boolean {
- return status !== 'converged' && /^[0-9a-f]{40}$/.test(bestSha)
+function shouldResetToBest (status: LoopStatus, bestSha: null | string): boolean {
+ return status !== 'converged' && bestSha !== null && /^[0-9a-f]{40}$/.test(bestSha)
}