workforest 0.3.0__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. {workforest-0.3.0 → workforest-0.5.0}/.claude/rules/architecture.md +5 -0
  2. {workforest-0.3.0 → workforest-0.5.0}/.github/workflows/ci.yml +6 -2
  3. {workforest-0.3.0 → workforest-0.5.0}/.github/workflows/publish_aur.yml +2 -1
  4. {workforest-0.3.0 → workforest-0.5.0}/.github/workflows/publish_homebrew.yml +6 -0
  5. workforest-0.5.0/PKG-INFO +369 -0
  6. workforest-0.5.0/README.md +349 -0
  7. workforest-0.5.0/completions/_workforest +35 -0
  8. workforest-0.5.0/man/wf.1 +1 -0
  9. workforest-0.5.0/man/wf.5 +1 -0
  10. workforest-0.5.0/man/workforest.1 +535 -0
  11. workforest-0.5.0/man/workforest.5 +398 -0
  12. {workforest-0.3.0 → workforest-0.5.0}/packaging/AUR/PKGBUILD.template +1 -0
  13. {workforest-0.3.0 → workforest-0.5.0}/packaging/homebrew/workforest.rb.template +9 -0
  14. {workforest-0.3.0 → workforest-0.5.0}/pyproject.toml +9 -0
  15. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/__init__.py +1 -1
  16. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/cli.py +32 -15
  17. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/commands.py +30 -3
  18. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/completions.py +12 -13
  19. workforest-0.5.0/src/workforest/config.py +352 -0
  20. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/errors.py +8 -0
  21. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/examples/config.yaml +86 -47
  22. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/gitutil.py +8 -0
  23. workforest-0.5.0/src/workforest/hooks.py +493 -0
  24. workforest-0.5.0/src/workforest/jobs.py +234 -0
  25. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/launch.py +99 -49
  26. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/shell/completion.bash +7 -4
  27. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/shell/completion.zsh +7 -24
  28. workforest-0.5.0/src/workforest/shellinit.py +58 -0
  29. {workforest-0.3.0 → workforest-0.5.0}/tests/test_cli.py +54 -9
  30. workforest-0.5.0/tests/test_config.py +475 -0
  31. workforest-0.5.0/tests/test_hooks.py +630 -0
  32. workforest-0.5.0/tests/test_jobs.py +310 -0
  33. {workforest-0.3.0 → workforest-0.5.0}/tests/test_launch.py +187 -58
  34. workforest-0.5.0/tests/test_man.py +103 -0
  35. {workforest-0.3.0 → workforest-0.5.0}/tests/test_shell.py +80 -18
  36. {workforest-0.3.0 → workforest-0.5.0}/tests/test_tui.py +13 -3
  37. workforest-0.3.0/PKG-INFO +0 -240
  38. workforest-0.3.0/README.md +0 -220
  39. workforest-0.3.0/completions/_workforest +0 -44
  40. workforest-0.3.0/src/workforest/config.py +0 -200
  41. workforest-0.3.0/src/workforest/hooks.py +0 -149
  42. workforest-0.3.0/src/workforest/shellinit.py +0 -27
  43. workforest-0.3.0/tests/test_config.py +0 -256
  44. workforest-0.3.0/tests/test_hooks.py +0 -185
  45. {workforest-0.3.0 → workforest-0.5.0}/.claude/rules/conventions.md +0 -0
  46. {workforest-0.3.0 → workforest-0.5.0}/.claude/rules/tests.md +0 -0
  47. {workforest-0.3.0 → workforest-0.5.0}/.github/workflows/publish_pypi.yml +0 -0
  48. {workforest-0.3.0 → workforest-0.5.0}/.github/workflows/release.yml +0 -0
  49. {workforest-0.3.0 → workforest-0.5.0}/.gitignore +0 -0
  50. {workforest-0.3.0 → workforest-0.5.0}/LICENSE +0 -0
  51. {workforest-0.3.0 → workforest-0.5.0}/Makefile +0 -0
  52. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/__main__.py +0 -0
  53. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/integrations/__init__.py +0 -0
  54. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/integrations/claude.py +0 -0
  55. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/output.py +0 -0
  56. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/shell/workforest.sh +0 -0
  57. {workforest-0.3.0 → workforest-0.5.0}/src/workforest/tui.py +0 -0
  58. {workforest-0.3.0 → workforest-0.5.0}/tests/__init__.py +0 -0
  59. {workforest-0.3.0 → workforest-0.5.0}/tests/conftest.py +0 -0
  60. {workforest-0.3.0 → workforest-0.5.0}/tests/test_claude.py +0 -0
  61. {workforest-0.3.0 → workforest-0.5.0}/tests/test_commands.py +0 -0
  62. {workforest-0.3.0 → workforest-0.5.0}/tests/test_gitutil.py +0 -0
  63. {workforest-0.3.0 → workforest-0.5.0}/tests/test_harness.py +0 -0
  64. {workforest-0.3.0 → workforest-0.5.0}/tests/test_output.py +0 -0
  65. {workforest-0.3.0 → workforest-0.5.0}/uv.lock +0 -0
@@ -14,3 +14,8 @@
14
14
  on-disk state. Session lines are rewritten by JSON parsing, never by
15
15
  string substitution.
16
16
  - README.md is the project reference; there is no separate design document.
17
+ The man pages under `man/` (`workforest.1`, `workforest.5`) are its
18
+ installed counterpart: any change to commands, options, config keys,
19
+ environment variables, or exit codes updates both README.md and the
20
+ affected page in the same change. `tests/test_man.py` catches drift from
21
+ `cli.py`, not from prose — keep the wording in sync by hand.
@@ -11,7 +11,7 @@ jobs:
11
11
  container: archlinux:latest
12
12
  steps:
13
13
  - name: Install system dependencies
14
- run: pacman -Syu --noconfirm git python uv make zsh
14
+ run: pacman -Syu --noconfirm git python uv make zsh groff
15
15
 
16
16
  - uses: actions/checkout@v4
17
17
 
@@ -30,7 +30,7 @@ jobs:
30
30
  container: archlinux:latest
31
31
  steps:
32
32
  - name: Install system dependencies
33
- run: pacman -Syu --noconfirm git python python-build python-installer python-wheel python-hatchling
33
+ run: pacman -Syu --noconfirm git python python-build python-installer python-wheel python-hatchling man-db
34
34
 
35
35
  - uses: actions/checkout@v4
36
36
 
@@ -45,3 +45,7 @@ jobs:
45
45
  /tmp/venv/bin/wf --version
46
46
  /tmp/venv/bin/workforest shell-init bash | bash -n
47
47
  /tmp/venv/bin/workforest shell-init zsh | head -1 | grep -q Workforest
48
+ # man pages ride along in the wheel's share/man data, and shell-init
49
+ # puts that directory on $MANPATH for venv installs
50
+ MANPATH=/tmp/venv/share/man man -w workforest wf 5 workforest wf
51
+ /tmp/venv/bin/workforest shell-init bash | grep -q 'MANPATH=/tmp/venv/share/man'
@@ -16,7 +16,7 @@ jobs:
16
16
  steps:
17
17
  - name: Install system dependencies
18
18
  run: >
19
- pacman -Syu --noconfirm base-devel git openssh pacman-contrib namcap
19
+ pacman -Syu --noconfirm base-devel git openssh pacman-contrib namcap man-db
20
20
  python python-yaml python-build python-installer python-wheel python-hatchling
21
21
 
22
22
  - uses: actions/checkout@v4
@@ -41,6 +41,7 @@ jobs:
41
41
  pacman -U --noconfirm workforest-*.pkg.tar.zst
42
42
  workforest --version
43
43
  wf --version
44
+ man -w workforest wf 5 workforest wf
44
45
 
45
46
  - name: Push to AUR
46
47
  env:
@@ -44,6 +44,12 @@ jobs:
44
44
  wf --version
45
45
  workforest shell-init bash | bash -n
46
46
  workforest shell-init zsh | head -1 | grep -q Workforest
47
+ test -f "$(brew --prefix)/share/man/man1/workforest.1"
48
+ test -f "$(brew --prefix)/share/man/man1/wf.1"
49
+ test -f "$(brew --prefix)/share/man/man5/workforest.5"
50
+ test -f "$(brew --prefix)/share/man/man5/wf.5"
51
+ # the formula relocates them, so shell-init must not touch MANPATH
52
+ ! workforest shell-init bash | grep -q MANPATH
47
53
 
48
54
  - name: Push to tap
49
55
  env:
@@ -0,0 +1,369 @@
1
+ Metadata-Version: 2.5
2
+ Name: workforest
3
+ Version: 0.5.0
4
+ Summary: Git worktree forest management: create, open, and clean up per-branch worktrees with project-defined setup hooks
5
+ Project-URL: Homepage, https://github.com/ArkadyBuryakov/workforest
6
+ Author-email: Arkady Buryakov <arkady@buryakov.pro>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: cli,developer-tools,git,worktree
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Software Development :: Version Control :: Git
17
+ Requires-Python: >=3.14
18
+ Requires-Dist: pyyaml>=6.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Workforest
22
+
23
+ ## Elevator pitch
24
+
25
+ Your repo has one working directory; your AI agents want five. Workforest
26
+ gives every feature — and every agent — its own disposable git worktree, so
27
+ parallel work on the same repo never collides.
28
+
29
+ ## About
30
+
31
+ Git worktree forest management: one main checkout plus any number of
32
+ disposable, per-branch worktrees in a predictable location — created, set up,
33
+ opened, and cleaned up with one command.
34
+
35
+ ```
36
+ ~/dev/
37
+ ├── api/ # main checkout
38
+ └── worktrees/
39
+ └── api/
40
+ ├── feature-x/ # wf create feature-x
41
+ └── fix-y/
42
+ ```
43
+
44
+ - **Create** a worktree for any branch (local, remote, or brand new) and have
45
+ it set up automatically: symlinks for untracked assets (`node_modules`,
46
+ `.env`, …) and project-defined setup scripts.
47
+ - **Open** it in your editor — in your terminal, in a new terminal window or
48
+ multiplexer pane, or in a GUI app, all from a small shell-command config.
49
+ - **Run** named project scripts with well-known `WF_*` environment variables.
50
+ - **Delete** worktrees safely, or **checkout**: collapse one back into the
51
+ main checkout.
52
+ - Drive everything from an interactive fzf **TUI** (`wf` with no arguments).
53
+
54
+ ## Install
55
+
56
+ ```sh
57
+ # Arch Linux
58
+ yay -S workforest # AUR
59
+
60
+ # macOS (or Linux with Homebrew)
61
+ brew install arkadyburyakov/tap/workforest
62
+
63
+ # anywhere else
64
+ uv tool install workforest # or: pipx install workforest
65
+ ```
66
+
67
+ This installs two commands: `workforest` and its alias `wf`. Then add one
68
+ line to your `~/.bashrc` / `~/.zshrc`:
69
+
70
+ ```sh
71
+ eval "$(workforest shell-init)"
72
+ ```
73
+
74
+ This upgrades `wf` to a shell function (needed so `wf open` can change your
75
+ shell's directory — a plain binary cannot), registers completions, and — for
76
+ `uv tool`/`pipx` installs, whose files live in a venv — puts the man pages on
77
+ `$MANPATH`. Without it everything still works, but "open in current shell"
78
+ prints the `cd` command instead of performing it.
79
+
80
+ Reference: `man workforest` (commands) and `man 5 workforest` (the
81
+ configuration files); `wf` works in place of `workforest` for both.
82
+
83
+ Requirements: Linux or macOS, git ≥ 2.36, Python ≥ 3.14 (the AUR and
84
+ Homebrew packages bring their own). Optional: `fzf` for the TUI.
85
+
86
+ ## Quick start
87
+
88
+ ```sh
89
+ wf create feature/login # create worktree + run hooks + open in $EDITOR
90
+ wf list # what's in the forest
91
+ wf open login -o 'lazygit' # open with any command instead
92
+ wf run test # run a named script from config
93
+ wf run -b backend # detached; `wf stop backend` ends it
94
+ wf run make check -j2 # extra args are appended to the script command
95
+ wf checkout login # fold the branch back into the main checkout
96
+ wf delete fix-y # remove a worktree (asks about dirty changes)
97
+ wf # interactive TUI (fzf)
98
+ ```
99
+
100
+ ## Configuration
101
+
102
+ Layered, YAML or JSON; later layers override earlier ones:
103
+
104
+ | Layer | Location | Typical content |
105
+ |---|---|---|
106
+ | system | `/etc/workforest/config.yaml` | org-wide defaults |
107
+ | user | `~/.config/workforest/config.yaml` | your terminal/editor setup |
108
+ | project (shared) | `.workforest.yaml` in the repo root | repo policy, committed |
109
+ | project (local) | `.vscode/` or `.idea/` `.workforest.yaml` | personal overrides, untracked |
110
+
111
+ Scalars and lists replace; the `openers`/`wrappers`/`scripts` mappings
112
+ merge per key (`null` removes an entry). `workforest config` shows the
113
+ merged result and where each layer came from; `workforest init` scaffolds a
114
+ project file (`--local` for a personal one). Nothing in the environment
115
+ changes the result — files and flags only.
116
+
117
+ All keys, with defaults:
118
+
119
+ ```yaml
120
+ worktrees_dir: "$WF_MAIN/../worktrees/$WF_NAME" # where the forest lives
121
+ opener: "" # default opener: an `openers` name or a shell command;
122
+ # "" → $VISUAL → $EDITOR
123
+ openers: {} # name -> what `-o NAME` runs, and where
124
+ wrappers: {} # name -> command that runs $WF_COMMAND elsewhere (window, pane, direnv)
125
+ symlinks: [] # untracked assets linked from main into new worktrees
126
+ setup_scripts: [] # shell snippets run in a fresh worktree
127
+ scripts: {} # name -> command for `wf run NAME` (or a mapping, see Scripts)
128
+ stop_timeout: 30 # seconds a stopped script gets after SIGTERM before SIGKILL
129
+ ```
130
+
131
+ ### Openers
132
+
133
+ An opener is a shell command that runs with the worktree root as working
134
+ directory, either **in your terminal** (the default: the `wf` wrapper does
135
+ `cd` there and runs it) or **in the background** (`background: true`:
136
+ spawned detached, `wf` returns immediately — for GUI apps and commands that
137
+ hand off to a daemon or multiplexer). Optionally it runs **through a
138
+ wrapper**, a command that receives it as `$WF_COMMAND` and takes it
139
+ somewhere else: a new terminal window, a tmux window, a `direnv exec`. With
140
+ no config at all, `wf` runs `$VISUAL`/`$EDITOR` in your terminal.
141
+
142
+ ```yaml
143
+ opener: win # what `wf create` / `wf open` run by default
144
+
145
+ wrappers: # get the opener command as $WF_COMMAND
146
+ kitty:
147
+ command: kitty --title "$WF_TITLE" --directory "$WF_WORKTREE" $SHELL -c "$WF_COMMAND"
148
+ background: true
149
+ tmux: # the tmux server has its own environment: WF_ENV re-creates ours
150
+ command: tmux new-window -n "$WF_TITLE" -c "$WF_WORKTREE" "export $WF_ENV; $WF_COMMAND"
151
+ background: true
152
+
153
+ openers:
154
+ edit: $EDITOR "$WF_TARGET" # in your terminal
155
+ code: # GUI app: detached
156
+ command: code "$WF_WORKTREE"
157
+ background: true
158
+ kitty: # edit's command, in a new kitty window
159
+ from: edit
160
+ wrap: kitty
161
+ tmux: # edit's command, in a new tmux window
162
+ from: edit
163
+ wrap: tmux
164
+ git: lazygit
165
+ ```
166
+
167
+ An entry is a shell command string, or a mapping with either `command` (a
168
+ shell command, never a name) or `from` (another opener's command — one
169
+ level: the target has a `command` of its own — inheriting its `background`
170
+ unless the entry sets one), plus optionally `background: true` or
171
+ `wrap: NAME`. `wrap` and `background` never sit on the same entry: the
172
+ wrapper decides where the whole thing runs, via its own `background` flag.
173
+ `-o VALUE` (and `opener:`) is an `openers` name, else a shell command;
174
+ `-w NAME` on `create`/`open` overrides the opener's wrapper (`-w ''` for
175
+ none). Wrappers are environment-specific, so they belong in the per-machine
176
+ user config — a host without your terminal emulator simply has none.
177
+ `from` and `wrap` names are checked when the config loads, so `wf config`
178
+ reports a misspelled one.
179
+
180
+ All of them are plain shell commands, run via `$SHELL -c` with one variable
181
+ family in the environment — the same family the launched process and every
182
+ script receive:
183
+
184
+ | Variable | Value |
185
+ |---|---|
186
+ | `WF_MAIN` | main worktree path, `/home/user/Projects/project_name` |
187
+ | `WF_NAME` | repo name, `project_name` |
188
+ | `WF_WORKTREES_DIR` | resolved worktrees directory |
189
+ | `WF_WORKTREE` | this worktree's path |
190
+ | `WF_BRANCH` | its branch (empty if detached) |
191
+ | `WF_TARGET` | the `-p` argument, default `.` (launch-only) |
192
+ | `WF_TITLE` | window label, `project_name: feat-x` (launch-only) |
193
+ | `WF_ENV` | all of the above as shell-quoted `NAME=value` assignments (launch-only) |
194
+ | `WF_COMMAND` | in a wrapper: the opener command, unexpanded |
195
+
196
+ Standard shell rules apply — there is no workforest template syntax:
197
+ `"$WF_X"` is exactly one argument, bare `$WF_X` word-splits, and `$$`,
198
+ braces, pipes, and `&&` mean whatever your shell says they mean (a
199
+ misspelled `$WF_VAR` expands to empty, as in any shell). `$WF_COMMAND`
200
+ reaches the wrapper unexpanded, so run it through a shell of its own for its
201
+ `$WF_*` references to resolve: `$SHELL -c "$WF_COMMAND"`. When that shell
202
+ runs somewhere this environment is not inherited — a tmux server, an ssh
203
+ host — hand the family over as text: `"export $WF_ENV; $WF_COMMAND"` is
204
+ re-parsed on the far side, quoting intact. Background
205
+ processes shed activation state inherited from the invoking shell (Python
206
+ venv, conda, nvm, rvm) so a new window starts clean instead of carrying an
207
+ environment it cannot deactivate.
208
+
209
+ Fully commented reference configs:
210
+ [`config.yaml`](src/workforest/examples/config.yaml) (user/system) and
211
+ [`.workforest.yaml`](src/workforest/examples/.workforest.yaml) (project) —
212
+ installed to `/usr/share/doc/workforest/examples/` by the Arch package.
213
+
214
+ ### Scripts
215
+
216
+ `wf run NAME [ARGS...]` runs a `scripts` entry from the root of the current
217
+ worktree, with any extra arguments shell-quoted and appended. An entry is a
218
+ shell command string, or a mapping:
219
+
220
+ ```yaml
221
+ scripts:
222
+ test: npm test # shorthand for {command: npm test}
223
+ frontend:
224
+ command: cd frontend && npm run dev
225
+ exclusive: true # at most one instance per project: it owns the port
226
+ backend:
227
+ command: docker compose up
228
+ exclusive: true
229
+ background: true # detached, output in a log file
230
+ cleanup: docker compose down # runs after the command ends, however it ended
231
+ stop_timeout: 60 # give it a minute to come down before SIGKILL
232
+ ```
233
+
234
+ The command runs in a process group of its own, in the foreground of your
235
+ terminal, so Ctrl-C and Ctrl-Z reach it directly; a signal sent to `wf`
236
+ itself (SIGINT, SIGTERM, SIGHUP) is forwarded to the whole group. `cleanup`
237
+ then runs — after a normal exit, a failure, or a kill — in the same
238
+ worktree with the same `WF_*` variables. `wf run` fails with the command's
239
+ status, or `128+N` when the command was killed by signal N; a command
240
+ killed by SIGINT ends `wf` the way Ctrl-C would, so a shell loop around it
241
+ aborts.
242
+
243
+ A `background` script (or `wf run -b NAME`) is detached instead: it runs
244
+ under a supervisor of its own — the same process-group and cleanup
245
+ handling, minus the terminal — with its output in
246
+ `.git/workforest/logs/NAME/WORKTREE.log` of the main checkout, and `wf`
247
+ returns as soon as it is clearly running (a command that dies right away
248
+ is reported with the tail of its log). That way several long-running
249
+ scripts can be started from one terminal. `wf stop NAME` stops this
250
+ worktree's instance, foreground or background, from any terminal;
251
+ `--all` stops every worktree's. A script runs once per worktree: starting
252
+ one that is already running there fails (`wf stop` it first).
253
+
254
+ An `exclusive` script runs at most once per project: starting it stops the
255
+ running instance in any worktree (this one included), waits for that
256
+ instance's cleanup to finish, and only then starts. Stopping — by `wf
257
+ stop` or by preemption — is SIGTERM, then SIGKILL after `stop_timeout`
258
+ seconds (30 by default; the top-level key changes it for every script, an
259
+ entry's own `stop_timeout` for that one). The stopped instance's `wf run`
260
+ reports ``killed by SIGTERM (stopped by `wf run backend` in 'feat-x')``
261
+ and exits 143. Running instances are recorded under
262
+ `.git/workforest/running/` in the main checkout. A record is a hint, not
263
+ the truth: pid, process group, and boot are verified before anything is
264
+ signalled, so a crash, a reboot, or a `kill -9`'d `wf` leaves nothing to
265
+ tidy by hand. A command still running after its `wf run` is gone (an
266
+ orphan) is stopped and cleaned up by whoever stops it when `/proc`
267
+ confirms the pid was not recycled; elsewhere it is reported and left
268
+ alone.
269
+
270
+ ### Script environment
271
+
272
+ `setup_scripts`, `scripts`, and hooks run via `$SHELL -c` with:
273
+
274
+ | Variable | Value |
275
+ |---|---|
276
+ | `WF_MAIN` | main worktree path |
277
+ | `WF_NAME` | repo name (main checkout directory name) |
278
+ | `WF_WORKTREE` | current/new worktree path |
279
+ | `WF_WORKTREES_DIR` | resolved worktrees directory |
280
+ | `WF_BRANCH` | branch of the current/new worktree |
281
+
282
+ `worktrees_dir` is a template using the same naming pattern: `$WF_MAIN` and
283
+ `$WF_NAME` (plus regular environment variables like `$HOME`) expand there —
284
+ the per-worktree variables don't, since no worktree exists yet when the base
285
+ directory is resolved.
286
+
287
+ ### Example project config
288
+
289
+ ```yaml
290
+ # .workforest.yaml — committed to the repo
291
+ symlinks: [node_modules, .env]
292
+ setup_scripts:
293
+ - npm install --prefer-offline
294
+ scripts:
295
+ test: npm test
296
+ migrate: npm run db:migrate
297
+ frontend:
298
+ command: cd frontend && npm run dev
299
+ exclusive: true
300
+ backend:
301
+ command: docker compose up
302
+ exclusive: true
303
+ background: true
304
+ cleanup: docker compose down
305
+ stop_timeout: 60
306
+ ```
307
+
308
+ ## Commands
309
+
310
+ ```
311
+ workforest create [BRANCH] [-o OPENER] [-w WRAPPER] [-p PATH] [--no-hooks] [--no-open]
312
+ workforest open [NAME] [-o OPENER] [-w WRAPPER] [-p PATH]
313
+ workforest list [--porcelain]
314
+ workforest delete NAME... [--force] [--delete-branch | --keep-branch]
315
+ workforest checkout NAME [--force]
316
+ workforest run [-b] SCRIPT [ARGS...]
317
+ workforest stop SCRIPT [--all]
318
+ workforest tui [MODE]
319
+ workforest init [--local]
320
+ workforest config [--json]
321
+ workforest shell-init [bash|zsh]
322
+ workforest claude copy-session SESSION_ID # experimental
323
+ ```
324
+
325
+ `open` without NAME opens the worktree you are standing in.
326
+
327
+ `create` resolves BRANCH in order: existing local branch, then a branch on
328
+ exactly one remote (checked out tracking it), then a brand-new branch.
329
+ `REMOTE/BRANCH` picks the remote explicitly — needed when several remotes
330
+ carry the same branch name; if that local name is already taken, `create`
331
+ prompts for a different one.
332
+
333
+ `workforest claude` (shown only when `~/.claude` exists) copies a Claude
334
+ Code session from the main worktree into the current one. It is
335
+ **experimental**: it manipulates Claude Code's private on-disk state,
336
+ which is not a stable interface, so any Claude Code update may break it.
337
+
338
+ Exit codes: `0` ok · `1` error · `2` usage · `3` cancelled · `4` config error
339
+ · `128+N` the `run` command was killed by signal N.
340
+ Human messages go to stderr; stdout carries only machine output (`cd`
341
+ directives for the `wf` wrapper, `--porcelain` listings, dumps).
342
+
343
+ ## Development
344
+
345
+ ```sh
346
+ uv sync # venv + dev dependencies (uv.lock)
347
+ make check # ruff + mypy --strict + pytest (coverage gate ≥ 90%)
348
+ make install # install this checkout as a uv tool (~/.local/bin/workforest)
349
+ make uninstall # remove it again
350
+ ```
351
+
352
+ Man pages are hand-written roff under `man/` (`workforest.1` and
353
+ `workforest.5`, plus `wf.1`/`wf.5` links to them); `tests/test_man.py` fails when they drift from
354
+ `cli.py`. They ship as `share/man` data in the wheel, which is how every
355
+ package gets them: the Arch package installs the wheel to `/usr`, the
356
+ Homebrew formula relocates them out of its venv, and `uv tool`/`pipx` keep
357
+ them in the venv where `workforest shell-init` points `$MANPATH`.
358
+
359
+ Packaging templates live under `packaging/` (one directory per package
360
+ manager: `packaging/AUR/`, `packaging/homebrew/`); the `@VERSION@` and
361
+ `@SHA256@` placeholders are filled in at release time.
362
+ Release: bump `__version__` and push to main — CI tags the release,
363
+ renders the templates, and publishes to PyPI, the AUR, and the
364
+ [Homebrew tap](https://github.com/ArkadyBuryakov/homebrew-tap). The
365
+ published AUR package and tap are the only places rendered recipes exist.
366
+
367
+ ## License
368
+
369
+ [MIT](./LICENSE)