refactor: hard-fork GSD -> MSD (Make Software Done)

Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
This commit is contained in:
Jakub Zych
2026-10-06 01:47:40 +02:00
parent fe069b2a56
commit a9a7a328e6
2763 changed files with 78465 additions and 78434 deletions

View File

@@ -3,7 +3,7 @@
// runtime loads — testing text content tests the deployed contract.
/**
* GSD Tools Tests - Milestone
* MSD Tools Tests - Milestone
*
* Covers: milestone complete command, phases clear command,
* requirements mark-complete command (regex-global fix), new-milestone
@@ -16,7 +16,7 @@ const { test, describe, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { runGsdTools, createTempProject, cleanup, parseFrontmatter } = require('./helpers.cjs');
const { runMsdTools, createTempProject, cleanup, parseFrontmatter } = require('./helpers.cjs');
// ─── helpers ─────────────────────────────────────────────────────────────────
@@ -68,7 +68,7 @@ describe('milestone complete command', () => {
// No ROADMAP.md — mirrors 'handles missing ROADMAP.md gracefully' so the
// milestone-phase-filter guard never fires.
const result = runGsdTools('milestone complete v0.5 --name Test --confirm', tmpDir);
const result = runMsdTools('milestone complete v0.5 --name Test --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
@@ -91,7 +91,7 @@ describe('milestone complete command', () => {
writeState(tmpDir);
mkPhaseDir(tmpDir, '01-foundation', { oneLiner: 'Set up project infrastructure' });
const result = runGsdTools('milestone complete v1.0 --name MVP Foundation --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name MVP Foundation --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -128,7 +128,7 @@ describe('milestone complete command', () => {
// Capture pre-state
const stateBefore = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
const result = runGsdTools('milestone complete v1.0 --name Test --dry-run', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Test --dry-run', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -179,7 +179,7 @@ describe('milestone complete command', () => {
writeState(tmpDir);
mkPhaseDir(tmpDir, '01-foundation');
const result = runGsdTools('milestone complete v1.0 --dry-run --no-archive-phases', tmpDir);
const result = runMsdTools('milestone complete v1.0 --dry-run --no-archive-phases', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -195,7 +195,7 @@ describe('milestone complete command', () => {
// Capture pre-state
const stateBefore = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
const result = runGsdTools('milestone complete v1.0 --dry-run --force', tmpDir);
const result = runMsdTools('milestone complete v1.0 --dry-run --force', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -222,7 +222,7 @@ describe('milestone complete command', () => {
writeState(tmpDir);
mkPhaseDir(tmpDir, '01-foundation', { oneLiner: 'Set up project infrastructure' });
const result = runGsdTools(['milestone', 'complete', 'v1.0', '--name', 'Test', '--dry-run', '--raw'], tmpDir);
const result = runMsdTools(['milestone', 'complete', 'v1.0', '--name', 'Test', '--dry-run', '--raw'], tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
// Before the fix, output(dryRunResult, raw, 'dry-run') meant --raw discarded
@@ -249,7 +249,7 @@ describe('milestone complete command', () => {
writeRoadmap(tmpDir, `# Roadmap v1.0\n`);
writeState(tmpDir);
const result = runGsdTools('milestone complete v1.0 --name Beta --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Beta --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const milestones = fs.readFileSync(path.join(tmpDir, '.planning', 'MILESTONES.md'), 'utf-8');
@@ -266,9 +266,9 @@ describe('milestone complete command', () => {
writeRoadmap(tmpDir, `# Roadmap v1.1\n`);
writeState(tmpDir);
assert.ok(runGsdTools('milestone complete v1.1 --name Second --confirm', tmpDir).success);
assert.ok(runMsdTools('milestone complete v1.1 --name Second --confirm', tmpDir).success);
writeRoadmap(tmpDir, `# Roadmap v1.2\n`);
assert.ok(runGsdTools('milestone complete v1.2 --name Third --confirm', tmpDir).success);
assert.ok(runMsdTools('milestone complete v1.2 --name Third --confirm', tmpDir).success);
const m = fs.readFileSync(path.join(tmpDir, '.planning', 'MILESTONES.md'), 'utf-8');
const [i10, i11, i12] = ['v1.0 First', 'v1.1 Second', 'v1.2 Third'].map(s => m.indexOf(s));
@@ -282,7 +282,7 @@ describe('milestone complete command', () => {
writeState(tmpDir);
mkPhaseDir(tmpDir, '01-foundation', { oneLiner: 'Set up project infrastructure' });
const result = runGsdTools('milestone complete v1.0 --name MVP --archive-phases --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name MVP --archive-phases --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -299,7 +299,7 @@ describe('milestone complete command', () => {
writeRoadmap(tmpDir, `# Roadmap v1.0\n`);
writeState(tmpDir);
assert.ok(runGsdTools('milestone complete v1.0 --name MVP --confirm', tmpDir).success);
assert.ok(runMsdTools('milestone complete v1.0 --name MVP --confirm', tmpDir).success);
const archivedReq = fs.readFileSync(
path.join(tmpDir, '.planning', 'milestones', 'v1.0-REQUIREMENTS.md'), 'utf-8',
@@ -315,7 +315,7 @@ describe('milestone complete command', () => {
writeRoadmap(tmpDir, `# Roadmap v1.0\n`);
writeState(tmpDir);
const result = runGsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -330,37 +330,37 @@ describe('milestone complete command', () => {
writeRoadmap(tmpDir, `# Roadmap v1.0\n`);
fs.writeFileSync(
path.join(tmpDir, '.planning', 'STATE.md'),
`# State\n\n**Status:** In progress\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working\n\n## Current Position\n\nPhase: 03 — EXECUTING\nPlan: 03-02\nStatus: Executing\nLast activity: 2025-01-01 — Running phase\n\n## Operator Next Steps\n\n- Re-run /gsd:complete-milestone v1.0\n`,
`# State\n\n**Status:** In progress\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working\n\n## Current Position\n\nPhase: 03 — EXECUTING\nPlan: 03-02\nStatus: Executing\nLast activity: 2025-01-01 — Running phase\n\n## Operator Next Steps\n\n- Re-run /msd:complete-milestone v1.0\n`,
);
const result = runGsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
assert.ok(state.includes('Phase: Milestone v1.0 complete'));
assert.ok(state.includes('Status: Awaiting next milestone'));
assert.ok(!state.includes('Re-run /gsd:complete-milestone'));
assert.ok(state.includes('/gsd-new-milestone'));
assert.ok(!state.includes('Re-run /msd:complete-milestone'));
assert.ok(state.includes('/msd-new-milestone'));
});
test('appends canonical narrative sections when STATE.md headings are missing (#3088)', () => {
writeRoadmap(tmpDir, `# Roadmap v1.0\n`);
writeState(tmpDir);
const result = runGsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
assert.ok(state.includes('## Current Position'));
assert.ok(state.includes('Phase: Milestone v1.0 complete'));
assert.ok(state.includes('## Operator Next Steps'));
assert.ok(state.includes('/gsd-new-milestone'));
assert.ok(state.includes('/msd-new-milestone'));
});
test('handles missing ROADMAP.md gracefully', () => {
writeState(tmpDir);
const result = runGsdTools('milestone complete v1.0 --name NoRoadmap --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name NoRoadmap --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -387,7 +387,7 @@ describe('milestone complete command', () => {
fs.writeFileSync(path.join(p4, '04-02-PLAN.md'), '# Plan 2\n');
fs.writeFileSync(path.join(p4, '04-01-SUMMARY.md'), '---\none-liner: Polished UI\n---\n# Summary\n');
const result = runGsdTools('milestone complete v1.1 --name "Second Release" --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.1 --name "Second Release" --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -407,7 +407,7 @@ describe('milestone complete command', () => {
mkPhaseDir(tmpDir, '01-old', { plan: true });
mkPhaseDir(tmpDir, '02-current', { plan: true });
assert.ok(runGsdTools('milestone complete v1.1 --name Test --archive-phases --confirm', tmpDir).success);
assert.ok(runMsdTools('milestone complete v1.1 --name Test --archive-phases --confirm', tmpDir).success);
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'milestones', 'v1.1-phases', '02-current')));
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'phases', '01-old')));
@@ -421,7 +421,7 @@ describe('milestone complete command', () => {
mkPhaseDir(tmpDir, '01-foundation', { plan: true, oneLiner: 'Foundation work' });
mkPhaseDir(tmpDir, '10-scaling', { plan: true, oneLiner: 'Scaling work' });
const result = runGsdTools('milestone complete v1.0 --name MVP --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name MVP --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -439,7 +439,7 @@ describe('milestone complete command', () => {
fs.mkdirSync(misc, { recursive: true });
fs.writeFileSync(path.join(misc, 'PLAN.md'), '# Not a phase\n');
const result = runGsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
assert.ok(result.success);
const output = JSON.parse(result.output);
@@ -456,7 +456,7 @@ describe('milestone complete command', () => {
mkPhaseDir(tmpDir, '457-integration', { plan: true });
mkPhaseDir(tmpDir, '45-old', { plan: true });
const result = runGsdTools('milestone complete v1.49 --name DACP --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.49 --name DACP --confirm', tmpDir);
assert.ok(result.success);
assert.strictEqual(JSON.parse(result.output).phases, 2);
});
@@ -471,7 +471,7 @@ describe('milestone complete command', () => {
`---\none-liner: Built the foundation\n---\n\n# Phase 1: Foundation Summary\n\n**Built the foundation**\n\n## Performance\n\n- **Duration:** 28 min\n- **Tasks:** 7\n- **Files modified:** 12\n`,
);
const result = runGsdTools('milestone complete v1.0 --name MVP --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name MVP --confirm', tmpDir);
assert.ok(result.success);
assert.strictEqual(JSON.parse(result.output).tasks, 7);
});
@@ -486,7 +486,7 @@ describe('milestone complete command', () => {
`---\nphase: "01"\n---\n\n# Phase 1: Foundation Summary\n\n**JWT auth with refresh rotation using jose library**\n\n## Performance\n`,
);
const result = runGsdTools('milestone complete v1.0 --name MVP --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name MVP --confirm', tmpDir);
assert.ok(result.success);
assert.ok(JSON.parse(result.output).accomplishments.includes('JWT auth with refresh rotation using jose library'));
});
@@ -498,7 +498,7 @@ describe('milestone complete command', () => {
`# State\n\nStatus: In progress\nLast Activity: 2025-01-01\nLast Activity Description: Working\n`,
);
const result = runGsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
assert.ok(result.success);
assert.ok(fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8').includes('v1.0 milestone complete'));
});
@@ -507,7 +507,7 @@ describe('milestone complete command', () => {
writeRoadmap(tmpDir, `# Roadmap v1.0\n`);
writeState(tmpDir);
const result = runGsdTools('milestone complete v1.0 --name EmptyPhases --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name EmptyPhases --confirm', tmpDir);
assert.ok(result.success);
const output = JSON.parse(result.output);
@@ -520,13 +520,13 @@ describe('milestone complete command', () => {
// fs.existsSync(statePath) and a hardcoded `true` respectively — neither
// consulted whether the file's content actually changed in the
// transaction. Both now mirror requirements_updated's diff-tracking
// contract. Clock is pinned (GSD_TEST_MODE + GSD_NOW_MS) because
// contract. Clock is pinned (MSD_TEST_MODE + MSD_NOW_MS) because
// syncStateFrontmatter stamps a millisecond-resolution `last_updated:`
// field on every STATE.md write — an unpinned second run would genuinely
// differ by that timestamp alone, masking the no-op this test needs to
// observe.
describe('write-flag content-change contract (#3685)', () => {
const PINNED_CLOCK_ENV = { GSD_TEST_MODE: '1', GSD_NOW_MS: '1750000000000' };
const PINNED_CLOCK_ENV = { MSD_TEST_MODE: '1', MSD_NOW_MS: '1750000000000' };
test('state_updated is true and STATE.md content actually changes on a genuine completion', () => {
writeRoadmap(tmpDir, `# Roadmap v1.0\n`);
@@ -534,7 +534,7 @@ describe('milestone complete command', () => {
const statePath = path.join(tmpDir, '.planning', 'STATE.md');
const stateBefore = fs.readFileSync(statePath, 'utf-8');
const result = runGsdTools(['milestone', 'complete', 'v1.0', '--name', 'Test', '--confirm'], tmpDir, PINNED_CLOCK_ENV);
const result = runMsdTools(['milestone', 'complete', 'v1.0', '--name', 'Test', '--confirm'], tmpDir, PINNED_CLOCK_ENV);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -549,7 +549,7 @@ describe('milestone complete command', () => {
writeState(tmpDir);
const statePath = path.join(tmpDir, '.planning', 'STATE.md');
const run1 = runGsdTools(['milestone', 'complete', 'v1.0', '--name', 'Test', '--force', '--confirm'], tmpDir, PINNED_CLOCK_ENV);
const run1 = runMsdTools(['milestone', 'complete', 'v1.0', '--name', 'Test', '--force', '--confirm'], tmpDir, PINNED_CLOCK_ENV);
assert.ok(run1.success, `first milestone complete failed: ${run1.error}`);
const stateAfter1 = fs.readFileSync(statePath, 'utf-8');
@@ -558,7 +558,7 @@ describe('milestone complete command', () => {
// genuine no-op for STATE.md content, even though MILESTONES.md still
// gains a new (duplicate-looking) entry each call — the two flags are
// independent and must not be conflated.
const run2 = runGsdTools(['milestone', 'complete', 'v1.0', '--name', 'Test', '--force', '--confirm'], tmpDir, PINNED_CLOCK_ENV);
const run2 = runMsdTools(['milestone', 'complete', 'v1.0', '--name', 'Test', '--force', '--confirm'], tmpDir, PINNED_CLOCK_ENV);
assert.ok(run2.success, `second milestone complete failed: ${run2.error}`);
const stateAfter2 = fs.readFileSync(statePath, 'utf-8');
@@ -582,7 +582,7 @@ describe('milestone complete command', () => {
// shared write-seam composition (syncAndPreserveStateMd) instead of the bare
// writeStateMd — the "real exposure" Finding 2 identified (the #3374 shape,
// applied to milestone.complete). Test matrix:
// .gsd/phase/refactor-3469-one-write-seam/50-test-matrix.md
// .msd/phase/refactor-3469-one-write-seam/50-test-matrix.md
// ─────────────────────────────────────────────────────────────────────────────
describe('ADR-3408 §8.3 Matrix B: cmdMilestoneComplete preserves + warns (#3469)', () => {
@@ -596,7 +596,7 @@ describe('ADR-3408 §8.3 Matrix B: cmdMilestoneComplete preserves + warns (#3469
// field, exactly the #3374 shape (a stale body value vs. a curated
// frontmatter value) that Finding 2 says was silently clobbered pre-#3469.
function writeStateWithSession(dir, { fmStoppedAt, sessionStoppedAt, fmStatus = 'executing' } = {}) {
const fmLines = ['---', 'gsd_state_version: 1.0'];
const fmLines = ['---', 'msd_state_version: 1.0'];
if (fmStatus !== null) fmLines.push(`status: ${fmStatus}`);
if (fmStoppedAt !== undefined && fmStoppedAt !== null) fmLines.push(`stopped_at: "${fmStoppedAt}"`);
fmLines.push('---', '');
@@ -622,7 +622,7 @@ describe('ADR-3408 §8.3 Matrix B: cmdMilestoneComplete preserves + warns (#3469
test('B1: stale body Stopped at does not clobber a fresher curated frontmatter stopped_at', () => {
writeStateWithSession(tmpDir, { fmStoppedAt: 'Phase 7 verified PASS', sessionStoppedAt: 'Phase 3 work' });
const result = runGsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
@@ -640,10 +640,10 @@ describe('ADR-3408 §8.3 Matrix B: cmdMilestoneComplete preserves + warns (#3469
test('B2: the preserved frontmatter value is observable via a separate `state get` call', () => {
writeStateWithSession(tmpDir, { fmStoppedAt: 'Phase 7 verified PASS', sessionStoppedAt: 'Phase 3 work' });
const complete = runGsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
const complete = runMsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
assert.ok(complete.success, `Command failed: ${complete.error}`);
const got = runGsdTools('state get stopped_at', tmpDir);
const got = runMsdTools('state get stopped_at', tmpDir);
assert.ok(got.success, `state get failed: ${got.error}`);
const gotOutput = JSON.parse(got.output);
assert.ok(
@@ -659,7 +659,7 @@ describe('ADR-3408 §8.3 Matrix B: cmdMilestoneComplete preserves + warns (#3469
test('B3: a preserved divergence emits preservation_warnings[0].field === "stopped_at"', () => {
writeStateWithSession(tmpDir, { fmStoppedAt: 'Phase 7 verified PASS', sessionStoppedAt: 'Phase 3 work' });
const result = runGsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -682,7 +682,7 @@ describe('ADR-3408 §8.3 Matrix B: cmdMilestoneComplete preserves + warns (#3469
// derived result.
writeStateWithSession(tmpDir, { fmStatus: 'executing', fmStoppedAt: undefined, sessionStoppedAt: undefined });
const result = runGsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -705,7 +705,7 @@ describe('ADR-3408 §8.3 Matrix B: cmdMilestoneComplete preserves + warns (#3469
mkPhaseDir(tmpDir, '01-foundation', { oneLiner: 'Setup' });
writeState(tmpDir); // no frontmatter at all — nothing curated to diverge from
const result = runGsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -721,7 +721,7 @@ describe('ADR-3408 §8.3 Matrix B: cmdMilestoneComplete preserves + warns (#3469
test('B3 (#3471 regression pin): preservation_warnings shape and content unchanged by Phase 4', () => {
writeStateWithSession(tmpDir, { fmStoppedAt: 'Phase 7 verified PASS', sessionStoppedAt: 'Phase 3 work' });
const result = runGsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name Test --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
@@ -748,7 +748,7 @@ describe('phases clear command', () => {
fs.mkdirSync(p1, { recursive: true });
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan\n');
const result = runGsdTools('phases clear --confirm', tmpDir);
const result = runMsdTools('phases clear --confirm', tmpDir);
assert.ok(result.success);
assert.strictEqual(JSON.parse(result.output).cleared, 1);
assert.ok(!fs.existsSync(p1));
@@ -757,7 +757,7 @@ describe('phases clear command', () => {
test('requires --confirm when phase directories exist', () => {
const p1 = path.join(tmpDir, '.planning', 'phases', '01-setup');
fs.mkdirSync(p1, { recursive: true });
assert.ok(!runGsdTools('phases clear', tmpDir).success);
assert.ok(!runMsdTools('phases clear', tmpDir).success);
});
test('preserves 999.x backlog phase directories during clear (#1853)', () => {
@@ -771,7 +771,7 @@ describe('phases clear command', () => {
fs.writeFileSync(path.join(p999a, 'PLAN.md'), '# Backlog\n');
fs.writeFileSync(path.join(p999b, 'PLAN.md'), '# Backlog 2\n');
const result = runGsdTools('phases clear --confirm', tmpDir);
const result = runMsdTools('phases clear --confirm', tmpDir);
assert.ok(result.success);
assert.strictEqual(JSON.parse(result.output).cleared, 1);
assert.ok(!fs.existsSync(p1));
@@ -783,7 +783,7 @@ describe('phases clear command', () => {
const p999a = path.join(tmpDir, '.planning', 'phases', '999.1-idea');
fs.mkdirSync(p999a, { recursive: true });
const result = runGsdTools('phases clear --confirm', tmpDir);
const result = runMsdTools('phases clear --confirm', tmpDir);
assert.ok(result.success);
assert.strictEqual(JSON.parse(result.output).cleared, 0);
assert.ok(fs.existsSync(p999a));
@@ -851,7 +851,7 @@ describe('requirements mark-complete command', () => {
test('#2140 checkbox-only reconcile surfaces table_unmatched (not silent success)', () => {
writeRequirements(tmpDir, TABLE_WITHOUT_FOO);
const result = runGsdTools('requirements mark-complete FOO-01', tmpDir);
const result = runMsdTools('requirements mark-complete FOO-01', tmpDir);
assert.ok(result.success);
const out = JSON.parse(result.output);
@@ -884,9 +884,9 @@ describe('requirements mark-complete command', () => {
test('#2140 re-run on the half-written file does NOT mask the drift as already_complete', () => {
writeRequirements(tmpDir, TABLE_WITHOUT_FOO);
// First run: flips the checkbox, surfaces table_unmatched.
runGsdTools('requirements mark-complete FOO-01', tmpDir);
runMsdTools('requirements mark-complete FOO-01', tmpDir);
// Second run on the now-[x]-checkbox-with-no-row file.
const result = runGsdTools('requirements mark-complete FOO-01', tmpDir);
const result = runMsdTools('requirements mark-complete FOO-01', tmpDir);
assert.ok(result.success);
const out = JSON.parse(result.output);
@@ -911,7 +911,7 @@ describe('requirements mark-complete command', () => {
// A REQUIREMENTS.md with no traceability table is legitimate; a checkbox-only
// reconcile must remain an unqualified success with no table_unmatched entry.
writeRequirements(tmpDir, '# Requirements\n\n- [ ] **NO-TABLE-01**: thing\n');
const result = runGsdTools('requirements mark-complete NO-TABLE-01', tmpDir);
const result = runMsdTools('requirements mark-complete NO-TABLE-01', tmpDir);
assert.ok(result.success);
const out = JSON.parse(result.output);
assert.ok(out.marked_complete.includes('NO-TABLE-01'));
@@ -951,7 +951,7 @@ describe('requirements mark-complete command', () => {
`,
);
const result = runGsdTools('requirements mark-complete REQ-01,REQ-02', tmpDir);
const result = runMsdTools('requirements mark-complete REQ-01,REQ-02', tmpDir);
assert.ok(result.success);
const out = JSON.parse(result.output);
@@ -1001,7 +1001,7 @@ describe('requirements mark-complete command', () => {
`,
);
const result = runGsdTools('requirements mark-complete REQ-01,REQ-02', tmpDir);
const result = runMsdTools('requirements mark-complete REQ-01,REQ-02', tmpDir);
assert.ok(result.success);
const out = JSON.parse(result.output);
@@ -1020,7 +1020,7 @@ describe('requirements mark-complete command', () => {
test('marks single requirement complete (checkbox + table)', () => {
writeRequirements(tmpDir, STANDARD_REQUIREMENTS);
const result = runGsdTools('requirements mark-complete TEST-01', tmpDir);
const result = runMsdTools('requirements mark-complete TEST-01', tmpDir);
assert.ok(result.success);
const output = JSON.parse(result.output);
@@ -1042,7 +1042,7 @@ describe('requirements mark-complete command', () => {
});
test('#2245 F1: traceability write is NOT fooled by an earlier Out of Scope table', () => {
// The shipped requirements template (gsd-core/templates/requirements.md)
// The shipped requirements template (msd-core/templates/requirements.md)
// puts an `## Out of Scope` table (`| Feature | Reason |`, no Status
// column) BEFORE `## Traceability`. updateTableCell binds to the FIRST
// GFM table in whatever text it is given — an unscoped whole-file call
@@ -1071,7 +1071,7 @@ describe('requirements mark-complete command', () => {
`,
);
const result = runGsdTools('requirements mark-complete REQ-001', tmpDir);
const result = runMsdTools('requirements mark-complete REQ-001', tmpDir);
assert.ok(result.success);
const out = JSON.parse(result.output);
@@ -1095,7 +1095,7 @@ describe('requirements mark-complete command', () => {
test('handles mixed prefixes in single call (TEST-XX, REG-XX, INFRA-XX)', () => {
writeRequirements(tmpDir, STANDARD_REQUIREMENTS);
const result = runGsdTools('requirements mark-complete TEST-01,REG-01,INFRA-01', tmpDir);
const result = runMsdTools('requirements mark-complete TEST-01,REG-01,INFRA-01', tmpDir);
assert.ok(result.success);
const output = JSON.parse(result.output);
@@ -1116,7 +1116,7 @@ describe('requirements mark-complete command', () => {
test('accepts space-separated IDs', () => {
writeRequirements(tmpDir, STANDARD_REQUIREMENTS);
const result = runGsdTools('requirements mark-complete TEST-01 TEST-02', tmpDir);
const result = runMsdTools('requirements mark-complete TEST-01 TEST-02', tmpDir);
assert.ok(result.success);
assert.strictEqual(JSON.parse(result.output).marked_complete.length, 2);
@@ -1128,7 +1128,7 @@ describe('requirements mark-complete command', () => {
test('accepts bracket-wrapped IDs [REQ-01, REQ-02]', () => {
writeRequirements(tmpDir, STANDARD_REQUIREMENTS);
const result = runGsdTools('requirements mark-complete [TEST-01,TEST-02]', tmpDir);
const result = runMsdTools('requirements mark-complete [TEST-01,TEST-02]', tmpDir);
assert.ok(result.success);
assert.strictEqual(JSON.parse(result.output).marked_complete.length, 2);
@@ -1140,7 +1140,7 @@ describe('requirements mark-complete command', () => {
test('returns not_found for invalid IDs while updating valid ones', () => {
writeRequirements(tmpDir, STANDARD_REQUIREMENTS);
const result = runGsdTools('requirements mark-complete TEST-01,FAKE-99', tmpDir);
const result = runMsdTools('requirements mark-complete TEST-01,FAKE-99', tmpDir);
assert.ok(result.success);
const output = JSON.parse(result.output);
@@ -1153,7 +1153,7 @@ describe('requirements mark-complete command', () => {
test('idempotent — re-marking already-complete requirement does not corrupt', () => {
writeRequirements(tmpDir, STANDARD_REQUIREMENTS);
const result = runGsdTools('requirements mark-complete TEST-03', tmpDir);
const result = runMsdTools('requirements mark-complete TEST-03', tmpDir);
assert.ok(result.success);
const output = JSON.parse(result.output);
@@ -1169,7 +1169,7 @@ describe('requirements mark-complete command', () => {
test('returns already_complete for idempotent calls on completed requirements', () => {
writeRequirements(tmpDir, STANDARD_REQUIREMENTS);
const output = JSON.parse(runGsdTools('requirements mark-complete TEST-03', tmpDir).output);
const output = JSON.parse(runMsdTools('requirements mark-complete TEST-03', tmpDir).output);
assert.deepStrictEqual(output.already_complete, ['TEST-03']);
assert.deepStrictEqual(output.not_found, []);
});
@@ -1178,7 +1178,7 @@ describe('requirements mark-complete command', () => {
writeRequirements(tmpDir, STANDARD_REQUIREMENTS);
const output = JSON.parse(
runGsdTools('requirements mark-complete TEST-01,TEST-03,FAKE-99', tmpDir).output,
runMsdTools('requirements mark-complete TEST-01,TEST-03,FAKE-99', tmpDir).output,
);
assert.deepStrictEqual(output.marked_complete, ['TEST-01']);
assert.deepStrictEqual(output.already_complete, ['TEST-03']);
@@ -1186,7 +1186,7 @@ describe('requirements mark-complete command', () => {
});
test('missing REQUIREMENTS.md returns expected error structure', () => {
const output = JSON.parse(runGsdTools('requirements mark-complete TEST-01', tmpDir).output);
const output = JSON.parse(runMsdTools('requirements mark-complete TEST-01', tmpDir).output);
assert.strictEqual(output.updated, false);
assert.strictEqual(output.reason, 'REQUIREMENTS.md not found');
});
@@ -1212,7 +1212,7 @@ describe('requirements mark-complete command', () => {
test('#2788 defect 1: a Gaps Found row moves to Complete via mark-complete (no longer terminal)', () => {
writeRequirements(tmpDir, GAPS_FOUND_REQUIREMENTS);
const result = runGsdTools('requirements mark-complete REQ-01', tmpDir);
const result = runMsdTools('requirements mark-complete REQ-01', tmpDir);
assert.ok(result.success);
const out = JSON.parse(result.output);
// The row EXISTS and moved to Complete, so updated/marked_complete are truthful.
@@ -1240,7 +1240,7 @@ describe('requirements mark-complete command', () => {
| REQ-01 | Phase 1 | Blocked |
`;
writeRequirements(tmpDir, blockedRequirements);
const result = runGsdTools('requirements mark-complete REQ-01', tmpDir);
const result = runMsdTools('requirements mark-complete REQ-01', tmpDir);
assert.ok(result.success);
const out = JSON.parse(result.output);
assert.strictEqual(out.updated, false,
@@ -1281,12 +1281,12 @@ describe('requirements mark-complete command', () => {
`;
writeRequirements(tmpDir, completeRequirements);
// revert-phase strands the row at Gaps Found (the gaps_found response).
const reverted = JSON.parse(runGsdTools('requirements revert-phase REQ-01', tmpDir).output);
const reverted = JSON.parse(runMsdTools('requirements revert-phase REQ-01', tmpDir).output);
assert.ok(reverted.reverted.includes('REQ-01'));
let content = readRequirements(tmpDir);
assert.ok(/REQ-01 \| Phase 1 \| Gaps Found/.test(content), 'revert should strand at Gaps Found');
// Now the requirement is genuinely satisfied again — mark-complete must recover it.
const marked = JSON.parse(runGsdTools('requirements mark-complete REQ-01', tmpDir).output);
const marked = JSON.parse(runMsdTools('requirements mark-complete REQ-01', tmpDir).output);
assert.ok(marked.updated, 'the stranded row must be recoverable via mark-complete');
content = readRequirements(tmpDir);
assert.ok(/REQ-01 \| Phase 1 \| Complete/.test(content),
@@ -1295,7 +1295,7 @@ describe('requirements mark-complete command', () => {
test('#2788 negative-space: the normal Pending → Complete path is unchanged', () => {
writeRequirements(tmpDir, STANDARD_REQUIREMENTS);
const out = JSON.parse(runGsdTools('requirements mark-complete TEST-01', tmpDir).output);
const out = JSON.parse(runMsdTools('requirements mark-complete TEST-01', tmpDir).output);
assert.ok(out.updated);
assert.ok(out.marked_complete.includes('TEST-01'));
const content = readRequirements(tmpDir);
@@ -1343,7 +1343,7 @@ describe('requirements mark-complete: traceability write (regression, ADR-2143
`,
);
const result = runGsdTools('requirements mark-complete REQ-001,REQ-002', tmpDir);
const result = runMsdTools('requirements mark-complete REQ-001,REQ-002', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const out = JSON.parse(result.output);
@@ -1380,7 +1380,7 @@ describe('requirements mark-complete: traceability write (regression, ADR-2143
`,
);
const result = runGsdTools('requirements mark-complete REQ-001', tmpDir);
const result = runMsdTools('requirements mark-complete REQ-001', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const out = JSON.parse(result.output);
@@ -1416,7 +1416,7 @@ describe('requirements mark-complete: traceability write (regression, ADR-2143
`,
);
const result = runGsdTools('requirements mark-complete REQ-001', tmpDir);
const result = runMsdTools('requirements mark-complete REQ-001', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const out = JSON.parse(result.output);
@@ -1449,7 +1449,7 @@ describe('requirements mark-complete: traceability write (regression, ADR-2143
`,
);
const result = runGsdTools('requirements mark-complete REQ-001', tmpDir);
const result = runMsdTools('requirements mark-complete REQ-001', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const out = JSON.parse(result.output);
@@ -1476,7 +1476,7 @@ describe('requirements mark-complete: traceability write (regression, ADR-2143
`,
);
const result = runGsdTools('requirements mark-complete REQ-001', tmpDir);
const result = runMsdTools('requirements mark-complete REQ-001', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const out = JSON.parse(result.output);
@@ -1548,7 +1548,7 @@ describe('requirements ready-ids command (#2388 shared-ID gate)', () => {
// finished yet, so SHARED-01 must be blocked.
fs.writeFileSync(path.join(dir, '05-05-01-a-SUMMARY.md'), 'done\n');
const readyForPlan1 = JSON.parse(
runGsdTools(['query', 'requirements.ready-ids', plan1, 'SHARED-01'], tmpDir).output,
runMsdTools(['query', 'requirements.ready-ids', plan1, 'SHARED-01'], tmpDir).output,
);
assert.deepStrictEqual(readyForPlan1.ready, [], 'SHARED-01 must be blocked — plan 02 has not finished yet');
assert.deepStrictEqual(readyForPlan1.blocked, ['SHARED-01']);
@@ -1564,13 +1564,13 @@ describe('requirements ready-ids command (#2388 shared-ID gate)', () => {
// ready. SOLO-01 has no sibling declaring it at all, so it was always ready.
fs.writeFileSync(path.join(dir, '05-05-02-b-SUMMARY.md'), 'done\n');
const readyForPlan2 = JSON.parse(
runGsdTools(['query', 'requirements.ready-ids', plan2, 'SHARED-01,SOLO-01'], tmpDir).output,
runMsdTools(['query', 'requirements.ready-ids', plan2, 'SHARED-01,SOLO-01'], tmpDir).output,
);
assert.deepStrictEqual(readyForPlan2.ready.sort(), ['SHARED-01', 'SOLO-01']);
assert.deepStrictEqual(readyForPlan2.blocked, []);
// Hand the ready subset to the UNMODIFIED mark-complete path.
runGsdTools(['query', 'requirements.mark-complete', ...readyForPlan2.ready], tmpDir);
runMsdTools(['query', 'requirements.mark-complete', ...readyForPlan2.ready], tmpDir);
content = readRequirements(tmpDir);
assert.ok(content.includes('- [x] **SHARED-01**'), 'SHARED-01 checkbox should now be checked');
assert.ok(content.includes('| SHARED-01 | Phase 5 | Complete |'), 'SHARED-01 traceability should now be Complete');
@@ -1586,7 +1586,7 @@ describe('requirements ready-ids command (#2388 shared-ID gate)', () => {
fs.writeFileSync(plan1, '---\nphase: 05-05\nplan: 01\nrequirements: [SOLO-01]\n---\nSolo plan\n');
const result = JSON.parse(
runGsdTools(['query', 'requirements.ready-ids', plan1, 'SOLO-01'], tmpDir).output,
runMsdTools(['query', 'requirements.ready-ids', plan1, 'SOLO-01'], tmpDir).output,
);
assert.deepStrictEqual(result.ready, ['SOLO-01']);
assert.deepStrictEqual(result.blocked, []);
@@ -1599,7 +1599,7 @@ describe('requirements ready-ids command (#2388 shared-ID gate)', () => {
fs.writeFileSync(plan1, '---\nphase: 05-05\nplan: 01\nrequirements: [SHARED-01]\n---\nOnly plan\n');
const result = JSON.parse(
runGsdTools(['query', 'requirements.ready-ids', plan1, 'SHARED-01'], tmpDir).output,
runMsdTools(['query', 'requirements.ready-ids', plan1, 'SHARED-01'], tmpDir).output,
);
assert.deepStrictEqual(result.ready, ['SHARED-01']);
assert.deepStrictEqual(result.blocked, []);
@@ -1645,7 +1645,7 @@ describe('requirements revert-phase command (#2388 gaps_found revert)', () => {
`,
);
const result = runGsdTools('requirements revert-phase PHASE5-01', tmpDir);
const result = runMsdTools('requirements revert-phase PHASE5-01', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const out = JSON.parse(result.output);
assert.deepStrictEqual(out.reverted, ['PHASE5-01']);
@@ -1675,7 +1675,7 @@ describe('requirements revert-phase command (#2388 gaps_found revert)', () => {
`,
);
const result = runGsdTools('requirements revert-phase PHASE5-01', tmpDir);
const result = runMsdTools('requirements revert-phase PHASE5-01', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const out = JSON.parse(result.output);
assert.deepStrictEqual(out.reverted, []);
@@ -1693,7 +1693,7 @@ describe('requirements revert-phase command (#2388 gaps_found revert)', () => {
describe('new-milestone workflow verification gate', () => {
test('new-milestone workflow has verification step before writing PROJECT.md', () => {
const workflowPath = path.join(__dirname, '..', 'gsd-core', 'workflows', 'new-milestone.md');
const workflowPath = path.join(__dirname, '..', 'msd-core', 'workflows', 'new-milestone.md');
const content = fs.readFileSync(workflowPath, 'utf8');
assert.ok(content.includes('Verify Milestone Understanding'));
@@ -1705,7 +1705,7 @@ describe('new-milestone workflow verification gate', () => {
});
test('verification step uses AskUserQuestion with adjust loop', () => {
const workflowPath = path.join(__dirname, '..', 'gsd-core', 'workflows', 'new-milestone.md');
const workflowPath = path.join(__dirname, '..', 'msd-core', 'workflows', 'new-milestone.md');
const content = fs.readFileSync(workflowPath, 'utf8');
const section = content.slice(content.indexOf('## 3.5'), content.indexOf('## 4.'));
@@ -1724,7 +1724,7 @@ describe('new-milestone workflow verification gate', () => {
describe('milestone complete explicit version scope (#3043)', () => {
test('milestone.complete v3.6 uses v3.6 phases even when STATE milestone is v3.5', () => {
const tmpDir = createTempProject('gsd-bug-3043-');
const tmpDir = createTempProject('msd-bug-3043-');
try {
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '---\nmilestone: v3.5\n---\n');
writeRoadmap(
@@ -1739,7 +1739,7 @@ describe('milestone complete explicit version scope (#3043)', () => {
fs.writeFileSync(path.join(p, 'SUMMARY.md'), `one-liner: ${liner}\n\n## Summary\n${liner.split(' ')[0]}\n`);
}
const result = runGsdTools(['milestone', 'complete', 'v3.6', '--raw', '--confirm'], tmpDir);
const result = runMsdTools(['milestone', 'complete', 'v3.6', '--raw', '--confirm'], tmpDir);
assert.equal(result.success, true, result.error || result.output);
const payload = JSON.parse(result.output);
assert.equal(payload.version, 'v3.6');
@@ -1750,14 +1750,14 @@ describe('milestone complete explicit version scope (#3043)', () => {
});
test('milestone.complete fails when explicit milestone version resolves no phases', () => {
const tmpDir = createTempProject('gsd-bug-3043-empty-');
const tmpDir = createTempProject('msd-bug-3043-empty-');
try {
fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '---\nmilestone: v1.0\n---\n');
writeRoadmap(tmpDir, '# Roadmap\n\n## 🚧 v1.0\n### Phase 1: foundation\n');
fs.writeFileSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md'), '# Requirements\n');
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-foundation'), { recursive: true });
const result = runGsdTools(['milestone', 'complete', 'v9.9', '--raw', '--confirm'], tmpDir);
const result = runMsdTools(['milestone', 'complete', 'v9.9', '--raw', '--confirm'], tmpDir);
assert.equal(result.success, false, 'expected command to fail when no phases match explicit version');
assert.match(result.error || '', /no phases|phase/i);
} finally {
@@ -1789,7 +1789,7 @@ describe('#1911 — milestone complete --ws archives to the workstream', () => {
// Root milestones dir pre-exists; it must NOT receive the workstream archive.
fs.mkdirSync(path.join(tmpDir, '.planning', 'milestones'), { recursive: true });
const result = runGsdTools('milestone complete v2.0 --ws ws1 --force --confirm', tmpDir);
const result = runMsdTools('milestone complete v2.0 --ws ws1 --force --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
// Archive lands inside the workstream.
@@ -1820,7 +1820,7 @@ describe('#1911 — milestone complete --ws archives to the workstream', () => {
fs.writeFileSync(path.join(wsBase, 'phases', '01-foo', '01-SUMMARY.md'), '---\none-liner: foo done\n---\n# Summary\n');
fs.mkdirSync(path.join(tmpDir, '.planning', 'milestones'), { recursive: true });
const result = runGsdTools('milestone complete v2.0 --ws ws1 --force --confirm', tmpDir);
const result = runMsdTools('milestone complete v2.0 --ws ws1 --force --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const archivedReq = fs.readFileSync(
@@ -1858,7 +1858,7 @@ describe('#1871 — milestone complete archives phase dirs by default', () => {
test('archives phase dirs by default (no --archive-phases flag needed)', () => {
seedCompletableMilestone();
const result = runGsdTools('milestone complete v1.0 --name MVP --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name MVP --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const out = JSON.parse(result.output);
assert.ok(out.archived.phases, 'phases should be archived by default');
@@ -1871,7 +1871,7 @@ describe('#1871 — milestone complete archives phase dirs by default', () => {
test('--no-archive-phases opts out of default archiving', () => {
seedCompletableMilestone();
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
const result = runGsdTools('milestone complete v1.0 --name MVP --no-archive-phases --confirm', tmpDir);
const result = runMsdTools('milestone complete v1.0 --name MVP --no-archive-phases --confirm', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const out = JSON.parse(result.output);
assert.ok(!out.archived.phases, 'phases should NOT be archived with --no-archive-phases');
@@ -1889,10 +1889,10 @@ describe('#1871 — milestone complete archives phase dirs by default', () => {
'use strict';
/**
* Regression test for bug #978: `gsd-tools milestone complete --force` was a
* Regression test for bug #978: `msd-tools milestone complete --force` was a
* dead flag. The milestone source (src/milestone.cts) has a guard that checks
* `options.force` and tells users to "Re-run with --force to override", but the
* CLI dispatcher (gsd-core/bin/gsd-tools.cjs) never parsed `--force` and never
* CLI dispatcher (msd-core/bin/msd-tools.cjs) never parsed `--force` and never
* passed it into the options object. So `options.force` was always `undefined`
* and the guard could never be overridden regardless of what the user typed.
*/
@@ -1901,7 +1901,7 @@ const { test, describe, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const { runMsdTools, createTempProject, cleanup } = require('./helpers.cjs');
/**
* Build a fixture where the guard will fire:
@@ -1934,7 +1934,7 @@ describe('bug-978: milestone complete --force overrides unstarted-phase guard',
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject('gsd-bug-978-');
tmpDir = createTempProject('msd-bug-978-');
});
afterEach(() => {
@@ -1944,7 +1944,7 @@ describe('bug-978: milestone complete --force overrides unstarted-phase guard',
test('without --force the guard fires and emits the documented error message', () => {
makeGuardFixture(tmpDir, 'v1.0');
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Regression Test', '--confirm'],
tmpDir,
);
@@ -1959,7 +1959,7 @@ describe('bug-978: milestone complete --force overrides unstarted-phase guard',
test('with --force the guard is bypassed and the command succeeds', () => {
makeGuardFixture(tmpDir, 'v1.0');
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Regression Test', '--force', '--confirm'],
tmpDir,
);
@@ -2006,12 +2006,12 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const { runMsdTools, createTempProject, cleanup } = require('./helpers.cjs');
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject('gsd-bug-2946-');
tmpDir = createTempProject('msd-bug-2946-');
});
afterEach(() => {
@@ -2048,7 +2048,7 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
test('without --force the guard fires even when STATE.md milestone: desyncs from the requested version', () => {
makeFixture(tmpDir, 'v1.0', 'desync');
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Regression Test', '--dry-run'],
tmpDir,
);
@@ -2061,7 +2061,7 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
test('without --force the guard fires even when STATE.md has no milestone: field', () => {
makeFixture(tmpDir, 'v1.0', 'absent');
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Regression Test', '--dry-run'],
tmpDir,
);
@@ -2074,7 +2074,7 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
test('without --force the guard fires even when STATE.md does not exist', () => {
makeFixture(tmpDir, 'v1.0', 'no-file');
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Regression Test', '--dry-run'],
tmpDir,
);
@@ -2087,7 +2087,7 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
test('with --force the guard is bypassed even when STATE.md milestone: desyncs', () => {
makeFixture(tmpDir, 'v1.0', 'desync');
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Regression Test', '--dry-run', '--force'],
tmpDir,
);
@@ -2101,7 +2101,7 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
test('a STATE milestone mismatch emits a warning naming both versions', () => {
makeFixture(tmpDir, 'v1.0', 'desync');
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Regression Test', '--dry-run'],
tmpDir,
);
@@ -2126,7 +2126,7 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
// but a missing milestone: declaration is a normal fresh-project state,
// not a mismatch — so no WARNING should accompany it.
makeFixture(tmpDir, 'v1.0', 'absent');
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Regression Test', '--dry-run'],
tmpDir,
);
@@ -2148,7 +2148,7 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
path.join(tmpDir, '.planning', 'STATE.md'),
`---\nmilestone: v1.0\n---\n# State\n\n**Status:** In progress\n`,
);
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Fresh', '--dry-run'],
tmpDir,
);
@@ -2169,7 +2169,7 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
path.join(tmpDir, '.planning', 'STATE.md'),
`---\n---\n# State\n\n**Status:** In progress\n`,
);
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Sentinel', '--dry-run'],
tmpDir,
);
@@ -2206,7 +2206,7 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
test('guard does not fire for a started phase whose directory is digit-leading (#2528)', () => {
makeDigitLeadingFixture(tmpDir, '5', '05-80-20-cleanup');
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Digit Leading', '--dry-run'],
tmpDir,
);
@@ -2221,7 +2221,7 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
// 80 inside it is slug text. Resolving it to Phase 80 would disarm the
// guard on a one-way-door operation.
makeDigitLeadingFixture(tmpDir, '80', '05-80-20-cleanup');
const result = runGsdTools(
const result = runMsdTools(
['milestone', 'complete', 'v1.0', '--name', 'Digit Leading', '--dry-run'],
tmpDir,
);
@@ -2240,7 +2240,7 @@ describe('bug #2946: unstarted-phase guard runs independent of STATE.md mileston
const { describe: __foldDescribe } = require('node:test');
__foldDescribe("folded:bug-2660-one-liner-extraction (consolidation epic #1969 B3 #1972)", () => {
/**
* Bug #2660: `gsd-tools milestone complete <version>` writes MILESTONES.md
* Bug #2660: `msd-tools milestone complete <version>` writes MILESTONES.md
* bullets that read "- One-liner:" (the literal label) instead of the prose
* after the label.
*
@@ -2254,7 +2254,7 @@ const assert = require('node:assert/strict');
const path = require('path');
const { extractOneLinerFromBody } = require(
path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'core-utils.cjs')
path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'core-utils.cjs')
);
describe('bug #2660: extractOneLinerFromBody', () => {
@@ -2329,7 +2329,7 @@ describe('#3170: extractOneLinerFromBody anchors to a summary-shaped heading', (
// Self-contained require (the #2660 block's require is fold-scoped).
const path = require('path');
const { extractOneLinerFromBody } = require(
path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'core-utils.cjs')
path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'core-utils.cjs')
);
test('row 1 — an incidental first heading does not contribute its bold run; the Summary heading does', () => {
@@ -2400,8 +2400,8 @@ const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const TEMPLATE = path.join(__dirname, '..', 'gsd-core', 'templates', 'project.md');
const COMPLETE_MILESTONE = path.join(__dirname, '..', 'gsd-core', 'workflows', 'complete-milestone.md');
const TEMPLATE = path.join(__dirname, '..', 'msd-core', 'templates', 'project.md');
const COMPLETE_MILESTONE = path.join(__dirname, '..', 'msd-core', 'workflows', 'complete-milestone.md');
function parseTemplateContract(content) {
const lines = content.split(/\r?\n/);
@@ -2526,7 +2526,7 @@ describe('#3726: milestone complete refuses to mutate without --confirm', () =>
['milestone', 'complete', 'v1.0', '--name', 'MVP'],
['query', 'milestone.complete', 'v1.0', '--name', 'MVP'],
]) {
const result = runGsdTools(argv, tmpDir);
const result = runMsdTools(argv, tmpDir);
assert.strictEqual(result.success, false, `${argv.join(' ')} must refuse without --confirm`);
assert.match(result.error || '', /--confirm/, 'refusal must name the flag that proceeds');
assert.match(result.error || '', /irreversible/i, 'refusal must say why it refused');
@@ -2540,7 +2540,7 @@ describe('#3726: milestone complete refuses to mutate without --confirm', () =>
test('--force alone does not satisfy the confirmation gate', () => {
seedMutableMilestone();
const before = snapshotPlanning();
const result = runGsdTools(['milestone', 'complete', 'v1.0', '--force'], tmpDir);
const result = runMsdTools(['milestone', 'complete', 'v1.0', '--force'], tmpDir);
assert.strictEqual(result.success, false, '--force without --confirm must still refuse');
assert.match(result.error || '', /--confirm/);
assert.deepStrictEqual(snapshotPlanning(), before, '--force refusal must leave .planning/ untouched');
@@ -2561,7 +2561,7 @@ describe('#3726: milestone complete refuses to mutate without --confirm', () =>
['query', 'milestone.complete', 'v1.0', '--confirm=false'],
['query', 'milestone.complete', 'v1.0', '--confirm=0'],
]) {
const result = runGsdTools(argv, tmpDir);
const result = runMsdTools(argv, tmpDir);
assert.strictEqual(result.success, false, `${argv.join(' ')} must refuse — the exact token is absent`);
assert.match(result.error || '', /--confirm/, 'refusal must name the flag that proceeds');
assert.deepStrictEqual(snapshotPlanning(), before, `${argv.join(' ')} must leave .planning/ untouched`);
@@ -2572,7 +2572,7 @@ describe('#3726: milestone complete refuses to mutate without --confirm', () =>
test('--dry-run previews without --confirm and mutates nothing', () => {
seedMutableMilestone();
const before = snapshotPlanning();
const result = runGsdTools(['milestone', 'complete', 'v1.0', '--dry-run', '--raw'], tmpDir);
const result = runMsdTools(['milestone', 'complete', 'v1.0', '--dry-run', '--raw'], tmpDir);
assert.ok(result.success, `dry-run failed: ${result.error}`);
const preview = JSON.parse(result.output);
assert.strictEqual(preview.dry_run, true);
@@ -2583,7 +2583,7 @@ describe('#3726: milestone complete refuses to mutate without --confirm', () =>
// exactly as before the gate existed.
test('--confirm proceeds through the archive (query form)', () => {
seedMutableMilestone();
const result = runGsdTools(['query', 'milestone.complete', 'v1.0', '--name', 'MVP', '--confirm'], tmpDir);
const result = runMsdTools(['query', 'milestone.complete', 'v1.0', '--name', 'MVP', '--confirm'], tmpDir);
assert.ok(result.success, `--confirm run failed: ${result.error}`);
assert.ok(fs.existsSync(path.join(tmpDir, '.planning', 'milestones', 'v1.0-ROADMAP.md')), 'ROADMAP archived');
assert.ok(!fs.existsSync(path.join(tmpDir, '.planning', 'phases', '01-foundation')), 'phase dir moved');
@@ -2591,13 +2591,13 @@ describe('#3726: milestone complete refuses to mutate without --confirm', () =>
// #3726 (PR #3774 review, Nit 1): the documented arg-discovery path is
// "invoke the command without args and the error lists what is required"
// (gsd-tools.cjs top-level usage). The version-required refusal is that
// (msd-tools.cjs top-level usage). The version-required refusal is that
// error for `milestone complete`, so it must name --confirm too — otherwise
// discovering the flag takes a second round trip through the gate.
test('the version-required refusal names --confirm (arg-discovery path)', () => {
seedMutableMilestone();
const before = snapshotPlanning();
const result = runGsdTools(['milestone', 'complete'], tmpDir);
const result = runMsdTools(['milestone', 'complete'], tmpDir);
assert.strictEqual(result.success, false, 'bare `milestone complete` must refuse');
assert.match(result.error || '', /version required/, 'refusal must still name the missing version');
assert.match(result.error || '', /--confirm/, 'refusal must name --confirm so one invocation lists everything required');
@@ -2617,7 +2617,7 @@ describe('#3726: milestone complete refuses to mutate without --confirm', () =>
describe('#3726 milestone complete docs pin', () => {
const REPO_ROOT = path.join(__dirname, '..');
const readDoc = (...segs) => fs.readFileSync(path.join(REPO_ROOT, 'docs', ...segs), 'utf-8');
const SYNOPSIS = 'node gsd-tools.cjs milestone complete <version> (--confirm | --dry-run)';
const SYNOPSIS = 'node msd-tools.cjs milestone complete <version> (--confirm | --dry-run)';
const MIRRORS = [['CLI-TOOLS.md'], ['ja-JP', 'CLI-TOOLS.md'], ['ko-KR', 'CLI-TOOLS.md'], ['pt-BR', 'CLI-TOOLS.md'], ['zh-CN', 'CLI-TOOLS.md']];
test('the synopsis renders --confirm and --dry-run as alternatives in CLI-TOOLS.md and every localized mirror', () => {
@@ -2650,7 +2650,7 @@ describe('#3726 milestone complete docs pin', () => {
return [
{ doc: 'CLI-TOOLS.md', rows: section(cli, 'CLI-TOOLS.md', /^### `milestone complete` refuses an untrustworthy window/, /^## /) },
{ doc: 'CLI-TOOLS.md', rows: section(cli, 'CLI-TOOLS.md', /^## Milestone Commands/, /^## /) },
{ doc: 'COMMANDS.md', rows: section(readDoc('COMMANDS.md'), 'COMMANDS.md', /^### `\/gsd-complete-milestone`/, /^### /) },
{ doc: 'COMMANDS.md', rows: section(readDoc('COMMANDS.md'), 'COMMANDS.md', /^### `\/msd-complete-milestone`/, /^### /) },
];
};