ハーネスドキュメントモデル リファレンス
ハーネスドキュメントの判断基準を集約した内部リファレンス。「何を書くか」「どこに置くか」「どう書くか」の共通知識を提供する。
ハーネスドキュメントの定義
ハーネスドキュメントとは、リポジトリに置かれ、人間かエージェントが著者として書き、読んだ内容がエージェントの行動の根拠になる文書類。これら3条件すべてを満たすものを指す。拡張子や置き場所で決まるのではないので、下の表は判定の例であって列挙ではない。
| 判定 | 例 | 理由 |
|---|---|---|
| 対象 | CLAUDE.md / AGENTS.md |
起動時に読まれる指示とインデックス |
| 対象 | .claude/rules/** |
行動を縛る指示 |
| 対象 | docs/**(.md 以外の .mdx / .txt / .rst / .adoc も) |
設計背景と判断の理由 |
| 対象 | feature README.md |
機能単位の全体像 |
| 対象 | コード内のコメント | コードと同じ場所に置かれた「なぜこうなっているか」 |
| 対象外 | コード本体・設定値 | 文章ではない。事実はコードが正本 |
| 対象外 | 生成物(CHANGELOG、ロックファイル、スナップショット) | 著者が書いたものではなく、履歴やツールの出力 |
| 対象外 | 取り込んだ第三者の文章(vendor、ライセンス、引用) | 自分の規約を適用する対象ではない |
| 対象外 | テストの入力データ、fixture 内の文字列 | 検証の素材であり、行動の根拠ではない |
| 対象外 | auto memory(~/.claude/projects/<project>/memory/ 等) |
リポジトリの外にあり、ユーザー固有で共有されない |
扱いを分けるもの
3条件を満たすかどうかとは別に、扱いが他と違うため独立させるものが2つある。
- コード内のコメント: 対象だが、ソースに散在するため走査の費用が桁で違い、是正がソースファイルの変更になる。
1natsu-document-harness-auditでは独立したステップで検査する - auto memory: 対象外だが、恒久的なプロジェクト知識が紛れ込むため検査はする。削除が git で戻せないので、同じく独立したステップで扱う。設計判断や規約をメモリに依存させず、ハーネスドキュメントへ明示的に書くのが原則
3層モデル
ハーネスドキュメントは役割に応じて3つの層に分かれる。各層には適切な配置先があり、混在させない。層を混ぜると、エージェントが「ルール」と「背景説明」を区別できず、指示の優先度を誤判断するリスクがある。
| 層 | 役割 | 配置先 | 例 |
|---|---|---|---|
| 指示 | 「こうしろ / するな」 | .claude/rules/ |
コーディング規約、データ整合性ルール |
| インデックス | 「何が重要か / どこを読め」 | CLAUDE.md |
パッケージ概要、重要な前提、詳細へのポインタ |
| 知識 | 「なぜこうなっているか」 | docs/ / feature README.md |
設計背景、データフロー、依存関係、状態遷移 |
3層に加え、手順的・反復的なタスク知識(特定作業のワークフロー、繰り返す操作手順)は、常時ロードの CLAUDE.md/rules ではなく skill(必要時に on-demand ロード)へ置く。常時コンテキストを消費させず、関連する時だけ呼び出せる。
コード内のコメントは知識層に属する。 コードと同じ場所に置かれた「なぜこうなっているか」であり、書く基準は docs/ と変わらない。ただし置き場所として docs/ と競合するわけではない。実装の近傍でしか意味をなさない説明はコメントに置き、複数のファイルやレイヤーにまたがる背景は docs/ に置く。
3層分離の例
認証モジュールのトークン有効期限を30分→15分に変更した場合:
- 指示 (
.claude/rules/auth.md): 「トークン有効期限は15分とする。変更する場合はセキュリティチームの承認が必要」 - インデックス (
CLAUDE.md): 「認証仕様の詳細はdocs/auth-design.mdを参照」(プレーンポインタ=必要時にロード) - 知識 (
docs/auth-design.md): 「有効期限を30分から15分に短縮した。理由: セキュリティ監査でセッションハイジャックのリスク軽減を指摘されたため」
書くべきもの / 書くべきでないもの
ドキュメントの価値は「コードだけでは得られない情報」にある。コードから読み取れることを繰り返すと、コードとドキュメントの乖離が必ず起き、エージェントがどちらを信頼すべきか判断できなくなる。
書く:
- コードから読み取れない設計判断とその理由
- 暗黙の依存関係(バッチ処理、外部サービス、ワーカー等)
- 複数レイヤーにまたがるデータフロー
- 非自明な制約や前提条件
- エッジケースや落とし穴
書かない:
- コードから直接読み取れること(関数のシグネチャ、型定義の列挙等)
- 実装の偶有(採用中のライブラリ名・内部変数名・内部関数名など「今たまたまそうなっているだけ」の事実)。仕様ではない。判断だったなら理由として書く(→「実装の偶有を仕様に蒸留する」)
- git history から得られること(誰がいつ変更したか)
- 標準的な言語・フレームワークの慣例
- 自明な事実(「このファイルは X を export している」等)
実装の偶有を仕様に蒸留する
ドキュメントが捉えるべきは 契約・不変条件・設計判断とその理由 であって、その時点の実装が偶然そうなっている事実 ではない。コードは変わるが契約は残る。実装の偶有を仕様として転記すると、リファクタのたびにドキュメントが嘘になり、エージェントはコードとドキュメントのどちらを信じればいいか分からなくなる。
書く対象を3種に分けて考える:
| 種別 | 何か | 扱い |
|---|---|---|
| 契約・ポリシー | 実装が変わっても保たれるべき約束・不変条件(「クライアント単位で制限する」「トークンは15分で失効」「レスポンスは snake_case」) | 仕様として書く。ルールの指示・docs の骨格はこれ |
| 実装の偶有 | 今たまたまそうなっているだけの事実(内部変数名・内部関数名・採用中のライブラリ・ファイル分割) | 仕様として転記しない。コードから読めるものは省く。判断だったなら理由として書く |
| 外部契約面の名前 | 名前そのものが契約になっている識別子(利用者が設定する環境変数名・公開 API ルート・設定キー・公開型) | 名指ししてよい。利用者が知らないと使えないため |
swap test(リトマス試験)
ある記述が仕様か偶有かは、こう問えば判定できる:
「実装を別の妥当な方法に書き換えたら、この記述は嘘になるか?」 嘘になるなら、それは仕様ではなく実装の偶有を書いている。
- 「クライアント単位でレート制限する」→ Redis でも LRU でも真。仕様。 書く。
- 「Redis でレート制限する」→ LRU に変えたら嘘。偶有。 ルールには書かない。
- 「当初 Redis にしたがレイテンシで LRU へ変更した」→ 過去に下した判断を述べる rationale。swap test の対象外。docs に書く。
蒸留オペレーション
セッションの変更・プラン・コードをそのまま転記しない。各情報を次のいずれかに振り分けてから配置する:
- omit: コードから直接読めること(シグネチャ、型の列挙、自明な事実)は書かない
- rationale 化: 「なぜその選択をしたか/捨てた選択肢/トレードオフ/前提」は理由として
docs/に書く。機構名(Redis/LRU 等)はここで初めて、判断の対象として登場してよい - name: 外部契約面の名前(env var 名・公開ルート等)はそのまま名指しする
ルールのレベリング
.claude/rules/ は指示・ポリシー層。機構名をルールに書いてよいのは「今後もそれを使え」という義務(mandated policy)の時だけで、「今そうなっている」という現状報告として書いてはいけない。
- ✅ 「認証は社内 SSO を使う。独自実装は禁止」→ 機構の使用そのものが義務。ルールに書く
- ❌ 「レート制限は Redis を使う」→ 現状そうなっているだけ。ルールではなく docs の rationale へ。ルールは「クライアント単位で制限する」というポリシーに徹する
機構をルールに固定すると、実装を差し替えた時にルール(指示)が嘘をつき、エージェントが古い機構を強制される。
蒸留の例(レート制限を Redis → インメモリ LRU に乗り換え)
| 転記(精度が低い) | 蒸留(仕様として正しい) | |
|---|---|---|
| rule | 「Redis を使ってレートリミットする。rateLimit.ts で lru-cache を…」 |
「API はクライアント(IP)単位でレート制限する。上限・ウィンドウは環境変数で調整可能」 |
| docs | rateLimit.ts が lru-cache を呼ぶ手順を列挙 |
「当初は共有ストアに分散カウンタを置く設計だったが、レイテンシ要件を満たせずプロセス内キャッシュへ変更。複数インスタンス構成では要再検討」 |
転記版は機構を差し替えると全文が嘘になる。蒸留版はルールが無改訂で済み、docs は「判断と理由」として将来も生き残る。
用語と文体
ハーネスドキュメントは人間とエージェントの両方が読み、読んだ内容で行動が決まる。読み手が語の指す実体を復元できなければ、書いてある通りには動けない。ここでは言語によらない3つの原則と、日本語で書く場合の表記規約を定める。生成(harness)と検出(audit)で同じ基準を使う。
適用対象は、リポジトリに書く日本語の文章全般。ハーネスドキュメント(.md に限らず .mdx / .txt / .rst / .adoc 等も含む)だけでなく、コード内のコメントも対象になる。読み手が実体を復元できないという問題は、書かれた場所では変わらない。
実体を名指しする
英語の技術メタファー(gate / floor / home / anchor 等)を比喩のまま訳語へ移さず、指している実体を名指しする。
- 「XX のゲート」→「XX の通過条件」
- 「底であって保証ではない」→「下限値であって保証ではない」
比喩は書き手の頭の中では一意でも、読み手には複数の解釈が立つ。同じ理由で、そのリポジトリの外で通じない造語を発明しない。既に定着した語があるならそれを使う。
名指しできないなら、その概念をまだ理解できていない。比喩で埋めて先へ進まず、「ここは実体を確定できていない」と明示して確認を取る。曖昧なまま書かれた1行は、後続のエージェントが自分の解釈で実装する根拠になる。
既存の比喩を書き直すときは、比喩を消した後の文だけを読んで、元の文が求めていた行動を復元できるかで判定する。復元できるなら比喩は冗長なので落とす。復元できないなら、その比喩は情報を担っていたので確定できていない。確認へ回す。
文が成立するかで判定しない。 比喩を削っただけの文はたいてい日本語として成立し、意味も通る。それでも読み手が何をすればよいか決められなくなっていれば、情報は落ちている。復元できるかを問うのは、成立と情報の保存を取り違えないため。原文にない語を補って埋めるのも同じ理由で行わない。
- 「ステージングは本番のアンカーとして扱い、差分がある状態でリリースしない」→ 消しても「ステージングと本番に差分がある状態でリリースしない」で求める行動が復元できる。1文にまとめる
- 「リリースはステージングのゲートを通してから本番へ出す」→ 消すと「ステージングを通してから本番へ出す」。文は成立するが、何を満たせば通したことになるかを読み手が決められない。確認へ回す
- 「ジョブは投入されると『キューのホーム』に置かれ、ワーカーが順に引き取る」→ 消すと置き場所を指す語が無くなり、補わなければ文にならない。確認へ回す
同じ概念に複数の語を流通させない。ドキュメント間で語が割れると、読み手には別物を指しているのか同義なのか判断できない。書く前に既出の語を確認し、割れているなら片方へ統一する(x-retry-count のような外部契約面の識別子は、本文の用語を統一しても変えない)。
規範に作業記録を混ぜない
規範(rules の指示)と決定(docs の判断)には、今も有効な内容とその根拠だけを置く。経緯と日付は「削っても規範の妥当性を判断できるか」で決める。
- 判断できる(いつ・誰の依頼で足したか、何度直したか)→ 作業記録。git history の役割なので落とす
- 判断できない(一般に B を選ぶ場面で A を選んだ理由、捨てた選択肢とその棄却理由)→ 根拠。残す
これは「実装の偶有を仕様に蒸留する」の時間軸版にあたる。偶有が「今たまたまこう実装されている」なら、こちらは「今たまたまこういう経緯で足された」。どちらも残すとドキュメントが履歴の堆積になり、現行の規範がどれなのか読み取れなくなる。
ナラティブにしない
出来事を時系列で語る形にしない。ドキュメントの読み手は先頭から順に読むとは限らず、必要な節だけを拾う。時系列の文脈に依存した書き方は、その節だけ読んだときに意味が通らない。
例外は、コンテキストを持たない第三者が履歴を線形に追う必要がある文章(インシデントレポート、調査・ログの報告)。この場合も置き先は docs/ で、指示層には持ち込まない。
日本語で書く場合の表記
対象ドキュメントが日本語のときに適用する(英語で書かれたドキュメントには適用しない)。
この規約を根拠に、英語の文章を日本語へ書き換えない。 英語の文に混ざったダッシュはそもそも違反ではないので、規約の是正としては触らない。言語を揃えるかどうかは文体の一貫性の問題で、別の判断として扱う。揃えると決めた場合は、規約の是正と混ぜず、言語を変えたことを報告に書く。読み手が「規約に従っただけ」と受け取ると、言語の選択が誰の判断だったのか追えなくなる。
- 技術用語は原語のまま: pull request、feature flag、dry run、SSoT のように「英単語のままで意味が通り、実務でその形で使われている」語は訳さない。直訳すると実務で使われない不自然な語になり、検索でも引けなくなる。定着したカタカナ・略語も同じ扱い。これは「実体を名指しする」と衝突しない。分野で定着した術語(jitter、backoff、idempotent 等)はそれ自体が実体の名前なので、説明的な日本語へ言い換えず原語で書く。言い換えると、読み手はその語で書かれた他の資料と結びつけられなくなる
- ダッシュを使わない:
—(em dash)–(en dash)―(horizontal bar)─(box drawing) とその連結を日本語の文章に使わない。英語の用法を日本語へ移すと、接続助詞や読点で書ける関係が記号に吸収されて辿れなくなる。理由・帰結・逆接は接続詞と読点、ラベルと値は:、長い補足は文を分ける。例外は表の空セルのみ。ハイフン-、長音符ー、数値範囲(18–21)は対象外 - 不自然な文を残さない: 日本語ネイティブの技術ライターが書く語と文にする。冗長な言い換え、意味の薄い形容、同じ内容の繰り返しは削る
既存ドキュメントとの関係
周囲のドキュメントに合わせるのは構成・粒度・詳細度まで。用語と表記の規約は、既存ドキュメントが違反していてもそれに合わせない。 合わせると違反が複製され、次の書き手にとっては「このリポジトリの文体」に見えてしまう。
ただし是正の範囲は分ける。生成・更新(harness)で直すのは今回自分が書く記述と、そのために触る既存記述まで。リポジトリ全体の一斉是正は検査(audit)の責務であり、harness のスコープを広げない。
配置先ごとのルール
CLAUDE.md
- 200行以下に収める。詳細は別ファイルに出してポインタで指し示す
- 「何が重要か」「どこを読めばいいか」だけを伝えるインデックスに徹する
CLAUDE.md はエージェントのセッション開始時(cwd +その祖先ディレクトリ)に常にコンテキストへ読み込まれる。肥大化するとトークンを浪費し、本当に重要な指示が埋もれる。
外出しの手段でロード挙動が変わる点に注意する。@path インポートは起動時にフルロードされ、コンテキストは節約できない(ファイル整理にはなるが遅延ロードではない)。実際にコンテキストを遅延させたいなら、@ ではなく プレーンなポインタ(「詳細は docs/auth-design.md を参照」と書き、Claude が必要時に Read する)、paths スコープ rule、または skill を使う。@ は「全セッションで必ず読ませたい × 別ファイルに分けたい(共有・再利用)」が両立する少数のケースに限る。なお CLAUDE.md 本文で @path を説明として言及するだけならバックティックで囲む(囲まないと import が発火する。理由と例は placement-guide 参照)。
.claude/rules/
- 1ファイル = 1トピック
- 「こうしろ / するな」の行動指示に徹し、理由(why)を簡潔に添える
- 知識や設計背景は書かない(それは docs/ の役割)
paths frontmatter の有無でロードモードが決まる(実測確認)。なし = 起動時に常時ロード(.claude/CLAUDE.md と同格・同コスト。subtree の rule は配下ファイルを Read した時に on-demand)。あり = マッチしたファイルを触った時だけの条件ロード(コンテキスト節約の主手段。glob は rule ファイルのあるディレクトリ基準で解決=monorepo の注意点参照)。paths なしは常時載るので、複数トピックを混ぜず1ファイル1トピックを守る。手段別のロードタイミング早見表は placement-guide を参照。
docs/
- 人間も読むドキュメントとして書く(Claude 専用にしない)
- 「なぜこうなっているか」の設計背景を中心に
- ファイル名は内容を表す具体的な名前にする(同階層に複数ある場合)
feature README.md
- その機能の全体像を1ファイルで把握できるようにする
- レイヤー間の依存関係、非自明な設計判断とその理由、暗黙の前提(バッチ処理、外部ワーカー等)を記載する
機能ディレクトリに README がないと、エージェントはコードを読んで構造を推測するしかなく、暗黙の依存関係を見落とす。
コンテキスト経済: eager / lazy と意図の明示
ハーネスドキュメントは「いつコンテキストに載るか」で eager(起動時に常時ロード)と lazy(必要時だけロード)に分かれる。LLM のコンテキストは有限なので、既定は lazy(節約)に倒し、eager は理由を明記した例外として扱う。lazy は paths スコープ rule / docs/ へのプレーンポインタ / skill、eager は CLAUDE.md 本体 / paths なし rule / @-import の3つだけ(手段別のロードタイミングは placement-guide の早見表)。
lazy 側は仕組み自体が「いつ載るか」を宣言している(paths が条件、ポインタ表現が遅延を示す)ため追加説明は要らない。一方 eager 側は「paths の不在」「@ の存在」という記号でしか表れず理由が消える。理由がないと、後続の編集者(特に AI)が文脈なしに paths を付け外ししたり @ を足し引きして、ロード挙動を意図せず反転させてしまう。
そこで eager にする時は、その場に意図を1行マーカーで残す。要点は手間を非対称にすることで、既定の lazy は追加作業ゼロ、コンテキストを消費する eager にだけ理由の明記を求める。HTML コメントはコンテキスト注入前に strip され(CLAUDE.md・rules ともに実測で確認)、編集時の Read では見えるので、実行時トークン0で編集者にだけ意図を伝えられる。
paths なし rule(意図的に global):
<!-- scope:global (eager): `paths:` の不在は意図的。
理由: 全ファイルに適用される規約のため、paths で絞ると編集対象を取りこぼす。
`paths:` を足さないこと。 -->
# コミットメッセージ規約
CLAUDE.md の @-import(plain でなく @ を選ぶ理由):
<!-- eager-import: `@` は起動時フルロード(節約にはならない)。
plain ポインタでなく `@` にする理由: AGENTS.md を他ツールと共有し全セッション必読のため。 -->
@AGENTS.md
逆に、マーカーのある global rule に paths を足したり、プレーンポインタを @ に変える時は、必ずそのマーカーの理由を読み直してから行う。
monorepo での注意点
- パッケージ固有のコンテキスト → パッケージ直下の
CLAUDE.md(パッケージから起動すれば起動時 eager、ルートから起動すれば配下ファイルを Read した時に on-demand) - パッケージ固有の指示は パッケージ内
.claude/rules/にも置ける(ネストした rules も正式にロードされる。実測で確認) - 横断的な規約はルートの
.claude/rules/に配置する - パッケージの
CLAUDE.mdもインデックスに徹し、詳細はパッケージ内のdocs/に分離する
パッケージ固有の情報をルートに集めると、無関係なパッケージの作業時にもコンテキストに読み込まれてしまう。
paths の glob は rule ファイルのあるディレクトリ基準で解決される(repo root 基準ではない)。パッケージ内 rule は paths をパッケージ相対で短く書き(src/**)、ルート rule は repo root 基準で書く。例・全手段のロードタイミング・settings.json の継承挙動は placement-guide を参照。
docs/ の配置原則
CLAUDE.md と .claude/rules/ は Claude の仕様でパスが決まっているが、docs/ はユーザー定義のディレクトリであり、配置に仕様上の制約はない。そのため monorepo では locality の原則に従い、関心のスコープで配置先を分ける:
| スコープ | 配置先 | 例 |
|---|---|---|
| リポジトリ全体に関する知識 | ルート docs/ |
CI/CD 設計、monorepo 全体のアーキテクチャ、横断的な ADR |
| 特定パッケージに閉じた知識 | packages/xxx/docs/ |
パッケージ固有の設計背景、データフロー、状態遷移 |
パッケージの CLAUDE.md からパッケージ内の docs/... をプレーンポインタで指せば、関心がパッケージ内で完結する(背景知識なので @ ではなくプレーンポインタ=必要時 Read)。パッケージの関心をルート docs/ に書くと、どのパッケージの知識なのか曖昧になり、パッケージを削除・移動した時にドキュメントが取り残される。
配置判断リファレンス
配置判断のフローチャート、早見表、迷った時の判断基準は references/placement-guide.md を参照。
生成・更新スキル / 監査スキルの責務分担
このモデルを参照する2つのスキルは、対象とする「ドキュメントの状態」で責務が分かれる。
1natsu-document-harness(生成・更新)
現セッションで自分が触った変更を、ハーネスドキュメントに反映する。2つのモードを持つ:
- init モード: 該当変更について、まだドキュメント化していない状態からの生成
- sync モード: 同一セッション内で既に harness を実行済みの状態で、追加修正・実装乗り換え・バグ修正が発生したときの追従更新
sync モードでは「前回出力した自分のドキュメント」を再読し、書いた当時の前提と今の実装の差分を洗い出す。古い前提(捨てた A 案、修正前の振る舞い等)の記述を残さないことが要点で、書き換え・削除を能動的に行う。スコープは現セッションで触った変更のみに限定し、無関係な既存ドキュメントには手を入れない。
ドリフトを直す時は単純置換でなく再蒸留する。機構名を差し替えるだけだと、ルールに機構名が載っていた抽象度の誤りを温存してしまう。置換後の値は推測せずコードで裏取りする。やり方は「実装の偶有を仕様に蒸留する」を参照。
1natsu-document-harness-audit(検査)
セッションをまたいで存在している既存ドキュメントを対象に、問題を検出して改善提案を行う。検査軸は重複・不整合・陳腐化・参照切れ・欠落・粒度逸脱・参照不足・コンテキスト経済・実装結合(蒸留不足)・用語と文体の規約違反の10カテゴリ。加えてメモリ監査として、エージェントの永続メモリ(auto memory 等)を「リポジトリのドキュメントが正本」の原則で検査し、メモリ限定の恒久知識の蒸留・メモリとドキュメントの不整合や重複・陳腐化メモリの削除を提案する(蒸留先の判断は本モデルの3層モデル・swap test に従う)。
陳腐化は2種類を扱う:
- コード乖離: ドキュメント記述とコードの実態の乖離。修正する時は置換後の値を必ずコードで裏取りする(推測しない。挙動依存の事実は実測か要検証フラグ)
- 表現の陳腐化: 過去のモデル前提で書かれた冗長な MUST / NEVER の連発、過剰な前置き等を、今のモデルに最適化された短く理由ベースの表現に書き直す
**実装結合(蒸留不足)**は陳腐化の手前の問題を扱う。記述が「今は正しい」が実装の偶有(機構名・内部変数名・コード転記)を仕様として書いているため、コードが変わった瞬間に嘘になるドリフト予備軍を検出する。判定は swap test、是正は再蒸留(機構をルールから docs の rationale へ移す等)。
用語と文体の規約違反は「用語と文体」の基準を既存ドキュメント全件に当てる。生成側(harness)が守る規約と同じものを検出側で使うため、片方だけが緩むことがない。
audit の対象範囲は「現セッションで自分が触っていない既存ドキュメント」。現セッション内のドリフト追従は harness の sync モードで扱い、audit には持ち込まない(ユーザーが誤って audit を呼んだ場合は harness sync へリダイレクトを提案する)。