Files
STM32F4-Base/.dsh/skills/keil-build/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

19 KiB
Raw Blame History

name, description, whenToUse
name description whenToUse
keil-build Keil MDK-ARM 命令行编译闭环(自带独立脚本 keil_build.py,纯标准库,拷到任何 Keil 项目即用),覆盖 UV4 -b 构建、「0 错误 0 警告」判定、build_log 定位报错、PowerShell 与 bash 两种调用形态及各自的 exit 2 归属、退出码与 build 锁持有者名判读(含插件树的 pw: 前缀)、exit 2 时 UV4.exe 的候选路径探测与单次调用 env 注入闭环、UV4 改写工程文件的入库陷阱、构建副产物「%SystemDrive% 字面量垃圾目录」的取证、清理与入库排除。 需要编译 Keil MDK 工程、跑 /keil-build 验证代码改动(只改 .c 注释也算改动)是否「0 错误 0 警告」、/keil-build 报 BUILD_ENV_ERROR 与 UV4.exe not found(退出码 2;DSH 会话看不见用户级 DTU_KEIL_UV4)、解读 UV4 构建日志、退出码 2/4 或 build 锁持有者名(含 pw: 前缀),在 bash / ops_bash 里执行 PowerShell 形态的编译命令行报 syntax error near unexpected token,或构建后 git status 冒出 %SystemDrive% 目录时。

keil-build — Keil 命令行编译(独立可运行 skill)

执行体 = 本 skill 自带脚本 .dsh/skills/keil-build/scripts/keil_build.py (纯标准库,任意 python3;拷整个 skill 目录到别的 Keil 项目即可用,不依赖仓库脚本)。

# cwd = 项目根目录;PowerShell 形态(AGENTS「构建与验证」的编译入口)
& $PY ".dsh\skills\keil-build\scripts\keil_build.py"
# 覆盖:--project X.uvprojx --uv4 UV4.exe --target 名字 --log 路径 --timeout 600

⚠ 改本文件 frontmatter 要么整值加双引号,要么一个 ASCII 冒号都别写(2026-10-30 这份就因此整份加载失败)。 DSH 用 yaml 包的 parse() 读 frontmatter(@deepseek-ai/dsh-skill-filesystem 的 parseFrontmatter()), plain scalar 里出现 ASCII「冒号 + 空格」(例如把报错原文 BUILD_ENV_ERROR: UV4.exe not found 或 pw: 前缀 直接写进 description)就报 Nested mappings are not allowed in compact mappings at line 2, column 14 → 技能整份不加载(skill() 读不到、也不进技能目录)。js-yaml 能吃下去 —— 拿它自检会假绿,必须用 DSH 自带的那个解析器验。另 — 值首字符不能是 %(directive indicator)或 & (anchor),双引号里的 %SystemDrive% 之类不受影响。落盘后让 DSH 真加载一次再收口。

调用形态:先认执行器是哪个 shell(2026-10-26 实测)

  • 同一条编译命令,PowerShell 与 bash 是两种写法:bash 侧要去掉前导 & 并把路径改正斜杠(照抄 AGENTS 的 PowerShell 形态进 ops_bash 会语法错秒退 exit 2)
  • ⚠ 那个 exit 2 不是脚本的环境错误 2:stderr 含 syntax error near unexpected token = 写法问题,换 bash 形态重跑,别去查 DTU_KEIL_UV4、别当环境错误排障
  • 后台跑(run_in_background: true)要自己收码,且别把脚本接进管道:实测 $? 取到的是 tail 的码(恒 0);要看报错行就单独 grep MDK-ARM/build_log.txt
  • 解析顺序:命令行 > 项目档案 .serialtest/config.json > 环境 > 探测;UV4 显式设了但路径无效 = 立即报错,不静默回退
  • 退出码 0=PASS(0错0警) / 1=构建失败 / 2=环境错误 / 3=用法 / 4=build 锁被占用;2026-10-12 修正过 2 与 3 的归属,该篇末尾记了前后口径
  • bash / ops_bash 侧「构建到底过没过」的一次自查(2026-10-26 实测口径):先 cd 到仓库根(或该 worktree 根),跑 .venv/Scripts/python.exe .dsh/skills/keil-build/scripts/keil_build.py; echo BUILD_RC=$? —— 正斜杠、无前导 &,退出码归 python、不归 bash;再单独读日志计数 grep -a -E "Error\(s\)|error:|Warning\(s\)" MDK-ARM/build_log.txt。报结论要三样齐 —— BUILD_RC=0 + UV4_RC: 0 + BUILD_OK: 0 errors, 0 warnings(脚本自己判「无 error: 行 且 尾行含 0 Error(s), 0 Warning(s)」);bash 吐的 syntax error near unexpected token 既不是构建通过也不是构建失败,那是写法问题,别拿它当构建结论报。

本节全文(逐字搬移、未改写,含命令与实测数字)→ executor-shell-forms.md。动手前先读那篇。

exit 2 实战闭环:UV4.exe not found 的探测 → 注入 → 复核(2026-10-30 实测)

先定性,别动源码:脚本报 BUILD_ENV_ERROR: UV4.exe not found (--uv4 / config 'uv4' / $DSH_KEIL_UV4 / $DTU_KEIL_UV4 / PATH) 且退出码 2 = 环境错,不是构建失败——本次 UV4 根本没启动、MDK-ARM/build_log.txt 也没被改写, 日志里看到的是上一次的。此时去改 .c/.h 或怀疑代码,就是把环境问题当编译问题排。 (会话为什么看不见:DSH 会话的 shell 由常驻服务派生,服务启动之后设的用户级变量不会自动进来; run_code 起的 Node 进程 process.env 干脆是空的——AGENTS.md「会话不继承用户级变量」同记此坑。 所以变量"已设但本会话看不见"是常态,不是用户没设。)

  1. 探测候选安装路径(逐个 fs.existsSync / test -f,命中即取绝对路径)—— C:/Keil_v5/UV4/UV4.exe、C:/Keil/UV4/UV4.exe、D:/Keil_v5/UV4/UV4.exe、D:/Keil/UV4/UV4.exe、 C:/Program Files/Keil_v5/UV4/UV4.exe、D:/Program Files/Keil_v5/UV4/UV4.exe (2026-10-30 实测只命中 C:/Keil_v5/UV4/UV4.exe,其余五个都不存在)。
    for p in 'C:/Keil_v5/UV4/UV4.exe' 'C:/Keil/UV4/UV4.exe' 'D:/Keil_v5/UV4/UV4.exe' \
             'D:/Keil/UV4/UV4.exe' 'C:/Program Files/Keil_v5/UV4/UV4.exe' 'D:/Program Files/Keil_v5/UV4/UV4.exe'; do
      [ -f "$p" ] && echo "HIT $p"; done
    
  2. 读用户级来源确认"值本来就对"(同一轮里顺手做掉,别只靠猜路径): bash / Node 侧 reg query HKCU\Environment(整表打出来再找 DTU_KEIL_UV4 / DSH_KEIL_UV4), PowerShell 侧 [Environment]::GetEnvironmentVariable('DTU_KEIL_UV4','User')。 有值 → 直接用它;没值/值指向旧目录 → 用第 1 步的命中路径。 ⚠ 不替用户改全局:不要 setx、不要 SetEnvironmentVariable(...,'User')——那是用户自己在真终端里 做的事(docs/环境与部署/本地工具路径环境变量方案.md);本闭环只解决"这一次构建怎么跑起来", 报告里也必须这么写。
  3. 只给这一次调用注入再重跑(两种等价形态,二选一):
    // run_code:options.env 是**整体替换**,所以要连 SystemRoot / Path / USERPROFILE 一起给
    const { execSync } = await import('node:child_process');
    execSync('".venv/Scripts/python.exe" .dsh/skills/keil-build/scripts/keil_build.py', {
      cwd: MAIN, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
      env: { SystemRoot: 'C:\\Windows', Path: 'C:\\Windows;C:\\Windows\\System32',
             USERPROFILE: 'C:\\Users\\<你>', DTU_KEIL_UV4: 'C:\\Keil_v5\\UV4\\UV4.exe' } });
    
    • bash / ops_bash 侧的等价写法 = 显式参数 --uv4 'C:\Keil_v5\UV4\UV4.exe'(值含空格要整体加引号)。
    • run_code 的 timeoutMs 给足:注入后这次构建实测 10 s 跑完,但首次冷构建更长(我给的 500000)。
    • 别顺手加 --no-lock:注入重跑时锁链照旧(LOCK: 已获取 build 锁(task=build-cli) → 已释放锁)。
  4. 复核口径(三样齐了才算真跑过,缺一样就是没构建):脚本回显 UV4: <绝对路径> (<来源>)(来源随注入方式变:$DTU_KEIL_UV4 或 --uv4)→ UV4_RC: 0 → BUILD_OK: 0 errors, 0 warnings,且外层退出码 0(自己包 execSync 就显式把码打出来, 别接管道,见「调用形态」)。报告里写清"本次注入 + 来源",不得声称已永久设置系统/用户环境变量。
  • ⚠ 注入的若是注册表里那个陈旧值,会撞同族的另一种 2: BUILD_ENV_ERROR: $DTU_KEIL_UV4 is set but not a file: … (fix the value; no silent fallback) —— 值在但指向旧盘符/旧目录名(本仓踩过 D:\Code\DTU 程序\… 搬迁)。脚本刻意不静默回退, 回到第 1 步用探测命中的路径覆盖它,别去猜目录、更别改脚本让它"猜 C 盘"(设计上明确禁止从旧路径猜)。
  • ⚠ 本闭环只回答"UV4 在哪儿",不回答"调用写法对不对":从 bash 侧照抄 PowerShell 的 & 也是 exit 2, 但 stderr 是 syntax error near unexpected token(见「调用形态」)。先看 stderr 再决定排障方向, 两种 2 的处置完全不同。
  • 改 .c 注释(哪怕只改一行中文注释)也必须跑完这道构建验证:2026-10-30 的实测就是 「改注释 → /keil-build → 吃 rc=2 → 探测+注入 → 10 s 通过」。注释类坑(BOM 被剥 → #870-D、 注释里写 ** 路径 → 9-D)只在重编译那个 TU 时才暴露,"以前 0 警告"不代表安全。

build 锁:脚本自持自释(2026-10-12)

UV4 不能两个实例同时 -b,但构建锁不该由调用方"记得释放"——那正是泄漏源: Agent 手工 lock acquire build 后忘了 release,锁要挂到 TTL 才被接管。 故本脚本自己管:进入 acquire → try 构建 → finally 无条件 release (构建失败、异常、SystemExit 都会释放)。

开关 作用
--lock-task <ID> 持有者名。默认 $DTU_TASK_ID → 从 cwd 反推:_wt/<ID>-<slug>/ → <ID>(@task 那批);.dsh-worktrees/worktree/<name>/ → pw:<name>(DSH worktree 插件建的树,plugin_tree_owner())→ 都没有才落 build-cli
--lock-wait <N> 锁被占时等待 N 秒(默认 0 = 立即失败,退出码 4)
--no-lock 完全不加锁(危险:并发 -b 互相干扰)
DTU_LOCK_HELD=build 环境变量:调用方已持锁时设它,本脚本跳过自持自释(task-worktree done 的门禁即如此用)
  • TTL 自报(2026-10-12):acquire 时按 --timeout 声明陈旧窗口 = timeout/3600 + 15min,下限 0.5h(默认 600s → 0.5h)。这样崩溃/强杀留下的锁, 其自动接管窗口与本次构建上限同量级,而不再是全局 6h —— 编译很快,兜底窗口也该短。
  • 锁工具 = 兄弟 skill task-worktree/scripts/task_worktree.py;找不到则降级为不加锁 并告警(保持本 skill 可独立搬运)。
  • ⚠ 从 _wt/<ID>-<slug>/ 反推持有者名,意味着"手工 lock acquire build --task <ID> 后再跑 /keil-build"仍幂等(同名即视为自己持有),不会自我死锁。
  • 退出码 4 的持有者名带 pw:(如 pw:refactor-logging)= 插件在 .dsh-worktrees/worktree/<name>/ 里跑的构建占着 UV4,不是某个 @task 任务 ID: 插件树没有台账,别拿它去 lock release <ID>(释放不掉别人的锁)、更别去杀 UV4 进程。正解 = 等那次构建自己 finally 释放(自持自释;崩溃则按自报 TTL 兜底),或本次加 --lock-wait <N> 排队; 想知道是哪棵树的谁:"$PY" "$TW" plugin list(缺联接再 plugin adopt <name>)→ 细节归 task-worktree skill F 节(插件桥)。
  • 只 build 锁自动;bench 锁仍须 Agent 手工持(它是会话作用域,跨 烧录→/serial-boot-test→探针多条命令,逐命令自动释放会让别的任务插进来扰动设备)。

构建副产物:%SystemDrive% 字面量垃圾目录(2026-10-26 两次复发 → 2026-10-28 定性并加守卫)

  • 症状:/keil-build(或 check_font_charset.py 这类 fontTools 工具)跑完,仓库根冒出未跟踪的 %SystemDrive%/ProgramData/…/Caches/(几个 .db,约 1 MB)= 本机缓存被按字面量原地复刻
  • 根因(2026-10-28 实测):run_code 的 Node 进程 process.env 是空的 → known-folder 可展开串展不开变成相对路径 → 按当前目录建在仓库根;同族还会冒出 %USERPROFILE%/AppData/Local/Temp
  • 清理有安全阀:⚠ 绝不用 cmd / PowerShell 直接删(rd /s /q %SystemDrive% 会被展开成 C:);只用 bash 引号形态,先 find '%SystemDrive%' -type l 预检再 rm -rf --。它是工具副产物,直接删、别 stash、更别提交
  • 两层防线均已落地可核验:.gitignore:167 挡入库 + 脚本 harden_windows_env() 回填环境变量、check_percent_literal_junk() 只告警不自动删;守卫是兜底不是许可,/keil-build 仍要走 ops_bash / PowerShell
  • 取证在前,处置在后(2026-10-28 实测,别一上来 rm -rf):ls -laR -- "%SystemDrive%" + du -sh "%SystemDrive%" 枚举内容与体积,再 find "%SystemDrive%" -printf "%T@ %p\n" | sort -rn | head -3 看最新 mtime —— 最近几分钟还在被重建 = 环境仍残缺(脚本会打 WARN: ENV_SPARSE:),先断源头(换 ops_bash/PowerShell 跑,或按「UV4.exe not found」一节把 env 传全)再删,否则删完立刻长回来;顺带确认仓库里只有这一个该名字的东西(find . -maxdepth 1 -name "*SystemDrive*")
  • 要留证据就搬走、别就地删 —— ⚠ shell 的 mv 搬不动它:mv "%SystemDrive%/..." 报 cannot stat(含 % 的目录名被当变量展开 / 吃掉,路径根本对不上)。改用 Python 遍历搬到仓库外的临时目录 —— os.walk + shutil.move(或 glob.glob("%SystemDrive%/*") 逐个 shutil.move 到 os.environ["TEMP"] 下);确认是纯工具副产物(几个 .db 缓存)才 rm -rf --
  • 入库排除要落成条目并核验:补 .gitignore 前先 grep -n "SystemDrive" .gitignore 定位精确原文行再 Edit(凭记忆写 old_string 必不匹配,2026-10-28 踩过)。本仓条目 = .gitignore:167 的 %SystemDrive%/,上面那段注释就是复发证据(新增条目也照此写明第几次复发、谁造的)。收尾核验 —— git status --porcelain 里该目录零命中(且别的未跟踪产物一个没少)、git check-ignore -v 落在那一行、git add -A --dry-run 零命中;绝不提交(把本机缓存塞进历史)

本节全文(逐字搬移、未改写,含命令与实测数字)→ systemdrive-junk-dir.md。动手前先读那篇。

本仓库

本 skill 的调用名 = /keil-build(技能调用名不是 shell 命令——原样敲进终端是 command not found / syntax error,可执行形态永远是下面这条命令行):在仓库根目录跑 & '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py—— bash / ops_bash 下要写成 '.venv/Scripts/python.exe' .dsh/skills/keil-build/scripts/keil_build.py (原因见「调用形态」)。 判定口径(与原 MDK-ARM/build.bat 同款;该文件已于 2026-10-10 删除,本 skill 即编译入口): 先删旧日志 → -b -j0 -o → 无 error: 且含 0 Error(s), 0 Warning(s)。 本 skill 另有超时保护、UV4 多级解析链(含 PATH 兜底)、失败只打报错行。 环境变量 DTU_KEIL_UV4(用户级,指向 UV4.exe)——注意它设了不等于本会话能看见,仍报退出码 2 时走「exit 2 实战闭环」那一节,别先去找用户。

铁律与陷阱(实测教训)

  • 任何代码改动后必须先编译验证(0 错误 0 警告)再谈烧录/提交——编译不过就进 硬件闭环只会把编译错误伪装成运行问题。只改 .c 里一行中文注释也算改动(2026-10-30 实测: 改注释 → /keil-build → 环境错 → 探测注入 → 10 s 通过):BOM/嵌套注释这类坑只在重编译那个 TU 时才暴露。
  • 警告也算失败:判 PASS 只认退出码 0,别只看"有没有 error:"(取退出码的管道陷阱见「调用形态」)。
  • MDK-ARM/*.uvprojx 每次构建后被 UV4 自动改写,改动一律不入库:UV4 会把工程 XML 归一化成本机风格(<Tag /> → <Tag></Tag>、XML 声明加 standalone="no")并把 PackID 降到本机装的 DFP 版本(属降级——提交后会把别人的 Pack 版本一起改掉)。 判断:git diff 里只见 XML 格式/PackID 差异而无源文件挂载增删 → git checkout -- <uvprojx> 丢弃;只有真的增删了源文件(grep 文件名计数变化)才提交。 同目录的 *.uvoptx / *.uvguix.* 是 IDE 个人设置,已在 .gitignore 里,不会露出来。
  • 构建后仓库根冒出 %SystemDrive%/ = UV4 / 字体工具在残缺环境(缺 SystemDrive、ProgramData) 里按未展开的 known-folder 字面量建出来的相对路径目录,不是任何人在飞的改动: rm -rf -- '%SystemDrive%'(bash 引号形态)删掉即可,严禁 rd /s /q %SystemDrive%——cmd 会把它 展开成 C:;.gitignore 已兜住 + 脚本已加 ENV_SPARSE/JUNK_DIR 守卫 → 见「构建副产物」一节。
  • 报错定位读日志别只看终端回显:本仓库 = MDK-ARM\build_log.txt。
  • exit 2 先分三种再动手(2026-10-10 / 2026-10-26 / 2026-10-30 实测):① bash 语法错——stderr 有 syntax error near unexpected token,是把 PowerShell 形态丢进了 ops_bash,换写法即可; ② 真没设 DTU_KEIL_UV4;③ 已设但本会话看不见(agent 的 shell 由常驻服务派生,服务启动之后 设的用户级变量不会进来;run_code 的 process.env 直接是空的)。 ②③ 一律走 「exit 2 实战闭环」 那一节:探测候选路径 + 读 HKCU\Environment, 再只往那一次调用的 env / --uv4 里注入命中路径重跑——别急着让用户去设,也别替他改全局。
  • 编码类报错(#870-D: invalid multibyte character sequence 等)先怀疑源文件 BOM/编码被改写,而不是代码本身(见仓库 AGENTS.md 编码铁律)。

与其它 skill 的衔接

  • build 锁的对面是谁(BUILD_LOCK_BUSY、退出码 4、pw:<name> 插件树、@task plugin list / plugin adopt、_wt/ 台账与合并回收)归 task-worktree skill(F 节 = DSH worktree 插件桥);换机/新克隆时 DTU_* 工具路径变量的设立与「会话不继承」坑 = new-machine-setup skill。
  • 烧录/回读 = stlink-flash skill;串口闭环 = serial-boot-test skill (其档案 flash_cmd/readback_cmd 即 stlink-flash 的入口命令)。
  • 收尾固定链(2026-10-26 实证):/keil-build → scripts/ram_baseline.py --update → 体积门禁盖戳 → 内存数字回填与 --scan → 复跑 ci_local 静态段, 直到钩子全绿且 0 错误 0 警告(RAM/栈口径见 mem-budget skill)。