chore: auto-commit (799 archivos)

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 00:28:20 +02:00
parent 20f72edb5a
commit 47fac22230
805 changed files with 5515 additions and 810 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func ArtefactDoctor(registryRoot string) ([]ArtefactCheck, error)"
description: "Audita la salud de cada artefacto (apps + analyses) registrado en registry.db. Para cada uno verifica: directorio en disco, presencia de .git, manifest parseable (app.md/analysis.md con campo name), venv de Python valido (solo analyses) y rama upstream configurada en git. Read-only, no toca red ni modifica nada."
tags: [doctor, health, artefact, audit]
tags: [doctor, health, artefact, audit, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+209
View File
@@ -0,0 +1,209 @@
package infra
import (
"bufio"
"database/sql"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
_ "github.com/mattn/go-sqlite3"
)
// CapabilityGroupAudit holds the audit result for a single capability group.
type CapabilityGroupAudit struct {
Group string `json:"group"` // slug del grupo (tag canonico)
DeclaredInIndex bool `json:"declared_in_index"` // aparece en docs/capabilities/INDEX.md
DocExists bool `json:"doc_exists"` // existe docs/capabilities/<group>.md
FunctionCount int `json:"function_count"` // funciones con ese tag en registry.db
Issues []string `json:"issues"` // lista de problemas detectados
OK bool `json:"ok"` // true si Issues esta vacio
}
// indexLinkRe matches markdown links [slug](target.md). Go regexp has no
// backreferences, so we capture both sides and compare in code.
var indexLinkRe = regexp.MustCompile(`\[([a-z][a-z0-9_-]*)\]\(([a-z][a-z0-9_-]*)\.md\)`)
// AuditCapabilityGroups audits the drift between:
// 1. Groups declared in docs/capabilities/INDEX.md (parsed via link regex).
// 2. Tags present in functions.tags of registry.db (via json_each).
// 3. Doc pages existing as docs/capabilities/<group>.md on disk.
//
// Returns one CapabilityGroupAudit per group/candidate, ordered by Group ASC.
// Tags with count >= 3 that are not declared in INDEX are surfaced as
// "ungrouped_candidate". Doc files not listed in INDEX are surfaced as
// "doc_orphan".
func AuditCapabilityGroups(root string) ([]CapabilityGroupAudit, error) {
// --- 1. Open registry.db (read-only) ---
dbPath := filepath.Join(root, "registry.db")
dsn := fmt.Sprintf("file:%s?mode=ro&_foreign_keys=on", dbPath)
db, err := sql.Open("sqlite3", dsn)
if err != nil {
return nil, fmt.Errorf("audit_capability_groups: open db: %w", err)
}
defer db.Close()
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("audit_capability_groups: ping db: %w", err)
}
// --- 2. Query all tags with their function counts ---
tagCounts, err := queryTagCounts(db)
if err != nil {
return nil, fmt.Errorf("audit_capability_groups: query tags: %w", err)
}
// --- 3. Parse INDEX.md for declared group slugs ---
indexPath := filepath.Join(root, "docs", "capabilities", "INDEX.md")
indexSlugs, err := parseIndexSlugs(indexPath)
if err != nil {
return nil, fmt.Errorf("audit_capability_groups: parse INDEX.md: %w", err)
}
// --- 4. Scan docs/capabilities/ for existing .md files (excluding INDEX.md) ---
capDir := filepath.Join(root, "docs", "capabilities")
docSlugs, err := scanDocSlugs(capDir)
if err != nil {
return nil, fmt.Errorf("audit_capability_groups: scan docs: %w", err)
}
// --- 5. Build audit entries ---
// We collect all relevant slugs: declared in INDEX, present as doc, or tags >= 3.
allSlugs := make(map[string]struct{})
for s := range indexSlugs {
allSlugs[s] = struct{}{}
}
for s := range docSlugs {
allSlugs[s] = struct{}{}
}
for tag, count := range tagCounts {
if count >= 3 {
allSlugs[tag] = struct{}{}
}
}
results := make([]CapabilityGroupAudit, 0, len(allSlugs))
for slug := range allSlugs {
_, declaredInIndex := indexSlugs[slug]
_, docExists := docSlugs[slug]
count := tagCounts[slug]
var issues []string
if declaredInIndex {
if !docExists {
issues = append(issues, "doc_missing")
}
if count == 0 {
issues = append(issues, "no_functions")
} else if count < 3 {
issues = append(issues, "below_minimum")
}
} else if docExists {
// Doc exists but not in INDEX
issues = append(issues, "doc_orphan")
} else if count >= 3 {
// Tag with enough functions but no declaration or doc
issues = append(issues, "ungrouped_candidate")
}
results = append(results, CapabilityGroupAudit{
Group: slug,
DeclaredInIndex: declaredInIndex,
DocExists: docExists,
FunctionCount: count,
Issues: issues,
OK: len(issues) == 0,
})
}
sort.Slice(results, func(i, j int) bool {
return results[i].Group < results[j].Group
})
return results, nil
}
// queryTagCounts returns a map of tag → function count using json_each on functions.tags.
func queryTagCounts(db *sql.DB) (map[string]int, error) {
rows, err := db.Query(`
SELECT j.value AS tag, COUNT(*) AS cnt
FROM functions f, json_each(f.tags) j
GROUP BY j.value
`)
if err != nil {
return nil, err
}
defer rows.Close()
counts := make(map[string]int)
for rows.Next() {
var tag string
var cnt int
if err := rows.Scan(&tag, &cnt); err != nil {
continue
}
counts[tag] = cnt
}
return counts, rows.Err()
}
// parseIndexSlugs reads INDEX.md and extracts group slugs from links of the
// form [slug](slug.md). Returns the set of declared slugs.
func parseIndexSlugs(indexPath string) (map[string]struct{}, error) {
f, err := os.Open(indexPath)
if err != nil {
if os.IsNotExist(err) {
// No INDEX.md — return empty set, not an error.
return make(map[string]struct{}), nil
}
return nil, err
}
defer f.Close()
slugs := make(map[string]struct{})
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
matches := indexLinkRe.FindAllStringSubmatch(line, -1)
for _, m := range matches {
// Self-referencing link only: [slug](slug.md), not external.
label := strings.TrimSpace(m[1])
target := strings.TrimSpace(m[2])
if label != "" && label == target {
slugs[label] = struct{}{}
}
}
}
return slugs, scanner.Err()
}
// scanDocSlugs returns the set of slugs that have a corresponding .md file in
// capDir, excluding INDEX.md itself.
func scanDocSlugs(capDir string) (map[string]struct{}, error) {
entries, err := os.ReadDir(capDir)
if err != nil {
if os.IsNotExist(err) {
return make(map[string]struct{}), nil
}
return nil, err
}
slugs := make(map[string]struct{})
for _, e := range entries {
if e.IsDir() {
continue
}
name := e.Name()
if !strings.HasSuffix(name, ".md") {
continue
}
slug := strings.TrimSuffix(name, ".md")
if slug == "INDEX" {
continue
}
slugs[slug] = struct{}{}
}
return slugs, nil
}
@@ -0,0 +1,79 @@
---
name: audit_capability_groups
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func AuditCapabilityGroups(root string) ([]CapabilityGroupAudit, error)"
description: "Audita drift entre grupos declarados en docs/capabilities/INDEX.md, tags presentes en registry.db y paginas existentes en docs/capabilities/. Detecta grupos sin doc, sin funciones, candidatos sin declarar y docs huerfanas."
tags: [doctor, capability-groups, audit, infra]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports:
- "database/sql"
- "bufio"
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "sort"
- "strings"
- "github.com/mattn/go-sqlite3"
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/audit_capability_groups.go"
params:
- name: root
desc: "raiz del repo fn_registry (donde vive registry.db y docs/capabilities/)"
output: "lista de CapabilityGroupAudit ordenada por group asc, con drift detectado"
---
## Tipo: CapabilityGroupAudit
```go
type CapabilityGroupAudit struct {
Group string `json:"group"` // slug del grupo (tag canonico)
DeclaredInIndex bool `json:"declared_in_index"` // aparece en INDEX.md
DocExists bool `json:"doc_exists"` // existe docs/capabilities/<group>.md
FunctionCount int `json:"function_count"` // funciones con ese tag
Issues []string `json:"issues"` // lista de problemas detectados
OK bool `json:"ok"` // true si Issues esta vacio
}
```
## Issues posibles
| Issue | Condicion |
|---|---|
| `doc_missing` | Grupo declarado en INDEX pero no existe el .md |
| `no_functions` | Grupo declarado pero ningun tag en registry.db coincide |
| `below_minimum` | Grupo declarado con 1 o 2 funciones (minimo es 3) |
| `ungrouped_candidate` | Tag con >= 3 funciones, no declarado en INDEX ni tiene .md |
| `doc_orphan` | Existe docs/capabilities/<g>.md pero no esta en INDEX.md |
## Ejemplo
```go
audits, err := AuditCapabilityGroups("/home/lucas/fn_registry")
if err != nil {
log.Fatal(err)
}
for _, a := range audits {
if !a.OK {
fmt.Printf("%s: %v\n", a.Group, a.Issues)
}
}
```
## Notas
- Abre `registry.db` en modo read-only (no modifica nada).
- Parsea INDEX.md con regex `\[([a-z][a-z0-9_-]*)\]\(\1\.md\)` — solo links donde el texto y el href tienen el mismo slug.
- Tags con count >= 3 que no estan en INDEX se emiten como `ungrouped_candidate`.
- Si INDEX.md no existe, se trata como conjunto vacio (no es error).
- Usada por `fn doctor capabilities` para diagnostico del registry.
+341
View File
@@ -0,0 +1,341 @@
package infra
import (
"crypto/sha256"
"database/sql"
"fmt"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
_ "github.com/mattn/go-sqlite3"
)
// CopiedCodeEntry represents a function body in an app file that matches
// the normalized body of a registry function. MVP only emits exact_copy.
type CopiedCodeEntry struct {
AppFile string `json:"app_file"`
AppFunction string `json:"app_function"`
RegistryID string `json:"registry_id"`
BodyHash string `json:"body_hash"`
Similarity float64 `json:"similarity"`
Kind string `json:"kind"` // exact_copy | near_copy | partial_match
}
// AuditCopiedCode walks apps/ and projects/*/apps/, extracts function
// declarations per language, computes a normalized body hash (strip
// comments + collapse whitespace), and matches against fingerprints
// built from registry.db.functions.code.
//
// MVP scope:
// - Languages: go, py, bash, ts, cpp
// - Match level: exact_copy only (similarity = 1.0)
// - Skips paths whose own file_path is the registry function (a function
// is not a copy of itself).
//
// Returns the list of suspected copies. Does NOT write to any DB.
// Persistence is the caller's responsibility (e.g. call_monitor).
func AuditCopiedCode(registryRoot string) ([]CopiedCodeEntry, error) {
dbPath := filepath.Join(registryRoot, "registry.db")
db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL")
if err != nil {
return nil, fmt.Errorf("audit_copied_code: open db: %w", err)
}
defer db.Close()
// Build fingerprint index: normalized_hash -> [registry_id ...]
// and skip-set: file_path -> registry_id (so we don't flag the function
// as a copy of itself).
rows, err := db.Query("SELECT id, name, lang, code, file_path FROM functions WHERE code != ''")
if err != nil {
return nil, fmt.Errorf("audit_copied_code: query functions: %w", err)
}
defer rows.Close()
fpIndex := map[string][]string{}
registryFilePaths := map[string]struct{}{}
for rows.Next() {
var id, name, lang, code, filePath string
if err := rows.Scan(&id, &name, &lang, &code, &filePath); err != nil {
return nil, err
}
if filePath != "" {
registryFilePaths[filePath] = struct{}{}
}
// If the `code` column contains a whole module (e.g. Python file
// with multiple defs), only index the function declaration whose
// name matches the registry entry. Otherwise (single-fn files
// like Go), hash the whole body. Both paths yield a single
// normalized hash mapped to this id.
decls := extractFunctions(code, lang)
var fnBody string
if len(decls) > 1 {
for _, d := range decls {
if d.Name == name {
fnBody = d.Body
break
}
}
} else if len(decls) == 1 {
fnBody = decls[0].Body
} else {
fnBody = code
}
if fnBody == "" {
continue
}
h := normalizedBodyHash(fnBody, lang)
if h == "" {
continue
}
fpIndex[h] = append(fpIndex[h], id)
}
if err := rows.Err(); err != nil {
return nil, err
}
// Walk app trees
scanDirs := []string{filepath.Join(registryRoot, "apps")}
if entries, err := os.ReadDir(filepath.Join(registryRoot, "projects")); err == nil {
for _, p := range entries {
if !p.IsDir() {
continue
}
scanDirs = append(scanDirs, filepath.Join(registryRoot, "projects", p.Name(), "apps"))
}
}
var out []CopiedCodeEntry
for _, d := range scanDirs {
_ = filepath.WalkDir(d, func(path string, dirent fs.DirEntry, err error) error {
if err != nil {
return nil
}
if dirent.IsDir() {
if shouldSkipDir(dirent.Name()) {
return filepath.SkipDir
}
return nil
}
lang := langFromExt(path)
if lang == "" {
return nil
}
rel, _ := filepath.Rel(registryRoot, path)
// Don't audit the registry function file itself
if _, isRegistry := registryFilePaths[rel]; isRegistry {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return nil
}
funcs := extractFunctions(string(data), lang)
for _, fn := range funcs {
h := normalizedBodyHash(fn.Body, lang)
if h == "" {
continue
}
if matches, ok := fpIndex[h]; ok {
for _, rid := range matches {
out = append(out, CopiedCodeEntry{
AppFile: rel,
AppFunction: fn.Name,
RegistryID: rid,
BodyHash: h,
Similarity: 1.0,
Kind: "exact_copy",
})
}
}
}
return nil
})
}
return out, nil
}
// ---- Helpers (unexported) ----
func shouldSkipDir(name string) bool {
switch name {
case ".git", ".venv", "node_modules", "__pycache__", "build", "dist", "vendor", ".pytest_cache", ".cache", "_vendored":
return true
}
return false
}
func langFromExt(path string) string {
switch strings.ToLower(filepath.Ext(path)) {
case ".go":
return "go"
case ".py":
return "py"
case ".sh":
return "bash"
case ".ts", ".tsx":
return "ts"
case ".cpp", ".cc", ".cxx":
return "cpp"
}
return ""
}
func normalizedBodyHash(code, lang string) string {
norm := stripCommentsAndWhitespace(code, lang)
if len(norm) < 20 {
// Skip trivial bodies: too short to be meaningful match.
return ""
}
h := sha256.Sum256([]byte(norm))
return fmt.Sprintf("%x", h)[:16]
}
func stripCommentsAndWhitespace(s, lang string) string {
switch lang {
case "go", "ts", "cpp":
s = stripCStyleComments(s)
case "py":
s = stripPythonDocstrings(s)
s = stripHashComments(s)
case "bash":
s = stripHashComments(s)
}
return collapseWhitespace(s)
}
var (
reCStyleSingle = regexp.MustCompile(`//[^\n]*`)
reCStyleMulti = regexp.MustCompile(`(?s)/\*.*?\*/`)
reHashLine = regexp.MustCompile(`(?m)#.*$`)
reTripleQuote = regexp.MustCompile(`(?s)"""[\s\S]*?"""|'''[\s\S]*?'''`)
reWS = regexp.MustCompile(`\s+`)
)
func stripCStyleComments(s string) string {
s = reCStyleMulti.ReplaceAllString(s, " ")
s = reCStyleSingle.ReplaceAllString(s, " ")
return s
}
func stripHashComments(s string) string {
return reHashLine.ReplaceAllString(s, "")
}
func stripPythonDocstrings(s string) string {
return reTripleQuote.ReplaceAllString(s, " ")
}
func collapseWhitespace(s string) string {
return strings.TrimSpace(reWS.ReplaceAllString(s, " "))
}
type fnDecl struct {
Name string
Body string
}
// extractFunctions extracts function declarations from source.
// MVP: regex-based, naive brace matching. Misses nested closures, methods
// with complex receivers, multi-line signatures with embedded {.
func extractFunctions(src, lang string) []fnDecl {
switch lang {
case "go":
return extractBracedFunctions(src, regexp.MustCompile(`(?m)^func\s+(?:\([^)]*\)\s+)?(\w+)\s*\(`))
case "bash":
return extractBracedFunctions(src, regexp.MustCompile(`(?m)^(\w[\w_]*)\s*\(\s*\)\s*\{`))
case "ts":
return extractBracedFunctions(src, regexp.MustCompile(`(?m)(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(`))
case "cpp":
return extractBracedFunctions(src, regexp.MustCompile(`(?m)^[\w:][\w:\s\*&<>,]*\s+(\w+)\s*\([^)]*\)\s*(?:const\s*)?(?:noexcept\s*)?\{`))
case "py":
return extractPythonFunctions(src)
}
return nil
}
// extractBracedFunctions matches a name via regex, then finds the body by
// scanning to the next '{' and balancing braces. Crude: ignores strings,
// comments, and char literals — false positives possible on weird code.
func extractBracedFunctions(src string, re *regexp.Regexp) []fnDecl {
var out []fnDecl
matches := re.FindAllStringSubmatchIndex(src, -1)
for _, m := range matches {
name := src[m[2]:m[3]]
// Find first '{' after match end
start := -1
for i := m[1]; i < len(src); i++ {
if src[i] == '{' {
start = i
break
}
// Bail out if newline-newline encountered (not a function with body)
if i+1 < len(src) && src[i] == '\n' && src[i+1] == '\n' {
break
}
}
if start == -1 {
continue
}
depth := 0
end := -1
for i := start; i < len(src); i++ {
switch src[i] {
case '{':
depth++
case '}':
depth--
if depth == 0 {
end = i + 1
}
}
if end != -1 {
break
}
}
if end == -1 {
continue
}
out = append(out, fnDecl{Name: name, Body: src[start:end]})
}
return out
}
// extractPythonFunctions uses indentation: def name(...): ... until dedent.
func extractPythonFunctions(src string) []fnDecl {
var out []fnDecl
lines := strings.Split(src, "\n")
re := regexp.MustCompile(`^(\s*)def\s+(\w+)\s*\(`)
for i, line := range lines {
m := re.FindStringSubmatch(line)
if m == nil {
continue
}
defIndent := len(m[1])
name := m[2]
bodyLines := []string{line}
for j := i + 1; j < len(lines); j++ {
l := lines[j]
if strings.TrimSpace(l) == "" {
bodyLines = append(bodyLines, l)
continue
}
indent := 0
for _, c := range l {
if c == ' ' || c == '\t' {
indent++
} else {
break
}
}
if indent <= defIndent {
break
}
bodyLines = append(bodyLines, l)
}
out = append(out, fnDecl{Name: name, Body: strings.Join(bodyLines, "\n")})
}
return out
}
+74
View File
@@ -0,0 +1,74 @@
---
name: audit_copied_code
lang: go
domain: infra
version: 0.1.0
purity: impure
kind: function
description: "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."
tags: [audit, drift, monitoring, registry, doctor, copied-code]
signature: "func AuditCopiedCode(registryRoot string) ([]CopiedCodeEntry, error)"
error_type: "error_go_core"
returns_optional: false
params:
- name: registryRoot
desc: "Path absoluto al directorio raiz del fn_registry (contiene registry.db). El audit busca apps/ y projects/*/apps/ dentro."
output: "Lista de CopiedCodeEntry: {app_file, app_function, registry_id, body_hash, similarity, kind}. similarity=1.0 y kind='exact_copy' en MVP."
uses_functions: []
uses_types: []
imports:
- crypto/sha256
- database/sql
- io/fs
- os
- path/filepath
- regexp
- strings
- github.com/mattn/go-sqlite3
example: |
import "fn-registry/functions/infra"
entries, err := infra.AuditCopiedCode("/home/lucas/fn_registry")
if err != nil { ... }
for _, e := range entries {
fmt.Printf("%s:%s ~ %s (%s, %.2f)\n",
e.AppFile, e.AppFunction, e.RegistryID, e.Kind, e.Similarity)
}
file_path: "functions/infra/audit_copied_code.go"
tested: false
notes: |
Pipeline interno:
1. Lee `registry.db.functions(id, lang, code, file_path)` para construir
fingerprint index `hash → [registry_id, ...]`. Cada `code` se parsea por
`extractFunctions(code, lang)` para indexar cada funcion declarada
dentro del archivo (importante para `.py` que contiene varias).
2. Construye skip-set con los `file_path` del registry para no flagear la
funcion como copia de si misma.
3. Walk recursivo de `apps/` y `projects/*/apps/`. Skip de `.git`,
`.venv`, `node_modules`, `__pycache__`, `build`, `dist`, `vendor`,
`.pytest_cache`, `.cache`.
4. Por cada archivo `.go/.py/.sh/.ts/.cpp`, extrae funciones y hash.
5. Match en fpIndex → emite CopiedCodeEntry.
Normalizacion:
- Go/TS/C++: strip `//...` y `/* ... */`, collapse whitespace.
- Python: strip docstrings (triple quotes), strip `#...` lines, collapse.
- Bash: strip `#...` lines, collapse.
Hash: sha256 hex truncado a 16 chars del cuerpo normalizado. Cuerpos
con menos de 20 chars normalizados se descartan (trivialmente similares).
Limitaciones MVP:
- Solo exact_copy. near_copy/partial_match (fuzzy hash, AST SimHash,
embeddings) en fase futura.
- extractFunctions con regex naive. Falla con:
- signatures multi-linea
- lambdas y closures
- metodos con receivers complejos
- cadenas/comentarios con `{` dentro
- C++ regex es la mas fragil (templates, namespaces, multiple inheritance).
- No detecta copias con renames de variables. Para eso = fuzzy hash.
Privacidad: solo se persisten paths, function names, y hashes. Nunca
contenido del codigo. El cuerpo solo vive en memoria durante el audit.
---
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func AuditCppApps(registryRoot string) ([]CppAppAudit, error)"
description: "Audita conformidad de apps C++ del registry con cpp/PATTERNS.md y .claude/rules/cpp_apps.md. Verifica que cada main.cpp use fn::run_app, declare cfg.about y cfg.log, no llame fn_ui::app_menubar manualmente, no llame ImGui::DockSpaceOverViewport sin opt-out (cfg.auto_dockspace = false), y que app.md tenga framework: imgui y entry_point. Read-only."
tags: [doctor, registry-first, audit, cpp, imgui, standard]
tags: [doctor, registry-first, audit, cpp, imgui, standard, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func AuditMlEnv(registryRoot string) (MlEnvReport, error)"
description: "Audita el entorno ML del sistema: GPUs NVIDIA, toolkit CUDA, venv Python, paquetes clave (torch, diffusers, transformers, huggingface_hub), herramientas CLI (sd, llama-cli) y el vault de modelos. Retorna un MlEnvReport con OverallOK=true solo si hay al menos 1 GPU y los checks criticos estan en ok/warning."
tags: [ml, cuda, gpu, nvidia, audit, doctor, infra, torch, diffusers]
tags: [ml, cuda, gpu, nvidia, audit, doctor, infra, torch, diffusers, pendiente-usar]
uses_functions: [get_gpu_info_go_infra]
uses_types: [gpu_info_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func CacheToSQLite(dbPath, namespace string) (*SQLiteCache, error)"
description: "Cache key-value persistido en SQLite con TTL y lazy eviction. Valores almacenados como JSON bytes; el caller serializa y deserializa. Thread-safe con sync.Mutex. Soporta Get, Set, Delete, Clear y GetOrSet."
tags: [cache, sqlite, persistence, ttl, key-value, concurrent]
tags: [cache, sqlite, persistence, ttl, key-value, concurrent, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func ClickHouseOpen(host string, port int, user, password, database string) (*sql.DB, error)"
description: "Conecta a ClickHouse construyendo DSN clickhouse://user:pass@host:port/database."
tags: [database, clickhouse, connection, sql, olap]
tags: [database, clickhouse, connection, sql, olap, pendiente-usar]
uses_functions: []
uses_types: [db_config_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func ConfigDump(cfg any) map[string]string"
description: "Convierte una struct de configuracion a map[string]string para logging o inspeccion. Campos con tag secret:\"true\" aparecen como \"***\". Solo campos exportados de nivel superior."
tags: [config, dump, debug, logging, infra, reflect]
tags: [config, dump, debug, logging, infra, reflect, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func ConfigFromEnv(target any) error"
description: "Puebla una struct de configuracion desde variables de entorno usando reflection y struct tags (env, default, required, secret). Soporta string, int, int64, bool, float64 y []string (comma-separated). Acumula todos los errores antes de retornar."
tags: [config, env, reflect, infra, tags]
tags: [config, env, reflect, infra, tags, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func ConfigFromFile(path string, target any) error"
description: "Carga configuracion desde un archivo en target. Soporta JSON (encoding/json stdlib). YAML es stub: retorna 'not implemented'. Extension determina el formato."
tags: [config, file, json, yaml, infra]
tags: [config, file, json, yaml, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func ConfigMerge(base, override map[string]string) map[string]string"
description: "Merge de dos map[string]string. El map override tiene precedencia sobre base en claves comunes. Ninguno de los inputs es mutado — retorna un mapa nuevo."
tags: [config, merge, map, infra]
tags: [config, merge, map, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func ConfigValidate(cfg any) ConfigValidation"
description: "Valida una struct de configuracion usando struct tags. Acumula todos los errores (required, format:email, format:url, min, max, oneof) sin detener en el primero. Retorna ConfigValidation con IsValid=true si no hay errores."
tags: [config, validation, env, infra, reflect]
tags: [config, validation, env, infra, reflect, pendiente-usar]
uses_functions: []
uses_types: [config_validation_go_infra, config_error_go_infra]
returns: [config_validation_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func CRUDDefineResource(name string, table string, fields []CRUDField, softDelete bool) (CRUDResource, error)"
description: "Construye un CRUDResource validando nombre, tabla y campos. Rechaza nombres de campo reservados (id, created_at, updated_at, deleted_at), duplicados y tipos distintos de TEXT, INTEGER, REAL, BLOB."
tags: [crud, resource, define, validation, infra]
tags: [crud, resource, define, validation, infra, pendiente-usar]
uses_functions: []
uses_types: [CRUDResource_go_infra, CRUDField_go_infra]
returns: [CRUDResource_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func CRUDGenerateTableSQL(res CRUDResource) string"
description: "Genera el DDL CREATE TABLE IF NOT EXISTS de un CRUDResource. Incluye id como PRIMARY KEY, timestamps created_at/updated_at y deleted_at si soft_delete. Cada campo aplica su tipo SQLite y constraints NOT NULL/UNIQUE/DEFAULT."
tags: [crud, sql, ddl, generate, sqlite, infra]
tags: [crud, sql, ddl, generate, sqlite, infra, pendiente-usar]
uses_functions: []
uses_types: [CRUDResource_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func CRUDRegisterRoutes(mux *http.ServeMux, basePath string, res CRUDResource, db *sql.DB)"
description: "Registra las 5 rutas REST de un CRUDResource en un http.ServeMux: GET /base, GET /base/{id}, POST /base, PUT /base/{id}, DELETE /base/{id}. Usa la sintaxis 'METHOD /path' de Go 1.22+."
tags: [crud, routes, register, http, mux, infra]
tags: [crud, routes, register, http, mux, infra, pendiente-usar]
uses_functions: [crud_generate_handlers_go_infra]
uses_types: [CRUDResource_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DBClose(db *sql.DB) error"
description: "Cierra la conexion a la base de datos. Wrapper sobre db.Close() para composabilidad en pipelines que gestionan el ciclo de vida de *sql.DB explicitamente."
tags: [database, sql, close, lifecycle]
tags: [database, sql, close, lifecycle, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DBCreateTable(db *sql.DB, table string, columns []string) error"
description: "Ejecuta CREATE TABLE IF NOT EXISTS con las definiciones de columnas dadas. Valida que el nombre de tabla sea un identificador SQL seguro."
tags: [database, sql, ddl, create, table, schema]
tags: [database, sql, ddl, create, table, schema, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DBExec(db *sql.DB, query string, args ...any) (int64, error)"
description: "Ejecuta un statement no-SELECT (INSERT, UPDATE, DELETE, DDL) y retorna el numero de filas afectadas."
tags: [database, sql, exec, insert, update, delete, ddl]
tags: [database, sql, exec, insert, update, delete, ddl, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DBInsertBatch(db *sql.DB, table string, columns []string, rows [][]any) (int64, error)"
description: "Inserta multiples filas en una transaccion usando prepared statement. Retorna el total de filas afectadas. Mas eficiente que llamar DBInsertRow en un loop."
tags: [database, sql, insert, batch, transaction, bulk]
tags: [database, sql, insert, batch, transaction, bulk, pendiente-usar]
uses_functions: [db_insert_row_go_infra]
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DBQuery(db *sql.DB, query string, args ...any) ([]map[string]any, error)"
description: "Ejecuta un SELECT y retorna los resultados como slice de maps. Convierte valores a tipos nativos Go segun el tipo de columna reportado por el driver."
tags: [database, sql, query, select, generic]
tags: [database, sql, query, select, generic, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DeployApp(appDir string, imageName string, port int, envVars map[string]string) (string, error)"
description: "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."
tags: [docker, deploy, go, pipeline, infra, container]
tags: [docker, deploy, go, pipeline, infra, container, pendiente-usar]
uses_functions: [generate_dockerfile_go_infra, write_dockerfile_go_infra, docker_build_image_go_infra, docker_run_container_go_infra]
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DeployAppRemote(conn SSHConn, cfg DeployConfig) error"
description: "Orquesta el deploy continuo de una app a un VPS: verifica SSH, compila localmente, sube binario, reinicia systemd y hace health check."
tags: [deploy, vps, remote, ci, cd, pipeline, infra]
tags: [deploy, vps, remote, ci, cd, pipeline, infra, pendiente-usar]
uses_functions: [ssh_check_go_infra, ssh_upload_go_infra, ssh_exec_go_infra, systemd_restart_go_infra, health_check_http_go_infra]
uses_types: [ssh_conn_go_infra, DeployConfig_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DockerComposeDown(composeFile string, removeVolumes bool) (string, error)"
description: "Baja un stack docker-compose desde el archivo dado. Si removeVolumes es true elimina también los volumes declarados (-v). Retorna el stdout del comando."
tags: [docker, compose, down, infra]
tags: [docker, compose, down, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DockerComposeUp(composeFile string, detach bool) (string, error)"
description: "Levanta un stack docker-compose desde el archivo dado. Si detach es true ejecuta en background (-d). Retorna el stdout del comando."
tags: [docker, compose, up, infra]
tags: [docker, compose, up, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DockerRemoveNetwork(nameOrID string) error"
description: "Elimina una red Docker por nombre o ID."
tags: [docker, network, remove, infra]
tags: [docker, network, remove, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DockerVolumeCreate(name string) (string, error)"
description: "Crea un volume Docker con el nombre dado. Retorna el nombre del volume creado tal como lo confirma Docker."
tags: [docker, volume, create, infra]
tags: [docker, volume, create, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DockerVolumeList() ([]map[string]string, error)"
description: "Lista los volumes Docker disponibles localmente. Parsea la salida JSON de docker volume ls. Retorna slice de maps con campos Driver, Name, Scope, Labels, Mountpoint."
tags: [docker, volume, list, infra]
tags: [docker, volume, list, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DockerVolumeRemove(name string, force bool) error"
description: "Elimina un volume Docker por nombre. Si force es true fuerza la eliminación aunque esté en uso."
tags: [docker, volume, remove, delete, infra]
tags: [docker, volume, remove, delete, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DotenvLoad(path string) error"
description: "Parsea un archivo .env (KEY=VALUE), ignora comentarios # y lineas vacias, y llama os.Setenv para cada par. No sobreescribe variables ya presentes en el entorno. Soporta valores con comillas simples o dobles."
tags: [dotenv, env, config, os, infra]
tags: [dotenv, env, config, os, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func DuckDBOpen(path string) (*sql.DB, error)"
description: "Abre (o crea) una base de datos DuckDB. Path vacio o ':memory:' abre una base en memoria."
tags: [database, duckdb, connection, sql, analytics]
tags: [database, duckdb, connection, sql, analytics, pendiente-usar]
uses_functions: []
uses_types: [db_config_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func E2ERunChecks(checks []E2ECheck, workDir string) ([]CheckResult, error)"
description: "Ejecuta una lista de E2ECheck en orden y retorna un CheckResult por check. Soporta comandos de shell (via bash -c), health checks HTTP, y referencias a otros artefactos (Ref, actualmente skip). Los checks individuales que fallan no abortan la ejecucion del resto."
tags: [e2e, testing, infra, checks, validation, monitoring, pipeline]
tags: [e2e, testing, infra, checks, validation, monitoring, pipeline, pendiente-usar]
uses_functions: [health_check_http_go_infra]
uses_types: [E2ECheck_go_infra, CheckResult_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func EmailBuildHTML(from string, to []string, subject, bodyHTML string) EmailMessage"
description: "Construye un EmailMessage con cuerpo HTML. Retorna una nueva estructura sin CC, BCC, adjuntos ni headers custom."
tags: [email, smtp, html, builder]
tags: [email, smtp, html, builder, pendiente-usar]
uses_functions: []
uses_types: [EmailMessage_go_infra]
returns: [EmailMessage_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func EmailBuildText(from string, to []string, subject, bodyText string) EmailMessage"
description: "Construye un EmailMessage con cuerpo de texto plano. Retorna una nueva estructura sin CC, BCC, adjuntos ni headers custom."
tags: [email, smtp, text, builder]
tags: [email, smtp, text, builder, pendiente-usar]
uses_functions: []
uses_types: [EmailMessage_go_infra]
returns: [EmailMessage_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func EmailTemplateRender(tmplStr string, data map[string]any) (string, error)"
description: "Renderiza un Go text/template con el data map dado y retorna el string resultante. Falla si el template tiene sintaxis invalida o si la ejecucion produce un error."
tags: [email, template, render, text/template]
tags: [email, template, render, text/template, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func EmailWithAttachment(msg EmailMessage, att EmailAttachment) EmailMessage"
description: "Retorna una copia del EmailMessage con el adjunto añadido al final. No muta el mensaje original."
tags: [email, smtp, attachment, immutable, builder]
tags: [email, smtp, attachment, immutable, builder, pendiente-usar]
uses_functions: []
uses_types: [EmailMessage_go_infra, EmailAttachment_go_infra]
returns: [EmailMessage_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func EnvRequire(key string) (string, error)"
description: "Lee una variable de entorno con os.Getenv y retorna error descriptivo si esta vacia o no seteada. Fail-fast para variables obligatorias al arrancar la app."
tags: [env, config, os, infra, required]
tags: [env, config, os, infra, required, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func EnvRequireAll(keys []string) (map[string]string, error)"
description: "Verifica y retorna multiples variables de entorno. Todas las claves se comprueban aunque algunas fallen, acumulando los nombres de las faltantes en un unico error. Util para validacion exhaustiva al arranque."
tags: [env, config, os, infra, required, batch]
tags: [env, config, os, infra, required, batch, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func FileDelete(path string) error"
description: "Elimina un archivo del disco. Rechaza paths con \"..\" para evitar path traversal. Retorna error si el archivo no existe o si falla el remove."
tags: [file, delete, disk, storage, security, infra]
tags: [file, delete, disk, storage, security, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func FileServe(dir string, pathPrefix string, maxAge int) http.Handler"
description: "Retorna un http.Handler que sirve archivos estaticos desde dir, stripeando pathPrefix del URL. Setea Cache-Control con max-age. Rechaza paths con \"..\"."
tags: [http, file, serve, static, cache, security, infra]
tags: [http, file, serve, static, cache, security, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
@@ -0,0 +1,307 @@
package infra
import (
"crypto/sha1"
"database/sql"
"encoding/json"
"fmt"
"path/filepath"
"strings"
"time"
_ "github.com/mattn/go-sqlite3"
)
// ProposalDraft is a candidate proposal computed from call_monitor telemetry
// + registry static data. Persistence step writes it into registry.db.proposals
// with deterministic id (so re-runs are idempotent).
type ProposalDraft struct {
ID string `json:"id"`
Kind string `json:"kind"` // new_function | improve_function | deprecate_function | new_pipeline | ...
TargetID string `json:"target_id"` // existing function id (empty for new_function)
Title string `json:"title"`
Description string `json:"description"`
RuleID string `json:"rule_id"` // copy_detected | orphan | bug | wrapper_skipped | ...
Evidence map[string]any `json:"evidence"`
}
// GenerateProposalsFromTelemetry inspects call_monitor.operations.db and
// registry.db, applies the rules documented in issue 0085, and returns the
// proposed drafts. It does NOT write to any DB. Call PersistProposalDrafts
// to upsert into registry.db.proposals.
//
// Implemented rules (MVP):
// - copy_detected → improve_function (target = matched registry_id)
// - orphan → deprecate_function (calls_90d=0 AND writes_count=0
// AND no upstream consumer in registry.db.functions.uses_functions)
// - bug → improve_function (error_rate > 0.1 AND calls_total > 3)
// - wrapper_skip → improve_function (violations.function_id grouped, count > 3)
//
// Skipped rules (no data yet): perf_regression, test_flaky, e2e_blast_radius,
// pattern_inline (requires patterns table populated).
func GenerateProposalsFromTelemetry(registryRoot string) ([]ProposalDraft, error) {
regPath := filepath.Join(registryRoot, "registry.db")
monPath := filepath.Join(registryRoot, "projects", "fn_monitoring", "apps", "call_monitor", "operations.db")
regDB, err := sql.Open("sqlite3", regPath+"?_journal_mode=WAL")
if err != nil {
return nil, fmt.Errorf("open registry: %w", err)
}
defer regDB.Close()
monDB, err := sql.Open("sqlite3", monPath+"?_journal_mode=WAL")
if err != nil {
return nil, fmt.Errorf("open call_monitor: %w", err)
}
defer monDB.Close()
// Build set of "function_ids consumed by anything upstream" from registry
// (functions/apps/analysis.uses_functions JSON array). Used to filter orphan
// candidates.
consumers, err := loadConsumerSet(regDB)
if err != nil {
return nil, err
}
var drafts []ProposalDraft
// --- Rule 1: copy_detected ---
rows, err := monDB.Query(`SELECT app_file, app_function, registry_id, kind, similarity
FROM copied_code ORDER BY detected_at DESC`)
if err == nil {
for rows.Next() {
var appFile, appFunc, regID, kind string
var sim float64
if err := rows.Scan(&appFile, &appFunc, &regID, &kind, &sim); err != nil {
continue
}
drafts = append(drafts, ProposalDraft{
ID: deterministicID("copy_detected", regID, appFile+":"+appFunc),
Kind: "improve_function",
TargetID: regID,
Title: fmt.Sprintf("Replace inline copy with import: %s in %s::%s", regID, shortPath(appFile), appFunc),
Description: fmt.Sprintf("App `%s` declares `%s` with a body matching `%s` (%s, similarity %.2f). "+
"Replace the inline body with a registry import to centralize maintenance.",
appFile, appFunc, regID, kind, sim),
RuleID: "copy_detected",
Evidence: map[string]any{
"rule_id": "copy_detected",
"app_file": appFile,
"app_function": appFunc,
"registry_id": regID,
"kind": kind,
"similarity": sim,
},
})
}
rows.Close()
}
// --- Rule 2: orphan (deprecate_function) ---
// function_stats rows with no calls in 90d AND no writes.
// Cross-check against consumers set: if anybody imports it, NOT orphan.
rows, err = monDB.Query(`
SELECT function_id, calls_90d, writes_count
FROM function_stats
WHERE calls_90d = 0 AND writes_count = 0`)
if err == nil {
for rows.Next() {
var fnID string
var calls90d, writes int
if err := rows.Scan(&fnID, &calls90d, &writes); err != nil {
continue
}
if _, isConsumed := consumers[fnID]; isConsumed {
continue
}
drafts = append(drafts, ProposalDraft{
ID: deterministicID("orphan", fnID, ""),
Kind: "deprecate_function",
TargetID: fnID,
Title: "Deprecate orphan function: " + fnID,
Description: fmt.Sprintf("`%s` has no calls in 90d, no recent writes, and no upstream consumer "+
"declared in registry. Candidate for deprecation. Verify with `fn doctor unused` and manual review.", fnID),
RuleID: "orphan",
Evidence: map[string]any{
"rule_id": "orphan",
"calls_90d": calls90d,
"writes_count": writes,
},
})
}
rows.Close()
}
// --- Rule 3: bug (improve_function on error_rate) ---
rows, err = monDB.Query(`
SELECT function_id, calls_total, errors_total, error_rate
FROM function_stats
WHERE error_rate > 0.1 AND calls_total > 3`)
if err == nil {
for rows.Next() {
var fnID string
var calls, errs int
var rate float64
if err := rows.Scan(&fnID, &calls, &errs, &rate); err != nil {
continue
}
drafts = append(drafts, ProposalDraft{
ID: deterministicID("bug", fnID, ""),
Kind: "improve_function",
TargetID: fnID,
Title: fmt.Sprintf("Investigate high error rate: %s (%.0f%% over %d calls)", fnID, rate*100, calls),
Description: fmt.Sprintf("`%s` fails %.1f%% of the time across %d calls (%d errors). "+
"Check call_monitor.operations.db for recent error_class samples.",
fnID, rate*100, calls, errs),
RuleID: "bug",
Evidence: map[string]any{
"rule_id": "bug",
"calls_total": calls,
"errors_total": errs,
"error_rate": rate,
},
})
}
rows.Close()
}
// --- Rule 4: wrapper_skip (violations grouped) ---
rows, err = monDB.Query(`
SELECT function_id, COUNT(*) AS hits, MAX(severity) AS sev
FROM violations
WHERE function_id != ''
GROUP BY function_id
HAVING hits > 3`)
if err == nil {
for rows.Next() {
var fnID, sev string
var hits int
if err := rows.Scan(&fnID, &hits, &sev); err != nil {
continue
}
drafts = append(drafts, ProposalDraft{
ID: deterministicID("wrapper_skip", fnID, ""),
Kind: "improve_function",
TargetID: fnID,
Title: fmt.Sprintf("Wrapper skipped %d times: %s", hits, fnID),
Description: fmt.Sprintf("Agent bypassed the registry wrapper for `%s` %d times. "+
"API surface may be incomplete — check what callers needed and extend.", fnID, hits),
RuleID: "wrapper_skip",
Evidence: map[string]any{
"rule_id": "wrapper_skip",
"hits": hits,
"severity": sev,
},
})
}
rows.Close()
}
return drafts, nil
}
// PersistProposalDrafts upserts each draft into registry.db.proposals via
// INSERT OR IGNORE so previously-reviewed proposals (approved/rejected) are
// preserved untouched. Returns (newly_inserted, total_seen, error).
func PersistProposalDrafts(registryRoot string, drafts []ProposalDraft) (int, int, error) {
if len(drafts) == 0 {
return 0, 0, nil
}
regPath := filepath.Join(registryRoot, "registry.db")
conn, err := sql.Open("sqlite3", regPath+"?_journal_mode=WAL")
if err != nil {
return 0, 0, fmt.Errorf("open registry: %w", err)
}
defer conn.Close()
tx, err := conn.Begin()
if err != nil {
return 0, 0, err
}
stmt, err := tx.Prepare(`INSERT OR IGNORE INTO proposals
(id, kind, target_id, title, description, evidence, status, created_by, reviewed_by, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, 'pending', 'reactive_loop', '', ?, ?)`)
if err != nil {
_ = tx.Rollback()
return 0, 0, err
}
defer stmt.Close()
now := time.Now().UTC().Format(time.RFC3339)
inserted := 0
for _, d := range drafts {
evidence, _ := json.Marshal(d.Evidence)
res, err := stmt.Exec(d.ID, d.Kind, d.TargetID, d.Title, d.Description, string(evidence), now, now)
if err != nil {
_ = tx.Rollback()
return 0, 0, err
}
if n, _ := res.RowsAffected(); n > 0 {
inserted++
}
}
if err := tx.Commit(); err != nil {
return 0, 0, err
}
return inserted, len(drafts), nil
}
// ---- Helpers ----
// loadConsumerSet returns every function id that appears in any uses_functions
// JSON array across functions, apps, and analysis tables. Used to filter orphan
// candidates.
func loadConsumerSet(db *sql.DB) (map[string]struct{}, error) {
consumers := make(map[string]struct{})
queries := []string{
"SELECT uses_functions FROM functions WHERE uses_functions != '[]'",
"SELECT uses_functions FROM apps WHERE uses_functions != '[]'",
"SELECT uses_functions FROM analysis WHERE uses_functions != '[]'",
}
for _, q := range queries {
rows, err := db.Query(q)
if err != nil {
// Table may not exist (e.g. analysis); ignore.
continue
}
for rows.Next() {
var raw string
if err := rows.Scan(&raw); err != nil {
continue
}
var ids []string
if err := json.Unmarshal([]byte(raw), &ids); err != nil {
continue
}
for _, id := range ids {
consumers[id] = struct{}{}
}
}
rows.Close()
}
return consumers, nil
}
// deterministicID returns a stable proposal id derived from (rule, target, extra).
// Format: "auto_<rule>_<sha1-10>" so re-running the generator never inserts
// duplicates of the same logical proposal.
func deterministicID(rule, target, extra string) string {
key := rule + "|" + target + "|" + extra
h := sha1.Sum([]byte(key))
short := fmt.Sprintf("%x", h)[:10]
return "auto_" + rule + "_" + short
}
func shortPath(p string) string {
idx := strings.LastIndex(p, "/")
if idx == -1 || idx == len(p)-1 {
return p
}
parent := p[:idx]
pidx := strings.LastIndex(parent, "/")
if pidx == -1 {
return p
}
return p[pidx+1:]
}
@@ -0,0 +1,62 @@
---
name: generate_proposals_from_telemetry
lang: go
domain: infra
version: 0.1.0
purity: impure
kind: function
description: "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."
tags: [audit, reactive-loop, monitoring, proposals, registry]
signature: "func GenerateProposalsFromTelemetry(registryRoot string) ([]ProposalDraft, error)"
error_type: "error_go_core"
returns_optional: false
params:
- name: registryRoot
desc: "Path absoluto a raiz del fn_registry (contiene registry.db y projects/fn_monitoring/apps/call_monitor/operations.db)."
output: "Lista de ProposalDraft con kind, target_id, title, description, rule_id, evidence. NO escribe a DB — usar PersistProposalDrafts para persistir."
uses_functions: []
uses_types: []
imports:
- crypto/sha1
- database/sql
- encoding/json
- fmt
- path/filepath
- strings
- time
- github.com/mattn/go-sqlite3
example: |
import "fn-registry/functions/infra"
drafts, err := infra.GenerateProposalsFromTelemetry("/home/lucas/fn_registry")
if err != nil { ... }
inserted, total, err := infra.PersistProposalDrafts("/home/lucas/fn_registry", drafts)
fmt.Printf("%d/%d proposals nuevas\n", inserted, total)
file_path: "functions/infra/generate_proposals_from_telemetry.go"
tested: false
notes: |
Reglas MVP implementadas:
| Regla | Threshold | Proposal |
|---|---|---|
| copy_detected | match en copied_code | improve_function (target = registry_id matched) |
| orphan | calls_90d=0 AND writes_count=0 AND no upstream consumer | deprecate_function |
| bug | error_rate>0.1 AND calls_total>3 | improve_function |
| wrapper_skip | violations.function_id COUNT > 3 | improve_function |
Reglas pendientes (sin data aun):
- perf_regression: requiere p95 (no almacenado todavia).
- test_flaky: requiere test_runs poblado.
- e2e_blast_radius: requiere e2e_runs_fn poblado.
- pattern_inline: requiere patterns clustering (0085f).
Idempotencia: cada proposal tiene `id = auto_{rule}_{sha1-10}` deterministico
sobre (rule_id, target_id, extra). PersistProposalDrafts usa INSERT OR IGNORE
para no sobreescribir proposals ya revisadas (approved/rejected).
created_by='reactive_loop' para distinguir de proposals manuales.
Filtrado orphan: cruza con `uses_functions` JSON arrays de functions/apps/
analysis. Si alguien declara la funcion como dependencia, no se marca orfana
aunque tenga calls_90d=0 (uso intencional, no instrumentado por telemetria).
---
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func GoBuildBinary(projectDir, outputPath string, ldflags string, tags string) error"
description: "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."
tags: [go, build, binary, compile, infra, deploy]
tags: [go, build, binary, compile, infra, deploy, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func HttpDownloadFile(url, destPath string, headers map[string]string, timeout time.Duration) (int64, error)"
description: "Descarga url en destPath en streaming con io.Copy. Crea directorios con os.MkdirAll. Usa archivo temporal + rename para atomicidad (no deja archivo corrupto si falla). Retorna bytes escritos."
tags: [http, download, file, streaming, atomic, network, stdlib, infra]
tags: [http, download, file, streaming, atomic, network, stdlib, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func HttpGetJSON(url string, headers map[string]string, timeout time.Duration) (map[string]any, error)"
description: "GET request que espera JSON. Agrega Accept: application/json automaticamente. Retorna error con status code si >= 400. Siempre cierra body con defer."
tags: [http, json, get, client, network, stdlib, infra]
tags: [http, json, get, client, network, stdlib, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func HttpPostJSON(url string, body any, headers map[string]string, timeout time.Duration) (map[string]any, error)"
description: "POST request con body JSON serializado con json.Marshal. Agrega Content-Type: application/json y Accept: application/json. Retorna error con status code si >= 400."
tags: [http, json, post, client, network, stdlib, infra]
tags: [http, json, post, client, network, stdlib, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func JobCleanup(q *JobQueue, olderThan time.Duration) (int64, error)"
description: "Elimina jobs en estados terminales (completed, failed, dead) cuyo created_at sea mas antiguo que olderThan. Retorna el numero de filas eliminadas. Util para mantener la tabla compacta en apps de larga duracion."
tags: [job, queue, cleanup, delete, maintenance, sqlite, async, background, infra]
tags: [job, queue, cleanup, delete, maintenance, sqlite, async, background, infra, pendiente-usar]
uses_functions: []
uses_types: [job_queue_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func JobEnqueue(q *JobQueue, jobType string, payload string, opts ...EnqueueOption) (string, error)"
description: "Inserta un nuevo job en la cola con UUID generado, tipo, payload JSON y opciones. Retorna el UUID del job. Opciones: WithPriority, WithScheduledAt, WithMaxAttempts."
tags: [job, queue, enqueue, insert, async, background, infra, sqlite]
tags: [job, queue, enqueue, insert, async, background, infra, sqlite, pendiente-usar]
uses_functions: []
uses_types: [job_queue_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func JobQueueCreate(db *sql.DB, tableName string) (*JobQueue, error)"
description: "Crea (o verifica) la tabla de jobs con indices en SQLite, activa WAL mode, y retorna un *JobQueue listo para usar. tableName puede ser cualquier identificador SQLite valido (default: 'jobs')."
tags: [job, queue, sqlite, async, background, infra, create, schema]
tags: [job, queue, sqlite, async, background, infra, create, schema, pendiente-usar]
uses_functions: []
uses_types: [job_queue_go_infra]
returns: [job_queue_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func JobStatusSummary(counts map[string]int) string"
description: "Formatea un mapa de status→conteo en un resumen legible. Siempre incluye los cinco estados canonicos en orden fijo: pending, running, completed, failed, dead."
tags: [job, queue, status, summary, format, pure, infra]
tags: [job, queue, status, summary, format, pure, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func JobWorkerPool(ctx context.Context, q *JobQueue, n int, handler JobHandler, opts ...WorkerOption) error"
description: "Lanza n workers concurrentes que comparten la misma cola y handler. Usa errgroup para concurrencia estructurada y soporta graceful shutdown via context. Bloquea hasta que todos los workers terminen."
tags: [job, queue, worker, pool, concurrency, errgroup, async, background, infra, graceful-shutdown]
tags: [job, queue, worker, pool, concurrency, errgroup, async, background, infra, graceful-shutdown, pendiente-usar]
uses_functions: [job_worker_go_infra]
uses_types: [job_queue_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func JWTMiddleware(secret string) Middleware"
description: "Middleware HTTP que extrae el JWT del header Authorization: Bearer y valida con JWTValidate. Inyecta las claims en el context del request (recuperables con JWTClaimsFromContext). Responde 401 si falta el header, formato incorrecto o token invalido."
tags: [jwt, auth, middleware, http, server, infra]
tags: [jwt, auth, middleware, http, server, infra, pendiente-usar]
uses_functions: [jwt_validate_go_infra, http_error_response_go_infra]
uses_types: [JWTClaims_go_infra, Middleware_go_infra, HTTPError_go_infra]
returns: [Middleware_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func LogDebug(logger *Logger, msg string, fields ...any)"
description: "Emite un log a nivel debug en el Logger. Los fields son pares key-value variadicos. Si el nivel del logger es mayor que Debug, el mensaje se descarta silenciosamente."
tags: [logging, log, debug, slog, infra]
tags: [logging, log, debug, slog, infra, pendiente-usar]
uses_functions: []
uses_types: [Logger_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func LogError(logger *Logger, msg string, fields ...any)"
description: "Emite un log a nivel error en el Logger. Los fields son pares key-value variadicos. Nivel maximo de severidad, siempre se emite salvo que el logger tenga un handler que lo filtre explicitamente."
tags: [logging, log, error, slog, infra]
tags: [logging, log, error, slog, infra, pendiente-usar]
uses_functions: []
uses_types: [Logger_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func LogWarn(logger *Logger, msg string, fields ...any)"
description: "Emite un log a nivel warn en el Logger. Los fields son pares key-value variadicos. Indica situaciones anomalas que no impiden el funcionamiento del sistema."
tags: [logging, log, warn, slog, infra]
tags: [logging, log, warn, slog, infra, pendiente-usar]
uses_functions: []
uses_types: [Logger_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func LoggerMiddleware(logger *Logger) Middleware"
description: "Retorna un Middleware HTTP que emite un log estructurado por cada request. Cada request produce una entrada info con method, path, status y duration_ms. Respeta los campos contextuales del Logger (app, version, request_id...)."
tags: [logging, log, slog, middleware, http, server, infra]
tags: [logging, log, slog, middleware, http, server, infra, pendiente-usar]
uses_functions: [log_info_go_infra]
uses_types: [Logger_go_infra, Middleware_go_infra]
returns: [Middleware_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func LoggerNew(level LogLevel, output io.Writer, format string) (*Logger, error)"
description: "Crea un Logger estructurado sobre log/slog con nivel, destino y formato configurables. Formato soportado: json o text. Si output es nil cae en os.Stderr."
tags: [logging, log, slog, logger, infra]
tags: [logging, log, slog, logger, infra, pendiente-usar]
uses_functions: []
uses_types: [Logger_go_infra, LogLevel_go_infra]
returns: [Logger_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func LoggerWith(logger *Logger, fields map[string]any) *Logger"
description: "Retorna una copia del Logger con campos contextuales adicionales. No muta el logger original — los campos se apilan sobre los existentes y aparecen en cada entrada del nuevo logger."
tags: [logging, log, slog, logger, context, pure, infra]
tags: [logging, log, slog, logger, context, pure, infra, pendiente-usar]
uses_functions: []
uses_types: [Logger_go_infra]
returns: [Logger_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseAuth(baseURL, email, password string) (MetabaseClient, error)"
description: "Autentica contra la API de Metabase con email y password. Retorna un MetabaseClient con session token valido por 14 dias (configurable con MAX_SESSION_AGE en Metabase). Endpoint: POST /api/session."
tags: [metabase, auth, session, api]
tags: [metabase, auth, session, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: [MetabaseClient_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseCreateCard(client MetabaseClient, name string, datasetQuery map[string]any, display string, collectionID int, description string) (map[string]any, error)"
description: "Crea una nueva card/pregunta en Metabase con query SQL nativa o MBQL. Endpoint: POST /api/card."
tags: [metabase, card, question, create, api]
tags: [metabase, card, question, create, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseCreateDashboard(client MetabaseClient, name, description string, collectionID int) (map[string]any, error)"
description: "Crea un nuevo dashboard vacio en Metabase. Para agregar cards usar MetabaseUpdateDashboard con el campo dashcards. Endpoint: POST /api/dashboard."
tags: [metabase, dashboard, create, api]
tags: [metabase, dashboard, create, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseCreateUser(client MetabaseClient, firstName, lastName, email, password string, groupIDs []int) (map[string]any, error)"
description: "Crea un nuevo usuario en Metabase. Si no se provee password, Metabase envia email de invitacion. Requiere permisos de superusuario. Endpoint: POST /api/user."
tags: [metabase, user, create, api]
tags: [metabase, user, create, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseDeactivateUser(client MetabaseClient, userID int) error"
description: "Desactiva (soft-delete) un usuario en Metabase. El usuario no se elimina permanentemente, solo se marca como inactivo. Para reactivar, usar PUT /api/user/:id/reactivate. Endpoint: DELETE /api/user/:id."
tags: [metabase, user, delete, deactivate, api]
tags: [metabase, user, delete, deactivate, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseDeleteCard(client MetabaseClient, cardID int) error"
description: "Elimina permanentemente una card/pregunta de Metabase. Accion irreversible. Para soft-delete usar MetabaseUpdateCard con archived:true. Endpoint: DELETE /api/card/:id."
tags: [metabase, card, question, delete, api]
tags: [metabase, card, question, delete, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseDeleteDashboard(client MetabaseClient, dashboardID int) error"
description: "Elimina permanentemente un dashboard de Metabase. Accion irreversible. Para soft-delete usar MetabaseUpdateDashboard con archived:true. Endpoint: DELETE /api/dashboard/:id."
tags: [metabase, dashboard, delete, api]
tags: [metabase, dashboard, delete, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseExecuteCard(client MetabaseClient, cardID int, parameters []map[string]any) (map[string]any, error)"
description: "Ejecuta la query de una card/pregunta guardada en Metabase y retorna los resultados. Soporta parametros para queries parametrizadas. Endpoint: POST /api/card/:id/query."
tags: [metabase, card, question, execute, query, api]
tags: [metabase, card, question, execute, query, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseExecuteQuery(client MetabaseClient, databaseID int, sql string, maxResults int) (map[string]any, error)"
description: "Ejecuta una query SQL ad-hoc contra una database de Metabase sin guardarla como card. Util para consultas rapidas y exploracion. Endpoint: POST /api/dataset."
tags: [metabase, query, execute, sql, dataset, api]
tags: [metabase, query, execute, sql, dataset, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseGetCard(client MetabaseClient, cardID int) (map[string]any, error)"
description: "Obtiene los detalles completos de una card/pregunta de Metabase por su ID. Incluye la query, visualizacion y metadata. Endpoint: GET /api/card/:id."
tags: [metabase, card, question, get, api]
tags: [metabase, card, question, get, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseGetDashboard(client MetabaseClient, dashboardID int) (map[string]any, error)"
description: "Obtiene un dashboard completo de Metabase incluyendo todas sus dashcards (cards posicionadas en el dashboard), tabs y parametros. Endpoint: GET /api/dashboard/:id."
tags: [metabase, dashboard, get, api]
tags: [metabase, dashboard, get, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseGetUser(client MetabaseClient, userID int) (map[string]any, error)"
description: "Obtiene los detalles de un usuario de Metabase por su ID numerico. Endpoint: GET /api/user/:id."
tags: [metabase, user, get, api]
tags: [metabase, user, get, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseListCards(client MetabaseClient, filter string, modelID int) ([]map[string]any, error)"
description: "Lista preguntas/cards de Metabase con filtro opcional. Retorna array de cards. Endpoint: GET /api/card."
tags: [metabase, card, question, list, api]
tags: [metabase, card, question, list, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseListDashboards(client MetabaseClient, filter string) ([]map[string]any, error)"
description: "Lista dashboards de Metabase con filtro opcional. Retorna array de dashboards resumidos (sin dashcards). Endpoint: GET /api/dashboard."
tags: [metabase, dashboard, list, api]
tags: [metabase, dashboard, list, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseListUsers(client MetabaseClient, status, query string, limit, offset int) (map[string]any, error)"
description: "Lista usuarios de una instancia Metabase con filtros opcionales por estado, nombre/email y paginacion. Endpoint: GET /api/user. Requiere permisos de superusuario."
tags: [metabase, user, list, api]
tags: [metabase, user, list, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseUpdateCard(client MetabaseClient, cardID int, fields map[string]any) (map[string]any, error)"
description: "Actualiza campos de una card/pregunta en Metabase. Solo se modifican los campos incluidos en el map. Endpoint: PUT /api/card/:id."
tags: [metabase, card, question, update, api]
tags: [metabase, card, question, update, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseUpdateDashboard(client MetabaseClient, dashboardID int, fields map[string]any) (map[string]any, error)"
description: "Actualiza un dashboard en Metabase incluyendo metadata, cards y tabs. El campo dashcards representa el estado completo deseado: cards nuevas con ID negativo, existentes con ID positivo, omitidas se eliminan. Endpoint: PUT /api/dashboard/:id."
tags: [metabase, dashboard, update, cards, api]
tags: [metabase, dashboard, update, cards, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MetabaseUpdateUser(client MetabaseClient, userID int, fields map[string]any) (map[string]any, error)"
description: "Actualiza campos de un usuario en Metabase. Solo se modifican los campos incluidos en el map. Requiere permisos de superusuario. Endpoint: PUT /api/user/:id."
tags: [metabase, user, update, api]
tags: [metabase, user, update, api, pendiente-usar]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MigrationCreate(dir string, name string) (string, error)"
description: "Crea un archivo .sql de migracion con template -- +up / -- +down en el directorio indicado. Calcula automaticamente el siguiente numero de version escaneando archivos .sql existentes. Retorna el path absoluto del archivo creado."
tags: [migration, database, sql, schema, sqlite, create, file]
tags: [migration, database, sql, schema, sqlite, create, file, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MigrationDown(db *sql.DB, n int) ([]Migration, error)"
description: "Revierte las ultimas n migraciones aplicadas ejecutando su down_sql guardado en la tabla _migrations. Las reversiones ocurren en orden inverso de version (la mas alta primero). Cada reversion corre en su propia transaccion. Retorna las migraciones revertidas."
tags: [migration, database, sql, schema, sqlite, rollback, down]
tags: [migration, database, sql, schema, sqlite, rollback, down, pendiente-usar]
uses_functions: []
uses_types: [Migration_go_infra]
returns: [Migration_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func MigrationGetStatus(db *sql.DB, dir string) ([]MigrationStatus, error)"
description: "Cruza los archivos .sql del directorio con los registros en _migrations y retorna una lista ordenada por version con el estado de cada migracion (applied/pending). Migraciones en BD pero sin archivo en disco se marcan como orphaned. Si _migrations no existe aun, todas las migraciones del directorio aparecen como pending."
tags: [migration, database, sql, schema, sqlite, status, list]
tags: [migration, database, sql, schema, sqlite, status, list, pendiente-usar]
uses_functions: [migration_parse_go_infra]
uses_types: [MigrationStatus_go_infra]
returns: [MigrationStatus_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func MigrationValidate(migrations []Migration) []string"
description: "Verifica que una secuencia de migraciones sea valida: versiones secuenciales sin huecos comenzando en 1, sin duplicados, con up_sql y nombre no vacios. Retorna lista de errores (vacia si todo OK)."
tags: [migration, database, sql, schema, sqlite, validate]
tags: [migration, database, sql, schema, sqlite, validate, pendiente-usar]
uses_functions: []
uses_types: [Migration_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func NordVPNContainerRun(opts NordVPNContainerRunOpts) (string, error)"
description: "Ejecuta un container Docker cuyo trafico pasa por el gateway NordVPN usando --network=container:<gateway>. El container hereda la IP y tunel VPN del gateway."
tags: [vpn, nordvpn, docker, container, run, infra, network]
tags: [vpn, nordvpn, docker, container, run, infra, network, pendiente-usar]
uses_functions: ["docker_run_container_go_infra"]
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func NordVPNContainerStart(opts NordVPNContainerOpts) (string, error)"
description: "Levanta un container Docker con NordVPN como gateway de red. Otros containers pueden rutear su trafico a traves de este con --network=container:<name>. Espera hasta 30s a que el tunel este activo."
tags: [vpn, nordvpn, docker, container, gateway, infra, network]
tags: [vpn, nordvpn, docker, container, gateway, infra, network, pendiente-usar]
uses_functions: ["docker_run_container_go_infra", "docker_remove_container_go_infra", "docker_container_logs_go_infra"]
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func NordVPNContainerStop(gateway string, clientNames ...string) error"
description: "Detiene y elimina el container gateway NordVPN y opcionalmente los containers cliente que usan su red."
tags: [vpn, nordvpn, docker, container, stop, cleanup, infra]
tags: [vpn, nordvpn, docker, container, stop, cleanup, infra, pendiente-usar]
uses_functions: ["docker_stop_container_go_infra", "docker_remove_container_go_infra"]
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func NotifyTelegram(botToken string, chatID string, text string, parseMode string) error"
description: "Envía un mensaje a un chat de Telegram via Bot API. Útil para alertas de deploy, fallos de assertions y eventos del bucle reactivo."
tags: [notify, telegram, alert, http]
tags: [notify, telegram, alert, http, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func Oauth2AuthURL(config OAuthConfig, state string) string"
description: "Construye la URL de autorizacion OAuth2 a partir de la config. Funcion pura que concatena el AuthURL del proveedor con los query params (client_id, redirect_uri, response_type=code, scope, state)."
tags: [oauth, oauth2, auth, url, infra]
tags: [oauth, oauth2, auth, url, infra, pendiente-usar]
uses_functions: []
uses_types: [OAuthConfig_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func Oauth2Exchange(config OAuthConfig, code string) (OAuthTokens, error)"
description: "Intercambia un authorization code por tokens OAuth2. POST al TokenURL del proveedor con grant_type=authorization_code y las credenciales del cliente. Retorna OAuthTokens con AccessToken, RefreshToken y ExpiresAt calculado."
tags: [oauth, oauth2, auth, token, exchange, http, infra]
tags: [oauth, oauth2, auth, token, exchange, http, infra, pendiente-usar]
uses_functions: []
uses_types: [OAuthConfig_go_infra, OAuthTokens_go_infra]
returns: [OAuthTokens_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func Oauth2Refresh(config OAuthConfig, refreshToken string) (OAuthTokens, error)"
description: "Renueva un access token OAuth2 usando el refresh token. POST al TokenURL con grant_type=refresh_token. Conserva el refresh token original si el proveedor no devuelve uno nuevo."
tags: [oauth, oauth2, auth, token, refresh, http, infra]
tags: [oauth, oauth2, auth, token, refresh, http, infra, pendiente-usar]
uses_functions: []
uses_types: [OAuthConfig_go_infra, OAuthTokens_go_infra]
returns: [OAuthTokens_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: pure
signature: "func ParseNordVPNStatus(output string) NordVPNStatus"
description: "Parsea la salida de texto de nordvpn status a un struct tipado. Elimina codigos ANSI y normaliza claves."
tags: [vpn, nordvpn, parser, pure, infra]
tags: [vpn, nordvpn, parser, pure, infra, pendiente-usar]
uses_functions: []
uses_types: ["NordVPNStatus_go_infra"]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func PcLocationsDrift(registryRoot string, pcID string) ([]LocationDrift, error)"
description: "Compara la tabla pc_locations contra el estado real del disco para el PC actual. Detecta tres tipos de drift: carpetas activas que no existen, entradas missing cuya carpeta reaparece, y artefactos indexados en disco sin fila en pc_locations."
tags: [doctor, sync, pc_locations, drift]
tags: [doctor, sync, pc_locations, drift, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func PdfSimpleReport(title string, sections []ReportSection, outputPath string) (string, error)"
description: "Genera un PDF simple con titulo, secciones de texto y tablas opcionales. Stub activo: requiere go-pdf/fpdf que no esta en el modulo. Para PDFs desde Go añadir la dependencia; para casos complejos usar el builder Python."
tags: [pdf, report, go, stub, infra]
tags: [pdf, report, go, stub, infra, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func PostgresOpen(host string, port int, user, password, dbname string, sslmode string) (*sql.DB, error)"
description: "Conecta a PostgreSQL construyendo el DSN desde parametros individuales. sslmode por defecto 'disable' si vacio."
tags: [database, postgres, postgresql, connection, sql]
tags: [database, postgres, postgresql, connection, sql, pendiente-usar]
uses_functions: []
uses_types: [db_config_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func ProposalFromFailure(registryDB string, appID string, results []CheckResult, executionID string) ([]string, error)"
description: "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."
tags: [proposals, reactive-loop, e2e, monitoring, registry, infra]
tags: [proposals, reactive-loop, e2e, monitoring, registry, infra, pendiente-usar]
uses_functions: [random_hex_id_go_core, sqlite_open_go_infra]
uses_types: [CheckResult_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func RateLimitMiddleware(rl *RateLimiter) Middleware"
description: "Middleware HTTP que aplica rate limiting por IP del cliente. Extrae IP de X-Forwarded-For, X-Real-IP o RemoteAddr. Responde 429 con headers Retry-After y X-RateLimit-* cuando se supera el limite."
tags: [rate_limit, http, middleware, ip, server, infra]
tags: [rate_limit, http, middleware, ip, server, infra, pendiente-usar]
uses_functions: [rate_limiter_by_key_go_infra]
uses_types: [RateLimiter_go_infra, Middleware_go_infra]
returns: [Middleware_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func RateLimiterCleanup(rl *RateLimiter, maxAge time.Duration, interval time.Duration) func()"
description: "Arranca una goroutine que purga periodicamente las entries del RateLimiter sin actividad reciente. Retorna una funcion para detener el GC sin leaks de goroutine."
tags: [rate_limit, http, middleware, cleanup, gc, goroutine, infra]
tags: [rate_limit, http, middleware, cleanup, gc, goroutine, infra, pendiente-usar]
uses_functions: []
uses_types: [RateLimiter_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func RateLimiterNew(rate float64, burst int) *RateLimiter"
description: "Crea un RateLimiter token-bucket in-memory vacio. rate son los tokens recargados por segundo, burst es la capacidad maxima del bucket. Valores <= 0 se sustituyen por 1 como minimo seguro."
tags: [rate_limit, http, middleware, token_bucket, infra]
tags: [rate_limit, http, middleware, token_bucket, infra, pendiente-usar]
uses_functions: []
uses_types: [RateLimiter_go_infra]
returns: [RateLimiter_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func RBACMiddleware(roles []Role, required Permission) Middleware"
description: "Middleware HTTP que verifica que el usuario autenticado tenga un permiso concreto. Lee las claims del context (puestas por JWTMiddleware), extrae el rol de claims.Custom[role] y evalua con RBACCheck. Responde 403 si no tiene permiso, 401 si no hay claims."
tags: [rbac, auth, middleware, http, server, infra]
tags: [rbac, auth, middleware, http, server, infra, pendiente-usar]
uses_functions: [rbac_check_go_infra, http_error_response_go_infra]
uses_types: [Role_go_infra, Permission_go_infra, Middleware_go_infra, HTTPError_go_infra]
returns: [Middleware_go_infra]
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func S3Download(cfg S3Config, key string, dst io.Writer) error"
description: "STUB. Descarga el objeto key desde un bucket S3-compatible y escribe su contenido en dst. Permite streaming directo a disco o a un HTTP response. Requiere github.com/aws/aws-sdk-go-v2."
tags: [s3, download, storage, cloud, stub, infra]
tags: [s3, download, storage, cloud, stub, infra, pendiente-usar]
uses_functions: []
uses_types: [S3Config_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func S3PresignURL(cfg S3Config, key string, expiry time.Duration) (string, error)"
description: "STUB. Genera una URL presignada para download (GET) del objeto key en un bucket S3-compatible, valida durante expiry. Util para descargas directas sin pasar por el servidor. Requiere github.com/aws/aws-sdk-go-v2."
tags: [s3, presign, url, storage, cloud, stub, infra]
tags: [s3, presign, url, storage, cloud, stub, infra, pendiente-usar]
uses_functions: []
uses_types: [S3Config_go_infra]
returns: []
+1 -1
View File
@@ -7,7 +7,7 @@ version: "1.0.0"
purity: impure
signature: "func S3Upload(cfg S3Config, key string, data io.Reader, contentType string) error"
description: "STUB. Sube data a un bucket S3-compatible (AWS S3, MinIO, etc) bajo la key indicada con el Content-Type dado. La implementacion real requiere github.com/aws/aws-sdk-go-v2."
tags: [s3, upload, storage, cloud, stub, infra]
tags: [s3, upload, storage, cloud, stub, infra, pendiente-usar]
uses_functions: []
uses_types: [S3Config_go_infra]
returns: []

Some files were not shown because too many files have changed in this diff Show More