diff --git a/.dsh/skills/keil-build/SKILL.md b/.dsh/skills/keil-build/SKILL.md index b152efb1..1298287d 100644 --- a/.dsh/skills/keil-build/SKILL.md +++ b/.dsh/skills/keil-build/SKILL.md @@ -1,7 +1,7 @@ --- name: keil-build -description: Keil MDK-ARM 命令行编译闭环(自带独立脚本 keil_build.py,纯标准库,拷到任何 Keil 项目即用):UV4 -b 构建 → "0 错误 0 警告"判定 → build_log 定位报错。封装入口约定(PowerShell 与 bash/ops_bash 两种调用形态及各自的 exit 2 归属)、退出码判读、UV4 改写工程文件的入库陷阱、构建副产物「%SystemDrive% 字面量垃圾目录」的定性与清理/入库排除。 -whenToUse: 需要编译 Keil MDK 工程、跑 `@build` 验证代码改动是否 0 错误 0 警告、解读 UV4 构建日志与退出码 2/4,或在 bash / `ops_bash` 里执行 `@build` 报语法错(syntax error near unexpected token),或构建后 `git status` 冒出 `%SystemDrive%` 目录时。 +description: "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% 字面量垃圾目录」的取证、清理与入库排除。" +whenToUse: "需要编译 Keil MDK 工程、跑 @build 验证代码改动(只改 .c 注释也算改动)是否「0 错误 0 警告」、@build 报 BUILD_ENV_ERROR 与 UV4.exe not found(退出码 2;DSH 会话看不见用户级 DTU_KEIL_UV4)、解读 UV4 构建日志、退出码 2/4 或 build 锁持有者名(含 pw: 前缀),在 bash / ops_bash 里执行 @build 报 syntax error near unexpected token,或构建后 git status 冒出 %SystemDrive% 目录时。" --- # keil-build — Keil 命令行编译(独立可运行 skill) @@ -22,8 +22,71 @@ whenToUse: 需要编译 Keil MDK 工程、跑 `@build` 验证代码改动是否 - 后台跑(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 的归属,该篇末尾记了前后口径 +⚠ **改本文件 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 真加载一次**再收口。 + +- **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](reference/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`,其余五个都不存在)。 + ```bash + 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. **只给这一次调用注入**再重跑(两种等价形态,二选一): + ```js + // 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 的实测就是 + 「改注释 → `@build` → 吃 rc=2 → 探测+注入 → 10 s 通过」。注释类坑(BOM 被剥 → `#870-D`、 + 注释里写 `**` 路径 → 9-D)只在**重编译那个 TU 时**才暴露,"以前 0 警告"不代表安全。 + ## build 锁:脚本自持自释(2026-10-12) **UV4 不能两个实例同时 `-b`,但构建锁不该由调用方"记得释放"**——那正是泄漏源: @@ -33,7 +96,7 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被 | 开关 | 作用 | |---|---| -| `--lock-task ` | 持有者名。默认 `$DTU_TASK_ID` → 从 cwd 的 `_wt/-/` 反推 `` → `build-cli` | +| `--lock-task ` | 持有者名。默认 `$DTU_TASK_ID` → 从 cwd 反推:`_wt/-/` → ``(`@task` 那批);`.dsh-worktrees/worktree//` → `pw:`(DSH worktree **插件**建的树,`plugin_tree_owner()`)→ 都没有才落 `build-cli` | | `--lock-wait ` | 锁被占时等待 N 秒(默认 0 = 立即失败,退出码 4) | | `--no-lock` | 完全不加锁(危险:并发 `-b` 互相干扰) | | `DTU_LOCK_HELD=build` | 环境变量:**调用方已持锁**时设它,本脚本跳过自持自释(`task-worktree done` 的门禁即如此用) | @@ -45,6 +108,11 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被 并告警**(保持本 skill 可独立搬运)。 - ⚠ 从 `_wt/-/` 反推持有者名,意味着"手工 `lock acquire build --task ` 后再跑 `@build`"仍**幂等**(同名即视为自己持有),不会自我死锁。 +- **退出码 4 的持有者名带 `pw:`(如 `pw:refactor-logging`)= 插件在 `.dsh-worktrees/worktree//` 里跑的构建占着 UV4,不是某个 `@task` 任务 ID**: + 插件树**没有台账**,别拿它去 `lock release `(释放不掉别人的锁)、更别去杀 UV4 进程。正解 = + 等那次构建自己 `finally` 释放(自持自释;崩溃则按自报 TTL 兜底),或本次加 `--lock-wait ` 排队; + 想知道是哪棵树的谁:`"$PY" "$TW" plugin list`(缺联接再 `plugin adopt `)→ 细节归 + `task-worktree` skill F 节(插件桥)。 - **只 build 锁自动;`bench` 锁仍须 Agent 手工持**(它是**会话作用域**,跨 烧录→`@test`→探针多条命令,逐命令自动释放会让别的任务插进来扰动设备)。 @@ -54,6 +122,9 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被 - 根因(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() 只告警不自动删;守卫是兜底不是许可,@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](reference/systemdrive-junk-dir.md)。动手前先读那篇。 ## 本仓库 @@ -65,12 +136,13 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被 判定口径(与原 `MDK-ARM/build.bat` 同款;该文件已于 2026-10-10 删除,本 skill 即 `@build`): 先删旧日志 → `-b -j0 -o` → 无 `error:` 且含 `0 Error(s), 0 Warning(s)`。 本 skill 另有超时保护、UV4 多级解析链(含 PATH 兜底)、失败只打报错行。 -环境变量 `DTU_KEIL_UV4`(用户级,指向 UV4.exe)。 +环境变量 `DTU_KEIL_UV4`(用户级,指向 UV4.exe)——注意**它设了不等于本会话能看见**,仍报退出码 2 时走「exit 2 实战闭环」那一节,别先去找用户。 ## 铁律与陷阱(实测教训) - **任何代码改动后必须先编译验证(0 错误 0 警告)再谈烧录/提交**——编译不过就进 - 硬件闭环只会把编译错误伪装成运行问题。 + 硬件闭环只会把编译错误伪装成运行问题。**只改 `.c` 里一行中文注释也算改动**(2026-10-30 实测: + 改注释 → `@build` → 环境错 → 探测注入 → 10 s 通过):BOM/嵌套注释这类坑只在重编译那个 TU 时才暴露。 - **警告也算失败**:判 PASS 只认退出码 0,别只看"有没有 error:"(取退出码的管道陷阱见「调用形态」)。 - **`MDK-ARM/*.uvprojx` 每次构建后被 UV4 自动改写,改动一律不入库**:UV4 会把工程 XML 归一化成本机风格(`` → ``、XML 声明加 `standalone="no"`)并把 @@ -83,15 +155,17 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被 `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 实测):① **bash 语法错**——stderr 有 +- **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 由常驻服务派生, - 服务启动**之后**设置的用户级环境变量不会自动进来。查 - `[Environment]::GetEnvironmentVariable('DTU_KEIL_UV4','User')`,有值就 - `$env:DTU_KEIL_UV4 = <该值>` 注入本会话再跑,别急着让用户去设。 + ② **真没设** `DTU_KEIL_UV4`;③ **已设但本会话看不见**(agent 的 shell 由常驻服务派生,服务启动**之后** + 设的用户级变量不会进来;`run_code` 的 `process.env` 直接是空的)。 + ②③ 一律走 **「exit 2 实战闭环」** 那一节:探测候选路径 + 读 `HKCU\Environment`, + 再**只往那一次调用**的 env / `--uv4` 里注入命中路径重跑——别急着让用户去设,也别替他改全局。 - 编码类报错(`#870-D: invalid multibyte character sequence` 等)先怀疑源文件 BOM/编码被改写,而不是代码本身(见仓库 AGENTS.md 编码铁律)。 + +- **build 锁的对面是谁**(`BUILD_LOCK_BUSY`、退出码 4、`pw:` 插件树、`@task plugin list` / `plugin adopt`、`_wt/` 台账与合并回收)归 `task-worktree` skill(F 节 = DSH worktree 插件桥);换机/新克隆时 `DTU_*` 工具路径变量的设立与「会话不继承」坑 = `new-machine-setup` skill。 ## 与其它 skill 的衔接 - 烧录/回读 = `stlink-flash` skill;串口闭环 = `serial-boot-test` skill