Files
repo_Claude/.claude/hooks/goal_phase_worker.sh
T
egutierrez f881b7703b feat(statusline): historial de estados + clasificacion al escribir el usuario
- statusline.sh: muestra los ultimos 7 estados previos como emojis atenuados
  (DIM) separados por │, entre el objetivo y la fase actual. El historial se
  guarda en el goal JSON (campo .history), colapsando estados consecutivos
  repetidos, hasta 12 entradas.
- goal_phase_worker.sh: dos modos. 'stop' (tras la respuesta del asistente, con
  filtro de trabajo real) y 'prompt' (tras el prompt del usuario, clasifica la
  intencion para feedback inmediato). Nuevo veredicto 'sin_cambio' para
  preguntas/charla que no implican cambio de actividad; ante la duda, no toca.
  Ambos modos mantienen el historial.
- goal_tracker.sh: en cada prompt con objetivo activo lanza el worker en modo
  prompt (background) ademas del Stop hook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 14:07:39 +02:00

122 lines
5.7 KiB
Bash
Executable File

#!/bin/bash
# Worker del sistema objetivo+fase. Clasifica la fase de la tarea con ask_llm
# (haiku, API directa; nunca `claude -p`, ver regla llm_invocation.md) y la
# escribe en el goal JSON manteniendo un historial de estados.
#
# Dos modos:
# stop (default): se lanza tras una respuesta del asistente. Lee el ultimo
# turno del transcript y SOLO reevalua si hubo trabajo real (tool_use);
# en turnos de charla pura no toca nada.
# prompt : se lanza tras un prompt del usuario, para feedback inmediato. Usa la
# intencion del prompt. Si el prompt es charla/pregunta y no implica
# avanzar la tarea, el modelo responde sin_cambio y no se toca nada.
#
# Args: <session_id> <transcript_path> <goal_json> [mode] [prompt_text]
SID="$1"
TRANSCRIPT="$2"
F="$3"
MODE="${4:-stop}"
PROMPT_ARG="$5"
PY="$HOME/fn_registry/python/.venv/bin/python3"
ASK="$HOME/fn_registry/python/functions/core/ask_llm.py"
[ -x "$PY" ] || exit 0
[ -f "$ASK" ] || exit 0
[ -f "$F" ] || exit 0
GOAL=$(jq -r '.goal // ""' "$F" 2>/dev/null)
[ -z "$GOAL" ] && exit 0
USER_MSG=""
LAST=""
if [ "$MODE" = "prompt" ]; then
# Modo prompt: clasifica la intencion del mensaje recien enviado.
USER_MSG="$PROMPT_ARG"
[ -z "$USER_MSG" ] && exit 0
LAST="(el usuario acaba de enviar este mensaje; aun no hay respuesta del asistente)"
else
# Modo stop: una sola pasada de abajo a arriba sobre el turno actual.
# Captura la ultima respuesta de texto del asistente + detecta trabajo real.
[ -f "$TRANSCRIPT" ] || exit 0
HAS_WORK=0
while IFS= read -r line; do
t=$(printf '%s' "$line" | jq -r '.type // empty' 2>/dev/null)
if [ "$t" = "assistant" ]; then
if [ -z "$LAST" ]; then
txt=$(printf '%s' "$line" | jq -r '(.message.content // [])[]? | select(.type=="text") | .text' 2>/dev/null)
[ -n "$txt" ] && LAST="$txt"
fi
if printf '%s' "$line" | jq -e '(.message.content // [])[]? | select(.type=="tool_use")' >/dev/null 2>&1; then
HAS_WORK=1
fi
elif [ "$t" = "user" ]; then
ctype=$(printf '%s' "$line" | jq -r '.message.content | type' 2>/dev/null)
if [ "$ctype" = "string" ]; then
USER_MSG=$(printf '%s' "$line" | jq -r '.message.content' 2>/dev/null)
break
fi
if ! printf '%s' "$line" | jq -e '(.message.content // [])[]? | select(.type=="tool_result")' >/dev/null 2>&1; then
USER_MSG=$(printf '%s' "$line" | jq -r '(.message.content // [])[]? | select(.type=="text") | .text' 2>/dev/null)
break
fi
fi
done < <(tac "$TRANSCRIPT")
# Charla pura (sin trabajo en el turno): no tocar la fase.
[ "$HAS_WORK" = "0" ] && exit 0
LAST=$(printf '%s' "$LAST" | tail -c 4000)
[ -z "$LAST" ] && exit 0
fi
USER_MSG=$(printf '%s' "$USER_MSG" | tail -c 1500)
SYS="Eres un clasificador de fase de tarea. Recibes el objetivo, la ULTIMA PETICION DEL USUARIO (marca la intencion) y la ULTIMA RESPUESTA DEL ASISTENTE. Clasifica la actividad actual con UNA sola palabra de esta lista exacta, sin nada mas: investigando planificando haciendo testeando puliendo iterando pendiente_revision bloqueado hecho sin_cambio. Definiciones: investigando=explorando o leyendo ACTIVAMENTE codigo/archivos del proyecto como paso para avanzar la tarea (no una simple pregunta conceptual); planificando=disenando el enfoque sin ejecutar todavia; haciendo=implementando cambios; testeando=corriendo tests, probando o validando tecnicamente (si piden 'testea/prueba/valida', es testeando); puliendo=retoques finales sobre algo ya casi listo; iterando=ciclo continuo de ajustes pequenos; pendiente_revision=el asistente espera que el humano revise o decida algo importante; bloqueado=no puede avanzar por un error o falta de informacion; hecho=el objetivo esta claramente terminado y verificado; sin_cambio=la peticion NO implica un cambio de actividad: preguntas, peticiones de explicacion o aclaracion conceptual, comentarios, opiniones o dudas que no ordenan avanzar, modificar o probar la tarea. La peticion del usuario pesa: refleja que se pidio hacer. Usa 'hecho' SOLO si el trabajo esta completo y confirmado. Ante la duda entre una fase concreta y sin_cambio, prefiere sin_cambio."
PROMPT="OBJETIVO DE LA TAREA: ${GOAL}
ULTIMA PETICION DEL USUARIO:
${USER_MSG}
ULTIMA RESPUESTA DEL ASISTENTE:
${LAST}
Responde la fase actual (una sola palabra de la lista, o sin_cambio):"
RAW=$("$PY" "$ASK" --model claude-haiku-4-5-20251001 --system "$SYS" "$PROMPT" 2>/dev/null | tr '[:upper:]' '[:lower:]')
[ -z "$RAW" ] && exit 0
# Normalizar la respuesta a un slug canonico (tolerante a verbosidad/acentos).
case "$RAW" in
*sin_cambio*|*sincambio*|*ninguna*|*charla*) exit 0 ;;
*pendiente*revis*|*revis*) PHASE=pendiente_revision ;;
*investig*) PHASE=investigando ;;
*planific*) PHASE=planificando ;;
*test*) PHASE=testeando ;;
*puli*) PHASE=puliendo ;;
*iter*) PHASE=iterando ;;
*bloque*) PHASE=bloqueado ;;
*hecho*|*complet*|*termin*|*done*) PHASE=hecho ;;
*hacien*|*implement*) PHASE=haciendo ;;
*) exit 0 ;;
esac
# Escribir la fase + mantener el historial (append solo si cambia respecto al
# ultimo, para no llenar de repetidos; se conservan los ultimos 12 estados).
TMP="${F}.tmp.$$"
if jq --arg p "$PHASE" '
.phase = $p
| .history = (
( .history // [] ) as $h
| ( if ($h | length) > 0 and ($h[-1] == $p) then $h else ($h + [$p]) end )
| .[-12:]
)
' "$F" > "$TMP" 2>/dev/null; then
mv "$TMP" "$F"
else
rm -f "$TMP"
fi
exit 0