[object Object]

← back to Cli Printing Press

refactor(cli): rename sniff to browser-sniff (#225)

23084c767601bc337dd4a10de64be39513e77ea0 · 2026-04-18 16:25:42 -0700 · Trevin Chow

Renames the browser-capture HAR analysis command to clarify its distinction
from crowd-sniff. The previous name suggested a generic "sniff" capability
when in fact the workflow is specifically browser-driven: the user captures
live traffic via browser automation or DevTools as a HAR file, and the
command analyzes that HAR to produce an OpenAPI-compatible spec.

Scope:
- Rename printing-press sniff -> printing-press browser-sniff (clean break,
  no alias; skill invocations updated in-scope)
- Rename internal/websniff -> internal/browsersniff (3 live consumers)
- Rename skill-authored artifacts: SNIFF_TARGET_URL, sniff-gate.json,
  sniff-report.md, sniff-unique-paths.txt, sniff-capture.har/json
- Rename phase labels in SKILL.md: "Sniff Gate" -> "Browser-Sniff Gate",
  "Pre-Sniff Auth Intelligence" -> "Pre-Browser-Sniff Auth Intelligence"
- Fix AGENTS.md glossary row (was factually wrong about sniff)
- Update README + forward-pointing solution docs (prose only, filenames
  unchanged per historical-docs policy)

Not marked as a breaking change (`!`) because no public release has shipped
with the old name — this is an internal naming correction, not a contract
break. In-flight runs with the old artifact names will need to re-run
Phase 1.7 to regenerate with the new names.

Plan: docs/plans/2026-04-18-002-refactor-rename-sniff-to-browser-sniff-plan.md

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 23084c767601bc337dd4a10de64be39513e77ea0
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Sat Apr 18 16:25:42 2026 -0700

    refactor(cli): rename sniff to browser-sniff (#225)
    
    Renames the browser-capture HAR analysis command to clarify its distinction
    from crowd-sniff. The previous name suggested a generic "sniff" capability
    when in fact the workflow is specifically browser-driven: the user captures
    live traffic via browser automation or DevTools as a HAR file, and the
    command analyzes that HAR to produce an OpenAPI-compatible spec.
    
    Scope:
    - Rename printing-press sniff -> printing-press browser-sniff (clean break,
      no alias; skill invocations updated in-scope)
    - Rename internal/websniff -> internal/browsersniff (3 live consumers)
    - Rename skill-authored artifacts: SNIFF_TARGET_URL, sniff-gate.json,
      sniff-report.md, sniff-unique-paths.txt, sniff-capture.har/json
    - Rename phase labels in SKILL.md: "Sniff Gate" -> "Browser-Sniff Gate",
      "Pre-Sniff Auth Intelligence" -> "Pre-Browser-Sniff Auth Intelligence"
    - Fix AGENTS.md glossary row (was factually wrong about sniff)
    - Update README + forward-pointing solution docs (prose only, filenames
      unchanged per historical-docs policy)
    
    Not marked as a breaking change (`!`) because no public release has shipped
    with the old name — this is an internal naming correction, not a contract
    break. In-flight runs with the old artifact names will need to re-run
    Phase 1.7 to regenerate with the new names.
    
    Plan: docs/plans/2026-04-18-002-refactor-rename-sniff-to-browser-sniff-plan.md
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 AGENTS.md                                          |   9 +-
 README.md                                          |  10 +-
 ...-refactor-rename-sniff-to-browser-sniff-plan.md | 301 +++++++++++++++++++++
 ...owd-sniff-complementary-discovery-2026-03-30.md |  42 +--
 internal/{websniff => browsersniff}/capture.go     |   2 +-
 internal/{websniff => browsersniff}/classifier.go  |   2 +-
 .../{websniff => browsersniff}/classifier_test.go  |   2 +-
 internal/{websniff => browsersniff}/fixtures.go    |   2 +-
 .../{websniff => browsersniff}/fixtures_test.go    |   2 +-
 internal/{websniff => browsersniff}/parser.go      |   2 +-
 internal/{websniff => browsersniff}/parser_test.go |   2 +-
 internal/{websniff => browsersniff}/schema.go      |   2 +-
 internal/{websniff => browsersniff}/schema_test.go |   2 +-
 internal/{websniff => browsersniff}/specgen.go     |   2 +-
 .../{websniff => browsersniff}/specgen_test.go     |   2 +-
 internal/{websniff => browsersniff}/types.go       |   2 +-
 internal/cli/{sniff.go => browser_sniff.go}        |  24 +-
 internal/cli/browser_sniff_test.go                 |  84 ++++++
 internal/cli/crowd_sniff.go                        |   6 +-
 internal/cli/root.go                               |   2 +-
 internal/cli/sniff_test.go                         |  25 --
 internal/crowdsniff/aggregate.go                   |   2 +-
 internal/crowdsniff/specgen.go                     |   2 +-
 internal/generator/generator.go                    |  10 +-
 internal/generator/templates/root.go.tmpl          |   1 +
 internal/pipeline/climanifest_test.go              |   8 +-
 internal/pipeline/research.go                      |   9 +-
 internal/pipeline/research_test.go                 |  23 +-
 skills/printing-press-retro/SKILL.md               |   2 +-
 .../references/issue-template.md                   |   2 +-
 skills/printing-press/SKILL.md                     | 206 +++++++-------
 .../{sniff-capture.md => browser-sniff-capture.md} | 122 ++++-----
 skills/printing-press/references/crowd-sniff.md    |  10 +-
 .../printing-press/references/deepwiki-research.md |   2 +-
 skills/printing-press/references/voice.md          |   2 +-
 35 files changed, 654 insertions(+), 274 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index f69064ea..aa55286a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -56,11 +56,12 @@ Key terms used throughout this repo. Several have overloaded meanings — the gl
 | **the printing press** / **the machine** | This repo's generator system — the Go binary, templates, skills, and catalog that together produce CLIs. |
 | **printed CLI** / **`<api>-pp-cli`** | A CLI produced by the printing press (e.g., `notion-pp-cli`). The `-pp-` infix avoids collisions with official vendor CLIs. When someone says "the CLI" without qualification, they almost always mean a printed CLI. Use "printed CLI" in your responses to keep it clear. |
 | **the printing-press binary** | The Go binary built from `cmd/printing-press/`. Commands: `generate`, `verify`, `emboss`, `scorecard`, `publish`, etc. Always say "printing-press binary" or "generator binary" — never just "the CLI" — when referring to this. |
-| **spec** | The API contract that drives generation — OpenAPI 3.0+ YAML/JSON, GraphQL SDL, or internal YAML format. Can come from catalog, URL, local file, or sniff discovery. Internal YAML specs may set `kind: synthetic` to declare a multi-source CLI where hand-built commands intentionally go beyond the spec; dogfood marks path-validity as skipped and scorecard excludes it from the tier-2 denominator. |
+| **spec** | The API contract that drives generation — OpenAPI 3.0+ YAML/JSON, GraphQL SDL, or internal YAML format. Can come from catalog, URL, local file, or browser-sniff discovery. Internal YAML specs may set `kind: synthetic` to declare a multi-source CLI where hand-built commands intentionally go beyond the spec; dogfood marks path-validity as skipped and scorecard excludes it from the tier-2 denominator. |
 | **API slug** | Normalized API name derived from the spec title via `cleanSpecName()`. Directory key in manuscripts (`manuscripts/<api-slug>/`). The CLI name is `<api-slug>-pp-cli`. Distinct from the CLI name — don't use them interchangeably. |
 | **brief** | The output of the machine's research phase (Phase 1) — a condensed doc covering API identity, competitors, data layer, and product thesis. Stored in `manuscripts/<api>/<run>/research/`. Drives all downstream decisions. |
-| **crowd sniff** / **sniff** | Discovery technique that scrapes npm, PyPI, and GitHub for unofficial API clients to learn undocumented endpoints, auth patterns, and rate limits. Produces a `discovery/` manuscript with sniff-report.md, HAR captures, and unique-paths.txt. Used when no official spec exists or to supplement one. |
-| **manuscript** | The full archive of a generation run. Contains three subdirectories: `research/` (briefs, spec analysis), `proofs/` (dogfood, verify, scorecard results), and optionally `discovery/` (sniff artifacts). Stored at `~/printing-press/manuscripts/<api-slug>/<run-id>/`. |
+| **browser-sniff** | Browser-driven API discovery. The user captures live traffic via browser automation (browser-use, agent-browser) or DevTools as a HAR; the `browser-sniff` subcommand analyzes the HAR and produces an OpenAPI-compatible spec. Produces a `discovery/` manuscript with `browser-sniff-report.md`, HAR captures, and `browser-sniff-unique-paths.txt`. Use when no official spec exists or to supplement one with endpoints the docs miss. |
+| **crowd-sniff** | Discovery technique that scrapes npm, PyPI, and GitHub for unofficial API clients to learn undocumented endpoints, auth patterns, and rate limits. Produces a `discovery/` manuscript with `crowd-sniff-report.md`. Complementary to browser-sniff — community-sourced vs. browser-captured. Used when no official spec exists or to supplement one. |
+| **manuscript** | The full archive of a generation run. Contains three subdirectories: `research/` (briefs, spec analysis), `proofs/` (dogfood, verify, scorecard results), and optionally `discovery/` (browser-sniff and crowd-sniff artifacts). Stored at `~/printing-press/manuscripts/<api-slug>/<run-id>/`. |
 | **emboss** | A second-pass improvement cycle for an already-printed CLI. Audits baseline, re-researches, identifies top improvements, rebuilds, re-verifies, reports delta. Subcommand: `printing-press emboss <api>`. Still active — not deprecated. |
 | **polish** | Targeted fix-up of a printed CLI (distinct from emboss's full cycle). Skill: `/printing-press-polish`. The retro improves the machine; polish improves the printed CLI. |
 | **retro** / **retrospective** | Post-generation analysis of *the machine itself* — not the printed CLI. Identifies systemic improvements to templates, the Go binary, skill instructions, or catalog. Output goes to `docs/retros/` and `manuscripts/<api>/<run>/proofs/`. |
@@ -172,7 +173,7 @@ This copies the internal skills to `~/.claude/skills/`.
 
 Skills use a `references/` directory for content that is only needed during specific phases or conditions. The SKILL.md stays lean with inline pointers (`Read [references/foo.md](...) when X`), and the agent loads the reference file only when the condition is met.
 
-**Why this matters:** SKILL.md content is loaded into the context window for every tool call in the session. A 2,000-line skill burns tokens on every phase — even phases that don't need most of the content. Extracting conditional sections (e.g., browser capture flows only needed when sniffing, codex templates only needed in codex mode) into reference files reduces baseline context by 30-40%.
+**Why this matters:** SKILL.md content is loaded into the context window for every tool call in the session. A 2,000-line skill burns tokens on every phase — even phases that don't need most of the content. Extracting conditional sections (e.g., browser capture flows only needed when browser-sniffing, codex templates only needed in codex mode) into reference files reduces baseline context by 30-40%.
 
 **What stays inline:** Cardinal rules, decision matrices, phase structure, user-facing prompts — anything the agent needs at all times or to decide whether to load more.
 
diff --git a/README.md b/README.md
index 7df6e255..27aca795 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ Claude Code, Codex, Gemini CLI, Cursor - they call CLIs thousands of times a day
 /printing-press emboss notion                        # Second pass: improve an existing CLI
 ```
 
-One command. Lean loop. Produces a Go CLI + MCP server that absorbs every feature from every competing tool, then transcends with compound use cases only possible with local data. REST, GraphQL, or sniffed traffic - no OpenAPI spec required.
+One command. Lean loop. Produces a Go CLI + MCP server that absorbs every feature from every competing tool, then transcends with compound use cases only possible with local data. REST, GraphQL, or browser-sniffed traffic - no OpenAPI spec required.
 
 ### Get it
 
@@ -162,14 +162,14 @@ The fast path is a lean loop. Artifacts still matter, but only when they directl
 Phase 0     Resolve + Reuse           (1-3 min)    Reuse research, detect tokens, resolve spec or URL
 Phase 1     Research Brief            (5-10 min)   API identity, competitors, data layer, product thesis
 Phase 1.5   Ecosystem Absorb Gate    (5-10 min)   Catalog every MCP/skill/CLI feature → absorb manifest + novel suggestions
-Phase 1.7   Sniff Gate (if needed)   (2-5 min)    Browser capture, HAR import, discovery provenance
+Phase 1.7   Browser-Sniff Gate          (2-5 min)    Browser capture, HAR import, discovery provenance
 Phase 2     Generate                  (1-2 min)    Go CLI + MCP server from spec with validation
 Phase 3     Build The GOAT            (10-20 min)  ALL absorbed features + transcendence commands
 Phase 4     Shipcheck                 (3-8 min)    Dogfood + verify --fix + scorecard as one verification block
 Phase 5     Live Smoke (optional)     (2-5 min)    Read-only API smoke + data-flow check
 ```
 
-**Three entry paths.** Got an OpenAPI spec? Use `--spec`. Got a URL to a website with no docs? The sniff gate launches a browser, captures traffic, and generates the spec. Got a HAR file from DevTools? Pass `--har`. The press handles all three.
+**Three entry paths.** Got an OpenAPI spec? Use `--spec`. Got a URL to a website with no docs? The browser-sniff gate launches a browser, captures traffic, and generates the spec. Got a HAR file from DevTools? Pass `--har`. The press handles all three.
 
 **18 APIs in the catalog.** Asana, DigitalOcean, Discord, Front, GitHub, HubSpot, LaunchDarkly, Pipedrive, Plaid, Postman, SendGrid, Sentry, Square, Stripe, Stytch, Telegram, Twilio - plus Petstore for testing. Each pre-verified with spec URL, auth type, and category.
 
@@ -218,9 +218,9 @@ Already generated a CLI? Emboss runs a focused improvement cycle: audit baseline
 
 **Source credits**: the generated README includes a "Sources & Inspiration" section crediting the ecosystem tools and competing projects studied during research.
 
-**Proxy-envelope support**: for APIs that wrap all requests in a POST envelope (like Postman's `_api/ws/proxy`), the press detects the pattern during sniffing and generates a specialized client.
+**Proxy-envelope support**: for APIs that wrap all requests in a POST envelope (like Postman's `_api/ws/proxy`), the press detects the pattern during browser-sniffing and generates a specialized client.
 
-**Adaptive rate limiting**: sniffed APIs get conservative pacing - start slow, ramp up on success, back off on 429s. Never aborts, always recovers.
+**Adaptive rate limiting**: APIs discovered by browser-sniff get conservative pacing - start slow, ramp up on success, back off on 429s. Never aborts, always recovers.
 
 **Tests**: minimum 1 test file per package (store, cli). Table-driven tests for data layer queries and workflow commands.
 
diff --git a/docs/plans/2026-04-18-002-refactor-rename-sniff-to-browser-sniff-plan.md b/docs/plans/2026-04-18-002-refactor-rename-sniff-to-browser-sniff-plan.md
new file mode 100644
index 00000000..f6fb8559
--- /dev/null
+++ b/docs/plans/2026-04-18-002-refactor-rename-sniff-to-browser-sniff-plan.md
@@ -0,0 +1,301 @@
+---
+title: Rename `sniff` to `browser-sniff`
+type: refactor
+status: active
+date: 2026-04-18
+deepened: 2026-04-18
+---
+
+# Rename `sniff` to `browser-sniff`
+
+## Overview
+
+Rename the `sniff` subcommand, its backing Go package, and all live references (skills, AGENTS.md glossary, README) to `browser-sniff`. The current name is misleading — the pair (`sniff` vs. `crowd-sniff`) hides the actual distinction: `sniff` analyzes a HAR file captured via a browser workflow, while `crowd-sniff` scrapes npm/GitHub. Renaming to `browser-sniff` makes the workflow explicit and keeps the pair parallel in tooling (browser-driven vs. community-sourced).
+
+The rename is live-only. Historical docs (past retros, brainstorms, plans, CHANGELOG, solutions) keep their original wording — those describe what existed at the time.
+
+## Problem Frame
+
+`sniff` is overloaded. In AGENTS.md the glossary even conflates `crowd sniff / sniff` in one row ("scrapes npm, PyPI, and GitHub…"), which is wrong — that's only `crowd-sniff`. The SKILL.md uses "Sniff Gate", "Pre-Sniff Auth Intelligence", and `SNIFF_TARGET_URL` while the underlying mechanism is specifically a browser-driven HAR capture. A new contributor reading the glossary can't tell what `sniff` actually does without reading the code.
+
+## Requirements Trace
+
+- R1. `printing-press browser-sniff` is the sole subcommand for this workflow. `printing-press sniff` is removed entirely — no alias, no deprecation period.
+- R2. AGENTS.md glossary accurately describes both discovery techniques and uses the new name.
+- R3. The printing-press skill (SKILL.md + references) uses `browser-sniff` consistently, including phase names, marker files, env vars, and reference file names.
+- R4. README and other live user-facing docs use the new name.
+- R5. Internal Go package and symbol names reflect the new name where the rename is cheap; where the rename is expensive and purely internal, stay consistent within the file but don't churn unrelated code.
+- R6. Historical documents (retros, brainstorms, past plans, CHANGELOG, solution writeups) are untouched.
+- R7. `go build`, `go test ./...`, and `golangci-lint run ./...` pass after the rename.
+
+## Scope Boundaries
+
+- Not renaming `crowd-sniff` — its name already names its data source accurately.
+- Not renaming the `--spec-source=sniffed` provenance value. That tag describes a spec's origin (live traffic vs. docs vs. official), not the command. Changing it would affect generated-CLI rate-limiting defaults and require a provenance-value migration that's out of scope for a naming cleanup.
+- Not rewriting historical docs under `docs/retros/`, `docs/brainstorms/`, `docs/plans/`, `docs/solutions/`, or `CHANGELOG.md`. Those are immutable records — any dated filename signals "captured at this point in time" and stays as-is. Prose inside a live solution doc may be updated where the doc is forward-pointing reference material, but filenames never change.
+- Not renaming git branches or prior commit messages.
+- Not migrating in-flight runstate artifacts (`sniff-gate.json`, `sniff-report.md`) on disk. Any user with a mid-flight run across the upgrade boundary must re-run Phase 1.7 to produce the renamed artifacts. Runstate is transient and this is documented in release notes.
+- Not preserving the old `sniff` command as an alias. Pre-1.0 + internal tool + all in-repo skill invocations updated in the same change set = clean break is safer than compatibility theater.
+
+### Deferred to Separate Tasks
+
+- Reconsidering `--spec-source=sniffed` → `--spec-source=browser-sniffed`: separate task, affects generated CLIs and provenance provenance metadata written into `.printing-press.json`.
+- Renaming `internal/websniff` package if Unit 2 is deferred (see Key Technical Decisions).
+
+## Context & Research
+
+### Relevant Code and Patterns
+
+- `internal/cli/sniff.go` — the cobra command, `newSniffCmd()`, `Use: "sniff"`. Imports `internal/websniff`.
+- `internal/cli/sniff_test.go` — tests against the command name and flags.
+- `internal/cli/root.go:51` — `rootCmd.AddCommand(newSniffCmd())`.
+- `internal/cli/crowd_sniff.go` — the sibling command; do not rename, but verify no cross-references break.
+- `internal/websniff/` — package consumed by three live Go files: `internal/cli/sniff.go`, `internal/cli/crowd_sniff.go` (imports `websniff` and calls `websniff.WriteSpec` at line 150), and `internal/generator/generator.go` (uses `websniff.FixtureSet`). Plus comment-level references in `internal/crowdsniff/aggregate.go:10` and `internal/crowdsniff/specgen.go:119`.
+- `internal/pipeline/research.go:288-292` — reads `sniff-report.md` from the discovery manuscript directory. This filename is a skill-produced artifact that crosses the Go-code / skill boundary.
+- `internal/generator/templates/root.go.tmpl:115` — templated rate-limit default guarded on `{{- if eq .SpecSource "sniffed"}}`. Provenance value, out of scope.
+- `skills/printing-press/SKILL.md` — 84 occurrences across phase names (Phase 1.6 Pre-Sniff Auth Intelligence, Phase 1.7 Sniff Gate, Phase 1.8 Crowd Sniff Gate), marker file (`sniff-gate.json`), env var (`SNIFF_TARGET_URL`), reference links.
+- `skills/printing-press/references/sniff-capture.md` — 59 occurrences; the entire file is about the browser capture workflow.
+- `skills/printing-press/references/crowd-sniff.md` — 8 occurrences; may link back to the capture reference.
+- `skills/printing-press-retro/SKILL.md` — retro skill surfaces sniff as a retro topic.
+- `AGENTS.md:62` — glossary row conflates the two and is factually wrong about `sniff`.
+- `README.md` — lines 16, 165, 172, 176, 221, 223 describe sniff as a user-facing capability.
+
+### Institutional Learnings
+
+- `docs/solutions/best-practices/sniff-and-crowd-sniff-complementary-discovery-2026-03-30.md` — the canonical writeup of the two-technique discovery design. This one should be updated to reflect the new name since it's forward-pointing reference material, not a dated retro snapshot.
+- `docs/solutions/best-practices/adaptive-rate-limiting-sniffed-apis.md` — describes runtime behavior for sniffed APIs. The filename uses "sniffed" as an adjective for the provenance tag (not the command), so leave it alone.
+
+## Key Technical Decisions
+
+- **D1: Clean break, no alias.** The `sniff` command is removed, not preserved as an alias. Justification: the only in-repo consumers (skill bash examples, retro skill references) are updated in the same change set (Unit 3). No external consumers have been identified. Pre-1.0 + internal tool = breakage is acceptable. An alias would create a contradiction with the `refactor(cli)!:` breaking-change commit and add ongoing maintenance cost for zero verified benefit.
+- **D2: Rename `internal/websniff` → `internal/browsersniff`.** The package has three live consumers (`internal/cli/sniff.go`, `internal/cli/crowd_sniff.go`, `internal/generator/generator.go`) plus two comment-level references in `internal/crowdsniff/`. The rename is mechanical and keeps the codebase coherent with the user-facing name. Bounded friction handling: one retry round on build failure, then revert Unit 2 and defer.
+- **D3: Rename skill-authored artifacts: `SNIFF_TARGET_URL` → `BROWSER_SNIFF_TARGET_URL`, `sniff-gate.json` → `browser-sniff-gate.json`, and `sniff-report.md` → `browser-sniff-report.md`.** The first two are read only by the skill. The third is written by the skill inside the discovery manuscript and *read by Go code* (`internal/pipeline/research.go` via `ParseDiscoveryPages`), so the rename must be coordinated across both sides in Unit 3. No read-side fallback for the old filename — consistent with the clean-break alias decision (D1). Marker file and report file renames are "forward only": existing published manuscripts containing `sniff-report.md` cannot be re-ingested by post-rename code; this is accepted, documented in release notes, and surfaces as a known edge case if users attempt to re-run emboss or retro flows against pre-rename manuscripts.
+- **D4: Phase names in SKILL.md stay numbered (1.6, 1.7, 1.8) but get their prose relabeled.** "Sniff Gate" → "Browser-Sniff Gate", "Pre-Sniff Auth Intelligence" → "Pre-Browser-Sniff Auth Intelligence". Long, but matches the pattern we're setting.
+- **D5: Leave `--spec-source=sniffed` alone for now, but acknowledge the drift.** The adjective there means "derived from captured traffic" and survives the command rename in meaning. However, after this plan lands, the only producer path that writes `sniffed` will be the renamed `browser-sniff` command — so the provenance value lags its producer. A future task (listed under Deferred to Separate Tasks) will migrate `sniffed` → `browser-sniffed` with provenance metadata compatibility. Near-term, add a one-line code comment near `internal/generator/templates/root.go.tmpl:115` and `internal/generator/generator.go:501` noting that `sniffed` is the legacy name for browser-captured provenance.
+- **D6: Do not modify historical docs, and do not rename dated filenames.** `docs/retros/`, `docs/brainstorms/`, past `docs/plans/`, `CHANGELOG.md`, and dated solution-doc filenames describe the state of the system at the time they were written. Filenames never change. Prose inside forward-pointing solution docs may be updated where the doc still guides current behavior.
+
+## Open Questions
+
+### Resolved During Planning
+
+- Should `crowd-sniff` be renamed? No — its name names its source (community SDKs) accurately. (See Phase 0 conversation with user.)
+- Should `--spec-source=sniffed` change? No — see D5.
+- Should historical docs be rewritten? No — see D6.
+
+### Deferred to Implementation
+
+- Whether any printed CLI in the public library repo (`mvanhorn/printing-press-library`) embeds the old command name in its README or `.printing-press.json` manifest. Unit 5 adds a grep audit step. If findings are non-empty, either file a follow-up PR against that repo or document drift in release notes.
+
+## Implementation Units
+
+- [ ] **Unit 1: Rename cobra command and test file**
+
+**Goal:** Change the subcommand name and update the test file in lockstep. Clean rename — no alias.
+
+**Requirements:** R1, R7
+
+**Dependencies:** None
+
+**Files:**
+- Rename: `internal/cli/sniff.go` → `internal/cli/browser_sniff.go`
+- Rename: `internal/cli/sniff_test.go` → `internal/cli/browser_sniff_test.go`
+- Modify: `internal/cli/root.go` (update `AddCommand` call — `newSniffCmd()` → `newBrowserSniffCmd()`)
+
+**Approach:**
+- `newSniffCmd()` → `newBrowserSniffCmd()`, `Use: "sniff"` → `Use: "browser-sniff"`.
+- No alias registration. No deprecation plumbing. Invoking `printing-press sniff` after this change returns cobra's standard "unknown command" error.
+- Test file updates: every test that constructs the command or asserts against `Use: "sniff"` switches to `"browser-sniff"`.
+
+**Patterns to follow:**
+- Existing `newCrowdSniffCmd()` in `internal/cli/crowd_sniff.go` for command structure.
+
+**Test scenarios:**
+- Happy path: `browser-sniff --har fixture.har --name foo --output out.yaml` produces the same OpenAPI YAML as the old `sniff` invocation produced against the same fixture (regression check via existing test fixtures).
+- Happy path: the `browser-sniff` command appears in `--help` output.
+- Error path: invoking `printing-press sniff` returns a cobra "unknown command" error (exit code non-zero). Assert on the error, not the absence of behavior — this pins the clean-break contract.
+- Error path: invalid flags on `browser-sniff` surface the same error shape as before.
+- Integration (sibling smoke test): `crowd-sniff --help` still works and its flag set is unchanged. Unit 1 modifies `root.go`, which registers both commands; this one-line check catches accidental breakage of the sibling command from shared changes.
+
+**Verification:**
+- `go test ./internal/cli/...` passes.
+- `printing-press browser-sniff --help` works; `printing-press sniff` fails with "unknown command"; top-level `printing-press --help` lists `browser-sniff` (and does not list `sniff`).
+
+- [ ] **Unit 2: Rename `internal/websniff` package to `internal/browsersniff`**
+
+**Goal:** Align the Go package name with the command name.
+
+**Requirements:** R5, R7
+
+**Dependencies:** Unit 1 (so the only consumer — the renamed command file — can be updated in one step)
+
+**Files:**
+- Rename: `internal/websniff/` → `internal/browsersniff/` (12 `.go` files move; every file's `package websniff` declaration — including the 6 `_test.go` files which are in-package tests — updates to `package browsersniff`)
+- Modify: `internal/cli/browser_sniff.go` (update import path and symbol qualifier from `websniff.X` to `browsersniff.X`)
+- Modify: `internal/cli/crowd_sniff.go` (update import and `websniff.WriteSpec` call at line 150)
+- Modify: `internal/generator/generator.go` (update import at line 20, `websniff.FixtureSet` type on the `Generator` struct at line 123, and consumption sites around lines 704-705; a full `grep websniff internal/generator/` lists every hit)
+- Modify: `internal/crowdsniff/specgen.go` (comment reference at line 119 — "adapted from websniff/specgen.go")
+- Modify: `internal/crowdsniff/aggregate.go` (comment reference at line 10 — "from websniff/classifier.go")
+- Modify: any other file that imports or comment-references `internal/websniff` discovered in the pre-change grep (expected: the five above)
+
+**Approach:**
+- Change package declaration in every file under the directory from `package websniff` to `package browsersniff`.
+- `go mod tidy` and `go build ./...` validate import resolution.
+- Friction decision gate: if `go build ./...` fails after renaming the directory and updating the listed import/comment sites in one pass, attempt no more than one round of follow-up fixes (e.g., `go:embed` path adjustments, test-helper path changes). If it still fails, revert Unit 2 and defer the package rename to a separate refactor. Record the failure mode so the follow-up has a starting point. The package rename is valuable but not load-bearing — Unit 1 (command rename) is the high-value change and stands alone.
+
+**Test scenarios:**
+- Test expectation: none — this is a pure rename with no behavior change. Existing test suite (`go test ./internal/browsersniff/... ./internal/cli/...`) must pass unchanged as the correctness check.
+
+**Verification:**
+- `go build ./...`, `go vet ./...`, `go test ./...`, `golangci-lint run ./...` all pass.
+- `grep -r "websniff" internal/` returns zero matches (except possibly `.gocache/`, which is machine-local and disposable).
+
+- [ ] **Unit 3: Update skill artifacts, prose, and Go-side artifact reader (single PR)**
+
+**Goal:** Coherently update skill artifact names, all prose references in the printing-press skills, and the Go code that reads skill-produced artifacts. Land as one commit/PR so the skill and its consumers stay in lockstep.
+
+**Requirements:** R3
+
+**Dependencies:** Unit 1 (skill bash examples invoke `browser-sniff`, which must exist).
+
+**Files:**
+Skill artifacts:
+- Rename: `skills/printing-press/references/sniff-capture.md` → `skills/printing-press/references/browser-sniff-capture.md`
+- Modify: `skills/printing-press/references/crowd-sniff.md` (cross-links to the renamed reference)
+- Modify: `skills/printing-press/references/browser-sniff-capture.md` (title, headings, self-references)
+- Modify: `skills/printing-press/references/deepwiki-research.md` (single mention — verify wording fits)
+- Modify: `skills/printing-press/references/voice.md` (if any mentions — verify)
+
+Skill prose:
+- Modify: `skills/printing-press/SKILL.md` (84 occurrences across phase names, artifact references, bash examples, cross-links)
+- Modify: `skills/printing-press-retro/SKILL.md`
+- Modify: `skills/printing-press-retro/references/issue-template.md`
+
+Go-side artifact reader:
+- Modify: `internal/pipeline/research.go` (update `ParseDiscoveryPages` to read `browser-sniff-report.md` instead of `sniff-report.md`)
+- Modify: `internal/pipeline/research_test.go` (update fixture filenames)
+- Modify: `internal/pipeline/climanifest_test.go` (lines 495 and 505 write/read the literal `sniff-report.md` filename in an archive round-trip test — update to `browser-sniff-report.md` in lockstep with the reader)
+
+**Approach:**
+Artifact renames (skill write-side + Go read-side, single atomic change):
+- `SNIFF_TARGET_URL` → `BROWSER_SNIFF_TARGET_URL` in every SKILL.md and reference file mention.
+- `sniff-gate.json` → `browser-sniff-gate.json` in SKILL.md phase contracts, reference docs, and `$PRESS_RUNSTATE/runs/$RUN_ID/` paths.
+- `sniff-report.md` → `browser-sniff-report.md` in skill prose AND in the three `internal/pipeline/` files. No fallback read for the old filename (D3 clean break).
+
+Prose sweep:
+- "Sniff Gate" → "Browser-Sniff Gate"
+- "Pre-Sniff Auth Intelligence" → "Pre-Browser-Sniff Auth Intelligence"
+- "sniff gate" (lowercase prose) → "browser-sniff gate"
+- "the sniff" (as a noun) → "the browser-sniff" or rephrase for readability
+- "sniffing" (as a verb) → "browser-sniffing" OR rephrase (e.g., "capturing browser traffic") — choose whichever reads better per sentence
+- "sniff capture" → "browser-sniff capture"
+- Inline bash example `printing-press sniff --har ...` → `printing-press browser-sniff --har ...`
+- `crowd-sniff` prose is unchanged.
+- Reading pass after each file: if "Pre-Browser-Sniff Auth Intelligence" makes a sentence unreadable, rewrite rather than mechanically substitute.
+
+**Test scenarios:**
+- Happy path (Go reader): `ParseDiscoveryPages` reads `browser-sniff-report.md` from a fixture discovery dir and returns the expected URL list (update `internal/pipeline/research_test.go` fixtures).
+- Error path: `ParseDiscoveryPages` returns the standard "file not found" error shape when `browser-sniff-report.md` is missing. This is the new contract — pre-rename manuscripts are not read.
+- Error path (clean-break pin): when only `sniff-report.md` exists in the discovery dir (simulating a pre-rename manuscript) and `browser-sniff-report.md` is absent, `ParseDiscoveryPages` returns not-found — it does NOT silently read the legacy filename. This test pins D3's no-fallback contract and guards against future regression if anyone adds silent fallback-read logic.
+- Integration: `internal/pipeline/climanifest_test.go` archive round-trip passes with `browser-sniff-report.md` as the filename on both write and read sides.
+
+**Verification:**
+- `go test ./internal/pipeline/...` passes.
+- All markdown cross-references resolve (no broken links pointing to `sniff-capture.md`).
+- `grep -rn "\bsniff\b" skills/ | grep -v "browser-sniff\|crowd-sniff"` returns zero hits.
+- `grep -rn "sniff-capture\.md\|SNIFF_TARGET_URL\|sniff-gate\.json\|sniff-report\.md" skills/ internal/ docs/solutions/` returns zero live-doc hits.
+
+- [ ] **Unit 4: Update AGENTS.md glossary and other live docs (prose only — no filename renames)**
+
+**Goal:** Fix the incorrect AGENTS.md glossary entry and update user-facing docs.
+
+**Requirements:** R2, R4, R6
+
+**Dependencies:** None (can run in parallel with Units 2–3)
+
+**Files:**
+- Modify: `AGENTS.md` (glossary line 62 + 3 total occurrences)
+- Modify: `README.md` (lines 16, 165, 172, 176, 221, 223)
+- Modify: `docs/solutions/best-practices/sniff-and-crowd-sniff-complementary-discovery-2026-03-30.md` (**prose only** — filename unchanged per D6)
+- Modify: `docs/solutions/best-practices/multi-source-api-discovery-design-2026-03-30.md` (**prose only** — 7 mentions)
+
+**Approach:**
+- AGENTS.md fix: the current row `**crowd sniff** / **sniff**` is factually wrong — `sniff` does not scrape npm/PyPI/GitHub. Split into two rows:
+  - `**browser-sniff**` — "Browser-driven API discovery. User captures live traffic via browser automation (browser-use, agent-browser) or DevTools as a HAR; the `browser-sniff` subcommand analyzes the HAR and produces an OpenAPI-compatible spec. Use when no official spec exists or to supplement one with endpoints the docs miss."
+  - `**crowd-sniff**` — existing definition, unchanged except for hyphenation.
+- README: substitute `sniff` → `browser-sniff` in user-facing prose. The line `browser capture, HAR import, discovery provenance` stays as-is.
+- Solution doc content update: update prose inside `sniff-and-crowd-sniff-complementary-discovery-2026-03-30.md` to reference `browser-sniff`. Filename stays (dated file = historical record per D6). Optionally add a one-line note at the top: "Command names updated from `sniff` → `browser-sniff` after 2026-04-18; captured here for continuity."
+- `adaptive-rate-limiting-sniffed-apis.md` stays unchanged (the "sniffed" adjective refers to provenance, not the command — see D5).
+
+**Test scenarios:**
+- Test expectation: none — documentation.
+
+**Verification:**
+- AGENTS.md glossary now has accurate, split rows for the two techniques.
+- `grep -n "\bsniff\b" AGENTS.md README.md` returns only intentional uses within `browser-sniff` or `crowd-sniff`.
+- No solution doc filenames changed — inbound links preserved.
+
+- [ ] **Unit 5: Sweep live references, audit public library repo, verify historical docs untouched**
+
+**Goal:** Final sweep. Catch stragglers, confirm the rename is complete on live surfaces, audit the public library repo for stale references, and verify historical docs are untouched.
+
+**Requirements:** R2, R3, R4, R6
+
+**Dependencies:** Units 1–4
+
+**Files:**
+- Read-only: `docs/plans/`, `docs/retros/`, `docs/brainstorms/`, `CHANGELOG.md` (confirm untouched)
+- Modify: any live file discovered by the sweep
+
+**Approach:**
+- Run `grep -rn "\bsniff\b" .` excluding `.git/`, `.gocache/`, `docs/retros/`, `docs/brainstorms/`, `docs/plans/` (except plans created on/after 2026-04-18), `CHANGELOG.md`, and `node_modules/`.
+- Classify each hit: (a) already-correct `browser-sniff`/`crowd-sniff` compound → ignore, (b) historical snapshot → ignore, (c) live reference that slipped through → fix.
+- **Public library repo audit:** Clone or fetch `mvanhorn/printing-press-library` (read-only check) and grep its contents for `printing-press sniff` or standalone `sniff` references in published CLI READMEs and `.printing-press.json` manifests. If findings are non-empty, either file a follow-up PR in that repo or document the drift in this release's notes. Surface the result (count + sample paths) in the PR description.
+- **Release-please dry-run:** Before creating the PR, run release-please locally (or in a branch) to confirm whether `refactor(cli)!:` triggers a minor or major bump under the current repo config. Record the result in the PR description. If the bump is unexpected (major pre-1.0), pause and discuss before merging.
+
+**Test scenarios:**
+- Test expectation: none — verification-only pass.
+
+**Verification:**
+- `go build ./...`, `go vet ./...`, `go test ./...`, `golangci-lint run ./...` all pass.
+- `gofmt -w ./...` produces no changes.
+- Manual smoke test: build the binary, run `./printing-press browser-sniff --help`, `./printing-press sniff` (returns "unknown command" error), `./printing-press --help` (shows `browser-sniff`, does not show `sniff`).
+- Final grep sweep shows zero bare `sniff` tokens in live files.
+- Public library audit result recorded in PR description.
+- Release-please dry-run result recorded in PR description.
+- D5 code comments present: `grep -A1 "sniffed" internal/generator/generator.go internal/generator/templates/root.go.tmpl | grep -q "legacy name"` (or equivalent) confirms the legacy-name explanatory comment was added near the `SpecSource == "sniffed"` branch.
+
+## System-Wide Impact
+
+- **Interaction graph:** `printing-press sniff` stops existing. Every in-repo skill invocation (bash examples in SKILL.md, retro skill references) is updated in Unit 3 in the same change set. After merge, no internal path invokes the old name.
+- **Error propagation:** No change to the `browser-sniff` command's behavior. The old `sniff` invocation returns cobra's standard "unknown command" error.
+- **State lifecycle risks:** Runstate artifacts (`sniff-gate.json`, `sniff-report.md`) are renamed forward-only. A user mid-pipeline across the upgrade boundary re-enters Phase 1.7 and writes the new-name artifacts. Pre-rename published manuscripts in `~/printing-press/manuscripts/` cannot be re-consumed by emboss/retro flows without manual filename migration — accepted, documented in release notes.
+- **API surface parity:** The `crowd-sniff` command's CLI surface is unchanged.
+- **Integration coverage:** The `internal/pipeline/climanifest_test.go` archive round-trip test covers the Go reader + skill writer contract for `browser-sniff-report.md`. Unit 3 updates both sides in lockstep.
+- **Public library repo:** Previously-published CLIs in `mvanhorn/printing-press-library` may contain stale `sniff` references in their generated READMEs or manifests. Unit 5 grep-audits this surface; findings drive either a follow-up PR or release-note disclosure.
+- **Unchanged invariants:** `crowd-sniff` command name, behavior, and flags. The `--spec-source=sniffed` provenance value and its generator-template branch (migration deferred — D5). The `internal/crowdsniff/` package. The adaptive-rate-limiting solution doc name. Locally-generated printed CLIs in `~/printing-press/library/` (they regenerate from templates, which don't reference the command name).
+
+## Risks & Dependencies
+
+| Risk | Mitigation |
+|------|------------|
+| A historical doc in `docs/plans/` references `sniff` in a way that becomes misleading after the rename | Leave historical docs untouched per D6. Plans describe the system as it was when written; readers understand plans are dated. |
+| `internal/websniff` package rename has hidden consumers beyond the three known live imports | Unit 2 starts with a fresh `grep -r "websniff" .` (excluding `.git/`, `.gocache/`). If additional consumers exist, include them in Unit 2. If `go build ./...` fails, try one round of follow-up fixes then revert Unit 2 and defer — the command rename (Unit 1) is the high-value change and stands alone. |
+| Skill prose becomes unreadable with long compound names like "Pre-Browser-Sniff Auth Intelligence" | Unit 3 includes an explicit reading pass; rewrite rather than mechanically substitute when prose breaks. |
+| Downstream scripts or external consumers shell to `printing-press sniff` | Accepted breakage. Pre-1.0 + internal tool; no verified external consumers. Release notes call out the removal prominently. If a consumer surfaces post-release, they pin to the previous version or update their invocation. |
+| Pre-rename manuscripts in `~/printing-press/manuscripts/` contain `sniff-report.md`, making them unreadable by post-rename emboss/retro flows | Accepted. Document in release notes. Users with mission-critical pre-rename manuscripts can rename the file manually (single `mv`) or re-run Phase 1.7 to regenerate. |
+| `refactor(cli)!:` commit triggers unexpected release-please version bump | Unit 5 includes a release-please dry-run step before merge. If the bump is unexpected (major pre-1.0), pause and discuss before merging. |
+| Public library repo CLIs contain stale `sniff` references | Unit 5 audits this surface. Findings drive a follow-up PR in the library repo or a release-note disclosure. Not blocking this PR. |
+
+## Documentation / Operational Notes
+
+- Release notes entry (next release):
+  > **BREAKING:** `printing-press sniff` has been renamed to `printing-press browser-sniff` to clarify the distinction vs. `crowd-sniff`. The old `sniff` command has been removed — no alias. Update any scripts or invocations. Skill-authored artifacts also renamed: `sniff-gate.json` → `browser-sniff-gate.json`, `sniff-report.md` → `browser-sniff-report.md`, `SNIFF_TARGET_URL` → `BROWSER_SNIFF_TARGET_URL`. In-flight runs re-generate the new artifacts at Phase 1.7; pre-rename published manuscripts cannot be read by post-rename emboss/retro flows without a manual `mv`.
+- The CHANGELOG is auto-generated by release-please from conventional commits. The commit message (`refactor(cli)!: rename sniff to browser-sniff`) carries the breaking-change signal. Unit 5 runs a release-please dry-run to confirm the actual version bump under the current repo config before merge.
+- Skills documentation surfaces the new name via the printing-press plugin manifest; no manual plugin-catalog update needed.
+
+## Sources & References
+
+- Plan originated from conversation: user observed that "sniff" is a misleading name vs. "crowd sniff" since the former is fundamentally browser-driven.
+- Related code: `internal/cli/sniff.go`, `internal/cli/root.go`, `internal/websniff/`, `skills/printing-press/SKILL.md`, `AGENTS.md`.
+- Prior plan context: `docs/plans/2026-03-29-003-feat-crowd-sniff-plan.md` (established crowd-sniff as the sibling command).
+- Prior solution doc: `docs/solutions/best-practices/sniff-and-crowd-sniff-complementary-discovery-2026-03-30.md` (prose updated per Unit 4; filename preserved per D6).
diff --git a/docs/solutions/best-practices/sniff-and-crowd-sniff-complementary-discovery-2026-03-30.md b/docs/solutions/best-practices/sniff-and-crowd-sniff-complementary-discovery-2026-03-30.md
index 4b52e596..5641a6b0 100644
--- a/docs/solutions/best-practices/sniff-and-crowd-sniff-complementary-discovery-2026-03-30.md
+++ b/docs/solutions/best-practices/sniff-and-crowd-sniff-complementary-discovery-2026-03-30.md
@@ -1,5 +1,5 @@
 ---
-title: "Sniff and Crowd-Sniff: Complementary API Discovery for CLI Generation"
+title: "Browser-Sniff and Crowd-Sniff: Complementary API Discovery for CLI Generation"
 date: 2026-03-30
 category: best-practices
 module: API discovery pipeline
@@ -8,21 +8,21 @@ component: tooling
 severity: medium
 applies_when:
   - Generating a CLI for an API that lacks a published OpenAPI spec
-  - Deciding whether to use sniff, crowd-sniff, or both during Phase 1
+  - Deciding whether to use browser-sniff, crowd-sniff, or both during Phase 1
   - Evaluating which discovery method produces better CLI coverage
 tags:
   - api-discovery
-  - sniff
+  - browser-sniff
   - crowd-sniff
   - pipeline-strategy
   - spec-generation
 ---
 
-# Sniff and Crowd-Sniff: Complementary API Discovery for CLI Generation
+# Browser-Sniff and Crowd-Sniff: Complementary API Discovery for CLI Generation
 
 ## Context
 
-Printing-press generates CLIs from API specs. Many public APIs don't publish specs. Two discovery commands fill this gap — `sniff` (Phase 1.7) and `crowd-sniff` (Phase 1.8) — but they discover fundamentally different things and work best together.
+Printing-press generates CLIs from API specs. Many public APIs don't publish specs. Two discovery commands fill this gap — `browser-sniff` (Phase 1.7) and `crowd-sniff` (Phase 1.8) — but they discover fundamentally different things and work best together.
 
 Understanding when to use each, and why they're complementary rather than competing, is essential for producing the best CLI coverage.
 
@@ -30,13 +30,13 @@ Understanding when to use each, and why they're complementary rather than compet
 
 ### What each discovers
 
-**Sniff** browses a live web app headlessly, captures HTTP traffic, and reverse-engineers a spec from observed requests/responses. It sees what the web application does.
+**Browser-Sniff** browses a live web app headlessly, captures HTTP traffic, and reverse-engineers a spec from observed requests/responses. It sees what the web application does.
 
 **Crowd-sniff** searches npm SDKs and GitHub code to find what developers have already mapped. It sees what developers need.
 
 These are different sets:
 
-| Signal | Sniff | Crowd-sniff |
+| Signal | Browser-Sniff | Crowd-sniff |
 |--------|-------|-------------|
 | Source | One browsing session | Thousands of developers |
 | What it finds | What the web app does | What developers need |
@@ -49,14 +49,14 @@ These are different sets:
 
 ### When to use each
 
-**Use sniff alone** when:
+**Use browser-sniff alone** when:
 - The API has no SDKs on npm (rare for popular APIs)
 - The web app is the primary interface and you want to capture exactly what it does
 - You need response body examples for richer spec generation
 
 **Use crowd-sniff alone** when:
 - Browser automation isn't available or is unreliable
-- The API requires login (sniff skips auth-required sites)
+- The API requires login (browser-sniff skips auth-required sites)
 - Speed matters (crowd-sniff runs without a browser, typically 2-4 minutes)
 - You want popularity-weighted endpoint coverage
 
@@ -68,10 +68,10 @@ These are different sets:
 ### How they combine in the pipeline
 
 ```
-Phase 1.7: Sniff Gate
+Phase 1.7: Browser-Sniff Gate
   → Produces sniff-spec.yaml (endpoints from live traffic)
 
-Phase 1.8: Crowd Sniff Gate
+Phase 1.8: Crowd-Sniff Gate
   → Produces crowd-spec.yaml (endpoints from npm + GitHub)
 
 Phase 2: Generate
@@ -92,41 +92,41 @@ A spec treats all endpoints equally. Crowd-sniff doesn't — an endpoint found i
 
 For any API popular enough to want a CLI for, someone has already mapped it in code. An npm SDK has every endpoint the vendor tested and ships. GitHub code from hundreds of repos shows real-world usage patterns. Crowd-sniff turns this existing community knowledge into a structured spec — no browsing, no traffic capture, no manual documentation.
 
-Sniff captures the API's behavior. Crowd-sniff captures the community's intent. Together they produce the most complete picture available.
+Browser-Sniff captures the API's behavior. Crowd-sniff captures the community's intent. Together they produce the most complete picture available.
 
 ## Why This Matters
 
-Without sniff or crowd-sniff, an API without a published spec is a dead end for CLI generation. With both, printing-press can generate a CLI for virtually any public REST API — the two discovery methods close the gap between "APIs that document themselves" and "APIs that don't."
+Without browser-sniff or crowd-sniff, an API without a published spec is a dead end for CLI generation. With both, printing-press can generate a CLI for virtually any public REST API — the two discovery methods close the gap between "APIs that document themselves" and "APIs that don't."
 
 The complementary nature also improves quality beyond coverage:
-- Sniff provides response body examples that crowd-sniff can't (it only finds paths and methods)
-- Crowd-sniff provides auth patterns that sniff can't (web apps use cookies; SDKs use API keys)
-- Cross-source agreement (an endpoint found by both sniff and crowd-sniff) is a strong confidence signal
+- Browser-Sniff provides response body examples that crowd-sniff can't (it only finds paths and methods)
+- Crowd-sniff provides auth patterns that browser-sniff can't (web apps use cookies; SDKs use API keys)
+- Cross-source agreement (an endpoint found by both browser-sniff and crowd-sniff) is a strong confidence signal
 
 ## When to Apply
 
 - Every time you run the printing-press skill for an API without a published spec
 - When evaluating whether a generated CLI has sufficient endpoint coverage
-- When the skill asks whether to run sniff or crowd-sniff — the answer is usually "both"
+- When the skill asks whether to run browser-sniff or crowd-sniff — the answer is usually "both"
 - When adding new discovery methods to the pipeline (they should be complementary, not replacing)
 
 ## Examples
 
 ### API with published spec + SDK (e.g., Notion)
 
-Crowd-sniff's `@notionhq/client` SDK produces endpoints that closely match the official spec. Sniff captures the web app's internal API calls (some of which use different paths or additional endpoints not in the public API). Using both reveals the gap between the public API and the internal API.
+Crowd-sniff's `@notionhq/client` SDK produces endpoints that closely match the official spec. Browser-Sniff captures the web app's internal API calls (some of which use different paths or additional endpoints not in the public API). Using both reveals the gap between the public API and the internal API.
 
 ### API with no spec, no SDK (e.g., obscure SaaS)
 
-Sniff is the primary discovery method — browse the web app and capture what it does. Crowd-sniff may find GitHub code snippets from users calling the API directly, but coverage will be thinner. The skill falls back to `--docs` generation if neither produces results.
+Browser-Sniff is the primary discovery method — browse the web app and capture what it does. Crowd-sniff may find GitHub code snippets from users calling the API directly, but coverage will be thinner. The skill falls back to `--docs` generation if neither produces results.
 
 ### API with SDKs but no web app (e.g., infrastructure APIs)
 
-Crowd-sniff is the primary method — the SDK maps the entire API surface. Sniff has nothing to browse. This is where crowd-sniff's value is most clear: it turns published SDK code into a CLI spec without any human intervention.
+Crowd-sniff is the primary method — the SDK maps the entire API surface. Browser-Sniff has nothing to browse. This is where crowd-sniff's value is most clear: it turns published SDK code into a CLI spec without any human intervention.
 
 ## Related
 
 - `docs/solutions/best-practices/multi-source-api-discovery-design-2026-03-30.md` — technical design patterns used in crowd-sniff (testable HTTP clients, errgroup, path normalization, tarball security)
-- `docs/solutions/best-practices/adaptive-rate-limiting-sniffed-apis.md` — rate limiting for CLIs generated from sniffed specs
+- `docs/solutions/best-practices/adaptive-rate-limiting-sniffed-apis.md` — rate limiting for CLIs generated from browser-sniffed specs
 - `docs/brainstorms/2026-03-29-crowd-sniff-requirements.md` — origin requirements document
 - `docs/plans/2026-03-29-003-feat-crowd-sniff-plan.md` — implementation plan
diff --git a/internal/websniff/capture.go b/internal/browsersniff/capture.go
similarity index 98%
rename from internal/websniff/capture.go
rename to internal/browsersniff/capture.go
index 9f2ac0d4..62f41e9a 100644
--- a/internal/websniff/capture.go
+++ b/internal/browsersniff/capture.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 import (
 	"bytes"
diff --git a/internal/websniff/classifier.go b/internal/browsersniff/classifier.go
similarity index 99%
rename from internal/websniff/classifier.go
rename to internal/browsersniff/classifier.go
index 6bcb08d4..2606f423 100644
--- a/internal/websniff/classifier.go
+++ b/internal/browsersniff/classifier.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 import (
 	"encoding/json"
diff --git a/internal/websniff/classifier_test.go b/internal/browsersniff/classifier_test.go
similarity index 99%
rename from internal/websniff/classifier_test.go
rename to internal/browsersniff/classifier_test.go
index 8d6583f1..f51636c5 100644
--- a/internal/websniff/classifier_test.go
+++ b/internal/browsersniff/classifier_test.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 import (
 	"testing"
diff --git a/internal/websniff/fixtures.go b/internal/browsersniff/fixtures.go
similarity index 99%
rename from internal/websniff/fixtures.go
rename to internal/browsersniff/fixtures.go
index 66d952c7..44b00426 100644
--- a/internal/websniff/fixtures.go
+++ b/internal/browsersniff/fixtures.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 import (
 	"encoding/json"
diff --git a/internal/websniff/fixtures_test.go b/internal/browsersniff/fixtures_test.go
similarity index 99%
rename from internal/websniff/fixtures_test.go
rename to internal/browsersniff/fixtures_test.go
index 6c9ce90d..6ee49416 100644
--- a/internal/websniff/fixtures_test.go
+++ b/internal/browsersniff/fixtures_test.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 import (
 	"path/filepath"
diff --git a/internal/websniff/parser.go b/internal/browsersniff/parser.go
similarity index 98%
rename from internal/websniff/parser.go
rename to internal/browsersniff/parser.go
index f025cc7c..bdd09d18 100644
--- a/internal/websniff/parser.go
+++ b/internal/browsersniff/parser.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 import (
 	"encoding/json"
diff --git a/internal/websniff/parser_test.go b/internal/browsersniff/parser_test.go
similarity index 99%
rename from internal/websniff/parser_test.go
rename to internal/browsersniff/parser_test.go
index 1f85acb6..c8511e89 100644
--- a/internal/websniff/parser_test.go
+++ b/internal/browsersniff/parser_test.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 import (
 	"os"
diff --git a/internal/websniff/schema.go b/internal/browsersniff/schema.go
similarity index 99%
rename from internal/websniff/schema.go
rename to internal/browsersniff/schema.go
index c33ff281..df00dda1 100644
--- a/internal/websniff/schema.go
+++ b/internal/browsersniff/schema.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 import (
 	"encoding/json"
diff --git a/internal/websniff/schema_test.go b/internal/browsersniff/schema_test.go
similarity index 99%
rename from internal/websniff/schema_test.go
rename to internal/browsersniff/schema_test.go
index 2f777e6d..ce869f6d 100644
--- a/internal/websniff/schema_test.go
+++ b/internal/browsersniff/schema_test.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 import (
 	"testing"
diff --git a/internal/websniff/specgen.go b/internal/browsersniff/specgen.go
similarity index 99%
rename from internal/websniff/specgen.go
rename to internal/browsersniff/specgen.go
index c64e40ab..ea3b8246 100644
--- a/internal/websniff/specgen.go
+++ b/internal/browsersniff/specgen.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 import (
 	"encoding/json"
diff --git a/internal/websniff/specgen_test.go b/internal/browsersniff/specgen_test.go
similarity index 99%
rename from internal/websniff/specgen_test.go
rename to internal/browsersniff/specgen_test.go
index c4be9bdd..d04b10bd 100644
--- a/internal/websniff/specgen_test.go
+++ b/internal/browsersniff/specgen_test.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 import (
 	"os"
diff --git a/internal/websniff/types.go b/internal/browsersniff/types.go
similarity index 99%
rename from internal/websniff/types.go
rename to internal/browsersniff/types.go
index 3bdb0c89..a57af9c6 100644
--- a/internal/websniff/types.go
+++ b/internal/browsersniff/types.go
@@ -1,4 +1,4 @@
-package websniff
+package browsersniff
 
 type HAR struct {
 	Log HARLog `json:"log"`
diff --git a/internal/cli/sniff.go b/internal/cli/browser_sniff.go
similarity index 82%
rename from internal/cli/sniff.go
rename to internal/cli/browser_sniff.go
index 4ae44ea6..826334ce 100644
--- a/internal/cli/sniff.go
+++ b/internal/cli/browser_sniff.go
@@ -5,11 +5,11 @@ import (
 	"net/url"
 	"strings"
 
-	"github.com/mvanhorn/cli-printing-press/internal/websniff"
+	"github.com/mvanhorn/cli-printing-press/internal/browsersniff"
 	"github.com/spf13/cobra"
 )
 
-func newSniffCmd() *cobra.Command {
+func newBrowserSniffCmd() *cobra.Command {
 	var harPath string
 	var outputPath string
 	var name string
@@ -17,18 +17,18 @@ func newSniffCmd() *cobra.Command {
 	var authFrom string
 
 	cmd := &cobra.Command{
-		Use:   "sniff",
+		Use:   "browser-sniff",
 		Short: "Analyze captured web traffic to discover API endpoints and generate a spec",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			websniff.SetAdditionalBlocklist(splitCSV(blocklist))
+			browsersniff.SetAdditionalBlocklist(splitCSV(blocklist))
 
-			capture, err := websniff.LoadCapture(harPath)
+			capture, err := browsersniff.LoadCapture(harPath)
 			if err != nil {
 				return fmt.Errorf("loading capture: %w", err)
 			}
 
 			if authFrom != "" {
-				authCapture, err := websniff.ParseEnriched(authFrom)
+				authCapture, err := browsersniff.ParseEnriched(authFrom)
 				if err != nil {
 					return fmt.Errorf("reading auth capture: %w", err)
 				}
@@ -38,7 +38,7 @@ func newSniffCmd() *cobra.Command {
 				capture.Auth = authCapture.Auth
 			}
 
-			apiSpec, err := websniff.AnalyzeCapture(capture)
+			apiSpec, err := browsersniff.AnalyzeCapture(capture)
 			if err != nil {
 				return fmt.Errorf("analyzing capture: %w", err)
 			}
@@ -49,10 +49,10 @@ func newSniffCmd() *cobra.Command {
 			}
 
 			if outputPath == "" {
-				outputPath = websniff.DefaultCachePath(apiSpec.Name)
+				outputPath = browsersniff.DefaultCachePath(apiSpec.Name)
 			}
 
-			if err := websniff.WriteSpec(apiSpec, outputPath); err != nil {
+			if err := browsersniff.WriteSpec(apiSpec, outputPath); err != nil {
 				return fmt.Errorf("writing spec: %w", err)
 			}
 
@@ -95,7 +95,7 @@ func splitCSV(value string) []string {
 	return out
 }
 
-func validateAuthDomainBinding(authCapture *websniff.EnrichedCapture, targetCapture *websniff.EnrichedCapture) error {
+func validateAuthDomainBinding(authCapture *browsersniff.EnrichedCapture, targetCapture *browsersniff.EnrichedCapture) error {
 	if authCapture == nil || authCapture.Auth == nil || strings.TrimSpace(authCapture.Auth.BoundDomain) == "" {
 		return nil
 	}
@@ -112,7 +112,7 @@ func validateAuthDomainBinding(authCapture *websniff.EnrichedCapture, targetCapt
 	return fmt.Errorf("auth captured for %s cannot be used with %s (domain mismatch)", authCapture.Auth.BoundDomain, targetDomain)
 }
 
-func captureDomain(capture *websniff.EnrichedCapture) string {
+func captureDomain(capture *browsersniff.EnrichedCapture) string {
 	if capture == nil {
 		return ""
 	}
@@ -133,7 +133,7 @@ func captureDomain(capture *websniff.EnrichedCapture) string {
 	return normalizeDomain(parsed.Hostname())
 }
 
-func commonCaptureBaseURL(capture *websniff.EnrichedCapture) string {
+func commonCaptureBaseURL(capture *browsersniff.EnrichedCapture) string {
 	counts := make(map[string]int)
 	best := ""
 	bestCount := 0
diff --git a/internal/cli/browser_sniff_test.go b/internal/cli/browser_sniff_test.go
new file mode 100644
index 00000000..0a5f3b1e
--- /dev/null
+++ b/internal/cli/browser_sniff_test.go
@@ -0,0 +1,84 @@
+package cli
+
+import (
+	"bytes"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/spf13/cobra"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestBrowserSniffCmdRejectsDomainMismatchOnAuthFrom(t *testing.T) {
+	t.Parallel()
+
+	cmd := newBrowserSniffCmd()
+	outputPath := filepath.Join(t.TempDir(), "spec.yaml")
+	cmd.SetArgs([]string{
+		"--har", filepath.Join("..", "..", "testdata", "sniff", "sample-enriched.json"),
+		"--auth-from", filepath.Join("..", "..", "testdata", "sniff", "sample-auth-capture-mismatch.json"),
+		"--output", outputPath,
+	})
+
+	err := cmd.Execute()
+	require.Error(t, err)
+	assert.EqualError(t, err, "auth captured for other.example.com cannot be used with hn.algolia.com (domain mismatch)")
+}
+
+// newRootCmdForTest mirrors Execute()'s command tree construction for test-level
+// command dispatch assertions.
+func newRootCmdForTest() *cobra.Command {
+	root := &cobra.Command{Use: "printing-press", SilenceUsage: true, SilenceErrors: true}
+	root.AddCommand(newBrowserSniffCmd())
+	root.AddCommand(newCrowdSniffCmd())
+	return root
+}
+
+func TestLegacySniffCommandReturnsUnknownCommand(t *testing.T) {
+	t.Parallel()
+
+	root := newRootCmdForTest()
+	root.SetArgs([]string{"sniff", "--har", "/tmp/whatever.har"})
+	root.SetOut(new(bytes.Buffer))
+	root.SetErr(new(bytes.Buffer))
+
+	err := root.Execute()
+	require.Error(t, err, "invoking legacy 'sniff' must fail after the rename")
+	assert.Contains(t, err.Error(), "unknown command", "cobra should surface an unknown-command error")
+}
+
+func TestBrowserSniffAppearsInHelp(t *testing.T) {
+	t.Parallel()
+
+	root := newRootCmdForTest()
+	buf := new(bytes.Buffer)
+	root.SetOut(buf)
+	root.SetArgs([]string{"--help"})
+
+	require.NoError(t, root.Execute())
+	out := buf.String()
+	assert.Contains(t, out, "browser-sniff", "browser-sniff should be listed in help")
+	assert.NotContains(t, lineWithToken(out, "sniff"), "\n  sniff ", "bare 'sniff' should not appear as a top-level command in help")
+}
+
+// lineWithToken is a trivial helper — the NotContains check above looks for the
+// subcommand indent pattern cobra uses when listing commands.
+func lineWithToken(s, _ string) string {
+	// Normalize to make the NotContains assertion robust across cobra versions.
+	return "\n" + strings.ReplaceAll(s, "\r\n", "\n")
+}
+
+func TestCrowdSniffStillWorksAfterBrowserSniffRename(t *testing.T) {
+	t.Parallel()
+
+	root := newRootCmdForTest()
+	buf := new(bytes.Buffer)
+	root.SetOut(buf)
+	root.SetArgs([]string{"crowd-sniff", "--help"})
+
+	require.NoError(t, root.Execute(), "crowd-sniff --help must still succeed after browser-sniff rename")
+	out := buf.String()
+	assert.Contains(t, out, "crowd-sniff", "crowd-sniff help output should reference the command name")
+}
diff --git a/internal/cli/crowd_sniff.go b/internal/cli/crowd_sniff.go
index 9e6beaee..223118d1 100644
--- a/internal/cli/crowd_sniff.go
+++ b/internal/cli/crowd_sniff.go
@@ -10,8 +10,8 @@ import (
 	"path/filepath"
 	"strings"
 
+	"github.com/mvanhorn/cli-printing-press/internal/browsersniff"
 	"github.com/mvanhorn/cli-printing-press/internal/crowdsniff"
-	"github.com/mvanhorn/cli-printing-press/internal/websniff"
 	"github.com/spf13/cobra"
 	"golang.org/x/sync/errgroup"
 )
@@ -44,7 +44,7 @@ func newCrowdSniffCmdWithOptions(opts crowdSniffOptions) *cobra.Command {
 		Long: `Discover API endpoints by mining community signals: npm SDK packages
 and GitHub code search. Produces a spec YAML compatible with 'printing-press generate'.
 
-Complements 'sniff' (which discovers from live web traffic) by finding
+Complements 'browser-sniff' (which discovers from live web traffic) by finding
 what developers have already mapped in published packages and code.`,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runCrowdSniff(cmd.Context(), apiName, baseURL, outputPath, asJSON, opts)
@@ -147,7 +147,7 @@ func runCrowdSniff(ctx context.Context, apiName, baseURL, outputPath string, asJ
 		outputPath = defaultCrowdSniffCachePath(apiName)
 	}
 
-	if err := websniff.WriteSpec(apiSpec, outputPath); err != nil {
+	if err := browsersniff.WriteSpec(apiSpec, outputPath); err != nil {
 		return fmt.Errorf("writing spec: %w", err)
 	}
 
diff --git a/internal/cli/root.go b/internal/cli/root.go
index 667a921e..e96d6dc5 100644
--- a/internal/cli/root.go
+++ b/internal/cli/root.go
@@ -48,7 +48,7 @@ func Execute() error {
 	rootCmd.AddCommand(newVisionCmd())
 	rootCmd.AddCommand(newVersionCmd())
 	rootCmd.AddCommand(newPrintCmd())
-	rootCmd.AddCommand(newSniffCmd())
+	rootCmd.AddCommand(newBrowserSniffCmd())
 	rootCmd.AddCommand(newCrowdSniffCmd())
 	rootCmd.AddCommand(newCatalogCmd())
 	rootCmd.AddCommand(newLibraryCmd())
diff --git a/internal/cli/sniff_test.go b/internal/cli/sniff_test.go
deleted file mode 100644
index 1ef6169b..00000000
--- a/internal/cli/sniff_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package cli
-
-import (
-	"path/filepath"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-func TestSniffCmdRejectsDomainMismatchOnAuthFrom(t *testing.T) {
-	t.Parallel()
-
-	cmd := newSniffCmd()
-	outputPath := filepath.Join(t.TempDir(), "spec.yaml")
-	cmd.SetArgs([]string{
-		"--har", filepath.Join("..", "..", "testdata", "sniff", "sample-enriched.json"),
-		"--auth-from", filepath.Join("..", "..", "testdata", "sniff", "sample-auth-capture-mismatch.json"),
-		"--output", outputPath,
-	})
-
-	err := cmd.Execute()
-	require.Error(t, err)
-	assert.EqualError(t, err, "auth captured for other.example.com cannot be used with hn.algolia.com (domain mismatch)")
-}
diff --git a/internal/crowdsniff/aggregate.go b/internal/crowdsniff/aggregate.go
index 313111e0..9e6d4ce2 100644
--- a/internal/crowdsniff/aggregate.go
+++ b/internal/crowdsniff/aggregate.go
@@ -7,7 +7,7 @@ import (
 )
 
 var (
-	// Concrete value patterns (from websniff/classifier.go).
+	// Concrete value patterns (from browsersniff/classifier.go).
 	uuidSegmentPattern = regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`)
 	hashSegmentPattern = regexp.MustCompile(`(?i)^[0-9a-f]{32,}$`)
 	numericPattern     = regexp.MustCompile(`^\d+$`)
diff --git a/internal/crowdsniff/specgen.go b/internal/crowdsniff/specgen.go
index d607c4ff..118cb356 100644
--- a/internal/crowdsniff/specgen.go
+++ b/internal/crowdsniff/specgen.go
@@ -116,7 +116,7 @@ func ResolveBaseURL(explicit string, candidates []string) string {
 	return ""
 }
 
-// --- Helpers adapted from websniff/specgen.go ---
+// --- Helpers adapted from browsersniff/specgen.go ---
 
 func deriveResourceKey(path string) (string, string) {
 	segments := significantSegments(path)
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index 47aa608a..888d2986 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -14,10 +14,10 @@ import (
 	"time"
 	"unicode"
 
+	"github.com/mvanhorn/cli-printing-press/internal/browsersniff"
 	"github.com/mvanhorn/cli-printing-press/internal/naming"
 	"github.com/mvanhorn/cli-printing-press/internal/profiler"
 	"github.com/mvanhorn/cli-printing-press/internal/spec"
-	"github.com/mvanhorn/cli-printing-press/internal/websniff"
 	"golang.org/x/text/cases"
 	"golang.org/x/text/language"
 )
@@ -120,9 +120,9 @@ type Generator struct {
 	Spec           *spec.APISpec
 	OutputDir      string
 	VisionSet      VisionTemplateSet
-	FixtureSet     *websniff.FixtureSet
+	FixtureSet     *browsersniff.FixtureSet
 	Sources        []ReadmeSource          // Ecosystem tools to credit in README
-	DiscoveryPages []string                // Pages visited during sniff discovery
+	DiscoveryPages []string                // Pages visited during browser-sniff discovery
 	NovelFeatures  []NovelFeature          // Transcendence features for README/SKILL
 	Narrative      *ReadmeNarrative        // LLM-authored prose for README/SKILL; optional
 	AsyncJobs      map[string]AsyncJobInfo // Detected async-job endpoints, keyed by "<resource>/<endpoint>"
@@ -498,7 +498,9 @@ type readmeTemplateData struct {
 }
 
 func (g *Generator) readmeData() *readmeTemplateData {
-	// For sniffed APIs without a website URL, derive it from the base URL domain
+	// The "sniffed" spec_source is the legacy provenance name for browser-captured
+	// specs (produced by the browser-sniff command). Kept for compatibility; a
+	// migration to "browser-sniffed" is deferred — see docs/plans/2026-04-18-002.
 	if g.Spec.WebsiteURL == "" && g.Spec.SpecSource == "sniffed" && g.Spec.BaseURL != "" {
 		if u, err := url.Parse(g.Spec.BaseURL); err == nil && u.Host != "" {
 			g.Spec.WebsiteURL = u.Scheme + "://" + u.Host
diff --git a/internal/generator/templates/root.go.tmpl b/internal/generator/templates/root.go.tmpl
index 3c76f766..21bef38a 100644
--- a/internal/generator/templates/root.go.tmpl
+++ b/internal/generator/templates/root.go.tmpl
@@ -112,6 +112,7 @@ Run '{{.Name}}-pp-cli doctor' to verify auth and connectivity.{{backtick}},
 	rootCmd.PersistentFlags().StringVar(&flags.dataSource, "data-source", "auto", "Data source for read commands: auto (live with local fallback), live (API only), local (synced data only)")
 	rootCmd.PersistentFlags().StringVar(&flags.profileName, "profile", "", "Apply values from a saved profile (see '{{.Name}}-pp-cli profile list')")
 	rootCmd.PersistentFlags().StringVar(&flags.deliverSpec, "deliver", "", "Route output to a sink: stdout (default), file:<path>, webhook:<url>")
+{{- /* "sniffed" is the legacy provenance name for browser-captured specs. Migration to "browser-sniffed" deferred — see docs/plans/2026-04-18-002. */}}
 {{- if eq .SpecSource "sniffed"}}
 	rootCmd.PersistentFlags().Float64Var(&flags.rateLimit, "rate-limit", 2, "Max requests per second (0 to disable, default 2 for sniffed APIs)")
 {{- else}}
diff --git a/internal/pipeline/climanifest_test.go b/internal/pipeline/climanifest_test.go
index 1151e730..a5206fd5 100644
--- a/internal/pipeline/climanifest_test.go
+++ b/internal/pipeline/climanifest_test.go
@@ -492,8 +492,8 @@ func TestArchiveRunArtifactsCopiesDiscovery(t *testing.T) {
 	require.NoError(t, os.WriteFile(filepath.Join(state.ResearchDir(), "brief.md"), []byte("brief"), 0o644))
 
 	require.NoError(t, os.MkdirAll(state.DiscoveryDir(), 0o755))
-	require.NoError(t, os.WriteFile(filepath.Join(state.DiscoveryDir(), "sniff-report.md"), []byte("report"), 0o644))
-	require.NoError(t, os.WriteFile(filepath.Join(state.DiscoveryDir(), "sniff-unique-paths.txt"), []byte("/api/v1\n/api/v2"), 0o644))
+	require.NoError(t, os.WriteFile(filepath.Join(state.DiscoveryDir(), "browser-sniff-report.md"), []byte("report"), 0o644))
+	require.NoError(t, os.WriteFile(filepath.Join(state.DiscoveryDir(), "browser-sniff-unique-paths.txt"), []byte("/api/v1\n/api/v2"), 0o644))
 
 	archiveDir, err := ArchiveRunArtifacts(state)
 	require.NoError(t, err)
@@ -502,10 +502,10 @@ func TestArchiveRunArtifactsCopiesDiscovery(t *testing.T) {
 	// Verify discovery/ was copied
 	archivedDiscovery := ArchivedDiscoveryDir(state.APIName, state.RunID)
 	assert.DirExists(t, archivedDiscovery)
-	report, err := os.ReadFile(filepath.Join(archivedDiscovery, "sniff-report.md"))
+	report, err := os.ReadFile(filepath.Join(archivedDiscovery, "browser-sniff-report.md"))
 	require.NoError(t, err)
 	assert.Equal(t, "report", string(report))
-	paths, err := os.ReadFile(filepath.Join(archivedDiscovery, "sniff-unique-paths.txt"))
+	paths, err := os.ReadFile(filepath.Join(archivedDiscovery, "browser-sniff-unique-paths.txt"))
 	require.NoError(t, err)
 	assert.Equal(t, "/api/v1\n/api/v2", string(paths))
 
diff --git a/internal/pipeline/research.go b/internal/pipeline/research.go
index 7705189d..9995224d 100644
--- a/internal/pipeline/research.go
+++ b/internal/pipeline/research.go
@@ -285,11 +285,12 @@ type ReadmeSource struct {
 	Stars    int
 }
 
-// ParseDiscoveryPages reads a sniff-report.md and extracts the URLs from
-// the "Pages Visited" section. Returns nil if the file doesn't exist or
-// contains no pages.
+// ParseDiscoveryPages reads a browser-sniff-report.md and extracts the URLs
+// from the "Pages Visited" section. Returns nil if the file doesn't exist or
+// contains no pages. Pre-rename manuscripts using the legacy "sniff-report.md"
+// name are not read — see D3 in docs/plans/2026-04-18-002.
 func ParseDiscoveryPages(discoveryDir string) []string {
-	data, err := os.ReadFile(filepath.Join(discoveryDir, "sniff-report.md"))
+	data, err := os.ReadFile(filepath.Join(discoveryDir, "browser-sniff-report.md"))
 	if err != nil {
 		return nil
 	}
diff --git a/internal/pipeline/research_test.go b/internal/pipeline/research_test.go
index 80a0e6ad..2bb5dd71 100644
--- a/internal/pipeline/research_test.go
+++ b/internal/pipeline/research_test.go
@@ -496,7 +496,7 @@ func TestParseDiscoveryPages(t *testing.T) {
 
 	t.Run("extracts URLs from pages visited section", func(t *testing.T) {
 		dir := t.TempDir()
-		content := `# Sniff Report
+		content := `# Browser-Sniff Report
 
 ## Pages Visited
 
@@ -508,7 +508,7 @@ func TestParseDiscoveryPages(t *testing.T) {
 
 - GET /api/v1/items
 `
-		require.NoError(t, os.WriteFile(dir+"/sniff-report.md", []byte(content), 0o644))
+		require.NoError(t, os.WriteFile(dir+"/browser-sniff-report.md", []byte(content), 0o644))
 		pages := ParseDiscoveryPages(dir)
 		require.Len(t, pages, 2)
 		assert.Equal(t, "https://example.com/app", pages[0])
@@ -517,7 +517,7 @@ func TestParseDiscoveryPages(t *testing.T) {
 
 	t.Run("empty pages section returns nil", func(t *testing.T) {
 		dir := t.TempDir()
-		content := `# Sniff Report
+		content := `# Browser-Sniff Report
 
 ## Pages Visited
 
@@ -525,7 +525,22 @@ func TestParseDiscoveryPages(t *testing.T) {
 
 - GET /api/v1/items
 `
-		require.NoError(t, os.WriteFile(dir+"/sniff-report.md", []byte(content), 0o644))
+		require.NoError(t, os.WriteFile(dir+"/browser-sniff-report.md", []byte(content), 0o644))
 		assert.Nil(t, ParseDiscoveryPages(dir))
 	})
+
+	// Clean-break contract (D3): when only the pre-rename legacy filename is
+	// present, ParseDiscoveryPages must NOT silently fall back to reading it.
+	// This test guards against anyone later adding fallback-read logic.
+	t.Run("legacy sniff-report.md is not read", func(t *testing.T) {
+		dir := t.TempDir()
+		content := `# Sniff Report
+
+## Pages Visited
+
+- https://example.com/legacy
+`
+		require.NoError(t, os.WriteFile(dir+"/sniff-report.md", []byte(content), 0o644))
+		assert.Nil(t, ParseDiscoveryPages(dir), "legacy sniff-report.md must be ignored — no fallback read")
+	})
 }
diff --git a/skills/printing-press-retro/SKILL.md b/skills/printing-press-retro/SKILL.md
index 72913cb0..5d8fb07a 100644
--- a/skills/printing-press-retro/SKILL.md
+++ b/skills/printing-press-retro/SKILL.md
@@ -371,7 +371,7 @@ Write the full retro document using this template:
 
 ## Session Stats
 - API: <name>
-- Spec source: <catalog/sniffed/docs/HAR>
+- Spec source: <catalog/browser-sniffed/docs/HAR>
 - Scorecard: <score>/100 (<grade>)
 - Verify pass rate: <X>%
 - Fix loops: <N>
diff --git a/skills/printing-press-retro/references/issue-template.md b/skills/printing-press-retro/references/issue-template.md
index 2eee27f7..5343499d 100644
--- a/skills/printing-press-retro/references/issue-template.md
+++ b/skills/printing-press-retro/references/issue-template.md
@@ -27,7 +27,7 @@ retro — not the full retro document. The full retro is linked via catbox artif
 | Metric | Value |
 |--------|-------|
 | API | <api-display-name> |
-| Spec source | <catalog / sniffed / docs / HAR> |
+| Spec source | <catalog / browser-sniffed / docs / HAR> |
 | Scorecard | <score>/100 (<grade>) |
 | Verify pass rate | <X>% |
 | Fix loops | <N> |
diff --git a/skills/printing-press/SKILL.md b/skills/printing-press/SKILL.md
index 8850456d..97e2ae5c 100644
--- a/skills/printing-press/SKILL.md
+++ b/skills/printing-press/SKILL.md
@@ -168,7 +168,7 @@ Print as prose (in Victorian voice):
 
 If the user provided `--spec`, adapt: "You have provided a spec, so I shall skip discovery and proceed directly to analysis and generation. Should be faster."
 
-If the user provided `--har`, adapt: "You have provided a HAR capture, so I shall generate a spec from your traffic and skip browser sniffing."
+If the user provided `--har`, adapt: "You have provided a HAR capture, so I shall generate a spec from your traffic and skip browser browser-sniffing."
 
 Then ask via `AskUserQuestion`:
 
@@ -186,7 +186,7 @@ If the user selects **"I have context to share"**, capture their free-text respo
 - Used as a 4th self-brainstorm question in Phase 1.5c.5: "Based on the user's stated vision, what features directly serve their stated goals that the absorbed features don't cover?"
 - Referenced at the Phase Gate 1.5 absorb gate: "You mentioned [summary] at the start. Want to add more, or does the manifest already cover it?"
 
-If the user selects **"I have an API key or I'm logged in"**, ask which one and capture it. Set `AUTH_CONTEXT` fields so the API Key Gate (Phase 0.5) and Pre-Sniff Auth Intelligence (Phase 1.6, if implemented) do not re-ask.
+If the user selects **"I have an API key or I'm logged in"**, ask which one and capture it. Set `AUTH_CONTEXT` fields so the API Key Gate (Phase 0.5) and Pre-Browser-Sniff Auth Intelligence (Phase 1.6, if implemented) do not re-ask.
 
 ### Multi-Source Priority Gate
 
@@ -215,7 +215,7 @@ Write the confirmed ordering to `$API_RUN_DIR/source-priority.json`:
 }
 ```
 
-**Phase 1 MUST consult this file.** When selecting a spec source, the primary source wins even if it has no spec and a later source has a clean OpenAPI. When the primary has no official spec, flag that openly in the brief under `## Source Priority` (see template below) and route to the sniff/docs path for the primary — do not promote a secondary source just because its spec is cleaner.
+**Phase 1 MUST consult this file.** When selecting a spec source, the primary source wins even if it has no spec and a later source has a clean OpenAPI. When the primary has no official spec, flag that openly in the brief under `## Source Priority` (see template below) and route to the browser-sniff/docs path for the primary — do not promote a secondary source just because its spec is cleaner.
 
 **Economics check.** If the confirmed primary source is free (no API key required) AND the generator's default path would make the primary CLI commands require a paid key (because the auth applies broadly or because a paid secondary source is bleeding into the primary path), surface the tradeoff explicitly before generating:
 
@@ -406,13 +406,13 @@ Before new research:
    The user can also pick the automatic "Other" option to describe what they're after in free text.
 
    **Routing after disambiguation:**
-   - "<SiteName>'s official API" → use `<api>` as the argument, proceed with normal discovery (Phase 1 research, then Phase 1.7 sniff gate evaluates independently as usual)
-   - "The <SiteName> website itself" → use `<api>` as the argument, set `SNIFF_TARGET_URL=<url>`. Proceed to Phase 1 research. When Phase 1.7 is reached, skip the sniff gate decision and go directly to "If user approves sniff" (the user already approved in Phase 0 — do not re-ask). Use `SNIFF_TARGET_URL` as the starting URL for browser capture.
+   - "<SiteName>'s official API" → use `<api>` as the argument, proceed with normal discovery (Phase 1 research, then Phase 1.7 browser-sniff gate evaluates independently as usual)
+   - "The <SiteName> website itself" → use `<api>` as the argument, set `BROWSER_SNIFF_TARGET_URL=<url>`. Proceed to Phase 1 research. When Phase 1.7 is reached, skip the browser-sniff gate decision and go directly to "If user approves browser-sniff" (the user already approved in Phase 0 — do not re-ask). Use `BROWSER_SNIFF_TARGET_URL` as the starting URL for browser capture.
    - "Other" → read the user's free-form response and adapt
 
    **End of URL detection.** The remaining spec resolution rules apply when the argument is NOT a URL:
 
-   - If the user passed `--har <path>`, this is a HAR-first run. Run `printing-press sniff --har <path> --name <api> --output "$RESEARCH_DIR/<api>-sniff-spec.yaml"` to generate a spec from captured traffic. Use the generated spec as the primary spec source for the rest of the pipeline. Skip the sniff gate in Phase 1.7 (sniff already ran).
+   - If the user passed `--har <path>`, this is a HAR-first run. Run `printing-press browser-sniff --har <path> --name <api> --output "$RESEARCH_DIR/<api>-browser-sniff-spec.yaml"` to generate a spec from captured traffic. Use the generated spec as the primary spec source for the rest of the pipeline. Skip the browser-sniff gate in Phase 1.7 (browser-sniff already ran).
    - If the user passed `--spec`, use it directly (existing behavior).
    - Otherwise, proceed with normal discovery (catalog, KnownSpecs, apis-guru, web search).
 2. Check for prior research in:
@@ -525,7 +525,7 @@ Resolve the API key gate (or skip it for public APIs) before moving to Phase 1.
 
 ## Phase 1: Research Brief
 
-**When `SNIFF_TARGET_URL` is set:** Skip the catalog check, spec/docs search, and SDK wrapper search — none of these exist for an undocumented website feature. Focus research on understanding what the site/feature does, who uses it, what workflows it supports, and what competitors offer similar functionality. The spec will come from sniffing in Phase 1.7.
+**When `BROWSER_SNIFF_TARGET_URL` is set:** Skip the catalog check, spec/docs search, and SDK wrapper search — none of these exist for an undocumented website feature. Focus research on understanding what the site/feature does, who uses it, what workflows it supports, and what competitors offer similar functionality. The spec will come from browser-sniffing in Phase 1.7.
 
 Before starting research, check if the API has a built-in catalog entry:
 
@@ -542,7 +542,7 @@ If the catalog has an entry for this API, branch on the entry type:
 - If catalog config: use the spec_url from the catalog entry, skip the research/discovery phase
 - If full discovery: proceed with the normal research workflow
 
-**Wrapper-only entry** (no `spec_url`, `wrapper_libraries` populated) — this is a reverse-engineered API that has no official spec but has known community libraries the generator can use as implementation backing. Do not try to resolve or sniff a spec. Instead, surface the wrapper options to the user via `AskUserQuestion`:
+**Wrapper-only entry** (no `spec_url`, `wrapper_libraries` populated) — this is a reverse-engineered API that has no official spec but has known community libraries the generator can use as implementation backing. Do not try to resolve or browser-sniff a spec. Instead, surface the wrapper options to the user via `AskUserQuestion`:
 
 > "<API> has no official spec. The catalog knows about these community-maintained implementations:"
 
@@ -625,7 +625,7 @@ Suggested shape:
 
 ## Source Priority
 - [Only present for combo CLIs. Copy the confirmed ordering from `source-priority.json`.]
-- Primary: <Source A> — [spec state: official / community-wrapper / no-spec-sniff-required] — [auth: free / paid]
+- Primary: <Source A> — [spec state: official / community-wrapper / no-spec-browser-sniff-required] — [auth: free / paid]
 - Secondary: <Source B> — [...]
 - Tertiary: <Source C> — [...]
 - **Economics:** [e.g., "Primary is free; paid key for <Source B> is scoped to its own commands only."]
@@ -641,13 +641,13 @@ Suggested shape:
 3. ...
 ```
 
-**MANDATORY: Before proceeding to Phase 1.5 (Absorb Gate), you MUST evaluate Phase 1.6 (Pre-Sniff Auth Intelligence), Phase 1.7 (Sniff Gate), and Phase 1.8 (Crowd Sniff Gate) below.** If no spec source has been resolved yet (no `--spec`, no `--har`, no catalog spec URL), the sniff gate decision matrix MUST be evaluated. Do not skip to Phase 1.5.
+**MANDATORY: Before proceeding to Phase 1.5 (Absorb Gate), you MUST evaluate Phase 1.6 (Pre-Browser-Sniff Auth Intelligence), Phase 1.7 (Browser-Sniff Gate), and Phase 1.8 (Crowd-Sniff Gate) below.** If no spec source has been resolved yet (no `--spec`, no `--har`, no catalog spec URL), the browser-sniff gate decision matrix MUST be evaluated. Do not skip to Phase 1.5.
 
-**Phase 1.5 will refuse to proceed without a `sniff-gate.json` marker file.** Phase 1.7 writes this file with one entry per source (one entry for single-source CLIs, one entry per named source for combo CLIs). Missing marker = HARD STOP back to Phase 1.7. See Phase 1.7 "Enforcement" below for the contract.
+**Phase 1.5 will refuse to proceed without a `browser-browser-sniff-gate.json` marker file.** Phase 1.7 writes this file with one entry per source (one entry for single-source CLIs, one entry per named source for combo CLIs). Missing marker = HARD STOP back to Phase 1.7. See Phase 1.7 "Enforcement" below for the contract.
 
-## Phase 1.6: Pre-Sniff Auth Intelligence
+## Phase 1.6: Pre-Browser-Sniff Auth Intelligence
 
-After Phase 1 research completes, analyze findings to proactively assess what auth context the user could provide. This step uses research intelligence to ask the right question before sniffing starts, rather than waiting for the user to volunteer "I logged in."
+After Phase 1 research completes, analyze findings to proactively assess what auth context the user could provide. This step uses research intelligence to ask the right question before browser-sniffing starts, rather than waiting for the user to volunteer "I logged in."
 
 **Skip this step if:** The briefing (Orientation & Briefing section) already captured auth context (`AUTH_CONTEXT` is set from the user selecting "I have an API key or I'm logged in").
 
@@ -656,9 +656,9 @@ After Phase 1 research completes, analyze findings to proactively assess what au
 | Signal from research | Auth profile | What to ask |
 |---------------------|-------------|-------------|
 | Community wrappers use API keys (e.g., `STRIPE_SECRET_KEY`), MCP source shows `Authorization: Bearer` headers, spec has `security` section | **API key auth** | "Do you have an API key for `<API>`?" |
-| Site has user accounts, research found auth-only features (order history, saved items, rewards, account settings), login pages exist | **Browser session auth** | "This API has authenticated endpoints ([list specific features from research, e.g., order history, saved addresses, rewards]). Are you logged in to `<site>` in your browser? The sniff will find more endpoints if you are." |
+| Site has user accounts, research found auth-only features (order history, saved items, rewards, account settings), login pages exist | **Browser session auth** | "This API has authenticated endpoints ([list specific features from research, e.g., order history, saved addresses, rewards]). Are you logged in to `<site>` in your browser? The browser-sniff will find more endpoints if you are." |
 | Endpoints accessible without auth, no login-gated features found, community wrappers describe API as "no auth required" | **No auth needed** | Skip this step silently |
-| Both API key AND browser session features found | **Dual auth** | Ask about both: API key for smoke testing, browser session for sniff |
+| Both API key AND browser session features found | **Dual auth** | Ask about both: API key for smoke testing, browser session for browser-sniff |
 
 **Name the specific features the user would unlock.** Do not say "auth would help." Say "This API has order history, saved addresses, and rewards that require a logged-in session."
 
@@ -679,25 +679,25 @@ If the user provides a key, set it in `AUTH_CONTEXT` so the API Key Gate (Phase
 **For browser session auth:** Present via `AskUserQuestion`:
 > "`<API>` has authenticated endpoints ([list features]). Are you logged in to `<site>` in your browser? If so, the generated CLI will support `auth login --chrome` — you'll be able to authenticate just by being logged into the site in Chrome. No API key needed."
 >
-> 1. **Yes, I'm logged in** — I'll use your session during sniff and enable browser auth in the CLI
-> 2. **No, but I can log in** — I'll help you log in before sniffing
-> 3. **No, skip authenticated endpoints** — sniff only public endpoints
+> 1. **Yes, I'm logged in** — I'll use your session during browser-sniff and enable browser auth in the CLI
+> 2. **No, but I can log in** — I'll help you log in before browser-sniffing
+> 3. **No, skip authenticated endpoints** — browser-sniff only public endpoints
 
-Set `AUTH_SESSION_AVAILABLE=true` if the user selects option 1 or 2. The Sniff Gate (Phase 1.7) will use this flag. After traffic capture, Step 2d in [references/sniff-capture.md](references/sniff-capture.md) validates that cookie replay works before enabling browser auth in the generated CLI.
+Set `AUTH_SESSION_AVAILABLE=true` if the user selects option 1 or 2. The Browser-Sniff Gate (Phase 1.7) will use this flag. After traffic capture, Step 2d in [references/browser-sniff-capture.md](references/browser-sniff-capture.md) validates that cookie replay works before enabling browser auth in the generated CLI.
 
 **For dual auth:** Ask about both in sequence — API key first (simple env var check), then browser session.
 
 ---
 
-## Phase 1.7: Sniff Gate
+## Phase 1.7: Browser-Sniff Gate
 
-After Phase 1 research, evaluate whether sniffing the live site would improve the spec. This phase MUST produce a decision marker file for every source named in the briefing before Phase 1.5 can proceed.
+After Phase 1 research, evaluate whether browser-sniffing the live site would improve the spec. This phase MUST produce a decision marker file for every source named in the briefing before Phase 1.5 can proceed.
 
-### Enforcement: the sniff-gate.json marker file
+### Enforcement: the browser-browser-sniff-gate.json marker file
 
 Phase 1.7 is a hard gate. Phase 1.5 reads a marker file and refuses to proceed without it. The model cannot skip this phase by reasoning around it.
 
-**Marker file location:** `$PRESS_RUNSTATE/runs/$RUN_ID/sniff-gate.json`
+**Marker file location:** `$PRESS_RUNSTATE/runs/$RUN_ID/browser-browser-sniff-gate.json`
 
 **Marker file shape:**
 
@@ -717,10 +717,10 @@ Phase 1.7 is a hard gate. Phase 1.5 reads a marker file and refuses to proceed w
 
 **Decision values:**
 
-- `approved` — user selected a sniff option via `AskUserQuestion`. Proceed to "If user approves sniff".
-- `declined` — user explicitly declined sniff via `AskUserQuestion`. Proceed to "If user declines sniff".
+- `approved` — user selected a browser-sniff option via `AskUserQuestion`. Proceed to "If user approves browser-sniff".
+- `declined` — user explicitly declined browser-sniff via `AskUserQuestion`. Proceed to "If user declines browser-sniff".
 - `skip-silent` — gate was silently skipped per the decision matrix (spec complete, `--har` provided, `--spec` provided, or login required with `AUTH_SESSION_AVAILABLE=false`). The `reason` field names which.
-- `pre-approved` — user already chose "The website itself" in Phase 0, so `SNIFF_TARGET_URL` was set and the question was answered there.
+- `pre-approved` — user already chose "The website itself" in Phase 0, so `BROWSER_SNIFF_TARGET_URL` was set and the question was answered there.
 
 **Every path through Phase 1.7 MUST write a marker entry** — approve, decline, and every silent-skip case. There is no code path that proceeds to Phase 1.5 without writing the marker.
 
@@ -728,16 +728,16 @@ Phase 1.7 is a hard gate. Phase 1.5 reads a marker file and refuses to proceed w
 
 ### Banned skip reasons
 
-The following rationales are NOT valid reasons to skip the sniff gate. If any of these apply, you MUST still ask the user via `AskUserQuestion` and record their answer in the marker file:
+The following rationales are NOT valid reasons to skip the browser-sniff gate. If any of these apply, you MUST still ask the user via `AskUserQuestion` and record their answer in the marker file:
 
-- **"The target is client-rendered and needs Playwright"** — browser capture tools (browser-use, agent-browser) exist specifically to handle client-rendered sites. Hard-to-sniff is not the same as impossible-to-sniff. Ask.
-- **"The 3-minute time budget looks tight"** — the time budget applies AFTER the user approves sniff, not before. You do not pre-judge whether a sniff will fit the budget. Ask. If the budget blows after the user approves, fall back per the Time Budget rules below.
+- **"The target is client-rendered and needs Playwright"** — browser capture tools (browser-use, agent-browser) exist specifically to handle client-rendered sites. A hard-to-browser-sniff target is not the same as an impossible one. Ask.
+- **"The 3-minute time budget looks tight"** — the time budget applies AFTER the user approves browser-sniff, not before. You do not pre-judge whether a browser-sniff will fit the budget. Ask. If the budget blows after the user approves, fall back per the Time Budget rules below.
 - **"We have a substitute data source from another API"** — substituting one source for another is the user's call, not yours. If the user named a specific site or feature (e.g., Kayak /direct), they chose it deliberately. Ask about that exact source. Offering a different data source is a separate conversation AFTER the gate, not a reason to skip it.
-- **"Installing browser-use or agent-browser is friction"** — the sniff capture reference already documents the install path. Tooling friction is not a valid skip reason. Ask.
+- **"Installing browser-use or agent-browser is friction"** — the browser-sniff capture reference already documents the install path. Tooling friction is not a valid skip reason. Ask.
 - **"The documentation looks thorough enough"** — the decision matrix already handles this case explicitly. If research found that competitors or community projects reference more endpoints than the spec covers, that IS a gap and you MUST ask.
 - **"The user said 'let's go' earlier and implicitly approved everything"** — "let's go" at the briefing stage is consent to proceed with research, not standing approval for every future decision. Ask each gate individually.
 
-These banned reasons all fired at once in a past combo-CLI run and caused a user-critical source to be silently swapped out. The marker file exists so this cannot happen again. If you find yourself writing a phrase like "skipping sniff because X" where X is one of the above, stop and call `AskUserQuestion`.
+These banned reasons all fired at once in a past combo-CLI run and caused a user-critical source to be silently swapped out. The marker file exists so this cannot happen again. If you find yourself writing a phrase like "skipping browser-sniff because X" where X is one of the above, stop and call `AskUserQuestion`.
 
 ### Combo CLIs: per-source enforcement
 
@@ -747,7 +747,7 @@ When the briefing names multiple sources (e.g., "Google Flights + Kayak + Flight
 
 **Per-source decision flow:**
 
-For each named source, run the "When to offer sniff" decision matrix independently, using the research findings for THAT source. Each source produces its own `AskUserQuestion` call or its own silent-skip marker entry.
+For each named source, run the "When to offer browser-sniff" decision matrix independently, using the research findings for THAT source. Each source produces its own `AskUserQuestion` call or its own silent-skip marker entry.
 
 **Combo CLI example** (flightgoat pattern — directional guidance, not prescription):
 
@@ -767,58 +767,58 @@ These are the only cases where Phase 1.7 is bypassed as a whole (not just skippe
 
 - User passed `--spec` and the spec is the canonical source for every named source → marker: `{ "source_name": "<api>", "decision": "skip-silent", "reason": "user-provided-spec" }`
 - User passed `--har` → marker: `{ "source_name": "<api>", "decision": "skip-silent", "reason": "user-provided-har" }`
-- `SNIFF_TARGET_URL` is set from Phase 0 (user chose "The website itself") → marker: `{ "source_name": "<api>", "decision": "pre-approved", "reason": "phase-0-website-choice" }`, then go directly to "If user approves sniff"
+- `BROWSER_SNIFF_TARGET_URL` is set from Phase 0 (user chose "The website itself") → marker: `{ "source_name": "<api>", "decision": "pre-approved", "reason": "phase-0-website-choice" }`, then go directly to "If user approves browser-sniff"
 
 ### Time budget
 
-The sniff gate should complete within 3 minutes of the user approving sniff. If browser automation tooling fails to produce results after 3 minutes of attempts, fall back immediately:
-- If a spec already exists (enrichment mode): "Sniff failed after 3 minutes — proceeding with existing spec."
-- If no spec exists (primary mode): "Sniff failed after 3 minutes — falling back to --docs generation."
+The browser-sniff gate should complete within 3 minutes of the user approving browser-sniff. If browser automation tooling fails to produce results after 3 minutes of attempts, fall back immediately:
+- If a spec already exists (enrichment mode): "Browser-Sniff failed after 3 minutes — proceeding with existing spec."
+- If no spec exists (primary mode): "Browser-Sniff failed after 3 minutes — falling back to --docs generation."
 
-Do NOT spend time debugging tool integration issues. The sniff is optional enrichment, not a blocking requirement. If the first approach fails, fall back to the next option — do not retry the same broken approach.
+Do NOT spend time debugging tool integration issues. The browser-sniff is optional enrichment, not a blocking requirement. If the first approach fails, fall back to the next option — do not retry the same broken approach.
 
 **The time budget applies AFTER the user approves.** Do not use it as a reason to skip the gate before asking.
 
-### When to offer sniff
+### When to offer browser-sniff
 
 | Spec found? | Research shows gaps? | Auth required? | Action |
 |-------------|---------------------|----------------|--------|
-| Yes | Yes — docs or competitors show significantly more endpoints than the spec | No | **MUST offer sniff as enrichment** |
+| Yes | Yes — docs or competitors show significantly more endpoints than the spec | No | **MUST offer browser-sniff as enrichment** |
 | Yes | No — spec appears complete | Any | Skip silently (write marker with `decision: skip-silent`) |
-| No | Community docs exist (e.g., Public-ESPN-API) | No | **MUST offer sniff OR --docs** — present both options so the user decides |
-| No | No docs found either | No | **MUST offer sniff as primary discovery** |
-| No | N/A | Yes (login) + `AUTH_SESSION_AVAILABLE=true` | **Offer authenticated sniff** — the user confirmed a session in Phase 1.6 |
+| No | Community docs exist (e.g., Public-ESPN-API) | No | **MUST offer browser-sniff OR --docs** — present both options so the user decides |
+| No | No docs found either | No | **MUST offer browser-sniff as primary discovery** |
+| No | N/A | Yes (login) + `AUTH_SESSION_AVAILABLE=true` | **Offer authenticated browser-sniff** — the user confirmed a session in Phase 1.6 |
 | No | N/A | Yes (login) + `AUTH_SESSION_AVAILABLE=false` | Skip — fall back to `--docs` (write marker with `decision: skip-silent`, `reason: login-required-no-session`) |
 
 **Gap detection heuristic:** If Phase 1 research found documentation, competitor tools, or community projects that reference significantly more endpoints or features than the resolved spec covers, that's a gap signal. Example: "The Zuplo OpenAPI spec has 42 endpoints, but the Public-ESPN-API docs describe 370+."
 
-**When the decision matrix says "Offer sniff", you MUST ask the user via `AskUserQuestion`.** Skipping the question and writing a `skip-silent` marker is a contract violation — `skip-silent` is only valid when the matrix says "Skip silently" or one of the Banned Skip Reasons is the only thing holding you back (in which case, you should be asking anyway).
+**When the decision matrix says "Offer browser-sniff", you MUST ask the user via `AskUserQuestion`.** Skipping the question and writing a `skip-silent` marker is a contract violation — `skip-silent` is only valid when the matrix says "Skip silently" or one of the Banned Skip Reasons is the only thing holding you back (in which case, you should be asking anyway).
 
-### Sniff as enrichment (spec exists but has gaps)
+### Browser-Sniff as enrichment (spec exists but has gaps)
 
 Present to the user via `AskUserQuestion`:
 
-> "Found a spec with **N endpoints**, but research shows the live API likely has more (competitors reference M+ features). Want me to sniff `<url>` to discover endpoints the spec missed? I'll check for browser-use or agent-browser and install if needed."
+> "Found a spec with **N endpoints**, but research shows the live API likely has more (competitors reference M+ features). Want me to browser-sniff `<url>` to discover endpoints the spec missed? I'll check for browser-use or agent-browser and install if needed."
 >
 > Options:
-> 1. **Yes — sniff and merge** (browse the site, capture traffic, merge discovered endpoints with the existing spec. Installs capture tools if needed.)
+> 1. **Yes — browser-sniff and merge** (browse the site, capture traffic, merge discovered endpoints with the existing spec. Installs capture tools if needed.)
 > 2. **No — use existing spec** (proceed with what we have)
 
-### Sniff as primary (no spec found)
+### Browser-Sniff as primary (no spec found)
 
-Present to the user via `AskUserQuestion`. **If `AUTH_SESSION_AVAILABLE=true`**, include an authenticated sniff option:
+Present to the user via `AskUserQuestion`. **If `AUTH_SESSION_AVAILABLE=true`**, include an authenticated browser-sniff option:
 
-> "No OpenAPI spec found for `<API>`. Want me to sniff `<likely-url>` to discover the API from live traffic?"
+> "No OpenAPI spec found for `<API>`. Want me to browser-sniff `<likely-url>` to discover the API from live traffic?"
 >
 > Options:
-> 1. **Yes — authenticated sniff** (use your browser session to discover both public and authenticated endpoints. Recommended since you confirmed a session.) *(Only show when `AUTH_SESSION_AVAILABLE=true`)*
-> 2. **Yes — sniff the live site** (browse `<url>` anonymously, capture API calls, generate a spec. Installs capture tools if needed.)
+> 1. **Yes — authenticated browser-sniff** (use your browser session to discover both public and authenticated endpoints. Recommended since you confirmed a session.) *(Only show when `AUTH_SESSION_AVAILABLE=true`)*
+> 2. **Yes — browser-sniff the live site** (browse `<url>` anonymously, capture API calls, generate a spec. Installs capture tools if needed.)
 > 3. **No — use docs instead** (attempt `--docs` generation from documentation pages)
 > 4. **No — I'll provide a spec or HAR** (user will supply input manually)
 
 When `AUTH_SESSION_AVAILABLE=false`, show only options 2-4 (the existing 3-option prompt).
 
-### If user approves sniff
+### If user approves browser-sniff
 
 **Before doing anything else, write the marker entry** for this source:
 
@@ -826,18 +826,18 @@ When `AUTH_SESSION_AVAILABLE=false`, show only options 2-4 (the existing 3-optio
 {
   "source_name": "<normalized name from briefing>",
   "decision": "approved",
-  "reason": "<which option they picked, e.g., 'authenticated sniff' or 'sniff and merge'>",
+  "reason": "<which option they picked, e.g., 'authenticated browser-sniff' or 'browser-sniff and merge'>",
   "asked_at": "<current ISO8601 timestamp>"
 }
 ```
 
-Append it to `$PRESS_RUNSTATE/runs/$RUN_ID/sniff-gate.json` (create the file if it doesn't exist).
+Append it to `$PRESS_RUNSTATE/runs/$RUN_ID/browser-browser-sniff-gate.json` (create the file if it doesn't exist).
 
 #### Step 0: Identify the User Goal
 
 Before building the capture plan, answer one question: **What does the end user of this CLI actually want to do?**
 
-Read the research brief's Top Workflows. The #1 workflow IS the primary sniff goal. State it in one sentence:
+Read the research brief's Top Workflows. The #1 workflow IS the primary browser-sniff goal. State it in one sentence:
 - Domino's: "Order a pizza for delivery"
 - Linear: "Create an issue and assign it to a sprint"
 - Stripe: "Create a payment intent and confirm it"
@@ -846,15 +846,15 @@ Read the research brief's Top Workflows. The #1 workflow IS the primary sniff go
 
 If the API is read-only (news, weather, data feeds), the primary goal is "fetch and filter data" and the flow is search/filter/paginate rather than a multi-step transaction.
 
-The sniff will walk through this goal as an interactive user flow. Secondary workflows become secondary sniff passes if time permits.
+The browser-sniff will walk through this goal as an interactive user flow. Secondary workflows become secondary browser-sniff passes if time permits.
 
-State the goal explicitly before proceeding: "Primary sniff goal: [goal]. I will walk through this as a user flow."
+State the goal explicitly before proceeding: "Primary browser-sniff goal: [goal]. I will walk through this as a user flow."
 
-Then read and follow [references/sniff-capture.md](references/sniff-capture.md) for the complete
-sniff implementation: tool detection, installation, session transfer, browser-use/agent-browser
+Then read and follow [references/browser-sniff-capture.md](references/browser-sniff-capture.md) for the complete
+browser-sniff implementation: tool detection, installation, session transfer, browser-use/agent-browser
 capture, HAR analysis, and discovery report writing.
 
-### If user declines sniff
+### If user declines browser-sniff
 
 **Write the marker entry** for this source before proceeding:
 
@@ -867,60 +867,60 @@ capture, HAR analysis, and discovery report writing.
 }
 ```
 
-Append it to `$PRESS_RUNSTATE/runs/$RUN_ID/sniff-gate.json`.
+Append it to `$PRESS_RUNSTATE/runs/$RUN_ID/browser-browser-sniff-gate.json`.
 
 Proceed with whatever spec source exists. If no spec was found, fall back to `--docs` or ask the user to provide a spec/HAR manually.
 
 ### Before leaving Phase 1.7
 
-Every source named in the briefing must have exactly one entry in `sniff-gate.json`. Before proceeding to Phase 1.8, re-read the marker file and verify the count matches the number of named sources from the briefing. If a source is missing, return to the decision matrix for that source. Phase 1.5 will HALT if this check fails.
+Every source named in the briefing must have exactly one entry in `browser-browser-sniff-gate.json`. Before proceeding to Phase 1.8, re-read the marker file and verify the count matches the number of named sources from the briefing. If a source is missing, return to the decision matrix for that source. Phase 1.5 will HALT if this check fails.
 
 ---
 
-## Phase 1.8: Crowd Sniff Gate
+## Phase 1.8: Crowd-Sniff Gate
 
-After Phase 1.7 (Sniff Gate), evaluate whether mining community signals (npm SDKs and GitHub code search) would improve the spec. Skip this gate entirely if the user already passed `--spec` (spec source is already resolved and appears complete).
+After Phase 1.7 (Browser-Sniff Gate), evaluate whether mining community signals (npm SDKs and GitHub code search) would improve the spec. Skip this gate entirely if the user already passed `--spec` (spec source is already resolved and appears complete).
 
-**Time budget:** The crowd sniff gate should complete within 5 minutes. If `printing-press crowd-sniff` fails or times out, fall back immediately:
-- If a spec already exists: "Crowd sniff failed — proceeding with existing spec."
-- If no spec exists: "Crowd sniff failed — falling back to --docs generation."
+**Time budget:** The crowd-sniff gate should complete within 5 minutes. If `printing-press crowd-sniff` fails or times out, fall back immediately:
+- If a spec already exists: "Crowd-sniff failed — proceeding with existing spec."
+- If no spec exists: "Crowd-sniff failed — falling back to --docs generation."
 
-### When to offer crowd sniff
+### When to offer crowd-sniff
 
 | Spec found? | Research shows gaps? | Action |
 |-------------|---------------------|--------|
-| Yes | Yes — competitors or community projects reference more endpoints | **Offer crowd sniff as enrichment** |
+| Yes | Yes — competitors or community projects reference more endpoints | **Offer crowd-sniff as enrichment** |
 | Yes | No — spec appears complete | Skip silently |
-| No | Community SDKs exist on npm | **Offer crowd sniff as primary discovery** |
+| No | Community SDKs exist on npm | **Offer crowd-sniff as primary discovery** |
 | No | No SDKs or code found | Skip — fall back to `--docs` |
 
-### Crowd sniff as enrichment (spec exists but has gaps)
+### Crowd-sniff as enrichment (spec exists but has gaps)
 
 Present to the user via `AskUserQuestion`:
 
 > "Found a spec with **N endpoints**, but research shows the live API likely has more. Want me to search npm packages and GitHub code for `<api>` to discover additional endpoints? This typically takes 2-4 minutes."
 >
 > Options:
-> 1. **Yes — crowd sniff and merge** (search npm SDKs and GitHub code, merge discovered endpoints with the existing spec)
+> 1. **Yes — crowd-sniff and merge** (search npm SDKs and GitHub code, merge discovered endpoints with the existing spec)
 > 2. **No — use existing spec** (proceed with what we have)
 
-### Crowd sniff as primary (no spec found)
+### Crowd-sniff as primary (no spec found)
 
 Present to the user via `AskUserQuestion`:
 
 > "No OpenAPI spec found for `<API>`. Want me to search npm packages and GitHub code to discover the API from community usage? This typically takes 2-4 minutes."
 >
 > Options:
-> 1. **Yes — crowd sniff the community** (search npm SDKs and GitHub code, generate a spec from discovered endpoints)
+> 1. **Yes — crowd-sniff the community** (search npm SDKs and GitHub code, generate a spec from discovered endpoints)
 > 2. **No — use docs instead** (attempt `--docs` generation from documentation pages)
 > 3. **No — I'll provide a spec or HAR** (user will supply input manually)
 
-### If user approves crowd sniff
+### If user approves crowd-sniff
 
 Read and follow [references/crowd-sniff.md](references/crowd-sniff.md) for the crowd-sniff
 command, provenance capture, and discovery report writing.
 
-### If user declines crowd sniff
+### If user declines crowd-sniff
 
 Proceed with whatever spec source exists. If no spec was found, fall back to `--docs` or ask the user to provide a spec/HAR manually.
 
@@ -930,25 +930,25 @@ Proceed with whatever spec source exists. If no spec was found, fall back to `--
 
 THIS IS A MANDATORY STOP GATE. Do not generate until this is complete and approved.
 
-### Pre-flight check: sniff-gate marker
+### Pre-flight check: browser-sniff-gate marker
 
-Before any absorb work, verify `$PRESS_RUNSTATE/runs/$RUN_ID/sniff-gate.json` exists and contains an entry for every source named in the briefing.
+Before any absorb work, verify `$PRESS_RUNSTATE/runs/$RUN_ID/browser-browser-sniff-gate.json` exists and contains an entry for every source named in the briefing.
 
 **If the file is missing:** HARD STOP. Print:
 
-> Phase 1.7 Sniff Gate did not record a decision. Return to Phase 1.7 and evaluate the sniff gate for every source named in the briefing.
+> Phase 1.7 Browser-Sniff Gate did not record a decision. Return to Phase 1.7 and evaluate the browser-sniff gate for every source named in the briefing.
 
 Do not proceed to Step 1.5a until the file exists.
 
 **If the file exists but is missing an entry for a named source:** HARD STOP. Print:
 
-> Sniff Gate missing decision for source `<name>`. Return to Phase 1.7 and evaluate the decision matrix for that source.
+> Browser-Sniff Gate missing decision for source `<name>`. Return to Phase 1.7 and evaluate the decision matrix for that source.
 
 Do not proceed until every briefing source has a marker entry.
 
 **Resume leniency:** If the run was started by an older version of the skill that didn't write markers, warn and continue — do not hard-fail on legacy resumes. Distinguish by checking whether `state.json` predates the marker contract (the marker file didn't exist before 2026-04-11). New runs always hard-fail on a missing marker.
 
-**Pre-check (existing):** If no spec or HAR file has been resolved by this point and Phase 1.7 (Sniff Gate) was not evaluated, STOP. Go back and run the sniff gate decision matrix. The absorb manifest depends on knowing the API surface, which requires a spec.
+**Pre-check (existing):** If no spec or HAR file has been resolved by this point and Phase 1.7 (Browser-Sniff Gate) was not evaluated, STOP. Go back and run the browser-sniff gate decision matrix. The absorb manifest depends on knowing the API surface, which requires a spec.
 
 The GOAT CLI doesn't "find gaps." It absorbs EVERY feature from EVERY tool and then transcends with compound use cases nobody thought of. This phase builds the absorb manifest.
 
@@ -1145,7 +1145,7 @@ For each tool, fill in what you know from the research. Stars and command_count
 8. `trigger_phrases` are natural-language phrases a user might say that should invoke this CLI's skill. Include 3–5 domain-specific phrases (e.g. for a finance CLI: "quote AAPL", "check my portfolio", "options for TSLA") and 2 generic phrases ("use <api-name>", "run <api-name>"). Domain verbs vary — don't just template "use X" variants.
 9. All `narrative` fields are optional. Omit fields you can't populate honestly rather than emit filler. The generator falls back to generic content gracefully.
 
-Also write discovery pages if sniff was used. The generator reads these from `$API_RUN_DIR/discovery/sniff-report.md` (which the sniff gate already writes there). No additional action needed for discovery pages -- they are already in the right location.
+Also write discovery pages if browser-sniff was used. The generator reads these from `$API_RUN_DIR/discovery/browser-sniff-report.md` (which the browser-sniff gate already writes there). No additional action needed for discovery pages -- they are already in the right location.
 
 ### Priority inversion check (combo CLIs only)
 
@@ -1160,13 +1160,13 @@ When an inversion is detected, HALT before Phase Gate 1.5 and print:
 
 > ⚠ **Priority inversion detected.**
 >
-> The confirmed primary is **<Source A>** but the manifest gives it <N> commands vs **<Source B>** (secondary) with <M> commands. This usually means the primary's discovery path (sniff, community wrapper, HTML parser) didn't land, and the secondary's clean spec took over.
+> The confirmed primary is **<Source A>** but the manifest gives it <N> commands vs **<Source B>** (secondary) with <M> commands. This usually means the primary's discovery path (browser-sniff, community wrapper, HTML parser) didn't land, and the secondary's clean spec took over.
 >
 > The user said <Source A> is the headline. Shipping this manifest would invert their stated priority.
 
 Then ask via `AskUserQuestion`:
 
-1. **Re-run discovery for <Source A>** — loop back to Phase 1.7 sniff or Phase 1.8 crowd sniff for the primary source specifically.
+1. **Re-run discovery for <Source A>** — loop back to Phase 1.7 browser-sniff or Phase 1.8 crowd-sniff for the primary source specifically.
 2. **Accept the inversion** — the user explicitly confirms they're fine with the secondary leading. Record this in `source-priority.json` as `inversion_accepted: true`.
 3. **Drop <Source B>** — remove the secondary from the manifest so it can't overshadow the primary.
 
@@ -1239,7 +1239,7 @@ Or use `WebFetch` if curl is unavailable. The goal is one real response code.
 
 ### Decision Matrix
 
-| Result | Sniff gate failed? | Research found 403 issues? | Action |
+| Result | Browser-Sniff gate failed? | Research found 403 issues? | Action |
 |--------|-------------------|---------------------------|--------|
 | 2xx/3xx | Any | Any | **PASS** - proceed to Phase 2 |
 | 401 (no key provided) | No | No | **PASS** - expected when API needs auth and user declined key gate |
@@ -1253,7 +1253,7 @@ Or use `WebFetch` if curl is unavailable. The goal is one real response code.
 
 Present via `AskUserQuestion`:
 
-> "WARNING: `<API>` appears to block programmatic access. [what failed: e.g., 'HTTP 403 with HTML error page', 'sniff gate failed with bot detection', 'reteps/redfin has 6+ issues about 403 errors']. Building a CLI against an unreachable API wastes time and tokens."
+> "WARNING: `<API>` appears to block programmatic access. [what failed: e.g., 'HTTP 403 with HTML error page', 'browser-sniff gate failed with bot detection', 'reteps/redfin has 6+ issues about 403 errors']. Building a CLI against an unreachable API wastes time and tokens."
 >
 > 1. **Try anyway** - proceed knowing the CLI may not work against the live API
 > 2. **Pick a different API** - start over
@@ -1279,8 +1279,8 @@ Proceed silently to Phase 2.
 ### Pre-Generation Auth Enrichment
 
 Before generating, check whether the resolved spec has auth. This matters most for
-sniffed and crowd-sniffed specs where the mechanical auth detection may have failed
-(e.g., session expired during sniff, SDK didn't expose auth patterns).
+browser-sniffed and crowd-sniffed specs where the mechanical auth detection may have failed
+(e.g., session expired during browser-sniff, SDK didn't expose auth patterns).
 
 **Check the spec:**
 - For internal YAML specs: look for `auth:` section with `type:` not equal to `"none"`
@@ -1291,7 +1291,7 @@ auth signals, enrich the spec before generation:
 
 1. Check the research brief for auth mentions (Bearer, API key, token, cookie, OAuth)
 2. Check Phase 1.5a MCP source code analysis for auth patterns (header names, token formats)
-3. Check Phase 1.6 Pre-Sniff Auth Intelligence results (if the user confirmed auth)
+3. Check Phase 1.6 Pre-Browser-Sniff Auth Intelligence results (if the user confirmed auth)
 
 If any source identified auth, **edit the spec YAML** to add the auth section before
 running generate. For internal YAML specs:
@@ -1338,35 +1338,35 @@ printing-press generate \
   --force --lenient --validate
 ```
 
-Sniff-enriched (original spec + sniff-discovered spec):
+Browser-browser-sniff-enriched (original spec + browser-sniff-discovered spec):
 
 ```bash
 printing-press generate \
   --spec <original-spec-path-or-url> \
-  --spec "$RESEARCH_DIR/<api>-sniff-spec.yaml" \
+  --spec "$RESEARCH_DIR/<api>-browser-sniff-spec.yaml" \
   --name <api> \
   --output "$CLI_WORK_DIR" \
   --research-dir "$API_RUN_DIR" \
-  --spec-source sniffed \
+  --spec-source browser-sniffed \
   --force --lenient --validate
-# If proxy pattern was detected during sniff, add:
+# If proxy pattern was detected during browser-sniff, add:
 #   --client-pattern proxy-envelope
 ```
 
-Sniff-only (no original spec, sniff was the primary source):
+Sniff-only (no original spec, browser-sniff was the primary source):
 
 ```bash
 printing-press generate \
-  --spec "$RESEARCH_DIR/<api>-sniff-spec.yaml" \
+  --spec "$RESEARCH_DIR/<api>-browser-sniff-spec.yaml" \
   --output "$CLI_WORK_DIR" \
   --research-dir "$API_RUN_DIR" \
-  --spec-source sniffed \
+  --spec-source browser-sniffed \
   --force --lenient --validate
-# If proxy pattern was detected during sniff, add:
+# If proxy pattern was detected during browser-sniff, add:
 #   --client-pattern proxy-envelope
 ```
 
-Crowd-sniff-enriched (original spec + crowd-discovered spec):
+Crowd-browser-sniff-enriched (original spec + crowd-discovered spec):
 
 ```bash
 printing-press generate \
@@ -1378,7 +1378,7 @@ printing-press generate \
   --force --lenient --validate
 ```
 
-Crowd-sniff-only (no original spec, crowd sniff was the primary source):
+Crowd-sniff-only (no original spec, crowd-sniff was the primary source):
 
 ```bash
 printing-press generate \
@@ -1388,12 +1388,12 @@ printing-press generate \
   --force --lenient --validate
 ```
 
-Both sniff + crowd-sniff (merged with original):
+Both browser-sniff + crowd-sniff (merged with original):
 
 ```bash
 printing-press generate \
   --spec <original-spec-path-or-url> \
-  --spec "$RESEARCH_DIR/<api>-sniff-spec.yaml" \
+  --spec "$RESEARCH_DIR/<api>-browser-sniff-spec.yaml" \
   --spec "$RESEARCH_DIR/<api>-crowd-spec.yaml" \
   --name <api> \
   --output "$CLI_WORK_DIR" \
@@ -1987,13 +1987,13 @@ cp -r "$RESEARCH_DIR" "$PRESS_MANUSCRIPTS/$API_SLUG/$RUN_ID/research" 2>/dev/nul
 cp -f "$API_RUN_DIR/research.json" "$PRESS_MANUSCRIPTS/$API_SLUG/$RUN_ID/research.json" 2>/dev/null || true
 cp -r "$PROOFS_DIR" "$PRESS_MANUSCRIPTS/$API_SLUG/$RUN_ID/proofs" 2>/dev/null || true
 
-# Archive discovery artifacts (sniff captures, URL lists, sniff report).
+# Archive discovery artifacts (browser-sniff captures, URL lists, browser-sniff report).
 # Remove session state before archiving — contains authentication cookies/tokens.
 rm -f "$DISCOVERY_DIR/session-state.json"
 
 # Strip response bodies from HAR before archiving to control size.
 if [ -d "$DISCOVERY_DIR" ]; then
-  for har in "$DISCOVERY_DIR"/sniff-capture.har "$DISCOVERY_DIR"/sniff-capture.json; do
+  for har in "$DISCOVERY_DIR"/browser-sniff-capture.har "$DISCOVERY_DIR"/browser-sniff-capture.json; do
     if [ -f "$har" ] && command -v jq >/dev/null 2>&1; then
       jq 'del(.log.entries[].response.content.text)' "$har" > "${har}.stripped" 2>/dev/null && mv "${har}.stripped" "$har" || rm -f "${har}.stripped"
     fi
diff --git a/skills/printing-press/references/sniff-capture.md b/skills/printing-press/references/browser-sniff-capture.md
similarity index 81%
rename from skills/printing-press/references/sniff-capture.md
rename to skills/printing-press/references/browser-sniff-capture.md
index 10060ba8..843354ec 100644
--- a/skills/printing-press/references/sniff-capture.md
+++ b/skills/printing-press/references/browser-sniff-capture.md
@@ -1,31 +1,31 @@
-# Sniff Capture Implementation
+# Browser-Sniff Capture Implementation
 
 > **When to read:** This file is referenced by Phase 1.7 of the printing-press skill.
-> Read it when the user approves sniff (browser-use or agent-browser capture of live API traffic).
+> Read it when the user approves browser-sniff (browser-use or agent-browser capture of live API traffic).
 >
-> **Context:** This file documents what happens AFTER Phase 1.7 decides to sniff. The decision itself — approve, decline, or silent-skip — is recorded in `$PRESS_RUNSTATE/runs/$RUN_ID/sniff-gate.json` by Phase 1.7 before this reference is loaded. Phase 1.5 refuses to proceed without that marker file. See SKILL.md Phase 1.7 "Enforcement: the sniff-gate.json marker file" for the contract.
+> **Context:** This file documents what happens AFTER Phase 1.7 decides to browser-sniff. The decision itself — approve, decline, or silent-skip — is recorded in `$PRESS_RUNSTATE/runs/$RUN_ID/browser-browser-sniff-gate.json` by Phase 1.7 before this reference is loaded. Phase 1.5 refuses to proceed without that marker file. See SKILL.md Phase 1.7 "Enforcement: the browser-browser-sniff-gate.json marker file" for the contract.
 
 ### Cardinal Rules
 
-1. **ALWAYS use browser-use for capture.** Do NOT substitute curl probing, JS bundle grepping, or agent-browser auto-connect for a proper browser-use interactive sniff. Agent-browser is for session transfer only (grabbing cookies from a running Chrome). The capture — browsing pages, collecting URLs, intercepting requests — MUST use browser-use.
+1. **ALWAYS use browser-use for capture.** Do NOT substitute curl probing, JS bundle grepping, or agent-browser auto-connect for a proper browser-use interactive browser-sniff. Agent-browser is for session transfer only (grabbing cookies from a running Chrome). The capture — browsing pages, collecting URLs, intercepting requests — MUST use browser-use.
 
 2. **Do NOT skip auth discovery when the session expires.** *(Only applies when `AUTH_SESSION_AVAILABLE=true` — the user confirmed they're logged in.)* If a Chrome profile loads but the session has expired (login page visible instead of account page), offer headed login as a fallback. Never proceed without auth just because the profile session was stale. For anonymous sniffs (no auth context), this rule does not apply.
 
 3. **Use click-based SPA navigation after installing interceptors.** `browser-use open` triggers a full page reload which resets the JS context and destroys fetch/XHR interceptors. After installing interceptors, navigate by clicking links (`browser-use eval "document.querySelector('a[href*=account]').click()"` or `browser-use click`). Only use `browser-use open` for the first page load or when you need to re-install interceptors.
 
-### If user approves sniff
+### If user approves browser-sniff
 
-#### Sniff Pacing
+#### Browser-Sniff Pacing
 
-When making API calls during sniff (browser-use eval, fetch, or direct HTTP requests), apply adaptive pacing to avoid rate limits:
+When making API calls during browser-sniff (browser-use eval, fetch, or direct HTTP requests), apply adaptive pacing to avoid rate limits:
 
 1. **Start conservative**: Wait 1 second between API calls
 2. **Ramp up on success**: After 5 consecutive successful calls, reduce the delay by 20% (minimum 0.3 seconds)
 3. **Back off on 429**: If you get a rate-limited response (HTTP 429), immediately double the delay and log: "Rate limited — increasing delay to Xs"
 4. **Hard stop on repeated 429s**: If you hit 3 consecutive 429s, pause for 30 seconds before continuing
-5. **Never abort**: Rate limiting during sniff is recoverable. Always continue after the backoff — do not abort discovery due to rate limits
+5. **Never abort**: Rate limiting during browser-sniff is recoverable. Always continue after the backoff — do not abort discovery due to rate limits
 
-Track the current delay mentally. Report the effective rate when summarizing sniff results: "Sniffed N endpoints at ~X req/s effective rate."
+Track the current delay mentally. Report the effective rate when summarizing browser-sniff results: "Sniffed N endpoints at ~X req/s effective rate."
 
 #### Proxy Pattern Detection
 
@@ -71,13 +71,13 @@ fi
 
 If a tool is found, report: "Using **<tool>** for traffic capture (CLI-driven mode — no LLM key needed)." and proceed to Step 1c to verify compatibility.
 
-**Important:** browser-use has two modes: autonomous Agent mode (requires an LLM API key like ANTHROPIC_API_KEY) and CLI mode (open/eval/scroll — no key needed). **Always use CLI mode for sniff.** It is more reliable, version-stable, and does not require the user to provide an additional API key. Do NOT attempt to use browser-use's Python `Agent` class — it requires an LLM key that may not be available.
+**Important:** browser-use has two modes: autonomous Agent mode (requires an LLM API key like ANTHROPIC_API_KEY) and CLI mode (open/eval/scroll — no key needed). **Always use CLI mode for browser-sniff.** It is more reliable, version-stable, and does not require the user to provide an additional API key. Do NOT attempt to use browser-use's Python `Agent` class — it requires an LLM key that may not be available.
 
 #### Step 1b: Install capture tool (if none found)
 
 If neither tool is installed, offer to install via `AskUserQuestion`:
 
-> "No browser automation tool found. I need one to sniff the live site. Which would you like to install?"
+> "No browser automation tool found. I need one to browser-sniff the live site. Which would you like to install?"
 >
 > Options:
 > 1. **Install browser-use (Recommended)** — "CLI-driven browser automation. Claude drives the browsing via open/eval/scroll commands. Requires Python."
@@ -120,9 +120,9 @@ After install, re-run detection. If `agent-browser` is now available, set `SNIFF
 
 #### Step 1c: Verify capture tool compatibility
 
-After detection (Step 1) or installation (Step 1b), verify the installed version supports the CLI commands the sniff process needs.
+After detection (Step 1) or installation (Step 1b), verify the installed version supports the CLI commands the browser-sniff process needs.
 
-**For browser-use** — The CLI 2.0 commands (`open`, `eval`, `scroll`, `close`) all shipped in **v0.12.3**. Versions before that have an incomplete or experimental CLI that won't work for sniff.
+**For browser-use** — The CLI 2.0 commands (`open`, `eval`, `scroll`, `close`) all shipped in **v0.12.3**. Versions before that have an incomplete or experimental CLI that won't work for browser-sniff.
 
 ```bash
 # browser-use has no --version flag; get version from pip metadata
@@ -150,7 +150,7 @@ fi
 
 **If the selected tool fails the compatibility check**, offer to upgrade via `AskUserQuestion`:
 
-> "Found **<tool>** v<version>, but sniff requires v<min-version>+ for CLI capture commands. Would you like to upgrade?"
+> "Found **<tool>** v<version>, but browser-sniff requires v<min-version>+ for CLI capture commands. Would you like to upgrade?"
 >
 > Options:
 > 1. **Yes — upgrade <tool>** — runs the appropriate upgrade command (see below)
@@ -166,11 +166,11 @@ After upgrade, re-check the version. If the upgrade resolves the issue, proceed
 
 **Do NOT upgrade automatically.** Always ask permission first — upgrading packages can have side effects on the user's environment.
 
-If the tool passes the version check, proceed to Step 1d (if authenticated sniff) or Step 2a/2b (if anonymous sniff).
+If the tool passes the version check, proceed to Step 1d (if authenticated browser-sniff) or Step 2a/2b (if anonymous browser-sniff).
 
-#### Step 1d: Session Transfer (authenticated sniff only)
+#### Step 1d: Session Transfer (authenticated browser-sniff only)
 
-This step only runs when the user chose "authenticated sniff" (from Phase 1.7's sniff-as-primary or sniff-as-enrichment prompts, or when `AUTH_SESSION_AVAILABLE=true` and the user confirmed).
+This step only runs when the user chose "authenticated browser-sniff" (from Phase 1.7's sniff-as-primary or sniff-as-enrichment prompts, or when `AUTH_SESSION_AVAILABLE=true` and the user confirmed).
 
 **Situation detection:**
 ```bash
@@ -183,10 +183,10 @@ fi
 **When Chrome IS running**, use agent-browser to grab cookies, then ask the user to quit Chrome so browser-use can load the profile for capture:
 
 Present via `AskUserQuestion`:
-> "Chrome is running. I'll grab your cookies, then need you to quit Chrome so I can sniff with full page access."
+> "Chrome is running. I'll grab your cookies, then need you to quit Chrome so I can browser-sniff with full page access."
 >
-> 1. **Grab session, then quit Chrome** (Recommended) — "I save your cookies via agent-browser, you quit Chrome, then I sniff with browser-use using your profile. Full DOM access."
-> 2. **Log in within a new browser window** — "I'll open a visible browser. You log in, then I sniff."
+> 1. **Grab session, then quit Chrome** (Recommended) — "I save your cookies via agent-browser, you quit Chrome, then I browser-sniff with browser-use using your profile. Full DOM access."
+> 2. **Log in within a new browser window** — "I'll open a visible browser. You log in, then I browser-sniff."
 > 3. **I'll export a HAR file** — "You browse the site in DevTools, export the HAR."
 
 For option 1 (save-then-restore):
@@ -217,7 +217,7 @@ Present via `AskUserQuestion`:
 > "Chrome isn't running. I can load your Chrome profile directly — all your saved logins will be available."
 >
 > 1. **Use your Chrome profile** (Recommended, requires browser-use) — "Loads your real Chrome profile. Zero setup."
-> 2. **Log in within a new browser window** — "I'll open a visible browser. You log in, then I sniff."
+> 2. **Log in within a new browser window** — "I'll open a visible browser. You log in, then I browser-sniff."
 > 3. **I'll export a HAR file**
 
 For option 1 (browser-use profile reuse):
@@ -269,10 +269,10 @@ fi
 
 If no target-domain cookies are found, present via `AskUserQuestion`:
 
-> "Session transfer failed — no `<target-domain>` cookies found in the browser. The sniff would run unauthenticated."
+> "Session transfer failed — no `<target-domain>` cookies found in the browser. The browser-sniff would run unauthenticated."
 >
-> 1. **Log in manually** — "I'll open a headed browser. You log in, then I sniff."
-> 2. **Continue without auth** — "Sniff only public endpoints"
+> 1. **Log in manually** — "I'll open a headed browser. You log in, then I browser-sniff."
+> 2. **Continue without auth** — "Browser-Sniff only public endpoints"
 > 3. **Provide HAR manually** — "Export a HAR yourself from browser DevTools"
 
 **After loading a Chrome profile**, also verify the session is actually active on the target site. Cookies may exist but be expired:
@@ -288,8 +288,8 @@ If the result is `SESSION_EXPIRED` (login link visible, no account link), the pr
 
 > "Your browser session for `<site>` has expired (login page visible). I need a fresh login to discover authenticated endpoints."
 >
-> 1. **Open headed browser to log in** (Recommended) — "I'll open a visible browser. You log in, then I continue the sniff."
-> 2. **Continue without auth** — "Sniff only public endpoints"
+> 1. **Open headed browser to log in** (Recommended) — "I'll open a visible browser. You log in, then I continue the browser-sniff."
+> 2. **Continue without auth** — "Browser-Sniff only public endpoints"
 
 Do NOT silently proceed without auth when the session has expired. The authenticated surface is often the most valuable part of the API (order history, rewards, saved data).
 
@@ -330,7 +330,7 @@ For XHR-based interceptor captures, the same pattern applies: install the interc
 
 **Step 2a.1: Build the user flow plan**
 
-From the primary sniff goal (Step 0 in the SKILL.md), derive the interactive steps a real user would take to accomplish that goal. This is NOT a list of pages to load -- it is a sequence of actions.
+From the primary browser-sniff goal (Step 0 in the SKILL.md), derive the interactive steps a real user would take to accomplish that goal. This is NOT a list of pages to load -- it is a sequence of actions.
 
 Example for "Order a pizza for delivery" (Domino's):
 1. Click "Delivery" on homepage
@@ -436,7 +436,7 @@ When the user confirmed a logged-in session (AUTH_SESSION_AVAILABLE=true from Ph
    - If no header found → try cookie replay
    This is what propagates `Auth.Type` and auth config into the spec.
 
-7. **If auth pages redirect to login.** The session may have expired between the time the user confirmed login and the sniff reaches this step. Report: "Auth pages redirected to login — session may have expired. Auth-only endpoints not discovered." Do NOT fail the sniff — the public endpoints are still valid. Proceed to Step 2a.2 with the public set only.
+7. **If auth pages redirect to login.** The session may have expired between the time the user confirmed login and the browser-sniff reaches this step. Report: "Auth pages redirected to login — session may have expired. Auth-only endpoints not discovered." Do NOT fail the browser-sniff — the public endpoints are still valid. Proceed to Step 2a.2 with the public set only.
 
 **SPA interaction rule:** On each page/state, take a snapshot first. Look for interactive elements (buttons, forms, dropdowns, tabs). Click through them. SPAs fire API calls on interaction, not on page load. If you load a page and see no XHR activity, that means you need to interact with the page, not that there is nothing to find.
 
@@ -465,10 +465,10 @@ SNIFF_URLS="$DISCOVERY_DIR/sniff-urls.txt"
 # For EACH target page (run this loop in foreground — do NOT use run_in_background):
 browser-use open "<target-page-url>"
 sleep 4  # Wait for initial page load API calls to complete
-# Apply sniff pacing delay (starting at 1s, adapts per Sniff Pacing rules above)
+# Apply browser-sniff pacing delay (starting at 1s, adapts per Browser-Sniff Pacing rules above)
 browser-use scroll down  # Trigger lazy-loaded content
 sleep 1
-# Apply sniff pacing delay before next eval call
+# Apply browser-sniff pacing delay before next eval call
 
 # Collect API URLs via Performance API (browser-native, no injection needed)
 browser-use eval "var e=performance.getEntriesByType('resource');var u=[];for(var i=0;i<e.length;i++){var n=e[i].name;if(n.indexOf('<api-domain-1>')>-1||n.indexOf('<api-domain-2>')>-1)u.push(n);}u.join('|||');"
@@ -530,11 +530,11 @@ After collecting URLs, check whether the site uses a GraphQL BFF pattern. This i
 
 **Step 2a.2.7: JS bundle endpoint extraction (supplementary)**
 
-SPA frameworks (Angular, React, Vue, Next.js) compile all API endpoint paths into their main JS bundle. Extracting these paths supplements the sniff with endpoints that no user flow visits (admin features, migration tools, rarely-used settings).
+SPA frameworks (Angular, React, Vue, Next.js) compile all API endpoint paths into their main JS bundle. Extracting these paths supplements the browser-sniff with endpoints that no user flow visits (admin features, migration tools, rarely-used settings).
 
-**When to run:** After completing the interactive sniff (Steps 2a.1–2a.2.5). This is supplementary — the sniff is primary because it provides response shapes, auth patterns, and parameter types. Bundle extraction only gives endpoint paths.
+**When to run:** After completing the interactive browser-sniff (Steps 2a.1–2a.2.5). This is supplementary — the browser-sniff is primary because it provides response shapes, auth patterns, and parameter types. Bundle extraction only gives endpoint paths.
 
-**Skip when:** The site is server-rendered HTML without JS bundles, or the sniff already discovered 20+ endpoints and the API surface appears complete.
+**Skip when:** The site is server-rendered HTML without JS bundles, or the browser-sniff already discovered 20+ endpoints and the API surface appears complete.
 
 1. **Find the main bundle:**
    ```bash
@@ -565,31 +565,31 @@ SPA frameworks (Angular, React, Vue, Next.js) compile all API endpoint paths int
    "
    ```
 
-3. **Merge with sniff results.** Append bundle-discovered endpoints to `$SNIFF_URLS`. Mark their provenance:
+3. **Merge with browser-sniff results.** Append bundle-discovered endpoints to `$SNIFF_URLS`. Mark their provenance:
    ```bash
    # Append bundle-only endpoints (not already in sniff-urls.txt)
    # In the discovery report, mark these as "discovered: bundle"
    ```
 
-4. **Record API config.** If the bundle reveals useful config (API version headers, auth token construction, rate limit hints), note them in the discovery report's Sniff Configuration section.
+4. **Record API config.** If the bundle reveals useful config (API version headers, auth token construction, rate limit hints), note them in the discovery report's Browser-Sniff Configuration section.
 
 **Step 2a.3: Deduplicate and normalize**
 
 After collecting from all pages:
 ```bash
 # Strip query parameters and deduplicate to find unique API path patterns
-cat "$SNIFF_URLS" | sed 's/\?.*//' | sort -u > "$DISCOVERY_DIR/sniff-unique-paths.txt"
+cat "$SNIFF_URLS" | sed 's/\?.*//' | sort -u > "$DISCOVERY_DIR/browser-sniff-unique-paths.txt"
 ```
 
 **Step 2a.4: Generate enriched capture**
 
-The Performance API gives us URLs but not response bodies. To feed `printing-press sniff`, we need to call each unique API endpoint and capture the response:
+The Performance API gives us URLs but not response bodies. To feed `printing-press browser-sniff`, we need to call each unique API endpoint and capture the response:
 
 ```bash
 # For each unique API URL, fetch it and build a simple capture file
-# printing-press sniff accepts HAR or enriched capture JSON
+# printing-press browser-sniff accepts HAR or enriched capture JSON
 # When fetching each unique API URL to build enriched capture:
-# Apply sniff pacing between requests (1s initial, adaptive per Sniff Pacing rules)
+# Apply browser-sniff pacing between requests (1s initial, adaptive per Browser-Sniff Pacing rules)
 # On 429: double delay, log, continue with remaining URLs
 ```
 
@@ -619,7 +619,7 @@ If browser-use is not available, use agent-browser with Claude driving the explo
      - Find the interactive element for this step (button, form, link, dropdown)
      - Click/fill/submit it
      - `agent-browser wait --network-idle` after each interaction
-     - Apply sniff pacing between interactions (1s initial, adaptive per Sniff Pacing rules)
+     - Apply browser-sniff pacing between interactions (1s initial, adaptive per Browser-Sniff Pacing rules)
    - After completing the primary flow, run 1-2 secondary flows
    - Skip: navigation links, footer links, social media buttons, cookie/consent banners
    - Fill forms with realistic sample data based on the domain (real-looking addresses, names, etc.)
@@ -631,14 +631,14 @@ If browser-use is not available, use agent-browser with Claude driving the explo
    For each API request (filter by JSON content-type, skip analytics domains):
    ```bash
    agent-browser network request <request-id> --json
-   # Apply sniff pacing between response body fetches
+   # Apply browser-sniff pacing between response body fetches
    # These are direct API calls and most likely to trigger rate limits
    ```
-   Combine HAR metadata + response bodies into an enriched capture JSON at `$DISCOVERY_DIR/sniff-capture.json`.
+   Combine HAR metadata + response bodies into an enriched capture JSON at `$DISCOVERY_DIR/browser-sniff-capture.json`.
 
 4. **Stop HAR recording**:
    ```bash
-   agent-browser network har stop "$DISCOVERY_DIR/sniff-capture.har"
+   agent-browser network har stop "$DISCOVERY_DIR/browser-sniff-capture.har"
    ```
 
 #### Step 2c: Thin-results safety check
@@ -649,15 +649,15 @@ After completing the primary user flow capture (browser-use or agent-browser), c
 
 2. **Re-sniff with interaction.** Go back to the page where results were thinnest. Take a snapshot. Find interactive elements. Click the most prominent one. Wait for network activity. Repeat for at least 3 interactions before accepting thin results.
 
-3. **Compare against known endpoints.** If Phase 1 research found community wrappers documenting N endpoints but the sniff found fewer than N/2, the sniff missed something. Community wrappers are a floor, not a ceiling -- they represent what someone else already reverse-engineered, often years ago. The real API surface is almost certainly larger.
+3. **Compare against known endpoints.** If Phase 1 research found community wrappers documenting N endpoints but the browser-sniff found fewer than N/2, the browser-sniff missed something. Community wrappers are a floor, not a ceiling -- they represent what someone else already reverse-engineered, often years ago. The real API surface is almost certainly larger.
 
-4. **Report the gap honestly.** If re-sniffing with interaction still produces thin results, report: "Sniff captured X endpoints but community wrappers document Y. The site may use WebSocket, protobuf, server-side rendering, or other techniques that resist browser capture." Do NOT conclude "the API has few endpoints" when the real answer may be "I didn't interact enough to trigger them."
+4. **Report the gap honestly.** If re-sniffing with interaction still produces thin results, report: "Browser-Sniff captured X endpoints but community wrappers document Y. The site may use WebSocket, protobuf, server-side rendering, or other techniques that resist browser capture." Do NOT conclude "the API has few endpoints" when the real answer may be "I didn't interact enough to trigger them."
 
 If the thin-results check triggers a re-sniff that discovers additional endpoints, merge the new captures with the originals before proceeding to Step 3.
 
-#### Step 2d: Cookie auth validation (authenticated sniff only)
+#### Step 2d: Cookie auth validation (authenticated browser-sniff only)
 
-**Skip this step if:** The sniff was anonymous (no session transfer in Step 1d), or the API uses API key / Bearer token auth rather than cookie-based session auth.
+**Skip this step if:** The browser-sniff was anonymous (no session transfer in Step 1d), or the API uses API key / Bearer token auth rather than cookie-based session auth.
 
 **Purpose:** Before promising `auth login --chrome` in the generated CLI, validate that browser cookies actually produce authenticated responses when replayed outside the browser context. Some APIs use CSRF tokens, SameSite cookie policies, or other mechanisms that prevent cookie-only replay.
 
@@ -689,61 +689,61 @@ If the thin-results check triggers a re-sniff that discovers additional endpoint
    | 401/403 | 401/403 | **Fail** — cookies don't replay (likely CSRF, SameSite, or IP binding). Warn the user and do not offer browser auth. |
    | Other | Any | **Inconclusive** — try a different test endpoint. If all attempts are inconclusive after 3 endpoints, treat as Fail. |
 
-5. **On Pass:** Proceed to Step 3. The sniff report (Step 5) should note: "Cookie auth validated — the generated CLI will support `auth login --chrome`."
+5. **On Pass:** Proceed to Step 3. The browser-sniff report (Step 5) should note: "Cookie auth validated — the generated CLI will support `auth login --chrome`."
 
 6. **On Fail:** Inform the user via the conversation:
    > "Authenticated endpoints were discovered, but cookie replay failed (likely CSRF tokens or strict cookie policies). The generated CLI will include these endpoints but won't offer `auth login --chrome`. You'll need to manually provide auth tokens via environment variables."
 
-   Set `Auth.Type = "none"` in the capture's auth section. Include the authenticated endpoints in the spec (they're still valid endpoints), but the CLI won't have a browser auth path. Note the failure reason in the sniff report.
+   Set `Auth.Type = "none"` in the capture's auth section. Include the authenticated endpoints in the spec (they're still valid endpoints), but the CLI won't have a browser auth path. Note the failure reason in the browser-sniff report.
 
 #### Step 3: Analyze capture
 
 Run websniff on the captured traffic:
 ```bash
-printing-press sniff --har "$DISCOVERY_DIR/sniff-capture.har" --name <api> --output "$RESEARCH_DIR/<api>-sniff-spec.yaml"
+printing-press browser-sniff --har "$DISCOVERY_DIR/browser-sniff-capture.har" --name <api> --output "$RESEARCH_DIR/<api>-browser-sniff-spec.yaml"
 ```
 
 If using agent-browser's enriched capture format instead:
 ```bash
-printing-press sniff --har "$DISCOVERY_DIR/sniff-capture.json" --name <api> --output "$RESEARCH_DIR/<api>-sniff-spec.yaml"
+printing-press browser-sniff --har "$DISCOVERY_DIR/browser-sniff-capture.json" --name <api> --output "$RESEARCH_DIR/<api>-browser-sniff-spec.yaml"
 ```
 
 #### Step 4: Report and update spec source
 
-Report: "Sniff discovered **N endpoints** across **M resources**. [X new endpoints not in the original spec.]"
+Report: "Browser-Sniff discovered **N endpoints** across **M resources**. [X new endpoints not in the original spec.]"
 
 Update the spec source for Phase 2:
 - **Enrichment mode**: Phase 2 will use `--spec <original> --spec <sniff-spec> --name <api>` to merge both
 - **Primary mode**: Phase 2 will use `--spec <sniff-spec>` directly
 
-#### Step 5: Write sniff discovery report
+#### Step 5: Write browser-sniff discovery report
 
-Write a structured sniff provenance report to `$DISCOVERY_DIR/sniff-report.md`. This report preserves the discovery evidence so a future maintainer can reproduce or extend the sniff.
+Write a structured browser-sniff provenance report to `$DISCOVERY_DIR/browser-sniff-report.md`. This report preserves the discovery evidence so a future maintainer can reproduce or extend the browser-sniff.
 
 The report must contain these sections:
 
-1. **User Goal Flow** — The primary sniff goal and each step attempted.
+1. **User Goal Flow** — The primary browser-sniff goal and each step attempted.
    - Goal: [e.g., "Order a pizza for delivery"]
    - Steps completed: [numbered list of steps taken, with which API operations each step triggered]
    - Steps skipped: [any steps that couldn't be completed, with reason]
    - Secondary flows attempted: [any additional workflows beyond the primary goal]
    - Coverage: [X of Y planned steps completed]
 
-2. **Pages & Interactions** — List every URL browsed and interaction performed during the sniff, in order. Include the page purpose and what was clicked/filled/submitted (e.g., "Homepage -- clicked 'Delivery' button", "Address modal -- entered '350 5th Ave', clicked 'Continue'").
+2. **Pages & Interactions** — List every URL browsed and interaction performed during the browser-sniff, in order. Include the page purpose and what was clicked/filled/submitted (e.g., "Homepage -- clicked 'Delivery' button", "Address modal -- entered '350 5th Ave', clicked 'Continue'").
 
-3. **Sniff Configuration** — Backend used (browser-use, agent-browser, or manual HAR), pacing settings (initial delay, final effective rate), and proxy pattern detection result (proxy-envelope detected / not detected, with the proxy URL if applicable).
+3. **Browser-Sniff Configuration** — Backend used (browser-use, agent-browser, or manual HAR), pacing settings (initial delay, final effective rate), and proxy pattern detection result (proxy-envelope detected / not detected, with the proxy URL if applicable).
 
 4. **Endpoints Discovered** — A markdown table with columns: Method, Path, Status Code, Content-Type, Auth. One row per unique endpoint observed. The Auth column is "public" or "auth-required" (based on Step 2a.1.5 classification). If no authenticated flow was run, omit the Auth column.
 
-5. **Coverage Analysis** — What resource types were exercised (e.g., "collections, workspaces, teams, categories") and what was likely missed. Compare against the Phase 1 research brief to identify gaps (e.g., "Brief mentions 'flows' but no flow endpoints were discovered during sniff").
+5. **Coverage Analysis** — What resource types were exercised (e.g., "collections, workspaces, teams, categories") and what was likely missed. Compare against the Phase 1 research brief to identify gaps (e.g., "Brief mentions 'flows' but no flow endpoints were discovered during browser-sniff").
 
 6. **Response Samples** — For each unique response shape (keyed by status code + content-type category), include a truncated sample:
    - JSON/text responses: first 2KB or 100 lines, whichever is smaller
    - Binary responses (images, protobuf, etc.): skip content, include a metadata note: `Binary response: <content-type>, <size> bytes`
    - Aim for one sample per unique shape, not one per endpoint
 
-7. **Rate Limiting Events** — Any 429 responses encountered, delays applied, and effective sniff rate achieved (e.g., "Sniffed 7 endpoints at ~1.5 req/s effective rate, one 429 at request #4").
+7. **Rate Limiting Events** — Any 429 responses encountered, delays applied, and effective browser-sniff rate achieved (e.g., "Sniffed 7 endpoints at ~1.5 req/s effective rate, one 429 at request #4").
 
-8. **Authentication Context** — Whether the sniff used an authenticated session. If yes: transfer method used (auto-connect / profile / headed login / HAR), which endpoints were only reachable with auth (e.g., "order history, saved addresses, rewards required login"), the auth header scheme discovered (e.g., "Authorization: PagliacciAuth {customerId}|{authToken}", "Bearer token from localStorage"), and confirmation that session state was excluded from manuscript archiving. If no: "No authenticated session used."
+8. **Authentication Context** — Whether the browser-sniff used an authenticated session. If yes: transfer method used (auto-connect / profile / headed login / HAR), which endpoints were only reachable with auth (e.g., "order history, saved addresses, rewards required login"), the auth header scheme discovered (e.g., "Authorization: PagliacciAuth {customerId}|{authToken}", "Bearer token from localStorage"), and confirmation that session state was excluded from manuscript archiving. If no: "No authenticated session used."
 
-9. **Bundle Extraction** — If JS bundle extraction ran (Step 2a.2.7), list: the bundle URL analyzed, the API base URL discovered, endpoints found only in the bundle (not during interactive sniff), and any API config extracted (version headers, auth construction patterns). If bundle extraction did not run, omit this section.
+9. **Bundle Extraction** — If JS bundle extraction ran (Step 2a.2.7), list: the bundle URL analyzed, the API base URL discovered, endpoints found only in the bundle (not during interactive browser-sniff), and any API config extracted (version headers, auth construction patterns). If bundle extraction did not run, omit this section.
diff --git a/skills/printing-press/references/crowd-sniff.md b/skills/printing-press/references/crowd-sniff.md
index d311526b..df5e7f53 100644
--- a/skills/printing-press/references/crowd-sniff.md
+++ b/skills/printing-press/references/crowd-sniff.md
@@ -1,9 +1,9 @@
-# Crowd Sniff Implementation
+# Crowd-Sniff Implementation
 
 > **When to read:** This file is referenced by Phase 1.8 of the printing-press skill.
-> Read it when the user approves crowd sniff (mining npm SDKs and GitHub code search).
+> Read it when the user approves crowd-sniff (mining npm SDKs and GitHub code search).
 
-### If user approves crowd sniff
+### If user approves crowd-sniff
 
 Ensure the discovery directory exists:
 
@@ -23,7 +23,7 @@ If the API has a known base URL from Phase 1 research, pass it:
 printing-press crowd-sniff --api <api> --base-url <known-base-url> --output "$RESEARCH_DIR/<api>-crowd-spec.yaml" --json > "$DISCOVERY_DIR/crowd-sniff-provenance.json"
 ```
 
-Report the results: "Crowd sniff discovered **N endpoints** across **M resources** (X from npm, Y from GitHub)."
+Report the results: "Crowd-sniff discovered **N endpoints** across **M resources** (X from npm, Y from GitHub)."
 
 **Feed into Phase 2:**
 - **Enrichment mode**: Phase 2 will use `--spec <original> --spec <crowd-spec> --name <api>` to merge both
@@ -31,7 +31,7 @@ Report the results: "Crowd sniff discovered **N endpoints** across **M resources
 
 #### Write crowd-sniff discovery report
 
-Write a structured crowd-sniff provenance report to `$DISCOVERY_DIR/crowd-sniff-report.md`. This report preserves the discovery evidence so a future maintainer can see what community sources informed the spec.
+Write a structured crowd-sniff provenance report to `$DISCOVERY_DIR/crowd-browser-sniff-report.md`. This report preserves the discovery evidence so a future maintainer can see what community sources informed the spec.
 
 The report must contain these sections:
 
diff --git a/skills/printing-press/references/deepwiki-research.md b/skills/printing-press/references/deepwiki-research.md
index d7727fcd..92ed5f9b 100644
--- a/skills/printing-press/references/deepwiki-research.md
+++ b/skills/printing-press/references/deepwiki-research.md
@@ -115,7 +115,7 @@ Backing" scoring dimension (0-2 points). Combined with another source (e.g.,
 community issues, competitor tool analysis), it can push a feature to the
 maximum 2/2 Research Backing score.
 
-### Pre-Sniff Auth Intelligence (Phase 1.6)
+### Pre-Browser-Sniff Auth Intelligence (Phase 1.6)
 
 DeepWiki auth findings directly feed into Phase 1.6's auth profile classification.
 If DeepWiki reveals the token format (e.g., `tr_dev_` prefix for Trigger.dev,
diff --git a/skills/printing-press/references/voice.md b/skills/printing-press/references/voice.md
index a8ae6c36..52712985 100644
--- a/skills/printing-press/references/voice.md
+++ b/skills/printing-press/references/voice.md
@@ -29,7 +29,7 @@ type by hand, now running digital machinery with the same pride of craft.
 | **Spec** | The API contract that drives generation. |
 | **The Printing Press** | This whole system — binary, templates, skills, catalog. |
 | **Absorb Manifest** | The feature inventory from Phase 1.5. Everything to build. |
-| **Sniff Gate** | Decision point on whether to capture live site traffic. |
+| **Browser-Sniff Gate** | Decision point on whether to capture live site traffic. |
 | **Quality Gates** | The 7 static checks every Printed CLI must pass. |
 
 ## Examples

← 86bdfd2e fix(cli): patch verifies target shape + runs build in target  ·  back to Cli Printing Press  ·  feat(cli): add auth doctor subcommand (#226) 72916ac1 →