From 2740f4a41acaeac2eac745c5d6c467d2a7597ef1 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 16 May 2026 16:33:23 +0200 Subject: [PATCH] chore: auto-commit (3 archivos) - tool_create_function.go - naming.go - naming_test.go Co-Authored-By: Claude Opus 4.7 (1M context) --- naming.go | 120 ++++++++++++++++++++++++++++++++++++++++ naming_test.go | 51 +++++++++++++++++ tool_create_function.go | 7 ++- 3 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 naming.go create mode 100644 naming_test.go diff --git a/naming.go b/naming.go new file mode 100644 index 0000000..e7d747e --- /dev/null +++ b/naming.go @@ -0,0 +1,120 @@ +package main + +import ( + "fmt" + "strings" +) + +// canonicalDomains lists registry domains accepted by fn_create_function. +// Keep in sync with `mcp__registry__fn_list_domains` and `.claude/rules/ids_naming.md`. +var canonicalDomains = map[string]bool{ + "browser": true, + "core": true, + "cybersecurity": true, + "datascience": true, + "docker": true, + "finance": true, + "gamedev": true, + "geo": true, + "gfx": true, + "infra": true, + "metabase": true, + "ml": true, + "notebook": true, + "pipelines": true, + "shell": true, + "tui": true, + "ui": true, + "viz": true, +} + +// actionVerbs is the allowlist of verbs that make a function name predictable. +// See .claude/rules/ids_naming.md. Add new recurring verbs here when introduced. +var actionVerbs = map[string]bool{} + +// acronymExceptions are nouns accepted as standalone names because the registry +// has historical precedent (math/stats indicators). +var acronymExceptions = map[string]bool{ + "sma": true, "ema": true, "rsi": true, "vwap": true, "adx": true, + "macd": true, "atr": true, "obv": true, "bollinger": true, +} + +func init() { + for _, v := range []string{ + "get", "set", "list", "find", "search", "show", "read", "load", "fetch", "scan", "query", "lookup", + "parse", "format", "encode", "decode", "marshal", "unmarshal", "serialize", "deserialize", + "validate", "check", "ensure", "verify", "audit", "diagnose", "test", "match", + "filter", "map", "reduce", "sort", "group", "count", "sum", "aggregate", "compute", "calculate", + "score", "rank", "cluster", "classify", "detect", + "init", "create", "make", "build", "generate", "scaffold", "install", "setup", "configure", "register", + "add", "insert", "append", "prepend", "update", "upsert", "modify", "edit", "patch", "replace", + "delete", "remove", "clear", "drop", "prune", "clean", + "copy", "move", "rename", "sync", "clone", "extract", "inject", "import", "export", + "send", "post", "put", "call", "dispatch", "exec", "run", "launch", "start", "stop", "kill", + "restart", "redeploy", "deploy", + "open", "close", "connect", "disconnect", "login", "logout", "authenticate", + "enable", "disable", "toggle", "lock", "unlock", + "propose", "promote", "deprecate", "approve", "reject", "apply", + "emit", "render", "draw", "paint", "serve", "host", + "pull", "push", "checkout", "commit", "tag", "merge", "rebase", + "watch", "monitor", "observe", "log", "trace", "profile", "benchmark", + "snapshot", "backup", "restore", "archive", "compress", "decompress", + "hash", "encrypt", "decrypt", "sign", + "taskkill", "recopile", "recopilate", "vault", + "gather", "collect", "fold", "head", "tail", "take", "chunk", "batch", + "debounce", "throttle", "retry", "await", "sleep", "ping", + "prime", "warm", "refresh", "invalidate", "reload", "reset", "rollback", + "fork", "spawn", "daemon", + "plot", "capture", "replay", + "slice", "window", "split", "join", "concat", "flatten", "unflatten", "transpose", "reverse", + "shuffle", "sample", "dedup", "distinct", "unique", + "audit", "verify", "lint", "format", + "tally", "bucket", "histogram", + } { + actionVerbs[v] = true + } +} + +// validateName checks that name is predictable per ids_naming.md: +// - snake_case +// - contains an action verb token, or is a recognized acronym +// Components (kind=component) skip the verb check. +func validateName(name, kind string) error { + if name == "" { + return fmt.Errorf("name is empty") + } + if !isSnakeCase(name) { + return fmt.Errorf("name must be snake_case (lowercase + digits + underscores), got %q", name) + } + if kind == "component" || kind == "type" { + return nil + } + tokens := strings.Split(name, "_") + for _, t := range tokens { + if actionVerbs[t] { + return nil + } + } + // single-token acronym exception + if len(tokens) == 1 && acronymExceptions[tokens[0]] { + return nil + } + return fmt.Errorf( + "naming: name %q lacks action verb. Add a verb token (get_X, X_lookup, parse_X, ...). See .claude/rules/ids_naming.md", + name, + ) +} + +// validateDomain checks that domain is in the canonical list. +func validateDomain(domain string) error { + if domain == "" { + return fmt.Errorf("domain is empty") + } + if !canonicalDomains[domain] { + return fmt.Errorf( + "naming: domain %q not in canonical list. Allowed: browser, core, cybersecurity, datascience, docker, finance, gamedev, geo, gfx, infra, metabase, ml, notebook, pipelines, shell, tui, ui, viz. Add new domain to CLAUDE.md + apps/registry_mcp/naming.go first.", + domain, + ) + } + return nil +} diff --git a/naming_test.go b/naming_test.go new file mode 100644 index 0000000..21f58be --- /dev/null +++ b/naming_test.go @@ -0,0 +1,51 @@ +package main + +import "testing" + +func TestValidateName(t *testing.T) { + cases := []struct { + name string + kind string + wantErr bool + }{ + {"filter_slice", "function", false}, + {"bank_login", "function", false}, + {"metabase_get_dashboard", "function", false}, + {"sma", "function", false}, // acronym exception + {"slice", "function", false}, // 'slice' is verb in allowlist + {"data", "function", true}, // bare noun + {"user", "function", true}, // bare noun + {"foobarbaz", "function", true}, // unknown single token + {"chat_panel", "component", false}, + {"result_go_core", "type", false}, + {"Slice", "function", true}, // not snake_case + {"filter-slice", "function", true}, + {"", "function", true}, + } + for _, c := range cases { + err := validateName(c.name, c.kind) + if (err != nil) != c.wantErr { + t.Errorf("validateName(%q,%q) err=%v wantErr=%v", c.name, c.kind, err, c.wantErr) + } + } +} + +func TestValidateDomain(t *testing.T) { + cases := []struct { + domain string + wantErr bool + }{ + {"core", false}, + {"infra", false}, + {"viz", false}, + {"bogus", true}, + {"", true}, + {"Core", true}, + } + for _, c := range cases { + err := validateDomain(c.domain) + if (err != nil) != c.wantErr { + t.Errorf("validateDomain(%q) err=%v wantErr=%v", c.domain, err, c.wantErr) + } + } +} diff --git a/tool_create_function.go b/tool_create_function.go index b0c8b19..71c063a 100644 --- a/tool_create_function.go +++ b/tool_create_function.go @@ -141,8 +141,11 @@ func validateCreateFunctionArgs(a *createFunctionArgs) error { if a.Name == "" || a.Lang == "" || a.Domain == "" || a.Description == "" || a.Code == "" { return fmt.Errorf("name, lang, domain, description, code are required") } - if !isSnakeCase(a.Name) { - return fmt.Errorf("name must be snake_case (lowercase + digits + underscores), got %q", a.Name) + if err := validateName(a.Name, a.Kind); err != nil { + return err + } + if err := validateDomain(a.Domain); err != nil { + return err } switch a.Lang { case "go", "py", "bash", "ts":