flake-update-20260505
  1#!/usr/bin/env bun
  2/**
  3 * PreToolUse hook (Bash) — validate git commands for safety.
  4 *
  5 * Matches pi's validate-git-push.ts extension behaviour:
  6 *   • git push without explicit refspec  → block
  7 *   • git add -A / --all / .             → block
  8 *   • push to protected branches         → warn (stderr)
  9 *
 10 * Handles command chaining: &&, ||, ;, |, $()
 11 */
 12
 13import { readStdinJSON } from "./lib.ts";
 14
 15interface PreToolInput {
 16  tool_name: string;
 17  tool_input: { command?: string };
 18}
 19
 20// Match git commands even after chaining operators
 21const CMD_PREFIX = String.raw`(^|&&|\|\||;|\||\$\()\s*`;
 22
 23const DANGEROUS_ADD = [
 24  new RegExp(CMD_PREFIX + String.raw`git\s+add\s+-A(\s|$)`),
 25  new RegExp(CMD_PREFIX + String.raw`git\s+add\s+--all(\s|$)`),
 26  new RegExp(CMD_PREFIX + String.raw`git\s+add\s+\.(\s|$)`),
 27];
 28
 29const GIT_PUSH = new RegExp(CMD_PREFIX + String.raw`git\s+push(\s|$)`);
 30const HAS_REFSPEC = /git\s+push\s+.*\s+\S+:\S+/;
 31
 32function isDangerousAdd(cmd: string): boolean {
 33  return DANGEROUS_ADD.some((re) => re.test(cmd));
 34}
 35
 36function isGitPush(cmd: string): boolean {
 37  return GIT_PUSH.test(cmd);
 38}
 39
 40function pushesToProtected(cmd: string): boolean {
 41  return [":main", ":master"].some((b) => cmd.includes(b));
 42}
 43
 44async function main() {
 45  const data = await readStdinJSON<PreToolInput>();
 46  if (!data) process.exit(0);
 47
 48  const cmd = data.tool_input?.command?.trim() || "";
 49  if (!cmd) process.exit(0);
 50
 51  // ── git add safety ────────────────────────────────────────────
 52  if (isDangerousAdd(cmd)) {
 53    process.stdout.write(
 54      JSON.stringify({
 55        decision: "block",
 56        reason: [
 57          "BLOCKED: Dangerous git add command detected!",
 58          "",
 59          "Commands like 'git add -A', 'git add --all', and 'git add .' can add unintended files.",
 60          "",
 61          "Use explicit file paths instead:",
 62          "  git add path/to/specific/file.txt",
 63          "  git add path/to/directory/",
 64          "",
 65          `Blocked command: ${cmd}`,
 66        ].join("\n"),
 67      })
 68    );
 69    process.exit(0);
 70  }
 71
 72  // ── git push safety ───────────────────────────────────────────
 73  if (isGitPush(cmd)) {
 74    if (!HAS_REFSPEC.test(cmd)) {
 75      process.stdout.write(
 76        JSON.stringify({
 77          decision: "block",
 78          reason: [
 79            "BLOCKED: git push without explicit refspec!",
 80            "",
 81            "Implicit branch tracking can push to wrong branches.",
 82            "",
 83            "Use explicit refspec instead:",
 84            "  git push origin <branch>:<branch>",
 85            "  git push origin HEAD:<branch>",
 86            "",
 87            `Blocked command: ${cmd}`,
 88          ].join("\n"),
 89        })
 90      );
 91      process.exit(0);
 92    }
 93
 94    if (pushesToProtected(cmd)) {
 95      process.stderr.write(
 96        "⚠️  Warning: pushing to protected branch (main/master)\n"
 97      );
 98    }
 99  }
100
101  process.exit(0);
102}
103
104main();