Files
STM32F4-Base/.dsh/skills/task-worktree/reference/merge-dirty-main-tree.md
T
wandering1 ecf9d3ad3d docs(task-worktree skill): 修 @T3@ 围栏腐坏,正文瘦身并把细节外置 reference/
围栏腐坏是真 bug:@T3@ 是被误提交进仓的代码围栏占位符,Markdown 渲染出
来就是一行 @T3@ 加裸命令。git log -S 定位:842d711e(2026-10-28)
SKILL.md 还有 18 个真三反引号围栏行、0 个占位符;21c68521(2026-10-29
插件桥接那笔)起变 0 围栏 / 34 个占位符。全仓只有本技能 6 个文件有此
病,draw-diagram / keil-build / ui-verify 等都用真围栏。本轮七个文件
占位符清零、围栏全部成对。

信息密度:SKILL.md 28,139 → 23,087 B(−18%,占插件写侧上限 65536 的
35%,给自进化留头量)。同一句外置理由在 C2/D/E/F/G/陷阱/验证 重复 7 次
→ 0;frontmatter 与 F 节写了两遍的插件桥接说明收进 F 节;「这不是免费
的」「挡给你看的保护」这类修辞删掉。删掉的细节全部落进对应 reference
篇——skill-size 钩子会校验指针不悬空:现 26 处链接 / 8 篇目标 / 悬空 0,
每篇都有入口。

锚点未重排:节号 A–G/C2、各节步骤号、铁律编号被 reference/ 反向引用
(铁律 3/5/7/9 与 A 第 1、5 步,C 第 5~7 步,E 第 1~6 步均结构性存在)。
只在 SKILL.md 出现的 PIPESTATUS 教训保留(reference/ 里没有,删了就丢)。

门禁:check_skill_size.py rc=0(17 个 SKILL.md 全在写侧上限内、无悬空
指针);七文件均无 BOM / CR / U+FFFD、单个尾换行。
2026-10-30 00:31:36 +08:00

87 lines
7.4 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.
### 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 <ID>` 的前置正好卡在这——
它要求主工作树干净,否则未提交改动会被卷进合并(见 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/<ID>-<slug>; echo MT_RC=$?
```
判据 = **打印出一个 tree sha 且 RC=0**(实测打出 `4220906b…` + `MT_RC=0` 才敢往下走)。⚠ 本机 git 太旧没有
这个子命令时退回 `$TW done <ID> --dry-run`——它的前置也是「主树干净」,可用环境变量 `DTU_INTEGRATION_WORKTREE`
把集成点指到一棵干净的临时树去演练(见本文开头的环境变量);**别为了跑 dry-run 去动主树那份脏改动**。
预演报冲突 → 回到冲突本身处理,不许直接 `--no-ff` 硬合。
3. **主工作树里手工 `--no-ff` 合并**(合并信息走 `-F` 文件,放在被忽略的 `ci-artifacts/`;编码口径同 C 第 5 步:
UTF-8 无 BOM、正文每行 ≤80 字符;标题格式 `merge: task/<ID>-<slug> 合入 <base>`):
```bash
git -C "$MAIN" merge --no-ff --no-edit -F ci-artifacts/merge-msg-<slug>.txt task/<ID>-<slug>
```
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/<ID>.json" "$MAIN/ci-artifacts/<ID>.json.state-bak" # 先备份再动
"$PY" "$TW" done <ID> # 打「已合入 <base>,跳过合并,直接清场」→ 先摘 .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` 会得到「任务 <ID> 不存在(缺台账 …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 前后一致)。「不能丢弃别人的工作」类问题九成是并发会话造成的,不写清就等于没取证。