tasktree 0.0.20__tar.gz → 0.0.21__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 (76) hide show
  1. {tasktree-0.0.20 → tasktree-0.0.21}/.claude/settings.local.json +3 -1
  2. {tasktree-0.0.20 → tasktree-0.0.21}/.gitignore +1 -2
  3. {tasktree-0.0.20 → tasktree-0.0.21}/CLAUDE.md +20 -5
  4. {tasktree-0.0.20 → tasktree-0.0.21}/PKG-INFO +135 -7
  5. {tasktree-0.0.20 → tasktree-0.0.21}/README.md +134 -6
  6. tasktree-0.0.21/__init__.py +3 -0
  7. tasktree-0.0.21/example/tasktree.yaml +64 -0
  8. {tasktree-0.0.20 → tasktree-0.0.21}/pyproject.toml +1 -1
  9. {tasktree-0.0.20 → tasktree-0.0.21}/schema/README.md +24 -6
  10. {tasktree-0.0.20 → tasktree-0.0.21}/schema/tasktree-schema.json +20 -5
  11. tasktree-0.0.21/src/__init__.py +4 -0
  12. {tasktree-0.0.20 → tasktree-0.0.21}/src/tasktree/__init__.py +4 -1
  13. {tasktree-0.0.20 → tasktree-0.0.21}/src/tasktree/cli.py +107 -29
  14. {tasktree-0.0.20 → tasktree-0.0.21}/src/tasktree/docker.py +87 -53
  15. {tasktree-0.0.20 → tasktree-0.0.21}/src/tasktree/executor.py +194 -129
  16. {tasktree-0.0.20 → tasktree-0.0.21}/src/tasktree/graph.py +123 -72
  17. {tasktree-0.0.20 → tasktree-0.0.21}/src/tasktree/hasher.py +68 -19
  18. {tasktree-0.0.20 → tasktree-0.0.21}/src/tasktree/parser.py +340 -229
  19. {tasktree-0.0.20 → tasktree-0.0.21}/src/tasktree/state.py +46 -17
  20. {tasktree-0.0.20 → tasktree-0.0.21}/src/tasktree/substitution.py +162 -103
  21. {tasktree-0.0.20 → tasktree-0.0.21}/src/tasktree/types.py +50 -12
  22. {tasktree-0.0.20 → tasktree-0.0.21}/tests/e2e/__init__.py +4 -1
  23. {tasktree-0.0.20 → tasktree-0.0.21}/tests/e2e/test_docker_basic.py +20 -5
  24. {tasktree-0.0.20 → tasktree-0.0.21}/tests/e2e/test_docker_environment.py +20 -5
  25. {tasktree-0.0.20 → tasktree-0.0.21}/tests/e2e/test_docker_ownership.py +16 -4
  26. {tasktree-0.0.20 → tasktree-0.0.21}/tests/e2e/test_docker_volumes.py +24 -6
  27. {tasktree-0.0.20 → tasktree-0.0.21}/tests/e2e/test_non_docker.py +16 -4
  28. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_arg_choices.py +48 -12
  29. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_arg_min_max.py +60 -15
  30. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_builtin_variables.py +52 -13
  31. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_clean_state.py +28 -7
  32. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_cli_options.py +143 -36
  33. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_dependency_execution.py +30 -8
  34. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_dependency_outputs.py +38 -8
  35. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_docker_build_args.py +12 -3
  36. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_end_to_end.py +20 -5
  37. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_exported_args.py +51 -13
  38. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_input_detection.py +24 -6
  39. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_missing_outputs.py +9 -2
  40. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_nested_imports.py +24 -6
  41. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_parameterized_deps_execution.py +24 -6
  42. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_parameterized_deps_templates.py +32 -8
  43. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_private_tasks_execution.py +40 -10
  44. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_self_references.py +544 -46
  45. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_state_persistence.py +24 -6
  46. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_variables.py +108 -27
  47. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_working_directory.py +24 -6
  48. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_cli.py +92 -23
  49. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_dependency_parsing.py +76 -19
  50. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_docker.py +183 -46
  51. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_environment_tracking.py +84 -21
  52. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_executor.py +197 -47
  53. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_graph.py +94 -22
  54. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_hasher.py +43 -10
  55. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_list_formatting.py +164 -42
  56. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_parameterized_graph.py +44 -11
  57. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_parser.py +1031 -222
  58. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_private_tasks.py +28 -7
  59. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_state.py +34 -7
  60. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_substitution.py +618 -115
  61. {tasktree-0.0.20 → tasktree-0.0.21}/tests/unit/test_types.py +144 -36
  62. tasktree-0.0.20/example/tasktree.yaml +0 -37
  63. tasktree-0.0.20/src/__init__.py +0 -0
  64. {tasktree-0.0.20 → tasktree-0.0.21}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  65. {tasktree-0.0.20 → tasktree-0.0.21}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  66. {tasktree-0.0.20 → tasktree-0.0.21}/.github/workflows/claude-code-review.yml +0 -0
  67. {tasktree-0.0.20 → tasktree-0.0.21}/.github/workflows/claude.yml +0 -0
  68. {tasktree-0.0.20 → tasktree-0.0.21}/.github/workflows/release.yml +0 -0
  69. {tasktree-0.0.20 → tasktree-0.0.21}/.github/workflows/test.yml +0 -0
  70. {tasktree-0.0.20 → tasktree-0.0.21}/.github/workflows/validate-pipx-install.yml +0 -0
  71. {tasktree-0.0.20 → tasktree-0.0.21}/.python-version +0 -0
  72. {tasktree-0.0.20 → tasktree-0.0.21}/example/source.txt +0 -0
  73. {tasktree-0.0.20 → tasktree-0.0.21}/schema/vscode-settings-snippet.json +0 -0
  74. {tasktree-0.0.20 → tasktree-0.0.21}/tasktree.yaml +0 -0
  75. {tasktree-0.0.20 → tasktree-0.0.21}/tests/integration/test_parameterized_dependencies.yaml +0 -0
  76. {tasktree-0.0.20 → tasktree-0.0.21}/uv.lock +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
  }
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tasktree
3
- Version: 0.0.20
3
+ Version: 0.0.21
4
4
  Summary: A task automation tool with incremental execution
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: click>=8.1.0
@@ -835,7 +835,7 @@ Hint: Define named outputs like: outputs: [{ missing: 'path/to/file' }]
835
835
 
836
836
  ### Self-References
837
837
 
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.
838
+ 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
839
 
840
840
  **Named Inputs and Outputs:**
841
841
 
@@ -862,8 +862,10 @@ tasks:
862
862
 
863
863
  - **Defining named inputs**: `inputs: [{ name: "path/to/file" }]`
864
864
  - **Defining named outputs**: `outputs: [{ name: "path/to/file" }]`
865
- - **Referencing inputs**: `{{ self.inputs.input_name }}`
866
- - **Referencing outputs**: `{{ self.outputs.output_name }}`
865
+ - **Defining anonymous inputs**: `inputs: ["path/to/file"]`
866
+ - **Defining anonymous outputs**: `outputs: ["path/to/file"]`
867
+ - **Referencing by name**: `{{ self.inputs.input_name }}` or `{{ self.outputs.output_name }}`
868
+ - **Referencing by index**: `{{ self.inputs.0 }}` or `{{ self.outputs.1 }}` (0-based)
867
869
  - **Mixed format**: Can combine named and anonymous inputs/outputs in the same task
868
870
 
869
871
  **Why Use Self-References?**
@@ -972,6 +974,129 @@ tasks:
972
974
  cmd: build-tool --config {{ self.inputs.config }} --output {{ self.outputs.binary }}
973
975
  ```
974
976
 
977
+ **Positional Index References:**
978
+
979
+ 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:
980
+
981
+ - **Anonymous inputs/outputs**: Reference items that don't have names
982
+ - **Simple sequential access**: When order is more important than naming
983
+ - **Mixed with named access**: Use both styles in the same task
984
+
985
+ **Syntax:**
986
+
987
+ ```yaml
988
+ tasks:
989
+ process:
990
+ inputs: ["file1.txt", "file2.txt", "file3.txt"]
991
+ outputs: ["output1.txt", "output2.txt"]
992
+ cmd: |
993
+ cat {{ self.inputs.0 }} {{ self.inputs.1 }} > {{ self.outputs.0 }}
994
+ cat {{ self.inputs.2 }} > {{ self.outputs.1 }}
995
+ ```
996
+
997
+ Indices follow YAML declaration order, starting from 0 (zero-based indexing):
998
+ - First input/output = index 0
999
+ - Second input/output = index 1
1000
+ - Third input/output = index 2, etc.
1001
+
1002
+ **Works with Both Named and Anonymous:**
1003
+
1004
+ ```yaml
1005
+ tasks:
1006
+ build:
1007
+ inputs:
1008
+ - config: "build.yaml" # Index 0, also accessible as {{ self.inputs.config }}
1009
+ - "src/**/*.c" # Index 1, ONLY accessible as {{ self.inputs.1 }}
1010
+ - headers: "include/*.h" # Index 2, also accessible as {{ self.inputs.headers }}
1011
+ outputs:
1012
+ - "dist/app.js" # Index 0
1013
+ - bundle: "dist/bundle.js" # Index 1, also accessible as {{ self.outputs.bundle }}
1014
+ cmd: |
1015
+ # Mix positional and named references
1016
+ build-tool \
1017
+ --config {{ self.inputs.0 }} \
1018
+ --sources {{ self.inputs.1 }} \
1019
+ --headers {{ self.inputs.headers }} \
1020
+ --output {{ self.outputs.bundle }}
1021
+ ```
1022
+
1023
+ **Same Item, Multiple Ways:**
1024
+
1025
+ Named items can be accessed by both name and index:
1026
+
1027
+ ```yaml
1028
+ tasks:
1029
+ copy:
1030
+ inputs:
1031
+ - source: data.txt
1032
+ cmd: |
1033
+ # These are equivalent:
1034
+ cat {{ self.inputs.source }} > copy1.txt
1035
+ cat {{ self.inputs.0 }} > copy2.txt
1036
+ ```
1037
+
1038
+ **With Variables and Arguments:**
1039
+
1040
+ Positional references work with variable-expanded paths:
1041
+
1042
+ ```yaml
1043
+ variables:
1044
+ version: "1.0"
1045
+
1046
+ tasks:
1047
+ package:
1048
+ args: [platform]
1049
+ inputs:
1050
+ - "dist/app-{{ var.version }}.js"
1051
+ - "dist/lib-{{ arg.platform }}.so"
1052
+ outputs: ["release-{{ var.version }}-{{ arg.platform }}.tar.gz"]
1053
+ cmd: tar czf {{ self.outputs.0 }} {{ self.inputs.0 }} {{ self.inputs.1 }}
1054
+ ```
1055
+
1056
+ **Index Boundaries:**
1057
+
1058
+ Indices are validated before execution:
1059
+
1060
+ ```yaml
1061
+ tasks:
1062
+ build:
1063
+ inputs: ["file1.txt", "file2.txt"]
1064
+ cmd: cat {{ self.inputs.5 }} # Error: index 5 out of bounds!
1065
+ ```
1066
+
1067
+ Error message:
1068
+ ```
1069
+ Task 'build' references input index '5' but only has 2 inputs (indices 0-1)
1070
+ ```
1071
+
1072
+ **Empty Lists:**
1073
+
1074
+ Referencing an index when no inputs/outputs exist:
1075
+
1076
+ ```yaml
1077
+ tasks:
1078
+ generate:
1079
+ cmd: echo "test" > {{ self.outputs.0 }} # Error: no outputs defined!
1080
+ ```
1081
+
1082
+ Error message:
1083
+ ```
1084
+ Task 'generate' references output index '0' but has no outputs defined
1085
+ ```
1086
+
1087
+ **When to Use Index References:**
1088
+
1089
+ - **Anonymous items**: Only way to reference inputs/outputs without names
1090
+ - **Order-based processing**: When the sequence matters more than naming
1091
+ - **Simple tasks**: Quick access without defining names
1092
+ - **Compatibility**: Accessing items in legacy YAML that uses anonymous format
1093
+
1094
+ **When to Use Named References:**
1095
+
1096
+ - **Clarity**: Names make commands more readable (`{{ self.inputs.config }}` vs `{{ self.inputs.2 }}`)
1097
+ - **Maintainability**: Adding/removing items doesn't break indices
1098
+ - **Complex tasks**: Many inputs/outputs are easier to manage with names
1099
+
975
1100
  **Error Messages:**
976
1101
 
977
1102
  If you reference a non-existent input or output:
@@ -1011,18 +1136,21 @@ Hint: Define named inputs like: inputs: [{ src: 'file.txt' }]
1011
1136
 
1012
1137
  **Key Behaviors:**
1013
1138
 
1139
+ - **Two access methods**: Reference by name (`{{ self.inputs.name }}`) or by index (`{{ self.inputs.0 }}`)
1014
1140
  - **Template resolution**: Self-references are resolved during dependency graph planning
1015
1141
  - **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
1142
+ - **Fail-fast validation**: Errors are caught before execution begins (missing names, out-of-bounds indices)
1143
+ - **Clear error messages**: Lists available names/indices if reference doesn't exist
1018
1144
  - **Backward compatible**: Existing anonymous inputs/outputs work unchanged
1019
1145
  - **State tracking**: Works correctly with incremental execution and freshness checks
1146
+ - **Index order**: Positional indices follow YAML declaration order (0-based)
1020
1147
 
1021
1148
  **Limitations:**
1022
1149
 
1023
- - **Anonymous not referenceable**: Only named inputs/outputs can be referenced
1150
+ - **Anonymous not referenceable by name**: Anonymous inputs/outputs cannot be referenced by name (use positional index instead: `{{ self.inputs.0 }}`)
1024
1151
  - **Case sensitive**: `{{ self.inputs.Src }}` and `{{ self.inputs.src }}` are different
1025
1152
  - **Argument defaults**: Self-references in argument defaults are not supported (arguments are evaluated before self-references)
1153
+ - **No negative indices**: Python-style negative indexing (`{{ self.inputs.-1 }}`) is not supported
1026
1154
 
1027
1155
  **Use Cases:**
1028
1156
 
@@ -820,7 +820,7 @@ Hint: Define named outputs like: outputs: [{ missing: 'path/to/file' }]
820
820
 
821
821
  ### Self-References
822
822
 
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.
823
+ 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
824
 
825
825
  **Named Inputs and Outputs:**
826
826
 
@@ -847,8 +847,10 @@ tasks:
847
847
 
848
848
  - **Defining named inputs**: `inputs: [{ name: "path/to/file" }]`
849
849
  - **Defining named outputs**: `outputs: [{ name: "path/to/file" }]`
850
- - **Referencing inputs**: `{{ self.inputs.input_name }}`
851
- - **Referencing outputs**: `{{ self.outputs.output_name }}`
850
+ - **Defining anonymous inputs**: `inputs: ["path/to/file"]`
851
+ - **Defining anonymous outputs**: `outputs: ["path/to/file"]`
852
+ - **Referencing by name**: `{{ self.inputs.input_name }}` or `{{ self.outputs.output_name }}`
853
+ - **Referencing by index**: `{{ self.inputs.0 }}` or `{{ self.outputs.1 }}` (0-based)
852
854
  - **Mixed format**: Can combine named and anonymous inputs/outputs in the same task
853
855
 
854
856
  **Why Use Self-References?**
@@ -957,6 +959,129 @@ tasks:
957
959
  cmd: build-tool --config {{ self.inputs.config }} --output {{ self.outputs.binary }}
958
960
  ```
959
961
 
962
+ **Positional Index References:**
963
+
964
+ 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:
965
+
966
+ - **Anonymous inputs/outputs**: Reference items that don't have names
967
+ - **Simple sequential access**: When order is more important than naming
968
+ - **Mixed with named access**: Use both styles in the same task
969
+
970
+ **Syntax:**
971
+
972
+ ```yaml
973
+ tasks:
974
+ process:
975
+ inputs: ["file1.txt", "file2.txt", "file3.txt"]
976
+ outputs: ["output1.txt", "output2.txt"]
977
+ cmd: |
978
+ cat {{ self.inputs.0 }} {{ self.inputs.1 }} > {{ self.outputs.0 }}
979
+ cat {{ self.inputs.2 }} > {{ self.outputs.1 }}
980
+ ```
981
+
982
+ Indices follow YAML declaration order, starting from 0 (zero-based indexing):
983
+ - First input/output = index 0
984
+ - Second input/output = index 1
985
+ - Third input/output = index 2, etc.
986
+
987
+ **Works with Both Named and Anonymous:**
988
+
989
+ ```yaml
990
+ tasks:
991
+ build:
992
+ inputs:
993
+ - config: "build.yaml" # Index 0, also accessible as {{ self.inputs.config }}
994
+ - "src/**/*.c" # Index 1, ONLY accessible as {{ self.inputs.1 }}
995
+ - headers: "include/*.h" # Index 2, also accessible as {{ self.inputs.headers }}
996
+ outputs:
997
+ - "dist/app.js" # Index 0
998
+ - bundle: "dist/bundle.js" # Index 1, also accessible as {{ self.outputs.bundle }}
999
+ cmd: |
1000
+ # Mix positional and named references
1001
+ build-tool \
1002
+ --config {{ self.inputs.0 }} \
1003
+ --sources {{ self.inputs.1 }} \
1004
+ --headers {{ self.inputs.headers }} \
1005
+ --output {{ self.outputs.bundle }}
1006
+ ```
1007
+
1008
+ **Same Item, Multiple Ways:**
1009
+
1010
+ Named items can be accessed by both name and index:
1011
+
1012
+ ```yaml
1013
+ tasks:
1014
+ copy:
1015
+ inputs:
1016
+ - source: data.txt
1017
+ cmd: |
1018
+ # These are equivalent:
1019
+ cat {{ self.inputs.source }} > copy1.txt
1020
+ cat {{ self.inputs.0 }} > copy2.txt
1021
+ ```
1022
+
1023
+ **With Variables and Arguments:**
1024
+
1025
+ Positional references work with variable-expanded paths:
1026
+
1027
+ ```yaml
1028
+ variables:
1029
+ version: "1.0"
1030
+
1031
+ tasks:
1032
+ package:
1033
+ args: [platform]
1034
+ inputs:
1035
+ - "dist/app-{{ var.version }}.js"
1036
+ - "dist/lib-{{ arg.platform }}.so"
1037
+ outputs: ["release-{{ var.version }}-{{ arg.platform }}.tar.gz"]
1038
+ cmd: tar czf {{ self.outputs.0 }} {{ self.inputs.0 }} {{ self.inputs.1 }}
1039
+ ```
1040
+
1041
+ **Index Boundaries:**
1042
+
1043
+ Indices are validated before execution:
1044
+
1045
+ ```yaml
1046
+ tasks:
1047
+ build:
1048
+ inputs: ["file1.txt", "file2.txt"]
1049
+ cmd: cat {{ self.inputs.5 }} # Error: index 5 out of bounds!
1050
+ ```
1051
+
1052
+ Error message:
1053
+ ```
1054
+ Task 'build' references input index '5' but only has 2 inputs (indices 0-1)
1055
+ ```
1056
+
1057
+ **Empty Lists:**
1058
+
1059
+ Referencing an index when no inputs/outputs exist:
1060
+
1061
+ ```yaml
1062
+ tasks:
1063
+ generate:
1064
+ cmd: echo "test" > {{ self.outputs.0 }} # Error: no outputs defined!
1065
+ ```
1066
+
1067
+ Error message:
1068
+ ```
1069
+ Task 'generate' references output index '0' but has no outputs defined
1070
+ ```
1071
+
1072
+ **When to Use Index References:**
1073
+
1074
+ - **Anonymous items**: Only way to reference inputs/outputs without names
1075
+ - **Order-based processing**: When the sequence matters more than naming
1076
+ - **Simple tasks**: Quick access without defining names
1077
+ - **Compatibility**: Accessing items in legacy YAML that uses anonymous format
1078
+
1079
+ **When to Use Named References:**
1080
+
1081
+ - **Clarity**: Names make commands more readable (`{{ self.inputs.config }}` vs `{{ self.inputs.2 }}`)
1082
+ - **Maintainability**: Adding/removing items doesn't break indices
1083
+ - **Complex tasks**: Many inputs/outputs are easier to manage with names
1084
+
960
1085
  **Error Messages:**
961
1086
 
962
1087
  If you reference a non-existent input or output:
@@ -996,18 +1121,21 @@ Hint: Define named inputs like: inputs: [{ src: 'file.txt' }]
996
1121
 
997
1122
  **Key Behaviors:**
998
1123
 
1124
+ - **Two access methods**: Reference by name (`{{ self.inputs.name }}`) or by index (`{{ self.inputs.0 }}`)
999
1125
  - **Template resolution**: Self-references are resolved during dependency graph planning
1000
1126
  - **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
1127
+ - **Fail-fast validation**: Errors are caught before execution begins (missing names, out-of-bounds indices)
1128
+ - **Clear error messages**: Lists available names/indices if reference doesn't exist
1003
1129
  - **Backward compatible**: Existing anonymous inputs/outputs work unchanged
1004
1130
  - **State tracking**: Works correctly with incremental execution and freshness checks
1131
+ - **Index order**: Positional indices follow YAML declaration order (0-based)
1005
1132
 
1006
1133
  **Limitations:**
1007
1134
 
1008
- - **Anonymous not referenceable**: Only named inputs/outputs can be referenced
1135
+ - **Anonymous not referenceable by name**: Anonymous inputs/outputs cannot be referenced by name (use positional index instead: `{{ self.inputs.0 }}`)
1009
1136
  - **Case sensitive**: `{{ self.inputs.Src }}` and `{{ self.inputs.src }}` are different
1010
1137
  - **Argument defaults**: Self-references in argument defaults are not supported (arguments are evaluated before self-references)
1138
+ - **No negative indices**: Python-style negative indexing (`{{ self.inputs.-1 }}`) is not supported
1011
1139
 
1012
1140
  **Use Cases:**
1013
1141
 
@@ -0,0 +1,3 @@
1
+ """
2
+ @athena: a46f4c60df41
3
+ """
@@ -0,0 +1,64 @@
1
+ variables:
2
+ log_level: { env: LOG_LEVEL, default: "info" }
3
+
4
+ tasks:
5
+ build:
6
+ desc: Building outputs (imagine this is a call to Cargo, or gcc, or something)
7
+ args:
8
+ - build_type:
9
+ choices: [ "debug", "release", "{{ var.log_level }}" ]
10
+ default: "{{ var.log_level }}"
11
+ - target:
12
+ type: str
13
+ choices:
14
+ - "x86"
15
+ - "x86_64"
16
+ - "arm"
17
+ - "ppc"
18
+ default: "x86_64"
19
+ outputs: [build/**]
20
+ cmd: |
21
+ echo building for {{ arg.target }}...
22
+ cd build
23
+ echo "code and stuff" > output-{{ arg.build_type }}-{{ arg.target }}.txt
24
+
25
+ package:
26
+ desc: Create archive
27
+ deps:
28
+ - build: [ "debug", "x86" ]
29
+ - build: [ "release" ]
30
+ - build: { build_type: "release", target: "ppc" }
31
+ outputs: [archive.tar.gz]
32
+ cmd: echo "making archive.tar.gz..."
33
+
34
+ transform:
35
+ desc: Transform data using self-references (demonstrates named inputs/outputs)
36
+ inputs:
37
+ - source: build/output-debug-x86_64.txt # Named input - can reference with {{ self.inputs.source }}
38
+ - config: config.yaml # Named input
39
+ outputs:
40
+ - result: processed/result.txt # Named output - can reference with {{ self.outputs.result }}
41
+ - log: processed/transform.log # Named output
42
+ cmd: |
43
+ mkdir -p processed
44
+ echo "Processing {{ self.inputs.source }} with config {{ self.inputs.config }}" > {{ self.outputs.log }}
45
+ cat {{ self.inputs.source }} | tr '[:lower:]' '[:upper:]' > {{ self.outputs.result }}
46
+ echo "Done" >> {{ self.outputs.log }}
47
+
48
+ deploy:
49
+ desc: Deploy using dependency outputs and self-references
50
+ deps: [transform]
51
+ inputs:
52
+ - manifest: deploy-manifest.yaml
53
+ outputs:
54
+ - deployed: deployed/app.tar.gz
55
+ cmd: |
56
+ mkdir -p deployed
57
+ echo "Deploying with manifest {{ self.inputs.manifest }}"
58
+ tar czf {{ self.outputs.deployed }} {{ dep.transform.outputs.result }} {{ self.inputs.manifest }}
59
+ echo "Deployed to {{ self.outputs.deployed }}"
60
+
61
+ clean:
62
+ desc: Clean generated files
63
+ inputs: [ archive.tar.gz ]
64
+ cmd: rm -f archive.tar.gz
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tasktree"
3
- version = "0.0.20"
3
+ version = "0.0.21"
4
4
  description = "A task automation tool with incremental execution"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -62,11 +62,14 @@ check-jsonschema --schemafile schema/tasktree-schema.json tasktree.yaml
62
62
 
63
63
  The schema validates:
64
64
 
65
- - **Top-level structure**: Only `imports`, `environments`, and `tasks` are allowed at root
65
+ - **Top-level structure**: Only `imports`, `environments`, `variables`, and `tasks` are allowed at root
66
66
  - **Required fields**: Tasks must have a `cmd` field
67
67
  - **Field types**: Ensures strings, arrays, and objects are used correctly
68
68
  - **Naming patterns**: Task names and namespaces must match `^[a-zA-Z][a-zA-Z0-9_-]*$`
69
- - **Environment requirements**: Environments must specify a `shell`
69
+ - **Named inputs/outputs**: Supports both anonymous (strings) and named (objects) format
70
+ - **Self-references**: Named inputs/outputs can be referenced with `{{ self.inputs.name }}` and `{{ self.outputs.name }}`
71
+ - **Dependency outputs**: Named outputs can be referenced with `{{ dep.task.outputs.name }}`
72
+ - **Environment requirements**: Environments must specify a `shell` (or `dockerfile` for Docker environments)
70
73
 
71
74
  ## Example
72
75
 
@@ -85,25 +88,40 @@ tasks:
85
88
  build:
86
89
  desc: Build the application
87
90
  deps: [base.setup]
88
- inputs: ["src/**/*.rs"]
91
+ inputs:
92
+ - sources: "src/**/*.rs" # Named input - can use {{ self.inputs.sources }}
89
93
  outputs:
90
94
  - binary: target/release/bin # Named output - can be referenced
91
95
  - target/release/bin.map # Anonymous output
92
- cmd: cargo build --release
96
+ cmd: cargo build --release --manifest-path {{ self.inputs.sources }}/../Cargo.toml
93
97
 
94
98
  test:
95
99
  desc: Run tests
96
100
  deps: [build]
97
101
  cmd: cargo test
98
102
 
103
+ package:
104
+ desc: Package the application
105
+ deps: [build]
106
+ inputs:
107
+ - manifest: package.yaml # Named input
108
+ outputs:
109
+ - archive: dist/app.tar.gz # Named output
110
+ cmd: |
111
+ mkdir -p dist
112
+ # Use self-references for own inputs/outputs
113
+ tar czf {{ self.outputs.archive }} \
114
+ {{ dep.build.outputs.binary }} \
115
+ {{ self.inputs.manifest }}
116
+
99
117
  deploy:
100
118
  desc: Deploy to environment
101
- deps: [build]
119
+ deps: [package]
102
120
  args: [environment, region=us-west-1]
103
121
  cmd: |
104
122
  echo "Deploying to {{ arg.environment }} in {{ arg.region }}"
105
123
  # Reference named output from dependency
106
- scp {{ dep.build.outputs.binary }} server:/opt/
124
+ scp {{ dep.package.outputs.archive }} server:/opt/
107
125
  ./deploy.sh {{ arg.environment }} {{ arg.region }}
108
126
  ```
109
127
 
@@ -244,21 +244,36 @@
244
244
  ]
245
245
  },
246
246
  "inputs": {
247
- "description": "Input file patterns (glob supported). Task re-runs if inputs change.",
247
+ "description": "Input file patterns (glob supported). Supports anonymous inputs (strings) and named inputs (objects). Named inputs can be referenced in the same task using {{ self.inputs.name }}. Task re-runs if inputs change.",
248
248
  "oneOf": [
249
249
  {
250
250
  "type": "array",
251
251
  "items": {
252
- "type": "string"
252
+ "oneOf": [
253
+ {
254
+ "type": "string",
255
+ "description": "Anonymous input file pattern"
256
+ },
257
+ {
258
+ "type": "object",
259
+ "description": "Named input (format: { name: 'path/to/file' })",
260
+ "minProperties": 1,
261
+ "maxProperties": 1,
262
+ "additionalProperties": {
263
+ "type": "string"
264
+ }
265
+ }
266
+ ]
253
267
  }
254
268
  },
255
269
  {
256
- "type": "string"
270
+ "type": "string",
271
+ "description": "Single anonymous input file pattern"
257
272
  }
258
273
  ]
259
274
  },
260
275
  "outputs": {
261
- "description": "Output file patterns (glob supported). Supports anonymous outputs (strings) and named outputs (objects). Named outputs can be referenced by dependent tasks using {{ dep.task.outputs.name }}. Task re-runs if outputs missing.",
276
+ "description": "Output file patterns (glob supported). Supports anonymous outputs (strings) and named outputs (objects). Named outputs can be referenced by dependent tasks using {{ dep.task.outputs.name }} or in the same task using {{ self.outputs.name }}. Task re-runs if outputs missing.",
262
277
  "oneOf": [
263
278
  {
264
279
  "type": "array",
@@ -352,7 +367,7 @@
352
367
  },
353
368
  "cmd": {
354
369
  "type": "string",
355
- "description": "Shell command to execute. Use {{arg}} for parameter substitution."
370
+ "description": "Shell command to execute. Supports template substitution: {{ arg.name }} for arguments, {{ var.name }} for variables, {{ env.NAME }} for environment variables, {{ dep.task.outputs.name }} for dependency outputs, {{ self.inputs.name }} and {{ self.outputs.name }} for own inputs/outputs, {{ tt.* }} for built-in variables."
356
371
  }
357
372
  },
358
373
  "required": ["cmd"],
@@ -0,0 +1,4 @@
1
+ """
2
+ A task automation tool that combines simple command execution with dependency tracking and incremental execution.
3
+ @athena: e94406c67571
4
+ """
@@ -1,4 +1,7 @@
1
- """Task Tree - A task automation tool with intelligent incremental execution."""
1
+ """
2
+ Task Tree - A task automation tool with intelligent incremental execution.
3
+ @athena: 1b4a97c4bc42
4
+ """
2
5
 
3
6
  try:
4
7
  from importlib.metadata import version