* fix(#2022): gate roadmap update-plan-progress checkbox on verification passed cmdRoadmapUpdatePlanProgress stamped the phase checkbox + completion date the moment all summaries landed, with NO verification gate — unlike cmdPhaseComplete (phase.cts:1436) which checks readVerificationStatus. Since update-plan-progress is called after every wave and every plan, the checkbox fired before gsd-verifier confirmed the phase. - src/roadmap.cts: isComplete now requires summaryCount >= planCount AND readVerificationStatus(phaseDir).status === 'passed'. - tests/roadmap.test.cjs: 2 regression tests (no VERIFICATION.md → not complete; gaps_found → not complete) + updated 3 existing complete tests to include a passed VERIFICATION.md. Closes #2022 * docs(#2022): backfill changeset pr 2030
This commit is contained in:
5
.changeset/2022-roadmap-verify-gate.md
Normal file
5
.changeset/2022-roadmap-verify-gate.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2030
|
||||
---
|
||||
**`roadmap update-plan-progress` no longer checks the phase checkbox without verification** — the command stamped the phase-level ROADMAP checkbox and completion date the moment the last plan summary landed (called routinely after every wave and every plan), with **no verification gate** — unlike `phase.complete` which correctly requires `readVerificationStatus(...).status === 'passed'`. Now `isComplete` requires both all plan summaries AND a passed verification, matching the `cmdPhaseComplete` contract, so the checkbox only fires after `gsd-verifier` has confirmed the phase. (#2022)
|
||||
@@ -34,6 +34,9 @@ const { countMatchedSummaries } = coreUtils;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import frontmatter = require('./frontmatter.cjs');
|
||||
const { extractFrontmatter, parseMustHavesBlock } = frontmatter;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import verificationMod = require('./verification.cjs');
|
||||
const { readVerificationStatus } = verificationMod;
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -504,7 +507,13 @@ function cmdRoadmapUpdatePlanProgress(cwd: string, phaseNum: string | null | und
|
||||
return;
|
||||
}
|
||||
|
||||
const isComplete = summaryCount >= planCount;
|
||||
// Verification gate (#2022): do NOT check the phase checkbox or stamp a
|
||||
// completion date until the phase's verification status is 'passed', matching
|
||||
// cmdPhaseComplete's gate (phase.cts:1436). Previously the checkbox fired the
|
||||
// moment the last plan summary landed — before gsd-verifier had verified.
|
||||
const phaseDir = path.join(cwd, phaseInfo!.directory);
|
||||
const verificationPassed = readVerificationStatus(phaseDir).status === 'passed';
|
||||
const isComplete = summaryCount >= planCount && verificationPassed;
|
||||
const status = isComplete ? 'Complete' : summaryCount > 0 ? 'In Progress' : 'Planned';
|
||||
const today = realClock.today();
|
||||
|
||||
|
||||
@@ -814,6 +814,80 @@ describe('roadmap update-plan-progress command', () => {
|
||||
assert.ok(!/\[x\] \*\*Phase 30/.test(roadmapContent), 'phase checkbox must not be checked');
|
||||
});
|
||||
|
||||
test('#2022 — all summaries present but verification NOT passed → checkbox NOT checked', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap
|
||||
|
||||
- [ ] **Phase 1: Test** - description
|
||||
|
||||
### Phase 1: Test
|
||||
**Goal:** Test goal
|
||||
**Plans:** TBD
|
||||
|
||||
## Progress
|
||||
|
||||
| Phase | Milestone | Plans Complete | Status | Completed |
|
||||
|-------|-----------|----------------|--------|-----------|
|
||||
| 1. Test | v1.0 | 0/1 | Planned | - |
|
||||
`
|
||||
);
|
||||
|
||||
// 1 plan + 1 summary (all summaries present) but NO VERIFICATION.md → the
|
||||
// verification gate (#2022) must prevent the checkbox from being checked.
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-test');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan 1');
|
||||
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary 1');
|
||||
|
||||
const result = runGsdTools('roadmap update-plan-progress 1', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.complete, false, 'must NOT be complete without verification');
|
||||
assert.strictEqual(output.status, 'In Progress', 'status should be In Progress (not Complete)');
|
||||
|
||||
const roadmapContent = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
|
||||
assert.ok(roadmapContent.includes('[ ] **Phase 1'), 'phase checkbox must remain unchecked');
|
||||
assert.ok(!/\[x\] \*\*Phase 1/.test(roadmapContent), 'phase checkbox must NOT be checked without verification');
|
||||
assert.ok(roadmapContent.includes('1/1'), 'plan count should still be updated');
|
||||
});
|
||||
|
||||
test('#2022 — verification status NOT passed → checkbox NOT checked', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap
|
||||
|
||||
- [ ] **Phase 1: Test**
|
||||
|
||||
### Phase 1: Test
|
||||
**Goal:** Test goal
|
||||
|
||||
## Progress
|
||||
|
||||
| Phase | Milestone | Plans Complete | Status | Completed |
|
||||
|-------|-----------|----------------|--------|-----------|
|
||||
| 1. Test | v1.0 | 0/1 | Planned | - |
|
||||
`
|
||||
);
|
||||
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-test');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan 1');
|
||||
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary 1');
|
||||
// Verification exists but status is gaps_found (not passed)
|
||||
fs.writeFileSync(path.join(p1, '01-VERIFICATION.md'), '---\nstatus: gaps_found\n---\n# Verification\n');
|
||||
|
||||
const result = runGsdTools('roadmap update-plan-progress 1', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.complete, false, 'must NOT be complete (verification gaps_found)');
|
||||
assert.strictEqual(output.status, 'In Progress');
|
||||
const roadmapContent = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
|
||||
assert.ok(!roadmapContent.includes('[x]'), 'checkbox must NOT be checked (verification not passed)');
|
||||
});
|
||||
|
||||
test('updates progress and checks checkbox on completion', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
@@ -833,11 +907,12 @@ describe('roadmap update-plan-progress command', () => {
|
||||
`
|
||||
);
|
||||
|
||||
// Create phase dir with 1 plan, 1 summary (complete)
|
||||
// Create phase dir with 1 plan, 1 summary (complete) + verification passed (#2022 gate)
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-test');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan 1');
|
||||
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary 1');
|
||||
fs.writeFileSync(path.join(p1, '01-VERIFICATION.md'), '---\nstatus: passed\n---\n# Verification\n');
|
||||
|
||||
const result = runGsdTools('roadmap update-plan-progress 1', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
@@ -878,6 +953,7 @@ describe('roadmap update-plan-progress command', () => {
|
||||
fs.mkdirSync(p3, { recursive: true });
|
||||
fs.writeFileSync(path.join(p3, '03-01-PLAN.md'), '# Plan 1');
|
||||
fs.writeFileSync(path.join(p3, '03-01-SUMMARY.md'), '# Summary 1');
|
||||
fs.writeFileSync(path.join(p3, '03-VERIFICATION.md'), '---\nstatus: passed\n---\n# Verification\n');
|
||||
|
||||
const result = runGsdTools('roadmap update-plan-progress 03', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
@@ -959,6 +1035,7 @@ describe('roadmap update-plan-progress command', () => {
|
||||
fs.mkdirSync(p50, { recursive: true });
|
||||
fs.writeFileSync(path.join(p50, '50-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(path.join(p50, '50-01-SUMMARY.md'), '# Summary');
|
||||
fs.writeFileSync(path.join(p50, '50-VERIFICATION.md'), '---\nstatus: passed\n---\n# Verification\n');
|
||||
|
||||
const result = runGsdTools('roadmap update-plan-progress 50', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
Reference in New Issue
Block a user