diff --git a/.claude/rules/devpod-workflow.md b/.claude/rules/devpod-workflow.md new file mode 100644 index 00000000000..9fd8449ad8d --- /dev/null +++ b/.claude/rules/devpod-workflow.md @@ -0,0 +1,276 @@ +# devpod-based Development Workflow + +`/Users/kouji/work/smalruby/smalruby3-editor` の開発は **devpod 経由で devcontainer 内で行う** ことを基本とする。社内ガイドライン (NaCl Claude Code 利用ガイドライン v0.2) の階層 B 要件 (隔離環境推奨) に準拠するためで、Claude Code をホスト直接実行しない。 + +## 前提と全体像 + +| 観点 | 値 | +|---|---| +| 主用途 | devcontainer の中で開発 (Claude Code, npm, git すべて中で完結) | +| エディタ | devpod ssh + tmux (VS Code Dev Containers でも可) | +| host から見える環境 | ブラウザ (port forwarding 経由)、git の push/PR (gh CLI 経由)、CDK deploy | +| 採用していないもの | `docker compose run --rm app ...`, `bin/dx`, `bin/setup-worktree` (compose 前提なので不要) | +| 例外的に host で必要 | `docker compose` 自体は dev server を host から開きたい人のために残してあるが、本人は使わない | + +## 起動からの流れ (毎日のルーチン) + +### 1. 初回 / `devpod delete` 後のセットアップ + +```bash +# 一度だけ: provider 登録 +brew install devpod +devpod provider add docker + +# 各 worktree で必要: devcontainer.json を個人用にカスタマイズ +# 詳細は .devcontainer/README.md +cp .devcontainer/devcontainer.json.example .devcontainer/devcontainer.json +# 必要なら mounts を編集 (Claude マウントの有効化など) + +# または対話的に: /devcontainer-setup スキル +``` + +### 2. ホストシェルでの 1 行ルーチン + +```bash +export GH_TOKEN=$(gh auth token) # macOS keychain 由来。毎セッション必要 +cd /Users/kouji/work/smalruby/smalruby3-editor +devpod up . --ide none # 初回 build はかかる、以降は数秒 +``` + +### 3. tmux で入って作業 + +```bash +devpod ssh smalruby3-editor -- bash -lc 'tmux new -A -s work' +``` + +container 内で: +```bash +cd /app +claude # 初回ログインが必要 (host とは分離) +npm run lint # そのまま動く +npm start # dev server をここで起動 → forwardPorts でホストに公開 +``` + +### 4. 終了 / 一時停止 + +```bash +# 一時停止 (container 残す、状態保持) +devpod stop smalruby3-editor + +# 完全削除 (再ログイン必要、image は残る) +devpod delete smalruby3-editor +``` + +## docker compose ベースの旧ワークフローとの差分 + +以下は **本ガイドの利用者 (devpod 主体) は基本やらない** が、他のメンバーが +compose で動かしている場合に違いを理解しておくため。 + +| やりたいこと | 旧 (compose) | 新 (devpod) | +|---|---|---| +| dev server 起動 | `docker compose up app` | container 内で `npm start`, host の `localhost:8601` (forwardPorts で自動転送) | +| lint 1 回 | `bin/dx bash -c "npm run lint"` | container の tmux で `npm run lint` | +| 個別 jest | `bin/dx bash -c "cd packages/scratch-gui && npm exec jest test/xxx"` | 同上、container 内で実行 | +| npm install | `docker compose run --rm app npm install` | container 内で `npm install` (named volume で host 側 compose とも共有される) | +| `docker compose run` でテスト | `docker compose run --rm app npm test` | container 内で `npm test` | +| 単独のフリー仕事 (curl 確認等) | host 上で実行 | 任意。container 内でも OK | +| Playwright MCP | host の Playwright が動く想定 | host の Playwright が container の `localhost:8601` (forwardPorts) を見る | +| CDK deploy | host の `aws-vault` 等で | **同じく host で実行** (container には `~/.aws` を mount していないため) | + +### named volume の共有挙動 (重要) + +`.devcontainer/devcontainer.json` は **既存 docker-compose と同じ named volume** を +マウントするため、以下が同期される: + +- `/app/node_modules` ↔ compose の `smalruby3-editor_smalruby3-editor_node_modules` +- `/root/.npm` ↔ `smalruby3-editor_smalruby3-editor_root_npm` +- `/root/.cache` ↔ `smalruby3-editor_smalruby3-editor_root_cache` + +これにより、devpod の container で `npm install` した結果を `docker compose run app` +でも (またはその逆でも) そのまま使える。**両方の経路を混在させても OK**。 + +## git worktree との組み合わせ + +git worktree を作るときの挙動が **compose 時代と大きく違う** ので注意。 + +### compose 時代の挙動 (参考) + +- `docker-compose.yml` の `name: smalruby3-editor` で project 名固定 +- `docker compose run app` をどの worktree から実行しても、**同じ container を共有** (一個だけ生きている) +- container の `/app` は、container 起動時にバインドされた特定 worktree のパス +- 別 worktree の作業をするには `docker compose down && cd <他 worktree> && docker compose up` +- 結果: **同時に動かせる worktree は実質 1 つ** + +### devpod 時代の挙動 + +- devpod の workspace ID は **host の絶対パス** から生成される (`smalruby3-editor` と `smalruby3-editor-tutorial` は別 workspace) +- worktree ごとに **別の workspace = 別の container** が立ち上がる +- 別 container でも mount 経由で **以下は共有**: + - named volume の `node_modules` / `.npm` / `.cache` (再 install 不要) + - bind の `~/.claude/skills` / `~/.claude/projects/-app` (skills と memory) + - bind の `~/.gitconfig` / `~/.config/gh` / `~/ghq` +- 別 container なので **以下は per-worktree**: + - container 内 `~/.claude.json` (Claude 認証) → **各 worktree で初回再ログインが必要** + - container 内のシェル履歴 / tmux セッション / インストールしたアドホックなツール + - port forwarding (後述) + +### worktree を作るときの手順 + +```bash +# 1. host で worktree を作る (CLAUDE.md / git-workflow.md の従来手順) +git worktree add ../smalruby3-editor- -b / develop +cd ../smalruby3-editor- + +# 2. env ファイルを copy する (compose も devpod も両方使えるよう) +bin/sync-worktree-env + +# 3. devcontainer.json を個人用にコピー (worktree 内は .gitignore 対象なので別途必要) +cp .devcontainer/devcontainer.json.example .devcontainer/devcontainer.json +# 必要なら mounts を編集 + +# 4. devpod 起動 +export GH_TOKEN=$(gh auth token) +devpod up . --ide none + +# 5. tmux で入る +devpod ssh smalruby3-editor- -- bash -lc 'tmux new -A -s work' + +# 6. 初回 Claude ログイン (worktree ごとに必要) +claude +``` + +### `bin/setup-worktree` の扱い + +`bin/setup-worktree` は **docker compose 経由で npm install + build:dev を行う** +スクリプトで、compose 利用者向けに作られている。devpod 主体ならこれは不要。 + +ただし named volume が空 (一度も install していない) の状態で初めて worktree を +立てるときは、最低 1 度は何らかの形で `npm install` を走らせる必要がある。 +container 内で: + +```bash +docker compose run app npm install # host 上で +# or +# devpod 内で: +cd /app && npm install +``` + +どちらでも結果は named volume に入り、以降の worktree で再利用される。 + +### worktree を捨てるとき + +```bash +# host で +cd /Users/kouji/work/smalruby/smalruby3-editor # main checkout に戻る +devpod delete smalruby3-editor- # devpod workspace を削除 +git worktree remove ../smalruby3-editor- # worktree を削除 +git branch -d / # ブランチ削除 (merge 済みなら) +``` + +`devpod delete` を忘れると container が残りリソースを食う。 + +### worktree を複数同時に走らせるときの注意 + +devpod では **複数 workspace が同時に container 起動可能**。これは compose と +違って **完全並列開発が可能** だが、以下の制約がある: + +- **port 衝突**: 各 workspace の `forwardPorts: [8601]` が同時に host port 8601 を + 欲しがる。最初の workspace は 8601、次は 8602 など devpod が自動で別 port に + 振り分ける (`devpod list` で確認できる) +- **メモリ消費**: container 1 つあたり数百 MB〜数 GB。worktree が 5 つあれば + 全部 up しているとそれなりに重い +- **アクティブでない workspace は `devpod stop`**: 停止しても state は残るので + すぐ再開できる +- **named volume は全 workspace で共有**: `npm install` が 1 つの workspace で + 進行中に他 workspace で並列 install すると競合する。**install は同時に走らせない** + +## port forwarding と dev server + +| 何を見たいか | 操作 | +|---|---| +| dev server (smalruby editor) | container 内で `npm start` → host の `localhost:8601` (devpod が自動転送) で開く | +| smalruby3-gui (VNC) | これは旧 compose の `smalruby3-gui` サービス専用。devpod ではこのサービス分は立ち上げない。VNC 確認は host から `docker compose up smalruby3-gui` で起動 (devpod と独立) | +| 自分の Playwright MCP | host の Playwright が `localhost:8601` を見るだけ | + +`devpod list` で実際に forward されている port を確認できる。 + +## Claude Code を container 内で動かす + +### 認証 + +- 初回起動時 `claude` でログイン (host とは分離) +- `~/.claude.json` は **container 内 fs に保存**、`devpod stop/up` を跨ぐが + `devpod delete` で消える。3 ヶ月に 1 度くらい再ログイン覚悟 + +### 設定の上書き不可 + +- `~/.claude/settings.json` は read-only bind なので container 内では変更不能 +- 変更したいときは host 側 `~/.claude/settings.json` を編集 → devcontainer 再起動 + +### skills, plugins + +- host で開発した skills / plugins が read-only bind されて container でも使える +- container 内で skills を編集することはできない (ro) + +### memory (このプロジェクト固有) + +- `~/.claude/projects/-app` を bind rw で共有 +- container 内 Claude が memory を書くと host 側 Claude にも反映 (双方向) + +### auto-update + +- `containerEnv.DISABLE_AUTOUPDATER=1` で抑止済み +- container の Claude version は features が build 時に固定したものに留まる +- バージョン更新したい時は devcontainer rebuild (`devpod delete` → `devpod up`) + +## できなくなること / 制約 + +| 旧 (compose) | 新 (devpod 主体) | 理由 | +|---|---|---| +| `bin/dx bash -c "..."` の手軽さ | container 内で同じことを 1 コマンドで | host 上 docker run が不便なので tmux に入って実行 | +| 1 つの container で全 worktree を切替使用 | 各 worktree が独立 container | devpod の workspace 設計上 | +| host の `~/.aws` を使った CDK deploy を container 内から | **そのまま host で実行** | secret leak 防止のため container には mount しない | +| host の Claude Code 認証を共有 | container 内で別途ログイン | host secrets 持ち込み禁止原則 | +| smalruby3 (Ruby gem) の SDL2 GUI | host で動かす (X server / SDL2 が container 内に無い) | devcontainer は GUI 想定外 | +| docker compose の他サービス (`infra`, `smalruby3-gui`) との同時起動 | devpod は `app` 相当のみ。他サービスは host から `docker compose up ` | devcontainer は単一サービス | + +## トラブルシューティング + +### `gh auth status` が失敗 + +- ホスト側で `export GH_TOKEN=$(gh auth token)` を忘れている +- 古い shell で `devpod up` していて env が更新されていない +- 対処: 新しいシェルで export してから `devpod up . --ide none` + +### container 内の `npm install` が遅い / 競合 + +- 別 worktree の workspace で並行 install していないか確認 (`devpod list`) +- named volume が壊れていたら `docker volume rm smalruby3-editor_smalruby3-editor_node_modules` でリセット (要 npm install からやり直し) + +### `claude` がいない / バージョンが古い + +- features は最新を install する。固定したい時は Dockerfile で `npm install -g @anthropic-ai/claude-code@X.Y.Z` +- バージョン上げたい: `devpod delete` → `devpod up` で features の最新を取り直し + +### worktree の `docker compose run app ...` が devpod workspace を壊した + +`docker compose` は `name: smalruby3-editor` 固定で project を作る。devpod は +`default-XXX-` prefix で別 project を作る。両者は基本独立だが、**named volume と +port 8601 は共有リソース** なので衝突しうる: + +- compose 側で port 8601 を握っていると devpod 側が conflict (DNS 孤立) +- 対処: `docker compose stop app` で compose 側を止める、または devpod を先に + 落としてから compose 起動 + +### tmux session が消えた + +`devpod stop` は container を止めるので session も消える。**永続化させる必要が +ある作業 (長時間 build / 監視) は tmux + nohup or systemd など別途工夫が必要**。 + +## 参考 + +- 利用ポリシー: NaCl Claude Code 利用ガイドライン v0.2 §3-2 +- devcontainer 設計: `.devcontainer/README.md` +- 対話的セットアップ: `/devcontainer-setup` skill +- 旧 worktree 手順 (compose 利用者向け): `.claude/rules/git-workflow.md` の「Git Worktree Setup」 +- Anthropic 公式 devcontainer ドキュメント: https://code.claude.com/docs/en/devcontainer diff --git a/.claude/skills/devcontainer-setup/SKILL.md b/.claude/skills/devcontainer-setup/SKILL.md new file mode 100644 index 00000000000..9b06cd3c94d --- /dev/null +++ b/.claude/skills/devcontainer-setup/SKILL.md @@ -0,0 +1,141 @@ +--- +name: devcontainer-setup +description: smalruby3-editor の devcontainer 設定 (.devcontainer/devcontainer.json) を対話的に作成する。テンプレート (.example) を基に、利用者の環境に合わせて mounts セクションを ON/OFF する。devpod / VS Code Dev Containers で開発したい人が初回に使う。 +--- + +# /devcontainer-setup - Interactive devcontainer.json builder + +`.devcontainer/devcontainer.json` は `.gitignore` 対象で、各自で `.example` から +コピーして自分の環境に合わせて mounts を編集する設計。本スキルは AskUserQuestion +を使ってこの作業を対話的に行う。 + +## 前提 + +- 本 repo の repo root から起動する +- `.devcontainer/devcontainer.json.example` が存在する +- `~/.gitconfig` `~/.config/gh` は普通にある前提 (devcontainer 起動の必須要件) + +## 実行フロー + +### Step 1: 既存 devcontainer.json の確認 + +```bash +if [[ -f .devcontainer/devcontainer.json ]]; then + 既存ファイルがある旨を伝えて、続行するか確認する + AskUserQuestion で「上書き」「中止」「diff を見る」を提示 +fi +``` + +中止が選ばれたら exit。 + +### Step 2: 利用シーンの確認 + +`AskUserQuestion` で: + +- 質問: 「どの IDE / ツールから devcontainer を起動しますか?」 +- header: "起動方法" +- options: + - "devpod (CLI)" — devpod up . で起動 + - "VS Code Dev Containers" — Reopen in Container で起動 + - "両方" — どちらでも動く設定にする + +この回答は後段で差を付ける必要は基本ないが、トラブルシュート時の案内に使う +(devpod の場合は GH_TOKEN export の警告を強めるなど)。 + +### Step 3: 個人マウントの選択 (multiSelect) + +`AskUserQuestion` で multiSelect: + +- 質問: 「以下の個人マウントのうち、有効にするものを選んでください」 +- header: "Mounts" +- multiSelect: true +- options: + - "ghq (~/ghq → /ghq)" — 関連 OSS の参照 + - "Claude Code 設定・skills (~/.claude/settings.json, skills, plugins, statusline-command.sh, projects/-app)" — host で開発した skills + このプロジェクトの memory を container に持ち込む。container 内 Claude の初回起動時にログインが必要 (auth は意図的に共有しない) + +**重要**: `~/.claude.json` の bind は **オプションから外している**。container と +host の auth/version 分離のため、container は別途ログインする設計。auto-update +抑止のため containerEnv.DISABLE_AUTOUPDATER=1 は常に有効。詳細は +.devcontainer/README.md の「Claude Code 認証は マウントしない」セクション参照。 + +### Step 4: ホスト側の状態確認 + +選ばれた mount のソースが host に存在するか確認: + +```bash +[[ -d "$HOME/ghq" ]] || 警告: ~/ghq が無いので作成するか確認 +[[ -d "$HOME/.claude/skills" ]] || 警告: Claude Code を起動したことが無い可能性 +[[ -f "$HOME/.claude/settings.json" ]] || 警告: 同上 +``` + +存在しないソースが選ばれた場合は AskUserQuestion で: + +- "作成する (mkdir -p / touch)" — bind mount のソースを作る +- "選択を取り消す" — mount を有効化しない +- "気にせず進める" — devcontainer 起動時に bind 失敗の覚悟あり + +### Step 5: devcontainer.json の生成 + +`.devcontainer/devcontainer.json.example` を読み、選択された mount 行の +`// ` プレフィックスを除去する。生成方法: + +```bash +# 1. テンプレートをコピー +cp .devcontainer/devcontainer.json.example .devcontainer/devcontainer.json + +# 2. Edit ツールを使って、選択された各 mount 行のコメントを外す +# 例: ghq が選ばれた場合 +# `// ,"source=${localEnv:HOME}/ghq,target=/ghq,type=bind"` を +# `,"source=${localEnv:HOME}/ghq,target=/ghq,type=bind"` に置換 +``` + +実装上は AskUserQuestion で得た選択結果を順に Edit ツールで反映する。 + +### Step 6: GH_TOKEN 案内 + +macOS かどうかを確認し、macOS なら必ず案内: + +```text +devcontainer を起動する前に、以下をホスト側のシェルで実行してください: + + export GH_TOKEN=$(gh auth token) + +VS Code 利用の場合は、その export を実行した同じシェルから: + + code . + +devpod 利用の場合は: + + devpod up . --ide none + +(セッションを跨ぐと export が消えるので、毎日 1 回は必要) +``` + +### Step 7: 動作確認の促し + +最後に「テンプレートから生成した devcontainer.json はこちらです」と +パス (`.devcontainer/devcontainer.json`) を提示し、確認を促す。 + +`gh auth token` が取れているかと、選択した mount のソースが全部存在することを +最終チェックして報告する。 + +## エラーハンドリング + +- `.devcontainer/devcontainer.json.example` が無い → 「PR #687 がまだマージされていない可能性があります」と案内して exit +- repo root 以外で実行された → cd でない場合は exit +- 既存 `.devcontainer/devcontainer.json` がある → diff を見せて上書き確認 +- `gh` が host に無い → 「`brew install gh` してから再実行してください」 + +## 完了後の TODO 案内 + +- README に「次のステップ」として以下を提示: + - VS Code: `Cmd+Shift+P` → `Dev Containers: Reopen in Container` + - devpod: `devpod up . --ide none` + - 詳細は `.devcontainer/README.md` + +## 設計上の注意 + +- 本スキルは **生成だけ** を行う。devcontainer の起動・テストは行わない (副作用が大きいため) +- AskUserQuestion で multiSelect を使うことで、上述の Claude 3 マウントをまとめて選択させる +- 既存 devcontainer.json がある場合は安全側に倒す (上書きには明示的な確認) +- 編集には Edit ツールを使い、行ごとの正確な置換を行う (sed では空白や引用符でハマりやすい) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 90bcce00f11..302caed9213 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -5,83 +5,55 @@ NaCl Claude Code 利用ガイドライン **階層 B(OSS / 自社開発)** ## 設計方針 -- **既存の `docker-compose.yml` の `app` サービスを再利用** する。`docker-compose.devcontainer.yml` で mount のみ追加する override 構成 -- ホスト全体ではなく、**作業に必要なディレクトリのみ** をマウントする(物理アクセス範囲の制限) -- 既存の `bin/dx` / `bin/setup-worktree` / git worktree 運用と共存する -- VS Code Dev Containers と devpod の両方で動作する標準準拠 +- **Dockerfile-only**: 既存 `Dockerfile` を直接ビルドする構成。`docker-compose` は使わない (devpod との相性問題を回避するため)。 +- **既存 `docker compose` ワークフローには影響なし**: `docker compose up app` / `docker compose run --rm app ...` / `bin/dx` は従来通り使える。devcontainer はそれと**別の経路**で動く。 +- **named volume を共有**: `docker compose run app npm install` で作った `node_modules` を devcontainer も使う。`bin/dx` の起動高速化メリットと同じ。git worktree 切替時も再インストール不要。 +- **個人別設定はテンプレート方式**: `.devcontainer/devcontainer.json` は `.gitignore` 対象。`devcontainer.json.example` をコピーして自分の mount を編集する。 -## マウント構成 +## 利用者の前提 -devcontainer のマウントは **共有用** と **個人用** を 2 つの compose ファイルに -分けている。 +このディレクトリの devcontainer は **devpod / VS Code Dev Containers で開発したい人** 向けです。`docker compose run --rm app ...` で十分な人は無視して構いません。 -### `docker-compose.devcontainer.yml` (commit、全員共通) +## 初回セットアップ -| ホスト側 | コンテナ内 | 用途 | -|---|---|---| -| `` | `/app` | 作業ディレクトリ(既存 compose と同じ) | -| `~/.gitconfig` | `/root/.gitconfig` (ro) | git ユーザー名・コミット署名 | -| `~/.config/gh` | `/root/.config/gh` | gh CLI 設定 | - -### `docker-compose.local.yml` (`.gitignore`、各自で `*.example` からコピー) - -ghq の有無、Claude Code の利用有無などはユーザーによって違うため、個人ごとに -override する。`.devcontainer/docker-compose.local.yml.example` をコピーして -自分の環境に合わせる: +### 1. テンプレートから自分の `devcontainer.json` を作る ```bash -cp .devcontainer/docker-compose.local.yml.example \ - .devcontainer/docker-compose.local.yml -# 不要な mount をコメントアウトして使う +cp .devcontainer/devcontainer.json.example .devcontainer/devcontainer.json ``` -template には以下がデフォルトで含まれる: - -| ホスト側 | コンテナ内 | 用途 | -|---|---|---| -| `~/ghq` | `/ghq` | 関連 OSS の参照・push (使わない人はコメントアウト) | -| `~/.claude.json` | `/root/.claude.json` | Claude Code 認証 | -| `~/.claude/settings.json` | 同上 (ro) | Claude Code グローバル設定 | -| `~/.claude/skills` | 同上 (ro) | 自作 skills | -| `~/.claude/plugins` | 同上 (ro) | プラグイン | -| `~/.claude/statusline-command.sh` | 同上 (ro) | カスタム statusline | -| `~/.claude/projects/-app` | `/root/.claude/projects/-app` | このプロジェクト固有 memory | - -`docker-compose.local.yml` が無い場合は `initialize.sh` が空の stub を自動生成 -するので、Claude Code を使わない・ghq を使わない人は何もせずそのまま動作する。 +`.devcontainer/devcontainer.json` は `.gitignore` 対象なので、個人の編集は他開発者に共有されません。 -**マウントしないもの (全員)**: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, -`~/Desktop`, `~/Library`, 他案件ディレクトリ、**`~/.claude/projects/`, -`~/.claude/sessions/`, `~/.claude/history.jsonl`, `~/.claude/file-history/`, -`~/.claude/shell-snapshots/`** (他プロジェクトの転写・履歴の漏れを防ぐ)。 +### 2. 必要に応じて mounts を編集 -### Claude Code の認証・設定・memory 共有について +`devcontainer.json` の `mounts` セクションの末尾にある **個人マウント** がコメントアウト状態で入っています: -`~/.claude` のうち、共有して安全なもの(グローバル設定・自作 skills・認証)だけを bind mount し、**他プロジェクトの transcripts や履歴は意図的に隔離** する設計。 +- `~/ghq` (関連 OSS の参照) +- `~/.claude.json`, `~/.claude/skills`, `~/.claude/plugins`, etc. (Claude Code 設定) -このプロジェクトのメモリ (`~/.claude/projects//memory/`) はホスト側スラッグがユーザーのパスに依存するため、`initialize.sh` がホスト側に **`~/.claude/projects/-app` → `~/.claude/projects/`** のシンボリックリンクを作成する。コンテナ内では workspace=`/app` のため slug=`-app` で固定され、リンク経由で同じ memory を読み書きできる。他のマシンや他の worktree に切り替えても、`initialize.sh` がそのときの host slug にリンクを張り直す。 +使うものだけアンコメントする。 -### gh CLI の認証トークンについて (macOS 必須手順) +### 3. macOS: GH_TOKEN を export -macOS の `gh` は PAT を **Keychain** に保存するため、`~/.config/gh` を -bind mount しても `oauth_token` がコンテナに渡らない。devcontainer 起動前に -ホスト側のシェルで以下を実行して `GH_TOKEN` 環境変数として注入する: +macOS の `gh` は PAT を Keychain に保存するため、`~/.config/gh` を bind mount しても +`oauth_token` がコンテナに渡らない。devcontainer 起動前にホスト側のシェルで以下を実行: ```bash export GH_TOKEN=$(gh auth token) ``` -devcontainer.json の `remoteEnv.GH_TOKEN: "${localEnv:GH_TOKEN}"` 経由で -コンテナ内 `gh` が PAT を読み取れるようになる。未設定で devcontainer を -起動した場合、`initialize.sh` が警告を出す (devcontainer 自体は起動する)。 +`initialize.sh` が `GH_TOKEN` 未設定なら警告を出します。 ## 起動方法 ### VS Code Dev Containers -1. VS Code に「Dev Containers」拡張をインストール -2. リポジトリを VS Code で開く -3. コマンドパレットから **Dev Containers: Reopen in Container** +```bash +export GH_TOKEN=$(gh auth token) # 必須 (上述) +code /path/to/smalruby3-editor +``` + +VS Code 側で `Cmd+Shift+P` → **Dev Containers: Reopen in Container**。 ### devpod (CLI) @@ -91,44 +63,94 @@ brew install devpod devpod provider add docker # 起動 -cd -devpod up . +export GH_TOKEN=$(gh auth token) # 必須 +cd /path/to/smalruby3-editor +devpod up . --ide none # or --ide vscode -# Claude Code をコンテナ内で起動 -devpod ssh smalruby3-editor -- claude +# シェルに入る +devpod ssh smalruby3-editor # 停止 devpod stop smalruby3-editor + +# 完全削除 +devpod delete smalruby3-editor ``` -### docker compose 直接 (CLI only) +## マウント解説 -```bash -# devcontainer のオーバーレイを適用して起動 -docker compose -f docker-compose.yml -f .devcontainer/docker-compose.devcontainer.yml up -d app +### 全員共通 (テンプレートに有効状態で含まれる) -# コンテナに入る -docker compose -f docker-compose.yml -f .devcontainer/docker-compose.devcontainer.yml exec app bash -``` +| ホスト側 | コンテナ内 | 用途 | +|---|---|---| +| repo root | `/app` | 作業ディレクトリ | +| `~/.gitconfig` | `/root/.gitconfig` (ro) | git ユーザー情報 | +| `~/.config/gh` | `/root/.config/gh` | gh CLI 設定 | +| named volume `smalruby3-editor_smalruby3-editor_node_modules` | `/app/node_modules` | compose と共有 | +| named volume `smalruby3-editor_smalruby3-editor_root_npm` | `/root/.npm` | npm cache 共有 | +| named volume `smalruby3-editor_smalruby3-editor_root_cache` | `/root/.cache` | 各種 cache 共有 | + +### 個人 (テンプレートにコメントアウトで含まれる) + +| ホスト側 | コンテナ内 | 用途 | +|---|---|---| +| `~/ghq` | `/ghq` | 関連 OSS の参照・push | +| `~/.claude/settings.json` | 同上 (ro) | Claude Code グローバル設定 (host で編集、container は読むだけ) | +| `~/.claude/skills` | 同上 (ro) | 自作 skills | +| `~/.claude/plugins` | 同上 (ro) | プラグイン | +| `~/.claude/statusline-command.sh` | 同上 (ro) | カスタム statusline | +| `~/.claude/projects/-app` | `/root/.claude/projects/-app` (rw) | このプロジェクト固有 memory (host と共有) | -## 既存ワークフローへの影響 +### Claude Code 認証は **マウントしない** (重要) -| 既存仕組み | 影響 | -|---|---| -| `docker compose run --rm app ...` | 変更なし。devcontainer を立てなくても従来通り動く | -| `bin/dx` | 変更なし(devcontainer 内では二重 docker を避けるラッパーを後続フェーズで検討) | -| `bin/setup-worktree` | `.devcontainer/post-create.sh` 経由で **worktree のときだけ** 実行(main checkout では skip) | -| git worktree | compose の `name: smalruby3-editor` 固定で named volume を共有 | +`~/.claude.json` は host のものを意図的に共有しない。理由: -## AWS CDK deploy について +- container 内 Claude が auto-update すると host の version 表記を書き換える (host + 側 Claude との不整合発生源になる) +- host secrets / API tokens を container に持ち込まないという Anthropic 公式の + 原則とも整合する + +代わりに container 内では **初回 `claude` 起動時にログインを行う**。container fs に +保存された auth は `devpod stop` / `devpod up` を跨いで永続。`devpod delete` または +devcontainer rebuild で消えるが、再ログインは数十秒で済む。 + +加えて **`DISABLE_AUTOUPDATER=1`** を `containerEnv` に設定済み。container 内 +Claude は自動アップデートしない。version 固定は IDE 側で features が install する +最新版を build 時に決め打ちする扱い。 + +**マウントしないもの (全員)**: `~/.ssh`, `~/.aws`, `~/Documents`, `~/Downloads`, +`~/Desktop`, `~/Library`, 他案件ディレクトリ、**`~/.claude/projects/`, +`~/.claude/sessions/`, `~/.claude/history.jsonl`, `~/.claude/file-history/`, +`~/.claude/shell-snapshots/`** (他プロジェクトの転写・履歴の漏れを防ぐ)。 + +### Claude Code memory の共有メカニズム + +このプロジェクトの memory (`~/.claude/projects//memory/`) はホスト側スラッグがユーザーのパスに依存するため、`initialize.sh` がホスト側に +**`~/.claude/projects/-app` → `~/.claude/projects/`** のシンボリックリンクを作成する。コンテナ内では workspace=`/app` のため slug=`-app` で固定され、リンク経由で同じ memory を読み書きできる。他のマシンや他の worktree に切り替えても、`initialize.sh` がそのときの host slug にリンクを張り直す。 -`~/.aws` はマウントしません。`cdk deploy` は人間が diff を見て発動する操作なので、 -**ホスト側で実行** することを推奨します。コンテナ内では `cdk synth` / `cdk diff` まで(AWS 認証不要)。 +## なぜ docker-compose を使わないか + +過去のリビジョンでは `dockerComposeFile` 形式で `app` サービスを共有していたが、以下の問題があり Dockerfile-only に移行した: + +1. **devpod での features 失敗**: `dockerComposeFile` + `features` の組み合わせで devpod が Dockerfile-with-features の build context を壊し、`COPY entrypoint.sh` が "not found" で失敗 +2. **port 衝突**: 通常 `docker compose up app` と devcontainer 用 compose が同時に host port 8601 を取り合い、後発の network attach が失敗 → DNS 孤立 +3. **個人別の override 困難**: compose の override 構文 (`docker-compose.local.yml`) は便利だが、devcontainer.json 自体の個人別カスタマイズには使えない + +Dockerfile-only にすることで: +- devpod でも features (claude-code, github-cli) がそのまま動く +- 通常 compose とは独立した volume / network のため衝突が起きない (named volume だけは共有して deps を再利用) +- `.devcontainer/devcontainer.json` 自体を `.gitignore` 対象にして個人別に編集できる + +通常開発で `docker compose` を使い続けたい人は、本ディレクトリを無視して従来通り `docker compose run --rm app ...` 等を使えば良い。 + +## AWS CDK deploy について -必要時のみ短命 STS token を環境変数で注入する運用も可能(要検討)。 +`~/.aws` は **マウントしません**。`cdk deploy` は人間が diff を見て発動する操作なので、 +**ホスト側で実行** することを推奨します。コンテナ内では `cdk synth` / `cdk diff` まで (AWS 認証不要)。 ## トラブルシューティング -- **gh auth が効かない**: macOS では PAT が keychain に格納されるため、`export GH_TOKEN=$(gh auth token)` を実行してから devcontainer を起動する (上記「gh CLI の認証トークンについて」参照) -- **`/ghq` が空**: `ghq root` の出力が `~/ghq` 以外を指していないか確認 -- **ポート 8601 が見えない**: VS Code は `forwardPorts` で自動転送。devpod / 直接 compose では `docker-compose.yml` の `ports` 設定で `localhost:8601` に公開される +- **gh auth が効かない**: macOS では PAT が keychain に格納されるため、`export GH_TOKEN=$(gh auth token)` を実行してから devcontainer を起動する +- **`/ghq` が空 or マウントエラー**: `devcontainer.json` の `mounts` で ghq 行をアンコメントしているか確認。`ghq root` が `~/ghq` 以外を指している場合は source パスを書き換える +- **node_modules が共有されない**: `docker compose run --rm app npm install` を一度実行して named volume を作ってから devcontainer を起動する +- **`devcontainer.json` が無いと言われる**: `.example` からコピーする (上記「初回セットアップ」を参照) diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json deleted file mode 100644 index 93d4d3e6533..00000000000 --- a/.devcontainer/devcontainer-lock.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "features": { - "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": { - "version": "1.0.5", - "resolved": "ghcr.io/anthropics/devcontainer-features/claude-code@sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a", - "integrity": "sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a" - }, - "ghcr.io/devcontainers/features/github-cli:1": { - "version": "1.1.0", - "resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671", - "integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671" - } - } -} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index d81dc1f22da..00000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "smalruby3-editor (Claude Code)", - "dockerComposeFile": [ - "../docker-compose.yml", - "docker-compose.devcontainer.yml", - "docker-compose.local.yml" - ], - "service": "app", - "workspaceFolder": "/app", - "overrideCommand": false, - "features": { - "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}, - "ghcr.io/devcontainers/features/github-cli:1": {} - }, - "remoteEnv": { - "GHQ_ROOT": "/ghq", - "GH_TOKEN": "${localEnv:GH_TOKEN}" - }, - "forwardPorts": [8601], - "initializeCommand": ".devcontainer/initialize.sh", - "postCreateCommand": ".devcontainer/post-create.sh", - "customizations": { - "vscode": { - "extensions": [ - "anthropic.claude-code", - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode" - ] - } - } -} diff --git a/.devcontainer/devcontainer.json.example b/.devcontainer/devcontainer.json.example new file mode 100644 index 00000000000..dac8b9c02ea --- /dev/null +++ b/.devcontainer/devcontainer.json.example @@ -0,0 +1,112 @@ +// .devcontainer/devcontainer.json +// +// このファイルは TEMPLATE です。`.devcontainer/devcontainer.json` は +// `.gitignore` 対象。各自で以下のようにコピーして使う: +// +// cp .devcontainer/devcontainer.json.example .devcontainer/devcontainer.json +// # 必要に応じて mounts セクションをコメントアウト/編集 +// +// 詳細は .devcontainer/README.md を参照。 +// +// ## 設計方針 +// - **Dockerfile-only** 構成 (docker-compose は使わない)。devpod でも features が +// そのまま動く。 +// - 既存 docker-compose の named volume (`smalruby3-editor_*_node_modules` 等) を +// そのまま mount することで、`docker compose run app npm install` と +// devcontainer 起動の **両方が同じ deps を共有** する (起動高速化、git +// worktree 切替時もインストール不要)。 +// - 個人マウント (~/.claude*, ~/ghq 等) はテンプレートではコメントアウト。 +// 使うものだけアンコメントする。 +{ + "name": "smalruby3-editor", + + // 既存の Dockerfile をそのまま使う (context = repo root, dockerfile も repo root) + "build": { + "context": "..", + "dockerfile": "../Dockerfile" + }, + + "workspaceFolder": "/app", + "workspaceMount": "source=${localWorkspaceFolder},target=/app,type=bind,consistency=cached", + + // entrypoint.sh の "build if not built" 処理を抑止して container を長寿命化 + "overrideCommand": true, + + "features": { + "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}, + "ghcr.io/devcontainers/features/github-cli:1": {} + }, + + "remoteEnv": { + // macOS の gh は PAT を keychain に保存するため、devcontainer 起動前に + // export GH_TOKEN=$(gh auth token) + // が必要。詳細は README.md。 + "GH_TOKEN": "${localEnv:GH_TOKEN}" + }, + + "containerEnv": { + // container 内 Claude が host の ~/.claude.json のバージョンを + // 勝手に書き換えるのを防ぐ。host との version drift 防止。 + "DISABLE_AUTOUPDATER": "1" + }, + + "forwardPorts": [8601], + + "initializeCommand": ".devcontainer/initialize.sh", + "postCreateCommand": ".devcontainer/post-create.sh", + + "mounts": [ + // ----- 全員共通 (必須) ----- + // git の identity (commit author 等) + "source=${localEnv:HOME}/.gitconfig,target=/root/.gitconfig,type=bind,readonly", + // gh CLI 設定 (PAT 本体は GH_TOKEN env で渡す。これは hosts.yml のユーザ情報のみ) + "source=${localEnv:HOME}/.config/gh,target=/root/.config/gh,type=bind", + + // ----- 既存 docker-compose との volume 共有 ----- + // `docker compose run app npm install` で生成された node_modules を + // devcontainer でもそのまま使う。volume 名は _ + // 形式で、project_name は docker-compose.yml の `name:` 指定で固定済み。 + "source=smalruby3-editor_smalruby3-editor_node_modules,target=/app/node_modules,type=volume", + "source=smalruby3-editor_smalruby3-editor_root_npm,target=/root/.npm,type=volume", + "source=smalruby3-editor_smalruby3-editor_root_cache,target=/root/.cache,type=volume" + + // ----- 個人マウント (必要なものだけアンコメント) ----- + + // 関連 OSS の参照 (smalruby/ruby-sdl2, scratchfoundation/scratch-editor 等) + // ,"source=${localEnv:HOME}/ghq,target=/ghq,type=bind" + + // ---- Claude Code 関連 (Anthropic 公式 + 安全側を考慮した設計) ---- + // + // - **認証 (~/.claude.json) は mount しない**。container 内で初回 `claude` 起動時に + // 別途ログインする。container の auth state は container 内 fs に残り、 + // `devpod delete` または devcontainer rebuild まで永続。host の ~/.claude.json + // は触らない (auto-update でも host が汚れない)。 + // - **DISABLE_AUTOUPDATER=1** (上記 containerEnv) で auto-update も止める。 + // host との version drift を防ぐ。 + // - skills / plugins / settings / statusline は host から **read-only** + // bind で持ち込む。container 内では書き換えできないので汚染しない。 + // - **memory のみ bind rw** で host と共有 (このプロジェクト固有の memory + // なので host 側 Claude と相互に読み書きする想定)。 + // - その他 (~/.claude/projects/*, sessions, history, file-history) は + // マウントしないことで他プロジェクトの transcripts を隔離。 + + // host の skills / plugins / 設定を読むだけ (container は書き換え不可) + // ,"source=${localEnv:HOME}/.claude/settings.json,target=/root/.claude/settings.json,type=bind,readonly" + // ,"source=${localEnv:HOME}/.claude/skills,target=/root/.claude/skills,type=bind,readonly" + // ,"source=${localEnv:HOME}/.claude/plugins,target=/root/.claude/plugins,type=bind,readonly" + // ,"source=${localEnv:HOME}/.claude/statusline-command.sh,target=/root/.claude/statusline-command.sh,type=bind,readonly" + + // このプロジェクト固有の Claude memory (initialize.sh が `-app` シンボリックリンクを作る) + // ,"source=${localEnv:HOME}/.claude/projects/-app,target=/root/.claude/projects/-app,type=bind" + ], + + "customizations": { + "vscode": { + "extensions": [ + "anthropic.claude-code", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] + } + } +} diff --git a/.devcontainer/docker-compose.devcontainer.yml b/.devcontainer/docker-compose.devcontainer.yml deleted file mode 100644 index ca3d58072c3..00000000000 --- a/.devcontainer/docker-compose.devcontainer.yml +++ /dev/null @@ -1,14 +0,0 @@ -services: - app: - # devcontainer 用に長寿命化(既存 compose は `npm start` で短命) - command: sleep infinity - volumes: - # 全開発者で共通のマウント。個人依存のもの (~/ghq, ~/.claude 等) は - # docker-compose.local.yml に置く (gitignore、各自で .example からコピー)。 - - type: bind - source: ${HOME}/.gitconfig - target: /root/.gitconfig - read_only: true - - type: bind - source: ${HOME}/.config/gh - target: /root/.config/gh diff --git a/.devcontainer/docker-compose.local.yml.example b/.devcontainer/docker-compose.local.yml.example deleted file mode 100644 index 242d8a8332d..00000000000 --- a/.devcontainer/docker-compose.local.yml.example +++ /dev/null @@ -1,55 +0,0 @@ -# .devcontainer/docker-compose.local.yml -# -# 個人ごとに devcontainer を拡張するための override。`.gitignore` 対象。 -# 使い方: このファイルを `docker-compose.local.yml` にコピーし、自分の環境に -# 合わせて編集する。 -# -# cp .devcontainer/docker-compose.local.yml.example \ -# .devcontainer/docker-compose.local.yml -# -# devcontainer.json はこのファイルを dockerComposeFile に含めるため、Reopen -# in Container すると自動で適用される。空のままでも害はない。 -# -# 注意: -# - bind mount のソースが存在しないと devcontainer の起動が失敗するため、 -# 不要なエントリはコメントアウトしておく。 -# - Claude Code を使わない人は claude セクションを丸ごと省略してよい。 -# - ghq を使わない人は ghq セクションを省略してよい。 - -services: - app: - volumes: - # --- ghq (関連 OSS の参照) --- - # 自分の ghq root が ~/ghq でない場合は source を書き換える。 - - type: bind - source: ${HOME}/ghq - target: /ghq - - # --- Claude Code: グローバル設定・認証のみ共有 --- - # 他プロジェクトの transcripts / sessions / history は意図的に - # マウントしないことで漏れを防ぐ。詳細は README.md 参照。 - - type: bind - source: ${HOME}/.claude.json - target: /root/.claude.json - - type: bind - source: ${HOME}/.claude/settings.json - target: /root/.claude/settings.json - read_only: true - - type: bind - source: ${HOME}/.claude/skills - target: /root/.claude/skills - read_only: true - - type: bind - source: ${HOME}/.claude/plugins - target: /root/.claude/plugins - read_only: true - - type: bind - source: ${HOME}/.claude/statusline-command.sh - target: /root/.claude/statusline-command.sh - read_only: true - # このプロジェクトの memory のみ共有。initialize.sh が - # ${HOME}/.claude/projects/-app を host slug へのシンボリックリンクと - # して作成する。 - - type: bind - source: ${HOME}/.claude/projects/-app - target: /root/.claude/projects/-app diff --git a/.devcontainer/initialize.sh b/.devcontainer/initialize.sh index cbad9e744e1..fc847c168fb 100755 --- a/.devcontainer/initialize.sh +++ b/.devcontainer/initialize.sh @@ -2,35 +2,19 @@ # .devcontainer/initialize.sh # # Runs on the host before the devcontainer starts (devcontainer.json の -# initializeCommand から呼ばれる)。以下を行う: +# initializeCommand から呼ばれる)。役割: # -# 1. docker-compose.local.yml が無ければ空の stub を作る -# (devcontainer.json は常にこのファイルを参照するため) -# 2. GH_TOKEN が export されていなければ案内を出す -# (macOS の gh は PAT を keychain に保存するため、~/.config/gh を -# bind mount しただけではコンテナ内 gh から認証できない) -# 3. ホスト側に ~/.claude/projects/-app シンボリックリンクを作成 -# (Claude Code を使う人向けの per-project memory bridge) +# 1. GH_TOKEN が export されていなければ案内を出す +# (macOS の gh は PAT を keychain に保存するため、~/.config/gh の bind +# mount だけではコンテナ内 gh から認証できない) +# 2. Claude Code を使う人向けに ~/.claude/projects/-app シンボリックリンクを +# 作成 (per-project memory bridge) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -# --- 1) docker-compose.local.yml の stub 作成 ----------------------------- -LOCAL_COMPOSE="${SCRIPT_DIR}/docker-compose.local.yml" -if [[ ! -f "${LOCAL_COMPOSE}" ]]; then - cat >"${LOCAL_COMPOSE}" <<'EOF' -# Auto-generated by .devcontainer/initialize.sh. -# 個人ごとの mount を追加したい場合は -# .devcontainer/docker-compose.local.yml.example を参考に書き換える。 -# このファイルは .gitignore 対象なので、個人の変更は他開発者に共有されない。 -services: - app: {} -EOF - echo "initialize: created empty ${LOCAL_COMPOSE} (edit to add personal mounts)" -fi - -# --- 2) GH_TOKEN チェック ------------------------------------------------- +# --- 1) GH_TOKEN チェック ------------------------------------------------- if [[ -z "${GH_TOKEN:-}" ]]; then if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then cat <<'MSG' >&2 @@ -49,9 +33,9 @@ MSG fi fi -# --- 3) Claude Code per-project memory bridge ---------------------------- +# --- 2) Claude Code per-project memory bridge ---------------------------- # Claude Code をホスト側にインストールしていないユーザは何もしない。 -# このセクションは本人が docker-compose.local.yml で +# このセクションは本人が devcontainer.json で # ~/.claude/projects/-app マウントを有効にしている場合に意味を持つ。 if [[ -d "${HOME}/.claude/projects" ]]; then WORKSPACE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 26107b7ff3c..2bbdfb89e2a 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -6,7 +6,7 @@ # # - worktree であれば bin/setup-worktree を実行 (env コピー + npm install + build:dev) # - main checkout であれば既存の node_modules / dist / .env がそのままあるので -# 何もしない (husky hooks も既にインストール済みのはず) +# 何もしない (gh / claude のインストールは devcontainer features が build 時に行う) set -euo pipefail diff --git a/.gitignore b/.gitignore index 26c1bdffe48..5ef55f91a2b 100644 --- a/.gitignore +++ b/.gitignore @@ -160,5 +160,5 @@ infra/**/tmp/ infra/**/.cdk.staging/ infra/**/cdk-out-*/ -# devcontainer の個人用 mount override (initialize.sh が空 stub を生成する) -.devcontainer/docker-compose.local.yml +# devcontainer の個人別設定 (各自で .example からコピーして mounts を編集する) +.devcontainer/devcontainer.json