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, python).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.
profile: packagingturns the planner into a packaging-first planner 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, or packaging |
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", "python"],
"targets": ["ubuntu:24.04", "debian:13", "fedora:42"],
"defaults": {
"mode": "components",
"jobs": ["feelpp", "testsuite", "toolboxes", "mor", "python"],
"targets": ["ubuntu:24.04"]
},
"fullBuild": { "job": "feelpp-full" }
}{
"jobs": ["feelpp", "testsuite", "toolboxes", "mor", "python"],
"targets": ["ubuntu:24.04", "debian:13", "fedora:42"],
"defaults": {
"mode": "components",
"targets": ["ubuntu:24.04"]
},
"modes": {
"components": {
"jobs": ["feelpp", "testsuite", "toolboxes", "mor", "python"],
"targets": ["ubuntu:24.04"]
},
"full": {
"jobs": ["feelpp-full"],
"targets": ["ubuntu:24.04"]
}
}
}{
"jobs": ["feelpp", "toolboxes", "mor", "python"],
"fullBuild": {
"jobs": ["feelpp-full", "feelpp-full-debug"],
"targets": ["ubuntu:24.04"]
}
}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 }}"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=python |
Skip the python 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 |
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.