eirmos — explore the mental graph of your CI/CD pipelines. Parse and visualise pipeline dependencies across 13 systems (GitLab CI, GitHub Actions, Jenkins, CircleCI, Azure Pipelines, Bitbucket, Drone/Woodpecker, Travis, AppVeyor, Buildkite, Codefresh, Semaphore).
Project description
eirmos
Explore the mental graph of your CI/CD pipelines.
eirmos (from Greek ειρμός, coherent train of thought) parses and visualises CI/CD pipeline dependencies across 13 systems — GitHub Actions, GitLab CI, Jenkins, CircleCI, Azure Pipelines, Bitbucket Pipelines, Drone CI, Woodpecker CI, Travis CI, AppVeyor, Buildkite, Codefresh, and Semaphore.
Point it at a repo and get a job-dependency graph rendered as a terminal tree, Mermaid diagram, Graphviz dot, or text summary. Everything runs on your machine — no telemetry, no remote upload, no cloud account.
┌─────────────────────────────────────────────────────────────────┐
│ repo path ─► detect() ─► parse ─► DependencyGraph │
│ ▲ │
│ │ │
│ Tree / Mermaid / Dot │
│ Summary / Variables │
└─────────────────────────────────────────────────────────────────┘
Supported systems
| System | Detection | Dependency model |
|---|---|---|
| GitHub Actions | .github/workflows/*.yml |
jobs[].needs |
| GitLab CI | .gitlab-ci.yml (+ includes) |
needs:, extends:, rules:, trigger: |
| Jenkins | Jenkinsfile (declarative DSL) |
sequential stage(...), parallel { ... } |
| CircleCI | .circleci/config.yml |
workflow jobs: requires: |
| Azure Pipelines | azure-pipelines.yml, .azure-pipelines/*.yml |
stages[].dependsOn, jobs[].dependsOn, implicit prev-stage |
| Bitbucket Pipelines | bitbucket-pipelines.yml |
sequential steps; parallel: siblings |
| Drone CI | .drone.yml |
depends_on (string/list); sequential fallback |
| Woodpecker CI | .woodpecker.yml, .woodpecker/*.yml |
same model as Drone |
| Travis CI | .travis.yml |
jobs.include[].stage; stages sequential, jobs-within parallel |
| AppVeyor | appveyor.yml, .appveyor.yml |
phases × matrix (capped) |
| Buildkite | .buildkite/pipeline*.yml |
key/depends_on; wait barriers; group: flatten |
| Codefresh | codefresh.yml, .codefresh.yml |
when.steps[]; type: parallel flattening |
| Semaphore | .semaphore/semaphore.yml |
blocks[].dependencies |
Polyglot repos (e.g. mid-migration from Travis to GitHub Actions) are auto-detected:
eirmoswill print a warning naming all matched systems and use the first per registry order. Override with--ci github.
Install
Pick the path that matches your environment:
# Recommended: uv tool (isolated, fast, no virtualenv juggling)
uv tool install eirmos
# One-shot, no install (uv ≥0.5)
uvx eirmos .
# Classic pipx (also isolated)
pipx install eirmos
# Single-file zipapp — runs anywhere with Python ≥3.9, no install
curl -L -o eirmos.pyz https://github.com/<you>/<repo>/releases/latest/download/eirmos.pyz
chmod +x eirmos.pyz
./eirmos.pyz .
# Plain pip (last resort, pollutes site-packages)
pip install eirmos
# From source for development
git clone https://github.com/<you>/<repo>
cd <repo>
pip install -e ".[dev]"
make test # 172 tests
make coverage # ≥90% gate
make pyz # build the single-file zipapp
CLI
# Auto-detect and render as a terminal tree
eirmos .
# Explicit path
eirmos /path/to/repo
# Force a CI system instead of auto-detection
# Slugs: github, gitlab, circleci, jenkins, azure, bitbucket, drone,
# woodpecker, travis, appveyor, buildkite, codefresh, semaphore
eirmos --ci buildkite .
# Mermaid output (paste into GitHub / docs)
eirmos --format mermaid . > pipeline.mmd
# Graphviz dot for high-res rendering
eirmos --format dot . | dot -Tsvg -o pipeline.svg
# Filter to a single stage / job
eirmos --stage test .
eirmos --job deploy_prod .
# Inventory views
eirmos --list-stages .
eirmos --list-jobs .
# Skip GitLab includes
eirmos --no-includes .
Library usage
Every parser implements the same protocol; you can use them directly:
from pathlib import Path
from eirmos import (
BuildkiteParser, DependencyGraph, MermaidFormatter,
)
parser = BuildkiteParser(base_path="my-repo").parse(
Path("my-repo/.buildkite/pipeline.yml")
)
graph = DependencyGraph(parser)
print(MermaidFormatter(parser, graph).render())
Auto-detect at runtime:
from pathlib import Path
from eirmos.parsers import detect
from eirmos import DependencyGraph, TreeFormatter
adapter, main_file = detect(Path("my-repo"))
parser = adapter.parser_class(base_path="my-repo").parse(main_file)
graph = DependencyGraph(parser)
print(TreeFormatter(parser, graph).render())
Output formats
--format |
Use case |
|---|---|
tree |
Default. Coloured terminal tree grouped by stage. |
mermaid |
GitHub / GitLab / docs (renders inline). |
dot |
Graphviz; pipe into dot -Tsvg for high-res. |
summary |
Statistics only (job counts, edge counts, roots). |
variables |
Lists global + per-job variables (where supported). |
Non-obvious per-system semantics
These are the bits that aren't immediately obvious from the YAML — worth knowing when you read a generated graph.
Buildkite — wait is a cross-product barrier
wait
step_a ──┐ ┌──► step_d
step_b ──┤ ├──► step_e
step_c ──┘ └──► step_f
Every step after a wait implicitly depends on every step before
the same wait. If a post-wait step already declares depends_on,
the explicit list wins (no double-add).
Codefresh — type: parallel flattens to peers
prev_step ──► [parallel block] ──► next_step
├── child_1
├── child_2 (peers, no edges between them)
└── child_3
Children of a type: parallel block are exposed as peer jobs. Each
inherits the parallel block's predecessor (computed from when.steps
of the parent block, or sequential fallback).
Travis — stages sequential, jobs-in-stage parallel
stage: build ── compile ◄──┐
├── (predecessor of every "test" job)
stage: test ── unit ◄──┤
── integ ◄──┘ (peers, no edge)
stage: deploy ── release ◄── unit AND integ
AppVeyor — phases × matrix, capped
Phases run sequentially: init → install → before_build → build → after_build → before_test → test → after_test → deploy → after_deploy.
For each combination in environment.matrix × image, every active
phase produces one job. Combinations are capped at matrix_limit
(default 200) — passes that cap and the parser emits a warning.
Azure — implicit prev-stage default
A stage without dependsOn: implicitly depends on the previous stage
in declaration order. Stage-level dependsOn is mapped to the last
jobs of the predecessor stage so cross-stage edges always connect to
real nodes.
Polyglot detection
detect() walks the entire registry. If two or more adapters match
the same repo, a yellow warning is printed naming all matches and the
first one (per registry order) is used. Override with --ci.
Architecture
The codebase is organised in clear layers:
┌───────────────────────────────────┐
│ cli.py (argparse + glue) │
└───────┬───────────────────────────┘
│
┌──────────────▼──────────────┐
│ parsers/registry.detect() │
│ first-match + multi warn │
└──────────────┬──────────────┘
│ ParserAdapter
┌──────────────────▼─────────────────────┐
│ parsers/ (BasePipelineParser) │
│ GitHub GitLab CircleCI Jenkins │
│ Azure Bitbucket Drone Woodpecker │
│ Travis AppVeyor Buildkite │
│ Codefresh Semaphore │
└──────────────────┬─────────────────────┘
│ jobs / file_map / get_job_*
▼
┌──────────────────────┐
│ graph.py │
│ DependencyGraph │
└──────────┬───────────┘
│ edges / roots / has_cycle
▼
┌──────────────────────────────────────┐
│ formatters/ (BaseFormatter) │
│ Tree Mermaid Dot Summary Vars │
└──────────────────────────────────────┘
A deeper write-up with class and sequence diagrams lives in
docs/architecture.md.
Adding a new CI system
- Implement
BasePipelineParserineirmos/parsers/<system>.py. Populateself.jobs,self.file_map,self.parsed_files, and overrideget_job_stage/get_job_needs. - Reuse the shared YAML loader:
content = self._load_yaml(path). - Register a
ParserAdapterinparsers/__init__.pywith adetect()helper that returns the main pipeline file (orNone). - Add a fixture in
tests/examples/and atests/test_<system>_parser.pycovering happy path, malformed YAML, missing file, cycle, empty, and single-job cases. - Add the parser to
eirmos/__init__.pyexports.
The cross-cutting test in tests/test_graph_integration.py will
automatically smoke-test the new parser through every formatter once
it's added to the PARSER_FIXTURES list.
Development
# Run the full suite (172 tests)
python -m unittest discover -s tests
# Coverage gate (≥90%)
python -m coverage run --source=eirmos -m unittest discover -s tests
python -m coverage report -m --fail-under=90
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 eirmos-0.5.2.tar.gz.
File metadata
- Download URL: eirmos-0.5.2.tar.gz
- Upload date:
- Size: 46.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a0f715638f4fb08c6680de20bf56b299fabcf3ddd8200cfe80a17b816bf3a51
|
|
| MD5 |
ce005e0ef71f565d5d74c3c7800b0761
|
|
| BLAKE2b-256 |
31c205e501d28aabf52a8fd39e57857bf9619034a5c08e4f4bf5e94f5c461214
|
Provenance
The following attestation bundles were made for eirmos-0.5.2.tar.gz:
Publisher:
release.yml on eirmos/eirmos
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
eirmos-0.5.2.tar.gz -
Subject digest:
5a0f715638f4fb08c6680de20bf56b299fabcf3ddd8200cfe80a17b816bf3a51 - Sigstore transparency entry: 1499936889
- Sigstore integration time:
-
Permalink:
eirmos/eirmos@5758bad783096f5606803369e511daac28474377 -
Branch / Tag:
refs/tags/v0.5.2 - Owner: https://github.com/eirmos
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5758bad783096f5606803369e511daac28474377 -
Trigger Event:
release
-
Statement type:
File details
Details for the file eirmos-0.5.2-py3-none-any.whl.
File metadata
- Download URL: eirmos-0.5.2-py3-none-any.whl
- Upload date:
- Size: 43.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
556324ae200ca5392f76af36aa59c1377d8cb4862c19f38ab9719c7ee1af46a1
|
|
| MD5 |
733e9b46f7175567f75ef4f840924736
|
|
| BLAKE2b-256 |
f0e77ba69d1168b6595eb72dd9af34d05226f934b752340c60f13579ec16a2f6
|
Provenance
The following attestation bundles were made for eirmos-0.5.2-py3-none-any.whl:
Publisher:
release.yml on eirmos/eirmos
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
eirmos-0.5.2-py3-none-any.whl -
Subject digest:
556324ae200ca5392f76af36aa59c1377d8cb4862c19f38ab9719c7ee1af46a1 - Sigstore transparency entry: 1499936971
- Sigstore integration time:
-
Permalink:
eirmos/eirmos@5758bad783096f5606803369e511daac28474377 -
Branch / Tag:
refs/tags/v0.5.2 - Owner: https://github.com/eirmos
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5758bad783096f5606803369e511daac28474377 -
Trigger Event:
release
-
Statement type: