4a4ad6bc5f
现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名, 不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体 run_boot_test.py 的真实调用。 - AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改 「编译(/keil-build)」「串口回归(/serial-boot-test)」 - keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree 方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围) - 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入 等 41 行历史证据保留旧名 - gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑 - 顺手补并行方案同行漏网计数 51→52 钩子 验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52 钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
17 KiB
17 KiB
陷阱(实测)
--force不是万能钥匙:它只覆盖四类检查——并发上限、持有者与--task不符、陈旧锁接管、 清场时强制移除工作树。台账缺失这类前置/环境错误(exit 2)跟它无关(实测lock release --force --bench-pass报的是同一句话,锁照旧没放)。同理abort --force也只覆盖「树里有东西」这一条拒绝, 不会替你做 E 节的内容保全——它只会删得更快。- 未跟踪文件在
git log、提交门禁与done的合并里都不留痕(2026-10-29 回收现场):钩子按 HEAD tree 的 diff-index 取文件,未跟踪文件从没进过任何钩子的视野;git log也照不出「唯一未合并」——它压根没被提交过。 所以「这棵树没有遗留改动」这种结论只能由 E 第 2 步的逐文件内容比对给出,log --oneline -1+ 脏计数都不够。 git status --porcelain不带-uall会折叠未跟踪目录(E 第 1 步):「全景」少一项、分桶少一个文件, 最后拆掉的是别人唯一的草稿。回收路径上一律-uall。$TW list空 ≠ 没有工作树:list只遍历台账(A 第 1 步),用户说的「那 3 棵 done 掉」很可能在$TW list里 一个都不存在(实测就是)→ 别照$TW list的空结果回「没有任务」,先git worktree list拿全集再逐棵定性(E 节)。 2026-10-29 起list还会另起一段列出插件树(.dsh-worktrees/,F 节)——"看见"不等于"归done管"。- 「这棵树很久没动」不是回收判据:实测
bench-*两棵既没台账也没脏文件、mtime 很旧,但真正决定去留的是git merge-base --is-ancestor <c> HEAD= MERGED(提交已进主线)+ 未跟踪层为空;而另一棵 mtime 更新的树反而 全是已在主线的副本。判据是内容与祖先关系,不是时间戳。 - 「我记得没建过联接」不是安全判据:判据只有扫出来的
REPARSE: none(E 第 3 步实测bench-*各挂一个.venv联接,正是「不是自己建的」那类)。 - 联接不是符号链接:本仓库
core.symlinks=false,git 里的 symlink 会被检出成 「内容是目标路径的文本文件」。共享.venv只能用 Windows 目录联接(mklink /J, 不需要管理员权限),且联接必须建在版本控制之外。 实测(2026-10-12):用 ctypes 直调DeviceIoControl(FSCTL_SET_REPARSE_POINT)会被系统拒绝(Win32 4392),mklink /J正常——所以脚本走后者,参数以列表交给 subprocess,含空格的路径(本项目DTU Code)由 subprocess 自动加引号。 但读属性用 ctypes 没问题:GetFileAttributesW+FILE_ATTRIBUTE_REPARSE_POINT就是 E 第 3 步的预扫手段。 - 联接建失败不中止
new:只告警并打印手工补建命令,避免留下半成品任务空间; 但该任务里跑不了.venv\Scripts\python.exe,/keil-build/ci_local.py会找不到解释器。 done前主工作树必须干净:否则未提交改动会被卷进合并(补完基线再重跑done时最容易忘,见 C 第 6 步)。- ⚠ 任务树里
check_doc_links.py --all的红多半是simulator/SDL2目录联接的假红:全库扫描会吞下那份 「仓库里的第二份仓库副本」(实测任务树 222 个文档 / 35 处问题全在联接下、RC=1,而主树同命令基线全绿)。 判红必须先过滤simulator/SDL2下的问题行、再与主树同一条命令的基线比对;正解是树内按单文件跑、--all基线合并后在主树跑 → G 节。 - 新建的任务树里没有
ci-artifacts/(被.gitignore忽略 → 不被检出):> ci-artifacts/x.txt与fs.writeFileSync("<tree>/ci-artifacts/x", …)都会ENOENT;cd进树后再用../_wt/x.txt落到_wt/_wt/→No such file or directory(两起实测,第二次直接废掉一整轮体检)。落盘前先mkdir -p ci-artifacts, 树内输出一律用树内相对路径 → G 节。 done报「主工作树不干净」而那份脏改动不是你的(别的并发会话 / 插件留下的,实测两次都是.dsh/skills/serial-boot-test/scripts/run_boot_test.py):不许用 stash / 提交 / 删除去清场(项目级 Never, 你也无从判断它写没写完)→ 走 C2:取证(mtime + diff +hash-object存进 ci-artifacts)→merge-tree预演 → 主树merge --no-ff -F→ 合并前后 blob id 与diff --stat对账。「它看着像废弃的」不是丢弃判据。- ⚠
done的门禁跑在/keil-build之前 → 依赖.map的钩子会误报(2026-10-12 实测):done的顺序是「合并 → 重生成 → 门禁(ci_local) →/keil-build」,而mem-budget(登记簿条目「缩堆迁 CCM 后半(暂缓)」)/mem-numbers是拿文档快照 vs.map比对 —— 门禁跑时.map还是主工作树合并前的旧产物,与刚合并进来的新台账必然不一致 → 误报[FAIL] 台账受管区与 .map 不一致。/keil-build跑完后.map才是新的。 处置(2026-10-25 补全):按 C 节第 5 步的完整链补——check_mem_numbers.py --write→ram_baseline.py --write→ram_baseline.py --update→check_test_coverage.py --update-baseline→check_doc_links.py --all→ 提交 → 重跑done(它会检测到「已合入」直接清场)。 此时主干已含合并提交,绝不能用提示里的reset --hard回退。 done的门禁 =ci_local.py=pre-commit run --all-files(50 条钩子全跑)(2026-10-26):红了先读[SUMMARY] static: pre-commit hooks -- passed=N failed=M skipped=K,按钩子 id 定根因,别一律当「.map滞后」 去跑补基线链。典型例外:generated-fresh(生成器幂等性,触发面tools/**、docs/images/*.svg、UI 生成文件)—— 合进来的分支改了生成器却没重跑产物时,补基线链救不了,处置是在主工作树重跑那个生成器、脚本与产物同一笔提交, 再重跑done。另注意pre-commit run --all-files只跑 pre-commit stage,个别只在 commit-msg / 手工阶段跑的守卫不在此列 (口径见scripts/ci_local.py头部说明)。- ⚠ 新建工作树从未构建过 → 体积类门禁必然红(2026-10-25 实测):
.map是每棵树各自的 构建产物、不入版本库,所以new出来的空间里mem-numbers/mem-budget/ci_local.py的体积门禁步都拿不到事实源,报[FAIL] 找不到 .map 事实源(脚本提示已写明原因,不是代码缺陷)。 同理done的重生成步骤会[skip] 未找到 .map,跳过内存快照同步(需先 /keil-build)。 处置:新空间第一次提交前先/keil-build一次(build锁自动排队,与别的树串行)。与上一条 的区别要分清:上一条是「有.map但是旧的」,本条是「根本没有.map」,处置动作不同。 - ⚠ 只改注释里的文档路径,也会把「体积同码凭证」刷红(2026-10-30 实测,
done testdoc):scripts/check_size_gate.py的判据是索引 blob 指纹(实测 1466 个文件),不是体积数字—— 那笔全库路径同步顺带改了 20 个.c/.h的注释,Program Size逐项没变 (Code=246160 / RO-data=29816 / RW-data=2036 / ZI-data=149700),凭证照旧红:本次代码(指纹 172c6c78b8fb…,1466 文件)与体积测量(指纹 1ed4267c77f6…,2026-10-30T01:15:01)不同码。 处置比上一条那条补绿链短:合并后done自己那次/keil-build已 0 错 0 警,此时只需ram_baseline.py --update重写凭证(改的是scripts/size_attestation.json;同日同数字时ram_baseline.tsv连趋势行都不会新增),别顺手--write去动内存受管快照——数字没变就改受管区, 等于给别的在飞分支造冲突面(台账归属口径见mem-budgetskill)。 判据顺序:先比Program Size与合并前是否逐项相同 → 相同就只补凭证;不同才走 C 节第 5 步全链。 连带的第二个坑:done对「分支已合入」的路径在门禁之前就 return(cmd_done里already and ahead == 0那条分支直接清场),所以补完凭证重跑done不会替你复绿—— 落主干后的全量证据得自己跑:scripts/ci_local.py(实测 47 runner / 3896 断言全绿)- 主树
check_doc_links.py --all(实测 215 个文档 / 2993 处章节引用 rc=0)。
- 主树
- ⚠ 增量构建可能不重链 →
.map数字瞬态不一致(2026-10-12 实测):/keil-build若判定无需重链,.map保持旧值,此时--write生成的快照是瞬态值 (实测同一份源码两次读到 223,560 与 223,724,差 164 B)。以主工作树(集成点) 的.map为准;改完 RAM 相关代码后确认重链再刷新快照。 ram_baseline.py的 help 串里有未转义的%(2026-10-25 实测:某些直调姿势会渲染 argparse help 并崩)—— 查用法读脚本头部 docstring,别靠--help;修脚本本身属mem-budgetskill 的范围。- 合并冲突常落在「生成物」上:
docs/内存管理/RAM使用分析与优化方案.md(mem-numbers 快照)在双方都改过时必冲突,且两边都不能直接选(各自基于不同基线)—— 正确做法是解冲突后用合并后的实际构建重新生成,不要挑一侧。 - 并集合并残留:双方都改过的文件(尤其
.gitignore)会被 git 自动并成并集, 可能留下指向已删目录的死规则或重复条目。脚本会自动报「双方都改过」的文件清单 + 连续重复行;权威判据是 base / 两个 tip 三方对比(git show <ref>:<file>逐项grep -F), 别用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」四层,必被吞。 E 节那两段扫描脚本同理:落成ci-artifacts/*.py再跑(该目录已被忽略,不会污染第 5 步复验用的脏行数)。 - 取门禁数字: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/archive/id_gate/id_registry.json的undecidable_total_ceiling)量的是派生值refs_total - decidable(实测 8,744 - 7,582 = 1,162,拆开 =skip_bare_unmanaged834 +noise_allowed326 + 硬不可判定 2,三数精确闭合)。所以带属主登记处的族(TC-XXX 这类)加多少引用都算 「可判定」、一点不吃这条余量,真正吃余量的是裸形字母+数字字面量;反过来「把可判定引用改写成散文」才是 稀释比值的动作,那才是绝对数红线要防的。口径混用会当场给整条道下错指令(本次就下错过、量完立刻撤回)。 - 队友的自查结论要复核,小心它测的是错变量(2026-10-27):log 道做过受控实验、结论是「我们没推高这个数」——
他们改的是 TC 号、盯的是
skip_bare_unmanaged,那个组合当然不动;而 +16 走的是另一条路(新增裸形 「字母 T 累进数字 4」)。 定责用 D 节:逐文件注入 + HEAD/在飞两版同一计数器相减 + 对新增行 grep;闭合后 +16 = 16 处裸形 「字母 T 累进数字 4」, 推翻了自查结论。口头自查与--note都不是证据,可复核的差分数字才是。 .workbuddy-ai/刻意不共享:记忆不随任务分叉,也不给联接递归删除的机会。 任务工作树自己的记忆由done/abort在清场前归并回主工作树。⚠ 手工回收(E 节)没有这一步归并: 拆之前顺手ls <tree>/.workbuddy-ai/看一眼,有内容就一并备份进ci-artifacts/wt-leftover-backup/。--lean只省磁盘不省时间:实测全量 smudge 7.25 s / 83 MB,跳过 smudge 7.05 s / 45 MB, 两者工作区都干净。默认走全量(参考图可用),磁盘紧张时才用--lean。- 锁不绑定进程存活:
lock acquire本身是短命进程,而真正用资源的烧录/构建在别的进程里跑。 所以判「陈旧」只看时效(该锁的 TTL)加显式--force;pid 仅作排障记录。 同任务重复 acquire 是幂等的(不会把自己锁死)。 - TTL 按锁分设(2026-10-12):
build= 1h、bench= 6h(LOCK_TTL_HOURS)。 口径是「兜底窗口应与操作真实时长同量级」——编译是秒~分钟级,6h 的兜底太钝; bench 是会话级(flash→/serial-boot-test→探针),才配得上小时级。持有者还可用lock acquire ... --ttl <小时>自报更精确的窗口(keil_build.py即按其--timeout自报)。 - 陈旧锁自动接管:超过该锁 TTL 未释放即视为陈旧,下次
acquire会接管并提示; 未到 TTL 的活锁需--force。done的收口前置也会自动释放陈旧锁(不再硬失败),但 未到 TTL 的活锁会把done挡在前置检查(B 节第 8 步)。 build锁不必手工持(2026-10-12 起):/keil-build(keil_build.py)自持自释—— 进入 acquire、finally无条件 release,所以"Agent 忘了 release、锁一直挂到 TTL"这条在构建路径上已消除。done的门禁自己持锁并给子进程设DTU_LOCK_HELD=build告知跳过(新旧脚本都兼容)。bench锁仍须 Agent 手工持(会话作用域:跨烧录→/serial-boot-test→探针,见keil-buildSKILL.md)。- ⚠
done --dry-run只保护预演段、不保护清场段(2026-10-29 实测):done <ID> --dry-run --skip-build --skip-gates在「分支已合入主干」这条路上照样执行清场——摘.venv/simulator/SDL2联接、删分支、清台账,然后打「完成(分支此前已合入)」。想真·零改动就只跑不带--skip-gates的--dry-run并停在预演输出;用完一律git worktree list复验(判完成以git log为准,不是以有没有再敲一次done为准)。另:--dry-run的前置检查同样要抢build锁——被别的任务占着时它会直接报「独占资源 build 仍被 X 占用,先 release」,此时加--skip-build或先 release,别为了预演去抢别人的锁。 - ⚠ 文件工具「先读后改」按解析后的绝对路径记账,切树后要重新读(2026-10-30 实测,屏板链路任务树):开新树后先用相对路径读过一批文件(读历史记到当时解析到的主树文件上),再进任务树直接
edit→ 6 条并行编辑全被file has not been read — read the file, then retry拒。判据:树内改动一律用树内绝对路径,先 read 再改——读历史不跨路径复用,工具不做「同名相对路径」宽容。 - ⚠ 中文门禁输出回显乱码 ≠ 路径坏(2026-10-30 实测):对含中文文件名的文档跑
check_doc_links.py,终端回显整段乱码,但 rc 与判定计数全部正确。只认[SUMMARY]行与退出码,别误诊成「文档丢失」;脚本化批量调用走 python subprocess + 显式encoding,别经命令行中转中文名。