Files
msd-core/tests/partial-init-routing.test.cjs
Tom Boucher 580059251a fix(#4040): route partially-created .planning to initialization recovery (#4283)
* test(#4040): add failing-first regression tests for partial-init routing

Red: init.progress/init.resume/init.new-project payloads carry no
partial-init discriminator, and progress.md/resume-project.md/
new-project.md route an interrupted bootstrap (.planning/PROJECT.md +
config.json only) to Route F / STATE reconstruction / a hard error.

* fix(#4040): route partially-created .planning to initialization recovery

A bootstrap interrupted after .planning/PROJECT.md (but before
REQUIREMENTS.md/ROADMAP.md/STATE.md) was mis-routed three ways:
progress.md read it as between-milestones (Route F) or 'no planning
structure', resume-project.md offered STATE.md reconstruction, and
new-project.md errored 'already initialized' — a routing loop with no
recovery exit.

Add a shared buildInitCompletenessFields discriminator
(planning_exists / requirements_exists / milestones_exists /
init_incomplete) to the init.progress, init.resume and init.new-project
payloads, and branch on init_incomplete in progress.md, resume-project.md
and new-project.md BEFORE the legacy branches. MILESTONES.md presence
excludes the archival between-milestones state, so Route F and the
STATE-reconstruction path keep working.

Emitted-Drift-Ack-Growth: progress.md — deliberate #4040 growth: new init_incomplete recovery branch (routing text + guard on the no-planning and Route F branches) added ahead of the legacy init_context routes.
Emitted-Drift-Ack-Growth: resume-project.md — deliberate #4040 growth: new init_incomplete branch routing an interrupted bootstrap to initialization recovery before the STATE.md-reconstruction branch.
Emitted-Drift-Ack-Growth: new-project.md — deliberate #4040 growth: project_exists gate split on init_incomplete so a partial bootstrap resumes initialization instead of erroring.

* chore(#4040): add changeset fragment

* chore(#4040): backfill PR number in changeset fragment

---------

Co-authored-by: sim <sim@local>
2026-09-04 16:03:42 -04:00

96 lines
4.5 KiB
JavaScript

// allow-test-rule: source-text-is-the-product (see #4040)
// Workflow .md files — their text IS what the runtime loads. Testing text
// content tests the deployed contract. Per CONTRIBUTING.md exception matrix.
/**
* GSD Tools Tests - #4040 partial-init routing contract
*
* Validates that the progress / resume-project / new-project workflows route
* an interrupted bootstrap (`init_incomplete` from the init.progress /
* init.resume payload) to initialization RECOVERY, and that this branch runs
* BEFORE the branches that mis-classified the state pre-fix:
* - progress.md Route F ("between milestones": PROJECT.md present,
* ROADMAP.md missing)
* - progress.md "no planning structure found" (project_exists=false while
* .planning/ exists)
* - resume-project.md "reconstruct STATE.md"
* - new-project.md "project already initialized" error
*
* Closes: #4040
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
describe('#4040 partial-init routing', () => {
const workflowsDir = path.join(__dirname, '..', 'gsd-core', 'workflows');
const progressMd = () => fs.readFileSync(path.join(workflowsDir, 'progress.md'), 'utf8');
const resumeMd = () => fs.readFileSync(path.join(workflowsDir, 'resume-project.md'), 'utf8');
const newProjectMd = () => fs.readFileSync(path.join(workflowsDir, 'new-project.md'), 'utf8');
test('progress.md routes init_incomplete to initialization recovery before Route F', () => {
const content = progressMd();
const recoveryIdx = content.indexOf('init_incomplete');
assert.ok(recoveryIdx > -1, 'progress.md must branch on init_incomplete');
const routeFIdx = content.indexOf('Route F: Between milestones');
assert.ok(routeFIdx > -1, 'Route F definition must exist');
assert.ok(
recoveryIdx < routeFIdx,
'the init_incomplete recovery branch must appear before the Route F (between milestones) definition'
);
const recoverySlice = content.slice(recoveryIdx, recoveryIdx + 1200);
assert.ok(
/REQUIREMENTS\.md/.test(recoverySlice) && /ROADMAP\.md/.test(recoverySlice) && /STATE\.md/.test(recoverySlice),
'the recovery branch must name the missing REQUIREMENTS.md / ROADMAP.md / STATE.md artifacts'
);
assert.ok(
recoverySlice.includes('new-project'),
'the recovery branch must route to /gsd:new-project to resume initialization'
);
});
test('progress.md init_context no-planning branch is gated on planning_exists', () => {
const content = progressMd();
const noPlanningIdx = content.indexOf('No planning structure found');
assert.ok(noPlanningIdx > -1, 'no-planning branch must exist');
const branchSlice = content.slice(Math.max(0, noPlanningIdx - 600), noPlanningIdx);
assert.ok(
branchSlice.includes('planning_exists'),
'the no-planning branch must check planning_exists so an existing-but-partial .planning/ is not reported as absent'
);
});
test('resume-project.md routes init_incomplete to initialization recovery before STATE reconstruction', () => {
const content = resumeMd();
const recoveryIdx = content.indexOf('init_incomplete');
assert.ok(recoveryIdx > -1, 'resume-project.md must branch on init_incomplete');
const reconstructIdx = content.indexOf('Offer to reconstruct STATE.md');
assert.ok(reconstructIdx > -1, 'STATE reconstruction branch must exist');
assert.ok(
recoveryIdx < reconstructIdx,
'the init_incomplete recovery branch must appear before the reconstruct-STATE.md branch'
);
const parseIdx = content.indexOf('Parse JSON for');
const parseSlice = content.slice(parseIdx, parseIdx + 400);
assert.ok(
parseSlice.includes('init_incomplete'),
'the resume parse list must include init_incomplete (payload is the source of truth)'
);
});
test('new-project.md resumes initialization when init_incomplete instead of erroring', () => {
const content = newProjectMd();
const gateIdx = content.indexOf('project_already');
const gateText = 'If `project_exists` is true';
const gate = gateIdx > -1 ? gateIdx : content.indexOf(gateText);
assert.ok(gate > -1, 'the project_exists gate must exist');
const gateSlice = content.slice(gate, gate + 1500);
assert.ok(
gateSlice.includes('init_incomplete'),
'the project_exists gate must discriminate on init_incomplete (resume a partial bootstrap instead of erroring)'
);
});
});