tasktree-manager 2.12.0__tar.gz → 2.13.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 (73) hide show
  1. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.github/workflows/release.yml +112 -64
  2. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/CHANGELOG.md +18 -0
  3. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/PKG-INFO +3 -2
  4. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/README.md +5 -2
  5. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/docs/configuration.md +25 -3
  6. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/docs/installation.md +4 -1
  7. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/docs/troubleshooting.md +24 -24
  8. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/docs/user-guide.md +35 -8
  9. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/mise.toml +10 -7
  10. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/pyproject.toml +2 -1
  11. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/scripts/bump_version.py +38 -8
  12. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/_version.py +2 -2
  13. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/app.py +371 -83
  14. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/cli.py +10 -2
  15. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/claude_hooks.py +95 -24
  16. tasktree_manager-2.13.0/tasktree_manager/services/claude_sessions.py +236 -0
  17. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/config.py +106 -72
  18. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/forge.py +12 -10
  19. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/git_ops.py +208 -66
  20. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/task_manager.py +170 -76
  21. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/create_modal.py +22 -6
  22. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/setup_modal.py +27 -21
  23. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/status_panel.py +67 -0
  24. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/task_list.py +21 -21
  25. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/worktree_list.py +44 -28
  26. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_app.py +65 -1
  27. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_claude_hooks.py +77 -1
  28. tasktree_manager-2.13.0/tests/test_claude_sessions.py +248 -0
  29. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_cli.py +10 -4
  30. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_config.py +31 -0
  31. tasktree_manager-2.13.0/tests/test_medium_fixes.py +484 -0
  32. tasktree_manager-2.13.0/tests/test_safety_fixes.py +338 -0
  33. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_status_panel.py +96 -0
  34. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_task_manager.py +76 -18
  35. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/SKILL.md +0 -0
  36. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/assets/README.md +0 -0
  37. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/assets/dashboard_app.py +0 -0
  38. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/assets/data_viewer.py +0 -0
  39. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/assets/todo_app.py +0 -0
  40. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/assets/worker_demo.py +0 -0
  41. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/references/layouts.md +0 -0
  42. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/references/official-guides-index.md +0 -0
  43. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/references/styling.md +0 -0
  44. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/references/widgets.md +0 -0
  45. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.github/templates/CHANGELOG.md.j2 +0 -0
  46. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.github/workflows/ci.yml +0 -0
  47. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.gitignore +0 -0
  48. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/CONTRIBUTING.md +0 -0
  49. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/LICENSE +0 -0
  50. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/OPENSPEC.md +0 -0
  51. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/ROADMAP.md +0 -0
  52. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/docs/claude-code-integration-spec.md +0 -0
  53. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/__init__.py +0 -0
  54. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/commands.py +0 -0
  55. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/__init__.py +0 -0
  56. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/agent_sessions.py +0 -0
  57. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/models.py +0 -0
  58. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/themes.py +0 -0
  59. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/__init__.py +0 -0
  60. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/app_header.py +0 -0
  61. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/messages_panel.py +0 -0
  62. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/__init__.py +0 -0
  63. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/conftest.py +0 -0
  64. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_agent_sessions.py +0 -0
  65. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_commands.py +0 -0
  66. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_forge.py +0 -0
  67. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_git_ops.py +0 -0
  68. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_integration_git.py +0 -0
  69. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_memory.py +0 -0
  70. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_messages_panel.py +0 -0
  71. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_modals.py +0 -0
  72. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_setup_modal.py +0 -0
  73. {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/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.13.0] - 2026-09-11
19
+
20
+ ### Added
21
+ - **tui**: Show Claude session recap in the Info panel
22
+
23
+
24
+ ## [2.12.1] - 2026-09-03
25
+
26
+ ### Changed
27
+ - Align guides with deletion, config and platform behaviour
28
+ - **release**: Gate tag on lint and tests, derive version from tags
29
+
30
+ ### Fixed
31
+ - **tui**: Install configured keybindings, safe worktree delete, markup-safe rows
32
+ - **config**: Keep env overrides out of config.toml and fail on bad TOML
33
+ - **services**: Harden task deletion, creation and archive safety
34
+
35
+
18
36
  ## [2.12.0] - 2026-07-31
19
37
 
20
38
  ### Added
@@ -1,10 +1,11 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: tasktree-manager
3
- Version: 2.12.0
3
+ Version: 2.13.0
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'
@@ -20,6 +20,7 @@
20
20
  - 🔧 **Flexible Configuration** - TOML config file, custom keybindings, environment variables
21
21
  - 🤖 **Headless CLI** - Create, list, inspect, finish, and delete tasks from scripts and AI agents without opening the TUI
22
22
  - 🛰️ **Agent Dashboard** - Live Claude Code session state per worktree (from `claude agents`), plus one-key background-agent dispatch — the multi-repo agent view Claude Code lacks natively
23
+ - ✻ **Session Recap** - The Info panel shows each task's latest Claude Code session: title, last turn duration, and Claude's own idle recap of what was done and what comes next — read straight from the transcript, no CLI call
23
24
  - 🔀 **Forge-Aware Safety** - MR/PR and CI state via `glab`/`gh`; squash- and rebase-merged branches count as merged instead of blocking deletion forever
24
25
  - 📦 **Finish Flow with Archives** - Ending a task saves its combined diff (committed-but-unmerged + uncommitted) to an archive before deleting
25
26
 
@@ -82,7 +83,7 @@ tasktree-manager repos # available repos in REPOS_DIR
82
83
  tasktree-manager add-repo DIC-1901-argocd-tls infra
83
84
  tasktree-manager status # current task inferred from $PWD; also --json / --oneline / --forge
84
85
  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
86
+ tasktree-manager delete DIC-1901-argocd-tls # archive diff -> delete; refuses on unfinished work; --force overrides
86
87
  ```
87
88
 
88
89
  Exit code is 0 on success, 1 on failure with the reason on stderr. `delete` and `finish` run
@@ -173,7 +174,9 @@ Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
173
174
  - Python 3.10+ (3.13+ recommended)
174
175
  - Git 2.0+
175
176
  - 256-color terminal
176
- - Unix-like OS (macOS, Linux, WSL2)
177
+ - Unix-like OS (macOS, Linux, WSL2). Opening a folder, editor or Claude session in a
178
+ new terminal tab (`o`, `e`, `c`) drives Ghostty via AppleScript and is macOS-only;
179
+ elsewhere those keys show a notice
177
180
 
178
181
  See [Installation Guide](docs/installation.md) for detailed requirements and setup.
179
182
 
@@ -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
 
@@ -177,9 +180,21 @@ claude_memory_dir = "~/.claude/tasktree-memory"
177
180
  # worktree of a repo survives worktree deletion and is shared with future
178
181
  # worktrees of that repo and with sessions in the main checkout. The settings
179
182
  # file is added to the repo's .git/info/exclude so worktrees stay clean.
183
+ # The directory name follows the current Claude CLI rule (every character
184
+ # outside [A-Za-z0-9] becomes "-"); memory saved by older tasktree versions
185
+ # under the previous name (which kept "_") is moved once, automatically.
180
186
  # Default: true
181
187
  claude_repo_memory = true
182
188
 
189
+ # Claude session recap in the Info panel. When a task is highlighted, the
190
+ # bottom of the panel shows its latest Claude Code session: title, last turn
191
+ # duration ("Baked for 45s · done 5:27 PM"), and the idle recap Claude writes
192
+ # a few minutes after a turn ("※ recap: ..."), or the last prompt when no
193
+ # recap exists yet. Read from the session transcript under ~/.claude/projects
194
+ # (honours CLAUDE_CONFIG_DIR); no CLI call.
195
+ # Default: true
196
+ claude_recap = true
197
+
183
198
  # Path to glab executable (GitLab CLI, used for MR/CI status)
184
199
  # Default: "glab" (searches PATH)
185
200
  glab_path = "glab"
@@ -547,7 +562,10 @@ blocklist = [
547
562
  > **Upgrading note:** if your existing `config.toml` already contains a
548
563
  > `blocklist`, it keeps overriding the defaults — the key-material patterns
549
564
  > added in newer versions do NOT apply until you add them (or delete the
550
- > `blocklist` line to inherit the defaults).
565
+ > `blocklist` line to inherit the defaults). Saving the config from the app
566
+ > (for example on a theme switch) no longer writes the default list: the key
567
+ > is only written when you customised it, so the built-in defaults keep
568
+ > applying as they evolve.
551
569
 
552
570
  ### Common Use Cases
553
571
 
@@ -687,10 +705,14 @@ Override configuration for a single session using environment variables:
687
705
  | `TASKS_DIR` | `tasks_dir` | `export TASKS_DIR=~/worktrees` |
688
706
  | `TASKTREE_THEME` | `ui.theme` | `export TASKTREE_THEME=nord` |
689
707
  | `TASKTREE_DEFAULT_BRANCH` | `git.default_base_branch` | `export TASKTREE_DEFAULT_BRANCH=develop` |
690
- | `EDITOR` | `tools.editor` | `export EDITOR=nvim` |
691
- | `SHELL` | `tools.shell` | `export SHELL=/bin/zsh` |
708
+ | `EDITOR` | `tools.editor` (fallback: used only when `tools.editor` is empty) | `export EDITOR=nvim` |
692
709
  | `XDG_CONFIG_HOME` | (config location) | `export XDG_CONFIG_HOME=~/.cfg` |
693
710
 
711
+ Environment overrides apply to the running session only: saving the config (for
712
+ example by switching themes with `t`) writes the config-file values back, never the
713
+ overridden ones. Empty values (`TASKS_DIR=`) are ignored. A config file that fails to
714
+ parse is an error at startup, not an empty config.
715
+
694
716
  ### Usage Examples
695
717
 
696
718
  **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
@@ -219,6 +219,23 @@ Shows detailed git status for the selected worktree:
219
219
  - **Sync status**: Ahead/behind counts
220
220
  - **File changes**: Modified, staged, and untracked files with git status codes
221
221
 
222
+ While the task list is focused, the panel shows the task summary instead (every
223
+ repo with its changes or `clean`), followed by a **Claude session recap** when the
224
+ task has a Claude Code session:
225
+
226
+ ```
227
+ ─ claude ─────────────────
228
+ access-m2
229
+ ✻ Baked for 45s · done 5:27 PM · busy
230
+ ※ recap: Fulfilling ACCESS2-7180, read-only M2 access: gitops RBAC and
231
+ terraform edits sit unstaged. Next, decide whether to hand-create the group.
232
+ ```
233
+
234
+ - **Title**: the session name (`/rename`), else Claude's own title, else the session slug
235
+ - **Turn line**: duration of the last turn and when it finished; `busy`/`idle` mirrors the task's Claude status indicator
236
+ - **Recap**: the summary Claude writes a few minutes after a turn when the session idles. Until one exists, the dim `last prompt:` line shows what was asked last
237
+ - Nothing is shown for tasks without a session. The block refreshes as you move the cursor and every 5 seconds while a transcript changes. Disable with `claude_recap = false` under `[tools]` in `config.toml`
238
+
222
239
  **Git status codes:**
223
240
  - `M` - Modified
224
241
  - `A` - Added (staged)
@@ -310,10 +327,10 @@ what is displayed. Press `?` for the full reference at any time.
310
327
  5. Select repos with `Space` (can select multiple)
311
328
  6. Tab to "Create" button, press `Enter`
312
329
 
313
- Task names may contain letters, numbers, `.`, `_`, `/` and `-`. Because the
314
- name becomes a directory under your tasks dir (and a git branch), names with
315
- `.`/`..` path segments, empty segments, or a leading `-` are rejected; base
316
- branch names must not start with `-` either.
330
+ Task names may contain letters, numbers, `.`, `_` and `-`. The name becomes a
331
+ single directory under your tasks dir (and a git branch), so `/`, `.`, `..` and a
332
+ leading `-` are rejected; base branch names must be plain branch names too (no
333
+ leading `-`/`+`, no `:` refspecs, none of the characters git forbids in refs).
317
334
 
318
335
  **Clone task workflow:**
319
336
  Useful when you're spinning up several similar tasks that share the same set of repos.
@@ -648,6 +665,14 @@ d (delete task)
648
665
  - **Uncommitted changes**: Commit or stash first
649
666
  - **Unpushed commits**: Push or force delete
650
667
  - **Unmerged branches**: Merge PR or force delete
668
+ - **Wrong branch checked out**: a worktree that is detached or on another branch
669
+ is reported as an error — switch back to the task branch (the check and the
670
+ archive follow the task branch, because that is what deletion removes)
671
+
672
+ **Delete a single worktree:**
673
+ 1. Highlight the worktree in the right panel
674
+ 2. Press `D` — the same safety check, dialog (Push / Open Lazygit / Force Delete)
675
+ and diff archive apply, scoped to that one repo
651
676
 
652
677
  **Safety features:**
653
678
  - tasktree-manager checks for uncommitted changes
@@ -664,7 +689,9 @@ d (delete task)
664
689
  - ✅ Worktrees removed from `TASKS_DIR`
665
690
  - ✅ Branches remain on remote (if pushed)
666
691
  - ✅ Original repos in `REPOS_DIR` untouched
667
- - ❌ Local branches remain in original repos (cleanup manually if needed)
692
+ - ❌ The local task branch is deleted from each repo (`git branch -D`) — unpushed
693
+ commits survive only in the archive patch, which is why the safety check blocks
694
+ unpushed work and why a worktree must be on its task branch to count as safe
668
695
 
669
696
  **From scripts:** `tasktree-manager finish <task>` runs the same guided flow headlessly
670
697
  (safety sweep → optional `--push` → archive → delete), and `tasktree-manager status
@@ -752,9 +779,9 @@ npm i -g hunkdiff
752
779
 
753
780
  **Direct editor integration:**
754
781
  1. Highlight task or worktree
755
- 2. Press `e` to open your configured editor
756
- 3. tasktree-manager suspends while editor runs
757
- 4. Selection is preserved when you return
782
+ 2. Press `e` to open your configured editor in a new Ghostty tab (`$EDITOR .`)
783
+ 3. tasktree-manager keeps running alongside the editor (macOS + Ghostty only, see
784
+ Limitations)
758
785
 
759
786
  **Configure your editor** in `~/.config/tasktree-manager/config.toml`:
760
787
  ```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.12.0"
74
+ version = "2.13.0"
74
75
  tag_format = "v{version}"
75
76
  branch = "main"
76
77
  commit_message = "chore(release): {version} [skip ci]"