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 #!/bin/bash
# Mantiene objetivo+DoD coherentes con los prompts acumulados del usuario y avisa # Ajusta SOLO el DoD para mantenerlo coherente con los prompts del usuario hacia
# si la terminal empieza a mezclar tareas. Lo lanza goal_tracker.sh en background # el objetivo. El OBJETIVO es fijo (identificativo de la terminal) y NUNCA se
# (no bloquea). Usa ask_llm (haiku, API directa; nunca `claude -p`). # 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] # Args: <session_id> <goal_json_file>
# 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" SID="$1"
F="$2" F="$2"
FORCE="$3"
[ -f "$F" ] || exit 0 [ -f "$F" ] || exit 0
PY="$HOME/fn_registry/python/.venv/bin/python3" 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) PROMPTS=$(jq -r '(.prompts // []) | to_entries | map("\(.key+1). \(.value)") | join("\n")' "$F" 2>/dev/null)
[ -z "$PROMPTS" ] && exit 0 [ -z "$PROMPTS" ] && exit 0
if [ "$FORCE" = "force" ]; then 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."
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} PROMPT="OBJETIVO (fijo, no lo cambies): ${GOAL}
DoD ACTUAL: ${DOD} DoD ACTUAL: ${DOD}
PROMPTS DEL USUARIO (orden cronologico): PROMPTS DEL USUARIO (orden cronologico):
${PROMPTS} ${PROMPTS}
Responde el JSON:" Responde el JSON con el DoD:"
RAW=$("$PY" "$ASK" --system "$SYS" "$PROMPT" 2>/dev/null) RAW=$("$PY" "$ASK" --system "$SYS" "$PROMPT" 2>/dev/null)
[ -z "$RAW" ] && exit 0 [ -z "$RAW" ] && exit 0
JSON=$(printf '%s' "$RAW" | tr '\n' ' ' | grep -o '{[^{}]*}' | head -1) JSON=$(printf '%s' "$RAW" | tr '\n' ' ' | grep -o '{[^{}]*}' | head -1)
[ -z "$JSON" ] && exit 0 [ -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) ND=$(printf '%s' "$JSON" | jq -r '.dod // ""' 2>/dev/null)
[ -z "$ND" ] && exit 0
case "$ACTION" in TMP="${F}.tmp.$$"
refine) if jq --arg d "$ND" '.dod=$d' "$F" > "$TMP" 2>/dev/null; then
TMP="${F}.tmp.$$" mv "$TMP" "$F"
if jq --arg g "$NG" --arg d "$ND" ' else
(if $g != "" then .goal=$g else . end) rm -f "$TMP"
| (if $d != "" then .dod=$d else . end) fi
| .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 exit 0
+15 -25
View File
@@ -1,21 +1,20 @@
#!/bin/bash #!/bin/bash
# UserPromptSubmit hook del sistema de objetivo+fase por terminal. # UserPromptSubmit hook del sistema de objetivo+fase por terminal.
# #
# Comandos META que lee directamente de tu prompt. Estos se ejecutan FUERA DE # Modelo:
# BANDA: el hook hace su efecto y BLOQUEA el prompt (decision=block), asi el # - El OBJETIVO (target) es el IDENTIFICATIVO de la terminal: se genera una vez
# agente NO lo recibe ni responde — sigue idle con lo suyo. Solo ves una # (del primer prompt, o a mano con "objetivo: ...") y NUNCA cambia solo.
# confirmacion breve. # - El DoD SI se ajusta con tus prompts para reflejar la condicion de terminado.
# objetivo: <texto> fija el objetivo de la terminal (meta:/goal: equivalen). # - 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). # 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. # 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). # 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) INPUT=$(cat)
SID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null) 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. # Bloquea el prompt (no llega al agente) y muestra <reason> al usuario.
block() { jq -n --arg r "$1" '{decision:"block", reason:$r}'; exit 0; } 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) GOAL_LINE=$(printf '%s' "$PROMPT" | grep -ioE '^[[:space:]]*(objetivo|meta|goal)[[:space:]]*:[[:space:]]*.+' | head -1)
if [ -n "$GOAL_LINE" ]; then if [ -n "$GOAL_LINE" ]; then
NEWGOAL=$(printf '%s' "$GOAL_LINE" | sed -E 's/^[^:]*:[[:space:]]*//; s/[[:space:]]+$//') NEWGOAL=$(printf '%s' "$GOAL_LINE" | sed -E 's/^[^:]*:[[:space:]]*//; s/[[:space:]]+$//')
@@ -69,14 +68,6 @@ if [ -n "$DOD_LINE" ]; then
block "🏁 DoD fijado: ${NEWDOD}" block "🏁 DoD fijado: ${NEWDOD}"
fi 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) --- # --- pausa (marca manual; Ctrl-C no dispara hooks en Claude Code) ---
case "$PROMPT_TRIM" in case "$PROMPT_TRIM" in
pausa|pause|pausar|"en pausa"|/pausa) pausa|pause|pausar|"en pausa"|/pausa)
@@ -88,13 +79,12 @@ case "$PROMPT_TRIM" in
block "⏸️ Fase marcada en pausa." ;; block "⏸️ Fase marcada en pausa." ;;
esac 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 if [ -f "$F" ]; then
G=$(jq -r '.goal // ""' "$F" 2>/dev/null) G=$(jq -r '.goal // ""' "$F" 2>/dev/null)
P=$(jq -r '.phase // ""' "$F" 2>/dev/null) P=$(jq -r '.phase // ""' "$F" 2>/dev/null)
D=$(jq -r '.dod // ""' "$F" 2>/dev/null) D=$(jq -r '.dod // ""' "$F" 2>/dev/null)
# Acumular el prompt y refinar objetivo+DoD coherentes (background). Si es una # Acumular el prompt y ajustar SOLO el DoD (background). El objetivo no cambia.
# tarea totalmente distinta, goal_refine marca alert=true (mezcla de tareas).
if [ "${#PROMPT_TRIM}" -ge 12 ]; then if [ "${#PROMPT_TRIM}" -ge 12 ]; then
TMP="${F}.tmp.$$" TMP="${F}.tmp.$$"
if jq --arg p "$PROMPT_TRIM" '.prompts = ((.prompts // []) + [$p])[-12:]' "$F" > "$TMP" 2>/dev/null; then 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 fi
nohup bash "$HOME/.claude/hooks/goal_refine.sh" "$SID" "$F" >/dev/null 2>&1 & nohup bash "$HOME/.claude/hooks/goal_refine.sh" "$SID" "$F" >/dev/null 2>&1 &
fi 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 else
# Sin objetivo: autogenerar objetivo + DoD desde el primer prompt sustantivo, # Sin objetivo: autogenerar objetivo + DoD desde el primer prompt sustantivo,
# en background (no bloquea). Se omite para prompts triviales (saludos, ok...). # 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) GOAL=$(jq -r '.goal // ""' "$GOAL_FILE" 2>/dev/null)
PHASE=$(jq -r '.phase // ""' "$GOAL_FILE" 2>/dev/null) PHASE=$(jq -r '.phase // ""' "$GOAL_FILE" 2>/dev/null)
DOD=$(jq -r '.dod // ""' "$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 if [ -n "$GOAL" ]; then
GC=$(goal_color "$SESSION_ID") GC=$(goal_color "$SESSION_ID")
# ⚠️ si la terminal esta mezclando tareas (objetivo cambio por completo). LEFT="${GC}🎯 ${GOAL}${RESET}"
if [ "$ALERT" = "true" ]; then
LEFT="\033[1;31m⚠️\033[0m ${GC}🎯 ${GOAL}${RESET}"
else
LEFT="${GC}🎯 ${GOAL}${RESET}"
fi
LINE0="${LEFT}" LINE0="${LEFT}"