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:
+110
-35
@@ -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)
|
## 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.
|
**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`.
|
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)
|
# Busqueda basica por nombre/descripcion (FTS5 detras)
|
||||||
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;"
|
mcp__registry__fn_search query="slice"
|
||||||
|
|
||||||
# FTS5 con prefijo (encuentra slice, slicing, sliced...)
|
# Filtros: kind, purity, domain, lang
|
||||||
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;"
|
mcp__registry__fn_search query="filter" kind="function" purity="pure" domain="core"
|
||||||
|
|
||||||
# FTS5 en tipos
|
# Prefijo FTS5 — encuentra slice/slicing/sliced
|
||||||
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;"
|
mcp__registry__fn_search query="slic*"
|
||||||
|
|
||||||
# FTS5 por semantica de params (composabilidad)
|
# Buscar tipos
|
||||||
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');"
|
mcp__registry__fn_search query="result" entity="types"
|
||||||
|
|
||||||
# Por dominio
|
# Apps
|
||||||
sqlite3 registry.db "SELECT id, purity, signature FROM functions WHERE domain = 'finance' ORDER BY name;"
|
mcp__registry__fn_search query="kanban" entity="apps"
|
||||||
|
|
||||||
# Puras de un dominio
|
# Listar dominios
|
||||||
sqlite3 registry.db "SELECT id, signature FROM functions WHERE domain = 'core' AND purity = 'pure' ORDER BY name;"
|
mcp__registry__fn_list_domains
|
||||||
|
|
||||||
# Tipos por dominio
|
# Ver una entrada concreta (functions, types, apps, analysis, proposals...)
|
||||||
sqlite3 registry.db "SELECT id, algebraic, description FROM types WHERE domain = 'cybersecurity';"
|
mcp__registry__fn_show id="filter_slice_go_core"
|
||||||
|
|
||||||
# Dependencias
|
# Codigo fuente de una funcion/tipo
|
||||||
sqlite3 registry.db "SELECT id, uses_functions, uses_types FROM functions WHERE uses_functions != '[]';"
|
mcp__registry__fn_code id="filter_slice_go_core"
|
||||||
|
|
||||||
# Proposals pendientes
|
# Quien consume una funcion (consumidores indexados via uses_functions)
|
||||||
sqlite3 registry.db "SELECT id, kind, status, title FROM proposals WHERE status = 'pending';"
|
mcp__registry__fn_uses id="filter_slice_go_core"
|
||||||
|
|
||||||
# Schema completo
|
# Proposals (pending, approved, ...)
|
||||||
sqlite3 registry.db ".schema"
|
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:
|
```
|
||||||
|
# MAL: query="description:single-page" -> "no such column: page"
|
||||||
```bash
|
# BIEN
|
||||||
# MAL: description:single-page → "no such column: page"
|
mcp__registry__fn_search query='description:"single-page" OR description:"embed.FS"'
|
||||||
# MAL: description:embed.FS → 'syntax error near "."'
|
mcp__registry__fn_search query='description:"react router"'
|
||||||
# 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\"');"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
### Schema rapido
|
||||||
|
|
||||||
@@ -109,7 +147,7 @@ Tokens multi-palabra tambien necesitan comillas: `description:"react router"`.
|
|||||||
- `entity_type`: app, analysis, project, vault
|
- `entity_type`: app, analysis, project, vault
|
||||||
- `status`: active, missing, archived
|
- `status`: active, missing, archived
|
||||||
- Se puebla con `fn sync`, NO con `fn index`
|
- 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):**
|
**FTS5 (columnas buscables):**
|
||||||
- `functions_fts`: id, name, description, tags, signature, domain, example, notes, documentation, code, params_schema
|
- `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
|
## Estructura
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -237,7 +312,7 @@ Entornos usados automaticamente:
|
|||||||
|
|
||||||
## Añadir funciones
|
## 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:
|
2. Crea dos archivos segun el lenguaje:
|
||||||
- Go: `functions/{domain}/{name}.go` + `.md`
|
- Go: `functions/{domain}/{name}.go` + `.md`
|
||||||
- Python: `python/functions/{domain}/{name}.py` + `.md`
|
- Python: `python/functions/{domain}/{name}.py` + `.md`
|
||||||
|
|||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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.
|
||||||
@@ -77,4 +77,21 @@ Input: <paths absolutos, IDs registry, run_id si aplica>
|
|||||||
Tarea: <accion concreta y acotada>
|
Tarea: <accion concreta y acotada>
|
||||||
Criterio exito: <como sabe que termino>
|
Criterio exito: <como sabe que termino>
|
||||||
Limites: <que NO debe tocar>
|
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.
|
||||||
|
|||||||
@@ -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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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).
|
||||||
Executable
+234
@@ -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
|
||||||
Executable
+107
@@ -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
@@ -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
|
||||||
|
}
|
||||||
|
}'
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"registry": {
|
"registry": {
|
||||||
"command": "/home/egutierrez/fn_registry/apps/registry_mcp/registry_mcp",
|
"command": "./apps/registry_mcp/registry_mcp",
|
||||||
"args": ["--enable-run", "--enable-write"],
|
"args": ["--enable-run", "--enable-write"]
|
||||||
"env": {
|
|
||||||
"FN_REGISTRY_ROOT": "/home/egutierrez/fn_registry"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "analyze_dns(domain: string, mode: string) -> void"
|
signature: "analyze_dns(domain: string, mode: string) -> void"
|
||||||
description: "Análisis DNS completo de un dominio: registros A/AAAA/MX/NS/TXT/CNAME/SOA, consulta whois y verificación contra listas negras DNSBL (spamhaus, spamcop, sorbs, barracuda)."
|
description: "Análisis DNS completo de un dominio: registros A/AAAA/MX/NS/TXT/CNAME/SOA, consulta whois y verificación contra listas negras DNSBL (spamhaus, spamcop, sorbs, barracuda)."
|
||||||
tags: [bash, cybersecurity, dns, network, whois, dnsbl, reconnaissance]
|
tags: [bash, cybersecurity, dns, network, whois, dnsbl, reconnaissance, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "audit_http_headers(url: string) -> void"
|
signature: "audit_http_headers(url: string) -> void"
|
||||||
description: "Audita las cabeceras HTTP de seguridad de una URL: verifica la presencia de HSTS (con validación de max-age mínimo de 6 meses), Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy y cabeceras CORS. También detecta cabeceras que exponen información del servidor."
|
description: "Audita las cabeceras HTTP de seguridad de una URL: verifica la presencia de HSTS (con validación de max-age mínimo de 6 meses), Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy y cabeceras CORS. También detecta cabeceras que exponen información del servidor."
|
||||||
tags: [bash, cybersecurity, web, http, headers, security, hsts, csp, hardening]
|
tags: [bash, cybersecurity, web, http, headers, security, hsts, csp, hardening, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "audit_ssh_config(config_path: string) -> void"
|
signature: "audit_ssh_config(config_path: string) -> void"
|
||||||
description: "Audita la configuración de sshd_config evaluando parámetros de seguridad críticos (PermitRootLogin, PasswordAuthentication, Port, MaxAuthTries, X11Forwarding, AllowUsers). También revisa intentos de login fallidos en los logs y lista las claves autorizadas del usuario actual."
|
description: "Audita la configuración de sshd_config evaluando parámetros de seguridad críticos (PermitRootLogin, PasswordAuthentication, Port, MaxAuthTries, X11Forwarding, AllowUsers). También revisa intentos de login fallidos en los logs y lista las claves autorizadas del usuario actual."
|
||||||
tags: [bash, cybersecurity, ssh, audit, security, hardening, linux]
|
tags: [bash, cybersecurity, ssh, audit, security, hardening, linux, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "check_firewall() -> void"
|
signature: "check_firewall() -> void"
|
||||||
description: "Detecta el firewall activo del sistema (ufw, firewalld o iptables) y muestra su estado, reglas activas y puertos en escucha para cruzar con las reglas. Si no se detecta ningún firewall, emite una advertencia de exposición."
|
description: "Detecta el firewall activo del sistema (ufw, firewalld o iptables) y muestra su estado, reglas activas y puertos en escucha para cruzar con las reglas. Si no se detecta ningún firewall, emite una advertencia de exposición."
|
||||||
tags: [bash, cybersecurity, firewall, ufw, iptables, network, hardening, linux]
|
tags: [bash, cybersecurity, firewall, ufw, iptables, network, hardening, linux, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "detect_suspicious_users() -> void"
|
signature: "detect_suspicious_users() -> void"
|
||||||
description: "Revisa el sistema en busca de indicadores de compromiso en cuentas de usuario: UIDs 0 extras (además de root), usuarios con shell de login válida, homes en rutas inusuales, miembros de grupos privilegiados (sudo, docker, wheel, adm, etc.) y sesiones activas."
|
description: "Revisa el sistema en busca de indicadores de compromiso en cuentas de usuario: UIDs 0 extras (además de root), usuarios con shell de login válida, homes en rutas inusuales, miembros de grupos privilegiados (sudo, docker, wheel, adm, etc.) y sesiones activas."
|
||||||
tags: [bash, cybersecurity, users, audit, linux, privilege-escalation, hardening]
|
tags: [bash, cybersecurity, users, audit, linux, privilege-escalation, hardening, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "encrypt_file(mode: string, file: string) -> void"
|
signature: "encrypt_file(mode: string, file: string) -> void"
|
||||||
description: "Cifra o descifra un archivo usando AES-256-CBC con PBKDF2 (310.000 iteraciones) via openssl. La contraseña se lee de la variable de entorno ENCRYPT_PASSWORD o se solicita interactivamente. El archivo cifrado se guarda con extensión .enc; al descifrar se recupera el nombre original."
|
description: "Cifra o descifra un archivo usando AES-256-CBC con PBKDF2 (310.000 iteraciones) via openssl. La contraseña se lee de la variable de entorno ENCRYPT_PASSWORD o se solicita interactivamente. El archivo cifrado se guarda con extensión .enc; al descifrar se recupera el nombre original."
|
||||||
tags: [bash, cybersecurity, encryption, aes256, openssl, crypto, pbkdf2]
|
tags: [bash, cybersecurity, encryption, aes256, openssl, crypto, pbkdf2, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "enumerate_subdomains(domain: string, output_file: string) -> void"
|
signature: "enumerate_subdomains(domain: string, output_file: string) -> void"
|
||||||
description: "Enumera subdominios de un dominio objetivo usando un diccionario integrado de ~100 subdominios comunes (www, mail, api, dev, admin, vpn, etc.). Detecta tanto registros A (IP directa) como CNAME. Muestra progreso cada 20 subdominios y opcionalmente guarda los resultados en un archivo."
|
description: "Enumera subdominios de un dominio objetivo usando un diccionario integrado de ~100 subdominios comunes (www, mail, api, dev, admin, vpn, etc.). Detecta tanto registros A (IP directa) como CNAME. Muestra progreso cada 20 subdominios y opcionalmente guarda los resultados en un archivo."
|
||||||
tags: [bash, cybersecurity, dns, subdomain, enumeration, reconnaissance, osint]
|
tags: [bash, cybersecurity, dns, subdomain, enumeration, reconnaissance, osint, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "generate_password(mode: string, length: int, count: int) -> void"
|
signature: "generate_password(mode: string, length: int, count: int) -> void"
|
||||||
description: "Genera contraseñas seguras en cuatro modos: full (alfanumérico + símbolos, excluye caracteres ambiguos), alpha (solo alfanumérico), passphrase (palabras aleatorias unidas con guión) y pin (numérico). Calcula y muestra la entropía en bits para cada modo."
|
description: "Genera contraseñas seguras en cuatro modos: full (alfanumérico + símbolos, excluye caracteres ambiguos), alpha (solo alfanumérico), passphrase (palabras aleatorias unidas con guión) y pin (numérico). Calcula y muestra la entropía en bits para cada modo."
|
||||||
tags: [bash, cybersecurity, password, generator, entropy, security, urandom]
|
tags: [bash, cybersecurity, password, generator, entropy, security, urandom, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "geolocate_ip(target: string) -> void"
|
signature: "geolocate_ip(target: string) -> void"
|
||||||
description: "Geolocaliza una dirección IP o dominio usando la API pública de ip-api.com. Muestra país, región, ciudad, coordenadas, ISP, ASN y detecta VPN, Proxy o infraestructura de hosting."
|
description: "Geolocaliza una dirección IP o dominio usando la API pública de ip-api.com. Muestra país, región, ciudad, coordenadas, ISP, ASN y detecta VPN, Proxy o infraestructura de hosting."
|
||||||
tags: [bash, cybersecurity, network, geoip, ip, osint, reconnaissance]
|
tags: [bash, cybersecurity, network, geoip, ip, osint, reconnaissance, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "inspect_ssl_cert(host: string) -> void"
|
signature: "inspect_ssl_cert(host: string) -> void"
|
||||||
description: "Inspecciona el certificado SSL/TLS de un host: muestra sujeto, emisor, fechas de validez, días hasta expiración, SANs (Subject Alternative Names), cadena de confianza completa y detecta soporte de versiones inseguras TLS 1.0/1.1."
|
description: "Inspecciona el certificado SSL/TLS de un host: muestra sujeto, emisor, fechas de validez, días hasta expiración, SANs (Subject Alternative Names), cadena de confianza completa y detecta soporte de versiones inseguras TLS 1.0/1.1."
|
||||||
tags: [bash, cybersecurity, ssl, tls, certificate, web, openssl, security]
|
tags: [bash, cybersecurity, ssl, tls, certificate, web, openssl, security, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "list_active_connections(mode: string) -> void"
|
signature: "list_active_connections(mode: string) -> void"
|
||||||
description: "Muestra conexiones de red activas del sistema usando ss: puertos en escucha, conexiones establecidas y detección de conexiones hacia IPs externas (excluye RFC1918, loopback y link-local)."
|
description: "Muestra conexiones de red activas del sistema usando ss: puertos en escucha, conexiones establecidas y detección de conexiones hacia IPs externas (excluye RFC1918, loopback y link-local)."
|
||||||
tags: [bash, cybersecurity, network, connections, monitoring, ss, ports]
|
tags: [bash, cybersecurity, network, connections, monitoring, ss, ports, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "verify_file_hash(file: string, algorithm: string, expected_hash: string) -> void"
|
signature: "verify_file_hash(file: string, algorithm: string, expected_hash: string) -> void"
|
||||||
description: "Calcula el hash criptográfico de un archivo con el algoritmo especificado (md5, sha1, sha256, sha512) y opcionalmente lo compara con un hash esperado para verificar integridad. Retorna exit code 1 si los hashes no coinciden."
|
description: "Calcula el hash criptográfico de un archivo con el algoritmo especificado (md5, sha1, sha256, sha512) y opcionalmente lo compara con un hash esperado para verificar integridad. Retorna exit code 1 si los hashes no coinciden."
|
||||||
tags: [bash, cybersecurity, hash, integrity, checksum, md5, sha256, sha512]
|
tags: [bash, cybersecurity, hash, integrity, checksum, md5, sha256, sha512, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "analyze_disk_space([target_dir: string], [mode: string]) -> void"
|
signature: "analyze_disk_space([target_dir: string], [mode: string]) -> void"
|
||||||
description: "Analiza el uso de espacio en disco. Modos: partitions (df con filtros), top-dirs (du top 10), top-files (find top 20), inodes (df -i), all (todos). Emite advertencias si el uso supera el 90%."
|
description: "Analiza el uso de espacio en disco. Modos: partitions (df con filtros), top-dirs (du top 10), top-files (find top 20), inodes (df -i), all (todos). Emite advertencias si el uso supera el 90%."
|
||||||
tags: [bash, disk, space, analysis, filesystem]
|
tags: [bash, disk, space, analysis, filesystem, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_app_clear([--serial <S>], package: string) -> void"
|
signature: "android_app_clear([--serial <S>], package: string) -> void"
|
||||||
description: "Wipe app data + cache via pm clear. App keeps installed but factory-state. Multi-emulator via --serial."
|
description: "Wipe app data + cache via pm clear. App keeps installed but factory-state. Multi-emulator via --serial."
|
||||||
tags: [android, adb, app, clear, reset]
|
tags: [android, adb, app, clear, reset, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: "--serial <S>"
|
- name: "--serial <S>"
|
||||||
desc: "Optional target device/emulator serial. Auto-detected if omitted."
|
desc: "Optional target device/emulator serial. Auto-detected if omitted."
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_app_info([--serial <S>], package, [--json]) -> stdout"
|
signature: "android_app_info([--serial <S>], package, [--json]) -> stdout"
|
||||||
description: "Inspect installed app: version, target SDK, activities via dumpsys package."
|
description: "Inspect installed app: version, target SDK, activities via dumpsys package."
|
||||||
tags: [android, adb, app, info, dumpsys]
|
tags: [android, adb, app, info, dumpsys, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: "--serial <S>"
|
- name: "--serial <S>"
|
||||||
desc: "Optional ADB serial to target a specific device/emulator. Auto-detected if omitted."
|
desc: "Optional ADB serial to target a specific device/emulator. Auto-detected if omitted."
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_app_kill([--serial <S>], package: string) -> void"
|
signature: "android_app_kill([--serial <S>], package: string) -> void"
|
||||||
description: "Force-stop running app via am force-stop. Multi-emulator via --serial."
|
description: "Force-stop running app via am force-stop. Multi-emulator via --serial."
|
||||||
tags: [android, adb, app, kill, force-stop]
|
tags: [android, adb, app, kill, force-stop, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: "--serial <S>"
|
- name: "--serial <S>"
|
||||||
desc: "Optional target device/emulator serial. Auto-detected if omitted."
|
desc: "Optional target device/emulator serial. Auto-detected if omitted."
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_app_launch([--serial <S>], package: string, [activity: string]) -> void"
|
signature: "android_app_launch([--serial <S>], package: string, [activity: string]) -> void"
|
||||||
description: "Launch app activity via am start. Multi-emulator via --serial."
|
description: "Launch app activity via am start. Multi-emulator via --serial."
|
||||||
tags: [android, adb, app, launch, activity]
|
tags: [android, adb, app, launch, activity, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: "--serial <S>"
|
- name: "--serial <S>"
|
||||||
desc: "Optional target serial. Default: first device"
|
desc: "Optional target serial. Default: first device"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_app_uninstall([--serial <S>] package [--keep-data]) -> void"
|
signature: "android_app_uninstall([--serial <S>] package [--keep-data]) -> void"
|
||||||
description: "Uninstall app via adb uninstall. Optionally keep data with --keep-data."
|
description: "Uninstall app via adb uninstall. Optionally keep data with --keep-data."
|
||||||
tags: [android, adb, app, uninstall]
|
tags: [android, adb, app, uninstall, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: "--serial <S>"
|
- name: "--serial <S>"
|
||||||
desc: "Optional target device/emulator serial. Auto-detects first connected device if omitted."
|
desc: "Optional target device/emulator serial. Auto-detects first connected device if omitted."
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_emu_battery([--serial <S>], level: int, [--charging <true|false>]) -> void"
|
signature: "android_emu_battery([--serial <S>], level: int, [--charging <true|false>]) -> void"
|
||||||
description: "Simulate battery state on emulator (level + charging). Emulator-only."
|
description: "Simulate battery state on emulator (level + charging). Emulator-only."
|
||||||
tags: [android, emulator, battery, power]
|
tags: [android, emulator, battery, power, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: "--serial <S>"
|
- name: "--serial <S>"
|
||||||
desc: "Optional emulator serial (e.g. emulator-5554). Auto-detected if omitted."
|
desc: "Optional emulator serial (e.g. emulator-5554). Auto-detected if omitted."
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_emu_geo_fix([--serial <S>], longitude: string, latitude: string, [altitude: string]) -> void"
|
signature: "android_emu_geo_fix([--serial <S>], longitude: string, latitude: string, [altitude: string]) -> void"
|
||||||
description: "Fake GPS location on Android emulator via emu geo fix. Emulator-only (not physical devices)."
|
description: "Fake GPS location on Android emulator via emu geo fix. Emulator-only (not physical devices)."
|
||||||
tags: [android, emulator, geo, gps, location]
|
tags: [android, emulator, geo, gps, location, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: "--serial <S>"
|
- name: "--serial <S>"
|
||||||
desc: "Optional emulator serial. Auto-detected if omitted."
|
desc: "Optional emulator serial. Auto-detected if omitted."
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_emu_rotate([--serial <S>] [portrait|landscape|0|90|180|270])"
|
signature: "android_emu_rotate([--serial <S>] [portrait|landscape|0|90|180|270])"
|
||||||
description: "Rotate emulator screen. Empty=toggle, or fixed orientation. Locks autorotate."
|
description: "Rotate emulator screen. Empty=toggle, or fixed orientation. Locks autorotate."
|
||||||
tags: [android, emulator, rotation, orientation]
|
tags: [android, emulator, rotation, orientation, pendiente-usar]
|
||||||
uses_functions: [adb_wsl_bash_infra]
|
uses_functions: [adb_wsl_bash_infra]
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_input_keyevent([--serial <S>] key: string)"
|
signature: "android_input_keyevent([--serial <S>] key: string)"
|
||||||
description: "Send key event via adb shell input keyevent. Accepts aliases (BACK, HOME, POWER, ENTER, MENU, RECENT_APPS, VOLUME_UP, VOLUME_DOWN), raw numeric codes, or explicit KEYCODE_* names."
|
description: "Send key event via adb shell input keyevent. Accepts aliases (BACK, HOME, POWER, ENTER, MENU, RECENT_APPS, VOLUME_UP, VOLUME_DOWN), raw numeric codes, or explicit KEYCODE_* names."
|
||||||
tags: [android, adb, input, keyevent, ui-test]
|
tags: [android, adb, input, keyevent, ui-test, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: "--serial <S>"
|
- name: "--serial <S>"
|
||||||
desc: "Optional target device/emulator serial. If omitted, adb_pick_serial resolves the single connected device."
|
desc: "Optional target device/emulator serial. If omitted, adb_pick_serial resolves the single connected device."
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_input_swipe([--serial <S>], x1: int, y1: int, x2: int, y2: int, [duration_ms: int])"
|
signature: "android_input_swipe([--serial <S>], x1: int, y1: int, x2: int, y2: int, [duration_ms: int])"
|
||||||
description: "Send swipe gesture between two points with duration."
|
description: "Send swipe gesture between two points with duration."
|
||||||
tags: [android, adb, input, swipe, gesture, ui-test]
|
tags: [android, adb, input, swipe, gesture, ui-test, pendiente-usar]
|
||||||
uses_functions: [adb_wsl_bash_infra]
|
uses_functions: [adb_wsl_bash_infra]
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_input_tap([--serial <S>], x: int, y: int) -> void"
|
signature: "android_input_tap([--serial <S>], x: int, y: int) -> void"
|
||||||
description: "Send tap gesture at screen coordinates via adb shell input tap."
|
description: "Send tap gesture at screen coordinates via adb shell input tap."
|
||||||
tags: [android, adb, input, tap, ui-test, gesture]
|
tags: [android, adb, input, tap, ui-test, gesture, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: "--serial <S>"
|
- name: "--serial <S>"
|
||||||
desc: "Optional target device serial. Auto-detected if omitted."
|
desc: "Optional target device serial. Auto-detected if omitted."
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_input_text([--serial <S>], text: string) -> void"
|
signature: "android_input_text([--serial <S>], text: string) -> void"
|
||||||
description: "Type text in focused field via adb shell input text. Spaces handled."
|
description: "Type text in focused field via adb shell input text. Spaces handled."
|
||||||
tags: [android, adb, input, text, ui-test]
|
tags: [android, adb, input, text, ui-test, pendiente-usar]
|
||||||
uses_functions: [adb_wsl_bash_infra]
|
uses_functions: [adb_wsl_bash_infra]
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_pull [--serial <S>] remote_path local_path"
|
signature: "android_pull [--serial <S>] remote_path local_path"
|
||||||
description: "Pull file/dir from Android device to WSL via adb pull."
|
description: "Pull file/dir from Android device to WSL via adb pull."
|
||||||
tags: [android, adb, pull, file, transfer]
|
tags: [android, adb, pull, file, transfer, pendiente-usar]
|
||||||
uses_functions: [adb_wsl_bash_infra]
|
uses_functions: [adb_wsl_bash_infra]
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_push([--serial <S>], local_path: string, remote_path: string) -> void"
|
signature: "android_push([--serial <S>], local_path: string, remote_path: string) -> void"
|
||||||
description: "Push file/dir from WSL to Android device via adb push."
|
description: "Push file/dir from WSL to Android device via adb push."
|
||||||
tags: [android, adb, push, file, transfer]
|
tags: [android, adb, push, file, transfer, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: "--serial <S>"
|
- name: "--serial <S>"
|
||||||
desc: "Optional target device/emulator serial. Auto-detected if omitted."
|
desc: "Optional target device/emulator serial. Auto-detected if omitted."
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_screen_record([--serial <S>] [--duration <s>] [--bit-rate <bps>] [--size <WxH>] output_path: string) -> void"
|
signature: "android_screen_record([--serial <S>] [--duration <s>] [--bit-rate <bps>] [--size <WxH>] output_path: string) -> void"
|
||||||
description: "Record screen video via adb screenrecord, pulls to local path."
|
description: "Record screen video via adb screenrecord, pulls to local path."
|
||||||
tags: [android, adb, screen, record, video]
|
tags: [android, adb, screen, record, video, pendiente-usar]
|
||||||
uses_functions: [adb_wsl_bash_infra]
|
uses_functions: [adb_wsl_bash_infra]
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_screenshot([--serial <S>], output_path: string) -> void"
|
signature: "android_screenshot([--serial <S>], output_path: string) -> void"
|
||||||
description: "Capture screen as PNG via adb exec-out screencap -p."
|
description: "Capture screen as PNG via adb exec-out screencap -p."
|
||||||
tags: [android, adb, screenshot, screen, capture]
|
tags: [android, adb, screenshot, screen, capture, pendiente-usar]
|
||||||
uses_functions: [adb_wsl_bash_infra]
|
uses_functions: [adb_wsl_bash_infra]
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "android_shell([--serial <S>], cmd ...args)"
|
signature: "android_shell([--serial <S>], cmd ...args)"
|
||||||
description: "Execute arbitrary shell command on Android device. Multi-emulator via --serial."
|
description: "Execute arbitrary shell command on Android device. Multi-emulator via --serial."
|
||||||
tags: [android, adb, shell, exec]
|
tags: [android, adb, shell, exec, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: "--serial <S>"
|
- name: "--serial <S>"
|
||||||
desc: "Optional target device serial. Omit to auto-pick (single device) or use ADB_SERIAL env."
|
desc: "Optional target device serial. Omit to auto-pick (single device) or use ADB_SERIAL env."
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "append_diary_entry(titulo: string, cuerpo: string) -> string"
|
signature: "append_diary_entry(titulo: string, cuerpo: string) -> string"
|
||||||
description: "Añade una entrada al diario del dia en ${DIARY_DIR:-docs/diary}/YYYY-MM-DD.md. Crea el archivo con cabecera si no existe. Nunca reescribe contenido previo. Si cuerpo es vacio escribe solo el header de la seccion."
|
description: "Añade una entrada al diario del dia en ${DIARY_DIR:-docs/diary}/YYYY-MM-DD.md. Crea el archivo con cabecera si no existe. Nunca reescribe contenido previo. Si cuerpo es vacio escribe solo el header de la seccion."
|
||||||
tags: [diary, markdown, append, idempotent, infra]
|
tags: [diary, markdown, append, idempotent, infra, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "build_cpp_linux(target?: string) -> void"
|
signature: "build_cpp_linux(target?: string) -> void"
|
||||||
description: "Compila las funciones y apps C++ del registry para Linux nativo usando cmake"
|
description: "Compila las funciones y apps C++ del registry para Linux nativo usando cmake"
|
||||||
tags: [cpp, build, cmake, linux, imgui]
|
tags: [cpp, build, cmake, linux, imgui, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "0.1.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "build_wasm_cpp_app(app_name: string, [--no-budget-check]) -> void"
|
signature: "build_wasm_cpp_app(app_name: string, [--no-budget-check]) -> void"
|
||||||
description: "Compila una app C++ del registry (cpp/apps/<name>) a WASM via emscripten. Sale build/wasm/<name>/<name>.{html,js,wasm,wasm.gz}. Falla si gzip > 2 MB."
|
description: "Compila una app C++ del registry (cpp/apps/<name>) a WASM via emscripten. Sale build/wasm/<name>/<name>.{html,js,wasm,wasm.gz}. Falla si gzip > 2 MB."
|
||||||
tags: [wasm, emscripten, cpp, build, gamedev]
|
tags: [wasm, emscripten, cpp, build, gamedev, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "cuda_toolkit_check() -> void"
|
signature: "cuda_toolkit_check() -> void"
|
||||||
description: "Detecta componentes CUDA instalados en el sistema y emite pares key=value a stdout: nvcc (version o missing), nvidia_smi (present/missing), driver_version, cuda_libs (path o missing) y overall (ok|partial|missing). Exit code 0 siempre — funcion informativa, no fatal."
|
description: "Detecta componentes CUDA instalados en el sistema y emite pares key=value a stdout: nvcc (version o missing), nvidia_smi (present/missing), driver_version, cuda_libs (path o missing) y overall (ok|partial|missing). Exit code 0 siempre — funcion informativa, no fatal."
|
||||||
tags: [cuda, nvidia, gpu, hardware, probe, infra, toolkit]
|
tags: [cuda, nvidia, gpu, hardware, probe, infra, toolkit, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "detect_wsl([--check]) -> void"
|
signature: "detect_wsl([--check]) -> void"
|
||||||
description: "Detecta si el sistema es WSL (Windows Subsystem for Linux). Con --check retorna solo exit code (0=WSL, 1=no WSL) sin output. Sin argumentos imprime versión WSL, usuario Windows, distribución, hostname, unidades montadas y ruta Windows del directorio actual."
|
description: "Detecta si el sistema es WSL (Windows Subsystem for Linux). Con --check retorna solo exit code (0=WSL, 1=no WSL) sin output. Sin argumentos imprime versión WSL, usuario Windows, distribución, hostname, unidades montadas y ruta Windows del directorio actual."
|
||||||
tags: [bash, wsl, windows, detect, integration]
|
tags: [bash, wsl, windows, detect, integration, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: e2e_run_cpp_windows
|
|||||||
lang: bash
|
lang: bash
|
||||||
domain: infra
|
domain: infra
|
||||||
description: "Cross-compila una app C++ del registry para Windows con mingw-w64, deploy al Desktop\\apps de Windows (matando instancia previa con taskkill.exe), lanza el .exe nativamente desde WSL y devuelve stdout + exit code. Pensado para tests headless tipo altsnap_jitter_test."
|
description: "Cross-compila una app C++ del registry para Windows con mingw-w64, deploy al Desktop\\apps de Windows (matando instancia previa con taskkill.exe), lanza el .exe nativamente desde WSL y devuelve stdout + exit code. Pensado para tests headless tipo altsnap_jitter_test."
|
||||||
tags: [windows, e2e, cross-compile, test, mingw]
|
tags: [windows, e2e, cross-compile, test, mingw, pendiente-usar]
|
||||||
purity: impure
|
purity: impure
|
||||||
kind: function
|
kind: function
|
||||||
signature: "e2e_run_cpp_windows(target string, --no-build, --no-deploy) int"
|
signature: "e2e_run_cpp_windows(target string, --no-build, --no-deploy) int"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "frontend_doctor(project_dir: string) -> diagnostics_stdout"
|
signature: "frontend_doctor(project_dir: string) -> diagnostics_stdout"
|
||||||
description: "Diagnostica la salud de un proyecto frontend Mantine. Verifica Node, React, Mantine, PostCSS, TypeScript, vite.config y detecta residuos de shadcn/@base-ui. Imprime tabla de checks con exit code 0/1."
|
description: "Diagnostica la salud de un proyecto frontend Mantine. Verifica Node, React, Mantine, PostCSS, TypeScript, vite.config y detecta residuos de shadcn/@base-ui. Imprime tabla de checks con exit code 0/1."
|
||||||
tags: [frontend, mantine, doctor, diagnostics, health, validation]
|
tags: [frontend, mantine, doctor, diagnostics, health, validation, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: 1.0.0
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "git_hook_audit_app_drift <repo_dir>"
|
signature: "git_hook_audit_app_drift <repo_dir>"
|
||||||
description: "Pre-commit guard: bloquea commit si los archivos staged tocan una app cuyo app.md tiene drift de uses_functions. Permite ediciones a app.md (correcciones)."
|
description: "Pre-commit guard: bloquea commit si los archivos staged tocan una app cuyo app.md tiene drift de uses_functions. Permite ediciones a app.md (correcciones)."
|
||||||
tags: [git, hook, precommit, registry-first, audit]
|
tags: [git, hook, precommit, registry-first, audit, pendiente-usar]
|
||||||
uses_functions:
|
uses_functions:
|
||||||
- audit_uses_functions_go_infra
|
- audit_uses_functions_go_infra
|
||||||
uses_types: []
|
uses_types: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "gitea_create_webhook(owner: string, repo: string, target_url: string, secret?: string) -> json"
|
signature: "gitea_create_webhook(owner: string, repo: string, target_url: string, secret?: string) -> json"
|
||||||
description: "Crea un webhook de push en un repositorio Gitea. El webhook notifica a target_url en cada push."
|
description: "Crea un webhook de push en un repositorio Gitea. El webhook notifica a target_url en cada push."
|
||||||
tags: [gitea, webhook, push, deploy, ci, infra]
|
tags: [gitea, webhook, push, deploy, ci, infra, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "gitea_list_repos(owner: string) -> string"
|
signature: "gitea_list_repos(owner: string) -> string"
|
||||||
description: "Lista repositorios de un owner en Gitea. Intenta listar como org primero; si falla, lista como usuario. Imprime una línea por repo en formato name<TAB>html_url<TAB>description."
|
description: "Lista repositorios de un owner en Gitea. Intenta listar como org primero; si falla, lista como usuario. Imprime una línea por repo en formato name<TAB>html_url<TAB>description."
|
||||||
tags: [gitea, git, repo, list, org, user, api, infra]
|
tags: [gitea, git, repo, list, org, user, api, infra, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "gradle_clean(project_dir: string) -> int"
|
signature: "gradle_clean(project_dir: string) -> int"
|
||||||
description: "Limpia build artifacts de un proyecto Android (gradle clean + rm .gradle + rm build)."
|
description: "Limpia build artifacts de un proyecto Android (gradle clean + rm .gradle + rm build)."
|
||||||
tags: [android, gradle, clean, build]
|
tags: [android, gradle, clean, build, pendiente-usar]
|
||||||
params:
|
params:
|
||||||
- name: project_dir
|
- name: project_dir
|
||||||
desc: "Raiz del proyecto Gradle"
|
desc: "Raiz del proyecto Gradle"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "install_cpp_deps() -> void"
|
signature: "install_cpp_deps() -> void"
|
||||||
description: "Verifica e instala las dependencias de sistema necesarias para compilar C++ con ImGui (cmake, g++, glfw, mesa)"
|
description: "Verifica e instala las dependencias de sistema necesarias para compilar C++ con ImGui (cmake, g++, glfw, mesa)"
|
||||||
tags: [cpp, dependencies, setup, cmake, imgui]
|
tags: [cpp, dependencies, setup, cmake, imgui, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "install_mantine(project_dir: string) -> void"
|
signature: "install_mantine(project_dir: string) -> void"
|
||||||
description: "Instala Mantine UI con todas sus dependencias (@mantine/core, hooks, charts, notifications, form) y PostCSS en un proyecto frontend. Detecta package manager por lockfile. Genera postcss.config.cjs si no existe. Idempotente."
|
description: "Instala Mantine UI con todas sus dependencias (@mantine/core, hooks, charts, notifications, form) y PostCSS en un proyecto frontend. Detecta package manager por lockfile. Genera postcss.config.cjs si no existe. Idempotente."
|
||||||
tags: [mantine, frontend, install, react, ui, postcss]
|
tags: [mantine, frontend, install, react, ui, postcss, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "install_nodejs([version: string]) -> void"
|
signature: "install_nodejs([version: string]) -> void"
|
||||||
description: "Instala Node.js en Linux usando nvm. Instala nvm v0.39.7 si no está presente. Instala la versión de Node indicada, la activa con 'nvm use' y la configura como default. Idempotente si nvm ya está instalado."
|
description: "Instala Node.js en Linux usando nvm. Instala nvm v0.39.7 si no está presente. Instala la versión de Node indicada, la activa con 'nvm use' y la configura como default. Idempotente si nvm ya está instalado."
|
||||||
tags: [bash, install, nodejs, nvm]
|
tags: [bash, install, nodejs, nvm, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "install_nordvpn() -> void"
|
signature: "install_nordvpn() -> void"
|
||||||
description: "Instala NordVPN CLI en Ubuntu/Debian (incluido WSL2). Configura repositorio oficial, instala paquete y habilita servicio nordvpnd. Idempotente."
|
description: "Instala NordVPN CLI en Ubuntu/Debian (incluido WSL2). Configura repositorio oficial, instala paquete y habilita servicio nordvpnd. Idempotente."
|
||||||
tags: [vpn, nordvpn, install, infra, wsl2]
|
tags: [vpn, nordvpn, install, infra, wsl2, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "install_pnpm() -> void"
|
signature: "install_pnpm() -> void"
|
||||||
description: "Instala pnpm globalmente usando npm (npm install -g pnpm). Verifica que npm esté disponible. Idempotente: si pnpm ya está instalado, informa y termina sin hacer nada."
|
description: "Instala pnpm globalmente usando npm (npm install -g pnpm). Verifica que npm esté disponible. Idempotente: si pnpm ya está instalado, informa y termina sin hacer nada."
|
||||||
tags: [bash, install, pnpm, node]
|
tags: [bash, install, pnpm, node, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "install_python312() -> void"
|
signature: "install_python312() -> void"
|
||||||
description: "Instala Python 3.12 detectando la distribución Linux automáticamente. Ubuntu/Debian/Mint usan deadsnakes PPA; Fedora/RHEL usan dnf; Arch/Manjaro usan pacman. Instala también python3.12-venv, python3.12-dev y verifica pip. Idempotente."
|
description: "Instala Python 3.12 detectando la distribución Linux automáticamente. Ubuntu/Debian/Mint usan deadsnakes PPA; Fedora/RHEL usan dnf; Arch/Manjaro usan pacman. Instala también python3.12-venv, python3.12-dev y verifica pip. Idempotente."
|
||||||
tags: [bash, install, python, python312]
|
tags: [bash, install, python, python312, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "install_uv() -> void"
|
signature: "install_uv() -> void"
|
||||||
description: "Instala uv, el gestor de paquetes Python ultra-rápido escrito en Rust, usando el instalador oficial de astral.sh. Configura PATH en ~/.bashrc y ~/.zshrc. Idempotente: si uv ya está instalado, informa y termina."
|
description: "Instala uv, el gestor de paquetes Python ultra-rápido escrito en Rust, usando el instalador oficial de astral.sh. Configura PATH en ~/.bashrc y ~/.zshrc. Idempotente: si uv ya está instalado, informa y termina."
|
||||||
tags: [bash, install, uv, python]
|
tags: [bash, install, uv, python, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "install_volta() -> void"
|
signature: "install_volta() -> void"
|
||||||
description: "Instala Volta, el gestor de versiones de Node.js, usando el instalador oficial de get.volta.sh. Configura VOLTA_HOME y PATH en ~/.bashrc y ~/.zshrc. Idempotente: si Volta ya está instalado, informa y termina."
|
description: "Instala Volta, el gestor de versiones de Node.js, usando el instalador oficial de get.volta.sh. Configura VOLTA_HOME y PATH en ~/.bashrc y ~/.zshrc. Idempotente: si Volta ya está instalado, informa y termina."
|
||||||
tags: [bash, install, volta, node]
|
tags: [bash, install, volta, node, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "keepass_delete(entry: string)"
|
signature: "keepass_delete(entry: string)"
|
||||||
description: "Elimina una entry del KeePassXC database via keepassxc-cli rm. La entry pasa a la papelera dentro del .kdbx (no se borra fisicamente)."
|
description: "Elimina una entry del KeePassXC database via keepassxc-cli rm. La entry pasa a la papelera dentro del .kdbx (no se borra fisicamente)."
|
||||||
tags: [keepass, keepassxc, secret, credential, delete]
|
tags: [keepass, keepassxc, secret, credential, delete, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "keepass_generate(entry: string, length?: int, username?: string, url?: string) -> string"
|
signature: "keepass_generate(entry: string, length?: int, username?: string, url?: string) -> string"
|
||||||
description: "Genera un password aleatorio (lower+upper+digits+special), lo almacena en una entry nueva y lo imprime a stdout. Length default 24."
|
description: "Genera un password aleatorio (lower+upper+digits+special), lo almacena en una entry nueva y lo imprime a stdout. Length default 24."
|
||||||
tags: [keepass, keepassxc, secret, credential, generate, random]
|
tags: [keepass, keepassxc, secret, credential, generate, random, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "keepass_get(entry: string, attr?: string) -> string"
|
signature: "keepass_get(entry: string, attr?: string) -> string"
|
||||||
description: "Lee un atributo (Password por defecto) de una entry del KeePassXC database via keepassxc-cli. Resuelve master password desde pass (meta/keepassxc-master) o env KEEPASS_PASSWORD."
|
description: "Lee un atributo (Password por defecto) de una entry del KeePassXC database via keepassxc-cli. Resuelve master password desde pass (meta/keepassxc-master) o env KEEPASS_PASSWORD."
|
||||||
tags: [keepass, keepassxc, secret, credential, get]
|
tags: [keepass, keepassxc, secret, credential, get, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "keepass_list(prefix?: string) -> json"
|
signature: "keepass_list(prefix?: string) -> json"
|
||||||
description: "Lista paths de entries del KeePassXC database como array JSON. Filtra opcionalmente por prefijo de grupo. Internamente usa keepass_dump y proyecta solo los paths."
|
description: "Lista paths de entries del KeePassXC database como array JSON. Filtra opcionalmente por prefijo de grupo. Internamente usa keepass_dump y proyecta solo los paths."
|
||||||
tags: [keepass, keepassxc, list]
|
tags: [keepass, keepassxc, list, pendiente-usar]
|
||||||
uses_functions:
|
uses_functions:
|
||||||
- keepass_dump_bash_infra
|
- keepass_dump_bash_infra
|
||||||
uses_types: []
|
uses_types: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "keepass_search(term: string) -> json"
|
signature: "keepass_search(term: string) -> json"
|
||||||
description: "Busca entries en el KeePassXC database por substring. Devuelve array JSON de paths que matchean (title/username/url/notes)."
|
description: "Busca entries en el KeePassXC database por substring. Devuelve array JSON de paths que matchean (title/username/url/notes)."
|
||||||
tags: [keepass, keepassxc, search, query]
|
tags: [keepass, keepassxc, search, query, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "keepass_set(entry: string, password: string, username?: string, url?: string)"
|
signature: "keepass_set(entry: string, password: string, username?: string, url?: string)"
|
||||||
description: "Crea o sobreescribe una entry en el KeePassXC database. Auto-detecta si existe (edit) o no (add). Soporta username y url opcionales."
|
description: "Crea o sobreescribe una entry en el KeePassXC database. Auto-detecta si existe (edit) o no (add). Soporta username y url opcionales."
|
||||||
tags: [keepass, keepassxc, secret, credential, set, write]
|
tags: [keepass, keepassxc, secret, credential, set, write, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "list_listening_ports([mode: string]) -> void"
|
signature: "list_listening_ports([mode: string]) -> void"
|
||||||
description: "Lista puertos activos del sistema usando ss (preferido) o netstat como fallback. Modos: all (LISTEN), tcp, udp, established (conexiones activas), stats (resumen + interfaces). Imprime salida tabulada a stdout."
|
description: "Lista puertos activos del sistema usando ss (preferido) o netstat como fallback. Modos: all (LISTEN), tcp, udp, established (conexiones activas), stats (resumen + interfaces). Imprime salida tabulada a stdout."
|
||||||
tags: [bash, ports, network, listening, monitoring]
|
tags: [bash, ports, network, listening, monitoring, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "nordvpn_connect(country?: string, city?: string) -> json"
|
signature: "nordvpn_connect(country?: string, city?: string) -> json"
|
||||||
description: "Conecta a NordVPN por pais, ciudad o servidor especifico. Sin argumentos conecta al mejor servidor disponible. Devuelve JSON con resultado."
|
description: "Conecta a NordVPN por pais, ciudad o servidor especifico. Sin argumentos conecta al mejor servidor disponible. Devuelve JSON con resultado."
|
||||||
tags: [vpn, nordvpn, connect, infra, network]
|
tags: [vpn, nordvpn, connect, infra, network, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "nordvpn_disconnect() -> json"
|
signature: "nordvpn_disconnect() -> json"
|
||||||
description: "Desconecta de NordVPN. Idempotente — si no hay conexion activa retorna ok. Devuelve JSON con resultado."
|
description: "Desconecta de NordVPN. Idempotente — si no hay conexion activa retorna ok. Devuelve JSON con resultado."
|
||||||
tags: [vpn, nordvpn, disconnect, infra, network]
|
tags: [vpn, nordvpn, disconnect, infra, network, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "nordvpn_get_ip() -> json"
|
signature: "nordvpn_get_ip() -> json"
|
||||||
description: "Obtiene IP publica actual con fallback entre multiples servicios. Indica si la conexion VPN esta activa y el servidor usado."
|
description: "Obtiene IP publica actual con fallback entre multiples servicios. Indica si la conexion VPN esta activa y el servidor usado."
|
||||||
tags: [vpn, nordvpn, ip, infra, network, verification]
|
tags: [vpn, nordvpn, ip, infra, network, verification, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "nordvpn_list_cities(country: string) -> json"
|
signature: "nordvpn_list_cities(country: string) -> json"
|
||||||
description: "Lista ciudades disponibles de un pais en NordVPN como array JSON ordenado."
|
description: "Lista ciudades disponibles de un pais en NordVPN como array JSON ordenado."
|
||||||
tags: [vpn, nordvpn, cities, infra, network]
|
tags: [vpn, nordvpn, cities, infra, network, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "nordvpn_list_countries() -> json"
|
signature: "nordvpn_list_countries() -> json"
|
||||||
description: "Lista paises disponibles en NordVPN como array JSON ordenado alfabeticamente."
|
description: "Lista paises disponibles en NordVPN como array JSON ordenado alfabeticamente."
|
||||||
tags: [vpn, nordvpn, countries, infra, network]
|
tags: [vpn, nordvpn, countries, infra, network, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "nordvpn_set_protocol(protocol: string) -> json"
|
signature: "nordvpn_set_protocol(protocol: string) -> json"
|
||||||
description: "Cambia el protocolo de NordVPN entre NordLynx (WireGuard) y OpenVPN. NordLynx recomendado por velocidad."
|
description: "Cambia el protocolo de NordVPN entre NordLynx (WireGuard) y OpenVPN. NordLynx recomendado por velocidad."
|
||||||
tags: [vpn, nordvpn, protocol, nordlynx, wireguard, openvpn, infra]
|
tags: [vpn, nordvpn, protocol, nordlynx, wireguard, openvpn, infra, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "nordvpn_status() -> json"
|
signature: "nordvpn_status() -> json"
|
||||||
description: "Obtiene estado actual de NordVPN como JSON estructurado. Incluye servidor, IP, pais, protocolo y estado de conexion."
|
description: "Obtiene estado actual de NordVPN como JSON estructurado. Incluye servidor, IP, pais, protocolo y estado de conexion."
|
||||||
tags: [vpn, nordvpn, status, infra, network]
|
tags: [vpn, nordvpn, status, infra, network, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "pass_delete(entry: string) -> void"
|
signature: "pass_delete(entry: string) -> void"
|
||||||
description: "Elimina un secreto del password store (pass)."
|
description: "Elimina un secreto del password store (pass)."
|
||||||
tags: [pass, secret, credential, delete]
|
tags: [pass, secret, credential, delete, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "pass_generate(entry: string, [length: int]) -> string"
|
signature: "pass_generate(entry: string, [length: int]) -> string"
|
||||||
description: "Genera un password aleatorio, lo almacena en el password store e imprime el valor generado."
|
description: "Genera un password aleatorio, lo almacena en el password store e imprime el valor generado."
|
||||||
tags: [pass, secret, credential, generate, random]
|
tags: [pass, secret, credential, generate, random, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "pass_list([prefix: string]) -> json"
|
signature: "pass_list([prefix: string]) -> json"
|
||||||
description: "Lista entradas del password store como JSON array. Filtra opcionalmente por prefijo."
|
description: "Lista entradas del password store como JSON array. Filtra opcionalmente por prefijo."
|
||||||
tags: [pass, secret, credential, list]
|
tags: [pass, secret, credential, list, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "pass_sync() -> json"
|
signature: "pass_sync() -> json"
|
||||||
description: "Sincroniza el password store con el repositorio git remoto (pull + push)."
|
description: "Sincroniza el password store con el repositorio git remoto (pull + push)."
|
||||||
tags: [pass, secret, sync, git]
|
tags: [pass, secret, sync, git, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "port_kill(port: int, signal: string) -> void"
|
signature: "port_kill(port: int, signal: string) -> void"
|
||||||
description: "Mata los procesos que escuchan en un puerto TCP dado. Idempotente: si no hay proceso en el puerto retorna exit 0. Hace un segundo intento con SIGKILL si el primer intento con signal no libera el puerto."
|
description: "Mata los procesos que escuchan en un puerto TCP dado. Idempotente: si no hay proceso en el puerto retorna exit 0. Hace un segundo intento con SIGKILL si el primer intento con signal no libera el puerto."
|
||||||
tags: ["port", "kill", "process", "tcp"]
|
tags: [port, kill, process, tcp, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "pre_commit_hook_install(repo_dir: string, [--force]) -> void"
|
signature: "pre_commit_hook_install(repo_dir: string, [--force]) -> void"
|
||||||
description: "Instala un hook pre-commit en .git/hooks/pre-commit de un repo dado. El hook invoca scan_secrets_in_dirty para abortar el commit si detecta secrets en archivos staged. Idempotente: si el hook ya esta instalado (marca fn_registry-pre-commit-v1) no lo sobreescribe a menos que se pase --force."
|
description: "Instala un hook pre-commit en .git/hooks/pre-commit de un repo dado. El hook invoca scan_secrets_in_dirty para abortar el commit si detecta secrets en archivos staged. Idempotente: si el hook ya esta instalado (marca fn_registry-pre-commit-v1) no lo sobreescribe a menos que se pase --force."
|
||||||
tags: ["git", "hook", "precommit", "secrets"]
|
tags: [git, hook, precommit, secrets, pendiente-usar]
|
||||||
uses_functions: ["scan_secrets_in_dirty_bash_cybersecurity"]
|
uses_functions: ["scan_secrets_in_dirty_bash_cybersecurity"]
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "systemd_local_restart(name: string) -> json"
|
signature: "systemd_local_restart(name: string) -> json"
|
||||||
description: "Reinicia un servicio systemd local con systemctl restart. Útil tras actualizar el binario o cambiar el unit. Requiere sudo."
|
description: "Reinicia un servicio systemd local con systemctl restart. Útil tras actualizar el binario o cambiar el unit. Requiere sudo."
|
||||||
tags: [systemd, service, local, infra, restart]
|
tags: [systemd, service, local, infra, restart, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "systemd_local_uninstall(name: string) -> json"
|
signature: "systemd_local_uninstall(name: string) -> json"
|
||||||
description: "Detiene, deshabilita y elimina el unit file de un servicio systemd local. Idempotente: no falla si el servicio ya está parado o el unit no existe. Requiere sudo."
|
description: "Detiene, deshabilita y elimina el unit file de un servicio systemd local. Idempotente: no falla si el servicio ya está parado o el unit no existe. Requiere sudo."
|
||||||
tags: [systemd, service, local, infra, uninstall, cleanup]
|
tags: [systemd, service, local, infra, uninstall, cleanup, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "tail_journal(unit: string, lines: int=100, follow: bool=false, since: string=\"\", priority: string=\"info\") -> void"
|
signature: "tail_journal(unit: string, lines: int=100, follow: bool=false, since: string=\"\", priority: string=\"info\") -> void"
|
||||||
description: "Wrapper sobre journalctl con formato consistente. Tail logs de una unidad systemd con coloreado, filtro por prioridad y seguimiento opcional."
|
description: "Wrapper sobre journalctl con formato consistente. Tail logs de una unidad systemd con coloreado, filtro por prioridad y seguimiento opcional."
|
||||||
tags: ["journal", "systemd", "logs"]
|
tags: [journal, systemd, logs, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "tbd_branch_create(mode: string, ...args: string) -> void"
|
signature: "tbd_branch_create(mode: string, ...args: string) -> void"
|
||||||
description: "Crea una rama TBD (trunk-based development) desde master/main actualizado. Soporta modos 'issue <NNNN> <slug>' y 'quick <slug>'. Autodetecta la rama base (master/main), verifica working tree limpio, hace pull --rebase y crea la rama. Valida formato de numero de issue (4 digitos) y slug (kebab-case ASCII)."
|
description: "Crea una rama TBD (trunk-based development) desde master/main actualizado. Soporta modos 'issue <NNNN> <slug>' y 'quick <slug>'. Autodetecta la rama base (master/main), verifica working tree limpio, hace pull --rebase y crea la rama. Valida formato de numero de issue (4 digitos) y slug (kebab-case ASCII)."
|
||||||
tags: [git, tbd, branch, trunk-based-development, workflow]
|
tags: [git, tbd, branch, trunk-based-development, workflow, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "tbd_branch_finish([merge_title: string]) -> void"
|
signature: "tbd_branch_finish([merge_title: string]) -> void"
|
||||||
description: "Integra una rama TBD (issue/* o quick/*) a master/main con merge --no-ff, publica el merge al remote y elimina la rama local. Autodetecta la rama base (master/main), verifica working tree limpio y construye el titulo del merge commit. NO ejecuta tests — esa responsabilidad es del caller. Exit 2 si hay conflicto de merge (deja al usuario resolver)."
|
description: "Integra una rama TBD (issue/* o quick/*) a master/main con merge --no-ff, publica el merge al remote y elimina la rama local. Autodetecta la rama base (master/main), verifica working tree limpio y construye el titulo del merge commit. NO ejecuta tests — esa responsabilidad es del caller. Exit 2 si hay conflicto de merge (deja al usuario resolver)."
|
||||||
tags: [git, tbd, merge, trunk-based-development, workflow]
|
tags: [git, tbd, merge, trunk-based-development, workflow, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
name: telemetry_prelude
|
||||||
|
lang: bash
|
||||||
|
domain: infra
|
||||||
|
version: 0.1.0
|
||||||
|
purity: impure
|
||||||
|
kind: function
|
||||||
|
description: "Prelude bash que envuelve cada funcion del registry definida en el shell con un wrapper que mide duration y registra cada llamada en call_monitor.operations.db. Activable con FN_TELEMETRY=1. Issue 0085c."
|
||||||
|
tags: [telemetry, monitoring, registry, bash-wrapper, pendiente-usar]
|
||||||
|
signature: "source telemetry_prelude.sh"
|
||||||
|
error_type: "error_go_core"
|
||||||
|
returns_optional: false
|
||||||
|
params:
|
||||||
|
- name: FN_TELEMETRY (env)
|
||||||
|
desc: "Si vale '1', el prelude auto-envuelve cada funcion bash conocida del registry. Si no, return 0 inmediato sin hacer nada."
|
||||||
|
- name: FN_REGISTRY_ROOT (env)
|
||||||
|
desc: "Override de la raiz. Si no se setea, se descubre walking up desde cwd buscando registry.db."
|
||||||
|
- name: CLAUDE_SESSION_ID (env)
|
||||||
|
desc: "ID de sesion Claude Code persistido en cada fila de calls."
|
||||||
|
output: "Sin output. Side effect: cada funcion del registry sourceada queda reemplazada por un wrapper con telemetria. Idempotente."
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
imports:
|
||||||
|
- sqlite3 (CLI)
|
||||||
|
- date (coreutils)
|
||||||
|
- find (coreutils)
|
||||||
|
example: |
|
||||||
|
# Auto-wrap
|
||||||
|
export FN_TELEMETRY=1
|
||||||
|
source bash/functions/infra/android_screenshot.sh # define android_screenshot()
|
||||||
|
source bash/functions/infra/telemetry_prelude.sh # envuelve android_screenshot
|
||||||
|
android_screenshot /tmp/out.png # registrado en calls como android_screenshot_bash_infra
|
||||||
|
file_path: "bash/functions/infra/telemetry_prelude.sh"
|
||||||
|
tested: false
|
||||||
|
notes: |
|
||||||
|
Mecanismo: `declare -f <name>` extrae el cuerpo de la funcion. Se renombra
|
||||||
|
a `_fn_t_orig_<name>` via eval. La funcion original queda reemplazada por
|
||||||
|
un wrapper que mide `date +%s%3N` antes/despues, ejecuta `_fn_t_orig_<name>`,
|
||||||
|
captura exit code, y llama `_fn_t_log function_id duration_ms success error_class`.
|
||||||
|
|
||||||
|
function_id heuristic: `{name}_bash_{domain}` donde `name`=basename del .sh
|
||||||
|
y `domain`=basename del directorio padre. Coincide con convencion del registry.
|
||||||
|
|
||||||
|
Fail-safe: si la BD no existe, sqlite3 falta, o INSERT falla, el wrapper
|
||||||
|
ignora silenciosamente y retorna el exit code del original. NUNCA aborta
|
||||||
|
ni modifica el comportamiento de la funcion envuelta.
|
||||||
|
|
||||||
|
Idempotente: marca cada wrapper con `_FN_T_WRAPPED_<name>=1` y no
|
||||||
|
re-envuelve. Sourcear el prelude N veces es seguro.
|
||||||
|
|
||||||
|
Limitacion: el wrapper requiere que las funciones del registry ya esten
|
||||||
|
sourceadas antes de cargarse este prelude. Si la app sourcea una funcion
|
||||||
|
DESPUES del prelude, esa funcion NO queda envuelta automaticamente — hay
|
||||||
|
que llamar `_fn_t_autowrap` manualmente o usar `_fn_t_wrap <name> <id>`.
|
||||||
|
---
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# telemetry_prelude — Wrapper de telemetria para funciones bash del registry.
|
||||||
|
# Issue 0085c-bash.
|
||||||
|
#
|
||||||
|
# Uso 1 (manual): source bash/functions/<dom>/<fn>.sh primero, despues source
|
||||||
|
# este archivo, y todas las funciones bash del registry
|
||||||
|
# quedaran envueltas con logging a call_monitor.operations.db.
|
||||||
|
#
|
||||||
|
# Uso 2 (auto): exportar FN_TELEMETRY=1 antes de sourcear; este prelude
|
||||||
|
# detecta cada funcion definida que coincida con una funcion
|
||||||
|
# del registry (bash/functions/<dom>/<name>.sh) y la
|
||||||
|
# redefine como wrapper.
|
||||||
|
#
|
||||||
|
# Reglas:
|
||||||
|
# - Idempotente: marca cada wrapper con flag _FN_T_WRAPPED_<name>=1; no
|
||||||
|
# re-envuelve si ya esta envuelta.
|
||||||
|
# - No-op silencioso si BD no existe o INSERT falla. NUNCA aborta el caller.
|
||||||
|
# - Solo guarda function_id, duration_ms, success, error_class. NUNCA args.
|
||||||
|
|
||||||
|
if [ "${FN_TELEMETRY:-0}" != "1" ]; then
|
||||||
|
return 0 2>/dev/null || exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- Resolve registry root ----
|
||||||
|
_fn_t_root() {
|
||||||
|
if [ -n "${FN_REGISTRY_ROOT:-}" ] && [ -f "$FN_REGISTRY_ROOT/registry.db" ]; then
|
||||||
|
printf '%s' "$FN_REGISTRY_ROOT"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local d="${PWD}"
|
||||||
|
while [ "$d" != "/" ]; do
|
||||||
|
if [ -f "$d/registry.db" ]; then
|
||||||
|
printf '%s' "$d"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
d=$(dirname "$d")
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Resolve operations.db ----
|
||||||
|
_FN_T_DB=""
|
||||||
|
_fn_t_resolve_db() {
|
||||||
|
if [ -n "$_FN_T_DB" ] && [ -f "$_FN_T_DB" ]; then return 0; fi
|
||||||
|
local root
|
||||||
|
root=$(_fn_t_root) || return 1
|
||||||
|
_FN_T_DB="$root/projects/fn_monitoring/apps/call_monitor/operations.db"
|
||||||
|
if [ ! -f "$_FN_T_DB" ]; then
|
||||||
|
_FN_T_DB=""
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Log call ----
|
||||||
|
_fn_t_log() {
|
||||||
|
local fn_id="$1" duration_ms="$2" success="$3" error_class="${4:-}"
|
||||||
|
_fn_t_resolve_db || return 0
|
||||||
|
command -v sqlite3 >/dev/null 2>&1 || return 0
|
||||||
|
local sid="${CLAUDE_SESSION_ID:-}"
|
||||||
|
local ts
|
||||||
|
ts=$(date -u +%s)
|
||||||
|
sid="${sid//\'/\'\'}"
|
||||||
|
fn_id="${fn_id//\'/\'\'}"
|
||||||
|
error_class="${error_class//\'/\'\'}"
|
||||||
|
sqlite3 "$_FN_T_DB" "INSERT INTO calls (session_id, function_id, tool_used, args_hash, duration_ms, success, error_class, error_snippet, ts) VALUES ('$sid','$fn_id','bash_wrapper','',$duration_ms,$success,'$error_class','',$ts);" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Wrap a single function by name ----
|
||||||
|
_fn_t_wrap() {
|
||||||
|
local orig="$1" fn_id="$2"
|
||||||
|
|
||||||
|
# already wrapped?
|
||||||
|
local guard
|
||||||
|
guard="_FN_T_WRAPPED_${orig}"
|
||||||
|
if [ "${!guard:-0}" = "1" ]; then return 0; fi
|
||||||
|
|
||||||
|
# must exist as a function
|
||||||
|
declare -F "$orig" >/dev/null 2>&1 || return 1
|
||||||
|
|
||||||
|
# capture original body, rename to _fn_t_orig_<name>
|
||||||
|
local body
|
||||||
|
body=$(declare -f "$orig") || return 1
|
||||||
|
# body starts with "<orig> ()"; prepend prefix to rename
|
||||||
|
local renamed="_fn_t_orig_${body}"
|
||||||
|
eval "$renamed"
|
||||||
|
|
||||||
|
# define wrapper with the original name
|
||||||
|
eval "
|
||||||
|
${orig}() {
|
||||||
|
local _t0_ms _t1_ms _rc _dur
|
||||||
|
_t0_ms=\$(date +%s%3N)
|
||||||
|
_fn_t_orig_${orig} \"\$@\"
|
||||||
|
_rc=\$?
|
||||||
|
_t1_ms=\$(date +%s%3N)
|
||||||
|
_dur=\$((_t1_ms - _t0_ms))
|
||||||
|
if [ \$_rc -eq 0 ]; then
|
||||||
|
_fn_t_log \"${fn_id}\" \$_dur 1 \"\"
|
||||||
|
else
|
||||||
|
_fn_t_log \"${fn_id}\" \$_dur 0 \"exit_\$_rc\"
|
||||||
|
fi
|
||||||
|
return \$_rc
|
||||||
|
}
|
||||||
|
"
|
||||||
|
eval "$guard=1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Auto-wrap: walk bash/functions and wrap every function currently defined ----
|
||||||
|
_fn_t_autowrap() {
|
||||||
|
local root
|
||||||
|
root=$(_fn_t_root) || return 1
|
||||||
|
local f domain name fn_id
|
||||||
|
while IFS= read -r f; do
|
||||||
|
domain=$(basename "$(dirname "$f")")
|
||||||
|
name=$(basename "$f" .sh)
|
||||||
|
fn_id="${name}_bash_${domain}"
|
||||||
|
if declare -F "$name" >/dev/null 2>&1; then
|
||||||
|
_fn_t_wrap "$name" "$fn_id"
|
||||||
|
fi
|
||||||
|
done < <(find "$root/bash/functions" -type f -name '*.sh' 2>/dev/null)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Run autowrap on source ----
|
||||||
|
_fn_t_autowrap
|
||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "wait_for_http <url> [timeout_seconds] [interval_seconds]"
|
signature: "wait_for_http <url> [timeout_seconds] [interval_seconds]"
|
||||||
description: "Hace polling a una URL HTTP/HTTPS hasta recibir respuesta 2xx o agotar el timeout. Util en deploys, post-restart de servicios y smoke tests."
|
description: "Hace polling a una URL HTTP/HTTPS hasta recibir respuesta 2xx o agotar el timeout. Util en deploys, post-restart de servicios y smoke tests."
|
||||||
tags: [http, wait, poll, health, deploy, smoke-test]
|
tags: [http, wait, poll, health, deploy, smoke-test, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "wait_for_port(host: string, port: int, timeout_seconds: int, interval_seconds: int) -> int"
|
signature: "wait_for_port(host: string, port: int, timeout_seconds: int, interval_seconds: int) -> int"
|
||||||
description: "Hace polling TCP a host:puerto hasta que acepte conexiones o agote el timeout. Util para esperar a que un servicio (DB, API, container) este listo antes de ejecutar pasos siguientes."
|
description: "Hace polling TCP a host:puerto hasta que acepte conexiones o agote el timeout. Util para esperar a que un servicio (DB, API, container) este listo antes de ejecutar pasos siguientes."
|
||||||
tags: [tcp, wait, poll, port]
|
tags: [tcp, wait, poll, port, pendiente-usar]
|
||||||
uses_functions: []
|
uses_functions: []
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "compile_cpp_app(app_name?: string) -> void"
|
signature: "compile_cpp_app(app_name?: string) -> void"
|
||||||
description: "Pipeline que resuelve la app C++ desde el nombre o CWD, la cross-compila para Windows con mingw-w64, y despliega el .exe al escritorio de Windows. Composicion de resolve_cpp_app_dir + build_cpp_windows + deploy_cpp_exe_to_windows."
|
description: "Pipeline que resuelve la app C++ desde el nombre o CWD, la cross-compila para Windows con mingw-w64, y despliega el .exe al escritorio de Windows. Composicion de resolve_cpp_app_dir + build_cpp_windows + deploy_cpp_exe_to_windows."
|
||||||
tags: [cpp, compile, windows, mingw, cross-compile, deploy, pipeline]
|
tags: [cpp, compile, windows, mingw, cross-compile, deploy, pipeline, pendiente-usar]
|
||||||
uses_functions:
|
uses_functions:
|
||||||
- resolve_cpp_app_dir_bash_infra
|
- resolve_cpp_app_dir_bash_infra
|
||||||
- build_cpp_windows_bash_infra
|
- build_cpp_windows_bash_infra
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "full_git_pull() -> stdout: tabla resumen"
|
signature: "full_git_pull() -> stdout: tabla resumen"
|
||||||
description: "Pull automatico de fn_registry + todos los sub-repos locales + submodules + fn sync. Descubre repos locales, stashea dirty trees antes de pullear, hace pull --ff-only, actualiza submodulos del repo principal, pulla ~/.password-store, regenera registry.db con fn index y ejecuta fn sync."
|
description: "Pull automatico de fn_registry + todos los sub-repos locales + submodules + fn sync. Descubre repos locales, stashea dirty trees antes de pullear, hace pull --ff-only, actualiza submodulos del repo principal, pulla ~/.password-store, regenera registry.db con fn index y ejecuta fn sync."
|
||||||
tags: [git, pull, sync, registry, pipeline]
|
tags: [git, pull, sync, registry, pipeline, pendiente-usar]
|
||||||
uses_functions:
|
uses_functions:
|
||||||
- discover_git_repos_bash_infra
|
- discover_git_repos_bash_infra
|
||||||
- git_pull_with_stash_bash_infra
|
- git_pull_with_stash_bash_infra
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "full_git_push(commit_message?: string) -> stdout: tabla resumen"
|
signature: "full_git_push(commit_message?: string) -> stdout: tabla resumen"
|
||||||
description: "Push automatico de fn_registry + todos los sub-repos + fn sync. Descubre repos, escanea secrets (aborta si detecta), auto-inicializa apps/analyses sin .git via ensure_repo_synced, auto-commitea dirty trees, pushea solo repos adelantados, pushea ~/.password-store sin commitear, y ejecuta fn sync."
|
description: "Push automatico de fn_registry + todos los sub-repos + fn sync. Descubre repos, escanea secrets (aborta si detecta), auto-inicializa apps/analyses sin .git via ensure_repo_synced, auto-commitea dirty trees, pushea solo repos adelantados, pushea ~/.password-store sin commitear, y ejecuta fn sync."
|
||||||
tags: [git, push, sync, registry, pipeline]
|
tags: [git, push, sync, registry, pipeline, pendiente-usar]
|
||||||
uses_functions:
|
uses_functions:
|
||||||
- discover_git_repos_bash_infra
|
- discover_git_repos_bash_infra
|
||||||
- scan_secrets_in_dirty_bash_cybersecurity
|
- scan_secrets_in_dirty_bash_cybersecurity
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
---
|
||||||
|
name: generate_capability_doc
|
||||||
|
kind: pipeline
|
||||||
|
lang: bash
|
||||||
|
domain: pipelines
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "generate_capability_doc(group: string, --registry: string?, --out: string?) -> void"
|
||||||
|
description: "Regenera la tabla de funciones de una pagina capability en docs/capabilities/<group>.md consultando registry.db. Preserva bloques curated (Ejemplo canonico, Fronteras, Prerequisitos, Notas). Si el archivo no existe lo crea con plantilla minima."
|
||||||
|
tags: ["capability-groups", "docs", "doctor", "generator", "pipeline"]
|
||||||
|
uses_functions:
|
||||||
|
- audit_capability_groups_go_infra
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: group
|
||||||
|
desc: "slug del capability group (ej. notebook, metabase). Coincide con el tag canonico en frontmatter de funciones."
|
||||||
|
- name: --registry
|
||||||
|
desc: "path opcional a registry.db o a su directorio padre (default: walk-up desde cwd hasta encontrar registry.db)."
|
||||||
|
- name: --out
|
||||||
|
desc: "path opcional del archivo de salida (default: <root>/docs/capabilities/<group>.md)."
|
||||||
|
output: "path del archivo actualizado o creado + count de funciones a stdout. Exit 0 ok, exit 1 si error."
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/pipelines/generate_capability_doc.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Regenerar tabla de notebook (ya existe, preserva Ejemplo canonico / Fronteras)
|
||||||
|
./bash/functions/pipelines/generate_capability_doc.sh notebook
|
||||||
|
# → /home/lucas/fn_registry/docs/capabilities/notebook.md updated (5 functions)
|
||||||
|
|
||||||
|
# Crear pagina nueva para un grupo sin pagina todavia
|
||||||
|
./bash/functions/pipelines/generate_capability_doc.sh metabase
|
||||||
|
# → /home/lucas/fn_registry/docs/capabilities/metabase.md created (12 functions)
|
||||||
|
|
||||||
|
# Especificar registry y destino custom
|
||||||
|
./bash/functions/pipelines/generate_capability_doc.sh android \
|
||||||
|
--registry /ruta/alternativa/registry.db \
|
||||||
|
--out /tmp/android_cap.md
|
||||||
|
# → /tmp/android_cap.md created (8 functions)
|
||||||
|
|
||||||
|
# Grupo sin funciones todavia (avisa pero no falla)
|
||||||
|
./bash/functions/pipelines/generate_capability_doc.sh nuevo_grupo
|
||||||
|
# WARN: El grupo 'nuevo_grupo' no tiene funciones con ese tag en registry.db.
|
||||||
|
# → /home/lucas/fn_registry/docs/capabilities/nuevo_grupo.md created (0 functions)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comportamiento detallado
|
||||||
|
|
||||||
|
### Resolucion de root
|
||||||
|
|
||||||
|
Walk-up desde `cwd` buscando `registry.db`. Si se pasa `--registry`:
|
||||||
|
- Si es un archivo: toma el directorio padre.
|
||||||
|
- Si es un directorio: lo usa directamente.
|
||||||
|
|
||||||
|
### Tabla generada (SQL)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT f.id, f.signature, f.description
|
||||||
|
FROM functions f, json_each(f.tags) j
|
||||||
|
WHERE j.value = '<group>'
|
||||||
|
ORDER BY f.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
JOIN custom via `json_each` — usa `sqlite3` directo (excepcion autorizada para JOINs no expuestos por MCP).
|
||||||
|
|
||||||
|
### Formato de tabla
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
| ID | Firma | Que hace |
|
||||||
|
|---|---|---|
|
||||||
|
| `<id>` | `<signature>` | <description> |
|
||||||
|
```
|
||||||
|
|
||||||
|
Los `|` dentro de signatures y descriptions se escapan como `\|`.
|
||||||
|
|
||||||
|
### Preservacion de bloques curated
|
||||||
|
|
||||||
|
Cuando el archivo ya existe:
|
||||||
|
- El bloque entre `## Funciones` y la siguiente `## ` se reemplaza.
|
||||||
|
- Todo lo anterior y posterior (Ejemplo canonico, Fronteras, Prerequisitos, Notas, etc.) se mantiene intacto.
|
||||||
|
- Implementado con `awk`: copia hasta `## Funciones`, imprime nueva tabla, salta contenido viejo hasta encontrar `^## `, reanuda copia.
|
||||||
|
|
||||||
|
### Archivo nuevo
|
||||||
|
|
||||||
|
Si `docs/capabilities/<group>.md` no existe, se crea con plantilla minima:
|
||||||
|
- Titulo `# Capability: <group>`.
|
||||||
|
- Placeholder de descripcion (editable a mano).
|
||||||
|
- Tabla generada.
|
||||||
|
- Secciones vacias: `## Ejemplo canonico`, `## Fronteras`.
|
||||||
|
|
||||||
|
## Codigos de salida
|
||||||
|
|
||||||
|
| Codigo | Significado |
|
||||||
|
|--------|-------------|
|
||||||
|
| 0 | Exito |
|
||||||
|
| 1 | Error: grupo no especificado, registry.db no encontrado, fallo SQL, awk vacio |
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
- `uses_functions: []` — depende de `sqlite3` y `awk` del sistema, no de funciones del registry.
|
||||||
|
- El tag del grupo debe ser plano (ej. `notebook`, `metabase`), no `tag:notebook`.
|
||||||
|
- Enganchado en `docs/capabilities/INDEX.md` como el mecanismo de auto-generacion referenciado bajo `fn doctor capabilities --update`.
|
||||||
|
- Seguro para re-ejecucion: idempotente si el registry no cambia.
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# generate_capability_doc — regenera la tabla "Funciones" de una pagina capability
|
||||||
|
# Preserva bloques curated (Ejemplo canonico, Fronteras, Prerequisitos, Notas, etc.)
|
||||||
|
# Usage: generate_capability_doc <group> [--registry <path>] [--out <path>]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||||
|
warn() { echo "WARN: $*" >&2; }
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Parse args
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
GROUP=""
|
||||||
|
REGISTRY_PATH=""
|
||||||
|
OUT_PATH=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--registry)
|
||||||
|
shift
|
||||||
|
REGISTRY_PATH="${1:-}"
|
||||||
|
[[ -z "$REGISTRY_PATH" ]] && die "--registry requiere un valor"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--out)
|
||||||
|
shift
|
||||||
|
OUT_PATH="${1:-}"
|
||||||
|
[[ -z "$OUT_PATH" ]] && die "--out requiere un valor"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
die "Opcion desconocida: $1"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
[[ -n "$GROUP" ]] && die "Solo se acepta un <group>. Ya se especifico: '$GROUP'"
|
||||||
|
GROUP="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -z "$GROUP" ]] && die "Uso: generate_capability_doc <group> [--registry <path>] [--out <path>]"
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Resolver registry root
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
find_registry_root() {
|
||||||
|
local dir
|
||||||
|
dir="$(pwd)"
|
||||||
|
while [[ "$dir" != "/" ]]; do
|
||||||
|
if [[ -f "$dir/registry.db" ]]; then
|
||||||
|
echo "$dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
dir="$(dirname "$dir")"
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -n "$REGISTRY_PATH" ]]; then
|
||||||
|
# Si se pasa un path que termina en registry.db, tomar el directorio
|
||||||
|
if [[ -f "$REGISTRY_PATH" ]]; then
|
||||||
|
REGISTRY_ROOT="$(dirname "$(realpath "$REGISTRY_PATH")")"
|
||||||
|
elif [[ -d "$REGISTRY_PATH" ]]; then
|
||||||
|
REGISTRY_ROOT="$(realpath "$REGISTRY_PATH")"
|
||||||
|
else
|
||||||
|
die "registry no encontrado en: $REGISTRY_PATH"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
REGISTRY_ROOT="$(find_registry_root)" || die "No se encontro registry.db. Ejecutar desde dentro del registry o pasar --registry."
|
||||||
|
fi
|
||||||
|
|
||||||
|
DB="$REGISTRY_ROOT/registry.db"
|
||||||
|
[[ -f "$DB" ]] || die "registry.db no encontrado en: $DB"
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Resolver output path
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
if [[ -z "$OUT_PATH" ]]; then
|
||||||
|
OUT_PATH="$REGISTRY_ROOT/docs/capabilities/${GROUP}.md"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Consultar funciones del grupo
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# JOIN custom entre functions y json_each(tags) — excepcion autorizada para sqlite3 directo.
|
||||||
|
# Usamos U+001F (ASCII Unit Separator) como separador de campos para evitar conflictos
|
||||||
|
# con el caracter | que aparece en signatures Python (ej. "list[int] | None").
|
||||||
|
SEP=$'\x1f'
|
||||||
|
ROWS="$(sqlite3 -separator "$SEP" "$DB" \
|
||||||
|
"SELECT f.id, f.signature, f.description
|
||||||
|
FROM functions f, json_each(f.tags) j
|
||||||
|
WHERE j.value = '${GROUP}'
|
||||||
|
ORDER BY f.id;" 2>/dev/null)" || die "Error al consultar registry.db"
|
||||||
|
|
||||||
|
# Escapar | en un valor para que no rompa la tabla Markdown
|
||||||
|
escape_pipe() {
|
||||||
|
printf '%s' "$1" | sed 's/|/\\|/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Construir tabla Markdown
|
||||||
|
TABLE_HEADER="| ID | Firma | Que hace |
|
||||||
|
|---|---|---|"
|
||||||
|
|
||||||
|
TABLE_ROWS=""
|
||||||
|
FUNC_COUNT=0
|
||||||
|
|
||||||
|
if [[ -n "$ROWS" ]]; then
|
||||||
|
while IFS="$SEP" read -r id signature description; do
|
||||||
|
# Limpiar espacios extra
|
||||||
|
id="${id// /}"
|
||||||
|
signature="$(printf '%s' "$signature" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||||
|
description="$(printf '%s' "$description" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||||
|
# Escapar pipes internos en firma y descripcion
|
||||||
|
sig_esc="$(escape_pipe "$signature")"
|
||||||
|
desc_esc="$(escape_pipe "$description")"
|
||||||
|
TABLE_ROWS="${TABLE_ROWS}| \`${id}\` | \`${sig_esc}\` | ${desc_esc} |
|
||||||
|
"
|
||||||
|
FUNC_COUNT=$((FUNC_COUNT + 1))
|
||||||
|
done <<< "$ROWS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $FUNC_COUNT -eq 0 ]]; then
|
||||||
|
warn "El grupo '${GROUP}' no tiene funciones con ese tag en registry.db."
|
||||||
|
TABLE_CONTENT="_No hay funciones con tag ${GROUP}._"
|
||||||
|
else
|
||||||
|
TABLE_CONTENT="${TABLE_HEADER}
|
||||||
|
${TABLE_ROWS}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Crear o actualizar el archivo
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
mkdir -p "$(dirname "$OUT_PATH")"
|
||||||
|
|
||||||
|
if [[ ! -f "$OUT_PATH" ]]; then
|
||||||
|
# --- Archivo nuevo: plantilla minima ---
|
||||||
|
cat > "$OUT_PATH" <<TEMPLATE
|
||||||
|
# Capability: ${GROUP}
|
||||||
|
|
||||||
|
_(Descripcion del grupo — editar a mano)_
|
||||||
|
|
||||||
|
## Funciones
|
||||||
|
|
||||||
|
${TABLE_CONTENT}
|
||||||
|
|
||||||
|
## Ejemplo canonico
|
||||||
|
|
||||||
|
_(Anadir 1-2 bloques de codigo end-to-end)_
|
||||||
|
|
||||||
|
## Fronteras
|
||||||
|
|
||||||
|
_(Que NO cubre este grupo)_
|
||||||
|
TEMPLATE
|
||||||
|
echo "${OUT_PATH} created (${FUNC_COUNT} functions)"
|
||||||
|
else
|
||||||
|
# --- Archivo existente: reemplazar SOLO el bloque "## Funciones" ---
|
||||||
|
# Estrategia awk:
|
||||||
|
# - Copiar todo hasta (e incluyendo) "## Funciones"
|
||||||
|
# - Imprimir linea en blanco + nueva tabla + linea en blanco
|
||||||
|
# - Saltar lineas hasta encontrar la proxima seccion "^## "
|
||||||
|
# - Reanudar copia desde esa seccion en adelante
|
||||||
|
|
||||||
|
# Escribir tabla en archivo temporal para pasarla a awk sin problemas de escaping
|
||||||
|
TMP_TABLE="$(mktemp)"
|
||||||
|
trap 'rm -f "$TMP_TABLE"' EXIT
|
||||||
|
printf '%s\n' "$TABLE_CONTENT" > "$TMP_TABLE"
|
||||||
|
|
||||||
|
TMP_OUT="$(mktemp)"
|
||||||
|
trap 'rm -f "$TMP_TABLE" "$TMP_OUT"' EXIT
|
||||||
|
|
||||||
|
awk -v table_file="$TMP_TABLE" '
|
||||||
|
BEGIN {
|
||||||
|
in_functions = 0
|
||||||
|
done = 0
|
||||||
|
# Leer tabla nueva en una variable
|
||||||
|
table_content = ""
|
||||||
|
while ((getline line < table_file) > 0) {
|
||||||
|
table_content = table_content line "\n"
|
||||||
|
}
|
||||||
|
close(table_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detectar inicio de la seccion Funciones
|
||||||
|
/^## Funciones$/ && !done {
|
||||||
|
print $0
|
||||||
|
printf "\n"
|
||||||
|
printf "%s", table_content
|
||||||
|
in_functions = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mientras estamos en el bloque Funciones, saltar lineas hasta proxima seccion
|
||||||
|
in_functions && /^## / {
|
||||||
|
in_functions = 0
|
||||||
|
done = 1
|
||||||
|
printf "\n"
|
||||||
|
print $0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
in_functions {
|
||||||
|
# Saltar contenido viejo del bloque Funciones
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fuera del bloque: copiar tal cual
|
||||||
|
{ print $0 }
|
||||||
|
|
||||||
|
END {
|
||||||
|
# Si no habia seccion siguiente (Funciones era la ultima), cerrar bien
|
||||||
|
if (in_functions) {
|
||||||
|
printf "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$OUT_PATH" > "$TMP_OUT"
|
||||||
|
|
||||||
|
# Verificar que el archivo resultante no este vacio
|
||||||
|
if [[ ! -s "$TMP_OUT" ]]; then
|
||||||
|
rm -f "$TMP_OUT"
|
||||||
|
die "Error: awk produjo un archivo vacio. El archivo original no fue modificado."
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$TMP_OUT" "$OUT_PATH"
|
||||||
|
echo "${OUT_PATH} updated (${FUNC_COUNT} functions)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "install_systemd_service --name <N> --exec <PATH> [opts] -> json"
|
signature: "install_systemd_service --name <N> --exec <PATH> [opts] -> json"
|
||||||
description: "Pipeline que registra una app como servicio systemd del sistema: genera el unit, lo instala en /etc/systemd/system/, hace daemon-reload, enable, start y devuelve status. Requiere sudo sin password para systemctl y escritura en /etc/systemd/system/."
|
description: "Pipeline que registra una app como servicio systemd del sistema: genera el unit, lo instala en /etc/systemd/system/, hace daemon-reload, enable, start y devuelve status. Requiere sudo sin password para systemctl y escritura en /etc/systemd/system/."
|
||||||
tags: [systemd, service, local, infra, pipeline, install]
|
tags: [systemd, service, local, infra, pipeline, install, pendiente-usar]
|
||||||
uses_functions:
|
uses_functions:
|
||||||
- systemd_local_install_unit_bash_infra
|
- systemd_local_install_unit_bash_infra
|
||||||
- systemd_local_enable_bash_infra
|
- systemd_local_enable_bash_infra
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
name: propose_capability_groups
|
||||||
|
kind: pipeline
|
||||||
|
lang: bash
|
||||||
|
domain: pipelines
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "propose_capability_groups([--min-count N], [--max-domains M], [--json], [--apply tag]) -> report"
|
||||||
|
description: "Analiza tags candidatos a capability group (issue 0086). Filtra via blocklist + cap de dominios. Lista candidatos o promociona con --apply (llama generate_capability_doc + actualiza INDEX.md)."
|
||||||
|
tags: ["capability-groups", "doctor", "audit", "pipeline"]
|
||||||
|
uses_functions:
|
||||||
|
- generate_capability_doc_bash_pipelines
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: --min-count
|
||||||
|
desc: "minimo de funciones con el tag para considerarlo candidato (default 3)"
|
||||||
|
- name: --max-domains
|
||||||
|
desc: "maximo de dominios distintos entre funciones del tag (default 3, filtra tags genericos que cruzan muchos dominios)"
|
||||||
|
- name: --json
|
||||||
|
desc: "salida JSON estructurada en vez de texto (util para agentes)"
|
||||||
|
- name: --apply
|
||||||
|
desc: "promociona el tag dado a capability group: genera doc con generate_capability_doc + actualiza docs/capabilities/INDEX.md (idempotente)"
|
||||||
|
output: "tabla de candidatos a stdout (tag, count, domains, already_group, samples), o efectos en disco si --apply (nuevo .md + fila en INDEX.md)"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/pipelines/propose_capability_groups.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Listar candidatos con al menos 10 funciones
|
||||||
|
bash bash/functions/pipelines/propose_capability_groups.sh --min-count 10
|
||||||
|
|
||||||
|
# Listar con salida JSON (para agentes)
|
||||||
|
bash bash/functions/pipelines/propose_capability_groups.sh --min-count 5 --json
|
||||||
|
|
||||||
|
# Promocionar un tag a capability group
|
||||||
|
bash bash/functions/pipelines/propose_capability_groups.sh --apply metabase
|
||||||
|
|
||||||
|
# Via fn run
|
||||||
|
./fn run propose_capability_groups --min-count 10
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logica
|
||||||
|
|
||||||
|
### Modo listar (sin --apply)
|
||||||
|
|
||||||
|
1. Resuelve la raiz del registry (walk-up hasta `registry.db`).
|
||||||
|
2. Filtra tags via **blocklist** hardcodeada (idiomas, dominios, CRUD generico, verbos super-genericos, primitivas, estados).
|
||||||
|
3. Query SQL con `json_each(f.tags)` + `GROUP BY` + `HAVING cnt >= N AND domains <= M`.
|
||||||
|
4. Marca cada candidato con `already_group: yes/no` parseando los links `[tag](tag.md)` de `docs/capabilities/INDEX.md`.
|
||||||
|
5. Imprime tabla formateada con maximo 3 IDs de muestra por candidato.
|
||||||
|
6. Resumen: total candidatos, ya-grupos, nuevos.
|
||||||
|
|
||||||
|
### Modo --apply
|
||||||
|
|
||||||
|
1. Valida que el tag no esta en blocklist y pasa el filtro de count/domains.
|
||||||
|
2. Llama a `bash/functions/pipelines/generate_capability_doc.sh <tag>`.
|
||||||
|
3. Inserta fila en `docs/capabilities/INDEX.md` (idempotente — no duplica si ya existe).
|
||||||
|
4. Imprime checklist de edicion manual para el usuario.
|
||||||
|
|
||||||
|
## Blocklist
|
||||||
|
|
||||||
|
Tags en la blocklist nunca son candidatos (caso exacto):
|
||||||
|
|
||||||
|
- **Idioma**: `go py bash ps ts python cpp`
|
||||||
|
- **Dominio**: `core infra finance datascience cybersecurity shell tui pipelines browser`
|
||||||
|
- **Kind/purity**: `function pipeline component pure impure`
|
||||||
|
- **CRUD generico**: `add create delete list update get set remove insert`
|
||||||
|
- **Verbo super-generico**: `compose convert combine append empty exists check find format parse render`
|
||||||
|
- **Estructural**: `generic helper utility wrapper test`
|
||||||
|
- **Primitivas**: `string number int float array slice map dict value key`
|
||||||
|
- **Estados**: `pending-usar pendiente-usar`
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
- Usa `sqlite3` directo para el JOIN custom `functions + json_each(tags)` (autorizado por la regla registry_calls.md — JOINs custom no expuestos por el MCP).
|
||||||
|
- La insercion en INDEX.md usa `python3` embebido para manejar la posicion relativa a la cabecera de tabla de forma portable.
|
||||||
|
- El flag `--max-domains` es la heuristica clave: un tag como `add` aparece en 8 dominios → filtrado; `metabase` aparece en 2 → candidato valido.
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# propose_capability_groups — analiza tags candidatos a capability group (issue 0086)
|
||||||
|
# Filtra via blocklist + cap de dominios. Lista candidatos o promociona con --apply.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Blocklist: tags genericos que nunca son capability groups
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
BLOCKLIST=(
|
||||||
|
# idioma
|
||||||
|
go py bash ps ts python cpp
|
||||||
|
# dominio
|
||||||
|
core infra finance datascience cybersecurity shell tui pipelines browser
|
||||||
|
# kind / purity
|
||||||
|
function pipeline component pure impure
|
||||||
|
# CRUD generico
|
||||||
|
add create delete list update get set remove insert
|
||||||
|
# verbo super-generico
|
||||||
|
compose convert combine append empty exists check find format parse render
|
||||||
|
# estructural
|
||||||
|
generic helper utility wrapper test
|
||||||
|
# primitivas
|
||||||
|
string number int float array slice map dict value key
|
||||||
|
# estados
|
||||||
|
pending-usar pendiente-usar
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Resolver raiz del registry (walk-up hasta registry.db)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
find_registry_root() {
|
||||||
|
local dir
|
||||||
|
dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
while [[ "$dir" != "/" ]]; do
|
||||||
|
if [[ -f "$dir/registry.db" ]]; then
|
||||||
|
echo "$dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
dir="$(dirname "$dir")"
|
||||||
|
done
|
||||||
|
echo "ERROR: registry.db no encontrado en ningún directorio padre" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Defaults
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
MIN_COUNT=3
|
||||||
|
MAX_DOMAINS=4
|
||||||
|
JSON_MODE=false
|
||||||
|
APPLY_TAG=""
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Parse args
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--min-count)
|
||||||
|
MIN_COUNT="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--max-domains)
|
||||||
|
MAX_DOMAINS="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--json)
|
||||||
|
JSON_MODE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--apply)
|
||||||
|
APPLY_TAG="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Uso: propose_capability_groups [--min-count N] [--max-domains M] [--json] [--apply <tag>]"
|
||||||
|
echo ""
|
||||||
|
echo " --min-count N Minimo de funciones con el tag (default: 3)"
|
||||||
|
echo " --max-domains M Maximo de dominios distintos (default: 3)"
|
||||||
|
echo " --json Salida JSON"
|
||||||
|
echo " --apply <tag> Promociona el tag a capability group"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: argumento desconocido: $1" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
REGISTRY_ROOT="$(find_registry_root)"
|
||||||
|
DB="$REGISTRY_ROOT/registry.db"
|
||||||
|
INDEX_MD="$REGISTRY_ROOT/docs/capabilities/INDEX.md"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Construir lista de tags bloqueados como CSV quoted para SQL IN (...)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
build_blocklist_sql() {
|
||||||
|
local csv=""
|
||||||
|
for tag in "${BLOCKLIST[@]}"; do
|
||||||
|
csv="${csv}'${tag}',"
|
||||||
|
done
|
||||||
|
# quitar coma final
|
||||||
|
echo "${csv%,}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Parsear tags ya en INDEX.md: extraer slugs de [tag](tag.md)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
get_existing_groups() {
|
||||||
|
if [[ ! -f "$INDEX_MD" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
grep -oP '\[([^\]]+)\]\(\1\.md\)' "$INDEX_MD" | grep -oP '\[([^\]]+)\]' | tr -d '[]' || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Comprobar si un tag esta en blocklist
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
in_blocklist() {
|
||||||
|
local tag="$1"
|
||||||
|
for blocked in "${BLOCKLIST[@]}"; do
|
||||||
|
if [[ "$blocked" == "$tag" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# MODO --apply: promocionar un tag a capability group
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
apply_tag() {
|
||||||
|
local tag="$1"
|
||||||
|
|
||||||
|
# Validar: no en blocklist
|
||||||
|
if in_blocklist "$tag"; then
|
||||||
|
echo "ERROR: '$tag' está en la blocklist de tags genericos. No se puede promocionar." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validar: count >= min y domains <= max
|
||||||
|
local blocklist_sql
|
||||||
|
blocklist_sql="$(build_blocklist_sql)"
|
||||||
|
local row
|
||||||
|
row="$(sqlite3 "$DB" "
|
||||||
|
SELECT COUNT(*) AS cnt, COUNT(DISTINCT f.domain) AS domains
|
||||||
|
FROM functions f, json_each(f.tags) j
|
||||||
|
WHERE j.value = '${tag}'
|
||||||
|
GROUP BY j.value;
|
||||||
|
" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ -z "$row" ]]; then
|
||||||
|
echo "ERROR: tag '$tag' no encontrado en el registry o no tiene funciones." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cnt domains
|
||||||
|
cnt="$(echo "$row" | cut -d'|' -f1)"
|
||||||
|
domains="$(echo "$row" | cut -d'|' -f2)"
|
||||||
|
|
||||||
|
if [[ "$cnt" -lt "$MIN_COUNT" ]]; then
|
||||||
|
echo "ERROR: tag '$tag' tiene $cnt funciones, minimo requerido es $MIN_COUNT." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "$domains" -gt "$MAX_DOMAINS" ]]; then
|
||||||
|
echo "ERROR: tag '$tag' aparece en $domains dominios distintos (maximo $MAX_DOMAINS). Probablemente es generico." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Promocionando tag '$tag' a capability group..."
|
||||||
|
echo " funciones: $cnt dominios: $domains"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Paso 1: llamar a generate_capability_doc
|
||||||
|
local gen_script="$REGISTRY_ROOT/bash/functions/pipelines/generate_capability_doc.sh"
|
||||||
|
if [[ ! -f "$gen_script" ]]; then
|
||||||
|
echo "ERROR: no se encontro generate_capability_doc.sh en $gen_script" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "=> Generando docs/capabilities/${tag}.md ..."
|
||||||
|
bash "$gen_script" "$tag"
|
||||||
|
echo " OK"
|
||||||
|
|
||||||
|
# Paso 2: anadir fila a INDEX.md (idempotente)
|
||||||
|
if [[ ! -f "$INDEX_MD" ]]; then
|
||||||
|
echo "ERROR: no se encontro INDEX.md en $INDEX_MD" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local row_pattern
|
||||||
|
row_pattern="| \[${tag}\](${tag}.md)"
|
||||||
|
if grep -qF "$row_pattern" "$INDEX_MD"; then
|
||||||
|
echo "=> Fila para '$tag' ya existe en INDEX.md — sin cambios."
|
||||||
|
else
|
||||||
|
echo "=> Anadiendo fila a INDEX.md ..."
|
||||||
|
# Insertar despues de la linea de cabecera |---|---|---| de la tabla "Grupos vigentes"
|
||||||
|
# Buscamos la linea del separador de cabecera de tabla que va despues de "## Grupos vigentes"
|
||||||
|
local new_row="| [${tag}](${tag}.md) | ${cnt} | _(editar — promovido automaticamente)_ |"
|
||||||
|
# Usar Python para insertar la linea de forma portable (awk no maneja bien insercion relativa)
|
||||||
|
python3 - "$INDEX_MD" "$new_row" <<'PYEOF'
|
||||||
|
import sys
|
||||||
|
|
||||||
|
index_path = sys.argv[1]
|
||||||
|
new_row = sys.argv[2]
|
||||||
|
|
||||||
|
with open(index_path, "r") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
# Encontrar el bloque "Grupos vigentes" y luego la linea separadora |---|---|---|
|
||||||
|
in_section = False
|
||||||
|
insert_after = -1
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if "## Grupos vigentes" in line:
|
||||||
|
in_section = True
|
||||||
|
if in_section and line.strip().startswith("|---|"):
|
||||||
|
insert_after = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if insert_after == -1:
|
||||||
|
print("ERROR: no se encontro la tabla 'Grupos vigentes' en INDEX.md", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
lines.insert(insert_after + 1, new_row + "\n")
|
||||||
|
|
||||||
|
with open(index_path, "w") as f:
|
||||||
|
f.writelines(lines)
|
||||||
|
|
||||||
|
print(f" Fila insertada en posicion {insert_after + 1}")
|
||||||
|
PYEOF
|
||||||
|
echo " OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "HECHO. Pasos manuales pendientes:"
|
||||||
|
echo " 1. Editar docs/capabilities/${tag}.md:"
|
||||||
|
echo " - Anadir parrafo de descripcion del grupo."
|
||||||
|
echo " - Completar seccion 'Ejemplo canonico' con codigo real."
|
||||||
|
echo " - Completar seccion 'Fronteras' (que NO hace el grupo)."
|
||||||
|
echo " - Anadir 'Notas' si aplica."
|
||||||
|
echo " 2. Actualizar la frase descripcion en docs/capabilities/INDEX.md"
|
||||||
|
echo " (reemplazar el placeholder con descripcion real)."
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# MODO LISTAR: analizar candidatos
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
list_candidates() {
|
||||||
|
local blocklist_sql
|
||||||
|
blocklist_sql="$(build_blocklist_sql)"
|
||||||
|
|
||||||
|
# Query: tags con suficientes funciones y no demasiados dominios
|
||||||
|
local query
|
||||||
|
query="
|
||||||
|
SELECT
|
||||||
|
j.value AS tag,
|
||||||
|
COUNT(*) AS cnt,
|
||||||
|
COUNT(DISTINCT f.domain) AS domains,
|
||||||
|
GROUP_CONCAT(DISTINCT f.domain) AS domain_list,
|
||||||
|
GROUP_CONCAT(f.id) AS function_ids
|
||||||
|
FROM functions f, json_each(f.tags) j
|
||||||
|
WHERE j.value NOT IN (${blocklist_sql})
|
||||||
|
GROUP BY j.value
|
||||||
|
HAVING cnt >= ${MIN_COUNT} AND domains <= ${MAX_DOMAINS}
|
||||||
|
ORDER BY cnt DESC;
|
||||||
|
"
|
||||||
|
|
||||||
|
local raw_results
|
||||||
|
raw_results="$(sqlite3 "$DB" "$query" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ -z "$raw_results" ]]; then
|
||||||
|
echo "No se encontraron candidatos con min-count=${MIN_COUNT} y max-domains=${MAX_DOMAINS}."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Obtener grupos ya existentes
|
||||||
|
local existing_groups
|
||||||
|
existing_groups="$(get_existing_groups)"
|
||||||
|
|
||||||
|
if $JSON_MODE; then
|
||||||
|
# Salida JSON
|
||||||
|
echo "["
|
||||||
|
local first=true
|
||||||
|
while IFS='|' read -r tag cnt domains domain_list function_ids; do
|
||||||
|
[[ -z "$tag" ]] && continue
|
||||||
|
local already_group="false"
|
||||||
|
if echo "$existing_groups" | grep -qxF "$tag" 2>/dev/null; then
|
||||||
|
already_group="true"
|
||||||
|
fi
|
||||||
|
# Tomar hasta 3 samples
|
||||||
|
local samples
|
||||||
|
samples="$(echo "$function_ids" | tr ',' '\n' | head -3 | tr '\n' ',' | sed 's/,$//')"
|
||||||
|
if $first; then
|
||||||
|
first=false
|
||||||
|
else
|
||||||
|
echo ","
|
||||||
|
fi
|
||||||
|
printf ' {"tag":"%s","count":%s,"domains":%s,"domain_list":"%s","already_group":%s,"samples":[%s]}' \
|
||||||
|
"$tag" "$cnt" "$domains" "$domain_list" "$already_group" \
|
||||||
|
"$(echo "$samples" | sed 's/,/","/g' | sed 's/^/"/' | sed 's/$/"/')"
|
||||||
|
done <<< "$raw_results"
|
||||||
|
echo ""
|
||||||
|
echo "]"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Salida texto humano
|
||||||
|
local total=0
|
||||||
|
local already=0
|
||||||
|
local nuevos=0
|
||||||
|
|
||||||
|
# Acumular lineas para contar primero
|
||||||
|
local lines_output=()
|
||||||
|
while IFS='|' read -r tag cnt domains domain_list function_ids; do
|
||||||
|
[[ -z "$tag" ]] && continue
|
||||||
|
local already_group="no"
|
||||||
|
if echo "$existing_groups" | grep -qxF "$tag" 2>/dev/null; then
|
||||||
|
already_group="yes"
|
||||||
|
((already++)) || true
|
||||||
|
else
|
||||||
|
((nuevos++)) || true
|
||||||
|
fi
|
||||||
|
((total++)) || true
|
||||||
|
# Tomar hasta 3 samples
|
||||||
|
local samples
|
||||||
|
samples="$(echo "$function_ids" | tr ',' '\n' | head -3 | paste -sd ',' -)"
|
||||||
|
lines_output+=("$(printf "%-22s %-6s %-25s %-13s %s" "$tag" "$cnt" "$domain_list" "$already_group" "$samples")")
|
||||||
|
done <<< "$raw_results"
|
||||||
|
|
||||||
|
printf "%-22s %-6s %-25s %-13s %s\n" "TAG" "COUNT" "DOMAINS" "ALREADY_GROUP" "SAMPLES"
|
||||||
|
printf "%-22s %-6s %-25s %-13s %s\n" "----------------------" "------" "-------------------------" "-------------" "-------"
|
||||||
|
for line in "${lines_output[@]}"; do
|
||||||
|
echo "$line"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo "${total} candidatos. ${already} ya son grupo. ${nuevos} son nuevos."
|
||||||
|
echo "Promociona con: bash bash/functions/pipelines/propose_capability_groups.sh --apply <tag>"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Entry point
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [[ -n "$APPLY_TAG" ]]; then
|
||||||
|
apply_tag "$APPLY_TAG"
|
||||||
|
else
|
||||||
|
list_candidates
|
||||||
|
fi
|
||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "check_command(cmd: string, [error_code: string], [description: string]) -> void; check_commands(cmd...: string) -> void; check_directory(dir: string, [msg: string]) -> void; check_file(file: string, [msg: string]) -> void"
|
signature: "check_command(cmd: string, [error_code: string], [description: string]) -> void; check_commands(cmd...: string) -> void; check_directory(dir: string, [msg: string]) -> void; check_file(file: string, [msg: string]) -> void"
|
||||||
description: "Verifica existencia de comandos, directorios y archivos con output formateado. Complementa assert_command_exists con mensajes de error detallados y logging."
|
description: "Verifica existencia de comandos, directorios y archivos con output formateado. Complementa assert_command_exists con mensajes de error detallados y logging."
|
||||||
tags: [bash, check, dependency, command, exists, validation]
|
tags: [bash, check, dependency, command, exists, validation, pendiente-usar]
|
||||||
uses_functions: [bash_log_bash_shell]
|
uses_functions: [bash_log_bash_shell]
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ version: "1.0.0"
|
|||||||
purity: impure
|
purity: impure
|
||||||
signature: "bash_confirm(prompt: string, [default: string]) -> exit_code"
|
signature: "bash_confirm(prompt: string, [default: string]) -> exit_code"
|
||||||
description: "Dialogo interactivo de confirmacion y/n con valor por defecto configurable. Soporta respuestas yes/y/si."
|
description: "Dialogo interactivo de confirmacion y/n con valor por defecto configurable. Soporta respuestas yes/y/si."
|
||||||
tags: [bash, confirm, prompt, interactive, dialog]
|
tags: [bash, confirm, prompt, interactive, dialog, pendiente-usar]
|
||||||
uses_functions: [bash_colors_bash_shell]
|
uses_functions: [bash_colors_bash_shell]
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user