diff --git a/.changeset/graceful-foxes-dart.md b/.changeset/graceful-foxes-dart.md new file mode 100644 index 000000000..04d05fba8 --- /dev/null +++ b/.changeset/graceful-foxes-dart.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 4052 +--- +smart-entry classifies a STATE.md status of verified (or verification) as verify-pending instead of falling through to unknown — the classifier matched the exact word verify and missed the verif stem its own normalizeStateStatus uses (#3864) diff --git a/src/smart-entry.cts b/src/smart-entry.cts index 022fb2964..76405c120 100644 --- a/src/smart-entry.cts +++ b/src/smart-entry.cts @@ -599,7 +599,13 @@ export function classify(s: SmartEntrySignals): Situation { if (isComplete(s)) return 'complete'; if (/\bplanning|planned\b/i.test(s.status)) return 'planning'; if (/\bexecut(e|ing)|active|in.progress|building\b/i.test(s.status)) return 'executing'; - if (/\bverify|review|needs.review|pending.review\b/i.test(s.status)) return 'verify-pending'; + // #3864: `verif` (not `verify`) — the same stem state-document's + // normalizeStateStatus matches. "verified" and "verification" contain no + // "verify" substring, so the exact word left them falling through to + // `unknown` (or idle-stranded on a clean unpushed tree — differently + // wrong). verify_failed is tested above, so a failed verification still + // wins over this branch. + if (/\bverif|review|needs.review|pending.review\b/i.test(s.status)) return 'verify-pending'; if (isIdleStranded(s)) return 'idle-stranded'; return 'unknown'; } diff --git a/tests/smart-entry.unit.test.cjs b/tests/smart-entry.unit.test.cjs index 1d1f08a48..4fd13cb7b 100644 --- a/tests/smart-entry.unit.test.cjs +++ b/tests/smart-entry.unit.test.cjs @@ -114,6 +114,52 @@ describe('smart-entry: situation coverage', () => { }); } + // #3860-family guard is NOT wanted here; these are #3864's missing pins: + // the classifier's verify branch must match the same verif* stem that + // state-document's normalizeStateStatus matches, or a STATE.md declaring + // `status: verified` falls through to `unknown` (and, on a clean tree with + // unpushed commits, to idle-stranded — differently wrong). + test('#3864: status "verified" classifies as verify-pending', () => { + const dir = track(makeProject({ state: state({ status: 'verified', total_phases: 5, current_phase: 2 }), roadmap: true })); + const result = classifyProject(dir); + assert.equal(result.situation, 'verify-pending'); + }); + + test('#3864: status "verification" classifies as verify-pending', () => { + const dir = track(makeProject({ state: state({ status: 'verification', total_phases: 5, current_phase: 2 }), roadmap: true })); + const result = classifyProject(dir); + assert.equal(result.situation, 'verify-pending'); + }); + + test('#3864: the stem does not over-match — "completed" still reaches complete', () => { + const dir = track(makeProject({ state: state({ status: 'completed', total_phases: 5, current_phase: 5 }), roadmap: true })); + const result = classifyProject(dir); + assert.equal(result.situation, 'complete'); + }); + + test('#3864: "Phase complete — ready for verification" at 5/5 stays complete (complete beats a verif-bearing status)', () => { + // A real handler-written Status default (state-document.cts). It carries + // the verif stem but names COMPLETION — isComplete must win over the + // verify-pending branch when the project is actually done. + const dir = track(makeProject({ state: state({ status: 'Phase complete — ready for verification', total_phases: 5, current_phase: 5 }), roadmap: true })); + const result = classifyProject(dir); + assert.equal(result.situation, 'complete'); + }); + + test('#3864: "Phase complete — ready for verification" mid-project is verify-pending', () => { + // Same real status word, phases remaining: pre-fix this fell through to + // unknown/idle-stranded (no "verify" substring); the stem now routes it. + const dir = track(makeProject({ state: state({ status: 'Phase complete — ready for verification', total_phases: 5, current_phase: 2 }), roadmap: true })); + const result = classifyProject(dir); + assert.equal(result.situation, 'verify-pending'); + }); + + test('#3864: verify-failed still wins over the verify-pending stem (ordering pinned)', () => { + const dir = track(makeProject({ state: state({ status: 'verify-failed', total_phases: 5, current_phase: 2 }), roadmap: true, verifyFail: true })); + const result = classifyProject(dir); + assert.equal(result.situation, 'verify-failed'); + }); + test('SITUATIONS constant lists all 11 (incl unknown) and is frozen', () => { assert.equal(SITUATIONS.length, 11); assert.ok(SITUATIONS.includes('unknown'));