Compare commits

37 Commits

Author SHA1 Message Date
egutierrez e355a65b5b Merge orq/dod-no-haiku: poblar goal de ejecutores + quitar regeneracion LLM del dod 2026-06-21 15:14:26 +02:00
integrador d4640a0660 fix(goal): poblar goal de ejecutores spawneados (statusline/FleetView sin objetivo)
spawn_fleet_agent pre-crea ~/.claude/goals/<sid>.json con solo
{role, parent_orchestrator} antes del primer prompt. goal_tracker.sh usaba
'el archivo existe' como proxy de 'objetivo definitivo', así que para esos
ejecutores nunca lanzaba goal_autogen: el goal quedaba vacío para siempre y el
statusline (LINE0) y FleetView mostraban '(sin objetivo)'.

Fix: 'definitivo' ahora exige .goal NO vacío (no solo que el archivo exista).
Cuando el archivo existe pero sin goal (ejecutor spawneado), se fija un goal
provisional PRESERVANDO role/parent_orchestrator y se lanza autogen, que lo pisa
con el definitivo. No regresiona el caso definitivo ni el primer-prompt sin
archivo (verificado con 3 casos).
2026-06-21 14:59:15 +02:00
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
egutierrez 3d77f4a5d2 merge: quick/install-commands — agregar commands a repo e instalación 2026-04-09 23:27:15 +02:00
egutierrez be97d03c97 feat: incluir commands en instalación
Se agrega "commands" al array FOLDERS de install.sh para que al instalar
se cree el symlink ~/.claude/commands -> repo/.claude/commands. Esto
permite que los commands del repo estén disponibles en cualquier proyecto
sin copiarlos manualmente.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 23:27:05 +02:00
egutierrez 31b28cf260 feat: agregar skill parallel-fix-issues
Nuevo skill que permite implementar múltiples issues en paralelo usando
git worktrees aislados y agentes concurrentes. Analiza dependencias,
verifica builds/tests e integra a master en orden. Incluye scripts
auxiliares en scripts/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 23:26:58 +02:00
egutierrez e6f24187b4 feat: agregar commands al repositorio
Se añaden 4 commands en .claude/commands/ que reemplazan a los skills
obsoletos con formato SKILL.md. Los commands usan el formato nativo de
Claude Code (.md en commands/) y cubren: create-issue, fix-issue,
git-branch y git-push. Esto simplifica la invocación y mantenimiento.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 23:26:51 +02:00
egutierrez b0a9e31abd chore: eliminar SKILL.md de skills obsoletos
Se eliminan los archivos SKILL.md y scripts asociados de 20 skills que
ya no se usan. Estos skills fueron reemplazados por commands (.claude/commands/)
o por skills nuevos con estructura actualizada. Limpieza necesaria para
evitar confusión entre skills activos y obsoletos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 23:26:44 +02:00
44 changed files with 2015 additions and 3479 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
+154
View File
@@ -0,0 +1,154 @@
# Command: create issue
Crea un issue nuevo en `dev/issues/` siguiendo **estrictamente** la regla `create_issue.md`. Si el issue es grande, lo desglosa automaticamente en sub-issues con feature flags.
## Inputs
Se necesitan los datos del issue. Si no se proporcionan, preguntar.
- `titulo`: titulo corto y descriptivo (ej: "Hot reload de configuracion")
- `descripcion`: objetivo/descripcion de lo que se quiere lograr
- `dependencias` (opcional): issues de los que depende (ej: "Requiere issue 0010")
## Flujo obligatorio
### 1. Determinar el numero del issue
Buscar el numero mas alto en `dev/issues/` y `dev/issues/completed/` y usar el siguiente.
Formato: 4 digitos con ceros a la izquierda (`0023`, `0024`, etc.).
```bash
ls dev/issues/ dev/issues/completed/ | grep -oP '^\d{4}' | sort -rn | head -1
```
### 2. Generar slug
A partir del titulo:
- Lowercase
- Palabras separadas por guiones
- Conciso (2-4 palabras)
- Ejemplo: "Hot reload de configuracion" → `hot-reload`
### 3. Evaluar tamano del issue
Antes de escribir el issue, analizar el alcance y determinar si cabe en **una sola rama corta (horas)**.
**Criterios para desglosar en sub-issues:**
- Toca mas de 2 capas del patron (pkg/ + shell/ + agents/ + tools/)
- Requiere mas de ~3 fases de implementacion
- El usuario lo indica explicitamente
- La descripcion implica multiples componentes independientes
**Si es un issue simple** (cabe en una rama):
- Crear un solo archivo `dev/issues/<NNNN>-<slug>.md`
- Seguir directo al paso 4
**Si es un issue grande** (necesita desglose):
- Crear el issue principal `dev/issues/<NNNN>-<slug>.md` con seccion `## Desglose multi-issue`
- Crear cada sub-issue como `dev/issues/<NNNN><letra>-<sub-slug>.md` (ej: `0023a-types`, `0023b-client`)
- Cada sub-issue es autocontenido: debe compilar, pasar tests, no romper master
- Agregar feature flag en la descripcion del issue principal
- Registrar todos los sub-issues en `dev/issues/README.md`
### 4. Crear el issue desde el template
Copiar `.claude/templates/issue.md` y rellenar **todas** las secciones:
- **Objetivo**: 1-3 frases claras
- **Contexto**: que existe, que falta, dependencias
- **Arquitectura**: archivos afectados (marcar `NEW` los nuevos). Explicar que va en `pkg/` (puro) vs `shell/` (impuro)
- **Tareas**: fases con tareas numeradas (`1.1`, `1.2`, etc.). Cada tarea concreta y verificable. Siempre incluir fase de tests y fase de cleanup/docs
- **Ejemplo de uso**: flujo concreto
- **Decisiones de diseno**: justificaciones clave
- **Prerequisitos**: que debe existir antes
- **Riesgos**: problemas potenciales y mitigacion
### 5. Para issues multi-issue — contenido adicional
En el issue principal, agregar despues de las tareas:
```markdown
## Desglose multi-issue
Este issue se implementa en sub-issues independientes, cada uno en su propia rama.
| Sub-issue | Rama | Alcance | Estado |
|-----------|------|---------|--------|
| <NNNN>a-<slug> | issue/<NNNN>a-<slug> | <que cubre> | pendiente |
| <NNNN>b-<slug> | issue/<NNNN>b-<slug> | <que cubre> | pendiente |
| ...
### Feature flag
Nombre: `<nombre-del-flag>`
Se activa en el ultimo sub-issue cuando todo esta integrado.
### Progreso por tarea
- [ ] **1.1** <tarea> — sub-issue <NNNN>a
- [ ] **1.2** <tarea> — sub-issue <NNNN>a
- [ ] **2.1** <tarea> — sub-issue <NNNN>b
...
```
Cada sub-issue individual debe tener su propio archivo con:
- Objetivo especifico del sub-issue
- Tareas que le corresponden del issue principal
- Nota de que es parte de un issue mayor
### 6. Registrar feature flag (solo multi-issue)
Actualizar `dev/feature_flags.json`:
```json
{
"<nombre-del-flag>": {
"enabled": false,
"issue": "<NNNN>",
"description": "<descripcion breve>",
"added": "<YYYY-MM-DD>"
}
}
```
### 7. Actualizar el indice
En `dev/issues/README.md`, agregar filas al final de la tabla.
**Issue simple:**
```markdown
| <N> | <Titulo> | [<NNNN>-<slug>.md](<NNNN>-<slug>.md) | pendiente |
```
**Issue multi-issue (agregar fila por cada sub-issue tambien):**
```markdown
| <N> | <Titulo> | [<NNNN>-<slug>.md](<NNNN>-<slug>.md) | pendiente |
| <N>a | <Titulo> (parte a) | [<NNNN>a-<slug>.md](<NNNN>a-<slug>.md) | pendiente |
| <N>b | <Titulo> (parte b) | [<NNNN>b-<slug>.md](<NNNN>b-<slug>.md) | pendiente |
```
### 8. Verificar
- [ ] Archivo(s) creado(s) en `dev/issues/`
- [ ] Todas las secciones del template rellenadas
- [ ] Fila(s) agregada(s) en `dev/issues/README.md`
- [ ] Numero de issue es consecutivo (sin saltos ni duplicados)
- [ ] Si es multi-issue: sub-issues creados, feature flag en `dev/feature_flags.json`, seccion de desglose en issue principal
### 9. Reportar al usuario
Mostrar resumen:
- Numero y titulo del issue
- Si fue desglosado: listar sub-issues con su alcance
- Recordar: usar `/fix-issue <NNNN>` (o `/fix-issue <NNNN>a`, `<NNNN>b`, etc.) para implementar
## Reglas criticas
- Seguir `create_issue.md` de forma estricta
- **Patron pure core / impure shell**: toda feature debe explicar que va en `pkg/` vs `shell/`
- **Tareas atomicas**: cada tarea debe ser implementable de forma independiente
- **Numeracion continua**: nunca reusar numeros
- **Estado**: issues nuevos siempre `pendiente`
- **Issues grandes**: desglosar en sub-issues con feature flags, nunca dejar una rama abierta por dias
- **Feature flag != WIP**: un flag protege codigo terminado y testeado, no codigo a medias
- **No commitear**: este comando solo crea archivos en `dev/issues/`. No hace commits ni crea ramas
+96
View File
@@ -0,0 +1,96 @@
# Command: fix issue
Ejecuta de punta a punta el flujo de implementacion/cierre de un issue siguiendo **estrictamente** la regla `fix_issue.md`.
## Inputs
Se necesita el issue objetivo. Si no se proporciona, preguntar.
- `issue`: numero o nombre (ej: `0010` o `0010-access-control`)
## Flujo obligatorio
1. Resolver el issue objetivo:
- Si viene solo numero (`0010`), buscar `dev/issues/0010-*.md`.
- Si viene slug completo (`0010-access-control`), usar `dev/issues/0010-access-control.md`.
- Si no existe en `dev/issues/`, **STOP** e informar al usuario.
- Si ya esta en `dev/issues/completed/`, **STOP** e informar al usuario.
2. Leer completo el issue y extraer:
- objetivo
- tareas/fases
- arquitectura y limites (pure core / impure shell)
3. Crear rama de trabajo (inline, sin invocar `/git-branch`):
Verificar la rama actual:
```bash
git branch --show-current
```
- Si ya estamos en `issue/<NNNN>-<slug>` que coincide con el issue → continuar directamente a paso 4.
- Si estamos en `master` o cualquier otra rama → crear la rama:
```bash
git checkout master
git pull --rebase
git checkout -b issue/<NNNN>-<slug>
```
Nunca trabajar directamente en `master`.
4. Planificar con `TodoWrite`:
- Crear plan basado en las tareas del issue.
- Respetar el orden de fases.
- Incluir siempre una tarea de tests.
5. Implementar el issue completo:
- Ejecutar tareas en orden.
- Respetar pure core / impure shell (`pkg/` puro, `shell/` impuro).
- Compilar frecuentemente: `go build -tags goolm ./...`.
- Marcar progreso en `TodoWrite` al completar cada bloque.
6. Tests obligatorios:
```bash
go test -tags goolm ./...
```
- Si falla, corregir antes de continuar.
- No cerrar el issue sin tests pasando.
7. Feature flags (si aplica):
- Evaluar si es feature multi-issue o despliegue gradual.
- Si aplica, actualizar `dev/feature_flags.json` en el commit correspondiente.
- No usar flags para esconder codigo incompleto.
8. Cerrar el issue al terminar:
```bash
mv dev/issues/<NNNN>-<slug>.md dev/issues/completed/
```
Actualizar `dev/issues/README.md`:
- Link a `completed/<NNNN>-<slug>.md`
- Estado a `completado`
9. Integrar/publicar con `/git-push`:
```text
/git-push
```
## Reglas criticas
- Seguir `fix_issue.md` de forma estricta.
- No saltear tareas del issue.
- No hacer commits WIP.
- Commits atomicos por bloque logico (`feat:`, `fix:`, `test:`, `docs:`, `refactor:`, `chore:`).
- Siempre usar `-tags goolm` en build/test.
+63
View File
@@ -0,0 +1,63 @@
# Command: git branch (TBD)
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.
## Uso
```
/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 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, sub-issues con sufijo letra:
```
issue/0015a-telegram-types
issue/0015b-telegram-client
issue/0015c-telegram-listener
```
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.
+130
View File
@@ -0,0 +1,130 @@
# Command: git push (TBD)
Integra cambios a master y publica. Soporta ramas `issue/*` y `quick/*`.
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
```bash
git branch --show-current
git status --short
```
#### Si estamos en `issue/*` o `quick/*`
Continuar al paso 2.
#### Si estamos en master con cambios pendientes
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>`.
**No inventar números de issue.** Solo usar `issue/` si existe en `dev/issues/`.
#### Si estamos en master sin cambios
**STOP**: nada que publicar.
### 2. Revisar cambios y crear commits atómicos
```bash
git status --short
git diff --stat
git diff
```
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 "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 "Descripción larga en español."
```
**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. Comando depende del stack:
| 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 fallan → **STOP** y corregir. Si pasan → paso 4.
### 4. Evaluar feature flags
Feature flag = código terminado y testeado, **no** código a medias.
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).
Si autocontenido, saltar.
### 5. Cerrar la rama (registry)
```bash
# Path portable (cualquier PC): FN_REGISTRY_ROOT si está, si no ~/fn_registry.
# Se invoca con `bash` (no `source`): el script tiene un entry point que llama a
# tbd_branch_finish con los argumentos cuando se ejecuta directamente, y así
# funciona aunque la shell de la sesión sea zsh (evita el fallo de BASH_SOURCE).
bash "${FN_REGISTRY_ROOT:-$HOME/fn_registry}/bash/functions/infra/tbd_branch_finish.sh" "<descripción corta del merge>"
```
La función:
- 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>`.
### 6. Confirmar al usuario
La función ya imprime `Rama '<rama>' integrada a <base> y publicada. Rama local eliminada.` Repetirlo al usuario.
## Convención de commits
- `feat:` nueva funcionalidad
- `fix:` corrección de error
- `refactor:` cambio estructural sin cambio funcional
- `docs:` documentación
- `chore:` mantenimiento
- `test:` tests nuevos o modificados
- `merge:` commit de merge (lo genera `tbd_branch_finish` con `--no-ff`)
## Regla de mensajes
- Título corto resume el bloque.
- Cuerpo en español: qué se cambió, por qué, qué impacto, qué no se tocó.
## Checklist
- [ ] 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).
- [ ] `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
+144
View File
@@ -0,0 +1,144 @@
#!/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"
GOAL_NOW=""
if [ -f "$F" ]; then
PROV=$(jq -r '.provisional // false' "$F" 2>/dev/null)
GOAL_NOW=$(jq -r '.goal // ""' "$F" 2>/dev/null)
fi
# "Objetivo definitivo" = archivo con goal NO vacio y no provisional. El check de
# goal no vacio es clave para los ejecutores lanzados por spawn_fleet_agent: su
# goal.json se PRE-CREA con solo {role, parent_orchestrator} (sin goal). Sin este
# guard, el hook tomaria ese archivo como objetivo definitivo y nunca lanzaria
# autogen, dejando el goal vacio para siempre (statusline y FleetView sin objetivo).
if [ -f "$F" ] && [ "$PROV" != "true" ] && [ -n "$GOAL_NOW" ]; then
G=$(jq -r '.goal // ""' "$F" 2>/dev/null)
P=$(jq -r '.phase // ""' "$F" 2>/dev/null)
D=$(jq -r '.dod // ""' "$F" 2>/dev/null)
echo "GOAL-TRACKER: file=$F | goal=\"$G\" dod=\"$D\" phase=\"$P\". El objetivo es fijo (identificativo de la terminal, NO lo cambies). El DoD inicial lo fija el autogen una vez (sin LLM por prompt); el usuario lo ajusta con \"dod: ...\" — NO lo regeneres tu. La fase la mantienen los hooks (PostToolUse=activo, Stop=reposo) — NO la escribas. Comandos meta del usuario (no los uses tu): objetivo:/dod:/pausa."
else
# Sin objetivo definitivo todavia. Mostramos de inmediato un objetivo PROVISIONAL
# igual a tu propio texto (truncado), para que el statusline no quede vacio
# mientras haiku genera el real en background. autogen pisara este provisional
# con el definitivo al terminar (su guard respeta .provisional).
if [ "${#PROMPT_TRIM}" -ge 12 ]; then
TMP="${F}.tmp.$$"
PROV_GOAL=$(printf '%s' "$PROMPT_TRIM" | head -c 70)
if [ -n "$GOAL_NOW" ]; then
# Ya habia goal provisional: conserva su goal, solo acumula el prompt.
jq --arg p "$PROMPT_TRIM" '.prompts = ((.prompts // []) + [$p])[-12:]' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F" || rm -f "$TMP"
elif [ -f "$F" ]; then
# Archivo PRE-CREADO por spawn_fleet_agent ({role, parent_orchestrator})
# sin goal: fija el provisional PRESERVANDO los campos existentes (role,
# parent_orchestrator) y deja que autogen lo pise con el definitivo.
jq --arg g "$PROV_GOAL" --arg p "$PROMPT_TRIM" \
'. + {goal:$g, phase:"planificando", history:["planificando"], prompts:[$p], provisional:true}' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F" || rm -f "$TMP"
else
jq -n --arg g "$PROV_GOAL" --arg p "$PROMPT_TRIM" \
'{goal:$g, phase:"planificando", history:["planificando"], prompts:[$p], provisional:true}' > "$TMP" 2>/dev/null && mv "$TMP" "$F" || rm -f "$TMP"
fi
nohup bash "$HOME/.claude/hooks/goal_autogen.sh" "$SID" "$F" "$PROMPT" >/dev/null 2>&1 &
fi
echo "GOAL-TRACKER: file=$F (objetivo PROVISIONAL = tu texto; generando el objetivo+DoD real con haiku en background). El usuario tambien puede fijarlo con \"objetivo: ...\" / \"dod: ...\"."
fi
exit 0
+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": { "permissions": {
"allow": [ "allow": [
"Edit(~/.claude/**)", "Edit(~/.claude/**)",
"Write(~/.claude/**)", "Write(~/.claude/**)",
"Edit(.claude/**)", "Edit(.claude/**)",
"Write(.claude/**)" "Write(.claude/**)",
"Bash(CGO_ENABLED=1 go test *)",
"Bash(sqlite3 *)",
"Read(//home/enmanuel/.claude/**)"
], ],
"deny": [ "deny": [
"Edit(~/.claude/.git/**)", "Edit(~/.claude/.git/**)",
"Write(~/.claude/.git/**)", "Write(~/.claude/.git/**)",
"Edit(.git/**)", "Edit(.git/**)",
"Write(.git/**)" "Write(.git/**)"
],
"defaultMode": "dontAsk"
},
"hooks": {
"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
} }
-75
View File
@@ -1,75 +0,0 @@
---
name: auto-create
description: Crea un issue nuevo e integra automáticamente SIN pedir confirmación
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write, Edit
---
# auto-create
Crea un issue nuevo y lo integra automáticamente **sin pedir confirmación**.
## Sintaxis
```bash
/auto-create
```
## Diferencia con /create-issue
Este comando NO pausa para confirmación. Solicita datos pero integra automáticamente.
## Flujo
### 1-7. Crear issue (igual que /create-issue)
1. Determinar número
2. Solicitar inputs (titulo, descripción)
3. Generar slug
4. Evaluar tamaño
5. Crear desde template
6. Feature flag (si aplica)
7. Actualizar índice
**Sin confirmación** - continuar directamente.
### 8. Integración automática
```bash
git checkout master
git pull --rebase
git checkout -b quick/create-issue-<NNNN>
# Commit
git add dev/issues/<NNNN>*.md dev/issues/README.md
git commit -m "docs: crear issue <NNNN>-<slug>"
# Si multi-issue, commit de feature flag
git add dev/feature_flags.json
git commit -m "feat: agregar feature flag <nombre>"
# Tests (si aplican)
go test -tags goolm ./...
# Merge
git checkout master
git merge --no-ff quick/create-issue-<NNNN>
git push
git branch -d quick/create-issue-<NNNN>
```
### 9. Mostrar resultado
```
Issue <NNNN>-<slug> creado e integrado automáticamente
Para implementar:
/fix-issue <NNNN>
```
## Convenciones
- Sin confirmación
- Mismo formato que /create-issue
- Trunk-based con rama quick/
-75
View File
@@ -1,75 +0,0 @@
---
name: auto-fix
description: Implementa un issue completo automáticamente SIN pedir confirmación
argument-hint: <NNNN>
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, TodoWrite
---
# auto-fix
Implementa un issue completo automáticamente **sin pedir confirmación** antes de integrar.
## Sintaxis
```bash
/auto-fix <NNNN>
/auto-fix <NNNN>-<slug>
```
## Diferencia con /fix-issue
Este comando NO pausa para confirmación. Ejecuta todo el flujo automáticamente.
**Usar cuando:** estés completamente seguro de que el issue puede implementarse automáticamente.
## Flujo
### 1-8. Implementar (igual que /fix-issue)
1. Resolver issue objetivo
2. Leer issue completo
3. Crear rama `issue/<NNNN>-<slug>`
4. Planificar con TodoWrite
5. Implementar completo
6. Tests obligatorios
7. Feature flags (si aplica)
8. Cerrar issue
**Sin confirmación** - continuar directamente.
### 9. Integración automática
```bash
git checkout master
git pull --rebase
go test -tags goolm ./... # verificación final
git merge --no-ff issue/<NNNN>-<slug> -m "merge: issue/<NNNN>-<slug>"
git push
git branch -d issue/<NNNN>-<slug>
```
### 10. Mostrar resultado
```
Issue <NNNN> completado e integrado automáticamente
Commits integrados: N
Tests: pasando
Issue: movido a completed/
NOTA: Integración automática sin confirmación.
```
## Convenciones
- Sin confirmación (diferencia clave)
- Misma calidad que /fix-issue
- STOP si tests fallan
## Reglas
- NO pedir confirmación
- MISMA calidad que /fix-issue
- STOP si tests fallan (no integrar código roto)
-74
View File
@@ -1,74 +0,0 @@
---
name: cleanup-worktrees
description: Limpia worktrees y ramas locales después de merge
argument-hint: <issue_number> | --all
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read
---
# cleanup-worktrees
Elimina worktrees y sus ramas locales asociadas después de haber sido mergeadas.
## Sintaxis
```bash
/cleanup-worktrees <NNNN> # Limpiar worktree específico
/cleanup-worktrees --all # Limpiar todos
```
## Flujo
### 1. Validar argumentos
- Número de issue (4 dígitos): limpiar ese worktree
- `--all`: limpiar todos en `worktrees/`
### 2. Determinar worktrees a limpiar
```bash
# Para issue específica
WORKTREE_PATH="worktrees/issue-$ISSUE_NUM"
# Para --all
find worktrees -maxdepth 1 -type d -name "issue-*"
```
### 3. Confirmar con usuario
```
Se eliminarán:
- worktrees/issue-0003 (rama: quick/fix-issue-0003)
¿Continuar? (y/N):
```
### 4. Limpiar cada worktree
Para cada uno:
1. Verificar si rama fue mergeada
2. Si NO mergeada: advertir y preguntar
3. Eliminar worktree: `git worktree remove <path> --force`
4. Eliminar rama: `git branch -D <branch>`
### 5. Reportar resultado
```
Limpieza completada
Worktrees restantes:
(ninguno)
```
## Convenciones
- Nomenclatura worktrees: `worktrees/issue-NNNN`
- Nomenclatura ramas: `quick/fix-issue-NNNN`
- Confirmación interactiva siempre
## Reglas
- SIEMPRE verificar merge antes de eliminar
- NUNCA eliminar sin confirmación
- SIEMPRE usar --force en worktree remove
-439
View File
@@ -1,439 +0,0 @@
---
name: create-agent
description: Crea un nuevo agente especializado en .claude/agents/ con su SKILL.md y estructura completa
argument-hint: [nombre]
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, AskUserQuestion
---
# create-agent
Crea un nuevo agente especializado en `.claude/agents/` con archivo `SKILL.md` obligatorio siguiendo la estructura oficial de Claude Code.
## Sintaxis
```bash
/create-agent [nombre]
/create-agent api-client
/create-agent cloud-deploy
```
## Precondiciones
- [ ] Carpeta `.claude/agents/` existe
- [ ] No existe agente con el mismo nombre
- [ ] Nombre cumple convenciones (minúsculas, guiones)
## Flujo
### 1. Validar nombre
- Solo minúsculas, números y guiones
- No nombres reservados (help, clear, exit)
- Máximo 64 caracteres
```bash
ls -d .claude/agents/*/ 2>/dev/null | xargs -n1 basename | grep -E "^${nombre}$"
```
Si existe, STOP.
### 2. Solicitar inputs usando AskUserQuestion
Usar `AskUserQuestion` para obtener:
#### Input 1: Información básica
- **nombre**: minúsculas y guiones (ej: `api-client`)
- **descripcion**: qué hace el agente y cuándo invocarlo (1-2 frases claras)
#### Input 2: Configuración técnica
- **model**: Modelo Claude a usar
- `sonnet` (default): Balance costo/capacidad
- `opus`: Tareas complejas que requieren máximo razonamiento
- `haiku`: Tareas simples y rápidas
- **tools**: Herramientas necesarias (separadas por coma)
- Default: `Read, Write, Bash, Glob, Grep, Edit`
- Opcionales: `WebFetch, WebSearch, NotebookEdit`
#### Input 3: Configuración de proyecto
- **gestiona_repo**: ¿Gestiona un repositorio local?
- `si`: Crear carpeta en `~/.local_agentes/`
- `no`: Solo definición de agente
- **usa_mcp**: ¿Usa MCP servers? (gitea, sqlite, etc)
- `si`: Solicitar configuración de MCP
- `no`: Omitir mcpServers
#### Input 4: MCP Servers (si usa_mcp = si)
Preguntar qué MCP servers necesita:
- `gitea`: Gestión de repositorios Gitea
- `sqlite`: Bases de datos SQLite
- `filesystem`: Sistema de archivos
- `otro`: Configuración personalizada
#### Input 5: Documentación
- **rol**: Rol del agente (ej: "Eres un experto en...")
- **capacidades**: Lista de capacidades principales
- **flujo_trabajo**: Descripción del flujo de trabajo típico
- **ejemplos_uso**: Ejemplos de cuándo invocar al agente
### 3. Crear carpeta del agente
```bash
mkdir -p .claude/agents/${nombre}
```
### 4. Crear carpeta local (si gestiona_repo = si)
```bash
mkdir -p ~/.local_agentes/${nombre}
```
### 5. Generar frontmatter YAML
Estructura base:
```yaml
---
name: ${nombre}
description: ${descripcion}
model: ${model}
tools: ${tools}
mcpServers: # Solo si usa_mcp = si
- ${mcp_config}
---
```
### 6. Generar SKILL.md completo
Template oficial de agente:
```markdown
---
name: ${nombre}
description: ${descripcion}
model: ${model}
tools: ${tools}
${mcp_servers_section}
---
# Agente ${nombre}
${rol}
## Tu entorno
${descripcion_entorno}
## Capacidades principales
${capacidades}
## Flujo de trabajo
${flujo_trabajo}
## Templates disponibles
${templates_codigo}
## Integración con otros agentes
${integracion}
## Ejemplos de uso
${ejemplos_uso}
## Comandos útiles
${comandos}
## Notas y convenciones
${notas}
```
### 7. Templates de MCP Servers
#### Gitea MCP
```yaml
mcpServers:
- gitea:
type: stdio
command: gitea-mcp
args:
- -t
- stdio
- --host
- "${GITEA_URL}"
- --token
- "${GITEA_TOKEN}"
```
#### SQLite MCP
```yaml
mcpServers:
- sqlite:
type: stdio
command: sqlite-mcp
args:
- --db
- "${DB_PATH}"
```
#### Filesystem MCP
```yaml
mcpServers:
- filesystem:
type: stdio
command: mcp-server-filesystem
args:
- --allowed-directories
- "${ALLOWED_DIR}"
```
### 8. Mostrar y confirmar
```
Agente creado: ${nombre}
Ubicación: .claude/agents/${nombre}/SKILL.md
${carpeta_local ? "Carpeta local: ~/.local_agentes/" + nombre : ""}
Configuración:
- Model: ${model}
- Tools: ${tools}
- MCP: ${usa_mcp ? "Sí" : "No"}
- Repositorio local: ${gestiona_repo ? "Sí" : "No"}
¿Te parece bien?
- Si correcto: commit e integrar automáticamente
- Si ajustes: edita manualmente antes de integrar
```
### 9. Crear README.md en carpeta local (si gestiona_repo = si)
```bash
cat > ~/.local_agentes/${nombre}/README.md <<EOF
# ${nombre}
${descripcion}
## Estructura del proyecto
\`\`\`
~/.local_agentes/${nombre}/
├── README.md
├── CLAUDE.md # Instrucciones para Claude
└── ... # Archivos del proyecto
\`\`\`
## Sincronización con Gitea
\`\`\`bash
cd ~/.local_agentes/${nombre}
git remote add origin \${GITEA_URL}/\${user}/${nombre}.git
git push -u origin main
\`\`\`
## Uso
Invocar con:
\`\`\`
Hablar con el agente ${nombre} para [tarea]
\`\`\`
EOF
```
### 10. Ejecutar /git-push
Si confirma, crear rama `quick/create-agent-${nombre}` e integrar.
### 11. Verificar disponibilidad
```
Agente "${nombre}" creado e integrado
El agente está disponible en:
.claude/agents/${nombre}/SKILL.md
${gestiona_repo ? "Carpeta de trabajo:\n ~/.local_agentes/" + nombre : ""}
Para usar, solicita al usuario:
"Trabajar con el agente ${nombre} para [tarea]"
Configuración:
- Model: ${model}
- Tools: ${tools}
- MCP Servers: ${usa_mcp ? "Sí" : "No"}
```
## Campos del frontmatter de agentes
| Campo | Descripción | Requerido |
|-------|-------------|-----------|
| name | Nombre del agente | Sí |
| description | Qué hace y cuándo invocarlo | Sí |
| model | Model Claude (sonnet, opus, haiku) | Sí |
| tools | Herramientas disponibles | Sí |
| mcpServers | Servidores MCP (opcional) | No |
## Estructura de documentación de agentes
Seguir este orden en el contenido Markdown:
1. **Título y rol**: Descripción del rol del agente
2. **Tu entorno**: Dónde trabaja, qué repositorios gestiona
3. **Capacidades principales**: Lista de lo que puede hacer
4. **Flujo de trabajo**: Cómo abordar tareas típicas
5. **Templates disponibles**: Código de ejemplo
6. **Integración con otros agentes**: Cómo colabora con otros
7. **Ejemplos de uso**: Cuándo invocar al agente
8. **Comandos útiles**: Comandos CLI relevantes
9. **Notas y convenciones**: Reglas y mejores prácticas
## Convenciones
- Nombres descriptivos con guiones (ej: `api-client`, `cloud-deploy`)
- Descripciones claras para invocación automática por Claude
- Un agente por dominio/especialización
- Documentación completa con ejemplos prácticos
- Templates de código cuando sea aplicable
## Diferencia entre Agentes y Skills
| Característica | Agente | Skill |
|----------------|--------|-------|
| Ubicación | `.claude/agents/` | `.claude/skills/` |
| Propósito | Experto especializado | Automatización/herramienta |
| Invocación | Claude decide cuándo | Usuario con `/nombre` |
| Contenido | Conocimiento de dominio | Flujo de trabajo |
| Ejemplo | `backend-lib`, `docker` | `git-push`, `create-issue` |
## Ejemplos de agentes
### Agente simple (sin repo ni MCP)
```yaml
---
name: code-review
description: Agente para revisar código y sugerir mejoras
model: sonnet
tools: Read, Grep, Glob
---
# Agente Code Review
Eres un experto en revisión de código...
```
### Agente complejo (con repo y MCP)
```yaml
---
name: api-client
description: Agente para generar clientes API desde especificaciones OpenAPI
model: sonnet
tools: Read, Write, Bash, Glob, Grep, Edit
mcpServers:
- gitea:
type: stdio
command: gitea-mcp
args:
- -t
- stdio
- --host
- "${GITEA_URL}"
- --token
- "${GITEA_TOKEN}"
---
# Agente API Client
Eres un experto en generación de clientes API...
## Tu entorno
- **Repositorio**: `Bl4cksmith/api-clients` (Gitea)
- **Carpeta local**: `~/.local_agentes/api-client`
- **Stack**: TypeScript, Go, Python
...
```
## Reglas
- Validar nombre antes de crear
- SKILL.md es obligatorio
- Confirmación antes de integrar
- Crear carpeta local solo si gestiona_repo = si
- MCP servers solo si usa_mcp = si
- Documentación completa y con ejemplos
## Integración con otros agentes
### Con gitea
```bash
# Crear repositorio para el agente (si gestiona_repo = si)
cd ~/.local_agentes/${nombre}
git init
git add .
git commit -m "Initial commit"
# Usar agente gitea para crear repo y push
```
### Con backend-lib / frontend-lib
```markdown
# En SKILL.md del nuevo agente, documentar integración:
## Integración con otros agentes
### Con backend-lib (DevFactory)
- Usar DevFactory para estructuras funcionales Go
- Integrar via go.work
### Con frontend-lib
- Usar Frontend_Library para componentes React
- Integrar via pnpm link
```
## Variables dinámicas
| Variable | Descripción |
|----------|-------------|
| ${nombre} | Nombre del agente |
| ${descripcion} | Descripción del agente |
| ${model} | Model Claude |
| ${tools} | Herramientas disponibles |
| ${CLAUDE_SKILL_DIR} | Ruta del skill |
## Flujo completo de ejemplo
```bash
# Usuario invoca
/create-agent api-client
# Skill valida nombre
✓ Nombre válido
# Skill pregunta configuración con AskUserQuestion
? Descripción: Agente para generar clientes API desde OpenAPI
? Model: sonnet
? Tools: Read, Write, Bash, Glob, Grep, Edit
? ¿Gestiona repositorio?: Sí
? ¿Usa MCP?: Sí
? MCP servers: gitea
# Skill crea estructura
✓ Carpeta creada: .claude/agents/api-client/
✓ Carpeta local creada: ~/.local_agentes/api-client/
✓ SKILL.md generado
✓ README.md generado
# Skill confirma
Agente "api-client" creado
# Skill integra con git
✓ Rama: quick/create-agent-api-client
✓ Commit: "feat: crear agente api-client"
✓ Push exitoso
```
-99
View File
@@ -1,99 +0,0 @@
---
name: create-issue
description: Crea un issue nuevo en dev/issues/ con confirmación del usuario
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write, Edit
---
# create-issue
Crea un issue nuevo con estructura completa. Si es grande, lo desglosa en sub-issues con feature flags.
## Sintaxis
```bash
/create-issue
```
## Precondiciones
- [ ] Directorio `dev/issues/` existe
- [ ] Template `.claude/templates/issue.md` existe
## Flujo
### 1. Determinar número del issue
```bash
ls dev/issues/ dev/issues/completed/ | grep -oP '^\d{4}' | sort -rn | head -1
```
Próximo issue = número_más_alto + 1 (formato 4 dígitos)
### 2. Solicitar inputs
- `titulo`: título corto y descriptivo
- `descripcion`: objetivo de lo que se quiere lograr
- `dependencias` (opcional): issues de los que depende
### 3. Generar slug
Título → lowercase → palabras separadas por guiones → 2-4 palabras
### 4. Evaluar tamaño
**Criterios para sub-issues:**
- Toca más de 2 capas (core/ + shell/ + app/)
- Requiere más de 3 fases
- El usuario lo indica
**Issue simple:** crear un archivo `dev/issues/<NNNN>-<slug>.md`
**Issue grande:** crear SOLO sub-issues `<NNNN>a-`, `<NNNN>b-`, etc.
### 5. Crear desde template
Usar template en `${CLAUDE_SKILL_DIR}/issue.md` y rellenar todas las secciones:
- Metadata, Objetivo, Contexto
- Arquitectura, Patrón pure/impure
- Tareas, Ejemplo de uso
- Criterios de aceptación
### 6. Feature flag (solo multi-issue)
Actualizar `dev/feature_flags.json`:
```json
{
"<nombre-flag>": {
"enabled": false,
"issue": "<NNNN>",
"description": "..."
}
}
```
### 7. Actualizar índice
En `dev/issues/README.md` agregar fila(s).
### 8. Mostrar y confirmar
```
Issue creado: <NNNN>-<slug>
¿Te parece bien?
- Si es correcto: commit y push automáticamente
- Si necesitas ajustes: edita manualmente
```
### 9. Ejecutar /git-push automáticamente
Si confirma, crear rama `quick/create-issue-<NNNN>` y ejecutar flujo git.
## Convenciones
- Numeración continua sin saltos
- Estado inicial: pendiente
- Issues cortos (horas por rama)
- Sub-issues autocontenidos
-128
View File
@@ -1,128 +0,0 @@
# NNNN — [Título de la Issue]
## Metadata
| Campo | Valor |
|-------|-------|
| **ID** | NNNN |
| **Estado** | 🟡 pendiente / 🔵 en progreso / ✅ completado / 🔴 bloqueado |
| **Prioridad** | alta / media / baja |
| **Tipo** | feature / bugfix / refactor / docs / infrastructure |
## Dependencias
<!-- Issues que DEBEN estar completadas antes de empezar esta -->
| ID | Título | Estado | Requerido |
|----|--------|--------|-----------|
| 0001 | Actualizar nombre del módulo | ✅ | Sí |
| 0002 | Implementar core/ | ✅ | Sí |
**Bloqueada por:** `#0001, #0002`
**Desbloquea:** `#0006, #0007`
> **⚠️ VALIDACIÓN AUTOMÁTICA**: Esta issue no puede iniciarse hasta que todas las dependencias estén en estado `✅ completado`.
---
## Objetivo
[Descripción concisa de qué se quiere lograr en 1-3 oraciones]
## Contexto
- [Punto de contexto 1]
- [Punto de contexto 2]
- [Referencias a otras issues o decisiones previas]
## Arquitectura
```
[Estructura de archivos afectados]
dir/
├── file1.go — Descripción
├── file2.go — NEW: Nuevo archivo
└── file3.go — MODIFY: Modificación
```
### Patrón pure core / impure shell
- `core/` — [Qué funciones puras se agregan]
- `shell/` — [Qué operaciones I/O se implementan]
- `app/` — [Cómo se orquesta]
## Tareas
### Fase 1: [Nombre de fase]
- [ ] **1.1** [Descripción detallada de tarea]
- [ ] **1.2** [Otra tarea]
### Fase 2: [Otra fase]
- [ ] **2.1** [Tarea]
- [ ] **2.2** [Tarea]
### Fase N: Cleanup y docs
- [ ] Actualizar `README.md` con cambios relevantes
- [ ] Actualizar `CLAUDE.md` si hay cambios arquitectónicos
- [ ] Ejecutar `go mod tidy`
- [ ] Ejecutar `go test ./...`
- [ ] Actualizar issue en `dev/issues/README.md`
---
## Ejemplo de uso
```bash
# Comandos de ejemplo
comando ejemplo arg1 arg2
# Output esperado:
# ✓ Success message
```
```go
// Código de ejemplo si aplica
package example
func Example() {}
```
## Decisiones de diseño
- **Decisión 1**: Razón y trade-offs
- **Decisión 2**: Alternativas consideradas y por qué se eligió esta
## Prerequisitos
- Issue #NNNN completado
- Herramienta X instalada
- Configuración Y realizada
## Riesgos
- **Riesgo 1**: Descripción del riesgo. **Mitigación**: Cómo se mitigará
- **Riesgo 2**: Otro riesgo. **Mitigación**: Plan de mitigación
## Criterios de aceptación
- [ ] Todos los tests pasan
- [ ] Feature flag agregado en `feature_flags.json`
- [ ] Documentación actualizada
- [ ] Code review aprobado
- [ ] Deployable a main
---
## Notas de implementación
[Notas que surjan durante la implementación, decisiones tomadas, problemas encontrados]
## Referencias
- [Link a documentación relevante]
- [Link a PRs relacionados]
- [Link a discusiones]
-73
View File
@@ -1,73 +0,0 @@
---
name: create-repo
description: Crea un nuevo subrepo en workspaces/ con estructura core/shell/app
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write
---
# create-repo
Crea un nuevo workspace (subrepo) con estructura estándar, repo en Gitea, y registro en BD.
## Prerequisitos
- Variables: `GITEA_URL` y `GITEA_TOKEN`
- Feature flag `workspace_commands` habilitado
## Flujo interactivo
### 1. Solicitar inputs
1. **Nombre**: URL-safe (lowercase, alfanumérico, guiones)
2. **Descripción**: texto libre
3. **Tipo**: go, data, etl, api
4. **¿Privado?**: s/n (default: n)
### 2. Mostrar resumen y confirmar
```
Resumen:
Nombre: my-etl-pipeline
Path local: ./workspaces/my-etl-pipeline
Gitea: https://gitea.../my-etl-pipeline
Tipo: etl
Privado: no
¿Crear repositorio? (s/n):
```
### 3. Ejecutar creación
Usa `app.CreateWorkspaceCommand(config, params)`:
1. Validar nombre
2. Verificar que no existe
3. Crear estructura core/shell/app/
4. Escribir templates (go.mod, main.go, etc.)
5. git init + configurar usuario
6. Crear repo en Gitea
7. Push inicial
8. Registrar en SQLite
**Rollback automático** si falla cualquier paso.
### 4. Mostrar resultado
```
Workspace creado: ./workspaces/my-etl-pipeline
Para trabajar:
cd workspaces/my-etl-pipeline
```
## Validación de nombre
- Solo letras, números y guiones
- No empezar/terminar con guión
- 2-100 caracteres
## Troubleshooting
- "nombre inválido": usar solo lowercase, alfanumérico, guiones
- "ya existe": verificar `ls workspaces/` o usar otro nombre
- "error Gitea": verificar GITEA_TOKEN
@@ -21,10 +21,15 @@ log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${CYAN}[STEP]${NC} $1"; } log_step() { echo -e "${CYAN}[STEP]${NC} $1"; }
# --- Parámetros --- # --- 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:-}" MODULE_NAME="${1:-}"
TARGET_PATH="${2:-.}" TARGET_PATH="${2:-.}"
DEVFACTORY_PATH="$HOME/.local_agentes/backend" DEVFACTORY_PATH="${DEVFACTORY_PATH:-$HOME/.local_agentes/backend}"
DEVFACTORY_MODULE="github.com/lucasdataproyects/devfactory" DEVFACTORY_MODULE="${DEVFACTORY_MODULE:-github.com/lucasdataproyects/devfactory}"
GO_NAMESPACE="${GO_NAMESPACE:-github.com/lucasdataproyects}"
# --- Validar nombre --- # --- Validar nombre ---
if [[ -z "$MODULE_NAME" ]]; then if [[ -z "$MODULE_NAME" ]]; then
@@ -36,7 +41,7 @@ fi
# Normalizar nombre a kebab-case # 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') 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" PROJECT_DIR="$TARGET_PATH/$MODULE_NAME"
GO_MODULE="github.com/lucasdataproyects/$MODULE_NAME" GO_MODULE="${GO_NAMESPACE}/$MODULE_NAME"
# --- Check estado existente --- # --- Check estado existente ---
if [[ -f "$PROJECT_DIR/go.mod" ]]; then 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
-112
View File
@@ -1,112 +0,0 @@
---
name: fix-issue
description: Implementa un issue completo de punta a punta con confirmación
argument-hint: <NNNN>
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, TodoWrite
---
# fix-issue
Ejecuta el flujo completo de implementación/cierre de un issue: crear rama, implementar, testear, cerrar, confirmar, integrar.
## Sintaxis
```bash
/fix-issue <NNNN>
/fix-issue <NNNN>-<slug>
```
## Precondiciones
- [ ] Directorio `dev/issues/` existe
- [ ] Directorio `dev/issues/completed/` existe
- [ ] Tests configurados
- [ ] Working tree limpio
## Flujo
### 1. Resolver issue objetivo
```bash
ls dev/issues/<NNNN>-*.md
```
- Si no existe: STOP "Issue no encontrado"
- Si ya completado: STOP "Issue ya completado"
### 2. Leer issue completo
Extraer: objetivo, tareas, arquitectura, patrón pure/impure, tests.
### 3. Crear rama de trabajo
```bash
git checkout master
git pull --rebase
git checkout -b issue/<NNNN>-<slug>
```
### 4. Planificar con TodoWrite
Crear plan basado en tareas del issue.
### 5. Implementar completo
Para cada tarea:
1. Implementar siguiendo patrón pure core / impure shell
2. Compilar frecuentemente: `go build -tags goolm ./...`
3. Crear commits atómicos durante implementación
### 6. Tests obligatorios
```bash
go test -tags goolm ./...
```
- Pasan: continuar
- Fallan: STOP y corregir
### 7. Feature flags (si aplica)
Actualizar `dev/feature_flags.json` si es multi-issue.
### 8. Cerrar issue
```bash
mv dev/issues/<NNNN>-<slug>.md dev/issues/completed/
```
Actualizar índice en README.md.
### 9. Mostrar resumen y confirmar
```
Issue <NNNN> completado
Resumen:
- N archivos modificados
- N commits realizados
- Tests: pasando
¿Integrar a master?
```
### 10. Ejecutar /git-push
Si confirma, ejecutar flujo de integración.
## Convenciones
- Implementar TODAS las tareas
- Commits atómicos durante implementación
- Tests obligatorios
- Pure core / impure shell
## Reglas
- NO saltear tareas
- NO commits WIP
- SIEMPRE tests antes de cerrar
- Confirmación obligatoria antes de integrar
-97
View File
@@ -1,97 +0,0 @@
---
name: git-branch
description: Crea una rama de trabajo (issue/* o quick/*). Nunca trabajar directamente en master.
argument-hint: <tipo> <args>
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read
---
# git-branch
Crea una rama de trabajo siguiendo trunk-based development. **Nunca trabajar directamente en master.**
## Sintaxis
```bash
/git-branch issue <NNNN> <slug>
/git-branch quick <slug>
```
## Ejemplos
```bash
/git-branch issue 0013 hot-reload # Crea issue/0013-hot-reload
/git-branch quick fix-typo-readme # Crea quick/fix-typo-readme
```
## Precondiciones
- [ ] Repositorio git válido
- [ ] Branch master existe
- [ ] Working tree limpio (sin cambios pendientes)
## Flujo
### 1. Verificar estado del repositorio
```bash
git branch --show-current
git status --short
```
**Si no estamos en master:** `git checkout master`
**Si hay cambios sin commitear:** STOP y avisar al usuario:
```
Hay cambios sin commitear. Opciones:
1. Commitear: git add . && git commit -m "mensaje"
2. Stash: git stash
3. Descartar: git reset --hard (peligroso)
```
### 2. Actualizar master desde remoto
```bash
git pull --rebase
```
### 3. Crear rama según tipo
**Para issues:**
```bash
git checkout -b issue/<NNNN>-<slug>
```
**Para cambios rápidos:**
```bash
git checkout -b quick/<slug>
```
### 4. Confirmar creación
```bash
git branch --show-current
```
Informar:
```
Rama `<nombre-rama>` creada desde master actualizado
Cuando termines:
/git-push
```
## Convenciones
- **Formato issue**: `issue/<NNNN>-<slug>` (4 dígitos)
- **Formato quick**: `quick/<slug>`
- **Ramas cortas**: horas, no días
- **No pushear ramas**: integrar via merge a master
- **No underscores**: solo guiones
## Reglas
- NUNCA trabajar directamente en master
- SIEMPRE verificar working tree limpio
- SIEMPRE actualizar master antes de crear rama
-116
View File
@@ -1,116 +0,0 @@
---
name: git-push
description: Integra cambios a master y publica. Soporta ramas issue/* y quick/*.
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write, Edit
---
# git-push
Integra cambios a master y publica al remoto. Detecta automáticamente la rama actual.
## Sintaxis
```bash
/git-push
```
## Flujo
### 1. Verificar rama actual y estado
```bash
git branch --show-current
git status --short
```
**Caso A: En rama issue/* o quick/*** - Continuar al paso 2
**Caso B: En master con cambios** - Crear rama quick automáticamente:
- Analizar archivos modificados para generar slug
- `git checkout -b quick/<slug-generado>`
**Caso C: En master sin cambios** - STOP: "No hay nada que publicar"
### 2. Crear commits por bloque lógico
```bash
git status --short
git diff --stat
```
Agrupar cambios por tipo y crear commits atómicos:
```bash
git add <archivos_bloque_1>
git commit -m "<tipo>: <resumen>" -m "<descripción en español>"
```
**Tipos:** feat, fix, refactor, docs, chore, test
**Reglas de commits:**
- No WIP
- No mezclar tipos
- Descripción larga obligatoria en español
### 3. Ejecutar tests
```bash
go test -tags goolm ./...
```
- Tests pasan: continuar
- Tests fallan: STOP y corregir
- No hay tests: informar y continuar
### 4. Merge a master
```bash
git checkout master
git pull --rebase
git merge --no-ff <rama> -m "merge: <rama> — <título>"
```
### 5. Push a remoto
```bash
git push
```
### 6. Limpiar rama local
```bash
git branch -d <rama>
```
### 7. Verificación final
```bash
git log --oneline -3
```
```
Rama `<rama>` integrada a master y publicada
Commits creados:
- <commit 1>
- merge: <rama>
Rama local eliminada.
```
## Convenciones
- Commits atómicos
- Tests obligatorios antes de merge
- Merge --no-ff siempre
- Push inmediato
## Reglas
- NO commits WIP
- NO mezclar tipos en un commit
- NO saltear tests
- NO push --force a master
- SIEMPRE usar --no-ff
-105
View File
@@ -1,105 +0,0 @@
---
name: git-recovery
description: Recupera el repositorio de estados inconsistentes (worktrees huérfanos, branches bloqueados)
argument-hint: [--aggressive]
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read
---
# git-recovery
Recupera el repositorio de estados inconsistentes causados por worktrees huérfanos, branches bloqueados o conflictos git.
## Sintaxis
```bash
/git-recovery # Recuperación estándar
/git-recovery --aggressive # Limpieza agresiva
```
## Cuándo usar
- Errores "exit status 128" al crear worktrees
- Git reporta "worktree already exists"
- Branches que no se pueden eliminar
- Worktrees huérfanos en `git worktree list`
## Flujo
### 1. Diagnóstico inicial
```bash
git branch --show-current
git status --porcelain
```
### 2. Análisis de problemas
```bash
git worktree list
git branch --list
git remote -v
```
### 3. Limpieza de worktrees huérfanos
```bash
git worktree prune -v
```
Si existe directorio `worktrees/`:
- Verificar cada worktree contra `git worktree list`
- Eliminar directorios huérfanos
### 4. Verificar branches bloqueados
Para cada branch issue/* o quick/*:
- Si está mergeada: `git branch -d <branch>`
- Si NO está mergeada: advertir
### 5. Sincronizar con remoto
```bash
git checkout master
git fetch origin
git pull --rebase origin master
```
### 6. Modo agresivo (solo con --aggressive)
```bash
git remote prune origin -v
git fsck --full
git gc --prune=now
rm -f .git/index.lock # si existe
```
### 7. Verificación final
```bash
git status
git worktree list
git branch --list
```
## Patrones de error que activan recovery
- `exit status 128`
- `worktree .* already exists`
- `reference is not a tree`
- `cannot lock ref`
- `index.lock`
## Convenciones
- No destructivo por defecto
- Modo agresivo solo con flag explícito
- Siempre sincroniza con remoto
- Preserva cambios locales
## Reglas
- NUNCA git reset --hard sin --aggressive
- NUNCA eliminar branches no mergeadas automáticamente
- SIEMPRE sincronizar con remoto después de limpieza
-91
View File
@@ -1,91 +0,0 @@
---
name: import-repo
description: Importa repositorios existentes al sistema Dataforge desde URL remota o local
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write
---
# import-repo
Importa repositorios existentes: desde GitHub/GitLab/Gitea, o adoptando un repo local.
## Prerequisitos
- Variables: `GITEA_URL` y `GITEA_TOKEN`
- Feature flag `workspace_commands` habilitado
## Modos
### Desde URL remota
1. Crear repo vacío en Gitea
2. Clonar origen con `git clone --mirror`
3. Push a Gitea con `git push --mirror`
4. Clonar en `workspaces/`
5. Registrar en BD
### Adoptar repo local
1. Verificar que existe `.git`
2. Crear repo vacío en Gitea
3. Añadir remote `gitea`
4. Push de branches y tags
5. Registrar en BD
## Flujo interactivo
### 1. Solicitar fuente
```
Fuente del repositorio:
- URL remota (ej: https://github.com/user/repo)
- Nombre local en workspaces/ (ej: legacy-tool)
```
### 2. Detectar modo y analizar
Usa `core.DetectImportMode(source)`
### 3. Solicitar nombre de destino
```
Nombre en Gitea (Enter para usar 'nombre-sugerido'):
```
### 4. Verificar que no existe en Gitea
### 5. Opciones adicionales
```
¿Repositorio privado? (s/N):
Descripción (opcional):
```
### 6. Resumen y confirmación
```
Resumen:
Fuente: https://...
Destino: gitea.example.com/...
Tipo: importar desde URL remota
¿Importar? (s/N):
```
### 7. Ejecutar importación
### 8. Mostrar resultado
```
Repositorio importado exitosamente.
Workspace: workspaces/nombre
Gitea URL: https://...
```
## Convenciones
- Confirmación obligatoria
- Rollback automático si falla
- Historia Git siempre preservada
-62
View File
@@ -1,62 +0,0 @@
---
name: init-frontend
description: Inicializa proyecto frontend (React/Vite) o desktop (Wails) con Frontend_Library
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write, Edit
---
# init-frontend
Inicializa un proyecto frontend (webapp React/Vite) o desktop (Wails + Go + React). Coherente con Frontend_Library y el stack del frontend-lib/build-wails agents.
## Sintaxis
```bash
/init-frontend [nombre] [--wails] [--path /ruta/destino]
```
- `nombre`: nombre del proyecto (kebab-case). Si no se da, se pregunta.
- `--wails`: modo desktop con Wails (Go backend + React frontend). Sin flag = webapp pura.
- `--path`: directorio destino. Default: directorio actual.
## Flujo
### 1. Ejecutar script de setup
```bash
bash "${CLAUDE_SKILL_DIR}/setup-frontend.sh" [nombre] [--wails] [path]
```
### 2. Si el script reporta STATUS: CONFIGURED
Informar al usuario que el proyecto ya existe.
### 3. Si el script reporta STATUS: READY
Mostrar resumen según modo:
**Webapp:**
- `pnpm dev` para desarrollo
- `pnpm build` para producción
- Frontend_Library linkeada via pnpm
**Wails:**
- `make dev` para desarrollo con hot reload
- `make build` para compilar
- Frontend_Library + DevFactory integrados
- Bindings Go→TS auto-generados
### 4. Si el script reporta STATUS: ERROR
Mostrar el error y sugerir corrección.
## Convenciones
- pnpm exclusivamente (no npm ni yarn)
- React 19 + TypeScript + Vite + Tailwind CSS 4
- @anthropic/frontend-lib via pnpm link
- Temas OKLCH con semantic tokens
- Phosphor Icons
- Vite dedupe obligatorio para react/react-dom
- En modo Wails: go.work con DevFactory, patrón pure core / impure shell
@@ -1,438 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# =============================================================================
# setup-frontend.sh — Inicializa proyecto React/Vite o Wails desktop
# Coherente con Frontend_Library + DevFactory + build-wails agent
# =============================================================================
# --- Colores ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${CYAN}[STEP]${NC} $1"; }
# --- Parámetros ---
PROJECT_NAME=""
WAILS_MODE=false
TARGET_PATH="."
while [[ $# -gt 0 ]]; do
case "$1" in
--wails) WAILS_MODE=true; shift ;;
--path) TARGET_PATH="$2"; shift 2 ;;
-*) log_error "Flag desconocido: $1"; echo "STATUS: ERROR"; exit 1 ;;
*) PROJECT_NAME="$1"; shift ;;
esac
done
# --- Rutas de librerías ---
FRONTEND_LIB="$HOME/.local_agentes/frontend/frontend"
DEVFACTORY_PATH="$HOME/.local_agentes/backend"
TEMPLATES_DIR="$HOME/.local_agentes/frontend/templates/base"
WAILS_TEMPLATES="$HOME/.claude/agents/build-wails/templates"
# --- Validar nombre ---
if [[ -z "$PROJECT_NAME" ]]; then
log_error "Uso: setup-frontend.sh <nombre> [--wails] [--path /ruta]"
echo "STATUS: ERROR"
exit 1
fi
# Normalizar
PROJECT_NAME=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
PROJECT_DIR="$TARGET_PATH/$PROJECT_NAME"
# --- Check estado existente ---
if [[ -f "$PROJECT_DIR/package.json" ]] || [[ -f "$PROJECT_DIR/wails.json" ]]; then
log_warn "El proyecto $PROJECT_NAME ya existe en $PROJECT_DIR"
echo "STATUS: CONFIGURED"
exit 0
fi
# --- Verificar dependencias ---
log_step "Verificando dependencias..."
if ! command -v pnpm &>/dev/null; then
log_error "pnpm no está instalado. Instala con: npm install -g pnpm"
echo "STATUS: ERROR"
exit 1
fi
log_ok "pnpm $(pnpm --version) encontrado"
if ! command -v node &>/dev/null; then
log_error "Node.js no encontrado"
echo "STATUS: ERROR"
exit 1
fi
log_ok "Node $(node --version) encontrado"
if [[ "$WAILS_MODE" == true ]]; then
if ! command -v wails &>/dev/null; then
log_error "Wails no está instalado. Instala con: go install github.com/wailsapp/wails/v2/cmd/wails@latest"
echo "STATUS: ERROR"
exit 1
fi
log_ok "Wails $(wails version 2>/dev/null | head -1 || echo 'v2.x') encontrado"
if ! command -v go &>/dev/null; then
log_error "Go no encontrado (requerido para Wails)"
echo "STATUS: ERROR"
exit 1
fi
log_ok "Go $(go version | grep -oP '\d+\.\d+' | head -1) encontrado"
fi
if [[ ! -d "$FRONTEND_LIB" ]]; then
log_warn "Frontend_Library no encontrada en $FRONTEND_LIB — se creará sin link"
HAS_FRONTEND_LIB=false
else
log_ok "Frontend_Library encontrada"
HAS_FRONTEND_LIB=true
fi
# ============================================================================
# MODO WAILS — Desktop app (Go + React)
# ============================================================================
if [[ "$WAILS_MODE" == true ]]; then
log_step "Creando proyecto Wails '$PROJECT_NAME'..."
# Usar wails init con template react-ts
wails init -n "$PROJECT_NAME" -t react-ts -d "$TARGET_PATH" 2>/dev/null
cd "$PROJECT_DIR"
# --- go.work con DevFactory ---
if [[ -d "$DEVFACTORY_PATH" ]]; then
log_step "Configurando go.work con DevFactory..."
cat > go.work << EOF
go 1.22
use (
.
$DEVFACTORY_PATH
)
EOF
log_ok "DevFactory enlazado via go.work"
fi
# --- Configurar frontend con pnpm ---
log_step "Configurando frontend con pnpm..."
cd frontend
# Reemplazar npm por pnpm en wails.json
cd ..
if [[ -f "wails.json" ]]; then
sed -i 's/"npm install"/"pnpm install"/g' wails.json
sed -i 's/"npm run dev"/"pnpm dev"/g' wails.json
sed -i 's/"npm run build"/"pnpm build"/g' wails.json
fi
cd frontend
# Instalar con pnpm
rm -f package-lock.json 2>/dev/null || true
pnpm install
# --- Linkear Frontend_Library ---
if [[ "$HAS_FRONTEND_LIB" == true ]]; then
log_step "Linkeando Frontend_Library..."
pnpm add "@anthropic/frontend-lib@link:$FRONTEND_LIB"
log_ok "@anthropic/frontend-lib linkeada"
fi
# --- Instalar Tailwind CSS 4 ---
log_step "Instalando Tailwind CSS 4..."
pnpm add -D tailwindcss @tailwindcss/vite
# --- Configurar vite.config.ts con dedupe y tailwind ---
log_step "Configurando Vite (dedupe + tailwind)..."
cat > vite.config.ts << 'VEOF'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import { resolve } from 'path'
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'@wails': resolve(__dirname, './wailsjs'),
},
dedupe: ['react', 'react-dom'],
},
})
VEOF
# --- CSS base con Tailwind ---
cat > src/style.css << 'CSSEOF'
@import "tailwindcss";
CSSEOF
# --- Instalar Phosphor Icons ---
pnpm add @phosphor-icons/react
cd ..
# --- Makefile (basado en build-wails agent) ---
log_step "Generando Makefile..."
cat > Makefile << 'MKEOF'
.PHONY: dev dev-debug build build-prod build-linux build-windows build-all clean generate doctor
APP_NAME := $(shell basename $(CURDIR))
## dev: Desarrollo con hot reload
dev:
wails dev
## dev-debug: Desarrollo con DevTools
dev-debug:
wails dev -devtools
## build: Build para plataforma actual
build:
wails build
## build-prod: Build optimizado para producción
build-prod:
wails build -clean -trimpath -ldflags="-s -w"
## build-linux: Build para Linux AMD64
build-linux:
wails build -platform linux/amd64
## build-windows: Cross-compile para Windows
build-windows:
wails build -platform windows/amd64
## build-all: Linux + Windows
build-all: build-linux build-windows
## generate: Regenerar bindings TypeScript
generate:
wails generate module
## clean: Limpiar artefactos
clean:
rm -rf build/bin frontend/dist
## doctor: Verificar instalación
doctor:
wails doctor
## help: Muestra esta ayuda
help:
@grep -E '^## ' Makefile | sed 's/## //' | column -t -s ':'
MKEOF
# --- .gitignore ---
cat >> .gitignore << 'EOF'
node_modules/
frontend/dist/
build/bin/
*.exe
EOF
# --- Resumen Wails ---
echo ""
log_ok "Proyecto Wails '$PROJECT_NAME' creado en $PROJECT_DIR"
echo ""
echo -e "${CYAN}Estructura:${NC}"
echo " $PROJECT_NAME/"
echo " ├── main.go, app.go — Backend Go"
echo " ├── go.work — Enlace a DevFactory"
echo " ├── frontend/ — React + TypeScript + Vite"
echo " │ ├── src/ — Componentes React"
echo " │ └── wailsjs/ — Bindings auto-generados"
echo " ├── Makefile — dev, build, build-all"
echo " └── wails.json — Configuración Wails"
echo ""
echo -e "${CYAN}Comandos:${NC}"
echo " make dev — Desarrollo con hot reload"
echo " make build — Compilar app desktop"
echo " make build-all — Linux + Windows"
echo " make generate — Regenerar bindings TS"
echo ""
if [[ "$HAS_FRONTEND_LIB" == true ]]; then
echo -e "${CYAN}Frontend_Library:${NC}"
echo " import { Button, Card } from '@anthropic/frontend-lib'"
echo " import { useTheme } from '@anthropic/frontend-lib/hooks'"
echo ""
fi
echo "STATUS: READY"
exit 0
fi
# ============================================================================
# MODO WEBAPP — React + Vite (sin Wails)
# ============================================================================
log_step "Creando proyecto webapp '$PROJECT_NAME'..."
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
# --- Usar template de Frontend_Library si existe ---
if [[ -d "$TEMPLATES_DIR" ]]; then
log_step "Usando template de Frontend_Library..."
cp -r "$TEMPLATES_DIR"/* .
cp -r "$TEMPLATES_DIR"/.[!.]* . 2>/dev/null || true
else
log_step "Creando desde cero con Vite..."
# package.json
cat > package.json << PKGEOF
{
"name": "$PROJECT_NAME",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
}
}
PKGEOF
# Instalar dependencias base
pnpm add react react-dom
pnpm add -D typescript @types/react @types/react-dom
pnpm add -D vite @vitejs/plugin-react
pnpm add -D tailwindcss @tailwindcss/vite
# tsconfig.json
cat > tsconfig.json << 'TSEOF'
{
"compilerOptions": {
"target": "ES2023",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"paths": { "@/*": ["./src/*"] },
"skipLibCheck": true
},
"include": ["src"]
}
TSEOF
# vite.config.ts
cat > vite.config.ts << 'VEOF'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import { resolve } from 'path'
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: { '@': resolve(__dirname, './src') },
dedupe: ['react', 'react-dom'],
},
})
VEOF
# index.html
cat > index.html << HTMLEOF
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>$PROJECT_NAME</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
HTMLEOF
# src/
mkdir -p src
cat > src/main.tsx << 'TSXEOF'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import './app.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
TSXEOF
cat > src/App.tsx << 'TSXEOF'
function App() {
return (
<div className="min-h-screen bg-surface text-foreground flex items-center justify-center">
<h1 className="text-3xl font-bold">Ready</h1>
</div>
)
}
export default App
TSXEOF
cat > src/app.css << 'CSSEOF'
@import "tailwindcss";
CSSEOF
fi
# --- Linkear Frontend_Library ---
if [[ "$HAS_FRONTEND_LIB" == true ]]; then
log_step "Linkeando Frontend_Library..."
pnpm add "@anthropic/frontend-lib@link:$FRONTEND_LIB"
log_ok "@anthropic/frontend-lib linkeada"
fi
# --- Phosphor Icons ---
pnpm add @phosphor-icons/react
# --- .gitignore ---
cat > .gitignore << 'EOF'
node_modules/
dist/
.vite/
*.local
EOF
# --- Resumen Webapp ---
echo ""
log_ok "Proyecto webapp '$PROJECT_NAME' creado en $PROJECT_DIR"
echo ""
echo -e "${CYAN}Estructura:${NC}"
echo " $PROJECT_NAME/"
echo " ├── src/"
echo " │ ├── App.tsx — Componente principal"
echo " │ ├── main.tsx — Entry point"
echo " │ └── app.css — Tailwind CSS"
echo " ├── vite.config.ts — Vite + Tailwind + dedupe"
echo " ├── tsconfig.json — TypeScript strict"
echo " └── package.json — pnpm"
echo ""
echo -e "${CYAN}Comandos:${NC}"
echo " pnpm dev — Servidor de desarrollo"
echo " pnpm build — Build de producción"
echo " pnpm preview — Preview del build"
echo ""
if [[ "$HAS_FRONTEND_LIB" == true ]]; then
echo -e "${CYAN}Frontend_Library:${NC}"
echo " import { Button, Card } from '@anthropic/frontend-lib'"
echo " import { useTheme } from '@anthropic/frontend-lib/hooks'"
echo ""
fi
echo "STATUS: READY"
-53
View File
@@ -1,53 +0,0 @@
---
name: init-go-module
description: Inicializa un módulo Go funcional con bindings Python (CGO c-shared + ctypes)
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write, Edit
---
# init-go-module
Inicializa un módulo Go con arquitectura funcional (pure core / impure shell) y bindings Python automáticos via CGO c-shared + ctypes. Coherente con DevFactory y el stack del backend-lib agent.
## Sintaxis
```bash
/init-go-module [nombre] [--path /ruta/destino]
```
- `nombre`: nombre del módulo (kebab-case). Si no se da, se pregunta.
- `--path`: directorio destino. Default: directorio actual.
## Flujo
### 1. Ejecutar script de setup
```bash
bash "${CLAUDE_SKILL_DIR}/setup-go-module.sh" [nombre] [path]
```
### 2. Si el script reporta STATUS: CONFIGURED
Informar al usuario que el módulo ya está configurado.
### 3. Si el script reporta STATUS: READY
Mostrar resumen:
- Estructura creada
- Cómo compilar: `make build`
- Cómo generar bindings Python: `make python`
- Cómo testear: `make test`
- Cómo usar desde Python: `from bindings.modulo import *`
### 4. Si el script reporta STATUS: ERROR
Mostrar el error y sugerir corrección.
## Convenciones
- Usa DevFactory como dependencia via `go.work` (igual que build-wails)
- Patrón pure core / impure shell de DevFactory
- `Result[T]` y `Option[T]` del core de DevFactory
- Funciones exportadas a Python son thin wrappers en `export/`
- El wrapper Python se auto-genera desde los `//export` comments
@@ -1,466 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# =============================================================================
# setup-go-module.sh — Inicializa módulo Go funcional con bindings Python
# Coherente con DevFactory (pure core / impure shell) + CGO c-shared + ctypes
# =============================================================================
# --- Colores ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${CYAN}[STEP]${NC} $1"; }
# --- Parámetros ---
MODULE_NAME="${1:-}"
TARGET_PATH="${2:-.}"
DEVFACTORY_PATH="$HOME/.local_agentes/backend"
DEVFACTORY_MODULE="github.com/lucasdataproyects/devfactory"
# --- Validar nombre ---
if [[ -z "$MODULE_NAME" ]]; then
log_error "Uso: setup-go-module.sh <nombre> [path]"
echo "STATUS: ERROR"
exit 1
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"
# --- Check estado existente ---
if [[ -f "$PROJECT_DIR/go.mod" ]]; then
log_warn "El módulo $MODULE_NAME ya existe en $PROJECT_DIR"
if [[ -f "$PROJECT_DIR/export/exports.go" ]]; then
log_ok "Bindings Python ya configurados"
else
log_warn "Falta directorio export/ — ejecuta de nuevo para completar"
fi
echo "STATUS: CONFIGURED"
exit 0
fi
# --- Verificar dependencias ---
log_step "Verificando dependencias..."
if ! command -v go &>/dev/null; then
log_error "Go no está instalado. Instala Go 1.22+"
echo "STATUS: ERROR"
exit 1
fi
GO_VERSION=$(go version | grep -oP '\d+\.\d+' | head -1)
log_ok "Go $GO_VERSION encontrado"
if ! command -v python3 &>/dev/null; then
log_warn "Python3 no encontrado — los bindings se generarán pero no se podrán testear"
fi
if [[ ! -d "$DEVFACTORY_PATH" ]]; then
log_warn "DevFactory no encontrado en $DEVFACTORY_PATH — se creará go.mod sin go.work"
fi
# --- Crear estructura ---
log_step "Creando estructura del módulo '$MODULE_NAME'..."
mkdir -p "$PROJECT_DIR"/{core,shell,export,python/bindings,cmd,internal}
# --- go.mod ---
log_step "Generando go.mod..."
cat > "$PROJECT_DIR/go.mod" << EOF
module github.com/lucasdataproyects/$MODULE_NAME
go 1.22
require $DEVFACTORY_MODULE v0.0.0
EOF
# --- go.work (si DevFactory existe localmente) ---
if [[ -d "$DEVFACTORY_PATH" ]]; then
log_step "Generando go.work con DevFactory local..."
cat > "$PROJECT_DIR/go.work" << EOF
go 1.22
use (
.
$DEVFACTORY_PATH
)
EOF
log_ok "go.work enlazado a DevFactory"
fi
# --- core/transform.go — Funciones puras de ejemplo ---
log_step "Generando core/ (funciones puras)..."
cat > "$PROJECT_DIR/core/transform.go" << 'GOEOF'
// Package core contiene funciones puras sin side effects.
// Todas las funciones son deterministas y composables.
package core
import (
"strings"
"errors"
df "github.com/lucasdataproyects/devfactory/core"
)
// ToUpper transforma texto a mayúsculas (función pura).
func ToUpper(s string) string {
return strings.ToUpper(s)
}
// ProcessItems aplica una transformación a cada elemento usando MapSlice de DevFactory.
func ProcessItems(items []string, transform func(string) string) []string {
return df.MapSlice(items, transform)
}
// FilterNonEmpty filtra elementos vacíos usando FilterSlice de DevFactory.
func FilterNonEmpty(items []string) []string {
return df.FilterSlice(items, func(s string) bool {
return len(strings.TrimSpace(s)) > 0
})
}
// SafeDivide retorna Result[float64] para evitar panic en división por cero.
func SafeDivide(a, b float64) df.Result[float64] {
if b == 0 {
return df.Err[float64](errors.New("division by zero"))
}
return df.Ok(a / b)
}
GOEOF
# --- core/types.go — Tipos exportables a Python ---
cat > "$PROJECT_DIR/core/types.go" << 'GOEOF'
package core
// DataPoint representa un punto de datos exportable a Python.
// Los campos usan tipos C-compatible para facilitar el binding.
type DataPoint struct {
Label string
Value float64
}
// Summary es el resultado de un procesamiento, exportable a Python.
type Summary struct {
Count int
Total float64
Items []string
}
GOEOF
# --- shell/io.go — Operaciones I/O con Result[T] ---
log_step "Generando shell/ (operaciones I/O)..."
cat > "$PROJECT_DIR/shell/io.go" << 'GOEOF'
// Package shell contiene operaciones con side effects, wrapeadas en Result[T].
package shell
import (
df "github.com/lucasdataproyects/devfactory/core"
"github.com/lucasdataproyects/devfactory/shell"
)
// ReadDataFile lee un archivo y retorna su contenido como Result.
func ReadDataFile(path string) df.Result[string] {
return shell.ReadString(path)
}
// WriteResult escribe un resultado a archivo.
func WriteResult(path string, content string) df.Result[struct{}] {
return shell.WriteString(path, content)
}
GOEOF
# --- export/exports.go — Funciones exportadas via CGO ---
log_step "Generando export/ (bindings CGO)..."
cat > "$PROJECT_DIR/export/exports.go" << GOEOF
// Package main exporta funciones Go como C shared library.
// Cada función con //export se expone como símbolo C callable desde Python.
package main
import "C"
import (
"encoding/json"
"unsafe"
"github.com/lucasdataproyects/$MODULE_NAME/core"
)
//export GoToUpper
func GoToUpper(input *C.char) *C.char {
result := core.ToUpper(C.GoString(input))
return C.CString(result)
}
//export GoProcessItems
func GoProcessItems(jsonInput *C.char) *C.char {
var items []string
if err := json.Unmarshal([]byte(C.GoString(jsonInput)), &items); err != nil {
return C.CString("[]")
}
result := core.ProcessItems(items, core.ToUpper)
out, _ := json.Marshal(result)
return C.CString(string(out))
}
//export GoFilterNonEmpty
func GoFilterNonEmpty(jsonInput *C.char) *C.char {
var items []string
if err := json.Unmarshal([]byte(C.GoString(jsonInput)), &items); err != nil {
return C.CString("[]")
}
result := core.FilterNonEmpty(items)
out, _ := json.Marshal(result)
return C.CString(string(out))
}
//export GoSafeDivide
func GoSafeDivide(a, b C.double) *C.char {
result := core.SafeDivide(float64(a), float64(b))
if result.IsErr() {
return C.CString(`{"error":"` + result.Error().Error() + `"}`)
}
out, _ := json.Marshal(map[string]float64{"value": result.Unwrap()})
return C.CString(string(out))
}
//export GoFree
func GoFree(ptr *C.char) {
C.free(unsafe.Pointer(ptr))
}
func main() {}
GOEOF
# --- python/bindings/__init__.py — Wrapper ctypes auto-generado ---
log_step "Generando python/bindings/ (ctypes wrapper)..."
# Nombre de la shared library según OS
SO_NAME="lib${MODULE_NAME}.so"
cat > "$PROJECT_DIR/python/bindings/__init__.py" << PYEOF
"""
Auto-generated Python bindings for $MODULE_NAME.
Uses ctypes to call Go functions compiled as C shared library.
Usage:
from bindings import to_upper, process_items, filter_non_empty, safe_divide
"""
import ctypes
import json
import os
from pathlib import Path
# Localizar la shared library
_LIB_DIR = Path(__file__).parent.parent.parent / "build"
_LIB_NAME = "$SO_NAME"
_LIB_PATH = _LIB_DIR / _LIB_NAME
if not _LIB_PATH.exists():
raise FileNotFoundError(
f"Shared library not found at {_LIB_PATH}. "
f"Run 'make build' in the project root first."
)
_lib = ctypes.CDLL(str(_LIB_PATH))
# --- Configurar tipos de retorno ---
_lib.GoToUpper.argtypes = [ctypes.c_char_p]
_lib.GoToUpper.restype = ctypes.c_char_p
_lib.GoProcessItems.argtypes = [ctypes.c_char_p]
_lib.GoProcessItems.restype = ctypes.c_char_p
_lib.GoFilterNonEmpty.argtypes = [ctypes.c_char_p]
_lib.GoFilterNonEmpty.restype = ctypes.c_char_p
_lib.GoSafeDivide.argtypes = [ctypes.c_double, ctypes.c_double]
_lib.GoSafeDivide.restype = ctypes.c_char_p
_lib.GoFree.argtypes = [ctypes.c_char_p]
_lib.GoFree.restype = None
def to_upper(text: str) -> str:
"""Convert text to uppercase using Go core."""
result = _lib.GoToUpper(text.encode("utf-8"))
return result.decode("utf-8")
def process_items(items: list[str]) -> list[str]:
"""Process items through Go pipeline (ToUpper transformation)."""
input_json = json.dumps(items).encode("utf-8")
result = _lib.GoProcessItems(input_json)
return json.loads(result.decode("utf-8"))
def filter_non_empty(items: list[str]) -> list[str]:
"""Filter empty strings using Go core."""
input_json = json.dumps(items).encode("utf-8")
result = _lib.GoFilterNonEmpty(input_json)
return json.loads(result.decode("utf-8"))
def safe_divide(a: float, b: float) -> float:
"""Safe division using Go Result type. Raises ValueError on division by zero."""
result = _lib.GoSafeDivide(ctypes.c_double(a), ctypes.c_double(b))
data = json.loads(result.decode("utf-8"))
if "error" in data:
raise ValueError(data["error"])
return data["value"]
PYEOF
# --- python/example.py ---
cat > "$PROJECT_DIR/python/example.py" << PYEOF
"""Example usage of $MODULE_NAME Go bindings from Python."""
from bindings import to_upper, process_items, filter_non_empty, safe_divide
# String transformation
print(to_upper("hello from go")) # HELLO FROM GO
# Batch processing via Go's MapSlice
items = ["hello", "world", "from", "go"]
print(process_items(items)) # ["HELLO", "WORLD", "FROM", "GO"]
# Filtering via Go's FilterSlice
mixed = ["hello", "", "world", " ", "go"]
print(filter_non_empty(mixed)) # ["hello", "world", "go"]
# Safe division with Result[T] error handling
print(safe_divide(10.0, 3.0)) # 3.333...
try:
safe_divide(10.0, 0.0)
except ValueError as e:
print(f"Caught: {e}") # Caught: division by zero
PYEOF
# --- core/transform_test.go ---
log_step "Generando tests..."
cat > "$PROJECT_DIR/core/transform_test.go" << 'GOEOF'
package core
import (
"testing"
)
func TestToUpper(t *testing.T) {
if got := ToUpper("hello"); got != "HELLO" {
t.Errorf("ToUpper(\"hello\") = %q, want %q", got, "HELLO")
}
}
func TestFilterNonEmpty(t *testing.T) {
items := []string{"hello", "", "world", " ", "go"}
result := FilterNonEmpty(items)
if len(result) != 3 {
t.Errorf("FilterNonEmpty got %d items, want 3", len(result))
}
}
func TestSafeDivide(t *testing.T) {
ok := SafeDivide(10, 2)
if ok.IsErr() {
t.Error("SafeDivide(10, 2) should not error")
}
if ok.Unwrap() != 5.0 {
t.Errorf("SafeDivide(10, 2) = %f, want 5.0", ok.Unwrap())
}
err := SafeDivide(10, 0)
if !err.IsErr() {
t.Error("SafeDivide(10, 0) should error")
}
}
GOEOF
# --- Makefile ---
log_step "Generando Makefile..."
cat > "$PROJECT_DIR/Makefile" << MKEOF
.PHONY: build test clean python dev
MODULE_NAME := $MODULE_NAME
BUILD_DIR := build
SO_NAME := $SO_NAME
## build: Compila la shared library (.so) para Python
build:
@mkdir -p \$(BUILD_DIR)
cd export && CGO_ENABLED=1 go build -buildmode=c-shared -o ../\$(BUILD_DIR)/\$(SO_NAME) .
@echo "✓ Built \$(BUILD_DIR)/\$(SO_NAME)"
## test: Ejecuta tests de Go
test:
go test ./core/... ./shell/... -v
## python: Compila y ejecuta ejemplo Python
python: build
cd python && python3 example.py
## clean: Limpia artefactos
clean:
rm -rf \$(BUILD_DIR)
find . -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
## dev: Tests + build en un paso
dev: test build
@echo "✓ Ready — run 'make python' to test bindings"
## tidy: go mod tidy
tidy:
go mod tidy
## help: Muestra esta ayuda
help:
@grep -E '^## ' Makefile | sed 's/## //' | column -t -s ':'
MKEOF
# --- .gitignore ---
cat > "$PROJECT_DIR/.gitignore" << 'EOF'
build/
*.so
*.h
*.dylib
*.dll
__pycache__/
*.pyc
.pytest_cache/
EOF
# --- go mod tidy ---
log_step "Ejecutando go mod tidy..."
cd "$PROJECT_DIR"
if [[ -f "go.work" ]]; then
go mod tidy 2>/dev/null || log_warn "go mod tidy falló — revisa el go.work"
else
go mod tidy 2>/dev/null || log_warn "go mod tidy falló — DevFactory no está disponible"
fi
# --- Resumen ---
echo ""
log_ok "Módulo '$MODULE_NAME' creado en $PROJECT_DIR"
echo ""
echo -e "${CYAN}Estructura:${NC}"
echo " $MODULE_NAME/"
echo " ├── core/ — Funciones puras (sin side effects)"
echo " ├── shell/ — Operaciones I/O con Result[T]"
echo " ├── export/ — Funciones exportadas via CGO"
echo " ├── python/bindings — Wrapper ctypes auto-generado"
echo " ├── Makefile — build, test, python, clean"
echo " └── go.work — Enlace a DevFactory"
echo ""
echo -e "${CYAN}Comandos:${NC}"
echo " make test — Ejecutar tests Go"
echo " make build — Compilar shared library"
echo " make python — Testear bindings Python"
echo " make dev — Test + build en un paso"
echo ""
echo "STATUS: READY"
+2 -1
View File
@@ -19,7 +19,8 @@ Skill para preparar cualquier repo para exploración de datos con Jupyter + Clau
```bash ```bash
# Obtener ruta del script (está junto a este SKILL.md) # 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) # Ejecutar con la ruta del proyecto (argumento del skill o directorio actual)
bash "$SKILL_DIR/setup-jupyter.sh" "${1:-.}" bash "$SKILL_DIR/setup-jupyter.sh" "${1:-.}"
-57
View File
@@ -1,57 +0,0 @@
---
name: issues-status
description: Dashboard global de issues en todos los workspaces con métricas y filtros
argument-hint: [workspace] [--status pending] [--tag tag] [--export json|csv]
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read
---
# issues-status
Muestra dashboard global de todas las issues con métricas, filtros y sugerencias.
## Sintaxis
```bash
/issues-status # Dashboard global
/issues-status <workspace> # Detalle de workspace
/issues-status --status pending # Filtrar por estado
/issues-status --tag backend # Filtrar por tag
/issues-status --export json # Exportar
```
## Flujo
### 1. Parsear argumentos
- Primer arg (sin --): `filterWorkspace`
- `--status <value>`: pending | in_progress | completed
- `--tag <value>`: filtrar por tag
- `--export <format>`: json | csv
### 2. Ejecutar dashboard
Llama `app.IssuesDashboardCommand(config, filterWorkspace, filterStatus, filterTag, exportFormat)`
### 3. Modo interactivo (dashboard global)
Si no hay filtros:
1. Mostrar dashboard con sugerencias
2. Preguntar: "¿Ver detalle de un workspace? (nombre o 'n')"
3. Si responde nombre: mostrar detalle
4. Si responde 'n': terminar
### Comandos sugeridos
```
Commands:
/issues-status <workspace>
/issues-status --status pending
/fix-issue <issue>
```
## Manejo de errores
- Si no hay workspaces: sugerir crear o sincronizar
- Si no hay issues: mostrar dashboard vacío con sugerencias
+319
View File
@@ -0,0 +1,319 @@
---
name: parallel-fix-issues
description: >
Implementar múltiples issues en paralelo. Analiza dependencias entre issues pendientes,
crea git worktrees aislados, lanza agentes concurrentes para cada issue, verifica
resultados (build + tests) e integra todo a master en orden.
allowed-tools: Bash Read Write Edit Grep Glob Agent
argument-hint: "[issue-numbers... | all]"
---
# Parallel Fix Issues
Skill para implementar múltiples issues simultáneamente usando git worktrees y agentes paralelos.
## Inputs
- `$ARGUMENTS`: lista de issue numbers (ej: `0026 0027 0031`) o `all` para todos los pendientes.
- Si no hay argumentos, preguntar al usuario qué issues quiere procesar.
## Proceso completo
### Fase 1: Análisis de dependencias
Lanzar un **Agent** (subagent_type: `Explore`) para analizar los issues y producir un plan de ejecución.
El agente debe:
1. Leer `dev/issues/README.md` y filtrar los issues pendientes
2. Si `$ARGUMENTS` no es `all`, filtrar solo los issues solicitados
3. Para cada issue pendiente, leer el archivo completo y extraer:
- **Objetivo** (resumen)
- **Prerequisitos** y dependencias explícitas (ej: "requiere issue 0026")
- **Archivos afectados** (para detectar conflictos potenciales entre issues)
4. Construir un **grafo de dependencias** y agrupar en **waves** (oleadas):
- Wave 1: issues sin dependencias entre sí y sin dependencias pendientes
- Wave 2: issues que dependen de wave 1
- Wave N: etc.
5. Dentro de cada wave, identificar **conflictos potenciales** (dos issues que tocan los mismos archivos)
6. Devolver el resultado en este formato exacto:
```
WAVE 1 (paralelo):
- <NNNN>-<slug> — <objetivo resumido> — archivos: <lista>
- <NNNN>-<slug> — <objetivo resumido> — archivos: <lista>
WAVE 2 (paralelo, después de wave 1):
- <NNNN>-<slug> — <objetivo resumido> — depende de: <NNNN>
CONFLICTOS POTENCIALES:
- <NNNN> y <NNNN> tocan <archivo> — riesgo de merge conflict
ISSUES EXCLUIDOS:
- <NNNN>-<slug> — razón (dependencia externa no resuelta, etc.)
```
**Mostrar el resultado al usuario y pedir confirmación** antes de continuar. El usuario puede:
- Aprobar el plan tal cual
- Excluir issues específicos
- Reordenar waves
### Fase 2: Setup de worktrees
Una vez aprobado el plan, crear los worktrees.
```bash
.claude/skills/parallel-fix-issues/scripts/setup-worktrees.sh <slug-1> <slug-2> ...
```
El script crea un worktree por issue en `worktrees/<slug>/`, cada uno en su propia branch `issue/<slug>`.
**Verificar** que todos los worktrees se crearon correctamente:
```bash
git worktree list
```
### Fase 3: Ejecución paralela por waves
Para cada wave, lanzar **Agents en paralelo** (un Agent por issue, todos en el mismo mensaje para ejecución concurrente).
**CRÍTICO**: Lanzar todos los agentes de una wave en una sola respuesta con múltiples tool calls. NO lanzar de uno en uno.
**PERMISOS**: Todas las llamadas Bash de los agentes deben usar `dangerouslyDisableSandbox: true` para evitar prompts de permisos que bloqueen la ejecución paralela. Indicar esto explícitamente en el prompt de cada agente.
El prompt de cada agente debe incluir:
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
```
Eres un agente de desarrollo implementando el issue <NNNN>-<slug>.
## Directorio de trabajo
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=<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
IMPORTANTE: En TODAS tus llamadas al tool Bash, usa el parámetro `dangerouslyDisableSandbox: true`.
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", dangerouslyDisableSandbox: true })
## Issue a implementar
<PEGAR CONTENIDO COMPLETO DEL ISSUE AQUÍ>
## Instrucciones
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 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 codigo a medias
- Compilar frecuentemente:
Bash({ command: "cd $W && eval \"$BUILD_CMD\"", dangerouslyDisableSandbox: true })
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 && 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 })
- Commit: docs: cerrar issue <NNNN>
IMPORTANTE: usar `git mv` (no `mv` + `git add`) para que git registre el movimiento.
IMPORTANTE: NO modificar dev/issues/README.md — lo hace el orquestador después del merge
para evitar conflictos entre agentes paralelos.
5. **NO hacer merge a master, NO hacer push.** La integración la maneja el orquestador.
6. **Reportar resultado** al final:
- ÉXITO: qué se implementó, cuántos commits, tests pasando
- FALLO: qué falló, en qué paso, qué queda pendiente
```
**Esperar** a que todos los agentes de la wave terminen antes de pasar a la siguiente wave.
### Fase 4: Verificación
Después de cada wave, verificar TODOS los worktrees completados:
```bash
.claude/skills/parallel-fix-issues/scripts/verify-worktree.sh worktrees/<slug>
```
El script verifica:
- `$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
3. Si se excluye, marcar para no integrar
### Fase 5: Integración a master
Una vez todas las waves verificadas, integrar a master **en orden de waves** (wave 1 primero, luego wave 2, etc.).
```bash
.claude/skills/parallel-fix-issues/scripts/integrate-worktrees.sh <slug-1> <slug-2> ...
```
El script hace para cada branch:
1. `git checkout master`
2. `git merge --no-ff issue/<slug>` con mensaje descriptivo
3. Si hay **merge conflict**: PARAR e informar al usuario
**Despues de cada merge**, re-verificar que master compila usando los `BUILD_CMD`/`TEST_CMD` resueltos:
```bash
eval "$BUILD_CMD" && eval "$TEST_CMD"
```
`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
Después de integrar TODOS los issues exitosos, actualizar `dev/issues/README.md` **una sola vez** desde master.
Esto evita conflictos: los agentes paralelos solo mueven archivos, el orquestador actualiza el índice.
Para cada issue integrado:
1. Cambiar el link de `[<NNNN>-<slug>.md](<NNNN>-<slug>.md)` a `[<NNNN>-<slug>.md](completed/<NNNN>-<slug>.md)`
2. Cambiar el estado de `pendiente` a `completado`
Hacer un solo commit:
```bash
git add dev/issues/README.md
git commit -m "docs: actualizar README de issues — marcar <N> issues como completados"
```
### Fase 7: Limpieza
Si todo fue exitoso:
```bash
# Eliminar worktrees y branches
for slug in <slugs...>; do
git worktree remove "worktrees/${slug}" 2>/dev/null
git branch -d "issue/${slug}" 2>/dev/null
done
```
### Fase 8: Reporte final
Mostrar al usuario un resumen:
```
## Resultado de parallel-fix-issues
### Issues completados
- ✓ 0026-split-runtime — 5 commits
- ✓ 0027-prune-config-schema — 3 commits
- ✓ 0031-expand-file-tools — 7 commits
### Issues fallidos
- ✗ 0029-core-tests — falló en fase de tests (excluido)
### Estado de master
- Build: OK
- Tests: OK (142 passed)
- Commits nuevos: 18
### Siguiente paso
Ejecutar: git push
```
## Notas importantes
- **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
- **Un worktree = un issue = una branch** — nunca mezclar
- Los worktrees se crean desde `master` actualizado
- La carpeta `worktrees/` está en `.gitignore`
- Issues con dependencias externas no resueltas se excluyen automáticamente
- **README centralizado**: los agentes NO tocan `dev/issues/README.md` — solo el orquestador lo actualiza después del merge, en un solo commit. Esto evita merge conflicts entre agentes paralelos
- **`git mv` para cerrar issues**: usar `git mv` (no `mv` + `git add`) para mover issues a `completed/`
## Casos de uso
```
# Implementar todos los issues pendientes
/parallel-fix-issues all
# Implementar issues específicos
/parallel-fix-issues 0026 0027 0031
# Solo los issues de refactor
/parallel-fix-issues 0026 0027 0028
```
@@ -0,0 +1,121 @@
#!/bin/bash
# integrate-worktrees.sh — Integra branches de worktrees a master con --no-ff
#
# Uso: ./integrate-worktrees.sh <slug-1> <slug-2> ...
# Ejemplo: ./integrate-worktrees.sh 0026-split-runtime 0027-prune-config-schema
#
# Para cada slug:
# 1. git merge --no-ff issue/<slug> a master
# 2. Verificar que master compila después del merge
# 3. Si hay conflict o fallo de build, PARAR inmediatamente
#
# Los slugs deben pasarse en el orden correcto (waves ya resueltas).
# NO hace push — eso lo decide el usuario.
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
if [ $# -eq 0 ]; then
echo "ERROR: se necesita al menos un slug"
echo "Uso: $0 <slug-1> <slug-2> ..."
exit 1
fi
# Asegurar que estamos en master
echo "=== Cambiando a master ==="
cd "$REPO_ROOT"
git checkout master
MERGED=0
FAILED_AT=""
for slug in "$@"; do
branch="issue/${slug}"
echo ""
echo "=== Integrando: ${branch} ==="
# Verificar que la branch existe
if ! git show-ref --verify --quiet "refs/heads/${branch}"; then
echo "FAIL: branch ${branch} no existe"
FAILED_AT="$slug"
break
fi
# Merge --no-ff
if ! git merge --no-ff "$branch" -m "merge: ${branch} — implementación paralela"; then
echo ""
echo "CONFLICT: merge de ${branch} tiene conflictos"
echo "Resolver manualmente y luego continuar con los slugs restantes"
echo ""
echo "Para resolver:"
echo " 1. git status (ver archivos en conflicto)"
echo " 2. Resolver conflictos en cada archivo"
echo " 3. git add <archivos>"
echo " 4. git commit"
echo ""
echo "Slugs pendientes después de ${slug}:"
FOUND=0
for remaining in "$@"; do
if [ "$FOUND" -eq 1 ]; then
echo " - ${remaining}"
fi
if [ "$remaining" = "$slug" ]; then
FOUND=1
fi
done
exit 1
fi
echo "MERGED: ${branch}"
# 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
MERGED=$((MERGED + 1))
done
echo ""
echo "=== Resumen de integración ==="
echo "Mergeados: ${MERGED} de $#"
if [ -n "$FAILED_AT" ]; then
echo "Falló en: ${FAILED_AT}"
echo ""
echo "Worktrees NO limpiados (resolver primero el fallo)"
exit 1
fi
# Limpieza de worktrees y branches
echo ""
echo "=== Limpieza ==="
for slug in "$@"; do
path="${REPO_ROOT}/worktrees/${slug}"
branch="issue/${slug}"
if [ -d "$path" ]; then
git worktree remove "$path" 2>/dev/null && echo "REMOVED: worktree ${path}" || echo "WARN: no se pudo eliminar worktree ${path}"
fi
git branch -d "$branch" 2>/dev/null && echo "DELETED: branch ${branch}" || echo "WARN: no se pudo eliminar branch ${branch}"
done
echo ""
echo "=== Integración completa ==="
echo "Master tiene ${MERGED} merges nuevos."
echo ""
echo "Para publicar: git push"
@@ -0,0 +1,76 @@
#!/bin/bash
# setup-worktrees.sh — Crea git worktrees para ejecución paralela de issues
#
# Uso: ./setup-worktrees.sh <slug-1> <slug-2> ...
# Ejemplo: ./setup-worktrees.sh 0026-split-runtime 0027-prune-config-schema
#
# Cada slug genera:
# worktrees/<slug>/ (worktree completo)
# branch: issue/<slug>
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
WORKTREE_DIR="${REPO_ROOT}/worktrees"
if [ $# -eq 0 ]; then
echo "ERROR: se necesita al menos un slug de issue"
echo "Uso: $0 <slug-1> <slug-2> ..."
exit 1
fi
# Asegurar que master está actualizado
echo "=== Actualizando master ==="
CURRENT_BRANCH="$(git branch --show-current)"
git checkout master 2>/dev/null
git pull --rebase 2>/dev/null || echo "WARN: no se pudo pull (sin remote o sin conexión)"
# Volver a la rama original si no era master
if [ "$CURRENT_BRANCH" != "master" ] && [ -n "$CURRENT_BRANCH" ]; then
git checkout "$CURRENT_BRANCH" 2>/dev/null
fi
mkdir -p "$WORKTREE_DIR"
CREATED=0
SKIPPED=0
FAILED=0
for slug in "$@"; do
branch="issue/${slug}"
path="${WORKTREE_DIR}/${slug}"
if [ -d "$path" ]; then
echo "SKIP: worktree ya existe: ${path}"
SKIPPED=$((SKIPPED + 1))
continue
fi
# Verificar que la branch no existe ya
if git show-ref --verify --quiet "refs/heads/${branch}" 2>/dev/null; then
echo "WARN: branch ${branch} ya existe, creando worktree desde ella"
git worktree add "$path" "$branch" 2>/dev/null || {
echo "FAIL: no se pudo crear worktree para ${slug}"
FAILED=$((FAILED + 1))
continue
}
else
echo "CREATE: worktree ${path} (branch ${branch})"
git worktree add -b "$branch" "$path" master 2>/dev/null || {
echo "FAIL: no se pudo crear worktree para ${slug}"
FAILED=$((FAILED + 1))
continue
}
fi
CREATED=$((CREATED + 1))
done
echo ""
echo "=== Resumen ==="
echo "Creados: ${CREATED}"
echo "Existentes: ${SKIPPED}"
echo "Fallidos: ${FAILED}"
echo ""
echo "=== Worktrees activos ==="
git worktree list
@@ -0,0 +1,165 @@
#!/bin/bash
# verify-worktree.sh — Verifica build, tests y cierre de issue en un worktree.
#
# Uso:
# ./verify-worktree.sh <worktree-path> [build-cmd] [test-cmd]
#
# 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 fallo
# 3 = tests fallaron
# 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> [build-cmd] [test-cmd]"
exit 1
fi
WORKTREE="$1"
ARG_BUILD_CMD="${2:-}"
ARG_TEST_CMD="${3:-}"
# Resolver path absoluto
if [[ "$WORKTREE" != /* ]]; then
REPO_ROOT="$(git rev-parse --show-toplevel)"
WORKTREE="${REPO_ROOT}/${WORKTREE}"
fi
if [ ! -d "$WORKTREE" ]; then
echo "ERROR: worktree no encontrado: ${WORKTREE}"
exit 1
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
echo "FAIL: sin commits propios en la branch"
exit 5
fi
echo "OK: ${COMMIT_COUNT} commits desde master"
cd "$WORKTREE" && git log master..HEAD --oneline
# 2. Build
echo ""
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 "--- Build SKIPPED (sin comando) ---"
fi
# 3. Tests
echo ""
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 "--- Tests SKIPPED (sin comando) ---"
fi
# 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)
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 detecto issue movido a completed/ (verificar manualmente)"
fi
echo ""
echo "=== RESULTADO: ${SLUG} — OK ==="
-104
View File
@@ -1,104 +0,0 @@
---
name: parallel-issues
description: Analiza issues y genera plan de ejecución paralela en PARALLEL_EXECUTION_ORDER.md
argument-hint: [--dry-run]
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write
---
# parallel-issues
Analiza issues pendientes y genera plan de ejecución paralela agrupando issues independientes.
## Sintaxis
```bash
/parallel-issues # Genera archivo
/parallel-issues --dry-run # Solo muestra análisis
```
## Cuándo usar
- Identificar issues paralelizables sin conflictos
- Planificar desarrollo con múltiples worktrees
- Antes de sesiones intensivas de desarrollo
## Flujo
### 1. Detectar contexto
```bash
# Proyecto padre o hijo?
if [[ "$PWD" == *"/workspaces/"* ]]; then
PROJECT_TYPE="child"
else
PROJECT_TYPE="parent"
fi
```
### 2. Listar issues pendientes
```bash
ls -1 dev/issues/*.md | grep -E '[0-9]{4}-.*\.md$' | sort
```
Para cada issue extraer:
- Número, título, estado
- Archivos mencionados
- Dependencias explícitas (#NNNN)
### 3. Analizar conflictos
**Criterios de conflicto (NO paralelizables):**
- Archivos compartidos
- Dependencias explícitas
- Dependencias transitivas
### 4. Agrupar por independencia
Algoritmo de grafos:
- Grupo 1: Issues sin dependencias
- Grupo 2: Issues que dependen solo de Grupo 1
- etc.
### 5. Estimar tiempos
Factores:
- Cantidad de archivos
- Capa afectada (core/shell/app)
- Palabras clave (refactor, fix, nuevo)
### 6. Generar PARALLEL_EXECUTION_ORDER.md
```markdown
# Plan de Ejecución Paralela
## Grupo 1: Issues Independientes
- Issue #0003 - ...
- Issue #0006 - ...
## Grupo 2: Dependientes de Grupo 1
- Issue #0004 - depende de #0003
## Resumen
| Métrica | Valor |
|---------|-------|
| Ahorro tiempo | 60% |
```
### 7. Mostrar resultado
```
Plan generado: PARALLEL_EXECUTION_ORDER.md
Issues analizadas: N
Grupos paralelos: M
Ahorro estimado: X%
```
## Convenciones
- Nombres de grupo: "Grupo N"
- Worktrees: `worktrees/issue-NNNN`
- Estimación en horas (redondeado a .5)
+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 - 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) - 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 - 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
-77
View File
@@ -1,77 +0,0 @@
---
name: quick-issue
description: Crea un issue automáticamente desde TUI con detección automática de número
argument-hint: --text "descripción"
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write, Edit
---
# quick-issue
Crea un issue rápido desde TUI. **No invocar manualmente** - es para uso automático.
## Sintaxis
```bash
/quick-issue --text "descripción del issue"
```
## Precondiciones
- [ ] Directorio `dev/issues/` existe
- [ ] Parámetro `--text` proporcionado
- [ ] Working tree limpio
## Flujo
### 1. Determinar número
```bash
ls -1 dev/issues/*.md | grep -E '^dev/issues/[0-9]{4}[a-z]?-' | sort -V
```
Siguiente = último número base + 1 (ignorar letras).
### 2. Generar título y slug
- Título: usar `--text` directamente
- Slug: convertir a kebab-case
### 3. Crear archivo de issue
Template minimalista con:
- Metadata básica
- Objetivo = texto del parámetro
- Tareas a completar con /fix-issue
### 4. Actualizar índice
Agregar línea en `dev/issues/README.md`.
### 5. Crear commits y mergear (sin confirmación)
```bash
git checkout -b quick/quick-issue-NNNN
git add dev/issues/NNNN-slug.md dev/issues/README.md
git commit -m "docs: crear issue NNNN-slug"
git checkout master
git merge --no-ff quick/quick-issue-NNNN
git push
git branch -d quick/quick-issue-NNNN
```
### 6. Reportar resultado
```
Issue NNNN-slug creado e integrado
Para implementar:
/fix-issue NNNN
```
## Convenciones
- Auto-detección de número
- Sin confirmación (flujo automático)
- Template minimalista
+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.
-88
View File
@@ -1,88 +0,0 @@
---
name: sort-issues
description: Analiza dependencias y genera orden de ejecución óptimo de issues
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash, Read, Write
---
# sort-issues
Analiza issues, construye grafo de dependencias y muestra/genera orden de ejecución recomendado.
## Sintaxis
```bash
/sort-issues
```
## Flujo
### 1. Listar issues pendientes
```bash
ls dev/issues/*.md | grep -E '^dev/issues/[0-9]{4}[a-z]?-.*\.md$' | sort
```
### 2. Extraer dependencias de cada issue
Buscar:
- Tabla "## Dependencias"
- Línea "Bloqueada por"
- Referencias #NNNN
### 3. Construir grafo y detectar ciclos
Si hay ciclos:
```
Dependencias circulares detectadas:
0010 → 0011 → 0012 → 0010
Revisar:
- dev/issues/0010-*.md
- dev/issues/0011-*.md
```
### 4. Calcular orden topológico
Algoritmo Kahn o DFS post-order.
Desempate:
1. Número menor primero
2. Issues sin deps primero
### 5. Generar EXECUTION_ORDER.md
```markdown
# Execution Order
## Recommended Order
1. #0001 - titulo — razón
2. #0002 - titulo — razón
## Critical Path
- #0001#0002, #0003
## Parallelizable Groups
### Group 1 (after #0001)
- #0002
- #0003
```
### 6. Mostrar resultado
```
Orden generado: dev/EXECUTION_ORDER.md
Issues: N
Camino crítico: #X → #Y → #Z
Grupos paralelos: M
Próxima issue: #0001 - titulo
```
## Convenciones
- Solo leer issues (no modificar)
- Detectar ambos formatos de dependencias
- Reportar ciclos claramente
+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_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') CONTEXT_TOTAL=$(echo "$INPUT" | jq -r '.context_window.context_window_size // 200000')
CURRENT_DIR=$(echo "$INPUT" | jq -r '.workspace.current_dir // "~"' | sed "s|$HOME|~|") 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) # 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') 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) CONTEXT_PCT=$(echo "scale=0; $CONTEXT_USED * 100 / $CONTEXT_TOTAL" | bc)
fi fi
# Persistir el contexto por sesión en un sidecar para que fleetview (y otras
# herramientas) puedan mostrarlo sin tener este stdin. El statusline se re-ejecuta
# cada pocos segundos, así que el dato se mantiene fresco mientras la sesión vive.
if [ -n "$SESSION_ID" ]; then
RTDIR="$HOME/.claude/runtime"
mkdir -p "$RTDIR" 2>/dev/null
RTF="$RTDIR/${SESSION_ID}.json"
RTMP="${RTF}.tmp.$$"
if jq -n \
--argjson pct "${CONTEXT_PCT:-0}" \
--argjson used "${CONTEXT_USED:-0}" \
--argjson total "${CONTEXT_TOTAL:-200000}" \
'{ctx_pct:$pct, ctx_used:$used, ctx_total:$total}' > "$RTMP" 2>/dev/null; then
mv "$RTMP" "$RTF" 2>/dev/null
else
rm -f "$RTMP" 2>/dev/null
fi
fi
# Costos # Costos
TOTAL_COST=$(echo "$INPUT" | jq -r '.cost.total_cost_usd // 0' | xargs printf "%.3f") TOTAL_COST=$(echo "$INPUT" | jq -r '.cost.total_cost_usd // 0' | xargs printf "%.3f")
SESSION_DURATION=$(echo "$INPUT" | jq -r '.cost.total_duration_ms // 0') SESSION_DURATION=$(echo "$INPUT" | jq -r '.cost.total_duration_ms // 0')
@@ -49,11 +73,27 @@ LINES_REMOVED=$(echo "$INPUT" | jq -r '.cost.total_lines_removed // 0')
# Rate Limits # Rate Limits
RATE_5H=$(echo "$INPUT" | jq -r '.rate_limits.five_hour.used_percentage // 0' | xargs printf "%.0f") 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") 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_BRANCH=""
GIT_STATUS="" 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") GIT_BRANCH=$(git branch --show-current 2>/dev/null || echo "detached")
# Obtener archivos staged, modified, untracked # Obtener archivos staged, modified, untracked
@@ -81,8 +121,41 @@ if git rev-parse --git-dir > /dev/null 2>&1; then
# Trim trailing space # Trim trailing space
GIT_STATUS=$(echo "$GIT_STATUS" | sed 's/ $//') 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 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 # Función para crear barra de progreso
progress_bar() { progress_bar() {
local pct=$1 local pct=$1
@@ -190,23 +263,49 @@ LINE2="${LINE2} ${GRAY}│${RESET} ${DIM}Total:${RESET} ${CYAN}↓${TOTAL_IN_FMT
# 4. Rate Limits (siempre mostrar) # 4. Rate Limits (siempre mostrar)
LINE2="${LINE2} ${GRAY}${RESET} ${BOLD}Limits:${RESET}" LINE2="${LINE2} ${GRAY}${RESET} ${BOLD}Limits:${RESET}"
# 5h limit # Color por burndown vs tasa esperada
if [ "$RATE_5H" -ge 80 ]; then # Tasa: % consumible permitido por unidad de tiempo (5h: 20%/h, 7d: 14%/dia)
LINE2="${LINE2} ${RED}5h:${RATE_5H}%${RESET}" # expected = tasa * unidades_restantes_hasta_reset
elif [ "$RATE_5H" -ge 50 ]; then # available = 100 - used%
LINE2="${LINE2} ${YELLOW}5h:${RATE_5H}%${RESET}" # verde: available >= expected (consumo bajo control)
else # amarillo: available >= expected/2 (consumo agresivo)
LINE2="${LINE2} ${GREEN}5h:${RATE_5H}%${RESET}" # rojo: available < expected/2 (riesgo de agotar antes del reset)
fi 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 # 5h limit (tasa 20%/h)
if [ "$RATE_7D" -ge 80 ]; then SECS_5H=0
LINE2="${LINE2} ${RED}7d:${RATE_7D}%${RESET}" [ "$RESET_5H_EPOCH" -gt "$NOW_EPOCH" ] && SECS_5H=$((RESET_5H_EPOCH - NOW_EPOCH))
elif [ "$RATE_7D" -ge 50 ]; then C5=$(burndown_color $RATE_5H $SECS_5H 20 3600)
LINE2="${LINE2} ${YELLOW}7d:${RATE_7D}%${RESET}" RESET_5H_STR=""
else [ -n "$RESET_5H" ] && RESET_5H_STR=" ${C5}${RESET_5H}${RESET}"
LINE2="${LINE2} ${GREEN}7d:${RATE_7D}%${RESET}" LINE2="${LINE2} ${C5}5h:${RATE_5H}%${RESET}${RESET_5H_STR} ${GRAY}${RESET}"
fi
# 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 # 5. Duración sesión
if [ "$SESSION_DURATION" -gt 0 ]; then if [ "$SESSION_DURATION" -gt 0 ]; then
@@ -217,6 +316,60 @@ fi
# 6. Directorio actual # 6. Directorio actual
LINE2="${LINE2} ${GRAY}${RESET} ${BLUE}${CURRENT_DIR}${RESET}" 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) # Imprimir resultado (2 líneas)
echo -e "$LINE1" echo -e "$LINE1"
echo -e "$LINE2" echo -e "$LINE2"
+79 -11
View File
@@ -9,7 +9,7 @@ REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLAUDE_DIR="$HOME/.claude" CLAUDE_DIR="$HOME/.claude"
# Carpetas a enlazar (configuración compartible) # Carpetas a enlazar (configuración compartible)
FOLDERS=("skills" "agents") FOLDERS=("skills" "agents" "commands")
echo "=== Instalando configuración de Claude ===" echo "=== Instalando configuración de Claude ==="
echo "Repositorio: $REPO_DIR/.claude" echo "Repositorio: $REPO_DIR/.claude"
@@ -54,20 +54,26 @@ done
echo "" echo ""
echo "=== Instalando archivos de configuración ===" echo "=== Instalando archivos de configuración ==="
# 1. Status Line Script # 1. Status Line Script (enlace simbólico)
STATUSLINE_SOURCE="$REPO_DIR/.claude/statusline.sh" STATUSLINE_SOURCE="$REPO_DIR/.claude/statusline.sh"
STATUSLINE_TARGET="$CLAUDE_DIR/statusline.sh" STATUSLINE_TARGET="$CLAUDE_DIR/statusline.sh"
if [ -f "$STATUSLINE_SOURCE" ]; then if [ -f "$STATUSLINE_SOURCE" ]; then
if [ -f "$STATUSLINE_TARGET" ]; then chmod +x "$STATUSLINE_SOURCE"
BACKUP="$STATUSLINE_TARGET.backup.$(date +%Y%m%d_%H%M%S)" if [ -L "$STATUSLINE_TARGET" ] && [ "$(readlink "$STATUSLINE_TARGET")" = "$STATUSLINE_SOURCE" ]; then
echo "Backup: statusline.sh -> $BACKUP" echo "OK: statusline.sh ya está enlazado correctamente"
mv "$STATUSLINE_TARGET" "$BACKUP" 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 fi
cp "$STATUSLINE_SOURCE" "$STATUSLINE_TARGET"
chmod +x "$STATUSLINE_TARGET"
echo "Copiado: statusline.sh (ejecutable)"
else else
echo "WARN: statusline.sh no encontrado en el repo" echo "WARN: statusline.sh no encontrado en el repo"
fi fi
@@ -96,6 +102,66 @@ else
echo "WARN: settings.json no encontrado en el repo" echo "WARN: settings.json no encontrado en el repo"
fi fi
# 3. CLAUDE.md (enlace simbólico - preferencias globales)
CLAUDEMD_SOURCE="$REPO_DIR/.claude/CLAUDE.md"
CLAUDEMD_TARGET="$CLAUDE_DIR/CLAUDE.md"
if [ -f "$CLAUDEMD_SOURCE" ]; then
if [ -L "$CLAUDEMD_TARGET" ] && [ "$(readlink "$CLAUDEMD_TARGET")" = "$CLAUDEMD_SOURCE" ]; then
echo "OK: CLAUDE.md ya está enlazado correctamente"
else
# Symlink (roto o apuntando mal): borrar; archivo real: backup
if [ -L "$CLAUDEMD_TARGET" ]; then
rm -f "$CLAUDEMD_TARGET"
elif [ -e "$CLAUDEMD_TARGET" ]; then
BACKUP="$CLAUDEMD_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
echo "Backup: CLAUDE.md -> $BACKUP"
mv "$CLAUDEMD_TARGET" "$BACKUP"
fi
ln -s "$CLAUDEMD_SOURCE" "$CLAUDEMD_TARGET"
echo "Enlazado: CLAUDE.md -> $CLAUDEMD_SOURCE"
fi
else
echo "WARN: CLAUDE.md no encontrado en el repo"
fi
# === Instalando hooks (enlace simbólico por archivo) ===
echo ""
echo "=== Instalando hooks ==="
HOOKS_SOURCE_DIR="$REPO_DIR/.claude/hooks"
HOOKS_TARGET_DIR="$CLAUDE_DIR/hooks"
if [ -d "$HOOKS_SOURCE_DIR" ]; then
mkdir -p "$HOOKS_TARGET_DIR"
for hook in "$HOOKS_SOURCE_DIR"/*.sh; do
[ -e "$hook" ] || continue
chmod +x "$hook"
HOOK_NAME="$(basename "$hook")"
HOOK_TARGET="$HOOKS_TARGET_DIR/$HOOK_NAME"
# Si ya es symlink correcto, saltar
if [ -L "$HOOK_TARGET" ] && [ "$(readlink "$HOOK_TARGET")" = "$hook" ]; then
echo "OK: hooks/$HOOK_NAME ya está enlazado correctamente"
continue
fi
# Symlink (roto o apuntando mal): borrar sin backup; archivo real: backup
if [ -L "$HOOK_TARGET" ]; then
rm -f "$HOOK_TARGET"
elif [ -e "$HOOK_TARGET" ]; then
BACKUP="$HOOK_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
echo "Backup: hooks/$HOOK_NAME -> $BACKUP"
mv "$HOOK_TARGET" "$BACKUP"
fi
ln -s "$hook" "$HOOK_TARGET"
echo "Enlazado: hooks/$HOOK_NAME -> $hook"
done
else
echo "WARN: $HOOKS_SOURCE_DIR no existe, saltando hooks"
fi
# === Limpieza de configuración que no debe cambiar === # === Limpieza de configuración que no debe cambiar ===
echo "" echo ""
echo "=== Limpiando configuración inmutable ===" echo "=== Limpiando configuración inmutable ==="
@@ -188,7 +254,9 @@ echo "=== Instalación completada ==="
echo "Tus comandos y configuración ahora están sincronizados con el repositorio." echo "Tus comandos y configuración ahora están sincronizados con el repositorio."
echo "" echo ""
echo "Configuración instalada:" echo "Configuración instalada:"
echo " • Skills y Agents enlazados simbólicamente" echo " • Skills, Agents y Commands enlazados simbólicamente"
echo " • Hooks (goal_*.sh) enlazados simbólicamente"
echo " • CLAUDE.md (preferencias globales) enlazado"
echo " • Status Line configurada con vibecoding setup" echo " • Status Line configurada con vibecoding setup"
echo " • Settings.json enlazado (compartido entre repos)" echo " • Settings.json enlazado (compartido entre repos)"
echo " • Backups viejos limpiados (>7 días)" echo " • Backups viejos limpiados (>7 días)"