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:
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user