Compare commits
12 Commits
393a77b597
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| a42cad769a | |||
| e355a65b5b | |||
| d4640a0660 | |||
| e2b5ac56eb | |||
| 86252b7d2c | |||
| e976fb303a | |||
| 8e53e0818e | |||
| bb735cad17 | |||
| 0e8d2d2ff2 | |||
| ffb3f9b270 | |||
| 1b769a9666 | |||
| 963b3bd7e1 |
@@ -19,10 +19,14 @@ Wrapper sobre `tbd_branch_create_bash_infra`. La función del registry maneja to
|
|||||||
|
|
||||||
2. **Llamar la función del registry**:
|
2. **Llamar la función del registry**:
|
||||||
```bash
|
```bash
|
||||||
source /home/lucas/fn_registry/bash/functions/infra/tbd_branch_create.sh
|
# Path portable (cualquier PC): FN_REGISTRY_ROOT si está, si no ~/fn_registry.
|
||||||
tbd_branch_create issue 0021 hot-reload
|
# Se invoca con `bash` (no `source`): el script llama a tbd_branch_create con
|
||||||
|
# los argumentos al ejecutarse directamente, y así funciona aunque la shell de
|
||||||
|
# la sesión sea zsh (evita el fallo de BASH_SOURCE).
|
||||||
|
FN_TBD="${FN_REGISTRY_ROOT:-$HOME/fn_registry}/bash/functions/infra/tbd_branch_create.sh"
|
||||||
|
bash "$FN_TBD" issue 0021 hot-reload
|
||||||
# o
|
# o
|
||||||
tbd_branch_create quick fix-typo-readme
|
bash "$FN_TBD" quick fix-typo-readme
|
||||||
```
|
```
|
||||||
|
|
||||||
La función:
|
La función:
|
||||||
|
|||||||
@@ -81,8 +81,11 @@ Si autocontenido, saltar.
|
|||||||
### 5. Cerrar la rama (registry)
|
### 5. Cerrar la rama (registry)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
source /home/lucas/fn_registry/bash/functions/infra/tbd_branch_finish.sh
|
# Path portable (cualquier PC): FN_REGISTRY_ROOT si está, si no ~/fn_registry.
|
||||||
tbd_branch_finish "<descripción corta del merge>"
|
# Se invoca con `bash` (no `source`): el script tiene un entry point que llama a
|
||||||
|
# tbd_branch_finish con los argumentos cuando se ejecuta directamente, y así
|
||||||
|
# funciona aunque la shell de la sesión sea zsh (evita el fallo de BASH_SOURCE).
|
||||||
|
bash "${FN_REGISTRY_ROOT:-$HOME/fn_registry}/bash/functions/infra/tbd_branch_finish.sh" "<descripción corta del merge>"
|
||||||
```
|
```
|
||||||
|
|
||||||
La función:
|
La función:
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ SID="$1"
|
|||||||
F="$2"
|
F="$2"
|
||||||
PROMPT="$3"
|
PROMPT="$3"
|
||||||
|
|
||||||
# Si ya existe objetivo (lo creo otro proceso o el usuario), no pisar.
|
# Si ya existe objetivo DEFINITIVO (usuario manual u otro autogen ya termino), no
|
||||||
[ -f "$F" ] && exit 0
|
# 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"
|
PY="$HOME/fn_registry/python/.venv/bin/python3"
|
||||||
ASK="$HOME/fn_registry/python/functions/core/ask_llm.py"
|
ASK="$HOME/fn_registry/python/functions/core/ask_llm.py"
|
||||||
@@ -20,7 +24,7 @@ ASK="$HOME/fn_registry/python/functions/core/ask_llm.py"
|
|||||||
P=$(printf '%s' "$PROMPT" | tail -c 2000)
|
P=$(printf '%s' "$PROMPT" | tail -c 2000)
|
||||||
[ -z "$P" ] && exit 0
|
[ -z "$P" ] && exit 0
|
||||||
|
|
||||||
SYS="Dado el PRIMER mensaje de un usuario a un asistente de codigo en una terminal, infiere un OBJETIVO breve de la tarea (maximo 8 palabras, en espanol, sin comillas) y un DoD breve (definition of done: condicion concreta de 'terminado', maximo 8 palabras, en espanol). Responde SOLO un objeto JSON en una sola linea, sin markdown ni texto extra: {\"goal\":\"...\",\"dod\":\"...\"}. Si el mensaje es un saludo, charla trivial o no describe ninguna tarea, responde exactamente {}."
|
SYS="Dado el PRIMER mensaje de un usuario a un asistente de codigo en una terminal, infiere un OBJETIVO breve de la tarea (maximo 8 palabras, en espanol, sin comillas), un DoD breve (definition of done: condicion concreta de 'terminado', maximo 8 palabras, en espanol) y EXACTAMENTE 3 EMOJIS que representen visualmente la tarea (3 emojis pegados, sin espacios ni texto entre ellos). Responde SOLO un objeto JSON en una sola linea, sin markdown ni texto extra: {\"goal\":\"...\",\"dod\":\"...\",\"emojis\":\"🔭✨🌌\"}. Si el mensaje es un saludo, charla trivial o no describe ninguna tarea, responde exactamente {}."
|
||||||
|
|
||||||
RAW=$("$PY" "$ASK" --system "$SYS" "$P" 2>/dev/null)
|
RAW=$("$PY" "$ASK" --system "$SYS" "$P" 2>/dev/null)
|
||||||
[ -z "$RAW" ] && exit 0
|
[ -z "$RAW" ] && exit 0
|
||||||
@@ -31,16 +35,32 @@ JSON=$(printf '%s' "$RAW" | tr '\n' ' ' | grep -o '{[^{}]*}' | head -1)
|
|||||||
|
|
||||||
GOAL=$(printf '%s' "$JSON" | jq -r '.goal // ""' 2>/dev/null)
|
GOAL=$(printf '%s' "$JSON" | jq -r '.goal // ""' 2>/dev/null)
|
||||||
DOD=$(printf '%s' "$JSON" | jq -r '.dod // ""' 2>/dev/null)
|
DOD=$(printf '%s' "$JSON" | jq -r '.dod // ""' 2>/dev/null)
|
||||||
|
EMOJIS=$(printf '%s' "$JSON" | jq -r '.emojis // ""' 2>/dev/null)
|
||||||
[ -z "$GOAL" ] && exit 0
|
[ -z "$GOAL" ] && exit 0
|
||||||
|
|
||||||
# Carrera: si entre tanto se creo el archivo, no pisar.
|
# Carrera: si entre tanto aparecio un objetivo DEFINITIVO (manual), respetarlo.
|
||||||
[ -f "$F" ] && exit 0
|
# 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.$$"
|
TMP="${F}.tmp.$$"
|
||||||
if jq -n --arg g "$GOAL" --arg d "$DOD" --arg p "$P" \
|
if [ -f "$F" ]; then
|
||||||
'{goal:$g, phase:"planificando", history:["planificando"], prompts:[$p]} | if $d != "" then .dod=$d else . end' > "$TMP" 2>/dev/null; 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"
|
mv "$TMP" "$F"
|
||||||
else
|
else
|
||||||
rm -f "$TMP"
|
rm -f "$TMP"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
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
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -1,48 +1,22 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Ajusta SOLO el DoD para mantenerlo coherente con los prompts del usuario hacia
|
# DESACTIVADO (2026-06-21): este hook regeneraba el campo `.dod` (movil) del
|
||||||
# el objetivo. El OBJETIVO es fijo (identificativo de la terminal) y NUNCA se
|
# goal.json llamando a un LLM (haiku via ask_llm.py) en CADA prompt de CADA
|
||||||
# toca aqui. Lo lanza goal_tracker.sh en background (no bloquea). Usa ask_llm
|
# sesion. Con muchas sesiones de la flota activas a la vez eso amplificaba el
|
||||||
# (haiku, API directa; nunca `claude -p`, ver regla llm_invocation.md).
|
# rate-limit compartido de la organizacion ("Server is temporarily limiting
|
||||||
|
# requests"). Una request API por turno por agente = coste innecesario.
|
||||||
#
|
#
|
||||||
# Args: <session_id> <goal_json_file>
|
# El `.dod` movil NO lo consume nadie: el parser de la flota
|
||||||
|
# (functions/infra/list_claude_fleet.go, struct goalFile/readGoal) solo lee
|
||||||
SID="$1"
|
# goal/phase/emojis/rename/dod_contract/dod_status/role; ignora `.dod` por
|
||||||
F="$2"
|
# completo. El criterio de aceptacion real que clasifica la flota es
|
||||||
|
# `dod_contract` + `dod_status`, escrito por set_dod_contract.py (sin LLM) y
|
||||||
[ -f "$F" ] || exit 0
|
# consumido por ClassifyFleetTermination. Ese sistema queda intacto.
|
||||||
PY="$HOME/fn_registry/python/.venv/bin/python3"
|
#
|
||||||
ASK="$HOME/fn_registry/python/functions/core/ask_llm.py"
|
# Por tanto la regeneracion del `.dod` movil con haiku se elimina por completo:
|
||||||
[ -x "$PY" ] || exit 0
|
# cero llamadas LLM por prompt. El objetivo+DoD inicial los sigue generando
|
||||||
[ -f "$ASK" ] || exit 0
|
# goal_autogen.sh una sola vez por terminal (junto con goal/emojis, que si se
|
||||||
|
# usan); el usuario puede ajustar el DoD a mano con "dod: ...".
|
||||||
GOAL=$(jq -r '.goal // ""' "$F" 2>/dev/null)
|
#
|
||||||
[ -z "$GOAL" ] && exit 0
|
# Se conserva el archivo como no-op para no romper ningun disparador historico
|
||||||
DOD=$(jq -r '.dod // ""' "$F" 2>/dev/null)
|
# (defensa en profundidad). El disparo desde goal_tracker.sh tambien se retiro.
|
||||||
PROMPTS=$(jq -r '(.prompts // []) | to_entries | map("\(.key+1). \(.value)") | join("\n")' "$F" 2>/dev/null)
|
|
||||||
[ -z "$PROMPTS" ] && exit 0
|
|
||||||
|
|
||||||
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 (fijo, no lo cambies): ${GOAL}
|
|
||||||
DoD ACTUAL: ${DOD}
|
|
||||||
|
|
||||||
PROMPTS DEL USUARIO (orden cronologico):
|
|
||||||
${PROMPTS}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
ND=$(printf '%s' "$JSON" | jq -r '.dod // ""' 2>/dev/null)
|
|
||||||
[ -z "$ND" ] && exit 0
|
|
||||||
|
|
||||||
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
|
exit 0
|
||||||
|
|||||||
@@ -27,8 +27,16 @@ 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; }
|
||||||
|
|
||||||
|
# Los comandos meta (objetivo:/dod:/pausa) solo cuentan en la PRIMERA linea del
|
||||||
|
# prompt. Sin este recorte, un prompt multilinea que CONTENGA una linea
|
||||||
|
# "objetivo:"/"dod:" en su interior (p.ej. una <task-notification> de un subagente
|
||||||
|
# que trae DoD-contratos esbozados, o un mensaje que cita un DoD) se malinterpreta
|
||||||
|
# como comando meta: se bloquea con decision:block (atrapando la notificacion) y se
|
||||||
|
# corrompe el .dod del goal con esa linea interior. Anclar a la 1a linea lo evita.
|
||||||
|
FIRST_LINE=$(printf '%s' "$PROMPT" | head -1)
|
||||||
|
|
||||||
# --- objetivo: <texto> (manual; 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' "$FIRST_LINE" | 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:]]+$//')
|
||||||
case "$NEWGOAL" in
|
case "$NEWGOAL" in
|
||||||
@@ -52,8 +60,12 @@ if [ -n "$GOAL_LINE" ]; then
|
|||||||
block "🎯 Objetivo fijado: ${NEWGOAL}"
|
block "🎯 Objetivo fijado: ${NEWGOAL}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Nota: el rename de FleetView se hace ahora con alt+r DENTRO de la TUI (escribe
|
||||||
|
# el campo .rename del goal directamente). Ya no se captura /rename en este hook,
|
||||||
|
# asi el built-in /rename de Claude Code queda libre para renombrar la sesion.
|
||||||
|
|
||||||
# --- dod: <texto> ---
|
# --- dod: <texto> ---
|
||||||
DOD_LINE=$(printf '%s' "$PROMPT" | grep -ioE '^[[:space:]]*dod[[:space:]]*:[[:space:]]*.+' | head -1)
|
DOD_LINE=$(printf '%s' "$FIRST_LINE" | grep -ioE '^[[:space:]]*dod[[:space:]]*:[[:space:]]*.+' | head -1)
|
||||||
if [ -n "$DOD_LINE" ]; then
|
if [ -n "$DOD_LINE" ]; then
|
||||||
NEWDOD=$(printf '%s' "$DOD_LINE" | sed -E 's/^[^:]*:[[:space:]]*//; s/[[:space:]]+$//')
|
NEWDOD=$(printf '%s' "$DOD_LINE" | sed -E 's/^[^:]*:[[:space:]]*//; s/[[:space:]]+$//')
|
||||||
[ -f "$F" ] || block "Fija primero un objetivo (\"objetivo: ...\") antes del DoD."
|
[ -f "$F" ] || block "Fija primero un objetivo (\"objetivo: ...\") antes del DoD."
|
||||||
@@ -79,28 +91,62 @@ case "$PROMPT_TRIM" in
|
|||||||
block "⏸️ Fase marcada en pausa." ;;
|
block "⏸️ Fase marcada en pausa." ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# --- prompt NORMAL: pasa al agente + acumula para refinar el DoD + estado ---
|
# --- prompt NORMAL: pasa al agente + estado ---
|
||||||
|
# Distinguimos dos situaciones por el flag .provisional del goal file:
|
||||||
|
# - no existe el archivo -> primer prompt: ponemos objetivo PROVISIONAL = tu
|
||||||
|
# texto + lanzamos autogen (haiku, UNA sola vez)
|
||||||
|
# que lo definira.
|
||||||
|
# - existe pero .provisional -> autogen aun no termino (o fallo): conservamos el
|
||||||
|
# provisional y relanzamos autogen (idempotente,
|
||||||
|
# self-healing).
|
||||||
|
# - existe y NO provisional -> objetivo definitivo: solo mostramos estado.
|
||||||
|
#
|
||||||
|
# NOTA (2026-06-21): el campo `.dod` movil YA NO se regenera con LLM en cada
|
||||||
|
# prompt. goal_refine.sh esta desactivado (era una request haiku por turno por
|
||||||
|
# sesion -> amplificaba el rate-limit compartido de la organizacion). El `.dod`
|
||||||
|
# movil no lo consume nadie; el criterio que clasifica la flota es `dod_contract`
|
||||||
|
# + `dod_status` (set_dod_contract.py, sin LLM). El DoD inicial lo fija autogen
|
||||||
|
# una vez; el usuario lo ajusta a mano con "dod: ...".
|
||||||
|
PROV="false"
|
||||||
|
GOAL_NOW=""
|
||||||
if [ -f "$F" ]; then
|
if [ -f "$F" ]; then
|
||||||
|
PROV=$(jq -r '.provisional // false' "$F" 2>/dev/null)
|
||||||
|
GOAL_NOW=$(jq -r '.goal // ""' "$F" 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# "Objetivo definitivo" = archivo con goal NO vacio y no provisional. El check de
|
||||||
|
# goal no vacio es clave para los ejecutores lanzados por spawn_fleet_agent: su
|
||||||
|
# goal.json se PRE-CREA con solo {role, parent_orchestrator} (sin goal). Sin este
|
||||||
|
# guard, el hook tomaria ese archivo como objetivo definitivo y nunca lanzaria
|
||||||
|
# autogen, dejando el goal vacio para siempre (statusline y FleetView sin objetivo).
|
||||||
|
if [ -f "$F" ] && [ "$PROV" != "true" ] && [ -n "$GOAL_NOW" ]; 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 ajustar SOLO el DoD (background). El objetivo no cambia.
|
echo "GOAL-TRACKER: file=$F | goal=\"$G\" dod=\"$D\" phase=\"$P\". El objetivo es fijo (identificativo de la terminal, NO lo cambies). El DoD inicial lo fija el autogen una vez (sin LLM por prompt); el usuario lo ajusta con \"dod: ...\" — NO lo regeneres tu. 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 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
|
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
|
PROV_GOAL=$(printf '%s' "$PROMPT_TRIM" | head -c 70)
|
||||||
mv "$TMP" "$F"
|
if [ -n "$GOAL_NOW" ]; then
|
||||||
|
# Ya habia goal 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"
|
||||||
|
elif [ -f "$F" ]; then
|
||||||
|
# Archivo PRE-CREADO por spawn_fleet_agent ({role, parent_orchestrator})
|
||||||
|
# sin goal: fija el provisional PRESERVANDO los campos existentes (role,
|
||||||
|
# parent_orchestrator) y deja que autogen lo pise con el definitivo.
|
||||||
|
jq --arg g "$PROV_GOAL" --arg p "$PROMPT_TRIM" \
|
||||||
|
'. + {goal:$g, phase:"planificando", history:["planificando"], prompts:[$p], provisional:true}' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F" || rm -f "$TMP"
|
||||||
else
|
else
|
||||||
rm -f "$TMP"
|
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
|
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\". 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...).
|
|
||||||
if [ "${#PROMPT_TRIM}" -ge 12 ]; then
|
|
||||||
nohup bash "$HOME/.claude/hooks/goal_autogen.sh" "$SID" "$F" "$PROMPT" >/dev/null 2>&1 &
|
nohup bash "$HOME/.claude/hooks/goal_autogen.sh" "$SID" "$F" "$PROMPT" >/dev/null 2>&1 &
|
||||||
fi
|
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
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
+36
-6
@@ -4,34 +4,57 @@
|
|||||||
"Edit(~/.claude/**)",
|
"Edit(~/.claude/**)",
|
||||||
"Write(~/.claude/**)",
|
"Write(~/.claude/**)",
|
||||||
"Edit(.claude/**)",
|
"Edit(.claude/**)",
|
||||||
"Write(.claude/**)"
|
"Write(.claude/**)",
|
||||||
|
"Bash(CGO_ENABLED=1 go test *)",
|
||||||
|
"Bash(sqlite3 *)",
|
||||||
|
"Read(//home/enmanuel/.claude/**)"
|
||||||
],
|
],
|
||||||
"deny": [
|
"deny": [
|
||||||
"Edit(~/.claude/.git/**)",
|
"Edit(~/.claude/.git/**)",
|
||||||
"Write(~/.claude/.git/**)",
|
"Write(~/.claude/.git/**)",
|
||||||
"Edit(.git/**)",
|
"Edit(.git/**)",
|
||||||
"Write(.git/**)"
|
"Write(.git/**)"
|
||||||
]
|
],
|
||||||
|
"defaultMode": "dontAsk"
|
||||||
},
|
},
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"UserPromptSubmit": [
|
"UserPromptSubmit": [
|
||||||
{
|
{
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{ "type": "command", "command": "~/.claude/hooks/goal_tracker.sh" }
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "~/.claude/hooks/goal_tracker.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Notification": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "~/.claude/hooks/goal_notify.sh"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Stop": [
|
"Stop": [
|
||||||
{
|
{
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{ "type": "command", "command": "~/.claude/hooks/goal_phase_eval.sh" }
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "~/.claude/hooks/goal_phase_eval.sh"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"PostToolUse": [
|
"PostToolUse": [
|
||||||
{
|
{
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{ "type": "command", "command": "~/.claude/hooks/goal_phase_active.sh" }
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "~/.claude/hooks/goal_phase_active.sh"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -54,7 +77,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"language": "Español",
|
||||||
"effortLevel": "xhigh",
|
"effortLevel": "xhigh",
|
||||||
|
"voice": {
|
||||||
|
"enabled": true,
|
||||||
|
"mode": "hold"
|
||||||
|
},
|
||||||
"skipDangerousModePermissionPrompt": true,
|
"skipDangerousModePermissionPrompt": true,
|
||||||
"agentPushNotifEnabled": false
|
"preferredNotifChannel": "notifications_disabled",
|
||||||
|
"agentPushNotifEnabled": false,
|
||||||
|
"voiceEnabled": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,25 @@ if [ "$CONTEXT_PCT" -eq 0 ] && [ "$CONTEXT_USED" -gt 0 ]; then
|
|||||||
CONTEXT_PCT=$(echo "scale=0; $CONTEXT_USED * 100 / $CONTEXT_TOTAL" | bc)
|
CONTEXT_PCT=$(echo "scale=0; $CONTEXT_USED * 100 / $CONTEXT_TOTAL" | bc)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Persistir el contexto por sesión en un sidecar para que fleetview (y otras
|
||||||
|
# herramientas) puedan mostrarlo sin tener este stdin. El statusline se re-ejecuta
|
||||||
|
# cada pocos segundos, así que el dato se mantiene fresco mientras la sesión vive.
|
||||||
|
if [ -n "$SESSION_ID" ]; then
|
||||||
|
RTDIR="$HOME/.claude/runtime"
|
||||||
|
mkdir -p "$RTDIR" 2>/dev/null
|
||||||
|
RTF="$RTDIR/${SESSION_ID}.json"
|
||||||
|
RTMP="${RTF}.tmp.$$"
|
||||||
|
if jq -n \
|
||||||
|
--argjson pct "${CONTEXT_PCT:-0}" \
|
||||||
|
--argjson used "${CONTEXT_USED:-0}" \
|
||||||
|
--argjson total "${CONTEXT_TOTAL:-200000}" \
|
||||||
|
'{ctx_pct:$pct, ctx_used:$used, ctx_total:$total}' > "$RTMP" 2>/dev/null; then
|
||||||
|
mv "$RTMP" "$RTF" 2>/dev/null
|
||||||
|
else
|
||||||
|
rm -f "$RTMP" 2>/dev/null
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Costos
|
# Costos
|
||||||
TOTAL_COST=$(echo "$INPUT" | jq -r '.cost.total_cost_usd // 0' | xargs printf "%.3f")
|
TOTAL_COST=$(echo "$INPUT" | jq -r '.cost.total_cost_usd // 0' | xargs printf "%.3f")
|
||||||
SESSION_DURATION=$(echo "$INPUT" | jq -r '.cost.total_duration_ms // 0')
|
SESSION_DURATION=$(echo "$INPUT" | jq -r '.cost.total_duration_ms // 0')
|
||||||
@@ -304,9 +323,21 @@ 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)
|
||||||
|
EMOJIS=$(jq -r '.emojis // ""' "$GOAL_FILE" 2>/dev/null)
|
||||||
|
PROVISIONAL=$(jq -r '.provisional // false' "$GOAL_FILE" 2>/dev/null)
|
||||||
if [ -n "$GOAL" ]; then
|
if [ -n "$GOAL" ]; then
|
||||||
GC=$(goal_color "$SESSION_ID")
|
GC=$(goal_color "$SESSION_ID")
|
||||||
|
# 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}"
|
LEFT="${GC}🎯 ${GOAL}${RESET}"
|
||||||
|
fi
|
||||||
|
|
||||||
LINE0="${LEFT}"
|
LINE0="${LEFT}"
|
||||||
|
|
||||||
|
|||||||
+74
-6
@@ -54,20 +54,26 @@ done
|
|||||||
echo ""
|
echo ""
|
||||||
echo "=== Instalando archivos de configuración ==="
|
echo "=== Instalando archivos de configuración ==="
|
||||||
|
|
||||||
# 1. Status Line Script
|
# 1. Status Line Script (enlace simbólico)
|
||||||
STATUSLINE_SOURCE="$REPO_DIR/.claude/statusline.sh"
|
STATUSLINE_SOURCE="$REPO_DIR/.claude/statusline.sh"
|
||||||
STATUSLINE_TARGET="$CLAUDE_DIR/statusline.sh"
|
STATUSLINE_TARGET="$CLAUDE_DIR/statusline.sh"
|
||||||
|
|
||||||
if [ -f "$STATUSLINE_SOURCE" ]; then
|
if [ -f "$STATUSLINE_SOURCE" ]; then
|
||||||
if [ -f "$STATUSLINE_TARGET" ]; then
|
chmod +x "$STATUSLINE_SOURCE"
|
||||||
|
if [ -L "$STATUSLINE_TARGET" ] && [ "$(readlink "$STATUSLINE_TARGET")" = "$STATUSLINE_SOURCE" ]; then
|
||||||
|
echo "OK: statusline.sh ya está enlazado correctamente"
|
||||||
|
else
|
||||||
|
# Symlink (roto o apuntando mal): borrar; archivo real: backup
|
||||||
|
if [ -L "$STATUSLINE_TARGET" ]; then
|
||||||
|
rm -f "$STATUSLINE_TARGET"
|
||||||
|
elif [ -e "$STATUSLINE_TARGET" ]; then
|
||||||
BACKUP="$STATUSLINE_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
|
BACKUP="$STATUSLINE_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
|
||||||
echo "Backup: statusline.sh -> $BACKUP"
|
echo "Backup: statusline.sh -> $BACKUP"
|
||||||
mv "$STATUSLINE_TARGET" "$BACKUP"
|
mv "$STATUSLINE_TARGET" "$BACKUP"
|
||||||
fi
|
fi
|
||||||
|
ln -s "$STATUSLINE_SOURCE" "$STATUSLINE_TARGET"
|
||||||
cp "$STATUSLINE_SOURCE" "$STATUSLINE_TARGET"
|
echo "Enlazado: statusline.sh -> $STATUSLINE_SOURCE"
|
||||||
chmod +x "$STATUSLINE_TARGET"
|
fi
|
||||||
echo "Copiado: statusline.sh (ejecutable)"
|
|
||||||
else
|
else
|
||||||
echo "WARN: statusline.sh no encontrado en el repo"
|
echo "WARN: statusline.sh no encontrado en el repo"
|
||||||
fi
|
fi
|
||||||
@@ -96,6 +102,66 @@ else
|
|||||||
echo "WARN: settings.json no encontrado en el repo"
|
echo "WARN: settings.json no encontrado en el repo"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 3. CLAUDE.md (enlace simbólico - preferencias globales)
|
||||||
|
CLAUDEMD_SOURCE="$REPO_DIR/.claude/CLAUDE.md"
|
||||||
|
CLAUDEMD_TARGET="$CLAUDE_DIR/CLAUDE.md"
|
||||||
|
|
||||||
|
if [ -f "$CLAUDEMD_SOURCE" ]; then
|
||||||
|
if [ -L "$CLAUDEMD_TARGET" ] && [ "$(readlink "$CLAUDEMD_TARGET")" = "$CLAUDEMD_SOURCE" ]; then
|
||||||
|
echo "OK: CLAUDE.md ya está enlazado correctamente"
|
||||||
|
else
|
||||||
|
# Symlink (roto o apuntando mal): borrar; archivo real: backup
|
||||||
|
if [ -L "$CLAUDEMD_TARGET" ]; then
|
||||||
|
rm -f "$CLAUDEMD_TARGET"
|
||||||
|
elif [ -e "$CLAUDEMD_TARGET" ]; then
|
||||||
|
BACKUP="$CLAUDEMD_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
|
||||||
|
echo "Backup: CLAUDE.md -> $BACKUP"
|
||||||
|
mv "$CLAUDEMD_TARGET" "$BACKUP"
|
||||||
|
fi
|
||||||
|
ln -s "$CLAUDEMD_SOURCE" "$CLAUDEMD_TARGET"
|
||||||
|
echo "Enlazado: CLAUDE.md -> $CLAUDEMD_SOURCE"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "WARN: CLAUDE.md no encontrado en el repo"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# === Instalando hooks (enlace simbólico por archivo) ===
|
||||||
|
echo ""
|
||||||
|
echo "=== Instalando hooks ==="
|
||||||
|
|
||||||
|
HOOKS_SOURCE_DIR="$REPO_DIR/.claude/hooks"
|
||||||
|
HOOKS_TARGET_DIR="$CLAUDE_DIR/hooks"
|
||||||
|
|
||||||
|
if [ -d "$HOOKS_SOURCE_DIR" ]; then
|
||||||
|
mkdir -p "$HOOKS_TARGET_DIR"
|
||||||
|
for hook in "$HOOKS_SOURCE_DIR"/*.sh; do
|
||||||
|
[ -e "$hook" ] || continue
|
||||||
|
chmod +x "$hook"
|
||||||
|
HOOK_NAME="$(basename "$hook")"
|
||||||
|
HOOK_TARGET="$HOOKS_TARGET_DIR/$HOOK_NAME"
|
||||||
|
|
||||||
|
# Si ya es symlink correcto, saltar
|
||||||
|
if [ -L "$HOOK_TARGET" ] && [ "$(readlink "$HOOK_TARGET")" = "$hook" ]; then
|
||||||
|
echo "OK: hooks/$HOOK_NAME ya está enlazado correctamente"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Symlink (roto o apuntando mal): borrar sin backup; archivo real: backup
|
||||||
|
if [ -L "$HOOK_TARGET" ]; then
|
||||||
|
rm -f "$HOOK_TARGET"
|
||||||
|
elif [ -e "$HOOK_TARGET" ]; then
|
||||||
|
BACKUP="$HOOK_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
|
||||||
|
echo "Backup: hooks/$HOOK_NAME -> $BACKUP"
|
||||||
|
mv "$HOOK_TARGET" "$BACKUP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -s "$hook" "$HOOK_TARGET"
|
||||||
|
echo "Enlazado: hooks/$HOOK_NAME -> $hook"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "WARN: $HOOKS_SOURCE_DIR no existe, saltando hooks"
|
||||||
|
fi
|
||||||
|
|
||||||
# === Limpieza de configuración que no debe cambiar ===
|
# === Limpieza de configuración que no debe cambiar ===
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Limpiando configuración inmutable ==="
|
echo "=== Limpiando configuración inmutable ==="
|
||||||
@@ -189,6 +255,8 @@ echo "Tus comandos y configuración ahora están sincronizados con el repositori
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Configuración instalada:"
|
echo "Configuración instalada:"
|
||||||
echo " • Skills, Agents y Commands enlazados simbólicamente"
|
echo " • Skills, Agents y Commands enlazados simbólicamente"
|
||||||
|
echo " • Hooks (goal_*.sh) enlazados simbólicamente"
|
||||||
|
echo " • CLAUDE.md (preferencias globales) enlazado"
|
||||||
echo " • Status Line configurada con vibecoding setup"
|
echo " • Status Line configurada con vibecoding setup"
|
||||||
echo " • Settings.json enlazado (compartido entre repos)"
|
echo " • Settings.json enlazado (compartido entre repos)"
|
||||||
echo " • Backups viejos limpiados (>7 días)"
|
echo " • Backups viejos limpiados (>7 días)"
|
||||||
|
|||||||
Reference in New Issue
Block a user