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 全过;字节卫生零漂移。
This commit is contained in:
2026-10-30 13:03:12 +08:00
parent dc964090ea
commit 4a4ad6bc5f
123 changed files with 455 additions and 455 deletions
@@ -32,7 +32,7 @@ LOCK_NAMES = ("build", "bench")
# 按锁分设陈旧窗口(小时)——兜底窗口应与「操作真实时长」同量级:
# build:编译是秒~分钟级(keil_build.py 自持自释,持有窗口 ≈ UV4 运行时长,
# 上限 = --timeout,默认 600s);崩溃泄漏时应在数十分钟内即可接管 → 1h。
# bench:会话级(flash → @test → 探针,天然几十分钟~数小时)→ 保留 6h。
# bench:会话级(flash → /serial-boot-test → 探针,天然几十分钟~数小时)→ 保留 6h。
# 另:锁 payload 里持有者可用 `ttl` 字段自报窗口(见 lock_acquire(ttl=...)),
# 优先于这里的默认值——这样 build 锁的窗口能自动跟随其 --timeout。
LOCK_TTL_HOURS = {"build": 1.0, "bench": 6.0}
@@ -404,7 +404,7 @@ def bootstrap(wt: Path):
# 插件建的树(.dsh-worktrees/worktree/<name>)缺三样东西,下面的命令逐条补。
# 回收**不在这里**:插件树由「/worktree remove|prune」收,done/abort 也不该拿去处理它。
# 1. 共享联接——没有 .venv 与 simulator 的 SDL2 SDK,插件树里「.venv\Scripts\python.exe」
# 直接找不到解释器:@build、ci_local.py、所有 scripts/check_*.py 全部起跑失败;
# 直接找不到解释器:/keil-build、ci_local.py、所有 scripts/check_*.py 全部起跑失败;
# 2. 可见性——插件树不在台账里,「list」看不见它,8 个活跃名额也只数登记任务;
# 3. 独占资源归属——build 锁由 keil_build.py 自持自释(按 cwd 反推持有者名,
# 插件树里算成 pw:<name>),bench 锁仍要人占,占锁名得和「lock status」对得上。
@@ -519,7 +519,7 @@ def print_plugin_trees() -> int:
def miss(rel):
return any(t["jn"].get(rel) == "absent" for t in trees)
if miss(".venv"):
print(" 缺 .venv:进去跑 @build / ci_local.py 会**静默**落到机器级解释器(无 "
print(" 缺 .venv:进去跑 /keil-build / ci_local.py 会**静默**落到机器级解释器(无 "
"fontTools,generated-fresh 假红的老根因)")
if miss("simulator/SDL2"):
print(" 缺 simulator/SDL2:模拟器 build_sim.bat 直接报错——CMakeLists.txt 的 "
@@ -562,7 +562,7 @@ def cmd_plugin(a) -> int:
print("({} 下没有插件工作树)".format(PLUGIN_SUBDIR))
print(" 建法:新会话输入框选「Worktree模式」/ 让模型调 worktree_create / "
"/worktree create <name>")
print(" 建完回来「plugin adopt <name>」补共享联接,才能在里面跑 @build 与门禁。")
print(" 建完回来「plugin adopt <name>」补共享联接,才能在里面跑 /keil-build 与门禁。")
return EXIT_OK
wt = plugin_dir_for(a.name)
@@ -584,7 +584,7 @@ def cmd_plugin(a) -> int:
print("[warn] 仍缺 {}:主线里该路径不存在或联接失败——先补主线再复跑 adopt"
"(这棵树还不能算就绪)".format("、".join(still)))
else:
print("该目录现在可以跑 @build / ci_local.py / @test")
print("该目录现在可以跑 /keil-build / ci_local.py / /serial-boot-test")
print("build 锁由 keil_build.py 自持自释,持有者名就是 {}".format(owner))
print("上台架前:$TW lock acquire bench --task {} --note <说明>".format(owner))
print("下台架后:$TW lock release bench --task {}".format(owner))
@@ -606,25 +606,25 @@ def regen(cwd: Path) -> None:
if list((Path(cwd) / "MDK-ARM").glob("**/*.map")):
run_script("scripts/check_mem_numbers.py", ["--write"], cwd, "同步内存/Flash 快照")
else:
print("[skip] 未找到 .map,跳过内存快照同步(需先 @build)")
print("[skip] 未找到 .map,跳过内存快照同步(需先 /keil-build)")
def run_gates(cwd: Path, skip_build: bool) -> bool:
ok = run_script("scripts/ci_local.py", [], cwd, "静态门禁 + 主机侧纯逻辑单测 本地等价 CI")
if skip_build:
print("[skip] 按 --skip-build 跳过 @build")
print("[skip] 按 --skip-build 跳过 /keil-build")
return ok
got, info = lock_acquire("build", "__done__", wait=900)
if not got:
print("!! build 锁被 {} 占用(UV4 不能并发构建),跳过 @build".format(info.get("task")))
print("!! build 锁被 {} 占用(UV4 不能并发构建),跳过 /keil-build".format(info.get("task")))
return False
# 告知 @build「调用方已持锁」:新版 keil_build.py 自持自释(见其 docstring),
# 告知 /keil-build「调用方已持锁」:新版 keil_build.py 自持自释(见其 docstring),
# 读到本变量即跳过,避免自我死锁;**旧版根本不看此变量、也不会自锁**,
# 故此处对「工作树里还是旧脚本」的在途任务同样安全(向后兼容)。
os.environ["DTU_LOCK_HELD"] = "build"
try:
if not run_script(".dsh/skills/keil-build/scripts/keil_build.py", [], cwd,
"@build(0 错 0 警)"):
"/keil-build(0 错 0 警)"):
ok = False
finally:
os.environ.pop("DTU_LOCK_HELD", None)
@@ -789,7 +789,7 @@ def cmd_new(a) -> int:
print()
print("下一步:把新会话的工作目录切到")
print(" " + str(wt))
print("在该目录里干活、跑 @build 与 ci_local.py(并行安全);")
print("在该目录里干活、跑 /keil-build 与 ci_local.py(并行安全);")
print("要动台架先 `lock acquire bench --task {}`,完事 `lock release bench --task {} --bench-pass`。".format(
a.task_id, a.task_id))
print("回收用 `done {}`(**必须在主工作树执行**)。".format(a.task_id))
@@ -978,7 +978,7 @@ def cmd_done(a) -> int:
return EXIT_OK
if st.get("needs_bench") and not st.get("bench_pass"):
raise Fail("任务标记 --needs-bench 但无台架 PASS 记录;跑完 @test 后执行 "
raise Fail("任务标记 --needs-bench 但无台架 PASS 记录;跑完 /serial-boot-test 后执行 "
"`lock release bench --task {} --bench-pass`".format(st["id"]))
print("[ok] 前置检查通过(领先 {} 共 {} 个提交)".format(base, ahead))
@@ -1125,7 +1125,7 @@ def cmd_postmerge(a) -> int:
if Path.cwd().resolve() != main.resolve():
raise EnvError("postmerge 必须在主工作树跑:{}".format(main))
if not list((main / "MDK-ARM").glob("**/*.map")):
raise EnvError("主树没有 .map——先 @build 0/0 再回来(C.5 的前提)")
raise EnvError("主树没有 .map——先 /keil-build 0/0 再回来(C.5 的前提)")
gate = "scripts/check_size_gate.py"
if not a.force and run_script(gate, ["--local"], main, "体积凭证检查(同码)", tail=8):
print("[ok] 凭证同码新鲜,无需补链")
@@ -1234,7 +1234,7 @@ def build_parser():
p.add_argument("task_id")
p.add_argument("--base", default=None)
p.add_argument("--dry-run", action="store_true", help="只预演,不做任何改动")
p.add_argument("--skip-build", action="store_true", help="跳过 @build")
p.add_argument("--skip-build", action="store_true", help="跳过 /keil-build")
p.add_argument("--skip-gates", action="store_true", help="跳过全部门禁")
p.add_argument("--force", action="store_true", help="清场时强制移除工作树")
p.set_defaults(func=cmd_done)