1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

チームでClaude Codeを導入して3ヶ月 — CLAUDE.md標準化・レビュー運用・ガバナンス設計の全記録

1
Posted at

結論: チームでClaude Codeを使うなら「ガバナンス設計」が9割

個人で使うClaude Codeは最高です。でもチームに展開した瞬間カオスになりました。

3ヶ月かけてたどり着いた答えはシンプルです。CLAUDE.mdの階層分離・コスト可視化・レビュープロセスの再設計——この3本柱を整備すれば、チーム全体でAIコーディングの恩恵を安全に受けられます。

この記事では、5人チームで3ヶ月間運用して確立したガバナンス設計の全記録を公開します。

環境・前提条件

項目 内容
チーム規模 バックエンド3名 + フロントエンド2名
Claude Code プラン Max(5人分)
主要技術スタック TypeScript / Next.js / NestJS / PostgreSQL
リポジトリ管理 GitHub(モノレポ構成)
CI/CD GitHub Actions
導入期間 2025年3月〜6月(約3ヶ月)

1. チーム導入で起きた3つのカオス

導入初月に発生した問題を正直に共有します。

カオス①: CLAUDE.mdの属人化

各メンバーが独自のCLAUDE.mdを書き始め、コーディング規約の解釈がバラバラになりました。あるメンバーは「テストは必ずVitest」と書き、別のメンバーは「Jestで書く」と指定。Claude Codeが生成するコードのスタイルがメンバーごとに全く異なる状況に陥りました。

カオス②: コスト青天井

「便利だから」とトークン消費を気にせず使った結果、初月の請求額が想定の3倍に。特にリファクタリング系の大規模プロンプトを繰り返したメンバーのコストが突出していました。

カオス③: レビュー崩壊

AI生成コードがPRに大量に流れ込み、レビュアーが「どこを重点的に見ればいいかわからない」状態に。差分が500行を超えるPRが日常化し、レビュー時間が倍増しました。

2. CLAUDE.mdのチーム標準テンプレート — 共通ルール層とプロジェクト層の分離設計

この問題を解決するために、CLAUDE.mdを4階層に分離しました。

各層の役割

配置場所 Git管理 内容例
Global ~/.claude/CLAUDE.md 日本語応答、基本的な応答スタイル
Team リポジトリルートの CLAUDE.md コーディング規約、テストフレームワーク指定、命名規則
Project apps/*/CLAUDE.md API設計方針、DB命名規則、特定ライブラリの使い方
Personal .claude/local/CLAUDE.md 個人の作業メモ、実験的設定

Team層テンプレート(実際に使っているもの)

# チーム共通 CLAUDE.md

## コーディング規約
- 言語: TypeScript(strict mode)
- フォーマッター: Biome(`biome check --write` で自動修正)
- テスト: Vitest を使用。Jest は使わない
- 命名規則: 変数・関数は camelCase、型は PascalCase、定数は UPPER_SNAKE_CASE

## コミットルール
- Conventional Commits に従う(feat: / fix: / refactor: / test: / docs:)
- 1コミットの変更は1つの論理的単位に限定する

## 禁止事項
- any 型の使用禁止(やむを得ない場合は // TODO: fix any コメント必須)
- console.log のコミット禁止(logger モジュールを使用)
- node_modules や .env をコミットしない

## テスト方針
- 新規関数には必ずユニットテストを書く
- テストファイルは対象ファイルと同階層に `*.test.ts` として配置
- カバレッジ目標: 80%以上

## PR作成時の指示
- PRのdescriptionに「変更理由」「影響範囲」「テスト方法」を記載する
- AI生成コードには `🤖` ラベルを付ける

ポイント: Team層のCLAUDE.mdはPRでレビューを通して変更する運用にしています。チームの合意なく勝手に規約を変えられない仕組みです。

3. Skills共有リポジトリの運用

Claude CodeのCustom Skills.claude/commands/配下のMarkdownファイル)をチームで共有するために、専用リポジトリを構築しました。

ディレクトリ構成

claude-team-skills/
├── commands/
│   ├── review-pr.md          # PRレビュー支援
│   ├── create-test.md        # テスト自動生成
│   ├── refactor-function.md  # 関数リファクタリング
│   ├── create-api-endpoint.md # API エンドポイント生成
│   └── debug-error.md        # エラー調査支援
├── CHANGELOG.md
├── README.md
└── scripts/
    └── sync-skills.sh        # 各リポジトリへの同期スクリプト

同期スクリプト

#!/bin/bash
# sync-skills.sh: Skills共有リポジトリから各プロジェクトへ同期

SKILLS_REPO="git@github.com:your-org/claude-team-skills.git"
TARGET_DIR=".claude/commands"

echo "📦 Syncing team skills..."

# 一時ディレクトリにclone
tmp_dir=$(mktemp -d)
git clone --depth 1 "$SKILLS_REPO" "$tmp_dir" 2>/dev/null

# commandsディレクトリを同期
mkdir -p "$TARGET_DIR"
cp "$tmp_dir/commands/"*.md "$TARGET_DIR/"

# クリーンアップ
rm -rf "$tmp_dir"

echo "✅ Skills synced to $TARGET_DIR"

CI連携(GitHub Actions)

# .github/workflows/sync-skills.yml
name: Sync Claude Skills
on:
  repository_dispatch:
    types: [skills-updated]
  schedule:
    - cron: '0 9 * * 1'  # 毎週月曜9時に同期

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Sync skills
        run: bash scripts/sync-skills.sh
      - name: Create PR if changed
        uses: peter-evans/create-pull-request@v6
        with:
          title: "chore: sync Claude team skills"
          body: "Claude Team Skills の最新版を同期しました"
          branch: chore/sync-claude-skills

Skillsの変更もPRベースで管理することで、「誰かが勝手にプロンプトを変えてチーム全体の出力が変わった」という事故を防いでいます。

4. コスト管理: メンバー別トークン消費ダッシュボードの構築

Claude Codeの使用状況を可視化するために、簡易的なダッシュボードを構築しました。

アーキテクチャ

Claude Code の使用ログを収集し、メンバー別・プロジェクト別のコストを可視化します。

// cost-tracker.ts: Claude Code使用ログのパーサー・集計スクリプト

import { readdir, readFile } from "fs/promises";
import { join } from "path";

interface UsageEntry {
  member: string;
  date: string;
  inputTokens: number;
  outputTokens: number;
  model: string;
  project: string;
}

interface CostSummary {
  member: string;
  totalInputTokens: number;
  totalOutputTokens: number;
  estimatedCost: number;
  sessionCount: number;
}

// Claude Codeのセッションログディレクトリからデータを収集
// ログは各メンバーが日次でエクスポートする運用
async function parseUsageLogs(logDir: string): Promise<UsageEntry[]> {
  const files = await readdir(logDir);
  const entries: UsageEntry[] = [];

  for (const file of files.filter((f) => f.endsWith(".json"))) {
    const content = await readFile(join(logDir, file), "utf-8");
    const log = JSON.parse(content);
    entries.push({
      member: log.member,
      date: log.date,
      inputTokens: log.usage?.input_tokens ?? 0,
      outputTokens: log.usage?.output_tokens ?? 0,
      model: log.model ?? "claude-sonnet-4-20250514",
      project: log.project ?? "unknown",
    });
  }
  return entries;
}

// コスト計算(Sonnet 4 の料金で概算)
function calculateCost(entries: UsageEntry[]): CostSummary[] {
  const INPUT_COST_PER_1K = 0.003; // $3 / 1M tokens
  const OUTPUT_COST_PER_1K = 0.015; // $15 / 1M tokens

  const grouped = Map.groupBy(entries, (e) => e.member);
  const summaries: CostSummary[] = [];

  for (const [member, memberEntries] of grouped) {
    if (!memberEntries) continue;
    const totalInput = memberEntries.reduce((s, e) => s + e.inputTokens, 0);
    const totalOutput = memberEntries.reduce((s, e) => s + e.outputTokens, 0);

    summaries.push({
      member,
      totalInputTokens: totalInput,
      totalOutputTokens: totalOutput,
      estimatedCost:
        (totalInput / 1000) * INPUT_COST_PER_1K +
        (totalOutput / 1000) * OUTPUT_COST_PER_1K,
      sessionCount: memberEntries.length,
    });
  }

  return summaries.sort((a, b) => b.estimatedCost - a.estimatedCost);
}

// Slack通知用のサマリーを生成
function formatSlackMessage(summaries: CostSummary[]): string {
  const totalCost = summaries.reduce((s, e) => s + e.estimatedCost, 0);
  let message = `📊 *Claude Code Weekly Cost Report*\n`;
  message += `Total: *$${totalCost.toFixed(2)}*\n\n`;

  for (const s of summaries) {
    const bar = "".repeat(Math.ceil(s.estimatedCost / totalCost * 20));
    message += `• ${s.member}: $${s.estimatedCost.toFixed(2)} ${bar} (${s.sessionCount} sessions)\n`;
  }

  return message;
}

// メイン処理
async function main() {
  const entries = await parseUsageLogs("./usage-logs");
  const summaries = calculateCost(entries);
  console.log(formatSlackMessage(summaries));
}

main().catch(console.error);

運用ルール:

  • 週次でSlackに自動レポートを投稿
  • 月間予算の80%に到達したらアラート発火
  • 突出したメンバーがいる場合は1on1で使い方をヒアリング(責めるのではなく改善目的)

補足: Claude Code Maxプランの場合はトークン単位の従量課金ではなく定額制のため、上記のコスト計算はAPIプランを利用しているケースを想定しています。Maxプランでも「使用量の可視化」自体はチーム内の公平性を保つために有効です。

5. AIコード×人間レビューのハイブリッドフロー

AI生成コードのレビューは、従来の人間コードのレビューとは観点が異なります。3ヶ月の試行錯誤で確立したフローを紹介します。

PR時に人間がチェックすべき5つの観点

AI生成コードで特に注意すべきポイントを明確化し、レビューガイドラインとして共有しました。

# チェック観点 理由
1 ビジネスロジックの正しさ AIはコンテキスト外の業務要件を知らない
2 セキュリティ SQLインジェクション、XSS、認証漏れなどをAIが見落とすことがある
3 パフォーマンスへの影響 N+1クエリ、不要な再レンダリングなどの生成は頻出
4 既存コードとの一貫性 CLAUDE.mdで制御しきれない暗黙のチームルールがある
5 テストの妥当性 AIはテストを通すためだけの実装を書くことがある

実運用のコツ: PRのdescriptionに「Claude Codeへの指示内容(プロンプト要約)」を記載するルールにしました。レビュアーが「開発者が何を意図していたか」を把握できるため、レビュー効率が大幅に改善しました。

6. 権限制御: Claude Codeが触れるディレクトリ・コマンドを制限する設定

Claude Codeがチーム環境で暴走しないために、settings.jsonCLAUDE.mdの両面で制限をかけています。

CLAUDE.mdでのディレクトリ制限

## 作業範囲の制限

### 触れてよいディレクトリ
- `src/` 配下のソースコード
- `tests/` 配下のテストコード
- `docs/` 配下のドキュメント

### 絶対に触れてはいけないもの
- `.env*` ファイル(シークレット情報を含む)
- `infrastructure/` ディレクトリ(Terraform等のIaCコード)
- `scripts/deploy*.sh`(デプロイスクリプト)
- `database/migrations/` の既存ファイル(新規作成のみ許可)

### 実行してよいコマンド
- `npm run test` / `npm run lint` / `npm run build`
- `git status` / `git diff` / `git log`

### 実行禁止コマンド
- `rm -rf` (ディレクトリの再帰的削除)
- `git push`(プッシュは人間が行う)
- `npm publish`
- データベースへの直接操作(`psql` 等)

.claude/settings.json での制御

{
  "permissions": {
    "allow": [
      "Read",
      "Write src/**",
      "Write tests/**",
      "Write docs/**",
      "Bash(npm run test*)",
      "Bash(npm run lint*)",
      "Bash(npm run build*)",
      "Bash(git status)",
      "Bash(git diff*)",
      "Bash(git log*)"
    ],
    "deny": [
      "Write .env*",
      "Write infrastructure/**",
      "Write scripts/deploy*",
      "Bash(rm -rf*)",
      "Bash(git push*)",
      "Bash(npm publish*)",
      "Bash(psql*)",
      "Bash(docker rm*)"
    ]
  }
}

この設定はチームリポジトリの.claude/settings.jsonにコミットして共有します。個人のローカル設定で上書きできないよう、CIでdiffチェックを入れる運用も併せて行っています。

7. 導入前後のチーム生産性比較

3ヶ月の運用データから、主要な指標の変化を共有します。

指標 導入前(2月) カオス期(3月) 安定期(5月) 変化率
PR数 / 週 12 22 28 +133%
PR平均レビュー時間 45分 72分 😱 30分 -33%
バグ密度(バグ数/1000行) 2.1 3.8 😱 1.4 -33%
テストカバレッジ 68% 61% 82% +14pt
デプロイ頻度 / 週 3回 2回 5回 +67%

注目すべきは「カオス期」の悪化です。 ガバナンスなしでClaude Codeを導入すると、一時的に品質が下がることがデータからも明らかです。CLAUDE.mdの標準化とレビュープロセスの再設計を経て、安定期には導入前を大きく上回る数値になりました。

定性的な変化

  • ポジティブ: ボイラープレートコードの記述が激減し、設計・レビューに時間を使えるようになった
  • ポジティブ: ジュニアメンバーのPR品質が底上げされた(CLAUDE.mdが暗黙知を明文化する副次効果)
  • 注意点: 「AIが書いたから大丈夫」というバイアスが生まれやすい。レビューガイドラインの徹底が不可欠

まとめ

  • CLAUDE.mdは4階層に分離する(Global → Team → Project → Personal)。Team層はPRレビュー必須にして属人化を防ぐ
  • コスト可視化とレビュープロセスの再設計はDay 1からやるべき。カオス期を経験してからでは手戻りが大きい
  • 権限制御はsettings.jsonで明示的にdenyする。「AIに触らせたくないもの」を先に決めることがガバナンスの第一歩

チームでのClaude Code導入は、ツールの導入ではなく開発プロセスの再設計です。この記事が皆さんのチームの参考になれば幸いです。

参考リンク

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?