### C2. `done` 被「主工作树不干净」挡住,而那份脏改动是**别的并发会话**留下的 **场景**(2026-10-29 同一套流程在同一天跑了两遍,两遍都撞上它):任务树里的文档改完、准备合回 `v2-on-ui`, 主工作树却压着一个自己从没碰过的未提交改动(两次都是 `M .dsh/skills/serial-boot-test/scripts/run_boot_test.py`, 另一个并发会话/插件留下的)。它既不能提交(不是我的改动,也不知道写没写完),也不能 stash / checkout / reset / 删除(项目级 Never:丢弃别人的工作)。`$TW done ` 的前置正好卡在这—— 它要求主工作树干净,否则未提交改动会被卷进合并(见 C 第 6 步),于是**合并与回收一起被挡**。 **正解 = 取证 → `merge-tree` 预演 → 主树手工 `--no-ff` 合并 → 自证外部文件没被动过 → 照常收工**,六步: 1. **先取证再动手**(别一看见脏就下「谁弄坏的」结论)。记下它的 mtime 与 diff 内容,判断与本合并有无交集, 并把**合并前**的 blob id 与 `diff --stat` 存进 `ci-artifacts/`(第 4 步拿它对账): ```bash MAIN="D:/Code/DTU Code/STM32F4-Base" EXT=".dsh/skills/serial-boot-test/scripts/run_boot_test.py" # 外部脏文件的仓库相对路径 mkdir -p ci-artifacts git -C "$MAIN" status --porcelain # 全量脏清单(本会话只认自己那几条) stat -c "%y %s %n" "$MAIN/$EXT" # mtime + 字节数:定性是谁在什么时候改的 git -C "$MAIN" diff -- "$EXT" | cat # 内容性质;与本次合并有无同文件同区域 git -C "$MAIN" hash-object "$MAIN/$EXT" | tee ci-artifacts/ext-blob-before.txt # ← 存下这个 blob id git -C "$MAIN" diff --stat -- "$EXT" | tee ci-artifacts/ext-stat-before.txt # ← 以及这份统计(几加几减) ``` 实测:mtime 落在另一会话的活跃时段内,diff 是「docstring 引号改写」2+/2−,与要合的文档毫无交集 → 定性为**与本合并无交集、不丢弃**。顺手 `git -C "$MAIN" worktree list` 看一眼有没有**别人中途新建的任务树** (两次里都撞见第二棵树 `data-trust-restructure`,正是这份外部改动的来路)——这是并发会话的硬证据。 2. **`merge-tree` 预演:先证明能干净合并,再碰主干**。`$TW done --dry-run` 在这条路上同样被前置挡住, 预演只能自己敲(git 2.38+ 的新语法,打印的是**结果树 sha**): ```bash git -C "$MAIN" merge-tree --write-tree v2-on-ui task/-; echo MT_RC=$? ``` 判据 = **打印出一个 tree sha 且 RC=0**(实测打出 `4220906b…` + `MT_RC=0` 才敢往下走)。⚠ 本机 git 太旧没有 这个子命令时退回 `$TW done --dry-run`——它的前置也是「主树干净」,可用环境变量 `DTU_INTEGRATION_WORKTREE` 把集成点指到一棵干净的临时树去演练(见本文开头的环境变量);**别为了跑 dry-run 去动主树那份脏改动**。 预演报冲突 → 回到冲突本身处理,不许直接 `--no-ff` 硬合。 3. **主工作树里手工 `--no-ff` 合并**(合并信息走 `-F` 文件,放在被忽略的 `ci-artifacts/`;编码口径同 C 第 5 步: UTF-8 无 BOM、正文每行 ≤80 字符;标题格式 `merge: task/- 合入 `): ```bash git -C "$MAIN" merge --no-ff --no-edit -F ci-artifacts/merge-msg-.txt task/- ``` pre-commit 会**自己**把那份未提交改动 stash 起来、跑完钩子再放回(输出里能看到 `[INFO] Stashing unstaged files to …`) ——**这条不是让你去手工 stash**,正好相反:钩子的 stash 往返让它自己走完,你只需在第 4 步验证它把东西原样还回来了。 **为什么可以绕开 `done`**(报告里要写清这句):`done` 的前置要求主工作树干净,而那份外部改动既不能提交也不能 丢弃,所以合并只剩「手工预演 + 手工合并」一条合法路径;被跳过的门禁与审计由第 2 步(预演 sha + RC=0)、 第 4 步(blob id 对账)、G 节(合并后在主树跑全库基线)、第 6 步(`lock status`)逐项补回。 **这不是绕过保护**,是把保护要求的证据逐条自己交出来。 4. **自证那份外部改动原样还在**(本节唯一的硬证据,两次实测都靠它结案):合并后**再跑一次**同一对命令, 与第 1 步存的两个值**逐项相同**: ```bash git -C "$MAIN" hash-object "$MAIN/$EXT" | tee ci-artifacts/ext-blob-after.txt git -C "$MAIN" diff --stat -- "$EXT" | tee ci-artifacts/ext-stat-after.txt diff ci-artifacts/ext-blob-before.txt ci-artifacts/ext-blob-after.txt # 空输出才算过 git -C "$MAIN" status --porcelain # 该文件仍是 M:没被顺手提交,也没被清成干净 ``` 实测:合并前后 blob id 同为 `369380ca8eb024b593d872455b6a020797abcd8d`、`diff --stat` 同为 2+/2− → 证明 pre-commit 的 stash 往返没动别人的工作;合进主干的那份文档另按 G 节复核(零残留 + 全库基线)。 blob id 变了 = stash pop 出了半冲突(现场会留下 stash 与冲突标记)→ **立刻停止收工**,把两份 blob id 与两份 diff 原样贴进报告交给该文件属主;**绝不用 `git checkout -- $EXT` 抹平**(那是销毁别人的工作,项目级 Never)。 5. **收工仍走技能自身的代码路径,别自己手写 rm**:先把台账**备份成 `*.state-bak`**(后缀与 E 第 6 步统一), 再让脚本自己摘联接: ```bash cp "$WTROOT/.state/.json" "$MAIN/ci-artifacts/.json.state-bak" # 先备份再动 "$PY" "$TW" done # 打「已合入 ,跳过合并,直接清场」→ 先摘 .venv 与 simulator/SDL2 联接 → 删工作树 → 删分支 → unlink 台账 ``` 实测这条路径能走完(日志:`state backup -> ci-artifacts\screenlink-doc-history.json.state-bak` + `[ok] 已摘除联接 .venv` + `[ok] 已摘除联接 simulator/SDL2`)。**若它仍拿「主工作树不干净」挡在合并前**(没走到 清场分支),改走 **E 节**的手工回收路径:台账仍按本步先 `cp` 成 `.state-bak`;第 1~2 步的前置换成 「`status --porcelain -uall` 为空 + 唯一提交为空 + `merge-base --is-ancestor` 判 MERGED」三条(本次实测三条齐), 第 3 步的联接预扫 + `rmdir` 摘联接**一步不能省**;再 `git worktree remove`(三条前置齐了就不必 `--force`)、 删分支、`git worktree prune`。清完再敲一次 `done` 会得到「任务 不存在(缺台账 …json)」(exit 2)—— **这不是失败**(台账已被 unlink;判完成以 `git log` 为准,见 C 第 7 步)。 6. **复验收工**:`git -C "$MAIN" worktree list` 只剩主树 + **别人的树**(那棵不是你的,保留并在报告里点名, 别顺手回收 → E 节);`"$PY" "$TW" lock status` 打 `build 空闲(TTL 1.0h)` + `bench 空闲(TTL 6.0h)` 才收工。 **并发会话证据必须写进报告**:主树里那份外部脏文件、中途冒出来的第二棵任务树,都要点名——是谁的、 你怎么定性的、对这次合并的影响(本例:与本合并无交集 / 不丢弃 / 不进本次提交),以及你**没动它**的证据 (blob id 前后一致)。「不能丢弃别人的工作」类问题九成是并发会话造成的,不写清就等于没取证。