# 文档规范 > **适用对象**:本仓库 `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` 的 ``、`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` 的 ``(→ `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 | 栈 (words) | 优先级 | |---|---|---| | ... | ``` **内存/Flash 快照**(`docs/内存管理/RAM使用分析与优化方案.md` §2.1): ```markdown | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| | ... | ``` > **改栈流程**:先改 `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) | | 引用"计划中"文件被拦 | 没加 `[计划] ` 前缀 | 加前缀 |