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.
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.github/workflows/release.yml +112 -64
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/CHANGELOG.md +18 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/PKG-INFO +3 -2
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/README.md +5 -2
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/docs/configuration.md +25 -3
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/docs/installation.md +4 -1
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/docs/troubleshooting.md +24 -24
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/docs/user-guide.md +35 -8
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/mise.toml +10 -7
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/pyproject.toml +2 -1
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/scripts/bump_version.py +38 -8
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/_version.py +2 -2
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/app.py +371 -83
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/cli.py +10 -2
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/claude_hooks.py +95 -24
- tasktree_manager-2.13.0/tasktree_manager/services/claude_sessions.py +236 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/config.py +106 -72
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/forge.py +12 -10
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/git_ops.py +208 -66
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/task_manager.py +170 -76
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/create_modal.py +22 -6
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/setup_modal.py +27 -21
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/status_panel.py +67 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/task_list.py +21 -21
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/worktree_list.py +44 -28
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_app.py +65 -1
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_claude_hooks.py +77 -1
- tasktree_manager-2.13.0/tests/test_claude_sessions.py +248 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_cli.py +10 -4
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_config.py +31 -0
- tasktree_manager-2.13.0/tests/test_medium_fixes.py +484 -0
- tasktree_manager-2.13.0/tests/test_safety_fixes.py +338 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_status_panel.py +96 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_task_manager.py +76 -18
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/SKILL.md +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/assets/README.md +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/assets/dashboard_app.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/assets/data_viewer.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/assets/todo_app.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/assets/worker_demo.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/references/layouts.md +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/references/official-guides-index.md +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/references/styling.md +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.claude/skills/textual-tui/references/widgets.md +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.github/templates/CHANGELOG.md.j2 +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.github/workflows/ci.yml +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/.gitignore +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/CONTRIBUTING.md +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/LICENSE +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/OPENSPEC.md +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/ROADMAP.md +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/docs/claude-code-integration-spec.md +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/__init__.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/commands.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/__init__.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/agent_sessions.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/services/models.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/themes.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/__init__.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/app_header.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tasktree_manager/widgets/messages_panel.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/__init__.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/conftest.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_agent_sessions.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_commands.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_forge.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_git_ops.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_integration_git.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_memory.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_messages_panel.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_modals.py +0 -0
- {tasktree_manager-2.12.0 → tasktree_manager-2.13.0}/tests/test_setup_modal.py +0 -0
- {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
|
|
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
|
-
|
|
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.
|
|
73
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
66
74
|
run: |
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
81
|
-
# When no qualifying commits exist
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
echo "
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: tasktree-manager
|
|
3
|
-
Version: 2.
|
|
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 #
|
|
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`
|
|
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
|
-
###
|
|
695
|
+
### Task branch already exists
|
|
696
696
|
|
|
697
|
-
|
|
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:
|
|
704
|
+
fatal: 'FEAT-123' is already checked out at '/Users/you/repos/repo-name'
|
|
700
705
|
```
|
|
701
706
|
|
|
702
|
-
**Cause:**
|
|
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
|
-
**
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
-
|
|
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, `.`, `_
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
- ❌
|
|
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
|
|
757
|
-
|
|
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 = "
|
|
77
|
-
run = "
|
|
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 = "
|
|
81
|
-
run = "
|
|
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 = "
|
|
85
|
-
run = "
|
|
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.
|
|
74
|
+
version = "2.13.0"
|
|
74
75
|
tag_format = "v{version}"
|
|
75
76
|
branch = "main"
|
|
76
77
|
commit_message = "chore(release): {version} [skip ci]"
|