tasktree-manager 2.11.1__tar.gz → 2.12.1__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 (71) hide show
  1. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.github/workflows/release.yml +112 -64
  2. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/CHANGELOG.md +18 -0
  3. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/PKG-INFO +3 -2
  4. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/README.md +5 -2
  5. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/docs/configuration.md +15 -3
  6. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/docs/installation.md +4 -1
  7. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/docs/troubleshooting.md +24 -24
  8. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/docs/user-guide.md +19 -8
  9. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/mise.toml +10 -7
  10. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/pyproject.toml +2 -1
  11. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/scripts/bump_version.py +38 -8
  12. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/_version.py +2 -2
  13. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/app.py +346 -83
  14. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/cli.py +11 -2
  15. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/commands.py +5 -0
  16. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/claude_hooks.py +47 -19
  17. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/config.py +99 -72
  18. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/forge.py +12 -10
  19. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/git_ops.py +208 -66
  20. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/models.py +18 -0
  21. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/task_manager.py +186 -76
  22. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/create_modal.py +75 -7
  23. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/setup_modal.py +27 -21
  24. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/task_list.py +23 -23
  25. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/worktree_list.py +44 -28
  26. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_app.py +69 -1
  27. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_claude_hooks.py +5 -1
  28. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_cli.py +10 -4
  29. tasktree_manager-2.12.1/tests/test_medium_fixes.py +484 -0
  30. tasktree_manager-2.12.1/tests/test_safety_fixes.py +338 -0
  31. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_task_manager.py +118 -18
  32. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/SKILL.md +0 -0
  33. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/assets/README.md +0 -0
  34. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/assets/dashboard_app.py +0 -0
  35. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/assets/data_viewer.py +0 -0
  36. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/assets/todo_app.py +0 -0
  37. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/assets/worker_demo.py +0 -0
  38. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/references/layouts.md +0 -0
  39. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/references/official-guides-index.md +0 -0
  40. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/references/styling.md +0 -0
  41. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/references/widgets.md +0 -0
  42. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.github/templates/CHANGELOG.md.j2 +0 -0
  43. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.github/workflows/ci.yml +0 -0
  44. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.gitignore +0 -0
  45. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/CONTRIBUTING.md +0 -0
  46. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/LICENSE +0 -0
  47. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/OPENSPEC.md +0 -0
  48. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/ROADMAP.md +0 -0
  49. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/docs/claude-code-integration-spec.md +0 -0
  50. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/__init__.py +0 -0
  51. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/__init__.py +0 -0
  52. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/agent_sessions.py +0 -0
  53. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/themes.py +0 -0
  54. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/__init__.py +0 -0
  55. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/app_header.py +0 -0
  56. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/messages_panel.py +0 -0
  57. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/status_panel.py +0 -0
  58. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/__init__.py +0 -0
  59. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/conftest.py +0 -0
  60. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_agent_sessions.py +0 -0
  61. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_commands.py +0 -0
  62. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_config.py +0 -0
  63. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_forge.py +0 -0
  64. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_git_ops.py +0 -0
  65. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_integration_git.py +0 -0
  66. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_memory.py +0 -0
  67. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_messages_panel.py +0 -0
  68. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_modals.py +0 -0
  69. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_setup_modal.py +0 -0
  70. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_status_panel.py +0 -0
  71. {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_worktree_badges.py +0 -0
@@ -4,9 +4,16 @@
4
4
  # Handles version bumping, testing, building, PyPI publishing,
5
5
  # and GitHub release creation.
6
6
  #
7
+ # Order matters: the version is only *computed* up front; lint and the full
8
+ # test matrix run against the pushed commit, and only when they pass is the
9
+ # bump committed, tagged and pushed. A red release run therefore never
10
+ # leaves a ghost tag or a bumped pyproject on main.
11
+ #
7
12
  # Triggers:
8
13
  # - Push to main: semantic-release determines if a release is needed
9
- # - Manual dispatch: choose release type, pre-release, and draft options
14
+ # - Manual dispatch: choose release type, pre-release, and draft options.
15
+ # Both `draft` and `pre_release` skip the PyPI upload (the pipeline can
16
+ # only produce final X.Y.Z versions) and only shape the GitHub release.
10
17
 
11
18
  name: Release
12
19
 
@@ -26,12 +33,12 @@ on:
26
33
  - minor
27
34
  - major
28
35
  pre_release:
29
- description: "Mark as pre-release?"
36
+ description: "Mark the GitHub release as pre-release? (skips PyPI)"
30
37
  required: false
31
38
  default: false
32
39
  type: boolean
33
40
  draft:
34
- description: "Create as draft release?"
41
+ description: "Create the GitHub release as a draft? (skips PyPI)"
35
42
  required: false
36
43
  default: false
37
44
  type: boolean
@@ -49,7 +56,6 @@ jobs:
49
56
  uses: actions/checkout@v4
50
57
  with:
51
58
  fetch-depth: 0
52
- token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
53
59
 
54
60
  - name: Set up Python
55
61
  uses: actions/setup-python@v5
@@ -57,84 +63,79 @@ jobs:
57
63
  python-version: "3.12"
58
64
 
59
65
  - name: Install dependencies
60
- run: pip install python-semantic-release build hatch-vcs
66
+ # Pinned: an unpinned resolver pull once produced a PSR/GitPython
67
+ # combination that crashed on startup
68
+ run: pip install "python-semantic-release==10.6.2"
61
69
 
62
70
  - name: Determine version
63
71
  id: determine_version
64
72
  env:
65
- GH_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
73
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66
74
  run: |
67
- # Get current version from pyproject.toml
68
- current_version=$(python -c "
69
- import re
70
- content = open('pyproject.toml').read()
71
- match = re.search(r'^\s*version\s*=\s*\"([^\"]+)\"', content, re.MULTILINE)
72
- print(match.group(1) if match else '0.0.0')
73
- ")
75
+ # The latest v* tag is the source of truth for the current version
76
+ # (semantic-release derives the next one from it). pyproject is
77
+ # only reported: a stray local bump must not shift the release.
78
+ latest_tag=$(git describe --tags --abbrev=0 --match 'v[0-9]*' 2>/dev/null || echo "v0.0.0")
79
+ current_version="${latest_tag#v}"
74
80
  echo "current_version=${current_version}" >> $GITHUB_OUTPUT
75
- echo "Current version: ${current_version}"
81
+ echo "Current version (latest tag): ${current_version}"
76
82
 
77
83
  release_type="${{ inputs.release_type || 'auto' }}"
78
84
 
79
85
  if [ "$release_type" = "auto" ]; then
80
- # Use semantic-release to determine bump type from commit history.
81
- # When no qualifying commits exist, `version --print` prints the
86
+ # Use semantic-release to determine the next version from the
87
+ # commit history. When no qualifying commits exist it prints the
82
88
  # CURRENT version — require an actual bump before releasing.
83
- VERSION=$(semantic-release version --print 2>/dev/null || echo "")
89
+ # A tool crash is an error, never "no release needed".
90
+ if ! VERSION=$(semantic-release version --print); then
91
+ echo "::error::semantic-release failed to compute the next version"
92
+ exit 1
93
+ fi
84
94
  if [ -n "$VERSION" ] && [ "$VERSION" != "$current_version" ]; then
85
- # Determine bump type by comparing versions
86
- IFS='.' read -r cur_major cur_minor cur_patch <<< "$current_version"
87
- IFS='.' read -r new_major new_minor new_patch <<< "$VERSION"
88
- if [ "$new_major" -gt "$cur_major" ]; then
89
- bump_type="major"
90
- elif [ "$new_minor" -gt "$cur_minor" ]; then
91
- bump_type="minor"
92
- else
93
- bump_type="patch"
94
- fi
95
- # Use bump_version.py to get the actual new version
96
- new_version=$(python scripts/bump_version.py "$bump_type" --dry-run 2>&1 | grep "New version:" | awk '{print $3}')
95
+ new_version="$VERSION"
97
96
  echo "new_version=${new_version}" >> $GITHUB_OUTPUT
98
97
  echo "released=true" >> $GITHUB_OUTPUT
99
- echo "bump_type=${bump_type}" >> $GITHUB_OUTPUT
100
- echo "Auto-detected bump type: ${bump_type}, version: ${new_version}"
98
+ echo "Auto-detected next version: ${new_version}"
101
99
  else
102
100
  echo "released=false" >> $GITHUB_OUTPUT
103
101
  echo "No release needed (no qualifying commits)"
104
102
  fi
105
103
  else
106
- # Manual bump using bump_version.py
107
- new_version=$(python scripts/bump_version.py "$release_type" --dry-run 2>&1 | grep "New version:" | awk '{print $3}')
108
- if [ -n "$new_version" ]; then
109
- echo "new_version=${new_version}" >> $GITHUB_OUTPUT
110
- echo "released=true" >> $GITHUB_OUTPUT
111
- echo "bump_type=${release_type}" >> $GITHUB_OUTPUT
112
- echo "Manual bump to: ${new_version}"
113
- else
114
- echo "released=false" >> $GITHUB_OUTPUT
115
- echo "Failed to determine new version"
116
- exit 1
117
- fi
104
+ # Manual bump computed from the latest tag
105
+ IFS='.' read -r major minor patch <<< "$current_version"
106
+ case "$release_type" in
107
+ major) new_version="$((major + 1)).0.0" ;;
108
+ minor) new_version="${major}.$((minor + 1)).0" ;;
109
+ patch) new_version="${major}.${minor}.$((patch + 1))" ;;
110
+ *) echo "::error::unknown release type ${release_type}"; exit 1 ;;
111
+ esac
112
+ echo "new_version=${new_version}" >> $GITHUB_OUTPUT
113
+ echo "released=true" >> $GITHUB_OUTPUT
114
+ echo "Manual bump to: ${new_version}"
118
115
  fi
119
116
 
120
- - name: Apply version bump
121
- if: steps.determine_version.outputs.released == 'true'
122
- run: |
123
- bump_type="${{ steps.determine_version.outputs.bump_type }}"
124
- python scripts/bump_version.py "$bump_type"
117
+ lint:
118
+ name: Lint
119
+ needs: prepare-release
120
+ if: needs.prepare-release.outputs.released == 'true'
121
+ runs-on: ubuntu-latest
122
+ steps:
123
+ - name: Checkout code
124
+ uses: actions/checkout@v4
125
125
 
126
- - name: Commit and tag version
127
- if: steps.determine_version.outputs.released == 'true'
128
- run: |
129
- new_version="${{ steps.determine_version.outputs.new_version }}"
130
- git config --local user.email "github-actions[bot]@users.noreply.github.com"
131
- git config --local user.name "github-actions[bot]"
132
- git add -A
133
- git diff --cached --quiet && echo "No changes to commit" || \
134
- git commit -m "chore(release): ${new_version} [skip ci]"
135
- git tag -fa "v${new_version}" -m "Release v${new_version}"
136
- git push origin main
137
- git push origin "v${new_version}" --force
126
+ - name: Set up Python
127
+ uses: actions/setup-python@v5
128
+ with:
129
+ python-version: "3.12"
130
+
131
+ - name: Install ruff
132
+ run: pip install ruff
133
+
134
+ - name: Run ruff linter
135
+ run: ruff check tasktree_manager tests
136
+
137
+ - name: Run ruff formatter check
138
+ run: ruff format --check tasktree_manager tests
138
139
 
139
140
  test:
140
141
  name: Test (Python ${{ matrix.python-version }})
@@ -144,12 +145,12 @@ jobs:
144
145
  strategy:
145
146
  fail-fast: false
146
147
  matrix:
147
- python-version: ["3.11", "3.13"]
148
+ # Same matrix as ci.yml: the release gate must not be weaker than CI
149
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
148
150
  steps:
149
151
  - name: Checkout code
150
152
  uses: actions/checkout@v4
151
153
  with:
152
- ref: "v${{ needs.prepare-release.outputs.new_version }}"
153
154
  fetch-depth: 0
154
155
 
155
156
  - name: Set up Python ${{ matrix.python-version }}
@@ -170,9 +171,47 @@ jobs:
170
171
  - name: Run tests
171
172
  run: pytest tests/ -v --tb=short
172
173
 
174
+ tag-release:
175
+ name: Commit and Tag
176
+ needs: [prepare-release, lint, test]
177
+ runs-on: ubuntu-latest
178
+ steps:
179
+ - name: Checkout code
180
+ uses: actions/checkout@v4
181
+ with:
182
+ # The exact commit that was tested. Pushing HEAD:main below fails
183
+ # (non-fast-forward) if main moved in the meantime, instead of
184
+ # tagging code the gate never saw.
185
+ ref: ${{ github.sha }}
186
+ fetch-depth: 0
187
+ token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
188
+
189
+ - name: Set up Python
190
+ uses: actions/setup-python@v5
191
+ with:
192
+ python-version: "3.12"
193
+
194
+ - name: Apply version bump
195
+ # Write the exact version semantic-release computed; deriving it
196
+ # again from pyproject would drift whenever pyproject and the tag
197
+ # disagree (phantom or skipped releases)
198
+ run: python scripts/bump_version.py --set "${{ needs.prepare-release.outputs.new_version }}"
199
+
200
+ - name: Commit and tag version
201
+ run: |
202
+ new_version="${{ needs.prepare-release.outputs.new_version }}"
203
+ git config --local user.email "github-actions[bot]@users.noreply.github.com"
204
+ git config --local user.name "github-actions[bot]"
205
+ git add -A
206
+ git diff --cached --quiet && echo "No changes to commit" || \
207
+ git commit -m "chore(release): ${new_version} [skip ci]"
208
+ git tag -a "v${new_version}" -m "Release v${new_version}"
209
+ git push origin HEAD:main
210
+ git push origin "v${new_version}"
211
+
173
212
  build:
174
213
  name: Build Package
175
- needs: [prepare-release, test]
214
+ needs: [prepare-release, tag-release]
176
215
  runs-on: ubuntu-latest
177
216
  steps:
178
217
  - name: Checkout code
@@ -210,6 +249,9 @@ jobs:
210
249
  publish-pypi:
211
250
  name: Publish to PyPI
212
251
  needs: [prepare-release, build]
252
+ # Drafts and pre-releases never reach PyPI: bump_version.py only emits
253
+ # final X.Y.Z versions, so uploading would publish a real release
254
+ if: ${{ !inputs.draft && !inputs.pre_release }}
213
255
  runs-on: ubuntu-latest
214
256
  environment: pypi
215
257
  permissions:
@@ -231,7 +273,13 @@ jobs:
231
273
 
232
274
  github-release:
233
275
  name: Create GitHub Release
234
- needs: [prepare-release, publish-pypi]
276
+ needs: [prepare-release, build, publish-pypi]
277
+ # Run after a successful build whether PyPI publishing ran or was
278
+ # skipped (draft / pre-release), but never after a failed upload
279
+ if: >-
280
+ always() &&
281
+ needs.build.result == 'success' &&
282
+ (needs.publish-pypi.result == 'success' || needs.publish-pypi.result == 'skipped')
235
283
  runs-on: ubuntu-latest
236
284
  permissions:
237
285
  contents: write
@@ -15,6 +15,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
  - Git hooks support
16
16
  - Custom task metadata
17
17
 
18
+ ## [2.12.1] - 2026-09-03
19
+
20
+ ### Changed
21
+ - Align guides with deletion, config and platform behaviour
22
+ - **release**: Gate tag on lint and tests, derive version from tags
23
+
24
+ ### Fixed
25
+ - **tui**: Install configured keybindings, safe worktree delete, markup-safe rows
26
+ - **config**: Keep env overrides out of config.toml and fail on bad TOML
27
+ - **services**: Harden task deletion, creation and archive safety
28
+
29
+
30
+ ## [2.12.0] - 2026-07-31
31
+
32
+ ### Added
33
+ - **tui**: Rename task display label with R
34
+
35
+
18
36
  ## [2.11.1] - 2026-07-31
19
37
 
20
38
  ### Fixed
@@ -1,10 +1,11 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: tasktree-manager
3
- Version: 2.11.1
3
+ Version: 2.12.1
4
4
  Summary: TUI application for managing worktree-based tasks
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.10
7
7
  Requires-Dist: textual>=7.0.0
8
+ Requires-Dist: tomli>=2.0.1; python_version < '3.11'
8
9
  Provides-Extra: dev
9
10
  Requires-Dist: build>=1.0.0; extra == 'dev'
10
11
  Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
@@ -82,7 +82,7 @@ tasktree-manager repos # available repos in REPOS_DIR
82
82
  tasktree-manager add-repo DIC-1901-argocd-tls infra
83
83
  tasktree-manager status # current task inferred from $PWD; also --json / --oneline / --forge
84
84
  tasktree-manager finish DIC-1901-argocd-tls # safety sweep -> archive diff -> delete; --push / --no-archive / --force
85
- tasktree-manager delete DIC-1901-argocd-tls # low-level delete; refuses on unfinished work; --force overrides
85
+ tasktree-manager delete DIC-1901-argocd-tls # archive diff -> delete; refuses on unfinished work; --force overrides
86
86
  ```
87
87
 
88
88
  Exit code is 0 on success, 1 on failure with the reason on stderr. `delete` and `finish` run
@@ -113,6 +113,7 @@ a Claude Code statusline or shell prompt.
113
113
  | `r` | Refresh | `s` | Sort tasks |
114
114
  | `S` | Group worktrees | `m` | Messages |
115
115
  | `t` | Cycle theme | `Tab` | Next panel |
116
+ | `R` | Rename task | | |
116
117
  | `?` | Show help | `q` | Quit |
117
118
 
118
119
  **Themes:** The default `tasktree` theme pairs a lazygit-classic ANSI palette with layered panels, numbered title bars, green focus borders, and a keycap footer. Five more design-system palettes ship tuned to the same shape — tokyo-night, catppuccin-mocha, gruvbox, dracula, nord — and every other Textual built-in theme works too (`Ctrl+P` to switch); the whole UI is styled with Textual design tokens, so any theme restyles it consistently.
@@ -172,7 +173,9 @@ Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
172
173
  - Python 3.10+ (3.13+ recommended)
173
174
  - Git 2.0+
174
175
  - 256-color terminal
175
- - Unix-like OS (macOS, Linux, WSL2)
176
+ - Unix-like OS (macOS, Linux, WSL2). Opening a folder, editor or Claude session in a
177
+ new terminal tab (`o`, `e`, `c`) drives Ghostty via AppleScript and is macOS-only;
178
+ elsewhere those keys show a notice
176
179
 
177
180
  See [Installation Guide](docs/installation.md) for detailed requirements and setup.
178
181
 
@@ -165,6 +165,9 @@ claude_path = "claude"
165
165
  # task's .claude/settings.local.json so every task session reads and writes one
166
166
  # shared memory pool that persists across tasks.
167
167
  # Set to "" to disable (each task keeps its own throwaway memory).
168
+ # If you relocated Claude Code's config home with $CLAUDE_CONFIG_DIR, point
169
+ # this under that directory too (the per-repo worktree memory below and
170
+ # session resume already honour the variable).
168
171
  # Default: "~/.claude/tasktree-memory"
169
172
  claude_memory_dir = "~/.claude/tasktree-memory"
170
173
 
@@ -223,6 +226,7 @@ help = "?" # Show help modal
223
226
  new_task = "n" # Create new task
224
227
  add_repo = "a" # Add repository to current task
225
228
  delete_task = "d" # Delete/finish task
229
+ rename_task = "R" # Rename task display label (folder/branch unchanged, shift+r)
226
230
  delete_worktree = "D" # Remove single worktree from task (shift+d)
227
231
 
228
232
  # Git operations
@@ -546,7 +550,10 @@ blocklist = [
546
550
  > **Upgrading note:** if your existing `config.toml` already contains a
547
551
  > `blocklist`, it keeps overriding the defaults — the key-material patterns
548
552
  > added in newer versions do NOT apply until you add them (or delete the
549
- > `blocklist` line to inherit the defaults).
553
+ > `blocklist` line to inherit the defaults). Saving the config from the app
554
+ > (for example on a theme switch) no longer writes the default list: the key
555
+ > is only written when you customised it, so the built-in defaults keep
556
+ > applying as they evolve.
550
557
 
551
558
  ### Common Use Cases
552
559
 
@@ -583,6 +590,7 @@ All 22 customizable keybindings with descriptions:
583
590
  | `clone_task` | `y` | Clone current task (same repos, new name) | `clone_task = "ctrl+y"` |
584
591
  | `add_repo` | `a` | Add repository to current task | `add_repo = "ctrl+a"` |
585
592
  | `delete_task` | `d` | Delete/finish current task | `delete_task = "delete"` |
593
+ | `rename_task` | `R` | Rename task display label (alias only) | `rename_task = "ctrl+r"` |
586
594
 
587
595
  ### Git Operations
588
596
 
@@ -685,10 +693,14 @@ Override configuration for a single session using environment variables:
685
693
  | `TASKS_DIR` | `tasks_dir` | `export TASKS_DIR=~/worktrees` |
686
694
  | `TASKTREE_THEME` | `ui.theme` | `export TASKTREE_THEME=nord` |
687
695
  | `TASKTREE_DEFAULT_BRANCH` | `git.default_base_branch` | `export TASKTREE_DEFAULT_BRANCH=develop` |
688
- | `EDITOR` | `tools.editor` | `export EDITOR=nvim` |
689
- | `SHELL` | `tools.shell` | `export SHELL=/bin/zsh` |
696
+ | `EDITOR` | `tools.editor` (fallback: used only when `tools.editor` is empty) | `export EDITOR=nvim` |
690
697
  | `XDG_CONFIG_HOME` | (config location) | `export XDG_CONFIG_HOME=~/.cfg` |
691
698
 
699
+ Environment overrides apply to the running session only: saving the config (for
700
+ example by switching themes with `t`) writes the config-file values back, never the
701
+ overridden ones. Empty values (`TASKS_DIR=`) are ignored. A config file that fails to
702
+ parse is an error at startup, not an empty config.
703
+
692
704
  ### Usage Examples
693
705
 
694
706
  **Temporary different repos directory:**
@@ -256,7 +256,10 @@ Launch tasktree-manager:
256
256
  tasktree-manager
257
257
  ```
258
258
 
259
- On first run, you'll see a configuration wizard:
259
+ On first run, you'll see a configuration wizard (it appears when the repositories
260
+ directory, `~/repos` by default, does not exist yet; with `~/repos` already present
261
+ the defaults apply and no `config.toml` is written until you change a setting, such
262
+ as the theme):
260
263
 
261
264
  #### Configuration Wizard
262
265
 
@@ -692,34 +692,33 @@ wsl --shutdown
692
692
 
693
693
  ## Git-Specific Issues
694
694
 
695
- ### "Branch already exists"
695
+ ### Task branch already exists
696
696
 
697
- **Error:**
697
+ Creating a task whose branch already exists in a repo (left behind by an earlier
698
+ task, or created by hand) is not an error: the worktree is created **on the existing
699
+ branch as-is**, keeping every commit on it. The base branch you enter is ignored for
700
+ that repo.
701
+
702
+ **Error you can still hit:**
698
703
  ```
699
- fatal: A branch named 'FEAT-123' already exists
704
+ fatal: 'FEAT-123' is already checked out at '/Users/you/repos/repo-name'
700
705
  ```
701
706
 
702
- **Cause:** Branch exists in repository from previous task.
707
+ **Cause:** the branch is checked out in the main repository (or another worktree);
708
+ git allows a branch in only one worktree at a time.
703
709
 
704
- **Solutions:**
705
-
706
- 1. **Use different task name:**
707
- ```bash
708
- # Instead of FEAT-123, use FEAT-123-v2
709
- ```
710
-
711
- 2. **Delete old branch:**
712
- ```bash
713
- cd ~/repos/repo-name
714
- git branch -d FEAT-123 # If merged
715
- git branch -D FEAT-123 # Force delete
716
- ```
710
+ **Solution:**
711
+ ```bash
712
+ cd ~/repos/repo-name
713
+ git switch main # move the main checkout off the task branch
714
+ # then retry the task creation / add-repo
715
+ ```
717
716
 
718
- 3. **Use existing branch:**
719
- ```bash
720
- # When creating task, use existing branch as base
721
- Base branch: FEAT-123
722
- ```
717
+ To start the task fresh from the base branch instead, delete the old branch first:
718
+ ```bash
719
+ git branch -d FEAT-123 # If merged
720
+ git branch -D FEAT-123 # Force delete (loses its unmerged commits)
721
+ ```
723
722
 
724
723
  ### "Detached HEAD state"
725
724
 
@@ -921,10 +920,11 @@ Yes! Each task creates its own worktree and branch:
921
920
  ### What happens to branches when I delete a task?
922
921
 
923
922
  - Worktrees are removed from `TASKS_DIR`
924
- - Local branches remain in main repository
923
+ - The local task branch is deleted from the main repository (`git branch -D`);
924
+ the remaining diff is archived under `archive_dir` first
925
925
  - Remote branches remain on GitHub/GitLab (if pushed)
926
926
 
927
- **Manual cleanup:**
927
+ **Manual cleanup (only for branches created outside tasktree):**
928
928
  ```bash
929
929
  cd ~/repos/repo-name
930
930
  git branch -d FEAT-123 # Delete local branch
@@ -300,6 +300,7 @@ what is displayed. Press `?` for the full reference at any time.
300
300
  | `y` | Clone task | Create a new task pre-populated with the current task's repos |
301
301
  | `a` | Add repository | Add additional repositories to the current task |
302
302
  | `d` | Delete/finish | Delete current task and remove worktrees |
303
+ | `R` | Rename task | Set a display alias shown in the task list (folder/branch unchanged; empty input reverts) |
303
304
 
304
305
  **Task creation workflow:**
305
306
  1. Press `n`
@@ -309,10 +310,10 @@ what is displayed. Press `?` for the full reference at any time.
309
310
  5. Select repos with `Space` (can select multiple)
310
311
  6. Tab to "Create" button, press `Enter`
311
312
 
312
- Task names may contain letters, numbers, `.`, `_`, `/` and `-`. Because the
313
- name becomes a directory under your tasks dir (and a git branch), names with
314
- `.`/`..` path segments, empty segments, or a leading `-` are rejected; base
315
- branch names must not start with `-` either.
313
+ Task names may contain letters, numbers, `.`, `_` and `-`. The name becomes a
314
+ single directory under your tasks dir (and a git branch), so `/`, `.`, `..` and a
315
+ leading `-` are rejected; base branch names must be plain branch names too (no
316
+ leading `-`/`+`, no `:` refspecs, none of the characters git forbids in refs).
316
317
 
317
318
  **Clone task workflow:**
318
319
  Useful when you're spinning up several similar tasks that share the same set of repos.
@@ -647,6 +648,14 @@ d (delete task)
647
648
  - **Uncommitted changes**: Commit or stash first
648
649
  - **Unpushed commits**: Push or force delete
649
650
  - **Unmerged branches**: Merge PR or force delete
651
+ - **Wrong branch checked out**: a worktree that is detached or on another branch
652
+ is reported as an error — switch back to the task branch (the check and the
653
+ archive follow the task branch, because that is what deletion removes)
654
+
655
+ **Delete a single worktree:**
656
+ 1. Highlight the worktree in the right panel
657
+ 2. Press `D` — the same safety check, dialog (Push / Open Lazygit / Force Delete)
658
+ and diff archive apply, scoped to that one repo
650
659
 
651
660
  **Safety features:**
652
661
  - tasktree-manager checks for uncommitted changes
@@ -663,7 +672,9 @@ d (delete task)
663
672
  - ✅ Worktrees removed from `TASKS_DIR`
664
673
  - ✅ Branches remain on remote (if pushed)
665
674
  - ✅ Original repos in `REPOS_DIR` untouched
666
- - ❌ Local branches remain in original repos (cleanup manually if needed)
675
+ - ❌ The local task branch is deleted from each repo (`git branch -D`) — unpushed
676
+ commits survive only in the archive patch, which is why the safety check blocks
677
+ unpushed work and why a worktree must be on its task branch to count as safe
667
678
 
668
679
  **From scripts:** `tasktree-manager finish <task>` runs the same guided flow headlessly
669
680
  (safety sweep → optional `--push` → archive → delete), and `tasktree-manager status
@@ -751,9 +762,9 @@ npm i -g hunkdiff
751
762
 
752
763
  **Direct editor integration:**
753
764
  1. Highlight task or worktree
754
- 2. Press `e` to open your configured editor
755
- 3. tasktree-manager suspends while editor runs
756
- 4. Selection is preserved when you return
765
+ 2. Press `e` to open your configured editor in a new Ghostty tab (`$EDITOR .`)
766
+ 3. tasktree-manager keeps running alongside the editor (macOS + Ghostty only, see
767
+ Limitations)
757
768
 
758
769
  **Configure your editor** in `~/.config/tasktree-manager/config.toml`:
759
770
  ```toml
@@ -69,17 +69,20 @@ description = "Run tasktree-manager in dev mode with textual console"
69
69
  run = "textual run --dev tasktree_manager.app:TaskTreeApp"
70
70
 
71
71
  [tasks."release:check"]
72
- description = "Dry-run to preview what a release would do"
72
+ description = "Dry-run to preview what a patch release would do"
73
73
  run = "python scripts/bump_version.py patch --dry-run"
74
74
 
75
+ # Releases are cut by the Release workflow (tests gate the tag). Bumping
76
+ # pyproject locally desyncs it from the latest tag and makes the workflow
77
+ # skip or double-bump, so these tasks dispatch the workflow instead.
75
78
  [tasks."release:patch"]
76
- description = "Bump patch version locally"
77
- run = "python scripts/bump_version.py patch"
79
+ description = "Trigger a patch release via GitHub Actions"
80
+ run = "gh workflow run release.yml -f release_type=patch"
78
81
 
79
82
  [tasks."release:minor"]
80
- description = "Bump minor version locally"
81
- run = "python scripts/bump_version.py minor"
83
+ description = "Trigger a minor release via GitHub Actions"
84
+ run = "gh workflow run release.yml -f release_type=minor"
82
85
 
83
86
  [tasks."release:major"]
84
- description = "Bump major version locally"
85
- run = "python scripts/bump_version.py major"
87
+ description = "Trigger a major release via GitHub Actions"
88
+ run = "gh workflow run release.yml -f release_type=major"
@@ -9,6 +9,7 @@ description = "TUI application for managing worktree-based tasks"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
11
11
  "textual>=7.0.0",
12
+ "tomli>=2.0.1; python_version < '3.11'",
12
13
  ]
13
14
 
14
15
  [project.optional-dependencies]
@@ -70,7 +71,7 @@ version-file = "tasktree_manager/_version.py"
70
71
  # Semantic Release Configuration
71
72
  [tool.semantic_release]
72
73
  version_toml = ["pyproject.toml:tool.semantic_release.version"]
73
- version = "2.11.1"
74
+ version = "2.12.1"
74
75
  tag_format = "v{version}"
75
76
  branch = "main"
76
77
  commit_message = "chore(release): {version} [skip ci]"