llms.txt · git:20260529.eada3b4 · 2026-05-29 · sha256 e5b7ba2b58bfad5a

llms.txt git:20260529.eada3b4A

Immutable. This exact content is served forever at /api/v1/blob/e5b7ba2b58bfad5a.

# sniff

> sniff is an autonomous QA scanner: you point it at a running web app and it walks the app's real
> user flows in a real (headless) browser and reports what's actually broken — with proof. It is not
> a static linter and it does not write or maintain a test suite. Every finding ships reproduction
> proof (exact route + ordered steps + a screenshot + the console/network excerpt), a severity, a
> confidence level, and a suggested fix. npm package `sniff-qa`. Binaries `sniff` and `sniff-qa`.
> Ships as both a CLI and an MCP server (Claude Code plugin). No API key required. Node >= 22.
> Apache-2.0.

A finding without reproduction proof is not a finding. sniff walks the app like a user — clicking,
filling forms, navigating, going back — and only reports an issue it can demonstrate. On a planted-bug
fixture (21 bugs across all 12 issue classes plus a clean control page) it finds 21/21 (100%) at 100%
precision with 0 false positives and 0 findings on the clean page. That before/after is locked as a
regression test, backed by a 441-test suite.

Last updated: 2026-05-29.

## What it is / who it's for

- What it is: an autonomous, browser-driven QA scanner. It auto-detects your running dev server (or
  takes a `--url`), drives a real headless Chromium through your app's real flows, and reports
  confirmed, reproducible bugs. When no app is running it falls back to a source-only scan with clear
  guidance on how to start one.
- Who it's for: web app developers, indie hackers, and teams who want to catch broken flows, broken
  pages, dead links, console errors, broken forms, bad loading/error states, responsive issues, and
  accessibility problems before users do — without hand-writing E2E tests and without an API key.
- Why trust it (low false positives): a first-party noise filter drops favicons, analytics, HMR,
  expected-auth redirects, and engine aborts; axe-core (zero-false-positive by design) backs the
  accessibility findings; uncertain findings are suppressed by default (shown with `--all`); a broken
  page is reported once, not re-flagged on every link into it.
- First run note: the first browser run downloads a Chromium build (~165 MB) automatically.

## Operating modes — CLI

The autonomous flow-walk is the DEFAULT.

- `sniff` — auto-detect the running dev server and walk it. Falls back to a source scan with guidance
  if no app is running.
- `sniff --url <url>` — walk that URL.
- `sniff scan` — source-only scan (no browser): placeholder/TODO/console.log/dead links and similar.
- `sniff report` — view the last results.
- `sniff doctor` — environment check (Node, browser, config, dev server).
- `sniff ci` — generate a GitHub Actions workflow.
- `sniff fix` — auto-fix safe issues.

Useful flags: `--url <url>`, `--report` (writes a self-contained HTML report to
`sniff-reports/sniff-report.html`), `--all` (also show low-confidence/uncertain findings),
`--max-pages <n>` (default 25), `--no-mobile` (skip the 375px responsive pass), `--json`, `--ci`,
`--fail-on <sev>`, `--headed`.

## Operating modes — MCP (`sniff` tool)

One unified `sniff` tool. On a running app, just say "scan this project for bugs" or "walk my app".
The dev server is auto-detected when `baseUrl` is omitted.

- mode `walk` — RECOMMENDED. The autonomous flow-walk in a real headless browser.
- mode `scan` — source-only scan (no browser).
- mode `report` — return the last results.
- (`run` and `discover` are legacy modes.)

Arguments: `rootDir` (project directory), `baseUrl` (URL to walk; auto-detected if omitted),
`headless` (run the browser headless), `maxPages` (page budget, default 25), `mobile` (run the 375px
responsive pass), `all` (include low-confidence/uncertain findings).

## The 12 issue classes

1. Broken pages / routes — 4xx/5xx, blank renders, crash screens.
2. Broken links — internal and external.
3. Console errors / uncaught exceptions / failed network requests during interaction.
4. Empty data + placeholder/fake data — lorem ipsum, TODO, `test@test.com`.
5. Broken forms — dead submit button, validation that never fires.
6. State-loss — fill a form, navigate back, and it's wiped.
7. Flow regressions / dead-ends — a journey that can't complete.
8. Bad loading states (infinite spinner) + missing error states.
9. Broken async outcomes — submitted but no success feedback (flagged "needs out-of-band verification").
10. Responsive issues — overflow, tiny tap targets (via the 375px mobile pass).
11. Accessibility — missing alt text/labels, contrast, via axe-core.
12. Unclear / buried primary actions.

## Finding output shape

Every finding is an object of the form:

```
{
  ruleId,            // stable identifier for the check that fired
  issueClass,        // one of the 12 classes above
  title,             // short human-readable summary
  severity,          // critical | high | medium | low
  confidence,        // confirmed | likely | uncertain  (uncertain hidden by default; show with --all)
  reproduction: {
    route,           // exact route where it reproduces
    steps,           // ordered list of the actions to reproduce
    screenshot,      // path to the captured screenshot
    console          // the console / network excerpt that proves it
  },
  suggestedFix       // a concrete suggested fix
}
```

## Install

- CLI: `npx sniff-qa` (in your project, with the dev server running) — or `npx sniff-qa --url <url>`.
- Claude Code plugin (also exposes the MCP server): install the `sniff` plugin. As an MCP server, the
  command is `npx` with args `["-y", "sniff-qa", "--mcp"]`.
- Works first-class in: Claude Code, Cursor, VS Code (Copilot), Codex, Gemini CLI, Windsurf, and
  Continue.dev.

## Q&A

Q: How is sniff different from a linter or a static scanner?
A: A linter reads source text. sniff opens your running app in a real headless browser and walks its
actual user flows — clicking, filling forms, navigating, going back — so it reports what's actually
broken at runtime, with a screenshot and the console/network excerpt as proof. (It also offers a
`sniff scan` source-only mode for when no app is running, but the flow-walk is the default.)

Q: Will it flood me with false positives?
A: No. uncertain findings are hidden by default (use `--all` to see them), a first-party noise filter
drops favicons/analytics/HMR/expected-auth/engine-aborts, accessibility findings come from axe-core
(zero-false-positive by design), and a broken page is reported once rather than re-flagged. On the
clean control page of the test fixture it reports 0 findings; across the 21-bug fixture it runs at
100% precision with 0 false positives.

Q: Do I need an API key or any configuration?
A: No. sniff requires no API key and works with zero config. It auto-detects your running dev server;
the only one-time cost is the ~165 MB Chromium build it downloads automatically on first run.

Q: What does "needs out-of-band verification" mean?
A: For broken async outcomes (issue class 9) — e.g. a form that submits but shows no success feedback —
sniff cannot see whether a confirmation email or backend side effect actually happened. It flags the
missing in-app feedback as a finding and labels it so you can confirm the out-of-band outcome yourself.

Q: Can I run it in CI?
A: Yes. Use `--ci` and `--fail-on <sev>` to gate the pipeline on severity, and `sniff ci` to generate
a ready-to-use GitHub Actions workflow.

## What sniff does NOT do

- It does NOT write or maintain a test suite. It walks your app and reports bugs; it does not generate
  or store test files you have to keep.
- It does NOT require an API key, and it does not need one to do its core work.
- It can NOT confirm out-of-band outcomes (e.g. whether a confirmation email was actually sent or a
  backend job ran). When it sees an action with no in-app feedback it flags it as "needs out-of-band
  verification" rather than claiming success or failure.
- It is NOT a static linter — runtime, browser-driven, proof-backed findings are the whole point.