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、单个尾换行。
This commit is contained in:
@@ -10,14 +10,14 @@ detached HEAD 的干净工作树当净室,一次只往里注入一份在飞文
|
||||
1. **建净室 + 先落基线数**(路径必须**在仓库目录之外**;铁律 7 对临时树同样成立——放仓内就污染 `git status` /
|
||||
`pre-commit --all-files` / 全库搜索):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
MAIN="D:/Code/DTU Code/STM32F4-Base" # 主工作树绝对路径(含空格 → 变量整体加引号)
|
||||
WT="$MAIN/../_wt/und-attr" # 仓外同级 _wt/ 下;一次测量固定用同一个名字
|
||||
rm -rf "$WT" 2>/dev/null
|
||||
git -C "$MAIN" worktree add --detach "$WT" HEAD >/dev/null 2>&1 || { echo WT_FAIL; exit 9; }
|
||||
git -C "$WT" status --porcelain # 应为空
|
||||
"$PY" "$WT/scripts/archive/id_gate/check_ids.py" 2>&1 | tail -25 # ← 口径:id-check 钩子已于 2026-10-28 退役(脚本移入 scripts/archive/id_gate/),按该脚本自身默认参数跑;基线与注入用**同一条命令**
|
||||
@T3@
|
||||
```
|
||||
|
||||
- `--detach`(**不要 `-b`**):净室没有分支、不写 `_wt/.state/<ID>.json` 台账 → **不占 `MAX_CONCURRENT = 8`
|
||||
名额、不出现在 `$TW list`**,也没人会被误指去对它跑 `done`(建与拆都不走 `$TW new` / `$TW done`)。
|
||||
@@ -27,14 +27,14 @@ git -C "$WT" status --porcelain # 应为空
|
||||
看清会删什么,只挪走多出来的那些再测;语料被污染出来的数字与 CI 不可比。
|
||||
2. **一次只注入一份在飞文件 → 跑同一个扫描器 → 与基线差分**(得到的是每个文件的**独立**贡献,不是合计):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
for f in "App/log/sys_log_mcu.c" "test/host/sys_log_mcu/sys_log_mcu_test.c" "docs/子系统设计/运行与自检记录设计说明.md"; do
|
||||
mkdir -p "$WT/$(dirname "$f")"
|
||||
cp "$MAIN/$f" "$WT/$f" # 拷进净室;主树那份只读、一字节不动
|
||||
echo "== $f =="; "$PY" "$WT/scripts/archive/id_gate/check_ids.py" 2>&1 | tail -25 # ← 与第 1 步同一条命令
|
||||
git -C "$WT" checkout -- "$f" # 只还原净室里自己刚拷进来的那份
|
||||
done
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测:`run_par.c` / `board_ui_nav.py` / `check_nested_comment.py` / keil `SKILL.md` **各 +0**,
|
||||
而 log 道那三个文件 **+6 / +8 / +2**,合计**正好 +16**。**Σ(逐文件增量) ≠ 观察到的涨幅 → 还有没被注入的
|
||||
@@ -45,11 +45,11 @@ done
|
||||
全部落进 `skip_bare_unmanaged` —— 到这一步结论才配发给属主。
|
||||
4. **闭合 + 复验主树一个字节没被污染**:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
git -C "$MAIN" worktree remove --force "$WT" # 净室里只有自己的拷贝、没有原件,--force 安全
|
||||
git -C "$MAIN" worktree list # 不该再有 und-attr
|
||||
git -C "$MAIN" status --porcelain | sort # 与测量前**逐行一致**(测量前先存一份基线清单)
|
||||
@T3@
|
||||
```
|
||||
|
||||
⚠ **上面第一行的前提是有条件的**:树里混进了别人的在飞 / 未跟踪内容时「只有自己的拷贝」就不成立了,
|
||||
`--force` 直接拆等于替别人把唯一版本删掉 → 先走 **E 节**(全景 → 逐文件分桶 → 联接预扫 → 唯一残留先备份),
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
|
||||
三步接上(`<name>` 可带 `worktree/` 前缀,也可多段如 `refactor/logging`):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
"$PY" "$TW" plugin list # 一览:分支 / 脏 / 缺哪个联接(缺项另起一行说后果)
|
||||
"$PY" "$TW" plugin adopt N21-dhara-isfree # 只补缺失的 .venv 与 simulator/SDL2 联接
|
||||
"$PY" "$TW" plugin unadopt N21-dhara-isfree # 只摘联接(os.rmdir),绝不动目标内容与树里文件
|
||||
@T3@
|
||||
```
|
||||
|
||||
- **两条联接坏了的样子完全不同**:缺 `.venv` → `ci_local.py` 的四级解释器回退**静默**落到机器级
|
||||
python(无 fontTools = `generated-fresh` 假红的老根因,AGENTS.md 记着);缺 `simulator/SDL2` →
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
(扫描根就是那一个文件,联接目录不在面内)。主树本身没有 SDL2 时不产生这类假红(联接如实镜像主树,
|
||||
F 节那棵树连 SDL2 都没得联),但**别据此认为 `--all` 在树内可信**。
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
F="docs/<子目录>/<本次改动的文件>.md" # 本次改动的那一个文件
|
||||
"$PY" scripts/check_doc_links.py "$F" > ci-artifacts/dl-<slug>.txt 2>&1; echo RC=$?
|
||||
cat ci-artifacts/dl-<slug>.txt
|
||||
@T3@
|
||||
```
|
||||
|
||||
逐文件跑的实测读数:需求文档 161 处章节引用 / 98% 机器可判定 / rc=0;方案文档 16 处 / 88% / rc=0。
|
||||
确实要在树内跑 `--all` 时(例如怀疑主干基线本身),判红顺序**固定三步**,缺一步都不许下结论:
|
||||
@@ -47,13 +47,15 @@ cat ci-artifacts/dl-<slug>.txt
|
||||
`ci-artifacts/`(被忽略 → 不污染脏行数),UTF-8 无 BOM、CR=0、标题 ≤100 字符、正文每行 ≤80(实测标题 31 / 32 字符、
|
||||
正文最长行 50 / 55)——`-m` 会把中文按终端代码页转码搞坏,长中文标题还容易撞 gitlint。硬自检:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
git log -1 --pretty=%B | grep -c '485' # 被清退主题的关键词:必须是 0
|
||||
git log -1 --pretty=%B | grep -ci '\bDE\b' # 同主题的次要关键词:同样 0
|
||||
@T3@
|
||||
```
|
||||
|
||||
关键词不许出现在**标题、正文、合并信息**任何一处(实测标题改写成「删除与本方案无关的其它串口通道对照内容」这种
|
||||
语义短语替掉了它)——否则「别让我看到 X」这条要求在 `git log` 里当场破产。合并信息是另一份文件:
|
||||
`ci-artifacts/merge-msg-<slug>.txt` 落盘后**同样 grep 一遍**再 `git merge --no-ff --no-edit -F`(C2 第 3 步)。
|
||||
自审发现消息里的行数 / 统计算错而**尚未推送** → `git commit --amend -F <改好的文件>` 就地修(实测就这么修掉一次
|
||||
off-by-one);**别追加一笔「更正」提交**——那等于把禁词和被更正的错数一起写回历史。
|
||||
6. **合并提交期 pre-commit 只查冲突文件(2026-10-29 实测)**:合并那一次提交的钩子日志首行是 `[INFO] Checking merge-conflict files only.`,其余钩子全打 `(no files to check) Skipped` —— 于是「合并提交通过了 30 项钩子」这句话**只覆盖冲突文件**,别拿它当全量绿。正解:合并落主干后立刻在主工作树跑全量 `scripts/ci_local.py`(静态 + 提交信息 + 主机侧单测 + manual 段),实测全量这一步才会抓到「表格断列」「章节引用悬空」这类跨文件问题。合并解冲突时若上游把 Markdown 表格重排过,注意**单元格里的竖线必须写成反斜杠竖线**(先例 `docs/子系统设计/运行与自检记录设计说明.md` 的 `FA_WRITE\|FA_SEEKEND`),否则 `FA_WRITE|FA_OPEN_APPEND` 会被当列分隔符打断,而 `doc-links` 不查表格列数——要自己数(第 4 条)。
|
||||
7. **跨层改动拆不成「按层分笔」提交(2026-10-29 实测,四笔改一笔)**:钩子看到的世界 = HEAD + 本笔 staged——pre-commit 会 stash 已跟踪文件的未暂存改动,而**未跟踪文件不在 `git diff-index` 里、不会被藏**(`git add -N` 也救不了,pre-commit 会把它撤下索引、文件仍在盘上)。后果:把双端登记(`MDK-ARM/*.uvprojx` / `simulator/CMakeLists.txt`)、生成器产物、互锁引用的文档分到别的笔里,本笔必被 `doc-links`(引用了还没入册的小节)、`registration-check`(源文件登记在另一笔)、`generated-fresh`(`#page=` 找不到页面描述符、字库清单与源不一致)、`mem-numbers`(受管区还是旧 .map 的数)四道**整仓**门禁一起拦。判据:**一笔提交必须自洽**——互锁的改动(代码 + 登记 + 生成物 + 文档)走同一笔;机制与三条处置结论(半成品要么先落地要么先挪出工作树)→ `docs/代码规范/pre-commit使用说明.md` §2「stash 覆盖面」。
|
||||
|
||||
@@ -11,7 +11,7 @@ stash / checkout / reset / 删除(项目级 Never:丢弃别人的工作)
|
||||
1. **先取证再动手**(别一看见脏就下「谁弄坏的」结论)。记下它的 mtime 与 diff 内容,判断与本合并有无交集,
|
||||
并把**合并前**的 blob id 与 `diff --stat` 存进 `ci-artifacts/`(第 4 步拿它对账):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
MAIN="D:/Code/DTU Code/STM32F4-Base"
|
||||
EXT=".dsh/skills/serial-boot-test/scripts/run_boot_test.py" # 外部脏文件的仓库相对路径
|
||||
mkdir -p ci-artifacts
|
||||
@@ -20,7 +20,7 @@ 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 # ← 以及这份统计(几加几减)
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测:mtime 落在另一会话的活跃时段内,diff 是「docstring 引号改写」2+/2−,与要合的文档毫无交集 →
|
||||
定性为**与本合并无交集、不丢弃**。顺手 `git -C "$MAIN" worktree list` 看一眼有没有**别人中途新建的任务树**
|
||||
@@ -28,9 +28,9 @@ git -C "$MAIN" diff --stat -- "$EXT" | tee ci-artifacts/ext-stat-before.txt
|
||||
2. **`merge-tree` 预演:先证明能干净合并,再碰主干**。`$TW done --dry-run` 在这条路上同样被前置挡住,
|
||||
预演只能自己敲(git 2.38+ 的新语法,打印的是**结果树 sha**):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
git -C "$MAIN" merge-tree --write-tree v2-on-ui task/<ID>-<slug>; echo MT_RC=$?
|
||||
@T3@
|
||||
```
|
||||
|
||||
判据 = **打印出一个 tree sha 且 RC=0**(实测打出 `4220906b…` + `MT_RC=0` 才敢往下走)。⚠ 本机 git 太旧没有
|
||||
这个子命令时退回 `$TW done <ID> --dry-run`——它的前置也是「主树干净」,可用环境变量 `DTU_INTEGRATION_WORKTREE`
|
||||
@@ -39,9 +39,9 @@ git -C "$MAIN" merge-tree --write-tree v2-on-ui task/<ID>-<slug>; echo MT_RC=$?
|
||||
3. **主工作树里手工 `--no-ff` 合并**(合并信息走 `-F` 文件,放在被忽略的 `ci-artifacts/`;编码口径同 C 第 5 步:
|
||||
UTF-8 无 BOM、正文每行 ≤80 字符;标题格式 `merge: task/<ID>-<slug> 合入 <base>`):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
git -C "$MAIN" merge --no-ff --no-edit -F ci-artifacts/merge-msg-<slug>.txt task/<ID>-<slug>
|
||||
@T3@
|
||||
```
|
||||
|
||||
pre-commit 会**自己**把那份未提交改动 stash 起来、跑完钩子再放回(输出里能看到 `[INFO] Stashing unstaged files to …`)
|
||||
——**这条不是让你去手工 stash**,正好相反:钩子的 stash 往返让它自己走完,你只需在第 4 步验证它把东西原样还回来了。
|
||||
@@ -52,12 +52,12 @@ git -C "$MAIN" merge --no-ff --no-edit -F ci-artifacts/merge-msg-<slug>.txt task
|
||||
4. **自证那份外部改动原样还在**(本节唯一的硬证据,两次实测都靠它结案):合并后**再跑一次**同一对命令,
|
||||
与第 1 步存的两个值**逐项相同**:
|
||||
|
||||
@T3@bash
|
||||
```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:没被顺手提交,也没被清成干净
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测:合并前后 blob id 同为 `369380ca8eb024b593d872455b6a020797abcd8d`、`diff --stat` 同为 2+/2−
|
||||
→ 证明 pre-commit 的 stash 往返没动别人的工作;合进主干的那份文档另按 G 节复核(零残留 + 全库基线)。
|
||||
@@ -66,10 +66,10 @@ git -C "$MAIN" status --porcelain # 该文件仍是 M:没被顺
|
||||
5. **收工仍走技能自身的代码路径,别自己手写 rm**:先把台账**备份成 `*.state-bak`**(后缀与 E 第 6 步统一),
|
||||
再让脚本自己摘联接:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
cp "$WTROOT/.state/<ID>.json" "$MAIN/ci-artifacts/<ID>.json.state-bak" # 先备份再动
|
||||
"$PY" "$TW" done <ID> # 打「已合入 <base>,跳过合并,直接清场」→ 先摘 .venv 与 simulator/SDL2 联接 → 删工作树 → 删分支 → unlink 台账
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测这条路径能走完(日志:`state backup -> ci-artifacts\screenlink-doc-history.json.state-bak` +
|
||||
`[ok] 已摘除联接 .venv` + `[ok] 已摘除联接 simulator/SDL2`)。**若它仍拿「主工作树不干净」挡在合并前**(没走到
|
||||
|
||||
@@ -115,3 +115,4 @@
|
||||
进入 acquire、`finally` 无条件 release,所以"Agent 忘了 release、锁一直挂到 TTL"这条在构建路径上已消除。
|
||||
`done` 的门禁自己持锁并给子进程设 `DTU_LOCK_HELD=build` 告知跳过(新旧脚本都兼容)。
|
||||
**`bench` 锁仍须 Agent 手工持**(会话作用域:跨烧录→`@test`→探针,见 `keil-build` SKILL.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,**别为了预演去抢别人的锁**。
|
||||
|
||||
@@ -14,7 +14,7 @@ HEAD 是 detached(`git rev-parse --abbrev-ref HEAD` 打 `HEAD`);或树里
|
||||
1. **建全景(全程只读)**:先看 `git worktree list` 的全集,再**逐棵**记三件事(脏行数含未跟踪 / 树顶提交 / 有无分支)
|
||||
加一条台账判定,同时存下主树的拆前基线清单:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
MAIN="D:/Code/DTU Code/STM32F4-Base"; WTROOT="$MAIN/../_wt"
|
||||
git -c core.quotePath=false worktree list # 全集:登记树 + 非登记树都在这份里
|
||||
MAIN_SHA=$(git -C "$MAIN" rev-parse --short HEAD) # 下面所有比对都以这一版为主线尺子
|
||||
@@ -28,7 +28,7 @@ for t in "$WTROOT"/*/; do
|
||||
git -C "$t" rev-parse --abbrev-ref HEAD # 打 HEAD = detached,压根没分支可 done
|
||||
ls "$WTROOT/.state/$(basename "$t").json" 2>/dev/null || echo "NO-LEDGER" # 台账位置随 DTU_WT_ROOT
|
||||
done
|
||||
@T3@
|
||||
```
|
||||
|
||||
- **`-uall` 不是可选**:默认 `status --porcelain` 会把未跟踪目录折叠成一行 `dir/`,`-uall` 才展开到文件级;少了它
|
||||
第 2 步的分桶就漏项(实测 `und-attr` 唯一那份就是未跟踪 `?? scripts/check_nested_comment.py`)。
|
||||
@@ -39,7 +39,7 @@ done
|
||||
内容,拆 = 丢)/ `OLDER-DRAFT`(同路径主线已有更新版本)。落成独立只读探针跑(别用 heredoc 现写现改,
|
||||
见陷阱「别用 heredoc 打补丁造探针」):
|
||||
|
||||
@T3@python
|
||||
```python
|
||||
# ci-artifacts/wt_bucket_scan.py — 只读:逐文件与主线工作区/HEAD 比对,三桶归类
|
||||
import os, subprocess, tempfile
|
||||
MAIN = r"D:\Code\DTU Code\STM32F4-Base"
|
||||
@@ -87,12 +87,12 @@ for name in TREES:
|
||||
for b in ("UNIQUE-UNMERGED", "OLDER-DRAFT", "OTHER"):
|
||||
for rel, why in tally.get(b, []):
|
||||
print(f" {b}: {rel} # {why}")
|
||||
@T3@
|
||||
```
|
||||
|
||||
报告里必须**逐桶给计数 + 判定证据**(实测:`verify-task16` IN-MAINLINE=6 / UNIQUE=0;`verify-batch` IN-MAINLINE=35 /
|
||||
UNIQUE=1;`und-attr` IN-MAINLINE=0 / UNIQUE=1)。两个非空桶再各钉一条命令把证据坐实:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
# 「唯一未合并」是真更新还是更旧草稿?blob 级 diff + 逐行看方向
|
||||
git -c core.quotePath=false -C "$MAIN" diff --stat "$MAIN_SHA" -- simulator/tests/README.md
|
||||
diff <(git -C "$MAIN" show "$MAIN_SHA":simulator/tests/README.md) "$WTROOT/verify-batch/simulator/tests/README.md"
|
||||
@@ -100,7 +100,7 @@ diff <(git -C "$MAIN" show "$MAIN_SHA":simulator/tests/README.md) "$WTROOT/verif
|
||||
for c in 77779d49 1d4b6a17; do
|
||||
git -C "$MAIN" merge-base --is-ancestor "$c" HEAD && echo "$c MERGED" || echo "$c NOT-merged"
|
||||
done
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测:那份 `simulator/tests/README.md` 树内写「27 页 × 3200 B」、主线 HEAD 已是「60 张 × 3200 B」→ **更旧草稿**(被取代的
|
||||
早先版本),不是未合并成果;`bench-21-77779d49` / `bench-23-1d4b6a17` 两棵 `merge-base --is-ancestor` 判 **MERGED** 且脏数 = 0
|
||||
@@ -109,7 +109,7 @@ done
|
||||
还得看未跟踪那一层。`UNIQUE-UNMERGED` 非空又确实要留 → 它进第 4 步的备份,**不许靠 `--force` 顺手带走**。
|
||||
3. **拆前扫联接 / reparse point(物理安全性 = 铁律 5 的可执行判据)**。存成 `ci-artifacts/wt_junction_scan.py` 再跑:
|
||||
|
||||
@T3@python
|
||||
```python
|
||||
# 只读预扫:一个路径是不是 reparse point(Windows 目录联接在这里现形);发现即停止深入,绝不 walk 进联接
|
||||
import os, ctypes
|
||||
FILE_ATTRIBUTE_REPARSE_POINT = 0x400
|
||||
@@ -131,7 +131,7 @@ for name in ["verify-task16", "verify-batch", "und-attr", "bench-21-77779d49", "
|
||||
print(" REPARSE: none → 可直接 git worktree remove --force"
|
||||
if not hits else " REPARSE:\n " + "
|
||||
".join(hits))
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测输出 `== verify-task16 ... REPARSE: none`(每棵的体积 / 条目数 + 零联接就是证据),而 `bench-*` 两棵各挂一个 `.venv`
|
||||
联接 → 判 **KEEP**(本节不拆它们;将来真要拆,先 `rmdir` 摘掉联接再 remove)。
|
||||
@@ -144,7 +144,7 @@ for name in ["verify-task16", "verify-batch", "und-attr", "bench-21-77779d49", "
|
||||
`ci-artifacts/wt-leftover-backup/`(`ci-artifacts/` 已被 `.gitignore` 忽略 → **备份不会改变主树脏行数**;仓库根不放一次性产物),
|
||||
**文件名 + 字节数必须打进输出**当证据:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
BK="$MAIN/ci-artifacts/wt-leftover-backup"; mkdir -p "$BK"
|
||||
# 命名约定:<树名>__<原路径里的 / 换成 _>,反向可定位;台账备份统一后缀 .state-bak
|
||||
cp "$WTROOT/verify-batch/simulator/tests/README.md" "$BK/verify-batch__simulator_tests_README.md"
|
||||
@@ -154,25 +154,25 @@ for t in verify-task16 verify-batch und-attr; do
|
||||
git -C "$MAIN" worktree remove --force "$WTROOT/$t"; echo "$t remove_rc=$?" # 不接管道才读得到码(见「读输出」)
|
||||
done
|
||||
git -C "$MAIN" worktree prune # 顺带清 .git/worktrees 里的残籍
|
||||
@T3@
|
||||
```
|
||||
|
||||
自检:**备份条目数 = 第 2 步里判「要留档」的条目数**,且每个都带字节数。这一步就是本节相对 D 第 4 步的增量——
|
||||
D 的前提是「净室里只有自己的拷贝」,本节的树里混着别人在飞的东西,`--force` 是一次不可逆删除,**先备份才谈得上安全**。
|
||||
5. **复验:主树一个字节没动 + 树确实没了**:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
git -C "$MAIN" status --porcelain -uall > ci-artifacts/wt-main-after.txt
|
||||
wc -l < ci-artifacts/wt-main-before.txt; wc -l < ci-artifacts/wt-main-after.txt # 两个数必须相同(实测 0 → 0)
|
||||
diff ci-artifacts/wt-main-before.txt ci-artifacts/wt-main-after.txt # 空输出 = 逐行一致
|
||||
git -c core.quotePath=false -C "$MAIN" worktree list # 已拆路径逐条消失;主树与判「保留」的树原样在列
|
||||
@T3@
|
||||
```
|
||||
|
||||
行数不同 = 备份或摘除碰到了主线(多半是备份写进了未被忽略的目录,或漏摘联接)→ 立刻定位,别报「回收完成」。
|
||||
第 2 步判「保留不动」的树还在 `worktree list` 里是**预期结果**,在报告里点名解释为什么留(附 `MERGED` 判据)。
|
||||
6. **顺手清悬挂台账**(branch / path 都已不存在的 `_wt/.state/<ID>.json`;它会让 `$TW list` 打出一行怪条目、并让下次
|
||||
`new` 同 ID 报「任务 <ID> 已存在」)。先给证据、后动手:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
for j in "$WTROOT"/.state/*.json; do # 台账位置随 DTU_WT_ROOT
|
||||
id=$(basename "$j" .json)
|
||||
br=$("$PY" -c "import json,sys;print(json.load(open(sys.argv[1],encoding='utf-8')).get('branch',''))" "$j")
|
||||
@@ -181,19 +181,19 @@ for j in "$WTROOT"/.state/*.json; do # 台账位置随 DTU_WT_ROOT
|
||||
[ -d "$pp" ] && p=path-OK || p=path-MISSING
|
||||
echo "$id $b $p <- $j"
|
||||
done
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测这条判据打出的正是 `v5tool2 branch-MISSING path-MISSING`:`git rev-parse task/v5tool2-v11` 报
|
||||
`fatal: ambiguous argument 'task/v5tool2-v11': unknown revision ...`,台账 `path` 指的目录已无,`git worktree list` 里也没有这棵树。
|
||||
**三条齐(rev-parse fatal + path 目录不存在 + worktree list 无此树)才叫悬挂**;缺任何一条都回到第 1~2 步查是不是真有东西要保。
|
||||
然后**备份 → 删 → 复跑两条 list 复验**:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
cp "$WTROOT/.state/v5tool2.json" "$BK/v5tool2.json.state-bak" && rm "$WTROOT/.state/v5tool2.json"
|
||||
"$PY" "$TW" list # 该条目消失(全空时打「(没有任务空间。用 new <ID> <slug> 建一个)」),echo RC=$? 应为 0
|
||||
"$PY" "$TW" lock status # build 空闲(TTL 1.0h)/ bench 空闲(TTL 6.0h)—— 证明清台账没牵连两把锁
|
||||
ls "$WTROOT"/.state/locks/ 2>/dev/null # 若该 ID 名下还有 .lock,先按 B 节释放,再删台账
|
||||
@T3@
|
||||
```
|
||||
|
||||
- **必须 `cp` 先备份再 `rm`**:台账里存着 base / junctions / needs_bench / bench_pass,是真出纠纷时唯一的现场记录。
|
||||
- 悬挂条目按定义 `path` 已不存在 → 通常**不占** `MAX_CONCURRENT = 8` 名额(A 第 1 步的「活跃」判据要求 path 目录还在),
|
||||
|
||||
Reference in New Issue
Block a user