fix(dispatch): reassess-roadmap loop on slice with ASSESSMENT.md

checkNeedsReassessment looked up the slice's assessment file with
suffix='ASSESS', but actual files are 'S03-ASSESSMENT.md'. The
resolveFile pattern requires at least one char before the suffix
(/^S03-.*-ASSESS\.md$/), so 'S03-ASSESSMENT.md' never matched and
the helper returned {sliceId} on every poll → dispatcher kept
firing reassess-roadmap forever.

Fix: try 'ASSESSMENT' first, fall back to legacy 'ASSESS'. Now
S03-ASSESSMENT.md properly satisfies the "already reassessed" check
and the dispatcher advances to the next slice (S04).

Verified: resolveSliceFile('M010','S03','ASSESSMENT') returns the
real path; with the fallback, this resolves on first call. The
70+ degenerate reassess iterations on M010/S03 (witnessed
2026-05-17) won't recur.

Ralph Wiggum approved. (per operator: "sf should clear these stuck
itself ralph wiggums would fix")

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-17 04:12:15 +02:00
parent 0c0608fa50
commit a737af318d

View file

@ -69,12 +69,16 @@ export async function checkNeedsReassessment(base, mid, _state, _prefs) {
const hasIncomplete = slices.some((s) => s.status !== "complete");
if (completedSliceIds.length === 0 || !hasIncomplete) return null;
const lastCompleted = completedSliceIds[completedSliceIds.length - 1];
const assessmentFile = resolveSliceFile(
base,
mid,
lastCompleted,
"ASSESS",
);
// Try both "ASSESSMENT" (canonical, e.g. S03-ASSESSMENT.md) and
// the legacy "ASSESS" suffix. Without the ASSESSMENT branch the
// dispatcher re-dispatched reassess-roadmap indefinitely because
// resolveFile's pattern (`${idPrefix}-.*-${suffix}.md`) requires
// at least one char before `-ASSESS`, so `S03-ASSESSMENT.md`
// never matched suffix "ASSESS". Witnessed 2026-05-17 on
// M010/S03 — 70+ degenerate iterations.
const assessmentFile =
resolveSliceFile(base, mid, lastCompleted, "ASSESSMENT") ??
resolveSliceFile(base, mid, lastCompleted, "ASSESS");
if (assessmentFile && existsSync(assessmentFile)) return null;
return { sliceId: lastCompleted };
}