关于 facts-discover
facts-discover 是用于对照代码库审查 `.facts` 清单的 Agent 技能。它按生命周期阶段分类事实,并补充遗漏内容、修正错误或移除过时条目。
主要功能
- 01对照代码库审查事实
- 02按生命周期阶段分类事实
- 03补充缺失的行为事实
- 04修正或移除过时事实
- 05评估验证命令是否有效
适用场景
- 01使事实清单与当前代码同步
- 02检查事实的生命周期标签
- 03查找缺失或过时的项目行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ av/ facts --skill facts-discover安装后在 Claude Code 中输入/facts-discover确认已加载。
详细介绍
facts-discover
你是一名事实表维护者。你的工作是在单次会话中扫描代码库,按生命周期阶段对每条事实进行分类,并补充缺失的事实。
提示: 提供了简短的 CLI 别名,建议在高频操作中使用:ll(list --light)、at <id> <tag>(quick --add-tag)、rt <id> <tag>(quick --remove-tag)、rm 和 ls。所有额外参数都会被转发。请参阅 facts --help 或 facts skills show facts。
何时使用此技能
此技能用于对事实进行分类,并使事实表与实际情况同步。仅在用户明确要求发现、审计或同步事实时使用。 如果用户说“处理事实”或“添加事实”,他们是想定义规范——请改用 facts 技能,而不是此技能。
目标
运行此技能后,每条事实都应带有正确的生命周期标签:
@draft— 事实含糊不清或过于笼统;需要细化后才能实现(例如:“此项目支持 Stripe 付款”)@spec— 事实精确且可执行,但代码尚未提供支持(例如:“POST /payments会创建 Stripe PaymentIntent 并返回客户端密钥”)@implemented— 事实属实,且有代码库作为证据- 无标签 — 从代码库中发现的事实依据;已通过观察验证
此外,还要补充事实表中尚未包含的重要事实(这些事实已属实,因此不带标签)、修正不准确的事实,并删除过时的事实。
带有有效验证命令的事实可以自动验证自身——它们能自动发现回归问题。但手动事实胜过带有无用命令的事实。 无论事实是否属实,某条命令都总是通过,这只会造成虚假的信心,甚至不如完全没有命令。只有在命令确实能验证声明时才添加命令。
流程
1. 加载当前事实表
运行 facts list 查看所有现有事实。记录已有的章节及其涵盖的内容。
运行 facts check 查看哪些命令型事实通过了检查,哪些未通过。未通过的事实可考虑删除或修正。
对于每条手动事实(输出中以 ? 标记):阅读其声明,检查相关代码,并根据实际发现进行分类——不要只看标签。手动事实往往最重要,因为它们描述的行为难以通过简单命令验证。
2. 扫描代码库
通过追踪项目实际执行的行为而不只是其结构,建立对项目的整体认识。重点关注最终用户可见的行为——使用或集成此项目的人会关心的功能、工作流和契约。
对于大型代码库,使用多个子 Agent 并行扫描不同区域。为每个子 Agent 指定一个功能领域或模块,而不是“依赖项”或“构建系统”之类的结构类别。对于各自负责的区域,子 Agent 应回答:
- 它能做什么? — 从用户角度描述其行为
- 输入和输出是什么? — API 契约、CLI 标志、文件格式、事件结构
- 有哪些边界情况? — 错误处理、边界条件、回退行为
- 如果用简单方式重新实现,什么会出问题? — 非显而易见的不变量、顺序依赖、时序约束、组件间的隐式契约
- 关键概念有哪些? — 命名类型、领域抽象、数据结构。此模块中的名称代表什么?它们与其他模块中的概念有何关联?
每个子 Agent 都应反馈行为层面的观察结果——而不是“此文件存在”或“此处使用了库 X”,而是“发生 X 时会导致 Y”以及“如果 X 失败,系统会执行 Y”。
不要把事实浪费在结构琐事上。“项目有一个 src/auth.rs 文件”不是有用的事实。“过期令牌会被拒绝,返回 401,且响应包含一个 reason 字段”才是。
2b. 构建项目本体
在分类或编写事实之前,先在主 .facts 文件的 ## domain 部分中,将项目的关键实体及其关系确立为事实。此词汇表将成为表格中所有其他事实的标准命名。
- 根据子 Agent 的扫描结果,找出在代码库多个部分中出现的命名概念——这些就是项目的实体。
- 为每个实体按照
a <Name> is <definition>模式编写定义事实。使用代码库实际使用的名称(结构体名称、类型名称或文档中的术语),不要自行创造抽象名称。 - 找出实体之间的重要关系——什么包含什么、什么产生或使用什么、什么验证或转换什么。使用定义好的实体名称,以自然的陈述句编写关系事实。语法不必固定——关系应具体,并始终一致地使用实体名称。
- 检查现有事实表是否存在术语不一致。如果同一概念在一条事实中称为“sheet”,在另一条中称为“fact file”,请统一为一个术语,并修改不一致的事实。
如果已存在 ## domain 部分,请更新它——补充缺失的实体、删除过时的实体,并修正不准确的定义。domain 部分应随代码库不断演进。
质量筛选:
- 仅定义作为概念出现在代码库多个区域中的实体。如果某个概念仅限于单个函数,且不会出现在其他事实中,就不需要为它定义 domain。完成步骤 3–4 中的行为事实后,移除任何最终未被引用的 domain 实体。
- 关系用于描述拓扑结构——也就是无法从单条行为事实中看出的实体间连接图。只有实体定义、没有任何关系的 domain 部分,就像一份没有装配说明的零件清单。如果定义了实体,请思考它们如何连接:什么产生、使用、包含或转换什么?
- 使用代码中的实际名称。如果代码库将其称为
FactSheet,事实中就使用FactSheet。除非项目自身的文档这样称呼,否则不要规范化为“Fact Sheet”。 - domain 部分通常包含 5–15 个实体和少量关系。如果定义了 20 个以上实体,很可能纳入了实现细节,而非领域概念。如果没有概念符合跨领域标准,就完全跳过 domain 部分——并非每个项目都需要它。
对于将事实拆分到多个文件(cli.facts、api.facts)中的项目,## domain 部分应放在主 .facts 文件中,因为它适用于整个项目。
示例(支付处理项目):
## domain
- a PaymentIntent is a Stripe object representing a single charge attempt
- a Webhook is an incoming HTTP POST from Stripe reporting a payment event
- a DeadLetter is a Webhook that exhausted all retry attempts without acknowledgement
- a Merchant is a registered business account that receives payments
- a PaymentIntent produces Webhooks on status changes
- a Webhook becomes a DeadLetter after 3 failed delivery attempts
- a Merchant owns PaymentIntents
反例(不应这样写):
## domain
- a Rust source file contains module definitions
- parser.rs is responsible for parsing
- the project has a CLI that accepts commands
这些只是关于文件存在与否的结构性琐事,并非领域概念。
3. 按生命周期阶段对事实分类
对于每条现有事实,请对照代码库进行检查,并赋予正确的生命周期标签:
- 真实且有代码支持 → 添加
@implemented标签:facts edit <id> --add-tag "implemented" - 准确且可执行,但代码尚不存在 → 添加
@spec标签:facts edit <id> --add-tag "spec" - 模糊或偏高层次,尚不可执行 → 添加
@draft标签:facts edit <id> --add-tag "draft" - 部分属实 — 先编辑标签,再分类:
facts edit <id> --label "corrected statement" - 错误或已过时 — 删除:
facts remove <id> - 缺少验证方式 — 该事实可通过命令验证,但尚未提供命令:
facts edit <id> --command "check command"
如果事实已有生命周期标签,请确认标签仍然正确。代码已被移除的 @implemented 事实应重新分类为 @spec。在其他地方已完善的 @draft 事实应予以更新。
删除事实时,请确认相关概念是已经演变,而不是已经消失 — 如果同一概念以新形式延续,应编辑而不是删除后再添加。
编辑事实标签时,请使用 ## domain 中确定的术语。如果某条事实写的是“file”,但领域部分将该概念定义为“FactSheet”,请将标签更新为使用“FactSheet”,以保持一致。
3b. 为手动维护的事实添加命令
逐条检查手动维护的事实,并思考:能否用一条简短的 Shell 命令进行检查,并且在该事实不再成立时确实失败?
第二个条件是关键筛选标准。添加命令前,请先进行以下测试:
如果有人修改代码库,使这条事实不再成立,该命令会失败吗?
如果答案是否 — 或者只是有可能 — 就保留手动维护,不要添加命令。
什么样的命令才有意义
命令只有在检查主张本身(而不只是相关代码是否存在)时,才能验证一条事实。命令应当:
- 可证伪 — 事实不再成立时确实会失败
- 快速 — 在一秒内运行完成(grep、test、jq、wc、head)
- 幂等 — 只读,无副作用
- 稳定 — 不会因无关更改而失败(避免检查行数,匹配模式而非位置)
- 成功时静默 — 退出码为 0 表示事实成立,非零表示事实不成立
好的命令会检查具体且明确的内容:
# Dependency exists in manifest
grep -q '^clap' Cargo.toml
grep -q '"express"' package.json
# File or directory exists
test -f tests/cli.rs
test -d src/components
# A specific value or setting in config
jq -e '.scripts.test' package.json >/dev/null
grep -q 'edition = "2024"' Cargo.toml
# Build or test suite passes
cargo build --quiet 2>/dev/null
npm test --silent
# A property holds (or does not hold) across the codebase
! grep -rq 'unsafe' src/
! grep -rq 'unwrap()' src/handlers/
# Count-based invariants (use ranges, not exact numbers)
test $(find src -name '*.rs' | wc -l) -ge 10
# Behavioral test — actually exercise the tool
facts list --help 2>&1 | grep -q '\-\-section'
echo '- test fact' | facts lint /dev/stdin 2>/dev/null
什么样的命令毫无用处
最常见的问题是按关键词 grep:从事实标签中挑一个词,再检查它是否出现在某个源文件中。这并不能验证事实 — 它只能验证代码库使用了类似的词汇。
# BAD: "heading depth maps to hierarchy"
grep -q "depth" src/parser.rs
# This checks that the word "depth" appears in the file. It would pass
# even if depth handling was completely broken. It would fail if someone
# renamed the variable to "level" even though the behavior is unchanged.
# BAD: "tags are freeform tokens for filtering and categorisation"
grep -q "tags" src/model.rs
# The word "tags" will always be in a file that deals with tags.
# This tells you nothing about whether they're freeform or used for filtering.
# BAD: "commands run sequentially"
! grep -q "async\|tokio" src/check.rs
# Absence of async doesn't prove sequential execution — there are other
# ways to run things concurrently. And this would still pass if someone
# added parallelism via std::thread.
# BAD: "the CLI treats sections as first-class citizens"
grep -q "section" src/list.rs
# What does this even check? That the word "section" appears? Of course it does.
需要注意这种模式:如果你的命令是 grep -q "<keyword from the fact>" <file that obviously contains that keyword>,那就不是真正的检查。请停下来,寻找有意义的命令,否则就保留手动维护。
黑盒命令
好的检查命令会从外部测试行为。请比较:
# Coupled to implementation — breaks on any refactor:
node -e 'const src=fs.readFileSync("src/setup.rs","utf8"); if (!src.includes("portable-pty")) process.exit(1)'
grep -q 'status_code=401' src/auth.py && grep -q '_http_exception_handler' src/main.py
python -m pytest tests/test_streaming.py::TestTerminalEvents -q
# Black-box — tests what the user sees:
echo '{}' | myapp upload --file /dev/stdin 2>&1 | grep -q '413'
curl -s -o /dev/null -w '%{http_code}' -H 'Authorization: Bearer expired' localhost:8080/api | grep -q '401'
myapp stream --input test 2>&1 | tail -1 | grep -q '"type":"done"'
第一组会读取源码中的库名称、grep 变量名,或者只是运行已有测试。它们验证的是实现,而不是行为。第二组会运行工具并检查输出。这种检查才能经受重构。
何时保留手动维护的事实
并非每条事实都能或都应该配备命令。以下事实应保留手动维护:
- 主观或定性的内容 —“极其简单”“代码库遵循 DRY 原则”“UX 精致”
- 关于人工流程的内容 —“升级版本、提交、推送”
- 关于无法在本地查询的外部系统的内容
- 关于需要复杂集成测试才能验证,且已由项目测试套件覆盖的行为
- 关于设计意图的内容 —“每条事实都是原子且独立的”“文件顺序具有规范性”
- 只能通过关键词 grep 检查的内容 — 如果你能写出的唯一命令只检查关键词,而不是实际主张,就保留手动维护
包含 30 条经过实际验证的事实和 20 条如实标明为手动维护的事实的事实表,远比包含 50 条 grep -q "<word>" <file> 命令的事实表有用得多。
4. 补充缺失事实
找出尚未记录的重要行为和功能。按以下顺序优先处理:
- 面向用户的行为 — 用户可以用这个项目做什么?操作后会发生什么?操作错误时会发生什么?
- 组件间的契约 — 模块如何通信?每个模块向其他模块承诺什么?
- 边界情况和错误处理 — 哪些情况会出错、如何出错,以及用户会看到什么?
- 结构/架构事实 — 仅记录会限制行为的事实(例如,“单线程,因此处理程序不能阻塞”很重要;“使用 clap crate”通常无关紧要)
# Good — captures behavior a rewrite must preserve
facts add "uploading a file larger than 10MB returns 413 with a human-readable error" --section api/upload
facts add "duplicate messages within the 5-minute dedup window are silently dropped; the first is kept" --section processing/dedup
facts add "when the database is unreachable, queued writes retry 3 times with exponential backoff" --section resilience
# Bad — structural trivia that wastes space and tells an agent nothing useful
facts add "the project uses PostgreSQL for persistence" --section architecture
facts add "tests are in the tests/ directory"
facts add "the CLI is written in Rust"
优先选择符合以下特点的事实:
- 描述行为 — 说明会发生什么,而非存在什么
- 原子性 — 每条事实只表达一个真相
- 可证伪 — 可以设想一个不符合该事实的错误实现
- 值得保留 — 如果有人仅凭事实表从头重写这个项目,这条事实是否有助于他们实现正确的行为?
添加任何事实前,先检查现有事实表是否已覆盖。 如果某条现有事实已经描述了该行为,就应完善它,而不是添加近似重复的事实。如果多条现有事实以极细的粒度描述同一个端点或功能(每个错误代码一条、每个分支一条),请将它们合并为一条描述该功能契约的事实。精心维护的事实表之所以有用,正是因为事实既会合并,也会删除,而不只是不断添加。
一份包含 40 条精准行为事实的事实表,比一份包含 200 条结构观察的事实表更有用。如果单个文件中的事实表增长到约 80 条以上,请拆分为主题明确的文件(api.facts、cli.facts),并精简无关的结构性内容。
编写新事实时,请使用 ## domain 部分中的实体名称。事实表中的用词保持一致,有助于 Agent 建立准确的心智模型。如果你发现自己使用的术语未出现在 ## domain 中,请先将其添加到该部分,或者改用已有术语。
5. 组织
使用 --section 将相关事实分组到各个章节中。章节路径支持嵌套(例如 api/auth、cli/subcommands)。每个章节都应聚焦明确,内容宽泛时请拆分。
6. 验证并报告
运行 facts check,确认所有命令事实均通过检查(这也会对文件进行 lint)。
报告变更内容:新增、编辑、删除的事实,以及新增的命令。如果代码库中有任何部分存在歧义或无法完整记录,请说明。
指南
- 事实标签应简洁、明确。
- 命令质量比命令数量更重要。 如果某条命令实际上无法验证事实,那么它只会制造虚假的信心,效果还不如不加命令。只添加在事实不成立时会失败的命令。
- 编写检查命令时,优先使用
grep -q、test -f、test -d、jq -e等快速、只读的检查。除非事实本身就是要验证某项操作(例如“项目构建成功”),否则避免使用会构建、安装或修改内容的命令。 - 适用时使用标签进行分类(例如
@ci、@api、@core)。增量更改标签时使用--add-tag和--remove-tag。 - CLI 会自动清理不再包含任何事实的章节。
- 生命周期分类是首要任务。 每条事实都应具有正确的生命周期标签(
@draft、@spec、@implemented)或不带标签(真实依据)。不要移除带有@draft或@spec标签的事实——应对其进行分类,而不是删除尚未实现的工作。 - 添加从代码库中发现的新事实时,不要加标签——观察到的内容已经属实。
- 优先记录行为事实,而非结构事实。 选择要添加的内容时,先问:“重写此项目的 Agent 是否需要了解这点才能正确实现其行为?” 文件是否存在、依赖项名称等信息已记录在清单中。去重逻辑、错误响应、重试语义和边缘情况等内容在重写时容易被遗漏——这些才值得记录。
- 先确立词汇,再编写事实。
## domain章节定义了项目的关键实体及其关系。在整个事实表中保持名称一致。如果使用了## domain中未出现的新术语,要么将其添加到该章节,要么改用现有术语。
示例会话
# Load current state
facts list
facts check
# Spawn subagents to scan by feature area (auth, payments, notifications)
# Build the project ontology from scan results:
facts add "a PaymentIntent is a Stripe object representing a single charge attempt" --section domain
facts add "a Webhook is an incoming HTTP POST from Stripe reporting a payment event" --section domain
facts add "a PaymentIntent produces Webhooks on status changes" --section domain
# Classify existing facts:
facts edit x1z --add-tag "implemented" # true, code-backed
facts edit a2b --add-tag "spec" # precise but unimplemented
facts edit c3d --add-tag "draft" # vague, needs refinement
facts remove p2q # obsolete
# Consolidate: found 4 facts about webhook error paths — merge into one
facts remove w1a && facts remove w2b && facts remove w3c
facts edit w4d --label "failed payment webhooks retry 3 times with exponential backoff, then dead-letter"
# Add discovered behavioral truths (untagged = ground truth)
facts add "API rate limits to 100 req/min per key; exceeded requests get 429 with Retry-After header" \
--section api/limits
# Verify
facts check