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

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

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

View File

@@ -3,10 +3,10 @@
// runtime loads — testing text content tests the deployed contract.
/**
* GSD Code Review Tests
* MSD Code Review Tests
*
* Validates all code review artifacts from Phases 1-4:
* - Agent frontmatter (gsd-code-reviewer, gsd-code-fixer)
* - Agent frontmatter (msd-code-reviewer, msd-code-fixer)
* - Command structure (code-review.md, code-review-fix.md)
* - Workflow structure (code-review.md, code-review-fix.md)
* - Config key registration (workflow.code_review, workflow.code_review_depth)
@@ -24,8 +24,8 @@ const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { scanFencedBlocks } = require('../gsd-core/bin/lib/markdown-sectionizer.cjs');
const { splitLines } = require('../gsd-core/bin/lib/text-lines.cjs');
const { scanFencedBlocks } = require('../msd-core/bin/lib/markdown-sectionizer.cjs');
const { splitLines } = require('../msd-core/bin/lib/text-lines.cjs');
/** Return the raw text of every ```bash fenced block in `content`. */
function extractBashBlocks(content) {
@@ -39,19 +39,19 @@ function extractBashBlocks(content) {
return blocks;
}
const os = require('os');
const { runGsdTools, createTempProject, createTempGitProject, cleanup } = require('./helpers.cjs');
const { runMsdTools, createTempProject, createTempGitProject, cleanup } = require('./helpers.cjs');
const { runNode } = require('./helpers/process-seam.cjs');
const { escapeRegex } = require('../gsd-core/bin/lib/pattern.cjs');
const { escapeRegex } = require('../msd-core/bin/lib/pattern.cjs');
const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const REPO_ROOT = path.join(__dirname, '..');
const GSD_TOOLS_BIN = path.join(REPO_ROOT, 'gsd-core', 'bin', 'gsd-tools.cjs');
const MSD_TOOLS_BIN = path.join(REPO_ROOT, 'msd-core', 'bin', 'msd-tools.cjs');
// --- Test Environment Setup ---
const AGENTS_DIR = path.join(__dirname, '..', 'agents');
const COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows');
const COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'msd');
const WORKFLOWS_DIR = path.join(__dirname, '..', 'msd-core', 'workflows');
/**
* Parse top-level (non-nested, non-escaped) Skill() invocations from a workflow .md file.
@@ -189,130 +189,130 @@ function parseSkillCallBody(body) {
}
// Plugin directory resolution (cross-platform safe)
const PLUGIN_WORKFLOWS_DIR = process.env.GSD_PLUGIN_ROOT || path.join(os.homedir(), '.claude', 'gsd-core', 'workflows');
const PLUGIN_WORKFLOWS_DIR = process.env.MSD_PLUGIN_ROOT || path.join(os.homedir(), '.claude', 'msd-core', 'workflows');
const PLUGIN_AVAILABLE = fs.existsSync(PLUGIN_WORKFLOWS_DIR);
// --- CR-AGENT: code review agent frontmatter ---
describe('CR-AGENT: code review agent frontmatter', () => {
test('gsd-code-reviewer.md has required frontmatter fields', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-reviewer.md'), 'utf-8');
test('msd-code-reviewer.md has required frontmatter fields', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-reviewer.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
assert.ok(frontmatter.includes('name:'), 'gsd-code-reviewer missing name:');
assert.ok(frontmatter.includes('description:'), 'gsd-code-reviewer missing description:');
assert.ok(frontmatter.includes('tools:'), 'gsd-code-reviewer missing tools:');
assert.ok(frontmatter.includes('color:'), 'gsd-code-reviewer missing color:');
assert.ok(frontmatter.includes('name:'), 'msd-code-reviewer missing name:');
assert.ok(frontmatter.includes('description:'), 'msd-code-reviewer missing description:');
assert.ok(frontmatter.includes('tools:'), 'msd-code-reviewer missing tools:');
assert.ok(frontmatter.includes('color:'), 'msd-code-reviewer missing color:');
});
test('gsd-code-fixer.md has required frontmatter fields', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
test('msd-code-fixer.md has required frontmatter fields', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-fixer.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
assert.ok(frontmatter.includes('name:'), 'gsd-code-fixer missing name:');
assert.ok(frontmatter.includes('description:'), 'gsd-code-fixer missing description:');
assert.ok(frontmatter.includes('tools:'), 'gsd-code-fixer missing tools:');
assert.ok(frontmatter.includes('color:'), 'gsd-code-fixer missing color:');
assert.ok(frontmatter.includes('name:'), 'msd-code-fixer missing name:');
assert.ok(frontmatter.includes('description:'), 'msd-code-fixer missing description:');
assert.ok(frontmatter.includes('tools:'), 'msd-code-fixer missing tools:');
assert.ok(frontmatter.includes('color:'), 'msd-code-fixer missing color:');
});
test('gsd-code-reviewer.md has Read, Bash, Glob, Grep, Write tools', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-reviewer.md'), 'utf-8');
test('msd-code-reviewer.md has Read, Bash, Glob, Grep, Write tools', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-reviewer.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
assert.ok(frontmatter.includes('Read'), 'gsd-code-reviewer missing Read tool');
assert.ok(frontmatter.includes('Bash'), 'gsd-code-reviewer missing Bash tool');
assert.ok(frontmatter.includes('Glob'), 'gsd-code-reviewer missing Glob tool');
assert.ok(frontmatter.includes('Grep'), 'gsd-code-reviewer missing Grep tool');
assert.ok(frontmatter.includes('Write'), 'gsd-code-reviewer missing Write tool');
assert.ok(frontmatter.includes('Read'), 'msd-code-reviewer missing Read tool');
assert.ok(frontmatter.includes('Bash'), 'msd-code-reviewer missing Bash tool');
assert.ok(frontmatter.includes('Glob'), 'msd-code-reviewer missing Glob tool');
assert.ok(frontmatter.includes('Grep'), 'msd-code-reviewer missing Grep tool');
assert.ok(frontmatter.includes('Write'), 'msd-code-reviewer missing Write tool');
});
test('gsd-code-fixer.md has Read, Edit, Write, Bash, Grep, Glob tools', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
test('msd-code-fixer.md has Read, Edit, Write, Bash, Grep, Glob tools', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-fixer.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
assert.ok(frontmatter.includes('Read'), 'gsd-code-fixer missing Read tool');
assert.ok(frontmatter.includes('Edit'), 'gsd-code-fixer missing Edit tool');
assert.ok(frontmatter.includes('Write'), 'gsd-code-fixer missing Write tool');
assert.ok(frontmatter.includes('Bash'), 'gsd-code-fixer missing Bash tool');
assert.ok(frontmatter.includes('Read'), 'msd-code-fixer missing Read tool');
assert.ok(frontmatter.includes('Edit'), 'msd-code-fixer missing Edit tool');
assert.ok(frontmatter.includes('Write'), 'msd-code-fixer missing Write tool');
assert.ok(frontmatter.includes('Bash'), 'msd-code-fixer missing Bash tool');
});
test('gsd-code-reviewer.md does not have skills: in frontmatter', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-reviewer.md'), 'utf-8');
test('msd-code-reviewer.md does not have skills: in frontmatter', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-reviewer.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
assert.ok(!frontmatter.includes('skills:'),
'gsd-code-reviewer has skills: in frontmatter — breaks Gemini CLI');
'msd-code-reviewer has skills: in frontmatter — breaks Gemini CLI');
});
test('gsd-code-fixer.md does not have skills: in frontmatter', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
test('msd-code-fixer.md does not have skills: in frontmatter', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-fixer.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
assert.ok(!frontmatter.includes('skills:'),
'gsd-code-fixer has skills: in frontmatter — breaks Gemini CLI');
'msd-code-fixer has skills: in frontmatter — breaks Gemini CLI');
});
test('gsd-code-fixer.md rollback uses git checkout (not Write tool)', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
test('msd-code-fixer.md rollback uses git checkout (not Write tool)', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-fixer.md'), 'utf-8');
assert.ok(content.includes('git checkout --'),
'gsd-code-fixer rollback should use git checkout -- {file} for atomic rollback');
'msd-code-fixer rollback should use git checkout -- {file} for atomic rollback');
assert.ok(!content.includes('PRE_FIX_CONTENT'),
'gsd-code-fixer should not use PRE_FIX_CONTENT in-memory capture (use git checkout instead)');
'msd-code-fixer should not use PRE_FIX_CONTENT in-memory capture (use git checkout instead)');
});
test('gsd-code-fixer.md success_criteria consistent with rollback strategy (git checkout)', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
test('msd-code-fixer.md success_criteria consistent with rollback strategy (git checkout)', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-fixer.md'), 'utf-8');
// eslint-disable-next-line local/no-unbounded-quantifier -- parses this repo's own agent .md content, fixed-size author-controlled content
const successCriteria = content.match(/<success_criteria>([\s\S]*?)<\/success_criteria>/)?.[1] || '';
assert.ok(successCriteria.includes('git checkout'),
'gsd-code-fixer success_criteria must reference git checkout rollback');
'msd-code-fixer success_criteria must reference git checkout rollback');
assert.ok(!successCriteria.includes('Write tool with captured'),
'gsd-code-fixer success_criteria must not say Write tool for rollback');
'msd-code-fixer success_criteria must not say Write tool for rollback');
});
test('gsd-code-fixer.md flags logic-bug fixes for human review', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
test('msd-code-fixer.md flags logic-bug fixes for human review', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-fixer.md'), 'utf-8');
assert.ok(content.includes('requires human verification'),
'gsd-code-fixer should flag logic-bug fixes as requiring human verification');
'msd-code-fixer should flag logic-bug fixes as requiring human verification');
});
test('gsd-code-reviewer.md REVIEW.md spec includes files_reviewed_list field', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-reviewer.md'), 'utf-8');
test('msd-code-reviewer.md REVIEW.md spec includes files_reviewed_list field', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-reviewer.md'), 'utf-8');
assert.ok(content.includes('files_reviewed_list'),
'gsd-code-reviewer REVIEW.md frontmatter spec must include files_reviewed_list for --auto scope persistence');
'msd-code-reviewer REVIEW.md frontmatter spec must include files_reviewed_list for --auto scope persistence');
});
// #2825: gsd-code-fixer is the only writer that hand-rolls a git worktree; it
// #2825: msd-code-fixer is the only writer that hand-rolls a git worktree; it
// must honor workflow.use_worktrees (the documented opt-out) like its four
// sibling writer workflows, and never rm -rf a possible Windows reparse point.
test('#2825 gsd-code-fixer.md reads workflow.use_worktrees and gates git worktree add on it', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
test('#2825 msd-code-fixer.md reads workflow.use_worktrees and gates git worktree add on it', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-fixer.md'), 'utf-8');
assert.ok(
content.includes('workflow.use_worktrees'),
'gsd-code-fixer setup_worktree must read the workflow.use_worktrees config flag (#2825)',
'msd-code-fixer setup_worktree must read the workflow.use_worktrees config flag (#2825)',
);
// The git worktree add must be CONDITIONAL on the flag, not unconditional.
// Locate the worktree-add line and confirm a USE_WORKTREES gate precedes it.
assert.ok(
/USE_WORKTREES=.false./.test(content) || content.includes('if [ "$USE_WORKTREES" = "false" ]'),
'gsd-code-fixer must gate worktree creation on USE_WORKTREES=false (skip when opted out) (#2825)',
'msd-code-fixer must gate worktree creation on USE_WORKTREES=false (skip when opted out) (#2825)',
);
});
test('#2825 gsd-code-fixer.md forbids rm -rf on a possible reparse point (Windows junction safety)', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
test('#2825 msd-code-fixer.md forbids rm -rf on a possible reparse point (Windows junction safety)', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-fixer.md'), 'utf-8');
assert.ok(
/rm -rf.*reparse point|reparse point.*rm -rf|NEVER .rm -rf.|never use .rm -rf/i.test(content),
'gsd-code-fixer must forbid rm -rf on a possible reparse point/junction (#2825) — on Windows that is the delete-the-target path',
'msd-code-fixer must forbid rm -rf on a possible reparse point/junction (#2825) — on Windows that is the delete-the-target path',
);
});
test('#2825 gsd-code-fixer.md records where verification ran (main checkout vs worktree)', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
test('#2825 msd-code-fixer.md records where verification ran (main checkout vs worktree)', () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, 'msd-code-fixer.md'), 'utf-8');
assert.ok(
// eslint-disable-next-line local/no-unbounded-quantifier -- parses this repo's own agent .md content, fixed-size author-controlled content
/verification[\s\S]*(main checkout|worktree)|(main checkout|worktree)[\s\S]*verification/i.test(content),
'gsd-code-fixer REVIEW-FIX.md must record where verification ran (main checkout vs worktree) so a reader knows if the numbers are reproducible (#2825)',
'msd-code-fixer REVIEW-FIX.md must record where verification ran (main checkout vs worktree) so a reader knows if the numbers are reproducible (#2825)',
);
});
});
@@ -320,11 +320,11 @@ describe('CR-AGENT: code review agent frontmatter', () => {
// --- CR-CMD: code review command structure ---
describe('CR-CMD: code review command structure', () => {
test('code-review.md has correct frontmatter name: gsd:code-review', () => {
test('code-review.md has correct frontmatter name: msd:code-review', () => {
const content = fs.readFileSync(path.join(COMMANDS_DIR, 'code-review.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
assert.ok(frontmatter.includes('name: gsd:code-review'),
assert.ok(frontmatter.includes('name: msd:code-review'),
'code-review.md missing correct name in frontmatter');
});
@@ -396,11 +396,11 @@ describe('CR-WORKFLOW: code review workflow structure', () => {
'code-review.md workflow missing check_config_gate step');
});
test('code-review.md workflow references gsd-code-reviewer agent', () => {
test('code-review.md workflow references msd-code-reviewer agent', () => {
const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'code-review.md'), 'utf-8');
assert.ok(content.includes('gsd-code-reviewer'),
'code-review.md workflow does not reference gsd-code-reviewer agent');
assert.ok(content.includes('msd-code-reviewer'),
'code-review.md workflow does not reference msd-code-reviewer agent');
});
test('code-review-fix.md workflow has <step name="initialize">', () => {
@@ -410,11 +410,11 @@ describe('CR-WORKFLOW: code review workflow structure', () => {
'code-review-fix.md workflow missing initialize step');
});
test('code-review-fix.md workflow references gsd-code-fixer agent', () => {
test('code-review-fix.md workflow references msd-code-fixer agent', () => {
const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'code-review-fix.md'), 'utf-8');
assert.ok(content.includes('gsd-code-fixer'),
'code-review-fix.md workflow does not reference gsd-code-fixer agent');
assert.ok(content.includes('msd-code-fixer'),
'code-review-fix.md workflow does not reference msd-code-fixer agent');
});
test('code-review-fix.md workflow has iteration cap', () => {
@@ -456,7 +456,7 @@ describe('CR-WORKFLOW: code review workflow structure', () => {
'code-review-fix.md should use portable while-read loop instead of mapfile');
});
// #3661: configurable code-review hook point (see .gsd/phase/feat-3661-code-review-hook-point/
// #3661: configurable code-review hook point (see .msd/phase/feat-3661-code-review-hook-point/
// 40-design.md and 50-test-matrix.md Section G) — manual invocation reads
// workflow.code_review directly (independent of the automatic loop-point
// selector workflow.code_review_point) instead of gating on registry
@@ -474,8 +474,8 @@ describe('CR-WORKFLOW: code review workflow structure', () => {
const stepContent = extractStepBody(content, 'check_config_gate');
assert.ok(stepContent, 'code-review.md missing check_config_gate step');
assert.ok(/gsd_run query config-get workflow\.code_review\b/.test(stepContent),
'check_config_gate must read workflow.code_review via gsd_run query config-get');
assert.ok(/msd_run query config-get workflow\.code_review\b/.test(stepContent),
'check_config_gate must read workflow.code_review via msd_run query config-get');
});
test('G2: checkConfigGateNoLongerProbesExecutePostHooks', () => {
@@ -560,7 +560,7 @@ describe('CR-CONFIG: config key registration', () => {
test('config-set accepts workflow.code_review', () => {
const tmpDir = createTempProject();
try {
const result = runGsdTools('config-set workflow.code_review true', tmpDir);
const result = runMsdTools('config-set workflow.code_review true', tmpDir);
assert.ok(result.success, `config-set should accept workflow.code_review: ${result.error}`);
} finally {
cleanup(tmpDir);
@@ -570,7 +570,7 @@ describe('CR-CONFIG: config key registration', () => {
test('config-set accepts workflow.code_review_depth', () => {
const tmpDir = createTempProject();
try {
const result = runGsdTools('config-set workflow.code_review_depth standard', tmpDir);
const result = runMsdTools('config-set workflow.code_review_depth standard', tmpDir);
assert.ok(result.success, `config-set should accept workflow.code_review_depth: ${result.error}`);
} finally {
cleanup(tmpDir);
@@ -581,10 +581,10 @@ describe('CR-CONFIG: config key registration', () => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const setResult = runGsdTools(['config-set', 'workflow.code_review', 'true'], tmpDir);
const setResult = runMsdTools(['config-set', 'workflow.code_review', 'true'], tmpDir);
assert.ok(setResult.success, `config-set workflow.code_review failed: ${setResult.error}`);
const getResult = runGsdTools(['config-get', 'workflow.code_review'], tmpDir);
const getResult = runMsdTools(['config-get', 'workflow.code_review'], tmpDir);
assert.ok(getResult.success, `config-get workflow.code_review failed: ${getResult.error}`);
assert.strictEqual(getResult.output, 'true',
`workflow.code_review should return "true", got ${getResult.output}`);
@@ -594,23 +594,23 @@ describe('CR-CONFIG: config key registration', () => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const setResult = runGsdTools(['config-set', 'workflow.code_review_depth', 'standard'], tmpDir);
const setResult = runMsdTools(['config-set', 'workflow.code_review_depth', 'standard'], tmpDir);
assert.ok(setResult.success, `config-set workflow.code_review_depth failed: ${setResult.error}`);
const getResult = runGsdTools(['config-get', 'workflow.code_review_depth'], tmpDir);
const getResult = runMsdTools(['config-get', 'workflow.code_review_depth'], tmpDir);
assert.ok(getResult.success, `config-get workflow.code_review_depth failed: ${getResult.error}`);
assert.strictEqual(getResult.output, '"standard"',
`workflow.code_review_depth should return '"standard"', got ${getResult.output}`);
});
// ── #3661: workflow.code_review_point — CLI-behavioral (50-test-matrix.md
// Section F, rows F4-F7). Uses createTempGitProject + runGsdTools + the real
// gsd-tools subprocess (via runNode), not source-grep, per the matrix's
// Section F, rows F4-F7). Uses createTempGitProject + runMsdTools + the real
// msd-tools subprocess (via runNode), not source-grep, per the matrix's
// coverage-strategy note.
function renderHooksEnvelope(tmpDir, point) {
const result = runNode(
[GSD_TOOLS_BIN, 'loop', 'render-hooks', point, '--cwd', tmpDir],
[MSD_TOOLS_BIN, 'loop', 'render-hooks', point, '--cwd', tmpDir],
{ cwd: REPO_ROOT, timeoutMs: PROBE_TIMEOUT_MS },
);
assert.strictEqual(result.exitCode, 0, `Expected exit 0 for render-hooks ${point}. stderr: ` + (result.stderr || ''));
@@ -639,7 +639,7 @@ describe('CR-CONFIG: config key registration', () => {
const tmpDir = createTempGitProject();
t.after(() => cleanup(tmpDir));
const setResult = runGsdTools(['config-set', 'workflow.code_review_point', 'execute:wave:post'], tmpDir);
const setResult = runMsdTools(['config-set', 'workflow.code_review_point', 'execute:wave:post'], tmpDir);
assert.ok(setResult.success, `config-set workflow.code_review_point failed: ${setResult.error}`);
const postEnvelope = renderHooksEnvelope(tmpDir, 'execute:post');
@@ -654,7 +654,7 @@ describe('CR-CONFIG: config key registration', () => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const result = runGsdTools(['config-set', 'workflow.code_review_point', 'bogus'], tmpDir);
const result = runMsdTools(['config-set', 'workflow.code_review_point', 'bogus'], tmpDir);
assert.strictEqual(result.success, false, 'config-set must reject an out-of-enum workflow.code_review_point value');
assert.match(
result.error || '',
@@ -663,7 +663,7 @@ describe('CR-CONFIG: config key registration', () => {
);
// The rejected value must not have been persisted.
const getResult = runGsdTools(['config-get', 'workflow.code_review_point'], tmpDir);
const getResult = runMsdTools(['config-get', 'workflow.code_review_point'], tmpDir);
assert.ok(getResult.success, `config-get workflow.code_review_point failed: ${getResult.error}`);
assert.notStrictEqual(getResult.output, '"bogus"', 'out-of-enum value must not be silently accepted/persisted');
});
@@ -713,20 +713,20 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
// a genuine hang, not a test artifact (#4209 round 5, BL-01). This test's subject is the
// FENCE's own bash control flow (CODE_REVIEW_POINT/EXPLICIT_JOINED computed correctly and
// threaded into dispatch-step's argv) — not the external CLI dispatch-step goes on to spawn.
// `gsd_run` stays real for `config-get`/`review-lane explicit-from-argv` (what this test
// `msd_run` stays real for `config-get`/`review-lane explicit-from-argv` (what this test
// verifies) and is short-circuited ONLY for `review-lane dispatch-step`, whose argv is
// captured to a file for assertion instead of executed for real.
const driver = [
`GSD_TOOLS=${JSON.stringify(GSD_TOOLS_BIN)}`,
`MSD_TOOLS=${JSON.stringify(MSD_TOOLS_BIN)}`,
`DISPATCH_ARGS_PATH=${JSON.stringify(dispatchArgsPath)}`,
'gsd_run() {',
'msd_run() {',
' if [ "$1" = "review-lane" ] && [ "$2" = "dispatch-step" ]; then',
' printf \'%s\\n\' "$@" > "$DISPATCH_ARGS_PATH"',
' cat >/dev/null',
' echo \'{"ok":true,"dispatched":false,"selection":{},"results":[]}\'',
' return 0',
' fi',
' node "$GSD_TOOLS" "$@"',
' node "$MSD_TOOLS" "$@"',
'}',
`REPO_ROOT=${JSON.stringify(tmpDir)}`,
'REVIEW_DEPTH=standard',
@@ -785,7 +785,7 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
const tmpDir = createTempGitProject();
try {
const result = runNode(
[GSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
[MSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
'--repo-root', tmpDir, '--depth', 'standard', '--base-sha', 'deadbeef',
'--run-dir', tmpDir, '--cwd', tmpDir, '--explicit', 'not-a-real-reviewer-xyz',
'--cap-id', 'code-review', '--point', 'execute:post', '--raw'],
@@ -808,7 +808,7 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
const tmpDir = createTempGitProject();
try {
const result = runNode(
[GSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
[MSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
'--repo-root', tmpDir, '--depth', 'standard', '--base-sha', 'deadbeef',
'--run-dir', tmpDir, '--cwd', tmpDir, '--explicit', 'codex',
'--cap-id', 'no-such-capability-xyz', '--point', 'execute:post', '--raw'],
@@ -828,7 +828,7 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
const tmpDir = createTempGitProject();
try {
const result = runNode(
[GSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
[MSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
'--repo-root', tmpDir, '--depth', 'standard', '--base-sha', 'deadbeef',
'--run-dir', tmpDir, '--cwd', tmpDir, '--explicit', 'codex', '--raw'],
{ cwd: REPO_ROOT, timeoutMs: PROBE_TIMEOUT_MS, input: 'src/foo.ts\n' },
@@ -846,7 +846,7 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
const tmpDir = createTempGitProject();
try {
const result = runNode(
[GSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
[MSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
'--repo-root', tmpDir, '--depth', 'standard', '--base-sha', 'deadbeef',
'--run-dir', tmpDir, '--cwd', tmpDir, '--explicit', 'codex',
'--cap-id', 'code-review', '--raw'],
@@ -864,7 +864,7 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
test('review-lane explicit-from-argv matches CLI flags against the merged roster (#4209 RQ-02)', () => {
const result = runNode(
[GSD_TOOLS_BIN, 'review-lane', 'explicit-from-argv', '--', '--codex', '--agy'],
[MSD_TOOLS_BIN, 'review-lane', 'explicit-from-argv', '--', '--codex', '--agy'],
{ cwd: REPO_ROOT, timeoutMs: PROBE_TIMEOUT_MS },
);
assert.strictEqual(result.exitCode, 0, `expected exit 0, stderr: ${result.stderr || ''}`);
@@ -873,7 +873,7 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
test('review-lane explicit-from-argv resolves to empty when no known flag is present', () => {
const result = runNode(
[GSD_TOOLS_BIN, 'review-lane', 'explicit-from-argv', '--'],
[MSD_TOOLS_BIN, 'review-lane', 'explicit-from-argv', '--'],
{ cwd: REPO_ROOT, timeoutMs: PROBE_TIMEOUT_MS },
);
assert.strictEqual(result.exitCode, 0, `expected exit 0, stderr: ${result.stderr || ''}`);
@@ -896,7 +896,7 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
// eslint-disable-next-line local/no-unbounded-quantifier -- bounded author-controlled workflow markdown
const stepMatch = workflowContent.match(/<step name="dispatch_reviewer_lanes">([\s\S]*?)<\/step>/);
const stepContent = stepMatch[1];
const calls = stepContent.match(/gsd_run review-lane dispatch-step/g) || [];
const calls = stepContent.match(/msd_run review-lane dispatch-step/g) || [];
assert.strictEqual(calls.length, 1,
`dispatch_reviewer_lanes must call review-lane dispatch-step exactly once, found ${calls.length}`);
});
@@ -954,7 +954,7 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
const tmpDir = createTempGitProject();
try {
const result = runNode(
[GSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
[MSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
'--repo-root', tmpDir, '--depth', 'standard', '--base-sha', 'deadbeef',
'--run-dir', tmpDir, '--cwd', tmpDir,
'--cap-id', 'code-review', '--point', 'execute:post', '--raw'],
@@ -973,7 +973,7 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
const tmpDir = createTempGitProject();
try {
const result = runNode(
[GSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
[MSD_TOOLS_BIN, 'review-lane', 'dispatch-step',
'--repo-root', tmpDir, '--depth', 'standard', '--base-sha', 'deadbeef',
'--run-dir', tmpDir, '--cwd', tmpDir, '--explicit', 'not-a-real-reviewer-xyz',
'--cap-id', 'code-review', '--point', 'execute:post', '--raw'],
@@ -1029,13 +1029,13 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
dispatched: true,
ok: false,
results: [
{ slug: 'codex', ok: true, reviewPath: '/tmp/gsd-review-codex.md' },
{ slug: 'codex', ok: true, reviewPath: '/tmp/msd-review-codex.md' },
{ slug: 'agy', ok: false, reason: 'invoke_failed', detail: 'binary not found' },
],
}));
assert.match(stderr, /external reviewer lane 'agy' failed \(invoke_failed: binary not found\)/,
`expected a per-lane failure warning, got stderr: ${stderr}`);
assert.strictEqual(stdout.trim(), '- codex: /tmp/gsd-review-codex.md',
assert.strictEqual(stdout.trim(), '- codex: /tmp/msd-review-codex.md',
'the lane that succeeded must still produce an evidence line');
});
@@ -1045,10 +1045,10 @@ describe('CR-REVIEWER-LANES: optional external source-reviewer dispatch (#4209)'
const payloadPath = path.join(tmpDir, 'dispatch-result.json');
fs.writeFileSync(payloadPath, JSON.stringify({
dispatched: true, ok: true,
results: [{ slug: 'codex', ok: true, reviewPath: '/tmp/gsd-review-codex.md' }],
results: [{ slug: 'codex', ok: true, reviewPath: '/tmp/msd-review-codex.md' }],
}));
const { stdout, stderr } = runReducer(`@file:${payloadPath}`);
assert.strictEqual(stdout.trim(), '- codex: /tmp/gsd-review-codex.md',
assert.strictEqual(stdout.trim(), '- codex: /tmp/msd-review-codex.md',
`expected the @file:-wrapped payload to be unwrapped and parsed, got stdout: ${stdout} stderr: ${stderr}`);
} finally {
cleanup(tmpDir);
@@ -1082,7 +1082,7 @@ describe('CR-INTEGRATION: workflow integration points', () => {
});
// #3661: the generic execute:wave:post step-dispatch contract
// (loop-hook-dispatch.md § step) invokes `Skill(skill="gsd-<ref.skill>")` with no
// (loop-hook-dispatch.md § step) invokes `Skill(skill="msd-<ref.skill>")` with no
// phase argument, but code-review.md's `initialize` step requires one positionally
// (`PHASE_ARG="${1}"`) — without it the review reports "Phase not found" and exits.
// Caught by the orthogonal spec review; fixed with a precedented carve-out in step
@@ -1096,8 +1096,8 @@ describe('CR-INTEGRATION: workflow integration points', () => {
assert.ok(stepContent.includes('ref.skill == "code-review"'),
'step 5.75 must carve out ref.skill == "code-review" from the generic step-dispatch contract');
assert.ok(/Skill\(skill="gsd-code-review",\s*args="\$\{PHASE_NUMBER\}"\)/.test(stepContent),
'step 5.75 must dispatch code-review with an explicit args="${PHASE_NUMBER}", matching code_review_gate\'s invocation — the bare generic Skill(skill="gsd-<ref.skill>") form has no phase argument and code-review.md requires one');
assert.ok(/Skill\(skill="msd-code-review",\s*args="\$\{PHASE_NUMBER\}"\)/.test(stepContent),
'step 5.75 must dispatch code-review with an explicit args="${PHASE_NUMBER}", matching code_review_gate\'s invocation — the bare generic Skill(skill="msd-<ref.skill>") form has no phase argument and code-review.md requires one');
});
test('execute-phase.md does NOT contain ls.*REVIEW.md.*head pattern', { skip: !PLUGIN_AVAILABLE ? 'Plugin dir not installed' : false }, () => {
@@ -1125,12 +1125,12 @@ describe('CR-INTEGRATION: workflow integration points', () => {
const start = content.indexOf('**Step 6.25: Code review (auto)**');
// #2994 (pre-existing since #2994's earlier quick-verification.md extraction,
// 18ff35d20): Step 6.5's content moved into
// gsd-core/workflows/quick/steps/quick-verification.md behind a
// `<!-- gsd:section id="quick-verification" -->` marker, so the literal
// msd-core/workflows/quick/steps/quick-verification.md behind a
// `<!-- msd:section id="quick-verification" -->` marker, so the literal
// "**Step 6.5: Verification" heading text no longer follows Step 6.25 in
// this file — the marker is the correct end-of-step delimiter now (mirrors
// phase6-review-capabilities.test.cjs's identical retarget for the same move).
const end = content.indexOf('<!-- gsd:section id="quick-verification"', start);
const end = content.indexOf('<!-- msd:section id="quick-verification"', start);
assert.ok(start !== -1 && end !== -1, 'quick.md missing Step 6.25 code review section');
const reviewContent = content.slice(start, end);
@@ -1146,62 +1146,62 @@ describe('CR-INTEGRATION: workflow integration points', () => {
// not the user-installed plugin dir. The plugin dir can lag behind the repo until the
// user re-installs, so asserting against it produces false negatives. The repo file
// is the source of truth and is always present in CI checkouts.
test('autonomous.md contains gsd-code-review skill invocation', () => {
test('autonomous.md contains msd-code-review skill invocation', () => {
const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'autonomous.md'), 'utf-8');
// Parse Skill(...) invocations into structured objects and assert canonical
// hyphen form is referenced. Canonical command form is hyphen
// (gsd-code-review); colon form (gsd:code-review) is the legacy
// (msd-code-review); colon form (msd:code-review) is the legacy
// frontmatter-name form removed in PR #2819.
const invocations = parseWorkflowSkillInvocations(content);
const skillNames = invocations.map(inv => inv.skill);
assert.ok(skillNames.includes('gsd-code-review'),
`autonomous.md must invoke Skill(skill="gsd-code-review", ...); found skills: ${JSON.stringify(skillNames)}`);
assert.ok(!skillNames.includes('gsd:code-review'),
'autonomous.md must not use legacy colon form gsd:code-review (canonical is hyphen form)');
assert.ok(skillNames.includes('msd-code-review'),
`autonomous.md must invoke Skill(skill="msd-code-review", ...); found skills: ${JSON.stringify(skillNames)}`);
assert.ok(!skillNames.includes('msd:code-review'),
'autonomous.md must not use legacy colon form msd:code-review (canonical is hyphen form)');
});
test('autonomous.md auto-fix uses consolidated gsd-code-review --fix invocation (#2790)', () => {
// After #2790, gsd-code-review-fix was absorbed into gsd-code-review as
test('autonomous.md auto-fix uses consolidated msd-code-review --fix invocation (#2790)', () => {
// After #2790, msd-code-review-fix was absorbed into msd-code-review as
// the --fix flag. The autonomous workflow must invoke the consolidated
// form, not the deleted gsd-code-review-fix skill.
// form, not the deleted msd-code-review-fix skill.
const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'autonomous.md'), 'utf-8');
const invocations = parseWorkflowSkillInvocations(content);
const skillNames = invocations.map(inv => inv.skill);
assert.ok(!skillNames.includes('gsd-code-review-fix'),
`autonomous.md must not invoke deleted gsd-code-review-fix skill (consolidated into --fix); found: ${JSON.stringify(skillNames)}`);
assert.ok(!skillNames.includes('gsd:code-review-fix'),
'autonomous.md must not use legacy colon form gsd:code-review-fix');
assert.ok(!skillNames.includes('msd-code-review-fix'),
`autonomous.md must not invoke deleted msd-code-review-fix skill (consolidated into --fix); found: ${JSON.stringify(skillNames)}`);
assert.ok(!skillNames.includes('msd:code-review-fix'),
'autonomous.md must not use legacy colon form msd:code-review-fix');
// Find a gsd-code-review invocation that carries the --fix flag (the
// Find a msd-code-review invocation that carries the --fix flag (the
// consolidated auto-fix entry point).
const fixInvocation = invocations.find(inv => {
if (inv.skill !== 'gsd-code-review') return false;
if (inv.skill !== 'msd-code-review') return false;
const tokens = new Set((inv.args ?? '').split(/\s+/).filter(Boolean));
return tokens.has('--fix');
});
assert.ok(fixInvocation,
`autonomous.md must invoke Skill(skill="gsd-code-review", args="... --fix ...") for auto-fix; found: ${JSON.stringify(invocations)}`);
`autonomous.md must invoke Skill(skill="msd-code-review", args="... --fix ...") for auto-fix; found: ${JSON.stringify(invocations)}`);
});
test('autonomous.md contains --auto flag on consolidated --fix invocation (#2790)', () => {
const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'autonomous.md'), 'utf-8');
// Find the gsd-code-review invocation that carries --fix (the consolidated
// Find the msd-code-review invocation that carries --fix (the consolidated
// auto-fix entry point), then assert --auto is one of its arg tokens.
// Tokenize via whitespace-split to avoid substring matches that could
// conflate --auto with --auto-foo.
const invocations = parseWorkflowSkillInvocations(content);
const fixInvocation = invocations.find(inv => {
if (inv.skill !== 'gsd-code-review') return false;
if (inv.skill !== 'msd-code-review') return false;
const tokens = new Set((inv.args ?? '').split(/\s+/).filter(Boolean));
return tokens.has('--fix');
});
assert.ok(fixInvocation, 'autonomous.md missing Skill(skill="gsd-code-review", args="... --fix ...") invocation');
assert.ok(fixInvocation, 'autonomous.md missing Skill(skill="msd-code-review", args="... --fix ...") invocation');
const argTokens = new Set((fixInvocation.args ?? '').split(/\s+/).filter(Boolean));
assert.ok(argTokens.has('--auto'),
`autonomous.md gsd-code-review-fix args missing --auto flag; got args="${fixInvocation.args}"`);
`autonomous.md msd-code-review-fix args missing --auto flag; got args="${fixInvocation.args}"`);
});
});
@@ -1215,7 +1215,7 @@ describe('CR-INTEGRATION: workflow integration points', () => {
/**
* Regression test for bug #2839
*
* /gsd-code-review-fix cleanup tail is non-transactional. If the agent is
* /msd-code-review-fix cleanup tail is non-transactional. If the agent is
* interrupted (system restart, OOM kill) AFTER the last fix commit but
* BEFORE `git worktree remove`, the worktree is orphaned in
* `git worktree list`, the agent's branch is left with unmerged commits,
@@ -1228,14 +1228,14 @@ describe('CR-INTEGRATION: workflow integration points', () => {
* REMOVED only after `git worktree remove` completes, so the cleanup
* tail is transactional from the orchestrator's perspective. If the
* process dies in between, the sentinel is left behind pointing at the
* orphan worktree and branch — a future run, /gsd-resume-work, or
* /gsd-progress can detect and complete the recovery.
* orphan worktree and branch — a future run, /msd-resume-work, or
* /msd-progress can detect and complete the recovery.
*/
'use strict';
// allow-test-rule: source-text-is-the-product (see #2839)
// The gsd-code-fixer agent's working instructions ARE the product — Claude
// The msd-code-fixer agent's working instructions ARE the product — Claude
// follows them at runtime. Structural assertions over the markdown source
// test the deployed contract. See bug-2686 for the same pattern.
@@ -1254,14 +1254,14 @@ function extractStep(content, stepName) {
return m ? m[1] : null;
}
describe('bug-2839: /gsd-code-review-fix cleanup is transactional', () => {
describe('bug-2839: /msd-code-review-fix cleanup is transactional', () => {
let agentPath;
let agentContent;
let frontmatter;
before(() => {
agentPath = path.join(__dirname, '..', 'agents', 'gsd-code-fixer.md');
assert.ok(fs.existsSync(agentPath), 'agents/gsd-code-fixer.md must exist');
agentPath = path.join(__dirname, '..', 'agents', 'msd-code-fixer.md');
assert.ok(fs.existsSync(agentPath), 'agents/msd-code-fixer.md must exist');
agentContent = fs.readFileSync(agentPath, 'utf-8');
frontmatter = parseFrontmatter(agentContent);
assert.ok(frontmatter, 'agent must have YAML frontmatter');
@@ -1270,7 +1270,7 @@ describe('bug-2839: /gsd-code-review-fix cleanup is transactional', () => {
test('agent declares a recovery sentinel filename', () => {
assert.ok(
agentContent.includes(SENTINEL_NAME),
`gsd-code-fixer.md must reference the recovery sentinel ${SENTINEL_NAME} so an interrupted cleanup tail is discoverable (#2839)`
`msd-code-fixer.md must reference the recovery sentinel ${SENTINEL_NAME} so an interrupted cleanup tail is discoverable (#2839)`
);
});
@@ -1319,7 +1319,7 @@ describe('bug-2839: /gsd-code-review-fix cleanup is transactional', () => {
for (const key of ['worktree_path', 'branch', 'padded_phase']) {
assert.ok(
agentContent.includes(key),
`recovery sentinel must record \`${key}\` so a future /gsd-resume-work or /gsd-progress can locate the orphan state (#2839)`
`recovery sentinel must record \`${key}\` so a future /msd-resume-work or /msd-progress can locate the orphan state (#2839)`
);
}
});