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:
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* GSD Tools Tests - Init Manager
|
||||
* MSD Tools Tests - Init Manager
|
||||
*/
|
||||
|
||||
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, absPlanningPath } = require('./helpers.cjs');
|
||||
const { runMsdTools, createTempProject, cleanup, absPlanningPath } = require('./helpers.cjs');
|
||||
const { seedWorkstream } = require('./fixtures/index.cjs');
|
||||
|
||||
// Helper: write a minimal ROADMAP.md with phases
|
||||
@@ -76,14 +76,14 @@ describe('init manager', () => {
|
||||
|
||||
test('fails without ROADMAP.md', () => {
|
||||
writeState(tmpDir);
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(!result.success);
|
||||
assert.ok(result.error.includes('ROADMAP.md'));
|
||||
});
|
||||
|
||||
test('fails without STATE.md', () => {
|
||||
writeRoadmap(tmpDir, [{ number: '1', name: 'Setup' }]);
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(!result.success);
|
||||
assert.ok(result.error.includes('STATE.md'));
|
||||
});
|
||||
@@ -96,7 +96,7 @@ describe('init manager', () => {
|
||||
{ number: '3', name: 'UI' },
|
||||
]);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
@@ -128,7 +128,7 @@ describe('init manager', () => {
|
||||
scaffoldPhase(tmpDir, 4, { slug: 'empty-phase' });
|
||||
// Phase 5: no directory at all
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
@@ -151,7 +151,7 @@ describe('init manager', () => {
|
||||
fs.writeFileSync(path.join(phaseDir, 'plans', 'PLAN-02.md'), '# Plan 2');
|
||||
fs.writeFileSync(path.join(phaseDir, 'plans', 'SUMMARY-01.md'), '# Summary 1');
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
@@ -168,7 +168,7 @@ describe('init manager', () => {
|
||||
]);
|
||||
writePassedVerification(scaffoldPhase(tmpDir, 1, { slug: 'foundation', plans: 1, summaries: 1 }), '01');
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.strictEqual(output.phases[0].deps_satisfied, true);
|
||||
@@ -182,7 +182,7 @@ describe('init manager', () => {
|
||||
{ number: '2', name: 'Depends on 1', depends_on: 'Phase 1' },
|
||||
]);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.strictEqual(output.phases[0].deps_satisfied, true); // no deps
|
||||
@@ -197,7 +197,7 @@ describe('init manager', () => {
|
||||
{ number: '3', name: 'UI' },
|
||||
]);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
// All three phases are undiscussed — all should be discussable
|
||||
@@ -221,7 +221,7 @@ describe('init manager', () => {
|
||||
// Phase 1 discussed
|
||||
scaffoldPhase(tmpDir, 1, { slug: 'foundation', context: true });
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
// Phase 1 is discussed; phases 2 and 3 are both undiscussed and discussable
|
||||
@@ -252,7 +252,7 @@ describe('init manager', () => {
|
||||
scaffoldPhase(tmpDir, 2, { slug: 'api-layer', context: true, plans: 2 }); // planned
|
||||
scaffoldPhase(tmpDir, 3, { slug: 'auth', context: true }); // discussed
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
// Phases 4 and 5 are both undiscussed — both discussable
|
||||
@@ -281,7 +281,7 @@ describe('init manager', () => {
|
||||
scaffoldPhase(tmpDir, 2, { slug: 'ready-to-execute', context: true, plans: 2 });
|
||||
scaffoldPhase(tmpDir, 3, { slug: 'ready-to-plan', context: true });
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.ok(output.recommended_actions.length >= 3);
|
||||
@@ -300,7 +300,7 @@ describe('init manager', () => {
|
||||
{ number: '2', name: 'Blocked', depends_on: 'Phase 1' },
|
||||
]);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
// Phase 2 should not appear in recommendations (blocked by phase 1)
|
||||
@@ -317,7 +317,7 @@ describe('init manager', () => {
|
||||
writePassedVerification(scaffoldPhase(tmpDir, 1, { slug: 'done', plans: 1, summaries: 1 }), '01');
|
||||
writePassedVerification(scaffoldPhase(tmpDir, 2, { slug: 'also-done', plans: 1, summaries: 1 }), '02');
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.strictEqual(output.all_complete, true);
|
||||
@@ -331,7 +331,7 @@ describe('init manager', () => {
|
||||
]);
|
||||
scaffoldPhase(tmpDir, 1, { slug: 'implemented', plans: 1, summaries: 1 });
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
@@ -359,7 +359,7 @@ describe('init manager', () => {
|
||||
fs.utimesSync(verificationPath, older, older);
|
||||
fs.utimesSync(summaryPath, newer, newer);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
@@ -398,7 +398,7 @@ describe('init manager', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), roadmap);
|
||||
scaffoldPhase(tmpDir, 1, { slug: 'foundation', plans: 1, summaries: 1 });
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
@@ -421,7 +421,7 @@ describe('init manager', () => {
|
||||
JSON.stringify(waiting)
|
||||
);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.deepStrictEqual(output.waiting_signal, waiting);
|
||||
@@ -434,7 +434,7 @@ describe('init manager', () => {
|
||||
{ number: '2', name: 'API', goal: 'Build endpoints', depends_on: 'Phase 1' },
|
||||
]);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.strictEqual(output.phases[0].goal, 'Set up the base');
|
||||
@@ -451,7 +451,7 @@ describe('init manager', () => {
|
||||
{ number: '3', name: 'This Name Is Way Too Long For The Table' },
|
||||
]);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.strictEqual(output.phases[0].display_name, 'Short');
|
||||
@@ -469,7 +469,7 @@ describe('init manager', () => {
|
||||
// Scaffold with a file — it will have current mtime (within 5 min)
|
||||
scaffoldPhase(tmpDir, 1, { slug: 'active-phase', context: true });
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.strictEqual(output.phases[0].is_active, true);
|
||||
@@ -488,8 +488,8 @@ describe('init manager', () => {
|
||||
fs.utimesSync(path.join(phaseDir, f), old, old);
|
||||
}
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir, {
|
||||
GSD_TEST_MODE: '1', GSD_NOW_MS: String(PINNED_NOW_MS),
|
||||
const result = runMsdTools('init manager', tmpDir, {
|
||||
MSD_TEST_MODE: '1', MSD_NOW_MS: String(PINNED_NOW_MS),
|
||||
});
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
@@ -508,7 +508,7 @@ describe('init manager', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
[
|
||||
'# Roadmap: GSD Core — Native OMP Runtime Support (v1.13)',
|
||||
'# Roadmap: MSD Core — Native OMP Runtime Support (v1.13)',
|
||||
'',
|
||||
'## Phases',
|
||||
'',
|
||||
@@ -526,7 +526,7 @@ describe('init manager', () => {
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
[
|
||||
'---',
|
||||
'gsd_state_version: 1.0',
|
||||
'msd_state_version: 1.0',
|
||||
'milestone: v1.13',
|
||||
'milestone_name: Native OMP Runtime Support',
|
||||
'status: planning',
|
||||
@@ -537,7 +537,7 @@ describe('init manager', () => {
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.milestone_version, 'v1.13');
|
||||
@@ -547,13 +547,13 @@ describe('init manager', () => {
|
||||
|
||||
// #3314 — ADR-456 subprocess clock pin: cmdInitManager's `is_active` gate
|
||||
// (nowMs - newestMtime < 300000) is CLI-subprocess tested, so only the
|
||||
// GSD_TEST_MODE+GSD_NOW_MS pin can control "now" here (t.mock.timers in the
|
||||
// MSD_TEST_MODE+MSD_NOW_MS pin can control "now" here (t.mock.timers in the
|
||||
// test process cannot reach the spawned child). newestMtime is set via
|
||||
// fs.utimesSync to an exact epoch offset from the pinned "now" so the
|
||||
// 300000ms boundary is provable exactly, not just "roughly recent"/"roughly old".
|
||||
describe('is_active boundary (#3314 — exactly 300000ms, condition is strict <)', () => {
|
||||
const PINNED_NOW_MS = 1_700_000_000_000; // 2023-11-14T22:13:20.000Z (second-aligned)
|
||||
const PIN_ENV = { GSD_TEST_MODE: '1', GSD_NOW_MS: String(PINNED_NOW_MS) };
|
||||
const PIN_ENV = { MSD_TEST_MODE: '1', MSD_NOW_MS: String(PINNED_NOW_MS) };
|
||||
|
||||
function scaffoldWithMtimeOffset(tmpDirLocal, offsetMs) {
|
||||
writeState(tmpDirLocal);
|
||||
@@ -568,21 +568,21 @@ describe('init manager', () => {
|
||||
|
||||
test('boundary: 299999ms since last activity → is_active true', () => {
|
||||
scaffoldWithMtimeOffset(tmpDir, 299_999);
|
||||
const result = runGsdTools('init manager', tmpDir, PIN_ENV);
|
||||
const result = runMsdTools('init manager', tmpDir, PIN_ENV);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phases[0].is_active, true);
|
||||
});
|
||||
|
||||
test('boundary: exactly 300000ms since last activity → is_active false', () => {
|
||||
scaffoldWithMtimeOffset(tmpDir, 300_000);
|
||||
const result = runGsdTools('init manager', tmpDir, PIN_ENV);
|
||||
const result = runMsdTools('init manager', tmpDir, PIN_ENV);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phases[0].is_active, false);
|
||||
});
|
||||
|
||||
test('boundary: 300001ms since last activity → is_active false', () => {
|
||||
scaffoldWithMtimeOffset(tmpDir, 300_001);
|
||||
const result = runGsdTools('init manager', tmpDir, PIN_ENV);
|
||||
const result = runMsdTools('init manager', tmpDir, PIN_ENV);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phases[0].is_active, false);
|
||||
});
|
||||
@@ -601,7 +601,7 @@ describe('init manager', () => {
|
||||
// Phase 3: planned and deps would be met if Phase 2 were complete, but it's not
|
||||
scaffoldPhase(tmpDir, 3, { slug: 'auth', context: true, plans: 1 });
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
// Phase 2 is partial — should NOT appear as execute recommendation (already running)
|
||||
@@ -623,7 +623,7 @@ describe('init manager', () => {
|
||||
// Phase 3: planned, no deps — independent of Phase 2
|
||||
scaffoldPhase(tmpDir, 3, { slug: 'notifications', context: true, plans: 1 });
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
// Phase 3 is independent of Phase 2 — should be recommended for execution
|
||||
@@ -636,7 +636,7 @@ describe('init manager', () => {
|
||||
writeState(tmpDir);
|
||||
writeRoadmap(tmpDir, [{ number: '1', name: 'Test' }]);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
// macOS resolves /var → /private/var; normalize both sides
|
||||
@@ -647,7 +647,7 @@ describe('init manager', () => {
|
||||
writeState(tmpDir);
|
||||
writeRoadmap(tmpDir, [{ number: '1', name: 'Test' }]);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.ok(output.manager_flags, 'should include manager_flags');
|
||||
@@ -674,7 +674,7 @@ describe('init manager', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.ok(output.manager_flags, 'should include manager_flags');
|
||||
@@ -700,7 +700,7 @@ describe('init manager', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
// Invalid flags should be sanitized to empty string
|
||||
@@ -723,7 +723,7 @@ describe('init manager', () => {
|
||||
fs.mkdirSync(backlogDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(backlogDir, '999.1-01-PLAN.md'), '# Backlog Plan');
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
@@ -745,7 +745,7 @@ describe('init manager', () => {
|
||||
JSON.stringify({ response_language: 'Japanese' })
|
||||
);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.strictEqual(output.response_language, 'Japanese');
|
||||
@@ -755,7 +755,7 @@ describe('init manager', () => {
|
||||
writeState(tmpDir);
|
||||
writeRoadmap(tmpDir, [{ number: '1', name: 'Test' }]);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
assert.strictEqual(output.response_language, undefined);
|
||||
@@ -775,7 +775,7 @@ describe('init manager', () => {
|
||||
writePassedVerification(scaffoldPhase(tmpDir, 2, { slug: 'core', plans: 1, summaries: 1 }), '02');
|
||||
writePassedVerification(scaffoldPhase(tmpDir, 3, { slug: 'polish', plans: 1, summaries: 1 }), '03');
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
@@ -795,7 +795,7 @@ describe('init manager', () => {
|
||||
writePassedVerification(scaffoldPhase(tmpDir, 2, { slug: 'core', plans: 1, summaries: 1 }), '02');
|
||||
// Phase 3 has no directory — should trigger discuss recommendation
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
@@ -870,7 +870,7 @@ describe('init manager — cross-milestone dependency satisfaction (#2267)', ()
|
||||
writeRoadmapWithShippedMilestone(tmpDir);
|
||||
writeStateWithMilestone(tmpDir, 'v2.0');
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
@@ -904,7 +904,7 @@ describe('init manager — cross-milestone dependency satisfaction (#2267)', ()
|
||||
].join('\n');
|
||||
fs.writeFileSync(roadmapPath, withExtra);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir);
|
||||
const result = runMsdTools('init manager', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
@@ -939,11 +939,11 @@ describe('init manager — state_path/roadmap_path/archive_dir (#4455)', () => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('flat mode (no GSD_WORKSTREAM): paths resolve to root .planning (regression guard)', () => {
|
||||
test('flat mode (no MSD_WORKSTREAM): paths resolve to root .planning (regression guard)', () => {
|
||||
writeState(tmpDir);
|
||||
writeRoadmap(tmpDir, [{ number: '1', name: 'Setup' }]);
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir, { GSD_WORKSTREAM: '' });
|
||||
const result = runMsdTools('init manager', tmpDir, { MSD_WORKSTREAM: '' });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
@@ -952,14 +952,14 @@ describe('init manager — state_path/roadmap_path/archive_dir (#4455)', () => {
|
||||
assert.strictEqual(output.archive_dir, absPlanningPath(tmpDir, 'milestones'));
|
||||
});
|
||||
|
||||
test('GSD_WORKSTREAM=alpha: paths resolve into the workstream, not root (#4455 regression)', () => {
|
||||
test('MSD_WORKSTREAM=alpha: paths resolve into the workstream, not root (#4455 regression)', () => {
|
||||
seedWorkstream(tmpDir, {
|
||||
name: 'alpha',
|
||||
state: '---\nstatus: active\n---\n# State\n',
|
||||
roadmap: '# Roadmap\n\n## Progress\n\n- [ ] **Phase 1: Setup**\n\n### Phase 1: Setup\n\n**Goal:** Bootstrap\n',
|
||||
});
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir, { GSD_WORKSTREAM: 'alpha' });
|
||||
const result = runMsdTools('init manager', tmpDir, { MSD_WORKSTREAM: 'alpha' });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
@@ -977,7 +977,7 @@ describe('init manager — state_path/roadmap_path/archive_dir (#4455)', () => {
|
||||
// past its own readiness guard, so exercise the null branch through
|
||||
// init complete-milestone instead — same fs.existsSync(...) ? ... : null
|
||||
// pattern, no upstream guard blocking an empty-fixture run.
|
||||
const result = runGsdTools('init complete-milestone', tmpDir);
|
||||
const result = runMsdTools('init complete-milestone', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
@@ -998,11 +998,11 @@ describe('init complete-milestone — state_path/roadmap_path/archive_dir (#4455
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('flat mode (no GSD_WORKSTREAM): paths resolve to root .planning (regression guard)', () => {
|
||||
test('flat mode (no MSD_WORKSTREAM): paths resolve to root .planning (regression guard)', () => {
|
||||
writeState(tmpDir);
|
||||
writeRoadmap(tmpDir, [{ number: '1', name: 'Setup' }]);
|
||||
|
||||
const result = runGsdTools('init complete-milestone', tmpDir, { GSD_WORKSTREAM: '' });
|
||||
const result = runMsdTools('init complete-milestone', tmpDir, { MSD_WORKSTREAM: '' });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
@@ -1011,14 +1011,14 @@ describe('init complete-milestone — state_path/roadmap_path/archive_dir (#4455
|
||||
assert.strictEqual(output.archive_dir, absPlanningPath(tmpDir, 'milestones'));
|
||||
});
|
||||
|
||||
test('GSD_WORKSTREAM=alpha: paths resolve into the workstream, not root (#4455 regression)', () => {
|
||||
test('MSD_WORKSTREAM=alpha: paths resolve into the workstream, not root (#4455 regression)', () => {
|
||||
seedWorkstream(tmpDir, {
|
||||
name: 'alpha',
|
||||
state: '---\nstatus: active\n---\n# State\n',
|
||||
roadmap: '# Roadmap\n\n## Progress\n\n- [ ] **Phase 1: Setup**\n\n### Phase 1: Setup\n\n**Goal:** Bootstrap\n',
|
||||
});
|
||||
|
||||
const result = runGsdTools('init complete-milestone', tmpDir, { GSD_WORKSTREAM: 'alpha' });
|
||||
const result = runMsdTools('init complete-milestone', tmpDir, { MSD_WORKSTREAM: 'alpha' });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
@@ -1053,7 +1053,7 @@ describe('init complete-milestone — milestones_path/project_path/requirements_
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'MILESTONES.md'), '# Milestones\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md'), '# Requirements\n');
|
||||
|
||||
const result = runGsdTools('init complete-milestone', tmpDir, { GSD_WORKSTREAM: '' });
|
||||
const result = runMsdTools('init complete-milestone', tmpDir, { MSD_WORKSTREAM: '' });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
@@ -1062,7 +1062,7 @@ describe('init complete-milestone — milestones_path/project_path/requirements_
|
||||
assert.strictEqual(output.requirements_path, absPlanningPath(tmpDir, 'REQUIREMENTS.md'));
|
||||
});
|
||||
|
||||
test('GSD_WORKSTREAM=alpha: milestones_path/requirements_path resolve into the workstream, but project_path STAYS root (PROJECT.md is shared, #4455 follow-up regression)', () => {
|
||||
test('MSD_WORKSTREAM=alpha: milestones_path/requirements_path resolve into the workstream, but project_path STAYS root (PROJECT.md is shared, #4455 follow-up regression)', () => {
|
||||
seedWorkstream(tmpDir, {
|
||||
name: 'alpha',
|
||||
state: '---\nstatus: active\n---\n# State\n',
|
||||
@@ -1070,13 +1070,13 @@ describe('init complete-milestone — milestones_path/project_path/requirements_
|
||||
});
|
||||
// PROJECT.md is only ever written at root — cmdWorkstreamCreate never
|
||||
// creates a per-workstream copy (it is a documented shared file, see
|
||||
// gsd-core/references/workstream-flag.md's directory diagram).
|
||||
// msd-core/references/workstream-flag.md's directory diagram).
|
||||
writeRootProjectMd(tmpDir);
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'workstreams', 'alpha'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'workstreams', 'alpha', 'MILESTONES.md'), '# Milestones\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'workstreams', 'alpha', 'REQUIREMENTS.md'), '# Requirements\n');
|
||||
|
||||
const result = runGsdTools('init complete-milestone', tmpDir, { GSD_WORKSTREAM: 'alpha' });
|
||||
const result = runMsdTools('init complete-milestone', tmpDir, { MSD_WORKSTREAM: 'alpha' });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
@@ -1089,21 +1089,21 @@ describe('init complete-milestone — milestones_path/project_path/requirements_
|
||||
assert.notStrictEqual(output.project_path, absPlanningPath(tmpDir, 'workstreams', 'alpha', 'PROJECT.md'));
|
||||
});
|
||||
|
||||
test('GSD_PROJECT=second-product: project_path resolves into the PROJECT namespace, not root (#3749 regression — round 1 of this fix broke this)', () => {
|
||||
test('MSD_PROJECT=second-product: project_path resolves into the PROJECT namespace, not root (#3749 regression — round 1 of this fix broke this)', () => {
|
||||
// The FIRST version of this #4455 follow-up resolved project_path via
|
||||
// planningRoot(cwd), which ignores GSD_PROJECT entirely — gsd-test caught
|
||||
// planningRoot(cwd), which ignores MSD_PROJECT entirely — msd-test caught
|
||||
// this immediately (tests/init.test.cjs's pre-existing #3749 coverage) on
|
||||
// this fix's own first push. PROJECT.md is shared across a project's own
|
||||
// WORKSTREAMS, but a DIFFERENT project (GSD_PROJECT) legitimately gets
|
||||
// WORKSTREAMS, but a DIFFERENT project (MSD_PROJECT) legitimately gets
|
||||
// its own separate PROJECT.md at `.planning/<project>/PROJECT.md`. The
|
||||
// correct resolution is planningDir(cwd, null) — `ws` explicitly nulled
|
||||
// (never read from GSD_WORKSTREAM), `project` left to default from
|
||||
// GSD_PROJECT.
|
||||
// (never read from MSD_WORKSTREAM), `project` left to default from
|
||||
// MSD_PROJECT.
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'second-product'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'second-product', 'PROJECT.md'), '# Second Product\n');
|
||||
writeRootProjectMd(tmpDir); // an unrelated root PROJECT.md must not win
|
||||
|
||||
const result = runGsdTools('init complete-milestone', tmpDir, { GSD_PROJECT: 'second-product' });
|
||||
const result = runMsdTools('init complete-milestone', tmpDir, { MSD_PROJECT: 'second-product' });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
@@ -1111,11 +1111,11 @@ describe('init complete-milestone — milestones_path/project_path/requirements_
|
||||
assert.notStrictEqual(output.project_path, absPlanningPath(tmpDir, 'PROJECT.md'));
|
||||
});
|
||||
|
||||
test('GSD_PROJECT=second-product AND GSD_WORKSTREAM=alpha together: project_path follows the PROJECT namespace, ignoring the workstream', () => {
|
||||
test('MSD_PROJECT=second-product AND MSD_WORKSTREAM=alpha together: project_path follows the PROJECT namespace, ignoring the workstream', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'second-product', 'workstreams', 'alpha'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'second-product', 'PROJECT.md'), '# Second Product\n');
|
||||
|
||||
const result = runGsdTools('init complete-milestone', tmpDir, { GSD_PROJECT: 'second-product', GSD_WORKSTREAM: 'alpha' });
|
||||
const result = runMsdTools('init complete-milestone', tmpDir, { MSD_PROJECT: 'second-product', MSD_WORKSTREAM: 'alpha' });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
@@ -1144,13 +1144,13 @@ describe('withProjectRoot — project_title reads PROJECT.md from root even unde
|
||||
test('flat mode: project_title is populated from the root PROJECT.md (regression guard)', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'PROJECT.md'), '# My Test Project\n\nSome content.\n');
|
||||
|
||||
const result = runGsdTools('init complete-milestone', tmpDir, { GSD_WORKSTREAM: '' });
|
||||
const result = runMsdTools('init complete-milestone', tmpDir, { MSD_WORKSTREAM: '' });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.project_title, 'My Test Project');
|
||||
});
|
||||
|
||||
test('GSD_WORKSTREAM=alpha: project_title is STILL populated from the root PROJECT.md, not silently dropped (#4455 follow-up regression)', () => {
|
||||
test('MSD_WORKSTREAM=alpha: project_title is STILL populated from the root PROJECT.md, not silently dropped (#4455 follow-up regression)', () => {
|
||||
seedWorkstream(tmpDir, {
|
||||
name: 'alpha',
|
||||
state: '---\nstatus: active\n---\n# State\n',
|
||||
@@ -1158,7 +1158,7 @@ describe('withProjectRoot — project_title reads PROJECT.md from root even unde
|
||||
});
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'PROJECT.md'), '# My Test Project\n\nSome content.\n');
|
||||
|
||||
const result = runGsdTools('init complete-milestone', tmpDir, { GSD_WORKSTREAM: 'alpha' });
|
||||
const result = runMsdTools('init complete-milestone', tmpDir, { MSD_WORKSTREAM: 'alpha' });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.project_title, 'My Test Project',
|
||||
@@ -1178,7 +1178,7 @@ describe('init subcommands sharing the project_exists/project_path PROJECT.md pa
|
||||
// withProjectRoot). `new-milestone` was originally deferred to #4456's own
|
||||
// new-milestone.md workstream-forwarding work (see PR #4543), but that PR
|
||||
// only exercised the *workflow's* `--ws` argv forwarding through a stubbed
|
||||
// gsd_run, never cmdInitNewMilestone's real project_exists/project_path
|
||||
// msd_run, never cmdInitNewMilestone's real project_exists/project_path
|
||||
// output — #4457 closes that gap by folding it into this loop; it has no
|
||||
// manager-style readiness precondition, so it fits the shared assertion
|
||||
// shape below without a dedicated test.
|
||||
@@ -1195,7 +1195,7 @@ describe('init subcommands sharing the project_exists/project_path PROJECT.md pa
|
||||
});
|
||||
|
||||
for (const subcommand of SUBCOMMANDS) {
|
||||
test(`GSD_WORKSTREAM=alpha: "init ${subcommand}" resolves project_path to the shared root PROJECT.md`, () => {
|
||||
test(`MSD_WORKSTREAM=alpha: "init ${subcommand}" resolves project_path to the shared root PROJECT.md`, () => {
|
||||
seedWorkstream(tmpDir, {
|
||||
name: 'alpha',
|
||||
state: '---\nstatus: active\n---\n# State\n',
|
||||
@@ -1203,7 +1203,7 @@ describe('init subcommands sharing the project_exists/project_path PROJECT.md pa
|
||||
});
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'PROJECT.md'), '# Test Project\n');
|
||||
|
||||
const result = runGsdTools(`init ${subcommand}`, tmpDir, { GSD_WORKSTREAM: 'alpha' });
|
||||
const result = runMsdTools(`init ${subcommand}`, tmpDir, { MSD_WORKSTREAM: 'alpha' });
|
||||
assert.ok(result.success, `init ${subcommand} failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
|
||||
@@ -1218,7 +1218,7 @@ describe('init subcommands sharing the project_exists/project_path PROJECT.md pa
|
||||
});
|
||||
}
|
||||
|
||||
test('milestone-op: GSD_WORKSTREAM=alpha does not report project_exists=false for a root-only PROJECT.md', () => {
|
||||
test('milestone-op: MSD_WORKSTREAM=alpha does not report project_exists=false for a root-only PROJECT.md', () => {
|
||||
// milestone-op does not expose a project_path field, only project_exists
|
||||
// via buildInitCompletenessFields — the coreComplete/init_incomplete fix.
|
||||
seedWorkstream(tmpDir, {
|
||||
@@ -1228,7 +1228,7 @@ describe('init subcommands sharing the project_exists/project_path PROJECT.md pa
|
||||
});
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'PROJECT.md'), '# Test Project\n');
|
||||
|
||||
const result = runGsdTools('init milestone-op', tmpDir, { GSD_WORKSTREAM: 'alpha' });
|
||||
const result = runMsdTools('init milestone-op', tmpDir, { MSD_WORKSTREAM: 'alpha' });
|
||||
assert.ok(result.success, `init milestone-op failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.project_exists, true,
|
||||
@@ -1272,7 +1272,7 @@ describe('init new-project: sub_repos_detected (#4458)', () => {
|
||||
assert.ok(fs.statSync(path.join(worktreeDir, '.git')).isFile(),
|
||||
'fixture setup: linked worktree .git must be a file, not a directory');
|
||||
|
||||
const result = runGsdTools('init new-project', tmpDir);
|
||||
const result = runMsdTools('init new-project', tmpDir);
|
||||
assert.ok(result.success, `init new-project failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.ok(Array.isArray(output.sub_repos_detected), 'sub_repos_detected must be an array');
|
||||
@@ -1288,7 +1288,7 @@ describe('init new-project: sub_repos_detected (#4458)', () => {
|
||||
const cloneDir = path.join(tmpDir, 'child-clone');
|
||||
fs.mkdirSync(path.join(cloneDir, '.git'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('init new-project', tmpDir);
|
||||
const result = runMsdTools('init new-project', tmpDir);
|
||||
assert.ok(result.success, `init new-project failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.ok(output.sub_repos_detected.includes('child-clone'),
|
||||
@@ -1299,7 +1299,7 @@ describe('init new-project: sub_repos_detected (#4458)', () => {
|
||||
tmpDir = fs.realpathSync(createTempProject());
|
||||
fs.mkdirSync(path.join(tmpDir, 'not-a-repo'));
|
||||
|
||||
const result = runGsdTools('init new-project', tmpDir);
|
||||
const result = runMsdTools('init new-project', tmpDir);
|
||||
assert.ok(result.success, `init new-project failed: ${result.error}`);
|
||||
const output = JSON.parse(result.output);
|
||||
assert.ok(!output.sub_repos_detected.includes('not-a-repo'),
|
||||
@@ -1317,12 +1317,12 @@ describe('init new-project: sub_repos_detected (#4458)', () => {
|
||||
/**
|
||||
* Regression tests for bug #3584 — runtime emitters use the slash formatter.
|
||||
*
|
||||
* These tests exercise the actual runtime command handlers via `runGsdTools`
|
||||
* These tests exercise the actual runtime command handlers via `runMsdTools`
|
||||
* and assert on the structured payloads they emit/persist. They prove that
|
||||
* the high-impact emitters identified in the issue (`init.cjs` recommended
|
||||
* actions, `phase.cjs` ROADMAP persistence, `verify.cjs` remediation hints,
|
||||
* `milestone.cjs` Operator-Next-Steps persistence, `validate-command-router`
|
||||
* fracture recommendations) no longer emit the unroutable `/gsd:<cmd>` colon
|
||||
* fracture recommendations) no longer emit the unroutable `/msd:<cmd>` colon
|
||||
* form for skills-based runtimes.
|
||||
*/
|
||||
|
||||
@@ -1334,21 +1334,21 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
runGsdTools,
|
||||
runMsdTools,
|
||||
createTempProject,
|
||||
createTempDir,
|
||||
cleanup,
|
||||
} = require('./helpers.cjs');
|
||||
|
||||
// Helper: assert no string field anywhere in `value` (recursive) contains '/gsd:'.
|
||||
// Helper: assert no string field anywhere in `value` (recursive) contains '/msd:'.
|
||||
function assertNoColonForm(value, label) {
|
||||
const stack = [{ v: value, p: label }];
|
||||
while (stack.length > 0) {
|
||||
const { v, p } = stack.pop();
|
||||
if (typeof v === 'string') {
|
||||
assert.ok(
|
||||
!v.includes('/gsd:'),
|
||||
`${p}: must not contain deprecated /gsd: form, got ${JSON.stringify(v)}`,
|
||||
!v.includes('/msd:'),
|
||||
`${p}: must not contain deprecated /msd: form, got ${JSON.stringify(v)}`,
|
||||
);
|
||||
} else if (Array.isArray(v)) {
|
||||
v.forEach((item, i) => stack.push({ v: item, p: `${p}[${i}]` }));
|
||||
@@ -1403,13 +1403,13 @@ describe('bug-3584: init manager recommendedActions emit hyphen form', () => {
|
||||
|
||||
afterEach(() => cleanup(tmpDir));
|
||||
|
||||
test('skills runtime (claude) emits /gsd-<cmd> in recommended_actions[].command', () => {
|
||||
test('skills runtime (claude) emits /msd-<cmd> in recommended_actions[].command', () => {
|
||||
// Plan-but-not-executed: directory exists with a PLAN.md → execute is recommended.
|
||||
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
||||
fs.mkdirSync(phaseDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(phaseDir, '01-01-PLAN.md'), '# Plan\n');
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir, { GSD_RUNTIME: 'claude' });
|
||||
const result = runMsdTools('init manager', tmpDir, { MSD_RUNTIME: 'claude' });
|
||||
assert.ok(result.success, `init manager failed: ${result.error || result.output}`);
|
||||
|
||||
const payload = JSON.parse(result.output);
|
||||
@@ -1418,23 +1418,23 @@ describe('bug-3584: init manager recommendedActions emit hyphen form', () => {
|
||||
|
||||
for (const cmd of commands) {
|
||||
assert.ok(
|
||||
cmd.startsWith('/gsd-'),
|
||||
`recommended_actions command must start with /gsd- for skills-based runtimes, got ${cmd}`,
|
||||
cmd.startsWith('/msd-'),
|
||||
`recommended_actions command must start with /msd- for skills-based runtimes, got ${cmd}`,
|
||||
);
|
||||
assert.ok(
|
||||
!cmd.includes('/gsd:'),
|
||||
`recommended_actions command must not contain /gsd: colon form, got ${cmd}`,
|
||||
!cmd.includes('/msd:'),
|
||||
`recommended_actions command must not contain /msd: colon form, got ${cmd}`,
|
||||
);
|
||||
}
|
||||
assertNoColonForm(payload, 'init.manager payload');
|
||||
});
|
||||
|
||||
test('codex runtime emits $gsd-<cmd> in recommended_actions[].command', () => {
|
||||
test('codex runtime emits $msd-<cmd> in recommended_actions[].command', () => {
|
||||
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
||||
fs.mkdirSync(phaseDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(phaseDir, '01-01-PLAN.md'), '# Plan\n');
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir, { GSD_RUNTIME: 'codex' });
|
||||
const result = runMsdTools('init manager', tmpDir, { MSD_RUNTIME: 'codex' });
|
||||
assert.ok(result.success, `init manager (codex) failed: ${result.error || result.output}`);
|
||||
|
||||
const payload = JSON.parse(result.output);
|
||||
@@ -1443,18 +1443,18 @@ describe('bug-3584: init manager recommendedActions emit hyphen form', () => {
|
||||
|
||||
for (const cmd of commands) {
|
||||
assert.ok(
|
||||
cmd.startsWith('$gsd-'),
|
||||
`codex recommended_actions command must use $gsd- shell-var form, got ${cmd}`,
|
||||
cmd.startsWith('$msd-'),
|
||||
`codex recommended_actions command must use $msd- shell-var form, got ${cmd}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('codex alias runtime emits $gsd-<cmd> in recommended_actions[].command', () => {
|
||||
test('codex alias runtime emits $msd-<cmd> in recommended_actions[].command', () => {
|
||||
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
||||
fs.mkdirSync(phaseDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(phaseDir, '01-01-PLAN.md'), '# Plan\n');
|
||||
|
||||
const result = runGsdTools('init manager', tmpDir, { GSD_RUNTIME: 'codex-app' });
|
||||
const result = runMsdTools('init manager', tmpDir, { MSD_RUNTIME: 'codex-app' });
|
||||
assert.ok(result.success, `init manager (codex-app) failed: ${result.error || result.output}`);
|
||||
|
||||
const payload = JSON.parse(result.output);
|
||||
@@ -1463,8 +1463,8 @@ describe('bug-3584: init manager recommendedActions emit hyphen form', () => {
|
||||
|
||||
for (const cmd of commands) {
|
||||
assert.ok(
|
||||
cmd.startsWith('$gsd-'),
|
||||
`codex alias recommended_actions command must use $gsd- shell-var form, got ${cmd}`,
|
||||
cmd.startsWith('$msd-'),
|
||||
`codex alias recommended_actions command must use $msd- shell-var form, got ${cmd}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -1489,10 +1489,10 @@ describe('bug-3584: phase add persists hyphen form into ROADMAP.md', () => {
|
||||
|
||||
test('phase add persists hyphen-form slash command in roadmap get-phase payload', () => {
|
||||
// 1. Add a phase — the system under test persists the phase entry into ROADMAP.md.
|
||||
const addResult = runGsdTools(
|
||||
const addResult = runMsdTools(
|
||||
['phase', 'add', 'Test new feature'],
|
||||
tmpDir,
|
||||
{ GSD_RUNTIME: 'claude' },
|
||||
{ MSD_RUNTIME: 'claude' },
|
||||
);
|
||||
assert.ok(addResult.success, `phase add failed: ${addResult.error || addResult.output}`);
|
||||
const addPayload = JSON.parse(addResult.output);
|
||||
@@ -1501,10 +1501,10 @@ describe('bug-3584: phase add persists hyphen form into ROADMAP.md', () => {
|
||||
// 2. Read the persisted section back via the structured roadmap-get-phase
|
||||
// contract (NOT via readFileSync; the `section` field on the JSON payload
|
||||
// is the runtime's typed projection of the on-disk ROADMAP content).
|
||||
const getResult = runGsdTools(
|
||||
const getResult = runMsdTools(
|
||||
['roadmap', 'get-phase', String(addPayload.phase_number)],
|
||||
tmpDir,
|
||||
{ GSD_RUNTIME: 'claude' },
|
||||
{ MSD_RUNTIME: 'claude' },
|
||||
);
|
||||
assert.ok(getResult.success, `roadmap get-phase failed: ${getResult.error || getResult.output}`);
|
||||
const getPayload = JSON.parse(getResult.output);
|
||||
@@ -1513,12 +1513,12 @@ describe('bug-3584: phase add persists hyphen form into ROADMAP.md', () => {
|
||||
|
||||
// 3. The persisted phase section must use the routable hyphen form.
|
||||
assert.ok(
|
||||
!getPayload.section.includes('/gsd:plan-phase'),
|
||||
`persisted phase section must not contain /gsd:plan-phase, got: ${getPayload.section}`,
|
||||
!getPayload.section.includes('/msd:plan-phase'),
|
||||
`persisted phase section must not contain /msd:plan-phase, got: ${getPayload.section}`,
|
||||
);
|
||||
assert.ok(
|
||||
getPayload.section.includes('/gsd-plan-phase'),
|
||||
`persisted phase section must contain /gsd-plan-phase, got: ${getPayload.section}`,
|
||||
getPayload.section.includes('/msd-plan-phase'),
|
||||
`persisted phase section must contain /msd-plan-phase, got: ${getPayload.section}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1530,10 +1530,10 @@ describe('bug-3584: validate health emits hyphen form in fix hints', () => {
|
||||
// No .planning directory → E001 fires with a `fix:` string that contains
|
||||
// the slash-command form.
|
||||
|
||||
const result = runGsdTools(
|
||||
const result = runMsdTools(
|
||||
['validate', 'health'],
|
||||
tmpDir,
|
||||
{ GSD_RUNTIME: 'claude' },
|
||||
{ MSD_RUNTIME: 'claude' },
|
||||
);
|
||||
// validate health exits non-zero on broken projects but still emits JSON to stdout.
|
||||
const stdout = result.output;
|
||||
@@ -1553,16 +1553,16 @@ describe('bug-3584: validate health emits hyphen form in fix hints', () => {
|
||||
|
||||
const fixesWithSlash = allIssues
|
||||
.map((i) => i.fix)
|
||||
.filter((f) => typeof f === 'string' && /gsd[-:]/.test(f));
|
||||
.filter((f) => typeof f === 'string' && /msd[-:]/.test(f));
|
||||
assert.ok(
|
||||
fixesWithSlash.length > 0,
|
||||
'at least one fix hint should reference a /gsd- slash command',
|
||||
'at least one fix hint should reference a /msd- slash command',
|
||||
);
|
||||
|
||||
for (const fix of fixesWithSlash) {
|
||||
assert.ok(
|
||||
!fix.includes('/gsd:'),
|
||||
`validate health fix hint must not contain /gsd: colon form, got ${JSON.stringify(fix)}`,
|
||||
!fix.includes('/msd:'),
|
||||
`validate health fix hint must not contain /msd: colon form, got ${JSON.stringify(fix)}`,
|
||||
);
|
||||
}
|
||||
assertNoColonForm(payload, 'validate health payload');
|
||||
@@ -1570,40 +1570,40 @@ describe('bug-3584: validate health emits hyphen form in fix hints', () => {
|
||||
});
|
||||
|
||||
describe('bug-3584: validate context recommendation uses hyphen form', () => {
|
||||
test('warning/critical recommendations emit /gsd-thread', (t) => {
|
||||
test('warning/critical recommendations emit /msd-thread', (t) => {
|
||||
const tmpDir = createTempProject();
|
||||
t.after(() => cleanup(tmpDir));
|
||||
|
||||
// Critical band: 75% utilization. validate context emits JSON only with --json.
|
||||
const result = runGsdTools(
|
||||
const result = runMsdTools(
|
||||
['validate', 'context', '--tokens-used', '75000', '--context-window', '100000', '--json'],
|
||||
tmpDir,
|
||||
{ GSD_RUNTIME: 'claude' },
|
||||
{ MSD_RUNTIME: 'claude' },
|
||||
);
|
||||
assert.ok(result.success, `validate context failed: ${result.error || result.output}`);
|
||||
|
||||
const payload = JSON.parse(result.output);
|
||||
assert.ok(payload.recommendation, 'critical utilization should produce a recommendation');
|
||||
assert.ok(
|
||||
!payload.recommendation.includes('/gsd:'),
|
||||
`recommendation must not contain /gsd:, got ${JSON.stringify(payload.recommendation)}`,
|
||||
!payload.recommendation.includes('/msd:'),
|
||||
`recommendation must not contain /msd:, got ${JSON.stringify(payload.recommendation)}`,
|
||||
);
|
||||
assert.ok(
|
||||
payload.recommendation.includes('/gsd-thread'),
|
||||
`recommendation must reference /gsd-thread, got ${JSON.stringify(payload.recommendation)}`,
|
||||
payload.recommendation.includes('/msd-thread'),
|
||||
`recommendation must reference /msd-thread, got ${JSON.stringify(payload.recommendation)}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bug-3584: validate health uses formatter for codex runtime too', () => {
|
||||
test('validate health under codex emits $gsd-<cmd> in fix strings (positive assertion)', (t) => {
|
||||
test('validate health under codex emits $msd-<cmd> in fix strings (positive assertion)', (t) => {
|
||||
const tmpDir = createTempDir();
|
||||
t.after(() => cleanup(tmpDir));
|
||||
|
||||
const result = runGsdTools(
|
||||
const result = runMsdTools(
|
||||
['validate', 'health'],
|
||||
tmpDir,
|
||||
{ GSD_RUNTIME: 'codex' },
|
||||
{ MSD_RUNTIME: 'codex' },
|
||||
);
|
||||
let payload;
|
||||
try {
|
||||
@@ -1617,29 +1617,29 @@ describe('bug-3584: validate health uses formatter for codex runtime too', () =>
|
||||
.concat(payload.warnings || [])
|
||||
.concat(payload.info || []);
|
||||
|
||||
// Collect fixes that mention any gsd slash-command form so we can lock
|
||||
// Collect fixes that mention any msd slash-command form so we can lock
|
||||
// both the absence of legacy forms AND the presence of the codex shape.
|
||||
const fixesWithGsdRef = allIssues
|
||||
const fixesWithMsdRef = allIssues
|
||||
.map((i) => i.fix)
|
||||
.filter((f) => typeof f === 'string' && /(?:\$|\/)gsd[-:]/.test(f));
|
||||
.filter((f) => typeof f === 'string' && /(?:\$|\/)msd[-:]/.test(f));
|
||||
|
||||
assert.ok(
|
||||
fixesWithGsdRef.length > 0,
|
||||
'validate health on a bare tmpdir must produce at least one fix hint referencing a gsd command',
|
||||
fixesWithMsdRef.length > 0,
|
||||
'validate health on a bare tmpdir must produce at least one fix hint referencing a msd command',
|
||||
);
|
||||
|
||||
for (const fix of fixesWithGsdRef) {
|
||||
for (const fix of fixesWithMsdRef) {
|
||||
assert.ok(
|
||||
fix.includes('$gsd-'),
|
||||
`codex validate health fix must use shell-var $gsd- form, got ${JSON.stringify(fix)}`,
|
||||
fix.includes('$msd-'),
|
||||
`codex validate health fix must use shell-var $msd- form, got ${JSON.stringify(fix)}`,
|
||||
);
|
||||
assert.ok(
|
||||
!fix.includes('/gsd:'),
|
||||
`codex validate health fix must not contain /gsd: colon form, got ${JSON.stringify(fix)}`,
|
||||
!fix.includes('/msd:'),
|
||||
`codex validate health fix must not contain /msd: colon form, got ${JSON.stringify(fix)}`,
|
||||
);
|
||||
assert.ok(
|
||||
!fix.includes('/gsd-'),
|
||||
`codex validate health fix must not contain /gsd- (skills) form, got ${JSON.stringify(fix)}`,
|
||||
!fix.includes('/msd-'),
|
||||
`codex validate health fix must not contain /msd- (skills) form, got ${JSON.stringify(fix)}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -1675,7 +1675,7 @@ describe('#4764 dep_phases extracts only Phase-prefixed references', () => {
|
||||
{ number: '654', name: 'Prior phase' },
|
||||
]);
|
||||
|
||||
const output = JSON.parse(runGsdTools('init manager', tmpDir).output);
|
||||
const output = JSON.parse(runMsdTools('init manager', tmpDir).output);
|
||||
const row = output.phases.find((p) => p.number === '663');
|
||||
|
||||
assert.deepEqual(row.dep_phases, ['654']);
|
||||
@@ -1691,7 +1691,7 @@ describe('#4764 dep_phases extracts only Phase-prefixed references', () => {
|
||||
{ number: '674', name: 'Ops current', depends_on: 'Nothing. Opened by the owner on 2026-09-15 after asking whether the operations app was current with the backend.' },
|
||||
]);
|
||||
|
||||
const output = JSON.parse(runGsdTools('init manager', tmpDir).output);
|
||||
const output = JSON.parse(runMsdTools('init manager', tmpDir).output);
|
||||
const row = output.phases.find((p) => p.number === '674');
|
||||
|
||||
assert.deepEqual(row.dep_phases, []);
|
||||
@@ -1705,7 +1705,7 @@ describe('#4764 dep_phases extracts only Phase-prefixed references', () => {
|
||||
{ number: '607', name: 'Sha phase', depends_on: 'Landed in 8bf403100d and 9445745c08; follow-up 76aea5c36f tracked separately.' },
|
||||
]);
|
||||
|
||||
const output = JSON.parse(runGsdTools('init manager', tmpDir).output);
|
||||
const output = JSON.parse(runMsdTools('init manager', tmpDir).output);
|
||||
const row = output.phases.find((p) => p.number === '607');
|
||||
|
||||
assert.deepEqual(row.dep_phases, []);
|
||||
@@ -1728,7 +1728,7 @@ describe('#4764 dep_phases extracts only Phase-prefixed references', () => {
|
||||
writePassedVerification(scaffoldPhase(tmpDir, n, { plans: 1, summaries: 1 }), `0${n}`);
|
||||
}
|
||||
|
||||
const output = JSON.parse(runGsdTools('init manager', tmpDir).output);
|
||||
const output = JSON.parse(runMsdTools('init manager', tmpDir).output);
|
||||
assert.deepEqual(output.phases.find((p) => p.number === '3').dep_phases, ['1', '2']);
|
||||
assert.deepEqual(output.phases.find((p) => p.number === '4').dep_phases, ['1', '2']);
|
||||
assert.deepEqual(output.phases.find((p) => p.number === '5').dep_phases, ['1', '2'], '& separator');
|
||||
@@ -1752,7 +1752,7 @@ describe('#4764 dep_phases extracts only Phase-prefixed references', () => {
|
||||
writePassedVerification(scaffoldPhase(tmpDir, n, { plans: 1, summaries: 1 }), `0${n}`);
|
||||
}
|
||||
|
||||
const output = JSON.parse(runGsdTools('init manager', tmpDir).output);
|
||||
const output = JSON.parse(runMsdTools('init manager', tmpDir).output);
|
||||
const row = output.phases.find((p) => p.number === '3');
|
||||
assert.deepEqual(row.dep_phases, ['1', '2']);
|
||||
assert.strictEqual(row.deps_satisfied, true);
|
||||
@@ -1769,7 +1769,7 @@ describe('#4764 dep_phases extracts only Phase-prefixed references', () => {
|
||||
]);
|
||||
writePassedVerification(scaffoldPhase(tmpDir, 1, { plans: 1, summaries: 1 }), '01');
|
||||
|
||||
const output = JSON.parse(runGsdTools('init manager', tmpDir).output);
|
||||
const output = JSON.parse(runMsdTools('init manager', tmpDir).output);
|
||||
const row = output.phases.find((p) => p.number === '4');
|
||||
assert.deepEqual(row.dep_phases, ['1', '3']);
|
||||
assert.strictEqual(row.deps_satisfied, false, '3 is incomplete — a real blocker must not be dropped');
|
||||
@@ -1777,7 +1777,7 @@ describe('#4764 dep_phases extracts only Phase-prefixed references', () => {
|
||||
|
||||
test('#4764 property: extraction pulls exactly the Phase-prefixed references out of arbitrary prose', () => {
|
||||
// House fast-check config (seed 42); per-call numRuns caps the cost — each
|
||||
// run spawns the real CLI (runGsdTools), unlike the pure-function properties.
|
||||
// run spawns the real CLI (runMsdTools), unlike the pure-function properties.
|
||||
const fc = require('./helpers/fast-check-setup.cjs');
|
||||
// Junk fragments whose digit runs the old whole-field scrape pulled in as
|
||||
// "dependencies": calendar dates, git shas, ledger ids, counts, round names.
|
||||
@@ -1804,7 +1804,7 @@ describe('#4764 dep_phases extracts only Phase-prefixed references', () => {
|
||||
{ number: '9', name: 'Property phase', depends_on: fragments.join('; ') + '.' },
|
||||
{ number: '1', name: 'Ref target', complete: true },
|
||||
]);
|
||||
const output = JSON.parse(runGsdTools('init manager', tmpDir).output);
|
||||
const output = JSON.parse(runMsdTools('init manager', tmpDir).output);
|
||||
const row = output.phases.find((p) => p.number === '9');
|
||||
const extracted = row.dep_phases.map((d) => String(d));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user