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

@@ -5,7 +5,7 @@
/**
* Ingest Docs Tests — ingest-docs.test.cjs
*
* Structural assertions for /gsd-ingest-docs (#2387). Agents and workflows
* Structural assertions for /msd-ingest-docs (#2387). Agents and workflows
* are prompt-based; these tests guard the contract (files exist, frontmatter
* present, required references wired up, safety semantics preserved).
*/
@@ -14,15 +14,15 @@ const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { extractFrontmatter } = require('../gsd-core/bin/lib/frontmatter.cjs');
const { scanFencedBlocks } = require('../gsd-core/bin/lib/markdown-sectionizer.cjs');
const { extractFrontmatter } = require('../msd-core/bin/lib/frontmatter.cjs');
const { scanFencedBlocks } = require('../msd-core/bin/lib/markdown-sectionizer.cjs');
const ROOT = path.join(__dirname, '..');
const CMD_PATH = path.join(ROOT, 'commands', 'gsd', 'ingest-docs.md');
const WF_PATH = path.join(ROOT, 'gsd-core', 'workflows', 'ingest-docs.md');
const CLASSIFIER_PATH = path.join(ROOT, 'agents', 'gsd-doc-classifier.md');
const SYNTHESIZER_PATH = path.join(ROOT, 'agents', 'gsd-doc-synthesizer.md');
const CONFLICT_ENGINE_PATH = path.join(ROOT, 'gsd-core', 'references', 'doc-conflict-engine.md');
const CMD_PATH = path.join(ROOT, 'commands', 'msd', 'ingest-docs.md');
const WF_PATH = path.join(ROOT, 'msd-core', 'workflows', 'ingest-docs.md');
const CLASSIFIER_PATH = path.join(ROOT, 'agents', 'msd-doc-classifier.md');
const SYNTHESIZER_PATH = path.join(ROOT, 'agents', 'msd-doc-synthesizer.md');
const CONFLICT_ENGINE_PATH = path.join(ROOT, 'msd-core', 'references', 'doc-conflict-engine.md');
/** Return the raw text of every ```bash fenced block in `content`. */
function extractBashBlocks(content) {
@@ -40,16 +40,16 @@ function extractBashBlocks(content) {
describe('ingest-docs file structure (#2387)', () => {
test('command file exists', () => {
assert.ok(fs.existsSync(CMD_PATH), 'commands/gsd/ingest-docs.md should exist');
assert.ok(fs.existsSync(CMD_PATH), 'commands/msd/ingest-docs.md should exist');
});
test('workflow file exists', () => {
assert.ok(fs.existsSync(WF_PATH), 'gsd-core/workflows/ingest-docs.md should exist');
assert.ok(fs.existsSync(WF_PATH), 'msd-core/workflows/ingest-docs.md should exist');
});
test('classifier agent exists', () => {
assert.ok(fs.existsSync(CLASSIFIER_PATH), 'agents/gsd-doc-classifier.md should exist');
assert.ok(fs.existsSync(CLASSIFIER_PATH), 'agents/msd-doc-classifier.md should exist');
});
test('synthesizer agent exists', () => {
assert.ok(fs.existsSync(SYNTHESIZER_PATH), 'agents/gsd-doc-synthesizer.md should exist');
assert.ok(fs.existsSync(SYNTHESIZER_PATH), 'agents/msd-doc-synthesizer.md should exist');
});
test('shared conflict-engine reference exists', () => {
assert.ok(fs.existsSync(CONFLICT_ENGINE_PATH), 'references/doc-conflict-engine.md should exist');
@@ -62,7 +62,7 @@ describe('ingest-docs command frontmatter', () => {
const content = fs.readFileSync(CMD_PATH, 'utf-8');
test('has name field', () => {
assert.match(content, /^name:\s*gsd:ingest-docs$/m);
assert.match(content, /^name:\s*msd:ingest-docs$/m);
});
test('has description field', () => {
assert.match(content, /^description:\s*.+$/m);
@@ -90,19 +90,19 @@ describe('ingest-docs command references', () => {
test('references the ingest-docs workflow', () => {
assert.ok(
content.includes('@~/.claude/gsd-core/workflows/ingest-docs.md'),
content.includes('@~/.claude/msd-core/workflows/ingest-docs.md'),
'command must @-reference its workflow'
);
});
test('references the doc-conflict-engine', () => {
assert.ok(
content.includes('@~/.claude/gsd-core/references/doc-conflict-engine.md'),
content.includes('@~/.claude/msd-core/references/doc-conflict-engine.md'),
'command must load the shared conflict-engine contract'
);
});
test('references gate-prompts', () => {
assert.ok(
content.includes('@~/.claude/gsd-core/references/gate-prompts.md'),
content.includes('@~/.claude/msd-core/references/gate-prompts.md'),
'command must load gate-prompts for AskUserQuestion patterns'
);
});
@@ -151,14 +151,14 @@ describe('ingest-docs workflow content', () => {
assert.ok(content.includes('spec'), 'workflow must match SPEC/RFC directory convention');
});
test('spawns gsd-doc-classifier and gsd-doc-synthesizer', () => {
test('spawns msd-doc-classifier and msd-doc-synthesizer', () => {
assert.ok(
content.includes('gsd-doc-classifier'),
'workflow must spawn gsd-doc-classifier'
content.includes('msd-doc-classifier'),
'workflow must spawn msd-doc-classifier'
);
assert.ok(
content.includes('gsd-doc-synthesizer'),
'workflow must spawn gsd-doc-synthesizer'
content.includes('msd-doc-synthesizer'),
'workflow must spawn msd-doc-synthesizer'
);
});
@@ -181,10 +181,10 @@ describe('ingest-docs workflow content', () => {
);
});
test('routes to gsd-roadmapper in new mode', () => {
test('routes to msd-roadmapper in new mode', () => {
assert.ok(
content.includes('gsd-roadmapper'),
'new mode must delegate to gsd-roadmapper'
content.includes('msd-roadmapper'),
'new mode must delegate to msd-roadmapper'
);
});
@@ -193,9 +193,9 @@ describe('ingest-docs workflow content', () => {
// come first — an approval gate placed after the subagent call would
// authorize nothing.
const gateIdx = content.indexOf('Routing — create the planning setup now?');
const delegateIdx = content.indexOf('subagent_type: "gsd-roadmapper"');
const delegateIdx = content.indexOf('subagent_type: "msd-roadmapper"');
assert.ok(gateIdx !== -1, 'new mode must display a routing gate question before delegating');
assert.ok(delegateIdx !== -1, 'new mode must still delegate to gsd-roadmapper');
assert.ok(delegateIdx !== -1, 'new mode must still delegate to msd-roadmapper');
assert.ok(gateIdx < delegateIdx, 'the routing gate must precede the roadmapper delegation');
// The gate display must show the user exactly what will be written
@@ -258,7 +258,7 @@ describe('ingest-docs workflow content', () => {
// ─── Classifier Agent ───────────────────────────────────────────────────────────
describe('gsd-doc-classifier agent', () => {
describe('msd-doc-classifier agent', () => {
const content = fs.readFileSync(CLASSIFIER_PATH, 'utf-8');
test('has Read and Write tools', () => {
@@ -286,14 +286,14 @@ describe('gsd-doc-classifier agent', () => {
// ─── Synthesizer Agent ──────────────────────────────────────────────────────────
describe('gsd-doc-synthesizer agent', () => {
describe('msd-doc-synthesizer agent', () => {
const content = fs.readFileSync(SYNTHESIZER_PATH, 'utf-8');
test('has Read/Write/Bash tools', () => {
assert.match(content, /^tools:\s*.*Read.*Write.*Bash.*/m);
});
test('documents default precedence ADR > SPEC > PRD > DOC', () => {
// eslint-disable-next-line local/no-unbounded-quantifier -- parses maintainer-authored gsd-doc-synthesizer agent markdown, bounded prose, not adversarial input
// eslint-disable-next-line local/no-unbounded-quantifier -- parses maintainer-authored msd-doc-synthesizer agent markdown, bounded prose, not adversarial input
const precedenceBlock = content.match(/ADR[^.]*SPEC[^.]*PRD[^.]*DOC/);
assert.ok(precedenceBlock, 'default precedence ordering must be documented');
});
@@ -357,13 +357,13 @@ describe('doc-conflict-engine shared reference', () => {
// ─── Import command still consumes the shared reference (#2387 refactor) ───────
describe('import command adopts shared conflict-engine', () => {
const cmdContent = fs.readFileSync(path.join(ROOT, 'commands', 'gsd', 'import.md'), 'utf-8');
const wfContent = fs.readFileSync(path.join(ROOT, 'gsd-core', 'workflows', 'import.md'), 'utf-8');
const cmdContent = fs.readFileSync(path.join(ROOT, 'commands', 'msd', 'import.md'), 'utf-8');
const wfContent = fs.readFileSync(path.join(ROOT, 'msd-core', 'workflows', 'import.md'), 'utf-8');
test('import command loads doc-conflict-engine reference', () => {
assert.ok(
cmdContent.includes('@~/.claude/gsd-core/references/doc-conflict-engine.md'),
'/gsd-import must load the shared conflict-engine contract'
cmdContent.includes('@~/.claude/msd-core/references/doc-conflict-engine.md'),
'/msd-import must load the shared conflict-engine contract'
);
});
test('import workflow cites the shared reference', () => {
@@ -389,15 +389,15 @@ describe('import command adopts shared conflict-engine', () => {
/**
* Regression test for bug #2801
*
* `/gsd-ingest-docs` was broken because:
* 1. `workflows/ingest-docs.md` called `gsd-sdk query init.ingest-docs` but the
* installed binary is `gsd-tools` (not `gsd-sdk`).
* 2. `gsd-tools init` had no `ingest-docs` case in its dispatch switch.
* `/msd-ingest-docs` was broken because:
* 1. `workflows/ingest-docs.md` called `msd-sdk query init.ingest-docs` but the
* installed binary is `msd-tools` (not `msd-sdk`).
* 2. `msd-tools init` had no `ingest-docs` case in its dispatch switch.
*
* The fix:
* - Added `case 'ingest-docs'` to the `init` switch in `gsd-tools.cjs`.
* - Added `case 'ingest-docs'` to the `init` switch in `msd-tools.cjs`.
* - Exported `cmdInitIngestDocs` from `init.cjs`.
* - Updated `workflows/ingest-docs.md` to call `gsd-tools init ingest-docs`.
* - Updated `workflows/ingest-docs.md` to call `msd-tools init ingest-docs`.
*
* This test prevents regression of the dispatch omission.
*/
@@ -413,7 +413,7 @@ const { runNode } = require('./helpers/process-seam.cjs');
const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const REPO_ROOT = path.join(__dirname, '..');
const WORKFLOW_FILE = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'ingest-docs.md');
const WORKFLOW_FILE = path.join(REPO_ROOT, 'msd-core', 'workflows', 'ingest-docs.md');
// Note (#3147 migration): the original execFileSync-based implementation
// caught its own throw and degraded it into `{exitCode, stdout}` data —
@@ -423,11 +423,11 @@ const WORKFLOW_FILE = path.join(REPO_ROOT, 'gsd-core', 'workflows', 'ingest-docs
// .exitCode), not a throw-preserving one, so this uses `runNode` directly
// rather than `throwIfFailed`. stdout/stderr are concatenated on a non-zero
// exit to match the original catch-block behavior exactly.
function spawnGsdTools(args, projectDir) {
function spawnMsdTools(args, projectDir) {
const result = runNode(
[TOOLS_PATH, ...args, '--cwd', projectDir],
{
env: { ...process.env, GSD_SESSION_KEY: '' },
env: { ...process.env, MSD_SESSION_KEY: '' },
timeoutMs: PROBE_TIMEOUT_MS,
}
);
@@ -436,11 +436,11 @@ function spawnGsdTools(args, projectDir) {
return { exitCode, stdout };
}
describe('bug-2801: gsd-tools init ingest-docs handler exists', () => {
describe('bug-2801: msd-tools init ingest-docs handler exists', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject('gsd-test-2801-');
tmpDir = createTempProject('msd-test-2801-');
});
afterEach(() => {
@@ -448,12 +448,12 @@ describe('bug-2801: gsd-tools init ingest-docs handler exists', () => {
});
test('init ingest-docs exits 0 (not "Unknown init workflow")', () => {
const { exitCode, stdout } = spawnGsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
const { exitCode, stdout } = spawnMsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
assert.strictEqual(exitCode, 0, `expected exit 0, got: ${stdout}`);
});
test('init ingest-docs returns JSON with project_exists field', () => {
const { exitCode, stdout } = spawnGsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
const { exitCode, stdout } = spawnMsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
assert.strictEqual(exitCode, 0);
let json;
try { json = JSON.parse(stdout.trim()); } catch { assert.fail(`non-JSON output: ${stdout}`); }
@@ -461,21 +461,21 @@ describe('bug-2801: gsd-tools init ingest-docs handler exists', () => {
});
test('init ingest-docs returns JSON with planning_exists field', () => {
const { exitCode, stdout } = spawnGsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
const { exitCode, stdout } = spawnMsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
assert.strictEqual(exitCode, 0);
const json = JSON.parse(stdout.trim());
assert.ok(Object.prototype.hasOwnProperty.call(json, 'planning_exists'), 'planning_exists present');
});
test('init ingest-docs returns JSON with has_git field', () => {
const { exitCode, stdout } = spawnGsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
const { exitCode, stdout } = spawnMsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
assert.strictEqual(exitCode, 0);
const json = JSON.parse(stdout.trim());
assert.ok(Object.prototype.hasOwnProperty.call(json, 'has_git'), 'has_git present');
});
test('init ingest-docs returns JSON with project_path field', () => {
const { exitCode, stdout } = spawnGsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
const { exitCode, stdout } = spawnMsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
assert.strictEqual(exitCode, 0);
const json = JSON.parse(stdout.trim());
assert.ok(Object.prototype.hasOwnProperty.call(json, 'project_path'), 'project_path present');
@@ -483,52 +483,52 @@ describe('bug-2801: gsd-tools init ingest-docs handler exists', () => {
});
test('planning_exists is true when .planning/ directory exists', () => {
const { exitCode, stdout } = spawnGsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
const { exitCode, stdout } = spawnMsdTools(['init', 'ingest-docs', '--raw'], tmpDir);
assert.strictEqual(exitCode, 0);
const json = JSON.parse(stdout.trim());
assert.strictEqual(json.planning_exists, true, 'planning_exists should be true (.planning/ created by createTempProject)');
});
});
describe('bug-2801: ingest-docs.md workflow calls gsd-tools not gsd-sdk', () => {
test('no bash code block in ingest-docs.md calls gsd-sdk', () => {
describe('bug-2801: ingest-docs.md workflow calls msd-tools not msd-sdk', () => {
test('no bash code block in ingest-docs.md calls msd-sdk', () => {
const content = fs.readFileSync(WORKFLOW_FILE, 'utf-8');
// Extract bash fenced code blocks structurally.
const bashBlocks = extractBashBlocks(content);
assert.ok(bashBlocks.length > 0, 'expected bash code blocks in workflow');
// Check every line in every bash block — not just lines that start with the token,
// since gsd-sdk can appear in subshell expansions like $(gsd-sdk query ...).
// since msd-sdk can appear in subshell expansions like $(msd-sdk query ...).
const sdkCalls = bashBlocks
.join('\n')
.split('\n')
.filter((line) => /\bgsd-sdk\b/.test(line));
.filter((line) => /\bmsd-sdk\b/.test(line));
assert.deepStrictEqual(
sdkCalls,
[],
`workflow bash blocks still reference gsd-sdk (should use gsd-tools): ${sdkCalls.join(', ')}`
`workflow bash blocks still reference msd-sdk (should use msd-tools): ${sdkCalls.join(', ')}`
);
});
test('ingest-docs.md init step uses the gsd_run launcher (#637)', () => {
test('ingest-docs.md init step uses the msd_run launcher (#637)', () => {
const content = fs.readFileSync(WORKFLOW_FILE, 'utf-8');
// Parse fenced bash blocks structurally — do not match raw markdown text.
const bashLines = extractBashBlocks(content)
.flatMap((block) => block.split('\n'))
.filter((l) => !/^\s*#/.test(l));
// #637 routes ingest-docs through the resolved `gsd_run` launcher instead of
// the hardcoded `node "$HOME/.../gsd-tools.cjs"` path (which misses global
// installs). The legacy bare `gsd-tools` form remains the bug and is still
// #637 routes ingest-docs through the resolved `msd_run` launcher instead of
// the hardcoded `node "$HOME/.../msd-tools.cjs"` path (which misses global
// installs). The legacy bare `msd-tools` form remains the bug and is still
// rejected by bug-2851's repo-wide guard.
const initLine = bashLines.find((l) =>
/\bgsd_run\s+init\s+ingest-docs\b/.test(l)
/\bmsd_run\s+init\s+ingest-docs\b/.test(l)
);
assert.ok(initLine, 'workflow must invoke init ingest-docs via the gsd_run launcher (#637)');
assert.ok(initLine, 'workflow must invoke init ingest-docs via the msd_run launcher (#637)');
});
test('cmdInitIngestDocs is exported from init.cjs', () => {
const init = require(path.join(REPO_ROOT, 'gsd-core', 'bin', 'lib', 'init.cjs'));
const init = require(path.join(REPO_ROOT, 'msd-core', 'bin', 'lib', 'init.cjs'));
assert.strictEqual(typeof init.cmdInitIngestDocs, 'function', 'cmdInitIngestDocs must be exported');
});
});