feat(门禁): 新增 layer-deps——BSP 反向 include App 的增量拦截(37→38)
推荐清单第 5 步的拆分执行:反转依赖(5a)暂缓,**只把"防扩大"的门禁先上**。
**为什么门禁比搬迁值钱**:AGENTS.md 与分层图早就写着"六层单向依赖",可从来没有
机械检查——于是同一件事的记载从 2026-09 审查报告的"NETCFG 一处",漂到 2026-10-22
实测的 **8 个文件 9 处**,全靠人读代码才发现。边界的静默腐蚀只能靠门禁止住。
**机制(与 `id-check` 的增量拦截同构)**:`scripts/check_layer_deps.py` 扫
`Drivers/BSP/**.{c,h}` 的 include,按 `App/**` 头文件索引解析反向边(同名头按字面
路径消歧),命中分三类:
· ALLOW 永久豁免 3 条横切叶子头——`App/log/console_log.h`(日志后端,BSP 的
dbg_log.h 就建在它上面)、`App/util/sys_clock.h`(时间源)、`App/util/sha256.h`
(纯算法、无状态)。它们是"任何层都要用的叶子能力",不是上层服务;新增豁免
必须先回答"它为什么不是上层服务"。
· BASELINE 钉住存量 6 条:`cfg_file.h`×2(NETCFG)、`vol_lock.h`×1(lftpd_io)、
`sys_log.h`×3(UI 两处 + `ui_w_records.h`)。**只减不增**(脚本没有 `--write`)。
· 新出现的 (BSP 文件, App 头) 组合 → FAIL,并给出三条出路(注入回调 / 上层传参 /
确属叶子才可豁免)。
`--prune` 在某条存量还清时点名"可从基线删除",于是"还欠几条"成为可见进度,而不是
文档里的一句口号。
**当场自证**:落地时临时给 `Drivers/BSP/RS485/rs485.c` 加了一行 include `cfg_file.h
`,钩子当场 FAIL 并点名该文件与该头(退出码 1),删掉后转绿——没被触发过的门禁
等于没有门禁。
**边界要说清(免得被误读成缺陷)**:本钩子只拦**静态 include 方向**。运行时的属主
纪律本来就没破——UI 只投递 `net_cfg_save_request()`,真正写盘在 defaultTask,模拟器
也早在主机侧跑整条配置链。
**口径同步**:钩子数 37 → 38 共 6 处(`AGENTS.md` 两处、`pre-commit使用说明.md` 两处
并新增 §2.26 详解、`并行任务工作树方案.md`、报告 §0.1/§1.1);
`项目架构总览.md` §1 分层原则处补"此方向已机械检查";报告 §0.4 的 S-1 计数由
"7 文件 15 处"更正为脚本口径的 8 文件 9 处,§9 状态改 🟡。
**暂缓项(都已在报告 §0.4 记因)**:5a 反转依赖——没有功能在等它,且要过"改 IP
免重启生效"整条真机链,留给下次真动 NETCFG/UI 时顺带做;5b 目录搬迁——uvprojx 326
条 + IncludePath + CMakeLists + 42 个 UI 文件 + 全库引用改写,收益只是"目录名好看",
而远程 CI 仍刻意不跑 `@build`,兜底未成立。
门禁:`layer-deps` 自身通过;`check_doc_links.py --all --sections-strict` 146 文档 0
问题;`check_ids.py --strict` 增量拦截通过、可判定 82.84%;`mem-numbers` 0;
`py_compile` 通过。本轮无 `.c/.h` 改动,未重跑 `@build`。未推送。
This commit is contained in:
@@ -548,3 +548,22 @@ repos:
|
||||
language: system
|
||||
files: \.(md|c|h|py|ya?ml|json|txt)$|^\.pre-commit-config\.yaml$
|
||||
pass_filenames: false
|
||||
|
||||
# 14) 分层方向门禁(2026-10-22 加,报告 §0.4 结构债 S-1 的"防扩大"那一半)
|
||||
# 背景:AGENTS.md 与 项目架构总览.md 早就写着"六层单向依赖",但**从来没有机械检查**
|
||||
# ——Drivers/BSP/ 里可以随手 include "cfg_file.h" 而无人报警。后果可测量:2026-09 审查
|
||||
# 报告记"NETCFG 一处",2026-10-22 实测已长到 **8 个文件 9 处**。边界的静默腐蚀只能
|
||||
# 靠门禁止住,所以本钩子做**增量拦截**(与 id-check 同构):现状 6 条钉成 BASELINE
|
||||
# (只减不增,没有 --write 往里加),新出现的 (BSP 文件, App 头) 组合直接 FAIL;
|
||||
# 3 条横切基础设施头走 ALLOW 永久豁免(日志后端 / 时间源 / 纯算法工具——它们是
|
||||
# 任何层都要用的叶子能力,不是上层服务)。修完存量后跑 --prune 会点名让你收紧基线,
|
||||
# 于是"还欠几条"变成可见进度,而不是文档里的一句口号。
|
||||
# 注:本钩子只拦"静态 include 方向";**运行时的属主纪律本来就没错**(UI 只投递
|
||||
# net_cfg_save_request(),写盘在 defaultTask),别把它当缺陷读。
|
||||
# 扫描范围恒为全库(要跨文件建 App 头索引),故 pass_filenames: false。
|
||||
- id: layer-deps
|
||||
name: 分层方向(BSP 不得反向 include App)
|
||||
entry: python scripts/check_layer_deps.py
|
||||
language: system
|
||||
files: ^Drivers/BSP/|^scripts/check_layer_deps\.py$|^\.pre-commit-config\.yaml$
|
||||
pass_filenames: false
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
|
||||
## 提交门禁(pre-commit)
|
||||
|
||||
- 任一失败即拦截,共 **37 个钩子**(逐个详解 → `docs/代码规范/pre-commit使用说明.md`):通用 5 + `gitlint` + `cppcheck` / `test-cppcheck` + `no-mojibake` + `coding-standard` + 文档三件套 `doc-links` / `task-table` / `mem-numbers` + `id-check`(**编号门禁**,`docs/编号规范.md` §8 八项校验;**已关闸为【增量拦截】**:只拦 `scripts/id_known_backlog.json` 台账之外的新错误级问题,存量按指纹豁免、台账只减不增;另有一根恒生效的线——可判定比例跌破 `id_registry.json` 基线即失败)+ `lv-conf-sync`(两份 `lv_conf.h` 特性子集一致)+ `cubemx-survival` + 主机侧单测 16 个(`-unit-test` 后缀)+ `test-suite-switch` + `registration-check`(双端注册反向校验 + UI 页面描述符表接线,N-23/U4)+ `mem-budget`(RAM 92% 预算门禁,N-27)+ `font-charset`(UI 字符集差集检查,防缺字白框,U4)+ `ui-golden`(UI 像素回归,菜单 8 状态 + 内容页 11 页黄金参考,U4)
|
||||
- 任一失败即拦截,共 **38 个钩子**(逐个详解 → `docs/代码规范/pre-commit使用说明.md`):通用 5 + `gitlint` + `cppcheck` / `test-cppcheck` + `no-mojibake` + `coding-standard` + 文档三件套 `doc-links` / `task-table` / `mem-numbers` + `id-check`(**编号门禁**,`docs/编号规范.md` §8 八项校验;**已关闸为【增量拦截】**:只拦 `scripts/id_known_backlog.json` 台账之外的新错误级问题,存量按指纹豁免、台账只减不增;另有一根恒生效的线——可判定比例跌破 `id_registry.json` 基线即失败)+ `lv-conf-sync`(两份 `lv_conf.h` 特性子集一致)+ `cubemx-survival` + 主机侧单测 16 个(`-unit-test` 后缀)+ `test-suite-switch` + `registration-check`(双端注册反向校验 + UI 页面描述符表接线,N-23/U4)+ `mem-budget`(RAM 92% 预算门禁,N-27)+ `font-charset`(UI 字符集差集检查,防缺字白框,U4)+ `ui-golden`(UI 像素回归,菜单 8 状态 + 内容页 11 页黄金参考,U4)+ `layer-deps`(**分层方向**:`Drivers/BSP/**` 不得 include `App/**` 的头;存量 6 条基线只减不增,报告 S-1)
|
||||
- **范围 = 项目代码**:第三方(`Lib/`、`Middlewares/`、CMSIS、HAL、`lftpd`、`dhara`、`MDK-ARM`)排除;`cppcheck`/`coding-standard` 另排除 `test/`,规范检查再排除 CubeMX 生成的 `Src/`/`Inc/`。**`simulator/` 已纳入全部钩子**(2026-10-11)——生成物只靠 `.gitignore` 挡,**不在 `exclude` 里写目录名**(教训见 `docs/代码规范/pre-commit使用说明.md` §5.5)
|
||||
- ⚠ **pre-commit 4.x 已移除 `always: true`**(实测对 `local` 钩子只告警 `Unexpected key(s)` 并跳过)→ 想"总是跑"的钩子**必须让 `files:` 覆盖到所有会触发它的文件**,没有兜底开关
|
||||
- **改写型钩子(`trailing-whitespace` + `end-of-file-fixer`)豁免 CubeMX/Keil 生成文件**(配置里 YAML 锚点 `&cubemx-generated`);⚠ **只读检查不受影响**:`cppcheck` / 乱码 / `cubemx-survival` / `task-table` / `mem-numbers` 仍覆盖 `Src/`、`Inc/`
|
||||
@@ -173,7 +173,7 @@
|
||||
| ADC 采样节拍 / 通道映射 / V0.1 硬件缺陷 | `docs/子系统设计/ADC采样节拍设计说明.md`、`docs/硬件平台/ADC通道映射说明.md` |
|
||||
| 存储栈(FatFs / dhara / lftpd / 两条 FatFs 陷阱) | `docs/架构设计/STM32F4-Base存储架构说明.md` |
|
||||
| RAM / 栈预算 / 容量台账 | `docs/内存管理/RAM使用分析与优化方案.md`、`内存内容分配策略.md` |
|
||||
| pre-commit 37 个钩子逐个详解 | `docs/代码规范/pre-commit使用说明.md` |
|
||||
| pre-commit 38 个钩子逐个详解 | `docs/代码规范/pre-commit使用说明.md` |
|
||||
| 实体编号怎么写 / 前缀总表 / `id-check` 八项校验 | `docs/编号规范.md`(§4 总表、§8 门禁与实测基线) |
|
||||
| 代码规范 V1.0 / cppcheck 口径 | `docs/代码规范/嵌入式C语言代码规范_V1.0.md`、`cppcheck使用说明.md` |
|
||||
| 自动化测试 / 实机测试 / 板级套件 | `docs/测试说明/自动化测试流程.md`、`实机测试实施指南.md`、`board-test-suite` skill |
|
||||
|
||||
@@ -461,6 +461,19 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法
|
||||
| **显式盲区(不是"检查通过")** | ① 裸形同字母多义(§1.2:`P1` 五义 / `L1` 四义 / `U1` 四义 / `D7` 三义)与元器件位号同形(§4.4)→ 裸形族**只在属主登记处文档内**判越游标,文档外降告警;② 圈号编号(①②③…)与正常列举无法机械区分,不入判定;③ `docs/芯片手册/`、`docs/硬件平台/` 整体排除(位号 `B1`/`C1`/`L1` 与编号同形,是撞形根源)。前两块收口属 §9 阶段 1-②/1-③ 迁移批次,落地后再扩判定范围——盲区登记在 `id_registry.json` 的 `blind_spots` / `unmanaged_bare` |
|
||||
| **运行器** | `scripts/check_ids.py`(纯标准库;`--strict` 拦提交、`--update-baseline` 抬基线(**只许涨**,跌则拒绝写入)、`--only 1,3,5` 跑部分、`--top N` / `--verbose` 出细节、`--no-trace` 跳过 TC 追溯;退出码 0=通过或仅告警 / 1=错误或比例跌破基线 / 2=环境错误) |
|
||||
|
||||
### 2.26 layer-deps — 分层方向:BSP 不得反向 include App(2026-10-22 加,报告 §0.4 结构债 S-1)
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| **为什么需要** | AGENTS.md 与本仓分层图一直写着"六层单向依赖",但**从来没有机械检查**——`Drivers/BSP/` 里可以随手 `#include "cfg_file.h"` 而无人报警。后果可测量:2026-09 审查报告记"NETCFG 一处",2026-10-22 实测已长到 **8 个文件 9 处**。边界的静默腐蚀只能靠门禁止住 |
|
||||
| **判据** | 扫 `Drivers/BSP/**.{c,h}` 的 `#include "..."`,按 `App/**` 的头文件名索引解析(同名头按字面路径消歧)。命中后分三类:**ALLOW 豁免**(横切叶子能力:`App/log/console_log.h` 日志后端、`App/util/sys_clock.h` 时间源、`App/util/sha256.h` 纯算法——任何层都要用、不依赖上层服务)/**BASELINE 存量**(2026-10-22 清点的 6 条:`cfg_file.h`×2、`vol_lock.h`×1、`sys_log.h`×3)/**新增 → FAIL** |
|
||||
| **增量拦截语义** | 与 `id-check` 同构:**基线只减不增**,脚本没有 `--write` 往里加。修完一条跑 `--prune`,它会点名"这条已消失,请从 `BASELINE` 删除"→ "还欠几条"变成可见进度而不是一句口号 |
|
||||
| **修法(报错时给用户的三条路)** | ① 把上层能力**注入**给 BSP(函数指针/回调,参照 `net_cfg_set_save_req_cb` / `net_cfg_request_apply` 的既有先例);② 由上层把数据取好**传进来**(参数/结构体),BSP 不自己去拿;③ 若它其实是叶子工具(无状态、不依赖上层服务),才可加进 `ALLOW` 并注明理由 |
|
||||
| **不是缺陷清单** | 本钩子只拦**静态 include 方向**。**运行时的属主纪律本来就没破**——UI 只投递 `net_cfg_save_request()`,真正写盘在 defaultTask;模拟器也早在主机侧跑整条配置链。别把它读成"以前会写出错" |
|
||||
| **配置** | `files: ^Drivers/BSP/\|^scripts/check_layer_deps\.py$\|^\.pre-commit-config\.yaml$`、`pass_filenames: false`(要跨文件建 App 头索引,恒扫全库;实测 83 个 BSP 源文件,毫秒级) |
|
||||
| **运行器** | `scripts/check_layer_deps.py`(纯标准库;`--verbose` 列全部边与判定、`--prune` 只报可删基线;退出码 0=通过 / 1=新增反向依赖) |
|
||||
| **实测自证** | 落地时做过一次"故意违规"验证:临时给 `Drivers/BSP/RS485/rs485.c` 加一行 `#include "cfg_file.h"` → 钩子当场 FAIL 并点名该文件与该头(退出码 1),删掉后转绿。**没有当场触发过的门禁等于没有门禁** |
|
||||
|
||||
---
|
||||
|
||||
## 3. 日常工作流
|
||||
@@ -588,7 +601,7 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env
|
||||
|
||||
| 文件 | 位置 | 说明 |
|
||||
|---|---|---|
|
||||
| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(**37 个钩子**,2026-10-20 清点;本文 §2 详解非单测类——N-19 新增的两个单测钩子见 §2.16/§2.17,N-23 双端注册反向校验(含 U4 补强的 UI 页面描述符表接线 ④)见 §2.18,N-27 RAM 预算门禁见 §2.19,B4 IEC104 协议栈单测见 §2.20,B8 频率测量单测见 §2.21,B9 SOE 动作记录单测见 §2.22,N-21 dhara NAND 移植层单测见 §2.23,U4 残余字符集差集检查见 §2.24,编号门禁八项校验见 §2.25,其余主机侧单测见 AGENTS.md 门禁条;主机侧单测共 16 个;UI 门禁另有 `font-charset` 与 `ui-golden`) |
|
||||
| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(**38 个钩子**,2026-10-22 清点;本文 §2 详解非单测类——N-19 新增的两个单测钩子见 §2.16/§2.17,N-23 双端注册反向校验(含 U4 补强的 UI 页面描述符表接线 ④)见 §2.18,N-27 RAM 预算门禁见 §2.19,B4 IEC104 协议栈单测见 §2.20,B8 频率测量单测见 §2.21,B9 SOE 动作记录单测见 §2.22,N-21 dhara NAND 移植层单测见 §2.23,U4 残余字符集差集检查见 §2.24,编号门禁八项校验见 §2.25,分层方向门禁见 §2.26,其余主机侧单测见 AGENTS.md 门禁条;主机侧单测共 16 个;UI 门禁另有 `font-charset` 与 `ui-golden`) |
|
||||
| `.gitlint` | 仓库根 | gitlint 规则(宽松,兼容中文) |
|
||||
| `.cubemx_survival_baseline.json` | 仓库根 | CubeMX 易失定制基线(哈希;`--write` 重建) |
|
||||
| `scripts/check_mojibake.py` | scripts/ | U+FFFD 检测脚本 |
|
||||
@@ -600,6 +613,7 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env
|
||||
| `scripts/check_lv_conf_sync.py` | scripts/ | 两份 `lv_conf.h` 特性子集一致性(比对生效值 + 无效宏名 + 重复定义) |
|
||||
| `scripts/check_test_suite.py` | scripts/ | 测试套件开关门禁(防测试固件静默提交) |
|
||||
| `scripts/check_font_charset.py` | scripts/ | UI 字符集差集检查(复用 `tools/font_gen/` 提取与 cmap 解析规则,防缺字白框) |
|
||||
| `scripts/check_layer_deps.py` | scripts/ | 分层方向门禁(BSP 反向 include App 的增量拦截,存量基线只减不增) |
|
||||
| `docs/代码规范/嵌入式C语言代码规范_V1.0.md` | docs/代码规范/ | 代码规范原文(CODE-RULE-01~CODE-RULE-19 规则来源) |
|
||||
| `docs/代码规范/cppcheck使用说明.md` | docs/代码规范/ | cppcheck 钩子详解(规则/配置/抑制) |
|
||||
| `docs/文档规范.md` | docs/ | 文档体系规范(链接形态/命名/门禁要求) |
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
> 上图由 `tools/arch_diagram/gen_arch_svg.py` 生成(**手绘 SVG**,布局确定、零连线交叉);改架构后编辑脚本顶部 `ROWS` 数据重跑即可,源数据与 §1.1 表一致。
|
||||
> 需要 PNG 预览(PPT/聊天分享):用 Edge 无头渲染 `docs/images/项目架构总览.svg` 即可(PNG 不入库)。
|
||||
|
||||
> 分层原则:**上层只依赖相邻下层(单向)**;**① 业务层为顶层**(仅 `net_cfg`,被 UI 与基础服务共用),**② UI 层与 ③ 基础服务层同级并列**。跨层的具体调用关系见 §1.2 表。
|
||||
> 分层原则:**上层只依赖相邻下层(单向)**——2026-10-22 起这条方向已有机械检查(`layer-deps` 钩子 / `scripts/check_layer_deps.py`:`Drivers/BSP/**` 不得 include `App/**` 的头,存量 6 条反向边钉成基线、只减不增,三个横切叶子头 `console_log.h`/`sys_clock.h`/`sha256.h` 显式豁免);**① 业务层为顶层**(仅 `net_cfg`,被 UI 与基础服务共用),**② UI 层与 ③ 基础服务层同级并列**。跨层的具体调用关系见 §1.2 表。
|
||||
|
||||
### 1.1 各层职责与关键模块
|
||||
|
||||
|
||||
@@ -397,7 +397,7 @@ docs\内存管理\RAM使用分析与优化方案.md:585 [路径引用] docs/内
|
||||
|
||||
| 主题 | 去处 |
|
||||
|---|---|
|
||||
| 提交门禁 37 个钩子 | `docs/代码规范/pre-commit使用说明.md` |
|
||||
| 提交门禁 38 个钩子 | `docs/代码规范/pre-commit使用说明.md` |
|
||||
| 自动化测试与台架流程 | `docs/测试说明/自动化测试流程.md` |
|
||||
| 文档写作规范(编码/链接/登记) | `docs/文档规范.md` |
|
||||
| 工具路径环境变量(换机) | `docs/环境与部署/本地工具路径环境变量方案.md` |
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| 维度 | 评级 | 今天的实测依据 | 相比 2026-10-15 的变化 |
|
||||
|---|---|---|---|
|
||||
| 结构与分层 | 🟢 良好 | 单向依赖未被破坏;双端注册反向校验 PASS;326 个编译单元全部可追溯 | 无退化 |
|
||||
| 文档与治理 | 🟢 良好 | 37 个提交钩子 + 16 套主机单测;工程 md 91 份;`doc-links` 全库 0 问题 | 新增 `编号门禁增量拦截` 与内存快照回填常态化 |
|
||||
| 文档与治理 | 🟢 良好 | 38 个提交钩子 + 16 套主机单测;工程 md 91 份;`doc-links` 全库 0 问题 | 新增 `编号门禁增量拦截` 与内存快照回填常态化 |
|
||||
| 内存与 Flash | 🟡 可控但已换位置 | SRAM1 **57.4%**(余 55,808 B)、Flash **22.1%**、**CCM 87.9%(余 7,904 B)** | **红线从 SRAM1 迁到 CCM**(堆 2026-10-11 迁 CCM + 池 40→16 KB 后 SRAM1 已宽松) |
|
||||
| 可靠性与自恢复 | 🟢 良好 | 4 个 Fault 全采集现场;IWDG 五面 `required_mask`;复位原因与 `[STACK]` 水位常开 | 台架复跑持续 PASS(今日真机 708 ms 起网、无 `[ERR]`/Fault) |
|
||||
| 测试与验证 | 🟡 半办 | 16 套主机单测 + `ui-golden`/`font-charset` 入门禁;TC 双向追溯 593∶271 交集 265 | **仍无覆盖率**、`coding-standard` **仍无 `malloc` 正则** |
|
||||
@@ -48,7 +48,7 @@
|
||||
| 内存 RAM | R-2 剩余 / R-3 / R-4 / R-6 ⏸(N-27) | 待迁体量 29,570 B,而 CCM 只剩 7,904 B → 搬迁已无空间收益 | 暂缓;重开条件 §5.3 |
|
||||
| 内存 RAM | N-14 🟡(N-22) | FTL 单页写回缓存无空闲/掉电强制 flush(整页写与换页已 flush,`f_close` 经 `CTRL_SYNC` 已落盘) | 用户裁定 2026-10-22 暂不做;残余窗口只是"单条记录" |
|
||||
| Flash | **F-5 🟡 观察项**(N-28) | uvprojx 201 个 LVGL `.c` 里 **127 个空 TU**;增量构建 4~5 s、CI 不跑 `@build` | 有实测全量耗时痛点再做,用 `<IncludeInBuild>` |
|
||||
| 结构 | **S-1 ❌**(口径已按实测更正) | **不是"命名错位",是真实的 BSP→App 反向依赖**:`Drivers/BSP/NETCFG/{net_cfg,ftp_cfg}.c` include `App/cfg/cfg_file.h`(10 处调用点:`cfg_file_load/save/poll/register`),`Drivers/BSP/UI` 另有 3 个文件 include App 层头(`ui_page_runlog.c`、`ui_page_selftest.c`、`ui_w_records.h`);21 个 BSP 文件用 `dbg_log.h`(横切,待显式豁免) | **反转依赖**(约 7 文件 / 15 处)。⚠ 修正一条我先前写下的理由:这不是"运行时属主纪律被破"——UI 只投递 `net_cfg_save_request()`,写盘仍在 defaultTask,且 NETCFG 已有 `net_cfg_set_save_req_cb` / `net_cfg_request_apply` 的**回调注入**先例;也不是"测不了"——模拟器今天就在主机侧跑 `net_cfg_load → poll → save` 整条链(`../../simulator/README.md` 的验证链)。要修的只是**静态分层方向**(BSP 引用了 App 的头)+ 加一条「`Drivers/BSP/**` 不得 include `App/**`」的门禁;**目录搬迁暂缓**(远程 CI 仍不跑 `@build`,兜底未成立) |
|
||||
| 结构 | **S-1 已止住恶化**(2026-10-22 上门禁) | 不是"命名错位",是真实的 **BSP→App 反向 include**,精确口径 **8 个文件 9 处**(上一版写"7 文件 15 处"也不准,已按 `check_layer_deps.py` 判定重算):`cfg_file.h`×2(NETCFG)、`vol_lock.h`×1(lftpd_io)、`sys_log.h`×3(UI 两处 + `ui_w_records.h`);`console_log.h` / `sys_clock.h` / `sha256.h` 三个横切叶子头已**显式豁免**。**运行时的属主纪律本来就没破**(UI 只投递 `net_cfg_save_request()`,写盘在 defaultTask),要修的只是静态方向 | **门禁已落地**(`layer-deps` + `scripts/check_layer_deps.py`:存量 6 条基线只减不增、新增当场 FAIL,落地时做过故意违规自证)。剩下的才是反转:把 `cfg_file_*` 直接调用换成注入的 ops(NETCFG 已有 `net_cfg_set_save_req_cb` 先例);**目录搬迁仍暂缓**(远程 CI 不跑 `@build`,兜底未成立) |
|
||||
| 结构 | **S-2 只剩 1 处**(24 处手写已消 23) | 今天已消 23 处:2 处与 `Inc/spi.h`/`Inc/i2c.h` 重复(GD5F/SD2506)+ 19 处任务句柄与属性(新建 `App/task/task_handles.h`)+ 1 处 `main.c` 函数内 `g_rcc_csr_latched`(改由 `App/app/app_main.h` 声明)+ 1 处 `ch395f.c:177`(**改为契约转调**:GD5F 的完成标志收为文件内 `static`,导出 `gd5f_spi1_notify_done()` / `gd5f_spi1_notify_error()`,CH395F 那个"HAL 全局唯一回调"的 SPI1 分支只转调、不再直接写它的变量;`HAL_SPI_AbortCpltCallback` 仍无人实现 → 另列观察)。**只剩 `net_socket.c:77` 的 `netMsgQueueHandle`**。**两类不算债**:`stm32f4xx_it.c:241-256` 是 CubeMX「External variables」生成块(删了会被写回来);`:183/195/207/220` 是 `__asm` Handler 里的汇编标签引用(ARMCC `__asm` 取不到 C 数据符号,**合法例外**) | 剩下一步:`netMsgQueueHandle` 收进 net_socket 内部 + 提供投递 API |
|
||||
| 驱动规范 | S-5 ❌ | `bsp_err_t` 至今只出现在本报告里(无统一错误码/超时/重试约定) | 定义后进 `docs/代码规范/` |
|
||||
| 网络 | N-09 / PHY ❌(N-25) | `NET_MAX_SOCKETS` 仍为 8(与单连接复用语义不符);PHY 强制 100M 无自协商回退;PASV 每次新建监听 | 见 §4.2;随 N-25 一并处置 |
|
||||
@@ -66,7 +66,7 @@
|
||||
- 体量:`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`(2026-10-22 `@build`)→ `data/ram_report_20261022.md`
|
||||
- 栈:`STM32F407-Demo.htm`(静态调用图)+ 同日真机 `[STACK]` 心跳 → `data/stack_report_20261022.md`
|
||||
- 仓库:`git ls-files` 逐个 `stat`(必须 `-z`,否则中文名被控制台代码页破坏)
|
||||
- 门禁:`.pre-commit-config.yaml`(37 个 `- id:`)+ 各钩子今日实跑输出
|
||||
- 门禁:`.pre-commit-config.yaml`(38 个 `- id:`)+ 各钩子今日实跑输出
|
||||
- 待办:`docs/任务规划/登记簿.md`(在策 18 条)与 `docs/任务规划/README.md` §2
|
||||
|
||||
### 1.2 复现命令(本报告所有数字可重跑)
|
||||
@@ -374,7 +374,7 @@ W6 簇按 U4 → U1 → U3 → U2 的顺序执行完毕(U2 停在"够用即止
|
||||
| **D-1** | 外部资料与工程文档同仓同树 | ❌ 暂不处理 | 本文 §3.3、登记簿 N-29 |
|
||||
| D-2 ~ D-7 | 文档治理条目(链接/同源/编号/BOM 等) | ✅ 多数已制度化为钩子 | 本文 §3.2、`../文档规范.md` |
|
||||
| D-9 | 文档面残余观察 | 🟡 观察 | 本节即定义位 |
|
||||
| **S-1 / S-2** | 分层反向依赖 / 跨模块裸 `extern` | 🟡 半办(2026-10-22:24 处手写裸 extern 已消 23 处,只剩 `net_socket.c:77`;S-1 未动 = NETCFG+UI 反向 include) | 本文 §0.4、登记簿 C5 |
|
||||
| **S-1 / S-2** | 分层反向依赖 / 跨模块裸 `extern` | 🟡 半办(2026-10-22:24 处手写裸 extern 已消 23 处,只剩 `net_socket.c:77`;反向 include 8 文件 9 处已上门禁、6 条基线待还) | 本文 §0.4、登记簿 C5 |
|
||||
| **S-4** | `app_cfg.h` 未接线占位开关 | ✅ **已闭环 2026-10-22**(4 个零消费点宏删除) | `App/app/app_cfg.h` 修改记录 v1.2;口径同步见 `../环境与部署/项目部署环境要求.md` §3.2/§3.5、`../产品需求/产品需求设计文档.md`、`../任务规划/排期详述.md` C5 |
|
||||
| **S-3** | `.ioc` 侧 USART1 配置 | ❌ 在策 | 登记簿 S-3 |
|
||||
| **S-5** | 统一 `bsp_err_t` 错误码约定 | ❌ 未做 | 本文 §0.4、`docs/代码规范/`(待建) |
|
||||
@@ -409,6 +409,22 @@ W6 簇按 U4 → U1 → U3 → U2 的顺序执行完毕(U2 停在"够用即止
|
||||
**改判的三条旧结论**:① §6 的 U1/U3/U4 原写「❌ 未建」,实际已落地(`ui_tokens.h` 69 行、`ui_page_registry.c` 描述符注册表、`ui-golden`+`font-charset` 两枚钩子);U2 只有 `ui_w_records` 一个组件;"27 页未落地"实为 7 页。② "新增一页 ≤30 行"目标**未达**(最省 40 行),如实记为未达项。③ §0.4 与登记簿已漂开 → 本版把待办清单降级为**快照 + 指向登记簿**,权威只留一处。
|
||||
**刻意保留的三层**(删了会出事):§9 **编号族登记处**——本文是 `id_registry.json` 里 `N-RPT`/`D-RPT`/`S-RPT`/`R-RPT`(游标 R-9)/`F-RPT`/`U-RPT`(游标 U5) 六族的属主登记处,且外部存在「报告 N-14」「报告 F-4/F-5/F-6」「报告 D-1」「报告 R-2/R-3」式紧邻引用与 `check_dangling`,删定义位即触发编号门禁;§1.2/§1.3 方法与口径(数字可重跑);文末**变更记录 + 已销项去处表**原样冻结保留(52 行)。被外部点名的锚点(`§0.3`/`§3.1`/`§4.1`/`§4.2`/`§4.3`/`§5.3`×9/`§5.4`/`§5.5`×4/`§6.2`/`§6.3`/`§6.5`)逐个保留,**68 处入站引用零改动**。
|
||||
**门禁**:`check_doc_links.py --all --sections-strict` → 146 文档 0 问题(章节引用 2,499、可判定 1,483);`check_ids.py --strict` 增量拦截通过、**存量债 650 → 641(−9)**、可判定比例 82.98% → **82.79%**(仍高于基线 82.6%,但缓冲从 0.38pp 缩到 0.19pp——删掉的那批引用 99% 是可判定的,属预期副作用);`mem-numbers` / `ram_baseline --gate` / `task-table` / `cubemx-survival` 全 0。字节卫生:BOM 无 / CR 0 / U+FFFD 0 / 无行尾空格 / LF 结尾。 |
|
||||
| **2026-10-22** | **S-1 只做「防扩大」那一半:新增 `layer-deps` 分层方向门禁**(推荐清单第 5 步拆分执行,钩子 37 → **38**) | 上一轮的结论是「反转暂缓、门禁现在就该上」,这次照做。
|
||||
为什么门禁比搬迁值钱:AGENTS 与分层图早就写着「六层单向依赖」,可从来没有机械检查——于是同一件事的记载从 2026-09 的
|
||||
「NETCFG 一处」漂到今天的「8 文件 9 处」,全靠人读代码才发现。边界的静默腐蚀只能靠门禁止住。 |
|
||||
| | **机制(与 `id-check` 的增量拦截同构)**:`scripts/check_layer_deps.py` 扫 `Drivers/BSP/**.{c,h}` 的 include,按 `App/**`
|
||||
头索引解析反向边,分三类——ALLOW 永久豁免 3 条横切叶子头(`console_log.h` 日志后端 / `sys_clock.h` 时间源 /
|
||||
`sha256.h` 纯算法,任何层都要用、不依赖上层服务);BASELINE 钉住存量 6 条(`cfg_file.h`×2、`vol_lock.h`×1、
|
||||
`sys_log.h`×3);**新出现的组合直接 FAIL**。基线只减不增(脚本没有 `--write`),`--prune` 在某条还清时点名「可删」,
|
||||
于是「还欠几条」成为可见进度。挂 `files: ^Drivers/BSP/`、`pass_filenames: false`(要跨文件建索引,恒扫全库)。 |
|
||||
| | **当场自证**:落地时临时给 `Drivers/BSP/RS485/rs485.c` 加一行 `#include "cfg_file.h"` → 钩子当场 FAIL 并点名该文件与该头
|
||||
(退出码 1),删掉后转绿。**没被触发过的门禁等于没有门禁。** |
|
||||
| | **反转(5a)仍暂缓**:没有功能在等它,且要过「改 IP 免重启生效」整条真机链,留给下次真动 NETCFG/UI 时顺带做
|
||||
(N-25 网络项、U5 页面扩展都会碰到)。目录搬迁(5b)同样暂缓:uvprojx 326 条 + IncludePath + CMakeLists + 42 个 UI 文件
|
||||
+ 全库引用改写,收益只是「目录名好看」,而远程 CI 仍不跑 `@build`。 |
|
||||
| | **口径同步**:钩子数 37 → 38 共 6 处(`AGENTS.md` 两处、`docs/代码规范/pre-commit使用说明.md` 两处含新增 §2.26、
|
||||
`并行任务工作树方案.md`、本文 §0.1/§1.1);`项目架构总览.md` §1 分层原则处补「此方向已机械检查」。§0.4 的 S-1 计数由
|
||||
「7 文件 15 处」更正为脚本口径的 **8 文件 9 处**。本轮只加脚本 + 配置 + 文档,无 `.c/.h` 改动,故不重跑 `@build`。 |
|
||||
| **2026-10-22** | **S-2 第三步:SPI1 完成标志的回调归属拆清**(推荐清单第 4a 步) | 问题不是风格:HAL 的 SPI 完成/错误回调
|
||||
是**全局唯一符号**,全工程只能有一份实现,当前由 CH395F 代管并按 `hspi->Instance` 分发——于是 GD5F 的
|
||||
`g_spi1_dma_done` 被 CH395F 跨模块裸 `extern` 直接写,**GD5F 出错时的"释放等待者"寄居在别人的 .c 里**。
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""分层方向门禁:Drivers/BSP/** 不得 include App/** 的头(反向依赖增量拦截)。
|
||||
|
||||
背景(2026-10-22 加)
|
||||
----------------
|
||||
AGENTS.md 与 docs/架构设计/项目架构总览.md 都写着"六层单向依赖:业务 → UI/基础服务
|
||||
→ 中间件 → BSP 驱动 → HAL/RTOS → 硬件",但**此前没有任何机械检查**:Drivers/BSP/ 里
|
||||
可以随手 include "cfg_file.h" 而无人报警。后果是可测量的口径漂移——2026-09 审查报告
|
||||
记"NETCFG 一处",2026-10-22 实测已长到 8 个文件 9 处。边界的静默腐蚀只能靠门禁止住:
|
||||
先把现状钉成基线,然后**只拦新增**。
|
||||
|
||||
与 scripts/check_ids.py 的"增量拦截"同构:
|
||||
· 新出现的 (引用方, App 头) 组合 → FAIL(拦提交)
|
||||
· 基线里的组合已消失 → WARN,并提示收紧(基线只减不增,绝没有 --write 往里加)
|
||||
· 横切基础设施头走 ALLOW 永久豁免:它们是"任何层都要用的叶子能力",不是上层服务
|
||||
|
||||
用法
|
||||
----
|
||||
python scripts/check_layer_deps.py # 门禁模式(pre-commit / CI)
|
||||
python scripts/check_layer_deps.py --verbose # 列出全部反向边与判定
|
||||
python scripts/check_layer_deps.py --prune # 只报告可以删掉的基线条目
|
||||
|
||||
退出码:0 = 通过;1 = 发现新增反向依赖;2 = 用法/环境错误
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BSP_PREFIX = "Drivers/BSP/"
|
||||
APP_PREFIX = "App/"
|
||||
SRC_EXT = (".c", ".h")
|
||||
|
||||
# 横切基础设施:任何层都可用,允许被 BSP include。
|
||||
# 新增条目必须先回答"它为什么不是上层服务"——否则就是给自己开后门。
|
||||
ALLOW = {
|
||||
"App/log/console_log.h", # 日志后端:BSP 的 dbg_log.h 本来就建在它上面
|
||||
"App/util/sys_clock.h", # 时间源:日志时间戳的唯一提供者
|
||||
"App/util/sha256.h", # 纯算法工具:无状态、不依赖任何上层服务
|
||||
}
|
||||
|
||||
# 存量基线(2026-10-22 清点,只减不增)。每条 = (引用方, 被引用的 App 头)。
|
||||
# 修法是"注入/传参",不是把上层服务搬进 BSP——详见报告 §0.4 的 S-1 行。
|
||||
BASELINE = {
|
||||
("Drivers/BSP/NETCFG/net_cfg.c", "App/cfg/cfg_file.h"), # 配置引擎属主 = defaultTask
|
||||
("Drivers/BSP/NETCFG/ftp_cfg.c", "App/cfg/cfg_file.h"), # 同上([ftp] 段注册)
|
||||
("Drivers/BSP/NET/lftpd/lftpd_io.c", "App/fs/vol_lock.h"), # 卷锁在 App/fs
|
||||
("Drivers/BSP/UI/ui_page_runlog.c", "App/log/sys_log.h"), # 记录读取应由上层递进来
|
||||
("Drivers/BSP/UI/ui_page_selftest.c", "App/log/sys_log.h"), # 同上
|
||||
("Drivers/BSP/UI/ui_w_records.h", "App/log/sys_log.h"), # 同上(组件头,会外溢到 include 链)
|
||||
}
|
||||
|
||||
INC_RE = re.compile(r'^[ \t]*#[ \t]*include[ \t]*"([^"]+)"', re.M)
|
||||
|
||||
|
||||
def tracked_files():
|
||||
"""以 git ls-files 为准(不扫构建产物);git 不可用时退回 os.walk。"""
|
||||
try:
|
||||
out = subprocess.check_output(["git", "ls-files", "-z"], cwd=REPO)
|
||||
return [p.decode("utf-8") for p in out.split(b"\0") if p]
|
||||
except Exception: # noqa: BLE001
|
||||
files = []
|
||||
for root, dirs, names in os.walk(REPO):
|
||||
dirs[:] = [d for d in dirs if d not in (".git", ".venv", "build")];
|
||||
for n in names:
|
||||
files.append(os.path.relpath(os.path.join(root, n), REPO).replace(os.sep, "/"))
|
||||
return files
|
||||
|
||||
|
||||
def app_header_index(files):
|
||||
idx = {}
|
||||
for f in files:
|
||||
if f.startswith(APP_PREFIX) and f.endswith(".h"):
|
||||
idx.setdefault(os.path.basename(f), []).append(f)
|
||||
return idx
|
||||
|
||||
|
||||
def scan(files, idx):
|
||||
edges = set()
|
||||
for f in files:
|
||||
if not f.startswith(BSP_PREFIX) or not f.endswith(SRC_EXT):
|
||||
continue
|
||||
try:
|
||||
with open(os.path.join(REPO, f), encoding="utf-8-sig") as fh:
|
||||
text = fh.read()
|
||||
except (OSError, UnicodeDecodeError) as exc:
|
||||
print("[WARN] 读取失败,跳过:%s(%s)" % (f, exc))
|
||||
continue
|
||||
for m in INC_RE.finditer(text):
|
||||
literal = m.group(1)
|
||||
owners = idx.get(os.path.basename(literal))
|
||||
if not owners:
|
||||
continue
|
||||
norm = os.path.normpath(literal).replace(os.sep, "/")
|
||||
exact = [o for o in owners if norm == o or o.endswith("/" + norm)]
|
||||
for o in (exact or owners):
|
||||
edges.add((f, o))
|
||||
return sorted(edges)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser(description="BSP 不得反向 include App 的头(增量拦截)")
|
||||
ap.add_argument("--verbose", action="store_true", help="列出全部反向边与判定")
|
||||
ap.add_argument("--prune", action="store_true", help="只报告已消失、可从基线删除的条目")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
files = tracked_files()
|
||||
edges = scan(files, app_header_index(files))
|
||||
live = set(e for e in edges if e[1] not in ALLOW)
|
||||
allowed = [e for e in edges if e[1] in ALLOW]
|
||||
new = sorted(live - BASELINE)
|
||||
gone = sorted(BASELINE - live)
|
||||
|
||||
if args.verbose:
|
||||
print("== 反向 include 全量边(含豁免)==")
|
||||
for src, hdr in edges:
|
||||
tag = "豁免" if hdr in ALLOW else ("基线" if (src, hdr) in BASELINE else "新增")
|
||||
print(" [%s] %s -> %s" % (tag, src, hdr))
|
||||
|
||||
if args.prune:
|
||||
if not gone:
|
||||
print("[OK] 基线无需收紧(%d 条存量全部仍成立)" % len(BASELINE))
|
||||
return 0
|
||||
print("以下基线条目已不再成立,请从 BASELINE 删除(基线只减不增):")
|
||||
for src, hdr in gone:
|
||||
print(' ("%s", "%s"),' % (src, hdr))
|
||||
return 0
|
||||
|
||||
print("分层方向:BSP 反向 include App 共 %d 处(豁免 %d / 基线内 %d / 新增 %d)"
|
||||
% (len(edges), len(allowed), len(live & BASELINE), len(new)))
|
||||
if gone:
|
||||
print("[WARN] %d 条基线已消失,建议收紧(跑 --prune 看明细)" % len(gone))
|
||||
if new:
|
||||
print("")
|
||||
print("[FAIL] 新增反向依赖 %d 处——BSP 只依赖相邻下层,不许 include App 层的头:" % len(new))
|
||||
for src, hdr in new:
|
||||
print(" %s: include \"%s\"" % (src, hdr))
|
||||
print("")
|
||||
print(" 正确做法(三选一):")
|
||||
print(" ① 把上层能力注入给 BSP(函数指针/回调,参照 net_cfg_set_save_req_cb 的先例);")
|
||||
print(" ② 由上层把数据取好再传进来(参数/结构体),BSP 不自己去拿;")
|
||||
print(" ③ 若它其实是叶子工具(无状态、不依赖上层服务),可加进 ALLOW 并注明理由。")
|
||||
print(" 顺手修掉存量也欢迎:跑 --prune 看哪些基线可以删。")
|
||||
return 1
|
||||
print("[OK] 无新增反向依赖(存量基线只减不增)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user