// tests/commands/trekexecute-parallel-portability.test.mjs // // Executable tests for the two shell blocks /trekexecute Phase 2.6 Step 2a' and // Phase 2.55 Check 2 tell the agent to run. Both blocks wrecked a real voyage on // macOS (order 20260831T214411Z-941965142-from-.claude): // // Defect 1: `realpath --relative-to` is GNU coreutils. On BSD realpath the // command substitution fails, PROJECT_REL becomes EMPTY, and `mkdir -p // "$wt/"` + `cp ... "$wt//"` both SUCCEED — brief.md/plan.md land at the // worktree root instead of the project relpath. Exit status stays 0; only // file location tells the truth. Every assertion here checks placement. // Defect 2: Check 2 ran `git add {plan-path}` unconditionally. When the // project directory is gitignored (normal — .claude/projects/ is tool- // managed and local-only) the add fails and the plan never reaches HEAD. // // The blocks are EXTRACTED from commands/trekexecute.md and executed, so the // test binds to what an agent actually copies, not to prose about it. Anchors // are grep-able strings, never line numbers. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync, chmodSync } from 'node:fs'; import { execFileSync, spawnSync } from 'node:child_process'; import { tmpdir } from 'node:os'; import { realpathSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const COMMAND_FILE = join(ROOT, 'commands', 'trekexecute.md'); const COPY_BLOCK_ANCHOR = "**2a'. Copy gitignored project artifacts"; const CHECK2_ANCHOR = '### Check 2 —'; // The pre-fix form, kept ONLY as the negative control for Defect 1. const LEGACY_GNU_LINE = 'PROJECT_REL="$(realpath --relative-to="$REPO_ROOT" "$PROJECT_SOURCE")"'; // --- helpers ------------------------------------------------------------- /** Extract the first ```bash fence that follows `anchor` in commands/trekexecute.md. */ function extractBashBlock(anchor) { const text = readFileSync(COMMAND_FILE, 'utf8'); const at = text.indexOf(anchor); assert.ok(at >= 0, `anchor not found in trekexecute.md: ${anchor}`); const fenceOpen = text.indexOf('```bash', at); assert.ok(fenceOpen >= 0, `no bash fence after anchor: ${anchor}`); const bodyStart = text.indexOf('\n', fenceOpen) + 1; const fenceClose = text.indexOf('```', bodyStart); assert.ok(fenceClose > bodyStart, `unterminated bash fence after anchor: ${anchor}`); return text.slice(bodyStart, fenceClose); } /** * A PATH directory whose `realpath` behaves like BSD realpath: it rejects every * GNU long option and resolves bare paths correctly. Stubbed, never assumed — * the machine running the suite may or may not have GNU coreutils. */ function bsdRealpathStubDir() { const dir = mkdtempSync(join(tmpdir(), 'trek-bsdstub-')); const stub = join(dir, 'realpath'); writeFileSync(stub, [ '#!/bin/sh', '# BSD realpath stand-in: no GNU long options.', 'for a in "$@"; do', ' case "$a" in', ' --*) echo "realpath: illegal option -- -" >&2; exit 1 ;;', ' esac', 'done', "exec python3 -c 'import os,sys", 'for p in sys.argv[1:]: print(os.path.realpath(p))', "' \"$@\"", '', ].join('\n')); chmodSync(stub, 0o755); return dir; } /** A PATH directory whose `git check-ignore` dies fatally (128), everything else real. */ function fatalCheckIgnoreGitStubDir() { const realGit = execFileSync('/usr/bin/env', ['sh', '-c', 'command -v git'], { encoding: 'utf8' }).trim(); assert.ok(realGit, 'git not on PATH — cannot build the check-ignore stub'); const dir = mkdtempSync(join(tmpdir(), 'trek-gitstub-')); const stub = join(dir, 'git'); writeFileSync(stub, [ '#!/bin/sh', '# `git check-ignore` fatal (128): NOT an answer about ignore status.', 'for a in "$@"; do', ' if [ "$a" = "check-ignore" ]; then', ' echo "fatal: simulated check-ignore failure" >&2', ' exit 128', ' fi', 'done', `exec ${realGit} "$@"`, '', ].join('\n')); chmodSync(stub, 0o755); return dir; } function git(cwd, ...args) { return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); } /** * A temp repo with a committed file plus a project directory holding brief/plan * (+ research unless opts.research === false). `gitignoreProject: true` adds the * .gitignore line that makes the project directory invisible to `git add` — * and, note, invisible to `git status --porcelain` too, which is exactly why * this topology sails through Check 1 and only trips on Check 2. */ function makeRepo(opts = {}) { const root = realpathSync(mkdtempSync(join(tmpdir(), 'trek-exec-'))); git(root, 'init', '-q', '-b', 'main'); git(root, 'config', 'user.email', 'test@example.invalid'); git(root, 'config', 'user.name', 'Test'); writeFileSync(join(root, 'README.md'), '# fixture\n'); if (opts.gitignoreProject) writeFileSync(join(root, '.gitignore'), '.claude/projects/\n'); git(root, 'add', 'README.md', ...(opts.gitignoreProject ? ['.gitignore'] : [])); git(root, 'commit', '-qm', 'init'); const projectRel = join('.claude', 'projects', 'demo'); const projectDir = join(root, projectRel); mkdirSync(projectDir, { recursive: true }); writeFileSync(join(projectDir, 'brief.md'), '# brief\n'); writeFileSync(join(projectDir, 'plan.md'), '# plan\n'); if (opts.research !== false) { mkdirSync(join(projectDir, 'research'), { recursive: true }); writeFileSync(join(projectDir, 'research', '01-x.md'), '# r\n'); } const worktreeDir = join(root, '.claude', 'trekplan-sessions', 'demo', 'worktrees'); mkdirSync(join(worktreeDir, 'session-1'), { recursive: true }); mkdirSync(join(worktreeDir, 'session-2'), { recursive: true }); return { root, projectRel, projectDir, worktreeDir }; } function runBlock(script, { cwd, env }) { return spawnSync('bash', ['-c', script], { cwd, env: { ...process.env, ...env }, encoding: 'utf8' }); } function copyBlockEnv(repo, pathPrefixDir) { return { REPO_ROOT: repo.root, PROJECT_DIR: repo.projectDir, WORKTREE_DIR: repo.worktreeDir, PATH: `${pathPrefixDir}:${process.env.PATH}`, }; } function exists(p) { try { readFileSync(p); return true; } catch { return false; } } // --- Defect 1: portable relpath derivation ------------------------------- test("2a' — the BSD realpath stub is known-positive: bare path resolves, --relative-to is rejected", () => { const stubDir = bsdRealpathStubDir(); try { const bare = runBlock('realpath "$HOME"', { cwd: ROOT, env: { PATH: `${stubDir}:${process.env.PATH}` } }); assert.equal(bare.status, 0, 'stub must resolve a bare path (proves it can succeed)'); assert.equal(bare.stdout.trim(), realpathSync(process.env.HOME)); const gnu = runBlock('realpath --relative-to=/ "$HOME"', { cwd: ROOT, env: { PATH: `${stubDir}:${process.env.PATH}` } }); assert.notEqual(gnu.status, 0, 'stub must reject the GNU long option'); assert.match(gnu.stderr, /illegal option/, 'stub must fail the way BSD realpath fails'); assert.equal(gnu.stdout.trim(), '', 'no stdout — this is what leaves PROJECT_REL empty'); } finally { rmSync(stubDir, { recursive: true, force: true }); } }); test("2a' — NEGATIVE CONTROL: the GNU form drops plan.md at the worktree ROOT under BSD realpath", () => { const repo = makeRepo(); const stubDir = bsdRealpathStubDir(); try { const legacy = [ 'PROJECT_SOURCE="$(realpath "${PROJECT_DIR}")"', LEGACY_GNU_LINE, 'for wt in "$WORKTREE_DIR"/session-*; do', ' [ -d "$wt" ] || continue', ' mkdir -p "$wt/$PROJECT_REL"', ' cp "$PROJECT_SOURCE"/brief.md "$wt/$PROJECT_REL/"', ' cp "$PROJECT_SOURCE"/plan.md "$wt/$PROJECT_REL/"', 'done', ].join('\n'); runBlock(legacy, { cwd: repo.root, env: copyBlockEnv(repo, stubDir) }); const wt = join(repo.worktreeDir, 'session-1'); assert.equal(exists(join(wt, repo.projectRel, 'plan.md')), false, 'the broken form must NOT put plan.md at the project relpath'); assert.equal(exists(join(wt, 'plan.md')), true, 'the broken form silently drops plan.md at the worktree root — the measured havari'); } finally { rmSync(repo.root, { recursive: true, force: true }); rmSync(stubDir, { recursive: true, force: true }); } }); test("2a' — the shipped block copies brief/plan/research to $wt/$PROJECT_REL without GNU realpath", () => { const repo = makeRepo(); const stubDir = bsdRealpathStubDir(); try { const r = runBlock(extractBashBlock(COPY_BLOCK_ANCHOR), { cwd: repo.root, env: copyBlockEnv(repo, stubDir) }); assert.equal(r.status, 0, `block must succeed without GNU realpath. stderr: ${r.stderr}`); for (const s of ['session-1', 'session-2']) { const dest = join(repo.worktreeDir, s, repo.projectRel); assert.equal(exists(join(dest, 'plan.md')), true, `${s}: plan.md must reach $wt/$PROJECT_REL`); assert.equal(exists(join(dest, 'brief.md')), true, `${s}: brief.md must reach $wt/$PROJECT_REL`); assert.equal(exists(join(dest, 'research', '01-x.md')), true, `${s}: research/ must reach $wt/$PROJECT_REL`); assert.equal(exists(join(repo.worktreeDir, s, 'plan.md')), false, `${s}: nothing may land at the worktree root`); } } finally { rmSync(repo.root, { recursive: true, force: true }); rmSync(stubDir, { recursive: true, force: true }); } }); test("2a' — a project without research/ is not a failure (block still exits 0)", () => { const repo = makeRepo({ research: false }); const stubDir = bsdRealpathStubDir(); try { const r = runBlock(extractBashBlock(COPY_BLOCK_ANCHOR), { cwd: repo.root, env: copyBlockEnv(repo, stubDir) }); assert.equal(r.status, 0, `missing research/ must not fail the wave. stderr: ${r.stderr}`); assert.equal(exists(join(repo.worktreeDir, 'session-1', repo.projectRel, 'plan.md')), true); } finally { rmSync(repo.root, { recursive: true, force: true }); rmSync(stubDir, { recursive: true, force: true }); } }); test("2a' — an underivable relpath fails LOUDLY instead of dropping files at the worktree root", () => { const repo = makeRepo(); const stubDir = bsdRealpathStubDir(); const outside = realpathSync(mkdtempSync(join(tmpdir(), 'trek-outside-'))); try { mkdirSync(join(outside, 'p'), { recursive: true }); writeFileSync(join(outside, 'p', 'brief.md'), 'b'); writeFileSync(join(outside, 'p', 'plan.md'), 'p'); const env = { ...copyBlockEnv(repo, stubDir), PROJECT_DIR: join(outside, 'p') }; const r = runBlock(extractBashBlock(COPY_BLOCK_ANCHOR), { cwd: repo.root, env }); assert.notEqual(r.status, 0, 'a project outside REPO_ROOT must abort the wave'); assert.match(r.stderr, /relpath/i, 'the abort must name the cause'); assert.equal(exists(join(repo.worktreeDir, 'session-1', 'plan.md')), false, 'nothing may be dropped at the worktree root'); } finally { rmSync(repo.root, { recursive: true, force: true }); rmSync(outside, { recursive: true, force: true }); rmSync(stubDir, { recursive: true, force: true }); } }); test("2a' — the GNU-only form is gone from the block agents copy", () => { const block = extractBashBlock(COPY_BLOCK_ANCHOR); assert.equal(block.includes('--relative-to'), false, 'the copied block must not contain `realpath --relative-to` (GNU-only)'); assert.ok(block.includes('os.path.relpath'), 'the copied block must derive the relpath portably'); }); test("both copied blocks are ASCII-clean (bash 3.2 dies on a multibyte char under set -u)", () => { const nonAscii = (s) => s.split('\n') .map((line, i) => [i + 1, line]) .filter(([, line]) => /[^\x00-\x7F]/.test(line)); // Known-positive: the detector must actually fire on a multibyte char. assert.equal(nonAscii('echo "a — b"').length, 1, 'detector must find an em-dash'); for (const anchor of [COPY_BLOCK_ANCHOR, CHECK2_ANCHOR]) { assert.deepEqual(nonAscii(extractBashBlock(anchor)), [], `non-ASCII inside the shell block after ${anchor} (prose outside the fence is fine)`); } }); // --- Defect 2: gitignored project directory ------------------------------ // The block carries the `{plan-path}` placeholder the way every other block in // trekexecute.md does. Substitute it exactly as an agent would — never inject // PLAN_PATH through the environment: that would supply what the doc must supply // itself, and a block that never assigns the variable would still pass. function check2Script(planPath) { const block = extractBashBlock(CHECK2_ANCHOR); assert.ok(block.includes('{plan-path}'), 'Check 2 block must carry the {plan-path} placeholder for the agent to substitute'); return block.replace('{plan-path}', planPath); } function check2Env(repo, pathPrefixDir) { const env = { REPO_ROOT: repo.root, PLAN_PATH: '' }; if (pathPrefixDir) env.PATH = `${pathPrefixDir}:${process.env.PATH}`; return env; } test('Check 2 — gitignored project dir: no commit, no failure, and 2a\' still delivers the plan', () => { const repo = makeRepo({ gitignoreProject: true }); const stubDir = bsdRealpathStubDir(); try { // Known-positive on the premise: the plan file really is ignored here. const ci = spawnSync('git', ['check-ignore', '-v', join(repo.projectRel, 'plan.md')], { cwd: repo.root, encoding: 'utf8' }); assert.equal(ci.status, 0, 'fixture premise: the plan file must actually be gitignored'); const head = git(repo.root, 'rev-parse', 'HEAD').trim(); const r = runBlock(check2Script(join(repo.projectRel, 'plan.md')), { cwd: repo.root, env: check2Env(repo) }); assert.equal(r.status, 0, `Check 2 must tolerate a gitignored plan file. stderr: ${r.stderr}`); assert.equal(git(repo.root, 'rev-parse', 'HEAD').trim(), head, 'an ignored plan file must NOT be forced into history (origin is a public mirror)'); const copy = runBlock(extractBashBlock(COPY_BLOCK_ANCHOR), { cwd: repo.root, env: copyBlockEnv(repo, stubDir) }); assert.equal(copy.status, 0, `copy step must succeed. stderr: ${copy.stderr}`); assert.equal(exists(join(repo.worktreeDir, 'session-1', repo.projectRel, 'plan.md')), true, 'the plan must reach the worktree even though git never tracked it'); } finally { rmSync(repo.root, { recursive: true, force: true }); rmSync(stubDir, { recursive: true, force: true }); } }); test('Check 2 — KNOWN-POSITIVE: an untracked, NOT-ignored plan file is still added and committed', () => { const repo = makeRepo(); try { const planPath = join(repo.projectRel, 'plan.md'); const head = git(repo.root, 'rev-parse', 'HEAD').trim(); const r = runBlock(check2Script(planPath), { cwd: repo.root, env: check2Env(repo) }); assert.equal(r.status, 0, `Check 2 must succeed on a normal untracked plan. stderr: ${r.stderr}`); assert.notEqual(git(repo.root, 'rev-parse', 'HEAD').trim(), head, 'a trackable plan file must still be committed for worktree visibility'); const ls = spawnSync('git', ['ls-files', '--error-unmatch', planPath], { cwd: repo.root, encoding: 'utf8' }); assert.equal(ls.status, 0, 'the plan file must now be tracked'); } finally { rmSync(repo.root, { recursive: true, force: true }); } }); test('Check 2 — a FATAL git check-ignore (128) is not read as "not ignored"', () => { const repo = makeRepo({ gitignoreProject: true }); const stubDir = fatalCheckIgnoreGitStubDir(); try { const head = git(repo.root, 'rev-parse', 'HEAD').trim(); const r = runBlock(check2Script(join(repo.projectRel, 'plan.md')), { cwd: repo.root, env: check2Env(repo, stubDir) }); assert.notEqual(r.status, 0, 'a fatal check-ignore must stop, not fall through to git add'); assert.match(r.stderr, /check-ignore/, 'the stop must name the failing probe'); assert.equal(git(repo.root, 'rev-parse', 'HEAD').trim(), head, 'no commit may be made on a fatal probe'); } finally { rmSync(repo.root, { recursive: true, force: true }); rmSync(stubDir, { recursive: true, force: true }); } });