Lockyc/docgraph
GitHub: Lockyc/docgraph
一款基于 Go 的仓库文档图谱审计门禁工具,用于在推送或 CI 阶段检测孤儿文档、失效链接和未跟踪文件,保障 AI agent 导航文档的结构完整性。
Stars: 0 | Forks: 0
# docgraph
[](https://github.com/lockyc/docgraph/releases/latest)
[](https://github.com/lockyc/docgraph/actions/workflows/ci.yml)

[](LICENSE)
审计仓库的 **面向 agent 的文档图谱** —— 即 AI agent 通过 grep 和
跟踪 `[x](y.md)` 链接来导航的文档,而不是人类浏览的渲染
站点 —— 并扫描已跟踪文件的内容,查找泄露的密钥/所有者特定的
字符串。它的设计是作为 **pre-push 门禁**或在 CI 中无包装运行:
一旦发现结果,将以非零状态退出并中止推送。
## 快速开始
```
go install github.com/lockyc/docgraph/v2@latest # needs Go + git on PATH
docgraph . # audit the current repo
docgraph install-hook # wire it in as a pre-push gate
```
在 Claude Code 下,`/docgraph:install` 会执行安装,连接 `doc-drift`
Stop 钩子,并为你初始化 leaks 配置。请参阅 [安装](#install) 了解
完整菜单。
## 四种模式
docgraph 有四种独立的模式,每种模式都有自己的触发器和范围:
| 模式 | 命令 | 扫描范围 | 运行时机 | 是否拦截? |
| --- | --- | --- | --- | --- |
| **全状态检查** | `docgraph [path]` | 当前树 | pre-push / CI | **是** — 任何发现都会以 exit 1 退出 |
| **`footgun-drift`** | `docgraph footgun-drift` | 推送 *新增* 的内容 | pre-push(建议性附加) | 否 — 仅提示,总是以 exit 0 退出 |
| **`covers-drift`** | `docgraph covers-drift` | 推送 *修改* 的代码 | pre-push(建议性附加) | 否 — 仅提示,总是以 exit 0 退出 |
| **`doc-drift`** | `docgraph doc-drift` | 分支差异(包含未提交内容) | agent Stop 钩子 | **是** — 任何发现都会以 exit 2 退出 |
此外还有**只读**辅助工具,它们从不作为门禁:`schema`(输出 frontmatter
词汇表),以及文档图谱的 **视图** `covers` / `index` / `stale`。
**范围说明。** 全状态文档图谱检查仅审计 *文档*(`docs/`,
`CLAUDE.md`,配置目录的 READMEs)—— 而不是站点框架路由和渲染的
内容(例如 Astro/MkDocs 内容集合,种子数据语料库);请在每个仓库中通过
`.docgraphignore` 排除这些内容。`leaks` 检查的范围更广——它会扫描 *所有*
已跟踪文件(参见 [`leaks`](#leaks--the-content-scan))。
## 全状态检查
在 `docgraph [path]` 时默认运行六项检查。**所有内容均被强制执行;你需要**
使用 `--skip ` **明确排除** —— 不存在选项式开启(opt-in)的
内容,因此在更高版本中添加的检查会在其落地的当天在所有地方启用,无需
更新运行列表。(例如,一个基于导航的 MkDocs 仓库可以运行 `--skip
orphans`。)
1. **Orphans(孤儿文档)** —— 未从入口点可达的已跟踪文档。可达性
遵循 markdown 链接、裸路径/内联代码路径提及(`` `docs/x.md` ``),
*以及* frontmatter 类型化 edges(参见 `edges`)—— agent 会跟踪这三种中的任何一种,
因此仅通过 `part-of` edge 到达的文档不是孤儿。每一个真实的
`.md` 都会被审计,包括 `docs/` 之外的文档(例如配置目录的 README);
只有 `.claude/` 和 `.agents/` 下的 agent 工具以及未跟踪的草稿会被
排除。
2. **Broken links(失效链接)** —— 目标不存在的 `[x](y.md)`。检查范围
与孤儿文档相同的已跟踪、未忽略的 `.md` 集合。
3. **Untracked(未跟踪)** —— 存在于磁盘但不在 git 中的 `.md`(忘记执行 `git add`)。
4. **Leaks(泄露)** —— 匹配配置的泄露模式的已跟踪文件 *内容*(参见
[`leaks`](#leaks--the-content-scan))。
5. **Frontmatter** —— 文档开头的 YAML 块(第一行精确为 `---` 到
下一个 `---`),如果存在,必须是格式良好的 YAML 并包含 `type` 字段。完全
没有块也是可以的;格式错误的 YAML 或缺少 `type` 会被视为发现。`type`
是一个建议性的词汇表 —— 参见 [`schema`](#docgraph-schema--the-frontmatter-vocabulary)。
6. **Edges(边)** —— frontmatter `links:` 列表中的每个内部 `to` 目标(一个
相对于仓库根目录的 `.md` 文档或代码路径)必须存在,并且文档之间的 `part-of` /
`supersedes` edges 不能形成环。外部 URL 和
`owner/repo:...` 跨仓库目标永远不会被检查。
### `docgraph schema` —— frontmatter 词汇表
```
docgraph schema # prints the JSON Schema (draft 2020-12) to stdout
```
输出描述有效文档
frontmatter 的 [JSON Schema](https://json-schema.org/) —— 即 `frontmatter` 和 `edges` 检查
强制执行的 `type` / `verified` / `review` / `links` 结构,
以及建议性的 `type` / `rel`
词汇表(作为 `x-docgraph-core-types` / `x-docgraph-core-rels`)。其他工具
(如编辑器、目录构建器)可以根据 docgraph 使用的相同规则进行验证,
而无需重新编码。**只读** —— 从不读取仓库,从不是
门禁的一部分。
**在哪里放置 frontmatter。** 相比于
`README.md`,更推荐放在 `CLAUDE.md` 和 `docs/` 页面中:GitHub 会将开头的 YAML frontmatter 块渲染为位于
页面内容上方的元数据表,因此带有 frontmatter 的 `README.md` 会用
`type` 和 `links` 行组成的表格迎接每一位访客。Frontmatter 是面向 agent 的元数据,而
README 是人类的大门——两者不需要同一个文件。docgraph
本身遵循这一点:`CLAUDE.md` 将 `covers` edges 携带到其代码中,而此
README 完全不包含 frontmatter。没有规则强制要求这一点——带有 frontmatter 的
README 是有效的,只是会被渲染成那个样子。
### `leaks` —— 内容扫描
扫描**已跟踪的文件内容**(仅工作区,从不扫描 git 历史)以查找
密钥/所有者特定的字符串,在仓库公开之前捕获它们。默认
运行;`--skip leaks` 可将其关闭。
范围受 **git 跟踪**控制,而不是文档图谱的忽略层:每个
`git ls-files` 条目都会被扫描(因此 `.gitignore` 决定了排除的内容),并且
`defaultIgnores` / `.docgraphignore` **不会**缩小其范围——只有明确的
`--ignore` glob 才会。已跟踪的 `.claude/` 配置会在公开克隆中提供,因此即使文档图谱检查跳过它,它也
仍在扫描范围内。
模式来源于**全局 TOML 文件,从不提交到仓库**——每个仓库的
拒绝/允许列表本身就会枚举你的敏感词汇。解析顺序:
`--leaks-config ` → `$DOCGRAPH_LEAKS` →
`$XDG_CONFIG_HOME/docgraph/leaks.toml`(默认为 `~/.config/docgraph/leaks.toml`)。
`terms` 按字面意思匹配,不区分大小写;`regex` / `allow_regex` 是 Go
正则表达式,除非你通过在模式开头添加
`(?-i)` 来针对特定模式禁用,否则也不区分大小写。`allow` / `allow_regex` 会抑制它们所涵盖的拒绝匹配。`[[dir]]`
部分将异常范围限制在绝对 `path` 下的文件中(开头的 `~/` 会被
展开;非绝对的 `path` 是致命的配置错误)。
```
terms = ["acme-host", "you@example.com", "/Users/you"]
regex = ['10\.0\.0\.\d+']
allow = ["github.com/you"]
allow_regex = ['com\.example\.[a-z]+']
[[dir]]
path = "/abs/path/to/repo"
ignore = ["vendor/*.json"] # skip vendored specs
[[dir]]
path = "/abs/path/to/repo/sub"
allow = ["some-project"] # legit in this subtree
```
**配置是规则的唯一来源——没有隐藏的内置规则。** 通用的密钥
形式(PEM、AWS、GitHub、Slack)只是你添加的 `regex` 条目,通过在开头加上
`(?-i)` 来保持大小写敏感:
```
regex = [
'(?-i)-----BEGIN [A-Z ]*PRIVATE KEY-----',
'(?-i)AKIA[0-9A-Z]{16}',
'(?-i)ghp_[A-Za-z0-9]{36}',
'(?-i)xox[baprs]-[A-Za-z0-9-]{10,}',
]
```
由于配置是机器本地的,CI 和全新克隆(无文件)没有规则,
因此扫描在那里是空操作——它仍然是一个**本地**的 pre-push 门禁。处理方式:
- **无配置文件** → 无规则,不扫描任何内容,打印警告。**不**致命
(硬失败会导致每次 CI 推送都失败)。
- **格式错误的配置**(错误的 TOML、错误的正则表达式、非绝对的 `[[dir]]` 路径) →
exit 2,失败即关闭。损坏的配置是一个真实的 bug,而不是“尚未设置”的情况。
**已知缺口:**没有 git 历史 *检测*(使用手动泄露审计技能);从历史中
*清除* 已知的泄露请使用下方的 `docgraph leaks-rules`。无
针对每条规则的消息。
### `docgraph leaks-rules` —— 导出用于历史清理的规则
`leaks` 检查会扫描当前树。要清除已经存在于 **git
历史** 中的泄露,请将词汇表导出为
[`git-filter-repo`](https://github.com/newren/git-filter-repo) 规则文件,然后单独运行
重写:
```
docgraph leaks-rules > rules.txt # non-destructive: reads only the config
git filter-repo --replace-text rules.txt # destructive: rewrites history
```
它为每个拒绝规则生成一行 `regex:`(词汇被转义且不区分大小写;
`regex` 条目保持不区分大小写,除非它们带有 `(?-i)`,并被规范化为
普通的大小写敏感模式),使用 filter-repo 的 `***REMOVED***` 替换。一条
stderr 摘要会报告它**丢弃**的任何 `allow` / `allow_regex` / `[[dir]]` 规则——
filter-repo 根据内容重写所有路径和历史,因此
这些异常无法应用。生成的模式针对的是 filter-repo 的 Python 正则
引擎,因此仅 RE2 的语法可能需要手动审查。
## `footgun-drift` —— 建议性的 pre-push 附加检查
`footgun-drift` 仅扫描推送到已跟踪 markdown 的**新增**内容,并且是
**建议性的**:它打印提示但以 exit 0 退出,从不拦截。它会标记任何 *新增* 行上的
footgun **声明**(行首的 `Footgun:` 标记或行中加粗的 footgun
引导——指的是引入一个,而不是提及它;交叉引用或单纯的 `##
Footguns` 标题永远不计入)。
```
docgraph footgun-drift # reads git's pre-push ref lines from stdin
docgraph footgun-drift --range base..head # explicit range, for manual use
```
在没有 `--range` 的情况下,它读取 git 在 stdin 上提供给 `pre-push` 钩子的 ref 行,并
为每个 ref 推导出 `remotesha..localsha`(新分支会回退到与最近的集成分支的 merge-base)。只有当其行位于该范围的 *新增* 行集合中时,声明才会被计入,并在头修订版本读取——因此远程上已有的
声明永远不会被重新标记。文件范围是 diff 涉及的每个 `.md`,包括被文档图谱检查排除的 `.claude/` skill 文件。
它是一个**提示,而不是裁判**:它报告每一个新增的声明,因为它无法
评估所陈述的“原因”是否充分——这是它不打算做的判断。在发现时它会问两个问题 —— (1) 这是一个真实的 footgun(你遇到的陷阱、诱人但错误的方法、重新讨论的决定)吗?(2) 它是否处于正确的文档级别(常量 → `CLAUDE.md`,原理解释 → `docs/`,人类说明 → `README`)?修复它或将其留待后续处理;没有任何内容被拦截。
`DOCGRAPH_FOOTGUN_OFF=1` 会直接静默它(适用于不使用
`Footgun:` 约定的仓库);`docgraph install-hook --no-footgun-drift` 会生成一个从不调用它的
钩子。没有文件内的抑制标记。
## `covers-drift` —— 建议性的 pre-push 附加检查
`covers-drift` 扫描推送更改的**代码**,并报告任何在其上声明了 frontmatter `covers` edge 而文档本身未被触及的文档。与
`footgun-drift` 一样,它是**建议性的**:它打印提示,以 exit 0 退出,
并且从不拦截。
```
docgraph covers-drift # reads git's pre-push ref lines from stdin
docgraph covers-drift --range base..head # explicit range, for manual use
```
这是 [`doc-drift`](#docgraph-doc-drift--the-stop-hook-staleness-gate)
无法做到的图谱连接:一个被重写的函数,其文档以文字形式描述了旧行为,没有留下任何被移除的符号,也没有更改后的字面量可供 grep,但是一个 `covers` edge声明该文档为该代码的架构记录。它做不到的是*做出判断*——它无法知道文档是否真的需要
协调,这正是它从不作为门禁的原因。
在没有 `--range` 的情况下,它读取 git 在 stdin 上提供给 `pre-push` 钩子的 ref 行,并
为每个 ref 推导出 `remotesha..localsha`,在各个 ref 之间去重发现结果。文档仅限
`.md`——这与每次全状态检查读取的 `RepoDocs`/`trackedMD` 集合相同,
不像 `doc-drift` 的 doc-grep 还会匹配 `.mdx`;“代码”是每个其他
已跟踪的路径,除了文字格式 `.txt`/`.rst`/`.adoc`/`.markdown`。一个
`.mdx` 文件因此既不是文档也不是代码:在一个文件中声明的 `covers` edge 永远不会触发。
**编辑文档会使其静默**——变更集触及的文档永远不会触发,因此
没有什么可以抑制的,也不存在文件内的标记。一个没有 `covers` edges 的仓库永远不会看到它。要彻底关闭它:
`DOCGRAPH_COVERS_OFF=1`,或者使用
`docgraph install-hook --no-covers-drift` 生成一个从不调用它的钩子。
## `docgraph doc-drift` —— Stop 钩子的过时检查门禁
将 `doc-drift` 连接到你的 agent harness 的 `Stop` 钩子中(直接调用,无包装器),这样它就会在每次结束时运行,在任何 agent 交回控制权之前,捕获仍然描述了刚刚在其下方更改的代码的已跟踪文档。
它扫描一个 **包含工作区的** 范围 —— base→worktree,覆盖已提交
*和* 未提交的更改,因为它在提交必然存在之前触发。
在主干分支上,base 是 `HEAD`(仅未提交内容);在特性分支上,它是
最近集成分支的 merge-base(即到目前为止的整个分支)。它标记两类机械性的过时,并且两者都会**阻止**回合结束:
1. **Dangling reference(悬空引用)** —— 其 *定义* 已在 diff 中移除
并且在已跟踪代码中的其他任何地方都不存在,但已跟踪文档仍然引用了它的符号。
2. **Anchored value drift(锚定值漂移)** —— 其数值 *值* 已更改的常量,而已跟踪文档仍然引用该常量 **并且** 显示了旧的字面量。
```
docgraph doc-drift # bare: resolves the base itself, applies the loop-guard
docgraph doc-drift --range base..head # explicit range — bypasses the loop-guard
```
直接调用应用了一个**基于每个 HEAD 一次的循环保护(loop-guard)**:在它为特定的
`HEAD` 提示之后,在同一个 `HEAD` 处重复调用将保持静默,因此一个不断结束其
回合而不采取行动的 agent 不会在每次 Stop 时都被提示。下一次提交会移动 `HEAD` 并
重新启用它。这会对*提示*去重,它不会抑制*发现结果*。
任何一个类别的发现都会**阻止**回合 —— 打印到 **stderr**,以 **2** 退出。两者
都是机械性的事实,这正是让它们能够通过钩子停止回合的原因;
判断性的决定属于建议性的 pre-push 附加检查。
`DOC_DRIFT_OFF=1` 会完全禁用整个子命令,适用于不使用它所依赖的锚定符号和值约定的仓库。没有
其他抑制途径(没有 `.docgraphignore`,没有针对单个发现的标志,没有内联
标记):被标记的引用是一个判断性决定——协调文档,或确认它是具有历史框架意图的。
值得了解的范围限制:
- **文档是 `.md`/`.mdx`;“代码”是其他所有内容**,除了文字格式
`.txt`/`.rst`/`.adoc`/`.markdown`,它们被排除在代码扫描之外,因此
类似定义的文字(例如 `CHANGELOG.txt` 中的 `class …` 句子)不会被读取为已移除的
定义。
- **锚定值漂移不进行邻近度检查** —— 当一个引用符号的文档在文件中的 *任何地方*
也包含旧的字面量时,它就会触发,因此如果
该数字巧合地出现,它可能会过度报告。
- **`--range` 针对当前工作区进行评估**,因此一个带有未提交更改的工作区可能会
更改相同范围的判定结果。纯粹的 Stop 钩子模式比较 base→worktree,并且是
自洽的。
- 它只捕获那两类机械性问题 —— 一个改述的值或一个
没有锚定符号的推翻决定需要语义上的文档扫描。
## `covers`, `index`, `stale` —— 只读视图
查询文档图谱以供人类或 agent 工具使用的三个子命令,建立在
六项检查使用的相同解析之上。这三个都是**只读**的:从不写入,从不
作为门禁(不在 `checkNames` 中,不可通过 `--skip` 跳过,不由生成的钩子运行),
成功时始终以 exit `0` 退出 —— 仅在用法/git 错误时以 `2` 退出。
```
docgraph covers # docs that cover (repo-root-relative)
docgraph index # generated markdown index of the doc graph
docgraph stale # docs whose verified date is past its threshold
docgraph stale --older-than 90 # override the default 180-day threshold
```
- **`covers `** —— 打印每个通过 frontmatter `covers` edge 记录 `` 的已跟踪文档,
直接覆盖或通过覆盖父目录
(`covers: src/auth/` 覆盖 `src/auth/login.go`)。`` 是
**相对于仓库根目录的路径** —— frontmatter edges 根据仓库根目录进行解析,与
内联 markdown 链接不同。如果没有文档覆盖它,则不打印任何内容(exit `0`)。同样的
`covers` edges 提供给
[`covers-drift`](#covers-drift--the-advisory-pre-push-rider) pre-push 附加检查,因此
声明一个 edge 既能回答此查询,又能在其代码更改时使文档被呈现出来。
- **`index`** —— 打印一个**生成的** markdown 索引:每个带有
frontmatter 的文档,按 `type` 分组(核心类型按规范顺序排列,然后
自定义类型按字母顺序排列),每个格式为 `- [label](path) — description`(当文档没有 `description` 时,尾部被
省略)。标签是文档的 `title`(如果
设置了的话),否则是其 **body H1**,否则是其路径 —— 因此文档可以获得一个可读的条目,
而无需在其 frontmatter 中重述自己的标题;仅当
索引标签应该与 H1 不同时才设置 `title`。它是一个视图,而不是一个手动维护的
页面 —— 将其重定向到一个已跟踪的文件中(`docgraph index > docs/index.md`)并在
图谱更改时重新生成。
- **`stale [--older-than ]`** —— 打印 `verified` 日期超过其过时阈值的每个文档:
`docs/old.md (verified 2026-01-01 — 195d old,
threshold 180d)`。阈值是 `--older-than`(默认 **180**),除非
文档自己的 `review:` 频率(例如 `review: 90d`)覆盖了它。没有
`verified` 日期或值无法解析的文档将被静默跳过——格式错误的
frontmatter 是 `frontmatter` 检查的关注点。
## 安装
**引导式(Claude Code):** `/docgraph:install` —— 安装二进制文件,提供将
`doc-drift` Stop 钩子连接到 `~/.claude/settings.json` 的选项,提供此仓库的
pre-push 门禁,并初始化 leaks 配置。
**手动:**
```
curl -fsSL https://raw.githubusercontent.com/lockyc/docgraph/main/install.sh | bash
```
运行 `go install`(如果你当前在检出中,则从当前检出运行,否则运行 `@latest`),
初始化 `~/.config/docgraph/`,并打印二进制文件的安装位置。或者直接:
```
go install github.com/lockyc/docgraph/v2@latest # or, from a checkout: just install
```
需要在 PATH 上有 **Go**(安装命令是 `go install`)和 **git**(docgraph 在运行时调用
它)。模块依赖项是 `github.com/BurntSushi/toml`
(配置解码)和 `gopkg.in/yaml.v3`(frontmatter 解码);其余部分是 Go
标准库。
### 作为 pre-push 门禁
```
docgraph install-hook [path] # gate: enforce ALL checks (default)
docgraph install-hook --skip orphans # nav-driven repos (no orphan gate)
docgraph install-hook --ignore '**/*_test.go' # bake an --ignore glob into the hook
docgraph install-hook --force # regenerate an existing hook
docgraph install-hook --no-footgun-drift # omit the footgun-drift rider
docgraph install-hook --no-covers-drift # omit the covers-drift rider
```
生成的钩子运行全状态门禁 `docgraph .`(纯粹的调用,因此
无需重新生成即可强制执行更高版本的检查),然后是建议性的
`docgraph footgun-drift` 和 `docgraph covers-drift`,每个都接收 git 的 pre-push
stdin。只有前者能阻止推送——两个附加检查都带有 `|| true`,因此即使其中一个发生操作错误,也
不会中止它。它写入一个已跟踪的 `.githooks/pre-push`
并为此克隆设置 `core.hooksPath`(其他克隆通过 `git config
core.hooksPath .githooks` 激活)。它拒绝覆盖现有的钩子(传递
`--force`,或者将 docgraph 调用集成到你自己的钩子中)。它以**失败即关闭**运行——缺失的
`docgraph` 会阻止推送,因为当工具缺失时跳过检查的门禁是虚假的绿灯。
## 用法
```
docgraph [path] # path defaults to '.'; enforces all checks
docgraph --root wiki/Home.md # add an extra entry point (repeatable)
docgraph --ignore 'vendor/**' # exclude a glob from checks (repeatable)
docgraph --skip orphans # exclude a check (comma-separated)
docgraph --leaks-config # override the global leak rules file
docgraph --config # override the global config.toml (usage logging)
docgraph footgun-drift # advisory: reads pre-push ref lines from stdin
docgraph covers-drift # advisory: docs covering the code a push changes
docgraph doc-drift # Stop-hook: working-tree-inclusive diff
docgraph schema # print the frontmatter JSON Schema (read-only)
docgraph covers # read-only: docs that cover
docgraph index # read-only: generated markdown index
docgraph stale # read-only: docs past their freshness threshold
docgraph version # print version (also --version, -v)
```
**退出代码。** `docgraph [path]`:`0` 干净 · `1` 发现结果 · `2` 用法 / 不是
git 仓库 / 格式错误的 leak 配置。`footgun-drift` 和
`covers-drift` 是建议性的:无论是否有发现结果都是 `0`(stdout 上有提示),仅在发生 git/用法
错误时为 `2`。`doc-drift` 会拦截:`0` 干净(或被循环保护静默) · `2` 表示发现
悬空引用或锚定值(stderr)或发生错误。`covers` /
`index` / `stale` 是只读的:成功时始终为 `0`,仅在出错时为 `2`。
在发现结果时,`docgraph [path]` 会在
发现结果下方打印一个自描述的页脚——docgraph 是什么,为什么非零退出会中止推送,以及如何
修正每个类别——因此不必逆向工程即可了解失败的推送。
干净/CI 运行保持简洁。
### 入口点(根节点)
可达性从被跟踪的 `CLAUDE.md`、`README.md`、`AGENTS.md`(仓库
根目录)和 `docs/index.md` 中的任何一个开始,加上你添加的任何 `--root`。这涵盖了
整个文档仓库以及文档为 `CLAUDE.md` + `docs/` 的项目。
### 忽略路径
对于文档图谱检查,`**/superpowers/**`、`.claude/**` 和 `.agents/**` 默认被忽略(未跟踪的草稿,以及永远不属于
文档图谱的 agent skill/配置工具)。通过 `.docgraphignore`(gitignore 语法)或
可重复的 `--ignore` glob(`**`、`*`、`?`)添加更多内容。leak 扫描仅遵循 `--ignore`,
不遵循 default/`.docgraphignore` 层 —— 参见 [`leaks`](#leaks--the-content-scan)。
**没有内联标记。** 每个抑制都存在于配置或命令行中——
`.docgraphignore`、`--ignore`、`--skip` 以及 leaks 配置的 `allow` /
`allow_regex` / `[[dir]]`。docgraph 从不读取被审计文件内部的抑制注释。`footgun-drift`、`covers-drift` 和 `doc-drift` 根本没有文件内的
转义途径——它们只能通过整个检查级别的选项禁用,即通过 `DOCGRAPH_FOOTGUN_OFF=1` /
`--no-footgun-drift`、`DOCGRAPH_COVERS_OFF=1` / `--no-covers-drift`,以及
`DOC_DRIFT_OFF=1`。
### 文档模型以及何时使用 `--skip orphans`
孤儿检查假定一个**文字链接的**文档图谱(入口文档通过链接/提及遍历
`docs/`),这适用于大多数仓库。有两个例外:
- **基于导航的 MkDocs 站点** —— 一个没有 `nav:` 块的 `docs/`;MkDocs
自动构建侧边栏,且页面从不交叉链接,因此每个页面按设计
都是文字孤儿。使用 `--skip orphans` 进行门禁。
- 带有真正未被引用的设计文档的仓库会报告真实的孤儿——从
`CLAUDE.md`/`README` 链接它们,或者接受现状并使用 `--skip orphans`。
**一个内容语料库——备忘单部分、wiki 页面、种子导出、
逐字剪裁——只有一个问题:它是否由你掌管以符合规范?**
- **手动维护 → 使其符合规范。** 给每个页面一个 `type:`,并为语料库提供一个
手动维护的文字链接索引页面以保证可达性。不要用 `.docgraphignore`,
不要用 `--skip`。外部的 frontmatter 词汇表不构成障碍——未知的键会被
原样保留,因此 Obsidian 剪裁保留 `created`/`source`/`author` 并
获得 `type reference`。语料库成为真实的图谱节点,并且
`broken`/`untracked` 继续覆盖它。
- **派生 / 从不手动编辑 → 排除它**,使用 `.docgraphignore`,因为
重新生成会丢弃你添加的任何 `type:`,所以符合规范是无法持久的。
- **`--skip` 在任何一种情况下都是错误的** —— 它是全仓库范围的,因此语料库的约定
也会在你的 `CLAUDE.md`/`README.md` 上禁用这些检查,而在那里它们是有效的。
孤儿或 frontmatter 发现结果的大量涌入是去查看的理由,而不是排除的理由:
每一个都有一个廉价的修复方法(一个索引页面;一行 `type:`),而排除
通过永远不再检查该内容换取了一个安静的门禁。
`.docgraphignore` **不会**豁免语料库的 `leaks` 检查——该扫描的范围
由 git 跟踪决定,而不是文档图谱的忽略层。那是一个
带有独立控制杆的独立决定:一个合理地充满了你的规则匹配的主机、路径和标识符的
知识库不是在泄露,它只是在展示自己,因此请在
**leaks 配置**中使用针对该语料库的 `[[dir]]` `ignore` 将其静默。两个忽略
层,两个问题——“这是一个文档图谱吗?”和“应该对这段内容进行 leak 扫描吗?”——独立回答
它们。
不使用 `Footgun:` 约定的仓库可以完全退出 `footgun-drift`
(它不是检查,因此没有 `--skip` 名称)——`DOCGRAPH_FOOTGUN_OFF=1` 或
`install-hook --no-footgun-drift`。`covers-drift` 具有相同的
选项(`DOCGRAPH_COVERS_OFF=1` / `install-hook --no-covers-drift`),尽管没有
`covers` edges 的仓库没有什么可关闭的。
## 使用日志
docgraph 可以在每次运行时向本地日志追加一行 JSON,用于跟踪所有仓库随时间推移的使用情况和发现
趋势。它是**可选的**并且是机器本地的:除非全局
`config.toml` 启用它,否则处于关闭状态,因此 CI、全新的克隆和贡献者
永远不会记录。
在 `~/.config/docgraph/config.toml` 中启用它(解析顺序 `--config` →
`$DOCGRAPH_CONFIG` → `$XDG_CONFIG_HOME/docgraph/config.toml`):
```
[log]
enabled = true
level = 1 # 1 counts · 2 +paths · 3 +findings
# path = "~/.local/state/docgraph/usage.jsonl" # 可选;这是默认值
```
记录会存放在 `$XDG_STATE_HOME/docgraph/usage.jsonl`(默认为
`~/.local/state/docgraph/usage.jsonl`),可通过 `[log].path` 或
`DOCGRAPH_LOG` 覆盖。一级记录:
```
{"ts":"2026-07-09T21:30:00+10:00","version":"2.0.0","repo":"/abs/git/root",
"cmd":"run","checks":["broken","edges","frontmatter","leaks","orphans","untracked"],
"exit":1,
"counts":{"broken":1,"edges":0,"frontmatter":0,"leaks":0,"orphans":0,"untracked":0}}
```
**详细级别**在丰富度和暴露度之间进行权衡:
- **1 —— 仅计数。** 无路径,无内容。安全的默认值。
- **2 —— 添加 `files`。** 标记的路径(broken/leaks 包含 `file:line`),但
**绝不包含泄露的匹配文本。**
- **3 —— 添加 `findings`。** 完整的详细信息,**包括泄露匹配字符串** —— 这
会将日志准确地变成 `leaks` 存在以防止的敏感字符串汇集地。
仅在受信任的机器上使用。
注意:
- **无配置 → 静默关闭**(正常状态)。**格式错误的**
`config.toml` 会发出警告并禁用日志记录,但**不会导致运行失败**——日志记录
是辅助功能,因此日志配置的拼写错误绝不能阻止推送(这与格式错误的
`leaks.toml` 不同,后者是致命的)。
- `DOCGRAPH_NO_LOG=1` 即使在配置启用它时,也会为单次运行禁用日志记录。
- 尽力而为:无法写入的日志文件将被静默跳过,并且永远不会更改
退出代码。
## 已知缺口
锚点有效性(`y.md#missing`)、外部 URL 活性、HTML 块中的原始 ``、基于部分
`index.md` 的隐式导航以及特定于仓库的约定均不在
范围内。Markdown 链接提取(失效链接检测、链接 edges)跳过
fenced/内联代码,因此示例路径不会被视为真实链接;相反,孤儿
*可达性* 过程确实会读取内联代码的路径提及。
`footgun-drift` 的声明扫描没有代码块意识——` ``` ` 块中的示例
`Footgun:` 行会被读取为真实的声明。
## 开发
```
just test # go test ./...
just build # go build -o docgraph .
just install # go install . -> ~/go/bin/docgraph
just gate # gofmt check + vet + tests (pre-release gate)
```
工作落在 `dev` 集成分支上;`main` 是发布分支,并且仅
快进到标记的发布。分支特性/修复工作在 `dev` 外进行,合并前运行 `just
gate`。发布遵循 [semver](https://semver.org):根
`VERSION` 文件是单一的事实来源(通过 `go:embed` 嵌入),并且 `just
release` 打上 `v` 标记并发布 GitHub 版本。使用者运行 `go install
…@latest`,因此发布会移动每个人固定使用的工具——保持 `main` 处于可发布状态。
当钩子触发时,必须可以访问到已安装的二进制文件:生成的 pre-push
钩子通过 PATH **和** Go bin 目录(`$GOBIN` / `$GOPATH/bin` /
`~/go/bin`)解析 docgraph,因为 git 使用调用者的 PATH 运行钩子,而 GUI 客户端 /
沙箱化的 agent 经常在带有缺少 `~/go/bin` 的纯粹 PATH 的情况下进行推送。
布局:`main.go` 是一个轻量级的 CLI(flags → 审计 → 报告 → 退出代码);
`internal/audit/` 包含逻辑(链接解析、glob-ignore、git 包装器加上
diff 助手、全状态 `Audit` 编排器、leak 扫描器、
基于 diff 范围的 `FootgunDrift` 和 `CoversDrift`,以及对比代码并 grep 文档的 `DocDrift` 编排器)。有关设计常量,请参见 `CLAUDE.md`。
标签:AI辅助开发, EVTX分析, Git Hooks, Go, Python安全, Ruby工具, SOC Prime, 云安全监控, 开发工具, 开源框架, 持续集成, 文档检查, 日志审计, 网络安全研究, 静态分析