Skip to content

Continuous integration

The whole CI surface is the exit code: 0 if nothing is over the threshold, 1 if something is. No reporter to configure, no output format to teach the runner.

.github/workflows/complexity.yml
name: Complexity
on: [push, pull_request]
jobs:
bonsai-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx bonsai-lint --over 15 .

No toolchain setup, no caching worth bothering with, no matrix. npx fetches a ~1 MB binary and the scan itself is fast enough that it is not the thing you wait for. 1.26 million lines across three languages takes under a second.

.gitlab-ci.yml
complexity:
image: node:22-alpine
script:
- npx bonsai-lint --over 15 .

Most existing codebases have findings on day one. Gating on the absolute number means either a threshold so high it catches nothing, or a red build nobody can fix this sprint.

Record what exists, commit it, and the gate then only fires on things that get worse:

Terminal window
npx bonsai-lint --over 15 --write-baseline .
git add .bonsai-lint-baseline.json

CI runs the same command it always did. A unit already in the baseline passes at its recorded score or below; anything new, or anything that climbed, fails. See baselines for how entries are keyed and what happens when code moves.

Exit 1 does not only mean “too complex”. It also means the scan could not be trusted: a path that could not be read, or no supported files found under the paths given.

That is deliberate. A quality gate that silently checks nothing when it is misconfigured is worse than no gate, because it reports success. If you point the command at a directory that does not exist, you want a red build, not a green one.

For anything that needs to consume results rather than just pass or fail:

Terminal window
npx bonsai-lint --over 15 --format json . > complexity.json

The shape is documented in JSON output. Findings are sorted worst-first and the ordering is deterministic, because the report is assembled after the scan rather than streamed as results arrive. Two runs of the same commit are byte-for-byte identical regardless of how many workers ran. A diff between runs is always a real change.