@@ -4,23 +4,34 @@ SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
SPDX-License-Identifier: CC0-1.0
-->
-# wt - Lua 5.4 Project
+# wt, Lua worktree manager
## 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!)
+- `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/` — 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)
+```
+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`
@@ -28,10 +39,9 @@ SPDX-License-Identifier: CC0-1.0
- 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
+- Lua 5.4; use LuaCATS annotations (`---@param`, `---@return`, `---@class`)
- 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
+- Prefix intentionally unused variables with `_` to silence luacheck
## Architecture
@@ -41,125 +51,79 @@ wt manages git repositories using a **bare repository structure**:
- `.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.
+### 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`
-### 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`
+Utility modules (`shell`, `git`, `path`, `config`, `hooks`) are shared across commands.
### 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:
-```lua
-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:
```lua
-return {
- hooks = {
- copy = {"Makefile", "*.mk"},
- symlink = {".env", "config.toml"},
- run = {"make setup", "npm install"}
- }
-}
+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)
```
-#### 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/foo` → `project/feature/foo`)
-- **Flat**: Slashes become separators (`feature/foo` → `project_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
+Use `shell.die(msg, code)` to print to stderr and exit. Default code is `EXIT_USER_ERROR`.
### 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
+- **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
-### External Dependencies
-- `gum` - for interactive prompts (choose, confirm, table)
-- Standard POSIX utilities: `mkdir`, `cp`, `ln`, `rm`, `mv`
-- git (obviously)
+### Testing
-### Non-Obvious Behaviors
+Tests use busted framework in `spec/`. Key patterns:
-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.
+```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)
+```
-2. **Branch Existence Checking**: When adding a worktree without `-b`, checks both local branches and all remotes. Fails if branch exists on multiple remotes.
+The `dofile("src/main.lua")` pattern works because main.lua detects when it's `require`d vs run directly and exports test utilities.
-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.
+### Distribution
-4. **Hook Permissions**: Permissions are stored by absolute project root path. If you move a project, you'll be prompted again.
+`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
-5. **Default Remote Strategy**: The `--own` flag significantly changes remote naming (origin vs upstream). Default behavior is optimized for contributing to others' projects.
+**Order matters**: Update `MODULE_ORDER` in `scripts/bundle.lua` when adding new modules (dependencies before dependents).
-6. **Worktree Path Conflicts**: Checks for `.git` file in target path before creating worktree to prevent overwrites.
+## Non-Obvious Behaviors
-7. **Force Flag**: `-f` bypasses uncommitted changes check but doesn't bypass branch deletion safety checks (bare HEAD, other worktrees).
+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.
-8. **Error Recovery**: Some operations attempt recovery (e.g., `wt init` tries to move `.bare` back to `.git` if .git file creation fails).
+## External Dependencies
+- `gum` - interactive prompts (choose, confirm)
+- Standard POSIX: `mkdir`, `cp`, `ln`, `rm`, `mv`, `pwd`, `mktemp`
+- git