Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
289 changes: 289 additions & 0 deletions docs/memory/wt-cli/go-command-contract.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/memory/wt-cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ description: "Behavior contracts for the `wt` CLI binary — commands, exit code
| File | Description | Last Updated |
|------|-------------|-------------|
| [create-output-phases](create-output-phases.md) | Phase-separator output contract for `wt create` / `wt init` — Git/Init/Open separators on stderr, stdout reserved for the machine result. | 2026-06-20 |
| [go-command-contract](go-command-contract.md) | `wt go` worktree-selection contract — selection-only navigation via `WT_CD_FILE`/stdout (no launch), exit codes, the current-worktree-included menu, and the `wt open --go` composition. | 2026-06-20 |
| [help-dump-contract](help-dump-contract.md) | Contract for the hidden `wt help-dump` command — the JSON envelope shll.ai's scheduled puller consumes. | 2026-06-20 |
Comment thread
Copilot marked this conversation as resolved.
| [idle-staleness-contract](idle-staleness-contract.md) | The shared idle predicate, the `wt delete --stale` selector, and the safety invariant that idleness never gates a deletion on its own. | 2026-06-20 |
| [init-failure-contract](init-failure-contract.md) | Init-failure behavior of `wt create` / `wt init` — kept-worktree contract, `ExitInitFailed`, SIGINT handling, and terminal-foreground reclaim. | 2026-06-20 |
Expand Down
9 changes: 8 additions & 1 deletion docs/memory/wt-cli/menu-navigation-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ This file documents the contract that `ShowMenu` honors after the arrow-key navi
- Both validation error messages: `Invalid choice. Please enter a number.` and `Invalid choice. Please enter a number between 0 and N.`
- Existing test harnesses driving `ShowMenu` via piped stdin continue to pass unmodified. Integration tests under `cmd/integration_test.go` invoke the binary with pipes and naturally land on the fallback path.

### `wt go` is a new `ShowMenu`/`MenuSession` caller (`260620-3pp5`)

- The `260620-3pp5-open-worktree-from-worktree` change added `wt go` (and `wt open --go`), whose worktree-selection menu renders through the **shared** `selectWorktree(ctx, session, prompt)` helper (`src/cmd/wt/open.go`) — which calls `session.Show(...)` → `ShowMenu`. No new menu primitive was introduced; `wt go`'s picker inherits this contract wholesale (arrow keys, digit-submit, Cancel→`0`, wrap-around, the `(default)` marker, and the in-place redraw) with no `menu.go` edits.
- **Non-TTY fallback**: `wt go`'s no-arg menu degrades through the same byte-identical numbered-prompt fallback as every other caller — `isInteractiveTTY()` gates it, and piped/CI invocations land on the historical numbered prompt automatically. `wt go` adds a separate, earlier guard for the **`--non-interactive` no-arg** case: it refuses with `ExitGeneralError` *before* reaching `selectWorktree` at all (a no-arg selection has no non-interactive default — see `/wt-cli/go-command-contract.md`), so that path never renders even the fallback prompt. `wt go <name>` and `wt go` interactive both reach the menu only when a menu is actually wanted.
- **Shared `MenuSession` for the launch-chaining callers**: `selectAndOpen` and `wt open --go`'s `openGo` pass ONE `MenuSession` to both `selectWorktree` (the "Select worktree…" menu) and `handleAppMenuWithSession` (the "Open in:" menu), so the two consecutive menus share a single stdin reader — the documented byte-theft fix. `wt go` chains no second menu, so it uses a one-shot session. The single-reader requirement and why it matters are the `MenuSession` contract this file's navigation behavior sits on.

### Pure `nextMenuState` and `parseKey` are testable without a PTY

- `nextMenuState(prev menuState, key keyEvent) menuStateTransition` is a **pure function** with no I/O, no globals, no clock. It encodes every key-mapping, wrap-around, digit boundary, and Cancel transition.
Expand Down Expand Up @@ -153,4 +159,5 @@ Originally `paintMenu` and `redrawMenu` had two parallel rendering paths — the
- Tests: `src/internal/worktree/menu_test.go` — `nextMenuState` table-driven coverage (every keybinding, wrap-around, digit boundaries, seeding), `parseKey` table-driven coverage (arrow sequences, bare-Esc-vs-arrow timeout via fake clock, unknown sequences), fallback-path byte-equality tests, `TestRunInteractiveMenuCore_PanicRestore` (defer-restore guarantee), `TestPaintAndRedrawShareCore` (first-paint / redraw byte-equality).
- Constitution: Principle I (Single-Binary CLI — motivated rejecting third-party TUI deps), Principle IV (Test What the User Sees — motivated the pure state-machine seam), Principle VI (Interactive by Default, Scriptable on Demand — motivated the byte-identical non-TTY fallback).
- Sibling memory: `wt-cli/init-failure-contract.md` (different `wt` subcommand, same post-change invariant-capture pattern), `wt-cli/list-status-contract.md` (different subcommand, same pattern).
- Call sites (informational, not edited by this change): `src/cmd/wt/open.go` (2 calls), `src/cmd/wt/delete.go` (7 calls), `src/cmd/wt/create.go` (2 calls).
- Sibling memory: `wt-cli/go-command-contract.md` — the `wt go` selector / `wt open --go` composition whose worktree-selection menu is a new caller of this contract (via the shared `selectWorktree` → `ShowMenu`/`MenuSession`).
- Call sites (informational): `src/cmd/wt/open.go` (the shared `selectWorktree` → `session.Show`, plus the "Open in:" menu, plus `wt open --go`'s `openGo`), `src/cmd/wt/go.go` (`wt go`'s no-arg selection menu, via `selectWorktree`; added by `260620-3pp5`), `src/cmd/wt/delete.go` (7 calls), `src/cmd/wt/create.go` (2 calls).
35 changes: 28 additions & 7 deletions docs/memory/wt-cli/recency-ordering-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ explicit spec amendment supersedes them.

### Open / delete menus list non-main worktrees newest-first

- `selectAndOpen` (`src/cmd/wt/open.go`) and `handleDeleteMenu`
(`src/cmd/wt/delete.go`) build a `wtOption` slice of non-main worktrees
(the main worktree / `ctx.RepoRoot` is skipped) and sort it with
`wt.SortByRecency` so the **newest worktree appears at the top** of the menu.
This replaces the previous behavior where items appeared in porcelain order and
only the newest was highlighted.
- `selectAndOpen` (`src/cmd/wt/open.go`, via the shared `selectWorktree` helper as
of `260620-3pp5` — see below) and `handleDeleteMenu` (`src/cmd/wt/delete.go`)
build a `wtOption` slice of non-main worktrees (the main worktree /
`ctx.RepoRoot` is skipped) and sort it with `wt.SortByRecency` so the **newest
worktree appears at the top** of the menu. This replaces the previous behavior
where items appeared in porcelain order and only the newest was highlighted.
- The pre-selected menu default remains the **most-recent** worktree — this is
behavior-preserving for the default selection. Only the item *ordering* changed.
- `wt open`: `defaultIdx = 1` (newest is the first menu item; index 0 is the
Expand All @@ -95,6 +95,23 @@ explicit spec amendment supersedes them.
- The two menus produce identical non-main ordering (both driven by the same
`SortByRecency` call), so `wt open` and `wt delete` never disagree on order.

### `wt go`'s no-arg menu is a new `SortByRecency` consumer (`260620-3pp5`)

- The `260620-3pp5-open-worktree-from-worktree` change extracted the
`selectAndOpen` menu logic into the shared `selectWorktree(ctx, session,
prompt)` helper (`src/cmd/wt/open.go`), which calls `wt.SortByRecency` over its
local `wtOption` slice (filtering out the main repo, `defaultIdx = 1`). That
single helper now backs **three** menu callers — `wt open` (main-repo no-arg,
prompt "Select worktree to open:"), `wt go` (no-arg, prompt "Select worktree to
go to:"), and `wt open --go` (no-arg). So `wt go`'s selection menu is a new
consumer of the same newest-first ordering, joining `wt list` / `wt open` /
`wt delete` — there is still exactly one `SortByRecency` call site for the
open/go selection menu, not a per-verb copy.
- The ordering, branch display, and newest-default are byte-identical across all
three callers because they share the one helper. See
`/wt-cli/go-command-contract.md` for the `wt go` / `wt open --go` behavior
contract and the `selectWorktree` extraction details.

### `wt delete` menu: stale-aware annotation + "All idle" (`260530-5fyu`)

- `handleDeleteMenu` (`src/cmd/wt/delete.go`) now annotates each idle non-main
Expand Down Expand Up @@ -188,6 +205,9 @@ produced.
(built on the `RecencyOf` signal documented here), `DefaultIdleThreshold`, and
the `wt delete --stale` selector; the authoritative cross-command idle contract
behind the `defaultIdx` 2/3 shift and `, idle` annotation noted above.
- Sibling memory: `wt-cli/go-command-contract.md` — the `wt go` selector and
`wt open --go` composition; the new `selectWorktree` menu consumers of the
`SortByRecency` ordering documented here.
- Spec doc: `docs/specs/cli-surface.md` — `wt list` (`--sort` flag), `wt open`
(selection menu, "most recently modified worktree" default), `wt delete`
(selection menu).
Expand All @@ -196,7 +216,8 @@ produced.
`<repo>.worktrees/<name>/` root.
- Source: `src/internal/worktree/recency.go` — `RecencyOf`, `RecencyLess`,
`SortByRecency`.
- Source: `src/cmd/wt/open.go` (`selectAndOpen`), `src/cmd/wt/delete.go`
- Source: `src/cmd/wt/open.go` (`selectWorktree` shared helper + `selectAndOpen`;
`selectWorktree` is also called by `wt go` and `wt open --go`), `src/cmd/wt/delete.go`
(`handleDeleteMenu` — now stale-aware: `firstWorktreeIdx`/`defaultIdx` 2/3,
`, idle` annotation, "All idle (N)"; plus `handleDeleteStale`), `src/cmd/wt/list.go`
(`sortEntries`, `resolveSort`).
Expand Down
49 changes: 45 additions & 4 deletions docs/specs/cli-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,16 @@ Exit codes: `ExitInvalidArgs` if `--path` is combined with `--json` or
## `wt open [name|path]`

Open a directory in a detected application (editor, terminal, file manager).
`wt open` is the canonical directory launcher — external callers (notably
`wt open` is the canonical directory **launcher** — external callers (notably
`hop`) MAY delegate to it via subprocess invocation. The full env-var contract
is documented in [`launcher-contract.md`](launcher-contract.md).
is documented in [`launcher-contract.md`](launcher-contract.md). Worktree
**selection** (picking which worktree) is the job of [`wt go`](#wt-go-name);
`wt open --go` composes the two (select, then launch).

| Flag | Default | Description |
|------|---------|-------------|
| `--app <name\|default>` | (none) | Open directly in the named app, skipping the menu. `default` selects the auto-detected default. |
| `--go` | `false` | Select a worktree first (via `wt go`'s menu when no name is given, or resolve-by-name when one is), then launch it. Requires a git repository; composes with `--app`. From a non-git cwd, exits `ExitGitError`. |

Comment on lines +87 to 88

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed — the wt open exit-code summary now lists ExitGitError for the --go non-git-cwd precondition, alongside the name-resolution git-op case. (08e0a1f)

Positional arg `[name|path]`:

Expand All @@ -98,12 +101,50 @@ Positional arg `[name|path]`:
reachable when the cwd is inside a git repo.

Exit codes: `ExitInvalidArgs` when `--app` is used with the main-repo selection
menu; `ExitGitError` only when a git operation fails during name resolution
(not for path-only or no-args invocations from outside a repo);
menu; `ExitGitError` when a git operation fails during name resolution, or when
`--go` is invoked from a non-git cwd (the `--go` git-repo precondition) — but
not for path-only or no-args invocations from outside a repo;
`ExitByobuTabError` / `ExitTmuxWindowError` for terminal-app failures;
`ExitGeneralError` for unknown apps, unresolved targets, or name args supplied
from a non-git cwd.

## `wt go [name]`

Select a worktree of the current repository and **navigate** there. `wt go` is
the worktree **selector** (the counterpart to `wt open`, the launcher): it
changes the shell's working directory to the chosen worktree and launches
nothing. Navigation reuses the same `WT_CD_FILE` shell-cd plumbing as the
launcher's "Open here" option — see [`launcher-contract.md`](launcher-contract.md) §3.

| Flag | Default | Description |
|------|---------|-------------|
| `--non-interactive` | `false` | No prompts. With no name, refuses deterministically (a no-arg selection menu has no non-interactive default) instead of prompting. |

Positional arg `[name]`:

- Omitted: shows a worktree-selection menu for the current repo (newest-first,
branch shown per entry, newest pre-selected as default). Reachable from
anywhere in the repository — the main repo **or** inside another worktree.
On selection, navigates to the chosen worktree.
- Provided: resolved as a worktree name (case-insensitive); navigates there
directly with no menu.

`wt go` always **requires a git repository** — worktree resolution walks the
repo's worktree list. It is scoped to the current repo's worktrees only;
cross-repo navigation is `hop`'s job.

Navigation mechanism: the resolved absolute path is written to `WT_CD_FILE`
(when set; mode `0600`, truncate-on-write) so the `wt shell-init` wrapper cd's
the parent shell there, **and** is printed to stdout as the last line so the
no-wrapper scripting form works: `cd "$(command wt go some-name)"`. When
`WT_CD_FILE` is unset and `WT_WRAPPER` is not `1`, the same "shell wrapper not
loaded" hint the launcher prints applies. `wt go` never cd's the parent shell
directly.

Exit codes: `ExitGitError` (3) when the cwd is not in a git repository or
`git worktree list` fails; `ExitGeneralError` (1) for an unknown worktree name,
or for a no-arg invocation under `--non-interactive`.

## `wt delete [worktree-names...]`

Delete one or more worktrees with optional branch cleanup.
Expand Down
11 changes: 11 additions & 0 deletions docs/specs/launcher-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ existing directory and the cwd is in a git repo.
When `WT_CD_FILE` is unset, "Open here" falls back to printing `cd -- '<path>'`
to stdout (single-quoted with `'\''` escaping for shell safety).

**Reused by `wt go`.** The `wt go` selector (see
[`cli-surface.md`](cli-surface.md#wt-go-name)) navigates to a worktree using
this **same** `WT_CD_FILE` mechanism — no new environment variable is
introduced. It writes the resolved absolute path to `WT_CD_FILE` with the
identical semantics above (mode `0600`, truncate-on-write, contents = resolved
directory path), and additionally always prints the path to stdout as the last
line (so `cd "$(command wt go <name>)"` works without the wrapper). Because
`wt go` adds no new env-var name and does not alter any semantics in this
section or §5, the stability guarantees in §6 are unchanged — no constitution
amendment is required.

## 4. `WT_WRAPPER`

`WT_WRAPPER=1` signals that the caller is handling the `cd` itself (via
Expand Down
12 changes: 12 additions & 0 deletions fab/changes/260620-3pp5-open-worktree-from-worktree/.history.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{"action":"enter","driver":"fab-new","event":"stage-transition","stage":"intake","ts":"2026-06-20T05:44:24Z"}
{"args":"There's no easy way to open a worktree from another worktree. wt open seems like the command that should do this, but right now it opens the current folder. Thinking holistically, I am also fine transferring the responsibility of 'opening folders' to the hop command, and make wt open open a worktree and delegate to hop open","cmd":"fab-new","event":"command","ts":"2026-06-20T05:44:24Z"}
{"delta":"+4.6","event":"confidence","score":4.6,"trigger":"calc-score","ts":"2026-06-20T06:46:53Z"}
{"delta":"+0.0","event":"confidence","score":4.6,"trigger":"calc-score","ts":"2026-06-20T06:46:58Z"}
{"cmd":"fab-fff","event":"command","ts":"2026-06-20T06:47:41Z"}
{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"apply","ts":"2026-06-20T06:47:47Z"}
{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"review","ts":"2026-06-20T06:56:54Z"}
{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"hydrate","ts":"2026-06-20T07:03:18Z"}
{"event":"review","result":"passed","ts":"2026-06-20T07:03:18Z"}
{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"ship","ts":"2026-06-20T07:08:00Z"}
{"action":"enter","driver":"git-pr","event":"stage-transition","stage":"review-pr","ts":"2026-06-20T07:09:37Z"}
{"event":"review","result":"passed","ts":"2026-06-20T07:15:49Z"}
53 changes: 53 additions & 0 deletions fab/changes/260620-3pp5-open-worktree-from-worktree/.status.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
id: 3pp5
name: 260620-3pp5-open-worktree-from-worktree
created: 2026-06-20T05:44:24Z
created_by: sahil-noon
change_type: feat
issues: []
progress:
intake: done
apply: done
review: done
hydrate: done
ship: done
review-pr: done
plan:
generated: true
task_count: 9
acceptance_count: 20
acceptance_completed: 20
confidence:
certain: 4
confident: 4
tentative: 0
unresolved: 0
score: 4.6
fuzzy: true
dimensions:
signal: 78.1
reversibility: 77.5
competence: 86.9
disambiguation: 79.4
stage_metrics:
intake: {started_at: "2026-06-20T05:44:24Z", driver: fab-new, iterations: 1, completed_at: "2026-06-20T06:47:47Z"}
apply: {started_at: "2026-06-20T06:47:47Z", driver: fab-fff, iterations: 1, completed_at: "2026-06-20T06:56:54Z"}
review: {started_at: "2026-06-20T06:56:54Z", driver: fab-fff, iterations: 1, completed_at: "2026-06-20T07:03:18Z"}
hydrate: {started_at: "2026-06-20T07:03:18Z", driver: fab-fff, iterations: 1, completed_at: "2026-06-20T07:08:00Z"}
ship: {started_at: "2026-06-20T07:08:00Z", driver: fab-fff, iterations: 1, completed_at: "2026-06-20T07:09:37Z"}
review-pr: {started_at: "2026-06-20T07:09:37Z", driver: git-pr, iterations: 1, completed_at: "2026-06-20T07:15:49Z"}
prs:
- https://github.com/sahil87/wt/pull/23
change_type_source: explicit
true_impact:
added: 0
deleted: 0
net: 0
excluding:
added: 0
deleted: 0
net: 0
computed_at: "2026-06-20T07:08:00Z"
computed_at_stage: hydrate
summary: Add wt go (current-repo worktree selection, WT_CD_FILE+stdout navigation, no launch) and wt open --go composition; extract shared selectWorktree helper
# true_impact: lazily created on first apply-finish (no placeholder here).
last_updated: 2026-06-20T07:15:49Z
Loading
Loading