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:
@@ -10,7 +10,7 @@
|
||||
* 4. No *-VERIFICATION.md → 'missing'
|
||||
* 5. Frontmatter status present but unknown value → 'unknown'
|
||||
* 6. BROAD-GREP REGRESSION: body `status:` lines ignored, frontmatter wins
|
||||
* 7. PARITY: VERIFIER_STATUSES covered by routing table; gsd-verifier.md emitted statuses covered
|
||||
* 7. PARITY: VERIFIER_STATUSES covered by routing table; msd-verifier.md emitted statuses covered
|
||||
* 8. CRLF line endings in frontmatter
|
||||
* 9. Body-only file (no frontmatter block) → missing
|
||||
* 10. Nonexistent phase directory → missing
|
||||
@@ -42,7 +42,7 @@ const os = require('node:os');
|
||||
const { cleanup, createTempGitProject } = require('./helpers.cjs');
|
||||
const { runGit: seamRunGit, OUTCOME } = require('./helpers/process-seam.cjs');
|
||||
const { gitOrThrow } = require('./helpers/git-fixture.cjs');
|
||||
const { scanFencedBlocks } = require('../gsd-core/bin/lib/markdown-sectionizer.cjs');
|
||||
const { scanFencedBlocks } = require('../msd-core/bin/lib/markdown-sectionizer.cjs');
|
||||
|
||||
const {
|
||||
VERIFIER_STATUSES,
|
||||
@@ -58,7 +58,7 @@ const {
|
||||
isSharedPlanningDoc,
|
||||
parseFingerprintVersion,
|
||||
parseFingerprintFileArgs,
|
||||
} = require('../gsd-core/bin/lib/verification.cjs');
|
||||
} = require('../msd-core/bin/lib/verification.cjs');
|
||||
|
||||
// #3145: class-norm timeout, not a per-suite value — see helpers/timeouts.cjs.
|
||||
const { GIT_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
|
||||
@@ -74,7 +74,7 @@ const { GIT_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
|
||||
* @param {string} phaseDirName - basename of the phase dir (default '01-foo')
|
||||
*/
|
||||
function mkPhaseDir(suffix, phaseDirName = '01-foo') {
|
||||
const parent = fs.mkdtempSync(path.join(os.tmpdir(), `gsd-651-${suffix}-`));
|
||||
const parent = fs.mkdtempSync(path.join(os.tmpdir(), `msd-651-${suffix}-`));
|
||||
const phaseDir = path.join(parent, phaseDirName);
|
||||
fs.mkdirSync(phaseDir);
|
||||
return phaseDir;
|
||||
@@ -120,7 +120,7 @@ describe('verification-status', () => {
|
||||
// ── Case 2: gaps_found with phase token extraction ────────────────────────
|
||||
test('status: gaps_found in "03-foo" dir → next_command includes phase token 03', () => {
|
||||
// Phase dir basename starts with "03" — extractPhaseToken('03-foo') → '03'
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-651-parent-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-651-parent-'));
|
||||
const phaseDir = path.join(baseDir, '03-foo');
|
||||
fs.mkdirSync(phaseDir);
|
||||
try {
|
||||
@@ -135,7 +135,7 @@ describe('verification-status', () => {
|
||||
result.next_command.includes('--gaps'),
|
||||
`next_command should include --gaps; got: ${result.next_command}`,
|
||||
);
|
||||
assert.equal(result.next_command, '/gsd-plan-phase 03 --gaps');
|
||||
assert.equal(result.next_command, '/msd-plan-phase 03 --gaps');
|
||||
} finally {
|
||||
cleanup(baseDir);
|
||||
}
|
||||
@@ -157,7 +157,7 @@ describe('verification-status', () => {
|
||||
assert.equal(result.status, 'human_needed');
|
||||
// #2617: human_needed now names the command the next_action describes.
|
||||
// This fixture's dir is not phase-shaped, so no number is appended.
|
||||
assert.equal(result.next_command, '/gsd-verify-work');
|
||||
assert.equal(result.next_command, '/msd-verify-work');
|
||||
assert.ok(result.next_action.length > 0);
|
||||
} finally {
|
||||
cleanup(path.dirname(dir));
|
||||
@@ -175,7 +175,7 @@ describe('verification-status', () => {
|
||||
fs.writeFileSync(path.join(dir, 'README.md'), '# phase');
|
||||
const result = readVerificationStatus(dir);
|
||||
assert.equal(result.status, 'missing');
|
||||
assert.equal(result.next_command, '/gsd-execute-phase');
|
||||
assert.equal(result.next_command, '/msd-execute-phase');
|
||||
assert.ok(result.next_action.includes('verify step never completed'));
|
||||
assert.ok(
|
||||
result.next_action.includes('does not re-run plans that already have a SUMMARY.md'),
|
||||
@@ -195,7 +195,7 @@ describe('verification-status', () => {
|
||||
writeVerificationMd(dir, '01-u-VERIFICATION.md', 'bogus');
|
||||
const result = readVerificationStatus(dir);
|
||||
assert.equal(result.status, 'unknown');
|
||||
assert.equal(result.next_command, '/gsd-execute-phase');
|
||||
assert.equal(result.next_command, '/msd-execute-phase');
|
||||
assert.ok(
|
||||
result.next_action.includes('bogus'),
|
||||
`next_action should mention the raw value; got: ${result.next_action}`,
|
||||
@@ -258,7 +258,7 @@ describe('verification-status', () => {
|
||||
// ── Case 7: PARITY ASSERTION ──────────────────────────────────────────────
|
||||
//
|
||||
// (a) Every value in VERIFIER_STATUSES has a corresponding key in VERIFICATION_ROUTING_TABLE.
|
||||
// (b) Parse agents/gsd-verifier.md for emitted statuses via /→ \*\*status:\s*([a-z_]+)\*\*/g,
|
||||
// (b) Parse agents/msd-verifier.md for emitted statuses via /→ \*\*status:\s*([a-z_]+)\*\*/g,
|
||||
// collect the set, and assert every emitted status is a routing key.
|
||||
//
|
||||
test('PARITY: VERIFIER_STATUSES covered by routing table', () => {
|
||||
@@ -270,8 +270,8 @@ describe('verification-status', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('PARITY: gsd-verifier.md emitted statuses all have routing table entries', () => {
|
||||
const verifierPath = path.join(__dirname, '..', 'agents', 'gsd-verifier.md');
|
||||
test('PARITY: msd-verifier.md emitted statuses all have routing table entries', () => {
|
||||
const verifierPath = path.join(__dirname, '..', 'agents', 'msd-verifier.md');
|
||||
const content = fs.readFileSync(verifierPath, 'utf-8');
|
||||
|
||||
const emittedStatuses = new Set();
|
||||
@@ -298,15 +298,15 @@ describe('verification-status', () => {
|
||||
|
||||
assert.ok(
|
||||
emittedStatuses.size > 0,
|
||||
'No emitted statuses found in gsd-verifier.md — regex or file path may be wrong. ' +
|
||||
'No emitted statuses found in msd-verifier.md — regex or file path may be wrong. ' +
|
||||
'Checked: (a) → **status: X** arrow lines, (b) status: A | B | C template lines.',
|
||||
);
|
||||
|
||||
for (const s of emittedStatuses) {
|
||||
assert.ok(
|
||||
s in VERIFICATION_ROUTING_TABLE,
|
||||
`gsd-verifier.md emits status '${s}' but VERIFICATION_ROUTING_TABLE has no entry for it. ` +
|
||||
'Add a route or remove/rename the status in gsd-verifier.md.',
|
||||
`msd-verifier.md emits status '${s}' but VERIFICATION_ROUTING_TABLE has no entry for it. ` +
|
||||
'Add a route or remove/rename the status in msd-verifier.md.',
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -348,10 +348,10 @@ describe('verification-status', () => {
|
||||
|
||||
// Missing / nonexistent phase directory → missing
|
||||
test('nonexistent phase directory → missing', () => {
|
||||
const nonexistent = path.join(os.tmpdir(), 'gsd-651-nonexistent-' + Date.now());
|
||||
const nonexistent = path.join(os.tmpdir(), 'msd-651-nonexistent-' + Date.now());
|
||||
const result = readVerificationStatus(nonexistent);
|
||||
assert.equal(result.status, 'missing', 'unreadable/nonexistent dir must return missing');
|
||||
assert.equal(result.next_command, '/gsd-execute-phase');
|
||||
assert.equal(result.next_command, '/msd-execute-phase');
|
||||
});
|
||||
|
||||
// Multiple *-VERIFICATION.md files, NEITHER matching the phase dir's own
|
||||
@@ -385,7 +385,7 @@ describe('verification-status', () => {
|
||||
});
|
||||
|
||||
test('passed verification older than a summary returns stale', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-651-parent-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-651-parent-'));
|
||||
const dir = path.join(baseDir, '01-stale-passed');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -404,9 +404,9 @@ describe('verification-status', () => {
|
||||
// #4682: stale means covered source changed after the verifier ran — the
|
||||
// only remedy is re-running the verifier. execute-phase resumes at the
|
||||
// verification gates and re-runs it (its resume tree routes a stale
|
||||
// report to re-verification); /gsd-verify-work never rewrote the report,
|
||||
// report to re-verification); /msd-verify-work never rewrote the report,
|
||||
// so routing there was an advice loop.
|
||||
assert.equal(result.next_command, '/gsd-execute-phase 01');
|
||||
assert.equal(result.next_command, '/msd-execute-phase 01');
|
||||
assert.doesNotMatch(result.next_command, /verify-work/);
|
||||
assert.match(result.next_action, /verifier/i,
|
||||
'the stale action must name the verifier re-run as the remedy');
|
||||
@@ -416,7 +416,7 @@ describe('verification-status', () => {
|
||||
});
|
||||
|
||||
test('gaps_found verification older than a summary still returns gaps_found (not stale)', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-651-parent-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-651-parent-'));
|
||||
const dir = path.join(baseDir, '01-stale-gaps');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -429,14 +429,14 @@ describe('verification-status', () => {
|
||||
|
||||
const result = readVerificationStatus(dir);
|
||||
assert.equal(result.status, 'gaps_found');
|
||||
assert.equal(result.next_command, '/gsd-plan-phase 01 --gaps');
|
||||
assert.equal(result.next_command, '/msd-plan-phase 01 --gaps');
|
||||
} finally {
|
||||
cleanup(baseDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('human_needed verification older than nested plans/SUMMARY-NN.md returns stale', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-651-parent-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-651-parent-'));
|
||||
const dir = path.join(baseDir, '01-stale-human-nested');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -452,7 +452,7 @@ describe('verification-status', () => {
|
||||
// git times unavailable → mtime-fallback path (#2348).
|
||||
const result = readVerificationStatus(dir, { phaseCleanCommitTimesMs: () => new Map() });
|
||||
assert.equal(result.status, 'stale');
|
||||
assert.equal(result.next_command, '/gsd-execute-phase 01');
|
||||
assert.equal(result.next_command, '/msd-execute-phase 01');
|
||||
} finally {
|
||||
cleanup(baseDir);
|
||||
}
|
||||
@@ -486,7 +486,7 @@ describe('verification-status', () => {
|
||||
})();
|
||||
|
||||
test('committed passed verification is NOT stale from mtime skew alone when the summary was not committed later (#2348)', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2348-parent-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2348-parent-'));
|
||||
const dir = path.join(baseDir, '02-clone-skew');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -516,7 +516,7 @@ describe('verification-status', () => {
|
||||
});
|
||||
|
||||
test('committed verification IS stale when the summary was committed later, even if its mtime is older — git clock wins (#2348)', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2348-parent-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2348-parent-'));
|
||||
const dir = path.join(baseDir, '02-git-stale');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -534,7 +534,7 @@ describe('verification-status', () => {
|
||||
|
||||
const result = readVerificationStatus(dir, { phaseCleanCommitTimesMs });
|
||||
assert.equal(result.status, 'stale');
|
||||
assert.equal(result.next_command, '/gsd-execute-phase 02');
|
||||
assert.equal(result.next_command, '/msd-execute-phase 02');
|
||||
} finally {
|
||||
cleanup(baseDir);
|
||||
}
|
||||
@@ -547,7 +547,7 @@ describe('verification-status', () => {
|
||||
{ deltaMs: 0, expected: 'passed' },
|
||||
{ deltaMs: 1, expected: 'stale' },
|
||||
]) {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2348-boundary-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2348-boundary-'));
|
||||
const dir = path.join(baseDir, '03-boundary');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -575,7 +575,7 @@ describe('verification-status', () => {
|
||||
});
|
||||
|
||||
test('a committed-clean verification is stale when a summary is edited afterward (dirty) — the edit is not shadowed by the summary commit time (#2348 dirty regression)', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2348-dirty-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2348-dirty-'));
|
||||
const dir = path.join(baseDir, '02-dirty-summary');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -599,14 +599,14 @@ describe('verification-status', () => {
|
||||
'stale',
|
||||
'a dirty summary edited after the verification must stale it via mtime, not be shadowed by an equal/earlier commit time',
|
||||
);
|
||||
assert.equal(result.next_command, '/gsd-execute-phase 02');
|
||||
assert.equal(result.next_command, '/msd-execute-phase 02');
|
||||
} finally {
|
||||
cleanup(baseDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('both files uncommitted (no clean-commit time) fall back to mtime ordering (#2348)', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2348-uncommitted-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2348-uncommitted-'));
|
||||
const dir = path.join(baseDir, '02-uncommitted');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -627,7 +627,7 @@ describe('verification-status', () => {
|
||||
});
|
||||
|
||||
test('the git-commit-time resolver is invoked at most once per phase, regardless of summary count (#2348 no per-file fan-out)', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2348-fanout-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2348-fanout-'));
|
||||
const dir = path.join(baseDir, '01-fanout');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -652,7 +652,7 @@ describe('verification-status', () => {
|
||||
});
|
||||
|
||||
test('a phase with no summary files performs zero git walks and is never stale (#2348)', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2348-nosummary-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2348-nosummary-'));
|
||||
const dir = path.join(baseDir, '01-no-summary');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -675,7 +675,7 @@ describe('verification-status', () => {
|
||||
'real git: a summary committed after the verification reads stale via the real git clock, even for a dash-named file (#2348 end-to-end + `--` argv guard)',
|
||||
{ skip: GIT_AVAILABLE ? false : 'git binary not available' },
|
||||
() => {
|
||||
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2348-realgit-'));
|
||||
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2348-realgit-'));
|
||||
const runGit = (args, extraEnv) =>
|
||||
gitOrThrow(args, {
|
||||
cwd: repo,
|
||||
@@ -716,7 +716,7 @@ describe('verification-status', () => {
|
||||
'stale',
|
||||
'summary committed after the verification must read stale on the real git clock, and the dash-named file must resolve through the `--` pathspec guard',
|
||||
);
|
||||
assert.equal(result.next_command, '/gsd-execute-phase 01');
|
||||
assert.equal(result.next_command, '/msd-execute-phase 01');
|
||||
} finally {
|
||||
cleanup(repo);
|
||||
}
|
||||
@@ -727,7 +727,7 @@ describe('verification-status', () => {
|
||||
'real git: a committed summary edited on disk (dirty) reads stale via mtime, not shadowed by its commit time (#2348 dirty regression, end-to-end)',
|
||||
{ skip: GIT_AVAILABLE ? false : 'git binary not available' },
|
||||
() => {
|
||||
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2348-realgit-dirty-'));
|
||||
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2348-realgit-dirty-'));
|
||||
const runGit = (args, extraEnv) =>
|
||||
gitOrThrow(args, {
|
||||
cwd: repo,
|
||||
@@ -764,7 +764,7 @@ describe('verification-status', () => {
|
||||
'stale',
|
||||
'a committed-then-edited (dirty) summary must read stale via mtime, not be shadowed by its now-stale commit time',
|
||||
);
|
||||
assert.equal(result.next_command, '/gsd-execute-phase 01');
|
||||
assert.equal(result.next_command, '/msd-execute-phase 01');
|
||||
} finally {
|
||||
cleanup(repo);
|
||||
}
|
||||
@@ -859,7 +859,7 @@ describe('verification-status', () => {
|
||||
// sentinel (its user-visible gate error key). This test re-anchors that contract.
|
||||
//
|
||||
test('ship.md still emits the PHASE_VERIFICATION_INCOMPLETE gate sentinel (contract anchor for #651 consolidation)', () => {
|
||||
const shipMdPath = path.join(__dirname, '..', 'gsd-core', 'workflows', 'ship.md');
|
||||
const shipMdPath = path.join(__dirname, '..', 'msd-core', 'workflows', 'ship.md');
|
||||
const content = fs.readFileSync(shipMdPath, 'utf-8');
|
||||
assert.ok(
|
||||
content.includes('PHASE_VERIFICATION_INCOMPLETE'),
|
||||
@@ -1086,7 +1086,7 @@ describe('#3357/#3492: phase-pinned *-VERIFICATION.md resolution when multiple c
|
||||
});
|
||||
|
||||
test('behavioral (readVerificationStatus): a phase dir holding only a cross-phase stray reports missing, not the stray\'s status (#3511)', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3511-stray-only-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3511-stray-only-'));
|
||||
const dir = path.join(baseDir, '03-foo');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -1107,7 +1107,7 @@ describe('#3357/#3492: phase-pinned *-VERIFICATION.md resolution when multiple c
|
||||
});
|
||||
|
||||
test('behavioral (readVerificationStatus): a phase dir holding only its own non-canonically-named report still resolves it (#3357 guarantee survives #3511 scoping)', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3511-own-noncanon-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3511-own-noncanon-'));
|
||||
const dir = path.join(baseDir, '03-foo');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -1129,7 +1129,7 @@ describe('#3357/#3492: phase-pinned *-VERIFICATION.md resolution when multiple c
|
||||
// derives token "03" — the exact same derivation readVerificationStatus
|
||||
// performs internally, so this exercises the real production call path
|
||||
// (not just the pure resolver), pinned to counterexample 2's shape.
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3492-parent-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3492-parent-'));
|
||||
const dir = path.join(baseDir, '03-canonical-test');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -1151,7 +1151,7 @@ describe('#3357/#3492: phase-pinned *-VERIFICATION.md resolution when multiple c
|
||||
});
|
||||
|
||||
test('behavioral (readVerificationStatus): a phase with both its own canonical report and an ad-hoc worksheet reports the canonical report\'s status, not missing (#3357 original regression)', () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3357-parent-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3357-parent-'));
|
||||
const dir = path.join(baseDir, '03-canonical-test');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -1180,7 +1180,7 @@ describe('#3357/#3492: phase-pinned *-VERIFICATION.md resolution when multiple c
|
||||
// A stray cross-phase file ("99-VERIFICATION.md") is alphabetically AFTER
|
||||
// this phase's own "03-VERIFICATION.md", so this also demonstrates the
|
||||
// pin is not merely riding on alphabetical luck.
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3492-stale-parent-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3492-stale-parent-'));
|
||||
const dir = path.join(baseDir, '03-stale-pin-test');
|
||||
fs.mkdirSync(dir);
|
||||
try {
|
||||
@@ -1318,7 +1318,7 @@ describe('#4187: status surface recognizes a bare VERIFICATION.md', () => {
|
||||
writeVerificationMd(dir, 'VERIFICATION.md', 'human_needed');
|
||||
const result = readVerificationStatus(dir, { phaseCleanCommitTimesMs: () => new Map() });
|
||||
assert.equal(result.status, 'human_needed');
|
||||
assert.equal(result.next_command, '/gsd-verify-work 99');
|
||||
assert.equal(result.next_command, '/msd-verify-work 99');
|
||||
});
|
||||
|
||||
test('#4187: bare report with status: gaps_found routes to plan-phase --gaps', (t) => {
|
||||
@@ -1328,7 +1328,7 @@ describe('#4187: status surface recognizes a bare VERIFICATION.md', () => {
|
||||
writeVerificationMd(dir, 'VERIFICATION.md', 'gaps_found');
|
||||
const result = readVerificationStatus(dir, { phaseCleanCommitTimesMs: () => new Map() });
|
||||
assert.equal(result.status, 'gaps_found');
|
||||
assert.equal(result.next_command, '/gsd-plan-phase 99 --gaps');
|
||||
assert.equal(result.next_command, '/msd-plan-phase 99 --gaps');
|
||||
});
|
||||
|
||||
test('#4187 negative space: no verification file at all still reads missing with the execute-phase recommendation', (t) => {
|
||||
@@ -1337,7 +1337,7 @@ describe('#4187: status surface recognizes a bare VERIFICATION.md', () => {
|
||||
|
||||
const result = readVerificationStatus(dir);
|
||||
assert.equal(result.status, 'missing', 'a genuinely absent report must stay missing');
|
||||
assert.equal(result.next_command, '/gsd-execute-phase 99', 'the missing recommendation is correct here and must not change');
|
||||
assert.equal(result.next_command, '/msd-execute-phase 99', 'the missing recommendation is correct here and must not change');
|
||||
});
|
||||
|
||||
test('#4187 parity: bare + canonical dashed report — the dashed file wins on the status surface too', (t) => {
|
||||
@@ -1370,7 +1370,7 @@ describe('#4187: status surface recognizes a bare VERIFICATION.md', () => {
|
||||
});
|
||||
|
||||
test('#4187: directory scoping — a bare report in a DIFFERENT directory does not leak into the queried one', (t) => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4187-otherdir-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4187-otherdir-'));
|
||||
t.after(() => cleanup(baseDir));
|
||||
const queried = path.join(baseDir, '99-probe');
|
||||
const neighbor = path.join(baseDir, '98-other');
|
||||
@@ -1404,7 +1404,7 @@ describe('#4187: status surface recognizes a bare VERIFICATION.md', () => {
|
||||
|
||||
const result = readVerificationStatus(dir, { phaseCleanCommitTimesMs: () => new Map() });
|
||||
assert.equal(result.status, 'stale', 'a bare report must be staleness-checked like a dashed one');
|
||||
assert.equal(result.next_command, '/gsd-execute-phase 99');
|
||||
assert.equal(result.next_command, '/msd-execute-phase 99');
|
||||
});
|
||||
|
||||
test('#4187 unit (findStaleVerificationSummary): staleness is computed against the bare report', (t) => {
|
||||
@@ -1433,7 +1433,7 @@ describe('#4187: status surface recognizes a bare VERIFICATION.md', () => {
|
||||
// stray in that same directory.
|
||||
describe('#4142: opts.convention threads through findStaleVerificationSummary', () => {
|
||||
test('a bracket phase checks staleness against its own report, not a newer cross-phase stray', (t) => {
|
||||
const dir = mkPhaseDir('bracket-stale-thread', 'GSD.02-03-three');
|
||||
const dir = mkPhaseDir('bracket-stale-thread', 'MSD.02-03-three');
|
||||
t.after(() => cleanup(path.dirname(dir)));
|
||||
|
||||
writeVerificationMd(dir, '03-VERIFICATION.md', 'passed');
|
||||
@@ -1454,7 +1454,7 @@ describe('#4142: opts.convention threads through findStaleVerificationSummary',
|
||||
'stale',
|
||||
'opts.convention must reach the legacy staleness seam so phase 03 is compared to 03-VERIFICATION.md',
|
||||
);
|
||||
assert.equal(result.next_command, '/gsd-execute-phase');
|
||||
assert.equal(result.next_command, '/msd-execute-phase');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1465,22 +1465,22 @@ describe('#4142: opts.convention threads through findStaleVerificationSummary',
|
||||
// cross-phase report cannot satisfy the gate merely by sitting in the bracket
|
||||
// phase's directory.
|
||||
describe('#4142: phase complete verdict scopes bracket verification reports', () => {
|
||||
const { runGsdTools } = require('./helpers.cjs');
|
||||
const { runMsdTools } = require('./helpers.cjs');
|
||||
|
||||
test('a passed cross-phase stray cannot complete a bracket phase', (t) => {
|
||||
const projectDir = createTempGitProject('gsd-4142-phase-complete-');
|
||||
const projectDir = createTempGitProject('msd-4142-phase-complete-');
|
||||
t.after(() => cleanup(projectDir));
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(projectDir, '.planning', 'config.json'),
|
||||
JSON.stringify({ phase_id_convention: 'bracket' }, null, 2),
|
||||
);
|
||||
const phaseDirName = 'GSD.02-03-three';
|
||||
const phaseDirName = 'MSD.02-03-three';
|
||||
const phaseDir = path.join(projectDir, '.planning', 'phases', phaseDirName);
|
||||
fs.mkdirSync(phaseDir, { recursive: true });
|
||||
writeVerificationMd(phaseDir, '01-VERIFICATION.md', 'passed');
|
||||
|
||||
const result = runGsdTools(
|
||||
const result = runMsdTools(
|
||||
['--json-errors', 'phase', 'complete', phaseDirName],
|
||||
projectDir,
|
||||
);
|
||||
@@ -1504,11 +1504,11 @@ describe('#4142: phase complete verdict scopes bracket verification reports', ()
|
||||
// frontmatter statuses and asserting the routed status matches the expected
|
||||
// winner).
|
||||
describe('#4187 CLI parity: verification.resolve-file and verification.status agree', () => {
|
||||
const { runGsdTools } = require('./helpers.cjs');
|
||||
const { runMsdTools } = require('./helpers.cjs');
|
||||
|
||||
function runBothVerbs(dir) {
|
||||
const resolveRes = runGsdTools(['query', 'verification.resolve-file', dir, '--raw'], dir);
|
||||
const statusRes = runGsdTools(['query', 'verification.status', dir, '--raw'], dir);
|
||||
const resolveRes = runMsdTools(['query', 'verification.resolve-file', dir, '--raw'], dir);
|
||||
const statusRes = runMsdTools(['query', 'verification.status', dir, '--raw'], dir);
|
||||
assert.equal(resolveRes.success, true, `resolve-file failed: ${resolveRes.error}`);
|
||||
assert.equal(statusRes.success, true, `status failed: ${statusRes.error}`);
|
||||
return { resolvedPath: resolveRes.output.trimEnd(), status: JSON.parse(statusRes.output).status, nextCommand: JSON.parse(statusRes.output).next_command };
|
||||
@@ -1688,7 +1688,7 @@ describe('#3518: call-site guard — every *-UAT.md single-pick routes through r
|
||||
|
||||
describe('#3057 B3: staleness check — indeterminate is distinguishable from not-stale', () => {
|
||||
test('an fs failure inside the staleness check yields staleCheckIndeterminate:true, not a silent "not stale"', (t) => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3057-b3-fault-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3057-b3-fault-'));
|
||||
t.after(() => cleanup(baseDir));
|
||||
const dir = path.join(baseDir, '01-stale-check-fault');
|
||||
fs.mkdirSync(dir);
|
||||
@@ -1729,7 +1729,7 @@ describe('#3057 B3: staleness check — indeterminate is distinguishable from no
|
||||
});
|
||||
|
||||
test('a completed staleness check that finds nothing stale never reports indeterminate', (t) => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3057-b3-ok-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3057-b3-ok-'));
|
||||
t.after(() => cleanup(baseDir));
|
||||
const dir = path.join(baseDir, '01-stale-check-ok');
|
||||
fs.mkdirSync(dir);
|
||||
@@ -1764,7 +1764,7 @@ describe('#3057 B3: staleness check — indeterminate is distinguishable from no
|
||||
|
||||
describe('#4155: computeCoveredDigest — direct unit coverage', () => {
|
||||
test('same covered set, different array order → identical digest (order-independent)', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-order-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-order-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.writeFileSync(path.join(root, 'a.txt'), 'A');
|
||||
fs.writeFileSync(path.join(root, 'b.txt'), 'B');
|
||||
@@ -1777,7 +1777,7 @@ describe('#4155: computeCoveredDigest — direct unit coverage', () => {
|
||||
});
|
||||
|
||||
test('a "./"-prefixed path and its bare equivalent → identical digest (canonicalized, not double-counted)', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-dotslash-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-dotslash-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.writeFileSync(path.join(root, 'impl.txt'), 'content');
|
||||
|
||||
@@ -1791,7 +1791,7 @@ describe('#4155: computeCoveredDigest — direct unit coverage', () => {
|
||||
});
|
||||
|
||||
test('an internal ".." segment disguising an escape (not just a leading one) → null (fail closed)', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-internal-dotdot-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-internal-dotdot-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.mkdirSync(path.join(root, 'a'));
|
||||
fs.writeFileSync(path.join(path.dirname(root), 'outside.txt'), 'secret');
|
||||
@@ -1801,7 +1801,7 @@ describe('#4155: computeCoveredDigest — direct unit coverage', () => {
|
||||
});
|
||||
|
||||
test('a covered file whose content changes → digest changes', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-changed-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-changed-'));
|
||||
t.after(() => cleanup(root));
|
||||
const target = path.join(root, 'impl.txt');
|
||||
fs.writeFileSync(target, 'before');
|
||||
@@ -1812,27 +1812,27 @@ describe('#4155: computeCoveredDigest — direct unit coverage', () => {
|
||||
});
|
||||
|
||||
test('a covered file that is missing → null (fail closed)', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-missing-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-missing-'));
|
||||
t.after(() => cleanup(root));
|
||||
assert.equal(computeCoveredDigest(root, ['does-not-exist.txt']), null);
|
||||
});
|
||||
|
||||
test('a covered file that is unreadable (directory, not a regular file) → null (fail closed)', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-unreadable-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-unreadable-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.mkdirSync(path.join(root, 'a-directory'));
|
||||
assert.equal(computeCoveredDigest(root, ['a-directory']), null);
|
||||
});
|
||||
|
||||
test('a covered path that escapes the project root via ".." → null (fail closed)', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-escape-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-escape-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.writeFileSync(path.join(path.dirname(root), 'outside.txt'), 'secret');
|
||||
assert.equal(computeCoveredDigest(root, ['../outside.txt']), null);
|
||||
});
|
||||
|
||||
test('an absolute covered path → null (fail closed)', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-absolute-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-absolute-'));
|
||||
t.after(() => cleanup(root));
|
||||
const absolute = path.join(root, 'impl.txt');
|
||||
fs.writeFileSync(absolute, 'x');
|
||||
@@ -1840,15 +1840,15 @@ describe('#4155: computeCoveredDigest — direct unit coverage', () => {
|
||||
});
|
||||
|
||||
test('an empty covered-files array → null', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-empty-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-empty-'));
|
||||
t.after(() => cleanup(root));
|
||||
assert.equal(computeCoveredDigest(root, []), null);
|
||||
});
|
||||
|
||||
test('an in-root symlink whose TARGET escapes the project root → null (fail closed, not the target\'s content)', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-symlink-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-symlink-'));
|
||||
t.after(() => cleanup(root));
|
||||
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-symlink-outside-'));
|
||||
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-symlink-outside-'));
|
||||
t.after(() => cleanup(outside));
|
||||
const outsideFile = path.join(outside, 'secret.txt');
|
||||
fs.writeFileSync(outsideFile, 'not covered by this project');
|
||||
@@ -1925,7 +1925,7 @@ describe('#4155: readVerificationStatus — fingerprint supersedes legacy mtime
|
||||
});
|
||||
|
||||
test('unchanged covered inputs → status stays passed even though a SUMMARY is newer (legacy mtime check bypassed)', (t) => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-unchanged-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-unchanged-'));
|
||||
t.after(() => cleanup(baseDir));
|
||||
const dir = path.join(baseDir, '01-foo');
|
||||
fs.mkdirSync(dir);
|
||||
@@ -1950,7 +1950,7 @@ describe('#4155: readVerificationStatus — fingerprint supersedes legacy mtime
|
||||
});
|
||||
|
||||
test('a plan or summary added to the phase dir after verification, never declared in covered_files → stale (#4155 completeness check)', (t) => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-uncovered-artifact-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-uncovered-artifact-'));
|
||||
t.after(() => cleanup(baseDir));
|
||||
const dir = path.join(baseDir, '01-foo');
|
||||
fs.mkdirSync(dir);
|
||||
@@ -1970,7 +1970,7 @@ describe('#4155: readVerificationStatus — fingerprint supersedes legacy mtime
|
||||
});
|
||||
|
||||
test('an unreadable nested plans/ dir fails CLOSED to stale, not open to passed (#4155 review finding: allCurrentArtifactsCovered must branch on scanPhasePlans scope, not a try/catch it never throws into)', (t) => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-scan-unreadable-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-scan-unreadable-'));
|
||||
t.after(() => cleanup(baseDir));
|
||||
const dir = path.join(baseDir, '01-foo');
|
||||
fs.mkdirSync(dir);
|
||||
@@ -2017,7 +2017,7 @@ describe('#4155: readVerificationStatus — fingerprint supersedes legacy mtime
|
||||
});
|
||||
|
||||
test('a covered file that changed after verification → status is stale', (t) => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-stale-changed-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-stale-changed-'));
|
||||
t.after(() => cleanup(baseDir));
|
||||
const dir = path.join(baseDir, '01-foo');
|
||||
fs.mkdirSync(dir);
|
||||
@@ -2033,7 +2033,7 @@ describe('#4155: readVerificationStatus — fingerprint supersedes legacy mtime
|
||||
|
||||
const result = readVerificationStatus(dir, { phaseCleanCommitTimesMs: () => new Map() });
|
||||
assert.equal(result.status, 'stale');
|
||||
assert.equal(result.next_command, '/gsd-execute-phase 01');
|
||||
assert.equal(result.next_command, '/msd-execute-phase 01');
|
||||
});
|
||||
|
||||
// Ponytail #4155 review finding: "disappeared" and "escapes confinement"
|
||||
@@ -2063,7 +2063,7 @@ describe('#4155: readVerificationStatus — fingerprint supersedes legacy mtime
|
||||
],
|
||||
]) {
|
||||
test(`${name} → stale (fail closed, not a silent legacy downgrade)`, (t) => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-malformed-fingerprint-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-malformed-fingerprint-'));
|
||||
t.after(() => cleanup(baseDir));
|
||||
const dir = path.join(baseDir, '01-foo');
|
||||
fs.mkdirSync(dir);
|
||||
@@ -2075,7 +2075,7 @@ describe('#4155: readVerificationStatus — fingerprint supersedes legacy mtime
|
||||
}
|
||||
|
||||
test('a legacy report with no fingerprint metadata still uses the mtime check', (t) => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4155-legacy-'));
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4155-legacy-'));
|
||||
t.after(() => cleanup(baseDir));
|
||||
const dir = path.join(baseDir, '01-foo');
|
||||
fs.mkdirSync(dir);
|
||||
@@ -2092,7 +2092,7 @@ describe('#4155: readVerificationStatus — fingerprint supersedes legacy mtime
|
||||
});
|
||||
|
||||
describe('#4155: verification.fingerprint CLI', () => {
|
||||
const { runGsdTools, createTempGitProject } = require('./helpers.cjs');
|
||||
const { runMsdTools, createTempGitProject } = require('./helpers.cjs');
|
||||
|
||||
test('emits a covered_digest matching the direct computeCoveredDigest call, sorted covered_files', () => {
|
||||
const projectDir = createTempGitProject();
|
||||
@@ -2102,7 +2102,7 @@ describe('#4155: verification.fingerprint CLI', () => {
|
||||
fs.writeFileSync(path.join(phaseDir, '01-01-PLAN.md'), '# Plan\n');
|
||||
fs.writeFileSync(path.join(phaseDir, '01-01-SUMMARY.md'), '# Summary\n');
|
||||
|
||||
const res = runGsdTools(
|
||||
const res = runMsdTools(
|
||||
[
|
||||
'verification',
|
||||
'fingerprint',
|
||||
@@ -2134,7 +2134,7 @@ describe('#4155: verification.fingerprint CLI', () => {
|
||||
const phaseDir = path.join(projectDir, '.planning', 'phases', '01-example');
|
||||
fs.mkdirSync(phaseDir, { recursive: true });
|
||||
|
||||
const res = runGsdTools(
|
||||
const res = runMsdTools(
|
||||
['verification', 'fingerprint', phaseDir, 'does-not-exist.md'],
|
||||
projectDir,
|
||||
);
|
||||
@@ -2165,7 +2165,7 @@ describe('#4155: verification.fingerprint CLI', () => {
|
||||
'.planning/phases/01-example/01-01-SUMMARY.md',
|
||||
'src/thing.cts',
|
||||
];
|
||||
const fpRes = runGsdTools(['verification', 'fingerprint', phaseDir, ...coveredFiles], projectDir);
|
||||
const fpRes = runMsdTools(['verification', 'fingerprint', phaseDir, ...coveredFiles], projectDir);
|
||||
assert.equal(fpRes.success, true, `expected success, got: ${fpRes.output}${fpRes.error}`);
|
||||
const { covered_files: sortedCovered, covered_digest: digest } = JSON.parse(fpRes.output);
|
||||
|
||||
@@ -2273,7 +2273,7 @@ describe('#4623: isSharedPlanningDoc — a repo-wide planning document is a DIRE
|
||||
assert.equal(isSharedPlanningDoc('.planning/workstreams/w/ROADMAP.md', roots), true);
|
||||
assert.equal(isSharedPlanningDoc('.planning/workstreams/w/phases/01-a/01-01-PLAN.md', roots), false);
|
||||
assert.equal(isSharedPlanningDoc('.planning/workstreams/w/ROADMAP.md'), false, 'unknown root without the phase dir');
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4623-roots-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4623-roots-'));
|
||||
t.after(() => cleanup(root));
|
||||
assert.deepEqual(sharedPlanningRoots(root), ['.planning']);
|
||||
assert.deepEqual(sharedPlanningRoots(root, path.join(root, '.planning', 'phases', '01-a')), ['.planning']);
|
||||
@@ -2359,7 +2359,7 @@ describe('#4623: parseFingerprintFileArgs — every --files form the issue tried
|
||||
|
||||
describe('#4623: computeCoveredDigest v2 — shared planning documents do not enter the digest', () => {
|
||||
test('defaults to v2 and names the version in the digest', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4623-version-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4623-version-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.writeFileSync(path.join(root, 'impl.txt'), 'x');
|
||||
assert.match(computeCoveredDigest(root, ['impl.txt']), /^v2:sha256:[0-9a-f]{64}$/);
|
||||
@@ -2368,7 +2368,7 @@ describe('#4623: computeCoveredDigest v2 — shared planning documents do not en
|
||||
});
|
||||
|
||||
test('an unknown version → null (fail closed, never a digest under guessed semantics)', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4623-unknown-version-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4623-unknown-version-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.writeFileSync(path.join(root, 'impl.txt'), 'x');
|
||||
assert.equal(computeCoveredDigest(root, ['impl.txt'], 9), null);
|
||||
@@ -2376,7 +2376,7 @@ describe('#4623: computeCoveredDigest v2 — shared planning documents do not en
|
||||
});
|
||||
|
||||
test('a byte change to .planning/ROADMAP.md or REQUIREMENTS.md leaves the v2 digest unchanged; a phase artifact or implementation change still moves it', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4623-shared-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4623-shared-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.mkdirSync(path.join(root, '.planning', 'phases', '01-a'), { recursive: true });
|
||||
fs.mkdirSync(path.join(root, 'src'));
|
||||
@@ -2398,7 +2398,7 @@ describe('#4623: computeCoveredDigest v2 — shared planning documents do not en
|
||||
});
|
||||
|
||||
test('a nested planning file (.planning/research/…) is NOT shared and still moves the digest', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4623-nested-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4623-nested-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.mkdirSync(path.join(root, '.planning', 'research'), { recursive: true });
|
||||
const note = path.join(root, '.planning', 'research', 'notes.md');
|
||||
@@ -2409,7 +2409,7 @@ describe('#4623: computeCoveredDigest v2 — shared planning documents do not en
|
||||
});
|
||||
|
||||
test('a declared shared document is validated like any other path — present: inert; missing, a directory, or an escaping symlink: null (fail closed, as v1)', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4623-shared-validated-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4623-shared-validated-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.mkdirSync(path.join(root, '.planning', 'phases'), { recursive: true });
|
||||
fs.writeFileSync(path.join(root, 'impl.txt'), 'x');
|
||||
@@ -2423,7 +2423,7 @@ describe('#4623: computeCoveredDigest v2 — shared planning documents do not en
|
||||
assert.equal(computeCoveredDigest(root, ['impl.txt', '.planning/phases']), null);
|
||||
// An in-root symlink whose target escapes → null, as v1 (nothing is read either way,
|
||||
// but the declaration is still an escape and still invalidates the set).
|
||||
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4623-shared-outside-'));
|
||||
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4623-shared-outside-'));
|
||||
t.after(() => cleanup(outside));
|
||||
fs.writeFileSync(path.join(outside, 'secret.txt'), 'not this project');
|
||||
fs.symlinkSync(path.join(outside, 'secret.txt'), path.join(root, '.planning', 'ESCAPE.md'));
|
||||
@@ -2431,7 +2431,7 @@ describe('#4623: computeCoveredDigest v2 — shared planning documents do not en
|
||||
});
|
||||
|
||||
test('a declaration made only of shared planning documents hashes nothing → null (fail closed, like an empty one)', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4623-all-shared-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4623-all-shared-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.mkdirSync(path.join(root, '.planning'));
|
||||
writeSharedDocs4623(root);
|
||||
@@ -2441,7 +2441,7 @@ describe('#4623: computeCoveredDigest v2 — shared planning documents do not en
|
||||
});
|
||||
|
||||
test('the phase\'s own planning root is shared too: a workstream-scoped ROADMAP.md is inert, a research note beside it is not', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4623-workstream-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4623-workstream-'));
|
||||
t.after(() => cleanup(root));
|
||||
const wsRoot = path.join(root, '.planning', 'workstreams', 'payments');
|
||||
const phaseDir = path.join(wsRoot, 'phases', '01-a');
|
||||
@@ -2466,14 +2466,14 @@ describe('#4623: computeCoveredDigest v2 — shared planning documents do not en
|
||||
});
|
||||
|
||||
test('a shared-looking path that escapes the root still fails the whole set', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4623-escape-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4623-escape-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.writeFileSync(path.join(root, 'impl.txt'), 'x');
|
||||
assert.equal(computeCoveredDigest(root, ['impl.txt', '../.planning/ROADMAP.md']), null);
|
||||
});
|
||||
|
||||
test('v1 semantics are preserved on request: a shared-doc change still moves a v1 digest', (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4623-v1-'));
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4623-v1-'));
|
||||
t.after(() => cleanup(root));
|
||||
fs.mkdirSync(path.join(root, '.planning'));
|
||||
fs.writeFileSync(path.join(root, 'impl.txt'), 'x');
|
||||
@@ -2485,10 +2485,10 @@ describe('#4623: computeCoveredDigest v2 — shared planning documents do not en
|
||||
});
|
||||
|
||||
describe('#4623: readVerificationStatus — shared planning documents no longer stale a phase', () => {
|
||||
const { runGsdTools } = require('./helpers.cjs');
|
||||
const { runMsdTools } = require('./helpers.cjs');
|
||||
|
||||
function fingerprintViaCli(projectDir, phase, coveredFiles) {
|
||||
const res = runGsdTools(['verification', 'fingerprint', phase.phaseDir, ...coveredFiles], projectDir);
|
||||
const res = runMsdTools(['verification', 'fingerprint', phase.phaseDir, ...coveredFiles], projectDir);
|
||||
assert.equal(res.success, true, `expected success, got: ${res.output}${res.error}`);
|
||||
return JSON.parse(res.output).covered_digest;
|
||||
}
|
||||
@@ -2609,7 +2609,7 @@ describe('#4623: readVerificationStatus — shared planning documents no longer
|
||||
});
|
||||
|
||||
describe('#4623: verification.fingerprint CLI — --files forms and the phase-dir guard', () => {
|
||||
const { runGsdTools } = require('./helpers.cjs');
|
||||
const { runMsdTools } = require('./helpers.cjs');
|
||||
|
||||
function setup() {
|
||||
const projectDir = createTempGitProject();
|
||||
@@ -2622,7 +2622,7 @@ describe('#4623: verification.fingerprint CLI — --files forms and the phase-di
|
||||
}
|
||||
|
||||
function run(projectDir, phaseDir, ...tokens) {
|
||||
return runGsdTools(['verification', 'fingerprint', phaseDir, ...tokens], projectDir);
|
||||
return runMsdTools(['verification', 'fingerprint', phaseDir, ...tokens], projectDir);
|
||||
}
|
||||
|
||||
function expectJson(res) {
|
||||
@@ -2679,7 +2679,7 @@ describe('#4623: verification.fingerprint CLI — --files forms and the phase-di
|
||||
test('an omitted phase dir (first argument is a covered file) is an error, not a plausible digest over the wrong set at exit 0', (t) => {
|
||||
const { projectDir } = setup();
|
||||
t.after(() => cleanup(projectDir));
|
||||
const res = runGsdTools(['verification', 'fingerprint', 'a.rb', 'b.rb', '--raw'], projectDir);
|
||||
const res = runMsdTools(['verification', 'fingerprint', 'a.rb', 'b.rb', '--raw'], projectDir);
|
||||
assert.equal(res.success, false);
|
||||
assert.match(`${res.output}${res.error}`, /phase directory not found/);
|
||||
assert.doesNotMatch(res.output, /^v\d+:sha256:/);
|
||||
@@ -2729,27 +2729,27 @@ describe('#4623: verification.fingerprint CLI — --files forms and the phase-di
|
||||
// Regression tests for #2617 — verification-status `next_command` bypassed the
|
||||
// runtime command-surface projection.
|
||||
//
|
||||
// `src/verification.cts` stored and synthesized hard-coded `/gsd:…` strings with
|
||||
// `src/verification.cts` stored and synthesized hard-coded `/msd:…` strings with
|
||||
// no runtime context, and `phase complete` relayed that raw field straight into
|
||||
// its verification-blocked error. On a Codex project the suggested next step was
|
||||
// `/gsd:execute-phase`, which Codex does not install — the surface there is
|
||||
// `$gsd-execute-phase`. The colon form is doubly wrong: `runtime-slash.cts`
|
||||
// `/msd:execute-phase`, which Codex does not install — the surface there is
|
||||
// `$msd-execute-phase`. The colon form is doubly wrong: `runtime-slash.cts`
|
||||
// documents that "the colon form is never emitted", so every runtime was getting
|
||||
// a deprecated shape. (The 11 `/gsd-…` assertions above were `/gsd:…` before this
|
||||
// a deprecated shape. (The 11 `/msd-…` assertions above were `/msd:…` before this
|
||||
// fix — they are the failing-first record.)
|
||||
//
|
||||
// The fix keeps ONE routing seam and makes its emitted command runtime-aware:
|
||||
// the table stores bare command names and every return path projects through
|
||||
// `formatGsdSlash`, with callers passing `resolveRuntime(cwd)`.
|
||||
// `formatMsdSlash`, with callers passing `resolveRuntime(cwd)`.
|
||||
//
|
||||
// Coverage is the matrix the issue asked for — missing, unknown, gaps_found and
|
||||
// stale, against Codex (`$gsd-…`) and a slash-hyphen runtime (`/gsd-…`) — plus
|
||||
// stale, against Codex (`$msd-…`) and a slash-hyphen runtime (`/msd-…`) — plus
|
||||
// the `phase complete` error path, not merely the router's return object.
|
||||
|
||||
/** Codex installs `$gsd-<cmd>`; every other shipped runtime installs `/gsd-<cmd>`. */
|
||||
/** Codex installs `$msd-<cmd>`; every other shipped runtime installs `/msd-<cmd>`. */
|
||||
const RUNTIMES = [
|
||||
{ id: 'codex', prefix: '$gsd-' },
|
||||
{ id: 'cursor', prefix: '/gsd-' },
|
||||
{ id: 'codex', prefix: '$msd-' },
|
||||
{ id: 'cursor', prefix: '/msd-' },
|
||||
];
|
||||
|
||||
// NOTE: deliberately NOT file-scope beforeEach/afterEach. node:test applies
|
||||
@@ -2767,7 +2767,7 @@ let projPhaseDir;
|
||||
*/
|
||||
function useProjectionPhaseDir() {
|
||||
beforeEach(() => {
|
||||
projBaseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2617-'));
|
||||
projBaseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2617-'));
|
||||
projPhaseDir = path.join(projBaseDir, '01-example');
|
||||
fs.mkdirSync(projPhaseDir, { recursive: true });
|
||||
});
|
||||
@@ -2837,7 +2837,7 @@ for (const { id, prefix } of RUNTIMES) {
|
||||
});
|
||||
|
||||
test('passed has no next step and stays empty, not a bare prefix', () => {
|
||||
// Boundary: projecting an empty command must not emit `$gsd-` / `/gsd-`.
|
||||
// Boundary: projecting an empty command must not emit `$msd-` / `/msd-`.
|
||||
writeStatus('passed');
|
||||
assert.equal(read(id).next_command, '',
|
||||
'passed has no next command and must project to the empty string');
|
||||
@@ -2876,7 +2876,7 @@ describe('#2617: no verification output suggests the deprecated colon form', ()
|
||||
|
||||
for (const result of cases) {
|
||||
assert.ok(
|
||||
!result.next_command.includes('/gsd:'),
|
||||
!result.next_command.includes('/msd:'),
|
||||
`deprecated colon form leaked for runtime=${String(runtime)}: ${result.next_command}`,
|
||||
);
|
||||
checked++;
|
||||
@@ -2889,8 +2889,8 @@ describe('#2617: no verification output suggests the deprecated colon form', ()
|
||||
|
||||
test('the default runtime yields the canonical hyphen form, not the colon form', () => {
|
||||
removeVerification();
|
||||
// No `runtime` option at all — the pre-fix default emitted `/gsd:execute-phase`.
|
||||
assert.equal(readVerificationStatus(projPhaseDir).next_command, '/gsd-execute-phase 01');
|
||||
// No `runtime` option at all — the pre-fix default emitted `/msd:execute-phase`.
|
||||
assert.equal(readVerificationStatus(projPhaseDir).next_command, '/msd-execute-phase 01');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2899,7 +2899,7 @@ describe('#2617: the phase-complete error path projects too', () => {
|
||||
// user-visible surface is `phase complete`, which relays next_command into its
|
||||
// blocked-completion error. Driven through the real CLI so the assertion is on
|
||||
// what a user actually sees.
|
||||
const { runGsdTools, createTempGitProject } = require('./helpers.cjs');
|
||||
const { runMsdTools, createTempGitProject } = require('./helpers.cjs');
|
||||
|
||||
for (const { id, prefix } of RUNTIMES) {
|
||||
test(`phase complete on ${id} suggests ${prefix}execute-phase`, () => {
|
||||
@@ -2913,8 +2913,8 @@ describe('#2617: the phase-complete error path projects too', () => {
|
||||
fs.mkdirSync(phase, { recursive: true });
|
||||
// No *-VERIFICATION.md → the completion gate blocks with reason "missing".
|
||||
|
||||
const res = runGsdTools(['phase', 'complete', '01'], projectDir);
|
||||
// The blocked-completion message goes to stderr, which runGsdTools
|
||||
const res = runMsdTools(['phase', 'complete', '01'], projectDir);
|
||||
// The blocked-completion message goes to stderr, which runMsdTools
|
||||
// surfaces as `error` (NOT `stderr`) on a clean non-zero exit. Reading
|
||||
// the wrong field yields '' and makes every assertion below vacuous.
|
||||
const text = `${res.output || ''}${res.error || ''}`;
|
||||
@@ -2932,7 +2932,7 @@ describe('#2617: the phase-complete error path projects too', () => {
|
||||
`phase complete must suggest ${prefix}execute-phase on ${id}, got: ${text}`,
|
||||
);
|
||||
assert.ok(
|
||||
!text.includes('/gsd:'),
|
||||
!text.includes('/msd:'),
|
||||
`phase complete must not surface the deprecated colon form: ${text}`,
|
||||
);
|
||||
} finally {
|
||||
@@ -2957,7 +2957,7 @@ describe('#2617: the phase-complete error path projects too', () => {
|
||||
'---\nstatus: gaps_found\n---\n\n# Verification\n',
|
||||
);
|
||||
|
||||
const res = runGsdTools(['phase', 'complete', '01'], projectDir);
|
||||
const res = runMsdTools(['phase', 'complete', '01'], projectDir);
|
||||
const text = `${res.output || ''}${res.error || ''}`;
|
||||
|
||||
assert.equal(res.success, false, 'gaps_found must block completion');
|
||||
@@ -2965,7 +2965,7 @@ describe('#2617: the phase-complete error path projects too', () => {
|
||||
text.includes(`${prefix}plan-phase 01 --gaps`),
|
||||
`phase complete must suggest ${prefix}plan-phase 01 --gaps on ${id}, got: ${text}`,
|
||||
);
|
||||
assert.ok(!text.includes('/gsd:'), `deprecated colon form leaked: ${text}`);
|
||||
assert.ok(!text.includes('/msd:'), `deprecated colon form leaked: ${text}`);
|
||||
} finally {
|
||||
cleanup(projectDir);
|
||||
}
|
||||
@@ -2977,12 +2977,12 @@ describe('#2617: the phase-complete error path projects too', () => {
|
||||
//
|
||||
// execute-phase's `discover_and_group_plans` step resumes at the phase gates
|
||||
// when every plan is summarized but no *-VERIFICATION.md exists yet. That
|
||||
// resume decision is driven by `gsd_run query verification status <phaseDir>
|
||||
// resume decision is driven by `msd_run query verification status <phaseDir>
|
||||
// --pick status` reading `missing`. These tests pin the CLI query's behavior
|
||||
// on the exact fixture shapes the workflow branches on, via the real CLI
|
||||
// (runGsdTools), not the in-process readVerificationStatus() helper used above.
|
||||
// (runMsdTools), not the in-process readVerificationStatus() helper used above.
|
||||
describe('#2868: verification status CLI drives the execute-phase stranded-phase resume', () => {
|
||||
const { runGsdTools, createTempGitProject } = require('./helpers.cjs');
|
||||
const { runMsdTools, createTempGitProject } = require('./helpers.cjs');
|
||||
|
||||
test('D1: all plans summarized, no *-VERIFICATION.md → status is missing', () => {
|
||||
const projectDir = createTempGitProject();
|
||||
@@ -2992,7 +2992,7 @@ describe('#2868: verification status CLI drives the execute-phase stranded-phase
|
||||
fs.writeFileSync(path.join(phaseDir, '01-01-PLAN.md'), '# Plan\n');
|
||||
fs.writeFileSync(path.join(phaseDir, '01-01-SUMMARY.md'), '# Summary\n');
|
||||
|
||||
const res = runGsdTools(['verification', 'status', phaseDir, '--pick', 'status'], projectDir);
|
||||
const res = runMsdTools(['verification', 'status', phaseDir, '--pick', 'status'], projectDir);
|
||||
assert.equal(res.success, true, `verification status should succeed: ${res.error}`);
|
||||
assert.equal(res.output, 'missing', 'no VERIFICATION.md at all → status must be missing');
|
||||
} finally {
|
||||
@@ -3012,7 +3012,7 @@ describe('#2868: verification status CLI drives the execute-phase stranded-phase
|
||||
'---\nstatus: passed\n---\n\n# Verification\n',
|
||||
);
|
||||
|
||||
const res = runGsdTools(['verification', 'status', phaseDir, '--pick', 'status'], projectDir);
|
||||
const res = runMsdTools(['verification', 'status', phaseDir, '--pick', 'status'], projectDir);
|
||||
assert.equal(res.success, true, `verification status should succeed: ${res.error}`);
|
||||
assert.notEqual(res.output, 'missing', 'a passed VERIFICATION.md must not read as missing');
|
||||
assert.equal(res.output, 'passed');
|
||||
@@ -3033,7 +3033,7 @@ describe('#2868: verification status CLI drives the execute-phase stranded-phase
|
||||
// verification never ran). The query must not conflate the two.
|
||||
fs.writeFileSync(path.join(phaseDir, '01-02-PLAN.md'), '# Plan 2\n');
|
||||
|
||||
const res = runGsdTools(['verification', 'status', phaseDir, '--pick', 'status'], projectDir);
|
||||
const res = runMsdTools(['verification', 'status', phaseDir, '--pick', 'status'], projectDir);
|
||||
assert.equal(res.success, true, `verification status should succeed: ${res.error}`);
|
||||
assert.equal(
|
||||
res.output,
|
||||
@@ -3069,20 +3069,20 @@ describe('#2868: verification status CLI drives the execute-phase stranded-phase
|
||||
// replacement honest.
|
||||
describe('quick verification status read (#3174)', () => {
|
||||
const QUICK_VERIFICATION = path.join(
|
||||
__dirname, '..', 'gsd-core', 'workflows', 'quick', 'steps', 'quick-verification.md',
|
||||
__dirname, '..', 'msd-core', 'workflows', 'quick', 'steps', 'quick-verification.md',
|
||||
);
|
||||
// The canonical launcher preamble. scripts/sync-runtime-launcher.cjs rewrites
|
||||
// every workflow's bootstrap from this file, so THIS is the authority — not
|
||||
// whichever sibling step file happens to carry a copy today.
|
||||
const LAUNCHER_SNIPPET = path.join(
|
||||
__dirname, '..', 'gsd-core', 'workflows', '_runtime-launcher.snippet.sh',
|
||||
__dirname, '..', 'msd-core', 'workflows', '_runtime-launcher.snippet.sh',
|
||||
);
|
||||
|
||||
const SHIM_ANCHOR = '_GSD_SHIM_NAME="gsd-tools.cjs"';
|
||||
const SHIM_ANCHOR = '_MSD_SHIM_NAME="msd-tools.cjs"';
|
||||
|
||||
test('status is read through the canonical query, not a raw frontmatter grep', () => {
|
||||
const content = fs.readFileSync(QUICK_VERIFICATION, 'utf-8');
|
||||
const queryIdx = content.indexOf('gsd_run query verification.status "${QUICK_DIR}"');
|
||||
const queryIdx = content.indexOf('msd_run query verification.status "${QUICK_DIR}"');
|
||||
|
||||
assert.ok(queryIdx !== -1, 'quick-verification.md must read status via the verification.status query');
|
||||
assert.ok(
|
||||
@@ -3097,17 +3097,17 @@ describe('#2868: verification status CLI drives the execute-phase stranded-phase
|
||||
// nothing, 2>/dev/null swallows it, and the default arm is taken forever.
|
||||
const content = fs.readFileSync(QUICK_VERIFICATION, 'utf-8');
|
||||
const shimIdx = content.indexOf(SHIM_ANCHOR);
|
||||
const queryIdx = content.indexOf('gsd_run query verification.status');
|
||||
const queryIdx = content.indexOf('msd_run query verification.status');
|
||||
|
||||
assert.ok(shimIdx !== -1, 'the step file must carry its own runtime shim bootstrap');
|
||||
assert.ok(queryIdx > shimIdx, 'the shim bootstrap must precede the gsd_run call');
|
||||
assert.ok(queryIdx > shimIdx, 'the shim bootstrap must precede the msd_run call');
|
||||
});
|
||||
|
||||
test('the shim bootstrap is the canonical launcher preamble, not a fork of it', () => {
|
||||
// Anchored on _runtime-launcher.snippet.sh rather than on a sibling step
|
||||
// file: sync-runtime-launcher.cjs regenerates every workflow from the
|
||||
// snippet, so a synchronized launcher update keeps this green (correct),
|
||||
// and a sibling that legitimately stops calling gsd_run cannot fail us.
|
||||
// and a sibling that legitimately stops calling msd_run cannot fail us.
|
||||
const lineWithShim = (file) => fs.readFileSync(file, 'utf-8')
|
||||
.split(/\r?\n/)
|
||||
.find((line) => line.startsWith(SHIM_ANCHOR));
|
||||
@@ -3132,7 +3132,7 @@ describe('#2868: verification status CLI drives the execute-phase stranded-phase
|
||||
const fences = scanFencedBlocks(contentLines)
|
||||
.filter((b) => b.closeLineIdx !== -1 && (b.infoString || '').trim() === 'bash')
|
||||
.map((b) => contentLines.slice(b.openLineIdx, b.closeLineIdx + 1).join('\n'));
|
||||
const statusFence = fences.find((f) => f.includes('gsd_run query verification.status'));
|
||||
const statusFence = fences.find((f) => f.includes('msd_run query verification.status'));
|
||||
|
||||
assert.ok(statusFence, 'the status read must live in a bash fence');
|
||||
assert.ok(
|
||||
|
||||
Reference in New Issue
Block a user