docs(flows): DoD obligatorio con user-facing surface + abrir issues 0100-0103 (taxonomia, frontmatter migration, dev_console, work dashboard)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,33 @@ Probar end-to-end el stack: navegator AutoExtract -> recipe -> dag_engine schedu
|
||||
- `dag_engine.dag_step_results`: step `extract` con `function_id='cdp_extract_recipe_py_pipelines'`.
|
||||
- `call_monitor.calls`: chain function call.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Ver `README.md` seccion DoD + user-facing.
|
||||
|
||||
### Generico
|
||||
|
||||
- [ ] **Repetibilidad**: corre 3 veces consecutivas via cron sin intervencion.
|
||||
- [ ] **Observabilidad**: `call_monitor.calls` registra `cdp_extract_recipe_py_pipelines` + `data_factory.runs` muestra `node_id=hn_top_stories`.
|
||||
- [ ] **Error-path**: si Chrome :9222 cae, el step falla con mensaje claro (no crash silencioso del DAG).
|
||||
- [ ] **Idempotencia**: dedup `dedup_duckdb_table_by_hash_py_pipelines` corre tras extract; mismo HTML 2x = 0 filas nuevas.
|
||||
- [ ] **Secrets**: N/A (HN publico).
|
||||
- [ ] **Docs**: `## Notas` con comandos para reproducir + onboarding.
|
||||
- [ ] **Registry-first**: extract sin codigo inline en el DAG.
|
||||
- [ ] **INDEX + status**: `status: done` + `INDEX.md` + movido a `completed/`.
|
||||
|
||||
### User-facing
|
||||
|
||||
- [ ] **User-facing**: usuario abre `data_factory.exe` → tab "All Runs" filtra `node_id=hn_top_stories` → ve >=30 filas con rank/title/url/points.
|
||||
- [ ] **User-facing repeat**: vuelve manana al mismo tab, ve runs frescos (cada 30 min) y tabla actualizada.
|
||||
- [ ] **User-facing onboarding**: parrafo en `## Notas`: "Para ver HN top: lanzar `data_factory.exe` → tab Extractors → `hn_top_stories`. DuckDB en `apps/data_factory/data/hn_top_stories.duckdb` tabla `hn_stories`."
|
||||
- [ ] **User-facing latencia**: cron `*/30 * * * *` → datos frescos en <31 min p95.
|
||||
|
||||
### Custom
|
||||
|
||||
- [ ] 7/7 campos cubiertos en TODOS los runs ultimas 24h (rank/title/url/points/author/age/comments).
|
||||
- [ ] Latencia extract <30s p95 (cdp_extract_recipe + render).
|
||||
|
||||
## Notas
|
||||
|
||||
(rellenas tras correr)
|
||||
|
||||
@@ -63,6 +63,33 @@ Probar path HTTP-only (sin Chrome/CDP). Extractor REST -> data_factory -> sink g
|
||||
- `data_factory.runs`: 24 entries/dia.
|
||||
- `data_factory.databases.last_seen_at` actualizado por sink.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Ver `README.md` seccion DoD + user-facing.
|
||||
|
||||
### Generico
|
||||
|
||||
- [ ] **Repetibilidad**: cron `0 * * * *` corre 3h consecutivas sin error.
|
||||
- [ ] **Observabilidad**: extractor en `call_monitor.calls`, runs en `data_factory.runs`, fila en `databases.last_seen_at`.
|
||||
- [ ] **Error-path**: AEMET 5xx → 3 reintentos exp-backoff, despues marca run failed (no crash).
|
||||
- [ ] **Idempotencia**: re-run mismo timestamp = upsert PostGIS, sin duplicar puntos.
|
||||
- [ ] **Secrets**: API key AEMET en `pass aemet/api-key`, nunca en el DAG.
|
||||
- [ ] **Docs**: `## Notas` con comandos + onboarding.
|
||||
- [ ] **Registry-first**: extractor AEMET creado como funcion del registry (`aemet_get_madrid_observations_py_*` o reuso de `http_get_json_*`), nada inline.
|
||||
- [ ] **INDEX + status**: `status: done` + INDEX + movido.
|
||||
|
||||
### User-facing
|
||||
|
||||
- [ ] **User-facing**: usuario abre `footprint_geo_stack` → preset `madrid-weather` → ve overlay tiles con puntos meteo + tooltip (temp/humidity).
|
||||
- [ ] **User-facing repeat**: mismo preset manana muestra datos refrescados; tooltip ultima hora.
|
||||
- [ ] **User-facing onboarding**: parrafo en `## Notas`: "Para ver weather Madrid: `footprint_geo_stack.exe` → File → Open preset `madrid-weather`. Tile server local en :3000."
|
||||
- [ ] **User-facing latencia**: cron 1h → mapa refleja datos en <61 min.
|
||||
|
||||
### Custom
|
||||
|
||||
- [ ] PostGIS schema via `migrations/NNN_*.sql` (no `CREATE TABLE` inline).
|
||||
- [ ] Tile overlay sirve en <3s desde click.
|
||||
|
||||
## Notas
|
||||
|
||||
- Sin LLM/CDP. Mas barato que flow 0001.
|
||||
|
||||
@@ -61,6 +61,36 @@ Caso de uso REAL con auth + datos sensibles. Probar persistencia local (duckdb e
|
||||
- `data_factory.runs`: 1 entry status=success.
|
||||
- `auto_metabase`: 1 card creado.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Ver `README.md` seccion DoD + user-facing. **Risk=high** -> DoD strict obligatorio.
|
||||
|
||||
### Generico
|
||||
|
||||
- [ ] **Repetibilidad**: re-login + extraccion mensual reproducible (no flaky por DOM changes inesperados).
|
||||
- [ ] **Observabilidad**: `call_monitor.calls` muestra ejecucion sin valores; `data_factory.runs` registra ambos nodos.
|
||||
- [ ] **Error-path**: sesion expirada → mensaje claro al usuario para re-login (no datos corruptos).
|
||||
- [ ] **Idempotencia**: re-extraer mismo mes = upsert por `movimiento_id`, 0 duplicados.
|
||||
- [ ] **Secrets**: credenciales BBVA solo en `pass bbva/login`; vault `~/vaults/finanzas/` gitignored verificado.
|
||||
- [ ] **Docs**: `## Notas` con onboarding + procedimiento de rotacion mensual.
|
||||
- [ ] **Registry-first**: recipe + persistencia duckdb usan funciones del registry.
|
||||
- [ ] **INDEX + status**: `status: done` + INDEX + movido.
|
||||
|
||||
### User-facing
|
||||
|
||||
- [ ] **User-facing**: usuario abre Metabase LOCAL :3000 → dashboard `Finanzas personales` → card `Gasto mensual` con grafico actualizado.
|
||||
- [ ] **User-facing repeat**: misma URL manana muestra movimientos del mes hasta hoy; despues de re-login mensual, mes nuevo aparece automatico.
|
||||
- [ ] **User-facing onboarding**: parrafo en `## Notas`: "Para revisar gasto: abrir http://localhost:3000 (creds en `pass metabase/local`) → dashboard `Finanzas personales`. Re-login BBVA: lanzar navegator → recipe `bbva_movimientos` → click Run."
|
||||
- [ ] **User-facing latencia**: tras re-login + run manual, card actualizada en <2 min.
|
||||
|
||||
### Custom (risk=high)
|
||||
|
||||
- [ ] **No-leak**: `fn sync` NO sube duckdb (verificado: `pc_locations` registra path, sync no transmite bytes).
|
||||
- [ ] **No-leak**: recipe extrae solo campos minimos (fecha, concepto, importe, categoria); NO DNI, NO saldo, NO IBAN completo.
|
||||
- [ ] **No-leak**: Metabase corre LOCAL; verificar `auto_metabase.app.md` declara `tags: [local-only]`.
|
||||
- [ ] **Rotacion**: re-login mensual probado sin perder datos historicos.
|
||||
- [ ] **Red-team**: ningun log/screenshot/traza del flow contiene valores sensibles (grep IBAN/saldo en `call_monitor.calls`, `data_factory.runs`, `~/.cache/`).
|
||||
|
||||
## Notas
|
||||
|
||||
- **NO commitear** `~/vaults/finanzas/` (gitignored por defecto).
|
||||
|
||||
@@ -61,6 +61,34 @@ Probar webhooks como trigger (no cron, no manual). Cada push a un repo `dataforg
|
||||
- Por repo: 1 nodo extractor.
|
||||
- Matrix: 1 msg por push.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Ver `README.md` seccion DoD + user-facing.
|
||||
|
||||
### Generico
|
||||
|
||||
- [ ] **Repetibilidad**: 3 pushes test distintos disparan 3 mensajes Matrix sin intervencion.
|
||||
- [ ] **Observabilidad**: `data_factory.runs` con `trigger=webhook` + `call_monitor.calls` chain por push.
|
||||
- [ ] **Error-path**: payload invalido → 4xx + entry en log + NO crash receptor.
|
||||
- [ ] **Idempotencia**: recepcion duplicada (Gitea retry) → 1 mensaje, no N.
|
||||
- [ ] **Secrets**: webhook secret en `pass gitea/webhook-secret`; HMAC verificado por receptor.
|
||||
- [ ] **Docs**: `## Notas` con setup webhook + onboarding.
|
||||
- [ ] **Registry-first**: receptor reusa `http_post_json_*` + `matrix_send_message_*`.
|
||||
- [ ] **INDEX + status**: `status: done` + INDEX + movido.
|
||||
|
||||
### User-facing
|
||||
|
||||
- [ ] **User-facing**: usuario lee mensaje en sala Matrix `#fn-registry-news` con formato `[<repo>] <author> pushed <N> commits to <branch>` + link al commit.
|
||||
- [ ] **User-facing repeat**: cada push real a `dataforge/*` dispara mensaje; sala es la fuente diaria de actividad multi-repo.
|
||||
- [ ] **User-facing onboarding**: parrafo en `## Notas`: "Para enterarse de pushes: unirse a sala Matrix `#fn-registry-news`. Para anadir un repo nuevo: `gitea_create_webhook_bash_infra <owner> <repo> <url> <secret>`."
|
||||
- [ ] **User-facing latencia**: push → mensaje en <5s p95.
|
||||
|
||||
### Custom
|
||||
|
||||
- [ ] >=3 repos cubiertos (no solo 1).
|
||||
- [ ] Rate-limit: max 1 mensaje/repo/minuto (no flood si N pushes seguidos).
|
||||
- [ ] Health endpoint `/webhook/health` retorna 200 + lista repos suscritos.
|
||||
|
||||
## Notas
|
||||
|
||||
- Webhook secret debe estar en `pass gitea/webhook-secret` o env var.
|
||||
|
||||
@@ -58,6 +58,35 @@ Probar paralelismo (multiples scraping jobs concurrentes) + agregacion a grafo.
|
||||
- `operations.db` de osint_graph: entities += N, relations += N.
|
||||
- `function_stats.claude_cli_prompt_py_infra`: calls += 1.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Ver `README.md` seccion DoD + user-facing. **Risk=medium** -> attention en datos personales.
|
||||
|
||||
### Generico
|
||||
|
||||
- [ ] **Repetibilidad**: 3 lookups distintos (3 personas test) producen reports completos sin re-config.
|
||||
- [ ] **Observabilidad**: 3 jobs visibles en `odr_console.operations.db` + `call_monitor.calls` chain por job.
|
||||
- [ ] **Error-path**: si LinkedIn devuelve 429 → job marcado failed, otros 2 continuan (no aborta el flow entero).
|
||||
- [ ] **Idempotencia**: re-lookup misma persona → upsert por `snippet_hash`, no duplica nodos Person.
|
||||
- [ ] **Secrets**: creds Twitter/GitHub en `pass`; LinkedIn usa sesion del navegador (cookie via navegator).
|
||||
- [ ] **Docs**: `## Notas` con onboarding + check legal.
|
||||
- [ ] **Registry-first**: recipes + agregacion + render reusan funciones registry.
|
||||
- [ ] **INDEX + status**: `status: done` + INDEX + movido.
|
||||
|
||||
### User-facing
|
||||
|
||||
- [ ] **User-facing**: usuario abre `graph_explorer.exe` → File → Load dataset `osint/<persona>` → ve grafo Person + N Snippets navegable (zoom, click → snippet content).
|
||||
- [ ] **User-facing repeat**: persona nueva → comando lanza job, dataset aparece en lista de graph_explorer en <5min.
|
||||
- [ ] **User-facing onboarding**: parrafo en `## Notas`: "Para investigar persona: `/flow run 0005 --target '<nombre>'` (o `odr_console.exe` → New Job → 3 recipes). Esperar ~5min. Abrir `graph_explorer.exe` → Load `osint/<nombre>`. Resumen LLM en `report.md` del repo."
|
||||
- [ ] **User-facing latencia**: job lanzado → grafo listo en <5min (3 jobs paralelos).
|
||||
|
||||
### Custom
|
||||
|
||||
- [ ] Paralelismo medido: 3 jobs concurrentes <60s wall vs ~180s en serie.
|
||||
- [ ] Race-condition test: 2 corridas simultaneas del flow no corrompen operations.db.
|
||||
- [ ] Red-team: nada de menores/info no publica en snippets capturados.
|
||||
- [ ] Report `.md` firmado por commit en repo `osint_graph`.
|
||||
|
||||
## Notas
|
||||
|
||||
- Consideracion legal: extracciones publicas (perfiles abiertos). NO bypassear paywalls/captchas.
|
||||
|
||||
@@ -72,6 +72,35 @@ Probar flujo INVERSO al tipico: extraer estado de un servicio interno (Metabase)
|
||||
- 1 run/dia en data_factory.
|
||||
- 7 commits en metabase_registry repo (1 semana baseline).
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Ver `README.md` seccion DoD + user-facing.
|
||||
|
||||
### Generico
|
||||
|
||||
- [ ] **Repetibilidad**: cron diario 02:00 corre 7 dias consecutivos sin error.
|
||||
- [ ] **Observabilidad**: `data_factory.runs` + 1 commit en repo `metabase_registry` por dia (o `NO_CHANGES`).
|
||||
- [ ] **Error-path**: token Metabase expirado → healthcheck pre-pull falla con mensaje claro, no silencio.
|
||||
- [ ] **Idempotencia**: NO_CHANGES no genera commit vacio en git.
|
||||
- [ ] **Secrets**: token Metabase en `pass metabase/api-token`.
|
||||
- [ ] **Docs**: `## Notas` con onboarding + rollback procedure.
|
||||
- [ ] **Registry-first**: pull/diff/push reusan funciones registry.
|
||||
- [ ] **INDEX + status**: `status: done` + INDEX + movido.
|
||||
|
||||
### User-facing
|
||||
|
||||
- [ ] **User-facing**: usuario navega a `https://gitea.../dataforge/metabase_registry/commits/master` → ve commits diarios con diff YAML de dashboards/cards.
|
||||
- [ ] **User-facing repeat**: misma URL manana muestra commit nuevo (o `NO_CHANGES` skip); rollback con click derecho en commit → restore.
|
||||
- [ ] **User-facing onboarding**: parrafo en `## Notas`: "Para auditar cambios Metabase: abrir Gitea repo `dataforge/metabase_registry`. Rollback: revertir commit en Gitea → push trigger DAG manual → Metabase restaurado. Matrix bot diario en `#fn-registry-ops` a las 09:00."
|
||||
- [ ] **User-facing latencia**: cambio manual en Metabase → commit visible al dia siguiente 02:00.
|
||||
|
||||
### Custom
|
||||
|
||||
- [ ] Rollback E2E probado: revertir commit → siguiente run aplica YAML viejo → Metabase restaura dashboard.
|
||||
- [ ] Diff YAML estable: keys ordenadas, no churn aleatorio.
|
||||
- [ ] Dashboards eliminados → commit `DELETED:`, no tombstone huerfano.
|
||||
- [ ] Backup adicional a vault (no solo git).
|
||||
|
||||
## Notas
|
||||
|
||||
- Riesgo: si Metabase token expira, el DAG falla silenciosamente. Anadir healthcheck pre-pull.
|
||||
|
||||
@@ -71,6 +71,34 @@ Tres triggers distintos, mismo sink.
|
||||
- `function_stats.matrix_send_message_*`: calls dependientes de eventos reales.
|
||||
- Sala Matrix recibe mensajes de los 3 origenes.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Ver `README.md` seccion DoD + user-facing.
|
||||
|
||||
### Generico
|
||||
|
||||
- [ ] **Repetibilidad**: 3 triggers (node fail / DAG fail / violations) disparan mensaje cada uno, 100x sin perdida.
|
||||
- [ ] **Observabilidad**: cada envio en `call_monitor.calls`; cola persistente registra envios pendientes si Matrix down.
|
||||
- [ ] **Error-path**: Matrix down → cola en operations.db; al reconectar drena en orden.
|
||||
- [ ] **Idempotencia**: dedup: misma alerta 10x en 1min → 1 mensaje agregado, no flood.
|
||||
- [ ] **Secrets**: bot token en `pass matrix/bot-token`.
|
||||
- [ ] **Docs**: `## Notas` con onboarding + comandos para provocar trigger de prueba.
|
||||
- [ ] **Registry-first**: `matrix_send_message_py_infra` registrado + reusado.
|
||||
- [ ] **INDEX + status**: `status: done` + INDEX + movido.
|
||||
|
||||
### User-facing
|
||||
|
||||
- [ ] **User-facing**: usuario lee alerta en sala Matrix `#fn-registry-ops` con prefix emoji severidad + nombre app + link al dashboard de la app fallida.
|
||||
- [ ] **User-facing repeat**: cada fallo real dispara mensaje en la sala; sala es el feed de salud diario del stack.
|
||||
- [ ] **User-facing onboarding**: parrafo en `## Notas`: "Para enterarse de fallos: unirse a `#fn-registry-ops` (creds Matrix en `pass matrix/user`). Heartbeat 09:00 confirma bot vivo. Probar trigger: `./fn run inject_synthetic_violation`."
|
||||
- [ ] **User-facing latencia**: evento → mensaje en <3s p95 (medido sobre 100 envios).
|
||||
|
||||
### Custom
|
||||
|
||||
- [ ] Severity routing: `critical` → `#fn-registry-ops`; `warning` → `#fn-registry-dev`.
|
||||
- [ ] Self-test diario 09:00: bot envia heartbeat si vivo; ausencia heartbeat = alerta meta.
|
||||
- [ ] Mensaje formateado con link al dashboard (no solo texto plano).
|
||||
|
||||
## Notas
|
||||
|
||||
- Throttling: max 1 mensaje/minuto por origen para evitar spam.
|
||||
|
||||
@@ -21,6 +21,13 @@ Al recibir "crea flow para <X>" o `/flow create <slug>`:
|
||||
4. **Marca riesgo** (low/medium/high) por sensibilidad de datos.
|
||||
5. **Sugiere schedule** (cron / webhook / manual) basado en el tipo de fuente.
|
||||
6. **Sugiere apps** del stack que encajan, sin inflar — solo las que realmente tocara.
|
||||
7. **REDACTA `## Definition of Done` OBLIGATORIO**. No scaffold sin DoD. Empieza por la plantilla minima del `README.md` y anade DoD especificos al dominio del flow (ej. "datos NO viajan a registry.organic-machine", "geo: tiles sirven en <3s", "matrix bot tarda <5s en mensaje"). Acceptance != DoD: Acceptance = "corre"; DoD = "esta listo para vivir solo".
|
||||
8. **DECLARA USER-FACING SURFACE**. Dentro del DoD, los 4 checks `User-facing` son OBLIGATORIOS y concretos. Responde antes de scaffold:
|
||||
- **donde** lo ve el humano? (app concreta + tab/panel, sala Matrix, dashboard URL, Metabase card, repo Gitea con commits, archivo en vault). NUNCA "en la BD" o "en un log".
|
||||
- **cuanto tarda** en aparecer? (declara latencia en segundos/minutos).
|
||||
- **como vuelve** a verlo manana? (URL bookmark? slash command? cron + dashboard?).
|
||||
- **que parrafo onboarding** ira en `## Notas` para que un humano nuevo lo use sin leer el flow.
|
||||
Si la unica respuesta es "lo consume otro flow/app", devuelve el flow a borrador — falta superficie humana.
|
||||
|
||||
## Mapa de discovery — donde mirar para cada decision
|
||||
|
||||
|
||||
+11
-9
@@ -2,20 +2,22 @@
|
||||
|
||||
Tabla de casos de uso multi-app. Mantenida por `/flow create` y `/flow done`.
|
||||
|
||||
| ID | Slug | Apps | Status | Risk | Updated |
|
||||
|----|------|------|--------|------|---------|
|
||||
| [0001](0001-hn-top-stories.md) | hn-top-stories | navegator_dashboard, dag_engine, data_factory, agents_and_robots | pending | low | 2026-05-16 |
|
||||
| [0002](0002-aemet-madrid.md) | aemet-madrid | dag_engine, data_factory, footprint_geo_stack | pending | low | 2026-05-16 |
|
||||
| [0003](0003-bbva-movimientos.md) | bbva-movimientos | navegator_dashboard, dag_engine, data_factory, auto_metabase | pending | high | 2026-05-16 |
|
||||
| [0004](0004-gitea-releases-monitor.md) | gitea-releases-monitor | registry_api, data_factory, agents_and_robots | pending | low | 2026-05-16 |
|
||||
| [0005](0005-osint-person-lookup.md) | osint-person-lookup | navegator_dashboard, odr_console, graph_explorer | pending | medium | 2026-05-16 |
|
||||
| [0006](0006-metabase-versioning.md) | metabase-versioning | auto_metabase, dag_engine | pending | medium | 2026-05-16 |
|
||||
| [0007](0007-matrix-telemetry-bot.md) | matrix-telemetry-bot | data_factory, dag_engine, call_monitor, agents_and_robots | pending | low | 2026-05-16 |
|
||||
| ID | Slug | Pattern | Apps | Status | Risk | DoD % | Updated |
|
||||
|----|------|---------|------|--------|------|-------|---------|
|
||||
| [0001](0001-hn-top-stories.md) | hn-top-stories | smoke-cron | navegator_dashboard, dag_engine, data_factory, agents_and_robots | pending | low | 0% | 2026-05-16 |
|
||||
| [0002](0002-aemet-madrid.md) | aemet-madrid | smoke-cron | dag_engine, data_factory, footprint_geo_stack | pending | low | 0% | 2026-05-16 |
|
||||
| [0003](0003-bbva-movimientos.md) | bbva-movimientos | prod-data | navegator_dashboard, dag_engine, data_factory, auto_metabase | pending | high | 0% | 2026-05-16 |
|
||||
| [0004](0004-gitea-releases-monitor.md) | gitea-releases-monitor | event-driven | registry_api, data_factory, agents_and_robots | pending | low | 0% | 2026-05-16 |
|
||||
| [0005](0005-osint-person-lookup.md) | osint-person-lookup | manual-deep | navegator_dashboard, odr_console, graph_explorer | pending | medium | 0% | 2026-05-16 |
|
||||
| [0006](0006-metabase-versioning.md) | metabase-versioning | gitops | auto_metabase, dag_engine | pending | medium | 0% | 2026-05-16 |
|
||||
| [0007](0007-matrix-telemetry-bot.md) | matrix-telemetry-bot | event-driven | data_factory, dag_engine, call_monitor, agents_and_robots | pending | low | 0% | 2026-05-16 |
|
||||
|
||||
## Leyenda
|
||||
|
||||
- **Status**: `pending` (no arrancado) / `running` / `done` / `failed` / `deferred`.
|
||||
- **Risk**: `low` (datos publicos), `medium` (auth pero no sensible), `high` (datos personales/financieros).
|
||||
- **Pattern**: `smoke-cron` / `prod-data` / `event-driven` / `manual-deep` / `gitops` / `realtime-loop` — ver `README.md`.
|
||||
- **DoD %**: ratio de checks `[x]` en el bloque `## Definition of Done` del flow. `/flow done` exige 100%.
|
||||
|
||||
## Completados
|
||||
|
||||
|
||||
@@ -9,8 +9,53 @@ Un flow describe una secuencia de pasos que atraviesa varias apps (`navegator_da
|
||||
- Archivo por flow: `NNNN-<slug>.md` (numeracion zero-padded propia, NO comparte con `dev/issues/`).
|
||||
- Estado vivo en frontmatter (`status`).
|
||||
- Acceptance checkboxes `[ ]` en el body — `/flow status` calcula % completado.
|
||||
- **Definition of Done OBLIGATORIA** — ver seccion abajo. Sin DoD el flow NO puede crearse.
|
||||
- Cerrados se mueven a `completed/`.
|
||||
|
||||
## Definition of Done (OBLIGATORIA)
|
||||
|
||||
Cada flow al crearse DEBE declarar un bloque `## Definition of Done` distinto de `## Acceptance`. Sin el, `/flow create` rechaza el scaffold y `/flow done` rechaza el cierre.
|
||||
|
||||
**Diferencia:**
|
||||
|
||||
| `## Acceptance` | `## Definition of Done` |
|
||||
|---|---|
|
||||
| Checks task-level del flow (ejecucion concreta) | Contrato global de calidad para considerar el flow CERRADO |
|
||||
| Pueden quedar `[ ]` mientras iteras | TODOS deben estar `[x]` antes de mover a `completed/` |
|
||||
| Verifica que el flow CORRE | Verifica que el flow es REPETIBLE, OBSERVABLE y MANTENIBLE |
|
||||
|
||||
**Plantilla minima de DoD** (anadir/ajustar segun flow):
|
||||
|
||||
```markdown
|
||||
## Definition of Done
|
||||
|
||||
- [ ] **Repetibilidad**: el flow corre N veces consecutivas (N declarado en el flow, default 3) sin intervencion manual.
|
||||
- [ ] **Observabilidad**: queda trazado en `call_monitor.calls` + `data_factory.runs` + dashboard correspondiente.
|
||||
- [ ] **Error-path**: al menos 1 modo de fallo probado y manejado (no crash silencioso).
|
||||
- [ ] **Idempotencia**: re-ejecutar no duplica datos ni rompe estado (clave en sinks).
|
||||
- [ ] **Secrets**: cero credenciales en disco fuera de `pass`/vaults; cero datos sensibles fuera de `risk` declarado.
|
||||
- [ ] **Docs**: `## Notas` rellenado con hallazgos reales + comandos para reproducir.
|
||||
- [ ] **Registry-first**: todas las piezas reutilizables existen como funciones del registry (no inline en apps).
|
||||
- [ ] **INDEX + status**: `status: done` en frontmatter + fila actualizada en `INDEX.md` + archivo movido a `completed/`.
|
||||
```
|
||||
|
||||
Cada flow puede anadir DoD especificos al dominio (ej. `bbva-movimientos`: "datos NUNCA cruzan a registry.organic-machine"). El bloque DoD se **versiona con el flow** — un cambio de DoD requiere bump de `updated:` en frontmatter.
|
||||
|
||||
### User-facing surface (sub-bloque OBLIGATORIO dentro de DoD)
|
||||
|
||||
"DoD verde" sin valor visible al humano = plumbing limpio sin razon de existir. Cada DoD DEBE incluir, al menos, estos cuatro checks tipo `User-facing`:
|
||||
|
||||
```markdown
|
||||
- [ ] **User-facing**: <accion concreta del humano + lugar exacto donde ve/usa el output>.
|
||||
- [ ] **User-facing repeat**: el humano vuelve manana al mismo lugar y ve datos frescos sin conocer el flow.
|
||||
- [ ] **User-facing onboarding**: parrafo en `## Notas` que explica "para ver/usar esto: hacer X" — sin leer el flow.
|
||||
- [ ] **User-facing latencia**: el humano percibe el cambio en <X segundos|minutos> tras el evento (X declarado por flow).
|
||||
```
|
||||
|
||||
Regla: si la respuesta a "donde lo ves" es "en una BD" o "en un log" -> NO vale. Tiene que ser una superficie usada por el humano (UI de una app, sala Matrix, dashboard, Metabase card, repo Gitea, archivo en vault abierto a mano). Si el output solo lo consume otra app/flow, esa app/flow es quien debe declarar su propia user-facing surface.
|
||||
|
||||
`/flow done` rechaza el cierre si falta alguno de los 4 user-facing checks o si `## Notas` no contiene parrafo onboarding.
|
||||
|
||||
## Para agentes / LLMs
|
||||
|
||||
Antes de crear o editar un flow, lee `AGENT_GUIDE.md`. Define:
|
||||
|
||||
@@ -71,6 +71,30 @@ Pasos numerados. Cada paso puede ser:
|
||||
- [ ] Criterio 1.
|
||||
- [ ] Criterio 2.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Contrato global de cierre. TODOS marcados antes de mover a `completed/`. Ver README.md seccion "Definition of Done".
|
||||
|
||||
- [ ] **Repetibilidad**: corre 3 veces consecutivas sin intervencion manual.
|
||||
- [ ] **Observabilidad**: trazado en `call_monitor.calls` + `data_factory.runs` + dashboard relevante.
|
||||
- [ ] **Error-path**: >=1 modo de fallo probado y manejado.
|
||||
- [ ] **Idempotencia**: re-ejecucion no duplica ni corrompe sinks.
|
||||
- [ ] **Secrets**: cero credenciales fuera de `pass`/vaults; risk declarado coincide con datos reales.
|
||||
- [ ] **Docs**: `## Notas` con hallazgos + comandos reproducibles.
|
||||
- [ ] **Registry-first**: piezas reutilizables viven como funciones del registry.
|
||||
- [ ] **INDEX + status**: `status: done` + `INDEX.md` actualizado + movido a `completed/`.
|
||||
|
||||
### User-facing (obligatorio)
|
||||
|
||||
- [ ] **User-facing**: <accion concreta del humano + lugar exacto donde ve/usa el output>.
|
||||
- [ ] **User-facing repeat**: humano vuelve manana al mismo lugar, ve datos frescos sin conocer el flow.
|
||||
- [ ] **User-facing onboarding**: parrafo en `## Notas` explica "para ver/usar esto: hacer X" sin leer el flow.
|
||||
- [ ] **User-facing latencia**: humano percibe el cambio en <Xs|Xmin> tras el evento (X declarado).
|
||||
|
||||
### Custom (opcional, dominio-especifico)
|
||||
|
||||
- [ ] _(custom)_ <DoD especifica al dominio si aplica>.
|
||||
|
||||
## Telemetria esperada
|
||||
|
||||
- `call_monitor.calls`: que aparece.
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generador de iconos .ico para apps C++ del registry.
|
||||
Toma SVG phosphor → renderiza con cairosvg → compone fondo redondeado + glyph
|
||||
blanco → exporta .ico multi-resolucion (16,24,32,48,64,128,256) en
|
||||
<app_dir>/appicon.ico.
|
||||
|
||||
Mapping: APPS = [(app_id, dir, phosphor_icon, accent_hex)]
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import cairosvg
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
REGISTRY_ROOT = Path(__file__).resolve().parent.parent
|
||||
PHOSPHOR_FILL = REGISTRY_ROOT / "sources/phosphor-core/assets/fill"
|
||||
|
||||
APPS = [
|
||||
("altsnap_jitter_test", "apps/altsnap_jitter_test", "arrows-clockwise", "#dc2626"),
|
||||
("chart_demo", "apps/chart_demo", "chart-bar", "#0ea5e9"),
|
||||
("dag_engine_ui", "apps/dag_engine_ui", "tree-structure", "#7c3aed"),
|
||||
("data_factory", "apps/data_factory", "factory", "#f97316"),
|
||||
("engine_smoke", "apps/engine_smoke", "game-controller", "#16a34a"),
|
||||
("graph_explorer", "projects/osint_graph/apps/graph_explorer", "graph", "#0891b2"),
|
||||
("navegator_dashboard", "projects/navegator/apps/navegator_dashboard", "compass", "#2563eb"),
|
||||
("odr_console", "projects/online_data_recopilation/apps/odr_console", "terminal-window", "#52525b"),
|
||||
("primitives_gallery", "apps/primitives_gallery", "shapes", "#db2777"),
|
||||
("registry_dashboard", "projects/fn_monitoring/apps/registry_dashboard", "gauge", "#059669"),
|
||||
("runtime_test", "apps/runtime_test", "flask", "#9333ea"),
|
||||
("shaders_lab", "apps/shaders_lab", "palette", "#ea580c"),
|
||||
("text_editor_smoke", "apps/text_editor_smoke", "note-pencil", "#0d9488"),
|
||||
]
|
||||
|
||||
ICON_SIZES = [16, 24, 32, 48, 64, 128, 256]
|
||||
RENDER_SIZE = 256 # canvas of reference, downscaled to each .ico size
|
||||
|
||||
|
||||
def hex_to_rgb(h: str) -> tuple[int, int, int]:
|
||||
h = h.lstrip("#")
|
||||
return tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))
|
||||
|
||||
|
||||
def render_glyph_white(svg_path: Path, size: int) -> Image.Image:
|
||||
"""Render phosphor SVG as white-on-transparent at given size."""
|
||||
svg = svg_path.read_text()
|
||||
# phosphor uses fill="currentColor". Force white.
|
||||
svg = svg.replace('fill="currentColor"', 'fill="#ffffff"')
|
||||
png_bytes = cairosvg.svg2png(
|
||||
bytestring=svg.encode("utf-8"),
|
||||
output_width=size,
|
||||
output_height=size,
|
||||
)
|
||||
return Image.open(io.BytesIO(png_bytes)).convert("RGBA")
|
||||
|
||||
|
||||
def make_icon_image(svg_path: Path, accent_hex: str, size: int) -> Image.Image:
|
||||
"""Compose: rounded-square accent background + centered white glyph."""
|
||||
bg_color = hex_to_rgb(accent_hex) + (255,)
|
||||
canvas = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
radius = max(2, size // 6) # ~16% rounded corners
|
||||
draw.rounded_rectangle(
|
||||
[(0, 0), (size - 1, size - 1)],
|
||||
radius=radius,
|
||||
fill=bg_color,
|
||||
)
|
||||
# Glyph occupies inner ~70% (padding ~15% all around).
|
||||
glyph_size = int(size * 0.7)
|
||||
if glyph_size < 8:
|
||||
glyph_size = max(8, size - 2)
|
||||
glyph = render_glyph_white(svg_path, glyph_size)
|
||||
off = ((size - glyph_size) // 2, (size - glyph_size) // 2)
|
||||
canvas.alpha_composite(glyph, dest=off)
|
||||
return canvas
|
||||
|
||||
|
||||
def build_ico(app_id: str, app_dir: Path, phosphor_name: str, accent_hex: str) -> Path:
|
||||
svg_file = PHOSPHOR_FILL / f"{phosphor_name}-fill.svg"
|
||||
if not svg_file.exists():
|
||||
raise FileNotFoundError(f"phosphor icon not found: {svg_file}")
|
||||
# Render the highest-quality image (256) and let Pillow downscale via `sizes`.
|
||||
# Using append_images with custom-rendered per-size variants preserves
|
||||
# crispness of the phosphor glyph at small sizes (16/24).
|
||||
images = {s: make_icon_image(svg_file, accent_hex, s) for s in ICON_SIZES}
|
||||
out = app_dir / "appicon.ico"
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
biggest = images[max(ICON_SIZES)]
|
||||
others = [images[s] for s in ICON_SIZES if s != max(ICON_SIZES)]
|
||||
biggest.save(
|
||||
out,
|
||||
format="ICO",
|
||||
sizes=[(s, s) for s in ICON_SIZES],
|
||||
append_images=others,
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def main() -> int:
|
||||
errors = 0
|
||||
for app_id, rel_dir, phosphor_name, accent_hex in APPS:
|
||||
app_dir = REGISTRY_ROOT / rel_dir
|
||||
if not app_dir.exists():
|
||||
print(f"SKIP {app_id}: dir not found ({rel_dir})", file=sys.stderr)
|
||||
errors += 1
|
||||
continue
|
||||
try:
|
||||
out = build_ico(app_id, app_dir, phosphor_name, accent_hex)
|
||||
print(f"OK {app_id:25s} -> {out.relative_to(REGISTRY_ROOT)} ({phosphor_name}, {accent_hex})")
|
||||
except Exception as e: # pragma: no cover - reporting only
|
||||
print(f"FAIL {app_id}: {e}", file=sys.stderr)
|
||||
errors += 1
|
||||
return 1 if errors else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,105 @@
|
||||
# 0100 — Migrar frontmatter inline a YAML canonico en dev/issues/
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-16
|
||||
**Type:** chore
|
||||
**Priority:** alta
|
||||
**Domain:** registry-quality
|
||||
**Scope:** registry-only
|
||||
**Depends:** —
|
||||
**Blocks:** 0102 (work dashboard tab necesita filtros frontmatter)
|
||||
**Related:** 0103 (taxonomia + slash commands)
|
||||
|
||||
## Problema
|
||||
|
||||
Hoy los 71 archivos `dev/issues/*.md` declaran metadata como markdown inline:
|
||||
|
||||
```
|
||||
# 0099 — datahub app (launcher central)
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-16
|
||||
**Type:** app
|
||||
**Priority:** alta
|
||||
**Depends:** 0096 — DONE
|
||||
**Blocks:** —
|
||||
```
|
||||
|
||||
Imposible filtrar/agrupar sin parsers ad-hoc por linea. Issues antiguos (0027-0070) ni siquiera tienen `Type` ni `Priority`. Resultado: `/issue list` no existe; humano lee `README.md` (tabla manual) que se queda obsoleta.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Frontmatter YAML canonico al inicio de cada issue, igual modelo que `dev/flows/`. Mantener el contenido humano intacto debajo.
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: 0099
|
||||
title: datahub app launcher central
|
||||
status: pendiente # pendiente | in-progress | bloqueado | completado | deferred
|
||||
type: app # app | feature | bugfix | refactor | chore | docs | spike | epic | infra
|
||||
domain: [apps-infra, cpp-stack]
|
||||
scope: app-scoped # registry-only | app-scoped | multi-app | cross-stack
|
||||
priority: alta # alta | media | baja
|
||||
depends: [0096]
|
||||
blocks: []
|
||||
related: [0095, 0097]
|
||||
created: 2026-05-16
|
||||
updated: 2026-05-16
|
||||
tags: []
|
||||
---
|
||||
|
||||
# 0099 — datahub app launcher central
|
||||
|
||||
(cuerpo original sin tocar)
|
||||
```
|
||||
|
||||
## Pipeline propuesto
|
||||
|
||||
`migrate_issues_frontmatter_bash_pipelines` (o python, lo que encaje mejor). Idempotente.
|
||||
|
||||
1. Para cada `dev/issues/*.md`:
|
||||
- Si ya tiene frontmatter YAML (`---` en linea 1): merge campos faltantes solo, no sobreescribe.
|
||||
- Si no: parsea las lineas `**Key:** value` debajo del H1, extrae a YAML.
|
||||
- Si `Type` / `Priority` ausentes: deja vacios + log warning para revision manual.
|
||||
- `domain` y `scope` se infieren con heuristica por nombre/contenido (ej. `cpp-*` -> `cpp-stack`, `kanban-*` -> `kanban`, `trading-*` -> `trading`).
|
||||
2. Backup en `dev/issues/.backup_pre_0100/` antes de cualquier escritura.
|
||||
3. Output final: tabla de issues sin clasificar para review humano.
|
||||
|
||||
## Dominios canonicos (allowlist)
|
||||
|
||||
```
|
||||
meta, cpp-stack, kanban, trading, gamedev, osint, data-ingest,
|
||||
registry-quality, notify, imagegen, apps-infra, dev-ux, deploy,
|
||||
frontend, mcp, browser, telemetry, docs
|
||||
```
|
||||
|
||||
Cualquier issue con `domain:` fuera de esta lista hace fallar el indexer.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] Pipeline existe en `bash/functions/pipelines/` o `python/functions/pipelines/`.
|
||||
- [ ] 71 issues migrados sin perder contenido (diff vs backup solo en cabecera).
|
||||
- [ ] `dev/issues/README.md` ya no es fuente de verdad — se genera desde frontmatter via subcomando `/issue list` o cron diario.
|
||||
- [ ] Issues completados en `dev/issues/completed/` tambien migrados.
|
||||
- [ ] `fn doctor issues` (subcomando nuevo) reporta issues sin Type/Priority/Domain/Scope.
|
||||
- [ ] Pipeline idempotente (segunda corrida = 0 cambios).
|
||||
|
||||
## Definition of Done
|
||||
|
||||
### Generico
|
||||
|
||||
- [ ] **Repetibilidad**: pipeline corre N veces sin diff.
|
||||
- [ ] **Observabilidad**: log de campos inferidos vs por defecto.
|
||||
- [ ] **Error-path**: archivo malformado -> skip + log + exit code != 0.
|
||||
- [ ] **Idempotencia**: archivo ya migrado -> 0 cambios.
|
||||
- [ ] **Secrets**: N/A.
|
||||
- [ ] **Docs**: README de `dev/issues/` actualizado para apuntar al frontmatter.
|
||||
- [ ] **Registry-first**: pipeline reusa `parse_yaml_frontmatter_*` (crear si no existe).
|
||||
- [ ] **INDEX + status**: issue cerrado + movido a `completed/`.
|
||||
|
||||
### User-facing
|
||||
|
||||
- [ ] **User-facing**: tras correr el pipeline, `head -20 dev/issues/0099-datahub-app-launcher.md` muestra YAML legible + `/issue show 0099` (cuando exista) imprime tabla limpia.
|
||||
- [ ] **User-facing repeat**: cada issue nuevo creado con `/issue create` (issue 0101) hereda el formato.
|
||||
- [ ] **User-facing onboarding**: parrafo en `dev/issues/README.md`: "Cada issue empieza con frontmatter YAML. Para ver/filtrar: `/issue list --domain trading --status pendiente`."
|
||||
- [ ] **User-facing latencia**: migracion completa en <60s sobre 71 archivos.
|
||||
@@ -0,0 +1,100 @@
|
||||
# 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,77 @@
|
||||
# 0102 — Tab Work en registry_dashboard (issues + flows + telemetria)
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-16
|
||||
**Type:** feature
|
||||
**Priority:** media
|
||||
**Domain:** meta
|
||||
**Scope:** app-scoped
|
||||
**Depends:** 0100 (frontmatter migration), 0101 (dev_console --json)
|
||||
**Blocks:** —
|
||||
**Related:** 0103 (slash commands)
|
||||
|
||||
## Problema
|
||||
|
||||
Hoy para ver "estado global del trabajo" hay que:
|
||||
|
||||
1. `ls dev/issues/*.md` + leer cabeceras.
|
||||
2. `cat dev/flows/INDEX.md` + abrir flow por flow.
|
||||
3. `sqlite3 call_monitor.db` para metricas.
|
||||
4. Cruzar a mano que issues bloquean que flow.
|
||||
|
||||
Cero visibilidad cross-cutting. Y nada me dice "abre el flow 0001 ya, todos sus checks user-facing estan listos" o "issue 0099 esta verde pero su dependencia 0096 esta marcada como DONE incorrectamente".
|
||||
|
||||
## Objetivo
|
||||
|
||||
Tab nueva `Work` en `projects/fn_monitoring/apps/registry_dashboard` (C++ ImGui). Tres paneles:
|
||||
|
||||
### Panel 1 — Kanban issues
|
||||
|
||||
Columnas: `pendiente | in-progress | bloqueado | completado-hoy`. Filtros (combo): domain, type, priority. Card por issue muestra: id, title, prio, deps no resueltas (en rojo si las hay).
|
||||
|
||||
Drag entre columnas -> llama `dev_console issue tag NNNN --status X` por debajo.
|
||||
|
||||
### Panel 2 — Flows table
|
||||
|
||||
Tabla con columnas: id, slug, pattern, status, Acceptance %, DoD %, **DoD user-facing %**, ultima run en `data_factory.runs`. Click en fila -> abre archivo .md (o panel detalle al lado).
|
||||
|
||||
Boton `User-test` por fila -> lanza `dev_console flow user-test NNNN` (abre URL/app/sala Matrix declarada).
|
||||
|
||||
### Panel 3 — Telemetria (resumen call_monitor)
|
||||
|
||||
KPIs ultimas 24h: `calls_24h`, `violations_24h`, `pending_proposals`, `Reg %`. Sparkline 7d por KPI. Misma fuente que el hook UserPromptSubmit.
|
||||
|
||||
## Reglas
|
||||
|
||||
- ImGui + `data_table_cpp_viz` para tablas (registry-first).
|
||||
- Datos vienen de `dev_console work dashboard --json` (call cada 5s en debug, cada 30s en prod).
|
||||
- Si `dev_console` no esta instalado: panel muestra placeholder + comando para instalar (sin crash).
|
||||
- Tab carga en <300ms (issue 0101 garantiza el binario).
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] Tab Work aparece en `registry_dashboard` con los 3 paneles.
|
||||
- [ ] Filtros funcionan (domain, type, priority, pattern).
|
||||
- [ ] Drag de issue actualiza disco.
|
||||
- [ ] User-test boton abre superficie usuario.
|
||||
- [ ] Refresh manual + auto cada 30s.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
### Generico
|
||||
|
||||
- [ ] **Repetibilidad**: tab abre 10x sin leak handles ni memoria.
|
||||
- [ ] **Observabilidad**: cada accion (drag, click User-test) loguea via `fn_log`.
|
||||
- [ ] **Error-path**: `dev_console` falla -> tab muestra error formateado, no crash.
|
||||
- [ ] **Idempotencia**: refresh 100x = misma tabla.
|
||||
- [ ] **Secrets**: N/A.
|
||||
- [ ] **Docs**: `registry_dashboard.app.md` lista la tab + casos de uso.
|
||||
- [ ] **Registry-first**: reusa `data_table_cpp_viz`, `selectable_text`, `fn_log`.
|
||||
- [ ] **INDEX + status**: issue cerrado.
|
||||
|
||||
### User-facing
|
||||
|
||||
- [ ] **User-facing**: usuario abre `registry_dashboard.exe` -> tab Work -> ve issues kanban + flows table + KPIs todo en una pantalla.
|
||||
- [ ] **User-facing repeat**: mismo dashboard manana muestra estado actualizado sin reset (deps resueltas se reflejan).
|
||||
- [ ] **User-facing onboarding**: parrafo en `app.md`: "Para el estado del trabajo: lanzar `registry_dashboard.exe` -> tab Work. Boton User-test abre la superficie usuario del flow."
|
||||
- [ ] **User-facing latencia**: refresh <300ms; cambio en disco visible en <30s (auto-refresh).
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user