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:
@@ -14,7 +14,7 @@ whenToUse: 要跑板级测试套件(CH395F/STORAGE/GD5F/RTC)回归、要开/
|
||||
实测 GD5F 整轮破坏性跑一次即多记一个 NAND 坏块(容量 95248→95200),
|
||||
计数持久化在 dhara journal checkpoint 头里,普通格式化恢复不回来。
|
||||
- 提交门禁:pre-commit 钩子 `test-suite-switch` 默认拦截测试构建
|
||||
(确需提交用 `ALLOW_TEST_BUILD=1` 显式放行)——**跑完务必改回全注释并 `@build`**。
|
||||
(确需提交用 `ALLOW_TEST_BUILD=1` 显式放行)——**跑完务必改回全注释并 `/keil-build`**。
|
||||
|
||||
## 一键回归(推荐路径,勿手工循环)
|
||||
|
||||
@@ -28,13 +28,13 @@ whenToUse: 要跑板级测试套件(CH395F/STORAGE/GD5F/RTC)回归、要开/
|
||||
- 脚本 try/finally **无条件还原 `test_config.h`**(含 Ctrl-C 路径)——这是它存在的核心
|
||||
理由:跑完忘还原 = 测试固件进库。`--keep-config` 仅调试用。
|
||||
- 退出码 0=全 PASS / 1=有 FAIL / 2=环境错误;4 套件真机一轮数分钟起,**仅回归窗口用,绝不挂 pre-commit**。
|
||||
- 手工循环时的顺序(每轮):改宏(只开一个)→ `@build` 0错0警 → 烧录 →
|
||||
- 手工循环时的顺序(每轮):改宏(只开一个)→ `/keil-build` 0错0警 → 烧录 →
|
||||
`serial-assistant` 采集(open→clear→烧录→wait-idle)→ 判读套件汇总行。
|
||||
|
||||
## 跑后检查清单(必做)
|
||||
|
||||
1. `test_config.h` 恢复全注释(用 run_all_suites.py 则已自动);
|
||||
2. `@build` 确认产品构建 0 错 0 警;
|
||||
2. `/keil-build` 确认产品构建 0 错 0 警;
|
||||
3. 若开过破坏性开关:核对开机日志容量数字(健康满值 96064 pages / 187 MB),
|
||||
掉了 → 按 `docs/测试说明/专项验证/存储维护与容量恢复.md` 走恢复动作;
|
||||
4. 提交前 `test-suite-switch` 门禁会再兜一道。
|
||||
|
||||
@@ -52,7 +52,7 @@ whenToUse: 要用 CubeMX 重新生成工程(改 .ioc / 外设配置)、改
|
||||
- 启用 FreeRTOS 后模板会生成 `USER CODE BEGIN 4 / 5` 区(默认空桩)——**钩子实现必须
|
||||
整体写进这两个区**,且 helper 函数放更早的区(同名两处定义 = 编译错)。
|
||||
- 生成文件行尾/末尾空行按工具自身风格,**勿用改写型钩子去修剪**(已豁免,勿"顺手格式化")。
|
||||
- 重生成后除审计脚本外,照常走 `@build`(0 错 0 警)+ 必要时 `@test` 闭环。
|
||||
- 重生成后除审计脚本外,照常走 `/keil-build`(0 错 0 警)+ 必要时 `/serial-boot-test` 闭环。
|
||||
|
||||
## 其它 CubeMX 项目怎么套
|
||||
|
||||
|
||||
@@ -110,10 +110,10 @@ version: 1.1.0
|
||||
2. 编号字面量与**不可判定引用绝对数**只降不升、不超 `undecidable_total_ceiling`;可判定比例不低于 `id_registry.json` 基线(86.0%)。
|
||||
3. 本族剩余站点复扫归零:有识别面族从 `ck.refs` 按族正则过滤字面量逐条打印;除冻结历史/原始证据行外无残留。
|
||||
4. `pre-commit run generated-fresh --all-files` PASS + 目检 svg diff 只有文案与框宽自适应(只在动过生成器时适用);`check_doc_links.py --all` 通过。
|
||||
5. `@build`:`BUILD=0`、0 错 0 警、`Program Size` 与改前同数(零行为改动证据,**本回合真跑**);相关主机组(如 `net_socket 457/457`)全绿。
|
||||
5. `/keil-build`:`BUILD=0`、0 错 0 警、`Program Size` 与改前同数(零行为改动证据,**本回合真跑**);相关主机组(如 `net_socket 457/457`)全绿。
|
||||
6. 真实 pre-commit 钩子全绿(如 `passed=33 failed=0`);提交笔内不含他道文件。
|
||||
7. 规范与台账已回写并复跑核数:自己先前写进规范的清点数字要么按最终实测改对,要么标明为动手前基线;「等你裁定」残留 = 0;「不做」裁定已在 §4.1.1 + §9.1 + 批次结论段落三处落地(原「变更记录」那一处已随 2026-10-28 清流水退场)。
|
||||
8. **无识别面族**:`git grep -nE '卡[ ]*[0-9]' -- .` 无输出(裸跑读 rc);自建扫描器的站点/文件数与提交正文一致;交付里明写「本族不在 check_ids 语料内、门禁判不到」与「刻意不进 `id_aliases.json`」的理由;§4.1.1 停发行 + §9.3 换算表 + 批次结论段落三处齐全,换算表行数 = 该族字面量数(实测 14),且写明旧号永久作废、不得重新取走。
|
||||
9. **二次清洗三道各有实测数**:同句重复短语 0 行、各空格形态先计数后归一(复扫 0)、固定正则残留 0;被代码头注释引用的设计约束章节仍在(`git diff` 里没被动过)。
|
||||
10. **叙述同步**:`AGENTS.md` / `docs/README.md` / `docs/任务规划/README.md` / `docs/任务规划/登记簿.md` 里旧口径(「三项」「卡 N」类)实测归零;`ci-artifacts/` 里本次 spec 与中间产物已删。
|
||||
11. 门禁复跑清单齐:`check_ids.py` plain + `--strict`、`check_doc_links.py --all`、`bom-guard`、`no-mojibake`、`git diff --check`、`@build` 或受影响单测——**每个数字标明是本回合实测**。
|
||||
11. 门禁复跑清单齐:`check_ids.py` plain + `--strict`、`check_doc_links.py --all`、`bom-guard`、`no-mojibake`、`git diff --check`、`/keil-build` 或受影响单测——**每个数字标明是本回合实测**。
|
||||
|
||||
@@ -25,6 +25,6 @@
|
||||
- **游标整型比较静默不生效**(字符串形态);抬完用 `--strict` 与 plain 双验。
|
||||
- **`git add` 目录路径带进别道文件**、`git add -A` 卷进在飞改动;撤销自己的改动只按文件点名 `git restore scripts/archive/id_gate/id_registry.json`,不碰同树其它道文件。
|
||||
- **amend 被 gitlint B1 拦**:正文行 >80,别把 ASCII 双引号写成正文首字符。
|
||||
- **改「只动注释」要拿机器证据**:`@build` 0 错 0 警且 `Program Size` 与改前**逐字节同数**(本回合真跑,引用历史提交正文自报的数字不算证据),别口头承诺。
|
||||
- **改「只动注释」要拿机器证据**:`/keil-build` 0 错 0 警且 `Program Size` 与改前**逐字节同数**(本回合真跑,引用历史提交正文自报的数字不算证据),别口头承诺。
|
||||
- **冻结历史与原始证据不改**:boot 串口日志等采集现场是冻结证据,改了就是篡改;历史「修改记录」行按裁定保持改名后形态(号已全仓退役,留一个查无此号的引用比留改过名的条目更坏)。
|
||||
- **`*.uvprojx` 抖动不入库**;`ci-artifacts/` 里的 spec 与扫描中间产物收尾删掉,别当交付物留下。
|
||||
|
||||
@@ -62,4 +62,4 @@
|
||||
- **同句重复短语**:编号位置补上的语义短语与原句已有短语撞车 → 扫「同一行里同一短语出现 ≥2 次」(实测 12 行,如 `.pre-commit-config.yaml:401`「运行参数段(「数据可信」运行参数段交付)」、`App/algo/README.md:110`「遥测快照发布(L2 发布侧,「数据可信」遥测快照发布)」)→ 改法是**把括号里的引用降级**成不含该短语的写法(「道D 交付项」「波次」「该点 DoD 原句」),逐处断言命中 = 1,别删信息;
|
||||
- **空格与引号形态归一**:同一语义短语可能有 `端子 / 子板变比确认` 与 `端子/子板变比确认` 两种形态 → **先 `git grep -c -F` 分别数**(实测带空格 8 处),再整文件归一,归一后复扫到 0(残留常藏在表格单元格里,逐行打印定位);引号形态同理;
|
||||
- **固定正则复扫**:`卡[ ]*[0-9]` 全仓无输出。⚠ 判 rc 时**不要把命令接在 `| head` 之后再读 `$?`**(那拿到的是 `head` 的 0,会把「还有残留」读成「已归零」)。
|
||||
9. **收尾复跑 + 清中间产物**:`check_ids.py`(plain 与 `--strict`)、`check_doc_links.py --all`、bom 与乱码两道(`pre-commit run bom-guard --files <改过的文件>`、`pre-commit run no-mojibake --files <改过的文件>`)、`git diff --check`;**纯注释/纯文档改动不算免编译**——仍跑一次 `@build` 取「0 错误 0 警告」且 `Program Size` 与改前同数(**本回合真跑**,引用历史提交正文自报的四个数不算),受影响的主机组单测照跑。最后**删掉 spec 与全部扫描中间产物**,别往 `ci-artifacts/` 再堆 log/txt。
|
||||
9. **收尾复跑 + 清中间产物**:`check_ids.py`(plain 与 `--strict`)、`check_doc_links.py --all`、bom 与乱码两道(`pre-commit run bom-guard --files <改过的文件>`、`pre-commit run no-mojibake --files <改过的文件>`)、`git diff --check`;**纯注释/纯文档改动不算免编译**——仍跑一次 `/keil-build` 取「0 错误 0 警告」且 `Program Size` 与改前同数(**本回合真跑**,引用历史提交正文自报的四个数不算),受影响的主机组单测照跑。最后**删掉 spec 与全部扫描中间产物**,别往 `ci-artifacts/` 再堆 log/txt。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: keil-build
|
||||
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% 目录时。"
|
||||
whenToUse: "需要编译 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)
|
||||
@@ -10,7 +10,7 @@ whenToUse: "需要编译 Keil MDK 工程、跑 @build 验证代码改动(只
|
||||
(纯标准库,任意 python3;拷整个 skill 目录到别的 Keil 项目即可用,不依赖仓库脚本)。
|
||||
|
||||
```powershell
|
||||
# cwd = 项目根目录;PowerShell 形态(AGENTS.md 里的 `@build` 写法)
|
||||
# cwd = 项目根目录;PowerShell 形态(AGENTS「构建与验证」的编译入口)
|
||||
& $PY ".dsh\skills\keil-build\scripts\keil_build.py"
|
||||
# 覆盖:--project X.uvprojx --uv4 UV4.exe --target 名字 --log 路径 --timeout 600
|
||||
```
|
||||
@@ -25,7 +25,7 @@ column 14` → **技能整份不加载**(`skill()` 读不到、也不进技能
|
||||
|
||||
## 调用形态:先认执行器是哪个 shell(2026-10-26 实测)
|
||||
|
||||
- 同一个 @build,PowerShell 与 bash 是两种写法:bash 侧要去掉前导 & 并把路径改正斜杠(照抄 AGENTS 的 PowerShell 形态进 ops_bash 会语法错秒退 exit 2)
|
||||
- 同一条编译命令,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 显式设了但路径无效 = 立即报错,不静默回退
|
||||
@@ -84,7 +84,7 @@ column 14` → **技能整份不加载**(`skill()` 读不到、也不进技能
|
||||
但 stderr 是 `syntax error near unexpected token`(见「调用形态」)。**先看 stderr 再决定排障方向**,
|
||||
两种 2 的处置完全不同。
|
||||
- **改 `.c` 注释(哪怕只改一行中文注释)也必须跑完这道构建验证**:2026-10-30 的实测就是
|
||||
「改注释 → `@build` → 吃 rc=2 → 探测+注入 → 10 s 通过」。注释类坑(BOM 被剥 → `#870-D`、
|
||||
「改注释 → `/keil-build` → 吃 rc=2 → 探测+注入 → 10 s 通过」。注释类坑(BOM 被剥 → `#870-D`、
|
||||
注释里写 `**` 路径 → 9-D)只在**重编译那个 TU 时**才暴露,"以前 0 警告"不代表安全。
|
||||
|
||||
## build 锁:脚本自持自释(2026-10-12)
|
||||
@@ -107,21 +107,21 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
||||
- 锁工具 = 兄弟 skill `task-worktree/scripts/task_worktree.py`;**找不到则降级为不加锁
|
||||
并告警**(保持本 skill 可独立搬运)。
|
||||
- ⚠ 从 `_wt/<ID>-<slug>/` 反推持有者名,意味着"手工 `lock acquire build --task <ID>`
|
||||
后再跑 `@build`"仍**幂等**(同名即视为自己持有),不会自我死锁。
|
||||
后再跑 `/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 手工持**(它是**会话作用域**,跨
|
||||
烧录→`@test`→探针多条命令,逐命令自动释放会让别的任务插进来扰动设备)。
|
||||
烧录→`/serial-boot-test`→探针多条命令,逐命令自动释放会让别的任务插进来扰动设备)。
|
||||
|
||||
## 构建副产物:`%SystemDrive%` 字面量垃圾目录(2026-10-26 两次复发 → 2026-10-28 定性并加守卫)
|
||||
|
||||
- 症状:@build(或 check_font_charset.py 这类 fontTools 工具)跑完,仓库根冒出未跟踪的 %SystemDrive%/ProgramData/…/Caches/(几个 .db,约 1 MB)= 本机缓存被按字面量原地复刻
|
||||
- 症状:/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() 只告警不自动删;守卫是兜底不是许可,@build 仍要走 ops_bash / PowerShell
|
||||
- 两层防线均已落地可核验:.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` 零命中;**绝不提交**(把本机缓存塞进历史)
|
||||
@@ -129,11 +129,11 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
||||
|
||||
## 本仓库
|
||||
|
||||
**`@build` = 本 skill**(2026-10-10 起为仓库文档口径;**`@build` 是文档别名、不是 shell 命令**——原样敲进终端是 command not found / syntax error,可执行形态永远是下面这条命令行;DSH 会话显式调用 `/keil-build`):在仓库根目录跑
|
||||
**本 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 即 `@build`):
|
||||
判定口径(与原 `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 实战闭环」那一节,别先去找用户。
|
||||
@@ -142,7 +142,7 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
||||
|
||||
- **任何代码改动后必须先编译验证(0 错误 0 警告)再谈烧录/提交**——编译不过就进
|
||||
硬件闭环只会把编译错误伪装成运行问题。**只改 `.c` 里一行中文注释也算改动**(2026-10-30 实测:
|
||||
改注释 → `@build` → 环境错 → 探测注入 → 10 s 通过):BOM/嵌套注释这类坑只在重编译那个 TU 时才暴露。
|
||||
改注释 → `/keil-build` → 环境错 → 探测注入 → 10 s 通过):BOM/嵌套注释这类坑只在重编译那个 TU 时才暴露。
|
||||
- **警告也算失败**:判 PASS 只认退出码 0,别只看"有没有 error:"(取退出码的管道陷阱见「调用形态」)。
|
||||
- **`MDK-ARM/*.uvprojx` 每次构建后被 UV4 自动改写,改动一律不入库**:UV4 会把工程
|
||||
XML 归一化成本机风格(`<Tag />` → `<Tag></Tag>`、XML 声明加 `standalone="no"`)并把
|
||||
@@ -169,6 +169,6 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
||||
- **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 实证):`@build` → `scripts/ram_baseline.py --update` →
|
||||
- 收尾固定链(2026-10-26 实证):`/keil-build` → `scripts/ram_baseline.py --update` →
|
||||
体积门禁盖戳 → 内存数字回填与 `--scan` → 复跑 `ci_local` 静态段,
|
||||
直到钩子全绿且 0 错误 0 警告(RAM/栈口径见 `mem-budget` skill)。
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 本文由 keil-build 的 SKILL.md「调用形态:先认执行器是哪个 shell(2026-10-26 实测)」一节逐字外置而来(渐进展开:主文件只留索引与指针)。步骤号沿用原文,未重编。
|
||||
|
||||
同一个 `@build`,**PowerShell 与 bash 是两种写法**;`ops_bash` / `run_code` 起的是
|
||||
同一条编译命令,**PowerShell 与 bash 是两种写法**;`ops_bash` / `run_code` 起的是
|
||||
`/usr/bin/bash -c`,**不认 PowerShell 形态**,照抄仓库 `AGENTS.md` 会直接语法错退出。
|
||||
|
||||
| 执行器 | 正确写法(cwd = 仓库根) |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 本文由 keil-build 的 SKILL.md「构建副产物:`%SystemDrive%` 字面量垃圾目录(2026-10-26 两次复发 → 2026-10-28 定性并加守卫)」一节逐字外置而来(渐进展开:主文件只留索引与指针)。步骤号沿用原文,未重编。
|
||||
|
||||
**症状**:`@build`(或 `scripts/check_font_charset.py` 这类 fontTools 字体工具)跑完,仓库根
|
||||
**症状**:`/keil-build`(或 `scripts/check_font_charset.py` 这类 fontTools 字体工具)跑完,仓库根
|
||||
冒出未跟踪目录 `%SystemDrive%/ProgramData/Microsoft/Windows/Caches/`,里面是几个 `.db`
|
||||
(`cversions.2.db` + `{6AF0698E-…}` 图标缓存 + `{DDF571F2-…}` 字体缓存,合计约 1 MB)——
|
||||
就是本机 `C:\ProgramData\Microsoft\Windows\Caches` 那份缓存被**按字面量原地复刻**了一份。
|
||||
@@ -41,4 +41,4 @@ node -e "console.log(Object.keys(process.env).length)" # run_code 里 = 0(
|
||||
并把 `TMP`/`TEMP` 指到真实目录;命中即打 `WARN: ENV_SPARSE:`。构建后再跑
|
||||
`check_percent_literal_junk()`,看到 `%SystemDrive%`/`%USERPROFILE%` 一类字面量目录就打
|
||||
`WARN: JUNK_DIR:`(**只告警不自动删**——删目录不该由构建脚本代劳)。守卫是兜底不是许可:
|
||||
**`@build` 仍要走 `ops_bash` / PowerShell(继承完整环境)**,非在 Node 里 spawn 就把 `env` 传全。
|
||||
**`/keil-build` 仍要走 `ops_bash` / PowerShell(继承完整环境)**,非在 Node 里 spawn 就把 `env` 传全。
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
(环境变量**显式设了但路径无效 = 立即报错,不静默回退**)
|
||||
日志 --log > 档案 "build_log" > <工程目录>\\build_log.txt
|
||||
|
||||
判定口径(实测教训沉淀;原仓库 MDK-ARM/build.bat 已删除,本脚本即 @build):
|
||||
判定口径(实测教训沉淀;原仓库 MDK-ARM/build.bat 已删除,本脚本即仓库编译入口):
|
||||
1. 先删旧日志——UV4 -o 只在真跑了构建才写日志,残留旧日志会被当成本次结果
|
||||
2. UV4 单实例:IDE 开着时 -b 可能被转发给 IDE(退出码丢失)→ 只 WARN 不杀进程
|
||||
3. PASS = 日志存在 + 无 "error:"(大小写不敏感)+ 含 "0 Error(s), 0 Warning(s)"
|
||||
@@ -157,7 +157,7 @@ def harden_windows_env():
|
||||
if filled or expanded:
|
||||
print("WARN: ENV_SPARSE: 子进程环境残缺/含未展开字面量 —— 已回填[{}] 已展开[{}]".format(
|
||||
", ".join(sorted(filled)) or "-", ", ".join(sorted(expanded)) or "-"))
|
||||
print(" 正解:用继承**完整环境**的 shell 跑 @build(ops_bash / PowerShell),"
|
||||
print(" 正解:用继承**完整环境**的 shell 跑编译(ops_bash / PowerShell),"
|
||||
"别从 Node spawn(空 process.env)直接起 UV4 或字体门禁脚本")
|
||||
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ PC 上编译运行与 MCU **同一份** UI 源码(CMake + MSVC + SDL2,x64,
|
||||
|
||||
## UI 改动回归 → 用 `ui-verify` skill(勿在此零散手跑)
|
||||
|
||||
5 步门禁 = 构建 → `check_menu_rows.py`(菜单 8 状态像素 + 池基线)→ `check_content_pages.py`(文本断言 + 66 张黄金参考 + 往返池)→ `@build` → `coding-standard`。
|
||||
5 步门禁 = 构建 → `check_menu_rows.py`(菜单 8 状态像素 + 池基线)→ `check_content_pages.py`(文本断言 + 66 张黄金参考 + 往返池)→ `/keil-build` → `coding-standard`。
|
||||
RemoDispBus 推流回归单独跑:`.venv\Scripts\python.exe simulator/tests/check_remo_sink.py`。
|
||||
黄金参考 `tests/ref/*.bin` **随仓库入库**(`.gitignore` 在全仓 *.bin 兜底之后又显式反选,2026-10-22 起;缺基准 = 新克隆跑 `ui-golden` 必 FAIL):不入库的只是派生目检图 `ref/*.png`,换机后先 `check_content_pages.py --update` 生成,再 `regen_ref_png.py` 出目检 PNG。
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: mem-budget
|
||||
description: 嵌入式 RAM/栈预算核算工作流:Keil .map/.htm 静态报告(ram_report/stack_report)+ 串口运行时水位叠加 + 权威构建后的台账回填(ram_baseline.py --update 只往 ram_baseline.tsv 追加趋势行、--write 才同步 RAM预算台账 受管区、check_mem_numbers.py --write 同步文档快照区)+ DBG 日志成本模型(每条约 90B Flash/216B 栈)+ 跨树(worktree)测量的身份口径——脚本按 __file__ 定位产物、什么才算一次新测量、多条链同涨同一数值的共享帧/幻影环归因法。"改任务/缓冲/栈之前必须核算"、"这个数属于哪棵树、哪次构建"与"内存/Flash 数字钩子红了、提交前台账怎么回填"的固定动作和判读口径。
|
||||
whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报 L6406E 或 RAM 溢出、串口 [STACK] 水位余量低于底线、要回答"这个改动花多少 RAM/Flash";以及要复算或比对另一个 worktree 的栈数字、独立验证某个栈回归该记在哪一笔提交上、判断多次启动的栈峰值抖动算不算劣化时;还有 @build 之后要把内存数字回填进台账/文档、纠结该跑 ram_baseline.py --write 还是 --update、提交时 mem-budget / mem-numbers 两条钩子报"与 .map 不一致"时。
|
||||
whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报 L6406E 或 RAM 溢出、串口 [STACK] 水位余量低于底线、要回答"这个改动花多少 RAM/Flash";以及要复算或比对另一个 worktree 的栈数字、独立验证某个栈回归该记在哪一笔提交上、判断多次启动的栈峰值抖动算不算劣化时;还有 /keil-build 之后要把内存数字回填进台账/文档、纠结该跑 ram_baseline.py --write 还是 --update、提交时 mem-budget / mem-numbers 两条钩子报"与 .map 不一致"时。
|
||||
---
|
||||
|
||||
# mem-budget — RAM/栈预算核算
|
||||
@@ -9,7 +9,7 @@ whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报
|
||||
## 固定动作(改前、改后各跑一次)
|
||||
|
||||
```powershell
|
||||
# cwd = 项目根;先 @build 保证 .htm/.map 是最新的(产物按脚本自身位置定位,见下节)
|
||||
# cwd = 项目根;先 /keil-build 保证 .htm/.map 是最新的(产物按脚本自身位置定位,见下节)
|
||||
& '.venv\Scripts\python.exe' ".dsh\skills\mem-budget\scripts\mem_check.py"
|
||||
# 等价于分别跑:
|
||||
# scripts/ram_report.py (.map → RAM/Flash 分区与按文件占用表)
|
||||
@@ -67,7 +67,7 @@ whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报
|
||||
| `docs/内存管理/RAM预算台账.md` 的 `<!-- MEM_BUDGET:BEGIN/END -->` 受管区 | `ram_baseline.py --write` | 从 .map 重渲染区域水位 + 子系统配额对照(打印「已同步台账受管区。」或「台账受管区已是最新。」) |
|
||||
| `docs/内存管理/RAM使用分析与优化方案.md` 的 `<!-- MEM_SNAPSHOT:BEGIN/END -->` 快照区 | `check_mem_numbers.py --write` | 从**同一份** .map 重渲染内存/Flash 快照表(打印「已更新内存快照:…」) |
|
||||
|
||||
**AGENTS.md 与台账 §4 那句「每次 `@build` 后跑 `scripts/ram_baseline.py --update`」喂的是趋势行,不等于回填受管区。**
|
||||
**AGENTS.md 与台账 §4 那句「每次 `/keil-build` 后跑 `scripts/ram_baseline.py --update`」喂的是趋势行,不等于回填受管区。**
|
||||
实测代价:照那句跑完 `--update` 就提交 → `mem-budget`、`mem-numbers` 两条钩子当场红。
|
||||
`mem-budget` 的 entry 是 `python scripts/ram_baseline.py --gate`(`.pre-commit-config.yaml:571`)——它在 92% 阈值之外
|
||||
还核**台账受管区与 .map 的一致性**,漂移即 `[FAIL] 台账受管区与 .map 不一致(区域水位或子系统配额表漂移)`
|
||||
@@ -144,7 +144,7 @@ git add docs/内存管理/RAM预算台账.md docs/内存管理/RAM使用分析
|
||||
3. 树身份已按 `tree/commit + 时刻 + 产物来源` 写进提交正文/台账,而不是只留在终端输出里;
|
||||
4. 结论里互比的数字必须来自**同一棵树同一次链接**;跨树比较要各自给出树与时刻;
|
||||
5. 涉及劣化判定时注明依据的是静态最坏链(硬线)还是运行时峰值(趋势),并已计入跨启动抖动。
|
||||
6. 要入库的数字:本轮已按「`@build` → `ram_baseline.py --update` → `ram_baseline.py --write` →
|
||||
6. 要入库的数字:本轮已按「`/keil-build` → `ram_baseline.py --update` → `ram_baseline.py --write` →
|
||||
`check_mem_numbers.py --write`」回填,并逐字自跑 `ram_baseline.py --gate` 与 `check_mem_numbers.py --scan` 各返 0;
|
||||
台账 / 文档 / tsv 的改动与代码同一笔入库,**未手改数字、未用 `--no-verify`**。
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
(`ram_baseline.py:232`),**拦下来的是受管区漂移**。所以"钩子绿"绝不等于"趋势行也补了",反之亦然,各查各的。
|
||||
- **`--gate` 无 .map → SKIP,而脚本自己写明「SKIP 不等于 PASS」**(`ram_baseline.py:177`):
|
||||
新开的 task-worktree 从没在这棵树上构建过时必然走到这里;同一状态下 `--write` 直接
|
||||
`[FAIL] 找不到 .map 事实源,先 @build`(`ram_baseline.py:250`)——不是代码缺陷,是这棵树还没测过体积。
|
||||
`[FAIL] 找不到 .map 事实源,先 /keil-build`(`ram_baseline.py:250`)——不是代码缺陷,是这棵树还没测过体积。
|
||||
无需 Keil 的强制兜底是 `scripts/check_size_gate.py --local`(= `scripts/ci_local.py` 的体积门禁步)与 CI 的 `--ci`。
|
||||
- **`--map <path>` 只换读、不换写**:`LEDGER`(`ram_baseline.py:45`)与 `TARGETS`(`check_mem_numbers.py:53-54`)
|
||||
都由 `REPO_ROOT = Path(__file__).resolve().parent.parent` 算出。拿主仓脚本配 `--map <另一棵树>/…map` 去 `--write`,
|
||||
|
||||
@@ -36,5 +36,5 @@ cmake --build simulator/build --config Debug # 0 警告
|
||||
- **禁止**把源文件复制进 `simulator/`——两端共用 `Drivers/BSP/` 原件(UI_界面制作流程.md C7)
|
||||
- 只注册 `.c`(含 `.s`);`.h` 靠 include 路径找到,不加工程
|
||||
- 漏注册的典型症状:一端编译失败(undefined reference)或一端行为悄悄停留在旧逻辑
|
||||
- 注册完成后跑回归:UI 文件 → `ui-verify` skill;其他模块 → `@test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`)
|
||||
- 注册完成后跑回归:UI 文件 → `ui-verify` skill;其他模块 → `/serial-boot-test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`)
|
||||
- 改过 `CMakeLists.txt` 后需重新 `cmake -B build` 配置
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
status 全状态(单条 JSON)
|
||||
log --tail N --grep P 读日志(先过滤后截尾;-i 忽略大小写 / --regex 正则)
|
||||
clear 清空日志(原子截断 + 代际递增)
|
||||
wait-idle 等 RX 静默(@test 用;--settle-ms / --max-ms / --first-timeout-ms)
|
||||
wait-idle 等 RX 静默(/serial-boot-test 用;--settle-ms / --max-ms / --first-timeout-ms)
|
||||
start / stop / restart daemon 生命周期(其余命令会自动拉起 daemon)
|
||||
selftest 无头自检(行拆分/解码回退/截头算法,无需硬件)
|
||||
|
||||
@@ -615,10 +615,10 @@ class Daemon:
|
||||
def wait_idle(self, settle_s, max_s, first_timeout_s=None):
|
||||
"""等 RX 静默,返回 reason ∈ {idle, no_data, timeout}。
|
||||
|
||||
2026-10-09 修三处(旧实现会让 @test 在"板子全程没输出"时误判收尾):
|
||||
2026-10-09 修三处(旧实现会让 /serial-boot-test 在"板子全程没输出"时误判收尾):
|
||||
1. no_data 判据:旧实现比的是"进入函数时 daemon 累计 rx_bytes 是否为 0"。
|
||||
daemon 常驻(本机实测 rx_bytes=1.2MB)→ 永远 >0 → 板子一个字节都不发
|
||||
也返回 idle:true,@test 的 no_data 拦截形同虚设。现按**本次增量**判。
|
||||
也返回 idle:true,/serial-boot-test 的 no_data 拦截形同虚设。现按**本次增量**判。
|
||||
2. 首字节门:旧实现从进入即开始计静默时间,板子第一行比 settle_ms 来得
|
||||
晚就提前收尾("开机日志缺尾巴"的另一半成因)。现首字节到达前不启动
|
||||
settle 计时,改由 first_timeout_s 兜底(默认 = max_s)。
|
||||
@@ -1353,7 +1353,7 @@ def build_parser():
|
||||
|
||||
sub.add_parser("clear", parents=[common], help="清空日志(代际 +1)")
|
||||
|
||||
p = sub.add_parser("wait-idle", parents=[common], help="等 RX 静默(@test 用)")
|
||||
p = sub.add_parser("wait-idle", parents=[common], help="等 RX 静默(/serial-boot-test 用)")
|
||||
p.add_argument("--settle-ms", type=int, default=8000,
|
||||
help="静默判据毫秒(默认 8000:boot 有 3~4s 自然静默间隙)")
|
||||
p.add_argument("--max-ms", type=int, default=30000, help="最长等待毫秒(默认 30000)")
|
||||
|
||||
@@ -14,9 +14,9 @@ whenToUse: 需要向目标板烧录或复位后,用串口日志自动判定运
|
||||
| 脚本 | 作用 |
|
||||
|---|---|
|
||||
| `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | 一键闭环:open+clear → 烧录 → wait-idle → 提取 boot log → 烧录回读校验 → 标记校验。串口助手按 `$DSH_SERIAL_TOOL` > 同目录 > `../serial-assistant/scripts/serial_tool.py` 解析 |
|
||||
| `.dsh/skills/serial-boot-test/scripts/verify_log.py` | 对任意日志文件做标记校验(Required/Forbidden/锚点计数/顺序/耗时),可单独用。`--exact` 做**整行 `^…$` 锚定**(子串口径照不出"行还在但行首被吃掉/行内被插字节"那一类损坏)、`--pair` 做跨复位配对、`--allow-residual/--head-char` 服务崩溃注入腿、`--selftest` 跑内置判别用例(**证明这套判据不是空跑**)。四个行级开关(`--exact` / `--pair` / `--allow-residual` / `--head-char`)同时接进了 `run_boot_test.py`,@test 闭环直接可用 |
|
||||
| `.dsh/skills/serial-boot-test/scripts/verify_log.py` | 对任意日志文件做标记校验(Required/Forbidden/锚点计数/顺序/耗时),可单独用。`--exact` 做**整行 `^…$` 锚定**(子串口径照不出"行还在但行首被吃掉/行内被插字节"那一类损坏)、`--pair` 做跨复位配对、`--allow-residual/--head-char` 服务崩溃注入腿、`--selftest` 跑内置判别用例(**证明这套判据不是空跑**)。四个行级开关(`--exact` / `--pair` / `--allow-residual` / `--head-char`)同时接进了 `run_boot_test.py`,/serial-boot-test 闭环直接可用 |
|
||||
|
||||
> **`@test` 是 AGENTS.md 的文档别名,不是 shell 命令**(同 `@build` / `@task`,见 AGENTS「构建与验证」总口径):可执行形态 = 上表 `run_boot_test.py` 命令行(仓库根跑),DSH 会话显式调用 `/serial-boot-test`;项目配置与判据见本仓 `docs/测试说明/流程与规范/自动化测试流程.md`。
|
||||
> **`/serial-boot-test` 是 DSH 会话的技能调用名、不是 shell 命令**(同 `/keil-build`,见 AGENTS「构建与验证」总口径):可执行形态 = 上表 `run_boot_test.py` 命令行(仓库根跑);项目配置与判据见本仓 `docs/测试说明/流程与规范/自动化测试流程.md`。
|
||||
|
||||
> **判据取向(重要,会复发)**:`--exact` 默认**只锚文本、不钉长度**——字节长度属于某一份构建,钉死它会在无辜改动上假红(`@@LEN=<n>` 是显式逃生口,不是默认)。子串 `--required` 不能用于"可能丢行首/被插字节"的行。来龙去脉与固定动作见本仓 `docs/测试说明/专项验证/启动日志原子性复验流程.md`。
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ def _ftp_quit(ftp):
|
||||
def _prune_out_dir(out_dir, keep):
|
||||
"""只保留最近 N 份产物(`boot_log_*.txt` / `selftest_log_*.log`)。
|
||||
|
||||
这些文件按 `%Y%m%d_%H%M%S` 命名、每次 @test 各留一份,旧实现从不清理——
|
||||
这些文件按 `%Y%m%d_%H%M%S` 命名、每次 /serial-boot-test 各留一份,旧实现从不清理——
|
||||
本机实测 out 目录已堆到 31 份 boot_log + 7 份 selftest_log(2026-10-10)。
|
||||
文件名含时间戳 → 按名字排序即按时间排序。
|
||||
"""
|
||||
@@ -294,7 +294,7 @@ def _fetch_selftest_log(device_ip, out_dir, stamp, ftp_user="admin", ftp_pass="a
|
||||
2026-10-02 台架修复:原实现只 `except OSError`,而 ftplib 的 error_temp /
|
||||
error_perm 继承自 Exception 而非 OSError —— 台账"仅故障才写",干净设备上
|
||||
/log/selftest.log 根本不存在,RETR 返回 450 直接抛出未捕获异常,令整个
|
||||
`@test --require-selftest-pass` 在最后一步崩掉(而它本该是 PASS 路径)。
|
||||
`run_boot_test.py --require-selftest-pass` 在最后一步崩掉(而它本该是 PASS 路径)。
|
||||
现把"设备侧无此文件"单列为正常路径(INFO),其余 FTP 错误保留 WARN。
|
||||
"""
|
||||
local = Path(out_dir) / "selftest_log_{}.log".format(stamp)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""verify_log.py - 对抓取的 MCU 调试日志做标记校验(纯标准库,2026-09-20 替代 verify_log.ps1)。
|
||||
|
||||
用于 @test 闭环:烧录后从串口助手日志提取的开机日志交给本脚本 grep 标记,
|
||||
用于 /serial-boot-test 闭环:烧录后从串口助手日志提取的开机日志交给本脚本 grep 标记,
|
||||
输出结构化判定(格式与旧 PowerShell 版逐字一致,下游文档/习惯不受影响)。
|
||||
|
||||
用法:
|
||||
@@ -16,7 +16,7 @@
|
||||
[--require-order 'App init OK>Network Init>net init: OK']
|
||||
[--done-marker 'net init: OK'] [--max-boot-ms 8000] [--json]
|
||||
|
||||
退出码(2026-10-27 task-32 起把 2 讲清楚;@test 那层的码不变):
|
||||
退出码(2026-10-27 task-32 起把 2 讲清楚;/serial-boot-test 那层的码不变):
|
||||
0 = PASS(所有 Required 标记找到,Forbidden 零命中,汇总行要求满足)
|
||||
1 = FAIL(**设备侧真有坏消息**:缺 Required / 命中 Forbidden / 汇总行不满足 / 顺序或耗时超限)
|
||||
2 = **测量没成立**(日志不存在或不可读;或开了 --max-boot-count 却零启动锚点 = 采集器没开、
|
||||
|
||||
@@ -59,7 +59,7 @@ PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktr
|
||||
|
||||
```bash
|
||||
"$PY" "$TW" new N21 dhara-isfree --needs-bench # 把新会话工作目录切到打印出来的 _wt/N21-dhara-isfree/
|
||||
"$PY" "$TW" lock acquire bench --task N21 # 烧录 / @test / 板级套件
|
||||
"$PY" "$TW" lock acquire bench --task N21 # 烧录 / /serial-boot-test / 板级套件
|
||||
"$PY" "$TW" lock release bench --task N21 --bench-pass
|
||||
"$PY" "$TW" done N21 # 回主工作树(铁律 1);先 --dry-run 读全量
|
||||
```
|
||||
@@ -110,7 +110,7 @@ git add <上面刷出来的基线/快照文件> # 显式列文件,别 -A
|
||||
git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.md 边界)
|
||||
```
|
||||
|
||||
为什么必然要补 → 陷阱「`done` 的门禁跑在 `@build` 之前」。合并解过冲突时,**合并提交期 pre-commit 只查冲突文件**(→ G 节第 3 条)→ 落主干后必须另跑全量。
|
||||
为什么必然要补 → 陷阱「`done` 的门禁跑在 `/keil-build` 之前」。合并解过冲突时,**合并提交期 pre-commit 只查冲突文件**(→ G 节第 3 条)→ 落主干后必须另跑全量。
|
||||
**注释/文档-only 但碰了 .c/.h 的短链**:先比 `Program Size` 与合并前是否逐项相同——相同就只 `ram_baseline.py --update` 重写体积凭证(`scripts/size_attestation.json` 跟着补提交笔走),**别 `--write` 动受管区**(等于给在飞分支造冲突面);数字变了才走上面全链。**已命令化**:`"$PY" "$TW" postmerge`——新鲜直接跳过;红则先短链,再用只读 `check_mem_numbers` 复检裁定是否升全链;末尾跑 `--all` 并列出待提交文件(不代提交)。上面的手工全链仍是权威兜底。
|
||||
6. **重跑 `done` 前主树必须干净**,否则 `失败:主工作树不干净(…)——合并会把未提交改动卷进来` → 第 5 步产物先提交。分支已并时重跑打印 `[i] task/<ID>-<slug> 已合入 <base>,跳过合并,直接清场`——**该路径在门禁之前就返回,不会替你复绿**:上一次门禁没跑到的尾巴(主树 `ci_local.py` + `check_doc_links.py --all`;体积/文档那半可 `postmerge` 一把跑)必须自己补。主树脏且那份脏不是你的 → **C2**。
|
||||
7. 清场后再敲一次 `done <ID>` 得 `前置/环境错误:任务 <ID> 不存在(缺台账 …json)`(exit 2)——**不是失败**:清场最后一步 unlink 了台账。**判完成以 `git log` 为准**。
|
||||
@@ -153,7 +153,7 @@ git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.md
|
||||
|
||||
1. **`done` 只能在主工作树执行**——主干分支检出在主树,git 不允许同一分支两处检出。
|
||||
2. **`done` 不 push**:它结束于本地合入 + 门禁全绿;推送需用户明确许可。
|
||||
3. **`build` 与 `bench` 都必须串行**(UV4 不能两个实例同时 `-b`;COM6、ST-LINK、设备 IP `192.168.1.100` 各一份)。`build` 锁由 **@build 自持自释**(`keil_build.py`)→ 调用方**不要**手工 acquire/release;`bench` 锁**必须手工** acquire → 烧录 / `@test` → release `--bench-pass`。非登记持有者怎么放 → B 节;插件树 → F 节。
|
||||
3. **`build` 与 `bench` 都必须串行**(UV4 不能两个实例同时 `-b`;COM6、ST-LINK、设备 IP `192.168.1.100` 各一份)。`build` 锁由 **/keil-build 自持自释**(`keil_build.py`)→ 调用方**不要**手工 acquire/release;`bench` 锁**必须手工** acquire → 烧录 / `/serial-boot-test` → release `--bench-pass`。非登记持有者怎么放 → B 节;插件树 → F 节。
|
||||
4. **单写者文档**:`docs/任务规划/`(整个文件夹)、`AGENTS.md` 任务表块、`MDK-ARM/*.uvprojx`、`.pre-commit-config.yaml`、内存快照 —— 任务期**不在任务树里改**,留到 `done` 统一处理;用户显式豁免时(在任务树写、由 done 合回)要在交付报告里点名这条例外。
|
||||
5. **清场必须先摘联接**:脚本先 `os.rmdir` 掉 `.venv` / `simulator/SDL2` 联接再 `git worktree remove`。手工拆树前必跑 **E 第 3 步**预扫拿到 `REPARSE: none`——否则 git 可能递归进联接把主树 `.venv` 一起删掉。
|
||||
6. **活跃任务硬上限 8 = 脚本常量 `MAX_CONCURRENT`(`task_worktree.py:30`)——唯一事实源**:报错文案与 `new` 的插件树提示都插值它,AGENTS.md / 方案文档 / frontmatter / A 节的数字全是引用同步。改这个数字 → 全形态扫描走 `stale-doc-count-sweep` skill + 隔离小仓验证上限真生效;每棵磁盘代价按实测口径重述、别照抄旧数(细节 → 验证清单末条)。满了 exit 2 → A;悬挂台账 → E 第 6 步。
|
||||
@@ -163,11 +163,11 @@ git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.md
|
||||
|
||||
## 陷阱与验证(收工前各扫一遍)
|
||||
|
||||
- [reference/pitfalls.md](reference/pitfalls.md):全部实测坑逐条(含 `--dry-run` 不保护清场段、门禁跑在 `@build` 之前、未跟踪文件在 `git log` 与合并里都不留痕、并集合并残留、新树从未构建 → 体积类门禁必然红、注释-only 也刷红体积凭证、「已合入」快路径在门禁前返回不替你复绿、`--force` 只覆盖四类检查、联接不是 symlink(`core.symlinks=false`)、锁不绑进程存活、文件工具先读后改按绝对路径记账、中文门禁回显乱码但 rc 可信)。收工前扫一眼。
|
||||
- [reference/pitfalls.md](reference/pitfalls.md):全部实测坑逐条(含 `--dry-run` 不保护清场段、门禁跑在 `/keil-build` 之前、未跟踪文件在 `git log` 与合并里都不留痕、并集合并残留、新树从未构建 → 体积类门禁必然红、注释-only 也刷红体积凭证、「已合入」快路径在门禁前返回不替你复绿、`--force` 只覆盖四类检查、联接不是 symlink(`core.symlinks=false`)、锁不绑进程存活、文件工具先读后改按绝对路径记账、中文门禁回显乱码但 rc 可信)。收工前扫一眼。
|
||||
- [reference/verification.md](reference/verification.md):每节要留的证据(预演 sha + `MT_RC=0`、blob id 对账、两把锁空闲、禁词计数 0、`REPARSE: none`、改上限数字后的全形态扫描)。
|
||||
|
||||
## 与其它 skill 的衔接
|
||||
|
||||
- 构建 = `keil-build`(`@build`)· 烧录/回读 = `stlink-flash` · 串口闭环 = `serial-boot-test`(真实 hex 路径以 `.serialtest/config.json` 的 hex 键为准——实测 `MDK-ARM/Objects/STM32F407-Demo.hex` 不存在,拷错 = 烧旧镜像)
|
||||
- 构建 = `keil-build`· 烧录/回读 = `stlink-flash` · 串口闭环 = `serial-boot-test`(真实 hex 路径以 `.serialtest/config.json` 的 hex 键为准——实测 `MDK-ARM/Objects/STM32F407-Demo.hex` 不存在,拷错 = 烧旧镜像)
|
||||
- 新 `.c` 双端注册 = `new-source-file` · RAM/.map 台账 = `mem-budget` · UI 回归 = `ui-verify` · 计数同步 = `stale-doc-count-sweep` · 卡死的技能台账行 = `mnemon-skill-ledger-stuck-repair`
|
||||
- DSH worktree 插件(`/worktree` / `worktree_create`,仓内 `.dsh-worktrees/`)与本 skill 的分工、桥接命令、锁归属 → F 节 · 完整机制与目录分配 → `docs/环境与部署/并行任务工作树方案.md`
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
它按缺的那一样分别给后果,`adopt` 没补齐也只报「还不能算就绪」,不许说「现在可以跑」。
|
||||
- **adopt 只碰联接**:已联的跳过;`simulator/SDL2` 若已是实体目录只告警不动;`.dsh-worktrees/` 之外的路径
|
||||
一律 exit 2(实测 `plugin adopt ".."` 被拒)——这道闸防的是把主树或 `_wt` 目录当插件树遮蔽掉真实内容。
|
||||
- **锁归属现在认得出来**:`@build` 在插件树里自持自释时,持有者名从 cwd 反推成 `pw:<name>`(多段名压平成
|
||||
- **锁归属现在认得出来**:`/keil-build` 在插件树里自持自释时,持有者名从 cwd 反推成 `pw:<name>`(多段名压平成
|
||||
`-`),与 `plugin adopt` 打印的那个名字逐字相同(`keil_build.py: plugin_tree_owner()` ↔
|
||||
`task_worktree.py: plugin_lock_owner()`)→ `lock status` 不再一律落成 `build-cli`。
|
||||
- 上台架仍须手工占锁、用同一个名:`lock acquire bench --task pw:<name> --note <说明>` →
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
subprocess,含空格的路径(本项目 `DTU Code`)由 subprocess 自动加引号。
|
||||
但**读属性**用 ctypes 没问题:`GetFileAttributesW` + `FILE_ATTRIBUTE_REPARSE_POINT` 就是 E 第 3 步的预扫手段。
|
||||
- **联接建失败不中止 `new`**:只告警并打印手工补建命令,避免留下半成品任务空间;
|
||||
但该任务里跑不了 `.venv\Scripts\python.exe`,`@build` / `ci_local.py` 会找不到解释器。
|
||||
但该任务里跑不了 `.venv\Scripts\python.exe`,`/keil-build` / `ci_local.py` 会找不到解释器。
|
||||
- **`done` 前主工作树必须干净**:否则未提交改动会被卷进合并(补完基线再重跑 `done` 时最容易忘,见 C 第 6 步)。
|
||||
- **⚠ 任务树里 `check_doc_links.py --all` 的红多半是 `simulator/SDL2` 目录联接的假红**:全库扫描会吞下那份
|
||||
「仓库里的第二份仓库副本」(实测任务树 222 个文档 / 35 处问题全在联接下、RC=1,而主树同命令基线全绿)。
|
||||
@@ -39,11 +39,11 @@
|
||||
`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):**不许用 stash / 提交 / 删除去清场**(项目级 Never,
|
||||
你也无从判断它写没写完)→ 走 **C2**:取证(mtime + diff + `hash-object` 存进 ci-artifacts)→ `merge-tree` 预演
|
||||
→ 主树 `merge --no-ff -F` → 合并前后 blob id 与 `diff --stat` 对账。**「它看着像废弃的」不是丢弃判据**。
|
||||
- **⚠ `done` 的门禁跑在 `@build` 之前 → 依赖 `.map` 的钩子会误报**(2026-10-12 实测):
|
||||
`done` 的顺序是「合并 → 重生成 → **门禁(ci_local)** → `@build`」,而
|
||||
- **⚠ `done` 的门禁跑在 `/keil-build` 之前 → 依赖 `.map` 的钩子会误报**(2026-10-12 实测):
|
||||
`done` 的顺序是「合并 → 重生成 → **门禁(ci_local)** → `/keil-build`」,而
|
||||
`mem-budget`(登记簿条目「缩堆迁 CCM 后半(暂缓)」)/ `mem-numbers` 是拿**文档快照 vs `.map`** 比对 —— 门禁跑时
|
||||
`.map` 还是**主工作树合并前**的旧产物,与刚合并进来的新台账**必然不一致** →
|
||||
误报 `[FAIL] 台账受管区与 .map 不一致`。`@build` 跑完后 `.map` 才是新的。
|
||||
误报 `[FAIL] 台账受管区与 .map 不一致`。`/keil-build` 跑完后 `.map` 才是新的。
|
||||
**处置(2026-10-25 补全)**:按 C 节第 5 步的完整链补——`check_mem_numbers.py --write` →
|
||||
`ram_baseline.py --write` → `ram_baseline.py --update` → `check_test_coverage.py --update-baseline` →
|
||||
`check_doc_links.py --all` → 提交 → 重跑 `done`(它会检测到「已合入」直接清场)。
|
||||
@@ -57,15 +57,15 @@
|
||||
- **⚠ 新建工作树从未构建过 → 体积类门禁必然红**(2026-10-25 实测):`.map` 是每棵树各自的
|
||||
构建产物、不入版本库,所以 `new` 出来的空间里 `mem-numbers` / `mem-budget` / `ci_local.py`
|
||||
的体积门禁步都拿不到事实源,报 `[FAIL] 找不到 .map 事实源`(脚本提示已写明原因,不是代码缺陷)。
|
||||
同理 `done` 的重生成步骤会 `[skip] 未找到 .map,跳过内存快照同步(需先 @build)`。
|
||||
**处置**:新空间第一次提交前先 `@build` 一次(`build` 锁自动排队,与别的树串行)。与上一条
|
||||
同理 `done` 的重生成步骤会 `[skip] 未找到 .map,跳过内存快照同步(需先 /keil-build)`。
|
||||
**处置**:新空间第一次提交前先 `/keil-build` 一次(`build` 锁自动排队,与别的树串行)。与上一条
|
||||
的区别要分清:上一条是「有 `.map` 但是旧的」,本条是「根本没有 `.map`」,处置动作不同。
|
||||
- **⚠ 只改注释里的文档路径,也会把「体积同码凭证」刷红**(2026-10-30 实测,`done testdoc`):
|
||||
`scripts/check_size_gate.py` 的判据是**索引 blob 指纹**(实测 1466 个文件),**不是体积数字**——
|
||||
那笔全库路径同步顺带改了 20 个 `.c/.h` 的注释,`Program Size` 逐项没变
|
||||
(Code=246160 / RO-data=29816 / RW-data=2036 / ZI-data=149700),凭证照旧红:
|
||||
`本次代码(指纹 172c6c78b8fb…,1466 文件)与体积测量(指纹 1ed4267c77f6…,2026-10-30T01:15:01)不同码`。
|
||||
**处置比上一条那条补绿链短**:合并后 `done` 自己那次 `@build` 已 0 错 0 警,此时只需
|
||||
**处置比上一条那条补绿链短**:合并后 `done` 自己那次 `/keil-build` 已 0 错 0 警,此时只需
|
||||
`ram_baseline.py --update` 重写凭证(改的是 `scripts/size_attestation.json`;同日同数字时
|
||||
`ram_baseline.tsv` 连趋势行都不会新增),**别顺手 `--write` 去动内存受管快照**——数字没变就改受管区,
|
||||
等于给别的在飞分支造冲突面(台账归属口径见 `mem-budget` skill)。
|
||||
@@ -75,7 +75,7 @@
|
||||
落主干后的全量证据得自己跑:`scripts/ci_local.py`(实测 47 runner / 3896 断言全绿)
|
||||
+ 主树 `check_doc_links.py --all`(实测 215 个文档 / 2993 处章节引用 rc=0)。
|
||||
- **⚠ 增量构建可能不重链 → `.map` 数字瞬态不一致**(2026-10-12 实测):
|
||||
`@build` 若判定无需重链,`.map` 保持旧值,此时 `--write` 生成的快照是**瞬态值**
|
||||
`/keil-build` 若判定无需重链,`.map` 保持旧值,此时 `--write` 生成的快照是**瞬态值**
|
||||
(实测同一份源码两次读到 223,560 与 223,724,差 164 B)。**以主工作树(集成点)
|
||||
的 `.map` 为准**;改完 RAM 相关代码后确认重链再刷新快照。
|
||||
- **`ram_baseline.py` 的 help 串里有未转义的 `%`**(2026-10-25 实测:某些直调姿势会渲染 argparse help 并崩)——
|
||||
@@ -120,15 +120,15 @@
|
||||
同任务重复 acquire 是幂等的(不会把自己锁死)。
|
||||
- **TTL 按锁分设**(2026-10-12):`build` = **1h**、`bench` = **6h**(`LOCK_TTL_HOURS`)。
|
||||
口径是「兜底窗口应与操作真实时长同量级」——编译是秒~分钟级,6h 的兜底太钝;
|
||||
bench 是会话级(flash→`@test`→探针),才配得上小时级。持有者还可用
|
||||
bench 是会话级(flash→`/serial-boot-test`→探针),才配得上小时级。持有者还可用
|
||||
`lock acquire ... --ttl <小时>` **自报**更精确的窗口(`keil_build.py` 即按其 `--timeout` 自报)。
|
||||
- **陈旧锁自动接管**:超过该锁 TTL 未释放即视为陈旧,下次 `acquire` 会接管并提示;
|
||||
未到 TTL 的活锁需 `--force`。`done` 的收口前置也会**自动释放陈旧锁**(不再硬失败),但
|
||||
**未到 TTL 的活锁会把 `done` 挡在前置检查**(B 节第 8 步)。
|
||||
- **`build` 锁不必手工持**(2026-10-12 起):`@build`(`keil_build.py`)**自持自释**——
|
||||
- **`build` 锁不必手工持**(2026-10-12 起):`/keil-build`(`keil_build.py`)**自持自释**——
|
||||
进入 acquire、`finally` 无条件 release,所以"Agent 忘了 release、锁一直挂到 TTL"这条在构建路径上已消除。
|
||||
`done` 的门禁自己持锁并给子进程设 `DTU_LOCK_HELD=build` 告知跳过(新旧脚本都兼容)。
|
||||
**`bench` 锁仍须 Agent 手工持**(会话作用域:跨烧录→`@test`→探针,见 `keil-build` SKILL.md)。
|
||||
**`bench` 锁仍须 Agent 手工持**(会话作用域:跨烧录→`/serial-boot-test`→探针,见 `keil-build` SKILL.md)。
|
||||
- **⚠ `done --dry-run` 只保护预演段、不保护清场段**(2026-10-29 实测):`done <ID> --dry-run --skip-build --skip-gates` 在「分支已合入主干」这条路上**照样执行清场**——摘 `.venv` / `simulator/SDL2` 联接、删分支、清台账,然后打「完成(分支此前已合入)」。想真·零改动就只跑不带 `--skip-gates` 的 `--dry-run` 并停在预演输出;用完一律 `git worktree list` 复验(判完成以 `git log` 为准,不是以有没有再敲一次 `done` 为准)。另:`--dry-run` 的前置检查同样要抢 `build` 锁——被别的任务占着时它会直接报「独占资源 build 仍被 X 占用,先 release」,此时加 `--skip-build` 或先 release,**别为了预演去抢别人的锁**。
|
||||
- **⚠ 文件工具「先读后改」按解析后的绝对路径记账,切树后要重新读**(2026-10-30 实测,屏板链路任务树):开新树后先用**相对路径**读过一批文件(读历史记到当时解析到的主树文件上),再进任务树直接 `edit` → 6 条并行编辑全被 `file has not been read — read the file, then retry` 拒。判据:**树内改动一律用树内绝对路径,先 read 再改**——读历史不跨路径复用,工具不做「同名相对路径」宽容。
|
||||
- **⚠ 中文门禁输出回显乱码 ≠ 路径坏**(2026-10-30 实测):对含中文文件名的文档跑 `check_doc_links.py`,终端回显整段乱码,但 **rc 与判定计数全部正确**。只认 `[SUMMARY]` 行与退出码,别误诊成「文档丢失」;脚本化批量调用走 python subprocess + 显式 `encoding`,别经命令行中转中文名。
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user