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

This commit is contained in:
upstream-mirror
2026-09-18 01:48:50 +05:00
commit 93ecc1a6f8
787 changed files with 75240 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env node
// PreToolUse(Bash) — only the dispatched `merger` agent may run merge-class git
// commands. The main orchestrator session and every other subagent (developer,
// reviewers, …) are blocked; if any of them falls back to merging itself, the
// team workflow looks healthy but is silently broken (the dev↔merger path is
// dead). The behavioral rule lives in orchestrator.md ("NEVER act as merger
// yourself"); this is the runtime guard.
//
// OPT-IN: inert unless HARNESS_ENFORCE_MERGE_GUARD=1 (deprecated fallback:
// ATOMIC_CRM_ENFORCE_MERGE_GUARD, kept for one release). In a plain Claude Code
// checkout the main session is a general assistant, and blocking its git
// merge/pull/checkout would break normal use, so it stays off until the
// orchestrator explicitly enables it before a team wave.
import { readFileSync } from "node:fs";
import { basename } from "node:path";
import { createHookContext } from "./lib/context.mjs";
import { isMerger } from "./lib/teams.mjs";
import { loadConfig, launcher } from "./lib/config.mjs";
const mergeGuardEnabled =
process.env.HARNESS_ENFORCE_MERGE_GUARD === "1" ||
process.env.ATOMIC_CRM_ENFORCE_MERGE_GUARD === "1";
if (!mergeGuardEnabled) process.exit(0);
const input = JSON.parse(readFileSync(0, "utf8"));
const ctx = createHookContext(input, "block-orchestrator-merge");
// Allow only the dispatched `merger` agent through. The runtime carries its
// (possibly suffixed) identity in agentName or agentType, so check both.
if ([ctx.agentName, ctx.agentType].some(isMerger)) process.exit(0);
const cmd = input.tool_input?.command || "";
if (!cmd) process.exit(0);
// The post-checkout script (e.g. CRM Builder's apply-app-variant.sh) is a
// launcher extension point: its path comes from config.launcher.postCheckoutScript
// so no chat-service path is hardcoded here. When unset, that guard clause is
// inert (a project with no post-checkout script has nothing to gate).
let postCheckoutScript = "";
try {
postCheckoutScript = launcher(loadConfig()).postCheckoutScript || "";
} catch {
postCheckoutScript = "";
}
let blocked = "";
if (/(^|[;&|\s])git\s+merge(\s|$)/.test(cmd)) blocked = "git merge";
else if (/(^|[;&|\s])git\s+checkout\s+(master|main)(\s|$)/.test(cmd))
blocked = "git checkout master/main";
else if (/(^|[;&|\s])git\s+pull(\s|$)/.test(cmd)) blocked = "git pull";
else if (/(^|[;&|\s])git\s+worktree\s+remove(\s|$)/.test(cmd))
blocked = "git worktree remove";
else if (postCheckoutScript && cmd.includes(basename(postCheckoutScript)))
blocked = `${basename(postCheckoutScript)} (merger-only command)`;
if (blocked) {
ctx.fail(
`Blocked: only the dispatched \`merger\` agent may run "${blocked}".\n\n` +
"Rule: the orchestrator must NEVER act as merger, and neither may any other\n" +
"subagent. Only the `merger` runs merge-class git commands. See orchestrator.md\n" +
'"NEVER act as merger yourself".\n\n' +
"If you got here because the merger didn't report back, the team communication broke —\n" +
"the dev's \"ready\" message never reached the merger. Don't salvage by merging yourself;\n" +
"report the failure to the user and stop. Salvaging hides the bug.\n\n" +
`Blocked command:\n ${cmd}`,
{ log: `BLOCK ${blocked}` },
);
}
process.exit(0);