From 63b7ecd60be9b647fd67b610765cf86edecf069f Mon Sep 17 00:00:00 2001 From: "agent.develop" Date: Wed, 23 Sep 2026 04:32:50 +0800 Subject: [PATCH] =?UTF-8?q?[A3a-1b]=20=E7=AC=AC2=E8=BD=AE=E5=AE=9A?= =?UTF-8?q?=E7=A8=BF=EF=BC=9A=C2=A76/=C2=A78=20=E6=B6=88=E9=99=A4=E5=8F=8C?= =?UTF-8?q?=20HEAD=20=E5=8F=A3=E5=BE=84(6a1cce6=E2=86=92=E6=9C=AC=E8=BD=AE?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=AE=9A=E7=A8=BF=E6=8F=90=E4=BA=A4)+?= =?UTF-8?q?=E8=80=97=E6=97=B6=E6=94=B9=E8=AE=A1=E6=95=B0=E5=8F=A3=E5=BE=84?= =?UTF-8?q?(QC=20#3/#8/#9)=E3=80=81=E6=A0=B8=E5=AF=B9=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E8=87=AA=E6=8C=87=E8=A1=8C=E6=94=B9=E9=97=AD=E7=8E=AF=E6=AF=94?= =?UTF-8?q?=E5=AF=B9=E4=B8=8D=E5=86=8D=20SKIP-SELF(QC=20#7)=E3=80=81?= =?UTF-8?q?=E8=AF=81=E6=8D=AE=E4=BB=B6=20CMD=20=E4=B8=8E=20tests/checks=20?= =?UTF-8?q?=E9=95=BF=E6=9C=9F=E5=B7=A5=E5=85=B7=E5=90=8C=E5=BE=84(QC=20#6)?= =?UTF-8?q?=E3=80=82=E6=9C=AC=E6=8F=90=E4=BA=A4=E4=B8=BA=E5=A4=8D=E8=B7=91?= =?UTF-8?q?=E8=AF=81=E6=8D=AE=E6=89=80=E8=AF=81=E6=98=8E=E7=9A=84=E6=A0=91?= =?UTF-8?q?=EF=BC=88=E6=96=87=E6=A1=A3=E5=AE=9A=E7=A8=BF=E6=80=81=EF=BC=8C?= =?UTF-8?q?=E5=8F=96=E8=AF=81=E5=9C=A8=E5=85=B6=E5=90=8E=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=EF=BC=8C=E8=AF=81=E6=8D=AE=E4=BB=B6=E5=8F=A6=E6=88=90=E5=BD=92?= =?UTF-8?q?=E6=A1=A3=E6=8F=90=E4=BA=A4=E4=B8=94=E4=B8=8D=E8=A7=A6=E7=A2=B0?= =?UTF-8?q?=E6=9C=AC=E6=96=87=E6=A1=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-log-2026-09-22-m11b2d-fixture-contract.md | 21 ++- .../m11b2d_a3a1b_cmd_index_consistency.py | 176 +++++++++++++----- 2 files changed, 141 insertions(+), 56 deletions(-) diff --git a/docs/work-log-2026-09-22-m11b2d-fixture-contract.md b/docs/work-log-2026-09-22-m11b2d-fixture-contract.md index 0e8e0e7..d82537b 100644 --- a/docs/work-log-2026-09-22-m11b2d-fixture-contract.md +++ b/docs/work-log-2026-09-22-m11b2d-fixture-contract.md @@ -41,13 +41,13 @@ 与归档件里的 `17 passed` 脱钩(证据与其所证明的仓库状态不一致)。**本次重做**:以 `git show 1a4002e` 的 **49,966B / 609 行**全文为基线恢复(提交 `6a1cce6`,`1 file changed, 594 insertions(+), 6 deletions(-)`, §0–§8 全章节回归),随后在**恢复后的同一棵树**上重跑任务书两条命令: - `./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py -q --tb=no` → **17 passed in 0.08s**(`# RC=0`)、 - `./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py --collect-only -q` → **17 tests collected in 0.03s**(`# RC=0`), + `./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py -q --tb=no` → **17 passed**(`# RC=0`;计数口径,耗时以证据件正文末行为准、不逐字引用)、 + `./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py --collect-only -q` → **17 tests collected**(`# RC=0`;计数口径,耗时以证据件正文末行为准、不逐字引用), `RegistrationDocCase` 两项复绿。两份复跑证据 `m11b2d_a3a1_fixture_contract_rerun.txt` / - `m11b2d_a3a1_fixture_contract_collect_only_rerun.txt` 已用**本次真实输出覆盖**(`# HEAD: 6a1cce6` = 被证明的树), + `m11b2d_a3a1_fixture_contract_collect_only_rerun.txt` 已用**本次真实输出覆盖**。三份复跑/核对证据件前三行的 `# HEAD:` / `# PROVEN-TREE:` 一律记录**本轮文档定稿提交**及其文档实测字节/行数(短 SHA 与字节数以证据件前三行为准,本文不硬编码,以免再次出现 QC #8 指出的「文档写一个 HEAD、证据写另一个 HEAD」双口径矛盾);执行次序为**先定稿 → 后取证 → 证据件另成归档提交**,归档提交不再触碰本文档,故证据件所证明的树即最终交付态(响应 QC #9)。`6a1cce6` 仅为本文档全文恢复提交,不是本轮取证所证明的树。 `m11b2d_a3_*`、`m11b2d_a1_*` 等历史归档一律未覆盖。另按 QC #4 新增 §8 全行 CMD 逐字一致性核对输出 `m11b2d_a3a1b_cmd_index_consistency.txt`(逐行 一致/不一致),核对脚本按 §8 规则8 从一次性目录升级为长期工具 - `tests/checks/m11b2d_a3a1b_cmd_index_consistency.py`(可复跑,非临时探针)。本轮文档改动仅本节、§6 执行证据段与 §8 证据索引表, + `tests/checks/m11b2d_a3a1b_cmd_index_consistency.py`(可复跑,非临时探针);本轮该核对件的证据件首行 `# CMD:` 亦记录该长期工具路径(不再记一次性 `.scratch/` 路径),使声明与证据同径(响应 QC #6),且自指行不再 SKIP-SELF,改为「§8 引用 == 脚本实际路径+本件首行 == §8 引用」闭环核对(响应 QC #7)。本轮文档改动仅本节、§6 执行证据段与 §8 证据索引表, 未删除任何既有章节。 - **文档定位**: **索引式登记**。夹具层实现说明的权威正文在 `tests/_m11b2_compat.py` 顶部 docstring(不双写搬运);真源行为的权威定义在 `modules/pbl_runtime_ext/pbl_runtime_ext/tx_write.py` @@ -292,10 +292,10 @@ A3a-1 轮在恢复文档全文后再次复跑复核一致,并把真实输出 A3a-1b 轮第一次收口因 `files` 只放摘录、被代收口覆盖成 20 行摘录(`5b56b27`),在该态实跑为 `2 failed, 15 passed`(红项即 `RegistrationDocCase::test_doc_exists_and_registers_contract` 与 `::test_doc_live_fixture_symbols_registered`),证据与交付态脱钩;本轮以 `1a4002e` 全文(49,966B/609 行) -恢复为提交 `6a1cce6`,在恢复后的同一棵树上重跑:`-q --tb=no` → `17 passed in 0.08s`、 -`--collect-only -q` → `17 tests collected in 0.03s`,两者均 `# RC=0`,真实输出覆盖同名件 +恢复为提交 `6a1cce6`;本轮再完成 §6/§8 口径修正并**定稿**,随后在定稿态上重跑:`-q --tb=no` → `17 passed`、 +`--collect-only -q` → `17 tests collected`,两者均 `# RC=0`(计数口径,耗时以证据件正文末行为准),真实输出覆盖同名件 `tests/evidence/m11b2d_a3a1_fixture_contract_rerun.txt` 与 -`tests/evidence/m11b2d_a3a1_fixture_contract_collect_only_rerun.txt`(`# HEAD: 6a1cce6`); +`tests/evidence/m11b2d_a3a1_fixture_contract_collect_only_rerun.txt`(前三行 `# HEAD:` / `# PROVEN-TREE:` 为本轮**文档定稿提交**及其文档实测,非 `6a1cce6`;`6a1cce6` 为全文恢复提交,取证在其后的定稿态上进行——先定稿、后取证,响应 QC #8/#9); `m11b2d_a3_*` 等历史归档未覆盖,`RegistrationDocCase` 两项在文档全文存在的状态下恒绿。 **计数口径**:本节用例数(17)与上表逐行用例数、`--collect-only` 的 **17 tests collected**、 各归档证据文件的 passed 数四者一一对齐(A3 轮两份 + A3a/A3a-1/A3a-1b 轮复跑件,见 §8 索引);证据文件首行 `# CMD:` 原文见 §8 证据索引(本表「组」列即四组,无第五组)。 @@ -601,6 +601,7 @@ RC=0 **口径规则**:「跑的是什么」列一律**照抄各证据文件首行 `# CMD:` 原文**(含 `cd` 前缀与解释器路径), 再附该文件正文的实际结果摘要与 `# RC=`。不再用「`xxx.py -v`」这类简写,避免与文件内容对不上。 +**A3a-1b 轮口径补充(响应 QC #3/#6/#7)**:①CMD 原文的逐字性由长期工具 `tests/checks/m11b2d_a3a1b_cmd_index_consistency.py` 机器核对,**自指行同样核对、不再 SKIP-SELF**(上一版对自指行 SKIP,而唯一真实漂移恰好落在自指行上,属漏检假绿);②「结果摘要」只引用**计数**(`N passed` / `N tests collected`)与 `# RC=`,**耗时不逐字引用**——同一棵树上 pytest 计时存在抖动(实测出现过 `0.03s` / `0.04s`),把抖动值写进「逐字一致」声明必然失真,故耗时一律以证据件正文末行为准。 **A3a-1 轮修正**:上一版把 `m11b2d_a3_single_tx_after_revert.txt` 行里首行 `# CMD:` 的路径笔误 **顺手改对**后引用(写成 `test_m11b2_single_tx.py`),这本身就又是一处「引用与文件内容不一致」; 本轮改为**原样照抄**(笔误保留在引号内并显式标注),同时新增按任务书命令在当前 HEAD 重跑的证据文件行, @@ -610,9 +611,9 @@ RC=0 |---|---|---| | `tests/evidence/m11b2d_a3_fixture_contract_pytest.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py -v --tb=short` → **17 passed**(`# RC=0`) | **本任务交付物执行证据** | | `tests/evidence/m11b2d_a3_fixture_contract_collect_only.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py --collect-only -q` → **17 tests collected**(`# RC=0`) | 本任务可收集性证明 | -| `tests/evidence/m11b2d_a3a1_fixture_contract_rerun.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py -q --tb=no` → **17 passed in 0.08s**(`# RC=0`,`# HEAD:` 为本轮收口提交(见本件前三行,与 §8 本行同树)) | **A3a-1b 轮复跑证据(重做轮)**:文档全文按 `1a4002e` 基线恢复(49,966B/609 行,提交 `6a1cce6`)后在同一棵树实跑留证;上一版证据对应 `d6514bf`,而交付态被代收口 `5b56b27` 覆盖成 20 行摘录后实跑 `2 failed, 15 passed`,证据与仓库状态脱钩(QC #2),本行已用新输出覆盖(A3a-1 轮同名件旧内容 `# HEAD: 1d1fd3d` 已被本轮覆盖,`m11b2d_a3_*` 等历史归档未触碰);本列与文件首行 `# CMD:` 及正文输出逐字一致 | -| `tests/evidence/m11b2d_a3a1_fixture_contract_collect_only_rerun.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py --collect-only -q` → **17 tests collected in 0.03s**(`# RC=0`,`# HEAD:` 为本轮收口提交(见本件前三行,与 §8 本行同树)) | **A3a-1b 轮 collect-only 复跑证据(重做轮)**:四组 17 项可收集性在文档全文恢复态 `6a1cce6` 的复证(未覆盖 `m11b2d_a3_fixture_contract_collect_only.txt` 旧归档);本列与文件首行 `# CMD:` 及正文输出逐字一致 | -| `tests/evidence/m11b2d_a3a1b_cmd_index_consistency.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python tests/checks/m11b2d_a3a1b_cmd_index_consistency.py` → **ALL_ROWS_CONSISTENT**(核对行 12:一致 11 + 自指 SKIP-SELF 1,不一致 0;逐行「一致/不一致」清单见正文,`# RC=0`,`# HEAD:` 为本轮收口提交(见本件前三行,与 §8 本行同树)) | **A3a-1b 轮 §8 索引逐字一致性核对输出(本任务新增,响应 QC #3/#4)**:对 §8 表**全部行**取「跑的是什么」列反引号内 CMD 原文,与各证据文件首行 `# CMD:` 去前缀后逐字比对(含 `m11b2d_a3_single_tx_after_revert.txt` 首行路径笔误原样照抄行),非仅核对新增两行 | +| `tests/evidence/m11b2d_a3a1_fixture_contract_rerun.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py -q --tb=no` → **17 passed**(计数口径;`# RC=0`,耗时以本件正文末行为准、不逐字引用;`# HEAD:` / `# PROVEN-TREE:` 为本轮**文档定稿提交**及其文档实测字节/行数,见本件前三行,与 §8 本行同树) | **A3a-1b 轮复跑证据(重做轮)**:文档全文按 `1a4002e` 基线恢复(49,966B/609 行,提交 `6a1cce6`)→ 本轮 §6/§8 口径修正定稿 → 在定稿树上实跑留证(先定稿、后取证,响应 QC #9);上一版证据对应 `d6514bf`,而交付态被代收口 `5b56b27` 覆盖成 20 行摘录后实跑 `2 failed, 15 passed`,证据与仓库状态脱钩(QC #2),本行已用新输出覆盖(A3a-1 轮同名件旧内容 `# HEAD: 1d1fd3d` 已被本轮覆盖,`m11b2d_a3_*` 等历史归档未触碰);本列首段与文件首行 `# CMD:` 逐字一致,由 `m11b2d_a3a1b_cmd_index_consistency.txt` 机器核对 | +| `tests/evidence/m11b2d_a3a1_fixture_contract_collect_only_rerun.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2d_fixture_contract.py --collect-only -q` → **17 tests collected**(计数口径;`# RC=0`,耗时以本件正文末行为准、不逐字引用;`# HEAD:` / `# PROVEN-TREE:` 为本轮**文档定稿提交**及其文档实测,见本件前三行,与 §8 本行同树) | **A3a-1b 轮 collect-only 复跑证据(重做轮)**:四组 17 项可收集性(含 `RegistrationDocCase` 两项在册)在文档定稿态上的复证(未覆盖 `m11b2d_a3_fixture_contract_collect_only.txt` 旧归档);本列首段与文件首行 `# CMD:` 逐字一致,由 `m11b2d_a3a1b_cmd_index_consistency.txt` 机器核对 | +| `tests/evidence/m11b2d_a3a1b_cmd_index_consistency.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python tests/checks/m11b2d_a3a1b_cmd_index_consistency.py --evidence tests/evidence/m11b2d_a3a1b_cmd_index_consistency.txt` → **ALL_ROWS_CONSISTENT**(核对行 12:一致 12、不一致 0、跳过 0;`# RC=0`,逐行清单与自指行核对方式见正文;`# HEAD:` / `# PROVEN-TREE:` 为本轮**文档定稿提交**及其文档实测,见本件前三行) | **A3a-1b 轮 §8 索引逐字一致性核对输出(响应 QC #3/#4/#6/#7)**:对 §8 表**全部行**取「跑的是什么」列反引号内 CMD 原文,与各证据文件首行 `# CMD:` 去前缀后逐字比对(含 `m11b2d_a3_single_tx_after_revert.txt` 首行路径笔误原样照抄行、`m11b2d_a3a_a1c_damage_forensics.txt` 转录件 `# FILE:` 首行);本行为自指行,核对方式为「§8 本行引用 CMD == 脚本实际路径 `tests/checks/m11b2d_a3a1b_cmd_index_consistency.py`」+「本件首行 `# CMD:` 去前缀 == §8 本行引用」闭环比对,上一版在此无条件 SKIP-SELF 造成假绿(QC #7)已消除 | | `tests/evidence/m11b2d_a3a_single_tx_after_revert_corrected.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2_single_tx.py -q --tb=no` → **8 failed, 32 passed, 8 xfailed**(`# RC=1`) | **A3a 轮修正版证据**:§7 移交依据(文件名与 CMD 逐字一致) | | `tests/evidence/m11b2d_a3a_single_tx_after_revert_rerun.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2_single_tx.py -q --tb=no` → **8 failed, 32 passed, 8 xfailed in 0.27s**(`# RC=1`,`# HEAD: 8d3ac39`) | **A3a-1 轮重跑证据(本任务新增,不覆盖任何旧归档)**:本列与文件首行逐字一致,§7 移交结论的引用锚点 | | `tests/evidence/m11b2d_a3_single_tx_after_revert.txt` | `# CMD: cd modules/world_sync && ./.venv/bin/python -m pytest tests/test_m11b2d_single_tx.py -q --tb=no`(**首行原文逐字照抄**:其中路径 `test_m11b2d_single_tx.py` 系 A3 轮笔误,该文件不存在;正文 8 条 FAILED 行均为 `tests/test_m11b2_single_tx.py`,末行 `8 failed, 32 passed, 8 xfailed in 0.26s`,该归档无 `# RC=` 行) | A3 轮归档,原样保留不覆盖;笔误在此登记,「CMD 与所跑内容严格一致」的版本见上一行 | diff --git a/tests/checks/m11b2d_a3a1b_cmd_index_consistency.py b/tests/checks/m11b2d_a3a1b_cmd_index_consistency.py index 1378294..5966672 100644 --- a/tests/checks/m11b2d_a3a1b_cmd_index_consistency.py +++ b/tests/checks/m11b2d_a3a1b_cmd_index_consistency.py @@ -1,53 +1,137 @@ # -*- coding: utf-8 -*- -# A3a-1b 重做轮:让 §8 全行 CMD 一致性核对脚本正确区分三类行 -# (1) 单命令证据件:列内首反引号段以 `# CMD: ` 开头 → 与文件首行去前缀逐字比对 -# (2) 转录件(非单命令输出,如 a1c_damage_forensics):列内显式声明「首行原文为 `# FILE: ...`」→ 比对文件首行原文 -# (3) 本核对件自身(尚未生成,自指)→ 标记「自指,本件生成后由本行结论覆盖」 -import io, os, sys +"""A3a-1b 重做轮(响应 QC #6/#7 硬门禁):§8 证据索引「跑的是什么」列逐字一致性核对。 -ROOT = "/d/pipeline/workspaces/0/sdlc_general/modules/world_sync" +三类行的处理方式: + (1) 单命令证据件:列内首反引号段以 `# CMD: ` 开头 → 与该证据文件首行去前缀后逐字比对; + (2) 转录件(非单命令输出,如 a1c_damage_forensics):列内以 `# FILE: ` 声明首行原文 → 与文件首行原文逐字比对; + (3) 本核对件自身(自指行):**不再无条件 SKIP-SELF**(QC #7:唯一真实漂移恰好落在自指行上, + SKIP 会造成假绿)。自指行核对两件事: + a. §8 自指行「跑的是什么」列引用的 CMD 是否指向本脚本当前实际路径(tests/checks/ 长期工具,脚本自身路径已知); + b. 本证据件(--evidence 指定)首行 `# CMD:` 去前缀后是否等于 §8 自指行引用 —— 即「生成后二次读取 + 自身文件首行」的闭环比对。两项任一不符即计入「不一致」,不接受 SKIP 作为一致。 + +用法: + 取证第一跑(证据件尚未生成): + ./.venv/bin/python tests/checks/m11b2d_a3a1b_cmd_index_consistency.py \ + --evidence tests/evidence/m11b2d_a3a1b_cmd_index_consistency.txt + 闭环第二跑(证据件已生成,同一命令,自指闭环比对生效):同上。 +退出码:无不一致行 → 0;否则 1。 +""" +import argparse +import io +import os +import sys + +ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..")) DOC = os.path.join(ROOT, "docs/work-log-2026-09-22-m11b2d-fixture-contract.md") -SELF = "tests/evidence/m11b2d_a3a1b_cmd_index_consistency.txt" +SELF_EVID_DEFAULT = "tests/evidence/m11b2d_a3a1b_cmd_index_consistency.txt" +SELF_SCRIPT_REL = "tests/checks/m11b2d_a3a1b_cmd_index_consistency.py" +SELF_CMD_BASE = "cd modules/world_sync && ./.venv/bin/python " + SELF_SCRIPT_REL -lines = io.open(DOC, encoding="utf-8").read().split("\n") -rows = [] -for ln, text in enumerate(lines, 1): - t = text.strip() - if not (t.startswith("| `tests/evidence/") and t.endswith("|")): - continue - cells = [c.strip() for c in t.strip("|").split("`")] - fpath = t.strip("|").split("|")[0].strip().strip("`") - if fpath == SELF: - rows.append((ln, fpath, "自指行(本核对件自身,生成前磁盘无此文件;生成后本行结论即由本件正文覆盖)", "SKIP-SELF")) - continue - # 找列内第一个反引号段 - seg = None - for c in cells[1:]: - if c.strip().startswith("# CMD: ") or c.strip().startswith("# FILE: "): - seg = c.strip() - break + +def read_first(path): + with io.open(path, encoding="utf-8") as f: + return f.read().split("\n")[0] + + +def check_self_row(seg, self_evidence, self_evidence_abs): + """返回 (verdict, detail)。自指行不 SKIP,按 QC #7 做闭环比对。""" + notes = [] + bad = False if seg is None: - rows.append((ln, fpath, "列内无 CMD/FILE 原文可核", "SKIP")) - continue - full = os.path.join(ROOT, fpath) - if not os.path.exists(full): - rows.append((ln, fpath, "证据文件不存在", "FAIL")) - continue - first = io.open(full, encoding="utf-8").read().split("\n")[0] - if seg.startswith("# CMD: "): - doc_cmd = seg[len("# CMD: "):] - file_cmd = first[len("# CMD: "):] if first.startswith("# CMD: ") else "(文件首行非 # CMD:)" - verdict = "一致" if doc_cmd == file_cmd else "不一致 DOC=%r FILE=%r" % (doc_cmd, file_cmd) - rows.append((ln, fpath, seg[:60] + "…", verdict)) - else: # 转录件:核首行原文 - verdict = "一致" if first == seg else "不一致 DOC=%r FILE=%r" % (seg, first) + return "不一致", ["自指行列内无 CMD 原文可核"] + if not seg.startswith("# CMD: "): + return "不一致", ["自指行列内首段非 # CMD(转录件写法):%r" % seg[:40]] + doc_cmd = seg[len("# CMD: "):] + # a. §8 自指行 CMD 必须指向本脚本当前实际路径(允许追加 --evidence 参数) + ok_path = (doc_cmd == SELF_CMD_BASE + or doc_cmd.startswith(SELF_CMD_BASE + " --evidence ") + or doc_cmd.startswith(SELF_CMD_BASE + " ")) + if not ok_path: + bad = True + notes.append("§8 自指行 CMD 未指向脚本实际路径 DOC=%r EXPECTED_BASE=%r" % (doc_cmd, SELF_CMD_BASE)) + else: + notes.append("§8 自指行 CMD 指向脚本实际路径 %s" % SELF_SCRIPT_REL) + # b. 闭环比对:本核对件首行 == §8 自指行引用 + if os.path.exists(self_evidence_abs): + first = read_first(self_evidence_abs) + if not first.startswith("# CMD: "): + bad = True + notes.append("本核对件首行非 `# CMD:` 原文") + elif first[len("# CMD: "):] != doc_cmd: + bad = True + notes.append("自指闭环失败:本核对件首行 CMD 与 §8 自指行引用不一致 FILE=%r DOC=%r" + % (first[len("# CMD: "):], doc_cmd)) + else: + notes.append("自指闭环通过:本核对件首行 `# CMD:` 去前缀后与 §8 自指行引用逐字相同") + else: + notes.append("本核对件尚未生成(磁盘无 %s),自指闭环比对留待生成后以同一命令第二跑完成" + % self_evidence) + return ("不一致 " if bad else "一致 ") + ";".join(notes), notes + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--evidence", default=SELF_EVID_DEFAULT, + help="本核对件(证据文件)路径,用于自指闭环比对") + args = ap.parse_args() + + self_evidence = args.evidence.strip() or SELF_EVID_DEFAULT + self_evidence_abs = os.path.join(ROOT, self_evidence) + + lines = io.open(DOC, encoding="utf-8").read().split("\n") + rows = [] + for ln, text in enumerate(lines, 1): + t = text.strip() + if not (t.startswith("| `tests/evidence/") and t.endswith("|")): + continue + cells = [c.strip() for c in t.strip("|").split("`")] + fpath = t.strip("|").split("|")[0].strip().strip("`") + + if fpath == self_evidence: + seg = None + for c in cells[1:]: + if c.strip().startswith("# CMD: ") or c.strip().startswith("# FILE: "): + seg = c.strip() + break + verdict, _ = check_self_row(seg, self_evidence, self_evidence_abs) + rows.append((ln, fpath, "自指行(本核对件自身,按 QC #7 闭环比对)", verdict)) + continue + + seg = None + for c in cells[1:]: + if c.strip().startswith("# CMD: ") or c.strip().startswith("# FILE: "): + seg = c.strip() + break + if seg is None: + rows.append((ln, fpath, "列内无 CMD/FILE 原文可核", "SKIP")) + continue + full = os.path.join(ROOT, fpath) + if not os.path.exists(full): + rows.append((ln, fpath, "证据文件不存在", "FAIL")) + continue + first = read_first(full) + if seg.startswith("# CMD: "): + doc_cmd = seg[len("# CMD: "):] + file_cmd = first[len("# CMD: "):] if first.startswith("# CMD: ") else "(文件首行非 # CMD:)" + verdict = "一致" if doc_cmd == file_cmd else "不一致 DOC=%r FILE=%r" % (doc_cmd, file_cmd) + else: # 转录件:核首行原文 + verdict = "一致" if first == seg else "不一致 DOC=%r FILE=%r" % (seg, first) rows.append((ln, fpath, seg[:60] + "…", verdict)) -fails = [r for r in rows if r[3].startswith("不一致") or r[3] == "FAIL"] -for ln, fpath, seg, verdict in rows: - print(" L%-4d %-62s %s" % (ln, fpath, verdict)) -print("统计:核对行 %d,一致 %d,跳过(自指/无原文) %d,不一致 %d" - % (len(rows), sum(1 for r in rows if r[3] == "一致"), - sum(1 for r in rows if r[3].startswith("SKIP")), len(fails))) -print("RESULT: " + ("ALL_ROWS_CONSISTENT" if not fails else "HAS_INCONSISTENT_ROWS")) -sys.exit(0 if not fails else 1) + fails = [r for r in rows if r[3].startswith("不一致") or r[3] == "FAIL"] + print("SELF-SCRIPT: " + SELF_SCRIPT_REL) + print("SELF-EVIDENCE: %s (exists=%s)" + % (self_evidence, "yes" if os.path.exists(self_evidence_abs) else "no")) + print("SELF-CMD-BASE: " + SELF_CMD_BASE) + for ln, fpath, seg, verdict in rows: + print(" L%-4d %-62s %s" % (ln, fpath, verdict)) + print("统计:核对行 %d,一致 %d,跳过(列内无原文可核) %d,不一致 %d" + % (len(rows), sum(1 for r in rows if r[3].startswith("一致")), + sum(1 for r in rows if r[3] == "SKIP"), len(fails))) + print("RESULT: " + ("ALL_ROWS_CONSISTENT" if not fails else "HAS_INCONSISTENT_ROWS")) + return 0 if not fails else 1 + + +if __name__ == "__main__": + sys.exit(main())