tasktree 0.0.2__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 (38) hide show
  1. tasktree-0.0.2/.github/workflows/release.yml +62 -0
  2. tasktree-0.0.2/.github/workflows/test.yml +61 -0
  3. tasktree-0.0.2/.gitignore +161 -0
  4. tasktree-0.0.2/CLAUDE.md +69 -0
  5. tasktree-0.0.2/PKG-INFO +375 -0
  6. tasktree-0.0.2/README.md +361 -0
  7. tasktree-0.0.2/example/source.txt +1 -0
  8. tasktree-0.0.2/example/tasktree.yaml +15 -0
  9. tasktree-0.0.2/pyproject.toml +29 -0
  10. tasktree-0.0.2/src/__init__.py +0 -0
  11. tasktree-0.0.2/src/tasktree/__init__.py +42 -0
  12. tasktree-0.0.2/src/tasktree/cli.py +506 -0
  13. tasktree-0.0.2/src/tasktree/executor.py +378 -0
  14. tasktree-0.0.2/src/tasktree/graph.py +139 -0
  15. tasktree-0.0.2/src/tasktree/hasher.py +74 -0
  16. tasktree-0.0.2/src/tasktree/parser.py +300 -0
  17. tasktree-0.0.2/src/tasktree/state.py +119 -0
  18. tasktree-0.0.2/src/tasktree/tasks.py +8 -0
  19. tasktree-0.0.2/src/tasktree/types.py +130 -0
  20. tasktree-0.0.2/tasktree.yaml +48 -0
  21. tasktree-0.0.2/tests/integration/test_clean_state.py +162 -0
  22. tasktree-0.0.2/tests/integration/test_cli_options.py +353 -0
  23. tasktree-0.0.2/tests/integration/test_dependency_execution.py +209 -0
  24. tasktree-0.0.2/tests/integration/test_end_to_end.py +129 -0
  25. tasktree-0.0.2/tests/integration/test_input_detection.py +180 -0
  26. tasktree-0.0.2/tests/integration/test_missing_outputs.py +114 -0
  27. tasktree-0.0.2/tests/integration/test_nested_imports.py +246 -0
  28. tasktree-0.0.2/tests/integration/test_state_persistence.py +177 -0
  29. tasktree-0.0.2/tests/integration/test_working_directory.py +105 -0
  30. tasktree-0.0.2/tests/unit/test_cli.py +109 -0
  31. tasktree-0.0.2/tests/unit/test_executor.py +583 -0
  32. tasktree-0.0.2/tests/unit/test_graph.py +195 -0
  33. tasktree-0.0.2/tests/unit/test_hasher.py +76 -0
  34. tasktree-0.0.2/tests/unit/test_parser.py +884 -0
  35. tasktree-0.0.2/tests/unit/test_state.py +114 -0
  36. tasktree-0.0.2/tests/unit/test_tasks.py +18 -0
  37. tasktree-0.0.2/tests/unit/test_types.py +278 -0
  38. tasktree-0.0.2/uv.lock +226 -0
@@ -0,0 +1,62 @@
1
+ name: Release to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*.*.*' # Triggers on tags like v1.0.0, v1.2.3, etc.
7
+
8
+ jobs:
9
+ release:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write # For creating GitHub release
13
+ id-token: write # For trusted publishing to PyPI
14
+
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0 # Full history for proper version detection
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: '3.11'
25
+
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@v4
28
+
29
+ - name: Extract version from tag
30
+ id: get_version
31
+ run: |
32
+ VERSION=${GITHUB_REF#refs/tags/v}
33
+ echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
34
+ echo "Releasing version $VERSION"
35
+
36
+ - name: Update version in pyproject.toml
37
+ run: |
38
+ sed -i 's/version = ".*"/version = "${{ steps.get_version.outputs.VERSION }}"/' pyproject.toml
39
+ cat pyproject.toml | grep version
40
+
41
+ - name: Build package
42
+ run: uv build
43
+
44
+ - name: Verify wheel contents
45
+ run: |
46
+ unzip -l dist/*.whl
47
+ ls -lh dist/
48
+
49
+ - name: Create GitHub Release
50
+ uses: softprops/action-gh-release@v2
51
+ with:
52
+ files: dist/*
53
+ generate_release_notes: true
54
+ draft: false
55
+ prerelease: false
56
+
57
+ - name: Publish to PyPI
58
+ uses: pypa/gh-action-pypi-publish@release/v1
59
+ with:
60
+ # Uses trusted publisher authentication (no API token needed)
61
+ # Configure at https://pypi.org/manage/account/publishing/
62
+ packages-dir: dist/
@@ -0,0 +1,61 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, develop ]
6
+ pull_request:
7
+ branches: [ main, develop ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, macos-latest, windows-latest]
16
+ python-version: ['3.11', '3.12']
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@v4
28
+
29
+ - name: Install dependencies
30
+ run: |
31
+ uv pip install --system -e ".[dev]"
32
+
33
+ - name: Run tests
34
+ run: |
35
+ python -m pytest tests/ -v --tb=short
36
+
37
+ - name: Test CLI commands
38
+ run: |
39
+ python -m tasktree.cli --help
40
+ python -m tasktree.cli --list || true
41
+
42
+ lint:
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+
47
+ - name: Set up Python
48
+ uses: actions/setup-python@v5
49
+ with:
50
+ python-version: '3.12'
51
+
52
+ - name: Install uv
53
+ uses: astral-sh/setup-uv@v4
54
+
55
+ - name: Install dependencies
56
+ run: |
57
+ uv pip install --system -e ".[dev]"
58
+
59
+ - name: Check code can be imported
60
+ run: |
61
+ python -c "from tasktree import Executor, Recipe, Task; print('Import successful')"
@@ -0,0 +1,161 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ *.py,cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+ cover/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Django stuff:
57
+ *.log
58
+ local_settings.py
59
+ db.sqlite3
60
+ db.sqlite3-journal
61
+
62
+ # Flask stuff:
63
+ instance/
64
+ .webassets-cache
65
+
66
+ # Scrapy stuff:
67
+ .scrapy
68
+
69
+ # Sphinx documentation
70
+ docs/_build/
71
+
72
+ # PyBuilder
73
+ .pybuilder/
74
+ target/
75
+
76
+ # Jupyter Notebook
77
+ .ipynb_checkpoints
78
+
79
+ # IPython
80
+ profile_default/
81
+ ipython_config.py
82
+
83
+ # pyenv
84
+ .python-version
85
+
86
+ # pipenv
87
+ Pipfile.lock
88
+
89
+ # PEP 582
90
+ __pypackages__/
91
+
92
+ # Celery stuff
93
+ celerybeat-schedule
94
+ celerybeat.pid
95
+
96
+ # SageMath parsed files
97
+ *.sage.py
98
+
99
+ # Environments
100
+ .env
101
+ .venv
102
+ env/
103
+ venv/
104
+ ENV/
105
+ env.bak/
106
+ venv.bak/
107
+
108
+ # Spyder project settings
109
+ .spyderproject
110
+ .spyproject
111
+
112
+ # Rope project settings
113
+ .ropeproject
114
+
115
+ # mkdocs documentation
116
+ /site
117
+
118
+ # mypy
119
+ .mypy_cache/
120
+ .dmypy.json
121
+ dmypy.json
122
+
123
+ # Pyre type checker
124
+ .pyre/
125
+
126
+ # pytype static type analyzer
127
+ .pytype/
128
+
129
+ # Cython debug symbols
130
+ cython_debug/
131
+
132
+ # IDEs
133
+ .idea/
134
+ .vscode/
135
+ *.swp
136
+ *.swo
137
+ *~
138
+ .project
139
+ .pydevproject
140
+ .settings/
141
+
142
+ # OS
143
+ .DS_Store
144
+ .DS_Store?
145
+ ._*
146
+ .Spotlight-V100
147
+ .Trashes
148
+ ehthumbs.db
149
+ Thumbs.db
150
+ Desktop.ini
151
+
152
+ # Task Tree specific
153
+ .tasktree-state
154
+
155
+ # Example outputs (keep source files but ignore generated outputs)
156
+ example/output.txt
157
+ example/archive.tar.gz
158
+ example/.tasktree-state
159
+
160
+ # UV lock file (optional - some prefer to commit this)
161
+ # uv.lock
@@ -0,0 +1,69 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ Task Tree (tt) is a task automation tool that combines simple command execution with intelligent dependency tracking and incremental execution. The project is a Python application built with a focus on:
8
+
9
+ - **Intelligent incremental execution**: Tasks only run when necessary based on input changes, dependency updates, or task definition changes
10
+ - **YAML-based task definition**: Tasks are defined in `tasktree.yaml` or `tt.yaml` files with dependencies, inputs, outputs, and commands
11
+ - **Automatic input inheritance**: Tasks automatically inherit inputs from dependencies
12
+ - **Parameterized tasks**: Tasks can accept typed arguments with defaults
13
+ - **File imports**: Task definitions can be split across multiple files and namespaced
14
+
15
+ ## Architecture
16
+
17
+ ### Core Components
18
+
19
+ - **`src/tasktree/tasks.py`**: Core task execution logic using subprocess to run shell commands
20
+ - **`src/tasktree/cli.py`**: Command-line interface (currently minimal)
21
+ - **`main.py`**: Entry point for the application
22
+ - **`tests/unit/test_tasks.py`**: Unit tests using Python's unittest framework
23
+
24
+ ### Key Dependencies
25
+
26
+ - **PyYAML**: For recipe parsing
27
+ - **Typer, Click, Rich**: For CLI (mentioned in README but not yet implemented)
28
+ - **graphlib.TopologicalSorter**: For dependency resolution
29
+ - **pathlib**: For file operations and glob expansion
30
+
31
+ ## Development Commands
32
+
33
+ ### Testing
34
+ ```bash
35
+ python -m pytest tests/
36
+ ```
37
+
38
+ ### Running the Application
39
+ ```bash
40
+ python main.py
41
+ ```
42
+
43
+ ### Package Management
44
+ This project uses `uv` for dependency management (indicated by `uv.lock` file).
45
+
46
+ ## State Management
47
+
48
+ The application uses a `.tasktree-state` file at the project root to track:
49
+ - When tasks last ran
50
+ - Timestamps of input files at execution time
51
+ - Task hashes based on command, outputs, and working directory
52
+
53
+ ## Testing Approach
54
+
55
+ The project uses Python's built-in `unittest` framework with mocking via `unittest.mock`. Tests focus on verifying subprocess calls for task execution.
56
+
57
+ ## Task Definition Format
58
+
59
+ Tasks are defined in YAML with the following structure:
60
+ ```yaml
61
+ task-name:
62
+ desc: Description (optional)
63
+ deps: [dependency-tasks]
64
+ inputs: [glob-patterns]
65
+ outputs: [glob-patterns]
66
+ working_dir: execution-directory
67
+ args: [typed-parameters]
68
+ cmd: shell-command
69
+ ```
@@ -0,0 +1,375 @@
1
+ Metadata-Version: 2.4
2
+ Name: tasktree
3
+ Version: 0.0.2
4
+ Summary: A task automation tool with incremental execution
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: click>=8.1.0
7
+ Requires-Dist: colorama>=0.4.6
8
+ Requires-Dist: pyyaml>=6.0
9
+ Requires-Dist: rich>=13.0.0
10
+ Requires-Dist: typer>=0.9.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=9.0.2; extra == 'dev'
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Task Tree (tt)
16
+
17
+ [![Tests](https://github.com/kevinchannon/task-tree/actions/workflows/test.yml/badge.svg)](https://github.com/kevinchannon/task-tree/actions/workflows/test.yml)
18
+
19
+ A task automation tool that combines simple command execution with dependency tracking and incremental execution.
20
+
21
+ ## Installation
22
+
23
+ ### From PyPI (Recommended)
24
+
25
+ ```bash
26
+ pipx install tasktree
27
+ ```
28
+
29
+ ### From Source
30
+
31
+ For the latest unreleased version from GitHub:
32
+
33
+ ```bash
34
+ pipx install git+https://github.com/kevinchannon/task-tree.git
35
+ ```
36
+
37
+ Or to install from a local clone:
38
+
39
+ ```bash
40
+ git clone https://github.com/kevinchannon/task-tree.git
41
+ cd tasktree
42
+ pipx install .
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ Create a `tasktree.yaml` (or `tt.yaml`) in your project:
48
+
49
+ ```yaml
50
+ build:
51
+ desc: Compile the application
52
+ outputs: [target/release/bin]
53
+ cmd: cargo build --release
54
+
55
+ test:
56
+ desc: Run tests
57
+ deps: [build]
58
+ cmd: cargo test
59
+ ```
60
+
61
+ Run tasks:
62
+
63
+ ```bash
64
+ tt build # Build the application
65
+ tt test # Run tests (builds first if needed)
66
+ tt --list # Show all available tasks
67
+ ```
68
+
69
+ ## Core Concepts
70
+
71
+ ### Intelligent Incremental Execution
72
+
73
+ Task Tree only runs tasks when necessary. A task executes if:
74
+
75
+ - Its definition (command, outputs, working directory) has changed
76
+ - Any input files have changed since the last run
77
+ - Any dependencies have re-run
78
+ - It has never been executed before
79
+ - It has no inputs or outputs (always runs)
80
+
81
+ ### Automatic Input Inheritance
82
+
83
+ Tasks automatically inherit inputs from dependencies, eliminating redundant declarations:
84
+
85
+ ```yaml
86
+ build:
87
+ outputs: [dist/app]
88
+ cmd: go build -o dist/app
89
+
90
+ package:
91
+ deps: [build]
92
+ outputs: [dist/app.tar.gz]
93
+ cmd: tar czf dist/app.tar.gz dist/app
94
+ # Automatically tracks dist/app as an input
95
+ ```
96
+
97
+ ### Single State File
98
+
99
+ All state lives in `.tasktree-state` at your project root. Stale entries are automatically pruned—no manual cleanup needed.
100
+
101
+ ## Task Definition
102
+
103
+ ### Basic Structure
104
+
105
+ ```yaml
106
+ task-name:
107
+ desc: Human-readable description (optional)
108
+ deps: [other-task] # Task dependencies
109
+ inputs: [src/**/*.go] # Explicit input files (glob patterns)
110
+ outputs: [dist/binary] # Output files (glob patterns)
111
+ working_dir: subproject/ # Execution directory (default: project root)
112
+ args: [param1, param2:path=default] # Task parameters
113
+ cmd: go build -o dist/binary # Command to execute
114
+ ```
115
+
116
+ ### Commands
117
+
118
+ Multi-line commands using YAML literal blocks:
119
+
120
+ ```yaml
121
+ deploy:
122
+ cmd: |
123
+ mkdir -p dist
124
+ cp build/* dist/
125
+ rsync -av dist/ server:/opt/app/
126
+ ```
127
+
128
+ Or folded blocks for long single-line commands:
129
+
130
+ ```yaml
131
+ compile:
132
+ cmd: >
133
+ gcc -o bin/app
134
+ src/*.c
135
+ -I include
136
+ -L lib -lm
137
+ ```
138
+
139
+ ### Parameterised Tasks
140
+
141
+ Tasks can accept arguments with optional defaults:
142
+
143
+ ```yaml
144
+ deploy:
145
+ args: [environment, region=eu-west-1]
146
+ deps: [build]
147
+ cmd: |
148
+ aws s3 cp dist/app.zip s3://{{environment}}-{{region}}/
149
+ aws lambda update-function-code --function-name app-{{environment}}
150
+ ```
151
+
152
+ Invoke with: `tt deploy production` or `tt deploy staging us-east-1` or `tt deploy staging region=us-east-1`.
153
+
154
+ Arguments may be typed, or not and have a default, or not. Valid argument types are:
155
+
156
+ * int - an integer value (e.g. 0, 10, 123, -9)
157
+ * float - a floating point value (e.g. 1.234, -3.1415, 2e-4)
158
+ * bool - Boolean-ish value (e.g. true, false, yes, no, 1, 0, etc)
159
+ * str - a string
160
+ * path - a pathlike string
161
+ * datetime - a datetime in the format 2025-12-17T16:56:12
162
+ * ip - an ip address (v4 or v6)
163
+ * ipv4 - an IPv4 value
164
+ * ipv6 - an IPv6 value
165
+ * email - String validated, but not positively confirmed to be a reachable address.
166
+ * hostname - looks like a hostname, resolution of the name is not attempted as part of the validation
167
+
168
+ Different argument values are tracked separately—tasks re-run when invoked with new arguments.
169
+
170
+ ## File Imports
171
+
172
+ Split task definitions across multiple files for better organisation:
173
+
174
+ ```yaml
175
+ # tasktree.yaml
176
+ import:
177
+ - file: build/tasks.yml
178
+ as: build
179
+ - file: deploy/tasks.yml
180
+ as: deploy
181
+
182
+ test:
183
+ deps: [build.compile, build.test-compile]
184
+ cmd: ./run-tests.sh
185
+
186
+ ci:
187
+ deps: [build.all, test, deploy.staging]
188
+ ```
189
+
190
+ Imported tasks are namespaced and can be referenced as dependencies. Each imported file is self-contained—it cannot depend on tasks in the importing file.
191
+
192
+ ## Glob Patterns
193
+
194
+ Input and output patterns support standard glob syntax:
195
+
196
+ - `src/*.rs` — All Rust files in `src/`
197
+ - `src/**/*.rs` — All Rust files recursively
198
+ - `{file1,file2}` — Specific files
199
+ - `**/*.{js,ts}` — Multiple extensions recursively
200
+
201
+ ## State Management
202
+
203
+ ### How State Works
204
+
205
+ Each task is identified by a hash of its definition (command, outputs, working directory). State tracks:
206
+
207
+ - When the task last ran
208
+ - Timestamps of input files at that time
209
+
210
+ Tasks are re-run when their definition changes or inputs are newer than the last run.
211
+
212
+ ### What's Not In The Hash
213
+
214
+ Changes to these don't invalidate cached state:
215
+
216
+ - Task name (tasks can be renamed freely)
217
+ - Description
218
+ - Dependencies (only affects execution order)
219
+ - Explicit inputs (tracked by timestamp, not definition)
220
+
221
+ ### Automatic Cleanup
222
+
223
+ At the start of each invocation, state is checked for invalid task hashes and non-existent ones are automatically removed. Delete a task from your recipe file and its state disappears the next time you run `tt <cmd>`
224
+
225
+ ## Example: Full Build Pipeline
226
+
227
+ ```yaml
228
+ imports:
229
+ - file: common/docker.yml
230
+ as: docker
231
+
232
+ compile:
233
+ desc: Build application binaries
234
+ outputs: [target/release/app]
235
+ cmd: cargo build --release
236
+
237
+ test-unit:
238
+ desc: Run unit tests
239
+ deps: [compile]
240
+ cmd: cargo test
241
+
242
+ package:
243
+ desc: Create distribution archive
244
+ deps: [compile]
245
+ outputs: [dist/app-{{version}}.tar.gz]
246
+ args: [version]
247
+ cmd: |
248
+ mkdir -p dist
249
+ tar czf dist/app-{{version}}.tar.gz \
250
+ target/release/app \
251
+ config/ \
252
+ migrations/
253
+
254
+ deploy:
255
+ desc: Deploy to environment
256
+ deps: [package, docker.build-runtime]
257
+ args: [environment, version]
258
+ cmd: |
259
+ scp dist/app-{{version}}.tar.gz {{environment}}:/opt/
260
+ ssh {{environment}} /opt/deploy.sh {{version}}
261
+
262
+ integration-test:
263
+ desc: Run integration tests against deployed environment
264
+ deps: [deploy]
265
+ args: [environment, version]
266
+ cmd: pytest tests/integration/ --env={{environment}}
267
+ ```
268
+
269
+ Run the full pipeline:
270
+
271
+ ```bash
272
+ tt integration-test staging version=1.2.3
273
+ ```
274
+
275
+ This will:
276
+ 1. Compile if sources have changed
277
+ 2. Run unit tests if compilation ran
278
+ 3. Package if compilation ran or version argument is new
279
+ 4. Build Docker runtime (from imported file) if needed
280
+ 5. Deploy if package or Docker image changed
281
+ 6. Run integration tests (always runs)
282
+
283
+ ## Implementation Notes
284
+
285
+ Built with Python 3.11+ using:
286
+
287
+ - **PyYAML** for recipe parsing
288
+ - **Typer**, **Click**, **Rich** for CLI
289
+ - **graphlib.TopologicalSorter** for dependency resolution
290
+ - **pathlib** for file operations and glob expansion
291
+
292
+ State file uses JSON format for simplicity and standard library compatibility.
293
+
294
+ ## Development
295
+
296
+ ### Setup Development Environment
297
+
298
+ ```bash
299
+ # Clone repository
300
+ git clone https://github.com/kevinchannon/task-tree.git
301
+ cd tasktree
302
+
303
+ # Install uv (if not already installed)
304
+ curl -LsSf https://astral.sh/uv/install.sh | sh
305
+
306
+ # Install dependencies
307
+ uv sync
308
+
309
+ # Install in editable mode
310
+ pipx install -e .
311
+ ```
312
+
313
+ ### Running Tests
314
+
315
+ ```bash
316
+ # Run all tests
317
+ uv run pytest
318
+
319
+ # Run with verbose output
320
+ uv run pytest -v
321
+
322
+ # Run specific test file
323
+ uv run pytest tests/unit/test_executor.py
324
+ ```
325
+
326
+ ### Using Task Tree for Development
327
+
328
+ The repository includes a `tasktree.yaml` with development tasks:
329
+
330
+ ```bash
331
+ tt test # Run tests
332
+ tt build # Build wheel package
333
+ tt install-dev # Install package in development mode
334
+ tt clean # Remove build artifacts
335
+ ```
336
+
337
+ ## Releasing
338
+
339
+ New releases are created by pushing version tags to GitHub. The release workflow automatically:
340
+ - Builds wheel and source distributions
341
+ - Creates a GitHub Release with artifacts
342
+ - Publishes to PyPI via trusted publishing
343
+
344
+ ### Release Process
345
+
346
+ 1. Ensure main branch is ready:
347
+ ```bash
348
+ git checkout main
349
+ git pull
350
+ ```
351
+
352
+ 2. Create and push a version tag:
353
+ ```bash
354
+ git tag v1.0.0
355
+ git push origin v1.0.0
356
+ ```
357
+
358
+ 3. GitHub Actions will automatically:
359
+ - Extract version from tag (e.g., `v1.0.0` → `1.0.0`)
360
+ - Update `pyproject.toml` with the version
361
+ - Build wheel and sdist
362
+ - Create GitHub Release
363
+ - Publish to PyPI
364
+
365
+ 4. Verify the release:
366
+ - GitHub: https://github.com/kevinchannon/task-tree/releases
367
+ - PyPI: https://pypi.org/kevinchannon/tasktree/
368
+ - Test: `pipx install --force tasktree`
369
+
370
+ ### Version Numbering
371
+
372
+ Follow semantic versioning:
373
+ - `v1.0.0` - Major release (breaking changes)
374
+ - `v1.1.0` - Minor release (new features, backward compatible)
375
+ - `v1.1.1` - Patch release (bug fixes)