wt - Lua 5.4 Project
Commands
make all— format, lint, type-check, and testmake fmt— format code with styluamake lint— lint with luacheckmake check— type-check with emmyluamake 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 filelx run— run the application (src/main.lua)make ci— CI mode (format check + lint + check + test)
Structure
src/— application source code; entry point issrc/main.luaspec/— 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>, notluarocks 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/*.luamodules; userequire()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:
- A
cmd_<name>(args)function parsing arguments - Validation and error handling with
die()for user errors - System command execution via
run_cmd()orrun_cmd_silent() - 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}placeholderdefault_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 worktreehooks.symlink- array of file patterns to symlinkhooks.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 (
--ownflag): First remote becomesorigin, additional remotes added - Contributing (default):
originrenamed toupstream, 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.gitfile pointing to.barerun_cmd(cmd)- Execute command, return output and exit coderun_cmd_silent(cmd)- Execute command silently, return boolean successbranch_to_path(root, branch, style, separator)- Convert branch name to worktree pathload_global_config()- Load and parse~/.config/wt/config.luaload_project_config(root)- Load and parse<root>/.wt.luadetect_source_worktree(root)- Check if cwd is inside a worktree (not project root)check_hook_permission(root, hooks)- Prompt user for hook permission if neededrun_hooks(source, target, hooks, root)- Execute copy/symlink/run hooksextract_project_name(url)- Parse git URL to extract project namebranch_exists_local(git_dir, branch)- Check if branch exists locallyfind_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
-
Source Worktree Detection:
wt amust be run from inside a worktree (not project root) for hooks to trigger. From project root, hooks are skipped with a warning. -
Branch Existence Checking: When adding a worktree without
-b, checks both local branches and all remotes. Fails if branch exists on multiple remotes. -
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. -
Hook Permissions: Permissions are stored by absolute project root path. If you move a project, you'll be prompted again.
-
Default Remote Strategy: The
--ownflag significantly changes remote naming (origin vs upstream). Default behavior is optimized for contributing to others' projects. -
Worktree Path Conflicts: Checks for
.gitfile in target path before creating worktree to prevent overwrites. -
Force Flag:
-fbypasses uncommitted changes check but doesn't bypass branch deletion safety checks (bare HEAD, other worktrees). -
Error Recovery: Some operations attempt recovery (e.g.,
wt inittries to move.bareback to.gitif .git file creation fails).