Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
96 lines
2.8 KiB
JavaScript
96 lines
2.8 KiB
JavaScript
// Tests for enforce-dev-dispatch.mjs — the PreToolUse(Agent) gate that forces a
|
|
// `developer` dispatch onto the setup-worktree-prepared worktree (WORKTREE_PATH
|
|
// in the prompt) and forbids the Agent tool's own isolation:"worktree". Exit 2
|
|
// blocks the dispatch; exit 0 allows it.
|
|
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { spawnSync } from "node:child_process";
|
|
import { describe, test, expect, beforeAll, afterAll } from "vitest";
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const HOOK = join(HERE, "..", "enforce-dev-dispatch.mjs");
|
|
|
|
let env;
|
|
let TMP;
|
|
|
|
const run = (tool_input) =>
|
|
spawnSync("node", [HOOK], {
|
|
input: JSON.stringify({
|
|
session_id: "ab12cd34-1111-2222-3333-444455556666",
|
|
tool_input,
|
|
}),
|
|
env,
|
|
encoding: "utf8",
|
|
}).status;
|
|
|
|
beforeAll(() => {
|
|
TMP = mkdtempSync(join(tmpdir(), "enforce-dev-dispatch-test-"));
|
|
env = { ...process.env, APP_DIR: TMP, CRM_TMP_ROOT: join(TMP, "scratch") };
|
|
});
|
|
|
|
afterAll(() => rmSync(TMP, { recursive: true, force: true }));
|
|
|
|
describe("enforce-dev-dispatch", () => {
|
|
test("allows a developer dispatch carrying WORKTREE_PATH", () => {
|
|
expect(
|
|
run({
|
|
subagent_type: "developer",
|
|
prompt:
|
|
"ROLE: developer\nTASK_ID: TASK-001\nWORKTREE_PATH: /wt/x/TASK-001\nBRANCH_NAME: x/TASK-001",
|
|
}),
|
|
).toBe(0);
|
|
});
|
|
|
|
test("blocks a developer dispatch missing WORKTREE_PATH", () => {
|
|
expect(
|
|
run({
|
|
subagent_type: "developer",
|
|
prompt: "ROLE: developer\nTASK_ID: TASK-001",
|
|
}),
|
|
).toBe(2);
|
|
});
|
|
|
|
test("blocks a developer dispatch using isolation:worktree", () => {
|
|
expect(
|
|
run({
|
|
subagent_type: "developer",
|
|
isolation: "worktree",
|
|
prompt: "ROLE: developer\nWORKTREE_PATH: /wt/x/TASK-001",
|
|
}),
|
|
).toBe(2);
|
|
});
|
|
|
|
test("blocks a developer dispatch using run_in_background:true", () => {
|
|
expect(
|
|
run({
|
|
subagent_type: "developer",
|
|
run_in_background: true,
|
|
prompt:
|
|
"ROLE: developer\nTASK_ID: TASK-001\nWORKTREE_PATH: /wt/x/TASK-001\nBRANCH_NAME: x/TASK-001",
|
|
}),
|
|
).toBe(2);
|
|
});
|
|
|
|
test("allows the promotion-conflict-resolver (no WORKTREE_PATH by design)", () => {
|
|
expect(
|
|
run({
|
|
subagent_type: "developer",
|
|
prompt:
|
|
"ROLE: promotion-conflict-resolver (gated $CLAUDE_PROJECT_DIR exception)\nSESSION_SHORT_ID: ab12cd34\nUnder the promotion lock, in $CLAUDE_PROJECT_DIR on main, re-run the merge.",
|
|
}),
|
|
).toBe(0);
|
|
});
|
|
|
|
test("ignores non-developer dispatches (e.g. merger)", () => {
|
|
expect(
|
|
run({
|
|
subagent_type: "merger",
|
|
prompt: "ROLE: merger\nTASK_ID: TASK-001",
|
|
}),
|
|
).toBe(0);
|
|
});
|
|
});
|