#!/bin/bash # UserPromptSubmit hook del sistema de objetivo+fase por terminal. # # Comandos que lee directamente de tu prompt (sin intervencion del modelo): # objetivo: fija el objetivo de la terminal (meta:/goal: equivalen). # objetivo: clear lo borra (tambien -, none, borrar, quitar, reset). # dod: fija un Definition of Done corto (se muestra junto al objetivo). # dod: clear lo borra. # pausa marca la fase en en_pausa. Util tras interrumpir con Ctrl-C, # que en Claude Code no dispara ningun hook (no hay forma de # detectar la interrupcion automaticamente). # En cualquier otro caso, inyecta el estado actual como contexto para el modelo. # # La FASE la mantienen los hooks de fase: PostToolUse (activo) y Stop (reposo). INPUT=$(cat) SID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null) [ -z "$SID" ] && exit 0 F="$HOME/.claude/goals/${SID}.json" PROMPT=$(printf '%s' "$INPUT" | jq -r '.prompt // ""' 2>/dev/null) PROMPT_TRIM=$(printf '%s' "$PROMPT" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//') # --- objetivo: (preserva el DoD si ya existia) --- GOAL_LINE=$(printf '%s' "$PROMPT" | grep -ioE '^[[:space:]]*(objetivo|meta|goal)[[:space:]]*:[[:space:]]*.+' | head -1) if [ -n "$GOAL_LINE" ]; then NEWGOAL=$(printf '%s' "$GOAL_LINE" | sed -E 's/^[^:]*:[[:space:]]*//; s/[[:space:]]+$//') case "$NEWGOAL" in -|clear|none|borrar|quitar|reset) rm -f "$F" echo "GOAL-TRACKER: objetivo de esta terminal borrado." exit 0 ;; esac if [ -f "$F" ]; then PH=$(jq -r '.phase // "planificando"' "$F" 2>/dev/null) DD=$(jq -r '.dod // ""' "$F" 2>/dev/null) else PH="planificando"; DD="" fi TMP="${F}.tmp.$$" if jq -n --arg g "$NEWGOAL" --arg p "$PH" --arg d "$DD" \ '{goal:$g, phase:$p} | if $d != "" then .dod=$d else . end' > "$TMP" 2>/dev/null; then mv "$TMP" "$F" else rm -f "$TMP" fi echo "GOAL-TRACKER: objetivo fijado desde tu prompt -> \"$NEWGOAL\"." exit 0 fi # --- dod: --- DOD_LINE=$(printf '%s' "$PROMPT" | grep -ioE '^[[:space:]]*dod[[:space:]]*:[[:space:]]*.+' | head -1) if [ -n "$DOD_LINE" ]; then NEWDOD=$(printf '%s' "$DOD_LINE" | sed -E 's/^[^:]*:[[:space:]]*//; s/[[:space:]]+$//') [ -f "$F" ] || { echo "GOAL-TRACKER: fija primero un objetivo (\"objetivo: ...\") antes del DoD."; exit 0; } case "$NEWDOD" in -|clear|none|borrar|quitar|reset) TMP="${F}.tmp.$$" jq 'del(.dod)' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F" echo "GOAL-TRACKER: DoD borrado." exit 0 ;; esac TMP="${F}.tmp.$$" if jq --arg d "$NEWDOD" '.dod=$d' "$F" > "$TMP" 2>/dev/null; then mv "$TMP" "$F"; else rm -f "$TMP"; fi echo "GOAL-TRACKER: DoD fijado -> \"$NEWDOD\"." exit 0 fi # --- pausa (marca manual; Ctrl-C no dispara hooks en Claude Code) --- case "$PROMPT_TRIM" in pausa|pause|pausar|"en pausa"|/pausa) [ -f "$F" ] || exit 0 TMP="${F}.tmp.$$" if jq '.phase="en_pausa" | .history=((.history // [])+["en_pausa"])[-12:]' "$F" > "$TMP" 2>/dev/null; then mv "$TMP" "$F" fi echo "GOAL-TRACKER: fase marcada en_pausa." exit 0 ;; esac # --- informativo: estado actual para el modelo --- if [ -f "$F" ]; then G=$(jq -r '.goal // ""' "$F" 2>/dev/null) P=$(jq -r '.phase // ""' "$F" 2>/dev/null) D=$(jq -r '.dod // ""' "$F" 2>/dev/null) echo "GOAL-TRACKER: file=$F | goal=\"$G\" dod=\"$D\" phase=\"$P\". La fase la mantienen los hooks (PostToolUse=activo, Stop=reposo) — NO la escribas. El usuario fija objetivo con \"objetivo: ...\" y un DoD corto con \"dod: ...\"; si redefine la tarea en lenguaje natural, actualiza \"goal\" en ese JSON." else echo "GOAL-TRACKER: file=$F (sin objetivo aun). El usuario fija el objetivo con \"objetivo: \" y opcionalmente un DoD con \"dod: \". Si describe una tarea clara sin prefijo, crea {\"goal\":\"\",\"phase\":\"planificando\"} leyendo su prompt. Sin objetivo, ignora." fi exit 0