Compile declarative CI recipes into fully-wired GitHub Actions workflows with typed data-flow validation.
Project description
reci
Compile declarative CI recipes into fully-wired GitHub Actions workflows with typed data-flow validation.
To install: pip install reci
What it does
Write a short recipe YAML describing your CI pipeline as a DAG of GitHub Actions.
reci introspects each action's action.yml for typed input/output contracts,
infers data-flow edges from variable names, injects config from pyproject.toml,
and compiles the whole thing into a complete workflow YAML — with all the
cross-job output wiring generated for you.
Quick start
from reci import parse_recipe_string, compile_recipe, dump_workflow, ActionSpec, InputSpec, OutputSpec
recipe = parse_recipe_string("""
name: CI
on: [push, pull_request]
jobs:
test:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '${{ config.python_version }}'
- id: run_tests
run: pytest
""")
# Action specs can be fetched from GitHub or provided manually
specs = {
"actions/checkout@v4": ActionSpec(ref="actions/checkout@v4"),
"actions/setup-python@v5": ActionSpec(
ref="actions/setup-python@v5",
inputs={"python_version": InputSpec(name="python_version", default="3.x")},
),
}
config = {"python_version": "3.12"}
workflow = compile_recipe(recipe, specs, config=config)
print(dump_workflow(workflow))
The compiler:
- Injects a
setupjob that exports config values as job outputs - Rewrites
${{ config.* }}to${{ needs.setup.outputs.* }} - Auto-wires
needs:edges from data-flow dependencies - Generates the cross-job output forwarding ceremony (step output -> job output ->
needsconsumption)
CLI
# Inspect an action's input/output contract
reci inspect actions/setup-python@v5
# Compile a recipe to workflow YAML
reci compile recipe.yml --output .github/workflows/ci.yml
# Validate a recipe + config
reci validate --recipe recipe.yml --format cli
# Scaffold a config skeleton from a recipe
reci scaffold recipe.yml
Five-level input resolution
For each action input, the compiler resolves its value using this precedence:
- Explicit
with:in the recipe — used verbatim - Upstream output match —
${{ steps.<id>.outputs.<name> }} - Config value (scoped
action__key, then sharedkey) —${{ needs.setup.outputs.<key> }} - Default from action.yml — omitted (GitHub uses the default)
- Required + no source — validation error
Validation
ESLint-style severity (error/warning/info) with ruff-style rule prefixes:
| Rule | Category | What it catches |
|---|---|---|
| DAG001 | Structure | Cycle detected |
| DAG002 | Structure | Duplicate output name in a job |
| FLOW001 | Data flow | Required input has no source |
| FLOW002 | Data flow | Ambiguous wiring (multiple upstream matches) |
| FLOW006 | Data flow | Matrix job output consumed downstream (non-deterministic) |
| CONF001 | Config | Required config key missing |
| PURE001 | Purity | run: step breaks typed contract |
| ACT002 | Action | Referenced action not found |
Config adapters
Read CI config from your project file of choice:
| Adapter | Section | Library |
|---|---|---|
pyproject |
[tool.ci] |
tomlkit (round-trip) |
wads |
[tool.wads.ci] |
tomlkit |
package-json |
"ci" key |
json |
yaml |
.ci.yml |
ruamel.yaml |
The recipe format
A recipe looks like a GitHub Actions workflow with reci extensions:
${{ config.* }}— references to config values (resolved from pyproject.toml etc.)bind:— input renaming (bind: {tag_name: version}wires upstream outputversionto inputtag_name)outputs:onrun:steps — manual output annotation for untyped steps
name: Python CI
on: [push, pull_request]
jobs:
test:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '${{ config.python_version }}'
- run: pytest
publish:
needs: [test]
steps:
- uses: actions/checkout@v4
- id: bump
uses: i2mint/isee/actions/bump-version-number@master
- uses: i2mint/wads/actions/git-tag@master
bind:
tag_name: version # wire bump's "version" output to git-tag's "tag_name" input
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file reci-0.0.3.tar.gz.
File metadata
- Download URL: reci-0.0.3.tar.gz
- Upload date:
- Size: 68.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d432dc40e751b291dfa9fefcc6261169ef6b3892d38b68aea61825cf39936bd0
|
|
| MD5 |
4732b5da6c47620e3eb3fe4c9db90d91
|
|
| BLAKE2b-256 |
66dd695f45b615401fe06f4d854c3f8c6e2d934c692d776336338758ba93527b
|
File details
Details for the file reci-0.0.3-py3-none-any.whl.
File metadata
- Download URL: reci-0.0.3-py3-none-any.whl
- Upload date:
- Size: 30.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
31c4a77f644b388479bfa81de07bc1b36fb73ed4fe9cd4dd88186835897ef595
|
|
| MD5 |
c018e551babe1fd209c79681ee47b303
|
|
| BLAKE2b-256 |
cf4b47738926028e78022e776f9c2b59d8148e7393a428be0bf00aeedd4cbc36
|