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.
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.github/workflows/release.yml +112 -64
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/CHANGELOG.md +18 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/PKG-INFO +3 -2
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/README.md +5 -2
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/docs/configuration.md +15 -3
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/docs/installation.md +4 -1
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/docs/troubleshooting.md +24 -24
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/docs/user-guide.md +19 -8
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/mise.toml +10 -7
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/pyproject.toml +2 -1
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/scripts/bump_version.py +38 -8
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/_version.py +2 -2
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/app.py +346 -83
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/cli.py +11 -2
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/commands.py +5 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/claude_hooks.py +47 -19
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/config.py +99 -72
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/forge.py +12 -10
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/git_ops.py +208 -66
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/models.py +18 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/task_manager.py +186 -76
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/create_modal.py +75 -7
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/setup_modal.py +27 -21
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/task_list.py +23 -23
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/worktree_list.py +44 -28
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_app.py +69 -1
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_claude_hooks.py +5 -1
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_cli.py +10 -4
- tasktree_manager-2.12.1/tests/test_medium_fixes.py +484 -0
- tasktree_manager-2.12.1/tests/test_safety_fixes.py +338 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_task_manager.py +118 -18
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/SKILL.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/assets/README.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/assets/dashboard_app.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/assets/data_viewer.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/assets/todo_app.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/assets/worker_demo.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/references/layouts.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/references/official-guides-index.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/references/styling.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.claude/skills/textual-tui/references/widgets.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.github/templates/CHANGELOG.md.j2 +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.github/workflows/ci.yml +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/.gitignore +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/CONTRIBUTING.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/LICENSE +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/OPENSPEC.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/ROADMAP.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/docs/claude-code-integration-spec.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/__init__.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/__init__.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/services/agent_sessions.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/themes.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/__init__.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/app_header.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/messages_panel.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tasktree_manager/widgets/status_panel.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/__init__.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/conftest.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_agent_sessions.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_commands.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_config.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_forge.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_git_ops.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_integration_git.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_memory.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_messages_panel.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_modals.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_setup_modal.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.1}/tests/test_status_panel.py +0 -0
- {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
|
|
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.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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: tasktree-manager
|
|
3
|
-
Version: 2.
|
|
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 #
|
|
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`
|
|
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
|
-
###
|
|
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
|
|
@@ -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, `.`, `_
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
- ❌
|
|
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
|
|
756
|
-
|
|
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 = "
|
|
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.12.1"
|
|
74
75
|
tag_format = "v{version}"
|
|
75
76
|
branch = "main"
|
|
76
77
|
commit_message = "chore(release): {version} [skip ci]"
|