# 开发笔记(原 README) > 这是 0.1.0 发布前的开发记录:里程碑、宿主契约踩坑、19 个工具的实现细节、A1–A13 验收与宿主侧复验。 > 面向使用者的说明已移到仓库根目录的 [README.md](../README.md)。 # dsh-stm32 DSH 插件(宿主侧,无 UI):把 STM32F1 全系(F100/101/102/103/105/107;0.1.x 时只有 STM32F103C8T6 一块板)的 **环境探测 → .ioc 事务(含时钟)→ 生成工程 → 编译 → 烧录 → 调试** 变成一组可被模型调用的工具。 > **2026-10-30 更新**:F1 全系已支持,HAL 与寄存器两条路线都吃任意 F1 型号,时钟配置已并入 `stm32_ioc_plan`。本文件正文里凡是「只支持 / 只覆盖 C8T6」的句子都是当时的历史记录(按时间记账的要求保留原文),最新口径见 §28。 规格书:[`docs/dsh-stm32-plugin-规格.md`](docs/dsh-stm32-plugin-规格.md)(v0.2)。 ## 现在有什么 | 里程碑 | 内容 | 状态 | | --- | --- | --- | | M0 | CubeMX 无头调用实测(E1–E4) | 完成,结论见规格书 §8 | | M1 | 最小可加载插件 + `stm32_env_probe` + `stm32_generate` | 完成(端到端 6/6 通过,见下) | | M2 | 只读层:ioc_inspect / regs_read / doctor | 完成(三个工具都已实现并真机实测) | | M3 | build / flash / debug_run("只烧录不调试") | 完成(三个工具已实现,真板 e2e 全过) | | M4 | 事务层:ioc_plan / ioc_apply / 备份 / 回滚 / 调试会话租约 | 完成(六个工具已实现;宿主复验全过,`turn/end` 在我这轮说完话时自动释放探针,已实测) | | M5 | 打磨 | 完成(五条斜杠命令 + 随插件下发的 `stm32-workflow` SKILL;单测 6/6、真板 e2e 14/14) | | M6 | 补缺口:`stm32_project_create` / `stm32_code_patch` / 烧录守卫(A5)/ 首次构建自动配置 | 完成(离线 80 个用例 0 失败、契约 16/16 覆盖全部 15 个工具;A1–A13 逐条验收通过,A1 真机计数器 17 → 39,见下与规格书 §8.12) | ## 快速开始(从 GitHub 克隆) ```powershell git clone git@github.com:JingYu-er/dsh-stm32.git # 或 https://github.com/JingYu-er/dsh-stm32.git cd dsh-stm32 npm install --no-audit --no-fund # 只装 typescript npm run build # tsc -> lib/ npm test # 可选:497 个离线用例(34 个套件) ``` 然后把 `lib/` 用 `link:` 挂进 dsh 的 profile 并重启 dsh(见下一节)。宿主要求: | 依赖 | 用途 | 缺了会怎样 | | --- | --- | --- | | dsh(能加载 bundle 层) | 宿主 | 插件不会被加载 | | Node ≥ 20 | 构建 / 测试 | — | | `openocd`、`gdb-multiarch`、`arm-none-eabi-*`(MSYS2 UCRT64 或 ARM GNU Toolchain) | 编译 / 烧录 / 调试 | `stm32_doctor` 逐个列出缺哪个,并给出该装到哪 | | STM32CubeMX | `stm32_generate`、首次建工程 | 生成类工具不可用,其余照常 | | STM32Cube 固件包 F1(随 CubeMX 安装) | `stm32_project_create` 抄 Drivers | 建骨架报「找不到固件包」 | 默认路径在 [`src/data/search-roots.ts`](src/data/search-roots.ts)(msys64 优先),可用 `STM32_BIN_DIRS` / `STM32_CUBEMX_ROOT` 覆盖。型号覆盖 **STM32F1 全系**(F100/101/102/103/105/107,2026-10-30 起;本机验证板仍是 C8T6):启动文件 / 设备宏 / 链接脚本由 [`src/data/stm32f1.ts`](src/data/stm32f1.ts) + [`src/engine/hal-chip.ts`](src/engine/hal-chip.ts) 派生,引脚表内置一份、也可由本机 CubeMX `db/mcu/*.xml` 派生(见 §28)。 ## 开发 ```powershell cd dsh-stm32 npm install --no-audit --no-fund # 只装 typescript(devDependency) npm run build # tsc -> lib/ ``` 零运行时依赖:`lib/` 里只有本包自己的 JS,不 require 任何第三方包。 ### 为什么自己写类型 shim dsh 的 asar 只发布 `lib/**/*.js`;各包 `package.json` 的 `types` 指向的 `lib/types/index.d.ts` 并不随包分发。所以 `src/types.ts` 手写本项目用到的那一小部分 宿主契约(tools / subprocess / 句柄字段),保证离线可编译。 ## 安装到 dsh ```powershell # 1) 在 dsh 的 profile 目录里把这个包加为依赖——必须用 link:,不要用 file: # file: 在 nodeLinker=hoisted 下是"复制一份",改了 lib/ 重装才生效,且极易忘 cd "$env:USERPROFILE\.dsh\profiles\desktop" pnpm add "link:<克隆目录>\dsh-stm32" # 例如 link:D:\work\dsh-stm32 # 若此前用 file: 装过,先删掉复制出来的目录再装,否则 pnpm 可能保留旧副本: # Remove-Item node_modules\dsh-stm32 -Recurse -Force # 2) 在 profile 的 package.json 的 dsh.profile.bundles 末尾加 "dsh-stm32" # 3) 重启 dsh(bundle 层只在进程启动时加载) ``` 本包的 `cordis.patch.yml` 会把 `stm32` 这一行插入宿主组合。 安装状态(我这台机器,2026-10-22):已用 `link:` 装进 `~/.dsh/profiles/desktop`, `node_modules/dsh-stm32` 现在是 **Junction**(LinkType=Junction,Target=仓库目录), 并在该 profile 的 `dsh.profile.bundles` 末尾登记 `dsh-stm32`;profile 的 `package.json` 留有回滚备份 `package.json.bak-dsh-stm32`。 > 踩过的坑:一开始用 `file:` 装,pnpm 在 `nodeLinker: hoisted` 下把包**复制**成真实目录, > 于是改完 `src/` + 重新 `tsc` 后,宿主加载的仍是安装那一刻的旧 `lib/`——连重启两次都还是旧代码 > (表现为修好的 bug 依旧复现)。改用 `link:` 后 `lib/` 改动才会被宿主读到。 > 无论哪种装法,bundle 层只在进程启动时加载,改完 `lib/` 仍需重启 dsh 才生效(但不再需要重装)。 ## 宿主 subprocess 契约的一个坑 `ctx.subprocess.spawn(spec)` 的 `spec.cwd` **必须显式给字符串**。宿主的 `targetEnvironment()` 会直接对 `spec.cwd` 调 `includes()`,缺省时报 `Cannot read properties of undefined (reading 'includes')`(表现为"命令启动失败"而不是 "cwd 缺省")。`engine/subprocess-runner.ts` 因此总是填 `cwd = request.cwd ?? process.cwd()`, `test/smoke-env-probe.mjs` 的假宿主也复刻了这条严格性,避免本地测试掩盖真实宿主里的失败。 ## 宿主 skill 契约的一个坑 `ctx.skills.register()` 只校验 `name` / `description` / `invocation`,缺字段照样注册成功(skill 会出现在模型看到的目录里); 但那份定义在**被读取时**还会过一遍 `validateDefinition()`:`name` / `description` / `source` / `provider` / `content` 必须都是字符串,否则 `skill` 工具直接报 `loaded skill "…" source must be a string`。 `provider` 由 `register()` 兜底成 `runtime`,**`source` 必须自己给**(本插件给的是 `dsh-stm32:src/skill/stm32-workflow.ts`)。教训与 subprocess 的 `cwd` 那条同源: 注册期校验 ≠ 使用期校验,离线测试要按**使用期**校验来断言(`test/commands-units.test.mjs` 现在逐个字段断言)。 ## stm32_project_create:空目录 → 一条命令出工程 CubeMX 的 CLI 只产 `Core/` 下的 C 源,不产 CMake 工程文件(§8.3)。所以"从零建工程"要靠脚手架, 而这个工具的原则是**复刻、不发明**: | 产物 | 来源 | 为什么 | | --- | --- | --- | | `CMakeLists.txt`、`CMakePresets.json`、`cmake/**`、`STM32F103XX_FLASH.ld`、`.gitignore` | 随插件下发的**模板快照**(`templates/scaffold/`) | 与同版本 CubeMX GUI 新建工程逐字节一致,只把工程名占位符换掉 | | `.vscode/{launch,settings,c_cpp_properties}.json` | 同上 | 顺手把"① 只烧录 / ② 烧录并调试"两份配置也给你 | | `.ioc` | 同上(`demo.ioc` → `<工程名>.ioc`) | 8 个引脚 + RCC + SWD 已配好,`KeepUserCode=true` | | `Core/Src/{sysmem.c,syscalls.c}` | 模板快照 | GUI 会生成、CLI **不**生成;缺了 `cmake/stm32cubemx/CMakeLists.txt` 的源清单里就有两个不存在的文件,配置直接失败 | | `Drivers/**`、`startup_stm32f103xb.s`、`Core/Src/system_stm32f1xx.c` | 本机固件包 `/Repository/STM32Cube_FW_F1_V1.8.7` | 99 MB 的 ST 代码不内嵌进插件;实测与 GUI 工程同文件逐字节一致 | - 目标目录非空默认拒绝(宁可报错也不覆盖别人的工程),`overwrite: true` 才继续; - 工程名必须是 C 标识符(`^[A-Za-z_][A-Za-z0-9_]*$`),否则 `blink-1` 这种名字会进 `set(CMAKE_PROJECT_NAME …)` 和 `.elf` 名里; - 固件包 Drivers 里有 5392 个文件,4000+ 是 CMSIS 文档站、DSP/NN 示例与自测套件、BSP 板级组件—— 对编译 HAL 毫无用处,只会让工程变大、建工程变慢,所以**明确跳过**(`CMSIS/docs/`、`CMSIS/DSP/{Examples,DSP_Lib_TestSuite,Projects}/`、 `CMSIS/NN/{Examples,NN_Lib_Tests}/`、`BSP/`)。实测:629 个文件、**~2 s**; - 不产 `.mxproject`(那是 GUI 的工程元数据,伪造容易和 `.ioc` 不一致);`Core/` 下的 `main.c` 由 `stm32_generate` 生成。 ## stm32_ioc_inspect:只读解析 .ioc 把 `.ioc`(CubeMX 的纯文本配置:每行 `key=value`,值里的 `:` 写成 `\:`)解析成结构化摘要: MCU 型号、每个引脚的信号/GPIO 标签/模式/归类(gpio / debug / clock / virtual)、使能的外设与中断、 时钟频率(SYSCLK / AHX / APB1 / APB2、PLL 倍频与源)、工程设置(工具链 / 落位目录 / KeepUserCode / CoupleFile)。 毫秒级、不写盘、不起进程(`isConcurrencySafe: true`)。派生判断里最重要的两条: - **没启用 Serial Wire**(缺 `SYS_JTMS-SWDIO` / `SYS_JTCK-SWCLK`):CubeMX 会生成 `__HAL_AFIO_REMAP_SWJ_DISABLE()`,固件一上电就关掉 SWD,烧录后再也连不上(本项目实测踩过, 只能用 BOOT0 进系统 bootloader 救回来)。工具会直接报这条警告。 - `KeepUserCode=false`、`CoupleFile=true`、非 CMake 工具链、`MainLocation` 不是 `Src`/`Core/Src`: 都会影响"重新生成时 USER CODE 能不能保住"和落位,逐条报警。 ## stm32_generate:无头生成 + 落位 CubeMX 的 CLI 模式(`java -jar STM32CubeMX.exe -q