chore: auto-commit (3 archivos)

- tool_create_function.go
- naming.go
- naming_test.go

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 16:33:23 +02:00
parent e69b6ab6de
commit 2740f4a41a
3 changed files with 176 additions and 2 deletions
+120
View File
@@ -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
}
+51
View File
@@ -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)
}
}
}
+5 -2
View File
@@ -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":