tasktree 0.0.20__tar.gz → 0.0.22__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 (77) hide show
  1. {tasktree-0.0.20 → tasktree-0.0.22}/.claude/settings.local.json +3 -1
  2. {tasktree-0.0.20 → tasktree-0.0.22}/.github/workflows/test.yml +9 -16
  3. {tasktree-0.0.20 → tasktree-0.0.22}/.gitignore +1 -2
  4. {tasktree-0.0.20 → tasktree-0.0.22}/CLAUDE.md +22 -5
  5. tasktree-0.0.20/README.md → tasktree-0.0.22/PKG-INFO +161 -20
  6. tasktree-0.0.20/PKG-INFO → tasktree-0.0.22/README.md +144 -35
  7. tasktree-0.0.22/__init__.py +3 -0
  8. tasktree-0.0.22/example/tasktree.yaml +64 -0
  9. {tasktree-0.0.20 → tasktree-0.0.22}/pyproject.toml +5 -4
  10. {tasktree-0.0.20 → tasktree-0.0.22}/schema/README.md +24 -6
  11. {tasktree-0.0.20 → tasktree-0.0.22}/schema/tasktree-schema.json +20 -5
  12. tasktree-0.0.22/src/__init__.py +4 -0
  13. {tasktree-0.0.20 → tasktree-0.0.22}/src/tasktree/__init__.py +4 -1
  14. {tasktree-0.0.20 → tasktree-0.0.22}/src/tasktree/cli.py +198 -60
  15. {tasktree-0.0.20 → tasktree-0.0.22}/src/tasktree/docker.py +105 -64
  16. {tasktree-0.0.20 → tasktree-0.0.22}/src/tasktree/executor.py +427 -310
  17. {tasktree-0.0.20 → tasktree-0.0.22}/src/tasktree/graph.py +138 -82
  18. {tasktree-0.0.20 → tasktree-0.0.22}/src/tasktree/hasher.py +81 -25
  19. {tasktree-0.0.20 → tasktree-0.0.22}/src/tasktree/parser.py +554 -344
  20. {tasktree-0.0.20 → tasktree-0.0.22}/src/tasktree/state.py +50 -22
  21. {tasktree-0.0.20 → tasktree-0.0.22}/src/tasktree/substitution.py +188 -117
  22. {tasktree-0.0.20 → tasktree-0.0.22}/src/tasktree/types.py +80 -25
  23. {tasktree-0.0.20 → tasktree-0.0.22}/tasktree.yaml +29 -26
  24. {tasktree-0.0.20 → tasktree-0.0.22}/tests/e2e/__init__.py +9 -2
  25. {tasktree-0.0.20 → tasktree-0.0.22}/tests/e2e/test_docker_basic.py +27 -13
  26. {tasktree-0.0.20 → tasktree-0.0.22}/tests/e2e/test_docker_environment.py +29 -10
  27. {tasktree-0.0.20 → tasktree-0.0.22}/tests/e2e/test_docker_ownership.py +22 -10
  28. {tasktree-0.0.20 → tasktree-0.0.22}/tests/e2e/test_docker_volumes.py +32 -19
  29. {tasktree-0.0.20 → tasktree-0.0.22}/tests/e2e/test_non_docker.py +24 -16
  30. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_arg_choices.py +71 -21
  31. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_arg_min_max.py +65 -18
  32. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_builtin_variables.py +155 -69
  33. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_clean_state.py +30 -9
  34. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_cli_options.py +188 -82
  35. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_dependency_execution.py +48 -24
  36. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_dependency_outputs.py +52 -36
  37. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_docker_build_args.py +12 -3
  38. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_end_to_end.py +75 -14
  39. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_exported_args.py +78 -61
  40. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_input_detection.py +27 -9
  41. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_missing_outputs.py +20 -4
  42. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_nested_imports.py +52 -16
  43. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_parameterized_deps_execution.py +26 -8
  44. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_parameterized_deps_templates.py +42 -16
  45. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_private_tasks_execution.py +54 -16
  46. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_self_references.py +700 -90
  47. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_state_persistence.py +26 -8
  48. tasktree-0.0.22/tests/integration/test_unified_execution.py +215 -0
  49. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_variables.py +108 -27
  50. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_working_directory.py +39 -11
  51. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_cli.py +134 -63
  52. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_dependency_parsing.py +76 -21
  53. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_docker.py +244 -61
  54. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_environment_tracking.py +119 -44
  55. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_executor.py +761 -237
  56. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_graph.py +156 -44
  57. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_hasher.py +44 -10
  58. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_list_formatting.py +225 -89
  59. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_parameterized_graph.py +44 -11
  60. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_parser.py +1395 -620
  61. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_private_tasks.py +28 -7
  62. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_state.py +40 -11
  63. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_substitution.py +706 -152
  64. {tasktree-0.0.20 → tasktree-0.0.22}/tests/unit/test_types.py +144 -36
  65. {tasktree-0.0.20 → tasktree-0.0.22}/uv.lock +126 -3
  66. tasktree-0.0.20/example/tasktree.yaml +0 -37
  67. tasktree-0.0.20/src/__init__.py +0 -0
  68. {tasktree-0.0.20 → tasktree-0.0.22}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  69. {tasktree-0.0.20 → tasktree-0.0.22}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  70. {tasktree-0.0.20 → tasktree-0.0.22}/.github/workflows/claude-code-review.yml +0 -0
  71. {tasktree-0.0.20 → tasktree-0.0.22}/.github/workflows/claude.yml +0 -0
  72. {tasktree-0.0.20 → tasktree-0.0.22}/.github/workflows/release.yml +0 -0
  73. {tasktree-0.0.20 → tasktree-0.0.22}/.github/workflows/validate-pipx-install.yml +0 -0
  74. {tasktree-0.0.20 → tasktree-0.0.22}/.python-version +0 -0
  75. {tasktree-0.0.20 → tasktree-0.0.22}/example/source.txt +0 -0
  76. {tasktree-0.0.20 → tasktree-0.0.22}/schema/vscode-settings-snippet.json +0 -0
  77. {tasktree-0.0.20 → tasktree-0.0.22}/tests/integration/test_parameterized_dependencies.yaml +0 -0
@@ -15,7 +15,9 @@
15
15
  "Bash(cat:*)",
16
16
  "Bash(tt --help:*)",
17
17
  "Bash(head:*)",
18
- "Bash(wc:*)"
18
+ "Bash(wc:*)",
19
+ "Bash(athena status:*)",
20
+ "Bash(athena sync:*)"
19
21
  ]
20
22
  }
21
23
  }
@@ -27,31 +27,26 @@ jobs:
27
27
  uses: astral-sh/setup-uv@v4
28
28
 
29
29
  - name: Install dependencies
30
- run: |
31
- uv pip install --system -e ".[dev]"
30
+ run: uv sync --extra dev
32
31
 
33
32
  - name: Pre-pull Docker images for E2E tests
34
33
  if: runner.os == 'Linux'
35
- run: |
36
- docker pull alpine:latest
34
+ run: docker pull alpine:latest
37
35
 
38
36
  - name: Run unit tests
39
- run: |
40
- python -m pytest tests/unit/ -v --tb=short
37
+ run: uv run pytest tests/unit/ --tb=short
41
38
 
42
39
  - name: Run integration tests
43
- run: |
44
- python -m pytest tests/integration/ -v --tb=short
40
+ run: uv run pytest tests/integration/ --tb=short
45
41
 
46
42
  - name: Run E2E Docker tests
47
43
  if: runner.os == 'Linux'
48
- run: |
49
- python -m pytest tests/e2e/ -v --tb=short
44
+ run: uv run pytest tests/e2e/ --tb=short
50
45
 
51
46
  - name: Test CLI commands
52
47
  run: |
53
- python -m tasktree.cli --help
54
- python -m tasktree.cli --list || true
48
+ uv run tt --help
49
+ uv run tt --list || true
55
50
 
56
51
  lint:
57
52
  runs-on: ubuntu-latest
@@ -67,9 +62,7 @@ jobs:
67
62
  uses: astral-sh/setup-uv@v4
68
63
 
69
64
  - name: Install dependencies
70
- run: |
71
- uv pip install --system -e ".[dev]"
65
+ run: uv sync --extra dev
72
66
 
73
67
  - name: Check code can be imported
74
- run: |
75
- python -c "from tasktree import Executor, Recipe, Task; print('Import successful')"
68
+ run: uv run python -c "from tasktree import Executor, Recipe, Task; print('Import successful')"
@@ -157,6 +157,5 @@ example/.tasktree-state
157
157
  # Requirements files copied from GitHub issues (for driving Claude Code)
158
158
  requirements/*
159
159
 
160
- # UV lock file (optional - some prefer to commit this)
161
- # uv.lock
160
+ .athena-cache
162
161
 
@@ -74,15 +74,30 @@ Your sponsor is not made of money! Try to minimise token useage, so that we can
74
74
 
75
75
  ## Development Commands
76
76
 
77
+ ## IMPORTANT! Tool use
78
+ ### Athena Code Knowledge
79
+ This repo uses Athena to store and retrieve knowledge about the codebase. YOU SHOULD USE THE TOOL TO MAKE INQUIRIES ABOUT THE CODE.
80
+
81
+ 1. Find a function/class: `athena locate <function/class name>`
82
+ 2. Find information about what a function/class does:
83
+ - `athena info path/to/module.py:ClassName`
84
+ - `athena info path/to/module.py:ClassName.method`
85
+ - `athena info path/to/module.py:some_function`
86
+ 3. Search for relevant code information:
87
+ - `athena serch "generate JWT authentication tokens`
88
+ 4. AFTER code changes, run `athena status` to check which docs should have been updated
89
+ 5. AFTER checking/updating docs, run `athena sync` to register the changes
90
+
91
+ **prefer this workflow to using native `grep` and `find` tools** for understanding the code.
92
+
77
93
  ### Testing
78
94
  ```bash
79
95
  python3 -m pytest tests/
80
96
  ```
81
-
82
- The project has **656 tests** across three categories:
83
- - **Unit tests** (`tests/unit/`): 15 test files covering parser, executor, graph, hasher, types, substitution, state
84
- - **Integration tests** (`tests/integration/`): 21 test files for CLI options, parameterized tasks, Docker, variables, arg validation
85
- - **E2E tests** (`tests/e2e/`): 5 test files for Docker volumes, ownership, environment, and basic functionality
97
+ The project has tests across three categories:
98
+ - **Unit tests** (`tests/unit/`): Should always be updated for any change. Makes up the bulk of code coverage
99
+ - **Integration tests** (`tests/integration/`): Test changes using the Typer CliRunner to cover end-to-end workflows across multiple modules
100
+ - **E2E tests** (`tests/e2e/`): Heavyweight tests that cover running the tool in a subprocess and/or containerized environment
86
101
 
87
102
  ### Running the Application
88
103
  ```bash
@@ -139,6 +154,8 @@ environments:
139
154
  env-name:
140
155
  default: true # Make this the default environment
141
156
  shell: /bin/bash # Shell environment
157
+ preamble: | # Optional preamble prepended to all commands
158
+ set -euo pipefail
142
159
  # OR
143
160
  dockerfile: path/to/Dockerfile # Docker environment
144
161
  context: build-context-dir
@@ -1,3 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: tasktree
3
+ Version: 0.0.22
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: pathspec>=0.11.0
9
+ Requires-Dist: pyyaml>=6.0
10
+ Requires-Dist: rich>=13.0.0
11
+ Requires-Dist: typer>=0.9.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: black>=26.1.0; extra == 'dev'
14
+ Requires-Dist: pytest>=9.0.2; extra == 'dev'
15
+ Requires-Dist: ruff>=0.14.14; extra == 'dev'
16
+ Description-Content-Type: text/markdown
17
+
1
18
  # Task Tree (tt)
2
19
 
3
20
  [![Tests](https://github.com/kevinchannon/task-tree/actions/workflows/test.yml/badge.svg)](https://github.com/kevinchannon/task-tree/actions/workflows/test.yml)
@@ -240,20 +257,19 @@ tasks:
240
257
  cmd: go build -o dist/binary # Command to execute
241
258
  ```
242
259
 
260
+ **Task name constraints:**
261
+ - Task names cannot contain dots (`.`) - they are reserved for namespacing imported tasks
262
+ - Example: `build.release` is invalid as a task name, but valid as a reference to task `release` in namespace `build`
263
+
243
264
  ### Commands
244
265
 
245
- **Single-line commands** are executed directly via the configured shell:
266
+ All commands are executed by writing them to temporary script files. This provides consistent behavior and better shell syntax support:
246
267
 
247
268
  ```yaml
248
269
  tasks:
249
270
  build:
250
271
  cmd: cargo build --release
251
- ```
252
272
 
253
- **Multi-line commands** are written to temporary script files for proper execution:
254
-
255
- ```yaml
256
- tasks:
257
273
  deploy:
258
274
  cmd: |
259
275
  mkdir -p dist
@@ -261,7 +277,7 @@ tasks:
261
277
  rsync -av dist/ server:/opt/app/
262
278
  ```
263
279
 
264
- Multi-line commands preserve shell syntax (line continuations, heredocs, etc.) and support shebangs on Unix/macOS.
280
+ Commands preserve shell syntax (line continuations, heredocs, etc.) and support shebangs on Unix/macOS.
265
281
 
266
282
  Or use folded blocks for long single-line commands:
267
283
 
@@ -277,7 +293,7 @@ tasks:
277
293
 
278
294
  ### Execution Environments
279
295
 
280
- Configure custom shell environments for task execution:
296
+ Configure custom shell environments for task execution. Use the `preamble` field to add initialization code to all commands:
281
297
 
282
298
  ```yaml
283
299
  environments:
@@ -285,17 +301,14 @@ environments:
285
301
 
286
302
  bash-strict:
287
303
  shell: bash
288
- args: ['-c'] # For single-line: bash -c "command"
289
- preamble: | # For multi-line: prepended to script
304
+ preamble: | # Prepended to all commands
290
305
  set -euo pipefail
291
306
 
292
307
  python:
293
308
  shell: python
294
- args: ['-c']
295
309
 
296
310
  powershell:
297
311
  shell: powershell
298
- args: ['-ExecutionPolicy', 'Bypass', '-Command']
299
312
  preamble: |
300
313
  $ErrorActionPreference = 'Stop'
301
314
 
@@ -324,8 +337,8 @@ tasks:
324
337
  4. Platform default (bash on Unix, cmd on Windows)
325
338
 
326
339
  **Platform defaults** when no environments are configured:
327
- - **Unix/macOS**: bash with `-c` args
328
- - **Windows**: cmd with `/c` args
340
+ - **Unix/macOS**: bash
341
+ - **Windows**: cmd
329
342
 
330
343
  ### Docker Environments
331
344
 
@@ -820,7 +833,7 @@ Hint: Define named outputs like: outputs: [{ missing: 'path/to/file' }]
820
833
 
821
834
  ### Self-References
822
835
 
823
- Tasks can reference their own named inputs and outputs using `{{ self.inputs.name }}` and `{{ self.outputs.name }}` templates. This eliminates repetition when paths contain variables or when tasks have multiple inputs/outputs.
836
+ Tasks can reference their own inputs and outputs using `{{ self.inputs.name }}` (named access) or `{{ self.inputs.0 }}` (positional access) templates. This eliminates repetition when paths contain variables or when tasks have multiple inputs/outputs.
824
837
 
825
838
  **Named Inputs and Outputs:**
826
839
 
@@ -847,8 +860,10 @@ tasks:
847
860
 
848
861
  - **Defining named inputs**: `inputs: [{ name: "path/to/file" }]`
849
862
  - **Defining named outputs**: `outputs: [{ name: "path/to/file" }]`
850
- - **Referencing inputs**: `{{ self.inputs.input_name }}`
851
- - **Referencing outputs**: `{{ self.outputs.output_name }}`
863
+ - **Defining anonymous inputs**: `inputs: ["path/to/file"]`
864
+ - **Defining anonymous outputs**: `outputs: ["path/to/file"]`
865
+ - **Referencing by name**: `{{ self.inputs.input_name }}` or `{{ self.outputs.output_name }}`
866
+ - **Referencing by index**: `{{ self.inputs.0 }}` or `{{ self.outputs.1 }}` (0-based)
852
867
  - **Mixed format**: Can combine named and anonymous inputs/outputs in the same task
853
868
 
854
869
  **Why Use Self-References?**
@@ -957,6 +972,129 @@ tasks:
957
972
  cmd: build-tool --config {{ self.inputs.config }} --output {{ self.outputs.binary }}
958
973
  ```
959
974
 
975
+ **Positional Index References:**
976
+
977
+ In addition to named references, you can access inputs and outputs by their positional index using `{{ self.inputs.0 }}`, `{{ self.inputs.1 }}`, etc. This provides an alternative way to reference items, especially useful for:
978
+
979
+ - **Anonymous inputs/outputs**: Reference items that don't have names
980
+ - **Simple sequential access**: When order is more important than naming
981
+ - **Mixed with named access**: Use both styles in the same task
982
+
983
+ **Syntax:**
984
+
985
+ ```yaml
986
+ tasks:
987
+ process:
988
+ inputs: ["file1.txt", "file2.txt", "file3.txt"]
989
+ outputs: ["output1.txt", "output2.txt"]
990
+ cmd: |
991
+ cat {{ self.inputs.0 }} {{ self.inputs.1 }} > {{ self.outputs.0 }}
992
+ cat {{ self.inputs.2 }} > {{ self.outputs.1 }}
993
+ ```
994
+
995
+ Indices follow YAML declaration order, starting from 0 (zero-based indexing):
996
+ - First input/output = index 0
997
+ - Second input/output = index 1
998
+ - Third input/output = index 2, etc.
999
+
1000
+ **Works with Both Named and Anonymous:**
1001
+
1002
+ ```yaml
1003
+ tasks:
1004
+ build:
1005
+ inputs:
1006
+ - config: "build.yaml" # Index 0, also accessible as {{ self.inputs.config }}
1007
+ - "src/**/*.c" # Index 1, ONLY accessible as {{ self.inputs.1 }}
1008
+ - headers: "include/*.h" # Index 2, also accessible as {{ self.inputs.headers }}
1009
+ outputs:
1010
+ - "dist/app.js" # Index 0
1011
+ - bundle: "dist/bundle.js" # Index 1, also accessible as {{ self.outputs.bundle }}
1012
+ cmd: |
1013
+ # Mix positional and named references
1014
+ build-tool \
1015
+ --config {{ self.inputs.0 }} \
1016
+ --sources {{ self.inputs.1 }} \
1017
+ --headers {{ self.inputs.headers }} \
1018
+ --output {{ self.outputs.bundle }}
1019
+ ```
1020
+
1021
+ **Same Item, Multiple Ways:**
1022
+
1023
+ Named items can be accessed by both name and index:
1024
+
1025
+ ```yaml
1026
+ tasks:
1027
+ copy:
1028
+ inputs:
1029
+ - source: data.txt
1030
+ cmd: |
1031
+ # These are equivalent:
1032
+ cat {{ self.inputs.source }} > copy1.txt
1033
+ cat {{ self.inputs.0 }} > copy2.txt
1034
+ ```
1035
+
1036
+ **With Variables and Arguments:**
1037
+
1038
+ Positional references work with variable-expanded paths:
1039
+
1040
+ ```yaml
1041
+ variables:
1042
+ version: "1.0"
1043
+
1044
+ tasks:
1045
+ package:
1046
+ args: [platform]
1047
+ inputs:
1048
+ - "dist/app-{{ var.version }}.js"
1049
+ - "dist/lib-{{ arg.platform }}.so"
1050
+ outputs: ["release-{{ var.version }}-{{ arg.platform }}.tar.gz"]
1051
+ cmd: tar czf {{ self.outputs.0 }} {{ self.inputs.0 }} {{ self.inputs.1 }}
1052
+ ```
1053
+
1054
+ **Index Boundaries:**
1055
+
1056
+ Indices are validated before execution:
1057
+
1058
+ ```yaml
1059
+ tasks:
1060
+ build:
1061
+ inputs: ["file1.txt", "file2.txt"]
1062
+ cmd: cat {{ self.inputs.5 }} # Error: index 5 out of bounds!
1063
+ ```
1064
+
1065
+ Error message:
1066
+ ```
1067
+ Task 'build' references input index '5' but only has 2 inputs (indices 0-1)
1068
+ ```
1069
+
1070
+ **Empty Lists:**
1071
+
1072
+ Referencing an index when no inputs/outputs exist:
1073
+
1074
+ ```yaml
1075
+ tasks:
1076
+ generate:
1077
+ cmd: echo "test" > {{ self.outputs.0 }} # Error: no outputs defined!
1078
+ ```
1079
+
1080
+ Error message:
1081
+ ```
1082
+ Task 'generate' references output index '0' but has no outputs defined
1083
+ ```
1084
+
1085
+ **When to Use Index References:**
1086
+
1087
+ - **Anonymous items**: Only way to reference inputs/outputs without names
1088
+ - **Order-based processing**: When the sequence matters more than naming
1089
+ - **Simple tasks**: Quick access without defining names
1090
+ - **Compatibility**: Accessing items in legacy YAML that uses anonymous format
1091
+
1092
+ **When to Use Named References:**
1093
+
1094
+ - **Clarity**: Names make commands more readable (`{{ self.inputs.config }}` vs `{{ self.inputs.2 }}`)
1095
+ - **Maintainability**: Adding/removing items doesn't break indices
1096
+ - **Complex tasks**: Many inputs/outputs are easier to manage with names
1097
+
960
1098
  **Error Messages:**
961
1099
 
962
1100
  If you reference a non-existent input or output:
@@ -996,18 +1134,21 @@ Hint: Define named inputs like: inputs: [{ src: 'file.txt' }]
996
1134
 
997
1135
  **Key Behaviors:**
998
1136
 
1137
+ - **Two access methods**: Reference by name (`{{ self.inputs.name }}`) or by index (`{{ self.inputs.0 }}`)
999
1138
  - **Template resolution**: Self-references are resolved during dependency graph planning
1000
1139
  - **Substitution order**: Variables → Dependency outputs → Self-references → Arguments/Environment
1001
- - **Fail-fast validation**: Errors are caught before execution begins
1002
- - **Clear error messages**: Lists available names if reference doesn't exist
1140
+ - **Fail-fast validation**: Errors are caught before execution begins (missing names, out-of-bounds indices)
1141
+ - **Clear error messages**: Lists available names/indices if reference doesn't exist
1003
1142
  - **Backward compatible**: Existing anonymous inputs/outputs work unchanged
1004
1143
  - **State tracking**: Works correctly with incremental execution and freshness checks
1144
+ - **Index order**: Positional indices follow YAML declaration order (0-based)
1005
1145
 
1006
1146
  **Limitations:**
1007
1147
 
1008
- - **Anonymous not referenceable**: Only named inputs/outputs can be referenced
1148
+ - **Anonymous not referenceable by name**: Anonymous inputs/outputs cannot be referenced by name (use positional index instead: `{{ self.inputs.0 }}`)
1009
1149
  - **Case sensitive**: `{{ self.inputs.Src }}` and `{{ self.inputs.src }}` are different
1010
1150
  - **Argument defaults**: Self-references in argument defaults are not supported (arguments are evaluated before self-references)
1151
+ - **No negative indices**: Python-style negative indexing (`{{ self.inputs.-1 }}`) is not supported
1011
1152
 
1012
1153
  **Use Cases:**
1013
1154
 
@@ -1,18 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: tasktree
3
- Version: 0.0.20
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: pathspec>=0.11.0
9
- Requires-Dist: pyyaml>=6.0
10
- Requires-Dist: rich>=13.0.0
11
- Requires-Dist: typer>=0.9.0
12
- Provides-Extra: dev
13
- Requires-Dist: pytest>=9.0.2; extra == 'dev'
14
- Description-Content-Type: text/markdown
15
-
16
1
  # Task Tree (tt)
17
2
 
18
3
  [![Tests](https://github.com/kevinchannon/task-tree/actions/workflows/test.yml/badge.svg)](https://github.com/kevinchannon/task-tree/actions/workflows/test.yml)
@@ -255,20 +240,19 @@ tasks:
255
240
  cmd: go build -o dist/binary # Command to execute
256
241
  ```
257
242
 
243
+ **Task name constraints:**
244
+ - Task names cannot contain dots (`.`) - they are reserved for namespacing imported tasks
245
+ - Example: `build.release` is invalid as a task name, but valid as a reference to task `release` in namespace `build`
246
+
258
247
  ### Commands
259
248
 
260
- **Single-line commands** are executed directly via the configured shell:
249
+ All commands are executed by writing them to temporary script files. This provides consistent behavior and better shell syntax support:
261
250
 
262
251
  ```yaml
263
252
  tasks:
264
253
  build:
265
254
  cmd: cargo build --release
266
- ```
267
255
 
268
- **Multi-line commands** are written to temporary script files for proper execution:
269
-
270
- ```yaml
271
- tasks:
272
256
  deploy:
273
257
  cmd: |
274
258
  mkdir -p dist
@@ -276,7 +260,7 @@ tasks:
276
260
  rsync -av dist/ server:/opt/app/
277
261
  ```
278
262
 
279
- Multi-line commands preserve shell syntax (line continuations, heredocs, etc.) and support shebangs on Unix/macOS.
263
+ Commands preserve shell syntax (line continuations, heredocs, etc.) and support shebangs on Unix/macOS.
280
264
 
281
265
  Or use folded blocks for long single-line commands:
282
266
 
@@ -292,7 +276,7 @@ tasks:
292
276
 
293
277
  ### Execution Environments
294
278
 
295
- Configure custom shell environments for task execution:
279
+ Configure custom shell environments for task execution. Use the `preamble` field to add initialization code to all commands:
296
280
 
297
281
  ```yaml
298
282
  environments:
@@ -300,17 +284,14 @@ environments:
300
284
 
301
285
  bash-strict:
302
286
  shell: bash
303
- args: ['-c'] # For single-line: bash -c "command"
304
- preamble: | # For multi-line: prepended to script
287
+ preamble: | # Prepended to all commands
305
288
  set -euo pipefail
306
289
 
307
290
  python:
308
291
  shell: python
309
- args: ['-c']
310
292
 
311
293
  powershell:
312
294
  shell: powershell
313
- args: ['-ExecutionPolicy', 'Bypass', '-Command']
314
295
  preamble: |
315
296
  $ErrorActionPreference = 'Stop'
316
297
 
@@ -339,8 +320,8 @@ tasks:
339
320
  4. Platform default (bash on Unix, cmd on Windows)
340
321
 
341
322
  **Platform defaults** when no environments are configured:
342
- - **Unix/macOS**: bash with `-c` args
343
- - **Windows**: cmd with `/c` args
323
+ - **Unix/macOS**: bash
324
+ - **Windows**: cmd
344
325
 
345
326
  ### Docker Environments
346
327
 
@@ -835,7 +816,7 @@ Hint: Define named outputs like: outputs: [{ missing: 'path/to/file' }]
835
816
 
836
817
  ### Self-References
837
818
 
838
- Tasks can reference their own named inputs and outputs using `{{ self.inputs.name }}` and `{{ self.outputs.name }}` templates. This eliminates repetition when paths contain variables or when tasks have multiple inputs/outputs.
819
+ Tasks can reference their own inputs and outputs using `{{ self.inputs.name }}` (named access) or `{{ self.inputs.0 }}` (positional access) templates. This eliminates repetition when paths contain variables or when tasks have multiple inputs/outputs.
839
820
 
840
821
  **Named Inputs and Outputs:**
841
822
 
@@ -862,8 +843,10 @@ tasks:
862
843
 
863
844
  - **Defining named inputs**: `inputs: [{ name: "path/to/file" }]`
864
845
  - **Defining named outputs**: `outputs: [{ name: "path/to/file" }]`
865
- - **Referencing inputs**: `{{ self.inputs.input_name }}`
866
- - **Referencing outputs**: `{{ self.outputs.output_name }}`
846
+ - **Defining anonymous inputs**: `inputs: ["path/to/file"]`
847
+ - **Defining anonymous outputs**: `outputs: ["path/to/file"]`
848
+ - **Referencing by name**: `{{ self.inputs.input_name }}` or `{{ self.outputs.output_name }}`
849
+ - **Referencing by index**: `{{ self.inputs.0 }}` or `{{ self.outputs.1 }}` (0-based)
867
850
  - **Mixed format**: Can combine named and anonymous inputs/outputs in the same task
868
851
 
869
852
  **Why Use Self-References?**
@@ -972,6 +955,129 @@ tasks:
972
955
  cmd: build-tool --config {{ self.inputs.config }} --output {{ self.outputs.binary }}
973
956
  ```
974
957
 
958
+ **Positional Index References:**
959
+
960
+ In addition to named references, you can access inputs and outputs by their positional index using `{{ self.inputs.0 }}`, `{{ self.inputs.1 }}`, etc. This provides an alternative way to reference items, especially useful for:
961
+
962
+ - **Anonymous inputs/outputs**: Reference items that don't have names
963
+ - **Simple sequential access**: When order is more important than naming
964
+ - **Mixed with named access**: Use both styles in the same task
965
+
966
+ **Syntax:**
967
+
968
+ ```yaml
969
+ tasks:
970
+ process:
971
+ inputs: ["file1.txt", "file2.txt", "file3.txt"]
972
+ outputs: ["output1.txt", "output2.txt"]
973
+ cmd: |
974
+ cat {{ self.inputs.0 }} {{ self.inputs.1 }} > {{ self.outputs.0 }}
975
+ cat {{ self.inputs.2 }} > {{ self.outputs.1 }}
976
+ ```
977
+
978
+ Indices follow YAML declaration order, starting from 0 (zero-based indexing):
979
+ - First input/output = index 0
980
+ - Second input/output = index 1
981
+ - Third input/output = index 2, etc.
982
+
983
+ **Works with Both Named and Anonymous:**
984
+
985
+ ```yaml
986
+ tasks:
987
+ build:
988
+ inputs:
989
+ - config: "build.yaml" # Index 0, also accessible as {{ self.inputs.config }}
990
+ - "src/**/*.c" # Index 1, ONLY accessible as {{ self.inputs.1 }}
991
+ - headers: "include/*.h" # Index 2, also accessible as {{ self.inputs.headers }}
992
+ outputs:
993
+ - "dist/app.js" # Index 0
994
+ - bundle: "dist/bundle.js" # Index 1, also accessible as {{ self.outputs.bundle }}
995
+ cmd: |
996
+ # Mix positional and named references
997
+ build-tool \
998
+ --config {{ self.inputs.0 }} \
999
+ --sources {{ self.inputs.1 }} \
1000
+ --headers {{ self.inputs.headers }} \
1001
+ --output {{ self.outputs.bundle }}
1002
+ ```
1003
+
1004
+ **Same Item, Multiple Ways:**
1005
+
1006
+ Named items can be accessed by both name and index:
1007
+
1008
+ ```yaml
1009
+ tasks:
1010
+ copy:
1011
+ inputs:
1012
+ - source: data.txt
1013
+ cmd: |
1014
+ # These are equivalent:
1015
+ cat {{ self.inputs.source }} > copy1.txt
1016
+ cat {{ self.inputs.0 }} > copy2.txt
1017
+ ```
1018
+
1019
+ **With Variables and Arguments:**
1020
+
1021
+ Positional references work with variable-expanded paths:
1022
+
1023
+ ```yaml
1024
+ variables:
1025
+ version: "1.0"
1026
+
1027
+ tasks:
1028
+ package:
1029
+ args: [platform]
1030
+ inputs:
1031
+ - "dist/app-{{ var.version }}.js"
1032
+ - "dist/lib-{{ arg.platform }}.so"
1033
+ outputs: ["release-{{ var.version }}-{{ arg.platform }}.tar.gz"]
1034
+ cmd: tar czf {{ self.outputs.0 }} {{ self.inputs.0 }} {{ self.inputs.1 }}
1035
+ ```
1036
+
1037
+ **Index Boundaries:**
1038
+
1039
+ Indices are validated before execution:
1040
+
1041
+ ```yaml
1042
+ tasks:
1043
+ build:
1044
+ inputs: ["file1.txt", "file2.txt"]
1045
+ cmd: cat {{ self.inputs.5 }} # Error: index 5 out of bounds!
1046
+ ```
1047
+
1048
+ Error message:
1049
+ ```
1050
+ Task 'build' references input index '5' but only has 2 inputs (indices 0-1)
1051
+ ```
1052
+
1053
+ **Empty Lists:**
1054
+
1055
+ Referencing an index when no inputs/outputs exist:
1056
+
1057
+ ```yaml
1058
+ tasks:
1059
+ generate:
1060
+ cmd: echo "test" > {{ self.outputs.0 }} # Error: no outputs defined!
1061
+ ```
1062
+
1063
+ Error message:
1064
+ ```
1065
+ Task 'generate' references output index '0' but has no outputs defined
1066
+ ```
1067
+
1068
+ **When to Use Index References:**
1069
+
1070
+ - **Anonymous items**: Only way to reference inputs/outputs without names
1071
+ - **Order-based processing**: When the sequence matters more than naming
1072
+ - **Simple tasks**: Quick access without defining names
1073
+ - **Compatibility**: Accessing items in legacy YAML that uses anonymous format
1074
+
1075
+ **When to Use Named References:**
1076
+
1077
+ - **Clarity**: Names make commands more readable (`{{ self.inputs.config }}` vs `{{ self.inputs.2 }}`)
1078
+ - **Maintainability**: Adding/removing items doesn't break indices
1079
+ - **Complex tasks**: Many inputs/outputs are easier to manage with names
1080
+
975
1081
  **Error Messages:**
976
1082
 
977
1083
  If you reference a non-existent input or output:
@@ -1011,18 +1117,21 @@ Hint: Define named inputs like: inputs: [{ src: 'file.txt' }]
1011
1117
 
1012
1118
  **Key Behaviors:**
1013
1119
 
1120
+ - **Two access methods**: Reference by name (`{{ self.inputs.name }}`) or by index (`{{ self.inputs.0 }}`)
1014
1121
  - **Template resolution**: Self-references are resolved during dependency graph planning
1015
1122
  - **Substitution order**: Variables → Dependency outputs → Self-references → Arguments/Environment
1016
- - **Fail-fast validation**: Errors are caught before execution begins
1017
- - **Clear error messages**: Lists available names if reference doesn't exist
1123
+ - **Fail-fast validation**: Errors are caught before execution begins (missing names, out-of-bounds indices)
1124
+ - **Clear error messages**: Lists available names/indices if reference doesn't exist
1018
1125
  - **Backward compatible**: Existing anonymous inputs/outputs work unchanged
1019
1126
  - **State tracking**: Works correctly with incremental execution and freshness checks
1127
+ - **Index order**: Positional indices follow YAML declaration order (0-based)
1020
1128
 
1021
1129
  **Limitations:**
1022
1130
 
1023
- - **Anonymous not referenceable**: Only named inputs/outputs can be referenced
1131
+ - **Anonymous not referenceable by name**: Anonymous inputs/outputs cannot be referenced by name (use positional index instead: `{{ self.inputs.0 }}`)
1024
1132
  - **Case sensitive**: `{{ self.inputs.Src }}` and `{{ self.inputs.src }}` are different
1025
1133
  - **Argument defaults**: Self-references in argument defaults are not supported (arguments are evaluated before self-references)
1134
+ - **No negative indices**: Python-style negative indexing (`{{ self.inputs.-1 }}`) is not supported
1026
1135
 
1027
1136
  **Use Cases:**
1028
1137
 
@@ -0,0 +1,3 @@
1
+ """
2
+ @athena: a46f4c60df41
3
+ """