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

@@ -4,7 +4,7 @@
// Per CONTRIBUTING.md exception matrix.
/**
* GSD Tests - path replacement in install.js
* MSD Tests - path replacement in install.js
*
* Verifies that global installs produce $HOME/ paths in .md files,
* so that shell commands expand correctly inside double quotes.
@@ -23,8 +23,8 @@ const repoRoot = path.join(__dirname, '..');
// Thin adapter over the REAL _computePathPrefix (ADR-1508 Phase 2: deleted hand-copy).
// Old signature: computePathPrefix(homedir, targetDir) assumed isGlobal=true, isOpencode=false.
// This adapter preserves that contract so existing call-sites stay unchanged.
process.env['GSD_TEST_MODE'] = '1';
const { _computePathPrefix } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
process.env['MSD_TEST_MODE'] = '1';
const { _computePathPrefix } = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
function computePathPrefix(homedir, targetDir) {
return _computePathPrefix({
isGlobal: true,
@@ -89,10 +89,10 @@ describe('pathPrefix computation', () => {
isGlobal: true,
isOpencode: false,
isWindowsHost: false,
resolvedTarget: '/opt/gsd/.claude',
resolvedTarget: '/opt/msd/.claude',
homeDir: '/home/user',
});
assert.strictEqual(prefix, '/opt/gsd/.claude/');
assert.strictEqual(prefix, '/opt/msd/.claude/');
assert.ok(!prefix.includes('$HOME'), `Should not contain $HOME for non-home paths`);
});
@@ -124,7 +124,7 @@ describe('source .md files have no quoted-tilde shell patterns', () => {
return results;
}
const dirsToCheck = ['commands', 'gsd-core', 'agents'].map(d => path.join(repoRoot, d));
const dirsToCheck = ['commands', 'msd-core', 'agents'].map(d => path.join(repoRoot, d));
const mdFiles = dirsToCheck.flatMap(collectMdFiles);
test('source .md files exist', () => {
@@ -166,7 +166,7 @@ describe('installed .md files contain no resolved absolute paths', () => {
return results;
}
const dirsToCheck = ['commands', 'gsd-core', 'agents'].map(d => path.join(repoRoot, d));
const dirsToCheck = ['commands', 'msd-core', 'agents'].map(d => path.join(repoRoot, d));
const mdFiles = dirsToCheck.flatMap(collectMdFiles);
test('after replacement, no .md file contains os.homedir()', () => {
@@ -233,14 +233,14 @@ const assert = require('node:assert/strict');
let computePathPrefix;
before(() => {
process.env.GSD_TEST_MODE = '1';
process.env.MSD_TEST_MODE = '1';
// Re-require fresh in case other tests already loaded it.
delete require.cache[require.resolve('../gsd-core/bin/lib/runtime-artifact-conversion.cjs')];
({ _computePathPrefix: computePathPrefix } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs'));
delete require.cache[require.resolve('../msd-core/bin/lib/runtime-artifact-conversion.cjs')];
({ _computePathPrefix: computePathPrefix } = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs'));
});
after(() => {
delete process.env.GSD_TEST_MODE;
delete process.env.MSD_TEST_MODE;
});
describe('bug-2376: OpenCode on Windows must use absolute path, not $HOME', () => {
@@ -315,13 +315,13 @@ const { cleanup } = require('./helpers.cjs');
let computePathPrefix;
before(() => {
process.env.GSD_TEST_MODE = '1';
delete require.cache[require.resolve('../gsd-core/bin/lib/runtime-artifact-conversion.cjs')];
({ _computePathPrefix: computePathPrefix } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs'));
process.env.MSD_TEST_MODE = '1';
delete require.cache[require.resolve('../msd-core/bin/lib/runtime-artifact-conversion.cjs')];
({ _computePathPrefix: computePathPrefix } = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs'));
});
after(() => {
delete process.env.GSD_TEST_MODE;
delete process.env.MSD_TEST_MODE;
});
describe('bug-2831: OpenCode pathPrefix uses absolute path on all platforms', () => {
@@ -391,18 +391,18 @@ describe('bug-2831: OpenCode pathPrefix uses absolute path on all platforms', ()
// copyFlattenedCommands when writing OpenCode command files. We invoke the
// real exported computePathPrefix; the regex passes mirror the install.js
// call sites (globalClaudeRegex / globalClaudeHomeRegex).
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2831-'));
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2831-'));
try {
const srcRoot = path.join(tmp, 'src');
const targetRoot = path.join(tmp, 'home', '.config', 'opencode');
const srcCmdDir = path.join(srcRoot, 'commands', 'gsd');
const srcCmdDir = path.join(srcRoot, 'commands', 'msd');
fs.mkdirSync(srcCmdDir, { recursive: true });
fs.mkdirSync(targetRoot, { recursive: true });
const srcFile = path.join(srcCmdDir, 'autonomous.md');
fs.writeFileSync(
srcFile,
'---\nname: autonomous\n---\n<execution_context>\n@~/.claude/gsd-core/workflows/autonomous.md\n@$HOME/.claude/gsd-core/references/ui-brand.md\n</execution_context>\n'
'---\nname: autonomous\n---\n<execution_context>\n@~/.claude/msd-core/workflows/autonomous.md\n@$HOME/.claude/msd-core/references/ui-brand.md\n</execution_context>\n'
);
const homeDir = path.join(tmp, 'home').replace(/\\/g, '/');
@@ -454,8 +454,8 @@ describe('bug-2831: OpenCode pathPrefix uses absolute path on all platforms', ()
// tilde form for @-references when the prefix is the $HOME (global) form.
// ────────────────────────────────────────────────────────────────────────
describe('#3133: global Claude @-references resolve on tilde, not $HOME', () => {
// Re-require to also pull _applyRuntimeRewrites (module is cached under GSD_TEST_MODE).
const conv = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
// Re-require to also pull _applyRuntimeRewrites (module is cached under MSD_TEST_MODE).
const conv = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
const _computePathPrefix = conv._computePathPrefix;
const _applyRuntimeRewrites = conv._applyRuntimeRewrites;
@@ -469,32 +469,32 @@ describe('#3133: global Claude @-references resolve on tilde, not $HOME', () =>
assert.ok(globalClaudePrefix.startsWith('$HOME/'), `expected $HOME prefix, got ${globalClaudePrefix}`);
test('row 1 — global Claude @-reference stays on tilde (resolves), not $HOME', () => {
const src = '---\nname: gsd-stats\n---\n<execution_context>\n@~/.claude/gsd-core/workflows/stats.md\n</execution_context>\n';
const src = '---\nname: msd-stats\n---\n<execution_context>\n@~/.claude/msd-core/workflows/stats.md\n</execution_context>\n';
const out = _applyRuntimeRewrites(src, 'claude', globalClaudePrefix, true, undefined);
assert.match(out, /@~\/\.claude\/gsd-core\/workflows\/stats\.md/, `@-ref must stay on tilde; got:\n${out}`);
assert.match(out, /@~\/\.claude\/msd-core\/workflows\/stats\.md/, `@-ref must stay on tilde; got:\n${out}`);
assert.doesNotMatch(out, /@\$HOME/, `must not emit @$HOME (Claude does not expand it); got:\n${out}`);
});
test('row 2 — global Claude normalizes an @$HOME source reference to @~', () => {
// Source may already carry @$HOME (e.g. a prior bad render); it must end on @~.
const src = '<execution_context>\n@$HOME/.claude/gsd-core/references/ui-brand.md\n</execution_context>\n';
const src = '<execution_context>\n@$HOME/.claude/msd-core/references/ui-brand.md\n</execution_context>\n';
const out = _applyRuntimeRewrites(src, 'claude', globalClaudePrefix, true, undefined);
assert.match(out, /@~\/\.claude\/gsd-core\/references\/ui-brand\.md/, `@$HOME source must normalize to @~; got:\n${out}`);
assert.match(out, /@~\/\.claude\/msd-core\/references\/ui-brand\.md/, `@$HOME source must normalize to @~; got:\n${out}`);
assert.doesNotMatch(out, /@\$HOME/, `no @$HOME may remain; got:\n${out}`);
});
test('row 3 — global Claude shell contexts keep $HOME (#1284 preserved)', () => {
// `~` does NOT expand inside double quotes; $HOME does. Shell commands must stay $HOME.
const src = 'Run: node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" query state\n';
const src = 'Run: node "$HOME/.claude/msd-core/bin/msd-tools.cjs" query state\n';
const out = _applyRuntimeRewrites(src, 'claude', globalClaudePrefix, true, undefined);
assert.match(out, /node "\$HOME\/\.claude\/gsd-core\/bin\/gsd-tools\.cjs"/, `shell $HOME must be preserved; got:\n${out}`);
assert.match(out, /node "\$HOME\/\.claude\/msd-core\/bin\/msd-tools\.cjs"/, `shell $HOME must be preserved; got:\n${out}`);
assert.doesNotMatch(out, /node "~\//, `must not introduce quoted-tilde (re-introduces #1284); got:\n${out}`);
});
test('row 4 — global Claude bare ~/ shell (unquoted) becomes $HOME', () => {
const src = 'cat ~/.claude/gsd-core/VERSION\n';
const src = 'cat ~/.claude/msd-core/VERSION\n';
const out = _applyRuntimeRewrites(src, 'claude', globalClaudePrefix, true, undefined);
assert.match(out, /cat \$HOME\/\.claude\/gsd-core\/VERSION/, `unquoted ~/ shell must become $HOME; got:\n${out}`);
assert.match(out, /cat \$HOME\/\.claude\/msd-core\/VERSION/, `unquoted ~/ shell must become $HOME; got:\n${out}`);
});
test('row 5 — local Claude @-reference rewrites to the absolute target', () => {
@@ -505,14 +505,14 @@ describe('#3133: global Claude @-references resolve on tilde, not $HOME', () =>
resolvedTarget: '/abs/projects/foo/.claude', homeDir,
});
assert.ok(!localPrefix.startsWith('$HOME'), `local prefix must be absolute, got ${localPrefix}`);
const src = '<execution_context>\n@~/.claude/gsd-core/workflows/stats.md\n</execution_context>\n';
const src = '<execution_context>\n@~/.claude/msd-core/workflows/stats.md\n</execution_context>\n';
const out = _applyRuntimeRewrites(src, 'claude', localPrefix, false, undefined);
assert.match(out, /@\/abs\/projects\/foo\/\.claude\/gsd-core\/workflows\/stats\.md/, `local @-ref must rewrite to absolute target; got:\n${out}`);
assert.match(out, /@\/abs\/projects\/foo\/\.claude\/msd-core\/workflows\/stats\.md/, `local @-ref must rewrite to absolute target; got:\n${out}`);
assert.doesNotMatch(out, /@~\/\.claude/, `local @-ref must not stay on global ~/; got:\n${out}`);
});
test('row 6 — global Claude @-ref normalization is idempotent (re-surface safe)', () => {
const src = '<execution_context>\n@~/.claude/gsd-core/workflows/stats.md\n</execution_context>\n';
const src = '<execution_context>\n@~/.claude/msd-core/workflows/stats.md\n</execution_context>\n';
const once = _applyRuntimeRewrites(src, 'claude', globalClaudePrefix, true, undefined);
const twice = _applyRuntimeRewrites(once, 'claude', globalClaudePrefix, true, undefined);
assert.equal(twice, once, `re-running the rewrite must be stable (idempotent)`);
@@ -520,7 +520,7 @@ describe('#3133: global Claude @-references resolve on tilde, not $HOME', () =>
});
test('row 7 — global Claude emits no resolved homedir literal (#3503 preserved)', () => {
const src = '<execution_context>\n@~/.claude/gsd-core/workflows/stats.md\n</execution_context>\nnode "$HOME/.claude/gsd-core/bin/gsd-tools.cjs"\n';
const src = '<execution_context>\n@~/.claude/msd-core/workflows/stats.md\n</execution_context>\nnode "$HOME/.claude/msd-core/bin/msd-tools.cjs"\n';
const out = _applyRuntimeRewrites(src, 'claude', globalClaudePrefix, true, undefined);
// A real homedir leak is followed by a path separator (#3503 trailing-slash rule).
assert.ok(!out.includes(homeDir + '/'), `must not leak resolved homedir ${homeDir}/; got:\n${out}`);
@@ -533,33 +533,33 @@ describe('#3133: global Claude @-references resolve on tilde, not $HOME', () =>
isGlobal: true, isOpencode: false, isWindowsHost: isWin,
resolvedTarget: homeDir + '/.codex', homeDir,
});
const src = '<execution_context>\n@~/.claude/gsd-core/workflows/stats.md\n</execution_context>\n';
const src = '<execution_context>\n@~/.claude/msd-core/workflows/stats.md\n</execution_context>\n';
const out = _applyRuntimeRewrites(src, 'codex', codexPrefix, true, undefined);
// Codex rewrites .claude -> .codex; the @-ref must NOT have been restored to @~/.claude/.
assert.doesNotMatch(out, /@~\/\.claude\//, `codex must not get the claude @~/.claude restoration; got:\n${out}`);
assert.match(out, /@\$HOME\/\.codex\/gsd-core\/workflows\/stats\.md/, `codex keeps its own rewrite; got:\n${out}`);
assert.match(out, /@\$HOME\/\.codex\/msd-core\/workflows\/stats\.md/, `codex keeps its own rewrite; got:\n${out}`);
});
test('row 9 — global Claude @-ref preserves the full tail path', () => {
// Deeper subpath than row 1 — ensures the @-ref normalization does not
// truncate the tail. (No `$` anchor: a realistic @-ref line ends with `\n`.)
const src = '@~/.claude/gsd-core/references/ui-brand.md\n';
const src = '@~/.claude/msd-core/references/ui-brand.md\n';
const out = _applyRuntimeRewrites(src, 'claude', globalClaudePrefix, true, undefined);
assert.match(out, /@~\/\.claude\/gsd-core\/references\/ui-brand\.md/, `tail must be intact; got:\n${out}`);
assert.match(out, /@~\/\.claude\/msd-core\/references\/ui-brand\.md/, `tail must be intact; got:\n${out}`);
assert.doesNotMatch(out, /@\$HOME/, `no @$HOME; got:\n${out}`);
});
});
// ────────────────────────────────────────────────────────────────────────
// #3544: `$HOME` does NOT expand in a Claude Code @-import (measured — see
// .gsd/bug/fix-3544-home-expansion-spec-tree/10-diagnosis.md's ADDENDUM).
// .msd/bug/fix-3544-home-expansion-spec-tree/10-diagnosis.md's ADDENDUM).
// #3133 (above) already fixed this for the SKILL/command staging pipeline
// (_applyRuntimeRewrites's 'claude' case) by restoring @-anchored $HOME
// references to tilde AFTER the blanket $HOME substitution. #3544 found the
// identical defect in bin/install.js's copyWithPathReplacement — the
// gsd-core/ SPEC-TREE emit path — which never had that restore step: every
// `@~/.claude/gsd-core/…` include in a global install's workflows/references
// tree (e.g. gsd-core/workflows/plan-phase.md's own @-includes) silently
// msd-core/ SPEC-TREE emit path — which never had that restore step: every
// `@~/.claude/msd-core/…` include in a global install's workflows/references
// tree (e.g. msd-core/workflows/plan-phase.md's own @-includes) silently
// resolved to nothing.
//
// The old test at the top of this file ('$HOME expands inside double-quoted
@@ -574,37 +574,37 @@ describe('#3133: global Claude @-references resolve on tilde, not $HOME', () =>
// local installs, and a second non-Claude global runtime (Cursor).
// ────────────────────────────────────────────────────────────────────────
describe('#3544: _restoreClaudeGlobalAtRefTilde (spec-tree @-ref restore, unit)', () => {
const { _restoreClaudeGlobalAtRefTilde } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
const { _restoreClaudeGlobalAtRefTilde } = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
test('restores an @$HOME/.claude/ reference to @~/.claude/ when pathPrefix is the $HOME form', () => {
const src = '@$HOME/.claude/gsd-core/references/ui-brand.md\n';
const src = '@$HOME/.claude/msd-core/references/ui-brand.md\n';
const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude/');
assert.strictEqual(out, '@~/.claude/gsd-core/references/ui-brand.md\n');
assert.strictEqual(out, '@~/.claude/msd-core/references/ui-brand.md\n');
});
test('leaves a non-@-anchored $HOME/.claude/ shell reference untouched (#1284)', () => {
const src = 'node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" query state\n';
const src = 'node "$HOME/.claude/msd-core/bin/msd-tools.cjs" query state\n';
const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude/');
assert.strictEqual(out, src, 'a double-quoted shell $HOME reference must never be rewritten to ~ (breaks #1284)');
});
test('is a no-op when pathPrefix is an absolute (local-install) path', () => {
const src = '@$HOME/.claude/gsd-core/references/ui-brand.md\n';
const src = '@$HOME/.claude/msd-core/references/ui-brand.md\n';
const out = _restoreClaudeGlobalAtRefTilde(src, '/abs/projects/foo/.claude/');
assert.strictEqual(out, src);
});
test('handles multiple @-references and preserves an interleaved shell reference', () => {
const src =
'@$HOME/.claude/gsd-core/references/a.md\n' +
'node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs"\n' +
'@$HOME/.claude/gsd-core/references/b.md\n';
'@$HOME/.claude/msd-core/references/a.md\n' +
'node "$HOME/.claude/msd-core/bin/msd-tools.cjs"\n' +
'@$HOME/.claude/msd-core/references/b.md\n';
const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude/');
assert.strictEqual(
out,
'@~/.claude/gsd-core/references/a.md\n' +
'node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs"\n' +
'@~/.claude/gsd-core/references/b.md\n',
'@~/.claude/msd-core/references/a.md\n' +
'node "$HOME/.claude/msd-core/bin/msd-tools.cjs"\n' +
'@~/.claude/msd-core/references/b.md\n',
);
});
@@ -614,9 +614,9 @@ describe('#3544: _restoreClaudeGlobalAtRefTilde (spec-tree @-ref restore, unit)'
// rather than a hardcoded '.claude' literal, or it silently no-ops for any
// non-default config-dir name (reproducing the exact defect #3544 fixes).
test('generalizes to a non-default config-dir name (not hardcoded to .claude)', () => {
const src = '@$HOME/.claude-work/gsd-core/workflows/plan-phase.md\n';
const src = '@$HOME/.claude-work/msd-core/workflows/plan-phase.md\n';
const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude-work/');
assert.strictEqual(out, '@~/.claude-work/gsd-core/workflows/plan-phase.md\n');
assert.strictEqual(out, '@~/.claude-work/msd-core/workflows/plan-phase.md\n');
});
// #3544 review (Finding 2): quote-aware, not line-start-anchored. A
@@ -669,7 +669,7 @@ describe('#3544: _restoreClaudeGlobalAtRefTilde (spec-tree @-ref restore, unit)'
// the fixture.
// ────────────────────────────────────────────────────────────────────────
describe('#3719 GAP 2: _restoreClaudeGlobalAtRefTilde is immune to $&/$` in pathPrefix', () => {
const { _restoreClaudeGlobalAtRefTilde } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
const { _restoreClaudeGlobalAtRefTilde } = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
test('a pathPrefix containing a literal "$&" round-trips to the plain tilde form (not duplicated)', () => {
const dollarAmp = '$' + '&';
@@ -705,24 +705,24 @@ describe('#3719 GAP 2: _restoreClaudeGlobalAtRefTilde is immune to $&/$` in path
// restoreClaudeGlobalAtRefTilde's doc comment (runtime-artifact-conversion.cts)
// for the controlled measurement that established the documented form is the
// one that actually resolves.
describe('#3544: spawned installer — gsd-core/ spec tree @-refs resolve on tilde', () => {
describe('#3544: spawned installer — msd-core/ spec tree @-refs resolve on tilde', () => {
const { before, after } = require('node:test');
const { cleanup } = require('./helpers.cjs');
const { runNode } = require('./helpers/process-seam.cjs');
const { INSTALL_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const { INSTALL_SCRIPT, installerEnv } = require('./helpers/install-shared.cjs');
const { splitLines } = require('../gsd-core/bin/lib/text-lines.cjs');
const { splitLines } = require('../msd-core/bin/lib/text-lines.cjs');
const sandboxes = [];
function makeSandbox(prefix) {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `gsd-3544-${prefix}-`));
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `msd-3544-${prefix}-`));
sandboxes.push(dir);
return dir;
}
// No --config-dir: install.js must resolve the DEFAULT <HOME>/.claude (or
// runtime equivalent) under the sandboxed HOME, so the emitted tree has the
// real `~/.claude/gsd-core/…` shape the diagnosis measured — a --config-dir
// real `~/.claude/msd-core/…` shape the diagnosis measured — a --config-dir
// run pins configDir to the sandbox root itself (target === home), which
// never reproduces that layout.
function spawnInstall(args, home) {
@@ -792,8 +792,8 @@ describe('#3544: spawned installer — gsd-core/ spec tree @-refs resolve on til
for (const dir of sandboxes) cleanup(dir);
});
test('claude --global: gsd-core/workflows/plan-phase.md @-lines use ~/, never @$HOME', () => {
const planPhase = path.join(claudeGlobalHome, '.claude', 'gsd-core', 'workflows', 'plan-phase.md');
test('claude --global: msd-core/workflows/plan-phase.md @-lines use ~/, never @$HOME', () => {
const planPhase = path.join(claudeGlobalHome, '.claude', 'msd-core', 'workflows', 'plan-phase.md');
assert.ok(fs.existsSync(planPhase), `expected ${planPhase} to exist`);
const content = fs.readFileSync(planPhase, 'utf8');
const atLines = splitLines(content).filter((l) => l.startsWith('@'));
@@ -807,9 +807,9 @@ describe('#3544: spawned installer — gsd-core/ spec tree @-refs resolve on til
);
});
test('claude --global: zero ^@$HOME lines remain anywhere under the emitted gsd-core/ tree', () => {
const gsdCoreDir = path.join(claudeGlobalHome, '.claude', 'gsd-core');
const homeLines = collectAtLines(gsdCoreDir).filter(({ line }) => /^@\$HOME/.test(line));
test('claude --global: zero ^@$HOME lines remain anywhere under the emitted msd-core/ tree', () => {
const msdCoreDir = path.join(claudeGlobalHome, '.claude', 'msd-core');
const homeLines = collectAtLines(msdCoreDir).filter(({ line }) => /^@\$HOME/.test(line));
assert.deepStrictEqual(homeLines, [], `found @$HOME lines: ${JSON.stringify(homeLines)}`);
});
@@ -817,14 +817,14 @@ describe('#3544: spawned installer — gsd-core/ spec tree @-refs resolve on til
// name reproduces the exact defect #3544 fixes if the restore is hardcoded
// to '.claude/'. This must FAIL against the pre-fix restoreClaudeGlobalAtRefTilde.
test('claude --global --config-dir <custom>: zero @$HOME lines remain, and @~/<custom>/ appears', () => {
const gsdCoreDir = path.join(claudeConfigDirTarget, 'gsd-core');
assert.ok(fs.existsSync(gsdCoreDir), `expected ${gsdCoreDir} to exist`);
const homeLines = collectAtLines(gsdCoreDir).filter(({ line }) => /^@\$HOME/.test(line));
const msdCoreDir = path.join(claudeConfigDirTarget, 'msd-core');
assert.ok(fs.existsSync(msdCoreDir), `expected ${msdCoreDir} to exist`);
const homeLines = collectAtLines(msdCoreDir).filter(({ line }) => /^@\$HOME/.test(line));
assert.deepStrictEqual(homeLines, [], `found @$HOME lines under a custom --config-dir: ${JSON.stringify(homeLines)}`);
const tildeLines = collectAtLines(gsdCoreDir).filter(({ line }) => line.startsWith('@~/.claude-work/'));
const tildeLines = collectAtLines(msdCoreDir).filter(({ line }) => line.startsWith('@~/.claude-work/'));
assert.ok(
tildeLines.length > 0,
`expected at least one @~/.claude-work/ line under a custom --config-dir, got: ${JSON.stringify(collectAtLines(gsdCoreDir))}`,
`expected at least one @~/.claude-work/ line under a custom --config-dir, got: ${JSON.stringify(collectAtLines(msdCoreDir))}`,
);
});
@@ -832,7 +832,7 @@ describe('#3544: spawned installer — gsd-core/ spec tree @-refs resolve on til
// At least one emitted file must still carry a double-quoted
// "$HOME/.claude/" shell reference — proving the restore step did NOT
// blanket-convert every $HOME occurrence, only @-anchored ones.
const gsdCoreDir = path.join(claudeGlobalHome, '.claude', 'gsd-core');
const msdCoreDir = path.join(claudeGlobalHome, '.claude', 'msd-core');
let sawShellHome = false;
const walk = (dir) => {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
@@ -843,23 +843,23 @@ describe('#3544: spawned installer — gsd-core/ spec tree @-refs resolve on til
}
}
};
walk(gsdCoreDir);
walk(msdCoreDir);
assert.ok(sawShellHome, 'expected at least one emitted file to preserve a "$HOME/.claude/ shell reference');
});
test('claude --local: pathPrefix is absolute — no @$HOME or bare @~ leak (negative proof)', () => {
const gsdCoreDir = path.join(claudeLocalHome, '.claude', 'gsd-core');
assert.ok(fs.existsSync(gsdCoreDir), `expected ${gsdCoreDir} to exist`);
const badLines = collectAtLines(gsdCoreDir).filter(
const msdCoreDir = path.join(claudeLocalHome, '.claude', 'msd-core');
assert.ok(fs.existsSync(msdCoreDir), `expected ${msdCoreDir} to exist`);
const badLines = collectAtLines(msdCoreDir).filter(
({ line }) => /^@\$HOME/.test(line) || /^@~\//.test(line),
);
assert.deepStrictEqual(badLines, [], `local install must not emit @$HOME or @~/: ${JSON.stringify(badLines)}`);
});
test('opencode --global: unaffected — never emits an @~/.claude/ line (negative proof)', () => {
const gsdCoreDir = path.join(opencodeGlobalHome, '.config', 'opencode', 'gsd-core');
assert.ok(fs.existsSync(gsdCoreDir), `expected ${gsdCoreDir} to exist`);
const leaked = collectAtLines(gsdCoreDir).filter(({ line }) => line.startsWith('@~/.claude/'));
const msdCoreDir = path.join(opencodeGlobalHome, '.config', 'opencode', 'msd-core');
assert.ok(fs.existsSync(msdCoreDir), `expected ${msdCoreDir} to exist`);
const leaked = collectAtLines(msdCoreDir).filter(({ line }) => line.startsWith('@~/.claude/'));
assert.deepStrictEqual(
leaked, [],
`opencode must never emit a @~/.claude/ line (claude-only restore leaked): ${JSON.stringify(leaked)}`,
@@ -867,9 +867,9 @@ describe('#3544: spawned installer — gsd-core/ spec tree @-refs resolve on til
});
test('cursor --global: unaffected — still emits @$HOME/.cursor/ (unrestored), never @~/.claude/ (negative proof)', () => {
const gsdCoreDir = path.join(cursorGlobalHome, '.cursor', 'gsd-core');
assert.ok(fs.existsSync(gsdCoreDir), `expected ${gsdCoreDir} to exist`);
const atLines = collectAtLines(gsdCoreDir);
const msdCoreDir = path.join(cursorGlobalHome, '.cursor', 'msd-core');
assert.ok(fs.existsSync(msdCoreDir), `expected ${msdCoreDir} to exist`);
const atLines = collectAtLines(msdCoreDir);
const leaked = atLines.filter(({ line }) => line.startsWith('@~/.claude/'));
assert.deepStrictEqual(
leaked, [],
@@ -887,8 +887,8 @@ describe('#3544: spawned installer — gsd-core/ spec tree @-refs resolve on til
// path-rewrite step, install-profiles.cts:964) does the 4 base ~/.claude/ and
// $HOME/.claude/ substitutions but NEVER calls restoreClaudeGlobalAtRefTilde
// — the SAME restore #3133 wired into `_applyRuntimeRewrites`'s 'claude' case
// and #3544 wired into the gsd-core/ spec-tree emit path. Consequence: every
// `agents/gsd-*.md` `@`-include (mandatory-initial-read.md, the
// and #3544 wired into the msd-core/ spec-tree emit path. Consequence: every
// `agents/msd-*.md` `@`-include (mandatory-initial-read.md, the
// untrusted-input boundary, the agent-skills bootstrap, etc.) ships as
// `@$HOME/.claude/...` in a global Claude install, which Claude Code does not
// expand — the include silently loads nothing. Unlike #3133/#3544, this is a
@@ -896,13 +896,13 @@ describe('#3544: spawned installer — gsd-core/ spec tree @-refs resolve on til
// $HOME/.claude/ substitutions non-@-anchored text still needs (#1284).
// ────────────────────────────────────────────────────────────────────────
describe('#3719: applyAgentPathRewrites must restore @-anchored $HOME refs to tilde', () => {
const { applyAgentPathRewrites } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
const { applyAgentPathRewrites } = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
const globalHomePrefix = '$HOME/.claude/';
test('row 1 [RED] — an @-include through the agents path emits @~/… not @$HOME/…', () => {
const src = '@~/.claude/gsd-core/references/mandatory-initial-read.md\n';
const src = '@~/.claude/msd-core/references/mandatory-initial-read.md\n';
const out = applyAgentPathRewrites(src, 'claude', globalHomePrefix);
assert.strictEqual(out, '@~/.claude/gsd-core/references/mandatory-initial-read.md\n');
assert.strictEqual(out, '@~/.claude/msd-core/references/mandatory-initial-read.md\n');
});
// #3719 review (MINOR 3): the original control string contained no `@` at
@@ -924,7 +924,7 @@ describe('#3719: applyAgentPathRewrites must restore @-anchored $HOME refs to ti
});
test('row 7 [CONTROL] — a noPathRewrite runtime (copilot) is untouched', () => {
const src = '@~/.claude/gsd-core/references/mandatory-initial-read.md\n';
const src = '@~/.claude/msd-core/references/mandatory-initial-read.md\n';
const out = applyAgentPathRewrites(src, 'copilot', globalHomePrefix);
assert.strictEqual(out, src, 'noPathRewrite runtimes must be returned unchanged (no substitutions at all)');
});
@@ -936,16 +936,16 @@ describe('#3719: applyAgentPathRewrites must restore @-anchored $HOME refs to ti
// pathPrefix substitution AND then hit the guard, which must suppress the
// `@~` restore because they are not `claude`.
test('row 10 [CONTROL] — cursor (non-claude, non-noPathRewrite) keeps @$HOME/.cursor/…, does not restore to tilde', () => {
const src = '@~/.claude/gsd-core/references/mandatory-initial-read.md\n';
const src = '@~/.claude/msd-core/references/mandatory-initial-read.md\n';
const out = applyAgentPathRewrites(src, 'cursor', '$HOME/.cursor/');
assert.strictEqual(out, '@$HOME/.cursor/gsd-core/references/mandatory-initial-read.md\n',
assert.strictEqual(out, '@$HOME/.cursor/msd-core/references/mandatory-initial-read.md\n',
'cursor must keep the @$HOME/.cursor/ form — the runtime guard must suppress the claude-only tilde restore');
});
test('row 11 [CONTROL] — kilo (non-claude, non-noPathRewrite) keeps @$HOME/.kilo/…, does not restore to tilde', () => {
const src = '@~/.claude/gsd-core/references/mandatory-initial-read.md\n';
const src = '@~/.claude/msd-core/references/mandatory-initial-read.md\n';
const out = applyAgentPathRewrites(src, 'kilo', '$HOME/.kilo/');
assert.strictEqual(out, '@$HOME/.kilo/gsd-core/references/mandatory-initial-read.md\n',
assert.strictEqual(out, '@$HOME/.kilo/msd-core/references/mandatory-initial-read.md\n',
'kilo must keep the @$HOME/.kilo/ form — the runtime guard must suppress the claude-only tilde restore');
});