The reusable GitHub Action emrecdr/codelore@v1 brings codelore's behavioural code analysis to any GitHub Actions workflow. Composite action — no Docker pull, no Node bootstrap; startup is essentially the binary download (see Performance below).
name: codelore
on:
pull_request:
push:
branches: [main]
jobs:
hotspots:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # required to upload SARIF
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 } # codelore needs the full history
- uses: emrecdr/codelore@v1
id: codelore
with:
analysis: hotspots
format: sarif
output: codelore-hotspots.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ steps.codelore.outputs.result-path }}
category: codelore-hotspotsThe findings appear in the PR's Security tab and the Files changed view, with severity derived from each row's max(0.1, (100 − cognitive_health) / 4) band (error at ≥ 7, warning at ≥ 4, otherwise note).
| Input | Default | Description |
|---|---|---|
command |
analyze |
Which subcommand to run: analyze | check | gate | diff. analysis/format/output apply to analyze only; for check/gate/diff pass command-specific flags (and diff's <base>..<head> range) via args |
analysis |
hotspots |
Any codelore analysis name (see codelore analyze --help or docs/research-foundations.md). Applies to command: analyze only |
format |
sarif |
csv | json | ndjson | sarif | markdown | gha | html | parquet | sqlite | spa | step-summary. Applies to command: analyze only |
output |
codelore-result |
Output file path relative to GITHUB_WORKSPACE. Empty string = stdout. Applies to command: analyze only |
repo |
. |
Path to repository to analyse (defaults to the checked-out workspace) |
args |
(empty) | Extra CLI flags appended verbatim, split on whitespace with no quote handling — see the note below. For analyze: --rows, --min-revs, --departed-threshold-days, etc. For check/gate/diff: the command-specific flags (--thresholds-file, --ratchet, --fail-on, and diff's <base>..<head> range) |
version |
latest |
latest follows the most recent v* release; vX.Y.Z pins to a specific version |
argsvalues cannot contain spaces. The Action splits this input on whitespace before building the command, and it does not honour quotes — soargs: '--exclude "some dir/**"'arrives as three separate arguments and the flag is rejected. Globbing is disabled during the split, so a bare*is safe. Where a value needs a space, use a flag whose value can avoid one (repeat--excludewith a glob per pattern), or run the binary directly in arun:step instead of through this Action.
| Output | Description |
|---|---|
result-path |
Absolute path to the generated file (empty when streaming to stdout). Always empty for command: check/gate/diff — check and gate stream their report and have no output file, and diff writes its own --output path, which the Action does not report back |
version-used |
The actual codelore version downloaded (resolved from latest or pinned) |
result |
For command: check/gate — the gate verdict: pass or fail. Empty for analyze/diff, which emit no verdict. |
violations |
For command: check/gate — the number of gate violations behind the verdict. Empty for analyze/diff. |
name: knowledge-loss-weekly
on:
schedule:
- cron: '0 9 * * 1' # Mondays 09:00 UTC
workflow_dispatch:
jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: emrecdr/codelore@v1
id: codelore
with:
analysis: knowledge-islands
format: html
output: knowledge-islands.html
args: '--departed-threshold-days 90 --rows 50'
- uses: actions/upload-artifact@v4
with:
name: knowledge-loss-report
path: knowledge-islands.html- uses: emrecdr/codelore@v1
id: codelore
with:
analysis: clone-coupling
format: sarif
output: clone-coupling.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ steps.codelore.outputs.result-path }}
category: codelore-clone-couplingClones that intersect with knowledge-island files sort first in the document, because at_risk is the primary sort key — so the most actionable debt is at the top of the run. Severity itself is combined_score × 10 and does not read at_risk. Reviewers see the most actionable debt findings first.
strategy:
matrix:
# SARIF is wired for hotspots, clones and clone-coupling only — an
# analysis without a SARIF rule exits 2, so it cannot ride this matrix.
analysis: [hotspots, clones, clone-coupling]
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: emrecdr/codelore@v1
with:
analysis: ${{ matrix.analysis }}
format: sarif
output: codelore-${{ matrix.analysis }}.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: codelore-${{ matrix.analysis }}.sarif
category: codelore-${{ matrix.analysis }}- uses: emrecdr/codelore@v1
with:
analysis: coupling
format: csv
output: coupling.csv
args: '--code-maat-compat --min-revs 5'CSV columns and row formatting match code-maat's verbose-mode output exactly. Drop-in for existing code-maat-targeted dashboards.
name: health-trend
on:
push:
branches: [main]
jobs:
trend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: emrecdr/codelore@v1
with:
analysis: health-trend
format: markdown
output: health-trend.md
- uses: emrecdr/codelore@v1
with:
analysis: architecture-trend
format: markdown
output: architecture-trend.md
- uses: actions/upload-artifact@v4
with:
name: health-over-time
path: '*-trend.md'Both trends wire csv | json | markdown; neither has an HTML emitter, so format: html fails the step. Use csv if a dashboard is going to read the series rather than a person.
health-trend emits a per-commit series of arch-health, code-health and combined-health with green/yellow/red bands; architecture-trend tracks propagation cost, cycle count and largest cycle over the same history. Neither needs configuration. Pair them with the gate below and you have the full loop: trends show the direction, gates stop the regressions. The README's Tracking health over time walks the whole loop end to end.
Set command: to run codelore's quality-gate subcommands instead of analyze. A gate violation exits non-zero, which fails the step and therefore the workflow — the intended CI signal.
These commands take their flags through args. The analysis/format/output inputs are analyze-only and are not injected for them: check, gate, and diff each accept their own --format over a different value set (and diff its own --analysis and --output), and the format default sarif is one gate rejects. Pass the command's own flags via args instead. All three stream their report to stdout and set no result-path, including diff when you give it an --output of its own.
name: codelore-gate
on:
pull_request:
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: emrecdr/codelore@v1
with:
command: check
args: '--thresholds-file .codelore-thresholds.toml'check reads .codelore-thresholds.toml, prints the gate table, and exits non-zero on any violation — failing the workflow. Add --ratchet to fail only on regressions against the recorded baseline, or --format sarif for a machine-readable report (check accepts text | sarif; json is a gate format, not a check one). (check also writes result=pass|fail to $GITHUB_OUTPUT.)
- uses: emrecdr/codelore@v1
with:
command: gate
args: '--thresholds-file .codelore-thresholds.toml'gate accepts --format text|json only (not sarif).
- uses: emrecdr/codelore@v1
with:
command: diff
# diff takes a positional <base>..<head> range plus its flags, all via args.
args: '${{ github.event.pull_request.base.sha }}..${{ github.sha }} --fail-on any'diff compares the two revisions and fails the step when the PR crosses the --fail-on boundary (none | rank-entrant | score-increase | any).
The action works on every GitHub-hosted runner family + their self-hosted equivalents:
runs-on |
Target |
|---|---|
ubuntu-latest / ubuntu-24.04 / ubuntu-22.04 |
x86_64-unknown-linux-gnu |
ubuntu-24.04-arm |
aarch64-unknown-linux-gnu |
macos-latest / macos-15 (Apple silicon) |
aarch64-apple-darwin |
macos-13 / macos-14 (Intel) |
x86_64-apple-darwin |
windows-latest / windows-2022 |
x86_64-pc-windows-msvc |
- Resolves
version(defaults to the latest v* release). - Detects the runner OS+arch and computes the target triple.
- Downloads + extracts the matching binary archive from GitHub Releases.
- Adds the install dir to
$GITHUB_PATH. - Runs the requested
codeloresubcommand (analyzeby default;check/gate/diffwhencommand:is set) with the requested flags. - Exposes
result-path(andversion-used) as outputs.
The action's startup cost is essentially the download (≈ 200-300 ms on warm GitHub CDN). No Docker pull, no Node bootstrap.
| Permission | Why |
|---|---|
contents: read |
Required by actions/checkout (default) |
security-events: write |
Required by github/codeql-action/upload-sarif |
There are two independent pins here, and it is worth being deliberate about both:
| pin | selects | ref |
|---|---|---|
uses: emrecdr/codelore@<ref> |
the Action — the logic in action.yml |
v1 |
version: input |
the binary the Action downloads | vX.Y.Z |
v1 tracks the Action's interface — its inputs, outputs, and behaviour. It
is not codelore's SemVer major (codelore is still 0.x); it moves onto each
release the way actions/checkout@v4 does, and only becomes v2 if the
Action's interface changes incompatibly.
Recommended for production — the Action follows fixes, the binary is whatever that release shipped:
- uses: emrecdr/codelore@v1For full reproducibility, pin both. Pinning only version: leaves the
Action's own logic floating, so what runs around your pinned binary can still
change:
# The Action is pinned to a commit; `v1` would still float.
- uses: emrecdr/codelore@<full-40-char-commit-sha> # v1
with:
# And the binary is pinned to a release tag (see
# https://github.com/emrecdr/codelore/releases for the current list).
version: vX.Y.Zfetch-depth: 0is required onactions/checkoutso codelore sees the full git history. Shallow clones return empty / partial analyses.- The action downloads the binary on every run; for self-hosted runners with limited bandwidth, consider caching
$RUNNER_TEMP/codelorebetween runs.