fix(#2349): exclude status: superseded plans from phase completion counts (#2404)

Adds a status: superseded plan-frontmatter marker that scanPhasePlans excludes from both plan and summary counts, so a phase with a deliberately-unexecuted plan no longer reads incomplete forever (the plan-level analogue of #1514). Includes all-superseded completion handling and a bounded, symlink-safe frontmatter read. Fixes #2349.
This commit is contained in:
Tom Boucher
2026-07-18 08:08:03 -04:00
committed by GitHub
parent 56a5c6404c
commit 13d181aedf
4 changed files with 248 additions and 3 deletions

View File

@@ -1873,6 +1873,164 @@ describe('scanPhasePlans — call-site parity on mixed fixture', () => {
assert.strictEqual(result.hasNestedPlans, true, 'plans/ dir exists with plans');
});
});
// ---------------------------------------------------------------------------
// #2349: plan-level supersession. A plan whose frontmatter declares
// `status: superseded` was deliberately reassigned / never executed and can
// never gain a matching SUMMARY. Like a retired phase (#1514, one level up) it
// must be excluded from BOTH the plan and summary counts, so the phase can still
// read 100% complete instead of being pinned below it forever. A plan WITHOUT
// the marker must be counted exactly as before (no over-exclusion).
// ---------------------------------------------------------------------------
describe('scanPhasePlans — superseded plans (#2349)', () => {
const SUPERSEDED_FM =
'---\nphase: "1"\nplan: "6"\ntype: implementation\nstatus: superseded\n---\n\n# Superseded plan\n';
function writePlan(dir, name, body) {
fs.writeFileSync(path.join(dir, name), body);
}
test('a plan marked status: superseded is excluded from planCount and planFiles', () => {
const dir = phaseDir();
touch(dir, '01-01-PLAN.md', '01-01-SUMMARY.md');
writePlan(dir, '01-06-PLAN.md', SUPERSEDED_FM);
const result = scanPhasePlans(dir);
assert.strictEqual(result.planCount, 1, 'superseded plan not counted in planCount');
assert.strictEqual(result.summaryCount, 1, 'summaryCount honest at 1');
assert.strictEqual(result.completed, true, 'phase completes despite the summary-less superseded plan');
assert.ok(!result.planFiles.includes('01-06-PLAN.md'), 'superseded plan absent from planFiles');
assert.ok(result.planFiles.includes('01-01-PLAN.md'), 'live plan still present');
});
test('reported case: 13 plans, 2 superseded, 11 summaries → 11/11 completed', () => {
const dir = phaseDir();
// 11 executed plans, each with its matching summary
for (let i = 1; i <= 11; i++) {
const n = String(i).padStart(2, '0');
touch(dir, `05-${n}-PLAN.md`, `05-${n}-SUMMARY.md`);
}
// 2 superseded plans, no summaries (work reassigned to later plans)
writePlan(dir, '05-12-PLAN.md', SUPERSEDED_FM);
writePlan(dir, '05-13-PLAN.md', SUPERSEDED_FM);
const result = scanPhasePlans(dir);
assert.strictEqual(result.planCount, 11, 'denominator excludes the 2 superseded plans');
assert.strictEqual(result.summaryCount, 11, 'numerator honest at 11');
assert.strictEqual(result.completed, true, 'phase reads complete — no longer pinned below 100%');
});
test('boundary: a live unsummarized plan still blocks completion (limit+1)', () => {
const dir = phaseDir();
touch(dir, '02-01-PLAN.md', '02-01-SUMMARY.md');
touch(dir, '02-02-PLAN.md'); // live plan, no summary → must still block
writePlan(dir, '02-09-PLAN.md', SUPERSEDED_FM); // superseded, excluded
const result = scanPhasePlans(dir);
assert.strictEqual(result.planCount, 2, 'only the superseded plan is excluded');
assert.strictEqual(result.summaryCount, 1);
assert.strictEqual(result.completed, false, 'a live unsummarized plan still blocks completion');
});
test('no over-exclusion: a plan without the marker (or with a non-superseded status) is counted', () => {
const dir = phaseDir();
touch(dir, '03-01-PLAN.md'); // empty file, no frontmatter at all
writePlan(dir, '03-02-PLAN.md', '---\nphase: "3"\nplan: "2"\nstatus: complete\n---\n\n# done\n');
const result = scanPhasePlans(dir);
assert.strictEqual(result.planCount, 2, 'only status: superseded is excluded; other/absent statuses count');
assert.ok(result.planFiles.includes('03-01-PLAN.md'));
assert.ok(result.planFiles.includes('03-02-PLAN.md'));
});
test('superseded marker is case-insensitive and whitespace-tolerant', () => {
const dir = phaseDir();
touch(dir, '04-01-PLAN.md', '04-01-SUMMARY.md');
writePlan(dir, '04-07-PLAN.md', '---\nphase: "4"\nplan: "7"\nstatus: Superseded \n---\n\n# x\n');
const result = scanPhasePlans(dir);
assert.strictEqual(result.planCount, 1, 'Superseded (mixed-case, padded) still excluded');
assert.strictEqual(result.completed, true);
});
test('nested layout: a superseded nested plan is excluded too', () => {
const dir = phaseDir();
const plansDir = path.join(dir, 'plans');
fs.mkdirSync(plansDir);
touch(plansDir, 'PLAN-01-setup.md', 'SUMMARY-01-setup.md');
writePlan(plansDir, 'PLAN-02-dropped.md', SUPERSEDED_FM);
const result = scanPhasePlans(dir);
assert.strictEqual(result.planCount, 1, 'nested superseded plan excluded');
assert.strictEqual(result.summaryCount, 1);
assert.strictEqual(result.completed, true);
assert.ok(!result.planFiles.includes('plans/PLAN-02-dropped.md'));
});
test('fail-safe: an unreadable plan path (a directory named *-PLAN.md) is counted, not treated as superseded', () => {
const dir = phaseDir();
touch(dir, '06-01-PLAN.md', '06-01-SUMMARY.md');
// A *directory* whose name matches a plan file: readdir lists it, but reading
// it as a file throws EISDIR on every platform (root-independent, leak-safe).
// The read-failure path must fail safe to "not superseded" (counted) — never
// silently drop a plan whose frontmatter simply could not be read.
fs.mkdirSync(path.join(dir, '06-02-PLAN.md'));
const result = scanPhasePlans(dir);
assert.strictEqual(result.planCount, 2, 'unreadable plan still counted (fail-safe, not excluded)');
assert.strictEqual(result.completed, false, 'the unreadable plan has no summary → still blocks');
});
test('all plans superseded → phase reads complete, not pinned below 100% (0 >= 0)', () => {
const dir = phaseDir();
// Every plan in the phase was reassigned elsewhere: nothing left to execute.
writePlan(dir, '08-01-PLAN.md', SUPERSEDED_FM);
writePlan(dir, '08-02-PLAN.md', SUPERSEDED_FM);
const result = scanPhasePlans(dir);
assert.strictEqual(result.planCount, 0, 'all plans excluded');
assert.strictEqual(result.summaryCount, 0);
assert.strictEqual(result.completed, true, 'a fully-superseded phase has no remaining work → complete');
});
test('a genuinely empty phase (no plans authored) still reads NOT complete', () => {
// Regression guard for the all-superseded fix: the completion guard keys off
// whether any plans existed on disk, so a phase with zero plans is unaffected.
const result = scanPhasePlans(phaseDir());
assert.strictEqual(result.planCount, 0);
assert.strictEqual(result.completed, false, 'no plans authored → not complete (unchanged)');
});
test('a superseded marker is detected even when the plan body is very large', () => {
// Frontmatter sits at byte 0; the scan reads only a bounded prefix, so a large
// body must neither hide the marker nor force reading the whole file.
const dir = phaseDir();
touch(dir, '09-01-PLAN.md', '09-01-SUMMARY.md');
const bigBody = 'x'.repeat(200 * 1024); // 200 KB body, well past the read cap
writePlan(dir, '09-09-PLAN.md', `---\nphase: "9"\nplan: "9"\nstatus: superseded\n---\n\n${bigBody}\n`);
const result = scanPhasePlans(dir);
assert.strictEqual(result.planCount, 1, 'superseded plan with a large body still excluded');
assert.strictEqual(result.completed, true);
});
test('property: K superseded plans never change the completed verdict of N summarized plans', () => {
const fc = require('fast-check');
fc.assert(
fc.property(fc.integer({ min: 1, max: 8 }), fc.integer({ min: 0, max: 5 }), (n, k) => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-plan-scan-prop-'));
try {
for (let i = 1; i <= n; i++) {
const idx = String(i).padStart(2, '0');
fs.writeFileSync(path.join(dir, `07-${idx}-PLAN.md`), '');
fs.writeFileSync(path.join(dir, `07-${idx}-SUMMARY.md`), '');
}
for (let j = 1; j <= k; j++) {
fs.writeFileSync(path.join(dir, `07-${String(50 + j)}-PLAN.md`), SUPERSEDED_FM);
}
const result = scanPhasePlans(dir);
assert.strictEqual(result.planCount, n, 'planCount ignores the K superseded plans');
assert.strictEqual(result.completed, true, 'N fully-summarized plans stay complete regardless of K');
} finally {
cleanup(dir);
}
}),
{ numRuns: 40 },
);
});
});
});
}