1行コピペで社員全員プログラマーになれるClaude Codeセットアップを作った

Shinji Saito
Shinji Saito

代表取締役社長 / 文部科学省 最高情報セキュリティアドバイザー

シンジです。SaaS関連の株価を下げまくっている話題のClaude Codeですが、SNSにも情報があふれていて、これをやれば10倍賢くなる!だとか、そんなのをよく目にしますが、それでもなおClaude Codeを触ったことがない人たちが大量にいるのも事実です。なんかすごいらしいという情報はあふれてるし、何をしたらいいのかわからない、その1歩を、たった1行コピペするだけで環境全部作ってくれるClaude Code Starter Kitを作りました。いわゆるSNSで騒がれる手法を厳選して取り込んでいるやーつです。結果としてシンジはこれを使って社内布教に使いました。当社はすでに全員プログラマー状態です。

READMEは初心者向けに丁寧に書いてありますので、この記事ではそこには書いていない「中でなにが起きているのか」を技術的に解説します。よく分からないけど使ってみたいという人は、↑のリンクから説明書を読んでください。ここでは、エンジニアが読んで「なるほど、こういう設計か」と思える内容にしています。


この記事で解説すること

この記事は4つのパートに分かれています。

  1. CLAUDE.md テンプレートエンジン
    変数置換・フィーチャーインジェクション・条件分岐によるテンプレートアセンブリの仕組み
  2. プラグインシステム
    マルチマーケットプレイス対応、衝突検知、修飾名の設計
  3. Hook フラグメントアセンブリ
    jq による JSON ディープマージと外部スクリプトフックのデプロイ
  4. Codex MCP 統合
    OpenAI の Codex を Claude Code から呼び出すパラレル AI ワークフロー

おまけで、macOS と Windows の両対応でどんな地雷を踏んだかの話もします。

設計のルーツ: everything-claude-code

本題に入る前に、このプロジェクトの設計思想のルーツについて触れておきます。

Claude Code のベストプラクティス集として、@affaanmustafa 氏が公開している everything-claude-code というリポジトリがあります。Anthropic × Forum Ventures のハッカソン優勝プロジェクトで、10ヶ月以上の本番運用(zenith.chat の開発)で磨かれた 13 エージェント・43 スキル・31 コマンドの集大成です。

Starter Kit はこの everything-claude-code のアーキテクチャ、特に「agents / skills / commands / rules / hooks」という5層構造の設計思想を継承しています。共通するコア9エージェント(planner, architect, tdd-guide, code-reviewer, security-reviewer, build-error-resolver, e2e-runner, refactor-cleaner, doc-updater)の名前と役割定義もここから来ています。

ただし、everything-claude-code は .claude-plugin/ 形式の Claude Code ネイティブプラグインとして配布される前提の設計です。Starter Kit はそこに「1行コピペで環境構築」という導入体験を加えるために、以下を独自実装しています。

  • CLAUDE.md テンプレートエンジン(3段パイプラインによる条件付きアセンブリ)
  • インタラクティブウィザード(プロファイル選択→自動デプロイ)
  • macOS / Windows クロスプラットフォーム対応
  • Hook フラグメントの jq ディープマージ
  • Codex MCP 統合(OpenAI への委任ワークフロー)

「初心者でもコピペ1行で始められるキット」ではあるものの、なんとなくClaude Codeを使っている人でもこのキットでセットアップして上書きすれば、世の中のClaude Codeマスター達が考え抜いて作り上げた最高の環境を一発で手に入れられるというわけです。

全体アーキテクチャ

まず全体像です。

claude-code-starter-kit/
├── setup.sh                    ← エントリポイント(インタラクティブウィザード)
├── config/
│   ├── plugins.json            ← プラグイン定義(マーケットプレイスマッピング)
│   └── settings-base.json      ← Hook の基盤 JSON
├── hooks/
│   ├── fragments/              ← Hook フラグメント(機能別 JSON)
│   └── scripts/                ← 外部スクリプトフック
├── i18n/
│   ├── en/
│   │   └── CLAUDE.md.base      ← 英語テンプレート
│   └── ja/
│       └── CLAUDE.md.base      ← 日本語テンプレート
├── features/
│   ├── *.md                    ← フィーチャーテキスト(CLAUDE.md に注入される)
│   └── *.json                  ← フィーチャー Hook フラグメント
└── permissions.json            ← パーミッション定義

setup.sh を実行すると、3つのプロファイル(minimal / standard / full)から選択するインタラクティブウィザードが起動します。macOS ならそのまま、Windows なら WSL2 上で実行します。選択に応じて以下が自動生成されます。

  • ~/.claude/CLAUDE.md
    テンプレートエンジンが組み立てた CLAUDE.md
  • ~/.claude/settings.json
    jq でディープマージされた Hook 設定
  • ~/.claude/hooks/
    外部スクリプトフック群
  • プラグインのインストール(claude CLI 経由)

ポイントは、すべてがシェルスクリプトで完結していることです。Node.js も Python も不要。macOS なら Bash 3.2 以上、Windows なら WSL2 上の Bash があれば動きます。

Part 1: CLAUDE.md テンプレートエンジン

なぜテンプレートエンジンが必要なのか

CLAUDE.md は Claude Code の振る舞いを制御する最も重要なファイルですが、その内容はプロジェクトの性質、チームの方針、使用するツールチェインによって大きく変わります。

手動で管理していると、こうなります。

  • プロジェクトAで良かった設定をプロジェクトBにコピペして、半分が不要だった
  • チームメンバーが各自の CLAUDE.md を持っていて、レビュー品質がバラバラ
  • セキュリティレビューの指示を入れ忘れて、脆弱なコードがマージされた

テンプレートエンジンはこの問題を「プロファイル選択 → 自動組み立て」で解決します。

テンプレートの構造

ベーステンプレートはこんな構造です(i18n/ja/CLAUDE.md.base から抜粋)。

# CLAUDE.md

{{PROJECT_DESCRIPTION}}

## コーディング規約

{{FEATURE:coding-standards}}

## セキュリティ

{{FEATURE:security-review}}

## テスト戦略

{{FEATURE:tdd-guide}}

{{OPTIONAL:e2e-testing}}

ここで3種類のプレースホルダーが使われています。

プレースホルダー構文役割変数{{VAR_NAME}}単純な文字列置換。プロジェクト名や言語設定などフィーチャー{{FEATURE:name}}features/name.md の内容を丸ごと注入オプショナル{{OPTIONAL:name}}プロファイルに応じて注入 or 除去

アセンブリパイプライン

テンプレートの組み立ては3段階のパイプラインで処理されます。

process_template() → inject_feature() → remove_unresolved()

ステップ1: process_template()

process_template() {
    local template="$1"
    local profile="$2"
    local lang="$3"

    local content
    content=$(cat "i18n/${lang}/CLAUDE.md.base")

    # 変数置換
    content="${content//\{\{PROJECT_DESCRIPTION\}\}/${PROJECT_DESC:-}}"
    content="${content//\{\{LANG\}\}/${lang}}"

    echo "$content"
}

ここでやっていることは単純な文字列置換です。Bash のパラメータ展開 ${var//pattern/replacement} を使っています。sed ではなく Bash ネイティブの置換を使っているのは、macOS のデフォルト sed(BSD sed)と Linux / WSL の GNU sed で挙動が違う問題を回避するためです。

sed -i の引数が macOS と Linux で違うのは有名な罠ですが、それ以外にも正規表現の方言の違い、エスケープルールの違いなど、macOS と Windows(WSL)の両方で sed を使うのは結構だるいです。Bash ネイティブの置換なら両方で同じ挙動が保証されます。

ステップ2: inject_feature()

inject_feature() {
    local content="$1"
    local feature_name="$2"
    local feature_file="features/${feature_name}.md"

    if [[ -f "$feature_file" ]]; then
        local feature_content
        feature_content=$(cat "$feature_file")
        content="${content//\{\{FEATURE:${feature_name}\}\}/${feature_content}}"
    fi

    echo "$content"
}

フィーチャーインジェクションは、{{FEATURE:name}} マーカーを features/name.md の内容で置き換えます。

たとえば {{FEATURE:security-review}} は features/security-review.md の内容に置き換わります。このファイルには以下のような内容が入っています。

### セキュリティレビューガイドライン

すべてのコード変更に対して、以下のセキュリティチェックを実行してください:

- SQL インジェクション、XSS、CSRF の検出
- 認証・認可ロジックの妥当性確認
- シークレット・クレデンシャルのハードコード検出
- 依存パッケージの既知脆弱性チェック

これがプロファイルに応じて注入されるわけです。minimal プロファイルではセキュリティレビューは入らず、standard 以上で入る、といった制御ができます。

ステップ3: remove_unresolved()

remove_unresolved() {
    local content="$1"

    # 未解決のプレースホルダーを除去
    content=$(echo "$content" | grep -v '{{FEATURE:' | grep -v '{{OPTIONAL:')

    # 空行の連続を整理
    content=$(echo "$content" | cat -s)

    echo "$content"
}

最後に、プロファイルの選択によって注入されなかったプレースホルダーを除去します。grep -v で行ごと削除して、cat -s で連続する空行を1つにまとめます。

プロファイルと機能の対応

3つのプロファイルで、どの機能が有効になるかはこうなっています。

minimal プロファイル ✅ 基本コーディング規約

standard プロファイル(minimal の全機能 +) ✅ コードレビュー ✅ セキュリティレビュー ✅ TDD ガイド ✅ ビルドエラー自動解決

full プロファイル(standard の全機能 +) ✅ E2E テスト ✅ リファクタリング ✅ ドキュメント自動更新

9つの AI エージェント

生成される CLAUDE.md には、最大9つの AI エージェントのペルソナが定義されます。これは everything-claude-code のコア設計をそのまま継承しています。

  • standard+ プロファイルで有効(6種)
  • planner
    タスク分解・実行計画の策定
  • architect
    アーキテクチャ設計・技術選定
  • tdd-guide
    テスト駆動開発のガイド
  • code-reviewer
    コードレビュー・品質チェック
  • security-reviewer
    セキュリティ脆弱性の検出
  • build-error-resolver
    ビルドエラーの自動診断・修正
  • full プロファイルで追加(3種)
  • e2e-runner
    E2E テストの実行・デバッグ
  • refactor-cleaner
    リファクタリング・技術的負債の解消
  • doc-updater
    ドキュメントの自動更新

everything-claude-code では13エージェント(上記9種に加えて go-reviewer, go-build-resolver, python-reviewer, database-reviewer)が定義されていますが、Starter Kit では言語非依存のコア9種に絞っています。Go や Python に特化したレビュアーは、各チームが CLAUDE.md に直接追記するか、フィーチャーとして追加する設計です。

これらはスラッシュコマンドと対応しています。/plan を実行すると planner エージェントのペルソナで Claude が応答し、/code-review なら code-reviewer として振る舞います。

個人的にはfullインストールがいい感じなのでおすすめです。


Part 2: プラグインシステム

プラグインとは何か

Claude Code にはプラグイン機構があります。プラグインは Claude の能力を拡張するもので、claude plugin install でインストールします。

Starter Kit では、プロファイルに応じて推奨プラグインを自動インストールする仕組みを持っています。が、ここで厄介な問題が出てきます。

マルチマーケットプレイス問題

Claude Code のプラグインには複数のマーケットプレイスが存在します。

  • claude-plugins-official ── 公式マーケットプレイス
  • claude-code-plugins ── コミュニティマーケットプレイス

同じ名前のプラグインが異なるマーケットプレイスに存在する可能性があります。これを区別しないと、意図しないプラグインがインストールされるリスクがあります。

config/plugins.json の設計

{
  "plugins": [
    {
      "name": "code-review-enhanced",
      "marketplace": "claude-plugins-official",
      "profiles": ["standard", "full"],
      "description": "Enhanced code review capabilities"
    },
    {
      "name": "security-scanner",
      "marketplace": "claude-code-plugins",
      "profiles": ["full"],
      "description": "Security vulnerability scanner"
    }
  ]
}

各プラグインはどのマーケットプレイスに属するか、どのプロファイルで有効かが定義されています。

衝突検知: _plugin_has_collision()

_plugin_has_collision() {
    local plugin_name="$1"
    local count

    # 同名プラグインが複数マーケットプレイスに存在するか確認
    count=$(jq -r --arg name "$plugin_name" \
        '[.plugins[] | select(.name == $name)] | length' \
        config/plugins.json)

    [[ "$count" -gt 1 ]]
}

同じ名前のプラグインが複数のマーケットプレイスに存在する場合、この関数が true を返します。

修飾名(Qualified Name)

衝突が検出された場合、プラグイン名はマーケットプレイス名で修飾されます。

code-review-enhanced                    ← 衝突なし(そのまま)
security-scanner@claude-code-plugins    ← 衝突あり(修飾名)

インストール処理はこうなります。

install_plugin() {
    local plugin_name="$1"
    local marketplace="$2"

    # マーケットプレイスを先に登録
    claude plugin marketplace add "$marketplace" 2>/dev/null || true

    # 衝突チェック
    if _plugin_has_collision "$plugin_name"; then
        # 修飾名でインストール
        claude plugin install "${plugin_name}@${marketplace}"
    else
        claude plugin install "$plugin_name"
    fi
}

claude plugin marketplace add は冪等(既に登録済みなら何もしない)なので、2>/dev/null || true で安全に呼べます。


Part 3: Hook フラグメントアセンブリ

Claude Code の Hook とは

Claude Code の Hook は、特定のイベント(コミット前、ファイル保存後など)にカスタム処理を差し込む仕組みです。~/.claude/settings.json に定義します。

everything-claude-code では hooks ディレクトリに PreToolUse / PostToolUse / Stop の3種のイベントフックが定義されていますが、Starter Kit ではこれをさらに細分化して9種類の Hook フラグメントとして提供しています。

  • tmux リマインダー
    tmux 未使用時に警告
  • git push レビュー
    push 前にコードレビューを促す
  • doc ブロッカー
    ドキュメント未更新でコミットをブロック
  • prettier 自動フォーマット
    保存時に自動フォーマット
  • console.log ガード
    console.log の消し忘れを検出
  • メモリ永続化
    コンテキストを自動保存
  • 戦略的コンパクト
    コンテキストウィンドウの自動圧縮
  • PR 作成ログ
    PR 作成時の操作を記録
  • Pre-compact Auto-commit
    compact 直前に未コミット変更を自動保存

3つのフックスタイル

Hook には3つの実装スタイルがあります。

1. インラインフック(Bash in JSON)

{
  "hooks": {
    "PreCommit": [
      {
        "command": "bash -c 'grep -rn console.log src/ && echo \"console.log found\" && exit 1 || exit 0'",
        "description": "Detect leftover console.log"
      }
    ]
  }
}

短い処理はインラインで書きます。JSON の中に Bash が入るので、エスケープが面倒ですが、外部ファイルへの依存がない利点があります。

2. 外部スクリプトフック

{
  "hooks": {
    "PreCommit": [
      {
        "command": "~/.claude/hooks/doc-blocker.sh",
        "description": "Block commit if docs are outdated"
      }
    ]
  }
}

複雑な処理は外部スクリプトに切り出します。スクリプトは hooks/scripts/ に置かれ、セットアップ時に ~/.claude/hooks/ にコピーされます。

3. トップレベル設定

{
  "permissions": {
    "allow": ["bash", "git", "npm"],
    "deny": ["rm -rf /"]
  }
}

Hook ではなく、パーミッションなどのグローバル設定です。

jq によるディープマージ

ここが Hook アセンブリの核心です。

最終的な settings.json は、複数の JSON フラグメントを jq でディープマージして生成されます。

settings-base.json        ← 基盤設定
  + permissions.json       ← パーミッション定義
  + feature-a.json         ← 機能Aの Hook
  + feature-b.json         ← 機能Bの Hook
  = settings.json          ← 最終出力

マージ処理のコアはこうなっています。

merge_json_fragments() {
    local base="$1"
    shift
    local fragments=("$@")

    local result
    result=$(cat "$base")

    for fragment in "${fragments[@]}"; do
        if [[ -f "$fragment" ]]; then
            result=$(echo "$result" | jq -s '
                def deep_merge(a; b):
                    a as $a | b as $b |
                    if ($a | type) == "object" and ($b | type) == "object" then
                        ($a | keys) + ($b | keys) | unique | map(
                            . as $key |
                            if ($a | has($key)) and ($b | has($key)) then
                                {($key): deep_merge($a[$key]; $b[$key])}
                            elif ($b | has($key)) then
                                {($key): $b[$key]}
                            else
                                {($key): $a[$key]}
                            end
                        ) | add
                    elif ($a | type) == "array" and ($b | type) == "array" then
                        $a + $b
                    else
                        $b
                    end;
                deep_merge(.[0]; .[1])
            ' - "$fragment")
        fi
    done

    echo "$result"
}

この jq スクリプトは再帰的なディープマージを行います。

  • オブジェクト同士 → キーをマージ(同じキーがあれば再帰的にマージ)
  • 配列同士 → 連結($a + $b)
  • それ以外 → 後勝ち($b)

つまり、PreCommit Hook が複数のフラグメントで定義されている場合、配列として結合されます。これにより「console.log ガード」と「doc ブロッカー」の両方が PreCommit に登録される、という挙動が実現できます。

__HOME__ トークン置換

Hook の中でホームディレクトリを参照する必要がある場合、JSON フラグメントでは __HOME__ トークンを使います。

{
  "hooks": {
    "PreCommit": [
      {
        "command": "__HOME__/.claude/hooks/doc-blocker.sh"
      }
    ]
  }
}

アセンブリの最終段階で、これが実際のパスに置換されます。

finalize_settings() {
    local settings="$1"
    local home_dir="$HOME"

    # __HOME__ トークンを実際のパスに置換
    settings="${settings//__HOME__/${home_dir}}"

    echo "$settings"
}

なぜ直接 $HOME を JSON に書かないのかといえば、JSON フラグメントは Git で管理されるからです。/Users/shinji や /home/shinji(WSL)みたいなパスがリポジトリに入るのは避けたいわけです。__HOME__ トークンにしておけば、macOS でも Windows(WSL)でも環境に依存しない形で配布できます。

JSON バリデーション

マージ後の JSON が壊れていないかを validate_json() で検証します。

validate_json() {
    local json_file="$1"

    if ! jq empty "$json_file" 2>/dev/null; then
        echo "ERROR: Invalid JSON generated at $json_file" >&2
        return 1
    fi
}

jq の empty フィルタは、入力が有効な JSON であれば何も出力せず成功し、無効であればエラーを返します。シンプルですが確実なバリデーションです。

記事を書きながら見つけたバグ: jq マージの * 演算子問題

ここから先は、まさにこの記事を書いている最中に発見して修正した話です。

初期実装では、jq のディープマージに * 演算子(recursive object merge)を使っていました。

# ❌ 旧実装(バグあり)
result=$(jq -s '.[0] * .[1]' "$base" "$fragment")

jq の * 演算子はオブジェクト同士の再帰マージをしてくれますが、配列に対しては「結合」ではなく「上書き」になります。つまり、

// base.json
{ "PreCompact": [{"command": "memory-persistence.sh"}] }

// fragment.json
{ "PreCompact": [{"command": "git commit ..."}] }

// ❌ jq の * 演算子の結果(後勝ち)
{ "PreCompact": [{"command": "git commit ..."}] }

// ✅ 期待する結果(配列結合)
{ "PreCompact": [{"command": "memory-persistence.sh"}, {"command": "git commit ..."}] }

memory-persistence の PreCompact フックが、記事書きながら追加した新機能 Pre-compact Auto-commit で消されます。Hook フラグメントアセンブリの設計思想は「複数フラグメントが同じイベントに Hook を登録できる」なのに、マージロジックがそれを遮りました。

これが、前述の deep_merge 関数で $a + $b(配列結合)を明示的に書いている理由です。* 演算子に頼らず、配列は結合、オブジェクトは再帰マージ、スカラーは後勝ち、というルールを自前で実装しています。

Pre-compact Auto-commit: この記事がきっかけで追加した Hook

この記事で Hook アセンブリの設計を整理していて、「compact 前の状態保存」が抜けていることに気づきました。そのまま実装して GitHub に push した話です。

解決する問題

Claude Code は長時間セッションでコンテキストウィンドウが埋まると、compact(コンテキスト圧縮)を実行します。このとき、Claude がファイルに書き込んだ変更のうち、まだコミットされていないものがある場合:

  • compact 後に Claude はそれらの変更の文脈を失う
  • 最悪のケースでは、compact 後に Claude が同じファイルを別の意図で上書きしてしまう
  • ユーザーが気づかないうちに、作業中の変更が意図しない方向に変質する

これを使ってなかったので、Claude Codeで20時間とか40時間とか作業させてると、前にやった文脈を忘れてせっかく作った環境をClaude Code自身が壊してやりなおすことがありました。とてもかなしい。ので、対策です。

PreCompact フックとは

Claude Code のフックシステムには以下のタイミングがあります。

  1. PreToolUse
    ツール(Edit, Bash等)実行前
  2. PostToolUse
    ツール実行後
  3. PreCompact
    コンテキスト圧縮の直前(SessionStartセッション開始時とStopセッション終了時)

PreCompact は compact が実行される直前に呼ばれるため、「今の作業状態を保存する」最後のチャンスです。

実行されるコマンドの詳細

cd "$CLAUDE_PROJECT_DIR" && git add -A && git diff --cached --quiet || git commit -m 'checkpoint: pre-compact auto-commit'

これを分解すると:

cd "$CLAUDE_PROJECT_DIR"       # Claude が作業中のプロジェクトディレクトリに移動
  && git add -A                # 全ての変更(新規・変更・削除)をステージング
  && git diff --cached --quiet # ステージングに変更があるか確認
  || git commit -m '...'       # 変更がある場合のみコミット

git diff --cached --quiet は、ステージングに変更がなければ exit code 0 を返し、|| の右側は実行されません(空コミット防止)。変更があれば exit code 1 を返し、コミットが実行されます。コミットメッセージは checkpoint: pre-compact auto-commit で、後から git log で自動コミットだと識別できます。

$CLAUDE_PROJECT_DIR が git リポジトリでない場合は、git add -A が失敗して && チェーンが中断します。エラーは出ますが、compact 自体はブロックされません。

memory-persistence との共存

ここで、先ほどの jq マージ修正が効いてきます。Starter Kit では memory-persistence も PreCompact フックを使っています。

compact 発火

PreCompact フック実行(配列順に実行)
  ├── [1] memory-persistence/pre-compact.sh  ← メモリファイルを保存
  └── [2] pre-compact-commit                 ← 変更を git commit

compact 実行(コンテキスト圧縮)

旧実装の * 演算子マージでは、後から追加された Pre-compact Auto-commit が memory-persistence を上書きしてしまいます。配列結合に修正したことで、settings.json にはこう出力されます。

{
  "PreCompact": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "command",
          "command": "~/.claude/hooks/memory-persistence/pre-compact.sh"
        }
      ]
    },
    {
      "matcher": "",
      "hooks": [
        {
          "type": "command",
          "command": "cd \"$CLAUDE_PROJECT_DIR\" && git add -A && git diff --cached --quiet || git commit -m 'checkpoint: pre-compact auto-commit'"
        }
      ]
    }
  ]
}

2つの PreCompact フックが配列の要素として共存しています。memory-persistence が先に実行されてメモリファイルを保存し、その後に Pre-compact Auto-commit が全変更(メモリファイル含む)を git commit する。この順序も意図的です。


Part 4: Codex MCP 統合

Codex MCP とは

Open AIのCodexに作らせたコードを、Claudeが評価して書き直させたりする流れが最も品質が上がるというのが定番らしいので実装してます。

MCP(Model Context Protocol)は、Claude Code が外部ツールと連携するためのプロトコルです。Codex MCP を設定すると、Claude Code から OpenAI の Codex(ChatGPT のコード生成エンジン)にタスクを委任できるようになります。

つまり、Claude Code が「この部分は Codex に任せよう」と判断して、別の AI にタスクを投げることができます。

everything-claude-code では mcp-configs/ ディレクトリに GitHub / Supabase / Vercel / Railway 等の複数 MCP 設定が用意されています。ただしそこでは重要な注意点も指摘されていて、MCP を有効にしすぎるとコンテキストウィンドウが 200k → 70k まで縮小する(推奨: MCP 10個以下、ツール80個以下)という問題があります。Starter Kit では MCP 設定を Codex 1つに絞ることで、この問題を回避しつつ最大の効果を得る設計にしています。

前提条件

  • ChatGPT Plus サブスクリプション($20/月)
  • OpenAI API キー

セットアップ

Starter Kit のウィザードで Codex MCP を有効にすると、以下が実行されます。

setup_codex_mcp() {
    local api_key="$1"

    # ユーザースコープで MCP サーバーを登録
    claude mcp add -s user codex-mcp \
        --type openai \
        --api-key "$api_key"
}

-s user はユーザースコープでのインストールを意味します。プロジェクトスコープ(-s project)ではなくユーザースコープにしているのは、Codex は特定のプロジェクトに紐づく機能ではなく、汎用的に使える機能だからです。

クレデンシャルの安全な取り扱い

API キーの取り扱いには特に注意しています。

test

# ❌ やってはいけない(プロセスリストにキーが露出)
curl -H "Authorization: Bearer $API_KEY" https://api.openai.com/...

# ✅ 安全な方法(stdin 経由でヘッダーを渡す)
curl --config - <<EOF https://api.openai.com/...
header = "Authorization: Bearer ${API_KEY}"
EOF

curl --config - は設定を stdin から読み取るオプションです。これにより、API キーがプロセスリスト(ps aux)に表示されるのを防ぎます。

パラレル AI ワークフロー

Codex MCP が有効な状態では、こんなワークフローが可能になります。

ユーザー: 「認証モジュールをリファクタリングして」

Claude Code (planner エージェント):
├── タスク1: 現状のコード分析 → Claude Code が担当
├── タスク2: テストケースの生成 → Codex に委任
├── タスク3: リファクタリング実装 → Claude Code が担当
└── タスク4: テスト実行・修正 → Claude Code が担当

Claude Code がオーケストレーターとして機能し、一部のタスクを Codex に委任する形です。これは人間のチーム開発に近いモデルで、「得意な人に任せる」という発想です。


安全設計

ここからは、ツールとして最低限必要なセーフティ設計について書きます。

ブートストラップの安全性

_safe_install_dir() は、危険なディレクトリへのインストールをブロックします。macOS と Windows(WSL)の両方のシステムディレクトリをカバーしています。

_safe_install_dir() {
    local dir="$1"

    # 危険なパスのブラックリスト(macOS + WSL 両対応)
    local -a dangerous_paths=(
        "/" "/home" "/usr" "/etc" "/var"
        "/bin" "/sbin" "/lib" "/opt"
        "/System" "/Library"              # macOS
        "/Windows" "/Program Files"       # Windows (WSL)
        "/mnt/c/Windows"                  # WSL のマウントパス
    )

    for dangerous in "${dangerous_paths[@]}"; do
        if [[ "$dir" == "$dangerous" ]]; then
            echo "ERROR: Cannot install to $dir" >&2
            return 1
        fi
    done
}

WSL 環境では /mnt/c/ 以下に Windows のファイルシステムがマウントされているため、/mnt/c/Windows もブラックリストに入れています。

マニフェストベースのアンインストール

Starter Kit がデプロイしたファイルを正確に追跡するために、マニフェストファイルを使います。

// ~/.claude/.starter-kit-manifest.json
{
  "version": "1.0.0",
  "installed_at": "2025-02-24T07:00:00Z",
  "platform": "darwin",
  "files": [
    "~/.claude/CLAUDE.md",
    "~/.claude/settings.json",
    "~/.claude/hooks/doc-blocker.sh",
    "~/.claude/hooks/prettier-format.sh"
  ]
}

platform フィールドにインストール時の OS を記録しているので、macOS でインストールした設定を WSL で誤ってアンインストールしようとした場合に警告できます。アンインストール時はこのマニフェストに記載されたファイルだけを削除します。Starter Kit がデプロイしていないファイルには触りません。

一時ファイルの衛生管理

# umask でパーミッションを制限
umask 077

# 一時ファイルの追跡
declare -a _SETUP_TMP_FILES=()

cleanup_tmp() {
    for f in "${_SETUP_TMP_FILES[@]}"; do
        [[ -f "$f" ]] && rm -f "$f"
    done
}

# EXIT トラップで確実にクリーンアップ
trap cleanup_tmp EXIT

umask 077 は作成されるファイルのパーミッションを 600(オーナーのみ読み書き可)に制限します。API キーなどの機密情報が一時ファイルに書かれた場合でも、他のユーザーから読まれるリスクを軽減します。

trap cleanup_tmp EXIT は、スクリプトが正常終了しても異常終了しても、一時ファイルを確実に削除します。

安全な設定ロード

_safe_source_config() {
    local config_file="$1"

    # 許可リスト方式で設定値を読み込む
    local -a allowed_keys=("LANG" "PROFILE" "PROJECT_DESC" "ENABLE_CODEX")

    while IFS='=' read -r key value; do
        for allowed in "${allowed_keys[@]}"; do
            if [[ "$key" == "$allowed" ]]; then
                # 安全にエクスポート
                export "$key=$value"
                break
            fi
        done
    done < "$config_file"
}

source で設定ファイルを直接読み込む(. config.sh)のは危険です。悪意のある設定ファイルに任意のコマンドが書かれていた場合、それが実行されてしまいます。

代わりに、許可リスト方式で必要なキーだけを読み取ります。


macOS / Windows 両対応で踏んだ地雷

Starter Kit は macOS と Windows(WSL2)の両方で動作します。ここからはその両対応で実際に踏んだ地雷の話です。

Bash 3.2 互換の苦しみ(macOS)

macOS のデフォルト Bash は 3.2 です。2007年のバージョンです。このバージョンでは以下が使えません。

  • 連想配列(declare -A) ── Bash 4.0 以降
  • readarray / mapfile ── Bash 4.0 以降
  • ${var,,} / ${var^^}(大文字小文字変換) ── Bash 4.0 以降

連想配列が使えないので、キーバリューのペアを管理するために、平行インデックス配列を使うパターンで対処しています。

# ❌ Bash 4.0+ の連想配列(macOS デフォルトでは使えない)
declare -A features
features["security"]="enabled"
features["tdd"]="disabled"

# ✅ Bash 3.2 互換の平行配列パターン
feature_keys=("security" "tdd")
feature_vals=("enabled" "disabled")

# 検索
get_feature() {
    local key="$1"
    local i
    for i in "${!feature_keys[@]}"; do
        if [[ "${feature_keys[$i]}" == "$key" ]]; then
            echo "${feature_vals[$i]}"
            return
        fi
    done
}

可読性は落ちますが、「Homebrew で Bash 5 を入れてください」と言うのは、ツールの導入障壁を上げることになるので、避けました。

Windows 対応(WSL2 + PowerShell interop)

Windows では WSL2 上で動作します。WSL2 は基本的に Linux なので Bash 5 系が使えて、macOS よりむしろ楽な場面もあります。ただし、WSL 特有の問題がいくつかあります。

パスの変換

WSL と Windows の間でファイルパスの体系が異なります。

# WSL → Windows パス変換
wslpath -w "$HOME/.claude/settings.json"
# 出力: \\wsl$\Ubuntu\home\shinji\.claude\settings.json

# Windows → WSL パス変換
wslpath -u "C:\Users\shinji"
# 出力: /mnt/c/Users/shinji

改行コードの罠

WSL 上でファイルを生成して Windows 側のエディタで開くと、改行コードが LF のまま表示されることがあります。逆に、Windows 側で編集したファイルを WSL で読むと CR(\r)が混入します。

# CRLF を LF に変換(WSL 環境でのセーフティ)
if is_wsl; then
    sed -i 's/\r$//' "$config_file"
fi

フォントのインストールや Windows Terminal の設定変更は Windows 側で行う必要がある

if is_wsl; then
    # WSL から PowerShell を呼び出してフォント設定
    powershell.exe -Command "
        \$wtSettings = Get-Content \$env:LOCALAPPDATA\\Packages\\Microsoft.WindowsTerminal_*\\LocalState\\settings.json | ConvertFrom-Json
        # フォント設定を更新...
    "
fi

WSL と Windows の境界をまたぐのは独特の難しさがあります。ファイルパスの変換、改行コードの違い(LF vs CRLF)、パーミッションモデルの違い、そして WSL 上のプロセスから Windows 側の powershell.exe を直接呼べるという便利だけどトリッキーな仕組み。これら全部をケアしないと「macOS では動くのに Windows で動かない」が頻発します。

OS 検出と分岐

両対応の起点になるのが OS 検出です。

bash
detect_platform() {
    case "$(uname -s)" in
        Darwin)  echo "macos" ;;
        Linux)
            if grep -qi microsoft /proc/version 2>/dev/null; then
                echo "wsl"
            else
                echo "linux"
            fi
            ;;
        *)       echo "unknown" ;;
    esac
}

Ghostty ターミナルセットアップ(macOS のみ)

Ghostty は比較的新しいターミナルエミュレータで、設定ファイルの形式が他のターミナルと異なります。

setup_ghostty() {
    local config_dir="$HOME/.config/ghostty"
    mkdir -p "$config_dir"

    cat > "$config_dir/config" <<'EOF'
font-family = "IBM Plex Mono"
font-size = 14
theme = "catppuccin-mocha"
EOF
}

Ghostty のセットアップは macOS でのみ実行されます。WSL 環境では Windows Terminal の設定を PowerShell 経由で行います。


14のスラッシュコマンド

最後に、Starter Kit で利用可能なスラッシュコマンドの一覧を載せておきます。everything-claude-code の9コマンドに加えて、Starter Kit 独自のコマンドを5つ追加しています。

everything-claude-code 由来(9コマンド)

  • /plan
    タスクの分解と実行計画(planner)
  • /tdd
    テスト駆動開発のガイド(tdd-guide)
  • /code-review
    コードレビュー(code-reviewer)
  • /security-review
    セキュリティレビュー(security-reviewer)
  • /build-fix
    ビルドエラーの診断と修正(build-error-resolver)
  • /e2e
    E2E テストの実行(e2e-runner)
  • /refactor-clean
    リファクタリング(refactor-cleaner)
  • /update-docs
    ドキュメント更新(doc-updater)

Starter Kit 独自(5コマンド)(標準コマンド含む)

  • /verify
    総合検証(lint+test+build)
  • /checkpoint
    Git チェックポイント作成
  • /status
    プロジェクト状態の確認
  • /init
    プロジェクト初期化(architect)
  • /deploy-check
    デプロイ前チェック
  • /perf
    パフォーマンス分析

まとめ

Claude Code Starter Kit は、見た目はシンプルなセットアップスクリプトですが、中身は以下の設計思想で構築されています。

  1. テンプレートエンジン
    プロファイルベースの条件付きアセンブリで、CLAUDE.md の属人化を排除
  2. プラグインシステム
    マルチマーケットプレイスの衝突を安全に処理する修飾名方式
  3. Hook フラグメントアセンブリ
    jq ディープマージによる宣言的な Hook 管理
  4. Codex MCP 統合
    MCP コンテキスト縮小問題を回避しつつ、パラレル AI ワークフローを実現
  5. 安全設計
    マニフェストベースのファイル追跡、許可リスト方式の設定ロード、クレデンシャル保護
  6. クロスプラットフォーム
    macOS(Bash 3.2 互換)と Windows(WSL2 + PowerShell interop)の両対応

設計のベースになった everything-claude-code の「agents / skills / commands / rules / hooks」アーキテクチャは、10ヶ月以上の本番運用で磨かれた実績あるものです。加えて今時の「これをやったら最強のClaude Code」みたいなやつで使えるものを評価した上で入れてます。

もともとこれを作ろうと思ったきっかけが、毎晩APEXを一緒にプレイしている友人が「Xとか見てるとClaude Codeの話いっぱい出てくるんですけど、触ったことないのってやっぱりIT界隈にいるのにやばいですよね」って言ってたのがきっかけなんです。もちろんこのとき「やばいね」って答えましたが、自分も今のClaude Codeの環境に至るまでにかなり苦労したので、よくわからん人でも一発で環境構築できるものを作りたかったのです。

結果としてこれを使って当社では社内全員がClaude Codeを使える環境が作れましたし(デバッグもついでに出来た)、エンジニアではない人たちがClaude Codeでいろんなものをあれこれしている様子が社内のSlackに投稿されているのを横目で見ています。

このセットアッププログラムは本当に第一歩であって、社内で使うにはGithubどうするんじゃーとか、デプロイインフラはどうするんじゃーとか課題は出てくると思いますが、我々の場合は組織のGithubを使って、基本デプロイはAWSを指定してあって、AWSにはClaude Codeで作ったものをデプロイする専用のアカウントを作って、誰が作ったものでも誰でも編集して更新できるスタイルにしています。今度はPRにSlack通知に承認制度にいろいろと監査面も拡充させていけたらいいですね。

社内全員プログラマー。夢が広がる第一歩をこれでぶち込んで欲しいです。

WSL は uname -s が Linux を返すので、/proc/version に microsoft が含まれるかどうかで判別します。この分岐を setup.sh の最初に行い、以降のすべての処理で $PLATFORM を参照する設計です。

この記事をシェア