Skip to main content

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: eirmos will 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

  1. Implement BasePipelineParser in eirmos/parsers/<system>.py. Populate self.jobs, self.file_map, self.parsed_files, and override get_job_stage / get_job_needs.
  2. Reuse the shared YAML loader: content = self._load_yaml(path).
  3. Register a ParserAdapter in parsers/__init__.py with a detect() helper that returns the main pipeline file (or None).
  4. Add a fixture in tests/examples/ and a tests/test_<system>_parser.py covering happy path, malformed YAML, missing file, cycle, empty, and single-job cases.
  5. Add the parser to eirmos/__init__.py exports.

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

eirmos-0.6.0.tar.gz (50.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

eirmos-0.6.0-py3-none-any.whl (47.9 kB view details)

Uploaded Python 3

File details

Details for the file eirmos-0.6.0.tar.gz.

File metadata

  • Download URL: eirmos-0.6.0.tar.gz
  • Upload date:
  • Size: 50.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for eirmos-0.6.0.tar.gz
Algorithm Hash digest
SHA256 e884280a58d5d25d157a0ac49ce79c5b74e5171127749787cb50ecdb77ce5e67
MD5 90963336fb7608ac574c98ed0b3b9092
BLAKE2b-256 58ac270121df101e0fedda79d3bef222c19d2e73c5721cf2097df1d2a1698b21

See more details on using hashes here.

Provenance

The following attestation bundles were made for eirmos-0.6.0.tar.gz:

Publisher: release.yml on eirmos/eirmos

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file eirmos-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: eirmos-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 47.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for eirmos-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 61a228fd72a380ad501602e7b97e587f41e9ca90f936cb648130887c54dbba75
MD5 096a4bd39a4010310ded8eb19d842f38
BLAKE2b-256 bbbb6d13b75cdf688c14903c27480d17d61f21ea23e3d34232c5521e7717e63d

See more details on using hashes here.

Provenance

The following attestation bundles were made for eirmos-0.6.0-py3-none-any.whl:

Publisher: release.yml on eirmos/eirmos

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page