docs(flows): DoD obligatorio con user-facing surface + abrir issues 0100-0103 (taxonomia, frontmatter migration, dev_console, work dashboard)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+80
-21
@@ -18,6 +18,7 @@ type searchArgs struct {
|
||||
Purity string `json:"purity,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Tags string `json:"tags,omitempty"`
|
||||
Entity string `json:"entity,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
}
|
||||
|
||||
@@ -28,10 +29,11 @@ type searchHit struct {
|
||||
Lang string `json:"lang"`
|
||||
Domain string `json:"domain"`
|
||||
Purity string `json:"purity"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Signature string `json:"signature,omitempty"`
|
||||
Description string `json:"description"`
|
||||
Algebraic string `json:"algebraic,omitempty"`
|
||||
Entity string `json:"entity"` // "function" | "type"
|
||||
Entity string `json:"entity"` // "function" | "type" | "module"
|
||||
}
|
||||
|
||||
func searchTool() mcp.Tool {
|
||||
@@ -61,6 +63,10 @@ func searchTool() mcp.Tool {
|
||||
mcp.WithString("tags",
|
||||
mcp.Description("Filter by multiple tags (CSV). AND across tags: all must be present. Ej: 'metabase,client'."),
|
||||
),
|
||||
mcp.WithString("entity",
|
||||
mcp.Description("Restrict search to a single entity type: functions | types | modules. Default: functions + types."),
|
||||
mcp.Enum("functions", "types", "modules"),
|
||||
),
|
||||
mcp.WithNumber("limit",
|
||||
mcp.Description("Max hits returned (default 50)."),
|
||||
mcp.Min(1),
|
||||
@@ -79,31 +85,66 @@ func (d *deps) handleSearch(ctx context.Context, _ mcp.CallToolRequest, args sea
|
||||
|
||||
tagFilters := parseTagFilters(args.Tag, args.Tags)
|
||||
|
||||
fns, err := d.db.SearchFunctions(q, registry.Kind(args.Kind), registry.Purity(args.Purity), args.Lang, args.Domain, tagFilters...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError("search functions: " + err.Error()), nil
|
||||
var hits []searchHit
|
||||
|
||||
// Modules-only path.
|
||||
if args.Entity == "modules" {
|
||||
mods, err := d.db.SearchModules(q, args.Lang)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError("search modules: " + err.Error()), nil
|
||||
}
|
||||
for _, m := range mods {
|
||||
if !matchAllTags(m.Tags, tagFilters) {
|
||||
continue
|
||||
}
|
||||
hits = append(hits, searchHit{
|
||||
ID: m.ID,
|
||||
Name: m.Name,
|
||||
Lang: m.Lang,
|
||||
Version: m.Version,
|
||||
Description: m.Description,
|
||||
Entity: "module",
|
||||
})
|
||||
if len(hits) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
out := map[string]any{
|
||||
"query": args.Query,
|
||||
"count": len(hits),
|
||||
"limit": limit,
|
||||
"results": hits,
|
||||
}
|
||||
b, _ := json.MarshalIndent(out, "", " ")
|
||||
return mcp.NewToolResultText(string(b)), nil
|
||||
}
|
||||
|
||||
var hits []searchHit
|
||||
for _, f := range fns {
|
||||
hits = append(hits, searchHit{
|
||||
ID: f.ID,
|
||||
Name: f.Name,
|
||||
Kind: string(f.Kind),
|
||||
Lang: f.Lang,
|
||||
Domain: f.Domain,
|
||||
Purity: string(f.Purity),
|
||||
Signature: f.Signature,
|
||||
Description: f.Description,
|
||||
Entity: "function",
|
||||
})
|
||||
if len(hits) >= limit {
|
||||
break
|
||||
// Default path: functions + types (optionally filtered to one of them).
|
||||
if args.Entity == "" || args.Entity == "functions" {
|
||||
fns, err := d.db.SearchFunctions(q, registry.Kind(args.Kind), registry.Purity(args.Purity), args.Lang, args.Domain, tagFilters...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError("search functions: " + err.Error()), nil
|
||||
}
|
||||
for _, f := range fns {
|
||||
hits = append(hits, searchHit{
|
||||
ID: f.ID,
|
||||
Name: f.Name,
|
||||
Kind: string(f.Kind),
|
||||
Lang: f.Lang,
|
||||
Domain: f.Domain,
|
||||
Purity: string(f.Purity),
|
||||
Signature: f.Signature,
|
||||
Description: f.Description,
|
||||
Entity: "function",
|
||||
})
|
||||
if len(hits) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Types: only when no kind filter (kind applies only to functions).
|
||||
if args.Kind == "" && len(hits) < limit {
|
||||
// Types: when no kind filter (kind applies only to functions) and entity allows.
|
||||
if args.Kind == "" && len(hits) < limit && (args.Entity == "" || args.Entity == "types") {
|
||||
ts, err := d.db.SearchTypes(q, args.Lang, args.Domain, tagFilters...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError("search types: " + err.Error()), nil
|
||||
@@ -134,6 +175,24 @@ func (d *deps) handleSearch(ctx context.Context, _ mcp.CallToolRequest, args sea
|
||||
return mcp.NewToolResultText(string(b)), nil
|
||||
}
|
||||
|
||||
// matchAllTags returns true if every tag in want is present in have.
|
||||
// Empty want matches everything.
|
||||
func matchAllTags(have, want []string) bool {
|
||||
if len(want) == 0 {
|
||||
return true
|
||||
}
|
||||
set := make(map[string]bool, len(have))
|
||||
for _, t := range have {
|
||||
set[t] = true
|
||||
}
|
||||
for _, w := range want {
|
||||
if !set[w] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// parseTagFilters merges single `tag` and CSV `tags` into a deduplicated slice.
|
||||
func parseTagFilters(tag, csv string) []string {
|
||||
seen := map[string]bool{}
|
||||
|
||||
Reference in New Issue
Block a user