feat(statusline): seguimiento de objetivo + fase por terminal

Cada terminal muestra su objetivo (color estable por session_id) y la fase de
trabajo actual, para distinguir sesiones y saber cuando algo esta hecho.

- statusline.sh: linea 0 con objetivo (izq, color por sesion) + fase (der) con
  el separador estandar; 9 fases (investigando, planificando, haciendo,
  testeando, puliendo, iterando, pendiente_revision, bloqueado, hecho) con icono,
  color y etiqueta. Purga de goal files de sesiones muertas (>7 dias).
- hooks/goal_tracker.sh (UserPromptSubmit): fija el objetivo leyendo el prompt
  del usuario ("objetivo: ...", "objetivo: clear" lo borra); si no, informa el
  estado actual al modelo.
- hooks/goal_phase_eval.sh (Stop): al terminar el turno lanza el worker en
  background, sin bloquear.
- hooks/goal_phase_worker.sh: clasifica la fase con ask_llm (haiku, API directa,
  nunca claude -p) usando la peticion del usuario + la ultima respuesta del
  asistente. Solo reevalua si el turno tuvo trabajo real (tool_use); en charla
  pura no toca la fase ni gasta llamada.
- settings.json: registra los hooks UserPromptSubmit y Stop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 14:01:05 +02:00
parent 71874079cd
commit 5efcedf9ba
5 changed files with 254 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# UserPromptSubmit hook del sistema de objetivo+fase por terminal.
#
# Dos funciones:
# 1) Si tu prompt empieza por "objetivo:" (o "meta:" / "goal:"), fija el objetivo
# de esta terminal leyendolo DIRECTAMENTE de tu prompt, sin intervencion del
# modelo. "objetivo: clear" (o -, none, borrar, quitar, reset) lo elimina.
# 2) En cualquier otro caso, inyecta el estado actual (goal+phase) como contexto
# para que el modelo sepa donde esta el archivo y que objetivo hay vigente.
#
# La FASE no la toca este hook: la mantiene el Stop hook (goal_phase_eval.sh).
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)
# 1) Fijar/limpiar el objetivo leyendo el prompt del usuario.
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. El statusline deja de mostrar la linea de objetivo."
exit 0
;;
esac
if [ -f "$F" ]; then
PH=$(jq -r '.phase // "planificando"' "$F" 2>/dev/null)
else
PH="planificando"
fi
TMP="${F}.tmp.$$"
if jq -n --arg g "$NEWGOAL" --arg p "$PH" '{goal:$g, phase:$p}' > "$TMP" 2>/dev/null; then
mv "$TMP" "$F"
else
rm -f "$TMP"
fi
echo "GOAL-TRACKER: objetivo fijado desde tu prompt -> \"$NEWGOAL\" (phase=$PH). El statusline ya lo muestra; la fase la mantiene el Stop hook automaticamente."
exit 0
fi
# 2) 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)
echo "GOAL-TRACKER: file=$F | goal=\"$G\" phase=\"$P\". La fase la mantiene el Stop hook automaticamente — NO escribas la fase. El usuario fija el objetivo escribiendo \"objetivo: <texto>\". Si redefine la tarea en lenguaje natural sin ese prefijo, actualiza \"goal\" en ese JSON leyendo su prompt."
else
echo "GOAL-TRACKER: file=$F (sin objetivo aun). El usuario fija el objetivo de la terminal escribiendo \"objetivo: <texto>\" (lo captura este hook directo de su prompt). Si describe una tarea clara sin ese prefijo, crea {\"goal\":\"<su objetivo>\",\"phase\":\"planificando\"} leyendo su prompt. Sin objetivo, ignora."
fi
exit 0