用户裁定:文档只写现状,历史流水一律删掉,过程查 git log。 - 43 篇「变更记录 / 修订记录」整节删除:继电保护规划 12.6 KB、产品 需求 11.8 KB、项目部署环境 5.5 KB、BSD Socket 指南 5.3 KB、架构 总览 5.0 KB、实机测试实施指南 3.8 KB 等 - 23 篇别处仍有指向:只留节名 + 一行「本文件不再记流水账,过程查 git log」,节号与锚点不位移(doc-links 不因此断链) - 5 篇整表保留(被当唯一证据 / 活台账引用):看门狗设计说明、配置 文件设计说明、RAM预算台账、内容页架构、本地工具路径环境变量方案 - 规范同步:文档规范 §5.1 改新裁定;测试文档模板规范删示例节与 登记句;测试开发标准流程 / UI 两篇 / 实机测试实施指南 / 四篇单测 说明 / 并行任务工作树方案 / 编号规范 的「登记变更记录」改「写进 正文」 - 技能同步:stale-doc-count-sweep / ui-verify / id-family 三个 SKILL 正文不再要求追加流水行(顺带消掉 SKILL.md:121「产品需求 §12」 失效引用) - 修 5 处悬空指针:采样有效性校验目录锚点、BSD 指南「文末变更 记录」、docs/README 过期描述、网络自愈架构两处指向面 72 篇文档净减 600 余行;check_doc_links --all --sections-strict 可判 定比例 61% → 78%、失效引用 0;静态门禁 passed=50 failed=0;gitlint 10 条历史提交信息 0 failed。 未提交他人在飞改动:.dsh/skills/keil-build/*(%SystemDrive% 守卫)。
31 KiB
并行任务工作树工作流(worktree + task-worktree skill)
这个文件是什么:多任务并行开发的机制规格——如何为每个任务开一个互不干扰的工作空间、 干完如何合并回主干、哪些资源必须串行、冲突怎么治理。
状态:已落地并端到端实测(2026-10-12)——skill 在
.dsh/skills/task-worktree/, 实测记录与暴露并修复的缺陷见 §11。决策基线(2026-10-12 用户确认):工作树放仓库同级
_wt/;集成分支沿用v2-on-ui;done回收不自动 push(沿用「push 需用户明确许可」)。
1. 目的与范围
要解决的问题:多个任务同时推进时,若共用同一个工作目录,会互相踩——构建产物、日志目录、
Python 环境、以及 docs/任务规划/ 这类全局唯一登记簿都会被并发写坏。
做法:每个任务一棵 git worktree + 一条任务分支,任务之间物理隔离;干完由固定命令合回主干。
范围:只解决「开发期隔离 + 回收期合并」的机械流程。不改变既有的分支纪律
(push 需许可)、门禁口径(pre-commit 51 钩子 + @build + @test)与文档约定。
2. 方案选型
| 方案 | 为什么不用 |
|---|---|
| 同一目录切分支 | 构建产物 / .venv / 日志目录 / .workbuddy-ai 全共享,两任务互相踩;且本仓库明令禁止用 git checkout 丢弃未提交工作 |
每任务 git clone |
对象库 234 MB × N 份、Git LFS 全量重复;克隆之间无法安全共享分支,推送关系也更绕 |
git stash / 打补丁倒换 |
不具备并发安全性,冲突只能靠人工记忆 |
git worktree(采用) |
唯一同时满足「共享对象库 + 独立工作目录 + 独立 HEAD/index」的机制;git 原生,本仓库已有先例 |
命名惯例来源:D:/Code/DTU Code/_v1pin-build、_v2oui-build 曾是遗留 worktree(detached HEAD,
2026-09-19 建、已过期),本方案沿用其「仓库同级 + _ 前缀」命名,但把散落的目录收敛到统一的
_wt/ 容器下。两者已于 2026-10-12 清理:_v1pin-build 干净直接移除;_v2oui-build 有 2 个
未提交改动(uvprojx / freertos.c),已按字节留档于 _wt/.archive/2026-10-12-legacy-worktrees/
后才移除(两提交由两条 wip/* 保活分支持有,其一为 wip/v2-pending-recv;无提交丢失)。
3. 目录与资源分配
3.1 目录布局
D:/Code/DTU Code/
├── STM32F4-Base/ # 主工作树 = 集成分支 v2-on-ui(唯一检出主干的地方)
│ ├── .git/ # 共享对象库 + hooks(worktree 自动共享,无需任何操作)
│ ├── .venv/ # 唯一实体(Python 环境)
│ ├── .workbuddy-ai/ # 唯一实体(记忆;**不**共享给任务树,见 3.2)
│ └── simulator/SDL2/ # 唯一实体(模拟器 SDK)
└── _wt/ # ★ 并行任务容器(不在任何 git 仓库内)
├── .state/<ID>.json # 任务台账:分支 / 联接 / 门禁与台架戳
├── .state/locks/ # 独占锁:build.lock(UV4)+ bench.lock(COM6/ST-LINK/设备 IP)
└── <ID>-<slug>/ # ← 一棵工作树(分支 task/<ID>-<slug>)
├── .git # 文件(指向共享对象库,git 自动创建)
├── .venv # → junction 到主工作树
├── simulator/SDL2 # → junction 到主工作树
├── .workbuddy-ai # 本任务自己的(清场前归并回主工作树)
└── MDK-ARM/… # 本任务独占的构建产物
工作树绝不能建在仓库内部(STM32F4-Base/ 里):那会变成未跟踪目录,污染 git status、
pre-commit --all-files 与全库搜索。故统一放仓库同级的 _wt/。
3.2 共享 vs 独占
| 资源 | 归属 | 原因 |
|---|---|---|
.git/ 对象库 + hooks |
共享(git 机制自动) | 工作树共享 common dir,提交门禁无需在新空间重装 |
.venv/ |
共享(junction) | 体积大、内容与任务无关;每任务重建一份纯属浪费 |
simulator/SDL2/ |
共享(junction) | 下载型 SDK,与任务无关 |
.workbuddy-ai/ |
独占(刻意不共享) | ① 避免并发写同一份日志丢更新;② 不给联接递归删除的机会——它是本项目最不可再生的目录。任务侧记忆由清场前归并回主工作树 |
MDK-ARM/ 构建产物、simulator/build/ |
独占 | 正是需要隔离的部分:两个任务可各自构建互不干扰 |
.serialtest/config.json、.dsh/skills/、.pre-commit-config.yaml |
独占但内容一致 | 受版本控制,随检出白拿,无需 bootstrap |
.mnemon/、ci-artifacts/ |
独占 | 本地运行态,gitignored |
3.3 junction 规则(不能用 symlink)
本仓库 core.symlinks=false:git 里的符号链接会被检出成「内容是目标路径的普通文本文件」,
不是链接。因此共享 .venv 只能靠 Windows 目录联接(junction),且 junction 必须创建在
版本控制之外(_wt/<ID>-<slug>/ 里的那几个路径都不在 git 索引内,安全)。
创建方式用 mklink /J(不需要管理员权限,也不同于符号链接)。实测(2026-10-12):
用 ctypes 直调 DeviceIoControl(FSCTL_SET_REPARSE_POINT) 会被系统拒绝(Win32 4392),
缓冲区经 hexdump 逐字节核对无误仍失败,而 mklink /J 正常 → 采用后者;参数以列表
交给 subprocess,含空格的路径(本项目 DTU Code)由 subprocess 自动加引号。
摘除联接必须用 os.rmdir(= RemoveDirectoryW,只摘链不递归)。实测 .venv 条目数
摘除前后一致(2809 → 2809)。清场顺序铁律:先摘联接,再 git worktree remove——
否则 git 可能递归进联接,把主工作树的 .venv / SDL2 一起删掉。
⚠ 不要在仓库里提交任何指向
../的 symlink 来实现共享——在本机就是坏文件。 ⚠ MSYS 的rmdir拒绝摘除联接(把它当符号链接),手工清场要用 Python 的os.rmdir或直接走本 skill 的abort/done。
4. 分支模型
| 项 | 取值 |
|---|---|
| 集成分支(主干) | v2-on-ui |
| 任务分支 | task/<ID>-<slug>,从主干当前 HEAD 切出 |
<ID> |
沿用 docs/任务规划/README.md 的编号空间(P0-x/N-x/S-x/T-x/B-x/C-x/D-x);无对应条目的临时任务用 X-<日期>-<slug> |
| 合并方式 | git merge --no-ff 合入主干 |
| 并发上限 | 同时最多 3 棵工作树 |
<ID> 复用登记簿编号是为了可追溯:任务分支名直接对应待办条目,回收时销项有据可查。
并发上限 3 的依据:.git 已 234 MB,每棵工作树要检出约 2400 个受控文件(含 Git LFS 图片),
并发太多磁盘与 I/O 都不划算,人工也管不过来。
5. 任务生命周期
| 阶段 | 动作 | 并行性 |
|---|---|---|
new |
建分支 + 工作树 + junction + 台账 | 可并行 |
| 编码 | 在新空间里改代码 | 可并行 |
| 单测产物 | %TEMP%\dtu_host\<工作树哈希>\ 分家 + 每跑独占 runs\<pid>-<ns>\ |
不需要锁,但必须按树分家(旧固定共享名跨树互踩:A 树可能执行 B 树编出的同名 exe,summary 相同不可辨;build/bench 是真独占,继续用锁) |
| 门禁 | scripts/ci_local.py(静态门禁 + 主机侧纯逻辑单测,纯软件) |
可并行 |
| 构建 | @build(UV4 增量,需持 build 锁) |
必须串行 |
| 台架 | @test / 板级套件(需先持台架锁,见 §7) |
必须串行 |
| 提交 | 本地 git commit(多行正文用 -F <UTF-8 无 BOM 文件>) |
可并行 |
sync |
rebase 追平主干,把冲突控制在任务分支上 |
可并行 |
done |
预演 → 合入主干 → 残留审计 → 重生成产物 → 门禁 → 清理工作树 | 必须串行 |
push |
推送主干/任务分支 | 需用户明确许可 |
为什么 done 必须串行:合并动作会移动主干引用,两处同时合入会互相覆盖;且主干只在主工作树检出。
6. 冲突治理
6.1 单写者文档
并行失败通常不是代码冲突,而是多个任务同时改同一个「登记簿」。以下文件在任务期间
禁止在任务工作树里修改,一律由 done 回收阶段在主工作树统一处理:
| 文件 | 冲突原因 | 回收时的处理 |
|---|---|---|
docs/任务规划/(整个文件夹) |
全项目唯一登记簿,每个任务都要销项 | 由主工作树统一改,任务分支不碰 |
AGENTS.md、docs/README.md(索引/登记类) |
每个任务都要在 AGENTS.md 的「按主题查哪份文档」表、docs/README.md §9.2 环境表、docs/README.md 的文档索引表里各加一行 → 同一张表的新增行两任务必撞(2026-10-12 实测:主工作树里本任务的行与他任务的行已并排混在同一 hunk)。变更记录那一路不再是冲突面:2026-10-28 起新文档一律不设变更记录节(docs/文档规范.md §5.1),存量也只剩 5 篇台账/证据文档 |
由主工作树统一追加;提交时只 git add 本任务的行(git apply --cached 选择性入库),勿整文件 git add |
AGENTS.md 的 TASK_TABLE 块 |
生成物,两个任务都抬栈就冲突 | 合并后重跑 scripts/gen_task_table.py 重新生成,不手工并 |
MDK-ARM/STM32F407-Demo.uvprojx |
新源文件注册 + UV4 自动改写 | 手工并 <File> 条目 → 重跑 registration-check → @build |
.pre-commit-config.yaml |
新增主机单测要加钩子 | 并集处理 |
docs/内存管理/RAM使用分析与优化方案.md(及其 mem-numbers 快照) |
mem-numbers 快照 |
绝不手工合数字,合并后 scripts/check_mem_numbers.py --write 重生成 |
6.2 合并残留审计(必做)
双方都改过的文件(尤其 .gitignore)会被 git 自动并成并集,可能留下:
- 死规则:指向已删目录的忽略项
- 重复条目:同一规则出现两次
判据 = 拿 base / 两个 tip 三方对比(git show <ref>:<file> 逐项 grep -F),
不要用 tail -N 目测(会漏看被合并在中间的行),也不要把合并前就存在的历史重复算进来。
6.3 冲突时的解决位置
done 先做非破坏性预演:git merge-tree --write-tree v2-on-ui task/<ID>-<slug>。
rc=0(无冲突)→ 继续合并rc≠0→ 在任务分支上git merge v2-on-ui解决冲突并提交,再重跑done
在一次性分支上解冲突,永不污染主干。
7. 资源串行化
7.1 台架锁
以下物理资源各只有一份,故 @test / 板级套件 / 烧录必须串行:
- 串口
COM6@115200 - ST-LINK SN
0B250D12A218303030303032 - 设备 IP
192.168.1.100
机制:_wt/.state/locks/{build,bench}.lock 记录持有者(任务 ID + pid + 时间戳 + 备注 + 自报 TTL)。
锁不绑定进程存活:lock acquire 本身是短命进程,而真正用资源的烧录/构建在另一个进程里跑。
若按 pid 判活,acquire 一退出锁就被判为陈旧 → 锁形同虚设(实测踩到)。
故判「陈旧」只看时效(该锁的 TTL)加显式 --force;pid 仅作排障记录。
同任务重复 acquire 幂等;他人持锁时 acquire 拒绝(--wait 可轮询等待)。
TTL 按锁分设(2026-10-12):LOCK_TTL_HOURS = {"build": 1.0, "bench": 6.0}。
口径 = 兜底窗口应与操作真实时长同量级:编译是秒~分钟级(keil_build.py 自持自释,
窗口 ≈ UV4 运行时长),1h 已足够宽松;bench 是会话级(flash→@test→探针),才配得上 6h。
持有者还可用 lock acquire ... --ttl <小时> 自报更精确的窗口——
keil_build.py 即按其 --timeout 自报(timeout/3600 + 15min,下限 0.5h)。
done 前置检查:陈旧锁自动接管释放(不再硬失败),仅当锁仍被有效占用时才拒绝回收。
⚠ 台架相关的既有陷阱(
--require-selftest-pass的陈旧台账、回读校验会复位目标) 不因引入工作树而改变,仍按docs/测试说明/自动化测试流程.md执行。
7.2 构建
@build(UV4 增量)在新空间里是独占目录,但 UV4 不能两个实例同时 -b(2026-10-12 用户确认,
无需实测)——因此构建同样纳入串行锁(build),与台架锁并列。
结论:任务期真正可并行的只有编码与静态门禁(
ci_local.py的静态门禁 / 主机侧纯逻辑单测,纯软件、零硬件);@build与烧录/台架都必须排队。
7.3 并行道与写范围规则(规划层 ↔ 执行层映射)
规划层(当前任务卡在 docs/任务规划/UI补齐.md,后续事项在 docs/任务规划/登记簿.md;
五字段 = DoD / 硬依赖 / 资源 / 写范围 / 状态)
按资源属性把任务切进三条道,本方案(工作树 + 锁)是它的执行层:
| 道 | 资源 | 并行性 | 锁 / 机制 |
|---|---|---|---|
| 台架道 | COM6 / ST-LINK / 设备(§7.1) | 串行——约束资源,永不空转(约束优先,TOC):代码道在跑时必须为下一个台架任务备料 | bench.lock |
| 代码道 | CPU / 源码 | 并行——worktree 独立空间;前提 = 写范围互不相交 + 强门禁(31 钩子 ci_local,同 Linux 内核子系统树并行而能合回的逻辑,见 docs.kernel.org/process/2.Process.html 的 subsystem tree 模型) |
无锁;分支 ≤2 天、绿即合(trunk-based 短分支) |
| 外部窗口 | 标准源 / 第三方主站等外部设备 | Fixed Date 类(Kanban 服务分类):窗口一到全道让路;窗口前 2 日特性冻结(只修稳定,Zephyr RC 语义 → docs.zephyrproject.org/latest/project/release_process.html) |
bench.lock + 外部预约 |
四条规则:
- WIP ≤ 2:每道最多 1 个进行中(典型 = 台架道 1 + 代码道 1),先完再开——登记簿需可见
⬜ 未开始 / 🟦 进行中两态,销项仍删行。 - 写范围相交 = 声明合并次序:任务卡必须写写范围(目录级);相交不排队等待,而是声明谁先合 (后合者在主工作树 rebase 后继续),claim 时定。
- 分支存活 ≤2 天、绿即合:合并走
done预演(§6.3);单写者文档(§6.1)仍由主工作树统一处理。 - 里程碑不拖延、功能顺延下一轮(时间驱动):里程碑的退出判据写在登记簿,判据不满足则该功能 移入下一波次,里程碑本身按判据改期而非无限等待。
登记簿是规划层唯一权威(§2 铁律不变);本节只定义执行层映射,不在本文排任务顺序。
8. SKILL 设计
名称:task-worktree 短命令:@task(沿用本仓库 @build / @test 的 @ 惯例)
执行体:.dsh/skills/task-worktree/scripts/task_worktree.py(纯标准库,
照 keil_build.py 的写法,整目录可拷到别的项目复用)。已落地并端到端实测(见 §11)。
| 子命令 | 作用 |
|---|---|
new <ID> <slug> [--base B] [--lean] [--needs-bench] |
建分支 task/<ID>-<slug> + _wt/<ID>-<slug>/ 工作树 + 共享联接 + 台账 |
list |
所有任务一览:分支 / 落后领先 / 脏文件数 / 台架状态 + 两个锁的归属 |
status <ID> |
单任务详情:未提交清单、相对基线漂移、台架记录、已建联接 |
sync <ID> |
rebase 追平主干,把冲突控制在任务分支上 |
lock acquire|release|status <build|bench> |
独占资源锁(--task / --wait / --force / --bench-pass) |
done <ID> [--dry-run] [--skip-build] [--skip-gates] |
预演 → 合入主干 → 残留审计 → 重生成产物 → 门禁 → 清场 |
abort <ID> [--force] |
放弃任务(有未提交/未合并内容时不加 --force 会拒绝) |
环境变量:DTU_WT_ROOT(任务容器位置)· DTU_TASK_BASE(默认基线)·
DTU_INTEGRATION_WORKTREE(集成点,默认主工作树;用于隔离演练/测试)。
关键约束:
done只能在主工作树执行——主干v2-on-ui检出在主工作树,git 不允许同一分支在两处检出。done前置检查主工作树干净,脏则中止并提示(避免把未提交工作卷进合并)。done不含push:结束于本地合入 + 门禁全绿,打印提示等用户点头。--needs-bench任务若没有台架 PASS 戳,done拒绝合并。- 门禁未全绿时
done保留工作树与分支(只留下合并提交),便于修正或回退——不静默清场。 - 清场顺序:先摘联接,再
git worktree remove(见 3.3)。
9. 门禁与验收
| 层级 | 内容 | 时机 |
|---|---|---|
| 任务期 | scripts/ci_local.py(静态门禁 + 主机侧纯逻辑单测,可并行) |
每个任务各自跑 |
| 任务期 | @build(0 错 0 警,持 build 锁串行) |
每个任务各自跑 |
| 新空间首次 | @build 一次,生成 .map |
见下表下注释:不先构建,体积类门禁必然红 |
| 任务期(可选) | @test / 板级套件(持 bench 锁) |
需真机验证的任务 |
| 回收期 | 重生成产物 → ci_local.py → @build |
done 内强制(--skip-gates 可跳过) |
| 人工 | push 许可 | 回收后 |
⚠ 新开工作树后先
@build一次再谈门禁(实测 2026-10-25):.map是每棵树各自的构建产物、 不入版本库,所以依赖它的事实源的门禁——mem-numbers、mem-budget、以及ci_local.py的 体积门禁——在从未构建过的新空间里必然红。这不是代码缺陷,脚本此时也会把这句原因打出来 (check_mem_numbers.py/ram_baseline.py的提示文案)。顺带一条同源提醒:done的门禁 排在@build之前(见下文陷阱条目),同一族误报在回收期也会重现一次。
done 的完成判据:主干上 git merge --no-ff 成功 + ci_local.py 全绿 + @build 0 错 0 警
- 残留审计无死规则/重复条目 + 工作树已移除。
10. 已知风险与待验证项
| 项 | 状态 | 处置 |
|---|---|---|
| UV4 能否并发构建 | 已确认不可(2026-10-12) | build 与 bench 一并纳入串行锁 |
| 跳过 Git LFS smudge 是否影响门禁 | 已实测(2026-10-12) | 见下表:只省磁盘、不省时间,两种方式工作区都干净 → 默认全量,--lean 备选 |
| 主工作树当前不干净 | 已知 | done 的前置检查会拦下 |
遗留 worktree(_v1pin-build / _v2oui-build) |
已知 | 落地时 git worktree remove 清理,避免新旧两套并存 |
| 「主工作树绿、干净工作树红」的隐藏依赖 | 已实证(2026-10-12) | 见 10.2。属原任务漏提交,非本 skill 缺陷 |
git worktree remove 中途被中断 → 残留「裸目录」 |
已实证(2026-10-12) | 见下方 10.3 |
10.1 LFS 实测数据(2026-10-12,2396 个受控文件 / 707 个 LFS *.jpg)
| 方式 | 建树耗时 | 检出体积 | git status |
参考图 |
|---|---|---|---|---|
| 全量 smudge(默认) | 7.25 s | 83 MB | 干净 | 真实 JPEG,可看 |
--lean(GIT_LFS_SKIP_SMUDGE=1) |
7.05 s | 45 MB | 干净 | 指针文本 |
结论:LFS 对象已在本地,smudge 几乎不耗时,跳过只省约 38 MB 磁盘。
故默认走全量(保留参考图可读性),磁盘紧张时才用 --lean。
另外两种方式在全新工作树里跑 pre-commit(含 doc-links)都正常通过,
证明 hooks 经 common dir 共享、门禁不因工作树而失效。
10.2 工作树是「提交物自洽性」的照妖镜(2026-10-12 实证)
演练中 done 的门禁在干净工作树里报出 2 处失效引用,而主工作树里同一门禁是绿的:
docs\内存管理\RAM使用分析与优化方案.md:277 [路径引用] scripts/ram_baseline.tsv
docs\内存管理\RAM使用分析与优化方案.md:585 [路径引用] docs/内存管理/RAM预算台账.md
根因:该文档已提交且无改动,但它引用的这两个文件从未 git add(属另一在途任务),
只在作者的主工作树磁盘上存在 → doc-links 查的是文件系统,于是在主工作树里通过;
一旦换到干净工作树(本演练的 _probe-base2),文件不存在 → 必然 FAIL。
判据与处置:这不是本 skill 的缺陷,而是被工作树逼出来的真问题——
「一个提交是否自洽」不能靠「在我的工作目录里能过」来证明。修复归属引用方任务
(补 git add,或给引用加 [计划] 前缀 / 写明「已删除」状态词,见 docs/文档规范.md)。
副作用提醒:这同时说明共享主工作树里的绿门禁不可尽信——只要有未提交文件躺在磁盘上,
门禁就可能对着「未提交的现状」放行。多任务并行期更该用工作树 + done 复核。
10.3 git worktree remove 中断会留下「裸目录」(2026-10-12 实证)
清理遗留 worktree 时踩到:git worktree remove --force 被中断后,_v2oui-build 的 .git 已被删除
(于是 git worktree list 显示 prunable),但目录里还躺着 2774 个条目没删完。
判据:看到 prunable + 目录仍存在 = git 已删掉 gitfile、删除未完成。此时那棵树已不再是 worktree
(git 不认它了),git worktree remove 无法再用,只能按普通目录删除。
处置:git worktree prune 清台账 → 手工删目录。删之前必须重新扫一遍 reparse——
理由它有 .git 时没有联接,不等于现在没有(尤其别让他人趁乱建了联接)。
清除手段用 Python 自底向上删并顺带 chmod +w(MDK 产物有 2 个只读文件),
切忌 rm -rf(MSYS 会递归进联接,实测见 3.3)。
已知不做:不引入第二套并行机制(如 git submodule / 多克隆), 保持「同一能力只有一个实现」的收敛原则。
11. 落地清单与实测记录
| 动作 | 目标 | 状态 |
|---|---|---|
| 新建 | .dsh/skills/task-worktree/SKILL.md(含 name / description / whenToUse frontmatter) |
✅ |
| 新建 | .dsh/skills/task-worktree/scripts/task_worktree.py(纯标准库) |
✅ |
| 新建 | .dsh/skills/task-worktree/.gitignore |
✅ |
| 修改 | AGENTS.md「按主题查哪份文档」加一行指向本文档 |
✅ |
| 修改 | docs/README.md §9.2 登记本文档 |
✅ |
| 清理 | 移除遗留 worktree _v1pin-build / _v2oui-build |
✅ 2026-10-12 已清理(脏改动先留档,见 §2) |
| 验证 | §10 的两个待验证项 | ✅ 见 §10 / §10.1 |
11.1 端到端实测记录(2026-10-12,隔离集成点演练)
用 DTU_INTEGRATION_WORKTREE 把集成点指到一次性工作树、基线用一次性分支,
全程未触碰真实主线 v2-on-ui:
| 场景 | 结果 |
|---|---|
new 建树 + 双联接(.venv / simulator/SDL2) |
✅ 透过联接跑解释器得 Python 3.12.4 |
新工作树里跑 pre-commit(共享 hooks) |
✅ doc-links 等通过;gitlint 也正常拦下非法邮箱 |
list / status |
✅ 落后领先、脏文件数、联接清单、锁归属均正确 |
lock acquire / 冲突拒绝 / 幂等 / 错误持有者拒绝 release |
✅ 期望的 rc 全部命中 |
done --dry-run 无冲突 |
✅ 输出预演结果树,零改动 |
done --dry-run 有冲突 |
✅ 拒绝合并(rc=1),提示到任务分支上解冲突 |
done 完整回收(合并 + 审计 + 重生成 + 清场) |
✅ 合并提交生成合规(标题 40 字符 ≤100、正文 ≤80) |
| 清场安全性 | ✅ 联接摘除前后 .venv 文件数 2809 → 2809,解释器完好 |
abort 未加 --force(有未合并提交) |
✅ 拒绝(rc=1)并列出将丢弃的内容 |
abort --force |
✅ 联接摘除、工作树移除、分支删除、.venv 2809 → 2809 |
实测暴露并已修复的 4 个缺陷:
- 锁绑定进程存活 → 形同虚设:
lock acquire是短命进程,一退出锁就被判陈旧、下次必被接管。 改为时效判定(当时全局 6h;2026-10-12 起改为按锁分设 TTL,见 §7.1)+ 显式--force,pid 仅作记录。 git branch -d按环境 HEAD 判「已合并」:集成点被改走时误判、拒绝删除。 改为显式merge-base --is-ancestor <branch> <base>后再删。- 直调 Win32 建联接被拒(Win32 4392):改走
mklink /J(详见 3.3)。 done门禁跑错工作树(最隐蔽的一个):run_script原用REPO_ROOT / script拼脚本路径, 而REPO_ROOT由task_worktree.py自身位置反推 = 主工作树。于是done本意是在集成 工作树里校验,实际却跑了主工作树里的同名脚本;该脚本再按它自己的__file__反推,又去校验 主工作树——校验对象整体错位。 证据(回归前/后对照):直接在工作树里跑ci_local.py报「扫描 .c: 68 / PASS」,经done调用 却报「扫描 .c: 71 / FAIL」并附[GAP] App/protocol/iec104/*.c | 缺 uvprojx(那 3 个文件只以 未跟踪状态存在于主工作树)。修法 = 从传入的cwd(= 集成工作树)解析脚本路径 (scripts/ci_local.pyL46 /scripts/check_registration.pyL52 的__file__反推是既定约定, 脚本必须由目标树里的那一份执行)。修复后同一条命令的双端注册反向校验 (uvprojx + CMakeLists)由 FAIL 转 Passed。
缺陷 4 的教训具有普适性:任何「按
__file__反推仓库根」的项目脚本,都必须由目标工作树里 的那一份来执行,否则门禁会静默地校验另一棵树。本 skill 的run_script已统一按cwd解析。
12. 关联文档
| 主题 | 去处 |
|---|---|
| 提交门禁 51 个钩子 | docs/代码规范/pre-commit使用说明.md |
| 自动化测试与台架流程 | docs/测试说明/自动化测试流程.md |
| 文档写作规范(编码/链接/登记) | docs/文档规范.md |
| 工具路径环境变量(换机) | docs/环境与部署/本地工具路径环境变量方案.md |
| 待办与排序登记簿 | docs/任务规划/README.md |