chore: auto-commit (799 archivos)

- .claude/CLAUDE.md
- .claude/commands/subagentes.md
- .claude/rules/INDEX.md
- .mcp.json
- bash/functions/cybersecurity/analyze_dns.md
- bash/functions/cybersecurity/audit_http_headers.md
- bash/functions/cybersecurity/audit_ssh_config.md
- bash/functions/cybersecurity/check_firewall.md
- bash/functions/cybersecurity/detect_suspicious_users.md
- bash/functions/cybersecurity/encrypt_file.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 00:28:20 +02:00
parent 20f72edb5a
commit 47fac22230
805 changed files with 5515 additions and 810 deletions
+110 -35
View File
@@ -18,6 +18,33 @@ Registry personal de codigo reutilizable con busqueda FTS. Diseñado para compos
---
## Delegacion + Capability Groups (REGLA DURA — issue 0086)
Claude **multiplica capacidades** delegando creacion de funciones a `fn-constructor` y reusandolas inmediatamente. NO escribir logica reutilizable inline.
### Flujo obligatorio (mismo turno)
1. **Detectar gap**. Si vas a escribir >=5 lineas de logica reutilizable inline -> STOP.
2. **Spawn `fn-constructor`** via `Agent(subagent_type="fn-constructor", ...)`. Sin preguntar al usuario.
3. **Paralelo**: si hay >1 funcion independiente -> **una sola llamada al Agent tool con N tool_use blocks paralelos** en mismo mensaje. NO serializar.
4. **Tag de grupo obligatorio** (`notebook`, `metabase`, `deploy`, etc.). Ver `docs/capabilities/INDEX.md`.
5. **`fn index`** + **importar + invocar en mismo turno**. No dejar funcion huerfana recien creada.
6. **Auto-verificar**: `fn doctor uses-functions` + `fn doctor unused` si tocas >=3 funciones nuevas.
### Capability groups
Cluster de >=3 funciones que comparten dominio operativo. Cada grupo tiene tag plano + pagina madre `docs/capabilities/<grupo>.md` con: lista de funciones, ejemplo canonico end-to-end, fronteras.
**Antes de buscar funciones sueltas en una tarea de dominio conocido:** lee `docs/capabilities/<grupo>.md` para cargar el cluster entero en un solo read. Filtro MCP: `mcp__registry__fn_search query="" tag="<grupo>"`.
Reglas completas: `.claude/rules/delegation.md` + `.claude/rules/capability_groups.md`.
### Telemetria CAPABILITY-GROWTH
Cada turno el hook `UserPromptSubmit` inyecta `CAPABILITY-GROWTH: created_this_session=X used=Y orphan=Z`. Si `orphan>0` -> integra la funcion antes de cerrar turno o documenta por que.
---
## Explorar el registry (OBLIGATORIO)
**SIEMPRE** consulta registry.db antes de escribir codigo, crear funciones, o responder sobre el registry. No uses grep/glob sobre archivos .go/.md — la BD es la fuente de verdad.
@@ -39,56 +66,67 @@ Registry personal de codigo reutilizable con busqueda FTS. Diseñado para compos
Razones: menos tokens, output estructurado, FTS5 escapado bien (sin gotchas de `column:"valor"`), permisos pre-aprobados, no requiere `cd` ni paths absolutos a `registry.db`.
**Cuando si caer a `sqlite3` (Bash):** SOLO si el MCP no cubre el caso — JOINs custom entre tablas, agregaciones (`COUNT`/`GROUP BY`), introspeccion de schema (`.schema`, `PRAGMA table_info`), o columnas que el MCP no expone. En ese caso, los patrones FTS5 estan documentados abajo.
**La BD contiene el codigo y la documentacion completa** de cada funcion y tipo en los campos `code`, `documentation` y `notes`. Tambien indexados en FTS5 — buscas dentro del codigo directamente. Para leer codigo: `mcp__registry__fn_code <id>`.
**La BD contiene el codigo y la documentacion completa** de cada funcion y tipo en los campos `code`, `documentation` y `notes`. Estos campos tambien estan indexados en FTS5, asi que puedes buscar dentro del codigo y la documentacion directamente. Para leer el codigo de una funcion: `mcp__registry__fn_code` (preferido) o `SELECT code FROM functions WHERE id = '...'` (fallback).
### Ejemplos MCP (usa estos, NO sqlite3)
**Busquedas FTS5 (cuando uses sqlite3 como fallback):** Usa SIEMPRE la tabla FTS5 para buscar tanto por `name` como por `description`. Esto encuentra coincidencias parciales y similares que una busqueda exacta perderia. Usa operadores FTS5: `OR` para ampliar, `*` para prefijos, `NEAR` para proximidad.
Cada llamada MCP se registra en `call_monitor` (issue 0085). Cada `sqlite3 registry.db "SELECT ..."` queda fuera del bucle reactivo y dispara el hook PreToolUse.
```bash
# Busqueda FTS5 por nombre Y descripcion (USAR SIEMPRE ESTE PATRON)
sqlite3 registry.db "SELECT id, kind, purity, description FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'name:slice OR description:slice') ORDER BY name;"
```
# Busqueda basica por nombre/descripcion (FTS5 detras)
mcp__registry__fn_search query="slice"
# FTS5 con prefijo (encuentra slice, slicing, sliced...)
sqlite3 registry.db "SELECT id, kind, purity, description FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'name:slic* OR description:slic*') ORDER BY name;"
# Filtros: kind, purity, domain, lang
mcp__registry__fn_search query="filter" kind="function" purity="pure" domain="core"
# FTS5 en tipos
sqlite3 registry.db "SELECT id, algebraic, description FROM types WHERE id IN (SELECT id FROM types_fts WHERE types_fts MATCH 'name:result OR description:result') ORDER BY name;"
# Prefijo FTS5 — encuentra slice/slicing/sliced
mcp__registry__fn_search query="slic*"
# FTS5 por semantica de params (composabilidad)
sqlite3 registry.db "SELECT id, json_extract(params_schema, '$.output') FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'params_schema:retornos');"
# Buscar tipos
mcp__registry__fn_search query="result" entity="types"
# Por dominio
sqlite3 registry.db "SELECT id, purity, signature FROM functions WHERE domain = 'finance' ORDER BY name;"
# Apps
mcp__registry__fn_search query="kanban" entity="apps"
# Puras de un dominio
sqlite3 registry.db "SELECT id, signature FROM functions WHERE domain = 'core' AND purity = 'pure' ORDER BY name;"
# Listar dominios
mcp__registry__fn_list_domains
# Tipos por dominio
sqlite3 registry.db "SELECT id, algebraic, description FROM types WHERE domain = 'cybersecurity';"
# Ver una entrada concreta (functions, types, apps, analysis, proposals...)
mcp__registry__fn_show id="filter_slice_go_core"
# Dependencias
sqlite3 registry.db "SELECT id, uses_functions, uses_types FROM functions WHERE uses_functions != '[]';"
# Codigo fuente de una funcion/tipo
mcp__registry__fn_code id="filter_slice_go_core"
# Proposals pendientes
sqlite3 registry.db "SELECT id, kind, status, title FROM proposals WHERE status = 'pending';"
# Quien consume una funcion (consumidores indexados via uses_functions)
mcp__registry__fn_uses id="filter_slice_go_core"
# Schema completo
sqlite3 registry.db ".schema"
# Proposals (pending, approved, ...)
mcp__registry__fn_proposal action="list" status="pending"
mcp__registry__fn_proposal action="show" id="<proposal_id>"
# Diagnostico read-only del registry (artefacts/services/sync/uses-functions/unused/cpp-apps)
mcp__registry__fn_doctor subcommand="artefacts"
mcp__registry__fn_doctor subcommand="sync"
```
**Regla:** Si necesitas saber si algo existe o hay algo similar, usa `mcp__registry__fn_search` (preferido) o consulta FTS5 con sqlite3 (fallback). No asumas que no existe sin consultar primero.
**Escapado FTS5 (gotcha cuando pasas query libre):** valores con `-`, `.`, `:`, espacios rompen el parser FTS5 si los expones como `column:valor`. El MCP escapa por defecto, pero si construyes una `query` con sintaxis FTS5 explicita, encierra el valor en comillas dobles:
**Escapado FTS5 (gotcha):** despues de `column:` el valor debe ser un solo token alfanumerico ASCII (underscores OK). Cualquier otro caracter (`-`, `.`, `:`, espacios) rompe el parser con `no such column: X` o `syntax error near "."`. Encierra el valor en comillas dobles dentro del MATCH:
```bash
# MAL: description:single-page → "no such column: page"
# MAL: description:embed.FS → 'syntax error near "."'
# BIEN:
sqlite3 registry.db "SELECT id FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'description:\"single-page\" OR description:\"embed.FS\"');"
```
# MAL: query="description:single-page" -> "no such column: page"
# BIEN
mcp__registry__fn_search query='description:"single-page" OR description:"embed.FS"'
mcp__registry__fn_search query='description:"react router"'
```
Tokens multi-palabra tambien necesitan comillas: `description:"react router"`.
### Excepciones autorizadas para sqlite3 directo
`sqlite3 registry.db` SOLO es legitimo si el MCP no expone la consulta:
- Introspeccion de schema: `.schema`, `.tables`, `PRAGMA table_info(...)`, `PRAGMA index_list(...)`.
- Agregaciones: `COUNT(*)`, `GROUP BY`, `SUM(...)`, `AVG(...)`.
- JOINs custom entre tablas (ej. `functions JOIN unit_tests ON ...`) no expuestos por el MCP.
Cualquier `SELECT ... FROM functions/types/apps/proposals WHERE ...` plano se hace via MCP. El hook PreToolUse avisa si ve `sqlite3 registry.db "SELECT ..."`.
### Schema rapido
@@ -109,7 +147,7 @@ Tokens multi-palabra tambien necesitan comillas: `description:"react router"`.
- `entity_type`: app, analysis, project, vault
- `status`: active, missing, archived
- Se puebla con `fn sync`, NO con `fn index`
- Consultas: `SELECT * FROM pc_locations WHERE pc_id = 'home-wsl'`
- Consultas: `mcp__registry__fn_doctor subcommand="sync"` (drift PC vs disco) o `sqlite3 registry.db "SELECT ... GROUP BY pc_id"` SOLO para agregaciones que el MCP no expone
**FTS5 (columnas buscables):**
- `functions_fts`: id, name, description, tags, signature, domain, example, notes, documentation, code, params_schema
@@ -118,6 +156,43 @@ Tokens multi-palabra tambien necesitan comillas: `description:"react router"`.
---
## Como invocar funciones del registry (CANONICO)
Tres patrones, uno por caso de uso. Toda invocacion del agente se loguea en `projects/fn_monitoring/apps/call_monitor/operations.db` para alimentar el bucle reactivo (issue 0085).
| Caso de uso | Patron canonico | Cuando usar |
|---|---|---|
| **Inspeccionar** registro (buscar, leer codigo, ver dependencias, dominios) | `mcp__registry__fn_search` / `fn_show` / `fn_code` / `fn_uses` / `fn_list_domains` | SIEMPRE para descubrimiento. Reemplaza `sqlite3 registry.db "SELECT ..."` inline. |
| **Ejecutar** UNA funcion o pipeline con sus args | `mcp__registry__fn_run <id> [args]` (preferido) o `./fn run <id> [args]` (fallback CLI) | Cuando hay UN id conocido a lanzar. Despacho automatico por lenguaje. Salida estructurada. |
| **Componer** ad-hoc varias funciones con logica intermedia | Heredoc `python/.venv/bin/python3 - <<'PYEOF' ... PYEOF` IMPORTANDO funciones del registry | Solo cuando hay loops/conditionals/dispatch entre N funciones. Las funciones del registry **se importan**, no se reescriben. |
Regla decisiva: antes de cada bloque de codigo, decide caso. Si dudas entre 2 y 3, casi siempre es 2 (un MCP run con args). Si el caso 3 se repite con el mismo shape >5 veces entre sesiones, **es candidato a pipeline** en `python/functions/pipelines/`.
### Antipatrones prohibidos
| Patron | Por que es malo | Sustituir por |
|---|---|---|
| `sqlite3 registry.db "SELECT ..."` para buscar funciones/tipos | Salta MCP, FTS5 gotchas, sin trazabilidad. Hook PreToolUse ya avisa. | `mcp__registry__fn_search` |
| `python -c "import metabase; print(dir(metabase))"` o `help(metabase)` para descubrir helpers | La fuente de verdad es el registry, no el `__init__.py` | `mcp__registry__fn_search "metabase"` + `mcp__registry__fn_show <id>` |
| Heredoc que reescribe logica que ya existe como funcion del registry | Reinvento + perdida de capitalizacion | Buscar primero; si falta, delegar a `fn-constructor` (no escribir inline) |
| `client._http.request(...)` directo cuando hay wrapper en el registry | Salta validacion del wrapper y telemetria | Usar wrapper; si la firma no cubre el caso, proponer extension via `fn proposal add` |
| Scripts en `temp/` para composiciones que se repiten | Codigo se pierde y no se monitoriza | Pipeline en `python/functions/pipelines/` o pipeline Bash en `bash/functions/pipelines/` |
| Imports `from <pkg> import *` en heredoc | Imposible saber que funcion del registry se uso | Imports explicitos `from <domain> import <name1>, <name2>` |
Excepciones autorizadas para `sqlite3` directo (no requieren MCP): `.schema`, `.tables`, `PRAGMA table_info`, `COUNT(*) GROUP BY`, JOINs custom entre tablas que el MCP no expone.
### Trazabilidad y bucle reactivo
Hook `PostToolUse` en `.claude/settings.local.json` parsea cada comando Bash + cada `mcp__registry__*` y escribe en la `operations.db` del call_monitor. Datos consumidos por:
1. **Tab "Claude usage" en `registry_dashboard`** — top funciones, latencias, error rate, huerfanas con `calls_90d=0`.
2. **Fase MEJORAR del bucle reactivo** — patrones inline repetidos generan proposals `new_function` con evidencia (session_ids + snippets). Funciones con error_rate alto y muchas llamadas suben en prioridad de bugfix.
3. **Auditoria de reglas** — assertions sobre `violation_count`, `mcp_ratio`, `heredoc_repetition`. Si fallan critical → proposal "actualizar CLAUDE.md / prompt del agente".
Datos sensibles: solo se guarda `args_hash`, NUNCA valores concretos de argumentos.
---
## Estructura
```
@@ -237,7 +312,7 @@ Entornos usados automaticamente:
## Añadir funciones
1. Consulta la BD para verificar que no existe algo similar
1. `mcp__registry__fn_search query="<nombre|desc>"` para verificar que no existe algo similar
2. Crea dos archivos segun el lenguaje:
- Go: `functions/{domain}/{name}.go` + `.md`
- Python: `python/functions/{domain}/{name}.py` + `.md`
+121
View File
@@ -0,0 +1,121 @@
# /autonomous-task — Lanza fn-orquestador (Fase 6 del ciclo reactivo)
Lanza el meta-orquestador autonomo que recorre el bucle CONSTRUIR → EJECUTAR → RECOPILAR → ANALIZAR → MEJORAR sobre un issue, sin intervencion humana, hasta convergencia / estancamiento / timeout / limite de iteraciones.
Issue 0069. Pre-condiciones obligatorias (chequear ANTES de despachar):
1. Migration `fn_operations/migrations/006_task_runs.sql` aplicada.
2. Subagentes `fn-constructor`, `fn-executor`, `fn-recopilador`, `fn-analizador`, `fn-mejorador`, `fn-orquestador` presentes en `.claude/agents/`.
3. `dev/autonomous_protected_paths.json` existe.
4. `master` local up-to-date con `origin/master`.
5. Branch `auto/<issue_id>` NO existe ya.
6. `gh auth status` OK (necesario para PR draft al converger).
7. Tipo de tarea soportado: `feature_app_simple`, `bugfix_with_repro`, `refactor_safe`, `add_e2e_check`.
Si alguna pre-condicion falla → ABORT con razon. NO improvisar.
---
## Argumento
`$ARGUMENTS``<issue_id>` o `<task_spec_path>` + flags opcionales.
```
/autonomous-task 0070
/autonomous-task 0070 --max-iterations 15 --max-minutes 90
/autonomous-task 0070 --auto-apply-proposals safe
/autonomous-task 0070 --dry-run
/autonomous-task path/to/spec.yaml --branch auto/custom-name
```
Flags:
- `--max-iterations N` tope de iteraciones (default 10)
- `--max-minutes M` timeout total (default 60)
- `--auto-apply-proposals` `none|safe|aggressive` (default `safe`)
- `--branch NAME` rama TBD (default `auto/<issue_id>`)
- `--dry-run` simula, NO aplica
---
## Comportamiento
1. **Verificar pre-condiciones** con script bash (ver arriba). Si alguna falla, reportar y salir.
2. **Despachar a `fn-orquestador`** via Agent tool con `subagent_type=fn-orquestador`. Pasar:
- `issue_id` o `task_spec`
- flags resueltos
- paths protegidos (leidos de `dev/autonomous_protected_paths.json`)
3. **El subagente:**
- Crea worktree aislado `/tmp/fn_orq_<issue>_<ts>/` desde `master`.
- Persiste estado en `task_runs` (operations.db del app target o repo root).
- Despacha por fases a los 5 subagentes especializados.
- Aplica proposals filtradas por `--auto-apply-proposals`.
- Termina con: `converged` (PR draft creado) | `stalled` | `timeout` | `iterations_exhausted` | `needs_human` | `aborted`.
4. **Reportar resultado al humano** con:
- `status`, `iterations / max`, `duration / max`
- `branch`, `worktree`, `PR draft url` si converged
- `proposals creadas / aplicadas`
- `last run_id` y status
- Resumen iter-por-iter del `progress_json`
---
## Reglas duras (no negociables)
- Sandbox de rama EN WORKTREE — nunca toca master ni el working tree del humano.
- No merge automatico — PR draft siempre.
- No `--no-verify`, no `--force`, no skip hooks.
- Paths protegidos via `dev/autonomous_protected_paths.json`.
- Watchdog: 2 iteraciones con mismo set de fails → `status=stalled`.
- Auditoria total en `task_runs.progress_json`.
- No self-modification: NO toca `.claude/agents/` ni `.claude/commands/`.
---
## Integracion con call_monitor (issue 0085)
El orquestador puede leer `projects/fn_monitoring/apps/call_monitor/operations.db` para:
- Consultar `function_stats` antes de decidir que funciones usar/reusar.
- Filtrar proposals existentes via `mcp__registry__fn_proposal --status pending` para evitar duplicados.
- Loggear sus invocaciones via el hook PostToolUse (automatico).
Tras converger, el `call_monitor propose` ejecutado por el humano (o futuro cron) absorbera las nuevas violations / copied_code / fails para alimentar la siguiente ronda.
---
## Tipos NO soportados
- Diseño arquitectura nuevo (humano decide).
- Decisiones UX subjetivas.
- Cambios BD productiva.
- Cualquier cosa que toque secrets/credenciales.
- Self-modification del propio orquestador.
Si el issue contiene criterios no-verificables programaticamente, ABORT con `status=needs_human`.
---
## Output canonico
```
=== /autonomous-task: 0070 ===
status: converged
iterations: 7 / 10
duration: 23 min / 60
branch: auto/0070
worktree: /tmp/fn_orq_0070_1731612345
PR draft: https://github.com/.../pull/123
proposals: 3 creadas, 2 auto-aplicadas
last run_id: e2e_run_abc123 (status: pass)
Iter:
1. construir → ok (2 funciones nuevas)
2. ejecutar → ok
3. analizar → fail (2/8 checks)
4. mejorar → 3 proposals (2 auto-applicadas)
5. construir → ok (re-build tras patches)
6. analizar → pass
7. recopilador → ok (operations.db integra)
Siguiente: revisar PR draft + fn proposal list -s pending --target-id 0070
```
+191
View File
@@ -0,0 +1,191 @@
---
description: "Auto-auditoria: verifica que la sesion registra uso de funciones, detecta gaps (patrones inline repetidos, wrappers saltados, heredocs sin function_id), lanza fn-constructor en paralelo para crear las funciones que faltan, y valida que Claude usara las nuevas en el siguiente turno"
---
# /fn_claude — auto-auditoria + auto-construccion del registry
Comando meta: Claude se audita a si mismo. Verifica que su comportamiento en esta sesion (y las recientes) deja rastro en `call_monitor.operations.db`, detecta gaps reales del registry para el trabajo actual, lanza sub-agentes `fn-constructor` en paralelo para cerrar esos gaps, y verifica que la proxima vez usara las funciones nuevas.
Issue 0085 fase autocompleta. Reemplaza el flujo manual de "veo un patron, decido si extraer, escribo proposal, espero humano, fn-mejorador genera, fn-orquestador opera". Con `/fn_claude` Claude hace todo eso solo, **autonomamente para si mismo**.
---
## Comportamiento (ejecutalo en este orden)
### 1. AUDIT — ¿estoy siendo registrado?
```bash
ROOT="/home/lucas/fn_registry"
MON="$ROOT/projects/fn_monitoring/apps/call_monitor/operations.db"
# Pre-condiciones
[ -f "$MON" ] || { echo "call_monitor.operations.db NO existe — issue 0085a no aplicado"; exit 1; }
[ "$FN_TELEMETRY" = "1" ] || echo "WARNING: FN_TELEMETRY != 1 — wrappers Python/Bash inactivos"
# Metricas de la sesion actual + ultimas 24h
sqlite3 "$MON" <<SQL
SELECT 'calls_session', COUNT(*) FROM calls WHERE session_id = '${CLAUDE_SESSION_ID:-unknown}'
UNION ALL SELECT 'calls_24h', COUNT(*) FROM calls WHERE ts >= CAST(strftime('%s','now','-1 day') AS INTEGER)
UNION ALL SELECT 'violations_24h', COUNT(*) FROM violations WHERE ts >= CAST(strftime('%s','now','-1 day') AS INTEGER)
UNION ALL SELECT 'tool_used_distribution_24h', NULL;
SELECT tool_used, COUNT(*) FROM calls WHERE ts >= CAST(strftime('%s','now','-1 day') AS INTEGER) GROUP BY tool_used ORDER BY 2 DESC;
SQL
```
Si `calls_session = 0` → algo esta mal (hook PostToolUse no fire o BD no escribible). Reporta y para.
Si `mcp_*` / total < 0.4 → estas usando demasiado heredoc/sqlite directo. Reporta como warning.
### 2. GAP — ¿que funciones faltan?
Dos fuentes:
#### 2a. Patrones repetidos en heredocs/Edit
```sql
-- En call_monitor.operations.db
SELECT tool_used, COUNT(*) AS hits
FROM calls
WHERE function_id = ''
AND ts >= CAST(strftime('%s','now','-7 days') AS INTEGER)
AND tool_used IN ('heredoc_py', 'heredoc_bash', 'sqlite_direct')
GROUP BY tool_used;
```
Si `heredoc_py > 5` sin function_id → Claude esta componiendo logica que probablemente debe ser pipeline. Investigar el ultimo heredoc del transcript: si reescribe algo que ya es funcion del registry → violation candidate. Si no, es candidato a pipeline nuevo.
#### 2b. Trabajo actual de la sesion — gap inferido del contexto
Lee el ultimo prompt del usuario y los ultimos 10 turnos. Lista funciones que:
- Has llamado inline (sed/awk/jq custom, transformaciones de datos, parsing).
- Has reinventado (HTTP client raw, SQLite open con flags, FS walks).
- Has compuesto >2 veces con el mismo shape.
Para cada candidato:
```bash
# Verifica si ya existe algo similar en el registry
mcp__registry__fn_search "<keyword del candidato>"
```
Si NO existe match relevante → candidato a `fn-constructor`.
Si existe pero firma incompleta → candidato a `improve_function` (proposal, NO auto-construccion).
### 3. PROPOSE — lista candidatos
Genera tabla:
```
| Candidato | Razon | Lenguaje | Dominio | Evidencia (snippet) |
|---|---|---|---|---|
| <name> | inline_repeated/wrapper_skip/new | go/py/bash | core/infra/... | <heredoc fragment> |
```
Si lista vacia → "no gaps detected, sesion saludable" + reporta metricas. Para.
### 4. CONSTRUCT — lanza fn-constructor en paralelo
Para cada candidato, dispara un sub-agente `fn-constructor` con prompt autocontenido:
```
Agent(subagent_type="fn-constructor", prompt=...)
```
Prompts en PARALELO en un mismo mensaje (varios Agent calls). Pasar:
- nombre propuesto, lang, domain
- firma esperada (params + return)
- pureza
- descripcion + ejemplo de uso (heredoc real detectado)
- nota: "esta funcion la necesita Claude para auto-uso futuro"
### 5. VALIDATE — ¿la proxima sesion la usara?
Despues de que fn-constructor termine:
```bash
./fn index 2>&1 | tail -2
# Verifica que las nuevas funciones existen
for fn in <lista>; do
mcp__registry__fn_show "$fn" >/dev/null && echo "OK: $fn" || echo "FAIL: $fn"
done
```
Tambien actualiza `call_monitor.copied_code` + `function_stats` corriendo:
```bash
cd "$ROOT/projects/fn_monitoring/apps/call_monitor" && ./call_monitor copied-code && ./call_monitor propose
```
Reporta:
- N funciones nuevas creadas (con IDs)
- N proposals nuevas en `registry.db.proposals`
- Recomendacion al usuario: "proximo turno mencionar/usar `<fn_id>` para validar que el wrapper se invoca correctamente"
### 6. SELF-TEST — telemetria del propio /fn_claude
`/fn_claude` mismo debe quedar registrado. Tras ejecutar, query final:
```bash
sqlite3 "$MON" "SELECT COUNT(*) FROM calls WHERE session_id = '${CLAUDE_SESSION_ID:-unknown}' AND ts >= <inicio_comando>"
```
Si la cuenta no aumento → el comando esta operando fuera de la telemetria (bug). Reportar.
---
## Reglas duras
1. **NO ejecutar fn-constructor para algo que ya existe.** Buscar primero via `mcp__registry__fn_search`. Si match relevante → NO crear duplicado.
2. **NO crear funciones especulativas.** Cada candidato debe tener evidencia real (snippet de heredoc o llamada inline detectada en esta sesion o en `call_monitor.calls` reciente).
3. **PARALELO**: si hay >1 candidato, lanza todos los `fn-constructor` en un solo mensaje con multiples `Agent` calls. NO secuencial.
4. **No autonomous merge**: las funciones nuevas viven en el branch local. NO push automatico. Humano revisa y push manual.
5. **Limites duros**: max 5 funciones nuevas por invocacion. Si detectas mas, prioriza por evidence weight (`occurrences * recency`) y reporta el resto como pending.
6. **Si la sesion no esta siendo registrada (`calls_session = 0`)**: ABORT antes de fase 2. No tiene sentido auto-construir sin telemetria.
---
## Output canonico
```
=== /fn_claude — auto-auditoria ===
session_id: <id>
calls_session: N
calls_24h: M (mcp_ratio: 0.XX)
violations_24h: K
pending_proposals: P (existentes en registry.db)
GAPS DETECTADOS:
1. <name>_<lang>_<domain> — razon — evidencia
2. ...
LANZADOS (en paralelo):
fn-constructor #1: <name1> → en progreso
fn-constructor #2: <name2> → en progreso
...
VALIDADAS tras ./fn index:
✓ <name1>_<lang>_<domain>
✓ <name2>_<lang>_<domain>
PROPOSALS NUEVAS: <count>
PROXIMO TURNO: menciona `<name1>` para validar wrapper.
```
---
## Cuando usar
- Al inicio de una sesion larga, para verificar telemetria activa.
- A media sesion, cuando notes que estas reescribiendo el mismo bloque.
- Antes de cerrar sesion, para capitalizar lo aprendido como funciones reutilizables.
- Tras `/autonomous-task` para validar que el orquestador no genero ruido (proposals/funciones huerfanas).
---
## Cuando NO usar
- En sesiones cortas (<5 turnos) — no hay datos suficientes.
- Si `call_monitor.operations.db` no esta inicializado (`call_monitor init` primero).
- Si el usuario quiere control manual del proceso de extraccion. Este comando es agresivo.
+17
View File
@@ -77,4 +77,21 @@ Input: <paths absolutos, IDs registry, run_id si aplica>
Tarea: <accion concreta y acotada>
Criterio exito: <como sabe que termino>
Limites: <que NO debe tocar>
Telemetria: tus tool calls quedan registradas en projects/fn_monitoring/apps/call_monitor/operations.db
via hook PostToolUse heredado de settings.local.json. Sigue patrones canonicos
(mcp__registry__fn_*, ./fn run, heredoc importando) — los antipatrones se loguean
como violations.
```
## Telemetria heredada (issue 0085 hardening 5)
Los hooks de `.claude/settings.local.json` se heredan automaticamente por cada sub-agente que Claude Code lance via la tool `Agent`. Eso significa:
- Cada Bash, Edit, Write, MultiEdit, `mcp__registry__*` del sub-agente dispara `hook_call_monitor.sh` exactamente igual que en la sesion principal.
- El `session_id` del JSON de input del hook viene del sub-agente, distinto al de la sesion padre. Util para auditar comportamiento por agente.
- Las violations detectadas (sqlite3 directo, heredoc reinventando, etc) cuentan tambien para sub-agentes — un `fn-constructor` que reescribe inline en lugar de delegar a otro `fn-constructor` queda registrado.
- `FN_TELEMETRY=1` esta en el `env` block de settings.local.json — los heredocs Python/Bash de sub-agentes ya tienen wrappers activos automaticamente.
Implicacion: NO necesitas pasar flags `--telemetry` a sub-agentes. Solo asegurate de que el prompt sigue patrones canonicos. La regla `.claude/rules/registry_calls.md` se aplica igual.
Si un sub-agente abre un proceso hijo que escapa al hook (ej. `nohup ... &`, daemons), ese subproceso queda fuera de la telemetria — documentalo en el prompt si es un caso valido.
+3
View File
@@ -30,3 +30,6 @@ Reglas operativas del proyecto. Cada archivo es una regla independiente.
| 24 | [feature_flags.md](feature_flags.md) | TBD: feature flags para mergear codigo incompleto sin romper master. Patrones por stack (Go/TS/Bash/Py), branch-by-abstraction, anti-patrones |
| 25 | [db_migrations.md](db_migrations.md) | Migraciones SQLite obligatorias para cualquier cambio de schema. Aditivas, idempotentes, archivos numerados. Nunca borrar .db ni modificar migraciones existentes |
| 26 | [e2e_validation.md](e2e_validation.md) | Contrato `e2e_checks` en `app.md` consumido por fn-analizador (fase 4 del bucle reactivo). Issue 0068 |
| 27 | [registry_calls.md](registry_calls.md) | Patrones canonicos para invocar funciones del registry (MCP inspect / MCP run / heredoc compose), antipatrones, excepciones, telemetria. Issue 0085 |
| 28 | [delegation.md](delegation.md) | Si vas a escribir logica reutilizable inline -> spawn fn-constructor inmediato + tag de grupo + usar en mismo turno. Issue 0086 |
| 29 | [capability_groups.md](capability_groups.md) | Tags planos + paginas madre `docs/capabilities/<grupo>.md` para desbloquear clusters de funciones en un read. Issue 0086 |
+60
View File
@@ -0,0 +1,60 @@
## Capability groups: tags + paginas madre en docs/capabilities/
Un **capability group** es un cluster de >=3 funciones del registry que comparten un dominio operativo (ej. `notebook`, `metabase`, `deploy`). Cada grupo tiene un **tag plano** (sin prefijo) y una **pagina madre** en `docs/capabilities/<grupo>.md`. La pagina madre desbloquea el conjunto entero en un solo read.
### Para que existen
Sin grupos, Claude redescubre funciones via FTS5 una a una cada sesion ("¿como interactuo con Jupyter? ¿como subo deploy?"). Con grupos, Claude lee `docs/capabilities/<grupo>.md` y carga las 5-10 funciones del cluster con su ejemplo canonico — menos turnos perdidos en discovery.
### Convencion de tag
- **Slug del grupo** = tag plano. Ej: `notebook`, `metabase`, `android-emu`.
- **No prefijos** (`cap:`, `group:`). Ya hay namespacing implicito porque convivirian con tags semanticos sueltos.
- **Una funcion puede llevar varios tags de grupo** si pertenece a dos clusters (raro pero valido).
- Filtro MCP: `mcp__registry__fn_search query="" tag="notebook"` lista el grupo.
### Cuando crear grupo nuevo
- **Minimo 3 funciones** afines. Con 2 no compensa pagina madre — quedan tags sueltos.
- **Dominio operativo claro**: el grupo debe ser describible en 1 frase ("operar Jupyter colaborativo", "deploy via SSH+systemd").
- **Frontera neta** con grupos existentes. Si solapa con otro -> reorganizar, no duplicar.
### Como crear grupo
1. Anadir el tag al frontmatter `.md` de >=3 funciones afines. `fn index` lo registra.
2. Crear `docs/capabilities/<grupo>.md` con plantilla:
- **Lista de funciones**: tabla `ID | firma corta | que hace`.
- **Ejemplo canonico**: 1-2 bloques de codigo end-to-end con los IDs reales.
- **Fronteras**: que NO cubre el grupo.
- **Prerequisitos** y **notas** si aplica.
3. Anadir fila al `docs/capabilities/INDEX.md`.
4. Correr `fn doctor capabilities` para auditar drift.
### Auto-generacion
`fn doctor capabilities --update` (TBD) reescribe la tabla de funciones de cada pagina madre preservando bloques curated (`Ejemplo canonico`, `Fronteras`, `Notas`). Las secciones curated nunca se sobrescriben.
### Como Claude usa los grupos
Cuando una tarea cae en un dominio conocido:
1. `Read docs/capabilities/INDEX.md` para localizar grupo.
2. `Read docs/capabilities/<grupo>.md` para cargar funciones + ejemplo.
3. Solo si el grupo no cubre lo necesario, `mcp__registry__fn_search` para funciones sueltas.
4. Si el grupo deberia cubrir pero falta funcion -> `fn-constructor` + tagear con el grupo en el frontmatter.
### Auditoria
```bash
fn doctor capabilities # lista grupos + drift
fn doctor capabilities --json # para agentes
```
Comprueba:
- Tag con N >=3 funciones pero sin pagina madre -> "tag huerfano".
- Pagina madre sin tag respaldo -> "grupo fantasma".
- Funcion con tag de grupo pero la pagina madre no la lista (autogen desfasada) -> "drift".
### Relacion con dominios
Los **dominios** del registry (`core`, `infra`, `finance`, `datascience`, `cybersecurity`, `shell`, `tui`, `pipelines`, `browser`) son taxonomia ortogonal — un grupo puede atravesar varios dominios (ej. `deploy` toca `infra` y `shell`). NO renombrar dominio a grupo ni viceversa.
+42
View File
@@ -0,0 +1,42 @@
## Delegacion: spawn fn-constructor en vez de escribir inline
**REGLA DURA.** Si vas a escribir logica reutilizable inline en un artefacto (app, analysis, playground) o heredoc, STOP y delega a `fn-constructor`. La misma sesion debe crear + usar la funcion. No acumular huerfanas.
### Cuando un patron es candidato a funcion
- Aparece >=2 veces en esta sesion o en heredocs recientes.
- Firma generica (no depende de tipos internos del artefacto).
- 1 responsabilidad clara (CRUD, parse, transform, http call, formato fijo, etc.).
- No es one-liner idiomatico de stdlib (`time.Now().UTC().Format(...)` queda fuera).
### Flujo obligatorio (mismo turno)
1. **Detectar**. 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** (autorizado por defecto).
- Si hay >1 funcion independiente -> una sola llamada al Agent tool con **N tool_use blocks paralelos** en el mismo mensaje. NO serializar.
3. **Tagear con grupo de capacidad** al menos UN tag de grupo (`notebook`, `metabase`, `deploy`, etc.). Ver `capability_groups.md`.
4. **`fn index`** para registrar.
5. **Importar + invocar en el mismo turno** — no dejar funcion huerfana recien creada.
6. **Auto-verificar** con `fn doctor uses-functions` y `fn doctor unused` si tocas >=3 funciones nuevas.
### Anti-patrones auditables
| Anti-patron | Consecuencia | Sustituir por |
|---|---|---|
| Escribir helper inline en artefacto en vez de delegar | Reinvento por sesion | Spawn fn-constructor |
| Crear N funciones serialmente | Latencia x N | Multiples `Agent()` en mismo mensaje |
| Crear funcion y no usarla en el turno | Huerfana desde dia 1 (`calls_90d=0`) | Importar + invocar antes de cerrar turno |
| Crear funcion sin tag de grupo | Imposible descubrir en bloque proxima sesion | Anadir tag de grupo (capability group) |
| Reescribir en heredoc logica que ya existe | Capitalizacion perdida | `mcp__registry__fn_search` antes de escribir |
### Excepciones
- **Logica de dominio especifica del artefacto** (CRUD de tabla concreta, layout de UI, flujo unico de la app) -> queda en el artefacto. Solo lo reutilizable se delega.
- **Stub temporal con `not implemented`**: aceptable si la dependencia externa no esta disponible. Documentar en `.md` (ver `stubs.md`).
### Telemetria
Cada `code_writes` + `calls` se registra en `call_monitor/operations.db` (issue 0085). Vista `session_capability_growth` mide ratio creadas vs usadas por sesion. Hook `UserPromptSubmit` inyecta `CAPABILITY-GROWTH: created_this_session=X used=Y orphan=Z` en cada turno.
Si `orphan>0` al cerrar la sesion -> revisar: o la funcion era especulativa (no debio crearse) o falta integrarla en el codigo del artefacto.
+132
View File
@@ -0,0 +1,132 @@
## Como invocar funciones del registry — patrones canonicos
Toda invocacion del agente al registry sigue uno de **tres patrones**. Cualquier otro patron es antipatron auditable. Las invocaciones se loguean en `projects/fn_monitoring/apps/call_monitor/operations.db` (issue 0085) para alimentar el bucle reactivo.
### Patrones canonicos
| Caso | Patron | Cuando |
|---|---|---|
| **Inspeccionar** (buscar, leer codigo, ver dependencias, listar dominios, leer proposals) | `mcp__registry__fn_search` / `fn_show` / `fn_code` / `fn_uses` / `fn_list_domains` / `fn_proposal` | SIEMPRE para descubrimiento, lectura de codigo, exploracion. |
| **Ejecutar** UNA funcion/pipeline con sus args | `mcp__registry__fn_run <id> [args]` (preferido) o `./fn run <id> [args]` (fallback CLI) | ID conocido + args planos. Despacho automatico por lenguaje. |
| **Componer** ad-hoc multi-funcion con logica intermedia | Heredoc `python/.venv/bin/python3 - <<'PYEOF' ... PYEOF` IMPORTANDO funciones del registry | Solo si hay loops/conditionals/dispatch entre N funciones. Las funciones del registry **se importan**, no se reescriben. |
### Antipatrones prohibidos (audit-targeted)
| Patron | Razon | Sustituir por |
|---|---|---|
| `sqlite3 registry.db "SELECT ..."` para buscar funciones/tipos | Salta MCP, FTS5 gotchas, sin trazabilidad | `mcp__registry__fn_search` |
| `sqlite3 registry.db "SELECT ... FROM proposals"` | Mismo problema | `mcp__registry__fn_proposal` |
| `python -c "import metabase; dir(metabase)"` para descubrir helpers | Fuente de verdad = registry, no `__init__.py` | `mcp__registry__fn_search "metabase"` + `mcp__registry__fn_show <id>` |
| Heredoc que reescribe logica que ya existe como funcion del registry | Reinvento + perdida de capitalizacion | Buscar primero; si falta, delegar a `fn-constructor` (no escribir inline) |
| `client._http.request(...)` saltando wrapper del registry | Salta validacion del wrapper y telemetria | Usar wrapper; si firma incompleta, `fn proposal add --kind improve_function` |
| Scripts en `temp/` para composiciones que se repiten >2 veces | Codigo perdido + sin monitoreo | Pipeline en `python/functions/pipelines/` o `bash/functions/pipelines/` |
| `from <pkg> import *` en heredoc | Imposible identificar funciones usadas | Imports explicitos `from <domain> import <name1>, <name2>` |
### Excepciones autorizadas para `sqlite3` directo
Casos donde el MCP no aplica y `sqlite3 registry.db` es legitimo:
- Introspeccion de schema: `.schema`, `.tables`, `PRAGMA table_info(...)`, `PRAGMA index_list(...)`.
- Agregaciones: `COUNT(*)`, `GROUP BY`, `SUM(...)`, `AVG(...)`.
- JOINs custom entre tablas que el MCP no expone (`functions JOIN unit_tests ON ...`).
- Columnas que el MCP no devuelve (rare; preferir proponer ampliacion del MCP).
El hook `PreToolUse` (`.claude/scripts/hook_registry_mcp.sh`) ya deja pasar estas excepciones y solo avisa cuando ve `sqlite3 registry.db "SELECT ..."` plano.
### Verificacion previa — `fn doctor`
Antes de empezar trabajo no trivial sobre el registry, ejecutar `fn doctor` para confirmar que el ecosistema esta sano:
- Artefactos OK (sin `git_not_initialized`, `venv_broken_path`, etc.).
- Services activos cuando se necesiten (`sqlite_api`, `registry_api`, `registry_mcp`).
- Sin drift `pc_locations` vs disco.
- Sin drift `uses_functions` vs imports reales.
Si `fn doctor` reporta `service inactive` para `registry_mcp.service`, el MCP estara siendo invocado en modo stdio por Claude Code (normal); el systemd unit solo aplica al modo HTTP. Si el binario no responde, rebuild: `cd apps/registry_mcp && CGO_ENABLED=1 go build -tags fts5 -o registry_mcp .`.
### Tools MCP disponibles
| Tool | Lectura/escritura | Gating |
|---|---|---|
| `fn_search` | read | siempre on |
| `fn_show` | read | siempre on |
| `fn_code` | read | siempre on |
| `fn_uses` | read | siempre on |
| `fn_list_domains` | read | siempre on |
| `fn_proposal` | read | siempre on |
| `fn_doctor` | read | siempre on |
| `fn_run` | execute (mutating side-effects) | requiere `--enable-run` |
| `fn_create_function` | write | requiere `--enable-write` |
### Heredoc Python — convenciones obligatorias
Cuando el caso 3 (composicion) sea inevitable:
1. **Imports explicitos** desde paquetes del registry. Nunca `import *`.
2. **No reescribir** la firma de una funcion del registry — importarla.
3. **Args via env vars o stdin JSON**, nunca interpolacion shell directa (inyeccion).
4. **Output a stdout JSON** cuando vaya a ser consumido por el siguiente paso.
5. **Si el heredoc supera ~30 lineas**, extraer a `python/functions/pipelines/`. El monitor avisara automaticamente cuando un patron similar se repita >5 veces.
### Trazabilidad — bucle reactivo
Cada evento alimenta a `call_monitor.db` (event-log append-only) y se rollupea en una vista `function_stats` con contadores por funcion del registry. Tablas event-log:
| Tabla | Captura |
|---|---|
| `calls` | Cada invocacion (heredoc/mcp/fn_run): function_id, tool_used, duration_ms, success, error_class, args_hash |
| `code_writes` | Cada Edit/Write sobre archivo del registry: function_id, session_id, lines_added/removed |
| `test_runs` | Cada `go test`/`pytest` que toca codigo del registry: function_id, test_id, passed, duration_ms |
| `e2e_runs_fn` | Cada check `e2e_checks` de app que usa la funcion: function_id, app_id, check_id, passed |
| `violations` | Antipatron detectado: rule_id, session_id, command_snippet, severity |
| `patterns` | Heredocs clusterizados: pattern_hash, session_ids[], occurrences, representative_snippet |
| `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) |
Datos sensibles: solo `args_hash`, NUNCA valores concretos. Snippets de error redactados via allowlist.
### Capas de monitorizacion (issue 0085)
Cobertura por capa, no todas activas a la vez:
| # | Capa | Activacion | Cobertura |
|---|---|---|---|
| 1 | Hook PostToolUse Bash | siempre (settings.local.json) | mcp, fn_cli_run, edit_registry, violations |
| 2 | Wrapper Python `registry_telemetry` | `FN_TELEMETRY=1` env var | heredocs + notebooks Jupyter |
| 3 | Wrapper Bash `telemetry_prelude.sh` | `source` explicito o `FN_TELEMETRY=1` | heredoc bash + apps bash |
| 4 | Interceptor en `fn run` | siempre (binario Go) | duration/error real de invocacion CLI |
| 5 | `fn doctor copied-code` | comando manual / cron | drift estatico: codigo copiado en apps |
| 6 | `function_versions` + snapshot | poblado por `fn index` + edit-hook | historial de versiones |
| 7-8 | Build-tag Go / macro C++ | opt-in por app | runtime de app (futuro) |
**Boundary:** monitorizamos al **agente** y a **invocaciones canonicas**. Runtime de apps Go/C++ compiladas queda fuera. Compensar con tests + `e2e_checks` (issue 0068).
### Que NO se monitoriza
- Funcion Go/C++ llamada internamente por app ya compilada.
- Funcion ejecutada por systemd timer / cron / Dagu sin pasar por `fn run`.
- Sub-agente (`Agent` tool) — sus tools no propagan a hook del padre.
- Service de produccion recibiendo HTTP.
**Implicacion:** una funcion con `calls_90d=0` puede ser huerfana real O usada en runtime invisible. Antes de proponer `deprecate_function`, cruzar con `consumer_apps_count > 0` (e2e) o con `fn doctor uses-functions` (declaraciones estaticas).
+234
View File
@@ -0,0 +1,234 @@
#!/usr/bin/env bash
# PostToolUse hook: registra cada invocacion del agente en
# projects/fn_monitoring/apps/call_monitor/operations.db (issue 0085b).
#
# Identifica tool, extrae function_id cuando es posible, clasifica el patron
# (mcp_*, fn_cli_run, heredoc_py, sqlite_direct, edit_registry, ...) y
# detecta antipatrones para registrar violations.
#
# NUNCA bloquea la herramienta. Falla silenciosamente si la BD no esta lista.
# Solo guarda args_hash, jamas valores concretos.
set -euo pipefail
# ---- Resolve registry root (walks up from cwd looking for registry.db) ----
resolve_root() {
local d="${PWD}"
while [ "$d" != "/" ]; do
if [ -f "$d/registry.db" ]; then
printf '%s' "$d"
return 0
fi
d=$(dirname "$d")
done
return 1
}
ROOT=$(resolve_root) || exit 0
DB="$ROOT/projects/fn_monitoring/apps/call_monitor/operations.db"
# Si la BD aun no existe, el hook no hace nada (esperando init).
[ -f "$DB" ] || exit 0
# ---- Read stdin JSON ----
INPUT=$(cat)
if [ -z "$INPUT" ]; then exit 0; fi
# Required jq presence
command -v jq >/dev/null 2>&1 || exit 0
command -v sqlite3 >/dev/null 2>&1 || exit 0
TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_name // ""')
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // ""')
TS=$(date -u +%s)
# Tool response success/error
SUCCESS=1
ERROR_CLASS=""
ERROR_SNIPPET=""
RESP_IS_ERROR=$(printf '%s' "$INPUT" | jq -r 'if (.tool_response | type) == "object" then (.tool_response.is_error // false) else false end')
if [ "$RESP_IS_ERROR" = "true" ]; then
SUCCESS=0
ERROR_SNIPPET=$(printf '%s' "$INPUT" | jq -r 'if (.tool_response | type) == "object" then (.tool_response.error // .tool_response.content // "") else "" end' | head -c 240 | tr '\n' ' ')
fi
# args_hash: sha256 truncado del tool_input (sin valores)
ARGS_HASH=$(printf '%s' "$INPUT" | jq -c '.tool_input // {}' | sha256sum | cut -c1-16)
# Helpers SQL
sql_escape() { printf '%s' "$1" | sed "s/'/''/g"; }
insert_call() {
local fn_id="$1" tool_used="$2" duration_ms="${3:-0}"
local fn_esc tu_esc ec_esc es_esc sid_esc ah_esc
fn_esc=$(sql_escape "$fn_id")
tu_esc=$(sql_escape "$tool_used")
ec_esc=$(sql_escape "$ERROR_CLASS")
es_esc=$(sql_escape "$ERROR_SNIPPET")
sid_esc=$(sql_escape "$SESSION_ID")
ah_esc=$(sql_escape "$ARGS_HASH")
sqlite3 "$DB" "INSERT INTO calls (session_id, function_id, tool_used, args_hash, duration_ms, success, error_class, error_snippet, ts) VALUES ('$sid_esc','$fn_esc','$tu_esc','$ah_esc',$duration_ms,$SUCCESS,'$ec_esc','$es_esc',$TS);" 2>/dev/null || true
}
insert_code_write() {
local fn_id="$1" file_path="$2" added="${3:-0}" removed="${4:-0}"
local fn_esc fp_esc sid_esc
fn_esc=$(sql_escape "$fn_id")
fp_esc=$(sql_escape "$file_path")
sid_esc=$(sql_escape "$SESSION_ID")
sqlite3 "$DB" "INSERT INTO code_writes (session_id, function_id, file_path, lines_added, lines_removed, ts) VALUES ('$sid_esc','$fn_esc','$fp_esc',$added,$removed,$TS);" 2>/dev/null || true
}
# Snapshot a function version row when an edit lands on a registry file.
# Uses sha256 of file bytes as content_hash (separate namespace from index source).
insert_edit_version() {
local fn_id="$1" abs_path="$2"
[ -f "$abs_path" ] || return 0
command -v sha256sum >/dev/null 2>&1 || return 0
local hash
hash=$(sha256sum "$abs_path" 2>/dev/null | awk '{print $1}')
[ -z "$hash" ] && return 0
local fn_esc h_esc
fn_esc=$(sql_escape "$fn_id")
h_esc=$(sql_escape "$hash")
sqlite3 "$DB" "INSERT OR IGNORE INTO function_versions (function_id, content_hash, version, snapped_at, source, lines_added, lines_removed) VALUES ('$fn_esc','$h_esc','',$TS,'edit_hook',0,0);" 2>/dev/null || true
}
insert_violation() {
local rule_id="$1" fn_id="$2" snippet="$3" severity="${4:-warning}"
local r_esc fn_esc sn_esc sev_esc sid_esc
r_esc=$(sql_escape "$rule_id")
fn_esc=$(sql_escape "$fn_id")
sn_esc=$(sql_escape "$(printf '%s' "$snippet" | head -c 240 | tr '\n' ' ')")
sev_esc=$(sql_escape "$severity")
sid_esc=$(sql_escape "$SESSION_ID")
sqlite3 "$DB" "INSERT INTO violations (session_id, rule_id, function_id, command_snippet, severity, ts) VALUES ('$sid_esc','$r_esc','$fn_esc','$sn_esc','$sev_esc',$TS);" 2>/dev/null || true
}
# ---- Derive function_id from registry file path ----
# Matches paths under functions/<domain>/<name>.<ext>, python/functions/<domain>/<name>.py,
# bash/functions/<domain>/<name>.sh, frontend/functions/<domain>/<name>.ts(x)
derive_fn_id_from_path() {
local p="$1"
[ -z "$p" ] && return 1
case "$p" in
functions/*/*.go|*/functions/*/*.go)
local dom name
dom=$(printf '%s' "$p" | sed -E 's|.*functions/([^/]+)/.*|\1|')
name=$(printf '%s' "$p" | sed -E 's|.*functions/[^/]+/([^/.]+)\..*|\1|')
[ -n "$dom" ] && [ -n "$name" ] && printf '%s_go_%s' "$name" "$dom" && return 0 ;;
python/functions/*/*.py)
local dom name
dom=$(printf '%s' "$p" | sed -E 's|python/functions/([^/]+)/.*|\1|')
name=$(printf '%s' "$p" | sed -E 's|python/functions/[^/]+/([^/.]+)\..*|\1|')
[ -n "$dom" ] && [ -n "$name" ] && printf '%s_py_%s' "$name" "$dom" && return 0 ;;
bash/functions/*/*.sh)
local dom name
dom=$(printf '%s' "$p" | sed -E 's|bash/functions/([^/]+)/.*|\1|')
name=$(printf '%s' "$p" | sed -E 's|bash/functions/[^/]+/([^/.]+)\..*|\1|')
[ -n "$dom" ] && [ -n "$name" ] && printf '%s_bash_%s' "$name" "$dom" && return 0 ;;
frontend/functions/*/*.ts|frontend/functions/*/*.tsx)
local dom name
dom=$(printf '%s' "$p" | sed -E 's|frontend/functions/([^/]+)/.*|\1|')
name=$(printf '%s' "$p" | sed -E 's|frontend/functions/[^/]+/([^/.]+)\..*|\1|')
[ -n "$dom" ] && [ -n "$name" ] && printf '%s_ts_%s' "$name" "$dom" && return 0 ;;
esac
return 1
}
# ---- Dispatch by tool ----
case "$TOOL_NAME" in
mcp__registry__fn_search)
insert_call "" "mcp_fn_search"
;;
mcp__registry__fn_show)
ID=$(printf '%s' "$INPUT" | jq -r '.tool_input.id // ""')
insert_call "$ID" "mcp_fn_show"
;;
mcp__registry__fn_code)
ID=$(printf '%s' "$INPUT" | jq -r '.tool_input.id // ""')
insert_call "$ID" "mcp_fn_code"
;;
mcp__registry__fn_uses)
ID=$(printf '%s' "$INPUT" | jq -r '.tool_input.id // ""')
insert_call "$ID" "mcp_fn_uses"
;;
mcp__registry__fn_run)
ID=$(printf '%s' "$INPUT" | jq -r '.tool_input.id // ""')
insert_call "$ID" "mcp_fn_run"
;;
mcp__registry__fn_list_domains)
insert_call "" "mcp_fn_list_domains"
;;
mcp__registry__fn_proposal)
insert_call "" "mcp_fn_proposal"
;;
mcp__registry__fn_doctor)
insert_call "" "mcp_fn_doctor"
;;
mcp__registry__fn_create_function)
insert_call "" "mcp_fn_create_function"
;;
Edit|Write|MultiEdit)
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // ""')
ABS_PATH="$FILE_PATH"
# Make path relative to root if absolute and inside root
case "$FILE_PATH" in
"$ROOT"/*) FILE_PATH="${FILE_PATH#$ROOT/}" ;;
/*) ABS_PATH="$FILE_PATH" ;;
*) ABS_PATH="$ROOT/$FILE_PATH" ;;
esac
FN_ID=$(derive_fn_id_from_path "$FILE_PATH" || true)
if [ -n "$FN_ID" ]; then
insert_code_write "$FN_ID" "$FILE_PATH" 0 0
insert_call "$FN_ID" "edit_registry"
insert_edit_version "$FN_ID" "$ABS_PATH"
fi
;;
Bash)
CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""')
CMD_HEAD=$(printf '%s' "$CMD" | head -c 200 | tr '\n' ' ')
# Classify
TOOL_USED="bash_other"
FN_ID=""
if printf '%s' "$CMD" | grep -qE '(^|[[:space:]])\./fn[[:space:]]+run[[:space:]]+'; then
TOOL_USED="fn_cli_run"
FN_ID=$(printf '%s' "$CMD" | sed -nE 's/.*\.\/fn[[:space:]]+run[[:space:]]+([A-Za-z0-9_]+).*/\1/p' | head -n1)
elif printf '%s' "$CMD" | grep -qE 'python/\.venv/bin/python3[[:space:]]+-[[:space:]]+<<'; then
TOOL_USED="heredoc_py"
elif printf '%s' "$CMD" | grep -qE 'sqlite3[[:space:]][^|]*\bregistry\.db\b'; then
TOOL_USED="sqlite_direct"
fi
insert_call "$FN_ID" "$TOOL_USED"
# ---- Violation rules ----
# 1. sqlite3 directo SELECT sobre registry.db (excepto schema/pragma/count/join)
if [ "$TOOL_USED" = "sqlite_direct" ]; then
if ! printf '%s' "$CMD" | grep -qiE '(\.schema|\.tables|PRAGMA[[:space:]]+(table_info|index_list)|COUNT\(|GROUP[[:space:]]+BY|JOIN[[:space:]])'; then
insert_violation "sqlite3_registry_select" "" "$CMD_HEAD" "warning"
fi
fi
# 2. python -c "import X; dir(X)"
if printf '%s' "$CMD" | grep -qE 'python[3]?[[:space:]]+-c[[:space:]]+["'\''].*import.*(dir|help)\('; then
insert_violation "python_dir_inspect" "" "$CMD_HEAD" "info"
fi
# 3. from <pkg> import * (en heredoc python)
if [ "$TOOL_USED" = "heredoc_py" ]; then
if printf '%s' "$CMD" | grep -qE 'from[[:space:]]+[A-Za-z0-9_.]+[[:space:]]+import[[:space:]]+\*'; then
insert_violation "import_star_in_heredoc" "" "$CMD_HEAD" "warning"
fi
if printf '%s' "$CMD" | grep -qE 'client\._http\.request\('; then
insert_violation "client_http_request_direct" "" "$CMD_HEAD" "warning"
fi
fi
;;
esac
exit 0
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env bash
# PostToolUse hook: gate "tag de capability group obligatorio" tras crear/modificar
# funciones del registry. Issue 0086 paso 9/gate.
#
# Comportamiento:
# - Detecta .md de funciones (functions/, python/functions/, bash/functions/,
# frontend/functions/, cpp/functions/) modificados en los ultimos 60s.
# - Lee frontmatter `tags:` y verifica si al menos uno coincide con un capability
# group declarado en docs/capabilities/INDEX.md.
# - Si NO hay match -> emite additionalContext con la lista de funciones afectadas.
# - NUNCA bloquea. Solo warning visible.
#
# Salida JSON consumida por Claude Code:
# { "hookSpecificOutput": { "hookEventName": "PostToolUse",
# "additionalContext": "..." } }
set -euo pipefail
resolve_root() {
local d="${PWD}"
while [ "$d" != "/" ]; do
if [ -f "$d/registry.db" ]; then
printf '%s' "$d"
return 0
fi
d=$(dirname "$d")
done
return 1
}
ROOT=$(resolve_root) || exit 0
INDEX="$ROOT/docs/capabilities/INDEX.md"
# Si no existe el INDEX aun, no hay grupos definidos -> nada que verificar.
[ -f "$INDEX" ] || exit 0
# Consume stdin (sin parsear — no necesitamos session_id para este gate)
cat >/dev/null
# Solo correr si hay jq disponible
command -v jq >/dev/null 2>&1 || exit 0
# 1. Cargar lista de capability groups desde el INDEX.
# Formato esperado en INDEX.md: | [name](name.md) | N | descripcion |
CAP_GROUPS=$(grep -oE '\[[a-z][a-z0-9_-]*\]\([a-z][a-z0-9_-]*\.md\)' "$INDEX" \
| sed -E 's/^\[([^]]+)\].*/\1/' \
| sort -u)
[ -z "$CAP_GROUPS" ] && exit 0
# 2. Encontrar .md de funciones modificados en ultimos 60s.
RECENT=$(find "$ROOT/functions" "$ROOT/python/functions" "$ROOT/bash/functions" \
"$ROOT/frontend/functions" "$ROOT/cpp/functions" \
-maxdepth 4 -type f -name '*.md' -mmin -1 2>/dev/null || true)
[ -z "$RECENT" ] && exit 0
# 3. Para cada .md reciente: extraer tags del frontmatter, comparar con groups.
MISSING=""
while IFS= read -r mdfile; do
[ -z "$mdfile" ] && continue
# Extrae el bloque entre los dos `---` del inicio
front=$(awk '/^---$/{c++; next} c==1 {print} c>=2 {exit}' "$mdfile" 2>/dev/null || true)
[ -z "$front" ] && continue
# tags: [a, b, c] o tags:\n - a\n - b
tags_inline=$( { printf '%s\n' "$front" | grep -E '^tags:[[:space:]]*\[' | head -1 \
| sed -E 's/^tags:[[:space:]]*\[(.*)\].*$/\1/' \
| tr ',' '\n' | sed -E 's/^[[:space:]"]+|[[:space:]"]+$//g'; } || true )
tags_block=$( { printf '%s\n' "$front" | awk '
/^tags:[[:space:]]*$/ {intag=1; next}
intag && /^[[:space:]]*-[[:space:]]/ {sub(/^[[:space:]]*-[[:space:]]*/, ""); print; next}
intag && !/^[[:space:]]/ {intag=0}
' | sed -E 's/^[[:space:]"]+|[[:space:]"]+$//g'; } || true )
tags=$( { printf '%s\n%s\n' "$tags_inline" "$tags_block" | grep -v '^$'; } || true )
matched=0
while IFS= read -r g; do
[ -z "$g" ] && continue
if printf '%s\n' "$tags" | grep -qx "$g"; then
matched=1
break
fi
done <<< "$CAP_GROUPS"
if [ "$matched" -eq 0 ]; then
rel="${mdfile#$ROOT/}"
MISSING="${MISSING}${rel}\n"
fi
done <<< "$RECENT"
# 4. Si hay funciones sin tag de grupo, emitir aviso.
if [ -n "$MISSING" ]; then
CAP_GROUPS_CSV=$(printf '%s' "$CAP_GROUPS" | tr '\n' ',' | sed 's/,$//')
WARN="CAPABILITY-GAP (issue 0086): funcion(es) recien tocada(s) sin tag de capability group: $(printf '%b' "$MISSING" | tr '\n' ' ')"
WARN+="| Grupos disponibles: ${CAP_GROUPS_CSV}. Anade al menos uno al frontmatter \`tags:\` y corre \`./fn index\`. Si la funcion no encaja en ningun grupo existente, considera crear grupo nuevo (>=3 funciones) o dejarla con tag plano (no de grupo)."
jq -n --arg ctx "$WARN" '{
hookSpecificOutput: {
hookEventName: "PostToolUse",
additionalContext: $ctx
}
}'
fi
exit 0
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# UserPromptSubmit hook: recordatorio compacto de patrones canonicos del registry.
# Inyectado como additionalContext en cada turno del usuario.
# Issue 0085 (hardening 2).
#
# NUNCA bloquea. Solo printf de additionalContext.
set -euo pipefail
# Resolve registry root (walks up from cwd)
resolve_root() {
local d="${PWD}"
while [ "$d" != "/" ]; do
if [ -f "$d/registry.db" ]; then
printf '%s' "$d"
return 0
fi
d=$(dirname "$d")
done
return 1
}
ROOT=$(resolve_root) || exit 0
# Read input, extract session_id (UserPromptSubmit payload includes it)
INPUT=$(cat)
SESSION_ID=""
if command -v jq >/dev/null 2>&1; then
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // ""' 2>/dev/null || true)
fi
# Count current pending proposals + recent violations for situational awareness
PROPOSALS_PENDING="?"
VIOLATIONS_24H="?"
CALLS_24H="?"
CAP_CREATED=0
CAP_USED=0
CAP_ORPHAN=0
if command -v sqlite3 >/dev/null 2>&1; then
REG="$ROOT/registry.db"
MON="$ROOT/projects/fn_monitoring/apps/call_monitor/operations.db"
[ -f "$REG" ] && PROPOSALS_PENDING=$(sqlite3 "$REG" "SELECT COUNT(*) FROM proposals WHERE status='pending'" 2>/dev/null || echo "?")
if [ -f "$MON" ]; then
VIOLATIONS_24H=$(sqlite3 "$MON" "SELECT COUNT(*) FROM violations WHERE ts >= CAST(strftime('%s','now','-1 day') AS INTEGER)" 2>/dev/null || echo "?")
CALLS_24H=$(sqlite3 "$MON" "SELECT COUNT(*) FROM calls WHERE ts >= CAST(strftime('%s','now','-1 day') AS INTEGER)" 2>/dev/null || echo "?")
if [ -n "$SESSION_ID" ]; then
sid_esc=$(printf '%s' "$SESSION_ID" | sed "s/'/''/g")
CAP_CREATED=$(sqlite3 "$MON" "SELECT COUNT(*) FROM session_capability_growth WHERE session_id='$sid_esc'" 2>/dev/null || echo 0)
CAP_USED=$(sqlite3 "$MON" "SELECT COUNT(*) FROM session_capability_growth WHERE session_id='$sid_esc' AND calls_in_session>0" 2>/dev/null || echo 0)
CAP_ORPHAN=$(sqlite3 "$MON" "SELECT COUNT(*) FROM session_capability_growth WHERE session_id='$sid_esc' AND calls_in_session=0" 2>/dev/null || echo 0)
fi
fi
fi
REMINDER="REGISTRY-FIRST (issue 0085 telemetry active): "
REMINDER+="Inspect → mcp__registry__fn_search/show/code/uses/proposal. "
REMINDER+="Execute one fn → mcp__registry__fn_run or ./fn run. "
REMINDER+="Compose multi-fn → heredoc python IMPORTANDO del registry. "
REMINDER+="NUNCA sqlite3 registry.db directo (salvo schema/PRAGMA/COUNT/JOIN). "
REMINDER+="NUNCA reescribir inline logica que ya es funcion. "
REMINDER+="Si patron se repite >2x → propose nueva funcion via fn-constructor. "
REMINDER+="Estado: pending_proposals=${PROPOSALS_PENDING} violations_24h=${VIOLATIONS_24H} calls_24h=${CALLS_24H}. "
REMINDER+="CAPABILITY-GROWTH (issue 0086): created_this_session=${CAP_CREATED} used=${CAP_USED} orphan=${CAP_ORPHAN}. Si orphan>0 -> integra la funcion en el codigo o documenta por que se quedo huerfana. "
REMINDER+="Comando autocheck: /fn_claude."
jq -n --arg ctx "$REMINDER" '{
hookSpecificOutput: {
hookEventName: "UserPromptSubmit",
additionalContext: $ctx
}
}'