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