reci 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.
@@ -0,0 +1 @@
1
+ *.ipynb linguist-documentation
@@ -0,0 +1,256 @@
1
+ name: Continuous Integration (uv)
2
+ on: [push, pull_request]
3
+
4
+ # Note: Environment variables (PROJECT_NAME and vars from [tool.wads.ci.env])
5
+ # are set by the read-ci-config action in the setup job and made available
6
+ # to all subsequent jobs via GITHUB_ENV
7
+
8
+ jobs:
9
+ # First job: Read configuration from pyproject.toml
10
+ setup:
11
+ name: Read Configuration
12
+ runs-on: ubuntu-latest
13
+ outputs:
14
+ project-name: ${{ steps.config.outputs.project-name }}
15
+ python-versions: ${{ steps.config.outputs.python-versions }}
16
+ pytest-args: ${{ steps.config.outputs.pytest-args }}
17
+ coverage-enabled: ${{ steps.config.outputs.coverage-enabled }}
18
+ exclude-paths: ${{ steps.config.outputs.exclude-paths }}
19
+ test-on-windows: ${{ steps.config.outputs.test-on-windows }}
20
+ build-sdist: ${{ steps.config.outputs.build-sdist }}
21
+ build-wheel: ${{ steps.config.outputs.build-wheel }}
22
+ metrics-enabled: ${{ steps.config.outputs.metrics-enabled }}
23
+ metrics-config-path: ${{ steps.config.outputs.metrics-config-path }}
24
+ metrics-storage-branch: ${{ steps.config.outputs.metrics-storage-branch }}
25
+ metrics-python-version: ${{ steps.config.outputs.metrics-python-version }}
26
+ metrics-force-run: ${{ steps.config.outputs.metrics-force-run }}
27
+
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+
31
+ - name: Set up uv
32
+ uses: astral-sh/setup-uv@v5
33
+
34
+ - name: Set up Python
35
+ run: uv python install 3.11
36
+
37
+ - name: Read CI Config
38
+ id: config
39
+ uses: i2mint/wads/actions/read-ci-config@master
40
+ with:
41
+ pyproject-path: .
42
+
43
+ # Second job: Validation using the config
44
+ validation:
45
+ name: Validation
46
+ if: "!contains(github.event.head_commit.message, '[skip ci]')"
47
+ needs: setup
48
+ runs-on: ubuntu-latest
49
+ strategy:
50
+ matrix:
51
+ python-version: ${{ fromJson(needs.setup.outputs.python-versions) }}
52
+
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+
56
+ - name: Set up uv
57
+ uses: astral-sh/setup-uv@v5
58
+ with:
59
+ enable-cache: true
60
+
61
+ - name: Set up Python ${{ matrix.python-version }}
62
+ run: |
63
+ uv python install ${{ matrix.python-version }}
64
+ uv venv --python ${{ matrix.python-version }}
65
+
66
+ - name: Install System Dependencies
67
+ uses: i2mint/wads/actions/install-system-deps@master
68
+ with:
69
+ pyproject-path: .
70
+
71
+ - name: Install Dependencies
72
+ run: |
73
+ source .venv/bin/activate
74
+ uv pip install -e ".[dev]"
75
+
76
+ - name: Format Source Code
77
+ run: uvx ruff format .
78
+
79
+ - name: Lint Validation
80
+ run: uvx ruff check --output-format=github ${{ needs.setup.outputs.project-name }}
81
+
82
+ - name: Run Tests
83
+ run: |
84
+ source .venv/bin/activate
85
+ PYTEST_ARGS="${{ needs.setup.outputs.pytest-args }}"
86
+ EXCLUDE="${{ needs.setup.outputs.exclude-paths }}"
87
+ COVERAGE="${{ needs.setup.outputs.coverage-enabled }}"
88
+ ROOT_DIR="${{ needs.setup.outputs.project-name }}"
89
+
90
+ CMD="python -m pytest"
91
+
92
+ # Add coverage flags
93
+ if [ "$COVERAGE" = "true" ]; then
94
+ uv pip install pytest-cov
95
+ CMD="$CMD --cov=$ROOT_DIR --cov-report=term-missing"
96
+ fi
97
+
98
+ # Add doctest flags
99
+ CMD="$CMD --doctest-modules"
100
+ CMD="$CMD -o doctest_optionflags='ELLIPSIS IGNORE_EXCEPTION_DETAIL'"
101
+
102
+ # Add exclude paths
103
+ if [ -n "$EXCLUDE" ]; then
104
+ IFS=',' read -ra PATHS <<< "$EXCLUDE"
105
+ for path in "${PATHS[@]}"; do
106
+ path=$(echo "$path" | xargs)
107
+ CMD="$CMD --ignore=$path"
108
+ done
109
+ fi
110
+
111
+ # Add extra pytest args
112
+ if [ -n "$PYTEST_ARGS" ]; then
113
+ CMD="$CMD $PYTEST_ARGS"
114
+ fi
115
+
116
+ echo "Running: $CMD"
117
+ eval $CMD
118
+
119
+ - name: Track Code Metrics
120
+ if: needs.setup.outputs.metrics-enabled == 'true'
121
+ uses: i2mint/umpyre/actions/track-metrics@master
122
+ continue-on-error: true
123
+ with:
124
+ github-token: ${{ secrets.GITHUB_TOKEN }}
125
+ config-path: ${{ needs.setup.outputs.metrics-config-path }}
126
+ storage-branch: ${{ needs.setup.outputs.metrics-storage-branch }}
127
+ python-version: ${{ needs.setup.outputs.metrics-python-version }}
128
+ force-run: ${{ needs.setup.outputs.metrics-force-run }}
129
+
130
+ # Optional Windows testing (if enabled in config)
131
+ windows-validation:
132
+ name: Windows Tests
133
+ if: "!contains(github.event.head_commit.message, '[skip ci]') && needs.setup.outputs.test-on-windows == 'true'"
134
+ needs: setup
135
+ runs-on: windows-latest
136
+ continue-on-error: true
137
+
138
+ steps:
139
+ - uses: actions/checkout@v4
140
+
141
+ - name: Set up uv
142
+ uses: astral-sh/setup-uv@v5
143
+ with:
144
+ enable-cache: true
145
+
146
+ - name: Set up Python
147
+ run: |
148
+ uv python install ${{ fromJson(needs.setup.outputs.python-versions)[0] }}
149
+ uv venv
150
+
151
+ - name: Install System Dependencies
152
+ uses: i2mint/wads/actions/install-system-deps@master
153
+ with:
154
+ pyproject-path: .
155
+
156
+ - name: Install Dependencies
157
+ run: |
158
+ .venv\Scripts\activate
159
+ uv pip install -e ".[dev]"
160
+
161
+ - name: Run tests
162
+ id: test
163
+ continue-on-error: true
164
+ run: |
165
+ .venv\Scripts\activate
166
+ pytest
167
+
168
+ - name: Report test results
169
+ if: always()
170
+ run: |
171
+ if ("${{ steps.test.outcome }}" -eq "failure") {
172
+ echo "::warning::Windows tests failed but workflow continues"
173
+ echo "## ⚠️ Windows Tests Failed" >> $env:GITHUB_STEP_SUMMARY
174
+ echo "Tests failed on Windows but this is informational only." >> $env:GITHUB_STEP_SUMMARY
175
+ } else {
176
+ echo "## ✅ Windows Tests Passed" >> $env:GITHUB_STEP_SUMMARY
177
+ }
178
+
179
+ # Publishing job
180
+ publish:
181
+ name: Publish
182
+ permissions:
183
+ contents: write
184
+ if: "!contains(github.event.head_commit.message, '[skip ci]') && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main')"
185
+ needs: [setup, validation]
186
+ runs-on: ubuntu-latest
187
+
188
+ steps:
189
+ - uses: actions/checkout@v4
190
+ with:
191
+ fetch-depth: 0
192
+ token: ${{ secrets.GITHUB_TOKEN }}
193
+
194
+ - name: Set up uv
195
+ uses: astral-sh/setup-uv@v5
196
+
197
+ - name: Set up Python
198
+ run: uv python install ${{ fromJson(needs.setup.outputs.python-versions)[0] }}
199
+
200
+ - name: Format Source Code
201
+ run: uvx ruff format .
202
+
203
+ - name: Update Version Number
204
+ id: version
205
+ uses: i2mint/isee/actions/bump-version-number@master
206
+
207
+ - name: Build Distribution
208
+ run: |
209
+ BUILD_ARGS=""
210
+ if [ "${{ needs.setup.outputs.build-sdist }}" = "false" ]; then
211
+ BUILD_ARGS="$BUILD_ARGS --no-sdist"
212
+ fi
213
+ if [ "${{ needs.setup.outputs.build-wheel }}" = "false" ]; then
214
+ BUILD_ARGS="$BUILD_ARGS --no-wheel"
215
+ fi
216
+ uv build $BUILD_ARGS
217
+
218
+ - name: Publish to PyPI
219
+ env:
220
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_PASSWORD }}
221
+ run: uv publish dist/*
222
+
223
+ - name: Force SSH for git remote
224
+ run: |
225
+ git remote set-url origin git@github.com:${{ github.repository }}.git
226
+
227
+ - name: Commit Changes
228
+ uses: i2mint/wads/actions/git-commit@master
229
+ with:
230
+ commit-message: "**CI** Formatted code + Updated version to ${{ env.VERSION }} [skip ci]"
231
+ ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
232
+ push: true
233
+
234
+ - name: Tag Repository
235
+ uses: i2mint/wads/actions/git-tag@master
236
+ with:
237
+ tag: ${{ env.VERSION }}
238
+ message: "Release version ${{ env.VERSION }}"
239
+ push: true
240
+
241
+ # Optional GitHub Pages
242
+ github-pages:
243
+ name: Publish GitHub Pages
244
+ permissions:
245
+ contents: write
246
+ pages: write
247
+ id-token: write
248
+ if: "!contains(github.event.head_commit.message, '[skip ci]') && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)"
249
+ needs: publish
250
+ runs-on: ubuntu-latest
251
+
252
+ steps:
253
+ - uses: i2mint/epythet/actions/publish-github-pages@master
254
+ with:
255
+ github-token: ${{ secrets.GITHUB_TOKEN }}
256
+ ignore: "tests/,scrap/,examples/"
reci-0.0.2/.gitignore ADDED
@@ -0,0 +1,121 @@
1
+ wads_configs.json
2
+ data/wads_configs.json
3
+ wads/data/wads_configs.json
4
+
5
+ # Byte-compiled / optimized / DLL files
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+
10
+
11
+ .DS_Store
12
+ # C extensions
13
+ *.so
14
+
15
+ # TLS certificates
16
+ ## Ignore all PEM files anywhere
17
+ *.pem
18
+ ## Also ignore any certs directory
19
+ certs/
20
+
21
+ # Distribution / packaging
22
+ .Python
23
+ build/
24
+ develop-eggs/
25
+ dist/
26
+ downloads/
27
+ eggs/
28
+ .eggs/
29
+ lib/
30
+ lib64/
31
+ parts/
32
+ sdist/
33
+ var/
34
+ wheels/
35
+ *.egg-info/
36
+ .installed.cfg
37
+ *.egg
38
+ MANIFEST
39
+ _build
40
+
41
+ # PyInstaller
42
+ # Usually these files are written by a python script from a template
43
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
44
+ *.manifest
45
+ *.spec
46
+
47
+ # Installer logs
48
+ pip-log.txt
49
+ pip-delete-this-directory.txt
50
+
51
+ # Unit test / coverage reports
52
+ htmlcov/
53
+ .tox/
54
+ .coverage
55
+ .coverage.*
56
+ .cache
57
+ nosetests.xml
58
+ coverage.xml
59
+ *.cover
60
+ .hypothesis/
61
+ .pytest_cache/
62
+
63
+ # Translations
64
+ *.mo
65
+ *.pot
66
+
67
+ # Django stuff:
68
+ *.log
69
+ local_settings.py
70
+ db.sqlite3
71
+
72
+ # Flask stuff:
73
+ instance/
74
+ .webassets-cache
75
+
76
+ # Scrapy stuff:
77
+ .scrapy
78
+
79
+ # Sphinx documentation
80
+ docs/_build/
81
+ docs/*
82
+
83
+ # PyBuilder
84
+ target/
85
+
86
+ # Jupyter Notebook
87
+ .ipynb_checkpoints
88
+
89
+ # pyenv
90
+ .python-version
91
+
92
+ # celery beat schedule file
93
+ celerybeat-schedule
94
+
95
+ # SageMath parsed files
96
+ *.sage.py
97
+
98
+ # Environments
99
+ .env
100
+ .venv
101
+ env/
102
+ venv/
103
+ ENV/
104
+ env.bak/
105
+ venv.bak/
106
+
107
+ # Spyder project settings
108
+ .spyderproject
109
+ .spyproject
110
+
111
+ # Rope project settings
112
+ .ropeproject
113
+
114
+ # mkdocs documentation
115
+ /site
116
+
117
+ # mypy
118
+ .mypy_cache/
119
+
120
+ # PyCharm
121
+ .idea
reci-0.0.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thor Whalen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
reci-0.0.2/PKG-INFO ADDED
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: reci
3
+ Version: 0.0.2
4
+ Summary: Compile declarative CI recipes into fully-wired GitHub Actions workflows with typed data-flow validation.
5
+ Project-URL: Homepage, https://github.com/i2mint/reci
6
+ Project-URL: Repository, https://github.com/i2mint/reci
7
+ Project-URL: Documentation, https://i2mint.github.io/reci
8
+ Author: Thor Whalen
9
+ License: mit
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: argh>=0.31
13
+ Requires-Dist: httpx>=0.27
14
+ Requires-Dist: ruamel-yaml>=0.18
15
+ Requires-Dist: tomlkit>=0.12
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
18
+ Requires-Dist: pytest>=7.0; extra == 'dev'
19
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
20
+ Provides-Extra: docs
21
+ Requires-Dist: sphinx-rtd-theme>=1.0; extra == 'docs'
22
+ Requires-Dist: sphinx>=6.0; extra == 'docs'
23
+ Description-Content-Type: text/markdown
24
+
25
+
26
+ # reci
27
+
28
+ Compile declarative CI recipes into fully-wired GitHub Actions workflows with typed data-flow validation.
29
+
30
+ To install: ```pip install reci```
31
+
32
+ ## What it does
33
+
34
+ Write a short recipe YAML describing your CI pipeline as a DAG of GitHub Actions.
35
+ `reci` introspects each action's `action.yml` for typed input/output contracts,
36
+ infers data-flow edges from variable names, injects config from `pyproject.toml`,
37
+ and compiles the whole thing into a complete workflow YAML — with all the
38
+ cross-job output wiring generated for you.
39
+
40
+ ## Quick start
41
+
42
+ ```python
43
+ from reci import parse_recipe_string, compile_recipe, dump_workflow, ActionSpec, InputSpec, OutputSpec
44
+
45
+ recipe = parse_recipe_string("""
46
+ name: CI
47
+ on: [push, pull_request]
48
+
49
+ jobs:
50
+ test:
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ - uses: actions/setup-python@v5
54
+ with:
55
+ python-version: '${{ config.python_version }}'
56
+ - id: run_tests
57
+ run: pytest
58
+ """)
59
+
60
+ # Action specs can be fetched from GitHub or provided manually
61
+ specs = {
62
+ "actions/checkout@v4": ActionSpec(ref="actions/checkout@v4"),
63
+ "actions/setup-python@v5": ActionSpec(
64
+ ref="actions/setup-python@v5",
65
+ inputs={"python_version": InputSpec(name="python_version", default="3.x")},
66
+ ),
67
+ }
68
+
69
+ config = {"python_version": "3.12"}
70
+ workflow = compile_recipe(recipe, specs, config=config)
71
+ print(dump_workflow(workflow))
72
+ ```
73
+
74
+ The compiler:
75
+ - Injects a `setup` job that exports config values as job outputs
76
+ - Rewrites `${{ config.* }}` to `${{ needs.setup.outputs.* }}`
77
+ - Auto-wires `needs:` edges from data-flow dependencies
78
+ - Generates the cross-job output forwarding ceremony (step output -> job output -> `needs` consumption)
79
+
80
+ ## CLI
81
+
82
+ ```bash
83
+ # Inspect an action's input/output contract
84
+ reci inspect actions/setup-python@v5
85
+
86
+ # Compile a recipe to workflow YAML
87
+ reci compile recipe.yml --output .github/workflows/ci.yml
88
+
89
+ # Validate a recipe + config
90
+ reci validate --recipe recipe.yml --format cli
91
+
92
+ # Scaffold a config skeleton from a recipe
93
+ reci scaffold recipe.yml
94
+ ```
95
+
96
+ ## Five-level input resolution
97
+
98
+ For each action input, the compiler resolves its value using this precedence:
99
+
100
+ 1. **Explicit `with:`** in the recipe — used verbatim
101
+ 2. **Upstream output match** — `${{ steps.<id>.outputs.<name> }}`
102
+ 3. **Config value** (scoped `action__key`, then shared `key`) — `${{ needs.setup.outputs.<key> }}`
103
+ 4. **Default from action.yml** — omitted (GitHub uses the default)
104
+ 5. **Required + no source** — validation error
105
+
106
+ ## Validation
107
+
108
+ ESLint-style severity (`error`/`warning`/`info`) with ruff-style rule prefixes:
109
+
110
+ | Rule | Category | What it catches |
111
+ |------|----------|----------------|
112
+ | DAG001 | Structure | Cycle detected |
113
+ | DAG002 | Structure | Duplicate output name in a job |
114
+ | FLOW001 | Data flow | Required input has no source |
115
+ | FLOW002 | Data flow | Ambiguous wiring (multiple upstream matches) |
116
+ | FLOW006 | Data flow | Matrix job output consumed downstream (non-deterministic) |
117
+ | CONF001 | Config | Required config key missing |
118
+ | PURE001 | Purity | `run:` step breaks typed contract |
119
+ | ACT002 | Action | Referenced action not found |
120
+
121
+ ## Config adapters
122
+
123
+ Read CI config from your project file of choice:
124
+
125
+ | Adapter | Section | Library |
126
+ |---------|---------|---------|
127
+ | `pyproject` | `[tool.ci]` | tomlkit (round-trip) |
128
+ | `wads` | `[tool.wads.ci]` | tomlkit |
129
+ | `package-json` | `"ci"` key | json |
130
+ | `yaml` | `.ci.yml` | ruamel.yaml |
131
+
132
+ ## The recipe format
133
+
134
+ A recipe looks like a GitHub Actions workflow with reci extensions:
135
+
136
+ - **`${{ config.* }}`** — references to config values (resolved from pyproject.toml etc.)
137
+ - **`bind:`** — input renaming (`bind: {tag_name: version}` wires upstream output `version` to input `tag_name`)
138
+ - **`outputs:`** on `run:` steps — manual output annotation for untyped steps
139
+
140
+ ```yaml
141
+ name: Python CI
142
+ on: [push, pull_request]
143
+
144
+ jobs:
145
+ test:
146
+ steps:
147
+ - uses: actions/checkout@v4
148
+ - uses: actions/setup-python@v5
149
+ with:
150
+ python-version: '${{ config.python_version }}'
151
+ - run: pytest
152
+
153
+ publish:
154
+ needs: [test]
155
+ steps:
156
+ - uses: actions/checkout@v4
157
+ - id: bump
158
+ uses: i2mint/isee/actions/bump-version-number@master
159
+ - uses: i2mint/wads/actions/git-tag@master
160
+ bind:
161
+ tag_name: version # wire bump's "version" output to git-tag's "tag_name" input
162
+ ```
reci-0.0.2/README.md ADDED
@@ -0,0 +1,138 @@
1
+
2
+ # reci
3
+
4
+ Compile declarative CI recipes into fully-wired GitHub Actions workflows with typed data-flow validation.
5
+
6
+ To install: ```pip install reci```
7
+
8
+ ## What it does
9
+
10
+ Write a short recipe YAML describing your CI pipeline as a DAG of GitHub Actions.
11
+ `reci` introspects each action's `action.yml` for typed input/output contracts,
12
+ infers data-flow edges from variable names, injects config from `pyproject.toml`,
13
+ and compiles the whole thing into a complete workflow YAML — with all the
14
+ cross-job output wiring generated for you.
15
+
16
+ ## Quick start
17
+
18
+ ```python
19
+ from reci import parse_recipe_string, compile_recipe, dump_workflow, ActionSpec, InputSpec, OutputSpec
20
+
21
+ recipe = parse_recipe_string("""
22
+ name: CI
23
+ on: [push, pull_request]
24
+
25
+ jobs:
26
+ test:
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - uses: actions/setup-python@v5
30
+ with:
31
+ python-version: '${{ config.python_version }}'
32
+ - id: run_tests
33
+ run: pytest
34
+ """)
35
+
36
+ # Action specs can be fetched from GitHub or provided manually
37
+ specs = {
38
+ "actions/checkout@v4": ActionSpec(ref="actions/checkout@v4"),
39
+ "actions/setup-python@v5": ActionSpec(
40
+ ref="actions/setup-python@v5",
41
+ inputs={"python_version": InputSpec(name="python_version", default="3.x")},
42
+ ),
43
+ }
44
+
45
+ config = {"python_version": "3.12"}
46
+ workflow = compile_recipe(recipe, specs, config=config)
47
+ print(dump_workflow(workflow))
48
+ ```
49
+
50
+ The compiler:
51
+ - Injects a `setup` job that exports config values as job outputs
52
+ - Rewrites `${{ config.* }}` to `${{ needs.setup.outputs.* }}`
53
+ - Auto-wires `needs:` edges from data-flow dependencies
54
+ - Generates the cross-job output forwarding ceremony (step output -> job output -> `needs` consumption)
55
+
56
+ ## CLI
57
+
58
+ ```bash
59
+ # Inspect an action's input/output contract
60
+ reci inspect actions/setup-python@v5
61
+
62
+ # Compile a recipe to workflow YAML
63
+ reci compile recipe.yml --output .github/workflows/ci.yml
64
+
65
+ # Validate a recipe + config
66
+ reci validate --recipe recipe.yml --format cli
67
+
68
+ # Scaffold a config skeleton from a recipe
69
+ reci scaffold recipe.yml
70
+ ```
71
+
72
+ ## Five-level input resolution
73
+
74
+ For each action input, the compiler resolves its value using this precedence:
75
+
76
+ 1. **Explicit `with:`** in the recipe — used verbatim
77
+ 2. **Upstream output match** — `${{ steps.<id>.outputs.<name> }}`
78
+ 3. **Config value** (scoped `action__key`, then shared `key`) — `${{ needs.setup.outputs.<key> }}`
79
+ 4. **Default from action.yml** — omitted (GitHub uses the default)
80
+ 5. **Required + no source** — validation error
81
+
82
+ ## Validation
83
+
84
+ ESLint-style severity (`error`/`warning`/`info`) with ruff-style rule prefixes:
85
+
86
+ | Rule | Category | What it catches |
87
+ |------|----------|----------------|
88
+ | DAG001 | Structure | Cycle detected |
89
+ | DAG002 | Structure | Duplicate output name in a job |
90
+ | FLOW001 | Data flow | Required input has no source |
91
+ | FLOW002 | Data flow | Ambiguous wiring (multiple upstream matches) |
92
+ | FLOW006 | Data flow | Matrix job output consumed downstream (non-deterministic) |
93
+ | CONF001 | Config | Required config key missing |
94
+ | PURE001 | Purity | `run:` step breaks typed contract |
95
+ | ACT002 | Action | Referenced action not found |
96
+
97
+ ## Config adapters
98
+
99
+ Read CI config from your project file of choice:
100
+
101
+ | Adapter | Section | Library |
102
+ |---------|---------|---------|
103
+ | `pyproject` | `[tool.ci]` | tomlkit (round-trip) |
104
+ | `wads` | `[tool.wads.ci]` | tomlkit |
105
+ | `package-json` | `"ci"` key | json |
106
+ | `yaml` | `.ci.yml` | ruamel.yaml |
107
+
108
+ ## The recipe format
109
+
110
+ A recipe looks like a GitHub Actions workflow with reci extensions:
111
+
112
+ - **`${{ config.* }}`** — references to config values (resolved from pyproject.toml etc.)
113
+ - **`bind:`** — input renaming (`bind: {tag_name: version}` wires upstream output `version` to input `tag_name`)
114
+ - **`outputs:`** on `run:` steps — manual output annotation for untyped steps
115
+
116
+ ```yaml
117
+ name: Python CI
118
+ on: [push, pull_request]
119
+
120
+ jobs:
121
+ test:
122
+ steps:
123
+ - uses: actions/checkout@v4
124
+ - uses: actions/setup-python@v5
125
+ with:
126
+ python-version: '${{ config.python_version }}'
127
+ - run: pytest
128
+
129
+ publish:
130
+ needs: [test]
131
+ steps:
132
+ - uses: actions/checkout@v4
133
+ - id: bump
134
+ uses: i2mint/isee/actions/bump-version-number@master
135
+ - uses: i2mint/wads/actions/git-tag@master
136
+ bind:
137
+ tag_name: version # wire bump's "version" output to git-tag's "tag_name" input
138
+ ```