feat(statusline): objetivo fijo (identificativo), solo el DoD se refina

Simplifica el modelo segun feedback:
- El OBJETIVO (target) es el identificativo de la terminal: se genera una vez y
  NUNCA cambia automaticamente. goal_refine deja de tocarlo.
- goal_refine ahora ajusta SOLO el DoD para mantenerlo coherente con los prompts.
- Se elimina la deteccion de cambio de tarea y el icono de alerta ⚠️ (campo alert
  ya no se escribe ni se lee; queda inocuo en JSONs antiguos).
- Se elimina el comando 'recalcular' y goal_refine.sh modo force.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 16:23:20 +02:00
parent a3ecb6a4cf
commit 50290a71e7
3 changed files with 31 additions and 76 deletions
+15 -44
View File
@@ -1,22 +1,13 @@
#!/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`).
# Ajusta SOLO el DoD para mantenerlo coherente con los prompts del usuario hacia
# el objetivo. El OBJETIVO es fijo (identificativo de la terminal) y NUNCA se
# toca aqui. Lo lanza goal_tracker.sh en background (no bloquea). Usa ask_llm
# (haiku, API directa; nunca `claude -p`, ver regla llm_invocation.md).
#
# Args: <session_id> <goal_json_file> [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.
# Args: <session_id> <goal_json_file>
SID="$1"
F="$2"
FORCE="$3"
[ -f "$F" ] || exit 0
PY="$HOME/fn_registry/python/.venv/bin/python3"
@@ -30,48 +21,28 @@ 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
SYS="Dado un OBJETIVO fijo de una terminal de trabajo y los prompts del usuario, define o ajusta el DoD (definition of done): la condicion concreta de 'terminado' para ese objetivo, coherente con lo que el usuario va pidiendo. El OBJETIVO no se toca, solo el DoD. Responde SOLO JSON en una linea, sin markdown: {\"dod\":\"...\"}. dod en espanol, breve, maximo 9 palabras. Si el DoD actual ya es adecuado, devuelvelo igual."
PROMPT="OBJETIVO ACTUAL: ${GOAL}
PROMPT="OBJETIVO (fijo, no lo cambies): ${GOAL}
DoD ACTUAL: ${DOD}
PROMPTS DEL USUARIO (orden cronologico):
${PROMPTS}
Responde el JSON:"
Responde el JSON con el DoD:"
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)
[ -z "$ND" ] && exit 0
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
TMP="${F}.tmp.$$"
if jq --arg d "$ND" '.dod=$d' "$F" > "$TMP" 2>/dev/null; then
mv "$TMP" "$F"
else
rm -f "$TMP"
fi
exit 0
+15 -25
View File
@@ -1,21 +1,20 @@
#!/bin/bash
# UserPromptSubmit hook del sistema de objetivo+fase por terminal.
#
# Comandos META que lee directamente de tu prompt. Estos se ejecutan FUERA DE
# BANDA: el hook hace su efecto y BLOQUEA el prompt (decision=block), asi el
# agente NO lo recibe ni responde — sigue idle con lo suyo. Solo ves una
# confirmacion breve.
# objetivo: <texto> fija el objetivo de la terminal (meta:/goal: equivalen).
# Modelo:
# - El OBJETIVO (target) es el IDENTIFICATIVO de la terminal: se genera una vez
# (del primer prompt, o a mano con "objetivo: ...") y NUNCA cambia solo.
# - El DoD SI se ajusta con tus prompts para reflejar la condicion de terminado.
# - La FASE la mantienen los hooks de fase: PostToolUse (activo) y Stop (reposo).
#
# Comandos META (se ejecutan FUERA DE BANDA: el hook hace su efecto y BLOQUEA el
# prompt con decision=block, asi el agente NO lo recibe ni responde; solo ves una
# confirmacion breve):
# objetivo: <texto> fija/cambia el objetivo a mano (meta:/goal: equivalen).
# objetivo: clear lo borra (tambien -, none, borrar, quitar, reset).
# dod: <texto> fija un Definition of Done corto.
# dod: <texto> fija un DoD a mano.
# dod: clear lo borra.
# recalcular regenera objetivo+DoD desde tus prompts y limpia alerta.
# pausa marca la fase en en_pausa (Ctrl-C no dispara hooks).
#
# Cualquier OTRO prompt es normal: pasa al agente, se acumula para refinar el
# objetivo y se inyecta el estado actual como contexto.
#
# 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)
@@ -28,7 +27,7 @@ PROMPT_TRIM=$(printf '%s' "$PROMPT" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$
# Bloquea el prompt (no llega al agente) y muestra <reason> al usuario.
block() { jq -n --arg r "$1" '{decision:"block", reason:$r}'; exit 0; }
# --- objetivo: <texto> (preserva el DoD si ya existia) ---
# --- objetivo: <texto> (manual; 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:]]+$//')
@@ -69,14 +68,6 @@ if [ -n "$DOD_LINE" ]; then
block "🏁 DoD fijado: ${NEWDOD}"
fi
# --- recalcular (regenera objetivo+DoD desde los prompts; limpia la alerta) ---
case "$PROMPT_TRIM" in
recalcular|recalcula|replantea|replantear|/recalcular)
[ -f "$F" ] || block "No hay objetivo que recalcular."
nohup bash "$HOME/.claude/hooks/goal_refine.sh" "$SID" "$F" force >/dev/null 2>&1 &
block "🔄 Recalculando objetivo+DoD desde tus prompts…" ;;
esac
# --- pausa (marca manual; Ctrl-C no dispara hooks en Claude Code) ---
case "$PROMPT_TRIM" in
pausa|pause|pausar|"en pausa"|/pausa)
@@ -88,13 +79,12 @@ case "$PROMPT_TRIM" in
block "⏸️ Fase marcada en pausa." ;;
esac
# --- prompt NORMAL: pasa al agente + acumula para refinar + inyecta estado ---
# --- prompt NORMAL: pasa al agente + acumula para refinar el DoD + estado ---
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)
# Acumular el prompt y refinar objetivo+DoD coherentes (background). Si es una
# tarea totalmente distinta, goal_refine marca alert=true (mezcla de tareas).
# Acumular el prompt y ajustar SOLO el DoD (background). El objetivo no cambia.
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
@@ -104,7 +94,7 @@ if [ -f "$F" ]; then
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 objetivo/DoD se autorefinan con tus prompts; si ves ⚠️ es que la terminal mezcla tareas. Comandos meta del usuario (no los uses tu): objetivo:/dod:/recalcular/pausa."
echo "GOAL-TRACKER: file=$F | goal=\"$G\" dod=\"$D\" phase=\"$P\". El objetivo es fijo (identificativo de la terminal, NO lo cambies). El DoD se ajusta solo con los prompts; la fase la mantienen los hooks (PostToolUse=activo, Stop=reposo) — NO la escribas. Comandos meta del usuario (no los uses tu): objetivo:/dod:/pausa."
else
# Sin objetivo: autogenerar objetivo + DoD desde el primer prompt sustantivo,
# en background (no bloquea). Se omite para prompts triviales (saludos, ok...).
+1 -7
View File
@@ -304,15 +304,9 @@ 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")
# ⚠️ 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
LEFT="${GC}🎯 ${GOAL}${RESET}"
LINE0="${LEFT}"