iter(g309): 留痕流程收敛为 ua iter 子命令

This commit is contained in:
HIsheR
2026-10-27 00:39:08 +08:00
parent 0be2287ae3
commit ae105b6e44
9 changed files with 85 additions and 34 deletions
-10
View File
@@ -1,10 +0,0 @@
import { readFileSync, writeFileSync } from 'node:fs';
// 用法:node .ua/add-entry.mjs <条目json文件>:把自补条目追加进 backlog(按 id 升序插到末尾)
const [src] = process.argv.slice(2);
const b = JSON.parse(readFileSync('.ua/backlog.json', 'utf8'));
const entry = JSON.parse(readFileSync(src, 'utf8'));
if (b.items.some((i) => i.id === entry.id)) throw new Error(`id 已存在:${entry.id}`);
b.items.push(entry);
writeFileSync('.ua/backlog.json', JSON.stringify(b, null, 2) + '\n', 'utf8');
console.log('added', entry.id, entry.title);
+9
View File
@@ -3167,6 +3167,15 @@
"motivation": "文档里写明「拖到边缘不会自动滚动,条目超过一屏就别用」,这等于把最常见的场景(几十条检查单)挡在门外:拖到容器下沿不动,用户只能反复滚动重来。",
"acceptance": "① dragstart 里一次算好最近的纵向滚动祖先(overflow-y auto/scroll 且确已溢出),dragover 每帧只在贴边 48px 感应带内按帧滚 12px;② 离开感应带或 dragend 立即取消帧并清状态,不留后台 rAF;③ 找不到滚动祖先(整页滚动)时不接管;④ manifest 步骤与配方 19 要点同步改写,npm run check 全绿。",
"notes": "dragstart 用 scrollHost() 一次算最近纵向滚动祖先(overflow-y auto/scroll 且已溢出),dragover 在上下 48px 感应带内按帧滚 12px(EDGE_BAND/EDGE_STEP 常量),离开感应带或 dragend 取消 rAF 并清 scroller/edgeDir;找不到祖先不接管;manifest 步骤与配方 19 要点同步"
},
{
"id": "g309",
"domain": "tooling",
"status": "open",
"title": "把每轮迭代的手工留痕脚本收成 ua iter done / add",
"motivation": "每轮收尾都要临时写 node 脚本改 backlog、手拼 iterations.log 的 JSON——历史踩过 ts 手写缺时区、字段顺序自创、状态忘记改、哈希抄错,lint-iterations 事后才拦。这条流程本该由 CLI 保证,不该每轮靠人记。",
"acceptance": "① 新增 ua iter done <id> <commit> <summary> [notes]:backlog 置 done、追加 {ts,id,result,commit,summary} 固定字段行,ts 由本机时钟生成带 +08:00 偏移,commit 校验 7-40 位哈希,条目不存在直接报错;② ua iter add <json>:五字段齐全 + id 唯一校验后追加;③ usage 串、dispatch、CLI 清单三处同步(doctor 通过);④ 临时脚本 .ua/log-one.mjs 与 .ua/add-entry.mjs 删除;⑤ npm run check 全绿。",
"notes": ""
}
]
}
+4 -3
View File
@@ -1,6 +1,6 @@
{
"$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-26T16:37:31.858Z",
"generatedAt": "2026-10-26T16:38:55.415Z",
"repoRoot": ".",
"packages": [
{
@@ -4769,9 +4769,10 @@
"ua audit:visual [--json|--baseline]:视觉漂移门禁,统计皮肤包里各类硬编码视觉字面量(字号/动效时长/阴影/圆角/焦点环/内距/控件高度/字重/行高/层级/边框色)并与 .ua/visual-baseline.json 棘轮比对,超基线退出码 1;--baseline 重写基线,--json 出机器可读计数与每类命中行号(line / prop / value / hint,Agent 可直接定位)",
"ua audit:contrast [--json|--list]:对比度门禁,把令牌里 18 组「前景色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮/暗 × 常规/高对比四套分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6 / 非文本 3)退出码 1;--json 出逐对明细,--list 只列配对表",
"ua audit:motion [--json]:动效兜底门禁,两类必查——① 带 infinite 的动画逐个核对是否在 prefers-reduced-motion 块里 animation: none;② 悬停 / 按压的位移(translate)是否在减少动效下归零(认 ua- 类通配的全局规则)。缺兜底退出码 1(--json 出逐条明细)",
"ua audit:surface [--json|--list]:表面阶梯门禁,逐档核对底色对底色的亮度比(页面底 → 表面 → 悬停底 → 按下底 ≥1.04、凹陷底 ≥1.02),亮/暗 × 常规/高对比四套主题各算一遍,不达标退出码 1;对比度只管文字读不读得清,管不到「悬停有没有反馈」(实测过 1.008 的悬停底)",
"ua audit:surface [--json|--list]:表面阶梯门禁,逐档核对底色对底色的亮度比(页面底 → 表面 → 悬停底 → 按下底 ≥1.04、凹陷底 ≥1.02),外加两档半透明淡底压在表面上的可见度(选中底 ≥1.04、悬停底 ≥1.04),亮/暗 × 常规/高对比四套主题各算一遍,不达标退出码 1;对比度只管文字读不读得清,管不到「悬停有没有反馈」(实测过 1.008 的悬停底)",
"ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe;get_component 支持 format: md 直接取可粘贴的 Markdown 片段",
"ua loop [--json]:迭代循环诊断,一眼看清锁心跳(是否 15 分钟内)、上次留痕、backlog 待办与视觉欠账数,并给出本轮该不该抢锁、下一条该选谁;多实例错位接力时不必翻调度器数据库"
"ua loop [--json]:迭代循环诊断,一眼看清锁心跳(是否 15 分钟内)、上次留痕、backlog 待办与视觉欠账数,并给出本轮该不该抢锁、下一条该选谁;多实例错位接力时不必翻调度器数据库",
"ua iter done <id> <commit> <summary> [notes]:一轮迭代收尾——backlog 条目置 done、按固定字段顺序追加 .ua/iterations.log 一行(ts 由本机时钟生成带 +08:00,commit 校验 7-40 位哈希);ua iter add <条目json文件> 补一条自选题(id 唯一 + 五字段齐全校验)"
],
"keyTypes": [
"ua.manifest.json 清单结构",
-13
View File
@@ -1,13 +0,0 @@
import { readFileSync, writeFileSync, appendFileSync } from 'node:fs';
// 用法:node .ua/log-one.mjs <id> <commit> <result> <summary> <ts>
const [id, commit, result, summary, ts] = process.argv.slice(2);
const b = JSON.parse(readFileSync('.ua/backlog.json', 'utf8'));
const item = b.items.find((i) => i.id === id);
if (item) item.status = result === 'done' ? 'done' : 'open'; // 失败保持 open
writeFileSync('.ua/backlog.json', JSON.stringify(b, null, 2) + '\n', 'utf8');
// 留痕行:字段顺序固定 ts/id/result/commit/summary,ts 由 date 命令实测传入
appendFileSync('.ua/iterations.log', JSON.stringify({ ts, id, result, commit, summary }) + '\n', 'utf8');
console.log('logged', id, result, commit, ts);
+1 -1
View File
@@ -52,7 +52,7 @@
可用工具:`list_components` / `get_component`(传 `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](迭代循环诊断:锁心跳 / 上次留痕 / 待办数) | list [--json|--components] | search <关键词> | info <name> [--json|--md](--md 出可粘贴的 Markdown 片段:标题 + 参数表 + 用法 + 示例) | recipes [编号] [--json] | index | docs | style | verify | changelog`(`--json` 给脚本与 Agent 用,不必解析文本);`search` 覆盖 id / 标题 / 参数名 / 参数说明 / 步骤五层 + 配方三层(标题 / 配方组件 / 要点,命中行直接给 `ua recipes <编号>` 指路),命中非 id 标题时会标出命中字段(如「← 参数说明含『处理中』」),`--json` 输出 `{ components, recipes }` 两数组、各项带 `matched` 字段
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> [--json|--md](--md 出可粘贴的 Markdown 片段:标题 + 参数表 + 用法 + 示例) | recipes [编号] [--json] | index | docs | style | verify | changelog`(`--json` 给脚本与 Agent 用,不必解析文本);`search` 覆盖 id / 标题 / 参数名 / 参数说明 / 步骤五层 + 配方三层(标题 / 配方组件 / 要点,命中行直接给 `ua recipes <编号>` 指路),命中非 id 标题时会标出命中字段(如「← 参数说明含『处理中』」),`--json` 输出 `{ components, recipes }` 两数组、各项带 `matched` 字段
3. **静态索引**:`.ua/index.json`(机器可读索引)、`llms.txt`(速览)或 **`llms-full.txt`(全量 Agent 文档:逐组件参数 / 回调 / 手把手步骤 / 令牌 / 组合配方,无图形纯文本,喂给 Agent 首选)**。
+3 -2
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版)
> 自动生成于 2026-10-26T16:37:31.858Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 自动生成于 2026-10-26T16:38:55.415Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手
@@ -2747,9 +2747,10 @@ API 速览:
- ua audit:visual [--json|--baseline]:视觉漂移门禁,统计皮肤包里各类硬编码视觉字面量(字号/动效时长/阴影/圆角/焦点环/内距/控件高度/字重/行高/层级/边框色)并与 .ua/visual-baseline.json 棘轮比对,超基线退出码 1;--baseline 重写基线,--json 出机器可读计数与每类命中行号(line / prop / value / hint,Agent 可直接定位)
- ua audit:contrast [--json|--list]:对比度门禁,把令牌里 18 组「前景色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮/暗 × 常规/高对比四套分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6 / 非文本 3)退出码 1;--json 出逐对明细,--list 只列配对表
- ua audit:motion [--json]:动效兜底门禁,两类必查——① 带 infinite 的动画逐个核对是否在 prefers-reduced-motion 块里 animation: none;② 悬停 / 按压的位移(translate)是否在减少动效下归零(认 ua- 类通配的全局规则)。缺兜底退出码 1(--json 出逐条明细)
- ua audit:surface [--json|--list]:表面阶梯门禁,逐档核对底色对底色的亮度比(页面底 → 表面 → 悬停底 → 按下底 ≥1.04、凹陷底 ≥1.02),亮/暗 × 常规/高对比四套主题各算一遍,不达标退出码 1;对比度只管文字读不读得清,管不到「悬停有没有反馈」(实测过 1.008 的悬停底)
- ua audit:surface [--json|--list]:表面阶梯门禁,逐档核对底色对底色的亮度比(页面底 → 表面 → 悬停底 → 按下底 ≥1.04、凹陷底 ≥1.02),外加两档半透明淡底压在表面上的可见度(选中底 ≥1.04、悬停底 ≥1.04),亮/暗 × 常规/高对比四套主题各算一遍,不达标退出码 1;对比度只管文字读不读得清,管不到「悬停有没有反馈」(实测过 1.008 的悬停底)
- ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe;get_component 支持 format: md 直接取可粘贴的 Markdown 片段
- ua loop [--json]:迭代循环诊断,一眼看清锁心跳(是否 15 分钟内)、上次留痕、backlog 待办与视觉欠账数,并给出本轮该不该抢锁、下一条该选谁;多实例错位接力时不必翻调度器数据库
- ua iter done <id> <commit> <summary> [notes]:一轮迭代收尾——backlog 条目置 done、按固定字段顺序追加 .ua/iterations.log 一行(ts 由本机时钟生成带 +08:00,commit 校验 7-40 位哈希);ua iter add <条目json文件> 补一条自选题(id 唯一 + 五字段齐全校验)
一行用法:
+2 -1
View File
@@ -161,9 +161,10 @@
- ua audit:visual [--json|--baseline]:视觉漂移门禁,统计皮肤包里各类硬编码视觉字面量(字号/动效时长/阴影/圆角/焦点环/内距/控件高度/字重/行高/层级/边框色)并与 .ua/visual-baseline.json 棘轮比对,超基线退出码 1;--baseline 重写基线,--json 出机器可读计数与每类命中行号(line / prop / value / hint,Agent 可直接定位)
- ua audit:contrast [--json|--list]:对比度门禁,把令牌里 18 组「前景色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮/暗 × 常规/高对比四套分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6 / 非文本 3)退出码 1;--json 出逐对明细,--list 只列配对表
- ua audit:motion [--json]:动效兜底门禁,两类必查——① 带 infinite 的动画逐个核对是否在 prefers-reduced-motion 块里 animation: none;② 悬停 / 按压的位移(translate)是否在减少动效下归零(认 ua- 类通配的全局规则)。缺兜底退出码 1(--json 出逐条明细)
- ua audit:surface [--json|--list]:表面阶梯门禁,逐档核对底色对底色的亮度比(页面底 → 表面 → 悬停底 → 按下底 ≥1.04、凹陷底 ≥1.02),亮/暗 × 常规/高对比四套主题各算一遍,不达标退出码 1;对比度只管文字读不读得清,管不到「悬停有没有反馈」(实测过 1.008 的悬停底)
- ua audit:surface [--json|--list]:表面阶梯门禁,逐档核对底色对底色的亮度比(页面底 → 表面 → 悬停底 → 按下底 ≥1.04、凹陷底 ≥1.02),外加两档半透明淡底压在表面上的可见度(选中底 ≥1.04、悬停底 ≥1.04),亮/暗 × 常规/高对比四套主题各算一遍,不达标退出码 1;对比度只管文字读不读得清,管不到「悬停有没有反馈」(实测过 1.008 的悬停底)
- ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe;get_component 支持 format: md 直接取可粘贴的 Markdown 片段
- ua loop [--json]:迭代循环诊断,一眼看清锁心跳(是否 15 分钟内)、上次留痕、backlog 待办与视觉欠账数,并给出本轮该不该抢锁、下一条该选谁;多实例错位接力时不必翻调度器数据库
- ua iter done <id> <commit> <summary> [notes]:一轮迭代收尾——backlog 条目置 done、按固定字段顺序追加 .ua/iterations.log 一行(ts 由本机时钟生成带 +08:00,commit 校验 7-40 位哈希);ua iter add <条目json文件> 补一条自选题(id 唯一 + 五字段齐全校验)
- 用法: `node packages/tools/cli/bin/ua.js <index|list|info <包名|组件id> [--json|--md]|docs|tokens [--json|--unused]|style|verify|commit-lint|audit:visual|audit:contrast|audit:motion|doctor|mcp>;仓库内等价于 npm run index / docs / style / verify / audit:visual / audit:contrast / audit:motion / doctor`
- 约定: 零依赖:只用 node 标准库,Node >= 18
- 约定: 清单是单一来源:代码注释、文档站、llms-full.txt、MCP 全部从 ua.manifest.json 派生
+62 -1
View File
@@ -1902,6 +1902,64 @@ function cmdDoctor() {
console.log(`doctor OK(${checks.length} 项全过)`);
}
/*** 作用:把「改 backlog + 追加 iterations.log」收成一条命令——字段顺序与 +08:00 时间格式由代码保证,
* 省掉每轮手搓临时脚本(历史踩过:ts 手写、字段顺序自创、状态忘了改、哈希抄错)。 ***/
const localStamp = () => {
const d = new Date();
const pad = (n) => String(n).padStart(2, '0');
const off = -d.getTimezoneOffset(); // 时区偏移(分钟),东八区为 +480
const sign = off >= 0 ? '+' : '-';
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${sign}${pad(Math.floor(Math.abs(off) / 60))}:${pad(Math.abs(off) % 60)}`;
};
function cmdIter(args) {
const [sub, ...rest] = args;
const backlogFile = path.join(INDEX_DIR, 'backlog.json');
const logFile = path.join(INDEX_DIR, 'iterations.log');
const load = () => JSON.parse(fs.readFileSync(backlogFile, 'utf8'));
const itemsOf = (data) => (Array.isArray(data) ? data : data.items);
const save = (data, items) => {
if (Array.isArray(data)) fs.writeFileSync(backlogFile, JSON.stringify(items, null, 2) + '\n', 'utf8');
else { data.items = items; fs.writeFileSync(backlogFile, JSON.stringify(data, null, 2) + '\n', 'utf8'); }
};
if (sub === 'done') {
const [id, commit, summary, notes] = rest;
if (!id || !commit || !summary) {
console.error('usage: ua iter done <id> <commit> <summary> [notes]');
process.exit(2);
}
if (!/^[0-9a-f]{7,40}$/.test(commit)) { console.error(`commit 须为 7-40 位短哈希:${commit}`); process.exit(2); }
const data = load();
const items = itemsOf(data);
const item = items.find((i) => i.id === id);
if (!item) { console.error(`backlog 里没有 ${id}(自补条目先用 ua iter add)`); process.exit(2); }
item.status = 'done';
if (notes) item.notes = notes; // 备注缺省保留原值:只有显式传了才覆盖
save(data, items);
const line = { ts: localStamp(), id, result: 'done', commit, summary }; // 字段顺序与 ITERATION.md 一致
fs.appendFileSync(logFile, JSON.stringify(line) + '\n', 'utf8');
console.log(`iter done ${id} → backlog 置 done,留痕 ${line.ts}(${commit})`);
console.log('接着提交元数据:git add -A && git commit -m "chore(backlog): 记录 ' + id + ' 完成"');
return;
}
if (sub === 'add') {
const [file] = rest;
if (!file) { console.error('usage: ua iter add <条目json文件>'); process.exit(2); }
const entry = JSON.parse(fs.readFileSync(file, 'utf8'));
const missing = ['id', 'domain', 'title', 'motivation', 'acceptance'].filter((k) => !entry[k]); // 选题规则要求三件套齐全
if (missing.length) { console.error(`条目缺字段:${missing.join(' / ')}`); process.exit(2); }
const data = load();
const items = itemsOf(data);
if (items.some((i) => i.id === entry.id)) { console.error(`id 已存在:${entry.id}`); process.exit(2); }
items.push({ ...entry, status: entry.status ?? 'open', notes: entry.notes ?? '' });
save(data, items);
console.log(`iter add ${entry.id}(${entry.domain}):${entry.title}`);
return;
}
console.error('usage: ua iter <done <id> <commit> <summary> [notes] | add <条目json文件>>');
process.exit(2);
}
// ---- 入口 ----
const [cmd, ...args] = process.argv.slice(2);
@@ -1967,10 +2025,13 @@ switch (cmd) {
case 'loop':
cmdLoop(args);
break;
case 'iter':
cmdIter(args);
break;
case 'mcp':
cmdMcp();
break;
default:
console.log('usage: ua <index|list|search <kw>|info <name> [--json|--md]|recipes [编号] [--json]|docs|tokens [--json|--unused]|style|sync-counts|verify|doctor|commit-lint|lint-iterations|audit:visual [--json|--baseline]|audit:contrast [--json|--list]|audit:motion [--json]|audit:surface [--json|--list]|loop [--json]|changelog|mcp>');
console.log('usage: ua <index|list|search <kw>|info <name> [--json|--md]|recipes [编号] [--json]|docs|tokens [--json|--unused]|style|sync-counts|verify|doctor|commit-lint|lint-iterations|iter|audit:visual [--json|--baseline]|audit:contrast [--json|--list]|audit:motion [--json]|audit:surface [--json|--list]|loop [--json]|changelog|mcp>');
process.exit(cmd ? 1 : 0);
}
+4 -3
View File
@@ -24,9 +24,10 @@
"ua audit:visual [--json|--baseline]:视觉漂移门禁,统计皮肤包里各类硬编码视觉字面量(字号/动效时长/阴影/圆角/焦点环/内距/控件高度/字重/行高/层级/边框色)并与 .ua/visual-baseline.json 棘轮比对,超基线退出码 1;--baseline 重写基线,--json 出机器可读计数与每类命中行号(line / prop / value / hint,Agent 可直接定位)",
"ua audit:contrast [--json|--list]:对比度门禁,把令牌里 18 组「前景色 × 底色」配对按 WCAG 公式算成对比度(求值支持 var() 与 color-mix,半透明底先压到页面底色),亮/暗 × 常规/高对比四套分算,低于阈值(正文 7 / 常规 4.5 / 禁用 2.6 / 非文本 3)退出码 1;--json 出逐对明细,--list 只列配对表",
"ua audit:motion [--json]:动效兜底门禁,两类必查——① 带 infinite 的动画逐个核对是否在 prefers-reduced-motion 块里 animation: none;② 悬停 / 按压的位移(translate)是否在减少动效下归零(认 ua- 类通配的全局规则)。缺兜底退出码 1(--json 出逐条明细)",
"ua audit:surface [--json|--list]:表面阶梯门禁,逐档核对底色对底色的亮度比(页面底 → 表面 → 悬停底 → 按下底 ≥1.04、凹陷底 ≥1.02),亮/暗 × 常规/高对比四套主题各算一遍,不达标退出码 1;对比度只管文字读不读得清,管不到「悬停有没有反馈」(实测过 1.008 的悬停底)",
"ua audit:surface [--json|--list]:表面阶梯门禁,逐档核对底色对底色的亮度比(页面底 → 表面 → 悬停底 → 按下底 ≥1.04、凹陷底 ≥1.02),外加两档半透明淡底压在表面上的可见度(选中底 ≥1.04、悬停底 ≥1.04),亮/暗 × 常规/高对比四套主题各算一遍,不达标退出码 1;对比度只管文字读不读得清,管不到「悬停有没有反馈」(实测过 1.008 的悬停底)",
"ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe;get_component 支持 format: md 直接取可粘贴的 Markdown 片段",
"ua loop [--json]:迭代循环诊断,一眼看清锁心跳(是否 15 分钟内)、上次留痕、backlog 待办与视觉欠账数,并给出本轮该不该抢锁、下一条该选谁;多实例错位接力时不必翻调度器数据库"
"ua loop [--json]:迭代循环诊断,一眼看清锁心跳(是否 15 分钟内)、上次留痕、backlog 待办与视觉欠账数,并给出本轮该不该抢锁、下一条该选谁;多实例错位接力时不必翻调度器数据库",
"ua iter done <id> <commit> <summary> [notes]:一轮迭代收尾——backlog 条目置 done、按固定字段顺序追加 .ua/iterations.log 一行(ts 由本机时钟生成带 +08:00,commit 校验 7-40 位哈希);ua iter add <条目json文件> 补一条自选题(id 唯一 + 五字段齐全校验)"
],
"keyTypes": [
"ua.manifest.json 清单结构",
@@ -55,4 +56,4 @@
],
"components": []
}
}
}