TL;DR(このノードは何をする・専門語ゼロ): 提案された技術判断を受け取り、まず「これはわざわざ記録に残す価値があるか」を最初にふるい分ける入口の関所。残す価値がなければ「普通の変更説明だけでよい」と差し戻す。残す価値があれば、その判断の重みを軽い・普通・重大の 3 段階に仕分けし、後続のチェックがどれだけ厳しく見るかを決める。

実装: drp/src/nodes/triage.ts プロンプト設計ドキュメント: prompts/01_triage.md プロンプト SSoT (KV デプロイ): drp/prompts/production/gate0-triage/prompt.md (ADR-0042 Type 1) テスト: drp/test-tc.mjs (TC-01〜05) 基盤 ADR: ADR-0019 / ADR-0020 (判定基準根拠) / ADR-0033 (モデル選定)


1. 役割と位置づけ

パイプライン最上流の「そもそも ADR〔Architecture Decision Record=技術判断の記録〕を書くべきか?」を判定する関所。書くべき場合は Light / Standard / Critical〔モード=判断の重みを軽い・普通・重大の 3 段階に仕分けする区分〕のモードを決め、下流ノードのスコア閾値・並列レビュー数を制御する。

  • 解決する課題: 軽微な変更 (typo・色調整・自明なバグ修正) に対して ADR ライフサイクル一式を回すと起案コスト・LLM コストが膨らみ、起案者が「ADR を書きたくない」心理になる。Triage〔トリアージ=ADR にする価値があるかの振り分け〕で対象外を弾くことでパイプラインの摩擦を最小化する。
  • 設計思想: 差戻し型ゲート〔Gate=通過条件を満たすか判定する関門〕is_adr_worthy=false で END に飛ばし、起案者に「通常の PR 概要欄に書いてください」と返却する。判断に迷ったら Standard を既定値とする保守的設計 (ADR-0020)。
  • 学術的根拠: Nygard 5 領域 / Chen et al. ASR / Zimmermann 5+2 / Bezos Type 1/Type 2 (詳細は ADR-0020)。

2. フロー図

flowchart LR
    START([POST /draft or /chat/start]) --> T[triage]
    T -->|needsAdr=true| S[socratic]
    T -->|needsAdr=false
rejected=true| END([END
「ADR 対象外」を起案者に返却])

具体例で見る処理の流れ

判定ルールの詳細は §5 にありますが、ここでは「実際に提案を出すとどう処理されるか」を 3 つの身近な例で示します。

例 1:誤字の修正 → その場で「対象外」

  • 出した提案: 「README の誤字を 1 か所直したい」(短い 1 文で、コード名や URL を含まない)
  • どう動くか: 記録に残すような判断要素がなく、文章も短いので、AI を呼ぶ前に入口で弾かれます。
  • 結果: 「ADR 対象外です。通常の PR 概要欄に書いてください」と差し戻し。審査はここで終わります。

例 2:データの保存先を変える(試算あり)→ 通過

  • 出した提案: 「請求データの保存先を新しいデータベースに変えたい。理由は〇〇。月◯円のコストがかかる見込み」
  • どう動くか: 将来に影響する判断なので「記録に残すべき(ADR 対象)」と判定。重みは「普通(Standard)」。コストの見込みも書かれているので入口の条件も満たします。
  • 結果: 次の段階(見落としチェック)へ進みます。

例 3:例 2 と同じ提案だが試算を書き忘れ → いったん差し戻し

  • 出した提案: 例 2 と同じ。ただしコストの見込みを書いていない。
  • どう動くか: 「記録に残すべき・重みは普通」までは例 2 と同じ。ただし「普通」以上はコストの見込みが必須なので、入口の条件チェックで止まります。
  • 結果: 「コストの見込みを書き足してください」と差し戻し。追記して出し直せば、例 2 と同じように通過します。

ポイント: 止まり方には 2 種類あります。例 1 は「そもそも記録に残す話ではない」、例 3 は「記録に残す話だが、出し方が条件を満たしていない」。差し戻しのメッセージもこの 2 つで変わります。


3. トリガー条件

エントリポイント起動グラフ説明
POST /draft (フォーム再送信型)buildGraphWithWebhook常に最初に実行 (START → triage)
POST /chat/start (チャット型)buildPreGraphtriage + socratic のみの軽量グラフ
POST /chat/run (チャット型・full)buildGraphwebhook を除く full pipeline

スキップ条件: なし。Triage は全エントリポイントで必ず実行される。


4. 入力 (State)

State フィールド必須用途
titlestring任意起案タイトル。未入力なら suggested_title で補完
contextstring必須背景・目的の自由記述
optionsstring任意検討した代替案

プロンプトに渡す形式:

タイトル: {title}              ← title が空なら省略

背景・目的:
{context}

検討した代替案:                  ← options が空なら省略
{options}

5. 処理ロジック

0. 冪等性: state.needsAdr が既に決定済なら何もしない (return {})
1. ADR-0095 Phase A short-circuit: LLM 呼出前に typo / 軽微変更系の短文かつ ADR 識別子なしを検知したら
   即 needsAdr=false で差戻し (LLM を呼ばない。triageDecisionSource='short_circuit',
   triageShortCircuitReason=理由, triageRejectKind='not-adr-worthy')
   - **閾値**: `context` の長さが `TRIAGE_SHORT_CIRCUIT_MAX_CHARS` (既定 80) 以下のときのみ対象。`"0"` で機能無効化 (緊急ロールバック)。
   - **長さは Grapheme クラスタで計数** (`String.length` でなく `Intl.Segmenter('ja')`)。日本語・絵文字混在草案の視覚長乖離を防ぐ (§盲点 #1)。
   - **ADR 識別子があれば短文でも素通し** (LLM へ): `## H2` 見出し / URL / `ENV_VAR` (UPPER_SNAKE) / `` `code span` `` / `func(` 呼出 / `module.func` / camelCase / snake_case のいずれか。
   - short-circuit hit は `event: 'triage_short_circuit'` ログ (reason + 先頭 50 字 sample) で false positive を週次監視 (§盲点 #6)。
2. loadLlmParams(env, 'gate0-triage', { temperature: 0.2, seed: 42 }) → createLlm(..., maxRetries=3)
3. loadPrompt(env, 'gate0-triage', FALLBACK_PROMPT) で system prompt を取得し、user message に起案入力を埋めて invoke
4. safeParseLlmJson<TriageResult>() で JSON 抽出 (parse 失敗時はフォールバック値)
5. consistency override (P1 fix): is_adr_worthy と mode の自己矛盾 (mode 付きなのに対象外等) を検知して整合化
6. is_adr_worthy=false → needsAdr=false, rejected=true, triageRejectKind='not-adr-worthy' で END
7. is_adr_worthy=true → 起案前ゲートを順に評価 (Light は免除):
   ① ADR-0088 コスト試算ゲート: Standard 以上で context にコスト試算が無ければ (!hasCostEstimate)
      rejected=true, rejectionReasonCode=COST_MISSING, triageRejectKind='pre-gate-block' で差戻し
   ② ADR-0091 3 ルールゲート: Standard 以上で no-placeholder-marker 等の 3 ルール違反なら
      最初の 1 件で rejected=true, rejectionReasonCode=..., triageRejectKind='pre-gate-block' で差戻し
8. 全ゲート通過 → needsAdr=true, triageMode/triageReason をセット、title 未入力なら suggested_title で補完

起案前ゲート (①②) は「ADR 対象だが品質要件未達」での差戻しで、triageRejectKind='pre-gate-block'。 一方 short-circuit / is_adr_worthy=false は「そもそも ADR 対象外」で triageRejectKind='not-adr-worthy' (ADR-0094 Phase C で意味論分離)。

5.1 処理フローチャート

triageNode() 内部の分岐 (src/nodes/triage.ts)。緑=needsAdr=true で socratic へ、赤=rejected=true で END。差戻しは どの triageRejectKind で起案者への意味が変わる。

Triage Node 処理フローチャート(① 入口の早期判定 → ② AI 判定 → ③ 起案前ゲート。緑=通過で socratic へ / 赤=差し戻しで END)

各ボックスにマウスを乗せると処理の説明が表示されます。図の元ソース: 01_triage_flow.d2d2 --layout=elk で SVG を再生成)。

処理説明(番号は図のボックスに対応)

番号処理名処理内容分岐・次の処理
1冪等判定state.needsAdr が既に決定済みかを確認済 → 何もしない(no-op で終了)/ 未 → 2
2早期判定(short-circuit)短文(≤ MAX_CHARS)かつ ADR 識別子なしを検知し、LLM を呼ばずに弾く該当 → 差し戻し「ADR 対象外」(not-adr-worthy)/ 非該当 → 3
3AI 判定gemini-flash にプロンプトを渡し、is_adr_worthy / mode / reason を JSON で取得4
4自己矛盾チェック(consistency override)「対象外なのに mode あり」等の矛盾を検知し、ADR 対象に補正5
5ADR 要否判定is_adr_worthy で記録に残すべきかを確定否 → 差し戻し「ADR 対象外」/ 要 → 6
6重みを決定mode を確定(未指定は Standard を既定値)7
7Light 判定Light なら起案前ゲートを免除Light → 通過(socratic へ)/ Standard・Critical → 8
8コスト試算ゲート(ADR-0088)context にコスト試算が書かれているかなし → 差し戻し「品質未達」(COST_MISSING)/ あり → 9
93 ルールゲート(ADR-0091)no-placeholder 等の必須 3 ルールを満たすか違反(先頭 1 件)→ 差し戻し「品質未達」/ 満たす → 通過

終端(結果): 通過 = needsAdr=true で socratic へ。差し戻しは 2 種類 — 処理 1〜5 由来の「ADR 対象外」(not-adr-worthy)と、処理 8〜9 由来の「品質未達」(pre-gate-block)。両者で起案者へのメッセージが変わる。

テストとの対応 (§12): TC-01/02 → 「ADR 対象外」(処理 2 または 5)/ TC-03 → Light 通過(処理 7)/ TC-04(Standard)・TC-05(Critical)→ 処理 8〜9 を通過。

読み方: 上から下へ評価し、最初に該当した分岐で終端する。LLM 呼出は short-circuit を抜けた草案のみ (コスト最適化)。起案前ゲート (ADR-0088 / 0091) は is_adr_worthy=true 確定後・Light 免除で順に評価し、最初の 1 件で差し戻す (起案者の段階的修正を促す)。

判定 7 ルール (システムプロンプト gate0-triage より要約):

  1. ADR (Architecture Decision Record) として記録すべき決定かを判定する。
  2. ADR 対象は「将来の自分・他人がその決定を覆す/参照する可能性のある、判断要素を含むもの」に限る。
  3. 以下は ADR 対象外: UI 文言・色・余白の微調整 / typo・コメント・外部挙動不変のリファクタ / 既存パターン完全準拠の CRUD 追加 / 一時デバッグログ・運用回避策 / 修正方針が自明なバグ修正。
    • ⚠️ 「実装作業でなく政策/プロセス/運用ルールの確立だから PR で十分」を理由に対象外にしてはならない (2026-06 lint-governance false-negative 修正)。lint ルール / CI ガバナンス / メタデータ規約 / ドキュメント運用ポリシー (鮮度 SLA・verified-as-of/frontmatter 規約・staleness 検知) の確立は ADR 対象 (Standard、precedent: ADR-0051/0053/0057/0058/0088)。対象外は「外部挙動が変わらない些末作業」に限る。
  4. ADR 対象の場合のモード:
    • Light = 内部実装の選択 (ライブラリ採用・命名規則等) で外部 I/F に影響しない。メタ ADR 典型例: テンプレ 1-2 行追加、命名規則微調整、README ガイド節追記、訂正注記 (corrigendum) 追加、ADR テンプレへの節・メタデータ項目追加 (Kruchten Type / Implementation Status / Confirmation 節等)。ただし CI 強制・既存成果物の遡及改訂・運用ポリシー化を伴うものは Light でなく Standard
    • Standard = データモデル変更 / 新規モジュール追加 / 業務フロー変更。メタ ADR 典型例: Pipeline ノードのモデル swap・プロンプト改修、サブワークフロー定義追加 (B1-B6 / C1-C6 等)、lint ルール / CI ガバナンス追加、ドキュメント運用ポリシー確立。
    • Critical = 認証・課金・会計仕訳ロジック / 請求・決済・税の検証/計算 / 外部 API 契約 / セキュリティ / マイグレーション。メタ ADR では原則 Critical にしない (例外: ADR 全体構造の根本変更・連鎖 Supersede・業務フロー全体の段構成確定)。
  5. 【排他的除外リスト — 外見が小さくても Light にしてはならない】 (ADR-0102 で追加)。以下は修正量が小さく見えても 必ず Standard (該当すれば Critical):
    • (汎用) データスキーマ/データモデル変更 (列追加・型変更・マスタ定義) / 外部 API・外部サービス連携の追加変更 (契約・認証フロー含む) / 認証・認可の変更 / 課金・会計仕訳ロジックの変更
    • (本プロジェクト固有の高ステークス領域) Decision Pipeline のノード/グラフ/ゲート挙動の変更 / triage 判定基準・閾値の変更 / telemetry・監査スキーマ (D1 カラム等) の変更
    • うち会計仕訳・認証・課金・外部 API 契約・マイグレーションは Critical を検討。
  6. 判断に迷ったら Standard を既定値 (Light は影響が真に限定的かつ排他的除外リスト非該当の場合のみ。Critical は明示的該当根拠がある場合のみ)。
  7. 整合性 (必須): is_adr_worthy=false なら mode は必ず null。逆に mode を Light/Standard/Critical に決めたら is_adr_worthy は必ず true (モードが決まる=ADR 対象)。両者の不一致を出力してはならない (P1 fix を prompt 側でも担保。コード側 consistency override は §5-5 / §10)。

5.2 シーケンス図(1 起案の時系列)

ノード内部の分岐 (§5.1) を、起案者・処理・AI のやり取りの時系列で見ると次のとおり。差し戻しは 2 種類 (対象外 = not-adr-worthy / 品質未達 = pre-gate-block)。

Triage Node シーケンス図(起案者 → Worker → triage ノード → LiteLLM Gateway。冪等・入口の早期判定・AI 判定の 3 経路)

図の元ソース: 01_triage_sequence.d2d2 --layout=elk で SVG を再生成)。


6. LLM 設定

項目根拠
モデルgemini-flash (MODELS.triage)分類タスクのみで deep think 不要・最安・最速 (ADR-0033)
temperature / seedKV gate0-triage params (fallback 0.2 / 42)判定の一貫性・再現性。実効値は KV 優先 (loadLlmParams)
thinking budget適用外 (gemini-flash)thinkingBudget state は gemini-pro 系のみ
期待出力JSON object (4 フィールド)system prompt で形式強制
コスト目安1 起案あたり < 0.01 USD (入力 1〜2KB)gemini-flash は他モデルの 1/10〜1/20
レイテンシ1〜3 秒パイプライン全体 (30〜60 秒) に対し無視できる

7. 副作用

なし。LiteLLM Gateway 経由の LLM 呼出のみ。GitHub API / KV / Webhook には触れない。


8. 出力 (State)

ADR 対象 (is_adr_worthy=true)

{
  needsAdr: true,
  triageMode: 'Light' | 'Standard' | 'Critical',
  triageReason: string,
  title?: string,  // suggested_title (元の title が空の場合のみ補完)
}

ADR 対象外 (is_adr_worthy=false / short-circuit)

{
  needsAdr: false,
  triageMode: null,
  triageReason: string,
  rejected: true,
  rejectionMsg: 'ADR 対象外です。通常の PR 概要欄に書いてください。\n理由: {reason}',
  triageRejectKind: 'not-adr-worthy',          // ADR-0094 Phase C
  triageDecisionSource?: 'short_circuit',       // short-circuit 経路のみ
  triageShortCircuitReason?: string,            // short-circuit 経路のみ
}

起案前ゲート差戻し (ADR 対象だが品質未達: ADR-0088 / ADR-0091)

{
  needsAdr: false,
  triageReason: string,
  rejected: true,
  rejectionMsg: string,                          // コスト試算欠落 / 3 ルール違反の指示
  rejectionReasonCode: 'COST_MISSING' | ...,     // REJECTION_REASON_CODES
  triageRejectKind: 'pre-gate-block',            // ADR-0094 Phase C
}

LLM 生 JSON (内部 TriageResult)

{
  "is_adr_worthy": true,
  "mode": "Standard",
  "reason": "新規データストア採用は将来の運用コスト・移行性に長期的な影響を与えるため ADR が必要。",
  "suggested_title": "ADR ストアの永続化先として DynamoDB を採用"
}

9. 分岐 (次ノード)

graph.ts の conditional edge:

.addConditionalEdges('triage', (s) => s?.needsAdr ? 'socratic' : END)
needsAdr次ノード起案者への表示
truesocraticGate 1 の Socratic〔ソクラテス式問答=前提・代替案を問い直して盲点を洗い出す手法〕問診フェーズへ
falseENDrejectionMsg を返却してパイプライン終了

10. エラー時の挙動

ADR-0081 / PR #1087・#1090 で堅牢化済:

失敗パターン挙動影響
LLM タイムアウト / Gateway 不達createLlmmaxRetries=3 (exponential backoff) で transient error をリトライ。全失敗時のみ例外スロー起案者に 500 系エラー (稀)
LLM が JSON 以外を返すsafeParseLlmJson<TriageResult>() がフォールバック値を返し例外を投げないstructured error log に記録
is_adr_worthy 等のフィールド欠落フォールバック値で補完。consistency override (P1 fix) が mode/is_adr_worthy の自己矛盾も整合化検出可能 (ログ出力)

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

  • メタ ADR の Critical 化抑制 (2026-04 追加): ADR テンプレ改修・Pipeline 改修が安易に Critical 判定されてパイプラインの並列レビュー数が増えコストが膨らむ問題に対し、システムプロンプトで「メタ ADR は原則 Critical にしない」と明示。
  • 判断迷い時のデフォルト変更 (2026-04 追加): 当初は「迷ったら厳しめ (上位モード)」だったが、Standard 想定の起案が次々 Critical に流れて並列レビューコストが膨らんだため、Standard を既定値に変更 (ADR-0020 §改訂)。
  • タイトル補完の境界: suggested_title起案者が title を空で送った場合のみ state を上書きする。明示入力された title は尊重される。
  • gemini-flash の JSON ブレ: ごく稀に ```json コードブロックで包んで返す → 実装側で正規表現除去で対処済。
  • Light の境界判定: 「内部実装の選択で外部 I/F に影響しない」の判定は LLM の解釈に依存。曖昧なケースは Standard に倒される傾向 (TC-03 で確認)。排他的除外リスト (§5 rule 5 / ADR-0102) で Decision Pipeline 変更・triage 基準変更・telemetry スキーマ変更等は LLM の解釈に関わらず強制 Standard に固定。
  • lint/CI ガバナンス系の false-negative (2026-06 修正): 「lint ルール確立は実装でなくプロセスだから PR で十分」と LLM が誤判定し ADR 対象外にする事例 (draft adr-doc-freshness-verified-as-of) が継続したため、(a) プロンプト rule 3 で「制度設計は ADR 対象 (Standard)」を明示、(b) is_adr_worthy=false かつ mode!=null の自己矛盾をコードで決定的に override (consistency override / §10) の二段で担保。
  • short-circuit の false positive 監視: LLM 非呼出のため retry ログが出ない。event: 'triage_short_circuit' を専用監視し、誤って弾いた草案を早期検知する (閾値は TRIAGE_SHORT_CIRCUIT_MAX_CHARS で調整、"0" で無効化)。

12. テストケース

ID種別内容期待
TC-01triageREADME typo 修正is_adr_worthy=false
TC-02triageUI 背景色変更 (デザインシステム内)is_adr_worthy=false
TC-03triageGAS 日付ライブラリ Luxon 採用is_adr_worthy=true, mode=Light
TC-04triage11_mst_account に税効果科目フラグ列追加is_adr_worthy=true, mode=Standard
TC-05triageAction B 決済日バリデーション緩和is_adr_worthy=true, mode=Critical

実行: node drp/test-tc.mjs --triage-only 注意: テストスクリプトは OpenAI 直接呼出 (LiteLLM Gateway を経由しない)。本番は gemini-flash のため厳密一致ではないが、判定の質傾向は確認できる。


13. 過去の設計判断ログ

日時変更経緯
2026-05-04v0.1 初版 (Phase 1 着手)Dify Cloud 上で初期検証
2026-05-04v0.2 (reason 80 字 / title 30 字を強制)TC-03/04/05 で出力長超過 → プロンプトで明示制約
2026-05-11LangGraph TS 移行Dify 退役 (ADR-0019)。プロンプト本文は v0.2 を踏襲
2026-05-12gemini-flash に確定ADR-0033 でノード別モデル選定。分類タスクは flash で十分と判定
2026-04 (概算)メタ ADR の Critical 抑制ルール追加ADR 運用改修系が Critical に流れすぎる問題に対処
2026-04 (概算)判断迷い時デフォルトを「厳しめ」→「Standard」に変更並列レビューコスト膨張への対処
2026-05-31ADR-0088 コスト検出の精度改善cross-section false positive 修正 (距離 40→15 char、形容詞的修飾を blacklist)
2026-05ADR-0094 Phase C: triageRejectKind 意味分離「ADR 対象外 (not-adr-worthy)」と「起案前ゲート差戻し (pre-gate-block)」を区別、UI 矛盾を解消
2026-05ADR-0095 Phase A: LLM 呼出前 short-circuittypo 系短文の retry 多発 (Gemini Flash 23-30s) を回避。Grapheme 計数 + ADR 識別子ガード
2026-06ADR-0102 フェーズ①: 排他的除外リスト追加 (§5 rule 5)局所的に見える高ステークス変更 (Pipeline/triage 基準/telemetry スキーマ等) の過小審査を防止
2026-06lint/CI ガバナンス false-negative 修正「制度設計は PR で十分」誤判定を prompt rule 3 + code consistency override の二段で解消

14. 関連リンク

  • 上位ドキュメント: decision_pipeline/README.md
  • プロンプト設計: prompts/01_triage.md
  • 次ノード: 02_socratic.md
  • State 定義: drp/src/state.ts
  • グラフ定義: drp/src/graph.ts
  • LLM Gateway: drp/src/llm/gateway.ts
  • 関連 ADR:
    • ADR-0019 — Dify → LangGraph 移行
    • ADR-0020 — Triage 判定基準の学術的根拠
    • ADR-0033 — ノード別モデル選定
    • ADR-0042 — プロンプト SSoT (KV) ライフサイクル
    • ADR-0081safeParseLlmJson<T>() で JSON parse を堅牢化 (PR #1087)
    • ADR-0085loadPrompt(env, 'gate0-triage', FALLBACK_PROMPT) で KV 経由読込 (PR #1090)
    • ADR-0103 — 入口統一 (案 C′)・triage 派生フィールド整形を src/triage/shared_triage.ts に集約

実装更新 (2026-05-28)

  • triage.ts: JSON.parsesafeParseLlmJson<TriageResult>() に置換 (PR #1087)
  • SYSTEM_PROMPTFALLBACK_PROMPT にリネームし loadPrompt(env, 'gate0-triage', FALLBACK_PROMPT) に切替 (PR #1090)
  • ChatOpenAI インスタンスに maxRetries=3 (exponential backoff) を付与

実装更新 (2026-06-03 / 本ドキュメントの実装追従)

  • ADR-0095 Phase A: LLM 呼出前 short-circuit (evaluateShortCircuit_ / graphemeCount_ / ADR_IDENTIFIER_PATTERNS / env TRIAGE_SHORT_CIRCUIT_MAX_CHARS)。
  • ADR-0094 Phase C: triageRejectKind (not-adr-worthy / pre-gate-block) + triageDecisionSource / triageShortCircuitReason
  • ADR-0088 / ADR-0091: 起案前ゲート (コスト試算 hasCostEstimate + checkTriageGate 3 ルール、rejectionReasonCode)。
  • ADR-0102 フェーズ①: FALLBACK_PROMPT排他的除外リスト (rule 5) を追加 — Pipeline ノード/ゲート変更・triage 基準変更・telemetry スキーマ変更等を強制 Standard。
  • lint/CI ガバナンス false-negative 修正: prompt rule 3 の制度設計=ADR 対象 明示 + is_adr_worthy=false×mode!=null の code consistency override。
  • ADR-0103 案 C′ (入口統一): triage 派生フィールドの整形 (chat 応答 / session cache / consumer DO result / telemetry) を src/triage/shared_triage.ts の共通関数に集約 (handler / consumer が同一定義を呼ぶ・PR #1353)。入口は /chat/start (Web UI 同期 triage 維持) と /runs (CI) の 2 つのまま。triage 内部ロジック (§5 / §5.1) は不変。