Why: fullwidth text missed case-folded terms, SpecByID shared maps, the catalog prompt printed handlers and private metadata, overlapping slots kept both values, and negation was easy to mistake for semantics. Evidence: GOWORK=off go vet ./... && go test -count=1 -race ./... passed, including TestR2NFKCFoldsFullwidthButNotTraditional, TestR2SpecByIDDeepCopiesMetadata, TestR2CatalogHidesPrivateMetadataAndHandlerInMarkdown, TestR2EvalRulesDocumentsMaxSpecs, TestR2OverlappingSlotsAreAmbiguous, and TestR2NegationIsOnlyExcludesAny. Impact: NFKC is opt-in and does not convert traditional Chinese; catalog markdown omits handlers and non-allowlisted metadata; overlapping slot spans are invalid. Refs: I4, I7, I8, I10, I11, I12 Co-authored-by: Cursor <cursoragent@cursor.com>
81 lines
2.0 KiB
Go
81 lines
2.0 KiB
Go
package intent
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"golang.org/x/text/unicode/norm"
|
|
)
|
|
|
|
// foldMatchText 做识别用折叠。nfkc 只做兼容等价(全角 ASCII、组合字符),
|
|
// 不做繁简转换。caseSensitive 为真时保留大小写。
|
|
func foldMatchText(s string, nfkc, caseSensitive bool) string {
|
|
if nfkc {
|
|
s = norm.NFKC.String(s)
|
|
}
|
|
if !caseSensitive {
|
|
s = strings.ToLower(s)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// foldInput 返回用于匹配的文本:第一段保留大小写(可选 NFKC),第二段是其小写。
|
|
func (s *Snapshot) foldInput(input string) (matchInput, folded string) {
|
|
if s == nil {
|
|
return input, strings.ToLower(input)
|
|
}
|
|
matchInput = foldMatchText(input, s.nfkc, true)
|
|
return matchInput, strings.ToLower(matchInput)
|
|
}
|
|
|
|
func cloneStrings(in []string) []string {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
return append([]string(nil), in...)
|
|
}
|
|
|
|
func cloneIntentSpec(in IntentSpec) IntentSpec {
|
|
out := in
|
|
out.Matcher.IncludesAny = cloneStrings(in.Matcher.IncludesAny)
|
|
out.Matcher.IncludesAll = cloneStrings(in.Matcher.IncludesAll)
|
|
out.Matcher.ExcludesAny = cloneStrings(in.Matcher.ExcludesAny)
|
|
out.Matcher.Patterns = cloneStrings(in.Matcher.Patterns)
|
|
if in.Slots != nil {
|
|
out.Slots = append([]SlotSpec(nil), in.Slots...)
|
|
for i := range out.Slots {
|
|
out.Slots[i].Enum = cloneStrings(in.Slots[i].Enum)
|
|
out.Slots[i].Normalize = cloneStrings(in.Slots[i].Normalize)
|
|
}
|
|
}
|
|
if in.Metadata != nil {
|
|
out.Metadata = make(map[string]string, len(in.Metadata))
|
|
for k, v := range in.Metadata {
|
|
out.Metadata[k] = v
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// catalogMetadataAllow 是目录与 Markdown 允许带出的 metadata 键。
|
|
// handler 仍留在 CatalogSpec 供执行器按名分派,但不写入 Markdown。
|
|
var catalogMetadataAllow = map[string]bool{
|
|
"blueprint": true,
|
|
"guidance": true,
|
|
}
|
|
|
|
func catalogMetadata(in map[string]string) map[string]string {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
out := map[string]string{}
|
|
for k, v := range in {
|
|
if catalogMetadataAllow[k] {
|
|
out[k] = v
|
|
}
|
|
}
|
|
if len(out) == 0 {
|
|
return nil
|
|
}
|
|
return out
|
|
}
|