Files
LexAI/.cursor/hooks/guard-destructive.mjs
john kevin asprec 8bc529ef2d
Some checks failed
CI — Test & Build / Test & Build (push) Has been cancelled
feat: add LexAI status bar and suggestion panel
- Implemented a status bar item for LexAI with dynamic status updates (ready, processing, notReady).
- Created a suggestion panel for displaying and interacting with AI-generated suggestions.
- Added functionality for accepting, regenerating, and discarding suggestions within the suggestion zone.
- Introduced configuration options for writing style, prompt patterns, personas, and formats.
- Integrated progress indicators for long-running tasks and improved user feedback.
- Established TypeScript configuration for the vscode package.
2026-08-13 18:06:45 +08:00

73 lines
3.0 KiB
JavaScript

#!/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);