iter(g176): 支持按参数说明检索组件

This commit is contained in:
HIsheR
2026-10-26 16:26:53 +08:00
parent 31650b1275
commit cc9ec02c94
5 changed files with 44 additions and 27 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "ui-arch-registry-index/0.1", "$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-26T08:22:46.231Z", "generatedAt": "2026-10-26T08:26:18.408Z",
"repoRoot": ".", "repoRoot": ".",
"packages": [ "packages": [
{ {
@@ -3680,7 +3680,7 @@
"intro": "工程层工具包(private,不发布)。所有 Agent 与人都通过它读取组件库:清单驱动索引、文档、MCP 三件套,外加 style / verify 两道门禁。", "intro": "工程层工具包(private,不发布)。所有 Agent 与人都通过它读取组件库:清单驱动索引、文档、MCP 三件套,外加 style / verify 两道门禁。",
"steps": [ "steps": [
"装好依赖后先跑 npm run check 确认基线全绿(build → demo → style → typecheck → smoke → index → verify)", "装好依赖后先跑 npm run check 确认基线全绿(build → demo → style → typecheck → smoke → index → verify)",
"查组件:ua list 看全量,ua search <关键词> 按 id/标题/props/步骤模糊找(拼错会回相近 id),ua info <组件id> 看 props/步骤/示例(ua info <包名> 看单包 api 与约定,加 --json 出原始清单)", "查组件:ua list 看全量,ua search <关键词> 按 id/标题/参数名/参数说明/步骤模糊找(「处理中」这类说明里的词也能搜到,命中非 id 标题时会标出命中字段;拼错会回相近 id),ua info <组件id> 看 props/步骤/示例(ua info <包名> 看单包 api 与约定,加 --json 出原始清单)",
"查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码", "查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码",
"改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料)", "改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料)",
"在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe" "在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe"
+1 -1
View File
@@ -50,7 +50,7 @@
可用工具:`list_components` / `get_component` / `search_components` / `get_tokens`(返回 --ua-* 令牌清单,避免硬编码颜色间距)/ `list_recipes` / `get_recipe`(取跨组件组合配方:骨架代码 + 要点 + 用到的组件 id,配好再逐个 `get_component` 查参数)。 可用工具:`list_components` / `get_component` / `search_components` / `get_tokens`(返回 --ua-* 令牌清单,避免硬编码颜色间距)/ `list_recipes` / `get_recipe`(取跨组件组合配方:骨架代码 + 要点 + 用到的组件 id,配好再逐个 `get_component` 查参数)。
2. **CLI**:`node packages/tools/cli/bin/ua.js list [--json|--components] | search <关键词> | info <name> [--json] | index | docs | style | verify | changelog`(`--json` 给脚本与 Agent 用,不必解析文本) 2. **CLI**:`node packages/tools/cli/bin/ua.js list [--json|--components] | search <关键词> | info <name> [--json] | index | docs | style | verify | changelog`(`--json` 给脚本与 Agent 用,不必解析文本);`search` 覆盖 id / 标题 / 参数名 / 参数说明 / 步骤五层,命中非 id 标题时会标出命中字段(如「← 参数说明含『处理中』」),`--json` 附带 `matched` 字段
3. **静态索引**:`.ua/index.json`(机器可读索引)、`llms.txt`(速览)或 **`llms-full.txt`(全量 Agent 文档:逐组件参数 / 回调 / 手把手步骤 / 令牌 / 组合配方,无图形纯文本,喂给 Agent 首选)**。 3. **静态索引**:`.ua/index.json`(机器可读索引)、`llms.txt`(速览)或 **`llms-full.txt`(全量 Agent 文档:逐组件参数 / 回调 / 手把手步骤 / 令牌 / 组合配方,无图形纯文本,喂给 Agent 首选)**。
+2 -2
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版) # @ui-arch 组件库 Agent 文档(无图形版)
> 自动生成于 2026-10-26T08:22:46.231Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 自动生成于 2026-10-26T08:26:18.408Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手 ## 快速上手
@@ -41,7 +41,7 @@
### @ui-arch/cli ### @ui-arch/cli
1. 装好依赖后先跑 npm run check 确认基线全绿(build → demo → style → typecheck → smoke → index → verify) 1. 装好依赖后先跑 npm run check 确认基线全绿(build → demo → style → typecheck → smoke → index → verify)
2. 查组件:ua list 看全量,ua search <关键词> 按 id/标题/props/步骤模糊找(拼错会回相近 id),ua info <组件id> 看 props/步骤/示例(ua info <包名> 看单包 api 与约定,加 --json 出原始清单) 2. 查组件:ua list 看全量,ua search <关键词> 按 id/标题/参数名/参数说明/步骤模糊找(「处理中」这类说明里的词也能搜到,命中非 id 标题时会标出命中字段;拼错会回相近 id),ua info <组件id> 看 props/步骤/示例(ua info <包名> 看单包 api 与约定,加 --json 出原始清单)
3. 查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码 3. 查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码
4. 改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料) 4. 改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料)
5. 在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe 5. 在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe
+37 -20
View File
@@ -203,25 +203,39 @@ function nearIds(name) {
.map(([id]) => id); .map(([id]) => id);
} }
/*** 按关键词搜组件:多词取最弱命中(AND 语义),id 命中优先于 title,其次 props 名与步骤文案;未命中退出码 1 ***/ /*** 组件的可检索字段:权重从高到低(id > 标题 > 参数名 > 参数说明 > 步骤),search 与 MCP 共用一份 ***/
function searchFields(comp) {
const props = comp.props ?? [];
return [
{ label: 'id', weight: 3, text: String(comp.id ?? '') },
{ label: '标题', weight: 2, text: String(comp.title ?? '') },
{ label: '参数', weight: 1, text: props.map((p) => String(p.n ?? '')).join(' ') },
{ label: '参数说明', weight: 0.8, text: props.map((p) => String(p.d ?? '')).join(' ') },
{ label: '步骤', weight: 0.5, text: (comp.steps ?? []).join(' ') },
].map((f) => ({ label: f.label, weight: f.weight, text: f.text.toLowerCase() }));
}
/*** 单个关键词在组件上的最佳命中:返回最高档字段(id 全等额外抬到最前),未命中 score 为 0 ***/
function matchOf(kw, comp) {
let best = { score: 0, label: '' };
for (const f of searchFields(comp)) {
if (!f.text.includes(kw)) continue;
const score = f.label === 'id' && f.text === kw ? 4 : f.weight; // id 全等单独一档,避免被 id 含稀释
if (score > best.score) best = { score, label: f.label };
}
return best;
}
/*** 按关键词搜组件:多词取最弱命中(AND 语义),并标出该词命中的字段,便于判断为何被召回 ***/
function cmdSearch(words = [], args = []) { function cmdSearch(words = [], args = []) {
const kws = [...new Set(words.map((w) => String(w).toLowerCase().trim()).filter(Boolean))]; const kws = [...new Set(words.map((w) => String(w).toLowerCase().trim()).filter(Boolean))];
if (!kws.length) return console.error('usage: ua search <关键词...> [--json]'); if (!kws.length) return console.error('usage: ua search <关键词...> [--json]');
const scoreOf = (kw, comp) => {
const id = String(comp.id ?? '').toLowerCase();
const title = String(comp.title ?? '').toLowerCase();
const props = (comp.props ?? []).map((p) => String(p.n ?? '').toLowerCase()).join(' ');
const steps = (comp.steps ?? []).join(' ').toLowerCase();
if (id === kw) return 4; // 分档只用于排序:id 全等 > id 含 > 标题 > props 名 > 步骤
if (id.includes(kw)) return 3;
if (title.includes(kw)) return 2;
if (props.includes(kw)) return 1;
return steps.includes(kw) ? 0.5 : 0;
};
const hits = collectComponents() const hits = collectComponents()
.map(({ pkg, comp }) => { .map(({ pkg, comp }) => {
const scores = kws.map((kw) => scoreOf(kw, comp)); const ms = kws.map((kw) => ({ kw, ...matchOf(kw, comp) }));
return scores.every((s) => s > 0) ? { pkg, comp, score: Math.min(...scores) } : null; // 任一词不中即淘汰 if (ms.some((m) => !m.score)) return null; // 任一词不中即淘汰
const weakest = ms.reduce((a, b) => (b.score < a.score ? b : a)); // 排序与提示都看最弱那一词
return { pkg, comp, score: weakest.score, field: weakest.label, keyword: weakest.kw };
}) })
.filter(Boolean) .filter(Boolean)
.sort((a, b) => b.score - a.score || String(a.comp.id).localeCompare(String(b.comp.id))); .sort((a, b) => b.score - a.score || String(a.comp.id).localeCompare(String(b.comp.id)));
@@ -231,9 +245,14 @@ function cmdSearch(words = [], args = []) {
return process.exit(1); return process.exit(1);
} }
if (args.includes('--json')) { if (args.includes('--json')) {
return console.log(JSON.stringify(hits.map((h) => ({ package: h.pkg.name, ...h.comp })), null, 2)); return console.log(
JSON.stringify(hits.map((h) => ({ package: h.pkg.name, ...h.comp, matched: { field: h.field, keyword: h.keyword } })), null, 2),
);
}
for (const h of hits) {
const why = h.field === 'id' || h.field === '标题' ? '' : ` ← ${h.field}含「${h.keyword}」`;
console.log(`${String(h.comp.id).padEnd(16)} ${h.comp.title ?? ''} (${h.pkg.name})${why}`);
} }
for (const h of hits) console.log(`${String(h.comp.id).padEnd(16)} ${h.comp.title ?? ''} (${h.pkg.name})`);
console.log(`\n共 ${hits.length} 个组件命中;查参数:ua info <组件id>`); console.log(`\n共 ${hits.length} 个组件命中;查参数:ua info <组件id>`);
} }
@@ -583,7 +602,7 @@ function cmdMcp() {
}, },
{ {
name: 'search_components', name: 'search_components',
description: 'Search by keyword: returns matched packages plus matched components (id/title/props)', description: 'Search by keyword: returns matched packages plus matched components (id/title/prop names/prop descriptions/steps)',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: { query: { type: 'string' } }, properties: { query: { type: 'string' } },
@@ -647,9 +666,7 @@ function cmdMcp() {
) )
.map((p) => ({ name: p.name, version: p.version, type: p.type, description: p.description })); .map((p) => ({ name: p.name, version: p.version, type: p.type, description: p.description }));
const comps = collectComponents(packages) // 组件级命中:包级 exports 只给出包名,Agent 还得再查一次 const comps = collectComponents(packages) // 组件级命中:包级 exports 只给出包名,Agent 还得再查一次
.filter(({ comp }) => .filter(({ comp }) => searchFields(comp).some((f) => f.text.includes(q))) // 与 CLI search 同一份字段表
[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 })); .map(({ pkg, comp }) => ({ package: pkg.name, id: comp.id, title: comp.title }));
if (!pkgs.length && !comps.length) throw new Error(`no match: ${args.query}`); if (!pkgs.length && !comps.length) throw new Error(`no match: ${args.query}`);
return { packages: pkgs, components: comps }; return { packages: pkgs, components: comps };
+1 -1
View File
@@ -36,7 +36,7 @@
"intro": "工程层工具包(private,不发布)。所有 Agent 与人都通过它读取组件库:清单驱动索引、文档、MCP 三件套,外加 style / verify 两道门禁。", "intro": "工程层工具包(private,不发布)。所有 Agent 与人都通过它读取组件库:清单驱动索引、文档、MCP 三件套,外加 style / verify 两道门禁。",
"steps": [ "steps": [
"装好依赖后先跑 npm run check 确认基线全绿(build → demo → style → typecheck → smoke → index → verify)", "装好依赖后先跑 npm run check 确认基线全绿(build → demo → style → typecheck → smoke → index → verify)",
"查组件:ua list 看全量,ua search <关键词> 按 id/标题/props/步骤模糊找(拼错会回相近 id),ua info <组件id> 看 props/步骤/示例(ua info <包名> 看单包 api 与约定,加 --json 出原始清单)", "查组件:ua list 看全量,ua search <关键词> 按 id/标题/参数名/参数说明/步骤模糊找(「处理中」这类说明里的词也能搜到,命中非 id 标题时会标出命中字段;拼错会回相近 id),ua info <组件id> 看 props/步骤/示例(ua info <包名> 看单包 api 与约定,加 --json 出原始清单)",
"查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码", "查令牌:ua tokens 打印全部 --ua-* 变量(颜色带暗色值),--json 输出结构化数组;写样式前先查,禁止硬编码",
"改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料)", "改完清单跑 npm run docs:同时刷新人类文档站与 llms-full.txt(Agent 首选喂料)",
"在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe" "在 ~/.workbuddy/mcp.json 注册 ua mcp 后,Agent 可直接调 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe"