Architecture Overview
Five packages, one dependency direction. Matches docs/ARCHITECTURE.md in
the repository — update that file, not just this page, if the boundary ever
changes.
@frontend-qa/shared (types, Zod schemas, redaction — depends on nothing) │ ▼@frontend-qa/core (orchestrator, config, detect, normalize, report writer) │ ▼@frontend-qa/browser (Playwright collectors — the only package that imports playwright) │ ├──▶ @frontend-qa/cli (bin: frontend-qa) └──▶ @frontend-qa/mcp-server (bin: frontend-qa-mcp)Package responsibilities
Section titled “Package responsibilities”@frontend-qa/shared
Section titled “@frontend-qa/shared”Pure types and Zod schemas, redaction utilities. No I/O, no side effects. Depended on by every other package.
Issue,Report,EvidenceTypetypesissueSchema,reportSchema,configSchema(Zod)redactHeaders(headers),redactString(s)hashIssueId(parts)— stable id for dedup across runs
@frontend-qa/core
Section titled “@frontend-qa/core”Framework-agnostic engine — no Playwright dependency.
QACollector<T>interfacerunScan(config): Promise<Report>— orchestratordetectProject(cwd): ProjectInforesolveConfig(cli, cwd): ResolvedConfigwriteReport(report, outDir): Promise<string>normalize.*— collector result →Issue[]analyzeBundle(buildDir)— filesystem-only bundle-size analysisanalyzeStatic(srcDir)— ts-morph static source analysis
@frontend-qa/browser
Section titled “@frontend-qa/browser”Playwright-driven collectors and the run orchestrator. The only package that
imports playwright.
runBrowserScan(input): Promise<{ report, reportPath, outDir }>consoleCollector,networkCollector,screenshotCollector,timingCollectora11yCollector,perfCollector,visualCollector— opt-indiscoverRoutes(page, baseUrl)waitForQuiet(page, quietMs?, maxMs?)findRunningDevServer(),launchDevServer(cwd, project),resolveDevServerUrl(cwd, project, config, onLog?)— shared by the CLI and the MCP serverslug(route)— filesystem-safe route naming for screenshotsgetFreePort()— for Lighthouse’s CDP connection
@frontend-qa/cli
Section titled “@frontend-qa/cli”The frontend-qa bin. Wires arg parsing → config → runScan → console
summary + exit code. No business logic lives here — everything reusable is
in core/browser.
@frontend-qa/mcp-server
Section titled “@frontend-qa/mcp-server”The frontend-qa-mcp bin (currently unpublished — see
MCP Server guide). Exposes five MCP tools over stdio
as thin wrappers over core/browser — no analysis logic of its own.
Collector interface
Section titled “Collector interface”Every runtime collector implements the same shape, so adding one never requires touching the orchestrator:
interface QACollector<TResult = unknown> { name: string; // e.g. 'browser.console' category: string; // e.g. 'runtime' setup(ctx: RunContext): Promise<void>; onNavigate?(route: string): Promise<void>; teardown(): Promise<TResult>; toIssues(result: TResult, ctx: RunContext): Issue[];}Coupling rules
Section titled “Coupling rules”shareddepends on nothing.coredepends only onshared.browserdepends onshared+core(+ Playwright).clidepends oncore,shared,browser.mcp-serverdepends oncore,shared,browser(+ the MCP SDK) — same shape ascli, no new coupling.
Extension points
Section titled “Extension points”Adding a new collector means a new module implementing QACollector and
registering with core’s orchestrator — no edits to core itself required.
Adding a new CLI command or MCP tool is additive in the same way: both are
thin callers into core/browser, so neither package needs to know the
other exists.
Non-goals in V1
Section titled “Non-goals in V1”- No LLM/API integration anywhere in the runtime.
- No cloud upload, no telemetry.
- The MCP server is stdio-only — no HTTP/SSE transport.