From 1840402453f905f186ae8a3817575943c524e74d Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 6 Jun 2026 16:01:15 +0200 Subject: [PATCH] feat(statusline): objetivo+DoD coherentes con los prompts + alerta de mezcla de tareas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El objetivo y el DoD dejan de quedarse congelados en el primer prompt: - goal_tracker acumula cada prompt sustantivo del usuario en .prompts y lanza goal_refine.sh (background, haiku) para mantener objetivo y DoD coherentes con TODO lo pedido (action refine), o dejarlos igual (action same). - goal_refine marca alert=true (action switch) cuando el ultimo prompt introduce una tarea completamente distinta del objetivo: senal de que la terminal mezcla tareas (principio: una terminal = una tarea). No cambia el objetivo, solo avisa. - statusline muestra ⚠️ en rojo antes del objetivo cuando alert=true. - Comando 'recalcular' (recalcula/replantea): fuerza regenerar objetivo+DoD desde los prompts y limpia la alerta (para cuando el cambio de tarea es intencional). - goal_autogen inicializa .prompts con el primer prompt. Coste: 1 haiku/prompt sustantivo en background (ademas del haiku de reposo del Stop), solicitado para mantener la coherencia. No bloquea el turno. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/hooks/goal_autogen.sh | 4 +- .claude/hooks/goal_refine.sh | 77 +++++++++++++++++++++++++++++++++++ .claude/hooks/goal_tracker.sh | 23 ++++++++++- .claude/statusline.sh | 8 +++- 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100755 .claude/hooks/goal_refine.sh diff --git a/.claude/hooks/goal_autogen.sh b/.claude/hooks/goal_autogen.sh index 1a7e5f3..d9e8990 100755 --- a/.claude/hooks/goal_autogen.sh +++ b/.claude/hooks/goal_autogen.sh @@ -37,8 +37,8 @@ DOD=$(printf '%s' "$JSON" | jq -r '.dod // ""' 2>/dev/null) [ -f "$F" ] && exit 0 TMP="${F}.tmp.$$" -if jq -n --arg g "$GOAL" --arg d "$DOD" \ - '{goal:$g, phase:"planificando", history:["planificando"]} | if $d != "" then .dod=$d else . end' > "$TMP" 2>/dev/null; then +if jq -n --arg g "$GOAL" --arg d "$DOD" --arg p "$P" \ + '{goal:$g, phase:"planificando", history:["planificando"], prompts:[$p]} | if $d != "" then .dod=$d else . end' > "$TMP" 2>/dev/null; then mv "$TMP" "$F" else rm -f "$TMP" diff --git a/.claude/hooks/goal_refine.sh b/.claude/hooks/goal_refine.sh new file mode 100755 index 0000000..25a5062 --- /dev/null +++ b/.claude/hooks/goal_refine.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# Mantiene objetivo+DoD coherentes con los prompts acumulados del usuario y avisa +# si la terminal empieza a mezclar tareas. Lo lanza goal_tracker.sh en background +# (no bloquea). Usa ask_llm (haiku, API directa; nunca `claude -p`). +# +# Args: [force] +# force: regenera siempre objetivo+DoD desde los prompts y limpia la alerta +# (lo usa el comando "recalcular"). +# +# Decision normal (sin force): el modelo devuelve +# {"action":"same|refine|switch","goal":"...","dod":"..."} +# - same : el objetivo ya describe bien el conjunto -> no se toca nada. +# - refine : ajusta objetivo/DoD para englobar mejor TODO lo pedido (estable). +# - switch : el ultimo prompt es una tarea COMPLETAMENTE distinta -> alert=true +# (una terminal deberia ser una sola tarea). No cambia el objetivo. + +SID="$1" +F="$2" +FORCE="$3" + +[ -f "$F" ] || exit 0 +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 + +GOAL=$(jq -r '.goal // ""' "$F" 2>/dev/null) +[ -z "$GOAL" ] && exit 0 +DOD=$(jq -r '.dod // ""' "$F" 2>/dev/null) +PROMPTS=$(jq -r '(.prompts // []) | to_entries | map("\(.key+1). \(.value)") | join("\n")' "$F" 2>/dev/null) +[ -z "$PROMPTS" ] && exit 0 + +if [ "$FORCE" = "force" ]; then + SYS="Regenera el OBJETIVO y el DoD de una terminal de trabajo a partir de la lista de prompts del usuario, sintetizando lo que se esta haciendo realmente. Responde SOLO JSON en una linea, sin markdown: {\"action\":\"refine\",\"goal\":\"...\",\"dod\":\"...\"}. goal: maximo 9 palabras en espanol. dod: condicion concreta de terminado, maximo 9 palabras en espanol." +else + SYS="Mantienes coherentes el OBJETIVO y el DoD de una terminal de trabajo segun los prompts del usuario. Principio: una terminal = una sola tarea. Analiza si el conjunto de prompts sigue describiendo UNA tarea coherente. Responde SOLO JSON en una linea, sin markdown: {\"action\":\"same|refine|switch\",\"goal\":\"...\",\"dod\":\"...\"}. action=same: el objetivo actual ya describe bien el conjunto (deja goal y dod vacios, no se cambia nada). action=refine: conviene ajustar objetivo/DoD para englobar mejor TODO lo pedido, manteniendolo estable y breve (goal y dod max 9 palabras cada uno, en espanol). action=switch: el ULTIMO prompt introduce una tarea COMPLETAMENTE distinta y no relacionada con el objetivo actual (la terminal esta mezclando tareas); deja goal y dod vacios. Se conservador con 'switch': uselo solo ante un cambio claro de tema, no por matices o sub-tareas del mismo objetivo." +fi + +PROMPT="OBJETIVO ACTUAL: ${GOAL} +DoD ACTUAL: ${DOD} + +PROMPTS DEL USUARIO (orden cronologico): +${PROMPTS} + +Responde el JSON:" + +RAW=$("$PY" "$ASK" --system "$SYS" "$PROMPT" 2>/dev/null) +[ -z "$RAW" ] && exit 0 +JSON=$(printf '%s' "$RAW" | tr '\n' ' ' | grep -o '{[^{}]*}' | head -1) +[ -z "$JSON" ] && exit 0 + +ACTION=$(printf '%s' "$JSON" | jq -r '.action // ""' 2>/dev/null) +NG=$(printf '%s' "$JSON" | jq -r '.goal // ""' 2>/dev/null) +ND=$(printf '%s' "$JSON" | jq -r '.dod // ""' 2>/dev/null) + +case "$ACTION" in + refine) + TMP="${F}.tmp.$$" + if jq --arg g "$NG" --arg d "$ND" ' + (if $g != "" then .goal=$g else . end) + | (if $d != "" then .dod=$d else . end) + | .alert=false + ' "$F" > "$TMP" 2>/dev/null; then + mv "$TMP" "$F" + else + rm -f "$TMP" + fi + ;; + switch) + TMP="${F}.tmp.$$" + if jq '.alert=true' "$F" > "$TMP" 2>/dev/null; then mv "$TMP" "$F"; else rm -f "$TMP"; fi + ;; + *) + : # same / desconocido -> no tocar + ;; +esac +exit 0 diff --git a/.claude/hooks/goal_tracker.sh b/.claude/hooks/goal_tracker.sh index 84e2cb9..ab36c7a 100755 --- a/.claude/hooks/goal_tracker.sh +++ b/.claude/hooks/goal_tracker.sh @@ -66,6 +66,15 @@ if [ -n "$DOD_LINE" ]; then exit 0 fi +# --- recalcular (regenera objetivo+DoD desde los prompts; limpia la alerta) --- +case "$PROMPT_TRIM" in + recalcular|recalcula|replantea|replantear|/recalcular) + [ -f "$F" ] || { echo "GOAL-TRACKER: no hay objetivo que recalcular."; exit 0; } + nohup bash "$HOME/.claude/hooks/goal_refine.sh" "$SID" "$F" force >/dev/null 2>&1 & + echo "GOAL-TRACKER: recalculando objetivo+DoD desde tus prompts (background)." + exit 0 ;; +esac + # --- pausa (marca manual; Ctrl-C no dispara hooks en Claude Code) --- case "$PROMPT_TRIM" in pausa|pause|pausar|"en pausa"|/pausa) @@ -83,7 +92,19 @@ 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." + # Acumular el prompt y refinar objetivo+DoD para mantenerlos coherentes con + # todo lo pedido (background). Si el prompt es una tarea totalmente distinta, + # goal_refine marca alert=true (la terminal mezcla tareas). + if [ "${#PROMPT_TRIM}" -ge 12 ]; then + TMP="${F}.tmp.$$" + if jq --arg p "$PROMPT_TRIM" '.prompts = ((.prompts // []) + [$p])[-12:]' "$F" > "$TMP" 2>/dev/null; then + mv "$TMP" "$F" + else + rm -f "$TMP" + fi + nohup bash "$HOME/.claude/hooks/goal_refine.sh" "$SID" "$F" >/dev/null 2>&1 & + fi + 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. El objetivo/DoD se autorefinan con tus prompts; si ves ⚠️ es que la terminal esta mezclando tareas." else # Sin objetivo: autogenerar objetivo + DoD desde el primer prompt sustantivo, # en background (no bloquea). Se omite para prompts triviales (saludos, ok...). diff --git a/.claude/statusline.sh b/.claude/statusline.sh index 76e1913..e1cd5bc 100755 --- a/.claude/statusline.sh +++ b/.claude/statusline.sh @@ -304,9 +304,15 @@ 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) + ALERT=$(jq -r '.alert // false' "$GOAL_FILE" 2>/dev/null) if [ -n "$GOAL" ]; then GC=$(goal_color "$SESSION_ID") - LEFT="${GC}🎯 ${GOAL}${RESET}" + # ⚠️ si la terminal esta mezclando tareas (objetivo cambio por completo). + if [ "$ALERT" = "true" ]; then + LEFT="\033[1;31m⚠️\033[0m ${GC}🎯 ${GOAL}${RESET}" + else + LEFT="${GC}🎯 ${GOAL}${RESET}" + fi LINE0="${LEFT}"