fix(#2204): phase-completion writes 'All phases complete' per ADR-2207 (#2259)

* fix(#2204): phase-completion writes 'All phases complete' per ADR-2207

completePhaseCore was writing the overloaded bare 'Milestone complete' on the
last phase — the same string space the milestone-close verb owns for terminal
state. Per ADR-2207, phase-completion now writes the existing intermediate
value 'All phases complete' (already used in gsd2-import.cts). Milestone
termination ('<version> milestone complete' / 'Awaiting next milestone')
remains solely with milestoneCompleteCore.

Status lifecycle: Ready to plan → All phases complete → <version> milestone
complete → Awaiting next milestone.

Changes:
- src/state-transition.cts: completePhaseCore status value
- src/phase.cts: #2028 guard comment
- tests/state-transition.test.cjs: assertion + test name
- tests/phase.test.cjs: 8 assertion updates (positive + negative)
- tests/state.test.cjs: normalizeStateStatus test case + reset regex
- tests/workstream.test.cjs: fixture status to terminal value
- gsd-core/workflows/progress.md: Route D label
- gsd-core/workflows/transition.md: Route B label
- CONTEXT.md: Status lifecycle glossary entry (ADR-2207)
- .changeset/brave-geese-jump.md

* test(#2204): regenerate golden-install-parity fixtures + workflow-size baseline

Workflow file edits (progress.md, transition.md) changed install payload
hashes and pushed past the committed workflow-size baseline. Regenerated
all 17 golden-install-parity fixtures + claude-local via the standalone gen
script (which now also covers the local-scope claude layout). Updated
workflow-size-baseline.json and agent-size-baseline.json via size:baseline.

* fix(#2204): correct claude-local golden hashes + document gen-script limitation

The gen-script's claude-local generation produces macOS-specific hashes
incompatible with Linux CI (local-scope install embeds platform-varying
node-runner paths). Reverted to manual update using Linux FAILURES.md
+actual hashes for the 2 changed workflow files. Added explanatory
comment in the gen script.

* test(#2204): add isCompletedInventory coverage + clarify CONTEXT.md glossary

Addresses orthogonal code-review findings (Medium #1 + #2):
- Add isCompletedInventory test cases for ADR-2207 status lifecycle
  (terminal 'milestone complete' → true; intermediate 'All phases
  complete' → false; archived → true; active statuses → false)
- Clarify CONTEXT.md glossary: note that isCompletedInventory
  intentionally excludes the intermediate value

* docs: backfill changeset PR number (#2259)

* docs(#2204): add Status lifecycle table to state-md reference (ADR-2207)
This commit is contained in:
Tom Boucher
2026-07-14 14:47:03 -04:00
committed by GitHub
parent c1885df9e5
commit 8b70db343b
33 changed files with 862 additions and 77 deletions

View File

@@ -2940,7 +2940,7 @@ describe('phase complete command', () => {
assert.strictEqual(output.next_phase, null, 'no next phase');
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
assert.ok(state.includes('Milestone complete'), 'status should be milestone complete');
assert.ok(state.includes('All phases complete'), 'status should be All phases complete');
});
// #1591: when the active milestone's phase checklist is wrapped in a
@@ -3031,8 +3031,8 @@ describe('phase complete command', () => {
// still in progress.
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
assert.ok(
!/Milestone complete/i.test(state),
'a mid-milestone phase must not flip STATE.md to "Milestone complete" (#1591)',
!/Milestone complete|All phases complete/i.test(state),
'a mid-milestone phase must not flip STATE.md to milestone-complete (#1591)',
);
});
@@ -3101,8 +3101,8 @@ describe('phase complete command', () => {
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
assert.ok(
!/Milestone complete/i.test(state),
'a mid-milestone phase must not flip STATE.md to "Milestone complete" — bold checklist (#1591)',
!/Milestone complete|All phases complete/i.test(state),
'a mid-milestone phase must not flip STATE.md to milestone-complete — bold checklist (#1591)',
);
});
@@ -3182,8 +3182,8 @@ describe('phase complete command', () => {
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
assert.ok(
!/Milestone complete/i.test(state),
'a mid-milestone phase must not flip STATE.md to "Milestone complete" (#1752)',
!/Milestone complete|All phases complete/i.test(state),
'a mid-milestone phase must not flip STATE.md to milestone-complete (#1752)',
);
const tpMatch = state.match(/total_phases:\s*(\d+)/);
assert.ok(tpMatch, 'STATE.md must carry a total_phases value after phase.complete');
@@ -3747,7 +3747,7 @@ describe('phase complete command', () => {
assert.ok(result.success, `Command failed: ${result.error}`);
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
assert.ok(state.includes('Milestone complete'), 'plain Status field should be updated');
assert.ok(state.includes('All phases complete'), 'plain Status field should be updated');
assert.ok(state.includes('Not started'), 'plain Plan field should be updated');
// Verify compound format preserved
assert.ok(state.match(/Phase:.*of\s+1/), 'should preserve "of N" in compound Phase format');
@@ -4169,7 +4169,7 @@ describe('#2028 — phase complete milestone-end + workstream guard', () => {
// A complement phase numbered AFTER Phase 9 but executed first. Completing the
// numerically-highest phase must not read as milestone-end while a lower phase
// is still outstanding (the isLastPhase blocks only checked for HIGHER phases).
test('does NOT stamp "Milestone complete" when a lower-numbered phase is still outstanding', () => {
test('does NOT stamp "All phases complete" when a lower-numbered phase is still outstanding', () => {
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
`# Roadmap\n\n- [ ] Phase 9: Introspection\n- [ ] Phase 10: Complement\n\n### Phase 9: Introspection\n**Goal:** baseline\n\n### Phase 10: Complement\n**Goal:** complement\n`
@@ -4198,8 +4198,8 @@ describe('#2028 — phase complete milestone-end + workstream guard', () => {
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
assert.ok(
!/Milestone complete/i.test(state),
'STATE.md must NOT flip to "Milestone complete" while a lower phase is outstanding',
!/Milestone complete|All phases complete/i.test(state),
'STATE.md must NOT flip to milestone-complete while a lower phase is outstanding',
);
assert.ok(/Ready to plan/i.test(state), 'status should be "Ready to plan"');
assert.match(
@@ -4236,7 +4236,7 @@ describe('#2028 — phase complete milestone-end + workstream guard', () => {
const output = JSON.parse(result.output);
assert.strictEqual(output.is_last_phase, true, 'all lower phases complete → Phase 10 is milestone-end');
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
assert.ok(/Milestone complete/i.test(state), 'status should be "Milestone complete"');
assert.ok(/All phases complete/i.test(state), 'status should be "All phases complete"');
});
// The lower-phase scan must not treat an unrelated checklist line that merely
@@ -4318,9 +4318,9 @@ describe('#2028 — phase complete milestone-end + workstream guard', () => {
rootState,
'root STATE.md must NOT be written when --ws targets a workstream',
);
// The workstream's own STATE.md advanced (single phase → milestone complete).
// The workstream's own STATE.md advanced (single phase → all phases complete).
const wsState = fs.readFileSync(path.join(wsDir, 'STATE.md'), 'utf-8');
assert.match(wsState, /Milestone complete/i, "the workstream's STATE.md should be the one updated");
assert.match(wsState, /All phases complete/i, "the workstream's STATE.md should be the one updated");
});
// The guard only fires in workstream mode — a flat project (no workstreams dir)