diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md
index b1a3f3f0..8d18d2ca 100644
--- a/.claude/CLAUDE.md
+++ b/.claude/CLAUDE.md
@@ -206,10 +206,11 @@ backend 内で `os.getenv("XXX")` のように文字列リテラル直接参照
## ADR(Architecture Decision Record)
-技術選定・アーキテクチャ判断を行う際は必ず `docs/adr/` を確認し、既存の判断と矛盾しない実装を行うこと。
+技術選定・アーキテクチャ判断を行う際は必ず ADR 索引(`docs/adr/README.md`)から関連 ADR を辿り、既存の判断と矛盾しない実装を行うこと。索引にはテーマ別の決定系統(どの判断がどれを置き換え・前提にしているか)がまとまっている。
-新たに重要な技術判断を行う場合は `CONTRIBUTING.md` の ADR 運用ルールに従い、ADR を作成してから実装を開始する。
+新たに重要な技術判断を行う場合は `CONTRIBUTING.md` の ADR 運用ルールに従い、ADR を作成してから実装を開始する。**ADR の新規作成・ステータス変更をしたら、同じ PR で索引も更新する**(存在・ステータス・見出し番号の整合は `make lint-adr-index` が CI で検証する)。
-- ADR 一覧: `docs/adr/`
+- ADR 索引(一覧・テーマ・決定系統の正本): `docs/adr/README.md`
+- 設計原則(ADR を貫く 7 原則。新規 ADR はどの原則に沿うかを明記する): `docs/design-principles.md`
- テンプレート: `docs/adr/0000-template.md`
- 運用ルール: `CONTRIBUTING.md`
diff --git a/.claude/skills/SEC_review/SKILL.md b/.claude/skills/SEC_review/SKILL.md
index c97d8615..45e92fb2 100644
--- a/.claude/skills/SEC_review/SKILL.md
+++ b/.claude/skills/SEC_review/SKILL.md
@@ -113,7 +113,10 @@ description: Use when running a security review / vulnerability check against th
### 5. LLM プロンプトのサニタイズ
-- 本プロジェクトは LLM 連携を廃止済み(ADR-0008)。現状この観点は該当なし。将来 LLM 連携を再導入する場合のみ、ユーザー由来文字列をプロンプトに埋め込む際のサニタイズ要否を検討する
+- DevForge Agent(ADR-0010 で LLM を対話型機能として再導入。マルチプロバイダ = ADR-0013、Vertex AI 経由 = ADR-0015)がユーザー由来文字列(経歴書フィールド・チャット入力)をプロンプトへ埋め込む
+- `backend/app/services/agent/context_builder.py` でのユーザー入力の埋め込み方(区切り・エスケープ・指示との分離)を確認する
+- プロンプト正本 `backend/app/prompts/agent_*.md` は静的維持が原則(`.claude/rules/backend/agent.md`)。動的にユーザー文字列を連結する変更が入っていないか確認する
+- LLM 出力の取り扱い: 構造化出力(`output_schema.py`)を素通しで DB へ書き込んでいないか(Agent は DB 非更新原則)
### 6. Frontend XSS
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 9ae3504a..7c7b84aa 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -24,6 +24,7 @@
### ADR(設計判断を伴う変更の場合のみ)
- [ ] 新しいライブラリ採用・アーキテクチャ変更を伴う場合、ADR を作成した(または既存 ADR が対応している)
+- [ ] ADR を新規作成・ステータス変更した場合: `docs/adr/README.md` の索引(一覧・テーマ・決定系統図)を更新した(存在・ステータスは `make lint-adr-index` で検証される)
---
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 3efd7161..c3551f6b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -157,6 +157,11 @@ jobs:
- name: Lint SSoT (env names / error codes)
run: bash scripts/lint-env-keys.sh
+ # ADR 索引(docs/adr/README.md)↔ ADR ファイルの drift 検知。
+ # 一覧の複製(旧 CONTRIBUTING の表)が陳腐化した再発防止。bash/grep/comm のみ。
+ - name: Lint SSoT (ADR index)
+ run: bash scripts/lint-adr-index.sh
+
- name: Install uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6dcee337..976377f9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -38,6 +38,9 @@
迷ったら書く。小さすぎる判断に ADR は不要ですが、書きすぎて困ることはありません。
+新しい ADR を書くときは [`docs/design-principles.md`](docs/design-principles.md)(既存 ADR から抽出した設計原則)を確認し、
+テンプレートの「設計原則との関係」欄でどの原則に沿う/反する判断かを明記してください。原則自体を変える判断も ADR 経由で行います。
+
### ファイル命名規則
```
@@ -64,20 +67,9 @@ docs/adr/XXXX-kebab-case-title.md
1. 新しい ADR を作成し、ステータスを `Accepted` にする
2. 古い ADR のステータスを `Superseded by ADR-XXXX` に変更する
3. 古い ADR の本文末尾に変更の経緯を一行追記する
+4. [`docs/adr/README.md`](docs/adr/README.md) の索引(ステータス列・「置き換え・関連」列・決定系統図)を更新する
+
+### ADR 一覧(索引)
-### 既存の ADR 一覧
-
-| No. | タイトル | ステータス |
-|---|---|---|
-| [ADR-0001](docs/adr/0001-sqlite-gcs-backup.md) | SQLite + GCS バックアップ方式の採用 | Accepted |
-| [ADR-0002](docs/adr/0002-jwt-cookie-auth.md) | JWT + Cookie 認証方式の採用 | Accepted |
-| [ADR-0003](docs/adr/0003-redux-toolkit-persist.md) | Redux Toolkit + redux-persist の採用 | Accepted |
-| [ADR-0004](docs/adr/0004-llm-provider-abstraction.md) | LLM プロバイダ抽象化(Ollama/Vertex AI) | Superseded by ADR-0008 |
-| [ADR-0005](docs/adr/0005-cloudrun-single-instance.md) | Cloud Run single instance 構成の採用 | Accepted |
-| [ADR-0006](docs/adr/0006-tanstack-query.md) | TanStack Query 導入検討 | Proposed |
-| [ADR-0007](docs/adr/0007-openapi-typescript-codegen.md) | OpenAPI → TypeScript 型生成(codegen-drift CI) | Accepted |
-| [ADR-0008](docs/adr/0008-remove-llm-to-rule-based-design.md) | LLM プロバイダ抽象化の撤去とルールベース設計への統一 | Superseded by ADR-0010 |
-| [ADR-0009](docs/adr/0009-frontend-toast-notification.md) | フロントエンドのトースト通知統一 | Accepted |
-| [ADR-0011](docs/adr/0011-frontend-textlint-proofread.md) | フロントエンド完結型文章校正(textlint) | Deprecated |
-| [ADR-0010](docs/adr/0010-devforge-agent.md) | DevForge Agent 機能の導入 | Accepted |
-| [ADR-0016](docs/adr/0016-github-skill-inference.md) | GitHub 連携によるスキル推論基盤(3 層モデル) | Accepted |
+ADR の一覧・テーマ・置き換え/関連の正本は [`docs/adr/README.md`](docs/adr/README.md) です(ここには複製しない)。
+新規作成・ステータス変更時は同じ PR で索引を更新してください。索引とファイルの整合は CI(`make lint-adr-index`)で検証されます。
diff --git a/Makefile b/Makefile
index 2a337d84..5eb7ef8b 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@
setup install-hooks install-backend install-web generate-keys \
dev dev-build dev-down dev-amd64 dev-amd64-build dev-web preview-web dev-proxy dev-proxy-only stripe-webhook \
test test-backend test-web mutation-backend mutation-web \
- lint lint-backend typecheck-backend lint-web lint-web-messages lint-env-keys lint-fix \
+ lint lint-backend typecheck-backend lint-web lint-web-messages lint-env-keys lint-adr-index lint-fix \
format format-check \
ci \
dupe-check dupe-check-html dupe-clean \
@@ -47,6 +47,7 @@ help:
@echo " lint-web Frontend: eslint"
@echo " lint-web-messages Frontend: setError等にリテラル日本語が渡っていないか検知"
@echo " lint-env-keys env名/エラーコードの SSoT drift を検知 (env_keys.py↔compose/cloud_run 双方向, リテラル参照禁止, errors.py↔errorCodes.ts)"
+ @echo " lint-adr-index ADR 索引の drift を検知 (docs/adr/README.md↔ADR ファイルの存在/ステータス/見出し番号)"
@echo " lint-fix リント自動修正 (ruff + eslint)"
@echo " format Prettier で整形"
@echo " format-check Prettier チェック"
@@ -158,7 +159,7 @@ mutation-backend:
mutation-web:
nix develop --command bash -c "cd web && npm run test:mutation"
-lint: lint-backend typecheck-backend lint-web lint-web-messages lint-env-keys
+lint: lint-backend typecheck-backend lint-web lint-web-messages lint-env-keys lint-adr-index
lint-backend:
nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check app tests alembic_migrations"
@@ -184,6 +185,12 @@ lint-web-messages:
lint-env-keys:
nix develop --command bash scripts/lint-env-keys.sh
+# ADR 索引(docs/adr/README.md)↔ ADR ファイルの drift を検知。
+# 存在(双方向)・ステータス・見出し番号の突合。bash/grep/sed/comm のみに依存するため
+# nix wrap 不要(devshell に無い依存を使わない)。
+lint-adr-index:
+ bash scripts/lint-adr-index.sh
+
lint-fix:
nix develop --command bash -c "cd backend && .venv/bin/python -m ruff check --fix app tests alembic_migrations"
cd web && npm run lint:fix
diff --git a/README.md b/README.md
index d0fd5ae0..e00b20dc 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,8 @@ GitHub活動分析、ブログ連携による発信力を集計
| [docs/deployment.md](./docs/deployment.md) | 本番デプロイ(GCP)・OpenTofu インフラ構成・CI/CD・ブランチ保護 |
| [docs/api.md](./docs/api.md) | REST API 一覧・環境変数リファレンス |
| [docs/data-model.md](./docs/data-model.md) | Turso (libSQL) 運用・Alembic マイグレーション・データ設計 |
-| [docs/adr/](./docs/adr/) | アーキテクチャ判断記録(ADR) |
+| [docs/design-principles.md](./docs/design-principles.md) | 設計原則(ADR から帰納抽出した 7 原則・原則×ADR マトリクス) |
+| [docs/adr/](./docs/adr/README.md) | アーキテクチャ判断記録(ADR)。索引にテーマ別の決定系統図あり |
| [docs/runbooks/](./docs/runbooks/) | 運用 Runbook |
| [docs/metrics/ai-friendliness.md](./docs/metrics/ai-friendliness.md) | AI フレンドリーさ指標の月次ダッシュボード(`make metrics-ai-friendliness` で再生成) |
diff --git a/docs/adr/0000-template.md b/docs/adr/0000-template.md
index 20248ebf..860631c5 100644
--- a/docs/adr/0000-template.md
+++ b/docs/adr/0000-template.md
@@ -4,6 +4,14 @@
[Proposed / Accepted / Deprecated / Superseded by ADR-XXXX]
+## 関連 ADR
+
+[無ければ「なし」。索引(README.md)の「置き換え・関連」列と整合させる]
+
+- Supersedes: [この ADR が置き換える旧 ADR。無ければ省略]
+- Superseded by: [この ADR を置き換えた新 ADR。無ければ省略]
+- 関連: [前提・踏襲・強結合の関係にある ADR と、その関係の一言説明]
+
## コンテキスト
[意思決定が必要になった背景・制約・課題]
@@ -24,6 +32,10 @@
[この判断を覆すトリガー・移行先]
+## 設計原則との関係
+
+[docs/design-principles.md のどの原則(P1〜P7)に沿う判断か。原則に反する場合はその理由を明記する]
+
## 関連リンク
[PR・Issue・参考資料へのリンク]
diff --git a/docs/adr/0011-frontend-textlint-proofread.md b/docs/adr/0011-frontend-textlint-proofread.md
index 27c9ba1f..6d03451f 100644
--- a/docs/adr/0011-frontend-textlint-proofread.md
+++ b/docs/adr/0011-frontend-textlint-proofread.md
@@ -1,4 +1,4 @@
-# ADR-0009: 職務経歴書のフロントエンド完結型 文章校正(textlint + kuromoji)
+# ADR-0011: 職務経歴書のフロントエンド完結型 文章校正(textlint + kuromoji)
## ステータス
diff --git a/docs/adr/README.md b/docs/adr/README.md
new file mode 100644
index 00000000..cce4d0f3
--- /dev/null
+++ b/docs/adr/README.md
@@ -0,0 +1,105 @@
+# ADR 索引
+
+- **本ファイルが ADR の一覧・関係(テーマ・置き換え・関連)の正本**。CONTRIBUTING.md 等に一覧を複製しない。
+- ADR を新規作成・ステータス変更したら、同じ PR で本索引を更新する(手順: [CONTRIBUTING.md](../../CONTRIBUTING.md) の「ADR」節)。
+- 「全 ADR 一覧」表の **ファイル存在・ステータス・見出し番号は CI で機械検証される**(`scripts/lint-adr-index.sh` / `make lint-adr-index`)。テーマ・関連・決定系統図は人間が編集する(機械検証外)。
+
+## 現在有効な決定(Accepted)
+
+いま生きている判断の早見表。詳細・経緯は各 ADR と「テーマ別の決定系統」を参照。
+
+| No. | タイトル | テーマ | 一言サマリ |
+|---|---|---|---|
+| [ADR-0002](./0002-jwt-cookie-auth.md) | JWT + Cookie 認証方式の採用 | 基盤 | GitHub OAuth only + JWT(RS256)を HttpOnly Cookie で扱い、PII をブラウザストレージに置かない |
+| [ADR-0003](./0003-redux-toolkit-persist.md) | Redux Toolkit + redux-persist の採用 | フロントエンド | フォーム一時キャッシュをページ遷移をまたいで保持(PII は localStorage 非保存) |
+| [ADR-0005](./0005-cloudrun-single-instance.md) | Cloud Run single instance 構成の採用 | 基盤 | コスト最優先で max-instances=1 / min-instances=0。個人開発規模を明示的な設計入力にする |
+| [ADR-0007](./0007-openapi-typescript-codegen.md) | OpenAPI → TypeScript 型コード生成の導入(完全移行) | 開発プロセス / 品質 | Pydantic スキーマを正本に TS 型を自動生成し、drift を CI(codegen-drift)で機械検知 |
+| [ADR-0009](./0009-frontend-toast-notification.md) | フロントエンドの一時通知をトースト方式に統一する | フロントエンド | 外部ライブラリを足さず自前 Toast 基盤(Context + Portal)に一時通知を統一 |
+| [ADR-0010](./0010-devforge-agent.md) | DevForge Agent 機能の導入 | LLM / Agent | 対話型 LLM を再導入。DB 非更新原則・スキーマ/プロンプトの責務分離・失敗の明示的エラー化 |
+| [ADR-0012](./0012-agent-model-switching-and-prepaid-billing.md) | Agent モデル切り替えとプリペイドクレジット課金 | LLM / Agent | モデルはエイリアス方式(実 ID はサーバー側 model_catalog が SSoT)+ プリペイド従量課金(Stripe) |
+| [ADR-0013](./0013-multi-provider-llm-selection.md) | マルチプロバイダ LLM(ユーザー選択式) | LLM / Agent | プロバイダをモデルエイリアスの属性に移しユーザー選択式へ。グローバル `LLM_PROVIDER` 廃止 |
+| [ADR-0014](./0014-renovate-dependency-automation.md) | Renovate による依存更新の自動化 | 開発プロセス / 品質 | 依存の完全固定(SHA / `==` ピン)を維持したまま、追従だけを Renovate で自動化 |
+| [ADR-0015](./0015-vertex-ai-for-gemini-anthropic.md) | Gemini / Anthropic を Vertex AI(SA→ADC)経由にする | LLM / Agent | API キー注入を廃止し ADC 認証へ。データ所在地(アジア圏)と学習除外を担保 |
+| [ADR-0016](./0016-github-skill-inference.md) | GitHub 連携によるスキル推論基盤 | LLM / Agent | スキルを 3 層に分離(機械=幅 / 人間=深さ)。推論パイプラインは決定論を維持 |
+| [ADR-0017](./0017-mutation-testing-and-slack-notifications.md) | ミューテーションテスト週次実行と Slack 通知チャンネル分割 | 開発プロセス / 品質 | テストの検出力を週次ミューテーションで可視化(warn-only)、CI 通知を用途別 Slack へ分割 |
+
+## 全 ADR 一覧
+
+ステータスの定義と変更手順は [CONTRIBUTING.md](../../CONTRIBUTING.md) を参照。
+「原則」列(P1〜P7)はその ADR の中心的な判断軸で、定義と対応マトリクスは [docs/design-principles.md](../design-principles.md) を参照。
+
+| No. | タイトル | ステータス | テーマ | 置き換え・関連 | 原則 |
+|---|---|---|---|---|---|
+| [ADR-0001](./0001-sqlite-gcs-backup.md) | SQLite + GCS バックアップ方式の採用 | Deprecated | 基盤 | Turso (libSQL) 移行で廃止。関連: 0005(同時移行前提の強結合) | P1 |
+| [ADR-0002](./0002-jwt-cookie-auth.md) | JWT + Cookie 認証方式の採用 | Accepted | 基盤 | — | P2 |
+| [ADR-0003](./0003-redux-toolkit-persist.md) | Redux Toolkit + redux-persist の採用 | Accepted | フロントエンド | 関連: 0006(責務境界・PII 方針を踏襲) | P2 |
+| [ADR-0004](./0004-llm-provider-abstraction.md) | LLM プロバイダ抽象化(Ollama/Vertex AI)の設計判断 | Superseded by ADR-0008 | LLM / Agent | 0008 が撤去。dev/prod 分離思想は 0010 が再利用 | P6 |
+| [ADR-0005](./0005-cloudrun-single-instance.md) | Cloud Run single instance 構成の採用 | Accepted | 基盤 | 関連: 0001(制約の起点)、0012(単一インスタンス前提の原子的 UPDATE) | P1 |
+| [ADR-0006](./0006-tanstack-query.md) | TanStack Query 導入検討 | Deprecated | フロントエンド | パイロット未実施で見送り。関連: 0003 | P6 |
+| [ADR-0007](./0007-openapi-typescript-codegen.md) | OpenAPI → TypeScript 型コード生成の導入(完全移行) | Accepted | 開発プロセス / 品質 | 関連: 0010(Agent の型契約も codegen 経由) | P3 |
+| [ADR-0008](./0008-remove-llm-to-rule-based-design.md) | LLM プロバイダ抽象化の撤去とルールベース設計への統一 | Superseded by ADR-0010 | LLM / Agent | Supersedes: 0004。0010 が「将来の移行条件」の手続きに従い再導入 | P5・P6 |
+| [ADR-0009](./0009-frontend-toast-notification.md) | フロントエンドの一時通知をトースト方式に統一する | Accepted | フロントエンド | — | P6 |
+| [ADR-0010](./0010-devforge-agent.md) | DevForge Agent 機能の導入 | Accepted | LLM / Agent | Supersedes: 0008。関連: 0004(積み残しリスクを設計段階で解消)、0007 | P4・P5 |
+| [ADR-0011](./0011-frontend-textlint-proofread.md) | 職務経歴書のフロントエンド完結型 文章校正(textlint + kuromoji) | Deprecated | フロントエンド | 実装後に運用不要と判断し撤去。関連: 0008(PII 非送信・ルールベース志向) | P2 |
+| [ADR-0012](./0012-agent-model-switching-and-prepaid-billing.md) | Agent モデル切り替えとプリペイドクレジット課金 | Accepted | LLM / Agent | 関連: 0010(チャット契約)、0005(原子的 UPDATE の前提) | P1 |
+| [ADR-0013](./0013-multi-provider-llm-selection.md) | マルチプロバイダ LLM(ユーザー選択式) | Accepted | LLM / Agent | 関連: 0010(切替 1 箇所の原則を維持)、0012(課金・model_catalog) | P3 |
+| [ADR-0014](./0014-renovate-dependency-automation.md) | Renovate による依存更新の自動化 | Accepted | 開発プロセス / 品質 | 関連: 0017(Actions の SHA ピン運用) | P7 |
+| [ADR-0015](./0015-vertex-ai-for-gemini-anthropic.md) | Gemini / Anthropic を Vertex AI(SA→ADC)経由にする | Accepted | LLM / Agent | 0013 の認証部分を更新。関連: 0010、0012 | P2 |
+| [ADR-0016](./0016-github-skill-inference.md) | GitHub 連携によるスキル推論基盤 | Accepted | LLM / Agent | 関連: 0010(責務分離の元思想)、0013、0015 | P4 |
+| [ADR-0017](./0017-mutation-testing-and-slack-notifications.md) | ミューテーションテスト週次実行と Slack 通知チャンネル分割 | Accepted | 開発プロセス / 品質 | 関連: 0014 | P3 |
+
+## テーマ別の決定系統
+
+各テーマの判断がどう連なり・覆されてきたかの系統図。実線 = 置き換え(supersede)、点線 = 参照・前提。
+
+### LLM / Agent
+
+```mermaid
+graph LR
+ A0004["0004
LLM 抽象設計"] -->|"撤去"| A0008["0008
ルールベース一本化"]
+ A0008 -->|"再導入"| A0010["0010
DevForge Agent"]
+ A0010 -.-> A0012["0012
モデル切替 + 課金"]
+ A0012 -.-> A0013["0013
マルチプロバイダ"]
+ A0013 -.-> A0015["0015
Vertex AI (ADC)"]
+ A0010 -.-> A0016["0016
スキル推論 3 層"]
+```
+
+このプロダクトで最も判断の往復が大きい系統。「LLM 抽象の先行実装(0004)→ 利用見込み薄と判断して全撤去(0008)→ 対話型として価値が明確になった時点で、0008 自身が規定した手続きに従い再導入(0010)→ 課金・マルチプロバイダ・データガバナンスへ段階拡張(0012/0013/0015)」という流れで、**撤退条件を先に書いておく運用が実際に機能した実例**になっている。0016 は 0010 の「機械検証可能な制約はコード、不能な制約はプロンプト」という責務分離を「機械=幅 / 人間=深さ」の 3 層モデルへ一般化した。
+
+### 基盤(データ / インフラ / 認証)
+
+```mermaid
+graph LR
+ B0001["0001
SQLite + GCS"] -.->|"制約の起点"| B0005["0005
single instance"]
+ B0001 -->|"Turso 移行で廃止"| BX["(Turso / libSQL)"]
+ B0002["0002
JWT + Cookie 認証"]
+```
+
+0005 の single instance 制約は当初 0001(SQLite の同時書き込み不可)に起因していた。Turso 移行で 0001 は Deprecated になったが、0005 は「コスト最適化・個人開発規模」という別の根拠で存続している。**同じ決定でも根拠が入れ替わることがある**ため、系統図は「何に依存していた判断か」を追う手がかりになる。0002 は独立した判断だが、「PII をブラウザに置かない」方針の起点として 0003 / 0011 に思想面で連なる。
+
+### フロントエンド
+
+```mermaid
+graph LR
+ C0003["0003
Redux Toolkit + persist"] -.->|"PII 方針を踏襲"| C0006["0006
TanStack Query (見送り)"]
+ C0009["0009
自前 Toast 統一"]
+ C0011["0011
textlint 校正 (撤去)"]
+```
+
+導入したもの(0003 / 0009)より、**見送り・撤去の判断(0006 / 0011)が残っていること**がこの系統の価値。0006 はパイロット未実施のまま導入せず、0011 は実装まで行った上で運用不要と判断して撤去した。外部ライブラリを安易に足さない・使われないものを残さない判断の記録として参照する。型定義の正本は backend にある(0007 の codegen が FE の手書き DTO を置き換えた)。
+
+### 開発プロセス / 品質
+
+```mermaid
+graph LR
+ D0007["0007
OpenAPI → TS codegen"]
+ D0014["0014
Renovate 自動追従"] -.->|"SHA ピン運用"| D0017["0017
ミューテーションテスト"]
+```
+
+「正本を 1 つに定め、複製との乖離は機械で検知する」(0007)、「依存は固定し、追従は自動化する」(0014)、「テストの検出力自体を計測する」(0017)という、**プロダクト機能ではなく開発体験そのものへの投資**の系統。`docs/metrics/ai-friendliness.md` はこの系統の効果を月次で観測するダッシュボード。
+
+## 運用
+
+- **新規作成**: [`0000-template.md`](./0000-template.md) をコピーし、[CONTRIBUTING.md](../../CONTRIBUTING.md) の命名規則・ステータス運用に従う。作成したら本索引の「全 ADR 一覧」(Accepted なら「現在有効な決定」にも)へ行を追加する
+- **ステータス変更(supersede / deprecate)**: CONTRIBUTING.md の手順に従い旧 ADR を更新した後、本索引のステータス列・「置き換え・関連」列・決定系統図を更新する
+- **検証**: `make lint-adr-index`(CI でも実行される)が「ADR ファイル ↔ 索引の行」の存在・ステータス・見出し番号の突合を行う
diff --git a/docs/deployment.md b/docs/deployment.md
index 7653a64b..d5be3ba4 100644
--- a/docs/deployment.md
+++ b/docs/deployment.md
@@ -149,13 +149,14 @@ Secret Manager は secret 本体だけでなく secret version が必要です
- `devforge--jwt-private-key` / `devforge--jwt-public-key`(`make generate-keys` で生成)
- `devforge--internal-secret`
- `devforge--turso-auth-token`
-- `devforge--anthropic-api-key`(DevForge Agent の Claude haiku / sonnet 用 / ADR-0010・0013)
- `devforge--stripe-secret-key` / `devforge--stripe-webhook-secret`(クレジット課金 / ADR-0012 Phase 2)
以下は条件付きで、有効化した環境だけ version を追加すれば足ります(無効なら未投入でもデプロイ・起動可能)。
- GitHub OAuth(`enable_github_oauth = true`): `devforge--github-client-id` / `devforge--github-client-secret`
-- Gemini / OpenAI(`enable_extra_llm_providers = true` / ADR-0013): `devforge--google-api-key` / `devforge--openai-api-key`
+- OpenAI(`enable_extra_llm_providers = true` / ADR-0013): `devforge--openai-api-key`
+
+Anthropic(Claude)と Gemini は Vertex AI(サービスアカウント → ADC 認証)経由のため API キーの secret は不要です(ADR-0015 で `anthropic-api-key` / `google-api-key` の注入を廃止済み)。
### 運用ルール
diff --git a/docs/design-principles.md b/docs/design-principles.md
new file mode 100644
index 00000000..dc721158
--- /dev/null
+++ b/docs/design-principles.md
@@ -0,0 +1,169 @@
+# DevForge 設計原則
+
+## このドキュメントの位置づけ
+
+- 本ドキュメントは、蓄積された ADR([索引](./adr/README.md))から**帰納的に抽出した**設計原則の言語化。先に原則があって ADR が従ったのではなく、個々の判断に繰り返し現れたパターンを原則として固定したもの。
+- **原則の改訂は ADR 経由で行う**。原則と矛盾する判断が必要になったら、本ファイルを直接書き換えるのではなく、その判断を ADR として起票し(矛盾と理由を明記)、Accepted になった時点で本ドキュメントへ反映する。
+- 新しい ADR を書くときは、テンプレートの「設計原則との関係」欄でどの原則に沿う/反するかを明示する([0000-template.md](./adr/0000-template.md))。
+
+## 原則一覧
+
+| ID | 原則 | 一言 | 代表 ADR |
+|---|---|---|---|
+| P1 | コスト最適化を第一制約にする | 個人開発規模(〜10 ユーザー)を明示的な設計入力として扱う | [0005](./adr/0005-cloudrun-single-instance.md) |
+| P2 | PII を信頼境界の外に出さない | ブラウザストレージ・外部 API・学習データ・国外リージョンに職務経歴を置かない | [0002](./adr/0002-jwt-cookie-auth.md), [0015](./adr/0015-vertex-ai-for-gemini-anthropic.md) |
+| P3 | 正本を定め、規律は機械検証で守る | SSoT を 1 箇所に置き、複製との乖離・規律の破れは CI が検知する | [0007](./adr/0007-openapi-typescript-codegen.md), [0017](./adr/0017-mutation-testing-and-slack-notifications.md) |
+| P4 | 責務を層で分離する | 機械が埋めるもの / 人間が書くもの、スキーマ / プロンプトを混ぜない | [0010](./adr/0010-devforge-agent.md), [0016](./adr/0016-github-skill-inference.md) |
+| P5 | デフォルトは決定論、LLM は対話型に限定する | 推論パイプラインはルールベース。LLM はユーザーが確認して適用する対話機能のみ | [0008](./adr/0008-remove-llm-to-rule-based-design.md), [0010](./adr/0010-devforge-agent.md) |
+| P6 | 可逆性を設計する | 撤退条件を先に書く。足さない・使われないものは撤去する | [0006](./adr/0006-tanstack-query.md), [0008](./adr/0008-remove-llm-to-rule-based-design.md) |
+| P7 | 依存は固定し、追従は自動化する | SHA / `==` 完全固定でサプライチェーンを守り、更新は Renovate に提案させる | [0014](./adr/0014-renovate-dependency-automation.md) |
+
+## P1: コスト最適化を第一制約にする
+
+### 内容
+
+「想定ユーザー数 〜10 人の個人開発」という規模を、暗黙の前提ではなく**明示的な設計入力**として扱う。可用性・スケーラビリティより先にコスト上限を制約として置き、規模が変わったときの移行条件(P6)とセットで判断する。
+
+### 根拠となった判断
+
+- [ADR-0005](./adr/0005-cloudrun-single-instance.md): Cloud Run を max-instances=1 / min-instances=0 に固定。SPOF・cold start を「個人開発フェーズでは許容する」と明記
+- [ADR-0001](./adr/0001-sqlite-gcs-backup.md): マネージド DB ではなく SQLite + GCS バックアップから開始(後に Turso へ移行)
+- [ADR-0012](./adr/0012-agent-model-switching-and-prepaid-billing.md): LLM コストをプリペイドクレジットでユーザー転嫁し、任意モデル文字列を拒否してコスト爆発を防ぐ
+- [ADR-0010](./adr/0010-devforge-agent.md) / [ADR-0013](./adr/0013-multi-provider-llm-selection.md): 用途に対して過剰なモデルを使わない(差分生成は Haiku 級で足りる)。ローカル開発は Ollama で API コストゼロ
+
+### 例外・緊張関係
+
+- 可用性と真っ向から競合する(0005 の SPOF)。ユーザー数 10 人到達・可用性要件の高まりが P6 の移行トリガーとして明文化されている
+- セキュリティ(P2)とは競合させない。コスト削減のために PII 保護を落とす判断はしていない
+
+## P2: PII を信頼境界の外に出さない
+
+### 内容
+
+職務経歴書は氏名・経歴を含む PII の塊である。**ブラウザの永続ストレージ・外部 API・モデル学習データ・国外リージョン**を信頼境界の外とみなし、PII を置かない・送らない・学習させない。
+
+### 根拠となった判断
+
+- [ADR-0002](./adr/0002-jwt-cookie-auth.md): トークンは HttpOnly Cookie。localStorage / sessionStorage / Redux store に生トークンを置かない
+- [ADR-0003](./adr/0003-redux-toolkit-persist.md): フォームキャッシュの persist 対象から PII を外す
+- [ADR-0011](./adr/0011-frontend-textlint-proofread.md): 文章校正を外部 API ではなくフロントエンド完結(Web Worker)で実装(機能自体は後に撤去)
+- [ADR-0015](./adr/0015-vertex-ai-for-gemini-anthropic.md): LLM を Vertex AI 経由にし、学習利用除外とデータ所在地(アジア圏リージョン)を担保
+
+### 例外・緊張関係
+
+- LLM 機能(P5)は「PII を外部に送らない」の例外を作る判断だった。0015 が所在地・学習除外で境界を引き直すことで両立させている
+- バックエンド DB(Turso)と Fernet フィールド暗号化は信頼境界の内側という整理
+
+## P3: 正本を定め、規律は機械検証で守る
+
+### 内容
+
+同じ情報を 2 箇所に書くなら、**正本(SSoT)を 1 つ決め、複製との乖離は CI が落とす**。人間の規律(目視同期・レビュー)に依存した整合性は必ず陳腐化する。検証できない複製は作らない。テストそのものの検出力も機械で計測する。
+
+### 根拠となった判断
+
+- [ADR-0007](./adr/0007-openapi-typescript-codegen.md): Pydantic スキーマを正本に TS 型を自動生成。codegen-drift CI が乖離で fail する
+- [ADR-0012](./adr/0012-agent-model-switching-and-prepaid-billing.md) / [ADR-0013](./adr/0013-multi-provider-llm-selection.md): 実モデル ID・料金・プロバイダはサーバー側 `model_catalog.py` が正本。クライアントはエイリアスのみ知る
+- [ADR-0017](./adr/0017-mutation-testing-and-slack-notifications.md): 行カバレッジでは測れないテストの検出力を週次ミューテーションテストで計測
+- ADR ではないが同系統: `scripts/lint-env-keys.sh`(env 名・エラーコードの drift 検知)、`scripts/lint-adr-index.sh`(ADR 索引の drift 検知)、`.claude/rules/` の scoped rules
+
+この原則の効果測定が [docs/metrics/ai-friendliness.md](./metrics/ai-friendliness.md)。「AI エージェントが不変条件を壊さず開発できるアーキテクチャ」は P3 の応用形で、機械検証があるほど人間にも AI にも壊しにくいコードベースになる。
+
+### 例外・緊張関係
+
+- 機械検証には実装コストがかかる。`docs/api.md` の環境変数表のように「drift の実害が小さい」複製は意図的に手動同期のまま残している(lint-env-keys.sh のコメント参照)
+- DRY と同じで、検証の自動化自体が過剰抽象化(P6)になり得る。Mermaid 図やテーマ分類のような「人間の編集価値が本体」の情報は機械検証しない
+
+## P4: 責務を層で分離する
+
+### 内容
+
+性質の異なる情報・制約を同じ層に混ぜない。**機械が客観的に埋められるもの**と**人間にしか書けないもの**、**機械検証可能な制約(スキーマ・コード)**と**機械検証不能な制約(プロンプト・文体)**を分け、それぞれの更新が互いを壊さないようにする。
+
+### 根拠となった判断
+
+- [ADR-0010](./adr/0010-devforge-agent.md): 「コードでテストが書ける制約はプロンプトに書かない」。構造制約はスキーマ、文体・思考方針はプロンプトへ
+- [ADR-0016](./adr/0016-github-skill-inference.md): スキルを 3 層に分離(Layer 1-2 = 機械が埋める「幅」、Layer 3 = 人間が書く「深さ」)。機械の更新が人間の記述を壊す事故を層で防ぐ
+- [ADR-0010](./adr/0010-devforge-agent.md) の DB 非更新原則も同型: Agent(提案する側)と保存 API(確定する側)の責務を分け、既存バリデーションを保存側に集約
+
+### 例外・緊張関係
+
+- 層を増やすこと自体が複雑化(P6 の「足さない」と緊張)。0016 は「双方向の事故が実際に起きうる」ことを示してから 3 層化しており、事故パターンの明示が分離の条件
+
+## P5: デフォルトは決定論、LLM は対話型に限定する
+
+### 内容
+
+バックグラウンドの分析・推論パイプラインは**ルールベース(決定論)**を第一選択とする。LLM を使うのは、**ユーザーが結果を確認してから適用する対話型機能**に限る。「LLM の出力を無確認で永続化する」経路を作らない。
+
+### 根拠となった判断
+
+- [ADR-0008](./adr/0008-remove-llm-to-rule-based-design.md): 利用見込みの薄い LLM 抽象を全撤去し、キャリア分析をルールベースへ一本化
+- [ADR-0010](./adr/0010-devforge-agent.md): LLM の再導入は「ユーザー対話型のフォアグラウンド機能」に限定。差分はフォーム state にのみ適用し、保存はユーザーの明示操作(DB 非更新原則)
+- [ADR-0016](./adr/0016-github-skill-inference.md): スキル推論(Layer 1-2)は決定論を維持。LLM が入りうるのは人間レビューが確定する Layer 3 のみ
+- [ADR-0011](./adr/0011-frontend-textlint-proofread.md): 校正も LLM ではなくルールベース(textlint)を選択
+
+### 例外・緊張関係
+
+- 「対話型なら LLM 可」の判断は品質・コスト(P1)・PII(P2)の 3 制約を同時に満たす必要がある。0012(コスト転嫁)・0015(データガバナンス)はこの原則を維持するための補強
+
+## P6: 可逆性を設計する
+
+### 内容
+
+判断には**撤退条件(「将来の移行条件」)を先に書く**。導入は「3 つ目の利用箇所が現れてから」(Rule of Three)、撤去は「使われないと分かったら躊躇なく」。休眠コード・使われない抽象を「いつか使うかも」で残さない。
+
+### 根拠となった判断
+
+- ADR テンプレートに「将来の移行条件」セクションが最初から組み込まれている
+- [ADR-0008](./adr/0008-remove-llm-to-rule-based-design.md) → [ADR-0010](./adr/0010-devforge-agent.md): 0008 が規定した再導入手続きに従って LLM を復活させた。**撤退条件を先に書く運用が実際に機能した実例**
+- [ADR-0006](./adr/0006-tanstack-query.md): TanStack Query をパイロット未実施のまま導入せず見送り
+- [ADR-0011](./adr/0011-frontend-textlint-proofread.md): 実装まで行った校正機能を、運用不要と判断して撤去
+- [ADR-0009](./adr/0009-frontend-toast-notification.md): 外部ライブラリを足さず自前 Toast(必要十分の実装)
+- [ADR-0013](./adr/0013-multi-provider-llm-selection.md): 既存抽象に無理に載せず、プロバイダ抽象の作り直しを許容
+
+CLAUDE.md の「過剰な抽象化を避ける」・`.claude/rules/common/duplication.md` の Rule of Three は本原則のコーディングレベルの表現。
+
+### 例外・緊張関係
+
+- `lifecycle { prevent_destroy = true }` のような不可逆リソースや、0015 の「シークレット destroy 後は旧リビジョンへロールバック不可」のように、可逆性を意図的に手放す場合は ADR にその旨を明記する
+
+## P7: 依存は固定し、追従は自動化する
+
+### 内容
+
+依存は**完全固定**する(GitHub Actions は SHA ピン、Python は `==`、lockfile 必須)。固定によるサプライチェーン防御と引き換えに生じる「追従されない」弱点は、**自動化(Renovate)で提案させ、人間はレビューだけする**形で補う。固定と追従を人間の記憶に頼らない。
+
+### 根拠となった判断
+
+- [ADR-0014](./adr/0014-renovate-dependency-automation.md): 完全固定運用を維持したまま Renovate を導入。CVE 対応の後追い・更新差分の肥大化を解消
+- [ADR-0017](./adr/0017-mutation-testing-and-slack-notifications.md): 新規 workflow でも Actions の SHA ピン運用を踏襲
+- [ADR-0007](./adr/0007-openapi-typescript-codegen.md): codegen ツールチェーンも固定の対象
+
+### 例外・緊張関係
+
+- 自動更新 PR の通知はノイズになりやすい。0017 の Slack チャンネル分割(`SLACK_WEBHOOK_URL_DEPS`)が運用面の補完
+
+## 原則 × ADR 対応マトリクス
+
+● = その ADR の中心的な判断軸、○ = 関係する判断軸。
+
+| ADR | P1 コスト | P2 PII | P3 正本+機械検証 | P4 層分離 | P5 決定論 | P6 可逆性 | P7 依存固定 |
+|---|---|---|---|---|---|---|---|
+| [0001](./adr/0001-sqlite-gcs-backup.md) SQLite + GCS | ● | | | | | ○ | |
+| [0002](./adr/0002-jwt-cookie-auth.md) JWT + Cookie 認証 | | ● | | | | | |
+| [0003](./adr/0003-redux-toolkit-persist.md) Redux + persist | | ● | | | | | |
+| [0004](./adr/0004-llm-provider-abstraction.md) LLM 抽象設計 | ○ | | | | | ● | |
+| [0005](./adr/0005-cloudrun-single-instance.md) single instance | ● | | | | | ○ | |
+| [0006](./adr/0006-tanstack-query.md) TanStack Query 見送り | ○ | ○ | | | | ● | |
+| [0007](./adr/0007-openapi-typescript-codegen.md) OpenAPI → TS codegen | | | ● | | | | ○ |
+| [0008](./adr/0008-remove-llm-to-rule-based-design.md) ルールベース一本化 | ○ | | | | ● | ● | |
+| [0009](./adr/0009-frontend-toast-notification.md) 自前 Toast | | | | | | ● | |
+| [0010](./adr/0010-devforge-agent.md) DevForge Agent | ○ | ○ | | ● | ● | | |
+| [0011](./adr/0011-frontend-textlint-proofread.md) textlint 校正(撤去) | ○ | ● | | | ○ | ○ | |
+| [0012](./adr/0012-agent-model-switching-and-prepaid-billing.md) モデル切替 + 課金 | ● | | ○ | | | | |
+| [0013](./adr/0013-multi-provider-llm-selection.md) マルチプロバイダ | ○ | | ● | | | ○ | |
+| [0014](./adr/0014-renovate-dependency-automation.md) Renovate | | | | | | | ● |
+| [0015](./adr/0015-vertex-ai-for-gemini-anthropic.md) Vertex AI (ADC) | | ● | | | | | ○ |
+| [0016](./adr/0016-github-skill-inference.md) スキル推論 3 層 | | | | ● | ○ | | |
+| [0017](./adr/0017-mutation-testing-and-slack-notifications.md) ミューテーションテスト | | | ● | | | | ○ |
diff --git a/scripts/lint-adr-index.sh b/scripts/lint-adr-index.sh
new file mode 100755
index 00000000..690ee111
--- /dev/null
+++ b/scripts/lint-adr-index.sh
@@ -0,0 +1,146 @@
+#!/usr/bin/env bash
+# ADR 索引(docs/adr/README.md)と ADR ファイルの drift を検知する。
+#
+# 背景:
+# ADR の一覧・関係の正本は docs/adr/README.md の索引。以前は CONTRIBUTING.md に
+# 一覧表を複製しており、ADR 追加・ステータス変更の追従漏れで陳腐化した
+# (0012〜0015・0017 の未掲載、0006 のステータス古い等)。複製は索引へ一本化した上で、
+# 「索引 ↔ 実ファイル」の整合だけを機械検証する(lint-env-keys.sh と同じ思想)。
+#
+# 検証内容:
+# (1) 見出し番号: docs/adr/NNNN-*.md のファイル名 NNNN と 1 行目 `# ADR-NNNN:` が
+# 一致するか(0011 が `# ADR-0009:` になっていた誤記の再発防止)。
+# (2) 正方向: すべての ADR ファイル(0000-template.md 除く)が索引の
+# 「全 ADR 一覧」表に載っているか(新規 ADR の索引更新忘れを止める)。
+# (3) 逆方向: 「全 ADR 一覧」表の各行のリンク先ファイルが実在するか
+# (rename / 削除時の索引残留と typo を検知する)。
+# (4) ステータス突合: 各 ADR の「## ステータス」直後の値と索引のステータス列が
+# 一致するか。加えて「現在有効な決定」表の集合が Accepted の集合と一致するか
+# (supersede / deprecate 時の索引更新忘れを止める)。
+# (5) タイトル整合: 「全 ADR 一覧」表と「現在有効な決定」表の両方に載っている ADR で、
+# タイトル列が一致するか(片方だけリネームして更新し忘れる drift を検知する)。
+#
+# 対象外(意図的):
+# - テーマ・「置き換え・関連」列・決定系統図(Mermaid): 人間の編集価値が本体で、
+# 機械検証には Markdown / Mermaid の構文解析が必要になり過剰なため。
+#
+# 正本:
+# - 一覧・関係: docs/adr/README.md
+# - ステータス: 各 ADR ファイルの「## ステータス」節
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+
+ADR_DIR="docs/adr"
+INDEX="$ADR_DIR/README.md"
+
+fail=0
+
+if [ ! -f "$INDEX" ]; then
+ echo "ERROR: $INDEX が存在しません。" >&2
+ exit 1
+fi
+
+# ── (1) ファイル名 NNNN と見出し `# ADR-NNNN:` の一致 ──────────────────────
+for f in "$ADR_DIR"/[0-9][0-9][0-9][0-9]-*.md; do
+ num=$(basename "$f" | cut -c1-4)
+ [ "$num" = "0000" ] && continue
+ heading_num=$(head -1 "$f" | sed -nE 's/^# ADR-([0-9]{4}):.*/\1/p')
+ if [ -z "$heading_num" ]; then
+ echo "ERROR: $f の 1 行目が \`# ADR-NNNN: タイトル\` 形式ではありません。" >&2
+ fail=1
+ elif [ "$heading_num" != "$num" ]; then
+ echo "ERROR: $f のファイル名($num)と見出し番号(ADR-$heading_num)が一致しません。" >&2
+ fail=1
+ fi
+done
+
+# ── 索引の 2 つの表から ADR 番号を抽出 ──────────────────────────────────────
+# セクション見出しで表を区別する(両表とも行は `| [ADR-NNNN](./file.md) | ...`)。
+index_all_rows=$(awk '/^## 全 ADR 一覧/{flag=1; next} /^## /{flag=0} flag' "$INDEX" \
+ | grep -E '^\| \[ADR-[0-9]{4}\]' || true)
+index_accepted_rows=$(awk '/^## 現在有効な決定/{flag=1; next} /^## /{flag=0} flag' "$INDEX" \
+ | grep -E '^\| \[ADR-[0-9]{4}\]' || true)
+index_accepted_nums=$(printf '%s\n' "$index_accepted_rows" | sed -E 's/^\| \[ADR-([0-9]{4})\].*/\1/' | sort -u)
+
+index_all_nums=$(printf '%s\n' "$index_all_rows" | sed -E 's/^\| \[ADR-([0-9]{4})\].*/\1/' | sort -u)
+file_nums=$(ls "$ADR_DIR" | sed -nE 's/^([0-9]{4})-.*\.md$/\1/p' | grep -v '^0000$' | sort -u)
+
+# ── (2) 正方向: ADR ファイル ⊆ 索引「全 ADR 一覧」 ─────────────────────────
+missing_in_index=$(comm -23 <(printf '%s\n' "$file_nums") <(printf '%s\n' "$index_all_nums"))
+if [ -n "$missing_in_index" ]; then
+ echo "ERROR: 次の ADR が $INDEX の「全 ADR 一覧」に載っていません:" >&2
+ printf ' - ADR-%s\n' $missing_in_index >&2
+ echo "ADR を新規作成したら索引に行を追加してください。" >&2
+ fail=1
+fi
+
+# ── (3) 逆方向: 索引の行 ⊆ ADR ファイル(リンク先の実在も確認) ─────────────
+stale_in_index=$(comm -13 <(printf '%s\n' "$file_nums") <(printf '%s\n' "$index_all_nums"))
+if [ -n "$stale_in_index" ]; then
+ echo "ERROR: $INDEX の「全 ADR 一覧」に実在しない ADR の行があります:" >&2
+ printf ' - ADR-%s\n' $stale_in_index >&2
+ echo "ADR を rename / 削除したら索引の行も追従してください。" >&2
+ fail=1
+fi
+
+while IFS= read -r link; do
+ [ -z "$link" ] && continue
+ if [ ! -f "$ADR_DIR/$link" ]; then
+ echo "ERROR: $INDEX のリンク先 $ADR_DIR/$link が存在しません。" >&2
+ fail=1
+ fi
+done </{print; exit}' "$f")
+ # 行が索引に無い場合 grep が exit 1 になる((2) が既に検知済み)ため || true で握る
+ index_status=$(printf '%s\n' "$index_all_rows" \
+ | grep -E "^\| \[ADR-$num\]" | awk -F'|' '{gsub(/^ +| +$/, "", $4); print $4}' || true)
+ if [ -n "$index_status" ] && [ "$file_status" != "$index_status" ]; then
+ echo "ERROR: ADR-$num のステータスが索引と一致しません(ファイル: '$file_status' / 索引: '$index_status')。" >&2
+ fail=1
+ fi
+
+ # (5) タイトル整合: 両表に載っている場合のみ比較(片方のみの場合は (2)(3)(4) が既に検知)
+ index_all_title=$(printf '%s\n' "$index_all_rows" \
+ | grep -E "^\| \[ADR-$num\]" | awk -F'|' '{gsub(/^ +| +$/, "", $3); print $3}' || true)
+ index_accepted_title=$(printf '%s\n' "$index_accepted_rows" \
+ | grep -E "^\| \[ADR-$num\]" | awk -F'|' '{gsub(/^ +| +$/, "", $3); print $3}' || true)
+ if [ -n "$index_all_title" ] && [ -n "$index_accepted_title" ] && [ "$index_all_title" != "$index_accepted_title" ]; then
+ echo "ERROR: ADR-$num のタイトルが索引の表間で一致しません(全 ADR 一覧: '$index_all_title' / 現在有効な決定: '$index_accepted_title')。" >&2
+ fail=1
+ fi
+
+ if [ "$file_status" = "Accepted" ]; then
+ accepted_file_nums="$accepted_file_nums$num"$'\n'
+ fi
+done
+accepted_file_nums=$(printf '%s' "$accepted_file_nums" | sort -u)
+
+not_in_accepted_table=$(comm -23 <(printf '%s\n' "$accepted_file_nums") <(printf '%s\n' "$index_accepted_nums"))
+stale_in_accepted_table=$(comm -13 <(printf '%s\n' "$accepted_file_nums") <(printf '%s\n' "$index_accepted_nums"))
+if [ -n "$not_in_accepted_table" ]; then
+ echo "ERROR: 次の Accepted な ADR が $INDEX の「現在有効な決定」に載っていません:" >&2
+ printf ' - ADR-%s\n' $not_in_accepted_table >&2
+ fail=1
+fi
+if [ -n "$stale_in_accepted_table" ]; then
+ echo "ERROR: $INDEX の「現在有効な決定」に Accepted でない ADR が残っています:" >&2
+ printf ' - ADR-%s\n' $stale_in_accepted_table >&2
+ echo "supersede / deprecate したら「現在有効な決定」から行を外してください。" >&2
+ fail=1
+fi
+
+if [ "$fail" -ne 0 ]; then
+ exit 1
+fi
+
+echo "lint-adr-index: OK(ADR ファイル ↔ 索引の存在・ステータス・見出し番号の drift なし)"