← back to Cli Printing Press
docs(cli): consolidate AGENTS.md via progressive disclosure (#574)
26fb45eac94ea5d283ba246d3370c1a5258c9801 · 2026-05-04 12:34:22 -0500 · Cathryn Lavery
* docs(cli): consolidate AGENTS.md via progressive disclosure
* docs(cli): drop checked-in PR description draft
* docs(cli): restore inline anti-reimplementation and golden triggers
* docs(cli): remove absolute local doc links
* docs(cli): restore inline operational write-time rules
Three rules that agents must apply at code-write time were extracted
into reference docs where agents are unlikely to fetch them mid-task:
- Side-effect command convention (print by default, opt-in flag, short-
circuit on cliutil.IsVerifyEnv()) — applies when authoring any novel
command that opens tabs, sends notifications, or dials OS handlers.
- Generator-reserved namespaces (internal/cliutil/, internal/mcp/cobratree/)
— applies when placing new files in a printed CLI.
- mcp:read-only skip cases (commands that mutate state or write to
--output/--repo) — applies when adding the annotation in a generator
template.
These are command-shaped rules, not definitions, so they belong inline.
* docs(cli): restore why-clauses for gofmt and lefthook rules
Two hygiene rules lost their justifications during consolidation:
- lefthook install --reset-hooks-path: the flag exists to clear stale
core.hooksPath settings; without that note the flag reads as cargo-cult.
- gofmt -w . from the repo root: the prohibition exists because gofmt
walks into testdata/golden/expected/ and rewrites frozen fixtures.
Without the consequence, agents will override the rule.
The "Why:" line is what lets agents judge edge cases instead of either
blindly following or quietly skipping. Cheap to preserve.
* docs(cli): inline pointer-rot rule in Editing AGENTS.md
The pointer-rot rule lived only in docs/DOCS.md, which created a chicken-
and-egg problem: an agent editing docs/CATALOG.md needs to know that the
inline AGENTS.md trigger sentence may also need updating, but the rule
itself was behind a pointer the agent had no reason to fetch first.
Inlining it here puts the rule where editors of extracted docs will see
it — on every turn — while keeping the longer doc-authoring guidance in
docs/DOCS.md.
---------
Co-authored-by: Trevin Chow <trevin@trevinchow.com>
Files touched
M AGENTS.mdA docs/ARTIFACTS.mdA docs/CATALOG.mdA docs/DOCS.mdA docs/GLOSSARY.mdA docs/GOLDEN.mdA docs/RELEASE.md
Diff
commit 26fb45eac94ea5d283ba246d3370c1a5258c9801
Author: Cathryn Lavery <50469282+cathrynlavery@users.noreply.github.com>
Date: Mon May 4 12:34:22 2026 -0500
docs(cli): consolidate AGENTS.md via progressive disclosure (#574)
* docs(cli): consolidate AGENTS.md via progressive disclosure
* docs(cli): drop checked-in PR description draft
* docs(cli): restore inline anti-reimplementation and golden triggers
* docs(cli): remove absolute local doc links
* docs(cli): restore inline operational write-time rules
Three rules that agents must apply at code-write time were extracted
into reference docs where agents are unlikely to fetch them mid-task:
- Side-effect command convention (print by default, opt-in flag, short-
circuit on cliutil.IsVerifyEnv()) — applies when authoring any novel
command that opens tabs, sends notifications, or dials OS handlers.
- Generator-reserved namespaces (internal/cliutil/, internal/mcp/cobratree/)
— applies when placing new files in a printed CLI.
- mcp:read-only skip cases (commands that mutate state or write to
--output/--repo) — applies when adding the annotation in a generator
template.
These are command-shaped rules, not definitions, so they belong inline.
* docs(cli): restore why-clauses for gofmt and lefthook rules
Two hygiene rules lost their justifications during consolidation:
- lefthook install --reset-hooks-path: the flag exists to clear stale
core.hooksPath settings; without that note the flag reads as cargo-cult.
- gofmt -w . from the repo root: the prohibition exists because gofmt
walks into testdata/golden/expected/ and rewrites frozen fixtures.
Without the consequence, agents will override the rule.
The "Why:" line is what lets agents judge edge cases instead of either
blindly following or quietly skipping. Cheap to preserve.
* docs(cli): inline pointer-rot rule in Editing AGENTS.md
The pointer-rot rule lived only in docs/DOCS.md, which created a chicken-
and-egg problem: an agent editing docs/CATALOG.md needs to know that the
inline AGENTS.md trigger sentence may also need updating, but the rule
itself was behind a pointer the agent had no reason to fetch first.
Inlining it here puts the rule where editors of extracted docs will see
it — on every turn — while keeping the longer doc-authoring guidance in
docs/DOCS.md.
---------
Co-authored-by: Trevin Chow <trevin@trevinchow.com>
---
AGENTS.md | 369 +++++++++++++++---------------------------------------
docs/ARTIFACTS.md | 26 ++++
docs/CATALOG.md | 20 +++
docs/DOCS.md | 27 ++++
docs/GLOSSARY.md | 57 +++++++++
docs/GOLDEN.md | 31 +++++
docs/RELEASE.md | 9 ++
7 files changed, 270 insertions(+), 269 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index f1f20638..7ef91d61 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,131 +1,69 @@
# CLI Printing Press - Development Conventions
## Machine vs Printed CLI
-
This repo is **the machine** (generator, templates, binary, skills) that produces **printed CLIs**. When fixing a bug or adding a feature, ask: machine change or printed-CLI change?
-
-- **Machine changes** (generator, templates, parser, skills) affect every future CLI; they must generalize across APIs, spec formats, and auth patterns.
-- **Printed-CLI changes** (`~/printing-press/library/<api-slug>/`) fix one CLI; they don't compound.
-
-Rules:
-
-- **Default to machine changes.** If a problem appears in a printed CLI, ask first whether the generator should have gotten this right. Only fix the printed CLI directly when the issue is genuinely API-specific.
-- **Don't change the machine for one CLI's edge case.** A fix that helps one API but breaks another doesn't belong in the generator — guard it with a clear conditional or leave it as a printed-CLI fix.
-- **Don't hardcode API/site names in reusable artifacts.** Skills, templates, generator code, prompts, shared docs must use placeholders (`<api>`, `<site>`, "the target site") unless the text is explicitly an example or test fixture. Concrete names belong in `Example:` paragraphs, never in operational instructions.
-- **Update dependent verifiers in the same change.** A new generator capability that affects scoring requires a scorer update; one that changes the MCP surface requires an audit update. Forgetting either half ships a CLI whose advertised contract diverges from what's emitted.
-
-When iterating on a printed CLI to discover issues, label findings as systemic (retro candidate) vs specific (printed-CLI fix). The retro → plan → implement loop feeds discoveries back into the machine.
+- **Machine changes** (generator, templates, parser, skills) affect every future CLI and must generalize across APIs, spec formats, and auth patterns.
+- **Printed-CLI changes** (`~/printing-press/library/<api-slug>/`) fix one CLI and do not compound.
+- **Default to machine changes.** If a problem appears in a printed CLI, ask first whether the generator should have gotten it right. Only fix the printed CLI directly when the issue is genuinely API-specific.
+- **Don't change the machine for one CLI's edge case.** If a fix helps one API but breaks another, guard it with a clear conditional or leave it as a printed-CLI fix.
+- **Don't hardcode API/site names in reusable artifacts.** Skills, templates, generator code, prompts, and shared docs must use placeholders (`<api>`, `<site>`, "the target site") unless the text is explicitly an example or test fixture.
+- **Update dependent verifiers in the same change.** A new generator capability that affects scoring requires a scorer update; one that changes the MCP surface requires an audit update.
+When iterating on a printed CLI to discover issues, label findings as systemic (retro candidate) vs specific (printed-CLI fix). The retro -> plan -> implement loop feeds discoveries back into the machine.
### Anti-reimplementation
-
-A printed CLI wraps an API; it does not replace one. Novel-feature commands must call the real endpoint or read from the local store populated by sync. Reimplementations are worse than the API they pretend to replace.
-
-Reject:
-
-- Hand-rolled response builders that return constants, hardcoded JSON, or struct literals shaped like an API payload
-- Endpoint stubs that return `"OK"` or a canned success message without calling the client
-- Aggregations computed in-process when the API has an aggregation endpoint
-- Enum mappings and reference data synthesized locally when the API returns them
-
-Carve-outs:
-
-- Commands that read from `internal/store` (the `stale`, `bottleneck`, `health`, `reconcile` family) — local-data, not fake API calls
-- Commands that bypass the store package and operate on the local SQLite file directly through `database/sql` (import + `sql.Open`/`sql.OpenDB`) — same data, thinner surface
-- Commands that cache an API response in the store after calling it — both a client call and a store call is fine
-- Commands whose data is the curated content itself (substitution tables, holiday lists, currency metadata) — opt in via `// pp:novel-static-reference` directive in the command's source file
-- Commands that call the real API through a wrapper the dogfood heuristic cannot see — opt in via `// pp:client-call` directive in the command's source file, and use it only when the hidden helper performs a real external API call. Do not use it for hardcoded payloads, local-only transforms, or fake endpoint stubs.
-
-Enforced by the absorb manifest's Kill Check (`skills/printing-press/references/absorb-scoring.md`) and dogfood's `reimplementation_check`, which flags handler files showing neither a client call nor a store access (without the static-reference or client-call opt-out).
+A printed CLI wraps an API; it does not replace one. Novel-feature commands must call the real endpoint or read from the local store populated by sync.
+- Reject hand-rolled response builders that return constants, hardcoded JSON, or struct literals shaped like an API payload.
+- Reject endpoint stubs that return `"OK"` or a canned success message without calling the client.
+- Reject aggregations computed in-process when the API has an aggregation endpoint.
+- Reject enum mappings and reference data synthesized locally when the API returns them.
+- Carve-outs: commands that read from `internal/store`; commands that operate on the local SQLite file via `database/sql`; commands that call the API and then cache to the store; commands whose data is curated static content via `// pp:novel-static-reference`; commands that make a real hidden client call via `// pp:client-call`, but only when the hidden helper performs a real external API call. Do not use `// pp:client-call` for hardcoded payloads, local-only transforms, or fake endpoint stubs.
+Enforced by the absorb manifest's Kill Check (`skills/printing-press/references/absorb-scoring.md`) and dogfood's `reimplementation_check`, which flags handler files showing neither a client call nor a store access without an opt-out.
## Agent-Native Surface
-
-Every printed CLI exposes two surfaces: a CLI surface for humans and an MCP surface for agents. Any action a user can take should be reachable by an agent, but the surfaces are not identical — operator ergonomics belong on the human-facing CLI, not in an agent's tool catalog.
+Every printed CLI exposes two surfaces: a CLI surface for humans and an MCP surface for agents. Any action a user can take should be reachable by an agent, but operator ergonomics belong on the human-facing CLI, not in an agent's tool catalog.
### Default: expose; skip rules are exceptions
-
-The runtime walker in `internal/mcp/cobratree/` mirrors the Cobra tree at server start, registering every user-facing command as an MCP tool. It skips a command only if one of these applies:
-
-1. **Endpoint mirrors keep typed schemas.** Commands annotated `cmd.Annotations["pp:endpoint"] = "<resource>.<endpoint>"` are already registered as typed tools elsewhere; the walker skips to avoid duplicates.
-2. **Framework commands.** Listed by name in `cobratree/classify.go.tmpl`'s `frameworkCommands` set. Two reasons qualify a command for that set: a typed equivalent is strictly better (`sql`, `search`, `context`), or the command is non-functional via MCP (interactive setup, version reporting, local-only state — `auth`, `completion`, `doctor`, `version`, `feedback`, `profile`, `which`, `help`).
-3. **Per-command opt-out.** `cmd.Annotations["mcp:hidden"] = "true"` for domain commands that need human-in-the-loop input.
-
-**Store-population commands stay exposed.** `sync`, `stale`, `orphans`, `reconcile`, `load`, `export`, `import`, `workflow`, `analytics` look like operator commands but have real agent value — `sql` and `search` return empty until `sync` populates the store. Hiding either side breaks the contract.
-
-**When in doubt, leave it exposed.** Hiding a command that should be exposed silently breaks contracts; exposing one that should be hidden just adds a low-value tool. The default flips toward exposure because agents must be able to do anything users can.
+The runtime walker in `internal/mcp/cobratree/` mirrors the Cobra tree at server start and registers every user-facing command as an MCP tool unless one of these applies:
+1. Commands annotated `cmd.Annotations["pp:endpoint"] = "<resource>.<endpoint>"` already have typed tools and are skipped to avoid duplicates.
+2. Framework commands listed in `cobratree/classify.go.tmpl`'s `frameworkCommands` set are skipped because a typed equivalent is better (`sql`, `search`, `context`) or the command is non-functional via MCP (`auth`, `completion`, `doctor`, `version`, `feedback`, `profile`, `which`, `help`).
+3. `cmd.Annotations["mcp:hidden"] = "true"` opts out a domain command that needs human-in-the-loop input.
+Store-population commands stay exposed: `sync`, `stale`, `orphans`, `reconcile`, `load`, `export`, `import`, `workflow`, `analytics`. `sql` and `search` return empty until `sync` populates the store. When in doubt, leave it exposed.
### Tool safety annotations
+MCP hosts use `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint` to decide when to ask for permission. Missing annotations default to "could write or delete."
+- Endpoint mirrors: `GET` -> read-only + open-world, `DELETE` -> destructive + open-world, `POST`/`PUT`/`PATCH` -> open-world.
+- Built-in tools: `context`, `sql`, `search` are read-only and local-only.
+- Runtime walker shell-out tools get no annotations by default. Opt into read-only with `cmd.Annotations["mcp:read-only"] = "true"` for novel commands that only read from the API, the local store, or the CLI tree itself. Skip the annotation when the command can mutate external state (writes via API, store updates, git pushes) or write to user-visible files outside the local cache (commands accepting `--output <file>`, `--repo <dir>`, etc.).
+Wrong annotations are worse than missing ones. A false `readOnlyHint: true` on a mutating tool is a real bug; a missing annotation is just a permission prompt.
-MCP hosts use `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint` to decide when to ask the user for permission. Missing annotations default to "could write or delete" — every call prompts.
-
-The generator emits annotations automatically:
+### Side-effect commands
+Hand-written novel commands that perform visible actions (open browser tabs, send notifications, dial out to OS handlers) follow a two-part rule:
+1. Print by default; require explicit opt-in (`--launch`, `--send`, `--play`, etc.) to actually act.
+2. Short-circuit when `cliutil.IsVerifyEnv()` is true. The verifier sets `PRINTING_PRESS_VERIFY=1` in every mock-mode subprocess; this env-var check is the floor that catches any side-effect command the verifier's heuristic classifier misses.
-- **Endpoint mirrors:** from HTTP method. `GET` → read-only + open-world. `DELETE` → destructive + open-world. `POST`/`PUT`/`PATCH` → open-world (writes, not destructive).
-- **Built-in tools:** `context`, `sql`, `search` are read-only (no open-world; they read local data).
-- **Shell-out tools (runtime walker):** no annotations by default — the walker can't infer from a Cobra command alone. Opt into read-only with `cmd.Annotations["mcp:read-only"] = "true"` for novel commands that don't mutate external state (read-only API queries, cache lookups, derivations).
-
-When adding a generator template that emits a novel command whose only effect is reading from the API, the local store, or the CLI tree itself, set `Annotations: map[string]string{"mcp:read-only": "true"}` in the rendered Cobra literal. Skip the annotation when the command can mutate external state (writes via API, store updates, git pushes) or write to user-visible files outside the local cache (e.g., commands accepting `--output <file>` or `--repo <dir>`). The polish skill catches misses heuristically per CLI, but template-time emission is the single-source path.
-
-Wrong annotations are worse than missing ones: the host trusts the claim and stops asking. A `readOnlyHint: true` on a mutating tool is a real bug; a missing annotation is just a permission prompt.
+### Generator-reserved namespaces
+`internal/cliutil/` and `internal/mcp/cobratree/` are generator-owned packages emitted into every printed CLI. Do not hand-author code in them and do not name agent-authored helpers that collide with their exports — regen will overwrite the work. Novel-feature code goes in command packages and may import from `cliutil`.
### Typed exit-code verification
-
-`printing-press verify` treats exit `0` as success by default. For commands where a non-zero code is an intentional, non-error control-flow result (for example, "no confident match"), declare that contract in the Cobra command:
-
-```go
-Annotations: map[string]string{"pp:typed-exit-codes": "0,2"},
-```
-
-The verifier reads this annotation first, then falls back to parsing a command-level `Exit codes:` help block when the annotation is absent. Do **not** put the whole global failure palette (`3` not found, `4` auth, `5` API error, etc.) in a command-level `Exit codes:` block unless those codes should count as a verify pass for that specific command. General troubleshooting exit-code docs belong in README/SKILL prose, not command help.
+`printing-press verify` treats exit `0` as success by default. For commands where a non-zero code is intentional control flow, declare it in Cobra with `Annotations: map[string]string{"pp:typed-exit-codes": "0,2"}`. The verifier reads that annotation first, then falls back to a command-level `Exit codes:` help block. Do not put the whole global failure palette in a command-level help block unless those codes should count as verify-pass for that specific command.
## Build, Test & Lint
-
```bash
go build -o ./printing-press ./cmd/printing-press
go test ./...
go fmt ./...
golangci-lint run ./...
```
+A pre-commit hook runs `gofmt -w` on staged Go files automatically. A pre-push hook runs `golangci-lint`. The same config in `.golangci.yml` runs in CI. Install hooks with `brew install lefthook && lefthook install --reset-hooks-path`; the `--reset-hooks-path` flag clears stale local `core.hooksPath` settings that block hook sync. Avoid `lefthook install --force` unless intentionally overriding a custom hooks path.
+After writing Go code, format it with `go fmt ./...` before handing back work. Use `go fmt ./...` for repo-wide formatting and `gofmt -w path/to/file.go` only for explicit files. Do not run `gofmt -w ./...` (gofmt does not accept Go package patterns) or `gofmt -w .` from the repo root (it walks into `testdata/golden/expected/` and rewrites frozen golden fixtures).
+Always use relative paths for build output. Never build to `/tmp` or another shared absolute path; use `./printing-press`.
-A pre-commit hook runs `gofmt -w` on staged Go files automatically. A pre-push hook runs `golangci-lint`. The same config (`.golangci.yml`: errcheck, govet, staticcheck, unused) runs in CI. Install hooks with `brew install lefthook && lefthook install --reset-hooks-path`; `--reset-hooks-path` clears stale local `core.hooksPath` settings that block hook sync. Avoid `lefthook install --force` unless intentionally overriding a custom hooks path. To run lint manually: `golangci-lint run ./...`
-
-**After writing Go code, format it with `go fmt ./...` before handing back work.** This is intentionally redundant with the pre-commit hook: `gofmt` is idempotent, and the hook is a safety net for commits while agents often stop before committing. Use `go fmt ./...` for repo-wide formatting and `gofmt -w path/to/file.go` only for explicit files. Do not run `gofmt -w ./...` — `gofmt` does not accept Go package patterns. Do not run `gofmt -w .` from the repo root — it can walk into `testdata/golden/expected/` and rewrite frozen golden fixtures. `go fmt ./...` formats package files and skips `testdata` and `vendor` by convention. Code written to external directories (e.g., `~/printing-press/library/`) must be formatted explicitly because repo hooks will not see it.
-
-**IMPORTANT: Always use relative paths for build output.** Never build to `/tmp` or any shared absolute path. Multiple worktrees run concurrently and will stomp on each other. Use `./printing-press` exactly as shown above.
-
-## Golden Output Harness
-
-The golden harness is a byte-level behavior check for deterministic, offline `printing-press` commands and generated artifacts. It complements unit tests by catching user-visible output drift and printed CLI artifact drift.
-
-Use golden tests as refactor confidence rails for the machine. When changing internals, templates, pipeline plumbing, or broad architecture, a passing golden suite tells agents that the externally observable contracts captured by the fixtures did not move. That is the main purpose: preserve stable command output and generated artifact contracts through major machine changes, not exhaustively test every branch.
-
+## Generator Output Stability
Run `scripts/golden.sh verify` whenever a change may affect CLI command output, catalog rendering, browser-sniff or crowd-sniff output, generated specs or generated printed CLI files, templates under `internal/generator/templates/`, naming, endpoint derivation, auth emission, manifest generation, scorecard output, or pipeline artifacts.
-
-If a refactor changes machine code but claims behavior is identical, `scripts/golden.sh verify` should pass without fixture updates.
-
-Run `scripts/golden.sh update` only when the behavior/output change is intentional. After updating, inspect the diffs manually and explain in the final response why the golden changes are expected. Never update goldens just to make a failing check pass.
-
-Golden cases must be deterministic, offline, and auth-free. Do not add cases that depend on network access, user credentials or env vars, `~/printing-press`, wall-clock timestamps unless normalized, machine-specific absolute paths unless normalized, or large generated printed CLI trees unless the compared subset is intentional.
-
-Passing `scripts/golden.sh verify` only proves existing fixtures did not drift. It does not prove golden coverage is complete. When adding a new deterministic CLI behavior or artifact contract, explicitly decide whether the golden suite needs a new or expanded case. Add golden coverage when the behavior is user-visible command output or persisted generated artifacts that should remain stable across refactors. Prefer unit tests for narrow helper logic, branchy internals, or cases where a golden snapshot would duplicate a focused package test without proving a CLI-level contract.
-
-Decision rubric:
-
-- **No golden update:** code changed but the captured external behavior is intentionally identical. Run `scripts/golden.sh verify`; it should pass unchanged.
-- **Update an existing fixture:** the behavior already covered by a golden case intentionally changed. Run `scripts/golden.sh update`, then inspect and explain the exact expected diff.
-- **Add or expand a fixture:** the change creates a new deterministic command output or persisted artifact contract that existing cases do not exercise. Add the smallest fixture that proves that contract.
-
-To add a case, create `testdata/golden/cases/<case-name>/`, add expected outputs under `testdata/golden/expected/<case-name>/`, and list behaviorally important generated files in `artifacts.txt` when the command creates artifacts. Prefer a small, high-signal artifact subset over snapshotting huge trees.
-
-Keep golden artifacts contract-shaped. Snapshot the specific files or output fields that demonstrate the stable behavior. Do not include broad reports, whole generated trees, or incidental diagnostics just because the harness can capture them; unrelated fields make refactors noisy and weaken the signal.
-
-Maintain `testdata/golden/fixtures/golden-api.yaml` as the purpose-built generated-CLI fixture for the Printing Press. When the machine gains deterministic generation capabilities that should survive major refactors — for example new auth shapes, pagination contracts, MCP surfaces, manifest fields, or endpoint naming rules — extend this fixture and add the smallest useful artifact comparison that proves the capability. Do not mutate this fixture for one printed CLI's edge case unless it represents a general machine behavior.
-
-If `verify` fails, inspect `.gotmp/golden/actual/<case-name>/` and the generated `.diff` files. Decide whether the change is a regression or an intentional behavior change. If it is a regression, fix code. If it is intentional, run `scripts/golden.sh update`, review fixture diffs, and mention the golden update in the final summary.
-
-Golden verification does not replace `go test ./...`, `go vet ./...`, `golangci-lint run ./...`, or `go build -o ./printing-press ./cmd/printing-press`. It is an additional check for behavior-sensitive changes and runs in CI as a separate `Golden` workflow, not as part of `go test ./...`.
+Never update goldens just to make a failing check pass. Run `scripts/golden.sh update` only when the behavior change is intentional, then inspect the diff and explain it in your final response. See [`docs/GOLDEN.md`](docs/GOLDEN.md) for the decision rubric, fixture conventions, and failure handling.
+When adding a new deterministic CLI behavior or generated artifact contract, explicitly decide whether the golden suite needs a new or expanded fixture. A passing `scripts/golden.sh verify` on existing cases does not prove coverage for new auth, pagination, MCP, manifest, naming, or similar deterministic generation behavior.
## Project Structure
-
- `cmd/printing-press/` - CLI entry point
- `internal/spec/` - Internal YAML spec parser
- `internal/openapi/` - OpenAPI 3.0+ parser
@@ -134,204 +72,97 @@ Golden verification does not replace `go test ./...`, `go vet ./...`, `golangci-
- `catalog/` - API catalog entries (YAML) + Go embed package (`catalog.FS`). Adding a YAML file here requires rebuilding the binary
- `skills/` - Claude Code skill definitions
- `testdata/` - Test fixtures (internal + OpenAPI specs)
-- `docs/PIPELINE.md` - Portable contract for the 9-phase generation pipeline (preflight through ship). Phase names and ordering are authoritative in `internal/pipeline/state.go`; per-phase intent is authoritative in `internal/pipeline/seeds.go`. Update `docs/PIPELINE.md` in the same PR whenever those files change
-- `docs/SPEC-EXTENSIONS.md` - Canonical reference for Printing Press-specific OpenAPI `x-*` extensions. Update it whenever `internal/openapi/parser.go` adds or changes an `Extensions["x-*"]` lookup
+- `docs/PIPELINE.md` - Portable contract for the 9-phase generation pipeline. Update it when `internal/pipeline/state.go` or `internal/pipeline/seeds.go` changes
+- `docs/SPEC-EXTENSIONS.md` - Canonical reference for Printing Press-specific OpenAPI `x-*` extensions. Update it when `internal/openapi/parser.go` adds or changes an `Extensions["x-*"]` lookup
- `docs/SKILLS.md` - Skill authoring conventions: workflow parity, reference-file pattern, frontmatter fields
-- `docs/PATTERNS.md` - Cross-cutting design patterns (deterministic-inventory ledger, etc.)
-
-## Glossary
-
-Key terms used throughout this repo. Several have overloaded meanings — the glossary establishes canonical names to use in conversation and code comments.
-
-**Use the canonical term** (left column) in your own responses so intent stays unambiguous. If the user's phrasing is ambiguous and the distinction affects what action to take — e.g., "publish it" could mean the pipeline step or pushing to the public library repo — ask before acting.
-
-**In skills and user-facing output** (GitHub issues, retro documents, confirmation prompts), use **"the Printing Press"** as the system name — never "the machine." Skills run as a plugin without AGENTS.md loaded, so readers won't have this glossary. "The machine" is fine in AGENTS.md, code comments, and developer conversation within this repo.
-
-**Subsystem names are fine alongside the Printing Press name.** When skills produce diagnostic output (retro findings, issue tables, work units), use component names — generator, scorer, skills, binary — to tell developers *where* to fix something. "Fix the Printing Press" is useless as an action item; "fix the scorer — it penalizes cookie auth" is actionable. The Printing Press is the system; the subsystems are how you navigate within it.
-
-**Default disambiguation conventions.** Several terms below are overloaded; when body prose uses one without qualifier, default to the local form:
-
-- "library" → local library (`~/printing-press/library/<api-slug>/`). The public library is always called out explicitly: "public library" or "public library repo."
-- "publish" → in body prose, prefer "the publish step" (pipeline) or "publish to the public library" (skill workflow) when context isn't already established.
-- "manifest" → `tools-manifest.json` (the MCP tool catalog). The other manifests (`manifest.json` for plugin metadata, `.printing-press.json` for provenance) are always called by full name.
-- "catalog" → the embedded `catalog/` in this repo. The public library's category-organized catalog of finished CLIs is "public library catalog."
-
-| Canonical term | Meaning |
-|----------------|---------|
-| **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 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. |
-| **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>/`. The local library is the working copy of the latest successful run for a given API; manuscripts are immutable archives across runs — same `<api-slug>` keys, separate top-level directories. |
-| **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/`. |
-| **quality gates** | 7 mechanical static checks every printed CLI must pass: go mod tidy, go vet, go build, binary build, `--help`, version, doctor. These are build-time checks — see **verify** for runtime testing. |
-| **verify** | Runtime behavioral testing of a printed CLI — runs every command against the real API (read-only) or a mock server. Produces PASS/WARN/FAIL verdicts. Has `--fix` mode for auto-patching. Distinct from quality gates (static) and dogfood (structural). |
-| **dogfood** | Generation-time structural validation of a printed CLI against its source spec. Catches dead flags, invalid API paths, auth mismatches, and MCP surface parity drift. Subcommand: `printing-press dogfood`. Compare with **doctor** (shipped in the CLI for end-users) and **verify** (runtime behavioral). |
-| **cliutil** | The generator-owned Go package emitted into every printed CLI at `internal/cliutil/`. Houses shared helpers meant for agent-authored novel code to import: `cliutil.FanoutRun` for aggregation commands (per-source error collection, bounded concurrency, source-order output), `cliutil.CleanText` for HTML/JSON-LD text normalization, `cliutil.IsVerifyEnv()` for the side-effect short-circuit (see **side-effect command convention**). **Generator-reserved namespace** — agents authoring novel code in Phase 3 must not put their code in `internal/cliutil/` or name their own helpers that collide with cliutil's exports. |
-| **cobratree** | The generator-owned Go package emitted into every printed CLI at `internal/mcp/cobratree/`. The MCP server uses it to walk the printed CLI's Cobra command tree at startup and register shell-out tools for user-facing commands that are not already typed endpoint tools. Classification rules and the framework skip list live in `cobratree/classify.go.tmpl`; see **Agent-Native Surface** for when to add to the framework set vs. annotate `mcp:hidden`. **Generator-reserved namespace** — do not hand-author code here. |
-| **side-effect command convention** | Two-part rule for hand-written novel commands that perform visible actions (open browser tabs, send notifications, dial out to OS handlers). (1) Print by default; require explicit opt-in (`--launch`, `--send`, `--play`) to actually act. (2) Short-circuit when `cliutil.IsVerifyEnv()` is true — the verifier sets `PRINTING_PRESS_VERIFY=1` in every mock-mode subprocess, and the env-var check is the floor that catches any command the verifier's heuristic side-effect classifier misses. Documented in `skills/printing-press/SKILL.md` Phase 3 (principle 9). |
-| **canonicalargs** | Tiny generator subpackage at `internal/canonicalargs/` exporting `Lookup(name) (string, bool)` for cross-domain positional placeholder names (`since`, `until`, `tag`, `vertical`). Both verify mock-mode dispatch and the SKILL template consult this registry as one step in the lookup chain `spec.Param.Default → canonicalargs → legacy syntheticArgValue switch → "mock-value"`. **Domain-specific names belong in the spec author's `Param.Default`, not here** — anti-pattern: "Never change the machine for one CLI's edge case." |
-| **mcp-sync** | Subcommand on the printing-press binary (`printing-press mcp-sync <cli-dir>`) that migrates generated MCP surfaces from the old static novel-feature list to the runtime Cobra-tree mirror. It rewrites generated MCP files, adds the root command export when possible, regenerates `tools-manifest.json`, and refuses hand-edited `internal/mcp/tools.go` unless `--force` is passed. |
-| **regen-merge** | Subcommand on the printing-press binary (`printing-press regen-merge <cli-dir> --fresh <fresh-dir>`) that classifies each Go file in a published library CLI by AST decl-set comparison against a fresh-generated tree, applies safe templated overwrites, restores lost AddCommand registrations in `root.go` and resource-parents, and merges `go.mod` while preserving the published monorepo module path. Targets the per-CLI sweep workflow (~5-15 min/CLI vs ~30-90 min manual). Six classification verdicts; stage-and-swap-with-recovery transactional apply; macOS+Linux only. Lives in `internal/pipeline/regenmerge/`. |
-| **shipcheck** | The verification block that gates publishing: dogfood + verify + workflow-verify + verify-skill + scorecard, run together. Dogfood includes `mcp_surface_parity`, so stale static MCP surfaces block shipping. All legs must pass before a printed CLI ships. |
-| **scorecard** / **scoring** | Two-tier quality assessment with a 50/50 weighted composite. Tier 1: infrastructure (16 string-matching dimensions, raw max 160, normalized to 0-50). Tier 2: domain correctness (7 semantic dimensions, raw max 60 when live verify ran, normalized to 0-50). Total /100 with letter grades. Source of truth: `internal/pipeline/scorecard.go` (tier1Max / tier2Max). Subcommand: `printing-press scorecard`. |
-| **machine-owned freshness** | Opt-in freshness contract for store-backed printed CLIs using `cache.enabled`. Covered command paths map to syncable resources; in `--data-source auto` they may run a bounded pre-read refresh before serving local data. `--data-source local` never refreshes, `--data-source live` must not mutate the local store, and env opt-out only disables the freshness hook. This is current-cache freshness, not a guarantee of full historical backfill or API-specific enrichment. |
-| **doctor** | Self-diagnostic command shipped inside every printed CLI for end-users to run. Checks environment, auth config, and connectivity at the user's runtime. Unlike dogfood (which validates at generation time), doctor runs post-install. |
-| **auth doctor** | Subcommand on the printing-press binary (`printing-press auth doctor`). Scans every installed printed CLI's `tools-manifest.json` under `~/printing-press/library/<api>/` and reports env-var status (ok / suspicious / not_set / no_auth / unknown) with redacted fingerprints. Diagnostic only — never gates, never probes the network. Lives in `internal/authdoctor/`. |
-| **mcp-audit** | Subcommand on the printing-press binary (`printing-press mcp-audit`). Walks every library CLI and reports transport, tool-design, and per-CLI recommendations for the `mcp:` spec surface introduced in the U1-U3 machine work (remote transport, intent tools, code-orchestration). Diagnostic only — exit 0 regardless of findings. Supports `--json` for machine-readable output. |
-| **mcp spec surface** | Opt-in fields on the spec's `mcp:` block introduced April 2026 to reach production agent-hosts: `transport: [stdio, http]` (remote-capable via streamable HTTP), `intents:` (multi-step composed MCP tools), `orchestration: code` (Cloudflare-style thin `<api>_search` + `<api>_execute` surface for 50+ endpoint APIs), `endpoint_tools: hidden` (suppress raw per-endpoint tools). Empty `mcp:` keeps today's stdio-only endpoint-mirror emission byte-compatible. |
-| **local library** | `~/printing-press/library/<api-slug>/` — where printed CLIs land after a successful run. Directory is keyed by API slug (e.g., `notion`), not CLI name. Local directory, not a git repo. |
-| **public library repo** | The GitHub repo [`mvanhorn/printing-press-library`](https://github.com/mvanhorn/printing-press-library) — public catalog of finished CLIs organized by category. `/printing-press-publish` pushes here. |
-| **publish (pipeline)** | The pipeline step that moves a working CLI into the local library and writes the `.printing-press.json` provenance manifest. |
-| **publish (to public library repo)** | The skill-driven workflow (`/printing-press-publish`) that packages a local library CLI and creates a PR in the public library repo. |
-| **provenance** / **`.printing-press.json`** | Manifest written to each published CLI's root. Contains generation metadata: spec URL, checksum, run ID, printing-press version, timestamp. `api_name` is the canonical API identity; `cli_name` is the executable name. Makes the directory self-describing. Distinct from `manifest.json` (plugin metadata) and `tools-manifest.json` (MCP tool catalog). |
-| **`manifest.json`** | Claude plugin manifest at the printed CLI root. Carries `display_name`, `description`, `homepage`, version, and other plugin-host fields. Read by Claude Desktop and other MCP-aware hosts when installing the CLI as a plugin. Distinct from `tools-manifest.json` (the MCP tool catalog) and `.printing-press.json` (provenance). |
-| **`tools-manifest.json`** | MCP tool catalog at the printed CLI root. For each tool, carries name, description, parameters, and auth metadata. The MCP server reads it at runtime to register typed tools with full schemas; the audit and scorecard pipelines consume it. "The manifest" without qualifier means this file. Distinct from `manifest.json` (plugin metadata) and `.printing-press.json` (provenance). |
-| **catalog** (embedded) | Embedded YAML entries in `catalog/` describing available APIs (name, spec URL, category, tier). Baked into the binary at build time via `catalog.FS`. Distinct from the **public library catalog**, which is the category-organized index of finished CLIs in the public library repo. |
-| **tier** | Catalog classification: `official` (vendor-maintained spec) or `community` (unofficial/reverse-engineered). Affects risk expectations. |
-| **runstate** | Mutable per-workspace state at `~/printing-press/.runstate/<scope>/`. Tracks current run and sync cursors. Distinct from manuscripts, which are immutable archives. |
+- `docs/PATTERNS.md` - Cross-cutting design patterns
+- `docs/GOLDEN.md` - Golden harness decision rubric and fixture conventions
+- `docs/GLOSSARY.md` - Canonical terms and the full disambiguation table
+- `docs/RELEASE.md` - release-please / goreleaser flow
+- `docs/CATALOG.md` - Catalog validation rationale and wrapper-only entry shape
+- `docs/ARTIFACTS.md` - Local library, manuscripts, and public-library flow
+- `docs/DOCS.md` - Doc-authoring rules, including pointer-rot prevention
+
+## Naming and Disambiguation
+Use canonical terms in your responses so intent stays unambiguous. In skills and user-facing output (GitHub issues, retro documents, confirmation prompts), use **"the Printing Press"** as the system name, never "the machine." Subsystem names (generator, scorer, skills, binary) are fine alongside it. When user phrasing is ambiguous and the distinction affects what action to take, ask before acting.
+- "library" -> local library (`~/printing-press/library/<api-slug>/`) unless the public library is called out explicitly
+- "publish" -> the publish step (pipeline) unless the public-library workflow is called out explicitly
+- "manifest" -> `tools-manifest.json` unless another manifest is named explicitly
+- "catalog" -> embedded `catalog/` unless "public library catalog" is stated
+See [`docs/GLOSSARY.md`](docs/GLOSSARY.md) for the full term table and disambiguation cases.
## Commit Style
-
-**Format:** `type(scope): description` — scope is always required.
-
-**Scopes** (these appear in changelogs and release notes):
-
-| Scope | Covers | Example |
-|-------|--------|---------|
-| `cli` | Go binary, commands, flags, embedded catalog, docs | `feat(cli): add catalog subcommands` |
-| `skills` | Skill definitions (SKILL.md), references, setup contract | `fix(skills): remove repo checkout requirement` |
-| `ci` | Workflows, release config, goreleaser | `feat(ci): add release-please` |
-| `main` | release-please generated release PRs targeting `main` | `chore(main): release 2.2.0` |
-
-`main` is reserved for release-please PR titles. Human-authored changes should use `cli`, `skills`, or `ci`.
-
-Every commit and PR title must include one of these scopes. The `PR Title` action enforces this.
-
-**Breaking changes** use `!` after the scope: `feat(cli)!: rename catalog command to registry`. This triggers a major version bump.
-
-**Version bump rules** (release-please reads these from commit prefixes):
-- `fix(scope):` → patch (0.4.0 → 0.4.1)
-- `feat(scope):` → minor (0.4.0 → 0.5.0)
-- `feat(scope)!:` or `BREAKING CHANGE:` footer → major (0.4.0 → 1.0.0)
-- `refactor(scope):` → included in the next release PR but doesn't trigger a bump alone
-- `docs:`, `chore:`, `test:` → don't trigger a bump alone and stay out of release notes by default
-
-**PR titles must follow the same format.** GitHub's "Squash and merge" uses the PR title as the squash commit message, so release-please reads PR titles on main. The `PR Title` GitHub Action (`.github/workflows/pr-title.yml`) enforces this — PRs with invalid titles cannot merge.
-
-## Versioning & Release
-
-Releases are fully automated by release-please + goreleaser; no manual steps. The flow:
-
-1. Merge PRs to main with conventional-commit titles.
-2. release-please opens (and updates) a release PR with the accumulated changelog.
-3. When ready to ship, merge the release PR. release-please bumps all version files, creates a git tag, opens a GitHub release; goreleaser builds and attaches cross-platform binaries.
-
-**Never manually edit version numbers.** Three files carry the version and release-please keeps them in sync:
-- `.claude-plugin/plugin.json` → `version`
-- `.claude-plugin/marketplace.json` → `plugins[0].version`
-- `internal/version/version.go` → `var Version` (annotated `x-release-please-version`)
-
-`TestVersionConsistencyAcrossFiles` in `internal/cli/release_test.go` fails if they drift.
+Format: `type(scope): description`. Scope is always required.
+- `cli` covers the Go binary, commands, flags, embedded catalog, and docs.
+- `skills` covers skill definitions (`SKILL.md`), references, and setup contract.
+- `ci` covers workflows, release config, and goreleaser.
+- `main` is reserved for release-please generated release PRs targeting `main`.
+Breaking changes use `!` after the scope: `feat(cli)!: rename catalog command to registry`.
+Version bump rules: `fix(scope):` -> patch; `feat(scope):` -> minor; `feat(scope)!:` or `BREAKING CHANGE:` -> major; `refactor(scope):` is included in the next release PR but does not trigger a bump alone; `docs:`, `chore:`, and `test:` do not trigger a bump alone and stay out of release notes by default.
+Every commit and PR title must include one of the allowed scopes. GitHub squash-and-merge uses the PR title as the squash commit message, and `.github/workflows/pr-title.yml` enforces the format.
+
+## Versioning
+Releases are automated by release-please. Never manually edit version numbers.
+- The plugin version lives in exactly two places and must stay in sync: `.claude-plugin/plugin.json` -> `version`, and `internal/version/version.go` -> `var Version` (annotated `x-release-please-version`; goreleaser injects via ldflags).
+- `TestVersionConsistencyAcrossFiles` in [`internal/cli/release_test.go`](internal/cli/release_test.go#L57) fails if those two versions drift.
+- Do not add a `version` field to `.claude-plugin/marketplace.json` plugin entries. `TestMarketplaceJSONHasNoPluginVersion` in [`internal/cli/release_test.go`](internal/cli/release_test.go#L81) fails if a reviewer re-adds one.
+See [`docs/RELEASE.md`](docs/RELEASE.md) for the merge-the-release-PR flow.
## Adding Catalog Entries
-
-Catalog entries in `catalog/` must pass `internal/catalog` validation:
-- Required fields: name, display_name, description, category, spec_url, spec_format, tier
-- spec_url must use HTTPS
-- category must be: ai, auth, cloud, commerce, developer-tools, devices, food-and-dining, marketing, media-and-entertainment, monitoring, payments, productivity, project-management, sales-and-crm, social-and-messaging, travel, or other
-- tier must be: official or community
+When adding or editing `catalog/*.yaml`, the entry must pass `internal/catalog` validation.
+- Required fields: `name`, `display_name`, `description`, `category`, and `tier`, plus `spec_url` and `spec_format` unless the entry is wrapper-only (`wrapper_libraries` is set and `spec_url` is omitted).
+- `spec_url`, when present, must use HTTPS.
+- `category` must be one of `ai`, `auth`, `cloud`, `commerce`, `developer-tools`, `devices`, `food-and-dining`, `marketing`, `media-and-entertainment`, `monitoring`, `payments`, `productivity`, `project-management`, `sales-and-crm`, `social-and-messaging`, `travel`, or `other`. The validator also accepts `example` as a test-only catch-all; do not use it for real catalog entries.
+- `tier` must be `official` or `community`.
+- Rebuild the binary after editing; `catalog.FS` is a Go embed.
+See [`docs/CATALOG.md`](docs/CATALOG.md) for validation rationale and the wrapper-only entry shape.
## Testing
-
-**When you change code, check for a `_test.go` file in the same package.** If one exists, read it — your change likely requires a test update. If tests fail after your change, investigate whether it's a bug in your code or a stale test — don't just delete.
-
+When you change code, check for a `_test.go` file in the same package. If one exists, read it; your change likely requires a test update. If tests fail after your change, investigate whether it is a bug in your code or a stale test; do not just delete the test.
Add tests for new non-trivial logic. Match the package's existing style (typically table-driven with `testify/assert`). Skip tests for CLI glue, trivial wrappers, and code only meaningfully tested via integration (`FULL_RUN=1`).
-
Run `go test ./...` before considering your work done.
## Quality Gates
+Generated CLIs must pass 7 gates: `go mod tidy`, `go vet`, `go build`, binary build, `--help`, `version`, and `doctor`.
-Generated CLIs must pass 7 gates: go mod tidy, go vet, go build, binary build, --help, version, doctor.
-
-## Local Artifacts (`~/printing-press/`)
-
-Generated artifacts live under the user's home directory, not in this repo.
-
-- `library/<api-slug>/` — Local library: printed CLIs the generator has produced (e.g., `notion`). Directory is keyed by API slug, not CLI name. The binary inside is still `<api-slug>-pp-cli`. This is the working copy; the public library is the published-and-curated counterpart (see "Public Library" below).
-- `manuscripts/<api-slug>/` — Archived research and verification proofs, keyed by API slug (e.g., `notion`), not CLI name. One API can have multiple runs.
-- `.runstate/<scope>/` — Mutable per-workspace state (current run, sync cursors). Scoped by repo basename + hash.
-
-The API slug is derived by the generator from the spec title (`cleanSpecName`), not manually chosen. The CLI binary name is `<api-slug>-pp-cli`. Never hardcode an API slug when the generator can derive it — names with periods (cal.com, dub.co) normalize differently than you'd guess.
-
-The `-pp-` infix exists to avoid colliding with official CLIs. The binary `notion-pp-cli` can coexist with whatever `notion-cli` Notion ships themselves. The library directory is just `notion/` — the `-pp-cli` suffix only appears on binary names, not directory names.
-
-## Public Library
-
-The public library is the GitHub repo [`mvanhorn/printing-press-library`](https://github.com/mvanhorn/printing-press-library) — a curated, category-organized catalog of finished printed CLIs. Users install printed CLIs from here; this is where a CLI goes when it's ready to ship.
-
-**Local → public flow.** A successfully generated printed CLI lives in the local library. The `/printing-press-publish` skill packages a local CLI and opens a PR against the public library repo. Merging that PR is what moves the CLI from "works on this machine" to "users can install it."
-
-**Local-vs-public divergence.** The local library and public library can drift in two ways:
-
-- **Expected divergence.** Some files are intentionally rewritten by the publish step — most notably `go.mod`'s module path. The polish skill's divergence check exempts these.
-- **Unexpected divergence.** Local edits since the last publish — polish in progress, manual fixes, mcp-sync regen — that haven't been pushed. The polish skill's divergence check surfaces these so you can decide to either republish or discard the local changes.
-
-Treat the public library as the durable artifact and the local library as the working copy. When users hit a bug, they're hitting the public library's version, not whatever's currently in `~/printing-press/library/`.
+## Local Artifacts
+Generated artifacts live under `~/printing-press/`, not in this repo: `library/<api-slug>/`, `manuscripts/<api-slug>/`, and `.runstate/<scope>/`. The API slug is derived by the generator from the spec title (`cleanSpecName`), and the binary name is `<api-slug>-pp-cli`. Never hardcode an API slug when the generator can derive it. See [`docs/ARTIFACTS.md`](docs/ARTIFACTS.md) for local-vs-public flow and divergence rules.
## Internal Skills
-
-`.claude/skills/` contains internal skills for developing the printing press itself (e.g., `printing-press-retro`). These load automatically when Claude Code is started from inside this repo — no setup needed.
-
-If you're running Claude Code from a different directory and need these skills available, install them globally:
-
+`.claude/skills/` contains internal skills for developing the Printing Press itself (for example `printing-press-retro`). These load automatically when Claude Code is started from inside this repo.
+If you are running Claude Code from a different directory and need these skills available, install them globally:
```bash
.claude/scripts/install-internal-skills.sh
```
-
This copies the internal skills to `~/.claude/skills/`.
## Skill Authoring
-
-When a machine change alters what an agent should do or what a command guarantees, update the relevant `SKILL.md` in the same change — don't leave the skill as a stale manual workaround for behavior the machine now owns.
-
-Detail in [`docs/SKILLS.md`](docs/SKILLS.md): workflow parity (when machine changes require skill changes), the reference-file pattern (extracting conditional content from SKILL.md), and the `context: fork` / `user-invocable` frontmatter fields.
+When a machine change alters what an agent should do or what a command guarantees, update the relevant `SKILL.md` in the same change; do not leave the skill as a stale manual workaround for behavior the machine now owns.
+Detail in [`docs/SKILLS.md`](docs/SKILLS.md): workflow parity, the reference-file pattern, and the `context: fork` / `user-invocable` frontmatter fields.
## Code & Comment Hygiene
-
### Write-time defaults
-
-- **No speculative future-proofing in comments.** "Structured to absorb additional dimensions if future X needs them" — write the future struct when the future arrives. Today's reader can't act on a comment about hypothetical needs.
-- **No dates, incidents, or ticket numbers in code comments.** Belongs in the PR description and commit message, not the code. Comments stay forever; incidents fade.
-- **Code comments must be self-contained.** Don't make them load-bearing on in-repo skill prose, plans, or reference files that could be reorganized. RFCs, vendor API docs, and language specs are durable; in-repo prose is not. If you find yourself wanting to link, keep enough context inline that the code reads correctly when the link breaks.
-- **Don't restate the field or function name in its comment.** `MCPDescriptionQuality int` does not need `// the score for MCP description quality`. Document WHY (hidden constraints, subtle invariants), not WHAT (the name already says it).
-- **Categorical strings → typed const at introduction.** When adding an event kind, finding type, status name, or any string that names a category, declare the const in the same commit even with one call site. The compiler catches typos at every future site, and the const adds two lines today.
-- **Single-case switch with default fallthrough → `||`.** If every branch returns the same thing, `switch x { case A, B: return true } return false` is just `return x == A || x == B`. Switch shape implies cases will diverge; if they won't, write the `||`.
-- **Parse command inputs once at the entry point.** In a `RunE`, read files / manifests / configs at the entry and pass parsed results into helpers. Don't re-read "for clarity" — the cost compounds when helpers cross-call.
-- **UTF-8 safe string truncation.** `s[:n] + "…"` cuts mid-rune on multibyte input. Use rune slicing or an existing truncate helper from the same package.
+- No speculative future-proofing in comments.
+- No dates, incidents, or ticket numbers in code comments.
+- Code comments must be self-contained; do not make them load-bearing on in-repo skills, plans, or reference prose.
+- Do not restate the field or function name in its comment; document why, not what.
+- Categorical strings -> typed const at introduction.
+- Single-case switch with default fallthrough -> `||`.
+- Parse command inputs once at the entry point.
+- Use UTF-8-safe string truncation.
### Pre-commit: scan the diff
-
- Near-identical loops or functions that should share a helper
-- A compound predicate (e.g., `f.Status != accepted || (requiresX(f.Kind) && missingX(f))`) inlined at 3+ sites that should be a named function
-- Parallel `hasX() bool` / `xCount() int` that drifted apart — derive one from the other
-- The same string literal repeated across sites where the categorical-const rule above would have applied — the const is cheap to add retroactively if missed at write-time
+- A compound predicate inlined at 3+ sites that should be a named function
+- Parallel `hasX() bool` / `xCount() int` that drifted apart
+- The same string literal repeated across sites where the categorical-const rule should have applied
## Editing AGENTS.md
+The "Code & Comment Hygiene" rules apply here too. Keep inline `AGENTS.md` rules command-shaped: trigger, required action or prohibition, concrete values, then a pointer to any longer doc.
-The "Code & Comment Hygiene" rules apply to this file too. Specifically:
+**Pointer-rot rule.** When editing a doc under `docs/` that `AGENTS.md` points to, update the inline trigger sentence here in the same PR if applicability changes — a new fire condition, a removed fire condition, or a changed prohibition, enum, file path, test name, or required value. The inline rule is what the agent sees on every turn; the extracted doc is only loaded if the agent follows the pointer.
-- **No dates, incidents, or ticket numbers in rules.** Justification belongs in the PR introducing the rule, not embedded in it.
-- **Don't defend the doc's structure inside the doc.** "We split this honestly because…" doesn't help future readers — write the rule, trust them.
-- **Make rules applicable at the moment they fire.** Write-time rules in a write-time section, diff-review rules in a review section. A rule the agent can't apply at the relevant moment is worse than no rule.
-- **Examples should be generic or anti-pattern-shaped, not lifted from the specific incident that prompted the rule.**
+See [`docs/DOCS.md`](docs/DOCS.md) for the full doc-authoring rules.
## Patterns
-
-Cross-cutting design patterns are documented in [`docs/PATTERNS.md`](docs/PATTERNS.md). Notably **Deterministic Inventory + Agent-Marked Ledger** — the shape used by `printing-press tools-audit` for workflows that combine mechanical detection with per-item agent judgment, with four enforcement primitives (pre-decision fields, duplicate-rationale rejection, numeric end-state gate, resume protocol) for cases where bulk-accept is a realistic failure mode.
+Cross-cutting design patterns are documented in [`docs/PATTERNS.md`](docs/PATTERNS.md). Notably **Deterministic Inventory + Agent-Marked Ledger** — the shape used by `printing-press tools-audit` for workflows that combine mechanical detection with per-item agent judgment.
diff --git a/docs/ARTIFACTS.md b/docs/ARTIFACTS.md
new file mode 100644
index 00000000..27ee56a7
--- /dev/null
+++ b/docs/ARTIFACTS.md
@@ -0,0 +1,26 @@
+# Local Artifacts and Public Library
+
+Generated artifacts live under the user's home directory, not in this repo.
+
+## Local artifacts
+
+- `~/printing-press/library/<api-slug>/` — local library: printed CLIs the generator has produced. Directory names are keyed by API slug, not CLI name. The binary inside is still `<api-slug>-pp-cli`.
+- `~/printing-press/manuscripts/<api-slug>/` — archived research and verification proofs, keyed by API slug. One API can have multiple runs.
+- `~/printing-press/.runstate/<scope>/` — mutable per-workspace state such as current run and sync cursors.
+
+The API slug is derived by the generator from the spec title (`cleanSpecName`), not manually chosen. The CLI binary name is `<api-slug>-pp-cli`. Never hardcode an API slug when the generator can derive it; names with periods normalize differently than you'd guess.
+
+The `-pp-` infix exists to avoid colliding with official CLIs. The binary `notion-pp-cli` can coexist with whatever `notion-cli` the vendor ships. The library directory is just `notion/`; the `-pp-cli` suffix appears on binary names, not directory names.
+
+## Public library
+
+The public library is the GitHub repo [`mvanhorn/printing-press-library`](https://github.com/mvanhorn/printing-press-library) — a curated, category-organized catalog of finished printed CLIs. Users install printed CLIs from there.
+
+Local-to-public flow: a successfully generated printed CLI lives in the local library first. The `/printing-press-publish` skill packages a local CLI and opens a PR against the public library repo. Merging that PR is what moves the CLI from "works on this machine" to "users can install it."
+
+The local library and public library can diverge in two ways:
+
+- **Expected divergence.** Some files are intentionally rewritten by the publish step, most notably `go.mod`'s module path. The polish skill's divergence check exempts these.
+- **Unexpected divergence.** Local edits since the last publish, such as polish in progress, manual fixes, or `mcp-sync` regen, that have not been pushed. The polish skill's divergence check surfaces these so you can decide whether to republish or discard the local changes.
+
+Treat the public library as the durable artifact and the local library as the working copy. When users hit a bug, they are hitting the public library's version, not whatever is currently in `~/printing-press/library/`.
diff --git a/docs/CATALOG.md b/docs/CATALOG.md
new file mode 100644
index 00000000..de32ebc9
--- /dev/null
+++ b/docs/CATALOG.md
@@ -0,0 +1,20 @@
+# Catalog Entry Validation
+
+Catalog entries in `catalog/` are validated by [`internal/catalog/catalog.go`](../internal/catalog/catalog.go). Keep the inline `AGENTS.md` rule in sync with that validator; when the validator's applicability or allowed values change, update the inline trigger sentence in the same PR.
+
+## Why the inline rule is strict
+
+The catalog is embedded into the printing-press binary via `catalog.FS`, so a bad entry is not a local typo; it becomes part of every rebuilt binary. The inline `AGENTS.md` rule keeps the write-time fence close to the edit, while this doc carries the longer rationale and the wrapper-only shape.
+
+`category` and `tier` are deliberately finite enums because they drive catalog browsing, risk expectations, and downstream copy. `other` is the public catch-all. `example` is accepted only as a test-only bucket for fixtures such as `catalog/petstore.yaml`; do not use it for real catalog entries.
+
+## Wrapper-only entries
+
+Wrapper-only entries are the carve-out where `spec_url` and `spec_format` stop being required. The validator treats an entry as wrapper-only when `wrapper_libraries` is non-empty and `spec_url` is empty. In that shape:
+
+- `name`, `display_name`, `description`, `category`, and `tier` are still required.
+- `wrapper_libraries[*].name`, `.url`, `.language`, and `.integration_mode` are required.
+- Wrapper library URLs must use HTTPS.
+- `spec_format` is optional, but if present it must still be one of the allowed formats.
+
+Use the wrapper-only carve-out only when the API is genuinely reached through wrapper libraries rather than a direct spec. If the validator or enum values change, update both this doc and the inline `AGENTS.md` rule together.
diff --git a/docs/DOCS.md b/docs/DOCS.md
new file mode 100644
index 00000000..f8c6259a
--- /dev/null
+++ b/docs/DOCS.md
@@ -0,0 +1,27 @@
+# Doc Authoring Rules
+
+These rules cover `AGENTS.md` and the extracted developer docs it points to.
+
+## Editing `AGENTS.md`
+
+The "Code & Comment Hygiene" rules apply to docs too:
+
+- No dates, incidents, or ticket numbers in rules. Justification belongs in the PR introducing the rule, not embedded in it.
+- Do not defend the doc's structure inside the doc. Write the rule and the pointer; skip narration about why the file is arranged that way.
+- Make rules applicable at the moment they fire. Keep the inline `AGENTS.md` rule command-shaped: trigger, required action or prohibition, concrete values, then the pointer to the longer doc.
+- Examples should be generic or anti-pattern-shaped, not lifted from the specific incident that prompted the rule.
+
+## Pointer-rot rule
+
+When an extracted doc's applicability changes, update the inline trigger sentence in `AGENTS.md` in the same PR. Applicability changes include a new fire condition, a removed fire condition, or a changed prohibition, enum, file path, test name, or required value.
+
+## Developer-doc surface
+
+The extracted developer docs are:
+
+- `docs/GOLDEN.md` — golden harness rubric and fixture conventions
+- `docs/GLOSSARY.md` — full canonical-term table
+- `docs/RELEASE.md` — release-please / goreleaser flow
+- `docs/CATALOG.md` — catalog validator rationale and wrapper-only shape
+- `docs/ARTIFACTS.md` — local library, manuscripts, and public-library flow
+- `docs/DOCS.md` — this doc-authoring guidance
diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md
new file mode 100644
index 00000000..802f5973
--- /dev/null
+++ b/docs/GLOSSARY.md
@@ -0,0 +1,57 @@
+# Glossary
+
+Key terms used throughout this repo. Several have overloaded meanings — the glossary establishes canonical names to use in conversation and code comments.
+
+Use the canonical term (left column) in your own responses so intent stays unambiguous. If the user's phrasing is ambiguous and the distinction affects what action to take, ask before acting.
+
+In skills and user-facing output (GitHub issues, retro documents, confirmation prompts), use **"the Printing Press"** as the system name — never "the machine." Skills run as a plugin without `AGENTS.md` loaded, so readers will not have the inline glossary stub. "The machine" is fine in `AGENTS.md`, code comments, and developer conversation within this repo.
+
+Subsystem names are fine alongside the Printing Press name. When skills produce diagnostic output (retro findings, issue tables, work units), use component names — generator, scorer, skills, binary — to tell developers where to fix something. "Fix the Printing Press" is useless as an action item; "fix the scorer — it penalizes cookie auth" is actionable.
+
+Default disambiguation conventions:
+
+- "library" → local library (`~/printing-press/library/<api-slug>/`). The public library is always called out explicitly: "public library" or "public library repo."
+- "publish" → prefer "the publish step" (pipeline) or "publish to the public library" (skill workflow) when context is not already established.
+- "manifest" → `tools-manifest.json` (the MCP tool catalog). The other manifests (`manifest.json` for plugin metadata, `.printing-press.json` for provenance) are always called by full name.
+- "catalog" → the embedded `catalog/` in this repo. The public library's category-organized catalog of finished CLIs is "public library catalog."
+
+| Canonical term | Meaning |
+|----------------|---------|
+| **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 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. |
+| **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>/`. The local library is the working copy of the latest successful run for a given API; manuscripts are immutable archives across runs — same `<api-slug>` keys, separate top-level directories. |
+| **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/`. |
+| **quality gates** | 7 mechanical static checks every printed CLI must pass: go mod tidy, go vet, go build, binary build, `--help`, version, doctor. These are build-time checks — see **verify** for runtime testing. |
+| **verify** | Runtime behavioral testing of a printed CLI — runs every command against the real API (read-only) or a mock server. Produces PASS/WARN/FAIL verdicts. Has `--fix` mode for auto-patching. Distinct from quality gates (static) and dogfood (structural). |
+| **dogfood** | Generation-time structural validation of a printed CLI against its source spec. Catches dead flags, invalid API paths, auth mismatches, and MCP surface parity drift. Subcommand: `printing-press dogfood`. Compare with **doctor** (shipped in the CLI for end-users) and **verify** (runtime behavioral). |
+| **cliutil** | The generator-owned Go package emitted into every printed CLI at `internal/cliutil/`. Houses shared helpers meant for agent-authored novel code to import: `cliutil.FanoutRun` for aggregation commands (per-source error collection, bounded concurrency, source-order output), `cliutil.CleanText` for HTML/JSON-LD text normalization, `cliutil.IsVerifyEnv()` for the side-effect short-circuit (see **side-effect command convention**). **Generator-reserved namespace** — agents authoring novel code in Phase 3 must not put their code in `internal/cliutil/` or name their own helpers that collide with cliutil's exports. |
+| **cobratree** | The generator-owned Go package emitted into every printed CLI at `internal/mcp/cobratree/`. The MCP server uses it to walk the printed CLI's Cobra command tree at startup and register shell-out tools for user-facing commands that are not already typed endpoint tools. Classification rules and the framework skip list live in `cobratree/classify.go.tmpl`; see **Agent-Native Surface** for when to add to the framework set vs. annotate `mcp:hidden`. **Generator-reserved namespace** — do not hand-author code here. |
+| **side-effect command convention** | Two-part rule for hand-written novel commands that perform visible actions (open browser tabs, send notifications, dial out to OS handlers). (1) Print by default; require explicit opt-in (`--launch`, `--send`, `--play`) to actually act. (2) Short-circuit when `cliutil.IsVerifyEnv()` is true — the verifier sets `PRINTING_PRESS_VERIFY=1` in every mock-mode subprocess, and the env-var check is the floor that catches any command the verifier's heuristic side-effect classifier misses. Documented in `skills/printing-press/SKILL.md` Phase 3 (principle 9). |
+| **canonicalargs** | Tiny generator subpackage at `internal/canonicalargs/` exporting `Lookup(name) (string, bool)` for cross-domain positional placeholder names (`since`, `until`, `tag`, `vertical`). Both verify mock-mode dispatch and the SKILL template consult this registry as one step in the lookup chain `spec.Param.Default → canonicalargs → legacy syntheticArgValue switch → "mock-value"`. **Domain-specific names belong in the spec author's `Param.Default`, not here** — anti-pattern: "Never change the machine for one CLI's edge case." |
+| **mcp-sync** | Subcommand on the printing-press binary (`printing-press mcp-sync <cli-dir>`) that migrates generated MCP surfaces from the old static novel-feature list to the runtime Cobra-tree mirror. It rewrites generated MCP files, adds the root command export when possible, regenerates `tools-manifest.json`, and refuses hand-edited `internal/mcp/tools.go` unless `--force` is passed. |
+| **regen-merge** | Subcommand on the printing-press binary (`printing-press regen-merge <cli-dir> --fresh <fresh-dir>`) that classifies each Go file in a published library CLI by AST decl-set comparison against a fresh-generated tree, applies safe templated overwrites, restores lost AddCommand registrations in `root.go` and resource-parents, and merges `go.mod` while preserving the published monorepo module path. Targets the per-CLI sweep workflow (~5-15 min/CLI vs ~30-90 min manual). Six classification verdicts; stage-and-swap-with-recovery transactional apply; macOS+Linux only. Lives in `internal/pipeline/regenmerge/`. |
+| **shipcheck** | The verification block that gates publishing: dogfood + verify + workflow-verify + verify-skill + scorecard, run together. Dogfood includes `mcp_surface_parity`, so stale static MCP surfaces block shipping. All legs must pass before a printed CLI ships. |
+| **scorecard** / **scoring** | Two-tier quality assessment with a 50/50 weighted composite. Tier 1: infrastructure (16 string-matching dimensions, raw max 160, normalized to 0-50). Tier 2: domain correctness (7 semantic dimensions, raw max 60 when live verify ran, normalized to 0-50). Total /100 with letter grades. Source of truth: `internal/pipeline/scorecard.go` (`tier1Max` / `tier2Max`). Subcommand: `printing-press scorecard`. |
+| **machine-owned freshness** | Opt-in freshness contract for store-backed printed CLIs using `cache.enabled`. Covered command paths map to syncable resources; in `--data-source auto` they may run a bounded pre-read refresh before serving local data. `--data-source local` never refreshes, `--data-source live` must not mutate the local store, and env opt-out only disables the freshness hook. This is current-cache freshness, not a guarantee of full historical backfill or API-specific enrichment. |
+| **doctor** | Self-diagnostic command shipped inside every printed CLI for end-users to run. Checks environment, auth config, and connectivity at the user's runtime. Unlike dogfood (which validates at generation time), doctor runs post-install. |
+| **auth doctor** | Subcommand on the printing-press binary (`printing-press auth doctor`). Scans every installed printed CLI's `tools-manifest.json` under `~/printing-press/library/<api>/` and reports env-var status (`ok` / `suspicious` / `not_set` / `no_auth` / `unknown`) with redacted fingerprints. Diagnostic only — never gates, never probes the network. Lives in `internal/authdoctor/`. |
+| **mcp-audit** | Subcommand on the printing-press binary (`printing-press mcp-audit`). Walks every library CLI and reports transport, tool-design, and per-CLI recommendations for the `mcp:` spec surface introduced in the U1-U3 machine work (remote transport, intent tools, code-orchestration). Diagnostic only — exit 0 regardless of findings. Supports `--json` for machine-readable output. |
+| **mcp spec surface** | Opt-in fields on the spec's `mcp:` block introduced April 2026 to reach production agent-hosts: `transport: [stdio, http]` (remote-capable via streamable HTTP), `intents:` (multi-step composed MCP tools), `orchestration: code` (Cloudflare-style thin `<api>_search` + `<api>_execute` surface for 50+ endpoint APIs), `endpoint_tools: hidden` (suppress raw per-endpoint tools). Empty `mcp:` keeps today's stdio-only endpoint-mirror emission byte-compatible. |
+| **local library** | `~/printing-press/library/<api-slug>/` — where printed CLIs land after a successful run. Directory is keyed by API slug (e.g., `notion`), not CLI name. Local directory, not a git repo. |
+| **public library repo** | The GitHub repo [`mvanhorn/printing-press-library`](https://github.com/mvanhorn/printing-press-library) — public catalog of finished CLIs organized by category. `/printing-press-publish` pushes here. |
+| **publish (pipeline)** | The pipeline step that moves a working CLI into the local library and writes the `.printing-press.json` provenance manifest. |
+| **publish (to public library repo)** | The skill-driven workflow (`/printing-press-publish`) that packages a local library CLI and creates a PR in the public library repo. |
+| **provenance** / **`.printing-press.json`** | Manifest written to each published CLI's root. Contains generation metadata: spec URL, checksum, run ID, printing-press version, timestamp. `api_name` is the canonical API identity; `cli_name` is the executable name. Makes the directory self-describing. Distinct from `manifest.json` (plugin metadata) and `tools-manifest.json` (MCP tool catalog). |
+| **`manifest.json`** | Claude plugin manifest at the printed CLI root. Carries `display_name`, `description`, `homepage`, version, and other plugin-host fields. Read by Claude Desktop and other MCP-aware hosts when installing the CLI as a plugin. Distinct from `tools-manifest.json` (the MCP tool catalog) and `.printing-press.json` (provenance). |
+| **`tools-manifest.json`** | MCP tool catalog at the printed CLI root. For each tool, carries name, description, parameters, and auth metadata. The MCP server reads it at runtime to register typed tools with full schemas; the audit and scorecard pipelines consume it. "The manifest" without qualifier means this file. Distinct from `manifest.json` (plugin metadata) and `.printing-press.json` (provenance). |
+| **catalog** (embedded) | Embedded YAML entries in `catalog/` describing available APIs (name, spec URL, category, tier). Baked into the binary at build time via `catalog.FS`. Distinct from the **public library catalog**, which is the category-organized index of finished CLIs in the public library repo. |
+| **tier** | Catalog classification: `official` (vendor-maintained spec) or `community` (unofficial/reverse-engineered). Affects risk expectations. |
+| **runstate** | Mutable per-workspace state at `~/printing-press/.runstate/<scope>/`. Tracks current run and sync cursors. Distinct from manuscripts, which are immutable archives. |
diff --git a/docs/GOLDEN.md b/docs/GOLDEN.md
new file mode 100644
index 00000000..72b54d0e
--- /dev/null
+++ b/docs/GOLDEN.md
@@ -0,0 +1,31 @@
+# Golden Output Harness
+
+The golden harness is a byte-level behavior check for deterministic, offline `printing-press` commands and generated artifacts. It complements unit tests by catching user-visible output drift and printed CLI artifact drift.
+
+Use golden tests as refactor confidence rails for the Printing Press. When changing internals, templates, pipeline plumbing, or broad architecture, a passing golden suite tells you the externally observable contracts captured by the fixtures did not move. That is the main purpose: preserve stable command output and generated artifact contracts through major machine changes, not exhaustively test every branch.
+
+If a refactor changes machine code but claims behavior is identical, `scripts/golden.sh verify` should pass without fixture updates.
+
+Golden cases must be deterministic, offline, and auth-free. Do not add cases that depend on network access, user credentials or env vars, `~/printing-press`, wall-clock timestamps unless normalized, machine-specific absolute paths unless normalized, or large generated printed CLI trees unless the compared subset is intentional.
+
+Passing `scripts/golden.sh verify` only proves existing fixtures did not drift. It does not prove golden coverage is complete. When adding a new deterministic CLI behavior or artifact contract, explicitly decide whether the golden suite needs a new or expanded case. Add golden coverage when the behavior is user-visible command output or persisted generated artifacts that should remain stable across refactors. Prefer unit tests for narrow helper logic, branchy internals, or cases where a golden snapshot would duplicate a focused package test without proving a CLI-level contract.
+
+## Decision rubric
+
+- **No golden update:** code changed but the captured external behavior is intentionally identical. Run `scripts/golden.sh verify`; it should pass unchanged.
+- **Update an existing fixture:** the behavior already covered by a golden case intentionally changed. Run `scripts/golden.sh update`, then inspect and explain the exact expected diff.
+- **Add or expand a fixture:** the change creates a new deterministic command output or persisted artifact contract that existing cases do not exercise. Add the smallest fixture that proves that contract.
+
+## Fixture authoring
+
+To add a case, create `testdata/golden/cases/<case-name>/`, add expected outputs under `testdata/golden/expected/<case-name>/`, and list behaviorally important generated files in `artifacts.txt` when the command creates artifacts. Prefer a small, high-signal artifact subset over snapshotting huge trees.
+
+Keep golden artifacts contract-shaped. Snapshot the specific files or output fields that demonstrate the stable behavior. Do not include broad reports, whole generated trees, or incidental diagnostics just because the harness can capture them; unrelated fields make refactors noisy and weaken the signal.
+
+Maintain `testdata/golden/fixtures/golden-api.yaml` as the purpose-built generated-CLI fixture for the Printing Press. When the machine gains deterministic generation capabilities that should survive major refactors, extend this fixture and add the smallest useful artifact comparison that proves the capability. Do not mutate this fixture for one printed CLI's edge case unless it represents a general machine behavior.
+
+## Failure handling
+
+If `verify` fails, inspect `.gotmp/golden/actual/<case-name>/` and the generated `.diff` files. Decide whether the change is a regression or an intentional behavior change. If it is a regression, fix code. If it is intentional, run `scripts/golden.sh update`, review fixture diffs, and mention the golden update in the final summary.
+
+Golden verification does not replace `go test ./...`, `go vet ./...`, `golangci-lint run ./...`, or `go build -o ./printing-press ./cmd/printing-press`. It is an additional check for behavior-sensitive changes and runs in CI as a separate `Golden` workflow, not as part of `go test ./...`.
diff --git a/docs/RELEASE.md b/docs/RELEASE.md
new file mode 100644
index 00000000..d3342364
--- /dev/null
+++ b/docs/RELEASE.md
@@ -0,0 +1,9 @@
+# Versioning and Release
+
+Releases are fully automated by release-please + goreleaser; no manual steps. The flow:
+
+1. Merge PRs to `main` with conventional-commit titles.
+2. release-please opens and updates a release PR with the accumulated changelog.
+3. When ready to ship, merge the release PR. release-please bumps the version files, creates a git tag, opens a GitHub release, and goreleaser builds and attaches cross-platform binaries.
+
+Do not manually edit version numbers or release artifacts to bypass this flow. If release behavior changes, update the inline `AGENTS.md` versioning rule in the same PR.
← 5bc73909 feat(cli): add spec-driven tier routing (#570)
·
back to Cli Printing Press
·
fix(skills): speed up retro issue filing (#575) 708a6159 →