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 / 结尾换行齐。
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: mem-budget
|
name: mem-budget
|
||||||
description: 嵌入式 RAM/栈预算核算工作流:Keil .map/.htm 静态报告(ram_report/stack_report)+ 串口运行时水位叠加 + DBG 日志成本模型(每条约 90B Flash/216B 栈)+ 跨树(worktree)测量的身份口径——脚本按 __file__ 定位产物、什么才算一次新测量、多条链同涨同一数值的共享帧/幻影环归因法。"改任务/缓冲/栈之前必须核算"与"这个数属于哪棵树、哪次构建"的固定动作和判读口径。
|
description: 嵌入式 RAM/栈预算核算工作流:Keil .map/.htm 静态报告(ram_report/stack_report)+ 串口运行时水位叠加 + 权威构建后的台账回填(ram_baseline.py --update 只往 ram_baseline.tsv 追加趋势行、--write 才同步 RAM预算台账 受管区、check_mem_numbers.py --write 同步文档快照区)+ DBG 日志成本模型(每条约 90B Flash/216B 栈)+ 跨树(worktree)测量的身份口径——脚本按 __file__ 定位产物、什么才算一次新测量、多条链同涨同一数值的共享帧/幻影环归因法。"改任务/缓冲/栈之前必须核算"、"这个数属于哪棵树、哪次构建"与"内存/Flash 数字钩子红了、提交前台账怎么回填"的固定动作和判读口径。
|
||||||
whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报 L6406E 或 RAM 溢出、串口 [STACK] 水位余量低于底线、要回答"这个改动花多少 RAM/Flash";以及要复算或比对另一个 worktree 的栈数字、独立验证某个栈回归该记在哪一笔提交上、判断多次启动的栈峰值抖动算不算劣化时。
|
whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报 L6406E 或 RAM 溢出、串口 [STACK] 水位余量低于底线、要回答"这个改动花多少 RAM/Flash";以及要复算或比对另一个 worktree 的栈数字、独立验证某个栈回归该记在哪一笔提交上、判断多次启动的栈峰值抖动算不算劣化时;还有 @build 之后要把内存数字回填进台账/文档、纠结该跑 ram_baseline.py --write 还是 --update、提交时 mem-budget / mem-numbers 两条钩子报"与 .map 不一致"时。
|
||||||
---
|
---
|
||||||
|
|
||||||
# mem-budget — RAM/栈预算核算
|
# mem-budget — RAM/栈预算核算
|
||||||
@@ -115,8 +115,86 @@ grep -aE 'Last Updated|Maximum Stack Usage' MDK-ARM/STM32F407-Demo/STM32F407-Dem
|
|||||||
**静态最坏链是上界(硬线)、运行时峰值是下界证据(趋势)**,两者都要看;余量 <1KB 底线要上报。
|
**静态最坏链是上界(硬线)、运行时峰值是下界证据(趋势)**,两者都要看;余量 <1KB 底线要上报。
|
||||||
- **RAM 总量**:`ram_report.py` 对 Grand Totals 与 `docs/内存管理/` 预算表核对;
|
- **RAM 总量**:`ram_report.py` 对 Grand Totals 与 `docs/内存管理/` 预算表核对;
|
||||||
链接错 `L6406E` = 已经溢出,用按文件表找大头。
|
链接错 `L6406E` = 已经溢出,用按文件表找大头。
|
||||||
- 任务表一致性由 pre-commit `task-table` 钩子保证;`mem-budget` 钩子拦 RW_IRAM1/RW_CCM >92%;
|
- 任务表一致性由 pre-commit `task-table` 钩子保证;`mem-budget` 钩子拦 RW_IRAM1/RW_CCM >92%,
|
||||||
`mem-numbers` 钩子核文档里的内存数字——数字与树/时刻脱钩时就是这两条钩子会响。
|
**并核台账受管区是否与 .map 同源**;`mem-numbers` 钩子核文档里的内存数字——
|
||||||
|
数字与树/时刻脱钩、或**构建后只跑了 `--update` 没回填受管区**时,响的就是这两条(回填见下一节)。
|
||||||
|
|
||||||
|
## 权威构建后的台账回填(三个旗标,别混)
|
||||||
|
|
||||||
|
`.map` 是内存/Flash 数字的**唯一事实源**;台账与文档里的数字由脚本从它生成——**不手抄、不手改**
|
||||||
|
(`docs/内存管理/RAM预算台账.md` 首段就写着"数字不手抄")。三份落点 = 三个旗标,一条命令只干其中一件:
|
||||||
|
`ram_baseline.py` 的 `--gate/--write/--update/--trend` 是 `add_mutually_exclusive_group()`
|
||||||
|
(`scripts/ram_baseline.py:415-419`),想干两件就得调两次。
|
||||||
|
|
||||||
|
| 落点 | 旗标 | 实际改了什么 |
|
||||||
|
|---|---|---|
|
||||||
|
| `scripts/ram_baseline.tsv` 趋势基线(顺带刷 `scripts/size_attestation.json` 同码凭证) | `ram_baseline.py --update` | **只**追加/覆盖当日那一行趋势(Code/RO/RW/ZI + 三区水位 + Top-10 RW+ZI)。**不碰台账正文** |
|
||||||
|
| `docs/内存管理/RAM预算台账.md` 的 `<!-- MEM_BUDGET:BEGIN/END -->` 受管区 | `ram_baseline.py --write` | 从 .map 重渲染区域水位 + 子系统配额对照(打印「已同步台账受管区。」或「台账受管区已是最新。」) |
|
||||||
|
| `docs/内存管理/RAM使用分析与优化方案.md` 的 `<!-- MEM_SNAPSHOT:BEGIN/END -->` 快照区 | `check_mem_numbers.py --write` | 从**同一份** .map 重渲染内存/Flash 快照表(打印「已更新内存快照:…」) |
|
||||||
|
|
||||||
|
**AGENTS.md 与台账 §4 那句「每次 `@build` 后跑 `scripts/ram_baseline.py --update`」喂的是趋势行,不等于回填受管区。**
|
||||||
|
实测代价:照那句跑完 `--update` 就提交 → `mem-budget`、`mem-numbers` 两条钩子当场红。
|
||||||
|
`mem-budget` 的 entry 是 `python scripts/ram_baseline.py --gate`(`.pre-commit-config.yaml:571`)——它在 92% 阈值之外
|
||||||
|
还核**台账受管区与 .map 的一致性**,漂移即 `[FAIL] 台账受管区与 .map 不一致(区域水位或子系统配额表漂移)`
|
||||||
|
并打印 `修复:python scripts/ram_baseline.py --write`(`scripts/ram_baseline.py:221-224`);
|
||||||
|
`mem-numbers` 的 entry 是 `python scripts/check_mem_numbers.py --scan`(同文件 :193),不一致时打印
|
||||||
|
`修复:运行 python scripts/check_mem_numbers.py --write`(`scripts/check_mem_numbers.py:416`)。
|
||||||
|
改用两个 `--write` 后重提,同一笔提交触发的 26 条钩子全 Passed。
|
||||||
|
|
||||||
|
### 提交前固定顺序(构建 → 回填 → 真钩子提交)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# cwd = 项目根,一律 .venv 解释器
|
||||||
|
# 0) 权威构建先行(0 错 0 警):回填读的是**盘上那份 .map**,构建没落定就 --write = 把旧数字写进台账,钩子照样红
|
||||||
|
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py
|
||||||
|
|
||||||
|
# 1) 趋势行(纪律项,不是回填):覆盖 tsv 当日行 + 顺带刷体积同码凭证
|
||||||
|
& '.venv\Scripts\python.exe' scripts/ram_baseline.py --update
|
||||||
|
|
||||||
|
# 2) 回填台账受管区(mem-budget 钩子核的就是这一段)
|
||||||
|
& '.venv\Scripts\python.exe' scripts/ram_baseline.py --write
|
||||||
|
|
||||||
|
# 3) 回填文档快照区(mem-numbers 钩子核的就是这一张表)
|
||||||
|
& '.venv\Scripts\python.exe' scripts/check_mem_numbers.py --write
|
||||||
|
|
||||||
|
# 4) 逐字自跑提交时会跑的这两条 entry(比事后翻 pre-commit 输出快)
|
||||||
|
& '.venv\Scripts\python.exe' scripts/ram_baseline.py --gate # 0 = 阈值 + 受管区都过
|
||||||
|
& '.venv\Scripts\python.exe' scripts/check_mem_numbers.py --scan # 0 = 快照区与 .map 一致
|
||||||
|
|
||||||
|
# 5) 把刚落盘的落点一并 add,然后**真钩子**提交(绝不 --no-verify、绝不手改数字)
|
||||||
|
git add docs/内存管理/RAM预算台账.md docs/内存管理/RAM使用分析与优化方案.md scripts/ram_baseline.tsv scripts/size_attestation.json
|
||||||
|
```
|
||||||
|
|
||||||
|
退出码:`ram_baseline.py` 0=通过或跳过 / **1=阈值超限或受管区漂移** / 2=.map 解析失败;
|
||||||
|
`check_mem_numbers.py` 0=一致 / 1=不一致、缺受管锚点或找不到事实源。
|
||||||
|
|
||||||
|
### 回填链上的坑(实测)
|
||||||
|
|
||||||
|
- **`--write` 说「已是最新」而钩子还红** ≠ 钩子坏了,是**落点找错了**:tsv / 台账受管区 / 文档快照区
|
||||||
|
三处各归一个旗标。按上表对号,别把同一个旗标重跑三遍;手改台账数字更不行——下次 `--write` 直接覆盖你,
|
||||||
|
钩子仍按 .map 判漂移。
|
||||||
|
- **tsv 落后只 WARN、不拦**:`--gate` 对「tsv 末行落后于当前 .map」只打印 `[WARN] …请跑 --update`
|
||||||
|
(`ram_baseline.py:232`),**拦下来的是受管区漂移**。所以"钩子绿"绝不等于"趋势行也补了",反之亦然,各查各的。
|
||||||
|
- **`--gate` 无 .map → SKIP,而脚本自己写明「SKIP 不等于 PASS」**(`ram_baseline.py:177`):
|
||||||
|
新开的 task-worktree 从没在这棵树上构建过时必然走到这里;同一状态下 `--write` 直接
|
||||||
|
`[FAIL] 找不到 .map 事实源,先 @build`(`ram_baseline.py:250`)——不是代码缺陷,是这棵树还没测过体积。
|
||||||
|
无需 Keil 的强制兜底是 `scripts/check_size_gate.py --local`(= `scripts/ci_local.py` 的 L0c 步)与 CI 的 `--ci`。
|
||||||
|
- **`--map <path>` 只换读、不换写**:`LEDGER`(`ram_baseline.py:45`)与 `TARGETS`(`check_mem_numbers.py:53-54`)
|
||||||
|
都由 `REPO_ROOT = Path(__file__).resolve().parent.parent` 算出。拿主仓脚本配 `--map <另一棵树>/…map` 去 `--write`,
|
||||||
|
等于用别的树的数字**改写主仓的台账**——比"半棵树混合数字"更糟,因为它落盘了。回填永远用本树自己的脚本。
|
||||||
|
- **`--scan` 的散句告警 `--write` 不替你改**:正文里手抄的旧值("现 RW_IRAM1 62.6%" 这类散句)只告警,
|
||||||
|
脚本明确写着「`--write` 不会自动改写正文」(`check_mem_numbers.py:418`)→ 人工按受管区改那一行
|
||||||
|
(Edit 精确替换;含中文的文档绝不整文件重写)。
|
||||||
|
- **`--update` 的盖戳排在索引后面**:它顺带调 `check_size_gate.cmd_stamp()`(`ram_baseline.py:345`),
|
||||||
|
而凭证指纹按 **git 索引内容**生成,有未 staged 的脏改动时**拒盖**,且这里只 `[WARN]`、不改返回值
|
||||||
|
→ 现象是 tsv 多了一行而凭证仍是旧的,要等 `ci_local` L0c / CI 体积门禁那一侧才报出来。
|
||||||
|
要么先 `git add` 再 `--update`,要么补跑 `python scripts/check_size_gate.py --stamp`。
|
||||||
|
- **改写型钩子当场改文件是流程不是故障**:`trailing-whitespace` / `end-of-file-fixer` 报
|
||||||
|
`files were modified by this hook`(新建的 .c 最常触发)时,先读 `ci-artifacts/cui.log` 的真实报错确认是哪几条红,
|
||||||
|
`git add` 被改写的文件后重提。两条改写红 + 两条内存红混在同一次里时,别顺手把内存那条也当"钩子误报"绕过去。
|
||||||
|
- **`mem-budget` 触发面很宽**:`files:` 覆盖 `App/` `Drivers/BSP/` `Src/` `Inc/` `Lib/lv_conf.h`
|
||||||
|
`test/board/` `scripts/ram_baseline.` `docs/内存管理/` `.pre-commit-config.yaml`,且 pre-commit 4.x 已无
|
||||||
|
`always: true` —— 纯 UI / 纯文档提交照样被它拦:盘上 .map 与台账不同源就红,跟这笔提交碰没碰内存文件无关。
|
||||||
|
|
||||||
## 运行时峰值抖动与"算不算劣化"
|
## 运行时峰值抖动与"算不算劣化"
|
||||||
|
|
||||||
@@ -147,6 +225,9 @@ grep -aE 'Last Updated|Maximum Stack Usage' MDK-ARM/STM32F407-Demo/STM32F407-Dem
|
|||||||
3. 树身份已按 `tree/commit + 时刻 + 产物来源` 写进提交正文/台账,而不是只留在终端输出里;
|
3. 树身份已按 `tree/commit + 时刻 + 产物来源` 写进提交正文/台账,而不是只留在终端输出里;
|
||||||
4. 结论里互比的数字必须来自**同一棵树同一次链接**;跨树比较要各自给出树与时刻;
|
4. 结论里互比的数字必须来自**同一棵树同一次链接**;跨树比较要各自给出树与时刻;
|
||||||
5. 涉及劣化判定时注明依据的是静态最坏链(硬线)还是运行时峰值(趋势),并已计入跨启动抖动。
|
5. 涉及劣化判定时注明依据的是静态最坏链(硬线)还是运行时峰值(趋势),并已计入跨启动抖动。
|
||||||
|
6. 要入库的数字:本轮已按「`@build` → `ram_baseline.py --update` → `ram_baseline.py --write` →
|
||||||
|
`check_mem_numbers.py --write`」回填,并逐字自跑 `ram_baseline.py --gate` 与 `check_mem_numbers.py --scan` 各返 0;
|
||||||
|
台账 / 文档 / tsv 的改动与代码同一笔入库,**未手改数字、未用 `--no-verify`**。
|
||||||
|
|
||||||
## 其它项目怎么套
|
## 其它项目怎么套
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
---
|
||||||
|
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 号 + 每笔改了哪些文件 + 没动的那几类及理由
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: task-worktree
|
name: task-worktree
|
||||||
description: 多任务并行开发的空间管理(git worktree 生命周期):new 开一棵工作树 + 任务分支、目录联接共享 .venv 与模拟器 SDK、活跃任务硬上限 3 与腾位、build/bench 两类独占锁(含非登记任务占锁后「缺台账」释放不掉的正解)、done 预演合并回主干、门禁未全绿的补基线收尾、并集合并残留审计。规格见 docs/环境与部署/并行任务工作树方案.md。
|
description: 多任务并行开发的空间管理(git worktree 生命周期):new 开一棵工作树 + 任务分支、目录联接共享 .venv 与模拟器 SDK、活跃任务硬上限 3 与腾位、build/bench 两类独占锁(含非登记任务占锁后「缺台账」释放不掉的正解)、done 预演合并回主干、门禁未全绿的补基线收尾、并集合并残留审计,以及**把仓外 detached worktree 当净室测量仪**——门禁指标异常增长时一次只注入一份在飞文件、与基线差分做逐文件归因。规格见 docs/环境与部署/并行任务工作树方案.md。
|
||||||
whenToUse: 要同时推进多个任务、担心同分支同文档互相冲突;要为新任务开一个隔离空间;要把任务合并回主干;或遇到 `new` 报「已有 3 个活跃任务(上限 3)」、`lock release` 报「任务 X 不存在(缺台账)」、`done` 报「门禁未全绿:工作树与分支保留,未清场」、两个任务抢 COM6 / UV4、"改了文档被覆盖"、并集合并残留时。
|
whenToUse: 要同时推进多个任务、担心同分支同文档互相冲突;要为新任务开一个隔离空间;要把任务合并回主干;或遇到 `new` 报「已有 3 个活跃任务(上限 3)」、`lock release` 报「任务 X 不存在(缺台账)」、`done` 报「门禁未全绿:工作树与分支保留,未清场」、两个任务抢 COM6 / UV4、"改了文档被覆盖"、并集合并残留时;或**门禁指标异常增长(如 id-check 的不可判定数从 1,162 涨到 1,178、+16)却查不出是哪个文件 / 哪条任务道造成的**、主工作树压着多条在飞改动而不许 stash / 提交 / 改门禁脚本、要按 HEAD 基线只读出像验收时。
|
||||||
---
|
---
|
||||||
|
|
||||||
# task-worktree — 多任务并行开发空间(短命令 `@task`)
|
# task-worktree — 多任务并行开发空间(短命令 `@task`)
|
||||||
@@ -75,7 +75,7 @@ PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktr
|
|||||||
`conflict` 字样的片段 → 据此误判「有冲突」。实测:`tail -2` 误读成 conflict,放宽到 `tail -10` 才看到
|
`conflict` 字样的片段 → 据此误判「有冲突」。实测:`tail -2` 误读成 conflict,放宽到 `tail -10` 才看到
|
||||||
`[ok] 无冲突,预演结果树 <sha>`。dry-run 全文只有 5~8 行,直接读全量。
|
`[ok] 无冲突,预演结果树 <sha>`。dry-run 全文只有 5~8 行,直接读全量。
|
||||||
|
|
||||||
## 分步操作(三条运行时机制)
|
## 分步操作(四条运行时机制)
|
||||||
|
|
||||||
### A. `new` 被拒「已有 3 个活跃任务(上限 3)」——怎么腾位
|
### A. `new` 被拒「已有 3 个活跃任务(上限 3)」——怎么腾位
|
||||||
|
|
||||||
@@ -179,6 +179,65 @@ git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.m
|
|||||||
7. 清场完成后再敲一次 `done <ID>` 会得到 `前置/环境错误:任务 <ID> 不存在(缺台账 …json)`(exit 2)——
|
7. 清场完成后再敲一次 `done <ID>` 会得到 `前置/环境错误:任务 <ID> 不存在(缺台账 …json)`(exit 2)——
|
||||||
**这不是失败**:清场的最后一步 unlink 了台账。判完成以 `git log` 为准。
|
**这不是失败**:清场的最后一步 unlink 了台账。判完成以 `git log` 为准。
|
||||||
|
|
||||||
|
### D. 门禁指标异常增长 → 逐文件归因(把净室工作树当**测量仪**,主树全程只读)
|
||||||
|
|
||||||
|
**场景**(2026-10-27 实测):`id-check` 突然 FAIL「不可判定 1,178 > 上限 1,166」——比上一轮量到的 1,162 又涨了 **+16**,
|
||||||
|
而主工作树里同时压着 4 条在飞任务道的未提交改动。**不许 stash / checkout / reset**(那是搬动甚至丢弃别人未提交的工作,
|
||||||
|
项目级 Never;`git status` 一乱,整条道的现场就没了),**不许改门禁脚本本身**(它就是那把尺子,改了数字就不可信),
|
||||||
|
**也别在脏主树里跑门禁**(那测的是 4 条道 + 未跟踪文件的混合体,永远分不出谁)。正解:**在仓库之外另起一棵
|
||||||
|
detached HEAD 的干净工作树当净室,一次只往里注入一份在飞文件**,与基线差分即得每个文件的独立贡献。两步:
|
||||||
|
**净室建树 → 单文件注入测量**。
|
||||||
|
|
||||||
|
1. **建净室 + 先落基线数**(路径必须**在仓库目录之外**;铁律 7 对临时树同样成立——放仓内就污染 `git status` /
|
||||||
|
`pre-commit --all-files` / 全库搜索):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MAIN="D:/Code/DTU Code/STM32F4-Base" # 主工作树绝对路径(含空格 → 变量整体加引号)
|
||||||
|
WT="$MAIN/../_wt/und-attr" # 仓外同级 _wt/ 下;一次测量固定用同一个名字
|
||||||
|
rm -rf "$WT" 2>/dev/null
|
||||||
|
git -C "$MAIN" worktree add --detach "$WT" HEAD >/dev/null 2>&1 || { echo WT_FAIL; exit 9; }
|
||||||
|
git -C "$WT" status --porcelain # 应为空
|
||||||
|
"$PY" "$WT/scripts/check_ids.py" 2>&1 | tail -25 # ← TODO 口径:以 .pre-commit-config.yaml 里 id-check 钩子的 entry/args 为准;基线与注入用**同一条命令**
|
||||||
|
```
|
||||||
|
|
||||||
|
- `--detach`(**不要 `-b`**):净室没有分支、不写 `_wt/.state/<ID>.json` 台账 → **不占 `MAX_CONCURRENT = 3`
|
||||||
|
名额、不出现在 `$TW list`**,也没人会被误指去对它跑 `done`(建与拆都不走 `$TW new` / `$TW done`)。
|
||||||
|
- 能这么量的前提:该门禁**只读仓库内文本**。依赖本树构建产物的门禁(`mem-numbers` / `mem-budget` 要 `.map`)
|
||||||
|
在净室里必然报「找不到 .map 事实源」(见陷阱「新建工作树从未构建过」)——**那类指标不能这么归因**。
|
||||||
|
- `git status --porcelain` 非空 = 有别人的未跟踪文件被带进净室 → 先 `git clean -xdn` **干扫**(别去 `n`)
|
||||||
|
看清会删什么,只挪走多出来的那些再测;语料被污染出来的数字与 CI 不可比。
|
||||||
|
2. **一次只注入一份在飞文件 → 跑同一个扫描器 → 与基线差分**(得到的是每个文件的**独立**贡献,不是合计):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for f in "App/log/sys_log_mcu.c" "test/host/sys_log_mcu/sys_log_mcu_test.c" "docs/子系统设计/运行与自检记录设计说明.md"; do
|
||||||
|
mkdir -p "$WT/$(dirname "$f")"
|
||||||
|
cp "$MAIN/$f" "$WT/$f" # 拷进净室;主树那份只读、一字节不动
|
||||||
|
echo "== $f =="; "$PY" "$WT/scripts/check_ids.py" 2>&1 | tail -25 # ← 与第 1 步同一条命令
|
||||||
|
git -C "$WT" checkout -- "$f" # 只还原净室里自己刚拷进来的那份
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
实测:`run_par.c` / `board_ui_nav.py` / `check_nested_comment.py` / keil `SKILL.md` **各 +0**,
|
||||||
|
而 log 道那三个文件 **+6 / +8 / +2**,合计**正好 +16**。**Σ(逐文件增量) ≠ 观察到的涨幅 → 还有没被注入的
|
||||||
|
在飞文件,或存在文件间交互项**,不许就此交卷。
|
||||||
|
3. **定性增量是哪一类字面量**(只有「谁」没有「为什么」= 给不出可执行的改法):同一计数器在 **HEAD 版**与
|
||||||
|
**在飞版**各数一遍,**差值才是新增行带来的**(存量的不算这条道的账);再对新增行直接 grep。本次 16 处全是
|
||||||
|
**裸形 「字母 T 累进数字 4」**(`T` 在 `scripts/id_registry.json` 的 `bare_check_letters` 里,规范形态是连字符形),
|
||||||
|
全部落进 `skip_bare_unmanaged` —— 到这一步结论才配发给属主。
|
||||||
|
4. **闭合 + 复验主树一个字节没被污染**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git -C "$MAIN" worktree remove --force "$WT" # 净室里只有自己的拷贝、没有原件,--force 安全
|
||||||
|
git -C "$MAIN" worktree list # 不该再有 und-attr
|
||||||
|
git -C "$MAIN" status --porcelain | sort # 与测量前**逐行一致**(测量前先存一份基线清单)
|
||||||
|
```
|
||||||
|
|
||||||
|
「增量 + 字面量类别 + 属主」三者齐了才算归因完成。**同一条道自己「我自查过不是我们」的结论别直接采信**
|
||||||
|
→ 陷阱「队友的自查结论要复核」。
|
||||||
|
**同一棵净室树的第二种用法**:按指定 commit **只读**构建出像 / 烧录验收——不搬动任何在飞改动,也不为台架
|
||||||
|
提前抢发一笔提交;构建与烧录都在这棵树里做(不在主树),仍受 `bench` / `build` 锁串行(铁律 3)。
|
||||||
|
|
||||||
|
|
||||||
## 铁律
|
## 铁律
|
||||||
|
|
||||||
1. **`done` 只能在主工作树执行**——主干分支检出在主工作树,git 不允许同一分支在两处检出。
|
1. **`done` 只能在主工作树执行**——主干分支检出在主工作树,git 不允许同一分支在两处检出。
|
||||||
@@ -247,6 +306,28 @@ git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.m
|
|||||||
可能留下指向已删目录的死规则或重复条目。脚本会自动报「双方都改过」的文件清单 +
|
可能留下指向已删目录的死规则或重复条目。脚本会自动报「双方都改过」的文件清单 +
|
||||||
连续重复行;**权威判据是 base / 两个 tip 三方对比**(`git show <ref>:<file>` 逐项 `grep -F`),
|
连续重复行;**权威判据是 base / 两个 tip 三方对比**(`git show <ref>:<file>` 逐项 `grep -F`),
|
||||||
别用 `tail -N` 目测。
|
别用 `tail -N` 目测。
|
||||||
|
- **⚠ 别用 heredoc 打补丁改门禁脚本造探针**(2026-10-27 实测三连败,白烧一整轮):heredoc 里的 python
|
||||||
|
把门禁脚本改坏——`patched 3` 之后 `PROBE_RC=1`、`SyntaxError: unterminated string literal (detected at
|
||||||
|
line 882)`,而它**正是被测对象**,改完的数不可信;另一处 `replace('\\', '/')`(想把 Windows 反斜杠换成正斜杠)里的反斜杠被多层引号吞掉
|
||||||
|
→ `SyntaxError (detected at line 16)`。**正解**:探针落成**独立 `.py` 文件**(写文件的工具,UTF-8 无 BOM)
|
||||||
|
再 `"$PY" probe.py`;非用 heredoc 不可就用单引号定界 `<<'PY'`(防变量展开),且**路径一律 `as_posix()`、
|
||||||
|
一个反斜杠都不写**——反斜杠字面量要穿「JS 模板串 → bash → shell 引号 → python」四层,必被吞。
|
||||||
|
- **取门禁数字:import 门禁模块,别猜输出、别打补丁**(2026-10-27;grep 源码 / 手抄正则 / 解析 stdout 三种姿势
|
||||||
|
都拿不到可复核的数,换成这句立刻拿到):先读门禁脚本的 `main()`(直读末尾那几十行)看它怎么装配检查器,
|
||||||
|
把**那几行照抄**进探针——`sys.path.insert(0, 'scripts')` → `import check_ids as ci` →
|
||||||
|
`ci.load_json(ci.REGISTRY_FILE, 'reg')` + `ci.load_json(ci.ALIASES_FILE, 'alias')` →
|
||||||
|
`ck = ci.Checker(reg, al, verbose=False)` → `ck.run()` → `print(ck.stats)`。别猜键名:先
|
||||||
|
`print(sorted(ck.stats))` 列出全量计数项,再按精确键取数(顺带 `git diff` 确认门禁脚本自己**没被改过**)。
|
||||||
|
- **别把「不可判定」当成一个数**(2026-10-27):`id-check` 有两种口径——检查器自己算的**硬不可判定**实测只有
|
||||||
|
**2** 处,而门禁红线(`scripts/id_registry.json` 的 `undecidable_total_ceiling`)量的是**派生值**
|
||||||
|
`refs_total - decidable`(实测 8,744 - 7,582 = **1,162**,拆开 = `skip_bare_unmanaged` 834 +
|
||||||
|
`noise_allowed` 326 + 硬不可判定 2,**三数精确闭合**)。所以**带属主登记处的族(TC-XXX 这类)加多少引用都算
|
||||||
|
「可判定」、一点不吃这条余量**,真正吃余量的是**裸形字母+数字**字面量;反过来「把可判定引用改写成散文」才是
|
||||||
|
**稀释比值**的动作,那才是绝对数红线要防的。口径混用会当场给整条道下错指令(本次就下错过、量完立刻撤回)。
|
||||||
|
- **队友的自查结论要复核,小心它测的是错变量**(2026-10-27):log 道做过受控实验、结论是「我们没推高这个数」——
|
||||||
|
他们改的是 TC 号、盯的是 `skip_bare_unmanaged`,那个组合当然不动;而 +16 走的是另一条路(新增裸形 「字母 T 累进数字 4」)。
|
||||||
|
定责用 D 节:逐文件注入 + HEAD/在飞两版同一计数器相减 + 对新增行 grep;闭合后 +16 = 16 处裸形 「字母 T 累进数字 4」,
|
||||||
|
**推翻**了自查结论。口头自查与 `--note` 都不是证据,**可复核的差分数字才是**。
|
||||||
- **`.workbuddy-ai/` 刻意不共享**:记忆不随任务分叉,也不给联接递归删除的机会。
|
- **`.workbuddy-ai/` 刻意不共享**:记忆不随任务分叉,也不给联接递归删除的机会。
|
||||||
任务工作树自己的记忆由 `done` / `abort` 在清场前归并回主工作树。
|
任务工作树自己的记忆由 `done` / `abort` 在清场前归并回主工作树。
|
||||||
- **`--lean` 只省磁盘不省时间**:实测全量 smudge 7.25 s / 83 MB,跳过 smudge 7.05 s / 45 MB,
|
- **`--lean` 只省磁盘不省时间**:实测全量 smudge 7.25 s / 83 MB,跳过 smudge 7.05 s / 45 MB,
|
||||||
@@ -273,6 +354,9 @@ git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.m
|
|||||||
- `git log --oneline -3`:能看到 `merge: task/<ID>-<slug> 合入 <base>` 与其后的补基线提交;
|
- `git log --oneline -3`:能看到 `merge: task/<ID>-<slug> 合入 <base>` 与其后的补基线提交;
|
||||||
`git status --porcelain` 为空(补链改动已提交,才谈得上重跑 `done`)。
|
`git status --porcelain` 为空(补链改动已提交,才谈得上重跑 `done`)。
|
||||||
- `ls _wt/.state/`:该 ID 的台账已无(清场最后一步 unlink);重复 `done` 报「任务不存在」不必再追。
|
- `ls _wt/.state/`:该 ID 的台账已无(清场最后一步 unlink);重复 `done` 报「任务不存在」不必再追。
|
||||||
|
- D 节归因:测量前后 `git -C "$MAIN" status --porcelain` **逐行一致**(主树全程只读);
|
||||||
|
`git worktree list` 里净室路径已消失;逐文件增量之和 **等于**观察到的涨幅(实测 +6+8+2 = +16),且每处增量
|
||||||
|
都指到具体字面量类别(裸形 「字母 T 累进数字 4」)与属主;取数走 `import check_ids` 读 `ck.stats`,且门禁脚本自身 `git diff` 为空。
|
||||||
- 补绿链的每一条都自己 `echo RC=$?` 看到 0,最后以重跑 `done` 打出的**清场日志**(`已摘除联接` /
|
- 补绿链的每一条都自己 `echo RC=$?` 看到 0,最后以重跑 `done` 打出的**清场日志**(`已摘除联接` /
|
||||||
`已删除分支` / `完成(分支此前已合入)`)为成功判据,而不是 `tail` 出来的最后一行。
|
`已删除分支` / `完成(分支此前已合入)`)为成功判据,而不是 `tail` 出来的最后一行。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user