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 @@
/**
* Worktree Safety Policy Module — typed IR tests
*
* Seam: gsd-core/bin/lib/worktree-safety.cjs
* Seam: msd-core/bin/lib/worktree-safety.cjs
* Interface: resolveWorktreeContext, parseWorktreePorcelain, planWorktreePrune,
* executeWorktreePrunePlan, listLinkedWorktreePaths, inspectWorktreeHealth,
* snapshotWorktreeInventory, planWorktreeWaveCleanup,
@@ -24,20 +24,20 @@ const fc = require('fast-check');
const { createTempDir, cleanup } = require('./helpers.cjs');
const { createFixture } = require('./fixtures/index.cjs');
const { makeFaultyGit } = require('./helpers/faulty-deps.cjs');
const { escapeRegex } = require('../gsd-core/bin/lib/pattern.cjs');
const { escapeRegex } = require('../msd-core/bin/lib/pattern.cjs');
const { HOOK_FANOUT_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
// 30000ms: this file's single named bound for every migrated subprocess call
// below (git plumbing on small mkdtemp fixtures, gsd-tools.cjs/hook CLI runs,
// below (git plumbing on small mkdtemp fixtures, msd-tools.cjs/hook CLI runs,
// and bash guard snippets) — well over any observed duration for any of
// those classes of call on this file's fixtures.
const SUBPROCESS_TIMEOUT_MS = 30_000;
const WORKTREE_SAFETY_PATH = path.join(
__dirname, '..', 'gsd-core', 'bin', 'lib', 'worktree-safety.cjs'
__dirname, '..', 'msd-core', 'bin', 'lib', 'worktree-safety.cjs'
);
const CORE_PATH = path.join(
__dirname, '..', 'gsd-core', 'bin', 'lib', 'worktree-safety.cjs'
__dirname, '..', 'msd-core', 'bin', 'lib', 'worktree-safety.cjs'
);
const {
@@ -248,7 +248,7 @@ describe('resolveWorktreeContext', () => {
// usable project root right here", not "is this a linked worktree" — a linked
// worktree created to isolate an executor is a full checkout, so it normally
// has its OWN checked-out .planning/ too. resolveWorktreeLinkage is the
// shortcut-free primitive the isolation guard (hooks/gsd-cursor-subagent-start.js)
// shortcut-free primitive the isolation guard (hooks/msd-cursor-subagent-start.js)
// needs instead: it must report "linked_worktree_root" for such a worktree even
// though .planning exists locally — exactly the case that would defeat the guard
// if resolveWorktreeContext were reused as-is.
@@ -310,10 +310,10 @@ describe('resolveWorktreeLinkage', () => {
// node:child_process.spawnSync primitive rather than a synthetic result.)
describe('shared isSpawnTimeout predicate — parity for worktree-base-ref evaluateWorktreeBaseDegrade (#3050)', () => {
const { isSpawnTimeout } = require(path.join(
__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'
__dirname, '..', 'msd-core', 'bin', 'lib', 'shell-command-projection.cjs'
));
const { evaluateWorktreeBaseDegrade } = require(path.join(
__dirname, '..', 'gsd-core', 'bin', 'lib', 'worktree-base-ref.cjs'
__dirname, '..', 'msd-core', 'bin', 'lib', 'worktree-base-ref.cjs'
));
const cases = [
@@ -1336,7 +1336,7 @@ describe('cmdWorktreeRecordAgent', () => {
});
// ─── record-agent: real CLI dispatch + workflow wiring (#1298 integration) ────
// The unit tests above inject IO; these pin the live `gsd-tools.cjs query
// The unit tests above inject IO; these pin the live `msd-tools.cjs query
// worktree.record-agent` dispatch and the execute-phase.md call site, so a
// future typo in the dotted command or the workflow wiring fails loudly.
@@ -1344,14 +1344,14 @@ describe('worktree record-agent — real CLI dispatch (#1298)', () => {
const fs = require('node:fs');
const { runNode } = require('./helpers/process-seam.cjs');
const { throwIfFailed } = require('./helpers/git-fixture.cjs');
const GSD_TOOLS = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs');
const MSD_TOOLS = path.join(__dirname, '..', 'msd-core', 'bin', 'msd-tools.cjs');
// runNode never throws; this describe's tests are written against the
// legacy execFileSync throw (an implicit dependency at the success-path
// call, and an explicit `err.status`/`err.stderr` read in the failure-path
// catch below) — this helper re-throws in that shape via the shared
// tests/helpers/git-fixture.cjs mechanism, for a non-git target.
function runGsdToolsOrThrow(args, opts) {
function runMsdToolsOrThrow(args, opts) {
const r = runNode(args, opts);
throwIfFailed(r, `node ${args.join(' ')}`);
return r.stdout;
@@ -1362,8 +1362,8 @@ describe('worktree record-agent — real CLI dispatch (#1298)', () => {
try {
const manifest = path.join(dir, 'wave-manifest.json');
fs.writeFileSync(manifest, `${JSON.stringify({ orchestrator_root: dir, worktrees: [] })}\n`);
const out = runGsdToolsOrThrow([
GSD_TOOLS, 'query', 'worktree.record-agent',
const out = runMsdToolsOrThrow([
MSD_TOOLS, 'query', 'worktree.record-agent',
'--manifest', manifest,
'--agent-id', 'a1',
'--path', path.join(dir, 'wt-a1'),
@@ -1390,8 +1390,8 @@ describe('worktree record-agent — real CLI dispatch (#1298)', () => {
fs.writeFileSync(manifest, `${JSON.stringify({ worktrees: [] })}\n`);
let threw = false;
try {
runGsdToolsOrThrow([
GSD_TOOLS, 'query', 'worktree.record-agent',
runMsdToolsOrThrow([
MSD_TOOLS, 'query', 'worktree.record-agent',
'--manifest', manifest,
'--path', path.join(dir, 'wt'), '--branch', 'worktree-agent-x', '--base', 'abc123',
], { timeoutMs: SUBPROCESS_TIMEOUT_MS });
@@ -1409,7 +1409,7 @@ describe('worktree record-agent — real CLI dispatch (#1298)', () => {
test('the execute-phase.md per-agent append calls the record-agent verb', () => {
const wf = fs.readFileSync(
path.join(__dirname, '..', 'gsd-core', 'workflows', 'execute-phase.md'), 'utf8',
path.join(__dirname, '..', 'msd-core', 'workflows', 'execute-phase.md'), 'utf8',
);
assert.match(wf, /worktree\.record-agent/, 'execute-phase.md must wire the record-agent verb');
});
@@ -1742,7 +1742,7 @@ describe('cmdWorktreeCreate', () => {
});
test('an arbitrary absolute path is rejected (the hole a ".."-segment check cannot see)', () => {
const { result } = run(rootedArgs('/etc/gsd-evil', '/repo/main'));
const { result } = run(rootedArgs('/etc/msd-evil', '/repo/main'));
assert.equal(result.ok, false);
assert.equal(result.reason, 'path_outside_root');
});
@@ -2035,7 +2035,7 @@ describe('executeWorktreeWaveCleanupPlan', () => {
// ── #4415 regression ───────────────────────────────────────────────────────
// Claude Code removes a subagent's worktree the moment the subagent finishes
// with a clean tree. A gsd-executor that committed everything (SUMMARY.md
// with a clean tree. A msd-executor that committed everything (SUMMARY.md
// included, under `commit_docs: true`) is exactly that case, so by the time
// the orchestrator reaches wave cleanup the directory is routinely gone while
// the branch it left behind is intact and mergeable.
@@ -2186,7 +2186,7 @@ describe('executeWorktreeWaveCleanupPlan', () => {
});
test('#3677 swap control: an absent path registered to a DIFFERENT branch blocks', () => {
// Maintainer review on #4612, Blocker 3. `tests/gsd-quick-batch-merge-integration.test.cjs`
// Maintainer review on #4612, Blocker 3. `tests/msd-quick-batch-merge-integration.test.cjs`
// covers the branch_mismatch swap control on the PRESENT path only, so the
// absent path could bypass it unnoticed — which is exactly how the earlier
// ref-based identity fallback slipped through a green suite.
@@ -3639,7 +3639,7 @@ describe('executeWorktreeWaveCleanupPlan', () => {
const KILL_BUDGET_MS = 1500;
function buildRepoWithSlowHook({ hook = true, hookName = 'pre-merge-commit', autoStash = false, prestage = false } = {}) {
const root = createTempDir('gsd-4721-');
const root = createTempDir('msd-4721-');
const repo = path.join(root, 'rr');
const wt = path.join(root, 'rr-wt');
const git = (args, cwd = repo) => gitFixture(args, { cwd, timeoutMs: SUBPROCESS_TIMEOUT_MS });
@@ -3900,7 +3900,7 @@ describe('executeWorktreeWaveCleanupPlan', () => {
// nothing, and the entry blocked worktree_dirty instead of rescuing.
// The fs deps are deliberately NOT injected: the real default walker and the real
// copy are the subjects under test.
const repoRoot = createTempDir('gsd-4758-repo-');
const repoRoot = createTempDir('msd-4758-repo-');
t.after(() => cleanup(repoRoot));
const worktreePath = '.claude/worktrees/agent-rel-4758';
const absWorktree = path.join(repoRoot, worktreePath);
@@ -5021,7 +5021,7 @@ describe('worktree-safety: resolveWorktreeRoot behaviour', () => {
// recreate the .planning/ directory it's writing into) so resolveWorktreeRoot
// actually reaches resolveWorktreeLinkage's real git rev-parse comparison.
test('resolveWorktreeRoot(git repo with no local .planning) reaches the git-based main_worktree path, returns {root: dir, reason: main_worktree}', (t) => {
const dir = createFixture({ prefix: 'gsd-wt-root-', planning: false, git: true, projectDoc: false });
const dir = createFixture({ prefix: 'msd-wt-root-', planning: false, git: true, projectDoc: false });
t.after(() => cleanup(dir));
const result = worktreeSafety.resolveWorktreeRoot(dir);
assert.deepStrictEqual(result, { root: dir, reason: 'main_worktree' });
@@ -5041,7 +5041,7 @@ describe('worktree-safety: pruneOrphanedWorktrees behaviour', () => {
const worktreeSafety = require(WORKTREE_SAFETY_PATH);
test('pruneOrphanedWorktrees(temp dir) returns [] and does not throw', (t) => {
const dir = createTempDir('gsd-prune-');
const dir = createTempDir('msd-prune-');
t.after(() => cleanup(dir));
const result = worktreeSafety.pruneOrphanedWorktrees(dir);
assert.deepStrictEqual(result, []);
@@ -5059,7 +5059,7 @@ describe('worktree-safety: pruneOrphanedWorktrees behaviour', () => {
// Real-filesystem tests for the two failure modes pinned in #3707:
// 1. executeWorktreeWaveCleanupPlan must unlock-then-retry when a worktree is locked.
// 2. reapOrphanWorktrees must reap dead-pid+merged entries and skip live / unmerged / fresh-mtime entries.
// 3. quick.md and execute-phase.md must wire gsd-sdk query worktree.reap-orphans at startup.
// 3. quick.md and execute-phase.md must wire msd-sdk query worktree.reap-orphans at startup.
'use strict';
@@ -5075,7 +5075,7 @@ const { gitOrThrow } = require('./helpers/git-fixture.cjs');
const {
executeWorktreeWaveCleanupPlan,
reapOrphanWorktrees,
} = require('../gsd-core/bin/lib/worktree-safety.cjs');
} = require('../msd-core/bin/lib/worktree-safety.cjs');
// ─── Fixed timestamps for deterministic stale-lock boundary ──────────────────
//
@@ -5220,7 +5220,7 @@ describe('bug-3707: executeWorktreeWaveCleanupPlan unlocks and retries on locked
let tmpBase;
beforeEach(() => {
tmpBase = fs.mkdtempSync(path.join(resolvedTmpDir(), 'gsd-3707-cleanup-'));
tmpBase = fs.mkdtempSync(path.join(resolvedTmpDir(), 'msd-3707-cleanup-'));
});
afterEach(() => {
@@ -5387,7 +5387,7 @@ describe('bug-3707: reapOrphanWorktrees', () => {
let tmpBase;
beforeEach(() => {
tmpBase = fs.mkdtempSync(path.join(resolvedTmpDir(), 'gsd-3707-reap-'));
tmpBase = fs.mkdtempSync(path.join(resolvedTmpDir(), 'msd-3707-reap-'));
});
afterEach(() => {
@@ -5557,14 +5557,14 @@ describe('bug-3707: reapOrphanWorktrees', () => {
// ─── Suite 3: Structural — startup sweep wiring ───────────────────────────────
describe('bug-3707: startup orphan sweep is wired into workflow entry points', () => {
const QUICK_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'quick.md');
const EXECUTE_PHASE_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'execute-phase.md');
const QUICK_PATH = path.join(__dirname, '..', 'msd-core', 'workflows', 'quick.md');
const EXECUTE_PHASE_PATH = path.join(__dirname, '..', 'msd-core', 'workflows', 'execute-phase.md');
test('quick.md calls worktree.reap-orphans at startup when USE_WORKTREES is not false', () => {
const content = fs.readFileSync(QUICK_PATH, 'utf8');
assert.ok(
content.includes('worktree.reap-orphans'),
'quick.md must call gsd-sdk query worktree.reap-orphans at startup'
'quick.md must call msd-sdk query worktree.reap-orphans at startup'
);
// Must be guarded by USE_WORKTREES check
assert.ok(
@@ -5581,13 +5581,13 @@ describe('bug-3707: startup orphan sweep is wired into workflow entry points', (
// now `ISOLATION != none`, which USE_WORKTREES=false forces — so the #3707
// protection is unchanged, just keyed one level up.
const ISOLATION_FRAGMENT_PATH = path.join(
__dirname, '..', 'gsd-core', 'workflows', 'execute-phase', 'steps', 'executor-isolation-dispatch.md',
__dirname, '..', 'msd-core', 'workflows', 'execute-phase', 'steps', 'executor-isolation-dispatch.md',
);
const content = fs.readFileSync(EXECUTE_PHASE_PATH, 'utf8')
+ fs.readFileSync(ISOLATION_FRAGMENT_PATH, 'utf8');
assert.ok(
content.includes('worktree.reap-orphans'),
'execute-phase must call gsd-sdk query worktree.reap-orphans at startup'
'execute-phase must call msd-sdk query worktree.reap-orphans at startup'
);
assert.ok(
/USE_WORKTREES.*!=.*false[\s\S]{0,200}worktree\.reap-orphans/m.test(content) ||
@@ -5599,12 +5599,12 @@ describe('bug-3707: startup orphan sweep is wired into workflow entry points', (
});
test('worktree-safety module exports reapOrphanWorktrees', () => {
const mod = require('../gsd-core/bin/lib/worktree-safety.cjs');
const mod = require('../msd-core/bin/lib/worktree-safety.cjs');
assert.strictEqual(typeof mod.reapOrphanWorktrees, 'function');
});
test('worktree-safety module exports cmdWorktreeReapOrphans', () => {
const mod = require('../gsd-core/bin/lib/worktree-safety.cjs');
const mod = require('../msd-core/bin/lib/worktree-safety.cjs');
assert.strictEqual(typeof mod.cmdWorktreeReapOrphans, 'function');
});
});
@@ -5628,7 +5628,7 @@ describe('bug-3707: reapOrphanWorktrees — nowMs clock-injection BOUNDARY tests
let tmpBase;
beforeEach(() => {
tmpBase = fs.mkdtempSync(path.join(resolvedTmpDir(), 'gsd-3707-nowms-'));
tmpBase = fs.mkdtempSync(path.join(resolvedTmpDir(), 'msd-3707-nowms-'));
});
afterEach(() => {
@@ -5710,7 +5710,7 @@ describe('bug-3707: reapOrphanWorktrees — adversarial edge cases', () => {
let tmpBase;
beforeEach(() => {
tmpBase = fs.mkdtempSync(path.join(resolvedTmpDir(), 'gsd-3707-adv-'));
tmpBase = fs.mkdtempSync(path.join(resolvedTmpDir(), 'msd-3707-adv-'));
});
afterEach(() => {
@@ -5851,7 +5851,7 @@ describe('bug-3707: reapOrphanWorktrees — adversarial edge cases', () => {
__foldDescribe("folded:bug-3129-validate-commit-git-bypass (consolidation epic #1969 B5 #1974)", () => {
'use strict';
// allow-test-rule: structural-regression-guard (see #3129)
// Reads the gsd-validate-commit.sh hook source to verify it delegates to
// Reads the msd-validate-commit.sh hook source to verify it delegates to
// git-cmd.js isGitSubcommand() rather than the old regex — a specific code
// pattern that must (and must not) exist; behavioral tests of tokenize()/
// isGitSubcommand() cannot observe which detection strategy the hook itself
@@ -5859,7 +5859,7 @@ describe('bug-3707: reapOrphanWorktrees — adversarial edge cases', () => {
// Regression tests for bug #3129.
//
// gsd-validate-commit.sh used `[[ "$CMD" =~ ^git[[:space:]]+commit ]]` to
// msd-validate-commit.sh used `[[ "$CMD" =~ ^git[[:space:]]+commit ]]` to
// detect git commit invocations. This regex silently bypasses Conventional
// Commits enforcement for three real git commit forms:
// 1. git -C /some/path commit -m "..." (working-directory prefix)
@@ -5949,24 +5949,24 @@ describe('git-cmd.js isGitSubcommand: should NOT match commit', () => {
}
});
// ── gsd-validate-commit.sh source check ──────────────────────────────────────
// ── msd-validate-commit.sh source check ──────────────────────────────────────
describe('gsd-validate-commit.sh delegates to git-cmd.js', () => {
describe('msd-validate-commit.sh delegates to git-cmd.js', () => {
const hookSrc = fs.readFileSync(
path.join(ROOT, 'hooks', 'gsd-validate-commit.sh'), 'utf8',
path.join(ROOT, 'hooks', 'msd-validate-commit.sh'), 'utf8',
);
test('hook no longer uses the stale ^git\\s+commit bash regex', () => {
assert.ok(
!hookSrc.includes('^git[[:space:]]+commit'),
'gsd-validate-commit.sh still uses the bypassed regex — fix not applied',
'msd-validate-commit.sh still uses the bypassed regex — fix not applied',
);
});
test('hook delegates to git-cmd.js isGitSubcommand', () => {
assert.ok(
hookSrc.includes('git-cmd.js') && hookSrc.includes('isGitSubcommand'),
'gsd-validate-commit.sh does not reference git-cmd.js or isGitSubcommand',
'msd-validate-commit.sh does not reference git-cmd.js or isGitSubcommand',
);
});
@@ -5999,7 +5999,7 @@ describe('git-cmd.js resolveCommitSubject', () => {
// dedicated row below (review of #3816, round 4).
// no space before << is legal bash too (review of #3816, round 3)
assert.strictEqual(resolveCommitSubject("$(cat<<'EOF'\nfix: nospace\nEOF\n)"), 'fix: nospace');
// NOTE: resolvable HERE, but unreachable through gsd-validate-commit.sh —
// NOTE: resolvable HERE, but unreachable through msd-validate-commit.sh —
// its DOUBLE-quoted `-m` capture stops at this spelling's own delimiter
// quote. Round 4 disproved the stronger form of this claim: the
// SINGLE-quoted capture delivers the spelling intact, so "unreachable"
@@ -6389,7 +6389,7 @@ const fs = require('node:fs');
const path = require('node:path');
const repoRoot = path.resolve(__dirname, '..');
const WORKTREE_BRANCH_CHECK_FRAGMENT = path.join(repoRoot, 'gsd-core', 'references', 'worktree-branch-check.md');
const WORKTREE_BRANCH_CHECK_FRAGMENT = path.join(repoRoot, 'msd-core', 'references', 'worktree-branch-check.md');
function read(relPath) {
return fs.readFileSync(path.join(repoRoot, relPath), 'utf8');
@@ -6397,7 +6397,7 @@ function read(relPath) {
describe('bug #3384: adjacent worktree data-loss guards', () => {
test('worktree cleanup CLI preserves caller cwd instead of resolving project root', () => {
const source = read('gsd-core/bin/gsd-tools.cjs');
const source = read('msd-core/bin/msd-tools.cjs');
const skipSet = source.slice(
source.indexOf('const SKIP_ROOT_RESOLUTION = new Set(['),
source.indexOf('if (!SKIP_ROOT_RESOLUTION.has(command))'),
@@ -6412,7 +6412,7 @@ describe('bug #3384: adjacent worktree data-loss guards', () => {
// fragment itself has the correct ordering: symbolic-ref/HEAD assertion and
// ^worktree-agent- allow-list appear before any work, and (c) the fragment
// is verify-only — no destructive self-recovery.
const diagnoseSource = read('gsd-core/workflows/diagnose-issues.md');
const diagnoseSource = read('msd-core/workflows/diagnose-issues.md');
assert.ok(
diagnoseSource.includes('worktree-branch-check.md'),
'diagnose-issues.md must reference the canonical worktree-branch-check.md fragment'
@@ -6430,7 +6430,7 @@ describe('bug #3384: adjacent worktree data-loss guards', () => {
});
test('remove-workspace fails closed when git worktree remove fails', () => {
const source = read('gsd-core/workflows/remove-workspace.md');
const source = read('msd-core/workflows/remove-workspace.md');
const init = source.indexOf('REMOVE_FAILED=false');
const loop = source.indexOf('For each repo in the workspace');
const remove = source.indexOf('git worktree remove "$WORKSPACE_PATH/$REPO_NAME"');
@@ -6448,11 +6448,11 @@ describe('bug #3384: adjacent worktree data-loss guards', () => {
test('validate health warns when worktree inventory cannot be listed', () => {
// Phase 11 (#3309, ADR-3180): this branch moved out of verify.cts into
// the W020 rule (src/health-diagnostic-rules/worktree-health.cts),
// compiled to gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs.
// compiled to msd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs.
// Accept both hand-written dot access and the tsc-compiled bracket form:
// hand-written: reason === 'git_list_failed'
// tsc-compiled: reason === 'git_list_failed' (unchanged shape either way)
const source = read('gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs');
const source = read('msd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs');
const failureBranch = source.search(/reason === 'git_list_failed'/);
const warning = source.indexOf("code: 'W020'", failureBranch);
@@ -6470,11 +6470,11 @@ describe('bug #3384: adjacent worktree data-loss guards', () => {
const { describe: __foldDescribe } = require('node:test');
__foldDescribe("folded:bug-260-worktree-path-guard (consolidation epic #1969 B6 #1975)", () => {
/**
* Regression tests for bug #260 — gsd-worktree-path-guard.js
* Regression tests for bug #260 — msd-worktree-path-guard.js
*
* Executor agents spawned with isolation="worktree" sometimes issue Edit/Write
* calls with absolute paths rooted at the MAIN repository instead of the
* worktree. The prose guard in gsd-executor.md step 0b is skipped under load,
* worktree. The prose guard in msd-executor.md step 0b is skipped under load,
* so we enforce the constraint at the tooling layer with a PreToolUse hook.
*
* This file verifies all guard behaviours:
@@ -6484,7 +6484,7 @@ describe('bug #3384: adjacent worktree data-loss guards', () => {
* 4. Absolute path inside worktree root passes
* 5. Absolute path outside worktree root is BLOCKED (exit 2)
* 6. Sibling path that merely shares a prefix is BLOCKED (/ boundary check)
* 7. install.js has an fs.existsSync guard for gsd-worktree-path-guard.js
* 7. install.js has an fs.existsSync guard for msd-worktree-path-guard.js
*/
'use strict';
@@ -6499,7 +6499,7 @@ const { runHook: seamRunHook } = require('./helpers/process-seam.cjs');
const { gitOrThrow } = require('./helpers/git-fixture.cjs');
const { QUICK_SPAWN_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const HOOK_PATH = path.join(__dirname, '..', 'hooks', 'gsd-worktree-path-guard.js');
const HOOK_PATH = path.join(__dirname, '..', 'hooks', 'msd-worktree-path-guard.js');
const INSTALL_SRC = path.join(__dirname, '..', 'bin', 'install.js');
// ADR-857 phase 5f-1b: settings-json hook registration moved to runtime-hooks-surface.cts.
const HOOKS_SURFACE_SRC = path.join(__dirname, '..', 'src', 'runtime-hooks-surface.cts');
@@ -6525,7 +6525,7 @@ function git(cwd, args) {
* Create a plain git repo (main repo — .git is a directory).
*/
function makeMainRepo() {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-260-main-'));
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-260-main-'));
git(dir, ['init', '-q']);
git(dir, ['config', 'user.email', 'test@example.com']);
git(dir, ['config', 'user.name', 'Test User']);
@@ -6544,7 +6544,7 @@ function makeMainRepo() {
*/
function makeWorktree(mainRepo, branchName) {
const branch = branchName || 'worktree-agent-test';
const wtDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-260-wt-'));
const wtDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-260-wt-'));
fs.rmdirSync(wtDir); // git worktree add creates the dir itself
git(mainRepo, ['worktree', 'add', '-q', '-b', branch, wtDir]);
return wtDir;
@@ -6555,7 +6555,7 @@ function makeWorktree(mainRepo, branchName) {
*/
function runHook(cwd, payload) {
// QUICK_SPAWN_TIMEOUT_MS (10000ms): previously UNBOUNDED (no `timeout`
// option passed to spawnSync). gsd-worktree-path-guard.js is a
// option passed to spawnSync). msd-worktree-path-guard.js is a
// synchronous, in-process path-guard hook (fs/path checks against a JSON
// stdin payload) — no subprocess or network work of its own. 10s leaves
// generous headroom over its sub-second worst case even on a heavily
@@ -6592,7 +6592,7 @@ after(() => {
// Tests
// ---------------------------------------------------------------------------
describe('bug #260: gsd-worktree-path-guard.js', () => {
describe('bug #260: msd-worktree-path-guard.js', () => {
// 1. No-op in main repo
describe('no-op in main repo', () => {
@@ -6964,7 +6964,7 @@ describe('bug #260: gsd-worktree-path-guard.js', () => {
// the worktree (it doesn't include the '/' boundary). The hook resolves the sibling's git
// toplevel (a different repo) so the different-git-root block fires regardless.
// (#1342: paths outside all git repos now fail open; only different-git-root blocks.)
const base = realp(fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-260-sib-base-')));
const base = realp(fs.mkdtempSync(path.join(os.tmpdir(), 'msd-260-sib-base-')));
const wtDir = path.join(base, 'wt');
const siblingRepoDir = path.join(base, 'wt-sibling');
// We need a genuine linked worktree at <base>/wt and a separate git repo at <base>/wt-sibling.
@@ -7223,14 +7223,14 @@ describe('#2304 — Kimi tool vocabulary engages the guard', () => {
});
// ---------------------------------------------------------------------------
// #1342 — GSD-activity gate + fail-open for no-repo targets
// #1342 — MSD-activity gate + fail-open for no-repo targets
// ---------------------------------------------------------------------------
describe('#1342 — GSD-activity gate + fail-open for no-repo targets', () => {
describe('#1342 — MSD-activity gate + fail-open for no-repo targets', () => {
// Fixtures: one non-agent linked worktree (plain user branch) + one agent worktree
let mainRepo1342;
let nonAgentWorktree; // on branch 'feature-x' — non-GSD
let agentWorktree; // on branch 'worktree-agent-foo' — GSD-managed
let nonAgentWorktree; // on branch 'feature-x' — non-MSD
let agentWorktree; // on branch 'worktree-agent-foo' — MSD-managed
before(() => {
mainRepo1342 = realp(makeMainRepo());
@@ -7250,7 +7250,7 @@ describe('#1342 — GSD-activity gate + fail-open for no-repo targets', () => {
test('(1) non-agent linked worktree: Write to a path outside all git repos exits 0 (no block)', () => {
// Simulates Claude Code plan-mode writing ~/.claude/plans/<slug>.md from a
// manually-created linked worktree that is NOT on a worktree-agent-* branch.
const plansDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-1342-plans-'));
const plansDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-1342-plans-'));
try {
const targetPath = path.join(plansDir, 'my-plan.md');
const payload = {
@@ -7278,14 +7278,14 @@ describe('#1342 — GSD-activity gate + fail-open for no-repo targets', () => {
};
const result = runHook(nonAgentWorktree, payload);
assert.strictEqual(result.status, 0,
`Non-agent linked worktree must exit 0 (GSD-activity gate fires before #260 check). ` +
`Non-agent linked worktree must exit 0 (MSD-activity gate fires before #260 check). ` +
`Got exit ${result.status}. stderr: ${result.stderr}`
);
assert.strictEqual(result.stdout, '', 'Expected no block output');
});
// Test 3 — GSD-managed worktree (worktree-agent-foo): Edit targeting main repo root → exit 2 (block)
test('(3) GSD-managed worktree: Edit targeting main repo root exits 2 with block decision', () => {
// Test 3 — MSD-managed worktree (worktree-agent-foo): Edit targeting main repo root → exit 2 (block)
test('(3) MSD-managed worktree: Edit targeting main repo root exits 2 with block decision', () => {
const payload = {
cwd: agentWorktree,
tool_name: 'Edit',
@@ -7293,15 +7293,15 @@ describe('#1342 — GSD-activity gate + fail-open for no-repo targets', () => {
};
const result = runHook(agentWorktree, payload);
assert.strictEqual(result.status, 2,
`GSD-managed worktree targeting main repo root must be blocked (exit 2). ` +
`MSD-managed worktree targeting main repo root must be blocked (exit 2). ` +
`Got exit ${result.status}. stderr: ${result.stderr}`
);
const parsed = JSON.parse(result.stdout);
assert.strictEqual(parsed.decision, 'block', 'Expected decision:"block" in output');
});
// Test 4 — GSD-managed worktree: absolute target INSIDE the active worktree → exit 0
test('(4) GSD-managed worktree: absolute target inside the active worktree exits 0', () => {
// Test 4 — MSD-managed worktree: absolute target INSIDE the active worktree → exit 0
test('(4) MSD-managed worktree: absolute target inside the active worktree exits 0', () => {
const payload = {
cwd: agentWorktree,
tool_name: 'Edit',
@@ -7309,20 +7309,20 @@ describe('#1342 — GSD-activity gate + fail-open for no-repo targets', () => {
};
const result = runHook(agentWorktree, payload);
assert.strictEqual(result.status, 0,
`GSD-managed worktree targeting its own subtree must pass. ` +
`MSD-managed worktree targeting its own subtree must pass. ` +
`Got exit ${result.status}. stderr: ${result.stderr}`
);
assert.strictEqual(result.stdout, '', 'Expected no block output');
});
// Test 5 — GSD-managed worktree: target OUTSIDE all git repos (tmpdir) → exit 0 (fail open)
test('(5) GSD-managed worktree: target outside all git repos exits 0 (fail open, not #260 vector)', () => {
// Test 5 — MSD-managed worktree: target OUTSIDE all git repos (tmpdir) → exit 0 (fail open)
test('(5) MSD-managed worktree: target outside all git repos exits 0 (fail open, not #260 vector)', () => {
// Create a temp dir that is NOT a git repository (no .git).
// This is the ~/.claude/plans/ scenario — a path that has a real ancestor
// directory but is outside every git repo.
// IMPORTANT: this dir must NOT be inside any .git directory — it must be a plain tempdir
// so the fail-open path (truly outside all repos) is exercised, not the .git-internals block.
const externalDir = realp(fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-1342-ext-')));
const externalDir = realp(fs.mkdtempSync(path.join(os.tmpdir(), 'msd-1342-ext-')));
try {
const targetPath = path.join(externalDir, 'notes.md');
const payload = {
@@ -7332,7 +7332,7 @@ describe('#1342 — GSD-activity gate + fail-open for no-repo targets', () => {
};
const result = runHook(agentWorktree, payload);
assert.strictEqual(result.status, 0,
`GSD-managed worktree writing to a path outside all git repos must fail open (exit 0). ` +
`MSD-managed worktree writing to a path outside all git repos must fail open (exit 0). ` +
`Only the different-git-root vector (#260) blocks; no-repo targets are not that vector. ` +
`Got exit ${result.status}. stderr: ${result.stderr}`
);
@@ -7342,8 +7342,8 @@ describe('#1342 — GSD-activity gate + fail-open for no-repo targets', () => {
}
});
// Test 6 — GSD-managed worktree: Write to .git/config of the MAIN repo → exit 2 (block)
test('(6) blocks absolute writes into the main repo .git internals from a GSD worktree (#1342)', () => {
// Test 6 — MSD-managed worktree: Write to .git/config of the MAIN repo → exit 2 (block)
test('(6) blocks absolute writes into the main repo .git internals from a MSD worktree (#1342)', () => {
// A target like /main-repo/.git/config or /main-repo/.git/hooks/pre-commit causes
// `git rev-parse --show-toplevel` to FAIL (a .git dir is not a work tree), so the
// "file not in any git repo" branch fires. Previously that branch failed open — but
@@ -7358,7 +7358,7 @@ describe('#1342 — GSD-activity gate + fail-open for no-repo targets', () => {
};
const result = runHook(agentWorktree, payload);
assert.strictEqual(result.status, 2,
`GSD-managed worktree targeting .git/config of another repo must be blocked (exit 2). ` +
`MSD-managed worktree targeting .git/config of another repo must be blocked (exit 2). ` +
`Got exit ${result.status}. stderr: ${result.stderr}`
);
const parsed = JSON.parse(result.stdout);
@@ -7374,7 +7374,7 @@ describe('#1342 — GSD-activity gate + fail-open for no-repo targets', () => {
// Static analysis: install.js guard
// ---------------------------------------------------------------------------
describe('install.js guard for gsd-worktree-path-guard.js', () => {
describe('install.js guard for msd-worktree-path-guard.js', () => {
let src;
before(() => {
@@ -7397,7 +7397,7 @@ describe('install.js guard for gsd-worktree-path-guard.js', () => {
);
});
test('install.js checks fs.existsSync before registering gsd-worktree-path-guard.js', () => {
test('install.js checks fs.existsSync before registering msd-worktree-path-guard.js', () => {
const anchorIdx = src.indexOf('hasWorktreePathGuardHook');
assert.ok(anchorIdx !== -1, 'hasWorktreePathGuardHook not found in install.js');
@@ -7408,20 +7408,20 @@ describe('install.js guard for gsd-worktree-path-guard.js', () => {
assert.ok(
block.includes('fs.existsSync') || block.includes('existsSync'),
'install.js must call fs.existsSync on the target path before registering ' +
'gsd-worktree-path-guard.js in settings.json. Without this guard, the hook ' +
'msd-worktree-path-guard.js in settings.json. Without this guard, the hook ' +
'is registered even when the .js file was never copied (root cause of #1754).'
);
});
test('install.js emits a skip warning when gsd-worktree-path-guard.js is missing', () => {
test('install.js emits a skip warning when msd-worktree-path-guard.js is missing', () => {
const anchorIdx = src.indexOf('hasWorktreePathGuardHook');
assert.ok(anchorIdx !== -1, 'hasWorktreePathGuardHook not found in install.js');
const block = src.slice(anchorIdx, Math.min(src.length, anchorIdx + 1200));
assert.ok(
block.includes('Skipped') && block.includes('gsd-worktree-path-guard'),
'install.js must emit a skip warning mentioning gsd-worktree-path-guard when the file is not found'
block.includes('Skipped') && block.includes('msd-worktree-path-guard'),
'install.js must emit a skip warning mentioning msd-worktree-path-guard when the file is not found'
);
});
});
@@ -7446,14 +7446,14 @@ const { cleanup } = require('./helpers.cjs');
const { runHook: seamRunHook } = require('./helpers/process-seam.cjs');
const { gitOrThrow } = require('./helpers/git-fixture.cjs');
const HOOK_PATH = path.join(__dirname, '..', 'hooks', 'gsd-workflow-guard.js');
const HOOK_PATH = path.join(__dirname, '..', 'hooks', 'msd-workflow-guard.js');
function git(cwd, args) {
return gitOrThrow(args, { cwd, timeoutMs: SUBPROCESS_TIMEOUT_MS });
}
function makeRepo(branch) {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-bug-261-'));
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-bug-261-'));
git(dir, ['init', '-q']);
git(dir, ['config', 'user.email', 'test@example.com']);
git(dir, ['config', 'user.name', 'Test User']);
@@ -7565,7 +7565,7 @@ describe('bug #261: workflow guard blocks forced git add on worktree-agent branc
}
});
test('allows git add -f on worktree-agent branch when no GSD config exists', () => {
test('allows git add -f on worktree-agent branch when no MSD config exists', () => {
const dir = makeRepo('worktree-agent-f6');
try {
const result = runBashHook(dir, 'git add -f .planning/SUMMARY.md');
@@ -7619,7 +7619,7 @@ describe('bug #261: workflow guard blocks forced git add on worktree-agent branc
* any submodule path.
*
* Behavioral test: the bash decision pipeline from
* gsd-core/workflows/execute-phase.md is extracted verbatim into an
* msd-core/workflows/execute-phase.md is extracted verbatim into an
* executable snippet here, then run via execFileSync('bash', ...) against
* real fixture projects built with `createTempGitProject()`. We assert
* the resulting USE_WORKTREES_FOR_PLAN value (printed on the final line
@@ -7780,7 +7780,7 @@ describe('Submodule worktree-isolation gate intersects planned paths (#2772)', (
let repo;
beforeEach(() => {
repo = createTempGitProject('gsd-test-2772-');
repo = createTempGitProject('msd-test-2772-');
});
afterEach(() => {
@@ -7981,14 +7981,14 @@ describe('execute-phase.md dispatch wires USE_WORKTREES_FOR_PLAN (#2772)', () =>
const workflowPath = path.join(
__dirname,
'..',
'gsd-core',
'msd-core',
'workflows',
'execute-phase.md'
);
const gatePath = path.join(
__dirname,
'..',
'gsd-core',
'msd-core',
'workflows',
'execute-phase',
'steps',
@@ -8029,7 +8029,7 @@ describe('execute-phase.md dispatch wires USE_WORKTREES_FOR_PLAN (#2772)', () =>
const pinFragmentPath = path.join(
__dirname,
'..',
'gsd-core',
'msd-core',
'workflows',
'execute-phase',
'steps',
@@ -8099,7 +8099,7 @@ describe('execute-phase.md dispatch wires USE_WORKTREES_FOR_PLAN (#2772)', () =>
// (b) actually abort when run against a fixture that stages a submodule path.
describe('quick.md executor pre-commit submodule guard (#2772)', () => {
const quickPath = path.join(__dirname, '..', 'gsd-core', 'workflows', 'quick.md');
const quickPath = path.join(__dirname, '..', 'msd-core', 'workflows', 'quick.md');
test('quick.md executor prompt injects SUBMODULE_PATHS', () => {
const md = fs.readFileSync(quickPath, 'utf-8');
@@ -8156,7 +8156,7 @@ describe('quick.md executor pre-commit submodule guard (#2772)', () => {
].join('\n');
test('guard ABORTs when a staged path falls inside a submodule', () => {
const repo = createTempGitProject('gsd-test-2772-quick-abort-');
const repo = createTempGitProject('msd-test-2772-quick-abort-');
try {
// Create a file inside the submodule path and stage it.
fs.mkdirSync(path.join(repo, 'vendor', 'foo'), { recursive: true });
@@ -8184,7 +8184,7 @@ describe('quick.md executor pre-commit submodule guard (#2772)', () => {
});
test('guard passes when no staged path falls inside a submodule', () => {
const repo = createTempGitProject('gsd-test-2772-quick-pass-');
const repo = createTempGitProject('msd-test-2772-quick-pass-');
try {
fs.mkdirSync(path.join(repo, 'src'), { recursive: true });
fs.writeFileSync(path.join(repo, 'src', 'index.ts'), 'export {};\n');
@@ -8202,7 +8202,7 @@ describe('quick.md executor pre-commit submodule guard (#2772)', () => {
});
test('guard normalizes leading "./" on staged paths and still ABORTs', () => {
const repo = createTempGitProject('gsd-test-2772-quick-norm-');
const repo = createTempGitProject('msd-test-2772-quick-norm-');
try {
fs.mkdirSync(path.join(repo, 'vendor', 'foo'), { recursive: true });
fs.writeFileSync(path.join(repo, 'vendor', 'foo', 'bar.ts'), 'export {};\n');
@@ -8275,11 +8275,11 @@ const path = require('node:path');
const { cleanup } = require('./helpers.cjs');
const { gitOrThrow } = require('./helpers/git-fixture.cjs');
const EXECUTOR_PATH = path.join(__dirname, '..', 'agents', 'gsd-executor.md');
const EXECUTOR_PATH = path.join(__dirname, '..', 'agents', 'msd-executor.md');
// ─── Test A — prompt content asserts the prohibition ───────────────────────
test('bug-3542: gsd-executor.md prohibits `git stash` family inside worktrees', () => {
test('bug-3542: msd-executor.md prohibits `git stash` family inside worktrees', () => {
const content = fs.readFileSync(EXECUTOR_PATH, 'utf-8');
// The prohibition must call out `git stash` explicitly. Just listing
@@ -8289,22 +8289,22 @@ test('bug-3542: gsd-executor.md prohibits `git stash` family inside worktrees',
assert.match(
content,
/`git stash`/,
'gsd-executor.md must explicitly forbid `git stash` (bare push) — see #3542',
'msd-executor.md must explicitly forbid `git stash` (bare push) — see #3542',
);
assert.match(
content,
/`git stash pop`/,
'gsd-executor.md must explicitly forbid `git stash pop` — the load-bearing footgun (#3542)',
'msd-executor.md must explicitly forbid `git stash pop` — the load-bearing footgun (#3542)',
);
assert.match(
content,
/`git stash apply`/,
'gsd-executor.md must explicitly forbid `git stash apply` — same shared-stack hazard as pop (#3542)',
'msd-executor.md must explicitly forbid `git stash apply` — same shared-stack hazard as pop (#3542)',
);
assert.match(
content,
/`git stash drop`/,
'gsd-executor.md must explicitly forbid `git stash drop` — mutates the shared stack (#3542)',
'msd-executor.md must explicitly forbid `git stash drop` — mutates the shared stack (#3542)',
);
// The prohibition must explain WHY (shared storage across worktrees) so
@@ -8313,7 +8313,7 @@ test('bug-3542: gsd-executor.md prohibits `git stash` family inside worktrees',
assert.match(
content,
/shared|share[d]?\s+(across|between)/i,
'gsd-executor.md must document that stash storage is shared across worktrees (#3542)',
'msd-executor.md must document that stash storage is shared across worktrees (#3542)',
);
// The prohibition must document at least one alternative the agent CAN
@@ -8324,11 +8324,11 @@ test('bug-3542: gsd-executor.md prohibits `git stash` family inside worktrees',
content,
);
const hasGitShow = /`git show /i.test(content);
// eslint-disable-next-line local/no-unbounded-quantifier -- parses maintainer-authored gsd-executor.md agent markdown, bounded prose, not adversarial input
// eslint-disable-next-line local/no-unbounded-quantifier -- parses maintainer-authored msd-executor.md agent markdown, bounded prose, not adversarial input
const hasGitDiffRef = /`git diff [^`]*\$?\{?ref\}?|`git diff [A-Z]+:/i.test(content);
assert.ok(
hasThrowawayBranch || hasGitShow || hasGitDiffRef,
'gsd-executor.md must document an alternative to `git stash` ' +
'msd-executor.md must document an alternative to `git stash` ' +
'(commit-to-throwaway-branch, or read-only `git show <ref>:<path>` / ' +
'`git diff <ref> -- <path>`) so the agent has a sanctioned escape path (#3542)',
);
@@ -8338,7 +8338,7 @@ test('bug-3542: gsd-executor.md prohibits `git stash` family inside worktrees',
assert.match(
content,
/#3542/,
'gsd-executor.md must reference issue #3542 next to the stash prohibition for traceability',
'msd-executor.md must reference issue #3542 next to the stash prohibition for traceability',
);
});
@@ -8393,7 +8393,7 @@ test('bug-3542: stash pushed in main checkout is visible inside a linked worktre
'`refs/stash` lives in the shared parent .git directory. If this ' +
'assertion ever stops holding (e.g. git introduces per-worktree ' +
'stash storage in a future release), the executor agent prohibition ' +
'in agents/gsd-executor.md can be relaxed.',
'in agents/msd-executor.md can be relaxed.',
);
// Stronger pin: a `git stash pop` inside the worktree must actually