围栏腐坏是真 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、单个尾换行。
7.4 KiB
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 合并 → 自证外部文件没被动过 → 照常收工,六步:
- 先取证再动手(别一看见脏就下「谁弄坏的」结论)。记下它的 mtime 与 diff 内容,判断与本合并有无交集,
并把合并前的 blob id 与
diff --stat存进ci-artifacts/(第 4 步拿它对账):
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):
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>):
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 步存的两个值逐项相同:
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 步统一),
再让脚本自己摘联接:
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 会得到「任务 不存在(缺台账 …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 前后一致)。「不能丢弃别人的工作」类问题九成是并发会话造成的,不写清就等于没取证。