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