fix(门禁): 追溯脚本与 B 层口径对齐 + [示例] 行豁免写反的 bug
为什么改(第 1 件,用户裁定的顺序)
- B 层已认 test/host/<模块>/* 为 TC 号属主登记处,但 check_test_traceability.py
仍按老规矩要求"每个号都必须出现在文档" → 两处判据互相打脸
- 实测矛盾面 284 处里有 173 处属于该口径:照老口径升 --strict 会当场拦死这些
本不该写文档的号,唯一出路是 --no-verify——门禁公信力就是这么丢的
追溯差异改为三档(口径与编号门禁一致)
- DOC-ONLY:文档声明了、代码没有 → 真断链,38 处
- CODE-ONLY:代码有、母号不在登记处、**且号旁边没写人话意图** → 真未登记,7 处
- IN-CODE:靠"号旁写着意图"放过 → 238 处,**弱于文档的保证,单列示数不算过关**,
对外举证仍需 C 层模块级说明(脚本里写明这条边界,禁止把它读成"已登记")
- 小写子项后缀(TC-TIMESYNC-003b)按母号算,与 check_ids 的 subitem_ok 同口径
- 真差异从 284 降到 45(38 + 7)
顺带修两个自己的 bug
- TC_RE 模块码上界停在 {0,4}:批 2 模块码单词化(ADCHEALTH 9 位)后代码侧集合
从 556 悄悄缩到 217(339 个号隐形)→ 放宽到 {0,8}。**同一个坑第二次踩**,
第一次是 check_ids.py 的候选正则——改名必须复核**所有**识别面,已写进 §8.1
- _scan_malformed 的豁免条件写成 `(skip and name != "空格形 Trap")`,语义正好反了:
带 [示例] 的行反而专门被 Trap 规则咬。改为示例行**全部**形态规则不判,
并给引用示例号的两行文档补 [示例] 标记
验证
- 编号门禁:错误级 712 → 710,可判定比例 82.73%(基线 82.6% 未跌破),校验 1/4/5 仍 0
- 追溯:代码侧 578 / 文档侧 298 / 交集 260 / 差异 45;--board-only 回退口径不变
- check_doc_links.py --all 文档路径与章节引用均通过;字节卫生复查 BOM=否、CR=0
- 未改 C/H,不需 @build
文档:docs/编号规范.md §8「顺带收编」条目改写 + §8.1 口径修正记录补两行 + 变更记录;
docs/代码规范/pre-commit使用说明.md §2.25「顺带收编」行同步三档口径
This commit is contained in:
@@ -455,7 +455,7 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法
|
||||
| **八项校验**([示例] 本行引用的是各种非法形态示例,不是真实编号) | ①未登记前缀 ②形态不合规(裸形 `S3` / 空格形 `Trap 07` / 中文形 `陷阱 1` / 补零错 / 档位词带后缀 / 大写后缀 / 缺模块码)③悬空引用 ④重复定义 ⑤冻结前缀越游标 ⑥文档内编号未紧邻文档名 ⑦孤儿条目(告警)⑧可判定比例(只许涨不许跌) |
|
||||
| **配置两份** | `scripts/id_registry.json`:前缀族 → 属主登记处 / 形态正则 / 是否冻结 / 游标,**逐项来自 `docs/编号规范.md` §4 总表**,游标取「规范记载值」与「全库实测最大号」的较大者,偏差写进 `cursor_note`(实测偏差见规范 §8.2);`scripts/id_aliases.json`:旧号 → 新号 / 作废标记(照搬 `section_aliases.json` 的成功范式) |
|
||||
| **判定口径** | 「定义」= 字面量出现在该族**属主登记处文档**里(不依赖表格行首等易漂移形态)→ §5.5「销项 = 删行 + 清全库引用」只做一半,校验 ③ 立刻报(`N-31`/`N-32` 正是这条)。「重复定义」= 字面量出现在**定义位**(表格首格 / 标题行 / 列表项行首),正文提到不算,否则任何交叉引用都会误判成事故。多族同形时的定族顺序:所在文档即属主 > 紧邻属主线索 > **定义位优先**(`docs/编号规范.md` §4.3 冻结快照族 `N-RPT`/`D-RPT`/`S-RPT`/`R-RPT`/`F-RPT`/`P-AUDIT`/`A-AUDIT`/`B-AUDIT` **不参与**——否则已销项的登记簿 `N-21` 会被审计报告同号接管,门禁反过来教人补错文档名,实测把校验 6 从 266 撑到 1437、可判定比例假崩到 68.7%)> 判悬空的族 > 全局族;小写子项后缀(`-b`/`-c`)在母号在册时视为已定义(`subitem_ok`),差异仍由双向追溯如实报 |
|
||||
| **顺带收编** | `scripts/check_test_traceability.py`(TC 用例双向追溯)由本钩子一并调用:正则补齐 `TC-[A-Z]{1,4}-\d{3,4}[a-z]?` 与大写后缀 / 无模块码形态(旧 `TC-[A-Za-z0-9]+-\d+` **整条漏扫** `TC-ADCIN-01A`),比对范围从「`test/board/*.c` ↔ `*_Test_Guide.md`」扩到 `test/host/` 一侧 + 7 篇「单元测试说明.md」(差异从 33 处涨到 326 处——**原口径漏的正是主机侧单测这一半**),另加 `--board-only` 回退旧口径 |
|
||||
| **顺带收编**([示例] 本行引用了非法形态示例号)| `scripts/check_test_traceability.py`(TC 用例双向追溯)由本钩子一并调用:正则补齐 `TC-[A-Z]{1,4}-\d{3,4}[a-z]?` 与大写后缀 / 无模块码形态(旧 `TC-[A-Za-z0-9]+-\d+` **整条漏扫** `TC-ADCIN-01A`),比对范围从「`test/board/*.c` ↔ `*_Test_Guide.md`」扩到 `test/host/` 一侧 + 7 篇「单元测试说明.md」(差异从 33 处涨到 326 处——**原口径漏的正是主机侧单测这一半**),另加 `--board-only` 回退旧口径。**2026-10-21 同口径改写**:差异分三档 `DOC-ONLY`(文档声明而代码没有,真断链 38)/ `CODE-ONLY`(代码有、母号不在登记处且号旁无意图,真未登记 7)/ `IN-CODE`(靠号旁写着意图放过 238,**弱于文档的保证,单列示数不算过关**);另修 `TC_RE` 模块码上界 `{0,4}`→`{0,8}`(批 2 单词化后代码侧一度从 556 缩到 217) |
|
||||
| **最新实测(2026-10-21,B 层口径后)** | 8322 处编号字面量 / 可判定 **82.71%**(6883/8322);错误级 711 = ②398 + ③44 + ⑥269,告警级 ③223 + ⑦100。⚠ **与 2026-10-20 的 1312 不可直接比**:其中 504 处属**口径修正**(主机侧单测 118 号实测在代码目录外零消费者,改认 `test/host/<目录>/*` 为属主登记处;子项后缀母号在册即算已定义),74 处属我修的**注册表漏配属主**(活登记处在 `docs/任务规划/README.md`,`N-21` 在其中出现 2 次而我只登记了 `登记簿.md`),**真正变好 0 处**——完整对照见 `docs/编号规范.md` §8.1 口径修正记录 |
|
||||
| **有牙实测(牙齿)**([示例] 下列字面量是注入用的假编号) | 注入打在临时档(`git add -N` 进索引,跑完 `git rm --cached` + 删除,`git status` 干净):`Z-99`→①、裸形 `S3`→②、`TC-NET-9999`→③、第二篇属主文档重复定义 `DP-01`→④、`E-70`→⑤(`E-xx` 冻结于 `E-63`)、裸引 `D-7`→⑥;六项分别被对应校验点名,`--strict` 退出码 1 |
|
||||
| **显式盲区(不是"检查通过")** | ① 裸形同字母多义(§1.2:`P1` 五义 / `L1` 四义 / `U1` 四义 / `D7` 三义)与元器件位号同形(§4.4)→ 裸形族**只在属主登记处文档内**判越游标,文档外降告警;② 圈号编号(①②③…)与正常列举无法机械区分,不入判定;③ `docs/芯片手册/`、`docs/硬件平台/` 整体排除(位号 `B1`/`C1`/`L1` 与编号同形,是撞形根源)。前两块收口属 §9 阶段 1-②/1-③ 迁移批次,落地后再扩判定范围——盲区登记在 `id_registry.json` 的 `blind_spots` / `unmanaged_bare` |
|
||||
|
||||
+5
-1
@@ -382,7 +382,8 @@ git grep -n '<旧字面量>' # 零残留
|
||||
|
||||
- 排除:`Lib/`、`Middlewares/`、HAL/CMSIS、`docs/芯片手册/`、`docs/硬件平台/`(位号)、构建产物。
|
||||
- 上线节奏:**先 warn-only 清 backlog → 清零后加 `--strict` 拦提交**(与 `doc-links` 章节校验完全同一条路径)。
|
||||
- 顺带收编 `scripts/check_test_traceability.py`:正则补 `TC-[A-Z]{1,4}-\d{3,4}[a-z]?`,覆盖 7 篇「单元测试说明.md」,一并挂进 `id-check`(现为 warn-only 且未挂钩子)。
|
||||
- 顺带收编 `scripts/check_test_traceability.py`:正则补齐大写后缀与无模块码形态,覆盖 7 篇「单元测试说明.md」,一并挂进 `id-check`。
|
||||
**2026-10-21 口径改写**(B 层之后):差异分三档——`DOC-ONLY` 文档声明而代码没有(真断链)、`CODE-ONLY` 代码有而母号不在登记处且号旁无意图(真未登记)、`IN-CODE` 靠「代码旁写着意图」放过(弱保证,单列示数、不算过关);小写子项后缀按母号算。
|
||||
|
||||
### 8.1 落地记录(2026-10-20)
|
||||
|
||||
@@ -421,6 +422,8 @@ git grep -n '<旧字面量>' # 零残留
|
||||
| **`snapshot_only`**:§4.3 冻结历史快照族(`N-RPT`/`D-RPT`/`S-RPT`/`R-RPT`/`F-RPT`/`P-AUDIT`/`A-AUDIT`/`B-AUDIT`)**不参与"定义位优先"** | 曾试过让定义位优先无差别生效:`N-21`(登记簿条目已销项)被审计报告里同号的另一码事"接管",门禁转而去报 ⑥"请写报告文档名"——**照做就制造 §1.2 的 `N-31` 撞号**;实测该做法使校验 6 从 266 暴涨到 1437、比例假崩到 68.7% | 校验 6 回到 269(与 B 层前持平) |
|
||||
| **补属主**:`docs/任务规划/README.md` 加入 `N`/`S-LDG`/`T-LDG` 族的 `owner_docs` | 这是**我的配置漏项**:活登记处在 README(`N-21` 出现 2 次),我原先只登记了 `登记簿.md`(`N-21` = 0)→ 真条目被误判悬空 74 处 | 悬空 −74(**修 bug,不是口径**) |
|
||||
| `excluded_paths` 加 `scripts/id_aliases.json`、`scripts/id_registry.json` | 别名表/注册表**自身不得制造引用面**——上批别名条目一度成为这些号的唯一"外部引用" | 极小 |
|
||||
| **追溯脚本同口径对齐**(`check_test_traceability.py`):文档声明了代码没有 = DOC-ONLY 真断链(38);代码有、母号不在登记处**且号旁没写人话意图** = CODE-ONLY 真未登记(7);代码有但旁边写着意图 → 单列 `IN-CODE`(238),**弱于文档的保证,禁止当成已登记** | B 层认代码为属主后,老口径「每个号都必须出现在文档」与门禁自相矛盾——照它升 `--strict` 会拦死 173+ 处本不该写文档的号,逼出 `--no-verify`,门禁公信力就是这么没的 | 差异从 284 降到 45(38+7),另 238 处转入「看得见但不过关」的 `IN-CODE` 账 |
|
||||
| ⚠ 同批改名要复核**所有**识别面:`TC_RE` 也停在 `{0,4}`,模块码单词化后代码侧集合从 556 悄悄缩到 217(339 个号隐形),已放宽到 `{0,8}` | 这是「改名必须连带复核判据面」教训的**第二次发生**,发生在另一支脚本上 | 修回后代码侧 578 |
|
||||
| 已销项历史任务号 `T-2`/`T-3`/`T-4`/`T-6` 入别名表(`status: void`) | §5.5「销项 = 删行 + 清引用」只做了一半的欠账;本条只让门禁停止把它当自造号,并如实指路 | 悬空错误 −35 |
|
||||
|
||||
**修正后实测(2026-10-21)**:错误级 **711**(B 层前 1318)= ②398 + ③44 + ⑥269;告警级 ③223 + ⑦100;可判定比例 **82.71%**(6883/8322),基线仍 82.6%。
|
||||
@@ -488,6 +491,7 @@ git grep -n '<旧字面量>' # 零残留
|
||||
|---|---|---|
|
||||
| 2026-10-14 | 新增 P-7 与引用可读性规则 | §2 加 **P-7「编号不语义,引用必语义」**(编号本体保持无语义并冻结,但正文引用必须带语义括注、禁裸 ID 独担信息)→ 六条原则变**七条**;§5.1 加前置步"先问能不能不加号";新增 **§5.6「编号密集文档的对照节(强制)」**(≥3 个编号族必须加「导读:编号与术语」);§4.1 补登记 **`E\d+`**(N34 自愈实验编号族,已封闭)并把 `N-\d{2}` 的在册范围订正为「曾用最大 `N-33`,现存至 `N-29`」;§1.2 悬空引用示例补 `N-32`;§7.1 同步订正。起因:`docs/网络/网络自愈架构设计.md` 合并与导读节实践暴露"裸编号引用不可读",用户裁定以后尽量用语义标识 |
|
||||
| (规划稿) | 建立本文档 | 全库扫描 1,385 个不同编号 / 19,306 处出现后定稿的两层编号体系(全局编号 + 文档内编号)、六条原则、目标前缀总表、三批迁移映射与 `check_ids.py` 门禁设计;用户决策:`E-xx` 冻结保留、严重度档位词 `P0~P3` 不变、文档落 `docs/编号规范.md`、阶段 1 三批全做 |
|
||||
| 2026-10-21 | **追溯脚本同口径对齐** | `check_test_traceability.py` 差异定义改为 DOC-ONLY / CODE-ONLY / IN-CODE 三档(真差异 45 = 38 + 7;弱保证 238 单列示数);顺带修 `TC_RE` 模块码上界 `{0,4}`→`{0,8}`——**同一个坑第二次踩**(代码侧一度从 556 缩到 217),已写进 §8.1 |
|
||||
| 2026-10-21 | **B 层口径落地(批 3)** | 主机侧单测 118 号实测在代码目录外零消费者 → 建 9 族 `TC-HOST-*` 认代码为属主登记处(不逐号补文档);新增 `subitem_ok`(小写子项后缀母号在册即算已定义)、`snapshot_only`(§4.3 冻结快照族不参与定义位优先)、`allow_orphans` 旗标与 `excluded_paths` 补两项;补 `docs/任务规划/README.md` 为 `N`/`S-LDG`/`T-LDG` 属主(**我的配置漏项,曾误判 74 处真条目悬空**);`T-2`/`T-3`/`T-4`/`T-6` 入别名表标 `void`。错误级 1318 → 711(其中 504 处属口径修正、74 处属修 bug,**真正变好 0 处**)。新增 §8.1 口径修正记录表 |
|
||||
| 2026-10-20 | **编号改名开禁(用户授权)+ 批 1 落地** | 新增 `scripts/rename_ids.py`(§7.3 规划脚本:映射表驱动、显式 scope、干跑/落盘两态、自动写 `id_aliases.json` 的 scoped 条目、跳过 `[示例]`/`[计划]` 行与变更记录表、按原字节读写保 BOM 与行尾)。**批 1**:代码规范条款 `S1~S19` → `CODE-RULE-nn`(78 处/2 文件)、UI 像素回归状态 `S0~S7` → `MENU-STATE-nn`(61 处/3 文件);`check_ids.py` 支持 **scoped 别名** 并登记两族(66 族);可判定比例 82.3% → **82.7%**、裸形判错 71 → 50、错误级 1312 → 1291。§4.1 补两行、新增 §8.3 迁移批次记录 |
|
||||
| 2026-10-20 | **§8 门禁落地(warn-only)** | 新增 `scripts/check_ids.py`(八项校验 + 可判定比例基线)、`scripts/id_registry.json`(逐项来自 §4 总表 + 实测游标)、`scripts/id_aliases.json`;挂上 `id-check` 钩子(36→37);顺带收编 `check_test_traceability.py`(正则补齐大写后缀/无模块码形态,比对范围扩到 7 篇「单元测试说明.md」+ `test/host/` 一侧,另加 `--board-only` 回退原口径)。**新增 §8.1 落地记录**(基线 82.3%、backlog 分布、有牙实测、显式盲区)与 **§8.2 规范↔仓库偏差表**(`E19`、`FR` 未补零、`TC` 模块码含数字、`F-<MOD>-<nn>` 总表缺行、`N` 子项后缀/扩位);§4.1 相应行按实测订正并补 `F-<MOD>` / `P-<n>` 两行 |
|
||||
|
||||
@@ -471,7 +471,12 @@ class Checker(object):
|
||||
# 才讲得清"什么算非法",在它自己头上判形态违规没有意义
|
||||
return
|
||||
for name, rx, sev, why, docs_only in self.malformed:
|
||||
if (docs_only and not is_md) or (skip and name != "空格形 Trap"):
|
||||
if skip:
|
||||
# `[示例]` / `[计划]` 行:本行引用的就是非法形态的样子,全部规则一律不判。
|
||||
# 这里原先写成 `(skip and name != "空格形 Trap")` —— 等于"示例行只放过其它规则、
|
||||
# 偏要抓 Trap",把标记反着用(实测带 `[示例]` 的行仍在报空格形 Trap 错误)。
|
||||
continue
|
||||
if docs_only and not is_md:
|
||||
continue
|
||||
for m in rx.finditer(line):
|
||||
self.add(2, sev, rel, lineno, m.group(0).strip(),
|
||||
|
||||
@@ -20,6 +20,14 @@
|
||||
作用域**只取 `*_Test_Guide.md`**:TC-CFG / TC-CLK / TC-WD 属主机侧单元
|
||||
测试文档(`配置文件单元测试说明.md` 等),不参与板级追溯,天然被排除。
|
||||
|
||||
**2026-10-21 B 层口径对齐**(docs/编号规范.md §8.1「口径修正记录」):主机侧单测的
|
||||
号改认 `test/host/<模块>/*` 为属主登记处(实测这些号在代码目录外零消费者),
|
||||
于是"文档没有"不再一律算断链。真差异收敛为三条:
|
||||
① 文档声明了、代码没有 → DOC-ONLY;
|
||||
② 代码有、母号不在文档、**且号旁边没写人话意图** → CODE-ONLY(真未登记);
|
||||
③ 代码有、靠"旁边有意图"放过 → 单独打印为 IN-CODE,**弱于文档**,不算已登记。
|
||||
小写子项后缀(`TC-XXX-003b`)按母号算(`subitem_ok`),与编号门禁同口径。
|
||||
|
||||
退出码:
|
||||
0 = 无差异;或有差异但**默认仅告警**(与 doc-links 演进路径一致)
|
||||
1 = 有差异且显式 `--strict`
|
||||
@@ -48,9 +56,12 @@ DOC_DIR = REPO_ROOT / "docs" / "测试说明"
|
||||
# ① 规范 §4.1 的合规形态 `TC-[A-Z]{1,4}-\d{3,4}[a-z]?`(带小写后缀,TC-NET-1502b);
|
||||
# ② 现存的大写后缀形态(TC-AI-01A [示例],§7.1 待统一小写)也要能抓出来,否则整条漏扫;
|
||||
# ③ 无模块码形态(iec104 的 TC-014/TC-104,§7.2 列为孤儿)纳入比对而不是静默忽略。
|
||||
TC_RE = re.compile(r"TC-(?:[A-Z][A-Z0-9]{0,4}-\d{1,4}[A-Za-z]?|\d{2,4}[a-z]?)")
|
||||
# ⚠ 模块码上界必须跟着 §6 模块码表走:批 2 单词化后码长可达 9 位(ADCHEALTH),
|
||||
# 这里曾停在 {0,4},导致代码侧集合从 556 悄悄缩到 217——**同一批改名要复核所有
|
||||
# 识别面,不止 check_ids.py**(2026-10-21 实测踩到)。
|
||||
TC_RE = re.compile(r"TC-(?:[A-Z][A-Z0-9]{0,8}-\d{1,4}[A-Za-z]?|\d{2,4}[a-z]?)")
|
||||
# 规范合规形态(用于 --verbose 里区分"形态待迁移"与"纯追溯断链")
|
||||
TC_CANON_RE = re.compile(r"^TC-[A-Z]{1,4}-\d{3,4}[a-z]?$")
|
||||
TC_CANON_RE = re.compile(r"^TC-[A-Z][A-Z0-9]{0,8}-\d{3,4}[a-z]?$") # 与 §4.1 实测形态同步
|
||||
# 占位符前缀(文档模板里的 `TC-XXX-…`)不参与比对
|
||||
PLACEHOLDER_PREFIXES = {"XXX"}
|
||||
|
||||
@@ -59,23 +70,52 @@ def _prefix(tc: str) -> str:
|
||||
return tc.rsplit("-", 1)[0]
|
||||
|
||||
|
||||
def collect(paths, label):
|
||||
"""返回 (ids_set, per_prefix_counts)。逐文件扫描,缺文件不致命。"""
|
||||
def _base(tc: str) -> str:
|
||||
"""去掉小写子项后缀:`TC-TIMESYNC-003b` → `TC-TIMESYNC-003`(与 check_ids 的
|
||||
`subitem_ok` 同口径——母号已在登记处即视为已覆盖,子项差异不在这里报)。"""
|
||||
return re.sub(r"[a-z]$", "", tc) if re.search(r"\d[a-z]$", tc) else tc
|
||||
|
||||
|
||||
def has_intent(text: str, tc: str) -> bool:
|
||||
"""该号在代码里是否**旁边写着意图**(B 层口径:`test/host/<模块>/` 就是属主登记处,
|
||||
不逐号写文档,但号必须自带一句人话,否则"代码即登记处"等于没有登记处)。
|
||||
|
||||
判据刻意保守:号后面(或本行注释里)要有 ≥6 个有效字符,且不能只是代码 token。
|
||||
这是**弱于文档**的保证,脚本会如实打印有多少号是靠这条弱判据过关的,别当成已登记。
|
||||
"""
|
||||
for line in text.splitlines():
|
||||
if tc not in line:
|
||||
continue
|
||||
tail = line.split(tc, 1)[1]
|
||||
tail = re.sub(r"^[\"'\)\]\*[::,,。\-_/`\\s]+", "", tail)
|
||||
tail = re.sub(r"[\"'\)\]\*\\s]+$", "", tail)
|
||||
body = re.sub(r"[A-Za-z0-9_]{1,3}$", "", tail) # 尾随的短代码 token
|
||||
if len(re.sub(r"[\s\-\*=.。,,::;;()()\[\]{}<>|/\\'\"`,]+", "", body)) >= 6:
|
||||
if re.search(r"[一-鿿]|[A-Za-z]{4,}", body):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def collect(paths, label, want_intent=False):
|
||||
"""返回 (ids_set, per_prefix_counts[, intent_set])。逐文件扫描,缺文件不致命。"""
|
||||
ids = set()
|
||||
intents = set()
|
||||
for p in paths:
|
||||
try:
|
||||
text = p.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError as e: # pragma: no cover - 环境错误
|
||||
print(f"[ERR] 无法读取 {p}: {e}", file=sys.stderr)
|
||||
return None, None
|
||||
return (None, None, None) if want_intent else (None, None)
|
||||
for m in TC_RE.findall(text):
|
||||
if _prefix(m) in PLACEHOLDER_PREFIXES:
|
||||
continue
|
||||
ids.add(m)
|
||||
if want_intent and has_intent(text, m):
|
||||
intents.add(m)
|
||||
counts = {}
|
||||
for i in sorted(ids):
|
||||
counts[_prefix(i)] = counts.get(_prefix(i), 0) + 1
|
||||
return ids, counts
|
||||
return (ids, counts, intents) if want_intent else (ids, counts)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -112,13 +152,22 @@ def main():
|
||||
print(f"[ERR] 未找到文档侧文件:{doc_dir}/*_Test_Guide.md", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
code_ids, code_counts = collect(code_files, "code")
|
||||
code_ids, code_counts, code_intents = collect(code_files, "code", want_intent=True)
|
||||
doc_ids, doc_counts = collect(doc_files, "doc")
|
||||
if code_ids is None or doc_ids is None:
|
||||
return 2
|
||||
|
||||
doc_bases = {_base(i) for i in doc_ids}
|
||||
# 真差异定义(B 层口径 2026-10-21,docs/编号规范.md §8.1):
|
||||
# ① 文档声明了、代码里没有 → 未实现/未标注(保留,这是硬断链);
|
||||
# ② 代码有、文档没有,**且**母号也不在文档、**且**号旁边没写意图 → 真未登记;
|
||||
# ③ 代码有、文档没有,但靠"代码即登记处"过关(旁边有意图)→ 单列统计,
|
||||
# 不算差异也不算合规达成——它是**弱于文档**的保证,必须看得见数量。
|
||||
doc_only = sorted(doc_ids - code_ids)
|
||||
code_only = sorted(code_ids - doc_ids)
|
||||
code_only = sorted(i for i in code_ids - doc_ids
|
||||
if _base(i) not in doc_bases and i not in code_intents)
|
||||
code_only_in_code = sorted(i for i in code_ids - doc_ids
|
||||
if _base(i) not in doc_bases and i in code_intents)
|
||||
|
||||
print("=== S4 用例追溯校验(TC 编号双向比对)===")
|
||||
print(f"代码侧:{len(code_files)} 个文件,{len(code_ids)} 个 TC")
|
||||
@@ -134,9 +183,16 @@ def main():
|
||||
print(f"[DOC-ONLY] 文档有、代码无(未实现/代码未标注):{len(doc_only)}")
|
||||
for i in doc_only:
|
||||
print(f" {i}")
|
||||
print(f"[CODE-ONLY] 代码有、文档无(未登记/文档漏写):{len(code_only)}")
|
||||
print(f"[CODE-ONLY] 代码有、文档无,且母号不在文档、旁边也没写意图(真未登记):{len(code_only)}")
|
||||
for i in code_only:
|
||||
print(f" {i}")
|
||||
print(f"[IN-CODE] 代码有、文档无,但号旁边写着意图 → 按「代码即属主登记处」放过:"
|
||||
f"{len(code_only_in_code)}")
|
||||
print(" ⚠ 这是**弱于文档**的保证(只证明该行有人话,不证明意图正确);"
|
||||
"对外举证时仍需 C 层模块级说明,清单见 --verbose")
|
||||
if args.verbose:
|
||||
for i in code_only_in_code:
|
||||
print(f" {i}")
|
||||
|
||||
if not doc_only and not code_only:
|
||||
print("\n[OK] 双向一一对应,无差异")
|
||||
|
||||
Reference in New Issue
Block a user