snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
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
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
This commit is contained in:
214
.claude/hooks/test/setup-worktree.test.mjs
Executable file
214
.claude/hooks/test/setup-worktree.test.mjs
Executable file
@@ -0,0 +1,214 @@
|
||||
// Tests for setup-worktree.mjs (vitest, "claude" Node project). setup-worktree is
|
||||
// now a PreToolUse(Agent) hook: it reads the dispatch tool_input (subagent_type,
|
||||
// name, prompt) and creates the worktree BEFORE the subagent starts. Uses a
|
||||
// throwaway git repo (APP_DIR) + CRM_TMP_ROOT. Tests are ordered and stateful —
|
||||
// each observes the state produced by the previous step.
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
||||
import { sanitizePath } from "../lib/paths.mjs";
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const HOOK = join(HERE, "..", "setup-worktree.mjs");
|
||||
const SESSION_ID = "ab12cd34-1111-2222-3333-444455556666";
|
||||
const SS = SESSION_ID.split("-")[0];
|
||||
|
||||
let TMP;
|
||||
let APP_DIR;
|
||||
let WB;
|
||||
let env;
|
||||
|
||||
const g = (...args) =>
|
||||
spawnSync("git", ["-C", APP_DIR, ...args], { encoding: "utf8" });
|
||||
|
||||
// Build a PreToolUse(Agent) dispatch payload and run the hook.
|
||||
const dispatch = ({
|
||||
subagentType = "developer",
|
||||
taskId = "",
|
||||
name = "",
|
||||
} = {}) => {
|
||||
const wp = taskId ? join(WB, taskId) : join(WB, "simple");
|
||||
const branch = taskId ? `${SS}/${taskId}` : `${SS}/simple`;
|
||||
const prompt =
|
||||
`ROLE: ${subagentType}\n` +
|
||||
(taskId ? `TASK_ID: ${taskId}\n` : "") +
|
||||
`WORKTREE_PATH: ${wp}\nBRANCH_NAME: ${branch}`;
|
||||
return spawnSync("node", [HOOK], {
|
||||
input: JSON.stringify({
|
||||
session_id: SESSION_ID,
|
||||
tool_input: {
|
||||
subagent_type: subagentType,
|
||||
name: name || (taskId ? `${subagentType}-${taskId}` : subagentType),
|
||||
prompt,
|
||||
},
|
||||
}),
|
||||
env,
|
||||
encoding: "utf8",
|
||||
});
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
TMP = mkdtempSync(join(tmpdir(), "setup-wt-test-"));
|
||||
APP_DIR = join(TMP, "app");
|
||||
mkdirSync(APP_DIR, { recursive: true });
|
||||
g("init", "-q", "-b", "main");
|
||||
g("config", "user.email", "t@t.t");
|
||||
g("config", "user.name", "t");
|
||||
writeFileSync(join(APP_DIR, "seed.txt"), "seed\n");
|
||||
g("add", ".");
|
||||
g("commit", "-q", "-m", "seed");
|
||||
mkdirSync(join(APP_DIR, "node_modules"), { recursive: true });
|
||||
|
||||
const CRM_TMP_ROOT = join(TMP, "scratch");
|
||||
WB = join(CRM_TMP_ROOT, sanitizePath(APP_DIR), SESSION_ID);
|
||||
|
||||
env = { ...process.env, APP_DIR, CRM_TMP_ROOT };
|
||||
delete env.VALIDATE_WORKTREE;
|
||||
|
||||
dispatch({ subagentType: "developer", taskId: "TASK-001" });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
rmSync(TMP, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("setup-worktree session-branch topology (PreToolUse/Agent)", () => {
|
||||
test("session branch created", () => {
|
||||
expect(
|
||||
g("show-ref", "--verify", "--quiet", `refs/heads/session/${SS}`).status,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test("session-base anchor created", () => {
|
||||
expect(
|
||||
g("show-ref", "--verify", "--quiet", `refs/heads/session-base/${SS}`)
|
||||
.status,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test("_session worktree created", () => {
|
||||
expect(existsSync(join(WB, "_session"))).toBe(true);
|
||||
});
|
||||
|
||||
test("task worktree created", () => {
|
||||
expect(existsSync(join(WB, "TASK-001"))).toBe(true);
|
||||
});
|
||||
|
||||
test("node_modules provisioned into task worktree", () => {
|
||||
expect(existsSync(join(WB, "TASK-001", "node_modules"))).toBe(true);
|
||||
});
|
||||
|
||||
test("task branch forked from session branch", () => {
|
||||
expect(
|
||||
g("merge-base", "--is-ancestor", `session/${SS}`, `${SS}/TASK-001`)
|
||||
.status,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test("non-dev dispatch (reviewer) is a silent no-op", () => {
|
||||
const r = spawnSync("node", [HOOK], {
|
||||
input: JSON.stringify({
|
||||
session_id: SESSION_ID,
|
||||
tool_input: {
|
||||
subagent_type: "quality-reviewer",
|
||||
name: "quality-reviewer-TASK-001",
|
||||
prompt: "ROLE: quality-reviewer\nTASK_ID: TASK-001",
|
||||
},
|
||||
}),
|
||||
env,
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(r.status).toBe(0);
|
||||
});
|
||||
|
||||
test("idempotent second run exits 0 and keeps the worktree", () => {
|
||||
expect(
|
||||
dispatch({ subagentType: "developer", taskId: "TASK-001" }).status,
|
||||
).toBe(0);
|
||||
expect(existsSync(join(WB, "TASK-001"))).toBe(true);
|
||||
});
|
||||
|
||||
test("stale _session registration survives dir wipe", () => {
|
||||
rmSync(join(WB, "_session"), { recursive: true, force: true });
|
||||
expect(
|
||||
g("worktree", "list", "--porcelain").stdout.includes(
|
||||
join(WB, "_session"),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("_session recreated after stale-registration wipe", () => {
|
||||
dispatch({ subagentType: "developer", taskId: "TASK-002" });
|
||||
expect(existsSync(join(WB, "_session", ".git"))).toBe(true);
|
||||
});
|
||||
|
||||
test("developer on the <short>/simple branch derives the simple worktree", () => {
|
||||
spawnSync("node", [HOOK], {
|
||||
input: JSON.stringify({
|
||||
session_id: SESSION_ID,
|
||||
tool_input: {
|
||||
subagent_type: "developer",
|
||||
name: "developer",
|
||||
prompt: `ROLE: developer\nWORKTREE_PATH: ${join(WB, "simple")}\nBRANCH_NAME: ${SS}/simple`,
|
||||
},
|
||||
}),
|
||||
env,
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(existsSync(join(WB, "simple"))).toBe(true);
|
||||
});
|
||||
|
||||
test("developer with WORKTREE_PATH but unresolvable task id is blocked (exit 2)", () => {
|
||||
const r = spawnSync("node", [HOOK], {
|
||||
input: JSON.stringify({
|
||||
session_id: SESSION_ID,
|
||||
tool_input: {
|
||||
subagent_type: "developer",
|
||||
name: "developer",
|
||||
prompt:
|
||||
"ROLE: developer\nWORKTREE_PATH: /wt/nowhere\nBRANCH_NAME: x/y",
|
||||
},
|
||||
}),
|
||||
env,
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(r.status).toBe(2);
|
||||
});
|
||||
|
||||
test("promotion-conflict-resolver is accepted with no worktree (exit 0)", () => {
|
||||
const r = spawnSync("node", [HOOK], {
|
||||
input: JSON.stringify({
|
||||
session_id: SESSION_ID,
|
||||
tool_input: {
|
||||
subagent_type: "developer",
|
||||
name: "developer",
|
||||
prompt: `ROLE: promotion-conflict-resolver (gated $CLAUDE_PROJECT_DIR exception)\nSESSION_SHORT_ID: ${SS}`,
|
||||
},
|
||||
}),
|
||||
env,
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(r.status).toBe(0);
|
||||
});
|
||||
|
||||
test("no SESSION-BRANCH FAILED logged", () => {
|
||||
let logTxt = "";
|
||||
try {
|
||||
logTxt = readFileSync(join(WB, "hooks.log"), "utf8");
|
||||
} catch {
|
||||
logTxt = "";
|
||||
}
|
||||
expect(logTxt.includes("SESSION-BRANCH FAILED")).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user