#!/bin/bash
# PreToolUse hook on Bash tool
# Blocks raw SQL operations against work tracker tables.
# Consuming projects customize via phases/work-tracker-guard.md
#
# Default: guards pib-db actions table
# Disable: phase file with "skip: true"

# Claude Code delivers the hook payload as JSON on stdin. The tool's
# input object is under `tool_input` (fall back to top-level for older
# payload shapes). NOTE: stdin is read ONCE — reuse $INPUT below.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); ti=d.get('tool_input', d); print(ti.get('command',''))" 2>/dev/null)

if [ -z "$COMMAND" ]; then
  exit 0
fi

# Check for phase file override
PHASE_FILE=".claude/skills/hooks/phases/work-tracker-guard.md"
if [ -f "$PHASE_FILE" ]; then
  FIRST_LINE=$(head -1 "$PHASE_FILE")
  if [ "$FIRST_LINE" = "skip: true" ]; then
    exit 0
  fi
fi

# The CLI complete-action arm (act:f9173885 — the FIFTH closure path, found by
# the §16 design checkpoint). `node scripts/pib-db.mjs complete-action <fid>`
# reaches completeAction with no gate: the completion gate is a PreToolUse
# hook matching only the MCP tool name, and the raw-SQL arm below matches SQL,
# not a CLI invocation. Since completeAction now stamps resolution='verified',
# an ungated CLI closure would stamp a verification that never ran (§16.1).
#
# This arm DELEGATES to action-completion-gate.sh — the sibling script that
# owns the breadcrumb predicate — by synthesizing the same stdin payload the
# MCP hook receives. One predicate, three callers (MCP hook, tracker server,
# this arm); a third restatement here would widen the stay-in-step problem
# act:b25a0e9a already names. No escape hatch, matching the MCP gate.
if echo "$COMMAND" | grep -qE 'pib-db(\.mjs)?["'"'"']?\s+complete-action\b'; then
  # Fired-at-least-once telemetry for THIS arm (act:ff693d4c): the gate script
  # logs its own firing, but a dead pattern here would be invisible without a
  # distinct record. Fail-open — telemetry never breaks the gate.
  FIRED_LOG="${CC_HOOK_FIRED_LOG:-${CLAUDE_PROJECT_DIR:-.}/.claude/state/hooks-fired.jsonl}"
  mkdir -p "$(dirname "$FIRED_LOG")" 2>/dev/null \
    && printf '{"hook":"work-tracker-guard/cli-complete-action","fired_at":"%s"}\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$FIRED_LOG" 2>/dev/null || true

  CA_FID=$(echo "$COMMAND" | grep -oE 'complete-action\s+["'"'"']?(act:[a-f0-9]{8})' | grep -oE 'act:[a-f0-9]{8}' | head -1)
  GATE_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/action-completion-gate.sh"
  if [ -z "$CA_FID" ]; then
    # No parseable fid: the CLI itself will refuse the malformed call; block
    # would teach nothing and allow risks nothing (completeAction validates).
    exit 0
  fi
  if [ ! -f "$GATE_SCRIPT" ]; then
    # The predicate owner is missing — fail CLOSED with a message that names
    # the repair, because failing open here silently reopens the fifth door.
    echo '{"decision":"block","reason":"CLI complete-action is gated by action-completion-gate.sh, but that script is missing beside this guard. Reinstall the hooks module (npx create-claude-cabinet --yes), or complete through the gated MCP tool pib_complete_action."}'
    exit 0
  fi
  GATE_OUT=$(printf '{"tool_input":{"fid":"%s"}}' "$CA_FID" | bash "$GATE_SCRIPT")
  if [ -n "$GATE_OUT" ]; then
    # The gate refused (its only output is a block decision) — pass its
    # verdict through verbatim so the CLI path teaches exactly what the MCP
    # path teaches.
    echo "$GATE_OUT"
  fi
  exit 0
fi

# Check for SQL operations against the guarded work-tracker tables.
#
# action_dependencies is here for the same reason `actions` is, and it was
# added the day the table shipped (act:d46f3b48): the dependency edge's ONLY
# integrity boundary is addDependency's cycle + dangling-target rejection, so a
# raw INSERT reaching the table writes a cycle that can never be started out of
# and that nothing will ever report. Leaving a second unguarded write path is
# exactly what act:4e0de399 refused to do one day earlier.
#
# projects and propagation_events joined the alternation with the propagation
# build (§16.2): `UPDATE projects SET status='done'` from Bash bypassed the
# kind gate, the open-children gate, and the ISO stamp — a third, shell-shaped
# closure path — and propagation_events is append-only history whose only
# legal writer is the walk.
#
# The table names are alternated explicitly rather than relying on a shared
# prefix: `\s+actions` does NOT match `action_dependencies` (the prefix is
# `action_`, not `actions`), and a bare `action` would over-match. \b keeps
# `actions_archive` and friends from matching by accident.
# REPLACE INTO / INSERT OR REPLACE INTO are covered too. That spelling is the
# natural upsert for a composite-PK table like action_dependencies, and for
# THAT table the guard is the only cycle and dangling-target boundary there is
# — so slipping through means a permanent silent wait, not merely a skipped
# quality gate.
if echo "$COMMAND" | grep -qiE '(INSERT(\s+OR\s+\w+)?\s+INTO|REPLACE\s+INTO|UPDATE|DELETE\s+FROM)\s+(actions|action_dependencies|projects|propagation_events)\b'; then
  # Override escape hatch — allow is the default, just exit 0
  if echo "$COMMAND" | grep -q '\-\-force-raw-sql'; then
    exit 0
  fi
  echo '{"decision":"block","reason":"Raw SQL against a work-tracker table (actions / action_dependencies / projects / propagation_events) detected. Use MCP tools instead: pib_create_action, pib_update_action, pib_complete_action, pib_get_action, pib_add_dependency, pib_remove_dependency, pib_create_project, pib_update_project, pib_complete_project. These enforce the quality, completion, kind, cycle and dangling-target gates. To override (almost certainly wrong): add --force-raw-sql to your command."}'
  exit 0
fi

exit 0
