CLI reference
Every flag, every subcommand, every output format. The complete @geoql/vue-doctor and @geoql/nuxt-doctor CLI surface.
The CLI surface is identical for @geoql/vue-doctor and
@geoql/nuxt-doctor — the latter just adds Nuxt-specific context
to the same engine. This page uses @geoql/vue-doctor for all
examples; swap the package name as needed.
Synopsis
npx -y @geoql/vue-doctor [flags]
npx -y @geoql/vue-doctor <subcommand> [flags]
Audit flags
These flags control the audit pass. They are the most-used subset.
| flag | description | default |
|---|---|---|
--root <path> | project root to scan | cwd |
--include <glob> | extra include glob (repeatable) | — |
--exclude <glob> | extra exclude glob (repeatable) | — |
--rule <id>:<level> | override a single rule's severity (repeatable). level = error|warn|info|off | — |
--preset <name> | minimal | recommended | strict | all | recommended |
--category <cat> | only score rules in this category (repeatable) | all |
--dimension <dim> | only score this dimension (repeatable); --dimension performance = perf-only | all |
--format <fmt> | agent | pretty | json | json-compact | sarif | html | agent |
--output <path> | write the report to a file (extension can imply format) | stdout |
--fix | apply auto-fixes where the rule supports it | off |
--fix-exclude <glob> | never auto-fix files matching this glob (repeatable) | — |
--no-lint | skip the oxlint subprocess pass | off |
--respect-inline-disables | honor // vue-doctor-disable-next-line comments | off |
--diff | scope the audit to files changed vs. HEAD (includes untracked) | off |
--staged | scope the audit to files in the git index | off |
--changed-files-from <ref> | scope the audit to source files changed since <ref> (e.g. origin/main) | off |
--include-untracked | fold non-ignored untracked files into --staged / --changed-files-from scope | off |
--full | override the scope flags and audit everything | off |
--score | print the score in CI-friendly format even when format is json | off |
--annotations | emit ::error file=…:: GitHub-style annotations | auto (in CI) |
--threshold <n> | override the score threshold (0–100) | 0 |
--fail-on <level> | error | warn | none — when to exit non-zero | error |
--verbose / --quiet | adjust log level | default |
--ci / --no-ci | force-enable or disable CI-only behavior (annotations, color) | auto |
--include-test-files | Score findings in test/story files too (reported but unscored by default) | |
--max-duration <seconds> | soft time budget; on exhaustion, skip remaining passes and return partial results | — |
--fail-on and --threshold are different. --fail-on controls
the exit code based on severity; --threshold controls the exit
code based on the score. You can use either, both, or neither.
Scoping by git changes
Three flags scope the audit to the files touched by a change set. They are
mutually exclusive — passing more than one exits 2.
# files changed vs HEAD in the working tree (untracked files included)
npx -y @geoql/vue-doctor --diff
# only what is staged in the git index (great for pre-commit hooks)
npx -y @geoql/vue-doctor --staged
# everything changed since a base ref — stacked branches, non-default PR bases
npx -y @geoql/vue-doctor --changed-files-from origin/main
--diff already unions non-ignored untracked files. For --staged and
--changed-files-from, add --include-untracked to fold newly-created
(non-ignored) files into the scope — useful when an agent writes a file and
audits before staging it:
npx -y @geoql/vue-doctor --staged --include-untracked
An unresolvable --changed-files-from ref exits 2. --full overrides every
scope flag and audits the whole project.
Scoping by category or dimension
--category and --dimension narrow the audit to a subset of rules.
Both are repeatable and compose (the scope is the union). They are
orthogonal to --preset/--rule: the preset decides which rules are
on, then the category/dimension scope keeps only the on-rules whose
category falls in the requested set.
# perf-only run — the most common case
npx -y @geoql/vue-doctor --dimension performance
# just security + the two perf categories
npx -y @geoql/vue-doctor --category security --dimension performance
# a single category
npx -y @geoql/vue-doctor --category reactivity
The six dimensions and the categories they cover:
| dimension | categories |
|---|---|
performance | performance, template-perf |
correctness | reactivity, composition, template, sfc, vue-builtin, hydration |
security | security |
maintainability | ai-slop, dead-code, build-quality, deps, structure, modules-deps |
nuxt | nitro, seo, cloudflare, server-routes, data-fetching |
design | design |
An unknown --category or --dimension value exits 2. Scoping
re-scores from the surviving findings, so the printed score reflects
only the in-scope rules.
Time-budgeting with --max-duration
--max-duration <seconds> sets a soft deadline for the whole audit.
When the budget is exhausted, doctor stops starting new passes (in
cost order: dead-code → template → lint → sfc → project), keeps every
finding already collected, and returns a partial result instead of
being killed with nothing.
# in CI, cap a slow monorepo scan at 90s
npx -y @geoql/vue-doctor --max-duration 90 --format json
The score stays a real number (√-decay over the findings that were
collected) and the exit code still follows --threshold / --fail-on
— a time-out never changes those. In json output the report gains:
{
"incomplete": true,
"skippedCheckReasons": [
{
"kind": "time-budget-exhausted",
"deadlineMs": 90000,
"elapsedMs": 90420,
"skippedPasses": ["dead-code", "project"],
},
],
}
On a run that finishes within budget, incomplete is omitted (the
JSON is byte-for-byte what you get today) and schemaVersion stays
"1".
Test and story files
Findings in test, story, and fixture files (*.test.*, *.spec.*,
*.stories.*, __tests__/, tests/, cypress/, e2e/, ...) are still
reported — each carries "surface": "test" in the JSON output — but they are
excluded from the score and the CI gate by default. Your test helpers
shouldn't drag down your production health score.
Route files that merely happen to be named test (pages/test.vue,
server/api/tests.get.ts) stay production surface.
Opt test findings back into the score with the flag or config:
npx @geoql/vue-doctor --include-test-files
export default defineConfig({
includeTestFiles: true,
});
Subcommands
list-rules
Print the registered rule catalogue. Use --json or --json-compact
to get a machine-readable form.
npx -y @geoql/vue-doctor list-rules
npx -y @geoql/vue-doctor list-rules --category hydration --json
Filters:
| flag | description |
|---|---|
--preset <name> | restrict to rules enabled by the given preset |
--category <cat> | ai-slop | composition | performance | reactivity | hydration | data-fetching | server-routes |
--source <src> | oxlint | core |
--severity <sev> | error | warn | info |
explain <ruleId>
Print the long-form description, the rationale, and the recommended fix for a single rule.
npx -y @geoql/vue-doctor explain no-em-dash-in-str
The output is the same text that appears in the docs at
/rules/vue and /rules/nuxt, but reachable from the terminal.
inspect [dir]
Print the detected project profile: framework versions, capabilities (auto-imports, Pinia, Vue Router), monorepo layout, and the rules that would be enabled under the active preset.
npx -y @geoql/vue-doctor inspect
npx -y @geoql/vue-doctor inspect apps/web --json
Useful in CI to debug "why is rule X not firing on this project?".
The JSON report's projectInfo carries a frameworkDetected boolean
(true when a Vue or Nuxt project is detected, false otherwise) so a
consumer can tell a real audit apart from a scan that found no Vue/Nuxt
project — without string-matching framework.
init
Generate a doctor.config.ts in the current directory. Interactive by
default; pass --yes to accept defaults.
npx -y @geoql/vue-doctor init --yes
npx -y @geoql/vue-doctor init --yes --config-format json
The init flow asks five questions (preset, output format, fail-on threshold, file scope, ignore globs) and writes a typed config that matches the config schema.
ci install
Scaffold a CI workflow that runs doctor on every push and pull request.
It writes a workflow which uses the published
geoql/doctor-action@v2 — it
does not inline an npx run, so upgrades come for free from the
action's floating major tag.
# GitHub Actions (auto-detected) — writes .github/workflows/doctor.yml
npx -y @geoql/vue-doctor ci install
# preview without writing
npx -y @geoql/vue-doctor ci install --dry-run
# also scaffold a PR-review workflow; skip inline PR comments
npx -y @geoql/vue-doctor ci install --pr --no-comments
| flag | description | default |
|---|---|---|
--pr | also scaffold a separate PR-review workflow | off |
--no-comments | strict gate only — omit the PR-comment inputs | comments |
--provider <name> | github | gitlab | auto (GitLab writes a gate-only job) | auto |
-y, --yes | write without prompting | off |
--force | overwrite an existing workflow | off |
--dry-run | print the planned files, write nothing | off |
The scaffolded workflow wires api-key to secrets.DOCTOR_API_KEY and
sets push-project, so pushing scores to the
dashboard only needs you to add the
secret. framework is fixed to the CLI you ran (vue for
vue-doctor, nuxt for nuxt-doctor). ci upgrade and ci config
are intentionally not provided — the floating @v2 tag handles
upgrades, and gate/scope/PR settings live on the audit flags and the
action's with: block.
design [dir]
Runs a focused design-quality scan — accessibility, interaction states, forms, and production polish — powered by shadscan-vue. The design score is its own weighted 0-100 and never affects the health score.
npx @geoql/vue-doctor design .
npx @geoql/vue-doctor design . --format json
npx @geoql/vue-doctor design . --fail-under 70
| Flag | What it does |
|---|---|
--format <name> | human (default), json, or agent |
--fail-under <score> | Exit 1 when the design score is below N |
Dashboard push (the-doctor.report)
When you run doctor in CI, it can stream privacy-stripped findings to your the-doctor.report dashboard so the score, severity counts, and per-rule findings show up over time.
| flag | description | default |
|---|---|---|
--push | after the audit, POST findings to the SaaS (negate with --no-push) | off |
--api-key <key> | the-doctor.report API key (doc_…); pass via a secret, never inline | — |
--push-project <slug> | dashboard project slug (e.g. owner/repo); falls back to the directory name | audited dir name |
--push-workspace <path> | sub-app label inside a monorepo project (e.g. apps/docs) | none (single-app repo) |
--push-url <url> | SaaS endpoint for the push | https://app.the-doctor.report/api/v1/findings |
npx -y @geoql/vue-doctor \
--push \
--api-key "$DOCTOR_API_KEY" \
--push-project "owner/repo"
Monorepos: one project, one score series per workspace
A repo that ships a Vue library and a Nuxt app has two codebases with two
very different scores. Push them under the same --push-project slug and
tag each with --push-workspace, and the dashboard keeps a separate score
series, findings list, and leaderboard row per sub-app — instead of the two
runs overwriting each other on every commit.
# Vue library
npx -y @geoql/vue-doctor packages/my-lib \
--push --api-key "$DOCTOR_API_KEY" \
--push-project "owner/repo" --push-workspace "packages/my-lib"
# Nuxt docs app in the same repo
npx -y @geoql/nuxt-doctor apps/docs \
--push --api-key "$DOCTOR_API_KEY" \
--push-project "owner/repo" --push-workspace "apps/docs"
With geoql/doctor-action@v2 you
do not pass this flag at all — the action forwards its own
working-directory as the workspace. Run the action once per sub-app and
each gets its own series automatically.
Omitting --push-workspace in a monorepo makes every sub-app share one
untagged series. Runs from the same commit then collapse to the newest
one, so one app's score silently replaces the other's.
--push-project is not the same as --project. --project is the
monorepo workspace-aggregation filter (audit named workspace packages);
--push-project is the dashboard slug. Passing an owner/repo slug to
--project makes the CLI look for a workspace package by that name, find
none, and exit 2.
Only privacy-safe fields are sent: rule id, severity, category, file path, and line/column. Never your source code, code snippets, or messages. A failed push never changes the audit exit code — a down dashboard will not break your CI.
Output formats
| format | best for | shape |
|---|---|---|
agent | local dev, CI logs (default) | one-line summary + per-finding block |
pretty | local dev, human reading | boxed tables with rule descriptions |
json | programmatic consumption | full AuditReport JSON |
json-compact | high-volume CI | single-line JSON per finding |
sarif | GitHub code scanning, GitLab code quality | SARIF 2.1.0 |
html | shareable report (emailable, PR comment) | self-contained HTML |
The format is auto-detected from the --output extension if you
don't pass --format. .json → json, .sarif → sarif,
.html → html, anything else → agent.
Exit codes
Stable across all subcommands:
| code | meaning |
|---|---|
0 | success — score above threshold, no findings at or above --fail-on |
1 | at or above --fail-on threshold (or score below --threshold) |
2 | invalid arguments, missing config, doctor crash, or I/O error |
If your CI script does if [ $? -ne 0 ]; then fail; fi, it will
correctly catch both code 1 and code 2. If you want to treat
exit 2 as a separate failure (e.g., page on-call), check for it
explicitly: if [ $? -eq 2 ]; then pager …; fi.
Environment variables
| variable | equivalent flag | notes |
|---|---|---|
DOCTOR_NO_COLOR | --no-color | disable ANSI in output |
DOCTOR_PRESET | --preset | picked up before the CLI flag |
DOCTOR_FAIL_ON | --fail-on | |
DOCTOR_FORMAT | --format | |
DOCTOR_ROOT | --root | |
CI | — | auto-enables annotations when set |
GITHUB_ACTIONS | — | also auto-enables annotations |
The CLI flag always wins over the env var. The env var is useful for shell aliases and CI matrix defaults.