Files
STM32F4-Base/docs/文档规范.md
T
wandering1 4a4ad6bc5f 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 全过;字节卫生零漂移。
2026-10-30 13:03:12 +08:00

409 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 文档规范
> **适用对象**:本仓库 `docs/` 下的全部 Markdown 文档,以及根目录 `README.md`、`AGENTS.md`。
> **强制程度**:§1~§6 为**强制**(pre-commit 机械校验,违反即拦截提交);§7~§8 为**约定**(评审时人工把关)。
> **相关文档**:[`README.md`](README.md)(文档索引)、[`代码规范/pre-commit使用说明.md`](代码规范/pre-commit使用说明.md)(钩子清单)、[`架构设计/图形绘制流程.md`](架构设计/图形绘制流程.md)(配图流程)。
---
## 1. 目录与命名
### 1.1 目录
| 规则 | 说明 |
|---|---|
| **目录名一律不含空格** | 空格迫使脚本/链接/CI 处处加引号,且 URL 编码形态(`UI%20设计`)极易被漏改(2026-09-25 起强制;全库已无含空格目录) |
| **按领域归入子目录** | 顶层不放散落 `.md`;新领域先建无空格子目录,再在 [`README.md`](README.md) 登记 |
| **子目录内文件过多时再分一层(只多分一层)** | `docs/测试说明/` 2026-10-30 起按用途分 `流程与规范/` `单元测试/` `板级测试/` `专项验证/` `测试工具/` `测试数据/` 六个子目录,**归层判据 → `docs/测试说明/README.md` §2**。再往下分不会让引用更短,只会让按目录递归的门禁 glob 更难核对 |
| 现有目录 | `架构设计/` `子系统设计/` `算法/` `网络/` `测试说明/` `代码规范/` `内存管理/` `问题记录/` `环境与部署/` `UI设计/` `硬件平台/` `芯片手册/` `images/` |
### 1.1b 代码目录的 README.md
**每个自建代码目录**(非第三方库、非 HAL/CMSIS 生成)**必须有一份 `README.md`**,
说明该目录的职责、文件清单与关键约束,让新接手者不必读遍代码即可建立认知。
| 规则 | 说明 |
|---|---|
| **覆盖范围** | `App/`(含 `log/` `task/` `util/`)、`Drivers/BSP/`(含各芯片子目录)、`Src/`、`Inc/`、`scripts/`、`tools/`(含各子工具)、`test/`、`MDK-ARM/`、`simulator/`(含 `sim/` `tests/`)、`docs/` |
| **排除** | 第三方库与生成代码:`Drivers/CMSIS/`、`Drivers/STM32F4xx_HAL_Driver/`、`Middlewares/Third_Party/`、`Lib/`(FatFs/dhara/lvgl)、`simulator/SDL2/`、构建产物目录 |
| **必备内容** | ① 一句话定位 + 是否自研 ② 文件/子目录清单表(每个一句话作用)③ 关键约束或陷阱(改前须知)④ 指向 `docs/` 权威文档的链接 |
| **命名** | 一律 `README.md`(大写),放该目录根部 |
| **链接** | 目录间互链用**同级相对路径**(如 `../NET/README.md`);指向 `docs/` 用**仓库根相对路径** |
| **门禁** | README 内所有链接受 `doc-links` 钩子检查;新增/改名目录或文件后须同步更新对应 README |
> 目的:**目录即文档的入口**。根 `README.md` 给全局地图,各目录 `README.md` 给局部细节,
> 二者构成"地图 + 地标"的导航体系(避免所有信息堆在单一 README)。
### 1.2 文件名
| 规则 | 说明 |
|---|---|
| **不含空格** | 同目录规则 |
| **主题词 + 类型后缀** | `xxx设计说明.md` / `xxx_Test_Guide.md` / `xxx_Trap_Records.md` / `xxx使用说明.md` |
| 中英混排 | 中文主题词直接写(如 `系统墙钟时间维护说明.md`);含专有名词/缩写可用下划线连接(如 `BSD_Socket_API_使用指南.md`) |
| 只用 ASCII 与中文 | **不含** `()【】〔〕` 等全角括号、不含 `&`、`?`、`*`、`<`、`>`、`|`、`:`、`"`、`\` 等路径敏感字符 |
| 版本号写法 | 版本后缀用下划线连接,**不用全角括号**:`xxx_V1.0.md`(✅)/ `xxx(V1.0).md`(❌) |
> **例外**:`docs/芯片手册/` 为外部芯片数据手册转录,文件名**沿用上游原名**(含空格等),不受本节约束。
---
## 2. 编码与换行
| 项 | 要求 |
|---|---|
| 编码 | **UTF-8**;`.md` 文档**不带 BOM** |
| 换行 | 与文件现状保持一致;**新建文档用 LF** |
| 行尾空白 | 禁止(pre-commit `trailing-whitespace` 自动清理) |
| 文件尾 | 必须以单个换行结束(`end-of-file-fixer`) |
| 乱码 | 禁止出现 `U+FFFD`(替换字符,字节序列 `EF BF BD`) |
| 长中文正文与跨语言字面量 | 经 run_code 等程序化工具改写本文时,程序体内定界符只用一种 ASCII 引号;正文里的中文引号用全角「」,反引号字符用码位现取后再拼,不把反引号或 shell 变量形态写进字面量。落点选唯一锚点并断言命中数为 1,全部替换在内存串上跑完才一次性写回;落盘后打印 bom、crlf、乱码字节数与占位残留,并回读目标行确认已是新文本 |
> ⚠️ **本仓库 BOM 铁律**(见 `AGENTS.md`「铁律:源码编辑与编码」):C 源码中 `Drivers/BSP/UI/*`、`App/task/ui_task.c`、`App/cfg/cfg_file.c`、`Lib/lv_conf.h` 等**带 BOM**,被工具改写后 BOM 会静默丢失 → ARMCC 改按 CP936 解码 → 中文字面量编译失败。
> **文档(`.md`)不受此限**,一律用无 BOM UTF-8。
>
> **行尾策略(2026-10-09 起写死进 `.gitattributes`,本节是权威口径)**:`* text=auto eol=lf`(仓库 blob 与工作区统一 LF)+ `*.bat`/`*.cmd` 强制 CRLF(cmd.exe 对 LF-only 的 goto/标签不可靠)+ PNG/TTF/DLL/LIB 等显式 `binary` + `*.jpg` 走 LFS 且 `-text`(该条必须排在 `*` 之后才能覆盖)。**不要再依赖 `core.autocrlf`,也不要把工作区文件"改回 CRLF"**——CubeMX 重生成写的就是 LF;此前每次重生成后 40+ 个第三方文件在 `git status` 里显示"已修改",根因就是行尾由每台机器各自的 autocrlf 决定。用脚本批量改写文档时仍须**按原样回写换行符**,否则会产生整文件行尾漂移的巨型 diff。
---
## 3. 链接规范
### 3.1 文档间互链:仓库根相对路径
文档之间引用**一律使用仓库根相对路径**(形如 `` `docs/xxx/yyy.md` `` 或 `[文本](docs/xxx/yyy.md)`),**不使用** `../` 相对链接。
> 理由:根相对路径对文档所在层级不敏感,迁移子目录时**零改动**;`../` 形态在目录调整时会成片失效。
### 3.2 图片链接:文档所在目录相对
图片是**例外**——用**相对当前文档**的路径:
| 文档位置 | 图片写法 |
|---|---|
| `docs/xxx.md`(docs 根下) | `![标题](images/RemoDispBus帧格式.svg)` |
| `docs/子目录/xxx.md` | `![标题](../images/RemoDispBus帧格式.svg)` |
> ⚠️ **踩坑记录**:2026-09-25 目录重组时,5 篇文档从 `docs/` 迁入子目录,`images/xxx.svg` 未同步改为 `../images/xxx.svg` → **7 处真实断链**(由 `doc-links` 门禁揪出)。迁移含图文档时务必检查此项。
### 3.3 引用尚未创建 / 已删除的文件
| 场景 | 写法 | 示例 |
|---|---|---|
| 计划中、尚未创建 | **反引号内加 `[计划] ` 前缀** | `` `[计划] scripts/gen_task_table.py` `` |
| 已删除 | 写明「已删除」等状态词 | `删除 \`tools/ui_mock/render_mockup.py\`(**已删除**)` |
| 引用报错样例 / 日志片段(含路径) | **该行加 `[示例]` 标记** | `\| **典型报错** \| \`[示例] docs/a.md:1 [markdown 链接] x.svg\` \|` |
**为什么**:`check_doc_links.py` 会豁免这些标记;否则门禁会把"诚实标注了未创建"的引用误判为断链。
### 3.4 链接写法示例须放行内代码
讲「怎么写链接」的**语法示例**必须包在反引号里(行内代码 span):
- ✅ 正确:`` 写 `![标题](相对路径.svg)` 的形式 ``
- ❌ 错误:`写 ![标题](相对路径.svg) 的形式`(会被门禁当作真实链接去校验存在性)
`check_doc_links.py` 扫描前会剥离行内代码 span,故写法示例不会误报;但**反引号内的仓库根相对路径**(`docs/xxx.md`)仍会被校验——那是本工程的引用约定形态。
### 3.5 引用章节:紧邻写法 + 别名表,让门禁能校验
引用某文档的某小节,写成 **`` `docs/xxx/yyy.md` §N.M ``**(文档名与 `§` 紧邻,中间只允许空白/反引号/括号)。
| 写法 | 门禁 |
|---|---|
| `` `docs/子系统设计/看门狗设计说明.md` §10.4 `` | ✅ 硬门禁(失效拦提交) |
| `` `看门狗设计说明.md` §10.4 ``(裸文件名,全库唯一) | ✅ 硬门禁 |
| `看门狗设计说明 §10.4`(省略 `.md`,文件名全库唯一) | ✅ 硬门禁 |
| `链路延迟 §8.3`(文档名**前缀**,全库唯一) | ✅ 硬门禁 |
| 「内存方案 §2.1」——已登记别名 | ✅ 硬门禁 |
| 「报告 §4.2」——昵称歧义、未登记 `[示例]` | ⚠️ 判不了 → 进 `--section-todo` 清单 |
| 本文档内「见 §3.1」 | ✅ 硬门禁(本文档必须有该编号;确实指别篇却省了文档名的,机器判不了 → 跳过并进待办) |
**两档严重性**:目标明确(路径 / 裸名 / 前缀 / 别名 / 同句 `§A / §B` 链)→ **错误**;按自引用解析 → 默认**告警**,但 **pre-commit 钩子带 `--sections-strict`,两档都拦提交**(2026-10-11 起:全库告警已清零,清零之后就该守住)。手工跑想只看告警:去掉该 flag。
**别名表 `scripts/section_aliases.json`**:昵称 → 文档路径。登记一次,正文一个字不用改,引用就纳入硬门禁。**只登记无歧义的昵称**(「报告」命中多篇 → 不登记,逼着写清楚)。
**覆盖率必须看着**:每次跑 `doc-links` 都打印一行
```
章节引用:共 1576 处,机器可判定 843 处(53%);跳过 → 昵称式 68 / 变更记录 419 / 外部手册 187 / …
```
比例只许涨不许跌。清 backlog:
```powershell
& '.venv\Scripts\python.exe' scripts/check_doc_links.py --all --section-todo
```
它列出全部机器判不了的 `§`(能唯一猜到的还给「建议改写:`docs/…` §N」),改一处涨一处;猜不到的就是**必须写清楚文档名**的那些。
> **口径怎么调出来的**:全库 1578 处 `§`。宽松版报警 173 处(几乎全是昵称误判)→ 加文档名解析 111 → 加紧邻 + `§` 链 + 前缀匹配 + 别名表 + 「父级编号不存在就不是自引用」判别 → 可判定比例 **7% → 58%(914 处)**,`--section-todo` 待办 **83 → 0**;清完 backlog 后**两档都拦提交**。变更记录小节(422 处)与外部手册编号(218 处)刻意不校验:前者是「当时那个编号」的历史指称,后者归厂商文档。
>
> **把待办清零靠的三类动作**(按性价比排序;引号内是**改前**写法 `[示例]`):① **手册引用写全名**——「手册 §9.2.1」一律改成 `docs/芯片手册/CH395F.md` §9.2.1(CH395F / GD5F / SD2506 / RM0090 摘录里都有编号标题,改完立刻纳入校验);② **昵称写全或登记别名**——「架构 §5.5」→ `UI_设计架构.md` §5.5、「报告 §6.5」→ 全路径、「V0.0 报告 §4.1」→ `V0.0 §4.1`(别名表登记 `V0.0`);③ **路径补 `docs/` 前缀**——`子系统设计/xxx.md` 这种「从 docs/ 起算」的写法机器解析不到。
>
> **两条写作铁律**(机器只看**同一行**):`§` 必须与文档名**紧邻**(换行/跨句就判不了);「见/按/该 + 昵称」这类含糊指代,宁可多打 30 个字符写全路径。
>
> **首轮清账揪出的真断链(41 处,已修)**——`[示例]` 下列编号是**修复前的错值**,故意原样保留作反例:`链路延迟测试说明.md` 10 处引 §8.1,而该文 §8 下只有 8.2/8.3/8.4(§8.1 早被删);`GD5F2GQ5UE_Test_Guide.md` 引 §4.8(BBT 实为 §3);`自动化测试流程.md` 引 `ST-LINK_CLI_复位使用说明.md` §10(该文只到 §7);`模拟器差距清单.md` 引 §219(实为 §4.1);`实机测试实施指南.md` 引 §4.4 白名单(白名单在 `看门狗设计说明.md` §4.3);`V0.1_网表详细分析报告.md` 表格里 12 处裸 `§3.4`/`§5.3` 其实指 V0.0 报告(补 `V0.0` 前缀 + 登记别名)。**教训:编号会随重构漂移,只有机器校验能发现。**
### 3.6 改名 / 挪目录:用 `scripts/rename_ref.py`,不要手工扫
**固定动作**(改名、挪目录、合并文档一律走这条,别靠 `git grep` 手改):
```powershell
# 1) 预览:看它打算改哪些文件的哪些行(默认 dry-run,不落盘)
& '.venv\Scripts\python.exe' scripts/rename_ref.py docs/旧/名.md docs/新/名.md
# 2) 确认无误后落盘:git mv + 改写全库引用 + 自动跑 check_doc_links --all 复核
& '.venv\Scripts\python.exe' scripts/rename_ref.py docs/旧/名.md docs/新/名.md --apply
# 整目录搬迁(末尾带 / = 挪进该目录并保留文件名;旧/新都是目录 = 前缀改写)
& '.venv\Scripts\python.exe' scripts/rename_ref.py docs/旧目录/ docs/新目录/ --apply
```
它覆盖的引用形态:反引号根相对引用、Markdown 链接(根相对与文档相对都按原风格改写)、**图片链接的 `../` 层级换算**(§3.2 那条实证坑由它兜住)、`#include "x.h"` 裸文件名、`MDK-ARM/*.uvprojx` 的 `<FilePath>`、`simulator/CMakeLists.txt` 的 `${PROJECT_ROOT}/…` 与 `${VAR}/…`、以及 skill / 脚本 / 配置里的路径串。
**它管不到的事**(1–3 一直是手工活;4–5 曾是真会把东西改坏的坑,2026-10-30 已修并进了 `--selftest` 回归;6 由脚本主动点名):
1. **昵称式引用**(「内存方案 §2.1」)与正文里的裸文件名散文 → 落盘后 `git grep -n '<旧文件名>'` 扫一遍;
2. **生成物**(`docs/images/引用关系图.*`)不改写 → 重跑 `& 'tools\refgraph\gen_refgraph.bat'`;
3. 改的是 `.c/.h` 且**新增了目录** → 还要把目录加进 `uvprojx` 的 `<IncludePath>`(→ `new-source-file` skill),随后 `/keil-build` 验证。
4. ~~搬整目录时会改写目录内部文件的内容~~ **已修**(2026-10-30,回归见 `--selftest` 案例 A):根因是
「相对引用文件所在目录」那条解析规则不检验目标是否存在,于是内部文件里**不是路径的斜杠片段**被
当成旧目录下的子路径并插上目录名 —— 分层那次 `1/1 PASSED` 改成 `test_data/1/1 PASSED`、日志日期占位
`[----/--/-- --:--:--]` 同样中招,连有效的 `see ../a.md` 都被换成指向不存在文件的 `see td/a.md`。
**修法**:该解析路径要求目标**真存在**(根相对写法保持原来的宽松口径),并且整目录搬迁时目录**内部**
互为相对的引用一律不改(两侧一起搬,改了必错)。
⚠ **仍要做的验收**:搬完看 `git diff --stat`,数据目录里**不该出现内容变更**、只该有改名 ——
一手证据与台架日志不接受任何改写,这条从"补救动作"降级成"核对判据"。
5. ~~搬整目录时目录内部文件指向外部的 `../` 出链不补层级~~ **已修**(同一 `--selftest` 案例 A):根因是
改写时把「本文件的新目录」取成了 `new_abs.parent`,整目录搬迁时少算一层,于是 `../images/x.svg`
搬深一层后仍指旧层级(**静默断链**:图片按 §3.2 是文档目录相对,`check_doc_links` 看不见)。
**修法**:按「`new_abs` / 相对旧目录的子路径」的父目录来算,出链层级自动补对;搬**单个**文件的换算
一字未动,由 `--selftest` 案例 B 专门守着。
6. **glob 形态引用**(如 `` `docs/td/*.md` ``):搬**目录**时前缀整体跟着搬(正确,不用管);搬**单个文件**时
一条 glob 可能同时盖住搬走和没搬走的两批文件,脚本不猜 → 但会把它**点名列出**(`⚠ 通配符形态引用 N 处`)。
分层那次有 4 处这种引用是**静默漏改**、事后靠人工补的,现在至少看得见。
> 同批实测的另两条工具侧事实:新路径**以 `/` 结尾**才是"挪进该目录并保留文件名"(见上例注释);`--apply` 收尾会自动跑
> `check_doc_links.py --all`,在带 junction 联接(`.venv`、`simulator/SDL2`)的任务工作树里会把联接目录扫成**假红**,
> 所以树内批量搬迁要加 `--no-verify`,全库 `--all` 复核留到主工作树跑(→ `docs/环境与部署/并行任务工作树方案.md`)。
> 另:脚本结尾的 `ℹ` 提示在 GBK 代码页会抛 `UnicodeEncodeError` 让进程**以退出码 1 收场**(此时 `git mv` 与改写**已经落盘**,
> RC 与真实结果相反)→ 2026-10-30 已在 `scripts/rename_ref.py` 内把 stdout/stderr `reconfigure(encoding="utf-8")`,
> 与 `check_ids.py` / `check_doc_links.py` / `check_test_traceability.py` 同法。
>
> **改 `scripts/rename_ref.py` 本体必须先跑 `--selftest`**(`& '.venv\Scripts\python.exe' scripts/rename_ref.py --selftest`):
> 它在一次性临时 git 仓里跑**本脚本的副本**——案例 A 守整目录搬迁那两条(目录内证据文件逐字节不变、出链 `../`
> 补对层级、别人文件的入链照改、glob 前缀跟着搬走),案例 B 守单文件搬迁的层级换算与 glob 提示。
> 反向对照也留了记录:同一夹具在改前版本上两条 bug **都复现**(`1/1 PASSED` → `td/1/1 PASSED`、
> `![img](../img/x.svg)` 不补层),所以这些断言不是凭空写的。
---
## 4. 门禁:文档-代码一致性
三个层次,全部由 pre-commit 机械校验:
| 层次 | 钩子 | 检查内容 | 触发条件 |
|---|---|---|---|
| **存在性** | `doc-links` | `docs/**/*.md` 里的链接、反引号路径是否指向真实文件;`§N.M` 章节引用是否指向真实小节(§3.5) | 改动任意 `.md` |
| **数值** | `task-table` | `README.md`、`AGENTS.md` 的 FreeRTOS 任务表是否与 `Src/freertos.c` 一致 | 改动 `README.md` / `AGENTS.md` / `Src/freertos.c` |
| **体积** | `mem-numbers` | 内存/Flash 数字是否与最新 `.map` 一致(受管区 + 陈旧散句数字) | 改动内存文档 / `App`·`Src`·`Inc` 源码 / 脚本 |
| **体积** | `check-added-large-files` | 阻止 >1MB 大文件入库 | 任意文件 |
### 4.1 本地手动运行
```powershell
# 全库文档链接存在性
& '.venv\Scripts\python.exe' scripts/check_doc_links.py --all
# 改名 / 挪目录(先预览再落盘,见 §3.6)
& '.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/gen_task_table.py --check
# 任务表不一致时:自动重写受管表格区
& '.venv\Scripts\python.exe' scripts/gen_task_table.py --write
# 内存数字(受管快照 + 陈旧散句数字扫描)
& '.venv\Scripts\python.exe' scripts/check_mem_numbers.py --scan
& '.venv\Scripts\python.exe' scripts/check_mem_numbers.py --write # 同步受管快照
# 只看期望内容,不写文件
& '.venv\Scripts\python.exe' scripts/gen_task_table.py --diff
& '.venv\Scripts\python.exe' scripts/check_mem_numbers.py --diff
# 生成完整内存台账(人工分析用)
& '.venv\Scripts\python.exe' scripts/ram_report.py -o docs/内存管理/data/ram_report_<日期>.md
```
### 4.2 受管区域(锚点)
文档里凡**随代码变化的具体数值**,都必须放进受管区,由脚本生成、禁止手改:
**任务表**(`README.md` / `AGENTS.md`)——表格外的说明文字原样保留:
```markdown
<!-- TASK_TABLE:BEGIN generated by scripts/gen_task_table.py -->
| Task | 栈 (words) | 优先级 |
|---|---|---|
| ... |
<!-- TASK_TABLE:END -->
```
**内存/Flash 快照**(`docs/内存管理/RAM使用分析与优化方案.md` §2.1):
```markdown
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ... |
<!-- MEM_SNAPSHOT:END -->
```
> **改栈流程**:先改 `Src/freertos.c` 的 `osThreadAttr_t` → 跑 `gen_task_table.py --write` → 提交。
> **改内存流程**:先改代码 + `/keil-build` 出 `.map` → 跑 `check_mem_numbers.py --write` → 提交。
> 钩子报不一致时**不要手改受管区**——跑 `--write` 让脚本重算。
#### 陈旧数字扫描(`mem-numbers --scan`)
除受管区外,脚本还会扫正文里的**陈旧区域数字**:从 `docs/内存管理/data/ram_report_*.md`
历史快照提取「曾经是区域用量、现已变化」的值(如旧 `RW_IRAM1 82,048`),
若正文仍以陈述现状的口吻引用即告警。
**允许保留旧数字的情况**(脚本按行内关键词自动放行):该行含
「历史 / 旧 / 曾 / 此前 / 快照 / `data/` / 修复前 / 优化前 / 迁入前 / 实施时 / 当时」等标记。
讲任务栈 / 内存池 / 峰值水位等**非区域用量**的行也自动跳过(那些数字语义不同)。
> 设计取舍:朴素地"扫所有 4~7 位数字"实测产生 **45 处误报**(栈配置 `4096`、堆配置 `49,152`、
> 峰值 `1184` 全是合法数字),门禁会被无视。改为"只报历史快照里出现过、当前已变的区域值"后
> **误报 0、真问题全中**。宁可窄而准,不可宽而吵。
### 4.3 豁免机制
`check_doc_links.py` 的豁免(不算失效,不报错):
- `` `[计划] ` `` 前缀标记
- 该行含 `[示例]` 标记(引用报错样例 / 日志片段等含路径的引文)
- 引用**附近 ±40 字符**内出现「未创建 / 尚未创建 / 已删除 / 待排期」等状态词
- 含通配符 `*`、尖括号 `<模块>` 等占位符
- 模板变量样式(`NN_` / `XX_` / `界面名` / `模块名` / `${...}` / `%s` 等)
- 行内代码 span 里的链接写法
- 外部 URL(`http/https/mailto`)与纯锚点(`#...`)
- `docs/芯片手册/` 等外部资料目录
**章节引用(D 类)另有的跳过**:昵称式引用(「内存方案 §2.1」)、非紧邻写法、变更记录小节内的历史指称、外部手册编号(RM0090 等)、目标文档本身不用数字编号标题。
> 「变更记录小节内的历史指称」这条跳过规则为何还留着:2026-10-28 起新文档不再设该节(见 §5.1),它现在只为**保留变更记录的 3 篇真台账**与 `scripts/archive/` 下的归档文档服务——历史行里的编号是「当时那个编号」,既不回改也不校验。
> ⚠️ 状态词按**就近**(±40 字符)判定,不是整行——否则文件名本身含「不存在」「删除」等字样时会误豁免同行的其它真实引用(2026-09-25 实测修正)。
### 4.4 生成物与生成器:必须同一笔提交(`generated-fresh`)
**规矩:改了生成器(或它的输入)就必须重跑,并把生成物与生成器放进同一笔提交。** 判据不是
「看起来差不多」而是**逐字节**:`generated-fresh` 钩子把生成器拷进**系统临时目录**重跑一遍,
与工作区逐字节比对,任何差异(含「重跑会新增文件」)即拦提交。钩子本身不写仓库,因此它不需要
还原现场、也不会 stash 别人在飞的改动(多人共用一棵工作树也能跑)。
为什么这条要机械化(两条实证,不是假想):
- `0f7b5a87`(编号迁移)改了 `tools/menu_gen/gen_menu.py` 的模板文字却没重跑生成物 → HEAD 里
`Drivers/BSP/UI/ui_menu_tab.h` 与模板不一致,后来是别人重跑生成器才被顺带纠正(属主确认以
生成器输出为准)。
- 2026-10-26 接线前实测又扫出 5 张 `docs/images/*.svg` 与各自生成器不一致——全是文档挪目录后
`scripts/rename_ref.py` 改写了 `tools/` 里的引用路径、**产物没跟着重跑**(见 §3.6)。
事故形态:**改生成器的人看不到任何 diff**(脚本改动编译、单测全过,因为产物没变),代价却
转嫁给下一个碰巧重跑生成器的人——他要么连带提交一段「不是我改的」改动,要么 revert 掉别人
真正的修复。两种处置都错,而门禁能让他不必遇到这个选择。
在册生成器(判定表 = `scripts/check_generated_fresh.py` 里的 `GENS`;新增生成器同笔在此登记一行):
| 生成器 | 生成物 | 一并核对的副作用 |
|---|---|---|
| `tools/menu_gen/gen_menu.py` | `Drivers/BSP/UI/ui_menu_tab.h`、`Drivers/BSP/UI/ui_menu_tab.c`、`Drivers/BSP/UI/ui_page_registry.h`、`Drivers/BSP/UI/ui_page_registry.c` | 回写 `MDK-ARM/STM32F407-Demo.uvprojx` 与 `simulator/CMakeLists.txt`(双端注册表一起比) |
| `tools/font_gen/extract_cn.py` | `ui_cn_chars.txt` | **唯一不按裸字节判**的一项,见下 |
| `tools/font_gen/make_font.py` | `Drivers/BSP/UI/ui_font_cn_12.c` | 需 node + 全局 lv_font_conv + 系统宋体,缺则该项 SKIP |
| `tools/arch_diagram/gen_*.py`(逐个) | `docs/images/*.svg` | 5 条存量在基线、只减不增;业务生成物零豁免面 |
两点配套:
- **改名 / 挪目录之后**(§3.6):`scripts/rename_ref.py` 会改写 `tools/` 里生成器源码的引用
路径,但**不会**重跑任何生成器 → 只要 `tools/` 被它改过,就要重跑对应生成器再提交。
- **字库清单不是裸字节判定**:`ui_cn_chars.txt` 按 **font-regen** 约定 = 源码在用 ∪ `KEEP_EXTRA`
(历史落盘记录用字),钩子改判「txt 恰等于 重跑结果 ∪ `KEEP_EXTRA`」,两向都不许差;清单一律
直载 `scripts/check_font_charset.py`,不留第二份(两套清单必然漂移,教训见 `docs/代码规范/pre-commit使用说明.md` §5.5)。
逐项详解(八段:查什么 / 拦下过什么真事 / 触发面 / 报错怎么读 / 限制与盲区 / 豁免与基线)
→ `docs/门禁说明/generated-fresh.md`。
---
## 5. 文档结构
### 5.1 必备要素
| 要素 | 要求 |
|---|---|
| **一级标题** | 文档首行为 `# 标题`,标题即主题,不写"文档""说明"等冗余后缀 |
| **开篇引言** | 可选 `>` 引用块,写明**适用对象 / 阅读前提 / 相关文档** |
| **章节编号** | 多章节文档用 `## 1.` `### 1.1` 编号,便于交叉引用(`§3.2`) |
| **变更记录** | **一律不设该节**(用户裁定 2026-10-28 起新文档、2026-10-29 起老文档也清完;标题变体 `修订记录` / 带编号的 `## 6. 变更记录` / `## 九、变更记录` 同规则)——文档只写现状、改了什么是正文说了算,改动过程查 `git log`。**例外只有 5 篇**(整表保留:表里是真台账、且被别处当唯一证据出处引用):`docs/子系统设计/看门狗设计说明.md`、`docs/子系统设计/配置文件设计说明.md`、`docs/内存管理/RAM预算台账.md`、`docs/UI设计/内容页架构.md`、`docs/环境与部署/本地工具路径环境变量方案.md`;`scripts/archive/` 下的归档文档整片都是历史,不受本条约束 |
> 数"还剩几篇留着这节"的命令(**别只 grep 裸标题**——带编号的 `## 6. 变更记录`、中文序号的 `## 九、变更记录`、`## 修订记录` 三种写法都要算):
> `grep -rn "^## " docs scripts` 再看命中的标题;2026-10-29 实测 **5 篇**真台账(上表那五篇)+ `scripts/archive/` 归档一篇。
### 5.2 新增文档检查清单
新增一篇文档时逐项确认:
- [ ] 放在正确的子目录(目录名无空格)
- [ ] 文件名符合命名约定(无空格 / 主题词+类型后缀)
- [ ] UTF-8 无 BOM、LF 换行
- [ ] 互链用仓库根相对路径;图片用当前目录相对路径
- [ ] 尚未创建/已删除的引用加 `[计划]` 或状态词
- [ ] 已在 [`README.md`](README.md) 对应章节登记一行(**否则视为未完成**)
- [ ] 若涉及任务表/栈,跑 `gen_task_table.py --check`
- [ ] `pre-commit run --files <新文档>` 通过
---
## 6. 与代码文档的边界
| 内容 | 归属 |
|---|---|
| 项目级铁律、构建/烧录/测试命令、平台约束 | `AGENTS.md`(AI 协作上下文,**精简优先**) |
| 开发者入门、架构速览、目录导航 | `README.md` |
| 详细设计、专项说明、测试指南 | `docs/` 下对应子目录 |
| 跨文档待办登记 | `docs/任务规划/README.md`(统一登记簿) |
| 评审报告与专项审计 | `docs/问题记录/` |
> **避免重复**:同一事实只在一处详述,其它位置用链接指向。典型如 FreeRTOS 任务表——`Src/freertos.c` 是唯一事实源,`README.md`/`AGENTS.md` 的表由脚本生成,不在别处再抄一份。
---
## 7. 写作约定
| 项 | 约定 |
|---|---|
| 语气 | 陈述事实,不用"我们认为""大家注意"等口语 |
| 标点 | 中文正文用全角标点;代码、路径、命令、文件名用行内代码 ``` `...` ``` |
| 术语 | 首次出现给全称 + 缩写(如"配置引擎(cfg_file)"),此后统一用同一术语 |
| 表格 | 对比、清单、参数优先用表格而非长段落 |
| 命令 | 用 `powershell` 代码块,含 `& '.venv\Scripts\python.exe'` 等本仓库统一入口 |
| 图 | 拓扑类用 Mermaid 代码块;技术配图走 SVG 生成器(见 [`架构设计/图形绘制流程.md`](架构设计/图形绘制流程.md)) |
| 状态标记 | ✅ 已完成 / ⚠️ 注意事项 / ❌ 禁止 / 📌 要点(克制使用,不滥用 emoji) |
---
## 8. 常见错误速查
| 现象 | 原因 | 修复 |
|---|---|---|
| `doc-links` 报失效链接 | 文件被移动/删除后引用未同步 | 改引用路径,或加 `[计划]` / 状态词 |
| `doc-links` 报语法示例行 | 链接示例没包反引号 | 把示例包进 `` ` `` |
| `task-table` 报不一致 | `freertos.c` 改栈后文档未同步 | 跑 `gen_task_table.py --write` |
| 提交 diff 出现整文件行尾变化 | 脚本改写时强制了 LF/CRLF | 改写须按原换行符回写(见 §2) |
| 图片不显示 | 文档迁入子目录后未改 `../images/` | 补 `../`(见 §3.2) |
| 引用"计划中"文件被拦 | 没加 `[计划] ` 前缀 | 加前缀 |