TL;DR(このノードは何をする・専門語ゼロ): 審査を通った決定記録を、GitHub 上に実際のファイルとして作り、変更提案(プルリクエスト)まで自動で用意する最後のノードです。起案者が GitHub の画面を操作しなくても、レビュー待ちの提案がそのまま出来上がります。サイドバーへの目次登録も合わせて行いますが、ここだけは失敗しても全体は止めず、後から手で直せる作りにしています。

実装: drp/src/nodes/webhook.ts プロンプト: なし (LLM 呼出なし) テスト: なし 基盤 ADR: ADR-0023 (4 桁ハイフン形式)


1. 役割と位置づけ

Pipeline 最終ノード。GitHub API を直接叩いて ADR ファイル作成 / docs/_config.json の nav 登録 / Pull Request 作成までを行う「書記」ノード。Pipeline 全体の成果物がここで GitHub 上に物理的に生まれる。

  • 解決する課題: 起案者が PR 作成 UI を操作せずに済むよう、Pipeline が「Status: Proposed の PR」まで自動で作る。レビュアー (代表取締役) は PR レビュー UI から即座にレビュー可能。
  • 設計思想: 差戻しなし / LLM 呼出なし。3 つの GitHub API 呼出 (ブランチ作成 / ファイル作成 / PR 作成) + 1 つの best-effort〔失敗しても全体は止めない補助〕操作 (nav 登録) を順に実行。
  • GitHub Actions 不経由: 設計初期は GitHub Actions に webhook〔別システムを自動で呼び出す仕組み〕を投げて Actions 側で PR 作成する案だったが、Workers から直接 GitHub API を叩く方式に変更 (シンプル化)。Actions は採番排他制御の concurrency 機構のみ使う。

2. フロー図

flowchart LR
    N[numbering] --> W[webhook]
    W -->|1. GET main ref| GH[(GitHub API)]
    W -->|2. POST refs
ブランチ作成| GH W -->|3. PUT contents
ADR ファイル作成| GH W -->|4. PUT _config.json
nav 登録 best-effort| GH W -->|5. POST pulls
PR 作成| GH GH --> W W --> END([END
prUrl を返却])

3. トリガー条件

グラフ挙動
buildGraphWithWebhook (/draft)実行
buildGraph (/chat/run)含まれない (チャット型では別途 UI から実行)
buildPreGraph (/chat/start)含まれない

4. 入力 (State)

State フィールド用途
adrBodyPR に commit する ADR 本文
adrSlugファイル名 / ブランチ名 (空なら 'adr-draft')
adrNumber0 パディングして 4 桁化 (空なら '0000')
titlePR タイトル / nav title
authorPR 本文の 起案者 メタ
triageModePR 本文の モード メタ
scorePR 本文の スコア メタ
scoringDetailPR 本文の Gate 4 セクション
crossValidationDetailPR 本文の Cross-Validation セクション (scoringDetail の直後に挿入)
consistencyVerdictPASS 以外なら PR 本文に Gate 2 セクション追加
consistencyDetailGate 2 セクション本文
parallelReviewDetailGate 3 セクション (アノテーション JSON)
policyAlignmentDetailPolicy Alignment セクション

5. 処理ロジック

1. slug = state.adrSlug || 'adr-draft'
2. padded = String(state.adrNumber || 0).padStart(4, '0')
3. branch = `adr/${padded}-${slug}`
4. filePath = `docs/adr/${padded}-${slug}.md`

5. GET /git/ref/heads/main → baseSha
6. POST /git/refs { ref: `refs/heads/${branch}`, sha: baseSha } → ブランチ作成
7. PUT /contents/${filePath} { message: `docs(adr): add ${slug} [Proposed]`, content: base64(adrBody), branch } → ファイル作成
8. registerAdrInNav(env, branch, padded, slug, title) を try/catch (best-effort、失敗してもログのみ)
   - GET /contents/docs/_config.json?ref=${branch} → 取得
   - lines を走査して最後の ADR エントリ行を検出 (/^(\s*)\{\s*"file":\s*"adr\/(?:(\d{4})-|(\d{3})_)[^"]+\.md"/)
   - 直前行に末尾カンマを付与
   - 新エントリを挿入: `{ "file": "adr/NNNN-slug.md", "title": "A.N {cleanTitle}" }`
   - PUT /contents/docs/_config.json (同ブランチに commit)
9. PR 本文を組み立て:
   - 起案者 / モード / スコア / 区切り線 / scoringDetail / **crossValidationSection** / consistencySection / parallelReviewSection / policyAlignmentSection
10. titleStripped = state.title.replace(/^\[ADR\]\s*/, '')  // 二重 [ADR] 防止 (PR #834/837/843)
    POST /pulls { title: `[ADR] ${titleStripped}`, head: branch, base: 'main', body } → PR 作成
11. prUrl を state に格納

registerAdrInNav の詳細:

  • 既存の最後の ADR エントリと同じインデントを継承
  • title の ADR-NNNN: プレフィックスは正規表現で除去
  • ファイルパス: adr/${padded}-${slug}.md
  • nav title: A.${number} ${cleanTitle} (例: A.42 DynamoDB を ADR ストアに採用)

PR 本文セクションの条件付き表示:

  • consistencyVerdict が PASS / null なら Gate 2 セクションを省略
  • parallelReviewDetail が空なら Gate 3 セクションを省略
  • policyAlignmentDetail が空なら Policy Alignment セクションを省略

6. LLM 設定

なし (LLM 呼出なし)。


7. 副作用

GitHub API への書き込み (5 操作):

#エンドポイント操作失敗時挙動
1GET /git/ref/heads/mainmain ブランチ SHA 取得例外スロー (致命的)
2POST /git/refs新規ブランチ作成例外スロー (致命的)
3PUT /contents/${filePath}ADR ファイル作成例外スロー (致命的)
4GET /contents/docs/_config.json?ref=${branch}nav 取得try/catch でログのみ (best-effort)
5PUT /contents/docs/_config.jsonnav 登録try/catch でログのみ (best-effort)
6POST /pullsPR 作成例外スロー (致命的)

認証: env.GITHUB_PAT (Workers Secrets) / User-Agent: decision-pipeline/1.0


8. 出力 (State)

{ prUrl: string }  // 例: "https://github.com/owner/repo/pull/123"

UI 側 (public/index.html) が prUrl を起案者に「PR が作成されました: 」として表示する。


9. 分岐 (次ノード)

.addEdge('webhook', END)

無条件で END。


10. エラー時の挙動

  • ブランチ作成失敗 (422 既存): 例外スロー。同名ブランチが既に存在する場合 (再起案時等) は失敗する。再採番が必要。
  • ファイル作成失敗: 例外スロー。ブランチは残るためゴミブランチ清掃が必要。
  • nav 登録失敗 (best-effort): try/catch でログのみ。PR は作成される。起案者は PR 内で _config.json を手動編集すれば良い (実用上は手動 docs:build で気づく)
  • PR 作成失敗: 例外スロー。ブランチ + ADR ファイルは残る。手動で PR 作成可能。
  • docs/_config.json フォーマット崩れ: 「No existing ADR entry found」エラーで nav 登録のみ失敗 (PR は作成される)。

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

  • ブランチ名衝突: adr/NNNN-slug は採番 + slug で決まる。同一起案を 2 回 (差戻し → 再送) 走らせると、numbering が +1 して次番号を採るため通常は衝突しない。ただし numbering が同番号を返した場合 (race〔競合状態=同時実行で結果が乱れる状態〕) はブランチ作成 422 で失敗。
  • nav 登録の手動編集競合: 起案後に人間が _config.json を手動編集すると、次起案の registerAdrInNav が「最後の ADR エントリ行検出」で誤動作する可能性 (インデント・形式が壊れる)。実用上は通常起こらない。
  • PR 本文の組み立て順: scoringDetail を先頭・policyAlignmentSection を末尾としているが、consistencyDetailverdict=PASS の場合は省略するロジック → PASS の整合性確認情報が PR に残らない。レビュアーが「整合性チェック済」を確認できない設計 (改善余地)。
  • clean title 正規表現: /^ADR-?\d+[:\s]*/ADR- プレフィックスを除去。LLM が「ADR42: 〜」「ADR-0042 〜」など揺れた形式で title を生成しても吸収できる。
  • GitHub API レート制限: 1 起案あたり 4〜6 リクエスト。PAT 5000 req/hour で 1 日 100 起案でも余裕。
  • Cloudflare Workers の subrequest 上限: 1 リクエストあたり 50 subrequest。webhook は 6 リクエスト程度のため余裕。

12. テストケース

なし。GitHub API モック整備コストが高い + 実起案で動作検証する設計。

副作用が大きいため運用初期は手動レビューで PR 内容を確認 (docs/operator_guide.md 参照)。


13. 過去の設計判断ログ

日時変更経緯
2026-05-11Phase 2a' 初期実装Workers から GitHub API 直叩き方式を採用
(設計時)GitHub Actions webhook 方式を不採用Workers から直接 API を叩く方がシンプル (Actions は採番 concurrency のみ使う)
(途中追加)registerAdrInNav 機能追加nav 登録忘れによる「docs サイドバーに新 ADR が現れない」問題を自動化
(途中追加)nav 登録を best-effort 化フォーマット崩れ等で PR 作成自体が止まる事故を防止
Phase 2c/3consistencySection / parallelReviewSection 追加Gate 2/3 の判定結果を PR 本文に転記
(実装途中)policyAlignmentSection 追加Policy Alignment ノード追加に追随

14. 関連リンク