From e976fb303ac63b0deeee70357b9b92fa3cab1b2a Mon Sep 17 00:00:00 2001 From: agent Date: Wed, 17 Jun 2026 01:09:11 +0200 Subject: [PATCH] fix(goals): /rename solo para FleetView + objetivo provisional - /rename escribe el nombre en FleetView (.rename del goal). NO renombra el titulo de la sesion de Claude Code: el built-in /rename usa estado interno y no re-lee el transcript, asi que un evento ai-title no cambia el prompt bar (comprobado). - objetivo provisional: el primer prompt fija un goal placeholder hasta que haiku genera el definitivo, para que el statusline no quede vacio. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/commands/rename.md | 4 +++ .claude/hooks/goal_autogen.sh | 35 ++++++++++++++----- .claude/hooks/goal_tracker.sh | 63 +++++++++++++++++++++++------------ .claude/statusline.sh | 14 +++++++- 4 files changed, 86 insertions(+), 30 deletions(-) create mode 100644 .claude/commands/rename.md diff --git a/.claude/commands/rename.md b/.claude/commands/rename.md new file mode 100644 index 0000000..b3f3c56 --- /dev/null +++ b/.claude/commands/rename.md @@ -0,0 +1,4 @@ +--- +description: Renombra esta terminal/Claude en FleetView (no toca el título de la sesión) +--- +rename: $ARGUMENTS diff --git a/.claude/hooks/goal_autogen.sh b/.claude/hooks/goal_autogen.sh index 9c17a12..667fe5d 100755 --- a/.claude/hooks/goal_autogen.sh +++ b/.claude/hooks/goal_autogen.sh @@ -9,8 +9,12 @@ SID="$1" F="$2" PROMPT="$3" -# Si ya existe objetivo (lo creo otro proceso o el usuario), no pisar. -[ -f "$F" ] && exit 0 +# Si ya existe objetivo DEFINITIVO (usuario manual u otro autogen ya termino), no +# pisar. Un archivo PROVISIONAL (.provisional=true) SI se pisa: es el placeholder +# (= texto del usuario) que pusimos para que el statusline no quede vacio. +if [ -f "$F" ] && [ "$(jq -r '.provisional // false' "$F" 2>/dev/null)" != "true" ]; then + exit 0 +fi PY="$HOME/fn_registry/python/.venv/bin/python3" ASK="$HOME/fn_registry/python/functions/core/ask_llm.py" @@ -34,14 +38,29 @@ DOD=$(printf '%s' "$JSON" | jq -r '.dod // ""' 2>/dev/null) EMOJIS=$(printf '%s' "$JSON" | jq -r '.emojis // ""' 2>/dev/null) [ -z "$GOAL" ] && exit 0 -# Carrera: si entre tanto se creo el archivo, no pisar. -[ -f "$F" ] && exit 0 +# Carrera: si entre tanto aparecio un objetivo DEFINITIVO (manual), respetarlo. +# Si solo esta el provisional, lo pisamos abajo con el definitivo. +if [ -f "$F" ] && [ "$(jq -r '.provisional // false' "$F" 2>/dev/null)" != "true" ]; then + exit 0 +fi TMP="${F}.tmp.$$" -if jq -n --arg g "$GOAL" --arg d "$DOD" --arg e "$EMOJIS" --arg p "$P" \ - '{goal:$g, phase:"planificando", history:["planificando"], prompts:[$p]} | (if $d != "" then .dod=$d else . end) | (if $e != "" then .emojis=$e else . end)' > "$TMP" 2>/dev/null; then - mv "$TMP" "$F" +if [ -f "$F" ]; then + # Pisar el provisional: fija goal/dod/emojis definitivos y quita el flag, + # preservando phase/history/prompts que el provisional ya hubiera acumulado. + if jq --arg g "$GOAL" --arg d "$DOD" --arg e "$EMOJIS" \ + 'del(.provisional) | .goal=$g | (if $d != "" then .dod=$d else . end) | (if $e != "" then .emojis=$e else . end)' \ + "$F" > "$TMP" 2>/dev/null; then + mv "$TMP" "$F" + else + rm -f "$TMP" + fi else - rm -f "$TMP" + if jq -n --arg g "$GOAL" --arg d "$DOD" --arg e "$EMOJIS" --arg p "$P" \ + '{goal:$g, phase:"planificando", history:["planificando"], prompts:[$p]} | (if $d != "" then .dod=$d else . end) | (if $e != "" then .emojis=$e else . end)' > "$TMP" 2>/dev/null; then + mv "$TMP" "$F" + else + rm -f "$TMP" + fi fi exit 0 diff --git a/.claude/hooks/goal_tracker.sh b/.claude/hooks/goal_tracker.sh index 69d7b2a..ba44482 100755 --- a/.claude/hooks/goal_tracker.sh +++ b/.claude/hooks/goal_tracker.sh @@ -52,27 +52,26 @@ if [ -n "$GOAL_LINE" ]; then block "🎯 Objetivo fijado: ${NEWGOAL}" fi -# --- /rename : guarda el nombre para FleetView (.rename del goal) pero NO -# bloquea, para que el comando NATIVO /rename de Claude Code renombre tambien -# la sesion (prompt bar + /resume picker). Por eso NO debe existir -# ~/.claude/commands/rename.md: competiria con el built-in y lo taparia. --- -RENAME_LINE=$(printf '%s' "$PROMPT" | grep -ioE '^[[:space:]]*/rename([[:space:]]+.*)?$' | head -1) +# --- /rename (o rename: via el command rename.md): pone el nombre +# de la terminal en FleetView (.rename del goal) y bloquea el prompt. +# NO renombra el titulo de la sesion de Claude Code: el built-in /rename usa +# estado interno y NO re-lee el transcript, asi que appendear un evento ai-title +# no cambia el prompt bar (comprobado 2026-06-17). El rename queda solo para +# FleetView. --- +RENAME_LINE=$(printf '%s' "$PROMPT" | grep -ioE '^[[:space:]]*(/rename|rename[[:space:]]*:)[[:space:]]*.*$' | head -1) if [ -n "$RENAME_LINE" ]; then - NEWNAME=$(printf '%s' "$RENAME_LINE" | sed -E 's#^[[:space:]]*/rename[[:space:]]*##; s/[[:space:]]+$//') + NEWNAME=$(printf '%s' "$RENAME_LINE" | sed -E 's#^[[:space:]]*(/rename|rename[[:space:]]*:)[[:space:]]*##; s/[[:space:]]+$//') case "$NEWNAME" in ""|-|clear|none|borrar|quitar|reset) - # /rename sin texto: Claude Code auto-genera; borramos el override de FleetView. - [ -f "$F" ] && { TMP="${F}.tmp.$$"; jq 'del(.rename)' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F"; } ;; - *) - if [ -f "$F" ]; then - TMP="${F}.tmp.$$"; jq --arg n "$NEWNAME" '.rename=$n' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F" - else - TMP="${F}.tmp.$$"; jq -n --arg n "$NEWNAME" '{rename:$n, phase:"planificando", prompts:[]}' > "$TMP" 2>/dev/null && mv "$TMP" "$F" - fi ;; + [ -f "$F" ] && { TMP="${F}.tmp.$$"; jq 'del(.rename)' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F"; } + block "🏷️ Nombre de la terminal borrado." ;; esac - # NO bloquear: el prompt sigue su curso y el built-in /rename de Claude Code - # renombra la sesion. El hook solo capturo el nombre para la lista de FleetView. - exit 0 + if [ -f "$F" ]; then + TMP="${F}.tmp.$$"; jq --arg n "$NEWNAME" '.rename=$n' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F" + else + TMP="${F}.tmp.$$"; jq -n --arg n "$NEWNAME" '{rename:$n, phase:"planificando", prompts:[]}' > "$TMP" 2>/dev/null && mv "$TMP" "$F" + fi + block "🏷️ Renombrado en FleetView: ${NEWNAME}" fi # --- dod: --- @@ -103,7 +102,18 @@ case "$PROMPT_TRIM" in esac # --- prompt NORMAL: pasa al agente + acumula para refinar el DoD + estado --- -if [ -f "$F" ]; then +# Distinguimos tres situaciones por el flag .provisional del goal file: +# - no existe el archivo -> primer prompt: ponemos objetivo PROVISIONAL = tu +# texto + lanzamos autogen (haiku) que lo definira. +# - existe pero .provisional -> autogen aun no termino (o fallo): conservamos el +# provisional, acumulamos el prompt y relanzamos +# autogen (idempotente, self-healing). +# - existe y NO provisional -> objetivo definitivo: acumulamos prompt y solo +# refinamos el DoD; el objetivo no se toca. +PROV="false" +[ -f "$F" ] && PROV=$(jq -r '.provisional // false' "$F" 2>/dev/null) + +if [ -f "$F" ] && [ "$PROV" != "true" ]; 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) @@ -119,11 +129,22 @@ if [ -f "$F" ]; then fi 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...). + # Sin objetivo definitivo todavia. Mostramos de inmediato un objetivo PROVISIONAL + # igual a tu propio texto (truncado), para que el statusline no quede vacio + # mientras haiku genera el real en background. autogen pisara este provisional + # con el definitivo al terminar (su guard respeta .provisional). if [ "${#PROMPT_TRIM}" -ge 12 ]; then + TMP="${F}.tmp.$$" + if [ -f "$F" ]; then + # Ya habia provisional: conserva su goal, solo acumula el prompt. + jq --arg p "$PROMPT_TRIM" '.prompts = ((.prompts // []) + [$p])[-12:]' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F" || rm -f "$TMP" + else + PROV_GOAL=$(printf '%s' "$PROMPT_TRIM" | head -c 70) + jq -n --arg g "$PROV_GOAL" --arg p "$PROMPT_TRIM" \ + '{goal:$g, phase:"planificando", history:["planificando"], prompts:[$p], provisional:true}' > "$TMP" 2>/dev/null && mv "$TMP" "$F" || rm -f "$TMP" + fi nohup bash "$HOME/.claude/hooks/goal_autogen.sh" "$SID" "$F" "$PROMPT" >/dev/null 2>&1 & fi - echo "GOAL-TRACKER: file=$F (sin objetivo aun). Autogenerando objetivo+DoD desde tu prompt en background (haiku). El usuario tambien puede fijarlo con \"objetivo: ...\" / \"dod: ...\"." + echo "GOAL-TRACKER: file=$F (objetivo PROVISIONAL = tu texto; generando el objetivo+DoD real con haiku en background). El usuario tambien puede fijarlo con \"objetivo: ...\" / \"dod: ...\"." fi exit 0 diff --git a/.claude/statusline.sh b/.claude/statusline.sh index 9628240..068135c 100755 --- a/.claude/statusline.sh +++ b/.claude/statusline.sh @@ -323,9 +323,21 @@ 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) + EMOJIS=$(jq -r '.emojis // ""' "$GOAL_FILE" 2>/dev/null) + PROVISIONAL=$(jq -r '.provisional // false' "$GOAL_FILE" 2>/dev/null) if [ -n "$GOAL" ]; then GC=$(goal_color "$SESSION_ID") - LEFT="${GC}🎯 ${GOAL}${RESET}" + # Prefijo del objetivo: + # - provisional (= tu propio texto, mientras haiku genera el real) -> ⏳ atenuado. + # - los 3 emojis generados (representan la tarea, igual que FleetView). + # - fallback al marcador generico de objetivo. + if [ "$PROVISIONAL" = "true" ]; then + LEFT="${GC}⏳ ${DIM}${GOAL}${RESET}" + elif [ -n "$EMOJIS" ]; then + LEFT="${GC}${EMOJIS} ${GOAL}${RESET}" + else + LEFT="${GC}🎯 ${GOAL}${RESET}" + fi LINE0="${LEFT}"