package intent import ( "context" "strings" "testing" ) func TestR2NFKCFoldsFullwidthButNotTraditional(t *testing.T) { spec := IntentSpec{ ID: "demo.greet.hello", Domain: "demo", Version: 1, Matcher: MatchSpec{IncludesAny: []string{"hello"}}, } b := NewRegistryBuilder(Options{NFKC: true}) if err := b.Register(spec); err != nil { t.Fatal(err) } reg, err := b.Build() if err != nil { t.Fatal(err) } rec := NewRecognizer(Options{NFKC: true}, nil, nil) res, err := rec.Recognize(context.Background(), "Hello", reg.Snapshot()) if err != nil || res.Top == nil || res.Top.ID != spec.ID { t.Fatalf("fullwidth hello: %+v %v", res, err) } trad, err := rec.Recognize(context.Background(), "寫入", reg.Snapshot()) if err != nil { t.Fatal(err) } if trad.Branch != BranchFallback { t.Fatalf("NFKC must not convert traditional Chinese, got %+v", trad) } } func TestR2SpecByIDDeepCopiesMetadata(t *testing.T) { b := NewRegistryBuilder(Options{}) if err := b.Register(IntentSpec{ ID: "demo.meta.copy", Domain: "demo", Version: 1, Matcher: MatchSpec{IncludesAny: []string{"复制"}}, Metadata: map[string]string{"guidance": "keep"}, }); err != nil { t.Fatal(err) } reg, err := b.Build() if err != nil { t.Fatal(err) } got, ok := reg.Snapshot().SpecByID("demo.meta.copy") if !ok { t.Fatal("missing spec") } got.Metadata["guidance"] = "mutated" again, _ := reg.Snapshot().SpecByID("demo.meta.copy") if again.Metadata["guidance"] != "keep" { t.Fatalf("snapshot metadata changed: %q", again.Metadata["guidance"]) } } func TestR2CatalogHidesPrivateMetadataAndHandlerInMarkdown(t *testing.T) { b := NewRegistryBuilder(Options{}) if err := b.Register(IntentSpec{ ID: "demo.catalog.public", Domain: "demo", Version: 1, Handler: "secret.handler", Matcher: MatchSpec{IncludesAny: []string{"目录"}}, Metadata: map[string]string{ "blueprint": "bp", "token": "do-not-leak", }, }); err != nil { t.Fatal(err) } reg, err := b.Build() if err != nil { t.Fatal(err) } snap := reg.Snapshot() cat := snap.Catalog() if len(cat) != 1 { t.Fatalf("catalog: %+v", cat) } if cat[0].Metadata["blueprint"] != "bp" || cat[0].Metadata["token"] != "" { t.Fatalf("metadata allowlist: %+v", cat[0].Metadata) } md := snap.CatalogMarkdown() if strings.Contains(md, "secret.handler") || strings.Contains(md, "do-not-leak") || strings.Contains(md, "handler:") { t.Fatalf("markdown leaked private fields:\n%s", md) } } func TestR2EvalRulesDocumentsMaxSpecs(t *testing.T) { if DefaultMaxSpecs != 2048 { t.Fatalf("linear scan cap changed: %d", DefaultMaxSpecs) } b := NewRegistryBuilder(Options{MaxSpecs: 1}) if err := b.Register( IntentSpec{ID: "demo.cap.a", Domain: "demo", Version: 1, Matcher: MatchSpec{IncludesAny: []string{"a"}}}, IntentSpec{ID: "demo.cap.b", Domain: "demo", Version: 1, Matcher: MatchSpec{IncludesAny: []string{"b"}}}, ); err != nil { t.Fatal(err) } if _, err := b.Build(); err == nil { t.Fatal("build must reject specs above MaxSpecs") } } func TestR2OverlappingSlotsAreAmbiguous(t *testing.T) { b := NewRegistryBuilder(Options{}) if err := b.Register(IntentSpec{ ID: "demo.slot.overlap", Domain: "demo", Version: 1, Matcher: MatchSpec{IncludesAny: []string{"abcd"}}, Slots: []SlotSpec{ {Key: "left", Pattern: "(abc)"}, {Key: "right", Pattern: "(bcd)"}, }, }); err != nil { t.Fatal(err) } reg, err := b.Build() if err != nil { t.Fatal(err) } res, err := NewRecognizer(Options{}, nil, nil).Recognize(context.Background(), "abcd", reg.Snapshot()) if err != nil || res.Top == nil { t.Fatalf("recognize: %+v %v", res, err) } if len(res.Top.Slots) != 0 { t.Fatalf("overlapping slots must not be kept: %+v", res.Top.Slots) } if res.Top.InvalidSlots["left"] != "span overlap" || res.Top.InvalidSlots["right"] != "span overlap" { t.Fatalf("invalid slots: %+v", res.Top.InvalidSlots) } } func TestR2NegationIsOnlyExcludesAny(t *testing.T) { plain := IntentSpec{ ID: "demo.write.file", Domain: "demo", Version: 1, Matcher: MatchSpec{IncludesAny: []string{"写入"}}, } excluded := plain excluded.ID = "demo.write.excluded" excluded.Matcher.ExcludesAny = []string{"不要"} rec := NewRecognizer(Options{}, nil, nil) plainBuilder := NewRegistryBuilder(Options{}) if err := plainBuilder.Register(plain); err != nil { t.Fatal(err) } built, err := plainBuilder.Build() if err != nil { t.Fatal(err) } hit, err := rec.Recognize(context.Background(), "不要写入文件", built.Snapshot()) if err != nil || hit.Top == nil || hit.Top.ID != plain.ID { t.Fatalf("without excludes_any the negated phrase still matches: %+v %v", hit, err) } exBuilder := NewRegistryBuilder(Options{}) if err := exBuilder.Register(excluded); err != nil { t.Fatal(err) } builtEx, err := exBuilder.Build() if err != nil { t.Fatal(err) } miss, err := rec.Recognize(context.Background(), "不要写入文件", builtEx.Snapshot()) if err != nil { t.Fatal(err) } if miss.Branch != BranchFallback { t.Fatalf("excludes_any must reject, got %+v", miss) } }