Compare commits

..

30 Commits

Author SHA1 Message Date
agent e2b5ac56eb fix(goal-tracker): eliminar regeneracion LLM (haiku) del dod movil en cada prompt
El hook goal_refine.sh regeneraba el campo .dod del goal.json llamando a
ask_llm.py (haiku) en background en CADA UserPromptSubmit de CADA sesion.
Con muchas sesiones de la flota activas esto amplificaba el rate-limit
compartido de la organizacion (una request API por turno por agente).

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. El criterio que
clasifica la flota (RECLAMA/DICE_TERMINADO/ESTANCADO) es dod_contract +
dod_status, escrito por set_dod_contract.py sin LLM y consumido por
ClassifyFleetTermination. Ese sistema queda intacto.

Cambios:
- goal_refine.sh: convertido en no-op (exit 0) documentado.
- goal_tracker.sh: retirado el disparo de goal_refine + la acumulacion de
  .prompts que solo lo alimentaba; mensaje GOAL-TRACKER actualizado.

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 ajusta el
DoD a mano con 'dod: ...'. Resultado: cero llamadas LLM por prompt.
2026-06-21 12:56:14 +02:00
agent 86252b7d2c chore(goals): retirar slash command /rename (lo reemplaza alt+r de FleetView)
El rename de la terminal en FleetView se hace ahora con alt+r dentro de
la TUI, que escribe el campo .rename del goal directamente. Se elimina el
slash command rename.md y la nota del hook lo documenta, dejando libre el
built-in /rename de Claude Code para renombrar la sesión.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 01:23:09 +02:00
agent e976fb303a 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) <noreply@anthropic.com>
2026-06-17 01:09:11 +02:00
agent 8e53e0818e fix(goals): /rename delega al built-in de Claude Code (no bloquea)
El hook capturaba /rename y bloqueaba el prompt, impidiendo que el comando NATIVO
/rename de Claude Code renombrara la sesion. Ahora el hook guarda el nombre para
FleetView (.rename del goal) y NO bloquea, asi el built-in tambien actua. Elimina
commands/rename.md (competia con el built-in y lo tapaba).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 00:36:09 +02:00
agent bb735cad17 feat(goals): emojis de objetivo + /rename + sidecar de contexto para FleetView
- goal_autogen.sh: genera 3 emojis representativos del objetivo (haiku) junto al
  goal+DoD, guardados en goals/<id>.json.
- goal_tracker.sh: comando meta /rename (y rename:) para nombrar la terminal;
  se guarda en goals/<id>.json .rename.
- commands/rename.md: slash command /rename.
- statusline.sh: persiste el % de contexto por sesion en runtime/<id>.json para
  que FleetView lo muestre.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 00:04:41 +02:00
egutierrez 0e8d2d2ff2 chore: auto-commit (1 archivos)
- .claude/settings.json

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-13 21:56:56 +02:00
egutierrez ffb3f9b270 fix(commands): path portable + invocación bash en /git-push y /git-branch
Los comandos hardcodeaban /home/lucas/fn_registry y hacían 'source' del script TBD, lo que fallaba en otros PCs (path inexistente) y bajo zsh (BASH_SOURCE sin definir).

- Path portable: ${FN_REGISTRY_ROOT:-$HOME/fn_registry} — usa la env var si está, si no ~/fn_registry. Válido en cualquier PC del ecosistema.
- Invocación con 'bash <script> <args>' en vez de 'source': los scripts tbd_branch_finish.sh y tbd_branch_create.sh tienen un entry point (if BASH_SOURCE[0] == $0) que llama a la función con los argumentos al ejecutarse directamente. Así funciona aunque la shell de la sesión sea zsh.

No se renombra el archivo del comando; solo se corrige la invocación interna. No incluye .claude/settings.json (cambio ajeno a esta tarea).
2026-06-13 14:47:52 +02:00
egutierrez 1b769a9666 chore: auto-commit (1 archivos)
- .claude/settings.json

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-11 00:16:46 +02:00
egutierrez 963b3bd7e1 fix(install): enlazar hooks y CLAUDE.md, reparar symlinks rotos
install.sh ahora gestiona los hooks goal_*.sh y CLAUDE.md ademas de
skills/agents/commands/settings. Antes quedaban fuera del script, por lo
que al mover repo_Claude de ~/DataProyects a fn_registry/external los
symlinks de hooks/ quedaban colgando y los hooks goal_* fallaban con
"not found".

Cambios:
- Enlace simbolico por archivo de todos los hooks .sh del repo.
- Enlace simbolico de CLAUDE.md (preferencias globales).
- statusline.sh pasa de copia a symlink (elimina backups basura por corrida).
- Logica de relink idempotente: symlink roto o mal-apuntado se borra y
  recrea; solo los archivos reales se respaldan en backup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:12:56 +02:00
egutierrez 393a77b597 chore: auto-commit (1 archivos)
- .claude/CLAUDE.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 11:42:32 +02:00
egutierrez 50290a71e7 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>
2026-06-06 16:23:20 +02:00
egutierrez a3ecb6a4cf feat(statusline): comandos meta fuera de banda (no molestan al agente)
Los comandos del usuario objetivo:/dod:/recalcular/pausa ahora bloquean el prompt
en UserPromptSubmit con {"decision":"block","reason":...}: el hook ejecuta su
efecto, el usuario ve una confirmacion breve, y el prompt NO llega al modelo — el
agente no genera respuesta y sigue idle con su tarea. Antes estos comandos se
procesaban como un turno normal e interrumpian al agente. Los prompts normales se
siguen pasando al modelo (texto plano como contexto) y acumulan/refían el objetivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 16:11:30 +02:00
egutierrez 1840402453 feat(statusline): objetivo+DoD coherentes con los prompts + alerta de mezcla de tareas
El objetivo y el DoD dejan de quedarse congelados en el primer prompt:

- goal_tracker acumula cada prompt sustantivo del usuario en .prompts y lanza
  goal_refine.sh (background, haiku) para mantener objetivo y DoD coherentes con
  TODO lo pedido (action refine), o dejarlos igual (action same).
- goal_refine marca alert=true (action switch) cuando el ultimo prompt introduce
  una tarea completamente distinta del objetivo: senal de que la terminal mezcla
  tareas (principio: una terminal = una tarea). No cambia el objetivo, solo avisa.
- statusline muestra ⚠️ en rojo antes del objetivo cuando alert=true.
- Comando 'recalcular' (recalcula/replantea): fuerza regenerar objetivo+DoD desde
  los prompts y limpia la alerta (para cuando el cambio de tarea es intencional).
- goal_autogen inicializa .prompts con el primer prompt.

Coste: 1 haiku/prompt sustantivo en background (ademas del haiku de reposo del
Stop), solicitado para mantener la coherencia. No bloquea el turno.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 16:01:15 +02:00
egutierrez 9ac52501b5 style(statusline): DoD en linea propia debajo del objetivo
El DoD se mostraba en la misma linea que el objetivo y la expandia demasiado a lo
ancho. Ahora va en una linea separada debajo, atenuado y con sangria. Si no hay
DoD, no se imprime la linea extra.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:51:42 +02:00
egutierrez 1a15108b56 feat(statusline): 'hecho' se decide comparando el resultado contra el DoD
El Stop worker recibe ahora el DoD de la tarea y lo usa como criterio para
distinguir hecho de pendiente_revision/en_pausa: marca 'hecho' unicamente si el
resultado descrito cumple el DoD punto por punto y esta verificado. Si el DoD no
se cumple del todo, cae en pendiente_revision (resultado para revisar) o en_pausa
(avance parcial). Si no hay DoD definido, mantiene el criterio anterior sobre el
objetivo. Hace el estado 'hecho' mucho mas preciso.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:49:28 +02:00
egutierrez 54f47570d1 feat(statusline): autogenerar objetivo + DoD desde el primer prompt
Cuando una terminal no tiene objetivo y el usuario envia su primer prompt
sustantivo (>=12 chars), goal_tracker lanza goal_autogen.sh en background (no
bloquea el turno). El script infiere con ask_llm (haiku) un objetivo corto y un
DoD corto a partir del prompt y crea el goal JSON con phase=planificando. Los
prompts triviales (saludos, ok) no generan nada (el modelo responde {}). El
statusline lo muestra en el siguiente refresco. El usuario puede sobrescribir a
mano con objetivo:/dod: o borrar con objetivo: clear.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:46:52 +02:00
egutierrez adfb45015e feat(statusline): triggers para planificando y puliendo
Los dos estados activos que no tenian disparador ahora se asignan de forma
determinista en el PostToolUse:
- planificando: al usar TodoWrite / ExitPlanMode / EnterPlanMode.
- puliendo: al editar (Edit/Write/MultiEdit/NotebookEdit) cuando la fase actual
  ya era testeando o puliendo, es decir retoques finales sobre algo ya probado.
  Una edicion normal (sin testeo previo) sigue siendo haciendo; volver a testear
  saca de puliendo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:31:54 +02:00
egutierrez 3ae472d1f3 feat(statusline): estado 'preguntando', DoD junto al objetivo y comando pausa
- Nuevo estado de reposo 'preguntando' ( esperando respuesta), distinto de
  pendiente_revision: lo usa el Stop worker cuando la respuesta termina con
  preguntas concretas al usuario en vez de dejar un resultado para revisar.
- DoD corto opcional junto al objetivo: se fija con "dod: <texto>" ("dod: clear"
  lo borra) y se muestra atenuado con 🏁 tras el objetivo. Re-fijar el objetivo
  preserva el DoD existente.
- Comando "pausa" (prompt) marca la fase en en_pausa. Es la alternativa manual a
  Ctrl-C: Claude Code no dispara ningun hook al interrumpir un turno (el Stop
  hook solo corre en finalizacion normal; feature pedido, sin implementar), asi
  que no es posible detectar la interrupcion automaticamente.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:26:51 +02:00
egutierrez fa2b2e16bc feat(statusline): refresco idle via refreshInterval + cache de git
El statusline solo se re-ejecutaba al cambiar los mensajes, asi que el estado de
reposo que el Stop hook escribe ~2s despues (en background) no se reflejaba hasta
que el usuario interactuaba. Se anade refreshInterval=2 para que el harness re-
ejecute el statusline cada 2s tambien estando idle, mostrando el valor final sin
necesidad de escribir y sin bloquear el turno.

Para que el refresco continuo no sea caro en repos grandes, el bloque git se
cachea por directorio con TTL de 6s (el estado git no cambia estando parado); los
ticks idle reusan el cache (~0.1s vs ~0.33s recomputando). El goal file (la fase)
se lee siempre fresco.

Se revierte el intento previo de Stop sincrono (bloqueaba ~2s el control).

Nota: Claude Code no soporta acotar el refresco a una ventana (p.ej. 10s y
parar); refreshInterval es continuo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:19:21 +02:00
egutierrez 4d1fff53e9 fix(statusline): al parar, salir del estado activo de inmediato
El Stop hook ahora pone en_pausa de forma sincrona si la fase estaba en un
estado activo (investigando/haciendo/testeando/puliendo), antes de lanzar el
worker haiku en background. Evita que el statusline se quede mostrando el ultimo
estado activo (p.ej. 'investigando' por un 'git log' final) durante los ~2s que
tarda la clasificacion de reposo. El provisional no toca el historial; el worker
escribe el reposo final (hecho/pendiente_revision/bloqueado/en_pausa) + history.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:07:57 +02:00
egutierrez eb42966295 style(statusline): historial con emojis pegados, sin separador entre ellos
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:04:43 +02:00
egutierrez 8c9919f1f8 feat(statusline): estados activo (deterministas) + reposo (haiku)
Separa el ciclo de trabajo en dos grupos con la fuente adecuada para cada uno:

- ACTIVO (mientras se trabaja): lo marca el hook PostToolUse de forma
  determinista, sin LLM, segun la herramienta usada — Read/Grep/Glob ->
  investigando; Edit/Write -> haciendo; Bash con tests -> testeando; Bash de
  lectura (ls/cat/git status...) -> investigando; mcp fn_search/show/... ->
  investigando. Refleja en tiempo real lo que hace el asistente.
- REPOSO (al parar y ceder el control): lo resuelve el Stop hook con ask_llm
  (haiku) -> hecho / pendiente_revision / bloqueado / en_pausa. Al parar nunca
  queda en un estado activo.

Cambios:
- goal_phase_active.sh: nuevo hook PostToolUse (mapa herramienta -> fase activa).
- goal_phase_worker.sh: ahora solo produce estados de reposo; se elimina el modo
  prompt. Mantiene el gate (resuelve reposo solo si hubo trabajo o se venia de
  activo) y el historial.
- goal_tracker.sh: deja de lanzar clasificacion LLM en el prompt (redundante);
  vuelve a fijar objetivo desde el prompt + informar estado.
- statusline.sh: nuevo estado en_pausa (en pausa); set de fases reordenado.
- settings.json: registra el hook PostToolUse.

Resultado: 1 sola llamada haiku por turno (Stop); el estado activo es gratis y
refleja las acciones reales en vez de la intencion del prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:04:07 +02:00
egutierrez f881b7703b feat(statusline): historial de estados + clasificacion al escribir el usuario
- statusline.sh: muestra los ultimos 7 estados previos como emojis atenuados
  (DIM) separados por │, entre el objetivo y la fase actual. El historial se
  guarda en el goal JSON (campo .history), colapsando estados consecutivos
  repetidos, hasta 12 entradas.
- goal_phase_worker.sh: dos modos. 'stop' (tras la respuesta del asistente, con
  filtro de trabajo real) y 'prompt' (tras el prompt del usuario, clasifica la
  intencion para feedback inmediato). Nuevo veredicto 'sin_cambio' para
  preguntas/charla que no implican cambio de actividad; ante la duda, no toca.
  Ambos modos mantienen el historial.
- goal_tracker.sh: en cada prompt con objetivo activo lanza el worker en modo
  prompt (background) ademas del Stop hook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 14:07:39 +02:00
egutierrez 5efcedf9ba feat(statusline): seguimiento de objetivo + fase por terminal
Cada terminal muestra su objetivo (color estable por session_id) y la fase de
trabajo actual, para distinguir sesiones y saber cuando algo esta hecho.

- statusline.sh: linea 0 con objetivo (izq, color por sesion) + fase (der) con
  el separador estandar; 9 fases (investigando, planificando, haciendo,
  testeando, puliendo, iterando, pendiente_revision, bloqueado, hecho) con icono,
  color y etiqueta. Purga de goal files de sesiones muertas (>7 dias).
- hooks/goal_tracker.sh (UserPromptSubmit): fija el objetivo leyendo el prompt
  del usuario ("objetivo: ...", "objetivo: clear" lo borra); si no, informa el
  estado actual al modelo.
- hooks/goal_phase_eval.sh (Stop): al terminar el turno lanza el worker en
  background, sin bloquear.
- hooks/goal_phase_worker.sh: clasifica la fase con ask_llm (haiku, API directa,
  nunca claude -p) usando la peticion del usuario + la ultima respuesta del
  asistente. Solo reevalua si el turno tuvo trabajo real (tool_use); en charla
  pura no toca la fase ni gasta llamada.
- settings.json: registra los hooks UserPromptSubmit y Stop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 14:01:05 +02:00
egutierrez 71874079cd chore: auto-commit (1 archivos)
- .claude/settings.json

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-05 17:34:23 +02:00
egutierrez 3d0002625b chore: auto-commit (4 archivos)
- .claude/CLAUDE.md
- .claude/agents/dagu/SKILL.md
- .claude/settings.json
- .claude/skills/dagu-auto/SKILL.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 01:29:46 +02:00
egutierrez 3c7a91e0c0 feat(skills): add /sino one-shot short-answer mode
Slash command for rapid yes/no/short iteration. Allows internal
reasoning + read-only tools but restricts user-facing output to
si/no/short phrase. One-shot: only affects the invoking turn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:49:12 +02:00
egutierrez 25eefbd5e3 chore: sync local config — caveman plugin, command/skill tweaks
- settings.json: enable caveman marketplace+plugin, effortLevel=high
- commands/git-branch + git-push: refactor docs
- skills/parallel-fix-issues: SKILL.md + scripts updates
2026-05-08 20:44:50 +02:00
egutierrez bf8651020e feat(statusline): rate-limit burndown colors + reset times
- Show 5h reset as HH:MM and 7d reset as "Day HH:MM" from rate_limits.*.resets_at
- Color 5h/7d pill+arrow by burndown vs expected rate (5h: 20%/h, 7d: 14%/day)
- Green if available% >= expected, yellow if >= expected/2, red otherwise
- Add separator between 5h and 7d blocks
2026-05-08 20:42:48 +02:00
egutierrez e2a131a6dc refactor: skills globales — eliminar hardcodes de paths/build tags
- parallel-fix-issues: detecta build tag del proyecto (auto o via BUILD_TAG env/arg),
  usa $(git rev-parse --show-toplevel) para rutas en vez de /home/ubuntu/agents_and_robots
- verify-worktree.sh: acepta BUILD_TAG como env o segundo argumento, auto-detecta con
  //go:build, ejecuta sin -tags si no hay tag configurado
- create-tui: DEVFACTORY_PATH, DEVFACTORY_MODULE y GO_NAMESPACE configurables via env
- init-jupyter: resuelve SKILL_DIR dinamicamente siguiendo el symlink de ~/.claude
- pass-usage: elimina GPG-ID hardcodeado, instruye leer de ~/.password-store/.gpg-id
- settings.json: refresh de formato + effortLevel

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:04:34 +02:00
21 changed files with 1172 additions and 864 deletions
+32
View File
@@ -0,0 +1,32 @@
# Preferencias globales
Aplican a todas las sesiones de Claude Code, en cualquier proyecto.
## Idioma
- Háblame SIEMPRE en español, sin importar el idioma del prompt, del código o de las instrucciones del proyecto.
## Modo caveman (plugin `caveman`)
- El estilo caveman aplica SOLO a tus mensajes de chat conmigo.
- Todo texto que escribas DENTRO de archivos va en prosa normal y completa, nunca en estilo caveman: código, comentarios, docstrings, archivos `.md` y documentación, mensajes de commit, cuerpos de PR y descripciones de issues.
- Nombres de función/variable, paths, comandos, flags y mensajes de error citados se mantienen literales (no se traducen ni se comprimen).
> Nota de mantenimiento: estas preferencias también están reforzadas en el plugin caveman
> (`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.
-373
View File
@@ -1,373 +0,0 @@
---
name: dagu
description: Agente para gestionar Dagu - instalar, organizar ~/dagu, crear/editar DAGs y automatizar workflows con YAML
model: sonnet
tools: Read, Write, Bash, Glob, Grep, Edit
---
# Agente Dagu
Eres un experto en Dagu, el motor de workflows local-first basado en DAGs. Tu rol es gestionar la instalación, configuración y creación de automatizaciones en Dagu.
## Tu entorno
- **Directorio base**: `~/dagu/`
- **DAGs**: `~/dagu/dags/`
- **Scripts**: `~/dagu/scripts/`
- **Logs**: `~/dagu/logs/`
- **Data**: `~/dagu/data/`
- **Config**: `~/dagu/dagu-config.yaml`
- **Binario**: `~/.local/bin/dagu`
- **Web UI**: http://localhost:8090
- **Servicio**: `systemctl --user status dagu.service`
## Capacidades principales
### Instalación
- Detectar si Dagu está instalado (`which dagu && dagu version`)
- Instalar en máquinas nuevas (Linux/macOS/Windows)
- Configurar como servicio systemd
- Crear estructura de directorios
### Organización de ~/dagu
- Mantener estructura limpia de carpetas
- Organizar DAGs por categoría en subcarpetas
- Gestionar scripts asociados a DAGs
- Limpiar logs y data obsoletos
### Creación de DAGs
- Generar workflows YAML completos
- Crear DAGs con dependencias (graph mode)
- Configurar schedules con cron
- Parametrizar workflows
- Crear sub-workflows con `call`
### Gestión
- Validar DAGs (`dagu validate`)
- Ver estado (`dagu status`)
- Ejecutar DAGs (`dagu start`)
- Ver historial (`dagu history`)
## Instalación en máquinas nuevas
### Detección
```bash
if command -v dagu &>/dev/null; then
echo "Dagu $(dagu version) ya instalado"
else
echo "Dagu no encontrado, instalando..."
fi
```
### Linux/macOS
```bash
curl -fsSL https://raw.githubusercontent.com/dagu-org/dagu/main/scripts/installer.sh | bash -s -- --install-dir ~/.local/bin
```
### Como servicio systemd
```bash
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/dagu.service << 'EOF'
[Unit]
Description=Dagu Workflow Scheduler
After=network.target
[Service]
Type=simple
ExecStart=%h/.local/bin/dagu start-all --config=%h/dagu/dagu-config.yaml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now dagu.service
```
### Estructura inicial
```bash
mkdir -p ~/dagu/{dags,scripts,logs,data}
cat > ~/dagu/dagu-config.yaml << 'EOF'
host: 0.0.0.0
port: 8090
dags: /home/$USER/dagu/dags
logDir: /home/$USER/dagu/logs
dataDir: /home/$USER/dagu/data
EOF
```
## Referencia YAML de DAGs
### Estructura mínima
```yaml
steps:
- command: echo "Hello from Dagu!"
```
### Estructura completa
```yaml
# Metadata
name: mi-workflow
description: Descripción del workflow
tags: [etl, produccion]
group: MiGrupo
# Tipo de ejecución
type: graph # "chain" (secuencial) o "graph" (dependencias)
# Programación cron
schedule: "0 2 * * *"
# Múltiples: schedule: ["0 9 * * MON-FRI", "0 14 * * SAT,SUN"]
# Con timezone: schedule: "CRON_TZ=America/Argentina/Buenos_Aires 0 9 * * *"
# Start/stop: schedule: { start: "0 8 * * *", stop: "0 18 * * *" }
skip_if_successful: true # Saltar si la última ejecución fue exitosa
# Control de ejecución
max_active_steps: 5 # Máximo pasos paralelos (graph mode)
timeout_sec: 7200 # Timeout del DAG
delay_sec: 10 # Delay antes de iniciar
# Shell
shell: ["/bin/bash", "-e", "-u"]
# Directorio de trabajo
working_dir: /tmp
# Variables de entorno
env:
- LOG_LEVEL: info
- DATE: "`date '+%Y-%m-%d'`" # Sustitución de comandos con backticks
- API_KEY: ${SECRET_API_KEY} # Referencia a env var del sistema
# Dotenv
dotenv: .env
# Parámetros tipados
params:
- name: ENVIRONMENT
type: string
default: production
enum: [dev, staging, prod]
- name: DRY_RUN
type: boolean
default: false
# Secretos
secrets:
- name: API_TOKEN
provider: env
key: PROD_API_TOKEN
# Precondiciones
preconditions:
- condition: "`date +%u`"
expected: "re:[1-5]" # Regex: solo días laborables
# Retención de historial
hist_retention_days: 90
# Handlers de ciclo de vida
handler_on:
success:
command: echo "Completado exitosamente"
failure:
command: echo "Falló la ejecución"
exit:
command: echo "Siempre se ejecuta"
# Steps
steps:
- id: paso_1
description: Primer paso
command: echo "Paso 1"
- id: paso_2
command: echo "Paso 2"
depends: [paso_1] # Solo en graph mode
env:
- EXTRA_VAR: valor
output: RESULTADO # Capturar stdout en variable
stdout: /tmp/output.log # Redirigir stdout a archivo
continue_on:
failure: true # Continuar si falla
retry_policy:
limit: 3
interval_sec: 30
backoff: true
timeout_sec: 300
```
### Tipos de steps especiales
#### Sub-workflow (call)
```yaml
steps:
- call: etl/extract
params: "SOURCE=s3://bucket/data"
output: EXTRACT_RESULT
```
#### HTTP
```yaml
steps:
- command: POST https://api.example.com/webhook
type: http
config:
headers:
Content-Type: application/json
body: '{"status": "started"}'
```
#### Docker
```yaml
steps:
- id: build
container:
image: python:3.11
volumes:
- ./src:/app
working_dir: /app
command: python run.py
```
#### SSH
```yaml
steps:
- name: deploy
type: ssh
config:
host: prod-server.example.com
user: deploy
key: ~/.ssh/id_rsa
command: cd /var/www && git pull
```
#### JQ (procesamiento JSON)
```yaml
steps:
- command: '.data[] | .email'
type: jq
script: ${API_RESPONSE}
```
#### Router (condicional)
```yaml
steps:
- id: router
type: router
value: ${STATUS}
routes:
"production": [prod_handler]
"staging": [staging_handler]
```
#### Parallel Iterator
```yaml
steps:
- call: processor
parallel:
items: [A, B, C]
max_concurrent: 2
params: "ITEM=${ITEM}"
```
#### Chat/LLM
```yaml
steps:
- type: chat
llm:
provider: anthropic
model: claude-sonnet-4-20250514
messages:
- role: user
content: "Analiza estos datos..."
output: ANSWER
```
### Variables especiales de runtime
| Variable | Descripción |
|----------|-------------|
| `DAG_NAME` | Nombre del DAG |
| `DAG_RUN_ID` | ID único de ejecución |
| `DAG_RUN_LOG_FILE` | Ruta al log agregado |
| `DAG_RUN_STEP_NAME` | Nombre del step actual |
| `DAG_RUN_STATUS` | Estado en handlers |
| `DAG_PARAMS_JSON` | JSON de parámetros |
### Paso de datos entre steps
```yaml
steps:
- command: git rev-parse --short HEAD
output: VERSION
- command: echo "Versión: ${VERSION}"
```
#### JSON Path
```yaml
steps:
- command: echo '{"db": {"host": "localhost", "port": 5432}}'
output: CONFIG
- command: psql -h ${CONFIG.db.host} -p ${CONFIG.db.port}
```
#### Referencia por step ID
```yaml
steps:
- id: extract
command: python extract.py
output: DATA
- command: echo "Exit: ${extract.exit_code}, Output: ${extract.output}"
```
## Flujo de trabajo
1. **Verificar instalación**: Comprobar que Dagu está instalado y corriendo
2. **Entender la necesidad**: Qué quiere automatizar el usuario
3. **Diseñar el DAG**: Elegir chain vs graph, definir steps y dependencias
4. **Crear archivos**: DAG YAML + scripts necesarios en ~/dagu/
5. **Validar**: `dagu validate ~/dagu/dags/nombre.yaml`
6. **Probar**: `dagu start nombre` o test desde Web UI
## Comandos útiles
```bash
# Estado del servicio
systemctl --user status dagu.service
systemctl --user restart dagu.service
# Gestión de DAGs
dagu start nombre.yaml # Ejecutar
dagu start nombre.yaml -- PARAM=valor # Con parámetros
dagu validate nombre.yaml # Validar
dagu status nombre # Estado
dagu history nombre # Historial
# Web UI + scheduler
dagu start-all --config=~/dagu/dagu-config.yaml
```
## Convenciones
- DAGs en `~/dagu/dags/` con extensión `.yaml`
- Scripts auxiliares en `~/dagu/scripts/`
- Nombres de DAG en snake_case o kebab-case
- Siempre incluir `name` y `description` en el DAG
- Usar `type: graph` cuando hay dependencias entre steps
- Preferir `id` sobre `name` en steps para referenciarlos
- Validar siempre antes de activar un schedule
- Organizar DAGs complejos en subcarpetas temáticas
## Notas
- Dagu corre como servicio systemd del usuario en esta máquina
- El puerto configurado es 8090 (no el default 8080)
- La config está en `~/dagu/dagu-config.yaml` (no en ~/.config/dagu/)
- Preferimos Dagu sobre cron para TODA programación de tareas
- El filtrado de env vars de Dagu solo pasa: PATH, HOME, USER, SHELL, TMPDIR, TERM, LANG, TZ, DAGU_*, LC_*, DAG_*
- Para pasar otras env vars, definirlas explícitamente en el DAG
+44 -67
View File
@@ -1,86 +1,63 @@
# Command: git branch (TBD)
Crea una rama de trabajo. **Nunca trabajar directamente en master.**
Wrapper sobre `tbd_branch_create_bash_infra`. La función del registry maneja toda la lógica determinista (verificar limpio, autodetectar master/main, pull --rebase, validar slug + número de issue, crear rama). Este comando solo decide los inputs.
Soporta dos tipos de rama:
- `issue/<NNNN>-<slug>` — para implementar un issue existente de `dev/issues/`
- `quick/<slug>` — para cambios pequeños sin issue asociado (fixes, config, docs, etc.)
## Inputs
Preguntar al usuario si el cambio esta asociado a un issue o no.
### Si es un issue:
- `issue_number`: numero de 4 digitos (e.g. `0020`)
- `slug`: nombre corto separado por guiones (e.g. `hot-reload`)
### Si es un cambio rapido (sin issue):
- `slug`: nombre corto descriptivo separado por guiones (e.g. `fix-typo-readme`)
## Flujo obligatorio
1. Verificar que estamos en master y limpio:
```bash
git branch --show-current
git status --short
```
Si no estamos en master, cambiar primero:
```bash
git checkout master
```
Si hay cambios sin commitear, **avisar al usuario** y no continuar hasta resolver.
2. Actualizar master desde remoto:
```bash
git pull --rebase
```
3. Crear la rama y cambiar a ella:
**Para issues:**
```bash
git checkout -b issue/<issue_number>-<slug>
```
Ejemplo: `git checkout -b issue/0013-hot-reload`
**Para cambios rapidos:**
```bash
git checkout -b quick/<slug>
```
Ejemplo: `git checkout -b quick/fix-typo-readme`
4. Confirmar al usuario:
## Uso
```
Rama `<nombre-rama>` creada desde master actualizado.
Puedes empezar a trabajar. Cuando termines, usa `/git-push` para integrar a master.
/git-branch # preguntar al usuario
/git-branch issue 0021 hot-reload
/git-branch quick fix-typo-readme
```
## Pasos del asistente
1. **Decidir modo e inputs**:
- Preguntar si el cambio está asociado a un issue o no.
- Si es issue: pedir `<NNNN>` (4 dígitos) y `<slug>` kebab-case.
- Si es quick: pedir `<slug>` kebab-case descriptivo.
2. **Llamar la función del registry**:
```bash
# 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
bash "$FN_TBD" quick fix-typo-readme
```
La función:
- Verifica que el working tree esté limpio (aborta si dirty).
- Cambia a master/main (autodetecta).
- `git pull --rebase` desde la rama base.
- Valida `<NNNN>` (regex 4 dígitos) y `<slug>` (kebab-case ASCII).
- `git checkout -b <rama>`.
- Imprime confirmación.
## Convenciones
- **Formato de rama issue**: `issue/<NNNN>-<slug>` (siempre 4 digitos)
- **Formato de rama quick**: `quick/<slug>` (sin numero)
- **Ramas cortas**: idealmente horas, no dias
- **Una rama por issue**: no mezclar issues en la misma rama
- **Nunca pushear la rama al remoto**: el push se hace desde master despues del merge
- **No rebase interactivo**: si los commits son limpios desde el inicio, no reescribir historia
- **No commits WIP**: cada commit en la rama debe ser atomico y con mensaje real (ver convencion en `/git-push`)
- **Formato issue**: `issue/<NNNN>-<slug>` (4 dígitos siempre).
- **Formato quick**: `quick/<slug>` (sin número).
- **Ramas cortas**: idealmente horas, no días.
- **Una rama por issue**: no mezclar issues en la misma rama.
- **Nunca pushear la rama al remoto**: el push se hace desde master después del merge (ver `/git-push`).
- **No commits WIP**: cada commit atómico con mensaje real.
## Features multi-issue
Para features que no caben en una sola rama, usar sub-issues con sufijo letra:
Para features que no caben en una sola rama, sub-issues con sufijo letra:
```
issue/0015a-telegram-types
issue/0015b-telegram-client
issue/0015c-telegram-listener
issue/0015d-telegram-enable
```
Cada sub-rama sigue el mismo flujo: crear → implementar → merge --no-ff → delete.
El codigo parcial se protege con **feature flags** en `dev/feature_flags.json` (no con commits WIP).
Cada sub-rama sigue el mismo flujo. El código parcial se protege con **feature flags** en `dev/feature_flags.json`.
## Para tocar la lógica
Editar `tbd_branch_create_bash_infra` en `bash/functions/infra/tbd_branch_create.sh`, no este wrapper.
+69 -96
View File
@@ -1,8 +1,10 @@
# Command: git push
# Command: git push (TBD)
Integra cambios a master y publica. Soporta ramas `issue/*` y `quick/*`.
## Flujo obligatorio
La fase final (`pull --rebase` master + `merge --no-ff` + `git push` + `git branch -d`) la hace `tbd_branch_finish_bash_infra` del registry. Tests y commits **no** los hace la función — los corre el asistente porque dependen del stack.
## Pasos del asistente
### 1. Verificar rama actual y estado
@@ -11,34 +13,24 @@ git branch --show-current
git status --short
```
#### Si estamos en una rama `issue/*` o `quick/*`
#### Si estamos en `issue/*` o `quick/*`
Continuar directamente al paso 2.
Continuar al paso 2.
#### Si estamos en `master` con cambios pendientes
#### Si estamos en master con cambios pendientes
Crear una rama automaticamente antes de continuar:
Crear rama primero:
1. Preguntar si el cambio está asociado a un issue.
2. Si es issue: pedir `<NNNN>` y `<slug>`, llamar `/git-branch issue <NNNN> <slug>`.
3. Si es quick: pedir `<slug>`, llamar `/git-branch quick <slug>`.
1. Preguntar al usuario: **¿Este cambio esta asociado a un issue existente?**
2. **Si es un issue**: pedir el numero y slug, crear rama `issue/<NNNN>-<slug>`.
3. **Si NO es un issue**: pedir un slug descriptivo, crear rama `quick/<slug>`.
**No inventar números de issue.** Solo usar `issue/` si existe en `dev/issues/`.
```bash
# Para issues:
git checkout -b issue/<NNNN>-<slug>
# Para cambios rapidos:
git checkout -b quick/<slug>
```
#### Si estamos en master sin cambios
4. Continuar al paso 2 con los cambios ya en la rama nueva.
**STOP**: nada que publicar.
**IMPORTANTE**: No inventar numeros de issue. Solo usar `issue/` si el issue existe en `dev/issues/`.
#### Si estamos en `master` sin cambios
**STOP**: no hay nada que publicar.
### 2. Revisar cambios y crear commits por bloque
### 2. Revisar cambios y crear commits atómicos
```bash
git status --short
@@ -46,112 +38,93 @@ git diff --stat
git diff
```
Crear commits **atomicos por bloque logico**. Cada commit agrupa cambios de la misma naturaleza:
Crear commits atómicos por bloque lógico. Cada commit agrupa cambios de la misma naturaleza:
```bash
git add <archivos_del_bloque_1>
git commit -m "<tipo>: <resumen breve>" -m "Descripcion larga en espanol explicando que cambia, por que se hizo, impacto esperado y alcance del bloque."
git commit -m "<tipo>: <resumen breve>" -m "Descripción larga en español: qué cambia, por qué, impacto, alcance."
git add <archivos_del_bloque_2>
git commit -m "<tipo>: <resumen breve>" -m "Descripcion larga en espanol."
git commit -m "<tipo>: <resumen breve>" -m "Descripción larga en español."
```
**Reglas criticas de commits:**
- **No WIP**: nunca commitear "wip", "tmp", "fix fix" ni codigo a medias. Cada commit debe ser atomico y completo.
- **No mezclar tipos**: no combinar `feat:` + `test:` en un mismo commit. Separar por bloque logico.
- **No squash**: los commits individuales se preservan en master via `--no-ff`. Usar `git log --first-parent master` para ver solo merge commits.
- **No rebase interactivo**: si los commits ya son limpios, no reescribir historia.
**Reglas críticas**:
- **No WIP**: nunca commitear "wip", "tmp", código a medias.
- **No mezclar tipos**: no combinar `feat:` + `test:` en un mismo commit.
- **No squash**: los commits individuales se preservan via `--no-ff`. Usar `git log --first-parent master` para ver merges.
- **No rebase interactivo**.
### 3. Ejecutar tests
**Obligatorio antes de mergear.** Si el proyecto tiene tests, ejecutarlos:
Obligatorio antes de mergear. Comando depende del stack:
```bash
go test -tags goolm ./...
```
| Stack | Comando |
|---|---|
| Go | `go test ./...` (o con tags si aplica: `-tags goolm` / `-tags fts5`) |
| C++ | `ctest --test-dir cpp/build` |
| Python | `pytest` |
| Sin tests aplicables (solo docs/config) | indicar al usuario y continuar |
- Si los tests **fallan****STOP**: corregir antes de continuar. No mergear codigo roto.
- Si los tests **pasan** → continuar al paso 4.
- Si no hay tests aplicables (e.g. solo cambios de docs/config) → indicar al usuario y continuar.
Si fallan → **STOP** y corregir. Si pasan → paso 4.
### 4. Evaluar feature flags
Feature flags se usan cuando el issue es **parte de una feature multi-issue** o el cambio tiene riesgo y necesita poder desactivarse. **Feature flag ≠ WIP** — un flag protege codigo terminado y testeado, no codigo a medias.
Feature flag = código terminado y testeado, **no** código a medias.
Si se modifico `dev/feature_flags.json` o si los cambios son parte de una feature que se despliega en fases:
Si se modificó `dev/feature_flags.json` o el cambio es parte de feature multi-fase:
1. Verificar que `dev/feature_flags.json` esté actualizado.
2. Confirmar estado correcto del flag (`enabled: true/false`).
3. Incluir el archivo en el commit correspondiente (no commit separado).
1. Verificar que `dev/feature_flags.json` existe y esta actualizado.
2. Confirmar que el flag correspondiente tiene el estado correcto (`enabled: true/false`).
3. Incluir el archivo en el commit correspondiente (no crear commit separado solo para flags).
Si autocontenido, saltar.
Si el issue es autocontenido (se completa en esta rama), no necesita flag. Saltar este paso.
### 5. Actualizar master y hacer merge --no-ff
### 5. Cerrar la rama (registry)
```bash
git checkout master
git pull --rebase
git merge --no-ff <rama> -m "merge: <rama> — <titulo breve>"
# 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>"
```
El merge commit debe tener formato:
- Titulo: `merge: <rama> — <descripcion corta>`
- Cuerpo (opcional): resumen de lo que entra
La función:
- Verifica working tree limpio.
- Autodetecta `master`/`main`.
- `git checkout <base>` + `git pull --rebase`.
- `git merge --no-ff <rama> -m "merge: <rama> — <título>"`.
- Si conflicto → exit 2, deja al usuario resolver con `git add` + `git commit` + retry.
- `git push`.
- `git branch -d <rama>`.
Ejemplos:
- `merge: issue/0021-threads-default-config — habilitar threads en agentes`
- `merge: quick/fix-typo-readme — corregir typo en README`
### 6. Confirmar al usuario
Si hay conflictos durante el merge:
1. Resolver los conflictos
2. `git add` los archivos resueltos
3. `git commit` (sin -m, para mantener el mensaje de merge)
La función ya imprime `Rama '<rama>' integrada a <base> y publicada. Rama local eliminada.` Repetirlo al usuario.
### 6. Push a remoto
```bash
git push
```
### 7. Limpiar rama local
```bash
git branch -d <rama>
```
### 8. Confirmar al usuario
```
Rama `<rama>` integrada a master y publicada.
Rama local eliminada.
```
## Convencion de commits
## Convención de commits
- `feat:` nueva funcionalidad
- `fix:` correccion de error
- `fix:` corrección de error
- `refactor:` cambio estructural sin cambio funcional
- `docs:` documentacion
- `docs:` documentación
- `chore:` mantenimiento
- `test:` tests nuevos o modificados
- `merge:` commit de merge (generado por --no-ff)
- `merge:` commit de merge (lo genera `tbd_branch_finish` con `--no-ff`)
## Regla de mensajes
- El titulo (`-m` corto) debe resumir el bloque.
- El cuerpo (`-m` largo) debe estar en espanol y explicar:
- que se cambio,
- por que se cambio,
- que impacto tiene,
- que no se toco.
- Título corto resume el bloque.
- Cuerpo en español: qué se cambió, por qué, qué impacto, qué no se tocó.
## Checklist rapido
## Checklist
- [ ] Todos los cambios estan commiteados en una rama `issue/*` o `quick/*`.
- [ ] Se separaron cambios distintos en commits diferentes.
- [ ] Cada commit tiene descripcion larga en espanol.
- [ ] Tests ejecutados y pasando (o no aplican).
- [ ] Cambios commiteados en rama `issue/*` o `quick/*`.
- [ ] Cambios distintos en commits diferentes.
- [ ] Cada commit con descripción larga en español.
- [ ] Tests pasando (o no aplican).
- [ ] Feature flags evaluados (o no aplican).
- [ ] `git merge --no-ff` ejecutado desde master.
- [ ] `git push` ejecutado correctamente.
- [ ] Rama local eliminada.
- [ ] `tbd_branch_finish` ejecutado con éxito.
## Para tocar la lógica de cierre
Editar `tbd_branch_finish_bash_infra` en `bash/functions/infra/tbd_branch_finish.sh`. La parte de tests y commits se queda en este comando porque depende del stack.
+66
View File
@@ -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
+75
View File
@@ -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
+38
View File
@@ -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
+122
View File
@@ -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
+22
View File
@@ -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
+129
View File
@@ -0,0 +1,129 @@
#!/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; }
# --- objetivo: <texto> (manual; preserva el DoD si ya existia) ---
GOAL_LINE=$(printf '%s' "$PROMPT" | grep -ioE '^[[:space:]]*(objetivo|meta|goal)[[:space:]]*:[[:space:]]*.+' | head -1)
if [ -n "$GOAL_LINE" ]; then
NEWGOAL=$(printf '%s' "$GOAL_LINE" | sed -E 's/^[^:]*:[[:space:]]*//; s/[[:space:]]+$//')
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' "$PROMPT" | 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"
[ -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)
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.$$"
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 (objetivo PROVISIONAL = tu texto; generando el objetivo+DoD real con haiku en background). El usuario tambien puede fijarlo con \"objetivo: ...\" / \"dod: ...\"."
fi
exit 0
+76 -11
View File
@@ -1,25 +1,90 @@
{
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh",
"padding": 1
},
"enabledPlugins": {
"gopls-lsp@claude-plugins-official": true
},
"skipDangerousModePermissionPrompt": true,
"permissions": {
"allow": [
"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,
"refreshInterval": 2
},
"enabledPlugins": {
"gopls-lsp@claude-plugins-official": true,
"caveman@caveman": true
},
"extraKnownMarketplaces": {
"caveman": {
"source": {
"source": "github",
"repo": "JuliusBrussee/caveman"
}
}
},
"language": "Español",
"effortLevel": "xhigh",
"voice": {
"enabled": true,
"mode": "hold"
},
"skipDangerousModePermissionPrompt": true,
"preferredNotifChannel": "notifications_disabled",
"agentPushNotifEnabled": false,
"voiceEnabled": true
}
@@ -21,10 +21,15 @@ log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${CYAN}[STEP]${NC} $1"; }
# --- Parámetros ---
# Env vars configurables (con defaults razonables):
# DEVFACTORY_PATH — ruta local al repo DevFactory (default: $HOME/.local_agentes/backend)
# DEVFACTORY_MODULE — nombre del módulo Go de DevFactory
# GO_NAMESPACE — namespace para el módulo Go del proyecto (default: github.com/lucasdataproyects)
MODULE_NAME="${1:-}"
TARGET_PATH="${2:-.}"
DEVFACTORY_PATH="$HOME/.local_agentes/backend"
DEVFACTORY_MODULE="github.com/lucasdataproyects/devfactory"
DEVFACTORY_PATH="${DEVFACTORY_PATH:-$HOME/.local_agentes/backend}"
DEVFACTORY_MODULE="${DEVFACTORY_MODULE:-github.com/lucasdataproyects/devfactory}"
GO_NAMESPACE="${GO_NAMESPACE:-github.com/lucasdataproyects}"
# --- Validar nombre ---
if [[ -z "$MODULE_NAME" ]]; then
@@ -36,7 +41,7 @@ fi
# Normalizar nombre a kebab-case
MODULE_NAME=$(echo "$MODULE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
PROJECT_DIR="$TARGET_PATH/$MODULE_NAME"
GO_MODULE="github.com/lucasdataproyects/$MODULE_NAME"
GO_MODULE="${GO_NAMESPACE}/$MODULE_NAME"
# --- Check estado existente ---
if [[ -f "$PROJECT_DIR/go.mod" ]]; then
-232
View File
@@ -1,232 +0,0 @@
---
name: dagu-auto
description: Genera automatizaciones Dagu (DAGs YAML) - crea workflows, schedules y scripts en ~/dagu/. Usar en vez de cron para cualquier tarea programada.
argument-hint: [descripción de la automatización]
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
---
# dagu-auto
Genera una automatización completa en Dagu: DAG YAML + scripts necesarios.
**Preferimos Dagu sobre cron para TODA programación de tareas.**
## Sintaxis
```
/dagu-auto backup diario de base de datos
/dagu-auto ETL pipeline cada hora
/dagu-auto limpiar logs viejos cada domingo
/dagu-auto monitorear API cada 5 minutos
```
O Claude puede invocar esta skill cuando detecte que el usuario necesita programar/automatizar algo.
## Precondiciones
- [ ] Dagu instalado (`which dagu`)
- [ ] Directorio `~/dagu/dags/` existe
- [ ] Servicio dagu corriendo (`systemctl --user is-active dagu.service`)
Si no se cumplen, instalar y configurar primero:
```bash
# Instalar
curl -fsSL https://raw.githubusercontent.com/dagu-org/dagu/main/scripts/installer.sh | bash -s -- --install-dir ~/.local/bin
# Crear estructura
mkdir -p ~/dagu/{dags,scripts,logs,data}
# Configurar servicio
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/dagu.service << 'SVCEOF'
[Unit]
Description=Dagu Workflow Scheduler
After=network.target
[Service]
Type=simple
ExecStart=%h/.local/bin/dagu start-all --config=%h/dagu/dagu-config.yaml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
SVCEOF
systemctl --user daemon-reload
systemctl --user enable --now dagu.service
```
## Flujo
### 1. Analizar la solicitud
Determinar del input ($ARGUMENTS o descripción del usuario):
- **Qué automatizar**: la tarea concreta
- **Frecuencia**: cron expression (si aplica)
- **Dependencias**: pasos secuenciales o paralelos
- **Scripts necesarios**: bash, python, go, etc.
- **Variables/Parámetros**: configuración dinámica
### 2. Elegir tipo de DAG
| Situación | Tipo |
|-----------|------|
| Pasos secuenciales simples | `type: chain` (default) |
| Pasos con dependencias complejas | `type: graph` |
| Pasos paralelos | `type: graph` + `max_active_steps` |
| Sub-workflows reutilizables | `call:` + archivo separado |
### 3. Generar nombre del DAG
```
# Convención: snake_case, descriptivo, corto
backup_postgres_diario
etl_ventas_hora
limpieza_logs_semanal
monitor_api_health
```
### 4. Crear scripts auxiliares (si necesario)
Si el step requiere lógica compleja, crear script en `~/dagu/scripts/`:
```bash
# ~/dagu/scripts/nombre_script.sh
#!/bin/bash
set -euo pipefail
# Lógica aquí
```
Siempre dar permisos de ejecución: `chmod +x ~/dagu/scripts/nombre_script.sh`
### 5. Generar el DAG YAML
Crear en `~/dagu/dags/nombre.yaml`:
```yaml
name: nombre-descriptivo
description: Qué hace este workflow
tags: [categoria]
# Schedule (si aplica)
schedule: "expresion_cron"
# Variables de entorno
env:
- VAR_NECESARIA: valor
# Parámetros (si necesita configuración)
params:
- name: PARAM
type: string
default: valor
# Handlers
handler_on:
failure:
command: echo "FALLÓ: ${DAG_NAME}" >> ~/dagu/logs/failures.log
steps:
- id: paso_1
description: Qué hace este paso
command: echo "ejecutando"
- id: paso_2
command: bash ~/dagu/scripts/mi_script.sh
depends: [paso_1]
retry_policy:
limit: 3
interval_sec: 10
```
### 6. Validar
```bash
dagu validate ~/dagu/dags/nombre.yaml
```
Si falla, corregir y re-validar.
### 7. Probar ejecución
```bash
dagu start ~/dagu/dags/nombre.yaml
# O con parámetros:
dagu start ~/dagu/dags/nombre.yaml -- PARAM=valor
```
### 8. Confirmar resultado
Mostrar al usuario:
```
DAG creado: ~/dagu/dags/nombre.yaml
Schedule: cada día a las 2:00 AM
Steps: 3 pasos (graph mode)
Scripts: ~/dagu/scripts/nombre_script.sh
Web UI: http://localhost:8090
Validación: OK
Test: OK
```
## Referencia rápida de cron
| Expresión | Significado |
|-----------|-------------|
| `"*/5 * * * *"` | Cada 5 minutos |
| `"0 * * * *"` | Cada hora |
| `"0 */2 * * *"` | Cada 2 horas |
| `"0 9 * * *"` | Cada día a las 9:00 |
| `"0 2 * * *"` | Cada día a las 2:00 AM |
| `"0 9 * * MON-FRI"` | Lunes a viernes a las 9:00 |
| `"0 0 * * SUN"` | Cada domingo a medianoche |
| `"0 0 1 * *"` | Primer día de cada mes |
| `"CRON_TZ=America/Argentina/Buenos_Aires 0 9 * * *"` | Con timezone |
## Referencia rápida de tipos de step
| Tipo | Uso |
|------|-----|
| `command:` | Comando shell (default) |
| `type: http` | Petición HTTP (GET/POST/PUT/DELETE) |
| `type: ssh` | Ejecutar en servidor remoto |
| `type: jq` | Procesar JSON |
| `type: mail` | Enviar email |
| `type: chat` | LLM (OpenAI/Anthropic) |
| `type: router` | Condicional/branching |
| `type: archive` | Comprimir/descomprimir |
| `call:` | Sub-workflow |
## Convenciones
- Nombres de DAG en snake_case
- Siempre incluir `name`, `description`, `tags`
- Un handler_on.failure mínimo para logging
- Scripts en `~/dagu/scripts/` con `chmod +x`
- Validar siempre antes de activar schedule
- Usar `type: graph` cuando hay dependencias
- Usar `retry_policy` en steps que pueden fallar (HTTP, SSH)
- Usar `output:` para pasar datos entre steps
## Errores comunes (Dagu v2.3+)
| Error | Causa | Solución |
|-------|-------|----------|
| `use snake_case keys (dir -> working_dir)` | Usaste `dir:` en un step | Cambiar a `working_dir:` |
| `depends field is not allowed for DAGs with type 'chain'` | Usaste `depends:` sin declarar el tipo de DAG | Añadir `type: graph` al nivel raíz del DAG |
| `invalid step ID format: must match ^[a-zA-Z][a-zA-Z0-9_]*$` | Guiones medios en step IDs | Usar solo `snake_case` en IDs: `git_push`, no `git-push` |
**Regla de oro: SIEMPRE usar `working_dir` (no `dir`), `type: graph` si hay `depends`, y `snake_case` en step IDs.**
## Reglas
- SIEMPRE verificar que Dagu está instalado antes de crear DAGs
- SIEMPRE validar el DAG después de crearlo
- SIEMPRE usar `working_dir` para el directorio de trabajo de un step (NO `dir`)
- SIEMPRE usar `type: graph` cuando algún step tiene `depends`
- SIEMPRE usar snake_case en step IDs (`mi_paso`, no `mi-paso`)
- NUNCA crear crontabs — usar Dagu schedule en su lugar
- NUNCA usar rutas relativas en commands — usar rutas absolutas
- NUNCA hardcodear secretos — usar `secrets:` o `env:` con referencias
- Si el usuario pide "programar algo" o "ejecutar periódicamente", usar Dagu
+2 -1
View File
@@ -19,7 +19,8 @@ Skill para preparar cualquier repo para exploración de datos con Jupyter + Clau
```bash
# Obtener ruta del script (está junto a este SKILL.md)
SKILL_DIR="$HOME/DataProyects/repo_Claude/.claude/skills/init-jupyter"
# Resolver via symlink a la ubicación real del skill (portable entre máquinas)
SKILL_DIR="$(dirname "$(readlink -f "$HOME/.claude/skills/init-jupyter/SKILL.md")")"
# Ejecutar con la ruta del proyecto (argumento del skill o directorio actual)
bash "$SKILL_DIR/setup-jupyter.sh" "${1:-.}"
+70 -19
View File
@@ -84,9 +84,32 @@ Para cada wave, lanzar **Agents en paralelo** (un Agent por issue, todos en el m
El prompt de cada agente debe incluir:
1. **Ruta absoluta del worktree**: `/home/ubuntu/CodeProyects/agents_and_robots/worktrees/<slug>`
2. **Contenido completo del issue** (copiar el markdown entero)
3. **Instrucciones de ejecución** (ver template abajo)
1. **Ruta absoluta del worktree** (calcular con `$(git rev-parse --show-toplevel)/worktrees/<slug>`, o pasar la ruta literal ya resuelta)
2. **Build tag Go** del proyecto (detectar — ver "Detección del build tag" más abajo)
3. **Contenido completo del issue** (copiar el markdown entero)
4. **Instrucciones de ejecución** (ver template abajo)
#### Detección del stack y comandos build/test
Antes de lanzar los agentes, detectar el stack del proyecto y los comandos correspondientes. La skill es **agnostica del lenguaje**: soporta Go, C++, Rust, Node, Python o cualquier otro stack via override.
**Resolucion de comandos** (en orden de prioridad):
1. **Override explicito** del usuario (env vars `BUILD_CMD` y `TEST_CMD` o argumentos al invocar la skill).
2. **Manifest opcional** `.parallel-fix-issues.yml` en la raiz del repo:
```yaml
build: "cmake -S cpp -B cpp/build && cmake --build cpp/build -j"
test: "ctest --test-dir cpp/build --output-on-failure"
```
3. **Auto-deteccion** segun ficheros raiz:
- `go.mod` → `go build [-tags X] ./...` + `go test [-tags X] ./...` (X auto-detectado de `//go:build`)
- `CMakeLists.txt` (raiz o `cpp/`) → `cmake -S <dir> -B <dir>/build -DCMAKE_BUILD_TYPE=Release && cmake --build <dir>/build -j` + `ctest --test-dir <dir>/build --output-on-failure || true`
- `Cargo.toml` → `cargo build` + `cargo test`
- `package.json` → `npm run build --if-present` + `npm test --if-present`
- `pyproject.toml` / `setup.py` → (sin build) + `pytest`
4. Si nada se detecta, **preguntar al usuario** que comandos usar antes de continuar.
**Mostrar al usuario los comandos resueltos** y pedir confirmacion antes de seguir. Pasar tanto `BUILD_CMD` como `TEST_CMD` (ya resueltos) al prompt de cada agente.
#### Template de prompt para cada agente
@@ -95,11 +118,25 @@ Eres un agente de desarrollo implementando el issue <NNNN>-<slug>.
## Directorio de trabajo
Worktree: /home/ubuntu/CodeProyects/agents_and_robots/worktrees/<slug>
Worktree: <RUTA_ABSOLUTA_DEL_WORKTREE> # ej: /home/user/proyecto/worktrees/<slug>
Usa SIEMPRE esta ruta como prefijo en paths absolutos.
Variable de conveniencia para comandos:
W=/home/ubuntu/CodeProyects/agents_and_robots/worktrees/<slug>
W=<RUTA_ABSOLUTA_DEL_WORKTREE>
## Comandos build/test del proyecto
BUILD_CMD=<COMANDO_RESUELTO> # ej: "cmake -S cpp -B cpp/build && cmake --build cpp/build -j"
TEST_CMD=<COMANDO_RESUELTO> # ej: "ctest --test-dir cpp/build --output-on-failure"
Estos comandos ya estan resueltos por el orquestador (auto-deteccion, override o manifest
.parallel-fix-issues.yml). Usalos tal cual desde la raiz del worktree:
Bash({ command: "cd $W && eval \"$BUILD_CMD\"", dangerouslyDisableSandbox: true })
Bash({ command: "cd $W && eval \"$TEST_CMD\"", dangerouslyDisableSandbox: true })
Si el issue requiere comandos adicionales (ej. `./fn index` tras añadir funciones, `npm install`,
`uv sync`), ejecutalos antes/despues segun corresponda.
## Permisos
@@ -107,7 +144,7 @@ IMPORTANTE: En TODAS tus llamadas al tool Bash, usa el parámetro `dangerouslyDi
Esto es necesario porque estás ejecutando en paralelo con otros agentes y no hay usuario interactivo
para aprobar permisos. Ejemplo:
Bash({ command: "cd $W && go build -tags goolm ./...", dangerouslyDisableSandbox: true })
Bash({ command: "cd $W && $GO_BUILD", dangerouslyDisableSandbox: true })
## Issue a implementar
@@ -120,18 +157,20 @@ Sigue este flujo estrictamente:
1. **Leer el issue** — ya lo tienes arriba, entiende objetivo, tareas y arquitectura.
2. **Implementar todas las tareas** en orden:
- Respetar pure core / impure shell (pkg/ puro, shell/ impuro)
- Hacer commits atómicos por bloque lógico
- Respetar las convenciones del proyecto (pure core / impure shell si aplica)
- Hacer commits atomicos por bloque logico
- Prefijos: feat:, fix:, test:, docs:, refactor:, chore:
- NO hacer commits WIP ni código a medias
- NO hacer commits WIP ni codigo a medias
- Compilar frecuentemente:
Bash({ command: "cd $W && go build -tags goolm ./...", dangerouslyDisableSandbox: true })
Bash({ command: "cd $W && eval \"$BUILD_CMD\"", dangerouslyDisableSandbox: true })
3. **Tests obligatorios**:
- Escribir tests para todo código nuevo
3. **Tests obligatorios** (en el lenguaje/framework apropiado del stack):
- Escribir tests para todo codigo nuevo. Usar el framework convencional del lenguaje:
Go → testing pkg, C++ → ctest/Catch2/gtest, Rust → cargo test, Python → pytest, etc.
- Ejecutar:
Bash({ command: "cd $W && go test -tags goolm ./...", dangerouslyDisableSandbox: true })
Bash({ command: "cd $W && eval \"$TEST_CMD\"", dangerouslyDisableSandbox: true })
- NO continuar si los tests fallan
- Si el issue requiere paso de indexacion u otros (ej. `./fn index`, `npm install`), ejecutarlo aqui
4. **Cerrar el issue** — solo mover el archivo, NO tocar README:
- Bash({ command: "cd $W && git mv dev/issues/<NNNN>-<slug>.md dev/issues/completed/", dangerouslyDisableSandbox: true })
@@ -158,11 +197,22 @@ Después de cada wave, verificar TODOS los worktrees completados:
```
El script verifica:
- `go build -tags goolm ./...` — compila sin errores
- `go test -tags goolm ./...` — tests pasan
- `$BUILD_CMD` — compila sin errores (auto-detectado o pasado por env/arg)
- `$TEST_CMD` — tests pasan
- Issue movido a `dev/issues/completed/`
- Al menos 1 commit en la branch
Pasar `BUILD_CMD` y `TEST_CMD` como variables de entorno o argumentos posicionales:
```bash
BUILD_CMD="cmake --build cpp/build" TEST_CMD="ctest --test-dir cpp/build" \
.claude/skills/parallel-fix-issues/scripts/verify-worktree.sh worktrees/<slug>
# o posicionales
.claude/skills/parallel-fix-issues/scripts/verify-worktree.sh worktrees/<slug> "go build ./..." "go test ./..."
```
Si no se pasan, el script auto-detecta el stack (go.mod, CMakeLists.txt, Cargo.toml, package.json, pyproject.toml).
**Si un worktree falla verificación**:
1. Reportar al usuario qué falló
2. Preguntar si quiere: (a) intentar arreglar, (b) excluir ese issue, (c) abortar todo
@@ -181,13 +231,14 @@ El script hace para cada branch:
2. `git merge --no-ff issue/<slug>` con mensaje descriptivo
3. Si hay **merge conflict**: PARAR e informar al usuario
**Después de cada merge**, re-verificar que master compila:
**Despues de cada merge**, re-verificar que master compila usando los `BUILD_CMD`/`TEST_CMD` resueltos:
```bash
go build -tags goolm ./... && go test -tags goolm ./...
eval "$BUILD_CMD" && eval "$TEST_CMD"
```
Si falla después de un merge, PARAR e informar — no continuar con más merges.
`integrate-worktrees.sh` ya verifica el build post-merge si `BUILD_CMD` esta exportado.
Si falla despues de un merge, PARAR e informar — no continuar con mas merges.
### Fase 6: Actualizar README de issues
@@ -243,7 +294,7 @@ Ejecutar: git push
## Notas importantes
- **Siempre compilar con `-tags goolm`**
- **Stack agnostico**: la skill detecta el stack (Go, C++, Rust, Node, Python) en Fase 3. Si la auto-deteccion falla o el proyecto es exotico, el usuario puede pasar `BUILD_CMD`/`TEST_CMD` por env var o crear `.parallel-fix-issues.yml` en la raiz. Si el proyecto no tiene build/test, esos pasos se omiten con WARN
- **Siempre usar `dangerouslyDisableSandbox: true`** en todas las llamadas Bash de los agentes paralelos
- **Nunca hacer push automáticamente** — el usuario decide cuándo pushear
- **Si hay merge conflicts**, parar y pedir intervención manual
@@ -70,17 +70,21 @@ for slug in "$@"; do
echo "MERGED: ${branch}"
# Verificar que master sigue compilando
echo "--- Verificando build post-merge ---"
if ! (cd "$REPO_ROOT" && go build -tags goolm ./... 2>&1); then
echo ""
echo "FAIL: master no compila después de mergear ${branch}"
echo "Revertir con: git reset --hard HEAD~1"
echo "Investigar el problema antes de continuar."
FAILED_AT="$slug"
break
# Verificar que master sigue compilando (si BUILD_CMD esta definido)
if [ -n "${BUILD_CMD:-}" ]; then
echo "--- Verificando build post-merge ($BUILD_CMD) ---"
if ! (cd "$REPO_ROOT" && bash -c "$BUILD_CMD" 2>&1); then
echo ""
echo "FAIL: master no compila despues de mergear ${branch}"
echo "Revertir con: git reset --hard HEAD~1"
echo "Investigar el problema antes de continuar."
FAILED_AT="$slug"
break
fi
echo "OK: build post-merge exitoso"
else
echo "--- Build post-merge SKIPPED (BUILD_CMD no definido) ---"
fi
echo "OK: build post-merge exitoso"
MERGED=$((MERGED + 1))
done
@@ -1,32 +1,47 @@
#!/bin/bash
# verify-worktree.sh — Verifica build, tests y cierre de issue en un worktree
# verify-worktree.sh — Verifica build, tests y cierre de issue en un worktree.
#
# Uso: ./verify-worktree.sh <worktree-path>
# Ejemplo: ./verify-worktree.sh worktrees/0026-split-runtime
# Uso:
# ./verify-worktree.sh <worktree-path> [build-cmd] [test-cmd]
#
# Checks:
# 1. El worktree existe y tiene commits propios
# 2. go build -tags goolm ./... compila
# 3. go test -tags goolm ./... pasa
# 4. El issue fue movido a completed/
# Ejemplos:
# ./verify-worktree.sh worktrees/0026-foo
# ./verify-worktree.sh worktrees/0026-foo "go build -tags fts5 ./..." "go test -tags fts5 ./..."
# BUILD_CMD="cmake --build cpp/build" TEST_CMD="ctest --test-dir cpp/build" ./verify-worktree.sh worktrees/0026-foo
#
# Resolucion de comandos (en orden de prioridad):
# 1. Argumentos posicionales (build-cmd, test-cmd)
# 2. Variables de entorno BUILD_CMD / TEST_CMD
# 3. Archivo .parallel-fix-issues.yml en la raiz del worktree (claves: build, test)
# 4. Auto-deteccion segun ficheros del proyecto:
# - go.mod → "go build ./..." + "go test ./..."
# - CMakeLists.txt → "cmake -S . -B build && cmake --build build" + "ctest --test-dir build"
# - Cargo.toml → "cargo build" + "cargo test"
# - package.json → "npm run build" + "npm test"
# - pyproject.toml → "" + "pytest"
# 5. Si nada se detecta, salta build/test con WARN.
#
# Auto-deteccion adicional: si hay go.mod, intenta extraer build tag de //go:build.
#
# Exit codes:
# 0 = todo OK
# 1 = error de argumento
# 2 = build falló
# 2 = build fallo
# 3 = tests fallaron
# 4 = issue no cerrado
# 4 = issue no cerrado (solo WARN, no falla)
# 5 = sin commits propios
set -euo pipefail
if [ $# -lt 1 ]; then
echo "ERROR: se necesita el path del worktree"
echo "Uso: $0 <worktree-path>"
echo "Uso: $0 <worktree-path> [build-cmd] [test-cmd]"
exit 1
fi
WORKTREE="$1"
ARG_BUILD_CMD="${2:-}"
ARG_TEST_CMD="${3:-}"
# Resolver path absoluto
if [[ "$WORKTREE" != /* ]]; then
@@ -42,7 +57,62 @@ fi
SLUG="$(basename "$WORKTREE")"
echo "=== Verificando: ${SLUG} ==="
# --- Resolver build/test commands ---
BUILD_CMD="${ARG_BUILD_CMD:-${BUILD_CMD:-}}"
TEST_CMD="${ARG_TEST_CMD:-${TEST_CMD:-}}"
# Manifest opcional
MANIFEST="${WORKTREE}/.parallel-fix-issues.yml"
if [ -z "$BUILD_CMD" ] && [ -f "$MANIFEST" ]; then
M_BUILD=$(grep -E "^build:" "$MANIFEST" 2>/dev/null | sed -E 's/^build:[[:space:]]*"?([^"]*)"?[[:space:]]*$/\1/' | head -1 || true)
if [ -n "$M_BUILD" ]; then BUILD_CMD="$M_BUILD"; echo "INFO: build desde manifest"; fi
fi
if [ -z "$TEST_CMD" ] && [ -f "$MANIFEST" ]; then
M_TEST=$(grep -E "^test:" "$MANIFEST" 2>/dev/null | sed -E 's/^test:[[:space:]]*"?([^"]*)"?[[:space:]]*$/\1/' | head -1 || true)
if [ -n "$M_TEST" ]; then TEST_CMD="$M_TEST"; echo "INFO: test desde manifest"; fi
fi
# Auto-deteccion
if [ -z "$BUILD_CMD" ] || [ -z "$TEST_CMD" ]; then
AUTO_BUILD=""
AUTO_TEST=""
if [ -f "${WORKTREE}/go.mod" ]; then
# Detectar build tag
AUTO_TAG=$(grep -rh "^//go:build " --include="*.go" "$WORKTREE" 2>/dev/null \
| sed -E 's|^//go:build ([a-zA-Z0-9_]+).*|\1|' \
| sort -u | head -1 || true)
TAG_FLAG=""
[ -n "$AUTO_TAG" ] && TAG_FLAG="-tags $AUTO_TAG"
AUTO_BUILD="go build $TAG_FLAG ./..."
AUTO_TEST="go test $TAG_FLAG ./..."
echo "INFO: stack detectado: Go${TAG_FLAG:+ ($TAG_FLAG)}"
elif [ -f "${WORKTREE}/CMakeLists.txt" ] || ls "${WORKTREE}"/cpp/CMakeLists.txt >/dev/null 2>&1; then
CMAKE_DIR="."
[ -f "${WORKTREE}/cpp/CMakeLists.txt" ] && [ ! -f "${WORKTREE}/CMakeLists.txt" ] && CMAKE_DIR="cpp"
AUTO_BUILD="cmake -S ${CMAKE_DIR} -B ${CMAKE_DIR}/build -DCMAKE_BUILD_TYPE=Release && cmake --build ${CMAKE_DIR}/build -j"
AUTO_TEST="ctest --test-dir ${CMAKE_DIR}/build --output-on-failure || true"
echo "INFO: stack detectado: C++/CMake (dir=${CMAKE_DIR})"
elif [ -f "${WORKTREE}/Cargo.toml" ]; then
AUTO_BUILD="cargo build"
AUTO_TEST="cargo test"
echo "INFO: stack detectado: Rust"
elif [ -f "${WORKTREE}/package.json" ]; then
AUTO_BUILD="npm run build --if-present"
AUTO_TEST="npm test --if-present"
echo "INFO: stack detectado: Node"
elif [ -f "${WORKTREE}/pyproject.toml" ] || [ -f "${WORKTREE}/setup.py" ]; then
AUTO_BUILD="" # python normalmente no tiene build step
AUTO_TEST="pytest"
echo "INFO: stack detectado: Python"
else
echo "WARN: no se detecto stack; usar BUILD_CMD/TEST_CMD env o manifest .parallel-fix-issues.yml"
fi
[ -z "$BUILD_CMD" ] && BUILD_CMD="$AUTO_BUILD"
[ -z "$TEST_CMD" ] && TEST_CMD="$AUTO_TEST"
fi
# 1. Verificar commits propios
echo ""
echo "--- Commits propios ---"
COMMIT_COUNT=$(cd "$WORKTREE" && git log master..HEAD --oneline 2>/dev/null | wc -l)
if [ "$COMMIT_COUNT" -eq 0 ]; then
@@ -54,25 +124,33 @@ cd "$WORKTREE" && git log master..HEAD --oneline
# 2. Build
echo ""
echo "--- Build ---"
if (cd "$WORKTREE" && go build -tags goolm ./... 2>&1); then
echo "OK: build exitoso"
if [ -n "$BUILD_CMD" ]; then
echo "--- Build ($BUILD_CMD) ---"
if (cd "$WORKTREE" && bash -c "$BUILD_CMD" 2>&1); then
echo "OK: build exitoso"
else
echo "FAIL: build fallo"
exit 2
fi
else
echo "FAIL: build falló"
exit 2
echo "--- Build SKIPPED (sin comando) ---"
fi
# 3. Tests
echo ""
echo "--- Tests ---"
if (cd "$WORKTREE" && go test -tags goolm ./... 2>&1); then
echo "OK: tests pasaron"
if [ -n "$TEST_CMD" ]; then
echo "--- Tests ($TEST_CMD) ---"
if (cd "$WORKTREE" && bash -c "$TEST_CMD" 2>&1); then
echo "OK: tests pasaron"
else
echo "FAIL: tests fallaron"
exit 3
fi
else
echo "FAIL: tests fallaron"
exit 3
echo "--- Tests SKIPPED (sin comando) ---"
fi
# 4. Issue cerrado (movido a completed/)
# 4. Issue cerrado
echo ""
echo "--- Cierre de issue ---"
COMPLETED_FILES=$(cd "$WORKTREE" && git diff --name-only master -- dev/issues/completed/ 2>/dev/null | wc -l)
@@ -80,8 +158,7 @@ if [ "$COMPLETED_FILES" -gt 0 ]; then
echo "OK: issue movido a completed/"
cd "$WORKTREE" && git diff --name-only master -- dev/issues/completed/
else
echo "WARN: no se detectó issue movido a completed/ (verificar manualmente)"
# No es un error fatal — puede que el issue no siga la convención exacta
echo "WARN: no se detecto issue movido a completed/ (verificar manualmente)"
fi
echo ""
+1 -1
View File
@@ -187,4 +187,4 @@ El repo de secretos vive en `dataforge/pass-secrets`. Para operaciones que requi
- Siempre verificar precondiciones antes de operar
- Si `pass` o `gpg` no están instalados, dar los comandos de instalación para la distro detectada (no ejecutar sudo directamente)
- Ofrecer `pass git push` después de cada modificación
- El GPG-ID actual es `91324463`
- Para obtener el GPG-ID del usuario actual: leerlo de `~/.password-store/.gpg-id` (ese archivo lo crea `pass init <gpg-id>` y contiene el keygrip/ID en uso). Si no existe, listar claves con `gpg --list-secret-keys --keyid-format=long` y pedir al usuario cuál usar
+55
View File
@@ -0,0 +1,55 @@
---
name: sino
description: Modo respuesta corta — solo si/no/ok/nope/yes/no o frases muy breves para iterar dudas rapidas. One-shot.
argument-hint: [pregunta]
disable-model-invocation: true
user-invocable: true
---
# sino
Modo **respuesta minima** para iterar dudas rapidas. Una sola pregunta = una sola respuesta corta.
## Sintaxis
```
/sino <pregunta>
```
## Reglas de respuesta
- Output al usuario: **solo** una de estas formas:
- "si" / "no"
- "yes" / "no" / "nope" / "ok" / "yep"
- frase muy breve (≤ ~8 palabras) cuando un binario puro pierde info critica
- Puedes pensar / razonar internamente lo que necesites antes de responder.
- Puedes usar tools de lectura (Read/Grep/Glob/Bash read-only) si la pregunta requiere comprobar algo del repo antes de contestar.
- **NO** expliques, **NO** justifiques, **NO** añadas contexto, **NO** ofrezcas alternativas.
- **NO** preguntes de vuelta salvo que la pregunta sea literalmente incontestable; en ese caso responde "?" o "ambiguo".
## One-shot
Aplica SOLO al turno en que se invoca `/sino`. Siguiente turno = comportamiento normal sin necesidad de "stop sino".
## Ejemplos
```
/sino existe la funcion filter_slice_go_core?
→ si
/sino deberia mergear esta rama ya?
→ no
/sino kanban usa migraciones?
→ si
/sino esto es seguro borrar /var?
→ no, jamas
/sino que hora es?
→ ?
```
## Prioridad
Si el usuario despues pide explicacion ("por que?", "explica"), salir de `/sino` y responder normal en ese siguiente turno.
+171 -18
View File
@@ -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')
@@ -49,11 +73,27 @@ LINES_REMOVED=$(echo "$INPUT" | jq -r '.cost.total_lines_removed // 0')
# Rate Limits
RATE_5H=$(echo "$INPUT" | jq -r '.rate_limits.five_hour.used_percentage // 0' | xargs printf "%.0f")
RATE_7D=$(echo "$INPUT" | jq -r '.rate_limits.seven_day.used_percentage // 0' | xargs printf "%.0f")
RESET_5H_EPOCH=$(echo "$INPUT" | jq -r '.rate_limits.five_hour.resets_at // 0')
RESET_7D_EPOCH=$(echo "$INPUT" | jq -r '.rate_limits.seven_day.resets_at // 0')
# Git info (si estamos en un repo)
# Formatear resets (vacio si epoch=0)
RESET_5H=""
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). 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
@@ -81,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
@@ -190,23 +263,49 @@ LINE2="${LINE2} ${GRAY}│${RESET} ${DIM}Total:${RESET} ${CYAN}↓${TOTAL_IN_FMT
# 4. Rate Limits (siempre mostrar)
LINE2="${LINE2} ${GRAY}${RESET} ${BOLD}Limits:${RESET}"
# 5h limit
if [ "$RATE_5H" -ge 80 ]; then
LINE2="${LINE2} ${RED}5h:${RATE_5H}%${RESET}"
elif [ "$RATE_5H" -ge 50 ]; then
LINE2="${LINE2} ${YELLOW}5h:${RATE_5H}%${RESET}"
else
LINE2="${LINE2} ${GREEN}5h:${RATE_5H}%${RESET}"
fi
# Color por burndown vs tasa esperada
# Tasa: % consumible permitido por unidad de tiempo (5h: 20%/h, 7d: 14%/dia)
# expected = tasa * unidades_restantes_hasta_reset
# available = 100 - used%
# verde: available >= expected (consumo bajo control)
# amarillo: available >= expected/2 (consumo agresivo)
# rojo: available < expected/2 (riesgo de agotar antes del reset)
NOW_EPOCH=$(date +%s)
burndown_color() {
local used_pct=$1
local secs_left=$2
local rate=$3
local secs_per_unit=$4
local available=$((100 - used_pct))
if [ "$secs_left" -le 0 ]; then
printf "%s" "$GREEN"; return
fi
local expected
expected=$(echo "scale=2; $rate * $secs_left / $secs_per_unit" | bc)
if (( $(echo "$available >= $expected" | bc -l) )); then
printf "%s" "$GREEN"
elif (( $(echo "$available >= $expected / 2" | bc -l) )); then
printf "%s" "$YELLOW"
else
printf "%s" "$RED"
fi
}
# 7d limit
if [ "$RATE_7D" -ge 80 ]; then
LINE2="${LINE2} ${RED}7d:${RATE_7D}%${RESET}"
elif [ "$RATE_7D" -ge 50 ]; then
LINE2="${LINE2} ${YELLOW}7d:${RATE_7D}%${RESET}"
else
LINE2="${LINE2} ${GREEN}7d:${RATE_7D}%${RESET}"
fi
# 5h limit (tasa 20%/h)
SECS_5H=0
[ "$RESET_5H_EPOCH" -gt "$NOW_EPOCH" ] && SECS_5H=$((RESET_5H_EPOCH - NOW_EPOCH))
C5=$(burndown_color $RATE_5H $SECS_5H 20 3600)
RESET_5H_STR=""
[ -n "$RESET_5H" ] && RESET_5H_STR=" ${C5}${RESET_5H}${RESET}"
LINE2="${LINE2} ${C5}5h:${RATE_5H}%${RESET}${RESET_5H_STR} ${GRAY}${RESET}"
# 7d limit (tasa 14%/dia)
SECS_7D=0
[ "$RESET_7D_EPOCH" -gt "$NOW_EPOCH" ] && SECS_7D=$((RESET_7D_EPOCH - NOW_EPOCH))
C7=$(burndown_color $RATE_7D $SECS_7D 14 86400)
RESET_7D_STR=""
[ -n "$RESET_7D" ] && RESET_7D_STR=" ${C7}${RESET_7D}${RESET}"
LINE2="${LINE2} ${C7}7d:${RATE_7D}%${RESET}${RESET_7D_STR}"
# 5. Duración sesión
if [ "$SESSION_DURATION" -gt 0 ]; then
@@ -217,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
View File
@@ -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)"