06. Consistency Node (Gate 2)
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 の文脈を保持しつつ関係性判定する長文理解力 |
| temperature | KV gate2-consistency params (fallback 0.4) | 判定の揺らぎ抑制。実効値は KV 登録値が優先 (loadLlmParams) |
| 入力サイズ | 50 ADR × 40 行 ≈ 60〜120 KB | claude-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` を明記してください。',
}
consistencyDetail は webhook ノードで PR 本文の「## Gate 2: 整合性チェック」セクションに転記される (verdict が PASS 以外の場合のみ)。
9. 分岐 (次ノード)
.addConditionalEdges('consistency', (s) => s.rejected ? END : 'parallel_review')
rejected | 次ノード |
|---|---|
false | parallel_review (Gate 3) |
true | END |
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:lintConsistency ゲートが file:line citations で再検証する設計。多層防御。 - ADR 増加によるコスト膨張: ADR が 100 件超えると入力 200KB 超 → コスト・レイテンシ増。将来は「関連 ADR を retriever で絞り込む」最適化余地。
12. テストケース
| ID | 内容 | 期待 |
|---|---|---|
| TC-C01 | 列番号ハードコード推奨 (ADR-011 と矛盾) | verdict=CONFLICT, rejected=true |
| TC-C02 | OCR を Claude へ移行 + Supersedes: ADR-007 明記 | verdict=SUPERSEDE, rejected=false |
| TC-C03 | Supersede したいが宣言なし | 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-12 | Phase 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. 関連リンク
- 前ノード: 05_cross_validation.md
- 次ノード: 07_parallel_review.md
- プロンプト設計: prompts/04_consistency.md
- adr-kit 棲み分け: ../adr_skill_setup/langgraph-adrkit-boundary.md
- 関連 ADR: ADR-0023 / ADR-0033 / ADR-0081 / ADR-0083 / ADR-0085
実装更新 (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.parseをsafeParseLlmJson<ConsistencyResult>()に置換 SYSTEM_PROMPTをFALLBACK_PROMPTにリネーム +loadPrompt(env, 'gate2-consistency', FALLBACK_PROMPT)に切替 (PR #1090)ChatOpenAIインスタンスにmaxRetries=3(exponential backoff) を付与