wtt is a Git worktree manager.
Initial platform support is Linux and macOS. Windows is out of scope until equivalent process-tree cancellation and shell integration are specified.
SPEC.md ──delegates named subject detail──> docs/
PLAN.md delivery scope and order
AGENTS.md engineering constraints
ISSUES.md unresolved verified defects and decisions
The subject owners are indexed in docs/README.md. On conflict this file wins;
its silence delegates only the indexed subject, never retires a root invariant. Archived documents
have no authority.
| Axis | State | Authority and consequence |
|---|---|---|
| Knowledge | unknown / not_attempted |
None; infer neither presence nor absence. |
| Knowledge | unavailable / failed attempt |
Retain last-known facts as stale; never infer absence. |
| Knowledge | absent |
Current authoritative absence; no PR facets; may satisfy no-PR selection. |
| Knowledge | present | Current authoritative PR identity with independently authoritative facets. |
| Knowledge | not_applicable |
Provider authority is structurally unnecessary; use local rules. |
| Coordination | queued / deferred |
Retain cached facts; grant no provider authority and show no execution. |
| Coordination | executing |
Retain cached facts; ⟳ may mark target-specific execution. |
| Coordination | complete |
Work is terminal; consult knowledge and facet authority for its result. |
Freshness is a third independent axis: stale facts may remain visible but cannot satisfy an operation requiring current authority. Human and JSON output preserve every axis.
Every cancellable mutation or publication follows this sequence unless a narrower section names a different boundary:
collect → validate → [final cancellation check] → MUTATE / COMMIT → reconcile / render
cancellation wins ← | → completion wins
^
linearization point
Cancellation before the boundary preserves prior authoritative state and output. After the boundary, completion wins; failure in later bookkeeping preserves the successful external operation and reports recovery instead of attempting an unsafe rollback.
- Index Git repositories in the filesystem. The user config defines one or more scan roots.
- Scan roots are searched recursively. By default, the scanner does not follow symlinks or cross filesystem boundaries.
- Do not descend into a repository after Git validates its boundary, or into built-in exclusions
for common tool and package-manager caches. A
.gitmarker alone is not sufficient: if Git rejects it, report a warning and continue looking for nested repositories. Users can add exclusion globs. - Yield directory entries named
.gitfor boundary validation because a scan root may contain a canonical bare or separate common Git directory with that name; never descend inside such an entry after it is processed. - Permission and transient filesystem errors do not abort a scan; report them as warnings.
- A repository found under a scan root is in scope. Once found, use
git worktree list --porcelainto index all of its worktrees, including worktrees whose paths are outside every scan root. - Canonicalise repositories by their common Git directory so encountering a linked worktree does not create a duplicate repository entry. If canonicalisation fails, warn and do not store a non-canonical fallback identity.
- Recognise both files-backed and reftable Git directory layouts as candidates; Git remains the authority that validates whether a candidate is a repository boundary. A candidate that is itself the canonical common Git directory is in scope even when it belongs to a non-bare repository with a separate external worktree.
- Reconcile clean scans into stable repository/worktree identities so cached provider state and
hook trust survive. A clean scan removes entries no longer present. If any filesystem or Git
warning makes a scan partial, update successfully discovered repositories but retain previously
indexed repositories that were not observed; a later clean scan may remove them.
Partial retention reads and writes occur under the same immediate transaction, so a concurrent
completed refresh cannot be overwritten by a stale read/modify/write merge.
Discovery and reconciliation are serialized per index across processes so an older overlapping
discovery cannot overwrite a newer completed refresh. Retained-only repositories preserve their
previous repository-level
scanned_at; only successfully observed repositories receive the new scan time. The refresh lock is derived from the canonical index path so symlink aliases serialize on the same lock identity. SIGINT/SIGTERMand internal cancellation abort discovery, Git inspection, and index reconciliation without replacing the last complete index. Git subprocesses run in isolated process groups; cancellation and timeout terminate and reap the direct child and kill its process group, including descendants that outlive the direct parent. A descendant that deliberately escapes that group is outside the containment guarantee, but must not delay command timeout or cancellation by retaining an inherited output pipe. Reconciliation's final cancellation check is the shared commit linearization point.- Store the index in SQLite WAL mode so refresh writes can commit while interactive readers retain a consistent snapshot. Index reads and partial-scan merging are cancellation-aware.
- Scanner-owned Git commands ignore inherited repository-selection and discovery environment such
as
GIT_DIR,GIT_WORK_TREE, andGIT_COMMON_DIR; each candidate path is authoritative. TransientGIT_CONFIG_COUNT/GIT_CONFIG_PARAMETERSinjection is also cleared, while ordinary protected system/global configuration remains in force. Recognized bare candidates are explicitly selected by canonical absolute--git-dir, preserving compatibility with protectedsafe.bareRepository=explicitpolicy and relative scan roots. - The indexing daemon defined below alone performs filesystem-wide discovery and background indexing. Commands that successfully mutate Git may reconcile only the affected repositories so their results are reflected immediately without depending on the daemon.
- Nonempty stderr from a successful scanner-owned Git command is retained as a Git warning, even if a later inspection step fails. On complete inspection the repository remains discovered, but reconciliation treats that scan as partial.
- Default paths follow the XDG Base Directory specification on both Linux and macOS. Configuration is
$XDG_CONFIG_HOME/wtt/config.toml(falling back to$HOME/.config/wtt/config.toml) and rebuildable state is under$XDG_STATE_HOME/wtt(falling back to$HOME/.local/state/wtt). Relative XDG base directory values are ignored as the XDG specification requires. Explicit--configand--databasepaths take precedence. - The first non-metadata command atomically creates a default configuration with no scan roots when the XDG configuration file is absent. It never overwrites a file; a missing explicit path is an error.
wtt daemon startatomically acquires lifetime ownership of the canonical index, watches configured roots, and performs incremental scans plus periodic full scans for initialization, validation, and missed events. A second daemon fails. On supported local filesystems, the default watcher uses the platform's native event facility and does not implement watching by repeatedly traversing configured roots. Native watcher registration failure or resource exhaustion degrades affected coverage toMAYBE_STALE; periodic validation repairs missed events without replacing the watcher with continuous full-tree polling. Missing or prunable worktrees remain indexed but their absent paths are never registered. An absent dynamic path becomes eligible again only when repository topology changes or the path reappears. Filesystem bursts are filtered and coalesced by repository before bounded incremental admission so status IPC remains responsive while scan and provider workers are busy.--detachbackgrounds it and returns when ownership, the index, and status IPC are ready—not when indexing finishes. With--detach --if-needed, a running daemon is a successful no-op;--if-neededis otherwise invalid.wtt daemon statusseparately reports reachability, index operability, watcher coverage, snapshot freshness and validation, scan progress, recent warnings, and the log path.wtt daemon stoprequests clean shutdown. Directwtt daemon, lifecycle flags ondaemon,shutdown, andrunare not public spellings.startruns in the foreground unless--detachis present;--if-neededis valid only withstart --detach. The current CLI may use the unchanged authenticated shutdown exchange from exactly the immediately preceding daemon protocol to stop an older same-index owner cleanly. When daemon auto-start or detached start is requested, it waits for that owner to release authority before starting the current daemon; all non-lifecycle requests remain fail-closed across protocol versions.wtt scanqueues a full local scan and returns.--waitfollows the queued or coalesced generation and reports its completion and warnings. Neither blocks snapshot readers, and cancelling a waiter does not cancel shared daemon work.wtt scan --refreshadditionally submits one daemon-owned provider phase for the same global index. Local and provider admission, progress, authority, and terminal outcomes remain independent. With--wait, both requested phases are followed and reported separately; failure of either requested phase returns nonzero without undoing a successful phase. The publicscan --providerspelling is removed completely.- Provider synchronization also has a first-class
wtt refreshcommand. Barerefreshtargets the current worktree;refresh WORKTREEtargets one exact logical worktree or indexed absolute path;refresh --repositorytargets every provider-capable worktree in the inferred current repository; andrefresh --allfollows one durable complete-index convergence sweep. These scopes are mutually exclusive. Explicit refresh waits for its terminal report by default and preserves typed per-target outcomes without exposing internal execution windows as request boundaries. - Scans publish validated incremental results rather than replacing the index at completion. Every write advances a monotonic revision; older work must merge, retry, or fail, never revert newer facts. A generation is complete only after covering every root and replaying events observed during it.
- Read-dependent commands consume the latest snapshot, including partial initial results, and expose
progress, freshness, and an initial-index warning. Client auto-start defaults on. If auto-start
fails or is disabled, daemon absence, watcher loss, or incomplete coverage marks results
MAYBE_STALEand suggestswtt daemon start --detach --if-needed. Corruption, incompatibility, migration, permission failure, and uncertain ownership are errors. Metadata-only commands remain daemon-independent. - Successful Git mutations directly reconcile affected repositories. Reconciliation failure preserves the Git result and marks the index stale; background work cannot overwrite a newer reconciliation.
- Restart serves the last snapshot as pending validation while validating it in the background; provider and local-index authority remain independent.
- Configuration changes are debounced and applied atomically after validation. Invalid changes retain the prior configuration and warn; root changes schedule reconciliation.
- Each watcher generation resolves a configured root to its current canonical target. A rename,
deletion, recreation, mount replacement, or symlink-target change creates an observation gap:
affected coverage is
MAYBE_STALEuntil watchers are rebuilt and a full validation of the new target commits. Overlapping roots are union coverage; removing one root never removes a repository still reached through another. Platform watcher differences may delay detection until periodic validation but may never preserve authoritative coverage across an observed gap. - Operational logs are private, redacted, size-rotated, and retained within a bound in the application state directory. Foreground diagnostics may also use stderr, never stdout.
-
Fatal human CLI diagnostics keep the failed condition, causal chain, and recovery guidance distinct. The primary message identifies the operation and concrete condition; a stable code identifies the leaf condition; and an optional
help:section contains at most one preferred deterministic next action (or a short ordered list when the alternatives materially differ). Unknown I/O, Git, protocol, clock, cancellation, and internal failures receive no speculative hint. Diagnostics and user-controlled values are terminal-safe and bounded. A command is rendered only when every interpolated value is safely representable by the supported shell boundary; otherwise the exact value is presented separately with a placeholder command. Human hints remain on stderr and never enter JSON stdout, completion output, private switch records, or another machine protocol. -
Recovery facts belong to the application layer and remain independent of interface presentation. The CLI projects facts to command-oriented
help:text, while Mission may project the same facts to its typedSuggestedActionvocabulary. Neither interface derives scope, mutation safety, collision, freshness, or retry policy independently, and neither presentation type is translated into the other. -
wtt configowns typed configuration inspection and mutation throughshow,get KEY,set KEY VALUE,path,validate [PATH], andreference.showprints the effective validated configuration,pathprints the active path, andreferenceprints every supported key and default with concise comments.getandsetuse stable dotted keys. -
config setperforms one atomic read, typed update, whole-document validation, and replacement. An unknown key, invalid value, malformed existing file, or failed replacement leaves the prior bytes unchanged. Configuration output contains no secret value unless that field has an explicit disclosure contract. -
One statically defined typed schema is the sole inventory for stable dotted keys, defaults, validation metadata, reference output, and finite completion values. Boolean values complete as exactly
trueandfalse; public enums complete as exactly their accepted variants; numeric, path, glob, and binding values have no fabricated finite candidates. Schema completion is pure and remains available without reading or creating configuration or index state. Everytui.bindings.<action>leaf is generated from the canonical application-action registry. -
The pre-release provider configuration exposes intent rather than scheduler internals.
provider.automatic_refreshdefaults totrueand controls only unattended daemon refresh; TUI-visible and explicit refresh remain available when it is false.provider.background_refresh_secondsdefaults to 300 seconds for unattended active work. The application derives pending-check refresh at 30 seconds and recent-terminal core refresh at 3,600 seconds, with bounded absence and failure retry schedules. Closed PRs use that terminal cadence only through the two-day stabilization window defined by the provider convergence contract; stabilized terminal facets do not recur merely because core membership is refreshed. TUI-visible refresh uses a fixed, non-configurable 15-second heartbeat.provider.execution_window_targetsdefaults to 512 and bounds active provider target operations without limiting accepted refresh scope. Subprocess timeout, batch, concurrency, output, daemon validation, and log bounds remain advanced safety controls. -
The removed provider mode, payload TTL, scheduling TTL, retry-backoff, and relevant-elapsed keys have no aliases, deprecation parser, or compatibility shim. They are unknown keys and fail with an actionable validation error. Automatically created configuration and
config referenceuse the canonical schema, distinguish normal from advanced settings, and include concise section guidance and defaults. -
wtt doctoris read-only. It reports stable check identifiers andok,warning,failed, orunavailablestatus for configuration, scan roots, index compatibility, daemon/watcher state, Git, optional provider tooling, determinable shell integration, and configuration/state/database/ log paths. It never repairs state.--provideropts into bounded provider authentication/network checks; without it, provider diagnosis is local-tooling-only.--jsonemits a versioned object and human prose never enters structured stdout. -
A future index schema requires upgrading WTT and is never presented as rebuildable by an older binary. For a corrupt, unversioned, or otherwise incompatible rebuildable index, recovery is explicit and non-destructive: identify the database and state paths with
wtt doctor, stop the owning daemon, copy the complete state directory to a backup, move the database and any matching SQLite sidecars into a separate retained backup location, then start the daemon and runwtt scan --waitto rebuild from Git and filesystem authority. WTT never deletes, overwrites, or automatically replaces the failed index. Custom--databasepaths use the same procedure at the reported location. -
Help groups arguments, command-specific options, scope options, output options, then global options. Conventional short flags are deliberately limited to
-a/--allfor list and switch,-f/--filter,-j/--json,-n/--dry-run,-y/--yes,-w/--wait,-c/--create, and-b/--base, plus existing-v,-h, and-V. Safety overrides and--all-repositoriesremain long-only. -
Every command whose primary result is meaningful state or a terminal operation report supports a versioned
--jsondocument. Human diagnostics and progress never contaminate structured stdout.
-
wtt listis the only non-interactive discovery command. The publicwtt searchspelling is removed.wtt switchwithout a target owns interactive fuzzy discovery. -
wtt list [--filter QUERY]reads the committed daemon-maintained index without starting a scan. Its human default is a headed responsive table led byREPOSITORY,WORKTREE,BRANCH,LOCAL,PR, and meaningfulAGE; primary worktree identity is never removed at narrow widths. Missing, unavailable, stale, unknown, and not-applicable values remain distinct. Redirected human output uses a deterministic non-terminal width; literal delimiter output, if retained, requires--tsv.--verboseadds operational detail and exact timestamps without changing row identity or order. -
--filteruses the shared grammar and values in the delegated query contract:query := term { whitespace term } term := fuzzy | qualifier ":" value qualifier := repo | wt | branch | state | pr | repo-pr | ci | fresh | hide value := exact | quoted-exact | exact "*"Repeated values of one qualifier are ORed; different qualifiers, flags, and
--filterare ANDed.repo-pr:openadmits repositories with a known nonterminal PR;open-or-unknownalso admits those without membership authority.pr:openfilters rows;hide:quiet-mainsuppresses clean main rows with known non-open state. Unknown grammar or values are errors. CLI, picker, and Mission share one Unicode-aware parser, evaluator, ranker, and validator; typed case breaks only otherwise-equal ties. Non-interactive identity values are exact unless suffixed by*. -
--dirtyand--cleanare mutually exclusive local-state filters; with neither, dirtiness does not filter. Query-time provider policy is--provider-refresh never|needed|all:neverreads committed cache only,neededsubmits demand-aware eligible work, andallfollows complete convergence for the promised query scope. -
Repository-aware commands use this effective scope:
Invocation context list/switchdefaultprunedefaultExplicit global scope Non-bare repository Canonical current repository Canonical current repository --all/--all-repositoriesOutside Git Global Error --all/ required--all-repositoriesBare repository Global Error --all/ required--all-repositoriesFilters only narrow this scope. Human results and plans state inferred repository scope once; interactive headers always show scope; JSON includes typed effective scope. Notices never enter JSON or machine-protocol stdout.
-
listretains repeatable structured filter flags and grouping/sorting conveniences only by compiling them into shared application models. It defaults to repository grouping and stable repository/worktree order; fuzzy text uses relevance with deterministic identity ties unless an explicit sort replaces rank. -
--jsonremains the authoritative versioned byte-safe interface. It contains typed effective scope and aworktreesarray; paths retain lossydisplayand exact Unixraw_base64. Human grouping, table decoration, and scope prose are absent. -
Interactive switch discovery is cache-only. Opening, editing, navigating, resizing, or toggling scope submits no provider request. Cached annotations remain visible and searchable, historical provider failures are not replayed as current warnings, and transient daemon readiness loss retains the last committed picker rows with one availability warning.
-
Generated Bash, Zsh, and Fish completions use a bounded quiet cache-only typed boundary. They do not start the daemon, invoke Git or provider tools, scan, access the network, or fall back to filesystem names. Switch completion reuses effective scope, live-target eligibility, and empty- query ordering. A unique logical name is inserted in effective scope; ambiguous global names use an absolute path accepted by
switch. Values unsafe for the active shell are omitted. Busy, absent, incompatible, or unavailable cache state returns no candidates and no prompt diagnostic. -
Repository reconciliation indexes every local branch and its attached worktree, if any, as rebuildable completion inventory.
create NAME --branchandswitch -c NAME --branchcomplete exact unoccupied local branches from that cache. Ordinary switch completion remains limited to existing worktrees and never implies mutation. Final creation revalidates branch existence, occupancy, base/HEAD authority, and destination collisions against live Git and the filesystem. -
Generated adapters obtain configuration keys and finite values only from the canonical typed schema.
config getand the key position ofconfig setcomplete every stable leaf;config setvalue completion is offered only for a schema-declared finite vocabulary. Selecting a typed completion boundary suppresses filesystem fallback even when that boundary returns no candidates. Completion never reads or repairs mutable configuration to discover the schema. -
Dynamic completion respects the real shell grammar. Zsh uses its
CURRENTword index.switchandremoveoffer a target only while their single positional slot is open, including after--, and do not mistake option values for targets.switch --createdoes not offer existing targets. Repeatable filter options remain repeatable. Bash, Zsh, and Fish implement equivalent position policy and never fall back to filesystem candidates at typed boundaries.
-
In an interactive terminal, omitting the subcommand opens the same cache-only picker as
wtt switchwithout a target. Switch-compatible global options may precede the omitted command.--helpand--versionretain root metadata behavior; another root operand remains an unknown subcommand rather than an implicit target; root completion continues to offer commands and options. Bare invocation with redirected stdin or stdout prints root usage and fails without opening a picker or waiting for input. -
Generated shell wrappers route effectively bare interactive invocation through the switch handoff. Opening or cancelling performs no mutation, scan, provider request, Git call, or network access. Only bare entry adds muted
wtt tuiandwtt --helphints above search. The header gives the cursor-scrolling query at most 30 cells, then shows scope and itsCtrl-Rdestination. Narrow layouts compact scope before shrinking the cursor-visible query viewport; query state is never truncated. Onboarding may displace one result row, never guidance or diagnostics. -
A worktree's logical name is the main worktree's directory name, the suffix after
{MAIN_WORKTREE_NAME}.for a managed child, or otherwise the linked worktree path's final component. -
Switch with
wtt switch {WORKTREE_NAME_OR_PATH}.- With no target, open the shared interactive picker.
- Use the shared effective scope above; interactive
Ctrl-Rtoggles repository/global scope. - Global exact-name resolution succeeds only when the name identifies exactly one worktree. On ambiguity, fail and list each candidate with its repository and absolute path. An absolute indexed path is also an exact public target so dynamic completion can disambiguate duplicate names. Resolution never applies fuzzy or suffix matching.
- An exact miss remains non-mutating and uses application-owned recovery facts in this precedence:
an existing branch-normalized logical name; one or more matching targets outside inferred
repository scope; the owning worktree of an already attached exact branch; an unoccupied exact
branch; viable explicit creation; then a known collision or missing creation context. A unique
out-of-scope target suggests
wtt switch --all; multiple targets list deterministic repository and absolute-path identities. An attached branch never suggests duplicate creation. An unoccupied slash-containing branch suggestswtt switch -c BRANCH; default creation both reuses that branch and derives the separator-free logical name. A generic miss offers creation only after live Git and filesystem preflight proves the derived identity available. Otherwise it explains the known constraint and points towtt create --help. Discovery of recovery facts does not change exact resolution, and cached scope facts are revalidated by the normal switch path when selected. - Detached worktrees are valid switch destinations. Before returning a destination, verify directly that the repository-qualified indexed worktree still exists. Missing or otherwise invalid paths are not selectable switch targets, and the selected target is revalidated immediately before its private path record is emitted. Switching to the current worktree succeeds.
- Completing a switch requires generated Bash, Zsh, or Fish integration because a child process cannot change its parent shell's directory. A direct invocation may still open and operate the interactive picker for discovery. Cancelling is quiet; accepting a valid destination emits one actionable integration diagnostic on stderr, writes nothing to stdout, performs no selected-path provider bookkeeping, and returns nonzero. Direct exact-name resolution follows the same completion behavior after verifying the destination.
- The private wrapper record carries the exact destination and prevalidated repository identity;
post-
cdacknowledgement cannot reclassify a completed switch after a pathname race. Human diagnostics never expose its framing or present stdout as shell integration. - Parent-shell
cdis the switch linearization point. Before it, the original directory and recency remain unchanged; after it, success wins and one synchronous acknowledgement allocates a globally monotonic MRU sequence. A vanished target produces a contextualwtt switchdiagnostic without wrapper internals; cleanup is silent under interactive job control. - For an empty interactive query, valid non-current destinations rank before the current worktree, then by successful-switch recency, meaningful worktree activity, and deterministic repository- qualified identity. Never-switched destinations use meaningful activity, not scan, refresh, or provider-attempt time. The initial recency facts remain frozen while the picker is open so row replacement preserves selection and viewport.
- For a nonempty query, relevance classes are exact repository, exact logical worktree, exact
branch, repository prefix, logical-worktree prefix, branch prefix, then fuzzy projection match.
A candidate takes its strongest class. Shared application query code normalizes relevance to
0..=1000and forms anchored near-tie bands no wider than 20 points from each band's highest remaining score. Successful-switch recency and meaningful activity order only within one class and band; neither may cross those boundaries.
-
wtt createandwtt switch --create(switch -c) share these creation modes:Mode Branch requirement Start point Result Default, derived branch absent Exact derived local branch absent --base REFor current HEADCreate derived branch and worktree. Default, derived branch present Exact local branch unoccupied; no --baseExisting branch Reuse branch and report reuse. --new-branch BRANCHNamed local branch absent --base REFor current HEADCreate named branch and worktree. --branch BRANCHNamed local branch exists Existing branch; --baseinvalidCreate worktree; Git decides occupancy. --detach REFNot applicable Resolved REF;--baseinvalidCreate detached worktree. An occupied default branch fails with its owning path; an existing default branch plus
--basefails; remote-tracking branches are never promoted.wtt switch BRANCHwithout--createremains non-mutating and suggests creation for an exact unoccupied local branch.switch --createaccepts every mode and uses the same planner, collision, hook, reconciliation, and recovery policy as standalone creation. Standalone creation never changes the parent shell; switch creation requires shell integration before mutation and enters the result after success.- The first operand is the worktree-name seed. Replace
/with-for derived logical identity and sibling placement, then reject empty,.,.., path separators, NUL, or a collision with any logical name, branch-derived name, registered worktree, or filesystem entry; never append an implicit suffix. - Validate new branch names and resolve every start point before mutation. In explicit
--branchand--detachmodes, the first operand remains only the worktree-name seed. - The repository's main worktree, in Git's terminology, is the parent for placement. For main
worktree
{ROOT_DIR}/{MAIN_WORKTREE_NAME}, children are created as{ROOT_DIR}/{MAIN_WORKTREE_NAME}.{NEW_WORKTREE_NAME}even when creation starts in a linked worktree. Bare repositories cannot create managed worktrees through this command initially. - On success, reconcile the affected repository and print the destination path. Standalone human
output describes the created identity;
--jsonis versioned and byte-safe. If only reconciliation fails, preserve the successfully created worktree, report a warning, and instruct the user to runwtt scan.
- The first operand is the worktree-name seed. Replace
-
Remove with
wtt remove {WORKTREE_NAME}. Bothremoveandpruneaccept--dry-run; dry run executes the real planner and current validation path, performs no mutation or confirmation, and renders the same candidates, safety facts, selection reasons, refusals, waived invariants, and branch disposition as execution.--dry-run --jsonemits a versioned advisory plan; later execution always recollects and revalidates mutable facts. -
Prune requires one or more explicit selection flags:
--merged-prselects authoritative merged pull requests;--merged-into[=BRANCH]selects candidate HEADs that Git proves are ancestors of an exact local branch;--include-unmergedselects attached branches for which both integration routes are decisively false; and--include-detachedselects locally safe detached or branchless worktrees. Barepruneis an error, removed pre-release--mergedand--no-prspellings are not aliases, and selectors form a rendered union while machine output preserves every established typed reason.- An omitted merge target resolves independently to each repository's structural main-worktree
branch. An explicit target resolves only
refs/heads/<BRANCH>; tags, remote-tracking branches, object IDs, revision expressions, invoking linked-worktree branches, forge defaults, and a literal fallback such asmainare never substituted. A missing or detached default target, or absent explicit target, aborts the complete assessment. The target worktree is always excluded. - Git ancestry is a typed positive, negative, or failed assessment equivalent to
git merge-base --is-ancestor C T. Unmerged classification requires decisive non-ancestry and current authoritative provider state that is not merged; open, closed-unmerged, and absent all qualify. Unavailable, failed, stale, cancelled, or unattempted authority is unknown, never a negative fact. - Use the shared effective-scope table. Canonical common-directory identity makes main and linked invocation equivalent. Repository scope includes an otherwise unindexed current repository; all-repository scope includes the index and first reconciles an unindexed current repository. One invocation assesses every eligible worktree in scope, not one selected branch.
- An omitted merge target resolves independently to each repository's structural main-worktree
branch. An explicit target resolves only
-
Removal and pruning safety:
Condition Default decision Sole override / consequence Main worktree Refuse remove and prune. None. Dirty Refuse. --allow-dirty; waive only dirtiness.Locked Refuse. --allow-locked; waive only the lock.Attached branch has no upstream or unpushed commits Refuse provider-only or unmerged prune; exact ancestry supersedes this requirement. --allow-unpushed; retain branch.Detached or branchless Exclude from prune. --include-detached; provider/upstream becomenot_applicable.Detached HEAD unreachable from a local branch, remote-tracking ref, or tag Refuse. --allow-unreferenced, valid only with--include-detached; warn about garbage collection.A remaining provider-dependent candidate lacks current authoritative presence/absence Cannot satisfy selection or unmerged classification. None; abort incomplete assessment. Named overrides are independent and never imply one another.
- Provider routing is selector-aware. Ancestry-only prune performs no provider work, and an ancestry-selected candidate in a mixed union needs no provider authority. Prune freezes only remaining provider-dependent targets, submits one durable scoped refresh, and waits through admission and execution windows. It plans only after every frozen incarnation has sufficient current authority. Failure, cancellation, or obsolete identity aborts without a plan, prompt, mutation, or silent retry.
- Show every proposed removal, including its safety state and reason for selection, before making
changes. Interactive sessions require confirmation; non-interactive sessions require
--yesand never assume confirmation.--yesskips only confirmation and grants no safety override. Human output groups eligible removals and decisive exclusions, suppresses irrelevant cascading reasons, usesPR: nonefor authoritative absence andPR: n/afor non-applicability, and ends with exact eligible/excluded/branch-deletion counts. Versioned JSON preserves the same typed applicability, authority, selection, reachability, and terminal assessment state. - After confirmation, attempt every eligible removal even if an earlier removal fails. Report removed, skipped, and failed worktrees separately; each skipped or failed target names its exact reason, including dirty or locked state.
- Perform removals through
git worktree remove; never delete a worktree directory directly. - An unmerged or detached removal always retains its branch. After safely removing a clean,
non-overridden worktree selected by merged-PR or ancestry proof, delete its local branch through
expected-ref guards. Execution revalidates candidate identity, exact target identity and tip,
and ancestry immediately before removal; ancestry-based branch deletion atomically verifies the
target ref again. If removal succeeds but that guard fails, retain the branch and report the
partial outcome. The public
--forcespelling remains removed. - Track and display Git's stale or prunable worktree metadata separately. Stale metadata alone never
causes
wtt removeorwtt pruneto remove a worktree.
-
Provider status initially supports GitHub through authenticated
gh. Resolve each attached branch's effective push repository using Git push-remote precedence andpushurl, preserving fork identity; branched non-bare main worktrees are normal candidates. Detached heads and repositories without a GitHub remote arenot_applicable. Missing or unauthenticatedgh, rate limits, and network failure areunavailableand never disable local work. -
Discover at most 16 admitted targets per GitHub host in one lightweight batch, publish exact core PR membership independently, and enrich only the selected PR's due facets through a separately bounded query. Ambiguous, paginated, identity-less, or incompatible responses use a bounded compatibility path and cannot establish false absence or revoke current authority. Draft PRs count as open; choose the most recently created open PR, otherwise the latest merged or closed PR.
-
Provider work is daemon-owned. Clients submit typed due, force, complete, or forced-scoped demand and never collect directly as a fallback. Valid demand is coalesced or durably queued under pressure; the default 512-target execution window bounds active work, not accepted scope. A complete refresh freezes one capable target set and converges across bounded, independently published checkpoints. Compatible requests follow existing work, while changed incarnations are reconciled without overlapping full sweeps.
-
Core membership, checks, diff/detail, and review-thread facets have independent freshness, demand, failure, and publication authority. Absence creates no enrichment; stabilized terminal facets do not recur automatically; optional failure cannot erase current membership. Human rows preserve stable facts and append at most
⟳,!, or~for executing, latest failure, or stale, in that precedence. -
Provider subprocesses, batches, turns, concurrency, output, IPC frames, and response writes are independently bounded. Operational warnings name the failed phase and report bounded per-turn and cumulative progress; queued or unstarted work is never mislabeled as a provider timeout. Clients receive one complete frame or a typed transport failure, never partial JSON.
- Repository setup hooks live under
.wtt/hooks/.wtt hooks installinstalls hooks under<git-common-dir>/wtt-hooks, where<git-common-dir>is returned bygit rev-parse --git-common-dir. All linked worktrees therefore read and write the same installed hooks.- Repository hooks are untrusted by default. Before first execution, show the commands/files that
will run and require interactive approval for that repository. Record trust against both the
repository identity and a digest of the installed hook contents; changed hooks require approval
again.
wtt hooks trustandwtt hooks revokemanage this decision explicitly. - Never prompt or execute untrusted hooks in a non-interactive session; fail the hook phase with an actionable message. A hook failure stops subsequent hooks and makes the parent operation fail, while preserving any worktree Git already created and reporting its path for recovery.
- The initial
post-createevent consists of regular executable files directly beneath.wtt/hooks/post-create. Installation rejects symlinks, directories, and non-executable files, copies the set into the common Git directory, and executes the installed files directly in bytewise lexical path order with the new worktree as the working directory. Hooks receiveWTT_EVENT,WTT_REPOSITORY,WTT_WORKTREE, andWTT_BRANCH; each hook is bounded byhooks.timeout_seconds, and timeout or cancellation terminates its process group.
- Bash, Zsh, and Fish are supported.
- Completions for the CLI.
wtt shell init {bash|zsh|fish}emits awttwrapper/function for users to source or evaluate.- The wrapper changes its directory for a successful
wtt switchdestination, includingswitch --create; standalonecreatenever does. Without integration, switch acceptance or exact resolution fails with installation guidance, and switch-create fails before mutation.
wtt tui opens Mission, the single global repository/worktree inventory.
- Mission always retains the complete indexed inventory as its source. Repository focus, all-worktrees mode, grouping, collapse, and search are reversible projections and cannot discard query state or repository-qualified selection while it remains eligible. The effective projection is visible and bounds rendered rows, local-diff work, and provider interest.
- A new session visibly applies
repo-pr:open-or-unknown hide:quiet-main. The first predicate admits repositories with a last-known nonterminal PR or without usable membership authority; the second hides only clean main worktrees with known non-open state. Stale facts retain their lifecycle and marker under the shared state vocabulary. Removing either predicate broadens only its dimension; Show all or clearing both reveals the full inventory. - Mission defaults to repository then PR/worktree-status grouping and meaningful Updated order. Updated is the newest PR update or durable local HEAD/branch change, with canonical identity ties; scan, cache, attempt, visibility, selection, scheduling, and completion times never count. Background replacement preserves existing identity order. Explicit sorting may recompute it, and an explicit refresh may do so once only when meaningful facts changed.
- Stable provider knowledge and lifecycle alone determine provider grouping, sorting, and filtering.
Provider-incapable worktrees use
N/Aindependently from local missing/prunable state. Every leaf group is collapsible; a collapsed bar is a typed selectable target and cannot leak a hidden child into actions or provider interest. Collapse preferences and child restoration survive projection changes, while search may reveal children without mutating those preferences. - Rows keep logical worktree and branch identity separate and expose color-independent local flags, PR lifecycle and number, CI summary, diff, review-thread counts, and meaningful age. Shared states and valid-empty values remain distinct. Narrow layouts remove fields by information priority and may expand only the selected logical row; both physical lines retain one selection, mouse target, hyperlink projection, and provider identity. Monochrome retains every semantic distinction.
- The viewport moves only enough to reveal the complete selected logical target and backfills at the
dataset end. Group bars, pinned context, and responsive detail count as physical lines. Projection,
resize, snapshot, and overlay changes preserve canonical focus and the smallest valid displacement;
ggresets focus and viewport to the absolute origin. Empty index, preset-hidden inventory, and user-query misses remain distinct and provide recovery appropriate to the affected layer. - Known PRs display
#<number>in CLI, picker, and Mission rows. A canonical provider URL becomes an OSC 8 hyperlink only on a supporting terminal; redirected output and--no-colorretain identical plain text, and structured JSON is unchanged. - One declarative application-action registry owns each public action's stable identifier,
tui.bindings.<action>key, defaults, label, description, target requirements, availability, and typed dispatch. Commands, configuration reference, and binding lookup derive from it. Unknown or conflicting configured bindings are diagnosed and fall back safely; unbound or unavailable actions remain discoverable with reasons.open_commandsdefaults to both?andCtrl-Punless explicitly replaced. - Single-line editors own every unmodified printable character. Only cancellation, acceptance,
navigation, the surface's documented completion action, and established readline editing keys
bypass text insertion.
Ctrl-WandCtrl-Backspaceuse readline word-rubout, treating:as the structured-token boundary. Terminal decoration cannot displace the visible cursor from the editor's logical position. /edits the shared listing query. Invalid drafts retain the last valid projection and show a terminal-safe error. Interactive prefix suggestions are cache-only; completion keys alone materialize an exact quoted candidate and cycle a frozen candidate set. Enter accepts the query or selected row without silently rewriting it. Search is Unicode-aware case-insensitive, with typed-case agreement only an otherwise-equal tie-breaker.- Discrete selectable lists use cyclic semantic previous/next navigation, skipping structural and hidden rows and remaining within the active pane or overlay. Empty and singleton lists are stable; paging, mouse scrolling, editor cursors, and scroll-only text remain bounded.
- Enter on a worktree opens a bounded contextual action popup whose initial facts come from cache. Optional last-commit detail loads asynchronously through an identity- and HEAD-qualified request; cancellation or obsolete completion cannot replace current state. Initial typed actions are Switch here, Open pull request, Copy path, Reveal in files, Refresh current, Remove, and repository Prune. Renderers perform no I/O, bounded adapters never interpolate shell commands, and failure preserves the popup with one typed diagnostic.
- Switch here restores the terminal before the shared final boundary and private wrapper response; direct invocation keeps it disabled. Copy path uses bounded OSC 52 with exact Unix path bytes, Open pull request uses only the cached canonical URL, and Reveal uses the supported platform adapter.
- TUI Remove and Prune reuse the unforced application planners. Prune remains repository-scoped and is unavailable until bounded planner-backed eligibility is known. Confirmation defaults to No; only a visible explicit Yes may mutate after final plan revalidation. These actions add no safety override or all-repository authority. Removing a missing/prunable registration revalidates its live repository identity and absence and never deletes replacement content or the retained branch.
- Mouse input is additive: overlays own it, wheel input targets the hovered scrollable region, and a second click activates an already selected modal row. Every feature remains keyboard-operable and mouse capture is released on every exit path.
- Diagnostics use a stable contextual footer and bounded typed overlay. Activity, row state, warnings, and explicit operation status remain distinct; repeated conditions deduplicate, obsolete generations cannot replace current state, and fatal exits restore the terminal. Mission retains the latest committed snapshot through transient daemon absence, timeout, contention, or ownership change and retries quietly; only an unusable local contract or explicit user action terminates it.
- Mission submits one coalesced due set when capable identities become visible and at the fixed
15-second heartbeat. Unchanged focus submits nothing; leaving visibility stops future interest
without cancelling accepted work.
refresh_current(r) forces the focused capable worktree;refresh_visible(R) forces exactly the visible capable set; unboundrefresh_all_indexedfollows one complete convergence sweep. Rescan remains separate filesystem/Git work. expand_current_groupdefaults to Right/landcollapse_current_groupto Left/h; bindings, Commands, Enter, mouse activation, and navigation dispatch the same typed transitions.