本目录只放只读的仓库检查脚本。脚本不修改任何被检查文件,也不安装任何依赖。
| 文件 | 责任 |
|---|---|
check_docs.py |
文档一致性检查:结构、链接、证据引用、图与数据块、版本固定点、措辞边界 |
test_check_docs.py |
check_docs.py 的自测套件:钉住两条防退化纪律,并为十项检查各给出正负样本 |
本仓库有 100+ 份 Markdown、约 18000 行正文、40 条带五维证据坐标的 Claim,以及七个固定源码 commit。链接、Claim 引用和版本固定点靠人工核对不可持续。AGENTS.md 的「文档一致性」一节要求提交前运行检查,check_docs.py 就是那条纪律的可执行形式。
仅需 Python 3.8+,无第三方依赖。在仓库根目录执行:
python scripts/check_docs.pyWindows PowerShell 同样:
python scripts/check_docs.py退出码:全部通过 0;存在 FAIL 1;参数错误 2。仓库根路径由脚本按自身位置推导,从任意工作目录调用都可以。
其他用法:
python scripts/check_docs.py --list # 列出全部检查项
python scripts/check_docs.py --only links # 只跑一项,可重复给出
python scripts/check_docs.py --only links --only claims
python scripts/check_docs.py --json # 机器可读输出,明细不截断| 检查项 | 检查什么 | 扫描范围 |
|---|---|---|
required-paths |
27 条必需路径存在(两个方案包的四类关键文件、证据账本、Host Probe、模板、质量入口等) | 固定清单 |
links |
仓库内相对 Markdown 链接的目标文件存在(大小写敏感,见下);# 片段命中目标文件的实际标题 |
全部 Markdown |
claims |
正文引用的 Claim ID(NEXUS / RF / TG / LOOP / SYN / LC / LG / LI / HS / MSGR)都在证据账本中有定义行 |
全部 Markdown |
mermaid |
代码围栏配对;每个 Mermaid 块首行是 flowchart / sequenceDiagram / stateDiagram-v2 |
全部 Markdown |
json-blocks |
所有 json 围栏可被 json.loads 解析;契约文件含 JSON 时带 Illustrative / Non-normative 标注 |
全部 Markdown |
version-pins |
七个固定源码 commit 与 V1 Evidence Source / V2 Host Stack / V3 RAG Asset Release / V4 Runtime Deployment 四层版本规则可定位 |
全部 Markdown |
gate-overreach |
按固定形态清单匹配把 G3-G5、宿主兼容、实验收益、生产可用写成已经成立的措辞;命中行连同它所在的结构块一起判定,结构块内出现带语义边界的否定语时不计(形态清单、否定词形态与已知覆盖缺口见下) | 全部 Markdown |
placeholders |
无未填占位标记:TODO、TBD、FIXME、待定、待补充、PLACEHOLDER、<placeholder> |
SCOPE_CONTENT |
legacy-names |
无历史口误名 TrustRAG、Rack Flow、Trust.Rag |
SCOPE_NAMING |
skill-boundary |
tooling/loop-skill/SKILL.md 不存在;产品内容范围内不把 Knowledge Loop 用作产品或 Scheme 名称 |
见左 |
gate-overreach 只拦截 G3-G5。G0 / G1 / G2 已由 Phase 5 全仓验收判定通过,引用这三级结论不是越权。
这一项是枚举式启发检查,不能替代人工复核。它匹配的不是「越权」这个语义类别,而是 OVERREACH_PATTERN 里逐条写死的措辞形态,共七类:
- 完成体直接接 Gate:
已 / 业已 / 已经+通过 / 达成 / 完成+ 四字以内 +G3-G5; - Gate 后八字以内接结论词:
已验收、已通过、已达成、已成立、已适配、就绪、成立; 宿主兼容/宿主适配(可带性)后接已、成立、就绪;P01-P12后十二字以内接已通过、已验证,或已在+ 十字以内 +通过;实验收益已;价值+ 四字以内 +已被验证 / 已验证 / 已被实验 / 已成立;生产采用已/生产可用已;以及可启动实验。
中文表述无法被正则穷举,同一个意思换一种写法就不再命中。已知的覆盖缺口举例:P01 已在 RAGFlow 生产环境通过 不会被命中,因为 已在……通过 的中间跨度上限是 10 个字符,这句中间是 12 个。因此本项 PASS 只表示未命中上面列出的已知形态,不构成「全仓无越权措辞」的证明——脚本输出的摘要行也是这样写的。措辞边界最终由 AGENTS.md 的「证据与措辞」一节和人工评审保证,本检查只是防止已知形态回归的网。
否定语境不按固定行数判定,而是按支配命中行的结构块:命中行所在的段落 / 列表块 / 表格(含命中行本身),必要时再补上引导该块的冒号标题行。因此「以下结论当前都不成立:」后面的列表项、以及「本仓库不能声称 G4 实验成立」这类同行否定,都不会被判为越权。
「在哪里找否定语」由结构块决定,「什么算否定语」由 NEGATION_PATTERN 决定——这是两件事,早期只做对了前一件。词表里曾经有 未 与 不是 两个裸字,它们是中文高频词,与命题无关的出现照样落在同一个结构块里。下表左列是两个实测过的漏报样本,它们声称的结论在本仓库都不成立,列在这里只是为了展示两版词表的差异:
| 样本写法(其中的断言均不成立) | 裸字词表 | 收紧后 |
|---|---|---|
| 本方案已通过 G4 固定实验验收,未来将扩展到更多宿主。 | PASS(未来 里的 未 放行了整块) |
FAIL |
| 本方案已通过 G4 固定实验验收。 这不是结论的全部。 |
PASS(下一行的 不是 放行了整块) |
FAIL |
两句里的 未来 和「不是结论的全部」都与那个断言毫无关系。这正是 negation_scope() 想消除的失败模式换了一层出现:判定范围收窄了,词表还是裸字,漏报照旧。现在这两个词必须指向被否定的对象:
未后面必须直接跟表示「某事尚未发生」的谓语或被动标记 ——未通过、未达成、未验收、未成立、未适配、未进入、未被实验支持、未经验证、未创建、未验证等(完整枚举见NEGATION_UNDONE)。时间名词未来、状态词未知不再放行。不是必须在同一小句内(不跨。、;、换行)指向被否定的结论 ——不是 G3 宿主适配就绪、不是宿主兼容承诺(完整枚举见NEGATION_CLAIM)。泛指的「不是结论的全部」不再放行。- 例外只有一个:整格为「不是」的表头单元格(
| 架构标签 | 对应事实 | 不是 |)。这类对照表整列写的都是「它不是什么」,列内出现越权形态是表格的正常内容。判定粒度与结构块一致——表头一旦声明了这一列,整张表按否定语境处理。
其余否定词(不得、不能、尚未、都不成立、禁止、不构成、不表示……)语义本就无歧义,保持原样。
收紧方向是单向的:只允许减少放行。「以下结论当前都不成立:」引导的清单、「任何文件都不得声称 G3 已适配」、「宿主兼容性尚未验证」这类合法否定必须继续 PASS——用漏报换误报不是修复。改词表前先跑 test_check_docs.py 的 TestNegationWordSemanticBoundary:它带一份裸字对照实现,把上表两行的差异跑出来,并且断言新词表匹配到的裸字表必然也匹配得到(往词表里加新词会在那里变红)。
目标存在性用 path_exists_case_sensitive(),不用 os.path.exists()。后者跟随文件系统语义:Windows 与 macOS 默认大小写不敏感,一条写成 B.md、实际文件却是 b.md 的链接会让它返回 True,本地检查全绿;CI 的 ubuntu-latest 大小写敏感,同一条链接在那里是断的。同一份仓库在两台机器上得出相反结论,本地那一侧的绿色就不是证据——这与「检查脚本自身正确,检查结果才有意义」是同一条纪律。
实现方式是逐段比对父目录的实际条目名,文件名和路径中间的目录段一视同仁(Sub/c.md 指向 sub/c.md 同样是断链)。仓库根以上的路径段不校验:那取决于仓库被 clone 到哪里,不是文档里能写错的东西。
mermaid 是结构检查:本仓库不安装 Mermaid CLI,因此从未做过渲染验证,该项通过不得读作「图已渲染通过」。
范围写在 check_docs.py 顶部,改动范围是一次需要写明理由的决定:
SCOPE_CONTENT:面向读者的产品内容 ——README.md、README.en.md、AGENTS.md、foundation/、research/、baseline/、schemes/、composition/、tooling/。SCOPE_NAMING:上述范围再加project/。
docs/、.github/、scripts/ 不在这两个常量的任何一个内,原因是同一条:这些文件必须写出被禁名称和占位标记本身,才能定义或解释这项检查;把它们纳入扫描,命中的会是检查的定义,而不是缺陷。模板和 Issue 表单同理,待填字段是它们的正常形态。
project/templates/ 的处理不对称,容易看错:它不在 SCOPE_CONTENT(因此占位符检查不覆盖它——模板的待填字段是正常形态),但在 SCOPE_NAMING(因此旧误称检查覆盖它——模板里出现旧误称是真实缺陷)。
这两个常量限定的是内容类断言的范围,不是文件发现的范围。 上表标「全部 Markdown」的检查会扫描包括 .github/ 在内的所有目录。文件发现由 markdown_files() 用 os.walk 完成,排除目录由 EXCLUDED_DIR_NAMES 显式列出:.git、.superpowers、.claude、.worktrees、.agents、node_modules、__pycache__。不要改回 glob('**/*.md') —— glob 的通配符不匹配以 . 开头的目录,会让 .github/ 下的 3 个 Issue / PR 模板整体退出全部检查;也不要用「目录名以 . 开头就跳过」代替这份显式清单,那只是把隐式盲区换成显式盲区。
check_docs.py 是本仓库唯一的代码,它的输出被当作验收证据使用——检查脚本自身正确,检查结果才有意义。一个全绿的检查,只有在它确实按声明的方式扫描时才是证据;扫描范围被悄悄缩小时,检查同样是绿的,但已经出现盲区。test_check_docs.py 就是防这个的,同样只用 Python 标准库(unittest),不安装任何依赖:
python -m unittest discover -s scripts -p 'test_*.py' -vPowerShell 下把单引号换成双引号:
python -m unittest discover -s scripts -p "test_*.py" -v从任意工作目录运行都可以:被测模块按测试文件自身位置导入,测试不硬编码任何绝对路径;样本仓库一律建在临时目录里,测试不写入仓库、不执行任何 Git 命令。
A 组 —— 三条防退化纪律。 三条都来自实际发生过的误判,此前只由 check_docs.py 的模块 docstring 保护。注释拦不住「顺手简化」,断言可以。
- 文件发现必须覆盖点开头目录。 断言
markdown_files()能发现.github/x.md与normal/y.md,并且只排除EXCLUDED_DIR_NAMES里显式列出的目录(.git/、node_modules/、__pycache__/)。这一条带变异验证:测试里另写了一份glob.glob(root + '/**/*.md', recursive=True)的对照实现,断言它确实漏掉.github/x.md。实测两侧差异是os.walk得到['.github/x.md', 'normal/y.md'],glob 得到['__pycache__/v.md', 'node_modules/w.md', 'normal/y.md']——glob 既漏扫了 Issue / PR 模板,又把字节码缓存当成正式文档扫了进来。改回 glob,这条测试立刻变红。 - 否定语境判定范围必须包含命中行本身。 断言四类情形:同行否定(
本仓库不能声称 G4 实验成立这种写法不会被判为越权)、清单标题在上方的邻行否定、中文硬折行导致否定谓语落在命中行下一行的写法,以及反方向的边界——否定语落在相邻但不同的结构块里时,不得放行命中行。这一条也带变异验证:一个「只看命中行前 3 行」的对照窗口找不到同行否定,测试把这条误报路径钉死,任何把判定范围改成不含命中行的固定窗口都会变红。 - 否定词本身必须带语义边界。 上一条管的是「在哪里找否定语」,这一条管「什么算否定语」。
TestNegationWordSemanticBoundary把两个实测过的漏报样本(未来与「这不是结论的全部」)断言为必须被判为越权,同时用九组合法否定清单样本断言不得误报——「以下结论当前都不成立:」引导的清单、「任何文件都不得声称 G3 已适配」、「宿主兼容性尚未验证」、尚未作为块内唯一否定词、不是直接指向 Gate、整格为「不是」的对照表表头、未被任何实验支持、未经验证、禁止清单。这一条同样带变异验证:一份裸字词表的对照实现被断言放行那两个漏报样本,删掉它这条纪律就还原成一句注释。另有一条方向断言——新词表匹配到的写法,裸字表必然也匹配得到——把「收紧」钉成单向操作,往词表里加新词会立刻变红。
B 组 —— 各检查项的正负样本。 十项检查逐一给出「应通过」与「应失败」的构造样本,全部在临时目录里构造,不依赖真实仓库内容(依赖真实内容会让测试随仓库演进而脆断)。覆盖:锚点 slugify 的关键行为(中文保留、标点与全角括号去除、空格转连字符、重复标题追加 -1 / -2)、相对链接与锚点、链接目标的大小写敏感判定(文件名与中间目录段各一组,另有一条断言新判定不会比 os.path.exists 更宽松)、Claim 定义与引用、Mermaid 图类型与围栏配对、JSON 可解析性与契约文件的 Illustrative 标注、占位符(含标记形态边界:TODOS 不算占位符)、旧误称、必需路径、版本固定点、Skill 边界,以及两个范围常量的取舍与命令行契约(--skip 不给 --reason 必须退出 2)。
大小写那一组的断言在三个平台上都成立,因此不断言 os.path.exists 的返回值——它按定义随平台变化,写进断言只会让测试在 Windows 上绿、在 CI 上红,正是这次要修的那种体验断裂。
C 组 —— 真实仓库冒烟。 对本仓库跑一次完整检查,断言退出码 0 且十项全部 PASS。
B 组与 C 组变红的含义不同,处置方式也不同:
- B 组断言的是脚本行为,与仓库内容无关,仓库怎么演进都不该让它变红。B 组变红 = 脚本被改坏了。
- C 组断言的是仓库当前状态,随仓库内容变化是正常的。C 组变红 = 仓库可能真有不一致项,按下一节的纪律核查后再决定改哪一侧。
先核查,再决定改哪一侧。
- 读
FAIL明细,逐条到实际文件里核对该条是否真实成立。 - 判断这是脚本缺陷还是仓库缺陷:
- 脚本缺陷的典型形态:扫描范围超出声明目录、正则把否定语境误判为肯定、按子串而非标记形态匹配。修脚本。
- 仓库缺陷的典型形态:链接目标改名或未创建、Claim 未入账本、契约 JSON 少标注、措辞越过证据。修仓库。
- 修正后重跑该项:
python scripts/check_docs.py --only <check>。
纪律(依据 project/retrospectives/scheme-package-2.0.md 的 TRD-2.0-016):不得为了让检查变绿而修改被检查内容或降低断言。 验收实现本身也是可能出错的实现,FAIL 不等于被检查对象有问题;反过来,确认是仓库缺陷后也不允许删除检查项、放宽正则或缩小范围来绕过。核查过程与脚本修正应作为负面结果被记录,不能只呈现最终的绿色结果。
需要临时关闭某一项时必须显式给出理由,脚本不接受静默跳过:
python scripts/check_docs.py --skip mermaid --reason "本次只改文字,未动任何图"关闭项会以 SKIP 打印并附理由,汇总行标注「本次不构成完整检查」。CI 永远不带 --skip。
.github/workflows/docs-check.yml 在 push 与 pull_request 时于 ubuntu-latest 上运行本目录的脚本。工作流只做四步:checkout、装 Python、跑自测、跑检查,不执行任何 pip install,与主仓库「不安装宿主依赖、不引入运行时」的边界一致。
自测排在检查之前,理由写在工作流注释里:检查脚本自身正确,检查结果才有意义。自测失败即工作流失败,检查失败同样即工作流失败。
check_docs.py 顶部的模块文档写明了三条检查纪律 —— 扫描范围按声明限定、否定语境按结构块判定、否定词本身带语义边界。三条都来自实际发生过的误判,不要以「简化」为由移除:
- 去掉范围限定,误称扫描会命中定义这项检查的阶段计划与本文件。
- 把
negation_scope()降回单行正则,「以下结论当前都不成立:」后面的列表项会被误判为越权声称;换成「只看命中行前 N 行」的固定窗口,「本仓库不能声称 G4 实验成立」这类否定语与措辞同行的写法会立刻变成误报,而邻近段落里无关的否定词又会静默吞掉真实断言。 - 把
NEGATION_PATTERN里的未与不是改回裸字,未来、「这不是结论的全部」会重新放行真实的越权断言——范围收窄了但词表没收窄,等于纪律 2 只做了一半。反过来,收紧到把合法否定清单判成越权同样是错的:那是用漏报换误报。
这三条现在都有可执行的保护:test_check_docs.py 的 A 组把它们写成断言,并附带三份对照实现(glob 版文件发现、只回看的固定窗口、裸字否定词表),把「改回去会发生什么」跑出来。改动 markdown_files()、EXCLUDED_DIR_NAMES、negation_scope()、NEGATION_PATTERN、NEGATION_UNDONE 或 NEGATION_CLAIM 之前先读那几组测试;确有必要改时,连同测试一起改,并在改动说明里写清理由——不要通过删断言让测试变绿。
写本文件时注意两个自指陷阱,两个都实际踩到过:本文件在 gate-overreach 与 links 的扫描范围内(那两项扫全部 Markdown,SCOPE_* 只约束内容类断言)。举例说明越权形态时,样本所在的结构块里必须有一句真正的否定语(如上表表头的「其中的断言均不成立」),否则解释这项检查的文档会被这项检查判负;举例说明链接形态时不要写出 ]( 序列,链接正则不认代码围栏,示例会被当成真链接去解析。
新增检查项时:在 CHECKS 元组注册,函数返回 result(...) 结构,写清 docstring 首行(--list 会打印它),在上表补一行说明扫描范围,并在 test_check_docs.py 的 B 组补一组正负样本(TestCheckRegistry 会断言注册项数量,漏补会变红)。