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:
@@ -17,8 +17,8 @@ const {
|
||||
platformWriteSync,
|
||||
platformReadSync,
|
||||
platformEnsureDir,
|
||||
dispatchGsdCommand,
|
||||
resolveGsdToolsPath,
|
||||
dispatchMsdCommand,
|
||||
resolveMsdToolsPath,
|
||||
projectPathActionProjection,
|
||||
projectPathExportLine,
|
||||
posixNormalize,
|
||||
@@ -31,7 +31,7 @@ const {
|
||||
escapeSingleQuotedShellLiteral,
|
||||
retryRenameSync,
|
||||
contentChangedAfterNormalize,
|
||||
} = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
} = require(path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
|
||||
const { createTempGitProject, createTempDir, cleanup } = require('./helpers.cjs');
|
||||
|
||||
@@ -44,7 +44,7 @@ const { createTempGitProject, createTempDir, cleanup } = require('./helpers.cjs'
|
||||
const EXEC_TOOL_OPTION_PASSTHROUGH_TIMEOUT_MS = 1234;
|
||||
|
||||
/**
|
||||
* Deliberately far smaller than any real dispatchGsdCommand invocation
|
||||
* Deliberately far smaller than any real dispatchMsdCommand invocation
|
||||
* could complete, forcing a genuine wall-clock timeout so this test can
|
||||
* assert timedOut:true is reported correctly (mirrors the
|
||||
* RUN_BASH_SCRIPT_FORCED_TIMEOUT_MS / BOUNDED_SHELL_FORCED_TIMEOUT_MS
|
||||
@@ -135,7 +135,7 @@ describe('execTool', () => {
|
||||
|
||||
// ─── resolveExecutableBinary (#3411) ────────────────────────────────────────
|
||||
// Platform is always INJECTED via opts.platform/opts.env — every win32 case
|
||||
// runs on macOS/Linux too. See .gsd/phase/feat-3411-windows-binary-seam/50-test-matrix.md.
|
||||
// runs on macOS/Linux too. See .msd/phase/feat-3411-windows-binary-seam/50-test-matrix.md.
|
||||
|
||||
describe('resolveExecutableBinary (#3411)', () => {
|
||||
test('R1: is exported as a function', () => {
|
||||
@@ -434,7 +434,7 @@ describe('resolveExecutableBinary (#3411)', () => {
|
||||
// ─── resolveExecutableBinary seam options (#3618) ───────────────────────────
|
||||
// prependPaths / requireExecutable — both opt-in, both default off, so
|
||||
// Phase 1 callers (R1-R25 above, P1-P16 below) stay byte-identical. See
|
||||
// .gsd/phase/chore-3618-fallow-binary-resolution/50-test-matrix.md (S1-S12).
|
||||
// .msd/phase/chore-3618-fallow-binary-resolution/50-test-matrix.md (S1-S12).
|
||||
|
||||
describe('resolveExecutableBinary seam options (#3618)', () => {
|
||||
test('S1: prependPaths with binary only in dirA resolves in dirA', () => {
|
||||
@@ -658,7 +658,7 @@ describe('resolveExecutableBinary seam options (#3618)', () => {
|
||||
// ambient environment." Added so resolveFallowBinary can hand the seam a
|
||||
// search path without also hand-threading PATHEXT (a private PATHEXT read
|
||||
// is exactly the shape local/no-private-binary-resolution forbids outside
|
||||
// this seam). See .gsd/phase/chore-3619-no-bare-binary-spawn/50-test-matrix.md (O1-O7).
|
||||
// this seam). See .msd/phase/chore-3619-no-bare-binary-spawn/50-test-matrix.md (O1-O7).
|
||||
|
||||
describe('resolveExecutableBinary pathOverride (#3619)', () => {
|
||||
test('O1: pathOverride omitted — identical to today, env.PATH is used', () => {
|
||||
@@ -1088,9 +1088,9 @@ describe('execTool (#3411 windows resolution)', () => {
|
||||
test('E2: posix ENOENT contract', (t) => {
|
||||
if (process.platform === 'win32') { t.skip('posix ENOENT shape'); return; }
|
||||
|
||||
const result = execTool('gsd-definitely-absent-binary-9f3a', []);
|
||||
const result = execTool('msd-definitely-absent-binary-9f3a', []);
|
||||
assert.equal(result.exitCode, 127);
|
||||
assert.equal(result.stderr, 'gsd-definitely-absent-binary-9f3a: not found');
|
||||
assert.equal(result.stderr, 'msd-definitely-absent-binary-9f3a: not found');
|
||||
});
|
||||
|
||||
test('E3: execTool mediates a .cmd through cmd.exe on win32', (t) => {
|
||||
@@ -1144,24 +1144,24 @@ describe('execTool (#3411 windows resolution)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── dispatchGsdCommand (#2102 Stage 2 — subprocess-shim dispatch to gsd-tools.cjs) ──
|
||||
// ─── dispatchMsdCommand (#2102 Stage 2 — subprocess-shim dispatch to msd-tools.cjs) ──
|
||||
//
|
||||
// The command-routing hub (`createHub()`) has no fully-populated factory
|
||||
// anywhere in the tree — every caller builds a single-family hub — so the
|
||||
// only dispatch path covering the FULL family/subcommand surface is the
|
||||
// gsd-tools.cjs CLI itself. This is the shared helper pi/gsd.cjs and the
|
||||
// msd-tools.cjs CLI itself. This is the shared helper pi/msd.cjs and the
|
||||
// companion MCP server both dispatch through.
|
||||
|
||||
describe('dispatchGsdCommand', () => {
|
||||
describe('dispatchMsdCommand', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => { tmpDir = createTempDir(); });
|
||||
afterEach(() => { cleanup(tmpDir); mock.restoreAll(); });
|
||||
|
||||
test('resolveGsdToolsPath resolves to the real gsd-tools.cjs on disk', () => {
|
||||
const toolsPath = resolveGsdToolsPath();
|
||||
assert.ok(fs.existsSync(toolsPath), `expected gsd-tools.cjs to exist at ${toolsPath}`);
|
||||
assert.equal(path.basename(toolsPath), 'gsd-tools.cjs');
|
||||
test('resolveMsdToolsPath resolves to the real msd-tools.cjs on disk', () => {
|
||||
const toolsPath = resolveMsdToolsPath();
|
||||
assert.ok(fs.existsSync(toolsPath), `expected msd-tools.cjs to exist at ${toolsPath}`);
|
||||
assert.equal(path.basename(toolsPath), 'msd-tools.cjs');
|
||||
});
|
||||
|
||||
test('a valid read-only family/subcommand dispatches for real and returns ok:true + non-empty stdout', () => {
|
||||
@@ -1171,7 +1171,7 @@ describe('dispatchGsdCommand', () => {
|
||||
// is withheld (null), breaking this reachability proxy.
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
|
||||
const result = dispatchGsdCommand({ family: 'progress', subcommand: 'json', cwd: tmpDir });
|
||||
const result = dispatchMsdCommand({ family: 'progress', subcommand: 'json', cwd: tmpDir });
|
||||
assert.equal(result.ok, true, `expected ok:true, got: ${JSON.stringify(result)}`);
|
||||
assert.equal(typeof result.stdout, 'string');
|
||||
assert.ok(result.stdout.length > 0, 'stdout must be non-empty');
|
||||
@@ -1183,7 +1183,7 @@ describe('dispatchGsdCommand', () => {
|
||||
|
||||
test('an unknown family returns ok:false without throwing', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
const result = dispatchGsdCommand({ family: 'no-such-family-8675309', cwd: tmpDir });
|
||||
const result = dispatchMsdCommand({ family: 'no-such-family-8675309', cwd: tmpDir });
|
||||
assert.equal(result.ok, false);
|
||||
assert.notEqual(result.code, 0);
|
||||
assert.equal(typeof result.stderr, 'string');
|
||||
@@ -1194,12 +1194,12 @@ describe('dispatchGsdCommand', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('a missing/bogus gsd-tools.cjs path degrades to ok:false without throwing', () => {
|
||||
test('a missing/bogus msd-tools.cjs path degrades to ok:false without throwing', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
const result = dispatchGsdCommand({
|
||||
const result = dispatchMsdCommand({
|
||||
family: 'progress',
|
||||
cwd: tmpDir,
|
||||
gsdToolsPath: path.join(tmpDir, 'definitely-not-a-real-gsd-tools-8675309.cjs'),
|
||||
msdToolsPath: path.join(tmpDir, 'definitely-not-a-real-msd-tools-8675309.cjs'),
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.timedOut, false);
|
||||
@@ -1207,7 +1207,7 @@ describe('dispatchGsdCommand', () => {
|
||||
});
|
||||
|
||||
test('a missing/empty "family" is rejected locally without spawning a subprocess', () => {
|
||||
const result = dispatchGsdCommand({ cwd: tmpDir });
|
||||
const result = dispatchMsdCommand({ cwd: tmpDir });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.code, null);
|
||||
assert.match(result.stderr, /requires a non-empty string "family"/);
|
||||
@@ -1215,13 +1215,13 @@ describe('dispatchGsdCommand', () => {
|
||||
|
||||
test('a wall-clock timeout is reported via timedOut:true, ok:false — never throws', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
const result = dispatchGsdCommand({ family: 'progress', subcommand: 'json', cwd: tmpDir, timeout: DISPATCH_FORCED_TIMEOUT_MS });
|
||||
const result = dispatchMsdCommand({ family: 'progress', subcommand: 'json', cwd: tmpDir, timeout: DISPATCH_FORCED_TIMEOUT_MS });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.timedOut, true);
|
||||
});
|
||||
});
|
||||
|
||||
// #3050 item 4: this site (dispatchGsdCommand's `timedOut` derivation) now
|
||||
// #3050 item 4: this site (dispatchMsdCommand's `timedOut` derivation) now
|
||||
// routes through the shared isSpawnTimeout predicate, which drops the
|
||||
// `signal === 'SIGTERM'` requirement — a Windows-shaped timeout (no signal,
|
||||
// only error.code === 'ETIMEDOUT') must still be detected. The real-timeout
|
||||
@@ -1236,7 +1236,7 @@ describe('dispatchGsdCommand', () => {
|
||||
error: Object.assign(new Error('spawnSync ETIMEDOUT'), { code: 'ETIMEDOUT' }),
|
||||
}));
|
||||
|
||||
const result = dispatchGsdCommand({ family: 'progress', subcommand: 'json', cwd: tmpDir });
|
||||
const result = dispatchMsdCommand({ family: 'progress', subcommand: 'json', cwd: tmpDir });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.timedOut, true);
|
||||
});
|
||||
@@ -1484,7 +1484,7 @@ describe('platformEnsureDir', () => {
|
||||
* hooks resolve correctly regardless of the shell's current working directory.
|
||||
*
|
||||
* The original bug: local install hook commands used bare relative paths like
|
||||
* `node .claude/hooks/gsd-context-monitor.js`. Claude Code persists the bash
|
||||
* `node .claude/hooks/msd-context-monitor.js`. Claude Code persists the bash
|
||||
* tool's cwd between calls, so a single `cd subdir && …` early in a session
|
||||
* permanently broke every hook for the rest of that session.
|
||||
*
|
||||
@@ -1498,7 +1498,7 @@ const { describe, test, before } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('path');
|
||||
|
||||
const projection = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
const projection = require(path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
const { projectLocalHookPrefix, projectShellCommandText } = projection;
|
||||
|
||||
describe('bug #1906: local hook commands use $CLAUDE_PROJECT_DIR', () => {
|
||||
@@ -1516,13 +1516,13 @@ describe('bug #1906: local hook commands use $CLAUDE_PROJECT_DIR', () => {
|
||||
const prefix = projectLocalHookPrefix({ runtime: 'claude', dirName: '.claude' });
|
||||
const command = projectShellCommandText({
|
||||
runnerToken: '"/usr/local/bin/node"',
|
||||
argTokens: [`${prefix}/hooks/gsd-context-monitor.js`],
|
||||
argTokens: [`${prefix}/hooks/msd-context-monitor.js`],
|
||||
runtime: 'claude',
|
||||
platform: 'linux',
|
||||
});
|
||||
assert.equal(
|
||||
command,
|
||||
'"/usr/local/bin/node" "$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-context-monitor.js',
|
||||
'"/usr/local/bin/node" "$CLAUDE_PROJECT_DIR"/.claude/hooks/msd-context-monitor.js',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1543,7 +1543,7 @@ describe('bug #1906: local hook commands use $CLAUDE_PROJECT_DIR', () => {
|
||||
*
|
||||
* $CLAUDE_PROJECT_DIR is a Claude Code-specific env variable. Gemini CLI did
|
||||
* not set it. On Windows, Gemini's own variable-substitution + path-join logic
|
||||
* produced a doubled path like `D:\Projects\GSD\'D:\Projects\GSD'`, causing
|
||||
* produced a doubled path like `D:\Projects\MSD\'D:\Projects\MSD'`, causing
|
||||
* every local project hook to fail at SessionStart.
|
||||
*
|
||||
* Fix: localPrefix was made runtime-conditional. Gemini/Antigravity used bare
|
||||
@@ -1551,7 +1551,7 @@ describe('bug #1906: local hook commands use $CLAUDE_PROJECT_DIR', () => {
|
||||
* dir as cwd. Claude Code and others still use "$CLAUDE_PROJECT_DIR"/ (#1906).
|
||||
*
|
||||
* #1928: Google sunset Gemini CLI (2026-06-18) and the `gemini` runtime was
|
||||
* removed from GSD entirely.
|
||||
* removed from MSD entirely.
|
||||
*
|
||||
* #2096: `projectLocalHookPrefix` no longer special-cases `antigravity` by
|
||||
* name — it branches on the caller-supplied `hookPathStyle` (sourced from
|
||||
@@ -1565,7 +1565,7 @@ const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
|
||||
const projection = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
const projection = require(path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
const { projectLocalHookPrefix, projectShellCommandText } = projection;
|
||||
|
||||
describe('bug #2557: Antigravity local hooks use relative paths (not $CLAUDE_PROJECT_DIR); gemini runtime removed (#1928)', () => {
|
||||
@@ -1591,7 +1591,7 @@ describe('bug #2557: Antigravity local hooks use relative paths (not $CLAUDE_PRO
|
||||
assert.equal(prefix, '"$CLAUDE_PROJECT_DIR"/.gemini');
|
||||
const command = projectShellCommandText({
|
||||
runnerToken: '"/usr/local/bin/node"',
|
||||
argTokens: [`${prefix}/hooks/gsd-check-update.js`],
|
||||
argTokens: [`${prefix}/hooks/msd-check-update.js`],
|
||||
runtime: 'gemini',
|
||||
platform: 'linux',
|
||||
});
|
||||
@@ -1613,14 +1613,14 @@ describe('bug #2557: Antigravity local hooks use relative paths (not $CLAUDE_PRO
|
||||
__foldDescribe("folded:bug-3413-shell-command-projection (consolidation epic #1969 B3 #1972)", () => {
|
||||
'use strict';
|
||||
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
process.env.MSD_TEST_MODE = '1';
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
|
||||
const projection = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
const hooksSurface = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'runtime-hooks-surface.cjs'));
|
||||
const projection = require(path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
const hooksSurface = require(path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'runtime-hooks-surface.cjs'));
|
||||
|
||||
const {
|
||||
hookCommandNeedsPowerShellCallOperator,
|
||||
@@ -1693,7 +1693,7 @@ describe('bug #2236: hookShell parameter routes PowerShell call operator', () =>
|
||||
});
|
||||
|
||||
test('formatHookCommandForRuntime prepends & when hookShell=powershell', () => {
|
||||
const cmd = '"C:/Program Files/nodejs/node.exe" "C:/Users/me/.claude/hooks/gsd-context-monitor.js"';
|
||||
const cmd = '"C:/Program Files/nodejs/node.exe" "C:/Users/me/.claude/hooks/msd-context-monitor.js"';
|
||||
assert.equal(
|
||||
formatHookCommandForRuntime(cmd, { platform: 'win32', runtime: 'claude', hookShell: 'powershell' }),
|
||||
`& ${cmd}`,
|
||||
@@ -1701,7 +1701,7 @@ describe('bug #2236: hookShell parameter routes PowerShell call operator', () =>
|
||||
});
|
||||
|
||||
test('formatHookCommandForRuntime omits & when hookShell unset (backward compat)', () => {
|
||||
const cmd = '"C:/Program Files/nodejs/node.exe" "C:/Users/me/.claude/hooks/gsd-context-monitor.js"';
|
||||
const cmd = '"C:/Program Files/nodejs/node.exe" "C:/Users/me/.claude/hooks/msd-context-monitor.js"';
|
||||
assert.equal(
|
||||
formatHookCommandForRuntime(cmd, { platform: 'win32', runtime: 'claude' }),
|
||||
cmd,
|
||||
@@ -1709,7 +1709,7 @@ describe('bug #2236: hookShell parameter routes PowerShell call operator', () =>
|
||||
});
|
||||
|
||||
test('buildHookCommand emits & prefix when hookShell=powershell', () => {
|
||||
const cmd = buildHookCommand('C:/Users/me/.claude', 'gsd-check-update.js', {
|
||||
const cmd = buildHookCommand('C:/Users/me/.claude', 'msd-check-update.js', {
|
||||
platform: 'win32',
|
||||
runtime: 'claude',
|
||||
hookShell: 'powershell',
|
||||
@@ -1719,7 +1719,7 @@ describe('bug #2236: hookShell parameter routes PowerShell call operator', () =>
|
||||
});
|
||||
|
||||
test('buildHookCommand omits & prefix when hookShell=bash (regression lock)', () => {
|
||||
const cmd = buildHookCommand('C:/Users/me/.claude', 'gsd-check-update.js', {
|
||||
const cmd = buildHookCommand('C:/Users/me/.claude', 'msd-check-update.js', {
|
||||
platform: 'win32',
|
||||
runtime: 'claude',
|
||||
hookShell: 'bash',
|
||||
@@ -1731,7 +1731,7 @@ describe('bug #2236: hookShell parameter routes PowerShell call operator', () =>
|
||||
|
||||
describe('bug #3413: installer hook surfaces consume runtime-aware projection', () => {
|
||||
test('buildHookCommand emits shell-neutral Claude hook command on Windows', () => {
|
||||
const cmd = buildHookCommand('C:/Users/me/.claude', 'gsd-check-update.js', {
|
||||
const cmd = buildHookCommand('C:/Users/me/.claude', 'msd-check-update.js', {
|
||||
platform: 'win32',
|
||||
runtime: 'claude',
|
||||
});
|
||||
@@ -1742,7 +1742,7 @@ describe('bug #3413: installer hook surfaces consume runtime-aware projection',
|
||||
const settings = {
|
||||
hooks: {
|
||||
SessionStart: [{
|
||||
hooks: [{ type: 'command', command: '& "/usr/local/bin/node" "C:/Users/me/.claude/hooks/gsd-check-update.js"' }],
|
||||
hooks: [{ type: 'command', command: '& "/usr/local/bin/node" "C:/Users/me/.claude/hooks/msd-check-update.js"' }],
|
||||
}],
|
||||
},
|
||||
};
|
||||
@@ -1753,28 +1753,28 @@ describe('bug #3413: installer hook surfaces consume runtime-aware projection',
|
||||
assert.equal(changed, true);
|
||||
assert.equal(
|
||||
settings.hooks.SessionStart[0].hooks[0].command,
|
||||
'"/usr/local/bin/node" "C:/Users/me/.claude/hooks/gsd-check-update.js"',
|
||||
'"/usr/local/bin/node" "C:/Users/me/.claude/hooks/msd-check-update.js"',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bug #3439: shell projection module owns managed-hook policy and legacy rewrite projection', () => {
|
||||
test('isManagedHookBasename is surface-aware', () => {
|
||||
assert.equal(isManagedHookBasename('/x/hooks/gsd-check-update.js', { surface: 'settings-json' }), true);
|
||||
assert.equal(isManagedHookBasename('/x/hooks/gsd-statusline.js', { surface: 'settings-json' }), true);
|
||||
assert.equal(isManagedHookBasename('/x/hooks/gsd-statusline.js', { surface: 'codex-toml' }), false);
|
||||
assert.equal(isManagedHookBasename('/x/hooks/msd-check-update.js', { surface: 'settings-json' }), true);
|
||||
assert.equal(isManagedHookBasename('/x/hooks/msd-statusline.js', { surface: 'settings-json' }), true);
|
||||
assert.equal(isManagedHookBasename('/x/hooks/msd-statusline.js', { surface: 'codex-toml' }), false);
|
||||
assert.equal(isManagedHookBasename('/x/hooks/custom-hook.js', { surface: 'settings-json' }), false);
|
||||
});
|
||||
|
||||
test('projectLegacySettingsHookCommand preserves non-Windows script token shape', () => {
|
||||
const command = projectLegacySettingsHookCommand({
|
||||
runnerToken: '"/usr/local/bin/node"',
|
||||
scriptPath: '/x/hooks/gsd-statusline.js',
|
||||
scriptToken: "'/x/hooks/gsd-statusline.js'",
|
||||
scriptPath: '/x/hooks/msd-statusline.js',
|
||||
scriptToken: "'/x/hooks/msd-statusline.js'",
|
||||
platform: 'linux',
|
||||
runtime: 'claude',
|
||||
});
|
||||
assert.equal(command, `"/usr/local/bin/node" '/x/hooks/gsd-statusline.js'`);
|
||||
assert.equal(command, `"/usr/local/bin/node" '/x/hooks/msd-statusline.js'`);
|
||||
});
|
||||
|
||||
test('projectLegacySettingsHookCommand normalizes Windows managed paths and runtime wrapper policy', () => {
|
||||
@@ -1783,12 +1783,12 @@ describe('bug #3439: shell projection module owns managed-hook policy and legacy
|
||||
// Gemini-backend successor 'antigravity'). No `& ` prefix is ever added.
|
||||
const command = projectLegacySettingsHookCommand({
|
||||
runnerToken: '"C:/nvm4w/nodejs/node.exe"',
|
||||
scriptPath: 'C:\\Users\\me\\.gemini\\hooks\\gsd-prompt-guard.js',
|
||||
scriptToken: "'C:\\Users\\me\\.gemini\\hooks\\gsd-prompt-guard.js'",
|
||||
scriptPath: 'C:\\Users\\me\\.gemini\\hooks\\msd-prompt-guard.js',
|
||||
scriptToken: "'C:\\Users\\me\\.gemini\\hooks\\msd-prompt-guard.js'",
|
||||
platform: 'win32',
|
||||
runtime: 'antigravity',
|
||||
});
|
||||
assert.equal(command, '"C:/nvm4w/nodejs/node.exe" "C:/Users/me/.gemini/hooks/gsd-prompt-guard.js"');
|
||||
assert.equal(command, '"C:/nvm4w/nodejs/node.exe" "C:/Users/me/.gemini/hooks/msd-prompt-guard.js"');
|
||||
});
|
||||
|
||||
test('projectLocalHookPrefix centralizes runtime-specific project-dir interpolation policy', () => {
|
||||
@@ -1837,13 +1837,13 @@ describe('bug #3439: shell projection module owns managed-hook policy and legacy
|
||||
|
||||
test('isManagedHookCommand classifies managed settings hooks and leaves user commands untouched', () => {
|
||||
assert.equal(
|
||||
isManagedHookCommand('"/usr/local/bin/node" "/Users/me/.claude/hooks/gsd-statusline.js"', {
|
||||
isManagedHookCommand('"/usr/local/bin/node" "/Users/me/.claude/hooks/msd-statusline.js"', {
|
||||
surface: 'settings-json',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
isManagedHookCommand('"C:/Program Files/Git/bin/bash.exe" "C:/Users/me/.claude/hooks/gsd-session-state.sh"', {
|
||||
isManagedHookCommand('"C:/Program Files/Git/bin/bash.exe" "C:/Users/me/.claude/hooks/msd-session-state.sh"', {
|
||||
surface: 'settings-json',
|
||||
}),
|
||||
true,
|
||||
@@ -1857,7 +1857,7 @@ describe('bug #3439: shell projection module owns managed-hook policy and legacy
|
||||
});
|
||||
|
||||
test('isManagedHookCommand supports codex surfaces and optional legacy alias matching', () => {
|
||||
const command = '"/usr/local/bin/node" "/Users/me/.codex/hooks/gsd-check-update.js"';
|
||||
const command = '"/usr/local/bin/node" "/Users/me/.codex/hooks/msd-check-update.js"';
|
||||
assert.equal(
|
||||
isManagedHookCommand(command, {
|
||||
surface: 'codex-toml',
|
||||
@@ -1865,13 +1865,13 @@ describe('bug #3439: shell projection module owns managed-hook policy and legacy
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
isManagedHookCommand('"/usr/local/bin/node" "/Users/me/.codex/hooks/gsd-update-check.js"', {
|
||||
isManagedHookCommand('"/usr/local/bin/node" "/Users/me/.codex/hooks/msd-update-check.js"', {
|
||||
surface: 'codex-toml',
|
||||
}),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
isManagedHookCommand('"/usr/local/bin/node" "/Users/me/.codex/hooks/gsd-update-check.js"', {
|
||||
isManagedHookCommand('"/usr/local/bin/node" "/Users/me/.codex/hooks/msd-update-check.js"', {
|
||||
surface: 'codex-toml',
|
||||
includeLegacyAliases: true,
|
||||
}),
|
||||
@@ -1890,7 +1890,7 @@ describe('#1693 regression: Windows legacy-node rewrite must not double-quote a
|
||||
// a literal `"`, treated it as relative, and died with MODULE_NOT_FOUND —
|
||||
// breaking every node managed hook at once (self-locking deadlock).
|
||||
test('projectLegacySettingsHookCommand emits the anchored path verbatim, not re-quoted', () => {
|
||||
const anchored = '"$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-context-monitor.js';
|
||||
const anchored = '"$CLAUDE_PROJECT_DIR"/.claude/hooks/msd-context-monitor.js';
|
||||
const command = projectLegacySettingsHookCommand({
|
||||
runnerToken: winRunner,
|
||||
scriptPath: anchored,
|
||||
@@ -1900,7 +1900,7 @@ describe('#1693 regression: Windows legacy-node rewrite must not double-quote a
|
||||
});
|
||||
assert.equal(
|
||||
command,
|
||||
'"C:/Program Files/nodejs/node.exe" "$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-context-monitor.js',
|
||||
'"C:/Program Files/nodejs/node.exe" "$CLAUDE_PROJECT_DIR"/.claude/hooks/msd-context-monitor.js',
|
||||
);
|
||||
assert.ok(!command.includes('\\"'), 'must not contain escaped double-quotes');
|
||||
});
|
||||
@@ -1908,7 +1908,7 @@ describe('#1693 regression: Windows legacy-node rewrite must not double-quote a
|
||||
// WHY: the fix must be surgical — a BARE absolute Windows path (no anchored
|
||||
// prefix) can contain spaces ("Program Files") and still REQUIRES quoting.
|
||||
test('projectLegacySettingsHookCommand still quotes a bare absolute Windows path', () => {
|
||||
const abs = 'C:/Program Files App/.claude/hooks/gsd-context-monitor.js';
|
||||
const abs = 'C:/Program Files App/.claude/hooks/msd-context-monitor.js';
|
||||
const command = projectLegacySettingsHookCommand({
|
||||
runnerToken: winRunner,
|
||||
scriptPath: abs,
|
||||
@@ -1918,7 +1918,7 @@ describe('#1693 regression: Windows legacy-node rewrite must not double-quote a
|
||||
});
|
||||
assert.equal(
|
||||
command,
|
||||
'"C:/Program Files/nodejs/node.exe" "C:/Program Files App/.claude/hooks/gsd-context-monitor.js"',
|
||||
'"C:/Program Files/nodejs/node.exe" "C:/Program Files App/.claude/hooks/msd-context-monitor.js"',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1931,12 +1931,12 @@ describe('#1693 regression: Windows legacy-node rewrite must not double-quote a
|
||||
test('projectLegacySettingsHookCommand preserves the original scriptToken for anchored paths on POSIX', () => {
|
||||
const command = projectLegacySettingsHookCommand({
|
||||
runnerToken: '"/usr/local/bin/node"',
|
||||
scriptPath: '"$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-statusline.js',
|
||||
scriptToken: "'/x/hooks/gsd-statusline.js'",
|
||||
scriptPath: '"$CLAUDE_PROJECT_DIR"/.claude/hooks/msd-statusline.js',
|
||||
scriptToken: "'/x/hooks/msd-statusline.js'",
|
||||
platform: 'linux',
|
||||
runtime: 'claude',
|
||||
});
|
||||
assert.equal(command, `"/usr/local/bin/node" '/x/hooks/gsd-statusline.js'`);
|
||||
assert.equal(command, `"/usr/local/bin/node" '/x/hooks/msd-statusline.js'`);
|
||||
});
|
||||
|
||||
// WHY: end-to-end through the installer rewrite — the actual #2979 path that
|
||||
@@ -1949,7 +1949,7 @@ describe('#1693 regression: Windows legacy-node rewrite must not double-quote a
|
||||
PostToolUse: [
|
||||
{
|
||||
hooks: [
|
||||
{ command: 'node "$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-context-monitor.js' },
|
||||
{ command: 'node "$CLAUDE_PROJECT_DIR"/.claude/hooks/msd-context-monitor.js' },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -1963,7 +1963,7 @@ describe('#1693 regression: Windows legacy-node rewrite must not double-quote a
|
||||
const rewritten = settings.hooks.PostToolUse[0].hooks[0].command;
|
||||
assert.equal(
|
||||
rewritten,
|
||||
'"C:/Program Files/nodejs/node.exe" "$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-context-monitor.js',
|
||||
'"C:/Program Files/nodejs/node.exe" "$CLAUDE_PROJECT_DIR"/.claude/hooks/msd-context-monitor.js',
|
||||
);
|
||||
assert.ok(!rewritten.includes('\\"'), 'rewritten command must not contain escaped double-quotes');
|
||||
});
|
||||
@@ -1980,7 +1980,7 @@ describe('#1693 regression: Windows legacy-node rewrite must not double-quote a
|
||||
__foldDescribe("folded:bug-3441-path-action-projection (consolidation epic #1969 B3 #1972)", () => {
|
||||
'use strict';
|
||||
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
process.env.MSD_TEST_MODE = '1';
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
@@ -1991,7 +1991,7 @@ const os = require('node:os');
|
||||
const projection = require(path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'gsd-core',
|
||||
'msd-core',
|
||||
'bin',
|
||||
'lib',
|
||||
'shell-command-projection.cjs',
|
||||
@@ -2000,7 +2000,7 @@ const install = require(path.join(__dirname, '..', 'bin', 'install.js'));
|
||||
const { withIsolatedProcessState, cleanup } = require('./helpers.cjs');
|
||||
|
||||
function createTempHome() {
|
||||
return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-home-3441-'));
|
||||
return fs.mkdtempSync(path.join(os.tmpdir(), 'msd-home-3441-'));
|
||||
}
|
||||
|
||||
|
||||
@@ -2009,7 +2009,7 @@ describe('bug #3441: PATH guidance is projected from typed shell action IR', ()
|
||||
assert.equal(typeof projection.projectPathActionProjection, 'function');
|
||||
});
|
||||
|
||||
// (formatSdkPathDiagnostic removed with the gsd-sdk shim, #191 — the PATH
|
||||
// (formatSdkPathDiagnostic removed with the msd-sdk shim, #191 — the PATH
|
||||
// action projection it wrapped is still covered by the tests below.)
|
||||
|
||||
test('persistent PATH export guidance is projected via the same seam', () => {
|
||||
@@ -2146,7 +2146,7 @@ const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
|
||||
const projection = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
const projection = require(path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
const { buildLocalShellHookCommand, shellHookOmitsBashRunner, projectLocalHookPrefix } = projection;
|
||||
|
||||
describe('bug #580: local .sh hooks on Claude/Windows must NOT wrap with bash.exe', () => {
|
||||
@@ -2154,12 +2154,12 @@ describe('bug #580: local .sh hooks on Claude/Windows must NOT wrap with bash.ex
|
||||
const localPrefix = projectLocalHookPrefix({ runtime: 'claude', dirName: '.claude' });
|
||||
const result = buildLocalShellHookCommand({
|
||||
localPrefix,
|
||||
hookFile: 'gsd-session-state.sh',
|
||||
hookFile: 'msd-session-state.sh',
|
||||
bashRunner: '"C:/Program Files/Git/bin/bash.exe"',
|
||||
runtime: 'claude',
|
||||
platform: 'win32',
|
||||
});
|
||||
assert.equal(result, '"$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-session-state.sh');
|
||||
assert.equal(result, '"$CLAUDE_PROJECT_DIR"/.claude/hooks/msd-session-state.sh');
|
||||
assert.ok(!result.includes('bash.exe'), `result must not contain bash.exe, got: ${result}`);
|
||||
});
|
||||
|
||||
@@ -2167,31 +2167,31 @@ describe('bug #580: local .sh hooks on Claude/Windows must NOT wrap with bash.ex
|
||||
const localPrefix = projectLocalHookPrefix({ runtime: 'claude', dirName: '.claude' });
|
||||
const result = buildLocalShellHookCommand({
|
||||
localPrefix,
|
||||
hookFile: 'gsd-session-state.sh',
|
||||
hookFile: 'msd-session-state.sh',
|
||||
bashRunner: null,
|
||||
runtime: 'claude',
|
||||
platform: 'win32',
|
||||
});
|
||||
assert.equal(result, '"$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-session-state.sh');
|
||||
assert.equal(result, '"$CLAUDE_PROJECT_DIR"/.claude/hooks/msd-session-state.sh');
|
||||
});
|
||||
|
||||
test('local .sh hook on POSIX keeps the bash runner', () => {
|
||||
const localPrefix = projectLocalHookPrefix({ runtime: 'claude', dirName: '.claude' });
|
||||
const result = buildLocalShellHookCommand({
|
||||
localPrefix,
|
||||
hookFile: 'gsd-session-state.sh',
|
||||
hookFile: 'msd-session-state.sh',
|
||||
bashRunner: 'bash',
|
||||
runtime: 'claude',
|
||||
platform: 'linux',
|
||||
});
|
||||
assert.equal(result, 'bash "$CLAUDE_PROJECT_DIR"/.claude/hooks/gsd-session-state.sh');
|
||||
assert.equal(result, 'bash "$CLAUDE_PROJECT_DIR"/.claude/hooks/msd-session-state.sh');
|
||||
});
|
||||
|
||||
test('local .sh hook on Windows non-Claude runtime keeps the bash runner', () => {
|
||||
const localPrefix = projectLocalHookPrefix({ runtime: 'codex', dirName: '.claude' });
|
||||
const result = buildLocalShellHookCommand({
|
||||
localPrefix,
|
||||
hookFile: 'gsd-session-state.sh',
|
||||
hookFile: 'msd-session-state.sh',
|
||||
bashRunner: '"C:/Program Files/Git/bin/bash.exe"',
|
||||
runtime: 'codex',
|
||||
platform: 'win32',
|
||||
@@ -2203,10 +2203,10 @@ describe('bug #580: local .sh hooks on Claude/Windows must NOT wrap with bash.ex
|
||||
test('all four managed local .sh hooks drop the wrapper on Claude/Windows', () => {
|
||||
const localPrefix = projectLocalHookPrefix({ runtime: 'claude', dirName: '.claude' });
|
||||
const hooks = [
|
||||
'gsd-session-state.sh',
|
||||
'gsd-validate-commit.sh',
|
||||
'gsd-graphify-update.sh',
|
||||
'gsd-phase-boundary.sh',
|
||||
'msd-session-state.sh',
|
||||
'msd-validate-commit.sh',
|
||||
'msd-graphify-update.sh',
|
||||
'msd-phase-boundary.sh',
|
||||
];
|
||||
for (const f of hooks) {
|
||||
const result = buildLocalShellHookCommand({
|
||||
@@ -2256,7 +2256,7 @@ describe('bug #580: local .sh hooks on Claude/Windows must NOT wrap with bash.ex
|
||||
* Filesystem fault-injection coverage for the canonical atomic-write
|
||||
* seam (#3595).
|
||||
*
|
||||
* `platformWriteSync` in `gsd-core/bin/lib/shell-command-projection.cjs`
|
||||
* `platformWriteSync` in `msd-core/bin/lib/shell-command-projection.cjs`
|
||||
* is the shared seam every config/state/generated-artifact writer in
|
||||
* the CJS layer routes through. Its contract:
|
||||
*
|
||||
@@ -2294,7 +2294,7 @@ const path = require('node:path');
|
||||
const {
|
||||
platformWriteSync,
|
||||
platformEnsureDir,
|
||||
} = require('../gsd-core/bin/lib/shell-command-projection.cjs');
|
||||
} = require('../msd-core/bin/lib/shell-command-projection.cjs');
|
||||
|
||||
/**
|
||||
* Create a fresh real-fs scratch dir per test so no two faults share
|
||||
@@ -2865,7 +2865,7 @@ describe('path export projection — escaping (#3118)', () => {
|
||||
});
|
||||
|
||||
test('leaves an ordinary value byte-identical', () => {
|
||||
const value = 'C:/Users/dev/gsd.js';
|
||||
const value = 'C:/Users/dev/msd.js';
|
||||
assert.equal(escapeTomlDoubleQuotedString(value), value);
|
||||
});
|
||||
|
||||
@@ -3109,7 +3109,7 @@ describe('path export projection — escaping (#3118)', () => {
|
||||
});
|
||||
|
||||
test('returns silently on a successful rename', (t) => {
|
||||
const dir = createTempDir('gsd-3118-rename-');
|
||||
const dir = createTempDir('msd-3118-rename-');
|
||||
t.after(() => cleanup(dir));
|
||||
const from = path.join(dir, 'source.txt');
|
||||
const to = path.join(dir, 'dest.txt');
|
||||
|
||||
Reference in New Issue
Block a user