三只 skill 文件收口(队友写成,Lead 核后入库): · 新增 `stale-doc-count-sweep`:把今天做的「一个事实数变了之后全仓同步」钉成流程——从权威来源**实测**真值 → 全形态扫描并**逐条分类**(当前态改 / 变更记录历史行只加当日新行 / 同数不同义的误报不动 / 带日期戳的 测量只加「当时」锁口径)→ 逐条字面替换且每处断言命中数 → 被改文件全文通读核各小节 → 复扫残留归零 → 跑门禁分笔提交。这份流程的价值全在**分类**那步:今天有 5 处「27 → 40」是历史事实、1 处「< 40KB」是同数 不同义(绘制缓冲红线),一并替换就会毁掉它们。 · `mem-budget`:补上 `ram_baseline.py --update`(只往 tsv 追加趋势行)与 `--write`(才同 步台账受管区)的区别、 以及「提交前台账怎么回填」的动作——这个坑是 Lead 本人 05:0x 踩的(用错一枚参数造出一次真的 mem-budget FAIL),写下来比口头转告可靠。 · `task-worktree`:把文中 4 处**自伤**的裸形改成形态描述——该节讲的正是「单字母紧跟数字」这类形态怎么被 校验 2 计数,结果例子自己就是那种形态,等于技能文自己吃门禁余量。改完不可判定绝对数 **982 → 978** (余量 +4),叙述信息量不减:形态与属主都还写清。 ⚠ 这条教训值得单记:**文档在讲某个反模式时,自己不要用那个反模式的字面量**——否则每次读它的人都得替它 付余量。同批 `docs/编号规范.md` §4.4 也已登记这类误判面。 自查:`check_ids --strict` exit 0、不可判定绝对数 978 / 上限 1,166;三文件 noBOM / LF / U +FFFD=0 / 结尾换行齐。
31 KiB
name, description, whenToUse
| name | description | whenToUse |
|---|---|---|
| task-worktree | 多任务并行开发的空间管理(git worktree 生命周期):new 开一棵工作树 + 任务分支、目录联接共享 .venv 与模拟器 SDK、活跃任务硬上限 3 与腾位、build/bench 两类独占锁(含非登记任务占锁后「缺台账」释放不掉的正解)、done 预演合并回主干、门禁未全绿的补基线收尾、并集合并残留审计,以及**把仓外 detached worktree 当净室测量仪**——门禁指标异常增长时一次只注入一份在飞文件、与基线差分做逐文件归因。规格见 docs/环境与部署/并行任务工作树方案.md。 | 要同时推进多个任务、担心同分支同文档互相冲突;要为新任务开一个隔离空间;要把任务合并回主干;或遇到 `new` 报「已有 3 个活跃任务(上限 3)」、`lock release` 报「任务 X 不存在(缺台账)」、`done` 报「门禁未全绿:工作树与分支保留,未清场」、两个任务抢 COM6 / UV4、"改了文档被覆盖"、并集合并残留时;或**门禁指标异常增长(如 id-check 的不可判定数从 1,162 涨到 1,178、+16)却查不出是哪个文件 / 哪条任务道造成的**、主工作树压着多条在飞改动而不许 stash / 提交 / 改门禁脚本、要按 HEAD 基线只读出像验收时。 |
task-worktree — 多任务并行开发空间(短命令 @task)
执行体 = 本 skill 自带脚本 .dsh/skills/task-worktree/scripts/task_worktree.py
(纯标准库,任意 python3;拷整个 skill 目录到别的项目即可用)。
# cwd = 仓库根(或任意子目录)
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" <子命令> [参数]
bash(ops_bash)里固定两个变量,下文用 $TW 代指脚本:
PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktree.py"
"$PY" "$TW" list
退出码:0=成功 / 1=操作失败 / 2=前置或环境错误 / 3=用法。
⚠ 这几个码只有「不接管道」时才读得到——原因与对策见「读输出」一节。
固定命令
| 命令 | 作用 |
|---|---|
new <ID> <slug> |
建分支 task/<ID>-<slug> + 工作树 + 共享联接 + 台账;打印新空间路径 |
list |
所有任务一览:分支 / 落后领先 / 脏文件数 / 台架状态 + 两个锁的归属 |
status <ID> |
单任务详情(未提交清单、相对基线漂移、台架记录、已建联接) |
sync <ID> |
在任务工作树里 rebase 追平主干,把冲突控制在任务分支上 |
| `lock acquire | release |
done <ID> |
预演 → 合入主干 → 并集残留审计 → 重生成产物 → 门禁 → 清场 |
abort <ID> |
放弃任务(工作树 + 分支一起删,永久丢弃) |
new 常用开关:--base <分支>(默认 v2-on-ui)· --needs-bench(无台架 PASS 则 done 拒合)·
--lean(跳过 Git LFS smudge)· --no-bootstrap(不建联接)·
--force(仅跳过并发上限 3 的检查,不解决「谁该让位」,见 A 节)。
done 常用开关:--dry-run(只预演,零改动)· --skip-build · --skip-gates · --force(清场时强制移除工作树)。
lock 开关:--task <ID>(acquire 必填)· --wait <秒> · --ttl <小时>(自报窗口)· --note <文本> ·
--bench-pass(release 时给台账记 PASS)· --force。
⚠ --note 只在 acquire 时写进锁 payload,release 时被忽略——别指望它留下验收证据(证据写进主干提交说明或文档)。
环境变量(高级):DTU_WT_ROOT(任务容器位置,默认仓库同级 _wt/)·
DTU_TASK_BASE(默认基线分支,默认 v2-on-ui)·
DTU_INTEGRATION_WORKTREE(集成点,默认主工作树——用于隔离演练/测试)。
典型流程
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" new N21 dhara-isfree --needs-bench
# → 把新会话的工作目录切到打印出来的 _wt/N21-dhara-isfree/,在那里干活
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" lock acquire bench --task N21
# 烧录 / @test / 板级套件……
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" lock release bench --task N21 --bench-pass
# 回到主工作树执行回收
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" done N21
读输出:管道与截断(两起误判的共同根因)
... 2>&1 | tail -N的退出码是tail的,恒为 0 → 脚本的1/2全被吞。实测三条印着 「rc=0」的输出其实都是 exit 2:new撞活跃上限、lock release缺台账两次。 所以判据先看文本([ok]/!!/前置/环境错误:/失败:),要真码就直接执行不接管道或补一句echo RC=$?;坚持接管道就在 bash 里取${PIPESTATUS[0]}。done/done --dry-run的输出不要用tail -2截:接管道时 python 的 stdout 是块缓冲、git 子进程的 stderr 不缓冲,两股流错位混排,tail -2只剩「--dry-run:到此为止…」加上 stderr 里带conflict字样的片段 → 据此误判「有冲突」。实测:tail -2误读成 conflict,放宽到tail -10才看到[ok] 无冲突,预演结果树 <sha>。dry-run 全文只有 5~8 行,直接读全量。
分步操作(四条运行时机制)
A. new 被拒「已有 3 个活跃任务(上限 3)」——怎么腾位
- 读原文:
前置/环境错误:已有 3 个活跃任务(上限 3);先 done/abort,或加 --force(exit 2)。 上限是脚本常量MAX_CONCURRENT = 3;「活跃」的判据 =_wt/.state/<ID>.json台账存在,且台账里path指向的目录还在(手工删了工作树目录就不占名额,但会留下悬挂台账,见第 5 步)。 $TW list看全景(分支 / 落后领先 / 脏文件数)决定谁让位:已完成的先done(合并回收,一步不省)。- 没人认领、零提交、尚未 claim 的空闲树 →
abort <ID>腾位(实测就是 abort 掉排在后面的空闲树换出名额)。abort= 工作树 + 分支一起删、永久丢弃;工作树有未提交改动或已有提交时脚本会拒绝,确认可弃再加--force。 - 确实要同时开第 4 棵才
new <ID> <slug> --force:磁盘代价约 83 MB/棵(--lean约 45 MB), 并行度上去了、冲突面与台架排队也一起上去——不是免费的。 - 别用手工
git worktree remove腾位:台账不会被删(下次new同 ID 会报「任务 已存在」), 还绕过了「先摘联接」(铁律 5),有让 git 递归进联接把主工作树.venv删掉的风险。 - 腾位后复验:
$TW list的条目数与ls _wt的目录数一致,再new。
B. 自己占的锁没有台账:lock release 报「任务 X 不存在(缺台账)」
症状(lead 在主工作树跑台架验收窗,用临时名占锁:lock acquire bench --task acceptwin):
"$PY" "$TW" lock release bench --task acceptwin --bench-pass
前置/环境错误:任务 acceptwin 不存在(缺台账 _wt/.state/acceptwin.json)
"$PY" "$TW" lock release bench --task acceptwin --force --bench-pass # 同一句错误,锁仍在
根因(读脚本确认):release 分支里只要带 --bench-pass 就先 load_state(task) 去给台账写 PASS,
台账不在就抛「前置/环境错误」,根本走不到释放那一步;--force 只覆盖「持有者与 --task 不符」和
acquire 的陈旧接管,管不到台账缺失。而 acquire 侧不查台账——这个不对称就是坑本身;报错文案里的
「任务不存在」也别误读成「我没占过锁」。
- 先
$TW lock status确认持有者 / pid / 持有时长 / TTL(如bench acceptwin 持有(pid=2784, 已 0.1 / TTL 6.0 小时))——别反复重试 release。 - 不需要同步记 PASS → 直接
$TW lock release bench --task <NAME>(不带--bench-pass)就能释放。 - 需要
--bench-pass而这个名字不是登记任务 → 手写最小占位台账_wt/.state/<NAME>.json(键与new写出的完全一致,共 11 个):
{
"id": "acceptwin",
"slug": "lead-window",
"branch": "(none: lead 验收窗占位台账)",
"path": "D:\\Code\\DTU Code\\STM32F4-Base",
"base": "v2-on-ui",
"created_at": "2026-10-26 17:00:00",
"junctions": [],
"needs_bench": true,
"bench_pass": false,
"note": "lead 验收窗(非工作树任务,为 lock 台账占位)",
"lean": true
}
用写文件的工具落 UTF-8(无 BOM),别用 echo / Set-Content(中文乱码铁律)。
4. $TW lock release bench --task <NAME> --bench-pass → [ok] 已为任务 … 记录台架 PASS +
已释放锁 bench(原持有者 …)。
5. 立刻删掉占位文件:它活着期间会被 list 当任务列出,并占掉 3 个活跃名额之一(path 指向的
目录存在就算活跃);而且绝不对这个 ID 跑 done / abort——清场会拿台账里的 path / branch
去 git worktree remove 和删分支。
6. 复验 $TW lock status:build 空闲(TTL 1.0h) / bench 空闲(TTL 6.0h)。
7. 占位台账一删,那条 PASS 也随之消失。这个 PASS 后面还要用的话,从一开始就用已登记的任务 ID 占锁
(lead 的验收窗借某个已 new 出来的 ID 最省事),或把验收结论写进主干提交说明/文档。
8. 为什么必须马上释放:done 的前置会跑 release_stale_locks()——未到 TTL 的活锁直接判失败:
独占资源 bench 仍被 acceptwin 占用(已 0.1 / TTL 6.0 小时),先 release。lead 挂着锁 = 挡住所有任务
的收口;只有超过 TTL 的锁才会被自动接管。
9. 最后手段(脚本本身都走不通时):锁就是 _wt/.state/locks/<name>.lock 里的单行 JSON
(name/task/pid/host/epoch/at/note/ttl),release 的实现就是 unlink 它——手工删这个文件等价于
「不记 PASS 的强制释放」,只在前面几步都不可用时才用。
C. done <ID> 的正确用法(它是合并点,不是查看器)
- 合并前确认任务属主已把全部改动提交完。
$TW status <ID>看未提交清单与领先提交数;跨会话任务 再问一句属主(send_message)。实测教训:属主还剩「2/2」没提交就done,把只含 1/2 的分支合进了主干, 只能另开一棵树补后半并向对方说明(是自己的时序错,不是分支问题)。 - 先
--dry-run,并读全量输出(别tail -2,见「读输出」)。判据两行:[ok] 前置检查通过(领先 <base> 共 N 个提交)+[ok] 无冲突,预演结果树 <sha>; N 必须等于属主应交的提交数,对不上回到第 1 步。dry-run 零改动。 - 正式
$TW done <ID>——必须在主工作树(铁律 1)。 - 输出
!! 门禁未全绿:工作树与分支**保留**,未清场时,紧跟的主干已含合并提交 <sha>已经说明 合并落进主干了。- ✅ 走提示里的 a) 在主工作树修正后追加提交;
- ❌ 绝不用 b) 的
git -C "<主树>" reset --hard <sha>——那是丢弃已入库的合并提交,而reset --hard本身是项目级 Never(会连未提交工作一起丢)。提示 b 只是列出的可能性,不是建议。 - 先定位是哪条钩子红:门禁输出里的
[SUMMARY] L0: pre-commit hooks -- passed=N failed=M, 不同根因处置不同(见陷阱「done的门禁 =pre-commit run --all-files」)。最常态是.map滞后 → 第 5 步。
- 补绿固定链(2026-10-25 实测;顺序即因果,全在主工作树跑,逐条不接管道看退出码):
"$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 的门禁跑在 @build 之前」。
6. 重跑 done 之前主工作树必须干净——否则前置检查直接
失败:主工作树不干净(…)——合并会把未提交改动卷进来,所以第 5 步的产物必须先提交。重跑时若分支
已并入,会打印 [i] task/<ID>-<slug> 已合入 <base>,跳过合并,直接清场 → 摘联接 → 完成(分支此前已合入)。
7. 清场完成后再敲一次 done <ID> 会得到 前置/环境错误:任务 <ID> 不存在(缺台账 …json)(exit 2)——
这不是失败:清场的最后一步 unlink 了台账。判完成以 git log 为准。
D. 门禁指标异常增长 → 逐文件归因(把净室工作树当测量仪,主树全程只读)
场景(2026-10-27 实测):id-check 突然 FAIL「不可判定 1,178 > 上限 1,166」——比上一轮量到的 1,162 又涨了 +16,
而主工作树里同时压着 4 条在飞任务道的未提交改动。不许 stash / checkout / reset(那是搬动甚至丢弃别人未提交的工作,
项目级 Never;git status 一乱,整条道的现场就没了),不许改门禁脚本本身(它就是那把尺子,改了数字就不可信),
也别在脏主树里跑门禁(那测的是 4 条道 + 未跟踪文件的混合体,永远分不出谁)。正解:在仓库之外另起一棵
detached HEAD 的干净工作树当净室,一次只往里注入一份在飞文件,与基线差分即得每个文件的独立贡献。两步:
净室建树 → 单文件注入测量。
- 建净室 + 先落基线数(路径必须在仓库目录之外;铁律 7 对临时树同样成立——放仓内就污染
git status/pre-commit --all-files/ 全库搜索):
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/check_ids.py" 2>&1 | tail -25 # ← TODO 口径:以 .pre-commit-config.yaml 里 id-check 钩子的 entry/args 为准;基线与注入用**同一条命令**
--detach(不要-b):净室没有分支、不写_wt/.state/<ID>.json台账 → 不占MAX_CONCURRENT = 3名额、不出现在$TW list,也没人会被误指去对它跑done(建与拆都不走$TW new/$TW done)。- 能这么量的前提:该门禁只读仓库内文本。依赖本树构建产物的门禁(
mem-numbers/mem-budget要.map) 在净室里必然报「找不到 .map 事实源」(见陷阱「新建工作树从未构建过」)——那类指标不能这么归因。 git status --porcelain非空 = 有别人的未跟踪文件被带进净室 → 先git clean -xdn干扫(别去n) 看清会删什么,只挪走多出来的那些再测;语料被污染出来的数字与 CI 不可比。
- 一次只注入一份在飞文件 → 跑同一个扫描器 → 与基线差分(得到的是每个文件的独立贡献,不是合计):
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/check_ids.py" 2>&1 | tail -25 # ← 与第 1 步同一条命令
git -C "$WT" checkout -- "$f" # 只还原净室里自己刚拷进来的那份
done
实测:run_par.c / board_ui_nav.py / check_nested_comment.py / keil SKILL.md 各 +0,
而 log 道那三个文件 +6 / +8 / +2,合计正好 +16。Σ(逐文件增量) ≠ 观察到的涨幅 → 还有没被注入的
在飞文件,或存在文件间交互项,不许就此交卷。
3. 定性增量是哪一类字面量(只有「谁」没有「为什么」= 给不出可执行的改法):同一计数器在 HEAD 版与
在飞版各数一遍,差值才是新增行带来的(存量的不算这条道的账);再对新增行直接 grep。本次 16 处全是
裸形 「字母 T 累进数字 4」(T 在 scripts/id_registry.json 的 bare_check_letters 里,规范形态是连字符形),
全部落进 skip_bare_unmanaged —— 到这一步结论才配发给属主。
4. 闭合 + 复验主树一个字节没被污染:
git -C "$MAIN" worktree remove --force "$WT" # 净室里只有自己的拷贝、没有原件,--force 安全
git -C "$MAIN" worktree list # 不该再有 und-attr
git -C "$MAIN" status --porcelain | sort # 与测量前**逐行一致**(测量前先存一份基线清单)
「增量 + 字面量类别 + 属主」三者齐了才算归因完成。同一条道自己「我自查过不是我们」的结论别直接采信
→ 陷阱「队友的自查结论要复核」。
同一棵净室树的第二种用法:按指定 commit 只读构建出像 / 烧录验收——不搬动任何在飞改动,也不为台架
提前抢发一笔提交;构建与烧录都在这棵树里做(不在主树),仍受 bench / build 锁串行(铁律 3)。
铁律
done只能在主工作树执行——主干分支检出在主工作树,git 不允许同一分支在两处检出。done不 push。它结束于本地合入 + 门禁全绿;推送需用户明确许可(项目既有纪律)。build与bench都必须串行:UV4 不能两个实例同时-b;串口 COM6、ST-LINK、 设备 IP192.168.1.100各只有一份。构建与烧录都不可并发。build锁由@build自持自释,调用方无需手工 acquire/release;bench锁必须手工:lock acquire bench --task <ID>→ 烧录/@test/探针 →lock release bench --task <ID> --bench-pass。非登记任务(lead 的验收窗)占了锁怎么放 → B 节。
- 单写者文档:
docs/任务规划/(整个文件夹)、AGENTS.md的任务表块、MDK-ARM/*.uvprojx、.pre-commit-config.yaml、内存快照 —— 任务期不在任务工作树里改,留到done统一处理。 - 清场必须先摘联接:脚本会先
os.rmdir掉.venv/simulator/SDL2联接再git worktree remove。手工清场时千万别直接git worktree remove——git 可能递归进联接, 把主工作树的.venv一起删掉。 - 活跃任务硬上限 3(脚本常量
MAX_CONCURRENT = 3,每棵约 83 MB 检出)。满了new直接 exit 2, 腾位顺序与--force的代价 → A 节。 - 工作树放仓库同级
_wt/,绝不在仓库内部——否则污染git status、pre-commit --all-files与全库搜索。 done是合并点:属主没提交完就别done;合并提交一旦进了主干,只用「主工作树补提交」收尾, 不用reset --hard反悔 → C 节。
陷阱(实测)
--force不是万能钥匙:它只覆盖四类检查——并发上限、持有者与--task不符、陈旧锁接管、 清场时强制移除工作树。台账缺失这类前置/环境错误(exit 2)跟它无关(实测lock release --force --bench-pass报的是同一句话,锁照旧没放)。- 联接不是符号链接:本仓库
core.symlinks=false,git 里的 symlink 会被检出成 「内容是目标路径的文本文件」。共享.venv只能用 Windows 目录联接(mklink /J, 不需要管理员权限),且联接必须建在版本控制之外。 实测(2026-10-12):用 ctypes 直调DeviceIoControl(FSCTL_SET_REPARSE_POINT)会被系统拒绝(Win32 4392),mklink /J正常——所以脚本走后者,参数以列表交给 subprocess,含空格的路径(本项目DTU Code)由 subprocess 自动加引号。 - 联接建失败不中止
new:只告警并打印手工补建命令,避免留下半成品任务空间; 但该任务里跑不了.venv\Scripts\python.exe,@build/ci_local.py会找不到解释器。 done前主工作树必须干净:否则未提交改动会被卷进合并(补完基线再重跑done时最容易忘,见 C 第 6 步)。- ⚠
done的门禁跑在@build之前 → 依赖.map的钩子会误报(2026-10-12 实测):done的顺序是「合并 → 重生成 → 门禁(ci_local) →@build」,而mem-budget(登记簿条目「缩堆迁 CCM 后半(暂缓)」)/mem-numbers是拿文档快照 vs.map比对 —— 门禁跑时.map还是主工作树合并前的旧产物,与刚合并进来的新台账必然不一致 → 误报[FAIL] 台账受管区与 .map 不一致。@build跑完后.map才是新的。 处置(2026-10-25 补全):按 C 节第 5 步的完整链补——check_mem_numbers.py --write→ram_baseline.py --write→ram_baseline.py --update→check_test_coverage.py --update-baseline→check_doc_links.py --all→ 提交 → 重跑done(它会检测到「已合入」直接清场)。 此时主干已含合并提交,绝不能用提示里的reset --hard回退。 done的门禁 =ci_local.py=pre-commit run --all-files(50 条钩子全跑)(2026-10-26):红了先读[SUMMARY] L0: pre-commit hooks -- passed=N failed=M skipped=K,按钩子 id 定根因,别一律当「.map滞后」 去跑补基线链。典型例外:generated-fresh(生成器幂等性,触发面tools/**、docs/images/*.svg、UI 生成文件)—— 合进来的分支改了生成器却没重跑产物时,补基线链救不了,处置是在主工作树重跑那个生成器、脚本与产物同一笔提交, 再重跑done。另注意pre-commit run --all-files只跑 pre-commit stage,个别只在 commit-msg / 手工阶段跑的守卫不在此列 (口径见scripts/ci_local.py头部说明)。- ⚠ 新建工作树从未构建过 → 体积类门禁必然红(2026-10-25 实测):
.map是每棵树各自的 构建产物、不入版本库,所以new出来的空间里mem-numbers/mem-budget/ci_local.py的 L0c 都拿不到事实源,报[FAIL] 找不到 .map 事实源(脚本提示已写明原因,不是代码缺陷)。 同理done的重生成步骤会[skip] 未找到 .map,跳过内存快照同步(需先 @build)。 处置:新空间第一次提交前先@build一次(build锁自动排队,与别的树串行)。与上一条 的区别要分清:上一条是「有.map但是旧的」,本条是「根本没有.map」,处置动作不同。 - ⚠ 增量构建可能不重链 →
.map数字瞬态不一致(2026-10-12 实测):@build若判定无需重链,.map保持旧值,此时--write生成的快照是瞬态值 (实测同一份源码两次读到 223,560 与 223,724,差 164 B)。以主工作树(集成点) 的.map为准;改完 RAM 相关代码后确认重链再刷新快照。 ram_baseline.py的 help 串里有未转义的%(2026-10-25 实测:某些直调姿势会渲染 argparse help 并崩)—— 查用法读脚本头部 docstring,别靠--help;修脚本本身属mem-budgetskill 的范围。- 合并冲突常落在「生成物」上:
docs/内存管理/RAM使用分析与优化方案.md(mem-numbers 快照)在双方都改过时必冲突,且两边都不能直接选(各自基于不同基线)—— 正确做法是解冲突后用合并后的实际构建重新生成,不要挑一侧。 - 并集合并残留:双方都改过的文件(尤其
.gitignore)会被 git 自动并成并集, 可能留下指向已删目录的死规则或重复条目。脚本会自动报「双方都改过」的文件清单 + 连续重复行;权威判据是 base / 两个 tip 三方对比(git show <ref>:<file>逐项grep -F), 别用tail -N目测。 - ⚠ 别用 heredoc 打补丁改门禁脚本造探针(2026-10-27 实测三连败,白烧一整轮):heredoc 里的 python
把门禁脚本改坏——
patched 3之后PROBE_RC=1、SyntaxError: unterminated string literal (detected at line 882),而它正是被测对象,改完的数不可信;另一处replace('\\', '/')(想把 Windows 反斜杠换成正斜杠)里的反斜杠被多层引号吞掉 →SyntaxError (detected at line 16)。正解:探针落成独立.py文件(写文件的工具,UTF-8 无 BOM) 再"$PY" probe.py;非用 heredoc 不可就用单引号定界<<'PY'(防变量展开),且路径一律as_posix()、 一个反斜杠都不写——反斜杠字面量要穿「JS 模板串 → bash → shell 引号 → python」四层,必被吞。 - 取门禁数字:import 门禁模块,别猜输出、别打补丁(2026-10-27;grep 源码 / 手抄正则 / 解析 stdout 三种姿势
都拿不到可复核的数,换成这句立刻拿到):先读门禁脚本的
main()(直读末尾那几十行)看它怎么装配检查器, 把那几行照抄进探针——sys.path.insert(0, 'scripts')→import check_ids as ci→ci.load_json(ci.REGISTRY_FILE, 'reg')+ci.load_json(ci.ALIASES_FILE, 'alias')→ck = ci.Checker(reg, al, verbose=False)→ck.run()→print(ck.stats)。别猜键名:先print(sorted(ck.stats))列出全量计数项,再按精确键取数(顺带git diff确认门禁脚本自己没被改过)。 - 别把「不可判定」当成一个数(2026-10-27):
id-check有两种口径——检查器自己算的硬不可判定实测只有 2 处,而门禁红线(scripts/id_registry.json的undecidable_total_ceiling)量的是派生值refs_total - decidable(实测 8,744 - 7,582 = 1,162,拆开 =skip_bare_unmanaged834 +noise_allowed326 + 硬不可判定 2,三数精确闭合)。所以带属主登记处的族(TC-XXX 这类)加多少引用都算 「可判定」、一点不吃这条余量,真正吃余量的是裸形字母+数字字面量;反过来「把可判定引用改写成散文」才是 稀释比值的动作,那才是绝对数红线要防的。口径混用会当场给整条道下错指令(本次就下错过、量完立刻撤回)。 - 队友的自查结论要复核,小心它测的是错变量(2026-10-27):log 道做过受控实验、结论是「我们没推高这个数」——
他们改的是 TC 号、盯的是
skip_bare_unmanaged,那个组合当然不动;而 +16 走的是另一条路(新增裸形 「字母 T 累进数字 4」)。 定责用 D 节:逐文件注入 + HEAD/在飞两版同一计数器相减 + 对新增行 grep;闭合后 +16 = 16 处裸形 「字母 T 累进数字 4」, 推翻了自查结论。口头自查与--note都不是证据,可复核的差分数字才是。 .workbuddy-ai/刻意不共享:记忆不随任务分叉,也不给联接递归删除的机会。 任务工作树自己的记忆由done/abort在清场前归并回主工作树。--lean只省磁盘不省时间:实测全量 smudge 7.25 s / 83 MB,跳过 smudge 7.05 s / 45 MB, 两者工作区都干净。默认走全量(参考图可用),磁盘紧张时才用--lean。- 锁不绑定进程存活:
lock acquire本身是短命进程,而真正用资源的烧录/构建在别的进程里跑。 所以判「陈旧」只看时效(该锁的 TTL)加显式--force;pid 仅作排障记录。 同任务重复 acquire 是幂等的(不会把自己锁死)。 - TTL 按锁分设(2026-10-12):
build= 1h、bench= 6h(LOCK_TTL_HOURS)。 口径是「兜底窗口应与操作真实时长同量级」——编译是秒~分钟级,6h 的兜底太钝; bench 是会话级(flash→@test→探针),才配得上小时级。持有者还可用lock acquire ... --ttl <小时>自报更精确的窗口(keil_build.py即按其--timeout自报)。 - 陈旧锁自动接管:超过该锁 TTL 未释放即视为陈旧,下次
acquire会接管并提示; 未到 TTL 的活锁需--force。done的收口前置也会自动释放陈旧锁(不再硬失败),但 未到 TTL 的活锁会把done挡在前置检查(B 节第 8 步)。 build锁不必手工持(2026-10-12 起):@build(keil_build.py)自持自释—— 进入 acquire、finally无条件 release,所以"Agent 忘了 release、锁一直挂到 TTL"这条在构建路径上已消除。done的门禁自己持锁并给子进程设DTU_LOCK_HELD=build告知跳过(新旧脚本都兼容)。bench锁仍须 Agent 手工持(会话作用域:跨烧录→@test→探针,见keil-buildSKILL.md)。
验证
$TW list:条目数 =ls _wt的目录数且 ≤ 3;刚回收的任务与 B 节的占位条目都不再出现。$TW lock status:build 空闲(TTL 1.0h)+bench 空闲(TTL 6.0h)——没有本会话留下的持有者。git log --oneline -3:能看到merge: task/<ID>-<slug> 合入 <base>与其后的补基线提交;git status --porcelain为空(补链改动已提交,才谈得上重跑done)。ls _wt/.state/:该 ID 的台账已无(清场最后一步 unlink);重复done报「任务不存在」不必再追。- D 节归因:测量前后
git -C "$MAIN" status --porcelain逐行一致(主树全程只读);git worktree list里净室路径已消失;逐文件增量之和 等于观察到的涨幅(实测 +6+8+2 = +16),且每处增量 都指到具体字面量类别(裸形 「字母 T 累进数字 4」)与属主;取数走import check_ids读ck.stats,且门禁脚本自身git diff为空。 - 补绿链的每一条都自己
echo RC=$?看到 0,最后以重跑done打出的清场日志(已摘除联接/已删除分支/完成(分支此前已合入))为成功判据,而不是tail出来的最后一行。
与其它 skill 的衔接
- 构建 =
keil-buildskill(@build);烧录/回读 =stlink-flashskill;串口闭环 =serial-boot-testskill - 烧录产物的真实 hex 路径以
.serialtest/config.json的 hex 键为准(实测MDK-ARM/Objects/STM32F407-Demo.hex这个路径并不存在),拷错等于烧了旧镜像 →serial-boot-testskill - 新
.c文件 =new-source-fileskill(uvprojx与CMakeLists.txt双端注册) .map口径 / RAM 台账与趋势 →mem-budgetskill;UI 回归 →ui-verifyskill- 完整机制与目录分配 →
docs/环境与部署/并行任务工作树方案.md