diff --git a/.ua/backlog.json b/.ua/backlog.json index 533484e..3846a13 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -1588,6 +1588,14 @@ "acceptance": "ua search a b 取各词命中的最弱档做 AND 过滤仍保持 id>标题>props>步骤 的排序;未命中输出相近建议并 exit 1;无参数打印用法。", "notes": "", "status": "done" + }, + { + "id": "g154", + "title": "MCP search_components 补组件级命中", + "motivation": "原实现只在包级字段(name/description/tags/api.exports)里搜,命中后只返回包:Agent 搜「日期」得到 @ui-arch/ui,还得再猜组件 id。", + "acceptance": "返回 { packages, components } 两级结构,components 按 id/标题/props 名命中并带 package 字段;空 query 与无命中都抛明确错误;工具描述同步。", + "notes": "", + "status": "open" } ] } diff --git a/packages/tools/cli/bin/ua.js b/packages/tools/cli/bin/ua.js index 24aeccf..b39e6dc 100644 --- a/packages/tools/cli/bin/ua.js +++ b/packages/tools/cli/bin/ua.js @@ -566,7 +566,7 @@ function cmdMcp() { }, { name: 'search_components', - description: 'Search packages by keyword across name/description/tags/api', + description: 'Search by keyword: returns matched packages plus matched components (id/title/props)', inputSchema: { type: 'object', properties: { query: { type: 'string' } }, @@ -619,8 +619,9 @@ function cmdMcp() { return { package: hit.pkg.name, ...hit.comp, recipes: recipesFor(hit.comp.id) }; } if (name === 'search_components') { - const q = String(args.query ?? '').toLowerCase(); - return packages + const q = String(args.query ?? '').toLowerCase().trim(); + if (!q) throw new Error('query 不能为空'); + const pkgs = packages .filter((p) => [p.name, p.description, p.type, ...(p.tags ?? []), ...(p.api?.exports ?? [])] .join(' ') @@ -628,6 +629,13 @@ function cmdMcp() { .includes(q), ) .map((p) => ({ name: p.name, version: p.version, type: p.type, description: p.description })); + const comps = collectComponents(packages) // 组件级命中:包级 exports 只给出包名,Agent 还得再查一次 + .filter(({ comp }) => + [comp.id, comp.title, ...(comp.props ?? []).map((p) => p.n)].join(' ').toLowerCase().includes(q), + ) + .map(({ pkg, comp }) => ({ package: pkg.name, id: comp.id, title: comp.title })); + if (!pkgs.length && !comps.length) throw new Error(`no match: ${args.query}`); + return { packages: pkgs, components: comps }; } if (name === 'get_tokens') { const list = loadTokenList();