From 3ae472d1f362fa452821f9c40df8e4e77a16335a Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 6 Jun 2026 15:26:51 +0200 Subject: [PATCH] feat(statusline): estado 'preguntando', DoD junto al objetivo y comando pausa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nuevo estado de reposo 'preguntando' (❓ esperando respuesta), distinto de pendiente_revision: lo usa el Stop worker cuando la respuesta termina con preguntas concretas al usuario en vez de dejar un resultado para revisar. - DoD corto opcional junto al objetivo: se fija con "dod: " ("dod: clear" lo borra) y se muestra atenuado con 🏁 tras el objetivo. Re-fijar el objetivo preserva el DoD existente. - Comando "pausa" (prompt) marca la fase en en_pausa. Es la alternativa manual a Ctrl-C: Claude Code no dispara ningun hook al interrumpir un turno (el Stop hook solo corre en finalizacion normal; feature pedido, sin implementar), asi que no es posible detectar la interrupcion automaticamente. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/hooks/goal_phase_worker.sh | 7 +-- .claude/hooks/goal_tracker.sh | 72 ++++++++++++++++++++++-------- .claude/statusline.sh | 7 ++- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/.claude/hooks/goal_phase_worker.sh b/.claude/hooks/goal_phase_worker.sh index 5d09842..4c227f6 100755 --- a/.claude/hooks/goal_phase_worker.sh +++ b/.claude/hooks/goal_phase_worker.sh @@ -72,7 +72,7 @@ LAST=$(printf '%s' "$LAST" | tail -c 4000) [ -z "$LAST" ] && exit 0 USER_MSG=$(printf '%s' "$USER_MSG" | tail -c 1500) -SYS="El asistente acaba de PARAR y cede el control al usuario. Clasifica el estado de REPOSO en que queda la tarea. Responde UNA sola palabra, sin nada mas, de: hecho pendiente_revision bloqueado en_pausa sin_cambio. hecho=el objetivo esta completo y verificado; pendiente_revision=el asistente espera que el humano revise un resultado o decida algo importante; bloqueado=no puede avanzar por un error o por falta de informacion del usuario; en_pausa=hizo un avance y espera la siguiente indicacion, sin estar terminado ni bloqueado ni a la espera de revision; sin_cambio=el turno no altera el estado de reposo actual (charla irrelevante). Usa 'hecho' SOLO si el trabajo esta completo y confirmado, nunca si queda algo pendiente." +SYS="El asistente acaba de PARAR y cede el control al usuario. Clasifica el estado de REPOSO en que queda la tarea. Responde UNA sola palabra, sin nada mas, de: hecho pendiente_revision preguntando bloqueado en_pausa sin_cambio. hecho=el objetivo esta completo y verificado; pendiente_revision=el asistente termino un trabajo y espera que el humano lo revise o apruebe (no hace una pregunta directa); preguntando=el asistente termina formulando una o varias PREGUNTAS concretas al usuario y necesita su respuesta o decision para continuar; bloqueado=no puede avanzar por un error o por falta de informacion/acceso; en_pausa=hizo un avance y espera la siguiente indicacion, sin estar terminado ni preguntar ni bloqueado; sin_cambio=el turno no altera el estado de reposo actual (charla irrelevante). Distingue: si la respuesta acaba con preguntas al usuario es 'preguntando'; si deja un resultado para que lo mire es 'pendiente_revision'. Usa 'hecho' SOLO si el trabajo esta completo y confirmado." PROMPT="OBJETIVO DE LA TAREA: ${GOAL} @@ -89,10 +89,11 @@ RAW=$("$PY" "$ASK" --model claude-haiku-4-5-20251001 --system "$SYS" "$PROMPT" 2 case "$RAW" in *sin_cambio*|*sincambio*|*ninguna*|*charla*) exit 0 ;; - *pendiente*revis*|*revis*) PHASE=pendiente_revision ;; + *pregunt*|*consulta*|*respuesta*) PHASE=preguntando ;; + *pendiente*revis*|*revis*|*aprob*) PHASE=pendiente_revision ;; *bloque*) PHASE=bloqueado ;; *hecho*|*complet*|*termin*|*done*) PHASE=hecho ;; - *pausa*|*pause*|*espera*|*siguiente*) PHASE=en_pausa ;; + *pausa*|*pause*|*siguiente*) PHASE=en_pausa ;; # Si por error devuelve un estado activo al parar, lo tratamos como pausa. investigando|planificando|haciendo|testeando|puliendo) PHASE=en_pausa ;; *) exit 0 ;; diff --git a/.claude/hooks/goal_tracker.sh b/.claude/hooks/goal_tracker.sh index 1bd327a..7d3dbb3 100755 --- a/.claude/hooks/goal_tracker.sh +++ b/.claude/hooks/goal_tracker.sh @@ -1,14 +1,17 @@ #!/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. +# 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 no la toca este hook: la mantiene el Stop hook (goal_phase_eval.sh). +# 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) @@ -16,41 +19,72 @@ SID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null) 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:]]+$//') -# 1) Fijar/limpiar el objetivo leyendo el prompt del usuario. +# --- 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. El statusline deja de mostrar la linea de objetivo." - exit 0 - ;; + 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" + PH="planificando"; DD="" fi TMP="${F}.tmp.$$" - if jq -n --arg g "$NEWGOAL" --arg p "$PH" '{goal:$g, phase:$p}' > "$TMP" 2>/dev/null; then + 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\" (phase=$PH). El statusline ya lo muestra; la fase la mantiene el Stop hook automaticamente." + echo "GOAL-TRACKER: objetivo fijado desde tu prompt -> \"$NEWGOAL\"." exit 0 fi -# 2) Informativo: estado actual para el modelo. -# La fase la mantienen los hooks automaticamente: PostToolUse marca el estado -# ACTIVO segun las herramientas usadas; el Stop hook resuelve el REPOSO con haiku. +# --- 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) - echo "GOAL-TRACKER: file=$F | goal=\"$G\" phase=\"$P\". La fase la mantienen los hooks automaticamente (PostToolUse = activo, Stop = reposo) β€” NO escribas la fase. El usuario fija el objetivo escribiendo \"objetivo: \"; si redefine la tarea en lenguaje natural, actualiza \"goal\" en ese JSON." + 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 de la terminal escribiendo \"objetivo: \" (lo captura este hook directo de su prompt). Si describe una tarea clara sin ese prefijo, crea {\"goal\":\"\",\"phase\":\"planificando\"} leyendo su prompt. Sin objetivo, ignora." + 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 diff --git a/.claude/statusline.sh b/.claude/statusline.sh index 6b1d698..03220e9 100755 --- a/.claude/statusline.sh +++ b/.claude/statusline.sh @@ -128,6 +128,7 @@ phase_style() { testeando) printf 'πŸ§ͺ|35|testeando' ;; puliendo) printf '✨|95|puliendo detalles' ;; pendiente_revision) printf 'πŸ‘€|93|pendiente de revisiΓ³n' ;; + preguntando) printf '❓|96|esperando respuesta' ;; bloqueado) printf 'β›”|31|bloqueado' ;; en_pausa) printf '⏸️|90|en pausa' ;; hecho) printf 'βœ…|32|hecho' ;; @@ -302,10 +303,12 @@ GOAL_FILE="$HOME/.claude/goals/${SESSION_ID}.json" if [ -n "$SESSION_ID" ] && [ -f "$GOAL_FILE" ]; then GOAL=$(jq -r '.goal // ""' "$GOAL_FILE" 2>/dev/null) PHASE=$(jq -r '.phase // ""' "$GOAL_FILE" 2>/dev/null) + DOD=$(jq -r '.dod // ""' "$GOAL_FILE" 2>/dev/null) if [ -n "$GOAL" ]; then GC=$(goal_color "$SESSION_ID") - LEFT_PLAIN="🎯 ${GOAL}" - LEFT="${GC}${LEFT_PLAIN}${RESET}" + LEFT="${GC}🎯 ${GOAL}${RESET}" + # DoD corto junto al objetivo, atenuado (🏁 = cuando esta hecho). + [ -n "$DOD" ] && LEFT="${LEFT} ${DIM}🏁 ${DOD}${RESET}" LINE0="${LEFT}"