* 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)
127 lines
5.4 KiB
JavaScript
127 lines
5.4 KiB
JavaScript
'use strict';
|
|
|
|
// Regression + projection coverage for the workstream-inventory module.
|
|
// #1913: status must be derived from authoritative shipped signals (milestone
|
|
// archive snapshot / ROADMAP SHIPPED marker), not trusted from the mutable
|
|
// STATE.md `Status` field.
|
|
|
|
const { describe, test, before, after } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { cleanup } = require('./helpers.cjs');
|
|
const { createFixture, seedWorkstream } = require('./fixtures/index.cjs');
|
|
const { buildWorkstreamInventory, isCompletedInventory } = require('../gsd-core/bin/lib/workstream-inventory-builder.cjs');
|
|
const { inspectWorkstream } = require('../gsd-core/bin/lib/workstream-inventory.cjs');
|
|
|
|
const STALE_STATE = 'status: executing\n';
|
|
const IN_PROGRESS_ROADMAP =
|
|
'# Roadmap\n## Milestones\n- v2.0 Test — IN PROGRESS\n## Phases\n### Phase 1: Foo\n**Goal:** foo\n';
|
|
|
|
describe('#1913 — workstream status derived from authoritative shipped signals', () => {
|
|
let tmpDir;
|
|
before(() => { tmpDir = createFixture(); });
|
|
after(() => cleanup(tmpDir));
|
|
|
|
test('builder: milestoneShipped overrides a stale executing field (derived + conflict)', () => {
|
|
const inv = buildWorkstreamInventory({
|
|
name: 'ws-a',
|
|
projectDir: tmpDir,
|
|
workstreamDir: path.join(tmpDir, '.planning', 'workstreams', 'ws-a'),
|
|
phaseDirNames: [],
|
|
activeWorkstreamName: '',
|
|
phaseFilesCounts: [],
|
|
roadmapPhaseCount: 0,
|
|
stateProjection: { status: 'executing', current_phase: null, last_activity: null },
|
|
filesExist: { roadmap: true, state: true, requirements: true },
|
|
milestoneShipped: true,
|
|
});
|
|
assert.equal(inv.status, 'milestone complete');
|
|
assert.equal(inv.status_source, 'derived');
|
|
assert.equal(inv.status_conflict, true);
|
|
});
|
|
|
|
test('builder: no shipped signal → field status, no conflict', () => {
|
|
const inv = buildWorkstreamInventory({
|
|
name: 'ws-b',
|
|
projectDir: tmpDir,
|
|
workstreamDir: path.join(tmpDir, '.planning', 'workstreams', 'ws-b'),
|
|
phaseDirNames: [],
|
|
activeWorkstreamName: '',
|
|
phaseFilesCounts: [],
|
|
roadmapPhaseCount: 0,
|
|
stateProjection: { status: 'executing', current_phase: null, last_activity: null },
|
|
filesExist: { roadmap: true, state: true, requirements: true },
|
|
milestoneShipped: false,
|
|
});
|
|
assert.equal(inv.status, 'executing');
|
|
assert.equal(inv.status_source, 'field');
|
|
assert.equal(inv.status_conflict, false);
|
|
});
|
|
|
|
test('inspectWorkstream: shipped archive snapshot + stale executing STATE → derived complete', () => {
|
|
const wsDir = seedWorkstream(tmpDir, { name: 'ws-archived' });
|
|
fs.writeFileSync(path.join(wsDir, 'STATE.md'), STALE_STATE);
|
|
fs.writeFileSync(path.join(wsDir, 'ROADMAP.md'), IN_PROGRESS_ROADMAP);
|
|
// Authoritative shipped signal: an archived milestone snapshot.
|
|
fs.mkdirSync(path.join(wsDir, 'milestones'), { recursive: true });
|
|
fs.writeFileSync(path.join(wsDir, 'milestones', 'v1.0-ROADMAP.md'), '# v1.0 archived\n');
|
|
|
|
const inv = inspectWorkstream(tmpDir, 'ws-archived', { active: null });
|
|
assert.ok(inv, 'inventory should be produced');
|
|
assert.equal(inv.status, 'milestone complete');
|
|
assert.equal(inv.status_source, 'derived');
|
|
assert.equal(inv.status_conflict, true);
|
|
});
|
|
|
|
test('inspectWorkstream: ROADMAP SHIPPED marker + stale executing STATE → derived complete', () => {
|
|
const wsDir = seedWorkstream(tmpDir, { name: 'ws-shipped' });
|
|
fs.writeFileSync(path.join(wsDir, 'STATE.md'), STALE_STATE);
|
|
fs.writeFileSync(
|
|
path.join(wsDir, 'ROADMAP.md'),
|
|
'# Roadmap\n## Milestones\n<details><summary>✅ v1.0 MVP - SHIPPED 2026-06-01</summary>\n## Phases\n### Phase 1: Foo\n**Goal:** foo\n'
|
|
);
|
|
|
|
const inv = inspectWorkstream(tmpDir, 'ws-shipped', { active: null });
|
|
assert.ok(inv, 'inventory should be produced');
|
|
assert.equal(inv.status, 'milestone complete');
|
|
assert.equal(inv.status_source, 'derived');
|
|
assert.equal(inv.status_conflict, true);
|
|
});
|
|
|
|
test('inspectWorkstream: no shipped signals + executing STATE → field status, no conflict', () => {
|
|
const wsDir = seedWorkstream(tmpDir, { name: 'ws-active' });
|
|
fs.writeFileSync(path.join(wsDir, 'STATE.md'), STALE_STATE);
|
|
fs.writeFileSync(path.join(wsDir, 'ROADMAP.md'), IN_PROGRESS_ROADMAP);
|
|
|
|
const inv = inspectWorkstream(tmpDir, 'ws-active', { active: null });
|
|
assert.ok(inv, 'inventory should be produced');
|
|
assert.equal(inv.status, 'executing');
|
|
assert.equal(inv.status_source, 'field');
|
|
assert.equal(inv.status_conflict, false);
|
|
});
|
|
});
|
|
|
|
describe('isCompletedInventory — ADR-2207 status lifecycle', () => {
|
|
test('terminal "milestone complete" variants are completed', () => {
|
|
assert.ok(isCompletedInventory('1.0 milestone complete'));
|
|
assert.ok(isCompletedInventory('Milestone complete'));
|
|
assert.ok(isCompletedInventory('milestone complete'));
|
|
});
|
|
|
|
test('intermediate "All phases complete" is NOT completed (ADR-2207)', () => {
|
|
assert.ok(!isCompletedInventory('All phases complete'),
|
|
'All phases complete is an intermediate state — milestone not yet formally closed');
|
|
});
|
|
|
|
test('archived is completed', () => {
|
|
assert.ok(isCompletedInventory('archived'));
|
|
});
|
|
|
|
test('active statuses are NOT completed', () => {
|
|
assert.ok(!isCompletedInventory('Ready to plan'));
|
|
assert.ok(!isCompletedInventory('In progress'));
|
|
assert.ok(!isCompletedInventory('Executing'));
|
|
});
|
|
});
|