Run in CI
Wire the doctor into GitHub Actions, GitLab CI, or any CI that can run Node. SARIF, exit codes, and inline annotations.
The doctor is designed to be boring in CI. One command, one exit code, one report artifact. This page walks through the three integration shapes you actually need.
GitHub Actions
The recommended pattern uses a composite action plus the SARIF
uploader. The doctor writes .doctor/report.sarif, and the
github/codeql-action/upload-sarif step surfaces every finding as an
inline PR annotation.
name: doctor
on:
pull_request:
push:
branches: [main]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # needed for --diff
- uses: actions/setup-node@v4
with:
node-version: 24
- name: Run the doctor
run: npx -y @geoql/vue-doctor --format sarif --output .doctor/report.sarif --fail-on warn
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: .doctor/report.sarif
category: doctor
The --fail-on warn flag makes the step exit 1 on any warn or
error. Pick the threshold that matches your team's tolerance:
| threshold | meaning |
|---|---|
error | exit 1 only on error findings (default) |
warn | exit 1 on warn and error |
none | never exit 1; useful for SARIF-only integrations |
Always upload the SARIF file with if: always(). If the doctor
crashes, the SARIF step still runs and the failure surfaces as a
workflow error — which is what you want, not a silent green check.
GitLab CI
GitLab uses the same SARIF artifact and a separate step to surface it
as a codequality report.
doctor:
image: node:24
script:
- npx -y @geoql/vue-doctor --format sarif --output doctor.sarif --fail-on warn
artifacts:
when: always
reports:
codequality: doctor.sarif
paths:
- doctor.sarif
expire_in: 30 days
GitLab renders the SARIF on the merge request's "Code Quality" widget. Each finding links back to the file and line in your repo.
Generic CI
For anything that can run Node, the surface is three flags and a report path:
npx -y @geoql/vue-doctor \
--format sarif \
--output ./doctor.sarif \
--fail-on warn
The exit codes are stable:
| exit | meaning |
|---|---|
0 | clean, score above threshold |
1 | at or above --fail-on threshold (or score below) |
2 | invalid arguments, missing config, doctor crash |
Exit 2 is reserved for infrastructure failures, not findings.
If your CI script checks if exit_code == 1, exit 2 will not match
— that's intentional. Investigate exit 2 like a build crash.
Diff mode for PRs
The doctor supports a --diff flag that scopes the audit to files
changed in the current branch vs. the merge base. This is much faster
on large repos and produces a smaller SARIF artifact.
npx -y @geoql/vue-doctor --diff --fail-on warn
For pre-commit / pre-push hooks, use --staged instead — it scopes
to files in the git index.
The diff is computed against origin/HEAD (or the configured merge
base). Make sure your CI step has fetch-depth: 0 so the full
history is available.
Composite action
The published geoql/doctor-action
wraps the above patterns with sensible defaults and works for both
frameworks via the framework input:
- uses: geoql/doctor-action@v2
with:
framework: vue # or nuxt
fail-on: warn
preset: recommended
The action installs the right CLI, runs the audit, emits SARIF, and returns the same exit codes as the raw CLI.
Push to the dashboard
Add an api-key and the action streams privacy-stripped findings
to your the-doctor.report dashboard —
the score, severity counts, and per-rule findings, charted over time.
- uses: geoql/doctor-action@v2
with:
framework: vue
api-key: ${{ secrets.DOCTOR_API_KEY }}
project: ${{ github.repository }} # the dashboard slug, owner/repo
Only privacy-safe fields leave your CI: rule id, severity, category, file path, and line/column. Never source code, snippets, or messages. A failed push never changes the audit exit code.
On the raw CLI the equivalent flags are --push, --api-key, and
--push-project. Note --push-project (the dashboard slug) is
distinct from --project (the monorepo workspace filter).
Monorepos: one job per sub-app
In a repo that ships more than one app, run the action once per sub-app
with a different working-directory. The action forwards it as the
workspace label, so each sub-app keeps its own score series, findings,
and leaderboard row under a single project slug.
- uses: geoql/doctor-action@v2
with:
framework: vue
working-directory: packages/my-lib
api-key: ${{ secrets.DOCTOR_API_KEY }}
project: ${{ github.repository }}
- uses: geoql/doctor-action@v2
with:
framework: nuxt
working-directory: apps/docs
api-key: ${{ secrets.DOCTOR_API_KEY }}
project: ${{ github.repository }}
Without distinct workspaces both jobs push to the same untagged series,
and runs sharing a commit collapse to the newest — one app's score
silently replaces the other's. On the raw CLI the flag is
--push-workspace <path>.
Annotations on the CLI
Even without SARIF, the doctor can emit ::error file=…,line=…::
annotations directly. This is what the default agent reporter does
on a TTY.
$ npx -y @geoql/vue-doctor --annotations
::error file=app/composables/use-cart.ts,line=14,col=15::no-non-null-assertion-on-ref-value: Avoid `ref.value!` …
If your CI log is a TTY (most modern runners are), you'll see these as inline annotations without any extra configuration.