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

@@ -61,7 +61,7 @@
* (fixed the same way — its `local` artifactLayout previously declared
* no `agents` kind at all, so the deleted inline loop's implicit
* scope-gate was silently replaced with NO gate, dropping every
* kimi-code local install's agents/gsd-*.md entirely).
* kimi-code local install's agents/msd-*.md entirely).
*
* H8 is mandatory, not optional: a parity harness never demonstrated failing
* is decoration. It feeds the oracle a DELIBERATELY WRONG (but real,
@@ -95,7 +95,7 @@ const path = require('node:path');
const { cleanup, createTempDir, sandboxHome } = require('./helpers.cjs');
const REPO_ROOT = path.join(__dirname, '..');
const LIB_DIR = path.join(REPO_ROOT, 'gsd-core', 'bin', 'lib');
const LIB_DIR = path.join(REPO_ROOT, 'msd-core', 'bin', 'lib');
const runtimeArtifactConversion = require(path.join(LIB_DIR, 'runtime-artifact-conversion.cjs'));
const installModelOverrideResolver = require(path.join(LIB_DIR, 'install-model-override-resolver.cjs'));
@@ -113,24 +113,24 @@ const installBin = require(path.join(REPO_ROOT, 'bin', 'install.js'));
* ~/.claude/ + $HOME/.claude/ (anchored + bare) path forms, a Co-Authored-By
* trailer, and one row-J4 "disallowedTools hit" agent name plus one "miss". */
const SAMPLE_AGENTS = {
'gsd-planner.md': [
'msd-planner.md': [
'---',
'name: gsd-planner',
'description: Plans phases for GSD workflows.',
'name: msd-planner',
'description: Plans phases for MSD workflows.',
'tools: Read, Write, Edit, Bash',
'---',
'',
'Reads @~/.claude/gsd-core/commands/gsd/plan-phase.md and $HOME/.claude/CLAUDE.md.',
'Reads @~/.claude/msd-core/commands/msd/plan-phase.md and $HOME/.claude/CLAUDE.md.',
'Bare forms too: ~/.claude and $HOME/.claude.',
'References Claude Code and CLAUDE.md and .claude/settings.json.',
'',
'Co-Authored-By: Claude <noreply@anthropic.com>',
'',
].join('\n'),
'gsd-plan-checker.md': [
'msd-plan-checker.md': [
'---',
'name: gsd-plan-checker',
'description: Checks plans for GSD workflows.',
'name: msd-plan-checker',
'description: Checks plans for MSD workflows.',
'tools: Read, Grep, Glob',
'---',
'',
@@ -140,27 +140,27 @@ const SAMPLE_AGENTS = {
};
function buildSourceTree(agentFiles) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-agent-parity-src-'));
const commandsGsd = path.join(root, 'commands', 'gsd');
fs.mkdirSync(commandsGsd, { recursive: true });
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-agent-parity-src-'));
const commandsMsd = path.join(root, 'commands', 'msd');
fs.mkdirSync(commandsMsd, { recursive: true });
// Runtime Surface source providers are atomic across every source class a
// layout needs. Keep this marker fixture complete for commands + agents so
// the installer resolver does not correctly fall back to the package tree.
fs.writeFileSync(path.join(commandsGsd, 'fixture-command.md'), '# Fixture command\n');
fs.writeFileSync(path.join(commandsMsd, 'fixture-command.md'), '# Fixture command\n');
const agentsDir = path.join(root, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
for (const [name, content] of Object.entries(agentFiles)) {
fs.writeFileSync(path.join(agentsDir, name), content);
}
return { root, commandsGsd, agentsDir };
return { root, commandsMsd, agentsDir };
}
/** A fresh "install destination" dir with a `.gsd-source` marker pointing at
* `commandsGsd` — the same marker findInstallSourceRoot/findAgentsSourceRoot
/** A fresh "install destination" dir with a `.msd-source` marker pointing at
* `commandsMsd` — the same marker findInstallSourceRoot/findAgentsSourceRoot
* read (runtime-artifact-layout.cts). */
function buildTargetDir(commandsGsd) {
const targetDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-agent-parity-dest-'));
fs.writeFileSync(path.join(targetDir, '.gsd-source'), commandsGsd);
function buildTargetDir(commandsMsd) {
const targetDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-agent-parity-dest-'));
fs.writeFileSync(path.join(targetDir, '.msd-source'), commandsMsd);
return targetDir;
}
@@ -213,7 +213,7 @@ function computeExpectedOutput(runtime, agentsDir, ctx, converterNameOverride) {
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
const agentSourcePath = path.join(agentsDir, entry.name);
let content = fs.readFileSync(agentSourcePath, 'utf8');
// Step 0 (#2995): strip gsd:section markers — same order the real
// Step 0 (#2995): strip msd:section markers — same order the real
// pipeline uses (stageAgentsForRuntimeWithConverter, install-profiles.cts).
content = composeWorkflow(content, { sourcePath: agentSourcePath });
const agentName = runtimeArtifactConversion.deriveAgentName(entry.name);
@@ -229,8 +229,8 @@ function computeExpectedOutput(runtime, agentsDir, ctx, converterNameOverride) {
if (MODEL_OVERRIDE_CONVERTER_NAMES.has(converterName)) {
const modelOverride = installModelOverrideResolver.resolveAgentModelOverride(
agentName,
installModelOverrideResolver.readGsdEffectiveModelOverrides(targetDir),
installModelOverrideResolver.readGsdRuntimeProfileResolver(targetDir),
installModelOverrideResolver.readMsdEffectiveModelOverrides(targetDir),
installModelOverrideResolver.readMsdRuntimeProfileResolver(targetDir),
);
content = fn(content, { isAgent: true, modelOverride });
} else {
@@ -244,7 +244,7 @@ function computeExpectedOutput(runtime, agentsDir, ctx, converterNameOverride) {
content = runtimeArtifactConversion.normalizeAgentBodyForRuntime(
content,
runtime,
runtimeArtifactConversion.readGsdCommandNames(),
runtimeArtifactConversion.readMsdCommandNames(),
);
out.set(entry.name, content);
}
@@ -310,13 +310,13 @@ const J8_OVERRIDE_MODEL = 'shared/explicit-model';
* model_overrides block for `runtime` through the real descriptor path.
*/
function _stageWithModelOverride(runtime, overrideModel) {
const agentFiles = { 'gsd-planner.md': SAMPLE_AGENTS['gsd-planner.md'] };
const { commandsGsd, root } = buildSourceTree(agentFiles);
const targetDir = buildTargetDir(commandsGsd);
const agentFiles = { 'msd-planner.md': SAMPLE_AGENTS['msd-planner.md'] };
const { commandsMsd, root } = buildSourceTree(agentFiles);
const targetDir = buildTargetDir(commandsMsd);
fs.mkdirSync(path.join(targetDir, '.planning'), { recursive: true });
fs.writeFileSync(
path.join(targetDir, '.planning', 'config.json'),
JSON.stringify({ model_overrides: { 'gsd-planner': overrideModel } }),
JSON.stringify({ model_overrides: { 'msd-planner': overrideModel } }),
);
try {
const ctx = { pathPrefix: `${targetDir}/`, attribution: undefined, targetDir };
@@ -328,8 +328,8 @@ function _stageWithModelOverride(runtime, overrideModel) {
}
function comparePipelines(runtime, scope, converterNameOverride) {
const { commandsGsd, agentsDir, root } = buildSourceTree(SAMPLE_AGENTS);
const targetDir = buildTargetDir(commandsGsd);
const { commandsMsd, agentsDir, root } = buildSourceTree(SAMPLE_AGENTS);
const targetDir = buildTargetDir(commandsMsd);
try {
const ctx = { pathPrefix: `${targetDir}/`, attribution: undefined, targetDir };
const expected = computeExpectedOutput(runtime, agentsDir, ctx, converterNameOverride);
@@ -375,8 +375,8 @@ for (const [runtime, scope] of RUNTIME_SCOPE_PAIRS) {
// ---------------------------------------------------------------------------
test('agent-descriptor-parity: H7 — a filename-only divergence fails the harness', (t) => {
const { commandsGsd, agentsDir, root } = buildSourceTree(SAMPLE_AGENTS);
const targetDir = buildTargetDir(commandsGsd);
const { commandsMsd, agentsDir, root } = buildSourceTree(SAMPLE_AGENTS);
const targetDir = buildTargetDir(commandsMsd);
t.after(() => {
cleanup(root);
cleanup(targetDir);
@@ -408,8 +408,8 @@ test('agent-descriptor-parity: H8 — a deliberately-wrong (but real, allowliste
// still uses hermes's real (correct) converter, so this proves: IF
// capability.json ever regressed to the wrong name, THIS harness's H row
// for hermes would go red exactly like this.
const { commandsGsd, agentsDir, root } = buildSourceTree(SAMPLE_AGENTS);
const targetDir = buildTargetDir(commandsGsd);
const { commandsMsd, agentsDir, root } = buildSourceTree(SAMPLE_AGENTS);
const targetDir = buildTargetDir(commandsMsd);
t.after(() => {
cleanup(root);
cleanup(targetDir);
@@ -426,7 +426,7 @@ test('agent-descriptor-parity: H8 — a deliberately-wrong (but real, allowliste
observedDiff = err.message;
}
assert.equal(threw, true, 'H8 FAILED: the harness did not go red for a deliberately-wrong expected converter');
assert.match(observedDiff, /content diverged for gsd-(planner|plan-checker)\.md/, 'expected the content-diverged assertion to name the mismatched file');
assert.match(observedDiff, /content diverged for msd-(planner|plan-checker)\.md/, 'expected the content-diverged assertion to name the mismatched file');
// Verbatim red-proof output for the record (see CHANGES report):
console.log(`H8 red-proof observed: ${observedDiff}`);
});
@@ -436,16 +436,16 @@ test('agent-descriptor-parity: H8 — a deliberately-wrong (but real, allowliste
// ---------------------------------------------------------------------------
test('agent-descriptor-parity: I3 — deriveAgentName matches the pipeline exactly, including a no-.md-suffix boundary', () => {
assert.equal(runtimeArtifactConversion.deriveAgentName('gsd-planner.md'), 'gsd-planner');
assert.equal(runtimeArtifactConversion.deriveAgentName('msd-planner.md'), 'msd-planner');
// Boundary: a filename with no trailing .md is returned unchanged (the
// regex has nothing to match) — matches `entry.name.replace(/\.md$/, '')`.
assert.equal(runtimeArtifactConversion.deriveAgentName('gsd-planner'), 'gsd-planner');
assert.equal(runtimeArtifactConversion.deriveAgentName('gsd-planner.MD'), 'gsd-planner.MD');
assert.equal(runtimeArtifactConversion.deriveAgentName('msd-planner'), 'msd-planner');
assert.equal(runtimeArtifactConversion.deriveAgentName('msd-planner.MD'), 'msd-planner.MD');
});
test('agent-descriptor-parity: I2 — real descriptor path with no agentCtx is unaffected (converter-only)', (t) => {
const { commandsGsd, agentsDir, root } = buildSourceTree(SAMPLE_AGENTS);
const targetDir = buildTargetDir(commandsGsd);
const { commandsMsd, agentsDir, root } = buildSourceTree(SAMPLE_AGENTS);
const targetDir = buildTargetDir(commandsMsd);
t.after(() => {
cleanup(root);
cleanup(targetDir);
@@ -455,8 +455,8 @@ test('agent-descriptor-parity: I2 — real descriptor path with no agentCtx is u
const agentsKindEntry = realLayout.kinds.find((k) => k.kind === 'agents');
const stagedDir = agentsKindEntry.stage({ name: 'full', skills: '*', agents: new Set() }); // no agentCtx
t.after(() => cleanup(stagedDir));
const planner = fs.readFileSync(path.join(stagedDir, 'gsd-planner.md'), 'utf8');
const original = fs.readFileSync(path.join(agentsDir, 'gsd-planner.md'), 'utf8');
const planner = fs.readFileSync(path.join(stagedDir, 'msd-planner.md'), 'utf8');
const original = fs.readFileSync(path.join(agentsDir, 'msd-planner.md'), 'utf8');
assert.equal(planner, original, 'no agentCtx must leave content byte-identical to source (converter:null == identity)');
});
@@ -465,8 +465,8 @@ test('agent-descriptor-parity: I2 — real descriptor path with no agentCtx is u
// ---------------------------------------------------------------------------
test('agent-descriptor-parity: J1 — claude effort is injected via applyAgentFrontmatterExtensions', () => {
const content = '---\nname: gsd-planner\ndescription: x\n---\n\nBody.\n';
const viaShared = runtimeArtifactConversion.applyAgentFrontmatterExtensions(content, { runtime: 'claude', agentName: 'gsd-planner', targetDir: null });
const content = '---\nname: msd-planner\ndescription: x\n---\n\nBody.\n';
const viaShared = runtimeArtifactConversion.applyAgentFrontmatterExtensions(content, { runtime: 'claude', agentName: 'msd-planner', targetDir: null });
assert.match(viaShared, /^effort: /m, 'expected an effort: key to be injected for claude');
});
@@ -476,20 +476,20 @@ test('agent-descriptor-parity: J2 — effort resolving to inherit writes NO effo
// with it) and never called applyAgentFrontmatterExtensions at all — so
// deleting the `universalEffort !== 'inherit'` guard at
// runtime-artifact-conversion.cts:3504 left this row green. This revision
// writes a REAL .planning/config.json under targetDir (readGsdEffectiveEffortConfig
// writes a REAL .planning/config.json under targetDir (readMsdEffectiveEffortConfig
// walks up from targetDir looking for it — install-effort-resolver.cts)
// and calls the REAL applyAgentFrontmatterExtensions end to end, so removing
// that guard makes THIS assertion fail (verified: red with the guard
// removed, green with it restored — see CHANGES report).
const targetDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-agent-parity-j2-'));
const targetDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-agent-parity-j2-'));
t.after(() => cleanup(targetDir));
fs.mkdirSync(path.join(targetDir, '.planning'), { recursive: true });
fs.writeFileSync(
path.join(targetDir, '.planning', 'config.json'),
JSON.stringify({ effort: { agent_overrides: { 'gsd-inherit-agent': 'inherit' } } }),
JSON.stringify({ effort: { agent_overrides: { 'msd-inherit-agent': 'inherit' } } }),
);
const content = '---\nname: gsd-inherit-agent\ndescription: x\n---\n\nBody.\n';
const out = runtimeArtifactConversion.applyAgentFrontmatterExtensions(content, { runtime: 'claude', agentName: 'gsd-inherit-agent', targetDir });
const content = '---\nname: msd-inherit-agent\ndescription: x\n---\n\nBody.\n';
const out = runtimeArtifactConversion.applyAgentFrontmatterExtensions(content, { runtime: 'claude', agentName: 'msd-inherit-agent', targetDir });
assert.doesNotMatch(out, /^effort:/m, 'an agent resolving to "inherit" must get no effort: key at all — not even effort: inherit');
// Sanity: injectEffortFrontmatter itself is dumb and WOULD write the
// literal if called with it — the guard is applyAgentFrontmatterExtensions
@@ -498,18 +498,18 @@ test('agent-descriptor-parity: J2 — effort resolving to inherit writes NO effo
});
test('agent-descriptor-parity: J3 — a runtime NOT declaring agentFrontmatterExtensions gets nothing injected', () => {
const content = '---\nname: gsd-plan-checker\ndescription: x\n---\n\nBody.\n';
const out = runtimeArtifactConversion.applyAgentFrontmatterExtensions(content, { runtime: 'opencode', agentName: 'gsd-plan-checker', targetDir: null });
const content = '---\nname: msd-plan-checker\ndescription: x\n---\n\nBody.\n';
const out = runtimeArtifactConversion.applyAgentFrontmatterExtensions(content, { runtime: 'opencode', agentName: 'msd-plan-checker', targetDir: null });
assert.equal(out, content, 'opencode declares no agentFrontmatterExtensions — output must be byte-identical to input');
});
test('agent-descriptor-parity: J4 — disallowedTools injected only on a READONLY_AGENT_DISALLOWED_TOOLS hit', () => {
const content = '---\nname: gsd-plan-checker\ndescription: x\n---\n\nBody.\n';
const hit = runtimeArtifactConversion.applyAgentFrontmatterExtensions(content, { runtime: 'claude', agentName: 'gsd-plan-checker', targetDir: null });
assert.match(hit, /^disallowedTools: /m, 'gsd-plan-checker is a declared read-only agent — expected a disallowedTools hit');
const content = '---\nname: msd-plan-checker\ndescription: x\n---\n\nBody.\n';
const hit = runtimeArtifactConversion.applyAgentFrontmatterExtensions(content, { runtime: 'claude', agentName: 'msd-plan-checker', targetDir: null });
assert.match(hit, /^disallowedTools: /m, 'msd-plan-checker is a declared read-only agent — expected a disallowedTools hit');
const missContent = '---\nname: gsd-not-a-readonly-agent\ndescription: x\n---\n\nBody.\n';
const miss = runtimeArtifactConversion.applyAgentFrontmatterExtensions(missContent, { runtime: 'claude', agentName: 'gsd-not-a-readonly-agent', targetDir: null });
const missContent = '---\nname: msd-not-a-readonly-agent\ndescription: x\n---\n\nBody.\n';
const miss = runtimeArtifactConversion.applyAgentFrontmatterExtensions(missContent, { runtime: 'claude', agentName: 'msd-not-a-readonly-agent', targetDir: null });
assert.doesNotMatch(miss, /^disallowedTools:/m, 'an agent absent from READONLY_AGENT_DISALLOWED_TOOLS must get no disallowedTools key');
});
@@ -518,24 +518,24 @@ test('agent-descriptor-parity: J4 — disallowedTools injected only on a READONL
// ---------------------------------------------------------------------------
test('agent-descriptor-parity: J5 — explicit model_overrides[agent] wins (highest precedence)', () => {
const modelOverrides = { 'gsd-planner': 'anthropic/explicit-model' };
const modelOverrides = { 'msd-planner': 'anthropic/explicit-model' };
const runtimeResolver = { resolve: () => ({ model: 'anthropic/tier-model' }) }; // would win if precedence were wrong
const result = installModelOverrideResolver.resolveAgentModelOverride('gsd-planner', modelOverrides, runtimeResolver);
const result = installModelOverrideResolver.resolveAgentModelOverride('msd-planner', modelOverrides, runtimeResolver);
assert.equal(result, 'anthropic/explicit-model');
});
test('agent-descriptor-parity: J6 — falls back to the runtime tier resolver when no explicit override exists', () => {
const runtimeResolver = { resolve: (agentName) => (agentName === 'gsd-planner' ? { model: 'anthropic/tier-model' } : null) };
const result = installModelOverrideResolver.resolveAgentModelOverride('gsd-planner', null, runtimeResolver);
const runtimeResolver = { resolve: (agentName) => (agentName === 'msd-planner' ? { model: 'anthropic/tier-model' } : null) };
const result = installModelOverrideResolver.resolveAgentModelOverride('msd-planner', null, runtimeResolver);
assert.equal(result, 'anthropic/tier-model');
});
test('agent-descriptor-parity: J7 — neither configured resolves to null (omit), never "" or the string "null"', () => {
const result = installModelOverrideResolver.resolveAgentModelOverride('gsd-planner', null, null);
const result = installModelOverrideResolver.resolveAgentModelOverride('msd-planner', null, null);
assert.equal(result, null);
assert.notEqual(result, '');
const contentWithoutModelOverride = installBin.convertClaudeToOpencodeFrontmatter(
'---\nname: gsd-planner\ndescription: x\ntools: Read\n---\n\nBody.\n',
'---\nname: msd-planner\ndescription: x\ntools: Read\n---\n\nBody.\n',
{ isAgent: true, modelOverride: result },
);
assert.doesNotMatch(contentWithoutModelOverride, /^model:/m, 'an omitted override must not appear as a model: key at all');
@@ -558,11 +558,11 @@ test('agent-descriptor-parity: J8 — kilo and opencode both resolve model overr
// metachar-bearing value would silently widen the match).
const expectedModelLine = `model: ${J8_OVERRIDE_MODEL}`;
assert.ok(
kiloOut.get('gsd-planner.md').split('\n').some((line) => line.trim() === expectedModelLine),
kiloOut.get('msd-planner.md').split('\n').some((line) => line.trim() === expectedModelLine),
'kilo must apply the shared model override via the real descriptor path',
);
assert.ok(
opencodeOut.get('gsd-planner.md').split('\n').some((line) => line.trim() === expectedModelLine),
opencodeOut.get('msd-planner.md').split('\n').some((line) => line.trim() === expectedModelLine),
'opencode must apply the SAME shared model override via the real descriptor path',
);
});
@@ -636,16 +636,16 @@ test('agent-descriptor-parity: K1 — every registry runtime declaring an agents
// #3712: this loop reaches `codex`, whose global skills kind declares a `home`
// override (`.agents`) resolved from os.homedir() rather than from targetDir.
// Sandboxing targetDir alone does NOT contain it — before this line the call
// below pruned every gsd-* skill from the developer's REAL ~/.agents/skills
// below pruned every msd-* skill from the developer's REAL ~/.agents/skills
// (71 -> 0) while the suite still exited 0. Sandbox HOME for the whole loop so
// codex's skills land in <homeDir>/.agents/skills inside a temp dir we clean.
const homeDir = createTempDir('gsd-adp-home-');
const homeDir = createTempDir('msd-adp-home-');
t.after(() => cleanup(homeDir));
sandboxHome(t, homeDir);
for (const runtime of runtimesWithAgentsKind) {
const { commandsGsd, root } = buildSourceTree(SAMPLE_AGENTS);
const targetDir = buildTargetDir(commandsGsd);
const { commandsMsd, root } = buildSourceTree(SAMPLE_AGENTS);
const targetDir = buildTargetDir(commandsMsd);
t.after(() => {
cleanup(root);
cleanup(targetDir);