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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user