TL;DR(このノードは何をする・専門語ゼロ): 新しい決定の下書きを、これまでに残した過去の決定すべてと照らし合わせる関門。過去の決定とぶつかっていないか、それとも古い決定を入れ替えようとしているのかを見分ける。古い決定を入れ替えるなら、下書きに「この決定で置き換えます」という宣言が書かれていることを必ず確かめ、宣言がなければ書くよう促して差し戻す。ぶつかりが見つかった場合も差し戻し、問題なければ次の関門へ進める。

実装: drp/src/nodes/consistency.ts プロンプト設計ドキュメント: prompts/04_consistency.md プロンプト SSoT: drp/prompts/production/gate2-consistency/prompt.md テスト: drp/test-tc-consistency.mjs (TC-C01〜C05) 基盤 ADR: ADR-0023 (4 桁形式) / ADR-0033 (モデル選定)


1. 役割と位置づけ

新規 ADR ドラフトを 既存 ADR 全件 と照合し、CONFLICT〔CONFLICT=過去決定と矛盾〕 / SUPERSEDE〔SUPERSEDE=旧 ADR を新 ADR で置き換える〕 / INFO〔INFO=矛盾はないが参照すべき関係〕 / PASS〔PASS=整合性問題なし〕 を判定する整合性ゲート〔Consistency=既存決定との整合性照合〕。CONFLICT 検出、または SUPERSEDE 判定で本文に Supersedes 宣言なしの場合は END で差戻す。

  • 解決する課題: 過去 ADR を読まずに似た決定を上書き / 矛盾する提案を出されると、ADR ストア全体の信頼性が損なわれる。LLM に過去 ADR の先頭 40 行ずつを読ませて関係性を機械判定する。
  • 設計思想: 差戻し型 + 上書き宣言強制ゲート。LLM が SUPERSEDE と判定しても、本文に Supersedes: ADR-NNNN が明記されていなければ「未宣言の上書き」として CONFLICT 相当に扱う (起案者の明示的な意図確認)。
  • adr-kit との棲み分け: Pipeline 側 = 起案時の門番 (PR 化前にブロック) / adr-kit (/adr-kit:lint) = オーナーレビュー時の補強 (Accepted 直前の最終チェック)。

2. フロー図

flowchart LR
    SC[scoring] -->|rejected=false| C[consistency]
    C -->|verdict=PASS/INFO
or SUPERSEDE+宣言あり| PR[parallel_review] C -->|verdict=CONFLICT| END1([END
矛盾を起案者に返却]) C -->|verdict=SUPERSEDE
宣言なし| END2([END
Supersedes 宣言を促す])

3. トリガー条件

条件挙動
scoring から rejected=false実行
scoring から rejected=trueスキップ (scoring が END に飛ばす)

4. 入力 (State)

State フィールド用途
adrBody新規 ADR ドラフト (Supersedes 宣言の正規表現マッチに使用)

外部入力 (GitHub):

  • docs/adr/ 配下の .md ファイル全件 (先頭 40 行ずつ抜粋) を GitHub Contents API + GraphQL で取得

5. 処理ロジック

1. fetchAdrSummaries(env):
   a. GET /repos/{owner}/{repo}/contents/docs/adr → ファイル一覧
   b. /^(\d{4}-|\d{3}_).*\.md$/ で ADR を絞り込み (README.md / _template.md 除外)
   c. GraphQL で全 ADR ファイルの内容を 1 リクエストで一括取得 (aliases: f0, f1, ...)
   d. 各ファイルの先頭 40 行を "=== filename ===\n{excerpt}" 形式で結合
2. loadLlmParams(env, 'gate2-consistency', { temperature: 0.4, seed: 42 }) → createLlm(env, MODELS.consistency='claude-opus', params.temperature)  // 実効値は KV 登録値が優先
3. user msg: "## 新規 ADR ドラフト\n{adrBody}\n\n## 既存 ADR 一覧(先頭 40 行抜粋)\n{summaries}"
4. invoke → JSON 抽出 → ConsistencyResult
5. verdict 分岐:
   - CONFLICT → rejected=true, rejectionMsg に summary_md + rejection_hint
   - SUPERSEDE → adrBody を /supersedes?.*ADR-\d{3,4}/i で検査
     - 宣言あり → 通過 (verdict=SUPERSEDE で state 記録)
     - 宣言なし → rejected=true, 「`Supersedes: ADR-NNNN` を明記してください」を返却
   - INFO / PASS → 通過 (verdict / detail のみ state 記録)

判定区分:

区分定義
CONFLICT決定の方向性が逆向き / 前提制約を破壊 / 既存採択済み制限を無視
SUPERSEDE既存 ADR の核心決定を置き換える (本文に Supersedes 宣言必須)
INFO直接の矛盾・上書きはないが参照・考慮すべき関係あり
PASS整合性問題なし

verdict は最重大を選ぶ (CONFLICT > SUPERSEDE > INFO > PASS)。


6. LLM 設定

項目根拠
モデルclaude-opus (claude-opus-4-7)50+ ADR の文脈を保持しつつ関係性判定する長文理解力
temperatureKV gate2-consistency params (fallback 0.4)判定の揺らぎ抑制。実効値は KV 登録値が優先 (loadLlmParams)
入力サイズ50 ADR × 40 行 ≈ 60〜120 KBclaude-opus の context window で十分カバー
コスト目安0.20〜0.50 USD / 起案入力サイズに依存 (ADR 数が増えると比例)
レイテンシ15〜40 秒

7. 副作用

  • GET /repos/{owner}/{repo}/contents/docs/adr (REST) — ファイル一覧
  • POST /graphql (GitHub GraphQL) — 全 ADR 本文一括取得 (N+1 回避のため REST 個別取得は使わない)
  • いずれも env.GITHUB_PAT で認証 / User-Agent: decision-pipeline/1.0
  • 読み取り専用。書き込みはしない

8. 出力 (State)

PASS / INFO 通過

{
  consistencyVerdict: 'PASS' | 'INFO',
  consistencyDetail: string,  // summary_md (PR 本文転記用)
}

SUPERSEDE 通過 (宣言あり)

{
  consistencyVerdict: 'SUPERSEDE',
  consistencyDetail: string,
}

CONFLICT 差戻し

{
  consistencyVerdict: 'CONFLICT',
  consistencyDetail: string,
  rejected: true,
  rejectionMsg: '**[Gate 2: 整合性チェック] CONFLICT** — 既存 ADR との矛盾...\n{summary_md}\n\n{rejection_hint}',
}

SUPERSEDE 差戻し (宣言なし)

{
  consistencyVerdict: 'SUPERSEDE',
  consistencyDetail: string,
  rejected: true,
  rejectionMsg: '**[Gate 2: 整合性チェック] SUPERSEDE 宣言が必要です。**\n...\n対象 ADR を上書きする場合、ADR 本文のメタデータに `Supersedes: ADR-NNNN` を明記してください。',
}

consistencyDetailwebhook ノードで PR 本文の「## Gate 2: 整合性チェック」セクションに転記される (verdict が PASS 以外の場合のみ)。


9. 分岐 (次ノード)

.addConditionalEdges('consistency', (s) => s.rejected ? END : 'parallel_review')
rejected次ノード
falseparallel_review (Gate 3)
trueEND

10. エラー時の挙動

  • GitHub API 失敗: fetchAdrSummaries 内で例外スロー (GitHub list docs/adr: 503 等) → グラフ全体失敗
  • GraphQL 部分失敗: ファイル個別に取得不可なら (取得不可) 文字列で埋める → 整合性判定は続行 (要注意: 該当 ADR との矛盾を見逃す可能性)
  • JSON parse 失敗: safeParseLlmJson<ConsistencyResult>()verdict: 'PASS' フォールバックを返し通過する (例外を投げない / ADR-0081・PR #1087)。LLM 呼出自体の失敗は例外スロー。
  • Supersedes 正規表現: ADR-0023 で 4 桁形式 (例: ADR-0017) に統一済。旧 3 桁形式 (ADR-017) も互換マッチ

11. 既知の弱点・運用注意

  • 40 行抜粋の限界: ADR が長くなると後半の決定本文を読まずに判定する。Status / Mode / 冒頭の決定要旨が 40 行以内に収まる前提。長文 ADR で誤判定リスクあり。
  • CONFLICT 過剰検出: 「軽微な方針の違い」も CONFLICT 判定されることがあり、起案者が困惑するケース → プロンプトで「軽微な違いは INFO」と明示しているが LLM 判断に揺れあり。
  • SUPERSEDE 宣言の正規表現: /supersedes?.*ADR-\d{3,4}/i は単純パターン。「Supersedes ADR-0017 and ADR-0018」のような複数列挙でも 1 マッチでパス → 全件超書きしないと CONFLICT 残存リスクあり (実用上は許容)。
  • adr-kit との二重チェック: Pipeline 通過後に adr-kit /adr-kit:lint Consistency ゲートが file:line citations で再検証する設計。多層防御。
  • ADR 増加によるコスト膨張: ADR が 100 件超えると入力 200KB 超 → コスト・レイテンシ増。将来は「関連 ADR を retriever で絞り込む」最適化余地。

12. テストケース

ID内容期待
TC-C01列番号ハードコード推奨 (ADR-011 と矛盾)verdict=CONFLICT, rejected=true
TC-C02OCR を Claude へ移行 + Supersedes: ADR-007 明記verdict=SUPERSEDE, rejected=false
TC-C03Supersede したいが宣言なしverdict=SUPERSEDE, rejected=true
TC-C04既存 ADR 参照のみ (整合)verdict=PASS, rejected=false
TC-C05軽微な方針差異verdict=INFO, rejected=false

実行: node drp/test-tc-consistency.mjs 注意: テストは gpt-4o 直接呼出 + ローカル docs/adr/ ファイル参照 (GraphQL を使わない)。本番 (claude-opus + GitHub API) とは厳密一致しない。


13. 過去の設計判断ログ

日時変更経緯
2026-05-12Phase 2c 実装 (PR #589)グラフに scoring → consistency → parallel_review を追加
(実装時)REST 個別 fetch → GraphQL 一括取得N+1 リクエスト回避 (Workers の subrequest 上限対策)
ADR-0023 採択3 桁→4 桁形式統一正規表現を \d{3,4} 両対応に
ADR-0033 採択claude-sonnet → claude-opus多数 ADR の関係性判定の精度向上
(設計時)Supersede 宣言強制ロジック導入LLM 単独判定だと「上書き意図の確認」が曖昧になるため起案者の明示宣言を要求

14. 関連リンク

実装更新 (2026-05-28)

  • ADR 一覧キャッシュ (ADR-0083 / PR #1089): fetchAdrSummaries の先頭で DRAFTS_KV._cache:adr-summaries を確認。cache hit 時は GitHub REST + GraphQL をスキップ。TTL=3600s。手動無効化手順は operator_guide_langgraph.md §7.6
  • JSON parse 堅牢化 (ADR-0081 / PR #1087): consistency の JSON.parsesafeParseLlmJson<ConsistencyResult>() に置換
  • SYSTEM_PROMPTFALLBACK_PROMPT にリネーム + loadPrompt(env, 'gate2-consistency', FALLBACK_PROMPT) に切替 (PR #1090)
  • ChatOpenAI インスタンスに maxRetries=3 (exponential backoff) を付与