feat(start): a worktree builds its search index when its main checkout has one #545

Merged
mph merged 1 commit from wip/zg-index-setup into main 2026-09-11 19:07:07 +00:00
Owner

Summary

iw start and iw open now start building a worktree's zvec-grep search index when the repository's main checkout has one.

  • A new built-in session setup hook, ZvecGrepIndexSetup, runs next to DirenvSetup.
  • When the main checkout has a zvec-grep index and the worktree has none, the hook sends a detached zg index '<worktree>' --embedding <model>, with the model named in the main index's manifest.json.
  • A repository opts in by indexing its main checkout. Nothing happens where zg is not on PATH, where the main checkout has no index, or where the worktree already has one.

Why

Semantic search must answer from the worktree's own content. A branch and main diverge quickly: we measured up to 449 files between a procedures worktree and main. An index per worktree keeps results correct, and building it by hand in every worktree is the step people forget.

Design

  • The decision is pure: WorktreeSearchIndex.buildCommand in core/model. It refuses a model name or a worktree path it cannot quote safely for the shell.
  • The hook only reads the facts it needs: the main checkout, whether each side has an index, and whether zg exists.
  • GitWorktreeAdapter.repoRoot finds a worktree's main checkout from git's common dir. LiveHerdrOps.repoRootOf now uses it instead of its own copy of the same git call.
  • The command runs detached, because zg index waits until the build ends and the session must not wait with it.

Tests

  • Unit: WorktreeSearchIndexTest covers every condition that suppresses the command, shell safety, and manifest parsing.
  • Integration: ZvecGrepIndexSetupTest uses real git worktrees and real manifest files. GitWorktreeAdapterTest has three repoRoot cases.
  • Wiring: a SessionHooksTest case goes through SessionHooks.run. It is skipped where zg is not on PATH.
  • E2E: two start.bats cases run a real iw start against a real zg index, in direct mode, and are skipped without zg. One case checks that the worktree gets an index. The other checks that nothing reaches the pane when the main checkout has no index.

Local gate: format check, Scalafix, core.test (3,451 tests), dashboard.test, dashboard.itest.testForked, command compilation and all BATS files pass.

The first gate run had 17 dashboard failures. They came from a Mill daemon started without WEBAWESOME_NPM_TOKEN, not from this change: the same cases passed on an untouched main and after the daemon restarted with the token. Filed as #544.

  • #526 asks for a project-declared worktree init hook. This PR adds one built-in, tool-specific setup step and does not implement #526.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UZkyNf4ufQzdFP99htcgNw

## Summary `iw start` and `iw open` now start building a worktree's zvec-grep search index when the repository's main checkout has one. - A new built-in session setup hook, `ZvecGrepIndexSetup`, runs next to `DirenvSetup`. - When the main checkout has a zvec-grep index and the worktree has none, the hook sends a detached `zg index '<worktree>' --embedding <model>`, with the model named in the main index's `manifest.json`. - A repository opts in by indexing its main checkout. Nothing happens where `zg` is not on PATH, where the main checkout has no index, or where the worktree already has one. ## Why Semantic search must answer from the worktree's own content. A branch and `main` diverge quickly: we measured up to 449 files between a procedures worktree and `main`. An index per worktree keeps results correct, and building it by hand in every worktree is the step people forget. ## Design - The decision is pure: `WorktreeSearchIndex.buildCommand` in `core/model`. It refuses a model name or a worktree path it cannot quote safely for the shell. - The hook only reads the facts it needs: the main checkout, whether each side has an index, and whether `zg` exists. - `GitWorktreeAdapter.repoRoot` finds a worktree's main checkout from git's common dir. `LiveHerdrOps.repoRootOf` now uses it instead of its own copy of the same git call. - The command runs detached, because `zg index` waits until the build ends and the session must not wait with it. ## Tests - Unit: `WorktreeSearchIndexTest` covers every condition that suppresses the command, shell safety, and manifest parsing. - Integration: `ZvecGrepIndexSetupTest` uses real git worktrees and real manifest files. `GitWorktreeAdapterTest` has three `repoRoot` cases. - Wiring: a `SessionHooksTest` case goes through `SessionHooks.run`. It is skipped where `zg` is not on PATH. - E2E: two `start.bats` cases run a real `iw start` against a real zg index, in direct mode, and are skipped without `zg`. One case checks that the worktree gets an index. The other checks that nothing reaches the pane when the main checkout has no index. Local gate: format check, Scalafix, `core.test` (3,451 tests), `dashboard.test`, `dashboard.itest.testForked`, command compilation and all BATS files pass. The first gate run had 17 dashboard failures. They came from a Mill daemon started without `WEBAWESOME_NPM_TOKEN`, not from this change: the same cases passed on an untouched `main` and after the daemon restarted with the token. Filed as #544. ## Related - #526 asks for a *project-declared* worktree init hook. This PR adds one built-in, tool-specific setup step and does not implement #526. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01UZkyNf4ufQzdFP99htcgNw
feat(start): a worktree builds its search index when its main checkout has one
All checks were successful
CI / changes (pull_request) Successful in 5s
CI / contract (pull_request) Has been skipped
CI / format (pull_request) Successful in 8s
CI / lint (pull_request) Successful in 9s
CI / compile (pull_request) Successful in 1m27s
CI / dashboard-build (pull_request) Successful in 1m27s
CI / test (pull_request) Successful in 14m30s
698c73f675
A new built-in session setup hook, ZvecGrepIndexSetup, runs next to
DirenvSetup for `iw start` and `iw open`. When the main checkout has a
zvec-grep index and the worktree has none, it sends a detached
`zg index` for the worktree, with the model named in the main index's
manifest. A repository opts in by indexing its main checkout; nothing
happens where zg is missing or the main checkout has no index.

The decision is pure (WorktreeSearchIndex in core/model); the hook only
reads the facts it needs. The command refuses a model name or a path it
cannot quote safely for the shell.

GitWorktreeAdapter.repoRoot finds the main checkout of a worktree from
git's common dir. LiveHerdrOps.repoRootOf now uses it instead of its own
copy of the same git call.

Tests: unit tests for the decision and manifest reading, integration
tests with real git worktrees, a SessionHooks wiring test, and two
start.bats cases that run a real `iw start` against a real zg index
(skipped where zg is not on PATH).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UZkyNf4ufQzdFP99htcgNw
mph merged commit 5f5661c2c6 into main 2026-09-11 19:07:07 +00:00
mph deleted branch wip/zg-index-setup 2026-09-11 19:07:07 +00:00
Sign in to join this conversation.
No description provided.