Skip to content

Core Concepts

Every command — scan, static, bundle — and every MCP tool ultimately produces the same two shapes: a Report containing Issue[]. Understanding these up front makes every other page easier to read.

One Report is written per scan run, to .local/frontend-qa/runs/<runId>/report.json:

interface Report {
schemaVersion: '1';
runId: string;
startedAt: string;
finishedAt: string;
target: { url: string; framework?: string };
config: unknown;
routesScanned: string[];
issues: Issue[];
artifacts: { screenshots: string[]; har?: string };
}

schemaVersion exists so future changes to the shape are detectable by consumers (including the MCP server) rather than silently breaking them.

Every finding — from every collector, and from static/bundle too — is normalized to the same Issue shape:

interface Issue {
id: string;
collector: string;
category: string;
severity: Severity;
route: string;
title: string;
description: string;
evidence: EvidenceType;
source?: { url?: string; line?: number; column?: number };
element?: { selector?: string; snippet?: string };
metadata: Record<string, unknown>;
suggestedNextStep?: string;
}

One shape means a console error, a failed network request, a missing alt attribute, and an oversized JS chunk all show up the same way — sortable, filterable, and readable by the same MCP tools regardless of which collector found them.

Every issue is tagged with how it was found, so a guess never reads like a fact:

Evidence type Meaning
Measured A concrete value was captured (a real HTTP status, a timing number)
Observed Something was directly witnessed happening (a console error firing)
StaticAnalysis Found by parsing source code, not by running it
Heuristic A pattern-based signal that’s often but not always meaningful
Inferred A conclusion drawn from indirect signals
Unavailable The check couldn’t run (e.g. a required flag was off)

This is why frontend-qa never presents a heuristic as a certainty — see Non-negotiable principles for why this matters to the project.

type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';

Used both for display and for --fail-on — the CLI exits non-zero when any issue is at or above the threshold you set (default high).

An Issue’s collector field identifies what produced it — browser.console, browser.network, browser.a11y, static.alt-text, bundle.chunk-size, and so on. Runtime collectors (console, network, screenshot, timing) always run during a scan; accessibility, performance, and visual diffing are opt-in via flags because they’re slower and not every project wants them on every run. See Basic Usage for the full list.