Compare commits
27 Commits
71874079cd
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| a42cad769a | |||
| e355a65b5b | |||
| d4640a0660 | |||
| e2b5ac56eb | |||
| 86252b7d2c | |||
| e976fb303a | |||
| 8e53e0818e | |||
| bb735cad17 | |||
| 0e8d2d2ff2 | |||
| ffb3f9b270 | |||
| 1b769a9666 | |||
| 963b3bd7e1 | |||
| 393a77b597 | |||
| 50290a71e7 | |||
| a3ecb6a4cf | |||
| 1840402453 | |||
| 9ac52501b5 | |||
| 1a15108b56 | |||
| 54f47570d1 | |||
| adfb45015e | |||
| 3ae472d1f3 | |||
| fa2b2e16bc | |||
| 4d1fff53e9 | |||
| eb42966295 | |||
| 8c9919f1f8 | |||
| f881b7703b | |||
| 5efcedf9ba |
@@ -16,3 +16,17 @@ Aplican a todas las sesiones de Claude Code, en cualquier proyecto.
|
||||
> (`skills/caveman/SKILL.md` + `src/hooks/caveman-mode-tracker.js`). Las copias del plugin en
|
||||
> `~/.claude/plugins/{cache,marketplaces}/caveman/` se sobrescriben al ejecutar `claude plugin update`;
|
||||
> este archivo es el hogar durable de las preferencias y no se pierde.
|
||||
|
||||
## Navegación web — usa SIEMPRE el MCP del navegador
|
||||
|
||||
Para CUALQUIER tarea de navegación, lectura o automatización web (abrir páginas, login, scraping, rellenar formularios, reconocimiento de endpoints) usa SIEMPRE el MCP `browser_mcp`. NUNCA CDP crudo inline (heredoc WebSocket, `Runtime.evaluate` a mano), NUNCA Playwright/Selenium, NUNCA lanzar `chromium`/`google-chrome` a pelo para esto.
|
||||
|
||||
- El MCP opera sobre un Chrome aislado (puerto 9333) separado del navegador diario.
|
||||
- **Navegar:** `tab_new` / `tab_navigate` (+ `tab_select` para elegir pestaña, `nav_back` / `nav_forward`).
|
||||
- **Esperar:** `page_wait_load` (DOM listo) / `page_wait_idle` (red en reposo; ya ignora WebSocket/EventSource, no cuelga en SPAs).
|
||||
- **Leer (por defecto, SIN capturas):** `page_perceive` (accessibility tree → outline indentado con marcadores `#ref` accionables) y `page_get_text` (texto visible, truncable). NO uses `page_screenshot` para leer: hoy guarda la imagen a archivo y el agente no la ve; las capturas son solo para depuración visual puntual, no para percepción.
|
||||
- **Actuar:** `dom_click_ref` / `dom_type_ref` / `dom_hover_ref` (por el `#ref` del outline de `page_perceive`), `dom_find_ref_by_text`, `press_key`, `scroll`. El bucle natural es: `page_perceive` → decidir sobre los `#ref` → `dom_*_ref` → `page_perceive` de nuevo (auto-observa el efecto).
|
||||
|
||||
Si el MCP no expone una capacidad concreta, usa `fn run cdp_<x>` antes de escribir CDP crudo: hay 46 funciones del dominio `browser` indexadas en el registry (incluidas `cdp_navigate`, `cdp_get_text`, `cdp_perceive_outline`, `cdp_click_ref`). El registry SÍ tiene navegación CDP genérica — si no la encuentras por búsqueda, mejora la búsqueda, no reinventes con un heredoc.
|
||||
|
||||
Requisito de disponibilidad: el `browser_mcp` debe estar registrado en el `.mcp.json` accesible a la sesión (hoy en `projects/web_scraping/.mcp.json`). Si trabajas en otra carpeta y las tools `browser_*`/`page_*`/`dom_*` no aparecen, registra el MCP en el `.mcp.json` de esa sesión.
|
||||
|
||||
@@ -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**:
|
||||
```bash
|
||||
source /home/lucas/fn_registry/bash/functions/infra/tbd_branch_create.sh
|
||||
tbd_branch_create issue 0021 hot-reload
|
||||
# Path portable (cualquier PC): FN_REGISTRY_ROOT si está, si no ~/fn_registry.
|
||||
# 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
|
||||
tbd_branch_create quick fix-typo-readme
|
||||
bash "$FN_TBD" quick fix-typo-readme
|
||||
```
|
||||
|
||||
La función:
|
||||
|
||||
@@ -81,8 +81,11 @@ Si autocontenido, saltar.
|
||||
### 5. Cerrar la rama (registry)
|
||||
|
||||
```bash
|
||||
source /home/lucas/fn_registry/bash/functions/infra/tbd_branch_finish.sh
|
||||
tbd_branch_finish "<descripción corta del merge>"
|
||||
# Path portable (cualquier PC): FN_REGISTRY_ROOT si está, si no ~/fn_registry.
|
||||
# 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:
|
||||
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
# Autogeneracion de objetivo + DoD a partir del primer prompt sustantivo de una
|
||||
# terminal que aun no tiene objetivo. Lo lanza goal_tracker.sh en background (no
|
||||
# bloquea el turno). Usa ask_llm (haiku, API directa; nunca `claude -p`).
|
||||
#
|
||||
# Args: <session_id> <goal_json_file> <prompt_text>
|
||||
|
||||
SID="$1"
|
||||
F="$2"
|
||||
PROMPT="$3"
|
||||
|
||||
# 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"
|
||||
[ -x "$PY" ] || exit 0
|
||||
[ -f "$ASK" ] || exit 0
|
||||
|
||||
P=$(printf '%s' "$PROMPT" | tail -c 2000)
|
||||
[ -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), 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)
|
||||
[ -z "$RAW" ] && exit 0
|
||||
|
||||
# Extraer el primer objeto JSON de la salida (tolerante a texto/markdown extra).
|
||||
JSON=$(printf '%s' "$RAW" | tr '\n' ' ' | grep -o '{[^{}]*}' | head -1)
|
||||
[ -z "$JSON" ] && exit 0
|
||||
|
||||
GOAL=$(printf '%s' "$JSON" | jq -r '.goal // ""' 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
|
||||
|
||||
# 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 [ -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
|
||||
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
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
# PostToolUse hook: marca el estado ACTIVO de la tarea segun la herramienta que
|
||||
# el asistente acaba de usar. Determinista, sin LLM, en tiempo real. Solo actua
|
||||
# si la terminal tiene un objetivo fijado.
|
||||
#
|
||||
# El estado de REPOSO (al parar: hecho/pendiente_revision/bloqueado/en_pausa) lo
|
||||
# pone el Stop hook (goal_phase_eval.sh + goal_phase_worker.sh).
|
||||
|
||||
INPUT=$(cat)
|
||||
SID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
||||
[ -z "$SID" ] && exit 0
|
||||
|
||||
F="$HOME/.claude/goals/${SID}.json"
|
||||
[ -f "$F" ] || exit 0
|
||||
GOAL=$(jq -r '.goal // ""' "$F" 2>/dev/null)
|
||||
[ -z "$GOAL" ] && exit 0
|
||||
CUR=$(jq -r '.phase // ""' "$F" 2>/dev/null)
|
||||
|
||||
TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
||||
[ -z "$TOOL" ] && exit 0
|
||||
|
||||
PHASE=""
|
||||
case "$TOOL" in
|
||||
Read|Grep|Glob|NotebookRead|WebFetch|WebSearch|ToolSearch)
|
||||
PHASE=investigando ;;
|
||||
Edit|Write|MultiEdit|NotebookEdit)
|
||||
# Editar tras haber testeado = retoques finales -> puliendo. Si no, es
|
||||
# implementacion normal -> haciendo.
|
||||
case "$CUR" in
|
||||
testeando|puliendo) PHASE=puliendo ;;
|
||||
*) PHASE=haciendo ;;
|
||||
esac
|
||||
;;
|
||||
Task|Agent|Workflow)
|
||||
PHASE=haciendo ;;
|
||||
Bash)
|
||||
CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
||||
case "$CMD" in
|
||||
*pytest*|*"go test"*|*ctest*|*jest*|*vitest*|*"npm test"*|*"npm run test"*|*"cargo test"*|*unittest*|*" test "*|*"./fn run"*test*)
|
||||
PHASE=testeando ;;
|
||||
ls|ls\ *|cat\ *|*grep*|find\ *|head\ *|tail\ *|stat\ *|tree*|rg\ *|fd\ *|*"git status"*|*"git log"*|*"git diff"*|*"git show"*|*"git branch"*)
|
||||
PHASE=investigando ;;
|
||||
*)
|
||||
PHASE=haciendo ;;
|
||||
esac
|
||||
;;
|
||||
mcp__registry__fn_search|mcp__registry__fn_show|mcp__registry__fn_code|mcp__registry__fn_uses|mcp__registry__fn_list_domains|mcp__registry__fn_doctor|mcp__registry__fn_proposal)
|
||||
PHASE=investigando ;;
|
||||
mcp__registry__fn_run)
|
||||
PHASE=haciendo ;;
|
||||
TodoWrite|ExitPlanMode|EnterPlanMode|Plan)
|
||||
PHASE=planificando ;;
|
||||
*)
|
||||
# Herramientas que no representan un cambio de actividad (AskUserQuestion,
|
||||
# etc.): no tocar la fase.
|
||||
exit 0 ;;
|
||||
esac
|
||||
[ -z "$PHASE" ] && exit 0
|
||||
|
||||
# Escribir la fase + mantener historial (append solo si cambia respecto al
|
||||
# ultimo; se conservan los ultimos 12 estados).
|
||||
TMP="${F}.tmp.$$"
|
||||
if jq --arg p "$PHASE" '
|
||||
.phase = $p
|
||||
| .history = (
|
||||
( .history // [] ) as $h
|
||||
| ( if ($h | length) > 0 and ($h[-1] == $p) then $h else ($h + [$p]) end )
|
||||
| .[-12:]
|
||||
)
|
||||
' "$F" > "$TMP" 2>/dev/null; then
|
||||
mv "$TMP" "$F"
|
||||
else
|
||||
rm -f "$TMP"
|
||||
fi
|
||||
exit 0
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# Stop hook: tras cada respuesta del asistente, dispara (en background) la
|
||||
# clasificacion de la fase de la tarea. Lee la ultima respuesta del transcript,
|
||||
# la clasifica con ask_llm (haiku) y escribe el resultado en el goal JSON de la
|
||||
# sesion. El statusline lo pinta en el siguiente render.
|
||||
#
|
||||
# No bloquea el cierre del turno: el trabajo pesado va al worker en background.
|
||||
|
||||
INPUT=$(cat)
|
||||
SID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
||||
TRANSCRIPT=$(printf '%s' "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
|
||||
|
||||
[ -z "$SID" ] && exit 0
|
||||
|
||||
F="$HOME/.claude/goals/${SID}.json"
|
||||
# Solo si esta terminal tiene un objetivo fijado.
|
||||
[ -f "$F" ] || exit 0
|
||||
GOAL=$(jq -r '.goal // ""' "$F" 2>/dev/null)
|
||||
[ -z "$GOAL" ] && exit 0
|
||||
|
||||
# Salir del estado ACTIVO de inmediato (sincrono, instantaneo): al parar no debe
|
||||
# quedarse mostrando investigando/haciendo/testeando mientras el worker (haiku,
|
||||
# background) afina el reposo a hecho/pendiente_revision/bloqueado/en_pausa.
|
||||
CUR=$(jq -r '.phase // ""' "$F" 2>/dev/null)
|
||||
case "$CUR" in
|
||||
investigando|planificando|haciendo|testeando|puliendo)
|
||||
TMP="${F}.prov.$$"
|
||||
if jq '.phase="en_pausa"' "$F" > "$TMP" 2>/dev/null; then mv "$TMP" "$F"; else rm -f "$TMP"; fi
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -z "$TRANSCRIPT" ] && exit 0
|
||||
[ -f "$TRANSCRIPT" ] || exit 0
|
||||
|
||||
# Afinar el reposo en background; el hook retorna de inmediato (no bloquea el
|
||||
# turno). El statusline reflejara el valor final en el siguiente refresco.
|
||||
nohup bash "$HOME/.claude/hooks/goal_phase_worker.sh" "$SID" "$TRANSCRIPT" "$F" >/dev/null 2>&1 &
|
||||
exit 0
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
#!/bin/bash
|
||||
# Worker del Stop hook: resuelve el estado de REPOSO de la tarea cuando el
|
||||
# asistente para y cede el control. Clasifica con ask_llm (haiku, API directa;
|
||||
# nunca `claude -p`, ver regla llm_invocation.md) y lo escribe en el goal JSON
|
||||
# manteniendo el historial.
|
||||
#
|
||||
# El estado ACTIVO (mientras se trabaja: investigando/haciendo/testeando) lo
|
||||
# marca el hook PostToolUse (goal_phase_active.sh), de forma determinista. Este
|
||||
# worker SOLO produce estados de reposo: hecho, pendiente_revision, bloqueado,
|
||||
# en_pausa.
|
||||
#
|
||||
# Args: <session_id> <transcript_path> <goal_json>
|
||||
|
||||
SID="$1"
|
||||
TRANSCRIPT="$2"
|
||||
F="$3"
|
||||
|
||||
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
|
||||
[ -f "$F" ] || exit 0
|
||||
[ -f "$TRANSCRIPT" ] || exit 0
|
||||
|
||||
GOAL=$(jq -r '.goal // ""' "$F" 2>/dev/null)
|
||||
[ -z "$GOAL" ] && exit 0
|
||||
CUR=$(jq -r '.phase // ""' "$F" 2>/dev/null)
|
||||
DOD=$(jq -r '.dod // ""' "$F" 2>/dev/null)
|
||||
[ -z "$DOD" ] && DOD="(no definido)"
|
||||
|
||||
is_active() {
|
||||
case "$1" in
|
||||
investigando|planificando|haciendo|testeando|puliendo) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Una pasada de abajo a arriba sobre el turno actual: ultima respuesta de texto
|
||||
# del asistente + ultima peticion del usuario + si hubo trabajo (tool_use).
|
||||
LAST=""
|
||||
USER_MSG=""
|
||||
HAS_WORK=0
|
||||
while IFS= read -r line; do
|
||||
t=$(printf '%s' "$line" | jq -r '.type // empty' 2>/dev/null)
|
||||
if [ "$t" = "assistant" ]; then
|
||||
if [ -z "$LAST" ]; then
|
||||
txt=$(printf '%s' "$line" | jq -r '(.message.content // [])[]? | select(.type=="text") | .text' 2>/dev/null)
|
||||
[ -n "$txt" ] && LAST="$txt"
|
||||
fi
|
||||
if printf '%s' "$line" | jq -e '(.message.content // [])[]? | select(.type=="tool_use")' >/dev/null 2>&1; then
|
||||
HAS_WORK=1
|
||||
fi
|
||||
elif [ "$t" = "user" ]; then
|
||||
ctype=$(printf '%s' "$line" | jq -r '.message.content | type' 2>/dev/null)
|
||||
if [ "$ctype" = "string" ]; then
|
||||
USER_MSG=$(printf '%s' "$line" | jq -r '.message.content' 2>/dev/null)
|
||||
break
|
||||
fi
|
||||
if ! printf '%s' "$line" | jq -e '(.message.content // [])[]? | select(.type=="tool_result")' >/dev/null 2>&1; then
|
||||
USER_MSG=$(printf '%s' "$line" | jq -r '(.message.content // [])[]? | select(.type=="text") | .text' 2>/dev/null)
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done < <(tac "$TRANSCRIPT")
|
||||
|
||||
# Solo resolver reposo si hubo trabajo este turno o si veniamos de un estado
|
||||
# activo (paramos tras currar). Charla sobre un reposo previo: no tocar.
|
||||
if [ "$HAS_WORK" = "0" ] && ! is_active "$CUR"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
LAST=$(printf '%s' "$LAST" | tail -c 4000)
|
||||
[ -z "$LAST" ] && exit 0
|
||||
USER_MSG=$(printf '%s' "$USER_MSG" | tail -c 1500)
|
||||
|
||||
SYS="El asistente acaba de PARAR y cede el control al usuario. Clasifica el estado de REPOSO en que queda la tarea. Responde UNA sola palabra, sin nada mas, de: hecho pendiente_revision preguntando bloqueado en_pausa sin_cambio. hecho=el objetivo esta completo y verificado; pendiente_revision=el asistente termino un trabajo y espera que el humano lo revise o apruebe (no hace una pregunta directa); preguntando=el asistente termina formulando una o varias PREGUNTAS concretas al usuario y necesita su respuesta o decision para continuar; bloqueado=no puede avanzar por un error o por falta de informacion/acceso; en_pausa=hizo un avance y espera la siguiente indicacion, sin estar terminado ni preguntar ni bloqueado; sin_cambio=el turno no altera el estado de reposo actual (charla irrelevante). Distingue: si la respuesta acaba con preguntas al usuario es 'preguntando'; si deja un resultado para que lo mire es 'pendiente_revision'. REGLA DEL DoD: se te da el DoD (definition of done) que define cuando la tarea esta TERMINADA. Marca 'hecho' UNICAMENTE si el resultado descrito por el asistente CUMPLE ese DoD de forma clara y verificada; compara punto por punto el resultado contra el DoD. Si el DoD no se cumple del todo, o no esta verificado, NO uses 'hecho': usa 'pendiente_revision' (dejas un resultado para que el humano lo revise) o 'en_pausa' (avance parcial). Si el DoD es '(no definido)', usa tu criterio: 'hecho' solo si el objetivo esta claramente completo y verificado."
|
||||
|
||||
PROMPT="OBJETIVO DE LA TAREA: ${GOAL}
|
||||
|
||||
DEFINITION OF DONE (DoD) — la tarea esta TERMINADA solo si esto se cumple:
|
||||
${DOD}
|
||||
|
||||
ULTIMA PETICION DEL USUARIO:
|
||||
${USER_MSG}
|
||||
|
||||
ULTIMA RESPUESTA DEL ASISTENTE:
|
||||
${LAST}
|
||||
|
||||
Compara el resultado contra el DoD y responde con una sola palabra de la lista permitida:"
|
||||
|
||||
RAW=$("$PY" "$ASK" --model claude-haiku-4-5-20251001 --system "$SYS" "$PROMPT" 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
||||
[ -z "$RAW" ] && exit 0
|
||||
|
||||
case "$RAW" in
|
||||
*sin_cambio*|*sincambio*|*ninguna*|*charla*) exit 0 ;;
|
||||
*pregunt*|*consulta*|*respuesta*) PHASE=preguntando ;;
|
||||
*pendiente*revis*|*revis*|*aprob*) PHASE=pendiente_revision ;;
|
||||
*bloque*) PHASE=bloqueado ;;
|
||||
*hecho*|*complet*|*termin*|*done*) PHASE=hecho ;;
|
||||
*pausa*|*pause*|*siguiente*) PHASE=en_pausa ;;
|
||||
# Si por error devuelve un estado activo al parar, lo tratamos como pausa.
|
||||
investigando|planificando|haciendo|testeando|puliendo) PHASE=en_pausa ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
# Escribir la fase + mantener historial (append solo si cambia respecto al
|
||||
# ultimo; se conservan los ultimos 12 estados).
|
||||
TMP="${F}.tmp.$$"
|
||||
if jq --arg p "$PHASE" '
|
||||
.phase = $p
|
||||
| .history = (
|
||||
( .history // [] ) as $h
|
||||
| ( if ($h | length) > 0 and ($h[-1] == $p) then $h else ($h + [$p]) end )
|
||||
| .[-12:]
|
||||
)
|
||||
' "$F" > "$TMP" 2>/dev/null; then
|
||||
mv "$TMP" "$F"
|
||||
else
|
||||
rm -f "$TMP"
|
||||
fi
|
||||
exit 0
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
# DESACTIVADO (2026-06-21): este hook regeneraba el campo `.dod` (movil) del
|
||||
# goal.json llamando a un LLM (haiku via ask_llm.py) en CADA prompt de CADA
|
||||
# sesion. Con muchas sesiones de la flota activas a la vez eso amplificaba el
|
||||
# rate-limit compartido de la organizacion ("Server is temporarily limiting
|
||||
# requests"). Una request API por turno por agente = coste innecesario.
|
||||
#
|
||||
# El `.dod` movil NO lo consume nadie: el parser de la flota
|
||||
# (functions/infra/list_claude_fleet.go, struct goalFile/readGoal) solo lee
|
||||
# goal/phase/emojis/rename/dod_contract/dod_status/role; ignora `.dod` por
|
||||
# completo. El criterio de aceptacion real que clasifica la flota es
|
||||
# `dod_contract` + `dod_status`, escrito por set_dod_contract.py (sin LLM) y
|
||||
# consumido por ClassifyFleetTermination. Ese sistema queda intacto.
|
||||
#
|
||||
# Por tanto la regeneracion del `.dod` movil con haiku se elimina por completo:
|
||||
# cero llamadas LLM por prompt. El objetivo+DoD inicial los sigue generando
|
||||
# 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: ...".
|
||||
#
|
||||
# Se conserva el archivo como no-op para no romper ningun disparador historico
|
||||
# (defensa en profundidad). El disparo desde goal_tracker.sh tambien se retiro.
|
||||
exit 0
|
||||
Executable
+152
@@ -0,0 +1,152 @@
|
||||
#!/bin/bash
|
||||
# UserPromptSubmit hook del sistema de objetivo+fase por terminal.
|
||||
#
|
||||
# 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 DoD a mano.
|
||||
# dod: clear lo borra.
|
||||
# pausa marca la fase en en_pausa (Ctrl-C no dispara hooks).
|
||||
|
||||
INPUT=$(cat)
|
||||
SID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
||||
[ -z "$SID" ] && exit 0
|
||||
|
||||
F="$HOME/.claude/goals/${SID}.json"
|
||||
PROMPT=$(printf '%s' "$INPUT" | jq -r '.prompt // ""' 2>/dev/null)
|
||||
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; }
|
||||
|
||||
# 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) ---
|
||||
GOAL_LINE=$(printf '%s' "$FIRST_LINE" | 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:]]+$//')
|
||||
case "$NEWGOAL" in
|
||||
-|clear|none|borrar|quitar|reset)
|
||||
rm -f "$F"
|
||||
block "🎯 Objetivo de esta terminal borrado." ;;
|
||||
esac
|
||||
if [ -f "$F" ]; then
|
||||
PH=$(jq -r '.phase // "planificando"' "$F" 2>/dev/null)
|
||||
DD=$(jq -r '.dod // ""' "$F" 2>/dev/null)
|
||||
else
|
||||
PH="planificando"; DD=""
|
||||
fi
|
||||
TMP="${F}.tmp.$$"
|
||||
if jq -n --arg g "$NEWGOAL" --arg p "$PH" --arg d "$DD" \
|
||||
'{goal:$g, phase:$p, prompts:[]} | if $d != "" then .dod=$d else . end' > "$TMP" 2>/dev/null; then
|
||||
mv "$TMP" "$F"
|
||||
else
|
||||
rm -f "$TMP"
|
||||
fi
|
||||
block "🎯 Objetivo fijado: ${NEWGOAL}"
|
||||
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_LINE=$(printf '%s' "$FIRST_LINE" | grep -ioE '^[[:space:]]*dod[[:space:]]*:[[:space:]]*.+' | head -1)
|
||||
if [ -n "$DOD_LINE" ]; then
|
||||
NEWDOD=$(printf '%s' "$DOD_LINE" | sed -E 's/^[^:]*:[[:space:]]*//; s/[[:space:]]+$//')
|
||||
[ -f "$F" ] || block "Fija primero un objetivo (\"objetivo: ...\") antes del DoD."
|
||||
case "$NEWDOD" in
|
||||
-|clear|none|borrar|quitar|reset)
|
||||
TMP="${F}.tmp.$$"
|
||||
jq 'del(.dod)' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F"
|
||||
block "🏁 DoD borrado." ;;
|
||||
esac
|
||||
TMP="${F}.tmp.$$"
|
||||
if jq --arg d "$NEWDOD" '.dod=$d' "$F" > "$TMP" 2>/dev/null; then mv "$TMP" "$F"; else rm -f "$TMP"; fi
|
||||
block "🏁 DoD fijado: ${NEWDOD}"
|
||||
fi
|
||||
|
||||
# --- pausa (marca manual; Ctrl-C no dispara hooks en Claude Code) ---
|
||||
case "$PROMPT_TRIM" in
|
||||
pausa|pause|pausar|"en pausa"|/pausa)
|
||||
[ -f "$F" ] || block "No hay objetivo en esta terminal."
|
||||
TMP="${F}.tmp.$$"
|
||||
if jq '.phase="en_pausa" | .history=((.history // [])+["en_pausa"])[-12:]' "$F" > "$TMP" 2>/dev/null; then
|
||||
mv "$TMP" "$F"
|
||||
fi
|
||||
block "⏸️ Fase marcada en pausa." ;;
|
||||
esac
|
||||
|
||||
# --- 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
|
||||
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)
|
||||
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\". 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
|
||||
TMP="${F}.tmp.$$"
|
||||
PROV_GOAL=$(printf '%s' "$PROMPT_TRIM" | head -c 70)
|
||||
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
|
||||
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 (objetivo PROVISIONAL = tu texto; generando el objetivo+DoD real con haiku en background). El usuario tambien puede fijarlo con \"objetivo: ...\" / \"dod: ...\"."
|
||||
fi
|
||||
exit 0
|
||||
+57
-3
@@ -4,19 +4,66 @@
|
||||
"Edit(~/.claude/**)",
|
||||
"Write(~/.claude/**)",
|
||||
"Edit(.claude/**)",
|
||||
"Write(.claude/**)"
|
||||
"Write(.claude/**)",
|
||||
"Bash(CGO_ENABLED=1 go test *)",
|
||||
"Bash(sqlite3 *)",
|
||||
"Read(//home/enmanuel/.claude/**)"
|
||||
],
|
||||
"deny": [
|
||||
"Edit(~/.claude/.git/**)",
|
||||
"Write(~/.claude/.git/**)",
|
||||
"Edit(.git/**)",
|
||||
"Write(.git/**)"
|
||||
],
|
||||
"defaultMode": "dontAsk"
|
||||
},
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/goal_tracker.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Notification": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/goal_notify.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/goal_phase_eval.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/goal_phase_active.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "~/.claude/statusline.sh",
|
||||
"padding": 1
|
||||
"padding": 1,
|
||||
"refreshInterval": 2
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"gopls-lsp@claude-plugins-official": true,
|
||||
@@ -30,7 +77,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": "Español",
|
||||
"effortLevel": "xhigh",
|
||||
"voice": {
|
||||
"enabled": true,
|
||||
"mode": "hold"
|
||||
},
|
||||
"skipDangerousModePermissionPrompt": true,
|
||||
"agentPushNotifEnabled": false
|
||||
"preferredNotifChannel": "notifications_disabled",
|
||||
"agentPushNotifEnabled": false,
|
||||
"voiceEnabled": true
|
||||
}
|
||||
|
||||
+121
-2
@@ -23,6 +23,11 @@ MODEL=$(echo "$INPUT" | jq -r '.model.display_name // "Unknown"')
|
||||
CONTEXT_PCT=$(echo "$INPUT" | jq -r '.context_window.used_percentage // 0' | xargs printf "%.0f")
|
||||
CONTEXT_TOTAL=$(echo "$INPUT" | jq -r '.context_window.context_window_size // 200000')
|
||||
CURRENT_DIR=$(echo "$INPUT" | jq -r '.workspace.current_dir // "~"' | sed "s|$HOME|~|")
|
||||
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""')
|
||||
|
||||
# Purga: borra goal files de sesiones muertas (no tocados en >7 dias). El worker
|
||||
# refresca el mtime en cada respuesta, asi que las sesiones vivas nunca caen.
|
||||
find "$HOME/.claude/goals" -maxdepth 1 -name '*.json' -mtime +7 -delete 2>/dev/null
|
||||
|
||||
# Tokens de entrada y salida (current_usage puede ser null antes del primer API call)
|
||||
INPUT_TOKENS=$(echo "$INPUT" | jq -r '.context_window.current_usage.input_tokens // 0')
|
||||
@@ -40,6 +45,25 @@ if [ "$CONTEXT_PCT" -eq 0 ] && [ "$CONTEXT_USED" -gt 0 ]; then
|
||||
CONTEXT_PCT=$(echo "scale=0; $CONTEXT_USED * 100 / $CONTEXT_TOTAL" | bc)
|
||||
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
|
||||
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')
|
||||
@@ -58,10 +82,18 @@ RESET_7D=""
|
||||
[ "$RESET_5H_EPOCH" -gt 0 ] && RESET_5H=$(date -d "@$RESET_5H_EPOCH" +"%H:%M" 2>/dev/null)
|
||||
[ "$RESET_7D_EPOCH" -gt 0 ] && RESET_7D=$(date -d "@$RESET_7D_EPOCH" +"%a %H:%M" 2>/dev/null)
|
||||
|
||||
# Git info (si estamos en un repo)
|
||||
# Git info (si estamos en un repo). Con cache de TTL corto: como el statusline
|
||||
# se re-ejecuta cada pocos segundos (refreshInterval), recomputar git en cada
|
||||
# tick es caro en repos grandes y el estado git no cambia estando idle. Se cachea
|
||||
# por directorio y se recomputa solo si el cache tiene mas de 6s.
|
||||
GIT_BRANCH=""
|
||||
GIT_STATUS=""
|
||||
if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
GIT_CACHE="/tmp/fn_sl_git_$(printf '%s' "$CURRENT_DIR" | cksum | cut -d' ' -f1).cache"
|
||||
GIT_CACHE_AGE=999
|
||||
[ -f "$GIT_CACHE" ] && GIT_CACHE_AGE=$(( $(date +%s) - $(stat -c %Y "$GIT_CACHE" 2>/dev/null || echo 0) ))
|
||||
if [ "$GIT_CACHE_AGE" -lt 6 ]; then
|
||||
. "$GIT_CACHE"
|
||||
elif git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
GIT_BRANCH=$(git branch --show-current 2>/dev/null || echo "detached")
|
||||
|
||||
# Obtener archivos staged, modified, untracked
|
||||
@@ -89,8 +121,41 @@ if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
|
||||
# Trim trailing space
|
||||
GIT_STATUS=$(echo "$GIT_STATUS" | sed 's/ $//')
|
||||
|
||||
# Guardar en cache (quoting seguro para re-source).
|
||||
printf 'GIT_BRANCH=%q\nGIT_STATUS=%q\n' "$GIT_BRANCH" "$GIT_STATUS" > "$GIT_CACHE" 2>/dev/null
|
||||
fi
|
||||
|
||||
# Color estable por sesión (hash del session_id → paleta ANSI 256 legible).
|
||||
# Cada terminal mantiene su color toda su vida; distinto entre terminales.
|
||||
goal_color() {
|
||||
local sid="$1"
|
||||
local palette=(39 45 51 75 81 114 120 156 183 210 215 222 213 159 228)
|
||||
local h
|
||||
h=$(printf '%s' "$sid" | cksum | cut -d' ' -f1)
|
||||
local idx=$(( h % ${#palette[@]} ))
|
||||
printf '\033[1;38;5;%dm' "${palette[$idx]}"
|
||||
}
|
||||
|
||||
# Fase de trabajo → icono | color ANSI | etiqueta visible.
|
||||
# El slug (clave) lo escribe el agente del Stop hook; aqui se mapea a su estilo.
|
||||
phase_style() {
|
||||
case "$1" in
|
||||
investigando) printf '🔎|36|investigando' ;;
|
||||
planificando) printf '📋|34|planificando' ;;
|
||||
haciendo) printf '🔨|33|haciendo' ;;
|
||||
testeando) printf '🧪|35|testeando' ;;
|
||||
puliendo) printf '✨|95|puliendo detalles' ;;
|
||||
pendiente_revision) printf '👀|93|pendiente de revisión' ;;
|
||||
preguntando) printf '❓|96|esperando respuesta' ;;
|
||||
bloqueado) printf '⛔|31|bloqueado' ;;
|
||||
en_pausa) printf '⏸️|90|en pausa' ;;
|
||||
hecho) printf '✅|32|hecho' ;;
|
||||
iterando) printf '🔁|94|iterando' ;;
|
||||
*) printf "•|90|$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Función para crear barra de progreso
|
||||
progress_bar() {
|
||||
local pct=$1
|
||||
@@ -251,6 +316,60 @@ fi
|
||||
# 6. Directorio actual
|
||||
LINE2="${LINE2} ${GRAY}│${RESET} ${BLUE}${CURRENT_DIR}${RESET}"
|
||||
|
||||
# ===== LÍNEA 0: Objetivo (izq) + Fase (der) =====
|
||||
# Solo si la sesión tiene archivo de objetivo con goal no vacío.
|
||||
GOAL_FILE="$HOME/.claude/goals/${SESSION_ID}.json"
|
||||
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")
|
||||
# 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}"
|
||||
|
||||
# Historial: emojis de los ultimos 7 estados PREVIOS (sin el actual, que
|
||||
# se muestra completo a la derecha), atenuados y separados por │.
|
||||
PREV=$(jq -r '(.history // []) | .[0:-1] | .[-7:] | .[]' "$GOAL_FILE" 2>/dev/null)
|
||||
if [ -n "$PREV" ]; then
|
||||
HJOIN=""
|
||||
while IFS= read -r slug; do
|
||||
[ -z "$slug" ] && continue
|
||||
HS=$(phase_style "$slug")
|
||||
HIC="${HS%%|*}"
|
||||
HJOIN="${HJOIN}${HIC}"
|
||||
done <<< "$PREV"
|
||||
[ -n "$HJOIN" ] && LINE0="${LINE0} ${GRAY}│${RESET} ${DIM}${HJOIN}${RESET}"
|
||||
fi
|
||||
|
||||
# Fase actual (completa, con color e icono).
|
||||
if [ -n "$PHASE" ]; then
|
||||
PS=$(phase_style "$PHASE")
|
||||
PICON="${PS%%|*}"
|
||||
REST="${PS#*|}"
|
||||
PCOL="${REST%%|*}"
|
||||
PLABEL="${REST#*|}"
|
||||
LINE0="${LINE0} ${GRAY}│${RESET} \033[1;${PCOL}m${PICON} ${PLABEL}${RESET}"
|
||||
fi
|
||||
echo -e "$LINE0"
|
||||
# DoD en su propia linea debajo del objetivo, atenuado (🏁 = condicion de hecho).
|
||||
[ -n "$DOD" ] && echo -e " ${DIM}🏁 ${DOD}${RESET}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Imprimir resultado (2 líneas)
|
||||
echo -e "$LINE1"
|
||||
echo -e "$LINE2"
|
||||
|
||||
+77
-9
@@ -54,20 +54,26 @@ done
|
||||
echo ""
|
||||
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_TARGET="$CLAUDE_DIR/statusline.sh"
|
||||
|
||||
if [ -f "$STATUSLINE_SOURCE" ]; then
|
||||
if [ -f "$STATUSLINE_TARGET" ]; then
|
||||
BACKUP="$STATUSLINE_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
echo "Backup: statusline.sh -> $BACKUP"
|
||||
mv "$STATUSLINE_TARGET" "$BACKUP"
|
||||
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)"
|
||||
echo "Backup: statusline.sh -> $BACKUP"
|
||||
mv "$STATUSLINE_TARGET" "$BACKUP"
|
||||
fi
|
||||
ln -s "$STATUSLINE_SOURCE" "$STATUSLINE_TARGET"
|
||||
echo "Enlazado: statusline.sh -> $STATUSLINE_SOURCE"
|
||||
fi
|
||||
|
||||
cp "$STATUSLINE_SOURCE" "$STATUSLINE_TARGET"
|
||||
chmod +x "$STATUSLINE_TARGET"
|
||||
echo "Copiado: statusline.sh (ejecutable)"
|
||||
else
|
||||
echo "WARN: statusline.sh no encontrado en el repo"
|
||||
fi
|
||||
@@ -96,6 +102,66 @@ else
|
||||
echo "WARN: settings.json no encontrado en el repo"
|
||||
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 ===
|
||||
echo ""
|
||||
echo "=== Limpiando configuración inmutable ==="
|
||||
@@ -189,6 +255,8 @@ echo "Tus comandos y configuración ahora están sincronizados con el repositori
|
||||
echo ""
|
||||
echo "Configuración instalada:"
|
||||
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 " • Settings.json enlazado (compartido entre repos)"
|
||||
echo " • Backups viejos limpiados (>7 días)"
|
||||
|
||||
Reference in New Issue
Block a user