specとADRで、Claudeの「記憶喪失」を設計でカバーする ── 毎セッション記憶ゼロのAIと開発する仕組み

specs(現在の仕様)と ADR(なぜ)の二層でAIに現在地を渡す構成図

はじめに

Claude Code(AIコーディングエージェント)と開発していて最初にぶつかるのは、AIが前回の判断を覚えていないという壁だ。

セッションが変わればコンテキストはリセットされる。前回「オフライン維持のためにこの機能はこう作り直した」と決めても、次のセッションのClaudeはそれを知らない。放っておくと、せっかく直したものをまた元に戻すような提案をしてくる。

慶弔記録アプリ tsutsum の開発では、この「記憶喪失」を人間の記憶力ではなくドキュメントの設計でカバーすることにした。docs/specs/(現在の仕様)と docs/adr/(なぜそうしたか)の二層構造だ。その考え方と運用を書く。


前提:Claudeは「今の姿」も「経緯」も知らない

新しいセッションのClaudeが知らないことは、大きく2つある。

  • 今どうなっているか(現在の仕様)
  • なぜそうなっているか(過去の判断とその理由)

この2つは性質が違う。だから同じドキュメントに混ぜると、両方とも使いづらくなる。「現在の仕様」に変更履歴が混ざればノイズになり、「判断の経緯」に最新仕様が混ざれば古くなって嘘になる。

tsutsumではこれを役割で分けた。

docs/
  specs/   # 現在の仕様だけ ── 「今どう動くか」
  adr/     # 判断の記録   ── 「なぜそうしたか」

specは「今の姿」、ADRは「なぜ」。役割を混ぜない、というのが唯一かつ最重要のルールだ。


specは「差分を書かない」

spec(docs/specs/NNNN-<feature>.md)には、その機能の現在の仕様だけを書く。

やりがちなのは、変更するたびに「旧仕様は○○だったが△△に変更」と差分を書き足していくことだ。tsutsumではこれを禁止している。振る舞いを変えたら、該当specの本文を「今の姿」に書き換える。差分は追記しない。

理由はふたつある。

履歴はgitが持っている

「いつ何を変えたか」は git loggit blame が正確に覚えている。ドキュメントに二重で持つ必要はないし、二重に持てば必ずどちらかが腐る。

Claudeが古い記述に引っ張られる

これがAIと開発するうえで大きい。specに「旧仕様は○○」という記述が残っていると、Claudeはそれをまだ有効な選択肢として拾ってしまうことがある。「今の姿」だけが書いてあれば、AIは迷わずそこを現在地として実装に入れる。

specはAIに渡す一枚の現在地だと考えると、差分を消す理由が腑に落ちる。


ADRは「なぜ」を残す。覆すときも消さない

一方、判断の経緯は消してはいけない。「なぜこの技術を選んだか」「なぜこの機能をこう作ったか」を失うと、Claudeも人間も、後から同じ議論を一からやり直すことになる。

そこで docs/adr/(Architecture Decision Record)に、1判断1ファイルで理由を残す。specには結論だけを書き、経緯はADRに逃がす。

面白いのは、決定を覆すときの作法だ。旧ADRを書き換えるのではなく、新しい番号で起票して、旧ADRのステータスを「廃止」にする。

tsutsumには実例がある。

  • ADR 0005: 共有シートからアイデアを追加する(受信共有)→ 廃止
  • ADR 0006: 共有からアイデア追加を「自動登録」方式へ(オフライン維持)

最初のアプローチを試したあと、「完全オフラインを貫く」という芯に照らして作り直した。このとき0005を消していたら、「なぜ自動登録方式にしたのか」という文脈ごと消えていた。0005を廃止として残し、0006で新しい理由を書いたことで、判断の履歴そのものが読み物として残る

次のセッションのClaudeがこの2枚を読めば、「オフライン維持のためにこうなっている」という前提を最初から共有できる。


「どこまで終わったか」も規約にする

AIに任せていて曖昧になりがちなのが、完了の定義だ。「実装した」と言われても、コード上で終わっているのか、実機確認が残っているのかが分からない。

tsutsumのspecは受け入れ条件のチェックに規約を持たせている。

  • [x] = 実装完了(コードで実現済み・flutter analyze / flutter test など自動で確認できる
  • [ ] = 実機・実値・ネイティブ設定・生成アセット配置など、実装後の手動/実機確認が残る

done ステータスなのに [ ] が残っているなら、それは「コードは書けたが実機確認待ち」という意味になる。この一手間で、Claudeも人間も「あと何が残っているか」を同じ基準で見られる。


コミットメッセージが、設計判断の索引になる

運用でもうひとつ効いているのが、コミットメッセージにADR番号を書くことだ。tsutsumの git log を眺めると、こうなっている。

feat: 共有を自動登録+iOS編集フォームに(オフライン・ADR 0006)
feat: 連絡先インポート(flutter_contacts・ADR 0009)
feat: きろく/ひとのヘッダーをスクロール連動に(… ADR 0010)

コミットとADRが紐づいているので、git log がそのまま「いつ・どの判断が・どのコードに落ちたか」の索引になる。あるコードの意図を知りたくなったら、コミット → ADR番号 → 理由、とたどれる。Claudeに「この変更の背景は?」と聞くときも、この鎖があると調査が速い。


Claudeユーザへの持ち帰り

この仕組みは Flutter でも tsutsum でも本質的に関係ない。ドキュメントをAIの外部記憶として設計する、という話だ。要点は3つ。

  1. 「今の姿」と「なぜ」を分ける。 現在の仕様(spec)に履歴を混ぜない。理由(ADR)に最新仕様を混ぜない。
  2. 現在の仕様は書き換える。差分を残さない。 古い記述はAIを迷わせる。履歴はgitに任せる。
  3. 判断は消さずに積む。 覆すときも旧記録を廃止として残し、新番号で理由を書く。

Claudeは記憶しない。だが、毎回同じ場所を読ませれば、記憶しているのと同じ状態を作れる。記憶力を鍛えるのではなく、読ませる場所を設計する。それが記憶ゼロのAIと長く開発するコツだと思う。


まとめ

「AIが前回の判断を忘れる」問題は、AIの性能が上がっても構造的には残る。コンテキストは有限だし、セッションはいつか切れる。

だからこそ、人間側が現在地と経緯を渡せるドキュメント構造を持っておくと強い。tsutsumではそれが spec と ADR の二層だった。特別なツールは要らない。Markdownを2種類のフォルダに分けて、役割を混ぜないだけだ。

記憶ゼロのAIと開発している人に、ひとつの型として参考になれば。


次回は、毎セッション先頭で読まれる CLAUDE.md そのものをどう書くか ── Claudeの「推論の深さ」まで設計する話を書く。

← 記事一覧へ戻る