Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
cf70e09
docs(superpowers): add spec for worktree-resume integration
tqer39 Apr 25, 2026
d84e92d
docs(spec): セッション存在判定をマーカーから ~/.claude/projects 参照へ変更
tqer39 Apr 25, 2026
2f97113
docs(plan): worktree-resume integration の実装計画を追加
tqer39 Apr 25, 2026
81b8bfc
feat(namegen): worktree/session 名ジェネレータを追加
tqer39 Apr 25, 2026
8250549
feat(claude): -n フラグと --continue を組み込み Continue にリネーム
tqer39 Apr 25, 2026
82201f2
feat(worktree): ~/.claude/projects 参照で HasSession を判定
tqer39 Apr 25, 2026
9b9f86a
refactor(worktree): HasSession で os.Getenv("HOME") を使用
tqer39 Apr 25, 2026
5656127
feat(worktree): Info.HasSession を List で埋める
tqer39 Apr 25, 2026
f91d0cc
feat(picker): RESUME / NEW バッジのスタイル
tqer39 Apr 25, 2026
ed8020b
feat(picker): worktree 行を L2 4 行レイアウトに拡張
tqer39 Apr 25, 2026
78d98f5
feat(tips): random TIPS パッケージを追加
tqer39 Apr 25, 2026
aed40aa
feat(picker): footer に random TIPS を表示
tqer39 Apr 25, 2026
9baacfa
feat(ccw): worktree 名 = session 名 を確立、resume を既存 worktree のデフォルトに
tqer39 Apr 25, 2026
f7d1d5c
test(e2e): 新規 worktree で --worktree と -n が同一名であることを確認
tqer39 Apr 25, 2026
51f732f
docs: --resume 警告を撤去し RESUME/NEW バッジ・命名規約を追記
tqer39 Apr 25, 2026
ba23913
fix(ccw): HasSession=false で --continue を呼ばないよう先に分岐
tqer39 Apr 25, 2026
a7b3598
fix: CodeRabbit 指摘 (resume fallback / in-worktree launch / 最低バージョン)
tqer39 Apr 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,26 @@ PR state badge (shown only when [`gh`](https://cli.github.com/) is installed and
| 🟪 `[MERGED]` | PR has been merged |
| 🟥 `[CLOSED]` | PR was closed without merging |

Selecting a worktree opens `[r] run` / `[d] delete` / `[b] back`. `run` launches a fresh `claude --permission-mode auto` in that worktree — ccw does **not** reuse Claude Code session IDs (no `--resume` under the hood). Bulk shortcuts (`[delete all]`, `[clean pushed]`, `[custom select]`) remove many at once; dirty items require either `--force` or a three-choice confirm (`y` force · `s` skip dirty · `N` cancel).
Session badge:

| Badge | Meaning |
|---|---|
| 💬 `RESUME` | Past session log exists — `run` restores the conversation |
| ⚡ `NEW` | No session log — `run` starts fresh |

Selecting a worktree opens `[r] run` / `[d] delete` / `[b] back`. `run` calls `claude --continue` to restore the past conversation when a session log exists, or `claude -n <worktree>` to start fresh otherwise. Bulk shortcuts (`[delete all]`, `[clean pushed]`, `[custom select]`) remove many at once; dirty items require either `--force` or a three-choice confirm (`y` force · `s` skip dirty · `N` cancel).

Without `gh`, the picker stays functional and shows a hint; rate-limit / network failures hide the PR column silently.

> ⚠️ **Passing `--resume` through `--` is unsupported.**
> `ccw -n -- --resume ID` and `ccw -s -- --resume ID` combine `claude --worktree` (new worktree) with `--resume` (continue a prior session); the resumed transcript's file references won't match the freshly-created worktree. Even the picker's re-entry path suffers the same mismatch if the selected worktree differs from the session's original. If a resumed session is what you want, run `claude --resume ID` directly — bypass ccw.
### Naming convention

When ccw creates a new worktree, the worktree directory and the Claude Code session name are kept 1:1:

- Directory: `<repo>/.claude/worktrees/<name>/`
- Branch: `worktree-<name>`
- Session name: `<name>` (set via `claude -n <name>`)

`<name>` is generated like `quick-falcon-7bd2`. Renaming the session manually with `/rename` is fine — ccw does not track it, and `--continue` keys off the working directory so conversation restore is unaffected.

## 📦 Installation

Expand All @@ -99,7 +113,7 @@ Make sure `~/.local/bin` is on your `PATH`.
### Requirements

- [`git`](https://git-scm.com/)
- [Claude Code](https://docs.claude.com/claude-code) `>= 2.1.49` — the `--worktree` flag that ccw relies on was introduced in 2.1.49 (2026-02-19). ccw offers to install `claude` via npm / brew if missing.
- [Claude Code](https://docs.claude.com/claude-code) `>= 2.1.118` — ccw uses `--worktree <name>` together with `-n <name>`, which was confirmed working from 2.1.118 onward. ccw offers to install `claude` via npm / brew if missing.
- *(optional)* [`gh`](https://cli.github.com/) — enables PR info in the picker
- *(optional)* [superpowers](https://github.com/obra/superpowers) plugin — auto-checked when `-s` is used

Expand Down
52 changes: 44 additions & 8 deletions cmd/ccw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/tqer39/ccw-cli/internal/claude"
"github.com/tqer39/ccw-cli/internal/cli"
"github.com/tqer39/ccw-cli/internal/gitx"
"github.com/tqer39/ccw-cli/internal/namegen"
"github.com/tqer39/ccw-cli/internal/picker"
"github.com/tqer39/ccw-cli/internal/superpowers"
"github.com/tqer39/ccw-cli/internal/ui"
Expand Down Expand Up @@ -71,7 +72,8 @@ func run(flags cli.Flags) int {
}

if flags.NewWorktree {
code, err := claude.LaunchNew(mainRepo, preamble, flags.Passthrough)
name := namegen.Generate()
code, err := claude.LaunchNew(mainRepo, name, preamble, flags.Passthrough)
if err != nil {
ui.Error("%v", err)
return 1
Expand All @@ -93,19 +95,15 @@ func runPicker(mainRepo string, passthrough []string, interactive bool) int {
case picker.ActionCancel:
return 0
case picker.ActionNew:
code, err := claude.LaunchNew(mainRepo, "", passthrough)
name := namegen.Generate()
code, err := claude.LaunchNew(mainRepo, name, "", passthrough)
if err != nil {
ui.Error("%v", err)
return 1
}
return code
case picker.ActionResume:
code, err := claude.Resume(sel.Path, passthrough)
if err != nil {
ui.Error("%v", err)
return 1
}
return code
return runResume(sel, passthrough)
case picker.ActionDelete:
if err := worktree.Remove(mainRepo, sel.Path, sel.ForceDelete); err != nil {
ui.Error("%v", err)
Expand All @@ -120,6 +118,35 @@ func runPicker(mainRepo string, passthrough []string, interactive bool) int {
}
}

// runResume launches `claude --continue` when the worktree has a session log,
// or `claude -n <name>` for fresh starts. Falls back to a new launch when
// `--continue` exits non-zero (e.g. session file removed underfoot).
func runResume(sel picker.Selection, passthrough []string) int {
if !sel.HasSession {
name := worktreeName(sel.Path)
code, err := claude.LaunchNew(sel.Path, name, "", passthrough)
if err != nil {
ui.Error("%v", err)
return 1
}
return code
}
code, err := claude.Continue(sel.Path, passthrough)
if err != nil {
ui.Error("%v", err)
return 1
}
if code != 0 {
name := worktreeName(sel.Path)
code, err = claude.LaunchNew(sel.Path, name, "", passthrough)
if err != nil {
ui.Error("%v", err)
return 1
}
}
return code
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

func applyBulkDelete(mainRepo string, bulk picker.BulkDeletion) int {
errs := 0
for _, p := range bulk.Paths {
Expand Down Expand Up @@ -237,6 +264,15 @@ func resolveMainRepo() (string, error) {
return mainRepo, nil
}

func worktreeName(path string) string {
for i := len(path) - 1; i >= 0; i-- {
if path[i] == '/' {
return path[i+1:]
}
}
return path
}

func maybeSuperpowers(enabled bool, interactive, assumeYes bool) (string, error) {
if !enabled {
return "", nil
Expand Down
22 changes: 18 additions & 4 deletions docs/README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,26 @@ PR 状態バッジ([`gh`](https://cli.github.com/) がインストール済み
| 🟪 `[MERGED]` | マージ済みの PR |
| 🟥 `[CLOSED]` | マージされずにクローズされた PR |

worktree を選択すると `[r] run` / `[d] delete` / `[b] back` のサブメニューに遷移。`run` は選択した worktree で `claude --permission-mode auto` を新規起動するもので、Claude Code のセッション ID を引き継ぐ(`--resume` 相当の)操作は**行いません**。`[delete all]` / `[clean pushed]` / `[custom select]` は一括削除のショートカットで、dirty を含む場合は `--force` か、または 3 択確認 (`y` force · `s` dirty を除外 · `N` キャンセル) を経由します。
セッションバッジ:

| バッジ | 意味 |
|---|---|
| 💬 `RESUME` | 過去のセッションログがあり、`run` で会話を復元できる |
| ⚡ `NEW` | セッションログ無し。`run` は新規起動 |

worktree を選択すると `[r] run` / `[d] delete` / `[b] back` のサブメニューに遷移。`run` はセッションログが残っていれば `claude --continue` で**過去会話を復元**、無ければ `claude -n <worktree名>` で新規起動します。`[delete all]` / `[clean pushed]` / `[custom select]` は一括削除のショートカットで、dirty を含む場合は `--force` か、または 3 択確認 (`y` force · `s` dirty を除外 · `N` キャンセル) を経由します。

`gh` が無い場合も picker は動作し、ヒントを下部に表示。rate limit / ネットワークエラー時は PR 列だけを静かに隠します。

> ⚠️ **`-- --resume ID` のパススルーは非推奨です。**
> `ccw -n -- --resume ID` や `ccw -s -- --resume ID` は `claude --worktree`(新 worktree 作成)と `--resume`(過去セッション継続)を同時に使うことになり、resume された会話中のファイル参照が新 worktree の実体と合いません。picker 経由で既存 worktree に再入場する場合も、選んだ worktree と session 元の worktree が違えば同様のズレが出ます。過去セッションを resume したいときは ccw を介さず直接 `claude --resume ID` を呼んでください。
### 命名規約

ccw は新規 worktree を作るとき、worktree 名と Claude Code のセッション名を 1:1 で揃えます:

- ディレクトリ: `<repo>/.claude/worktrees/<name>/`
- ブランチ: `worktree-<name>`
- セッション名: `<name>`(`claude -n <name>` で設定)

`<name>` は `quick-falcon-7bd2` のようにジェネレータが生成します。手動で `/rename` した場合も ccw は追跡しません。`--continue` は cwd 基準で動くので会話復元には影響しません。

## 📦 インストール

Expand All @@ -99,7 +113,7 @@ go build -o ~/.local/bin/ccw ~/ccw-cli/cmd/ccw
### 依存

- [`git`](https://git-scm.com/)
- [Claude Code](https://docs.claude.com/claude-code) `>= 2.1.49` — ccw が利用する `--worktree` フラグは 2.1.49 (2026-02-19) で追加されました。未導入なら起動時に npm / brew で入れるかを確認します。
- [Claude Code](https://docs.claude.com/claude-code) `>= 2.1.118` — ccw `--worktree <name>` と `-n <name>` を併用するため、2.1.118 以降で動作確認済みです。未導入なら起動時に npm / brew で入れるかを確認します。
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
- *(optional)* [`gh`](https://cli.github.com/) — picker で PR 情報を表示
- *(optional)* [superpowers](https://github.com/obra/superpowers) プラグイン — `-s` 利用時に自動チェック

Expand Down
Loading