iter(g322): MCP get_component 支持逗号批量与 CLI 解析口径合一
This commit is contained in:
@@ -3284,6 +3284,15 @@
|
||||
"motivation": "按钮 / 徽标 / 页签 / 分页 / 步骤条这类控件的文案是「动作」不是「内容」:双击或拖选会把按钮文字选中变蓝,观感割裂且打断连点(现全库只有 8 处零散 user-select,交互控件基本没盖住);移动端浏览器默认点按灰闪(tap highlight)与令牌皮肤同层突兀,按下反馈应由 :active 态承担。",
|
||||
"acceptance": "① ui.css 全局区新增一条控件清单式 user-select: none(button/tag/badge/fab/backtop/page-btn/tab/seg-btn/step/rate-star/checkbox/radio/switch/menu-item/crumb),内容型组件(代码块 / 表格 / 描述列表 / 链接)不入选、照常可复制;② 全站 ua- 类 -webkit-tap-highlight-color: transparent,不引入任何颜色字面量;③ audit:visual / audit:contrast / audit:surface / audit:motion 全绿,npm run check 通过。",
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "g322",
|
||||
"domain": "tooling",
|
||||
"status": "open",
|
||||
"title": "MCP get_component 支持逗号批量,与 ua info 对齐",
|
||||
"motivation": "g319 给 `ua info` 加了逗号批量,但 Agent 查组件的主通道是 MCP(get_component 一次仍只能查一个);拼一个界面要 3–5 次工具往返,MCP 侧同样需要批量,并抽出 CLI / MCP 共用的名字解析函数避免两处口径漂移。",
|
||||
"acceptance": "① get_component 的 name 支持逗号分隔多值:json 返回数组、md 返回以水平线分隔的多段片段,单值输出结构不变;② 不存在的 id 报错并标明序号;③ 名字解析抽成 CLI / MCP 共用函数;④ 工具 description 同步更新;⑤ npm run check 通过。",
|
||||
"notes": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
可用工具:`list_components` / `get_component`(传 `format: "md"` 直接拿 Markdown 片段,等同 `ua info --md`)/ `search_components` / `get_tokens`(返回 --ua-* 令牌清单,避免硬编码颜色间距)/ `list_recipes` / `get_recipe`(取跨组件组合配方:骨架代码 + 要点 + 用到的组件 id,配好再逐个 `get_component` 查参数)。
|
||||
可用工具:`list_components` / `get_component`(name 支持逗号分隔批量一次取多个;传 `format: "md"` 直接拿 Markdown 片段,批量片段以水平线分隔,等同 `ua info --md`)/ `search_components` / `get_tokens`(返回 --ua-* 令牌清单,避免硬编码颜色间距)/ `list_recipes` / `get_recipe`(取跨组件组合配方:骨架代码 + 要点 + 用到的组件 id,配好再逐个 `get_component` 查参数)。
|
||||
|
||||
2. **CLI**:`node packages/tools/cli/bin/ua.js loop [--json](迭代循环诊断:锁心跳 / 上次留痕 / 待办数) | iter done <id> <commit> <summary>(迭代收尾:置 done + 写留痕,ts 由本机时钟生成) | iter add <条目json>(补自选题) | list [--json|--components] | search <关键词> | info <name[,name…]> [--json|--md](name 支持逗号分隔一次查多个;--md 出可粘贴的 Markdown 片段:标题 + 参数表 + 用法 + 示例,批量片段以水平线分隔;--json 单值为对象、多值为数组) | recipes [编号] [--json] | index | docs | style | verify | changelog`(`--json` 给脚本与 Agent 用,不必解析文本);`search` 覆盖 id / 标题 / 参数名 / 参数说明 / 步骤五层 + 配方三层(标题 / 配方组件 / 要点,命中行直接给 `ua recipes <编号>` 指路),命中非 id 标题时会标出命中字段(如「← 参数说明含『处理中』」),`--json` 输出 `{ components, recipes }` 两数组、各项带 `matched` 字段
|
||||
|
||||
|
||||
@@ -227,6 +227,14 @@ function nearIds(name) {
|
||||
.map(([id]) => id);
|
||||
}
|
||||
|
||||
/*** 名字 → 包或组件条目:包名 / @ui-arch 前缀 / 组件 id 三种写法都认;CLI info 与 MCP get_component 共用,防止两处解析口径漂移 ***/
|
||||
function resolveEntry(packages, n) {
|
||||
const pkg = packages.find((x) => x.name === n || x.name === `@ui-arch/${n}`);
|
||||
if (pkg) return { kind: 'pkg', pkg };
|
||||
const hit = collectComponents(packages).find((x) => x.comp.id === n);
|
||||
return hit ? { kind: 'comp', hit } : null;
|
||||
}
|
||||
|
||||
/*** 组件的可检索字段:权重从高到低(id > 标题 > 参数名 > 参数说明 > 步骤),search 与 MCP 共用一份 ***/
|
||||
function searchFields(comp) {
|
||||
const props = comp.props ?? [];
|
||||
@@ -321,13 +329,7 @@ function cmdInfo(name, args = []) {
|
||||
const asJson = args.includes('--json');
|
||||
const asMd = args.includes('--md');
|
||||
const names = name.split(',').map((s) => s.trim()).filter(Boolean); // 批量:逗号分隔多组件一次拿全,省 Agent 往返
|
||||
const comps = collectComponents(packages);
|
||||
const resolve = (n) => { // 单个名字 → 包或组件条目;都没命中返回 null 交由调用方报错
|
||||
const pkg = packages.find((x) => x.name === n || x.name === `@ui-arch/${n}`);
|
||||
if (pkg) return { kind: 'pkg', pkg };
|
||||
const hit = comps.find((x) => x.comp.id === n);
|
||||
return hit ? { kind: 'comp', hit } : null;
|
||||
};
|
||||
const resolve = (n) => resolveEntry(packages, n); // 单个名字 → 包或组件条目;都没命中返回 null 交由调用方报错
|
||||
if (names.length === 1) {
|
||||
const r = resolve(names[0]);
|
||||
if (!r) {
|
||||
@@ -799,12 +801,12 @@ function cmdMcp() {
|
||||
},
|
||||
{
|
||||
name: 'get_component',
|
||||
description: 'Get one package by name, or one component by its id; format=md returns a paste-ready Markdown block instead of raw JSON',
|
||||
description: 'Get one package by name, or one or more components by comma-separated ids (e.g. "button,select"); format=md returns paste-ready Markdown blocks (multi-query blocks are separated by horizontal rules)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'component id (e.g. inputTag) or package name (e.g. @ui-arch/table-core)' },
|
||||
format: { type: 'string', enum: ['json', 'md'], description: 'json (default) returns the manifest entry; md returns a paste-ready Markdown block (title + props table + usage + example)' },
|
||||
name: { type: 'string', description: 'component id (e.g. inputTag), package name (e.g. @ui-arch/table-core), or comma-separated ids for batch query (e.g. button,select)' },
|
||||
format: { type: 'string', enum: ['json', 'md'], description: 'json (default) returns the manifest entry (array for batch); md returns a paste-ready Markdown block (title + props table + usage + example)' },
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
@@ -855,19 +857,24 @@ function cmdMcp() {
|
||||
return packages.map((p) => ({ name: p.name, version: p.version, type: p.type, description: p.description }));
|
||||
}
|
||||
if (name === 'get_component') {
|
||||
const p = packages.find((x) => x.name === args.name || x.name === `@ui-arch/${args.name}`);
|
||||
if (p) return p;
|
||||
const hit = collectComponents(packages).find((x) => x.comp.id === args.name); // 退化到组件级查询
|
||||
if (!hit) throw new Error(`not found: ${args.name}`);
|
||||
if (args.format === 'md') {
|
||||
// 与 ua info --md 共用同一渲染:Agent 拿到就能直接贴进回复 / 文档,不必自己排参数表
|
||||
const related = recipesFor(hit.comp.id);
|
||||
const tail = related.length ? `
|
||||
|
||||
出现在配方:${related.map((r) => `${r.id}(${r.title})`).join('、')}` : '';
|
||||
return formatComponentMd(hit.pkg, hit.comp) + tail; // 直接给字符串:上层原样作为 text 返回
|
||||
const names = String(args.name).split(',').map((s) => s.trim()).filter(Boolean); // 批量:逗号分隔一次拿全多个组件,与 ua info 同口径
|
||||
const resolved = names.map((n) => resolveEntry(packages, n));
|
||||
const bad = names.filter((n, i) => !resolved[i]);
|
||||
if (bad.length) throw new Error(`not found: ${bad.join(' / ')}(第 ${names.indexOf(bad[0]) + 1} 个,包名与组件 id 都没命中)`);
|
||||
const compMd = (r) => { // md 模式组件片段 + 配方尾注;包条目退化为标题行
|
||||
if (r.kind === 'pkg') return `## ${r.pkg.name}(${r.pkg.type})\n\n${r.pkg.description}`;
|
||||
const related = recipesFor(r.hit.comp.id);
|
||||
const tail = related.length ? `\n\n出现在配方:${related.map((x) => `${x.id}(${x.title})`).join('、')}` : '';
|
||||
return formatComponentMd(r.hit.pkg, r.hit.comp) + tail;
|
||||
};
|
||||
if (resolved.length > 1) {
|
||||
if (args.format === 'md') return resolved.map(compMd).join('\n\n---\n\n'); // 多段片段以水平线分隔
|
||||
return resolved.map((r) => (r.kind === 'pkg' ? r.pkg : { package: r.hit.pkg.name, ...r.hit.comp, recipes: recipesFor(r.hit.comp.id) }));
|
||||
}
|
||||
return { package: hit.pkg.name, ...hit.comp, recipes: recipesFor(hit.comp.id) };
|
||||
const r = resolved[0];
|
||||
if (r.kind === 'pkg') return r.pkg;
|
||||
if (args.format === 'md') return compMd(r); // 与 ua info --md 共用同一渲染:Agent 拿到就能直接贴进回复 / 文档,不必自己排参数表
|
||||
return { package: r.hit.pkg.name, ...r.hit.comp, recipes: recipesFor(r.hit.comp.id) };
|
||||
}
|
||||
if (name === 'search_components') {
|
||||
const q = String(args.query ?? '').toLowerCase().trim();
|
||||
|
||||
Reference in New Issue
Block a user