feat(namegen): deterministic worktree names (ccw-<owner>-<repo>-<shorthash6>)#48
Conversation
セッション名から「どの repo の、どの base commit から切ったか」が 判別できるよう、ランダム slug を origin owner/repo + default branch の short SHA に置き換える設計。
10 タスク構成 (TDD): gitx 拡張 (Origin/Branch/ShortHash/ParseOriginURL) → namegen 全置換 (normalize/buildName/Generate) → cmd/ccw 連携 → README 更新。
f783ee1 (bubbletea v2 へのアップグレード) で picker テストが下記により ビルド失敗していた: - view_test.go: m.View() の戻り値が tea.View 構造体に変更 → m.View().Content (string) を参照 - style_test.go: lipgloss v2 で ColorProfile()/SetColorProfile() および termenv 直接参照が削除 → プロファイル強制を削除(lipgloss v2 は Render 段階で常に ANSI を返す)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…po>-<shorthash6> ランダム slug Generate() を Generate(mainRepo) に置換。 - normalize: lowercase + [a-z0-9-] + dash 圧縮 + trim - buildName: ccw-<owner>-<repo>-<shorthash> の合成、衝突時 -N 付与(cap 99) - Generate: gitx (OriginURL / DefaultBranch / ShortHash) + filesystem ベースの 衝突検出。フェイク差し替え可能(テスト用) - origin 未設定時は owner=local、repo=basename にフォールバック - cmd/ccw/main.go の 2 箇所をエラー対応に更新
…ash6> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Addresses the final code review of the deterministic-worktree-name branch: - takenNames now unions .claude/worktrees entries with `git worktree list` basenames (spec required both; previously only the directory was checked). - Adds worktreeListFn hook so namegen tests stay hermetic. - Adds TestGenerate_CollisionWithGitWorktree and TestGenerate_ShortHashError to cover paths the previous suite never exercised. - Adds ssh:// and git:// cases to TestParseOriginURL. - main.go now appends a hint to the Generate failure message pointing at the branch / origin/HEAD remediation called out in the spec. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 57 minutes and 30 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR implements deterministic worktree naming by replacing random slug generation with a systematic format Changes
Sequence DiagramsequenceDiagram
participant CLI as CLI (main.go)
participant NameGen as Namegen
participant GitX as GitX Helpers
participant Repo as Repository
participant FS as Filesystem
CLI->>NameGen: Generate(mainRepo)
activate NameGen
NameGen->>GitX: OriginURL(mainRepo)
GitX->>Repo: git config --get remote.origin.url
Repo-->>GitX: origin URL / empty
GitX-->>NameGen: URL or empty string
alt Origin exists
NameGen->>GitX: ParseOriginURL(url)
GitX-->>NameGen: owner, repo, error?
else Origin missing
NameGen-->>NameGen: Use "local" as owner
end
NameGen->>GitX: DefaultBranch(mainRepo)
GitX->>Repo: git symbolic-ref refs/remotes/origin/HEAD
alt origin/HEAD set
Repo-->>GitX: default branch
else Try fallbacks
GitX->>Repo: git rev-parse --verify main/master
Repo-->>GitX: ref exists
end
GitX-->>NameGen: branch name or error
NameGen->>GitX: ShortHash(mainRepo, branch, 6)
GitX->>Repo: git rev-parse --short=6 branch
Repo-->>GitX: short hash
GitX-->>NameGen: hash
NameGen->>NameGen: normalize(owner, repo, hash)<br/>→ slug-safe tokens
NameGen->>NameGen: baseVersion = ccw-{owner}-{repo}-{hash}
NameGen->>FS: ls .claude/worktrees/
FS-->>NameGen: existing names
NameGen->>Repo: git worktree list --porcelain
Repo-->>NameGen: worktree basenames
NameGen->>NameGen: buildName(baseVersion)<br/>check collisions,<br/>append -2, -3, etc.
NameGen-->>CLI: final name or error
deactivate NameGen
alt Success
CLI->>Repo: git worktree add ...
else Error
CLI-->>CLI: Print error hint
CLI-->>CLI: Exit 1
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cmd/ccw/main.go`:
- Around line 74-79: The current error handling around namegen.Generate in the
NewWorktree path logs a narrow, duplicated hint that can mislead users; extract
a small helper (e.g., reportNamegenError) used by the NewWorktree branch and
runPicker to format errors from namegen.Generate, surface the raw error in the
ui.Error message, and tailor the user hint based on error categories (origin
URL/parse, missing tip/default branch, collision cap, worktree enumeration,
normalization) rather than always suggesting the default-branch fix; update
calls to namegen.Generate to call the helper so both sites share the same logic
and include the raw error details for debugging while giving a concise,
category-specific hint.
In `@docs/superpowers/plans/2026-04-25-deterministic-worktree-name.md`:
- Around line 437-457: The test case list contains a stale expectation for
normalize: the entry {"repo.git", "repo"} is incorrect because
normalize("repo.git") yields "repo-git"; update the test cases in the cases
slice (used by the t.Run loop) by either removing the {"repo.git", "repo"} line
or changing its expected value to {"repo.git", "repo-git"} so it matches the
implemented normalize function.
In `@docs/superpowers/specs/2026-04-25-deterministic-worktree-name-design.md`:
- Around line 188-200: The parenthetical claiming "normalize 単独でも `.` は `-` 置換 →
連続圧縮 → trim で消える" is incorrect: normalize("repo.git") => "repo-git" and the
interior dash is not removed by Trim; update the text to either remove that
parenthetical or replace it with a short clarification that ParseOriginURL must
strip a trailing `.git` before calling normalize, referencing the normalize
function and ParseOriginURL so readers know the implementation relies on
ParseOriginURL to remove `.git`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 119acbb3-afb8-4e03-b052-164eeae25873
📒 Files selected for processing (15)
.github/workflows/ci.yml.github/workflows/release.ymlREADME.mdcmd/ccw/main.godocs/README.ja.mddocs/superpowers/plans/2026-04-25-deterministic-worktree-name.mddocs/superpowers/specs/2026-04-25-deterministic-worktree-name-design.mdinternal/gitx/branch.gointernal/gitx/branch_test.gointernal/gitx/origin.gointernal/gitx/origin_test.gointernal/namegen/namegen.gointernal/namegen/namegen_test.gointernal/picker/style_test.gointernal/picker/view_test.go
💤 Files with no reviewable changes (1)
- internal/picker/style_test.go
| if flags.NewWorktree { | ||
| name := namegen.Generate() | ||
| name, err := namegen.Generate(mainRepo) | ||
| if err != nil { | ||
| ui.Error("generate worktree name: %v\nhint: ensure a 'main' or 'master' branch with at least one commit, or run `git remote set-head origin -a`", err) | ||
| return 1 | ||
| } |
There was a problem hiding this comment.
Hint is too narrow for the error space, and duplicated at the picker call site.
namegen.Generate can fail for several distinct reasons (origin URL parse, default branch resolution, short hash, worktree enumeration, collision cap of 99, normalization). The current hint only addresses the default-branch case and may mislead users when the failure is e.g. a malformed origin URL, a missing tip commit, or a saturated collision suffix space. The same exact message is also duplicated at lines 102–106 in runPicker — worth extracting a helper and/or tailoring the hint to the underlying error category.
♻️ Suggested refactor — extract helper, log raw error
+func reportNameGenError(err error) {
+ ui.Error("generate worktree name: %v", err)
+ ui.Info("hint: verify `origin` URL (or unset it for local), that the default branch (origin/HEAD, main, or master) has at least one commit, and that .claude/worktrees does not already contain ccw-<owner>-<repo>-<hash>-{2..99}.")
+}
+
func run(flags cli.Flags) int {
@@
if flags.NewWorktree {
- name, err := namegen.Generate(mainRepo)
- if err != nil {
- ui.Error("generate worktree name: %v\nhint: ensure a 'main' or 'master' branch with at least one commit, or run `git remote set-head origin -a`", err)
- return 1
- }
+ name, err := namegen.Generate(mainRepo)
+ if err != nil {
+ reportNameGenError(err)
+ return 1
+ }
@@
case picker.ActionNew:
- name, err := namegen.Generate(mainRepo)
- if err != nil {
- ui.Error("generate worktree name: %v\nhint: ensure a 'main' or 'master' branch with at least one commit, or run `git remote set-head origin -a`", err)
- return 1
- }
+ name, err := namegen.Generate(mainRepo)
+ if err != nil {
+ reportNameGenError(err)
+ return 1
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if flags.NewWorktree { | |
| name := namegen.Generate() | |
| name, err := namegen.Generate(mainRepo) | |
| if err != nil { | |
| ui.Error("generate worktree name: %v\nhint: ensure a 'main' or 'master' branch with at least one commit, or run `git remote set-head origin -a`", err) | |
| return 1 | |
| } | |
| func reportNameGenError(err error) { | |
| ui.Error("generate worktree name: %v", err) | |
| ui.Info("hint: verify `origin` URL (or unset it for local), that the default branch (origin/HEAD, main, or master) has at least one commit, and that .claude/worktrees does not already contain ccw-<owner>-<repo>-<hash>-{2..99}.") | |
| } | |
| func run(flags cli.Flags) int { | |
| if flags.NewWorktree { | |
| name, err := namegen.Generate(mainRepo) | |
| if err != nil { | |
| reportNameGenError(err) | |
| return 1 | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cmd/ccw/main.go` around lines 74 - 79, The current error handling around
namegen.Generate in the NewWorktree path logs a narrow, duplicated hint that can
mislead users; extract a small helper (e.g., reportNamegenError) used by the
NewWorktree branch and runPicker to format errors from namegen.Generate, surface
the raw error in the ui.Error message, and tailor the user hint based on error
categories (origin URL/parse, missing tip/default branch, collision cap,
worktree enumeration, normalization) rather than always suggesting the
default-branch fix; update calls to namegen.Generate to call the helper so both
sites share the same logic and include the raw error details for debugging while
giving a concise, category-specific hint.
| {"Anthropic", "anthropic"}, | ||
| {"My Org", "my-org"}, | ||
| {"_underscore_", "underscore"}, | ||
| {"--double--dash--", "double-dash"}, | ||
| {"repo.git", "repo"}, | ||
| {"a..b..c", "a-b-c"}, | ||
| {"", ""}, | ||
| {"a", "a"}, | ||
| {"123", "123"}, | ||
| {"日本語repo", "repo"}, | ||
| } | ||
| for _, tc := range cases { | ||
| t.Run(tc.in, func(t *testing.T) { | ||
| got := normalize(tc.in) | ||
| if got != tc.want { | ||
| t.Errorf("normalize(%q) = %q, want %q", tc.in, got, tc.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Stale test expectation: {"repo.git", "repo"} would fail the implemented normalize.
normalize("repo.git") produces repo-git, not repo — the . is replaced by -, and trim only removes leading/trailing dashes, not the interior one. The actual test in internal/namegen/namegen_test.go correctly omits this case (replaced by {"a..b..c", "a-b-c"}). Since this plan was already executed, this is just a stale aside that future readers might use as a reference; consider deleting the line or aligning it with the implemented test set.
📝 Proposed fix
- {"repo.git", "repo"},
{"a..b..c", "a-b-c"},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {"Anthropic", "anthropic"}, | |
| {"My Org", "my-org"}, | |
| {"_underscore_", "underscore"}, | |
| {"--double--dash--", "double-dash"}, | |
| {"repo.git", "repo"}, | |
| {"a..b..c", "a-b-c"}, | |
| {"", ""}, | |
| {"a", "a"}, | |
| {"123", "123"}, | |
| {"日本語repo", "repo"}, | |
| } | |
| for _, tc := range cases { | |
| t.Run(tc.in, func(t *testing.T) { | |
| got := normalize(tc.in) | |
| if got != tc.want { | |
| t.Errorf("normalize(%q) = %q, want %q", tc.in, got, tc.want) | |
| } | |
| }) | |
| } | |
| } | |
| ``` | |
| {"Anthropic", "anthropic"}, | |
| {"My Org", "my-org"}, | |
| {"_underscore_", "underscore"}, | |
| {"--double--dash--", "double-dash"}, | |
| {"a..b..c", "a-b-c"}, | |
| {"", ""}, | |
| {"a", "a"}, | |
| {"123", "123"}, | |
| {"日本語repo", "repo"}, | |
| } | |
| for _, tc := range cases { | |
| t.Run(tc.in, func(t *testing.T) { | |
| got := normalize(tc.in) | |
| if got != tc.want { | |
| t.Errorf("normalize(%q) = %q, want %q", tc.in, got, tc.want) | |
| } | |
| }) | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/superpowers/plans/2026-04-25-deterministic-worktree-name.md` around
lines 437 - 457, The test case list contains a stale expectation for normalize:
the entry {"repo.git", "repo"} is incorrect because normalize("repo.git") yields
"repo-git"; update the test cases in the cases slice (used by the t.Run loop) by
either removing the {"repo.git", "repo"} line or changing its expected value to
{"repo.git", "repo-git"} so it matches the implemented normalize function.
| - `normalize` (segment 単位、slash を含まない入力前提): | ||
| - `Anthropic` → `anthropic` | ||
| - `My Org` → `my-org` | ||
| - `_underscore_` → `underscore` | ||
| - `--double--dash--` → `double-dash` | ||
| - `repo.git` → `repo`(`.git` は `ParseOriginURL` で除去済み前提だが、normalize 単独でも `.` は `-` 置換 → 連続圧縮 → trim で消える) | ||
| - 空文字 → 空文字 | ||
| - `buildName`: | ||
| - `("tqer39", "ccw-cli", "a3f2b1", {})` → `"ccw-tqer39-ccw-cli-a3f2b1"` | ||
| - 衝突 1 個 → `-2` 付与 | ||
| - 衝突 2 個 → `-3` 付与 | ||
| - 衝突 99 個まで → `-99` 付与、100 でエラー | ||
| - `Generate` (integration): `gitx` 関数を関数値で差し替え可能にして hermetic に |
There was a problem hiding this comment.
Minor: incorrect claim about normalize alone handling repo.git.
The parenthetical on line 193 — "normalize 単独でも . は - 置換 → 連続圧縮 → trim で消える" — is not accurate. normalize("repo.git") yields repo-git: the . is replaced by -, but the resulting interior dash isn't removed by strings.Trim (which only strips leading/trailing dashes). The implementation correctly relies on ParseOriginURL stripping .git before normalize is called (matching the comment in internal/namegen/namegen.go line 23), so the design is sound — just this aside is misleading. Consider rewording or deleting the parenthetical to avoid future confusion.
📝 Proposed wording
- - `repo.git` → `repo`(`.git` は `ParseOriginURL` で除去済み前提だが、normalize 単独でも `.` は `-` 置換 → 連続圧縮 → trim で消える)
+ - `repo.git` → `repo`(`.git` は `ParseOriginURL` で事前に除去される。normalize 単独で渡された場合は `repo-git` になる)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - `normalize` (segment 単位、slash を含まない入力前提): | |
| - `Anthropic` → `anthropic` | |
| - `My Org` → `my-org` | |
| - `_underscore_` → `underscore` | |
| - `--double--dash--` → `double-dash` | |
| - `repo.git` → `repo`(`.git` は `ParseOriginURL` で除去済み前提だが、normalize 単独でも `.` は `-` 置換 → 連続圧縮 → trim で消える) | |
| - 空文字 → 空文字 | |
| - `buildName`: | |
| - `("tqer39", "ccw-cli", "a3f2b1", {})` → `"ccw-tqer39-ccw-cli-a3f2b1"` | |
| - 衝突 1 個 → `-2` 付与 | |
| - 衝突 2 個 → `-3` 付与 | |
| - 衝突 99 個まで → `-99` 付与、100 でエラー | |
| - `Generate` (integration): `gitx` 関数を関数値で差し替え可能にして hermetic に | |
| - `normalize` (segment 単位、slash を含まない入力前提): | |
| - `Anthropic` → `anthropic` | |
| - `My Org` → `my-org` | |
| - `_underscore_` → `underscore` | |
| - `--double--dash--` → `double-dash` | |
| - `repo.git` → `repo`(`.git` は `ParseOriginURL` で事前に除去される。normalize 単独で渡された場合は `repo-git` になる) | |
| - 空文字 → 空文字 | |
| - `buildName`: | |
| - `("tqer39", "ccw-cli", "a3f2b1", {})` → `"ccw-tqer39-ccw-cli-a3f2b1"` | |
| - 衝突 1 個 → `-2` 付与 | |
| - 衝突 2 個 → `-3` 付与 | |
| - 衝突 99 個まで → `-99` 付与、100 でエラー | |
| - `Generate` (integration): `gitx` 関数を関数値で差し替え可能にして hermetic に |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/superpowers/specs/2026-04-25-deterministic-worktree-name-design.md`
around lines 188 - 200, The parenthetical claiming "normalize 単独でも `.` は `-` 置換
→ 連続圧縮 → trim で消える" is incorrect: normalize("repo.git") => "repo-git" and the
interior dash is not removed by Trim; update the text to either remove that
parenthetical or replace it with a short clarification that ParseOriginURL must
strip a trailing `.git` before calling normalize, referencing the normalize
function and ParseOriginURL so readers know the implementation relies on
ParseOriginURL to remove `.git`.
# Conflicts: # internal/picker/style_test.go
Per project convention to keep code comments in English while user-facing text and design docs stay in Japanese. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary
quick-falcon-7bd2) を、リポジトリが一目で分かる決定論的形式ccw-<owner>-<repo>-<shorthash6>(例:ccw-tqer39-ccw-cli-a3f2b1) に置換しました。Claude Code のセッション名がディレクトリと 1:1 で揃うため、複数 worktree 並走時の取り違いが解消されます。internal/gitx/にParseOriginURL(SSH/HTTPS/ssh:///git://、.git除去、GitLab nested は末尾 2 segment)、OriginURL、DefaultBranch(origin/HEAD→main→masterフォールバック)、ShortHashを追加。internal/namegen/はGenerate(mainRepo)を中心に再構成し、衝突は-2,-3,… で回避 (cap 99)、origin未設定時はlocal/basename にフォールバックします。docs/superpowers/specs/2026-04-25-deterministic-worktree-name-design.mdと実装計画docs/superpowers/plans/2026-04-25-deterministic-worktree-name.mdを同梱。README EN/JA の Naming convention セクションも刷新しました。internal/picker/*_test.goの bubbletea v2 / lipgloss v2 互換性破損を別コミットで修復。pinactが自動正規化した workflow の version コメントも独立choreコミットに分離しています。Test Plan
go test ./...(209 pass / 14 packages)go vet ./...cleango build ./cmd/ccwsucceeds;ccw -h/-v表示確認lefthook run pre-commit --all-files全 11 hook greenccw -nを実行し.claude/worktrees/ccw-tqer39-ccw-cli-<sha>/が生成されること、Claude Code セッション名が一致することを目視確認🤖 Generated with Claude Code
Summary by CodeRabbit
Documentation
New Features
ccw-<owner>-<repo>-<shorthash6>, derived from git repository information and the default branch's short commit hash, replacing random slug generation.Chores