Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e2b5ac56eb | |||
| 86252b7d2c | |||
| e976fb303a | |||
| 8e53e0818e | |||
| bb735cad17 | |||
| 0e8d2d2ff2 | |||
| ffb3f9b270 | |||
| 1b769a9666 | |||
| 963b3bd7e1 | |||
| 393a77b597 | |||
| 50290a71e7 | |||
| a3ecb6a4cf | |||
| 1840402453 | |||
| 9ac52501b5 | |||
| 1a15108b56 | |||
| 54f47570d1 | |||
| adfb45015e | |||
| 3ae472d1f3 | |||
| fa2b2e16bc | |||
| 4d1fff53e9 | |||
| eb42966295 | |||
| 8c9919f1f8 | |||
| f881b7703b | |||
| 5efcedf9ba | |||
| 71874079cd | |||
| 3d0002625b | |||
| 3c7a91e0c0 | |||
| 25eefbd5e3 | |||
| bf8651020e | |||
| e2a131a6dc | |||
| 3d77f4a5d2 | |||
| be97d03c97 | |||
| 31b28cf260 | |||
| e6f24187b4 | |||
| b0a9e31abd |
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
# Autogeneracion de objetivo + DoD a partir del primer prompt sustantivo de una
|
||||
# terminal que aun no tiene objetivo. Lo lanza goal_tracker.sh en background (no
|
||||
# bloquea el turno). Usa ask_llm (haiku, API directa; nunca `claude -p`).
|
||||
#
|
||||
# Args: <session_id> <goal_json_file> <prompt_text>
|
||||
|
||||
SID="$1"
|
||||
F="$2"
|
||||
PROMPT="$3"
|
||||
|
||||
# Si ya existe objetivo DEFINITIVO (usuario manual u otro autogen ya termino), no
|
||||
# pisar. Un archivo PROVISIONAL (.provisional=true) SI se pisa: es el placeholder
|
||||
# (= texto del usuario) que pusimos para que el statusline no quede vacio.
|
||||
if [ -f "$F" ] && [ "$(jq -r '.provisional // false' "$F" 2>/dev/null)" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PY="$HOME/fn_registry/python/.venv/bin/python3"
|
||||
ASK="$HOME/fn_registry/python/functions/core/ask_llm.py"
|
||||
[ -x "$PY" ] || exit 0
|
||||
[ -f "$ASK" ] || exit 0
|
||||
|
||||
P=$(printf '%s' "$PROMPT" | tail -c 2000)
|
||||
[ -z "$P" ] && exit 0
|
||||
|
||||
SYS="Dado el PRIMER mensaje de un usuario a un asistente de codigo en una terminal, infiere un OBJETIVO breve de la tarea (maximo 8 palabras, en espanol, sin comillas), un DoD breve (definition of done: condicion concreta de 'terminado', maximo 8 palabras, en espanol) y EXACTAMENTE 3 EMOJIS que representen visualmente la tarea (3 emojis pegados, sin espacios ni texto entre ellos). Responde SOLO un objeto JSON en una sola linea, sin markdown ni texto extra: {\"goal\":\"...\",\"dod\":\"...\",\"emojis\":\"🔭✨🌌\"}. Si el mensaje es un saludo, charla trivial o no describe ninguna tarea, responde exactamente {}."
|
||||
|
||||
RAW=$("$PY" "$ASK" --system "$SYS" "$P" 2>/dev/null)
|
||||
[ -z "$RAW" ] && exit 0
|
||||
|
||||
# Extraer el primer objeto JSON de la salida (tolerante a texto/markdown extra).
|
||||
JSON=$(printf '%s' "$RAW" | tr '\n' ' ' | grep -o '{[^{}]*}' | head -1)
|
||||
[ -z "$JSON" ] && exit 0
|
||||
|
||||
GOAL=$(printf '%s' "$JSON" | jq -r '.goal // ""' 2>/dev/null)
|
||||
DOD=$(printf '%s' "$JSON" | jq -r '.dod // ""' 2>/dev/null)
|
||||
EMOJIS=$(printf '%s' "$JSON" | jq -r '.emojis // ""' 2>/dev/null)
|
||||
[ -z "$GOAL" ] && exit 0
|
||||
|
||||
# Carrera: si entre tanto aparecio un objetivo DEFINITIVO (manual), respetarlo.
|
||||
# Si solo esta el provisional, lo pisamos abajo con el definitivo.
|
||||
if [ -f "$F" ] && [ "$(jq -r '.provisional // false' "$F" 2>/dev/null)" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TMP="${F}.tmp.$$"
|
||||
if [ -f "$F" ]; then
|
||||
# Pisar el provisional: fija goal/dod/emojis definitivos y quita el flag,
|
||||
# preservando phase/history/prompts que el provisional ya hubiera acumulado.
|
||||
if jq --arg g "$GOAL" --arg d "$DOD" --arg e "$EMOJIS" \
|
||||
'del(.provisional) | .goal=$g | (if $d != "" then .dod=$d else . end) | (if $e != "" then .emojis=$e else . end)' \
|
||||
"$F" > "$TMP" 2>/dev/null; then
|
||||
mv "$TMP" "$F"
|
||||
else
|
||||
rm -f "$TMP"
|
||||
fi
|
||||
else
|
||||
if jq -n --arg g "$GOAL" --arg d "$DOD" --arg e "$EMOJIS" --arg p "$P" \
|
||||
'{goal:$g, phase:"planificando", history:["planificando"], prompts:[$p]} | (if $d != "" then .dod=$d else . end) | (if $e != "" then .emojis=$e else . end)' > "$TMP" 2>/dev/null; then
|
||||
mv "$TMP" "$F"
|
||||
else
|
||||
rm -f "$TMP"
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
# PostToolUse hook: marca el estado ACTIVO de la tarea segun la herramienta que
|
||||
# el asistente acaba de usar. Determinista, sin LLM, en tiempo real. Solo actua
|
||||
# si la terminal tiene un objetivo fijado.
|
||||
#
|
||||
# El estado de REPOSO (al parar: hecho/pendiente_revision/bloqueado/en_pausa) lo
|
||||
# pone el Stop hook (goal_phase_eval.sh + goal_phase_worker.sh).
|
||||
|
||||
INPUT=$(cat)
|
||||
SID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
||||
[ -z "$SID" ] && exit 0
|
||||
|
||||
F="$HOME/.claude/goals/${SID}.json"
|
||||
[ -f "$F" ] || exit 0
|
||||
GOAL=$(jq -r '.goal // ""' "$F" 2>/dev/null)
|
||||
[ -z "$GOAL" ] && exit 0
|
||||
CUR=$(jq -r '.phase // ""' "$F" 2>/dev/null)
|
||||
|
||||
TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
||||
[ -z "$TOOL" ] && exit 0
|
||||
|
||||
PHASE=""
|
||||
case "$TOOL" in
|
||||
Read|Grep|Glob|NotebookRead|WebFetch|WebSearch|ToolSearch)
|
||||
PHASE=investigando ;;
|
||||
Edit|Write|MultiEdit|NotebookEdit)
|
||||
# Editar tras haber testeado = retoques finales -> puliendo. Si no, es
|
||||
# implementacion normal -> haciendo.
|
||||
case "$CUR" in
|
||||
testeando|puliendo) PHASE=puliendo ;;
|
||||
*) PHASE=haciendo ;;
|
||||
esac
|
||||
;;
|
||||
Task|Agent|Workflow)
|
||||
PHASE=haciendo ;;
|
||||
Bash)
|
||||
CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
||||
case "$CMD" in
|
||||
*pytest*|*"go test"*|*ctest*|*jest*|*vitest*|*"npm test"*|*"npm run test"*|*"cargo test"*|*unittest*|*" test "*|*"./fn run"*test*)
|
||||
PHASE=testeando ;;
|
||||
ls|ls\ *|cat\ *|*grep*|find\ *|head\ *|tail\ *|stat\ *|tree*|rg\ *|fd\ *|*"git status"*|*"git log"*|*"git diff"*|*"git show"*|*"git branch"*)
|
||||
PHASE=investigando ;;
|
||||
*)
|
||||
PHASE=haciendo ;;
|
||||
esac
|
||||
;;
|
||||
mcp__registry__fn_search|mcp__registry__fn_show|mcp__registry__fn_code|mcp__registry__fn_uses|mcp__registry__fn_list_domains|mcp__registry__fn_doctor|mcp__registry__fn_proposal)
|
||||
PHASE=investigando ;;
|
||||
mcp__registry__fn_run)
|
||||
PHASE=haciendo ;;
|
||||
TodoWrite|ExitPlanMode|EnterPlanMode|Plan)
|
||||
PHASE=planificando ;;
|
||||
*)
|
||||
# Herramientas que no representan un cambio de actividad (AskUserQuestion,
|
||||
# etc.): no tocar la fase.
|
||||
exit 0 ;;
|
||||
esac
|
||||
[ -z "$PHASE" ] && exit 0
|
||||
|
||||
# Escribir la fase + mantener historial (append solo si cambia respecto al
|
||||
# ultimo; se conservan los ultimos 12 estados).
|
||||
TMP="${F}.tmp.$$"
|
||||
if jq --arg p "$PHASE" '
|
||||
.phase = $p
|
||||
| .history = (
|
||||
( .history // [] ) as $h
|
||||
| ( if ($h | length) > 0 and ($h[-1] == $p) then $h else ($h + [$p]) end )
|
||||
| .[-12:]
|
||||
)
|
||||
' "$F" > "$TMP" 2>/dev/null; then
|
||||
mv "$TMP" "$F"
|
||||
else
|
||||
rm -f "$TMP"
|
||||
fi
|
||||
exit 0
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# Stop hook: tras cada respuesta del asistente, dispara (en background) la
|
||||
# clasificacion de la fase de la tarea. Lee la ultima respuesta del transcript,
|
||||
# la clasifica con ask_llm (haiku) y escribe el resultado en el goal JSON de la
|
||||
# sesion. El statusline lo pinta en el siguiente render.
|
||||
#
|
||||
# No bloquea el cierre del turno: el trabajo pesado va al worker en background.
|
||||
|
||||
INPUT=$(cat)
|
||||
SID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
||||
TRANSCRIPT=$(printf '%s' "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
|
||||
|
||||
[ -z "$SID" ] && exit 0
|
||||
|
||||
F="$HOME/.claude/goals/${SID}.json"
|
||||
# Solo si esta terminal tiene un objetivo fijado.
|
||||
[ -f "$F" ] || exit 0
|
||||
GOAL=$(jq -r '.goal // ""' "$F" 2>/dev/null)
|
||||
[ -z "$GOAL" ] && exit 0
|
||||
|
||||
# Salir del estado ACTIVO de inmediato (sincrono, instantaneo): al parar no debe
|
||||
# quedarse mostrando investigando/haciendo/testeando mientras el worker (haiku,
|
||||
# background) afina el reposo a hecho/pendiente_revision/bloqueado/en_pausa.
|
||||
CUR=$(jq -r '.phase // ""' "$F" 2>/dev/null)
|
||||
case "$CUR" in
|
||||
investigando|planificando|haciendo|testeando|puliendo)
|
||||
TMP="${F}.prov.$$"
|
||||
if jq '.phase="en_pausa"' "$F" > "$TMP" 2>/dev/null; then mv "$TMP" "$F"; else rm -f "$TMP"; fi
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -z "$TRANSCRIPT" ] && exit 0
|
||||
[ -f "$TRANSCRIPT" ] || exit 0
|
||||
|
||||
# Afinar el reposo en background; el hook retorna de inmediato (no bloquea el
|
||||
# turno). El statusline reflejara el valor final en el siguiente refresco.
|
||||
nohup bash "$HOME/.claude/hooks/goal_phase_worker.sh" "$SID" "$TRANSCRIPT" "$F" >/dev/null 2>&1 &
|
||||
exit 0
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
#!/bin/bash
|
||||
# Worker del Stop hook: resuelve el estado de REPOSO de la tarea cuando el
|
||||
# asistente para y cede el control. Clasifica con ask_llm (haiku, API directa;
|
||||
# nunca `claude -p`, ver regla llm_invocation.md) y lo escribe en el goal JSON
|
||||
# manteniendo el historial.
|
||||
#
|
||||
# El estado ACTIVO (mientras se trabaja: investigando/haciendo/testeando) lo
|
||||
# marca el hook PostToolUse (goal_phase_active.sh), de forma determinista. Este
|
||||
# worker SOLO produce estados de reposo: hecho, pendiente_revision, bloqueado,
|
||||
# en_pausa.
|
||||
#
|
||||
# Args: <session_id> <transcript_path> <goal_json>
|
||||
|
||||
SID="$1"
|
||||
TRANSCRIPT="$2"
|
||||
F="$3"
|
||||
|
||||
PY="$HOME/fn_registry/python/.venv/bin/python3"
|
||||
ASK="$HOME/fn_registry/python/functions/core/ask_llm.py"
|
||||
|
||||
[ -x "$PY" ] || exit 0
|
||||
[ -f "$ASK" ] || exit 0
|
||||
[ -f "$F" ] || exit 0
|
||||
[ -f "$TRANSCRIPT" ] || exit 0
|
||||
|
||||
GOAL=$(jq -r '.goal // ""' "$F" 2>/dev/null)
|
||||
[ -z "$GOAL" ] && exit 0
|
||||
CUR=$(jq -r '.phase // ""' "$F" 2>/dev/null)
|
||||
DOD=$(jq -r '.dod // ""' "$F" 2>/dev/null)
|
||||
[ -z "$DOD" ] && DOD="(no definido)"
|
||||
|
||||
is_active() {
|
||||
case "$1" in
|
||||
investigando|planificando|haciendo|testeando|puliendo) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Una pasada de abajo a arriba sobre el turno actual: ultima respuesta de texto
|
||||
# del asistente + ultima peticion del usuario + si hubo trabajo (tool_use).
|
||||
LAST=""
|
||||
USER_MSG=""
|
||||
HAS_WORK=0
|
||||
while IFS= read -r line; do
|
||||
t=$(printf '%s' "$line" | jq -r '.type // empty' 2>/dev/null)
|
||||
if [ "$t" = "assistant" ]; then
|
||||
if [ -z "$LAST" ]; then
|
||||
txt=$(printf '%s' "$line" | jq -r '(.message.content // [])[]? | select(.type=="text") | .text' 2>/dev/null)
|
||||
[ -n "$txt" ] && LAST="$txt"
|
||||
fi
|
||||
if printf '%s' "$line" | jq -e '(.message.content // [])[]? | select(.type=="tool_use")' >/dev/null 2>&1; then
|
||||
HAS_WORK=1
|
||||
fi
|
||||
elif [ "$t" = "user" ]; then
|
||||
ctype=$(printf '%s' "$line" | jq -r '.message.content | type' 2>/dev/null)
|
||||
if [ "$ctype" = "string" ]; then
|
||||
USER_MSG=$(printf '%s' "$line" | jq -r '.message.content' 2>/dev/null)
|
||||
break
|
||||
fi
|
||||
if ! printf '%s' "$line" | jq -e '(.message.content // [])[]? | select(.type=="tool_result")' >/dev/null 2>&1; then
|
||||
USER_MSG=$(printf '%s' "$line" | jq -r '(.message.content // [])[]? | select(.type=="text") | .text' 2>/dev/null)
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done < <(tac "$TRANSCRIPT")
|
||||
|
||||
# Solo resolver reposo si hubo trabajo este turno o si veniamos de un estado
|
||||
# activo (paramos tras currar). Charla sobre un reposo previo: no tocar.
|
||||
if [ "$HAS_WORK" = "0" ] && ! is_active "$CUR"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
LAST=$(printf '%s' "$LAST" | tail -c 4000)
|
||||
[ -z "$LAST" ] && exit 0
|
||||
USER_MSG=$(printf '%s' "$USER_MSG" | tail -c 1500)
|
||||
|
||||
SYS="El asistente acaba de PARAR y cede el control al usuario. Clasifica el estado de REPOSO en que queda la tarea. Responde UNA sola palabra, sin nada mas, de: hecho pendiente_revision preguntando bloqueado en_pausa sin_cambio. hecho=el objetivo esta completo y verificado; pendiente_revision=el asistente termino un trabajo y espera que el humano lo revise o apruebe (no hace una pregunta directa); preguntando=el asistente termina formulando una o varias PREGUNTAS concretas al usuario y necesita su respuesta o decision para continuar; bloqueado=no puede avanzar por un error o por falta de informacion/acceso; en_pausa=hizo un avance y espera la siguiente indicacion, sin estar terminado ni preguntar ni bloqueado; sin_cambio=el turno no altera el estado de reposo actual (charla irrelevante). Distingue: si la respuesta acaba con preguntas al usuario es 'preguntando'; si deja un resultado para que lo mire es 'pendiente_revision'. REGLA DEL DoD: se te da el DoD (definition of done) que define cuando la tarea esta TERMINADA. Marca 'hecho' UNICAMENTE si el resultado descrito por el asistente CUMPLE ese DoD de forma clara y verificada; compara punto por punto el resultado contra el DoD. Si el DoD no se cumple del todo, o no esta verificado, NO uses 'hecho': usa 'pendiente_revision' (dejas un resultado para que el humano lo revise) o 'en_pausa' (avance parcial). Si el DoD es '(no definido)', usa tu criterio: 'hecho' solo si el objetivo esta claramente completo y verificado."
|
||||
|
||||
PROMPT="OBJETIVO DE LA TAREA: ${GOAL}
|
||||
|
||||
DEFINITION OF DONE (DoD) — la tarea esta TERMINADA solo si esto se cumple:
|
||||
${DOD}
|
||||
|
||||
ULTIMA PETICION DEL USUARIO:
|
||||
${USER_MSG}
|
||||
|
||||
ULTIMA RESPUESTA DEL ASISTENTE:
|
||||
${LAST}
|
||||
|
||||
Compara el resultado contra el DoD y responde con una sola palabra de la lista permitida:"
|
||||
|
||||
RAW=$("$PY" "$ASK" --model claude-haiku-4-5-20251001 --system "$SYS" "$PROMPT" 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
||||
[ -z "$RAW" ] && exit 0
|
||||
|
||||
case "$RAW" in
|
||||
*sin_cambio*|*sincambio*|*ninguna*|*charla*) exit 0 ;;
|
||||
*pregunt*|*consulta*|*respuesta*) PHASE=preguntando ;;
|
||||
*pendiente*revis*|*revis*|*aprob*) PHASE=pendiente_revision ;;
|
||||
*bloque*) PHASE=bloqueado ;;
|
||||
*hecho*|*complet*|*termin*|*done*) PHASE=hecho ;;
|
||||
*pausa*|*pause*|*siguiente*) PHASE=en_pausa ;;
|
||||
# Si por error devuelve un estado activo al parar, lo tratamos como pausa.
|
||||
investigando|planificando|haciendo|testeando|puliendo) PHASE=en_pausa ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
# Escribir la fase + mantener historial (append solo si cambia respecto al
|
||||
# ultimo; se conservan los ultimos 12 estados).
|
||||
TMP="${F}.tmp.$$"
|
||||
if jq --arg p "$PHASE" '
|
||||
.phase = $p
|
||||
| .history = (
|
||||
( .history // [] ) as $h
|
||||
| ( if ($h | length) > 0 and ($h[-1] == $p) then $h else ($h + [$p]) end )
|
||||
| .[-12:]
|
||||
)
|
||||
' "$F" > "$TMP" 2>/dev/null; then
|
||||
mv "$TMP" "$F"
|
||||
else
|
||||
rm -f "$TMP"
|
||||
fi
|
||||
exit 0
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
# DESACTIVADO (2026-06-21): este hook regeneraba el campo `.dod` (movil) del
|
||||
# goal.json llamando a un LLM (haiku via ask_llm.py) en CADA prompt de CADA
|
||||
# sesion. Con muchas sesiones de la flota activas a la vez eso amplificaba el
|
||||
# rate-limit compartido de la organizacion ("Server is temporarily limiting
|
||||
# requests"). Una request API por turno por agente = coste innecesario.
|
||||
#
|
||||
# El `.dod` movil NO lo consume nadie: el parser de la flota
|
||||
# (functions/infra/list_claude_fleet.go, struct goalFile/readGoal) solo lee
|
||||
# goal/phase/emojis/rename/dod_contract/dod_status/role; ignora `.dod` por
|
||||
# completo. El criterio de aceptacion real que clasifica la flota es
|
||||
# `dod_contract` + `dod_status`, escrito por set_dod_contract.py (sin LLM) y
|
||||
# consumido por ClassifyFleetTermination. Ese sistema queda intacto.
|
||||
#
|
||||
# Por tanto la regeneracion del `.dod` movil con haiku se elimina por completo:
|
||||
# cero llamadas LLM por prompt. El objetivo+DoD inicial los sigue generando
|
||||
# goal_autogen.sh una sola vez por terminal (junto con goal/emojis, que si se
|
||||
# usan); el usuario puede ajustar el DoD a mano con "dod: ...".
|
||||
#
|
||||
# Se conserva el archivo como no-op para no romper ningun disparador historico
|
||||
# (defensa en profundidad). El disparo desde goal_tracker.sh tambien se retiro.
|
||||
exit 0
|
||||
Executable
+129
@@ -0,0 +1,129 @@
|
||||
#!/bin/bash
|
||||
# UserPromptSubmit hook del sistema de objetivo+fase por terminal.
|
||||
#
|
||||
# Modelo:
|
||||
# - El OBJETIVO (target) es el IDENTIFICATIVO de la terminal: se genera una vez
|
||||
# (del primer prompt, o a mano con "objetivo: ...") y NUNCA cambia solo.
|
||||
# - El DoD SI se ajusta con tus prompts para reflejar la condicion de terminado.
|
||||
# - La FASE la mantienen los hooks de fase: PostToolUse (activo) y Stop (reposo).
|
||||
#
|
||||
# Comandos META (se ejecutan FUERA DE BANDA: el hook hace su efecto y BLOQUEA el
|
||||
# prompt con decision=block, asi el agente NO lo recibe ni responde; solo ves una
|
||||
# confirmacion breve):
|
||||
# objetivo: <texto> fija/cambia el objetivo a mano (meta:/goal: equivalen).
|
||||
# objetivo: clear lo borra (tambien -, none, borrar, quitar, reset).
|
||||
# dod: <texto> fija un DoD a mano.
|
||||
# dod: clear lo borra.
|
||||
# pausa marca la fase en en_pausa (Ctrl-C no dispara hooks).
|
||||
|
||||
INPUT=$(cat)
|
||||
SID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
||||
[ -z "$SID" ] && exit 0
|
||||
|
||||
F="$HOME/.claude/goals/${SID}.json"
|
||||
PROMPT=$(printf '%s' "$INPUT" | jq -r '.prompt // ""' 2>/dev/null)
|
||||
PROMPT_TRIM=$(printf '%s' "$PROMPT" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
|
||||
|
||||
# Bloquea el prompt (no llega al agente) y muestra <reason> al usuario.
|
||||
block() { jq -n --arg r "$1" '{decision:"block", reason:$r}'; exit 0; }
|
||||
|
||||
# --- objetivo: <texto> (manual; preserva el DoD si ya existia) ---
|
||||
GOAL_LINE=$(printf '%s' "$PROMPT" | grep -ioE '^[[:space:]]*(objetivo|meta|goal)[[:space:]]*:[[:space:]]*.+' | head -1)
|
||||
if [ -n "$GOAL_LINE" ]; then
|
||||
NEWGOAL=$(printf '%s' "$GOAL_LINE" | sed -E 's/^[^:]*:[[:space:]]*//; s/[[:space:]]+$//')
|
||||
case "$NEWGOAL" in
|
||||
-|clear|none|borrar|quitar|reset)
|
||||
rm -f "$F"
|
||||
block "🎯 Objetivo de esta terminal borrado." ;;
|
||||
esac
|
||||
if [ -f "$F" ]; then
|
||||
PH=$(jq -r '.phase // "planificando"' "$F" 2>/dev/null)
|
||||
DD=$(jq -r '.dod // ""' "$F" 2>/dev/null)
|
||||
else
|
||||
PH="planificando"; DD=""
|
||||
fi
|
||||
TMP="${F}.tmp.$$"
|
||||
if jq -n --arg g "$NEWGOAL" --arg p "$PH" --arg d "$DD" \
|
||||
'{goal:$g, phase:$p, prompts:[]} | if $d != "" then .dod=$d else . end' > "$TMP" 2>/dev/null; then
|
||||
mv "$TMP" "$F"
|
||||
else
|
||||
rm -f "$TMP"
|
||||
fi
|
||||
block "🎯 Objetivo fijado: ${NEWGOAL}"
|
||||
fi
|
||||
|
||||
# Nota: el rename de FleetView se hace ahora con alt+r DENTRO de la TUI (escribe
|
||||
# el campo .rename del goal directamente). Ya no se captura /rename en este hook,
|
||||
# asi el built-in /rename de Claude Code queda libre para renombrar la sesion.
|
||||
|
||||
# --- dod: <texto> ---
|
||||
DOD_LINE=$(printf '%s' "$PROMPT" | grep -ioE '^[[:space:]]*dod[[:space:]]*:[[:space:]]*.+' | head -1)
|
||||
if [ -n "$DOD_LINE" ]; then
|
||||
NEWDOD=$(printf '%s' "$DOD_LINE" | sed -E 's/^[^:]*:[[:space:]]*//; s/[[:space:]]+$//')
|
||||
[ -f "$F" ] || block "Fija primero un objetivo (\"objetivo: ...\") antes del DoD."
|
||||
case "$NEWDOD" in
|
||||
-|clear|none|borrar|quitar|reset)
|
||||
TMP="${F}.tmp.$$"
|
||||
jq 'del(.dod)' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F"
|
||||
block "🏁 DoD borrado." ;;
|
||||
esac
|
||||
TMP="${F}.tmp.$$"
|
||||
if jq --arg d "$NEWDOD" '.dod=$d' "$F" > "$TMP" 2>/dev/null; then mv "$TMP" "$F"; else rm -f "$TMP"; fi
|
||||
block "🏁 DoD fijado: ${NEWDOD}"
|
||||
fi
|
||||
|
||||
# --- pausa (marca manual; Ctrl-C no dispara hooks en Claude Code) ---
|
||||
case "$PROMPT_TRIM" in
|
||||
pausa|pause|pausar|"en pausa"|/pausa)
|
||||
[ -f "$F" ] || block "No hay objetivo en esta terminal."
|
||||
TMP="${F}.tmp.$$"
|
||||
if jq '.phase="en_pausa" | .history=((.history // [])+["en_pausa"])[-12:]' "$F" > "$TMP" 2>/dev/null; then
|
||||
mv "$TMP" "$F"
|
||||
fi
|
||||
block "⏸️ Fase marcada en pausa." ;;
|
||||
esac
|
||||
|
||||
# --- prompt NORMAL: pasa al agente + estado ---
|
||||
# Distinguimos dos situaciones por el flag .provisional del goal file:
|
||||
# - no existe el archivo -> primer prompt: ponemos objetivo PROVISIONAL = tu
|
||||
# texto + lanzamos autogen (haiku, UNA sola vez)
|
||||
# que lo definira.
|
||||
# - existe pero .provisional -> autogen aun no termino (o fallo): conservamos el
|
||||
# provisional y relanzamos autogen (idempotente,
|
||||
# self-healing).
|
||||
# - existe y NO provisional -> objetivo definitivo: solo mostramos estado.
|
||||
#
|
||||
# NOTA (2026-06-21): el campo `.dod` movil YA NO se regenera con LLM en cada
|
||||
# prompt. goal_refine.sh esta desactivado (era una request haiku por turno por
|
||||
# sesion -> amplificaba el rate-limit compartido de la organizacion). El `.dod`
|
||||
# movil no lo consume nadie; el criterio que clasifica la flota es `dod_contract`
|
||||
# + `dod_status` (set_dod_contract.py, sin LLM). El DoD inicial lo fija autogen
|
||||
# una vez; el usuario lo ajusta a mano con "dod: ...".
|
||||
PROV="false"
|
||||
[ -f "$F" ] && PROV=$(jq -r '.provisional // false' "$F" 2>/dev/null)
|
||||
|
||||
if [ -f "$F" ] && [ "$PROV" != "true" ]; then
|
||||
G=$(jq -r '.goal // ""' "$F" 2>/dev/null)
|
||||
P=$(jq -r '.phase // ""' "$F" 2>/dev/null)
|
||||
D=$(jq -r '.dod // ""' "$F" 2>/dev/null)
|
||||
echo "GOAL-TRACKER: file=$F | goal=\"$G\" dod=\"$D\" phase=\"$P\". El objetivo es fijo (identificativo de la terminal, NO lo cambies). El DoD inicial lo fija el autogen una vez (sin LLM por prompt); el usuario lo ajusta con \"dod: ...\" — NO lo regeneres tu. La fase la mantienen los hooks (PostToolUse=activo, Stop=reposo) — NO la escribas. Comandos meta del usuario (no los uses tu): objetivo:/dod:/pausa."
|
||||
else
|
||||
# Sin objetivo definitivo todavia. Mostramos de inmediato un objetivo PROVISIONAL
|
||||
# igual a tu propio texto (truncado), para que el statusline no quede vacio
|
||||
# mientras haiku genera el real en background. autogen pisara este provisional
|
||||
# con el definitivo al terminar (su guard respeta .provisional).
|
||||
if [ "${#PROMPT_TRIM}" -ge 12 ]; then
|
||||
TMP="${F}.tmp.$$"
|
||||
if [ -f "$F" ]; then
|
||||
# Ya habia provisional: conserva su goal, solo acumula el prompt.
|
||||
jq --arg p "$PROMPT_TRIM" '.prompts = ((.prompts // []) + [$p])[-12:]' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F" || rm -f "$TMP"
|
||||
else
|
||||
PROV_GOAL=$(printf '%s' "$PROMPT_TRIM" | head -c 70)
|
||||
jq -n --arg g "$PROV_GOAL" --arg p "$PROMPT_TRIM" \
|
||||
'{goal:$g, phase:"planificando", history:["planificando"], prompts:[$p], provisional:true}' > "$TMP" 2>/dev/null && mv "$TMP" "$F" || rm -f "$TMP"
|
||||
fi
|
||||
nohup bash "$HOME/.claude/hooks/goal_autogen.sh" "$SID" "$F" "$PROMPT" >/dev/null 2>&1 &
|
||||
fi
|
||||
echo "GOAL-TRACKER: file=$F (objetivo PROVISIONAL = tu texto; generando el objetivo+DoD real con haiku en background). El usuario tambien puede fijarlo con \"objetivo: ...\" / \"dod: ...\"."
|
||||
fi
|
||||
exit 0
|
||||
+76
-11
@@ -1,25 +1,90 @@
|
||||
{
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "~/.claude/statusline.sh",
|
||||
"padding": 1
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"gopls-lsp@claude-plugins-official": true
|
||||
},
|
||||
"skipDangerousModePermissionPrompt": true,
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Edit(~/.claude/**)",
|
||||
"Write(~/.claude/**)",
|
||||
"Edit(.claude/**)",
|
||||
"Write(.claude/**)"
|
||||
"Write(.claude/**)",
|
||||
"Bash(CGO_ENABLED=1 go test *)",
|
||||
"Bash(sqlite3 *)",
|
||||
"Read(//home/enmanuel/.claude/**)"
|
||||
],
|
||||
"deny": [
|
||||
"Edit(~/.claude/.git/**)",
|
||||
"Write(~/.claude/.git/**)",
|
||||
"Edit(.git/**)",
|
||||
"Write(.git/**)"
|
||||
],
|
||||
"defaultMode": "dontAsk"
|
||||
},
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/goal_tracker.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Notification": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/goal_notify.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/goal_phase_eval.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/hooks/goal_phase_active.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "~/.claude/statusline.sh",
|
||||
"padding": 1,
|
||||
"refreshInterval": 2
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"gopls-lsp@claude-plugins-official": true,
|
||||
"caveman@caveman": true
|
||||
},
|
||||
"extraKnownMarketplaces": {
|
||||
"caveman": {
|
||||
"source": {
|
||||
"source": "github",
|
||||
"repo": "JuliusBrussee/caveman"
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": "Español",
|
||||
"effortLevel": "xhigh",
|
||||
"voice": {
|
||||
"enabled": true,
|
||||
"mode": "hold"
|
||||
},
|
||||
"skipDangerousModePermissionPrompt": true,
|
||||
"preferredNotifChannel": "notifications_disabled",
|
||||
"agentPushNotifEnabled": false,
|
||||
"voiceEnabled": true
|
||||
}
|
||||
|
||||
@@ -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/
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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"; }
|
||||
|
||||
# --- Parámetros ---
|
||||
# Env vars configurables (con defaults razonables):
|
||||
# DEVFACTORY_PATH — ruta local al repo DevFactory (default: $HOME/.local_agentes/backend)
|
||||
# DEVFACTORY_MODULE — nombre del módulo Go de DevFactory
|
||||
# GO_NAMESPACE — namespace para el módulo Go del proyecto (default: github.com/lucasdataproyects)
|
||||
MODULE_NAME="${1:-}"
|
||||
TARGET_PATH="${2:-.}"
|
||||
DEVFACTORY_PATH="$HOME/.local_agentes/backend"
|
||||
DEVFACTORY_MODULE="github.com/lucasdataproyects/devfactory"
|
||||
DEVFACTORY_PATH="${DEVFACTORY_PATH:-$HOME/.local_agentes/backend}"
|
||||
DEVFACTORY_MODULE="${DEVFACTORY_MODULE:-github.com/lucasdataproyects/devfactory}"
|
||||
GO_NAMESPACE="${GO_NAMESPACE:-github.com/lucasdataproyects}"
|
||||
|
||||
# --- Validar nombre ---
|
||||
if [[ -z "$MODULE_NAME" ]]; then
|
||||
@@ -36,7 +41,7 @@ fi
|
||||
# Normalizar nombre a kebab-case
|
||||
MODULE_NAME=$(echo "$MODULE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
|
||||
PROJECT_DIR="$TARGET_PATH/$MODULE_NAME"
|
||||
GO_MODULE="github.com/lucasdataproyects/$MODULE_NAME"
|
||||
GO_MODULE="${GO_NAMESPACE}/$MODULE_NAME"
|
||||
|
||||
# --- Check estado existente ---
|
||||
if [[ -f "$PROJECT_DIR/go.mod" ]]; then
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -19,7 +19,8 @@ Skill para preparar cualquier repo para exploración de datos con Jupyter + Clau
|
||||
|
||||
```bash
|
||||
# Obtener ruta del script (está junto a este SKILL.md)
|
||||
SKILL_DIR="$HOME/DataProyects/repo_Claude/.claude/skills/init-jupyter"
|
||||
# Resolver via symlink a la ubicación real del skill (portable entre máquinas)
|
||||
SKILL_DIR="$(dirname "$(readlink -f "$HOME/.claude/skills/init-jupyter/SKILL.md")")"
|
||||
|
||||
# Ejecutar con la ruta del proyecto (argumento del skill o directorio actual)
|
||||
bash "$SKILL_DIR/setup-jupyter.sh" "${1:-.}"
|
||||
|
||||
@@ -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
|
||||
@@ -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 ==="
|
||||
@@ -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)
|
||||
@@ -187,4 +187,4 @@ El repo de secretos vive en `dataforge/pass-secrets`. Para operaciones que requi
|
||||
- Siempre verificar precondiciones antes de operar
|
||||
- Si `pass` o `gpg` no están instalados, dar los comandos de instalación para la distro detectada (no ejecutar sudo directamente)
|
||||
- Ofrecer `pass git push` después de cada modificación
|
||||
- El GPG-ID actual es `91324463`
|
||||
- Para obtener el GPG-ID del usuario actual: leerlo de `~/.password-store/.gpg-id` (ese archivo lo crea `pass init <gpg-id>` y contiene el keygrip/ID en uso). Si no existe, listar claves con `gpg --list-secret-keys --keyid-format=long` y pedir al usuario cuál usar
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
@@ -23,6 +23,11 @@ MODEL=$(echo "$INPUT" | jq -r '.model.display_name // "Unknown"')
|
||||
CONTEXT_PCT=$(echo "$INPUT" | jq -r '.context_window.used_percentage // 0' | xargs printf "%.0f")
|
||||
CONTEXT_TOTAL=$(echo "$INPUT" | jq -r '.context_window.context_window_size // 200000')
|
||||
CURRENT_DIR=$(echo "$INPUT" | jq -r '.workspace.current_dir // "~"' | sed "s|$HOME|~|")
|
||||
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""')
|
||||
|
||||
# Purga: borra goal files de sesiones muertas (no tocados en >7 dias). El worker
|
||||
# refresca el mtime en cada respuesta, asi que las sesiones vivas nunca caen.
|
||||
find "$HOME/.claude/goals" -maxdepth 1 -name '*.json' -mtime +7 -delete 2>/dev/null
|
||||
|
||||
# Tokens de entrada y salida (current_usage puede ser null antes del primer API call)
|
||||
INPUT_TOKENS=$(echo "$INPUT" | jq -r '.context_window.current_usage.input_tokens // 0')
|
||||
@@ -40,6 +45,25 @@ if [ "$CONTEXT_PCT" -eq 0 ] && [ "$CONTEXT_USED" -gt 0 ]; then
|
||||
CONTEXT_PCT=$(echo "scale=0; $CONTEXT_USED * 100 / $CONTEXT_TOTAL" | bc)
|
||||
fi
|
||||
|
||||
# Persistir el contexto por sesión en un sidecar para que fleetview (y otras
|
||||
# herramientas) puedan mostrarlo sin tener este stdin. El statusline se re-ejecuta
|
||||
# cada pocos segundos, así que el dato se mantiene fresco mientras la sesión vive.
|
||||
if [ -n "$SESSION_ID" ]; then
|
||||
RTDIR="$HOME/.claude/runtime"
|
||||
mkdir -p "$RTDIR" 2>/dev/null
|
||||
RTF="$RTDIR/${SESSION_ID}.json"
|
||||
RTMP="${RTF}.tmp.$$"
|
||||
if jq -n \
|
||||
--argjson pct "${CONTEXT_PCT:-0}" \
|
||||
--argjson used "${CONTEXT_USED:-0}" \
|
||||
--argjson total "${CONTEXT_TOTAL:-200000}" \
|
||||
'{ctx_pct:$pct, ctx_used:$used, ctx_total:$total}' > "$RTMP" 2>/dev/null; then
|
||||
mv "$RTMP" "$RTF" 2>/dev/null
|
||||
else
|
||||
rm -f "$RTMP" 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
# Costos
|
||||
TOTAL_COST=$(echo "$INPUT" | jq -r '.cost.total_cost_usd // 0' | xargs printf "%.3f")
|
||||
SESSION_DURATION=$(echo "$INPUT" | jq -r '.cost.total_duration_ms // 0')
|
||||
@@ -49,11 +73,27 @@ LINES_REMOVED=$(echo "$INPUT" | jq -r '.cost.total_lines_removed // 0')
|
||||
# Rate Limits
|
||||
RATE_5H=$(echo "$INPUT" | jq -r '.rate_limits.five_hour.used_percentage // 0' | xargs printf "%.0f")
|
||||
RATE_7D=$(echo "$INPUT" | jq -r '.rate_limits.seven_day.used_percentage // 0' | xargs printf "%.0f")
|
||||
RESET_5H_EPOCH=$(echo "$INPUT" | jq -r '.rate_limits.five_hour.resets_at // 0')
|
||||
RESET_7D_EPOCH=$(echo "$INPUT" | jq -r '.rate_limits.seven_day.resets_at // 0')
|
||||
|
||||
# Git info (si estamos en un repo)
|
||||
# Formatear resets (vacio si epoch=0)
|
||||
RESET_5H=""
|
||||
RESET_7D=""
|
||||
[ "$RESET_5H_EPOCH" -gt 0 ] && RESET_5H=$(date -d "@$RESET_5H_EPOCH" +"%H:%M" 2>/dev/null)
|
||||
[ "$RESET_7D_EPOCH" -gt 0 ] && RESET_7D=$(date -d "@$RESET_7D_EPOCH" +"%a %H:%M" 2>/dev/null)
|
||||
|
||||
# Git info (si estamos en un repo). Con cache de TTL corto: como el statusline
|
||||
# se re-ejecuta cada pocos segundos (refreshInterval), recomputar git en cada
|
||||
# tick es caro en repos grandes y el estado git no cambia estando idle. Se cachea
|
||||
# por directorio y se recomputa solo si el cache tiene mas de 6s.
|
||||
GIT_BRANCH=""
|
||||
GIT_STATUS=""
|
||||
if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
GIT_CACHE="/tmp/fn_sl_git_$(printf '%s' "$CURRENT_DIR" | cksum | cut -d' ' -f1).cache"
|
||||
GIT_CACHE_AGE=999
|
||||
[ -f "$GIT_CACHE" ] && GIT_CACHE_AGE=$(( $(date +%s) - $(stat -c %Y "$GIT_CACHE" 2>/dev/null || echo 0) ))
|
||||
if [ "$GIT_CACHE_AGE" -lt 6 ]; then
|
||||
. "$GIT_CACHE"
|
||||
elif git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
GIT_BRANCH=$(git branch --show-current 2>/dev/null || echo "detached")
|
||||
|
||||
# Obtener archivos staged, modified, untracked
|
||||
@@ -81,8 +121,41 @@ if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
|
||||
# Trim trailing space
|
||||
GIT_STATUS=$(echo "$GIT_STATUS" | sed 's/ $//')
|
||||
|
||||
# Guardar en cache (quoting seguro para re-source).
|
||||
printf 'GIT_BRANCH=%q\nGIT_STATUS=%q\n' "$GIT_BRANCH" "$GIT_STATUS" > "$GIT_CACHE" 2>/dev/null
|
||||
fi
|
||||
|
||||
# Color estable por sesión (hash del session_id → paleta ANSI 256 legible).
|
||||
# Cada terminal mantiene su color toda su vida; distinto entre terminales.
|
||||
goal_color() {
|
||||
local sid="$1"
|
||||
local palette=(39 45 51 75 81 114 120 156 183 210 215 222 213 159 228)
|
||||
local h
|
||||
h=$(printf '%s' "$sid" | cksum | cut -d' ' -f1)
|
||||
local idx=$(( h % ${#palette[@]} ))
|
||||
printf '\033[1;38;5;%dm' "${palette[$idx]}"
|
||||
}
|
||||
|
||||
# Fase de trabajo → icono | color ANSI | etiqueta visible.
|
||||
# El slug (clave) lo escribe el agente del Stop hook; aqui se mapea a su estilo.
|
||||
phase_style() {
|
||||
case "$1" in
|
||||
investigando) printf '🔎|36|investigando' ;;
|
||||
planificando) printf '📋|34|planificando' ;;
|
||||
haciendo) printf '🔨|33|haciendo' ;;
|
||||
testeando) printf '🧪|35|testeando' ;;
|
||||
puliendo) printf '✨|95|puliendo detalles' ;;
|
||||
pendiente_revision) printf '👀|93|pendiente de revisión' ;;
|
||||
preguntando) printf '❓|96|esperando respuesta' ;;
|
||||
bloqueado) printf '⛔|31|bloqueado' ;;
|
||||
en_pausa) printf '⏸️|90|en pausa' ;;
|
||||
hecho) printf '✅|32|hecho' ;;
|
||||
iterando) printf '🔁|94|iterando' ;;
|
||||
*) printf "•|90|$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Función para crear barra de progreso
|
||||
progress_bar() {
|
||||
local pct=$1
|
||||
@@ -190,23 +263,49 @@ LINE2="${LINE2} ${GRAY}│${RESET} ${DIM}Total:${RESET} ${CYAN}↓${TOTAL_IN_FMT
|
||||
# 4. Rate Limits (siempre mostrar)
|
||||
LINE2="${LINE2} ${GRAY}│${RESET} ${BOLD}Limits:${RESET}"
|
||||
|
||||
# 5h limit
|
||||
if [ "$RATE_5H" -ge 80 ]; then
|
||||
LINE2="${LINE2} ${RED}5h:${RATE_5H}%${RESET}"
|
||||
elif [ "$RATE_5H" -ge 50 ]; then
|
||||
LINE2="${LINE2} ${YELLOW}5h:${RATE_5H}%${RESET}"
|
||||
else
|
||||
LINE2="${LINE2} ${GREEN}5h:${RATE_5H}%${RESET}"
|
||||
fi
|
||||
# Color por burndown vs tasa esperada
|
||||
# Tasa: % consumible permitido por unidad de tiempo (5h: 20%/h, 7d: 14%/dia)
|
||||
# expected = tasa * unidades_restantes_hasta_reset
|
||||
# available = 100 - used%
|
||||
# verde: available >= expected (consumo bajo control)
|
||||
# amarillo: available >= expected/2 (consumo agresivo)
|
||||
# rojo: available < expected/2 (riesgo de agotar antes del reset)
|
||||
NOW_EPOCH=$(date +%s)
|
||||
burndown_color() {
|
||||
local used_pct=$1
|
||||
local secs_left=$2
|
||||
local rate=$3
|
||||
local secs_per_unit=$4
|
||||
local available=$((100 - used_pct))
|
||||
if [ "$secs_left" -le 0 ]; then
|
||||
printf "%s" "$GREEN"; return
|
||||
fi
|
||||
local expected
|
||||
expected=$(echo "scale=2; $rate * $secs_left / $secs_per_unit" | bc)
|
||||
if (( $(echo "$available >= $expected" | bc -l) )); then
|
||||
printf "%s" "$GREEN"
|
||||
elif (( $(echo "$available >= $expected / 2" | bc -l) )); then
|
||||
printf "%s" "$YELLOW"
|
||||
else
|
||||
printf "%s" "$RED"
|
||||
fi
|
||||
}
|
||||
|
||||
# 7d limit
|
||||
if [ "$RATE_7D" -ge 80 ]; then
|
||||
LINE2="${LINE2} ${RED}7d:${RATE_7D}%${RESET}"
|
||||
elif [ "$RATE_7D" -ge 50 ]; then
|
||||
LINE2="${LINE2} ${YELLOW}7d:${RATE_7D}%${RESET}"
|
||||
else
|
||||
LINE2="${LINE2} ${GREEN}7d:${RATE_7D}%${RESET}"
|
||||
fi
|
||||
# 5h limit (tasa 20%/h)
|
||||
SECS_5H=0
|
||||
[ "$RESET_5H_EPOCH" -gt "$NOW_EPOCH" ] && SECS_5H=$((RESET_5H_EPOCH - NOW_EPOCH))
|
||||
C5=$(burndown_color $RATE_5H $SECS_5H 20 3600)
|
||||
RESET_5H_STR=""
|
||||
[ -n "$RESET_5H" ] && RESET_5H_STR=" ${C5}→${RESET_5H}${RESET}"
|
||||
LINE2="${LINE2} ${C5}5h:${RATE_5H}%${RESET}${RESET_5H_STR} ${GRAY}│${RESET}"
|
||||
|
||||
# 7d limit (tasa 14%/dia)
|
||||
SECS_7D=0
|
||||
[ "$RESET_7D_EPOCH" -gt "$NOW_EPOCH" ] && SECS_7D=$((RESET_7D_EPOCH - NOW_EPOCH))
|
||||
C7=$(burndown_color $RATE_7D $SECS_7D 14 86400)
|
||||
RESET_7D_STR=""
|
||||
[ -n "$RESET_7D" ] && RESET_7D_STR=" ${C7}→${RESET_7D}${RESET}"
|
||||
LINE2="${LINE2} ${C7}7d:${RATE_7D}%${RESET}${RESET_7D_STR}"
|
||||
|
||||
# 5. Duración sesión
|
||||
if [ "$SESSION_DURATION" -gt 0 ]; then
|
||||
@@ -217,6 +316,60 @@ fi
|
||||
# 6. Directorio actual
|
||||
LINE2="${LINE2} ${GRAY}│${RESET} ${BLUE}${CURRENT_DIR}${RESET}"
|
||||
|
||||
# ===== LÍNEA 0: Objetivo (izq) + Fase (der) =====
|
||||
# Solo si la sesión tiene archivo de objetivo con goal no vacío.
|
||||
GOAL_FILE="$HOME/.claude/goals/${SESSION_ID}.json"
|
||||
if [ -n "$SESSION_ID" ] && [ -f "$GOAL_FILE" ]; then
|
||||
GOAL=$(jq -r '.goal // ""' "$GOAL_FILE" 2>/dev/null)
|
||||
PHASE=$(jq -r '.phase // ""' "$GOAL_FILE" 2>/dev/null)
|
||||
DOD=$(jq -r '.dod // ""' "$GOAL_FILE" 2>/dev/null)
|
||||
EMOJIS=$(jq -r '.emojis // ""' "$GOAL_FILE" 2>/dev/null)
|
||||
PROVISIONAL=$(jq -r '.provisional // false' "$GOAL_FILE" 2>/dev/null)
|
||||
if [ -n "$GOAL" ]; then
|
||||
GC=$(goal_color "$SESSION_ID")
|
||||
# Prefijo del objetivo:
|
||||
# - provisional (= tu propio texto, mientras haiku genera el real) -> ⏳ atenuado.
|
||||
# - los 3 emojis generados (representan la tarea, igual que FleetView).
|
||||
# - fallback al marcador generico de objetivo.
|
||||
if [ "$PROVISIONAL" = "true" ]; then
|
||||
LEFT="${GC}⏳ ${DIM}${GOAL}${RESET}"
|
||||
elif [ -n "$EMOJIS" ]; then
|
||||
LEFT="${GC}${EMOJIS} ${GOAL}${RESET}"
|
||||
else
|
||||
LEFT="${GC}🎯 ${GOAL}${RESET}"
|
||||
fi
|
||||
|
||||
LINE0="${LEFT}"
|
||||
|
||||
# Historial: emojis de los ultimos 7 estados PREVIOS (sin el actual, que
|
||||
# se muestra completo a la derecha), atenuados y separados por │.
|
||||
PREV=$(jq -r '(.history // []) | .[0:-1] | .[-7:] | .[]' "$GOAL_FILE" 2>/dev/null)
|
||||
if [ -n "$PREV" ]; then
|
||||
HJOIN=""
|
||||
while IFS= read -r slug; do
|
||||
[ -z "$slug" ] && continue
|
||||
HS=$(phase_style "$slug")
|
||||
HIC="${HS%%|*}"
|
||||
HJOIN="${HJOIN}${HIC}"
|
||||
done <<< "$PREV"
|
||||
[ -n "$HJOIN" ] && LINE0="${LINE0} ${GRAY}│${RESET} ${DIM}${HJOIN}${RESET}"
|
||||
fi
|
||||
|
||||
# Fase actual (completa, con color e icono).
|
||||
if [ -n "$PHASE" ]; then
|
||||
PS=$(phase_style "$PHASE")
|
||||
PICON="${PS%%|*}"
|
||||
REST="${PS#*|}"
|
||||
PCOL="${REST%%|*}"
|
||||
PLABEL="${REST#*|}"
|
||||
LINE0="${LINE0} ${GRAY}│${RESET} \033[1;${PCOL}m${PICON} ${PLABEL}${RESET}"
|
||||
fi
|
||||
echo -e "$LINE0"
|
||||
# DoD en su propia linea debajo del objetivo, atenuado (🏁 = condicion de hecho).
|
||||
[ -n "$DOD" ] && echo -e " ${DIM}🏁 ${DOD}${RESET}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Imprimir resultado (2 líneas)
|
||||
echo -e "$LINE1"
|
||||
echo -e "$LINE2"
|
||||
|
||||
+79
-11
@@ -9,7 +9,7 @@ REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLAUDE_DIR="$HOME/.claude"
|
||||
|
||||
# Carpetas a enlazar (configuración compartible)
|
||||
FOLDERS=("skills" "agents")
|
||||
FOLDERS=("skills" "agents" "commands")
|
||||
|
||||
echo "=== Instalando configuración de Claude ==="
|
||||
echo "Repositorio: $REPO_DIR/.claude"
|
||||
@@ -54,20 +54,26 @@ done
|
||||
echo ""
|
||||
echo "=== Instalando archivos de configuración ==="
|
||||
|
||||
# 1. Status Line Script
|
||||
# 1. Status Line Script (enlace simbólico)
|
||||
STATUSLINE_SOURCE="$REPO_DIR/.claude/statusline.sh"
|
||||
STATUSLINE_TARGET="$CLAUDE_DIR/statusline.sh"
|
||||
|
||||
if [ -f "$STATUSLINE_SOURCE" ]; then
|
||||
if [ -f "$STATUSLINE_TARGET" ]; then
|
||||
BACKUP="$STATUSLINE_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
echo "Backup: statusline.sh -> $BACKUP"
|
||||
mv "$STATUSLINE_TARGET" "$BACKUP"
|
||||
chmod +x "$STATUSLINE_SOURCE"
|
||||
if [ -L "$STATUSLINE_TARGET" ] && [ "$(readlink "$STATUSLINE_TARGET")" = "$STATUSLINE_SOURCE" ]; then
|
||||
echo "OK: statusline.sh ya está enlazado correctamente"
|
||||
else
|
||||
# Symlink (roto o apuntando mal): borrar; archivo real: backup
|
||||
if [ -L "$STATUSLINE_TARGET" ]; then
|
||||
rm -f "$STATUSLINE_TARGET"
|
||||
elif [ -e "$STATUSLINE_TARGET" ]; then
|
||||
BACKUP="$STATUSLINE_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
echo "Backup: statusline.sh -> $BACKUP"
|
||||
mv "$STATUSLINE_TARGET" "$BACKUP"
|
||||
fi
|
||||
ln -s "$STATUSLINE_SOURCE" "$STATUSLINE_TARGET"
|
||||
echo "Enlazado: statusline.sh -> $STATUSLINE_SOURCE"
|
||||
fi
|
||||
|
||||
cp "$STATUSLINE_SOURCE" "$STATUSLINE_TARGET"
|
||||
chmod +x "$STATUSLINE_TARGET"
|
||||
echo "Copiado: statusline.sh (ejecutable)"
|
||||
else
|
||||
echo "WARN: statusline.sh no encontrado en el repo"
|
||||
fi
|
||||
@@ -96,6 +102,66 @@ else
|
||||
echo "WARN: settings.json no encontrado en el repo"
|
||||
fi
|
||||
|
||||
# 3. CLAUDE.md (enlace simbólico - preferencias globales)
|
||||
CLAUDEMD_SOURCE="$REPO_DIR/.claude/CLAUDE.md"
|
||||
CLAUDEMD_TARGET="$CLAUDE_DIR/CLAUDE.md"
|
||||
|
||||
if [ -f "$CLAUDEMD_SOURCE" ]; then
|
||||
if [ -L "$CLAUDEMD_TARGET" ] && [ "$(readlink "$CLAUDEMD_TARGET")" = "$CLAUDEMD_SOURCE" ]; then
|
||||
echo "OK: CLAUDE.md ya está enlazado correctamente"
|
||||
else
|
||||
# Symlink (roto o apuntando mal): borrar; archivo real: backup
|
||||
if [ -L "$CLAUDEMD_TARGET" ]; then
|
||||
rm -f "$CLAUDEMD_TARGET"
|
||||
elif [ -e "$CLAUDEMD_TARGET" ]; then
|
||||
BACKUP="$CLAUDEMD_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
echo "Backup: CLAUDE.md -> $BACKUP"
|
||||
mv "$CLAUDEMD_TARGET" "$BACKUP"
|
||||
fi
|
||||
ln -s "$CLAUDEMD_SOURCE" "$CLAUDEMD_TARGET"
|
||||
echo "Enlazado: CLAUDE.md -> $CLAUDEMD_SOURCE"
|
||||
fi
|
||||
else
|
||||
echo "WARN: CLAUDE.md no encontrado en el repo"
|
||||
fi
|
||||
|
||||
# === Instalando hooks (enlace simbólico por archivo) ===
|
||||
echo ""
|
||||
echo "=== Instalando hooks ==="
|
||||
|
||||
HOOKS_SOURCE_DIR="$REPO_DIR/.claude/hooks"
|
||||
HOOKS_TARGET_DIR="$CLAUDE_DIR/hooks"
|
||||
|
||||
if [ -d "$HOOKS_SOURCE_DIR" ]; then
|
||||
mkdir -p "$HOOKS_TARGET_DIR"
|
||||
for hook in "$HOOKS_SOURCE_DIR"/*.sh; do
|
||||
[ -e "$hook" ] || continue
|
||||
chmod +x "$hook"
|
||||
HOOK_NAME="$(basename "$hook")"
|
||||
HOOK_TARGET="$HOOKS_TARGET_DIR/$HOOK_NAME"
|
||||
|
||||
# Si ya es symlink correcto, saltar
|
||||
if [ -L "$HOOK_TARGET" ] && [ "$(readlink "$HOOK_TARGET")" = "$hook" ]; then
|
||||
echo "OK: hooks/$HOOK_NAME ya está enlazado correctamente"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Symlink (roto o apuntando mal): borrar sin backup; archivo real: backup
|
||||
if [ -L "$HOOK_TARGET" ]; then
|
||||
rm -f "$HOOK_TARGET"
|
||||
elif [ -e "$HOOK_TARGET" ]; then
|
||||
BACKUP="$HOOK_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
echo "Backup: hooks/$HOOK_NAME -> $BACKUP"
|
||||
mv "$HOOK_TARGET" "$BACKUP"
|
||||
fi
|
||||
|
||||
ln -s "$hook" "$HOOK_TARGET"
|
||||
echo "Enlazado: hooks/$HOOK_NAME -> $hook"
|
||||
done
|
||||
else
|
||||
echo "WARN: $HOOKS_SOURCE_DIR no existe, saltando hooks"
|
||||
fi
|
||||
|
||||
# === Limpieza de configuración que no debe cambiar ===
|
||||
echo ""
|
||||
echo "=== Limpiando configuración inmutable ==="
|
||||
@@ -188,7 +254,9 @@ echo "=== Instalación completada ==="
|
||||
echo "Tus comandos y configuración ahora están sincronizados con el repositorio."
|
||||
echo ""
|
||||
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 " • Settings.json enlazado (compartido entre repos)"
|
||||
echo " • Backups viejos limpiados (>7 días)"
|
||||
|
||||
Reference in New Issue
Block a user