workforest 0.2.2__tar.gz → 0.3.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 (57) hide show
  1. workforest-0.3.0/.claude/rules/architecture.md +16 -0
  2. workforest-0.3.0/.claude/rules/conventions.md +12 -0
  3. workforest-0.3.0/.claude/rules/tests.md +17 -0
  4. {workforest-0.2.2 → workforest-0.3.0}/.github/workflows/publish_aur.yml +4 -25
  5. {workforest-0.2.2 → workforest-0.3.0}/.github/workflows/publish_homebrew.yml +19 -34
  6. {workforest-0.2.2 → workforest-0.3.0}/.gitignore +4 -1
  7. {workforest-0.2.2 → workforest-0.3.0}/PKG-INFO +48 -20
  8. {workforest-0.2.2 → workforest-0.3.0}/README.md +47 -19
  9. workforest-0.3.0/completions/_workforest +44 -0
  10. workforest-0.2.2/packaging/AUR/PKGBUILD → workforest-0.3.0/packaging/AUR/PKGBUILD.template +6 -2
  11. workforest-0.2.2/packaging/homebrew/workforest.rb → workforest-0.3.0/packaging/homebrew/workforest.rb.template +7 -5
  12. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/__init__.py +1 -1
  13. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/cli.py +51 -34
  14. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/commands.py +76 -28
  15. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/completions.py +30 -10
  16. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/config.py +41 -29
  17. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/errors.py +1 -1
  18. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/examples/config.yaml +34 -27
  19. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/gitutil.py +26 -23
  20. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/hooks.py +5 -2
  21. workforest-0.3.0/src/workforest/integrations/__init__.py +2 -0
  22. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/integrations/claude.py +19 -8
  23. workforest-0.3.0/src/workforest/launch.py +235 -0
  24. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/output.py +22 -7
  25. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/shell/completion.bash +3 -1
  26. workforest-0.3.0/src/workforest/shell/completion.zsh +67 -0
  27. workforest-0.3.0/src/workforest/shell/workforest.sh +18 -0
  28. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/tui.py +26 -13
  29. {workforest-0.2.2 → workforest-0.3.0}/tests/conftest.py +13 -7
  30. {workforest-0.2.2 → workforest-0.3.0}/tests/test_claude.py +2 -2
  31. {workforest-0.2.2 → workforest-0.3.0}/tests/test_cli.py +6 -5
  32. {workforest-0.2.2 → workforest-0.3.0}/tests/test_commands.py +77 -4
  33. {workforest-0.2.2 → workforest-0.3.0}/tests/test_config.py +2 -2
  34. {workforest-0.2.2 → workforest-0.3.0}/tests/test_gitutil.py +20 -7
  35. workforest-0.3.0/tests/test_launch.py +302 -0
  36. workforest-0.3.0/tests/test_output.py +56 -0
  37. {workforest-0.2.2 → workforest-0.3.0}/tests/test_shell.py +58 -6
  38. {workforest-0.2.2 → workforest-0.3.0}/tests/test_tui.py +10 -4
  39. workforest-0.2.2/completions/_workforest +0 -23
  40. workforest-0.2.2/packaging/AUR/.SRCINFO +0 -19
  41. workforest-0.2.2/src/workforest/integrations/__init__.py +0 -2
  42. workforest-0.2.2/src/workforest/launch.py +0 -162
  43. workforest-0.2.2/src/workforest/shell/completion.zsh +0 -38
  44. workforest-0.2.2/src/workforest/shell/workforest.sh +0 -16
  45. workforest-0.2.2/tests/test_launch.py +0 -204
  46. {workforest-0.2.2 → workforest-0.3.0}/.github/workflows/ci.yml +0 -0
  47. {workforest-0.2.2 → workforest-0.3.0}/.github/workflows/publish_pypi.yml +0 -0
  48. {workforest-0.2.2 → workforest-0.3.0}/.github/workflows/release.yml +0 -0
  49. {workforest-0.2.2 → workforest-0.3.0}/LICENSE +0 -0
  50. {workforest-0.2.2 → workforest-0.3.0}/Makefile +0 -0
  51. {workforest-0.2.2 → workforest-0.3.0}/pyproject.toml +0 -0
  52. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/__main__.py +0 -0
  53. {workforest-0.2.2 → workforest-0.3.0}/src/workforest/shellinit.py +0 -0
  54. {workforest-0.2.2 → workforest-0.3.0}/tests/__init__.py +0 -0
  55. {workforest-0.2.2 → workforest-0.3.0}/tests/test_harness.py +0 -0
  56. {workforest-0.2.2 → workforest-0.3.0}/tests/test_hooks.py +0 -0
  57. {workforest-0.2.2 → workforest-0.3.0}/uv.lock +0 -0
@@ -0,0 +1,16 @@
1
+ # Architecture invariants
2
+
3
+ - `gitutil.py` is the only module that spawns git. Consumers get typed
4
+ results; worktree data comes from `--porcelain -z` output, never from
5
+ parsing the human-readable form.
6
+ - `cli.py` is the sole stdout writer. Commands return
7
+ `ShellAction | str | None`; stdout carries the shell-wrapper cd protocol,
8
+ so nothing else may print there (hook/script stdout is diverted to stderr).
9
+ - `tui.py`: everything except the fzf subprocess is pure and unit-tested;
10
+ fzf is the only sanctioned external tool there.
11
+ - `completions.py` must never break the shell: any error yields an empty
12
+ candidate list, and output stays plain `NAME<TAB>ANNOTATION` lines.
13
+ - `integrations/claude.py` is experimental: it reads Claude Code's private
14
+ on-disk state. Session lines are rewritten by JSON parsing, never by
15
+ string substitution.
16
+ - README.md is the project reference; there is no separate design document.
@@ -0,0 +1,12 @@
1
+ # Code conventions
2
+
3
+ - Python 3.14, uv-managed. Verify changes with `make check` (ruff lint +
4
+ format check, mypy, pytest with a 90% coverage floor). Run
5
+ `uv run ruff format .` rather than hand-formatting.
6
+ - When a return value or constant bundles fields whose positions carry
7
+ meaning, use a small named dataclass — `@dataclass(slots=True,
8
+ frozen=True)` — not an anonymous tuple or nested dict. Plain dicts are for
9
+ genuine key→value lookups only (env maps, branch→remotes).
10
+ - Pre-1.0 with zero users: on breaking changes keep the clean design and
11
+ state what to re-run (e.g. re-eval shell-init). Never add
12
+ backward-compatibility shims.
@@ -0,0 +1,17 @@
1
+ ---
2
+ paths:
3
+ - "tests/**/*.py"
4
+ ---
5
+
6
+ # Test conventions
7
+
8
+ - Isolation contract (see `tests/conftest.py`): no test may read or write
9
+ the real environment. The autouse `isolated_env` fixture redirects HOME,
10
+ XDG_CONFIG_HOME, and git config, and pins SHELL=/bin/sh, EDITOR, and
11
+ NO_COLOR — rely on it instead of patching these per test.
12
+ - Build repos through the `repo` / `make_repo` fixtures and the `Repo`
13
+ helper methods (`add_branch`, `add_remote`, `write_project_config`,
14
+ `make_dirty`) rather than raw git calls.
15
+ - Coverage floor is 90% (`--cov-fail-under=90` in pyproject.toml); pure
16
+ helpers are expected to be unit-tested, subprocess/terminal glue may be
17
+ `# pragma: no cover`.
@@ -12,7 +12,7 @@ jobs:
12
12
  name: aur
13
13
  url: https://aur.archlinux.org/packages/workforest
14
14
  permissions:
15
- contents: write # commit the generated PKGBUILD back to main
15
+ contents: read
16
16
  steps:
17
17
  - name: Install system dependencies
18
18
  run: >
@@ -21,15 +21,15 @@ jobs:
21
21
 
22
22
  - uses: actions/checkout@v4
23
23
 
24
- - name: Update PKGBUILD to released version
24
+ - name: Render PKGBUILD from template
25
25
  run: |
26
26
  version="${GITHUB_REF_NAME#v}"
27
27
  cd packaging/AUR
28
- sed -i "s/^pkgver=.*/pkgver=$version/" PKGBUILD
29
- sed -i "s/^pkgrel=.*/pkgrel=1/" PKGBUILD
28
+ sed "s/@VERSION@/$version/g" PKGBUILD.template > PKGBUILD
30
29
  # makepkg refuses to run as root
31
30
  useradd -m builder
32
31
  chown -R builder .
32
+ # updpkgsums replaces the @SHA256@ placeholder with the real checksum
33
33
  runuser -u builder -- updpkgsums
34
34
  runuser -u builder -- sh -c 'makepkg --printsrcinfo > .SRCINFO'
35
35
 
@@ -70,24 +70,3 @@ jobs:
70
70
  git commit -m "Update to $version"
71
71
  git push origin HEAD:master
72
72
  fi
73
-
74
- - name: Commit generated PKGBUILD back to main
75
- run: |
76
- version="${GITHUB_REF_NAME#v}"
77
- cp packaging/AUR/PKGBUILD packaging/AUR/.SRCINFO /tmp/
78
- # The workspace is created by the host runner under a different UID
79
- # than the container's root, so git rejects it without this.
80
- git config --global --add safe.directory "$GITHUB_WORKSPACE"
81
- # The workflow runs detached at the release tag; move to main to commit.
82
- # -f discards the in-place edits we just copied to /tmp; build
83
- # artifacts in packaging/AUR are untracked and never git-added.
84
- git fetch origin main
85
- git checkout -f -B main origin/main
86
- cp /tmp/PKGBUILD /tmp/.SRCINFO packaging/AUR/
87
- git add packaging/AUR/PKGBUILD packaging/AUR/.SRCINFO
88
- if git diff --cached --quiet; then
89
- echo "Repo copy already up to date."
90
- else
91
- git commit -m "Update PKGBUILD to $version"
92
- git push origin main
93
- fi
@@ -3,32 +3,43 @@ name: Publish to Homebrew tap
3
3
  on:
4
4
  release:
5
5
  types: [published]
6
+ workflow_dispatch:
7
+ inputs:
8
+ version:
9
+ description: "Release version to publish (e.g. 0.2.1)"
10
+ required: true
6
11
 
7
12
  jobs:
8
13
  publish:
9
14
  runs-on: macos-latest
15
+ env:
16
+ RELEASE_VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}
10
17
  environment:
11
18
  name: homebrew-tap
12
19
  url: https://github.com/ArkadyBuryakov/homebrew-tap
13
20
  permissions:
14
- contents: write # commit the generated formula back to main
21
+ contents: read
15
22
  steps:
16
23
  - uses: actions/checkout@v4
17
24
 
18
- - name: Update formula to released version
25
+ - name: Render formula from template
19
26
  run: |
20
- version="${GITHUB_REF_NAME#v}"
27
+ version="${RELEASE_VERSION#v}"
21
28
  url="https://github.com/ArkadyBuryakov/workforest/archive/v$version.tar.gz"
22
29
  curl -fsSL "$url" -o /tmp/workforest.tar.gz
23
30
  sha256=$(shasum -a 256 /tmp/workforest.tar.gz | cut -d' ' -f1)
24
31
  cd packaging/homebrew
25
- sed -i '' "s|^ url .*| url \"$url\"|" workforest.rb
26
- sed -i '' "s|^ sha256 .*| sha256 \"$sha256\"|" workforest.rb
32
+ sed -e "s/@VERSION@/$version/g" -e "s/@SHA256@/$sha256/g" \
33
+ workforest.rb.template > workforest.rb
27
34
 
28
35
  - name: Build, test, and smoke test
29
36
  run: |
30
- brew install --build-from-source --formula packaging/homebrew/workforest.rb
31
- brew test --formula packaging/homebrew/workforest.rb
37
+ # Homebrew >= 4.6.4 refuses to install from a bare file path —
38
+ # formulas must live in a tap, so stage ours in a throwaway one.
39
+ brew tap-new --no-git arkadyburyakov/test
40
+ cp packaging/homebrew/workforest.rb "$(brew --repository arkadyburyakov/test)/Formula/"
41
+ brew install --build-from-source arkadyburyakov/test/workforest
42
+ brew test arkadyburyakov/test/workforest
32
43
  workforest --version
33
44
  wf --version
34
45
  workforest shell-init bash | bash -n
@@ -38,7 +49,7 @@ jobs:
38
49
  env:
39
50
  TAP_SSH_PRIVATE_KEY: ${{ secrets.TAP_SSH_PRIVATE_KEY }}
40
51
  run: |
41
- version="${GITHUB_REF_NAME#v}"
52
+ version="${RELEASE_VERSION#v}"
42
53
  # Host keys are pinned rather than keyscanned (same policy as the
43
54
  # AUR workflow); these are GitHub's published SSH fingerprints.
44
55
  mkdir -p /tmp/ssh
@@ -63,29 +74,3 @@ jobs:
63
74
  git commit -m "workforest $version"
64
75
  git push origin main
65
76
  fi
66
-
67
- - name: Commit generated formula back to main
68
- run: |
69
- version="${GITHUB_REF_NAME#v}"
70
- cp packaging/homebrew/workforest.rb /tmp/workforest.rb
71
- git config --global user.name "Arkady Buryakov"
72
- git config --global user.email "arkady@buryakov.pro"
73
- # The workflow runs detached at the release tag; move to main to
74
- # commit. Retry because the AUR publisher pushes to main from the
75
- # same release event and the two can race.
76
- for attempt in 1 2 3; do
77
- git fetch origin main
78
- git checkout -f -B main origin/main
79
- cp /tmp/workforest.rb packaging/homebrew/workforest.rb
80
- git add packaging/homebrew/workforest.rb
81
- if git diff --cached --quiet; then
82
- echo "Repo copy already up to date."
83
- break
84
- fi
85
- git commit -m "Update Homebrew formula to $version"
86
- if git push origin main; then
87
- break
88
- fi
89
- echo "Push raced with another workflow, retrying ($attempt)..."
90
- sleep 10
91
- done
@@ -13,9 +13,12 @@ build/
13
13
  .coverage
14
14
  htmlcov/
15
15
 
16
- # Packaging artifacts
16
+ # Packaging artifacts (rendered from *.template at release time)
17
17
  *.pkg.tar.zst
18
18
  pkg/
19
+ packaging/AUR/PKGBUILD
20
+ packaging/AUR/.SRCINFO
21
+ packaging/homebrew/workforest.rb
19
22
 
20
23
  # Tooling config
21
24
  .workforest.yaml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: workforest
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Git worktree forest management: create, open, and clean up per-branch worktrees with project-defined setup hooks
5
5
  Project-URL: Homepage, https://github.com/ArkadyBuryakov/workforest
6
6
  Author-email: Arkady Buryakov <arkady@buryakov.pro>
@@ -20,6 +20,14 @@ Description-Content-Type: text/markdown
20
20
 
21
21
  # Workforest
22
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
+
23
31
  Git worktree forest management: one main checkout plus any number of
24
32
  disposable, per-branch worktrees in a predictable location — created, set up,
25
33
  opened, and cleaned up with one command.
@@ -108,17 +116,17 @@ All keys, with defaults:
108
116
  ```yaml
109
117
  worktrees_dir: "$WF_MAIN/../worktrees/$WF_NAME" # where the forest lives
110
118
  opener: "" # default opener; "" → $VISUAL → $EDITOR
111
- openers: {} # name -> command template, e.g. edit: "$EDITOR {target}"
112
- window_command: "" # "" → current shell; or e.g.
113
- # "kitty --title {title} --directory {worktree} $WF_COMMAND"
119
+ openers: {} # name -> shell command, e.g. edit: '$EDITOR "$WF_TARGET"'
120
+ window_command: "" # "" → current shell; or e.g. kitty --title "$WF_TITLE"
121
+ # --directory "$WF_WORKTREE" $SHELL -c "$WF_COMMAND"
114
122
  symlinks: [] # untracked assets linked from main into new worktrees
115
123
  setup_scripts: [] # shell snippets run in a fresh worktree
116
124
  scripts: {} # name -> snippet for `wf run NAME`
117
125
  ```
118
126
 
119
- Openers and `window_command` are command templates sharing one variable
120
- family, which the launched process (and every script) also receives as
121
- environment variables:
127
+ Openers and `window_command` are plain shell commands, run via `$SHELL -c`
128
+ with one variable family in the environment — the same family the launched
129
+ process and every script receive:
122
130
 
123
131
  | Variable | Value |
124
132
  |---|---|
@@ -130,13 +138,17 @@ environment variables:
130
138
  | `WF_TARGET` | the `-p` argument, default `.` (launch-only) |
131
139
  | `WF_TITLE` | window label, `project_name: feat-x` (launch-only) |
132
140
 
133
- In templates, `$WF_X` (like any `$ENV` variable) inserts raw text that
134
- word-splits into multiple arguments, while `{x}` — `{worktree}`, `{target}`,
135
- `{title}`, … — inserts the shell-quoted value as exactly one argument.
136
- Openers run with the worktree root as working directory; in
137
- `window_command` the resolved opener command is additionally available as
138
- `$WF_COMMAND` (spliced into argv words) or `{command}` (one argument, for
139
- `$SHELL -c` wrappers).
141
+ Standard shell rules apply — there is no workforest template syntax:
142
+ `"$WF_X"` is exactly one argument, bare `$WF_X` word-splits, and `$$`,
143
+ braces, pipes, and `&&` mean whatever your shell says they mean (a
144
+ misspelled `$WF_VAR` expands to empty, as in any shell). Openers run with
145
+ the worktree root as working directory. In `window_command` the resolved
146
+ opener command is additionally available as `$WF_COMMAND` — still
147
+ unexpanded, so run it through a shell of its own for its `$WF_*` references
148
+ to resolve: `$SHELL -c "$WF_COMMAND"`. Spawned windows shed activation
149
+ state inherited from the invoking shell (Python venv, conda, nvm, rvm) so
150
+ the new session starts clean instead of carrying an environment it cannot
151
+ deactivate.
140
152
 
141
153
  Fully commented reference configs:
142
154
  [`config.yaml`](src/workforest/examples/config.yaml) (user/system) and
@@ -185,8 +197,23 @@ workforest tui [MODE]
185
197
  workforest init [--local]
186
198
  workforest config [--json]
187
199
  workforest shell-init [bash|zsh]
200
+ workforest claude copy-session SESSION_ID # experimental
188
201
  ```
189
202
 
203
+ `open` (and the opener shortcut, e.g. `wf edit`) without NAME opens the
204
+ worktree you are standing in.
205
+
206
+ `create` resolves BRANCH in order: existing local branch, then a branch on
207
+ exactly one remote (checked out tracking it), then a brand-new branch.
208
+ `REMOTE/BRANCH` picks the remote explicitly — needed when several remotes
209
+ carry the same branch name; if that local name is already taken, `create`
210
+ prompts for a different one.
211
+
212
+ `workforest claude` (shown only when `~/.claude` exists) copies a Claude
213
+ Code session from the main worktree into the current one. It is
214
+ **experimental**: it manipulates Claude Code's private on-disk state,
215
+ which is not a stable interface, so any Claude Code update may break it.
216
+
190
217
  Exit codes: `0` ok · `1` error · `2` usage · `3` cancelled · `4` config error.
191
218
  Human messages go to stderr; stdout carries only machine output (`cd`
192
219
  directives for the `wf` wrapper, `--porcelain` listings, dumps).
@@ -200,12 +227,13 @@ make install # install this checkout as a uv tool (~/.local/bin/workforest)
200
227
  make uninstall # remove it again
201
228
  ```
202
229
 
203
- Packaging recipes live under `packaging/` (one directory per package
204
- manager: `packaging/AUR/`, `packaging/homebrew/`).
205
- Release: bump `__version__` and push to main — CI tags the release and
206
- publishes to PyPI, the AUR, and the
207
- [Homebrew tap](https://github.com/ArkadyBuryakov/homebrew-tap), committing
208
- the regenerated recipes back to the repo.
230
+ Packaging templates live under `packaging/` (one directory per package
231
+ manager: `packaging/AUR/`, `packaging/homebrew/`); the `@VERSION@` and
232
+ `@SHA256@` placeholders are filled in at release time.
233
+ Release: bump `__version__` and push to main — CI tags the release,
234
+ renders the templates, and publishes to PyPI, the AUR, and the
235
+ [Homebrew tap](https://github.com/ArkadyBuryakov/homebrew-tap). The
236
+ published AUR package and tap are the only places rendered recipes exist.
209
237
 
210
238
  ## License
211
239
 
@@ -1,5 +1,13 @@
1
1
  # Workforest
2
2
 
3
+ ## Elevator pitch
4
+
5
+ Your repo has one working directory; your AI agents want five. Workforest
6
+ gives every feature — and every agent — its own disposable git worktree, so
7
+ parallel work on the same repo never collides.
8
+
9
+ ## About
10
+
3
11
  Git worktree forest management: one main checkout plus any number of
4
12
  disposable, per-branch worktrees in a predictable location — created, set up,
5
13
  opened, and cleaned up with one command.
@@ -88,17 +96,17 @@ All keys, with defaults:
88
96
  ```yaml
89
97
  worktrees_dir: "$WF_MAIN/../worktrees/$WF_NAME" # where the forest lives
90
98
  opener: "" # default opener; "" → $VISUAL → $EDITOR
91
- openers: {} # name -> command template, e.g. edit: "$EDITOR {target}"
92
- window_command: "" # "" → current shell; or e.g.
93
- # "kitty --title {title} --directory {worktree} $WF_COMMAND"
99
+ openers: {} # name -> shell command, e.g. edit: '$EDITOR "$WF_TARGET"'
100
+ window_command: "" # "" → current shell; or e.g. kitty --title "$WF_TITLE"
101
+ # --directory "$WF_WORKTREE" $SHELL -c "$WF_COMMAND"
94
102
  symlinks: [] # untracked assets linked from main into new worktrees
95
103
  setup_scripts: [] # shell snippets run in a fresh worktree
96
104
  scripts: {} # name -> snippet for `wf run NAME`
97
105
  ```
98
106
 
99
- Openers and `window_command` are command templates sharing one variable
100
- family, which the launched process (and every script) also receives as
101
- environment variables:
107
+ Openers and `window_command` are plain shell commands, run via `$SHELL -c`
108
+ with one variable family in the environment — the same family the launched
109
+ process and every script receive:
102
110
 
103
111
  | Variable | Value |
104
112
  |---|---|
@@ -110,13 +118,17 @@ environment variables:
110
118
  | `WF_TARGET` | the `-p` argument, default `.` (launch-only) |
111
119
  | `WF_TITLE` | window label, `project_name: feat-x` (launch-only) |
112
120
 
113
- In templates, `$WF_X` (like any `$ENV` variable) inserts raw text that
114
- word-splits into multiple arguments, while `{x}` — `{worktree}`, `{target}`,
115
- `{title}`, … — inserts the shell-quoted value as exactly one argument.
116
- Openers run with the worktree root as working directory; in
117
- `window_command` the resolved opener command is additionally available as
118
- `$WF_COMMAND` (spliced into argv words) or `{command}` (one argument, for
119
- `$SHELL -c` wrappers).
121
+ Standard shell rules apply — there is no workforest template syntax:
122
+ `"$WF_X"` is exactly one argument, bare `$WF_X` word-splits, and `$$`,
123
+ braces, pipes, and `&&` mean whatever your shell says they mean (a
124
+ misspelled `$WF_VAR` expands to empty, as in any shell). Openers run with
125
+ the worktree root as working directory. In `window_command` the resolved
126
+ opener command is additionally available as `$WF_COMMAND` — still
127
+ unexpanded, so run it through a shell of its own for its `$WF_*` references
128
+ to resolve: `$SHELL -c "$WF_COMMAND"`. Spawned windows shed activation
129
+ state inherited from the invoking shell (Python venv, conda, nvm, rvm) so
130
+ the new session starts clean instead of carrying an environment it cannot
131
+ deactivate.
120
132
 
121
133
  Fully commented reference configs:
122
134
  [`config.yaml`](src/workforest/examples/config.yaml) (user/system) and
@@ -165,8 +177,23 @@ workforest tui [MODE]
165
177
  workforest init [--local]
166
178
  workforest config [--json]
167
179
  workforest shell-init [bash|zsh]
180
+ workforest claude copy-session SESSION_ID # experimental
168
181
  ```
169
182
 
183
+ `open` (and the opener shortcut, e.g. `wf edit`) without NAME opens the
184
+ worktree you are standing in.
185
+
186
+ `create` resolves BRANCH in order: existing local branch, then a branch on
187
+ exactly one remote (checked out tracking it), then a brand-new branch.
188
+ `REMOTE/BRANCH` picks the remote explicitly — needed when several remotes
189
+ carry the same branch name; if that local name is already taken, `create`
190
+ prompts for a different one.
191
+
192
+ `workforest claude` (shown only when `~/.claude` exists) copies a Claude
193
+ Code session from the main worktree into the current one. It is
194
+ **experimental**: it manipulates Claude Code's private on-disk state,
195
+ which is not a stable interface, so any Claude Code update may break it.
196
+
170
197
  Exit codes: `0` ok · `1` error · `2` usage · `3` cancelled · `4` config error.
171
198
  Human messages go to stderr; stdout carries only machine output (`cd`
172
199
  directives for the `wf` wrapper, `--porcelain` listings, dumps).
@@ -180,12 +207,13 @@ make install # install this checkout as a uv tool (~/.local/bin/workforest)
180
207
  make uninstall # remove it again
181
208
  ```
182
209
 
183
- Packaging recipes live under `packaging/` (one directory per package
184
- manager: `packaging/AUR/`, `packaging/homebrew/`).
185
- Release: bump `__version__` and push to main — CI tags the release and
186
- publishes to PyPI, the AUR, and the
187
- [Homebrew tap](https://github.com/ArkadyBuryakov/homebrew-tap), committing
188
- the regenerated recipes back to the repo.
210
+ Packaging templates live under `packaging/` (one directory per package
211
+ manager: `packaging/AUR/`, `packaging/homebrew/`); the `@VERSION@` and
212
+ `@SHA256@` placeholders are filled in at release time.
213
+ Release: bump `__version__` and push to main — CI tags the release,
214
+ renders the templates, and publishes to PyPI, the AUR, and the
215
+ [Homebrew tap](https://github.com/ArkadyBuryakov/homebrew-tap). The
216
+ published AUR package and tap are the only places rendered recipes exist.
189
217
 
190
218
  ## License
191
219
 
@@ -0,0 +1,44 @@
1
+ #compdef workforest wf
2
+ # zsh completion for workforest / wf — installed to zsh site-functions.
3
+ # Dynamic candidates are delegated to `workforest --complete TOPIC`.
4
+
5
+ local topic cmd line name rest kind desc
6
+ local -a items cmds
7
+ cmd="${words[2]:-}"
8
+ if (( CURRENT == 2 )); then
9
+ topic=commands
10
+ else
11
+ case "$cmd" in
12
+ create) topic=branches ;;
13
+ open|delete|checkout) topic=worktrees ;;
14
+ run) topic=scripts ;;
15
+ claude) topic=claude-sessions ;;
16
+ tui|list|init|config|shell-init) topic=none ;;
17
+ *) topic=worktrees ;;
18
+ esac
19
+ fi
20
+ if [[ "$topic" != none ]]; then
21
+ items=(${(f)"$(workforest --complete "$topic" 2>/dev/null)"})
22
+ if [[ "$topic" == commands ]]; then
23
+ # NAME<TAB>KIND<TAB>DESCRIPTION → described candidates; one group
24
+ # (kind spelled out in the description) so list order, alignment,
25
+ # and the command/opener distinction survive fzf-tab's merging.
26
+ # Cyan for openers, but only under fzf-tab (fzf renders ANSI with
27
+ # --ansi; plain complist would print the escapes literally).
28
+ local pre="" post=""
29
+ if (( ${+functions[fzf-tab-complete]} )); then
30
+ pre=$'\e[36m' post=$'\e[0m'
31
+ fi
32
+ for line in "${items[@]}"; do
33
+ name="${line%%$'\t'*}"
34
+ rest="${line#*$'\t'}"
35
+ kind="${rest%%$'\t'*}"
36
+ desc="${rest#*$'\t'}"
37
+ [[ "$kind" == opener ]] && desc="${pre}opener: ${desc}${post}"
38
+ cmds+=("${name//:/\\:}:${desc}")
39
+ done
40
+ (( ${#cmds} )) && _describe -t commands 'workforest command' cmds
41
+ else
42
+ (( ${#items} )) && compadd -a items
43
+ fi
44
+ fi
@@ -1,6 +1,10 @@
1
1
  # Maintainer: Arkady Buryakov <arkady@buryakov.pro>
2
+ #
3
+ # Template — not a buildable PKGBUILD. On release the publish_aur workflow
4
+ # substitutes @VERSION@, fills sha256sums via updpkgsums, generates .SRCINFO,
5
+ # and pushes the rendered files to the AUR; nothing is committed back here.
2
6
  pkgname=workforest
3
- pkgver=0.2.1
7
+ pkgver=@VERSION@
4
8
  pkgrel=1
5
9
  pkgdesc="Git worktree forest management: per-branch worktrees with project-defined setup hooks"
6
10
  arch=(any)
@@ -10,7 +14,7 @@ depends=(python python-yaml git)
10
14
  makedepends=(python-build python-installer python-wheel python-hatchling)
11
15
  optdepends=('fzf: interactive TUI (workforest tui)')
12
16
  source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
13
- sha256sums=('47062e51ae374a9afce50ee0be6c39b08da6c96064450b3c2c58566d05fb7078')
17
+ sha256sums=('@SHA256@')
14
18
 
15
19
  build() {
16
20
  cd "$pkgname-$pkgver"
@@ -1,13 +1,15 @@
1
- # The publish_homebrew workflow rewrites url/sha256 on release and pushes
2
- # this file to the ArkadyBuryakov/homebrew-tap repo; this copy is the source
3
- # of truth for everything else (deps, completions, caveats, test).
1
+ # Template — not an installable formula. On release the publish_homebrew
2
+ # workflow substitutes @VERSION@ and @SHA256@ and pushes the rendered
3
+ # Formula/workforest.rb to the ArkadyBuryakov/homebrew-tap repo; nothing is
4
+ # committed back here. This copy is the source of truth for everything else
5
+ # (deps, completions, caveats, test).
4
6
  class Workforest < Formula
5
7
  include Language::Python::Virtualenv
6
8
 
7
9
  desc "Git worktree forest management with per-branch setup hooks"
8
10
  homepage "https://github.com/ArkadyBuryakov/workforest"
9
- url "https://github.com/ArkadyBuryakov/workforest/archive/v0.2.1.tar.gz"
10
- sha256 "47062e51ae374a9afce50ee0be6c39b08da6c96064450b3c2c58566d05fb7078"
11
+ url "https://github.com/ArkadyBuryakov/workforest/archive/v@VERSION@.tar.gz"
12
+ sha256 "@SHA256@"
11
13
  license "MIT"
12
14
  head "https://github.com/ArkadyBuryakov/workforest.git", branch: "main"
13
15
 
@@ -1,3 +1,3 @@
1
1
  """Workforest — git worktree forest management."""
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.3.0"