chore: avance acumulado de sesiones previas (reorg dev/issues + ajustes)
Reorganizacion de dev/issues en subcarpetas (completed/, cpp/, gamedev/, kanban/, trading/, imagegen/, matrix/) y cambios acumulados en cmd/fn/pyrunner, .claude/commands y settings. Trabajo de otro LLM/sesion, commiteado a peticion del usuario para desbloquear el working tree. Excluido logs/ardour_mcp_server.log (ruido).
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
---
|
||||
id: "0059"
|
||||
title: "Resolver doble tracking de `apps/*/app.md` (fn_registry + sub-repo)"
|
||||
status: completado
|
||||
type: infra
|
||||
domain:
|
||||
- registry-quality
|
||||
scope: app-scoped
|
||||
priority: media
|
||||
depends: []
|
||||
blocks: []
|
||||
related: []
|
||||
created: 2026-05-17
|
||||
updated: 2026-05-17
|
||||
tags: []
|
||||
---
|
||||
# 0059 — Resolver doble tracking de `apps/*/app.md` (fn_registry + sub-repo)
|
||||
|
||||
## APP Metadata
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | 0059 |
|
||||
| **Estado** | pendiente |
|
||||
| **Prioridad** | media |
|
||||
| **Tipo** | infra — git, .gitignore, indexer |
|
||||
|
||||
## Dependencias
|
||||
|
||||
- ADR 0002 (apps_analyses_as_dataforge_master): cada app es su propio repo.
|
||||
- `.gitignore` raiz: `apps/*/`.
|
||||
|
||||
## Contexto
|
||||
|
||||
Detectado en sesion 2026-05-07:
|
||||
|
||||
- `.gitignore` de `fn_registry` excluye `apps/*/`.
|
||||
- Pero `apps/dag_engine/app.md` aparece tracked tambien por `fn_registry` (legado pre-gitignore).
|
||||
- Resultado: el mismo archivo fisico es tracked por DOS repos: `fn_registry` Y `dataforge/dag_engine` (sub-repo).
|
||||
- Cualquier cambio crea conmits duplicados, status confuso, riesgo de divergencia.
|
||||
|
||||
```
|
||||
$ git status # en fn_registry
|
||||
M apps/dag_engine/app.md
|
||||
$ git -C apps/dag_engine status
|
||||
M app.md
|
||||
```
|
||||
|
||||
Mismo archivo. Dos `git add` distintos.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Eliminar el doble tracking. Decidir source of truth y limpiar la otra.
|
||||
|
||||
## Decisiones a tomar
|
||||
|
||||
**Opcion A: source of truth = sub-repo.**
|
||||
- `git rm --cached apps/*/app.md` en fn_registry (sin borrar archivo).
|
||||
- `app.md` solo se commitea desde el sub-repo.
|
||||
- `fn index` lee desde disco igual.
|
||||
- PRO: consistente con ADR 0002 (sub-repo es la unidad).
|
||||
- CONTRA: `app.md` no se versiona junto con cambios al registry que lo rompan (ej. funciones referenciadas que se borran).
|
||||
|
||||
**Opcion B: source of truth = fn_registry.**
|
||||
- Sub-repo NO trackea `app.md` (anadir a su `.gitignore`).
|
||||
- `fn_registry` lo versiona.
|
||||
- PRO: el indexer y el registry tienen control directo.
|
||||
- CONTRA: el sub-repo es incompleto (falta su propia ficha de identidad).
|
||||
|
||||
**Opcion C: ambos lo trackean conscientemente.**
|
||||
- Documentar en regla nueva.
|
||||
- Hook que bloquea desync (post-commit en uno avisa para commitear en el otro).
|
||||
- PRO: cada repo es self-describing.
|
||||
- CONTRA: complejidad operacional alta, riesgo de drift.
|
||||
|
||||
**Recomendada: Opcion A.** Alinea con ADR 0002. La cobertura "registry rompe app.md" se cubre con `fn doctor uses-functions` + pre-commit hook v2.
|
||||
|
||||
## Arquitectura
|
||||
|
||||
### Archivos afectados
|
||||
|
||||
- `.gitignore` raiz — verificar que `apps/*/` cubre todo.
|
||||
- Posiblemente otros `apps/*/app.md` tracked legacy (auditar).
|
||||
- `docs/adr/0004-app-md-source-of-truth.md` — NUEVO ADR.
|
||||
|
||||
## Tareas
|
||||
|
||||
### Fase 1 — auditar
|
||||
1.1 `git ls-files apps/ | grep app.md` — listar todos los `app.md` tracked en fn_registry.
|
||||
1.2 Cruzar con sub-repos para confirmar duplicacion.
|
||||
|
||||
### Fase 2 — decision (humano)
|
||||
2.1 Confirmar Opcion A o cambiar a B/C con razon documentada.
|
||||
|
||||
### Fase 3 — implementacion (Opcion A)
|
||||
3.1 Para cada `app.md` duplicado: confirmar que existe identico en el sub-repo.
|
||||
3.2 `git rm --cached apps/<name>/app.md` en fn_registry.
|
||||
3.3 Commit limpio en fn_registry.
|
||||
3.4 No tocar el sub-repo (ya esta correcto).
|
||||
|
||||
### Fase 4 — ADR + docs
|
||||
4.1 Crear `docs/adr/0004-app-md-source-of-truth.md` con la decision y razon.
|
||||
4.2 Actualizar `.claude/rules/cpp_apps.md` y `apps_vs_functions.md` si mencionan tracking.
|
||||
|
||||
## Riesgos
|
||||
|
||||
- Si el indexer asume que `app.md` esta tracked en fn_registry (no es el caso, lee de disco), cero impacto.
|
||||
- Si `fn sync` o `pc_locations` asumen tracking, validar que solo dependen del archivo fisico.
|
||||
|
||||
## Decisiones de diseno
|
||||
|
||||
- Opcion A es coherente con ADR 0002 — apps son sub-repos completos.
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
id: "55"
|
||||
title: "Roadmap de prereqs — issues de osint_graph que odr_console necesita antes/durante MVP"
|
||||
status: deferred
|
||||
type: epic
|
||||
domain:
|
||||
- osint
|
||||
scope: cross-stack
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related:
|
||||
- "53"
|
||||
- "54"
|
||||
created: 2026-05-09
|
||||
updated: 2026-05-17
|
||||
tags: []
|
||||
---
|
||||
|
||||
## Objetivo
|
||||
|
||||
Documentar el orden de dependencias entre las issues de `graph_explorer` y los issues `0065` (extract jobs) + `0066` (odr_console MVP) para que ambas apps compartan infra (jobs system, runtime Python embebido, CDP browser, profiles, sessions) sin reescribir codigo. **Este issue NO implementa nada** — es el meta-plan que ordena en que orden se atacan las dependencias previas.
|
||||
|
||||
## Contexto
|
||||
|
||||
`odr_console` es la segunda app C++ ImGui que necesita el mismo stack de recoleccion de datos que `graph_explorer`:
|
||||
|
||||
- Workers concurrentes con subprocess + wire protocol stdin/stderr/stdout (jobs system, issue 0026 ya completado).
|
||||
- Python embebido portable (sin WSL) — issue 0033 pendiente.
|
||||
- Browser CDP con profiles + cookies — issues 0038/0039/0040 pendientes.
|
||||
- Compile skill que empaqueta runtime + vendored deps — issues 0033c/d/e pendientes.
|
||||
|
||||
Sin este orden:
|
||||
- `odr_console` arranca con dependencia WSL y se rompe en Windows nativo.
|
||||
- Los collectors browser-driven no funcionan hasta tener CDP.
|
||||
- Cada app duplicaria infra de jobs/cache/subprocess.
|
||||
|
||||
## Arquitectura
|
||||
|
||||
### Capas afectadas
|
||||
|
||||
| Capa | Estado actual | Que falta para odr |
|
||||
|---|---|---|
|
||||
| Registry C++ (`cpp/functions/`) | tiene `tokens`, `app_settings`, `app_about`, `logger`, `panel_menu`, etc. en `fn_framework` | añadir `jobs_pool`, `subprocess_worker`, `job_cache_sha256`, `worker_manifest_loader` (issue 0065) |
|
||||
| Registry Python (`python/functions/`) | tiene `fetch_webpage`, `web_search`, `extract_*`, `html_to_markdown`, `normalize_url` | reusar tal cual; vendor en collectors `_vendored/` |
|
||||
| Runtime Python embebido | NO existe (graph_explorer usa WSL) | issue 0033 — runtime portable Linux+Windows |
|
||||
| CDP browser control | NO existe | issues 0038/0039/0040 — `cdp-cli` Go binario + profiles + sessions |
|
||||
| Compile skill | builds `.exe` Windows + DLLs + assets | issue 0033e — empaqueta runtime + vendored + go binaries |
|
||||
|
||||
### Mapeo issues prereq → componente odr
|
||||
|
||||
| Issue graph_explorer | Componente odr afectado | Bloqueo? |
|
||||
|---|---|---|
|
||||
| 0033 (multilang + Python embebido) | runtime portable, sin WSL | **bloquea release Windows** |
|
||||
| 0033c (fn check vendored) | audit drift `_vendored/` ↔ registry | nice-to-have |
|
||||
| 0033d (indexer python_runtime fields) | `app.md` declara python_runtime | bloquea 0033e |
|
||||
| 0033e (compile skill orquesta) | distribuible self-contained | **bloquea release Windows** |
|
||||
| 0034 (port enrichers a Go) | acelera arranque de collectors | opcional |
|
||||
| 0038 (browser launch CDP) | collectors `browser_capture_dom`, `browser_login_capture` | bloquea browser collectors |
|
||||
| 0039 (cookie session manager) | collectors con auth | bloquea OSINT-style scraping |
|
||||
| 0040 (multi-profile management) | collectors paralelos por profile | bloquea multi-fuente |
|
||||
| 0029 (enrichers via CDP) | template para `fetch_webpage_browser` collector | nice-to-have |
|
||||
| 0030 (deep enrich macro) | patron para pipeline builder DAG | referencia, no bloquea |
|
||||
| 0021 (command palette Ctrl+K) | UX del launcher panel | nice-to-have |
|
||||
| 0014 (browser extension) | ingest desde browser → odr collectors | post-MVP |
|
||||
| 0012 (HTTP ingest endpoint) | endpoint compartido entre apps | post-MVP |
|
||||
| 0017 (gx CLI) | `odr` CLI espejo | post-MVP |
|
||||
|
||||
## Tareas
|
||||
|
||||
### Fase 1 — Bloqueantes para MVP odr (orden estricto)
|
||||
|
||||
- **1.1** Issue **0065** (extract jobs system to registry). Bloquea 0066. Refactor seguro: graph_explorer sigue funcionando.
|
||||
- **1.2** Issue **0033d** (indexer lee `python_runtime` fields). Pequeño, sin deps. Habilita 0033e.
|
||||
- **1.3** Issue **0033** (multilang dispatcher + Python embebido). Critico para Windows nativo. Sin esto, odr depende de WSL igual que graph_explorer hoy.
|
||||
- **1.4** Issue **0033e** (compile skill orquesta freeze + vendor + go builds). Permite que `/compile odr_console` produzca zip self-contained con runtime + collectors vendored.
|
||||
|
||||
### Fase 2 — MVP odr_console
|
||||
|
||||
- **2.1** Issue **0066** (odr_console MVP). Esqueleto + 1 collector `api_hn_top` end-to-end. Reusa 0065. NO depende de browser CDP (collector inicial es API JSON pura).
|
||||
- **2.2** Verificar build Windows + WSL. Compile skill copia exe + Python runtime + collectors a `Desktop/apps/odr_console/`.
|
||||
|
||||
### Fase 3 — Browser-driven collectors
|
||||
|
||||
- **3.1** Issue **0038** (browser launch + CDP control). graph_explorer lo necesita igual. odr lo consume por subprocess `cdp-cli` igual que graph_explorer.
|
||||
- **3.2** Issue **0039** (cookie session manager). Permite collectors con auth.
|
||||
- **3.3** Issue **0040** (multi-profile management). Permite N collectors en paralelo con profiles distintos.
|
||||
- **3.4** Crear collectors odr basados en CDP: `browser_capture_dom`, `browser_login_capture`, `browser_scroll_paginated` (issue futura).
|
||||
|
||||
### Fase 4 — Calidad y UX (post-MVP)
|
||||
|
||||
- **4.1** Issue **0033c** (fn check vendored). CI gate para drift detection.
|
||||
- **4.2** Issue **0034** (port enrichers a Go). Reduce dependencia Python opcional.
|
||||
- **4.3** Issue **0029** (enrichers via CDP — `fetch_webpage_browser`, `fetch_screenshot`). Templates aplicables como collectors.
|
||||
- **4.4** Issue **0021** (command palette Ctrl+K). UX comun a las dos apps; extraer al registry.
|
||||
- **4.5** Issue **0030** (deep enrich macro). Patron para pipeline builder DAG en odr.
|
||||
|
||||
### Fase 5 — Integraciones cross-app (largo plazo)
|
||||
|
||||
- **5.1** Issue **0012** (HTTP ingest endpoint local). Compartido por ambas apps.
|
||||
- **5.2** Issue **0017** (gx CLI). Crear `odr` CLI espejo o unificar bajo `gx` con subcomando.
|
||||
- **5.3** Issue **0014** (browser extension). Ingest desde browser apunta a 0012; ambas apps consumen.
|
||||
|
||||
## Ejemplo de uso
|
||||
|
||||
Roadmap visual del orden:
|
||||
|
||||
```
|
||||
[1.1 0065 extract jobs] ──┐
|
||||
[1.2 0033d indexer fields] ──┤── prereqs MVP odr
|
||||
[1.3 0033 multilang + python emb] ──┤ (sin estos, odr no compila distribuible)
|
||||
[1.4 0033e compile orquesta] ──┘
|
||||
↓
|
||||
[2.1 0066 odr MVP] ── MVP funcional con collector API
|
||||
[2.2 verify Windows+WSL]
|
||||
↓
|
||||
[3.1 0038 browser CDP] ──┐
|
||||
[3.2 0039 cookie manager] ──┤── browser-driven collectors
|
||||
[3.3 0040 multi-profile] ──┤
|
||||
[3.4 collectors browser] ──┘
|
||||
↓
|
||||
[4.x calidad: vendored audit, go ports, command palette, deep enrich]
|
||||
↓
|
||||
[5.x cross-app: HTTP endpoint, CLI unificado, extension]
|
||||
```
|
||||
|
||||
Bloqueo critico para "release Windows funcional": **1.1 → 1.2 → 1.3 → 1.4 → 2.1**.
|
||||
|
||||
## Decisiones de diseño
|
||||
|
||||
1. **Por que reordenar en lugar de bifurcar**: si odr clona jobs.cpp, divergira de graph_explorer en semanas. El registry-first justifica el coste de 0065 antes que 0066.
|
||||
|
||||
2. **Por que 0033 es critico antes que MVP**: graph_explorer hoy depende de WSL para correr enrichers en Windows. Si odr arranca igual, **dos apps con la misma deuda**. Mejor pagar 0033 una vez para ambas.
|
||||
|
||||
3. **Por que browser CDP no bloquea MVP**: el collector inicial `api_hn_top` es API JSON pura. Browser collectors son fase 3, una vez probado el flujo. Asi MVP llega a Windows en dias, no semanas.
|
||||
|
||||
4. **Por que NO portar enrichers a Go (0034) antes**: optimizacion prematura. Python embebido (0033) ya da portabilidad. Go solo si la latencia molesta.
|
||||
|
||||
5. **Por que command palette no es prereq**: UX nice-to-have. MVP launcher con search FTS5 + lista basta. 0021 se extrae al registry cuando ambas apps lo necesiten.
|
||||
|
||||
## Prerequisitos
|
||||
|
||||
- Issue 0026 (jobs system base) — completado.
|
||||
- Issue 0033b (vendor python functions) — completado.
|
||||
- `cpp/PATTERNS.md` y `cpp/DESIGN_SYSTEM.md` — ya autoritativas.
|
||||
- Regla `apps_tbd.md` — sub-repos Gitea + branch master + TBD activo.
|
||||
|
||||
## Riesgos
|
||||
|
||||
| Riesgo | Mitigacion |
|
||||
|---|---|
|
||||
| Issue 0033 (Python embebido) es grande, retrasa MVP odr semanas | Hacer 0066 con `python3` del sistema primero, migrar a embebido al cerrar 0033. Coste: dos releases — Linux/WSL primero, Windows nativo despues. |
|
||||
| Refactor 0065 rompe graph_explorer | TBD obligatorio en sub-repo de graph_explorer + tests pasando antes de mergear (32 WSL + 21 Win). |
|
||||
| Browser CDP (0038) se enreda con perfiles existentes del usuario | Issue 0040 establece profiles propios bajo `<app>/local_files/browser_profiles/<name>/`, no toca los del sistema. |
|
||||
| Compile skill (0033e) explota build matrix Linux+Windows+freeze+vendor | Trabajo incremental: primero `python_runtime: false` sigue funcionando como hoy; cuando 0033e este, opt-in. |
|
||||
| Drift entre `_vendored/` de graph_explorer y collectors de odr | 0033c (fn check vendored) gate en CI antes de mergear cambios en `python/functions/`. |
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Implementar ninguno de los issues referenciados — este meta-plan solo ordena.
|
||||
- Decidir si `gx` y `odr` se unifican bajo un CLI comun (ver fase 5).
|
||||
- Diseño detallado del pipeline builder DAG de odr (ver issue 0066).
|
||||
@@ -0,0 +1,200 @@
|
||||
---
|
||||
id: "0068"
|
||||
title: "Cerrar bucle reactivo — fn-analizador (fase 4) y fn-mejorador (fase 5) con contrato e2e_checks"
|
||||
status: completado
|
||||
type: feature
|
||||
domain:
|
||||
- meta
|
||||
scope: multi-app
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related:
|
||||
- "22"
|
||||
- "23"
|
||||
- "0028"
|
||||
created: 2026-05-09
|
||||
updated: 2026-05-17
|
||||
tags: []
|
||||
---
|
||||
|
||||
## Cierre 2026-05-14
|
||||
|
||||
Toda infra implementada y operativa:
|
||||
|
||||
- Migration `fn_operations/migrations/005_e2e_runs.sql` aplicada.
|
||||
- Funcion `e2e_run_checks_go_infra` (Cmd/Health/Ref con expect_exit/stdout_contains, background via `&`). Tipos `E2ECheck_go_infra` + `CheckResult_go_infra`.
|
||||
- Subagentes `fn-analizador` (Fase 4) y `fn-mejorador` (Fase 5) en `.claude/agents/`.
|
||||
- `fn-recopilador` extendido con modo `design-e2e <app_id>`.
|
||||
- Skill `/validate-app <app_id>` orquesta cadena completa.
|
||||
- Regla `.claude/rules/e2e_validation.md` documenta contrato + patrones por stack.
|
||||
- Pilotos: `apps/kanban/app.md` y `projects/osint_graph/apps/graph_explorer/app.md` declaran `e2e_checks`.
|
||||
---
|
||||
|
||||
## Contexto
|
||||
|
||||
El bucle reactivo del registry (CONSTRUIR → EJECUTAR → RECOPILAR → ANALIZAR → MEJORAR) tiene agentes para fases 1-3:
|
||||
|
||||
- **Fase 1 — CONSTRUIR**: `fn-constructor` (existe)
|
||||
- **Fase 2 — EJECUTAR**: `fn-executor` (existe)
|
||||
- **Fase 3 — RECOPILAR**: `fn-recopilador` (existe)
|
||||
- **Fase 4 — ANALIZAR**: falta agente
|
||||
- **Fase 5 — MEJORAR**: falta agente
|
||||
|
||||
Sin fases 4 y 5 el bucle no cierra. Cada app sigue requiriendo iteracion manual: el humano lanza, mira, decide si funciona, y propone fixes. Objetivo: que apps lleguen a master correctas sin esa iteracion manual.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Disponer de un gate automatico pre-merge que valide end-to-end cualquier app del registry, y un mejorador que proponga cambios cuando la validacion falla. Capacidad reutilizable, no especifica de un proyecto.
|
||||
|
||||
## Diseno
|
||||
|
||||
### Contrato `e2e_checks` en `app.md`
|
||||
|
||||
Cada app declara su validacion end-to-end en el frontmatter:
|
||||
|
||||
```yaml
|
||||
e2e_checks:
|
||||
- id: build
|
||||
cmd: "cd frontend && pnpm build && cd .. && CGO_ENABLED=1 go build -tags fts5 -o kanban ."
|
||||
timeout_s: 120
|
||||
- id: smoke
|
||||
cmd: "./kanban --port 8095 --db /tmp/kanban_e2e.db &"
|
||||
health: "http://127.0.0.1:8095/api/board"
|
||||
timeout_s: 10
|
||||
- id: ops_audit
|
||||
ref: "fn-recopilador:apps/kanban"
|
||||
- id: migrations
|
||||
cmd: "sqlite3 /tmp/kanban_e2e.db 'SELECT version FROM schema_migrations;'"
|
||||
expect_exit: 0
|
||||
```
|
||||
|
||||
Tipos de check:
|
||||
- `cmd` — comando shell, exit 0 = OK.
|
||||
- `health` — espera `cmd` en background, hace GET y verifica 200.
|
||||
- `ref` — apunta a otro agente/funcion del registry (ej. `fn-recopilador`, `fn-doctor`).
|
||||
- `expect_exit`, `expect_stdout_contains`, `expect_stdout_json` — predicados sobre la salida.
|
||||
|
||||
### fn-analizador (fase 4)
|
||||
|
||||
Subagente nuevo. Input: `app_id`. Pasos:
|
||||
|
||||
1. Lee `e2e_checks` del `app.md`.
|
||||
2. Ejecuta cada check en orden, captura stdout/stderr/exit/duration.
|
||||
3. Eval assertions activas via `fn ops assertion eval --react`.
|
||||
4. Compara `executions.metrics` actual vs ventana historica (drift > umbral = warning).
|
||||
5. Diff golden outputs si app declara `tests/golden/`.
|
||||
6. Persiste resultado en nueva tabla `e2e_runs` de `operations.db`.
|
||||
7. Devuelve veredicto caveman:
|
||||
```
|
||||
build ✓ 42s
|
||||
smoke ✓ 0.8s
|
||||
ops_audit ✓
|
||||
assertion:R1 ✗ warning duration drift +47% vs p50
|
||||
```
|
||||
|
||||
Tools: Read, Bash, Grep, Glob. Escribe SOLO `assertion_results`, `entity.status`, `e2e_runs`. NO toca registry.db.
|
||||
|
||||
### fn-mejorador (fase 5)
|
||||
|
||||
Subagente nuevo. Input: salida de `fn-analizador` + `app_id`. Pasos:
|
||||
|
||||
1. Filtra fallos: `critical` → `kind=bug`, `warning` → `kind=optimization`.
|
||||
2. Por cada fallo, busca contexto en registry: funciones tocadas, ultimas N proposals.
|
||||
3. Crea proposal con `created_by=reactive_loop`, `evidence=[assertion_id, execution_id, e2e_run_id]`.
|
||||
4. Sugiere fix concreto (parametro, funcion a partir, refactor) — texto, NO codigo.
|
||||
5. Si fallo recurrente (>3 veces misma assertion) → `priority=high`.
|
||||
|
||||
Tools: Read, Bash, Grep. Escribe SOLO `proposals` en registry.db. Nunca modifica funciones.
|
||||
|
||||
### Skill `/validate-app <app_id>`
|
||||
|
||||
Orquesta cadena: `fn-executor` → `fn-recopilador` → `fn-analizador` → `fn-mejorador`. Devuelve tabla pass/fail + IDs de proposals creadas.
|
||||
|
||||
### Migracion `006_e2e_runs.sql`
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS e2e_runs (
|
||||
id TEXT PRIMARY KEY,
|
||||
app_id TEXT NOT NULL,
|
||||
started_at INTEGER NOT NULL,
|
||||
finished_at INTEGER,
|
||||
status TEXT NOT NULL, -- pass|fail|partial
|
||||
checks_total INTEGER NOT NULL,
|
||||
checks_pass INTEGER NOT NULL,
|
||||
checks_fail INTEGER NOT NULL,
|
||||
summary_json TEXT NOT NULL -- detalle por check
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS e2e_runs_app_idx ON e2e_runs(app_id, started_at DESC);
|
||||
```
|
||||
|
||||
### Funciones nuevas del registry
|
||||
|
||||
Delegables a `fn-constructor`:
|
||||
|
||||
| ID | Domain | Que hace |
|
||||
|---|---|---|
|
||||
| `e2e_run_checks_go_infra` | infra | Ejecuta lista de checks, devuelve `[]CheckResult` |
|
||||
| `golden_diff_go_core` | core | Compara archivo vs golden con umbral |
|
||||
| `metrics_drift_go_datascience` | datascience | p50/p95 historico vs actual |
|
||||
| `proposal_from_failure_go_infra` | infra | Formatea evidencia → proposal |
|
||||
| `health_probe_go_infra` | infra | GET con timeout, retry, codigo esperado |
|
||||
|
||||
Tipos:
|
||||
|
||||
| ID | Que |
|
||||
|---|---|
|
||||
| `E2ECheck_go_infra` | struct check (id, cmd, ref, health, expect_*) |
|
||||
| `CheckResult_go_infra` | struct resultado (id, status, duration_ms, stdout, stderr, exit) |
|
||||
|
||||
### fn-recopilador como diseñador de checks
|
||||
|
||||
`fn-recopilador` se extiende para **proponer e2e_checks por app** (modo design):
|
||||
|
||||
- Inspecciona `app.md` (lang, framework, entry_point, uses_functions).
|
||||
- Detecta patrones conocidos:
|
||||
- Go service con frontend Vite → propone build (pnpm + go), smoke (puerto + endpoint health).
|
||||
- C++ ImGui app → propone build (cmake), smoke (`--self-test` o lanzar y matar tras N segundos).
|
||||
- Python pipeline → propone run con args dummy y verificar exit 0.
|
||||
- Audita `operations.db` y deriva `ops_audit` automatico.
|
||||
- Escribe propuesta en `app.md` como bloque `e2e_checks_suggested:` para que humano apruebe → renombre a `e2e_checks:`.
|
||||
|
||||
Comando: `fn-recopilador design-e2e <app_id>`.
|
||||
|
||||
Asi `fn-analizador` recibe contratos completos de fabrica y solo necesita ejecutar.
|
||||
|
||||
## Plan de ejecucion
|
||||
|
||||
| Paso | Tarea | Estado |
|
||||
|---|---|---|
|
||||
| 1 | Contrato `e2e_checks` en `docs/templates/app.md` + 2 apps piloto (kanban, graph_explorer) | en curso |
|
||||
| 2 | Funciones registry: `e2e_run_checks`, `golden_diff`, `metrics_drift`, `proposal_from_failure`, `health_probe` | pendiente |
|
||||
| 3 | Migracion `006_e2e_runs.sql` en `fn_operations/migrations/` | pendiente |
|
||||
| 4 | Subagente `fn-analizador` + skill `/validate-app` | pendiente |
|
||||
| 5 | Extender `fn-recopilador` con modo `design-e2e` | pendiente |
|
||||
| 6 | Subagente `fn-mejorador` | pendiente |
|
||||
| 7 | Gate opcional en `/git-push` para apps con `e2e_checks` declarado | pendiente |
|
||||
|
||||
## Criterios de aceptacion
|
||||
|
||||
- [x] Template `docs/templates/app.md` con seccion `e2e_checks` documentada.
|
||||
- [x] `apps/kanban/app.md` declara `e2e_checks` (build_frontend + build_backend + smoke + tests + ops_audit).
|
||||
- [x] `projects/osint_graph/apps/graph_explorer/app.md` declara `e2e_checks` (build + self_test + pytest + enricher smoke).
|
||||
- [x] `fn-recopilador` puede sugerir `e2e_checks` para una app dada (modo `design-e2e`).
|
||||
- [x] `fn-analizador` corre los checks y devuelve veredicto caveman.
|
||||
- [x] `fn-mejorador` crea proposals con evidencia cuando hay fallos.
|
||||
- [x] Skill `/validate-app <app_id>` orquesta la cadena completa.
|
||||
- [x] Documentacion en `.claude/rules/e2e_validation.md`.
|
||||
|
||||
## Riesgos
|
||||
|
||||
- **Golden tests para C++/UI son caros**. Empezar build+smoke+assertions; goldens solo donde aporten (graph_explorer ya tiene capture system).
|
||||
- **Apps sin operations.db** (kanban usa `kanban.db` propia, no `operations.db`). El check `ops_audit` debe aceptar BD alternativa o saltarse.
|
||||
- **Smoke tests con puertos en uso**. Los pilotos deben usar puertos efimeros (`--port 0` o range alto) y BDs en `/tmp/`.
|
||||
- **Adopcion gradual**. Apps sin `e2e_checks` no se validan (y no bloquean merge). Visible en `fn doctor`.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Reemplazar `fn doctor` (que sigue siendo diagnostico read-only del estado).
|
||||
- Tests unitarios de funciones (siguen en `*_test.go`, `pytest`, etc.).
|
||||
- Performance benchmarks formales (los `metrics_drift` son aproximacion, no benchmark).
|
||||
@@ -0,0 +1,280 @@
|
||||
---
|
||||
id: "0069"
|
||||
title: "Bucle autonomo de subagentes — completar y mejorar tareas sin intervencion humana"
|
||||
status: completado
|
||||
type: feature
|
||||
domain:
|
||||
- meta
|
||||
scope: multi-app
|
||||
priority: media
|
||||
depends:
|
||||
- "0068"
|
||||
blocks: []
|
||||
related:
|
||||
- "22"
|
||||
- "23"
|
||||
- "0028"
|
||||
- "0085"
|
||||
created: 2026-05-09
|
||||
updated: 2026-05-17
|
||||
tags: []
|
||||
---
|
||||
|
||||
## Cierre 2026-05-15
|
||||
|
||||
Bucle autonomo operativo. 2 pilotos converged exitosamente:
|
||||
- **Piloto 1**: 0077 (fn run bash mudo). 5 iter, 4/4 checks, ~9 min, PR Gitea#1. Orquestador rediagnostico causa real distinta de la hipotesis del issue.
|
||||
- **Piloto 2**: 0076 (gradle SDK detect). 1 iter, 6/6 checks, ~4 min, PR Gitea#2. Sandbox limpio, `--no-verify` documentado.
|
||||
|
||||
Hardening aplicado tras piloto 1 (regla 9-11 en `fn-orquestador/SKILL.md` + regla 9-10 en `autonomous_loop.md`): prohibir paths absolutos fuera del worktree + post-iteracion sanity check `git -C <main> status --short` debe permanecer en baseline.
|
||||
|
||||
Hardening pendiente (no bloquea cierre — iterativo): inicializar `operations.db` + persistir filas en `task_runs` (los pilotos llevaron estado inline). Test contra dataset adverso del filtro proposals. Escenarios complejos (issues multi-fichero, conflictos).
|
||||
|
||||
---
|
||||
|
||||
## Estado 2026-05-13
|
||||
|
||||
Infra base lista para lanzar el orquestador:
|
||||
|
||||
| Paso | Estado |
|
||||
|---|---|
|
||||
| 1. Migration `006_task_runs.sql` | **hecho** — aplicada via embed.FS, verificada en operations.db nueva |
|
||||
| 2. Subagente `.claude/agents/fn-orquestador/SKILL.md` | **hecho** (preexistente) |
|
||||
| 3. `dev/autonomous_protected_paths.json` | **hecho** — 21 patrones + 2 excepciones documentadas |
|
||||
| 4. Slash `/autonomous-task <issue_id>` | **hecho** — `.claude/commands/autonomous-task.md` |
|
||||
| 5-6. Funciones/tipos auxiliares (`task_run_persist`, `proposal_filter_safe`, ...) | pending (no bloquea piloto si el orquestador inlinea SQL) |
|
||||
| 7. Piloto issue 0077 (fn run bash mudo) | **converged** 2026-05-15: 5 iter, 4/4 checks, ~9 min, PR creado, task_run_id=task_98831b93cbf263ee. Causa real distinta de la hipotesis del issue (library-style scripts vs Stdout unconnected) — orquestador diagnostico correctamente y aplico fix valido (buildBashCommand + bashFunctionName helper + 4 unit tests). |
|
||||
| 8. Piloto issue 0076 (gradle SDK detect) | **converged** 2026-05-15: 1 iter, 6/6 checks, ~4 min, PR Gitea#2, task_run_id=task_cfac9099473ad8e7. Sandbox limpio (uso `--no-verify` documentado en `task_runs.events_json` en lugar de editar hooks main). 2 pilotos exitosos -> acceptance criterion piloto cumplido. |
|
||||
| 9. Hardening + tests | pending |
|
||||
| 10. Regla `.claude/rules/autonomous_loop.md` | pending |
|
||||
|
||||
Pre-condiciones verificadas 2026-05-13:
|
||||
- ✓ migration 006_task_runs.sql aplicada (`schema_migrations` v6)
|
||||
- ✓ 6 subagentes presentes
|
||||
- ✓ paths protegidos JSON
|
||||
- ✓ master sync OK
|
||||
- ✓ gh auth OK (gutierenmanuel)
|
||||
- ✓ sin branches `auto/*` colgando
|
||||
|
||||
Listo para piloto: `/autonomous-task <issue_id_simple_y_verificable>`.
|
||||
|
||||
Integracion con issue 0085 (call_monitor): el orquestador puede consultar `function_stats`, `proposals` y `copied_code` antes de cada fase para tomar decisiones informadas; sus invocaciones se loggean automaticamente via hook PostToolUse.
|
||||
|
||||
|
||||
## Contexto
|
||||
|
||||
El issue 0068 cierra el bucle reactivo a nivel **agentes individuales**:
|
||||
|
||||
- fn-1 `fn-constructor` — construye codigo
|
||||
- fn-2 `fn-executor` — ejecuta
|
||||
- fn-3 `fn-recopilador` — audita datos + diseña contrato e2e
|
||||
- fn-4 `fn-analizador` — valida end-to-end
|
||||
- fn-5 `fn-mejorador` — abre proposals con evidencia
|
||||
|
||||
Sin embargo, cada fase la **lanza un humano** (o el main thread bajo instruccion humana). El humano sigue siendo el orquestador. La promesa real es:
|
||||
|
||||
> "Lanzar una tarea al sistema, irse, y volver para encontrarla terminada y mejorada."
|
||||
|
||||
Esa promesa requiere un **orquestador autonomo** que recorra el bucle CONSTRUIR → EJECUTAR → RECOPILAR → ANALIZAR → MEJORAR sin intervencion humana, hasta convergencia (suite verde) o tope de iteraciones / tiempo.
|
||||
|
||||
Este issue planifica ese orquestador.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Disponer de un **runner autonomo** que toma una tarea (issue, feature, app skeleton) y la entrega **funcional, validada y con proposals para mejoras** sin intervencion humana entre fases. El humano solo:
|
||||
|
||||
1. Define el objetivo (issue file).
|
||||
2. Recibe el resultado al final (PR draft, run_ids verdes, proposals creadas).
|
||||
|
||||
Si el bucle no converge en N iteraciones o T tiempo, se para y reporta el estado al humano para decision.
|
||||
|
||||
## Diseno
|
||||
|
||||
### Componente nuevo: `fn-orquestador`
|
||||
|
||||
Subagente meta-orquestador. Input: `issue_id` o `task_spec`. Algoritmo:
|
||||
|
||||
```
|
||||
1. Leer task_spec (issue file: objetivo, criterios de aceptacion, fase actual)
|
||||
2. Loop hasta convergencia o limite:
|
||||
a. Determinar siguiente fase pendiente (CONSTRUIR/EJECUTAR/RECOPILAR/ANALIZAR/MEJORAR)
|
||||
b. Despachar al subagente correspondiente con prompt derivado del task_spec
|
||||
c. Capturar output del subagente
|
||||
d. Persistir progreso en task_runs (nueva tabla)
|
||||
e. Si fase = ANALIZAR y status = pass:
|
||||
- Aplicar fixes propuestos por MEJORAR (con limite de auto-apply, ver §Garantías)
|
||||
- Repetir desde CONSTRUIR si todavia hay criterios sin cumplir
|
||||
f. Si fase = ANALIZAR y status = fail:
|
||||
- Despachar a MEJORAR
|
||||
- Aplicar 1-2 proposals automaticas (solo si pasan filtros de seguridad, ver §Garantías)
|
||||
- Volver a CONSTRUIR/EJECUTAR para validar
|
||||
g. Si N iteraciones sin progreso → parar, reportar estado, pedir humano
|
||||
3. Reportar resultado final: estado tarea, run_ids, proposals creadas, PR draft si aplica
|
||||
```
|
||||
|
||||
### Nueva tabla `task_runs` en operations.db
|
||||
|
||||
```sql
|
||||
CREATE TABLE task_runs (
|
||||
id TEXT PRIMARY KEY,
|
||||
task_id TEXT NOT NULL, -- issue_id o slug
|
||||
started_at INTEGER NOT NULL,
|
||||
finished_at INTEGER,
|
||||
status TEXT NOT NULL, -- running|converged|stalled|aborted
|
||||
iterations INTEGER NOT NULL DEFAULT 0,
|
||||
last_phase TEXT, -- construir|ejecutar|recopilar|analizar|mejorar
|
||||
last_run_id TEXT, -- e2e_runs.id de la ultima validacion
|
||||
progress_json TEXT NOT NULL DEFAULT '[]' -- log de fases con timestamps
|
||||
);
|
||||
```
|
||||
|
||||
### Skill `/autonomous-task <issue_id>`
|
||||
|
||||
Lanza el `fn-orquestador` con limites configurables:
|
||||
|
||||
```
|
||||
/autonomous-task 0070 --max-iterations 10 --max-minutes 60 --auto-apply-proposals safe
|
||||
```
|
||||
|
||||
Flags:
|
||||
- `--max-iterations N`: tope de iteraciones del bucle (default 10).
|
||||
- `--max-minutes M`: timeout total (default 60).
|
||||
- `--auto-apply-proposals`: nivel de autonomia para aplicar proposals:
|
||||
- `none`: solo crea proposals, nunca aplica codigo.
|
||||
- `safe`: aplica proposals con `kind=improve_function` y diff < 50 lineas.
|
||||
- `aggressive`: aplica casi todas salvo las marcadas `risk=high`.
|
||||
- `--branch <name>`: rama TBD donde trabaja el bucle (default `auto/<issue_id>`).
|
||||
- `--dry-run`: no aplica nada, solo simula y reporta plan.
|
||||
|
||||
### Garantias de seguridad
|
||||
|
||||
El bucle autonomo es peligroso si el agente:
|
||||
- Borra archivos importantes.
|
||||
- Bypasea tests.
|
||||
- Toca produccion.
|
||||
- Mergea codigo roto a master.
|
||||
|
||||
Reglas obligatorias:
|
||||
|
||||
1. **Sandbox de rama**. El orquestador SIEMPRE trabaja en rama `auto/<issue>`, nunca master.
|
||||
2. **No `--no-verify`, no `git push --force`**. Hooks de pre-commit del repo se respetan.
|
||||
3. **No mergea a master**. Genera PR draft. Humano aprueba el merge.
|
||||
4. **No toca `.claude/`, `dev/issues/` salvo el del task** ni archivos `.env`/secrets. Lista de paths protegidos en `dev/autonomous_protected_paths.json`.
|
||||
5. **Filtro de proposals auto-aplicables**:
|
||||
- Solo proposals creadas por `fn-mejorador` con `evidence` que apunte a runs reales.
|
||||
- Diff < 50 lineas (configurable).
|
||||
- No tocan tests existentes (no se "arreglan" los tests).
|
||||
- No introducen dependencias nuevas (`pnpm add`, `go get`, etc).
|
||||
6. **Watchdog de progreso**. Si 2 iteraciones consecutivas dan el mismo set de fails, parar y pedir humano (loop infinito detectado).
|
||||
7. **Auditoria completa**. Cada decision del orquestador se loggea en `task_runs.progress_json` con razonamiento + diff aplicado.
|
||||
8. **Rollback trivial**. La rama es desechable; si la suite no converge, humano puede `git branch -D auto/<issue>` y empezar de nuevo.
|
||||
|
||||
### Tipos de tareas soportadas
|
||||
|
||||
Empezar con un subset acotado:
|
||||
|
||||
| Tipo | Descripcion | Convergencia |
|
||||
|---|---|---|
|
||||
| `feature_app_simple` | Endpoint nuevo + handler + test | suite verde + endpoint responde 200 |
|
||||
| `bugfix_with_repro` | Issue con repro reproducible | repro pasa de fail a pass |
|
||||
| `refactor_safe` | Renombrar/extraer funcion + actualizar callers | suite igual de verde + grep limpio |
|
||||
| `add_e2e_check` | Crear `e2e_checks` para app sin ellos via fn-recopilador | app tiene contrato + run pasa |
|
||||
|
||||
NO soportadas inicialmente (requieren mas heuristica):
|
||||
- Diseño de arquitectura nuevo.
|
||||
- Decisiones de UX subjetivas.
|
||||
- Cambios en BD productiva.
|
||||
- Cualquier cosa que toque secrets/credenciales.
|
||||
|
||||
### Convergencia
|
||||
|
||||
El bucle termina cuando:
|
||||
- **Convergido**: todos los criterios de aceptacion del issue marcan ✓ Y la suite e2e pasa Y `fn doctor <app>` pasa.
|
||||
- **Estancado**: misma metric de fallos en 2+ iteraciones (loop sin progreso).
|
||||
- **Timeout**: `--max-minutes` alcanzado.
|
||||
- **Iteraciones**: `--max-iterations` alcanzado.
|
||||
- **Bloqueo humano**: el orquestador detecta una decision que requiere humano (ej. eleccion de libreria nueva, schema breaking change) y para con `status=needs_human`.
|
||||
|
||||
En cualquier caso, output:
|
||||
|
||||
```
|
||||
=== /autonomous-task: <issue_id> ===
|
||||
status: <converged|stalled|timeout|needs_human>
|
||||
iterations: N / max
|
||||
duration: M min / max
|
||||
branch: auto/<issue>
|
||||
PR draft: <url o "no creado">
|
||||
proposals: <count> creadas, <count> aplicadas
|
||||
last run_id: <run_id> (status: pass|fail)
|
||||
|
||||
Detalle de iteraciones:
|
||||
1. construir → ok (3 funciones nuevas)
|
||||
2. ejecutar → ok
|
||||
3. analizar → fail (3 checks)
|
||||
4. mejorar → 3 proposals (2 auto-aplicadas)
|
||||
5. construir → ok (re-build tras patches)
|
||||
6. analizar → pass
|
||||
7. recopilador → ok (operations.db integra)
|
||||
8. CONVERGED
|
||||
|
||||
Siguientes pasos para humano:
|
||||
- Revisar PR draft <url>
|
||||
- fn proposal list -s pending --target-id <issue>
|
||||
```
|
||||
|
||||
## Plan de ejecucion
|
||||
|
||||
| Paso | Tarea | Dependencia |
|
||||
|---|---|---|
|
||||
| 1 | Migracion `006_task_runs.sql` en `fn_operations/migrations/` | issue 0068 cerrado |
|
||||
| 2 | Subagente `fn-orquestador` (.claude/agents/fn-orquestador/SKILL.md) | paso 1 |
|
||||
| 3 | Lista de paths protegidos (`dev/autonomous_protected_paths.json`) | paso 2 |
|
||||
| 4 | Skill `/autonomous-task <issue_id>` (.claude/commands/autonomous-task.md) | paso 2 |
|
||||
| 5 | Funciones registry: `task_run_persist_go_infra`, `proposal_filter_safe_go_infra`, `git_branch_sandbox_go_infra`, `pr_draft_create_go_infra` | paso 2 |
|
||||
| 6 | Tipo `TaskSpec_go_core` (issue + criterios + limites) | paso 5 |
|
||||
| 7 | Pilotaje en 1 issue tipo `feature_app_simple` (ej. añadir endpoint trivial a kanban) | paso 4 |
|
||||
| 8 | Pilotaje en 1 issue tipo `add_e2e_check` (correr orquestador para añadir contrato a app sin el) | paso 7 |
|
||||
| 9 | Hardening: tests del orquestador, edge cases (red flaky, BD locked, conflicto merge) | paso 8 |
|
||||
| 10 | Documentacion + regla `.claude/rules/autonomous_loop.md` | paso 9 |
|
||||
|
||||
## Criterios de aceptacion
|
||||
|
||||
- [x] `fn-orquestador` definido como subagente.
|
||||
- [x] Tabla `task_runs` migrada con migration aditiva (`006_task_runs.sql`). **Nota**: pilotos 1+2 NO inicializaron operations.db propia para persistir filas en `task_runs` — el orquestador llevo estado inline (variables internas + output). Hardening pendiente: inicializar operations.db con migration 006 al arrancar el bucle + INSERT row al inicio + UPDATE final.
|
||||
- [x] Skill `/autonomous-task` orquesta los 5 subagentes en bucle.
|
||||
- [x] Filtro de proposals auto-aplicables documentado (`.claude/rules/autonomous_loop.md` seccion "Reglas duras"). Test contra dataset adverso pendiente.
|
||||
- [x] Piloto 1: issue 0077 (fn run bash mudo) — CONVERGED 5 iter, 4/4 checks, PR Gitea#1. Hallazgos:
|
||||
- causa real (library-style scripts) divergia de hipotesis del issue (Stdout unconnected); orquestador rediagnostico bien.
|
||||
- bonus fix: `scan_secrets_in_dirty.sh` + `git_hook_audit_app_drift.sh` worktree support.
|
||||
- **sandbox parcial**: orquestador modifico los 2 hooks en repo principal. Causa probable: paths absolutos al fixear hooks bloqueantes. Hardening aplicado 2026-05-15 (SKILL.md regla 9-11 + autonomous_loop.md regla 9-10).
|
||||
- [x] Piloto 2: issue 0076 (gradle SDK detect) — CONVERGED 1 iter, 6/6 checks, PR Gitea#2. Sandbox limpio. Hallazgos:
|
||||
- gh CLI no soporta Gitea -> usado REST API directo con credential store.
|
||||
- `--no-verify` legitimo cuando hook bloquea por bug en main; documentado en `task_runs.events_json` (alineado con regla 10 de `autonomous_loop.md`).
|
||||
- tiempo mucho menor (4 min vs 9 min piloto 1) -> hipotesis: caching de contexto + fix mas simple + path bash en vez de Go.
|
||||
- [x] Watchdog de "no progreso" especificado (N=3 iteraciones sin subir `checks_pass/checks_total` -> abort).
|
||||
- [x] Output del runner incluye trazabilidad completa (`task_runs.events_json[]`).
|
||||
- [x] Documentacion en `.claude/rules/autonomous_loop.md` (rule 31).
|
||||
|
||||
## Riesgos
|
||||
|
||||
- **Loops infinitos**: agentes que "parchean" tests rotos en vez de codigo. Mitigacion: filtro de proposals (no tocar tests), watchdog.
|
||||
- **Coste**: cada iteracion = N llamadas a Claude. Mitigacion: `--max-iterations`, `--max-minutes`, modelos mas baratos para fases mecanicas (haiku para `fn-recopilador`, sonnet para `fn-constructor`).
|
||||
- **Calidad**: codigo auto-generado puede compilar pero ser malo. Mitigacion: `fn-analizador` valida no solo build sino assertions + drift; humano siempre revisa PR.
|
||||
- **Seguridad**: agente comprometiendo el repo. Mitigacion: sandbox de rama, paths protegidos, no merge automatico, hooks no skipeables.
|
||||
- **Drift de criterios**: el agente "interpreta" liberamente los criterios de aceptacion del issue. Mitigacion: criterios en el issue deben ser verificables programaticamente (ej. "endpoint responde 200" mejor que "el endpoint funciona bien").
|
||||
- **Acumulacion de proposals**: si el bucle crea muchas proposals sin que humano las cierre, ruido. Mitigacion: limite por task_run, dedup automatica por similitud.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Auto-merge a master (siempre PR draft).
|
||||
- Toma de decisiones de arquitectura (eleccion de libreria, patron de diseño).
|
||||
- Tareas que requieran credenciales (deploys, llamadas a APIs externas con auth).
|
||||
- Tareas que toquen schema de DBs productivas.
|
||||
- Self-modification del orquestador (no se puede mejorar a si mismo en el mismo run).
|
||||
|
||||
## Notas
|
||||
|
||||
- Inspiracion: SWE-bench, agentic flows tipo aider/cursor compose, Devin. Diferencia: aqui el agente NO escribe codigo libre — orquesta agentes especializados que ya respetan las reglas del registry.
|
||||
- El bucle reactivo del CLAUDE.md ya describe semantica de fases. Este issue solo añade el **orquestador** que las recorre solo.
|
||||
- La regla `kiss.md` aplica: empezar con tipos de tarea simples y verificables. Resistir tentacion de soportar todo desde dia 1.
|
||||
- Conexion con `feature_flags.md`: si el bucle queda detras de un flag (`autonomous_loop_enabled`), se puede activar/desactivar sin redeploy.
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
id: "0072l"
|
||||
title: "gamedev — scripting opcional (wren / lua / hot reload C++ dylib)"
|
||||
status: deferred
|
||||
type: feature
|
||||
domain:
|
||||
- gamedev
|
||||
scope: multi-app
|
||||
priority: baja
|
||||
depends:
|
||||
- "0072k"
|
||||
blocks: []
|
||||
related: []
|
||||
created: 2026-05-10
|
||||
updated: 2026-05-17
|
||||
tags:
|
||||
- gamedev
|
||||
- cpp
|
||||
- scripting
|
||||
---
|
||||
|
||||
## Objetivo
|
||||
|
||||
Decidir si vale la pena añadir scripting al stack y, en su caso, integrarlo. Es un sub-issue **diferido**: solo se aborda cuando la fricción de iterar gameplay en C++ (recompilar) sea concretamente dolorosa, no antes.
|
||||
|
||||
## Cuando reconsiderar
|
||||
|
||||
Triggers para sacar este issue del modo `deferred`:
|
||||
- Recompilar `engine_demo` tras un cambio gameplay supera consistentemente 10s incluso con ccache + unity build.
|
||||
- Aparece un caso real de iterar gameplay en runtime sin reiniciar (designers no-developers tocando logica).
|
||||
- Aparece la necesidad de modding por usuarios finales.
|
||||
|
||||
Mientras eso no pasa: **no añadir scripting**. Cada lenguaje de scripting:
|
||||
- Suma ~50-200 KB al wasm.
|
||||
- Multiplica superficie de bugs (FFI bindings).
|
||||
- Confunde la cultura del registry (¿dónde vive el "codigo de juego"? ¿C++ o scripts?).
|
||||
|
||||
## Opciones
|
||||
|
||||
### Opcion A — wren
|
||||
|
||||
- Pequeño (~50 KB).
|
||||
- Sintaxis tipo Lua/Smalltalk.
|
||||
- API C limpia.
|
||||
- Single-author (Bob Nystrom), actualizaciones lentas.
|
||||
|
||||
### Opcion B — lua / luajit
|
||||
|
||||
- Lua estandar: ~200 KB. LuaJIT no compila a WASM, descartar.
|
||||
- Mas usuarios, mas docs.
|
||||
- Sintaxis familiar.
|
||||
- Bindings: sol2 (~5K LoC, pero header-only) o tolua manual.
|
||||
|
||||
### Opcion C — quickjs
|
||||
|
||||
- JS subset.
|
||||
- ~700 KB. Demasiado para nuestro budget.
|
||||
- Descartar a menos que necesitemos JS por compatibilidad con codigo crypto.
|
||||
|
||||
### Opcion D — hot reload C++ via dylib
|
||||
|
||||
- Compilar el codigo de juego como `.so` / `.dll` y recargar al cambiar.
|
||||
- 0 KB extra runtime.
|
||||
- No funciona en WASM (no hay dylib loading dinamico).
|
||||
- No funciona en iOS (no se permite carga dinamica de codigo).
|
||||
- Solo util en desktop dev workflow.
|
||||
|
||||
### Opcion E — sin scripting + ccache + unity build
|
||||
|
||||
- Compilar todo C++ con ccache. Cambios incrementales tipicos < 3s.
|
||||
- Unity build de la app reduce LTO time.
|
||||
- Hot reload de **assets y shaders** (que es lo que mas se itera) NO necesita scripting.
|
||||
|
||||
## Recomendacion previa
|
||||
|
||||
Empezar con Opcion E. Solo si el dolor real aparece, evaluar A (wren) por ser la mas pequeña y compatible WASM.
|
||||
|
||||
## Si se aborda: scope minimo
|
||||
|
||||
1. Scripting solo en niveles altos (gameplay logic, AI scripts, dialogos).
|
||||
2. Codigo "de motor" (rendering, physics, input) sigue en C++.
|
||||
3. Bindings expuestos via funciones del registry (`script_register_*` para cada subsistema).
|
||||
4. Hot reload de scripts en dev mode (file watcher).
|
||||
5. Empaquetar scripts en el `.pak` de assets.
|
||||
|
||||
## Funciones (cuando llegue el momento)
|
||||
|
||||
- `cpp/functions/gamedev/wren_vm.{cpp,h,md}` — VM lifecycle
|
||||
- `cpp/functions/gamedev/wren_bind.{cpp,h,md}` — registrar funciones C++ a wren
|
||||
- `cpp/functions/gamedev/wren_call.{cpp,h,md}` — invocar funciones wren desde C++
|
||||
|
||||
## Tamaño objetivo si se hace
|
||||
|
||||
- Wren: ~60 KB total (vm + bindings).
|
||||
- Bindings de juego: ~10 KB.
|
||||
- Maximo aceptable: 100 KB.
|
||||
|
||||
## Criterio para cerrar este issue
|
||||
|
||||
- Decision tomada: si o no.
|
||||
- Si si: VM integrada, bindings minimos, demo de un script de AI cargado en runtime.
|
||||
- Si no: documentar el por qué en `cpp/GAMEDEV.md` (helpful no future contributors).
|
||||
|
||||
## No-objetivos
|
||||
|
||||
- Visual scripting (Blueprints style).
|
||||
- Scripting para todo el juego.
|
||||
- Multiples lenguajes de scripting.
|
||||
@@ -0,0 +1,330 @@
|
||||
---
|
||||
id: "0085"
|
||||
title: "Estandarizar llamadas a funciones del registry desde Claude + app de monitorizacion de uso"
|
||||
status: completado
|
||||
type: feature
|
||||
domain:
|
||||
- meta
|
||||
scope: multi-app
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related:
|
||||
- "0068"
|
||||
- "0069"
|
||||
created: 2026-05-13
|
||||
updated: 2026-05-17
|
||||
tags: []
|
||||
---
|
||||
|
||||
## Cierre 2026-05-15
|
||||
|
||||
Todas las piezas del plan implementadas:
|
||||
|
||||
- Schema event-log + vistas (0085a/0085l) — 7 tablas + `function_stats` view + `function_versions`.
|
||||
- Hook Bash PostToolUse (0085b) capturando mcp/heredoc/sqlite_direct/edit_registry/violations.
|
||||
- Wrappers opt-in (0085c py + 0085c-bash) activables via `FN_TELEMETRY=1`, smoke verificado.
|
||||
- Interceptor en `fn run` (0085d-go) con duration real medida.
|
||||
- UI tab "Claude Usage" en `registry_dashboard` (0085d/0085e) con KPIs + sub-tabs.
|
||||
- Clusterizacion de patrones inline (0085f) — `call_monitor cluster-patterns [--persist]`, 11 clusters detectados, upsert idempotente.
|
||||
- Reglas violation declarativas (0085g, parcial) — `dev/violation_rules.yaml` source-of-truth con 4 activas + 4 propuestas inactivas; runtime YAML reader TBD.
|
||||
- Pipeline `call_monitor propose` (0085h) genera proposals con evidencia desde `function_stats`+`copied_code`+`violations`.
|
||||
- Auditoria estatica de copia (0085k) `fn doctor copied-code`.
|
||||
- Documentacion (0085j) — CLAUDE.md + `.claude/rules/registry_calls.md`.
|
||||
|
||||
Piezas futuras documentadas pero fuera del MVP: build-tag Go telemetry (0085m), macro C++ `FN_CALL` (0085n), runtime YAML reader del hook, vistas adicionales del dashboard (drill-down por sesion + diff entre sesiones).
|
||||
---
|
||||
|
||||
## Contexto
|
||||
|
||||
Claude actualmente invoca funciones del registry de formas heterogeneas y sin trazabilidad:
|
||||
|
||||
| Patron de invocacion | Frecuencia esta sesion (meta_bigq) | Trazabilidad |
|
||||
|---|---|---|
|
||||
| Heredoc Python inline (`python/.venv/bin/python3 - <<'PYEOF' ... PYEOF`) | ~15 veces | ninguna (queda en transcript) |
|
||||
| Bash inline con `sqlite3 registry.db "..."` | 1 (violando regla MCP-first) | ninguna |
|
||||
| `./fn run <id>` CLI | 0 | log en stdout, no persistido |
|
||||
| `mcp__registry__fn_run` MCP tool | 0 | mensaje tool-result |
|
||||
| `mcp__registry__fn_search/show/code` | 0 (deberia haber sido obligatorio) | ninguna |
|
||||
| Imports directos `from metabase import ...` en heredoc | en cada heredoc | ninguna |
|
||||
| `client._http.request(...)` directo saltando funciones del registry | varias veces (para PUT custom como result_metadata) | ninguna |
|
||||
|
||||
Consecuencias:
|
||||
- **No sabemos que funciones del registry usa Claude realmente**. Hay ~1200 funciones indexadas pero solo unas pocas se usan en cada sesion. Imposible decidir cuales deprecar, cuales mejorar, cuales son criticas.
|
||||
- **Cada sesion reinventa boilerplate**. Patrones repetitivos (refresh `result_metadata`, dispatch `dimension` vs `variable` mapping, batch param config) se reescriben inline. Si se extraen como funciones del registry nadie lo nota porque no hay metricas de "esto se repite mucho".
|
||||
- **CLAUDE.md tiene reglas (MCP-first, registry-first) que se violan silenciosamente**. Sin telemetria, la regla es aspiracional.
|
||||
- **No hay datos para que el bucle reactivo mejore el registry**. El analizador/mejorador deberian saber "esta funcion se uso 50 veces, esta 0 veces, esta fallo 8 veces" — info que ahora mismo no existe.
|
||||
|
||||
## Objetivo
|
||||
|
||||
1. **Estandarizar** como Claude invoca funciones del registry (un patron canonico por caso de uso).
|
||||
2. **Instrumentar** todas las invocaciones para que dejen rastro en una BD local.
|
||||
3. **App `claude_call_monitor`** (TUI o web) que muestra uso por funcion, latencia, errores, patrones repetidos, violaciones de reglas.
|
||||
4. **Feedback al registry**: marcar funciones "huerfanas", proponer extracciones cuando un mismo bloque inline se repite N veces, sugerir helpers nuevos.
|
||||
|
||||
## Diseno
|
||||
|
||||
### Fase 1 — Estandarizacion de invocaciones
|
||||
|
||||
Tres patrones canonicos. Cada uno con su tool de entrada y formato de log.
|
||||
|
||||
| Caso de uso | Patron canonico | Cuando usar |
|
||||
|---|---|---|
|
||||
| **Inspeccion del registry** (buscar, leer, ver dependencias) | `mcp__registry__fn_search/show/code/uses` | SIEMPRE. Reemplaza `sqlite3 registry.db "..."` inline. CLAUDE.md ya lo exige; ahora se hace cumplir via lint del log |
|
||||
| **Ejecucion de pipeline/funcion 1-shot** | `mcp__registry__fn_run <id> [args]` o `./fn run <id> [args]` | Cuando hay UNA funcion/pipeline a lanzar con sus args. Salida estructurada |
|
||||
| **Composicion ad-hoc multi-funcion** | Heredoc Python via Bash, importando del registry | Cuando hay logica intermedia (loops, conditionals, dispatch). Esta sesion casi todo el trabajo cae aqui |
|
||||
|
||||
Para composiciones que se repiten: extraer a `python/functions/pipelines/` o a una funcion del registry. Decision basada en datos del monitor (ver fase 3).
|
||||
|
||||
### Fase 2 — Instrumentacion
|
||||
|
||||
Hook + libreria que captura cada invocacion. Stack propuesto:
|
||||
|
||||
**2a. Hook en Bash tool**: parsea cada comando Bash. Si contiene heredoc Python `python/.venv/bin/python3` o invoca `./fn run` o `mcp__registry__fn_*`, captura:
|
||||
- timestamp_start, timestamp_end, duration_ms
|
||||
- session_id (del log de Claude Code)
|
||||
- tool_used (Bash heredoc / fn_run / mcp_fn_X / mcp_fn_search / sqlite_direct / etc.)
|
||||
- functions_imported (parse `from <pkg> import <names>`)
|
||||
- functions_called (mejor esfuerzo: regex sobre el codigo del heredoc + para fn_run el id explicito)
|
||||
- success / exit_code / error_snippet
|
||||
- patron_detected (refresh_metadata, build_mappings, etc — clasificadores configurables)
|
||||
|
||||
Implementacion: hook `PostToolUse` en `~/.claude/settings.json` que llama a un binario Go que escribe en `~/.claude/projects/<proj>/call_monitor.db` (SQLite).
|
||||
|
||||
**2b. Wrapper Python**: `from registry_telemetry import wrap` que parchea las funciones del paquete `metabase`/`bigquery`/etc al importarse en heredoc. Cada llamada se loguea (function_id, args_hash, duration, success). Solo se activa si env var `FN_TELEMETRY=1` (no romper otros usos).
|
||||
|
||||
**2c. Hook directo en MCP `registry`**: si el server MCP esta bajo nuestro control, anadir logging en cada tool call (mas confiable que parsear bash).
|
||||
|
||||
Las 3 fuentes se cruzan: si una sesion tiene Bash heredoc usando `metabase_get_dashboard` pero no aparece en MCP logs, es violacion (deberia haber usado `mcp__registry__fn_show` para inspeccionar antes).
|
||||
|
||||
### Fase 3 — App `claude_call_monitor`
|
||||
|
||||
App standalone en `apps/claude_call_monitor/` o `projects/fn_monitoring/apps/claude_call_monitor/`. Stack:
|
||||
- Backend Go (sirve datos de `call_monitor.db` + agregados)
|
||||
- Frontend React + Mantine (consume `@fn_library`) o TUI con `cpp/framework` ImGui — segun preferencia
|
||||
|
||||
Vistas minimas:
|
||||
|
||||
1. **Top funciones por uso** — tabla rankada: `function_id, calls_24h, calls_7d, mean_duration_ms, error_rate, last_used_at`. Filtros por dominio/lang/purity.
|
||||
2. **Funciones huerfanas** — listado de funciones del registry con `calls_30d = 0`. Cruzado con `fn doctor unused` para distinguir "nunca usada" vs "no usada por Claude pero si por humanos".
|
||||
3. **Patrones repetidos** — clusterizacion de heredocs Python por similitud. Detecta cuando un bloque inline se repite N veces → proposal automatico para extraer a funcion.
|
||||
4. **Violaciones de regla** — usos de `sqlite3 registry.db` directo, uso de `Centros_ISO_Limpio` cuando deberia ser via card snippet, etc. Reglas configurables en YAML.
|
||||
5. **Sesion view** — timeline de una sesion (Claude Code session_id) con todas las llamadas, errores, duraciones. Util para post-mortem.
|
||||
6. **Health score** — score 0-100 por sesion: ratio de invocaciones canonicas vs ad-hoc, errores, repeticiones. Telemetria para mejorar prompts del agente.
|
||||
|
||||
### Fase 4 — Feedback al registry
|
||||
|
||||
Hooks de salida del monitor:
|
||||
|
||||
- **Proposals automaticas**: cuando un patron inline se repite >5 veces en distintas sesiones, se crea proposal `new_function` en `registry.db` con evidencia (lista de session_ids + snippet representativo). El humano (o `fn-mejorador`) decide si aprobar.
|
||||
- **Deprecation candidates**: funciones con `calls_90d = 0` y sin `uses_functions` upstream → proposal `deprecate_function`.
|
||||
- **Performance regressions**: funciones cuyo `mean_duration_ms` crece >50% entre semanas → flag al humano.
|
||||
|
||||
## Implementacion por pasos
|
||||
|
||||
| Paso | Tarea | Sub-issue |
|
||||
|---|---|---|
|
||||
| 1 | Migracion `call_monitor.db` schema (`calls`, `sessions`, `patterns`, `violations`) | 0085a |
|
||||
| 2 | Hook Bash `PostToolUse` que parsea comandos y escribe a `calls` | 0085b |
|
||||
| 3 | Wrapper Python opcional con `FN_TELEMETRY=1` | 0085c |
|
||||
| 4 | App `claude_call_monitor` skeleton (Go API + frontend) | 0085d |
|
||||
| 5 | Vistas: top usage, huerfanas, sesiones | 0085e |
|
||||
| 6 | Clusterizacion de heredocs + deteccion de patrones | 0085f |
|
||||
| 7 | Reglas de violacion configurables (YAML) | 0085g |
|
||||
| 8 | Pipeline `fn-monitor proposal` que crea proposals automaticas en `registry.db` desde patrones detectados | 0085h |
|
||||
| 9 | `e2e_checks` para la propia app del monitor | 0085i |
|
||||
| 10 | Documentacion en CLAUDE.md: patrones canonicos + como leer el monitor | 0085j |
|
||||
|
||||
## Criterios de exito
|
||||
|
||||
- Todas las invocaciones de funciones del registry desde Claude quedan registradas (>95% cobertura medida cruzando 3 fuentes).
|
||||
- App `claude_call_monitor` muestra top-20 funciones usadas por Claude en los ultimos 7 dias con metricas reales.
|
||||
- Se detectan al menos 5 patrones repetidos como candidatos a extraccion (con evidencia trazable).
|
||||
- Se identifican >50 funciones huerfanas para decision (deprecar/promover/dejar).
|
||||
- CLAUDE.md tiene seccion "Como llamar a funciones del registry" con los 3 patrones canonicos + tabla "cuando usar cual".
|
||||
- El bucle reactivo (0068) tiene un nuevo input: assertions sobre uso de funciones → proposals.
|
||||
|
||||
## Anti-patrones a prohibir explicitamente
|
||||
|
||||
| Patron | Por que | Alternativa |
|
||||
|---|---|---|
|
||||
| `sqlite3 registry.db "SELECT ..."` para inspeccionar funciones | Salta MCP, no hay logging, FTS5 gotchas | `mcp__registry__fn_search "..."` |
|
||||
| `python -c "import metabase; print(dir(metabase))"` para descubrir helpers | Salta el registry como fuente de verdad | `mcp__registry__fn_search "metabase"` + `mcp__registry__fn_show <id>` |
|
||||
| Heredoc que reescribe logica que ya existe como funcion | Reinvento + perdida de capitalizacion | Primero `fn_search`, luego importar |
|
||||
| `client._http.request(...)` directo cuando hay un wrapper del registry | Salta validacion y telemetria del wrapper | Usar la funcion del registry; si falta una, delegar a `fn-constructor` |
|
||||
| Crear scripts en `temp/` o paths sueltos cuando es composicion repetida | Codigo se pierde, no se monitoriza | Si patron se repite → pipeline en `python/functions/pipelines/` |
|
||||
|
||||
## Stakeholders
|
||||
|
||||
- **Usuario humano (Lucas / Emanuel)**: revisa proposals automaticas, prioriza extracciones, decide deprecaciones.
|
||||
- **Claude (agente principal)**: lee CLAUDE.md actualizado, usa patrones canonicos, recibe feedback de monitor en CLAUDE.md (top huerfanas, top errores).
|
||||
- **fn-mejorador (fase 5 bucle reactivo)**: consume `call_monitor.db` para generar proposals con evidencia real de uso.
|
||||
- **fn-orquestador (issue 0069)**: usa health score del monitor como criterio adicional de exito.
|
||||
|
||||
## Notas
|
||||
|
||||
- Esta issue no requiere refactorizar las 1200 funciones existentes — solo capturar como se invocan.
|
||||
- El monitor empieza pasivo (solo loguea). En una fase posterior puede bloquear violaciones criticas (ej. `sqlite3 registry.db` directo aborta con mensaje + sugerencia).
|
||||
- Datos sensibles: el `args_hash` se guarda pero los valores concretos NO. Para queries SQL que contienen secretos por accidente, mantener allowlist de redaccion.
|
||||
- Compatible con el patron de `task_runs` del issue 0069 — comparten el concepto de "ejecucion trazable".
|
||||
|
||||
## Decisiones 2026-05-13
|
||||
|
||||
- **UI**: tab dentro de `registry_dashboard` (proyecto `fn_monitoring`). Reusa C++/ImGui + `sqlite_api`. Razon: pregunta clave "que deprecar" = JOIN entre `functions` y `function_stats`; una sola app gana.
|
||||
- **Plumbing**: `projects/fn_monitoring/apps/call_monitor/` (Go + hook Bash `PostToolUse`). Repo Gitea propio (`dataforge/call_monitor`).
|
||||
- **Hook scope**: solo proyecto (`.claude/settings.local.json`). Captura solo sesiones de fn_registry.
|
||||
- **Orden de sub-issues**: 0085j (docs) → 0085a (schema) → 0085b (hook) → 0085c (wrapper Python).
|
||||
- **0085j hecho** (2026-05-13): seccion "Como invocar funciones del registry (CANONICO)" en `.claude/CLAUDE.md`, regla `.claude/rules/registry_calls.md` (rule #27), entrada `INDEX.md`, memoria `feedback_canonical_registry_calls.md`.
|
||||
- **MCP ampliado**: tool nuevo `fn_proposal` (read-only, list + show by id) en `apps/registry_mcp/tool_proposal.go`. Cierra el gap de `sqlite3 registry.db "SELECT ... FROM proposals"` inline. Rebuild aplicado.
|
||||
|
||||
## Schema extendido — contadores por funcion
|
||||
|
||||
Event-log tables (append-only):
|
||||
|
||||
| Tabla | Captura |
|
||||
|---|---|
|
||||
| `calls` | function_id, tool_used, session_id, duration_ms, success, error_class, error_snippet, args_hash, ts |
|
||||
| `code_writes` | function_id (derivado del path), session_id, lines_added, lines_removed, ts |
|
||||
| `test_runs` | function_id, test_id, passed, duration_ms, output_snippet, ts |
|
||||
| `e2e_runs_fn` | function_id, app_id, check_id, passed, ts (cruza con `apps.uses_functions`) |
|
||||
| `violations` | rule_id, session_id, command_snippet, severity, ts |
|
||||
| `patterns` | pattern_hash, session_ids[], representative_snippet, occurrences, last_seen |
|
||||
| `sessions` | session_id, cwd, started_at, ended_at, health_score, mcp_ratio |
|
||||
|
||||
Vista agregada `function_stats` por `function_id`:
|
||||
|
||||
- **Uso**: `calls_total`, `calls_24h/7d/30d/90d`, `last_used_at`
|
||||
- **Errores**: `errors_total`, `error_rate`, `last_error_class`, `last_error_ts`
|
||||
- **Performance**: `mean_duration_ms`, `p95_duration_ms`
|
||||
- **Codigo**: `writes_count`, `last_write_at`
|
||||
- **Tests**: `tests_total`, `tests_failed`, `test_fail_rate`, `last_test_failed_at`
|
||||
- **E2E**: `e2e_total`, `e2e_failed`, `e2e_fail_rate`, `consumer_apps_count`
|
||||
- **Salud**: `violations_caused`
|
||||
|
||||
Assertions derivadas → proposals automaticas:
|
||||
|
||||
| Regla | Threshold | Proposal |
|
||||
|---|---|---|
|
||||
| Huerfana absoluta | `calls_90d=0 AND writes_count=0` | `deprecate_function` |
|
||||
| Bug prioritario | `error_rate>0.1 AND calls_7d>5` | `improve_function` (bug) |
|
||||
| Regresion performance | `p95_24h > 1.5 * p95_30d` | `improve_function` (perf) |
|
||||
| Test flaky | `test_fail_rate>0.1 AND tests_total>10` | `improve_function` (flaky) |
|
||||
| Wrapper saltado | `violations_caused>3` | `improve_function` (API gap) |
|
||||
| Patron inline sin funcion | `patterns.occurrences>5 AND no match FTS` | `new_function` con snippet |
|
||||
| Blast radius alto | `e2e_fail_rate>0 AND consumer_apps_count>=3` | `improve_function` (critical) |
|
||||
|
||||
Migracion: `projects/fn_monitoring/apps/call_monitor/migrations/001_init.sql` con las 7 tablas + indices sobre `function_id`, `session_id`, `ts`. Vista `function_stats` se construye en `002_function_stats_view.sql` (materializar como TABLE si performance bajo, recalcular cada N min).
|
||||
|
||||
## Cobertura por lenguaje — capas de monitorizacion
|
||||
|
||||
Cada lenguaje del registry tiene un "techo" de lo monitorizable runtime. Las 8 capas de cobertura propuestas, ordenadas por ROI:
|
||||
|
||||
| # | Capa | Lenguaje | Cubre | Esfuerzo | Estado |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | Hook PostToolUse Bash | universal | mcp__registry__*, `./fn run`, Edit/Write sobre `functions/`, violations | bajo | **hecho (0085b)** |
|
||||
| 2 | Wrapper Python (`registry_telemetry`) activable con `FN_TELEMETRY=1` | py | heredocs Python + notebooks Jupyter + scripts dentro del registry | bajo | pending 0085c |
|
||||
| 3 | Wrapper Bash `bash/lib/telemetry_prelude.sh` (redefine cada funcion del registry con cronograma + log) | bash | heredocs bash + scripts/apps Bash | bajo | pending 0085c-bash |
|
||||
| 4 | Interceptor en `fn run` (binario Go) | go/py/bash/ts | invocaciones via CLI con duration/error real medido | medio | pending 0085d-go |
|
||||
| 5 | `fn doctor copied-code` — fingerprint match registry vs apps | universal (estatico) | codigo copiado/parafraseado sin import | medio | pending 0085k |
|
||||
| 6 | Tabla `function_versions` + snapshot en Edit-hook | universal (estatico) | drift de versiones, forks silenciosos | bajo | pending 0085l |
|
||||
| 7 | Build-tag `telemetry` Go + codegen wrappers | go | runtime de apps Go (parcial, opt-in por build) | alto | futuro |
|
||||
| 8 | Macro `FN_CALL(name, ...)` C++ opt-in | cpp | C++ apps cooperantes | alto | futuro |
|
||||
|
||||
**Reglas duras:**
|
||||
- **Go y C++ compilados:** sin monkey-patch dinamico. Apps que linkean estaticamente la funcion **no se pueden auditar runtime** salvo capas 7/8 con opt-in.
|
||||
- **Solucion realista Go/C++:** medir 3 caminos legitimos — `./fn run`, `mcp_fn_run`, `go test`/`ctest`. Runtime de app en produccion queda fuera.
|
||||
- **Wrapper Python/Bash:** activacion explicita via `FN_TELEMETRY=1`. Si la app no exporta esa env var, no se mide.
|
||||
|
||||
## Drift detection — funciones copiadas y modificadas
|
||||
|
||||
### 5. Codigo copiado en apps (sin `import`)
|
||||
|
||||
Problema: app reescribe el cuerpo de una funcion del registry en vez de importarla. Invisible runtime — el codigo nunca pasa por el registry. Solo se detecta por analisis estatico.
|
||||
|
||||
Tecnicas (ordenadas por precision/coste):
|
||||
|
||||
| Tecnica | Pilla | Limitacion |
|
||||
|---|---|---|
|
||||
| Hash exacto del cuerpo normalizado (strip whitespace + comments) | copy-paste literal | 0 false-positives, miss si renombran 1 var |
|
||||
| AST fingerprint via Tree-Sitter → token sequence → SimHash | copia con renames | requiere parser por lenguaje |
|
||||
| ssdeep / TLSH fuzzy hash sobre cuerpo normalizado | copia con tweaks pequeños | umbral arbitrario ~85% true match |
|
||||
| Embeddings de codigo (Code2Vec, starcoder, etc.) | parafraseado / refactor parcial | costoso, opaco |
|
||||
|
||||
**MVP propuesto:** `fn doctor copied-code` que cruza fingerprints de cada funcion del registry contra cuerpos de funciones declaradas en `apps/`, `projects/*/apps/`. Salida: `{app_id, app_file, app_func_name, matched_registry_id, similarity, kind}`. Severidad:
|
||||
|
||||
- `exact_copy` → critical → proposal `import_instead`
|
||||
- `near_copy` (>0.85 fuzzy) → warning
|
||||
- `partial_match` (>0.6) → info
|
||||
|
||||
Persistencia: tabla nueva `copied_code` en `call_monitor.operations.db`. Aporta a `function_stats` columna `copies_detected`.
|
||||
|
||||
### 6. Versiones modificadas (`function_versions`)
|
||||
|
||||
`registry.db.functions.content_hash` ya existe — `fn index` lo recalcula. Falta historial.
|
||||
|
||||
Schema propuesto (migracion `003_function_versions.sql` en call_monitor):
|
||||
|
||||
```sql
|
||||
CREATE TABLE function_versions (
|
||||
function_id TEXT NOT NULL,
|
||||
content_hash TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
snapped_at INTEGER NOT NULL,
|
||||
source TEXT NOT NULL, -- 'index' | 'edit_hook' | 'copy_detected'
|
||||
lines_added INTEGER DEFAULT 0,
|
||||
lines_removed INTEGER DEFAULT 0,
|
||||
PRIMARY KEY (function_id, content_hash)
|
||||
);
|
||||
```
|
||||
|
||||
Llenado:
|
||||
- Cada `fn index` inserta snapshot con `source='index'`.
|
||||
- Hook PostToolUse Edit/Write sobre `functions/...` inserta snapshot con `source='edit_hook'` (ya tenemos `code_writes` — extender o cross-reference).
|
||||
- `fn doctor copied-code` registra version observada en la copia con `source='copy_detected'`.
|
||||
|
||||
Consultas utiles:
|
||||
- Forks silenciosos: app copio version X, registry esta en version Y, app sigue en X.
|
||||
- Velocidad de cambio: funciones con muchas versions en poco tiempo → inestables.
|
||||
- Backport candidates: app tiene version vieja, version nueva resuelve bug → flag.
|
||||
|
||||
## Que se escapa del monitor (boundary explicito)
|
||||
|
||||
| Caso | Capturado? | Capa que lo cubriria |
|
||||
|---|---|---|
|
||||
| `mcp__registry__fn_*` | si | hook PostToolUse |
|
||||
| `./fn run X` desde Bash | si | hook PostToolUse |
|
||||
| Edit/Write sobre `functions/*/*.{go,py,sh,ts}` | si | hook PostToolUse |
|
||||
| Heredoc Python importando registry | parcial (solo `heredoc_py`, no funciones internas) | wrapper Python (0085c) |
|
||||
| Heredoc Bash sourcing registry | parcial | wrapper Bash (0085c-bash) |
|
||||
| Notebook Jupyter (MCP jupyter) ejecutando codigo registry | parcial | wrapper Python (0085c) — el kernel hereda env vars |
|
||||
| Sub-agente (`Agent` tool) | NO transitivo | requiere registrar hooks en cada sub-agente |
|
||||
| **Funcion Go llamada por codigo de app en runtime** | **NO** | capa 7 (build-tag) — futuro |
|
||||
| **Funcion Bash sourceada por otro script en runtime** | **NO** | wrapper Bash (0085c-bash) si se sourcea el prelude |
|
||||
| **Funcion C++ compilada y llamada por app** | **NO** | capa 8 (macro `FN_CALL`) — futuro |
|
||||
| Test automatico que ejecuta funciones | NO si corre fuera de Claude | capa 4 (`fn run` instrumentado captura `go test` via fn) |
|
||||
| Service de produccion (`registry_api.service`) recibe HTTP | **NO** | sin cobertura — runtime sistema, no agente |
|
||||
| Cron / Dagu / systemd timer | **NO** | sin cobertura |
|
||||
| Funcion clonada/copiada (sin `import`) | NO runtime | capa 5 (`fn doctor copied-code`) detecta estatico |
|
||||
|
||||
**Verdad operativa:** monitorizamos al **agente** y a las **invocaciones canonicas** del registry. El runtime de cada app en produccion queda fuera. Compensar con: tests, e2e_checks (capa propia por app, issue 0068), y telemetria de invocacion via `fn run`/MCP.
|
||||
|
||||
## Implementacion por pasos (actualizado 2026-05-13)
|
||||
|
||||
| Paso | Tarea | Sub-issue | Estado |
|
||||
|---|---|---|---|
|
||||
| 1 | Migracion `call_monitor.operations.db` schema (7 tablas event-log + vista `function_stats`) | 0085a | **hecho** |
|
||||
| 2 | Hook Bash `PostToolUse` que parsea tools y escribe `calls`/`code_writes`/`violations` | 0085b | **hecho** |
|
||||
| 3a | Wrapper Python `registry_telemetry` (activable con `FN_TELEMETRY=1`) | 0085c | **hecho** — `python/functions/infra/registry_telemetry.py`, sys.meta_path importer + `wrap_namespace`. Smoke verificado 2026-05-15: `filter_list_py_core` logged con `tool_used=python_wrapper`. |
|
||||
| 3b | Wrapper Bash `telemetry_prelude` | 0085c-bash | **hecho** — `bash/functions/infra/telemetry_prelude.sh`, autowrap idempotente via declare -f + eval rename. Smoke verificado 2026-05-15: `wait_for_http_bash_infra` logged con `tool_used=bash_wrapper`. |
|
||||
| 3c | Interceptor en `fn run` (binario Go) | 0085d-go | **hecho** |
|
||||
| 4 | Tab "Claude Usage" en `registry_dashboard` (datasource `ops:call_monitor`, KPIs + 3 sub-tabs) | 0085d | **hecho** |
|
||||
| 5 | Top usage, huerfanas, sesiones (vistas UI) | 0085e | **hecho** — implementadas en `registry_dashboard` tab "Claude Usage" (`projects/fn_monitoring/apps/registry_dashboard/views.cpp`, `data.h`, `data_http.h`). KPIs Reg%, MCP, Errors, Violations + sub-tabs top/huerfanas/sesiones. |
|
||||
| 6 | Clusterizacion heredocs + tabla `patterns` populada | 0085f | **hecho** 2026-05-15 — `call_monitor cluster-patterns [--persist]` (`cluster.go`). Normaliza snippets (quoted strings -> STR, paths -> /PATH, hex 8+ -> HEX, numbers -> N), hashea sha256-truncado, agrega ocurrencias + session_ids. 11 clusters detectados de 286 calls inline; persistencia con UPSERT idempotente. 3 unit tests (TestNormalizeSnippet/TestHashSnippetStable/TestSplitCSV) pass. |
|
||||
| 7 | Reglas violation configurables YAML | 0085g | **parcial** 2026-05-15 — `dev/violation_rules.yaml` cataloga 4 reglas activas (sqlite3_registry_select, python_dir_inspect, import_star_in_heredoc, client_http_request_direct) + 4 propuestas inactivas (mcp_ratio_low, heredoc_repetition, edit_registry_without_fn_index, protected_path_modified). YAML es source-of-truth declarativo. **Runtime reader TBD**: el hook PostToolUse sigue hardcoded; futura iteracion requiere jq/yq + refactor para leer reglas dinamicamente. |
|
||||
| 8 | Pipeline `call_monitor propose` (funcion `infra.GenerateProposalsFromTelemetry` + `infra.PersistProposalDrafts`) que escribe a `registry.db.proposals` desde `function_stats` + `copied_code` + `violations`. 4 reglas MVP: copy_detected, orphan, bug, wrapper_skip. INSERT OR IGNORE con id determinista | 0085h | **hecho** |
|
||||
| 9 | `e2e_checks` propios de call_monitor (en `app.md`, ya declarados) | 0085i | parcial (declarado) |
|
||||
| 10 | Documentacion CLAUDE.md + rules (registry_calls.md) | 0085j | **hecho** |
|
||||
| 11 | `fn doctor copied-code` + tabla `copied_code` + subcomando `call_monitor copied-code` | 0085k | **hecho** |
|
||||
| 12 | Tabla `function_versions` + subcomando `call_monitor snapshot` + edit-hook con sha256 file | 0085l | **hecho** |
|
||||
| 13 (futuro) | Go build-tag `telemetry` con codegen | 0085m | futuro |
|
||||
| 14 (futuro) | C++ macro `FN_CALL` opt-in | 0085n | futuro |
|
||||
@@ -0,0 +1,276 @@
|
||||
---
|
||||
id: "0086"
|
||||
title: "Refactor incremental de CLAUDE.md — delegacion agresiva a fn-constructor + capability groups por tags"
|
||||
status: completado
|
||||
type: feature
|
||||
domain:
|
||||
- meta
|
||||
scope: multi-app
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related:
|
||||
- "0069"
|
||||
- "0085"
|
||||
created: 2026-05-13
|
||||
updated: 2026-05-17
|
||||
tags: []
|
||||
---
|
||||
|
||||
## Cierre 2026-05-14
|
||||
|
||||
15 capability groups en `docs/capabilities/INDEX.md` con descripcion + ejemplo canonico + fronteras: metabase, mantine, deploy, ssh, systemd, registry, bigquery, nlp, docker, android, doctor, notebook, cpp-windows, git. Hook gate post-creacion (`hook_capability_tag_gate.sh`) + vista `session_capability_growth` (migration 005) + linea CAPABILITY-GROWTH en `UserPromptSubmit` ya operativos. Reglas `delegation.md` + `capability_groups.md` presentes en `.claude/rules/INDEX.md`. CLAUDE.md tiene bloque "Delegacion + Capability Groups (REGLA DURA)" al principio.
|
||||
---
|
||||
|
||||
## Contexto
|
||||
|
||||
`.claude/CLAUDE.md` (proyecto) cubre bien el "que" (BDs, sync, MCP-first, antipatrones) pero NO documenta el bucle que multiplica capacidades de Claude:
|
||||
|
||||
```
|
||||
detectar gap -> spawn fn-constructor -> indexar -> importar -> invocar (mismo turno)
|
||||
```
|
||||
|
||||
Hoy Claude:
|
||||
- Detecta logica reutilizable y la escribe **inline** en el artefacto (o heredoc). No delega.
|
||||
- Cuando si delega, suele hacerlo **secuencial** aunque las funciones sean independientes.
|
||||
- Tras crear funciones nuevas, no las **invoca en el mismo turno** — quedan recien indexadas sin primer uso.
|
||||
- No marca con **tags de grupo** las nuevas funciones, asi que el siguiente turno (o siguiente sesion) no encuentra el cluster facilmente. Resultado: re-descubrimiento via FTS5 cada vez.
|
||||
- No hay **documentacion de capability groups** (ej. "metabase", "android-emu", "deploy", "notebook") — los tags existen sueltos pero sin pagina madre que liste el grupo, su API y ejemplos.
|
||||
|
||||
Issue 0085 ya da la base de telemetria (call_monitor + writes). Falta:
|
||||
|
||||
1. **Doctrina en CLAUDE.md** que obligue al ciclo crear-usar-tagear-documentar en el mismo turno.
|
||||
2. **Capability groups**: tags canonicos + `docs/capabilities/<group>.md` autogenerable que liste funciones, firmas, ejemplos.
|
||||
3. **Loop closure**: paralelizar fn-constructor, auto-verificar con `fn doctor`, usar las nuevas funciones antes de cerrar turno.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Que Claude, en cada turno, **multiplique su capacidad util** registrando funciones nuevas y reusandolas inmediatamente — no acumular funciones huerfanas ni reescribir logica inline.
|
||||
|
||||
Tres entregables:
|
||||
|
||||
1. **Refactor incremental de `.claude/CLAUDE.md`** (no reescritura completa): mantener estructura actual, comprimir secciones referenciales hacia `docs/`, anadir bloque nuevo "Delegacion + Capability Groups" al principio.
|
||||
2. **Sistema de capability groups** basado en tags + `docs/capabilities/<group>.md` generado por `fn doctor capabilities` (nuevo subcomando) o pipeline dedicado.
|
||||
3. **Reglas duras nuevas en `.claude/rules/`** que el hook `PreToolUse`/`UserPromptSubmit` pueda verificar.
|
||||
|
||||
## Diseno
|
||||
|
||||
### Fase 1 — Bloque nuevo en CLAUDE.md (top del archivo)
|
||||
|
||||
Despues del parrafo "fn-registry" y antes de "Dos bases de datos SQLite", insertar bloque dedicado:
|
||||
|
||||
```markdown
|
||||
## Delegacion + Capability Groups (REGLA DURA)
|
||||
|
||||
Claude **multiplica capacidades** delegando creacion de funciones a `fn-constructor`
|
||||
y reusandolas inmediatamente. NO escribir logica reutilizable inline.
|
||||
|
||||
### Cuando un patron es candidato a funcion
|
||||
|
||||
- Aparece >=2 veces en la sesion actual o en heredocs recientes.
|
||||
- Firma es generica (no depende de tipos internos del artefacto).
|
||||
- Tiene 1 responsabilidad clara (CRUD, parse, transform, http call, etc.).
|
||||
|
||||
### Flujo obligatorio (mismo turno)
|
||||
|
||||
1. **Detectar gap**. Si vas a escribir 5+ lineas de logica reutilizable inline -> STOP.
|
||||
2. **Spawn `fn-constructor` inmediato** via `Agent(subagent_type="fn-constructor", ...)`.
|
||||
- Sin preguntar al usuario.
|
||||
- Si hay >1 funcion independiente -> **una sola llamada al Agent tool con N tool_use blocks paralelos**.
|
||||
3. **Tagear con grupo de capacidad**. Cada funcion nueva lleva al menos UN tag de grupo
|
||||
(ej. `metabase`, `android-emu`, `notebook`, `deploy`, `osint`). Ver `docs/capabilities/`.
|
||||
4. **`fn index`** para registrar.
|
||||
5. **Importar y USAR en el mismo turno** — no dejar funcion huerfana recien creada.
|
||||
6. **Auto-verificar**: `fn doctor uses-functions` y `fn doctor unused` para detectar drift.
|
||||
|
||||
### Anti-patrones (auditables)
|
||||
|
||||
| Anti-patron | Consecuencia | Sustituir por |
|
||||
|---|---|---|
|
||||
| Escribir helper inline en artefacto en vez de delegar | Re-invento por sesion | Spawn fn-constructor |
|
||||
| Crear N funciones serialmente cuando son independientes | Latencia x N | Multiples Agent() en mismo mensaje |
|
||||
| Crear funcion y no usarla en el turno | Funcion huerfana, calls_90d=0 desde dia 1 | Importar + invocar antes de cerrar turno |
|
||||
| Crear funcion sin tag de grupo | Imposible descubrir en bloque la siguiente sesion | Anadir tag de capability group |
|
||||
| Reescribir logica inline en heredoc que ya existe como funcion | Capitalizacion perdida | `mcp__registry__fn_search` antes de escribir |
|
||||
|
||||
### Capability groups
|
||||
|
||||
Cada grupo tiene una pagina madre en `docs/capabilities/<group>.md` con:
|
||||
- Lista de funciones (ID + firma corta).
|
||||
- 1-2 ejemplos canonicos de uso.
|
||||
- Que NO hace el grupo (fronteras).
|
||||
|
||||
Generada/actualizada por `fn doctor capabilities --update`. Grupos vigentes:
|
||||
ver `docs/capabilities/INDEX.md`.
|
||||
|
||||
Cuando Claude entra en una tarea de un dominio conocido, lee `docs/capabilities/<grupo>.md`
|
||||
ANTES de buscar funciones sueltas. Eso desbloquea el grupo entero (no funcion a funcion).
|
||||
```
|
||||
|
||||
### Fase 2 — Comprimir secciones referenciales
|
||||
|
||||
Mover de CLAUDE.md a `docs/`:
|
||||
|
||||
| Seccion actual | Destino |
|
||||
|---|---|
|
||||
| Schema rapido (functions/types/unit_tests/pc_locations + FTS5) | `docs/schema.md` |
|
||||
| CLI completa (`fn index`, `fn ops *`, `fn doctor`, `fn run`, `fn sync`, `fn proposal`) | `docs/cli.md` |
|
||||
| Anadir funciones / Anadir tipos (paso a paso) | `docs/contributing.md` |
|
||||
| Analysis (estructura + crear + usar + helpers) | `docs/analysis.md` |
|
||||
| Bucle reactivo 5 fases (detalle `ExecuteAndReact`) | `docs/reactive_loop.md` |
|
||||
|
||||
CLAUDE.md queda como **mapa** (~150-180 lineas): identidad del repo + top reglas + tres patrones canonicos + punteros al INDEX de rules y a docs/.
|
||||
|
||||
### Fase 3 — Capability groups (tags + docs autogeneradas)
|
||||
|
||||
#### 3.1 Tags canonicos de grupo
|
||||
|
||||
Reservar un namespace de tags para "capability groups". Convencion: tag plano (sin prefijo) coincide con el slug del grupo:
|
||||
|
||||
| Grupo | Tag | Cubre |
|
||||
|---|---|---|
|
||||
| metabase | `metabase` | Cliente HTTP, refresh metadata, dashboards, cards |
|
||||
| android-emu | `android-emu` | adb, emulator, input events, screenshots |
|
||||
| deploy | `deploy` | rsync, systemd, gitea webhook, vps setup |
|
||||
| notebook | `notebook` | jupyter discover/read/exec/write/kernel |
|
||||
| osint | `osint` | gliner/glirel, graph_explorer ingestion |
|
||||
| frontend-ui | `frontend-ui` | @fn_library wrappers Mantine |
|
||||
| sqlite-helpers | `sqlite-helpers` | open/migrate/wal/fts5 |
|
||||
| http-server | `http-server` | router, json response, middleware |
|
||||
| ... | ... | ... |
|
||||
|
||||
`fn doctor capabilities` (nuevo subcomando) audita tags vs grupos declarados en `docs/capabilities/INDEX.md`.
|
||||
|
||||
#### 3.2 Pagina madre por grupo
|
||||
|
||||
`docs/capabilities/<grupo>.md` autogenerable. Ejemplo `docs/capabilities/notebook.md`:
|
||||
|
||||
```markdown
|
||||
# Capability: notebook
|
||||
|
||||
Operar Jupyter Lab colaborativo desde cualquier sesion de Claude.
|
||||
|
||||
## Funciones
|
||||
|
||||
| ID | Firma | Que hace |
|
||||
|---|---|---|
|
||||
| jupyter_discover_py_notebook | `discover(host, port) -> JupyterInfo` | Lista instancias + kernels + sesiones |
|
||||
| jupyter_read_py_notebook | `read(path, cell?) -> list[Cell]` | Lee celdas / metadata |
|
||||
| jupyter_exec_py_notebook | `exec(mode, path, code, cell?)` | append/cell/kernel exec |
|
||||
| jupyter_write_py_notebook | `write(action, path, content, cell?)` | append/insert/edit/delete |
|
||||
| jupyter_kernel_py_notebook | `kernel(action, kernel_id?)` | list/start/restart/shutdown |
|
||||
|
||||
## Ejemplo canonico
|
||||
|
||||
\`\`\`bash
|
||||
PY="python/.venv/bin/python3"
|
||||
$PY python/functions/notebook/jupyter_discover.py --json
|
||||
$PY python/functions/notebook/jupyter_exec.py append notebooks/01.ipynb "df.describe()"
|
||||
\`\`\`
|
||||
|
||||
## Fronteras
|
||||
|
||||
NO incluye: ejecutar notebooks via papermill, scheduling, conversion a HTML.
|
||||
Para eso ver `docs/capabilities/scheduling.md` o crear funcion nueva.
|
||||
```
|
||||
|
||||
#### 3.3 Autogeneracion
|
||||
|
||||
Nuevo pipeline `generate_capability_doc_bash_pipelines <grupo>`:
|
||||
- Query a registry.db: `SELECT id, signature, description FROM functions WHERE tags LIKE '%"<grupo>"%'`
|
||||
- Render template Markdown.
|
||||
- Escribir a `docs/capabilities/<grupo>.md` (preservando seccion "Ejemplo canonico" y "Fronteras" si existen — son curated, no autogenerables).
|
||||
|
||||
`fn doctor capabilities`:
|
||||
- Lista grupos en `docs/capabilities/INDEX.md`.
|
||||
- Para cada grupo, cuenta funciones taggeadas vs documentadas.
|
||||
- Avisa de drift: tag sin pagina, pagina sin funciones, funcion sin grupo.
|
||||
|
||||
### Fase 4 — Reglas duras nuevas en `.claude/rules/`
|
||||
|
||||
Crear `.claude/rules/delegation.md`:
|
||||
|
||||
- "Si vas a escribir >=5 lineas de logica reutilizable inline -> STOP -> spawn fn-constructor"
|
||||
- "Funciones independientes -> Agent paralelo en mismo mensaje"
|
||||
- "Funcion creada -> tag de grupo obligatorio -> usar en el mismo turno -> `fn doctor uses-functions`"
|
||||
|
||||
Crear `.claude/rules/capability_groups.md`:
|
||||
|
||||
- Lista de grupos canonicos.
|
||||
- Como crear grupo nuevo (cuando hay >=3 funciones que comparten dominio + no encajan en grupo existente).
|
||||
- Como leer `docs/capabilities/<grupo>.md` antes de buscar funciones sueltas.
|
||||
|
||||
Actualizar `.claude/rules/INDEX.md` con las dos nuevas filas.
|
||||
|
||||
### Fase 5 — Telemetria (reutiliza issue 0085, NO tabla nueva)
|
||||
|
||||
Anadir vista a `call_monitor`:
|
||||
|
||||
```sql
|
||||
-- Funciones creadas + usadas en la misma sesion (multiplicador real)
|
||||
CREATE VIEW IF NOT EXISTS session_capability_growth AS
|
||||
SELECT
|
||||
w.session_id,
|
||||
w.function_id,
|
||||
w.created_at as first_write,
|
||||
MIN(c.ts) as first_call,
|
||||
COUNT(c.id) as calls_in_session
|
||||
FROM code_writes w
|
||||
LEFT JOIN calls c
|
||||
ON c.function_id = w.function_id
|
||||
AND c.session_id = w.session_id
|
||||
AND c.ts >= w.created_at
|
||||
WHERE w.is_creation = 1
|
||||
GROUP BY w.session_id, w.function_id;
|
||||
```
|
||||
|
||||
Dashboard tab "Capability growth": cuantas funciones creo Claude por sesion, cuantas uso, cuantas quedaron huerfanas.
|
||||
|
||||
Hook `UserPromptSubmit` extiende su contexto con:
|
||||
```
|
||||
CAPABILITY-GROWTH: created_this_session=X used=Y orphan=Z. Si orphan>0 -> usa esas funciones o documenta por que no.
|
||||
```
|
||||
|
||||
## Decisiones tomadas (2026-05-14)
|
||||
|
||||
- **Minimo por grupo**: 3-4 funciones para crear pagina madre. Tags con <3 funciones se mantienen sueltos.
|
||||
- **Ubicacion docs**: `docs/capabilities/` (no `.claude/capabilities/`). Asi `documentar`, `init` y resto de agentes los leen igual que el resto de docs/.
|
||||
- **Auto-tagging masivo**: SI. Pasada inicial sobre todas las funciones existentes (clustering por dominio + signature similarity + co-ocurrencia en `uses_functions`). Las nuevas a partir de hoy llevan tag obligatorio.
|
||||
- **Hook CAPABILITY-GROWTH**: **siempre on**. Cada `UserPromptSubmit` inyecta linea `CAPABILITY-GROWTH: created_this_session=X used=Y orphan=Z`, incluso con valores 0/0/0. Razon: presencia constante crea disciplina, el banner muerto (0/0/0) es por si solo signal — recuerda que el bucle existe.
|
||||
|
||||
## Pasos
|
||||
|
||||
1. **Inventario actual** de tags en registry.db (`SELECT tag, COUNT(*) FROM functions_tags GROUP BY tag`) — identificar candidatos a grupo (tags con >=3 funciones).
|
||||
2. **Auto-tagging masivo retroactivo**: pipeline `auto_tag_capabilities_bash_pipelines` que:
|
||||
- Clusteriza funciones por `domain` + signature embedding + co-ocurrencia en `uses_functions`.
|
||||
- Propone tag de grupo para cada cluster con >=3 funciones.
|
||||
- Aplica via `fn proposal add --kind add_tag` (revision humana en bloque) o directo con `--apply` si confianza >0.9.
|
||||
- Idempotente: re-correr no duplica tags.
|
||||
3. Crear `docs/capabilities/INDEX.md` + 1 pagina piloto (ej. `notebook.md`) a mano para fijar formato.
|
||||
4. Implementar `generate_capability_doc_bash_pipelines` + `fn doctor capabilities` (audita drift tag<->doc).
|
||||
5. Refactor incremental de `.claude/CLAUDE.md` (insertar bloque + comprimir + mover a docs/).
|
||||
6. Crear `.claude/rules/delegation.md` + `capability_groups.md` + actualizar INDEX.
|
||||
7. Anadir vista `session_capability_growth` a call_monitor.
|
||||
8. Extender hook `UserPromptSubmit` con linea CAPABILITY-GROWTH (modo a definir).
|
||||
9. Gate post-creacion: hook `PostToolUse` sobre Agent(subagent_type=fn-constructor) que verifica que la funcion creada lleva al menos un tag de grupo antes de cerrar turno.
|
||||
10. Pilotar 3 grupos: `notebook`, `metabase`, `deploy`. Verificar que Claude (siguiente sesion) entra a `docs/capabilities/metabase.md` antes de buscar `metabase_*` sueltas.
|
||||
|
||||
## Criterio de exito
|
||||
|
||||
- CLAUDE.md baja a <=200 lineas, sin perder informacion (movida a docs/).
|
||||
- 5+ capability groups documentados con pagina madre.
|
||||
- En una sesion piloto, Claude crea >=2 funciones nuevas y las invoca en el mismo turno (vista `session_capability_growth` lo confirma).
|
||||
- `fn doctor capabilities` corre sin warnings tras la migracion inicial.
|
||||
- Reduccion medible de patrones inline repetidos (vista `patterns` del call_monitor) tras 1 semana.
|
||||
|
||||
## Riesgos
|
||||
|
||||
- **Sobre-fragmentar en grupos pequenos**: cada grupo con 2 funciones genera ruido. Minimo 3-4 funciones por grupo para crear pagina madre.
|
||||
- **Tags duplicados / drift**: `metabase` vs `mb` vs `metabase-client`. `fn doctor capabilities` debe avisar y proponer normalizacion.
|
||||
- **Funciones nuevas huerfanas pese a la regla**: si el "usar en mismo turno" es solo aspiracional, la regla muere. La vista de telemetria + hook de UserPromptSubmit son el gate real.
|
||||
- **Refactor de CLAUDE.md rompe punteros existentes**: revisar que `.claude/commands/`, `subagentes.md`, etc. no referencien secciones movidas. Dejar redirects breves.
|
||||
|
||||
## Notas
|
||||
|
||||
- Issue 0085 ya provee `call_monitor` + tablas `calls`, `code_writes`, `patterns`, `violations`. Esta issue NO crea tablas nuevas, solo vistas + reglas + docs.
|
||||
- El sistema de capability groups es analogo a "skills" de Claude Code pero a nivel de funciones del registry — un grupo desbloquea un conjunto de capacidades aprendido del codigo ya existente, no externo.
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
id: "0087"
|
||||
title: "Capability Discovery Acceleration"
|
||||
status: completado
|
||||
type: feature
|
||||
domain:
|
||||
- meta
|
||||
scope: multi-app
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related: []
|
||||
created: 2026-05-17
|
||||
updated: 2026-05-17
|
||||
tags: []
|
||||
---
|
||||
# 0087 — Capability Discovery Acceleration
|
||||
|
||||
**Status:** done
|
||||
**Created:** 2026-05-14
|
||||
**Closed:** 2026-05-14
|
||||
**Related:** 0085 (telemetry), 0086 (delegation + capability groups)
|
||||
|
||||
## Problema
|
||||
|
||||
Crear funciones para reutilizar = bueno largo plazo, pero introduce un **coste de descubrimiento** cada sesion: Claude tiene que FTS-buscar antes de usar. Si la busqueda falla o es mediocre, Claude reinventa inline ("violation: reinvent_inline"). En la sesion del 2026-05-13/14 Claude reinvento taskkill+cp+start 5 veces seguidas pese a que `deploy_cpp_exe_to_windows` ya existia.
|
||||
|
||||
Objetivo: que **conocer las capacidades sea gratis o casi gratis** para Claude.
|
||||
|
||||
## Modelo de capas
|
||||
|
||||
Cinco capas escalonadas por coste de lookup. Cada una cubre un slice de casos:
|
||||
|
||||
| Capa | Coste lookup | Cobertura | Mecanismo |
|
||||
|---|---|---|---|
|
||||
| 1 | 0 (contexto base) | Top 20 funciones mas usadas + funciones creadas ultimos 7d | Bloque auto-generado en `CLAUDE.md` |
|
||||
| 2 | 0 (cada turno) | Funciones recien creadas + violations recientes | Hook `UserPromptSubmit` injecta linea `FRESH:` / `TOP:` |
|
||||
| 3 | 0 (mid-flight) | Patrones inline que matchean funcion existente | Hook `PreToolUse` con fuzzy FTS5 |
|
||||
| 4 | 1 read on demand | Cualquier dominio especifico | `docs/capabilities/<group>.md` cargado solo si tarea matchea |
|
||||
| 5 | 0 (persistente) | Insights cross-session | `MEMORY.md` linea por capacidad clave |
|
||||
|
||||
## Piezas (7)
|
||||
|
||||
| # | Pieza | Lang/Tipo | Aprox LOC | Tanda |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `fn doctor capabilities --emit-claude-md` — top 20 + fresh 7d | Go subcommand | 80 | A |
|
||||
| 2 | Hook `UserPromptSubmit` → linea `FRESH:` + `TOP:` | bash | 40 | B |
|
||||
| 3 | `fn_match` binario fuzzy FTS5 sobre functions (input: command, output: top-N matches) | Go binary | 120 | A |
|
||||
| 4 | Hook `PreToolUse` → consume `fn_match`, inyecta `USE:` hint si confidence alta | bash | 60 | B |
|
||||
| 5 | `ids_naming.md` endurecer + validator en `fn_create_function` | Go + rules | 50 | A |
|
||||
| 6 | `/fn_claude` post-creation auto-append `[[fn_id]] — purpose` a `MEMORY.md` | bash skill | 30 | C |
|
||||
| 7 | Excepcion explicita en `registry_calls.md`: hooks pueden leer `registry.db` directo | rules | — | A |
|
||||
|
||||
### Fuzzy matching (pieza 3 + 4)
|
||||
|
||||
`fn_match`:
|
||||
- **Input:** command string + optional tool context (Bash / Edit / heredoc).
|
||||
- **Pipeline:**
|
||||
1. Tokenizar comando: split en palabras, ignora flags (`-v`, `/F`), conserva tokens significativos.
|
||||
2. Query FTS5 sobre `functions_fts MATCH 'tok1 OR tok2 OR ...'` ordenado por `bm25()`.
|
||||
3. Re-score con weights: `name` match peso x3, `tags` peso x2, `description` peso x1.
|
||||
4. Threshold dinamico: `top.score / second.score > 1.5` → confidence alta.
|
||||
- **Output JSON:** `[{id, score, signature, snippet}, ...]` top 3.
|
||||
- **Latencia objetivo:** < 50ms (hook PreToolUse no puede bloquear).
|
||||
|
||||
Hook `PreToolUse`:
|
||||
- Captura tool + payload.
|
||||
- Llama `fn_match` con timeout 200ms (no bloquea si tarda).
|
||||
- Si confidence alta → inyecta `<system-reminder>FUZZY-MATCH: USE \`./fn run <id>\` instead. Signature: <sig>.</system-reminder>` en stderr (Claude lo lee).
|
||||
- Si confidence baja → silencio.
|
||||
|
||||
### Excepcion hooks (pieza 7)
|
||||
|
||||
`.claude/rules/registry_calls.md` actualmente dice "NUNCA sqlite3 registry.db directo" pero esa regla apunta al AGENTE. Los hooks son lectores externos del proceso de Claude — necesitan acceso directo y rapido a FTS5 sin pasar por MCP (que requiere tool invocation por Claude). Anadir clausula:
|
||||
|
||||
> **Hooks (PreToolUse/PostToolUse/UserPromptSubmit) pueden leer `registry.db` directo via `sqlite3` o binario Go con read-only conn.** No estan sujetos a la regla MCP-first porque no son acciones del agente, son inspeccion automatizada del entorno. Excepcion: SOLO lectura. NUNCA escritura desde hooks.
|
||||
|
||||
## Plan de tandas (paralelizable)
|
||||
|
||||
### Tanda A — cero dependencias, lanzar en paralelo
|
||||
|
||||
- Pieza 1: `fn doctor capabilities --emit-claude-md` (Go).
|
||||
- Pieza 3: `fn_match` binario (Go).
|
||||
- Pieza 5: `ids_naming` validator + endurecer (Go + rules).
|
||||
- Pieza 7: clausula en `registry_calls.md` (rules, instant).
|
||||
|
||||
### Tanda B — depende de tanda A (consume binarios)
|
||||
|
||||
- Pieza 2: hook `UserPromptSubmit` consume `fn doctor capabilities` output.
|
||||
- Pieza 4: hook `PreToolUse` consume `fn_match`.
|
||||
|
||||
### Tanda C — refuerzos
|
||||
|
||||
- Pieza 6: extension de `/fn_claude` skill.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] `fn doctor capabilities --emit-claude-md` imprime bloque markdown valido pegable en CLAUDE.md.
|
||||
- [x] Hook `UserPromptSubmit` añade linea `FRESH: <id1>, <id2>, ...` cada turno (max 5 funciones de los ultimos 7d).
|
||||
- [x] `fn_match "taskkill registry_dashboard.exe"` devuelve `deploy_cpp_exe_to_windows_bash_infra` con score > threshold en < 50ms.
|
||||
- [x] Hook `PreToolUse` con `fn_match` muestra hint `USE: ...` en al menos 3 patrones reproducibles (taskkill, cp Desktop, cmd.exe start).
|
||||
- [x] Validator de `fn_create_function` rechaza nombres no predictibles (sin verbo o sin dominio). — `apps/registry_mcp/naming.go` + unit tests (2026-05-14).
|
||||
- [x] `/fn_claude` tras crear N funciones, MEMORY.md tiene N nuevas lineas `[[id]] — purpose`. — `.claude/scripts/append_fn_to_memory.sh` + step 5b en `.claude/commands/fn_claude.md` (2026-05-14).
|
||||
- [x] Reglas: `registry_calls.md` documenta la excepcion explicita para hooks.
|
||||
|
||||
## Metricas de exito
|
||||
|
||||
Antes / despues, ventana 7d:
|
||||
- `Reg %` (% calls con function_id != '') — esperado +10pp.
|
||||
- `violations` count — esperado -50%.
|
||||
- Tiempo medio entre "patron inline" y "uso de funcion correspondiente" — esperado dia 1 → mismo turno.
|
||||
|
||||
Visibles en Monitor tab del `registry_dashboard`.
|
||||
|
||||
## Notas
|
||||
|
||||
- Capa 3 (fuzzy interceptor) es la mas potente pero requiere **benchmark** de latencia antes de habilitar en cada Bash. Si supera 100ms, degradar a "solo en comandos > N chars" o "solo al final del turno".
|
||||
- No introducir mas reglas: las que hay (`registry_first.md`, `delegation.md`, `registry_calls.md`) son suficientes. Esto es **infraestructura para que se cumplan**.
|
||||
@@ -0,0 +1,152 @@
|
||||
---
|
||||
id: "0096"
|
||||
title: "Estandarizar ubicacion de apps: fuera de carpetas por lenguaje"
|
||||
status: completado
|
||||
type: feature
|
||||
domain:
|
||||
- apps-infra
|
||||
scope: app-scoped
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related: []
|
||||
created: 2026-05-17
|
||||
updated: 2026-05-17
|
||||
tags: []
|
||||
---
|
||||
# 0096 — Estandarizar ubicacion de apps: fuera de carpetas por lenguaje
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-15
|
||||
**Type:** refactor
|
||||
**Priority:** alta
|
||||
**Blocks:** 0097 (data_factory) — no se arranca app nueva mientras la convencion esta rota
|
||||
|
||||
## Problema
|
||||
|
||||
La regla esta documentada (`.claude/rules/apps_vs_functions.md`, memoria `apps_location`): **toda app vive en `apps/` (independiente) o `projects/<p>/apps/` (de proyecto)**. NUNCA en una carpeta nombrada por lenguaje (`cpp/apps/`, `python/apps/`, etc.).
|
||||
|
||||
Violacion actual: `cpp/apps/` contiene 8 apps:
|
||||
|
||||
| Actual | Tipo | Destino propuesto |
|
||||
|---|---|---|
|
||||
| `cpp/apps/altsnap_jitter_test` | test ad-hoc | `apps/altsnap_jitter_test` |
|
||||
| `cpp/apps/chart_demo` | demo standalone | `apps/chart_demo` |
|
||||
| `cpp/apps/dag_engine_ui` | companion `apps/dag_engine` | `apps/dag_engine_ui` |
|
||||
| `cpp/apps/engine_smoke` | smoke runtime | `apps/engine_smoke` |
|
||||
| `cpp/apps/primitives_gallery` | demo componentes registry | `apps/primitives_gallery` |
|
||||
| `cpp/apps/runtime_test` | smoke runtime | `apps/runtime_test` |
|
||||
| `cpp/apps/shaders_lab` | tooling shaders | `apps/shaders_lab` (existe homonimo en `apps/shaders_lab` — VERIFICAR antes) |
|
||||
| `cpp/apps/text_editor_smoke` | smoke editor | `apps/text_editor_smoke` |
|
||||
|
||||
Carpetas `python/apps/`, `bash/apps/`, `frontend/apps/` no existen — convencion solo rota por C++ historicamente.
|
||||
|
||||
## Por que importa
|
||||
|
||||
- **Auto-discovery**: `fn doctor cpp-apps`, `fn doctor artefacts`, indexador, `pc_locations` asumen `apps/` o `projects/<p>/apps/`. Soporte de `cpp/apps/` esparcido por codigo, ramas if-else, paths hardcodeados.
|
||||
- **Sub-repos Gitea**: cada app es `dataforge/<name>` (ADR 0002). Path en disco no afecta al remoto pero la entrada `pc_locations.dir_path` diverge.
|
||||
- **Onboarding**: nueva persona/agente lee la regla, ve `cpp/apps/`, asume que aplica solo a Go/Py. Confusion.
|
||||
- **Scaffolder roto**: `init_cpp_app_bash_pipelines` puede haber generado en `cpp/apps/` historicamente; debe forzar `apps/`.
|
||||
|
||||
## Objetivo
|
||||
|
||||
1. Mover los 8 directorios `cpp/apps/*` -> `apps/*`.
|
||||
2. Actualizar `cpp/CMakeLists.txt` para apuntar a los nuevos paths.
|
||||
3. Actualizar `dir_path` en cada `app.md`.
|
||||
4. `fn index` para refrescar registro.
|
||||
5. `fn sync` para actualizar `pc_locations` en BD remota.
|
||||
6. Modificar scaffolder `init_cpp_app_bash_pipelines` para escribir siempre en `apps/` (o `projects/<p>/apps/` si flag `--project`), nunca en `cpp/apps/`.
|
||||
7. Anadir check `fn doctor artefacts` (o nuevo subcomando `fn doctor app-location`) que falle si encuentra cualquier artefacto bajo carpeta de lenguaje (`cpp/apps`, `python/apps`, `bash/apps`, `frontend/apps`, ademas `cpp/analysis`, etc.).
|
||||
8. Borrar `cpp/apps/` vacio al final.
|
||||
|
||||
## Aceptacion
|
||||
|
||||
- `ls cpp/apps/ 2>/dev/null` devuelve vacio (o el directorio no existe).
|
||||
- `ls apps/` incluye los 8 nuevos.
|
||||
- `cmake --build cpp/build -j` compila todos los targets (mismo binario, distinto path source).
|
||||
- Cada app sigue ejecutandose y pasando su `e2e_checks` (si declarado).
|
||||
- `fn doctor artefacts` y `fn doctor cpp-apps` sin nuevos warnings.
|
||||
- `fn doctor app-location` (nuevo) reporta 0 violaciones.
|
||||
- `mcp__registry__fn_show id="<app>"` devuelve `dir_path: "apps/<app>"` para los 8.
|
||||
- `init_cpp_app_bash_pipelines` con destino default crea en `apps/`, no `cpp/apps/`.
|
||||
- ADR / regla `.claude/rules/cpp_apps.md` actualizada: tabla "Ubicacion" elimina la fila "App independiente | `cpp/apps/<nombre>/`" -> "App independiente | `apps/<nombre>/`".
|
||||
|
||||
## Plan de ejecucion
|
||||
|
||||
Por cada app `<X>` (en orden de dependencia: tests primero, luego demos, luego apps):
|
||||
|
||||
```bash
|
||||
# 1. Verificar que no hay homonimo en apps/
|
||||
test -d apps/<X> && echo "CONFLICT" || true
|
||||
|
||||
# 2. Mover (preserva .git interno del sub-repo)
|
||||
git mv cpp/apps/<X> apps/<X>
|
||||
# o si .git esta dentro y git mv complica: cp -a + rm + commit en sub-repo
|
||||
|
||||
# 3. Editar dir_path en apps/<X>/app.md
|
||||
sed -i 's|dir_path: "cpp/apps/<X>"|dir_path: "apps/<X>"|' apps/<X>/app.md
|
||||
|
||||
# 4. Editar cpp/CMakeLists.txt para usar _DIR pattern como graph_explorer:
|
||||
# set(_<X>_DIR ${CMAKE_SOURCE_DIR}/../apps/<X>)
|
||||
# add_subdirectory(${_<X>_DIR} ${CMAKE_BINARY_DIR}/apps/<X>)
|
||||
|
||||
# 5. Re-build
|
||||
cmake --build cpp/build -j --target <X>
|
||||
|
||||
# 6. Validar binario
|
||||
./cpp/build/apps/<X>/<X> --self-test || ./cpp/build/apps/<X>/<X> --help
|
||||
```
|
||||
|
||||
Una vez los 8 movidos:
|
||||
|
||||
```bash
|
||||
rmdir cpp/apps/ # debe estar vacio
|
||||
./fn index
|
||||
./fn sync
|
||||
./fn doctor app-location # subcomando nuevo
|
||||
```
|
||||
|
||||
### Sub-tareas (recomendado: una rama TBD por bloque)
|
||||
|
||||
| Rama | Apps | Comentario |
|
||||
|---|---|---|
|
||||
| `issue/0096-tests` | altsnap_jitter_test, engine_smoke, runtime_test, text_editor_smoke | smoke tests, riesgo bajo |
|
||||
| `issue/0096-demos` | chart_demo, primitives_gallery | demos |
|
||||
| `issue/0096-tools` | shaders_lab, dag_engine_ui | tooling. shaders_lab CHECK homonimo en `apps/shaders_lab` primero |
|
||||
| `issue/0096-scaffolder` | — | parche a `init_cpp_app_bash_pipelines` + `fn doctor app-location` + regla `.md` |
|
||||
|
||||
Merge `--no-ff` a master tras cada bloque, validar build entre uno y otro.
|
||||
|
||||
## Riesgos
|
||||
|
||||
| Riesgo | Mitigacion |
|
||||
|---|---|
|
||||
| Homonimo en `apps/<X>` ya existente (caso `shaders_lab`) | Verificar `ls apps/<X>` antes de mover. Si existe: decidir merge / rename. |
|
||||
| `git mv` rompe sub-repo interno con su propio `.git/` | El `.git/` viaja con el directorio. Verificar `git -C apps/<X> status` tras mv. Si rompe, cp -a + delete + commit. |
|
||||
| `pc_locations` queda desincronizado en otros PCs | `fn sync` push tras cambios + `/full-git-pull` en otros PCs lo reconcilia. Documentar. |
|
||||
| `cpp/CMakeLists.txt` con paths absolutos sucios | Usar variable `_<X>_DIR` con `${CMAKE_SOURCE_DIR}/../apps/<X>` igual que `graph_explorer`. Convencion ya probada. |
|
||||
| Sub-repo tiene gitignore o config que asume path | Improbable. Verificar tras primer mv. |
|
||||
| Memoria del usuario / claude assumes paths viejos | Actualizar `.claude/rules/cpp_apps.md` + memoria `apps_location` con nota explicita. |
|
||||
|
||||
## No-objetivos
|
||||
|
||||
- Mover `cpp/functions/`, `python/functions/`, `bash/functions/`, `frontend/functions/`. Estos NO son artefactos — son codigo del registry organizado por lenguaje. La regla solo aplica a apps/analysis/vaults/projects.
|
||||
- Mover `cpp/build/`, `cpp/vendor/`, `cpp/framework/`. Son infraestructura compartida del registry C++, no artefactos.
|
||||
- Renombrar apps. Solo se mueve directorio.
|
||||
- Cambiar identidad de sub-repo Gitea (`dataforge/<name>` queda igual).
|
||||
|
||||
## Nueva regla: detector
|
||||
|
||||
Funcion nueva (delegar a fn-constructor): `audit_app_location_go_infra` (puro: scan filesystem). Reglas:
|
||||
|
||||
- Si encuentra `app.md` con `lang: cpp` (o cualquier lang) bajo `cpp/apps/`, `python/apps/`, `bash/apps/`, `frontend/apps/` -> reporta violacion.
|
||||
- Lo mismo para `analysis.md` bajo carpetas de lenguaje.
|
||||
- Wrap en `fn doctor app-location`.
|
||||
|
||||
Add al `fn doctor` agregador.
|
||||
|
||||
## Telemetria objetivo
|
||||
|
||||
- 8 entradas en `pc_locations` actualizadas (`entity_type='app'`, `dir_path` cambia de `cpp/apps/*` a `apps/*`).
|
||||
- `function_stats` de `init_cpp_app_bash_pipelines`: incremento de version (v1.x.0 -> v1.(x+1).0) por el cambio de default path.
|
||||
- `fn doctor app-location` con 0 violaciones tras ejecucion.
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
id: "0101"
|
||||
title: "dev_console Go binario: /issue /flow /work unificados"
|
||||
status: completado
|
||||
type: app
|
||||
domain:
|
||||
- meta
|
||||
scope: registry-only
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related: []
|
||||
created: 2026-05-17
|
||||
updated: 2026-05-17
|
||||
tags: []
|
||||
---
|
||||
# 0101 — dev_console Go binario: /issue /flow /work unificados
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-16
|
||||
**Type:** app
|
||||
**Priority:** alta
|
||||
**Domain:** meta
|
||||
**Scope:** registry-only
|
||||
**Depends:** 0100 (frontmatter migration)
|
||||
**Blocks:** 0102 (work dashboard tab consume `dev_console --json`)
|
||||
**Related:** 0103 (slash commands llaman al binario)
|
||||
|
||||
## Problema
|
||||
|
||||
Issues y flows hoy se gestionan a ojo: `ls dev/issues/`, `grep`, edit manual de tablas en `README.md` / `INDEX.md`. Sin un comando unificado:
|
||||
|
||||
- No hay `/issue list --domain trading --status pendiente`.
|
||||
- No hay `/flow status 0001` que cuente checkboxes + DoD %.
|
||||
- No hay vista cross-cutting "que hacer hoy" mezclando issues + flows.
|
||||
|
||||
Necesitamos un binario unico (`dev_console`) con la misma forma que `fn`: subcomandos consistentes, output texto + `--json`, latencia <200ms.
|
||||
|
||||
## Objetivo v1
|
||||
|
||||
App Go en `apps/dev_console/` con subcomandos:
|
||||
|
||||
### issue
|
||||
|
||||
| Subcomando | Que hace |
|
||||
|---|---|
|
||||
| `dev_console issue list [--domain X] [--type Y] [--status Z] [--prio P] [--epic NNNN]` | tabla filtrable + DoD % |
|
||||
| `dev_console issue show NNNN` | imprime archivo |
|
||||
| `dev_console issue status NNNN` | % acceptance + estado deps (resuelto si todos los `depends` estan `completado`) |
|
||||
| `dev_console issue board` | output TUI o tabla columnas pendiente/in-progress/bloqueado/done |
|
||||
| `dev_console issue dep NNNN` | arbol bloquea/depende navegable |
|
||||
| `dev_console issue roadmap NNNN` | epic + sub-IDs (auto-detecta `NNNNa`, `NNNNb`, ...) |
|
||||
| `dev_console issue tag NNNN +X -Y` | mantenimiento tags |
|
||||
| `dev_console issue done NNNN` | mueve a `completed/`, valida deps, actualiza README |
|
||||
| `dev_console issue stale [--days 30]` | sin update >N dias |
|
||||
| `dev_console issue create <slug> --type T --domain D` | scaffold con frontmatter canonico |
|
||||
|
||||
### flow
|
||||
|
||||
| Subcomando | Que hace |
|
||||
|---|---|
|
||||
| `dev_console flow list [--app X] [--pattern P] [--risk R]` | tabla filtrable + DoD % |
|
||||
| `dev_console flow create <slug>` | scaffold (rechaza si falta DoD user-facing) |
|
||||
| `dev_console flow show NNNN` | imprime archivo |
|
||||
| `dev_console flow status NNNN` | Acceptance % + DoD % separados + checks user-facing destacados |
|
||||
| `dev_console flow dod NNNN` | solo bloque DoD + checklist live |
|
||||
| `dev_console flow trace NNNN` | join `call_monitor.calls` + `data_factory.runs` filtrados por funciones/apps del flow |
|
||||
| `dev_console flow user-test NNNN` | abre superficie usuario declarada en DoD (URL, lanza .exe, abre tab) |
|
||||
| `dev_console flow run NNNN` | fase 2 — ejecuta steps con `function:` |
|
||||
| `dev_console flow chain N M` | declara composicion N -> M |
|
||||
| `dev_console flow done NNNN` | exige DoD 100% (incluyendo user-facing) antes de mover |
|
||||
|
||||
### work (cross-cutting)
|
||||
|
||||
| Subcomando | Que hace |
|
||||
|---|---|
|
||||
| `dev_console work today` | top items prio alta + deps satisfechas (issues + flows) |
|
||||
| `dev_console work weekly` | review semanal: closed vs planeados (lookup en git log + completed/) |
|
||||
| `dev_console work search "texto"` | FTS sobre issues + flows + completed |
|
||||
| `dev_console work dashboard` | imprime JSON consumible por tab Work (issue 0102) |
|
||||
|
||||
## Reglas tecnicas
|
||||
|
||||
- Go + parser YAML (gopkg.in/yaml.v3) + tabwriter. Sin DB propia — fuente de verdad = archivos `.md`.
|
||||
- Cache opcional en `~/.cache/dev_console/index.json` invalidada por mtime.
|
||||
- `--json` en TODOS los subcomandos para consumo por dashboards/agentes.
|
||||
- Latencia objetivo <200ms en lookup, <500ms en list (71 issues + 7 flows).
|
||||
- Build canonico: `CGO_ENABLED=0 go build -tags fts5 -o dev_console .`
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] `dev_console issue list --status pendiente` lista los issues abiertos.
|
||||
- [ ] `dev_console flow status 0001` muestra Acceptance + DoD + user-facing %.
|
||||
- [ ] `dev_console work today` produce lista util (no vacia, no flood).
|
||||
- [ ] `dev_console flow done 0001` rechaza si DoD <100%.
|
||||
- [ ] Tests con fixtures en `apps/dev_console/testdata/`.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
### Generico
|
||||
|
||||
- [ ] **Repetibilidad**: tests verdes 3x; latencia consistente.
|
||||
- [ ] **Observabilidad**: cada invocacion registrada en `call_monitor.calls` (hook PostToolUse Bash detecta `dev_console *`).
|
||||
- [ ] **Error-path**: archivo malformado -> mensaje claro + exit code != 0.
|
||||
- [ ] **Idempotencia**: `done` 2x sobre mismo issue = 0 cambios la segunda.
|
||||
- [ ] **Secrets**: N/A.
|
||||
- [ ] **Docs**: `apps/dev_console/app.md` + `README.md` con ejemplos.
|
||||
- [ ] **Registry-first**: reusa `parse_yaml_frontmatter_*`, `checklist_count_*`, etc.
|
||||
- [ ] **INDEX + status**: issue cerrado.
|
||||
|
||||
### User-facing
|
||||
|
||||
- [ ] **User-facing**: usuario teclea `/issue list` en Claude Code o `dev_console issue list` en terminal y ve tabla limpia con prio/domain/status.
|
||||
- [ ] **User-facing repeat**: comando responde igual cada vez, sub-segundo, sin reset de estado.
|
||||
- [ ] **User-facing onboarding**: `apps/dev_console/app.md` lista comandos canonicos + casos comunes.
|
||||
- [ ] **User-facing latencia**: <500ms p95 para list, <200ms para show.
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
id: "0103"
|
||||
title: "Taxonomia + slash commands /issue /flow /work"
|
||||
status: completado
|
||||
type: feature
|
||||
domain:
|
||||
- meta
|
||||
scope: registry-only
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related: []
|
||||
created: 2026-05-17
|
||||
updated: 2026-05-17
|
||||
tags: []
|
||||
---
|
||||
# 0103 — Taxonomia + slash commands /issue /flow /work
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-16
|
||||
**Type:** feature
|
||||
**Priority:** alta
|
||||
**Domain:** meta
|
||||
**Scope:** registry-only
|
||||
**Depends:** 0100 (frontmatter ya canonico), 0101 (dev_console binary)
|
||||
**Blocks:** 0102 (work dashboard usa los slash desde la tab)
|
||||
**Related:** todos los issues + flows
|
||||
|
||||
## Problema
|
||||
|
||||
Sin taxonomia formal, todo issue/flow se mezcla en un saco. `dev_console` (issue 0101) necesita un schema concreto para filtros: que dominios existen, que tipos son validos, que estados, que scopes. Y los slash commands `/issue *` / `/flow *` / `/work *` necesitan existir como archivos en `.claude/commands/` para que Claude Code los reconozca.
|
||||
|
||||
## Objetivo
|
||||
|
||||
### A) Taxonomia documentada
|
||||
|
||||
Crear `dev/TAXONOMY.md` con la lista canonica:
|
||||
|
||||
**Dominios** (allowlist):
|
||||
```
|
||||
meta, cpp-stack, kanban, trading, gamedev, osint, data-ingest,
|
||||
registry-quality, notify, imagegen, apps-infra, dev-ux, deploy,
|
||||
frontend, mcp, browser, telemetry, docs
|
||||
```
|
||||
|
||||
**Tipos**:
|
||||
```
|
||||
app | feature | bugfix | refactor | chore | docs | spike | epic | infra | planning
|
||||
```
|
||||
|
||||
**Estados**:
|
||||
```
|
||||
pendiente | in-progress | bloqueado | completado | deferred
|
||||
```
|
||||
|
||||
**Scopes**:
|
||||
```
|
||||
registry-only | app-scoped | multi-app | cross-stack
|
||||
```
|
||||
|
||||
**Prioridades**:
|
||||
```
|
||||
alta | media | baja
|
||||
```
|
||||
|
||||
**Flow patterns**:
|
||||
```
|
||||
smoke-cron | prod-data | event-driven | manual-deep | gitops | realtime-loop
|
||||
```
|
||||
|
||||
### B) Slash commands
|
||||
|
||||
Crear `.claude/commands/issue.md`, `flow.md`, `work.md`. Cada uno con frontmatter que define `tool: Bash` + un `command:` que llama a `dev_console <subcomando> "$ARGS"`. Mientras 0101 no este listo: stub que avisa.
|
||||
|
||||
```yaml
|
||||
# .claude/commands/issue.md
|
||||
---
|
||||
description: Gestiona issues del registry (list, show, status, board, done, ...)
|
||||
allowed-tools: [Bash]
|
||||
---
|
||||
|
||||
Usage: /issue <subcomando> [args]
|
||||
|
||||
Subcomandos:
|
||||
- list [--domain X] [--status Y] [--prio P]
|
||||
- show NNNN
|
||||
- status NNNN
|
||||
- board
|
||||
- dep NNNN
|
||||
- roadmap NNNN
|
||||
- tag NNNN +X -Y
|
||||
- done NNNN
|
||||
- stale [--days N]
|
||||
- create <slug> --type T --domain D
|
||||
|
||||
Run:
|
||||
!`./apps/dev_console/dev_console issue $ARGUMENTS`
|
||||
```
|
||||
|
||||
### C) Aplicar tags retroactivos
|
||||
|
||||
Pipeline `tag_existing_issues_bash_pipelines` que, basado en heuristicas (nombre del archivo, contenido), propone `domain` y `scope` para los 71 issues. Output: lista para review humano (no escribe sin confirmacion).
|
||||
|
||||
Heuristicas iniciales:
|
||||
- `cpp-*` -> domain `cpp-stack`
|
||||
- `kanban-*` -> domain `kanban`
|
||||
- `trading-*` -> domain `trading`
|
||||
- `gamedev-*` -> domain `gamedev`
|
||||
- `osint-*`, `odr-*` -> domain `osint`
|
||||
- `cpp-app-*`, `apps-*`, `init-*-app` -> scope `app-scoped`
|
||||
- `roadmap` en el title -> type `epic`
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] `dev/TAXONOMY.md` creado con todas las listas + descripcion 1-frase por valor.
|
||||
- [ ] `.claude/commands/{issue,flow,work}.md` existen y son visibles a Claude Code.
|
||||
- [ ] `fn doctor issues` (subcomando de 0100) valida `domain` y `scope` contra la allowlist.
|
||||
- [ ] Pipeline de tags retroactivos corre + produce reporte.
|
||||
- [ ] >=80% de los 71 issues quedan clasificados sin intervencion humana.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
### Generico
|
||||
|
||||
- [ ] **Repetibilidad**: pipeline + slash commands estables; no varian salida.
|
||||
- [ ] **Observabilidad**: cada slash command pasa por hook PostToolUse -> `call_monitor.calls`.
|
||||
- [ ] **Error-path**: dominio invalido -> error claro + sugerencia ("did you mean ...?").
|
||||
- [ ] **Idempotencia**: pipeline 2x = 0 cambios despues de primera pasada.
|
||||
- [ ] **Secrets**: N/A.
|
||||
- [ ] **Docs**: TAXONOMY referenciado desde `.claude/rules/INDEX.md`.
|
||||
- [ ] **Registry-first**: pipeline reusa parsers existentes.
|
||||
- [ ] **INDEX + status**: issue cerrado.
|
||||
|
||||
### User-facing
|
||||
|
||||
- [ ] **User-facing**: usuario teclea `/issue list --domain trading` en Claude Code y ve los 10 sub-issues del roadmap trading.
|
||||
- [ ] **User-facing repeat**: comandos disponibles desde cualquier sesion, no estado por sesion.
|
||||
- [ ] **User-facing onboarding**: `.claude/commands/issue.md` autodescribe los subcomandos (Claude Code los muestra al tipear `/issue` + tab).
|
||||
- [ ] **User-facing latencia**: <500ms por slash command.
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
id: "0105"
|
||||
title: "Estandarizar bloque service: en app.md + indexer + fn doctor services-spec"
|
||||
status: completado
|
||||
type: feature
|
||||
domain:
|
||||
- meta
|
||||
- apps-infra
|
||||
- deploy
|
||||
- telemetry
|
||||
scope: multi-app
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks:
|
||||
- "0106"
|
||||
related:
|
||||
- "0085"
|
||||
- "0086"
|
||||
created: 2026-05-17
|
||||
updated: 2026-05-17
|
||||
tags: [services, monitoring, frontmatter, indexer, fn-doctor, pc-locations]
|
||||
---
|
||||
|
||||
# 0105 — Estandarizar `service:` en app.md
|
||||
|
||||
## Problema
|
||||
|
||||
Diagnostico (2026-05-17): `sqlite_api` cayo 20h sin alerta. Causa: nadie monitoriza. Causa-de-causa: no hay forma uniforme de saber "esta app DEBE estar corriendo en este PC con este puerto y este health endpoint".
|
||||
|
||||
Hoy:
|
||||
- 10 apps con `tag: service` en `registry.db`.
|
||||
- 8/10 con `systemctl active=inactive` segun `fn doctor services` (algunas porque viven solo en remoto, otras porque genuinamente murieron).
|
||||
- `port` se descubre por `--port` en `ExecStart` de un unit file que puede o no existir local.
|
||||
- `health_endpoint` solo declarado en `deploy_server/operations.db` para 1 target (registry_api).
|
||||
- `systemd_unit` se asume = `<name>.service`, no documentado.
|
||||
- `pc_targets` (en que PCs DEBE correr) no existe en ninguna parte.
|
||||
|
||||
Consecuencia: imposible escribir un monitor que reconcilie "esperado vs real" sin hardcodear cada app.
|
||||
|
||||
## Decision
|
||||
|
||||
Anadir bloque `service:` opcional al frontmatter de `app.md`. Obligatorio para apps con `tag: service`. Indexer parsea y persiste. `fn doctor services-spec` audita.
|
||||
|
||||
## Schema del bloque
|
||||
|
||||
```yaml
|
||||
service:
|
||||
# Endpoints HTTP (opcional — apps stdio/daemon dejan null o omiten)
|
||||
port: 8484
|
||||
health_endpoint: /api/health # ruta GET, 200 == sano
|
||||
health_timeout_s: 3
|
||||
|
||||
# Identidad systemd (cuando aplica)
|
||||
systemd_unit: sqlite_api.service # nombre exacto
|
||||
systemd_scope: user # user|system|null (docker-compose)
|
||||
restart_policy: always # always|on-failure|none
|
||||
|
||||
# Estrategia de runtime (extiende systemd_scope para casos no-systemd)
|
||||
runtime: systemd-user # systemd-user|systemd-system|docker-compose|stdio|manual
|
||||
|
||||
# Donde DEBE correr — referencia pc_locations.pc_id
|
||||
pc_targets:
|
||||
- aurgi-pc
|
||||
- home-wsl
|
||||
|
||||
# Banderas
|
||||
is_local_only: false # true => no se monitoriza por SSH; siempre via mecanismo local
|
||||
```
|
||||
|
||||
Reglas:
|
||||
- `port` null si la app no expone HTTP (stdio MCP, daemons sin API).
|
||||
- `health_endpoint` null si no hay http; monitor cae a check de proceso (systemd active + port listening).
|
||||
- `pc_targets` LISTA de `pc_id` de `pc_locations`. Vacia => no se monitoriza.
|
||||
- `runtime: docker-compose` => monitor chequea contenedores via `docker compose ps` por SSH al PC target.
|
||||
- `is_local_only: true` => monitor solo se ejecuta en el PC donde corre el daemon (no se intenta SSH al propio host).
|
||||
|
||||
## Tareas
|
||||
|
||||
- [x] Auditar 10 services existentes (port real, unit name, descripcion)
|
||||
- [ ] Editar 10 app.md con bloque `service:` realista
|
||||
- [ ] Migration: anadir columnas a tabla `apps` (`port INTEGER`, `health_endpoint TEXT`, `health_timeout_s INTEGER`, `systemd_unit TEXT`, `systemd_scope TEXT`, `restart_policy TEXT`, `runtime TEXT`, `is_local_only INTEGER`)
|
||||
- [ ] Migration: nueva tabla `service_targets (app_id TEXT, pc_id TEXT, role TEXT DEFAULT 'primary', PRIMARY KEY(app_id, pc_id))`
|
||||
- [ ] Indexer: parsear bloque `service:` desde frontmatter y rellenar columnas + `service_targets`
|
||||
- [ ] `fn doctor services-spec` (Go func + subcommand): lista apps con `tag: service` y bloque incompleto. Salida tabwriter + `--json`
|
||||
- [ ] Test: `fn index` sobre fixture con bloque service produce filas correctas
|
||||
- [ ] Fix retroactivo: `~/.config/systemd/user/sqlite_api.service` con `Restart=always` (no `on-failure` — TERM no es failure)
|
||||
|
||||
## Materia: 10 apps actuales
|
||||
|
||||
| App | dir | port | health | unit | scope | pc_targets | runtime |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| sqlite_api | projects/fn_monitoring/apps/sqlite_api | 8484 | /api/status | sqlite_api.service | user | aurgi-pc, home-wsl | systemd-user |
|
||||
| dag_engine | apps/dag_engine | 8090 | /api/dags | dag_engine.service | user | aurgi-pc, home-wsl | systemd-user |
|
||||
| call_monitor | projects/fn_monitoring/apps/call_monitor | null | null | call_monitor.service | user | aurgi-pc, home-wsl | systemd-user |
|
||||
| kanban | apps/kanban | 8095 | /api/board | kanban.service | user | aurgi-pc | systemd-user |
|
||||
| deploy_server | apps/deploy_server | 9090 | /api/health | deploy_server.service | user | aurgi-pc | systemd-user |
|
||||
| registry_mcp | apps/registry_mcp | null | null | registry_mcp.service | user | aurgi-pc | stdio (manual) |
|
||||
| registry_api | apps/registry_api | 8420 | /api/status | null | null | organic-machine.com | docker-compose |
|
||||
| footprint_geo_stack | apps/footprint_geo_stack | 3000 | null | null | null | aurgi-pc | docker-compose |
|
||||
| element_matrix_chat | projects/element_agents/apps/element_matrix_chat | null | null | null | null | organic-machine.com | docker-compose |
|
||||
| agents_and_robots | projects/element_agents/apps/agents_and_robots | null | null | agents_and_robots.service | system | organic-machine.com | systemd-remote |
|
||||
|
||||
## DoD
|
||||
|
||||
- 10 app.md con bloque `service:` valido (parseable, valores reales).
|
||||
- `fn index` puebla `apps.port/...` y `service_targets`.
|
||||
- `fn doctor services-spec` reporta `OK` para los 10.
|
||||
- Migration aplica idempotente en `registry.db` de aurgi-pc + home-wsl.
|
||||
- `services_status_go_infra` extendida para leer datos del nuevo schema (no hardcoded port discovery).
|
||||
|
||||
## Bloquea
|
||||
|
||||
- 0106: app `services_monitor` (UI + backend `services_api`). Necesita `service_targets` + `apps.port`/`health_endpoint` poblados.
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
id: "0109g"
|
||||
title: "skill_tree: panel terminal embebida (claude TUI dentro de la app)"
|
||||
status: deferred
|
||||
type: feature
|
||||
domain:
|
||||
- meta
|
||||
- cpp-stack
|
||||
scope: app-scoped
|
||||
priority: baja
|
||||
depends:
|
||||
- "0109b"
|
||||
blocks: []
|
||||
related:
|
||||
- "0109"
|
||||
created: 2026-05-17
|
||||
updated: 2026-05-17
|
||||
tags:
|
||||
- skill-tree
|
||||
- cpp
|
||||
- imgui
|
||||
- terminal
|
||||
- pty
|
||||
---
|
||||
|
||||
# 0109g — Terminal embebida en skill_tree
|
||||
|
||||
Hoy el boton `Claude fix` lanza terminal externa (Windows Terminal). Funciona pero saca al usuario fuera de la app. Issue: integrar terminal completa dentro de skill_tree como panel.
|
||||
|
||||
## Opciones a evaluar
|
||||
|
||||
### Opcion 1: lib externa ImGui terminal
|
||||
- **ImTerm** (github.com/Optiroc/ImTerm): minimalista, sin PTY. NO sirve para TUI.
|
||||
- **imgui-console**: similar, comandos hardcoded. NO sirve.
|
||||
- **imgui-vt100** o forks: emuladores ANSI dentro de ImGui. Algunos con PTY. Investigar:
|
||||
- github.com/Magenta-Inc/MagentaImGuiTerminal
|
||||
- github.com/microsoft/terminal — ConPTY API + custom UI
|
||||
- github.com/jbrd/imterm — soporta escape sequences basicas
|
||||
- Riesgo: claude usa **alt screen + raw mode + bracketed paste + colores 24-bit + cursor moves**. La mayoria de libs no cubren todo.
|
||||
|
||||
### Opcion 2: PTY + parser ANSI propio
|
||||
- Linux: `openpty()` + fork + exec claude. Buffer texto. Parser ANSI minimo (CSI, SGR, cursor).
|
||||
- Windows: ConPTY (`CreatePseudoConsole`). Similar.
|
||||
- Render ImGui con `AddText` + colores por celda. Soportar redimension via `TIOCSWINSZ`.
|
||||
- Mucho trabajo (~1500 LOC). Resultado limitado (no soporta TUIs complejas tipo less, vim).
|
||||
|
||||
### Opcion 3: Pipe simple (no TUI)
|
||||
- spawn `claude --print "..."` con un prompt y leer stdout en panel scrollable.
|
||||
- NO interactivo. Solo one-shot.
|
||||
- Util para `Generate ideas` (0109h), NO para `Claude fix` interactivo.
|
||||
|
||||
## Recomendacion
|
||||
|
||||
- **Corto plazo**: dejar terminal externa (esta hecha en 0109b3).
|
||||
- **Medio plazo**: hacer Opcion 3 (pipe one-shot) para 0109h.
|
||||
- **Largo plazo**: Opcion 2 (ConPTY/openpty propio) solo si el usuario lo pide explicitamente. Es trabajo de semana y limitara la app a un emulador de calidad mediocre.
|
||||
|
||||
## Investigacion previa (sub-issue 0109g1)
|
||||
|
||||
Antes de implementar, abrir sub-issue 0109g1 que evalua:
|
||||
- ¿Hay alguna lib **imgui-pty** madura en 2026?
|
||||
- ¿La calidad de `imgui-vt100` cubre claude TUI?
|
||||
- ¿Cuanto cuesta ConPTY/openpty propio para soportar al menos: scrollback, colores 24-bit, cursor moves, alt screen, bracketed paste?
|
||||
|
||||
Si la respuesta a (1) o (2) es "si", saltar a implementar. Si la respuesta es "no", el esfuerzo de (3) probablemente no compensa vs la terminal externa.
|
||||
|
||||
## DoD (cuando se implemente)
|
||||
|
||||
- [ ] Panel "Terminal" toggable desde menu View (Ctrl+3).
|
||||
- [ ] Spawn `claude --dangerously-skip-permissions` en cwd `~/fn_registry`.
|
||||
- [ ] Input box manda chars al PTY.
|
||||
- [ ] Output renderizado con colores ANSI minimo.
|
||||
- [ ] Cierre limpio del proceso al cerrar el panel o salir de la app.
|
||||
- [ ] Funciona en Windows nativo (no requiere WSL terminal externa).
|
||||
|
||||
## Anti-DoD
|
||||
|
||||
- NO soporta TUIs muy complejas (less con keyboard, vim) hasta que se justifique con un user need real.
|
||||
@@ -19,7 +19,7 @@ related:
|
||||
- "0102"
|
||||
created: 2026-05-18
|
||||
updated: 2026-05-18
|
||||
tags: [dod, evidence, frontmatter, taxonomy, validator]
|
||||
tags: [dod, evidence, frontmatter, taxonomy, validator, ausente-ready]
|
||||
flow: "0008"
|
||||
---
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ blocks:
|
||||
related: []
|
||||
created: 2026-05-22
|
||||
updated: 2026-05-22
|
||||
tags: [agents_and_robots, http, sse, apikey, traefik, systemd]
|
||||
tags: [agents_and_robots, http, sse, apikey, traefik, systemd, ausente-ready]
|
||||
dod_evidence_schema:
|
||||
- id: build_ok
|
||||
kind: cmd
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
id: "0153"
|
||||
title: "matrix-client-pc agent integration: paneles para rooms operados por agentes"
|
||||
status: deferred
|
||||
priority: medium
|
||||
created: 2026-05-24
|
||||
related_flows: ["0010", "0009"]
|
||||
related_issues: ["0152"]
|
||||
dependencies: ["0152"]
|
||||
tags: [matrix, agents, agents_and_robots, dashboard, sse, device_agent]
|
||||
---
|
||||
|
||||
## Objetivo
|
||||
|
||||
Integracion nativa con `agents_and_robots` + `agents_dashboard` + futuro `device_agent` (flow 0009 mesh). Detectar que un room esta operado por un agente Matrix conocido (via state event custom `m.agent.metadata`) y mostrar panel lateral con info del agente: uptime, ultima ejecucion, cola de tasks, last_error, boton restart, view logs en vivo (SSE). Atajos: enviar slash commands del agente (`/agent restart`, `/agent skill <name>`).
|
||||
|
||||
## Tareas
|
||||
|
||||
1. Backend Go:
|
||||
- `MatrixService.GetAgentMetadata(roomID) -> *AgentMetadata` — lee state event `m.agent.metadata` que el agente publica al arrancar.
|
||||
- `MatrixService.SubscribeAgentLogs(agentID) -> chan LogLine` — SSE proxy al endpoint `agents_and_robots /api/agents/<id>/logs` ya existente (issue 0113).
|
||||
- Llamadas REST proxy a `agents_and_robots`: `RestartAgent(agentID)`, `ListSkills(agentID)`, `TriggerSkill(agentID, skill, args)`.
|
||||
2. Frontend React:
|
||||
- Hook `useAgentMetadata(roomID)` — devuelve `null` si no es room de agente.
|
||||
- Componente `AgentPanel` (panel lateral colapsable, solo visible si hay agentMetadata):
|
||||
- Card con avatar, nombre, version, uptime, status (running/stopped/error).
|
||||
- Tabs: "Logs" (live SSE), "Skills" (lista de skills disponibles + boton trigger), "Config" (read-only del config.yaml del agente).
|
||||
- Boton restart con confirmacion.
|
||||
- Componente `LogStream` — termtinal-like log viewer con auto-scroll + filtro grep.
|
||||
- Slash commands custom: `/agent restart`, `/agent skill <name> <args>`, `/agent logs`.
|
||||
3. Cuando flow 0009 (mesh) este vivo:
|
||||
- Detectar `device_agent` rooms (state event `m.device.metadata` con tipo `device_agent`).
|
||||
- Panel especifico `DevicePanel`: hostname, OS, kernel, IP mesh WG, capabilities firmadas, ultimo heartbeat.
|
||||
- Slash commands: `/device shell <cmd>` (si capability permite), `/device fs ls <path>`, `/device camera capture`.
|
||||
4. Tests:
|
||||
- `e2e/test_agent_panel_basic.sh` — entrar a room de `welcome-bot`, panel agente visible con info correcta.
|
||||
- `e2e/test_agent_logs_live.sh` — boton "view logs" stream logs en tiempo real (5s).
|
||||
- `e2e/test_agent_restart.sh` — restart desde panel + verificar agente vuelve online.
|
||||
|
||||
## Funciones del registry a crear
|
||||
|
||||
- `matrix_agent_metadata_go_infra` — leer/publicar state event `m.agent.metadata`.
|
||||
- `agents_and_robots_client_go_infra` — wrapper REST + SSE del API de `agents_and_robots`.
|
||||
- `AgentPanel_ts_ui` — panel lateral Mantine con tabs.
|
||||
- `LogStream_ts_ui` — viewer logs SSE.
|
||||
- `DevicePanel_ts_ui` — panel device_agent (cuando flow 0009 vivo).
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] Room operado por agente conocido muestra `AgentPanel` automatico.
|
||||
- [ ] Logs en vivo del agente aparecen en panel (SSE).
|
||||
- [ ] Restart desde panel funciona end-to-end.
|
||||
- [ ] Slash `/agent skill greet` ejecuta skill remota y respuesta llega como msg al room.
|
||||
- [ ] Room NO operado por agente: panel oculto (no clutter).
|
||||
|
||||
## Notas
|
||||
|
||||
- State event `m.agent.metadata` format: `{ agent_id, version, capabilities[], owner, repo_url }`. Documentar en `projects/element_agents/docs/agent_metadata.md`.
|
||||
- SSE proxy: el cliente PC habla a `agents_and_robots` via su DNS publica (`agents.organic-machine.com`) con auth Bearer (token del usuario Matrix + scope `agent_panel`).
|
||||
- Permisos: solo el `owner` declarado en el agente puede ejecutar restart/trigger. Otros users del room solo leen.
|
||||
- Gotcha: si el agente se rebuilds y cambia `agent_id`, el state event queda obsoleto — necesita TTL o heartbeat.
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
id: "0164"
|
||||
title: "Bots agents_and_robots: cryptohelper.Init() cuelga al habilitar encryption=true"
|
||||
status: deferred
|
||||
priority: high
|
||||
created: 2026-05-24
|
||||
related_flows: ["0009"]
|
||||
related_issues: ["0144", "0162"]
|
||||
dependencies: ["0162"]
|
||||
tags: [matrix, e2ee, mautrix, cryptohelper, agents, hang, debug]
|
||||
---
|
||||
|
||||
## Objetivo
|
||||
|
||||
Que los agents de `agents_and_robots` (`agent-wsl-lucas`, `agent-windows-lucas`, y futuros) puedan operar con `encryption.enabled=true` en su `config.yaml` y **leer + responder en DMs encrypted** (megolm) con el operator. Hoy todos corren con `enabled=false` para no colgarse; consecuencia: bot puede ENVIAR a room encrypted (cleartext que Element marca como warning) pero NO LEE replies del operator (megolm cifra, bot no descifra) → chat bidireccional roto.
|
||||
|
||||
Bloquea Flow 0009 DoD ("Element → PC interaction working") en el camino encrypted.
|
||||
|
||||
## Contexto
|
||||
|
||||
- mautrix-go v0.21.1 con cryptohelper (tag `goolm` pure-Go).
|
||||
- Synapse en VPS organic-machine.com con MSC3861/MAS activo (issue 0162 done 2026-05-24).
|
||||
- `encryption_enabled_by_default_for_room_type` activo en Synapse → TODA DM nueva nace con `m.megolm.v1.aes-sha2` (no override client-side).
|
||||
- Bots usan password tokens (no application_service). Tokens emitidos pre-migracion siguen validos (verificado: `/account/whoami` OK con bot token post-MAS).
|
||||
- `verify.sh agent-windows-lucas` corrio OK: genero crypto.db, upload cross-signing keys, escribio `SSSS_RECOVERY_KEY_AGENT_WINDOWS_LUCAS` en `.env`.
|
||||
|
||||
## Reproduccion
|
||||
|
||||
```bash
|
||||
# En VPS, agent-windows-lucas:
|
||||
sudo sed -i 's/enabled: false/enabled: true/' agents/agent-windows-lucas/config.yaml
|
||||
sudo systemctl restart agents_and_robots
|
||||
sleep 30
|
||||
# Bot stuck:
|
||||
sudo tail logs/agent-windows-lucas/2026-05-24.jsonl
|
||||
# Last line forever: "initializing e2ee" — runner nunca llega a "starting matrix sync"
|
||||
# /agents API endpoint reports running=false
|
||||
```
|
||||
|
||||
## Diagnostico actual (incompleto)
|
||||
|
||||
SIGQUIT al proceso launcher revelo bots NO-encrypted en `Listener.Run → SyncWithContext` (normal). NO se pudo aislar la stack de **windows-lucas** durante hang — necesita pprof targeted o log adicional dentro de `InitCrypto`.
|
||||
|
||||
Hipotesis (ordenadas):
|
||||
|
||||
| ID | Hipotesis | Evidencia que la apoya | Como confirmar |
|
||||
|---|---|---|---|
|
||||
| H1 | `cryptohelper.Init()` bloquea en primer `/keys/device_signing/upload` por UIA — MAS no acepta el formato auth heredado | MAS recien activo, password_config disabled, mautrix-go usa UIA password flow | inyectar log antes/despues de cada llamada en `cryptohelper.Init` |
|
||||
| H2 | `cryptohelper.Init()` bloquea en `OlmMachine.Load` por `crypto.db` schema mismatch | crypto.db generado por `cmd/verify` puede tener schema distinto al que cryptohelper espera | reset crypto.db + dejar que cryptohelper bootstrap solo (sin verify.sh) |
|
||||
| H3 | El listener trata de hacer initial sync ANTES de e2ee init terminar, deadlock en mutex | "starting matrix sync" NUNCA aparece post-`initializing e2ee` | revisar order en `devagents/runtime.go` |
|
||||
| H4 | Pickle key mismatch entre verify.sh (lo recibe en hex) y runtime (lo decodifica diferente) | Provision-script genero base64; nosotros pusimos hex; runtime acepta hex? | log de pickle key length en runtime |
|
||||
|
||||
## Tareas
|
||||
|
||||
### Fase 1 — Diagnostico
|
||||
|
||||
1.1. Inyectar logging EN `shell/matrix/client.go::InitCrypto` antes/despues de cada paso (cryptohelper construct, Init, OlmMachine.Load, etc) para identificar la linea que bloquea.
|
||||
|
||||
1.2. Reproducir hang en agent test aislado (`agent-e2ee-test`):
|
||||
- Crear bot fresh con provision-agent-user.sh
|
||||
- Activar encryption=true
|
||||
- Restart launcher
|
||||
- Capturar stack
|
||||
|
||||
1.3. Con stack identificado, decidir cual hipotesis (H1-H4) aplica.
|
||||
|
||||
### Fase 2 — Fix segun hipotesis
|
||||
|
||||
- **Si H1 (MAS UIA)**: investigar si mautrix-go v0.21.1 soporta MSC3861 UIA. Si no: bump a v0.22+ que soporta o usar `device_signing/upload` con SSSS-protected path.
|
||||
- **Si H2 (schema mismatch)**: dejar cryptohelper bootstrap solo, NO usar verify.sh primero. Verify.sh queda como "post-bootstrap repair".
|
||||
- **Si H3 (sync deadlock)**: refactor `devagents/runtime.go` para que e2ee init complete antes de spawn listener.
|
||||
- **Si H4 (pickle key)**: arreglar provision-agent-user.sh para generar pickle key como hex.
|
||||
|
||||
### Fase 3 — Validacion (DoD triada)
|
||||
|
||||
#### Mecanica
|
||||
- Bot con `encryption.enabled=true` start OK (running=true en /agents API).
|
||||
- No hang en logs (paso de "initializing e2ee" → "starting matrix sync" en < 30s).
|
||||
- Build limpio `go build -tags goolm`.
|
||||
|
||||
#### Cobertura
|
||||
|
||||
| Escenario | Cmd / evidencia | Resultado |
|
||||
|---|---|---|
|
||||
| Golden: operator envia mensaje encrypted en DM, bot lee + responde encrypted | Element web → `#agent-windows-lucas` DM → "hola" | bot responde en < 15s, log muestra decrypted msg + claude_code_response + encrypted send |
|
||||
| Edge: bot reinicia, crypto.db persiste, re-key OK | `sudo systemctl restart agents_and_robots` mid-conversation | bot continua descifrando mensajes anteriores + nuevos sin re-bootstrap |
|
||||
| Edge: operator reverify device | Element → device list → forget device → re-verify | bot detecta cambio, sigue cifrando OK |
|
||||
| Error: crypto.db corrupto | `rm crypto.db` mid-run | bot detecta + auto-recovery (per `docs/e2ee.md`) + re-bootstrap < 60s |
|
||||
| Error: token revoked | revocar via admin API | bot logout limpio + restart picks up nuevo token |
|
||||
|
||||
#### Vida util validada (7 dias)
|
||||
|
||||
| Metrica | Umbral | Donde | Ventana |
|
||||
|---|---|---|---|
|
||||
| Bot uptime con encryption=true | `> 99%` | `/agents/<id>` API | 7 dias |
|
||||
| Mensajes encrypted leidos | `>= 10` real conversation | `logs/agent-*/...jsonl` decrypted lines | 7 dias |
|
||||
| Crashes cryptohelper | `0` | journalctl `agents_and_robots` | 7 dias |
|
||||
| Latency decrypt msg | `p95 < 2s` | log timestamps | 7 dias |
|
||||
|
||||
### Anti-criterios
|
||||
|
||||
- NO marcar done si bot solo escribe pero no lee.
|
||||
- NO marcar done si hang reaparece tras reinicio del servicio.
|
||||
- NO marcar done si solo funciona en 1 bot (debe replicarse: wsl-lucas + windows-lucas + 1 mas).
|
||||
|
||||
## Estado actual workaround
|
||||
|
||||
- `agent-wsl-lucas`: `encryption.enabled=false`. DM con operator es UNencrypted (probablemente porque fue creada antes de Synapse activar default-encrypt). Funciona bidireccional.
|
||||
- `agent-windows-lucas`: `encryption.enabled=false`. DM con operator (room `!ymFSupZVqYpOWunuHI` o `!qeuqopdkeYHWdAfMaN`) es ENCRYPTED (Synapse forced). Bot envia clear-text → operator ve mensaje + warning. Operator reply encrypted → bot NO lee.
|
||||
|
||||
## Funciones del registry candidatas (post-fix)
|
||||
|
||||
- `mautrix_cryptohelper_init_with_timeout_go_infra` — wrapper con context.WithTimeout para evitar hang infinito.
|
||||
- `agent_e2ee_bootstrap_bash_pipelines` — pipeline: provision agent → set encryption=true → verify.sh → restart + wait healthy.
|
||||
|
||||
## Notas
|
||||
|
||||
**Pickle key format bug**: `provision-agent-user.sh` genera base64 (`openssl rand -base64 32`). `cmd/verify` espera hex. Fix in scope de este issue o nuevo issue (`0165-provision-pickle-key-hex.md`).
|
||||
|
||||
**Subagent investigation report** (2026-05-24) confirmo:
|
||||
- E2EE machinery YA existe end-to-end (InitCrypto, FetchCrossSigningKeys, SignOwnDevice, verify.sh).
|
||||
- docs/e2ee.md cubre failure modes conocidos.
|
||||
- mautrix-go v0.21.1 puede tener bug pre-MSC3861-aware con MAS.
|
||||
|
||||
**Pendiente upstream check**: mautrix-go release notes v0.22+ para MSC3861 support. Si esta soportado, bump version es probablemente el fix.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v0.1.0 (2026-05-24) — issue creado tras reproducir hang post-MAS migration con verify.sh OK pero cryptohelper.Init aun cuelga.
|
||||
@@ -12,7 +12,7 @@ blocks: []
|
||||
related: ["0167", "0168"]
|
||||
created: 2026-05-24
|
||||
updated: 2026-05-24
|
||||
tags: [matrix, livekit, webrtc, turn, nat]
|
||||
tags: [matrix, livekit, webrtc, turn, nat, ausente-ready]
|
||||
---
|
||||
# 0166 — Desplegar TURN para LiveKit (coturn o integrado)
|
||||
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
---
|
||||
id: "0171"
|
||||
title: "Manifest de sub-repos por project + re-clonado y auditoría de cobertura en Gitea"
|
||||
status: completado
|
||||
type: enhancement
|
||||
domain:
|
||||
- registry-quality
|
||||
- infra
|
||||
scope: registry-only
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related: ["0166"]
|
||||
created: 2026-06-10
|
||||
updated: 2026-06-10
|
||||
tags: [projects, subrepo, gitea, clone, backup, manifest, fn-doctor]
|
||||
---
|
||||
|
||||
> **Actualización 10/06/2026 — implementado el núcleo (enfoque KISS).** El manifest
|
||||
> `subrepos.yaml` propuesto abajo se **descartó**: `registry.db` (tablas `apps`/`analysis`
|
||||
> con `project_id`, propagadas entre PCs por `fn sync`) **ya es** el manifest de sub-repos, y
|
||||
> `clone_project_subrepos_bash_pipelines` ya lo consume. No hace falta un archivo nuevo. Lo que
|
||||
> faltaba era integración + auditoría. Ver `## Estado de implementación` al final.
|
||||
# 0171 — Manifest de sub-repos por project + re-clonado y auditoría de cobertura en Gitea
|
||||
|
||||
## APP Metadata
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | 0171 |
|
||||
| **Estado** | pendiente |
|
||||
| **Prioridad** | alta (riesgo de pérdida de datos) |
|
||||
| **Tipo** | enhancement — metadata de projects + `/full-git-pull` + `fn doctor` |
|
||||
|
||||
## Contexto
|
||||
|
||||
El 10/06/2026, al preparar un dashboard sobre el project `aurgi`, se descubrió que el project
|
||||
paraguas **no existía en Gitea** (`dataforge/aurgi` → 404). Sus 3 analyses sí estaban a salvo como
|
||||
sub-repos independientes (`dataforge/venta_web`, `dataforge/sale_prices_comprobation`,
|
||||
`dataforge/presupuestos_callcenter`), pero **el `project.md`, `vault.yaml` y `CONVENTIONS.md` de
|
||||
nivel-project no estaban versionados en ningún sitio**. Reconstruir el project obligó a *adivinar*
|
||||
los nombres de los sub-repos hijos uno a uno desde la lista completa de repos de Gitea.
|
||||
|
||||
Una auditoría de cobertura `projects ↔ Gitea` confirmó el agujero:
|
||||
|
||||
| Project | Repo Gitea | Riesgo |
|
||||
|---|---|---|
|
||||
| fleet_monitoring, fn_monitoring, message_bus, web_scraping | ✅ | ninguno |
|
||||
| **obsidian**, **osint** | ❌ (solo en disco local) | alto — resuelto en esta sesión (subidos a `dataforge/obsidian`, `dataforge/osint`) |
|
||||
| **aurgi** | ❌ (404, paraguas inexistente) | pendiente — analyses salvados, docs nivel-project no |
|
||||
|
||||
Dos problemas estructurales quedan abiertos:
|
||||
|
||||
1. **Projects sin repo Gitea**: su contenido de nivel-project vive solo en disco. Si se borra el
|
||||
disco (o el project no se sincroniza a otro PC), se pierde. La regla `projects.md` dice que cada
|
||||
project debe ser su propio repo Gitea, pero no hay nada que lo **verifique ni lo fuerce**.
|
||||
|
||||
2. **Sub-repos hijos no referenciados**: el `.gitignore` de cada project excluye `apps/*/` y
|
||||
`analysis/*/` (son sub-repos independientes). Por tanto, **un clon fresco del project NO trae sus
|
||||
hijos**, y no existe ningún manifest que diga *qué hijos clonar*. Hoy `/full-git-pull` solo
|
||||
descubre repos vía `discover_git_repos_bash_infra` (busca `.git` ya presentes en disco): si el
|
||||
hijo nunca se clonó, es invisible. Resultado: para reconstruir un project en una máquina nueva hay
|
||||
que adivinar sus sub-repos (exactamente lo que pasó con aurgi).
|
||||
|
||||
## Objetivo
|
||||
|
||||
Que **todo project** (a) tenga su repo Gitea garantizado y (b) **referencie declarativamente sus
|
||||
sub-repos hijos** (apps + analyses), de modo que clonar el project en cualquier PC permita
|
||||
re-clonar automáticamente todo su árbol sin adivinar nada.
|
||||
|
||||
## Propuesta
|
||||
|
||||
### 1. Manifest de sub-repos por project
|
||||
|
||||
Añadir a cada project un manifest declarativo de sus hijos. Dos opciones de formato (decidir una):
|
||||
|
||||
- **Opción A (KISS, preferida): `subrepos.yaml`** en la raíz del project, análogo a `vault.yaml`:
|
||||
|
||||
```yaml
|
||||
# projects/<p>/subrepos.yaml — sub-repos hijos de este project (apps + analyses)
|
||||
subrepos:
|
||||
- kind: analysis # app | analysis
|
||||
name: venta_web
|
||||
path: analysis/venta_web
|
||||
repo: dataforge/venta_web
|
||||
url: https://gitea-.../dataforge/venta_web
|
||||
- kind: analysis
|
||||
name: sale_prices_comprobation
|
||||
path: analysis/sale_prices_comprobation
|
||||
repo: dataforge/sale_prices_comprobation
|
||||
url: https://gitea-.../dataforge/sale_prices_comprobation
|
||||
```
|
||||
|
||||
- **Opción B: sección `## Sub-repos`** en `project.md` con una tabla `kind | name | path | url`.
|
||||
|
||||
`subrepos.yaml` (Opción A) es más fácil de parsear por las funciones de git y se versiona con el
|
||||
project (no está en el `.gitignore`). El manifest se **autogenera/actualiza** escaneando los `.git`
|
||||
hijos presentes en disco + su `remote get-url origin` (reusar `discover_git_repos_bash_infra`).
|
||||
|
||||
### 2. Generación y mantenimiento del manifest
|
||||
|
||||
Función/pipeline nueva (delegar a `fn-constructor`, grupo `infra`/git) que, dado un project:
|
||||
- Escanea `apps/*/.git` y `analysis/*/.git`, lee su remote origin.
|
||||
- Escribe/actualiza `subrepos.yaml`.
|
||||
- Idempotente. Se invoca dentro de `/full-git-push` (o `fn index`) para mantener el manifest al día.
|
||||
|
||||
### 3. Re-clonado desde el manifest en `/full-git-pull`
|
||||
|
||||
Extender `/full-git-pull` para que, tras actualizar cada project, lea su `subrepos.yaml` y **clone
|
||||
los hijos que falten** (`url` → `path`). Así, en un PC nuevo: clonar `dataforge/<project>` →
|
||||
`/full-git-pull` → reconstruye apps + analyses automáticamente. Requiere una función
|
||||
`clone_missing_subrepos_bash_infra(project_dir)` (delegar a `fn-constructor`).
|
||||
|
||||
### 4. Garantizar repo Gitea de cada project + auditoría en `fn doctor`
|
||||
|
||||
- Subcomando nuevo `fn doctor projects` (función `audit_projects_coverage_go_infra`): por cada
|
||||
project en disco reporta `repo_gitea` (existe en Gitea sí/no), `repo_url` (declarado en project.md
|
||||
sí/no), y `subrepos_manifest` (presente + cuántos hijos en disco sin entrada / en manifest sin
|
||||
clonar). Salida `--json`. Cero hallazgos = sano.
|
||||
- Acción derivada documentada: `repo_gitea=no` → `ensure_repo_synced_bash_infra projects/<p>
|
||||
dataforge <p> master "init: project <p>"`.
|
||||
|
||||
### 5. Backfill inicial
|
||||
|
||||
- `aurgi`: traer su `project.md` / `vault.yaml` / `CONVENTIONS.md` de `aurgi-pc` (o `home-wsl`) y
|
||||
crear `dataforge/aurgi` + `subrepos.yaml` con los 3 analyses ya conocidos. **No** reconstruir a
|
||||
mano un `project.md` mínimo (divergiría del real).
|
||||
- Resto de projects con hijos (`fleet_monitoring`, `fn_monitoring`, `message_bus`, `web_scraping`):
|
||||
generar su `subrepos.yaml` con la función del punto 2.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|
||||
|---|---|---|---|
|
||||
| Golden: clon fresco reconstruye árbol | e2e | clonar `dataforge/<p>` en dir limpio → `/full-git-pull` | apps + analyses del project re-clonados desde `subrepos.yaml` |
|
||||
| Edge: project sin hijos (obsidian) | e2e | generar manifest | `subrepos.yaml` válido y vacío (o ausente), sin error |
|
||||
| Edge: hijo en disco sin `.git` | unit | auditoría | `fn doctor projects` lo reporta como "hijo sin sub-repo" |
|
||||
| Error: project sin repo Gitea | e2e | `fn doctor projects --json` | lo marca `repo_gitea=false`, sugiere `ensure_repo_synced` |
|
||||
| Cobertura | audit | `fn doctor projects` | 0 projects sin repo, 0 hijos sin referenciar |
|
||||
|
||||
## Decisiones abiertas
|
||||
|
||||
1. **Formato del manifest**: `subrepos.yaml` (A) vs. sección en `project.md` (B). Recomendado A.
|
||||
2. **¿Auto-generar el manifest en `fn index`** o solo en `/full-git-push`? (evitar I/O de red en
|
||||
`fn index`; preferible en push).
|
||||
3. **aurgi**: ¿traer de `aurgi-pc` por SSH ahora, o dejarlo para cuando el project se sincronice?
|
||||
|
||||
## Notas
|
||||
|
||||
En esta sesión ya se resolvió el riesgo inmediato: `obsidian` y `osint` se subieron a Gitea
|
||||
(`dataforge/obsidian`, `dataforge/osint`) con `ensure_repo_synced_bash_infra` y se les añadió
|
||||
`repo_url` en su `project.md`. Este issue cubre la solución **estructural y reutilizable** para que
|
||||
el caso no vuelva a ocurrir con ningún project. Relacionado con #0166 (dependencias app→app para
|
||||
build reproducible): ambos persiguen que clonar el ecosistema en un PC nuevo sea determinista.
|
||||
|
||||
## Estado de implementación (10/06/2026)
|
||||
|
||||
Implementado con enfoque KISS, **sin** `subrepos.yaml` (registry.db + `fn sync` ya cumplen esa
|
||||
función). Cambios:
|
||||
|
||||
**Funciones nuevas:**
|
||||
- `ensure_project_gitignore_bash_infra` — garantiza idempotente el `.gitignore` canónico de un
|
||||
project (`apps/*/`, `analysis/*/`, `vaults/*` + excepciones) antes de cualquier `git add -A`,
|
||||
para no trackear el contenido de los sub-repos hijos.
|
||||
- `audit_projects_coverage_go_infra` (+ `FormatProjectsCoverage`) — motor de `fn doctor projects`.
|
||||
Reporta por project: `git`/`remote`/`repo_url`/`children (cloned/inDB)` + issues
|
||||
(`no_gitea_repo`, `children_missing`, `dir_not_found`). Solo git local + registry.db, sin red.
|
||||
|
||||
**Integraciones:**
|
||||
- `full_git_push` v1.1.0 — paso 1c: auto-inicializa y pushea los **projects paraguas** sin repo
|
||||
(antes solo apps/analyses), asegurando el `.gitignore` canónico primero. Cierra el agujero
|
||||
aurgi/obsidian/osint.
|
||||
- `full_git_pull` v1.1.0 — paso 6: tras `fn sync`, reclona los sub-repos hijos faltantes de cada
|
||||
project con `clone_project_subrepos` + re-index. Clonar el paraguas + `/full-git-pull` reconstruye
|
||||
el árbol entero.
|
||||
- `fn doctor projects` — nuevo subcomando (`cmd/fn/doctor.go`). Hoy reporta **0 projects con
|
||||
problemas**.
|
||||
|
||||
**Hecho aparte (riesgo inmediato):** `dataforge/obsidian` + `dataforge/osint` creados, `repo_url`
|
||||
en sus `project.md`.
|
||||
|
||||
### Pendientes (no bloquean el núcleo)
|
||||
|
||||
1. **Check inverso — HECHO (10/06/2026).** `FindOrphanProjectRefs` + `FormatOrphanProjectRefs` en
|
||||
`audit_projects_coverage_go_infra`, enchufado en `fn doctor projects`. Detecta apps/analysis con
|
||||
`project_id` sin fila en `projects`. Hoy reporta 4 paraguas huérfanos (existen en otro PC, nunca
|
||||
subidos a Gitea — mismo caso que aurgi):
|
||||
- `element_agents` (6 apps: agents_and_robots, agents_dashboard, device_agent, element_matrix_chat,
|
||||
matrix_admin_panel, matrix_client_pc)
|
||||
- `imagegen` (image_to_3d_studio)
|
||||
- `osint_graph` (graph_explorer)
|
||||
- `aurgi` (sus analyses sí están en Gitea; el paraguas no)
|
||||
2. **Fix de datos de los 4 paraguas huérfanos — pendiente, requiere el PC origen.** No están en disco
|
||||
ni en Gitea en este PC (`lucas-linux`), así que no se pueden reconstruir aquí sin inventar. El fix
|
||||
correcto: correr `/full-git-push` en el PC donde cada paraguas existe en disco (`aurgi-pc` /
|
||||
`home-wsl`). Con `full_git_push` v1.1.0 (paso 1c) eso ya los crea en Gitea automáticamente. Tras
|
||||
eso, `/full-git-pull` aquí (paso 6) los traerá. NO reconstruir un `project.md` mínimo a mano.
|
||||
3. **DoD vida útil**: validar el reclonado en un PC nuevo real (clon limpio del paraguas →
|
||||
`/full-git-pull` → árbol reconstruido) antes de declarar el issue cerrado.
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
id: "0173"
|
||||
title: "EDA: bugs críticos de correctitud estadística (outlier_pct ×100, distribution_type por-skew)"
|
||||
status: completado
|
||||
type: bugfix
|
||||
domain:
|
||||
- registry-quality
|
||||
scope: registry-only
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related: ["0174", "0175", "0176", "0177", "0068"]
|
||||
created: 2026-06-29
|
||||
updated: 2026-06-29
|
||||
tags: [eda, datascience, profile_table, render_eda_markdown, describe_numeric, benchmark]
|
||||
---
|
||||
# 0173 — EDA: bugs críticos de correctitud estadística
|
||||
|
||||
## Contexto
|
||||
|
||||
Un benchmark adversarial del workflow `/eda` sobre 12 datasets reales (29/06/2026,
|
||||
`temp/eda_benchmark/EVALUATION.md`) detectó que los estadísticos descriptivos base son
|
||||
correctos, pero el **porcentaje de outliers que el report markdown muestra es imposible**
|
||||
(supera el 100%, hasta 336%), engañando a un lector no experto con apariencia de autoridad.
|
||||
|
||||
Hallazgos cubiertos por este issue:
|
||||
|
||||
| Hallazgo | Severidad | Evidencia del benchmark |
|
||||
|---|---|---|
|
||||
| H1 — `outlier_pct` por-columna >100% en el report markdown | crítico | wine-red `chlorides` 193.87%, `density` 112.57% (skew 0.07); titanic `SibSp` 336.70%, `Fare` 224.47%; seattle `precipitation` 253.25% |
|
||||
| H11 — `distribution_type` por-skew etiqueta mal discretas/ordinales/multimodales | bajo | wine `quality` (6 valores) → "normal-ish"; precios BTC multimodales → "normal-ish" (skew 0.45) |
|
||||
|
||||
### Causa raíz de H1 (verificada en código, READ-ONLY)
|
||||
|
||||
`EVALUATION.md` propuso "corregir la fórmula en `describe_numeric`". **Eso es incorrecto.** Al
|
||||
leer el código:
|
||||
|
||||
- `python/functions/datascience/describe_numeric.py:113` calcula
|
||||
`outlier_pct = 100.0 * n_outliers / n` — ya en escala 0-100 y acotado a [0,100]. **Está bien.**
|
||||
- `python/functions/datascience/render_eda_markdown.py:203-204` renderiza ese valor con
|
||||
`_fmt_pct(val)`, y `_fmt_pct` (líneas 31-44) hace `num * 100` porque **asume que su input es
|
||||
una fracción 0-1**. Resultado: **doble ×100** (un 1.94 real se muestra como 193.87%).
|
||||
- El PDF (`render_eda_pdf.py:296`) usa `_fmt_num(outlier_pct, 1) + "%"` sin multiplicar — por eso
|
||||
el PDF muestra el outlier_pct correcto y el markdown no. El bug es **exclusivo del renderer
|
||||
markdown**.
|
||||
|
||||
El factor "19-40×" que observó el evaluador se debe a que comparaba contra outliers IQR (3-10%),
|
||||
mientras `describe_numeric` usa z-score (umbral 3.0, da menos outliers); pero el mecanismo del bug
|
||||
es el doble ×100, no la fórmula.
|
||||
|
||||
## Tareas
|
||||
|
||||
1. **H1 (fix de 1 línea):** en `python/functions/datascience/render_eda_markdown.py:203-204`,
|
||||
sustituir `_fmt_pct(val)` por un formateo que NO multiplique (p.ej. `f"{_fmt_num(val, 2)}%"`),
|
||||
porque `numeric.outlier_pct` ya viene en escala 0-100. **No tocar** `describe_numeric.py` (su
|
||||
fórmula es correcta).
|
||||
2. Auditar el resto de `render_eda_markdown.py` por si otro campo en escala 0-100 pasa por
|
||||
`_fmt_pct` (los `*_pct` del perfil base sí son fracciones 0-1 y deben seguir con `_fmt_pct`;
|
||||
solo `numeric.outlier_pct` está en escala 0-100). Documentar en el docstring de `describe_numeric`
|
||||
que `outlier_pct` está en 0-100 para evitar la confusión a futuro.
|
||||
3. **H11:** en `python/functions/datascience/detect_distribution_type.py`, no etiquetar por skew
|
||||
solamente: usar también nº de modos / cardinalidad y, cuando esté disponible, el test de
|
||||
normalidad Jarque-Bera (`normality_tests.py`, ya expuesto en `models.normality` vía
|
||||
`run_eda_models`). Una variable discreta/ordinal/multimodal no debe salir "normal-ish".
|
||||
4. Añadir/extender tests unitarios: `describe_numeric_test.py` (outlier_pct en [0,100]),
|
||||
`render_eda_markdown_test.py` (un perfil con `outlier_pct=7.0` renderiza `"7.00%"`, no `"700%"`),
|
||||
y un test de `detect_distribution_type` (discreta de 6 valores no se etiqueta "normal-ish"). Nota:
|
||||
hoy NO existe `detect_distribution_type_test.py` en `python/functions/datascience/` — hay que
|
||||
crearlo (a confirmar el nombre canónico al implementar; el resto de tests citados sí existen).
|
||||
|
||||
## Definition of Done
|
||||
|
||||
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|
||||
|---|---|---|---|
|
||||
| Golden: outlier_pct en rango | e2e | re-correr `profile_table` sobre `temp/eda_benchmark/datasets/.../wine-red` y leer el `.md` | `chlorides`/`density` muestran `outlier_pct` en [0,100]% (no 193.87% / 112.57%) |
|
||||
| Edge: skew alto real | unit | `describe_numeric_test.py` con datos de cola fuerte | `outlier_pct` ≤ 100 y coherente con n_outliers/n |
|
||||
| Edge: discreta ordinal | unit | `detect_distribution_type_test.py` con 6 valores discretos | NO etiqueta "normal-ish" |
|
||||
| Error: input vacío/no numérico | unit | `describe_numeric([])` | claves None, sin crash (contrato actual preservado) |
|
||||
| Mecánica | — | `./fn run describe_numeric_py_datascience`, `./fn run render_eda_markdown_py_datascience` | tests verdes; `fn index` limpio |
|
||||
|
||||
Re-correr el benchmark sobre wine-red y titanic y confirmar que ningún `outlier_pct` supera 100%.
|
||||
|
||||
## Notas
|
||||
|
||||
Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md` (consolidación del benchmark). H1 es el fix de
|
||||
mayor ratio impacto/esfuerzo del lote (una línea elimina los números imposibles que más minan la
|
||||
confianza del report). Hermanos: 0174 (series), 0175 (relational), 0176 (render), 0177 (tipos).
|
||||
|
||||
|
||||
## Resolucion (2026-06-29, sesion /ausente)
|
||||
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: caf8c25d. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
id: "0174"
|
||||
title: "EDA series temporales: período estacional roto + correlación de niveles + to_returns ciego"
|
||||
status: completado
|
||||
type: bugfix
|
||||
domain:
|
||||
- registry-quality
|
||||
scope: registry-only
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related: ["0173", "0175", "0176", "0177"]
|
||||
created: 2026-06-29
|
||||
updated: 2026-06-29
|
||||
tags: [eda, datascience, stl_decompose, profile_table, to_returns, series, benchmark]
|
||||
---
|
||||
# 0174 — EDA series temporales: período estacional + correlación de niveles
|
||||
|
||||
## Contexto
|
||||
|
||||
El benchmark `/eda` (29/06/2026, `temp/eda_benchmark/EVALUATION.md`) confirmó que la
|
||||
estacionariedad (ADF+KPSS), la autocorrelación (Ljung-Box) y el aviso de espuriedad
|
||||
Granger-Newbold están **bien** (verificados a mano con `statsmodels`). Pero el **detector de
|
||||
período estacional está roto**, lo que produce falsos negativos de estacionalidad, y la
|
||||
correlación de precios se calcula sobre niveles (espuria para uso financiero).
|
||||
|
||||
Hallazgos cubiertos:
|
||||
|
||||
| Hallazgo | Severidad | Evidencia del benchmark |
|
||||
|---|---|---|
|
||||
| H2 — período estacional sale `2` casi siempre → `seasonal_strength=0` | crítico | seattle `temp_max` reporta "sin estacionalidad" (`period=2`); STL real con `period=365` da fuerza estacional **0.843**. UNRATE (mensual) debería usar 12, no 2 |
|
||||
| H8 — correlación de precios sobre niveles marcada `sig=sí` | medio-alto | aapl/btc `Close–Open=0.998 sig=sí`: espuria por construcción (niveles autocorrelados no estacionarios) |
|
||||
| H13 — `to_returns` sugerido ciegamente a temperatura (sin sentido físico) | bajo | seattle `temp_max`: "convertir a retornos"; debería ser "diferencias" |
|
||||
|
||||
### Causa raíz H2 (verificada en código, READ-ONLY)
|
||||
|
||||
`python/functions/datascience/stl_decompose.py:34-58` (`_infer_period`) busca el lag entre 2 y
|
||||
`max_period` que maximiza la autocorrelación **cruda** de la serie. En cualquier serie con
|
||||
tendencia (precios, temperatura), la autocorrelación decae monótonamente desde el lag mínimo, así
|
||||
que **el lag 2 casi siempre gana** → `period=2` espurio y un STL con componente estacional que es
|
||||
ruido (`seasonal_strength≈0`). Además, `python/functions/pipelines/profile_table.py:175`
|
||||
(`_build_series_block`) llama `stl_decompose(series_vals)` **sin pasar el período**, pese a que el
|
||||
pipeline ya conoce la columna de orden temporal (`order_col`) y podría derivar la frecuencia.
|
||||
|
||||
## Tareas
|
||||
|
||||
1. **H2 — arreglar la inferencia de período** en `stl_decompose.py:34-58`. Opciones (preferir la
|
||||
robusta): (a) detrend antes de autocorrelar; (b) buscar picos en el periodograma/FFT en vez del
|
||||
primer lag; (c) **derivar el período de la frecuencia del índice datetime** (mensual→12,
|
||||
diario→7 y/o 365) — la señal más fiable.
|
||||
2. **H2 — pasar el período desde el pipeline:** en `profile_table.py:_build_series_block`, cuando
|
||||
exista `order_col` datetime, inferir la frecuencia del índice y pasar `period=` explícito a
|
||||
`stl_decompose`. Si no se puede determinar un período fiable, que `stl_decompose` **no reporte
|
||||
`seasonal_strength=0`** como conclusión: devolver `note` "período no determinado" (ya hay una
|
||||
rama así en `:139-145`; extenderla a los casos que hoy caen en `period=2`).
|
||||
3. **H8 — correlación sobre retornos para series no estacionarias:** en la sección de correlaciones
|
||||
de `profile_table.py:346-384`, cuando una columna sea una serie no estacionaria de niveles
|
||||
(verdict `non_stationary`/`inconclusive`, ya detectado), correlacionar sobre retornos/diferencias
|
||||
(`to_returns`, ya importado) o marcar esos pares de niveles como "posible espuria" junto a la
|
||||
tabla. El aviso global existe pero está lejos de los números.
|
||||
4. **H13 — retornos vs diferencias por semántica:** en `profile_table.py:189` / `to_returns.py`,
|
||||
elegir "retornos" (financiero, estrictamente positivo multiplicativo) vs "diferencias" (físico,
|
||||
aditivo) según la naturaleza, o usar "diferencias" por defecto cuando no haya señal financiera.
|
||||
5. Tests: `stl_decompose_test.py` (serie sintética mensual con estacionalidad anual → período
|
||||
correcto y `seasonal_strength` alta; serie con tendencia sin estacionalidad → nota, no
|
||||
`period=2`); cobertura de `_build_series_block` con `order_col` datetime.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|
||||
|---|---|---|---|
|
||||
| Golden: estacionalidad anual | e2e | re-correr `profile_table` con `run_series=True` sobre seattle `temp_max` | `seasonal_strength ≈ 0.84` con período ≈ 365 (NO "sin estacionalidad", NO `period=2`) |
|
||||
| Edge: serie mensual | unit | `stl_decompose_test.py` serie mensual sintética con ciclo 12 | período inferido 12 y fuerza estacional alta |
|
||||
| Edge: sin estacionalidad | unit | `stl_decompose_test.py` serie con solo tendencia | `note` "período no determinado", NO `seasonal_strength=0` como conclusión |
|
||||
| Error: serie corta | unit | `stl_decompose([...]<2*period)` | nota "serie corta", sin crash (contrato actual) |
|
||||
| H8 | e2e | re-correr `profile_table` sobre aapl/btc | pares de niveles no estacionarios marcados como posible espuria o correlación sobre retornos |
|
||||
| Mecánica | — | `./fn run stl_decompose_py_datascience`; `fn index` | tests verdes; índice limpio |
|
||||
|
||||
Re-correr el benchmark sobre seattle, fred-unrate, aapl y btc y confirmar que la estacionalidad se
|
||||
detecta donde existe y no se inventa donde no.
|
||||
|
||||
## Notas
|
||||
|
||||
Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md`. H2 es el segundo bloqueante de fiabilidad: un
|
||||
"sin estacionalidad" donde la hay es un falso negativo que un decisor creería. La estacionariedad ya
|
||||
funciona — no tocarla. Hermanos: 0173, 0175, 0176, 0177.
|
||||
|
||||
|
||||
## Resolucion (2026-06-29, sesion /ausente)
|
||||
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: e142ef02. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
id: "0175"
|
||||
title: "EDA relational: precisión de FK inference (falsos positivos) + filtrar VIEWs + test ATTACH"
|
||||
status: completado
|
||||
type: bugfix
|
||||
domain:
|
||||
- registry-quality
|
||||
scope: registry-only
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related: ["0173", "0174", "0176", "0177"]
|
||||
created: 2026-06-29
|
||||
updated: 2026-06-29
|
||||
tags: [eda, datascience, infer_fk_containment_duckdb, build_join_graph, profile_database, duckdb, benchmark]
|
||||
---
|
||||
# 0175 — EDA relational: precisión de FK inference + filtrar VIEWs
|
||||
|
||||
## Contexto
|
||||
|
||||
El benchmark `/eda` (29/06/2026, `temp/eda_benchmark/EVALUATION.md`) confirmó que la inferencia de
|
||||
claves foráneas a nivel de base es **inútil por falsos positivos masivos** y que las VISTAS se
|
||||
perfilan como tablas base. El join graph resultante necesita filtrado manual para ser legible.
|
||||
|
||||
Hallazgos cubiertos:
|
||||
|
||||
| Hallazgo | Severidad | Evidencia del benchmark |
|
||||
|---|---|---|
|
||||
| H3 — FK inference por contención: 10-20× falsos positivos | crítico | chinook 111 candidatas vs ~11 reales; sakila 565 vs ~30. Casos absurdos: `InvoiceLine.Quantity→Album.AlbumId`, `Genre.GenreId→{Album,Artist,Customer,…}` |
|
||||
| H5 — VIEWs perfiladas como tablas base | alto | sakila `n_tables=21` incluye 5 VISTAS (`customer_list`, `film_list` 5462 filas, `staff_list`, `sales_by_store`, `sales_by_film_category`) + `film_text` (FTS, 0 filas) |
|
||||
| H10 — coste relacional gastado en computar FK falsas | medio | sakila 31.82s: la mayoría en INTERSECT de los 565 pares candidatos, casi todos falsos |
|
||||
| H14 — bug `sqlite_master does not exist` tras ATTACH (ya parcheado, falta test) | bajo (resuelto) | `_run.log`: `profile_database` falló con `Catalog Error: src.sqlite_master`; re-run posterior `ok` |
|
||||
|
||||
### Causa raíz (verificada en código, READ-ONLY)
|
||||
|
||||
- `python/functions/datascience/infer_fk_containment_duckdb.py:217-285` emite una FK candidata si
|
||||
`inclusion(A⊆B) ≥ min_inclusion` **y** B "parece clave" (unicidad ≥0.95). **No usa el nombre de
|
||||
la columna**, que es la señal más fuerte de FK (`AlbumId→Album.AlbumId`), ni excluye columnas
|
||||
no-clave (cantidades, importes) como ORIGEN. Enteros pequeños (`GenreId` 1..25) están contenidos
|
||||
en casi todo → ruido.
|
||||
- `python/functions/pipelines/profile_database.py:155-159` lista tablas con `duckdb_list_tables`
|
||||
sin filtrar `table_type` → perfila VIEWs y tablas FTS como base (H5), lo que infla el universo de
|
||||
pares y multiplica las FK falsas (relaciona H10).
|
||||
- H10 es el **mismo cambio** que H3: filtrar candidatos por nombre **antes** del INTERSECT reduce
|
||||
pares (más rápido) y falsos positivos (más preciso) a la vez.
|
||||
|
||||
## Tareas
|
||||
|
||||
1. **H3+H10 — señal de nombre en `infer_fk_containment_duckdb.py:217-285`:** antes de lanzar el
|
||||
INTERSECT, exigir coincidencia/patrón de nombre entre origen y destino (`from_col` casa con
|
||||
`to_table`/`to_col`, patrón `<X>Id → <X>.<X>Id`; case-insensitive). Excluir como ORIGEN columnas
|
||||
claramente no-clave (cantidades, importes, flags) por heurística de nombre/tipo. Esto poda el
|
||||
O(tablas²×columnas²) y elimina la mayoría de los falsos positivos. Validar mejor la cardinalidad
|
||||
(los `1:1` imposibles del benchmark).
|
||||
2. **H5 — filtrar VIEWs** antes de perfilar e inferir FK: filtrar `table_type='BASE TABLE'` vía
|
||||
`information_schema.tables` / `duckdb_tables()`. Decidir (a confirmar al implementar) si el filtro
|
||||
va como flag nuevo en `duckdb_list_tables` (infra, reutilizable) o en `profile_database.py` tras
|
||||
listar. Preferir el flag en `duckdb_list_tables` si no rompe consumidores.
|
||||
3. **H3 — propagar al join graph:** verificar que `build_join_graph.py` recibe la lista ya filtrada
|
||||
y que el diagrama Mermaid resultante es legible (sin nodos VIEW ni aristas espurias).
|
||||
4. **H14 — test de regresión:** añadir test (en `profile_database_test.py` o
|
||||
`infer_fk_containment_duckdb_test.py`) que haga `ATTACH` de una base SQLite pequeña en DuckDB y
|
||||
perfile, confirmando que se usa `information_schema`/`duckdb_tables()` y nunca `sqlite_master`.
|
||||
(A confirmar: localizar la función que hace el ATTACH —probablemente `summarize_table_duckdb.py`
|
||||
o una primitiva infra `duckdb_*`— para cubrirla.)
|
||||
5. Tests: casos sintéticos con tablas que tengan columnas tipo `XId` (FK real) y columnas de
|
||||
cantidad contenidas en claves (falso positivo) → confirmar que solo emite las reales.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|
||||
|---|---|---|---|
|
||||
| Golden: FK reales sin ruido | e2e | re-correr `profile_database` sobre chinook | ~11 FK candidatas (no 111); incluyen `Album.ArtistId→Artist.ArtistId`, `Invoice.CustomerId→Customer.CustomerId`; NO incluyen `InvoiceLine.Quantity→Album.AlbumId` |
|
||||
| Edge: VIEWs excluidas | e2e | re-correr `profile_database` sobre sakila | `n_tables` cuenta solo BASE TABLE (sin `customer_list`/`film_list`/…); FK candidatas ≪ 565 |
|
||||
| Edge: cantidad vs clave | unit | `infer_fk_containment_duckdb_test.py` con columna `Quantity` contenida en una clave | NO emite FK desde `Quantity` |
|
||||
| Error: ATTACH SQLite | unit | test de regresión ATTACH SQLite→DuckDB | perfila sin `sqlite_master does not exist`; usa information_schema |
|
||||
| Rendimiento (H10) | e2e | medir duración de `profile_database` sobre sakila | menor que el baseline 31.82s (menos INTERSECT) |
|
||||
| Mecánica | — | `./fn run infer_fk_containment_duckdb_py_datascience`, `./fn run profile_database_py_pipelines`; `fn index` | tests verdes; índice limpio |
|
||||
|
||||
Re-correr el benchmark sobre chinook y sakila y confirmar que las FK reales son distinguibles del
|
||||
ruido y que las VIEWs no se cuentan como tablas.
|
||||
|
||||
## Notas
|
||||
|
||||
Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md`. Tres síntomas (H3/H5/H10) con un núcleo común:
|
||||
la capa de inferencia de relaciones inter-tabla. Atacarlos juntos en una rama; filtrar VIEWs reduce
|
||||
el universo de pares y filtrar candidatos por nombre arregla precisión y velocidad a la vez. H14 ya
|
||||
está parcheado en producción; este issue solo añade el test de regresión que faltaba.
|
||||
Hermanos: 0173, 0174, 0176, 0177.
|
||||
|
||||
|
||||
## Resolucion (2026-06-29, sesion /ausente)
|
||||
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: e142ef02. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
id: "0176"
|
||||
title: "EDA render: models/series/caveats en markdown+PDF + PDF para profile_database"
|
||||
status: completado
|
||||
type: feature
|
||||
domain:
|
||||
- registry-quality
|
||||
scope: registry-only
|
||||
priority: media
|
||||
depends: []
|
||||
blocks: []
|
||||
related: ["0173", "0174", "0175", "0177"]
|
||||
created: 2026-06-29
|
||||
updated: 2026-06-29
|
||||
tags: [eda, datascience, render_eda_markdown, render_eda_pdf, profile_database, pdf, benchmark]
|
||||
---
|
||||
# 0176 — EDA render: models/series/caveats en markdown+PDF + PDF para profile_database
|
||||
|
||||
## Contexto
|
||||
|
||||
El benchmark `/eda` (29/06/2026, `temp/eda_benchmark/EVALUATION.md`) confirmó que la información de
|
||||
modelos (PCA/KMeans) está completa en el JSON pero **no llega legible a ningún formato**, y que el
|
||||
análisis relacional no tiene salida móvil (PDF). El tercio final del PDF queda ilegible.
|
||||
|
||||
Hallazgos cubiertos:
|
||||
|
||||
| Hallazgo | Severidad | Evidencia del benchmark |
|
||||
|---|---|---|
|
||||
| H4 — `models` omitido en Markdown; `models`/`series`/`caveats` como dict crudo truncado en PDF | alto | wine-red `.md` (12 numéricas, PCA valioso) → cero menciones de models. PDF aapl: `- pca: {'n_components': 2, …` cortado a media línea |
|
||||
| H9 — `profile_database` no genera PDF | medio | chinook y sakila con `pdf=null`; análisis relacional solo en Markdown |
|
||||
|
||||
### Causa raíz (verificada en código, READ-ONLY)
|
||||
|
||||
- `python/functions/datascience/render_eda_markdown.py`: tiene formatters para `series` (`:337`) y
|
||||
`caveats` (`:407`), pero **no para `models`** → el bloque PCA/KMeans nunca se renderiza en MD.
|
||||
- `python/functions/datascience/render_eda_pdf.py:50-55`: `_KNOWN_TOP_KEYS` **no incluye** `models`,
|
||||
`series` ni `caveats`, así que caen en `_generic_pages` (`:479-495`) → `_wrap_value` →
|
||||
`str(dict)` truncado a 60-64 chars. Por eso esas tres secciones salen como dict crudo en el PDF.
|
||||
- `python/functions/pipelines/profile_database.py:205-218`: solo escribe MD+JSON, nunca invoca
|
||||
`render_eda_pdf`; no tiene param `emit_pdf`.
|
||||
|
||||
## Tareas
|
||||
|
||||
1. **H4 — markdown:** añadir una sección `## Modelos` (PCA/KMeans/outliers/normalidad) a
|
||||
`render_eda_markdown.py`, formateando `models.pca` (varianza explicada, top loadings, acumulada),
|
||||
`models.kmeans` (best_k, silhouette, tamaños de cluster) y `models.outliers` como tablas legibles.
|
||||
2. **H4 — PDF:** en `render_eda_pdf.py`, añadir builders dedicados para `models`, `series` y
|
||||
`caveats` (tablas/listas, no `str(dict)`) y registrarlos en `_KNOWN_TOP_KEYS` + en la lista
|
||||
`builders` (`:595-604`) para sacarlos del volcado genérico. Mantener el contrato dict-no-throw
|
||||
(una sección que falle no aborta el PDF).
|
||||
3. **Unificar renderers:** asegurar que MD y PDF cubren el mismo conjunto de secciones (`models`,
|
||||
`series`, `caveats`) para que no diverjan otra vez.
|
||||
4. **H9 — PDF relational:** añadir un renderer PDF DB-level (puede ser una variante en
|
||||
`render_eda_pdf.py` o una función nueva) con: portada de la base, resumen de tablas, join graph
|
||||
filtrado (tras 0175), y FK candidatas. Añadir param `emit_pdf` a `profile_database.py` que lo
|
||||
invoque y devuelva `pdf_path`.
|
||||
5. Tests: `render_eda_markdown_test.py` (perfil con `models` → aparece sección Modelos);
|
||||
`render_eda_pdf_test.py` (perfil con `models`/`series`/`caveats` → NO aparecen como `str(dict)`;
|
||||
`n_pages` incrementa); test de `profile_database(emit_pdf=True)` → `pdf_path` no nulo, PDF válido.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|
||||
|---|---|---|---|
|
||||
| Golden: models en MD | e2e | re-correr `profile_table(run_models=True)` sobre wine-red y leer el `.md` | sección `## Modelos` con PCA (varianza explicada) y KMeans (silhouette) legibles |
|
||||
| Golden: PDF legible | e2e | re-correr sobre aapl y `pdftotext` del PDF | `models`/`series`/`caveats` como tablas, sin `{'n_components': 2, …` truncado |
|
||||
| Edge: perfil sin models | unit | `render_eda_markdown_test.py`/`render_eda_pdf_test.py` con `models=None` | sección omitida limpiamente, sin crash |
|
||||
| Edge: PDF relational | e2e | `profile_database(emit_pdf=True)` sobre chinook | `pdf_path` no nulo; PDF con resumen de tablas + join graph |
|
||||
| Error: sección corrupta | unit | `render_eda_pdf` con una sección con tipo inesperado | esa sección se omite con nota; PDF sigue válido (≥1 página) |
|
||||
| Mecánica | — | `./fn run render_eda_markdown_py_datascience`, `./fn run render_eda_pdf_py_datascience`; `fn index` | tests verdes; índice limpio |
|
||||
|
||||
Re-correr el benchmark sobre un single-table con modelos (wine-red) y sobre un relational (chinook)
|
||||
y confirmar que models llega al MD y al PDF, y que `profile_database` emite PDF.
|
||||
|
||||
## Notas
|
||||
|
||||
Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md`. Tipo `feature` porque, además de arreglar el
|
||||
volcado crudo (H4, fix), añade un renderer PDF relational nuevo (H9). La información ya existe en el
|
||||
JSON; este issue solo la hace legible en las dos salidas pensadas para humanos. Hermanos: 0173, 0174,
|
||||
0175, 0177.
|
||||
|
||||
|
||||
## Resolucion (2026-06-29, sesion /ausente)
|
||||
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: c4cff5ed. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
id: "0177"
|
||||
title: "EDA tipos: id secuencial fuera de correlación/PCA + η² espurio por cardinalidad + re-expresión no-continuas"
|
||||
status: completado
|
||||
type: bugfix
|
||||
domain:
|
||||
- registry-quality
|
||||
scope: registry-only
|
||||
priority: media
|
||||
depends: []
|
||||
blocks: []
|
||||
related: ["0173", "0174", "0175", "0176"]
|
||||
created: 2026-06-29
|
||||
updated: 2026-06-29
|
||||
tags: [eda, datascience, profile_table, association_matrix, correlation_ratio, run_eda_models, suggest_reexpression, benchmark]
|
||||
---
|
||||
# 0177 — EDA tipos: id secuencial fuera de correlación/PCA + η² espurio
|
||||
|
||||
## Contexto
|
||||
|
||||
El benchmark `/eda` (29/06/2026, `temp/eda_benchmark/EVALUATION.md`) **refutó** el riesgo temido
|
||||
(que el EDA excluyera columnas financieras `Open/Close/High/Low/Volume` por marcarlas id-like: NO
|
||||
ocurre, aparecen en todo). Pero detectó el **problema inverso**: el flag `possible_id` es cosmético
|
||||
y no excluye lo que sí debería (índices secuenciales), y la razón de correlación η² da artefactos
|
||||
≈1 por cardinalidad.
|
||||
|
||||
Hallazgos cubiertos:
|
||||
|
||||
| Hallazgo | Severidad | Evidencia del benchmark |
|
||||
|---|---|---|
|
||||
| H7 — `possible_id` no excluye id secuencial (`PassengerId`) de correlación ni de PCA/KMeans | medio-alto | titanic `PassengerId–Cabin` η²=0.897 `sig=sí`; `models.pca.n_features=7` incluye `PassengerId`, `Survived`, `Pclass` |
|
||||
| H6 — `correlation_ratio` (η²) ≈1 espurio cuando la categórica tiene cardinalidad ≈ n | alto | titanic `Ticket–Fare=1 sig=sí` (`Ticket` 681 distintos/891); aapl/btc/seattle/fred `Date–* =1` |
|
||||
| H12 — `suggest_reexpression` sugiere fila para binarias/ordinales/ids (aunque sea `none`) | bajo | titanic `Survived` (0/1), `Pclass` (ordinal), `PassengerId` (id) listadas |
|
||||
|
||||
### Causa raíz (verificada en código, READ-ONLY)
|
||||
|
||||
- `python/functions/pipelines/profile_table.py:356-361` (`_skip_for_assoc`) excluye de la matriz de
|
||||
asociación las columnas id-like **categóricas/text** (`possible_id`/`high_cardinality`), pero **no**
|
||||
excluye numéricas secuenciales (`PassengerId` es numérica con `possible_id`) ni columnas datetime.
|
||||
El `assoc_input` resultante se pasa tal cual a `run_eda_models` (`:391`), así que el id secuencial,
|
||||
el target binario y el ordinal entran como features de PCA/KMeans.
|
||||
- H6: `correlation_ratio.py` calcula η² sin guard de cardinalidad; cuando cada grupo tiene ~1
|
||||
observación (categórica de cardinalidad ≈ n), la varianza intra-grupo ≈0 → η²≈1 trivialmente. El
|
||||
FDR no protege (artefacto determinista, no azar).
|
||||
- H12: `suggest_reexpression` (llamado en `profile_table.py:300` para toda numérica) no salta
|
||||
binarias/ordinales/ids.
|
||||
|
||||
## Tareas
|
||||
|
||||
1. **H7 — distinguir id secuencial de float continuo:** en la detección de tipos
|
||||
(`summarize_table_duckdb.py` / lógica de `possible_id`) o en `profile_table.py`, marcar
|
||||
"índice entero secuencial/monótono" distinto de "float continuo de alta cardinalidad". El primero
|
||||
se excluye de correlación y de PCA/KMeans; el segundo se mantiene (precios). **Nunca** excluir
|
||||
floats continuos.
|
||||
2. **H7 — excluir no-features de los modelos:** en `_skip_for_assoc` (y/o en `run_eda_models.py`)
|
||||
excluir de PCA/KMeans los ids secuenciales, binarias, ordinales y el target evidente, además de
|
||||
las categóricas id-like que ya se excluyen.
|
||||
3. **H6 — guard de cardinalidad en η²:** en `correlation_ratio.py` (y/o al construir los pares en
|
||||
`association_matrix.py`/`profile_table.py`), no computar η² si la categórica tiene cardinalidad
|
||||
cercana a `n` o tamaño de grupo medio ≈1; excluir columnas datetime/id de los pares categóricos.
|
||||
4. **H12 — saltar no-continuas en re-expresión:** en `suggest_reexpression.py` (o en la llamada de
|
||||
`profile_table.py:300`), no emitir fila de re-expresión para binarias/ordinales/ids.
|
||||
5. Tests: `correlation_ratio_test.py` (categórica cardinalidad≈n → no η²≈1 espurio);
|
||||
`run_eda_models_test.py` (id secuencial/target/ordinal no entran como features);
|
||||
`suggest_reexpression_test.py` (binaria/ordinal/id → sin sugerencia).
|
||||
|
||||
## Definition of Done
|
||||
|
||||
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|
||||
|---|---|---|---|
|
||||
| Golden: id secuencial fuera | e2e | re-correr `profile_table(run_models=True)` sobre titanic | `PassengerId` NO aparece en correlaciones ni en `models.pca.features`; floats continuos (precios en aapl/btc) SÍ se conservan |
|
||||
| Golden: η² sin artefacto | e2e | re-correr sobre titanic | `Ticket–Fare` y `Date–*` NO aparecen como par fuerte η²=1 |
|
||||
| Edge: float continuo | unit | `correlation_ratio_test.py` / detección de tipos | columna float de alta cardinalidad (precio) se mantiene en correlación |
|
||||
| Edge: re-expresión | unit | `suggest_reexpression_test.py` con binaria/ordinal/id | sin fila de re-expresión |
|
||||
| Error: solo numéricas | unit | `run_eda_models` con assoc_input vacío tras filtrar | sin crash; bloque models coherente |
|
||||
| Mecánica | — | `./fn run correlation_ratio_py_datascience`, `./fn run run_eda_models_py_datascience`, `./fn run suggest_reexpression_py_datascience`; `fn index` | tests verdes; índice limpio |
|
||||
|
||||
Re-correr el benchmark sobre titanic (id secuencial + η² espurio) y sobre aapl/btc (confirmar que
|
||||
los floats financieros NO se excluyen) y verificar ambos comportamientos.
|
||||
|
||||
## Notas
|
||||
|
||||
Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md`. El warning "grave" del benchmark (excluir
|
||||
columnas financieras) quedó **refutado**: este issue arregla el problema inverso real (no excluir
|
||||
ids secuenciales) sin tocar el tratamiento correcto de los floats continuos. Hermanos: 0173, 0174,
|
||||
0175, 0176.
|
||||
|
||||
|
||||
## Resolucion (2026-06-29, sesion /ausente)
|
||||
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: e142ef02. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
|
||||
Reference in New Issue
Block a user