Claude Code を使い始めて、ハーネス(CLAUDE.md、skill、hook、settings.json など)が少しずつ育ってきました。最初は、バージョンやコマンド、ディレクトリ構成を CLAUDE.md に書いただけの素朴な状態から始まって、運用していくうちに、それなりの構造になってきた。

ある時、ふと「ハーネスの1段階目はクリアした、では次は何を育てるんだろう?」と考え込みました。最低限の環境情報は伝わっている、でも、そこから先がよく分からない。今回は、この問いをきっかけに、実際にハーネスを育ててみた過程を整理してみます。

ハーネスの4機能

以前ハーネスについて書いた記事で、ハーネスには4つの機能があると整理しました。

  • Inform(情報を伝える): バージョン、ライブラリ、ディレクトリ構成、用語など
  • Constrain(制約をかける): やってほしくないことを禁止する
  • Verify(検証する): 結果が正しいか機械的にチェックする
  • Correct(修正する): 違反を直す、または直させる

「1段階目はクリアした」というのは、Inform の基本部分(バージョン、コマンド、ディレクトリ構成)が伝わっている状態のこと。ここから、Inform の階層を深めたり、Constrain・Verify・Correct を足したりしながら、ハーネスを育てていきます。

Claude Code のメモリ機構

ここで一つ、調べていて知った話があります。Claude Code には、ユーザーが書く CLAUDE.md と、Claude が自動で書く Auto Memory の2つのメモリ機構があります(Auto Memory は v2.1.59 以降)。

  • CLAUDE.md: ユーザーが書く、ルールや指示
  • Auto Memory: Claude が自動で書く、訂正や好みからの学習
  • skill: タスク別の知識(ユーザーが書く)
  • hook: 機械的な制約・観測(ユーザーが書く)
  • settings.json: ツール権限など(ユーザーが書く)

これだけの機構があると、「何をどこに書くか」の判断が必要になります。各機構には、優先順位の違いもあります。

機構 優先順位 設計思想
skill enterprise > personal > project 個人優先
settings.json managed > local > project > user project優先(localで個人補完)
CLAUDE.md project > user project優先
hook マージ(両方発火) 重ねる

興味深いのは、skill だけ個人優先で、他は project 優先という点。これは設計思想の違いで、skill は「個人の専門性を尊重する」、CLAUDE.md や settings.json は「チームの規約をベースにする」という方針が見えます。

そして、もう一つ重要な軸があります。モデル依存 / モデル非依存です。

  • CLAUDE.md、skill の本文: モデルが読んで解釈する(モデル依存)
  • skill のロード順位、hook、settings.json: ハーネス本体が機械的に処理(モデル非依存)

強制力が強いものほど、モデル非依存になります。「指示は確率的、仕組みは決定的」という、以前から書いてきた話の構造的な裏付けです。

事例で見るハーネスの育ち方

事例1: 用途と主な機能の名称

最初に試したのは、CLAUDE.md にアプリの用途と主な機能の名称を追加することでした。「アプリの用途と主な機能を伝えたい」とAIに言ったら、用途と機能の名称が CLAUDE.md に書かれました。

ただし、AIは機能の説明までは書かないと判断していました。「機能の説明は実装見れば分かる」と。これは、ハーネス設計の重要な原則だと思います。実装から読める情報は CLAUDE.md に書かない。コードと CLAUDE.md の両方を更新するコストを避け、CLAUDE.md が肥大化するのも防ぐ。

メールや Slack の扱いを書きたいと伝えたら「基本方針」のセクションに、機能の使われ方や外部連絡の話を書きたいと伝えたら「ドメイン知識」のセクションに、AIが自動で振り分けてくれました。人間が「何を書きたいか」を伝え、AIが「どこに書くか」を判断する。役割分担が綺麗に機能した瞬間でした。

事例2: 「毎回 Spec 作成して」が CLAUDE.md に

ある時、「毎回 Spec 作成して」とAIに伝えたら、CLAUDE.md にこんな内容が追加されました。

## ロジック修正時の Spec 作成義務

コントローラ・モデル・サービス等のロジックを修正・新規実装した場合、
ユーザーが明示的に不要と言わない限り、必ず対応する spec を同時に作成・修正する。

- 修正対象の spec ファイルが存在する場合: 新規ロジックをカバーするテストケースを追加する
- spec ファイルが存在しない場合: 新規で作成する
- 「spec はあとで」等の明示があれば従う

ポイントは、例外条件が明示されていること。「ユーザーが明示的に不要と言わない限り」「『spec はあとで』等の明示があれば従う」── 100%守らせようとするのではなく、逃げ道を作っている。

これは、以前書いた「判断と責任は人間が持つ」と整合します。基本は守るが、人間が判断したときは省略できる。

事例3: 実装後の必須手順

以前から CLAUDE.md に追加してあった、実装後の必須手順がこれ。

## 実装後の必須手順(省略禁止)

### Spec 作成・修正後

1. SimpleCov でカバレッジ確認: `make rspec spec/...` → `coverage/index.html` で
   未カバー行・ブランチを確認 → 全てカバーされるまでテストを追加
2. RuboCop で lint チェック: `bundle exec rubocop spec/...` → エラーがなくなるまで修正

### 一般実装後の lint チェック

実装・修正完了時は必ず lint を実行する:
- Ruby/Rails: `bundle exec rubocop <ファイルパス>`(自動修正は `-a`)
- フロントエンド: `yarn lint:fix`

lint エラーが残った状態で完了報告しない。

ここで面白いのは、完了の定義を明示していること。「lint エラーが残った状態で完了報告しない」「全てカバーされるまでテストを追加」「エラーがなくなるまで修正」── 単に「実行する」だけでなく、条件が満たされるまで続けることが定義されている。

AIは「動いた」を「完了」と判断しがちなので、人間が定義する完了の基準を明示することで、完成度のギャップを埋めようとしている。これは、以前書いた「完成度のギャップ」への対策です。

事例4: shared_examples の見落とし

skill にこんな規約を書いていました。

単一呼び出し & 呼び出し元も単一実行の shared_examples はインライン化する。
次元名は context 名に反映

これがあっても、リファクタで it_behaves_like の片方を削除して残り1箇所になったケースで、AIはインライン化しなかった。指摘したら、こう追加されました。

リファクタで it_behaves_like の片方を削除した時も同じ。
残った呼び出しが1箇所になったら必ずインライン化する
(複数呼び出しを前提に作った shared_examples が1箇所だけになるケースは見落としやすい)

ここで気付いたのは、AIが「指摘対応で満足してしまう」ということです。指摘された shared_examples は直してくれるが、指摘の背後にある原則(「リファクタで結果的に単一になった場合も同じ」)を、自発的に他に適用しない。

これは、新人と似ています。指摘されたことは直すけど、「他にも同じパターンがないか」を自分で探さない。シニアエンジニアなら自然にやることを、AIも新人もやらない。

自信過剰な新人」という比喩が、別の角度で見えてきた瞬間でした。AIは「指示の文字通り」には動くが、「指示の背後にある原則」を抽出して他に適用するのは苦手。だから、見落としやすいケースを skill に明示的に書くしかない。

経験で「自然に気付く」シニアエンジニアの暗黙知を、AIに渡すには、明示的に書くしかない。これも、ハーネスを育てる重要なパターンです。

事例5: settings.json の allow/ask 設計

ツール権限の設定も育てました。「必ず聞いてほしいもの」と「手間なので聞かずに実行して欲しいもの」を伝えたら、AIが叩き台を作ってくれました。「他にもあるか」「漏れてそうな観点は?」と促すと、より丁寧な設計に。

個人共通(~/.claude/settings.json)の設定がこれ。

"  permissions": {
    "deny": [
      "Read(.env*)",
      "Read(~/.ssh/**)",
      "Read(~/.aws/**)"
    ],
    "ask": [
      "Bash(rm *)",
      "Bash(mv *)",
      "Bash(git commit *)",
      "Bash(git push *)",
      "Bash(git reset *)",
      "Bash(git checkout -- *)",
      "Bash(git checkout -b *)",
      "Bash(git restore *)",
      "Bash(git branch -D *)",
      "Bash(git branch -d *)",
      "Bash(git branch -M *)",
      "Bash(git clean *)",
      "Bash(bundle exec rails db:drop *)",
      "Bash(gh pr create *)",
      "Bash(gh pr edit *)"
    ],
    "allow": [
      "Skill(*)",
      "Bash(ls *)",
      "Bash(cat *)",
      "Bash(diff *)",
      "Bash(mkdir *)",
      "Bash(grep *)",
      "Bash(find *)",
      "Bash(tail *)",
      "Bash(git status *)",
      "Bash(git remote -v && git branch --show-current)",
      "Bash(git stash list)",
      "Bash(git log *)",
      "Bash(git diff *)",
      "Bash(git show *)",
      "Bash(git blame *)",
      "Bash(git add *)",
      "Bash(git pull)",
      "Bash(git pull *)",
      "Bash(git fetch *)",
      "Bash(git rev-list *)",
      "Bash(git branch --show-current)",
      "Bash(git branch -r *)",
      "Bash(source ~/.rvm/scripts/rvm && rvm use * && make rspec *)",
      // ...
      "Bash(gh pr view *)",
      "Bash(gh pr diff *)",
      "Bash(gh pr list *)",
      "Bash(gh api *)",
      "Bash(gh release *)"
    ]
  }

設計の軸は3つ。読み取り系は allow(git status、grep、find、gh pr view など)、検証系は allow(rubocop、rspec など)、不可逆な書き込み系は ask(rm、git push、git commit、git reset など)。

そして、deny セクションもあります。.env ファイル、SSH 鍵、AWS 認証情報を、読み取ることさえ拒否する。allow/ask だけでなく、完全拒否も用意されている。これは、機密情報の漏洩を防ぐ最も強い防御層です。

プロジェクト側(.claude/settings.json)では、これに加えてプロジェクト固有のコマンドを allow に追加しています。

settings.json の permissions(deny、ask、allow)は配列なので、両方の設定がマージされる。グローバル側の防御(deny、ask)は、プロジェクトでもそのまま適用される(プロジェクト側で明示的に allow に上書きしない限り)。個人の防御を全プロジェクトのベースにしつつ、プロジェクト固有のコマンドを追加していく設計です。

補足: 多層防御の限界

ただ、この設計にも抜け道があります。allow リストにはワイルドカード(`*`)が含まれるので、AIが工夫すれば確認をすり抜けるコマンドを生成できる可能性がある。

これを設計したAIに「抜け道があるのでは?」と聞いたら、「抜け道があって、AIを信用するしかない」と返してきました。正確な認識です。完全な技術的防御は難しく、どこかで「AIが悪意を持って動かない」という信頼に頼るしかない。その信頼は、論理的な保証ではなく、訓練・経験・コミュニティの観察から得られる現実的な判断です。

これは、以前書いた「全振りしない、延長線上で進める」の延長です。AI コーディング特有の話ではなく、何かを使うときの基本かもしれません。OS でもライブラリでも、どこかで「信頼」に頼っている。

事例6: skill とメモリの参照ログ

前 skills の検証について書いた記事で、PreToolUse hook で skill の参照ログを取る話を書きました。その流れで、メモリの参照ログも取れるんじゃないかと思って試してみました。下記はAIに書いて貰ったものです。

{
  "matcher": "Read",
  "hooks": [
    {
      "type": "command",
      "command": "mkdir -p ~/.claude/logs && jq -r 'select(.tool_input.file_path | test(\"/.claude/memory/\")) | \"\\(now | localtime | strftime(\"%Y-%m-%dT%H:%M:%S\"))\\tmemory=\\(.tool_input.file_path | split(\"/\") | last)\\tsession=\\(.session_id)\"' >> ~/.claude/logs/memory-reads.log 2>/dev/null || true"
    }
  ]
}

これは、Read ツール経由でメモリファイルが読まれたときに、ログに記録する仕組みです。test("/.claude/memory/") でディレクトリをフィルタしているので、メモリ以外のファイル読み込みは記録しない。実際に動かしてみると、こんなログが溜まります。

2026-05-16T13:22:55 memory=MEMORY.md session=37f32d30-...
2026-05-16T14:38:08 memory=feedback_write_spec_with_every_change.md session=678f62d5-...
2026-05-16T14:59:32 memory=feedback_branch_creation_pull_first.md session=678f62d5-...
2026-05-16T14:59:43 memory=feedback_branch_name_intent_over_implementation.md session=678f62d5-...

ここで見えるのは、Auto Memory が動的に動いていること。セッション開始時に MEMORY.md(インデックス)が読まれて、タスクが進むにつれて、関連する個別ファイルが読み込まれる。短時間で複数のファイルが連続して読まれることもある。

セッション開始時に一覧が読まれて、必要に応じて個別ファイルを呼び出す── これは、全部一気にロードするのではなく、必要な分だけ取り出す設計です。context window を効率的に使うための工夫が、ここでも見えてきます。

事例7: Auto Memory の中身

Auto Memory が動いていることが分かったので、中身も見てみました。MEMORY.md には、こんな項目が並んでいました(抽象化した一部例)。

  • 「ロジック修正時は毎回 spec も書く」
  • 「ブランチ作成前は必ず pull する」
  • 「ブランチ名は実装内容より目的(PRタイトル相当)を反映する」

これらは、まさにシニアエンジニアが新人に教えることです。Claude が訂正やフィードバックから学習して、自分で記録している。

ここで気付いたのは、「ロジック修正時は毎回 spec も書く」が CLAUDE.md と Auto Memory の両方にあること。同じ内容が二箇所に存在しているので、context が無駄に圧迫されているかもしれない。

整理の判断軸を AI に聞いてみたら、こんな答えが返ってきました。

  • 常時ロードして守ってほしいルール → Auto Memory(または CLAUDE.md)
  • 特定の skill が呼ばれたときだけ適用する手順 → skill 側だけに書く
  • 両方の文脈で必要 → 両方残す価値あり
  • 完全に重複していて役割分担の理由がない → どちらか一方に統合

例えば「コミットメッセージは WHY を書く」は git-commit skill が呼ばれたときだけ適用できれば十分なので、Auto Memory からは消す方向が自然。逆に「ブランチ作成前は pull する」は、ブランチ作成を skill 経由でない会話でやるケースもあるので、Auto Memory と skill の両方に残す価値がある。

こうやって、各機構の役割を意識しながら整理していく。これは、ハーネスを育てる過程で出てくる、整理の課題です。

そして、Auto Memory の存在は、以前書いた運用「学習できることはありましたか?」を、Claude Code が自動化している側面もあります。AIが「指摘対応で満足する」特性を、Auto Memory が部分的に補正しているとも見えます。ユーザーが訂正したことを、Claude が自動でメモして、次から守ろうとする。完璧ではないけど、仕組みでAIの弱点を補強する実例です。

事例8: Auto Memory の保存先を変更した話

Auto Memory はデフォルトで ~/.claude/projects/<project>/memory/ に保存されます。プロジェクトごとに分かれる仕組みです。

ただ、自分は同じリポジトリを複数 clone して並列開発することがあります。デフォルトのままだと、clone ごとに別々のメモリが溜まる状態になる。同じリポジトリの作業なのに、メモリが共有されない。一方の clone で覚えた学習が、もう一方の clone では活きない。

困っていたので、AIに相談しました。すると、autoMemoryDirectory 設定で保存先を変更できると教えてくれました。~/.claude/settings.json に設定を追加して、既存のメモリも新しいディレクトリに移動。

  "autoMemoryDirectory": "~/.claude/memory",

これで、複数 clone でも同じディレクトリ(~/.claude/memory/)を共有するようになりました。並列開発でも、メモリの蓄積が活きる。

これも、ハーネスを育てる過程の一つです。デフォルトの仕様が自分の使い方に合わないことが分かったときに、AIに相談して、設定を変える。AIは設定変更だけでなく、既存データの移行まで対応してくれた。

ちなみに、autoMemoryDirectory は user 設定(~/.claude/settings.json)または local 設定(.claude/settings.local.json)でしか設定できません。プロジェクト設定(.claude/settings.json、commit される)では設定不可。これは、別リポジトリから保存先をリダイレクトされないようにするセキュリティ上の配慮です。設定の制限にも、設計の意図が込められています。

事例9: rspec skill の factorybot 分割

以前 skills の検証について書いた記事で、29KB の rspec skill を「現状で困っていないなら分割は急がない、整理が先」と判断しました。

その後、改めて「rspec のスキルが大きくないか」とAIに聞いたら、判断が少し進化していました。「現状は不要だけど、factorybot は分けてもいいかも」と。

そこで、実際に分けてみました。rspec 本体はテストの書き方や describe/context の使い方、別の skill として factorybot を切り出して、factory の定義、traits、sequence、associations を独立させた。
ここで見えるのは、「急がないが、機が熟したら分ける」という自然な流れです。

  • 段階1: 完全に不要(skill が小さい、独立性なし)
  • 段階2: 現状で困っていない、急がない
  • 段階3: 分けてもいい(独立性が高い部分が見えてきた) ← ここ
  • 段階4: 分けるべき(困りごとが顕在化)

以前の議論では段階2、今回は段階3に進化しました。「機が熟す」とは、内容を見直したり、新しい情報が増えたり、AIの判断基準が深まったりして、「分けるメリットが見えてきた」状態のこと。

そして、全体ではなく特定部分を分けるという判断が興味深い。「rspec 全体を分けるか分けないか」の二択ではなく、「factorybot だけ独立性が高いから分ける」という部分的な分割。これも、ハーネスを育てる原則として優れています。

事例10: ハーネスそのものを Git 管理する

ハーネスが育ってくると、バージョン管理したくなる気がしてきました。CLAUDE.md、skill、settings.json、Auto Memory── すべて、運用の中で育てた資産です。マシンを切り替えたとき、または失ったときに、復元できる必要がある。

AIに相談したら、~/.claude/ を Git リポジトリにする方法を提案してくれました。GitHub のリポジトリ作成も含めて、AIが対応。最初に作ってもらった .gitignore がこれ。

# NOTE: deny-all + allow パターン。skills/ memory/ CLAUDE.md のみ track する為
/*
!/.gitignore
!/CLAUDE.md
!/memory/
!/skills/
# memory/ skills/ 配下の OS/エディタゴミだけは除外
**/.DS_Store

deny-all + allow パターンで、すべて除外して、必要なものだけ明示的に許可。これは、settings.json の allow/ask と同じ思想です。「安全な方をデフォルトにする」── 知らないファイルが混入しても、Git に追加されない。

ただ、運用しているうちに「hook の設定も Git 管理したい」「agents も入れたい」と思い始めて、AIに追加で相談しました。最終的にはこうなりました。

# NOTE: deny-all + allow パターン。個人設定だけ track して sensitive を取り込まない為
/*
!/.gitignore
!/CLAUDE.md
!/agents/
!/commands/
!/hooks/
!/keybindings.json
!/memory/
!/output-styles/
!/plugins/
!/settings.json
!/skills/
# NOTE: plugins/ 配下は blocklist.json のみ track(キャッシュ・lock 等は除外したい為)
/plugins/*
!/plugins/blocklist.json
# 配下の OS/エディタゴミは除外
**/.DS_Store

これも、ハーネスを育てる過程の実例です。最初は最小限から始めて、必要になったら追加する。AIが「何を含めるか、何を除外するか」を提案してくれて、deny-all のパターンで安全に管理する。

そして、plugins/ の扱いが繊細です。plugins/* で全部除外しつつ、!/plugins/blocklist.json だけ track。「設定は欲しいが、キャッシュや lock は要らない」という判断。これも、AIが状況を考慮して提案してくれた部分です。

ハーネスを Git 管理することで、履歴も残るようになりました。「いつ、何を追加したか」が見える。これは、シリーズで書いてきた「運用しながら育てる」の、もう一段の発展形かもしれません。

AIと協働でハーネスを育てる

これらの事例を振り返ると、共通するパターンが見えてきます。

人間とAIの役割分担:

  • 人間: 何を達成したいかを伝える(「毎回 Spec 作成して」「危ないものは聞いて」)
  • AI: どこに書くか、どう書くかを判断する(CLAUDE.md か skill か、構造、例外条件)
  • AI: 抜けを探す(「他にもあるか」「漏れてそうな観点は」と促すと、補完してくれる)

この役割分担で、ハーネスは自然に育っていきます。人間が叩き台を全部書く必要はないし、AIに全部任せる必要もない。

そして、AIには目的化を避ける配慮が見えることがあります。skill の分割を聞いたときの「今分けたい動機は?」、機能の説明を CLAUDE.md に書かなかった判断、「現状は不要だけど、〇〇は分けてもいい」という段階的な判断。これらは、ハーネス作りが目的化しないようにという配慮そのものです。

「指摘対応で満足する」AIの特性

shared_examples の事例で見たように、AIは「指摘対応で満足してしまう」特性があります。指摘されたことは直すけど、背後にある原則を他に適用するのは苦手。

これは、新人エンジニアと似た特性です。指摘されたことだけを直して、他に同じ問題がないか探さない。シニアエンジニアになると、指摘の背後にある原則を抽出して、他のコードにも適用する。

AIには、この「シニア化」が起きにくい。経験で「自然に気付く」ようにはならない。だから、人間が促す必要があります。

  • 指摘した後に「他にも同じパターンがないか確認して」と促す
  • 見落としやすいケース」を skill に書き出す
  • 同じ指摘を2回したら、skill か CLAUDE.md に追加する

これは、Auto Memory が部分的に補正してくれる側面もあります。Claude が訂正を自動で学習するので、次回からは外しにくくなる。完璧ではないけど、仕組みでAIの弱点を緩和する実例です。

何を書く・何を書かないか

ハーネスを育てる過程で、もう一つ重要なのは「何を書かないか」の判断です。
「機能の説明は実装見れば分かる」とAIが判断したように、実装から読める情報は CLAUDE.md に書かない。これを守らないと、CLAUDE.md が肥大化して、メンテナンス負担が増え、本当に重要な情報が薄まる。

書くべきこと:

  • 意図(なぜこの実装にしたか)
  • ドメイン知識(ビジネスルール、暗黙の前提)
  • 設計判断の経緯
  • 推奨されない選択肢(deprecated にしたもの)

書かないこと:

  • 関数の動作(コードを読めば分かる)
  • API の引数(型定義を見れば分かる)
  • データベースのスキーマ(migration を見れば分かる)

これは、ドキュメントの基本原則でもあります。「コードに書けないこと」をドキュメントに書く。AI コーディング特有の話というより、ドキュメント設計の常識です。

まとめ

「ハーネスの1段階目はクリアした、次に何を育てるか」という問いから始めて、実際に育ててみた過程を振り返りました。見えてきたのは、ハーネスは対話で段階的に育つということです。

  • 人間が「何を達成したいか」を伝える
  • AIが「どこに、どう書くか」を判断する
  • 「他にもあるか」「漏れてそうな観点は」と促すと、AIが補完する
  • 機が熟したら、部分的に分ける
  • 重複や古い情報は、定期的に整理する

そして、ハーネスの各層で共通する設計思想もあります。deny-all + allow パターンは、settings.json の allow/ask でも、.gitignore でも同じように使われていました。「安全な方をデフォルトにする」「明示的に許可したものだけ通す」── これは、ハーネス設計の一貫した原則として、各層で繰り返し現れます。

ハーネスには原理的な限界もあります。多層防御を作っても、ワイルドカードの抜け道が残る。完璧な制御は不可能で、どこかで AI を信頼するしかない。これも、以前から書いてきた「全振りしない、延長線上で進める」の延長です。

Claude Code 自体も、ハーネスの一部を自動化する方向に進んでいます。Auto Memory は、AIが自分で学習を記録する仕組み。これは、シリーズで書いてきた「運用しながら育てる」を、Anthropic が仕組みで支援している形です。

それでも、ユーザーが明示的に育てる価値は残ります。Auto Memory が全部はカバーしないし、CLAUDE.md や skill で構造化することには別の意味がある。自動化された部分と手動の部分を、適切に組み合わせる。

結局、これもいつものエンジニアリングの延長な気がしています。最小から始めて、対話で育てる。目的化させない。完璧な防御は諦めて、現実的なバランスで進む。何を書く・何を書かないかを判断する。AI コーディング特有の話というより、何かを設計するときの基本かもしれません。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です