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
+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": []
}
}
}