AGENTS.md

wt, Lua worktree manager

Commands

  • make all — format, lint, type-check, and test (the standard workflow)
  • make test-quietuse this! shows summary on success, full output on failure
  • make test — verbose test output (busted)
  • lx test spec/foo_spec.lua — run a single test file
  • lx run — run the application (src/main.lua)
  • make dist — bundle all modules into dist/wt single-file executable
  • make install — install to ~/.local/bin/
  • make ci — CI mode (format check + lint + check + test)

Structure

src/
├── main.lua           # Entry point, command dispatch
└── wt/
    ├── exit.lua       # Exit codes
    ├── shell.lua      # run_cmd, run_cmd_silent, die, quote
    ├── path.lua       # Path manipulation (branch_to_path, etc.)
    ├── git.lua        # Git operations, project root detection
    ├── config.lua     # Config loading (global + project)
    ├── hooks.lua      # Hook permissions and execution
    ├── help.lua       # Usage text
    └── cmd/           # One module per command
spec/                  # Tests (busted framework, *_spec.lua)
scripts/bundle.lua     # Builds single-file dist/wt

Lux Handles These (don't suggest manually)

  • Dependencies: use lx add <pkg>, not luarocks install
  • Tools (busted, luacheck, stylua): auto-installed on first use
  • Module paths: Lux loader resolves requires; don't manipulate package.path

Code Style

  • Lua 5.4; use LuaCATS annotations (---@param, ---@return, ---@class)
  • Tabs for indentation, 120 char line limit (see stylua.toml)
  • Prefix intentionally unused variables with _ to silence luacheck

Architecture

Core Pattern: Bare Repo + Worktrees

wt manages git repositories using a bare repository structure:

  • .bare/ - the actual git repository (moved from .git/)
  • .git - a file pointing to .bare/ (makes git commands work)
  • Worktrees checked out as sibling directories alongside .bare/

Module Organization

Each command lives in src/wt/cmd/<name>.lua and exports a cmd_<name>(args) function. Commands:

  1. Parse flags and positional args from the args table
  2. Validate inputs with shell.die(msg) for user errors
  3. Execute via shell.run_cmd() / shell.run_cmd_silent()
  4. Return consistent exit codes from wt.exit

Utility modules (shell, git, path, config, hooks) are shared across commands.

Exit Codes and Error Handling

local exit = require("wt.exit")
exit.EXIT_SUCCESS      -- 0: normal completion
exit.EXIT_USER_ERROR   -- 1: user mistakes (bad args, conflicts)
exit.EXIT_SYSTEM_ERROR -- 2: system failures (git errors, IO errors)

Use shell.die(msg, code) to print to stderr and exit. Default code is EXIT_USER_ERROR.

Git Command Patterns

  • Bare repo operations: GIT_DIR=<path>/.bare git <command>
  • Worktree operations: git -C <path> <command>
  • Always check exit codes from run_cmd() before assuming success
  • Use shell.quote(str) when embedding user input in commands

Testing

Tests use busted framework in spec/. Key patterns:

-- Load wt as module (exports functions when required, not run directly)
package.path = package.path .. ";./?.lua"
local wt = dofile("src/main.lua")

-- Use spec/test_helper.lua for git operations
local helper = require("spec.test_helper")
local git = helper.git  -- Preconfigured git wrapper (suppresses noise)

-- Standard setup/teardown for temp directories
local temp_dir
setup(function()
    local handle = io.popen("mktemp -d")
    if handle then
        temp_dir = handle:read("*l")
        handle:close()
    end
end)
teardown(function()
    if temp_dir then os.execute("rm -rf " .. temp_dir) end
end)

The dofile("src/main.lua") pattern works because main.lua detects when it's required vs run directly and exports test utilities.

Distribution

make dist bundles all modules into a single dist/wt file using scripts/bundle.lua. The bundler:

  • Embeds each module as a string literal
  • Provides custom require() that loads embedded modules first
  • Falls back to real require() for stdlib

Order matters: Update MODULE_ORDER in scripts/bundle.lua when adding new modules (dependencies before dependents).

Non-Obvious Behaviors

  1. Source Worktree Detection: wt a must run from inside a worktree (not project root) for hooks to trigger.
  2. Idempotent Add: Running wt a <branch> when worktree exists prints path and succeeds (exit 0), with hints if run from root.
  3. Branch Existence Checking: Adding without -b checks both local and all remotes. Fails if branch exists on multiple remotes (ambiguous).
  4. Bare HEAD Management: During wt init, bare repo's HEAD moves to placeholder so default branch can be checked out in worktree.
  5. Config File Format: Both return { ... } and bare table { ... } are accepted (loader tries prepending return).
  6. Test Module Export: src/main.lua uses pcall(debug.getlocal, 4, 1) to detect if it was required and exports internal functions for testing.

External Dependencies

  • gum - interactive prompts (choose, confirm)
  • Standard POSIX: mkdir, cp, ln, rm, mv, pwd, mktemp
  • git