Files
STM32F4-Base/.dsh/skills/task-worktree/SKILL.md
T
wandering1 4a4ad6bc5f chore(全仓): @build/@test 旧文档别名清退为技能调用名
现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名,
不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体
run_boot_test.py 的真实调用。

- AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改
  「编译(/keil-build)」「串口回归(/serial-boot-test)」
- keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree
  方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围)
- 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入
  等 41 行历史证据保留旧名
- gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑
- 顺手补并行方案同行漏网计数 51→52 钩子

验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all
rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52
钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
2026-10-30 13:03:12 +08:00

23 KiB
Raw Blame History

name, description, whenToUse
name description whenToUse
task-worktree 并行开发的空间管理(git worktree 生命周期;`@task` 只是文档别名不是可执行命令,DSH 会话显式调用 `/task-worktree`):开任务树与分支、联接共享 `.venv` 与模拟器 SDK、build/bench 独占锁、并发上限与腾位(A)、放不掉的锁(B)、`done` 是合并点不是查看器与补绿链(C)、主树被别的会话弄脏时的手工合并(C2)、门禁指标异常的净室归因(D)、回收非登记树与悬挂台账(E)、与 DSH worktree 插件共存及联接分级(F)、树内跑门禁与消息禁词(G)、树内自查与合并后补绿命令化(`precheck` / `postmerge`)。规格 = docs/环境与部署/并行任务工作树方案.md;深规程全文在 reference/。 要为新任务开隔离空间、同时推进多个任务、把任务合回主干或回收工作树;或撞到 `new` 报「已有 8 个活跃任务(上限 8)」、`lock release` 报「任务 X 不存在(缺台账)」、`done` 报「门禁未全绿」或「主工作树不干净」而那份脏改动是别的会话/插件留的、两个任务抢 COM6 或 UV4、并集合并残留;或在任务树里跑门禁写输出报 ENOENT、`--all` 的问题全在 `simulator/SDL2` 联接下;或门禁指标异常增长查不出哪个文件造成;或要回收 `list` 台账看不到的树、清 branch/path 都已不存在的悬挂台账;或要确认插件树里 `.venv` 与 `simulator/SDL2` 哪个必须补;或要改并行上限这个数字并同步全仓口径。

task-worktree — 多任务并行开发空间(@task 仅为文档别名;DSH 显式调用 /task-worktree;可执行形态见「固定命令」)

  • 执行体 = .dsh/skills/task-worktree/scripts/task_worktree.py(纯标准库;拷整个 skill 目录即可换项目用)。
  • 深规程全文在 reference/ 下:做 C2 / D / E / F / G 任何一条之前先把对应那篇读完——本文只留判得出来「要不要读」的判据。
  • 为什么这么拆:本文件受 dsh-mnemon 写侧上限 65536 字节(skill-size 钩子 49152 告警;实测 72,601 字节的技能照常可读可提交,只是自进化每次「生成草案」被拒、永久卡在「生成中」)→ 新增深规程进 reference/,本文只加判据。瘦身前 frontmatter 原文 → frontmatter-history.md。

症状 → 去哪一节

现场 / 目标 去
开新树 / 看全景 / 追平主干 / 放弃任务 固定命令、典型流程
new 报「已有 8 个活跃任务(上限 8)」 A
lock release 报「任务 X 不存在(缺台账)」,锁照旧挂着 B
done 做了什么、红了怎么补绿、能不能反悔、怎么算「真完成」 C
done 报「主工作树不干净」,而那份脏是别的会话/插件留的 C2 → merge-dirty-main-tree.md
门禁指标异常增长要逐文件归因(主树全程只读) D → clean-room-measure.md
list 台账里没有的树要回收 / detached / 有在飞内容 / 悬挂台账 E → reclaim-unregistered.md
插件树缺联接、锁归属 pw:<name>、「有插件为何还自持生命周期」 F → dsh-plugin-bridge.md
树内写门禁输出 ENOENT、--all 假红、写提交/合并信息、字节体检 G → gates-in-tree.md
收工前树内自查、或合并后体积凭证/台账红了想一条命令补绿 precheck / postmerge(固定命令表 + C 节第 5~6 步)
退出码读到 0 其实是 2、tail 之后误判「有冲突」 读输出
谁不许跑 done/abort、清场前必做什么、任务期哪些文件不许改 铁律 1–9
收工前最后一遍自查(各节该留什么证据)/全部实测坑 verification.md / pitfalls.md

固定命令

PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktree.py"
"$PY" "$TW" <子命令> [参数]    # PowerShell:& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" <子命令>(cwd = 仓库根或任意子目录)
命令 作用
new <ID> <slug> 建分支 task/<ID>-<slug> + 工作树 + 共享联接 + 台账,打印新空间路径
list 分支 / 落后领先 / 脏文件数 / 台架状态 + 两把锁归属。只遍历台账 → 非登记树看不见(E 节)
status <ID> 未提交清单、相对基线漂移、台架记录、已建联接
sync <ID> 在任务树里 rebase 追平主干,把冲突留在任务分支上
`lock acquire release
`plugin list adopt
done <ID> 预演 → 合入主干 → 并集残留审计 → 重生成产物 → 门禁 → 清场
abort <ID> 工作树 + 分支一起删,永久丢弃
precheck 树内自查(只读):BOM 漂移 / CR / U+FFFD + docs 逐档门禁 + --msgfile --ban 消息检查;顺手补 ci-artifacts/(G 节机械项)
postmerge 主树合并后补绿:体积凭证自动诊断(新鲜跳过;红→先短链 --update,只读复检真漂了才升全链)+ check_doc_links --all + 列待提交文件,不代提交
  • 退出码 0 成功 / 1 操作失败 / 2 前置或环境错误 / 3 用法——只有不接管道才读得到(→ 读输出)。
  • new:--base(默认 v2-on-ui)· --needs-bench(无台架 PASS 则 done 拒合)· --lean(跳 LFS smudge,只省磁盘不省时间)· --no-bootstrap(不建联接)· --force(只跳过并发上限检查,不解决谁让位 → A)
  • done:--dry-run · --skip-build · --skip-gates · --force。⚠ --dry-run 只保护预演段、不保护清场段(实测:对已合入的分支它照样摘联接、删分支、清台账;且它的前置也要抢 build 锁)→ 用完复验 git worktree list,细节 → 陷阱清单。
  • lock:--task(acquire 必填)· --wait <秒> · --ttl <小时>(默认 build=1h / bench=6h,超期视为陈旧、下次 acquire 自动接管)· --note · --bench-pass · --force。⚠ --note 只在 acquire 写进 payload,release 时被忽略 → 验收证据写主干提交说明或文档。
  • 环境变量:DTU_WT_ROOT(默认仓库同级 _wt/)· DTU_TASK_BASE(默认基线)· DTU_INTEGRATION_WORKTREE(集成点,用于隔离演练)。

典型流程

"$PY" "$TW" new N21 dhara-isfree --needs-bench   # 把新会话工作目录切到打印出来的 _wt/N21-dhara-isfree/
"$PY" "$TW" lock acquire bench --task N21        # 烧录 / /serial-boot-test / 板级套件
"$PY" "$TW" lock release bench --task N21 --bench-pass
"$PY" "$TW" done N21                             # 回主工作树(铁律 1);先 --dry-run 读全量

读输出:管道与截断(两起误判的共同根因)

  1. | tail -N 之后的退出码是 tail 的,恒 0 → 脚本的 1/2 全被吞(实测三条「rc=0」真码都是 2)。判据先看文本([ok] / !! / 前置/环境错误: / 失败:),要真码就不接管道或取 ${PIPESTATUS[0]}。
  2. done 的输出别 tail -2:管道下 python stdout 块缓冲、git 子进程 stderr 不缓冲,两股流错位 → 只剩尾部那句「--dry-run:到此为止…」加带 conflict 字样的 stderr 片段,据此误判「有冲突」。dry-run 全文 5~8 行,读全量。

分步操作(节号 A–G/C2、各节步骤号与铁律编号被 reference/ 与 docs 引用,不许重排)

A. new 撞并发上限 → 腾位

  1. 认原文 前置/环境错误:已有 8 个活跃任务(上限 8);先 done/abort,或加 --force(exit 2)。「活跃」= _wt/.state/<ID>.json 在且台账 path 目录还在 → 手工删目录不占名额、但留悬挂台账(E 第 6 步)。
  2. $TW list 定谁让位:已完成的先 done(合并回收一步不省)。
  3. 无人认领、零提交、未 claim 的空闲树 → abort <ID>;有未提交或有提交时脚本会拒,确认可弃才 --force。
  4. 真要第 9 棵才 --force:并行度上去,冲突面与台架排队一起上去(每棵磁盘代价见铁律 6)。
  5. 别用手工 git worktree remove 给登记任务腾位:台账不删(同 ID 报「任务已存在」)、又绕过「先摘联接」(铁律 5),有让 git 递归进联接删掉主树 .venv 的风险。要回收的是非登记树(list 里没有它)→ E 节。
  6. 复验:list 条目数 == ls _wt 目录数,再 new。

B. 非登记持有者的锁放不掉

症状:lock release bench --task acceptwin --bench-pass(带 --force 同)报 前置/环境错误:任务 acceptwin 不存在(缺台账 _wt/.state/acceptwin.json),锁照旧挂着。根因:release 带 --bench-pass 就先 load_state(task),而 acquire 侧不查台账 → 「释放」被「记账」挡死;锁没放,done 的 release_stale_locks() 对未到 TTL 的活锁判失败 → 一个没登记的名字挡住所有任务收口。

2026-10-29 已修:无台账降级为 [warn] …跳过 PASS 记录,仅释放锁,不再需要手写占位台账。

  1. $TW lock status 认现场(持有者 / pid / 已持有 / TTL)——别对同一句报错反复重试。
  2. $TW lock release <build|bench> --task <NAME> [--bench-pass] 直接放;PASS 记不上会 warn,锁照放。
  3. PASS 后面还要用(done 只认台账 bench_pass)→ 从一开始就用已 new 的 ID 占锁,或把结论写进主干提交/文档。插件树 pw:<name> 天然无台账 → F 节。
  4. 最后手段:锁 = _wt/.state/locks/<name>.lock 的单行 JSON(name/task/pid/host/epoch/at/note/ttl),release 就是 unlink 它——手工删 = 不记 PASS 的强制释放,前面各步都不可用才做。早期为绕坑手写的占位台账现在有害(既占上限名额又被 list 当任务)→ E 第 6 步备份后删。

C. done <ID>:它是合并点,不是查看器

  1. 合并前确认属主提交完:$TW status <ID> 看未提交与领先数,跨会话再问属主一句。实测教训:属主还剩「2/2」没提交就 done → 只含 1/2 的分支进了主干,只能另开一棵树补后半(自己的时序错,不是分支问题)。
  2. 先 --dry-run 且读全量。判据两行:[ok] 前置检查通过(领先 <base> 共 N 个提交) + [ok] 无冲突,预演结果树 <sha>;N 必须等于属主应交的提交数,对不上回第 1 步。
  3. 正式 $TW done <ID>——必须在主工作树(铁律 1)。
  4. 见 !! 门禁未全绿:工作树与分支**保留**,未清场 时,紧跟的 主干已含合并提交 <sha> 说明合并已落主干;先定位哪条钩子红([SUMMARY] static: pre-commit hooks -- passed=N failed=M),最常态是 .map 滞后 → 第 5 步。
    • ✅ 走提示 a):主工作树修正后追加提交。
    • ❌ 绝不用 b) 的 reset --hard <sha>——那是丢弃已入库的合并提交,且 reset --hard 是项目级 Never(连未提交工作一起丢)。提示 b 是列出的可能性,不是建议。
  5. 补绿固定链(顺序即因果,全在主工作树,逐条不接管道看退出码):
"$PY" scripts/check_mem_numbers.py    --write            # 台账受管区 ↔ 新 .map 对齐
"$PY" scripts/ram_baseline.py         --write            # RAM 快照 / 92% 预算台账
"$PY" scripts/ram_baseline.py         --update           # 趋势基线追加一行
"$PY" scripts/check_test_coverage.py  --update-baseline  # 覆盖率基线(新单测 / 新套件时必刷)
"$PY" scripts/check_doc_links.py      --all              # 文档链接与章节引用
git add <上面刷出来的基线/快照文件>    # 显式列文件,别 -A 把别人在飞的改动一起提交
git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.md 边界)

为什么必然要补 → 陷阱「done 的门禁跑在 /keil-build 之前」。合并解过冲突时,合并提交期 pre-commit 只查冲突文件(→ G 节第 3 条)→ 落主干后必须另跑全量。 注释/文档-only 但碰了 .c/.h 的短链:先比 Program Size 与合并前是否逐项相同——相同就只 ram_baseline.py --update 重写体积凭证(scripts/size_attestation.json 跟着补提交笔走),别 --write 动受管区(等于给在飞分支造冲突面);数字变了才走上面全链。已命令化:"$PY" "$TW" postmerge——新鲜直接跳过;红则先短链,再用只读 check_mem_numbers 复检裁定是否升全链;末尾跑 --all 并列出待提交文件(不代提交)。上面的手工全链仍是权威兜底。 6. 重跑 done 前主树必须干净,否则 失败:主工作树不干净(…)——合并会把未提交改动卷进来 → 第 5 步产物先提交。分支已并时重跑打印 [i] task/<ID>-<slug> 已合入 <base>,跳过合并,直接清场——该路径在门禁之前就返回,不会替你复绿:上一次门禁没跑到的尾巴(主树 ci_local.py + check_doc_links.py --all;体积/文档那半可 postmerge 一把跑)必须自己补。主树脏且那份脏不是你的 → C2。 7. 清场后再敲一次 done <ID> 得 前置/环境错误:任务 <ID> 不存在(缺台账 …json)(exit 2)——不是失败:清场最后一步 unlink 了台账。判完成以 git log 为准。

C2. done 被「主工作树不干净」挡住,而那份脏是别的并发会话留的

  • 不许对它做 stash / checkout / reset / 提交 / 删除(项目级 Never,你不知它写没写完)。done 要求主树干净 → 合并与回收一起被挡,那是保护不是工具坏了。
  • 正解六步,编号与 reference/merge-dirty-main-tree.md 第 1~6 步一一对应(每步的确切命令与判据在那篇):取证(mtime + diff 定性有无交集,顺带 worktree list 看有无别人中途新建的树)→ 预演 git merge-tree --write-tree <base> <task>(打出结果树 sha 且 RC=0)→ 主树手工合并 git merge --no-ff --no-edit -F ci-artifacts/merge-msg-<slug>.txt <task> → 对账(合并后 blob id 与 stat 同合并前存值逐项一致)→ 备份台账 cp <ID>.json <ID>.json.state-bak 后让 done 自己摘联接拆树 → 复验 worktree list + lock status。
  • blob id 变了 = stash pop 半冲突:立刻停止收工,把两份 blob id 与 diff 交给该文件属主,绝不 checkout 抹平。
  • 报告要点名:绕开 done 的理由 + 补回它的证据(预演 sha 与 RC、blob 对账、主树全库 check_doc_links.py --all、两把锁空闲)+ 那份外部脏文件与中途冒出来的第二棵树(谁的、你没动它的证据)。合并信息也过 G 的禁词自检;「这份脏通常是谁」见陷阱清单。

D. 门禁指标异常增长 → 净室逐文件归因(主树全程只读)

三条判据:仓外 detached 树当净室、一次只注入一份在飞文件、与 HEAD 基线逐文件差分;主树前后 git status --porcelain 逐行一致才算「全程只读」。全文 → reference/clean-room-measure.md。

E. 回收非登记工作树(list 为空 / detached / 树里有在飞或未跟踪内容)

三条不可跳过:① 逐文件内容三桶归类(与主线工作区/HEAD 比对;「很久没动」不是判据)② 动手前跑只读 reparse 预扫,拿到 REPARSE: none 才许 git worktree remove(铁律 5)③ 唯一未合并残留先备份再 --force,拆完复验 + 清悬挂台账。绝不跑 done/abort(铁律 9)。全文(第 1~6 步 + 两份探针脚本)→ reference/reclaim-unregistered.md。

F. 与 DSH worktree 插件(/worktree、worktree_create)共存

联接 级别 依据 缺了会怎样 逃生门
simulator/SDL2 硬需求 simulator/CMakeLists.txt:12 的 set(SDL2_DIR …) 非 CACHE,include / lib/x64 / SDL2.dll 全从它派生 build_sim.bat 当场失败 无
.venv 软需求 scripts/ci_local.py 有 DTU_PRECOMMIT_PYTHON + 五级解释器候选 静默落机器级解释器(缺 fontTools → generated-fresh 假红) 环境变量
  • plugin adopt 两条都补只是因为便宜,不代表两条都硬。插件树占锁记 pw:<name>、回收仍归 /worktree(本 skill 不删插件树,插件也不删 _wt/)。「有插件为何还自持生命周期」=先读插件包(npm root -g)再 grep 三个概念名报命中数,插件侧零命中就是理由——取证姿势与答案边界 → reference/dsh-plugin-bridge.md F-2。

G. 任务树内跑门禁:落点、--all 假红、消息禁词

  • 新树没有 ci-artifacts/(被 ignore → 不检出):重定向/写文件直接 ENOENT 且整段程序零执行 → 落盘前先单独一条 mkdir -p ci-artifacts;树内输出一律树内相对路径(cd 进树后 ../_wt/x.txt 落到 _wt/_wt/)。一次性产物不进仓库根。
  • 文档门禁树内按单文件跑;全库 --all 留到合并后在主树跑——树内 simulator/SDL2 是同一份仓库的联接,--all 把它一起扫成假红(实测 RC=1 而问题全在联接下、要改的文档一条没有)。非要树内跑:① 主树同命令拿基线 ② 过滤联接下的问题行 ③ 剩下的逐行比。只做一步就是误判。
  • 合并提交期 pre-commit 只查冲突文件(日志首行 [INFO] Checking merge-conflict files only.,其余全 (no files to check) Skipped)→ 合并落主干后必须另跑全量 scripts/ci_local.py。
  • 跨层改动拆不成「按层分笔」:钩子看到的世界 = HEAD + 本笔 staged(未暂存被 stash;未跟踪文件不被 stash 但会拖红同一棵树上别人的提交)→ 登记文件 / 生成物 / 互锁引用的文档分到他笔,本笔必被整仓门禁拦(doc-links/registration-check/generated-fresh/mem-numbers)。判据:一笔提交必须自洽。
  • 跑门禁前字节体检:lines / bytes / BOM / CR / U+FFFD / 三连续空行 / 行尾空格 / 末行单个 LF + Markdown 表格每行列数一致性(单元格内竖线写成反斜杠竖线,否则被当分隔符断列)。坏行未必是本次改动,先定归属。doc-links/no-mojibake 只覆盖编码那半。机械项已命令化:"$TW" precheck(BOM 漂移 / CR / U+FFFD + docs 逐档门禁,只读);列数一致性与归属判定仍人做。
  • 提交/合并信息一律 -F 读文件(UTF-8 无 BOM、标题 ≤100、正文每行 ≤80,放 ci-artifacts/);清退类任务加禁词自检:git log -1 --pretty=%B | grep -c '<关键词>' 必须是 0。未推送前发现消息写错 → --amend -F 就地修,别追加「更正」提交(等于把禁词写回历史)。BOM/CRLF/行长/禁词一次查:"$TW" precheck --msgfile <文件> --ban <词1> --ban <词2>…。
  • 全文(第 1~7 条,含 stash 机制与四道整仓门禁现场)→ reference/gates-in-tree.md。

铁律

  1. done 只能在主工作树执行——主干分支检出在主树,git 不允许同一分支两处检出。
  2. done 不 push:它结束于本地合入 + 门禁全绿;推送需用户明确许可。
  3. build 与 bench 都必须串行(UV4 不能两个实例同时 -b;COM6、ST-LINK、设备 IP 192.168.1.100 各一份)。build 锁由 /keil-build 自持自释(keil_build.py)→ 调用方不要手工 acquire/release;bench 锁必须手工 acquire → 烧录 / /serial-boot-test → release --bench-pass。非登记持有者怎么放 → B 节;插件树 → F 节。
  4. 单写者文档:docs/任务规划/(整个文件夹)、AGENTS.md 任务表块、MDK-ARM/*.uvprojx、.pre-commit-config.yaml、内存快照 —— 任务期不在任务树里改,留到 done 统一处理;用户显式豁免时(在任务树写、由 done 合回)要在交付报告里点名这条例外。
  5. 清场必须先摘联接:脚本先 os.rmdir 掉 .venv / simulator/SDL2 联接再 git worktree remove。手工拆树前必跑 E 第 3 步预扫拿到 REPARSE: none——否则 git 可能递归进联接把主树 .venv 一起删掉。
  6. 活跃任务硬上限 8 = 脚本常量 MAX_CONCURRENT(task_worktree.py:30)——唯一事实源:报错文案与 new 的插件树提示都插值它,AGENTS.md / 方案文档 / frontmatter / A 节的数字全是引用同步。改这个数字 → 全形态扫描走 stale-doc-count-sweep skill + 隔离小仓验证上限真生效;每棵磁盘代价按实测口径重述、别照抄旧数(细节 → 验证清单末条)。满了 exit 2 → A;悬挂台账 → E 第 6 步。
  7. 工作树放仓库同级 _wt/,绝不在仓库内部(否则污染 git status、--all-files 与全库搜索)。唯一例外 = 插件写死的仓内 .dsh-worktrees/(改不了,已用预置 .gitignore + 三个全库扫描排除兜住 → F 节)。
  8. done 是合并点:属主没提交完就别跑;合并提交进了主干就只用「主树补提交」收尾,不用 reset --hard 反悔 → C 节。
  9. 非登记 / detached 的树绝不跑 done / abort:两者都以台账为前置(无台账 exit 2),带残留台账时 abort --force 会绕过内容保全把别人在飞的改动连树一起删 → E 节。

陷阱与验证(收工前各扫一遍)

  • reference/pitfalls.md:全部实测坑逐条(含 --dry-run 不保护清场段、门禁跑在 /keil-build 之前、未跟踪文件在 git log 与合并里都不留痕、并集合并残留、新树从未构建 → 体积类门禁必然红、注释-only 也刷红体积凭证、「已合入」快路径在门禁前返回不替你复绿、--force 只覆盖四类检查、联接不是 symlink(core.symlinks=false)、锁不绑进程存活、文件工具先读后改按绝对路径记账、中文门禁回显乱码但 rc 可信)。收工前扫一眼。
  • reference/verification.md:每节要留的证据(预演 sha + MT_RC=0、blob id 对账、两把锁空闲、禁词计数 0、REPARSE: none、改上限数字后的全形态扫描)。

与其它 skill 的衔接

  • 构建 = keil-build· 烧录/回读 = stlink-flash · 串口闭环 = serial-boot-test(真实 hex 路径以 .serialtest/config.json 的 hex 键为准——实测 MDK-ARM/Objects/STM32F407-Demo.hex 不存在,拷错 = 烧旧镜像)
  • 新 .c 双端注册 = new-source-file · RAM/.map 台账 = mem-budget · UI 回归 = ui-verify · 计数同步 = stale-doc-count-sweep · 卡死的技能台账行 = mnemon-skill-ledger-stuck-repair
  • DSH worktree 插件(/worktree / worktree_create,仓内 .dsh-worktrees/)与本 skill 的分工、桥接命令、锁归属 → F 节 · 完整机制与目录分配 → docs/环境与部署/并行任务工作树方案.md