#!/usr/bin/env node
/**
 * beforeShellExecution hook — turn the kit's "explicit authorization before
 * destructive or external action" rule into something that actually stops.
 *
 * A rule in a prompt is advice the model can rationalize past under momentum.
 * This is a gate. It never denies on its own — it returns "ask", so you decide,
 * with the reason named. That keeps the failure mode "one extra confirmation"
 * rather than "the agent cannot work".
 *
 * Contract: stdin is the beforeShellExecution payload; stdout is
 * {"permission": "allow"|"ask"|"deny", "user_message": "...", "agent_message": "..."}.
 * Exit 0 = success. This hook has no failClosed flag in hooks.json, so if node is
 * missing or this script throws, Cursor fails open and the session keeps working.
 */

// Routine `git push` is deliberately NOT gated: a prompt that fires on every push gets
// approved reflexively and stops meaning anything. Force pushes are. Add your own.
const PATTERNS = [
  [/\brm\s+(-[a-zA-Z]*[rf][a-zA-Z]*\s+)+/, "recursive or forced delete"],
  [/\bgit\s+push\b.*(--force|-f)\b/, "force push"],
  [/\bgit\s+(reset\s+--hard|clean\s+-[a-zA-Z]*f)/, "discards uncommitted work"],
  [/\b(drop|truncate)\s+(table|database|schema)\b/i, "destructive database statement"],
  [/\b(migrate|db:migrate|alembic\s+upgrade|prisma\s+migrate\s+deploy)\b/i, "database migration"],
  [/\b(terraform|pulumi)\s+(apply|destroy)\b/, "infrastructure change"],
  [/\bkubectl\s+(delete|apply)\b/, "cluster change"],
  [/\b(npm|pnpm|yarn)\s+publish\b/, "package publish"],
  [/\b(vercel|netlify|fly|heroku|wrangler)\s+(deploy|publish)\b/i, "deployment"],
  [/\bdocker\s+(push|system\s+prune)\b/, "registry push or prune"],
  [/\bchmod\s+(-R\s+)?777\b/, "world-writable permissions"],
  [/\bcurl\b[^|]*\|\s*(ba)?sh\b/, "pipe-to-shell from the network"],
  [/>\s*\/dev\/sd[a-z]|\bmkfs\b|\bdd\s+if=.*of=\/dev\//, "raw device write"],
];

const readAll = () =>
  new Promise((resolve) => {
    let data = "";
    process.stdin.setEncoding("utf8");
    process.stdin.on("data", (c) => (data += c));
    process.stdin.on("end", () => resolve(data));
    setTimeout(() => resolve(data), 2000).unref();
  });

const allow = () => {
  process.stdout.write(JSON.stringify({ permission: "allow" }));
  process.exit(0);
};

const raw = await readAll();
let command = "";
try {
  command = JSON.parse(raw || "{}").command || "";
} catch {
  allow();
}

const hit = PATTERNS.find(([re]) => re.test(command));
if (!hit) allow();

const reason = hit[1];
process.stdout.write(
  JSON.stringify({
    permission: "ask",
    user_message: `Gated: ${reason}. Approve only if you intended this.`,
    agent_message:
      `This command was gated as a ${reason}. Per the quality gates in AGENTS.md, destructive, external, ` +
      `and irreversible actions need explicit owner authorization — loop momentum is not authorization. ` +
      `If the owner declines, record it as a decision-ready item in docs/PROGRESS.md and re-route to another ` +
      `independent unit rather than looking for a way around this command.`,
  }),
);
process.exit(0);
