<!--
SPDX-FileCopyrightText: Amolith <amolith@secluded.site>

SPDX-License-Identifier: CC0-1.0
-->

# wt, Lua worktree manager

## Commands
- `make all` — format, lint, type-check, and test (the standard workflow)
- `make test-quiet` — **use 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
```lua
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:

```lua
-- 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 `require`d 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 `require`d and exports internal functions for testing.

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