Files
STM32F4-Base/.dsh/skills/stale-doc-count-sweep/SKILL.md
T
wandering1 251268992e docs(skills): 收录陈旧计数同步流程 + 补台账回填动作 + 去掉技能文里自伤门禁的裸形
三只 skill 文件收口(队友写成,Lead 核后入库):

· 新增 `stale-doc-count-sweep`:把今天做的「一个事实数变了之后全仓同步」钉成流程——从权威来源**实测**真值
  → 全形态扫描并**逐条分类**(当前态改 / 变更记录历史行只加当日新行 / 同数不同义的误报不动 / 带日期戳的
  测量只加「当时」锁口径)→ 逐条字面替换且每处断言命中数 → 被改文件全文通读核各小节 → 复扫残留归零 →
  跑门禁分笔提交。这份流程的价值全在**分类**那步:今天有 5 处「27 → 40」是历史事实、1 处「< 40KB」是同数
  不同义(绘制缓冲红线),一并替换就会毁掉它们。
· `mem-budget`:补上 `ram_baseline.py --update`(只往 tsv 追加趋势行)与 `--write`(才同
步台账受管区)的区别、
  以及「提交前台账怎么回填」的动作——这个坑是 Lead 本人 05:0x 踩的(用错一枚参数造出一次真的 mem-budget
  FAIL),写下来比口头转告可靠。
· `task-worktree`:把文中 4 处**自伤**的裸形改成形态描述——该节讲的正是「单字母紧跟数字」这类形态怎么被
  校验 2 计数,结果例子自己就是那种形态,等于技能文自己吃门禁余量。改完不可判定绝对数 **982 → 978**
  (余量 +4),叙述信息量不减:形态与属主都还写清。

⚠ 这条教训值得单记:**文档在讲某个反模式时,自己不要用那个反模式的字面量**——否则每次读它的人都得替它
付余量。同批 `docs/编号规范.md` §4.4 也已登记这类误判面。

自查:`check_ids --strict` exit 0、不可判定绝对数 978 / 上限 1,166;三文件 noBOM / LF / U
+FFFD=0 / 结尾换行齐。
2026-10-27 09:11:18 +08:00

118 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: stale-doc-count-sweep
description: 全仓陈旧计数同步 playbook——某个事实数(黄金参考页数、菜单树叶数、钩子数等)变了之后,从权威来源实测真值 → 全形态扫描命中点并逐条分类(当前态改 / 变更记录历史行只加当日新行 / 同数不同义误报不动 / 带日期戳的测量只加「当时」锁口径)→ 逐条字面替换且每处断言命中数 → 被改文件全文通读核对各小节 → 复扫残留当前态命中归零 → 跑门禁分笔提交。
whenToUse: 某个计数/数字变了要跨 md/yaml/py 同步、用户说「同步一下 / 全仓改一遍 / 这个页数(叶数、钩子数)不对」、发现文档数字与实测不符、或同一份文档两节数字/顺序对不上要先核口径时。
---
# 陈旧计数全仓同步
适用:**一个事实数变了**(黄金参考页数 27→40→47、菜单树叶数、钩子数…),仓库里多处引用它的当前态表述要跟着变,而历史记录、同数异义、带日期戳的测量**不能**跟着变。
同一流程在本仓已跑过两次(`b24b2b5f` 27→40、13 个文件;`31ca4a1f` 40→47、11 个文件)——照下面的序列逐字复跑,别临场重新发明分类。
## Steps
1. **实测真值,不抄任何二手数字**(不抄用户口述、不抄另一处文档)。
```bash
cd "<repo>"
ls simulator/tests/ref/*.bin | wc -l # 内容页黄金参考页数(唯一判据 = 入库的 shot_*.bin 实数)
```
菜单树叶数这类**来源自带口径规则**的数,先读来源文件头再计数:
```bash
sed -n 1,12p ui_menu_tree.txt # 文件头写明「缩进即层级,每级 2 空格;# 开头为注释;空行忽略」
```
叶子判据 = **不存在更深缩进后继行**的行。把「节点行数」「叶子总数」「已绑」「未绑」四个数一次报全,并让
`未绑 = 待做 + 不做` 与分类表逐项闭合。实测量到 42 个节点行、33 个叶子、已绑 21、未绑 12(= 待做 7 + 不做 5)。
2. **全形态扫描**,一个形态都不许只扫一种写法:
```bash
grep -rn --include=*.md --include=*.py --include=*.yaml --include=*.txt \
-E '40 *(页|张|帧|个页面)|内容页.{0,6}40|黄金.{0,6}40' .
```
先列出 `文件:行:全文`,**判定前必须看整行**——`| cut -c1-120` 会截掉后半句(那里常写着日期戳或口径)。
3. **逐条分类,四类各走各的路**(这是本 playbook 的核心,别用一条规则套全部命中):
| 类别 | 判定 | 动作 |
|---|---|---|
| 当前态陈述 | 描述「现在是多少」 | 改成实测真值 |
| 变更记录历史行 | 形如「27 → 40 页」的当日事实 | **原样保留**;只在该文件 §变更记录**顶部加一行**当日的「40 → 47」(含判据与实测来源) |
| 同数不同义 | `最坏路径 < 40KB` 这类内存/绘制缓冲红线 | **不动**,并在结论里写明「为什么不动」 |
| 带日期戳的测量 | 「2026-10-26 实跑 …覆盖 40 页中的」 | **不臆改新数**,只补「当时」两字锁住口径;新数留待下次实跑回填 |
拿不准就回到第 2 步把整行打印出来再判;判错历史行 = 把当天事实改写成假账。
4. **逐条字面替换,每处断言命中数**(弃用整文件正则批处理)。脚本写成临时文件再跑(内嵌 heredoc/模板字符串里的反引号会截断整脚本),按**字节**替换以保 BOM 与行尾不动:
```python
import pathlib, sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
JOBS = [ # (文件, old, new, 期望命中数)
('AGENTS.md', '内容页 40 页黄金参考', '内容页 47 页黄金参考', 1),
('docs/UI设计/UI_自动化测试流程.md', '该集合现已扩至 40 页', '该集合现已扩至 47 页', 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}(未写入)'
p.write_bytes(raw.replace(o, n))
print(f'OK {path}: {old} -> {new} x{got}')
```
每行打印 `old -> new xN`,收口时这串输出就是「改了哪几处」的账。脚本报错/没编译过,**先 `git diff --stat` 确认零写入**再换写法(不要在半落地状态上继续叠改)。
5. **被改文件全文通读,核对其余小节**——不是只看 diff。检查同一文档内互相引用的小节(典型:`docs/任务规划/README.md` §1 当前执行表 vs §2 全项目顺序表;`UI补齐.md` §1 口径 vs §2 清单):顺序、计数、完成状态必须一致。若发现上一笔自己写进文档的数**口径就错了**(例如把 42 个节点行当叶子数写成「未绑 21 叶」),明确认错并改正,同时把**可复核的重算方法**写进文档(「按缩进判叶子:有更深缩进后继者即非叶子」),防止再错。
6. **复扫残留**,排除历史行与误报后,当前态命中必须为 **0**:
```bash
grep -rn --include=*.md --include=*.py --include=*.yaml -E '40 (页|张)|40 个内容页' \
AGENTS.md .pre-commit-config.yaml docs scripts | grep -v '27 → 40'
```
剩下每一条都要能说出归属(历史行 / 误报 / 已加「当时」)。说不出来的就是漏改。
7. **跑门禁,分笔提交**(`git commit` 属 Ask first,须用户明确指示后再做):
```bash
./.venv/Scripts/python.exe scripts/check_doc_links.py --all
./.venv/Scripts/python.exe scripts/ci_local.py # 或 pre-commit run --files <被改文件>
```
PowerShell 形态把 `./.venv/Scripts/python.exe` 换成 `& '.venv\Scripts\python.exe'`。提交信息正文长,一律 `git commit -F <UTF-8 无 BOM 文件>`,别用 `-m`(中文长标题易超 100 字符、终端代码页会转码)。
一笔只装一类事:计数同步一笔、口径/登记簿修正一笔。提交说明如实写(如「先认一笔自己的错:上一笔的未绑 21 叶是探针错」),别把口径修正写成「补充说明」。
## Pitfalls
- **照抄用户给的数**:用户说「从 40 扩到 47」也要自己数 `ref/*.bin`;数出来不一定是 47。真值只认权威来源实测。
- **把节点行当叶子数**:42 是 `ui_menu_tree.txt` 的节点行数,33 才是叶子;文件头写着「缩进即层级」,不读规则就数错(实测把错数写进两份文档,下一轮才自查出来)。
- **整文件正则批处理**:第一次尝试的正则没编译过、整批零落地。改法 = 逐条字面替换 + 每处 `assert count == expect`,宁可慢也要每处可审计。
- **反引号截断**:脚本里含 `ui-golden`、`#page=` 这类反引号时,经 TS 模板字符串/内嵌 heredoc 传递会被截断,表现为「整批零落地」。把脚本落成文件再执行,或用 `chr(96)` / `String.fromCharCode(96)` 拼。
- **只改记得的那节**:上一笔只改 §2 与开篇、漏改 §1,用户当场问出「§1 说 16 叶、§2 说剩 3 项,自相矛盾」。改完必须全文通读同一文档的所有小节。
- **历史行被当陈旧值改掉**:变更记录里的「27 → 40」是当天事实,改了就是伪造历史;正确动作是另加当日新行。
- **误报跟着改**:「< 40KB」是绘制缓冲红线,与页数无关,改了就是引入错误。
- **带日期戳的测量臆改新数**:「2026-10-26 实跑取数 …覆盖 40 页」改成 47 就是把历史测量冒充现值;只加「当时」。
- **用截断的 grep 输出判定**:`cut -c` 截掉的后半句常含日期戳/口径,据此分类必错。
- **新写文本撞编号门禁**:新增的「单字母+数字」裸形(如 `SD2506`、`CP56Time2a`)会把 `id-check` 的不可判定引用绝对数推高(实测 1156 → 1161,上限只剩 5)。收尾看门禁输出的 `und` 计数,涨了先把自己这批文本逐处归因,别去放宽 `noise_literals`。
## Verification
提交前逐条打勾:
- [ ] 真值来自本轮实测命令的输出(`ref/*.bin` 计数 / 按来源规则重算的叶数),并在结论里贴出该数字
- [ ] 分类表逐条列出:改了哪些当前态、哪些历史行保留、哪些误报不动(附理由)、哪些只加「当时」
- [ ] 替换脚本打印了每处 `old -> new xN`,且每处 `assert` 通过(无半落地状态;报错时已确认零写入)
- [ ] 被改文件已全文通读,同文档各小节(§1/§2)与配套 README/清单的顺序、计数一致;口径闭合(如 `未绑 = 待做 + 不做`)
- [ ] 复扫输出中当前态残留 = **0**,每条剩余命中都能归类
- [ ] `check_doc_links.py --all` 打印「文档路径检查通过(N 个文档)」;`ci_local.py` / `pre-commit` 全绿(`passed=N failed=0`、RC=0)
- [ ] 分笔提交完成,回报 commit 号 + 每笔改了哪些文件 + 没动的那几类及理由