A reusable GitHub Action to plan your CI job matrix based on action inputs, workflow dispatch inputs, pull request metadata, labels, or commit messages.
It helps you control which jobs and targets are executed in your workflows, making CI faster, more configurable, and easier to maintain across projects.
- Parse directives from:
- explicit action inputs
workflow_dispatchevent inputs- PR labels
- PR title/body
- PR head commit or push head commit
- Supported directives:
mode=components(default): run split jobs (feelpp, testsuite, toolboxes, mor).mode=full: collapse into full-build job(s) (e.g.feelpp-full).only=...β run only specific jobs (auto-detects full mode if full job is specified).skip=...β skip specific jobs.targets=...β override matrix targets.include=.../exclude=...β adjust targets incrementally.
pkg=...β select packaging targets (profile-driven).pkg-include=.../pkg-exclude=...β adjust packaging targets incrementally.- Catalog-backed profiles such as
imagesemitmatrix_jsondirectly from their target catalog. - Non-catalog profiles such as
cican setmatrixCatalogProfileto enrichmatrix_jsonfrom another profile catalog. profile: packagingremains packaging-specific and emits the packaging matrix as the primarymatrix_json.- Auto-detect full mode: Using
only=feelpp-fullautomatically switches to full mode. - Mode-specific targets: Full mode can have its own default targets.
- Multiple full jobs: Support for multiple jobs in full mode.
- Project-specific config via
.github/plan-ci.json. - Outputs are ready to use in
if:conditions andmatrix: fromJSON(...). - Built-in defaults if no config is provided.
- Includes unit tests and its own CI workflow.
| Input | Description | Default |
|---|---|---|
config-path |
Path to the JSON config file in the consumer repo (see below). | .github/plan-ci.json |
mode-input |
Override mode directly. | "" |
message-override |
Override the directive message directly. | "" |
labels-override |
Comma-separated labels to use instead of payload labels. | "" |
github-token |
Token used to read PR/push commit messages via the GitHub API. | "" |
profile |
Explicit profile to resolve when using profile-based configs. | "" |
| Output | Description |
|---|---|
mode |
components, full, packaging, or the active catalog-backed profile name such as images |
only_jobs |
Space-separated list of jobs forced by only=... |
only_jobs_json |
JSON array of jobs forced by only=... |
skip_jobs |
Space-separated list of jobs to skip (skip=... or inferred from message tokens) |
skip_jobs_json |
JSON array of jobs to skip |
targets_json |
JSON array of selected target keys |
targets_list |
Space-separated list of selected target keys |
matrix_json |
JSON workflow matrix object for the selected profile |
enabled_jobs |
Space-separated list of jobs that will run |
enabled_jobs_json |
JSON array of jobs that will run |
warnings_json |
JSON array of planner warnings |
profile |
Resolved profile (for profile-based configs) |
enabled_profiles_json |
JSON array of enabled profiles |
pkg_enabled |
Whether packaging targets are enabled |
pkg_targets_json |
JSON array of packaging targets |
pkg_matrix_json |
JSON packaging matrix object |
pkg_matrix_rows_json |
JSON array of packaging matrix rows |
directive_source |
Source used for directive harvesting |
head_commit_sha |
Commit SHA used when directives were harvested from a commit |
{
"jobs": ["feelpp", "testsuite", "toolboxes", "mor"],
"targets": ["ubuntu:24.04", "debian:13", "fedora:42"],
"defaults": {
"mode": "components",
"jobs": ["feelpp", "testsuite", "toolboxes", "mor"],
"targets": ["ubuntu:24.04"]
},
"fullBuild": { "job": "feelpp-full" }
}{
"jobs": ["feelpp", "testsuite", "toolboxes", "mor"],
"targets": ["ubuntu:24.04", "debian:13", "fedora:42"],
"defaults": {
"mode": "components",
"targets": ["ubuntu:24.04"]
},
"modes": {
"components": {
"jobs": ["feelpp", "testsuite", "toolboxes", "mor"],
"targets": ["ubuntu:24.04"]
},
"full": {
"jobs": ["feelpp-full"],
"targets": ["ubuntu:24.04"]
}
}
}{
"jobs": ["feelpp", "toolboxes", "mor"],
"fullBuild": {
"jobs": ["feelpp-full", "feelpp-full-debug"],
"targets": ["ubuntu:24.04"]
}
}{
"profiles": {
"ci": {
"jobs": ["feelpp", "testsuite", "toolboxes", "mor"],
"targets": ["ubuntu:noble", "debian:trixie"],
"matrixCatalogProfile": "images",
"defaults": {
"mode": "components",
"targets": ["ubuntu:noble"]
}
},
"images": {
"jobs": ["images"],
"defaults": {
"targets": ["ubuntu:noble"]
},
"catalog": {
"ubuntu:noble": {
"oci_dist": "ubuntu-24.04",
"image_backend": "apt",
"image_strategy": "components",
"base_image": "ubuntu:24.04"
}
}
}
}
}With matrixCatalogProfile, the ci profile still chooses jobs and targets,
but its matrix_json rows are resolved from profiles.images.catalog instead of
the fallback { "target": [...] } shape.
jobs:
plan_ci:
runs-on: ubuntu-latest
outputs:
mode: ${{ steps.plan.outputs.mode }}
enabled_jobs_json: ${{ steps.plan.outputs.enabled_jobs_json }}
only_jobs: ${{ steps.plan.outputs.only_jobs }}
only_jobs_json: ${{ steps.plan.outputs.only_jobs_json }}
skip_jobs_json: ${{ steps.plan.outputs.skip_jobs_json }}
matrix_json: ${{ steps.plan.outputs.matrix_json }}
steps:
- uses: actions/checkout@v4
- id: plan
uses: feelpp/ci-matrix-planner@v1
with:
config-path: .github/plan-ci.json
feelpp:
needs: plan_ci
if: contains(fromJSON(needs.plan_ci.outputs.enabled_jobs_json), 'feelpp')
runs-on: self-docker
strategy:
matrix: ${{ fromJSON(needs.plan_ci.outputs.matrix_json) }}
steps:
- run: echo "Building feelpp on ${{ matrix.target }} in mode=${{ needs.plan_ci.outputs.mode }}"jobs:
plan_pkg:
runs-on: ubuntu-latest
outputs:
matrix_json: ${{ steps.plan.outputs.matrix_json }}
steps:
- uses: actions/checkout@v4
- id: plan
uses: feelpp/ci-matrix-planner@v1
with:
config-path: .github/plan-ci.json
profile: packaging
build_pkg:
needs: plan_pkg
strategy:
matrix: ${{ fromJSON(needs.plan_pkg.outputs.matrix_json) }}
steps:
- run: echo "Packaging ${{ matrix.flavor }}:${{ matrix.dist }}"jobs:
plan_images:
runs-on: ubuntu-latest
outputs:
matrix_json: ${{ steps.plan.outputs.matrix_json }}
steps:
- uses: actions/checkout@v4
- id: plan
uses: feelpp/ci-matrix-planner@v1
with:
config-path: .github/plan-ci.json
profile: images
build_images:
needs: plan_images
strategy:
matrix: ${{ fromJSON(needs.plan_images.outputs.matrix_json) }}
steps:
- run: echo "Build OCI image target ${{ matrix.target }} via ${{ matrix.image_backend }} (${{ matrix.image_strategy }})"The planner resolves directives in this order:
- explicit action inputs
workflow_dispatchevent inputs- PR labels
- PR title/body
- PR head commit message
- push head commit message
git log -1
| Directive | Effect |
|---|---|
only=feelpp |
Run only the feelpp job |
only=feelpp-full |
Auto-switch to full mode and run feelpp-full |
skip=toolboxes |
Skip the toolboxes job |
targets=fedora:42 |
Restrict matrix to Fedora 42 |
include=debian:13 |
Add Debian 13 to current targets |
exclude=ubuntu:22.04 |
Remove Ubuntu 22.04 from current targets |
mode=full |
Switch to full mode, run configured full job(s) |
mode=full only=feelpp-full |
Full mode with specific job filter |
pkg=noble,trixie |
Select packaging targets |
pkg=none |
Disable packaging targets explicitly |
pkg=spack pkg-exclude=spack-openmpi |
Select spack targets and exclude one |
For catalog-backed profiles such as images, use targets=..., include=...,
and exclude=... against the profile catalog and its groups.
You can also use PR labels to control mode:
ci-mode-fullβ Switch to full modeci-mode-componentsβ Switch to components mode (default)
- The planner logic is implemented in
index.jsand exposed as a pure functioncomputePlan(). - Unit tests live in
tests/with fixtures in JSON form. - Run tests locally with:
npm install
npm test- CI is set up in this repo to run tests automatically on push/PR.
ci-matrix-planner makes your CI config-driven.
By parsing directives in commit messages, PRs, or labels, it lets you control what to build and where β without duplicating workflow YAML.