From cded6e8d0d36499eb832955bfbd96e2723979740 Mon Sep 17 00:00:00 2001 From: Amolith Date: Sun, 18 Jan 2026 12:17:09 -0700 Subject: [PATCH] docs: update AGENTS.md for modular structure - Document src/wt/ module structure with cmd/ subdirectory - Add make dist/install commands and bundling workflow - Update testing section with actual patterns from spec/ - Consolidate non-obvious behaviors; remove outdated info - Remove utility function list (agents discover via requires) Assisted-by: Claude Opus 4.5 via Crush --- AGENTS.md | 198 ++++++++++++++++++++++-------------------------------- 1 file changed, 81 insertions(+), 117 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ad6fb5e7cd54a84dfa7351a11cab01d2f52e59fe..7fa15b2a3aa9066b0d85206c3d5433dce84e827f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,23 +4,34 @@ SPDX-FileCopyrightText: Amolith 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 `, 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/.lua` and exports a `cmd_(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_(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 `/.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= git ` for bare repo operations -- Use `git -C ` for worktree operations -- Check exit codes from `run_cmd()` before assuming success -- Quote paths properly when building shell commands +- **Bare repo operations**: `GIT_DIR=/.bare git ` +- **Worktree operations**: `git -C ` +- 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 ` 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