facts-discover について
facts-discoverは、コードベースと`.facts`シートを照合するエージェントスキルです。事実をライフサイクル段階ごとに分類し、不足分の追加や誤りの修正、古い項目の削除を行います。
主な機能
- 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
あなたはファクトシートのメンテナーです。コードベースをスキャンし、すべてのファクトをライフサイクルの段階ごとに分類して、不足している真実を追加することが役割です。これを1回のセッションで行います。
ヒント: 高頻度の操作では、短い 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. コードベースをスキャンする
プロジェクトの構造だけでなく、プロジェクトが実際に何をするのかをたどって、全体像を把握します。エンドユーザーから見える振る舞い、つまり、このプロジェクトを使ったり統合したりする人が関心を持つ機能、ワークフロー、契約に注目してください。
大規模なコードベースでは、サブエージェントを使って異なる領域を並行してスキャンします。各サブエージェントには、「依存関係」や「ビルドシステム」のような構造上の分類ではなく、機能領域またはモジュールを割り当ててください。各領域について、サブエージェントには次の点を確認してもらいます。
- これは何をするのか? — ユーザーの視点から振る舞いを説明します
- 入力と出力は何か? — API 契約、CLI フラグ、ファイル形式、イベントの形状
- エッジケースは何か? — エラー処理、境界条件、フォールバック動作
- これを単純に再実装すると、何が壊れるか? — 自明ではない不変条件、順序の依存関係、タイミング制約、コンポーネント間の暗黙の契約
- 主要な概念は何か? — 名前付きの型、ドメインの抽象化、データ構造。このモジュールでは何をどのように呼んでいるのか。また、それらの名前は他のモジュールの概念とどう関連しているのか
各サブエージェントには、振る舞いに関する観察結果を報告させてください。「このファイルが存在する」「ライブラリ X を使用している」といった報告ではなく、「X が起きると Y が発生する」「X に失敗するとシステムは Y を行う」といった報告にします。
構造上の些細な事柄にファクトを費やさないでください。「プロジェクトには src/auth.rs ファイルがある」は、有用なファクトではありません。「期限切れのトークンは 401 で拒否され、レスポンスにはreasonフィールドが含まれる」は有用です。
2b. プロジェクトのオントロジーを構築する
分類や事実の記述を始める前に、プロジェクトの主要なエンティティと関係を、メインの .facts ファイルの ## domain セクションに事実として定義します。この語彙が、シート内のほかのすべての事実における正式な命名基準となります。
- サブエージェントによるスキャン結果から、コードベースの複数の領域に登場する名前付きの概念を特定します。これらがプロジェクトのエンティティです。
- 各エンティティについて、
a <Name> is <definition>のパターンで定義の事実を書きます。独自に抽象化した名前ではなく、コードベースで実際に使われている名前(構造体名、型名、ドキュメント内の用語)を使用します。 - エンティティ間の重要な関係、つまり、何が何を含むのか、何が何を生成または消費するのか、何が何を検証または変換するのかを特定します。定義したエンティティ名を使い、自然な宣言文で関係の事実を書きます。厳密な文法はありませんが、具体的なつながりを示し、エンティティ名は一貫して使用します。
- 既存のファクトシートに用語の不一致がないか確認します。同じ概念が、ある事実では「sheet」、別の事実では「fact file」と呼ばれている場合は、どちらかに統一し、不一致のある事実を編集します。
## domain セクションがすでにある場合は、更新します。不足しているエンティティを追加し、古くなったエンティティを削除し、不正確な定義を修正します。ドメインセクションはコードベースとともに進化します。
品質基準:
- コードベースの複数の領域に概念として登場するエンティティのみを定義します。概念が単一の関数内にしかなく、ほかの事実に登場しない場合は、ドメイン定義は不要です。ステップ 3~4 で動作に関する事実を書いた後、参照されていないドメインエンティティは削除します。
- 関係は、個々の動作に関する事実だけでは把握できない、エンティティ間のつながりを表します。エンティティの定義だけで関係がひとつもないドメインセクションは、組み立て方の説明がない部品リストにすぎません。エンティティを定義したら、どのようにつながっているか、何が何を生成、消費、包含、変換するのかを確認します。
- コード内で実際に使われている名前を使用します。コードベースで
FactSheetと呼ばれているなら、事実でもFactSheetを使います。プロジェクトのドキュメントでそう表記されていない限り、「Fact Sheet」に正規化しないでください。 - ドメインセクションには通常、5~15 個のエンティティと、いくつかの関係を記述します。20 個以上のエンティティを定義している場合は、ドメイン概念ではなく実装の詳細まで含めている可能性があります。複数領域にまたがる基準を満たす概念がなければ、ドメインセクションは作成しません。すべてのプロジェクトに必要なわけではありません。
ファクトを複数のファイル(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. 手動ファクトにコマンドを追加する
手動ファクトを確認し、次の点を検討します。ファクトが誤りになった場合に実際に失敗する、短いシェルコマンドで検証できますか?
後半が重要な判断基準です。コマンドを追加する前に、次のテストを行います。
誰かがコードベースを変更し、このファクトが真実でなくなった場合、コマンドは失敗しますか?
答えが「いいえ」、または「場合による」であれば、そのファクトは手動のままにします。
有効なコマンドの条件
コマンドは、関連するコードが存在するかどうかだけでなく、主張そのものを確認するときにファクトを検証できます。コマンドには次の性質が必要です。
- 反証可能 — 真実でなくなれば実際に失敗する
- 高速 — 1 秒以内に実行できる(grep、test、jq、wc、head)
- 冪等 — 読み取り専用で、副作用がない
- 安定 — 無関係な変更で失敗しない(行数の確認を避け、位置ではなくパターンを照合する)
- 成功時に無出力 — 終了コード 0 はファクトが成り立つことを示し、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
役に立たないコマンドの条件
最もよくある失敗は、キーワード検索です。ファクトのラベルから単語を選び、それがソースファイルのどこかに含まれているか確認します。これはファクトを検証するものではなく、コードベースで似た語彙が使われていることを確かめるだけです。
# 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 する、または既存のテストを実行するだけです。これらが検証するのは実装であり、動作ではありません。2 番目のグループはツールを実行し、出力を確認します。リファクタリング後も有効なのは、この方法です。
ファクトを手動のままにする場合
すべてのファクトにコマンドを付けられるわけではなく、付けるべきとも限りません。次のような場合は、ファクトを手動のままにします。
- 主観的または定性的 — 「極めてシンプル」「コードベースが DRY」「洗練された UX」
- 人間の作業プロセスに関するもの — 「バージョンを更新し、コミットして、プッシュする」
- ローカルから照会できない外部システムに関するもの
- 検証に複雑な統合テストが必要で、プロジェクトのテストスイートですでに確認されている動作に関するもの
- 設計意図に関するもの — 「各ファクトは原子的かつ独立している」「ファイルの順序は標準である」
- キーワード検索でしか確認できないもの — 実際の主張ではなくキーワードを確認するコマンドしか書けない場合は、手動のままにする
本当に検証されたファクトが 30 件あり、正直に手動扱いにしたファクトが 20 件あるファクトシートのほうが、すべて grep -q "<word>" <file> である 50 個のコマンドを備えたものより、はるかに役立ちます。
4. 不足している事実を追加する
まだ記録されていない重要な動作や機能を特定します。次の順で優先してください。
- ユーザー向けの動作 — このプロジェクトで何ができるのか。その操作をすると何が起きるのか。誤った操作をするとどうなるのか。
- コンポーネント間の契約 — モジュール同士はどのように連携するのか。それぞれがほかのモジュールに何を保証するのか。
- エッジケースとエラー処理 — 何がどのように失敗し、そのときユーザーには何が表示されるのか。
- 構造・アーキテクチャに関する事実 — 動作に制約を与える場合に限ります(例:「シングルスレッドなので、ハンドラーはブロックできない」は重要ですが、「clap クレートを使用している」はほとんど重要ではありません)。
# 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"
次のような事実を優先してください。
- 動作を示す — 何が存在するかではなく、何が起きるかを説明している
- 原子的である — 1つの事実につき、真実が1つである
- 反証可能である — この事実が成立しない壊れた実装を想定できる
- 残す価値がある — この事実だけを使ってプロジェクトを一から書き直す場合に、正しい動作を実現するうえで役立つ
事実を追加する前に、既存のシートを検索して内容を確認してください。すでに既存の事実でその動作が説明されている場合は、似た内容を重複して追加するのではなく、その事実を明確にしてください。複数の既存の事実が、同じエンドポイントや機能を細かく(エラーコードごと、分岐ごとなど)説明している場合は、その機能の契約を説明する1つの事実にまとめてください。適切に整理されたシートは、事実を追加するのと同じくらい頻繁に統合・削除することで、有用性を保てます。
正確な動作に関する事実が40件あるシートのほうが、構造に関する観察が200件あるシートよりも役に立ちます。1つのファイルで事実が約80件を超えて増える場合は、対象を絞ったファイル(api.facts、cli.facts)に分割し、構造だけの不要な情報を整理してください。
新しい事実を書くときは、## domain セクションにあるエンティティ名を使ってください。シート全体で用語を統一すると、エージェントが正確なメンタルモデルを構築しやすくなります。## domain にない用語を使う場合は、先にその用語を追加するか、既存の用語を使ってください。
5. 整理する
--section を使って、関連する事実をセクションごとにグループ化します。セクションのパスは階層化できます(例:api/auth、cli/subcommands)。各セクションの対象を絞り、広範なものは分割してください。
6. 検証して報告する
facts check を実行し、すべてのコマンドに関する事実が要件を満たすことを確認します(ファイルのリントも実行されます)。
変更内容を報告してください。追加・編集・削除した事実と、追加したコマンドを記載します。コードベースの一部に曖昧な箇所があった場合や、十分に記録できなかった場合は、その旨も伝えてください。
ガイドライン
- 事実ラベルは簡潔で断定的な表現にします。
- コマンドの品質は、コマンドの数より重要です。 事実を実際に検証できないコマンドは、コマンドを追加しないよりも悪影響があります。誤った安心感を与えるためです。事実が誤りになったときに失敗するコマンドだけを追加してください。
- チェックコマンドを書くときは、
grep -q、test -f、test -d、jq -eなど、高速で読み取り専用のチェックを優先してください。事実の内容が「プロジェクトが正常にビルドできる」などの場合を除き、ビルド、インストール、変更を行うコマンドは避けてください。 - 役立つ場合は、タグを使って分類します(例:
@ci、@api、@core)。タグを段階的に変更するには、--add-tagと--remove-tagを使います。 - 事実が残っていないセクションは、CLI によって自動的に整理されます。
- ライフサイクルの分類が最優先の作業です。 すべての事実に、適切なライフサイクルタグ(
@draft、@spec、@implemented)を付けるか、タグなし(実際の事実)にしてください。@draftや@specの事実は削除せず、分類してください。実現したい内容だからといって削除しないでください。 - コードベースから見つけた新しい事実を追加する場合は、タグを付けないでください。観察によってすでに真実だと確認できているためです。
- 構造上の事実より、動作上の事実を優先します。 追加する事実を選ぶときは、「このプロジェクトを再実装するエージェントが、動作を正しく再現するために知る必要があるか?」と考えてください。ファイルの有無や依存関係の名前はマニフェストに記載されています。重複排除のロジック、エラーレスポンス、再試行のセマンティクス、エッジケースは、書き直す際にひっそりと抜け落ちやすい情報です。記載する価値があるのは、こうした事実です。
- 事実を書く前に、用語を定めてください。
## 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