diff --git a/.dsh/skills/task-worktree/SKILL.md b/.dsh/skills/task-worktree/SKILL.md index 096f345f..fb09ac21 100644 --- a/.dsh/skills/task-worktree/SKILL.md +++ b/.dsh/skills/task-worktree/SKILL.md @@ -1,7 +1,7 @@ --- name: task-worktree -description: 多任务并行开发的空间管理(git worktree 生命周期):new 开一棵工作树 + 任务分支、目录联接共享 .venv 与模拟器 SDK、活跃任务硬上限 3 与腾位、build/bench 两类独占锁(含非登记任务占锁后「缺台账」释放不掉的正解)、done 预演合并回主干、门禁未全绿的补基线收尾、并集合并残留审计、**非登记工作树(detached HEAD / 无台账 / 树内有在飞或未跟踪内容)的安全回收**(逐文件内容分桶 + 联接/reparse 预扫 + 唯一残留先备份再 --force + 悬挂台账清理复验),以及**把仓外 detached worktree 当净室测量仪**——门禁指标异常增长时一次只注入一份在飞文件、与基线差分做逐文件归因;**以及与 DSH worktree 插件(`/worktree`、模型工具 `worktree_create`,树在仓内 .dsh-worktrees/)的桥接**——`plugin adopt` 补共享联接、锁归属 `pw:`、回收仍归 /worktree。规格见 docs/环境与部署/并行任务工作树方案.md。 -whenToUse: 要同时推进多个任务、担心同分支同文档互相冲突;要为新任务开一个隔离空间;要把任务合并回主干;或遇到 `new` 报「已有 3 个活跃任务(上限 3)」、`lock release` 报「任务 X 不存在(缺台账)」、`done` 报「门禁未全绿:工作树与分支保留,未清场」、两个任务抢 COM6 / UV4、"改了文档被覆盖"、并集合并残留时;或**门禁指标异常增长(如 id-check 的不可判定数从 1,162 涨到 1,178、+16)却查不出是哪个文件 / 哪条任务道造成的**、主工作树压着多条在飞改动而不许 stash / 提交 / 改门禁脚本、要按 HEAD 基线只读出像验收时;或**要回收 `@task list` 里根本看不到的工作树**(`git worktree list` 有它、台账没有 / detached HEAD / 树里疑似留着别的会话的在飞或未跟踪内容)、或**要清台账里 branch / path 都已不存在的悬挂条目**(`@task list` 输出发怪、`new` 报「任务已存在」或白占 3 个名额之一);或**这棵隔离空间是 DSH worktree 插件开的**(会话选了「Worktree模式」/ `/worktree create`,树在仓库内 `.dsh-worktrees/`)——进去发现 `.venv` 不在、`@build` 与门禁全部起跑失败,或想看清谁在占 UV4 / 台架 → F 节。 +description: 多任务并行开发的空间管理(git worktree 生命周期):new 开一棵工作树 + 任务分支、目录联接共享 .venv 与模拟器 SDK、活跃任务硬上限 8 与腾位、build/bench 两类独占锁(含非登记任务占锁后「缺台账」释放不掉的正解)、done 预演合并回主干、门禁未全绿的补基线收尾、并集合并残留审计、**非登记工作树(detached HEAD / 无台账 / 树内有在飞或未跟踪内容)的安全回收**(逐文件内容分桶 + 联接/reparse 预扫 + 唯一残留先备份再 --force + 悬挂台账清理复验),以及**把仓外 detached worktree 当净室测量仪**——门禁指标异常增长时一次只注入一份在飞文件、与基线差分做逐文件归因;**以及与 DSH worktree 插件(`/worktree`、模型工具 `worktree_create`,树在仓内 .dsh-worktrees/)的桥接**——`plugin adopt` 补共享联接、锁归属 `pw:`、回收仍归 /worktree。规格见 docs/环境与部署/并行任务工作树方案.md。 +whenToUse: 要同时推进多个任务、担心同分支同文档互相冲突;要为新任务开一个隔离空间;要把任务合并回主干;或遇到 `new` 报「已有 8 个活跃任务(上限 8)」、`lock release` 报「任务 X 不存在(缺台账)」、`done` 报「门禁未全绿:工作树与分支保留,未清场」、两个任务抢 COM6 / UV4、"改了文档被覆盖"、并集合并残留时;或**门禁指标异常增长(如 id-check 的不可判定数从 1,162 涨到 1,178、+16)却查不出是哪个文件 / 哪条任务道造成的**、主工作树压着多条在飞改动而不许 stash / 提交 / 改门禁脚本、要按 HEAD 基线只读出像验收时;或**要回收 `@task list` 里根本看不到的工作树**(`git worktree list` 有它、台账没有 / detached HEAD / 树里疑似留着别的会话的在飞或未跟踪内容)、或**要清台账里 branch / path 都已不存在的悬挂条目**(`@task list` 输出发怪、`new` 报「任务已存在」或白占 8 个名额之一);或**这棵隔离空间是 DSH worktree 插件开的**(会话选了「Worktree模式」/ `/worktree create`,树在仓库内 `.dsh-worktrees/`)——进去发现 `.venv` 不在、`@build` 与门禁全部起跑失败,或想看清谁在占 UV4 / 台架 → F 节。 --- # task-worktree — 多任务并行开发空间(短命令 `@task`) @@ -39,7 +39,7 @@ PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktr `new` 常用开关:`--base <分支>`(默认 `v2-on-ui`)· `--needs-bench`(无台架 PASS 则 `done` 拒合)· `--lean`(跳过 Git LFS smudge)· `--no-bootstrap`(不建联接)· -`--force`(**仅跳过并发上限 3 的检查**,不解决「谁该让位」,见 A 节)。 +`--force`(**仅跳过并发上限 8 的检查**,不解决「谁该让位」,见 A 节)。 `done` 常用开关:`--dry-run`(只预演,零改动)· `--skip-build` · `--skip-gates` · `--force`(清场时强制移除工作树)。 @@ -78,15 +78,15 @@ PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktr ## 分步操作(六条运行时机制) -### A. `new` 被拒「已有 3 个活跃任务(上限 3)」——怎么腾位 +### A. `new` 被拒「已有 8 个活跃任务(上限 8)」——怎么腾位 -1. 读原文:`前置/环境错误:已有 3 个活跃任务(上限 3);先 done/abort,或加 --force`(exit 2)。 - 上限是脚本常量 `MAX_CONCURRENT = 3`;**「活跃」的判据 = `_wt/.state/.json` 台账存在,且台账里 +1. 读原文:`前置/环境错误:已有 8 个活跃任务(上限 8);先 done/abort,或加 --force`(exit 2)。 + 上限是脚本常量 `MAX_CONCURRENT = 8`;**「活跃」的判据 = `_wt/.state/.json` 台账存在,且台账里 `path` 指向的目录还在**(手工删了工作树目录就不占名额,但会留下悬挂台账,见第 5 步与 **E 第 6 步**)。 2. `$TW list` 看全景(分支 / 落后领先 / 脏文件数)决定谁让位:**已完成的先 `done`**(合并回收,一步不省)。 3. 没人认领、零提交、尚未 claim 的空闲树 → `abort ` 腾位(实测就是 abort 掉排在后面的空闲树换出名额)。 `abort` = 工作树 + 分支一起删、**永久丢弃**;工作树有未提交改动或已有提交时脚本会拒绝,确认可弃再加 `--force`。 -4. 确实要同时开第 4 棵才 `new --force`:磁盘代价约 **83 MB/棵**(`--lean` 约 45 MB), +4. 确实要同时开第 9 棵才 `new --force`:磁盘代价约 **83 MB/棵**(`--lean` 约 45 MB), 并行度上去了、**冲突面与台架排队也一起上去**——不是免费的。 5. **别用手工 `git worktree remove` 给「登记任务」腾位**:台账不会被删(下次 `new` 同 ID 会报「任务 已存在」), 还绕过了「先摘联接」(铁律 5),有让 git 递归进联接把主工作树 `.venv` 删掉的风险。要回收的若是**非登记树** @@ -120,7 +120,7 @@ PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktr 最后手段(脚本本身都走不通时):锁就是 `_wt/.state/locks/.lock` 里的单行 JSON (`name/task/pid/host/epoch/at/note/ttl`),`release` 的实现就是 unlink 它——手工删这个文件等价于 「不记 PASS 的强制释放」,只在前面几步都不可用时才用。**历史遗留的占位台账**(早期为绕这个坑手写的 -`_wt/.state/.json`)现在已无必要:它活着时既占 `MAX_CONCURRENT = 3` 名额又被 `list` 当任务列出, +`_wt/.state/.json`)现在已无必要:它活着时既占 `MAX_CONCURRENT = 8` 名额又被 `list` 当任务列出, 按 E 第 6 步备份后删掉即可。 ### C. `done ` 的正确用法(它是合并点,不是查看器) @@ -179,7 +179,7 @@ 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/.json` 台账 → **不占 `MAX_CONCURRENT = 3` + - `--detach`(**不要 `-b`**):净室没有分支、不写 `_wt/.state/.json` 台账 → **不占 `MAX_CONCURRENT = 8` 名额、不出现在 `$TW list`**,也没人会被误指去对它跑 `done`(建与拆都不走 `$TW new` / `$TW done`)。 - 能这么量的前提:该门禁**只读仓库内文本**。依赖本树构建产物的门禁(`mem-numbers` / `mem-budget` 要 `.map`) 在净室里必然报「找不到 .map 事实源」(见陷阱「新建工作树从未构建过」)——**那类指标不能这么归因**。 @@ -415,7 +415,7 @@ ls "$WTROOT"/.state/locks/ 2>/dev/null # 若该 ID 名下还有 .lock,先按 @T3@ - **必须 `cp` 先备份再 `rm`**:台账里存着 base / junctions / needs_bench / bench_pass,是真出纠纷时唯一的现场记录。 - - 悬挂条目按定义 `path` 已不存在 → 通常**不占** `MAX_CONCURRENT = 3` 名额(A 第 1 步的「活跃」判据要求 path 目录还在), + - 悬挂条目按定义 `path` 已不存在 → 通常**不占** `MAX_CONCURRENT = 8` 名额(A 第 1 步的「活跃」判据要求 path 目录还在), 但它把 `$TW list` 搅成怪输出、并永久挡住同 ID 复用;**path 还在的占位/残留台账才占名额**(早期为绕 B 节那个坑手写的占位台账就是这种)。两种都要清。 - **仍不对这个 ID 跑 `done` / `abort`**:清场会拿台账里的死 `path` / 死 `branch` 去做 `git worktree remove` 和删分支 (这正是 A 第 5 步说的「手工删目录留下的尾巴」,正解就是本步)。历史遗留的占位台账同理——活着时也别 @@ -448,7 +448,7 @@ ls "$WTROOT"/.state/locks/ 2>/dev/null # 若该 ID 名下还有 .lock,先按 **这棵树是不是插件的**:`git worktree list` 里路径含 `.dsh-worktrees/` 就是。`$TW list` 现在会在台账段 下面另起一段列出它们,联接列**点名缺哪一样**(`已联` / `缺 .venv` / `缺 SDL2` / `齐(实体SDL2)`)。**插件树不占 -`MAX_CONCURRENT = 3` 名额**——回收不归本 skill, +`MAX_CONCURRENT = 8` 名额**——回收不归本 skill, 硬计入只会让别人那棵关不掉的树把 `new` 挡死(`new` 会打印一行「另有 N 个插件工作树不计入上限」提醒并发代价)。 三步接上(`` 可带 `worktree/` 前缀,也可多段如 `refactor/logging`): @@ -490,7 +490,7 @@ ls "$WTROOT"/.state/locks/ 2>/dev/null # 若该 ID 名下还有 .lock,先按 5. **清场必须先摘联接**:脚本会先 `os.rmdir` 掉 `.venv` / `simulator/SDL2` 联接再 `git worktree remove`。**手工清场时千万别直接 `git worktree remove`**——git 可能递归进联接, 把主工作树的 `.venv` 一起删掉。手工拆任何一棵树之前先跑 **E 第 3 步**的预扫,拿到 `REPARSE: none` 才动手。 -6. **活跃任务硬上限 3**(脚本常量 `MAX_CONCURRENT = 3`,每棵约 83 MB 检出)。满了 `new` 直接 exit 2, +6. **活跃任务硬上限 8**(脚本常量 `MAX_CONCURRENT = 8`,每棵约 83 MB 检出——du 口径,内容字节和 84.0 MiB → `docs/环境与部署/并行任务工作树方案.md` §4)。满了 `new` 直接 exit 2, 腾位顺序与 `--force` 的代价 → A 节;悬挂台账的清理与复验 → **E 第 6 步**。 7. **`@task` 的工作树放仓库同级 `_wt/`,绝不在仓库内部**——否则污染 `git status`、`pre-commit --all-files` 与全库搜索。唯一例外 = DSH worktree 插件写死的仓内 `.dsh-worktrees/`(改不了;已用「预置 .gitignore + diff --git a/.dsh/skills/task-worktree/scripts/task_worktree.py b/.dsh/skills/task-worktree/scripts/task_worktree.py index 63b2e5e5..5bf72d27 100644 --- a/.dsh/skills/task-worktree/scripts/task_worktree.py +++ b/.dsh/skills/task-worktree/scripts/task_worktree.py @@ -27,7 +27,7 @@ for _stream in (sys.stdout, sys.stderr): EXIT_OK, EXIT_FAIL, EXIT_ENV, EXIT_USAGE = 0, 1, 2, 3 BRANCH_PREFIX = "task/" -MAX_CONCURRENT = 3 +MAX_CONCURRENT = 8 LOCK_NAMES = ("build", "bench") # 按锁分设陈旧窗口(小时)——兜底窗口应与「操作真实时长」同量级: # build:编译是秒~分钟级(keil_build.py 自持自释,持有窗口 ≈ UV4 运行时长, @@ -405,7 +405,7 @@ def bootstrap(wt: Path): # 回收**不在这里**:插件树由「/worktree remove|prune」收,done/abort 也不该拿去处理它。 # 1. 共享联接——没有 .venv 与 simulator 的 SDL2 SDK,插件树里「.venv\Scripts\python.exe」 # 直接找不到解释器:@build、ci_local.py、所有 scripts/check_*.py 全部起跑失败; -# 2. 可见性——插件树不在台账里,「list」看不见它,3 个活跃名额也只数登记任务; +# 2. 可见性——插件树不在台账里,「list」看不见它,8 个活跃名额也只数登记任务; # 3. 独占资源归属——build 锁由 keil_build.py 自持自释(按 cwd 反推持有者名, # 插件树里算成 pw:),bench 锁仍要人占,占锁名得和「lock status」对得上。 diff --git a/docs/测试说明/启动日志原子性复验流程.md b/docs/测试说明/启动日志原子性复验流程.md index 01a50469..741deb40 100644 --- a/docs/测试说明/启动日志原子性复验流程.md +++ b/docs/测试说明/启动日志原子性复验流程.md @@ -138,7 +138,7 @@ SHA 只当坐标(2026-10-26 那天 SHA 被 amend 挪了三轮)。同镜像 - bench 锁是**会话级**,必须手工 acquire/release,且**用同一个名字**释放。 - 非登记任务名(如临时验收窗)走 `lock release bench --task `(**不带** `--bench-pass`)即可释放; `--bench-pass` 会先读 `_wt/.state/.json` 台账,没有就报"任务不存在",`--force` 也救不了。 - 为记一个 PASS 去**造假台账**会白占 3 棵并发工作树名额——PASS 写进任务卡描述即可。 + 为记一个 PASS 去**造假台账**会白占并发工作树名额——PASS 写进任务卡描述即可。 - ⚠ **脚手架 `*.json` 一律不得放 `_wt/.state/`**:那是 task-worktree 的**台账目录**,脚本把每个 `*.json` 当台账读,实测 `task_worktree.py list` 直接崩在 `KeyError: 'id'`。放 `_wt/scratch/`。 diff --git a/docs/环境与部署/并行任务工作树方案.md b/docs/环境与部署/并行任务工作树方案.md index 7723dd67..a7939027 100644 --- a/docs/环境与部署/并行任务工作树方案.md +++ b/docs/环境与部署/并行任务工作树方案.md @@ -129,12 +129,18 @@ D:/Code/DTU Code/ | 任务分支 | `task/-`,从主干当前 HEAD 切出 | | `` | **沿用 `docs/任务规划/README.md` 的编号空间**(`P0-x`/`N-x`/`S-x`/`T-x`/`B-x`/`C-x`/`D-x`);无对应条目的临时任务用 `X-<日期>-` | | 合并方式 | `git merge --no-ff` 合入主干 | -| 并发上限 | **同时最多 3 棵工作树** | +| 并发上限 | **同时最多 8 棵登记工作树**(2026-10-29 由 3 上调,用户裁定;另有插件树不计入,见 §3.4) | **`` 复用登记簿编号**是为了可追溯:任务分支名直接对应待办条目,回收时销项有据可查。 -**并发上限 3** 的依据:`.git` 已 234 MB,每棵工作树要检出约 2400 个受控文件(含 Git LFS 图片), -并发太多磁盘与 I/O 都不划算,人工也管不过来。 +**并发上限的依据**(2026-10-29 由 3 上调到 8,用户裁定;脚本常量 `MAX_CONCURRENT`): +磁盘从来不是这条线的硬约束——实测一棵纯净检出 **84.0 MiB / 2857 个受控文件**(含 LFS smudge +后的真图;`docs/` 独占 46 MiB),而 `.venv` 与 `simulator/SDL2` 走目录联接共享、不随树复制;同日 D +盘余 1.4 TB,**8 棵全开的最坏估算也只有约 3.2 GB**(每棵再叠一次构建产物:Keil 中间件 289 MB + 模拟器 35 MB)。 +真正随并行数上涨的是**排队与合并**:`@build` 与台架 `@test` 各被一把独占锁串行化(见 §5),`done` +的合并必须串行(§5 下方说明),而每多一棵树把 `sync` 追平与冲突面乘一遍。所以这条线现在是**管 +理半径闸门**而非磁盘闸门:调到 8 = 把「同时盯几棵」交回人,两把锁的排队代价用 `$TW lock status` 与 +§7 的台架台账观测;脑子里的名额够不够,看的是锁队列长度,不是磁盘。 ---