现状口径:/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 全过;字节卫生零漂移。
19 KiB
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「会话不继承用户级变量」同记此坑。
所以变量"已设但本会话看不见"是常态,不是用户没设。)
- 探测候选安装路径(逐个
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 - 读用户级来源确认"值本来就对"(同一轮里顺手做掉,别只靠猜路径):
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);本闭环只解决"这一次构建怎么跑起来", 报告里也必须这么写。 - 只给这一次调用注入再重跑(两种等价形态,二选一):
// 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)→已释放锁)。
- bash /
- 复核口径(三样齐了才算真跑过,缺一样就是没构建):脚本回显
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-worktreeskill 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-worktreeskill(F 节 = DSH worktree 插件桥);换机/新克隆时DTU_*工具路径变量的设立与「会话不继承」坑 =new-machine-setupskill。 - 烧录/回读 =
stlink-flashskill;串口闭环 =serial-boot-testskill (其档案flash_cmd/readback_cmd即 stlink-flash 的入口命令)。 - 收尾固定链(2026-10-26 实证):
/keil-build→scripts/ram_baseline.py --update→ 体积门禁盖戳 → 内存数字回填与--scan→ 复跑ci_local静态段, 直到钩子全绿且 0 错误 0 警告(RAM/栈口径见mem-budgetskill)。