#!/usr/bin/env bash # PreToolUse su Write/Edit — vincolo Data Safety (LOCKED) di ../../CLAUDE.md. # # Blocca la scrittura di una migration che cancella dati dalle entita' protette. # Non e' una prova: e' una rete. Un DROP scritto in modo esotico puo' passare — # la revisione a occhio dell'SQL resta obbligatoria. # # exit 0 = passa · exit 2 = bloccato, il messaggio su stderr torna a Claude. set -uo pipefail INPUT=$(cat) FILE=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty') # Fuori da src/db/migrations/ questo hook non ha voce in capitolo. case "$FILE" in *src/db/migrations/*) ;; *) exit 0 ;; esac # Write porta `content`, Edit porta `new_string`. MultiEdit porta un array. SQL=$(printf '%s' "$INPUT" | jq -r ' [ .tool_input.content?, .tool_input.new_string?, (.tool_input.edits? // [] | .[].new_string?) ] | map(select(. != null)) | join("\n") ') [ -z "$SQL" ] && exit 0 PROTETTE='clients|projects|payments|phases' # Via i commenti, tutto minuscolo, una riga per statement: cosi' "DROP" e il nome # della tabella devono stare nella STESSA istruzione per far scattare il blocco. PULITO=$(printf '%s' "$SQL" \ | sed -E 's/--.*$//' \ | tr '\n' ' ' \ | sed -E 's;/\*[^*]*\*+([^/*][^*]*\*+)*/; ;g' \ | tr '[:upper:]' '[:lower:]' \ | tr ';' '\n') COLPEVOLI=$(printf '%s\n' "$PULITO" \ | grep -E 'drop[[:space:]]+table|drop[[:space:]]+column|truncate|delete[[:space:]]+from' \ | grep -E "\\b($PROTETTE)\\b" || true) if [ -n "$COLPEVOLI" ]; then { echo "BLOCCATO — Data Safety (LOCKED, ../CLAUDE.md)." echo echo "Questa migration cancella dati da un'entita' protetta (clients, projects," echo "payments, phases). Le migration sono additive: si aggiungono colonne e" echo "tabelle, non si tolgono righe." echo echo "Istruzioni incriminate:" printf '%s\n' "$COLPEVOLI" | sed 's/^[[:space:]]*/ · /' echo echo "Se la rimozione serve davvero, e' una decisione da confermare a voce con" echo "l'utente prima di scriverla — non da aggirare qui." } >&2 exit 2 fi exit 0