ADR-0051: docs/_config.json nav 未登録の .md ファイル検出 lint を追加
- Status: Accepted
- Mode: Standard
- Scope: platform
- Kruchten Type: Existence/Property
- Implementation Status: In Progress
- 起案者: [email protected]
- 起案日時 (JST): 2026-05-18 23:54
- 承認日時 (JST): 2026-05-19
- Deciders: [email protected] (単独)
1. コンテキスト
1.1 背景 (Background)
新規 .md を docs/ 配下に追加した際、docs/_config.json への nav 登録漏れに気づかずビルド対象外のまま放置されるケースが構造的に発生する。CLAUDE.md に「新しい .md ファイル追加時は必ず docs/_config.json の nav 配列に登録」と運用ルールは明記されているが、機械チェックがないため人間/Claude の意識頼りであり、同種の漏れが再発するリスクが構造的に残存している。
1.2 現状 (Current State / As-Is)
実例 (2026-05-18 セッション中に発覚):
- 2026-05-16: PR #796 で
ref_smb_solo_accountant_jtbd_hypotheses.mdを main マージしたが nav 未登録 - 影響期間: 2026-05-16 (mtime) → 2026-05-18 (登録 PR #827) = 2 日間
- ユーザーがリンク要求するまで誰も気づかず Cloudflare Pages 上で 404 状態
- 現状の docs ビルド対象 .md は 459 ファイル / リポジトリ内 .md 総数とは差分あり (調査時点で構造的 orphan が残存している可能性 — 初回 lint 実行で確定)
1.3 課題 (Problem)
PR #796/#827 型事故は 2 日間 404 状態を発生させた実害がある。さらに Jr 引き継ぎコスト試算 (2026-10 入社予定 1 名):
- 引き継ぎ説明
30 分 × 質問 5 回 = **2.5 時間**を毎回繰り返す (属人運用継続) - vs. lint 導入後: nav 登録手順を Jr に「lint が教えてくれる」と説明 5 分 + 自動 fail で学習 → 累計 ~10 分
機械チェック不在 = 属人運用 = Jr 入社 (2026-10) で崩壊リスクが構造的に存在。
1.4 制約・要件 (Constraints & Requirements)
- 既存進行中 PR をブロックしないグレースピリオド設計が必要 (移行期間 2 週間)
- 初期導入工数は Standard 想定の 5-8 時間レンジ内
- GitHub Actions 無料枠 (2,000 分/月) 内で運用
- 外部ライブラリ不要 (Node.js + JSON parse のみ)
- allowlist による例外管理が可能であること
1.5 目標 (Goals / To-Be)
scripts/docs-nav-lint.mjs を新設し、3 ルール (R1〜R3) を CI 必須チェックとして強制することで PR #796/#827 型事故をゼロ化する。Non-Goals: R4-R6 (プレフィックス規約整合 / 階層深度違反 / 番号重複) は将来拡張とし、本 ADR では実装しない。
2. 決定
scripts/docs-nav-lint.mjs を新設し、3 ルール (R1〜R3) を実装、CI (GitHub Actions) で必須チェックとして実行する。具体:
- R1 (orphan-md):
docs/**/*.mdのうち_config.jsonの nav 配列に登録されていないファイルを検出。初期は WARN、2 週間の grace period 後に FAIL に昇格 (allowlist 設定可:CLAUDE.md/README.md/_template.md等) - R2 (broken-nav-entry):
_config.jsonのfileフィールドが指す実体ファイルが存在しない場合 FAIL - R3 (duplicate-nav-entry): 同一ファイルが複数 nav エントリに登録されている場合 FAIL
配置: .github/workflows/docs-nav-lint.yml で PR 時に実行、npm run docs:lint でローカル実行可能化。本 ADR は ADR-0042 (Prompt Lifecycle 管理) と同等の運用ガードレール拡張の位置付け — ADR-0042 が「プロンプト本文の SSoT を KV に置き fallback を hardcoded で持つ + 機械チェックは別途」というパターンで品質を担保しているのに対し、本 ADR は「文書の SSoT を _config.json (nav) に置き、機械チェック (lint) を CI で強制 + 運用ルールを CLAUDE.md に明記」する 2 層構造で品質を担保する。
3. 検討した代替案 (Alternatives Considered)
案 A〜D 評価軸テーブル (明示比較)
| 評価軸 | 案A CI lint | 案B build warn | 案C 何もしない | 案D 包括的 lint |
|---|---|---|---|---|
| CI gated? | ✅ | ❌ | ❌ | ✅ |
| 再発防止性 | ◎ 機械強制 | △ ログ流れる | × | ◎+ より厳格 |
| 初期導入工数 | ~6h | ~1h | 0 | ~15-20h |
| allowlist 整備コスト | 中 (初回 1h、現状 orphan 数 nav 比 5-15 件想定) | 低 (なし) | 0 | 高 (R4-R6 規約整合分含む) |
| 誤検知リスク | 低 (3 ルール明確、規則ベース) | n/a | n/a | 中 (規約整合は揺れる、F.X.NN プレフィックス変更等で誤発火) |
| 起案者手戻り頻度 | 月 1-2 件想定 (FAIL は明示ガード) | 0 (ブロックなし) | 0 | 月 5-10 件想定 (規約違反検出が多い) |
| Jr 引き継ぎ | ○ 自動ガード | △ ログ依存 | × 完全属人 | ○ 自動ガード |
| 拡張余地 | ◎ R4〜R6 段階追加可 | △ | × | × 既に MVP 過剰 |
- 案 A (採用): CI lint 新設 + 3 ルール R1-R3。機械的に再発防止、CI ベルトコンベアに乗る、誤検知リスク低、段階拡張可能。
- 案 B: ローカルビルド warn のみ — 不採用理由: ローカルビルドのログは流れて見過ごされやすく、CI gate していないとすり抜ける (案 A の劣化版)。
- 案 C: 何もしない (属人運用継続) — 不採用理由: PR #796/#827 で 1 度発生済、Jr 入社で属人運用は崩壊する想定。
- 案 D: 包括的 lint (R1-R6 一気導入) — 部分採用: 案 A の R1-R3 を MVP とし、R4 (プレフィックス規約
F.X.NN整合性) / R5 (階層深度違反) / R6 (番号重複) は将来拡張とする。一気に作ると初回手戻り過多 + allowlist 整備コスト過大。
4. コスト試算
実装工数 (Standard 想定の 5-8 時間に収まる)
scripts/docs-nav-lint.mjs実装: ~3h (R1 list diff / R2 file existence / R3 dup detection、Node.js + JSON parse のみ、外部ライブラリ不要).github/workflows/docs-nav-lint.yml作成: ~0.5h (既存 adr-lint.yml をテンプレ流用)npm run docs:lintスクリプト追加: ~0.1h (package.json 1 行)- 初期 allowlist 整備: ~1h (現状 orphan を grep して allowlist 一括追加、想定 5-15 files)
- 運用ガイド (
docs/_internal/05_how-to/nav-lint-guide.md) 執筆: ~1h (Jr 引き継ぎ可能化) - 合計: ~5.6h ≈ 6h (Standard 想定の中央値)
運用コスト (継続)
- CI 実行: GitHub Actions 無料枠で十分。lint 実行 < 30s × 月 ~20 PR = 月 ~10 分 (Actions 無料枠 2,000 分/月の 0.5%)
- allowlist メンテ: .md 構造変更時のみ (年 1-2 回想定、各 ~10 分)
- 撤退条件モニタリング: 月初 5 分 (
gh run list1 コマンド)
Jr 引き継ぎコスト削減効果
- 削減見込み: 引き継ぎ説明
2.5h → ~10 分 = **2 時間の削減** (1 回切り、ROI は半年で回収)
5. 影響 (Consequences)
5.1 正の影響 (Good)
- ドキュメント nav 漏れの再発防止 (機械チェック化、PR #796/#827 型事故ゼロ化)
- PR レビュー時の「nav 登録漏れ確認」観点が自動化され人間レビュアーの認知負荷削減
- 将来 Jr (2026-10 入社予定) が引き継ぐ際の品質ガードレール (引き継ぎコスト ~2.5h → ~10 分)
5.2 負の影響 (Bad)
- lint 失敗で PR がブロックされ起案者の修正手戻りが発生 (案 D 一気導入を避け 3 ルールに絞ることで月 1-2 件想定に抑制)
- 例外管理 (lint 対象外ファイルの allowlist) のメンテコスト (初回 1h、以降 .md 構造変更時のみ)
5.3 中立・トレードオフ (Neutral / Trade-offs)
- 既存 nav 未登録ファイル多数 (PDF /
_assets// dev/_quarantine/ 等) が初回実行で大量検出 → 初回 PR で allowlist 込みで導入 (下記 grace period 設計で対処)
進行中 PR の救済 (Grace Period 設計)
- 初回 PR: 既存の全 nav 未登録 .md を allowlist に一括登録 (現状凍結、初回 lint 実行でカウント確定後
scripts/docs-nav-lint.mjs --add-current-orphansで自動生成) - 移行期間 (2 週間): R1 を WARN-only で運用 → 起案者・レビュアーの慣熟期間、PR ブロックなし
- 2 週間後: R1 を WARN → FAIL に切り替え (
.github/workflows/docs-nav-lint.ymlのseverity: errorへの 1 行変更) - 既存進行中 PR が allowlist 範囲外の orphan を持つ場合は [skip-nav-lint] ラベルで bypass 可 (後述ロールバック §)
6. 検証可能な完了条件
scripts/docs-nav-lint.mjsが R1〜R3 を実装、以下 6 テストケースで動作確認:- R1 OK:
docs/_test_sample.mdを作成し nav 未登録 → WARN 出力確認 - R1 NG: 同 file を
_config.jsonnav に登録 → WARN 出力なし確認 - R1 allowlist マッチ:
CLAUDE.md(allowlist 登録済) → WARN 出力なし確認 - R2 OK: nav に存在しないファイルパス指定 → FAIL 確認
- R3 OK: 同一 file パスを 2 つの nav エントリに登録 → FAIL 確認
- 全 OK: 現状の docs/ 全体で R1/R2/R3 lint pass (allowlist 整備後)
- R1 OK:
.github/workflows/docs-nav-lint.ymlで PR 必須チェック (status check に表示確認)npm run docs:lintでローカル実行可能 (exit code 0/1 で CI 互換)- 初回 PR で allowlist (現状の例外ファイル群) を明示登録
docs/_internal/05_how-to/nav-lint-guide.mdで Jr 引き継ぎ可能化 (上記コマンド・撤退手順を網羅)
Confirmation (準拠確認 / Fitness Function)
本 ADR の決定は実装そのものが fitness function として機能する自己検証構造を持つ:
- 検証手段:
- 自動検証 (主):
.github/workflows/docs-nav-lint.ymlで PR 毎にscripts/docs-nav-lint.mjsを実行、R1/R2/R3 違反を検出。GitHub Actions の status check として PR UI に常時表示 - ローカル検証:
npm run docs:lintで開発者がコミット前にチェック可能 (exit code 0/1) - 撤退条件モニタリング: 月初に
gh run list --workflow=docs-nav-lint.yml --status=failure --created-after=$(date -v-1m +%Y-%m-%d) --json conclusion | jq 'length'で手戻り件数を集計 (BSD date / macOS 構文。Linux/CI ではdate --date='1 month ago'に置換)
- 自動検証 (主):
- 実行頻度:
- PR 作成・更新時 (自動、毎回必須)
- 月 1 回の撤退条件レビュー (起案者が月初に実施、5 分)
- Review After (2026-11-18 / 2027-05-18) で総合再評価
- 違反時の対応:
- R1 WARN (移行期間中、〜2026-06-01): PR template チェックボックスで起案者の自認を強制、レビュアーが status check 黄色マークを確認
- R1 FAIL / R2 / R3 (2026-06-01 以降): PR マージブロック → 起案者が
_config.json更新 or allowlist 追加 or[skip-nav-lint]ラベル付与で bypass - 撤退条件抵触 (月 5 件超 × 2 ヶ月連続): R1 を WARN-only に格下げ判断、別 ADR で再評価
7. 撤退条件 (Rollback Plan)
R1 (orphan-md) 撤退条件
以下のいずれかが発生したら R1 を WARN-only に格下げ or 撤去:
- 起案者の手戻り頻度が月 5 件超
- 計測方法:
gh run list --workflow=docs-nav-lint.yml --status=failure --created-after=$(date -v-1m +%Y-%m-%d) --json conclusion | jq 'length'を月初に実行 (or scripts/docs-nav-lint-stats.mjs を別途用意し集計自動化) - 5 件超を 2 ヶ月連続 → 撤退判断
- 計測方法:
- allowlist メンテコストが nav 登録手数を上回る (本末転倒)
- 計測: allowlist 編集 PR 数 ≥ nav 新規エントリ PR 数 を 1 ヶ月継続
- 同種の漏れ検出に Cloudflare Pages ビルドエラー等の代替メカニズムが整備された場合
R2 / R3 撤退
R2 / R3 は撤退しない (FAIL のみで運用、誤検知リスクが低い、機械的にチェック可能な仕様レベル)。
CI 撤去手順 (実施判断後)
| 対象 | 具体手順 | 影響範囲 |
|---|---|---|
| GitHub Actions workflow | .github/workflows/docs-nav-lint.yml を .yml.disabled にリネーム | 1 コミットで CI 無効化、再起動容易 |
| npm script | npm run docs:lint は npm script に残存 | ローカル任意実行のみ可 |
| 最終 PR | 撤退時の最終 PR にレビュアー sign-off 必須 | 撤退根拠の記録保持 |
ブロック中 PR の救済手順
- 当該 PR に
[skip-nav-lint]ラベル付与 → workflow がif: !contains(github.event.pull_request.labels.*.name, 'skip-nav-lint')で条件付きスキップ - 緊急時の allowlist 一括追加:
npm run docs:lint -- --add-current-orphans→ 現存 orphan を allowlist (.docs-nav-lint-allowlist.json等) に一括追記
R1 WARN 見落とし対策
- GitHub Actions の status check として表示 (PR UI で必ず見える、緑/黄/赤マーク)
- PR template (
.github/pull_request_template.md) に「nav-lint WARN を確認した」チェックボックスを追加 → 別 PR で対応
8. 半年〜1 年後の負債化リスク・Review After
Review After (再評価期限)
- 2026-11-18 (6 ヶ月後): 撤退条件閾値 (手戻り月 5 件) の実態と R4-R6 拡張可否を判断
- 2027-05-18 (1 年後): 想定外の運用パターン累積を再評価、規約変更の必要性確認
R4-R6 拡張起動条件
- 起動条件 (a): R1-R3 で月 3 件以上の事故検出が 2 ヶ月連続 (オペレーション疲弊サイン)
- 起動条件 (b): Jr 入社 (2026-10) 後の品質改善ニーズが明示的に表面化 (Jr または代表取締役氏からの要望)
- 起動タイミング: 上記条件を満たした時点 or Review After (2026-11-18) のいずれか早い方
負債化リスク
- 案 D の R4-R6 (規約整合チェック) を将来 MUST にすると、F.X.NN プレフィックス変更時に大量の手戻りが発生 → 段階導入 + grace period を必ず踏襲
- allowlist の規模膨張: 上限 30 件を目安、超えたら nav 構造そのものの見直しを起案 (allowlist が肥大化する = 「nav 登録すべきだが運用上できないファイル群」の増加 = 設計上の歪み)
- gh CLI 依存: 撤退条件計測コマンドが gh に依存、将来 GitHub Actions API 直叩きへの移行余地
参照 (References)
- 関連 ADR:
- ADR-0042 (Prompt Lifecycle 管理) との具体整合: ADR-0042 は「プロンプト本文の SSoT を KV に置く + fallback を hardcoded で持つ + 変更時は同一 PR で両方更新」という 「機械的に検証可能な SSoT + ドキュメントによる運用ガード」 のパターンを確立。本 ADR は同じパターンを「文書 nav の SSoT =
_config.json+ 運用ガード = CLAUDE.md + 機械チェック = docs-nav-lint」に展開、運用ガードレールの整合 - RQ-051 (Lint Rule Reference 文書構造) — 別 ADR、並行依存ではない。RQ-051 採択後の規約に従って本 lint のドキュメント形式を整える想定だが、現時点では非ブロッキング
- ADR-0042 (Prompt Lifecycle 管理) との具体整合: ADR-0042 は「プロンプト本文の SSoT を KV に置く + fallback を hardcoded で持つ + 変更時は同一 PR で両方更新」という 「機械的に検証可能な SSoT + ドキュメントによる運用ガード」 のパターンを確立。本 ADR は同じパターンを「文書 nav の SSoT =
- 関連 PR/Issue: PR #796 / PR #827 — 本起案の動機となった具体事故事例
- 外部資料: -