登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号) 一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、 采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。 门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏 undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8 reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF; id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。 test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字, 是本批唯一改变构建产物的文件集合。 为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged 部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、 不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。 实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01% (基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 / 告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。 基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处 引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使 识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。 ⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二 根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有), 今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。 固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0; App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889 断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿; check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带 BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。 .cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法: Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。 同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%, RW_CCM 57,376 B / 87.9%,阈值 92%。
6.5 KiB
layer-deps — 分层方向(Drivers/BSP/** 不得 include App/** 的头)
一句话:它只查一条边——下层(BSP)有没有反向依赖上层(App)。它不管同层乱依赖、 不管传递依赖,也不管"这行 include 是不是真的需要"。
- 配置:
entry: python scripts/check_layer_deps.py、pass_filenames: false、files: ^Drivers/BSP/|^scripts/check_layer_deps\.py$|^\.pre-commit-config\.yaml$。 - 规范出处:
docs/架构设计/项目架构总览.md(六层单向:业务 → UI/基础服务 → 中间件 → BSP 驱动 → HAL/RTOS → 硬件)、AGENTS.md分层边界。
1. 它在查什么
- 取
git ls-files的全部文件(不扫构建产物); - 用
App/**下的.h建**按文件名(basename)**的索引; - 扫
Drivers/BSP/**的.c/.h,抓#include "..."形式的引号路径; - 命中 App 头 → 记一条边
(引用方文件, App 头);先按完整路径精确匹配,精确命中为空才退回同名候选; - 头在
ALLOW集合里 → 永久豁免(横切基础设施);否则与BASELINE比对:新增即拦,消失即提示收紧。
ALLOW 三条(新增条目必须先回答"它为什么不是上层服务",否则就是给自己开后门):
App/log/console_log.h(日志后端,BSP 的 dbg_log.h 本来就建在它上面)、
App/util/sys_clock.h(时间源,日志时间戳的唯一提供者)、App/util/sha256.h(纯算法、无状态)。
BASELINE 现存三条(2026-10-22 清点六条 → 2026-10-25 道B 还清 UI 记录三条 → 现三条;只减不增):
NETCFG/net_cfg.c、NETCFG/ftp_cfg.c → App/cfg/cfg_file.h(配置引擎属主是 defaultTask);
NET/lftpd/lftpd_io.c → App/fs/vol_lock.h。
已还清:UI/ui_page_runlog.c、UI/ui_page_selftest.c、UI/ui_w_records.h → App/log/sys_log.h
(2026-10-25 task/UI-rec-reader:ui_w_records 改注入式 ui_rec_reader_t,契约见
docs/UI设计/UI_数据供给设计说明.md §7.2,绑定在 App/task/ui_task.c 与 simulator/main.c)。
修法是注入 / 传参(上层把数据或回调递进 BSP),不是把上层服务搬进 BSP。
2. 为什么要它
-
这句话在两份文档里写了很久,但此前没有任何机械检查:BSP 里可以随手
#include "cfg_file.h"而无人报警。 -
口径漂移是可测量的证据:2026-09 审查报告记"NETCFG 一处",2026-10-22 实测已长到 8 个文件 9 处。边界的静默腐蚀只能靠门禁止住——先钉基线,再只拦新增。
-
实测自证(落地当天):临时给
Drivers/BSP/RS485/rs485.c加一行#include "cfg_file.h"→ 钩子当场 FAIL 并点名该文件与该头(退出码 1),删掉即恢复。挂任何门禁前都该做一次这种注入自证。
3. 触发面
pass_filenames: false:每次都全库扫(反向依赖是全局事实,只看暂存文件会漏)。- 只看
Drivers/BSP/**里的.c/.h;App/**内部谁依赖谁、UI是否越层,一概不看。
4. 报错怎么读
[FAIL] 新增反向依赖(BSP -> App):
- Drivers/BSP/XXX/x.c -> App/cfg/cfg_file.h
- 单独跑:
--verbose列出全部边并标注 豁免/基线/新增;--prune只报已消失、可删的基线条目。 - 退出码 0 通过 / 1 有新增 / 2 用法或环境错误。
- ⚠ pre-commit 会 stash 未暂存改动:改完要先
git add再跑钩子,否则看到的是旧树。
报错时给的是三条修法(按优先级,脚本失败信息里也写着):
- 把上层能力注入给 BSP(函数指针 / 回调)——参照既有先例
net_cfg_set_save_req_cb、net_cfg_request_apply; - 由上层把数据递进来(UI 页面要读记录,就让上层读好再把缓冲传进来);
- 确认它其实是横切叶子能力 → 才谈
ALLOW(见 §7)。
⚠ 本门不是运行时缺陷清单:运行时属主纪律本来就没破(UI 只投递 net_cfg_save_request(),
真正写盘在 defaultTask;模拟器也早在主机侧跑整套逻辑)。它拦的是静态 include 方向这件事本身。
5. 好处
- 把"分层"从 PPT 变成一条命令的事实;债务总量随时可查(
--verbose就是清单)。 - 存量与新增分离,不阻塞正常工作,但保证它只会变小。
6. 限制与盲区
- 只查直接 include,查不到传递依赖。BSP 的
a.h里 include 了 App 头,BSP 的b.c只 includea.h——b.c这条边不出现,但a.h那条会报。原基线ui_w_records.h的 注释专门写着"(组件头,会外溢到 include 链)"(该条 2026-10-25 已还清),就是提醒这种外溢要人来看。 - 只认
#include "引号"形式;#include <App/...>这类写法不在管辖内(也违反 include 规范, 由coding-standard的 CODE-RULE-11 兜)。 - 按 basename 匹配:如果
App/与别处存在同名头,会按同名候选放宽(先精确后同名), 可能把边记到错误的属主上;反向地,如果 App 头改名而 BSP 用别的路径引到它,判定也会变。 - 只管
BSP → App一个方向。App → UI、UI → 驱动内部这类越层不在本门范围。 - 它不看是否真的用到那个头里的东西:删掉最后一处使用却忘了删 include,边仍然存在,
--prune也就不会提示你收紧基线。 git不可用时退回os.walk(跳过.git/.venv/build)——两条路径的清单可能不同, 在无 git 环境下跑出的"通过"不能当证据。- 绿灯的含义只是"没有新增反向边":现存 3 条存量债务仍然在那里(2026-10-25 还清 3 条)。
7. 豁免与基线
BASELINE与ALLOW都写在脚本里(不是外部台账)。只减不增,脚本没有--write。- 两类豁免语义不同,别混:
ALLOW= 永久横切能力(判"它是不是叶子");BASELINE= 待还债(判"这条边什么时候拆")。
8. 改这个门禁本身
- 往
ALLOW加条目要写理由,并想清楚会不会变成"给上层开后门";能进基线就别进ALLOW。 - 还掉一条债就跑
--prune并删掉那一行;基线只减不增,加行等于放弃约束。
9. 变更记录
| 日期 | 变更 |
|---|---|
| 2026-10-23 | 首篇逐项文档(在册 6 条基线逐条列出,含每条的属主背景) |
| 2026-10-25 | 道B(task/UI-rec-reader)还清 UI 记录 3 条:ui_w_records 注入式 reader(§7.2 契约落地),基线 6 → 3 |