--- name: fn_match lang: go domain: infra version: 0.1.0 description: "Fuzzy matcher entre un comando shell o snippet heredoc y funciones del registry. Devuelve las top-N funciones mas probables que cubren ese patron, con score normalizado. Consumido por el hook PreToolUse para sugerir funciones del registry en vez de codigo inline." tags: [discovery, hooks, match, fuzzy, registry] uses_functions: [] uses_types: [] framework: "" entry_point: "cmd/fn/match.go" dir_path: "apps/fn_match" repo_url: "" params: - name: command desc: "Comando shell o snippet heredoc a analizar. Puede pasarse como argumento posicional o por stdin." - name: top desc: "Numero maximo de resultados a devolver (default 3)." - name: format desc: "Formato de salida: json (default) o text." - name: min-score desc: "Umbral minimo de score normalizado 0..1 (default 0.3). Resultados por debajo se descartan." output: "JSON o texto con las top-N funciones del registry ordenadas por score descendente. Score 1.0 = mejor match; resto son relativos al top." --- ## Ejemplo ```bash # Arg posicional — JSON por defecto ./fn match "taskkill.exe /IM registry_dashboard.exe /F" # Texto legible ./fn match --format text --top 5 "rsync -avz src/ user@host:/opt/app" # Stdin pipe (util en hooks) echo "curl -sf https://api.example.com/health | jq .status" | ./fn match # Subir umbral para matches muy seguros solamente ./fn match --min-score 0.7 "encrypt file sha256" ``` Salida JSON: ```json { "query": "taskkill.exe /IM registry_dashboard.exe /F", "top": [ { "id": "deploy_cpp_exe_to_windows_bash_infra", "score": 1, "signature": "deploy_cpp_exe_to_windows(app_name: string, app_dir: string) -> void", "snippet": "Copia el .exe de Windows... Mata el proceso si esta corriendo (taskkill.exe pre-autorizado)..." } ], "high_confidence": false } ``` ## Cuando usarla **Hook PreToolUse (uso principal):** El hook intercepta comandos Bash antes de ejecutarse. Si el comando supera el umbral de score, el hook inyecta en el contexto: ``` USE: deploy_cpp_exe_to_windows_bash_infra [score=1.000] instead of inline ``` **Debug manual:** Para entender por que Claude escribe cierto patron inline repetidamente, correr `fn match` con ese patron y ver si el registry ya cubre el caso. **Auditoria de gaps:** Si `fn match` devuelve `top: []` para un patron que aparece >3 veces en sesiones, ese patron es candidato a nueva funcion del registry. ## Pipeline interno 1. **Tokenize**: split por no-alfanumericos + eliminacion de flags (`-v`, `/F`), paths absolutos (conserva basename sin extension), numeros puros, tokens < 3 chars, y stopwords del dominio (`registry`, `function`, `app`, `file`, `get`, `set`, ...). 2. **FTS5 query**: `tok1 OR tok2 OR ...` sobre `functions_fts` con `bm25()`. Resultado: hasta 50 candidatos con rank BM25. 3. **Fetch metadata**: segundo query `SELECT ... FROM functions WHERE id IN (...)` para obtener name, signature, description, tags. 4. **Re-score aditivo**: cada token suma su mejor boost de campo (`name=+3.0`, `tags=+2.0`, `signature=+1.5`, `description=+1.0`). No multiplicativo — evita que tokens genericos saturen todos los resultados a 1.0. 5. **Language penalty**: si query tiene markers Python (`def`, `import`, `class`) y el hit es bash → x0.5. Si tiene markers bash (`curl`, `rsync`, `taskkill`, `exe`) y hit es py → x0.5. 6. **Normalizar**: el top score raw se convierte en 1.0; el resto es relativo. 7. **Filtrar y limitar**: `--min-score` descarta ruido; `--top N` limita salida. 8. **High confidence flag**: `top[0].score / top[1].score > 1.5` → `"high_confidence": true`. ## Gotchas **Latencia:** 6-7ms en WSL2 con 1255 funciones indexadas. Sin daemon — proceso fresh por invocacion. Bien por debajo del objetivo de 50ms. **FTS5 WAL:** La conexion se abre en modo lectura normal (no `mode=ro`) porque bm25() con WAL no checkpointed falla con "missing row from content table". El binario nunca escribe. **FTS5 rebuild:** Si `fn match` devuelve `top: []` para queries que deberias matchear, el indice FTS5 puede estar desincronizado. Solucion: ```go // Rebuild manual del indice: INSERT INTO functions_fts(functions_fts, rank) VALUES('rebuild', NULL); INSERT INTO types_fts(types_fts, rank) VALUES('rebuild', NULL); INSERT INTO unit_tests_fts(unit_tests_fts, rank) VALUES('rebuild', NULL); ``` O simplemente `./fn index` — reindexar regenera el FTS5. **Stopwords del dominio:** Tokens como `registry`, `function`, `app`, `file`, `get`, `set` se descartan del tokenizer porque matchean cientos de funciones igualmente y no aportan señal. Si un query solo tiene stopwords, `fn match` retorna error "no significant tokens". **Scores normalizados:** El score 1.0 no significa "match perfecto" — es el mejor candidato relativo al batch. Un `high_confidence: false` con score 1.0 significa que el segundo candidato es similar. Para gates en hooks usar `high_confidence: true` como criterio. **Implementacion:** `cmd/fn/match.go` en el modulo raiz `fn-registry`. Se compila junto al binario `fn` principal — no es un binario separado. Invocar como `./fn match ...`. ## Capability growth log - v1.0.0 (2026-05-14): scoring multiplicativo inicial — todos los hits clampean a 1.0 cuando varios tokens matchean - v1.1.0 (2026-05-14): scoring aditivo por token con normalizacion relativa + stopwords del dominio. FTS5 rebuild fix. Latencia: 6-7ms