Skip to content

Add WT_INDEX feature for stable worktree identifiers#16

Merged
agarcher merged 2 commits intomainfrom
wt-index
Jan 12, 2026
Merged

Add WT_INDEX feature for stable worktree identifiers#16
agarcher merged 2 commits intomainfrom
wt-index

Conversation

@agarcher
Copy link
Owner

@agarcher agarcher commented Jan 12, 2026

Summary

  • Implements the WT_INDEX feature from IMPLEMENTATION_PLAN.md
  • Each worktree receives a stable, reusable numeric identifier (starting at 1)
  • Useful for port offsets and resource allocation in development environments
  • Index is stored in .git/worktrees/<name>/wt-index (auto-cleaned by git)
  • Optional index.max config to limit maximum worktrees

Changes

  • Config: Add IndexConfig with max field for optional limit
  • Git package: Add SetWorktreeIndex, GetWorktreeIndex, AllocateIndex functions
  • Hooks: Add Index field to Env, pass WT_INDEX environment variable
  • Create command: Allocate index during worktree creation
  • Delete/Cleanup commands: Pass index to hooks before deletion
  • List command: Display INDEX column in output
  • Example hook: Add setup-ports.sh demonstrating port offset calculation

Test plan

  • Unit tests for git package index functions
  • Unit tests for hooks.Env.ToEnvVars() with Index
  • Unit tests for config IndexConfig parsing
  • Integration test for index allocation during create
  • Integration test for list showing INDEX column
  • All existing tests pass
  • Linting passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Worktrees now receive stable numeric indices (WT_INDEX).
    • Per-worktree port setup with automatic offsets based on the index.
    • List output shows worktree indices; hooks receive WT_INDEX.
  • Configuration

    • Support for optional maximum index in config.
  • Tests

    • Added extensive tests covering index allocation, persistence, reuse, and hook env behavior.
  • Documentation

    • Docs and examples updated to describe indexing and new port setup script.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

📝 Walkthrough

Walkthrough

Adds a stable per-worktree numeric index (WT_INDEX) with allocation, persistence, and retrieval in the git layer; surfaces indices to commands, listing, and hook environments; introduces optional index.max configuration; and includes an example hook script that uses WT_INDEX to compute per-worktree port offsets.

Changes

Cohort / File(s) Summary
Core Index Management
internal/git/git.go, internal/git/git_test.go
Adds Index to WorktreeStatus and implements SetWorktreeIndex, GetWorktreeIndex, and AllocateIndex with tests for allocation, reuse, limits, and status reporting.
Command Workflows
internal/commands/create.go, internal/commands/delete.go, internal/commands/cleanup.go
Populate hook environment env.Index by allocating on create and reading index prior to delete/cleanup so hooks see WT_INDEX. Warnings used on allocation/storage failures.
Listing / UI
internal/commands/list.go
Collects worktree indices and exposes them in compact table (INDEX column) and verbose output (Index: line).
Configuration
internal/config/config.go, internal/config/config_test.go
Adds IndexConfig with max field and config parsing/tests for index.max and default behavior.
Hook Environment
internal/hooks/hooks.go, internal/hooks/hooks_test.go
Adds Index to Env and conditionally includes WT_INDEX in ToEnvVars() when index > 0; tests verify inclusion/exclusion.
Command Tests
internal/commands/commands_test.go
Adds tests verifying index allocation sequencing and that list output shows the INDEX column/value.
Example Hook
examples/hooks/setup-ports.sh
New Bash hook that reads WT_INDEX (via WT_INDEX/WT_PATH) to compute and write per-worktree port offsets into .wt-ports.env.
Docs
README.md, docs/ARCHITECTURE.md, CLAUDE.md
Documents the worktree index feature, hook env WT_INDEX, example usage, lifecycle, storage location, and optional index.max config.

Sequence Diagram(s)

sequenceDiagram
    participant User as CLI (create)
    participant Cmd as Create Command
    participant Git as internal/git
    participant FS as .git/worktrees/<name>/wt-index
    participant Hooks as Hook Runner

    User->>Cmd: wt create <name>
    Cmd->>Git: AllocateIndex(repoRoot, max)
    Git->>Git: scan existing wt-index files
    Git-->>Cmd: index (n) or error
    Cmd->>Git: SetWorktreeIndex(repoRoot, name, n)
    Git->>FS: write wt-index file
    Cmd->>Hooks: run post-create hooks with env.Index = n
    Hooks->>FS: (hook reads .wt-ports.env or uses WT_INDEX)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰
I hop and number each little tree,
A tidy index for you and me,
Ports shift right, hooks sing in tune,
Worktrees march beneath the moon,
A rabbit cheers: new order—soon! 🎋

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main feature addition: a stable worktree identifier system (WT_INDEX). The title is concise, clear, and directly reflects the primary objective documented in the PR objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/git/git.go (1)

541-567: Consider documenting the lack of atomicity for concurrent allocations.

The allocation algorithm is correct and efficient. However, if two wt create commands run concurrently, both could allocate the same index before either persists it via SetWorktreeIndex. This is likely acceptable given the CLI's typical single-user usage pattern, but might be worth noting in documentation or a code comment.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be30a6a and 213ef13.

📒 Files selected for processing (12)
  • examples/hooks/setup-ports.sh
  • internal/commands/cleanup.go
  • internal/commands/commands_test.go
  • internal/commands/create.go
  • internal/commands/delete.go
  • internal/commands/list.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/git/git.go
  • internal/git/git_test.go
  • internal/hooks/hooks.go
  • internal/hooks/hooks_test.go
🧰 Additional context used
📓 Path-based instructions (4)
internal/commands/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

internal/commands/**/*.go: Cobra command implementations should be located in internal/commands/ package
Use cmd.Println() for stderr output in Cobra commands
Use fmt.Fprintln(cmd.OutOrStdout(), ...) for stdout output (paths, listings) in Cobra commands

Files:

  • internal/commands/delete.go
  • internal/commands/create.go
  • internal/commands/cleanup.go
  • internal/commands/list.go
  • internal/commands/commands_test.go
internal/hooks/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

internal/hooks/**/*.go: Lifecycle hook execution should be in internal/hooks/ package
Pass standardized hook environment variables: WT_NAME, WT_PATH, WT_BRANCH, WT_REPO_ROOT, WT_WORKTREE_DIR

Files:

  • internal/hooks/hooks.go
  • internal/hooks/hooks_test.go
internal/config/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Configuration loading and parsing for .wt.yaml files should be in internal/config/ package

Files:

  • internal/config/config.go
  • internal/config/config_test.go
internal/git/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

internal/git/**/*.go: Git worktree command wrapping and porcelain output parsing should be in internal/git/ package
Determine if running in main repo vs worktree by checking if .git is a file (worktree) or directory (main repo)

Files:

  • internal/git/git.go
  • internal/git/git_test.go
🧬 Code graph analysis (8)
internal/commands/delete.go (1)
internal/git/git.go (1)
  • GetWorktreeIndex (530-539)
internal/hooks/hooks_test.go (1)
internal/hooks/hooks.go (1)
  • Env (14-21)
internal/git/git_test.go (1)
internal/git/git.go (7)
  • CreateWorktree (25-31)
  • RemoveWorktree (43-53)
  • GetWorktreeIndex (530-539)
  • SetWorktreeIndex (517-527)
  • AllocateIndex (542-567)
  • GetCurrentBranch (105-115)
  • GetWorktreeStatus (471-514)
internal/commands/create.go (1)
internal/git/git.go (2)
  • AllocateIndex (542-567)
  • SetWorktreeIndex (517-527)
internal/config/config_test.go (1)
internal/config/config.go (1)
  • Config (21-27)
internal/commands/cleanup.go (1)
internal/git/git.go (1)
  • GetWorktreeIndex (530-539)
internal/commands/list.go (1)
internal/git/git.go (1)
  • GetWorktreeIndex (530-539)
internal/commands/commands_test.go (1)
internal/git/git.go (1)
  • GetWorktreeIndex (530-539)
🔇 Additional comments (27)
examples/hooks/setup-ports.sh (1)

1-39: LGTM! Well-documented example hook.

This example script effectively demonstrates the WT_INDEX feature by calculating unique port offsets for each worktree. The graceful fallback on missing WT_INDEX (exit 0 with warning) is appropriate for an optional hook scenario.

internal/commands/delete.go (1)

104-107: LGTM! Index retrieval before deletion is correctly placed.

Fetching the index before the deletion flow ensures hooks receive the index value. Silently ignoring errors is appropriate since the index file may not exist for worktrees created before this feature.

internal/commands/cleanup.go (1)

204-207: LGTM! Consistent with delete.go pattern.

The index retrieval for cleanup candidates mirrors the approach in delete.go, maintaining consistency across deletion paths.

internal/config/config_test.go (1)

99-124: LGTM! Good test coverage for IndexConfig.

The tests appropriately verify both explicit configuration (index.max: 20) and the default zero-value behavior. The comment clarifying that 0 means "no limit" adds useful documentation.

internal/hooks/hooks_test.go (2)

48-97: LGTM! Comprehensive test for WT_INDEX inclusion.

The test correctly verifies that WT_INDEX is included when Index > 0. The explicit check at lines 86-96 is slightly redundant (already verified by the map lookup loop), but it adds clarity to the test intent.


99-123: LGTM! Good boundary test for Index=0.

This test correctly verifies the conditional behavior where WT_INDEX is excluded when Index is zero, ensuring hooks that depend on a valid index don't receive a misleading zero value.

internal/config/config.go (2)

26-26: LGTM!

The Index field follows the existing pattern and the zero value correctly represents "no limit" as documented.


15-18: Consider documenting or validating the behavior of negative Max values.

The IndexConfig struct only documents that 0 means "no limit", but negative values are also silently treated as unlimited when passed to AllocateIndex. Either add explicit validation in the Load function to reject negative values, or update the comment to document that negative values are treated the same as 0.

internal/hooks/hooks.go (2)

20-20: LGTM!

Adding Index to the Env struct aligns with the coding guidelines for passing standardized hook environment variables.


24-36: LGTM! Conditional WT_INDEX inclusion is well-designed.

The logic correctly omits WT_INDEX when the index is 0 (reserved for main repo), ensuring hooks only receive this variable when a valid worktree index has been allocated. This follows the coding guidelines for passing WT_INDEX to hooks.

internal/commands/create.go (1)

111-121: LGTM! Robust index allocation with graceful degradation.

The implementation correctly:

  1. Allocates index after worktree creation (ensuring worktree directory exists for SetWorktreeIndex)
  2. Uses warnings for failures rather than blocking (worktree remains usable without index)
  3. Only sets env.Index on full success, ensuring hooks receive valid data
  4. Follows coding guidelines using cmd.Printf for warning output to stderr
internal/commands/list.go (4)

49-49: LGTM!

Adding index field to worktreeInfo struct cleanly extends the display model.


104-106: LGTM!

Silently ignoring errors for index retrieval is appropriate here—missing indices simply display as "-" in the output, which is the expected fallback behavior.


157-165: LGTM! Clean compact output formatting.

The INDEX column formatting is appropriate. The fixed width of 5 characters handles typical use cases well (indices up to ~9999 with the # prefix). Follows coding guidelines by outputting to cmd.OutOrStdout().


178-181: LGTM!

Conditional display of index in verbose mode is consistent with the pattern used elsewhere (e.g., age, ahead/behind). Good placement after Branch for logical grouping.

internal/commands/commands_test.go (3)

10-11: LGTM!

Import addition for git package is necessary for the new test assertions using git.GetWorktreeIndex.


307-366: LGTM! Comprehensive test for index allocation and reuse.

The test thoroughly validates the core index allocation behavior:

  1. Sequential allocation (1, 2)
  2. Index reuse after deletion (freed index 1 is reused)

Good use of explicit assertions with descriptive error messages.


368-398: LGTM! Clean integration test for list output.

The test effectively validates that the INDEX column appears in list output with the expected #1 value for the first worktree.

internal/git/git.go (4)

242-242: LGTM!

The new Index field is appropriately typed as int and well-documented with a clear comment explaining its purpose.


509-512: LGTM!

The index retrieval is consistent with the existing error-handling pattern in this function. Silently falling back to 0 (the zero value) is appropriate since legacy worktrees won't have an index file.


516-527: LGTM!

The implementation follows the established pattern for storing worktree metadata. The directory existence check prevents orphaned index files, and the file permissions are appropriate.


529-539: LGTM!

The function correctly propagates errors (unlike some other getters that silently return zero values), which is appropriate since callers like AllocateIndex need to distinguish between "no index" and "index 0".

internal/git/git_test.go (5)

875-919: LGTM!

Comprehensive test coverage for SetWorktreeIndex and GetWorktreeIndex:

  • Verifies error on missing index file
  • Tests setting and retrieving an index
  • Tests updating an existing index

The test properly uses defer for cleanup.


921-957: LGTM!

Good test of the basic allocation flow, verifying that indexes start at 1 and increment correctly.


959-1006: LGTM!

Excellent test coverage for the index reuse algorithm. The test correctly verifies that:

  1. Freed indexes are reused
  2. The lowest available index is always chosen

The intentional lack of defer for wt1 and wt2 is appropriate here since their removal is part of the test logic.


1008-1056: LGTM!

Thorough test of the maxIndex limit behavior, covering:

  • Failure when limit is reached
  • Exact error message validation
  • Success with a higher limit
  • Success with no limit (maxIndex=0)

1058-1087: LGTM!

Good integration test verifying that GetWorktreeStatus correctly retrieves and populates the Index field from the stored metadata.

agarcher and others added 2 commits January 11, 2026 21:08
Implement the WT_INDEX feature as described in IMPLEMENTATION_PLAN.md.
Each worktree now receives a stable, reusable numeric identifier useful
for port offsets and resource allocation.

Key changes:
- Add IndexConfig to config.go with optional max limit
- Add SetWorktreeIndex, GetWorktreeIndex, AllocateIndex to git.go
- Add Index field to hooks.Env, pass WT_INDEX to hooks
- Allocate index during wt create, display in wt list
- Pass index to pre/post delete hooks in delete and cleanup commands
- Add setup-ports.sh example hook demonstrating WT_INDEX usage
- Add comprehensive tests for all new functionality

Storage: .git/worktrees/<name>/wt-index (auto-cleaned by git)
Allocation: Lowest unused index starting at 1 (0 reserved for main)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove # prefix from index in list output (avoids PR reference confusion)
- Update README.md with WT_INDEX feature documentation
- Update ARCHITECTURE.md with worktree index details
- Update CLAUDE.md hook environment vars
- Fix test to check for index without # prefix

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
docs/ARCHITECTURE.md (1)

75-81: Consider clarifying two behaviors.

The documentation is clear overall. Two optional clarifications:

  1. "index 0 is reserved for the main repo" - Consider clarifying whether the main repo actually receives index 0 or if WT_INDEX is simply not set for the main repo.
  2. The index.max cap - Consider documenting what happens when the limit is reached (e.g., does creation fail? Is an error returned?).
README.md (1)

152-170: Excellent user-facing documentation with practical examples.

The use cases and port allocation example (VITE_PORT=$((5173 + WT_INDEX * 10))) are particularly helpful for users.

Optional: Consider adding a brief note about what happens when index.max is reached (e.g., "Creation fails if all indexes 1-20 are in use").

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 213ef13 and 9d35df0.

📒 Files selected for processing (15)
  • CLAUDE.md
  • README.md
  • docs/ARCHITECTURE.md
  • examples/hooks/setup-ports.sh
  • internal/commands/cleanup.go
  • internal/commands/commands_test.go
  • internal/commands/create.go
  • internal/commands/delete.go
  • internal/commands/list.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/git/git.go
  • internal/git/git_test.go
  • internal/hooks/hooks.go
  • internal/hooks/hooks_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • internal/hooks/hooks_test.go
  • internal/commands/cleanup.go
  • internal/commands/delete.go
  • internal/commands/commands_test.go
  • internal/commands/list.go
  • internal/commands/create.go
🧰 Additional context used
📓 Path-based instructions (3)
internal/config/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Configuration loading and parsing for .wt.yaml files should be in internal/config/ package

Files:

  • internal/config/config.go
  • internal/config/config_test.go
internal/git/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

internal/git/**/*.go: Git worktree command wrapping and porcelain output parsing should be in internal/git/ package
Determine if running in main repo vs worktree by checking if .git is a file (worktree) or directory (main repo)

Files:

  • internal/git/git_test.go
  • internal/git/git.go
internal/hooks/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

internal/hooks/**/*.go: Lifecycle hook execution should be in internal/hooks/ package
Pass standardized hook environment variables: WT_NAME, WT_PATH, WT_BRANCH, WT_REPO_ROOT, WT_WORKTREE_DIR

Files:

  • internal/hooks/hooks.go
🧬 Code graph analysis (2)
internal/git/git_test.go (1)
internal/git/git.go (7)
  • CreateWorktree (25-31)
  • RemoveWorktree (43-53)
  • GetWorktreeIndex (530-539)
  • SetWorktreeIndex (517-527)
  • AllocateIndex (542-567)
  • GetCurrentBranch (105-115)
  • GetWorktreeStatus (471-514)
internal/config/config_test.go (1)
internal/config/config.go (1)
  • Config (21-27)
🔇 Additional comments (20)
CLAUDE.md (1)

39-41: LGTM!

The documentation accurately describes the new WT_INDEX environment variable and the worktree index feature. The hook environment section now properly includes all standardized variables as per the coding guidelines.

internal/config/config.go (2)

15-18: LGTM!

Clean struct definition with appropriate YAML tag. The comment clarifies that 0 means no limit, which is the correct default behavior.


26-26: LGTM!

The Index field is properly integrated into the Config struct. The zero-value default (Max=0, no limit) is appropriate and requires no explicit initialization in DefaultConfig().

internal/config/config_test.go (1)

99-124: LGTM!

Good test coverage for the new IndexConfig field, testing both explicit configuration and default behavior. The tests follow the existing table-driven pattern in the file.

internal/hooks/hooks.go (2)

20-20: LGTM!

The Index field is appropriately added to the Env struct to support passing WT_INDEX to hooks.


24-36: LGTM!

The conditional inclusion of WT_INDEX when Index > 0 correctly implements the design where index 0 is reserved for the main repo. The refactoring to use a local slice with conditional append is clean.

internal/git/git.go (5)

242-242: LGTM!

The Index field is properly added to WorktreeStatus to surface the worktree index in status reporting.


509-511: LGTM!

Consistent with the error-handling pattern used elsewhere in this function (e.g., CreatedAt, initialCommit). Missing or unreadable index defaults to 0.


516-526: LGTM!

The function properly validates the worktree directory exists before writing, preventing writes to non-existent worktrees. File permissions (0644) are appropriate for this metadata file.


529-538: LGTM!

Returns an error when the file doesn't exist, allowing callers to distinguish between "index not set" and "index is 0". This is appropriate since index 0 is reserved for the main repo.


541-566: LGTM!

The allocation algorithm correctly:

  • Handles missing worktrees directory (new repos)
  • Scans existing indexes and finds the lowest unused
  • Reserves 0 for main repo by starting at 1
  • Enforces maxIndex limit when configured
internal/git/git_test.go (5)

875-919: LGTM!

Comprehensive test for Set/Get operations including the initial error state when no index file exists, basic set/get, and update scenarios.


921-957: LGTM!

Good test coverage for basic allocation behavior, verifying that index 1 is returned first and subsequent allocations find the next available index.


959-1006: LGTM!

Excellent test for index reuse behavior. Verifies that when worktrees are deleted, their indexes become available and the lowest available index is allocated next.


1008-1056: LGTM!

Thorough boundary testing for the maxIndex limit, including error case when limit is reached, success with higher limit, and the no-limit (max=0) scenario.


1058-1087: LGTM!

Good integration test verifying that GetWorktreeStatus correctly includes the index field from the persisted wt-index file.

examples/hooks/setup-ports.sh (1)

1-39: LGTM!

Well-documented example hook demonstrating a practical use case for WT_INDEX. The script:

  • Gracefully handles missing WT_INDEX
  • Uses clear port offset calculation (WT_INDEX * 10)
  • Writes a sourceable env file for downstream tooling
  • Provides helpful output messages

Good addition to the examples.

docs/ARCHITECTURE.md (1)

73-73: LGTM!

Clear and concise documentation of the WT_INDEX environment variable.

README.md (2)

150-150: LGTM!

Consistent with the ARCHITECTURE.md documentation.


224-224: LGTM!

Good cross-reference to the example hook that demonstrates WT_INDEX usage.

@agarcher agarcher merged commit 5b6148f into main Jan 12, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant