最終更新: 2026/06/22 18:56
11. Webhook Node
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 フィールド | 用途 |
|---|---|
adrBody | PR に commit する ADR 本文 |
adrSlug | ファイル名 / ブランチ名 (空なら 'adr-draft') |
adrNumber | 0 パディングして 4 桁化 (空なら '0000') |
title | PR タイトル / nav title |
author | PR 本文の 起案者 メタ |
triageMode | PR 本文の モード メタ |
score | PR 本文の スコア メタ |
scoringDetail | PR 本文の Gate 4 セクション |
crossValidationDetail | PR 本文の Cross-Validation セクション (scoringDetail の直後に挿入) |
consistencyVerdict | PASS 以外なら PR 本文に Gate 2 セクション追加 |
consistencyDetail | Gate 2 セクション本文 |
parallelReviewDetail | Gate 3 セクション (アノテーション JSON) |
policyAlignmentDetail | Policy 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 操作):
| # | エンドポイント | 操作 | 失敗時挙動 |
|---|---|---|---|
| 1 | GET /git/ref/heads/main | main ブランチ SHA 取得 | 例外スロー (致命的) |
| 2 | POST /git/refs | 新規ブランチ作成 | 例外スロー (致命的) |
| 3 | PUT /contents/${filePath} | ADR ファイル作成 | 例外スロー (致命的) |
| 4 | GET /contents/docs/_config.json?ref=${branch} | nav 取得 | try/catch でログのみ (best-effort) |
| 5 | PUT /contents/docs/_config.json | nav 登録 | try/catch でログのみ (best-effort) |
| 6 | POST /pulls | PR 作成 | 例外スロー (致命的) |
認証: 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を末尾としているが、consistencyDetailがverdict=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-11 | Phase 2a' 初期実装 | Workers から GitHub API 直叩き方式を採用 |
| (設計時) | GitHub Actions webhook 方式を不採用 | Workers から直接 API を叩く方がシンプル (Actions は採番 concurrency のみ使う) |
| (途中追加) | registerAdrInNav 機能追加 | nav 登録忘れによる「docs サイドバーに新 ADR が現れない」問題を自動化 |
| (途中追加) | nav 登録を best-effort 化 | フォーマット崩れ等で PR 作成自体が止まる事故を防止 |
| Phase 2c/3 | consistencySection / parallelReviewSection 追加 | Gate 2/3 の判定結果を PR 本文に転記 |
| (実装途中) | policyAlignmentSection 追加 | Policy Alignment ノード追加に追随 |
14. 関連リンク
- 前ノード: 10_numbering.md
- 後: GitHub PR (Status: Proposed) → adr-kit
/adr-kit:lint→ オーナー (代表取締役) の Accepted 判断 - 起案者ガイド: ../../05_how-to/operator_guide.md
- 関連 ADR: ADR-0023