AGENTS.md

wt - Lua 5.4 Project

Commands

  • make all — format, lint, type-check, and test
  • make fmt — format code with stylua
  • make lint — lint with luacheck
  • make check — type-check with emmylua
  • make test — run all tests with busted (verbose)
  • make test-quiet — run tests, show full output only on failure (use this!)
  • lx test spec/foo_spec.lua — run a single test file
  • lx run — run the application (src/main.lua)
  • make ci — CI mode (format check + lint + check + test)

Structure

  • src/ — application source code; entry point is src/main.lua
  • spec/ — test files; must end in _spec.lua (busted framework)
  • lux.toml — project manifest (dependencies, metadata)

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 only; use LuaCATS annotations (---@param, ---@return, etc.) for type safety
  • Tabs for indentation, 120 char line limit (see stylua.toml)
  • Extract reusable logic into src/*.lua modules; use require() to import
  • Handle errors explicitly; avoid silent failures

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/

This pattern allows multiple worktrees while keeping the repository clean.

Command Structure

Each command (c, n, a, r, l, f, init) has:

  1. A cmd_<name>(args) function parsing arguments
  2. Validation and error handling with die() for user errors
  3. System command execution via run_cmd() or run_cmd_silent()
  4. Consistent exit codes: EXIT_SUCCESS=0, EXIT_USER_ERROR=1, EXIT_SYSTEM_ERROR=2

Exit Codes and Error Handling

  • Use die(msg, code) for errors - it prints to stderr and exits
  • EXIT_USER_ERROR (1) - user mistakes (bad args, existing dirs)
  • EXIT_SYSTEM_ERROR (2) - system failures (git failures, IO errors)
  • EXIT_SUCCESS (0) - normal completion

Configuration System

Global Config (~/.config/wt/config.lua)

Returns a Lua table with optional fields:

  • branch_path_style - "nested" (default) or "flat"
  • flat_separator - string separator for flat style (default: _)
  • remotes - table mapping remote names to URL templates with ${project} placeholder
  • default_remotes - "prompt", table of names, or nil (prompts if remotes exist)

Example:

return {
    branch_path_style = "flat",
    flat_separator = "_",
    remotes = {
        github = "git@github.com:myuser/${project}.git",
        gitlab = "git@gitlab.com:myuser/${project}.git"
    },
    default_remotes = "prompt" -- or {"github", "gitlab"}
}

Project Config (.wt.lua in project root)

Returns a Lua table with optional hooks field:

  • hooks.copy - array of file patterns to copy from source to new worktree
  • hooks.symlink - array of file patterns to symlink
  • hooks.run - array of shell commands to run in new worktree

Hooks only run when wt a is executed from inside an existing worktree.

Example:

return {
    hooks = {
        copy = {"Makefile", "*.mk"},
        symlink = {".env", "config.toml"},
        run = {"make setup", "npm install"}
    }
}

Hook Permissions

Stored at ~/.local/share/wt/hook-dirs.lua as a table mapping project root paths to boolean allowed status. Users are prompted once per project with gum confirm.

Remote Management

  • URL Templates: Use ${project} placeholder that gets substituted
  • Own Projects (--own flag): First remote becomes origin, additional remotes added
  • Contributing (default): origin renamed to upstream, user's remotes added
  • Multiple Remotes: Support for pushing to multiple remotes from bare repo

Path Styles

  • Nested: Branch names keep slashes (feature/fooproject/feature/foo)
  • Flat: Slashes become separators (feature/fooproject_feature_foo)

Key Utility Functions

All in src/main.lua (consider extracting to modules if codebase grows):

  • find_project_root() - Walk up from cwd to find .bare/ or .git file pointing to .bare
  • run_cmd(cmd) - Execute command, return output and exit code
  • run_cmd_silent(cmd) - Execute command silently, return boolean success
  • branch_to_path(root, branch, style, separator) - Convert branch name to worktree path
  • load_global_config() - Load and parse ~/.config/wt/config.lua
  • load_project_config(root) - Load and parse <root>/.wt.lua
  • detect_source_worktree(root) - Check if cwd is inside a worktree (not project root)
  • check_hook_permission(root, hooks) - Prompt user for hook permission if needed
  • run_hooks(source, target, hooks, root) - Execute copy/symlink/run hooks
  • extract_project_name(url) - Parse git URL to extract project name
  • branch_exists_local(git_dir, branch) - Check if branch exists locally
  • find_branch_remotes(git_dir, branch) - Find remotes containing branch

Testing

No tests exist yet. When adding tests:

  • Create spec/ directory if needed
  • Test files must end in _spec.lua
  • Use busted framework
  • Mock git commands and filesystem operations
  • Test both success and error paths

Git Command Patterns

  • Always use GIT_DIR=<path> git <command> for bare repo operations
  • Use git -C <path> <command> for worktree operations
  • Check exit codes from run_cmd() before assuming success
  • Quote paths properly when building shell commands

External Dependencies

  • gum - for interactive prompts (choose, confirm, table)
  • Standard POSIX utilities: mkdir, cp, ln, rm, mv
  • git (obviously)

Non-Obvious Behaviors

  1. Source Worktree Detection: wt a must be run from inside a worktree (not project root) for hooks to trigger. From project root, hooks are skipped with a warning.

  2. Branch Existence Checking: When adding a worktree without -b, checks both local branches and all remotes. Fails if branch exists on multiple remotes.

  3. Bare HEAD Management: During wt init, bare repo's HEAD is moved to a placeholder branch so the default branch can be checked out in a worktree.

  4. Hook Permissions: Permissions are stored by absolute project root path. If you move a project, you'll be prompted again.

  5. Default Remote Strategy: The --own flag significantly changes remote naming (origin vs upstream). Default behavior is optimized for contributing to others' projects.

  6. Worktree Path Conflicts: Checks for .git file in target path before creating worktree to prevent overwrites.

  7. Force Flag: -f bypasses uncommitted changes check but doesn't bypass branch deletion safety checks (bare HEAD, other worktrees).

  8. Error Recovery: Some operations attempt recovery (e.g., wt init tries to move .bare back to .git if .git file creation fails).