@@ -1,265 +1,289 @@
---
name: task-worktree
description: >-
多任务 并行开发的空间管理(git worktree 生命周期): `@task new` 开工作树+任务 分支、目录 联接共享 .venv 与模拟器 SDK、活跃上限 8 与腾位(A 节)、 build/bench 两类 独占锁与非登记持有者「缺台账」释放不掉(B 节 )、`done` 是合并点不是查看器(C 节 )、主工作 树被别的并发 会话弄脏时的手工合并路径 ( C2 节,详情已外置)、门禁指标异常增长时用净室工作树逐文件归因(D 节,外置)、回收 `@task list` 看不到的工作树并清 悬挂台账(E 节,外置 )、与 DSH worktree 插件 `/worktree` 、 `worktree_create` 共存(F 节,外置)、任务树内跑门禁与 `simulator/SDL2` 联接假红、树内提交/合并信息的禁词自检(G 节,外置)、插件树两条共享联接的分级( `simulator/SDL2` 是硬需求、 `.venv` 有解释器逃生门,F 节,外置)、并行上限数字的唯一事实源( `MAX_CONCURRENT` )。规格见 docs/环境与部署/并行任务工作树方案.md;标「外置」的 深规程全文在本技能目录 reference/ 下,动手前先读对应那篇 。
并行开发的空间管理(git worktree 生命周期,短命令 `@task` ):开任务树与 分支、联接共享 ` .venv` 与模拟器 SDK、build/bench 独占锁、并发上限与腾位(A)、放不掉的锁(B )、`done` 是合并点不是查看器与补绿链 (C)、主树被别的会话弄脏时的手工合并(C2)、门禁指标异常的净室归因(D)、回收非登记树与 悬挂台账(E)、与 DSH worktree 插件共存及联接分级(F)、树内跑门禁与消息禁词(G )。规格 = docs/环境与部署/并行任务工作树方案.md;深规程全文在 reference/。
whenToUse: >-
要同时推进多个任务、担心同分支同文档互相冲突;要为新任务开隔离空间;要 把任务合并 回主干;或遇 到 `new` 报「已有 8 个活跃任务(上限 8)」、`lock release` 报「任务 X 不存在(缺台账)」、`done` 报「门禁未全绿」或「主工作树不干净」而那份未提交 改动是别的会话/插件留下 的、两个任务抢 COM6 / UV4、"改了文档被覆盖"、 并集合并残留;或要 在任务树里跑文档/审计门禁(往 `ci-artifacts/` 写输出报 ENOENT、`--all` 的问题全在 `simulator/SDL2` 联接下) ;或门禁指标异常增长(如不可判定数 +16)却 查不出是 哪个文件造成的 ;或要回收 `@task list` 里根本 看不到的工作 树、清台账里 branch/path 都已不存在的悬挂条目;或任务是在任务树里改文档再合主干、要写提交/合并信息(被清退的主题不得在消息里复现);或想 确认插件工作 树里 `.venv` 与 `simulator/SDL2` 到底 哪个必须补;或要改并行上限这个数字并同步全仓口径。
要为新任务开隔离空间、 同时推进多个任务、把任务合回主干或回收工作树 ;或撞 到 `new` 报「已有 8 个活跃任务(上限 8)」、`lock release` 报「任务 X 不存在(缺台账)」、`done` 报「门禁未全绿」或「主工作树不干净」而那份脏 改动是别的会话/插件留的、两个任务抢 COM6 或 UV4、并集合并残留;或在任务树里跑门禁 写输出报 ENOENT、`--all` 的问题全在 `simulator/SDL2` 联接下;或门禁指标异常增长查不出哪个文件造成;或要回收 `@task list` 看不到的树、清 branch/path 都已不存在的悬挂台账;或要 确认插件树里 `.venv` 与 `simulator/SDL2` 哪个必须补;或要改并行上限这个数字并同步全仓口径。
---
# task-worktree — 多任务并行开发空间(短命令 `@task`)
**执行体 = 本 skill 自带脚本 ** `.dsh/skills/task-worktree/scripts/task_worktree.py`
(纯标准库,任意 python3;拷整个 skill 目录到别的项目即可用) 。
- **执行体** = `.dsh/skills/task-worktree/scripts/task_worktree.py` (纯标准库;拷整个 skill 目录即可换项目用)。
- **深规程全文在 reference/ 下:做 C2 / D / E / F / G 任何一条之前先把对应那篇读完**——本文只留判得出来「要不要读」的判据 。
- 为什么这么拆:本文件受 dsh-mnemon 写侧上限 **65536 字节 ** ( `skill-size` 钩子 49152 告警;实测 72,601 字节的技能照常可读可提交,只是自进化每次「生成草案」被拒、永久卡在「生成中」)→ **新增深规程进 reference/,本文只加判据 ** 。瘦身前 frontmatter 原文 → [frontmatter-history.md ](reference/frontmatter-history.md )。
**本文档受 dsh-mnemon 写侧上限 65536 字节约束 ** (实测 2026-10-29:正文涨到 72,601 字节后,技能自进化每次「生成草案」都被写侧拒绝、
那一行永久卡在「生成中」)。所以**新增的深规程写进 reference/<篇名>.md**,本文只留可执行判据的摘要 + 指针;瘦身前的 frontmatter
原文逐字留在 [reference/frontmatter-history.md ](reference/frontmatter-history.md )。**动手做某条深规程前,先把对应那篇 reference 读完**(摘要只用来判定要不要读)。
## 症状 → 去哪一节
@T3 @powershell
# cwd = 仓库根(或任意子目录)
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" <子命令> [参数]
@T3 @
bash( `ops_bash` )里固定两个变量,下文用 `$TW` 代指脚本:
@T3 @bash
PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktree.py"
"$PY" "$TW" list
@T3 @
退出码:`0` =成功 / `1` =操作失败 / `2` =前置或环境错误 / `3` =用法。
* * ⚠ 这几个码只有「不接管道」时才读得到**——原因与对策见「读输出」一节。
| 现场 / 目标 | 去 |
|---|---|
| 开新树 / 看全景 / 追平主干 / 放弃任务 | 固定命令、典型流程 |
| `new` 报「已有 8 个活跃任务(上限 8)」 | **A ** |
| `lock release` 报「任务 X 不存在(缺台账)」,锁照旧挂着 | **B ** |
| `done` 做了什么、红了怎么补绿、能不能反悔、怎么算「真完成」 | **C ** |
| `done` 报「主工作树不干净」,而那份脏是别的会话/插件留的 | **C2 ** → [merge-dirty-main-tree.md ](reference/merge-dirty-main-tree.md ) |
| 门禁指标异常增长要逐文件归因(主树全程只读) | **D ** → [clean-room-measure.md ](reference/clean-room-measure.md ) |
| `@task list` 里没有的树要回收 / detached / 有在飞内容 / 悬挂台账 | **E ** → [reclaim-unregistered.md ](reference/reclaim-unregistered.md ) |
| 插件树缺联接、锁归属 `pw:<name>` 、「有插件为何还自持生命周期」 | **F ** → [dsh-plugin-bridge.md ](reference/dsh-plugin-bridge.md ) |
| 树内写门禁输出 ENOENT、`--all` 假红、写提交/合并信息、字节体检 | **G ** → [gates-in-tree.md ](reference/gates-in-tree.md ) |
| 退出码读到 0 其实是 2、`tail` 之后误判「有冲突」 | 读输出 |
| 谁不许跑 `done` /`abort` 、清场前必做什么、任务期哪些文件不许改 | 铁律 1–9 |
| 收工前最后一遍自查(各节该留什么证据)/全部实测坑 | [verification.md ](reference/verification.md ) / [pitfalls.md ](reference/pitfalls.md ) |
## 固定命令
``` bash
PY = ".venv/Scripts/python.exe" ; TW = ".dsh/skills/task-worktree/scripts/task_worktree.py"
" $PY " " $TW " <子命令> [ 参数] # PowerShell: & $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" <子命令>( cwd = 仓库根或任意子目录)
```
| 命令 | 作用 |
|---|---|
| `new <ID> <slug>` | 建分支 `task/<ID>-<slug>` + 工作树 + 共享联接 + 台账; 打印新空间路径 |
| `list` | 所有任务一览: 分支 / 落后领先 / 脏文件数 / 台架状态 + 两个锁的归属 |
| `status <ID>` | 单任务详情( 未提交清单、相对基线漂移、台架记录、已建联接) |
| `sync <ID>` | 在任务工作 树里 `rebase` 追平主干,把冲突控制 在任务分支上 |
| `lock acquire\ |release\ |status <build\|bench>` | 独占资源锁 |
| `plugin list\ |adopt\ |unadopt [插件树名]` | 与 DSH worktree 插件的 桥:看清单 / 补共享联接 / 摘联接(**不删树**)→ F 节 |
| `done <ID>` | 预演 → 合入主干 → 并集残留审计 → 重生成产物 → 门禁 → 清场(主树被别的会话弄脏时改走 **C2 ** 手工合并) |
| `abort <ID>` | 放弃任务( 工作树 + 分支一起删,**永久丢弃**) |
| `new <ID> <slug>` | 建分支 `task/<ID>-<slug>` + 工作树 + 共享联接 + 台账, 打印新空间路径 |
| `list` | 分支 / 落后领先 / 脏文件数 / 台架状态 + 两把锁归属。**只遍历台账** → 非登记树看不见(E 节) |
| `status <ID>` | 未提交清单、相对基线漂移、台架记录、已建联接 |
| `sync <ID>` | 在任务树里 `rebase` 追平主干,把冲突留 在任务分支上 |
| `lock acquire|release|status <build\|bench>` | 独占资源锁(→ 铁律 3、B 节) |
| `plugin list|adopt|unadopt [插件树名]` | 插件桥:看清单 / 补共享联接 / 摘联接(**不删树**)→ F 节 |
| `done <ID>` | 预演 → 合入主干 → 并集残留审计 → 重生成产物 → 门禁 → 清场 |
| `abort <ID>` | 工作树 + 分支一起删,**永久丢弃** |
`new` 常用开关:`--base <分支>` (默认 `v2-on-ui` )· `--needs-bench` (无台架 PASS 则 `done` 拒合)·
`--lean` (跳过 Git LFS smudge)· `--no-bootstrap` (不建联接)·
`--force` (**仅跳过并发上限 8 的检查**,不解决「谁该让位」,见 A 节) 。
`done` 常用开关: `--dry-run` (只预演,零改动)· `--skip-build ` · `--skip-gates` · `--force` (清场时强制移除工作树 )。
`lock` 开关:`--task <ID>` ( acquire 必填)· `--wait <秒>` · `--ttl <小时>` (自报窗口)· `--note <文本>` ·
`--bench-pass` ( release 时给台账记 PASS)· `--force` 。
⚠ `--note` 只在 **acquire ** 时写进锁 payload, **release 时被忽略**——别指望它留下验收证据(证据写进主干提交说明或文档)。
**环境变量 ** (高级):`DTU_WT_ROOT` (任务容器位置,默认仓库同级 `_wt/` )·
`DTU_TASK_BASE` (默认基线分支,默认 `v2-on-ui` )·
`DTU_INTEGRATION_WORKTREE` (集成点,默认主工作树——用于隔离演练/测试)。
- **退出码** `0` 成功 / `1` 操作失败 / `2` 前置或环境错误 / `3` 用法——**只有不接管道才读得到**(→ 读输出)。
- `new` : `--base` (默认 `v2-on-ui` )· `--needs-bench` (无台架 PASS 则 `done` 拒合)· `--lean` (跳 LFS smudge,只省磁盘不省时间 )· `--no-bootstrap` (不建联接)· `--force` (**只跳过并发上限检查**,不解决谁让位 → A)
- `done` : `--dry-run` · `--skip-build` · `--skip-gates` · `--force` 。⚠ `--dry-run` **只保护预演段、不保护清场段 ** (实测:对已合入的分支它照样摘联接、删分支、清台账;且它的前置也要抢 `build` 锁)→ 用完复验 `git worktree list` ,细节 → 陷阱清单 。
- `lock` : `--task` ( acquire 必填)· `--wait <秒>` · `--ttl <小时>` (默认 `build` =1h / `bench` =6h,超期视为陈旧、下次 acquire 自动接管)· `--note` · `--bench-pass` · `--force` 。⚠ `--note` 只在 **acquire ** 写进 payload, **release 时被忽略** → 验收证据写主干提交说明或文档。
- 环境变量:`DTU_WT_ROOT` (默认仓库同级 `_wt/ `) · `DTU_TASK_BASE` (默认基线)· `DTU_INTEGRATION_WORKTREE` (集成点,用于隔离演练 )。
## 典型流程
@T3 @powershell
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" new N21 dhara-isfree --needs-bench
# → 把新会话的工作目录切到打印出来的 _wt/N21-dhara-isfree/,在那里干活
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" lock acquire bench --task N21
# 烧录 / @test / 板级套件……
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" lock release bench --task N21 --bench-pass
# 回到主工作树执行回收
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" done N21
@T3 @
``` bash
" $PY " " $TW " new N21 dhara-isfree --needs-bench # 把新会话工作目录切到打印出来的 _wt/N21-dhara-isfree/
" $PY " " $TW " lock acquire bench --task N21 # 烧录 / @test / 板级套件
" $PY " " $TW " lock release bench --task N21 --bench-pass
" $PY " " $TW " done N21 # 回主工作树(铁律 1);先 --dry-run 读全量
```
## 读输出:管道与截断(两起误判的共同根因)
1. * * `... 2>&1 | tail -N` 的退出码是 `tail` 的,恒为 0** → 脚本的 `1` / `2` 全被吞。实测三条印着
「rc=0」的输出其实都是 exit 2: `new` 撞活跃上限、 `lock release` 缺台账两次 。
所以**判据先看文本**( `[ok]` / `!!` / `前置/环境错误:` / `失败:` ),要真码就**直接执行不接管道**或补一句
`echo RC=$?` ;坚持接管道就在 bash 里取 `${PIPESTATUS[0]}` 。
2. * * `done` / `done --dry-run` 的输出不要用 `tail -2` 截**:接管道时 python 的 stdout 是块缓冲、git
子进程的 stderr 不缓冲,两股流错位混排,`tail -2` 只剩「--dry-run:到此为止…」加上 stderr 里带
`conflict` 字样的片段 → 据此误判「有冲突」。实测:`tail -2` 误读成 conflict,放宽到 `tail -10` 才看到
`[ok] 无冲突,预演结果树 <sha>` 。dry-run 全文只有 5~8 行,直接读全量。
1. * * `| tail -N` 之后 的退出码是 `tail` 的,恒 0** → 脚本的 1/2 全被吞(实测三条「rc=0」真码都是 2)。判据先看文本( `[ok]` / `!!` / `前置/环境错误:` / `失败:` ),要真码就**不接管道**或取 `${PIPESTATUS[0]}` 。
2. * * `done` 的输出别 `tail -2` **:管道下 python stdout 块缓冲、git 子进程 stderr 不缓冲,两股流错位 → 只剩尾部那句「--dry-run:到此为止…」加带 `conflict` 字样的 stderr 片段,据此误判「有冲突」。dry-run 全文 5~8 行,**读全量** 。
## 分步操作(八条运行时机制 )
## 分步操作(节号 A–G/C2、各节步骤号与铁律编号被 reference/ 与 docs 引用,**不许重排** )
### A. `new` 被拒「已有 8 个活跃任务(上限 8)」——怎么 腾位
### A. `new` 撞并发上限 → 腾位
1. 读 原文: `前置/环境错误:已有 8 个活跃任务(上限 8);先 done/abort,或加 --force` ( exit 2)。
上限是脚本常量 `MAX_CONCURRENT = 8` ; **「活跃」的判据 = `_wt/.state/<ID>.js on` 台账存在,且台账里
`path` 指向的目录还在**(手工删了工作树目录就不占名额,但会留下悬挂台账,见第 5 步与 **E 第 6 步 ** ) 。
2. `$TW list` 看全景(分支 / 落后领先 / 脏文件数)决定谁让位:**已完成的先 `done` **(合并回收,一步不省 )。
3. 没人认领、零提交、尚未 claim 的空闲树 → `abort <ID>` 腾位(实测就是 abort 掉排在后面的空闲树换出名额) 。
`abort` = 工作树 + 分支一起删、**永久丢弃**;工作树有未提交改动或已有提交时脚本会拒绝,确认可弃再加 `--force ` 。
4. 确实要同时开第 9 棵才 `new <ID> <slug> --force` :磁盘代价约 **83 MB/棵 ** ( `--lean` 约 45 MB),
并行度上去了、**冲突面与台架排队也一起上去**——不是免费的。
5. **别用手工 `git worktree remove` 给「登记任务」腾位 ** :台账不会被删(下次 `new` 同 ID 会报「任务 <ID> 已存在」),
还绕过了「先摘联接」(铁律 5),有让 git 递归进联接把主工作树 `.venv` 删掉的风险。要回收的若是**非登记树**
( `$TW list` 里根本没有它:`new` 出来又被手工删掉只剩悬挂台账的残骸 / D 节 `--detach` 净室 / 别的会话手工建的)
→ 腾位与回收一起走 **E 节 ** ,那里有「怎么证明这棵树零联接所以可以 remove」的可执行判据。
6. 腾位后复验:`$TW list` 的条目数与 `ls _wt` 的目录数一致,再 `new` 。
1. 认 原文 `前置/环境错误:已有 8 个活跃任务(上限 8);先 done/abort,或加 --force` ( exit 2)。「活跃」= `_wt/.state/<ID>.json` 在且台账 `path` 目录还在 → **手工删目录不占名额、但留悬挂台账 ** ( E 第 6 步)。
2. `$TW list` 定谁让位:**已完成的先 `d one `**(合并回收一步不省)。
3. 无人认领、零提交、未 claim 的空闲树 → `abort <ID>` ;有未提交或有提交时脚本会拒,确认可弃才 `--force` 。
4. 真要第 9 棵才 `--force` :并行度上去,**冲突面与台架排队一起上去**(每棵磁盘代价见铁律 6 )。
5. **别用手工 `git worktree remove` 给登记任务腾位 ** :台账不删(同 ID 报「任务已存在」)、又绕过「先摘联接」(铁律 5),有让 git 递归进联接删掉主树 `.venv` 的风险。要回收的是**非登记树**( `list` 里没有它)→ **E 节 ** 。
6. 复验:`list` 条目数 == `ls _wt` 目录数,再 `new ` 。
### B. 非登记持有者占 的锁: `lock release --bench-pass` 曾报「任务 X 不存在(缺台账)」
### B. 非登记持有者的锁放不掉
**症状 ** (lead 在主工作树跑台架验收窗,用临时名占锁 `lock acquire bench --task acceptwin` ):
症状:`lock release bench --task acceptwin --bench-pass` (带 `--force` 同)报 `前置/环境错误:任务 acceptwin 不存在(缺台账 _wt/.state/acceptwin.json) ` , **锁照旧挂着**。根因:`release` 带 `--bench-pass` 就先 `load_state(task)` ,而 `acquire` 侧不查台账 → 「释放」被「记账」挡死;锁没放,`done` 的 `release_stale_locks()` 对未到 TTL 的活锁判失败 → **一个没登记的名字挡住所有任务收口 ** 。
@T3 @bash
"$PY" " $TW" lock release bench --task acceptwin --bench-pass
前置/环境错误:任务 acceptwin 不存在(缺台账 _wt/.state/acceptwin.json) # 锁照旧挂着
"$PY" "$TW" lock release bench --task acceptwin --force -- bench- pass # 同一句错误,锁仍在
@T3 @
**2026-10-29 已修 ** :无台账降级为 `[warn] …跳过 PASS 记录,仅释放锁` , **不再需要手写占位台账**。
1. ` $TW lock status` 认现场(持有者 / pid / 已持有 / TTL)——别对同一句报错反复重试。
2. `$TW lock release <build|bench> --task <NAME> [--bench-pass]` 直接放;PASS 记不上会 warn,锁照放。
3. **PASS 后面还要用 ** ( `done` 只认台账 ` bench_ pass` )→ 从一开始就用已 `new` 的 ID 占锁,或把结论写进主干提交/文档。插件树 `pw:<name>` 天然无台账 → F 节。
4. 最后手段:锁 = `_wt/.state/locks/<name>.lock` 的单行 JSON( `name/task/pid/host/epoch/at/note/ttl` ),`release` 就是 unlink 它——手工删 = 不记 PASS 的强制释放,前面各步都不可用才做。**早期为绕坑手写的占位台账现在有害**(既占上限名额又被 `list` 当任务)→ E 第 6 步备份后删。
**根因 ** : `release` 分支里只要带 `--bench-pass` 就先 `load_state(task)` 去台账写 PASS,而 `acquire` 侧
**不查台账 ** ——这个不对称让「释放」被「记账」挡死(`--force` 只覆盖持有者不符与陈旧接管,管不到台账)。
后果比报错本身大:锁没放,`done` 的前置 `release_stale_locks()` 对未到 TTL 的活锁直接判失败 →
**一个名字没登记,就挡住所有任务的收口 ** 。
### C. `done <ID>`:它是合并点,不是查看器
**2026-10-29 起已修 ** ( `task_worktree.py` :无台账降级为 `[warn] …跳过 PASS 记录,仅释放锁` 并照常释放 )。
现在的正解只有三步,**不再需要手写占位台账**:
1. `$TW lock status` 认现场(持有者 / pid / 已持有时长 / TTL)——别对同一句报错反复重试 release。
2. `$TW lock release <build|bench> --task <NAME> [--bench-pass]` 直接放;PASS 记不上会打印 warn,锁照放。
3. **PASS 后面还要用 ** ( `done` 只认台账里的 `bench_pass` )→ 从一开始就用已 `new` 出来的任务 ID 占锁,
或把验收结论写进主干提交说明 / 文档。**插件树 `pw:<name>` 天然没有台账**,别指望它留 PASS → F 节。
最后手段(脚本本身都走不通时):锁就是 `_wt/.state/locks/<name>.lock` 里的单行 JSON
( `name/task/pid/host/epoch/at/note/ttl` ),`release` 的实现就是 unlink 它——手工删这个文件等价于
「不记 PASS 的强制释放」,只在前面几步都不可用时才用。**历史遗留的占位台账**(早期为绕这个坑手写的
`_wt/.state/<NAME>.json` )现在已无必要:它活着时既占 `MAX_CONCURRENT = 8` 名额又被 `list` 当任务列出,
按 E 第 6 步备份后删掉即可。
### C. `done <ID>` 的正确用法(它是合并点,不是查看器)
1. **合并前确认任务属主已把全部改动提交完 ** 。**`$TW status <ID>` ** 看未提交清单与领先提交数;跨会话任务
再问一句属主(`send_message` )。实测教训:属主还剩「2/2」没提交就 `done` ,把只含 1/2 的分支合进了主干,
只能另开一棵树补后半并向对方说明(是自己的时序错,不是分支问题)。
2. **先 `--dry-run`,并读全量输出 ** (别 `tail -2` ,见「读输出」)。判据两行:
`[ok] 前置检查通过(领先 <base> 共 N 个提交)` + `[ok] 无冲突,预演结果树 <sha>` ;
**N 必须等于属主应交的提交数 ** ,对不上回到第 1 步。dry-run 零改动。
1. **合并前确认属主提交完 ** : `$TW status <ID>` 看未提交与领先数,跨会话再问属主一句。实测教训:属主还剩「2/2」没提交就 `done` → 只含 1/2 的分支进了主干,只能另开一棵树补后半(自己的时序错,不是分支问题 )。
2. 先 `--dry-run` 且**读全量**。判据两行:`[ok] 前置检查通过(领先 <base> 共 N 个提交)` + `[ok] 无冲突,预演结果树 <sha>` ;**N 必须等于属主应交的提交数**,对不上回第 1 步。
3. 正式 `$TW done <ID>` ——**必须在主工作树**(铁律 1)。
4. 输出 `!! 门禁未全绿:工作树与分支**保留**,未清场` 时,紧跟的 `主干已含合并提交 <sha>` 已经 说明
**合并落进主干了 ** 。
- ✅ 走提示里的 **a) 在主工作树修正后追加提交 ** ;
- ❌ **绝不用 b) 的 `git -C "<主树>" reset --hard <sha>` ** ——那是丢弃已入库的合并提交,而
`reset --hard` 本身是项目级 Never(会连未提交工作一起丢)。提示 b 只是列出的可能性,不是建议。
- 先定位是**哪条钩子**红:门禁输出里的 `[SUMMARY] static: pre-commit hooks -- passed=N failed=M` ,
不同根因处置不同(见陷阱「`done` 的门禁 = `pre-commit run --all-files` 」)。最常态是 `.map` 滞后 → 第 5 步。
5. 补绿固定链(2026-10-25 实测;顺序即因果,全在**主工作树**跑,逐条不接管道看退出码):
4. 见 `!! 门禁未全绿:工作树与分支**保留**,未清场` 时,紧跟的 `主干已含合并提交 <sha>` 说明**合并已落主干**;先定位哪条钩子红( `[SUMMARY] static: pre-commit hooks -- passed=N failed=M` ),最常态是 `.map` 滞后 → 第 5 步。
- ✅ 走提示 a):主工作树修正后追加提交 。
- ❌ **绝不用 b) 的 `reset --hard <sha>` ** ——那是丢弃已入库的合并提交,且 `reset --hard` 是项目级 Never(连未提交工作一起丢)。提示 b 是列出的可能性,不是建议。
5. 补绿固定链(顺序即因果,全在主工作树,逐条不接管道看退出码):
@T3 @bash
``` bash
" $PY " scripts/check_mem_numbers.py --write # 台账受管区 ↔ 新 .map 对齐
" $PY " scripts/ram_baseline.py --write # RAM 快照 / 92% 预算台账
" $PY " scripts/ram_baseline.py --update # 趋势基线追加一行
" $PY " scripts/check_test_coverage.py --update-baseline # 覆盖率基线(新单测 / 新套件时必刷)
" $PY " scripts/check_doc_links.py --all # 文档链接与章节引用
git add <上面几条 刷出来的基线/快照文件> # 显式列文件,别 -A 把别人在飞的改动一起提交
git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first( AGENTS.md 边界),按用户指示再做
@T3 @
git add <上面刷出来的基线/快照文件> # 显式列文件,别 -A 把别人在飞的改动一起提交
git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first( AGENTS.md 边界)
```
为什么必然要补,见 陷阱「`done` 的门禁跑在 `@build` 之前」。
6. **重跑 `done` 之 前主工作 树必须干净 ** ——否则前置检查直接
`失败:主工作树不干净(…)——合并会把未提交改动卷进来` ,所以第 5 步的产物必须**先提交**。重跑时若分支
已并入,会打印 `[i] task/<ID>-<slug> 已合入 <base>,跳过合并,直接清场` → 摘联接 → `完成(分支此前已合入)` 。
7. 清场完成后再敲一次 `done <ID>` 会得到 `前置/环境错误:任务 <ID> 不存在(缺台账 …json) ` ( exit 2)——
**这不是失败 ** :清场的最后一步 unlink 了台账。判完成以 `git log` 为准。
为什么必然要补 → 陷阱「`done` 的门禁跑在 `@build` 之前」。合并解过冲突时,**合并提交期 pre-commit 只查冲突文件**(→ G 节第 3 条)→ 落主干后必须另跑全量。
6. **重跑 `done` 前主树必须干净 ** ,否则 `失败:主工作树不干净(…)——合并会把未提交改动卷进来` → 第 5 步产物先提交。分支已并时重跑打印 `[i] task/<ID>-<slug> 已合入 <base>,跳过合并,直接清场` 。主树脏且那份脏不是你的 → **C2 ** 。
7. 清场后再敲一次 `done <ID>` 得 `前置/环境错误:任务 <ID> 不存在(缺台账 …json) ` (exit 2)——**不是失败**:清场最后一步 unlink 了台账。**判完成以 `git log` 为准**。
### C2. `done` 被「主工作树不干净」挡住,而那份脏改动 是**别的并发会话**留下 的
### C2. `done` 被「主工作树不干净」挡住,而那份脏是**别的并发会话**留的
- **不许**对那份外部脏改动 做 stash / checkout / reset / 提交 / 删除(项目级 Never,你也 不知道 它写没写完);
`done` 的前置要求主工作树干净,于是**合并与回收一起被挡**——这是挡给你看的保护,不是工具坏了 。
- **正解链(六步)**:取证(mtime + diff 定性有无交集,顺手 `worktree list` 看有没有别人中途新建的树)→
`git merge-tree --write-tree <base> <task 分支>` 预演(判据:打出结果树 sha 且 RC=0)→ 主树
`git merge --no-ff --no-edit -F ci-artifacts/merge-msg-<slug>.txt <task 分支>` → 合并后把
`git hash-object <外部文件>` 与 `git diff --stat` 和**合并前存下的值逐项对账**(实测同为 `369380ca…` 、2+/2−
→ 证明 pre-commit 自己的 stash 往返没动别人的工作)→ 台账先 `cp` 成 `<ID>.json.state-bak` 再让 `done` (或 E 节
手工路径)自己摘 .venv / simulator/SDL2 联接拆树 → `worktree list` + `lock status` 复验收工。
- **报告里要写明绕开 `done` 的理由并逐项补回它的证据**:预演 sha + RC=0、blob id 对账、合并后在
**主树**跑全库 `check_doc_links.py --all` (见 G 节)、两把锁空闲;并且点名那份外部脏文件与中途冒出来的第二棵树
(谁的、怎么定性、你没动它的证据)。**blob id 变了 = stash pop 半冲突**:立刻停止收工,两份 blob id 与两份 diff
交给该文件属主,绝不 `checkout` 抹平。
- **认得出它**:本仓这份外部脏改动两次都是 `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` (2+/2−,别的会话/插件正在写它)——别把它当成自己漏提交的文件去提交或清掉。同一次窗口里 `git -C "$MAIN" worktree list` 还会冒出**别人中途新建的第二棵树**(实测 `data-trust-restructure` ),那是并发会话的硬证据:点名写进报告,并且不是你的树就别顺手回收(→ E 节)。
- **合并信息也过 G 节的禁词自检**:清退/删主题类任务,`merge:` 那行与正文里被清退主题的关键词计数必须是 0。
- **不许**对它 做 stash / checkout / reset / 提交 / 删除(项目级 Never,你不知它写没写完)。 `done` 要求主树干净 → 合并与回收一起被挡,**那是保护不是工具坏了**。
- 正解六步,编号与 [reference/merge-dirty-main-tree.md ]( reference/merge-dirty-main-tree.md ) 第 1~6 步一一对应(每步的确切命令与判据在那篇):**取证**(mtime + diff 定性有无交集,顺带 `worktree list` 看有无别人中途新建的树)→ **预演 ** `git merge-tree --write-tree <base> <task>` (打出结果树 sha 且 RC=0)→ **主树手工合并 ** `git merge --no-ff --no-edit -F ci-artifacts/merge-msg-<slug>.txt <task>` → **对账 ** (合并后 blob id 与 stat 同合并前存值逐项一致)→ **备份台账 ** `cp <ID>.json <ID>.json.state-bak` 后让 `done` 自己摘联接拆树 → **复验 ** `worktree list` + `lock status` 。
- **blob id 变了 = stash pop 半冲突**:立刻停止收工,把两份 blob id 与 diff 交给该文件属主,绝不 `checkout` 抹平。
- **报告要点名**:绕开 `done` 的理由 + 补回它的证据(预演 sha 与 RC、blob 对账、主树全库 `check_doc_links.py --all` 、两把锁空闲)+ 那份外部脏文件与中途冒出来的第二棵树(谁的、你没动它的证据)。合并信息也过 G 的禁词自检;「这份脏通常是谁」见陷阱清单。
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/merge-dirty-main-tree.md](reference/merge-dirty-main-tree.md) —— 主树被别的并发会话弄脏时的手工合并路径(取证 → `merge-tree` 预演 → 主树 `merge --no-ff -F` → 外部改动 blob id / `diff --stat` 前后对账)。
### D. 门禁指标异常增长 → 净室逐文件归因(主树全程只读)
### D. 门禁指标异常增长 → 逐文件归因(把净室工作树当**测量仪**,主树全程只读)
三条判据:仓外 detached 树当净室、**一次只注入一份在飞文件**、与 HEAD 基线逐文件差分;主树前后 `git status --porcelain` 逐行一致才算「全程只读」。全文 → [reference/clean-room-measure.md ](reference/clean-room-measure.md )。
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/clean-room-measure.md](reference/clean-room-measure.md) —— 把仓外 detached 工作树当净室测量仪:一次只注入一份在飞文件、与 HEAD 基线差分做逐文件归因、主树全程只读。
### E. 回收**非登记**工作树(`list` 为空 / detached / 树里有在飞或未跟踪内容)
### E. 回收**非登记**工作树(`$TW list` 为空 / detached HEAD / 树里有在飞或未跟踪内容)
三条不可跳过:① 逐文件内容**三桶归类**(与主线工作区/HEAD 比对;「很久没动」不是判据)② 动手前跑**只读 reparse 预扫**,拿到 `REPARSE: none` 才许 `git worktree remove` (铁律 5)③ 唯一未合并残留先备份再 `--force` ,拆完复验 + 清悬挂台账。**绝不跑 `done` /`abort` **(铁律 9)。全文(第 1~6 步 + 两份探针脚本)→ [reference/reclaim-unregistered.md ](reference/reclaim-unregistered.md )。
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/reclaim-unregistered.md](reference/reclaim-unregistered.md) —— 回收 `@task list` 看不到的工作树:逐文件内容三桶归类、reparse/联接只读预扫、唯一残留先备份再 `--force`、悬挂台账清理复验。
### F. 与 DSH worktree 插件(`/worktree`、`worktree_create`)共存
### F. 与 DSH worktree 插件(`/worktree`、模型工具 `worktree_create`)共存
| 联接 | 级别 | 依据 | 缺了会怎样 | 逃生门 |
|---|---|---|---|---|
| `simulator/SDL2` | **硬需求 ** | `simulator/CMakeLists.txt:12` 的 `set(SDL2_DIR …)` **非 `CACHE` ** , include / `lib/x64` / `SDL2.dll` 全从它派生 | `build_sim.bat` 当场失败 | **无 ** |
| `.venv` | 软需求 | `scripts/ci_local.py` 有 `DTU_PRECOMMIT_PYTHON` + 五级解释器候选 | **静默**落机器级解释器(缺 fontTools → `generated-fresh` 假红) | 环境变量 |
- **两条共享联接不同级**(别答成「两个都必须」): `simulator/SDL2` = **硬需求 ** —— `simulator/CMakeLists.txt:12` 是普通 `set(SDL2_DIR "${CMAKE_CURRENT_SOURCE_DIR}/SDL2/SDL2-2.32.10")` 而**非 `CACHE` **,既没有环境变量也没有 `-DSDL2_DIR=` 逃生门,include / `lib/x64` / `SDL2.dll` 三条路径全从它派生( `:34` `:70` `:71` `:180` `:207` )→ 缺它 `build_sim.bat` 当场失败。 `.venv` = **软需求 ** —— `scripts/ci_local.py` 有 `DTU_PRECOMMIT_PYTHON` 显式逃生门 + 五级解释器候选(显式 var → `sys.executable` → 仓库 `.venv` → `which python` → 机器级常见位置),绕开联接只是**静默**落到机器级解释器(缺 fontTools → `generated-fresh` 假红)。 `plugin adopt` 两条都补是因为便宜,不是两条都硬 。
- **「有插件了为什么还自持 Python 生命周期」别凭印象答**:先实读插件包(`npm root -g` → `node_modules/@deepseek-ai/dsh/` 下定位插件、读 `package.json` 记版本),再 grep 三个概念名并报命中数——独占锁 `lock` 、共享目录联接(`.venv` / `simulator` -SDL2)、联接状态三态显示;插件侧零命中就是自持的理由。重叠面只有开/收两个动作(两侧底层都是 `git worktree add` ,没重造「建树」);锁、联接、台账与 `done` 的「预演 → 合并 → 审计 → 门禁 → 清场」编排才是插件没有的东西(本仓资源各只有一份:一块 ST-LINK、一套 Keil 授权、一个 COM6,SDL2 又无逃生门 → 必须串行 + 必须共享)。
- `plugin adopt` 两条都补只是因为便宜,不代表两条都硬。插件树占锁记 `pw:<name>` 、**回收仍归 /worktree**(本 skill 不删插件树,插件也不删 `_wt/` )。「有插件为何还自持生命周期」=先读插件包( `npm root -g` )再 grep 三个概念名报命中数,插件侧零命中就是理由——取证姿势与答案边界 → [ reference/dsh-plugin-bridge.md ]( reference/dsh-plugin-bridge.md ) F-2 。
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/dsh-plugin-bridge.md](reference/dsh-plugin-bridge.md) —— 与 DSH worktree 插件(`/worktree`、`worktree_create`)共存:`plugin adopt` 补共享联接(两条联接的分级:SDL2 硬 / `.venv` 软)、锁归属 `pw:<name>`、回收仍归 /worktree、以及「为什么不用插件而自持生命周期」的取证姿势(F-2)。
### G. 任务树内跑门禁:落点、`--all` 假红、消息禁词
### G. 任务树内跑门禁与审计:输出落点(ci-artifacts)与 SDL2 联接假红
- **新建的任务树里没有 `ci-artifacts/` **(被 `.gitignore` 忽略 → 不会被检出):重定向输出或
`fs.writeFileSync` 直接 `ENOENT` ,**整段程序零执行**。落盘前先单独一条 `mkdir -p ci-artifacts` ;树内输出
一律用树内相对路径( `cd` 进树后 `../_wt/x.txt` 会落到 `_wt/_wt/` ,实测就是这句报错) 。
- **文档门禁在树内按单文件跑,全库 `--all` 基线留到合并后在主树跑**:树内的 `simulator/SDL2` 是指向
主树同一份的目录联接,`--all` 会把这份「第二份仓库副本」一起扫:实测树内打 222 个文档 / 35 处问题、RC=1
而 **35 处全在联接下 ** 、要改的文档一条没有 = 假红;合并后主树同一条命令 = 2,597 处 / 194 个文档 / rc=0,
这才是有效基线(单文件实测 161 处/98%/rc=0、16 处/88%/rc=0)。
- 确要在树内跑 `--all` :判红固定三步——① 主树同命令拿基线 ② 过滤 `simulator/SDL2` 联接下的问题行
③ 剩下的逐行比。只做一步(见 RC=1 就报「门禁挂了」/ 过滤后为空就报「全绿」)都是误判。
- **跑门禁前先做一次字节体检**(文档改动在树内提交前的固定项):`lines / bytes / BOM / CR / U+FFFD / 三连续空行 / 行尾空格 / 末行单个 LF` + 自写 Markdown 表格每行列数一致性检查。报出来的坏行**未必是本次改动**(实测 2,665 行的需求文档里两处坏表格与本次编辑无关)——先定位归属再决定动不动手,别顺手改别人的区域。这些面 `doc-links` / `no-mojibake` 只覆盖编码那半,表格断列与多出的空行要自己数。
- **提交/合并信息一律 `-F` 读文件**( UTF-8 无 BOM、标题 ≤100 字符、正文每行 ≤80,文件放 `ci-artifacts/` ),清退类任务另加一条**禁词自检**:`git log -1 --pretty=%B | grep -c '<被清退主题的关键词>'` 必须是 **0 ** (标题与正文都不许再出现那个关键词,改用「其它串口通道对照内容」这类语义短语;合并信息同样查一遍)。提交前自查消息里的行数/统计有没有算错;**未推送前**发现错了 → `git commit --amend -F <改好的文件>` 就地修,别追加一笔「更正」提交(那等于把禁词又写回历史)。
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/gates-in-tree.md](reference/gates-in-tree.md) —— 任务树内跑门禁与审计:`<tree>/ci-artifacts/` 先 `mkdir -p`、文档链接按单文件在树内跑、全库 `--all` 合并后在主树跑、`simulator/SDL2` 联接假红、提交/合并信息禁词自检、改完文档的字节与表格体检。
- **新树没有 `ci-artifacts/` **(被 ignore → 不检出):重定向/写文件直接 `ENOENT` 且**整段程序零执行** → 落盘前先单独一条 `mkdir -p ci-artifacts` ;树内输出一律树内相对路径(`cd` 进树后 `../_wt/x.txt` 落到 `_wt/_wt/` )。一次性产物**不进仓库根**。
- **文档门禁树内按单文件跑;全库 `--all` 留到合并后在主树跑**——树内 `simulator/SDL2` 是同一份仓库的联接,`--all` 把它一起扫成假红(实测 RC=1 而问题全在联接下、要改的文档一条没有)。非要树内跑:① 主树同命令拿基线 ② 过滤联接下的问题行 ③ 剩下的逐行比。**只做一步就是误判。**
- **合并提交期 pre-commit 只查冲突文件**(日志首行 `[INFO] Checking merge-conflict files only.` ,其余全 `(no files to check) Skipped` )→ 合并落主干后**必须另跑全量** `scripts/ci_local.py` 。
- **跨层改动拆不成「按层分笔」**:钩子看到的世界 = HEAD + 本笔 staged(未暂存被 stash;未跟踪文件不被 stash 但会拖红同一棵树上别人的提交)→ 登记文件 / 生成物 / 互锁引用的文档分到他笔,本笔必被整仓门禁拦(`doc-links` /`registration-check` /`generated-fresh` /`mem-numbers` )。判据:**一笔提交必须自洽**。
- **跑门禁前字节体检**: `lines / bytes / BOM / CR / U+FFFD / 三连续空行 / 行尾空格 / 末行单个 LF` + Markdown 表格每行列数一致性(单元格内竖线写成反斜杠竖线,否则被当分隔符断列)。坏行**未必是本次改动**,先定归属。 `doc-links` / `no-mojibake` 只覆盖编码那半 。
- **提交/合并信息一律 `-F` 读文件**( UTF-8 无 BOM、标题 ≤100、正文每行 ≤80,放 `ci-artifacts/` );清退类任务加**禁词自检**: `git log -1 --pretty=%B | grep -c '<关键词>'` 必须是 **0 ** 。未推送前发现消息写错 → `--amend -F` 就地修,别追加「更正」提交(等于把禁词写回历史)。
- 全文(第 1~7 条,含 stash 机制与四道整仓门禁现场)→ [reference/gates-in-tree.md ](reference/gates-in-tree.md )。
## 铁律
1. * * `done` 只能在主工作树执行**——主干分支检出在主工作树,git 不允许同一分支在两处检出。
2. * * `done` 不 push**。它结束于本地合入 + 门禁全绿;推送需用户明确许可(项目既有纪律)。
3. * * `build` 与 `bench` 都必须串行**:UV4 不能两个实例同时 `-b` ;串口 COM6、ST-LINK、
设备 IP `192.168.1.100` 各只有一份。**构建与烧录都不可并发。**
- `build` 锁由 * * `@build` 自持自释**,调用方无需手工 acquire/release;
- `bench` 锁**必须手工**: `lock acquire bench --task <ID>` → 烧录/`@test` /探针 →
`lock release bench --task <ID> --bench-pass` 。非登记持有者(lead 的验收窗、插件树 `pw:<name>` )占了锁
怎么放 → B 节(2026-10-29 起无台账也只 warn、照常释放);插件树整体口径 → F 节。
4. **单写者文档 ** : `docs/任务规划/` (整个文件夹)、`AGENTS.md` 的任务表块、`MDK-ARM/*.uvprojx` 、
`.pre-commit-config.yaml` 、内存快照 —— 任务期**不在任务工作树里改**,留到 `done` 统一处理。
5. **清场必须先摘联接 ** :脚本会先 `os.rmdir` 掉 `.venv` / `simulator/SDL2` 联接再
`git worktree remove` 。**手工清场时千万别直接 `git worktree remove` **——git 可能递归进联接,
把主工作树的 `.venv` 一起删掉。手工拆任何一棵树之前先跑 **E 第 3 步**的预扫,拿到 `REPARSE: none` 才动手。
6. **活跃任务硬上限 8 ** (脚本常量 `MAX_CONCURRENT = 8` ,每棵约 83 MB 检出——du 口径,内容字节和 84.0 MiB → `docs/环境与部署/并行任务工作树方案.md` §4)。满了 `new` 直接 exit 2,
腾位顺序与 `--force` 的代价 → A 节;悬挂台账的清理与复验 → **E 第 6 步 ** 。
**改上限 = 只改 `task_worktree.py` 里那一行常量 ** ( `MAX_CONCURRENT` ,:30):报错文案「已有 N 个活跃任务(上限 8)」
与 `new` 打的「另有 N 个 DSH 插件工作树(`.dsh-worktrees/` ,不计入上限 8)」提示都是 `.format(…, MAX_CONCURRENT)`
插值该常量(实测 `task_worktree.py:764-766` / `:768-771` )——AGENTS.md、方案文档、本技能 frontmatter 与 A 节里的
数字**全是引用同步**,不是第二处事实源。全形态扫描(`最多 N` / `N 棵` / `三棵` / `第 N+1 棵` / `白占 N 个名额` /
`MAX_CONCURRENT = N` / 硬上限)与「上限真生效」的隔离小仓验证(`REPO_ROOT` 由 `__file__` 反推 → 脚本必须按同一
目录深度放,才能测到同级 `_wt/` )→ `stale-doc-count-sweep` skill;每棵树的磁盘代价按实测口径重述,别照抄旧数。
7. * * `@task` 的工作树放仓库同级 `_wt/` ,绝不在仓库内部**——否则污染 `git status` 、 `pre-commit --all-fil es`
与全库搜索。唯一例外 = DSH worktree 插件写死的仓内 `.dsh-worktrees/` (改不了;已用「预置 .gitignore +
三个全库扫描门禁排除」兜住,见 **F 节 ** )。自己新建的树一律走 `_wt/ `。
8. * * `done` 是合并点**:属主没提交完就别 `done` ;合并提交一旦进了主干,只用「主工作树补提交」收尾,
**不用 `reset --hard` 反悔 ** → C 节。
9. **非登记 / detached 的树绝不跑 `done` / `abort` ** :两者都以台账为前置,无台账直接 exit 2;带着残留台账时
`abort --force` 会绕过内容保全把别人的在飞改动连树一起删。回收走 **E 节 ** (全景 → 分桶 → 联接预扫 → 备份再拆 →
复验 + 清悬挂台账)。
1. * *
`done`
只能在主工作树执行**——主干分支检出在主树,git 不允许同一分支两处检出。
2. * *
`done`
不 push**:它结束于本地合入 + 门禁全绿;推送需用户明确许可。
3. * *
`build`
与
`bench`
都必须串行**(UV4 不能两个实例同时
`-b`
; COM6、ST-LINK、设备 IP
`192.168.1.100`
各一份) 。
`build`
锁由 * * @build 自持自释**(
`keil_build.py`
)→ 调用方**不要**手工 acquire/release;
`bench`
锁**必须手工** acquire → 烧录/
`@t est `
→ release
`--bench-pass `
。非登记持有者怎么放 → B 节;插件树 → F 节。
4. **单写者文档 ** :
`docs/任务规划/`
(整个文件夹)、
`AGENTS.md`
任务表块、
`MDK-ARM/*.uvprojx`
、
`.pre-commit-config.yaml`
、内存快照 —— 任务期**不在任务树里改**,留到
`done`
统一处理;用户显式豁免时(在任务树写、由 done 合回)要在交付报告里点名这条例外。
5. **清场必须先摘联接 ** :脚本先
`os.rmdir`
掉
`.venv`
/
`simulator/SDL2`
联接再
`git worktree remove`
。手工拆树前必跑 **E 第 3 步**预扫拿到
`REPARSE: none`
——否则 git 可能递归进联接把主树
`.venv`
一起删掉。
6. **活跃任务硬上限 8 = 脚本常量 `MAX_CONCURRENT`( `task_worktree.py:30`)——唯一事实源 ** :报错文案与 `new` 的插件树提示都插值它,AGENTS.md / 方案文档 / frontmatter / A 节的数字全是引用同步。改这个数字 → 全形态扫描走 `stale-doc-count-sweep` skill + 隔离小仓验证上限真生效;每棵磁盘代价按实测口径重述、别照抄旧数(细节 → 验证清单末条)。满了 exit 2 → A;悬挂台账 → E 第 6 步。
`MAX_CONCURRENT`
(
`task_worktree.py:30`
)——唯一事实源**:报错文案与
`new`
的插件树提示都插值它,AGENTS.md / 方案文档 / frontmatter / A 节的数字全是引用同步。改这个数字 → 全形态扫描走
`stale-doc-count-sweep`
skill,并另建隔离小仓验证上限真生效;每棵磁盘代价按实测口径重述(du 约 83 MB /
`--lean`
约 45 MB,方案文档 §4)别照抄旧数。满了
`new`
直接 exit 2 → A 节;悬挂台账 → E 第 6 步。
7. **工作树放仓库同级
`_wt/`
,绝不在仓库内部**(否则污染
`git status`
、
`--all-files`
与全库搜索)。唯一例外 = 插件写死的仓内
`.dsh-worktrees/`
(改不了,已用预置 .gitignore + 三个全库扫描排除兜住 → F 节)。
8. * *
`done`
是合并点**:属主没提交完就别跑;合并提交进了主干就只用「主树补提交」收尾,**不用
`reset --hard`
* * 反悔 → C 节。
9. **非登记 / detached 的树绝不跑
`done`
/
`abort`
* * :两者都以台账为前置(无台账 exit 2),带残留台账时
`abort --force`
会绕过内容保全把别人在飞的改动连树一起删 → E 节。
## 陷阱(实测 )
## 陷阱与验证(收工前各扫一遍 )
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/pitfalls.md](reference/pitfalls.md) —— 「陷阱(实测)」逐条清单原文。
## 验证
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/verification.md](reference/verification.md) —— 「验证」清单原文。
- [reference/pitfalls.md ](reference/pitfalls.md ):
`done`
的门禁跑在
`@build`
之前 · 门禁 =
`pre-commit run --all-files`
- [reference/pitfalls.md ](reference/pitfalls.md ):全部实测坑逐条(含 `--dry-run` 不保护清场段、门禁跑在 `@build` 之前、未跟踪文件不留痕、并集合并残留、新树从未构建 → 体积类门禁必然红)。收工前扫一眼。
`git log`
与合并里都不留痕 · 并集合并残留 · 新树从未构建 → 体积类门禁必然红 ·
`--force`
只覆盖四类检查 · 联接不是 symlink(
`core.symlinks=false`
)· 锁不绑进程存活。
- [reference/verification.md ](reference/verification.md ):每节要留的证据(预演 sha +
- [reference/verification.md ](reference/verification.md ):每节要留的证据(预演 sha + `MT_RC=0` 、blob 对账、两把锁空闲、禁词计数 0、`REPARSE: none` 、改上限数字后的全形态扫描)。
、blob id 对账、两把锁空闲、禁词计数 0、
`REPARSE: none`
…)。
## 与其它 skill 的衔接
- 构建 = `keil-build` skill ( `@build` ) ; 烧录/回读 = `stlink-flash` skill; 串口闭环 = `serial-boot-test` skill
- 烧录产物的真实 hex 路径以 `.serialtest/config.json` 的 hex 键为准(实测
`MDK-ARM/Objects/STM32F407-Demo.hex` 这个路径并不存在),拷错等于烧了旧镜像 → `serial-boot-test` skill
- 新 `.c` 文件 = `new-source-file` skill( `uvprojx` 与 `CMakeLists.txt` 双端注册)
- **DSH worktree 插件**(人类命令 `/worktree` 、模型工具 `worktree_create` ,树在仓内 `.dsh-worktrees/` )
与本 skill 的分工、桥接命令与锁归属 → F 节(本 skill 不删插件树,插件也不删 `_wt/ `)
- `.map` 口径 / RAM 台账与趋势 → `mem-budget` skill; UI 回归 → `ui-verify` skill
- 完整机制与目录分配 → `docs/环境与部署/并行任务工作树方案.md `
- 构建 = `keil-build` ( `@build` ) · 烧录/回读 = `stlink-flash` · 串口闭环 = `serial-boot-test` (真实 hex 路径以 `.serialtest/config.json` 的 hex 键为准,拷错 = 烧旧镜像)
`keil-build`
(
`@build`
)· 烧录/回读 =
`stlink-flash `
· 串口闭环 =
`serial-boot-test `
(真实 hex 路径以
`.serialtest/config.json`
的 hex 键为准,实测
`MDK-ARM/Objects/STM32F407-Demo.hex`
不存在,拷错 = 烧旧镜像)
- 新
`.c`
双端注册 =
`new-source-file`
· RAM/.map 台账 =
`mem-budget`
· UI 回归 =
`ui-verify`
· 计数同步 =
`stale-doc-count-sweep`
· 卡死的技能台账行 =
`mnemon-skill-ledger-stuck-repair`
- DSH worktree 插件(
`/worktree`
/
`worktree_create`
,仓内
`.dsh-worktrees/`
)与本 skill 的分工、桥接命令、锁归属 → F 节 · 完整机制与目录分配 →
`docs/环境与部署/并行任务工作树方案.md`