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:
90
.claude/hooks/test/block-nested-orchestrator.test.mjs
Normal file
90
.claude/hooks/test/block-nested-orchestrator.test.mjs
Normal file
@@ -0,0 +1,90 @@
|
||||
// Tests for block-nested-orchestrator.mjs — the PreToolUse(Agent) gate that
|
||||
// enforces the harness dispatch topology. Two rules:
|
||||
// 1. the orchestrator may dispatch ONLY the typed harness agents
|
||||
// (planner, developer, quality-reviewer, merger, documentator).
|
||||
// 2. a subagent (has agent_id) must NOT dispatch an orchestrator.
|
||||
// A block is emitted via decisionBlock: exit 0 with {"decision":"block"} on
|
||||
// stdout. An allowed dispatch exits 0 with no decision JSON.
|
||||
|
||||
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 { afterAll, describe, test, expect } from "vitest";
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const HOOK = join(HERE, "..", "block-nested-orchestrator.mjs");
|
||||
const TMP = mkdtempSync(join(tmpdir(), "block-nested-orch-test-"));
|
||||
const env = { ...process.env, APP_DIR: TMP, CRM_TMP_ROOT: TMP };
|
||||
delete env.CLAUDE_AGENT_NAME;
|
||||
|
||||
afterAll(() => rmSync(TMP, { recursive: true, force: true }));
|
||||
|
||||
// callerType = the dispatching agent's agent_type; agentId distinguishes the
|
||||
// main thread ("") from a subagent (non-empty).
|
||||
const run = (callerType, target, agentId = "") => {
|
||||
const r = spawnSync("node", [HOOK], {
|
||||
input: JSON.stringify({
|
||||
session_id: "td-nested-1",
|
||||
agent_type: callerType,
|
||||
agent_id: agentId,
|
||||
tool_input: { subagent_type: target },
|
||||
}),
|
||||
env,
|
||||
encoding: "utf8",
|
||||
});
|
||||
return {
|
||||
blocked: /"decision":"block"/.test(r.stdout || ""),
|
||||
status: r.status,
|
||||
};
|
||||
};
|
||||
|
||||
describe("block-nested-orchestrator — Rule 1 (orchestrator allowlist)", () => {
|
||||
for (const t of [
|
||||
"planner",
|
||||
"developer",
|
||||
"quality-reviewer",
|
||||
"merger",
|
||||
"documentator",
|
||||
]) {
|
||||
test(`orchestrator dispatching ${t} → allowed`, () => {
|
||||
expect(run("orchestrator", t, "orch-1").blocked).toBe(false);
|
||||
});
|
||||
}
|
||||
|
||||
test("orchestrator dispatching general-purpose → blocked", () => {
|
||||
expect(run("orchestrator", "general-purpose", "orch-1").blocked).toBe(true);
|
||||
});
|
||||
|
||||
test("orchestrator dispatching another orchestrator → blocked", () => {
|
||||
expect(run("orchestrator", "orchestrator", "orch-1").blocked).toBe(true);
|
||||
});
|
||||
|
||||
test("a suffixed orchestrator-name caller is still gated by the allowlist", () => {
|
||||
expect(run("orchestrator-abc12", "Explore", "orch-1").blocked).toBe(true);
|
||||
expect(run("orchestrator-abc12", "planner", "orch-1").blocked).toBe(false);
|
||||
});
|
||||
|
||||
test("chat-orchestrator dispatching planner → allowed", () => {
|
||||
expect(run("chat-orchestrator", "planner", "chat-1").blocked).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("block-nested-orchestrator — Rule 2 (no nested orchestrator)", () => {
|
||||
test("a subagent dispatching orchestrator → blocked", () => {
|
||||
expect(run("general-purpose", "orchestrator", "gp-1").blocked).toBe(true);
|
||||
});
|
||||
|
||||
test("a subagent dispatching chat-orchestrator → blocked", () => {
|
||||
expect(run("developer", "chat-orchestrator", "dev-1").blocked).toBe(true);
|
||||
});
|
||||
|
||||
test("the main thread (no agent_id) dispatching orchestrator → allowed", () => {
|
||||
expect(run("", "orchestrator", "").blocked).toBe(false);
|
||||
});
|
||||
|
||||
test("a subagent dispatching a non-orchestrator agent → allowed", () => {
|
||||
expect(run("developer", "general-purpose", "dev-1").blocked).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user