# Steps(自 SKILL.md 外置) > 本文由 stale-doc-count-sweep 的 SKILL.md「Steps」一节逐字外置而来(渐进展开:主文件只留索引与指针)。步骤号沿用原文,未重编。 1. **实测真值:先剔伪命中,再双源互证**(不抄用户口述、不抄另一处文档)。逐数走上面那张表;两个来源对不上时**先找分歧根因**(把全部命中逐条打印出来看),不要取第一个、不要取平均,并在结论里写明分歧与取舍(实测:菜单 `#page=` 计数 29 对 注册表 28,根因是头部注释示例行)。 ```bash cd "" ls simulator/tests/ref/shot_*.bin | wc -l # 张数(判据 = 入库实数) awk '/^PAGES = \[/,/^\]/' simulator/tests/check_content_pages.py | grep -cE '^ *\("' # 页数(第二个来源,两者必须一致) grep -n '#page=' ui_menu_tree.txt | head -3 # 先看首条是不是注释示例行,再定叶数 grep -rn 'UI_PAGE_MAP_COUNT' Drivers/BSP/UI/ui_page_registry.h # 已绑叶数(生成物) grep -c '\.bitmap_index' Drivers/BSP/UI/ui_font_cn_12.c # 314 = 含 notdef;字形 = 该数 - 1 & '.venv\Scripts\python.exe' -c "import yaml;d=yaml.safe_load(open('.pre-commit-config.yaml',encoding='utf-8'));print(sum(len(r['hooks']) for r in d['repos']))" # 钩子数口径 ``` ⚠ **权威脚本输出乱码时**(`check_font_charset.py` 在 GBK 代码页下整行打印成 U+FFFD 替换字符串 + `: PASS`,数字全读不出来),**改为直接数源文件里符号出现次数**(`.bitmap_index` / `.range_start` / 码点唯一化)——不许臆读乱码,也不许因此退回去抄文档里的旧数。 2. **全形态 + 全命中面扫描**。一个数一次列出它全部历史形态,别只扫当前旧值:本轮 = `122/217`、`179/274`、`185/280`、`47 → 53`、`53 → 60`、`PAGES = 53`、`UI_PAGE_MAP_COUNT = 22`、`已绑 22 叶`、`未绑 11`、`内容页 53 页`。 ```bash grep -rn --include=*.md --include=*.py --include=*.yaml --include=*.txt --include=*.c --include=*.h \ -E '122 唯一字|217 字形|179 汉字|274 字形|185 唯一字|280 字形|47 → 53|53 → 60|内容页 53|PAGES = 53|UI_PAGE_MAP_COUNT = 22|已绑 22|未绑 11' . \ | grep -v -E '^\./(ci-artifacts|\.mnemon|\.dsh/runs)/' ``` **命中面比「文档」宽**,至少包含:`docs/**`、`AGENTS.md`、`.pre-commit-config.yaml`(**钩子 `name:` 显示名内嵌页数**:`name: UI 像素回归(菜单 8 状态 + 内容页 60 页 黄金参考)` 就是命中点)、`scripts/*.py`、`simulator/**/README.md`、以及 **`.dsh/skills/*/SKILL.md`**(本窗 `font-regen` 正文自带 179/274 计数,直到 step 150 才发现;编号门禁退役那轮 `id-family-semantic-migration`、`task-worktree` 两个 skill 正文与 `scripts/README.md` 同样是引用面)。 先列出 `文件:行:全文`,**判定前必须看整行**——`cut -c1-120` 会截掉后半句(那里常写着日期戳或口径)。 3. **逐条分类,五类各走各的路**(本 playbook 的核心,别用一条规则套全部命中): | 类别 | 判定 | 动作 | |---|---|---| | 当前态陈述 | 描述「现在是多少」 | 改成实测真值 | | 变更记录历史行 | 形如「47 → 53 页」的当日事实 | **原样冻结不回改**——但这条**只适用于保留变更记录的 5 篇真台账**(台账 / 被别处当唯一证据出处的文档,清单 → `docs/文档规范.md` §5.1)与 `scripts/archive/` 归档;其余文档没有变更记录节,**不再追加任何流水行**,数字同步只改**当前态** | | 同数不同义 | `最坏路径 < 40KB` 这类内存/绘制缓冲红线 | **不动**,并在结论里写明「为什么不动」 | | 带日期戳的测量 | 「2026-10-26 实跑 …覆盖当时 40 页中的」 | **不臆改新数**,只补「当时」两字锁口径;新数留待下次实跑回填 | | 数字对但**口径描述错** | 数没错、句子把口径讲错了(实测:写「218 字(含 ASCII/符号,共 313 字形)」,而 `ui_cn_chars.txt` 的 218 个码点**全为非 ASCII**) | **另起一笔只改口径的提交**(`6daa01ae`),不与数字同步混笔;这类命中在任何数字 diff 里都看不见,只能把口径句逐句读一遍 | 拿不准就回到第 2 步把整行打印出来再判;判错历史行 = 把当天事实改成假账。 4. **每处先读现文,再逐条字面替换并断言命中数**(弃用整文件正则批处理)。本窗两度返工都是没先读现文:step 45 断言 `HIT 0` 抛错(目标串与现文不一致),step 44 是脚本语法错。脚本写成临时文件再跑(内嵌 heredoc/模板字符串里的反引号会截断整脚本),按**字节**替换以保 BOM 与行尾不动: ```python import pathlib, sys sys.stdout.reconfigure(encoding='utf-8', errors='replace') JOBS = [ # (文件, old, new, 期望命中数) ('AGENTS.md', '内容页 53 页黄金参考', '内容页 60 页黄金参考', 1), ('.pre-commit-config.yaml', '内容页 53 页 黄金参考', '内容页 60 页 黄金参考', 1), ('docs/架构设计/项目架构总览.md', '122 字/217 字形', '218 字/313 字形', 1), ('docs/UI设计/UI_自动化测试流程.md', '文本断言 **310 条**、覆盖 40 页中的', '文本断言 **310 条**、覆盖当时 40 页中的', 1), ] for path, old, new, expect in JOBS: p = pathlib.Path(path); raw = p.read_bytes() o, n = old.encode('utf-8'), new.encode('utf-8') got = raw.count(o) assert got == expect, f'{path}: 命中 {got},期望 {expect}(未写入)' assert o != n, f'{path}: old 与 new 相同' p.write_bytes(raw.replace(o, n)) print(f'OK {path}: {old} -> {new} x{got}') ``` 每行打印 `old -> new xN`,收口时这串输出就是「改了哪几处」的账。脚本报错/没编译过,**先 `git diff --stat` 确认零写入**再换写法(不要在半落地状态上叠改)。 - **反引号**:TS 侧先用可见占位符(如 `~TB~`)写文本,落盘前再还原成真反引号 —— `const BT = String.fromCharCode(96); const q = s => s.split(TOKEN).join(BT)`(本窗实测:直接把带反引号的串塞进模板字符串/heredoc,整批零落地或把变更记录行写坏)。用 `tools.edit` 批量改时同样先 `await tools.read` 现文再传 `old_string`;批量里若某条 `old_string` 与 `new_string` 相同会报 `must differ` —— **只有该条作废、其余已落盘**,别整批重跑。 - **历史行被写坏**(反引号及紧随其后的字符经工具传递被吞 → 变更记录行只剩半行、尾部出现 `??` 这类残迹):立刻按**无反引号兜底写法整行重写**——裸词 `accident / oper / alarm / start / yk`、页面绑定写成不带反引号的 `#page=`——而不是拿同样带反引号的串再改一遍(实测连返工三次:step 81→82→83→85)。 5. **一笔只装一类事**。数字同步一笔(`cbb4fdb8`:11 处 + 变更记录一行);口径更正另起一笔(`6daa01ae`);提交后复扫逮到的漏网当前态再补独立一笔(`11d60057`)。钩子一增一减写成一个当日行的两个阶段(51 → 52 加 `id-check-new`、随即 52 → 51 摘 `id-check`),别写两行互相打脸。提交说明如实写(「先认一笔自己的错:上一笔的未绑 21 叶是探针错」),别把口径修正包装成「补充说明」。 6. **被改文件全文通读,核对其余小节**——不是只看 diff。同一文档内互相引用的小节必须一致(本窗产品需求:§2.4 立项未排、§5.14 FR-SOE-3、§8.2 绑定现状、§8.3 已绑分页表、§9.1 差距表、§9.2 顺序表六处同时引用这四个数;`docs/任务规划/README.md` §1 当前执行表 vs §2 全项目顺序表)。若发现上一笔自己写进文档的数**口径就错了**,明确认错并改正,同时把**可复核的重算方法**写进文档(「按缩进判叶子:有更深缩进后继者即非叶子」),防止再错。 7. **提交后再复扫一遍**(收尾硬步骤,不许省,也不许只在提交前扫)。按第 2 步的**全部**旧形态整仓再扫一次,逐条分类为 当前态 / 历史行 / 同数不同义:本窗 `cbb4fdb8` 提交后复扫得 14 条命中,12 条以上都在变更记录历史行(`47 → 53`、`53 → 60`、`122/217` 冻结),**唯一残留的当前态命中**是 `docs/架构设计/项目架构总览.md` §3「界面层」那行 → 单独补一笔 `11d60057`。说不出归属的就是漏改。 ```bash grep -rn -E '22 叶|未绑 11|11 叶|53 页|122 唯一字|217 字形|185 唯一字|280 字形|内容页 53|PAGES = 53|UI_PAGE_MAP_COUNT = 22' \ . | grep -v -E '^\./(ci-artifacts|\.mnemon|\.dsh/runs)/' ``` 8. **跑门禁、分笔提交**(`git commit` 属 Ask first,须用户明确指示后再做): ```bash & '.venv\Scripts\python.exe' scripts/check_doc_links.py --all --sections-strict # 期望「文档路径检查通过(N 个文档)」 & '\.venv\Scripts\python.exe' scripts/archive/id_gate/check_ids_new.py --range A..B # 编号形态普查(两道门禁 2026-10-28 / 2026-10-29 都已退役,不再是提交钩子) & '.venv\Scripts\python.exe' scripts/ci_local.py # 或 pre-commit run --files <被改文件> ``` PowerShell 形态把 `./.venv/Scripts/python.exe` 换成 `& '.venv\Scripts\python.exe'`。提交信息一律 `git commit -F `,别用 `-m`(中文长标题易超 100 字符、终端代码页会转码);标题 ≤100、**正文每行 ≤80**(实测把正文压到 66/78 字符才过 gitlint)。