tasktree 0.0.19__tar.gz → 0.0.20__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 (74) hide show
  1. {tasktree-0.0.19 → tasktree-0.0.20}/.claude/settings.local.json +2 -1
  2. {tasktree-0.0.19 → tasktree-0.0.20}/.gitignore +4 -0
  3. tasktree-0.0.20/CLAUDE.md +205 -0
  4. {tasktree-0.0.19 → tasktree-0.0.20}/PKG-INFO +236 -1
  5. {tasktree-0.0.19 → tasktree-0.0.20}/README.md +235 -0
  6. {tasktree-0.0.19 → tasktree-0.0.20}/pyproject.toml +1 -1
  7. {tasktree-0.0.19 → tasktree-0.0.20}/schema/tasktree-schema.json +99 -33
  8. {tasktree-0.0.19 → tasktree-0.0.20}/src/tasktree/__init__.py +2 -0
  9. {tasktree-0.0.19 → tasktree-0.0.20}/src/tasktree/cli.py +12 -4
  10. {tasktree-0.0.19 → tasktree-0.0.20}/src/tasktree/executor.py +14 -2
  11. {tasktree-0.0.19 → tasktree-0.0.20}/src/tasktree/graph.py +61 -0
  12. {tasktree-0.0.19 → tasktree-0.0.20}/src/tasktree/parser.py +68 -4
  13. {tasktree-0.0.19 → tasktree-0.0.20}/src/tasktree/substitution.py +75 -2
  14. {tasktree-0.0.19 → tasktree-0.0.20}/tasktree.yaml +1 -1
  15. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_dependency_outputs.py +2 -2
  16. tasktree-0.0.20/tests/integration/test_self_references.py +1331 -0
  17. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_graph.py +110 -0
  18. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_parser.py +205 -0
  19. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_substitution.py +268 -31
  20. tasktree-0.0.19/CLAUDE.md +0 -70
  21. {tasktree-0.0.19/.github/ISSUE_TEMPLATES → tasktree-0.0.20/.github/ISSUE_TEMPLATE}/bug_report.yml +0 -0
  22. {tasktree-0.0.19/.github/ISSUE_TEMPLATES → tasktree-0.0.20/.github/ISSUE_TEMPLATE}/feature_request.yml +0 -0
  23. {tasktree-0.0.19 → tasktree-0.0.20}/.github/workflows/claude-code-review.yml +0 -0
  24. {tasktree-0.0.19 → tasktree-0.0.20}/.github/workflows/claude.yml +0 -0
  25. {tasktree-0.0.19 → tasktree-0.0.20}/.github/workflows/release.yml +0 -0
  26. {tasktree-0.0.19 → tasktree-0.0.20}/.github/workflows/test.yml +0 -0
  27. {tasktree-0.0.19 → tasktree-0.0.20}/.github/workflows/validate-pipx-install.yml +0 -0
  28. {tasktree-0.0.19 → tasktree-0.0.20}/.python-version +0 -0
  29. {tasktree-0.0.19 → tasktree-0.0.20}/example/source.txt +0 -0
  30. {tasktree-0.0.19 → tasktree-0.0.20}/example/tasktree.yaml +0 -0
  31. {tasktree-0.0.19 → tasktree-0.0.20}/schema/README.md +0 -0
  32. {tasktree-0.0.19 → tasktree-0.0.20}/schema/vscode-settings-snippet.json +0 -0
  33. {tasktree-0.0.19 → tasktree-0.0.20}/src/__init__.py +0 -0
  34. {tasktree-0.0.19 → tasktree-0.0.20}/src/tasktree/docker.py +0 -0
  35. {tasktree-0.0.19 → tasktree-0.0.20}/src/tasktree/hasher.py +0 -0
  36. {tasktree-0.0.19 → tasktree-0.0.20}/src/tasktree/state.py +0 -0
  37. {tasktree-0.0.19 → tasktree-0.0.20}/src/tasktree/types.py +0 -0
  38. {tasktree-0.0.19 → tasktree-0.0.20}/tests/e2e/__init__.py +0 -0
  39. {tasktree-0.0.19 → tasktree-0.0.20}/tests/e2e/test_docker_basic.py +0 -0
  40. {tasktree-0.0.19 → tasktree-0.0.20}/tests/e2e/test_docker_environment.py +0 -0
  41. {tasktree-0.0.19 → tasktree-0.0.20}/tests/e2e/test_docker_ownership.py +0 -0
  42. {tasktree-0.0.19 → tasktree-0.0.20}/tests/e2e/test_docker_volumes.py +0 -0
  43. {tasktree-0.0.19 → tasktree-0.0.20}/tests/e2e/test_non_docker.py +0 -0
  44. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_arg_choices.py +0 -0
  45. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_arg_min_max.py +0 -0
  46. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_builtin_variables.py +0 -0
  47. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_clean_state.py +0 -0
  48. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_cli_options.py +0 -0
  49. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_dependency_execution.py +0 -0
  50. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_docker_build_args.py +0 -0
  51. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_end_to_end.py +0 -0
  52. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_exported_args.py +0 -0
  53. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_input_detection.py +0 -0
  54. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_missing_outputs.py +0 -0
  55. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_nested_imports.py +0 -0
  56. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_parameterized_dependencies.yaml +0 -0
  57. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_parameterized_deps_execution.py +0 -0
  58. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_parameterized_deps_templates.py +0 -0
  59. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_private_tasks_execution.py +0 -0
  60. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_state_persistence.py +0 -0
  61. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_variables.py +0 -0
  62. {tasktree-0.0.19 → tasktree-0.0.20}/tests/integration/test_working_directory.py +0 -0
  63. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_cli.py +0 -0
  64. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_dependency_parsing.py +0 -0
  65. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_docker.py +0 -0
  66. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_environment_tracking.py +0 -0
  67. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_executor.py +0 -0
  68. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_hasher.py +0 -0
  69. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_list_formatting.py +0 -0
  70. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_parameterized_graph.py +0 -0
  71. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_private_tasks.py +0 -0
  72. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_state.py +0 -0
  73. {tasktree-0.0.19 → tasktree-0.0.20}/tests/unit/test_types.py +0 -0
  74. {tasktree-0.0.19 → tasktree-0.0.20}/uv.lock +0 -0
@@ -14,7 +14,8 @@
14
14
  "Bash(ls:*)",
15
15
  "Bash(cat:*)",
16
16
  "Bash(tt --help:*)",
17
- "Bash(head:*)"
17
+ "Bash(head:*)",
18
+ "Bash(wc:*)"
18
19
  ]
19
20
  }
20
21
  }
@@ -154,5 +154,9 @@ example/output.txt
154
154
  example/archive.tar.gz
155
155
  example/.tasktree-state
156
156
 
157
+ # Requirements files copied from GitHub issues (for driving Claude Code)
158
+ requirements/*
159
+
157
160
  # UV lock file (optional - some prefer to commit this)
158
161
  # uv.lock
162
+
@@ -0,0 +1,205 @@
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 and constraints (int, float, bool, str, path, datetime, hostname, email, IP addresses)
13
+ - **File imports**: Task definitions can be split across multiple files and namespaced
14
+ - **Environment definitions**: Named execution environments (shell with custom configuration, or Docker containers with full image building support)
15
+ - **Template substitution**: Rich variable system with access to arguments, environment variables, built-in variables, dependency outputs, and task inputs/outputs
16
+ - **Docker support**: Full Docker integration with volume mounts, port mappings, build arguments, and user mapping
17
+ - **Named inputs/outputs**: Reference specific inputs and outputs by name for better clarity and DRY principles
18
+
19
+ ## IMPORTANT! Development philosophies
20
+
21
+ ### Small, incremental changes
22
+ The project team requires that each commit contains a small number of changes. Ideally, just the addition of a new line or statement in the code and an accompanying unit test (or tests) that validate the functionality of that line.
23
+
24
+ Tickets and features should be iteratively broken down until they can be implemented as a series of small commits.
25
+
26
+ When prompting the user to move to the next stage, estimate the size of the work and indicate whether you think there is sufficient usage to implement the next stage, or not. ("estimated required tokens: X, remaining usage in this session: Y tokens")
27
+
28
+ #### Local Claude Code work
29
+ When working locally on a user's machine, Claude Code should NEVER make commits - only stop and ask the user to review and commit, before carrying on with the next incremental change.
30
+
31
+ #### GitHub Claude Code integration work
32
+ When working as a GitHub agent, claude should still BREAK DOWN THE TASK into small, incremental commits, but commit those changes to the feature branch as they are made. GitHub integration Claude Code does not need to ask for permission to commit each change.
33
+
34
+ ### Write tests, not ad hoc test scripts
35
+ If you are checking that a feature you are implementing has been implemented correctly, DO NOT write a bespoke test script to check the output of the app with the new functionality. INSTEAD, write a unit/integration/end-to-end test that will confirm you have correctly implemented the feature and RUN JUST THAT TEST. If it passes, you have implemented things correctly; and you can either carry on with additional parts of the feature, or run all the tests to ensure no regressions.
36
+
37
+ It is still permissible to write and run an ad hoc script to investigate/confirm the current behaviour. Although, it is better to first search for a test that does the thing that you're investigating. If one exists and is passing: then the app does the thing.
38
+
39
+ ### Testas we go!
40
+ We do not plan to implement all the code (maybe even with unit tests) and then write a bunch of integration tests. We PLAN END-TO-END incremental changes. This will involve writing high-level test of the functionality as early as possible, to ensure that the new feature is progressing as expected.
41
+
42
+ ### Try to be efficient with token usage
43
+ Your sponsor is not made of money! Try to minimise token useage, so that we can maximise the effectiveness of Claude Code on a features per token basis. Obviously, if a thing needs doing and it takes a bunch of tokens, that's just the way it is. Just try to consider/avoid profligacy!
44
+
45
+ ### Architectural philosophies
46
+
47
+ - Try to follow SOLID principles
48
+ - Try to follow the advice in "Clean Code", by Robert Martin.
49
+ - Try to keep algorithmic logic abstracted from the TYPES that the logic can be run on. This is a restatement of the Liskov Substitution principle covered in the SOLID principles
50
+ - **Small, named functions are preferred over comments**. If a comment on WHAT the code is doing feels warranted, then refactor that code into a function with an indicative name. Comment on WHY code is like it is are more permissible.
51
+
52
+ ## Architecture
53
+
54
+ ### Core Components
55
+
56
+ - **`src/tasktree/parser.py`** (2,415 lines): YAML recipe parsing, task and environment definitions, circular import detection, schema validation
57
+ - **`src/tasktree/executor.py`** (1,200 lines): Task execution logic, incremental execution engine, state tracking, built-in variables, subprocess management
58
+ - **`src/tasktree/cli.py`** (591 lines): Typer-based CLI with commands: `--list`, `--show`, `--tree`, `--force`, `--only`, `--dry-run`, `--verbose`
59
+ - **`src/tasktree/graph.py`** (545 lines): Dependency resolution using graphlib.TopologicalSorter, parameterized dependencies, cycle detection
60
+ - **`src/tasktree/docker.py`** (446 lines): Docker image building and container execution, user mapping, volume mounts, build args
61
+ - **`src/tasktree/substitution.py`** (374 lines): Template variable substitution engine supporting multiple prefixes (var, arg, env, tt, dep, self)
62
+ - **`src/tasktree/types.py`** (139 lines): Custom Click parameter types for argument validation (hostname, email, IP, IPv4, IPv6, datetime)
63
+ - **`src/tasktree/hasher.py`** (161 lines): Task hashing for incremental execution, cache key generation, environment definition hashing
64
+ - **`src/tasktree/state.py`** (119 lines): State file management (.tasktree-state), task execution state tracking
65
+
66
+ ### Key Dependencies
67
+
68
+ - **PyYAML**: For recipe parsing
69
+ - **Typer, Click, Rich**: For CLI and rich terminal output
70
+ - **graphlib.TopologicalSorter**: For dependency resolution
71
+ - **pathlib**: For file operations and glob expansion
72
+ - **docker (Python SDK)**: For Docker image building and container management
73
+ - **jsonschema**: For YAML schema validation
74
+
75
+ ## Development Commands
76
+
77
+ ### Testing
78
+ ```bash
79
+ python3 -m pytest tests/
80
+ ```
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
86
+
87
+ ### Running the Application
88
+ ```bash
89
+ python3 main.py [task-names] [--options]
90
+ ```
91
+
92
+ ### Package Management
93
+ This project uses `uv` for dependency management (indicated by `uv.lock` file). Configuration is in `pyproject.toml`.
94
+
95
+ ## State Management
96
+
97
+ The application uses a `.tasktree-state` file at the project root to track:
98
+ - When tasks last ran
99
+ - Timestamps of input files at execution time
100
+ - Task hashes based on command, outputs, working directory, arguments, and environment definitions
101
+ - Cached results for incremental execution
102
+
103
+ ## Testing Approach
104
+
105
+ The project uses Python's built-in `unittest` framework with:
106
+ - `unittest.mock` for mocking subprocess calls and external dependencies
107
+ - `click.testing.CliRunner` for CLI testing
108
+ - Comprehensive coverage across unit, integration, and E2E test layers
109
+ - Tests verify subprocess calls, Docker operations, state management, and CLI behavior
110
+
111
+ ## Task Definition Format
112
+
113
+ Tasks are defined in YAML with the following structure:
114
+ ```yaml
115
+ tasks:
116
+ task-name:
117
+ desc: Description (optional)
118
+ deps: [dependency-tasks] # Can include parameterized dependencies: dep-name(arg1, key=value)
119
+ inputs:
120
+ - pattern1 # Anonymous glob patterns
121
+ - name: glob-pattern # Named inputs for reference
122
+ outputs:
123
+ - pattern1 # Anonymous glob patterns
124
+ - name: path-or-pattern # Named outputs for reference
125
+ working_dir: execution-directory
126
+ env: environment-name # Reference to environment definition
127
+ args:
128
+ - name: arg-name
129
+ type: str|int|float|bool|path|datetime|hostname|email|ip|ipv4|ipv6
130
+ default: value
131
+ choices: [option1, option2] # Optional constraint
132
+ min: value # Optional for numeric types
133
+ max: value # Optional for numeric types
134
+ exported: true # Export as $ARG_NAME environment variable
135
+ cmd: shell-command # Can use {{ var.name }}, {{ arg.name }}, {{ env.NAME }}, {{ tt.* }}, {{ dep.task.outputs.name }}, {{ self.inputs.name }}
136
+ private: true # Hide from --list but still executable
137
+
138
+ environments:
139
+ env-name:
140
+ default: true # Make this the default environment
141
+ shell: /bin/bash # Shell environment
142
+ # OR
143
+ dockerfile: path/to/Dockerfile # Docker environment
144
+ context: build-context-dir
145
+ image: optional-image-name
146
+ volumes:
147
+ - host_path:container_path[:ro]
148
+ ports:
149
+ - "host:container"
150
+ build_args:
151
+ ARG_NAME: value
152
+ environment:
153
+ ENV_VAR: value
154
+
155
+ variables:
156
+ var-name: value # Simple string value
157
+ var-from-env: { env: ENV_VAR, default: fallback } # From environment
158
+ var-from-eval: { eval: "command to run" } # Runtime command evaluation
159
+ var-from-file: { read: path/to/file } # Read file contents
160
+ ```
161
+
162
+ ## Built-in Variables
163
+
164
+ Tasks have access to these built-in template variables:
165
+ - `{{ tt.project_root }}`: Root directory of the project
166
+ - `{{ tt.recipe_dir }}`: Directory containing the recipe file
167
+ - `{{ tt.task_name }}`: Name of the current task
168
+ - `{{ tt.working_dir }}`: Working directory for task execution
169
+ - `{{ tt.timestamp }}`: ISO 8601 timestamp
170
+ - `{{ tt.timestamp_unix }}`: Unix timestamp
171
+ - `{{ tt.user_home }}`: User's home directory
172
+ - `{{ tt.user_name }}`: Current username
173
+
174
+ ## Key Features
175
+
176
+ ### Template Substitution
177
+ Commands and paths support template substitution with multiple prefixes:
178
+ - `{{ var.name }}`: Variables defined in the `variables` section
179
+ - `{{ arg.name }}`: Task arguments passed on command line
180
+ - `{{ env.NAME }}`: Environment variables
181
+ - `{{ tt.* }}`: Built-in variables (see above)
182
+ - `{{ dep.task_name.outputs.output_name }}`: Outputs from dependency tasks
183
+ - `{{ self.inputs.input_name }}`: Named inputs of the current task
184
+ - `{{ self.outputs.output_name }}`: Named outputs of the current task
185
+
186
+ ### Parameterized Dependencies
187
+ Tasks can pass arguments to their dependencies:
188
+ ```yaml
189
+ tasks:
190
+ caller:
191
+ deps:
192
+ - dependency(value1, key=value2) # Positional and named args
193
+ ```
194
+
195
+ ### Docker Integration
196
+ Full Docker support with:
197
+ - Dockerfile-based image building
198
+ - Volume mounts (read-only and read-write)
199
+ - Port mappings
200
+ - User mapping (run as non-root on Unix/macOS)
201
+ - Build arguments separate from shell arguments
202
+ - Environment variable injection
203
+
204
+ ### Schema Validation
205
+ The project includes JSON Schema definitions in `schema/` for validating recipe YAML files.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tasktree
3
- Version: 0.0.19
3
+ Version: 0.0.20
4
4
  Summary: A task automation tool with incremental execution
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: click>=8.1.0
@@ -833,6 +833,241 @@ Hint: Define named outputs like: outputs: [{ missing: 'path/to/file' }]
833
833
  - **Deployment pipelines**: Reference exact artifacts to deploy
834
834
  - **Configuration propagation**: Pass generated config files through build stages
835
835
 
836
+ ### Self-References
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.
839
+
840
+ **Named Inputs and Outputs:**
841
+
842
+ Just like dependency output references, inputs and outputs can have names:
843
+
844
+ ```yaml
845
+ tasks:
846
+ process:
847
+ inputs:
848
+ - src: "data/input.json" # Named input
849
+ - config: "config.yaml" # Named input
850
+ outputs:
851
+ - result: "output/result.json" # Named output
852
+ - log: "output/process.log" # Named output
853
+ cmd: |
854
+ process-tool \
855
+ --input {{ self.inputs.src }} \
856
+ --config {{ self.inputs.config }} \
857
+ --output {{ self.outputs.result }} \
858
+ --log {{ self.outputs.log }}
859
+ ```
860
+
861
+ **Syntax:**
862
+
863
+ - **Defining named inputs**: `inputs: [{ name: "path/to/file" }]`
864
+ - **Defining named outputs**: `outputs: [{ name: "path/to/file" }]`
865
+ - **Referencing inputs**: `{{ self.inputs.input_name }}`
866
+ - **Referencing outputs**: `{{ self.outputs.output_name }}`
867
+ - **Mixed format**: Can combine named and anonymous inputs/outputs in the same task
868
+
869
+ **Why Use Self-References?**
870
+
871
+ Self-references follow the DRY (Don't Repeat Yourself) principle:
872
+
873
+ ```yaml
874
+ # Without self-references - repetitive
875
+ tasks:
876
+ build:
877
+ inputs: [src/app-{{ var.version }}.c]
878
+ outputs: [build/app-{{ var.version }}.o]
879
+ cmd: gcc src/app-{{ var.version }}.c -o build/app-{{ var.version }}.o
880
+
881
+ # With self-references - DRY
882
+ tasks:
883
+ build:
884
+ inputs:
885
+ - source: src/app-{{ var.version }}.c
886
+ outputs:
887
+ - object: build/app-{{ var.version }}.o
888
+ cmd: gcc {{ self.inputs.source }} -o {{ self.outputs.object }}
889
+ ```
890
+
891
+ **Working with Variables:**
892
+
893
+ Self-references work seamlessly with variables:
894
+
895
+ ```yaml
896
+ variables:
897
+ platform: linux
898
+ arch: x86_64
899
+
900
+ tasks:
901
+ compile:
902
+ inputs:
903
+ - src: src/{{ var.platform }}/main.c
904
+ - header: include/{{ var.arch }}/defs.h
905
+ outputs:
906
+ - binary: build/{{ var.platform }}-{{ var.arch }}/app
907
+ cmd: |
908
+ gcc {{ self.inputs.src }} \
909
+ -include {{ self.inputs.header }} \
910
+ -o {{ self.outputs.binary }}
911
+ ```
912
+
913
+ Variables are evaluated first, then self-references substitute the expanded paths.
914
+
915
+ **Working with Arguments:**
916
+
917
+ Self-references work with parameterized tasks:
918
+
919
+ ```yaml
920
+ tasks:
921
+ deploy:
922
+ args: [environment]
923
+ inputs:
924
+ - config: configs/{{ arg.environment }}/app.yaml
925
+ outputs:
926
+ - deployed: deployed-{{ arg.environment }}.yaml
927
+ cmd: |
928
+ validate {{ self.inputs.config }}
929
+ deploy {{ self.inputs.config }} > {{ self.outputs.deployed }}
930
+ ```
931
+
932
+ ```bash
933
+ tt deploy production # Uses configs/production/app.yaml
934
+ tt deploy staging # Uses configs/staging/app.yaml
935
+ ```
936
+
937
+ **Working with Dependency Outputs:**
938
+
939
+ Self-references and dependency references can be used together:
940
+
941
+ ```yaml
942
+ tasks:
943
+ build:
944
+ outputs:
945
+ - artifact: dist/app.js
946
+ cmd: webpack build
947
+
948
+ package:
949
+ deps: [build]
950
+ inputs:
951
+ - manifest: package.json
952
+ outputs:
953
+ - tarball: release.tar.gz
954
+ cmd: tar czf {{ self.outputs.tarball }} \
955
+ {{ self.inputs.manifest }} \
956
+ {{ dep.build.outputs.artifact }}
957
+ ```
958
+
959
+ **Mixed Named and Anonymous:**
960
+
961
+ Tasks can mix named and anonymous inputs/outputs:
962
+
963
+ ```yaml
964
+ tasks:
965
+ build:
966
+ inputs:
967
+ - config: build.yaml # Named - can reference
968
+ - src/**/*.c # Anonymous - tracked but not referenceable
969
+ outputs:
970
+ - binary: bin/app # Named - can reference
971
+ - bin/app.debug # Anonymous - tracked but not referenceable
972
+ cmd: build-tool --config {{ self.inputs.config }} --output {{ self.outputs.binary }}
973
+ ```
974
+
975
+ **Error Messages:**
976
+
977
+ If you reference a non-existent input or output:
978
+
979
+ ```yaml
980
+ tasks:
981
+ build:
982
+ inputs:
983
+ - src: input.txt
984
+ cmd: cat {{ self.inputs.missing }} # Error!
985
+ ```
986
+
987
+ You'll get a clear error before execution:
988
+
989
+ ```
990
+ Task 'build' references input 'missing' but has no input named 'missing'.
991
+ Available named inputs: src
992
+ Hint: Define named inputs like: inputs: [{ missing: 'path/to/file' }]
993
+ ```
994
+
995
+ Similarly, if you try to reference an anonymous input:
996
+
997
+ ```yaml
998
+ tasks:
999
+ build:
1000
+ inputs: [file.txt] # Anonymous input
1001
+ cmd: cat {{ self.inputs.src }} # Error!
1002
+ ```
1003
+
1004
+ You'll get:
1005
+
1006
+ ```
1007
+ Task 'build' references input 'src' but has no input named 'src'.
1008
+ Available named inputs: (none - all inputs are anonymous)
1009
+ Hint: Define named inputs like: inputs: [{ src: 'file.txt' }]
1010
+ ```
1011
+
1012
+ **Key Behaviors:**
1013
+
1014
+ - **Template resolution**: Self-references are resolved during dependency graph planning
1015
+ - **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
1018
+ - **Backward compatible**: Existing anonymous inputs/outputs work unchanged
1019
+ - **State tracking**: Works correctly with incremental execution and freshness checks
1020
+
1021
+ **Limitations:**
1022
+
1023
+ - **Anonymous not referenceable**: Only named inputs/outputs can be referenced
1024
+ - **Case sensitive**: `{{ self.inputs.Src }}` and `{{ self.inputs.src }}` are different
1025
+ - **Argument defaults**: Self-references in argument defaults are not supported (arguments are evaluated before self-references)
1026
+
1027
+ **Use Cases:**
1028
+
1029
+ - **Eliminate repetition**: Define complex paths once, use them multiple times
1030
+ - **Variable composition**: Combine variables with self-references for clean commands
1031
+ - **Multiple inputs/outputs**: Reference specific files when tasks have many
1032
+ - **Complex build pipelines**: Keep commands readable with named artifacts
1033
+ - **Glob patterns**: Use self-references with glob patterns for dynamic inputs
1034
+
1035
+ **Example: Multi-Stage Build:**
1036
+
1037
+ ```yaml
1038
+ variables:
1039
+ version: "2.1.0"
1040
+ platform: "linux"
1041
+
1042
+ tasks:
1043
+ prepare:
1044
+ outputs:
1045
+ - builddir: build/{{ var.platform }}-{{ var.version }}
1046
+ cmd: mkdir -p {{ self.outputs.builddir }}
1047
+
1048
+ compile:
1049
+ deps: [prepare]
1050
+ inputs:
1051
+ - source: src/main.c
1052
+ - headers: include/*.h
1053
+ outputs:
1054
+ - object: build/{{ var.platform }}-{{ var.version }}/main.o
1055
+ cmd: |
1056
+ gcc -c {{ self.inputs.source }} \
1057
+ -I include \
1058
+ -o {{ self.outputs.object }}
1059
+
1060
+ link:
1061
+ deps: [compile]
1062
+ outputs:
1063
+ - executable: build/{{ var.platform }}-{{ var.version }}/app
1064
+ - symbols: build/{{ var.platform }}-{{ var.version }}/app.sym
1065
+ cmd: |
1066
+ gcc build/{{ var.platform }}-{{ var.version }}/main.o \
1067
+ -o {{ self.outputs.executable }}
1068
+ objcopy --only-keep-debug {{ self.outputs.executable }} {{ self.outputs.symbols }}
1069
+ ```
1070
+
836
1071
 
837
1072
  ### Private Tasks
838
1073