Architecture¶
VibeCode QA is a single zero-config CLI. It detects your stack, runs each check in isolation, folds the results into one weighted score, and emits a report. Everything runs locally — nothing is uploaded unless you pass --upload with a VCQA_TOKEN.
The surfaces¶
Five things ship, and only one of them analyses code. The desktop app and the MCP server do not contain a scanner — they launch the CLI and read the report it writes. That is deliberate: one engine, several hosts, so a check behaves the same wherever you run it.
flowchart TB
subgraph LOCAL["Your machine"]
CLI["<b>@vibecodeqa/cli</b><br/>the engine"]
DESK["<b>VibeCode Monitor</b><br/>desktop app"]
MCPL["<b>@vibecodeqa/mcp</b><br/>MCP server"]
AGENT(["Your coding agent"])
end
subgraph CLOUD["VibeCode QA cloud"]
WORKER["<b>API</b><br/>api.vibecodeqa.online"]
KV[("Report storage")]
APPP["<b>Dashboard</b><br/>app.vibecodeqa.online"]
end
GH(["GitHub<br/>API · Actions · webhooks"])
DESK -->|launches| CLI
MCPL -->|launches| CLI
AGENT -->|MCP| MCPL
CLI -->|"--upload"| WORKER
GH -->|Actions run| CLI
APPP -->|session cookie| WORKER
WORKER --> KV
WORKER <--> GH
| Surface | What it is | Where the analysis happens |
|---|---|---|
| CLI | npx @vibecodeqa/cli |
Here. This is the engine. |
| Desktop monitor | Tauri app watching a local folder | Launches the CLI, reads report.json |
| MCP server | stdio server for coding agents | Launches the CLI |
| Dashboard | Hosted web app | Reads stored reports; never analyses |
| API | Cloudflare Worker | Stores reports, serves history, runs server-side scans |
How a report reaches the dashboard¶
flowchart LR
subgraph P1["Local"]
D1["Desktop scan<br/><i>or</i> npx cli"] --> R1[".vibe-check/report.json"]
R1 -.->|"--upload"| KV1[("Report storage")]
end
subgraph P2["CI"]
D2["push / pull request"] --> A2["GitHub Actions"] --> KV2[("Report storage")]
end
subgraph P3["Server scan"]
D3["Dashboard button"] --> W3["API"] --> KV3[("Report storage")]
end
Local and CI scans run the full engine. The server scan is a lighter preview that reads your repository through the GitHub API without cloning it, so it can give you a first result with nothing installed and no workflow merged.
What each path measures¶
The difference is not a detail. A server scan runs 5 of the 38 documented checks.
flowchart LR
subgraph FULL["Local / CI — the engine"]
F1["38 checks<br/>every file in the tree"]
F2["lint · types · type-safety · complexity<br/>duplication · secrets · react · flutter<br/>performance · accessibility · architecture<br/>standards · dead-code · error-handling · …"]
end
subgraph PREVIEW["Server scan — preview"]
P1["5 checks<br/>≤80 files fetched"]
P2["structure · testing · docs<br/>security · dependencies"]
end
FULL -->|"33 checks not measured"| PREVIEW
| Local / CI | Server scan | |
|---|---|---|
| Checks | 38 | 5 |
| Files read | every file | ≤ 80, ≤ 1.5 MB |
| Repository | full clone | GitHub API, no clone |
| External tools | biome, eslint, tsc, knip, vitest, gitleaks | none |
| Secret scanning | gitleaks | tree-level only (.env committed) |
| Setup needed | install or a merged workflow | none |
Both write to the same report store, so check meta.source before comparing two
scores — a server-scan score and a full-engine score are not measured with the same
instrument, and a difference between them may be depth rather than a change in your
code.
A server scan is for the first look: connect a repo, see something real immediately. For a number you can track over time, run the engine — locally, or in CI.
Inside the engine¶
flowchart TB
ENTRY["CLI entry"]
DETECT["Stack detection"]
INV["File inventory<br/>+ scan policy<br/><i>one shared view of the tree</i>"]
RUNNERS["Check runners<br/><i>lint · types · security · complexity<br/>duplication · testing · react · flutter …</i>"]
TOOLS(["Delegated tools<br/>biome · eslint · tsc · knip<br/>vitest · gitleaks · dart analyze"])
SCORE["Scoring<br/>weights · composite · grade"]
OUT["report.json<br/>HTML · SARIF · badge"]
HIST["History · trend · delta"]
ENTRY --> DETECT --> INV --> RUNNERS --> TOOLS
RUNNERS --> SCORE --> OUT --> HIST
The file inventory is the single answer to "what files exist" — runners never walk the tree themselves, so an ignore rule applies identically to every check. Delegated tools are why the engine needs a real process environment.
Inside the desktop app¶
One codebase ships two things: the hosted dashboard and the desktop monitor. They share types and little else — the dashboard talks only to the API, the monitor talks only to the CLI.
flowchart TB
subgraph WEB["Dashboard — web"]
WC["Repo list · report viewer<br/>trends · settings"]
API["API client<br/><i>session cookie</i>"]
end
subgraph MON["Monitor — desktop"]
VIEWS["Views<br/><i>solution · duplication · complexity<br/>architecture · tests · trends</i>"]
IPC["Tauri IPC"]
end
RUST["Rust backend<br/>run_scan · watch · read_report"]
SHARED["Shared types"]
WORKER(["API"])
CLIP(["CLI via npx"])
WC --> API --> WORKER
VIEWS --> IPC --> RUST -->|launches| CLIP
SHARED -.-> WC
SHARED -.-> VIEWS
The scan pipeline¶
flowchart TD
A[CLI entry] --> B[Detect stack and workspace]
B --> C{Monorepo?}
C -->|yes| D[Resolve packages<br/>pnpm · turbo · nx · melos]
C -->|no| E[Single source root]
D --> F[Run 38 checks]
E --> F
F --> G[Per-check score 0–100]
G --> H[Weighted composite]
H --> I[Grade A–F + trend vs history]
I --> J[Terminal · HTML · JSON · SARIF · badge]
Each check is an independent runner that takes the project root and returns a CheckResult (score, grade, issues, timing). A crash in one runner is contained — it's recorded as errored and the scan continues.
Tool delegation¶
Where a best-in-class tool exists, VibeCode QA delegates to it when it's available and falls back to a built-in implementation otherwise — so it always works with zero setup, but gets sharper when you opt in.
flowchart LR
subgraph Secrets
S1[gitleaks] -.fallback.-> S2[15 regex patterns]
end
subgraph Duplication
D1[jscpd CLI] -.fallback.-> D2["@jscpd/core engine<br/>+ our tokenizer"]
end
subgraph Architecture
A1[dependency-cruiser] -.SFC/monorepo.-> A2[built-in resolver]
end
subgraph Dead code
K1[Knip] -.fallback.-> K2[skip]
end
subgraph React / a11y
R1[eslint-plugin-jsx-a11y] --> R2[normalized issues]
R3[html-validate] --> R2
R4[built-in heuristics] -.gaps.-> R2
end
The duplication fallback is notable: it runs jscpd's own @jscpd/core Rabin-Karp engine over a lightweight tokenizer we ship, giving mature maximal-clone detection without bundling jscpd's 2.5 MB language-grammar tokenizer. See Tool delegation.
How the score is built¶
flowchart LR
F[Foundations 23%] --> SUM([Σ check × weight])
Q[Quality 30%] --> SUM
T[Testing 13%] --> SUM
AR[Architecture 9%] --> SUM
SEC[Security 16%] --> SUM
AI[LLM Readiness 9%] --> SUM
SUM --> SCORE[Composite 0–100]
SCORE --> GRADE[Grade A–F]
Weights sum to 100 across the 37 checks that carry category metadata. The seven AI Analysis checks are weight 0 — they surface findings without affecting the score, as do the platform-specific zero-weight checks and the synthetic dead-code check. Full method on the Scoring page.
Output formats¶
| Format | Flag | Use |
|---|---|---|
| HTML report | (default) | Multi-page report in .vibe-check/report/ |
| JSON | --json |
Machine-readable; CI and tooling |
| SARIF | --sarif |
GitHub Code Scanning / Security tab |
| Badge | --badge |
shields.io-style SVG |
| Markdown | --markdown |
Paste into a PR or wiki |
See the CLI reference for every flag.
Hosted dashboard auth¶
The hosted dashboard uses GitHub OAuth for repo discovery and settings, but the browser never receives the GitHub access token. The API stores the token server-side behind an HttpOnly session cookie, validates OAuth state on callback, and checks GitHub repo permissions before reading settings, uploading manual reports, triggering scans, or showing private report history.
CLI uploads use a separate VibeCode QA platform token (VCQA_TOKEN). The CLI falls back to GITHUB_TOKEN when VCQA_TOKEN is unset and sends whichever it finds as the bearer token; whether the dashboard accepts a GitHub token is a server-side decision this repository cannot verify from the CLI source.
Last verified
Surfaces, module boundaries, and report paths verified against the four codebases on 2026-08-18. Pipeline, weights, and output formats verified against @vibecodeqa/cli 0.56.0.