# 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/**` 全部 47 套,统计自研生产源码行/函数覆盖(并集)+ 与 `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,按原样回写(本仓库各文件换行混杂) - **受管区锚点**:改文档中的数值/表格时用 `` 锚点,只替换锚点内内容 - 脚本**输出文件绝不能落在自身扫描范围内**(否则会"自我污染",见 `docs-tree-reorg` skill) - 生成器与校验器**共用同一套正则/口径**,避免两处数字打架 ## 相关文档 - `docs/代码规范/pre-commit使用说明.md` — 门禁完整说明与排除项 - `docs/代码规范/cppcheck使用说明.md` - `docs/文档规范.md` — 文档门禁三档 - `docs/内存管理/RAM使用分析与优化方案.md` — RAM/栈报告口径