chore: auto-commit (286 archivos)

- .claude/agents/fn-orquestador/SKILL.md
- .claude/commands/fn_claude.md
- .claude/rules/INDEX.md
- .claude/rules/cpp_apps.md
- .claude/rules/ids_naming.md
- CHANGELOG.md
- apps/dag_engine/README.md
- apps/dag_engine/api.go
- apps/dag_engine/dags_migrated/example.yaml
- apps/dag_engine/dags_migrated/example_lineage_tracking.yaml
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 16:33:22 +02:00
parent 0b9af8f1bb
commit a03675113a
281 changed files with 12596 additions and 19526 deletions
+6
View File
@@ -31,6 +31,12 @@ Indice de grupos de capacidades del registry. Cada grupo agrupa >=3 funciones qu
| [playwright](playwright.md) | 6 | E2E browser: launch chromium, login kanban, drag dnd-kit, keyboard sequence, wait predicate, assert class |
| [cpp-tables](tql.md) | 9 | Table Query Language C++ puro: filter, group, agg, sort, join, stats, formulas Lua, round-trip emit/apply |
| [data-table-renderers](data_table_renderers.md) | 1 | API declarativa de cell renderers para data_table: Badge, Progress, Duration, Icon via TableInput.column_specs |
| [scheduler](scheduler.md) | 4 | Cron expression parsing, matching, next-run y traduccion humana (consume `apps/dag_engine`) |
| [extractor](extractor.md) | 15 | Funciones que leen datos de fuentes externas (BD, API, archivos, web). Nodos input de `data_factory` |
| [transformer](transformer.md) | 15 | Funciones que clean/dedup/aggregate/feature-engineer datos. Nodos intermedios de `data_factory` |
| [sink](sink.md) | 11 | Funciones que escriben datos a destino externo (BD, dashboard, alerta, email). Nodos output |
| [validator](validator.md) | 6 | Funciones que verifican datos/config contra reglas. Pre-flight de sinks y gates en DAGs |
| [navegator](navegator.md) | 4 | Automatización de browser via CDP + AX tree + LLM: obtener, limpiar, chunkear AX tree y llamar a Claude CLI |
## Como anadir grupo
+26 -3
View File
@@ -1,6 +1,6 @@
# Capability: android
_(Descripcion del grupo — editar a mano)_
Toolbelt Android operable desde WSL2. Cubre: ADB (`adb_wsl`, conexion al daemon Windows), AVD emulator management (list/start/stop/wait, geo-fix), APK lifecycle (`android_apk_install`, `android_app_clear`, `android_app_launch`, `android_uninstall`), Capacitor build pipelines (`capacitor_build_apk`, `deploy_capacitor_to_emulator`), logcat streaming. WSL2 -> Windows adb daemon, no requiere Android Studio.
## Funciones
@@ -47,8 +47,31 @@ _(Descripcion del grupo — editar a mano)_
## Ejemplo canonico
_(Anadir 1-2 bloques de codigo end-to-end)_
### Arrancar emulator + deploy Capacitor + logcat
```bash
./fn run android_emulator_list
./fn run android_emulator_start --avd Pixel_7_API_34 --wait
./fn run deploy_capacitor_to_emulator \
--app-dir ~/projects/myapp \
--avd Pixel_7_API_34 \
--package com.example.myapp
./fn run adb_wsl -- logcat -v time | grep -E "(myapp|FATAL|AndroidRuntime)"
```
### APK install/uninstall manual
```bash
./fn run android_apk_install --apk ~/myapp.apk --device emulator-5554
./fn run android_app_launch --package com.example.myapp
./fn run android_app_clear --package com.example.myapp # limpia datos sin uninstall
```
## Fronteras
_(Que NO cubre este grupo)_
- **NO compila APK desde Gradle nativo**. Solo Capacitor build. Para Gradle puro, ver issue 0076 (`gradle_run`).
- **NO instala Android SDK**. Asume `ANDROID_HOME` apuntando al SDK Windows accessible via WSL.
- **NO maneja iOS**. Solo Android. Para iOS, ver issue 0072h (gamedev roadmap).
- **NO depura nativo NDK**. Para LLDB-stage debugging, attach manual via Android Studio.
+30 -3
View File
@@ -1,6 +1,6 @@
# Capability: bigquery
_(Descripcion del grupo — editar a mano)_
Operar Google BigQuery via SDK Python `google-cloud-bigquery` y Go `fetch_data_frame`. Cubre: auth (`bq_auth`), queries con caching, gestion de datasets/tables (create/copy/delete/get/list), jobs (cancel, wait), schemas, exports a GCS, y carga a DataFrames (`fetch_data_frame`, polars).
## Funciones
@@ -36,8 +36,35 @@ _(Descripcion del grupo — editar a mano)_
## Ejemplo canonico
_(Anadir 1-2 bloques de codigo end-to-end)_
### Query + cargar a polars
```python
import os, sys
sys.path.insert(0, os.path.join(os.environ["FN_REGISTRY_ROOT"], "python", "functions"))
from infra import bq_auth, bq_query
client = bq_auth(project_id="my-gcp-project")
df = bq_query(client, "SELECT * FROM `my-gcp-project.dataset.table` LIMIT 1000")
print(df.shape)
```
### Gestion de tablas
```bash
./fn run bq_list_tables --dataset analytics
./fn run bq_create_table --dataset analytics --table events --schema-file schema.json
./fn run bq_copy_table --src analytics.events --dst analytics.events_backup
```
### Carga a DataFrame Go (polars/pandas-go)
```bash
./fn run fetch_data_frame --query "SELECT date, sales FROM analytics.daily" --output sales.parquet
```
## Fronteras
_(Que NO cubre este grupo)_
- **NO maneja Auth ADC interactivo**. Asume credenciales via service account JSON o `gcloud auth application-default login` ya ejecutado.
- **NO billing analytics**. Para coste/quota analysis, BigQuery UI o Cloud Console.
- **NO orquesta Dataflow ni Dataproc**. Solo SQL queries + table ops.
- Comparte funciones con tags `gcp` y `google-cloud` (mismas funciones, multi-tag).
+23 -1
View File
@@ -12,7 +12,9 @@ Operar apps C++ del registry en Windows desde WSL2: compilar, desplegar, lanzar,
| `is_cpp_app_running_windows_bash_infra` | `is_cpp_app_running_windows(app_name)` | Exit 0 si el proceso esta vivo (tasklist.exe), stdout: `RUNNING: PID=N MEM=...K` |
| `launch_cpp_app_windows_bash_infra` | `launch_cpp_app_windows(app_name, [desktop_dir])` | Lanza .exe en Windows via cmd.exe /c start, retorna inmediatamente |
| `e2e_run_cpp_windows_bash_infra` | `e2e_run_cpp_windows(target, [--no-build], [--no-deploy])` | Build + deploy + run headless de app C++ (tests e2e tipo altsnap) |
| `redeploy_cpp_app_windows_bash_pipelines` | `redeploy_cpp_app_windows(app_name, app_dir, [--build])` | Pipeline completo: build? + deploy + launch + verify en un comando |
| `redeploy_cpp_app_windows_bash_pipelines` | `redeploy_cpp_app_windows(app_name, app_dir, [--build])` | Pipeline completo (UNA app): build? + deploy + launch + verify |
| `redeploy_all_cpp_apps_bash_pipelines` | `redeploy_all_cpp_apps([filter])` | Bulk: compila TODO el arbol cpp/ en un cmake pass + despliega cada `.exe`. Filtro opcional por substring de nombre. Tolerante a fallos por app. |
| `resolve_cpp_app_dir_bash_infra` | `resolve_cpp_app_dir([app_name])` | Resuelve `<name>\t<abs_dir>`. Busca en `apps/`, `cpp/apps/` (legacy), `projects/*/apps/`. Sin arg deduce desde CWD. |
## Ejemplo canonico
@@ -78,3 +80,23 @@ fi
El flujo estandar es: `build_cpp_windows``deploy_cpp_exe_to_windows``launch_cpp_app_windows``is_cpp_app_running_windows`. El pipeline `redeploy_cpp_app_windows` encapsula los pasos 2-4 (con 1 opcional via `--build`).
`deploy_cpp_exe_to_windows` ya incluye un `taskkill.exe` interno — no es necesario matar el proceso manualmente antes de llamarlo. `is_cpp_app_running_windows` es util cuando se quiere tomar la decision de matar/no matar de forma explicita antes del deploy.
### Bulk redeploy (2026-05-16)
Tras cambios en `cpp/framework/app_base.*` o cualquier funcion linkada por muchos apps, usar:
```bash
./fn run redeploy_all_cpp_apps # todas las apps
./fn run redeploy_all_cpp_apps graph # solo apps con "graph" en el nombre
```
Build best-effort: si test targets rotos (`test_llm_anthropic`, `test_graph_icons` usan `setenv()` no disponible en mingw), el pipeline avisa pero sigue desplegando los `.exe` que SI se construyeron. Resumen final con OK/SKIPPED/FAILED por app.
### Layouts soportados
`resolve_cpp_app_dir` (y por tanto `compile_cpp_app`) busca apps en este orden:
1. `apps/<X>/` — canonical issue 0096.
2. `cpp/apps/<X>/` — legacy.
3. `projects/*/apps/<X>/` — apps de proyectos.
Tambien deduce desde CWD si la sesion esta dentro de cualquiera de las tres. Fix retroactivo: antes del 2026-05-16 `resolve_cpp_app_dir` no veia `apps/<X>/` y `./fn run compile_cpp_app <name>` fallaba para apps en ese layout.
+69
View File
@@ -0,0 +1,69 @@
# Capability: deploy
Deploy completo de apps Go/C++/Capacitor a destinos diversos: VPS remotos via SSH+systemd+rsync, VPS con Docker+Traefik+Coolify, Windows Desktop via cross-compile WSL2->mingw-w64, emuladores Android. Cubre setup inicial (vps_setup_app, generate_dockerfile, traefik dynamic), deploys continuos (rsync_deploy + systemd_restart + wait_for_http), webhooks Gitea, y health checks.
## Funciones
| ID | Firma | Que hace |
|---|---|---|
| `compile_cpp_app_bash_pipelines` | `compile_cpp_app(app_name?: string) -> void` | 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. |
| `deploy_app_go_infra` | `func DeployApp(appDir string, imageName string, port int, envVars map[string]string) (string, error)` | Orquesta el deploy completo de una app Go en Docker. Pasos: genera Dockerfile, lo escribe a disco, construye la imagen y lanza el contenedor en modo detach con port mapping. Retorna el container ID. |
| `deploy_app_remote_go_infra` | `func DeployAppRemote(conn SSHConn, cfg DeployConfig) error` | Orquesta el deploy continuo de una app a un VPS: verifica SSH, compila localmente, sube binario, reinicia systemd y hace health check. |
| `deploy_capacitor_to_emulator_bash_pipelines` | `deploy_capacitor_to_emulator(app_dir: string, avd_name?: string, package_name?: string) -> void` | Pipeline end-to-end: build Capacitor APK + arranca AVD + instala + opcionalmente lanza la app. Valida que el AVD existe, construye el APK con capacitor_build_apk, arranca el emulador de forma idempotente, instala el APK y lanza la app si se da package_name. Imprime comando logcat sugerido al final. |
| `deploy_cpp_exe_to_windows_bash_infra` | `deploy_cpp_exe_to_windows(app_name: string, app_dir: string) -> void` | Copia el .exe de Windows (compilado por build_cpp_windows) y sus assets al escritorio de Windows /mnt/c/Users/lucas/Desktop/apps/<APP>/. Mata el proceso si esta corriendo (taskkill.exe pre-autorizado), copia DLLs, sincroniza assets/ y enrichers/ con rsync, maneja runtime Python embebido si python_runtime: true en app.md, y copia extras gx-cli. Preserva siempre local_files/ (estado del usuario). |
| `docker_compose_remote_deploy_bash_infra` | `docker_compose_remote_deploy(host: string, remote_dir: string, branch: string, compose_files: string) -> json` | Despliega un stack Docker Compose en un host remoto via SSH. Verifica conectividad, hace git pull del branch indicado, actualiza imagenes con docker-compose pull y levanta/recrea los servicios modificados con docker-compose up -d. Soporta compose files adicionales. Retorna JSON con status, containers corriendo y duracion. |
| `dockerize_app_bash_pipelines` | `dockerize_app(app_name: string, [--domain DOMAIN], [--port PORT], [--ssh-host HOST], [--remote-dir DIR], [--basic-auth USER:PASS], [--no-auth], [--no-gzip], [--env KEY=VAL]..., [--volume NAME], [--build-cmd CMD], [--standalone], [--dry-run]) -> json` | Empaqueta una app Go del registry para deploy a VPS organic-machine via Docker + Traefik + Coolify. Genera Dockerfile multi-stage, docker-compose.yml, traefik-dynamic.yml con basicAuth opcional y gzip, sube via rsync al VPS y arranca el stack remoto. Replica el patron de apps/registry_api/. |
| `generate_compose_traefik_go_infra` | `func GenerateComposeTraefik(cfg ComposeTraefikConfig) string` | Genera el texto YAML de un docker-compose.yml para una app Go desplegada behind Traefik + Coolify. Replica el patron de apps/registry_api/docker-compose.yml. Determinista: orden de EnvVars sigue el orden de entrada. |
| `generate_dockerfile_go_infra` | `func GenerateDockerfile(binaryName string, port int, envVars map[string]string) string` | Genera el texto de un Dockerfile multi-stage para una app Go. Stage build con golang:1.23-alpine, stage final con alpine:latest. Incluye ENV vars del map con orden determinista. Funcion pura sin I/O. |
| `generate_traefik_dynamic_go_infra` | `func GenerateTraefikDynamic(cfg TraefikDynamicConfig) string` | Genera el texto YAML de un traefik-dynamic.yml para el file provider de Traefik (Coolify). Replica el patron de apps/registry_api/traefik-dynamic.yml con routers HTTP/HTTPS, redirect, basicAuth opcional y gzip opcional. |
| `gitea_create_webhook_bash_infra` | `gitea_create_webhook(owner: string, repo: string, target_url: string, secret?: string) -> json` | Crea un webhook de push en un repositorio Gitea. El webhook notifica a target_url en cada push. |
| `go_build_binary_go_infra` | `func GoBuildBinary(projectDir, outputPath string, ldflags string, tags string) error` | Compila un binario Go desde un directorio de proyecto. Si ldflags está vacío usa -s -w (strip debug). Si outputPath está vacío usa build/{dirname} dentro del projectDir. Ejecuta con CGO_ENABLED=0. |
| `rsync_deploy_bash_infra` | `rsync_deploy(local_dir: string, ssh_alias: string, remote_dir: string) -> json` | Sincroniza un directorio local a un host remoto via rsync+SSH. Excluye archivos de desarrollo y bases de datos locales. Crea el directorio remoto si no existe. |
| `setup_registry_api_bash_infra` | `setup_registry_api(ssh_host: string, api_token: string, basic_auth_user: string, basic_auth_pass: string) -> json` | Deploy completo de registry_api en VPS con Docker + Traefik (Coolify proxy). Sincroniza el repo via rsync, genera el hash bcrypt para basicAuth, sube el traefik-dynamic.yml, crea el .env con el token, hace docker compose build+up y verifica el health check. |
| `setup_vps_app_go_infra` | `func SetupVPSApp(conn SSHConn, cfg DeployConfig) error` | Orquesta el setup inicial de una app en un VPS remoto: verifica SSH, crea dirs y usuario, sube binario, instala systemd unit y hace health check. |
| `stop_app_go_infra` | `func StopApp(containerName string, removeImage bool) error` | Para y elimina el contenedor de una app desplegada. Si removeImage es true elimina también la imagen Docker. containerName debe coincidir con el imageName usado en deploy_app. |
| `systemd_generate_unit_go_infra` | `func SystemdGenerateUnit(name, execStart, workDir, user string, env map[string]string) string` | Genera el texto de un archivo .service de systemd para una app. Incluye restart automático y env vars en orden determinista. |
| `systemd_install_go_infra` | `func SystemdInstall(conn SSHConn, unitName, unitContent string) error` | Sube un unit file al host remoto, hace daemon-reload, enable y restart. Idempotente: reemplaza si el unit ya existe. |
| `vps_setup_app_go_infra` | `func VPSSetupApp(conn SSHConn, appName, remoteDir, serviceUser string) error` | Prepara un host remoto para recibir una app: crea directorios, usuario de servicio y asigna ownership. |
| `wait_for_http_bash_infra` | `wait_for_http <url> [timeout_seconds] [interval_seconds]` | 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. |
| `write_dockerfile_go_infra` | `func WriteDockerfile(dir, content string) (string, error)` | Escribe content en dir/Dockerfile. Crea el directorio si no existe. Retorna el path absoluto del archivo escrito. Compañera impura de generate_dockerfile. |
## Ejemplo canonico
### App Go a VPS con Docker+Traefik
```bash
./fn run dockerize_app my_api \
--domain my_api.organic-machine.com \
--port 8080 \
--ssh-host organic-machine \
--remote-dir /srv/coolify/my_api \
--basic-auth admin:supersecret
```
Empaqueta `apps/my_api/` (Go), genera Dockerfile multistage + docker-compose + traefik-dynamic con basicAuth, sube via rsync, levanta el stack remoto.
### App Go a VPS con systemd (sin Docker)
```bash
# Setup inicial
./fn run setup_vps_app --host organic-machine --app my_api --port 8080
# Deploy continuo (build local + rsync + restart systemd + health check)
./fn run deploy_app_remote --host organic-machine --app my_api
```
### App C++ a Windows Desktop desde WSL2
```bash
./fn run compile_cpp_app registry_dashboard
# Cross-compila con mingw-w64 + deploya .exe + DLLs + assets a /mnt/c/Users/lucas/Desktop/apps/registry_dashboard/
```
## Fronteras
- **NO orquesta CI/CD pipelines**. Si necesitas pipelines completas (GitHub Actions, Gitea Actions), construye yaml a mano.
- **NO maneja secretos rotativos**. Pasa secretos via env vars; rotacion es manual.
- **NO hace blue-green ni canary**. Cada deploy es un swap directo. Si necesitas zero-downtime sofisticado, usa Traefik con multiple instancias y label-based routing manual.
- **NO incluye monitoring post-deploy**. Health check inicial sí; observabilidad continua es responsabilidad del operador (ver grupo `registry` para auditoria).
+32 -3
View File
@@ -1,6 +1,6 @@
# Capability: docker
_(Descripcion del grupo — editar a mano)_
Operar Docker desde Go y Bash. Cubre: build (`docker_build_image`), run/stop/rm (`docker_run`, `docker_stop_container`, `docker_rm_container`), Docker Compose (`docker_compose_up`, `down`, `restart`, `ps`, remoto via SSH), inspect/logs/exec, networks, volumes, registry push/pull. Pipelines de deploy combinan estas atomicas: `deploy_app`, `dockerize_app`, `docker_compose_remote_deploy`.
## Funciones
@@ -48,8 +48,37 @@ _(Descripcion del grupo — editar a mano)_
## Ejemplo canonico
_(Anadir 1-2 bloques de codigo end-to-end)_
### Build + run local
```bash
./fn run docker_build_image --path . --tag myapp:latest
./fn run docker_run --image myapp:latest --name myapp --port 8080:8080 --detach
./fn run docker_inspect_container --name myapp | jq .
./fn run docker_container_logs --name myapp --tail 100
```
### Docker Compose local
```bash
./fn run docker_compose_up --file docker-compose.yml --detach
./fn run docker_compose_ps
./fn run docker_compose_restart --service api
./fn run docker_compose_down
```
### Deploy a VPS con Compose + git pull
```bash
./fn run docker_compose_remote_deploy \
--host organic-machine \
--remote-dir /srv/coolify/myapp \
--branch master \
--compose-files "docker-compose.yml docker-compose.prod.yml"
```
## Fronteras
_(Que NO cubre este grupo)_
- **NO maneja Kubernetes ni Swarm**. Solo Docker engine + Compose.
- **NO instala Docker**. Asume `docker` y `docker compose` ya disponibles en PATH (local + remoto).
- **NO genera Dockerfiles a partir de codigo**. Solo opera contenedores y composes. Generacion: `generate_dockerfile_go_infra` del grupo `deploy`.
- **NO maneja secretos Docker Swarm**. Pasa secrets via env vars o files montados via `--volume`.
+67
View File
@@ -0,0 +1,67 @@
# Extractor — Funciones que leen datos de fuentes externas
Tag: `extractor`. Grupo de funciones que **leen datos crudos** de fuentes externas (DB, API, archivos, web) y los devuelven a la aplicacion. Son el primer nodo del flujo de datos en `data_factory` (issue 0097, analogia Factorio = drills).
Filtro MCP: `mcp__registry__fn_search query="" tag="extractor"`.
## Funciones del grupo
| ID | Lang | Fuente |
|---|---|---|
| [bq_query_py_infra](../../python/functions/infra/bq_query.md) | py | BigQuery SQL |
| [bq_preview_rows_py_infra](../../python/functions/infra/bq_preview_rows.md) | py | BigQuery table preview |
| [bq_get_table_py_infra](../../python/functions/infra/bq_get_table.md) | py | BigQuery metadata |
| [bq_list_tables_py_infra](../../python/functions/infra/bq_list_tables.md) | py | BigQuery catalog |
| [metabase_execute_card_py_infra](../../python/functions/infra/metabase_execute_card.md) | py | Metabase card |
| [metabase_execute_query_py_infra](../../python/functions/infra/metabase_execute_query.md) | py | Metabase ad-hoc |
| [load_csv_go_datascience](../../functions/datascience/load_csv.md) | go | CSV file |
| [load_parquet_go_datascience](../../functions/datascience/load_parquet.md) | go | Parquet file |
| [from_csv_py_core](../../python/functions/core/from_csv.md) | py | CSV file |
| [fetch_data_frame_go_datascience](../../functions/datascience/fetch_data_frame.md) | go | Generic dataframe loader |
| [http_get_json_py_infra](../../python/functions/infra/http_get_json.md) | py | HTTP JSON GET |
| [http_get_json_go_infra](../../functions/infra/http_get_json.md) | go | HTTP JSON GET |
| [http_download_file_py_infra](../../python/functions/infra/http_download_file.md) | py | HTTP file download |
| [http_download_file_go_infra](../../functions/infra/http_download_file.md) | go | HTTP file download |
| [jupyter_read_py_notebook](../../python/functions/notebook/jupyter_read.md) | py | Jupyter notebook cells |
## Ejemplo canonico
Extractor BigQuery -> dataframe local.
```python
from infra import bq_query
# 1. Extraer
df = bq_query(
project_id="my-gcp-project",
query="SELECT * FROM `dataset.events` WHERE event_date = CURRENT_DATE() LIMIT 10000",
)
print(f"extracted {len(df)} rows, {df.memory_usage(deep=True).sum() / 1024:.1f} KB")
```
## Fronteras del grupo
NO cubre:
- **Transform** (clean / dedup / aggregate) -> [[transformer]].
- **Sink** (escritura a destino externo) -> [[sink]].
- **Validacion** del dato (range / null / drift) -> [[validator]].
- Extraccion de funciones desde repos externos (eso es `sources/`, no este grupo).
- Streaming continuo (Kafka consumer, etc.) — los extractores son fetch puntual o batch.
## Cuando NO usar `extractor`
- Si la funcion lee de la BD interna del registry (`registry.db`, `operations.db`) -> tag `registry` o `doctor`, no `extractor`. Extractor implica fuente externa.
- Si solo parsea bytes ya cargados en memoria -> es `transformer`.
## Consumidores
- `data_factory` C++ ImGui app — tab Extractors lista el grupo entero y permite Run Now por nodo.
- `dag_engine` — un DAG step puede llamar a un extractor via `function: <id>`.
- Pipelines ad-hoc en notebooks.
## Notas
- BQ extractors estan tambien tageados `bigquery` (otro grupo); Metabase extractors tageados `metabase`. Una funcion puede pertenecer a multiples grupos.
- HTTP extractors duplicados Go/Py — eleccion segun stack del consumidor.
- Crece a medida que el registry incorpora nuevas fuentes (Mongo, Kafka, S3...). Mantener tag al frontmatter al crear funcion nueva.
+112
View File
@@ -0,0 +1,112 @@
# Capability: mantine
Componentes y helpers Mantine v9 + `@fn_library` (alias a `frontend/functions/ui/`). Cubre: inputs (TextInput, NumberInput, Select, MultiSelect, ColorPicker), layout (Group, Stack, AppShell, Grid), feedback (Modal, Drawer, Notification, Loader), data display (Table, Card, Badge), navegacion (Tabs, Accordion, Menu), instalacion+theming (`install_mantine`, `frontend_doctor`). Tambien componentes ImGui equivalentes en `@fn_library` C++ (cpp/core).
## Funciones
| ID | Firma | Que hace |
|---|---|---|
| `accordion_ts_ui` | `Accordion(props: AccordionProps): JSX.Element` | Secciones colapsables con animaciones. Mantine Accordion. Composable: AccordionItem + AccordionTrigger + AccordionContent. |
| `action_icon_ts_ui` | `FnActionIcon(props: FnActionIconProps): JSX.Element` | Boton de icono con variantes, loading y tooltip opcional. Wrapper sobre Mantine ActionIcon. |
| `alert_ts_ui` | `Alert(props: { variant?: 'default' \| 'destructive' \| 'success' \| 'warning' \| 'info' }): JSX.Element` | Alerta accesible con 5 variantes semánticas (default, destructive, success, warning, info). Mantine Alert con slots para título, descripción y acción. |
| `app_shell_ts_ui` | `FnAppShell(props: FnAppShellProps): JSX.Element` | Layout shell con header, navbar colapsable y area principal. Wrapper sobre Mantine AppShell. |
| `area_chart_ts_ui` | `AreaChart(props: AreaChartProps): JSX.Element` | Gráfico de área @mantine/charts con gradientes automáticos, multi-series, stacking y tooltips. |
| `auth_form_ts_ui` | `AuthForm(config: AuthFormConfig): ReactElement` | Genera página de autenticación con toggle login/register, social buttons opcionales, campos extra en registro y validación. Basado en Mantine AuthenticationForm. |
| `autocomplete_ts_ui` | `Autocomplete(props: AutocompleteProps): JSX.Element` | Input con sugerencias de autocompletado. Permite valores libres a diferencia de Select. Wrapper sobre Mantine Autocomplete. |
| `avatar_ts_ui` | `Avatar(props: AvatarProps): JSX.Element` | Imagen de usuario circular con fallback a iniciales generadas automaticamente. 5 tamaños via Mantine Avatar. |
| `badge_ts_ui` | `Badge(props: BadgeProps & VariantProps<typeof badgeVariants>): JSX.Element` | Badge con 10 variantes semánticas (default, secondary, destructive, outline, ghost, link, success, warning, error, info) y 2 tamaños. Mantine Badge. |
| `bar_chart_ts_ui` | `BarChart(props: BarChartProps): JSX.Element` | Gráfico de barras @mantine/charts con multi-series, orientación horizontal/vertical y tooltips. |
| `breadcrumb_ts_ui` | `Breadcrumb(props: BreadcrumbProps): JSX.Element` | Navegacion jerarquica con separadores, elipsis para paths largos y soporte para router links via asChild. Mantine Anchor/Text. |
| `button_ts_ui` | `Button(props: ButtonProps & VariantProps<typeof buttonVariants>): JSX.Element` | Botón accesible con 6 variantes (default, outline, secondary, ghost, destructive, link) y 8 tamaños. Mantine Button. |
| `chart_container_ts_ui` | `ChartContainer(props: { children: ReactNode; height?: number \| string }): JSX.Element` | Thin wrapper Paper y utilidades de colores/series para los charts @mantine/charts. |
| `checkbox_ts_ui` | `Checkbox(props: CheckboxProps): JSX.Element` | Input booleano accesible con label opcional y variante indeterminate. Mantine Checkbox. |
| `chip_ts_ui` | `Chip(props: ChipProps): JSX.Element` | Chip seleccionable con variantes filled/outline/light. ChipGroup para selección simple o múltiple. Wrapper sobre Mantine Chip. |
| `color_bg_ts_ui` | `colorBg(color: string): string` | Genera el valor CSS color-mix para el fondo de un elemento con color Mantine. Sin color retorna dark-6. Hex: mezcla 18% con dark-6. Token Mantine (blue, red, ...): mezcla tono -9 al 18% con dark-6. |
| `color_border_ts_ui` | `colorBorder(color: string): string` | Genera el valor CSS color-mix para el borde de un elemento con color Mantine. Sin color retorna dark-4. Hex: mezcla 30% con dark-4. Token Mantine: mezcla tono -7 al 30% con dark-4. |
| `color_input_ts_ui` | `ColorInput(props: ColorInputProps): JSX.Element` | Selector de color con picker, swatches predefinidos y eye dropper. Soporta hex, rgb, hsl con alpha. Wrapper sobre Mantine ColorInput. |
| `color_picker_grid_ts_ui` | `ColorPickerGrid(props: ColorPickerGridProps): JSX.Element` | Grid de swatches de color con boton extra que abre un modal con ColorPicker de Mantine para seleccionar un hexadecimal libre. Soporta tokens Mantine y valores hex. El swatch activo recibe borde blanco + box-shadow azul; el custom-active aplica el mismo feedback visual. |
| `color_swatch_ts_ui` | `colorSwatch(color: string): string` | Genera el valor CSS para mostrar un swatch (muestra de color) Mantine. Sin color retorna dark-3. Hex: retorna el hex directamente. Token Mantine: retorna CSS var del tono -7. |
| `date_picker_input_ts_ui` | `DatePickerInput(props: DatePickerInputProps): JSX.Element` | Selector de fecha con input y calendario desplegable. Soporta fecha simple, múltiple y rango. Wrapper sobre Mantine DatePickerInput. |
| `dialog_ts_ui` | `Dialog(props: DialogRootProps): JSX.Element` | Diálogo modal accesible con close button y sistema de slots (header, footer, title, description). Mantine Modal. |
| `dropdown_menu_ts_ui` | `DropdownMenu(props: DropdownMenuProps): JSX.Element` | Menu de acciones y contexto accesible con items, checkboxes, radios, separadores y submenus. Base-UI Menu primitive. |
| `dropzone_ts_ui` | `Dropzone(props: DropzoneProps): JSX.Element` | Zona de drag-and-drop para archivos con estados idle/accept/reject, límite de tamaño y tipos MIME. Wrapper sobre Mantine Dropzone. |
| `empty_state_ts_ui` | `EmptyState(props: EmptyStateProps): JSX.Element` | Placeholder para listas y tablas vacías con icono, título, descripción y acción opcional. Tabler Icons por defecto. |
| `error_page_ts_ui` | `ErrorPage(config: ErrorPageConfig): JSX.Element` | Genera página de error con código grande, título, descripción y acciones. Soporta 404, 500, 403 y cualquier código custom. |
| `file_input_ts_ui` | `FileInput(props: FileInputProps): JSX.Element` | Input de archivos con soporte para múltiples archivos, tipos aceptados y botón de limpiar. Wrapper sobre Mantine FileInput. |
| `frontend_doctor_bash_infra` | `frontend_doctor(project_dir: string) -> diagnostics_stdout` | 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. |
| `indicator_ts_ui` | `FnIndicator(props: FnIndicatorProps): JSX.Element` | Badge indicador posicionado sobre un elemento hijo. Wrapper sobre Mantine Indicator. |
| `input_ts_ui` | `Input(props: InputHTMLAttributes): JSX.Element` | Campo de entrada accesible con soporte para iconos, grupos, validación ARIA y estados disabled/invalid. Mantine TextInput. |
| `install_mantine_bash_infra` | `install_mantine(project_dir: string) -> void` | 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. |
| `label_ts_ui` | `Label(props: LabelHTMLAttributes): JSX.Element` | Etiqueta de formulario accesible con soporte para estados disabled. Mantine Text con component=label. |
| `line_chart_ts_ui` | `LineChart(props: LineChartProps): JSX.Element` | Gráfico de líneas @mantine/charts con multi-series, 5 tipos de curva, líneas de referencia y tooltips. |
| `loading_overlay_ts_ui` | `FnLoadingOverlay(props: FnLoadingOverlayProps): JSX.Element` | Overlay de carga con blur y opacidad configurable. Wrapper sobre Mantine LoadingOverlay. |
| `mantine_provider_ts_ui` | `FnMantineProvider({ children, theme?, defaultColorScheme? })` | Provider raiz de Mantine para apps del registry. Wrappea MantineProvider con Notifications incluido. Importa los CSS de @mantine/core, charts y notifications. |
| `month_heatmap_ts_ui` | `MonthHeatmap(props: MonthHeatmapProps): JSX.Element` | Grid mensual de calor (heatmap) con inicio en lunes. Renderiza 7 columnas con header de dias de semana y celdas que muestran numero del dia, hasta dos contadores con icono, borde azul para hoy y fondo tintado segun valores primary/secondary. |
| `multi_select_ts_ui` | `MultiSelect(props: MultiSelectProps): JSX.Element` | Selector múltiple con búsqueda, pills y límite de selecciones. Wrapper sobre Mantine MultiSelect. |
| `nav_link_ts_ui` | `FnNavLink(props: FnNavLinkProps): JSX.Element` | Link de navegacion con icono, descripcion y anidamiento. Wrapper sobre Mantine NavLink. |
| `number_input_ts_ui` | `FnNumberInput(props: FnNumberInputProps): JSX.Element` | Input numerico con min/max, step, prefijo y sufijo. Wrapper sobre Mantine NumberInput. |
| `pagination_ts_ui` | `Pagination(props: PaginationProps): JSX.Element` | Controles de navegacion de paginas autocontenido. Mantine Pagination. |
| `password_input_ts_ui` | `PasswordInput(props: PasswordInputProps): JSX.Element` | Input de contraseña con toggle de visibilidad y soporte para indicador de fortaleza. Wrapper sobre Mantine PasswordInput. |
| `pie_chart_ts_ui` | `PieChart(props: PieChartProps): JSX.Element` | Gráfico de torta/dona @mantine/charts con colores automáticos, labels y tooltip. Usa DonutChart para dona, PieChart para torta. |
| `pin_input_ts_ui` | `PinInput(props: PinInputProps): JSX.Element` | Input de código PIN/OTP con campos individuales y autocompletado. Wrapper sobre Mantine PinInput. |
| `popover_ts_ui` | `Popover(props: PopoverProps): JSX.Element` | Contenido flotante posicionado accesible con animaciones. Mantine Popover. |
| `progress_bar_ts_ui` | `ProgressBar(props: ProgressBarProps): JSX.Element` | Barra de progreso con variantes de color y tamaño, buffer, animación, modo indeterminado y display de valor. Mantine Progress. |
| `radio_group_ts_ui` | `RadioGroup(props: RadioGroupProps): JSX.Element` | Grupo de opciones exclusivas accesible. Mantine Radio.Group + Radio. |
| `rating_ts_ui` | `Rating(props: RatingProps): JSX.Element` | Selector de calificación con estrellas, fracciones y símbolos custom. Wrapper sobre Mantine Rating. |
| `ring_progress_ts_ui` | `FnRingProgress(props: FnRingProgressProps): JSX.Element` | Anillo de progreso con secciones coloreadas y label central. Wrapper sobre Mantine RingProgress. |
| `segmented_control_ts_ui` | `FnSegmentedControl(props: FnSegmentedControlProps): JSX.Element` | Control segmentado para seleccion unica entre opciones. Wrapper sobre Mantine SegmentedControl. |
| `select_ts_ui` | `Select(props: SelectProps): JSX.Element` | Select dropdown con búsqueda, grupos y accesibilidad. Wrapper sobre Mantine Select con API declarativa via prop data. |
| `sheet_ts_ui` | `Sheet(props: SheetProps): JSX.Element` | Panel lateral deslizante (drawer) accesible con variantes de lado y animaciones. Mantine Drawer. |
| `skeleton_ts_ui` | `Skeleton(props: HTMLAttributes<HTMLDivElement>): JSX.Element` | Sistema de loading skeletons: base, text, card, avatar, button, table. Variantes preconfiguradas para estados de carga. Mantine Skeleton. |
| `slider_cpp_core` | `bool slider_float(const char* label, float* v, float min, float max, const char* fmt); bool slider_float_log(...); bool slider_int(const char* label, int* v, int min, int max, const char* fmt); bool slider_double(const char* label, double* v, double min, double max, const char* fmt)` | Slider ImGui con label muted arriba, estilo acorde con fn_tokens (radius, border, primary grab). Variantes float, float_log (logaritmico), int, double. Equivalente al <Slider> de Mantine / fn_library. |
| `slider_ts_ui` | `Slider(props: SliderProps): JSX.Element \| RangeSlider(props: RangeSliderProps): JSX.Element` | Deslizador de valor numérico con marcas, labels y modo rango. Incluye RangeSlider. Wrapper sobre Mantine Slider. |
| `stepper_ts_ui` | `FnStepper(props: FnStepperProps): JSX.Element` | Stepper de pasos con orientacion horizontal/vertical. Wrapper sobre Mantine Stepper. |
| `sticker_picker_ts_ui` | `StickerPicker(props: StickerPickerProps): JSX.Element` | Selector de emoji/sticker encapsulado en un Popover de Mantine. Monta emoji-mart Picker una sola vez para evitar re-creaciones en cada render. |
| `switch_toggle_ts_ui` | `SwitchToggle(props: SwitchToggleProps): JSX.Element` | Toggle on/off accesible con label opcional a izquierda o derecha. Mantine Switch. |
| `tabs_ts_ui` | `Tabs(props: TabsRootProps): JSX.Element` | Sistema de tabs con orientacion horizontal/vertical, variantes default y line. Mantine Tabs. |
| `tags_input_ts_ui` | `TagsInput(props: TagsInputProps): JSX.Element` | Input de tags libre con sugerencias opcionales. Permite crear valores custom a diferencia de MultiSelect. Wrapper sobre Mantine TagsInput. |
| `textarea_ts_ui` | `Textarea(props: TextareaProps): JSX.Element` | Input multilinea accesible con auto-resize opcional. Mantine Textarea con autosize. |
| `timeline_ts_ui` | `FnTimeline(props: FnTimelineProps): JSX.Element` | Timeline vertical con items, iconos y colores. Wrapper sobre Mantine Timeline. |
| `toast_ts_ui` | `Toast(props: ToastProps): JSX.Element` | Notificaciones temporales con variantes semanticas (success, error, warning, info), iconos automaticos, auto-dismiss y provider con hook useToast. |
| `tooltip_ts_ui` | `Tooltip(props: TooltipRootProps): JSX.Element` | Tooltip accesible con posicionamiento automático. Mantine Tooltip con delay configurable. |
## Ejemplo canonico
### Frontend nuevo desde cero
```bash
./fn run install_mantine --target frontend
./fn doctor # verifica que pnpm + node + tsx esten OK (grupo registry)
cd frontend && pnpm dev
```
### Pagina con @fn_library
```tsx
import { FnMantineProvider, FnButton, FnTextInput, FnModal } from "@fn_library";
import { Stack, Group } from "@mantine/core";
import { IconHome } from "@tabler/icons-react";
export function App() {
return (
<FnMantineProvider>
<Stack p="md">
<Group>
<IconHome />
<FnTextInput label="Nombre" placeholder="..." />
</Group>
<FnButton variant="filled" leftSection={<IconHome />}>Submit</FnButton>
</Stack>
</FnMantineProvider>
);
}
```
## Fronteras
- **NO usa Tailwind, CVA, cn(), CSS variables custom**. Solo Mantine theme system + props.
- **NO usa lucide-react ni heroicons**. Solo `@tabler/icons-react` (set nativo de Mantine).
- **NO crea componentes HTML nativos cuando hay equivalente en @fn_library**. Antes de `<button>`, usa `FnButton`. Antes de `<input>`, usa `FnTextInput`.
- **NO usa MUI, Chakra, Ant Design u otro framework**. Stack es Mantine v9 + @fn_library exclusivamente.
- Componentes ImGui (`button_cpp_core`, `modal_dialog_cpp_core`, ...) viven en `@fn_library` C++ y son el equivalente para apps nativas; comparten naming pero **son codigo separado**, no convierten entre sí.
+39 -3
View File
@@ -1,6 +1,6 @@
# Capability: metabase
_(Descripcion del grupo — editar a mano)_
Operar Metabase 100% via API REST. Cubre: auth (`metabase_auth`), CRUD de cards/dashboards/collections/snippets/permissions/databases, ejecucion de queries (`metabase_execute_card`, `metabase_query`), refresh metadata + result_metadata, listado y archivado, gestion de pulses, y composiciones (`init_metabase`, `setup_metabase_volume`). 106 funciones Go+Py. Cliente reutilizable: `MetabaseClient` (Go: `metabase_client_go_infra`; Py: `MetabaseClient_py_infra`).
## Funciones
@@ -116,8 +116,44 @@ _(Descripcion del grupo — editar a mano)_
## Ejemplo canonico
_(Anadir 1-2 bloques de codigo end-to-end)_
### Auth + leer un dashboard (Python)
```python
import os, sys
sys.path.insert(0, os.path.join(os.environ["FN_REGISTRY_ROOT"], "python", "functions"))
from metabase import MetabaseClient, metabase_get_dashboard, metabase_list_cards
client = MetabaseClient(
base_url=os.environ["METABASE_URL"],
username=os.environ["METABASE_USER"],
password=os.environ["METABASE_PASS"],
)
client.auth()
dash = metabase_get_dashboard(client, dashboard_id=42)
cards = metabase_list_cards(client, collection_id=dash["collection_id"])
```
### Crear card + dashboard + ejecutar (Go)
```bash
./fn run metabase_auth --base-url $METABASE_URL --user admin --pass $MB_PASS
./fn run metabase_create_card --name "Sales 30d" --sql "SELECT ..." --database-id 1
./fn run metabase_create_dashboard --name "Sales overview" --collection-id 5
./fn run metabase_execute_card --card-id 123 | jq .
```
### Setup local con Docker
```bash
./fn run setup_metabase_volume
./fn run init_metabase --project fn_registry
```
## Fronteras
_(Que NO cubre este grupo)_
- **NO instala Metabase**. El binario/Docker container debe existir. Solo cliente API + composiciones.
- **NO usa el cliente Java/Python oficial**. Llama API REST directa via HTTP — mas simple, sin deps pesadas.
- **NO escribe SQL**. La query la pasa el caller. Para construir SQL custom, usar funciones del grupo `sql`.
- **NO maneja Metabase Embedded JS SDK** (frontend embed). Solo backend/API.
- Wrappear cada endpoint nuevo via `fn-constructor` con tag `metabase`. NO hacer `client._http.request(...)` raw.
+58
View File
@@ -0,0 +1,58 @@
# navegator — Automatización de browser via CDP + AX tree + LLM
Grupo de funciones para inspeccionar y automatizar Chrome/Chromium via el protocolo CDP (Chrome DevTools Protocol), procesar árboles de accesibilidad (AX tree) y orquestar llamadas a LLMs desde scripts Python.
> Nota: Este grupo crece con issue 0098. Las funciones actuales cubren obtención y procesamiento del AX tree. Las siguientes iteraciones añadirán interacción (click, type, navigate) y pipelines completos de agente web.
## Funciones
| ID | Firma corta | Qué hace |
|---|---|---|
| `claude_cli_prompt_py_infra` | `claude_cli_prompt(prompt, timeout_s, model, max_chars_response, extra_args) -> str` | Invoca `claude -p` via subprocess y devuelve la respuesta como string |
| `trim_ax_tree_py_core` | `trim_ax_tree(nodes) -> list[dict]` | Compacta AXNode CDP descartando nodos irrelevantes (ignored, generic vacíos, StaticText vacíos) |
| `chunk_ax_tree_py_core` | `chunk_ax_tree(nodes, max_chars) -> list[list[dict]]` | Divide AX tree en chunks con path-from-root como contexto para LLMs |
| `cdp_get_ax_tree_py_pipelines` | `cdp_get_ax_tree(debug_port, tab_id, depth) -> list[dict]` | Obtiene AX tree completo de un tab Chrome via CDP WebSocket |
## Ejemplo canónico end-to-end
```python
import json, urllib.request
from pipelines.cdp_get_ax_tree import cdp_get_ax_tree
from core.trim_ax_tree import trim_ax_tree
from core.chunk_ax_tree import chunk_ax_tree
from infra.claude_cli_prompt import claude_cli_prompt
# 1. Chrome debe correr con --remote-debugging-port=9222
# Listar tabs
with urllib.request.urlopen("http://127.0.0.1:9222/json/list") as r:
tabs = json.loads(r.read())
tab_id = tabs[0]["id"]
# 2. Obtener, limpiar y chunkear el AX tree
nodes = cdp_get_ax_tree(debug_port=9222, tab_id=tab_id)
trimmed = trim_ax_tree(nodes)
chunks = chunk_ax_tree(trimmed, max_chars=25000)
# 3. Procesar cada chunk con Claude
for i, chunk in enumerate(chunks):
chunk_json = json.dumps(chunk, ensure_ascii=False)
prompt = f"Extrae todos los botones y enlaces interactivos de este AX tree:\n{chunk_json}"
respuesta = claude_cli_prompt(prompt, timeout_s=90)
print(f"--- Chunk {i} ---")
print(respuesta)
```
## Fronteras
Este grupo NO cubre:
- Interacción directa con el DOM via CDP (clicks, typing, navegación) — ver funciones `cdp_*_go_browser` en Go.
- Automatización Playwright — ver grupo `playwright`.
- Captura de screenshots, HAR recording — ver `cdp_screenshot_go_browser`, `cdp_har_record_go_browser`.
- Análisis NLP de texto extraído — ver grupo `nlp`.
## Prerequisitos
- Chrome/Chromium lanzado con `--remote-debugging-port=<port>`.
- En WSL2: usar `chrome.exe` Windows con `--remote-debugging-address=0.0.0.0`.
- `websocket-client` en el venv Python (`pip install websocket-client`).
- `claude` CLI instalado y autenticado para `claude_cli_prompt`.
+32 -3
View File
@@ -1,6 +1,6 @@
# Capability: nlp
_(Descripcion del grupo — editar a mano)_
Pipeline NLP para extraccion de entities/relations sobre documentos en castellano (proyecto OSINT principalmente). Cubre: lectura de PDFs (`extract_pdf_text`, `clean_pdf_text`), OCR fallback, chunking (`chunk_with_overlap`), inferencia GLiNER (NER) + GLiREL (relation extraction), dedup (`dedup_entities`, `dedup_relations`), agregacion (`aggregate_extraction_results`), extraccion de elementos especificos (URLs, crypto wallets, IPs, dominios).
## Funciones
@@ -43,8 +43,37 @@ _(Descripcion del grupo — editar a mano)_
## Ejemplo canonico
_(Anadir 1-2 bloques de codigo end-to-end)_
### Pipeline completo PDF -> entities + relations
```python
import os, sys
sys.path.insert(0, os.path.join(os.environ["FN_REGISTRY_ROOT"], "python", "functions"))
from core import extract_pdf_text, clean_pdf_text, chunk_with_overlap
from datascience import (
gliner_extract_entities, glirel_extract_relations,
dedup_entities, dedup_relations, aggregate_extraction_results,
)
raw = extract_pdf_text("/path/to/doc.pdf")
text = clean_pdf_text(raw)
chunks = chunk_with_overlap(text, size=512, overlap=64)
entities = []
relations = []
for ch in chunks:
entities.extend(gliner_extract_entities(ch, labels=["PERSON","ORG","ACCOUNT"]))
relations.extend(glirel_extract_relations(ch, entity_pairs=entities))
result = aggregate_extraction_results(
entities=dedup_entities(entities),
relations=dedup_relations(relations),
)
```
## Fronteras
_(Que NO cubre este grupo)_
- **NO entrena modelos**. Solo inferencia con GLiNER/GLiREL pre-entrenados (HuggingFace).
- **NO maneja embeddings densos** (sentence-transformers / e5). Para vectores, usa funciones del grupo `ml`.
- **NO hace traduccion ni summarization LLM**. Solo NER + RE. Para LLM, ver tag `llm`.
- **NO escribe a BD** automaticamente. La persistencia (vault, sqlite, parquet) la maneja el caller via funciones de `infra`/`datascience`.
+62
View File
@@ -0,0 +1,62 @@
# Capability: registry
Auditoria y monitorizacion del propio registry (issue 0085/0086/0087). Cubre: detectar codigo copiado sin import (`audit_copied_code`), funciones huerfanas (`find_unused_functions`), drift entre imports y `uses_functions` declarado, drift `pc_locations` BD vs disco, drift de capability groups (`audit_capability_groups`), telemetria de llamadas (call_monitor `function_stats`), y generacion automatica de proposals (`generate_proposals_from_telemetry`).
## Funciones
| ID | Firma | Que hace |
|---|---|---|
| `audit_copied_code_go_infra` | `func AuditCopiedCode(registryRoot string) ([]CopiedCodeEntry, error)` | Audita apps en busca de cuerpos de funcion copiados del registry sin import. Calcula fingerprint normalizado (strip comments + whitespace) por funcion del registry y por funcion declarada en apps/, projects/*/apps/. Reporta matches exactos. MVP: exact_copy con sha256 truncado. Issue 0085k. |
| `audit_registry_paths_bash_pipelines` | `audit_registry_paths([output_file: string]) -> void` | Audita file_path de todas las functions y types en registry.db, verifica que cada ruta apunte a un archivo existente en disco, y genera un txt con las rutas rotas para que agentes puedan corregirlas. |
| `find_unused_functions_go_infra` | `func FindUnusedFunctions(registryRoot string) ([]UnusedFunction, error)` | Abre registry.db y retorna todas las funciones que no son referenciadas por ninguna otra funcion, app ni analisis. Util para detectar candidatas a deprecar o eliminar (fn doctor unused). |
| `full_git_pull_bash_pipelines` | `full_git_pull() -> stdout: tabla resumen` | 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. |
| `full_git_push_bash_pipelines` | `full_git_push(commit_message?: string) -> stdout: tabla resumen` | 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. |
| `generate_proposals_from_telemetry_go_infra` | `func GenerateProposalsFromTelemetry(registryRoot string) ([]ProposalDraft, error)` | Genera proposals automaticas para registry.db.proposals a partir de telemetria de call_monitor.operations.db. Aplica 4 reglas MVP: copy_detected, orphan, bug, wrapper_skip. IDs deterministas (sha1 truncado) garantizan idempotencia. Cierra la fase MEJORAR del bucle reactivo. Issue 0085h. |
| `parser_registry_py_core` | `class ParserRegistry: register(name: str, parser: BaseParser) -> None; unregister(name: str) -> None; get_parser(name: str) -> BaseParser \| None; get_parser_for_file(path: str) -> BaseParser \| None; async parse(source: str, **kwargs) -> ParseResult; list_parsers() -> list[str]; list_supported_extensions() -> list[str]` | Registry extensible que despacha parsing de archivos al parser correcto basado en extension. Patron plugin: registrar parsers por nombre y extensiones, resolver automaticamente. Mantiene estado mutable (mapa extension→parser). Singleton global disponible via get_registry(). |
| `proposal_from_failure_go_infra` | `func ProposalFromFailure(registryDB string, appID string, results []CheckResult, executionID string) ([]string, error)` | Crea una fila en la tabla proposals de registry.db por cada CheckResult con Status=fail. Usa kind=new_function para fallos criticos y kind=improve_function para warnings. Retorna los IDs de proposals creados. Parte del bucle reactivo: conecta los resultados de e2e_run_checks con la etapa MEJORAR. |
| `registry_telemetry_py_infra` | `install() -> bool` | Telemetria de invocaciones del registry desde Python. Patchea via sys.meta_path los paquetes del registry (core, finance, metabase, etc.) y registra cada llamada en call_monitor.operations.db. Activable con FN_TELEMETRY=1. Issue 0085c. |
| `setup_registry_api_bash_infra` | `setup_registry_api(ssh_host: string, api_token: string, basic_auth_user: string, basic_auth_pass: string) -> json` | Deploy completo de registry_api en VPS con Docker + Traefik (Coolify proxy). Sincroniza el repo via rsync, genera el hash bcrypt para basicAuth, sube el traefik-dynamic.yml, crea el .env con el token, hace docker compose build+up y verifica el health check. |
| `sql_workbench_cpp_core` | `void fn::sql_workbench(const char* id, sqlite3* db, fn::SqlWorkbenchState& state, ImVec2 size); bool fn::sql_workbench_run_query(sqlite3*, const char*, fn::SqlWorkbenchState&); void fn::sql_workbench_load_schema(sqlite3*, fn::SqlWorkbenchState&); void fn::sql_workbench_destroy(fn::SqlWorkbenchState&)` | Workbench SQL embebido en ImGui: editor con highlighting (text_editor + CodeLang::SQL), tabla de resultados (table_view), sidebar de schema (sqlite_master) e historial. Ejecuta queries contra una sqlite3* del caller (no abre/cierra la DB). |
| `tag_unused_pending_py_pipelines` | `python tag_unused_pending.py` | Etiqueta cada funcion del registry sin consumidor con el tag `pendiente-usar` (issue 0062). Lee `fn doctor unused --json`, modifica el frontmatter YAML del .md de cada funcion, y deja la indicacion para correr `fn index`. Idempotente: tambien retira el tag de funciones que han vuelto a usarse. |
| `telemetry_prelude_bash_infra` | `source telemetry_prelude.sh` | 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. |
| `validate_registry_paths_bash_shell` | `validate_registry_paths(db_path: string, table: string, root_dir: string) -> tsv_stdout` | Consulta registry.db y verifica que cada file_path apunte a un archivo existente en disco. Imprime a stdout las rutas rotas en formato TSV (id, file_path, domain, tabla). |
| `vault_aggregate_index_go_infra` | `func VaultAggregateIndex(repoRoot string) (AggregateReport, error)` | Agrega los índices de todos los vaults del registry en la tabla vault_files de registry.db. Lee cada vault_index.db (via VaultIndexOpen) y reemplaza las filas de forma atómica. Idempotente: re-ejecutar limpia y reescribe sin duplicar. |
| `write_analysis_md_bash_infra` | `write_analysis_md(analysis_dir: string, name: string, description: string, tags_csv: string) -> string` | Genera un archivo analysis.md con frontmatter valido para el registry. Calcula dir_path relativo a FN_REGISTRY_ROOT (o lo deduce buscando registry.db hacia arriba). Acepta tags como CSV. |
| `write_jupyter_registry_kernel_bash_infra` | `write_jupyter_registry_kernel([project_dir: string]) -> string` | Genera un script de startup de IPython que autoconfigura FN_REGISTRY_ROOT, sys.path a python/functions del registry, y helpers fn_query/fn_search/fn_code para consultar registry.db desde notebooks. |
## Ejemplo canonico
### Auditoria completa pre-merge
```bash
./fn doctor # todos los checks
./fn doctor uses-functions --json | jq .
./fn doctor unused --json | jq '.[] | select(.calls_90d == 0)'
./fn doctor copied-code --json | jq '.[] | select(.kind == "exact_copy")'
./fn doctor capabilities --json
```
### Telemetria del agente (issue 0085)
```bash
cd projects/fn_monitoring/apps/call_monitor
./call_monitor copied-code # popula tabla copied_code
./call_monitor snapshot # snapshot a function_versions
./call_monitor propose # crea proposals automaticas desde function_stats
```
### Detectar candidatos a capability group
```bash
./fn run propose_capability_groups --min-count 10 --max-domains 4
# Promociona uno: ./fn run propose_capability_groups --apply mantine
```
## Fronteras
- **NO modifica codigo del registry** automaticamente. Solo audita + abre proposals.
- **NO sincroniza** entre PCs. Para eso usar `fn sync` (grupo `git` cubre push/pull).
- **NO ejecuta tests**. Solo audita schema, hash, y telemetria.
- Funciones runtime de apps compiladas (Go/C++): quedan **fuera** de la telemetria salvo via `./fn run` o e2e_checks. Ver issue 0085 "Que se escapa del monitor".
+78
View File
@@ -0,0 +1,78 @@
# Scheduler — Cron expression parsing, matching, scheduling
Tag: `scheduler`. Grupo de funciones para trabajar con expresiones cron: parsear, validar coincidencias, calcular proximo run, traducir a lenguaje humano. Lo consume `apps/dag_engine` + `cpp/apps/dag_engine_ui` (tab Schedule). Crece a medida que se anaden nuevos formatos (Quartz, k8s CronJob, etc.).
Filtro MCP: `mcp__registry__fn_search query="" tag="scheduler"`.
## Funciones del grupo
| ID | Firma corta | Que hace |
|---|---|---|
| [parse_cron_expr_go_core](../../functions/core/parse_cron_expr.md) | `ParseCronExpr(expr string) (CronSchedule, error)` | Parsea expresion cron de 5 campos a struct expandido. Soporta `*`, rangos, listas, pasos y aliases `@hourly/@daily/@weekly/@monthly/@yearly`. |
| [cron_match_go_core](../../functions/core/cron_match.md) | `CronMatch(s CronSchedule, t time.Time) bool` | True si un instante coincide con un schedule. Compara los 5 campos. |
| [next_cron_time_go_core](../../functions/core/next_cron_time.md) | `NextCronTime(s CronSchedule, after time.Time) time.Time` | Proxima ejecucion despues de un instante. Salta minuto a minuto. Zero time si no hay match en 366 dias. |
| [cron_explain_go_core](../../functions/core/cron_explain.md) | `CronExplain(expr string) string` | Traduce expresion cron a frase humana corta (`"every 15 minutes"`, `"daily at 09:00"`, `"weekdays at HH:MM"`). |
## Ejemplo canonico end-to-end
Trabajar con un schedule completo: parsear, calcular proximo run, explicarlo al usuario.
```go
package main
import (
"fmt"
"time"
"fn-registry/functions/core"
)
func main() {
expr := "*/15 * * * *"
// 1. Parsear
sched, err := core.ParseCronExpr(expr)
if err != nil {
panic(err)
}
// 2. Proximo run
next := core.NextCronTime(sched, time.Now())
fmt.Printf("next run: %s\n", next.Format(time.RFC3339))
// 3. Explicacion humana
fmt.Printf("schedule: %s (%s)\n", expr, core.CronExplain(expr))
// -> schedule: */15 * * * * (every 15 minutes)
// 4. Verificacion puntual
if core.CronMatch(sched, time.Now()) {
fmt.Println("would trigger now")
}
}
```
## Fronteras del grupo
NO cubre:
- Persistencia de schedules (vive en BD de quien lo use: `apps/dag_engine/store/`).
- Spawn/wait de procesos (eso es `process_*_go_infra`).
- Sintaxis Quartz extendida con `?` o anos. Solo soporta los 5 campos estandar + aliases `@hourly/@daily/@weekly/@monthly/@yearly`.
- Timezones — todo se evalua en `time.Local` del proceso. Si necesitas TZ explicita, parsea fuera y traduce.
- Sintaxis especial de Kubernetes CronJob (segundos opcionales en el primer campo).
- DAG orchestration / DAG runs (consume estas funciones via `apps/dag_engine`).
## Prerequisitos
- Go stdlib. Sin dependencias externas en ninguna funcion del grupo.
- Tipo compartido `cron_schedule_go_core` (struct con `Minute/Hour/Day/Month/Weekday` como `[]int` expandidos).
## Notas
- `cron_match` esta tageada `pendiente-usar` — sin consumidores actuales aparte del propio paquete. Si se incorpora a `next_cron_time` como optimizacion -> quitar tag.
- `cron_explain` mantiene contrato simple: si el patron no encaja en los reconocidos, devuelve el expr crudo sin error. Apto para UI fallback.
- Para anadir nueva funcion al grupo: anadir tag `scheduler` al `tags:` del frontmatter, anadir fila a la tabla arriba, actualizar `N` en `docs/capabilities/INDEX.md`.
## Consumidores actuales
- `apps/dag_engine``parse_cron_expr_go_core`, `next_cron_time_go_core` (+ `cron_ticker_go_infra` adyacente).
- `cpp/apps/dag_engine_ui` — tab Schedule consume `/api/dags` expuesto por dag_engine; via backend usa estas funciones.
- Candidato futuro: `cron_explain_go_core` en `dag_engine` `/api/dags` (anadir campo `schedule_human` al JSON response) para que la UI no tenga que duplicar logica.
+81
View File
@@ -0,0 +1,81 @@
# Sink — Funciones que escriben datos a destinos externos
Tag: `sink`. Grupo de funciones que **escriben** datos a un destino externo: BD, archivo, dashboard, alerta, email. Input: datos. Output: efecto observable. Ultimo eslabon del flujo en `data_factory` (analogia Factorio = rocket silo).
Filtro MCP: `mcp__registry__fn_search query="" tag="sink"`.
## Funciones del grupo
| ID | Lang | Destino |
|---|---|---|
| [bq_insert_rows_py_infra](../../python/functions/infra/bq_insert_rows.md) | py | BigQuery streaming insert |
| [bq_load_from_file_py_infra](../../python/functions/infra/bq_load_from_file.md) | py | BigQuery batch load (file) |
| [bq_load_from_gcs_py_infra](../../python/functions/infra/bq_load_from_gcs.md) | py | BigQuery batch load (GCS) |
| [bq_export_to_gcs_py_infra](../../python/functions/infra/bq_export_to_gcs.md) | py | BigQuery -> GCS export |
| [metabase_create_card_py_infra](../../python/functions/infra/metabase_create_card.md) | py | Metabase saved question |
| [metabase_export_card_py_infra](../../python/functions/infra/metabase_export_card.md) | py | Metabase card export (CSV/Excel) |
| [metabase_create_dashboard_subscription_py_infra](../../python/functions/infra/metabase_create_dashboard_subscription.md) | py | Dashboard email subscription |
| [metabase_create_card_alert_py_infra](../../python/functions/infra/metabase_create_card_alert.md) | py | Card alert (threshold/schedule) |
| [http_post_json_py_infra](../../python/functions/infra/http_post_json.md) | py | HTTP JSON POST |
| [http_post_json_go_infra](../../functions/infra/http_post_json.md) | go | HTTP JSON POST |
| [db_insert_row_go_infra](../../functions/infra/db_insert_row.md) | go | SQL row insert |
## Ejemplo canonico
Persistir resultados a BigQuery + crear card Metabase + alerta.
```python
from infra import bq_insert_rows, metabase_create_card, metabase_create_card_alert
# 1. Insertar filas a BQ
bq_insert_rows(
project_id="my-gcp-project",
dataset_id="analytics",
table_id="daily_kpis",
rows=results,
)
# 2. Card en Metabase apuntando a esa tabla
card = metabase_create_card(
name="Daily KPIs",
sql="SELECT * FROM `my-gcp-project.analytics.daily_kpis` WHERE date >= CURRENT_DATE() - 7",
database_id=3,
)
# 3. Alerta si la metrica clave cae
metabase_create_card_alert(
card_id=card["id"],
threshold={"goal": 100, "above_goal": False},
schedule="hourly",
)
```
## Fronteras del grupo
NO cubre:
- **Extract** (leer de fuente externa) -> [[extractor]].
- **Transform** (modificar datos sin efectos externos) -> [[transformer]].
- Escritura a la BD interna del registry (`registry.db`, `operations.db`) — tag `registry` o equivalente.
- Deploy de codigo (eso es `deploy`).
- Logs / telemetria propia (van a `call_monitor.db`, no son sinks de pipeline).
## Cuando NO usar `sink`
- Si la funcion escribe pero los datos NO salen del proceso actual (cache en memoria) -> no es sink.
- Si la funcion devuelve algo al caller sin efecto externo -> es transformer.
## Side effects observables
Por definicion, todos los sinks son **impuros** y tienen `error_type` definido. La salida del flujo data_factory es siempre visible: una fila nueva en BD, un email enviado, un dashboard actualizado.
## Consumidores
- `data_factory` — tab Sinks.
- `dag_engine` DAG steps con `function: <sink_id>`.
- Pipelines de produccion (con secrets de credenciales fuera del registry).
## Notas
- BQ load/insert/export todos como sink — escriben a recurso externo.
- Metabase notifications (subscription + alert) son sinks "puros" en el sentido de que producen efectos sin cambiar datos.
- HTTP POST cuenta como sink solo cuando se usa para **enviar** datos (webhook, API target), no para "obtener via POST".
+58
View File
@@ -0,0 +1,58 @@
# Capability: ssh
Operar hosts remotos via SSH. Cubre: CRUD de `~/.ssh/config` (`ssh_config_add_entry`, `ssh_config_find`, `ssh_config_remove`), conexiones reutilizables (`SSHConn`), ejecucion remota (`ssh_run`, `ssh_check`), transferencia (`scp_put`, `scp_get`), port-forwarding, y orquestacion de deploys (setup_vps_app, deploy_app_remote).
## Funciones
| ID | Firma | Que hace |
|---|---|---|
| `audit_ssh_config_bash_cybersecurity` | `audit_ssh_config(config_path: string) -> void` | 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. |
| `docker_compose_remote_deploy_bash_infra` | `docker_compose_remote_deploy(host: string, remote_dir: string, branch: string, compose_files: string) -> json` | Despliega un stack Docker Compose en un host remoto via SSH. Verifica conectividad, hace git pull del branch indicado, actualiza imagenes con docker-compose pull y levanta/recrea los servicios modificados con docker-compose up -d. Soporta compose files adicionales. Retorna JSON con status, containers corriendo y duracion. |
| `rsync_deploy_bash_infra` | `rsync_deploy(local_dir: string, ssh_alias: string, remote_dir: string) -> json` | Sincroniza un directorio local a un host remoto via rsync+SSH. Excluye archivos de desarrollo y bases de datos locales. Crea el directorio remoto si no existe. |
| `setup_registry_api_bash_infra` | `setup_registry_api(ssh_host: string, api_token: string, basic_auth_user: string, basic_auth_pass: string) -> json` | Deploy completo de registry_api en VPS con Docker + Traefik (Coolify proxy). Sincroniza el repo via rsync, genera el hash bcrypt para basicAuth, sube el traefik-dynamic.yml, crea el .env con el token, hace docker compose build+up y verifica el health check. |
| `setup_vps_app_go_infra` | `func SetupVPSApp(conn SSHConn, cfg DeployConfig) error` | Orquesta el setup inicial de una app en un VPS remoto: verifica SSH, crea dirs y usuario, sube binario, instala systemd unit y hace health check. |
| `ssh_check_go_infra` | `func SSHCheck(conn SSHConn) error` | Verifica conectividad SSH ejecutando un comando noop en el host remoto. Timeout de 5 segundos. |
| `ssh_config_add_entry_go_infra` | `func SSHConfigAddEntry(entries []SSHConfigEntry, entry SSHConfigEntry) ([]SSHConfigEntry, error)` | Añade un nuevo SSHConfigEntry a la lista. Error si el alias ya existe. |
| `ssh_config_find_go_infra` | `func SSHConfigFind(entries []SSHConfigEntry, alias string) (SSHConfigEntry, bool)` | Busca un entry por alias en la lista de SSHConfigEntry. |
| `ssh_config_parse_go_infra` | `func SSHConfigParse(content string) []SSHConfigEntry` | Parsea el contenido de un archivo ~/.ssh/config y retorna una lista de SSHConfigEntry. |
| `ssh_config_read_go_infra` | `func SSHConfigRead() ([]SSHConfigEntry, error)` | Lee y parsea ~/.ssh/config. Retorna lista vacia si el archivo no existe. |
| `ssh_config_remove_entry_go_infra` | `func SSHConfigRemoveEntry(entries []SSHConfigEntry, alias string) ([]SSHConfigEntry, error)` | Elimina un entry por alias de la lista. Error si el alias no existe. |
| `ssh_config_render_go_infra` | `func SSHConfigRender(entries []SSHConfigEntry) string` | Convierte una lista de SSHConfigEntry al formato texto de ~/.ssh/config. |
| `ssh_config_write_go_infra` | `func SSHConfigWrite(entries []SSHConfigEntry) error` | Escribe entries a ~/.ssh/config con backup automatico del archivo previo. |
| `ssh_download_go_infra` | `func SSHDownload(conn SSHConn, remotePath, localPath string) error` | Descarga un archivo del host remoto al filesystem local via scp. |
| `ssh_exec_go_infra` | `func SSHExec(conn SSHConn, command string) (string, string, int, error)` | Ejecuta un comando en el host remoto via SSH. Retorna stdout, stderr y exit code separados. |
| `ssh_tunnel_close_go_infra` | `func SSHTunnelClose(pid int) error` | Cierra un tunel SSH enviando SIGTERM al proceso por PID. |
| `ssh_tunnel_open_go_infra` | `func SSHTunnelOpen(conn SSHConn, localPort int, remoteHost string, remotePort int) (int, error)` | Abre un tunel SSH (local port forwarding) en background. Retorna el PID del proceso para cerrarlo despues. |
| `ssh_upload_go_infra` | `func SSHUpload(conn SSHConn, localPath, remotePath string) error` | Sube un archivo local al host remoto via scp. |
| `validate_git_ssh_uri_py_core` | `def validate_git_ssh_uri(url: str) -> None` | Valida el formato de una URI SSH de git (git@host:path). Lanza ValueError si la URI es invalida. |
## Ejemplo canonico
### Anadir host nuevo + comprobar + ejecutar comando
```bash
./fn run ssh_config_add_entry \
--alias organic-machine \
--hostname 1.2.3.4 \
--user deploy \
--identity-file ~/.ssh/organic_ed25519
./fn run ssh_check --host organic-machine
./fn run ssh_run --host organic-machine --cmd "systemctl status nginx"
```
### Transferencia + deploy
```bash
./fn run scp_put --host organic-machine --src ./build/myapp --dst /opt/apps/myapp/myapp
./fn run systemd_restart --host organic-machine --unit myapp.service
./fn run wait_for_http https://myapp.example.com/health 30
```
## Fronteras
- **NO genera ni rota llaves SSH automaticamente**. Asume llave ya generada con `ssh-keygen`.
- **NO valida fingerprints de host**. Confianza inicial es responsabilidad del operador (acepta el prompt manualmente o usa `StrictHostKeyChecking=no` con conciencia del riesgo).
- **NO orquesta multi-hop / jump hosts** mas alla del ProxyJump del config. Para bastion compleja, edita `~/.ssh/config` a mano.
- **NO maneja credenciales con password**. Solo auth por llave. Tunneling password-based via sshpass queda fuera (ver `cybersecurity/audit_ssh_config` para auditar).
+68
View File
@@ -0,0 +1,68 @@
# Capability: systemd
Gestionar units systemd local y remoto via SSH. Cubre: generar texto de unit files (`systemd_generate_unit`, pura), instalar+enable+start (`systemd_install`, `install_systemd_service`), restart/status (`systemd_restart`, `systemd_status`, `services_status`), y auditoria de apps con tag `service`.
## Funciones
| ID | Firma | Que hace |
|---|---|---|
| `install_systemd_service_bash_pipelines` | `install_systemd_service --name <N> --exec <PATH> [opts] -> json` | 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/. |
| `services_status_go_infra` | `func ServicesStatus(registryRoot string) ([]ServiceStatus, error)` | Lista todas las apps registradas con tag 'service' y reporta su estado: unidad systemd activa, puerto escuchando, y pc_id local. |
| `setup_vps_app_go_infra` | `func SetupVPSApp(conn SSHConn, cfg DeployConfig) error` | Orquesta el setup inicial de una app en un VPS remoto: verifica SSH, crea dirs y usuario, sube binario, instala systemd unit y hace health check. |
| `systemd_generate_unit_go_infra` | `func SystemdGenerateUnit(name, execStart, workDir, user string, env map[string]string) string` | Genera el texto de un archivo .service de systemd para una app. Incluye restart automático y env vars en orden determinista. |
| `systemd_install_go_infra` | `func SystemdInstall(conn SSHConn, unitName, unitContent string) error` | Sube un unit file al host remoto, hace daemon-reload, enable y restart. Idempotente: reemplaza si el unit ya existe. |
| `systemd_local_enable_bash_infra` | `systemd_local_enable(name: string) -> json` | Habilita un servicio systemd local con systemctl enable para que arranque automáticamente al boot. Requiere sudo. |
| `systemd_local_install_unit_bash_infra` | `systemd_local_install_unit(name: string, unit_content: string) -> json` | Instala un unit file de systemd en /etc/systemd/system/<name>.service y ejecuta daemon-reload. Requiere sudo sin password para install y systemctl. Sobrescribe si el unit ya existe. |
| `systemd_local_restart_bash_infra` | `systemd_local_restart(name: string) -> json` | Reinicia un servicio systemd local con systemctl restart. Útil tras actualizar el binario o cambiar el unit. Requiere sudo. |
| `systemd_local_start_bash_infra` | `systemd_local_start(name: string) -> json` | Arranca un servicio systemd local con systemctl start. Devuelve el MainPID asignado. Requiere sudo. |
| `systemd_local_status_bash_infra` | `systemd_local_status(name: string, log_lines: int = 10) -> json` | Devuelve el estado de un servicio systemd local: active state, sub state, PID, enabled, y las N últimas líneas de journalctl. No requiere sudo. |
| `systemd_local_uninstall_bash_infra` | `systemd_local_uninstall(name: string) -> json` | 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. |
| `systemd_restart_go_infra` | `func SystemdRestart(conn SSHConn, unitName string) error` | Reinicia un servicio systemd en un host remoto via SSH. |
| `systemd_status_go_infra` | `func SystemdStatus(conn SSHConn, unitName string, logLines int) (SystemdServiceStatus, error)` | Consulta el estado de un servicio systemd en un host remoto. Retorna estado activo, sub-estado, PID y logs recientes. |
| `tail_journal_bash_infra` | `tail_journal(unit: string, lines: int=100, follow: bool=false, since: string="", priority: string="info") -> void` | Wrapper sobre journalctl con formato consistente. Tail logs de una unidad systemd con coloreado, filtro por prioridad y seguimiento opcional. |
## Ejemplo canonico
### Service local (requiere sudo)
```bash
./fn run install_systemd_service \
--name myapp \
--exec /opt/myapp/myapp \
--workdir /opt/myapp \
--user www-data \
--env "PORT=8080" \
--env "DB_PATH=/var/lib/myapp/db"
```
### Service remoto via SSH
```bash
# 1. Generar unit (pure, sin side effects)
unit=$(./fn run systemd_generate_unit \
--name myapp \
--exec-start "/opt/myapp/myapp" \
--work-dir /opt/myapp \
--user deploy \
--env "PORT=8080")
# 2. Instalar remoto
./fn run systemd_install --host organic-machine --unit-name myapp --content "$unit"
# 3. Status
./fn run systemd_status --host organic-machine --unit myapp.service
```
### Auditoria de todos los services
```bash
./fn doctor services --json | jq '.[] | {id, active, port_listening}'
```
## Fronteras
- **NO maneja timers ni paths units**. Solo service units. Para cron/timer usa Dagu o cron clasico.
- **NO genera socket activation**. Asume que la app abre su propio puerto.
- **NO instala unit a nivel usuario (`--user`)** salvo que el caller pase la flag al systemctl. Default es `/etc/systemd/system/`.
- Apps en `apps/` que se ejecutan local pero NO son service de larga duracion: NO necesitan systemd. Solo apps con `tags: [service]` en su `app.md`.
+95
View File
@@ -0,0 +1,95 @@
---
group: cpp-tables
tag: cpp-tables
lang: cpp
domain: core
n: 10
description: "Table Query Language: pipeline de transformacion tabular pura (filter, group, agg, sort, join, stats, formulas Lua, emit/apply round-trip) + render UI completa (data_table)."
---
# cpp-tables — Table Query Language (C++ puro)
Stack de transformacion tabular C++. Nucleo headless (filtros, agrupacion, stats, joins, formulas Lua, round-trip TQL). Entry-point de render via `data_table_cpp_viz` que compone todo el stack con UI ImGui completa. Apps migran desde el playground cambiando solo el include path.
## Funciones del grupo
| ID | Firma corta | Que hace |
|---|---|---|
| `auto_detect_type_cpp_core` | `ColumnType auto_detect_type(cells, rows, cols, col, sample_n=64)` | Infiere ColumnType escaneando hasta N celdas no-vacias |
| `compute_column_stats_cpp_core` | `ColStats compute_column_stats(cells, rows, cols, col, ...)` | Mean, p25/p50/p75, hist 24 bins, top-8 categorias |
| `compute_stage_cpp_core` | `StageOutput compute_stage(cells, rows, cols, headers, types, stage)` | Ejecuta un Stage TQL: filter → group+agg → sort |
| `compute_pipeline_cpp_core` | `StageOutput compute_pipeline(cells, rows, cols, headers, types, stages)` | Encadena N stages secuencialmente |
| `join_tables_cpp_core` | `StageOutput join_tables(left..., right, jn)` | Hash join multi-key (inner/left/right/full) |
| `lua_engine_cpp_core` | `Engine* get(); string eval(engine, id, ctx, err)` | Motor Lua 5.4 sandbox para formulas de columnas derivadas |
| `tql_emit_cpp_core` | `std::string tql::emit(state, headers, types)` | Serializa State a texto Lua TQL v1 |
| `tql_apply_cpp_core` | `ApplyResult tql::apply(lua_text, available_headers)` | Parsea texto TQL v1 y produce State + warnings |
| `tql_helpers_cpp_core` | `op_label, view_mode_token, agg_fn_token, ...` | Conversiones puras enum↔token usadas por emit/apply |
## Tipos usados
- `data_table_types_cpp_core` — enums y structs base: `ColumnType`, `Op`, `Stage`, `StageOutput`, `Filter`, `Aggregation`, `Join`, `TableInput`, `ViewMode`, `VizPanel`, `ViewConfig`, `State`, etc.
## Ejemplo canonico end-to-end
```cpp
// 1. Datos raw (row-major, 4 filas x 3 cols)
#include "core/auto_detect_type.h"
#include "core/compute_pipeline.h"
#include "core/tql_emit.h"
#include "core/tql_apply.h"
using namespace data_table;
const char* raw[] = {
"EU","A","100",
"US","A","200",
"EU","B","300",
"EU","A","50"
};
std::vector<std::string> hdrs = {"region","type","revenue"};
// 2. Auto-detectar tipos
std::vector<ColumnType> types;
for (int c = 0; c < 3; ++c) types.push_back(auto_detect_type(raw, 4, 3, c));
// 3. Pipeline: filtrar EU, agrupar por type, sumar revenue
Stage s0;
s0.filters.push_back({0, Op::Eq, "EU"});
Stage s1;
s1.breakouts = {"type"};
Aggregation agg; agg.fn = AggFn::Sum; agg.col = "revenue"; s1.aggregations.push_back(agg);
s1.sorts.push_back({"sum_revenue", true});
// 4. Ejecutar pipeline
StageOutput out = compute_pipeline(raw, 4, 3, hdrs, types, {s0, s1});
// out.rows==2: [B,300] [A,150]
// 5. Persistir estado como TQL (para guardar en BD o portapapeles)
State st;
st.stages = {s0, s1};
st.display = ViewMode::Bar;
std::string tql_text = tql::emit(st, hdrs, types);
// 6. Restaurar desde TQL
tql::ApplyResult res = tql::apply(tql_text, hdrs);
if (res.ok) {
// res.state listo — compilar formulas derivadas con lua_engine si las hay
}
```
| `data_table_cpp_viz` | `void data_table::render(id, tables, st, show_chrome=true)` | Render UI completa: chips bar, tabla, viz panels, drill, TQL editor, Ask AI. Entry-point publica del stack. |
## Fronteras del grupo headless (core)
- NO incluye render: ningun include de ImGui, ImPlot ni tipos de ventana.
- NO incluye I/O: no lee archivos CSV, no escribe a disco.
- El motor Lua (`lua_engine`) es la unica funcion impura del grupo — gestiona estado del interprete.
- `tql_apply` abre/cierra su propio `lua_State` por llamada (no usa lua_engine internamente). Fórmulas en `DerivedColumn.formula` se almacenan verbatim; el caller las compila con `lua_engine::compile()`.
- La deteccion de fechas solo reconoce ISO 8601 (`YYYY-MM-DD`). Otros formatos caen en String.
- Los joins trabajan sobre `TableInput` en memoria; no hay join lazy ni streaming.
## Prerequisitos
- `data_table_types_cpp_core` debe estar en el include path (`cpp/functions/`).
- Para `lua_engine` y `tql_apply`: linkar contra la lib Lua 5.4 vendored (`cpp/vendor/lua/`, target `lua54`).
- Para tests headless: usar `add_fn_test` del `cpp/tests/CMakeLists.txt` — no necesita fn_framework ni ImGui context.
+74
View File
@@ -0,0 +1,74 @@
# Transformer — Funciones que limpian, refinan o agregan datos
Tag: `transformer`. Grupo de funciones que **transforman datos**: clean, dedup, aggregate, feature-engineer, filter, impute, normalize. Input + output ambos datos (no efectos externos). Segundo eslabon del flujo en `data_factory` (analogia Factorio = assemblers).
Filtro MCP: `mcp__registry__fn_search query="" tag="transformer"`.
## Funciones del grupo
| ID | Lang | Que hace |
|---|---|---|
| [aggregate_by_group_py_datascience](../../python/functions/datascience/aggregate_by_group.md) | py | GROUP BY + agg |
| [deduplicate_entities_py_datascience](../../python/functions/datascience/deduplicate_entities.md) | py | Dedup entities |
| [deduplicate_relations_py_datascience](../../python/functions/datascience/deduplicate_relations.md) | py | Dedup relations |
| [align_relations_to_entities_py_datascience](../../python/functions/datascience/align_relations_to_entities.md) | py | Reconciliacion |
| [clip_py_datascience](../../python/functions/datascience/clip.md) | py | Cap valores fuera de rango |
| [clip_go_datascience](../../functions/datascience/clip.md) | go | Cap valores fuera de rango |
| [impute_py_datascience](../../python/functions/datascience/impute.md) | py | Rellenar nulls |
| [impute_go_datascience](../../functions/datascience/impute.md) | go | Rellenar nulls |
| [histogram_py_datascience](../../python/functions/datascience/histogram.md) | py | Bin frequencies |
| [histogram_go_datascience](../../functions/datascience/histogram.md) | go | Bin frequencies |
| [group_by_go_datascience](../../functions/datascience/group_by.md) | go | Group rows by key |
| [coerce_types_py_core](../../python/functions/core/coerce_types.md) | py | Convert column types |
| [csv_to_parquet_duckdb_py_core](../../python/functions/core/csv_to_parquet_duckdb.md) | py | Format conversion |
| [diff_entities_py_datascience](../../python/functions/datascience/diff_entities.md) | py | Compare entity snapshots |
| [diff_relations_py_datascience](../../python/functions/datascience/diff_relations.md) | py | Compare relation snapshots |
## Ejemplo canonico
Pipeline limpiar dataframe: impute nulls -> clip outliers -> dedup -> aggregate.
```python
from datascience import impute, clip, deduplicate_entities, aggregate_by_group
# 1. Rellenar nulls
df = impute(df, columns=["price"], strategy="median")
# 2. Cap valores extremos
df = clip(df, column="price", lower=0.0, upper=10000.0)
# 3. Deduplicar por clave
df = deduplicate_entities(df, key_columns=["sku"])
# 4. Agregar por categoria
out = aggregate_by_group(df, group_by=["category"], aggs={"price": "mean", "qty": "sum"})
```
## Fronteras del grupo
NO cubre:
- **Extract** (leer de fuente externa) -> [[extractor]].
- **Sink** (escribir a destino externo) -> [[sink]].
- **Validacion** (range/null/drift checks) -> [[validator]]. Transformers ASUMEN datos validos en entrada.
- ML training (eso es `trainer`, deferred a v2 de data_factory).
## Cuando NO usar `transformer`
- Si la funcion solo lee/escribe pero no transforma -> es extractor o sink.
- Si solo verifica una condicion (bool/list) -> es validator.
- Si solo encadena varias funciones -> es `pipeline` (kind), no transformer plano.
## Pureza
La mayoria de transformers son **puros** (mismo input -> mismo output). Algunos son impuros por usar SDKs con caches (duckdb, polars). El tag `transformer` no impone pureza — verifica `purity` en frontmatter de cada funcion.
## Consumidores
- `data_factory` — tab Transformers.
- `dag_engine` DAG steps con `function: <transformer_id>`.
- Notebooks analysis/ pipelines.
## Notas
- Transformers Go/Py duplicados intencionalmente — eleccion por stack y rendimiento.
- Si el registry incorpora `polars`/`duckdb` masivamente, considerar crear sub-grupos.
+64
View File
@@ -0,0 +1,64 @@
# Validator — Funciones que verifican datos o configuracion contra reglas
Tag: `validator`. Grupo de funciones que **verifican** un dato/config/spec contra una regla y devuelven resultado de validacion (bool / lista de errores / metricas de calidad). NO transforman ni mueven datos — solo dictaminan.
Filtro MCP: `mcp__registry__fn_search query="" tag="validator"`.
## Funciones del grupo
| ID | Lang | Que valida |
|---|---|---|
| [config_validate_go_infra](../../functions/infra/config_validate.md) | go | Estructura de config (campos requeridos, tipos) |
| [file_validate_type_go_infra](../../functions/infra/file_validate_type.md) | go | Tipo MIME / extension de archivo |
| [dag_validate_go_core](../../functions/core/dag_validate.md) | go | DAG topologia (ciclos, deps, schedule) |
| [detect_outliers_py_datascience](../../python/functions/datascience/detect_outliers.md) | py | Outliers numericos (z-score / IQR) |
| [detect_outliers_go_datascience](../../functions/datascience/detect_outliers.md) | go | Outliers numericos |
| [detect_drift_py_datascience](../../python/functions/datascience/detect_drift.md) | py | Drift de distribucion vs baseline |
## Ejemplo canonico
Validar dataset antes de cargarlo a sink productivo.
```python
from datascience import detect_outliers, detect_drift
# 1. Detectar outliers en columna critica
outliers = detect_outliers(df, column="amount", method="zscore", threshold=3.0)
if len(outliers) > len(df) * 0.05:
raise ValueError(f"too many outliers: {len(outliers)} / {len(df)}")
# 2. Comparar contra distribucion historica
drift_report = detect_drift(df_current=df, df_baseline=df_yesterday, column="amount")
if drift_report["ks_pvalue"] < 0.01:
print(f"WARNING: distribution drift detected (KS p={drift_report['ks_pvalue']:.4f})")
```
## Fronteras del grupo
NO cubre:
- **Audits** del registry mismo (capability groups, copied code, etc.) -> tag `doctor`.
- **Tests** unitarios (estan en `*_test.go`/`pytest`, no son funciones del registry).
- **Assertions** de runtime de apps (entan en `operations.db.assertions`).
- Validacion de codigo (lint, formato) — fuera del registry.
## Cuando NO usar `validator`
- Si la funcion transforma datos para HACERLOS validos (ej. clip outliers) -> es transformer.
- Si la funcion solo lee y reporta status sin verificar regla -> es extractor.
## Relacion con el bucle reactivo
Validators encajan en **Fase 4 ANALIZAR** del bucle reactivo (`fn_operations`). Una assertion definida en `operations.db.assertions` puede usar una funcion validator del registry como predicado.
## Consumidores
- `data_factory` — futura tab Validators (no v1; v1 solo extractor/transformer/sink/database).
- DAGs de auditoria (ej. `weekly-deep-scan.yaml` invoca audits del grupo `doctor`).
- Pipelines ML que validan datos antes de entrenar (defer v2).
- Pre-flight de sinks: ejecutar validator antes de escribir a destino productivo.
## Notas
- Casi todos los validators son **puros** o casi-puros (solo dependen del input).
- Grupo pequeño (6 funcs) — crecera cuando el registry incorpore validators de schema (JSON Schema, Pandera, etc.).
- DAG step `function: <validator_id>` con `continue_on.failure: false` actua como gate en un pipeline.
+124
View File
@@ -0,0 +1,124 @@
# 2026-05-16
## 02:00 — Framework C++ anti-jitter v2 + floating panel survival + Alt drag/resize + bulk redeploy
Extensiones al anti-jitter del framework C++ (`cpp/framework/app_base.cpp` + `.h`):
- **Multi-HWND subclass.** `g_subclassed` ahora `unordered_map<HWND, WNDPROC>`. Scan per-frame en `pio.Viewports` instala subclass en cada HWND nuevo (cubre main + cada viewport flotante). `prune_dead_subclassed()` con `IsWindow` limpia stale. `uninstall_sizemove_subclass_all()` al exit restaura cada HWND. Fix del temblor en paneles arrastrados fuera del main.
- **Iconified main + flotantes vivos.** El legacy `glfwWaitEvents+continue` paraba todo el frame loop. Con multi-viewport activo eso congelaba los flotantes. Ahora detecta secondary viewports y, si existen, fall-through al frame normal (main GL clear en HWND minimizado es harmless, contexts GL secundarios siguen pintando).
- **Alt + RMB resize anywhere.** WndProc detecta `WM_RBUTTONDOWN` + `GetAsyncKeyState(VK_MENU) & 0x8000`, calcula direccion por cuadrante del cursor (WMSZ_TOPLEFT/TOPRIGHT/BOTTOMLEFT/BOTTOMRIGHT relativo al centro del client rect), `ReleaseCapture()` + `PostMessage(WM_SYSCOMMAND, SC_SIZE|dir)`. Modal nativo, gate sizemove existente pausa render automaticamente.
- **Alt + LMB move anywhere.** Misma estructura para `WM_LBUTTONDOWN`. Posta `WM_SYSCOMMAND, SC_MOVE | HTCAPTION`. Modal MOVE nativo.
- **`io.ConfigWindowsMoveFromTitleBarOnly = true`** en `fn::run_app`. Floating panels (OS window borderless + UNA ventana ImGui rellenandolo) ahora respetan "solo header arrastra". Fix del drag-anywhere-sin-alt en flotantes.
- **`fn::internal::*` test observability.** `sizemove_enter_count()`, `alt_rmb_resize_count()`, `alt_lmb_move_count()`, `rbuttondown_seen_count()`, `set_force_alt_for_test(bool)`. Counters monotonicos zero-cost. En test mode los handlers de Alt cuentan pero NO postean SC_SIZE/SC_MOVE.
Tests en `apps/altsnap_jitter_test/main.cpp` extendidos a **6 phases** (p1 sync, p2 main HWND modal, p3 secondary HWND modal, p4 iconify+restore preserva flotante, p5 Alt+RMB consumed, p6 Alt+LMB consumed). Todas PASS en Windows.
Pipeline nuevo: **`redeploy_all_cpp_apps_bash_pipelines`** (`bash/functions/pipelines/redeploy_all_cpp_apps.sh` + `.md`). Cross-compila TODO el arbol `cpp/` en un solo cmake pass + redeploy de cada `.exe` al Desktop. Filtro opcional por substring. Tolerante a fallos (build best-effort, summary OK/SKIPPED/FAILED). Composicion: `build_cpp_windows_bash_infra` + loop `taskkill.exe` + `deploy_cpp_exe_to_windows_bash_infra`. Primera corrida: **12 OK / 1 SKIP / 0 FAILED**.
Fix `resolve_cpp_app_dir_bash_infra` **v1.1.0**: ahora busca apps en `apps/<X>/` (canonical issue 0096), luego `cpp/apps/<X>/` (legacy), luego `projects/*/apps/<X>/`. Antes solo veia los dos ultimos → `./fn run compile_cpp_app dag_engine_ui` fallaba. Deduccion desde CWD tambien cubre los tres layouts.
### Pitfalls / gotchas registrados
- `ImGui_ImplGlfw` subclassea el HWND DESPUES que nuestro framework. Captura nuestro WndProc como `bd->PrevWndProc` y chainea via `CallWindowProc` → todos los mensajes nos llegan en orden correcto. **NO re-subclassear** despues de ImGui init (genera recursion infinita por cycle).
- `keybd_event(VK_MENU)` no es fiable para drivear `GetAsyncKeyState` desde tests headless cross-compilados (sesion de input no foreground). Usar `set_force_alt_for_test(true)` + `SendMessageW` sincrono mismo-hilo. `PostMessage(WM_RBUTTONDOWN)` sintetizado tambien es dropeado por el kernel input filter.
- Pre-existing build break en `cpp/tests/test_llm_anthropic.cpp` + `cpp/tests/test_graph_icons.cpp` por `setenv()` no disponible en mingw-w64. NO bloquea `redeploy_all_cpp_apps` (build best-effort). Candidato a guard `#ifdef _WIN32` + `_putenv_s` o skip cross-compile.
### Lo siguiente que pega
- Resolver el build break de `test_llm_anthropic.cpp` + `test_graph_icons.cpp` con `_putenv_s` bajo `#ifdef _WIN32`. Quitar el SKIP del log de `redeploy_all_cpp_apps`.
- Considerar regla nueva sobre **ImGui io flags criticos** (`ConfigWindowsMoveFromTitleBarOnly`, `ConfigViewportsNoAutoMerge`, `ConfigDragClickToInputText`, etc.) que cambian comportamiento de viewports. Sub-seccion en `cpp_apps.md` o archivo separado en `.claude/rules/imgui_io_flags.md`.
- Documentar en `cpp/PATTERNS.md` que `add_imgui_app` ya embebe el icono `appicon.ico` si esta en el `<app_dir>` (verificado en sesion; el usuario lo mantuvo entre redeploys).
### Archivos tocados
- `cpp/framework/app_base.cpp` (multi-HWND, iconified-gate, Alt+RMB, Alt+LMB, ConfigWindowsMoveFromTitleBarOnly, fn::internal::*)
- `cpp/framework/app_base.h` (declaracion de fn::internal::*)
- `apps/altsnap_jitter_test/main.cpp` (phases 3-6)
- `apps/altsnap_jitter_test/app.md` (6 phases doc)
- `bash/functions/pipelines/redeploy_all_cpp_apps.sh` (nuevo)
- `bash/functions/pipelines/redeploy_all_cpp_apps.md` (nuevo, growth log v1.0.0)
- `bash/functions/infra/resolve_cpp_app_dir.sh` (v1.1.0)
- `bash/functions/infra/resolve_cpp_app_dir.md` (v1.1.0 + growth log)
- `.claude/rules/cpp_apps.md` (seccion 7.1 ampliada con v2)
- `docs/capabilities/cpp-windows.md` (tabla + seccion bulk redeploy + layouts)
- `CHANGELOG.md` (entrada 2026-05-16)
---
## 01:05 — Iconos .ico para apps C++ + funcion del registry
Las 11 apps C++ desplegadas a Windows no tenian icono → imposible distinguirlas en Desktop/taskbar (todas con el icono generico de exe).
### Pipeline build-time end-to-end
1. **Fuente de glyphs**: clonado `phosphor-icons/core` (MIT) en `sources/phosphor-core/` con `git clone --depth=1`. 1512 SVGs en 6 weights. Registrado en `sources/sources.yaml`.
2. **Mapping inicial** en `dev/gen_app_icons.py` (script reproducible, NO es del registry — vive en `dev/`). Tabla `APPS = [(app_id, dir, phosphor_icon, accent_hex), ...]` con un Tailwind color 500-700 por app:
- chart_demo → chart-bar / sky-500
- dag_engine_ui → tree-structure / violet-600
- data_factory → factory / orange-500
- engine_smoke (no aplicado: usa raw add_executable, no `add_imgui_app`)
- graph_explorer → graph / cyan-600
- navegator_dashboard → compass / blue-600
- odr_console → terminal-window / zinc-600
- primitives_gallery → shapes / pink-600
- registry_dashboard → gauge / emerald-600
- runtime_test (no aplicado: raw add_executable)
- shaders_lab → palette / orange-600
- text_editor_smoke → note-pencil / teal-600
- altsnap_jitter_test → arrows-clockwise / red-600
3. **Funcion del registry**: `generate_app_icon_py_infra` (`python/functions/infra/generate_app_icon.py`). Lo escribe `fn-constructor`. Signature `generate_app_icon(phosphor_icon_name, accent_hex, out_ico_path, *, weight='fill', sizes=None, phosphor_root=None) -> str`. Tags `cpp-windows, icon, phosphor`. Deps `cairosvg + Pillow` ya en `python/.venv` (`uv pip install cairosvg` en `python/`). Validado con `./fn show` + smoke test `/tmp/test_icon2.ico`.
4. **Wiring CMake** (`cpp/CMakeLists.txt`):
- Linea 1-5: `project(... LANGUAGES C CXX RC)` solo en WIN32. Linux ignora.
- Macro `add_imgui_app` (linea ~241): si `WIN32 AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/appicon.ico`, genera `${target}_appicon.rc` en build dir con `IDI_ICON1 ICON "<full_path>"` y lo anade a `add_executable`. `x86_64-w64-mingw32-windres` (ya en toolchain) lo compila a `.res` y se enlaza al `.exe` como seccion `.rsrc`.
5. **Rebuild + redeploy**: `cmake --build cpp/build/windows -- -j$(nproc)` + `deploy_cpp_exe_to_windows` para las 11. Verificacion: `x86_64-w64-mingw32-objdump -h <exe> | grep rsrc` muestra la seccion en todas.
### Decision: build-time vs post-build
Existia `set_exe_icon_go_infra` (Go puro, embebe `.ico` post-build modificando PE). Descartado porque:
- Falla si el `.exe` ya tiene seccion `.rsrc` (limita robustez).
- Anade paso extra al pipeline redeploy.
- Build-time es atomico: el `.exe` recien linkeado YA tiene el icono.
### Gotchas
- **engine_smoke** y **runtime_test** usan `add_executable` raw, no `add_imgui_app` — no reciben icono automatico. Son smoke tests, no apps user-facing. Skip aceptable.
- **shaders_lab** vive en `apps/shaders_lab/`, no en `cpp/apps/shaders_lab/` (confusion inicial del mapping; corregido).
- **Pillow `save(format='ICO')` con `append_images`**: si pasas la imagen 16x16 como base, el `.ico` final solo tiene 16x16. Hay que pasar la imagen MAS GRANDE (256x256) como base + lista de variants per-size. Variants per-size dan crispness en 16/24 mejor que dejar Pillow downscalear.
- **Cache iconos Windows** (`iconcache.db`): tras desplegar nuevo `.ico`, Explorer puede seguir mostrando el viejo. Refresh: `ie4uinit.exe -show` o restart explorer.
### Para anadir icono a una app nueva
```bash
# 1. Elegir nombre Phosphor desde sources/phosphor-core/assets/fill/
ls sources/phosphor-core/assets/fill/ | grep <keyword>
# 2. Generar el .ico
./fn run generate_app_icon "<phosphor-name>" "#<accent_hex>" "<app_dir>/appicon.ico"
# 3. Rebuild + redeploy
./fn run redeploy_cpp_app_windows <app_name> <app_dir> --build
```
### Archivos tocados
- `sources/phosphor-core/` (nuevo, clonado)
- `sources/sources.yaml` (entry phosphor-icons/core)
- `dev/gen_app_icons.py` (nuevo, script reproducible con mapping inicial)
- `python/functions/infra/generate_app_icon.py` (nueva funcion registro)
- `python/functions/infra/generate_app_icon.md` (idem)
- `python/functions/infra/__init__.py` (export)
- `cpp/CMakeLists.txt` (project LANGUAGES + add_imgui_app icon wiring)
- 11 x `<app_dir>/appicon.ico` (nuevos, multi-res)
- `.claude/rules/cpp_apps.md` (§11 Icono Windows)
- `CHANGELOG.md` (entrada 2026-05-16 Added: iconos .ico)
- 11 x `.exe` rebuildeadas + redesplegadas a `/mnt/c/Users/lucas/Desktop/apps/`
### Lo siguiente que pega
- Anadir icono tambien a `engine_smoke` y `runtime_test` si se promueven a apps user-facing (hoy son smoke tests).
- Considerar `fn doctor cpp-apps` check: app C++ sin `appicon.ico` → warning.
- Si aparecen iconos antiguos en Explorer tras redeploy, anadir `ie4uinit.exe -show` al final de `deploy_cpp_exe_to_windows`.