1221676272
2026-10-30 测试说明分层那次(28 个路径、209 文件)实测踩到两条,都是
「把不该改的东西改脏」,当时的补救全靠事后从 git 里捞原始字节:
B1 整目录搬迁会改写目录内部文件的内容。「相对引用文件所在目录」这条
解析规则不检验目标是否存在,于是台架日志里不是路径的斜杠片段被当成
「旧目录下的子路径」并插上目录名:`1/1 PASSED` → `test_data/1/1 PASSED`、
`13639/32768`、日期占位 `[----/--/-- --:--:--]` 全中招,连有效的
`see ../a.md` 也被换成指向不存在的 `see td/a.md`。23 个证据文件是
用 `git show HEAD:<旧路径>` 逐字节还原才救回来的。
B2 目录内部文件指向外部的 `../` 出链不补层级。emit_dir 取的是
`new_abs.parent`,整目录搬迁时少算一层,`../images/x.svg` 搬深一层后
仍指旧层级;图片按文档目录相对(§3.2),check_doc_links 看不见 → 静默断链。
改法(尽量小面):
- resolve_token:按引用文件目录解析时要求目标真存在;根相对写法保持原宽松口径。
- rewrite_text:moved 文件的 emit_dir 改成「本文件搬完实际所在的目录」;
单文件搬迁走原分支,一字未动。
- 整目录搬迁时目录内部互为相对的引用一律不改(两侧一起搬,改了必错);
「旧路径是不是目录」必须在搬迁前记进 old_is_dir_global——--apply 是先
git mv 再改写,那时旧路径已经不存在了。
- glob 形态改成看得见:搬目录时前缀整体跟着搬(本来就该改);搬单文件时
一条 glob 可能同时盖住搬走与没搬走的两批文件,不猜,只点名列出。
分层那次有 4 处这种引用是静默漏改、事后手改的。
回归:新增 `--selftest`(在一次性临时 git 仓里跑本脚本的副本,`.gitignore`
先排除 /scripts/ 免得脚本自己进扫描面)。案例 A 守整目录搬迁(证据文件
逐字节不变、出链补层、别人文件的入链照改、glob 前缀跟着搬),案例 B 守
单文件搬迁的层级换算与 glob 提示,18 项断言全绿。反向对照也做了:同一
夹具在改前版本上 B1/B2 都复现,所以断言不是凭空写的。
文档同步:docs/文档规范.md §3.6 第 4/5 条从「现存危害」改写成「已修 +
验收判据」,新增第 6 条 glob 提示,并加「改本体必须先跑 --selftest」;
docs/测试说明/README.md 第 3/5 节口径同步;scripts/README.md 补 --selftest
与任务树 --no-verify 的用法行。顺带修掉分层那笔里我写错的日期:
2026-11-01 → 2026-10-30(文档规范 4 处、测试说明 README 2 处,真值就是今天)。
树内验证:AST OK;--selftest PASS;改动的 3 个 .md 单文件
check_doc_links --sections-strict RC=0(36 处章节引用、可判定 34、无法判定 0);
字节体检干净。未跑 @build(无 .c/.h 改动)。
104 lines
11 KiB
Markdown
104 lines
11 KiB
Markdown
# scripts/ — 开发脚本(提交门禁 + 报告生成)
|
||
|
||
> 本目录是 **pre-commit 门禁脚本**与**报告生成脚本**的存放处。
|
||
> 与 `tools/`(面向开发者的功能工具)区别:本目录服务于**质量门禁与工程度量**。
|
||
|
||
## 文件一览
|
||
|
||
### 提交门禁(接入 `.pre-commit-config.yaml`)
|
||
|
||
| 脚本 | 钩子名 | 作用 |
|
||
|---|---|---|
|
||
| `check_coding_standard.py` | `C coding standard` | C 代码规范机械检查(命名前缀/常量右置/头文件保护宏等) |
|
||
| `check_mojibake.py` | `Forbid U+FFFD mojibake` | 扫描 `U+FFFD` 乱码(`EF BF BD` 字节) |
|
||
| `check_doc_links.py` | `doc-links` | **文档链接存在性** + 命名规范 + **`§N.M` 章节引用**(口径见 `docs/文档规范.md` §3.5)。钩子已带 `--sections-strict`(自引用失效也拦);`--section-todo` 列出机器判不了目标的 `§`(带改写建议)。每次跑打印「可判定比例」行,防覆盖率回落 |
|
||
| `section_aliases.json` | (被 `check_doc_links.py` 读) | 文档**昵称别名表**:「内存方案」→ `docs/内存管理/RAM使用分析与优化方案.md`。只登记无歧义昵称;登记一次即把该昵称的引用纳入硬门禁 |
|
||
| `check_ids.py` | ~~`id-check`~(已退役) | **编号门禁(已退役,2026-10-28)**:`docs/编号规范.md` §8 的八项校验(未登记前缀 / 形态 / 悬空 / 重复定义 / 越游标 / 未紧邻文档名 / 孤儿 / 可判定比例)。**已关闸为增量拦截**:钩子带 `--strict`,只拦 `scripts/archive/id_gate/id_known_backlog.json` 台账之外的新错误级问题(存量按指纹豁免、台账只减不增);可判定比例跌破基线任何模式都失败。详表(含限制与盲区)→ `docs/门禁说明/id-check.md` |
|
||
| `id_registry.json` | (被 `check_ids.py` 读) | 前缀族**注册表**:属主登记处 / 形态正则 / 冻结 / 游标,逐项来自 `docs/编号规范.md` §4;`decidable_ratio_baseline` = 校验 8 的基线(只许涨)。与 `section_aliases.json` 同一套"别名表 + 可判定比例"范式 |
|
||
| `id_aliases.json` | (被 `check_ids.py` 读) | 编号**别名/作废表**:旧字面量 → 现行字面量或 `status: void`(如已销项的旧号仍被引用 → `status: void`、模块码 `PHY→NET`) |
|
||
| `check_test_traceability.py` | (原被 `id-check` 顺带调用;后者已退役) | **TC 用例双向追溯**:文档 ↔ 代码两侧的 TC 编号集合比对;2026-10-20 起范围扩到 `test/host/` 与 7 篇「单元测试说明.md」(`--board-only` 回退旧板级口径)。此前该脚本**未挂任何钩子** |
|
||
| `gen_task_table.py` | `task-table` | FreeRTOS 任务表以 `Src/freertos.c` 为**唯一事实源**生成/校验 |
|
||
| `check_mem_numbers.py` | `mem-numbers` | 内存/Flash 数字以 `.map` 为唯一事实源校验(体积档) |
|
||
| `check_test_suite.py` | `test-suite-switch` | 测试套件开关=产品构建(防测试固件静默提交) |
|
||
|
||
### 报告生成(非门禁)
|
||
|
||
| 脚本 | 作用 |
|
||
|---|---|
|
||
| `ram_report.py` | 从 `.map` 生成**静态 RAM/Flash 占用**报告(→ `docs/内存管理/data/`) |
|
||
| `stack_report.py` | 从 Keil 静态调用图 + 串口 `[STACK]` 水位生成**每任务最坏链**报告 |
|
||
|
||
### 引用同步(改名 / 挪目录)
|
||
|
||
| 脚本 | 作用 |
|
||
|---|---|
|
||
| `rename_ids.py` | **编号(字面量)批量改名**:映射表 JSON 驱动,`files` 显式 scope(同一裸形字面量可能属多族,实测 `S3` 三义 → 全局替换必造新撞号);默认干跑,`--apply` 落盘并自动写 `id_aliases.json` 的 scoped 旧号→新号条目;`[示例]`/`[计划]` 行与变更记录表不改写;按原字节读写(保 BOM 与行尾)。用法与批次记录 → `scripts/archive/id_gate/编号治理历史.md` §8.3 |
|
||
| `rename_ref.py` | **改名/挪目录 + 全库引用自动改写**:默认 dry-run 预览,`--apply` 才落盘(`git mv` → 改写引用 → 自动跑 `check_doc_links --all` 复核)。覆盖反引号引用、Markdown 链接、图片 `../` 层级换算(**整目录搬迁时目录内部文件的出链按新目录补层**)、`#include`、`uvprojx`/`CMakeLists.txt` 源列表;搬**单文件**时指向旧目录的 glob 不自动改、但会点名列出。**改本脚本必须跑 `--selftest`**(一次性临时 git 仓,守住"搬目录把内部文件改脏"与"出链少补一层"两条实测踩过的坑)。固定动作与边界 → `docs/文档规范.md` §3.6 |
|
||
|
||
> `check_doc_links.py` 除链接存在性外,还校验 **`§N.M` 章节引用**是否指向目标文档真实存在的编号标题(口径见 `docs/文档规范.md` §3.5)。
|
||
|
||
### CI 与本地等价(2026-10-01)
|
||
|
||
| 脚本 / 文件 | 作用 |
|
||
|---|---|
|
||
| `ci_local.py` | **本地跑与 CI 等价的门禁**(静态门禁 pre-commit 47 根 + **manual 段**(`cppcheck` / `test-cppcheck` / `test-coverage` 三根逐根 `--hook-stage manual`,默认跑)+ 提交信息门禁 + **体积门禁** + 主机侧纯逻辑单测):自动选用装了 pre-commit 的解释器;无 `.map` 时自适应跳过 `mem-numbers` / `mem-budget`(⚠ 那是 SKIP 不是通过,缺口由体积门禁兜住);退出码 0/1/2 与 CI 对齐。CI 定义见 `../.gitea/workflows/ci.yml`,覆盖边界见 `docs/测试说明/流程与规范/测试开发标准流程.md` §9 |
|
||
| `check_size_gate.py` | **体积门禁的强制路径**:`.map` 只在本地有,钩子无它时自适应 SKIP,于是"没测=通过"。本脚本用**已提交的同码凭证** `scripts/size_attestation.json` 把这条判据变成无需 Keil 也能跑:`--stamp` 只在"`.map` 存在、比全部受管源码新、且未超 92%"时才盖章(已挂在 `ram_baseline.py --update` 里,无新增人工步骤);`--local` 供 ci_local 的体积门禁步;`--ci` 供 runner(同码性 + 按凭证数字复核红线,主干 push 拦红、其余事件 `--warn-only`);`--show` 排障。**口径与边界的唯一正文在本脚本模块 docstring** |
|
||
| `size_attestation.json` | (被 `check_size_gate.py` 读写)体积同码凭证:受管源码指纹(`git ls-files` 范围内逐文件 sha256 聚合)+ 测量数字与占比 + `.map` 出处。改固件源码后不重测 → 指纹对不上 → 体积门禁与 CI 同时拦红 |
|
||
| `check_test_coverage.py` | 主机单测**覆盖率**(2026-10-23):mscoverage 动态插桩跑 `test/host/**` 全部 44 套,统计自研生产源码行/函数覆盖(并集)+ 与 `test_coverage_baseline.json` 比回退;`--module/--update-baseline/--md/--json/--list`;挂在 `test-coverage` 钩子上(`stages: [manual]`,**`git commit` 不跑**),入口 = 远端 CI「手动阶段门禁」步 / `scripts/ci_local.py` 的 manual 段(`--coverage` 旗标保留只为兼容旧命令;口径见 `docs/测试说明/单元测试/主机单测覆盖率口径.md`) |
|
||
| `ci_gitlint_range.ps1` | CI 侧**提交信息门禁**:逐条校验本次 push/PR 引入的提交(`--all-files` 跑不到 `commit-msg` stage,故必须单独守) |
|
||
| `ci_find_python.ps1` | CI 侧 Python 探测(候选顺序须与 `ci_local.py` 同序同步,否则 CI 与本地判定会分叉) |
|
||
| `ci_runner_bootstrap.ps1` | **本机 runner 运维脚本**(体检 `-Status` / 干跑 `-DryRun` / 部署 `-SetEnv -StartNow`):装"等 Gitea 就绪 + 崩溃自拉起"的看门狗 + 开机计划任务 + 机器级 `DTU_*` |
|
||
|
||
> ⚠️ 后三个脚本是**本机 runner 专属运维**(远程 CI 只跑在本机一台机器上),用法、原理与
|
||
> 踩坑全部集中在 `docs/CI/README.md`,本表只登记不展开。
|
||
|
||
## 使用方式
|
||
|
||
```bash
|
||
# 全库跑门禁(用仓库 .venv 的 python,其 pre-commit 随 requirements.txt 安装;
|
||
# 其他装有 pre-commit 的解释器可用 DTU_PRECOMMIT_PYTHON 指定)
|
||
# 只覆盖 `pre-commit` 阶段的 48 根(`git commit` 实跑 = 48 + gitlint = 49 根)
|
||
& '.venv\Scripts\python.exe' -m pre_commit run --all-files
|
||
|
||
# manual 阶段三根(带 stages: [manual],提交期不跑):一次只能给一个 id
|
||
& '.venv\Scripts\python.exe' -m pre_commit run --all-files --hook-stage manual cppcheck
|
||
& '.venv\Scripts\python.exe' -m pre_commit run --all-files --hook-stage manual test-cppcheck
|
||
& '.venv\Scripts\python.exe' -m pre_commit run --all-files --hook-stage manual test-coverage
|
||
|
||
# 本地跑与 CI 等价的门禁(静态门禁 + 提交信息门禁 + 体积门禁 + 主机侧纯逻辑单测)
|
||
& '.venv\Scripts\python.exe' scripts/ci_local.py
|
||
& '.venv\Scripts\python.exe' scripts/ci_local.py --static-only # 只跑静态门禁
|
||
& '.venv\Scripts\python.exe' scripts/ci_local.py --host-unit-only # 只跑主机侧纯逻辑单测
|
||
& '.venv\Scripts\python.exe' scripts/ci_local.py --size-only # 只跑体积门禁(强制路径)
|
||
& '.venv\Scripts\python.exe' scripts/check_size_gate.py --show # 体积同码凭证现状(0.3 s,无需 Keil)
|
||
& '.venv\Scripts\python.exe' scripts/check_size_gate.py --stamp # 用新鲜 .map 重新盖戳(--update 已自动带做)
|
||
|
||
# 单个脚本
|
||
& '.venv\Scripts\python.exe' scripts/check_doc_links.py --all
|
||
& '.venv\Scripts\python.exe' scripts/rename_ref.py docs/旧/名.md docs/新/名.md # 预览
|
||
& '.venv\Scripts\python.exe' scripts/rename_ref.py docs/旧/名.md docs/新/名.md --apply # 落盘
|
||
& '.venv\Scripts\python.exe' scripts/rename_ref.py --selftest # 改 rename_ref 本体必须跑(一次性临时 git 仓回归)
|
||
# 任务工作树里落盘请加 --no-verify:树内 simulator/SDL2 是目录联接,收尾的 check_doc_links --all
|
||
# 会把同一份仓库当第二副本扫出假红;全库 --all 复核留到主工作树跑
|
||
& '.venv\Scripts\python.exe' scripts/gen_task_table.py --check # 或 --write / --diff
|
||
& '.venv\Scripts\python.exe' scripts/check_mem_numbers.py --check
|
||
& '.venv\Scripts\python.exe' scripts/check_test_suite.py # 门禁判定
|
||
& '.venv\Scripts\python.exe' scripts/check_test_suite.py --allow-test-build # 放行测试构建
|
||
& '.venv\Scripts\python.exe' scripts/ram_report.py
|
||
& '.venv\Scripts\python.exe' scripts/stack_report.py -s .dsh\skills\serial-assistant\scripts\state\serial.log
|
||
```
|
||
|
||
## ⚠️ 脚本编写约定
|
||
|
||
- **保真回写**:改文件前先探测原换行符(LF/CRLF)与 BOM,按原样回写(本仓库各文件换行混杂)
|
||
- **受管区锚点**:改文档中的数值/表格时用 `<!-- XXX:BEGIN/END -->` 锚点,只替换锚点内内容
|
||
- 脚本**输出文件绝不能落在自身扫描范围内**(否则会"自我污染",见 `docs-tree-reorg` skill)
|
||
- 生成器与校验器**共用同一套正则/口径**,避免两处数字打架
|
||
|
||
## 相关文档
|
||
|
||
- `docs/代码规范/pre-commit使用说明.md` — 门禁完整说明与排除项
|
||
- `docs/代码规范/cppcheck使用说明.md`
|
||
- `docs/文档规范.md` — 文档门禁三档
|
||
- `docs/内存管理/RAM使用分析与优化方案.md` — RAM/栈报告口径
|