Two supported paths:
- Plain pip --
pip install roam-code, run any commands, upload SARIF yourself. Works on every CI platform (GitHub Actions, GitLab CI, Jenkins, Azure Pipelines, BitBucket, CircleCI, ...). - Composite GitHub Action --
uses: Cranot/roam-code@main. Adds sticky PR comments, guardrail-enforced SARIF upload, and quality gates with one block.
Pick the plain pip path if you want explicit control or are not on GitHub Actions; pick the composite action if you want the batteries-included PR experience. Both share the same underlying CLI and SARIF output.
Five lines of real work. Copy into .github/workflows/roam.yml:
name: Roam scan
on: [push, pull_request]
permissions:
contents: read
security-events: write # required for SARIF upload
jobs:
roam:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install roam-code
- run: roam init
- run: roam --sarif health > roam-health.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: roam-health.sarifResults appear in the PR's Security tab under Code scanning alerts.
For non-GitHub platforms, roam ships ready-made templates -- run once at the repo root:
roam ci-setup --platform github # writes .github/workflows/roam.yml
roam ci-setup --platform gitlab # writes .gitlab-ci.yml
roam ci-setup --platform jenkins # writes Jenkinsfile
roam ci-setup --platform azure # writes azure-pipelines.yml
roam ci-setup --platform bitbucket # writes bitbucket-pipelines.ymlEach template runs roam init, generates SARIF, applies a health-score gate,
and archives JSON+SARIF artifacts -- adapt the variables at the top to taste.
Copy this workflow into your repository at .github/workflows/roam.yml:
name: roam-code Analysis
on:
pull_request:
branches: [main, master]
push:
branches: [main, master]
permissions:
contents: read
pull-requests: write
security-events: write # Required for SARIF upload
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: Cranot/roam-code@main
with:
commands: 'health pr-risk'
sarif: 'true'
comment: 'true'
gate: 'health_score>=60'That is all you need. The action installs roam-code, indexes your codebase, runs the requested analysis commands, posts a sticky PR comment with results, uploads SARIF findings to GitHub Code Scanning, and enforces quality gates.
| Input | Default | Description |
|---|---|---|
version |
latest |
roam-code version to install from PyPI. Use a pinned version for reproducibility (e.g., 11.1.2). |
commands |
health |
Space-separated roam commands to run. Each command produces JSON output that feeds into the PR comment and quality gate. |
changed-only |
false |
Incremental CI mode. Adapts supported commands to changed files and transitive dependents (when detectable). |
changed-depth |
3 |
Dependency depth used when computing changed+dependent file scope in changed-only mode. |
base-ref |
(auto) | Optional explicit base ref/SHA for incremental mode. Default is PR base SHA (or push before SHA). |
sarif |
false |
When true, exports SARIF for the selected SARIF command set and uploads a guarded combined SARIF file to GitHub Code Scanning. Requires security-events: write permission. |
sarif-commands |
auto |
Space-separated commands to export via --sarif. auto picks the SARIF-capable subset of commands (health, dead, complexity, rules, secrets, algo). |
sarif-category |
roam-code |
Base SARIF upload category. The action appends job/runtime suffixes to reduce collisions. |
sarif-max-runs |
20 |
Pre-upload guardrail: maximum runs kept in combined SARIF. Extra runs are dropped from the tail with a warning. |
sarif-max-results |
25000 |
Pre-upload guardrail: maximum results per run. Extra results are dropped from the tail with a warning. |
sarif-max-bytes |
10000000 |
Pre-upload guardrail: maximum SARIF JSON bytes (conservative cap before upload). |
comment |
true |
When true and running on a pull request, upserts one marker-managed sticky PR comment (idempotent) and removes duplicate sticky comments if they exist. Requires pull-requests: write permission. |
gate |
(empty) | Quality gate expression. Supports scalar checks (key>=value) and trend-aware functions (velocity(metric)<=0, direction(metric)!=worsening). The action exits with code 5 when the gate fails. |
cache |
true |
Cache pip packages and the .roam/ SQLite index between runs for faster incremental analysis. |
python-version |
3.11 |
Python version to use. Supports 3.9 through 3.13. |
| Output | Description |
|---|---|
health-score |
The health score (0-100) if the health command was included. |
exit-code |
The exit code from analysis. 0 = success, 5 = gate failure, 1 = error. |
sarif-file |
Path to the generated SARIF file (when sarif is true). |
sarif-category |
Resolved category used for SARIF upload. |
sarif-truncated |
true when SARIF guardrails dropped runs/results before upload. |
sarif-results |
Final SARIF result count after guardrails. |
changed-only |
Whether incremental mode was enabled. |
base-ref |
Resolved base ref/SHA used for incremental mode. |
affected-count |
Number of changed+dependent files detected for incremental mode. |
Quality gates let you enforce minimum standards on every PR. The gate expression is evaluated against the JSON summary of each analysis command.
key operator value
Where:
keyis any field in the JSON summary (e.g.,health_score,tangle_ratio,risk_score,issue_count)operatoris one of:>=,<=,>,<,=valueis a number
Trend-aware functions are also supported:
latest(metric)— latest value from trend payloadsdelta(metric)— first-to-last change over the trend windowslope(metric)— per-snapshot slopevelocity(metric)— worsening velocity (positive means trending worse)direction(metric)— semantic direction (improving,worsening,stable)
You can combine multiple expressions with commas:
gate: 'health_score>=70,velocity(cycle_count)<=0'# Require health score of at least 70
gate: 'health_score>=70'
# Require tangle ratio below 5%
gate: 'tangle_ratio<=5'
# Require zero critical issues
gate: 'issue_count=0'
# Fail if cycle count is accelerating in a bad direction
gate: 'velocity(cycle_count)<=0'
# Require trend direction to avoid worsening health
gate: 'direction(health_score)!=worsening'When a gate fails, the action:
- Prints an error annotation with the actual vs required value
- Exits with code 5 (distinct from code 1 for crashes)
- Marks the check as failed in the PR
The PR comment will show the gate result as PASSED or FAILED.
Fourteen commands honour the global --sarif flag. The authoritative list
lives at src/roam/cli.py -- _SARIF_CONSUMERS (drift-guarded by
tests/test_sarif_consumer_list.py). To print the current list at any time:
roam --help 2>&1 | grep -A1 -- '--sarif'The W26.1-audited set (alphabetical):
algo, audit-trail-conformance-check, check-rules, complexity, dead, health,
py-modern, py-types, rules, secrets, stale-refs, supply-chain, taint, vulns
Any other command run with --sarif falls back to its native JSON envelope
-- no error, just no SARIF.
Use the official github/codeql-action/upload-sarif@v3 step:
- run: roam --sarif health > roam-health.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: roam-health.sarif
category: roam-health # optional; distinguishes multiple uploadsAfter upload, findings appear in the PR's Security tab > Code scanning
alerts, get auto-deduplicated across pushes via SARIF's partialFingerprints,
and can block merges when severity gates trip (configure via repo Security
settings > Code scanning > Tool configuration).
Requires security-events: write permission. Free for public repos and
GitHub Advanced Security customers; on private repos without GHAS the upload
step no-ops gracefully.
When using the composite action with sarif: 'true' it performs the
equivalent flow with extra guardrails:
- Generates SARIF per command from
sarif-commands(orautosubset) - Merges SARIF runs into one payload
- Applies upload guardrails (
sarif-max-runs,sarif-max-results,sarif-max-bytes) - Uploads via
github/codeql-action/upload-sarifusing resolvedsarif-category - Emits truncation warning metadata when guardrails drop findings
sarif-max-runsandsarif-max-resultsalign with documented GitHub SARIF scale constraints.sarif-max-bytesis a conservative pre-upload byte cap to reduce failed uploads on large payloads.- If truncation occurs, use
sarif-truncatedandsarif-resultsoutputs to surface that in downstream CI steps.
- One category per command. Set
category: roam-<cmd>on each upload so GitHub doesn't merge unrelated findings into one alert stream. - Upload incrementally. Run SARIF-emitting commands in parallel jobs and upload each result separately -- faster feedback, finer-grained gating.
- Pin the roam-code version in CI (
pip install roam-code==<version>) so a release with new rules does not silently re-open a wave of alerts. - Combine with
--jsongates. SARIF is for humans browsing the Security tab; useroam --json health+ ajqcheck (or the composite action'sgate:input) to fail the build on hard thresholds.
health/cycle-- Dependency cycleshealth/god-component-- Components with excessive couplinghealth/bottleneck-- High-betweenness bottleneck symbolshealth/layer-violation-- Architectural layer violations
When cache: 'true' (default), the action caches:
-
pip packages -- Keyed on OS, Python version, and roam-code version. Avoids re-downloading roam-code and its dependencies on every run.
-
.roam/directory -- The SQLite index database. Keyed on OS and a hash of all source files (*.py,*.js,*.ts,*.go, etc.). When source files change, the cache misses androam initrebuilds only the changed files (incremental indexing).
Cache hits reduce analysis time from 30-60s to under 10s on typical codebases.
Set changed-only: 'true' to run incremental PR analysis.
- The action resolves a base ref (PR base SHA by default) and computes changed
plus transitive dependent files via
roam affected. - Supported commands are auto-adapted:
verify,syntax-check,test-gaps,suggest-reviewers,fileget the affected file set.pr-risk,pr-diff,semantic-diff,affected,api-changesget base/range aware flags.
- Unsupported commands still run in normal full-repo mode.
Example:
- uses: Cranot/roam-code@main
with:
commands: 'verify pr-risk api-changes'
changed-only: 'true'
changed-depth: '3'Any roam command can be passed via the commands input. Common choices:
| Command | What it does |
|---|---|
health |
Overall health score (0-100), cycles, god components, bottlenecks |
pr-risk |
PR risk score based on changed files, blast radius, coupling |
complexity |
Cognitive complexity analysis with severity ratings |
dead |
Dead code detection (unreferenced exports) |
debt |
Technical debt inventory |
fitness |
Fitness function evaluation against project rules |
breaking |
Detect breaking API changes |
conventions |
Naming convention violations |
Run roam --help for all 241 commands.
| Code | Meaning |
|---|---|
| 0 | Success -- analysis completed, no gate failures |
| 1 | Error -- unexpected failure or crash |
| 2 | Usage error -- invalid arguments or flags |
| 3 | Index missing -- roam init not run (should not happen with the action) |
| 5 | Gate failure -- quality gate check failed |
- uses: Cranot/roam-code@main
with:
commands: 'health complexity dead'
sarif-commands: 'health complexity dead'
sarif-category: 'roam-code-pr-${{ github.ref_name }}-${{ matrix.python-version }}'
gate: 'health_score>=80'
sarif: 'true'- uses: Cranot/roam-code@main
id: roam
with:
commands: 'pr-risk'
comment: 'false'
- name: Check risk score
if: steps.roam.outputs.exit-code != '0'
run: echo "Analysis found issues (exit ${{ steps.roam.outputs.exit-code }})"- uses: Cranot/roam-code@v11.1.2
with:
version: '11.1.2'
cache: 'false'
commands: 'health'- uses: Cranot/roam-code@main
id: analysis
with:
commands: 'health'
- name: Report health score
run: echo "Health score is ${{ steps.analysis.outputs.health-score }}"Add security-events: write to your workflow permissions:
permissions:
security-events: writeAdd pull-requests: write to your workflow permissions:
permissions:
pull-requests: writeThe first run indexes the entire codebase. Subsequent runs with cache: 'true'
will restore the cached index and only re-index changed files. Typical
improvement: 30-60s down to under 10s.
The gate key must exactly match a field in the JSON summary. Run
roam --json health locally to see available fields:
roam --json health | python3 -m json.tool | grep -A5 '"summary"'