reci
Compile declarative CI recipes into fully-wired GitHub Actions workflows with typed data-flow validation.
To install: pip install reci
Why reci
Writing GitHub Actions workflows by hand means wading through hundreds of lines of
boilerplate: forwarding step outputs to job outputs, threading needs.X.outputs.Y
expressions across jobs, and copy-pasting config values that belong in one place.
When something changes you touch a dozen lines across three jobs — and one typo
silently breaks your pipeline.
reci lets you write a short recipe that says what your CI does (which actions,
in what order, with what config), and the compiler handles how — auto-wiring
cross-job outputs, injecting config from pyproject.toml, and validating data-flow
before you push.
Agent skills (recommended)
reci ships with CLI and Python APIs (see below), but the best way to use it is through AI agent skills — structured instructions that let coding assistants operate reci on your behalf. Instead of memorizing CLI flags and YAML syntax, just say what you want:
- "Set up CI for this project"
- "Migrate my existing workflow to reci"
- "What CI actions make sense here?"
Available skills
| Skill | What it does |
|---|---|
| ci-setup | Examines your project, proposes a CI plan, writes a recipe, compiles to a workflow |
| ci-migrate | Converts an existing GitHub Actions workflow into a reci recipe |
| ci-advisor | Interactive discussion about what CI pipeline makes sense for your project |
| reci-dev | Guide for contributors — architecture, adding rules, adding adapters |
Using the skills
The skills live in reci/data/skills/, so they ship inside pip install reci and any agent host can install them from the repo with the GitHub CLI (gh 2.90+):
gh skill install i2mint/reci ci-setup
gh skill install i2mint/reci ci-migrate
gh skill install i2mint/reci ci-advisor
Claude Code — symlink or copy the skills into your project or your personal skills directory (from a checkout, or from the installed package's reci/data/skills/):
# Make available to all your projects
ln -s /path/to/reci/reci/data/skills/ci-setup ~/.claude/skills/ci-setup
ln -s /path/to/reci/reci/data/skills/ci-migrate ~/.claude/skills/ci-migrate
ln -s /path/to/reci/reci/data/skills/ci-advisor ~/.claude/skills/ci-advisor
# Or just for one project
ln -s /path/to/reci/reci/data/skills/ci-setup my-project/.claude/skills/ci-setup
Then invoke with /ci-setup, /ci-migrate, or /ci-advisor in Claude Code.
See the Claude Code skills docs for details.
Other agents — the skills are plain Markdown files following the open Agent Skills standard. For Cursor, Copilot, Windsurf, and other tools, see the standard's integration guide.
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
Metadata
Release files for reci 0.0.13
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| reci-0.0.13.tar.gz | 96.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| reci-0.0.13-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 164.9 kB
Release files / reci-0.0.13.tar.gz
| Download URL | reci-0.0.13.tar.gz |
|---|---|
| Size | 96.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fe11d01d4e49764b1ed2fe7caff9aadf6fff90e086c9b8e4247991967ecc5f25
|
|
BLAKE2b-256 checksum How to use checksums |
d5142d0e96a4e17a42e16544b8e86ee75c46abbcbf3e68206e7be2b34af8a983
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","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}
|
Release files / reci-0.0.13-py3-none-any.whl
| Download URL | reci-0.0.13-py3-none-any.whl |
|---|---|
| Size | 68.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b2538891b00ed0cb69ac6225f9d55797424ec462a5a6fce50fd50cfbab07270e
|
|
BLAKE2b-256 checksum How to use checksums |
a711cc0d328dee0a460646da74cf90b11a64b9f737a971be2af580812e93393f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","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}
|