ドキュメント管理ワークフロー
このページがガイドの本体。4 層構造・front matter・鮮度判定・日々の運用・AI 連携・CI までを通して説明する。
- 1. 全体像
- 2. 4 つの層
- 3. front matter
- 4. scope の書き方
- 5. status のライフサイクル
- 6. 鮮度判定の仕組み
- 7. 日々の運用
- 8. AI から使う
- 9. CI で守る
- 10. コマンド早見表
- 11. lint エラーコード早見表
1. 全体像
docs-kit の運用は、コード変更 1 回ごとに回る小さなループになる。
要点は 2 つだけ。
- 着手前に
which— 関連文書を機械に選ばせ、その文書だけを読む。全部読まない - 完了時に
verify— 実装と突き合わせた文書に「いま突き合わせた」という印を付ける
この 2 つを守っていれば、あとは CI が破綻を検出してくれる。
2. 4 つの層 — 何をどこに書くか
文書は 4 層のいずれかに置く。層は増やせない。
| 層 | 役割 | 寿命 | 書き換え | 鮮度判定 |
|---|---|---|---|---|
adr/ | 決定記録。なぜその方式を選んだか、何を捨てたか | 永久 | しない (追記のみ) | scope があれば対象 |
spec/ | 現在の仕様。いまどう動くのが正しいか | 実装と同期 | する | 対象 |
arch/ | アーキテクチャ。構成・依存方向・データフロー | 実装と同期 | する | 対象 |
plan/ | 一時的な作業計画。手順とゴール | 使い捨て | する | 常に対象外 |
どの層に書くか
層ごとの書き方の指針
adr/ — 決定記録
1 決定 1 ファイル。「状況 → 決定 → 帰結」の 3 節で書く。採用しなかった案とその理由を必ず残す。これが無いと、半年後に同じ議論を最初からやり直すことになる。
方針が変わったときは既存の ADR を書き換えず、新しい ADR を追加して古いほうに status: superseded と superseded_by: ADR-00NN を付ける。
spec/ — 現在の仕様
「いま何が正しいか」だけを書く。経緯は ADR にリンクする。scope を必ず書く — scope の無い spec は鮮度判定の対象外になり、docs-kit を使う意味がほとんどなくなる。
arch/ — アーキテクチャ
パッケージ構成・依存方向・データフロー。多くのプロジェクトでは arch/overview.md の 1 本で足りる。
plan/ — 作業計画
一時的な足場。マージしたら削除する。verified_at を持てず、鮮度判定にもかからない。進行状態 (done/doing のチェックボックス) は書かない — Issue トラッカーへ。
3. front matter — 文書のメタデータ
ファイル配置と id
管理対象は <root>/<layer>/ 直下の *.md だけ。サブディレクトリは走査されないので、画像などの資産置き場に自由に使える。<root>/INDEX.md は生成物。
docs/
├── INDEX.md ← 生成物。手で編集しない
├── adr/
│ ├── 0001-markdown-as-source-of-truth.md → ADR-0001
│ └── 0002-four-layer-structure.md → ADR-0002
├── spec/
│ ├── cli.md → SPEC-cli
│ └── staleness.md → SPEC-staleness
├── arch/
│ └── overview.md → ARCH-overview
├── plan/
│ └── phase1-core-cli.md → PLAN-phase1-core-cli
└── assets/ ← 走査されない。画像等はここに置ける
└── diagram.png
id とファイル名は 1 対 1 で対応させる。ずれると lint エラー (E004)。
| 層 | id 形式 | ファイル名 | 例 |
|---|---|---|---|
adr/ | ADR- + 4 桁数字 | NNNN-<slug>.md | ADR-0003 ↔ adr/0003-sha-based-staleness.md |
spec/ | SPEC-<slug> | <slug>.md | SPEC-cli ↔ spec/cli.md |
arch/ | ARCH-<slug> | <slug>.md | ARCH-overview ↔ arch/overview.md |
plan/ | PLAN-<slug> | <slug>.md | PLAN-phase1 ↔ plan/phase1.md |
slug は [a-z0-9]+(-[a-z0-9]+)* (小文字英数とハイフン)。id はリポジトリ内で一意。
フィールド一覧
---
id: SPEC-editing-model # 必須
title: 編集モデル # 必須
status: current # 必須
scope: # 任意 (spec/arch は推奨)
- src/core/editing/**
depends_on: [ADR-0003] # 任意
verified_at: 8f3a1c2e0b7d4a91c3f6e2d8b5a704c1e9f3d602 # 任意 (40 桁)
x-owner: platform-team # 任意 (自由)
---
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
id | string | ✔ | 上表の形式。リポジトリ内で一意 |
title | string | ✔ | 空文字は不可 |
status | enum | ✔ | current / draft / stale / superseded の 4 値のみ |
scope | string[] | — | この文書が責任を持つコード範囲。リポジトリルート相対・/ 区切りのグロブ |
depends_on | string[] | — | 前提とする文書の id。存在しない id を指すとエラー |
verified_at | string | — | 40 桁 hex の完全 commit SHA。docs-kit verify が書き込む |
superseded_by | string | superseded 時 | 後継文書の id |
x-* | 任意 | — | プロジェクト固有の拡張。ツールは解釈せず、そのまま保持する |
x- 以外の未知フィールドは lint エラー (E007)。 チーム固有のメタデータ (担当・Jira キー等) を足したいときは x- を付ける。
本文の規約
- front matter の直後に
# <id>: <title>の見出しを置くことを推奨 (lint は強制しない) - 要約フィールドは無い。
whichや MCP が返す 200 文字の要約は、本文の最初の非見出し・非空段落から自動で導出する。要約を別フィールドに持つと本文とドリフトするため - したがって、本文の 1 段落目に「この文書は何か」を書くと検索性が上がる
4. scope の書き方
scope は docs-kit の精度を決める唯一のパラメータ。ここだけは丁寧に書く価値がある。
scope:
- src/core/editing/** # ディレクトリ配下すべて
- src/api/editing.ts # 単一ファイル
- "src/**/*.editing.ts" # 名前パターン
- 構文は doublestar (
**サポート) - リポジトリルート相対・
/区切り (Windows でも/) - 先頭
/、./、..を含むパターンは不正 (E012) - 判定対象は git 管理下のファイルのみ
よくある失敗
| 失敗 | 症状 | 直し方 |
|---|---|---|
広すぎる (src/**) | 何をしても stale になる。やがて誰も見なくなる | 文書が実際に説明している範囲まで絞る。1 文書 1 モジュールが目安 |
| 狭すぎる (単一ファイルのみ) | 隣のファイルが変わっても気づけない | 「この文書の記述が嘘になる変更が起きうる範囲」まで広げる |
| テストを含めている | テストを直すたびに stale | docs.config.yaml の ignore に **/*_test.go 等を入れる |
| 存在しないパスを書いた | lint 警告 W002。判定は broken (dead-scope)。draft の間は対象外 | パスのタイポを直す。まだコードを書いていないだけなら draft のままでよい (下記) |
codeRoots の外を指している | lint 警告 W003 | codeRoots を実態に合わせるか、scope を直す |
判断の基準は 「このファイルが変わったとき、この文書を読み直す必要があるか?」。あるなら scope に含める。
コードより先に文書を書く
draft の間は、scope にまだ存在しないファイルを書いてよい。W002 は出ない。これは意図した挙動で、draft の scope は「これから書くコードの宣言」だからである。
status: draft
scope:
- Sources/Ingest/** # まだ書いていない。これでよい
グリーンフィールドで作業開始プロトコルが機能するのはこれによる。docs-kit which Sources/Ingest/Pipeline.swift は、そのファイルが存在する前から担当する spec を返す。「which が返したものを読め」と指示された AI に、読むものが存在する状態になる。
verify で current に昇格した瞬間に W002 と dead-scope が両方発火するので、本物のタイポは実害を持つ瞬間に必ず表面化する。W003 (codeRoots の外) は draft の間も出続ける。
scope を持つ文書が 1 つも無い場合、which は空を返しつつ docs/INDEX.md を指すヒントを出す。「読むものが無い」ように黙って見えることはない。
5. status のライフサイクル
| status | 意味 | AI の扱い | 誰が動かすか |
|---|---|---|---|
draft | 実装と突き合わせていない | 参考情報。コードを正とする | new / adopt が付与 |
current | 突き合わせ済み。仕様として有効 | 仕様として信じる | verify が昇格させる |
stale | 古いと分かっている | 参考情報。コードを正とする | 人間が手で宣言 |
superseded | 後継文書に置き換えられた | 読まない | 人間が手で宣言 (superseded_by 必須) |
押さえておくポイント。
scopeを持つ文書のdraft → currentはverifyを通す。 手でcurrentに書き換えてもverified_atが無ければ lint E008 で落ちるので、実質的に verify が強制されるscopeを持たない文書 (多くの ADR) は手でcurrentにしてよい。 鮮度保証の対象外なので verify する意味がないstaleは人間による宣言。docs-kit staleコマンドの判定結果は front matter に書き戻されない。コマンドの判定 (一時的な事実) と、文書の status (人間の意思表示) を混ぜないためsupersededは終端。 verify するとエラーになる
6. 鮮度判定の仕組み
判定対象
判定されるのは status: current かつ scope が非空かつ verified_at を持つ文書だけ。それ以外は skipped (判定せず、exit code にも影響しない)。
draft/stale/superseded— 鮮度保証を宣言していないので対象外plan層 — 常に対象外currentなのに scope や verified_at が無い文書 — lint が E008 で捕まえる
判定の流れ
全文書の判定後、exit code は最悪値になる (broken > stale > ok)。
broken の原因と対処
broken は「判定の前提が壊れている」状態で、文書の中身以前の問題。
| 理由コード | 何が起きたか | 対処 |
|---|---|---|
unknown-sha | verified_at の SHA がリポジトリに無い | shallow clone なら fetch-depth: 0 にする。履歴が消えたなら再 verify |
not-ancestor | rebase / force push で検証時点が現在の履歴から外れた | 実装と突き合わせ直して再 verify |
dead-scope | scope グロブがどのファイルにも一致しない (コードが消えた/移動した) | scope を実態に合わせる。機能ごと消えたなら文書を superseded にする |
missing-dependency | depends_on の参照先 id が存在しない | 参照先のタイポを直すか、参照を外す |
stale の原因
| 理由コード | 何が起きたか |
|---|---|
code-changed | scope 内のコードが verified_at 以降に変わった (最も一般的) |
premise-superseded | depends_on で前提にしていた文書が superseded になった |
scope-drift | リネームで、ファイルが黙って scope の外へ出た |
premise-superseded は地味に効く。「コードは変わっていないが、前提にしていた決定が覆った」という、人間が最も見落としやすい種類の腐り方を拾う。
自動で吸収される差分
「scope 内が変わったら常に stale」では運用が回らないので、明らかに仕様に影響しない差分は判定から除外する。
| 除外規則 | 吸収される差分 |
|---|---|
docs.config.yaml の ignore グロブ | テスト・スナップショットなど、仕様に影響しない変更 |
| verify コミットの除外 | 実装 + 文書更新 + verify を同一コミットで行ったときの自己言及的な差分 |
| tree diff との積 | 範囲内で加えて範囲内で打ち消された変更 (net-zero) |
| 純リネーム (R100) の吸収 | 内容変更を伴わないファイル移動 |
「verify コミットの除外」は、そのコミットが verified_at 行を書き換えているかで判定する。「文書を最後に触ったコミットを一律で除外」ではないので、文書だけ直して verify しなかったコミットはちゃんと差分として残る。
未コミット変更 (dirty)
判定は常に HEAD 基準。scope 内に未コミットの変更があると、判定結果に dirty 警告が添えられる (レベルは変わらない)。verify も同様に警告する — HEAD に入っていない変更を「検証済み」にはできないため。
出力例
docs-kit stale
SPEC-editing-model stale code-changed: 3 files changed since 8f3a1c2
- src/core/editing/model.ts (2 commits)
- src/core/editing/undo.ts
ARCH-overview ok (verified at 8f3a1c2)
SPEC-mcp-tools skipped (draft)
ok 1 / stale 1 / broken 0 / skipped 1
判定行の末尾に [uncommitted changes inside scope] が付くことがある (dirty 警告)。
CI や AI 向けには JSON も出せる。
docs-kit stale --format json
{
"results": [
{
"id": "SPEC-editing-model",
"path": "docs/spec/editing-model.md",
"level": "stale",
"verified_at_short": "8f3a1c2",
"reasons": [{ "code": "code-changed", "detail": "3 files changed" }],
"evidence": {
"files": ["src/core/editing/model.ts", "src/core/editing/undo.ts"],
"commits": ["...", "..."],
"absorbed": [{ "kind": "rename", "from": "src/a.ts", "to": "src/b.ts" }]
},
"dirty": false
}
],
"summary": { "ok": 1, "stale": 1, "broken": 0, "skipped": 1 },
"exit_code": 1
}
evidence に根拠 (変更ファイルとコミット) が入るので、AI はそのまま diff を読みに行ける。
7. 日々の運用
7.1 作業開始時 — which で読む文書を絞る
これから触るパスを渡すと、関連文書が返る。
docs-kit which src/core/editing/model.ts
primary:
SPEC-editing-model current 8f3a1c2 docs/spec/editing-model.md
related:
ADR-0003 current - docs/adr/0003-undo-stack.md
- primary — 引数パスが
scopeグロブに一致した文書 - related — primary の
depends_onを 1 ホップ展開したもの
複数パス・ディレクトリも渡せる (ディレクトリは <dir>/** として展開される)。
docs-kit which src/core/editing src/api/editing.ts
一致が無ければ空出力で exit 0 (「関連文書なし」は正常な情報であってエラーではない)。
返ってきた文書だけを読む。 これが docs-kit の検索性側の価値で、docs/ 全体を読ませないための仕組み。
7.2 作業中 — status を信じる基準
| status | どう扱うか |
|---|---|
current | 仕様として信じてよい |
draft / stale | 参考情報。実装コードを正とする |
superseded | 読まない (superseded_by の先を読む) |
current の文書と実装が矛盾していたら、勝手にどちらかへ合わせない。 矛盾を報告して判断を仰ぐ。docs-kit が検出できるのは「scope 内が変わったこと」までで、「文書と実装のどちらが正しいか」は人間の判断になる。
7.3 作業完了時 — 更新して verify する
verify は front matter の verified_at を HEAD に更新し、draft / stale なら current に昇格させる。
docs-kit verify SPEC-editing-model ARCH-overview
SPEC-editing-model: verified_at → 8f3a1c2
ARCH-overview: verified_at → 8f3a1c2 (promoted to current)
verify が書き換えるのは front matter の該当行だけ。 YAML の再シリアライズはしないので、x- フィールドもコメントも本文もバイト単位で保たれる。
前提条件を満たさないとエラー (exit 3) になる。
- id が存在すること
scopeを持つことplan層でないことstatus: supersededでないこと
7.4 コミットの粒度
判定の仕組み上、verify コミットに無関係な実装変更を同居させると、その変更は検出されない (verify コミットは差分計算から除外されるため)。
推奨する形:
コミット 1: 実装の変更
コミット 2: 設計書の更新 + verify ← ここに他の実装変更を混ぜない
1 コミットにまとめる場合も、そのコミットに入れる実装変更は、更新した文書が説明している範囲のものだけにする。
また、マージコミット経由でのみ入った変更は判定から落ちるため、squash マージを推奨する。
8. AI から使う (Claude Code)
MCP サーバ
claude mcp add docs-kit -- docs-kit mcp
登録は PATH 上の docs-kit を指すので、バイナリを差し替えても再登録は不要。セッションを開き直せば新しいツール定義が反映される。
提供されるツールは 4 つ。
| ツール | 対応する CLI | 用途 |
|---|---|---|
docs_which | which | パス → 読むべき文書の逆引き |
docs_stale | stale | 鮮度判定 |
docs_verify | verify | 突き合わせ済みの宣言 (front matter のみ) |
docs_new | new | テンプレートから新規文書を作成 |
このサーバは本文を返さないし書かない。 返すのは絶対パス・メタデータ・200 文字の要約だけ。本文の読み書きは AI 自身の Read / Edit ツールが担当する。おかげで diff 表示も許可プロンプトも部分編集もそのまま使える。
CLI との違いで実用上効くのは 2 点。
docs_staleは既定でskippedの文書を結果に含めない (件数だけsummaryに出る)。判定対象外の文書が結果の大半を占めるため、これだけでペイロードが大きく減るdocs_verifyは複数 id を受け、件別に結果を返す。前提条件を満たさない id があっても残りは処理を続けるので、AI は失敗した分だけ自己修復できる
CLAUDE.md 管理ブロック
docs-kit init が CLAUDE.md に挿入するブロック。全リポジトリで一字一句同じ内容が入る。
<!-- docs-kit:begin v1 -->
## docs-kit protocol
Design docs live under docs/ (adr = decision records, spec = current specification, arch = architecture, plan = temporary work plans).
Before starting work:
1. Run `docs-kit which <path you are changing>` to identify the relevant documents, and read only those
2. Trust documents with `status: current` as specification. Treat draft / stale as reference material and trust the implementation code instead
3. If a current document contradicts the implementation, do not quietly pick a side — report the contradiction and ask
When finishing work:
1. If the specification changed, update spec/. Record new design decisions as an ADR with `docs-kit new adr <slug>` (never rewrite an existing ADR — supersede it instead)
2. Run `docs-kit verify <id>` for every document you reconciled against the implementation
3. Confirm `docs-kit lint && docs-kit stale && docs-kit index --check` all pass before calling the work done
Conventions:
- Do not record progress state (done/doing) in docs. That belongs in an issue tracker
- docs/INDEX.md is generated. Never edit it by hand (regenerate with `docs-kit index`)
<!-- docs-kit:end -->
- マーカーの
v1はブロック仕様のバージョン。initを再実行すると、このブロックだけが最新版に置換される - ブロック外の CLAUDE.md には一切触れないので、既存の記述と共存できる
スラッシュコマンド
init は .claude/commands/ に 2 つのコマンドも置く。
| コマンド | 用途 |
|---|---|
/docs-verify | 作業完了時の締め手順 (spec 反映 → verify → lint/stale 確認) |
/docs-catchup | stale / broken になった文書を実装と突き合わせて解消する |
実際の読み分け
docs-kit 自身の開発セッションで実測した挙動。
docs_whichを 1 回呼んで primary 3 + related 12 = 15 文書が返り、本文を読んだのは 4 文書。残り 11 件はタイトルと要約だけで「今回は関係ない」と判断でき、開かずに済んだ- ただし 読むと決めた 4 文書はいずれも全文が必要だった。200 文字の要約は「読むか読まないか」の判定には十分だが、「何が書いてあるか」には足りない
- まだ存在しないパスを混ぜても静かに無視される。 「これから作る予定のパス」を着手前に投機的に渡せるのは実用上効く
9. CI で守る
docs-kit init が生成するワークフロー。
name: docs-kit
on:
pull_request:
push:
branches: [main]
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # stale は全履歴を必要とする
- name: Install docs-kit
run: |
curl -fsSL "https://downloads.noop.co.jp/docs-kit/${DOCS_KIT_VERSION}/docs-kit_linux_amd64.tar.gz" | tar -xz
sudo install -m 755 docs-kit /usr/local/bin/docs-kit
env:
DOCS_KIT_VERSION: v1.0.0
- run: docs-kit lint
- run: docs-kit stale
- run: docs-kit index --check
fetch-depth: 0 は必須。 shallow clone だと verified_at の SHA が履歴に無く、全文書が unknown-sha で broken になる。
3 つのコマンドの役割は分かれている。
| コマンド | 見るもの | git 履歴 |
|---|---|---|
lint | front matter のスキーマ違反 | 不要 |
stale | 実装との鮮度のずれ | 必要 |
index --check | INDEX.md が最新か (gofmt 方式) | 不要 |
導入直後で stale がたくさん出る場合は、段階的に厳しくするとよい (既存プロジェクトへの導入 参照)。
10. コマンド早見表
docs-kit init 雛形 + config + CLAUDE.md ブロック + CI を生成
docs-kit adopt 既存 docs に front matter スタブを付与 (status: draft)
docs-kit lint front matter のスキーマ検証
docs-kit stale [id...] 鮮度判定
docs-kit index [--check] INDEX.md を生成 (--check は CI 用の同期検証)
docs-kit which <path...> パス → 関連文書の逆引き
docs-kit new <layer> <slug> テンプレートから新規文書を作成 (--title)
docs-kit schema front matter のスキーマを出力 (フィールド / enum / 層ごとの規則)
docs-kit verify <id...> verified_at を HEAD に更新し current へ昇格
docs-kit mcp MCP サーバを stdio で起動
schema は config も git も読まないので、文書が 1 つも無いリポジトリを含め、どのディレクトリからでも答えられる。
共通フラグ:
| フラグ | 説明 |
|---|---|
-C <dir> | 実行ディレクトリを変更 (git と同様) |
--format text|json | lint / stale / which / schema の出力形式。既定は text |
--version | バージョン表示 |
--force | init のみ。生成 CI と .claude/commands を上書き |
exit code:
| code | 意味 |
|---|---|
| 0 | 成功 (警告のみを含む) |
| 1 | 検出あり (lint エラー / stale) |
| 2 | broken 検出 (stale のみ) |
| 3 | 実行エラー (config 不正・git 不在・引数エラー等) |
出力パスはすべてリポジトリルート相対・/ 区切り。
設定ファイル
docs.config.yaml はリポジトリルート直下に 1 本だけ。これが設定の全面積。
schemaVersion: 1 # 必須。現在は 1 のみ
root: docs # 任意。層ディレクトリの親。既定 "docs"
codeRoots: [src] # 任意。コードのルート群。既定 ["src"]
ignore: # 任意。stale 判定で無視するグロブ。既定 []
- "**/*.test.ts"
- "**/__snapshots__/**"
| キー | 用途 |
|---|---|
schemaVersion | 互換性管理。未知の値は exit 3 |
root | 層ディレクトリ (adr/ 等) の親 |
codeRoots | lint W003 (scope が域外を指す警告) の基準 |
ignore | stale の変更集合から除外するグロブ (scope 一致後に適用) |
未知のトップレベルキーはエラー。config に x- 名前空間は無い。設定が壊れた状態で lint/stale の結果を返すと誤った green を出すため、config 不正はすべて exit 3 で即座に落ちる。
11. lint エラーコード早見表
エラー (exit 1):
| コード | 内容 |
|---|---|
| E001 | front matter が無い、または YAML として解析不能 |
| E002 | 必須フィールド欠落 (id / title / status) |
| E003 | id の形式不正 (層プレフィックス・文字種) |
| E004 | id とファイル名の不整合 |
| E005 | id の重複 |
| E006 | status が enum 外 |
| E007 | 未知のトップレベルフィールド (x- 以外) |
| E008 | status: current かつ scope ありなのに verified_at が無い |
| E009 | verified_at が 40 桁 hex でない |
| E010 | status: superseded なのに superseded_by が無い |
| E011 | depends_on / superseded_by の参照先 id が存在しない |
| E012 | scope グロブの構文エラー (絶対パス・.. を含む場合も) |
警告 (exit 0):
| コード | 内容 |
|---|---|
| W001 | plan 層の文書に verified_at がある (無意味) |
| W002 | scope グロブが git 管理下のファイルに 1 件も一致しない。status: draft の間は報告しない |
| W003 | scope が codeRoots の外を指す (タイポの疑い) |
lint は git 履歴に依存しない静的検証のみを行う。SHA の解決可能性など履歴に依存する検証は stale の管轄。
次に読む
- 設計の背景 → コンセプトと設計思想
- 新規プロジェクトに導入する → プロジェクト初期設定
- 既存の設計書を取り込む → 既存プロジェクトへの導入