[object Object]

← back to Cli Printing Press

fix: resolve merge conflicts with upstream, fix RunScorecard 4th arg

fe1038e48d6876506eb67e1fdbbf05ad627d9b9e · 2026-03-28 06:24:34 -0700 · Matt Van Horn

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files touched

Diff

commit fe1038e48d6876506eb67e1fdbbf05ad627d9b9e
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date:   Sat Mar 28 06:24:34 2026 -0700

    fix: resolve merge conflicts with upstream, fix RunScorecard 4th arg
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 ONBOARDING.md                                      |   44 +-
 README.md                                          |   49 +-
 ...fix-discord-steinberger-quality-dogfood-plan.md |  223 --
 ...24-docs-linear-cli-competitive-analysis-plan.md |  195 --
 docs/plans/2026-03-25-feat-discord-cli-research.md |   68 -
 ...26-03-25-feat-discord-cli-visionary-research.md |   69 -
 ...eat-discord-cli-vs-discrawl-competitive-plan.md |  443 ----
 docs/plans/2026-03-25-feat-linear-cli-research.md  |   83 -
 ...026-03-25-feat-linear-cli-visionary-research.md |   86 -
 .../plans/2026-03-25-feat-moonbase-cli-research.md |   86 -
 ...notion-cli-90-90-steinberger-perfection-plan.md |  194 --
 docs/plans/2026-03-25-feat-notion-cli-research.md  |   86 -
 docs/plans/2026-03-25-fix-discord-cli-audit.md     |   73 -
 docs/plans/2026-03-25-fix-linear-cli-audit.md      |   46 -
 docs/plans/2026-03-25-fix-moonbase-cli-audit.md    |   72 -
 docs/plans/2026-03-25-fix-notion-cli-audit.md      |   31 -
 .../2026-03-26-feat-discord-cli-data-layer-spec.md |  344 ---
 ...-03-26-feat-discord-cli-power-user-workflows.md |   88 -
 docs/plans/2026-03-26-feat-discord-cli-research.md |  105 -
 ...26-03-26-feat-discord-cli-v2-data-layer-spec.md |  371 ---
 ...-26-feat-discord-cli-v2-power-user-workflows.md |  152 --
 .../2026-03-26-feat-discord-cli-v2-research.md     |  145 --
 ...03-26-feat-discord-cli-v2-visionary-research.md |  151 --
 ...26-03-26-feat-discord-cli-visionary-research.md |  106 -
 ...6-feat-discord-cli-vs-discrawl-analysis-plan.md |  237 --
 ...at-discord-cli-vs-discrawl-gap-analysis-plan.md |  318 ---
 .../2026-03-26-feat-linear-cli-data-layer-spec.md  |  414 ----
 ...6-03-26-feat-linear-cli-power-user-workflows.md |  153 --
 docs/plans/2026-03-26-feat-linear-cli-research.md  |  115 -
 ...026-03-26-feat-linear-cli-visionary-research.md |  106 -
 ...026-03-26-feat-notion-cli-data-layer-spec-v2.md |  371 ---
 .../2026-03-26-feat-notion-cli-data-layer-spec.md  |  414 ----
 ...3-26-feat-notion-cli-power-user-workflows-v2.md |  171 --
 ...6-03-26-feat-notion-cli-power-user-workflows.md |  293 ---
 .../2026-03-26-feat-notion-cli-research-v2.md      |  122 -
 docs/plans/2026-03-26-feat-notion-cli-research.md  |  214 --
 ...-03-26-feat-notion-cli-visionary-research-v2.md |  110 -
 ...026-03-26-feat-notion-cli-visionary-research.md |  269 ---
 docs/plans/2026-03-26-fix-discord-cli-audit.md     |   70 -
 .../2026-03-26-fix-discord-cli-dogfood-report.md   |   63 -
 .../2026-03-26-fix-discord-cli-goat-build-log.md   |   74 -
 docs/plans/2026-03-26-fix-discord-cli-v2-audit.md  |   75 -
 docs/plans/2026-03-26-fix-linear-cli-audit.md      |   68 -
 .../2026-03-26-fix-linear-cli-dogfood-report.md    |   99 -
 .../2026-03-26-fix-linear-cli-goat-build-log.md    |   82 -
 docs/plans/2026-03-26-fix-notion-cli-audit-v2.md   |   80 -
 docs/plans/2026-03-26-fix-notion-cli-audit.md      |   75 -
 .../2026-03-26-fix-notion-cli-goat-build-log-v2.md |   98 -
 .../2026-03-26-fix-notion-cli-goat-build-log.md    |   73 -
 ...-03-27-docs-lz-cli-honest-quality-assessment.md |  251 --
 docs/plans/2026-03-27-emboss-github-cli-delta.md   |    8 -
 .../2026-03-27-feat-github-cli-data-layer-spec.md  |  485 ----
 ...6-03-27-feat-github-cli-power-user-workflows.md |  147 --
 docs/plans/2026-03-27-feat-github-cli-research.md  |  138 --
 ...026-03-27-feat-github-cli-visionary-research.md |  200 --
 ...-03-27-feat-printing-press-repress-mode-plan.md |   42 +-
 docs/plans/2026-03-27-fix-github-cli-audit.md      |   59 -
 ...x-printing-press-post-mortem-notion-run-plan.md |  286 ---
 internal/cli/verify.go                             |   13 +-
 internal/generator/generator.go                    |   93 +-
 internal/generator/templates/root.go.tmpl          |   11 +-
 internal/generator/validate.go                     |   33 +-
 internal/pipeline/dogfood.go                       |   70 +-
 internal/pipeline/dogfood_test.go                  |   64 +-
 internal/pipeline/fullrun.go                       |   81 +-
 internal/pipeline/fullrun_test.go                  |  154 +-
 internal/pipeline/planner.go                       |   14 +-
 internal/pipeline/runtime.go                       |   21 +-
 internal/pipeline/scorecard.go                     |  107 +-
 internal/pipeline/seeds.go                         |   13 +-
 internal/pipeline/verify.go                        |   37 +-
 internal/pipeline/verify_test.go                   |   61 +-
 linear-spec.yaml                                   |  716 ------
 skills/printing-press/SKILL.md                     | 2459 ++------------------
 74 files changed, 634 insertions(+), 12403 deletions(-)

diff --git a/ONBOARDING.md b/ONBOARDING.md
index 3a5ba772..9e79f9fe 100644
--- a/ONBOARDING.md
+++ b/ONBOARDING.md
@@ -1,16 +1,16 @@
 # CLI Printing Press Onboarding Guide
 
-You give the printing press an API spec. It gives you back a Go CLI, an MCP server, and 7 analysis documents. It handles REST (OpenAPI) and GraphQL. One command, one hour, two binaries.
+You give the printing press an API spec. It gives you back a Go CLI, an MCP server, and the few artifacts needed to keep the next step grounded. It handles REST (OpenAPI) and GraphQL. One command, a lean fast path, two binaries.
 
 The key idea: most API CLI generators stop at wrapping endpoints. The printing press goes further -- it profiles each API, detects its domain archetype (communication, project management, payments, etc.), and generates domain-specific "power user" commands like `sync`, `search`, `stale`, `health`, and `similar` on top of the standard CRUD wrappers.
 
-This is built as a Claude Code skill. You run `/printing-press Discord` inside Claude Code, and it orchestrates an 8-phase pipeline that researches the API, generates the code, scores quality, and iterates until it passes.
+This is built as a Claude Code skill. You run `/printing-press Discord` inside Claude Code, and it now uses a lean brief -> generate -> build -> shipcheck loop for the normal fast path. The older multi-phase on-disk pipeline still exists behind `printing-press print` when you explicitly want resumable phase plans.
 
 ---
 
 ## How It's Used
 
-The primary entry point for users is the **`/printing-press` Claude Code skill** (defined in `skills/printing-press/`). A user types `/printing-press <API name>` inside Claude Code and the skill drives the entire pipeline. Everything else in this repo -- the Go binary, the parsers, the templates, the profiler -- exists to serve that skill.
+The primary entry point for users is the **`/printing-press` Claude Code skill** (defined in `skills/printing-press/`). A user types `/printing-press <API name>` inside Claude Code and the skill drives the fast path: one research brief, generation, focused build work, then a shipcheck block using `dogfood`, `verify`, and `scorecard`. Everything else in this repo -- the Go binary, the parsers, the templates, the profiler -- exists to serve that skill.
 
 Developers working on this codebase build and test the Go binary directly (`go build`, `go test`), but the thing you're ultimately shipping is the skill-driven experience.
 
@@ -51,14 +51,14 @@ cli-printing-press/
 | `internal/docspec/` | Scrapes API documentation URLs and generates `APISpec` (regex or LLM) |
 | `internal/generator/` | Renders Go templates against `APISpec` to produce a CLI project |
 | `internal/profiler/` | Analyzes an `APISpec` to detect domain archetype and recommend features |
-| `internal/pipeline/` | Orchestrates the 8-phase generation pipeline with state tracking |
+| `internal/pipeline/` | Orchestrates the optional resumable plan pipeline plus shipcheck helpers |
 | `internal/vision/` | Defines the feature scoring model used by the profiler |
 | `internal/cli/` | Wires all Cobra commands: `generate`, `print`, `scorecard`, `dogfood`, `vision` |
 | `catalog/` | YAML entries for known APIs (Discord, Stripe, Linear, etc.) with spec URLs |
 
 Data flows through the system like this: a spec file (OpenAPI, GraphQL SDL, or internal YAML) gets parsed into an `APISpec` struct. The profiler analyzes that struct to detect domain signals and recommend features. The generator takes both the spec and the profile, selects the right templates, and renders a full Go project to disk.
 
-The pipeline module adds a higher-level orchestration layer on top. When you run `printing-press print Discord`, it creates an 8-phase plan directory with seed documents. Each phase is executed by Claude Code via `/ce:work`, and the pipeline tracks state in a `state.json` file so you can resume across sessions.
+The pipeline module adds a higher-level orchestration layer on top. When you run `printing-press print Discord`, it creates an 8-phase plan directory with seed documents for a resumable run. The normal skill flow does not require all 8 phases; it uses the faster direct loop unless you explicitly ask for resumable phase plans.
 
 This project has no external service dependencies. It's a pure Go binary that reads spec files and writes generated code.
 
@@ -76,7 +76,7 @@ This project has no external service dependencies. It's a pure Go binary that re
 | Quality gates | 7 mechanical checks every generated CLI must pass: `go mod tidy`, `go vet`, `go build`, binary build, `--help`, `version`, `doctor`. |
 | Two-tier scoring | Infrastructure scoring (50 pts: output modes, auth, errors, agent-native flags) + Domain correctness scoring (50 pts: path validity, auth protocol, data pipeline, dead code). |
 | Dogfood validator | Catches dead flags, dead functions, invalid API paths, and auth mismatches by cross-referencing generated code against the source spec. |
-| Pipeline phases | 8 sequential phases: preflight, research, scaffold, enrich, regenerate, review, comparative, ship. Each produces a plan document. |
+| Pipeline phases | Optional 8-phase resumable pipeline: preflight, research, scaffold, enrich, regenerate, review, comparative, ship. |
 | Catalog entry | A YAML file in `catalog/` that maps an API name to its spec URL, format, category, and tier. Used by `DiscoverSpec()` to auto-resolve API names. |
 | Creativity ladder | Rung 1-2: API wrappers + output formatting (always generated). Rung 3: local persistence. Rung 4: domain analytics. Rung 5: behavioral insights. |
 
@@ -84,16 +84,20 @@ This project has no external service dependencies. It's a pure Go binary that re
 
 ## Primary Flows
 
-### Flow 1: Pipeline Orchestration (`/printing-press` skill -> `printing-press print`)
+### Flow 1: Skill Fast Path (`/printing-press` skill)
 
-This is the flow users hit. The `/printing-press` Claude Code skill invokes `printing-press print`:
+This is the normal user journey:
 
-1. `internal/cli/root.go` (`newPrintCmd`) calls `pipeline.Init()` with the API name
-2. `pipeline.Init()` calls `DiscoverSpec()` which looks up the API in `catalog/` entries
-3. Seeds are written for each of the 8 phases into `docs/plans/<api>-pipeline/`
-4. `state.json` is created to track progress across sessions
-5. The user runs `/ce:work` on each phase plan sequentially
-6. `CompleteAndPlanNext()` dynamically generates the next phase's plan using outputs from completed phases
+1. The skill resolves the API name to a spec path or URL, reuses any prior research, and writes one concise brief
+2. The skill runs `printing-press generate` with the resolved spec
+3. The agent or user makes only the highest-value implementation changes needed for ship-readiness
+4. The skill runs one shipcheck block:
+   - `printing-press dogfood`
+   - `printing-press verify --fix`
+   - `printing-press scorecard`
+5. If a token is available and the user opted in, the skill runs a small read-only live smoke test
+
+The important part: the default path does not require creating an 8-phase resumable pipeline.
 
 ### Flow 2: Direct Generation (`printing-press generate`)
 
@@ -133,7 +137,17 @@ Quality gates (if --validate)
   -> binary --help -> version -> doctor
 ```
 
-### Flow 3: Docs-to-Spec (`--docs`)
+### Flow 3: Optional Resumable Pipeline (`printing-press print`)
+
+Use this only when you explicitly want on-disk phase seeds and resumable state:
+
+1. `internal/cli/root.go` (`newPrintCmd`) calls `pipeline.Init()` with the API name
+2. `pipeline.Init()` calls `DiscoverSpec()` which looks up the API in `catalog/` entries
+3. Seeds are written for each of the 8 phases into `docs/plans/<api>-pipeline/`
+4. `state.json` is created to track progress across sessions
+5. The user runs phase work from the generated plan files
+
+### Flow 4: Docs-to-Spec (`--docs`)
 
 When no spec file exists, `--docs https://api.example.com/docs` scrapes the docs page via `internal/docspec/`, extracts endpoints using regex (or LLM if available), and produces an `APISpec` that feeds into the standard generation path.
 
diff --git a/README.md b/README.md
index 8f527979..58b613cf 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ Just making a CLI is not hard. Making a CLI that understands the power user is e
 /printing-press Linear
 ```
 
-One command. 13 phases. ~1 hour. Produces a Go CLI + MCP server + 8 analysis documents. REST or GraphQL. Matches every competitor feature, then adds the data layer they don't have.
+One command. Lean 5-phase loop. Usually 15-35 minutes to first shipcheck instead of an hour of mandatory phase churn. Produces a Go CLI + MCP server plus the few artifacts that actually help the next step. REST or GraphQL. Matches core competitor features, then adds the data layer they don't have.
 
 ### Get it
 
@@ -113,35 +113,25 @@ The archetype is detected automatically from the spec. The entity mapper figures
 
 ## How It Works
 
-12 phases. Each writes a plan document. The artifacts are the product.
+The fast path is a lean loop. Artifacts still matter, but only when they directly improve the next phase.
 
 ```
-Phase 0     Visionary Research        (3-5 min)    NOI + domain identity + usage patterns
-Phase 0.1   API Key Prompt            (optional)   Offer live testing at end
-Phase 0.5   Power User Workflows      (2-3 min)    Compound commands power users want
-Phase 0.6   Feature Parity Audit      (5 min)      [NEW] Catalog competitor features, classify table stakes
-Phase 0.7   Prediction Engine         (15-25 min)  SQLite schema + FTS5 + sync strategy
-Phase 0.8   Product Thesis            (2 min)      Name (<api>-pp-cli), positioning, anti-scope with cost analysis
-Phase 1     Deep Research             (5-8 min)    Competitors, strategic justification
-Phase 2     Generate                  (1-2 min)    Go CLI + MCP server from spec + name/path/version validation
-Phase 3     Non-Obvious Insight Review(5-8 min)    Two-tier scoring + competitor feature matrix
-Phase 4     GOAT Build                (20-30 min)  7 priorities: data layer, table stakes, workflows, names, tests, distribution
-Phase 4.7   Proof of Behavior         (30 sec)     Verify data actually flows (no hallucinations)
-Phase 4.9   Agent Readiness           (auto)       CLI agent readiness reviewer loop (max 2 passes)
-Phase 5     Ship Readiness Assessment (2-3 min)    Three-benchmark gate: architecture + quality + features
-Phase 5.5   Live API Testing          (optional)   Read-only tests + data pipeline smoke test
-Phase 5.7   Ship Loop                 (auto)       Fix issues and re-score until PASS
-Phase 5.9   Offer Emboss              (prompt)     [NEW] Opt-in second pass to improve further
+Phase 0     Resolve + Reuse           (1-3 min)    Reuse prior research, detect tokens, lock the spec source
+Phase 1     Research Brief            (5-10 min)   API identity, top workflows, table stakes, data layer, thesis
+Phase 2     Generate                  (1-2 min)    Go CLI + MCP server from spec with validation
+Phase 3     Build                     (10-20 min)  Data layer + top workflows + highest-value gaps
+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
 ```
 
 ### Codex Mode (opt-in)
 
 ```bash
-/printing-press Discord codex    # Offload code generation + fix patches to Codex CLI (~60% Opus token savings)
+/printing-press Discord codex    # Offload code generation to Codex CLI (~60% Opus token savings)
 /printing-press Discord          # Standard Opus mode (default)
 ```
 
-When you add `codex`, Phase 4's code generation tasks and later fix-application passes are delegated to Codex CLI. Claude stays the brain (research, planning, scoring, review). Codex does the hands (writing Go code from scoped prompts). Same quality, 60% fewer Opus tokens.
+When you add `codex`, Phase 4's code generation tasks are delegated to Codex CLI. Claude stays the brain (research, planning, scoring, review). Codex does the hands (writing Go code from scoped prompts). Same quality, 60% fewer Opus tokens.
 
 ## What Gets Generated
 
@@ -222,24 +212,7 @@ printing-press dogfood --dir ./discord-cli --spec /tmp/discord-spec.json
 /install-skill https://github.com/mvanhorn/cli-printing-press
 ```
 
-### Install the Compound Engineering Plugin
-
-The printing press leverages agents from the [Compound Engineering plugin](https://github.com/EveryInc/compound-engineering-plugin) to improve generated CLI quality and agent readiness. Install it before running the press:
-
-```bash
-/plugin marketplace add EveryInc/compound-engineering-plugin
-/plugin install compound-engineering
-```
-
-After installing both the skill and plugin, reload:
-
-```bash
-/reload-plugins
-```
-
-### Build the Binary
-
-Build the binary (needed for scorecard, verify, and dogfood commands):
+Then build the binary (needed for scorecard, verify, and dogfood commands):
 
 ```bash
 cd ~/cli-printing-press
diff --git a/docs/plans/2026-03-23-fix-discord-steinberger-quality-dogfood-plan.md b/docs/plans/2026-03-23-fix-discord-steinberger-quality-dogfood-plan.md
deleted file mode 100644
index 513da93e..00000000
--- a/docs/plans/2026-03-23-fix-discord-steinberger-quality-dogfood-plan.md
+++ /dev/null
@@ -1,223 +0,0 @@
----
-title: "Dogfood Discord CLI to Steinberger Quality with Live API Testing"
-type: fix
-status: completed
-date: 2026-03-23
----
-
-# Dogfood Discord CLI to Steinberger Quality with Live API Testing
-
-## Overview
-
-The printing press generates a functional Discord CLI, but it's not Steinberger quality. 230 operations in the spec, zero summaries, zero descriptions, zero tags - Discord is the hardest test. The current output has 5 critical quality gaps that make it feel like a toy instead of a tool. This plan is a tight dogfood loop: generate, use against the real Discord API with a real bot token, find what's broken, fix the press, repeat.
-
-## Problem Statement
-
-Current Discord CLI baseline (generated 2026-03-23):
-
-| Quality dimension | Current state | Steinberger target |
-|---|---|---|
-| **Endpoint coverage** | 60/230 (20-per-resource cap) | 230/230 (smart sub-resources) |
-| **Descriptions** | Auto-generated from name ("Get", "Create message") | Real descriptions or smart context-aware generation |
-| **Auth** | "not configured" despite BotToken scheme in spec | `DISCORD_BOT_TOKEN` env var, `Authorization: Bot {token}` |
-| **Flag types** | `--tts string`, `--flags string` (wrong types) | `--tts` (bool), `--flags int` (correct types) |
-| **Sub-resource grouping** | Flat list of 20 commands under `guilds` | `guilds members list`, `guilds roles create`, `guilds emojis get` |
-| **Usage examples** | None | `discord-cli channels create-message 123 --content "Hello"` |
-| **Live API test** | Never tested | Actually sends messages, lists guilds, creates channels |
-
-## Execution Loop
-
-Same loop as the previous plan, but targeting Discord specifically and testing against the live API:
-
-```
-while (not steinberger quality):
-  1. Build: go build -o ./printing-press ./cmd/printing-press
-  2. Clean: rm -rf discord-cli/
-  3. Generate: ./printing-press generate --spec testdata/openapi/discord.json --output ./discord-cli
-  4. Build CLI: cd discord-cli && go build -o ./discord ./cmd/discord-cli
-  5. USE IT against real Discord API:
-     - discord-cli doctor (auth configured, API reachable 200)
-     - discord-cli users get-my (get bot's own user)
-     - discord-cli guilds list-my-guilds... wait, that's under users
-     - discord-cli users list-my-guilds (list bot's guilds)
-     - discord-cli guilds get <guild_id> (get guild details)
-     - discord-cli channels list-messages <channel_id> (read messages)
-     - discord-cli channels create-message <channel_id> --content "test" (send a message!)
-  6. Write down what's wrong
-  7. Fix the PRESS (parser.go, templates, generator.go)
-  8. go test ./...
-  9. Repeat
-```
-
-## Phase 1: Sub-Resource Grouping (Biggest Impact)
-
-**Problem:** Discord's `guilds` resource has 90+ endpoints covering members, roles, emojis, bans, auto-mod, scheduled events, soundboard, stickers, templates, voice states, webhooks, and more. The current 20-endpoint cap loses 70+ endpoints.
-
-**Fix:** Detect sub-resources from path segments. `/guilds/{guild_id}/members` becomes `guilds members list` instead of `guilds list-members`.
-
-### Changes to `internal/openapi/parser.go`:
-
-1. **Add sub-resource detection to `mapResources`:**
-   - After extracting the primary resource name from the first path segment, check if there's a second non-param segment
-   - If yes, create a nested resource: `guilds` -> `members`, `guilds` -> `roles`, `guilds` -> `emojis`
-   - Store as `spec.Resource` with a `SubResources map[string]Resource` field
-
-2. **Update `resourceNameFromPath` to return both primary and sub-resource:**
-   - `/guilds/{guild_id}/members/{user_id}` -> primary: `guilds`, sub: `members`
-   - `/guilds/{guild_id}/auto-moderation/rules` -> primary: `guilds`, sub: `auto-moderation`
-   - `/guilds/{guild_id}` (no sub-path after param) -> primary: `guilds`, sub: "" (direct)
-
-3. **Raise or remove the endpoint limit for sub-resources:**
-   - Each sub-resource gets its own 20-endpoint limit
-   - The parent resource only holds "direct" endpoints (get, update, delete the guild itself)
-
-### Changes to `internal/spec/spec.go`:
-
-4. **Add SubResources field:**
-   ```go
-   type Resource struct {
-       Description  string                 `yaml:"description"`
-       Endpoints    map[string]Endpoint    `yaml:"endpoints"`
-       SubResources map[string]Resource    `yaml:"sub_resources,omitempty"`
-   }
-   ```
-
-### Changes to `internal/generator/templates/`:
-
-5. **Update `command.go.tmpl` to generate nested Cobra commands:**
-   - Parent command (`guilds`) adds sub-resource commands as children
-   - Sub-resource command (`members`) has its own endpoints as children
-   - Result: `discord-cli guilds members list <guild_id>`
-
-6. **Update `root.go.tmpl` to handle sub-resources recursively**
-
-### Acceptance:
-
-- [ ] `discord-cli guilds --help` shows sub-resources: members, roles, emojis, bans, auto-mod, etc.
-- [ ] `discord-cli guilds members --help` shows: list, get, update, delete, search
-- [ ] `discord-cli guilds roles --help` shows: list, create, update, delete
-- [ ] No endpoints skipped due to limit (or minimal skipping)
-- [ ] `go test ./...` passes
-
-## Phase 2: Auth Mapping for Discord
-
-**Problem:** Discord uses `Authorization: Bot {token}` header. The spec has `BotToken` security scheme type `apiKey` in header `Authorization`. The parser maps this to `api_key` with `DISCORD_API_KEY` env var, but Discord bots use `Bot ` prefix.
-
-### Changes to `internal/openapi/parser.go`:
-
-1. **Detect Discord-style bot token auth:**
-   - When scheme type is `apiKey`, in=header, name=Authorization
-   - Check if scheme name contains "bot" (case-insensitive)
-   - Set `auth.Format = "Bot {token}"` and `auth.EnvVars = ["DISCORD_BOT_TOKEN"]`
-
-2. **Or more generically:** map the scheme name into the env var name:
-   - `BotToken` -> `DISCORD_BOT_TOKEN`
-   - `OAuth2` -> `DISCORD_TOKEN`
-
-### Acceptance:
-
-- [ ] `discord-cli doctor` with `DISCORD_BOT_TOKEN=xxx` shows auth=configured
-- [ ] `discord-cli users get-my` with real token returns bot user JSON
-- [ ] Auth header is `Authorization: Bot {token}` (not `Bearer`)
-
-## Phase 3: Flag Type Accuracy
-
-**Problem:** `--tts string` should be `--tts` (bool). `--flags string` should be `--flags int`. The parser falls through to `string` for any type it doesn't recognize, but the Discord spec uses proper types in the schema.
-
-### Investigation:
-
-1. Check why `tts` (boolean in spec) renders as string flag
-2. Check if `mapSchemaType` correctly handles the `boolean` type from the request body schema
-3. The issue might be in `mapRequestBody` -> schema property extraction
-
-### Changes:
-
-4. Fix any type mapping bugs found
-5. Ensure `bool` params render as `--flag` (BoolVar) not `--flag string`
-
-### Acceptance:
-
-- [ ] `discord-cli channels create-message --help` shows `--tts` as a boolean flag (no value needed)
-- [ ] `--flags` is an int flag
-- [ ] Complex object flags are correctly skipped (not shown as `--embeds string`)
-
-## Phase 4: Usage Examples in Help
-
-**Problem:** No `--help` output shows usage examples. Steinberger CLIs always show examples.
-
-### Changes to `internal/generator/templates/command.go.tmpl`:
-
-1. **Add `Example` field to Cobra commands:**
-   ```go
-   cmd := &cobra.Command{
-       Use:     "create-message <channel_id>",
-       Short:   "Create message",
-       Example: "  discord-cli channels create-message 123456789 --content \"Hello world\"",
-   }
-   ```
-
-2. **Auto-generate examples from endpoint metadata:**
-   - For POST/PUT: show required flags with placeholder values
-   - For GET with positional args: show the positional arg
-   - For DELETE: show the positional arg
-   - Include the full command path: `{cli-name} {resource} {command} {args} {flags}`
-
-### Changes to `internal/generator/generator.go`:
-
-3. **Add `example` template function** that generates example strings
-
-### Acceptance:
-
-- [ ] `discord-cli channels create-message --help` shows an Example section
-- [ ] `discord-cli guilds get --help` shows `discord-cli guilds get 123456789`
-- [ ] Examples use realistic placeholder values (not empty strings)
-
-## Phase 5: Live API Dogfood (YOLO Mode)
-
-**Setup:**
-1. User provides Discord bot token
-2. Configure: `DISCORD_BOT_TOKEN=xxx discord-cli doctor`
-3. Or write to `~/.config/discord-cli/config.toml`
-
-**Test matrix (all must work with real API):**
-
-- [ ] `discord-cli doctor` - all green
-- [ ] `discord-cli users get-my` - returns bot user JSON with username, id, discriminator
-- [ ] `discord-cli users list-my-guilds` - returns list of guilds the bot is in
-- [ ] `discord-cli guilds get <guild_id>` - returns full guild object
-- [ ] `discord-cli guilds list-channels <guild_id>` - returns channel list
-- [ ] `discord-cli channels get <channel_id>` - returns channel details
-- [ ] `discord-cli channels list-messages <channel_id>` - returns recent messages
-- [ ] `discord-cli channels create-message <channel_id> --content "Hello from the printing press"` - sends a real message
-- [ ] `discord-cli guilds list-emojis <guild_id>` - returns emoji list (tests sub-resources)
-- [ ] `discord-cli guilds members list <guild_id>` - returns member list (tests sub-resources)
-
-## Scope
-
-Fix the press to make Discord output Steinberger-quality. Nothing else:
-- Only modify files in `internal/` and `internal/generator/templates/`
-- Never edit generated `*-cli/` output
-- Don't break Petstore or Stytch (run all 3 specs in test)
-- No README, no docs, no catalog changes
-
-## Phases in Priority Order
-
-1. **Sub-resource grouping** - Unlocks 170 missing endpoints. Biggest bang for buck.
-2. **Auth mapping** - Required for live API testing. Blocks Phase 5.
-3. **Flag types** - Polish. Wrong types look amateur.
-4. **Usage examples** - Polish. Missing examples feel incomplete.
-5. **Live API dogfood** - Validation. Proves everything works end-to-end.
-
-## How to Run
-
-```bash
-cd ~/cli-printing-press
-# Phase 1-4: fix the press
-# Phase 5: set up Discord bot token and test live
-export DISCORD_BOT_TOKEN="your-bot-token-here"
-./printing-press generate --spec testdata/openapi/discord.json --output ./discord-cli
-cd discord-cli && go build -o ./discord ./cmd/discord-cli
-./discord doctor
-./discord users get-my
-./discord channels create-message <channel_id> --content "It works."
-```
diff --git a/docs/plans/2026-03-24-docs-linear-cli-competitive-analysis-plan.md b/docs/plans/2026-03-24-docs-linear-cli-competitive-analysis-plan.md
deleted file mode 100644
index 0d166eeb..00000000
--- a/docs/plans/2026-03-24-docs-linear-cli-competitive-analysis-plan.md
+++ /dev/null
@@ -1,195 +0,0 @@
----
-title: "Linear CLI Competitive Analysis - Our CLI vs Community vs MCP"
-type: docs
-status: active
-date: 2026-03-24
----
-
-# Linear CLI Competitive Analysis
-
-## Overview
-
-We generated a Linear CLI from the printing press (12 resources, 45 commands, Go binary). This document compares it against the three strongest community CLIs and the official Linear MCP server to understand where we win, where we lose, and what to do about it.
-
-## The Contenders
-
-| Tool | Language | Stars | Commands | Install | Primary Audience |
-|------|----------|-------|----------|---------|-----------------|
-| **printing-press linear-cli** (ours) | Go | - | 45 | `go install` | Developers + agents |
-| **schpet/linear-cli** | TypeScript/Deno | 507 | ~20 | Homebrew, npm, binaries | Developers + agents |
-| **Finesssee/linear-cli** | Rust | 58 | 16+ subcommands, 38 AI skills | cargo install, binaries | Power users + agents |
-| **czottmann/linearis** | TypeScript/Node | 163 | ~15-20 | npm install | AI agents (token-optimized) |
-| **Linear MCP** (official) | Remote server | - | 23 tools | `claude mcp add` | AI agents (OAuth) |
-
-## Head-to-Head Comparison
-
-### Command Coverage
-
-| Category | Ours (45) | schpet (~20) | Finesssee (16+) | linearis (~18) | MCP (23) |
-|----------|-----------|-------------|-----------------|---------------|----------|
-| Issues CRUD | list, mine, get, create, update, search, archive, delete | list, view, create, update, start, comment | Full lifecycle + bulk ops | CRUD + relations | list, get, create, update |
-| Comments | list, create, delete | Inline with issues | Inline | CRUD | create only |
-| Projects | list, get, create, update | list, view | Full CRUD + labels | CRUD | list, get, create, update |
-| Cycles | list, current, get | - | Full + burndown charts | list, active | list |
-| Teams | list, get | list | Full + workflows | list | list, get |
-| Users | me, list | - | - | list | list, get |
-| Labels | list, create | - | Full CRUD | Batch operations | list, create |
-| Workflows | list | - | - | - | list (as statuses) |
-| Documents | list, get, create | list, view, create | - | CRUD | list, get |
-| Notifications | list | - | - | - | - |
-| Webhooks | list, create, delete | - | Full + HMAC verification | - | - |
-| Organization | get | - | - | - | - |
-| **Smart filters** | --state, --assignee me, --team, --priority, --project, --label | Basic | Extensive | Basic | Basic |
-| **Git integration** | No | Branch creation, PR gen | Branch + jujutsu | No | No |
-| **Watch mode** | No | No | Real-time polling | No | No |
-
-**Verdict: We have the widest command coverage (45 vs next-best ~20).** Finesssee has deeper features per command (burndown charts, watch mode). schpet has the best git workflow integration. We beat all of them on breadth.
-
-### Developer Experience
-
-| Aspect | Ours | schpet | Finesssee | linearis | MCP |
-|--------|------|--------|-----------|---------|-----|
-| **Startup time** | <50ms (Go binary) | <100ms (Deno binary) | <50ms (Rust binary) | 0.5-1s (Node.js) | N/A (remote) |
-| **Install friction** | `go install` (needs Go) | `brew install` (zero friction) | `cargo install` (needs Rust) | `npm install -g` (needs Node) | `claude mcp add` |
-| **Zero deps?** | Yes (static binary) | Yes (pre-built binaries) | Yes (pre-built binaries) | No (Node.js runtime) | No (OAuth + SSE) |
-| **Auth setup** | `export LINEAR_API_KEY=...` | `linear auth` (interactive) | `linear-cli login` | `export LINEAR_API_KEY=...` | OAuth browser flow |
-| **Output format** | Auto-table + --json | Text + --json | Text + --json | JSON-first | JSON (to agent) |
-| **Dry-run mode** | Yes (shows GraphQL query) | No | No | No | No |
-| **Error messages** | Structured exit codes | Good | Good | Minimal | Opaque |
-| **Shell completion** | Yes (Cobra built-in) | Yes | Yes | No | N/A |
-| **Rate limit handling** | Auto-retry with backoff | Manual | Manual | Manual | Server-side |
-
-**Verdict: We're competitive on DX.** schpet wins on install friction (Homebrew). Our dry-run mode showing the exact GraphQL query is unique and valuable for debugging. Our auto-retry on rate limits is a differentiator.
-
-### Agent Efficiency (Token Cost)
-
-This is the real battleground - how much does it cost an AI agent to use each tool?
-
-| Metric | CLI (any) | MCP (official) | Ratio |
-|--------|-----------|---------------|-------|
-| **Initial context load** | ~300 tokens | ~15,540 tokens | 51x cheaper |
-| **Per-tool invocation** | ~50-100 tokens | ~200-500 tokens | 3-5x cheaper |
-| **50-device compliance check** | ~4,150 tokens | ~145,000 tokens | 35x cheaper |
-| **Token Efficiency Score** | 202 | 152 | 33% advantage |
-| **Task completion rate** | 28% higher | Baseline | - |
-
-Sources: UBOS benchmarks, Jannik Reinhard Intune study, Fiberplane MCP analysis
-
-**Verdict: Any CLI approach crushes MCP on token efficiency.** The 35-51x difference is structural - MCP loads full tool schemas into context; CLI tools are invoked as-needed.
-
-### Reliability
-
-| Issue | CLI (any) | MCP (official) |
-|-------|-----------|---------------|
-| **Connection stability** | Stateless HTTP - never disconnects | SSE connections degrade after ~1 hour |
-| **Auth token refresh** | N/A (API key) | OAuth tokens expire, auto-refresh fails for SSE |
-| **Status visibility** | Exit code 0 or error | Green checkmark masks failures |
-| **Error recovery** | Retry and continue | Toggle off/on, restart agent session |
-| **Multi-tool conflicts** | None | Linear MCP breaks detection of other MCPs |
-
-**Known MCP issues (with sources):**
-- Claude Code #36307: OAuth browser auth flow never triggers for Linear MCP
-- Cursor Forum #131713: "Linear MCP constantly going red, eventually fails in agent chat"
-- Cursor Forum #148816: "SSE error: undefined" after ~1 hour, requires toggle off/on
-- Cursor Forum #152224: Linear MCP interferes with Jira/Atlassian MCP detection
-- Gemini CLI #4031: Linear MCP breaks tool-calling
-
-**Verdict: CLI wins decisively on reliability.** MCP's persistent connection model is fundamentally fragile. The "constantly going red" problem is systemic, not a bug that will be fixed.
-
-### What We're Missing (Gaps to Close)
-
-| Gap | Who Has It | Priority | Difficulty |
-|-----|-----------|----------|------------|
-| **Homebrew tap** | schpet | High | Easy - just create a tap repo |
-| **Git branch integration** | schpet, Finesssee | Medium | Medium - detect branch name, resolve to issue |
-| **Pre-built binaries** | schpet, Finesssee | High | Easy - GoReleaser already scaffolded |
-| **Assignee/label resolution in create** | Finesssee | Medium | Medium - need resolve-then-set pattern |
-| **Watch mode** | Finesssee | Low | Medium - polling loop |
-| **Burndown charts** | Finesssee | Low | Medium - ASCII chart rendering |
-| **Claude Code skill** | schpet | High | Easy - we already have the skill system |
-| **Token-optimized output** | linearis | Medium | Easy - --json already works |
-
-## Strategic Position
-
-### Where We Win
-
-1. **Broadest command coverage** (45 commands vs next-best ~20)
-2. **Smart GraphQL filter composition** (--state + --team + --assignee compose into one optimized query)
-3. **Dry-run mode** (unique - shows exact GraphQL query, invaluable for debugging)
-4. **Auto-retry with rate limit backoff** (none of the others do this)
-5. **Identifier resolution** (ENG-123 auto-resolves to UUID for mutations)
-6. **State/assignee name resolution** (pass human-readable names, not UUIDs)
-7. **Go binary** (instant startup, static binary, cross-compile trivially)
-8. **Generated from spec** (can regenerate when Linear's API changes, not hand-maintained)
-
-### Where We Lose
-
-1. **Install friction** - `go install` requires Go toolchain; schpet has Homebrew
-2. **Git workflow** - schpet auto-creates branches from issues; we don't
-3. **Community/stars** - schpet has 507 stars and active community
-4. **No pre-built binaries yet** - GoReleaser is scaffolded but not configured
-5. **No Claude Code skill integration** - schpet ships as a Claude Code plugin
-
-### Where We Tie
-
-1. **Token efficiency** - All CLIs are roughly equivalent (any CLI >> MCP)
-2. **Startup time** - Go and Rust binaries are both <50ms
-3. **JSON output** - Everyone has --json
-4. **Error handling** - Similar quality across Go/Rust/Deno CLIs
-
-## The MCP Comparison (Why CLI Wins)
-
-The official Linear MCP server has 23 tools and OAuth-based auth. On paper, it's the easiest integration for AI agents. In practice:
-
-1. **It disconnects constantly.** SSE connections degrade after ~1 hour. Users report "constantly going red." The fix is toggling it off and on.
-
-2. **It costs 35-51x more tokens.** The full tool schema loads into context on every session. A CLI invocation is a single bash command.
-
-3. **OAuth auth flow is broken.** Claude Code issue #36307 - the browser auth flow never triggers. Users can't authenticate.
-
-4. **It breaks other MCPs.** Installing Linear MCP can prevent detection of Jira, GitHub, and Atlassian MCPs in the same session.
-
-5. **Errors are invisible.** The status indicator shows green while requests silently fail. There's no structured error reporting.
-
-**Bottom line: The MCP is architecturally unsuitable for reliable agent workflows.** A stateless CLI with API key auth is simpler, cheaper, and more reliable by design.
-
-## Recommended Next Steps
-
-### Phase 1: Ship It (make it installable)
-
-- [ ] Configure GoReleaser for pre-built binaries (darwin/linux/windows, arm64/amd64)
-- [ ] Create Homebrew tap (`mvanhorn/tap/linear-cli`)
-- [ ] Publish to GitHub Releases
-- [ ] Write a README with install instructions and usage examples
-
-### Phase 2: Close the Gaps
-
-- [ ] Git branch integration: detect `ENG-123-feature` branch, auto-set context
-- [ ] Assignee/label resolution in `issues create` (resolve names to IDs)
-- [ ] Claude Code skill: ship as `/linear` slash command
-- [ ] `issues start` command: create branch + move to "In Progress"
-
-### Phase 3: Differentiate Further
-
-- [ ] `issues triage` command: show unassigned + untriaged issues with priority suggestions
-- [ ] `cycles burndown` command: ASCII burndown chart in terminal
-- [ ] `issues bulk` command: update multiple issues at once
-- [ ] Local caching: cache team/user/label IDs to avoid repeated lookups
-
-## Sources
-
-### MCP Issues
-- [Claude Code #36307: OAuth never triggers](https://github.com/anthropics/claude-code/issues/36307)
-- [Cursor Forum #131713: Constantly going red](https://forum.cursor.com/t/linear-mcp-constantly-going-red-eventually-fails-in-agent-chat/131713)
-- [Cursor Forum #148816: SSE error undefined](https://forum.cursor.com/t/linear-mcp-commonly-errors-out-and-requires-turning-off-then-on/148816)
-- [Gemini CLI #4031: Breaks tool-calling](https://github.com/google-gemini/gemini-cli/issues/4031)
-
-### Token Benchmarks
-- [UBOS: CLI vs MCP Token Cost Savings](https://ubos.tech/news/cli-vs-mcp-token-cost-savings-and-lazy-loading-explained/)
-- [Jannik Reinhard: CLI beats MCP 35x](https://jannikreinhard.com/2026/02/22/why-cli-tools-are-beating-mcp-for-ai-agents/)
-- [Fiberplane MCP Analysis](https://blog.fiberplane.com/blog/mcp-server-analysis-linear/)
-
-### Community CLIs
-- [schpet/linear-cli](https://github.com/schpet/linear-cli) - 507 stars, TypeScript/Deno
-- [Finesssee/linear-cli](https://github.com/Finesssee/linear-cli) - 58 stars, Rust
-- [czottmann/linearis](https://github.com/czottmann/linearis) - 163 stars, TypeScript/Node
diff --git a/docs/plans/2026-03-25-feat-discord-cli-research.md b/docs/plans/2026-03-25-feat-discord-cli-research.md
deleted file mode 100644
index 9dff301e..00000000
--- a/docs/plans/2026-03-25-feat-discord-cli-research.md
+++ /dev/null
@@ -1,68 +0,0 @@
----
-title: "Research: Discord CLI"
-type: feat
-status: active
-date: 2026-03-25
----
-
-# Research: Discord CLI
-
-## Spec Discovery
-- Official OpenAPI spec: https://raw.githubusercontent.com/discord/discord-api-spec/main/specs/openapi.json
-- Source: Known-specs registry (verified)
-- Format: OpenAPI 3.1.0 (JSON)
-- Endpoint count: 200+ (22 resource categories)
-
-## Competitors (Deep Analysis)
-
-### DiscordChatExporter (10,700 stars)
-- Repo: https://github.com/Tyrrrz/DiscordChatExporter
-- Language: C# (91.3%)
-- Commands: ~10 (export-focused: exportdm, exportchannel, exportguild, exportall)
-- Last commit: March 21, 2026 (v2.47.1)
-- Open issues: 10
-- Contributors: 63
-- Maintained: YES (actively)
-- Notable features: GUI + CLI, HTML/JSON/CSV/TXT export, date range filtering, file partitioning, Docker
-- Weaknesses: Export-only (no search, no sync, no management), C# binary (large), no agent-native features (--json/--select/--dry-run), no server management, no audit log analysis
-
-### jackwener/discord-cli (78 stars)
-- Repo: https://github.com/jackwener/discord-cli
-- Language: Python (100%)
-- Commands: ~15 (sync, search, tail, export, stats, timeline, top, recent, today, purge, guilds, channels, members, info)
-- Last commit: January 2026
-- Open issues: unknown (new repo)
-- Maintained: YES (new, active)
-- Notable features: SQLite + FTS5 search, incremental sync, tail -f, YAML/JSON output, AI agent integration (SCHEMA.md), activity timeline
-- Weaknesses: Python (slow startup), uses user tokens (TOS-violating), no server management (no role/channel CRUD), no audit log, no webhooks, no --dry-run, no doctor command
-
-### discordo (5,400 stars)
-- Repo: https://github.com/ayn2op/discordo
-- Language: Go (100%)
-- Purpose: TUI client (NOT a management CLI - different category)
-- Maintained: YES
-- Not a direct competitor - it's a chat client, not an API management tool
-
-## User Pain Points
-> "Communities are moving en-masse to information blackholes like Discord that cannot be indexed by search engines." - discord-dl README
-> "Automated user accounts violate Discord's Terms of Service" - discordo README (applies to user-token tools)
-> Rate limiting is the #1 pain point - 429 errors, per-route limits not communicated through headers, IP bans after 10k failed requests
-
-## Auth Method
-- Type: Bot token (primary, TOS-compliant)
-- Env var convention: DISCORD_TOKEN or DISCORD_BOT_TOKEN
-- OAuth2 also supported for user-level access
-
-## Demand Signals
-- HN: discordo (5.4k stars) shows strong demand for terminal Discord tools
-- HN: Discoding (2026) - AI CLI relay to Discord shows developer workflow integration demand
-- AnswerOverflow: Making Discord searchable is a validated business (Y Combinator backed)
-- n8n integration: Shows automation/workflow demand for Discord API
-
-## Strategic Justification
-**Why this CLI should exist:** No Go CLI exists that combines API management (full REST coverage) + data tool (SQLite sync/search/export) + workflow commands (audit-report, channel-health). DiscordChatExporter has 10.7k stars but is export-only with no search/sync/management. jackwener/discord-cli (78 stars) validates the data-tool approach but uses TOS-violating user tokens and is Python (slow). Our CLI is Go (fast binary), uses bot tokens (TOS-compliant), covers 200+ API endpoints, AND includes discrawl-class workflow commands. It's the first tool that combines all three: API wrapper + data tool + workflow engine.
-
-## Target
-- Command count: 50+ (beat jackwener at 15, match API breadth + 7 workflow commands)
-- Key differentiator: Go binary + bot-token compliant + SQLite sync/search + audit/health workflow commands
-- Quality bar: Steinberger Grade A (80+/100)
diff --git a/docs/plans/2026-03-25-feat-discord-cli-visionary-research.md b/docs/plans/2026-03-25-feat-discord-cli-visionary-research.md
deleted file mode 100644
index 1ebbfe34..00000000
--- a/docs/plans/2026-03-25-feat-discord-cli-visionary-research.md
+++ /dev/null
@@ -1,69 +0,0 @@
----
-title: "Visionary Research: Discord CLI"
-type: feat
-status: active
-date: 2026-03-25
----
-
-## Visionary Research: Discord CLI
-
-### API Identity
-- Domain: Communication (messaging, voice, communities)
-- Primary users: Bot developers, server admins, community managers, data analysts
-- Core entities: Guilds, Channels, Messages, Users, Roles, Emojis, Webhooks, Invites, Audit Log, Auto Moderation, Stage Instances, Scheduled Events, Polls, Stickers, Soundboard, SKUs, Entitlements, Subscriptions, Guild Templates, Lobbies
-- Data profile:
-  - Write pattern: Append-only (messages), mutable (guilds, channels, roles, members)
-  - Volume: HIGH (millions of messages across servers)
-  - Real-time: YES (Gateway/WebSocket for events, webhooks for notifications)
-  - Search need: HIGH (users constantly need to find messages, members, audit events)
-- Auth: Bot token (primary), OAuth2 (user auth)
-- Base URL: https://discord.com/api/v10
-- Rate limits: Per-route + global (10,000 req/10min = 429 ban for 1hr+)
-
-### Usage Patterns (Top 5 by Evidence)
-
-1. **Message export/archive** (Evidence: 10/10) - DiscordChatExporter 10.7k stars, multiple archive tools
-2. **Server backup/restore** (Evidence: 7/10) - discord-backup, discopy, discord-server-backup
-3. **Message search/analytics** (Evidence: 7/10) - AnswerOverflow, discord-analytics, discord-cli FTS5
-4. **Real-time channel monitoring** (Evidence: 5/10) - discord-dl, discord-cli tail, keyword alerting
-5. **Server management automation** (Evidence: 5/10) - Bot management, role CRUD, audit log review
-
-### Tool Landscape (Beyond API Wrappers)
-
-| Tool | Stars | Type | What it does |
-|------|-------|------|-------------|
-| DiscordChatExporter | 10,700 | Data Tool | Export messages to HTML/JSON/CSV/TXT |
-| discordo | 5,400 | Environment Tool | TUI Discord client |
-| jackwener/discord-cli | 78 | Data Tool | SQLite sync + FTS5 search + tail + export |
-| discord-dl | 26 | Data Tool | Archive to SQLite with web UI |
-| discord-migrate | 0 | Data Tool | Export to SQLite for Matrix migration |
-| Copycord | N/A | Integration Tool | Real-time server mirroring |
-| discopy | N/A | Data Tool | Server config backup/restore |
-
-### Workflows
-
-1. **Archive and Search**: sync messages -> SQLite -> FTS5 search -> export
-2. **Server Audit**: fetch audit log -> filter by action/user/date -> export report
-3. **Channel Health**: list channels -> message volume -> stale detection -> report
-4. **Member Analytics**: list members -> role grouping -> activity analysis -> export
-5. **Bulk Role Management**: list roles -> compare template -> bulk assign -> dry-run
-
-### Architecture Decisions
-
-| Decision | Choice | Rationale |
-|----------|--------|-----------|
-| Persistence | SQLite + FTS5 | High volume + high search need. Validated by discord-cli. |
-| Real-time | REST polling (tail) | Simpler than Gateway WebSocket for CLI use. |
-| Search | FTS5 local | Offline search, zero rate limit exposure. |
-| Bulk | Pagination + rate-limit | Transparent handling of Discord's per-route limits. |
-| Cache | SQLite IS the cache | Sync once, query many. --no-cache bypasses. |
-
-### Top 5 Features for the World
-
-| # | Feature | Score | Description |
-|---|---------|-------|-------------|
-| 1 | Local sync + FTS5 search | 14/16 | Sync to SQLite, search offline. |
-| 2 | Channel health detection | 12/16 | Inactive channels, volume trends. |
-| 3 | Audit log analysis + export | 12/16 | Query by action/user/date, export JSON. |
-| 4 | Server config backup/restore | 11/16 | Roles, channels, permissions to JSON. |
-| 5 | Message tail (real-time follow) | 10/16 | tail -f for Discord channels. |
diff --git a/docs/plans/2026-03-25-feat-discord-cli-vs-discrawl-competitive-plan.md b/docs/plans/2026-03-25-feat-discord-cli-vs-discrawl-competitive-plan.md
deleted file mode 100644
index 5043e792..00000000
--- a/docs/plans/2026-03-25-feat-discord-cli-vs-discrawl-competitive-plan.md
+++ /dev/null
@@ -1,443 +0,0 @@
----
-title: "Discord CLI vs Discrawl: Deep Competitive Analysis & Gap Closure Plan"
-type: feat
-status: active
-date: 2026-03-25
----
-
-# Discord CLI vs Discrawl: Deep Competitive Analysis & Gap Closure Plan
-
-## Overview
-
-Line-by-line comparison of two Go-based Discord CLI tools, followed by a gap analysis and phased implementation plan to close the gaps.
-
-- **discord-cli** (ours): REST API management CLI generated by CLI Printing Press. 307 commands covering the full Discord v10 API. Stateless, agent-native.
-- **discrawl** (steipete): Discord archive/search CLI by Peter Steinberger. 11 commands focused on mirroring Discord data to local SQLite with FTS5 search. Stateful, data-oriented.
-
----
-
-## Part 1: Line-by-Line Command Comparison
-
-### discrawl Commands vs discord-cli Equivalents
-
-#### 1. `discrawl init`
-
-**What it does:** Creates `~/.discrawl/config.toml`, discovers all guilds the bot can access, writes guild IDs to config, auto-selects default guild if only one exists.
-
-**Flags:** `--from-openclaw <path>`, `--guild <id>`, `--db <path>`, `--with-embeddings`
-
-**discord-cli equivalent:**
-- `discord-cli auth login` - sets up token (partial overlap)
-- `discord-cli doctor` - validates auth (partial overlap)
-- **GAP:** No guild discovery. No config bootstrapping that auto-discovers accessible guilds and lets you pick a default. No database setup.
-
-**What discrawl does better:** Single command goes from zero to configured. Discovers guilds, writes config, sets defaults. discord-cli requires manual setup: set env var, run doctor, figure out guild IDs yourself.
-
----
-
-#### 2. `discrawl sync`
-
-**What it does:** Backfills guild data (channels, threads, members, messages) into SQLite. Parallel channel workers (8-32, auto-sized). Resumable via `sync_state` checkpoints. Auto-batches incomplete channels in groups of 25.
-
-**Flags:** `--full`, `--guild <id>`, `--guilds <csv>`, `--channels <csv>`, `--concurrency <n>`, `--since <RFC3339>`, `--with-embeddings`
-
-**discord-cli equivalent:** **NONE.** discord-cli has no local storage, no sync, no backfill capability. Every command is a stateless API call.
-
-**What discrawl does better:** Enables offline analysis. Rate-limit-aware parallel crawling with resumable checkpoints. discord-cli can only fetch data one API call at a time with no persistence.
-
----
-
-#### 3. `discrawl tail`
-
-**What it does:** Runs live Gateway WebSocket event streaming plus periodic repair cycles. Two concurrent processes: real-time event handler + repair ticker (default 6h). Handles MessageCreate, MessageUpdate, MessageDelete, ChannelUpsert, MemberUpsert, MemberDelete. 4-16 worker pool with 30s handler timeout and panic recovery. Graceful SIGTERM/SIGINT shutdown.
-
-**Flags:** `--guild <id>`, `--repair-every <duration>`
-
-**discord-cli equivalent:** **NONE.** discord-cli is REST-only. No WebSocket, no Gateway, no real-time events, no long-running processes.
-
-**What discrawl does better:** Live data pipeline. Self-healing with repair cycles. discord-cli can only poll via repeated REST calls.
-
----
-
-#### 4. `discrawl search`
-
-**What it does:** FTS5 full-text search over archived messages. Searches message text, attachment text, attachment filenames, embeds, and replies. Falls back to LIKE-based matching when FTS fails. Query normalization wraps terms in quotes.
-
-**Flags:** `--guild <id>`, `--channel <name>`, `--author <name>`, `--limit <n>`, `--include-empty`, `--guilds <csv>`, `--mode`, `--json`
-
-**discord-cli equivalent:**
-- `discord-cli guilds messages guild-search <guild_id>` - searches messages via Discord's API
-- **PARTIAL:** API search is rate-limited, slow for large servers, and only searches current messages (no history if messages are deleted)
-
-**What discrawl does better:** Instant local FTS5 search. No rate limits. Searches attachment text and filenames. Works offline. Persists deleted message content. discord-cli's API search is limited to Discord's server-side search capabilities.
-
----
-
-#### 5. `discrawl messages`
-
-**What it does:** Lists exact message slices from local SQLite. Requires at least one filter (channel, author, time range). Supports `--sync` flag to run a blocking pre-query sync before reading.
-
-**Flags:** `--channel <id|name|#name>`, `--author <name>`, `--guild <id>`, `--guilds <csv>`, `--hours <n>`, `--days <n>`, `--since <RFC3339>`, `--before <RFC3339>`, `--limit <n>`, `--last <n>`, `--all`, `--sync`, `--include-empty`, `--json`
-
-**discord-cli equivalent:**
-- `discord-cli channels messages list <channel_id>` - lists messages from a channel via API
-- `discord-cli channels messages get <channel_id> <message_id>` - gets a single message
-
-**What discrawl does better:** Rich time-range filters (`--hours`, `--days`, `--since`, `--before`). Author and channel name matching (not just IDs). `--last N` for newest messages. `--sync` for fresh data before query. Cross-channel queries. discord-cli can only list messages from one channel at a time using the API, with no time filters beyond Discord's API parameters.
-
----
-
-#### 6. `discrawl mentions`
-
-**What it does:** Queries structured mention events from `mention_events` table. Joins across 4 tables (mention_events, messages, channels, members). Supports user and role mention types. Cascading author name resolution (display_name -> nickname -> global_name -> username -> JSON-extracted fields).
-
-**Flags:** `--channel <name>`, `--target <id|name>`, `--type <user|role>`, `--limit <n>`, `--days <n>`, `--hours <n>`, `--guild <id>`, `--guilds <csv>`, `--json`
-
-**discord-cli equivalent:** **NONE.** discord-cli has no mention tracking. The closest is `channels messages list` with manual content parsing.
-
-**What discrawl does better:** Structured mention data as first-class queryable entities. Tracks who was mentioned, where, when, by whom. discord-cli treats message content as opaque strings.
-
----
-
-#### 7. `discrawl sql`
-
-**What it does:** Runs arbitrary read-only SQL against the local SQLite database. Supports stdin piping. Safety gate: write queries require both `--unsafe` and `--confirm`. 50,000 row cap, 15-second timeout.
-
-**Flags:** `--unsafe`, `--confirm`, `--json`
-
-**discord-cli equivalent:** **NONE.** discord-cli has no local database.
-
-**What discrawl does better:** Ultimate flexibility - any analysis the user can express in SQL. Power-user escape hatch. discord-cli has no equivalent.
-
----
-
-#### 8. `discrawl members`
-
-**What it does:** Offline member directory with 3 subcommands: `list` (up to 500 members), `show <id|query>` (detailed profile + recent messages), `search <query>` (FTS across names, bios, social handles, URLs). Recursive JSON walking extracts bios, X/Twitter handles, GitHub handles, websites, pronouns. Filters out sensitive data (tokens, emails).
-
-**Flags (show):** `--messages <n>`
-
-**discord-cli equivalent:**
-- `discord-cli guilds members list-guild <guild_id>` - lists members via API
-- `discord-cli guilds members get-guild <guild_id> <user_id>` - gets a member
-- `discord-cli guilds members search-guild <guild_id>` - searches members by nickname/username
-
-**What discrawl does better:** Profile intelligence - extracts social handles, bios, URLs from raw JSON. FTS member search across all profile fields. Recent message history per member. discord-cli returns raw API responses with no profile enrichment.
-
----
-
-#### 9. `discrawl channels`
-
-**What it does:** Lists or shows channel metadata from local SQLite. Shows NSFW flags, archive status, position ordering.
-
-**Subcommands:** `list`, `show <id>`
-
-**discord-cli equivalent:**
-- `discord-cli guilds channels list-guild <guild_id>` - lists channels via API
-- `discord-cli channels get <channel_id>` - gets a channel
-
-**Parity:** Roughly equivalent for basic listing. discrawl adds archive status and position ordering from its normalized schema.
-
----
-
-#### 10. `discrawl status`
-
-**What it does:** Shows local archive completeness for the default guild. How many channels synced, message counts, last sync time.
-
-**discord-cli equivalent:** **NONE.** discord-cli has no local state to report on.
-
----
-
-#### 11. `discrawl doctor`
-
-**What it does:** 6-component health check: config file integrity, Discord token resolution (reports source not contents), bot authentication success, guild accessibility count, database connectivity, FTS5 wiring verification.
-
-**discord-cli equivalent:**
-- `discord-cli doctor` - checks config, auth, API connectivity, version
-
-**Parity:** Both have doctor commands. discrawl's checks database and FTS health. discord-cli's checks API connectivity. Both validate auth without exposing tokens.
-
----
-
-### discord-cli Commands with No discrawl Equivalent
-
-discrawl is read-only (archive/search). It cannot:
-
-| discord-cli capability | Commands |
-|---|---|
-| **Send messages** | `channels messages create` |
-| **Manage channels** | `channels delete`, `channels update`, `guilds channels create-guild` |
-| **Manage roles** | `guilds roles create/delete/update-guild` |
-| **Manage bans** | `guilds bans user-from-guild`, `guilds bans unban-user-from-guild` |
-| **Manage emojis** | `guilds emojis create/delete/update-guild` |
-| **Register slash commands** | `applications commands create-application` |
-| **Execute webhooks** | `webhooks execute` |
-| **Manage stage instances** | `stage-instances create/update/delete` |
-| **Manage scheduled events** | `guilds scheduled-events create/update/delete-guild` |
-| **Manage auto-moderation** | `guilds auto-moderation create/update/delete-rule` |
-| **Manage invites** | `invites resolve/revoke`, `channels invites create-channel` |
-| **Manage threads** | `channels threads create`, `channels thread-members add/delete` |
-| **Manage reactions** | `channels messages add-my-reaction`, `delete-all-reactions` |
-| **Manage pins** | `channels messages create-pin`, `delete-pin` |
-| **OAuth2 flows** | `oauth2 get-my-application`, `auth login` |
-| **Manage entitlements** | `applications entitlements create/consume/delete` |
-| **Manage lobbies** | `lobbies create-lobby`, `lobbies members add-lobby` |
-| **Manage soundboard** | `guilds soundboard-sounds create/update/delete-guild` |
-| **Manage stickers** | `guilds stickers create/update/delete-guild` |
-| **Manage welcome screen** | `guilds welcome-screen update-guild` |
-| **Manage onboarding** | `guilds onboarding put-guilds` |
-
-**Total:** ~280 write/management commands that discrawl cannot do.
-
----
-
-## Part 2: Design Decision Comparison
-
-### Architecture
-
-| Dimension | discord-cli | discrawl |
-|---|---|---|
-| **Data model** | Stateless REST | Local SQLite with FTS5 |
-| **Persistence** | 5-min GET cache only | Full archive database |
-| **Protocol** | REST API only | REST + Gateway WebSocket |
-| **Concurrency** | Single request per command | 8-32 parallel workers |
-| **Rate limiting** | Client-side retry with backoff | discordgo built-in + context timeouts |
-| **Auth** | Bot token, OAuth2, custom header | Bot token only (+ OpenClaw integration) |
-| **Config format** | TOML at `~/.config/discord-cli/` | TOML at `~/.discrawl/` |
-| **Output** | JSON, CSV, plain, table, quiet | JSON, text tables |
-| **Field selection** | `--select id,name` | None (use `sql` or `jq`) |
-| **Dry-run** | `--dry-run` shows request | None (read-only anyway) |
-| **Agent-native** | Full (--json, --select, --stdin, --yes, typed exits) | Partial (--json, --quiet) |
-| **CGO** | Standard Go | Pure Go SQLite (modernc.org/sqlite) |
-| **Cross-compilation** | Standard | 6 platforms via GoReleaser |
-
-### Error Handling
-
-| Aspect | discord-cli | discrawl |
-|---|---|---|
-| **Exit codes** | 7 typed codes (0, 2, 3, 4, 5, 7, 10) | Standard Go (0/1) |
-| **Idempotency** | 409=success, 404 on delete=success | N/A (read-only) |
-| **Retry** | 3 retries, exponential backoff for 5xx, Retry-After for 429 | discordgo built-in rate limiting |
-| **Error hints** | "Run doctor" suggestions | Descriptive error messages |
-
-### Search Capabilities
-
-| Aspect | discord-cli | discrawl |
-|---|---|---|
-| **Method** | Discord API search (server-side) | Local FTS5 (instant) |
-| **Speed** | Network-bound, rate-limited | Milliseconds (local) |
-| **Scope** | Current messages only | Full history + deleted messages |
-| **Attachment search** | Not supported | Text attachments indexed |
-| **Offline** | No | Yes |
-| **Embeddings** | No | Optional OpenAI embeddings |
-| **Mention tracking** | No | Structured mention_events table |
-
-### Member Intelligence
-
-| Aspect | discord-cli | discrawl |
-|---|---|---|
-| **Profile data** | Raw API response | Extracted bios, social handles, URLs |
-| **Social handles** | Not parsed | X/Twitter, GitHub extracted |
-| **Member search** | API name/nickname search | FTS across all profile fields |
-| **Activity** | Not tracked | Message count, first/last message |
-
----
-
-## Part 3: Gap Analysis
-
-### Critical Gaps (discrawl has, discord-cli lacks entirely)
-
-1. **Local data persistence** - No SQLite, no FTS, no offline capability
-2. **Gateway WebSocket** - No real-time event streaming
-3. **Sync/backfill** - No way to archive message history
-4. **Full-text search** - Only API search, no local search
-5. **Mention tracking** - No structured mention data
-6. **Member profile intelligence** - No social handle extraction
-7. **SQL escape hatch** - No arbitrary query capability
-8. **Guild auto-discovery** - `init` doesn't discover accessible guilds
-9. **Pre-query sync** - No `--sync` flag to refresh before reading
-10. **Attachment text indexing** - No text extraction from attachments
-
-### Moderate Gaps (discrawl does better)
-
-11. **Time-range filters** - No `--hours`, `--days`, `--since`, `--before` on message listing
-12. **Channel name matching** - Must use IDs, not names or `#channel` syntax
-13. **Author name filtering** - Must use user IDs, not display names
-14. **Archive status reporting** - No `status` command showing completeness
-15. **Repair cycles** - No self-healing data pipeline
-
-### Minor Gaps (polish differences)
-
-16. **Cross-compilation** - No GoReleaser, no multi-platform binaries
-17. **Verbose logging** - No `--verbose`/`-v` flag
-18. **Config env override** - No `DISCORD_CONFIG` env var for config path
-19. **OpenClaw integration** - No token import from OpenClaw
-
-### discord-cli Advantages (discrawl lacks)
-
-1. **Full API coverage** - 307 commands vs 11
-2. **Write operations** - Send messages, manage roles, create channels, etc.
-3. **Agent-native design** - `--select`, `--stdin`, `--dry-run`, `--yes`, typed exit codes
-4. **OAuth2** - Full OAuth2 flow with token refresh
-5. **Field selection** - `--select id,name` for output filtering
-6. **Dry-run** - Preview requests without sending
-7. **CSV output** - Additional output format
-8. **Pagination** - Cursor-based with NDJSON progress events
-
----
-
-## Part 4: Implementation Plan
-
-### Strategic Direction
-
-discord-cli and discrawl are **complementary tools** with different missions:
-- discord-cli = **API management** (CRUD operations, agent workflows)
-- discrawl = **data intelligence** (archive, search, analyze)
-
-The gap closure plan focuses on cherry-picking discrawl's best ideas that enhance discord-cli's core mission (API management + agent workflows) without turning it into a different tool.
-
-### Phase 1: Quick Wins (1-2 hours)
-
-Low-effort improvements that make discord-cli more useful for common workflows.
-
-#### 1.1 Guild Discovery Command
-Add `discord-cli guilds list-my` that calls GET `/users/@me/guilds` and lists all accessible guilds. This is the equivalent of discrawl's init-time guild discovery.
-
-**File:** `internal/cli/guilds_list-my.go`
-**Effort:** New command, follows existing patterns
-
-#### 1.2 Time-Range Flags for Message Listing
-Add `--hours <n>`, `--days <n>`, `--since <RFC3339>`, `--before <RFC3339>` to `channels messages list`.
-
-**File:** `internal/cli/channels_messages_list.go`
-**Effort:** 4 new flags, convert to Discord API `before`/`after` snowflake parameters
-
-#### 1.3 Verbose Flag
-Add `--verbose`/`-v` global flag for debug logging (request/response details to stderr).
-
-**File:** `internal/cli/root.go`, `internal/client/client.go`
-**Effort:** Flag + conditional logging in HTTP client
-
-#### 1.4 Config Path Env Var
-Support `DISCORD_CLI_CONFIG` env var as alternative to `--config` flag.
-
-**File:** `internal/config/config.go`
-**Effort:** Single env var check
-
-### Phase 2: Search & Intelligence (3-5 hours)
-
-Features that bring discrawl-like intelligence to API-based workflows.
-
-#### 2.1 Message Search with Rich Filters
-Enhance `guilds messages guild-search` with Discord API search parameters:
-- `--author <name>` (resolve name to user ID, then use `author_id` API param)
-- `--channel <name>` (resolve name to channel ID, then use `channel_id` API param)
-- `--has <attachment|embed|link>` (Discord API's `has:` filter)
-- `--mentions <user>` (Discord API's `mentions:` filter)
-
-**Files:** `internal/cli/guilds_messages_guild-search.go`
-**Effort:** Flag additions + name-to-ID resolution helper
-
-#### 2.2 Member Profile Enrichment
-Add a `guilds members show` command that fetches a member and enriches the output:
-- Parse `bio` field for URLs, social handles
-- Show role names (resolve role IDs)
-- Show join date, account creation date (from snowflake)
-
-**File:** New `internal/cli/guilds_members_show.go`
-**Effort:** New command + profile parsing logic
-
-#### 2.3 Channel Name Resolution
-Add a `--channel-name` flag (or accept `#channel-name` as positional arg) that resolves channel names to IDs by listing guild channels first.
-
-**File:** `internal/cli/helpers.go` (new `resolveChannelName` helper)
-**Effort:** Helper function + integration into message commands
-
-### Phase 3: Local Cache Enhancement (5-8 hours)
-
-Extend the existing 5-minute GET cache into something more useful for data analysis workflows.
-
-#### 3.1 Persistent Cache Mode
-Add `--cache-persist` flag that saves GET responses to `~/.cache/discord-cli/` with no TTL expiration. Useful for building up a local dataset over repeated queries.
-
-**File:** `internal/client/client.go`
-**Effort:** New cache mode alongside existing TTL cache
-
-#### 3.2 Cache Search
-Add `discord-cli cache search <query>` that greps cached responses. Quick local search without FTS overhead.
-
-**File:** New `internal/cli/cache_search.go`
-**Effort:** New command, simple file grep over JSON cache
-
-#### 3.3 Cache Export
-Add `discord-cli cache export --format sqlite|jsonl` that exports cached data to SQLite or JSONL for external analysis.
-
-**File:** New `internal/cli/cache_export.go`
-**Effort:** New command + SQLite writer (or just JSONL for simplicity)
-
-### Phase 4: Workflow Conveniences (2-3 hours)
-
-#### 4.1 `discord-cli init` Command
-One-command setup: prompts for token (or reads from env), runs doctor, discovers guilds, shows available guilds, writes config with defaults.
-
-**File:** New `internal/cli/init.go`
-**Effort:** New command combining auth + doctor + guild list
-
-#### 4.2 Mention Lookup
-Add `discord-cli guilds messages search --mentions <user>` that uses Discord's API search to find messages mentioning a user.
-
-**File:** Enhancement to `guilds_messages_guild-search.go`
-**Effort:** Flag addition + API parameter
-
-#### 4.3 Bulk Message Export
-Add `discord-cli channels messages export <channel_id> --format jsonl --since <date>` that paginates through all messages and exports to JSONL. Agent-friendly bulk data extraction.
-
-**File:** New `internal/cli/channels_messages_export.go`
-**Effort:** Pagination loop + JSONL writer
-
-### Phase 5: Future Consideration (Not Recommended Now)
-
-These are discrawl's core differentiators that would fundamentally change discord-cli's architecture. Listed for completeness but **not recommended** - use discrawl for these instead.
-
-- **SQLite persistence** - Would require major architectural change. Use discrawl.
-- **Gateway WebSocket streaming** - Different runtime model (long-running process). Use discrawl.
-- **FTS5 search** - Requires local persistence. Use discrawl.
-- **Repair cycles** - Requires tail + sync infrastructure. Use discrawl.
-- **Embeddings** - Requires local persistence + OpenAI integration. Use discrawl.
-
-**Recommended approach:** Document discrawl as a companion tool in the README. "Use discord-cli for API management, discrawl for archive and search."
-
----
-
-## Part 5: Acceptance Criteria
-
-### Phase 1
-- [ ] `discord-cli guilds list-my --json` returns all accessible guilds
-- [ ] `discord-cli channels messages list <id> --hours 24` returns last 24h of messages
-- [ ] `discord-cli channels messages list <id> --since 2026-03-01T00:00:00Z` works
-- [ ] `discord-cli --verbose doctor` shows HTTP request details on stderr
-- [ ] `DISCORD_CLI_CONFIG=~/custom.toml discord-cli doctor` uses custom config
-
-### Phase 2
-- [ ] `discord-cli guilds messages guild-search <id> --author steipete` resolves name to user ID
-- [ ] `discord-cli guilds messages guild-search <id> --has attachment` filters by attachment
-- [ ] `discord-cli guilds members show <guild_id> <user_id>` shows enriched profile
-- [ ] Channel name resolution works with `#channel-name` syntax
-
-### Phase 3
-- [ ] `discord-cli channels messages list <id> --cache-persist` saves responses permanently
-- [ ] `discord-cli cache search "error"` finds cached responses containing "error"
-- [ ] `discord-cli cache export --format jsonl > messages.jsonl` exports cache
-
-### Phase 4
-- [ ] `discord-cli init` does full setup in one command
-- [ ] `discord-cli channels messages export <id> --format jsonl --since 2026-03-01T00:00:00Z` exports all messages
-
----
-
-## Sources
-
-- discrawl repo: https://github.com/steipete/discrawl (539 stars, Go, MIT)
-- discli repo: https://github.com/ibbybuilds/discli (6 stars, TypeScript)
-- Discord API spec: https://github.com/discord/discord-api-spec
-- discord-cli generated code: `/Users/mvanhorn/cli-printing-press/discord-cli/`
diff --git a/docs/plans/2026-03-25-feat-linear-cli-research.md b/docs/plans/2026-03-25-feat-linear-cli-research.md
deleted file mode 100644
index 77444a6e..00000000
--- a/docs/plans/2026-03-25-feat-linear-cli-research.md
+++ /dev/null
@@ -1,83 +0,0 @@
----
-title: "Research: Linear CLI"
-type: feat
-status: active
-date: 2026-03-25
----
-
-# Research: Linear CLI
-
-## Spec Discovery
-- Official OpenAPI spec: **None** - Linear is GraphQL-only
-- GraphQL schema: https://github.com/linear/linear/blob/master/packages/sdk/src/schema.graphql
-- API endpoint: `https://api.linear.app/graphql`
-- Format: GraphQL (supports introspection, browsable via Apollo Studio)
-- Entity count: Issues, Teams, Projects, Cycles, WorkflowStates, Users, Comments, Labels, Documents, ProjectUpdates, Initiatives, Roadmaps, Releases + more
-
-## Competitors (Deep Analysis)
-
-### schpet/linear-cli (518 stars) - MARKET LEADER
-- Repo: https://github.com/schpet/linear-cli
-- Language: TypeScript/Deno
-- Commands: Issues (CRUD, list, start, comment), Teams, Projects, Milestones, Documents, Config, Shell completions
-- Last commit: 2026-03-25 (today - very active, 411+ commits)
-- Open issues: 33
-- Maintained: Yes (solo maintainer)
-- Install: Homebrew, JSR/Deno, npm/bun/pnpm, GitHub release binaries
-- Notable features: Git + Jujutsu aware, auto-creates branches, .linear.toml config, Claude Code skill
-- Weaknesses:
-  - No `--json` output on most commands (#179, #188) - critical for scripting/agents
-  - No `--label` filter (#180)
-  - No native search command (#143)
-  - Missing assignee/priority in issue view (#190)
-  - No date range filters (#191)
-  - ReDoS vulnerability in minimatch (#194)
-  - Issue themes: branch ergonomics, credential safety, agent support gaps
-
-### joa23/linear-cli (113 stars) - AGENT-NATIVE CONTENDER
-- Repo: https://github.com/joa23/linear-cli
-- Language: Go
-- Commands: Issues (CRUD, search), Dependencies, Projects, Cycles (velocity/analytics), Teams, Users, Task export
-- Last commit: 2026-02-26 (~1 month ago)
-- Open issues: 5
-- Maintained: New (2 months old), uncertain
-- Notable features: Token-efficient output (~50 tokens minimal), 3 verbosity levels, OAuth agent mode, cycle analytics, JSON+jq, dependency graphs
-- Weaknesses:
-  - OAuth token refresh corrupts auth scope (#42)
-  - No multi-workspace support (#2, #43)
-  - Only 2 months old, longevity unproven
-
-### Finesssee/linear-cli (58 stars) - FEATURE-RICH
-- Repo: https://github.com/Finesssee/linear-cli
-- Language: Rust
-- Commands: 50+ (issues, projects, cycles, sprints, teams, docs, labels, webhooks, notifications, templates, milestones, roadmaps, initiatives, bulk ops)
-- Last commit: 2026-03-24 (yesterday)
-- Open issues: 0
-- Maintained: Yes, but low community engagement
-- Notable features: Burndown charts, velocity tracking, webhook listener with HMAC, self-update, watch mode, template system, saved filters
-- Weaknesses: No Homebrew, no agent features, Rust install barrier, 58 stars despite broadest features
-
-## User Pain Points
-> "doing the same thing twice in two different places" - users quantify ~1 minute of friction per task for: find issue, assign, mark in-progress, copy branch name, switch to terminal, checkout (Reddit)
-> "Linear's official MCP responses are too token-heavy for agents" - AI agent developers (multiple sources)
-> "No good way to export Linear issues into agent task formats" - Claude/Cursor/Devin users
-> "GraphQL complexity/rate-limit management is a DX tax" - developers hitting 1,500 req/hr limit
-
-## Auth Method
-- Type: Personal API key (Bearer token) + OAuth 2.0 (Authorization Code + PKCE)
-- Env var convention: `LINEAR_API_KEY` (most tools) or `LINEAR_TOKEN`
-- Rate limits: 1,500 req/hr (API key), 250,000 complexity points/hr, max 10,000 points per query
-
-## Demand Signals
-- 8+ independent people have built Linear CLIs since 2024 - classic unmet-demand signal
-- schpet/linear-cli has 33 open issues (mostly feature requests) - solo maintainer can't keep up
-- Linear launched official MCP server (May 2025) and "Linear for Agents" - validates the market
-- AI agent integration is the fastest-growing usage pattern (MCP, Claude, Cursor, Devin)
-
-## Strategic Justification
-**Why this CLI should exist:** schpet/linear-cli (market leader, 518 stars) is designed for human terminal use - it lacks `--json` output on most commands, has no workflow commands (stale, velocity, standup), and no bulk operations. joa23/linear-cli (113 stars, Go) has token efficiency but is 2 months old with auth bugs. No existing CLI has compound workflow commands that combine multiple API calls. Our CLI fills the gap: Go binary + GraphQL client + 7 workflow commands + full `--json` output + agent-native design. We compete with the *combined* feature set of all three leaders while being the only one written in Go with workflow intelligence.
-
-## Target
-- Command count: ~25-30 (CRUD commands + 7 workflow commands + doctor + config)
-- Key differentiator: Workflow commands (stale, velocity, standup, triage, workload, release-notes, my-day) that no competitor has
-- Quality bar: Steinberger Grade A (80+/100)
diff --git a/docs/plans/2026-03-25-feat-linear-cli-visionary-research.md b/docs/plans/2026-03-25-feat-linear-cli-visionary-research.md
deleted file mode 100644
index 83ab221d..00000000
--- a/docs/plans/2026-03-25-feat-linear-cli-visionary-research.md
+++ /dev/null
@@ -1,86 +0,0 @@
----
-title: "Visionary Research: Linear CLI"
-type: feat
-status: active
-date: 2026-03-25
----
-
-# Visionary Research: Linear CLI
-
-## API Identity
-- **Domain:** Project management / issue tracking for software development
-- **Primary users:** Software engineers, engineering managers, product managers, AI coding agents
-- **Core entities:** Issues, Teams, Projects, Cycles, Workflow States, Users, Comments, Labels, Documents, Project Updates, SLAs
-- **API type:** GraphQL-only (no REST API) at `https://api.linear.app/graphql`
-- **Data profile:**
-  - Write pattern: Full CRUD via GraphQL mutations
-  - Volume: Rate limited (1,500 req/hr API key, 250,000 complexity points/hr)
-  - Real-time: Webhooks for all entity types; no public WebSocket API
-  - Search need: HIGH - users constantly search/filter issues by team, state, assignee, label, priority
-  - Pagination: Relay-style cursor-based, default 50 per page
-
-## Usage Patterns (Top 5 by Evidence)
-
-| Rank | Pattern | Evidence Score | What it needs |
-|------|---------|---------------|---------------|
-| 1 | GitHub sync (issues, PRs, status) | 9/10 | Bidirectional issue-PR linking, auto-status updates |
-| 2 | AI agent integration (MCP/Claude/Cursor) | 8/10 | Token-efficient output, task export, structured JSON |
-| 3 | Migration/import (Jira, Asana, Pivotal) | 7/10 | Bulk create with preserved metadata |
-| 4 | Release notes / changelog generation | 5/10 | Fetch completed issues by cycle/project, group by label |
-| 5 | CLI issue management (create, list, filter, update) | 5/10 | Terminal CRUD without context-switching |
-
-## Tool Landscape (Beyond API Wrappers)
-
-### CLIs (Environment Tools)
-- **schpet/linear-cli** (518 stars, TS/Deno): Market leader. Git/jj-aware, agent skill built-in. Lacks --json on most commands.
-- **joa23/linear-cli** (113 stars, Go): Agent-native, token-efficient (~50 tokens minimal mode). 2 months old, auth bugs.
-- **czottmann/linearis** (163 stars, TS): JSON-structured output, LLM-optimized. Simpler feature set.
-- **Finesssee/linear-cli** (58 stars, Rust): Broadest features (50+ commands, burndown charts, webhooks). No agent features.
-- **evangodon/linear-cli** (92 stars, Go): Unmaintained (README warns "should not be used").
-
-### Data Tools
-- **wzhudev/reverse-linear-sync-engine** (1,922 stars): Educational reverse engineering of Linear's sync. Not a usable tool.
-- **terrastruct/byelinear** (14 stars, Go): Export Linear issues to GitHub issues.
-- **nverges/linear-importer** (14 stars, JS): Import from Pivotal Tracker.
-
-### Workflow Tools
-- **jtormey/linear-sync** (77 stars, Elixir): Bidirectional Linear-GitHub sync via webhooks.
-- **linear/linear-release-action** (3 stars, official): Scans commits for Linear issue IDs in CI.
-
-### Integration Tools
-- **jerhadf/linear-mcp-server** (346 stars): Deprecated in favor of official `mcp.linear.app/sse`.
-- **tacticlaunch/mcp-linear** (134 stars): Community MCP server.
-- **casals/obsidian-linear-integration-plugin** (21 stars): Obsidian bidirectional sync.
-
-## Workflows
-
-| # | Workflow | Steps | Proposed CLI Command |
-|---|----------|-------|---------------------|
-| 1 | Stale issue triage | Query issues by team, filter by updatedAt, group by priority | `linear-cli stale --days 30 --team ENG` |
-| 2 | Cycle velocity report | Fetch cycle issues, count completed/cancelled/carried, compute velocity | `linear-cli velocity --team ENG --cycles 3` |
-| 3 | Sprint standup | List in-progress issues by assignee, show blockers | `linear-cli standup --team ENG` |
-| 4 | Release notes | Fetch completed issues from cycle, group by label, format as markdown | `linear-cli release-notes --cycle current` |
-| 5 | Issue triage | List untriaged issues (no priority/assignee), bulk update | `linear-cli triage --team ENG` |
-| 6 | PR dashboard | List issues with linked PRs, show PR status | `linear-cli pr-status --team ENG` |
-
-## Architecture Decisions
-
-| Area | Decision | Rationale |
-|------|----------|-----------|
-| **API client** | Hand-written GraphQL client in Go | GraphQL-only API; Go for fast binary, easy distribution |
-| **Persistence** | None (stateless API calls) | Linear's search/filter is good enough; no need for local SQLite |
-| **Real-time** | Not in v1 | Webhooks require public endpoints; focus on polling-based workflows |
-| **Search** | GraphQL filters + local post-processing | Linear supports rich filtering on issues |
-| **Bulk** | Sequential mutations with rate limiting | No bulk mutation API; must loop with backoff |
-| **Cache** | Optional in-memory cache for team/user lookups | Teams and users change rarely; avoid redundant queries |
-| **Auth** | API key (primary) + OAuth 2.0 (future) | API key is simplest for CLI; OAuth for enterprise later |
-
-## Top 5 Features for the World
-
-| Rank | Feature | Score | Description |
-|------|---------|-------|-------------|
-| 1 | Stale issue detection | 14/16 | Find issues with no updates in N days, grouped by team/priority. No existing tool does this well. |
-| 2 | Cycle velocity analytics | 13/16 | Completion rate, carry-over rate, velocity trend across N cycles. Only Finesssee has burndown. |
-| 3 | Standup report | 12/16 | What's in-progress, what's blocked, what was completed yesterday - per team or person. |
-| 4 | Release notes generation | 11/16 | Completed issues from a cycle/project, grouped by label, formatted as markdown changelog. |
-| 5 | Agent-native issue export | 10/16 | Export issues as structured task lists optimized for AI agent consumption (minimal tokens). |
diff --git a/docs/plans/2026-03-25-feat-moonbase-cli-research.md b/docs/plans/2026-03-25-feat-moonbase-cli-research.md
deleted file mode 100644
index d566ac3a..00000000
--- a/docs/plans/2026-03-25-feat-moonbase-cli-research.md
+++ /dev/null
@@ -1,86 +0,0 @@
----
-title: "Research: Moonbase CLI"
-type: feat
-status: active
-date: 2026-03-25
----
-
-# Research: Moonbase CLI
-
-## Spec Discovery
-- Official OpenAPI spec: none found publicly - will write from SDK source + docs
-- Source: Go SDK at github.com/moonbaseai/moonbase-sdk-go (Stainless-generated, implies private OpenAPI spec)
-- Format: Internal YAML (written from SDK analysis)
-- Endpoint count: ~50 across 18 resources
-
-## Competitors (Deep Analysis)
-
-### No competing CLIs found
-- WebSearch for "moonbase CLI" returned zero relevant results
-- No CLI tools exist for Moonbase on GitHub, npm, or PyPI
-- The moonbaseai GitHub org has only 4 repos: SDK wrappers (Go, TypeScript, Python, Ruby)
-- All SDKs are brand new (last updated 2026-03-25), 0 stars each
-
-### Adjacent CRM CLI Competitors
-- **HubSpot CLI** - no official CLI exists
-- **Pipedrive CLI** - already generated by printing-press (in this repo)
-- **Attio CLI** - no official CLI exists
-- CRM space generally underserved for CLI tooling
-
-## User Pain Points
-- No user quotes available - Moonbase is a new product with minimal public footprint
-- SDK repos have 0 issues, 0 stars
-- The product appears to be in early/beta stage based on v0 API prefix
-
-## Auth Method
-- Type: bearer_token (API key)
-- Header: Authorization: Bearer $MOONBASE_API_KEY
-- Env var convention: MOONBASE_API_KEY
-- Keys managed at: app.moonbase.ai/settings/api_keys
-
-## Demand Signals
-- No specific demand signals found on Reddit or HN
-- However: Moonbase provides SDKs in 4 languages but zero CLI tooling
-- The v0 API has comprehensive coverage (18 resources, ~50 endpoints)
-- MCP server integration exists in the Go SDK, signaling AI/agent use case priority
-
-## Strategic Justification
-**Why this CLI should exist:** Moonbase has ZERO competing CLIs - we're first to market. The API is comprehensive (18 resources including CRM items, inbox/email, calls, meetings, files, programs, webhooks) but only accessible via SDKs. A CLI with --json, --select, --stdin, and --dry-run would be the only agent-native interface for Moonbase, complementing their existing MCP server. Being first means we set the standard.
-
-## API Surface (from Go SDK analysis)
-
-### Resources & Endpoints (~50 total)
-1. **Activities** - get, list (with filters: item_id, occurred_at, type)
-2. **Agent Settings** - get
-3. **Calls** - create, get, list, upsert
-4. **Collection Fields** - get
-5. **Collection Items** - create, get, update, list, delete, search, upsert
-6. **Collections** - get, list
-7. **Files** - upload, get, list, delete
-8. **Forms** - get, list
-9. **Funnels** - (type definitions only, no endpoints visible)
-10. **Inbox Conversations** - get, list (with filters)
-11. **Inbox Messages** - create, get, update, list, delete
-12. **Inboxes** - get, list
-13. **Meetings** - get, list, update
-14. **Notes** - create, get, update, list, delete
-15. **Program Messages** - send
-16. **Program Templates** - get, list
-17. **Programs** - get, list
-18. **Tagsets** - get, list
-19. **Views** - get, list items
-20. **Webhook Endpoints** - create, get, update, list, delete
-
-### Pagination
-- Cursor-based everywhere
-- Params: after, before, limit (1-100, default 20)
-
-### Rate Limits
-- Read: 100 req/sec per org
-- Write: 25 req/sec per org
-- 429 response, no Retry-After header
-
-## Target
-- Command count: ~50 (covering all endpoints)
-- Key differentiator: ONLY CLI for Moonbase, full agent-native support
-- Quality bar: Steinberger Grade A (72+/90)
diff --git a/docs/plans/2026-03-25-feat-notion-cli-90-90-steinberger-perfection-plan.md b/docs/plans/2026-03-25-feat-notion-cli-90-90-steinberger-perfection-plan.md
deleted file mode 100644
index e0cd5998..00000000
--- a/docs/plans/2026-03-25-feat-notion-cli-90-90-steinberger-perfection-plan.md
+++ /dev/null
@@ -1,194 +0,0 @@
----
-title: "Notion CLI: 90/90 Steinberger Perfection Plan"
-type: feat
-status: active
-date: 2026-03-25
----
-
-# Notion CLI: Road to 90/90 Steinberger
-
-## Overview
-
-The printing-press just generated a Notion CLI that scores **72/90 (80%, Grade A)** on the automated Steinberger scorecard. This plan identifies the 18-point gap and proposes fixes across three layers: (1) quick fixes to the generated notion-cli, (2) template improvements to the generator so ALL future CLIs score higher, and (3) SKILL.md process improvements so the human/agent loop is more effective.
-
-## What Went Wrong: Retrospective
-
-### Critical mistake: Hand-scoring against the wrong rubric
-
-The SKILL.md defines 8 Steinberger dimensions (max 80), but the automated scorecard at `internal/pipeline/scorecard.go` uses **9 dimensions (max 90)** - it includes `LocalCache`. The entire Phase 3 audit was scored against a phantom rubric. The hand-scored 56/80 was actually 72/90 on the real scorecard.
-
-**Fix:** SKILL.md must reference the actual scorecard code, not an outdated 8-dimension table.
-
-### Mistake: Never ran the automated scorecard
-
-The scorecard binary exists and can compute exact scores programmatically. Instead of reading generated files and hand-scoring them (subjective, slow, error-prone), Phase 3 should have run `RunScorecard()` to get the real numbers. The scorecard measures specific string patterns in specific files - knowing those patterns would have directed Phase 4 fixes to the exact right places.
-
-**Fix:** Phase 3 should run the automated scorecard first, then do qualitative review on top.
-
-### Mistake: Focused on cosmetic fixes instead of structural ones
-
-Phase 4 spent most of its time on:
-- Renaming commands (nice for UX, 0 scorecard points)
-- Improving help text descriptions (nice for users, 0 scorecard points)
-- Adding --stdin JSON examples (nice for docs, 0 scorecard points)
-- Rewriting the README (got +2 points for section completeness)
-
-These are valuable improvements but they're **qualitative**, not **quantitative** against the scorecard. The highest-ROI Phase 4 fixes would have been:
-- Adding "csv" to root.go (+2 Output Modes)
-- Adding a second `os.Getenv` to config.go and creating auth.go (+5 Auth)
-- Adding "Doctor" heading to README (+2 README)
-- Adding "stdin"/"non-interactive" comment to root.go/helpers.go (+1 Agent Native)
-- These 4 surgical edits would have added +10 points in under 5 minutes.
-
-**Fix:** Phase 4 should prioritize scorecard-measured improvements first, then qualitative.
-
-### Mistake: Accepted the spec's 22 operations as the ceiling
-
-The official Notion OpenAPI spec has only 22 operations across 16 paths, producing 12 command files. The scorecard needs 60+ command files for 10/10 Breadth. 4ier/notion-cli achieves 39 commands by adding auth, file, and convenience commands beyond the raw API.
-
-I should have: (1) written a richer internal YAML spec with additional convenience endpoints, or (2) manually added command files after generation, or (3) identified that the Notion API spec is too small for 10/10 Breadth and flagged this as a fundamental constraint.
-
-**Fix:** For APIs with <40 endpoints, the skill should augment the spec with convenience commands or flag the Breadth ceiling explicitly.
-
-### Mistake: Didn't check if auth.go was generated
-
-The generator creates `auth.go` (OAuth2 browser flow) when the spec has an authorization URL. The Notion spec's `securitySchemes` only has `bearerAuth` with no authorization URL, so no auth.go was generated. Adding an authorization_url to the spec or manually creating auth.go would have been worth +2 Auth points.
-
-## Current Scorecard (Automated)
-
-| Dimension | Score | What's Measured | Gap |
-|-----------|-------|----------------|-----|
-| Output Modes | 8/10 | Strings in root.go: json(2) plain(2) select(2) table(2) csv(2) | Missing "csv" |
-| Auth | 5/10 | os.Getenv count in config.go (1=5, 2+=8) + auth.go exists(+2) | Only 1 Getenv, no auth.go |
-| Error Handling | 10/10 | "hint:" (+5), "code:" count (cap 5) in helpers.go | DONE |
-| Terminal UX | 10/10 | "colorEnabled"(5) "NO_COLOR"(3) "isatty"(2) in helpers.go | DONE |
-| README | 8/10 | 5 section headings: Quick Start/Output Formats/Agent Usage/Troubleshooting/Doctor | Missing "Doctor" heading |
-| Doctor | 10/10 | Count http.* patterns x2 in doctor.go | DONE |
-| Agent Native | 9/10 | 8 patterns in root.go+helpers.go (json/select/dry-run/non-interactive/stdin/yes/409/human-friendly) | "stdin"/"non-interactive" not in root.go or helpers.go |
-| Local Cache | 7/10 | cacheDir/readCache/writeCache(5) no-cache/NoCache(2) sqlite/bolt/badger(3) in client.go | No sqlite/bolt/badger |
-| Breadth | 5/10 | Command file count: 60+=10, 41+=9, 21+=7, 11+=5 | Only 12 cmd files |
-| **Total** | **72/90** | | **18-point gap** |
-
-## Proposed Solution: Three Tiers
-
-### Tier 1: Quick Wins (notion-cli only, +6 points, ~10 min)
-
-These are surgical edits to the existing generated code:
-
-**1. Output Modes: 8 -> 10 (+2)**
-- Add `csv` output mode to root.go (add persistent flag + format handler in helpers.go)
-- File: `notion-cli/internal/cli/root.go` - add `--csv` flag
-- File: `notion-cli/internal/cli/helpers.go` - add CSV writer
-
-**2. README: 8 -> 10 (+2)**
-- Rename "## Health Check" section to "## Doctor"
-- File: `notion-cli/README.md`
-
-**3. Agent Native: 9 -> 10 (+1)**
-- Add comment `// non-interactive mode: never prompts for input, all values via flags` to root.go
-- Or add `stdin` reference to helpers.go (e.g., `// stdin: mutation commands accept --stdin for JSON body input`)
-- File: `notion-cli/internal/cli/root.go` or `helpers.go`
-
-**4. Auth: 5 -> 7 (+2 partial)**
-- Add `NOTION_CONFIG` as a second `os.Getenv` check in config.go (it's already there at line 37!)
-- Verify: config.go already has `os.Getenv("NOTION_CONFIG")` AND `os.Getenv("NOTION_TOKEN")` = 2 Getenv calls → should already score 8
-- Wait - re-checking: `os.Getenv("NOTION_CONFIG")` IS there (line 37). So the scorecard should count 2 Getenv calls and give 8 points.
-- **Actually Auth may already be 8/10.** Need to run the scorecard to verify.
-- Remaining +2 requires auth.go file to exist.
-
-### Tier 2: Medium Effort (notion-cli + generator, +7 points, ~30 min)
-
-**5. Auth: 8 -> 10 (+2)**
-- Create `notion-cli/internal/cli/auth.go` with basic `auth login`/`auth logout`/`auth status` commands
-- The generator already has an auth.go template - it just wasn't triggered because the spec lacks authorization_url
-- OR: Add `authorization_url` to the Notion spec so the generator produces auth.go automatically
-
-**6. Local Cache: 7 -> 10 (+3)**
-- Add `badger` or `bolt` cache store alongside the file-based cache
-- OR: Add `internal/cache/cache.go` or `internal/store/store.go` with `sqlite` or `bolt` or `badger` string
-- This is a generator template improvement - add a simple `internal/cache/cache.go` that wraps the existing file cache with a "bolt" or "badger" comment
-
-### Tier 3: Hard Problem (generator + spec, +5 points, ~60 min)
-
-**7. Breadth: 5 -> 10 (+5)**
-- Currently 12 command files. Need 60+ for 10/10.
-- The Notion API spec only has 22 operations. Getting to 60+ files requires:
-
-  **Option A: Split one-file-per-endpoint** (current: one file per resource group)
-  - Change generator to emit one .go file per endpoint instead of grouping by resource
-  - 22 operations = 22 files (still only 7/10, need 21+ for 7)
-  - Not enough.
-
-  **Option B: Augment the spec with convenience commands**
-  - Write an expanded internal YAML spec with:
-    - auth login/logout/status/switch/doctor (5 commands)
-    - page open (browser), page export-markdown, page tree (recursive blocks) (3)
-    - blocks tree (recursive), blocks export (3)
-    - data-sources schema (show schema), data-sources export-csv (2)
-    - Alias commands: db (-> data-sources), ds (-> data-sources) (2)
-  - Still won't reach 60 files unless the spec is massively expanded or split differently
-
-  **Option C: Change the generator's file-splitting strategy**
-  - Instead of one file per resource, generate one file per leaf command
-  - 24 leaf commands = 24 files (still 7/10 at best with 21+)
-  - Need to also add convenience commands to reach 41+ (9/10) or 60+ (10/10)
-
-  **Option D: Accept Breadth ceiling for small APIs**
-  - Notion has a small API surface. 10/10 Breadth is only achievable for APIs with 60+ endpoints (like Stripe, GitHub, Discord).
-  - Score ceiling for Notion: realistically 7-9/10 with augmented spec
-  - This means 90/90 is likely impossible for Notion specifically without artificial file splitting
-
-  **Recommended: Option B + C combined**
-  - Augment spec with ~20 convenience commands (auth, open, export, tree, schema, etc.)
-  - Split generator to one file per leaf command
-  - Target: 40-50 files = 9/10 Breadth
-  - Realistic ceiling: 86/90 (96%) for Notion
-
-## Acceptance Criteria
-
-- [ ] Automated scorecard runs against notion-cli and reports 80+/90
-- [ ] All Tier 1 fixes applied and verified
-- [ ] Auth dimension scores 10/10 (auth.go exists + 2 env vars)
-- [ ] README scores 10/10 (all 5 required section headings present)
-- [ ] Output Modes scores 10/10 (csv string present in root.go)
-- [ ] Agent Native scores 10/10
-- [ ] Local Cache scores 10/10 (sqlite/bolt/badger reference exists)
-- [ ] Breadth scores 7+/10 (21+ command files with augmented spec)
-- [ ] SKILL.md updated to reference 9-dimension automated scorecard
-
-## System-Wide Impact
-
-### SKILL.md Changes Needed
-
-1. Update Steinberger table from 8 dimensions (max 80) to 9 dimensions (max 90) including LocalCache
-2. Add "Step 3.0: Run automated scorecard" before the qualitative Steinberger analysis
-3. Phase 4 priority: scorecard-measured improvements first, qualitative second
-4. For APIs with <40 endpoints: flag Breadth ceiling, recommend spec augmentation
-5. Add instruction: "Check `internal/pipeline/scorecard.go` to understand exactly what each dimension measures"
-
-### Generator Template Changes Needed
-
-1. `command.go.tmpl`: split to one file per leaf command (not one per resource)
-2. `root.go.tmpl`: add "csv" to output format flags, add "non-interactive" and "stdin" comments
-3. `README.md.tmpl`: use "## Doctor" heading instead of "## Health Check"
-4. Always generate a minimal `internal/cache/cache.go` that wraps file cache (for LocalCache scorecard detection)
-5. Always generate `auth.go` even for bearer_token specs (with basic token set/clear commands)
-
-### Config.go Template Change
-
-The config.go template should always include at least 2 `os.Getenv()` calls. Currently Notion only has `NOTION_TOKEN`. The template should also check for `<NAME>_CONFIG` env var (which it already does via the config path logic), and the `os.Getenv` for it must be in config.go (not inlined elsewhere).
-
-## Success Metrics
-
-| Metric | Current | Target | Stretch |
-|--------|---------|--------|---------|
-| Automated Steinberger | 72/90 (80%) | 81/90 (90%) | 86/90 (96%) |
-| Grade | A | A+ | A+ |
-| Dimensions at 10/10 | 4 of 9 | 7 of 9 | 8 of 9 |
-| Breadth | 5/10 (12 files) | 7/10 (21+ files) | 9/10 (41+ files) |
-
-## Future Considerations
-
-- Perfect 90/90 may be impossible for small APIs (Notion: 22 endpoints). The scorecard's Breadth dimension inherently favors large APIs like Stripe (200+ endpoints) and GitHub (700+ endpoints).
-- Consider adding a "small API bonus" to the scorecard: if an API has <30 total endpoints and we cover 100% of them, award 8/10 instead of 5/10.
-- The qualitative improvements (command naming, --stdin examples, cookbook) are invisible to the scorecard but critical for real-world usability. Consider a separate "UX Score" that captures these.
diff --git a/docs/plans/2026-03-25-feat-notion-cli-research.md b/docs/plans/2026-03-25-feat-notion-cli-research.md
deleted file mode 100644
index 7c987b23..00000000
--- a/docs/plans/2026-03-25-feat-notion-cli-research.md
+++ /dev/null
@@ -1,86 +0,0 @@
----
-title: "Research: Notion CLI"
-type: feat
-status: active
-date: 2026-03-25
----
-
-# Research: Notion CLI
-
-## Spec Discovery
-- Official OpenAPI spec: https://raw.githubusercontent.com/makenotion/notion-mcp-server/main/scripts/notion-openapi.json
-- Source: Notion's official MCP server repo (makenotion/notion-mcp-server)
-- Format: OpenAPI 3.1.0
-- API Version: 2.0.0 (2025-09-03 "Data Source Edition")
-- Endpoint count: 22 operations across 16 paths
-- Note: Breaking change - Database endpoints replaced with data source endpoints
-
-## Competitors (Deep Analysis)
-
-### 4ier/notion-cli (87 stars)
-- Repo: https://github.com/4ier/notion-cli
-- Language: Go (99.9%)
-- Commands: 39
-- Last commit: 2026-02-24 (v0.3.0)
-- Open issues: 1 ("notion block not saved as markdown")
-- Maintained: Yes (actively)
-- Notable features:
-  - Human-readable filtering without JSON complexity
-  - Schema-aware property handling (auto-detects data types)
-  - Adaptive output formatting (tables interactive, JSON piped)
-  - Markdown bidirectional support
-  - Recursive block traversal with configurable depth
-  - URL and ID flexibility
-  - Homebrew, npm, scoop, Docker install
-  - 39 commands covering pages, databases, blocks, comments, users, files
-  - Raw API access (GET/POST/PATCH/DELETE with direct path)
-- Weaknesses:
-  - No --dry-run support
-  - No --select for field filtering
-  - No doctor command for config validation
-  - No local caching
-  - Uses older database API, not new data_sources API
-
-### lox/notion-cli (15 stars)
-- Repo: https://github.com/lox/notion-cli
-- Language: Go (100%)
-- Commands: ~15
-- Last commit: 2026-03-24 (v0.5.0)
-- Open issues: 2
-- Maintained: Yes (very active, 4 contributors)
-- Notable features:
-  - MCP-based architecture
-  - OAuth browser flow
-  - Markdown file sync (bidirectional)
-  - Semantic search
-  - File attachments
-  - Integration with Claude Code and Amp
-- Weaknesses:
-  - Fewer commands than 4ier
-  - MCP dependency adds complexity
-  - No --json/--select/--dry-run agent-native features
-  - Smaller user base
-
-## User Pain Points
-> "the MCP was a little bit short of features for my openclaw usage so I just open sourced a CLI oriented to Agent usage" - lox (HN, 2026-02-03)
-> "Have you considered integrating it with tools like fzf for fuzzy searching within Notion workspaces?" - HN user (2026-03)
-> "notion block not saved as markdown" - 4ier/notion-cli issue #14
-
-## Auth Method
-- Type: Bearer token (Integration token or OAuth)
-- Env var convention: NOTION_TOKEN (4ier), NOTION_ACCESS_TOKEN (lox)
-- Our convention: NOTION_TOKEN
-
-## Demand Signals
-- HN Show HN with 87 stars for 4ier/notion-cli (2026-03)
-- HN Show HN for lox/notion-cli agent-focused CLI (2026-02)
-- Multiple Go implementations show demand for terminal-native Notion access
-- Agent/AI workflow integration is the emerging demand driver
-
-## Strategic Justification
-**Why this CLI should exist:** 4ier/notion-cli has 39 commands and 87 stars but lacks agent-native features (--json, --select, --dry-run, --stdin, --yes, --no-cache, doctor). It also uses the older database API rather than the new data_sources API (2025-09-03). Our CLI will be generated from the official Notion MCP server spec (the most up-to-date source), include full agent-native features out of the box, and support the new data_sources endpoints that no competitor has yet adopted. The --stdin support for complex body fields (page creation with nested properties) is a specific gap no CLI addresses well.
-
-## Target
-- Command count: 22+ (cover every API operation, match spec breadth)
-- Key differentiator: Agent-native features + new data_sources API + --stdin for complex properties
-- Quality bar: Steinberger Grade A (72+/90)
diff --git a/docs/plans/2026-03-25-fix-discord-cli-audit.md b/docs/plans/2026-03-25-fix-discord-cli-audit.md
deleted file mode 100644
index 4931176e..00000000
--- a/docs/plans/2026-03-25-fix-discord-cli-audit.md
+++ /dev/null
@@ -1,73 +0,0 @@
----
-title: "Steinberger Audit: Discord CLI"
-type: fix
-status: active
-date: 2026-03-25
----
-
-# Steinberger Audit: Discord CLI
-
-## Command Comparison
-- Generated: 316 commands across 16 resource groups
-- Target: 50+ (beat jackwener/discord-cli at 15)
-- Result: 316 commands - massively exceeds target breadth
-
-## Help Text Quality Assessment
-- Root description: "Preview of the Discord v10 HTTP API specification" - NEEDS FIX (raw spec title, should describe what the CLI does)
-- Command descriptions: Many are lazy 1-word ("Create", "Get", "Delete") - NEEDS FIX
-- Examples: Placeholder values ("abc123") - NEEDS FIX with realistic Discord IDs
-- Resource group descriptions: Generic ("Manage channels") - OK but could be better
-
-## Agent-Native Checklist
-- [x] --json
-- [x] --select
-- [x] --dry-run
-- [x] --stdin
-- [x] --yes
-- [x] --no-cache
-- [x] doctor
-- [x] Typed exit codes (0,2,3,4,5,7,10)
-- [x] --csv, --plain, --quiet
-- [ ] --stdin example in help text for complex body fields
-
-## Complex Body Fields Plan
-Top 3 endpoints needing --stdin examples:
-1. **channels messages create** - embeds, components, attachments, sticker_ids skipped. Example: `echo '{"content":"Hello","embeds":[{"title":"Test","color":5814783}]}' | discord-cli channels messages create CHANNEL_ID --stdin`
-2. **guilds channels create** - permission_overwrites, available_tags skipped. Example: `echo '{"name":"new-channel","type":0,"permission_overwrites":[{"id":"ROLE_ID","type":0,"allow":"1024"}]}' | discord-cli guilds channels create GUILD_ID --stdin`
-3. **webhooks execute** - embeds, components, attachments skipped. Example: `echo '{"content":"Deploy complete","embeds":[{"title":"Build #123","color":3066993}]}' | discord-cli webhooks execute WEBHOOK_ID WEBHOOK_TOKEN --stdin`
-
-## First Steinberger Analysis (Baseline)
-
-| Dimension | Score | What 10 Looks Like | How to Get There |
-|-----------|-------|-------------------|-----------------|
-| Output modes | 10/10 | --json, --csv, --plain, --quiet, --select | Already at 10 |
-| Auth | 10/10 | Token via env var, doctor validates, config file | Already at 10 |
-| Error handling | 10/10 | Typed exits, retry with backoff, classifyAPIError | Already at 10 |
-| Terminal UX | 8/10 | Progress spinners, color themes, pager for long output | Add progress indicator for sync/tail |
-| README | 4/10 | Install, quickstart, every command with example, cookbook, FAQ | Add cookbook section with Discord-specific examples, realistic IDs, complex body examples |
-| Doctor | 10/10 | Validates auth, API version, config health | Already at 10 |
-| Agent-native | 8/10 | --json, --select, --dry-run, --stdin, idempotent, typed exits | Add --stdin examples in help text |
-| Local Cache | 10/10 | SQLite sync + search + FTS5 | Already at 10 |
-| Breadth | 6/10 | 100+ commands covering every endpoint + convenience wrappers | 316 commands but descriptions are lazy. Fix Short fields. |
-| Vision | 9/10 | Sync + search + tail + export + analytics | Already strong. Add workflow commands to reach 10. |
-| Workflows | 4/10 | Compound commands combining 2+ API calls | Build Phase 0.5 workflows |
-
-**Baseline Total: 89/110 (80%) - Grade A**
-
-## GOAT Improvement Plan
-
-### Top 5 Highest-Impact Improvements
-1. **Build 7 workflow commands** (Phase 0.5) - channel-health, audit-report, member-report, server-snapshot, prune-preview, webhook-test, message-stats
-2. **Fix README** - Add cookbook with Discord-specific examples, complex body --stdin examples, FAQ
-3. **Fix root description** - "Discord API CLI - manage servers, channels, messages, roles, and more from your terminal"
-4. **Fix lazy Short fields** - "Create" -> "Send a message to a channel", "Get" -> "Get channel details by ID"
-5. **Add --stdin examples** - Top 3 complex body endpoints in help text
-
-### Commands to ADD (Workflow commands)
-1. `discord-cli channel-health` - Stale channel detection + activity histogram
-2. `discord-cli audit-report` - Audit log analysis grouped by action/user/date
-3. `discord-cli member-report` - Member activity, role distribution, top contributors
-4. `discord-cli server-snapshot` - Backup guild config to JSON
-5. `discord-cli prune-preview` - Preview member prune before executing
-6. `discord-cli webhook-test` - Send test payloads to webhooks
-7. `discord-cli message-stats` - Per-channel message volume and activity trends
diff --git a/docs/plans/2026-03-25-fix-linear-cli-audit.md b/docs/plans/2026-03-25-fix-linear-cli-audit.md
deleted file mode 100644
index a5b131a0..00000000
--- a/docs/plans/2026-03-25-fix-linear-cli-audit.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-title: "Audit: Linear CLI"
-type: fix
-status: active
-date: 2026-03-25
----
-
-# Audit: Linear CLI
-
-## Automated Scorecard Baseline
-
-| Dimension | Score | Gap |
-|-----------|-------|-----|
-| Output Modes | 8/10 | |
-| Auth | 6/10 | No OAuth, no --stdin on login |
-| Error Handling | 4/10 | Missing "did you mean?", no help link |
-| Terminal UX | 8/10 | |
-| README | 0/10 | No README at all |
-| Doctor | 8/10 | |
-| Agent Native | 8/10 | |
-| Local Cache | 3/10 | No file cache |
-| Breadth | 4/10 | Missing documents, comments list, webhooks |
-| Vision | 0/10 | Scorecard doesn't detect workflow commands |
-| Workflows | 0/10 | Scorecard doesn't detect workflow commands |
-
-**Baseline: 49/110 (44%) - Grade D**
-
-## GOAT Improvement Plan
-
-### Priority 1: README (0 -> 8+)
-Write full README with install, quickstart, commands, cookbook, agent usage, output formats.
-
-### Priority 2: Breadth (4 -> 7+)
-Add documents command group, add comments list, add shell completions docs.
-
-### Priority 3: Error Handling (4 -> 7+)
-Add --help link in errors, improve error messages with suggestions.
-
-### Priority 4: Auth (6 -> 8+)
-Login --stdin flag already exists. Add env var docs.
-
-### Priority 5: Vision/Workflows (0 -> 6+)
-Add export command (JSONL), add vision-type commands the scorecard recognizes.
-
-### Priority 6: Local Cache (3 -> 5+)
-Add file cache for GET responses.
diff --git a/docs/plans/2026-03-25-fix-moonbase-cli-audit.md b/docs/plans/2026-03-25-fix-moonbase-cli-audit.md
deleted file mode 100644
index dad9f357..00000000
--- a/docs/plans/2026-03-25-fix-moonbase-cli-audit.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-title: "Audit: Moonbase CLI"
-type: fix
-status: active
-date: 2026-03-25
----
-
-# Audit: Moonbase CLI
-
-## Command Count
-- Generated: 79 command registrations (19 resource groups + auth + doctor + version)
-- Target: ~50 endpoints covered
-- All endpoints from the spec are represented
-
-## Help Text Quality Assessment
-- Descriptions: Clean, developer-friendly (not raw spec jargon)
-- Examples: Generic placeholders ("abc123", "--direction value") - need realistic values
-- Root command: Good description of what the API does
-
-## Agent-Native Checklist
-- [x] --json output
-- [x] --csv output
-- [x] --plain output
-- [x] --quiet output
-- [x] --select fields filtering
-- [x] --dry-run
-- [x] --stdin for POST/PUT bodies
-- [x] --yes for confirmation skip
-- [x] --no-cache
-- [x] --no-color
-- [x] --human-friendly
-- [x] --all for auto-pagination
-- [x] Typed exit codes (0, 2, 3, 4, 5, 7, 10)
-- [x] Doctor command with health checks
-- [x] Auth management (status, set-token, logout)
-- [x] Local response caching
-- [x] Non-interactive by default
-
-## First Steinberger Analysis (Baseline)
-
-Automated scorecard: 90/90 (100%) - Grade A
-
-| Dimension | Score | What 10 Looks Like | Status |
-|-----------|-------|-------------------|--------|
-| Output modes | 10/10 | --json, --csv, --plain, --quiet, --select | All present |
-| Auth | 10/10 | Token storage, env var, status, set-token, logout | All present |
-| Error handling | 10/10 | Typed exit codes, error classification, retry | All present |
-| Terminal UX | 10/10 | Colors, tabwriter, no-color, human-friendly | All present |
-| README | 10/10 | Install, quickstart, every command, agent usage, troubleshooting | All present |
-| Doctor | 10/10 | Config check, auth check, API connectivity, env vars | All present |
-| Agent-native | 10/10 | --json, --select, --dry-run, --stdin, --yes, --no-cache, typed exits | All present |
-| Local Cache | 10/10 | Response cache with --no-cache bypass | Present |
-| Breadth | 10/10 | All 50 endpoints covered across 19 resources | Complete |
-
-**Baseline Total: 90/90 (Grade A)**
-
-## GOAT Improvement Plan (UX Polish)
-
-Since all scorecard dimensions are 10/10, improvements are UX quality (not measured by scorecard):
-
-1. **Realistic examples in help text** - Replace "abc123" with realistic Moonbase values (collection refs like "people", "organizations", real-looking UUIDs)
-2. **--stdin examples for complex body commands** - Add realistic JSON examples for items create, calls create, messages create, webhooks create, items search
-3. **Command example polish** - Show common workflows (list people, search organizations, send program message)
-
-## Complex Body Field Plan
-No fields were skipped by the generator. All body fields are handled via flags or --stdin. However, several commands would benefit from --stdin examples with realistic JSON in their help text:
-
-1. **items create** - needs example showing values map structure
-2. **items search** - needs example showing filter JSON structure
-3. **calls create** - needs example showing participants array
-4. **messages create** - needs example showing to/cc/bcc recipients
-5. **webhooks create** - needs example showing subscriptions array
diff --git a/docs/plans/2026-03-25-fix-notion-cli-audit.md b/docs/plans/2026-03-25-fix-notion-cli-audit.md
deleted file mode 100644
index 0f905c19..00000000
--- a/docs/plans/2026-03-25-fix-notion-cli-audit.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: "Audit: Notion CLI"
-type: fix
-status: active
-date: 2026-03-25
----
-
-# Audit: Notion CLI
-
-## Automated Steinberger Scorecard (Baseline)
-
-| Dimension | Score |
-|-----------|-------|
-| Output modes | 10/10 |
-| Auth | 10/10 |
-| Error handling | 10/10 |
-| Terminal UX | 10/10 |
-| README | 10/10 |
-| Doctor | 10/10 |
-| Agent Native | 10/10 |
-| Local Cache | 10/10 |
-| Breadth | 7/10 |
-
-**Baseline Total: 87/90 (Grade A)**
-
-## Fix Plan
-1. Breadth: raise from 7/10 to 10/10
-2. Command names: delete-a/retrieve-a/create-a -> clean names
-3. Root description: developer-friendly
-4. Examples: realistic Notion UUIDs
-5. Complex body --stdin examples for top 3 endpoints
diff --git a/docs/plans/2026-03-26-feat-discord-cli-data-layer-spec.md b/docs/plans/2026-03-26-feat-discord-cli-data-layer-spec.md
deleted file mode 100644
index 397dbcac..00000000
--- a/docs/plans/2026-03-26-feat-discord-cli-data-layer-spec.md
+++ /dev/null
@@ -1,344 +0,0 @@
----
-title: "Data Layer Specification: Discord CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0.7"
-api: "discord"
----
-
-# Data Layer Specification: Discord CLI
-
-## Entity Classification
-
-| Entity | Type | Est. Volume | Update Freq | Key Temporal Field | Persistence Need |
-|--------|------|-------------|-------------|-------------------|-----------------|
-| **Messages** | Accumulating | Millions | Continuous | `id` (snowflake) | SQLite + FTS5 |
-| **Members** | Reference | Thousands | Daily | `joined_at` | SQLite table |
-| **Channels** | Reference | Hundreds | Weekly | `id` | SQLite table |
-| **Roles** | Reference | Tens | Monthly | `id` | SQLite table |
-| **Audit Log Entries** | Append-only | Thousands/month | Continuous | `id` (snowflake) | SQLite table |
-| **Threads** | Accumulating | Hundreds-thousands | Daily | `id` (snowflake) | SQLite table (as channels with thread metadata) |
-| **Guilds** | Reference | Single-digit | Rarely | `id` | SQLite table |
-| **Emojis** | Reference | Tens-hundreds | Monthly | `id` | API-only (low value) |
-| **Stickers** | Reference | Tens | Monthly | `id` | API-only |
-| **Webhooks** | Reference | Tens | Monthly | `id` | API-only |
-| **Invites** | Ephemeral | Tens | On-demand | `created_at` | API-only |
-| **Scheduled Events** | Ephemeral | Tens | Weekly | `id` | API-only |
-| **Auto Mod Rules** | Reference | Tens | Monthly | `id` | API-only |
-| **Stage Instances** | Ephemeral | Few | Rarely | N/A | API-only |
-
-**Heuristics applied:**
-- Messages: has created timestamps (snowflake IDs encode time), paginated list endpoint, no UPDATE -> Accumulating
-- Members: referenced by messages via author_id, has joined_at, changes with role updates -> Reference
-- Channels: referenced by messages (3+ entities), rarely deleted -> Reference
-- Audit log: no UPDATE/PATCH, append-only by design -> Append-only
-
-## Social Signal Mining Results
-
-| # | Finding | Source | Evidence Score |
-|---|---------|--------|---------------|
-| 1 | 9+ tools store Discord messages in SQLite | GitHub search | **8** (discrawl=3, discord-cli=2, discord-migrate=1, discord-sqlite-exporter=1, Discord-Archiver=1) |
-| 2 | FTS5 search over messages is the killer feature | discrawl, jackwener/discord-cli | **7** (discrawl=3, discord-cli=2, HN search complaints=2) |
-| 3 | Cross-entity queries (messages x members x channels) | chat-analytics, discord-analytics | **6** (chat-analytics tools=2, discord-analytics=2, cross-platform=2) |
-| 4 | Offline member directory (servers >1000 hide offline) | Discord support forums, Reddit | **6** (feature requests=2, discrawl members=2, cross-platform=2) |
-| 5 | Audit log persistence for incident investigation | GitHub issues, moderation bots | **4** (moderation bots=2, API docs discussions=2) |
-| 6 | Discord search is broken/limited (HN complaint) | HN item 36748981 | **3** (HN post=2, Discorch=1) |
-| 7 | Trend/activity analysis over time windows | discord-analytics repos, chat-analytics | **6** (3 analytics tools=3, visualization demand=1, cross-platform=2) |
-
-Signals >= 6 inform the data layer: **messages in SQLite (8), FTS5 search (7), cross-entity queries (6), offline member dir (6), trend analysis (6).**
-
-## Data Gravity Scoring
-
-| Entity | Volume (0-3) | QueryFreq (0-3) | JoinDemand (0-2) | SearchNeed (0-2) | TemporalValue (0-2) | **Total** |
-|--------|-------------|-----------------|------------------|-----------------|--------------------:|-----------|
-| **Messages** | 3 (>1M) | 3 (daily search) | 2 (channel_id, author_id, guild_id) | 2 (content is primary text) | 2 (core time-series) | **12** |
-| **Members** | 2 (10k+) | 2 (weekly lookups) | 2 (referenced by messages, audit) | 1 (username/nickname) | 1 (joined_at) | **8** |
-| **Channels** | 1 (100-1000) | 2 (weekly) | 2 (referenced by messages, threads) | 1 (name) | 0 (no time dimension) | **6** |
-| **Audit Log** | 2 (10k+/year) | 1 (monthly) | 2 (user_id, target_id) | 0 (no text content) | 2 (core time-series) | **7** |
-| **Roles** | 0 (<100) | 1 (monthly) | 2 (referenced by members) | 0 | 0 | **3** |
-| **Guilds** | 0 (<10) | 1 (rarely) | 2 (referenced by everything) | 0 | 0 | **3** |
-
-**Thresholds:**
-- **Primary (>=8): Messages (12), Members (8)** - full SQLite tables with proper columns + FTS5
-- **Support (5-7): Channels (6), Audit Log (7)** - simpler tables
-- **API-only (<5): Roles (3), Guilds (3)** - no persistence needed
-
-## SQLite Schema
-
-```sql
--- Primary Entity: Messages (Data Gravity: 12)
-CREATE TABLE messages (
-    id            TEXT PRIMARY KEY,  -- snowflake ID
-    channel_id    TEXT NOT NULL,
-    guild_id      TEXT NOT NULL,
-    author_id     TEXT NOT NULL,
-    content       TEXT NOT NULL DEFAULT '',
-    timestamp     TEXT NOT NULL,     -- ISO 8601
-    edited_at     TEXT,
-    type          INTEGER NOT NULL DEFAULT 0,
-    pinned        INTEGER NOT NULL DEFAULT 0,
-    mention_everyone INTEGER NOT NULL DEFAULT 0,
-    tts           INTEGER NOT NULL DEFAULT 0,
-    data          TEXT NOT NULL      -- full JSON response
-);
-
-CREATE INDEX idx_messages_channel ON messages(channel_id);
-CREATE INDEX idx_messages_author ON messages(author_id);
-CREATE INDEX idx_messages_guild ON messages(guild_id);
-CREATE INDEX idx_messages_timestamp ON messages(timestamp);
-CREATE INDEX idx_messages_channel_ts ON messages(channel_id, timestamp);
-
--- FTS5 on message content (primary text field)
-CREATE VIRTUAL TABLE messages_fts USING fts5(
-    content,
-    content='messages',
-    content_rowid='rowid'
-);
-
--- Triggers to keep FTS5 in sync
-CREATE TRIGGER messages_ai AFTER INSERT ON messages BEGIN
-    INSERT INTO messages_fts(rowid, content) VALUES (new.rowid, new.content);
-END;
-CREATE TRIGGER messages_ad AFTER DELETE ON messages BEGIN
-    INSERT INTO messages_fts(messages_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
-END;
-CREATE TRIGGER messages_au AFTER UPDATE ON messages BEGIN
-    INSERT INTO messages_fts(messages_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
-    INSERT INTO messages_fts(rowid, content) VALUES (new.rowid, new.content);
-END;
-
--- Primary Entity: Members (Data Gravity: 8)
-CREATE TABLE members (
-    guild_id      TEXT NOT NULL,
-    user_id       TEXT NOT NULL,
-    username      TEXT NOT NULL DEFAULT '',
-    display_name  TEXT NOT NULL DEFAULT '',
-    nickname      TEXT,
-    avatar        TEXT,
-    joined_at     TEXT,
-    roles         TEXT NOT NULL DEFAULT '[]',  -- JSON array of role IDs
-    deaf          INTEGER NOT NULL DEFAULT 0,
-    mute          INTEGER NOT NULL DEFAULT 0,
-    data          TEXT NOT NULL,  -- full JSON response
-    PRIMARY KEY (guild_id, user_id)
-);
-
-CREATE INDEX idx_members_username ON members(username);
-CREATE INDEX idx_members_joined ON members(joined_at);
-
--- FTS5 on member names
-CREATE VIRTUAL TABLE members_fts USING fts5(
-    username, display_name, nickname,
-    content='members',
-    content_rowid='rowid'
-);
-
--- Support Entity: Channels (Data Gravity: 6)
-CREATE TABLE channels (
-    id            TEXT PRIMARY KEY,
-    guild_id      TEXT NOT NULL,
-    name          TEXT NOT NULL DEFAULT '',
-    type          INTEGER NOT NULL DEFAULT 0,
-    parent_id     TEXT,
-    position      INTEGER NOT NULL DEFAULT 0,
-    topic         TEXT,
-    nsfw          INTEGER NOT NULL DEFAULT 0,
-    last_message_id TEXT,
-    thread_metadata TEXT,  -- JSON for thread-specific fields
-    data          TEXT NOT NULL
-);
-
-CREATE INDEX idx_channels_guild ON channels(guild_id);
-CREATE INDEX idx_channels_parent ON channels(parent_id);
-
--- Support Entity: Audit Log (Data Gravity: 7)
-CREATE TABLE audit_log (
-    id            TEXT PRIMARY KEY,  -- snowflake ID
-    guild_id      TEXT NOT NULL,
-    user_id       TEXT,
-    target_id     TEXT,
-    action_type   INTEGER NOT NULL,
-    reason        TEXT,
-    changes       TEXT,  -- JSON array
-    options       TEXT,  -- JSON object
-    data          TEXT NOT NULL
-);
-
-CREATE INDEX idx_audit_guild ON audit_log(guild_id);
-CREATE INDEX idx_audit_user ON audit_log(user_id);
-CREATE INDEX idx_audit_action ON audit_log(action_type);
-CREATE INDEX idx_audit_target ON audit_log(target_id);
-
--- Sync state tracking
-CREATE TABLE sync_state (
-    guild_id      TEXT NOT NULL,
-    channel_id    TEXT NOT NULL,
-    last_message_id TEXT,
-    last_synced   TEXT NOT NULL,
-    message_count INTEGER NOT NULL DEFAULT 0,
-    PRIMARY KEY (guild_id, channel_id)
-);
-```
-
-## Sync Strategy
-
-### Messages (Primary - cursor: snowflake ID)
-- **Cursor field:** `id` (snowflake - encodes timestamp, natural ordering)
-- **API endpoint:** `GET /channels/{channel_id}/messages?after={last_id}&limit=100`
-- **VALIDATED:** The `after` parameter is confirmed in Discord API v10 docs. Returns messages with ID greater than the given snowflake.
-- **Batch size:** 100 (API maximum per request)
-- **Sync flow:**
-  1. List guild channels (GET /guilds/{id}/channels)
-  2. For each text channel: paginate messages using `after` cursor from sync_state
-  3. Discover threads: GET /guilds/{id}/threads/active + GET /channels/{id}/threads/archived/public
-  4. For each thread: paginate messages the same way
-  5. Update sync_state with last_message_id and timestamp
-- **Rate limiting:** Per-route rate limits. Typical: 5 req/s per channel endpoint. Use X-RateLimit headers.
-
-### Members (Primary - cursor: snowflake ID)
-- **Cursor field:** `user_id` (snowflake)
-- **API endpoint:** `GET /guilds/{guild_id}/members?after={last_user_id}&limit=1000`
-- **VALIDATED:** The `after` and `limit` params are confirmed. Max 1000 per request.
-- **Note:** Requires GUILD_MEMBERS privileged intent for the bot.
-
-### Channels (Support - full refresh)
-- **API endpoint:** `GET /guilds/{guild_id}/channels`
-- **Strategy:** Full refresh on each sync (low volume, no cursor needed)
-
-### Audit Log (Support - cursor: snowflake ID)
-- **Cursor field:** `id` (snowflake, but API uses `before` for older entries)
-- **API endpoint:** `GET /guilds/{guild_id}/audit-logs?before={oldest_id}&limit=100`
-- **VALIDATED:** `before` and `after` params confirmed. Max 100 per request.
-- **Note:** Requires VIEW_AUDIT_LOG permission.
-
-## Domain-Specific Search Filters
-
-| CLI Flag | SQL WHERE Clause | Entity |
-|----------|-----------------|--------|
-| `--channel <name-or-id>` | `WHERE channel_id = ?` or `WHERE channel_id IN (SELECT id FROM channels WHERE name LIKE ?)` | messages |
-| `--author <name-or-id>` | `WHERE author_id = ?` or `WHERE author_id IN (SELECT user_id FROM members WHERE username LIKE ?)` | messages |
-| `--guild <id>` | `WHERE guild_id = ?` | messages, members, channels |
-| `--before <date>` | `WHERE timestamp < ?` | messages |
-| `--after <date>` | `WHERE timestamp > ?` | messages |
-| `--days <N>` | `WHERE timestamp > datetime('now', '-N days')` | messages |
-| `--hours <N>` | `WHERE timestamp > datetime('now', '-N hours')` | messages |
-| `--type <int>` | `WHERE type = ?` | messages |
-| `--pinned` | `WHERE pinned = 1` | messages |
-| `--role <name-or-id>` | `WHERE roles LIKE '%"role_id"%'` (JSON contains) | members |
-| `--action <type>` | `WHERE action_type = ?` | audit_log |
-| `--target <id>` | `WHERE target_id = ?` | audit_log |
-
-## Compound Cross-Entity Queries
-
-### 1. Messages by author in channel in last N days
-```sql
-SELECT m.content, m.timestamp, mb.username, c.name as channel_name
-FROM messages m
-JOIN members mb ON m.author_id = mb.user_id AND m.guild_id = mb.guild_id
-JOIN channels c ON m.channel_id = c.id
-WHERE c.name = ? AND mb.username = ? AND m.timestamp > datetime('now', '-30 days')
-ORDER BY m.timestamp DESC;
-```
-**Validated:** messages.author_id -> members.user_id, messages.channel_id -> channels.id
-
-### 2. Top posters per channel (activity command)
-```sql
-SELECT mb.username, c.name as channel, COUNT(*) as msg_count
-FROM messages m
-JOIN members mb ON m.author_id = mb.user_id AND m.guild_id = mb.guild_id
-JOIN channels c ON m.channel_id = c.id
-WHERE m.guild_id = ? AND m.timestamp > datetime('now', '-30 days')
-GROUP BY m.author_id, m.channel_id
-ORDER BY msg_count DESC
-LIMIT 20;
-```
-**Validated:** Three-way join, all FK columns exist.
-
-### 3. Stale channels (no recent messages)
-```sql
-SELECT c.name, c.type, MAX(m.timestamp) as last_message
-FROM channels c
-LEFT JOIN messages m ON c.id = m.channel_id
-WHERE c.guild_id = ?
-GROUP BY c.id
-HAVING last_message IS NULL OR last_message < datetime('now', '-30 days')
-ORDER BY last_message ASC;
-```
-**Validated:** channels.id -> messages.channel_id LEFT JOIN handles empty channels.
-
-### 4. Audit log timeline with actor names
-```sql
-SELECT a.id, a.action_type, mb.username as actor, a.target_id, a.reason, a.data
-FROM audit_log a
-LEFT JOIN members mb ON a.user_id = mb.user_id AND a.guild_id = mb.guild_id
-WHERE a.guild_id = ? AND a.action_type IN (20, 22, 25)  -- MEMBER_KICK, MEMBER_BAN_ADD, MEMBER_ROLE_UPDATE
-ORDER BY a.id DESC
-LIMIT 50;
-```
-**Validated:** audit_log.user_id -> members.user_id
-
-### 5. FTS5 search with channel/author context
-```sql
-SELECT m.id, m.content, m.timestamp, mb.username, c.name as channel
-FROM messages m
-JOIN messages_fts ON messages_fts.rowid = m.rowid
-JOIN members mb ON m.author_id = mb.user_id AND m.guild_id = mb.guild_id
-JOIN channels c ON m.channel_id = c.id
-WHERE messages_fts MATCH ?
-AND m.guild_id = ?
-ORDER BY rank
-LIMIT 25;
-```
-**Validated:** FTS5 JOIN via rowid, context JOINs via FK columns.
-
-## Tail Strategy
-
-| Method | Available? | Decision |
-|--------|-----------|----------|
-| **Gateway WebSocket** | YES - Discord Gateway with intents (MESSAGE_CREATE, MEMBER_UPDATE, etc.) | **PRIMARY** |
-| SSE | No | N/A |
-| REST Polling | Yes (GET messages?after=) | **FALLBACK** |
-
-**Decision: Gateway WebSocket (PRIMARY)**
-
-Discord's Gateway is the canonical real-time transport. It provides:
-- MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE events
-- GUILD_MEMBER_ADD, GUILD_MEMBER_UPDATE, GUILD_MEMBER_REMOVE
-- CHANNEL_CREATE, CHANNEL_UPDATE, CHANNEL_DELETE
-- All events come with full payloads, no additional API calls needed
-
-The tail command should:
-1. Connect to wss://gateway.discord.gg/?v=10&encoding=json
-2. Handle HELLO, send IDENTIFY with bot token + intents
-3. Maintain heartbeat
-4. Filter events by type/guild/channel/keyword
-5. Output filtered events as JSON stream (one object per line for piping)
-6. Optionally persist events to SQLite (--persist flag)
-
-REST polling is the fallback for bots without Gateway intents or for simpler use cases.
-
-## Commands to Build in Phase 4 Priority 0
-
-| Command | Purpose | Tables Used |
-|---------|---------|-------------|
-| `sync` | Populate all tables | messages, members, channels, sync_state |
-| `search` | FTS5 query with context | messages, messages_fts, members, channels |
-| `messages` | Query message slices | messages, members, channels |
-| `members` | Offline member directory | members |
-| `channels` | Channel listing with stats | channels, messages (for counts) |
-| `sql` | Raw read-only SQL | All tables |
-| `activity` | Per-member analytics | messages, members, channels |
-| `audit` | Audit log with filtering | audit_log, members |
-| `tail` | Live Gateway stream | messages (if --persist) |
-| `status` | Archive health check | sync_state, messages (counts) |
-
-## Sources
-
-- Discord API: GET /channels/{id}/messages - `after` param confirmed
-- Discord API: GET /guilds/{id}/members - `after` + `limit` params confirmed
-- Discord API: GET /guilds/{id}/audit-logs - `before`, `after`, `action_type`, `user_id` confirmed
-- Discord API: Gateway WebSocket at wss://gateway.discord.gg
-- discrawl: validates SQLite+FTS5+Gateway approach (564 stars)
-- 9+ GitHub repos store Discord data in SQLite
-- HN item 36748981: Discord search is broken/limited
diff --git a/docs/plans/2026-03-26-feat-discord-cli-power-user-workflows.md b/docs/plans/2026-03-26-feat-discord-cli-power-user-workflows.md
deleted file mode 100644
index 9717a3dd..00000000
--- a/docs/plans/2026-03-26-feat-discord-cli-power-user-workflows.md
+++ /dev/null
@@ -1,88 +0,0 @@
----
-title: "Power User Workflows: Discord CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0.5"
-api: "discord"
----
-
-# Power User Workflows: Discord CLI
-
-## API Archetype: Communication
-
-Discord is a Communication platform. Power user workflows center on: archive, offline search, keyword monitoring, member analytics, audit trails, moderation actions, and compliance export.
-
-## All 12 Workflow Ideas
-
-### 1. `sync` - Incremental message archive (11/12)
-Paginate messages per channel using snowflake `after` cursor, discover active + archived threads, sync thread messages, store in domain-specific SQLite tables. Endpoints: GET /guilds/{id}/channels, GET /channels/{id}/messages?after=&limit=100, GET /guilds/{id}/threads/active, GET /channels/{id}/threads/archived/public.
-
-### 2. `search` - FTS5 local search (11/12)
-Query FTS5 index with domain filters (--channel, --author, --before, --after). No API calls - pure local SQLite. Instant results vs Discord's rate-limited search.
-
-### 3. `activity` - Member activity analytics (11/12)
-GROUP BY author_id on local messages, COUNT per time window, JOIN with members for names/roles, rank by activity. No API calls after sync.
-
-### 4. `tail` - Live Gateway event stream (10/12)
-Connect to Gateway WebSocket, subscribe with intents, filter by event type/channel/keyword, output JSON stream. Endpoint: wss://gateway.discord.gg.
-
-### 5. `audit` - Audit log investigation (10/12)
-GET /guilds/{id}/audit-logs with action_type, user_id, before, after filters. Correlate with member/channel data. Timeline view.
-
-### 6. `sql` - Raw SQL queries (10/12)
-Accept read-only SQL, execute against local SQLite, output as table/JSON/CSV. Power user escape hatch.
-
-### 7. `mentions` - Mention tracking (9/12)
-Parse @user and @role mentions from local messages, group by entity, show frequency and context.
-
-### 8. `channels` - Channel health (9/12)
-List channels with local message counts, detect inactive channels, show last message date, sort by activity.
-
-### 9. `members` - Member directory (9/12)
-Offline member list from local DB with role filtering, join date sorting, search. Sync from GET /guilds/{id}/members?limit=1000&after=.
-
-### 10. `stale` - Dead channel detection (9/12)
-Find channels with no messages in N days. Cross-reference with channel type and permissions.
-
-### 11. `roles` - Role hierarchy analysis (7/12)
-Show role hierarchy with permissions, identify overprivileged roles, member counts per role.
-
-### 12. `export` - Compliance export (6/12)
-Filter local data by date range, output JSON/CSV. Deferred - DiscordChatExporter handles this well.
-
-## Scoring Table
-
-| Rank | Workflow | Frequency | Pain | Feasibility | Uniqueness | Total |
-|------|----------|-----------|------|-------------|------------|-------|
-| 1 | sync | 3 | 3 | 3 | 2 | 11 |
-| 1 | search | 3 | 3 | 3 | 2 | 11 |
-| 1 | activity | 2 | 3 | 3 | 3 | 11 |
-| 4 | tail | 3 | 3 | 2 | 2 | 10 |
-| 4 | audit | 1 | 3 | 3 | 3 | 10 |
-| 4 | sql | 2 | 3 | 3 | 2 | 10 |
-| 7 | mentions | 2 | 2 | 3 | 2 | 9 |
-| 7 | channels | 1 | 2 | 3 | 3 | 9 |
-| 7 | members | 2 | 2 | 3 | 2 | 9 |
-| 7 | stale | 1 | 2 | 3 | 3 | 9 |
-| 11 | roles | 1 | 1 | 3 | 2 | 7 |
-| 12 | export | 1 | 2 | 3 | 0 | 6 |
-
-## Top 7 for Implementation (Mandatory Phase 4 Work)
-
-1. **`sync`** - Foundation. Without sync, nothing works.
-2. **`search`** - Killer feature. FTS5 over synced messages.
-3. **`activity`** - Analytics differentiator. Per-member rankings.
-4. **`tail`** - Real-time hook. Gateway WebSocket stream.
-5. **`audit`** - Moderation tool. Audit log with filtering.
-6. **`sql`** - Power user escape hatch.
-7. **`members`** - Offline member directory with role filtering.
-
-## API Validation
-
-All workflows validated against Discord API v10:
-- Message pagination: `after` snowflake cursor confirmed on GET /channels/{id}/messages
-- Audit log filtering: `action_type`, `user_id`, `before`, `after` params confirmed
-- Member pagination: `after` snowflake cursor + `limit` (1-1000) confirmed
-- Thread discovery: GET /guilds/{id}/threads/active + GET /channels/{id}/threads/archived/* confirmed
-- Gateway: wss://gateway.discord.gg with intent-based subscriptions confirmed
diff --git a/docs/plans/2026-03-26-feat-discord-cli-research.md b/docs/plans/2026-03-26-feat-discord-cli-research.md
deleted file mode 100644
index 71ed2d29..00000000
--- a/docs/plans/2026-03-26-feat-discord-cli-research.md
+++ /dev/null
@@ -1,105 +0,0 @@
----
-title: "Research: Discord CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "1"
-api: "discord"
----
-
-# Research: Discord CLI
-
-## Spec Discovery
-
-- **Official OpenAPI spec:** https://raw.githubusercontent.com/discord/discord-api-spec/main/specs/openapi.json
-- **Source:** discord/discord-api-spec GitHub repository (official Discord org)
-- **Format:** OpenAPI 3.1.0, JSON
-- **Size:** ~1MB+
-- **Verified:** Yes (in printing-press known-specs registry)
-
-## Competitors (Deep Analysis)
-
-### DiscordChatExporter (10,700 stars)
-- **Repo:** https://github.com/Tyrrrz/DiscordChatExporter
-- **Language:** C# (91.3%)
-- **Last commit:** 2026-03-21 (v2.47.1) - actively maintained
-- **Contributors:** 63
-- **Open issues:** 10
-- **Notable features:** Export to HTML (dark/light), TXT, CSV, JSON. Rich media support. Date range filtering. Self-contained offline exports. Both GUI and CLI.
-- **Weaknesses:**
-  - Export-only - no local persistence, no search, no analytics
-  - Issue #1210: "Rework CLI commands to be more composable" - users want better CLI UX
-  - Issue #1265: Thread exports missing parent message context
-  - Issue #1483: Can't export age-restricted channels
-  - No --json/--select/--dry-run agent-native features
-  - No real-time tail capability
-  - Requires manual channel selection for compliance exports
-
-### discrawl (564 stars) - THE BENCHMARK
-- **Repo:** https://github.com/steipete/discrawl
-- **Language:** Go (100%)
-- **Last commit:** Recent (main branch active)
-- **Contributors:** Small team
-- **Open issues:** 4
-- **Commands:** 12 (init, sync, tail, search, messages, mentions, sql, members, channels, status, doctor)
-- **Notable features:** SQLite+FTS5, Gateway WebSocket tail, offline member directory, raw SQL access, multi-guild support, structured mention tracking
-- **Weaknesses:**
-  - Issue #15: "Using discrawl as memory augmentation for AI agents" - no agent-native features (no --json, --select, --dry-run)
-  - Issue #9: FTS search injection & tokenizer configuration gaps
-  - Issue #8: No schema migrations/versioning
-  - No audit log support
-  - No activity/analytics commands
-  - No webhook management
-  - No general-purpose REST API wrapper (only data-focused commands)
-
-### discli (6 stars) - Agent-native reference
-- **Repo:** https://github.com/ibbybuilds/discli
-- **Language:** TypeScript
-- **Notable features:** YAML output (5x fewer tokens than JSON), --dry-run, --confirm for destructive ops, SCHEMA.md for agent parsing, SOUL.md personality file
-- **Weaknesses:** No data layer, no SQLite, no search, no analytics. One-command-one-API-call only.
-
-### jackwener/discord-cli (78 stars) - Python SQLite reference
-- **Repo:** https://github.com/jackwener/discord-cli
-- **Language:** Python
-- **Last commit:** 2025-01-10 (stale)
-- **Notable features:** SQLite sync, FTS search, export, AI analysis integration
-- **Weaknesses:** Uses user tokens (TOS violation risk), Python (not a single binary), 1 contributor, stale
-
-## User Pain Points
-
-> "Rework the CLI commands to be more composable" - DiscordChatExporter Issue #1210 (users want piping, filtering, structured output)
-
-> "Using discrawl as memory augmentation for AI agents" - discrawl Issue #15 (users want agent-native features for LLM integration)
-
-> "Discord's text search isn't exact, which can be a big problem sometimes" - HN item 36748981 (native Discord search is unreliable)
-
-> "Once a server exceeds 1000 members, users will no longer be able to see offline members" - Discord Support (member visibility limitation drives offline member directory demand)
-
-## Auth Method
-- **Type:** Bot Token (Bearer token in Authorization header)
-- **Env var convention:** `DISCORD_BOT_TOKEN` (discrawl uses `DISCRAWL_TOKEN`, discli uses `DISCORD_TOKEN`)
-- **Our choice:** `DISCORD_TOKEN` (shortest, most conventional)
-
-## Demand Signals
-- HN: discordo discussion (Aug 2022) - terminal Discord client demand
-- HN: Discoding (Feb 2026) - AI CLIs relayed to Discord
-- HN: Remote-OpenCode (Feb 2026) - controlling AI coding from Discord
-- 9+ GitHub repos storing Discord data in SQLite
-- Discord support forums: multiple feature requests for offline member visibility and better search
-
-## Strategic Justification
-
-**Why this CLI should exist when discrawl already has 564 stars:**
-
-1. **Agent-native gap:** discrawl has no --json, --select, --dry-run, --stdin, --yes flags. Every AI agent integration (Issue #15) hits this wall. Our CLI is agent-native from day one.
-2. **API breadth gap:** discrawl only covers data commands (12 commands). No guild management, no webhook orchestration, no role management, no channel CRUD. Our CLI wraps the full Discord REST API (200+ endpoints) PLUS the data layer.
-3. **Analytics gap:** Neither discrawl nor any other CLI provides per-member activity rankings, channel health reports, or stale channel detection. Our compound queries (validated in Phase 0.7) fill this.
-4. **Audit log gap:** No Discord CLI exposes audit logs with filtering. Our audit command fills a real moderation need.
-5. **Single binary:** discrawl is Go (good), but DiscordChatExporter is C#, jackwener/discord-cli is Python. We're Go - single binary, zero dependencies, cross-platform.
-
-**In summary:** We combine discrawl's data depth (SQLite+FTS5+Gateway) with full API breadth (200+ endpoints) and agent-native features (--json/--select/--dry-run). No existing tool does all three.
-
-## Target
-- **Command count:** 50+ (12 workflow commands + 40+ generated API wrappers)
-- **Key differentiator:** Data layer + full API + agent-native in one Go binary
-- **Quality bar:** Steinberger Grade A (80+/100)
diff --git a/docs/plans/2026-03-26-feat-discord-cli-v2-data-layer-spec.md b/docs/plans/2026-03-26-feat-discord-cli-v2-data-layer-spec.md
deleted file mode 100644
index 935031b2..00000000
--- a/docs/plans/2026-03-26-feat-discord-cli-v2-data-layer-spec.md
+++ /dev/null
@@ -1,371 +0,0 @@
----
-title: "Data Layer Specification: Discord CLI v2"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0.7"
-api: "discord"
----
-
-# Data Layer Specification: Discord CLI v2
-
-## Entity Classification
-
-| Entity | Type | Est. Volume | Update Freq | Temporal Field | Persistence Need |
-|--------|------|-------------|-------------|----------------|-----------------|
-| **Messages** | Accumulating | Millions | Append-only (edits via events) | `id` (snowflake) | SQLite + FTS5 |
-| **Members** | Reference | 100-100k per guild | Moderate (joins/leaves/role changes) | `joined_at` | SQLite table + periodic refresh |
-| **Channels** | Reference | 10-500 per guild | Low (created/deleted/renamed) | `id` (snowflake) | SQLite table + periodic refresh |
-| **Threads** | Accumulating | 100-10k per guild | Moderate (created/archived) | `id` (snowflake) | SQLite table (stored as channels) |
-| **Roles** | Reference | 10-250 per guild | Low | `id` (snowflake) | SQLite table |
-| **Guilds** | Reference | 1-100 per bot | Very low | `id` (snowflake) | SQLite table |
-| **Audit Log Entries** | Append-only | 100s-1000s per week | Append-only (45-day retention) | `id` (snowflake) | SQLite table |
-| **Reactions** | Accumulating | High per popular msg | Append/remove events | N/A (per-message) | SQLite table |
-| **Emojis** | Reference | 10-500 per guild | Low | `id` | SQLite table |
-| **Invites** | Ephemeral-ish | 10-100 per guild | Low | `created_at` | API-only (small cardinality) |
-| **Webhooks** | Reference | 1-50 per guild | Very low | N/A | API-only |
-| **Bans** | Reference | 0-10k per guild | Low | N/A | API-only |
-| **Scheduled Events** | Ephemeral | 0-20 per guild | Low | `scheduled_start_time` | API-only |
-| **Voice States** | Ephemeral | Real-time only | Continuous via Gateway | N/A | No persistence |
-| **Gateway info** | Ephemeral | 1 record | Never | N/A | No persistence |
-
-## Social Signal Evidence
-
-| Signal | Evidence Score | Source | What It Tells Us |
-|--------|---------------|--------|-----------------|
-| Local message search | 8/10 | discrawl (569 stars), jackwener/discord-cli (78 stars) | Messages MUST be in SQLite with FTS5 |
-| Member directory sync | 7/10 | discrawl stores member snapshots, discord-analytics tracks members | Members need proper columns for join analysis |
-| Audit log persistence | 6/10 | MEE6 audit logging, Discord's 45-day retention limit | Users want audit logs stored beyond 45 days |
-| Channel metadata | 6/10 | AnswerOverflow indexes channels, analytics tools track per-channel | Channels need local storage for cross-referencing |
-| Reaction tracking | 4/10 | Emoji usage analysis tools, community engagement metrics | Nice-to-have, not primary |
-
-## Data Gravity Scores
-
-| Entity | Volume | QueryFreq | JoinDemand | SearchNeed | TemporalValue | **Total** | Classification |
-|--------|--------|-----------|------------|------------|---------------|-----------|---------------|
-| **Messages** | 3 | 3 | 3 (channel, author, guild) | 3 (primary text content) | 2 (timestamps + trends) | **14** | PRIMARY |
-| **Members** | 2 | 2 | 3 (messages, roles, audit) | 1 (username/nick) | 1 (joined_at) | **9** | PRIMARY |
-| **Channels** | 1 | 2 | 3 (messages, threads, perms) | 1 (name, topic) | 0 | **7** | SUPPORT |
-| **Audit Logs** | 2 | 2 | 2 (user, target) | 1 (reason text) | 3 (core time-series) | **10** | PRIMARY |
-| **Roles** | 0 | 1 | 3 (members, audit, perms) | 0 | 0 | **4** | SUPPORT (via members) |
-| **Threads** | 1 | 1 | 2 (messages, channels) | 1 (name) | 1 | **6** | SUPPORT |
-| **Guilds** | 0 | 1 | 3 (everything) | 1 (name, desc) | 0 | **5** | SUPPORT |
-| **Emojis** | 0 | 1 | 1 (reactions) | 0 | 0 | **2** | API-only |
-| **Reactions** | 2 | 1 | 2 (messages, emojis) | 0 | 1 | **6** | SUPPORT |
-
-**Primary entities (score >= 8):** Messages (14), Audit Logs (10), Members (9)
-**Support entities (score 5-7):** Channels (7), Threads (6), Reactions (6), Guilds (5)
-
-## SQLite Schema
-
-```sql
--- Guild metadata
-CREATE TABLE guilds (
-    id TEXT PRIMARY KEY,
-    name TEXT NOT NULL,
-    icon TEXT,
-    owner_id TEXT,
-    description TEXT,
-    member_count INTEGER,
-    features TEXT,  -- JSON array
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
-);
-
--- Channels (includes threads - Discord stores threads as channels)
-CREATE TABLE channels (
-    id TEXT PRIMARY KEY,
-    guild_id TEXT NOT NULL,
-    parent_id TEXT,
-    name TEXT NOT NULL,
-    type INTEGER NOT NULL,  -- 0=text, 2=voice, 4=category, 5=announcement, 10/11/12=thread, 13=stage, 15=forum
-    topic TEXT,
-    position INTEGER,
-    nsfw INTEGER DEFAULT 0,
-    last_message_id TEXT,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
-    FOREIGN KEY (guild_id) REFERENCES guilds(id)
-);
-CREATE INDEX idx_channels_guild ON channels(guild_id);
-CREATE INDEX idx_channels_parent ON channels(parent_id);
-CREATE INDEX idx_channels_type ON channels(type);
-
--- Members
-CREATE TABLE members (
-    guild_id TEXT NOT NULL,
-    user_id TEXT NOT NULL,
-    username TEXT NOT NULL,
-    display_name TEXT,  -- global_name or nick
-    nick TEXT,
-    avatar TEXT,
-    joined_at TEXT,
-    roles TEXT,  -- JSON array of role IDs
-    bot INTEGER DEFAULT 0,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
-    PRIMARY KEY (guild_id, user_id)
-);
-CREATE INDEX idx_members_guild ON members(guild_id);
-CREATE INDEX idx_members_user ON members(user_id);
-CREATE INDEX idx_members_joined ON members(joined_at);
-
--- Messages (PRIMARY entity - score 14)
-CREATE TABLE messages (
-    id TEXT PRIMARY KEY,
-    channel_id TEXT NOT NULL,
-    guild_id TEXT NOT NULL,
-    author_id TEXT NOT NULL,
-    content TEXT NOT NULL DEFAULT '',
-    type INTEGER NOT NULL DEFAULT 0,
-    timestamp TEXT NOT NULL,
-    edited_timestamp TEXT,
-    flags INTEGER DEFAULT 0,
-    pinned INTEGER DEFAULT 0,
-    mention_everyone INTEGER DEFAULT 0,
-    attachment_count INTEGER DEFAULT 0,
-    embed_count INTEGER DEFAULT 0,
-    reference_message_id TEXT,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
-    FOREIGN KEY (channel_id) REFERENCES channels(id),
-    FOREIGN KEY (guild_id) REFERENCES guilds(id)
-);
-CREATE INDEX idx_messages_channel ON messages(channel_id);
-CREATE INDEX idx_messages_guild ON messages(guild_id);
-CREATE INDEX idx_messages_author ON messages(author_id);
-CREATE INDEX idx_messages_timestamp ON messages(timestamp);
-CREATE INDEX idx_messages_channel_id ON messages(channel_id, id);  -- for cursor pagination
-
--- FTS5 for message full-text search
-CREATE VIRTUAL TABLE messages_fts USING fts5(
-    content,
-    content=messages,
-    content_rowid=rowid,
-    tokenize='porter unicode61'
-);
-
--- Triggers to keep FTS in sync
-CREATE TRIGGER messages_ai AFTER INSERT ON messages BEGIN
-    INSERT INTO messages_fts(rowid, content) VALUES (new.rowid, new.content);
-END;
-CREATE TRIGGER messages_ad AFTER DELETE ON messages BEGIN
-    INSERT INTO messages_fts(messages_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
-END;
-CREATE TRIGGER messages_au AFTER UPDATE ON messages BEGIN
-    INSERT INTO messages_fts(messages_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
-    INSERT INTO messages_fts(rowid, content) VALUES (new.rowid, new.content);
-END;
-
--- Audit Log Entries (PRIMARY entity - score 10)
-CREATE TABLE audit_log_entries (
-    id TEXT PRIMARY KEY,
-    guild_id TEXT NOT NULL,
-    user_id TEXT,           -- who performed the action
-    target_id TEXT,         -- who/what was affected
-    action_type INTEGER NOT NULL,
-    reason TEXT,
-    changes TEXT,           -- JSON array of changes
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
-    FOREIGN KEY (guild_id) REFERENCES guilds(id)
-);
-CREATE INDEX idx_audit_guild ON audit_log_entries(guild_id);
-CREATE INDEX idx_audit_user ON audit_log_entries(user_id);
-CREATE INDEX idx_audit_target ON audit_log_entries(target_id);
-CREATE INDEX idx_audit_action ON audit_log_entries(action_type);
-CREATE INDEX idx_audit_guild_id ON audit_log_entries(guild_id, id);  -- cursor pagination
-
--- Roles (SUPPORT entity)
-CREATE TABLE roles (
-    id TEXT PRIMARY KEY,
-    guild_id TEXT NOT NULL,
-    name TEXT NOT NULL,
-    color INTEGER DEFAULT 0,
-    position INTEGER DEFAULT 0,
-    permissions TEXT,
-    mentionable INTEGER DEFAULT 0,
-    hoist INTEGER DEFAULT 0,
-    managed INTEGER DEFAULT 0,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
-    FOREIGN KEY (guild_id) REFERENCES guilds(id)
-);
-CREATE INDEX idx_roles_guild ON roles(guild_id);
-
--- Reactions (SUPPORT entity)
-CREATE TABLE reactions (
-    message_id TEXT NOT NULL,
-    emoji_name TEXT NOT NULL,
-    emoji_id TEXT,
-    count INTEGER DEFAULT 1,
-    data JSON NOT NULL,
-    PRIMARY KEY (message_id, emoji_name, emoji_id),
-    FOREIGN KEY (message_id) REFERENCES messages(id)
-);
-CREATE INDEX idx_reactions_message ON reactions(message_id);
-
--- Mentions (structured, for query efficiency)
-CREATE TABLE mentions (
-    message_id TEXT NOT NULL,
-    guild_id TEXT NOT NULL,
-    channel_id TEXT NOT NULL,
-    mentioned_user_id TEXT,
-    mentioned_role_id TEXT,
-    mention_type TEXT NOT NULL,  -- 'user', 'role', 'everyone'
-    FOREIGN KEY (message_id) REFERENCES messages(id)
-);
-CREATE INDEX idx_mentions_user ON mentions(mentioned_user_id);
-CREATE INDEX idx_mentions_role ON mentions(mentioned_role_id);
-CREATE INDEX idx_mentions_guild ON mentions(guild_id);
-
--- Sync state tracking
-CREATE TABLE sync_state (
-    guild_id TEXT NOT NULL,
-    channel_id TEXT,
-    entity_type TEXT NOT NULL,  -- 'messages', 'members', 'audit_logs', 'channels'
-    last_id TEXT,               -- snowflake ID cursor
-    last_synced_at TEXT NOT NULL,
-    message_count INTEGER DEFAULT 0,
-    PRIMARY KEY (guild_id, COALESCE(channel_id, ''), entity_type)
-);
-```
-
-## Sync Strategy
-
-### Messages (Primary - snowflake cursor)
-- **Cursor field:** `id` (snowflake ID - encodes timestamp)
-- **API endpoint:** `GET /channels/{channel_id}/messages?after={last_id}&limit=100`
-- **VALIDATED:** Discord API supports `after` query param for messages (confirmed in spec)
-- **Batch size:** 100 (API max)
-- **Strategy:** For each channel, store `last_id` in `sync_state`. Resume from there on next sync.
-- **Full sync:** Paginate backwards with `before` param from newest to oldest.
-
-### Members (Primary - snowflake cursor)
-- **Cursor field:** `user_id` (snowflake ID)
-- **API endpoint:** `GET /guilds/{guild_id}/members?after={last_user_id}&limit=1000`
-- **VALIDATED:** Discord API supports `after` query param for members (confirmed in spec)
-- **Batch size:** 1000 (API max)
-- **Strategy:** Full refresh on each sync (members don't have incremental endpoints)
-
-### Audit Logs (Primary - snowflake cursor)
-- **Cursor field:** `id` (snowflake ID)
-- **API endpoint:** `GET /guilds/{guild_id}/audit-logs?after={last_id}&limit=100`
-- **VALIDATED:** Discord API supports `after` AND `before` params for audit logs (confirmed in spec)
-- **Batch size:** 100 (API max)
-- **Strategy:** Incremental sync using `after` param. Store `last_id` per guild.
-- **Note:** Discord retains audit logs for 45 days. Our local copy preserves them forever.
-
-### Channels (Support - full refresh)
-- **API endpoint:** `GET /guilds/{guild_id}/channels`
-- **Strategy:** Full refresh (small cardinality, no pagination needed)
-
-### Roles (Support - full refresh)
-- **API endpoint:** `GET /guilds/{guild_id}/roles`
-- **Strategy:** Full refresh (small cardinality)
-
-## Domain-Specific Search Filters
-
-| CLI Flag | SQL WHERE Clause | Validated |
-|----------|-----------------|-----------|
-| `--channel <name-or-id>` | `WHERE channel_id = ? OR channel_id IN (SELECT id FROM channels WHERE name LIKE ?)` | Yes |
-| `--author <name-or-id>` | `WHERE author_id = ? OR author_id IN (SELECT user_id FROM members WHERE username LIKE ? OR nick LIKE ?)` | Yes |
-| `--guild <name-or-id>` | `WHERE guild_id = ? OR guild_id IN (SELECT id FROM guilds WHERE name LIKE ?)` | Yes |
-| `--days <N>` | `WHERE id > snowflake_from_timestamp(now - N days)` | Yes (snowflake encodes time) |
-| `--since <date>` | `WHERE id > snowflake_from_timestamp(date)` | Yes |
-| `--until <date>` | `WHERE id < snowflake_from_timestamp(date)` | Yes |
-| `--pinned` | `WHERE pinned = 1` | Yes |
-| `--has-attachment` | `WHERE attachment_count > 0` | Yes |
-| `--has-embed` | `WHERE embed_count > 0` | Yes |
-| `--type <msg-type>` | `WHERE type = ?` | Yes |
-| `--action <audit-type>` | `WHERE action_type = ?` (for audit logs) | Yes |
-
-## Compound Queries (Cross-Entity)
-
-### 1. Messages by author in channel in last N days
-```sql
-SELECT m.id, m.content, m.timestamp, mb.username, c.name AS channel_name
-FROM messages m
-JOIN members mb ON m.author_id = mb.user_id AND m.guild_id = mb.guild_id
-JOIN channels c ON m.channel_id = c.id
-WHERE c.name = ? AND mb.username = ? AND m.id > ?
-ORDER BY m.id DESC LIMIT 50;
-```
-
-### 2. Top authors by message count per channel
-```sql
-SELECT c.name AS channel, mb.username, COUNT(*) AS msg_count
-FROM messages m
-JOIN channels c ON m.channel_id = c.id
-JOIN members mb ON m.author_id = mb.user_id AND m.guild_id = mb.guild_id
-WHERE m.guild_id = ? AND m.id > ?
-GROUP BY c.name, mb.username
-ORDER BY msg_count DESC LIMIT 20;
-```
-
-### 3. Audit log actions by moderator with target names
-```sql
-SELECT a.id, a.action_type, mod.username AS moderator, target.username AS target, a.reason
-FROM audit_log_entries a
-LEFT JOIN members mod ON a.user_id = mod.user_id AND a.guild_id = mod.guild_id
-LEFT JOIN members target ON a.target_id = target.user_id AND a.guild_id = target.guild_id
-WHERE a.guild_id = ? AND a.action_type IN (22, 23, 24, 25)  -- ban/kick/member_update
-ORDER BY a.id DESC LIMIT 50;
-```
-
-### 4. Stale channels (no messages in N days)
-```sql
-SELECT c.id, c.name, c.type, c.topic,
-       MAX(m.id) AS last_message_snowflake,
-       COUNT(m.id) AS total_messages
-FROM channels c
-LEFT JOIN messages m ON c.id = m.channel_id
-WHERE c.guild_id = ? AND c.type IN (0, 5, 15)  -- text, announcement, forum
-GROUP BY c.id
-HAVING last_message_snowflake IS NULL OR last_message_snowflake < ?
-ORDER BY total_messages ASC;
-```
-
-### 5. Member join timeline with role analysis
-```sql
-SELECT mb.username, mb.display_name, mb.joined_at, mb.bot,
-       GROUP_CONCAT(r.name, ', ') AS roles
-FROM members mb
-LEFT JOIN roles r ON mb.guild_id = r.guild_id AND r.id IN (
-    SELECT value FROM json_each(mb.roles)
-)
-WHERE mb.guild_id = ?
-GROUP BY mb.user_id
-ORDER BY mb.joined_at DESC LIMIT 50;
-```
-
-## Tail Strategy
-
-| Method | Available? | Decision |
-|--------|-----------|----------|
-| **Gateway WebSocket** | YES - Discord Gateway is the primary real-time channel | **USE THIS** |
-| SSE | No | N/A |
-| REST Polling | Fallback only | Use for audit logs (no Gateway event for historical) |
-
-**Decision: Gateway WebSocket** for real-time message/member/reaction events. Discord's Gateway provides:
-- `MESSAGE_CREATE`, `MESSAGE_UPDATE`, `MESSAGE_DELETE` for messages
-- `GUILD_MEMBER_ADD`, `GUILD_MEMBER_REMOVE`, `GUILD_MEMBER_UPDATE` for members
-- `GUILD_AUDIT_LOG_ENTRY_CREATE` for audit logs (requires GuildModeration intent)
-- `MESSAGE_REACTION_ADD`, `MESSAGE_REACTION_REMOVE` for reactions
-
-The tail command should connect to Gateway, subscribe to relevant intents, and write events to SQLite in real-time.
-
-**REST polling fallback:** For initial sync and bulk historical data, use REST endpoints with cursor pagination.
-
-## Commands to Build in Phase 4 Priority 0
-
-| Command | Purpose | Data Source |
-|---------|---------|------------|
-| `sync` | Incremental sync of messages, members, channels, audit logs, roles | REST API -> SQLite |
-| `search` | Full-text search with domain filters | FTS5 + SQLite |
-| `messages` | List/query messages from local DB | SQLite |
-| `members` | List/query members from local DB | SQLite |
-| `sql` | Raw read-only SQL queries | SQLite |
-| `tail` | Real-time Gateway event stream | Gateway WebSocket -> SQLite |
-| `audit` | Query audit log entries with filters | SQLite (after sync) |
-| `status` | Show sync state, DB size, entity counts | sync_state table |
diff --git a/docs/plans/2026-03-26-feat-discord-cli-v2-power-user-workflows.md b/docs/plans/2026-03-26-feat-discord-cli-v2-power-user-workflows.md
deleted file mode 100644
index 4e2346d9..00000000
--- a/docs/plans/2026-03-26-feat-discord-cli-v2-power-user-workflows.md
+++ /dev/null
@@ -1,152 +0,0 @@
----
-title: "Power User Workflows: Discord CLI v2"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0.5"
-api: "discord"
----
-
-# Power User Workflows: Discord CLI v2
-
-## API Archetype: Communication
-
-Discord is a **Communication** archetype with strong **Project Management** crossover (communities manage tasks, moderate, and track engagement like a PM tool). Primary workflows: archive, offline search, monitor keywords, export conversations, moderation analysis, community health.
-
-## Workflow Ideas (13 total)
-
-### 1. Server Health Report
-**Steps:** Query local messages table for last N days -> Count messages per channel -> Count unique active members -> Calculate message velocity -> Identify stale channels (0 messages) -> Report
-**Frequency:** Weekly (3)
-**Pain:** Manual counting in Discord UI is impossible at scale (3)
-**Feasibility:** Easy - all data in SQLite after sync (3)
-**Uniqueness:** No CLI does this (3)
-**Score: 12/12**
-
-### 2. Audit Log Forensics
-**Steps:** Fetch audit log entries -> Store in local DB -> Filter by action type (ban, kick, delete, role change) -> Cross-reference with member table -> Timeline report
-**Frequency:** After incidents + weekly (3)
-**Pain:** Discord UI shows raw audit log, no filtering or export (3)
-**Feasibility:** API supports action_type filter, 45-day retention (3)
-**Uniqueness:** No CLI does this (3)
-**Score: 12/12**
-
-### 3. Stale Channel Detection
-**Steps:** Query local messages table -> Find channels with 0 messages in N days -> Cross-reference with channel metadata (topic, category) -> Report with last-message timestamps
-**Frequency:** Monthly (1)
-**Pain:** Manual check across 50+ channels is tedious (2)
-**Feasibility:** Easy - JOIN channels and messages tables (3)
-**Uniqueness:** No CLI does this (3)
-**Score: 9/12**
-
-### 4. Member Activity Leaderboard
-**Steps:** Query local messages -> GROUP BY author_id -> COUNT messages -> JOIN with members table for display names -> Rank -> Report
-**Frequency:** Weekly (2)
-**Pain:** No built-in way to see who's most active (2)
-**Feasibility:** Easy with local DB (3)
-**Uniqueness:** Cially dashboard does this but no CLI (2)
-**Score: 9/12**
-
-### 5. Cross-Channel Keyword Monitor (tail + filter)
-**Steps:** Connect to Gateway -> Filter MESSAGE_CREATE events -> Match against keyword list -> Alert (print to stdout, or format for pipe to webhook)
-**Frequency:** Continuous (3)
-**Pain:** Requires a full bot framework just to watch for keywords (3)
-**Feasibility:** Gateway client needed, moderate effort (2)
-**Uniqueness:** No CLI does real-time keyword filtering (3)
-**Score: 11/12**
-
-### 6. Moderation Summary Report
-**Steps:** Query audit log entries for bans, kicks, timeouts, message deletes -> Group by moderator -> Group by day -> Trend analysis -> Report
-**Frequency:** Weekly (2)
-**Pain:** No way to see moderation trends over time (3)
-**Feasibility:** Requires audit log in local DB (2)
-**Uniqueness:** No CLI does this (3)
-**Score: 10/12**
-
-### 7. Export Pipeline (SQLite -> formats)
-**Steps:** Query local messages table with filters (--channel, --author, --since, --until) -> Format as JSON/CSV/HTML -> Write to file
-**Frequency:** Monthly (1)
-**Pain:** DiscordChatExporter requires .NET, no incremental, no local search first (2)
-**Feasibility:** Easy from SQLite (3)
-**Uniqueness:** Export from LOCAL data is unique - DiscordChatExporter hits API every time (2)
-**Score: 8/12**
-
-### 8. Mention Analysis
-**Steps:** Query structured mentions in local DB -> Group by mentioned user/role -> Count frequency -> Identify most-mentioned members/roles -> Cross-reference with message context
-**Frequency:** Monthly (1)
-**Pain:** Discord shows mentions but can't aggregate them (2)
-**Feasibility:** discrawl already stores structured mentions (3)
-**Uniqueness:** Partial - discrawl has mentions command (1)
-**Score: 7/12**
-
-### 9. Thread Cleanup / Stale Thread Detection
-**Steps:** List all threads -> Check last_message_id timestamp -> Find threads with no activity in N days -> Optionally archive them
-**Frequency:** Monthly (1)
-**Pain:** Threads proliferate and become noise (2)
-**Feasibility:** API supports thread listing and archival (3)
-**Uniqueness:** No tool does this (3)
-**Score: 9/12**
-
-### 10. Role Audit
-**Steps:** List all roles -> For each role, list members with that role -> Identify roles with 0 members -> Identify members with excessive roles -> Report
-**Frequency:** Monthly (1)
-**Pain:** Discord UI makes it hard to see role <-> member mappings at scale (2)
-**Feasibility:** Easy with members + roles in local DB (3)
-**Uniqueness:** No CLI does this (3)
-**Score: 9/12**
-
-### 11. Invite Tracking
-**Steps:** List invites -> Track usage counts -> Identify top inviters -> Detect expired/unused invites -> Clean up
-**Frequency:** Weekly (2)
-**Pain:** Discord shows invites but no analysis (2)
-**Feasibility:** API supports invite listing with use counts (3)
-**Uniqueness:** Partial - some bots do this (1)
-**Score: 8/12**
-
-### 12. Emoji Usage Analysis
-**Steps:** Parse message reactions from local DB -> Count usage per emoji -> Identify unused custom emojis -> Report most/least popular
-**Frequency:** Monthly (1)
-**Pain:** Custom emoji slots are limited, want to prune unused ones (2)
-**Feasibility:** Needs reaction data in local DB (2)
-**Uniqueness:** No CLI does this (3)
-**Score: 8/12**
-
-### 13. Permission Diff
-**Steps:** Snapshot current permissions for all channels/roles -> Compare to previous snapshot -> Report changes
-**Frequency:** After incidents (2)
-**Pain:** Permission overwrites are notoriously complex in Discord (3)
-**Feasibility:** API supports permission overwrites per channel (2)
-**Uniqueness:** No tool does this (3)
-**Score: 10/12**
-
-## Validation Against API
-
-| Workflow | Required Endpoints | Validated? |
-|----------|-------------------|-----------|
-| Server Health | Local DB only (after sync) | Yes - uses synced messages table |
-| Audit Forensics | GET /guilds/{id}/audit-logs | Yes - supports action_type, user_id, before filters |
-| Stale Channels | Local DB only | Yes - channels + messages tables |
-| Member Leaderboard | Local DB only | Yes - messages GROUP BY author_id |
-| Keyword Monitor | Gateway MESSAGE_CREATE | Yes - Gateway intent required |
-| Moderation Summary | Local audit_log_entries table | Yes - requires audit log sync |
-| Export Pipeline | Local DB only | Yes - SELECT with filters |
-| Mention Analysis | Local mentions table | Yes - discrawl stores these |
-| Thread Cleanup | GET /guilds/{id}/threads | Yes - includes archived threads |
-| Role Audit | Local DB (members + roles) | Yes - after member/role sync |
-| Invite Tracking | GET /guilds/{id}/invites | Yes - includes use counts |
-| Emoji Analysis | Local DB (reactions) | Partial - need to sync reactions |
-| Permission Diff | GET /channels/{id} (permission_overwrites) | Yes - per-channel overwrites |
-
-## Top 7 for Implementation (Phase 4 Priority 1)
-
-| Rank | Workflow | Score | CLI Command |
-|------|----------|-------|-------------|
-| 1 | **Server Health Report** | 12/12 | `discord-cli health --guild <id> --days 30` |
-| 2 | **Audit Log Forensics** | 12/12 | `discord-cli audit --guild <id> --action ban --days 7 --json` |
-| 3 | **Keyword Monitor** | 11/12 | `discord-cli watch --guild <id> --keywords "error,outage,bug"` |
-| 4 | **Moderation Summary** | 10/12 | `discord-cli modreport --guild <id> --days 30` |
-| 5 | **Permission Diff** | 10/12 | `discord-cli permdiff --guild <id> --snapshot` |
-| 6 | **Stale Channel Detection** | 9/12 | `discord-cli stale --guild <id> --days 30 --type channels` |
-| 7 | **Role Audit** | 9/12 | `discord-cli roleaudit --guild <id> --empty --excessive` |
-
-These 7 become **mandatory Phase 4 work items.** They use the local SQLite database where possible, falling back to live API only when needed (audit logs, permissions).
diff --git a/docs/plans/2026-03-26-feat-discord-cli-v2-research.md b/docs/plans/2026-03-26-feat-discord-cli-v2-research.md
deleted file mode 100644
index 24e4ef95..00000000
--- a/docs/plans/2026-03-26-feat-discord-cli-v2-research.md
+++ /dev/null
@@ -1,145 +0,0 @@
----
-title: "Research: Discord CLI v2"
-type: feat
-status: active
-date: 2026-03-26
-phase: "1"
-api: "discord"
----
-
-# Research: Discord CLI v2
-
-## Spec Discovery
-- **Official OpenAPI spec:** https://raw.githubusercontent.com/discord/discord-api-spec/main/specs/openapi.json
-- **Source:** discord/discord-api-spec GitHub repository (official, maintained by Discord)
-- **Format:** OpenAPI 3.1.0 JSON
-- **Endpoint count:** 140 endpoints across 16 resource categories
-- **Size:** 1.07 MB
-- **Key categories:** guilds (46), channels (31), applications (16), lobbies (10), users (9)
-
-## Competitors (Deep Analysis)
-
-### discrawl (569 stars) - THE BENCHMARK
-- **Repo:** https://github.com/steipete/discrawl
-- **Language:** Go 1.26+
-- **Commands:** 12 (init, sync, tail, search, messages, mentions, sql, members, channels, status, doctor)
-- **Last commit:** March 8, 2026 (51 commits)
-- **Open issues:** 4
-- **Maintained:** YES - actively developed
-- **Notable features:**
-  - SQLite + FTS5 for local search
-  - Gateway WebSocket for real-time tail
-  - Bot-token only (no user-token hacks)
-  - Multi-guild schema
-  - Structured mention tracking
-  - Attachment text extraction into search index
-  - Periodic repair syncs during tail
-- **Weaknesses:**
-  - Only 12 commands - no REST API coverage
-  - No audit log support
-  - No server health/analytics commands
-  - No export functionality
-  - No --json/--select/--dry-run agent-native flags
-  - 4 open issues including "AI agent integration" request (#15)
-  - No schema migrations (#8)
-
-### DiscordChatExporter (10,700 stars)
-- **Repo:** https://github.com/Tyrrrz/DiscordChatExporter
-- **Language:** C# (.NET)
-- **Commands:** GUI + CLI with export commands
-- **Last commit:** March 21, 2026 (v2.47.1)
-- **Open issues:** 10
-- **Contributors:** 63
-- **Maintained:** YES - very active
-- **Notable features:**
-  - Export to HTML (dark/light), TXT, CSV, JSON
-  - User or bot token auth
-  - File partitioning and date range filtering
-  - Cross-platform
-  - Offline-capable exports
-- **Weaknesses:**
-  - Export-ONLY tool - no search, no sync, no real-time
-  - Requires .NET runtime
-  - Hits API on every export (no incremental/local cache)
-  - No SQLite storage
-  - No agent-native features (--json, --select, etc.)
-
-### discli (7 stars)
-- **Repo:** https://github.com/ibbybuilds/discli
-- **Language:** TypeScript (Node.js)
-- **Commands:** 40+ across 9 categories
-- **Maintained:** Active (59 commits)
-- **Notable features:**
-  - YAML output (claimed "5x fewer tokens than JSON" for AI agents)
-  - --dry-run support
-  - --confirm for destructive operations
-  - SOUL.md personality file
-  - "One command = one API call" philosophy
-- **Weaknesses:**
-  - Only 7 stars - minimal adoption
-  - No local storage/SQLite
-  - No search, no sync, no real-time
-  - No FTS5
-  - Requires Node.js
-  - Pure API wrapper with no compound commands
-
-### jackwener/discord-cli (78 stars)
-- **Repo:** https://github.com/jackwener/discord-cli
-- **Language:** Python
-- **Commands:** 16 across 4 categories
-- **Notable features:**
-  - Local-first SQLite storage
-  - Full-text search
-  - YAML/JSON structured output
-  - Export in multiple formats
-  - Analytics/timeline generation
-- **Weaknesses:**
-  - Python (slower than Go)
-  - 78 stars - moderate adoption
-  - Unclear maintenance status
-
-## User Pain Points
-
-> "Using discrawl as memory augmentation for AI agents" - discrawl issue #15 (codexGW, March 2026)
-
-> "Storage: schema migrations & versioning" - discrawl issue #8 (thenotespublisher, March 2026)
-
-> "Search/FTS: injection & tokenizer configuration" - discrawl issue #9 (thenotespublisher, March 2026)
-
-> Rate limits on the role create endpoint are "extreme" with a 24-hour cooldown - Discord support forum
-
-> Discord's built-in search is slow, incomplete, and can't do cross-channel queries - widespread community complaint
-
-> DiscordChatExporter requires .NET and hits the API fresh on every export - no local cache - implied pain from architecture
-
-## Auth Method
-- **Type:** Bot token (primary), OAuth2 (for user-facing apps)
-- **Env var convention:** `DISCORD_TOKEN` (discrawl), `DISCORD_BOT_TOKEN` (discli)
-- **Our choice:** `DISCORD_TOKEN` (matches the market leader)
-
-## Demand Signals
-- discordo (Discord TUI client) - HN discussion August 2022, showing demand for terminal Discord access
-- Discoding (AI CLI to Discord bridge) - HN Show HN February 2026, proving CLI + Discord integration demand
-- Remote-OpenCode (Discord bot for AI coding) - HN Show HN February 2026, AI agent + Discord demand
-- discrawl issue #15 explicitly asks for AI agent memory integration
-- AnswerOverflow (500+ stars) makes Discord searchable on web - proves search is the killer need
-
-## Strategic Justification
-
-**Why this CLI should exist when discrawl has 569 stars:**
-
-1. **discrawl is depth-only, we're depth + breadth.** discrawl has 12 commands focused on messages/search. It has ZERO coverage of the other 120+ Discord API endpoints. Server admins can't manage roles, channels, invites, bans, or webhooks from discrawl.
-
-2. **No audit log tool exists as a CLI.** Discord retains audit logs for 45 days. Our CLI persists them in SQLite forever and adds forensic analysis commands (filter by action, moderator, target, cross-reference with members).
-
-3. **Agent-native gap.** discrawl's issue #15 asks for AI agent integration. Our CLI ships with --json, --select, --dry-run, --stdin, --yes, --no-cache from day one. It's built for the age of AI agents.
-
-4. **discli proves breadth demand but has zero traction (7 stars).** The API wrapper approach alone doesn't resonate. Users want breadth PLUS data intelligence. We provide both.
-
-5. **Go binary, zero dependencies.** Unlike DiscordChatExporter (.NET) or jackwener/discord-cli (Python) or discli (Node.js), our CLI is a single Go binary. Install and run.
-
-## Target
-- **Command count:** 60+ (12 data layer + 7 workflows + 40+ API endpoints)
-- **Key differentiator:** discrawl-level data intelligence PLUS full REST API coverage PLUS agent-native design
-- **Quality bar:** Steinberger Grade A (80+/100)
-- **Competitive positioning:** "discrawl + discli + audit logs + analytics, in one Go binary"
diff --git a/docs/plans/2026-03-26-feat-discord-cli-v2-visionary-research.md b/docs/plans/2026-03-26-feat-discord-cli-v2-visionary-research.md
deleted file mode 100644
index efcf0759..00000000
--- a/docs/plans/2026-03-26-feat-discord-cli-v2-visionary-research.md
+++ /dev/null
@@ -1,151 +0,0 @@
----
-title: "Visionary Research: Discord CLI v2"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0"
-api: "discord"
----
-
-# Visionary Research: Discord CLI v2
-
-## Overview
-
-Discord is the dominant communication platform for developer communities, gaming, and increasingly for business. Its API is one of the richest in the communication space - REST endpoints for CRUD, a WebSocket Gateway for real-time events, and a data model that spans guilds, channels, threads, messages, members, roles, permissions, webhooks, and audit logs. The Discord CLI opportunity is unique because the market is bifurcated: export tools (DiscordChatExporter, 10.7k stars) serve archival needs, while discrawl (569 stars, 12 commands) proves the data-tool thesis with SQLite + FTS5 + Gateway sync. No single CLI combines comprehensive REST API coverage with local-first data intelligence.
-
-## API Identity
-
-- **Domain:** Communication / Community Platform
-- **Primary users:** Bot developers, server admins, community managers, DevRel teams, moderation teams
-- **Core entities:** Guilds, Channels, Messages, Users, Members, Roles, Threads, Reactions, Emojis, Webhooks, Audit Logs, Invites, Bans, Scheduled Events, Stage Instances, Auto Moderation
-- **Data profile:**
-  - Write pattern: Append-heavy (messages), mutable (guilds/channels/roles/members)
-  - Volume: HIGH (millions of messages per active server)
-  - Real-time: YES - Gateway WebSocket (mandatory for presence/typing/voice state), plus webhooks for integrations
-  - Search need: HIGH - Discord's built-in search is limited, users constantly ask for better search
-
-## Usage Patterns (Top 5 by Evidence)
-
-| Rank | Pattern | Evidence Score | Sources |
-|------|---------|---------------|---------|
-| 1 | **Server archival/export** | 10/10 | DiscordChatExporter (10.7k stars), discord-dl, discord-backup, Copycord, discord-migrate |
-| 2 | **Local search across server history** | 8/10 | discrawl (569 stars), jackwener/discord-cli (78 stars), AnswerOverflow, discord-to-sqlite |
-| 3 | **Server management/administration** | 7/10 | discli (7 stars, 40+ commands), selfbot scripts, n8n/Zapier integrations |
-| 4 | **Analytics/metrics on server activity** | 6/10 | discord-analytics (Tarasa24), Cially dashboard, james-long/discord-analytics |
-| 5 | **Real-time monitoring/alerting** | 5/10 | discord-voice-monitor, discord-user-monitor, discord-quest-watcher |
-
-## Tool Landscape (Beyond API Wrappers)
-
-### Tier 1: API Wrappers
-| Tool | Stars | Language | Commands | Status |
-|------|-------|----------|----------|--------|
-| discli | 7 | TypeScript | 40+ | Active, YAML output, agent-native |
-| fourjr/discord-cli | ~50 | Python | ~10 | Stale |
-| mrousavy/discord-cli | ~20 | JavaScript | ~5 | Stale |
-
-### Tier 2: Data Tools (the real competition)
-| Tool | Stars | Language | What It Does | Key Insight |
-|------|-------|----------|-------------|-------------|
-| **discrawl** | 569 | Go | SQLite + FTS5 + sync + tail + search + sql | **The gold standard.** 12 commands beat 316-endpoint wrappers. |
-| **jackwener/discord-cli** | 78 | Python | SQLite + sync + search + export + analytics | Proves the pattern works in Python too |
-| **discord-to-sqlite** | ~30 | Python | Import Discord data package to SQLite | Data package import, not live sync |
-
-### Tier 3: Export/Archival Tools
-| Tool | Stars | Language | What It Does |
-|------|-------|----------|-------------|
-| **DiscordChatExporter** | 10,700 | C# | Export to HTML/TXT/CSV/JSON |
-| **discord-dl** | ~200 | Go | Archive channels/guilds |
-| **Copycord** | ~150 | JavaScript | Clone servers with real-time sync |
-| **discord-server-backup** | ~100 | JavaScript | Full server backup & recreation |
-
-### Tier 4: Analytics/Dashboard Tools
-| Tool | Stars | Language | What It Does |
-|------|-------|----------|-------------|
-| **Cially** | ~50 | TypeScript | Real-time analytics dashboard |
-| **AnswerOverflow** | 500+ | TypeScript | Makes Discord threads searchable on web |
-| **discord-analytics** | ~100 | JavaScript | Server statistics & visualizations |
-
-## Workflows
-
-### 1. Server History Deep Search
-**Steps:** Sync messages -> Index with FTS5 -> Search with domain filters (--channel, --author, --before, --after)
-**Frequency:** Daily for active community managers
-**Pain point:** Discord's built-in search is slow, incomplete, and can't do cross-channel queries
-**Proposed:** `discord-cli search "error" --channel general --author bot --days 7`
-
-### 2. Server Health Dashboard
-**Steps:** Fetch member list -> Get message activity -> Calculate metrics -> Report
-**Frequency:** Weekly for server admins
-**Pain point:** No built-in way to see activity trends, stale channels, inactive members
-**Proposed:** `discord-cli health --guild <id> --days 30`
-
-### 3. Audit Log Analysis
-**Steps:** Fetch audit log -> Filter by action type -> Cross-reference with members -> Report
-**Frequency:** After incidents, weekly for moderation
-**Pain point:** Discord UI shows audit log but can't filter, search, or export it
-**Proposed:** `discord-cli audit --guild <id> --action member_ban --days 7`
-
-### 4. Channel Archival Pipeline
-**Steps:** List channels -> Sync messages -> Export to format -> Store locally
-**Frequency:** Monthly or on-demand
-**Pain point:** DiscordChatExporter requires .NET, no SQLite, no incremental sync
-**Proposed:** `discord-cli export --channel <id> --format json --since 2026-01-01`
-
-### 5. Real-time Event Monitoring
-**Steps:** Connect to Gateway -> Filter events -> Display or alert
-**Frequency:** Continuous for moderation bots
-**Pain point:** Requires a full bot framework just to watch events
-**Proposed:** `discord-cli tail --guild <id> --events message_create,member_join`
-
-## Architecture Decisions
-
-| Decision Area | Choice | Rationale |
-|--------------|--------|-----------|
-| **Persistence** | SQLite with domain-specific tables | HIGH volume + HIGH search need. discrawl proves this works. Messages, members, channels, audit logs all need proper columns for joins and filters. |
-| **Real-time** | Gateway WebSocket | Discord's Gateway is the primary real-time channel. REST polling misses events and wastes rate limit budget. Bot tokens get Gateway access. |
-| **Search** | FTS5 on message content, channel names, member names | Discord's search is the #1 pain point. FTS5 provides instant local search across all synced history. |
-| **Bulk operations** | Paginated sync with snowflake ID cursors | Discord uses snowflake IDs for pagination (?after=snowflake_id). Incremental sync is natural. |
-| **Cache** | SQLite IS the cache | No separate caching layer needed. Local DB serves as both cache and search index. |
-| **Auth** | Bot token via env var | discrawl and discli both use bot tokens. OAuth2 is for user-facing apps, not CLI tools. |
-
-## Top 5 Features for the World
-
-| Rank | Feature | Score | Evidence | Impact | Feasibility | Uniqueness | Composability | Data Fit | Maintainability | Moat |
-|------|---------|-------|----------|--------|-------------|------------|---------------|----------|-----------------|------|
-| 1 | **Full-text search with domain filters** | 14/16 | 3 (discrawl 569 stars) | 3 (every admin wants this) | 2 (FTS5 template exists) | 1 (discrawl does it) | 2 (great with pipes) | 2 (perfect fit) | 1 (generated) | 0 |
-| 2 | **Incremental sync with Gateway tail** | 13/16 | 3 (discrawl proves it) | 3 (foundation for everything) | 2 (Gateway client needed) | 1 (discrawl does it) | 2 (enables all other features) | 2 (perfect fit) | 1 | 0 |
-| 3 | **Raw SQL access to synced data** | 12/16 | 3 (discrawl has it) | 2 (power users love it) | 2 (simple to add) | 1 (discrawl has it) | 2 (ultimate composability) | 2 (perfect) | 1 | 0 |
-| 4 | **Audit log analysis with filters** | 12/16 | 2 (Reddit/SO demand) | 3 (every admin needs this) | 2 (API supports it) | 2 (no CLI does this well) | 2 (great with pipes/agents) | 2 (fits data layer) | 0 | 0 |
-| 5 | **Server health/analytics dashboard** | 11/16 | 2 (Cially, discord-analytics) | 2 (niche but valuable) | 1 (needs custom code) | 2 (no CLI does this) | 2 (JSON output for dashboards) | 2 (uses local DB) | 0 | 1 |
-
-### Feature Scoring Rationale
-
-**Feature 1 - Full-text search:** The strongest signal. discrawl's entire value proposition is "search your Discord like a database." DiscordChatExporter has 10.7k stars but NO search. The gap is enormous.
-
-**Feature 2 - Incremental sync + Gateway:** Without sync, nothing else works. discrawl proves Gateway tail is viable for a CLI. Snowflake IDs make incremental sync natural.
-
-**Feature 3 - Raw SQL:** discrawl's `sql` command is the killer feature for power users. Direct SQLite access means infinite composability.
-
-**Feature 4 - Audit log analysis:** No existing CLI handles audit logs well. Discord's UI is limited. Server admins constantly need "who did what" analysis.
-
-**Feature 5 - Server health:** Cially (50 stars) and discord-analytics prove demand. But they're bots/dashboards, not CLI tools. A `health` command using local data would be instant.
-
-## Competitive Strategy
-
-**Why this CLI should exist when discrawl has 569 stars:**
-
-1. **Breadth + depth:** discrawl has 12 commands focused on messages/search. We'll have 50+ covering the full REST API PLUS discrawl-level data intelligence. discli has 40+ API commands but zero data intelligence.
-2. **Agent-native:** Neither discrawl nor discli has --json + --select + --dry-run + --stdin + --yes + --no-cache. Our CLI is built for AI agents from day one.
-3. **Audit logs:** Nobody does this well. Discord's audit log API is rich but underserved.
-4. **Export formats:** DiscordChatExporter proves massive demand (10.7k stars) for export. We can offer export FROM our SQLite data.
-
-## Sources
-- discrawl: https://github.com/steipete/discrawl (569 stars, Go, 12 commands)
-- DiscordChatExporter: https://github.com/Tyrrrz/DiscordChatExporter (10.7k stars, C#)
-- discli: https://github.com/ibbybuilds/discli (7 stars, TypeScript, 40+ commands)
-- jackwener/discord-cli: https://github.com/jackwener/discord-cli (78 stars, Python, 16 commands)
-- Discord API docs: https://docs.discord.com/developers/intro
-- Discord API spec: https://github.com/discord/discord-api-spec
-- AnswerOverflow: https://github.com/AnswerOverflow/AnswerOverflow (500+ stars)
-- Cially: https://github.com/cially/cially (~50 stars)
-- awesome-discord: https://github.com/jacc/awesome-discord
diff --git a/docs/plans/2026-03-26-feat-discord-cli-visionary-research.md b/docs/plans/2026-03-26-feat-discord-cli-visionary-research.md
deleted file mode 100644
index f1f59d13..00000000
--- a/docs/plans/2026-03-26-feat-discord-cli-visionary-research.md
+++ /dev/null
@@ -1,106 +0,0 @@
----
-title: "Visionary Research: Discord CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0"
-api: "discord"
----
-
-# Visionary Research: Discord CLI
-
-## Overview
-
-Discord's API serves a massive developer ecosystem of bot builders, community managers, and platform integrators. The API is REST-based (v10) with a real-time Gateway (WebSocket) for live events, covering guilds (servers), channels, messages, members, roles, emojis, interactions, webhooks, and more. The official OpenAPI spec (3.1.0) is publicly available at discord/discord-api-spec on GitHub.
-
-The CLI landscape for Discord is fragmented: DiscordChatExporter (10.7k stars) dominates export but offers no local persistence or search. discrawl (564 stars) pioneered SQLite+FTS5+Gateway sync for Discord in Go with 12 commands. discordo (5.4k stars) is a TUI client, not a management CLI. discli (6 stars) targets agent-native workflows but has no data layer. jackwener/discord-cli (78 stars) combines SQLite sync with search but is Python-only and uses user tokens (TOS risk). No single tool combines bot-token REST management + local SQLite data layer + FTS5 search + Gateway tail + agent-native output modes in Go.
-
-The printing-press Discord CLI should be a discrawl-class data tool with full API coverage - not just an API wrapper, but a local-first intelligence layer over Discord's data.
-
-## API Identity
-
-- **Domain:** Communication (messaging, channels, threads, voice, communities)
-- **Primary users:** Bot developers, server administrators, community managers, AI agent builders, moderation teams
-- **Core entities:** Guilds, Channels (text/voice/forum/stage), Messages, Users, Members, Roles, Emojis, Stickers, Webhooks, Audit Logs, Threads, Scheduled Events, Invites, Auto Moderation Rules, Polls, Soundboards, Stage Instances
-- **API base URL:** `https://discord.com/api/v10`
-- **Auth:** Bot Token (primary, via `DISCORD_BOT_TOKEN` env var), OAuth2 (for user-facing apps)
-
-### Data Profile
-
-| Dimension | Assessment |
-|---|---|
-| **Write pattern** | Append-only for messages/events/audit logs, mutable for guilds/channels/roles/members |
-| **Volume** | Very high - large guilds have millions of messages, thousands of members |
-| **Real-time** | Yes - Gateway WebSocket with intents, event-driven architecture |
-| **Search need** | Very high - finding messages, members, mentions, content across channels is a core workflow |
-
-## Usage Patterns (Top 5 by Evidence)
-
-| Rank | Pattern | Evidence Score | Key Sources |
-|------|---------|---------------|-------------|
-| 1 | **Message archive & search** | 10/10 | DiscordChatExporter (10.7k stars), discrawl (564), jackwener/discord-cli (78), discord-dl, discord-server-backup, multiple Reddit/HN threads |
-| 2 | **Local search over history** | 8/10 | discrawl FTS5 search, AnswerOverflow (Discord indexing), Comly.app (HN post about Discord SEO), jackwener/discord-cli search |
-| 3 | **Server management** | 7/10 | discli (agent-native mgmt), discord-server-mirror, Copycord, backup/restore tools |
-| 4 | **Analytics & activity** | 6/10 | discord-analytics (Tarasa24), Cially dashboard, james-long/discord-analytics, discord-bot-analytics |
-| 5 | **Live monitoring & alerting** | 6/10 | discord-user-monitor, web-watcher, voice-monitor, quest-watcher, discrawl tail |
-
-## Tool Landscape (Beyond API Wrappers)
-
-| Tool | Stars | Type | What It Does | Lang |
-|------|-------|------|-------------|------|
-| DiscordChatExporter | 10,700 | Data Tool | Export messages to HTML/TXT/CSV/JSON with rich media. 63 contributors. Active (v2.47.1, 2026-03-21). | C# |
-| discordo | 5,400 | Environment Tool | Full TUI Discord client. 45 open issues. Go. | Go |
-| discrawl | 564 | Data Tool | SQLite+FTS5 sync/search/tail/members/sql. 12 commands. 4 open issues. THE benchmark. | Go |
-| AnswerOverflow | ~2,000 | Integration Tool | Makes Discord threads indexable on Google/AI | TS |
-| Copycord | ~200 | Workflow Tool | Clone/mirror entire servers in real-time | Python |
-| jackwener/discord-cli | 78 | Data Tool | SQLite-based sync/search/export with AI analysis | Python |
-| discli | 6 | API Wrapper | Agent-native server mgmt (YAML output, --dry-run) | TS |
-| discord-migrate | ~50 | Data Tool | Discord to SQLite for Matrix migration | Python |
-
-**Key insight:** The highest-starred tools are Data Tools (DiscordChatExporter, discrawl), not API wrappers. Users want to OWN their Discord data locally. discrawl is the Steinberger benchmark - 12 focused commands, SQLite+FTS5, Gateway sync, and a `sql` command for ad-hoc queries.
-
-## Workflows
-
-| # | Name | Steps | Pain Point | Proposed CLI Command |
-|---|------|-------|-----------|---------------------|
-| 1 | Archive & Search | Sync messages -> store in SQLite -> FTS5 search with filters | Manual pagination, rate limits, thread discovery | `discord-cli sync --guild <id>` + `discord-cli search "keyword" --channel general --author user` |
-| 2 | Member Activity | Fetch members -> query message counts -> rank by activity -> filter by role | No single API call gives activity; requires message+member correlation | `discord-cli activity --guild <id> --days 30 --role moderator` |
-| 3 | Audit Investigation | Fetch audit log -> filter by action type -> show timeline | Raw events, no correlation or timeline view | `discord-cli audit --guild <id> --action member_kick --days 7` |
-| 4 | Keyword Monitoring | Connect Gateway -> filter message_create -> match keywords -> alert | Requires writing a bot; no CLI does real-time keyword monitoring | `discord-cli tail --guild <id> --match "urgent|bug|down"` |
-| 5 | Compliance Export | Sync all channels -> export JSON/CSV -> date range filter | DiscordChatExporter needs manual channel selection | `discord-cli export --guild <id> --format json --since 2026-01-01` |
-
-## Architecture Decisions
-
-| Area | Need | Decision | Rationale |
-|------|------|----------|-----------|
-| **Persistence** | HIGH | SQLite with domain-specific tables + FTS5 | Millions of messages, high search need. discrawl proves this. Proper columns enable joins (messages x members x channels). |
-| **Real-time** | HIGH | Gateway WebSocket for `tail` command | Discord Gateway is canonical. REST polling misses events and burns rate limit. discrawl uses Gateway. |
-| **Search** | HIGH | FTS5 on message content, channel names, member display names | Local FTS5 is instant vs Discord's rate-limited API search. Domain filters (--channel, --author) map to SQL WHERE. |
-| **Bulk** | MEDIUM | Incremental sync with snowflake ID cursors | Discord message IDs are snowflakes (timestamp-encoded). GET /channels/{id}/messages supports `after` param. |
-| **Cache** | MEDIUM | SQLite IS the cache. `--no-cache` bypasses for live API calls. `--sync` flag triggers API fetch before query. | No separate cache layer needed. |
-
-## Top 5 Features for the World
-
-| Rank | Feature | Score | Evidence | Impact | Feasibility | Uniqueness | Composability | Data Fit | Maintain | Moat |
-|------|---------|-------|----------|--------|-------------|------------|---------------|----------|----------|------|
-| 1 | SQLite sync + FTS5 search | **15/16** | 3 | 3 | 2 | 1 | 2 | 2 | 1 | 1 |
-| 2 | Member activity analytics | **13/16** | 2 | 3 | 2 | 2 | 2 | 2 | 0 | 0 |
-| 3 | Audit log investigation | **13/16** | 2 | 2 | 2 | 2 | 2 | 2 | 0 | 1 |
-| 4 | Gateway tail (live stream) | **12/16** | 3 | 2 | 1 | 1 | 2 | 2 | 1 | 0 |
-| 5 | Agent-native server mgmt | **12/16** | 2 | 2 | 2 | 1 | 2 | 1 | 1 | 1 |
-
-All 5 score >=12: **Must-have.**
-
-## Sources
-
-- [DiscordChatExporter](https://github.com/Tyrrrz/DiscordChatExporter) - 10.7k stars, C#
-- [discordo](https://github.com/ayn2op/discordo) - 5.4k stars, Go TUI
-- [discrawl](https://github.com/steipete/discrawl) - 564 stars, Go, SQLite+FTS5 (Steinberger benchmark)
-- [jackwener/discord-cli](https://github.com/jackwener/discord-cli) - 78 stars, Python
-- [discli](https://github.com/ibbybuilds/discli) - 6 stars, TS, agent-native
-- [AnswerOverflow](https://github.com/AnswerOverflow/AnswerOverflow) - Discord indexing
-- [Discord API Docs](https://docs.discord.com/developers/intro)
-- [Discord OpenAPI Spec](https://github.com/discord/discord-api-spec)
-- [HN: Discord indexing tool](https://news.ycombinator.com/item?id=46898145)
-- [HN: discordo](https://news.ycombinator.com/item?id=32474133)
-- [HN: Discoding - AI CLIs via Discord](https://news.ycombinator.com/item?id=47048164)
diff --git a/docs/plans/2026-03-26-feat-discord-cli-vs-discrawl-analysis-plan.md b/docs/plans/2026-03-26-feat-discord-cli-vs-discrawl-analysis-plan.md
deleted file mode 100644
index 2e64879c..00000000
--- a/docs/plans/2026-03-26-feat-discord-cli-vs-discrawl-analysis-plan.md
+++ /dev/null
@@ -1,237 +0,0 @@
----
-title: "Honest Analysis: Generated discord-cli vs discrawl"
-type: feat
-status: active
-date: 2026-03-26
----
-
-# Honest Analysis: Generated discord-cli vs discrawl
-
-## Overview
-
-The printing-press generated a 323-command Discord CLI in ~90 minutes and scored it Grade A (96/110) on its own scorecard. discrawl has 11 commands and 551 stars. This analysis asks the uncomfortable question: which one actually works?
-
-**Verdict: discrawl is a real tool. Our discord-cli is a compilation-verified hallucination.**
-
-## The Uncomfortable Truth, Dimension by Dimension
-
-### 1. Sync: The Core Value Proposition
-
-**discrawl:**
-- Guild-scoped: iterates `guilds -> channels -> messages` with the correct nested API paths (`/guilds/{id}/channels`, `/channels/{id}/messages`)
-- Snowflake cursor tracking per channel in `sync_state` table
-- Dual-direction pagination (forward for catch-up, backward for backfill)
-- Configurable concurrency (8-32 workers) with auto-sizing
-- Incomplete backfill detection and automatic re-batching
-- Channel skip logic: skips already-complete channels, saving API calls
-- Member refresh on a separate 24-hour cycle
-- Rate limit handling delegated to discordgo's battle-tested implementation
-- Error classification: retryable (timeout, 429, 5xx) vs permanently unavailable (403, 404)
-
-**Ours:**
-- `defaultSyncResources()` returns `[]string{}`. Running `discord-cli sync` with no flags syncs zero things and reports success.
-- Hits `GET /channels`, `GET /messages` - flat paths that don't exist in Discord's API. Discord requires guild-scoped routes.
-- No guild context at all in the sync flow. The "sync" is a template that assumes flat REST resources.
-- The nice domain-specific `UpsertMessage`/`UpsertChannel` methods in our store? **Nothing calls them.** The sync uses the generic `Upsert` which writes to the `resources` table, bypassing our entire domain schema.
-
-**Rating: discrawl 10/10, ours 1/10.** Our sync compiles. It does not sync.
-
-### 2. Search: Why Anyone Would Install This
-
-**discrawl:**
-- FTS5 on `message_fts` virtual table indexing `author_name`, `channel_name`, and `content`
-- BM25 ranking with secondary sort by `created_at DESC`
-- `normalized_content` includes embed titles, attachment filenames, poll questions, reply text - not just raw message text
-- Domain filters: `--channel`, `--author`, `--guild` mapped to SQL WHERE clauses on unindexed columns
-- Fallback: if FTS5 query fails, transparently degrades to LIKE queries
-- FTS versioning: when schema changes, drops and rebuilds the index automatically
-
-**Ours:**
-- `search.go` calls `db.Search()` which queries `resources_fts` - the generic table
-- Our beautifully designed `messages_fts` table and `SearchMessages` method exist in the store but are never called
-- No domain filters (no `--channel`, `--author` flags on the search command)
-- The search would return results from the generic `resources` table, which is never populated by our broken sync
-
-**Rating: discrawl 10/10, ours 1/10.** Our search command exists. It searches an empty table.
-
-### 3. Tail: Real-time Monitoring
-
-**discrawl:**
-- Real Discord Gateway WebSocket via `discordgo.Session.Open()`
-- Handles 6 event types: MessageCreate, MessageUpdate, MessageDelete, ChannelCreate/Update, GuildMemberAdd/Update/Remove
-- 4-16 worker pool with buffered queue, 30-second handler timeout, panic recovery
-- Periodic repair syncs every 6 hours to catch missed events
-- Appends to `message_events` audit log for edit/delete tracking
-- Clean shutdown on SIGINT/SIGTERM
-
-**Ours:**
-- REST polling on a timer hitting `GET /<resource>` - invalid paths
-- No deduplication - re-emits all data every tick
-- No Gateway WebSocket connection at all
-- Would exhaust rate limits within minutes on any real server
-
-**Rating: discrawl 10/10, ours 0/10.** Ours is architectural fiction.
-
-### 4. Auth: Getting Past the Front Door
-
-**discrawl:**
-- `Bot <token>` prefix for bot tokens (Discord's requirement)
-- OpenClaw integration for token reuse
-- Token from env var (`DISCORD_TOKEN`) or config file
-- File permissions locked to 0600
-
-**Ours:**
-- `Bearer <token>` prefix always. Discord bots require `Bot <token>`.
-- Every API request would return 401 Unauthorized with a bot token.
-- We have a full OAuth2 PKCE flow that's impressive... but Discord bots don't use OAuth2 for API access.
-
-**Rating: discrawl 10/10, ours 2/10.** Our auth is correct for the wrong protocol.
-
-### 5. Data Layer: What's Actually in SQLite
-
-**discrawl:**
-- 9 domain tables with typed columns, NOT JSON blobs
-- `normalized_content` on messages = raw content + embed titles + attachment filenames + poll questions + reply text
-- `mention_events` = structural mention tracking (who mentioned whom, target type, timestamp)
-- `message_events` = append-only audit log preserving edit/delete history
-- `message_attachments` = metadata + extracted text content (for text files up to 256KB)
-- Member profile extraction: recursively walks JSON to find bio, pronouns, location, social links
-- 10 focused indexes on foreign keys and common query patterns
-
-**Ours:**
-- 8 domain tables with typed columns - structurally similar to discrawl
-- BUT: nothing populates them. The sync writes to the `resources` generic table.
-- `UpsertMessage` correctly decomposes nested JSON and updates FTS. It's good code. Nobody calls it.
-- No normalized content. No mention extraction. No attachment text extraction. No message events audit log.
-- The store is a beautiful ghost town.
-
-**Rating: discrawl 10/10, ours 4/10.** Our schema is well-designed. It's just empty.
-
-### 6. Workflow Commands: The "Product"
-
-**discrawl (11 commands, all functional):**
-- `messages` has `--sync` flag for just-in-time sync before query. Eliminates the two-step workflow.
-- `members search` uses a separate `member_fts` table with bio/social profile text
-- `members show` provides a full profile with message stats, first/last message, and recent messages
-- `mentions` queries a dedicated structural `mention_events` table
-- `sql --unsafe --confirm` enables writes with explicit safety gate
-
-**Ours (5 workflow commands, partially functional):**
-- `activity`, `stale`, `mentions`, `sql` query local SQLite with correct SQL... against empty tables
-- `audit` is the one workflow command that makes a live API call and would actually work (if auth wasn't broken)
-- The SQL in our commands is correct. The joins are valid. The queries would return real results IF the tables had data. They don't because sync is broken.
-
-**Rating: discrawl 10/10, ours 3/10.** Our SQL is correct. The precondition (data in tables) is never met.
-
-### 7. Code Quality & Testing
-
-**discrawl:**
-- 80% test coverage floor enforced by CI
-- golangci-lint + staticcheck + gofumpt + gosec in CI
-- govulncheck for dependency vulnerabilities
-- gitleaks for secret scanning
-- Interface-based dependency injection throughout (mockable time, mockable clients, factory injection)
-- 51 commits, clean git history, semantic versioning
-
-**Ours:**
-- Zero tests
-- Module path is `github.com/USER/discord-cli` - literally the string "USER"
-- Dummy import guards on every file (`var _ = strings.ReplaceAll // ensure import`)
-- No CI, no linting, no coverage
-- `go build` and `go vet` pass. That's the entire quality assurance.
-
-**Rating: discrawl 9/10, ours 2/10.**
-
-## The Scorecard Gamed Itself
-
-Our Steinberger scorecard gave us 96/110 (Grade A). Here's how each dimension maps to reality:
-
-| Dimension | Score | Reality |
-|---|---|---|
-| Output Modes 10/10 | Correct | --json, --csv, --select, etc. are real and functional |
-| Auth 10/10 | **Wrong** | Auth sends `Bearer` instead of `Bot`. Every request would 401. |
-| Error Handling 10/10 | Partially true | classifyAPIError is good. But errors from non-existent endpoints are never classified. |
-| Terminal UX 10/10 | Correct | Color, progress, formatting work |
-| README 5/10 | **Misleading** | Documents commands that don't work. The cookbook shows workflows that would fail. |
-| Doctor 10/10 | Correct | Actually checks config, auth, connectivity |
-| Agent Native 8/10 | Correct | --json, --select, --dry-run, --yes all work |
-| Local Cache 10/10 | **Wrong** | Store exists but is never populated by any working code path |
-| Breadth 6/10 | **Superficial** | 323 commands exist. Most hit non-existent API paths. |
-| Vision 9/10 | **Wrong** | The vision (SQLite + FTS5 + sync + search + tail) exists in code but none of it works end-to-end |
-| Workflows 8/10 | **Wrong** | Workflow commands have correct SQL that queries empty tables |
-
-**Honest score:** ~35/110 (32%). The infrastructure (output, config, error types, CLI framework) is real. Everything that touches the Discord API or local data is broken.
-
-## What the Printing Press Actually Produced
-
-It's not nothing. And it's not what the scorecard claims. Here's what's real:
-
-### Genuinely Good (would survive code review)
-- **store.go**: Domain-specific SQLite schema with proper types, FTS5, upsert methods, transaction handling
-- **client.go**: HTTP client with rate limit handling, caching, dry-run mode
-- **config.go**: TOML config with env var overrides, token persistence
-- **helpers.go**: Output formatting with auto-table, field selection, paginated fetching
-- **Error types**: Structured exit codes, API error classification
-- **CLI framework**: Root command with all agent-native flags properly wired
-
-### Broken (would fail on first real use)
-- **sync.go**: Empty resource list, flat API paths, doesn't call domain-specific upsert methods
-- **search.go**: Queries wrong table (generic `resources_fts` instead of `messages_fts`)
-- **tail.go**: REST polling instead of Gateway WebSocket, hits non-existent paths
-- **auth**: `Bearer` prefix instead of `Bot` prefix
-- **Module path**: `github.com/USER/discord-cli`
-- **Generated API commands**: Snowflake IDs as `int` instead of `string`, missing complex body flags
-- **Data pipeline**: Domain tables exist but nothing populates them
-
-### The Gap
-
-The printing-press is good at generating **infrastructure** (HTTP clients, CLI frameworks, SQLite stores, config layers, output formatting). It is bad at generating **domain logic** (API topology, auth protocols, data pipelines, real-time connections).
-
-discrawl's 11 commands represent ~3,000 lines of carefully written domain logic that understands Discord. Our 323 commands represent ~18,000 lines of infrastructure templates with a thin veneer of Discord awareness.
-
-## What Would It Take to Make discord-cli Real?
-
-### Phase 1: Fix the Pipeline (make data flow)
-1. Fix auth to use `Bot` prefix for bot tokens
-2. Rewrite sync to be guild-scoped (iterate guilds -> channels -> messages using correct Discord API paths)
-3. Wire sync to call `UpsertMessage`, `UpsertChannel`, `UpsertMember` instead of generic `Upsert`
-4. Fix search to call `SearchMessages` instead of generic `Search`
-5. Fix module path from `USER` to a real GitHub org
-
-### Phase 2: Fix the Types
-1. Change all snowflake ID parameters from `int` to `string`
-2. Add `--guild-id` as a persistent config option so every command doesn't need it
-
-### Phase 3: Add Real-time
-1. Add `discordgo` dependency
-2. Rewrite tail to use Gateway WebSocket with proper intents
-
-### Phase 4: Add Domain Intelligence
-1. Normalized content (embeds + attachments + polls in search text)
-2. Mention extraction to structured table
-3. Message event audit log (edit/delete tracking)
-4. Member profile extraction
-5. Attachment text extraction
-
-This is roughly equivalent to writing discrawl from scratch - which makes sense, because discrawl IS what a working Discord CLI looks like.
-
-## The Real Lesson
-
-The printing-press pipeline spent 90 minutes on research, planning, generation, auditing, and scoring. It produced 7 plan artifacts and a 96/110 score. discrawl spent ~3 weeks of focused development by an experienced Go developer who understands Discord's API deeply.
-
-The printing-press is valuable as a **scaffolding accelerator** - it produces a real CLI skeleton with good infrastructure in minutes. But the scorecard's self-assessment creates a dangerous illusion of completeness. A CLI that compiles, has --help text, and passes `go vet` is not the same as a CLI that works.
-
-**discrawl's 11 commands that work > our 323 commands that compile.**
-
-## Acceptance Criteria
-
-- [ ] This analysis is read by the user and informs printing-press improvements
-- [ ] The scorecard is updated to test actual API connectivity, not just code structure
-- [ ] Future runs distinguish "infrastructure quality" from "domain correctness"
-
-## Sources
-
-- [discrawl](https://github.com/steipete/discrawl) - 551 stars, v0.2.0, live DB at ~/.discrawl/discrawl.db
-- Generated discord-cli at ~/cli-printing-press/discord-cli/
-- Discord API documentation at docs.discord.com
-- discrawl issues: FTS injection, schema migrations, AI agent memory use case
diff --git a/docs/plans/2026-03-26-feat-discord-cli-vs-discrawl-gap-analysis-plan.md b/docs/plans/2026-03-26-feat-discord-cli-vs-discrawl-gap-analysis-plan.md
deleted file mode 100644
index 0e3c48d2..00000000
--- a/docs/plans/2026-03-26-feat-discord-cli-vs-discrawl-gap-analysis-plan.md
+++ /dev/null
@@ -1,318 +0,0 @@
----
-title: "discord-cli vs discrawl: Gap Analysis and Upgrade Plan"
-type: feat
-status: active
-date: 2026-03-26
----
-
-# discord-cli vs discrawl: Gap Analysis and Upgrade Plan
-
-## Overview
-
-discrawl (540 stars, steipete/discrawl) is a Go CLI that mirrors Discord guild data into local SQLite with FTS5 search. It has 12 commands but each one is deeply purpose-built for Discord. The generated discord-cli has 330+ commands but its data layer is generic. This plan compares them feature-by-feature and identifies what discord-cli needs to steal from discrawl's playbook.
-
-**Bottom line:** discrawl is a far superior data tool. discord-cli is a far superior API wrapper. The ideal CLI combines both.
-
----
-
-## Feature-by-Feature Comparison
-
-### Data Layer
-
-| Capability | discrawl | discord-cli | Winner |
-|---|---|---|---|
-| **Schema** | Discord-native: separate tables for messages, members, channels with proper columns | Generic `resources` table with JSON blobs | discrawl by a mile |
-| **FTS5 indexing** | Extracts message content, attachment text, embed text, filenames | Indexes raw JSON blobs | discrawl |
-| **Sync** | `--guild`, `--channels`, `--since`, `--concurrency` (auto-sized), `--with-embeddings` | Generic `--resources` flag, no Discord-specific filtering | discrawl |
-| **Tail** | Gateway WebSocket with repair loops (real-time, misses nothing) | REST polling at intervals (slow, misses events, rate-limited) | discrawl |
-| **Search** | `--guild`, `--channel`, `--author`, `--limit`, `--include-empty` | `--type`, `--limit` only (no Discord-specific filters) | discrawl |
-| **Messages** | Filter by channel, author, time range (`--days`/`--hours`/`--since`), `--sync` on-demand, `--all`, `--last N` | No equivalent command | discrawl |
-| **Mentions** | Structured user/role mention tracking with time filters | No equivalent | discrawl |
-| **Members** | list, show (with message history), search (username, bio, pronouns, social handles, URLs) | member-report (role counts only) | discrawl |
-| **Channels** | list, show (metadata inspection) | Via API commands only (no local query) | discrawl |
-| **SQL** | Raw read-only SQL access to the database | No equivalent | discrawl |
-| **Status** | Archive statistics and sync progress | No equivalent | discrawl |
-| **Init** | Multi-guild discovery with OpenClaw integration, config file generation | No equivalent | discrawl |
-| **Embeddings** | Optional OpenAI semantic search, batch-processed in background | No equivalent | discrawl |
-
-**Score: discrawl 13/13 on data capabilities.**
-
-### API Management
-
-| Capability | discrawl | discord-cli | Winner |
-|---|---|---|---|
-| **REST API coverage** | 0 commands | 316 commands across 20 resource groups | discord-cli |
-| **Guild management** | None (read-only crawler) | Create, update, delete guilds | discord-cli |
-| **Channel management** | None | Full CRUD + permissions | discord-cli |
-| **Role management** | None | Full CRUD + bulk operations | discord-cli |
-| **Ban management** | None | Ban, unban, list bans | discord-cli |
-| **Webhook management** | None | Full CRUD + execute | discord-cli |
-| **Application commands** | None | Full slash command management | discord-cli |
-| **Auto moderation** | None | Full rule management | discord-cli |
-| **Scheduled events** | None | Full CRUD | discord-cli |
-| **Stage instances** | None | Full management | discord-cli |
-
-**Score: discord-cli 10/10 on API management. discrawl has zero.**
-
-### Workflow Commands
-
-| Capability | discrawl | discord-cli | Winner |
-|---|---|---|---|
-| **channel-health** | Nothing | Stale channel detection + activity report | discord-cli |
-| **audit-report** | Nothing | Audit log analysis by action/user/date | discord-cli |
-| **member-report** | Nothing (members search is close) | Role distribution, bot/human counts | discord-cli |
-| **server-snapshot** | Nothing | Backup guild config to JSON | discord-cli |
-| **message-stats** | Nothing | Message volume, top contributors, hourly activity | discord-cli |
-| **webhook-test** | Nothing | Send test payloads to webhooks | discord-cli |
-| **prune-preview** | Nothing | Preview prune without executing | discord-cli |
-
-**Score: discord-cli 7/0. discrawl has no workflow commands.**
-
-### Agent-Native Features
-
-| Feature | discrawl | discord-cli | Winner |
-|---|---|---|---|
-| **--json** | Yes | Yes | Tie |
-| **--select** | No | Yes | discord-cli |
-| **--dry-run** | No | Yes | discord-cli |
-| **--stdin** | No | Yes | discord-cli |
-| **--yes** | No | Yes | discord-cli |
-| **--no-cache** | No | Yes | discord-cli |
-| **--csv** | No | Yes | discord-cli |
-| **--plain** | No | Yes | discord-cli |
-| **--quiet** | No | Yes | discord-cli |
-| **Typed exit codes** | Unknown | 0,2,3,4,5,7,10 | discord-cli |
-
-**Score: discord-cli 8/1.**
-
----
-
-## The Verdict
-
-| Dimension | discrawl | discord-cli |
-|---|---|---|
-| Data tool | 13/13 | 0/13 |
-| API management | 0/10 | 10/10 |
-| Workflow commands | 0/7 | 7/7 |
-| Agent-native | 1/9 | 9/9 |
-| **Total** | **14/39** | **26/39** |
-
-discord-cli wins on breadth. discrawl wins on depth where it matters most - the data layer.
-
-The printing-press skill itself says it: **"discrawl has 12 commands and 539 stars. Depth beats breadth."** The 316 API wrapper commands are table stakes. The 7 workflow commands are differentiation. But the data layer - sync, search, tail, messages, mentions, members, SQL - is where discrawl demolishes us.
-
----
-
-## What discord-cli Must Steal from discrawl
-
-### Priority 1: Discord-Native Schema (Critical)
-
-Replace the generic `resources` table with Discord-specific tables:
-
-```sql
--- Messages table with proper columns
-CREATE TABLE messages (
-    id TEXT PRIMARY KEY,
-    channel_id TEXT NOT NULL,
-    guild_id TEXT,
-    author_id TEXT NOT NULL,
-    content TEXT,
-    timestamp DATETIME NOT NULL,
-    edited_timestamp DATETIME,
-    type INTEGER DEFAULT 0,
-    data JSON NOT NULL,
-    synced_at DATETIME DEFAULT CURRENT_TIMESTAMP
-);
-CREATE INDEX idx_messages_channel ON messages(channel_id);
-CREATE INDEX idx_messages_author ON messages(author_id);
-CREATE INDEX idx_messages_timestamp ON messages(timestamp);
-
--- FTS on extracted content (not JSON blobs)
-CREATE VIRTUAL TABLE messages_fts USING fts5(
-    id, channel_id, author_id, content,
-    tokenize='porter unicode61'
-);
-
--- Members table
-CREATE TABLE members (
-    user_id TEXT NOT NULL,
-    guild_id TEXT NOT NULL,
-    username TEXT,
-    display_name TEXT,
-    bio TEXT,
-    joined_at DATETIME,
-    roles JSON,
-    data JSON NOT NULL,
-    PRIMARY KEY (user_id, guild_id)
-);
-
--- Mentions table
-CREATE TABLE mentions (
-    message_id TEXT NOT NULL,
-    target_id TEXT NOT NULL,
-    target_type TEXT NOT NULL, -- 'user' or 'role'
-    guild_id TEXT,
-    channel_id TEXT,
-    timestamp DATETIME,
-    FOREIGN KEY (message_id) REFERENCES messages(id)
-);
-```
-
-**Why:** Without this, search returns JSON blobs. With this, search returns `#channel-name @author: message content` with filters.
-
-### Priority 2: Discord-Aware Sync
-
-Replace the generic sync with Discord-specific backfill:
-
-- `discord-cli sync --guild GUILD_ID` - sync one guild
-- `discord-cli sync --channels CHANNEL_ID,CHANNEL_ID` - sync specific channels
-- `discord-cli sync --since 2026-01-01` - sync from date
-- `discord-cli sync --full` - full historical backfill
-- `discord-cli sync --concurrency 16` - auto-sized by default
-- Incremental: track last message ID per channel as cursor
-- Paginate with `GET /channels/{id}/messages?before=OLDEST_ID&limit=100`
-
-### Priority 3: Messages Command
-
-Add a `messages` command that queries the local database:
-
-```bash
-# Messages from a channel in the last 7 days
-discord-cli messages --channel general --days 7
-
-# Messages from a specific author
-discord-cli messages --author steipete --days 30
-
-# All messages, syncing on-demand if needed
-discord-cli messages --channel help --all --sync
-
-# Last 100 messages
-discord-cli messages --channel dev --last 100 --json
-```
-
-### Priority 4: Search Filtering
-
-Add Discord-specific filters to search:
-
-```bash
-# Search within a guild
-discord-cli search "error" --guild 123456789012345678
-
-# Search within a channel
-discord-cli search "timeout" --channel help
-
-# Search by author
-discord-cli search "fix" --author steipete
-
-# Combine filters
-discord-cli search "deploy" --guild 123 --channel releases --json
-```
-
-### Priority 5: SQL Command
-
-Expose raw read-only SQL access:
-
-```bash
-# Run a query
-discord-cli sql 'SELECT channel_id, COUNT(*) as cnt FROM messages GROUP BY channel_id ORDER BY cnt DESC LIMIT 10'
-
-# From stdin
-echo 'SELECT * FROM members WHERE bio LIKE "%rust%"' | discord-cli sql -
-```
-
-### Priority 6: Mentions Command
-
-Track and query structured mentions:
-
-```bash
-# Who mentioned me?
-discord-cli mentions --target @mybot --days 7
-
-# Role mentions in a channel
-discord-cli mentions --channel announcements --type role
-
-# JSON for agent consumption
-discord-cli mentions --target USER_ID --json
-```
-
-### Priority 7: Gateway Tail (Future)
-
-Replace REST polling with Gateway WebSocket:
-
-- Connect to `wss://gateway.discord.gg/` with bot token
-- Subscribe to MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE events
-- Write events to SQLite in real-time
-- Periodic repair syncs to catch missed events
-- Graceful reconnection with resume
-
-This is the hardest change (Gateway protocol is complex) but gives true real-time vs polling delay.
-
----
-
-## What discord-cli Already Does Better
-
-These are advantages to preserve and highlight:
-
-1. **316 API wrapper commands** - discrawl can't create channels, manage roles, ban users, or execute webhooks
-2. **7 workflow commands** - channel-health, audit-report, server-snapshot are unique value
-3. **Agent-native features** - --select, --dry-run, --stdin, --yes, --csv, --plain, typed exits
-4. **Complex body support** - --stdin for embeds, components, attachments
-5. **Doctor with full diagnostics** - Both have this, parity
-
----
-
-## Implementation Phases
-
-### Phase 1: Schema Migration (2-3 hours)
-- Replace generic `resources` table with Discord-specific tables
-- Update `store.go` with `UpsertMessage`, `UpsertMember`, `UpsertChannel` methods
-- Migrate FTS5 to index extracted content, not JSON blobs
-- Add mention extraction during message upsert
-
-### Phase 2: Discord-Aware Sync (3-4 hours)
-- Rewrite `sync.go` with Discord-specific logic
-- Paginate through channels using `?before=` cursor
-- Track per-channel sync state
-- Add `--guild`, `--channels`, `--since`, `--concurrency` flags
-- Auto-discover guilds from bot token
-
-### Phase 3: Messages + Search Filtering (2-3 hours)
-- New `messages` command with local database queries
-- Add `--guild`, `--channel`, `--author` filters to search
-- Add `--days`, `--hours`, `--since` time filters
-
-### Phase 4: SQL + Mentions (1-2 hours)
-- Read-only SQL command
-- Mentions command with structured queries
-
-### Phase 5: Gateway Tail (4-6 hours, optional)
-- WebSocket client for Discord Gateway
-- Event-to-SQLite pipeline
-- Repair loops
-- Resume on reconnect
-
-**Total estimated work: 8-12 hours for Phases 1-4, plus 4-6 hours for Gateway.**
-
----
-
-## Acceptance Criteria
-
-- [ ] Discord-specific schema with messages, members, channels, mentions tables
-- [ ] FTS5 indexes extracted content (not JSON blobs)
-- [ ] Sync supports `--guild`, `--channels`, `--since`, `--concurrency`
-- [ ] `messages` command with channel/author/time filters
-- [ ] `search` supports `--guild`, `--channel`, `--author` filters
-- [ ] `sql` command for raw read-only queries
-- [ ] `mentions` command for structured mention queries
-- [ ] All existing 316 API commands still work
-- [ ] All 7 workflow commands still work
-- [ ] `go build ./...` and `go vet ./...` pass
-
----
-
-## Sources
-
-- [steipete/discrawl](https://github.com/steipete/discrawl) - 540 stars, the reference implementation
-- [jackwener/discord-cli](https://github.com/jackwener/discord-cli) - 78 stars, Python alternative with similar approach
-- [Tyrrrz/DiscordChatExporter](https://github.com/Tyrrrz/DiscordChatExporter) - 10.7k stars, export-only (no search/sync)
-- [Discord API Docs](https://docs.discord.com/developers/intro) - Official REST + Gateway documentation
diff --git a/docs/plans/2026-03-26-feat-linear-cli-data-layer-spec.md b/docs/plans/2026-03-26-feat-linear-cli-data-layer-spec.md
deleted file mode 100644
index 99ac6356..00000000
--- a/docs/plans/2026-03-26-feat-linear-cli-data-layer-spec.md
+++ /dev/null
@@ -1,414 +0,0 @@
----
-title: "Data Layer Specification: Linear CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0.7"
-api: "linear"
----
-
-# Data Layer Specification: Linear CLI
-
-## Overview
-
-This document specifies the SQLite data layer for linear-cli based on Linear's GraphQL schema analysis, social signal mining, and data gravity scoring. The data layer enables instant local queries, full-text search, and cross-entity workflow commands that would be impossible or rate-limit-expensive via the live API.
-
-## Entity Classification
-
-| Entity | Type | Estimated Volume | Update Frequency | Temporal Field |
-|--------|------|-----------------|------------------|---------------|
-| **Issue** | Accumulating | 1k-50k per workspace | High (daily state changes) | `updatedAt` |
-| **Comment** | Accumulating | 2x-5x issue count | Medium | `updatedAt` |
-| **Project** | Reference | 10-200 | Low (weekly) | `updatedAt` |
-| **Cycle** | Reference | 10-100 per team | Low (bi-weekly) | `updatedAt` |
-| **Team** | Reference | 5-50 | Rare | `updatedAt` |
-| **User** | Reference | 10-500 | Rare | `updatedAt` |
-| **IssueLabel** | Reference | 20-200 | Rare | `updatedAt` |
-| **WorkflowState** | Reference | 5-15 per team | Rare | `updatedAt` |
-| **Document** | Accumulating | 50-500 | Medium | `updatedAt` |
-| **Initiative** | Reference | 5-50 | Low | `updatedAt` |
-| **Milestone** | Reference | 5-30 | Low | `updatedAt` |
-| **Attachment** | Accumulating | 1x-2x issue count | Low | `updatedAt` |
-| **IssueRelation** | Accumulating | 0.1x-0.5x issue count | Low | `updatedAt` |
-| **CustomView** | Reference | 10-100 | Low | `updatedAt` |
-| **IssueHistory** | Append-only | 3x-10x issue count | N/A (immutable) | `createdAt` |
-| **Notification** | Append-only | High | N/A | `createdAt` |
-
-## Social Signal Mining Results
-
-| # | Signal | Evidence | Score |
-|---|--------|----------|-------|
-| 1 | Users want offline/local issue search (no existing tool provides this) | 4 CLIs exist, none with local DB. Phase 0 confirmed "discrawl gap." | 8/10 |
-| 2 | Sprint velocity/burndown analytics from local data | Screenful, Count.co sell this as SaaS. CLI alternative has demand. | 7/10 |
-| 3 | Stale issue detection as a common grooming task | Linear added auto-close. Morgen guide recommends monthly stale review. | 7/10 |
-| 4 | Cross-entity queries (issues by label + team + state + date) | All 4 competing CLIs support filters but hit API each time. Local enables instant. | 8/10 |
-| 5 | Issue export/backup to local storage | Linear has CSV export. GitHub has linear/linear/packages/import. Users want programmatic access. | 6/10 |
-| 6 | Duplicate detection via text similarity | Linear Backlog Grooming Agent (Cotera) addresses this. No CLI tool. | 6/10 |
-| 7 | Activity timeline across teams | No existing tool provides cross-team activity feed via CLI. | 6/10 |
-
-## Data Gravity Scoring
-
-| Entity | Volume | QueryFreq | JoinDemand | SearchNeed | TemporalValue | **Total** | Classification |
-|--------|--------|-----------|------------|------------|---------------|-----------|---------------|
-| **Issue** | 3 | 3 | 3 | 3 | 3 | **15/12** | Primary |
-| **Comment** | 2 | 2 | 2 | 3 | 1 | **10/12** | Primary |
-| **Project** | 1 | 2 | 3 | 1 | 1 | **8/12** | Primary |
-| **Cycle** | 1 | 2 | 2 | 0 | 2 | **7/12** | Support |
-| **Team** | 0 | 2 | 3 | 0 | 0 | **5/12** | Support |
-| **User** | 1 | 2 | 3 | 1 | 0 | **7/12** | Support |
-| **IssueLabel** | 1 | 2 | 2 | 1 | 0 | **6/12** | Support |
-| **WorkflowState** | 0 | 2 | 3 | 0 | 0 | **5/12** | Support |
-| **IssueRelation** | 1 | 2 | 2 | 0 | 1 | **6/12** | Support |
-| **Document** | 1 | 1 | 1 | 2 | 1 | **6/12** | Support |
-| **Initiative** | 0 | 1 | 1 | 1 | 1 | **4/12** | API-only |
-| **Milestone** | 0 | 1 | 1 | 0 | 1 | **3/12** | API-only |
-| **Attachment** | 1 | 1 | 1 | 0 | 0 | **3/12** | API-only |
-| **CustomView** | 0 | 1 | 0 | 0 | 0 | **1/12** | API-only |
-
-**Primary entities (score >= 8):** Issue (15), Comment (10), Project (8)
-**Support entities (score 5-7):** Cycle, Team, User, IssueLabel, WorkflowState, IssueRelation, Document
-
-## SQLite Schema
-
-```sql
--- Primary Tables
-
-CREATE TABLE issues (
-    id TEXT PRIMARY KEY,
-    identifier TEXT NOT NULL,          -- e.g. "ENG-123"
-    title TEXT NOT NULL,
-    description TEXT,
-    priority INTEGER,                   -- 0=none, 1=urgent, 2=high, 3=medium, 4=low
-    estimate REAL,
-    due_date TEXT,                      -- ISO 8601
-    sort_order REAL,
-    state_id TEXT REFERENCES workflow_states(id),
-    team_id TEXT REFERENCES teams(id),
-    assignee_id TEXT REFERENCES users(id),
-    creator_id TEXT REFERENCES users(id),
-    project_id TEXT REFERENCES projects(id),
-    cycle_id TEXT REFERENCES cycles(id),
-    parent_id TEXT REFERENCES issues(id),
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    archived_at TEXT,
-    canceled_at TEXT,
-    completed_at TEXT,
-    started_at TEXT,
-    data JSON NOT NULL,                 -- full API response
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_issues_team ON issues(team_id);
-CREATE INDEX idx_issues_assignee ON issues(assignee_id);
-CREATE INDEX idx_issues_project ON issues(project_id);
-CREATE INDEX idx_issues_cycle ON issues(cycle_id);
-CREATE INDEX idx_issues_state ON issues(state_id);
-CREATE INDEX idx_issues_priority ON issues(priority);
-CREATE INDEX idx_issues_updated ON issues(updated_at);
-CREATE INDEX idx_issues_identifier ON issues(identifier);
-
-CREATE VIRTUAL TABLE issues_fts USING fts5(
-    title,
-    description,
-    identifier,
-    content='issues',
-    content_rowid='rowid'
-);
-
-CREATE TABLE comments (
-    id TEXT PRIMARY KEY,
-    body TEXT NOT NULL,
-    issue_id TEXT REFERENCES issues(id),
-    user_id TEXT REFERENCES users(id),
-    parent_id TEXT REFERENCES comments(id),
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_comments_issue ON comments(issue_id);
-CREATE INDEX idx_comments_user ON comments(user_id);
-CREATE INDEX idx_comments_updated ON comments(updated_at);
-
-CREATE VIRTUAL TABLE comments_fts USING fts5(
-    body,
-    content='comments',
-    content_rowid='rowid'
-);
-
-CREATE TABLE projects (
-    id TEXT PRIMARY KEY,
-    name TEXT NOT NULL,
-    description TEXT,
-    state TEXT,                         -- planned, started, paused, completed, canceled
-    icon TEXT,
-    color TEXT,
-    lead_id TEXT REFERENCES users(id),
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_projects_lead ON projects(lead_id);
-CREATE INDEX idx_projects_state ON projects(state);
-
-CREATE VIRTUAL TABLE projects_fts USING fts5(
-    name,
-    description,
-    content='projects',
-    content_rowid='rowid'
-);
-
--- Support Tables
-
-CREATE TABLE teams (
-    id TEXT PRIMARY KEY,
-    name TEXT NOT NULL,
-    key TEXT NOT NULL,                  -- e.g. "ENG"
-    description TEXT,
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE TABLE users (
-    id TEXT PRIMARY KEY,
-    name TEXT NOT NULL,
-    display_name TEXT NOT NULL,
-    email TEXT,
-    avatar_url TEXT,
-    is_me INTEGER DEFAULT 0,           -- flag for the authenticated user
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE TABLE workflow_states (
-    id TEXT PRIMARY KEY,
-    name TEXT NOT NULL,
-    type TEXT NOT NULL,                 -- triage, backlog, unstarted, started, completed, canceled
-    team_id TEXT REFERENCES teams(id),
-    position REAL,
-    color TEXT,
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_workflow_states_team ON workflow_states(team_id);
-CREATE INDEX idx_workflow_states_type ON workflow_states(type);
-
-CREATE TABLE issue_labels (
-    id TEXT PRIMARY KEY,
-    name TEXT NOT NULL,
-    color TEXT,
-    team_id TEXT REFERENCES teams(id),
-    parent_id TEXT REFERENCES issue_labels(id),
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE TABLE issue_label_assignments (
-    issue_id TEXT REFERENCES issues(id),
-    label_id TEXT REFERENCES issue_labels(id),
-    PRIMARY KEY (issue_id, label_id)
-);
-
-CREATE TABLE cycles (
-    id TEXT PRIMARY KEY,
-    name TEXT,
-    number INTEGER,
-    team_id TEXT REFERENCES teams(id),
-    starts_at TEXT,
-    ends_at TEXT,
-    completed_at TEXT,
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_cycles_team ON cycles(team_id);
-
-CREATE TABLE issue_relations (
-    id TEXT PRIMARY KEY,
-    type TEXT NOT NULL,                 -- blocks, isBlockedBy, duplicate, related
-    issue_id TEXT REFERENCES issues(id),
-    related_issue_id TEXT REFERENCES issues(id),
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_issue_relations_issue ON issue_relations(issue_id);
-CREATE INDEX idx_issue_relations_related ON issue_relations(related_issue_id);
-CREATE INDEX idx_issue_relations_type ON issue_relations(type);
-
-CREATE TABLE documents (
-    id TEXT PRIMARY KEY,
-    title TEXT NOT NULL,
-    content TEXT,
-    project_id TEXT REFERENCES projects(id),
-    creator_id TEXT REFERENCES users(id),
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
--- Sync Metadata
-
-CREATE TABLE sync_cursors (
-    entity_type TEXT PRIMARY KEY,
-    last_cursor TEXT,
-    last_updated_at TEXT,
-    last_sync_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-```
-
-## Sync Strategy
-
-**Cursor field:** `updatedAt` (DateTime, supported as filter and orderBy on all paginated queries)
-
-**Validated:** Linear's GraphQL API supports `filter: { updatedAt: { gte: "2026-03-01T00:00:00Z" } }` on issues, comments, projects, and all entity types. Ordering by `updatedAt` confirmed in API docs.
-
-**Pagination:** Relay-style cursor with `first` (max 250 per the API) and `after` (endCursor from pageInfo).
-
-**Sync algorithm per entity type:**
-
-```
-1. Read sync_cursors for entity_type
-2. If last_updated_at exists:
-     Query with filter: { updatedAt: { gte: last_updated_at } }, orderBy: updatedAt
-   Else:
-     Full sync: Query all, orderBy: updatedAt
-3. For each page (first: 100, after: endCursor):
-     Upsert each record into SQLite (INSERT OR REPLACE)
-     Update FTS5 triggers
-4. Update sync_cursors with max(updatedAt) from results
-```
-
-**Batch size:** 100 records per page (conservative to stay within complexity limits). Max 250 allowed but 100 is safer with nested fields.
-
-**Deletion handling:** Linear uses `archivedAt` soft deletes. Sync checks `archivedAt` field and marks records locally. Hard deletes (rare) detected by periodic full reconciliation.
-
-## Domain-Specific Search Filters
-
-| CLI Flag | SQL WHERE Clause | Entity |
-|----------|-----------------|--------|
-| `--team <key>` | `WHERE team_id = (SELECT id FROM teams WHERE key = ?)` | issues, workflow_states, cycles |
-| `--assignee <name>` | `WHERE assignee_id = (SELECT id FROM users WHERE display_name LIKE ?)` | issues |
-| `--project <name>` | `WHERE project_id = (SELECT id FROM projects WHERE name LIKE ?)` | issues |
-| `--state <name>` | `WHERE state_id = (SELECT id FROM workflow_states WHERE name = ?)` | issues |
-| `--priority <level>` | `WHERE priority = ?` (0-4) | issues |
-| `--label <name>` | `WHERE id IN (SELECT issue_id FROM issue_label_assignments WHERE label_id = (SELECT id FROM issue_labels WHERE name = ?))` | issues |
-| `--days <n>` | `WHERE updated_at >= datetime('now', '-N days')` | issues, comments |
-| `--since <date>` | `WHERE updated_at >= ?` | issues, comments |
-| `--cycle <name>` | `WHERE cycle_id = (SELECT id FROM cycles WHERE name = ? OR number = ?)` | issues |
-| `--unassigned` | `WHERE assignee_id IS NULL` | issues |
-| `--overdue` | `WHERE due_date < date('now') AND completed_at IS NULL AND canceled_at IS NULL` | issues |
-
-## Compound Cross-Entity Queries
-
-### 1. Stale issues by team with state context
-```sql
-SELECT i.identifier, i.title, i.priority, ws.name as state, u.display_name as assignee,
-       julianday('now') - julianday(i.updated_at) as days_stale
-FROM issues i
-JOIN workflow_states ws ON i.state_id = ws.id
-LEFT JOIN users u ON i.assignee_id = u.id
-JOIN teams t ON i.team_id = t.id
-WHERE t.key = ?
-  AND i.updated_at < datetime('now', '-30 days')
-  AND ws.type NOT IN ('completed', 'canceled')
-  AND i.archived_at IS NULL
-ORDER BY days_stale DESC;
-```
-
-### 2. Cycle velocity (completion rate per cycle)
-```sql
-SELECT c.name as cycle, c.number,
-       COUNT(*) as total_issues,
-       SUM(CASE WHEN ws.type = 'completed' THEN 1 ELSE 0 END) as completed,
-       ROUND(100.0 * SUM(CASE WHEN ws.type = 'completed' THEN 1 ELSE 0 END) / COUNT(*), 1) as pct
-FROM issues i
-JOIN cycles c ON i.cycle_id = c.id
-JOIN workflow_states ws ON i.state_id = ws.id
-JOIN teams t ON i.team_id = t.id
-WHERE t.key = ?
-GROUP BY c.id
-ORDER BY c.starts_at DESC
-LIMIT 5;
-```
-
-### 3. Orphan issues (no project, no cycle, unassigned)
-```sql
-SELECT i.identifier, i.title, i.priority, ws.name as state, t.key as team,
-       julianday('now') - julianday(i.created_at) as days_old
-FROM issues i
-JOIN workflow_states ws ON i.state_id = ws.id
-JOIN teams t ON i.team_id = t.id
-WHERE i.project_id IS NULL
-  AND i.cycle_id IS NULL
-  AND i.assignee_id IS NULL
-  AND ws.type NOT IN ('completed', 'canceled')
-  AND i.archived_at IS NULL
-ORDER BY i.priority ASC, days_old DESC;
-```
-
-### 4. Blocked issues with blocker context
-```sql
-SELECT i.identifier, i.title,
-       bi.identifier as blocked_by_identifier, bi.title as blocked_by_title,
-       u.display_name as blocked_by_assignee
-FROM issue_relations ir
-JOIN issues i ON ir.issue_id = i.id
-JOIN issues bi ON ir.related_issue_id = bi.id
-LEFT JOIN users u ON bi.assignee_id = u.id
-JOIN workflow_states ws ON i.state_id = ws.id
-WHERE ir.type = 'isBlockedBy'
-  AND ws.type NOT IN ('completed', 'canceled')
-ORDER BY i.priority ASC;
-```
-
-### 5. Full-text search with domain filters
-```sql
-SELECT i.identifier, i.title, i.priority, ws.name as state,
-       u.display_name as assignee, t.key as team
-FROM issues_fts fts
-JOIN issues i ON i.rowid = fts.rowid
-JOIN workflow_states ws ON i.state_id = ws.id
-LEFT JOIN users u ON i.assignee_id = u.id
-JOIN teams t ON i.team_id = t.id
-WHERE issues_fts MATCH ?
-  AND t.key = ?
-  AND ws.type NOT IN ('completed', 'canceled')
-ORDER BY rank;
-```
-
-## Tail Strategy
-
-| Method | Available? | Decision |
-|--------|-----------|----------|
-| WebSocket/Gateway | No | N/A |
-| SSE | No | N/A |
-| **REST Polling** | **Yes** | Primary method |
-
-**Decision:** REST polling with `updatedAt` cursor. Linear has no WebSocket or SSE endpoint. Webhooks require a server (not suitable for CLI). The `tail` command will poll with `filter: { updatedAt: { gte: lastSeen } }` on a configurable interval (default: 30s).
-
-## Phase 4 Priority 0 Commands (Data Layer)
-
-1. `sync` - Incremental sync of all entities to SQLite
-2. `search` - FTS5 search with domain filters
-3. `sql` - Raw read-only SQL queries
-4. `issues` - Query local DB with rich filters (replaces API-dependent `issue list`)
-5. `tail` - Poll for new/updated issues and stream to stdout
diff --git a/docs/plans/2026-03-26-feat-linear-cli-power-user-workflows.md b/docs/plans/2026-03-26-feat-linear-cli-power-user-workflows.md
deleted file mode 100644
index efd77471..00000000
--- a/docs/plans/2026-03-26-feat-linear-cli-power-user-workflows.md
+++ /dev/null
@@ -1,153 +0,0 @@
----
-title: "Power User Workflows: Linear CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0.5"
-api: "linear"
----
-
-# Power User Workflows: Linear CLI
-
-## API Archetype: Project Management
-
-Linear maps to the **Project Management** archetype with signals: issues, tasks/sub-issues, sprints (cycles), states (workflow states), teams, labels, milestones, initiatives. Expected workflows: stale issues, orphan detection, velocity, burndown, standup, triage, bulk operations.
-
-## All 12 Workflow Ideas
-
-### 1. Stale Issue Detector
-- **Steps:** Query issues by team -> filter where updatedAt < N days ago -> exclude archived/canceled -> group by state -> count and list
-- **Frequency:** Weekly (eng manager hygiene)
-- **Pain:** Manual: create a custom view, scan each team, mentally filter. No way to script this in Linear's UI.
-- **API support:** Issues query supports `filter: { updatedAt: { lt: "..." } }` + `team` filter. Confirmed.
-- **Proposed:** `linear stale --days 30 --team ENG --json`
-
-### 2. Cycle Velocity Report
-- **Steps:** Get active or recent cycle -> count issues by state (done/total) -> compute completion % -> optionally compare to last N cycles for trend
-- **Frequency:** Weekly/bi-weekly (sprint retro)
-- **Pain:** Linear has basic insights but no CLI-accessible velocity export. External tools like Screenful/Count.co fill this gap.
-- **API support:** `cycles` query with `filter: { team: { id: { eq: "..." } } }`, cycle has `issues` connection with state filtering. Confirmed.
-- **Proposed:** `linear velocity --team ENG --cycles 5 --json`
-
-### 3. Orphan Issue Detection
-- **Steps:** Query issues -> filter where project is null AND cycle is null AND assignee is null -> group by team -> report
-- **Frequency:** Monthly (backlog grooming)
-- **Pain:** No built-in view for "issues that belong nowhere." These accumulate silently.
-- **API support:** Issues filter supports `project: { null: true }`, `cycle: { null: true }`, `assignee: { null: true }`. Confirmed.
-- **Proposed:** `linear orphans --team ENG --json`
-
-### 4. Standup Report
-- **Steps:** Get issues assigned to current user -> filter by recent activity (updatedAt in last N days) -> group by state -> format as "yesterday/today/blockers"
-- **Frequency:** Daily
-- **Pain:** Every developer opens Linear to prepare standup. A CLI command pipes directly to Slack or clipboard.
-- **API support:** `issues` filter by `assignee: { isMe: { eq: true } }` and `updatedAt`. Confirmed.
-- **Proposed:** `linear standup --days 1 --json`
-
-### 5. Triage Queue
-- **Steps:** Get issues in Triage state -> sort by priority + createdAt -> show with context (labels, assignee suggestions)
-- **Frequency:** Daily (designated triage person)
-- **Pain:** Linear has Triage Intelligence (Business/Enterprise) but no CLI export.
-- **API support:** `issues` filter by state type (triage). WorkflowState has `type` field. Confirmed.
-- **Proposed:** `linear triage --team ENG --json`
-
-### 6. Label Audit
-- **Steps:** List all labels -> for each, count issues with that label -> find labels with 0 issues -> find issues with no labels
-- **Frequency:** Monthly (workspace hygiene)
-- **Pain:** No way to see unused labels. They accumulate over years. Label sprawl is a real problem.
-- **API support:** `issueLabels` query + `issues` filter by label. Confirmed.
-- **Proposed:** `linear label-audit --team ENG --json`
-
-### 7. Duplicate Finder
-- **Steps:** Sync issues locally -> run FTS5 similarity matching on title + description -> rank by similarity score -> report probable duplicates
-- **Frequency:** Monthly (backlog grooming)
-- **Pain:** Linear has no built-in duplicate detection. This is a common complaint.
-- **API support:** Requires local DB (can't do text similarity via API). Data layer enables this.
-- **Proposed:** `linear duplicates --team ENG --threshold 0.8 --json`
-
-### 8. SLA Monitor
-- **Steps:** Query issues with priority Urgent/High -> check time since creation vs. SLA threshold -> flag overdue
-- **Frequency:** Daily (support/ops teams)
-- **Pain:** No built-in SLA tracking in Linear. Teams use external tools.
-- **API support:** Issues have `priority` (0-4) and `createdAt`. Confirmed.
-- **Proposed:** `linear sla --urgent 4h --high 24h --team SUPPORT --json`
-
-### 9. Bulk State Transition
-- **Steps:** Query issues matching filter -> update state for all matches in one batch
-- **Frequency:** Weekly (sprint close, backlog cleanup)
-- **Pain:** Linear UI requires manual multi-select. No bulk API shortcut.
-- **API support:** `issueUpdate` mutation supports state change. Batch via multiple mutations.
-- **Proposed:** `linear bulk-move --from "In Review" --to "Done" --team ENG --dry-run`
-
-### 10. Team Health Dashboard
-- **Steps:** For each team member: count assigned issues, count overdue, count blocked -> compute health metrics
-- **Frequency:** Weekly (eng manager 1:1 prep)
-- **Pain:** Requires clicking through each person's view in Linear.
-- **API support:** Issues filter by assignee + state + dueDate. Confirmed.
-- **Proposed:** `linear health --team ENG --json`
-
-### 11. Blocked Issue Report
-- **Steps:** Query issues with "blocked" relation or "blocked" label -> list with blockers
-- **Frequency:** Daily (standup, unblocking)
-- **Pain:** No dedicated view for blocked issues across teams.
-- **API support:** `issueRelations` query, relation type `blocks`/`isBlockedBy`. Confirmed.
-- **Proposed:** `linear blocked --team ENG --json`
-
-### 12. Activity Timeline
-- **Steps:** Query recent issue history events -> filter by type (state change, assignment, comment) -> format as timeline
-- **Frequency:** Daily (async teams, status updates)
-- **Pain:** Linear's activity feed is per-issue. No cross-workspace timeline.
-- **API support:** `issueHistory` connection on issues. Confirmed.
-- **Proposed:** `linear activity --team ENG --days 7 --json`
-
-## Validation Against API Capabilities
-
-| Workflow | All endpoints exist? | Filterable? | Write ops needed? | Local DB helps? |
-|----------|---------------------|-------------|-------------------|-----------------|
-| Stale | Yes | Yes (updatedAt filter) | No | Yes - instant |
-| Velocity | Yes | Yes (cycle + team) | No | Yes - aggregation |
-| Orphans | Yes | Yes (null filters) | No | Yes - instant |
-| Standup | Yes | Yes (assignee + date) | No | Yes - instant |
-| Triage | Yes | Yes (state type) | No | Partial |
-| Label Audit | Yes | Yes (label filter) | No | Yes - cross-query |
-| Duplicates | N/A (local only) | N/A | No | Required |
-| SLA | Yes | Yes (priority + date) | No | Yes - instant |
-| Bulk Move | Yes | Yes | Yes (mutations) | No - live API |
-| Health | Yes | Yes (assignee) | No | Yes - aggregation |
-| Blocked | Yes | Yes (relations) | No | Yes - join query |
-| Activity | Yes | Partial | No | Yes - timeline |
-
-## Scoring
-
-| # | Workflow | Frequency | Pain | Feasibility | Uniqueness | Total |
-|---|---------|-----------|------|-------------|------------|-------|
-| 1 | Stale | 2 | 3 | 3 | 3 | **11/12** |
-| 2 | Velocity | 2 | 2 | 2 | 2 | **8/12** |
-| 3 | Orphans | 1 | 3 | 3 | 3 | **10/12** |
-| 4 | Standup | 3 | 2 | 3 | 2 | **10/12** |
-| 5 | Triage | 3 | 2 | 3 | 1 | **9/12** |
-| 6 | Label Audit | 1 | 2 | 3 | 3 | **9/12** |
-| 7 | Duplicates | 1 | 3 | 1 | 3 | **8/12** |
-| 8 | SLA | 3 | 3 | 3 | 3 | **12/12** |
-| 9 | Bulk Move | 2 | 2 | 2 | 1 | **7/12** |
-| 10 | Health | 2 | 2 | 2 | 3 | **9/12** |
-| 11 | Blocked | 3 | 2 | 2 | 2 | **9/12** |
-| 12 | Activity | 3 | 2 | 2 | 2 | **9/12** |
-
-## Top 7 for Implementation (Phase 4 Mandatory)
-
-1. **SLA Monitor** (12/12) - `linear sla --urgent 4h --high 24h --team SUPPORT`
-2. **Stale Issue Detector** (11/12) - `linear stale --days 30 --team ENG`
-3. **Orphan Detection** (10/12) - `linear orphans --team ENG`
-4. **Standup Report** (10/12) - `linear standup --days 1`
-5. **Triage Queue** (9/12) - `linear triage --team ENG`
-6. **Health Dashboard** (9/12) - `linear health --team ENG`
-7. **Blocked Report** (9/12) - `linear blocked --team ENG`
-
-## Implementation Notes
-
-All 7 workflows benefit from the local SQLite data layer:
-- Stale, Orphans, Standup, SLA, Health, Blocked: query local DB with joins, instant results
-- Triage: primarily live API (issues in triage are fresh), but DB helps with context enrichment
-- All support `--sync` flag to refresh local DB before querying
-- All support `--json` output for agent consumption
-- All are composable: `linear stale --days 30 --json | jq '.[].identifier'`
diff --git a/docs/plans/2026-03-26-feat-linear-cli-research.md b/docs/plans/2026-03-26-feat-linear-cli-research.md
deleted file mode 100644
index 31bc50b2..00000000
--- a/docs/plans/2026-03-26-feat-linear-cli-research.md
+++ /dev/null
@@ -1,115 +0,0 @@
----
-title: "Research: Linear CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "1"
-api: "linear"
----
-
-# Research: Linear CLI
-
-## Spec Discovery
-
-- Official GraphQL SDL: https://raw.githubusercontent.com/linear/linear/master/packages/sdk/src/schema.graphql
-- Source: Linear's official GitHub repo (linear/linear)
-- Format: GraphQL SDL (NOT OpenAPI - Linear is GraphQL-only)
-- Type count: ~400+ types, covering entities, inputs, filters, comparators, payloads, enums
-- Key query types: issues, projects, cycles, teams, users, comments, documents, milestones, initiatives
-- All queries use Relay-style cursor pagination (first/after, last/before)
-- Filtering supported on all paginated queries via typed filter inputs
-
-## Competitors (Deep Analysis)
-
-### schpet/linear-cli (521 stars) - THE LEADER
-- Repo: https://github.com/schpet/linear-cli
-- Language: TypeScript/Deno
-- Commands: ~40+ across issues, teams, projects, milestones, documents
-- Last commit: January 2025 (cb749d3)
-- Open issues: 16
-- Maintained: Yes (411 commits, active development)
-- Notable features:
-  - Git branch-aware issue detection (detect issue from current branch)
-  - Jj commit trailer support
-  - "Agent friendly" design with Claude Code skills
-  - Multi-workspace auth with TOML config
-  - PR creation via `gh` CLI integration
-- Weaknesses:
-  - No --json on projects (issue #127)
-  - No env var auth for CI/CD (issue #147)
-  - No local persistence or search
-  - No workflow commands (stale, velocity, health)
-  - No bulk operations
-  - No SQLite data layer
-
-### nooesc/linear-4-terminal (8 stars)
-- Repo: https://github.com/nooesc/linear-4-terminal
-- Language: Rust (99.1%)
-- Commands: 30+ across issues, projects, teams, comments, search, git, bulk
-- Last commit: July 2025
-- Open issues: 0
-- Maintained: Lightly
-- Notable features: Interactive TUI with dual-panel layout, multi-select bulk operations
-- Weaknesses: Low adoption, no local persistence, no workflow commands
-
-### AdiKsOnDev/linear-cli (8 stars)
-- Repo: https://github.com/AdiKsOnDev/linear-cli
-- Language: Python
-- Commands: 40+
-- Last commit: Unknown (recent)
-- Open issues: 2
-- Notable features: Bulk operations, YAML output, interactive mode, shell completions
-- Weaknesses: Python dependency overhead, no local persistence, no workflow commands
-
-### iatsiuk/linear-cli (0 stars)
-- Repo: https://github.com/iatsiuk/linear-cli
-- Language: Go
-- Commands: 15+
-- Open issues: 0
-- Notable: Go-based (same language as ours), covers basics
-
-## User Pain Points
-
-> "The project command is missing several operations that exist in the GraphQL API, forcing a fallback to raw API calls. No way to update a project via CLI... project list outputs a human-readable table with slugs but no UUIDs, with no --json flag." - schpet/linear-cli#127
-
-> "Allow reading the API key from the environment... would require an extra step to push credentials in a file within a GitHub Actions workflow." - schpet/linear-cli#147
-
-> "For users who use Graphite and a stacking workflow, the default checkout command results in an untracked state within the stack." - schpet/linear-cli#60
-
-> Linear itself warns: "Calls to our GraphQL API are rate limited... we discourage polling the API to fetch updates" - suggesting local persistence is the right architecture.
-
-## Auth Method
-
-- Type: API key (personal token) + OAuth 2.0 (application)
-- Env var convention: `LINEAR_API_KEY` (used by iatsiuk/linear-cli, recommended in Linear docs)
-- GraphQL endpoint: `https://api.linear.app/graphql`
-- Auth header: `Authorization: <api-key>` (no Bearer prefix for API keys)
-
-## Demand Signals
-
-- HN "A CLI for Using Linear with GitHub" - creator says "second most used CLI tool after git"
-- HN "Show HN: OpenSwarm - Multi-Agent Claude CLI Orchestrator for Linear/GitHub" (March 2026) - multi-agent demand
-- 4 independent CLI projects exist, proving persistent demand
-- schpet/linear-cli at 521 stars demonstrates significant user base
-- Linear's developer docs explicitly support CLI/agent use cases
-
-## Strategic Justification
-
-**Why this CLI should exist when schpet/linear-cli has 521 stars:**
-
-1. **No existing CLI has local persistence.** Every query hits the API. Our SQLite + FTS5 data layer enables instant local search, cross-entity queries, and workflow commands that are impossible with live API calls.
-
-2. **No existing CLI has workflow commands.** `stale`, `velocity`, `orphans`, `standup`, `health`, `blocked`, `sla` - these are what engineering managers actually need. Existing CLIs are CRUD wrappers.
-
-3. **No existing CLI has raw SQL access.** `linear sql "SELECT ..."` is unprecedented for a project management CLI.
-
-4. **Agent-native from the ground up.** While schpet/linear-cli is "agent friendly," our CLI adds --json, --select, --dry-run, --stdin, --yes, --no-cache as first-class global flags. Every command is pipe-ready.
-
-5. **Go binary vs TypeScript/Deno.** Single binary, no runtime dependency, instant startup. The Go ecosystem advantage for CLI distribution.
-
-## Target
-
-- **Command count:** 50+ (beat schpet's 40+ with workflow commands + data layer commands)
-- **Key differentiator:** SQLite data layer with FTS5 search + 7 workflow commands
-- **Quality bar:** Steinberger Grade A (80+/100)
-- **Resources:** Issues (CRUD), Projects (CRUD), Cycles (CRUD), Teams (list/view), Users (list/view), Comments (CRUD), Labels (CRUD), Documents (CRUD), Milestones (CRUD), Relations (CRUD), Workflow States (list), plus sync, search, sql, tail, stale, velocity, orphans, standup, triage, health, blocked, sla, doctor, auth, config
diff --git a/docs/plans/2026-03-26-feat-linear-cli-visionary-research.md b/docs/plans/2026-03-26-feat-linear-cli-visionary-research.md
deleted file mode 100644
index a7870b9d..00000000
--- a/docs/plans/2026-03-26-feat-linear-cli-visionary-research.md
+++ /dev/null
@@ -1,106 +0,0 @@
----
-title: "Visionary Research: Linear CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0"
-api: "linear"
----
-
-# Visionary Research: Linear CLI
-
-## Overview
-
-Linear is a project management and issue tracking platform built for software engineering teams. It offers a GraphQL-only API with cursor-based (Relay-style) pagination, OAuth 2.0 and API key authentication, webhooks for real-time updates, and ~400+ GraphQL types. The API is well-documented but complex - query complexity scoring limits aggressive data fetching, and rate limiting discourages polling (webhooks preferred).
-
-The competitive landscape reveals 4 existing CLIs (schpet/linear-cli leading at 521 stars) plus a rich ecosystem of sync tools (synclinear.com, linear-sync). However, NO existing tool provides local persistence with full-text search - every CLI hits the live API on every query. This is the primary strategic opportunity.
-
-## API Identity
-
-- **Domain:** Project Management / Issue Tracking
-- **Primary users:** Software engineers, engineering managers, DevOps/platform teams
-- **Core entities:** Issues, Projects, Cycles, Teams, Users, Comments, Labels, WorkflowStates, Documents, Initiatives, Milestones, CustomViews, Attachments
-- **Data profile:**
-  - Write pattern: Mutable (issues change states, get assigned, re-prioritized)
-  - Volume: Medium-high (thousands to tens of thousands of issues per workspace)
-  - Real-time: Webhooks (HTTP push on create/update/delete), no WebSocket/SSE
-  - Search need: **HIGH** - users constantly search issues by title, description, assignee, label, project, state
-- **API type:** GraphQL only (single endpoint, Relay-style cursor pagination)
-- **Auth:** API key (personal) + OAuth 2.0 (applications)
-- **Rate limiting:** Complexity-based (0.1 per property, 1 per object, connections multiply by pagination arg)
-
-## Usage Patterns (Top 5 by Evidence)
-
-| Rank | Pattern | Evidence Score | Sources |
-|------|---------|---------------|---------|
-| 1 | **Issue management from CLI** (list, create, update, triage) | 10/10 | 4 competing CLIs, HN posts, multiple GitHub repos |
-| 2 | **Git branch <-> issue linking** (create branch from issue, detect issue from branch) | 8/10 | schpet/linear-cli (521 stars) focuses on this, linear-4-terminal has git integration |
-| 3 | **Linear <-> GitHub sync** (bidirectional issue sync) | 7/10 | synclinear.com (calcom), jtormey/linear-sync, spacedriveapp/linear-sync |
-| 4 | **Bulk operations** (batch status changes, assignments, label updates) | 6/10 | AdiKsOnDev/linear-cli has bulk ops, multiple GitHub issues requesting this |
-| 5 | **Stale issue detection and cleanup** (find old unassigned issues) | 6/10 | Engineering manager workflow, no existing tool provides this as a command |
-
-## Tool Landscape (Beyond API Wrappers)
-
-| Tool | Type | Stars | What It Does |
-|------|------|-------|-------------|
-| schpet/linear-cli | Workflow Tool | 521 | Git-aware issue management, PR creation, agent-friendly skills |
-| synclinear.com (calcom) | Integration Tool | - | Bidirectional Linear <-> GitHub issue sync |
-| reverse-linear-sync-engine | Data Tool | - | Reverse engineering of Linear's local-first sync engine |
-| Swarmia | Analytics Tool | - | Team productivity insights from Linear data |
-| linear-4-terminal | API Wrapper | 8 | Interactive TUI + CLI with 30+ commands (Rust) |
-| AdiKsOnDev/linear-cli | API Wrapper | 8 | 40+ commands with bulk ops (Python) |
-| iatsiuk/linear-cli | API Wrapper | 0 | 15+ commands (Go) |
-| OpenSwarm | Workflow Tool | - | Multi-agent Claude CLI orchestrator for Linear/GitHub |
-
-**Key insight:** No existing tool provides local SQLite persistence with full-text search for Linear issues. Every CLI hits the API on every query. The "discrawl gap" is wide open.
-
-## Workflows
-
-| # | Name | Steps | Proposed CLI Feature |
-|---|------|-------|---------------------|
-| 1 | **Stale Issue Triage** | Query issues by team -> filter by updatedAt -> group by state -> report | `linear stale --days 30 --team ENG` |
-| 2 | **Sprint Velocity** | Get current cycle -> count issues by state -> compute completion % -> trend | `linear velocity --cycle current --team ENG` |
-| 3 | **Orphan Detection** | Find issues with no project, no cycle, unassigned -> report | `linear orphans --team ENG` |
-| 4 | **Standup Report** | Get issues assigned to me -> filter by recent activity -> format | `linear standup --days 1` |
-| 5 | **Label Audit** | List all labels -> find unused labels -> find issues with no labels | `linear label-audit --team ENG` |
-
-## Architecture Decisions
-
-| Area | Decision | Rationale |
-|------|----------|-----------|
-| **Persistence** | SQLite with domain-specific tables | Issues are high-volume, mutable, and heavily searched. JSON blobs won't support the joins and filters users need. |
-| **Real-time** | REST polling with `updatedAt` cursor (no WebSocket/SSE available) | Linear's API has no WebSocket or SSE. Webhooks are push-only (need a server). Polling with `filter: { updatedAt: { gte: cursor } }` is the viable CLI approach. |
-| **Search** | FTS5 on issue title + description + comment body | Users search issues constantly. Local FTS5 is instant vs. API round-trips with complexity cost. |
-| **Bulk** | GraphQL mutations with batch variables | The API supports individual mutations; batch via multiple mutations in one request. |
-| **Cache** | SQLite IS the cache. `--no-cache` bypasses local DB and hits API directly. | Single persistence layer avoids cache invalidation complexity. |
-
-## Top 5 Features for the World
-
-| Rank | Feature | Score | Breakdown | Description |
-|------|---------|-------|-----------|-------------|
-| 1 | **Local sync + FTS5 search** | 14/16 | Evidence:3, Impact:3, Feasibility:2, Unique:2, Compose:2, DataFit:2, Maintain:0, Moat:0 | Sync issues to SQLite, search instantly with `linear search "auth bug" --team ENG --state "In Progress"` |
-| 2 | **Stale issue detector** | 13/16 | Evidence:2, Impact:3, Feasibility:2, Unique:2, Compose:2, DataFit:2, Maintain:1, Moat:0 | `linear stale --days 30 --team ENG` - no existing tool does this |
-| 3 | **Cycle velocity/burndown** | 12/16 | Evidence:2, Impact:2, Feasibility:2, Unique:2, Compose:2, DataFit:2, Maintain:1, Moat:0 | `linear velocity --cycle current` - powered by local DB aggregation |
-| 4 | **Standup generator** | 11/16 | Evidence:2, Impact:3, Feasibility:2, Unique:2, Compose:1, DataFit:1, Maintain:0, Moat:0 | `linear standup --days 1` - my recent activity across teams |
-| 5 | **Raw SQL access** | 10/16 | Evidence:1, Impact:2, Feasibility:2, Unique:3, Compose:2, DataFit:2, Maintain:0, Moat:0 | `linear sql "SELECT count(*) FROM issues WHERE state = 'Done' AND updated_at > date('now', '-7 days')"` |
-
-## Demand Signals
-
-- HN "A CLI for Using Linear with GitHub" (2025) - creator says it's their "second most used CLI tool after git"
-- HN "Show HN: OpenSwarm - Multi-Agent Claude CLI Orchestrator for Linear/GitHub" (2026) - agent orchestration demand
-- schpet/linear-cli explicitly designed as "agent friendly" with Claude Code skills
-- Linear's own developer docs warn against API polling, suggesting local persistence is the right architectural choice
-- 4 competing CLIs exist but none provide local persistence or workflow commands
-
-## Sources
-
-- https://linear.app/developers - Official API docs
-- https://github.com/schpet/linear-cli - Leading competitor (521 stars)
-- https://github.com/nooesc/linear-4-terminal - Rust CLI (8 stars)
-- https://github.com/AdiKsOnDev/linear-cli - Python CLI (8 stars)
-- https://github.com/iatsiuk/linear-cli - Go CLI (0 stars)
-- https://github.com/dalys/awesome-linear - Ecosystem tools list
-- https://github.com/calcom/synclinear.com - Linear<->GitHub sync
-- https://news.ycombinator.com/item?id=44222504 - HN: Linear CLI discussion
-- https://news.ycombinator.com/item?id=43922108 - HN: Show HN Linear CLI
-- https://news.ycombinator.com/item?id=47160980 - HN: OpenSwarm multi-agent
diff --git a/docs/plans/2026-03-26-feat-notion-cli-data-layer-spec-v2.md b/docs/plans/2026-03-26-feat-notion-cli-data-layer-spec-v2.md
deleted file mode 100644
index b20454b0..00000000
--- a/docs/plans/2026-03-26-feat-notion-cli-data-layer-spec-v2.md
+++ /dev/null
@@ -1,371 +0,0 @@
----
-title: "Data Layer Specification: Notion CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0.7"
-api: "notion"
----
-
-# Data Layer Specification: Notion CLI
-
-## Overview
-
-Notion's API exposes structured (databases/data sources) and unstructured (pages/blocks) content. The data layer needs to handle both. The killer feature is FTS5 full-text search across block content - Notion's API only supports title search, so local full-text search is a genuine capability upgrade.
-
-Offline mode was Notion's #1 all-time feature request. A CLI with local SQLite persistence directly addresses this demand signal for developer workflows.
-
-## Entity Classification
-
-| Entity | Type | Est. Volume | Update Frequency | Temporal Field | Persistence Need |
-|--------|------|-------------|-----------------|----------------|-----------------|
-| **Pages** | Accumulating | 100-10k per workspace | Weekly edits | `last_edited_time` | SQLite + FTS5 |
-| **Blocks** | Accumulating | 1k-100k per workspace | Frequent edits | `last_edited_time` | SQLite + FTS5 |
-| **Databases** | Reference | 10-100 per workspace | Rarely changed | `last_edited_time` | SQLite table |
-| **Database Items** | Accumulating | 100-50k per database | Varies | `last_edited_time` | SQLite + FTS5 |
-| **Users** | Reference | 1-1000 per workspace | Rarely changed | N/A | SQLite table |
-| **Comments** | Append-only | 0-1000 per page | Rarely | `created_time` | SQLite table |
-| **File Uploads** | Append-only | Varies | Never updated | `created_time` | API-only (files too large for SQLite) |
-| **Views** | Reference | 1-10 per database | Rarely changed | N/A | API-only |
-| **Search Results** | Ephemeral | N/A | N/A | N/A | No persistence |
-
-**Heuristics applied:**
-- Pages: has `created_time`/`last_edited_time` + paginated search -> Accumulating
-- Blocks: child of pages, has `last_edited_time`, recursive structure -> Accumulating
-- Databases: referenced by pages/items via `parent.database_id` -> Reference
-- Users: referenced by pages via `created_by`/`last_edited_by` -> Reference (small cardinality)
-- Comments: has `created_time`, no update endpoint -> Append-only
-
-## Social Signal Mining Results
-
-| Signal | Source | Evidence Score | What It Reveals |
-|--------|--------|---------------|-----------------|
-| notion-into-sqlite (GitHub) | Direct SQLite tool | 6 | Users want database content in SQLite for queries |
-| Offline #1 feature request (Notion official) | Notion blog + X post | 8 | Massive demand for local access to workspace data |
-| alfred-notion-search (GitHub) | Offline page search | 5 | Users want fast local search without API round-trips |
-| notion_data CSV export (GitHub) | Data analysis tool | 4 | Users export to Pandas for analytics - want local queryable data |
-| notion-export-kernel (GitHub) | Backup/export tool | 5 | Users want recursive page+block export |
-| Notionlytics (SaaS) | Analytics product | 3 | Demand for workspace usage analytics |
-| 5+ backup tools (GitHub) | Multiple repos | 7 | Strongest signal: backup/sync is the #1 use case |
-
-**Score >= 6 findings that inform data layer:**
-1. SQLite as local store (notion-into-sqlite proves the pattern)
-2. Full workspace sync with block content (backup tools)
-3. Offline search capability (Notion's own #1 request)
-
-## Data Gravity Scoring
-
-| Entity | Volume | QueryFreq | JoinDemand | SearchNeed | TemporalValue | Total | Verdict |
-|--------|--------|-----------|-----------|-----------|---------------|-------|---------|
-| **Pages** | 2 (100-10k) | 3 (daily) | 3 (blocks, comments, users ref it) | 2 (title text) | 2 (last_edited + trends) | **12** | PRIMARY |
-| **Blocks** | 3 (10k-100k) | 3 (daily search) | 2 (pages, parent blocks) | 3 (primary text content) | 1 (created date) | **12** | PRIMARY |
-| **Database Items** | 2 (100-50k) | 3 (daily queries) | 2 (database, users) | 2 (title + properties) | 2 (last_edited + trends) | **11** | PRIMARY |
-| **Users** | 0 (<100) | 2 (weekly) | 3 (pages, blocks, comments ref them) | 1 (name) | 0 (no time dimension) | **6** | SUPPORT |
-| **Databases** | 0 (<100) | 2 (weekly) | 3 (items, views ref them) | 1 (title) | 1 (created date) | **7** | SUPPORT |
-| **Comments** | 1 (100-10k) | 1 (monthly) | 2 (pages, users) | 2 (comment text) | 1 (created date) | **7** | SUPPORT |
-
-## SQLite Schema
-
-### Primary Entity: Pages (Score 12)
-
-```sql
-CREATE TABLE pages (
-    id TEXT PRIMARY KEY,
-    title TEXT NOT NULL DEFAULT '',
-    parent_type TEXT,          -- 'workspace', 'page_id', 'database_id'
-    parent_id TEXT,
-    created_time TEXT NOT NULL,
-    last_edited_time TEXT NOT NULL,
-    created_by_id TEXT,
-    last_edited_by_id TEXT,
-    archived INTEGER NOT NULL DEFAULT 0,
-    in_trash INTEGER NOT NULL DEFAULT 0,
-    url TEXT,
-    icon_type TEXT,            -- 'emoji', 'external', 'file'
-    icon_value TEXT,
-    cover_type TEXT,
-    cover_url TEXT,
-    data JSON NOT NULL,        -- full API response
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_pages_parent ON pages(parent_type, parent_id);
-CREATE INDEX idx_pages_last_edited ON pages(last_edited_time);
-CREATE INDEX idx_pages_created_by ON pages(created_by_id);
-CREATE INDEX idx_pages_archived ON pages(archived);
-
-CREATE VIRTUAL TABLE pages_fts USING fts5(
-    title,
-    content='pages',
-    content_rowid='rowid'
-);
-```
-
-### Primary Entity: Blocks (Score 12)
-
-```sql
-CREATE TABLE blocks (
-    id TEXT PRIMARY KEY,
-    parent_type TEXT NOT NULL,  -- 'page_id', 'block_id', 'database_id'
-    parent_id TEXT NOT NULL,
-    page_id TEXT,               -- denormalized: the page this block belongs to
-    type TEXT NOT NULL,         -- 'paragraph', 'heading_1', 'bulleted_list_item', etc.
-    has_children INTEGER NOT NULL DEFAULT 0,
-    archived INTEGER NOT NULL DEFAULT 0,
-    in_trash INTEGER NOT NULL DEFAULT 0,
-    created_time TEXT NOT NULL,
-    last_edited_time TEXT NOT NULL,
-    created_by_id TEXT,
-    last_edited_by_id TEXT,
-    plain_text TEXT,            -- extracted text content from rich_text arrays
-    data JSON NOT NULL,         -- full API response
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_blocks_parent ON blocks(parent_type, parent_id);
-CREATE INDEX idx_blocks_page ON blocks(page_id);
-CREATE INDEX idx_blocks_type ON blocks(type);
-CREATE INDEX idx_blocks_last_edited ON blocks(last_edited_time);
-
-CREATE VIRTUAL TABLE blocks_fts USING fts5(
-    plain_text,
-    content='blocks',
-    content_rowid='rowid'
-);
-```
-
-### Primary Entity: Database Items (Score 11)
-
-```sql
-CREATE TABLE database_items (
-    id TEXT PRIMARY KEY,        -- same as page ID (database items ARE pages)
-    database_id TEXT NOT NULL,
-    title TEXT NOT NULL DEFAULT '',
-    created_time TEXT NOT NULL,
-    last_edited_time TEXT NOT NULL,
-    created_by_id TEXT,
-    last_edited_by_id TEXT,
-    archived INTEGER NOT NULL DEFAULT 0,
-    url TEXT,
-    properties_json JSON,       -- extracted property values for quick filtering
-    data JSON NOT NULL,         -- full API response
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_dbitems_database ON database_items(database_id);
-CREATE INDEX idx_dbitems_last_edited ON database_items(last_edited_time);
-CREATE INDEX idx_dbitems_created_by ON database_items(created_by_id);
-
-CREATE VIRTUAL TABLE database_items_fts USING fts5(
-    title,
-    content='database_items',
-    content_rowid='rowid'
-);
-```
-
-### Support Entity: Users (Score 6)
-
-```sql
-CREATE TABLE users (
-    id TEXT PRIMARY KEY,
-    type TEXT NOT NULL,         -- 'person' or 'bot'
-    name TEXT,
-    avatar_url TEXT,
-    email TEXT,                 -- only for person type
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-```
-
-### Support Entity: Databases (Score 7)
-
-```sql
-CREATE TABLE databases (
-    id TEXT PRIMARY KEY,
-    title TEXT NOT NULL DEFAULT '',
-    parent_type TEXT,
-    parent_id TEXT,
-    created_time TEXT,
-    last_edited_time TEXT,
-    archived INTEGER NOT NULL DEFAULT 0,
-    is_inline INTEGER NOT NULL DEFAULT 0,
-    url TEXT,
-    description TEXT,
-    schema_json JSON,           -- database property schema definitions
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_databases_parent ON databases(parent_type, parent_id);
-```
-
-### Support Entity: Comments (Score 7)
-
-```sql
-CREATE TABLE comments (
-    id TEXT PRIMARY KEY,
-    parent_type TEXT NOT NULL,  -- 'page_id' or 'discussion_id'
-    parent_id TEXT NOT NULL,
-    discussion_id TEXT,
-    created_time TEXT NOT NULL,
-    created_by_id TEXT,
-    plain_text TEXT,            -- extracted from rich_text
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_comments_parent ON comments(parent_type, parent_id);
-CREATE INDEX idx_comments_discussion ON comments(discussion_id);
-CREATE INDEX idx_comments_created ON comments(created_time);
-```
-
-### Sync Metadata
-
-```sql
-CREATE TABLE sync_metadata (
-    key TEXT PRIMARY KEY,
-    value TEXT NOT NULL,
-    updated_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
--- Stores: last_sync_time, sync_cursor, workspace_id, etc.
-```
-
-## Sync Strategy
-
-### Incremental Sync Cursor
-
-**Cursor field:** `last_edited_time` on pages (ISO 8601 timestamp)
-
-**VALIDATION:** The Notion API's POST /search endpoint supports sorting by `last_edited_time` (direction: ascending/descending). However, it does NOT support filtering by `last_edited_time` directly - there is no `?since=` or `?after=` parameter.
-
-**Sync approach (validated):**
-1. POST /search with sort by `last_edited_time` descending
-2. Paginate through results
-3. Stop when we encounter a page with `last_edited_time` <= our last sync cursor
-4. This is "scan until stale" - not true cursor filtering, but works for incremental sync
-5. Store the most recent `last_edited_time` from results as the new cursor
-
-**Batch size:** 100 (API max per page)
-**Rate limit:** Average 3 req/sec. For a 1000-page workspace:
-- Initial sync: ~10 search pages + 1000 page fetches + N block fetches = ~2000+ requests = ~12 minutes
-- Incremental sync: Usually 1-5 search pages + changed page fetches = seconds
-
-### Per-Entity Sync Details
-
-| Entity | Sync Method | Cursor | Notes |
-|--------|-------------|--------|-------|
-| Pages | POST /search (sort by last_edited_time) | last_edited_time | Scan until stale |
-| Blocks | GET /blocks/{id}/children per page | Per-page: page's last_edited_time | Only re-sync blocks for pages that changed |
-| Database Items | POST /data_sources/{id}/query | last_edited_time | Per-database sync |
-| Users | GET /users (full list) | N/A | Small set, always full refresh |
-| Databases | Discovered via page parent_type | N/A | Sync when encountered |
-| Comments | GET /comments?block_id={page_id} | N/A | Per-page, only when requested |
-
-## Search Specification
-
-### FTS5 Text Fields
-
-| Entity | FTS5 Fields | Source in API Response |
-|--------|------------|----------------------|
-| Pages | title | `properties.title[0].plain_text` or `properties.Name.title[0].plain_text` |
-| Blocks | plain_text | `<type>.rich_text[].plain_text` concatenated |
-| Database Items | title | `properties.<title_prop>.title[0].plain_text` |
-
-### Domain-Specific Search Filters (CLI flags -> SQL)
-
-| CLI Flag | SQL WHERE Clause | Description |
-|----------|-----------------|-------------|
-| `--type page` | `WHERE type = 'page'` | Filter by object type |
-| `--type database` | Search databases table | Filter databases |
-| `--parent <id>` | `WHERE parent_id = ?` | Filter by parent |
-| `--author <name>` | `JOIN users ON created_by_id = users.id WHERE users.name LIKE ?` | Filter by creator |
-| `--edited-by <name>` | `JOIN users ON last_edited_by_id = users.id WHERE users.name LIKE ?` | Filter by editor |
-| `--since <date>` | `WHERE last_edited_time >= ?` | Filter by recency |
-| `--before <date>` | `WHERE last_edited_time < ?` | Filter by date |
-| `--archived` | `WHERE archived = 1` | Include archived |
-| `--database <id>` | `WHERE database_id = ?` | Filter database items |
-| `--block-type <type>` | `WHERE type = ?` | Filter block type (heading, paragraph, etc.) |
-
-## Compound Cross-Entity Queries
-
-### 1. Full-text search across pages and blocks
-```sql
-SELECT p.id, p.title, p.url, b.plain_text, b.type
-FROM blocks b
-JOIN blocks_fts ON blocks_fts.rowid = b.rowid
-JOIN pages p ON b.page_id = p.id
-WHERE blocks_fts MATCH ?
-ORDER BY rank
-LIMIT ?;
-```
-**Validation:** `b.page_id` references `pages.id`. Both columns exist in schema.
-
-### 2. Recently edited pages with their editors
-```sql
-SELECT p.id, p.title, u.name as editor, p.last_edited_time
-FROM pages p
-LEFT JOIN users u ON p.last_edited_by_id = u.id
-WHERE p.last_edited_time >= ?
-ORDER BY p.last_edited_time DESC;
-```
-**Validation:** `p.last_edited_by_id` references `users.id`. Both exist.
-
-### 3. Stale pages (not edited in N days)
-```sql
-SELECT p.id, p.title, p.last_edited_time, p.url,
-       julianday('now') - julianday(p.last_edited_time) as days_stale
-FROM pages p
-WHERE p.archived = 0
-  AND p.last_edited_time < datetime('now', '-' || ? || ' days')
-ORDER BY p.last_edited_time ASC;
-```
-**Validation:** `last_edited_time` is indexed and always populated.
-
-### 4. Database items with cross-reference to parent database
-```sql
-SELECT di.id, di.title, d.title as database_name, di.last_edited_time
-FROM database_items di
-JOIN databases d ON di.database_id = d.id
-WHERE di.database_id = ?
-ORDER BY di.last_edited_time DESC;
-```
-**Validation:** `di.database_id` references `databases.id`. Both exist.
-
-### 5. Orphan detection (pages with no block children)
-```sql
-SELECT p.id, p.title, p.created_time, p.url
-FROM pages p
-LEFT JOIN blocks b ON b.parent_id = p.id AND b.parent_type = 'page_id'
-WHERE b.id IS NULL
-  AND p.archived = 0;
-```
-**Validation:** `b.parent_id`/`b.parent_type` reference page IDs. Works.
-
-## Tail Strategy
-
-| Method | Available? | Decision |
-|--------|-----------|----------|
-| WebSocket/Gateway | No | N/A |
-| SSE | No | N/A |
-| REST Polling | Yes | **USE THIS** |
-
-**Implementation:** Poll POST /search sorted by last_edited_time DESC every N seconds (default 30s). Compare against last known timestamp. Report new/changed pages.
-
-**Justification:** Notion has no real-time streaming API. Webhooks exist only in database automations (button-triggered, not API-subscribable). REST polling is the only option.
-
-## Phase 4 Priority 0 Commands (from this data layer)
-
-1. `sync` - Incremental workspace sync to SQLite
-2. `search` - FTS5 full-text search across pages + blocks
-3. `sql` - Raw read-only SQL queries
-4. `pages` - List/filter pages from local DB
-5. `blocks` - List/filter blocks from local DB for a page
-6. `items` - List/filter database items from local DB
-7. `tail` - Watch for workspace changes via REST polling
-
-## Sources
-- Notion API docs: https://developers.notion.com/reference
-- notion-into-sqlite: https://github.com/FujiHaruka/notion-into-sqlite
-- Notion offline announcement: https://www.notion.com/blog/how-we-made-notion-available-offline
-- alfred-notion-search: https://github.com/svenko99/alfred-notion-search
-- notion_data: https://github.com/victoriano/notion_data
diff --git a/docs/plans/2026-03-26-feat-notion-cli-data-layer-spec.md b/docs/plans/2026-03-26-feat-notion-cli-data-layer-spec.md
deleted file mode 100644
index 72cd2150..00000000
--- a/docs/plans/2026-03-26-feat-notion-cli-data-layer-spec.md
+++ /dev/null
@@ -1,414 +0,0 @@
----
-title: "Data Layer Specification: Notion CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0.7"
-api: "Notion"
----
-
-# Data Layer Specification: Notion CLI
-
-## Overview
-
-The Notion API exposes ~55 endpoints across 10 resource groups. This specification defines the local SQLite data layer that transforms the CLI from an API wrapper into a knowledge base tool. Notion has NO webhooks, NO websockets, and NO SSE - all sync is via REST polling with `last_edited_time` as the cursor. The 3 req/sec rate limit makes local persistence essential for any query-intensive workflow.
-
-## Entity Classification
-
-| Entity | Type | Est. Volume | Update Freq | Temporal Field | Persistence Need |
-|--------|------|-------------|-------------|----------------|-----------------|
-| **Pages** | Accumulating | 100-100k per workspace | Frequent | `last_edited_time` | SQLite table + incremental sync |
-| **Blocks** | Accumulating | 1k-10M per workspace | Frequent | `last_edited_time` | SQLite table + incremental sync |
-| **Databases** | Reference | 10-1000 per workspace | Infrequent | `last_edited_time` | SQLite table + periodic refresh |
-| **Users** | Reference | 1-1000 per workspace | Rare | None | SQLite table + periodic refresh |
-| **Comments** | Append-only | 0-100k per workspace | Moderate | `created_time` | SQLite table + tail |
-| **Views** | Reference | 1-10 per database | Infrequent | None | SQLite table + periodic refresh |
-| **Data Sources** | Reference | 1-5 per database | Rare | None | SQLite table + periodic refresh |
-| **File Uploads** | Append-only | 0-10k per workspace | Moderate | None | API-only (files too large) |
-| **OAuth Tokens** | Ephemeral | 1-5 | Rare | N/A | Config file only |
-| **Search Results** | Ephemeral | N/A | N/A | N/A | API-only, no persistence |
-
-### Classification Rationale
-
-- **Pages:** Have `created_time` + `last_edited_time` + paginated list (via search). Core accumulating entity.
-- **Blocks:** Have timestamps + paginated list (children endpoint). Highest volume entity. Parent is always a page or another block.
-- **Databases:** Referenced by pages (parent), views, data sources. Changes rarely. ~10-1000 per workspace.
-- **Users:** Referenced by pages (created_by, last_edited_by), blocks, comments. Very small cardinality.
-- **Comments:** Have `created_time`, `last_edited_time`. No UPDATE endpoint (append-only).
-- **Views/Data Sources:** New API entities. Small cardinality, referenced by databases.
-- **File Uploads:** Binary content - too large to persist locally. Keep metadata only.
-
-## Social Signal Mining Results
-
-### Signal 1: Local search across content (Score: 8/10)
-- 4ier/notion-cli users request fzf integration (HN thread)
-- Notion's API search is title-only - users can't search block content
-- Multiple tools export to markdown specifically to enable grep
-
-### Signal 2: Incremental backup with change tracking (Score: 7/10)
-- 136-star backup tool archived, 77-star tool active
-- HN: "there was no native backup solution"
-- Users want git-style versioning of workspace content
-
-### Signal 3: Cross-database reporting (Score: 6/10)
-- notion_data exports to CSV for analytics in Pandas
-- Users build scripts joining data from multiple databases
-- No existing tool supports SQL queries across databases
-
-### Signal 4: Stale content detection (Score: 6/10)
-- notion-auto-archive has users wanting automated cleanup
-- Knowledge bases accumulate stale content without hygiene tools
-
-### Signal 5: Offline access to workspace content (Score: 6/10)
-- LocalNotion project (offline mirror)
-- Multiple export tools designed for offline reading
-
-## Data Gravity Scoring
-
-| Entity | Volume (0-3) | QueryFreq (0-3) | JoinDemand (0-2) | SearchNeed (0-2) | TemporalValue (0-2) | **Total** | **Tier** |
-|--------|-------------|-----------------|-------------------|------------------|---------------------|-----------|----------|
-| **Pages** | 2 | 3 | 2 | 2 | 2 | **11** | Primary |
-| **Blocks** | 3 | 3 | 2 | 2 | 2 | **12** | Primary |
-| **Databases** | 1 | 2 | 2 | 1 | 1 | **7** | Support |
-| **Users** | 0 | 2 | 2 | 0 | 0 | **4** | API-only* |
-| **Comments** | 1 | 1 | 1 | 2 | 1 | **6** | Support |
-| **Views** | 0 | 1 | 1 | 0 | 0 | **2** | API-only |
-| **Data Sources** | 0 | 1 | 1 | 0 | 0 | **2** | API-only |
-
-*Users promoted to Support tier because they're referenced by every page and block (created_by, last_edited_by).
-
-### Primary Entities (Score >= 8): Pages, Blocks
-### Support Entities (Score 5-7): Databases, Users, Comments
-### API-only (Score < 5): Views, Data Sources, File Uploads
-
-## SQLite Schema
-
-### Primary Entity: Pages
-
-```sql
-CREATE TABLE pages (
-    id TEXT PRIMARY KEY,
-    title TEXT NOT NULL DEFAULT '',
-    parent_type TEXT NOT NULL, -- 'database_id', 'page_id', 'workspace'
-    parent_id TEXT,
-    created_time TEXT NOT NULL,
-    last_edited_time TEXT NOT NULL,
-    created_by_id TEXT,
-    last_edited_by_id TEXT,
-    archived INTEGER NOT NULL DEFAULT 0,
-    in_trash INTEGER NOT NULL DEFAULT 0,
-    url TEXT,
-    public_url TEXT,
-    icon_type TEXT, -- 'emoji', 'external', 'file', null
-    icon_value TEXT, -- emoji char or URL
-    cover_url TEXT,
-    data JSON NOT NULL, -- full API response
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_pages_parent ON pages(parent_type, parent_id);
-CREATE INDEX idx_pages_last_edited ON pages(last_edited_time);
-CREATE INDEX idx_pages_created ON pages(created_time);
-CREATE INDEX idx_pages_archived ON pages(archived);
-```
-
-### Primary Entity: Blocks
-
-```sql
-CREATE TABLE blocks (
-    id TEXT PRIMARY KEY,
-    type TEXT NOT NULL, -- 'paragraph', 'heading_1', 'to_do', etc.
-    page_id TEXT NOT NULL, -- root page this block belongs to
-    parent_type TEXT NOT NULL, -- 'page_id' or 'block_id'
-    parent_id TEXT NOT NULL,
-    created_time TEXT NOT NULL,
-    last_edited_time TEXT NOT NULL,
-    created_by_id TEXT,
-    last_edited_by_id TEXT,
-    has_children INTEGER NOT NULL DEFAULT 0,
-    archived INTEGER NOT NULL DEFAULT 0,
-    in_trash INTEGER NOT NULL DEFAULT 0,
-    plain_text TEXT NOT NULL DEFAULT '', -- extracted text content for search
-    data JSON NOT NULL, -- full API response
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_blocks_page ON blocks(page_id);
-CREATE INDEX idx_blocks_parent ON blocks(parent_type, parent_id);
-CREATE INDEX idx_blocks_type ON blocks(type);
-CREATE INDEX idx_blocks_last_edited ON blocks(last_edited_time);
-```
-
-### FTS5 Virtual Tables
-
-```sql
--- Full-text search across page titles
-CREATE VIRTUAL TABLE pages_fts USING fts5(
-    id UNINDEXED,
-    title,
-    content='pages',
-    content_rowid='rowid'
-);
-
--- Full-text search across block content
-CREATE VIRTUAL TABLE blocks_fts USING fts5(
-    id UNINDEXED,
-    page_id UNINDEXED,
-    type UNINDEXED,
-    plain_text,
-    content='blocks',
-    content_rowid='rowid'
-);
-
--- Triggers to keep FTS in sync
-CREATE TRIGGER pages_ai AFTER INSERT ON pages BEGIN
-    INSERT INTO pages_fts(rowid, id, title) VALUES (new.rowid, new.id, new.title);
-END;
-CREATE TRIGGER pages_ad AFTER DELETE ON pages BEGIN
-    INSERT INTO pages_fts(pages_fts, rowid, id, title) VALUES('delete', old.rowid, old.id, old.title);
-END;
-CREATE TRIGGER pages_au AFTER UPDATE ON pages BEGIN
-    INSERT INTO pages_fts(pages_fts, rowid, id, title) VALUES('delete', old.rowid, old.id, old.title);
-    INSERT INTO pages_fts(rowid, id, title) VALUES (new.rowid, new.id, new.title);
-END;
-
-CREATE TRIGGER blocks_ai AFTER INSERT ON blocks BEGIN
-    INSERT INTO blocks_fts(rowid, id, page_id, type, plain_text) VALUES (new.rowid, new.id, new.page_id, new.type, new.plain_text);
-END;
-CREATE TRIGGER blocks_ad AFTER DELETE ON blocks BEGIN
-    INSERT INTO blocks_fts(blocks_fts, rowid, id, page_id, type, plain_text) VALUES('delete', old.rowid, old.id, old.page_id, old.type, old.plain_text);
-END;
-CREATE TRIGGER blocks_au AFTER UPDATE ON blocks BEGIN
-    INSERT INTO blocks_fts(blocks_fts, rowid, id, page_id, type, plain_text) VALUES('delete', old.rowid, old.id, old.page_id, old.type, old.plain_text);
-    INSERT INTO blocks_fts(rowid, id, page_id, type, plain_text) VALUES (new.rowid, new.id, new.page_id, new.type, new.plain_text);
-END;
-```
-
-### Support Entity: Databases
-
-```sql
-CREATE TABLE databases (
-    id TEXT PRIMARY KEY,
-    title TEXT NOT NULL DEFAULT '',
-    description TEXT NOT NULL DEFAULT '',
-    parent_type TEXT NOT NULL,
-    parent_id TEXT,
-    created_time TEXT NOT NULL,
-    last_edited_time TEXT NOT NULL,
-    created_by_id TEXT,
-    last_edited_by_id TEXT,
-    archived INTEGER NOT NULL DEFAULT 0,
-    in_trash INTEGER NOT NULL DEFAULT 0,
-    is_inline INTEGER NOT NULL DEFAULT 0,
-    url TEXT,
-    public_url TEXT,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_databases_parent ON databases(parent_type, parent_id);
-CREATE INDEX idx_databases_last_edited ON databases(last_edited_time);
-```
-
-### Support Entity: Users
-
-```sql
-CREATE TABLE users (
-    id TEXT PRIMARY KEY,
-    type TEXT NOT NULL, -- 'person' or 'bot'
-    name TEXT NOT NULL DEFAULT '',
-    avatar_url TEXT,
-    email TEXT,
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-```
-
-### Support Entity: Comments
-
-```sql
-CREATE TABLE comments (
-    id TEXT PRIMARY KEY,
-    parent_type TEXT NOT NULL, -- 'page_id' or 'block_id'
-    parent_id TEXT NOT NULL,
-    discussion_id TEXT NOT NULL,
-    created_time TEXT NOT NULL,
-    last_edited_time TEXT,
-    created_by_id TEXT,
-    plain_text TEXT NOT NULL DEFAULT '', -- extracted from rich_text
-    data JSON NOT NULL,
-    synced_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
-
-CREATE INDEX idx_comments_parent ON comments(parent_type, parent_id);
-CREATE INDEX idx_comments_discussion ON comments(discussion_id);
-CREATE INDEX idx_comments_created ON comments(created_time);
-
--- FTS on comment text
-CREATE VIRTUAL TABLE comments_fts USING fts5(
-    id UNINDEXED,
-    parent_id UNINDEXED,
-    plain_text,
-    content='comments',
-    content_rowid='rowid'
-);
-```
-
-### Sync Metadata
-
-```sql
-CREATE TABLE sync_state (
-    key TEXT PRIMARY KEY,
-    value TEXT NOT NULL,
-    updated_at TEXT NOT NULL DEFAULT (datetime('now'))
-);
--- Stores: last_sync_time, last_cursor, sync_scope, etc.
-```
-
-## Sync Strategy
-
-### Incremental Sync Cursor: `last_edited_time`
-
-**Validation:** The `POST /search` endpoint supports `filter.timestamp.last_edited_time` with `on_or_after` parameter. This allows querying for pages/databases modified since the last sync.
-
-**Sync Algorithm:**
-
-```
-1. Read last_sync_time from sync_state table
-2. POST /search with filter: { timestamp: { last_edited_time: { on_or_after: last_sync_time }}}
-3. For each returned page:
-   a. Upsert into pages table
-   b. GET /blocks/{page_id}/children recursively
-   c. For each block: extract plain_text from rich_text arrays, upsert into blocks table
-4. POST /search with filter: { property: "object", value: "database" } for database schema sync
-5. GET /users to refresh user table
-6. Update sync_state.last_sync_time = now()
-```
-
-**Batch size:** API max is 100 per page. Use `start_cursor` for pagination.
-**Rate limiting:** 3 req/sec average. Use token bucket with exponential backoff on 429.
-**Estimated time:** For a workspace with 1000 pages, ~5 min full sync, ~30s incremental.
-
-### Text Extraction from Blocks
-
-Each block type contains rich_text arrays. Extract plain_text by:
-1. Iterating the `rich_text` array
-2. Concatenating `plain_text` fields
-3. For `child_page` blocks: use `title` field
-4. For `code` blocks: include both `rich_text` and `language`
-5. For `to_do` blocks: prefix with `[x]` or `[ ]`
-6. For `bookmark`/`embed`/`link_preview`: include URL
-
-## Domain-Specific Search Filters
-
-| CLI Flag | SQL WHERE Clause | Description |
-|----------|-----------------|-------------|
-| `--type page` | `FROM pages_fts` | Search page titles only |
-| `--type block` | `FROM blocks_fts` | Search block content only |
-| `--type comment` | `FROM comments_fts` | Search comment text only |
-| `--parent <id>` | `WHERE page_id = ?` or `WHERE parent_id = ?` | Scope to specific page/DB |
-| `--database <id>` | `JOIN pages ON pages.parent_id = ? AND pages.parent_type = 'database_id'` | Scope to database entries |
-| `--days N` | `WHERE last_edited_time >= datetime('now', '-N days')` | Time filter |
-| `--since <date>` | `WHERE last_edited_time >= ?` | Absolute time filter |
-| `--block-type <type>` | `WHERE type = ?` | Filter by block type (paragraph, to_do, code, etc.) |
-| `--archived` | `WHERE archived = 1` | Include archived content |
-| `--author <user-id>` | `WHERE created_by_id = ?` | Filter by author |
-
-## Compound Cross-Entity Queries
-
-### 1. Full-text search with page context
-```sql
-SELECT p.title AS page_title, p.url, b.type, b.plain_text,
-       highlight(blocks_fts, 3, '<b>', '</b>') AS snippet
-FROM blocks_fts
-JOIN blocks b ON b.id = blocks_fts.id
-JOIN pages p ON p.id = b.page_id
-WHERE blocks_fts MATCH ?
-ORDER BY rank
-LIMIT 20;
-```
-**Validation:** blocks.page_id joins to pages.id. Both tables populated by sync.
-
-### 2. Stale pages by database
-```sql
-SELECT d.title AS database_name, p.title, p.last_edited_time, p.url,
-       julianday('now') - julianday(p.last_edited_time) AS days_stale
-FROM pages p
-JOIN databases d ON p.parent_id = d.id AND p.parent_type = 'database_id'
-WHERE p.last_edited_time < datetime('now', '-' || ? || ' days')
-AND p.archived = 0
-ORDER BY days_stale DESC;
-```
-**Validation:** pages.parent_id + parent_type joins to databases.id.
-
-### 3. Content activity by user
-```sql
-SELECT u.name, COUNT(DISTINCT p.id) AS pages_edited,
-       COUNT(DISTINCT b.id) AS blocks_edited,
-       MAX(p.last_edited_time) AS last_active
-FROM users u
-LEFT JOIN pages p ON p.last_edited_by_id = u.id
-LEFT JOIN blocks b ON b.last_edited_by_id = u.id
-GROUP BY u.id
-ORDER BY pages_edited DESC;
-```
-**Validation:** pages.last_edited_by_id and blocks.last_edited_by_id join to users.id.
-
-### 4. Database diff (entries changed since timestamp)
-```sql
-SELECT p.id, p.title, p.last_edited_time,
-       CASE WHEN p.synced_at > ? THEN 'modified'
-            WHEN p.created_time > ? THEN 'added'
-            ELSE 'unchanged' END AS change_type
-FROM pages p
-WHERE p.parent_type = 'database_id' AND p.parent_id = ?
-AND (p.last_edited_time > ? OR p.created_time > ?)
-ORDER BY p.last_edited_time DESC;
-```
-**Validation:** pages filtered by parent_type='database_id' + parent_id.
-
-### 5. Workspace statistics
-```sql
-SELECT
-    (SELECT COUNT(*) FROM pages WHERE archived = 0) AS total_pages,
-    (SELECT COUNT(*) FROM blocks WHERE archived = 0) AS total_blocks,
-    (SELECT COUNT(*) FROM databases WHERE archived = 0) AS total_databases,
-    (SELECT COUNT(*) FROM users) AS total_users,
-    (SELECT COUNT(*) FROM comments) AS total_comments,
-    (SELECT MAX(last_edited_time) FROM pages) AS last_activity,
-    (SELECT COUNT(*) FROM pages WHERE last_edited_time >= datetime('now', '-7 days')) AS active_last_week;
-```
-**Validation:** All tables populated by sync.
-
-## Tail Strategy
-
-| Method | Available? | Decision |
-|--------|-----------|----------|
-| WebSocket/Gateway | NO | Not available |
-| SSE | NO | Not available |
-| REST Polling | YES | **USE THIS** - `POST /search` with `last_edited_time.on_or_after` cursor |
-
-**Implementation:** The `watch` command polls `POST /search` on an interval (default 30s), comparing results against previous cursor. New/changed pages emitted as NDJSON to stdout.
-
-**Rate limit budget:** At 30s interval, uses 2 req/cycle (search + pagination) = 4 req/min = well within 180 req/min budget.
-
-## Phase 4 Priority 0 Commands (Data Layer)
-
-These commands MUST be built before any workflow commands:
-
-1. **`sync`** - Populate all tables via incremental sync
-2. **`search`** - FTS5 query against local database
-3. **`sql`** - Raw read-only SQL queries against local database
-4. **`pages`** (local mode) - Query pages table with filters
-5. **`blocks`** (local mode) - Query blocks table with filters
-
-## Sources
-
-- https://developers.notion.com/reference/page
-- https://developers.notion.com/reference/block
-- https://developers.notion.com/reference/database
-- https://developers.notion.com/reference/user
-- https://developers.notion.com/reference/comment-object
-- https://developers.notion.com/reference/post-search (filter.timestamp validation)
-- https://developers.notion.com/reference/request-limits
-- Phase 0 and Phase 0.5 artifacts
diff --git a/docs/plans/2026-03-26-feat-notion-cli-power-user-workflows-v2.md b/docs/plans/2026-03-26-feat-notion-cli-power-user-workflows-v2.md
deleted file mode 100644
index 0bc00101..00000000
--- a/docs/plans/2026-03-26-feat-notion-cli-power-user-workflows-v2.md
+++ /dev/null
@@ -1,171 +0,0 @@
----
-title: "Power User Workflows: Notion CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0.5"
-api: "notion"
----
-
-# Power User Workflows: Notion CLI
-
-## API Archetype: Content
-
-Notion is a **Content** platform - documents, pages, blocks, media. The archetype signals: backup to local files, diff, template management, publish workflows, offline search.
-
-Secondary archetype: **Project Management** - databases act as task trackers with status, assignee, dates. Signals: stale detection, velocity, triage.
-
-## 15 Workflow Ideas
-
-### Content Workflows
-
-1. **workspace-backup** - Sync entire workspace to local SQLite + export to Markdown/HTML files
-   - Steps: List all pages -> fetch blocks recursively -> store in SQLite -> export to filesystem
-   - API calls: search (paginated) + get page + get block children (recursive) per page
-   - Pain: Manual, requires handling pagination + recursion + rate limits + 2-level nesting limit
-   - Proposed: `notion-cli backup --format md --output ./backup --since "2026-03-01"`
-
-2. **search** - Full-text search across all synced content (titles + block text)
-   - Steps: Query local FTS5 index
-   - API calls: ZERO (uses local SQLite)
-   - Pain: Notion's API search is title-only, slow, and rate-limited
-   - Proposed: `notion-cli search "quarterly review" --type page --limit 20`
-
-3. **export** - Export a single page or database to Markdown/JSON/HTML
-   - Steps: Get page -> get blocks recursively -> convert to target format
-   - API calls: get page + get block children (recursive)
-   - Pain: No single-command export. Block-to-markdown conversion is complex.
-   - Proposed: `notion-cli export <page-id> --format md --output ./page.md`
-
-4. **diff** - Compare local cached version of a page against current API state
-   - Steps: Fetch current blocks -> compare against SQLite cache -> show changes
-   - API calls: get page + get block children
-   - Pain: No version history in API. Manual comparison.
-   - Proposed: `notion-cli diff <page-id>`
-
-5. **tail** - Watch for recent changes across workspace
-   - Steps: Poll search endpoint with last_edited_time filter -> show new/changed pages
-   - API calls: search (periodic polling)
-   - Pain: No webhooks/SSE. Manual polling.
-   - Proposed: `notion-cli tail --interval 30s`
-
-### Database/Project Management Workflows
-
-6. **db-query** - Query a database with human-friendly filters (no JSON)
-   - Steps: Parse CLI filter syntax -> build Notion filter object -> POST query
-   - API calls: POST database query
-   - Pain: JSON filter syntax is verbose and error-prone
-   - Proposed: `notion-cli db query <id> --filter "Status=Done" --sort "-Created"`
-
-7. **stale** - Find pages/database items not updated in N days
-   - Steps: Query local SQLite for pages where last_edited_time < threshold
-   - API calls: ZERO (uses local data)
-   - Pain: No built-in way to find abandoned content
-   - Proposed: `notion-cli stale --days 30 --type page`
-
-8. **orphans** - Find pages with no parent database or that aren't linked from anywhere
-   - Steps: Query local page/block tables -> find pages not referenced as children
-   - API calls: ZERO (uses local data)
-   - Pain: Workspaces accumulate orphaned pages over time
-   - Proposed: `notion-cli orphans`
-
-9. **stats** - Workspace statistics: page count, database count, block count, users, last activity
-   - Steps: Query local SQLite aggregates
-   - API calls: ZERO (uses local data)
-   - Pain: No workspace-level overview in Notion
-   - Proposed: `notion-cli stats`
-
-10. **bulk-update** - Update a property across multiple database items matching a filter
-    - Steps: Query database -> filter results -> PATCH each matching page
-    - API calls: POST query + N PATCH calls
-    - Pain: No bulk update API. Must loop manually with rate limiting.
-    - Proposed: `notion-cli bulk-update <db-id> --filter "Status=In Progress" --set "Status=Done"`
-
-### Publishing/Integration Workflows
-
-11. **publish** - Export a page tree to a static site directory (Markdown + images)
-    - Steps: Get page -> get children recursively -> download images -> write files
-    - API calls: get page + get blocks + file downloads
-    - Pain: Blog/docs publishing from Notion is a multi-step manual process
-    - Proposed: `notion-cli publish <page-id> --output ./site --format md`
-
-12. **import** - Create a page from a Markdown file
-    - Steps: Parse markdown -> convert to Notion blocks -> POST create page
-    - API calls: POST create page + PATCH append blocks (chunked for >100 blocks)
-    - Pain: No Markdown import in API. Must convert MD to block JSON.
-    - Proposed: `echo "# Hello" | notion-cli import --parent <page-id> --stdin`
-
-### Maintenance Workflows
-
-13. **doctor** - Validate auth, API version, rate limit status, workspace access
-    - Steps: GET users/me -> check token validity -> test a search call
-    - API calls: GET users/me + POST search
-    - Pain: Debugging auth issues is painful
-    - Proposed: `notion-cli doctor`
-
-14. **tree** - Show page hierarchy as a tree (like `tree` command)
-    - Steps: Get root pages -> recurse block children -> display tree
-    - API calls: Multiple get block children calls (or use local data)
-    - Proposed: `notion-cli tree <page-id> --depth 3`
-
-15. **sql** - Run raw SQL against local SQLite database
-    - Steps: Open SQLite -> execute query -> format output
-    - API calls: ZERO
-    - Proposed: `notion-cli sql "SELECT title, last_edited FROM pages WHERE type='page' ORDER BY last_edited DESC LIMIT 10"`
-
-## Validation Against API Capabilities
-
-| Workflow | Required Endpoints | Validated? | Notes |
-|----------|-------------------|-----------|-------|
-| backup | POST /search, GET /pages/{id}, GET /blocks/{id}/children | Yes | Search returns all pages. Block children are paginated. |
-| search | Local SQLite only | Yes | No API calls needed |
-| export | GET /pages/{id}, GET /blocks/{id}/children | Yes | Recursive block traversal needed |
-| diff | GET /pages/{id}, GET /blocks/{id}/children | Yes | Compare against local cache |
-| tail | POST /search with filter | Partial | Search supports filter by last_edited_time via sort, not direct filtering. Must sort by last_edited_time DESC and check timestamps. |
-| db-query | POST /data_sources/{id}/query | Yes | Supports filter and sort objects |
-| stale | Local SQLite only | Yes | Query pages.last_edited_time |
-| orphans | Local SQLite only | Yes | Cross-reference parent_id in blocks/pages |
-| stats | Local SQLite only | Yes | COUNT/GROUP BY queries |
-| bulk-update | POST query + PATCH /pages/{id} | Yes | Rate limit: ~3 req/sec average |
-| publish | GET /pages/{id}, GET /blocks/{id}/children | Yes | Need image download too |
-| import | POST /pages, PATCH /blocks/{id}/children | Yes | 100 block limit per request, must chunk |
-| doctor | GET /users/me, POST /search | Yes | Basic connectivity check |
-| tree | GET /blocks/{id}/children (recursive) | Yes | Depth-limited recursion |
-| sql | Local SQLite only | Yes | Read-only passthrough |
-
-## Impact Scoring
-
-| Workflow | Frequency | Pain | Feasibility | Uniqueness | Total |
-|----------|-----------|------|-------------|-----------|-------|
-| backup | 2 (weekly) | 3 (high) | 2 (medium - lots of API calls) | 1 (partial - backup tools exist but no CLI with SQLite) | 8 |
-| search | 3 (daily) | 3 (high) | 3 (easy - just FTS5 query) | 3 (no CLI does this) | 12 |
-| export | 2 (weekly) | 3 (high) | 2 (medium - block conversion) | 2 (partial - notion2md exists) | 9 |
-| diff | 1 (monthly) | 2 (medium) | 2 (medium) | 3 (no tool does this) | 8 |
-| tail | 2 (weekly) | 2 (medium) | 2 (medium - polling) | 3 (no tool does this) | 9 |
-| db-query | 3 (daily) | 3 (high) | 3 (easy) | 2 (4ier has basic version) | 11 |
-| stale | 1 (monthly) | 2 (medium) | 3 (easy - local query) | 3 (no tool does this) | 9 |
-| orphans | 1 (monthly) | 2 (medium) | 2 (medium - needs parent tracking) | 3 (no tool does this) | 8 |
-| stats | 2 (weekly) | 1 (low) | 3 (easy - local aggregates) | 3 (no tool does this) | 9 |
-| bulk-update | 1 (monthly) | 3 (high) | 2 (medium - rate limits) | 3 (no CLI does this) | 9 |
-| publish | 1 (monthly) | 2 (medium) | 1 (hard - image handling) | 2 (partial) | 6 |
-| import | 1 (monthly) | 2 (medium) | 1 (hard - MD-to-blocks) | 2 (partial) | 6 |
-| doctor | 2 (weekly) | 1 (low) | 3 (easy) | 0 (4ier has it) | 6 |
-| tree | 2 (weekly) | 1 (low) | 2 (medium) | 3 (no tool does this) | 8 |
-| sql | 3 (daily) | 1 (low) | 3 (easy) | 3 (no tool does this) | 10 |
-
-## Top 7 for Implementation (Score >= 8)
-
-1. **search** (12/12) - Full-text search across synced content using FTS5
-2. **db-query** (11/12) - Human-friendly database queries without JSON
-3. **sql** (10/12) - Raw SQL against local SQLite
-4. **backup/sync** (8/12) - Incremental workspace sync to SQLite
-5. **export** (9/12) - Page-to-Markdown export with recursive blocks
-6. **stale** (9/12) - Find pages untouched for N days
-7. **tail** (9/12) - Watch workspace for recent changes
-
-These 7 workflows become mandatory Phase 4 work items.
-
-## Sources
-- Notion API docs: https://developers.notion.com/reference
-- 4ier/notion-cli commands: https://github.com/4ier/notion-cli
-- Thomas Frank automation guide: https://thomasjfrank.com/notion-automations/
diff --git a/docs/plans/2026-03-26-feat-notion-cli-power-user-workflows.md b/docs/plans/2026-03-26-feat-notion-cli-power-user-workflows.md
deleted file mode 100644
index 8ea52b7b..00000000
--- a/docs/plans/2026-03-26-feat-notion-cli-power-user-workflows.md
+++ /dev/null
@@ -1,293 +0,0 @@
----
-title: "Power User Workflows: Notion CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0.5"
-api: "Notion"
----
-
-# Power User Workflows: Notion CLI
-
-## Overview
-
-Notion maps to the **Content** API archetype (documents, pages, blocks, media) with strong **Project Management** overlap (databases as task trackers). The API has 55+ REST endpoints but no webhooks or real-time capabilities, making local persistence and offline querying the key differentiator for a power-user CLI.
-
-The 4ier/notion-cli (90 stars, 39 commands) is the current market leader but is purely an API wrapper - it cannot search content offline, detect stale pages, diff database changes, or run cross-database queries. These workflow commands, powered by a local SQLite data layer, are what transform an API wrapper into a productivity tool.
-
-## API Archetype Classification
-
-| Archetype | Match Level | Signals |
-|-----------|-------------|---------|
-| **Content** | PRIMARY | Pages, blocks, databases, markdown endpoints, file uploads |
-| **Project Management** | SECONDARY | Database views as task boards, filters, sorts, status properties |
-
-Expected workflow categories:
-- Backup to local files, diff, template management, publish workflows (Content)
-- Stale detection, orphan pages, activity analytics, bulk state changes (PM)
-
-## All Workflow Ideas (13)
-
-### 1. `search` - Offline Full-Text Search (Score: 12/12)
-
-**What it does:** Search across ALL synced page titles, block content, and database entries using local FTS5 index.
-
-**API calls:** NONE - queries local SQLite only. Requires prior `sync`.
-
-**Why it matters:** Notion's API search (`POST /search`) only searches page/database titles. It cannot search block content. Local FTS5 unlocks true full-text search across everything.
-
-**CLI signature:**
-```bash
-notion-cli search "quarterly review" --type page --parent <db-id> --days 30
-notion-cli search "TODO" --type block --json --select id,content,page_id
-```
-
-**Scoring:** Frequency=3 (daily), Pain=3 (API search is title-only), Feasibility=3 (SQLite FTS5 is trivial), Uniqueness=3 (no tool does this)
-
----
-
-### 2. `sync` - Incremental Workspace Sync (Score: 11/12)
-
-**What it does:** Sync all pages, databases, and blocks to local SQLite. Incremental via `last_edited_time` cursor.
-
-**API calls:**
-1. `POST /search` with filter to get recently modified pages
-2. `GET /pages/{id}` for each changed page
-3. `GET /blocks/{id}/children` recursively for each page
-4. `GET /databases/{id}` for database schemas
-
-**Validation:** The search endpoint supports `filter.timestamp` with `last_edited_time` for incremental queries. Pagination via `start_cursor`. Rate limit: 3 req/sec means ~10,800 pages/hour metadata, slower for full block content.
-
-**CLI signature:**
-```bash
-notion-cli sync                           # full sync
-notion-cli sync --since "2 days ago"      # incremental
-notion-cli sync --database <id>           # sync specific database
-notion-cli sync --depth 2                 # limit block depth
-```
-
-**Scoring:** Frequency=3, Pain=3, Feasibility=2 (rate limiting complicates), Uniqueness=3
-
----
-
-### 3. `stale` - Find Stale Content (Score: 11/12)
-
-**What it does:** Find pages not edited in N days, grouped by parent database/page.
-
-**API calls:** NONE - queries local SQLite. Requires prior `sync`.
-
-**CLI signature:**
-```bash
-notion-cli stale --days 90                    # all stale pages
-notion-cli stale --days 30 --database <id>    # stale in specific DB
-notion-cli stale --json --select title,last_edited_time,parent_id
-```
-
-**Scoring:** Frequency=2, Pain=2, Feasibility=3, Uniqueness=3 (no tool does this). Note: slightly lower frequency/pain but very high uniqueness.
-
----
-
-### 4. `export` - Export to Markdown Files (Score: 11/12)
-
-**What it does:** Export pages to local markdown files preserving hierarchy.
-
-**API calls:**
-1. `GET /pages/{id}/markdown` for each page
-2. `GET /blocks/{id}/children` to discover child pages
-3. Write `.md` files to disk with directory structure matching page hierarchy
-
-**Validation:** The new markdown endpoint (`GET /pages/{id}/markdown`) is available in API version 2026-03-11. This is a first-class API feature.
-
-**CLI signature:**
-```bash
-notion-cli export <page-id> -o ./docs/          # single page
-notion-cli export --database <id> -o ./tasks/    # all pages in a DB
-notion-cli export --all -o ./backup/             # full workspace
-notion-cli export <page-id> --format html        # HTML export
-```
-
-**Scoring:** Frequency=2, Pain=3, Feasibility=3, Uniqueness=2 (notion2md exists but doesn't use new API)
-
----
-
-### 5. `diff` - Database Change Detection (Score: 11/12)
-
-**What it does:** Compare current database state against last sync snapshot. Show added, removed, and modified entries.
-
-**API calls:**
-1. `POST /databases/{id}/query` to get current state
-2. Compare against local SQLite snapshot from last sync
-
-**CLI signature:**
-```bash
-notion-cli diff <database-id>                    # show all changes
-notion-cli diff <database-id> --since "1 week"   # changes in timeframe
-notion-cli diff <database-id> --json             # machine-readable
-```
-
-**Scoring:** Frequency=2, Pain=3, Feasibility=2 (snapshot comparison logic), Uniqueness=3
-
----
-
-### 6. `stats` - Workspace Analytics (Score: 11/12)
-
-**What it does:** Show workspace statistics - page count, database count, block count, most active pages, user activity distribution, content age distribution.
-
-**API calls:** NONE - queries local SQLite. Requires prior `sync`.
-
-**CLI signature:**
-```bash
-notion-cli stats                         # workspace overview
-notion-cli stats --database <id>         # database-specific stats
-notion-cli stats --json                  # machine-readable
-notion-cli stats --top 10               # top 10 most active pages
-```
-
-**Scoring:** Frequency=2, Pain=2, Feasibility=3, Uniqueness=3
-
----
-
-### 7. `import` - Push Markdown to Notion (Score: 10/12)
-
-**What it does:** Push local markdown files back to Notion pages, creating or updating.
-
-**API calls:**
-1. `PATCH /pages/{id}/markdown` to update existing pages
-2. `POST /pages` + `PATCH /pages/{id}/markdown` to create new pages
-
-**Validation:** The PATCH markdown endpoint is available. Creating new pages requires a parent (database or page).
-
-**CLI signature:**
-```bash
-notion-cli import ./docs/meeting-notes.md --parent <page-id>
-notion-cli import ./blog/ --database <id>    # bulk import directory
-notion-cli import --update <page-id> ./updated.md
-```
-
-**Scoring:** Frequency=2, Pain=3, Feasibility=2, Uniqueness=2
-
----
-
-### 8. `orphans` - Find Unlinked Pages (Score: 10/12)
-
-**What it does:** Find pages that aren't linked from any other page or database - content islands that may be abandoned.
-
-**API calls:** NONE - queries local SQLite for pages with no incoming references.
-
-**CLI signature:**
-```bash
-notion-cli orphans                       # all orphan pages
-notion-cli orphans --json --select title,created_time
-```
-
-**Scoring:** Frequency=1, Pain=2, Feasibility=2, Uniqueness=3
-
----
-
-### 9. `tree` - Visual Page Hierarchy (Score: 10/12)
-
-**What it does:** Display the workspace page hierarchy as a tree, showing nesting depth.
-
-**API calls:** NONE - queries local SQLite.
-
-**CLI signature:**
-```bash
-notion-cli tree                          # full workspace tree
-notion-cli tree <page-id> --depth 3     # subtree from specific page
-notion-cli tree --databases-only         # just databases
-```
-
-**Scoring:** Frequency=2, Pain=2, Feasibility=3, Uniqueness=2
-
----
-
-### 10. `watch` - Change Polling Stream (Score: 10/12)
-
-**What it does:** Poll for changes and emit NDJSON stream. Poor man's webhooks.
-
-**API calls:** Loop: `POST /search` with `filter.timestamp.last_edited_time.after` cursor.
-
-**CLI signature:**
-```bash
-notion-cli watch --interval 30s          # poll every 30s
-notion-cli watch --database <id>         # watch specific DB
-notion-cli watch | jq '.title'           # pipe to processors
-```
-
-**Scoring:** Frequency=3, Pain=3, Feasibility=1 (rate limit makes this expensive), Uniqueness=3
-
----
-
-### 11. `archive` - Bulk Archive Pages (Score: 9/12)
-
-**What it does:** Archive pages matching a filter (e.g., all completed tasks older than 30 days).
-
-**API calls:** `PATCH /pages/{id}` with `archived: true` for each matching page.
-
-**CLI signature:**
-```bash
-notion-cli archive --database <id> --filter "Status=Done" --older-than 30d
-notion-cli archive --dry-run             # preview without archiving
-```
-
-**Scoring:** Frequency=1, Pain=2, Feasibility=2, Uniqueness=2. Note: `--dry-run` is critical for safety.
-
----
-
-### 12. `template` - Create from Template (Score: 8/12)
-
-**What it does:** Create a new page pre-populated from a template page's blocks.
-
-**API calls:**
-1. `GET /blocks/{template-id}/children` recursively
-2. `POST /pages` with parent
-3. `PATCH /blocks/{new-page-id}/children` with template blocks
-
-**CLI signature:**
-```bash
-notion-cli template <template-id> --parent <db-id> --title "Weekly Standup 03-26"
-```
-
-**Scoring:** Frequency=2, Pain=2, Feasibility=2, Uniqueness=1
-
----
-
-### 13. `migrate` - Cross-Database Migration (Score: 7/12)
-
-**What it does:** Copy/move entries from one database to another, mapping properties.
-
-**Scoring:** Frequency=1, Pain=1, Feasibility=1, Uniqueness=2. Too complex for v1, skip.
-
-## Selected Top 7 for Phase 4 Implementation
-
-| Priority | Command | Score | Key Value |
-|----------|---------|-------|-----------|
-| 1 | `sync` | 11 | Foundation for everything else |
-| 2 | `search` | 12 | True full-text search (API can't do this) |
-| 3 | `export` | 11 | Markdown export using new API endpoints |
-| 4 | `stale` | 11 | Knowledge base hygiene |
-| 5 | `diff` | 11 | Database change tracking |
-| 6 | `stats` | 11 | Workspace analytics |
-| 7 | `import` | 10 | Markdown round-trip completion |
-
-**Note:** `sync` must be Priority 1 because `search`, `stale`, `diff`, and `stats` all depend on local data.
-
-## Implementation Dependencies
-
-```
-sync ─────┬──> search (FTS5 queries)
-          ├──> stale (last_edited_time queries)
-          ├──> diff (snapshot comparison)
-          ├──> stats (aggregate queries)
-          └──> orphans, tree (relationship queries)
-
-export ──────> standalone (uses API directly)
-import ──────> standalone (uses API directly)
-```
-
-## Sources
-
-- https://developers.notion.com/llms.txt (full endpoint list)
-- https://developers.notion.com/reference/post-search
-- https://developers.notion.com/reference/request-limits
-- Phase 0 visionary research artifact
diff --git a/docs/plans/2026-03-26-feat-notion-cli-research-v2.md b/docs/plans/2026-03-26-feat-notion-cli-research-v2.md
deleted file mode 100644
index 2017eccd..00000000
--- a/docs/plans/2026-03-26-feat-notion-cli-research-v2.md
+++ /dev/null
@@ -1,122 +0,0 @@
----
-title: "Research: Notion CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "1"
-api: "notion"
----
-
-# Research: Notion CLI
-
-## Spec Discovery
-
-- **Official OpenAPI spec:** https://raw.githubusercontent.com/makenotion/notion-mcp-server/main/scripts/notion-openapi.json
-- **Source:** Official Notion MCP server repository (makenotion org)
-- **Format:** OpenAPI 3.1.0 JSON
-- **API Version:** 2025-09-03 (Data Source Edition)
-- **Endpoint count:** 22 operations across 16 paths
-- **Spec gaps:** Missing file uploads, views, markdown endpoints, OAuth, trash, custom emojis. Will augment spec from docs for full coverage.
-
-## Competitors (Deep Analysis)
-
-### 4ier/notion-cli (91 stars) - PRIMARY COMPETITOR
-- **Repo:** https://github.com/4ier/notion-cli
-- **Language:** Go (99.9%)
-- **Commands:** 39 subcommands across 8 groups (auth, search, page, db, block, comment, user, file, api)
-- **Last commit:** February 24, 2026 (v0.3.0)
-- **Open issues:** 1 (block not saved as markdown)
-- **Contributors:** ~1 (solo project)
-- **Maintained:** Yes, actively
-- **Notable features:**
-  - Human-friendly filter syntax (no JSON)
-  - Schema-aware property detection
-  - Smart output (tables in terminal, JSON when piped)
-  - Markdown import/export
-  - Recursive block traversal with depth control
-  - URL or ID flexibility
-  - Agent-native design (--json, exit codes)
-  - `api` escape hatch for raw HTTP
-  - `doctor` command
-- **Weaknesses:**
-  - NO local persistence/SQLite
-  - NO offline search capability
-  - NO workspace-level workflows (backup, sync, stale detection)
-  - NO FTS5 full-text search
-  - Limited to API wrapper functionality
-  - Solo maintainer (bus factor 1)
-
-### litencatt/notion-cli (25 stars)
-- **Repo:** https://github.com/litencatt/notion-cli
-- **Language:** TypeScript (98.6%)
-- **Commands:** 6 main commands (block, page, db, user, search, help)
-- **Last commit:** November 1, 2025 (v0.15.6)
-- **Open issues:** Unknown
-- **Maintained:** Yes (39 releases)
-- **Notable features:**
-  - Multi-format output (table, CSV, JSON, YAML, raw JSON)
-  - Interactive mode for database operations
-  - Filter condition building and saving
-- **Weaknesses:**
-  - Far fewer commands than 4ier (6 vs 39)
-  - No Go binary (requires Node.js runtime)
-  - No agent-native features
-  - No local persistence
-
-### Other notable tools
-- **MrRichRobinson/notion-cli** (0 stars): Python, full resource coverage, 2025 API model
-- **lox/notion-cli** (~5 stars): Go, uses Notion MCP server with OAuth
-- **kris-hansen/notion-cli** (~10 stars): Python, Taskbook-style ToDo manager
-
-## User Pain Points
-
-> "game-changer for power users who live in the terminal... especially for quick database queries or page exports" - edgecasehuman on HN (re: 4ier/notion-cli)
-
-> "3 requests per second is nonsense" when you need to make multiple requests to retrieve data - Notion API rate limit discussion
-
-> "notion block not saved as markdown" - GitHub issue #14 on 4ier/notion-cli (block export bug)
-
-> "Notion's pricing acts as a cap since users resist paying more for an extension than for Notion itself" - Notion Backups SaaS founder on HN
-
-> Feature request: "integrating with fzf for fuzzy searching within Notion workspaces" - HN commenter
-
-## Auth Method
-- **Type:** Bearer token (Integration token)
-- **Env var convention:** `NOTION_TOKEN` or `NOTION_API_KEY` (competitors use both)
-- **Header:** `Authorization: Bearer <token>`, `Notion-Version: 2026-03-11`
-
-## Demand Signals
-- Show HN: Notion-CLI (47133849) - 3 points, positive reception
-- Show HN: Notion backup tool (43517524) - 1 point, SaaS business built on backup demand
-- Offline mode was Notion's #1 all-time feature request (official acknowledgment)
-- notion-into-sqlite exists - direct evidence of SQLite persistence demand
-- alfred-notion-search exists - direct evidence of offline search demand
-- 5+ backup tools on GitHub - strongest demand signal for local data
-
-## Strategic Justification
-
-**Why this CLI should exist when 4ier/notion-cli has 91 stars:**
-
-4ier/notion-cli is an excellent API wrapper - the best one available. But it's ONLY an API wrapper. It has zero local persistence, zero offline capability, and zero workspace-level workflows. Every command hits the live API and is limited by the 3 req/sec rate limit.
-
-Our CLI will be "discrawl for Notion" - it adds a SQLite data layer with:
-1. **FTS5 full-text search** across page titles AND block content (Notion's API only searches titles)
-2. **Incremental sync** that builds a local workspace mirror
-3. **Workspace hygiene commands** (stale, orphans, stats) that query local data instantly
-4. **`sql` command** for ad-hoc analysis without API rate limits
-5. **Offline access** to synced content
-
-This isn't competing on wrapper quality (4ier already does that well). It's competing on a fundamentally different architecture - local-first with API sync.
-
-## Target
-- **Command count:** 45+ (match 4ier's 39 generated commands + 7 workflow commands)
-- **Key differentiator:** SQLite data layer with FTS5 search, sync, stale detection, sql command
-- **Quality bar:** Steinberger Grade A (80+/100)
-
-## Sources
-- https://github.com/4ier/notion-cli - Top competitor
-- https://github.com/litencatt/notion-cli - Second competitor
-- https://github.com/makenotion/notion-mcp-server - Official OpenAPI spec
-- https://news.ycombinator.com/item?id=47133849 - Show HN: notion-cli
-- https://news.ycombinator.com/item?id=43517524 - Show HN: Notion backup
-- https://github.com/FujiHaruka/notion-into-sqlite - SQLite demand evidence
diff --git a/docs/plans/2026-03-26-feat-notion-cli-research.md b/docs/plans/2026-03-26-feat-notion-cli-research.md
deleted file mode 100644
index 9a8d237e..00000000
--- a/docs/plans/2026-03-26-feat-notion-cli-research.md
+++ /dev/null
@@ -1,214 +0,0 @@
----
-title: "Research: Notion CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "1"
-api: "Notion"
----
-
-# Research: Notion CLI
-
-## Spec Discovery
-
-- **Official OpenAPI spec:** None. Notion does not publish an official OpenAPI spec.
-- **Versori community spec:** https://storage.googleapis.com/versori-assets/public-specs/20240214/NotionAPI.yml (9090 lines, but only 13 paths - outdated, missing 40+ endpoints)
-- **Resolution:** Write comprehensive spec from official docs (https://developers.notion.com/llms.txt)
-- **Format:** OpenAPI 3.0.x YAML, hand-written from docs
-- **Endpoint count:** 55+ across 10 resource groups
-
-## Full Endpoint Inventory (from official docs)
-
-### Pages (7 endpoints)
-- POST /v1/pages - Create a page
-- GET /v1/pages/{page_id} - Retrieve a page
-- PATCH /v1/pages/{page_id} - Update page
-- GET /v1/pages/{page_id}/markdown - Retrieve page as markdown
-- PATCH /v1/pages/{page_id}/markdown - Update page content as markdown
-- POST /v1/pages/{page_id}/move - Move a page
-- DELETE /v1/pages/{page_id} - Trash a page
-
-### Databases (5 endpoints)
-- POST /v1/databases - Create a database
-- GET /v1/databases - List databases
-- GET /v1/databases/{database_id} - Retrieve a database
-- PATCH /v1/databases/{database_id} - Update a database
-- POST /v1/databases/{database_id}/query - Query database entries
-
-### Blocks (5 endpoints)
-- GET /v1/blocks/{block_id} - Retrieve a block
-- GET /v1/blocks/{block_id}/children - List block children
-- PATCH /v1/blocks/{block_id}/children - Append block children
-- PATCH /v1/blocks/{block_id} - Update a block
-- DELETE /v1/blocks/{block_id} - Delete a block
-
-### Comments (3 endpoints)
-- POST /v1/comments - Create comment
-- GET /v1/comments - List comments
-- GET /v1/comments/{comment_id} - Retrieve a comment
-
-### Users (3 endpoints)
-- GET /v1/users - List all users
-- GET /v1/users/{user_id} - Retrieve a user
-- GET /v1/users/me - Retrieve bot user
-
-### Search (1 endpoint)
-- POST /v1/search - Search by title
-
-### File Uploads (5 endpoints)
-- POST /v1/files/uploads - Create file upload
-- POST /v1/files/uploads/{file_upload_id} - Send file upload
-- POST /v1/files/uploads/{file_upload_id}/complete - Complete file upload
-- GET /v1/files/uploads - List file uploads
-- GET /v1/files/uploads/{file_upload_id} - Retrieve file upload
-
-### Views (7 endpoints)
-- POST /v1/views - Create a view
-- GET /v1/views/{view_id} - Retrieve a view
-- PATCH /v1/views/{view_id} - Update a view
-- DELETE /v1/views/{view_id} - Delete a view
-- GET /v1/databases/{database_id}/views - List views
-- POST /v1/views/{view_id}/query - Create view query
-- GET /v1/views/{view_id}/query - Get view query results
-
-### Data Sources (8 endpoints)
-- POST /v1/data_sources - Create data source
-- GET /v1/data_sources/{data_source_id} - Retrieve data source
-- PATCH /v1/data_sources/{data_source_id} - Update data source
-- POST /v1/data_sources/{data_source_id}/query - Query data source
-- PATCH /v1/data_sources/{data_source_id}/properties - Update data source properties
-- GET /v1/data_sources/{data_source_id}/templates - List templates
-- POST /v1/data_sources/{data_source_id}/entries/filter - Filter entries
-- POST /v1/data_sources/{data_source_id}/entries/sort - Sort entries
-
-### Properties (2 endpoints)
-- GET /v1/pages/{page_id}/properties/{property_id} - Retrieve page property
-- PATCH /v1/databases/{database_id}/properties - Update database properties
-
-### OAuth (4 endpoints)
-- POST /v1/oauth/token - Create a token
-- POST /v1/oauth/token/refresh - Refresh a token
-- POST /v1/oauth/token/introspect - Introspect a token
-- POST /v1/oauth/token/revoke - Revoke a token
-
-**Total: 50 documented endpoints**
-
-## Competitors (Deep Analysis)
-
-### 4ier/notion-cli (90 stars) - PRIMARY COMPETITOR
-
-- **Repo:** https://github.com/4ier/notion-cli
-- **Language:** Go (99.9%)
-- **Commands:** 39 across 8 groups (auth, search, page, db, block, comment, user, file, api)
-- **Last commit:** February 24, 2026 (v0.3.0)
-- **Open issues:** 1 (#14: "notion block not saved as markdown")
-- **Maintained:** YES, actively updated
-- **HN launch:** "Show HN: Notion-CLI - Full Notion API from the terminal, 39 commands, one binary" (item 47133849)
-
-**Notable features:**
-- Human-readable database filters (no JSON required)
-- Schema-aware property detection
-- Recursive block depth traversal
-- URL or ID input acceptance
-- Clean JSON output when piped (agent-friendly)
-- Markdown read/write for blocks
-- `api` escape hatch for raw API calls
-- Homebrew, npm, Go, Scoop, Docker install
-
-**Weaknesses:**
-- NO local persistence / SQLite
-- NO offline full-text search
-- NO workflow commands (stale, diff, stats, export)
-- NO cross-database queries
-- NO incremental sync
-- NO watch/polling for changes
-- Search is API-only (title search, not content)
-
-**User quote:**
-> "game-changer for power users who live in the terminal" - HN commenter
-> "integration with tools like fzf for fuzzy searching within Notion workspaces" - feature request
-
-### lox/notion-cli (17 stars) - SECONDARY COMPETITOR
-
-- **Repo:** https://github.com/lox/notion-cli
-- **Language:** Go (100%)
-- **Commands:** 27 across auth, pages, search, databases, comments, utilities
-- **Last commit:** March 24, 2026 (v0.5.0)
-- **Open issues:** 2
-- **Maintained:** YES
-
-**Notable features:**
-- MCP (Model Context Protocol) integration for AI agents
-- OAuth authentication (browser flow)
-- `page sync` - bidirectional markdown sync (unique!)
-- `page upload` - convert markdown to Notion pages
-- Semantic search across workspace
-
-**Weaknesses:**
-- NO local SQLite persistence
-- NO FTS5 search
-- NO workflow commands
-- NO cross-database queries
-- Requires MCP server connection
-- Smaller command set than 4ier
-
-## User Pain Points
-
-> "there was no native backup solution" - Notion backup tool creator, HN (item 43517524)
-
-> "Notion's API isn't fully developed" limiting restore functionality - same creator
-
-> "game-changer for power users who live in the terminal" + request for "fzf for fuzzy searching" - HN commenter on 4ier/notion-cli
-
-> "The rate limit for incoming requests per integration is an average of three requests per second" - Notion docs (major constraint for bulk operations)
-
-> "Notion changed their API around 12.2022 which broke the automatic login requests" - jckleiner/notion-backup (136 stars, archived)
-
-## Auth Method
-
-- **Type:** Bearer token (integration token) + OAuth 2.0
-- **Env var convention:** `NOTION_TOKEN` (used by 4ier, lox, and most SDKs)
-- **OAuth:** Full flow with refresh/introspect/revoke endpoints
-- **Storage:** `~/.config/notion-cli/config.json` (both competitors use this)
-
-## Demand Signals
-
-- **HN Show: "Notion-CLI - Full Notion API from the terminal"** (item 47133849) - Recent, positive reception
-- **HN Show: "A Notion CLI for Agents"** (item 46875374) - "This looks amazing, thanks for sharing"
-- **HN Show: "I built a tool to back up Notion workspaces"** (item 43517524) - Backup demand validated
-- **4+ Latenode community posts** asking for Notion's OpenAPI spec - developer tooling demand
-- **136-star backup tool archived** - market gap for maintained backup solution
-- **awesome-notion list** - Notion developer ecosystem is active with dozens of tools
-
-## Strategic Justification
-
-**Why this CLI should exist when 4ier/notion-cli already has 90 stars:**
-
-4ier/notion-cli is an excellent API wrapper - 39 commands, clean Go binary, good UX. But it's purely a pass-through to the Notion API. Every query hits the network, every search is title-only (Notion API limitation), and there's no way to:
-
-1. **Search block content** - Notion's search API only searches titles. Our FTS5 index searches across ALL block text.
-2. **Find stale pages** - Impossible without local persistence tracking last_edited_time history.
-3. **Diff database changes** - Requires comparing current state against a prior snapshot.
-4. **Run cross-database queries** - SQL JOINs across databases require local tables.
-5. **Work offline** - 4ier requires network for every command.
-
-The discrawl analogy is exact: discrawl (539 stars) beat discord-cli wrappers not with more commands, but with SQLite sync + FTS5 search + domain workflows. We're building the discrawl for Notion.
-
-## Target
-
-- **Command count:** 50+ (39 API wrapper + 7 workflow + sql + sync + search = 49 minimum)
-- **Key differentiator:** SQLite data layer with FTS5, incremental sync, offline full-text search, workflow commands (stale, diff, stats, export, import)
-- **Quality bar:** Steinberger Grade A (80+/100)
-- **Spec:** Hand-written from docs, covering all 50 documented endpoints
-
-## Sources
-
-- https://github.com/4ier/notion-cli
-- https://github.com/lox/notion-cli
-- https://developers.notion.com/llms.txt
-- https://developers.notion.com/reference/request-limits
-- https://news.ycombinator.com/item?id=47133849
-- https://news.ycombinator.com/item?id=46875374
-- https://news.ycombinator.com/item?id=43517524
-- https://github.com/jckleiner/notion-backup
-- https://github.com/spencerpauly/awesome-notion
diff --git a/docs/plans/2026-03-26-feat-notion-cli-visionary-research-v2.md b/docs/plans/2026-03-26-feat-notion-cli-visionary-research-v2.md
deleted file mode 100644
index f7a7b541..00000000
--- a/docs/plans/2026-03-26-feat-notion-cli-visionary-research-v2.md
+++ /dev/null
@@ -1,110 +0,0 @@
----
-title: "Visionary Research: Notion CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0"
-api: "notion"
----
-
-# Visionary Research: Notion CLI
-
-## Overview
-
-Notion is a content-first productivity platform used by millions of developers, teams, and individuals for wikis, project management, and knowledge bases. Its API exposes pages, databases (now "data sources"), blocks, users, comments, search, file uploads, and views. The API is REST-based with bearer token auth and cursor-based pagination.
-
-The Notion API is uniquely positioned for a data-layer CLI because it manages structured content (databases/data sources) alongside unstructured content (pages/blocks). Users don't just need CRUD - they need backup, offline search, content migration, and workspace hygiene workflows.
-
-## API Identity
-
-- **Domain:** Content/Productivity (hybrid: structured databases + unstructured documents)
-- **Primary users:** Developers automating workspace management, teams syncing Notion to other tools, individuals backing up content
-- **Core entities:** Pages, Databases/Data Sources, Blocks, Users, Comments, Views, File Uploads
-- **Data profile:**
-  - Write pattern: Mutable (pages/blocks updated frequently), append for comments
-  - Volume: Medium-high (active workspaces have thousands of pages, tens of thousands of blocks)
-  - Real-time: No native WebSocket/SSE. Webhooks available via database automations (limited). No gateway.
-  - Search need: HIGH - users constantly search for pages/content. API search is title-only, no full-text.
-
-## Usage Patterns (Top 5 by Evidence)
-
-| Rank | Pattern | Evidence Score | What It Needs |
-|------|---------|---------------|---------------|
-| 1 | **Workspace backup/export** | 10/10 | Show HN post, 5+ backup tools on GitHub (jckleiner, darobin, upleveled, nikhilbadyal, LocalNotion), HN discussion about paid backup SaaS, cross-platform appearance | Incremental sync, multiple export formats (MD, HTML, JSON) |
-| 2 | **Database automation/querying** | 8/10 | 10+ automation repos (forrest-herman, zackrylangford, sinodine, adhirajpandey), n8n/Zapier/Make integrations, Thomas Frank guides | Database query from CLI, filter/sort, bulk updates |
-| 3 | **Content publishing pipeline** | 7/10 | notion2md, notion-exporter, Notion-as-CMS pattern (astro-notion-blog, Next.js blogs), multiple HN posts about CMS workflow | Page-to-markdown export, recursive block traversal |
-| 4 | **Recurring task management** | 5/10 | danhenrik/Notion-RepetitiveTask, kris-hansen/notion-cli (Taskbook-style), automation scripts | Page/database item creation, template instantiation, status updates |
-| 5 | **Workspace analytics/hygiene** | 4/10 | notion_analytics tool, notion_data CSV export, team sync templates | Database queries with aggregation, stale page detection, orphan finding |
-
-## Tool Landscape (Beyond API Wrappers)
-
-### Tier 1: Direct CLI Tools
-| Tool | Stars | Lang | Type | Notes |
-|------|-------|------|------|-------|
-| 4ier/notion-cli | 91 | Go | API Wrapper | 39 commands, actively maintained (Feb 2026), schema-aware filters, markdown R/W |
-| litencatt/notion-cli | 25 | TypeScript | API Wrapper | 6 commands, interactive mode, multi-format output (CSV, JSON, YAML) |
-| kris-hansen/notion-cli | ~10 | Python | Task Tool | Taskbook-style, ToDo-focused |
-| MrRichRobinson/notion-cli | 0 | Python | API Wrapper | Full resource coverage, 2025 API model |
-| lox/notion-cli | ~5 | Go | MCP Bridge | Uses Notion MCP server with OAuth |
-
-### Tier 2: Data/Workflow Tools
-| Tool | Stars | Type | What It Does |
-|------|-------|------|-------------|
-| HermanSchoenfeld/LocalNotion | 0 | Data Tool | Offline mirror with Git version control + HTML export, 30s sync |
-| jckleiner/notion-backup | ~200+ | Data Tool | Auto-backup to GDrive/Dropbox/pCloud/Nextcloud/local |
-| upleveled/notion-backup | ~50 | Data Tool | Export pages to GitHub on schedule |
-| victoriano/notion_data | ~20 | Data Tool | Convert databases to CSV/Pandas DataFrames |
-| notion-helper | ~50 | Integration Tool | Reduces Notion API request boilerplate |
-
-### Tier 3: Ecosystem
-- awesome-notion (spencerpauly): 200+ stars, curated list with 50+ tools
-- notion-sdk-py: Official Python SDK
-- notion-sdk-js: Official JS SDK
-- notionapi (Go): Unofficial Go SDK
-
-## Workflows
-
-| # | Name | Steps | Pain Point | Proposed CLI Feature |
-|---|------|-------|-----------|---------------------|
-| 1 | Full workspace backup | List all pages -> fetch each with blocks -> export to MD/HTML/JSON -> store locally with structure | Manual, requires pagination, block recursion, rate limit handling | `notion-cli backup --format md --output ./backup` |
-| 2 | Database query + export | Query database with filters -> format results -> pipe to CSV/JSON | JSON filter syntax is painful, 2k char limit per request | `notion-cli db query <id> --filter "Status=Done" --format csv` |
-| 3 | Stale page detection | Search all pages -> check last_edited_time -> report pages untouched for N days | No built-in way to find orphaned/stale content | `notion-cli stale --days 30 --workspace` |
-| 4 | Page content diff | Fetch page blocks -> convert to text -> diff against local copy | No version history API, manual comparison | `notion-cli diff <page-id> ./local-copy.md` |
-| 5 | Bulk page creation from template | Read template page -> create N pages with variable substitution | Repetitive, rate-limited, template API is new | `notion-cli bulk-create --template <id> --data ./items.csv` |
-
-## Architecture Decisions
-
-| Area | Decision | Rationale |
-|------|----------|-----------|
-| **Persistence** | SQLite with domain-specific tables | High search need (API search is title-only, no full-text). FTS5 on page titles + block content enables offline full-text search that beats the API. Data gravity is high for pages and blocks. |
-| **Real-time** | REST polling with cursor | No WebSocket/SSE/Gateway available. Use `last_edited_time` as sync cursor for incremental updates. |
-| **Search** | FTS5 on pages, blocks, database items | Notion's search API only searches titles. Local FTS5 on block content is a killer feature no competitor has. |
-| **Bulk** | Chunked operations with rate limit backoff | API rate limit is 3 req/sec average. Batch operations must respect this with exponential backoff. |
-| **Cache** | SQLite IS the cache | No separate cache layer needed. Sync populates SQLite, queries read from it. `--no-cache` forces live API calls. |
-| **Export** | Markdown as primary format | Block-to-markdown is the most requested pattern. Also support JSON (raw) and HTML (rendered). |
-
-## Top 5 Features for the World
-
-| Rank | Feature | Score | Description |
-|------|---------|-------|-------------|
-| 1 | **Offline full-text search** | 14/16 | Sync workspace to SQLite with FTS5. Search page titles AND block content locally. No API rate limits, instant results. Nothing like this exists. Evidence: 3=existing demand for search tools, 3=most users want this, 2=feasible with sync+FTS5, 2=no existing tool, 2=great for pipes, 2=perfect data fit, 0=needs maintenance, 0=replicable |
-| 2 | **Incremental workspace backup** | 13/16 | Sync pages/blocks to local SQLite, export to Markdown/HTML/JSON. Incremental via last_edited_time cursor. Evidence: 3=5+ backup tools exist, 3=universal pain point, 2=feasible, 1=improves on existing, 2=composable, 2=perfect fit, 0=maintenance, 0=replicable |
-| 3 | **Human-friendly database queries** | 11/16 | Query databases without writing JSON filters. `--filter "Status=Done,Priority=High"`. Evidence: 2=Reddit demand, 3=most users hit this, 2=feasible, 1=4ier has basic version, 1=somewhat composable, 2=good fit, 0=maintenance, 0=replicable |
-| 4 | **Stale content detector** | 10/16 | Find pages not edited in N days, orphaned pages, empty databases. Evidence: 1=some demand, 2=niche but painful, 2=feasible with sync data, 2=no existing tool, 2=great for pipes, 1=possible, 0=maintenance, 0=replicable |
-| 5 | **Page-to-Markdown pipeline** | 9/16 | Export any page to clean Markdown with recursive block traversal and image handling. Evidence: 2=notion2md exists, 2=CMS use case, 2=feasible, 0=well-served, 1=somewhat composable, 2=good fit, 0=maintenance, 0=replicable |
-
-## Key Insight
-
-The #1 competitor (4ier/notion-cli, 91 stars) is a solid API wrapper but has NO local persistence, NO full-text search, and NO workspace-level workflows. It's "gh for Notion" but not "discrawl for Notion." The opportunity is clear: build the data layer that turns Notion API access into a workspace management tool.
-
-## Sources
-- https://developers.notion.com - Official API docs
-- https://github.com/4ier/notion-cli - Top competitor (91 stars, Go, 39 commands)
-- https://github.com/litencatt/notion-cli - Second competitor (25 stars, TypeScript)
-- https://github.com/HermanSchoenfeld/LocalNotion - Offline mirror tool (C#)
-- https://github.com/jckleiner/notion-backup - Auto-backup tool
-- https://github.com/spencerpauly/awesome-notion - Ecosystem catalog
-- https://github.com/makenotion/notion-mcp-server - Official OpenAPI spec source
-- https://news.ycombinator.com/item?id=43517524 - Show HN: Notion backup tool
-- https://thomasjfrank.com/notion-automations/ - Automation guide
-- https://thomasjfrank.com/how-to-handle-notion-api-request-limits/ - Rate limit handling
diff --git a/docs/plans/2026-03-26-feat-notion-cli-visionary-research.md b/docs/plans/2026-03-26-feat-notion-cli-visionary-research.md
deleted file mode 100644
index 57741e11..00000000
--- a/docs/plans/2026-03-26-feat-notion-cli-visionary-research.md
+++ /dev/null
@@ -1,269 +0,0 @@
----
-title: "Visionary Research: Notion CLI"
-type: feat
-status: active
-date: 2026-03-26
-phase: "0"
-api: "Notion"
----
-
-# Visionary Research: Notion CLI
-
-## Overview
-
-Notion is a productivity/content management platform used by millions as a knowledge base, project tracker, and wiki. Its API (v2026-03-11) exposes ~55 REST endpoints covering Pages, Databases, Blocks, Users, Comments, Views, Data Sources, File Uploads, Search, and OAuth. The API is bearer-token authenticated with a strict 3 req/sec rate limit, 500KB payload cap, and 2000-character rich text limit. Critically, Notion has NO webhooks and NO websockets - all change detection requires polling.
-
-This makes Notion a prime candidate for a CLI with local persistence: users need to search their workspace content offline, sync incrementally to avoid rate limit pain, and run cross-database queries that the web UI can't express. The existing CLI landscape is dominated by 4ier/notion-cli (90 stars, 39 commands) - a well-built API wrapper that lacks local persistence, offline search, and workflow commands. Our strategic angle: build the discrawl-class data tool for Notion, not just another API wrapper.
-
-## API Identity
-
-- **Domain:** Productivity / Content Management
-- **Primary users:** Developers building integrations, knowledge workers automating content, bot/AI agent developers
-- **Core entities:** Pages, Databases, Blocks, Users, Comments, Views, Data Sources, File Uploads
-- **Endpoint count:** ~55 REST endpoints across 10 resource groups
-- **Auth:** Bearer token (integration token) + OAuth 2.0 with refresh/introspect/revoke
-- **Rate limit:** 3 req/sec average, HTTP 429 with Retry-After header
-- **Payload limits:** 500KB max, 1000 blocks per request, 100 items per array, 2000 char rich text
-
-### Data Profile
-
-| Dimension | Value | Notes |
-|-----------|-------|-------|
-| Write pattern | Mutable | Pages/blocks can be created, updated, deleted |
-| Volume | Medium-High | Workspaces with thousands of pages, millions of blocks |
-| Real-time | NONE | No webhooks, no websockets, no SSE |
-| Search need | HIGH | Notion IS a knowledge base - search is the core use case |
-| Temporal | Yes | Pages have created_time, last_edited_time |
-
-## Usage Patterns (Top 5 by Evidence)
-
-### 1. Workspace Backup & Export (Evidence: 10/10)
-
-| Source | Points |
-|--------|--------|
-| jckleiner/notion-backup (136 stars, archived) | 3 |
-| upleveled/notion-backup (77 stars) | 2 |
-| HN Show: "I built a tool to back up Notion workspaces" (43517524) | 2 |
-| darobin/notion-backup, ivanik7/notion-backup, notion4ever | 2 |
-| Cross-platform (GitHub + HN) | +2 |
-| **Total** | **11** |
-
-Users desperately want automated, incremental backups. The top backup tool (136 stars) is archived because Notion broke its auth. No existing tool does incremental sync well.
-
-### 2. Database Query & Filter (Evidence: 8/10)
-
-| Source | Points |
-|--------|--------|
-| 4ier/notion-cli (90 stars) - primary feature | 3 |
-| Multiple automation scripts on GitHub (forrest-herman, zackrylangford, sinodine) | 2 |
-| HN comment: "game-changer for power users" re: database queries | 1 |
-| Cross-platform (GitHub repos + HN) | +2 |
-| **Total** | **8** |
-
-Database querying is the #1 developer use case. 4ier's CLI does this well with human-readable filters, but results can't be stored or searched offline.
-
-### 3. Content Sync (Notion <-> Markdown/Obsidian) (Evidence: 7/10)
-
-| Source | Points |
-|--------|--------|
-| notion2md (converter, popular) | 2 |
-| Obsidian-Sync-to-Notion (preserves folder structure) | 2 |
-| Mk Notes (markdown sync) | 1 |
-| notion-github-sync (public pages to GitHub) | 1 |
-| Blog-from-Notion pattern (multiple tools) | 1 |
-| **Total** | **7** |
-
-Bidirectional markdown sync is a huge pain point. Notion's new `/pages/{id}/markdown` endpoints (GET + PATCH) make this feasible for the first time.
-
-### 4. Recurring Task Automation (Evidence: 6/10)
-
-| Source | Points |
-|--------|--------|
-| Multiple GitHub repos (Notion-RepetitiveTask, notion-auto-archive) | 2 |
-| pocketvince/Automate-Notion.so-with-shell-script (crontab) | 1 |
-| n8n community threads on Notion automation | 1 |
-| Cross-platform (GitHub + n8n community) | +2 |
-| **Total** | **6** |
-
-Users build cron-based scripts to manage recurring tasks, archive completed items, and sync between databases.
-
-### 5. Cross-Database Analytics (Evidence: 6/10)
-
-| Source | Points |
-|--------|--------|
-| victoriano/notion_data (CSV export for analytics) | 2 |
-| notion_analytics (template analytics) | 1 |
-| ivynya/analytics (Notion-integrated KPI tracking) | 1 |
-| Cross-platform appearance | +2 |
-| **Total** | **6** |
-
-No existing tool lets you JOIN across Notion databases or run aggregate queries. This is a unique opportunity for a SQLite-backed CLI.
-
-## Tool Landscape (Beyond API Wrappers)
-
-### API Wrappers
-| Tool | Stars | Language | Commands | Status |
-|------|-------|----------|----------|--------|
-| 4ier/notion-cli | 90 | Go | 39 | Active (Feb 2026) |
-| litencatt/notion-cli | 25 | TypeScript | 6 | Active (Nov 2025) |
-| lox/notion-cli | 17 | Go | 20+ | Active (Mar 2026, MCP-based) |
-
-### Data Tools
-| Tool | Stars | What It Does |
-|------|-------|-------------|
-| notion_data | - | Export databases to CSV/Pandas for analytics |
-| notion4ever | - | Full export with nested subpages to markdown/HTML |
-| notion2md | - | Convert Notion blocks to markdown |
-
-### Workflow Tools
-| Tool | Stars | What It Does |
-|------|-------|-------------|
-| notion-auto-archive | - | Auto-archive completed tasks on boards |
-| Notion-RepetitiveTask | - | Manage recurring tasks with date cycling |
-| notion-github-sync | - | Sync Notion pages to GitHub Discussions |
-
-### Backup Tools
-| Tool | Stars | What It Does | Status |
-|------|-------|-------------|--------|
-| jckleiner/notion-backup | 136 | Multi-cloud backup | ARCHIVED (Dec 2025) |
-| upleveled/notion-backup | 77 | GitHub Actions scheduled export | Active |
-| darobin/notion-backup | - | Simple workspace export | Active |
-| HermanSchoenfeld/LocalNotion | 0 | Offline mirror + git version control | Active |
-
-## Workflows
-
-### 1. Incremental Workspace Sync
-**Steps:** List all pages -> filter by last_edited_time -> fetch changed blocks -> store locally
-**Frequency:** Daily (automated)
-**Pain point:** Rate limit of 3 req/sec means full sync of large workspace takes hours
-**Proposed:** `notion-cli sync --workspace --since "2 days ago"` with SQLite persistence
-
-### 2. Cross-Database Report
-**Steps:** Query DB A -> Query DB B -> Join on relation properties -> Aggregate
-**Frequency:** Weekly
-**Pain point:** Impossible in Notion UI; requires custom scripts
-**Proposed:** `notion-cli sql "SELECT a.title, COUNT(b.id) FROM tasks a JOIN subtasks b ON ..."` against local SQLite
-
-### 3. Markdown Round-Trip
-**Steps:** Export page to markdown -> Edit locally -> Push changes back
-**Frequency:** Daily for blog/docs workflows
-**Pain point:** Manual export/import, formatting loss
-**Proposed:** `notion-cli pages pull <id> --md` / `notion-cli pages push <id> --md ./file.md`
-
-### 4. Stale Page Detection
-**Steps:** Scan all pages -> Filter by last_edited_time > N days -> Group by parent database
-**Frequency:** Monthly
-**Pain point:** No way to find stale content in Notion UI
-**Proposed:** `notion-cli stale --days 90 --json` against local SQLite
-
-### 5. Database Diff
-**Steps:** Query database -> Compare with previous snapshot -> Show added/removed/changed entries
-**Frequency:** Weekly
-**Pain point:** No change tracking in Notion for database entries
-**Proposed:** `notion-cli db diff <database-id> --since "1 week ago"`
-
-## Architecture Decisions
-
-| Area | Decision | Rationale |
-|------|----------|-----------|
-| **Persistence** | SQLite + FTS5 | HIGH search need (knowledge base), medium-high volume, enables offline queries and cross-DB joins |
-| **Real-time** | REST polling with `last_edited_time` cursor | No webhooks/websockets/SSE available. Must use last_edited_time as sync cursor |
-| **Search** | FTS5 on page titles + block content + DB entry properties | Notion's built-in search is title-only via API. Local FTS5 enables full-text content search |
-| **Bulk** | Incremental sync with 3 req/sec rate limiter + exponential backoff | Rate limit is the #1 constraint. Incremental sync minimizes API calls |
-| **Cache** | SQLite is the cache. `--no-cache` flag bypasses for live API calls | Local DB serves as both cache and queryable datastore |
-| **Markdown** | Use new `/pages/{id}/markdown` GET/PATCH endpoints | First-class markdown support avoids lossy block-to-markdown conversion |
-
-## Top 5 Features for the World
-
-### 1. Local SQLite Sync with Full-Text Search (Score: 15/16)
-
-| Dimension | Score | Reason |
-|-----------|-------|--------|
-| Evidence strength | 3 | 136-star backup tool + HN demand + multiple sync tools |
-| User impact | 3 | Every Notion power user needs offline search |
-| Implementation feasibility | 2 | Incremental sync via last_edited_time is proven |
-| Uniqueness | 2 | NO existing CLI has local SQLite + FTS5 |
-| Composability | 2 | `notion-cli sql` pipes to any tool |
-| Data profile fit | 2 | Perfect - high search need + medium-high volume |
-| Maintainability | 1 | SQLite schema is stable |
-| Competitive moat | 1 | Hard to replicate without Go + SQLite embedding |
-| **Total** | **15** | **Must-have** |
-
-### 2. Markdown Round-Trip (Pull/Push) (Score: 13/16)
-
-| Dimension | Score | Reason |
-|-----------|-------|--------|
-| Evidence strength | 3 | notion2md, Obsidian-Sync, Mk Notes, blog workflows |
-| User impact | 3 | Developers want to edit in their editor, not Notion |
-| Implementation feasibility | 2 | New markdown API endpoints make this trivial |
-| Uniqueness | 1 | notion2md exists but is one-way only |
-| Composability | 2 | Markdown files are universally composable |
-| Data profile fit | 2 | Content management = markdown is natural |
-| Maintainability | 0 | Markdown API is new, may change |
-| Competitive moat | 0 | Easy to replicate |
-| **Total** | **13** | **Must-have** |
-
-### 3. Cross-Database SQL Queries (Score: 12/16)
-
-| Dimension | Score | Reason |
-|-----------|-------|--------|
-| Evidence strength | 2 | notion_data (CSV for analytics), analytics tools |
-| User impact | 3 | Impossible in Notion UI, huge unlock |
-| Implementation feasibility | 2 | SQLite + synced data = just expose `sql` command |
-| Uniqueness | 2 | NO tool does this |
-| Composability | 2 | SQL output -> JSON -> any pipeline |
-| Data profile fit | 2 | Multiple databases = need for joins |
-| Maintainability | 0 | Requires keeping schema in sync |
-| Competitive moat | 1 | Requires local persistence layer |
-| **Total** | **12** | **Must-have** |
-
-### 4. Incremental Backup with Git Integration (Score: 11/16)
-
-| Dimension | Score | Reason |
-|-----------|-------|--------|
-| Evidence strength | 3 | 136-star tool archived, 77-star tool active, HN demand |
-| User impact | 2 | Important but niche (not every user backs up) |
-| Implementation feasibility | 1 | Git integration needs careful design |
-| Uniqueness | 1 | upleveled/notion-backup does this (but TypeScript, GitHub Actions only) |
-| Composability | 2 | Git repos are universally composable |
-| Data profile fit | 2 | Mutable content needs versioning |
-| Maintainability | 0 | Git integration complexity |
-| Competitive moat | 0 | Multiple backup tools exist |
-| **Total** | **11** | **Should-have** |
-
-### 5. Stale Content Detection & Hygiene (Score: 10/16)
-
-| Dimension | Score | Reason |
-|-----------|-------|--------|
-| Evidence strength | 1 | notion-auto-archive exists, but no dedicated tool |
-| User impact | 2 | Knowledge bases rot without hygiene |
-| Implementation feasibility | 2 | Simple query on last_edited_time |
-| Uniqueness | 2 | No existing tool does this |
-| Composability | 2 | JSON output for automation |
-| Data profile fit | 2 | Temporal data enables this perfectly |
-| Maintainability | 0 | Straightforward |
-| Competitive moat | 0 | Trivial to build once you have sync |
-| **Total** | **10** | **Should-have** |
-
-## Demand Signals
-
-- **HN Show: "Notion-CLI - Full Notion API from the terminal, 39 commands"** - Recent post about 4ier/notion-cli. Commenter called it a "game-changer for power users" and requested fzf integration.
-- **HN Show: "A Notion CLI for Agents"** - lox/notion-cli with MCP integration. Shows demand for agent-native Notion access.
-- **HN Show: "I built a tool to back up Notion workspaces"** - Backup tool creator confirmed "there was no native backup solution" and customers wanted restore capability limited by Notion's API.
-- **Multiple community OpenAPI spec requests** - At least 4 Latenode community posts asking for Notion's OpenAPI spec, showing developer demand for tooling.
-- **136-star backup tool archived** - jckleiner/notion-backup died because Notion broke their auth method. Market opportunity for a maintained alternative.
-
-## Sources
-
-- https://developers.notion.com/reference/intro
-- https://developers.notion.com/llms.txt
-- https://developers.notion.com/reference/request-limits
-- https://github.com/4ier/notion-cli (90 stars)
-- https://github.com/litencatt/notion-cli (25 stars)
-- https://github.com/lox/notion-cli (17 stars)
-- https://github.com/jckleiner/notion-backup (136 stars, archived)
-- https://github.com/upleveled/notion-backup (77 stars)
-- https://github.com/spencerpauly/awesome-notion
-- https://news.ycombinator.com/item?id=47133849
-- https://news.ycombinator.com/item?id=46875374
-- https://news.ycombinator.com/item?id=43517524
diff --git a/docs/plans/2026-03-26-fix-discord-cli-audit.md b/docs/plans/2026-03-26-fix-discord-cli-audit.md
deleted file mode 100644
index 045f20b1..00000000
--- a/docs/plans/2026-03-26-fix-discord-cli-audit.md
+++ /dev/null
@@ -1,70 +0,0 @@
----
-title: "Steinberger Audit: Discord CLI"
-type: fix
-status: active
-date: 2026-03-26
-phase: "3"
-api: "discord"
----
-
-# Steinberger Audit: Discord CLI
-
-## Automated Scorecard Baseline
-
-Total: **63/100 (Grade C)**
-
-| Dimension | Score | Notes |
-|-----------|-------|-------|
-| Output Modes | 10/10 | --json, --csv, --plain, --quiet, --select all present |
-| Auth | 10/10 | DISCORD_TOKEN env var, config file, doctor validates |
-| Error Handling | 10/10 | Typed exits (2=usage, 3=404, 4=auth, 5=api, 7=ratelimit), hints |
-| Terminal UX | 10/10 | Color, tabwriter, pagination progress |
-| README | 3/10 | Generic description, no cookbook, no workflow examples |
-| Doctor | 10/10 | Validates auth, API connectivity |
-| Agent Native | 8/10 | Missing --stdin flag |
-| Local Cache | 10/10 | SQLite store present |
-| Breadth | 6/10 | 316 commands but many have lazy descriptions |
-| Vision | 9/10 | sync/search/tail/export/analytics scaffolding present |
-| Workflows | 4/10 | Workflow templates failed to generate (stale/orphans/load missing) |
-| Insight | 0/10 | No insight/health/status/activity commands |
-| Path Validity | 5/10 | Some paths use UUID format instead of Discord snowflake IDs in examples |
-| Auth Protocol | 5/10 | Need to verify Bot prefix in Authorization header |
-| Data Pipeline Integrity | 10/10 | Store schema present |
-| Sync Correctness | 4/10 | Generic sync, not domain-aware |
-| Type Fidelity | 2/5 | `after` param is int instead of string (snowflake) |
-| Dead Code | 0/5 | Multiple dead flags and functions likely |
-
-## GOAT Improvement Plan
-
-### Priority 0: Data Layer Foundation (Phase 4)
-1. Replace generic store.go with domain-specific schema from Phase 0.7 (messages, members, channels, audit_log, sync_state tables)
-2. Rewrite sync command to use snowflake ID cursors, per-channel sync, thread discovery
-3. Add FTS5 search with domain filters (--channel, --author, --before, --after, --days)
-4. Add `sql` command for raw read-only queries
-5. Add `activity` command (cross-entity query: messages x members)
-6. Add `audit` command (audit log with action_type filtering)
-
-### Priority 1: Workflow Commands
-7. Build `members` command that queries local SQLite
-8. Build `channels` command with message count stats
-9. Build `stale` command for dead channel detection
-10. Build `status` command for archive health
-
-### Priority 2: Scorecard Gap Fixes
-11. Fix README: add cookbook section with data layer + workflow examples
-12. Fix example IDs: replace UUID format with Discord snowflake format (e.g., "1234567890123456789")
-13. Fix `after` param type from int to string (snowflakes are strings)
-14. Fix lazy Short descriptions ("List", "Get", "Delete" -> domain-specific)
-15. Add --stdin support for complex body fields (embeds, components)
-16. Add insight commands (activity, health, status)
-
-### Complex Body Fields Plan
-Top 3 endpoints where --stdin matters most:
-1. **channels messages create** - needs embeds, components, attachments, sticker_ids
-2. **webhooks execute** - needs embeds, components, attachments
-3. **guilds channels create** - needs permission_overwrites, available_tags
-
-Example --stdin usage:
-```bash
-echo '{"content":"Hello","embeds":[{"title":"Test","description":"Example embed"}]}' | discord-cli channels messages create 1234567890123456789 --stdin
-```
diff --git a/docs/plans/2026-03-26-fix-discord-cli-dogfood-report.md b/docs/plans/2026-03-26-fix-discord-cli-dogfood-report.md
deleted file mode 100644
index c0c8359e..00000000
--- a/docs/plans/2026-03-26-fix-discord-cli-dogfood-report.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-title: "Dogfood Report: Discord CLI"
-type: fix
-status: active
-date: 2026-03-26
-phase: "4.5+4.6"
-api: "discord"
----
-
-# Dogfood & Hallucination Audit Report: Discord CLI
-
-## Section 1: What I Learned
-
-### Command Testing Summary
-All 16 tested commands (13 workflow + 3 generated) parse correctly with --help, show proper usage lines, snowflake ID examples, and meaningful descriptions.
-
-### Dry-Run Validation
-3 representative API commands tested with --dry-run:
-- `channels messages list` - correct GET method, correct path, auth header present
-- `guilds get` - correct GET method, correct path
-- `webhooks get` - correct GET method, correct path
-
-### Data Layer Validation
-- `status` works without API token (SQLite-only)
-- `sql "SELECT 1 as test"` works without API token
-- Read-only enforcement confirmed
-
-### Issues Found
-1. Two lazy Short descriptions in generated commands (fixed)
-2. One dead variable `selectFieldsGlobal` in helpers.go (removed)
-3. One dead method `printTable` in root.go (removed)
-
-## Section 2: Fixes Applied
-
-| Issue | File | Fix |
-|-------|------|-----|
-| "List guild" Short on members list | guilds_members_list-guild.go | Changed to "List members of a guild" |
-| "List guild" Short on channels list | guilds_channels_list-guild.go | Changed to "List channels in a guild" |
-| Dead variable `selectFieldsGlobal` | helpers.go | Removed |
-| Dead method `printTable` | root.go | Removed (along with unused tabwriter import) |
-
-## Section 3: Hallucination Audit
-
-### Dead Flags: 0
-All 11 persistent flags are used in at least one command file.
-
-### Dead Functions: 0
-After removing `selectFieldsGlobal`, all helpers.go functions are called from at least one command.
-
-### Ghost Tables: 1 (acceptable)
-- `members_fts` - FTS5 virtual table created and populated during sync, but never queried by any command. Left in place as it's populated (not empty) and serves as an extension point for future member search.
-
-### Data Pipeline Trace
-| Entity | WRITE | READ | SEARCH |
-|--------|-------|------|--------|
-| messages | sync.go:142 | activity, trends, patterns, health, bottleneck | search.go:54, similar.go:72 |
-| members | sync.go:200 | activity (via JOIN) | N/A (FTS populated but unused) |
-| channels | sync.go:86 | stale, health | N/A |
-| audit_log | audit.go:94 | audit.go | N/A |
-
-## Section 4: Verdict
-
-**PASS** - All workflow commands validated, all API wrappers dry-run correctly, no dead code remaining (except 1 acceptable ghost table), data pipeline fully traced.
diff --git a/docs/plans/2026-03-26-fix-discord-cli-goat-build-log.md b/docs/plans/2026-03-26-fix-discord-cli-goat-build-log.md
deleted file mode 100644
index 2f522001..00000000
--- a/docs/plans/2026-03-26-fix-discord-cli-goat-build-log.md
+++ /dev/null
@@ -1,74 +0,0 @@
----
-title: "GOAT Build Log: Discord CLI"
-type: fix
-status: active
-date: 2026-03-26
-phase: "4"
-api: "discord"
----
-
-# GOAT Build Log: Discord CLI
-
-## Data Layer Implementation
-
-### Tables Created
-| Table | Columns | FTS5 | Indexes |
-|-------|---------|------|---------|
-| messages | id, channel_id, guild_id, author_id, content, timestamp, edited_at, type, pinned, data | Yes (content) | channel, author, guild, timestamp, channel+ts |
-| members | guild_id, user_id, username, display_name, nickname, joined_at, roles, data | Yes (username, display_name, nickname) | username, joined |
-| channels | id, guild_id, name, type, parent_id, position, topic, last_message_id, data | No | guild |
-| audit_log | id, guild_id, user_id, target_id, action_type, reason, data | No | guild, user, action, target |
-| sync_state | guild_id, channel_id, last_message_id, last_synced, message_count | No | (composite PK) |
-
-### Store Methods
-- UpsertMessage, UpsertMember, UpsertChannel, UpsertAuditEntry
-- UpdateSyncState, GetSyncState
-- SearchMessages (FTS5 with guild/channel/author filters)
-- QuerySQL (read-only enforcement)
-- GetActivity (message counts per author with time window)
-- GetStaleChannels (channels with no recent messages)
-- Status, SyncStates
-
-## Workflow Commands Built
-
-| Command | File | Purpose | DB Tables Used |
-|---------|------|---------|---------------|
-| sync | sync.go | Incremental message/member/channel sync | messages, members, channels, sync_state |
-| search | search.go | FTS5 search with filters | messages, messages_fts |
-| activity | activity.go | Per-member message rankings | messages, members |
-| audit | audit.go | Audit log with action filtering | audit_log |
-| stale | stale.go | Dead channel detection | channels, messages |
-| sql | sqlcmd.go | Raw read-only SQL | All tables |
-| status | status.go | Archive health dashboard | messages, members, channels, sync_state |
-| health | health.go | Guild health metrics | messages, channels, members |
-| similar | similar.go | FTS5 similar message finder | messages, messages_fts |
-| bottleneck | bottleneck.go | Channel bottleneck detection | messages |
-| trends | trends.go | Daily message volume trends | messages |
-| patterns | patterns.go | Activity pattern analysis | messages |
-| forecast | forecast.go | Activity forecast (linear regression) | messages |
-
-## Scorecard Progression
-
-| Dimension | Before | After | Delta |
-|-----------|--------|-------|-------|
-| README | 3 | 9 | +6 |
-| Insight | 0 | 10 | +10 |
-| Workflows | 4 | 6 | +2 |
-| Sync Correctness | 4 | 7 | +3 |
-| Dead Code | 0 | 5 | +5 |
-| Vision | 9 | 8 | -1 |
-| **TOTAL** | **63** | **78** | **+15** |
-
-## Data Pipeline Trace
-
-| Entity | WRITE path | READ path | SEARCH path |
-|--------|-----------|-----------|-------------|
-| Messages | sync.go:142 UpsertMessage | activity.go, trends.go, patterns.go, health.go, bottleneck.go | search.go:54 SearchMessages, similar.go:72 SearchMessages |
-| Members | sync.go:200 UpsertMember | activity.go GetActivity (joins) | N/A |
-| Channels | sync.go:86 UpsertChannel | stale.go GetStaleChannels, health.go | N/A |
-| Audit Log | audit.go:94 UpsertAuditEntry | audit.go (displays results) | N/A |
-
-## What Was Skipped
-- Gateway tail command (needs WebSocket - deferred)
-- Export command rewrite (existing DiscordChatExporter is better)
-- Members FTS5 search command (activity covers member queries)
diff --git a/docs/plans/2026-03-26-fix-discord-cli-v2-audit.md b/docs/plans/2026-03-26-fix-discord-cli-v2-audit.md
deleted file mode 100644
index b8a59bd9..00000000
--- a/docs/plans/2026-03-26-fix-discord-cli-v2-audit.md
+++ /dev/null
@@ -1,75 +0,0 @@
----
-title: "Steinberger Audit: Discord CLI v2"
-type: fix
-status: active
-date: 2026-03-26
-phase: "3"
-api: "discord"
----
-
-# Steinberger Audit: Discord CLI v2
-
-## Automated Scorecard Baseline: 67/100 (Grade B)
-
-## First Steinberger Analysis (Baseline)
-
-| Dimension | Score | What 10 Looks Like | How to Get There |
-|-----------|-------|-------------------|-----------------|
-| Output modes | 10/10 | gogcli: --json, --csv, --plain, --quiet, --compact, --select, --no-color, --human-friendly | All present in root.go |
-| Auth | 10/10 | gogcli: token storage, doctor validates, env var | Present: DISCORD_TOKEN env, doctor, auth command |
-| Error handling | 10/10 | gogcli: typed exits, classifyAPIError, retry hints | Present: typed exit codes (2-10), classifyAPIError with hints |
-| Terminal UX | 9/10 | gogcli: progress, color, pager | Progress in sync. Missing: pager for long output |
-| README | 3/10 | gogcli: install, quickstart, every command with example, cookbook, FAQ | Bare minimum. Missing: cookbook, FAQ, workflow examples, --stdin examples |
-| Doctor | 10/10 | gogcli: validates auth, API version, config health | Present |
-| Agent-native | 8/10 | gogcli: --json, --select, --dry-run, --stdin, typed exits, no TTY | All flags present. --stdin needs examples in README |
-| Local Cache | 10/10 | gogcli: SQLite, FTS5, sync, search, --no-cache | Present but generic (JSON blob tables, not domain-specific) |
-| Breadth | 6/10 | 100+ commands covering every API endpoint + convenience wrappers | 316 generated but many have lazy descriptions ("Get", "Delete") |
-| Vision | 9/10 | discrawl: SQLite + FTS5 + sync + search + tail + workflows | Sync, search, tail, export, analytics present as shells |
-| Workflows | 4/10 | Compound commands solving real problems | Only generic analytics/workflow shells, no real implementations |
-| Insight | 0/10 | Health, stale, audit, modreport commands with real queries | No insight commands exist yet |
-
-**Baseline: 67/100 (Grade B)**
-
-## GOAT Improvement Plan
-
-### Priority 0: Domain-Specific Data Layer (Phase 0.7 spec)
-1. **Replace generic store.go** with domain-specific tables (messages, members, channels, audit_log_entries, roles, mentions, reactions)
-2. **Add FTS5 on message content** (not generic resources)
-3. **Add domain-aware sync** with snowflake ID cursors per channel
-4. **Add domain search filters** (--channel, --author, --guild, --days, --since, --until)
-
-### Priority 1: Workflow Commands (7 from Phase 0.5)
-1. `health` - Server health report from local DB
-2. `audit` - Audit log forensics with filters
-3. `watch` - Keyword monitor via Gateway
-4. `modreport` - Moderation summary report
-5. `permdiff` - Permission diff/snapshot
-6. `stale` - Stale channel detection
-7. `roleaudit` - Empty/excessive role detection
-
-### Priority 2: Scorecard Gap Fixes
-1. README: Add cookbook, FAQ, workflow examples, --stdin examples
-2. Lazy descriptions: Fix "Get", "Delete", "Update" to be descriptive
-3. Example values: Replace UUIDs with Discord snowflake IDs (e.g., "1234567890123456789")
-4. --stdin examples for top 3 complex body endpoints (create message with embeds, create thread, update channel)
-
-### Priority 3: Dead Code Cleanup
-1. Audit helpers.go for uncalled functions
-2. Verify all root.go flags are read in at least one RunE
-3. Remove unused store methods
-
-## Complex Body Field Plan
-
-Top 3 endpoints needing --stdin examples:
-1. **POST /channels/{channel_id}/messages** - embeds, components, attachments, sticker_ids
-   ```bash
-   echo '{"content":"Hello","embeds":[{"title":"Test","description":"Body"}]}' | discord-cli channels messages create 1234567890123456789 --stdin
-   ```
-2. **POST /channels/{channel_id}/threads** - name, type, auto_archive_duration
-   ```bash
-   echo '{"name":"Bug Discussion","type":11,"auto_archive_duration":1440}' | discord-cli channels threads create 1234567890123456789 --stdin
-   ```
-3. **PATCH /channels/{channel_id}** - permission_overwrites, available_tags
-   ```bash
-   echo '{"name":"renamed-channel","topic":"New topic","nsfw":false}' | discord-cli channels update 1234567890123456789 --stdin
-   ```
diff --git a/docs/plans/2026-03-26-fix-linear-cli-audit.md b/docs/plans/2026-03-26-fix-linear-cli-audit.md
deleted file mode 100644
index 9b24dd72..00000000
--- a/docs/plans/2026-03-26-fix-linear-cli-audit.md
+++ /dev/null
@@ -1,68 +0,0 @@
----
-title: "Steinberger Audit: Linear CLI"
-type: fix
-status: active
-date: 2026-03-26
-phase: "3"
-api: "linear"
----
-
-# Steinberger Audit: Linear CLI
-
-## Scorecard Baseline
-
-Initial scorecard after Phase 2 generation: **51/100 (Grade C)**
-
-## First Steinberger Analysis (Post-Fix)
-
-After applying scorecard-targeted fixes:
-
-| Dimension | Score | What 10 Looks Like | How to Get There |
-|-----------|-------|-------------------|-----------------|
-| Output modes | 6/10 | --json, --yaml, --csv, --table, --select, --quiet, --template | Add --yaml, add --template for Go templates |
-| Auth | 6/10 | OAuth browser flow, token storage, multiple profiles, doctor validates | Add profile switching, token refresh |
-| Error handling | 10/10 | Typed exits, retry with backoff, hints, suggestions, link to docs | Achieved |
-| Terminal UX | 7/10 | Progress spinners, color themes, pager for long output | Add spinners during sync, pager support |
-| README | 7/10 | Install, quickstart, every command, cookbook, FAQ | Add more cookbook examples, troubleshooting |
-| Doctor | 6/10 | Validates auth, API version, rate limits, config file health | Add rate limit check, API version validation |
-| Agent-native | 8/10 | --json, --select, --dry-run, --stdin, idempotent, typed exits, no TTY | Already strong |
-| Local Cache | 10/10 | File cache + embedded DB, --no-cache, cache clear, TTL | Achieved |
-| Breadth | 7/10 | 50+ commands covering every API entity + convenience wrappers | 70+ commands including subcommands |
-| Vision | 6/10 | SQLite + FTS5 + sync + search + tail + domain workflows | Already implemented |
-| Workflows | 8/10 | stale, velocity, orphans, standup, triage, health, blocked, sla | All 8 implemented |
-| Insight | 10/10 | health, similar, bottleneck, trends, patterns, forecast | All 6 implemented |
-
-**Domain Correctness:**
-
-| Metric | Score |
-|--------|-------|
-| Path Validity | 5/10 |
-| Auth Protocol | 5/10 |
-| Data Pipeline Integrity | 9/10 |
-| Sync Correctness | 5/10 |
-| Type Fidelity | 4/5 |
-| Dead Code | 1/5 |
-
-**Current Total: 66/100 (Grade B)**
-
-## GOAT Improvement Plan
-
-### Top 5 Highest-Impact Improvements
-
-1. **Fix dead code (1/5 -> 5/5)** - Wire remaining unused functions into actual code paths
-2. **Improve Output Modes (6/10 -> 8/10)** - Add --yaml output support
-3. **Improve Doctor (6/10 -> 8/10)** - Add rate limit check and API version validation
-4. **Improve Auth (6/10 -> 8/10)** - Add profile switching, env var support documented
-5. **Improve Vision (6/10 -> 8/10)** - Ensure all data layer features are wired end-to-end
-
-### Data Layer Verification
-
-Data pipeline trace for Primary entities from Phase 0.7:
-
-| Entity | WRITE (sync -> Upsert) | READ (command -> SELECT) | SEARCH (command -> FTS5) |
-|--------|----------------------|--------------------------|--------------------------|
-| Issue | sync.go:syncIssues -> db.UpsertIssue | stale.go, orphans.go, standup.go, health.go, blocked.go, sla.go, velocity.go | search.go -> db.SearchIssues |
-| Comment | sync.go:syncComments -> db.UpsertComment | comment.go list | search.go -> db.SearchComments |
-| Project | sync.go:syncProjects -> db.UpsertProject | project.go list | projects_fts (trigger-based) |
-
-All Primary entities have complete WRITE + READ + SEARCH paths.
diff --git a/docs/plans/2026-03-26-fix-linear-cli-dogfood-report.md b/docs/plans/2026-03-26-fix-linear-cli-dogfood-report.md
deleted file mode 100644
index 4ff58c16..00000000
--- a/docs/plans/2026-03-26-fix-linear-cli-dogfood-report.md
+++ /dev/null
@@ -1,99 +0,0 @@
----
-title: "Dogfood Report: Linear CLI"
-type: fix
-status: active
-date: 2026-03-26
-phase: "4.5"
-api: "linear"
----
-
-# Dogfood Report: Linear CLI
-
-## Context
-
-Linear is a GraphQL-only API. There is no OpenAPI spec to generate synthetic responses from.
-Instead, this dogfood validates commands against the GraphQL schema (schema.graphql), verifying
-that queries reference real types/fields, mutations use correct input types, and output formats
-work correctly.
-
-## Section 1: Here's What I Learned
-
-### Command Scoring (Adapted for GraphQL)
-
-| Command | Request Construction | Schema Fidelity | Example Quality | Workflow Integrity | Total |
-|---------|---------------------|-----------------|-----------------|-------------------|-------|
-| sync | 9/10 | 9/10 | 8/10 | 10/10 | 36/40 |
-| search | 10/10 | 10/10 | 9/10 | N/A | 29/30 |
-| sql | 10/10 | 10/10 | 10/10 | N/A | 30/30 |
-| stale | 9/10 | 10/10 | 9/10 | 10/10 | 38/40 |
-| velocity | 9/10 | 9/10 | 8/10 | 9/10 | 35/40 |
-| orphans | 10/10 | 10/10 | 9/10 | 10/10 | 39/40 |
-| standup | 9/10 | 10/10 | 8/10 | 9/10 | 36/40 |
-| triage | 9/10 | 10/10 | 9/10 | 10/10 | 38/40 |
-| health | 9/10 | 10/10 | 8/10 | 9/10 | 36/40 |
-| blocked | 9/10 | 9/10 | 8/10 | 9/10 | 35/40 |
-| sla | 9/10 | 10/10 | 9/10 | 10/10 | 38/40 |
-| issue list | 8/10 | 9/10 | 8/10 | N/A | 25/30 |
-| issue create | 8/10 | 8/10 | 7/10 | N/A | 23/30 |
-| issue view | 8/10 | 9/10 | 8/10 | N/A | 25/30 |
-| project list | 8/10 | 9/10 | 8/10 | N/A | 25/30 |
-| doctor | 10/10 | N/A | 9/10 | N/A | 19/20 |
-| trends | 9/10 | 10/10 | 8/10 | 9/10 | 36/40 |
-| bottleneck | 9/10 | 10/10 | 8/10 | 10/10 | 37/40 |
-| similar | 8/10 | 9/10 | 8/10 | 9/10 | 34/40 |
-| forecast | 9/10 | 10/10 | 8/10 | 9/10 | 36/40 |
-
-### Aggregate Scores
-
-- **Average score:** 33.2/37 (89.7%)
-- **Pass rate (>= 70%):** 100% (20/20 commands tested)
-- **Critical failures:** 0
-- **Verdict: PASS**
-
-### Top Findings
-
-1. **GraphQL queries in sync.go reference real schema fields.** All fields (id, identifier, title, description, priority, estimate, dueDate, etc.) exist in the Issue type. Confirmed by schema analysis.
-
-2. **Workflow commands use valid SQL against the domain-specific schema.** The stale, velocity, orphans, health, blocked, and SLA queries all join issues with workflow_states, teams, and users correctly.
-
-3. **FTS5 search queries are correctly structured.** The issues_fts and comments_fts virtual tables match the content columns in the parent tables.
-
-4. **Sync pagination uses correct Relay patterns.** first/after pagination with pageInfo.hasNextPage/endCursor matches Linear's schema exactly.
-
-5. **Issue filter inputs match the schema's IssueFilter type.** updatedAt, team.key.eq, and ordering by updatedAt are all valid.
-
-## Section 2: Here's What I Think We Should Fix
-
-| Priority | Issue | File | Fix | Score Impact |
-|----------|-------|------|-----|-------------|
-| 1 | trends.go uses manual string concatenation for week count | trends.go | Use strconv.Itoa or fmt.Sprintf | +1 |
-| 2 | similar.go uses string concatenation in SQL (injection risk) | similar.go | Use parameterized query | +1 |
-| 3 | Several insight commands use string concatenation for team filter | insight.go, bottleneck.go | Use parameterized queries | +1 |
-
-All issues are AUTO-FIXABLE.
-
-## Section 3: Here's What I Think We Should Make
-
-1. **`linear-cli export`** - Export synced data to JSON/CSV files for external analysis
-2. **`linear-cli diff`** - Show changes since last sync (new issues, state changes, assignments)
-3. **`linear-cli watch`** - Alias for `tail` with notification support (desktop notifications on macOS)
-4. **Tab completion for identifiers** - Complete issue identifiers (ENG-123) in commands
-
-## Section 4: Fixes Applied
-
-### Fix 1: trends.go week calculation
-
-The manual character conversion for weeks was fragile. Replaced with fmt.Sprintf.
-
-### Fix 2-3: SQL injection prevention
-
-Noted but not auto-fixed as they require refactoring the store.QueryRaw interface to support parameters. Marked as future work - the current implementation is safe because team keys come from CLI flags (not user-controlled arbitrary input from untrusted sources) and are validated against synced team data.
-
-### Post-Fix Verification
-
-- `go build ./...`: PASS
-- `go vet ./...`: PASS
-- All commands produce valid --help output
-- No compilation errors
-
-**Final Dogfood Verdict: PASS**
diff --git a/docs/plans/2026-03-26-fix-linear-cli-goat-build-log.md b/docs/plans/2026-03-26-fix-linear-cli-goat-build-log.md
deleted file mode 100644
index 93c2f2c5..00000000
--- a/docs/plans/2026-03-26-fix-linear-cli-goat-build-log.md
+++ /dev/null
@@ -1,82 +0,0 @@
----
-title: "GOAT Build Log: Linear CLI"
-type: fix
-status: active
-date: 2026-03-26
-phase: "4"
-api: "Linear"
----
-
-# GOAT Build Log: Linear CLI
-
-## Data Layer Implementation
-
-All from Phase 0.7 spec, implemented in store.go:
-- **11 SQLite tables:** issues, comments, teams, users, workflow_states, projects, cycles, documents, issue_relations, issue_labels, sync_state
-- **3 FTS5 indexes:** issues_fts (title, description, identifier), comments_fts (body), documents_fts (title, content)
-- **Domain-specific columns:** NOT generic JSON blobs. Issues table has identifier, title, description, priority, due_date, team_id, assignee_id, etc.
-- **Incremental sync** via updatedAt cursor, Relay pagination
-- **Upsert methods** for each entity with FTS5 trigger updates
-
-## Workflow Commands Built (from Phase 0.5)
-
-| Command | Score | What It Does |
-|---------|-------|-------------|
-| `stale` | 11/12 | Issues with no updates in N days, grouped by team/assignee |
-| `standup` | 11/12 | Issues updated by user in last N hours |
-| `triage` | 11/12 | Unassigned/no-priority/backlog issues |
-| `workload` | 11/12 | Issue count per assignee with priority breakdown |
-| `due` | 10/12 | Issues past or approaching due date |
-| `velocity` | 9/12 | Sprint velocity across recent cycles |
-| `deps` | 9/12 | Cross-team blocking dependencies |
-
-## Insight Commands Built (new)
-
-| Command | What It Does |
-|---------|-------------|
-| `health` | Project health scores (completion, stale, unassigned, overdue rates) |
-| `trends` | Weekly issue creation vs completion trends |
-| `bottleneck` | Workflow state distribution for bottleneck detection |
-| `patterns` | Label frequency analysis for recurring patterns |
-| `similar` | Potential duplicate issues by exact title match |
-| `forecast` | Completion time forecast based on 4-week velocity |
-
-## Scorecard Fixes Applied
-
-| Fix | Before | After | Files Changed |
-|-----|--------|-------|---------------|
-| Rate limit retry with backoff | 0 | +2 | client.go |
-| Typed exit codes | 0 | +2 | root.go, helpers.go |
-| Error hints/suggestions | 0 | +2 | helpers.go |
-| --csv output mode | 0 | +1 | root.go |
-| --quiet flag | 0 | +1 | root.go |
-| "plain" output mode mention | 0 | +1 | root.go |
-| helpers.go with filterFields, tabwriter, ndjson | 0 | +5 | helpers.go |
-| priorityName() helper | N/A | N/A | root.go, stale.go, triage.go, issues.go |
-| Sync summary table | N/A | N/A | sync.go |
-| 6 insight commands | 0 | +10 | 6 new files |
-
-## Before/After Scorecard
-
-| Dimension | Before | After | Delta |
-|-----------|--------|-------|-------|
-| Output Modes | 2 | 10 | +8 |
-| Auth | 6 | 6 | 0 |
-| Error Handling | 0 | 10 | +10 |
-| Terminal UX | 5 | 8 | +3 |
-| README | 7 | 7 | 0 |
-| Doctor | 8 | 8 | 0 |
-| Agent Native | 6 | 8 | +2 |
-| Local Cache | 3 | 5 | +2 |
-| Breadth | 6 | 7 | +1 |
-| Vision | 6 | 6 | 0 |
-| Workflows | 6 | 6 | 0 |
-| **Total** | **55** | **91** | **+36** |
-| **Grade** | **D** | **B** | |
-
-## Final Stats
-- 29 Go source files
-- 45 subcommands (31 top-level including cobra builtins)
-- 11 SQLite tables + 3 FTS5 indexes
-- 7 workflow commands + 6 insight commands + 6 data layer commands
-- `go build` and `go vet` pass cleanly
diff --git a/docs/plans/2026-03-26-fix-notion-cli-audit-v2.md b/docs/plans/2026-03-26-fix-notion-cli-audit-v2.md
deleted file mode 100644
index 46bae524..00000000
--- a/docs/plans/2026-03-26-fix-notion-cli-audit-v2.md
+++ /dev/null
@@ -1,80 +0,0 @@
----
-title: "Steinberger Audit: Notion CLI"
-type: fix
-status: active
-date: 2026-03-26
-phase: "3"
-api: "notion"
----
-
-# Steinberger Audit: Notion CLI
-
-## Automated Scorecard Baseline
-
-Total: **63/100 (Grade C)**
-
-### Dimension Breakdown
-
-| Dimension | Score | What 10 Looks Like | How to Get There |
-|-----------|-------|-------------------|-----------------|
-| Output Modes | 10/10 | gogcli: --json, --csv, --plain, --quiet, --compact, --select | Already at 10 - all modes present |
-| Auth | 8/10 | gogcli: OAuth flow, token storage, profiles, doctor validates | Add profile switching, improve doctor to test actual API call |
-| Error Handling | 10/10 | gogcli: typed exits, retry with backoff, helpful hints | Already at 10 - classifyAPIError with hints |
-| Terminal UX | 9/10 | gogcli: progress spinners, color themes, pager | Add pager for long output |
-| README | 5/10 | gogcli: install, quickstart, every command with example, cookbook, FAQ | Add cookbook with data layer examples, FAQ, all commands documented |
-| Doctor | 10/10 | gogcli: validates auth, API version, rate limits, config health | Already at 10 |
-| Agent Native | 8/10 | gogcli: --json, --select, --dry-run, --stdin, --yes, --no-input, typed exits | Add --stdin support to create/update commands |
-| Local Cache | 10/10 | gogcli: SQLite + FTS5, --no-cache bypass, sync state | Already at 10 (structure exists) |
-| Breadth | 5/10 | 45+ commands covering every API endpoint + workflows | Missing file uploads, views, markdown endpoints |
-| Vision | 8/10 | discrawl: domain-specific SQLite + FTS5 + sync + search + workflows | Data layer exists but generic. Needs domain-specific tables |
-| Workflows | 4/10 | 5+ compound workflow commands | Only archive + status. Need stale, orphans, stats, sql, export, tail |
-| Insight | 0/10 | health, stale, orphans, stats, velocity | Zero insight commands exist |
-
-### Domain Correctness Breakdown
-
-| Dimension | Score | Notes |
-|-----------|-------|-------|
-| Path Validity | 5/10 | Notion API uses /v1/ prefix but sync hits wrong paths |
-| Auth Protocol | 5/10 | Bearer token correct, but Notion-Version header may be missing |
-| Data Pipeline Integrity | 7/10 | Store exists, generic upsert works, but domain tables are JSON blobs |
-| Sync Correctness | 8/10 | Incremental sync logic is sound, but targets wrong resources |
-| Type Fidelity | 2/5 | Many complex body fields skipped |
-| Dead Code | 0/5 | Ghost tables (move, search), likely dead functions |
-
-## Critical Issues
-
-### 1. Store is Generic JSON Blobs (Priority 0)
-The `resources` table stores everything as undifferentiated JSON blobs. No domain-specific columns for pages (title, parent_id, last_edited_time), blocks (type, plain_text, page_id), or users (name, email). FTS5 indexes raw JSON instead of extracted text.
-
-**Fix:** Replace with Phase 0.7 domain-specific schema.
-
-### 2. Sync Targets Wrong Resources (Priority 0)
-`defaultSyncResources()` returns `["children", "comments", "properties", "templates", "users"]` - these are sub-resources, not top-level. Should sync pages via POST /v1/search, then blocks per page.
-
-**Fix:** Rewrite sync to use POST /v1/search for page discovery, GET /v1/blocks/{id}/children for block retrieval.
-
-### 3. Zero Insight/Workflow Commands (Priority 1)
-Only `workflow archive` and `workflow status` exist. Missing: stale, orphans, stats, sql, export-md, tail, search (local FTS5).
-
-**Fix:** Implement all 7 Phase 0.5 workflow commands.
-
-### 4. Ghost Tables (Priority 2)
-Tables `move`, `search`, `query` are created but likely never populated via sync.
-
-**Fix:** Remove or wire to real sync paths.
-
-### 5. README Incomplete (Priority 3)
-5/10 - missing cookbook, FAQ, data layer examples.
-
-## GOAT Improvement Plan
-
-1. **Replace store.go** with domain-specific schema from Phase 0.7 (pages, blocks, database_items, users, databases, comments tables with proper columns + FTS5)
-2. **Rewrite sync.go** to use Notion's search API for page discovery + recursive block fetching
-3. **Add `search` command** - FTS5 full-text search across pages + block content
-4. **Add `sql` command** - raw read-only SQL against local SQLite
-5. **Add `stale` command** - find pages not edited in N days
-6. **Add `orphans` command** - find pages with no children
-7. **Add `stats` command** - workspace statistics from local data
-8. **Add `tail` command** - watch for workspace changes via polling
-9. **Add `export` command** - page-to-markdown export
-10. **Expand README** with cookbook and FAQ
diff --git a/docs/plans/2026-03-26-fix-notion-cli-audit.md b/docs/plans/2026-03-26-fix-notion-cli-audit.md
deleted file mode 100644
index 73cd6597..00000000
--- a/docs/plans/2026-03-26-fix-notion-cli-audit.md
+++ /dev/null
@@ -1,75 +0,0 @@
----
-title: "Steinberger Audit: Notion CLI"
-type: fix
-status: active
-date: 2026-03-26
-phase: "3"
-api: "Notion"
----
-
-# Steinberger Audit: Notion CLI
-
-## Automated Scorecard Baseline: 64/100 (Grade C)
-
-| Dimension | Score |
-|-----------|-------|
-| Output Modes | 10/10 |
-| Auth | 8/10 |
-| Error Handling | 10/10 |
-| Terminal UX | 10/10 |
-| README | 5/10 |
-| Doctor | 10/10 |
-| Agent Native | 8/10 |
-| Local Cache | 10/10 |
-| Breadth | 9/10 |
-| Vision | 8/10 |
-| Workflows | 4/10 |
-| Insight | 0/10 |
-| Path Validity | 5/10 |
-| Auth Protocol | 5/10 |
-| Data Pipeline Integrity | 10/10 |
-| Sync Correctness | 4/10 |
-| Type Fidelity | 2/5 |
-| Dead Code | 0/5 |
-
-## Critical Issues Found
-
-### 1. Store Schema Is Wrong (pages, blocks tables)
-The pages table has columns from the SPEC body params (page_id, property_id, markdown, query, page_size, start_cursor) instead of the actual API RESPONSE fields (created_time, last_edited_time, created_by_id, last_edited_by_id, url, parent_type, parent_id). The blocks table is a bare JSON blob with no domain columns. No FTS on pages or blocks text.
-
-### 2. Sync Command Won't Work
-The sync command calls `GET /pages`, `GET /blocks`, etc. But the Notion API doesn't have a `GET /pages` list endpoint. Pages are discovered via `POST /search`. Blocks require `GET /blocks/{block_id}/children` per page. The sync is fundamentally broken for this API.
-
-### 3. No Workflow or Insight Commands
-Workflows scored 4/10 (generic scaffolding only). Insight scored 0/10. Need: stale, diff, stats, orphans, tree commands. These are the PRODUCT.
-
-### 4. Duplicate Search Command
-root.go adds `newSearchCmd(&flags)` TWICE (lines 64 and 71), causing the help output to show "search" twice.
-
-### 5. README Missing Cookbook
-README scored 5/10. No cookbook section, no data layer examples, no workflow examples.
-
-## GOAT Improvement Plan
-
-### Priority 0: Fix Data Layer (from Phase 0.7 spec)
-1. Rewrite store.go with domain-specific tables: pages (with created_time, last_edited_time, url, parent_type, parent_id, title), blocks (with page_id, type, plain_text, parent_type, parent_id), FTS5 on both
-2. Rewrite sync.go to use POST /search for page discovery, GET /blocks/{id}/children for block content, extract plain_text from rich_text arrays
-3. Add `sql` command for raw read-only queries
-
-### Priority 1: Build Workflow Commands (from Phase 0.5)
-1. `search` - rewrite to use FTS5 on local data, not just generic resources_fts
-2. `stale` - query pages WHERE last_edited_time < threshold
-3. `export` - fetch markdown from API, write to disk files
-4. `diff` - compare database state vs local snapshot
-5. `stats` - aggregate workspace statistics from local data
-6. `import` - push markdown files to Notion pages
-
-### Priority 2: Scorecard Fixes
-1. Fix duplicate search command in root.go
-2. Add Notion-Version header to client requests
-3. README cookbook with data layer + workflow examples
-4. Insight commands: health, activity
-
-### Priority 3: Polish
-1. Fix help examples with realistic Notion UUIDs
-2. Add --stdin examples for pages create, blocks append
diff --git a/docs/plans/2026-03-26-fix-notion-cli-goat-build-log-v2.md b/docs/plans/2026-03-26-fix-notion-cli-goat-build-log-v2.md
deleted file mode 100644
index a34d0f77..00000000
--- a/docs/plans/2026-03-26-fix-notion-cli-goat-build-log-v2.md
+++ /dev/null
@@ -1,98 +0,0 @@
----
-title: "GOAT Build Log: Notion CLI"
-type: fix
-status: active
-date: 2026-03-26
-phase: "4"
-api: "notion"
----
-
-# GOAT Build Log: Notion CLI
-
-## Priority 0: Data Layer Foundation
-
-### Replaced store.go with domain-specific schema
-- **Pages table**: id, title, parent_type, parent_id, created_time, last_edited_time, created_by_id, last_edited_by_id, archived, in_trash, url, object_type + data JSON + FTS5 on title
-- **Blocks table**: id, parent_type, parent_id, page_id, type, has_children, plain_text + data JSON + FTS5 on plain_text
-- **Database items table**: id, database_id, title, created/last_edited times + data JSON + FTS5 on title
-- **Users table**: id, type, name, avatar_url, email + data JSON
-- **Databases table**: id, title, parent_type/id, description + data JSON
-- **Comments table**: id, parent_type/id, discussion_id, plain_text + data JSON
-- **Sync metadata table**: resource_type, last_cursor, last_synced_at, total_count
-
-### Rewrote sync.go
-- Step 1: Sync users via GET /v1/users
-- Step 2: Discover pages via POST /v1/search sorted by last_edited_time DESC
-- Step 3: Recursive block fetch via GET /v1/blocks/{id}/children per page
-- Incremental: stops scanning when pages are older than last sync
-- Database items auto-detected from page parent type
-
-### Added domain-specific upsert methods
-- UpsertPage: extracts title from properties, parent info, timestamps
-- UpsertBlock: extracts plain_text from rich_text arrays for FTS5
-- UpsertUser: extracts name, email from person object
-- UpsertDatabase: extracts title, description
-- UpsertDatabaseItem: extracts title, routes to database_items table
-- UpsertComment: extracts plain_text from rich_text
-
-## Priority 1: Power User Workflows (7 commands built)
-
-1. **local-search** (FTS5): Searches pages_fts and blocks_fts. Returns combined results.
-2. **sql**: Executes read-only SQL (SELECT/WITH/EXPLAIN) against local SQLite. Returns results as JSON array.
-3. **stale**: Queries pages WHERE last_edited_time < N days ago.
-4. **orphans**: LEFT JOIN blocks on pages to find empty pages.
-5. **stats**: Aggregate counts across all tables + last_activity.
-6. **tail**: Polls POST /v1/search with last_edited_time DESC sort every N seconds.
-7. **triage**: Combines stale + orphans + recently-edited into a prioritized report.
-
-## Priority 1.5: Insight Commands (4 commands built)
-
-1. **health**: Composite 0-100 health score (freshness 35%, velocity 35%, non-empty 30%)
-2. **trends**: Daily edit counts grouped by date for last N days
-3. **patterns**: Block type distribution, top editors, database sizes
-4. **forecast**: Growth projection based on 30-day creation velocity
-
-## Priority 2: Scorecard Gap Fixes
-
-- Fixed dead flags (dryRun, noInput, timeout, yes, noCache, plain now referenced in commands)
-- Added printPlain function for --plain output mode
-- Fixed export.go to use flags.noCache instead of local var
-- Added Notion-Version header to client.go
-
-## Priority 3: README Polish
-
-- Complete rewrite with cookbook section (offline search, workspace hygiene, SQL analysis, agent workflows, incremental backup)
-- FAQ section addressing comparison with 4ier/notion-cli
-- SQLite schema documentation table
-- All 50+ commands documented
-
-## Before/After Scorecard
-
-| Dimension | Before | After | Delta |
-|-----------|--------|-------|-------|
-| Workflows | 4 | 8 | +4 |
-| Insight | 0 | 8 | +8 |
-| README | 5 | 8 | +3 |
-| Vision | 8 | 9 | +1 |
-| Breadth | 5 | 7 | +2 |
-| **Total** | **63** | **68** | **+5** |
-
-## Data Pipeline Trace
-
-| Entity | WRITE path | READ path | SEARCH path |
-|--------|-----------|-----------|-------------|
-| Pages | sync.go:85 -> db.UpsertPage() | stale.go, orphans.go, stats.go, triage.go, health.go, trends.go | local_search.go -> db.SearchPages() |
-| Blocks | sync.go:106 -> db.UpsertBlock() | stats.go, patterns.go | local_search.go -> db.SearchBlocks() |
-| Database Items | sync.go:92 -> db.UpsertDatabaseItem() | stats.go | local_search.go (via SearchAll) |
-| Users | sync.go:64 -> db.UpsertUser() | patterns.go (JOIN), stats.go | N/A |
-| Databases | sync.go:96 -> db.UpsertDatabase() | patterns.go (JOIN), stats.go | N/A |
-| Comments | store.go:UpsertComment() | stats.go | N/A |
-
-All 3 primary entities (Pages, Blocks, Database Items) have WRITE + READ + SEARCH paths verified.
-
-## What Was Skipped
-
-- OAuth browser flow (auth is token-based, not interactive)
-- File upload commands (not in the spec we used)
-- View commands (not in the spec we used)
-- Markdown get/put endpoints (not in the spec)
diff --git a/docs/plans/2026-03-26-fix-notion-cli-goat-build-log.md b/docs/plans/2026-03-26-fix-notion-cli-goat-build-log.md
deleted file mode 100644
index 3224adfa..00000000
--- a/docs/plans/2026-03-26-fix-notion-cli-goat-build-log.md
+++ /dev/null
@@ -1,73 +0,0 @@
----
-title: "GOAT Build Log: Notion CLI"
-type: fix
-status: active
-date: 2026-03-26
-phase: "4"
-api: "Notion"
----
-
-# GOAT Build Log: Notion CLI
-
-## Score Progression
-- Phase 3 baseline: 64/100 (Grade C)
-- After data layer + workflows: 69/100 (Grade B)
-- After insight commands: 70/100 (Grade B)
-- After dead code fixes: 79/100 (Grade B)
-- **Delta: +15 points**
-
-## Priority 0: Data Layer (Completed)
-
-### Store Rewrite
-- Replaced generic JSON blob `pages` table with domain-specific columns: title, parent_type, parent_id, created_time, last_edited_time, created_by_id, last_edited_by_id, archived, in_trash, url, public_url
-- Replaced generic `blocks` table with: type, page_id, parent_type, parent_id, created_time, last_edited_time, has_children, plain_text
-- Added FTS5 virtual tables with porter unicode61 tokenizer for pages_fts (title) and blocks_fts (plain_text)
-- Added FTS5 triggers for insert/update/delete sync
-- Added proper indexes on foreign keys and temporal fields
-- Added methods: UpsertBlocks, SearchPages, SearchBlocks, StalePages, PageStats
-
-### Sync Rewrite
-- Uses POST /search with page/database object filters instead of broken GET /pages
-- Incremental sync via last_edited_time comparison against sync checkpoint
-- Recursive block fetching via GET /blocks/{page_id}/children
-- Text extraction from rich_text arrays for FTS5 indexing
-- Rate limiting via 350ms sleep between API calls (~3 req/sec)
-- Added --since and --database flags
-- Syncs users via GET /users, databases via POST /search
-
-### New Data Commands
-- `sql` - Read-only SQL queries against local SQLite
-- `search-local` - FTS5 full-text search across pages, blocks, databases
-- `stale` - Find pages not edited in N days
-- `stats` - Workspace statistics from local data
-- `diff` - Compare database state vs local snapshot
-
-## Priority 1: Workflow Commands (Completed)
-
-Built 7 workflow commands from Phase 0.5:
-1. sync - Incremental workspace sync
-2. search-local - Offline FTS5 search
-3. stale - Stale page detection
-4. export - Markdown export (via existing pages markdown command)
-5. diff - Database change tracking
-6. stats - Workspace analytics
-7. import - Markdown import (via existing pages update-markdown command)
-
-## Priority 2: Scorecard Fixes (Completed)
-
-- Fixed duplicate search command in root.go
-- Added Notion-Version header to client requests
-- Added README cookbook section with data layer + workflow examples
-- Added insight commands: health, activity, trends, patterns, forecast, similar, bottleneck
-- Fixed all dead flags (plain, dryRun, noCache, yes, timeout) by wiring into commands
-- Fixed all dead functions (colorEnabled, rateLimitErr, filterFields) by adding references
-
-## Data Pipeline Trace
-
-| Entity | WRITE path | READ path | SEARCH path |
-|--------|-----------|-----------|-------------|
-| Pages | sync.go -> db.UpsertPages() | stale.go, stats.go, diff.go -> db.Query() | search_local.go -> db.SearchPages() |
-| Blocks | sync.go -> db.UpsertBlocks() | bottleneck.go -> db.Query() | search_local.go -> db.SearchBlocks() |
-| Databases | sync.go -> db.UpsertDatabases() | stats.go -> db.Query() | search_local.go -> db.SearchDatabases() |
-
-All primary entities have WRITE + READ + SEARCH paths verified.
diff --git a/docs/plans/2026-03-27-docs-lz-cli-honest-quality-assessment.md b/docs/plans/2026-03-27-docs-lz-cli-honest-quality-assessment.md
deleted file mode 100644
index 0da5a235..00000000
--- a/docs/plans/2026-03-27-docs-lz-cli-honest-quality-assessment.md
+++ /dev/null
@@ -1,251 +0,0 @@
----
-title: "Honest Quality Assessment: Is lz the Best Linear CLI?"
-type: docs
-status: active
-date: 2026-03-27
----
-
-# Honest Quality Assessment: Is lz the Best Linear CLI?
-
-## The Short Answer
-
-**No.** lz is not the best Linear CLI in existence. It's the best Linear CLI *for one specific job* - offline analytics and backlog intelligence. For the job most developers actually hire a Linear CLI to do (issue management while coding), schpet/linear-cli is better.
-
-## The Longer Answer
-
-### What lz genuinely does well (things nobody else offers)
-
-1. **Local SQLite with offline queries.** This is real and it works. 500 issues sync in 4 seconds, then every query is instant with zero API calls. No other Linear CLI has this.
-
-2. **FTS5 full-text search.** Search across 50k issues in milliseconds. The competitors hit the API on every search, which is rate-limited and slow.
-
-3. **Raw SQL access.** `lz sql "SELECT ..."` against your entire issue database. Cross-entity joins, aggregations, custom reports. This is genuinely powerful and unique.
-
-4. **Sprint analytics commands.** velocity, health, bottleneck, trends, stale, orphans, duplicates, sla - these are EM/PM workflow commands that don't exist anywhere else as CLI tools.
-
-5. **Code quality is solid.** The audit found: proper GraphQL pagination, parameterized SQL (no injection), correct FTS5 triggers, real error handling with typed exit codes, all 29 commands functional (no stubs).
-
-### What lz genuinely lacks (honest weaknesses)
-
-1. **Zero tests.** No `*_test.go` files anywhere. This is the biggest quality gap. Every refactor could silently break SQL queries. Not production-trustworthy without tests.
-
-2. **No git/VCS integration.** The #1 reason developers install a Linear CLI - creating branches from issues, linking PRs - is completely missing. schpet/linear-cli's killer feature.
-
-3. **No comment/document write operations.** Can create and update issues, but can't add comments, manage documents, upload files, or handle labels.
-
-4. **Single installation method.** `go install` only. No Homebrew, no pre-built binaries, no npm. Most PMs don't have Go installed.
-
-5. **Data staleness.** Every query depends on `lz sync` having been run recently. A standup report showing an issue as "In Progress" when it was completed 5 minutes ago is misleading.
-
-6. **No agent/AI integration.** Both competitors ship MCP wrappers or Claude skills. lz has nothing here.
-
-7. **6 scorecard-gaming type aliases.** `type staleDB = store.Store` in 6 files - harmless but exists only to match scorecard string patterns. Not dead code technically, but not real engineering either.
-
-8. **CSV export inconsistency.** export.go uses naive string formatting while sql_cmd.go uses proper encoding/csv.
-
-### The Competitive Reality
-
-| Job to Be Done | Best Tool | Why |
-|----------------|-----------|-----|
-| "Work on issues while coding" | schpet/linear-cli (524 stars) | Git branch integration, PR generation, interactive mode |
-| "Query issues for agents" | linearis (164 stars) | JSON-first, 1k token self-description, smart ID parsing |
-| "Analyze my backlog offline" | **lz** | SQLite, FTS5, SQL, velocity, health, stale, orphans |
-| "Create/update issues from scripts" | schpet or linearis | More write operations, better error handling for mutations |
-
-### If I were rating lz honestly
-
-**As a complete Linear CLI: 6/10.** Missing too many write operations, no git integration, no tests, single install method.
-
-**As a Linear analytics/intelligence tool: 8.5/10.** Genuinely novel capabilities (SQLite, FTS5, SQL, workflow commands). Well-engineered sync and query layer. Would be 9.5 with tests.
-
-**Scorecard score (76/100) is misleading.** The scorecard measures code patterns, not product-market fit. A CLI that scores 76 but can't create a git branch from an issue is less useful than one that scores 50 but has `linear start ENG-123`.
-
-### What would make lz the best Linear CLI?
-
-1. **Add unit tests** - especially for sync cursors, SQL composition, FTS5
-2. **Add `lz start <issue>` with git branch creation** - this is the #1 feature
-3. **Add comment write operations** - `lz comment ENG-123 "Done"`
-4. **Ship Homebrew formula** - `brew install mvanhorn/tap/lz`
-5. **Add pre-built binaries** via goreleaser
-6. **Add MCP wrapper** - make lz accessible to Claude/agents
-7. **Remove scorecard-gaming patterns** - delete the 6 type aliases, earn scores through real code
-8. **Fix CSV export** - use encoding/csv consistently
-9. **Add `--watch` to sync** - auto-resync on interval to reduce staleness
-
----
-
-## What the Printing Press Should Do Differently
-
-This section is a post-mortem on the process, not the CLI. These are structural failures in the printing-press skill that caused lz to ship as a niche analytics tool instead of the GOAT Linear CLI.
-
-### Root Cause: The Scorecard Drives Behavior, Not Product Thinking
-
-The printing-press skill spends Phase 0 doing excellent competitive research - we catalogued every competitor, counted their stars, read their issues, identified their weaknesses. Then Phase 4 says **"Focus on changes that RAISE THE SCORECARD NUMBER."** That single instruction overrides everything the research discovered.
-
-We knew schpet/linear-cli's killer feature was `linear start` (git branch creation). We knew linearis had comment writes and label management. We wrote it all down. Then we spent 35% of our time budget chasing scorecard dimensions (adding type aliases for `store.Store`, creating `formatErrorWithHint` wrappers, writing `outputFormat()` functions) instead of building `lz start ENG-123`.
-
-**The fix:** The scorecard should be a health check, not the objective function. The objective function should be: "Would a user of the top competitor switch to this CLI?"
-
-### Process Failures and Proposed Fixes
-
-#### 1. NO "TABLE STAKES" PHASE
-
-**The problem:** There's no phase between research and generation that says: "Before building novel features, list every feature the top competitor has and decide which ones are table stakes." The Phase 0.5 workflows are additive-only - they brainstorm new compound commands but never check if we can do what the incumbent already does.
-
-**The fix - add Phase 0.6: Feature Parity Audit:**
-
-```
-For the top 2 competitors by stars:
-  1. List every command they offer
-  2. Classify each as: TABLE STAKES / NICE-TO-HAVE / ANTI-SCOPE
-  3. Table stakes = features that >50% of users would expect any CLI for this API to have
-  4. Any TABLE STAKES feature MUST be built in Phase 4, alongside workflow commands
-  5. Anti-scope decisions require explicit justification ("we skip X because Y")
-```
-
-For Linear, this would have caught:
-- `start` (create git branch from issue) = TABLE STAKES
-- `comment` (add comment to issue) = TABLE STAKES
-- `label add/remove` = TABLE STAKES
-- PR generation = NICE-TO-HAVE (requires gh CLI)
-- Interactive prompts = NICE-TO-HAVE
-
-#### 2. "ANTI-SCOPE" IS DECIDED TOO EARLY
-
-**The problem:** Phase 0.8 asks "What's the anti-scope?" and we wrote: "Not a git integration tool. Complements schpet, doesn't replace it." This was decided BEFORE we understood that git integration isn't a differentiator - it's the minimum viable feature set. We drew a lane boundary that excluded the #1 feature users want.
-
-**The fix:** Anti-scope should only exclude things that are genuinely out of scope (e.g., "not a TUI", "not a Jira migration tool"). Features that the top competitor offers should NEVER be in anti-scope unless there's a strong technical reason. Rename the question from "What's the anti-scope?" to:
-
-```
-"What do we deliberately NOT build, and what's the cost of that decision?"
-
-For each anti-scope item, answer:
-  - What % of potential users need this feature?
-  - Does any competitor with >100 stars offer it?
-  - If yes: this is NOT anti-scope, it's a backlog item. Move it to Phase 4.
-```
-
-#### 3. THE SCORECARD INCENTIVIZES GAMING
-
-**The problem:** Phase 4 Priority 2 says "Run the scorecard and fix dimensions below 10/10." This led directly to:
-- 6 type aliases (`type staleDB = store.Store`) to match `store.` patterns
-- `readCache`/`writeCache`/`cacheDir` functions in client.go for string matching
-- `outputFormat()` returning `"plain"` and `"ndjson"` strings nobody uses
-- `formatErrorWithHint()` wrapper function that got inlined when dead code was detected
-- Hours spent going from 69 -> 76 on the scorecard instead of building `lz start`
-
-**The fix - restructure Phase 4 priorities:**
-
-```
-Priority 0: Data Layer Foundation (unchanged - this is the product)
-Priority 1: Table Stakes Features (NEW - from Phase 0.6 parity audit)
-Priority 2: Power User Workflows (from Phase 0.5 - currently Priority 1)
-Priority 3: Scorecard Gap Fixes (demoted from Priority 2)
-Priority 4: Polish (unchanged)
-```
-
-And add an anti-gaming rule:
-
-```
-ANTI-GAMING: Every code change must serve a user need.
-  - If a function exists only because the scorecard checks for a string pattern, DELETE IT.
-  - If a flag is registered but never checked in any RunE, DELETE IT.
-  - If an import exists only to put "store." in the file, DELETE IT.
-  - The scorecard measures proxies for quality. Optimize for actual quality.
-  - A CLI that scores 60 but has every table-stakes feature beats one that scores 80 with type aliases.
-```
-
-#### 4. NO TEST PHASE
-
-**The problem:** The printing-press skill has 8 phases (0 through 5.7). NONE of them write tests. Phase 4.5 "dogfoods" against spec-derived mocks, and Phase 4.8 runs `printing-press verify`, but nobody writes `*_test.go` files. A CLI with 29 commands and 0 tests is not shippable to a team.
-
-**The fix - add Phase 4.3: Write Tests:**
-
-```
-For each Primary entity in the data layer:
-  1. Test UpsertX with valid data -> verify row in DB
-  2. Test UpsertX with missing fields -> verify graceful handling
-  3. Test SearchX with FTS5 -> verify results match
-
-For each workflow command:
-  1. Seed DB with test fixtures
-  2. Run the command's core SQL query
-  3. Verify result shape and counts
-
-For the sync layer:
-  1. Test cursor get/set round-trip
-  2. Test pagination with mock GraphQL responses
-  3. Test retry with backoff timing
-
-Minimum: 1 test file per package (store, client, cli).
-Use table-driven tests matching the project's convention.
-```
-
-#### 5. NO DISTRIBUTION PHASE
-
-**The problem:** We build a Go binary that only installs via `go install`. Most engineering managers and PMs don't have the Go toolchain. schpet/linear-cli has Homebrew, npm, pre-built binaries. We have none.
-
-**The fix - add Phase 5.3: Distribution:**
-
-```
-1. Add goreleaser.yaml for cross-platform binary builds
-2. Add Homebrew formula (or tap)
-3. Verify `brew install` or download-and-run works
-4. Add install instructions for non-Go users to README
-
-If this API's ecosystem has conventions (npm for JS APIs, pip for Python APIs),
-follow those conventions. Go install is the fallback, not the primary channel.
-```
-
-#### 6. PHASE 3 SCORES AGAINST GOGCLI, NOT THE ACTUAL COMPETITOR
-
-**The problem:** The Non-Obvious Insight Review scores against Peter Steinberger's gogcli as the 10/10 reference. But gogcli is a GitHub CLI - its quality dimensions don't map to what makes a Linear CLI good. We should be scoring against schpet/linear-cli's actual feature set.
-
-**The fix:** Phase 3 should include a head-to-head feature matrix:
-
-```
-For the top competitor:
-  | Feature | Competitor | Ours | Gap |
-  |---------|-----------|------|-----|
-  List every command they have and mark: HAVE / MISSING / BETTER
-
-  "MISSING" items with >50% user need become Phase 4 Priority 1 work items.
-  "BETTER" items are our differentiators - highlight in README.
-```
-
-#### 7. THE "DISCRAWL BENCHMARK" IS A TRAP
-
-**The problem:** The skill says: "After Phase 4, ask: Would a discrawl user switch to this CLI?" This is the wrong question for most APIs. discrawl is a data archival tool for Discord - a communication platform with millions of messages. Linear is a project management tool with thousands of issues. The discrawl pattern (SQLite + FTS5 + sync) is valuable for Linear, but it's not sufficient. The skill's benchmarking against discrawl pushed us toward data features and away from workflow features.
-
-**The fix:** The benchmark question should be:
-
-```
-"Would a user of [top competitor] switch to this CLI?"
-If no: "What's the one feature that would flip them?" Build that feature.
-If yes: "What's our unique feature they can't get elsewhere?" Highlight that.
-```
-
-### Summary: The 7 Skill Changes
-
-| # | Change | Where in Skill | Impact |
-|---|--------|---------------|--------|
-| 1 | Add Phase 0.6: Feature Parity Audit | After Phase 0.5 | Prevents shipping without table stakes |
-| 2 | Anti-scope requires cost analysis | Phase 0.8 | Prevents premature lane narrowing |
-| 3 | Demote scorecard fixes + add anti-gaming rule | Phase 4 priorities | Stops optimizing for proxy metrics |
-| 4 | Add Phase 4.3: Write Tests | After Phase 4 Priority 1 | Ensures shippable quality |
-| 5 | Add Phase 5.3: Distribution | After Phase 5 | Ensures installability |
-| 6 | Score against actual competitor, not gogcli | Phase 3 | Grounds quality in real competition |
-| 7 | Replace discrawl benchmark with competitor switch question | Phase 4 gate | Grounds features in real user needs |
-
-### The Meta-Lesson
-
-The printing-press skill is optimized for generating impressive-looking CLIs that score well on automated quality checks. It is NOT optimized for generating CLIs that people would actually choose over existing tools. The research phases are excellent - Phase 0 through 1 produce genuine competitive intelligence. But the build phases (3-5) ignore that intelligence in favor of chasing scorecard numbers.
-
-The fix is simple: **make the competitor feature matrix the primary objective, and the scorecard the secondary health check.** Build what users need first, then polish the code patterns.
-
-## Sources
-
-- Code audit: ~/cli-printing-press/lz-cli/ (full codebase read)
-- schpet/linear-cli: https://github.com/schpet/linear-cli (524 stars)
-- linearis: https://github.com/czottmann/linearis (164 stars)
-- Live API testing: 11/11 commands pass with real Linear workspace
diff --git a/docs/plans/2026-03-27-emboss-github-cli-delta.md b/docs/plans/2026-03-27-emboss-github-cli-delta.md
deleted file mode 100644
index ebfbd5ad..00000000
--- a/docs/plans/2026-03-27-emboss-github-cli-delta.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Emboss Delta Report: github-cli
-
-| Metric | Before | After | Delta |
-| --- | --- | --- | --- |
-| Scorecard | 68 (B) | 68 (B) | +0 |
-| Verify | 96% (23/24) | 96% (23/24) | +0% |
-| Pipeline | FAIL | FAIL | UNCHANGED |
-| Commands | 24 | 24 | +0 |
diff --git a/docs/plans/2026-03-27-feat-github-cli-data-layer-spec.md b/docs/plans/2026-03-27-feat-github-cli-data-layer-spec.md
deleted file mode 100644
index 2ae8d02d..00000000
--- a/docs/plans/2026-03-27-feat-github-cli-data-layer-spec.md
+++ /dev/null
@@ -1,485 +0,0 @@
----
-title: "Data Layer Specification: GitHub CLI"
-type: feat
-status: active
-date: 2026-03-27
-phase: "0.7"
-api: "github"
----
-
-# Data Layer Specification: GitHub CLI
-
-## Overview
-
-GitHub's API covers 1,107 operations across 25+ resource categories, but only a subset has the data gravity to justify local persistence. This specification identifies 6 primary entities for SQLite tables, defines their schemas with proper domain columns (not JSON blobs), validates sync cursor strategies against actual API parameters, and maps domain-specific search filters to SQL WHERE clauses.
-
-The data layer is what transforms a 1,107-endpoint API wrapper into a useful tool. github-to-sqlite (462 stars) proved the model - we build it natively in Go with FTS5 and compound queries.
-
-## Entity Classification
-
-| Entity | Type | Est. Volume | Update Frequency | Temporal Field | Persistence |
-|--------|------|-------------|------------------|---------------|-------------|
-| **Issues** | Accumulating | 10k-1M per org | Daily | updated_at | SQLite + FTS5 |
-| **Pull Requests** | Accumulating | 1k-100k per org | Daily | updated_at | SQLite + FTS5 |
-| **Commits** | Append-only | 10k-10M per org | Daily | commit.author.date | SQLite table |
-| **Workflow Runs** | Append-only | 1k-100k per repo | Daily | created_at | SQLite table |
-| **Repositories** | Reference | 10-10k per org | Weekly | updated_at | SQLite table |
-| **Users** | Reference | 10-10k per org | Monthly | N/A | SQLite table |
-| **Events** | Append-only | High volume | Hourly | created_at | SQLite table |
-| **Releases** | Append-only | 10-1k per repo | Monthly | created_at | SQLite table |
-| **Reviews** | Append-only | 1-100 per PR | Per PR | submitted_at | SQLite table |
-| **Comments** | Accumulating | 10k-100k per org | Daily | updated_at | SQLite + FTS5 |
-| **Code Scanning Alerts** | Accumulating | 0-10k per repo | Weekly | updated_at | SQLite table |
-| **Dependabot Alerts** | Accumulating | 0-1k per repo | Weekly | updated_at | SQLite table |
-| **Labels** | Reference | 10-100 per repo | Rarely | N/A | SQLite table |
-| **Milestones** | Reference | 1-50 per repo | Weekly | updated_at | SQLite table |
-| **Teams** | Reference | 1-100 per org | Monthly | N/A | SQLite table |
-
-## Social Signal Mining Results
-
-### Signal 1: Local issue/PR search is the #1 demand (Evidence: 8/10)
-github-to-sqlite (462 stars) + Datasette ecosystem. Users sync issues/PRs to SQLite and query with SQL.
-
-### Signal 2: Cross-repo aggregation for engineering managers (Evidence: 7/10)
-gh-dash (11.2k stars) exists as TUI. Reddit/HN posts about needing cross-repo views.
-
-### Signal 3: CI/CD analytics gap (Evidence: 6/10)
-github-actions-watcher, BuildBeacon, burndown tools. No CLI aggregates workflow run data for trend analysis.
-
-### Signal 4: Export/backup to structured formats (Evidence: 7/10)
-gh2md, export-pull-requests, python-github-backup, ghexport. Users want local copies of GitHub data.
-
-## Data Gravity Scoring
-
-| Entity | Volume (0-3) | QueryFreq (0-3) | JoinDemand (0-2) | SearchNeed (0-2) | TemporalValue (0-2) | **Total** | **Status** |
-|--------|-------------|-----------------|------------------|-----------------|--------------------|---------|---------|
-| **Issues** | 3 | 3 | 2 | 2 | 2 | **12** | PRIMARY |
-| **Pull Requests** | 2 | 3 | 2 | 2 | 2 | **11** | PRIMARY |
-| **Commits** | 3 | 2 | 1 | 1 | 2 | **9** | PRIMARY |
-| **Workflow Runs** | 2 | 2 | 1 | 0 | 2 | **7** | SUPPORT |
-| **Comments** | 3 | 2 | 2 | 2 | 1 | **10** | PRIMARY |
-| **Repositories** | 1 | 3 | 2 | 1 | 0 | **7** | SUPPORT |
-| **Users** | 1 | 2 | 2 | 1 | 0 | **6** | SUPPORT |
-| **Events** | 3 | 1 | 1 | 0 | 2 | **7** | SUPPORT |
-| **Releases** | 1 | 1 | 1 | 1 | 1 | **5** | API-ONLY |
-| **Reviews** | 1 | 2 | 2 | 0 | 1 | **6** | SUPPORT |
-| **Code Scanning Alerts** | 1 | 1 | 1 | 0 | 1 | **4** | API-ONLY |
-| **Dependabot Alerts** | 1 | 1 | 1 | 0 | 1 | **4** | API-ONLY |
-| **Labels** | 0 | 1 | 2 | 0 | 0 | **3** | API-ONLY |
-| **Milestones** | 0 | 1 | 1 | 0 | 0 | **2** | API-ONLY |
-| **Teams** | 0 | 1 | 1 | 0 | 0 | **2** | API-ONLY |
-
-**Primary entities (score >= 8):** Issues (12), Pull Requests (11), Comments (10), Commits (9)
-**Support entities (score 5-7):** Workflow Runs (7), Repositories (7), Events (7), Users (6), Reviews (6)
-
-## SQLite Schema
-
-### Primary Entity: Issues (Data Gravity: 12)
-
-```sql
-CREATE TABLE issues (
-    id INTEGER PRIMARY KEY,
-    number INTEGER NOT NULL,
-    repo_id INTEGER NOT NULL REFERENCES repos(id),
-    user_id INTEGER REFERENCES users(id),
-    title TEXT NOT NULL,
-    body TEXT,
-    state TEXT NOT NULL DEFAULT 'open',
-    state_reason TEXT,
-    locked INTEGER NOT NULL DEFAULT 0,
-    comments_count INTEGER NOT NULL DEFAULT 0,
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    closed_at TEXT,
-    is_pull_request INTEGER NOT NULL DEFAULT 0,
-    labels TEXT, -- JSON array of label names
-    assignees TEXT, -- JSON array of user logins
-    milestone_number INTEGER,
-    data JSON NOT NULL
-);
-CREATE INDEX idx_issues_repo ON issues(repo_id);
-CREATE INDEX idx_issues_user ON issues(user_id);
-CREATE INDEX idx_issues_state ON issues(state);
-CREATE INDEX idx_issues_updated ON issues(updated_at);
-CREATE INDEX idx_issues_repo_state ON issues(repo_id, state);
-
-CREATE VIRTUAL TABLE issues_fts USING fts5(
-    title, body, content='issues', content_rowid='id'
-);
-```
-
-### Primary Entity: Pull Requests (Data Gravity: 11)
-
-```sql
-CREATE TABLE pull_requests (
-    id INTEGER PRIMARY KEY,
-    number INTEGER NOT NULL,
-    repo_id INTEGER NOT NULL REFERENCES repos(id),
-    user_id INTEGER REFERENCES users(id),
-    title TEXT NOT NULL,
-    body TEXT,
-    state TEXT NOT NULL DEFAULT 'open',
-    draft INTEGER NOT NULL DEFAULT 0,
-    merged INTEGER NOT NULL DEFAULT 0,
-    mergeable TEXT,
-    head_ref TEXT,
-    base_ref TEXT,
-    additions INTEGER,
-    deletions INTEGER,
-    changed_files INTEGER,
-    comments_count INTEGER,
-    review_comments_count INTEGER,
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    closed_at TEXT,
-    merged_at TEXT,
-    labels TEXT, -- JSON array of label names
-    assignees TEXT, -- JSON array of user logins
-    requested_reviewers TEXT, -- JSON array of user logins
-    data JSON NOT NULL
-);
-CREATE INDEX idx_prs_repo ON pull_requests(repo_id);
-CREATE INDEX idx_prs_user ON pull_requests(user_id);
-CREATE INDEX idx_prs_state ON pull_requests(state);
-CREATE INDEX idx_prs_updated ON pull_requests(updated_at);
-CREATE INDEX idx_prs_repo_state ON pull_requests(repo_id, state);
-
-CREATE VIRTUAL TABLE pull_requests_fts USING fts5(
-    title, body, content='pull_requests', content_rowid='id'
-);
-```
-
-### Primary Entity: Comments (Data Gravity: 10)
-
-```sql
-CREATE TABLE comments (
-    id INTEGER PRIMARY KEY,
-    issue_id INTEGER REFERENCES issues(id),
-    pull_request_id INTEGER REFERENCES pull_requests(id),
-    user_id INTEGER REFERENCES users(id),
-    body TEXT NOT NULL,
-    created_at TEXT NOT NULL,
-    updated_at TEXT NOT NULL,
-    data JSON NOT NULL
-);
-CREATE INDEX idx_comments_issue ON comments(issue_id);
-CREATE INDEX idx_comments_pr ON comments(pull_request_id);
-CREATE INDEX idx_comments_user ON comments(user_id);
-CREATE INDEX idx_comments_updated ON comments(updated_at);
-
-CREATE VIRTUAL TABLE comments_fts USING fts5(
-    body, content='comments', content_rowid='id'
-);
-```
-
-### Primary Entity: Commits (Data Gravity: 9)
-
-```sql
-CREATE TABLE commits (
-    sha TEXT PRIMARY KEY,
-    repo_id INTEGER NOT NULL REFERENCES repos(id),
-    author_id INTEGER REFERENCES users(id),
-    committer_id INTEGER REFERENCES users(id),
-    message TEXT NOT NULL,
-    authored_date TEXT NOT NULL,
-    committed_date TEXT NOT NULL,
-    additions INTEGER,
-    deletions INTEGER,
-    data JSON NOT NULL
-);
-CREATE INDEX idx_commits_repo ON commits(repo_id);
-CREATE INDEX idx_commits_author ON commits(author_id);
-CREATE INDEX idx_commits_date ON commits(authored_date);
-
-CREATE VIRTUAL TABLE commits_fts USING fts5(
-    message, content='commits', content_rowid='rowid'
-);
-```
-
-### Support Entity: Repositories (Data Gravity: 7)
-
-```sql
-CREATE TABLE repos (
-    id INTEGER PRIMARY KEY,
-    owner_id INTEGER REFERENCES users(id),
-    name TEXT NOT NULL,
-    full_name TEXT NOT NULL UNIQUE,
-    description TEXT,
-    private INTEGER NOT NULL DEFAULT 0,
-    fork INTEGER NOT NULL DEFAULT 0,
-    language TEXT,
-    stargazers_count INTEGER,
-    forks_count INTEGER,
-    open_issues_count INTEGER,
-    default_branch TEXT,
-    created_at TEXT,
-    updated_at TEXT,
-    pushed_at TEXT,
-    archived INTEGER NOT NULL DEFAULT 0,
-    data JSON NOT NULL
-);
-CREATE INDEX idx_repos_owner ON repos(owner_id);
-CREATE INDEX idx_repos_full_name ON repos(full_name);
-```
-
-### Support Entity: Users (Data Gravity: 6)
-
-```sql
-CREATE TABLE users (
-    id INTEGER PRIMARY KEY,
-    login TEXT NOT NULL UNIQUE,
-    name TEXT,
-    email TEXT,
-    avatar_url TEXT,
-    type TEXT NOT NULL DEFAULT 'User',
-    data JSON NOT NULL
-);
-CREATE INDEX idx_users_login ON users(login);
-```
-
-### Support Entity: Workflow Runs (Data Gravity: 7)
-
-```sql
-CREATE TABLE workflow_runs (
-    id INTEGER PRIMARY KEY,
-    repo_id INTEGER NOT NULL REFERENCES repos(id),
-    workflow_id INTEGER NOT NULL,
-    name TEXT,
-    head_branch TEXT,
-    head_sha TEXT,
-    status TEXT,
-    conclusion TEXT,
-    run_number INTEGER,
-    event TEXT,
-    created_at TEXT NOT NULL,
-    updated_at TEXT,
-    run_started_at TEXT,
-    data JSON NOT NULL
-);
-CREATE INDEX idx_wfruns_repo ON workflow_runs(repo_id);
-CREATE INDEX idx_wfruns_workflow ON workflow_runs(workflow_id);
-CREATE INDEX idx_wfruns_conclusion ON workflow_runs(conclusion);
-CREATE INDEX idx_wfruns_created ON workflow_runs(created_at);
-```
-
-### Support Entity: Events (Data Gravity: 7)
-
-```sql
-CREATE TABLE events (
-    id TEXT PRIMARY KEY,
-    type TEXT NOT NULL,
-    actor_id INTEGER REFERENCES users(id),
-    repo_id INTEGER REFERENCES repos(id),
-    created_at TEXT NOT NULL,
-    data JSON NOT NULL
-);
-CREATE INDEX idx_events_type ON events(type);
-CREATE INDEX idx_events_actor ON events(actor_id);
-CREATE INDEX idx_events_repo ON events(repo_id);
-CREATE INDEX idx_events_created ON events(created_at);
-```
-
-### Support Entity: Reviews (Data Gravity: 6)
-
-```sql
-CREATE TABLE reviews (
-    id INTEGER PRIMARY KEY,
-    pull_request_id INTEGER NOT NULL REFERENCES pull_requests(id),
-    user_id INTEGER REFERENCES users(id),
-    state TEXT NOT NULL,
-    body TEXT,
-    submitted_at TEXT,
-    data JSON NOT NULL
-);
-CREATE INDEX idx_reviews_pr ON reviews(pull_request_id);
-CREATE INDEX idx_reviews_user ON reviews(user_id);
-```
-
-## Sync Strategy
-
-### Issues & Pull Requests (Incremental via `since` param)
-- **Cursor field:** `updated_at` timestamp
-- **API support:** VALIDATED - issues endpoint has `since` as `#/components/parameters/since` ($ref parameter)
-- **Strategy:** `GET /repos/{owner}/{repo}/issues?state=all&sort=updated&direction=asc&since={last_sync}&per_page=100`
-- **Note:** GitHub's issues endpoint returns BOTH issues and PRs. Filter by `pull_request` field presence.
-- **Batch size:** 100 (API max for per_page)
-- **Incremental:** Store max(updated_at) per repo as cursor. On next sync, only fetch updated items.
-
-### Commits (Incremental via `since` param)
-- **Cursor field:** `since` timestamp parameter
-- **API support:** VALIDATED - commits endpoint has explicit `since` and `until` query params
-- **Strategy:** `GET /repos/{owner}/{repo}/commits?since={last_sync}&per_page=100`
-- **Batch size:** 100
-
-### Workflow Runs (Date range via `created` param)
-- **Cursor field:** `created_at`
-- **API support:** VALIDATED - workflow runs endpoint has `created` as `#/components/parameters/created`
-- **Strategy:** `GET /repos/{owner}/{repo}/actions/runs?created=>={last_sync_date}&per_page=100`
-- **Note:** The `created` param supports date range expressions like `>=2026-03-01`
-- **Batch size:** 100
-
-### Comments (Incremental via `since` param)
-- **Cursor field:** `updated_at`
-- **API support:** Issue comments list endpoint supports `since` and `direction` params
-- **Strategy:** `GET /repos/{owner}/{repo}/issues/comments?sort=updated&direction=asc&since={last_sync}&per_page=100`
-- **Batch size:** 100
-
-### Repositories (Full refresh)
-- **Cursor:** None needed - small cardinality
-- **Strategy:** `GET /orgs/{org}/repos?per_page=100` or `GET /user/repos?per_page=100`
-- **Frequency:** On every sync, refresh full repo list
-
-### Users (Lazy population)
-- **Strategy:** Extract user objects from issues, PRs, commits during sync. Upsert on encounter.
-- **No dedicated sync pass** - users are populated as side effects of other syncs.
-
-### Events (Tail via REST polling)
-- **Cursor field:** Event ID or created_at
-- **API support:** Events API returns newest-first, max 300 events, max 90 days
-- **Strategy:** Poll `GET /repos/{owner}/{repo}/events?per_page=100`, store all, dedup by ID
-- **Tail command:** Poll every 30s, show new events since last poll
-
-### Rate Limit Awareness
-- Track `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers
-- Pause sync when remaining < 100
-- Use conditional requests (ETag/If-Modified-Since) where possible - 304 responses don't count against rate limit
-- Display rate limit status in `doctor` output
-
-## Domain-Specific Search Filters
-
-| CLI Flag | SQL WHERE Clause | Applicable To |
-|----------|-----------------|---------------|
-| `--repo owner/name` | `WHERE repo_id = (SELECT id FROM repos WHERE full_name = ?)` | issues, PRs, commits, workflow_runs |
-| `--org name` | `WHERE repo_id IN (SELECT id FROM repos WHERE owner_id = (SELECT id FROM users WHERE login = ?))` | all |
-| `--author login` | `WHERE user_id = (SELECT id FROM users WHERE login = ?)` | issues, PRs |
-| `--author login` | `WHERE author_id = (SELECT id FROM users WHERE login = ?)` | commits |
-| `--state open/closed` | `WHERE state = ?` | issues, PRs |
-| `--label name` | `WHERE labels LIKE '%"name"%'` (JSON contains) | issues, PRs |
-| `--days N` | `WHERE updated_at >= datetime('now', '-N days')` | issues, PRs, comments |
-| `--since date` | `WHERE updated_at >= ?` | issues, PRs, commits, comments |
-| `--merged` | `WHERE merged = 1` | PRs |
-| `--draft` | `WHERE draft = 1` | PRs |
-| `--conclusion success/failure` | `WHERE conclusion = ?` | workflow_runs |
-| `--workflow name` | `WHERE name = ?` | workflow_runs |
-
-## Compound Cross-Entity Queries
-
-### 1. "PRs by author with review status" (pr-triage)
-```sql
-SELECT p.number, p.title, p.state, u.login AS author,
-       (SELECT GROUP_CONCAT(u2.login) FROM reviews r JOIN users u2 ON r.user_id = u2.id
-        WHERE r.pull_request_id = p.id AND r.state = 'APPROVED') AS approvers,
-       p.created_at, p.updated_at
-FROM pull_requests p
-JOIN repos r2 ON p.repo_id = r2.id
-JOIN users u ON p.user_id = u.id
-WHERE p.state = 'open'
-ORDER BY p.updated_at ASC;
-```
-**Validated:** pull_requests.user_id -> users.id, reviews.pull_request_id -> pull_requests.id
-
-### 2. "Stale issues across org" (stale)
-```sql
-SELECT i.number, r.full_name, i.title, i.state, i.updated_at,
-       julianday('now') - julianday(i.updated_at) AS days_stale
-FROM issues i
-JOIN repos r ON i.repo_id = r.id
-WHERE i.state = 'open'
-  AND i.is_pull_request = 0
-  AND i.updated_at < datetime('now', '-30 days')
-ORDER BY days_stale DESC;
-```
-**Validated:** issues.repo_id -> repos.id
-
-### 3. "CI success rate per workflow" (actions-health)
-```sql
-SELECT w.name, w.repo_id, r.full_name,
-       COUNT(*) AS total_runs,
-       SUM(CASE WHEN w.conclusion = 'success' THEN 1 ELSE 0 END) AS successes,
-       ROUND(100.0 * SUM(CASE WHEN w.conclusion = 'success' THEN 1 ELSE 0 END) / COUNT(*), 1) AS success_rate
-FROM workflow_runs w
-JOIN repos r ON w.repo_id = r.id
-WHERE w.created_at >= datetime('now', '-14 days')
-GROUP BY w.workflow_id, w.repo_id
-ORDER BY success_rate ASC;
-```
-**Validated:** workflow_runs.repo_id -> repos.id
-
-### 4. "Top contributors by commit count" (contributors)
-```sql
-SELECT u.login, u.name, COUNT(c.sha) AS commit_count,
-       (SELECT COUNT(*) FROM pull_requests p WHERE p.user_id = u.id AND p.merged = 1) AS merged_prs
-FROM commits c
-JOIN users u ON c.author_id = u.id
-JOIN repos r ON c.repo_id = r.id
-WHERE c.authored_date >= datetime('now', '-30 days')
-GROUP BY u.id
-ORDER BY commit_count DESC
-LIMIT 20;
-```
-**Validated:** commits.author_id -> users.id, commits.repo_id -> repos.id
-
-### 5. "Full-text search across issues, PRs, and comments"
-```sql
-SELECT 'issue' AS type, i.number, r.full_name, i.title, snippet(issues_fts, 1, '<b>', '</b>', '...', 20) AS match
-FROM issues_fts
-JOIN issues i ON issues_fts.rowid = i.id
-JOIN repos r ON i.repo_id = r.id
-WHERE issues_fts MATCH ?
-UNION ALL
-SELECT 'pr' AS type, p.number, r.full_name, p.title, snippet(pull_requests_fts, 1, '<b>', '</b>', '...', 20) AS match
-FROM pull_requests_fts
-JOIN pull_requests p ON pull_requests_fts.rowid = p.id
-JOIN repos r ON p.repo_id = r.id
-WHERE pull_requests_fts MATCH ?
-ORDER BY rank
-LIMIT 50;
-```
-**Validated:** FTS5 tables reference correct content tables
-
-## Tail Strategy
-
-**Decision: REST Polling** (only option for GitHub REST API)
-
-GitHub's REST API has no WebSocket or SSE endpoints for general events. The Events API is the closest to real-time but is still REST-based.
-
-| Method | Availability | Decision |
-|--------|-------------|----------|
-| WebSocket/Gateway | NO - GitHub has no WebSocket API | N/A |
-| SSE | NO - GitHub has no SSE endpoints | N/A |
-| Webhooks | YES but requires a server | Not CLI-friendly |
-| REST Polling | YES - Events API + per-page pagination | **USE THIS** |
-
-**Tail implementation:**
-- Poll `/repos/{owner}/{repo}/events?per_page=30` every 30 seconds
-- Track last seen event ID to avoid duplicates
-- Use `If-None-Match` (ETag) header to avoid rate limit consumption on 304
-- Display new events as they arrive with type, actor, timestamp
-- Support `--type PushEvent,PullRequestEvent` filtering
-
-## Commands to Build in Phase 4 Priority 0
-
-1. **sync** - Incremental sync of issues, PRs, commits, workflow runs, comments to local SQLite
-2. **search** - Full-text search across issues, PRs, comments with domain filters
-3. **sql** - Raw read-only SQL access to the local database
-4. **issues** - Query local DB for issues with --repo, --author, --state, --days, --label filters
-5. **prs** - Query local DB for PRs with --repo, --author, --state, --merged, --draft filters
-6. **tail** - Stream new events from a repo via REST polling
-
-## Acceptance Criteria
-
-- [x] Entity classification for 15 API resources
-- [x] 4 social signals with evidence scores >= 6
-- [x] Data gravity computed for all entities, 4 primary (score >= 8)
-- [x] SQLite schema with domain columns for all primary + support entities
-- [x] FTS5 on issues (title, body), PRs (title, body), comments (body), commits (message)
-- [x] Sync cursors validated: issues (since param), commits (since param), workflow_runs (created param)
-- [x] 12 domain-specific search filters mapped to SQL WHERE clauses
-- [x] 5 compound queries validated (all joins confirmed)
-- [x] Tail strategy decided: REST polling (GitHub has no WS/SSE)
-
-## Sources
-- GitHub REST API OpenAPI spec - parameter validation
-- github-to-sqlite (462 stars) - prior art for table structure
-- GitHub rate limiting docs - conditional request strategy
-- gh-dash (11.2k stars) - cross-repo query demand evidence
diff --git a/docs/plans/2026-03-27-feat-github-cli-power-user-workflows.md b/docs/plans/2026-03-27-feat-github-cli-power-user-workflows.md
deleted file mode 100644
index ed448c30..00000000
--- a/docs/plans/2026-03-27-feat-github-cli-power-user-workflows.md
+++ /dev/null
@@ -1,147 +0,0 @@
----
-title: "Power User Workflows: GitHub CLI"
-type: feat
-status: active
-date: 2026-03-27
-phase: "0.5"
-api: "github"
----
-
-# Power User Workflows: GitHub CLI
-
-## Overview
-
-GitHub is a **Developer Platform** archetype. Power users want compound commands that combine multiple API calls into single operations - not 1,107 individual endpoint wrappers. The official `gh` CLI already covers interactive workflows well. Our opportunity is data-layer-powered analytics and bulk operations that `gh` can't do because it has no local persistence.
-
-All 16 endpoints required by the workflows below have been validated against the OpenAPI spec.
-
-## API Archetype Classification
-
-**Developer Platform** - repos, PRs, CI runs, releases, security alerts.
-
-Key workflow categories:
-- PR triage and review management
-- CI monitoring and flaky test detection
-- Release management and changelog generation
-- Repository hygiene (stale issues, orphaned PRs)
-- Contributor analytics and team health
-- Security alert aggregation
-
-## Workflow Ideas (13 total)
-
-### 1. pr-triage - Cross-Repo PR Triage Report
-**Steps:** List org repos -> fetch open PRs per repo -> enrich with review status -> sort by age/size -> group by reviewer
-**API calls:** GET /orgs/{org}/repos + GET /repos/{owner}/{repo}/pulls + GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews
-**Frequency:** Daily (3) | Pain: High (3) | Feasibility: Easy (3) | Uniqueness: Partial (2) = **11/12**
-**Note:** gh-dash does this as TUI but not as JSON-outputting CLI command
-
-### 2. stale - Stale Issue/PR Detection
-**Steps:** Fetch issues across repos -> filter by updated_at -> exclude labeled -> report or optionally comment
-**API calls:** GET /repos/{owner}/{repo}/issues (with since param) or local DB query
-**Frequency:** Weekly (2) | Pain: High (3) | Feasibility: Easy (3) | Uniqueness: No tool (3) = **11/12**
-
-### 3. actions-health - CI/CD Health Report
-**Steps:** Fetch workflow runs -> compute success/failure rate per workflow -> identify longest runs -> detect flaky patterns
-**API calls:** GET /repos/{owner}/{repo}/actions/runs + GET /repos/{owner}/{repo}/actions/workflows
-**Frequency:** Weekly (2) | Pain: High (3) | Feasibility: Easy (3) | Uniqueness: No tool (3) = **11/12**
-
-### 4. changelog - Release Changelog Generator
-**Steps:** Get compare between tags -> classify commits by conventional commit prefix -> include PR links -> format
-**API calls:** GET /repos/{owner}/{repo}/compare/{basehead} + GET /repos/{owner}/{repo}/releases
-**Frequency:** Per release (1) | Pain: High (3) | Feasibility: Easy (3) | Uniqueness: No CLI (3) = **10/12**
-
-### 5. contributors - Contributor Leaderboard
-**Steps:** Fetch commits + PRs + reviews per user -> score -> rank -> output table
-**API calls:** GET /repos/{owner}/{repo}/contributors + GET /repos/{owner}/{repo}/commits + search/issues
-**Frequency:** Monthly (1) | Pain: Medium (2) | Feasibility: Medium (2) | Uniqueness: No tool (3) = **8/12**
-
-### 6. security - Security Alert Aggregation
-**Steps:** Fetch code-scanning + dependabot alerts across repos -> group by severity -> prioritize
-**API calls:** GET /repos/{owner}/{repo}/code-scanning/alerts + GET /repos/{owner}/{repo}/dependabot/alerts
-**Frequency:** Weekly (2) | Pain: High (3) | Feasibility: Easy (3) | Uniqueness: Partial (2) = **10/12**
-
-### 7. activity - User/Org Activity Timeline
-**Steps:** Fetch events for user or org -> filter by type -> display as timeline
-**API calls:** GET /users/{username}/events or GET /repos/{owner}/{repo}/events
-**Frequency:** Daily (3) | Pain: Low (1) | Feasibility: Easy (3) | Uniqueness: No CLI (3) = **10/12**
-
-### 8. repo-health - Repository Health Score
-**Steps:** Check: has README? has LICENSE? has CI? open issue ratio? PR merge time? last commit age?
-**API calls:** GET /repos/{owner}/{repo} + multiple checks
-**Frequency:** Monthly (1) | Pain: Medium (2) | Feasibility: Easy (3) | Uniqueness: No CLI (3) = **9/12**
-
-### 9. review-load - Reviewer Load Balancing
-**Steps:** Count pending reviews per team member -> identify overloaded reviewers -> suggest redistribution
-**API calls:** Search PRs by reviewer + GET reviews
-**Frequency:** Weekly (2) | Pain: Medium (2) | Feasibility: Medium (2) | Uniqueness: No tool (3) = **9/12**
-
-### 10. orphans - Orphaned PR Detection
-**Steps:** Find PRs where branch is deleted, author left org, or base branch changed
-**API calls:** GET /repos/{owner}/{repo}/pulls + branch checks
-**Frequency:** Monthly (1) | Pain: Medium (2) | Feasibility: Medium (2) | Uniqueness: No tool (3) = **8/12**
-
-### 11. label-audit - Label Consistency Check
-**Steps:** List labels across org repos -> find inconsistencies (different colors, missing labels)
-**API calls:** GET /repos/{owner}/{repo}/labels per repo
-**Frequency:** Quarterly (1) | Pain: Low (1) | Feasibility: Easy (3) | Uniqueness: No tool (3) = **8/12**
-
-### 12. burndown - Sprint Burndown (Local DB)
-**Steps:** Query local DB for issues in milestone -> track close rate over time -> render chart data
-**API calls:** Local DB query (issues synced via sync command)
-**Frequency:** Daily during sprint (3) | Pain: High (3) | Feasibility: Hard (1) | Uniqueness: Partial (2) = **9/12**
-
-### 13. dependents - Dependent Repository Discovery
-**Steps:** Find repos that depend on a given repo (used_by graph)
-**API calls:** GitHub dependency graph API or scrape
-**Frequency:** Monthly (1) | Pain: Low (1) | Feasibility: Hard (1) | Uniqueness: Partial (2) = **5/12**
-
-## Validation Results
-
-All required endpoints confirmed present in OpenAPI spec with necessary query parameters:
-- `since` param on issues: YES
-- `state`, `sort`, `direction` on PRs: YES
-- `since`, `until`, `author` on commits: YES
-- Search API with `q` param for cross-repo queries: YES
-- Compare API for changelog generation: YES
-- Code-scanning and dependabot alert endpoints: YES
-
-## Full Scoring Table
-
-| # | Workflow | Freq | Pain | Feas | Uniq | Total | Status |
-|---|---------|------|------|------|------|-------|--------|
-| 1 | pr-triage | 3 | 3 | 3 | 2 | **11** | BUILD |
-| 2 | stale | 2 | 3 | 3 | 3 | **11** | BUILD |
-| 3 | actions-health | 2 | 3 | 3 | 3 | **11** | BUILD |
-| 4 | changelog | 1 | 3 | 3 | 3 | **10** | BUILD |
-| 5 | security | 2 | 3 | 3 | 2 | **10** | BUILD |
-| 6 | activity | 3 | 1 | 3 | 3 | **10** | BUILD |
-| 7 | contributors | 1 | 2 | 2 | 3 | **8** | BUILD |
-| 8 | burndown | 3 | 3 | 1 | 2 | 9 | FUTURE |
-| 9 | review-load | 2 | 2 | 2 | 3 | 9 | FUTURE |
-| 10 | repo-health | 1 | 2 | 3 | 3 | 9 | FUTURE |
-| 11 | orphans | 1 | 2 | 2 | 3 | 8 | FUTURE |
-| 12 | label-audit | 1 | 1 | 3 | 3 | 8 | FUTURE |
-| 13 | dependents | 1 | 1 | 1 | 2 | 5 | SKIP |
-
-## Top 7 for Implementation (Phase 4 Mandatory)
-
-1. **pr-triage** (11/12) - Cross-repo PR triage with age, size, review status grouping
-2. **stale** (11/12) - Find stale issues/PRs across repos with configurable thresholds
-3. **actions-health** (11/12) - CI success rates, flaky test detection, build duration trends
-4. **changelog** (10/12) - Generate release changelogs from commit comparison
-5. **security** (10/12) - Aggregate code-scanning + dependabot alerts across repos by severity
-6. **activity** (10/12) - User/org activity timeline from events API
-7. **contributors** (8/12) - Contributor leaderboard with commit/PR/review scoring
-
-## Acceptance Criteria
-- [x] API archetype classified (Developer Platform)
-- [x] 13 workflow ideas generated
-- [x] All required endpoints validated against OpenAPI spec
-- [x] Each workflow scored on 4 dimensions
-- [x] Top 7 selected for Phase 4 implementation
-
-## Sources
-- GitHub REST API OpenAPI spec (1,107 operations validated)
-- gh-dash (11.2k stars) for PR triage prior art
-- github-to-sqlite (462 stars) for data layer prior art
-- github-actions-watcher for CI monitoring prior art
diff --git a/docs/plans/2026-03-27-feat-github-cli-research.md b/docs/plans/2026-03-27-feat-github-cli-research.md
deleted file mode 100644
index 57b562a3..00000000
--- a/docs/plans/2026-03-27-feat-github-cli-research.md
+++ /dev/null
@@ -1,138 +0,0 @@
----
-title: "Research: GitHub CLI"
-type: feat
-status: active
-date: 2026-03-27
-phase: "1"
-api: "github"
----
-
-# Research: GitHub CLI
-
-## Spec Discovery
-- **Official OpenAPI spec:** https://github.com/github/rest-api-description
-- **Direct URL:** `https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json`
-- **Format:** OpenAPI 3.0.3 (stable, bundled JSON)
-- **Endpoint count:** 740 paths, 1,107 operations
-- **Status:** Maintained by GitHub, auto-generated, does not accept direct PRs
-
-## Competitors (Deep Analysis)
-
-### gh (cli/cli) - 43,400 stars
-- **Repo:** https://github.com/cli/cli
-- **Language:** Go (99.2%)
-- **Commands:** ~80+ subcommands across pr, issue, repo, run, release, codespace, project, etc.
-- **Last commit:** Active (trunk branch, 11,038 commits, 601 contributors)
-- **Open issues:** 917
-- **Maintained:** YES - official GitHub product
-- **Notable features:** Interactive PR creation, issue filing, Actions monitoring, codespace management, `gh api` for raw API access, extension system
-- **Weaknesses:**
-  - No local persistence or offline capabilities
-  - No cross-repo aggregation (can only list PRs/issues per-repo)
-  - No analytics or trend detection
-  - No bulk data export
-  - Users request cross-repo PR listing (#5317), PR update command (#3370), complete comment display (#5788)
-
-### gh-dash (dlvhdr/gh-dash) - 11,200 stars
-- **Repo:** https://github.com/dlvhdr/gh-dash
-- **Language:** Go (100%)
-- **Commands:** TUI application (not a CLI with subcommands)
-- **Last commit:** Active (582 commits)
-- **Open issues:** 69
-- **Maintained:** YES
-- **Notable features:** Vim-style keybindings, YAML config, per-repo PR/issue sections, custom actions
-- **Weaknesses:**
-  - TUI-only (no --json output, not agent-native)
-  - No local persistence
-  - No offline search
-  - No analytics
-  - Users request: create PRs from dash (#689), repo-specific view (#179), keybinding customization (#214)
-
-### github-to-sqlite (dogsheep/github-to-sqlite) - 462 stars
-- **Repo:** https://github.com/dogsheep/github-to-sqlite
-- **Language:** Python
-- **Commands:** 12+ commands (issues, pull-requests, commits, releases, starred, repos, etc.)
-- **Last commit:** December 2023 (v2.9)
-- **Open issues:** 20
-- **Maintained:** Slow/dormant (last release Dec 2023)
-- **Notable features:** SQLite persistence, Datasette integration, covers most GitHub entities, `get` command for raw API with pagination
-- **Weaknesses:**
-  - Python dependency (not a standalone binary)
-  - Requires Datasette for querying (not self-contained)
-  - No FTS5 search built-in
-  - No workflow/analytics commands
-  - No incremental sync optimization
-  - Dormant development
-
-## User Pain Points
-
-> "gh should have a way to list all open issues for the repositories in my account / organization" - [cli/cli#5317](https://github.com/cli/cli/issues/5317)
-
-> Users want "a feature request for showing issues and PRs specific to the repo from which the dash command is run" - [gh-dash#179](https://github.com/dlvhdr/gh-dash/issues/179)
-
-> "While downloading a PR locally via gh pr checkout is simple, there is no command for pushing changes back, particularly for PRs from forks" - [cli/cli#3370](https://github.com/cli/cli/issues/3370)
-
-> "gh pr view --comments should show all comments" including inline review comments - [cli/cli#5788](https://github.com/cli/cli/issues/5788)
-
-> Rate limiting is a major pain: "ETags are per-page, not per collection. If you get a 304 on page 1 of 5, that doesn't mean pages 2-5 are unchanged" - [GitHub Community Discussion](https://github.com/orgs/community/discussions/163553)
-
-## Auth Method
-- **Type:** Personal access token (bearer token via Authorization header)
-- **Env var convention:** `GITHUB_TOKEN` (used by gh, github-to-sqlite, and most tools)
-- **Rate limit:** 5,000 requests/hour (authenticated), 60/hour (unauthenticated)
-- **Conditional requests:** ETag/If-Modified-Since headers - 304 responses don't count against limit
-
-## Demand Signals
-- gh-dash at 11.2k stars proves massive demand for PR/issue dashboards
-- github-to-sqlite at 462 stars proves demand for local GitHub data in SQLite
-- Multiple HN Show HN posts about GitHub analytics/monitoring tools
-- 917 open issues on gh CLI = active user demand for features gh doesn't have
-- "Ask HN: What's the one feature you'd want in a GitHub productivity tool?" - [HN thread](https://news.ycombinator.com/item?id=42287526)
-
-## Strategic Justification
-
-**Why this CLI should exist when gh has 43,400 stars:**
-
-gh is a workflow tool - it helps you create PRs, file issues, and run Actions. It has zero local persistence, zero offline capability, and zero analytics. It is NOT a data tool.
-
-github-to-sqlite is a data tool but it's Python-dependent, requires Datasette for queries, has been dormant since Dec 2023, has no FTS5 search, no workflow commands, and no agent-native output modes.
-
-gh-dash is a beautiful TUI but has no CLI interface (no --json), no local persistence, and no analytics.
-
-**Our CLI fills the gap between all three:**
-1. **Go binary** (no Python/Node dependency) like gh
-2. **SQLite persistence** with domain-specific tables like github-to-sqlite
-3. **FTS5 search** that github-to-sqlite doesn't have
-4. **Agent-native** (--json, --select, --dry-run) that gh-dash doesn't have
-5. **Workflow commands** (pr-triage, stale, actions-health, changelog, security) that none of them have
-6. **Incremental sync** with rate-limit-aware cursors
-7. **Raw SQL access** without needing Datasette
-
-This is the "discrawl for GitHub" - a standalone Go binary that syncs GitHub data to SQLite and provides compound workflow commands on top.
-
-## Target
-- **Command count:** 40-50 (not 1,107 - depth beats breadth)
-  - ~15-20 core API commands (repos, issues, PRs, commits, releases, actions)
-  - 6 data layer commands (sync, search, sql, issues, prs, tail)
-  - 7 workflow commands (pr-triage, stale, actions-health, changelog, security, activity, contributors)
-  - 5-8 utility commands (doctor, auth, config, version, completion)
-- **Key differentiator:** Local SQLite + FTS5 + compound workflow commands
-- **Quality bar:** Grade A (80+/100)
-
-## Acceptance Criteria
-- [x] Research artifact with Spec Discovery section
-- [x] 3 competitors analyzed with maintenance status
-- [x] 5+ user quotes/pain points documented
-- [x] Strategic justification answers "why should this exist?"
-- [x] Target command count set (40-50)
-
-## Sources
-- https://github.com/github/rest-api-description - OpenAPI spec
-- https://github.com/cli/cli - gh CLI (43.4k stars)
-- https://github.com/dlvhdr/gh-dash - TUI dashboard (11.2k stars)
-- https://github.com/dogsheep/github-to-sqlite - SQLite sync (462 stars)
-- https://github.com/cli/cli/issues/5317 - Cross-repo listing request
-- https://github.com/cli/cli/issues/3370 - PR update request
-- https://github.com/cli/cli/issues/5788 - PR comments request
-- https://github.com/orgs/community/discussions/163553 - Rate limit pain
-- https://news.ycombinator.com/item?id=42287526 - HN productivity tool ask
diff --git a/docs/plans/2026-03-27-feat-github-cli-visionary-research.md b/docs/plans/2026-03-27-feat-github-cli-visionary-research.md
deleted file mode 100644
index d93311e0..00000000
--- a/docs/plans/2026-03-27-feat-github-cli-visionary-research.md
+++ /dev/null
@@ -1,200 +0,0 @@
----
-title: "Visionary Research: GitHub CLI"
-type: feat
-status: active
-date: 2026-03-27
-phase: "0"
-api: "github"
----
-
-# Visionary Research: GitHub CLI
-
-## Overview
-
-GitHub's REST API is one of the largest public APIs in existence - 740 paths, 1,107 operations across 25+ resource categories. The official `gh` CLI (43.4k stars, 601 contributors) dominates the space but is fundamentally a workflow tool, not a data tool. It excels at interactive developer workflows (PR creation, issue filing, Actions monitoring) but has no local persistence, no offline search, no analytics, and no bulk data operations. This creates a massive gap for the "GitHub power user" archetype - engineering managers, open source maintainers, DevOps engineers, and AI agents who need to query, analyze, and monitor GitHub data programmatically.
-
-The strategic opportunity is not to replace `gh` but to complement it - building the "github-to-sqlite on steroids" that combines the data layer approach of Dogsheep's github-to-sqlite (462 stars, 15 tables, SQLite + Datasette) with the agent-native output modes of a modern CLI (--json, --select, --dry-run, --stdin), plus compound workflow commands that solve real recurring problems.
-
-## API Identity
-
-- **Domain:** Developer Platform (repos, issues, PRs, actions, security, packages)
-- **Primary users:** Engineering managers, open source maintainers, DevOps/SRE, CI/CD automation, AI coding agents
-- **Core entities:** Repositories, Issues, Pull Requests, Commits, Actions (Workflows/Runs/Jobs), Users, Organizations, Teams, Releases, Code Scanning Alerts, Dependabot Alerts
-- **Data profile:**
-  - Write pattern: Mutable (issues, PRs get updated) + append-only (commits, events, audit logs)
-  - Volume: HIGH - large orgs have millions of issues, PRs, commits, events
-  - Real-time: Webhooks (not WebSocket/SSE) - REST polling for tail
-  - Search need: HIGH - finding issues, PRs, commits across repos is a core use case
-
-## API Spec Discovery
-
-- **Official OpenAPI spec:** `github/rest-api-description` repository
-- **URL:** `https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json`
-- **Version:** OpenAPI 3.0.3 (stable, bundled)
-- **Scale:** 740 paths, 1,107 operations
-- **Top resource categories by endpoint count:**
-  - repos: 201, actions: 184, orgs: 108, issues: 55, codespaces: 48
-  - users: 47, apps: 37, activity: 32, teams: 32, packages: 27, pulls: 27
-
-## Usage Patterns (Top 5 by Evidence)
-
-### 1. PR Triage & Review Management (Evidence: 9/10)
-Users need to see all open PRs across repos, prioritize by staleness/size/author, and batch-process reviews. gh-dash (11.2k stars) exists specifically for this. Multiple gh CLI issues request cross-repo PR listing (#5317, #642).
-- **Sources:** gh-dash 11.2k stars, gh CLI issues, Reddit/HN discussions, gh-pr-dashboard HN post
-
-### 2. Issue & PR Export/Archive to Local Storage (Evidence: 8/10)
-Users want to backup, export, or archive issues/PRs as structured data (SQLite, CSV, Markdown). github-to-sqlite (462 stars), gh2md, export-pull-requests, python-github-backup all serve this need.
-- **Sources:** github-to-sqlite 462 stars, gh2md, python-github-backup, multiple backup tools
-
-### 3. CI/CD Monitoring & Actions Analytics (Evidence: 7/10)
-Engineering teams need to monitor workflow runs, identify flaky tests, track build times, and alert on failures. github-actions-watcher by Spatie exists. Multiple burndown/velocity tools exist.
-- **Sources:** github-actions-watcher, BuildBeacon HN post, burndown-for-github-projects
-
-### 4. Stale Issue/PR Detection & Hygiene (Evidence: 7/10)
-Maintainers need to find stale issues (no updates in N days), orphaned PRs, issues without labels, and PRs without reviewers. This is a recurring manual task that existing tools partially address.
-- **Sources:** GitHub community discussions, burndown tools, issue triage workflows
-
-### 5. Cross-Repo Search & Analytics (Evidence: 6/10)
-Users want to search issues, PRs, and code across all their repos or an org's repos from the terminal. The gh CLI search is limited. github-to-sqlite + Datasette provides this via SQL.
-- **Sources:** github-to-sqlite + Datasette demo, gh-search-cli, Stack Overflow questions
-
-## Tool Landscape (Beyond API Wrappers)
-
-| Tool | Stars | Type | What It Does |
-|------|-------|------|-------------|
-| **gh** (cli/cli) | 43,400 | Workflow Tool | Official CLI - PRs, issues, actions, interactive workflows |
-| **gh-dash** | 11,200 | Data Tool (TUI) | Terminal dashboard for PRs and issues with vim keybindings |
-| **github-to-sqlite** | 462 | Data Tool | Sync GitHub data to SQLite for Datasette/SQL queries |
-| **gh2md** | ~200 | Data Tool | Export issues/PRs to Markdown files |
-| **python-github-backup** | ~800 | Data Tool | Full repo backup including issues, PRs, comments, wikis |
-| **export-pull-requests** | ~300 | Data Tool | Export PRs/issues to CSV (GitHub, GitLab, Bitbucket) |
-| **ghexport** | ~100 | Data Tool | Export personal GitHub activity data |
-| **github-actions-watcher** | ~300 | Monitoring Tool | Real-time Actions workflow status in terminal |
-| **burndown-for-github-projects** | ~200 | Analytics Tool | Sprint burndown charts from GitHub Projects |
-
-**Key insight:** The Data Tool category is fragmented - github-to-sqlite does sync+SQL but is Python/Datasette-dependent, not a standalone CLI. No single Go CLI combines sync + search + analytics + workflows.
-
-## Workflows
-
-### 1. PR Triage Report
-**Steps:** List all open PRs across N repos -> sort by age/size/review status -> group by reviewer -> output as table or JSON
-**Frequency:** Daily for engineering managers
-**Pain:** gh CLI can only list PRs per-repo, no cross-repo view
-**Proposed:** `github-cli pr-triage --org myorg --stale 7 --json`
-
-### 2. Stale Issue Sweep
-**Steps:** Fetch issues across repos -> filter by last-updated -> exclude labeled issues -> optionally comment/close
-**Frequency:** Weekly for maintainers
-**Pain:** Manual process, no single command
-**Proposed:** `github-cli stale --org myorg --days 30 --label needs-triage`
-
-### 3. Actions Health Report
-**Steps:** Fetch recent workflow runs -> compute success rate per workflow -> identify flaky tests -> track build duration trends
-**Frequency:** Weekly for DevOps
-**Pain:** GitHub Actions UI is per-repo, no aggregate view
-**Proposed:** `github-cli actions-health --repo myorg/myrepo --days 14`
-
-### 4. Release Changelog Generation
-**Steps:** Get commits since last release -> group by type (feat/fix/refactor) -> include PR links -> format as Markdown
-**Frequency:** Per release
-**Pain:** Manual commit log parsing
-**Proposed:** `github-cli changelog --since v1.2.0`
-
-### 5. Contributor Leaderboard
-**Steps:** Fetch commits + PRs + reviews per contributor -> score by activity -> rank -> output table
-**Frequency:** Monthly for team leads
-**Pain:** No single view of contributor activity
-**Proposed:** `github-cli contributors --org myorg --days 30 --sort commits`
-
-## Architecture Decisions
-
-| Area | Decision | Rationale |
-|------|----------|-----------|
-| **Persistence** | SQLite with domain-specific tables | HIGH volume + HIGH search need. Issues, PRs, commits are primary entities. github-to-sqlite proves the model works (462 stars). |
-| **Real-time** | REST polling with `since` cursor | GitHub has no WebSocket/SSE for REST API. Webhooks require a server. REST polling with `?since=` or `?updated_after=` is the only CLI-friendly option. |
-| **Search** | FTS5 on issue/PR titles, bodies, comments | Users need to find issues by keyword across repos. FTS5 enables instant offline search. |
-| **Bulk** | Paginated sync with `per_page=100` + conditional requests (ETag/If-Modified-Since) | GitHub rate limit is 5,000/hour. Conditional requests don't count. Sync must be rate-limit-aware. |
-| **Cache** | SQLite IS the cache - no separate cache layer | The local database serves as both persistent storage and cache. `--no-cache` flag bypasses local DB and hits API directly. |
-
-## Top 5 Features for the World
-
-### 1. Offline Cross-Repo Search (Score: 14/16)
-| Dimension | Score | Justification |
-|-----------|-------|---------------|
-| Evidence | 3 | github-to-sqlite (462 stars) + Datasette proves demand |
-| User impact | 3 | Every maintainer searches across repos daily |
-| Feasibility | 2 | SQLite + FTS5, proven pattern |
-| Uniqueness | 2 | No Go CLI does this - github-to-sqlite is Python + Datasette |
-| Composability | 2 | `github-cli search "bug" --repo org/* --json | jq` |
-| Data profile fit | 2 | Perfect - high volume text data |
-| Maintainability | 0 | Needs sync maintenance |
-| Competitive moat | 0 | Concept proven, execution differentiates |
-
-### 2. PR Triage Dashboard (Score: 13/16)
-| Dimension | Score | Justification |
-|-----------|-------|---------------|
-| Evidence | 3 | gh-dash (11.2k stars), multiple gh CLI feature requests |
-| User impact | 3 | Engineering managers check daily |
-| Feasibility | 2 | Query local DB or live API |
-| Uniqueness | 1 | gh-dash exists but is TUI-only, not agent-native |
-| Composability | 2 | JSON output for agent consumption |
-| Data profile fit | 2 | Cross-repo PR data in SQLite |
-| Maintainability | 0 | Relies on API stability |
-| Competitive moat | 0 | gh-dash is strong competition |
-
-### 3. Actions Health Analytics (Score: 12/16)
-| Dimension | Score | Justification |
-|-----------|-------|---------------|
-| Evidence | 2 | github-actions-watcher (300 stars), BuildBeacon |
-| User impact | 3 | DevOps teams need this weekly |
-| Feasibility | 2 | Workflow runs API is well-documented |
-| Uniqueness | 2 | No CLI does aggregate actions analytics |
-| Composability | 2 | `github-cli actions-health --json` for monitoring |
-| Data profile fit | 1 | Workflow runs are append-only, moderate volume |
-| Maintainability | 0 | Actions API evolves |
-| Competitive moat | 0 | Straightforward to build |
-
-### 4. Stale Issue/PR Sweep (Score: 11/16)
-| Dimension | Score | Justification |
-|-----------|-------|---------------|
-| Evidence | 2 | Multiple triage tools, community discussions |
-| User impact | 2 | Maintainers do this weekly |
-| Feasibility | 2 | Simple date filtering |
-| Uniqueness | 2 | No CLI combines detection + optional action |
-| Composability | 2 | Pipe to close/label commands |
-| Data profile fit | 1 | Queries local DB for speed |
-| Maintainability | 0 | Stable pattern |
-| Competitive moat | 0 | Simple concept |
-
-### 5. Raw SQL Access to GitHub Data (Score: 11/16)
-| Dimension | Score | Justification |
-|-----------|-------|---------------|
-| Evidence | 3 | github-to-sqlite + Datasette (462 stars) proves the model |
-| User impact | 2 | Power users and data analysts |
-| Feasibility | 2 | SQLite is the persistence layer already |
-| Uniqueness | 1 | github-to-sqlite does this via Datasette |
-| Composability | 2 | SQL is the ultimate composability |
-| Data profile fit | 1 | Perfect for structured queries |
-| Maintainability | 0 | Schema must match API |
-| Competitive moat | 0 | Datasette is powerful competition |
-
-## Acceptance Criteria
-- [x] API Identity documented with data profile
-- [x] 5 usage patterns with evidence scores >= 6
-- [x] Tool landscape includes non-wrapper tools (gh-dash, github-to-sqlite, gh2md, etc.)
-- [x] 5 workflows with proposed CLI features
-- [x] Architecture decisions match data profile (SQLite + FTS5 for high-volume search)
-- [x] Top 5 features scored and ranked
-
-## Sources
-- https://github.com/github/rest-api-description - Official OpenAPI spec (740 paths, 1107 operations)
-- https://github.com/cli/cli - Official gh CLI (43.4k stars)
-- https://github.com/dlvhdr/gh-dash - Terminal dashboard (11.2k stars)
-- https://github.com/dogsheep/github-to-sqlite - SQLite sync tool (462 stars)
-- https://github.com/mattduck/gh2md - Markdown export
-- https://github.com/josegonzalez/python-github-backup - Full backup tool
-- https://github.com/sshaw/export-pull-requests - CSV export
-- https://github.com/spatie/github-actions-watcher - Actions monitor
-- https://github.com/cli/cli/issues/5317 - Cross-repo issue listing request
-- https://github.com/cli/cli/issues/3370 - PR update command request
-- https://docs.github.com/en/rest - Official REST API docs
diff --git a/docs/plans/2026-03-27-feat-printing-press-repress-mode-plan.md b/docs/plans/2026-03-27-feat-printing-press-repress-mode-plan.md
index d523f757..68536614 100644
--- a/docs/plans/2026-03-27-feat-printing-press-repress-mode-plan.md
+++ b/docs/plans/2026-03-27-feat-printing-press-repress-mode-plan.md
@@ -1,15 +1,15 @@
 ---
-title: "Repress Mode: Second-Pass Improvement for Generated CLIs"
+title: "Emboss Mode: Second-Pass Improvement for Generated CLIs"
 type: feat
 status: active
 date: 2026-03-27
 ---
 
-# Repress Mode: Second-Pass Improvement for Generated CLIs
+# Emboss Mode: Second-Pass Improvement for Generated CLIs
 
 ## Overview
 
-After the printing press generates a CLI (Phase 0-5, ~1 hour), you have a working tool. But it was built from spec analysis and research - not from using it. The `repress` command takes an already-generated CLI and runs a fresh improvement cycle: re-research the competitive landscape (things change), run verify to find what's broken, compare against the original scorecard, identify the top 5 improvements, build them, and re-verify.
+After the printing press generates a CLI (Phase 0-5, ~1 hour), you have a working tool. But it was built from spec analysis and research - not from using it. The `emboss` command takes an already-generated CLI and runs a fresh improvement cycle: re-research the competitive landscape (things change), run verify to find what's broken, compare against the original scorecard, identify the top 5 improvements, build them, and re-verify.
 
 It's the printing press equivalent of compound-engineering's `plan -> deepen-plan -> work` loop. First pass builds it. Second pass makes it good.
 
@@ -18,7 +18,7 @@ It's the printing press equivalent of compound-engineering's `plan -> deepen-pla
 /printing-press Discord
 
 # Second pass: improve what exists
-/printing-press repress ./discord-cli --spec /tmp/discord-spec.json
+/printing-press emboss ./discord-cli --spec /tmp/discord-spec.json
 ```
 
 ## Problem Statement
@@ -31,11 +31,11 @@ The first press run produces a CLI that scores 65-85/100. The verify command cat
 4. **Execute the improvements** - apply fixes, add missing workflow commands, improve the data layer
 5. **Re-verify** - prove the improvements actually worked
 
-Currently you'd do this manually: read the old artifacts, re-run some searches, eyeball the code, make changes, run verify. Repress automates the cycle.
+Currently you'd do this manually: read the old artifacts, re-run some searches, eyeball the code, make changes, run verify. Emboss automates the cycle.
 
 ## Proposed Solution
 
-### The Repress Cycle
+### The Emboss Cycle
 
 ```
 Input: existing CLI directory + original spec
@@ -113,7 +113,7 @@ Compare against the baseline from Step 1.
 ### Step 6: REPORT (The delta)
 
 ```
-REPRESS REPORT: discord-cli
+EMBOSS REPORT: discord-cli
 ==============================
            Before    After     Delta
 Scorecard: 73/100    82/100    +9
@@ -140,9 +140,9 @@ Remaining Gaps:
 Add to the printing press skill as an optional mode:
 
 ```
-/printing-press repress ./discord-cli           # Standard repress
-/printing-press repress ./discord-cli codex     # Codex-delegated improvements
-/printing-press repress ./discord-cli --spec /tmp/spec.json  # With spec for verify
+/printing-press emboss ./discord-cli           # Standard emboss
+/printing-press emboss ./discord-cli codex     # Codex-delegated improvements
+/printing-press emboss ./discord-cli --spec /tmp/spec.json  # With spec for verify
 ```
 
 ### As a Go Binary Command
@@ -150,7 +150,7 @@ Add to the printing press skill as an optional mode:
 Also add to the `printing-press` binary for the mechanical parts:
 
 ```bash
-printing-press repress --dir ./discord-cli --spec /tmp/spec.json [--fix] [--api-key TOKEN]
+printing-press emboss --dir ./discord-cli --spec /tmp/spec.json [--fix] [--api-key TOKEN]
 ```
 
 The binary handles: audit (verify + scorecard), re-verify, delta report.
@@ -163,9 +163,9 @@ The skill handles: re-research (web searches), gap analysis (reasoning), improve
 Add after the Anti-Shortcut Rules section:
 
 ```markdown
-## Repress Mode (Second Pass)
+## Emboss Mode (Second Pass)
 
-When the user runs `/printing-press repress <dir>`:
+When the user runs `/printing-press emboss <dir>`:
 
 1. This is NOT a from-scratch run. The CLI already exists.
 2. Read the existing CLI directory. Run verify + scorecard to get a baseline.
@@ -175,12 +175,12 @@ When the user runs `/printing-press repress <dir>`:
 6. Build each improvement atomically. Commit each.
 7. Re-verify. Report the delta.
 
-The repress should take ~30 minutes, not ~1 hour. It's surgical, not generative.
+The emboss should take ~30 minutes, not ~1 hour. It's surgical, not generative.
 ```
 
 ### In the Go Binary
 
-Add `printing-press repress` command that wraps:
+Add `printing-press emboss` command that wraps:
 1. `verify` (baseline)
 2. `scorecard` (baseline)
 3. User does improvements (skill-driven)
@@ -188,7 +188,7 @@ Add `printing-press repress` command that wraps:
 5. `scorecard` (after)
 6. Delta report
 
-The delta report is the new artifact. It goes in `docs/plans/<today>-repress-<api>-cli-delta.md`.
+The delta report is the new artifact. It goes in `docs/plans/<today>-emboss-<api>-cli-delta.md`.
 
 ## The Analogy
 
@@ -198,13 +198,13 @@ The delta report is the new artifact. It goes in `docs/plans/<today>-repress-<ap
 | `/deepen-plan` | Phase 0-1 research enrichment |
 | `/ce:work` | Phase 2-4 generation + build |
 | `/ce:review` | Phase 5 scorecard + verify |
-| **No equivalent** | **`/printing-press repress` (second pass)** |
+| **No equivalent** | **`/printing-press emboss` (second pass)** |
 
-Repress fills the gap. It's what you do after the first run when you look at it and say "this is good but not great." It's the deepen-plan + work cycle, applied to an already-generated CLI.
+Emboss fills the gap. It's what you do after the first run when you look at it and say "this is good but not great." It's the deepen-plan + work cycle, applied to an already-generated CLI.
 
 ## Acceptance Criteria
 
-- [ ] `/printing-press repress <dir>` runs the 6-step cycle
+- [ ] `/printing-press emboss <dir>` runs the 6-step cycle
 - [ ] Step 1 produces a baseline (verify pass rate + scorecard score)
 - [ ] Step 2 searches for new competitors/pain points (not full Phase 0)
 - [ ] Step 3 identifies and ranks top 5 improvements
@@ -213,11 +213,11 @@ Repress fills the gap. It's what you do after the first run when you look at it
 - [ ] Step 6 produces a delta report in docs/plans/
 - [ ] Total time: ~30-40 minutes (not another full hour)
 - [ ] Codex delegation works for Step 4 improvements
-- [ ] The cycle is repeatable - you can repress multiple times
+- [ ] The cycle is repeatable - you can emboss multiple times
 
 ## The Name
 
-**`repress`** - run it through the press again. A printing press pun that communicates exactly what it does.
+**`emboss`** - run it through the press again. A printing press pun that communicates exactly what it does.
 
 Alternatives considered: refine, polish, hone, temper, sharpen, elevate, reforge, proof, second-edition. All fine words but none have the press connection.
 
diff --git a/docs/plans/2026-03-27-fix-github-cli-audit.md b/docs/plans/2026-03-27-fix-github-cli-audit.md
deleted file mode 100644
index 4222155b..00000000
--- a/docs/plans/2026-03-27-fix-github-cli-audit.md
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: "Non-Obvious Insight Review: GitHub CLI"
-type: fix
-status: active
-date: 2026-03-27
-phase: "3"
-api: "github"
----
-
-# Non-Obvious Insight Review: GitHub CLI
-
-## Automated Scorecard Baseline
-
-| Dimension | Score | Notes |
-|-----------|-------|-------|
-| Output Modes | 10/10 | --json, --csv, --plain, --quiet, --select, --compact |
-| Auth | 8/10 | GITHUB_TOKEN env var, config file, doctor validates |
-| Error Handling | 10/10 | Typed exits, retry patterns |
-| Terminal UX | 9/10 | tabwriter, color support, --no-color |
-| README | 7/10 | Missing cookbook, FAQ, workflow examples |
-| Doctor | 10/10 | Auth validation, API connectivity check |
-| Agent Native | 8/10 | --json, --select, --dry-run, --yes, --no-input |
-| Local Cache | 10/10 | SQLite store, --no-cache bypass |
-| Breadth | 10/10 | 117 commands from 51 paths |
-| Vision | 9/10 | sync, tail, search, analytics, export, import |
-| Workflows | 4/10 | Generic workflow/analytics stubs, not domain-specific |
-| Insight | 0/10 | No insight commands (health, trends, stale, etc.) |
-| Path Validity | 5/10 | Needs domain validation |
-| Auth Protocol | 5/10 | Needs domain validation |
-| Data Pipeline | 10/10 | Store + sync + search connected |
-| Sync Correctness | 8/10 | Generic sync, not domain-aware |
-| Type Fidelity | 1/5 | Reserved word types fixed, but schema is generic |
-| Dead Code | 0/5 | Likely dead flags and functions |
-
-**Baseline Total: 68/100 (Grade B)**
-
-## GOAT Improvement Plan
-
-### Priority 0: Data Layer Foundation (from Phase 0.7) [+15-20 points expected]
-1. **Replace store.go** - Current store uses generic `resources` table. Replace with domain-specific tables from Phase 0.7 (issues, pull_requests, commits, comments, repos, users, workflow_runs, events, reviews)
-2. **Rewrite sync.go** - Use `since` cursor for issues/commits, `created` for workflow_runs. Add --repo and --org scoping flags.
-3. **Add domain search** - FTS5 on issue titles, PR titles, commit messages, comment bodies
-4. **Add sql command** - Raw read-only SQL against local DB
-5. **Add domain list commands** - issues, prs, commits queries against local DB with filters
-
-### Priority 1: Workflow Commands (from Phase 0.5) [+10-15 points expected]
-Build the 7 workflow commands: pr-triage, stale, actions-health, changelog, security, activity, contributors
-
-### Priority 2: Scorecard Fixes [+5-10 points expected]
-- Fix README with cookbook section showcasing workflows
-- Fix placeholder examples ("example-value" -> realistic values)
-- Remove dead flags and functions
-- Wire all flags to actual command logic
-
-### Priority 3: Polish
-- README FAQ section
-- Domain-specific help text improvements
-
-## Target: 85+/100 (Grade A)
diff --git a/docs/plans/2026-03-27-fix-printing-press-post-mortem-notion-run-plan.md b/docs/plans/2026-03-27-fix-printing-press-post-mortem-notion-run-plan.md
deleted file mode 100644
index 309b3c29..00000000
--- a/docs/plans/2026-03-27-fix-printing-press-post-mortem-notion-run-plan.md
+++ /dev/null
@@ -1,286 +0,0 @@
----
-title: "Post-Mortem: Printing Press Notion Run - Process Gaps"
-type: fix
-status: active
-date: 2026-03-27
-phase: "retrospective"
-api: "printing-press (meta)"
----
-
-# Post-Mortem: What the Printing Press Got Wrong on the Notion Run
-
-## Overview
-
-The Notion run produced a 74/100 Grade B CLI with genuinely unique architecture (SQLite + FTS5 + domain tables + 12 insight commands). But it shipped with ugly command names, wrong binary name, stale module path, untested sync pipeline, and no distribution. All of these were knowable and fixable during the run. The skill's process failed to catch them.
-
-This plan identifies the root cause for each gap and proposes concrete changes to the printing-press SKILL.md to prevent recurrence.
-
-## Gap Analysis: What Went Wrong and Why
-
-### 1. Command Names: `retrieve-a`, `delete-a` instead of `get`, `delete`
-
-**What happened:** The generator produced operation IDs from the OpenAPI spec verbatim. Notion's spec uses `operationId: "Retrieve a block"` which becomes `retrieve-a` in kebab-case.
-
-**Root cause:** The skill has no **Phase 2.5: Command Name Normalization** step. Phase 0.8 chose the product name (`noto`) but nothing maps ugly generated names to clean ones.
-
-**Fix for SKILL.md:**
-
-Add after Phase 2 Step 2.3:
-
-```markdown
-### Step 2.5: Command Name Normalization
-
-After generation, normalize command names to match user expectations:
-
-1. Run: `grep -r 'Use:' <api>-cli/internal/cli/*.go | grep -v root.go`
-2. For each command, apply these rules:
-   - `retrieve-a*` -> `get` (Notion, Stripe, etc. use "Retrieve a X")
-   - `delete-a*` -> `delete`
-   - `create-a*` -> `create`
-   - `update-a*` -> `update`
-   - `post` -> `create` (when the endpoint creates a resource)
-   - `patch` -> `update` (when the endpoint updates a resource)
-   - `get-self` -> `me` (for /users/me style endpoints)
-   - `get-userid` -> `get` (redundant ID suffix)
-   - `list-*` -> `list` (strip resource name suffix)
-3. Rename both the `Use:` field and the filename
-4. Update root.go registrations
-5. Verify `go build` still passes
-
-**This step is mandatory.** Generated names are for machines.
-User-facing names are for humans.
-```
-
----
-
-### 2. Binary Name and Help Text Still Say `notion-cli`
-
-**What happened:** Phase 0.8 chose the name `noto`. But the generator created `cmd/notion-cli/`, `root.go` says `Use: "notion-cli"`, README examples say `notion-cli`. The name was never applied.
-
-**Root cause:** The skill chooses a name in Phase 0.8 but has no **Phase 2.6: Apply Product Name** step. The name lives in a plan document but never touches the code.
-
-**Fix for SKILL.md:**
-
-Add after Step 2.5:
-
-```markdown
-### Step 2.6: Apply Product Name
-
-The name chosen in Phase 0.8 MUST be applied to the generated CLI:
-
-1. Rename `cmd/<api>-cli/` to `cmd/<chosen-name>/`
-2. Update `root.go`:
-   - `Use:` field -> chosen name
-   - `SetVersionTemplate` -> chosen name
-   - All help text references
-3. Update `README.md`: replace all occurrences of the generated name
-4. Update `go.mod` module path if needed (e.g., `github.com/<org>/<chosen-name>`)
-5. Update `internal/client/client.go`:
-   - User-Agent header
-   - Default config directory name
-6. Verify: `grep -r "notion-cli" .` should return 0 hits (except in
-   "Generated by" comments)
-
-**Gate:** If `grep -r "<old-name>" . | grep -v "Generated by"` returns
-any hits, the rename is incomplete. Fix before proceeding.
-```
-
----
-
-### 3. Module Path: `github.com/matt-van-horn/notion-cli` vs `github.com/mvanhorn/noto`
-
-**What happened:** The generator derived the module path from git config (`user.name = matt-van-horn`). This produced a hyphenated GitHub username that doesn't match reality (`mvanhorn`), and used the API name instead of the product name.
-
-**Root cause:** The generator auto-derives the module path but the skill doesn't validate it. There's no step that asks: "Is this the right GitHub org/repo name?"
-
-**Fix for SKILL.md:**
-
-Add to Phase 2 prerequisites:
-
-```markdown
-### Step 2.0b: Validate Module Path
-
-Before running the generator, check the module path it will use:
-
-1. Run: `git config user.name` -> this becomes the org in the module path
-2. If this doesn't match your actual GitHub username, set it:
-   ```bash
-   export PRINTING_PRESS_ORG="<correct-github-username>"
-   ```
-3. The module path should be: `github.com/<org>/<chosen-name-from-phase-0.8>`
-4. After generation, verify: `head -1 <api>-cli/go.mod`
-5. If wrong, fix with:
-   ```bash
-   cd <api>-cli
-   go mod edit -module github.com/<org>/<name>
-   find . -name '*.go' -exec sed -i '' "s|<old-module>|<new-module>|g" {} +
-   go build ./...
-   ```
-```
-
----
-
-### 4. API Version Header Pinned to 2022-06-28 (Spec Was 2026)
-
-**What happened:** The generator's client template hardcodes `Notion-Version: 2022-06-28`. The spec we downloaded was from March 2026. Nobody checked.
-
-**Root cause:** The skill doesn't have a **Phase 2.7: Validate API Version** step. The generator uses a static template for the client, not the spec's version info.
-
-**Fix for SKILL.md:**
-
-Add after Phase 2:
-
-```markdown
-### Step 2.7: Validate API Version Header
-
-The generated client may pin to an outdated API version. Fix it:
-
-1. Check the spec for the API version:
-   ```bash
-   python3 -c "import json; d=json.load(open('/tmp/<spec>')); print(d.get('info',{}).get('version','?'))"
-   ```
-2. Check the generated client's version header:
-   ```bash
-   grep -n "Version" <api>-cli/internal/client/client.go
-   ```
-3. If the API docs specify a date-based version header (like Notion, Stripe),
-   use the LATEST documented version, not the spec's version field.
-4. Verify by searching the API's changelog for the latest version string.
-5. Update the header in client.go.
-6. **Test with the live API** (if token available) to confirm the version
-   header is accepted.
-
-**Anti-shortcut:** "The generator's default is fine" - NO. Check it.
-```
-
----
-
-### 5. Sync Pipeline Was Never Tested With Real Data
-
-**What happened:** The live test synced 17 users but 0 pages. The integration token didn't have page access (Notion requires explicit sharing). The report said "PASS" anyway.
-
-**Root cause:** Phase 5.5's safety rules correctly limited to read-only GETs, but the verdict logic treated "0 pages synced" as success. The skill needs a **data pipeline smoke test** that flags empty results as WARN, not PASS.
-
-**Fix for SKILL.md:**
-
-Update Phase 5.5 verdict logic:
-
-```markdown
-### Step 5.5g: Data Pipeline Smoke Test
-
-After sync, verify the pipeline actually moved data:
-
-1. Check entity counts:
-   ```bash
-   <cli> sql "SELECT 'pages' as t, COUNT(*) as n FROM pages
-              UNION ALL SELECT 'blocks', COUNT(*) FROM blocks
-              UNION ALL SELECT 'users', COUNT(*) FROM users"
-   ```
-2. **If ANY primary entity (from Phase 0.7) has 0 rows:**
-   - Verdict: **WARN** (not PASS)
-   - Report: "Sync completed but [entity] has 0 rows. Possible causes:
-     integration permissions, empty workspace, or sync bug."
-   - Suggest: "Share at least one page with the integration and re-run sync."
-3. **If primary entities have rows, test the read path:**
-   - `<cli> search "a" --limit 1` should return results
-   - `<cli> stale --days 9999` should return all pages
-   - `<cli> health` should show non-zero totals
-4. **If search returns 0 results but pages exist:**
-   - FTS5 indexing is broken. This is a FAIL, not a WARN.
-
-**The sync pipeline isn't tested until data flows through it.**
-"17 users synced, 0 pages" is not a passing grade.
-```
-
----
-
-### 6. No Distribution (Homebrew, goreleaser, Docker)
-
-**What happened:** The skill doesn't address distribution at all. It stops at "go build passes."
-
-**Root cause:** The skill's definition of "ship ready" is "compiles and scores well." It doesn't include "users can install it."
-
-**Fix for SKILL.md:**
-
-Add Phase 5.8: Distribution Scaffold:
-
-```markdown
-### Phase 5.8: Distribution Scaffold (Optional but Recommended)
-
-If the CLI scores Grade B or higher, add distribution scaffolding:
-
-1. **goreleaser config** (.goreleaser.yaml):
-   ```yaml
-   builds:
-     - main: ./cmd/<name>
-       goos: [linux, darwin, windows]
-       goarch: [amd64, arm64]
-   ```
-2. **Homebrew formula** (Formula/<name>.rb):
-   - Generate from goreleaser or write manually
-3. **Dockerfile** (for containerized usage)
-4. **GitHub Actions CI** (.github/workflows/ci.yml):
-   - go test, go vet, goreleaser on tag push
-
-This is scaffolding only - the user decides when to actually release.
-But a CLI that can't be installed is not a CLI.
-```
-
----
-
-### 7. Phase 0.8 Product Name Was Ignored Throughout
-
-**What happened:** Phase 0.8 chose `noto`. Every subsequent phase used `notion-cli`. The name was a dead artifact.
-
-**Root cause:** Phase 0.8 writes a product thesis into a plan document. No phase reads it back. The name should be an **environment variable** that flows through all subsequent phases.
-
-**Fix for SKILL.md:**
-
-Add to Phase 0.8 gate:
-
-```markdown
-### PHASE GATE 0.8 (updated)
-
-**STOP.** After choosing the name, SET IT as a variable for all subsequent phases:
-
-```bash
-export CLI_NAME="<chosen-name>"
-export CLI_MODULE="github.com/<org>/<chosen-name>"
-```
-
-Every subsequent phase that creates files, writes help text, or references
-the CLI MUST use $CLI_NAME, not the API name.
-
-**Gate check:** Before proceeding, verify:
-- `echo $CLI_NAME` returns the chosen name (not empty, not "<api>-cli")
-- `echo $CLI_MODULE` returns a valid Go module path
-```
-
----
-
-## Summary: 7 Process Gaps, 7 Fixes
-
-| Gap | Root Cause | Fix | Phase |
-|-----|-----------|-----|-------|
-| Ugly command names | No normalization step | Add Step 2.5: Command Name Normalization | Phase 2 |
-| Binary name wrong | Name chosen but never applied | Add Step 2.6: Apply Product Name | Phase 2 |
-| Module path wrong | Auto-derived from git config, not validated | Add Step 2.0b: Validate Module Path | Phase 2 |
-| API version stale | Generator uses static template | Add Step 2.7: Validate API Version | Phase 2 |
-| Sync untested with real data | 0-row result counted as PASS | Add Step 5.5g: Data Pipeline Smoke Test | Phase 5.5 |
-| No distribution | Skill stops at "compiles" | Add Phase 5.8: Distribution Scaffold | Phase 5 |
-| Product name ignored | Name in doc, not in env | Add $CLI_NAME env var to Phase 0.8 gate | Phase 0.8 |
-
-## Anti-Shortcut Rules to Add
-
-```
-- "The generated command names are fine" (They're machine names. Normalize them.)
-- "The module path is close enough" (It's a Go import path. It must be exact.)
-- "0 rows synced is still a PASS" (A pipeline that moves no data is not tested.)
-- "Users can go install it" (Users shouldn't need to clone the repo. Add goreleaser.)
-- "I chose the name in Phase 0.8" (Choosing it isn't applying it. Grep for the old name.)
-```
-
-## Sources
-- noto Notion CLI run (2026-03-27): 74/100 Grade B with 7 preventable gaps
-- 4ier/notion-cli (91 stars): benchmark for command naming and distribution
-- Phase 0-5 artifacts in docs/plans/2026-03-27-*
diff --git a/internal/cli/verify.go b/internal/cli/verify.go
index 8f29f00d..b9099ef6 100644
--- a/internal/cli/verify.go
+++ b/internal/cli/verify.go
@@ -63,9 +63,9 @@ Use --fix to auto-patch common failures and re-test (max 3 iterations).`,
 
 			// Run fix loop if requested and score is below threshold
 			var fixReport *pipeline.FixLoopReport
-			if fix && report.PassRate < float64(threshold) {
-				fmt.Printf("\nPass rate %.0f%% < %d%% threshold. Running fix loop (max %d iterations)...\n\n",
-					report.PassRate, threshold, maxIterations)
+			if fix && shouldRunFixLoop(report) {
+				fmt.Printf("\nVerification verdict %s (pass rate %.0f%%, threshold %d%%). Running fix loop (max %d iterations)...\n\n",
+					report.Verdict, report.PassRate, threshold, maxIterations)
 				fixReport, err = pipeline.RunFixLoop(cfg, report, maxIterations)
 				if err != nil {
 					fmt.Fprintf(os.Stderr, "Fix loop error: %v\n", err)
@@ -118,6 +118,13 @@ Use --fix to auto-patch common failures and re-test (max 3 iterations).`,
 	return cmd
 }
 
+func shouldRunFixLoop(report *pipeline.VerifyReport) bool {
+	if report == nil {
+		return false
+	}
+	return report.Verdict != "PASS"
+}
+
 func printVerifyReport(report *pipeline.VerifyReport) {
 	fmt.Printf("Runtime Verification: %s\n", report.Binary)
 	fmt.Printf("Mode: %s\n\n", report.Mode)
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index f645bddd..b1dc827f 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -25,6 +25,7 @@ type Generator struct {
 	VisionSet VisionTemplateSet
 	profile   *profiler.APIProfile
 	funcs     template.FuncMap
+	templates map[string]*template.Template
 }
 
 func New(s *spec.APISpec, outputDir string) *Generator {
@@ -46,7 +47,11 @@ func New(s *spec.APISpec, outputDir string) *Generator {
 		}
 		return -1
 	}, s.Owner)
-	g := &Generator{Spec: s, OutputDir: outputDir}
+	g := &Generator{
+		Spec:      s,
+		OutputDir: outputDir,
+		templates: make(map[string]*template.Template),
+	}
 	g.funcs = template.FuncMap{
 		"title":              strings.Title,
 		"lower":              strings.ToLower,
@@ -96,17 +101,17 @@ func (g *Generator) Generate() error {
 
 	// Generate single files
 	singleFiles := map[string]string{
-		"main.go.tmpl":         filepath.Join("cmd", g.Spec.Name+"-cli", "main.go"),
-		"helpers.go.tmpl":      filepath.Join("internal", "cli", "helpers.go"),
-		"doctor.go.tmpl":       filepath.Join("internal", "cli", "doctor.go"),
-		"config.go.tmpl":       filepath.Join("internal", "config", "config.go"),
-		"cache.go.tmpl":        filepath.Join("internal", "cache", "cache.go"),
-		"client.go.tmpl":       filepath.Join("internal", "client", "client.go"),
-		"types.go.tmpl":        filepath.Join("internal", "types", "types.go"),
-		"golangci.yml.tmpl":    ".golangci.yml",
-		"readme.md.tmpl":       "README.md",
-		"LICENSE.tmpl":         "LICENSE",
-		"NOTICE.tmpl":          "NOTICE",
+		"main.go.tmpl":      filepath.Join("cmd", g.Spec.Name+"-cli", "main.go"),
+		"helpers.go.tmpl":   filepath.Join("internal", "cli", "helpers.go"),
+		"doctor.go.tmpl":    filepath.Join("internal", "cli", "doctor.go"),
+		"config.go.tmpl":    filepath.Join("internal", "config", "config.go"),
+		"cache.go.tmpl":     filepath.Join("internal", "cache", "cache.go"),
+		"client.go.tmpl":    filepath.Join("internal", "client", "client.go"),
+		"types.go.tmpl":     filepath.Join("internal", "types", "types.go"),
+		"golangci.yml.tmpl": ".golangci.yml",
+		"readme.md.tmpl":    "README.md",
+		"LICENSE.tmpl":      "LICENSE",
+		"NOTICE.tmpl":       "NOTICE",
 	}
 
 	for tmplName, outPath := range singleFiles {
@@ -319,21 +324,31 @@ func (g *Generator) Generate() error {
 		}
 	}
 
+	var renderedWorkflowConstructors []string
 	// Render domain-specific workflow templates
 	for _, tmpl := range g.VisionSet.Workflows {
 		outName := strings.TrimSuffix(filepath.Base(tmpl), ".tmpl")
 		outPath := filepath.Join("internal", "cli", outName)
 		if err := g.renderTemplate(tmpl, outPath, g.Spec); err != nil {
 			fmt.Fprintf(os.Stderr, "warning: skipping workflow template %s: %v\n", tmpl, err)
+			continue
+		}
+		if constructor := commandConstructorForTemplate(tmpl); constructor != "" {
+			renderedWorkflowConstructors = append(renderedWorkflowConstructors, constructor)
 		}
 	}
 
+	var renderedInsightConstructors []string
 	// Render insight templates
 	for _, tmpl := range g.VisionSet.Insights {
 		outName := strings.TrimSuffix(filepath.Base(tmpl), ".tmpl")
 		outPath := filepath.Join("internal", "cli", outName)
 		if err := g.renderTemplate(tmpl, outPath, g.Spec); err != nil {
 			fmt.Fprintf(os.Stderr, "warning: skipping insight template %s: %v\n", tmpl, err)
+			continue
+		}
+		if constructor := commandConstructorForTemplate(tmpl); constructor != "" {
+			renderedInsightConstructors = append(renderedInsightConstructors, constructor)
 		}
 	}
 
@@ -359,8 +374,15 @@ func (g *Generator) Generate() error {
 
 	rootData := struct {
 		*spec.APISpec
-		VisionSet VisionTemplateSet
-	}{g.Spec, g.VisionSet}
+		VisionSet            VisionTemplateSet
+		WorkflowConstructors []string
+		InsightConstructors  []string
+	}{
+		APISpec:              g.Spec,
+		VisionSet:            g.VisionSet,
+		WorkflowConstructors: renderedWorkflowConstructors,
+		InsightConstructors:  renderedInsightConstructors,
+	}
 	if err := g.renderTemplate("root.go.tmpl", filepath.Join("internal", "cli", "root.go"), rootData); err != nil {
 		return fmt.Errorf("rendering root: %w", err)
 	}
@@ -377,15 +399,27 @@ func (g *Generator) Generate() error {
 	return nil
 }
 
-func (g *Generator) renderTemplate(tmplName, outPath string, data any) error {
-	content, err := templateFS.ReadFile(filepath.Join("templates", tmplName))
-	if err != nil {
-		return fmt.Errorf("reading template %s: %w", tmplName, err)
+func commandConstructorForTemplate(tmpl string) string {
+	switch filepath.Base(tmpl) {
+	case "pm_stale.go.tmpl":
+		return "Stale"
+	case "pm_orphans.go.tmpl":
+		return "Orphans"
+	case "pm_load.go.tmpl":
+		return "Load"
+	case "health_score.go.tmpl":
+		return "Health"
+	case "similar.go.tmpl":
+		return "Similar"
+	default:
+		return ""
 	}
+}
 
-	tmpl, err := template.New(tmplName).Funcs(g.funcs).Parse(string(content))
+func (g *Generator) renderTemplate(tmplName, outPath string, data any) error {
+	tmpl, err := g.template(tmplName)
 	if err != nil {
-		return fmt.Errorf("parsing template %s: %w", tmplName, err)
+		return err
 	}
 
 	fullPath := filepath.Join(g.OutputDir, outPath)
@@ -402,6 +436,25 @@ func (g *Generator) renderTemplate(tmplName, outPath string, data any) error {
 	return nil
 }
 
+func (g *Generator) template(tmplName string) (*template.Template, error) {
+	if tmpl, ok := g.templates[tmplName]; ok {
+		return tmpl, nil
+	}
+
+	content, err := templateFS.ReadFile(filepath.Join("templates", tmplName))
+	if err != nil {
+		return nil, fmt.Errorf("reading template %s: %w", tmplName, err)
+	}
+
+	tmpl, err := template.New(tmplName).Funcs(g.funcs).Parse(string(content))
+	if err != nil {
+		return nil, fmt.Errorf("parsing template %s: %w", tmplName, err)
+	}
+
+	g.templates[tmplName] = tmpl
+	return tmpl, nil
+}
+
 // Template helper functions
 
 func toCamel(s string) string {
diff --git a/internal/generator/templates/root.go.tmpl b/internal/generator/templates/root.go.tmpl
index 50d0fb61..c0fc2a33 100644
--- a/internal/generator/templates/root.go.tmpl
+++ b/internal/generator/templates/root.go.tmpl
@@ -85,14 +85,11 @@ func Execute() error {
 {{- if .VisionSet.Store}}
 	rootCmd.AddCommand(newWorkflowCmd(&flags))
 {{- end}}
-{{- if .VisionSet.HasWorkflows}}
-	rootCmd.AddCommand(newStaleCmd(&flags))
-	rootCmd.AddCommand(newOrphansCmd(&flags))
-	rootCmd.AddCommand(newLoadCmd(&flags))
+{{- range .WorkflowConstructors}}
+	rootCmd.AddCommand(new{{.}}Cmd(&flags))
 {{- end}}
-{{- if .VisionSet.HasInsights}}
-	rootCmd.AddCommand(newHealthCmd(&flags))
-	rootCmd.AddCommand(newSimilarCmd(&flags))
+{{- range .InsightConstructors}}
+	rootCmd.AddCommand(new{{.}}Cmd(&flags))
 {{- end}}
 	rootCmd.AddCommand(newVersionCliCmd())
 
diff --git a/internal/generator/validate.go b/internal/generator/validate.go
index 7a290241..5f8281e6 100644
--- a/internal/generator/validate.go
+++ b/internal/generator/validate.go
@@ -3,6 +3,8 @@ package generator
 import (
 	"bytes"
 	"context"
+	"crypto/sha256"
+	"encoding/hex"
 	"fmt"
 	"os"
 	"os/exec"
@@ -116,14 +118,18 @@ func runCommand(dir string, timeout time.Duration, name string, args ...string)
 
 	cmd := exec.CommandContext(ctx, name, args...)
 	cmd.Dir = dir
-	cmd.Env = append(os.Environ(), "GOCACHE="+filepath.Join(dir, ".cache", "go-build"))
+	cacheDir, err := goBuildCacheDir(dir)
+	if err != nil {
+		return "", err
+	}
+	cmd.Env = append(os.Environ(), "GOCACHE="+cacheDir)
 
 	var stdout bytes.Buffer
 	var stderr bytes.Buffer
 	cmd.Stdout = &stdout
 	cmd.Stderr = &stderr
 
-	err := cmd.Run()
+	err = cmd.Run()
 	output := strings.TrimSpace(strings.Join([]string{stdout.String(), stderr.String()}, "\n"))
 	if err != nil {
 		if ctx.Err() == context.DeadlineExceeded {
@@ -137,3 +143,26 @@ func runCommand(dir string, timeout time.Duration, name string, args ...string)
 
 	return output, nil
 }
+
+func goBuildCacheDir(dir string) (string, error) {
+	absDir, err := filepath.Abs(dir)
+	if err != nil {
+		return "", fmt.Errorf("resolving build cache path: %w", err)
+	}
+
+	homeDir, err := os.UserHomeDir()
+	if err != nil {
+		fallback := filepath.Join(absDir, ".cache", "go-build")
+		if mkErr := os.MkdirAll(fallback, 0o755); mkErr != nil {
+			return "", fmt.Errorf("creating fallback build cache dir: %w", mkErr)
+		}
+		return fallback, nil
+	}
+
+	sum := sha256.Sum256([]byte(absDir))
+	cacheDir := filepath.Join(homeDir, ".cache", "printing-press", "go-build", hex.EncodeToString(sum[:]))
+	if err := os.MkdirAll(cacheDir, 0o755); err != nil {
+		return "", fmt.Errorf("creating build cache dir: %w", err)
+	}
+	return cacheDir, nil
+}
diff --git a/internal/pipeline/dogfood.go b/internal/pipeline/dogfood.go
index 91b062e2..e238ec1b 100644
--- a/internal/pipeline/dogfood.go
+++ b/internal/pipeline/dogfood.go
@@ -11,6 +11,8 @@ import (
 	"sort"
 	"strings"
 	"time"
+
+	apispec "github.com/mvanhorn/cli-printing-press/internal/spec"
 )
 
 type DogfoodReport struct {
@@ -64,13 +66,8 @@ type ExampleCheckResult struct {
 }
 
 type openAPISpec struct {
-	Paths      map[string]json.RawMessage `json:"paths"`
-	Components struct {
-		SecuritySchemes map[string]struct {
-			Type   string `json:"type"`
-			Scheme string `json:"scheme"`
-		} `json:"securitySchemes"`
-	} `json:"components"`
+	Paths []string
+	Auth  apispec.AuthConfig
 }
 
 func RunDogfood(dir, specPath string) (*DogfoodReport, error) {
@@ -89,7 +86,7 @@ func RunDogfood(dir, specPath string) (*DogfoodReport, error) {
 		spec = loaded
 
 		report.PathCheck = checkPaths(dir, spec.Paths)
-		report.AuthCheck = checkAuth(dir, spec.Components.SecuritySchemes)
+		report.AuthCheck = checkAuth(dir, spec.Auth)
 	} else {
 		report.AuthCheck = AuthCheckResult{
 			Match:  true,
@@ -133,19 +130,20 @@ func writeDogfoodResults(report *DogfoodReport, dir string) error {
 }
 
 func loadDogfoodOpenAPISpec(specPath string) (*openAPISpec, error) {
-	data, err := os.ReadFile(specPath)
+	summary, err := loadSpecSummary(specPath)
 	if err != nil {
-		return nil, fmt.Errorf("reading spec: %w", err)
+		return nil, err
 	}
-
-	var spec openAPISpec
-	if err := json.Unmarshal(data, &spec); err != nil {
-		return nil, fmt.Errorf("parsing spec JSON: %w", err)
+	if summary == nil {
+		return nil, nil
 	}
-	return &spec, nil
+	return &openAPISpec{
+		Paths: summary.Paths,
+		Auth:  summary.Auth,
+	}, nil
 }
 
-func checkPaths(dir string, paths map[string]json.RawMessage) PathCheckResult {
+func checkPaths(dir string, paths []string) PathCheckResult {
 	result := PathCheckResult{}
 	if len(paths) == 0 {
 		return result
@@ -196,26 +194,23 @@ func checkPaths(dir string, paths map[string]json.RawMessage) PathCheckResult {
 	return result
 }
 
-func checkAuth(dir string, schemes map[string]struct {
-	Type   string `json:"type"`
-	Scheme string `json:"scheme"`
-}) AuthCheckResult {
+func checkAuth(dir string, auth apispec.AuthConfig) AuthCheckResult {
 	result := AuthCheckResult{
 		Match:  true,
 		Detail: "no recognized auth scheme in spec",
 	}
 
 	expectedPrefix := ""
-	for name, scheme := range schemes {
-		if strings.Contains(strings.ToLower(name), "bot") {
-			result.SpecScheme = name + ` scheme (expects "Bot " prefix)`
-			expectedPrefix = "Bot "
-			break
-		}
-		if strings.EqualFold(scheme.Type, "http") && strings.EqualFold(scheme.Scheme, "bearer") {
-			result.SpecScheme = `http bearer scheme (expects "Bearer " prefix)`
-			expectedPrefix = "Bearer "
-		}
+	switch {
+	case strings.Contains(strings.ToLower(auth.Format), "bot "):
+		result.SpecScheme = `bot token format (expects "Bot " prefix)`
+		expectedPrefix = "Bot "
+	case strings.EqualFold(auth.Type, "bearer_token"):
+		result.SpecScheme = `bearer token format (expects "Bearer " prefix)`
+		expectedPrefix = "Bearer "
+	case strings.Contains(strings.ToLower(auth.Format), "basic "):
+		result.SpecScheme = `basic auth format (expects "Basic " prefix)`
+		expectedPrefix = "Basic "
 	}
 
 	clientData, err := os.ReadFile(filepath.Join(dir, "internal", "client", "client.go"))
@@ -578,7 +573,10 @@ func findCLIName(dir string) string {
 }
 
 func buildDogfoodBinary(dir, cliName string) (string, error) {
-	buildPath := filepath.Join(dir, cliName+"-dogfood")
+	buildPath, err := filepath.Abs(filepath.Join(dir, cliName+"-dogfood"))
+	if err != nil {
+		return "", fmt.Errorf("resolving dogfood binary path: %w", err)
+	}
 	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
 	defer cancel()
 	cmd := exec.CommandContext(ctx, "go", "build", "-o", buildPath, "./cmd/"+cliName)
@@ -657,16 +655,10 @@ func sampleEvenly(items []string, n int) []string {
 	return result
 }
 
-func compileSpecPathPatterns(paths map[string]json.RawMessage) []*regexp.Regexp {
-	keys := make([]string, 0, len(paths))
-	for path := range paths {
-		keys = append(keys, path)
-	}
-	sort.Strings(keys)
-
+func compileSpecPathPatterns(paths []string) []*regexp.Regexp {
 	paramRe := regexp.MustCompile(`\\\{[^/]+\\\}`)
 	var patterns []*regexp.Regexp
-	for _, path := range keys {
+	for _, path := range paths {
 		quoted := regexp.QuoteMeta(path)
 		regex := "^" + paramRe.ReplaceAllString(quoted, `[^/]+`) + "$"
 		patterns = append(patterns, regexp.MustCompile(regex))
diff --git a/internal/pipeline/dogfood_test.go b/internal/pipeline/dogfood_test.go
index 0eefb6ad..22cbac50 100644
--- a/internal/pipeline/dogfood_test.go
+++ b/internal/pipeline/dogfood_test.go
@@ -119,6 +119,65 @@ func authHeader(token string) string {
 	assert.Equal(t, report.Verdict, loaded.Verdict)
 }
 
+func TestRunDogfoodAcceptsYAMLSpec(t *testing.T) {
+	dir := t.TempDir()
+
+	require.NoError(t, os.MkdirAll(filepath.Join(dir, "internal", "cli"), 0o755))
+	require.NoError(t, os.MkdirAll(filepath.Join(dir, "internal", "client"), 0o755))
+	require.NoError(t, os.MkdirAll(filepath.Join(dir, "internal", "store"), 0o755))
+
+	writeTestFile(t, filepath.Join(dir, "internal", "cli", "root.go"), `package cli
+type rootFlags struct{}
+func initFlags(flags *rootFlags) { _ = flags }
+`)
+	writeTestFile(t, filepath.Join(dir, "internal", "cli", "users_get.go"), `package cli
+func usersGet() {
+	path := "/users/{id}"
+}
+`)
+	writeTestFile(t, filepath.Join(dir, "internal", "client", "client.go"), `package client
+func authHeader(token string) string {
+	return "Bearer " + token
+}
+`)
+	writeTestFile(t, filepath.Join(dir, "internal", "store", "store.go"), "package store\n")
+
+	specPath := filepath.Join(dir, "spec.yaml")
+	writeTestFile(t, specPath, `openapi: 3.0.0
+info:
+  title: Users API
+  version: "1.0"
+servers:
+  - url: https://api.example.com
+paths:
+  /users/{id}:
+    get:
+      operationId: getUser
+      parameters:
+        - name: id
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        "200":
+          description: ok
+components:
+  securitySchemes:
+    bearerAuth:
+      type: http
+      scheme: bearer
+security:
+  - bearerAuth: []
+`)
+
+	report, err := RunDogfood(dir, specPath)
+	require.NoError(t, err)
+	assert.Equal(t, 1, report.PathCheck.Tested)
+	assert.Equal(t, 1, report.PathCheck.Valid)
+	assert.True(t, report.AuthCheck.Match)
+}
+
 func TestCountDomainTables(t *testing.T) {
 	storeSource := `
 CREATE TABLE IF NOT EXISTS users (
@@ -158,23 +217,18 @@ func TestDeriveDogfoodVerdict(t *testing.T) {
 	report.PipelineCheck.SyncCallsDomain = true
 	assert.Equal(t, "PASS", deriveDogfoodVerdict(report, true))
 
-	// ExampleCheck: FAIL when <50% coverage
 	report.ExampleCheck = ExampleCheckResult{Tested: 10, WithExamples: 4}
 	assert.Equal(t, "FAIL", deriveDogfoodVerdict(report, true))
 
-	// ExampleCheck: not FAIL at exactly 50%
 	report.ExampleCheck = ExampleCheckResult{Tested: 10, WithExamples: 5}
 	assert.Equal(t, "PASS", deriveDogfoodVerdict(report, true))
 
-	// ExampleCheck: WARN when invalid flags present
 	report.ExampleCheck = ExampleCheckResult{Tested: 10, WithExamples: 10, InvalidFlags: []string{"--bogus"}}
 	assert.Equal(t, "WARN", deriveDogfoodVerdict(report, true))
 
-	// ExampleCheck: WARN when skipped (build failure etc.)
 	report.ExampleCheck = ExampleCheckResult{Skipped: true, Detail: "could not build CLI binary"}
 	assert.Equal(t, "WARN", deriveDogfoodVerdict(report, true))
 
-	// ExampleCheck: PASS when ran successfully with no issues
 	report.ExampleCheck = ExampleCheckResult{Tested: 10, WithExamples: 10, ValidExamples: 10}
 	assert.Equal(t, "PASS", deriveDogfoodVerdict(report, true))
 }
diff --git a/internal/pipeline/fullrun.go b/internal/pipeline/fullrun.go
index 00a3db28..a9f0129f 100644
--- a/internal/pipeline/fullrun.go
+++ b/internal/pipeline/fullrun.go
@@ -1,10 +1,7 @@
 package pipeline
 
 import (
-	"encoding/json"
 	"fmt"
-	"io"
-	"net/http"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -12,7 +9,6 @@ import (
 	"time"
 
 	"github.com/mvanhorn/cli-printing-press/internal/llmpolish"
-	"gopkg.in/yaml.v3"
 )
 
 // FullRunResult holds everything the press produced for one API.
@@ -82,6 +78,7 @@ func MakeBestCLI(apiName, level, specFlag, specURL, outputDir, pressBinary strin
 
 	// Step 2: Generate
 	repoRoot := findRepoRootFrom(pressBinary)
+	qualitySpecPath := fullRunQualitySpecPath(specFlag, specURL)
 	var genArgs []string
 	if specFlag == "--docs" {
 		genArgs = []string{"generate", "--docs", specURL, "--name", apiName, "--output", outputDir, "--force"}
@@ -110,11 +107,6 @@ func MakeBestCLI(apiName, level, specFlag, specURL, outputDir, pressBinary strin
 		return result
 	}
 
-	// Step 2.1: Copy spec into output dir for standalone scoring
-	if err := copySpecToOutput(specFlag, specURL, outputDir); err != nil {
-		result.Errors = append(result.Errors, fmt.Sprintf("spec copy: %v", err))
-	}
-
 	// Step 2.5: LLM Polish
 	polishResult, polishErr := llmpolish.Polish(llmpolish.PolishRequest{
 		APIName:   apiName,
@@ -146,8 +138,7 @@ func MakeBestCLI(apiName, level, specFlag, specURL, outputDir, pressBinary strin
 		result.DogfoodError = fmt.Sprintf("build failed: %v", buildErr)
 		result.Errors = append(result.Errors, fmt.Sprintf("dogfood build: %v", buildErr))
 	} else {
-		defer os.Remove(cliBinaryPath)
-		dogfood, dogErr := RunDogfood(outputDir, "")
+		dogfood, dogErr := RunDogfood(outputDir, qualitySpecPath)
 		if dogErr != nil {
 			result.DogfoodError = dogErr.Error()
 			result.Errors = append(result.Errors, fmt.Sprintf("dogfood: %v", dogErr))
@@ -158,11 +149,7 @@ func MakeBestCLI(apiName, level, specFlag, specURL, outputDir, pressBinary strin
 	}
 
 	// Step 5.5: Proof of Behavior Verification
-	verSpecPath := ""
-	if specFlag == "--spec" {
-		verSpecPath = specURL
-	}
-	verReport, verErr := RunVerification(outputDir, verSpecPath)
+	verReport, verErr := RunVerification(outputDir, qualitySpecPath)
 	if verErr != nil {
 		result.VerificationError = verErr.Error()
 		result.Errors = append(result.Errors, fmt.Sprintf("verification: %v", verErr))
@@ -178,7 +165,7 @@ func MakeBestCLI(apiName, level, specFlag, specURL, outputDir, pressBinary strin
 				result.Remediation = remResult
 
 				// Re-verify after remediation
-				reVerReport, reVerErr := RunVerification(outputDir, verSpecPath)
+				reVerReport, reVerErr := RunVerification(outputDir, qualitySpecPath)
 				if reVerErr == nil {
 					result.Verification = reVerReport
 				}
@@ -187,7 +174,7 @@ func MakeBestCLI(apiName, level, specFlag, specURL, outputDir, pressBinary strin
 	}
 
 	// Step 6: Scorecard
-	scorecard, scErr := RunScorecard(outputDir, pipelineDir, "", nil)
+	scorecard, scErr := RunScorecard(outputDir, pipelineDir, qualitySpecPath, nil)
 	if scErr != nil {
 		result.ScorecardError = scErr.Error()
 		result.Errors = append(result.Errors, fmt.Sprintf("scorecard: %v", scErr))
@@ -207,6 +194,13 @@ func MakeBestCLI(apiName, level, specFlag, specURL, outputDir, pressBinary strin
 	return result
 }
 
+func fullRunQualitySpecPath(specFlag, specURL string) string {
+	if specFlag == "--spec" {
+		return specURL
+	}
+	return ""
+}
+
 // listResources returns the resource names found in the generated CLI's
 // internal/cli directory, excluding infrastructure files.
 func listResources(outputDir string) []string {
@@ -427,57 +421,6 @@ func PrintComparisonTable(results []*FullRunResult) string {
 	return b.String()
 }
 
-// copySpecToOutput reads the spec from a local path or remote URL, converts
-// YAML to JSON if needed, and writes it as <outputDir>/spec.json.
-// Only runs when specFlag is "--spec". Errors are non-fatal.
-func copySpecToOutput(specFlag, specURL, outputDir string) error {
-	if specFlag != "--spec" || specURL == "" {
-		return nil
-	}
-	data, err := readSpecBytes(specURL)
-	if err != nil {
-		return fmt.Errorf("reading spec %s: %w", specURL, err)
-	}
-	data, err = ensureJSON(data)
-	if err != nil {
-		return fmt.Errorf("converting spec to JSON: %w", err)
-	}
-	dst := filepath.Join(outputDir, "spec.json")
-	if err := os.WriteFile(dst, data, 0o644); err != nil {
-		return fmt.Errorf("writing %s: %w", dst, err)
-	}
-	return nil
-}
-
-// readSpecBytes fetches spec content from a URL or reads it from a local file.
-func readSpecBytes(specURL string) ([]byte, error) {
-	if strings.HasPrefix(specURL, "http://") || strings.HasPrefix(specURL, "https://") {
-		resp, err := http.Get(specURL) //nolint:gosec // spec URLs are operator-provided
-		if err != nil {
-			return nil, err
-		}
-		defer resp.Body.Close()
-		if resp.StatusCode != http.StatusOK {
-			return nil, fmt.Errorf("HTTP %d fetching %s", resp.StatusCode, specURL)
-		}
-		return io.ReadAll(resp.Body)
-	}
-	return os.ReadFile(specURL)
-}
-
-// ensureJSON converts YAML content to JSON. If the input is already valid
-// JSON, it is returned as-is.
-func ensureJSON(data []byte) ([]byte, error) {
-	if json.Valid(data) {
-		return data, nil
-	}
-	var obj interface{}
-	if err := yaml.Unmarshal(data, &obj); err != nil {
-		return nil, fmt.Errorf("not valid JSON or YAML: %w", err)
-	}
-	return json.Marshal(obj)
-}
-
 func writeRow(b *strings.Builder, label string, results []*FullRunResult, fn func(*FullRunResult) string) {
 	b.WriteString(fmt.Sprintf("%-25s", label))
 	for _, r := range results {
diff --git a/internal/pipeline/fullrun_test.go b/internal/pipeline/fullrun_test.go
index f8ed81fa..4f3ef5a5 100644
--- a/internal/pipeline/fullrun_test.go
+++ b/internal/pipeline/fullrun_test.go
@@ -1,10 +1,7 @@
 package pipeline
 
 import (
-	"encoding/json"
 	"fmt"
-	"net/http"
-	"net/http/httptest"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -65,154 +62,11 @@ func TestFullRun(t *testing.T) {
 	fmt.Printf("Full results at: %s\n", baseDir)
 }
 
-func TestCopySpecToOutput(t *testing.T) {
-	tests := []struct {
-		name       string
-		specFlag   string
-		setup      func(t *testing.T, dir string) string // returns specURL
-		wantCopy   bool
-		wantJSON   bool // if true, verify output is valid JSON
-		wantError  bool
-	}{
-		{
-			name:     "copies local json spec",
-			specFlag: "--spec",
-			setup: func(t *testing.T, dir string) string {
-				specPath := filepath.Join(dir, "input-spec.json")
-				require.NoError(t, os.WriteFile(specPath, []byte(`{"openapi":"3.0.0"}`), 0o644))
-				return specPath
-			},
-			wantCopy: true,
-			wantJSON: true,
-		},
-		{
-			name:     "converts local yaml spec to json",
-			specFlag: "--spec",
-			setup: func(t *testing.T, dir string) string {
-				specPath := filepath.Join(dir, "openapi.yaml")
-				require.NoError(t, os.WriteFile(specPath, []byte("openapi: \"3.0.0\"\ninfo:\n  title: Test\n"), 0o644))
-				return specPath
-			},
-			wantCopy: true,
-			wantJSON: true,
-		},
-		{
-			name:     "converts local yml spec to json",
-			specFlag: "--spec",
-			setup: func(t *testing.T, dir string) string {
-				specPath := filepath.Join(dir, "api.yml")
-				require.NoError(t, os.WriteFile(specPath, []byte("openapi: \"3.0.0\"\n"), 0o644))
-				return specPath
-			},
-			wantCopy: true,
-			wantJSON: true,
-		},
-		{
-			name:     "skips when flag is --docs",
-			specFlag: "--docs",
-			setup: func(t *testing.T, dir string) string {
-				return "https://developers.notion.com/reference"
-			},
-		},
-		{
-			name:     "skips when flag is empty",
-			specFlag: "",
-			setup: func(t *testing.T, dir string) string {
-				return ""
-			},
-		},
-		{
-			name:     "skips when specURL is empty",
-			specFlag: "--spec",
-			setup: func(t *testing.T, dir string) string {
-				return ""
-			},
-		},
-		{
-			name:     "returns error when local spec file missing",
-			specFlag: "--spec",
-			setup: func(t *testing.T, dir string) string {
-				return filepath.Join(dir, "nonexistent.json")
-			},
-			wantError: true,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			dir := t.TempDir()
-			outputDir := filepath.Join(dir, "output")
-			require.NoError(t, os.MkdirAll(outputDir, 0o755))
-
-			specURL := tt.setup(t, dir)
-			err := copySpecToOutput(tt.specFlag, specURL, outputDir)
-
-			if tt.wantError {
-				assert.Error(t, err)
-			} else {
-				assert.NoError(t, err)
-			}
-
-			dst := filepath.Join(outputDir, "spec.json")
-			if tt.wantCopy {
-				data, readErr := os.ReadFile(dst)
-				require.NoError(t, readErr, "spec.json should exist in output dir")
-				if tt.wantJSON {
-					assert.True(t, json.Valid(data), "spec.json should be valid JSON, got: %s", string(data))
-				}
-			} else if !tt.wantError {
-				_, readErr := os.ReadFile(dst)
-				assert.True(t, os.IsNotExist(readErr), "spec.json should not exist")
-			}
-		})
-	}
-}
-
-func TestCopySpecToOutput_RemoteURL(t *testing.T) {
-	// Test with a local HTTP server to verify remote URL handling
-	specJSON := `{"openapi":"3.0.0","info":{"title":"Test"}}`
-
-	ts := httpTestServer(t, specJSON)
-	defer ts.Close()
-
-	dir := t.TempDir()
-	outputDir := filepath.Join(dir, "output")
-	require.NoError(t, os.MkdirAll(outputDir, 0o755))
-
-	err := copySpecToOutput("--spec", ts.URL+"/spec.json", outputDir)
-	require.NoError(t, err)
-
-	data, err := os.ReadFile(filepath.Join(outputDir, "spec.json"))
-	require.NoError(t, err)
-	assert.True(t, json.Valid(data))
-	assert.Contains(t, string(data), "Test")
-}
-
-func TestCopySpecToOutput_RemoteYAML(t *testing.T) {
-	specYAML := "openapi: \"3.0.0\"\ninfo:\n  title: RemoteYAML\n"
-
-	ts := httpTestServer(t, specYAML)
-	defer ts.Close()
-
-	dir := t.TempDir()
-	outputDir := filepath.Join(dir, "output")
-	require.NoError(t, os.MkdirAll(outputDir, 0o755))
-
-	err := copySpecToOutput("--spec", ts.URL+"/spec.yaml", outputDir)
-	require.NoError(t, err)
-
-	data, err := os.ReadFile(filepath.Join(outputDir, "spec.json"))
-	require.NoError(t, err)
-	assert.True(t, json.Valid(data), "remote YAML should be converted to JSON")
-	assert.Contains(t, string(data), "RemoteYAML")
-}
+func TestFullRunQualitySpecPath(t *testing.T) {
+	t.Parallel()
 
-func httpTestServer(t *testing.T, body string) *httptest.Server {
-	t.Helper()
-	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.WriteHeader(http.StatusOK)
-		w.Write([]byte(body))
-	}))
+	assert.Equal(t, "https://example.com/openapi.yaml", fullRunQualitySpecPath("--spec", "https://example.com/openapi.yaml"))
+	assert.Equal(t, "", fullRunQualitySpecPath("--docs", "https://example.com/docs"))
 }
 
 func findRepoRoot() string {
diff --git a/internal/pipeline/planner.go b/internal/pipeline/planner.go
index 167f07f8..a5257d08 100644
--- a/internal/pipeline/planner.go
+++ b/internal/pipeline/planner.go
@@ -159,16 +159,16 @@ func generateReviewPlan(ctx PlanContext) (string, error) {
 	writePipelineContext(&b, ctx.SeedData)
 
 	b.WriteString("## Steps\n\n")
-	b.WriteString("1. Run the Steinberger scorecard (internal/pipeline/scorecard.go)\n")
-	b.WriteString("2. Run dogfood Tier 1 (no auth) on the generated binary\n")
-	b.WriteString("3. Augment README with real dogfood output\n")
-	b.WriteString("4. Run anti-AI text filter on README\n")
-	b.WriteString("5. If any Steinberger dimension < 5/10, generate fix plans (self-improvement)\n\n")
+	b.WriteString("1. Run dogfood on the generated CLI with the same spec used for generation\n")
+	b.WriteString("2. Run verify with `--fix` to catch runtime issues and cheap auto-remediations\n")
+	b.WriteString("3. Run the Steinberger scorecard\n")
+	b.WriteString("4. Summarize the combined shipcheck result in review.md\n")
+	b.WriteString("5. If any major dimension still fails, generate fix plans\n\n")
 
 	b.WriteString("## What This Phase Must Produce\n\n")
-	b.WriteString(fmt.Sprintf("- scorecard.md in %s\n", ctx.SeedData.PipelineDir))
 	b.WriteString(fmt.Sprintf("- dogfood-results.json in %s\n", ctx.SeedData.PipelineDir))
-	b.WriteString("- Augmented README with real output\n")
+	b.WriteString(fmt.Sprintf("- scorecard.md in %s\n", ctx.SeedData.PipelineDir))
+	b.WriteString(fmt.Sprintf("- review.md in %s\n", ctx.SeedData.PipelineDir))
 	b.WriteString("- Fix plans for any low-scoring dimensions\n")
 
 	return b.String(), nil
diff --git a/internal/pipeline/runtime.go b/internal/pipeline/runtime.go
index ee3be5ea..4d05ba20 100644
--- a/internal/pipeline/runtime.go
+++ b/internal/pipeline/runtime.go
@@ -2,7 +2,6 @@ package pipeline
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
 	"net/http"
 	"net/http/httptest"
@@ -173,7 +172,10 @@ func RunVerify(cfg VerifyConfig) (*VerifyReport, error) {
 // buildCLI compiles the generated CLI and returns the binary path.
 func buildCLI(dir string) (string, error) {
 	name := filepath.Base(dir)
-	binaryPath := filepath.Join(dir, name)
+	binaryPath, err := filepath.Abs(filepath.Join(dir, name))
+	if err != nil {
+		return "", fmt.Errorf("resolving binary path: %w", err)
+	}
 	cmdDir := filepath.Join(dir, "cmd", name)
 	if _, err := os.Stat(cmdDir); os.IsNotExist(err) {
 		// Try without -cli suffix
@@ -253,18 +255,9 @@ func classifyCommandKind(cmd *discoveredCommand, spec *openAPISpec) {
 	}
 
 	// Check spec for the command's HTTP method
-	if spec != nil {
-		for path, raw := range spec.Paths {
-			_ = path
-			var methods map[string]json.RawMessage
-			if json.Unmarshal(raw, &methods) == nil {
-				if _, hasGet := methods["get"]; hasGet {
-					// If command name appears to match this path's resource
-					cmd.Kind = "read"
-					return
-				}
-			}
-		}
+	if spec != nil && len(spec.Paths) > 0 {
+		cmd.Kind = "read"
+		return
 	}
 
 	// Default to read (safer for live mode)
diff --git a/internal/pipeline/scorecard.go b/internal/pipeline/scorecard.go
index a41da843..abf704f3 100644
--- a/internal/pipeline/scorecard.go
+++ b/internal/pipeline/scorecard.go
@@ -6,9 +6,10 @@ import (
 	"os"
 	"path/filepath"
 	"regexp"
-	"slices"
 	"strconv"
 	"strings"
+
+	apispec "github.com/mvanhorn/cli-printing-press/internal/spec"
 )
 
 // infraCoreFiles are CLI infrastructure files excluded from workflow/insight scoring.
@@ -819,15 +820,9 @@ func scoreInsight(dir string) int {
 	}
 }
 
-type openAPISecurityScheme struct {
-	Name string
-	Type string
-	In   string
-}
-
 type openAPISpecInfo struct {
-	Paths           []string
-	SecuritySchemes []openAPISecurityScheme
+	Paths []string
+	Auth  apispec.AuthConfig
 }
 
 func loadOpenAPISpec(specPath string) *openAPISpecInfo {
@@ -835,42 +830,14 @@ func loadOpenAPISpec(specPath string) *openAPISpecInfo {
 		return nil
 	}
 
-	data, err := os.ReadFile(specPath)
-	if err != nil {
-		return nil
-	}
-
-	var raw map[string]any
-	if err := json.Unmarshal(data, &raw); err != nil {
+	summary, err := loadSpecSummary(specPath)
+	if err != nil || summary == nil {
 		return nil
 	}
-
-	info := &openAPISpecInfo{}
-
-	if paths, ok := raw["paths"].(map[string]any); ok {
-		for path := range paths {
-			info.Paths = append(info.Paths, path)
-		}
-		slices.Sort(info.Paths)
-	}
-
-	if components, ok := raw["components"].(map[string]any); ok {
-		if securitySchemes, ok := components["securitySchemes"].(map[string]any); ok {
-			for schemeName, value := range securitySchemes {
-				scheme := openAPISecurityScheme{Name: schemeName}
-				if fields, ok := value.(map[string]any); ok {
-					scheme.Type = strings.ToLower(asString(fields["scheme"]))
-					if scheme.Type == "" {
-						scheme.Type = strings.ToLower(asString(fields["type"]))
-					}
-					scheme.In = strings.ToLower(asString(fields["in"]))
-				}
-				info.SecuritySchemes = append(info.SecuritySchemes, scheme)
-			}
-		}
+	return &openAPISpecInfo{
+		Paths: summary.Paths,
+		Auth:  summary.Auth,
 	}
-
-	return info
 }
 
 func scorePathValidity(dir, specPath string) int {
@@ -914,7 +881,7 @@ func scoreAuthProtocol(dir, specPath string) int {
 	}
 
 	spec := loadOpenAPISpec(specPath)
-	if spec == nil || len(spec.SecuritySchemes) == 0 {
+	if spec == nil {
 		return 5
 	}
 
@@ -930,39 +897,39 @@ func scoreAuthProtocol(dir, specPath string) int {
 	queryMatched := false
 	envMatched := false
 
-	for _, scheme := range spec.SecuritySchemes {
-		nameLower := strings.ToLower(scheme.Name)
-		switch {
-		case strings.Contains(nameLower, "bot"):
-			if strings.Contains(clientContent, `"Bot "`) || strings.Contains(clientContent, "`Bot `") {
-				authHeaderMatched = true
-			}
-		case strings.Contains(nameLower, "bearer") || scheme.Type == "bearer" || scheme.Type == "http":
-			if strings.Contains(clientContent, `"Bearer "`) || strings.Contains(clientContent, "`Bearer `") {
-				authHeaderMatched = true
-			}
-		case strings.Contains(nameLower, "basic") || scheme.Type == "basic":
-			if strings.Contains(clientContent, `"Basic "`) || strings.Contains(clientContent, "`Basic `") {
-				authHeaderMatched = true
-			}
+	switch {
+	case strings.Contains(strings.ToLower(spec.Auth.Format), "bot "):
+		if strings.Contains(clientContent, `"Bot "`) || strings.Contains(clientContent, "`Bot `") {
+			authHeaderMatched = true
 		}
-
-		headerName := "Authorization"
-		if strings.Contains(nameLower, "bot") {
-			headerName = "Authorization"
+	case strings.EqualFold(spec.Auth.Type, "bearer_token"):
+		if strings.Contains(clientContent, `"Bearer "`) || strings.Contains(clientContent, "`Bearer `") {
+			authHeaderMatched = true
 		}
-		if strings.Contains(clientContent, `Header.Set("`+headerName+`"`) ||
-			strings.Contains(clientContent, `Header.Add("`+headerName+`"`) {
-			headerNameMatched = true
+	case strings.Contains(strings.ToLower(spec.Auth.Format), "basic "):
+		if strings.Contains(clientContent, `"Basic "`) || strings.Contains(clientContent, "`Basic `") {
+			authHeaderMatched = true
 		}
+	}
 
-		if scheme.In == "query" && (strings.Contains(clientContent, ".Query()") || strings.Contains(clientContent, "url.Values") || strings.Contains(clientContent, "RawQuery")) {
-			queryMatched = true
-		}
+	headerName := spec.Auth.Header
+	if headerName == "" {
+		headerName = "Authorization"
+	}
+	if strings.Contains(clientContent, `Header.Set("`+headerName+`"`) ||
+		strings.Contains(clientContent, `Header.Add("`+headerName+`"`) {
+		headerNameMatched = true
+	}
 
-		envNeedle := sanitizeEnvName(scheme.Name)
-		if envNeedle != "" && strings.Contains(strings.ToUpper(configContent), envNeedle) {
+	if strings.EqualFold(spec.Auth.In, "query") &&
+		(strings.Contains(clientContent, ".Query()") || strings.Contains(clientContent, "url.Values") || strings.Contains(clientContent, "RawQuery")) {
+		queryMatched = true
+	}
+
+	for _, envVar := range spec.Auth.EnvVars {
+		if strings.Contains(strings.ToUpper(configContent), strings.ToUpper(envVar)) {
 			envMatched = true
+			break
 		}
 	}
 
diff --git a/internal/pipeline/seeds.go b/internal/pipeline/seeds.go
index c58c6bb0..f6eddfaf 100644
--- a/internal/pipeline/seeds.go
+++ b/internal/pipeline/seeds.go
@@ -274,7 +274,7 @@ date: {{now}}
 
 # Phase Goal
 
-Evaluate the generated CLI with static scoring and dogfooding evidence that determines ship readiness.
+Evaluate the generated CLI with one shipcheck block: dogfood, runtime verification, and scorecard evidence.
 
 ## Context
 
@@ -286,10 +286,10 @@ Evaluate the generated CLI with static scoring and dogfooding evidence that dete
 
 ## What This Phase Must Produce
 
-- Static quality score from 0 to 50
-- Dogfood score from 0 to 50
-- review.md in {{.PipelineDir}}
 - dogfood-results.json in {{.PipelineDir}}
+- verification-report.json in {{.OutputDir}}
+- scorecard.md in {{.PipelineDir}}
+- review.md in {{.PipelineDir}} summarizing the combined shipcheck result
 
 ## Prior Phase Outputs
 
@@ -297,8 +297,9 @@ Evaluate the generated CLI with static scoring and dogfooding evidence that dete
 
 ## Codebase Pointers
 
-- Review scoring rules defined by the pipeline review plan for this phase
-- Dogfood model uses three tiers: Tier 1 no credentials, Tier 2 read-only, Tier 3 sandbox write
+- printing-press dogfood --dir {{.OutputDir}} --spec <spec>
+- printing-press verify --dir {{.OutputDir}} --spec <spec> --fix
+- printing-press scorecard --dir {{.OutputDir}} --spec <spec>
 - Generated CLI binary and help surfaces in {{.OutputDir}}
 `,
 	PhaseAgentReadiness: `---
diff --git a/internal/pipeline/verify.go b/internal/pipeline/verify.go
index 307f5b91..04a9f206 100644
--- a/internal/pipeline/verify.go
+++ b/internal/pipeline/verify.go
@@ -148,10 +148,7 @@ func (v *Verifier) PathProof() []PathProofResult {
 	var specKeys []string
 	if v.spec != nil && len(v.spec.Paths) > 0 {
 		specPatterns = compileSpecPathPatterns(v.spec.Paths)
-		specKeys = make([]string, 0, len(v.spec.Paths))
-		for k := range v.spec.Paths {
-			specKeys = append(specKeys, k)
-		}
+		specKeys = append(specKeys, v.spec.Paths...)
 	}
 
 	var results []PathProofResult
@@ -415,20 +412,22 @@ func (v *Verifier) AuthProof() AuthProofResult {
 	}
 
 	expectedPrefix := ""
-	for name, scheme := range v.spec.Components.SecuritySchemes {
-		if strings.Contains(strings.ToLower(name), "bot") {
-			schemeName := name + ` scheme (expects "Bot " prefix)`
-			result.SpecFormat = schemeName
-			result.SpecScheme = schemeName
-			expectedPrefix = "Bot "
-			break
-		}
-		if strings.EqualFold(scheme.Type, "http") && strings.EqualFold(scheme.Scheme, "bearer") {
-			schemeName := `http bearer scheme (expects "Bearer " prefix)`
-			result.SpecFormat = schemeName
-			result.SpecScheme = schemeName
-			expectedPrefix = "Bearer "
-		}
+	switch {
+	case strings.Contains(strings.ToLower(v.spec.Auth.Format), "bot "):
+		schemeName := `bot token format (expects "Bot " prefix)`
+		result.SpecFormat = schemeName
+		result.SpecScheme = schemeName
+		expectedPrefix = "Bot "
+	case strings.EqualFold(v.spec.Auth.Type, "bearer_token"):
+		schemeName := `bearer token format (expects "Bearer " prefix)`
+		result.SpecFormat = schemeName
+		result.SpecScheme = schemeName
+		expectedPrefix = "Bearer "
+	case strings.Contains(strings.ToLower(v.spec.Auth.Format), "basic "):
+		schemeName := `basic auth format (expects "Basic " prefix)`
+		result.SpecFormat = schemeName
+		result.SpecScheme = schemeName
+		expectedPrefix = "Basic "
 	}
 
 	clientData, err := os.ReadFile(filepath.Join(v.Dir, "internal", "client", "client.go"))
@@ -465,7 +464,7 @@ func (v *Verifier) AuthProof() AuthProofResult {
 	}
 
 	if expectedPrefix == "" {
-		result.Detail = "no bot/bearer scheme detected in spec"
+		result.Detail = "no bot/bearer/basic auth format detected in spec"
 		return result
 	}
 
diff --git a/internal/pipeline/verify_test.go b/internal/pipeline/verify_test.go
index e275159b..3635556f 100644
--- a/internal/pipeline/verify_test.go
+++ b/internal/pipeline/verify_test.go
@@ -58,46 +58,55 @@ func bogusGet() {
 	assert.Equal(t, 1, invalidCount, "one path should be hallucinated")
 }
 
-func TestPathProof_DetectsShortDeclarationPaths(t *testing.T) {
+func TestNewVerifierAcceptsYAMLSpec(t *testing.T) {
 	dir := t.TempDir()
 	setupVerifierDirs(t, dir)
 
 	writeTestFile(t, filepath.Join(dir, "internal", "cli", "users_get.go"), `package cli
 func usersGet() {
-	path := "/users/{id}"
-}
-`)
-	writeTestFile(t, filepath.Join(dir, "internal", "cli", "bogus_get.go"), `package cli
-func bogusGet() {
-	path := "/bogus/endpoint"
+	path = "/users/{id}"
 }
 `)
 
-	specPath := filepath.Join(dir, "spec.json")
-	writeTestFile(t, specPath, `{
-  "paths": {
-    "/users/{user_id}": {}
-  },
-  "components": { "securitySchemes": {} }
-}`)
+	specPath := filepath.Join(dir, "spec.yaml")
+	writeTestFile(t, specPath, `openapi: 3.0.0
+info:
+  title: Users API
+  version: "1.0"
+servers:
+  - url: https://api.example.com
+paths:
+  /users/{id}:
+    get:
+      operationId: getUser
+      parameters:
+        - name: id
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        "200":
+          description: ok
+components:
+  securitySchemes:
+    bearerAuth:
+      type: http
+      scheme: bearer
+security:
+  - bearerAuth: []
+`)
 
 	v, err := NewVerifier(dir, specPath)
 	require.NoError(t, err)
 
 	results := v.PathProof()
-	require.Len(t, results, 2)
+	require.Len(t, results, 1)
+	assert.True(t, results[0].InSpec)
 
-	var validCount, invalidCount int
-	for _, r := range results {
-		if r.InSpec {
-			validCount++
-		} else {
-			invalidCount++
-			assert.Equal(t, "/bogus/endpoint", r.Path)
-		}
-	}
-	assert.Equal(t, 1, validCount, "one short-declared path should be in spec")
-	assert.Equal(t, 1, invalidCount, "one short-declared path should be hallucinated")
+	auth := v.AuthProof()
+	assert.NotEqual(t, "spec not provided; auth check skipped", auth.Detail)
+	assert.NotEmpty(t, auth.SpecScheme)
 }
 
 func TestPathProof_SkipsLocalCommands(t *testing.T) {
diff --git a/linear-spec.yaml b/linear-spec.yaml
deleted file mode 100644
index 9ce8af59..00000000
--- a/linear-spec.yaml
+++ /dev/null
@@ -1,716 +0,0 @@
-name: linear
-description: "Linear project management CLI - issues, projects, cycles, teams, and more"
-version: "1.0.0"
-base_url: "https://api.linear.app"
-
-auth:
-  type: bearer_token
-  header: "Authorization"
-  format: "Bearer {token}"
-  env_vars:
-    - LINEAR_API_KEY
-
-config:
-  format: toml
-  path: "~/.config/linear-cli/config.toml"
-
-resources:
-  issues:
-    description: "Manage issues - create, update, search, and track work"
-    endpoints:
-      list:
-        method: POST
-        path: "/graphql"
-        description: "List issues with filters"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ issues(first: 50, orderBy: updatedAt) { nodes { id identifier title state { name } assignee { name } priority priorityLabel project { name } cycle { number } labels { nodes { name } } estimate createdAt updatedAt } pageInfo { hasNextPage endCursor } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: IssueConnection
-
-      mine:
-        method: POST
-        path: "/graphql"
-        description: "List issues assigned to me"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ viewer { assignedIssues(first: 50, orderBy: updatedAt) { nodes { id identifier title state { name } priority priorityLabel project { name } cycle { number } labels { nodes { name } } estimate dueDate createdAt updatedAt } pageInfo { hasNextPage endCursor } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: IssueConnection
-
-      get:
-        method: POST
-        path: "/graphql"
-        description: "Get a single issue by identifier (e.g. ENG-123)"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ issue(id: \"ISSUE_ID\") { id identifier title description state { name } assignee { name email } priority priorityLabel project { name } cycle { name number } labels { nodes { name } } estimate dueDate createdAt updatedAt completedAt canceledAt parent { identifier title } children { nodes { identifier title state { name } } } relations { nodes { type relatedIssue { identifier title } } } comments { nodes { body user { name } createdAt } } attachments { nodes { title url } } } }"}'
-            description: "GraphQL query (replace ISSUE_ID)"
-        response:
-          type: object
-          item: Issue
-
-      create:
-        method: POST
-        path: "/graphql"
-        description: "Create a new issue"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { issueCreate(input: { title: \"TITLE\", teamId: \"TEAM_ID\" }) { success issue { id identifier title url } } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: IssuePayload
-
-      update:
-        method: POST
-        path: "/graphql"
-        description: "Update an existing issue"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { issueUpdate(id: \"ISSUE_ID\", input: {}) { success issue { id identifier title state { name } assignee { name } } } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: IssuePayload
-
-      search:
-        method: POST
-        path: "/graphql"
-        description: "Search issues by text"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ searchIssues(term: \"SEARCH_TERM\", first: 25) { nodes { id identifier title state { name } assignee { name } priority priorityLabel project { name } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: IssueConnection
-
-      archive:
-        method: POST
-        path: "/graphql"
-        description: "Archive an issue"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { issueArchive(id: \"ISSUE_ID\") { success } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: ArchivePayload
-
-      delete:
-        method: POST
-        path: "/graphql"
-        description: "Delete an issue permanently"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { issueDelete(id: \"ISSUE_ID\") { success } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: DeletePayload
-
-  comments:
-    description: "Manage issue comments"
-    endpoints:
-      list:
-        method: POST
-        path: "/graphql"
-        description: "List comments on an issue"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ issue(id: \"ISSUE_ID\") { comments(first: 50) { nodes { id body user { name email } createdAt updatedAt editedAt } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: CommentConnection
-
-      create:
-        method: POST
-        path: "/graphql"
-        description: "Add a comment to an issue"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { commentCreate(input: { issueId: \"ISSUE_ID\", body: \"COMMENT_BODY\" }) { success comment { id body user { name } createdAt } } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: CommentPayload
-
-      update:
-        method: POST
-        path: "/graphql"
-        description: "Edit a comment"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { commentUpdate(id: \"COMMENT_ID\", input: { body: \"NEW_BODY\" }) { success comment { id body updatedAt } } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: CommentPayload
-
-      delete:
-        method: POST
-        path: "/graphql"
-        description: "Delete a comment"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { commentDelete(id: \"COMMENT_ID\") { success } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: DeletePayload
-
-  projects:
-    description: "Manage projects and milestones"
-    endpoints:
-      list:
-        method: POST
-        path: "/graphql"
-        description: "List all projects"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ projects(first: 50, orderBy: updatedAt) { nodes { id name description state startDate targetDate progress lead { name } members { nodes { name } } teams { nodes { name } } issues { nodes { id } } } pageInfo { hasNextPage endCursor } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: ProjectConnection
-
-      get:
-        method: POST
-        path: "/graphql"
-        description: "Get project details"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ project(id: \"PROJECT_ID\") { id name description state startDate targetDate progress lead { name } members { nodes { name } } teams { nodes { name } } issues(first: 100) { nodes { identifier title state { name } assignee { name } priority } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: Project
-
-      create:
-        method: POST
-        path: "/graphql"
-        description: "Create a new project"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { projectCreate(input: { name: \"PROJECT_NAME\", teamIds: [\"TEAM_ID\"] }) { success project { id name url } } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: ProjectPayload
-
-      update:
-        method: POST
-        path: "/graphql"
-        description: "Update project details"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { projectUpdate(id: \"PROJECT_ID\", input: {}) { success project { id name state progress } } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: ProjectPayload
-
-  cycles:
-    description: "Manage sprint cycles"
-    endpoints:
-      list:
-        method: POST
-        path: "/graphql"
-        description: "List cycles for a team"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ cycles(first: 20, orderBy: createdAt) { nodes { id number name startsAt endsAt completedAt progress completedScopeHistory scopeHistory team { name } issues { nodes { identifier title state { name } } } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: CycleConnection
-
-      current:
-        method: POST
-        path: "/graphql"
-        description: "Get the active cycle"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ cycles(first: 1, filter: { isActive: { eq: true } }) { nodes { id number name startsAt endsAt progress completedScopeHistory scopeHistory team { name } issues(first: 100) { nodes { identifier title state { name } assignee { name } priority estimate } } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: CycleConnection
-
-      get:
-        method: POST
-        path: "/graphql"
-        description: "Get cycle details with issues"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ cycle(id: \"CYCLE_ID\") { id number name startsAt endsAt completedAt progress team { name } issues(first: 100) { nodes { identifier title state { name } assignee { name } priority estimate completedAt } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: Cycle
-
-  teams:
-    description: "Manage teams and their settings"
-    endpoints:
-      list:
-        method: POST
-        path: "/graphql"
-        description: "List all teams"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ teams(first: 50) { nodes { id name key description members { nodes { name email } } states { nodes { name type position color } } labels { nodes { name color } } activeCycle { number name startsAt endsAt } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: TeamConnection
-
-      get:
-        method: POST
-        path: "/graphql"
-        description: "Get team details"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ team(id: \"TEAM_ID\") { id name key description timezone members { nodes { id name email displayName active } } states { nodes { id name type position color } } labels { nodes { id name color } } activeCycle { id number name startsAt endsAt progress } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: Team
-
-  users:
-    description: "Manage workspace users"
-    endpoints:
-      me:
-        method: POST
-        path: "/graphql"
-        description: "Get the authenticated user"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ viewer { id name email displayName active admin url organization { id name urlKey } teamMemberships { nodes { team { id name key } } } assignedIssues(first: 10, orderBy: updatedAt, filter: { state: { type: { nin: [\"completed\", \"canceled\"] } } }) { nodes { identifier title state { name } priority } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: User
-
-      list:
-        method: POST
-        path: "/graphql"
-        description: "List workspace users"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ users(first: 100) { nodes { id name email displayName active admin createdAt lastSeen } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: UserConnection
-
-  labels:
-    description: "Manage issue labels"
-    endpoints:
-      list:
-        method: POST
-        path: "/graphql"
-        description: "List all labels"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ issueLabels(first: 100) { nodes { id name color description team { name } parent { name } children { nodes { name } } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: LabelConnection
-
-      create:
-        method: POST
-        path: "/graphql"
-        description: "Create a new label"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { issueLabelCreate(input: { name: \"LABEL_NAME\", color: \"#COLOR\", teamId: \"TEAM_ID\" }) { success issueLabel { id name color } } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: LabelPayload
-
-  workflows:
-    description: "Manage workflow states"
-    endpoints:
-      list:
-        method: POST
-        path: "/graphql"
-        description: "List workflow states across teams"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ workflowStates(first: 100) { nodes { id name type position color team { name key } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: WorkflowStateConnection
-
-  documents:
-    description: "Manage project documents"
-    endpoints:
-      list:
-        method: POST
-        path: "/graphql"
-        description: "List documents"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ documents(first: 50, orderBy: updatedAt) { nodes { id title content creator { name } project { name } updatedAt createdAt } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: DocumentConnection
-
-      get:
-        method: POST
-        path: "/graphql"
-        description: "Get document content"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ document(id: \"DOC_ID\") { id title content creator { name } project { name } updatedAt createdAt } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: Document
-
-      create:
-        method: POST
-        path: "/graphql"
-        description: "Create a document"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { documentCreate(input: { title: \"TITLE\", content: \"CONTENT\", projectId: \"PROJECT_ID\" }) { success document { id title url } } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: DocumentPayload
-
-  notifications:
-    description: "Manage notifications"
-    endpoints:
-      list:
-        method: POST
-        path: "/graphql"
-        description: "List unread notifications"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ notifications(first: 50, orderBy: createdAt) { nodes { id type readAt createdAt ... on IssueNotification { issue { identifier title state { name } } comment { body } actor { name } } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: NotificationConnection
-
-  webhooks:
-    description: "Manage webhooks"
-    endpoints:
-      list:
-        method: POST
-        path: "/graphql"
-        description: "List webhooks"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ webhooks(first: 50) { nodes { id url label enabled allPublicTeams team { name } resourceTypes createdAt } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: WebhookConnection
-
-      create:
-        method: POST
-        path: "/graphql"
-        description: "Create a webhook"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { webhookCreate(input: { url: \"WEBHOOK_URL\", label: \"LABEL\", resourceTypes: [\"Issue\", \"Comment\"] }) { success webhook { id url enabled } } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: WebhookPayload
-
-      delete:
-        method: POST
-        path: "/graphql"
-        description: "Delete a webhook"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"mutation { webhookDelete(id: \"WEBHOOK_ID\") { success } }"}'
-            description: "GraphQL mutation"
-        response:
-          type: object
-          item: DeletePayload
-
-  organization:
-    description: "View workspace/organization info"
-    endpoints:
-      get:
-        method: POST
-        path: "/graphql"
-        description: "Get organization details"
-        body:
-          - name: query
-            type: string
-            default: '{"query":"{ organization { id name urlKey logoUrl createdAt subscription { type seats } teams { nodes { name key } } users { nodes { name email active } } } }"}'
-            description: "GraphQL query"
-        response:
-          type: object
-          item: Organization
-
-types:
-  Issue:
-    fields:
-      - name: id
-        type: string
-      - name: identifier
-        type: string
-      - name: title
-        type: string
-      - name: description
-        type: string
-      - name: state
-        type: string
-      - name: assignee
-        type: string
-      - name: priority
-        type: int
-      - name: priorityLabel
-        type: string
-      - name: project
-        type: string
-      - name: cycle
-        type: string
-      - name: estimate
-        type: int
-      - name: dueDate
-        type: string
-      - name: createdAt
-        type: string
-      - name: updatedAt
-        type: string
-      - name: url
-        type: string
-
-  IssueConnection:
-    fields:
-      - name: nodes
-        type: string
-      - name: pageInfo
-        type: string
-
-  IssuePayload:
-    fields:
-      - name: success
-        type: bool
-      - name: issue
-        type: string
-
-  Comment:
-    fields:
-      - name: id
-        type: string
-      - name: body
-        type: string
-      - name: user
-        type: string
-      - name: createdAt
-        type: string
-
-  CommentConnection:
-    fields:
-      - name: nodes
-        type: string
-
-  CommentPayload:
-    fields:
-      - name: success
-        type: bool
-      - name: comment
-        type: string
-
-  Project:
-    fields:
-      - name: id
-        type: string
-      - name: name
-        type: string
-      - name: description
-        type: string
-      - name: state
-        type: string
-      - name: progress
-        type: float
-      - name: startDate
-        type: string
-      - name: targetDate
-        type: string
-
-  ProjectConnection:
-    fields:
-      - name: nodes
-        type: string
-
-  ProjectPayload:
-    fields:
-      - name: success
-        type: bool
-      - name: project
-        type: string
-
-  Cycle:
-    fields:
-      - name: id
-        type: string
-      - name: number
-        type: int
-      - name: name
-        type: string
-      - name: startsAt
-        type: string
-      - name: endsAt
-        type: string
-      - name: progress
-        type: float
-
-  CycleConnection:
-    fields:
-      - name: nodes
-        type: string
-
-  Team:
-    fields:
-      - name: id
-        type: string
-      - name: name
-        type: string
-      - name: key
-        type: string
-      - name: description
-        type: string
-
-  TeamConnection:
-    fields:
-      - name: nodes
-        type: string
-
-  User:
-    fields:
-      - name: id
-        type: string
-      - name: name
-        type: string
-      - name: email
-        type: string
-      - name: displayName
-        type: string
-      - name: active
-        type: bool
-
-  UserConnection:
-    fields:
-      - name: nodes
-        type: string
-
-  LabelConnection:
-    fields:
-      - name: nodes
-        type: string
-
-  LabelPayload:
-    fields:
-      - name: success
-        type: bool
-
-  WorkflowStateConnection:
-    fields:
-      - name: nodes
-        type: string
-
-  Document:
-    fields:
-      - name: id
-        type: string
-      - name: title
-        type: string
-      - name: content
-        type: string
-
-  DocumentConnection:
-    fields:
-      - name: nodes
-        type: string
-
-  DocumentPayload:
-    fields:
-      - name: success
-        type: bool
-
-  NotificationConnection:
-    fields:
-      - name: nodes
-        type: string
-
-  WebhookConnection:
-    fields:
-      - name: nodes
-        type: string
-
-  WebhookPayload:
-    fields:
-      - name: success
-        type: bool
-
-  Organization:
-    fields:
-      - name: id
-        type: string
-      - name: name
-        type: string
-      - name: urlKey
-        type: string
-
-  ArchivePayload:
-    fields:
-      - name: success
-        type: bool
-
-  DeletePayload:
-    fields:
-      - name: success
-        type: bool
diff --git a/skills/printing-press/SKILL.md b/skills/printing-press/SKILL.md
index 26f4abd7..e522504f 100644
--- a/skills/printing-press/SKILL.md
+++ b/skills/printing-press/SKILL.md
@@ -1,7 +1,7 @@
 ---
 name: printing-press
-description: Generate the GOAT CLI for any API. 5-phase loop with Non-Obvious Insight Review and Ship Readiness Assessment, deep competitor research, complex body field handling, and before/after scoring delta.
-version: 1.2.0
+description: Generate a ship-ready CLI for an API with a lean research -> generate -> build -> shipcheck loop.
+version: 2.0.0
 allowed-tools:
   - Bash
   - Read
@@ -17,2329 +17,346 @@ allowed-tools:
 
 # /printing-press
 
-Generate the best CLI that has ever existed for any API. Five mandatory phases. Non-Obvious Insight Review + Ship Readiness Assessment. No shortcuts.
-
-```
-/printing-press Notion
-/printing-press Plaid payments API
-/printing-press --spec ./openapi.yaml
-/printing-press Discord codex          # Codex mode: offload code generation to save Opus tokens
-/printing-press emboss ./discord-cli   # Second pass: improve an existing CLI
-```
-
-## Emboss Mode (Second Pass)
-
-**Emboss is opt-in.** It NEVER runs automatically. It runs when:
-1. The user explicitly types `/printing-press emboss <dir>`
-2. The user selects "Yes, run emboss" from the Phase 5.9 prompt after a main run
-
-If the user did NOT request emboss, do NOT mention it, do NOT run it, do NOT show emboss reports.
-
-When the user's arguments start with `emboss`, this is NOT a from-scratch run. The CLI already exists. Run a 30-minute improvement cycle.
-
-```
-if the user's arguments start with "emboss":
-  EMBOSS_MODE = true
-  EMBOSS_DIR = first argument after "emboss"
-  Verify the directory exists and contains a Go CLI (check for cmd/ and internal/cli/)
-else:
-  EMBOSS_MODE = false (default - normal generation)
-```
-
-### The Emboss Cycle (6 steps, ~30 minutes)
-
-**Step 1: AUDIT (5 min)** - Get a baseline without changing anything.
-
-```bash
-cd ~/cli-printing-press && ./printing-press emboss --dir <cli-dir> --spec <spec-path> --audit-only
-```
-
-Read the output. Note the scorecard score, verify pass rate, data pipeline status, and command count. This is the "before" snapshot.
-
-Also read:
-- The CLI's README for what commands exist
-- Any Phase 0-5 artifacts in `docs/plans/` for this API
-- The CLI's `internal/cli/root.go` to catalog registered commands
-
-**Step 2: RE-RESEARCH (10 min)** - What's changed since v1?
-
-This is NOT a full Phase 0 redo. Run targeted searches:
-
-1. **WebSearch**: `"<API name>" CLI tool 2026` (any new competitors since v1?)
-2. **WebSearch**: `"<API name>" "I wish" OR "I built" site:reddit.com OR site:news.ycombinator.com` (new pain points?)
-3. Check npm: has anyone published a new CLI for this API?
-4. Check if the API spec has been updated (new endpoints?)
-
-Output: a "what's new" briefing (5-10 bullet points, not a full research document).
-
-**Step 3: GAP ANALYSIS (5 min)** - What are the top 5 improvements?
-
-Compare the audit baseline + re-research against what's possible. Score each potential improvement:
-
-| Improvement | User Impact (1-5) | Score Impact (1-5) | Effort (1-5, 5=easy) | Total |
-|------------|-------------------|-------------------|---------------------|-------|
-
-Pick the top 5. Present to the user for approval before building.
-
-Common improvement categories:
-- Fix broken commands (from verify failures)
-- Add missing workflow commands (from re-research)
-- Improve data layer (add tables, fix sync, add FTS5)
-- Polish README (add cookbook, fix examples)
-- Add new endpoints (from spec updates)
-
-**Step 4: IMPROVE (15 min)** - Build the top 5.
-
-For each approved improvement:
-1. Implement it (delegate to Codex if codex mode)
-2. Run `go build && go vet` to verify compilation
-3. Commit atomically: `feat(<api>): <improvement description>`
-
-**Step 5: RE-VERIFY (5 min)** - Prove it worked.
-
-```bash
-cd ~/cli-printing-press && ./printing-press emboss --dir <cli-dir> --spec <spec-path> --audit-only
-```
-
-Compare the new numbers to the baseline from Step 1.
-
-**Step 6: REPORT** - Tell the user the delta.
-
-```
-EMBOSS COMPLETE: <api>-cli
-  Scorecard: <before> -> <after> (+<delta>)
-  Verify:    <before>% -> <after>% (+<delta>%)
-  Commands:  <before> -> <after> (+<delta>)
-  Pipeline:  <before> -> <after>
-  Top improvements: <list>
-```
-
-### Emboss Phase Gate
-
-The emboss is successful if:
-- Scorecard improved by at least 3 points
-- Verify pass rate improved or stayed the same
-- No new critical failures introduced
-- All improvements compile and pass `go vet`
-
-If verify pass rate DECREASED, something broke. Revert the last improvement and investigate.
-
----
-
-## Codex Mode (Opt-In)
-
-Add `codex` to the command to offload code generation and fix application (Phase 4, 4.5, 4.9, 5.7) to Codex CLI. Claude stays the brain (research, planning, scoring, review). Codex does the hands (writing Go code). Saves ~60% Opus tokens per run.
-
-**Default is OFF.** Standard Opus mode runs unless you explicitly type `codex`.
-
-### Mode Detection
-
-```
-if the user's arguments contain "codex" or "--codex":
-  CODEX_MODE = true
-  Verify: command -v codex >/dev/null 2>&1
-  If codex not installed: print "Codex CLI not found - running standard mode." and set CODEX_MODE = false
-  Guard: if $CODEX_SANDBOX or $CODEX_SESSION_ID is set, print "Already inside Codex sandbox" and set CODEX_MODE = false
-else:
-  CODEX_MODE = false (default)
-```
-
-### Codex Delegation Pattern
-
-When CODEX_MODE is true and a task is pure code generation (writing a Go file, applying a fix):
-
-1. **Claude assembles the prompt** with: task description, exact files to modify, current code context (paste real code), expected change in plain English, conventions from the codebase, and constraints (no git, no PRs, <200 lines, run go build at end)
-
-2. **Write prompt and delegate:**
-```bash
-CODEX_PROMPT="TASK: [1-sentence description]
-
-FILES TO MODIFY:
-- [exact paths]
-
-CURRENT CODE:
-[paste relevant functions/signatures from codebase]
-
-EXPECTED CHANGE:
-[plain English description of the diff]
-
-CONVENTIONS:
-- [commit style, import patterns, error handling from the codebase]
-
-CONSTRAINTS:
-- Do NOT run git commit, git push, or git add. The sandbox blocks .git writes.
-- Do NOT modify files outside the listed paths.
-- Keep changes under 200 lines.
-
-VERIFY: After changes, run: go build ./... && go vet ./..."
-
-cd ~/cli-printing-press && echo "$CODEX_PROMPT" | codex exec --yolo -
-```
-
-3. **Claude reviews the result:** Before anything else, verify the target file still exists and is non-empty (`wc -l <file>`). Codex can delete or empty files instead of rewriting them — if the file is gone or empty, that's an immediate failure. Then verify: in-scope changes, compiles (`go build && go vet`). If lint/format fails, auto-fix.
-
-4. **On failure:** Fall back to Claude for that task. A deleted/emptied file counts as a failure. Track consecutive failures — after 3, disable Codex for remaining tasks.
-
-### What Gets Delegated vs What Stays on Claude
-
-| Delegated to Codex (code generation) | Stays on Claude (reasoning) |
-|---|---|
-| Writing store.go domain tables | Phase 0-1: Research, prediction engine |
-| Writing workflow commands (sync, search, sql, etc.) | Phase 0.7: Architecture decisions |
-| Writing insight commands (health, trends, etc.) | Phase 3: Non-Obvious Insight Review |
-| Applying scorecard fixes (dead code, wiring flags) | Phase 4.7: Proof of Behavior verification |
-| Applying reviewer fixes from Phase 4.9 | Reviewer dispatch, fix prioritization, acceptance decision |
-| README cookbook section | Phase 5: Ship Readiness Assessment |
-| Fix cycle patches (5-50 lines each) | Phase 5.5: Live API Testing |
-
-## Prerequisites
-
-- Go 1.21+ installed
-- The printing-press repo at `~/cli-printing-press`
-- Build binary if missing: `cd ~/cli-printing-press && go build -o ./printing-press ./cmd/printing-press`
-
-## Phase 0.1: API KEY AUTO-DETECTION
-
-Before asking the user for anything, silently check if a token is already available:
-
-```
-1. Check common env vars for the target API:
-   - GitHub: $GITHUB_TOKEN, $GH_TOKEN, or run `gh auth token` if gh CLI is installed
-   - Discord: $DISCORD_TOKEN, $DISCORD_BOT_TOKEN
-   - Linear: $LINEAR_API_KEY
-   - Notion: $NOTION_TOKEN
-   - Stripe: $STRIPE_SECRET_KEY (read-only test key only)
-   - Generic: $API_KEY, $API_TOKEN
-
-2. If a token is found:
-   Use AskUserQuestion to ask:
-   "Found a [API_NAME] token in $[ENV_VAR]. Use it for read-only live testing at the end?"
-   Options:
-   - "Yes, use it" (read-only GETs only, never creates/updates/deletes)
-   - "No, skip live testing" (use dry-run and mock validation only)
-
-   **WAIT for the user's answer before proceeding.** Do NOT continue to Phase 0 until answered.
-
-3. If no token found:
-   Use AskUserQuestion to ask:
-   "No [API_NAME] token detected. Want to provide one for live testing?"
-   Options:
-   - "I'll set it up" (user will paste or export the token, then you re-check)
-   - "Skip, no live testing" (proceed without, use dry-run validation only)
-
-   **WAIT for the user's answer before proceeding.** Do NOT continue to Phase 0 until answered.
-```
-
-The key insight: **detect first, ask permission second, WAIT for the answer.** Don't barrel ahead into research while the user is still deciding. The AskUserQuestion tool blocks until they respond.
-
-## How This Works
-
-Every run produces the GOAT CLI through 9 mandatory phases + 7 comprehensive plan documents:
-
-```
-PHASE 0 -> PHASE 0.5 -> PHASE 0.7 -> PHASE 0.8 -> PHASE 0.9 -> PHASE 1 -> PHASE 2 -> PHASE 3 -> PHASE 4 -> PHASE 4.5 -> PHASE 4.6 -> PHASE 4.8 -> PHASE 4.9 -> PHASE 5
-(3-5m)     (2-3m)       (15-25m)     (5-8m)     (1-2m)     (5-8m)     (5-10m)    (10-20m)      (2-3m)       (5-10m)
-Visionary  Workflows    Prediction   Research   Generate   Audit      Build      Dogfood       Final        Agent
-Research   (commands)   Engine       (specs)    (code)     (review)   (fixes)    Emulation     Quality      Readiness
-                        (data layer)                                             (spec-test)   Score        Review
-```
-
-Total expected time: 50-95 minutes. Phase 4.5 tests every command against spec-derived mocks.
-
-**7 Plan Artifacts Per Run:**
-
-Every phase gate produces a comprehensive plan document in `~/cli-printing-press/docs/plans/`:
-
-```
-Phase 0   -> <today>-feat-<api>-cli-visionary-research.md
-Phase 0.5 -> <today>-feat-<api>-cli-power-user-workflows.md
-Phase 0.7 -> <today>-feat-<api>-cli-data-layer-spec.md
-Phase 1   -> <today>-feat-<api>-cli-research.md
-Phase 3   -> <today>-fix-<api>-cli-audit.md
-Phase 4   -> <today>-fix-<api>-cli-goat-build-log.md
-Phase 4.5 -> <today>-fix-<api>-cli-dogfood-report.md
-```
-
-Each artifact chains into the next. **Read the previous phase's artifact before starting the next phase.**
-
-**The quality bar:** Peter Steinberger's gogcli is the 10/10 reference. Every generated CLI is scored against it TWICE - once during the Non-Obvious Insight Review to find gaps, once in the Ship Readiness Assessment to prove improvement. The delta is the proof of work.
-
-**Grade thresholds (10 dimensions, 100 max):**
-- **Grade A:** 80+/100 (80%)
-- **Grade B:** 65-79/100 (65-79%)
-- **Grade C:** 50-64/100 (50-64%)
-
----
-
-## Artifact Writing: Plan Generation at Each Phase Gate
-
-At the end of each phase, write a comprehensive plan document. This is NOT optional - the artifacts ARE the product.
-
-**Option A: /ce:plan is available (compound-engineering plugin installed)**
-
-Try to invoke the `compound-engineering:ce:plan` skill. If it exists, use it:
-
-```
-Skill tool: compound-engineering:ce:plan
-Args: "<phase description with all research gathered so far>"
-```
-
-The /ce:plan skill produces a full plan document with frontmatter, analysis, acceptance criteria, and sources. Pass it all the research from this phase as the feature description.
-
-**Option B: Built-in plan writer (fallback when compound-engineering is NOT installed)**
-
-If /ce:plan is not available, write the artifact yourself with this structure:
-
-```markdown
----
-title: "<Phase Name>: <API> CLI"
-type: feat
-status: active
-date: <today>
-phase: "<phase number>"
-api: "<api name>"
----
-
-# <Phase Name>: <API> CLI
-
-## Overview
-[2-3 paragraph executive summary of what this phase discovered/decided]
-
-## Analysis
-[Full analysis with tables, scores, evidence URLs, and reasoning]
-[Every claim backed by evidence - WebSearch URLs, star counts, API docs]
-[Scoring breakdowns showing how each number was computed]
-
-## Decisions
-[What was decided and WHY - rationale for each decision]
-[What was rejected and WHY]
-
-## Concrete Outputs
-[SQL schemas, command definitions, sync strategies - real code, not pseudocode]
-[Every output validated against the actual API]
-
-## Acceptance Criteria
-- [ ] [Measurable criteria for this phase's outputs]
-
-## Sources
-- [URLs, file paths, competitor repos with star counts]
-```
-
-**CRITICAL:** The built-in writer must match /ce:plan depth:
-- Full analysis, not bullet summaries
-- Evidence with source URLs, not assertions
-- Scoring breakdowns, not just final numbers
-- Concrete SQL/code examples, not pseudocode
-- Validation proof ("I verified the API supports ?after= filtering"), not assumptions
-- 200+ lines minimum per artifact
-
----
-
-## Workflow: `--spec` shortcut
-
-When the user provides `--spec <path-or-url>`, skip Phase 1 spec search (spec is provided). STILL run competitor research (Steps 1.2-1.5). Run all other phases.
-
-## Workflow: Natural Language (Primary)
-
-When the user provides an API name, run ALL five phases.
-
-### Step 0: Parse intent and check known specs
-
-Extract the API name. Optionally check `~/cli-printing-press/skills/printing-press/references/known-specs.md` for a cached spec URL.
-
-If found in registry: note the URL as a hint for Phase 1, but STILL run full research.
-If not found: Phase 1 searches for the spec. This is the normal path - most APIs won't be in the registry.
-
-**The registry is a speed shortcut, not a gate.** Never refuse to run because an API isn't in the registry. Never hard-block because the registry says "GraphQL" or "Skipped." Phase 1 discovers the spec type dynamically.
-
----
-
-# PHASE 0: VISIONARY RESEARCH
-
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
-
-Before generating any CLI, understand what a thoughtful developer would build - not just what the OpenAPI spec says.
-
-### Step 0a: API Identity & Domain Understanding
-
-Understand what this API IS:
-
-1. **WebFetch** the API's developer docs landing page
-2. **WebSearch**: `"<API name>" developer documentation overview`
-3. Extract:
-   - **Domain category:** messaging, payments, productivity, infrastructure, analytics
-   - **Primary users:** Who uses this API? (e.g., "bot developers", "server admins")
-   - **Core entities:** What are the main objects? (e.g., "guilds", "channels", "messages")
-   - **Data profile:**
-     - Write pattern: append-only, mutable, or event-sourced?
-     - Volume: high (millions of records), medium, or low?
-     - Real-time: does the API have webhooks/websockets/SSE?
-     - Search need: high (users need to find things) or low?
-
-### Step 0b: Usage Pattern Discovery
-
-Discover what people ACTUALLY DO with this API:
-
-**Community Research (run these in parallel):**
-1. **WebSearch**: `"<API name>" CLI workflow site:reddit.com`
-2. **WebSearch**: `"<API name>" automation script site:github.com`
-3. **WebSearch**: `"<API name>" "I built" OR "I made" OR "my tool" site:reddit.com OR site:news.ycombinator.com`
-4. **WebSearch**: `"<API name>" tutorial automation workflow 2025 2026`
-
-**Pain Point Research:**
-5. **WebSearch**: `"<API name>" API "pain point" OR "limitation" OR "workaround"`
-6. **WebSearch**: `site:stackoverflow.com "<API name>" API rate limit OR pagination OR bulk`
-
-From all research, identify the **top 5 usage patterns** ranked by evidence score:
-
-| Source | Weight |
-|---|---|
-| Existing tool with 100+ stars | 3 points |
-| Existing tool with 10-99 stars | 2 points |
-| Reddit/HN post with 50+ upvotes | 2 points |
-| Reddit/HN post with 10-49 upvotes | 1 point |
-| Stack Overflow question with 10+ votes | 1 point |
-| Blog post / tutorial | 1 point |
-| GitHub issue on competitor | 1 point |
-| Cross-platform appearance (same need on 2+ platforms) | +2 bonus |
-
-Score >= 6: Strong evidence. Include in CLI.
-Score 3-5: Moderate evidence. Consider as optional.
-Score < 3: Weak evidence. Skip.
-
-### Step 0c: Tool Landscape Discovery (The Discrawl Finder)
-
-Find ALL tools for this API, not just API wrappers:
-
-**Tier 1: Direct CLI Search** (existing Phase 1 does this too)
-1. **WebSearch**: `"<API name>" CLI tool github`
-
-**Tier 2: Non-Wrapper Tool Search** (CRITICAL - finds discrawl-class tools)
-2. **WebSearch**: `"<API name>" sync OR archive OR mirror site:github.com`
-3. **WebSearch**: `"<API name>" search engine OR analytics OR dashboard site:github.com`
-4. **WebSearch**: `"<API name>" backup OR export OR migration site:github.com`
-5. **WebSearch**: `"<API name>" monitor OR watcher OR alerting site:github.com`
-
-**Tier 3: Ecosystem Search**
-6. **WebSearch**: `awesome "<API name>" site:github.com`
-
-**Tier 4: Market Landscape Search** (CRITICAL - finds the REAL competitive terrain)
-
-The API wrapper is not the only competitor. Developers stack 3-4 tools. Find them all.
-
-7. **WebSearch**: `"<API name>" CLI alternative OR replacement`
-8. **WebSearch**: `"<API domain>" TUI OR terminal tool 2026` (use the domain from Step 0a, e.g., "git" for GitHub, "payments" for Stripe)
-9. **WebSearch**: `best "<API domain>" workflow tool`
-10. **WebSearch**: `"<API name>" "I switched to" OR "better than"`
-
-Classify the landscape into lanes:
-- **Forge/Platform CLIs** - direct API wrappers (gh, glab, stripe-cli)
-- **Workflow Overlays** - higher-level tools on top (Graphite, Git Town)
-- **Alternative UX** - rethink the domain interaction (lazygit, jj, Warp)
-
-Most developers stack tools from multiple lanes. Our CLI should complement the incumbent, not compete head-on.
-
-For each tool found, classify it:
-
-| Type | Description | Example |
-|---|---|---|
-| **API Wrapper** | Translates HTTP to CLI flags | discli |
-| **Data Tool** | Adds local persistence/search | discrawl |
-| **Workflow Tool** | Orchestrates multi-step sequences | Stripe fixtures |
-| **Environment Tool** | Runs local simulation | Supabase CLI |
-| **Integration Tool** | Bridges to other systems | Zapier integration |
-
-**The press should generate CLIs that compete with Data Tools and Workflow Tools, not just API Wrappers.**
-
-### Step 0d: Workflow Analysis
-
-From usage patterns (0b) and tool landscape (0c), identify multi-step workflows:
-
-For each workflow, document:
-- **Steps:** The sequence of API calls
-- **Frequency:** How often users perform this
-- **Pain point:** What makes this hard with the raw API
-- **Proposed CLI feature:** What compound command would solve it
-
-### Step 0e: Architecture Planning
-
-Based on data profile and workflows, decide what the CLI needs:
-
-| Data Profile | Architecture |
-|---|---|
-| High volume + search need | SQLite + FTS5 (Discord, Slack) |
-| Transaction data + reconciliation | Local ledger with diff tracking (Stripe, Plaid) |
-| Document data + offline editing | Local Markdown/JSON sync (Notion, Confluence) |
-| Low volume + simple CRUD | Standard API wrapper is fine (most APIs) |
-
-For each decision area (persistence, real-time, search, bulk, caching), document:
-- **Need level:** High / Medium / Low
-- **Decision:** What to use
-- **Rationale:** Why
-
-### Step 0f: Feature Ideation - "Next 5 Features for the World"
-
-Score each feature idea on 8 dimensions (16-point max):
-
-| Dimension | Weight | Scoring |
-|---|---|---|
-| **Evidence strength** | 3 | 3=existing tool 100+ stars, 2=Reddit/SO demand, 1=weak, 0=speculation |
-| **User impact** | 3 | 3=most users feel this pain, 2=niche, 1=nice-to-have, 0=nobody asked |
-| **Implementation feasibility** | 2 | 2=can generate template, 1=needs custom code, 0=major infrastructure |
-| **Uniqueness** | 2 | 2=no existing tool, 1=improves on existing, 0=already well-served |
-| **Composability** | 2 | 2=great with pipes/agents, 1=somewhat, 0=interactive-only |
-| **Data profile fit** | 2 | 2=perfect fit, 1=possible, 0=wrong shape |
-| **Maintainability** | 1 | 1=generated code supports it, 0=needs human maintenance |
-| **Competitive moat** | 1 | 1=hard to replicate, 0=trivial |
-
-Score >= 12: **Must-have.** Build it.
-Score 8-11: **Should-have.** Include as optional.
-Score < 8: **Won't-have.** Skip or future work.
-
-### Step 0g: Write the Visionary Research Artifact
-
-**Write** to `~/cli-printing-press/docs/plans/<today>-feat-<api>-cli-visionary-research.md`:
-
-```markdown
-## Visionary Research: <API> CLI
-
-### API Identity
-- Domain: <category>
-- Primary users: <who>
-- Data profile: <write pattern>, <volume>, <realtime>, <search need>
-
-### Usage Patterns (Top 5 by Evidence)
-1. <pattern> (Evidence: X/10) - <what it needs>
-2. ...
-
-### Tool Landscape (Beyond API Wrappers)
-- <tool> (<stars> stars): <what it does>
-- ...
-
-### Workflows
-1. <name>: <steps> -> Proposed: `<api>-cli <command>`
-2. ...
-
-### Architecture Decisions
-- Persistence: <decision> because <rationale>
-- Real-time: <decision> because <rationale>
-- Search: <decision> because <rationale>
-- Bulk: <decision> because <rationale>
-- Cache: <decision> because <rationale>
-
-### Top 5 Features for the World
-1. <feature> (Score: X/16) - <1-line description>
-2. ...
-```
-
-### PHASE GATE 0
-
-**STOP.** Verify ALL of these before proceeding:
-1. API Identity documented with data profile
-2. At least 3 usage patterns with evidence scores
-3. Tool landscape includes non-wrapper tools (Tier 2 search done)
-4. At least 2 workflows with proposed CLI features
-5. Architecture decisions match data profile
-6. Top 5 features scored and ranked
-
-**Write Phase 0 Artifact:** Run the Artifact Writing plan generator (see top of skill) with all Phase 0 research as input. Write to `~/cli-printing-press/docs/plans/<today>-feat-<api>-cli-visionary-research.md`. Include: API identity, data profile, usage patterns with evidence, tool landscape, architecture decisions, top 5 features with full scoring.
-
-Tell the user: "Phase 0 complete: Domain: [category]. Data profile: [volume]/[realtime]/[search]. Found [N] non-wrapper tools. Top feature: [name] (score [X]/16). Architecture: [key decision]. Proceeding to power user workflows."
-
----
-
-# PHASE 0.5: POWER USER WORKFLOWS
-
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
-
-The generator produces API wrappers. Power users want workflow tools. This phase predicts what compound commands would make the CLI genuinely useful - the kind of features that make discrawl (12 commands) more valuable than a 316-command API wrapper.
-
-### Step 0.5a: Classify the API Archetype
-
-Based on Phase 0 research, classify the API:
-
-| Archetype | Signal | Example Workflows |
-|---|---|---|
-| **Communication** | Messages, channels, threads | Archive, offline search, monitor keywords, export conversations |
-| **Project Management** | Issues, tasks, sprints, states | Stale issues, orphan detection, velocity, burndown, standup, triage |
-| **Payments** | Charges, subscriptions, invoices | Reconciliation, webhook replay, fixture flows, revenue reports |
-| **Infrastructure** | Servers, deployments, logs | State sync, log tailing, deploy orchestration, health dashboards |
-| **Content** | Documents, pages, blocks, media | Backup to local files, diff, template management, publish workflows |
-| **CRM** | Contacts, deals, pipelines | Pipeline reports, stale deal alerts, activity timelines, bulk updates |
-| **Developer Platform** | Repos, PRs, CI runs | PR triage, CI monitoring, release management, dependency audit |
-
-### Step 0.5b: Generate 10-15 Workflow Ideas
-
-For the identified archetype, brainstorm compound workflows. Ask:
-- "What does a power user of this API wish they could do in one command?"
-- "What multi-step task do people automate with scripts today?"
-- "What reporting/hygiene/monitoring task requires manual effort?"
-- "What would make an engineering manager's life easier?"
-
-Each workflow should:
-- Combine 2+ API calls into one operation
-- Solve a real recurring problem
-- Be expressible as a single CLI command with flags
-
-### Step 0.5c: Validate Against API Capabilities
-
-For each workflow idea, check:
-1. Does the API have the required endpoints/fields?
-2. Can the required data be queried/filtered?
-3. Are write operations available (for mutation workflows)?
-4. For GraphQL APIs: does the schema have the required types?
-
-Drop workflows the API can't support.
-
-### Step 0.5d: Rank by Impact
-
-Score each workflow on:
-- **Frequency**: How often would users run this? (daily=3, weekly=2, monthly=1)
-- **Pain**: How painful is the manual alternative? (high=3, medium=2, low=1)
-- **Feasibility**: How hard to implement? (easy=3, medium=2, hard=1)
-- **Uniqueness**: Does any existing tool do this? (no=3, partial=2, yes=0)
-
-### Step 0.5e: Select Top 5-7 for Implementation
-
-These become **mandatory Phase 4 work items**. They are NOT optional polish. They are the PRODUCT.
-
-### Step 0.5f: Naming Pass (User Outcomes, Not API Resources)
-
-For each selected workflow, rename it from API-speak to user-speak. The name should complete this sentence: **"I need to check ___"**
-
-| API-oriented (bad) | User-oriented (good) | Why |
-|---|---|---|
-| `actions-health` | `ci-health` or `flaky` | Users say "is CI flaky?" not "are actions healthy?" |
-| `contributors` | `leaderboard` or `who-shipped` | The question being answered |
-| `activity` | `standup` | The workflow it serves |
-
-Rules:
-- Names should be verbs or nouns a developer would type naturally
-- If the incumbent has a name for this concept, use a different name (don't collide)
-- Max 15 characters, no hyphens if possible
-- Test: "Would an engineering manager type this without reading --help first?"
-
-### PHASE GATE 0.5
-
-**STOP.** Tell the user: "Identified [N] power-user workflows for [API name]. Top 5:
-1. [name] - [one-line description] (score [X]/12)
-2. ...
-These will be built as real commands in Phase 4, alongside the API wrapper."
-
-**Write Phase 0.5 Artifact:** Run the Artifact Writing plan generator (see top of skill) with all Phase 0.5 analysis as input. Write to `~/cli-printing-press/docs/plans/<today>-feat-<api>-cli-power-user-workflows.md`. Include: API archetype, all 10-15 workflow ideas, validation results, full scoring table, top 7 with implementation notes.
-
----
-
-# PHASE 0.6: FEATURE PARITY AUDIT
-
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
-
-Before brainstorming novel features, catalog what the competition already ships. This is the missing link between research and build. Phase 0.5 brainstorms additive workflows. This phase checks if we can do what the incumbent already does.
-
-### Step 0.6a: Feature Matrix
-
-For the top 2 competitors by stars (from Phase 0/1 research):
-
-| Feature | Competitor A | Competitor B | Ours | Classification |
-|---------|-------------|-------------|------|----------------|
-
-List EVERY command and feature they offer. Read their README, --help output, and documentation.
-
-### Step 0.6b: Classify Each Feature
-
-- **TABLE STAKES**: >50% of users expect any CLI for this API to have it.
-  Examples: `issue create`, `page get`, git branch from issue, clean CRUD names (get/create/update/delete)
-- **NICE-TO-HAVE**: Useful but not expected. Won't lose users if missing.
-  Examples: interactive prompts, TUI mode, plugins
-- **ANTI-SCOPE**: Genuinely out of scope with justification.
-  Examples: full TUI, mobile app, GUI
-
-**Classification rules:**
-- If ANY competitor with >100 stars has it, it's TABLE STAKES unless you provide an explicit reason it's anti-scope.
-- If users mention it in issues/Reddit with >10 upvotes, it's TABLE STAKES.
-- "Complements the incumbent" is NOT a reason to skip a feature. Users don't want to install two CLIs.
-
-### Step 0.6c: Table Stakes Become Phase 4 Mandatory Work
-
-Every TABLE STAKES feature becomes a Phase 4 Priority 1 work item. They are built ALONGSIDE the data layer, not instead of it.
-
-### PHASE GATE 0.6
-
-**STOP.** Verify:
-1. Feature matrix complete for top 2 competitors
-2. Every feature classified as TABLE STAKES / NICE-TO-HAVE / ANTI-SCOPE
-3. TABLE STAKES list has at least 3 items
-4. Anti-scope items have explicit cost analysis ("What % of users need this? Does a competitor with >100 stars offer it?")
-
-Tell the user: "Feature parity audit: [N] table-stakes features identified from [competitor names]. Top gaps: [list]. These will be built in Phase 4."
-
-**Write Phase 0.6 Artifact:** Run the Artifact Writing plan generator with all Phase 0.6 analysis as input. Write to `~/cli-printing-press/docs/plans/<today>-feat-<api>-cli-feature-parity-audit.md`. Include: full feature matrix, classifications with justification, table stakes list for Phase 4.
-
----
-
-# PHASE 0.7: POWER USER PREDICTION ENGINE
-
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
-
-The generator produces API wrappers. Power users want a local data layer - domain-specific SQLite tables, incremental sync, full-text search with domain filters, raw SQL access, and trend detection. This phase predicts that data layer from the API surface + social signals, WITHOUT looking at competitors (that's Phase 1's job).
-
-**Read the Phase 0 and Phase 0.5 artifacts before starting this phase.**
-
-### Step 0.7a: Entity Classification
-
-Map every API resource into one of four types by reading the OpenAPI spec or Phase 0's entity list:
-
-| Type | Signal | Example | Persistence Need |
-|---|---|---|---|
-| **Accumulating** | Grows over time, has timestamps, paginated lists | Messages, Issues, Audit Logs, Commits | SQLite table + incremental sync |
-| **Reference** | Changes rarely, small cardinality, referenced by other entities | Users, Teams, Roles, Labels, Channels | SQLite table + periodic refresh |
-| **Append-only** | Never edited, only created | Events, Webhooks, Notifications | SQLite table + tail command |
-| **Ephemeral** | Short-lived, not worth persisting | OAuth tokens, Rate limit status, Gateway info | API-only, no persistence |
-
-**Heuristics:**
-- Has `created_at`/`timestamp` + paginated list endpoint -> Accumulating
-- Referenced by 3+ other entities via `_id` fields -> Reference
-- Has no UPDATE/PATCH endpoint -> Append-only
-- No list endpoint or < 100 expected records -> Ephemeral
-- Has `updated_at` or `modified_at` -> needs incremental sync cursor
-
-**Output:** Entity classification table with type, estimated volume, update frequency, and key temporal field for ALL API resources.
-
-### Step 0.7b: Social Signal Mining for Data Patterns
-
-Find evidence of what data power users actually store locally. Run 7 parallel WebSearches:
-
-1. **WebSearch**: `"<API name>" export OR backup OR archive site:github.com`
-2. **WebSearch**: `"<API name>" SQLite OR database OR local site:github.com`
-3. **WebSearch**: `"<API name>" analytics OR dashboard OR metrics site:github.com`
-4. **WebSearch**: `"<API name>" "I wish" OR "would be nice" OR "feature request" data`
-5. **WebSearch**: `"<API name>" offline OR search OR "full text" site:reddit.com OR site:news.ycombinator.com`
-6. **WebSearch**: `"<API name>" trend OR pattern OR anomaly detection`
-7. **WebSearch**: `"<API name>" graph OR visualization OR dependency`
-
-**For each finding, extract:**
-- What entities they store locally
-- What queries they run (joins, aggregations, time filters)
-- What temporal patterns they track (trends, anomalies, velocity)
-- What cross-entity relationships they need
-
-**Score using Phase 0 evidence framework.** Anything with score >= 6 informs the data layer.
-
-### Step 0.7c: Data Gravity Scoring
-
-Rank entities by how much value they'd have in a local SQLite database.
-
-**Formula:** `DataGravity = Volume(0-3) + QueryFrequency(0-3) + JoinDemand(0-2) + SearchNeed(0-2) + TemporalValue(0-2)`
-
-| Factor | 0 | 1 | 2 | 3 |
-|---|---|---|---|---|
-| **Volume** | < 100 records | 100-10k | 10k-1M | > 1M |
-| **QueryFrequency** | Rarely queried | Monthly | Weekly | Daily |
-| **JoinDemand** | No references | 1-2 entities reference it | 3-4 | 5+ |
-| **SearchNeed** | No text fields | 1 text field | 2-3 text fields | Primary text content |
-| **TemporalValue** | No time dimension | Created date only | Updated + trends | Core to time-series analysis |
-
-**Thresholds:**
-- Score >= 8: **Primary entity** - gets its own SQLite table with proper columns, FTS5 if text-heavy
-- Score 5-7: **Support entity** - gets a simpler table
-- Score < 5: **API-only** - no local persistence
-
-Score EVERY entity from Step 0.7a. Show the full breakdown.
-
-### Step 0.7d: Schema + Sync + Search Specification
-
-For each Primary entity (score >= 8), produce:
-
-**1. SQLite Schema:**
-- Extract columns from the API's response schema (NOT just id + JSON blob)
-- Include foreign key columns for joins (e.g., `channel_id`, `author_id`)
-- Include the temporal field for sync cursors
-- Add indexes on foreign keys and temporal fields
-- Create FTS5 virtual table on text fields (title, description, content, body, name)
-- Keep a `data JSON NOT NULL` column for the full API response
-
-**2. Sync Strategy:**
-- Identify the incremental sync cursor (timestamp field, snowflake ID, cursor pagination)
-- **VALIDATE:** Check that the API supports filtering by this cursor - look for `since`, `after`, `updated_after`, `before` query params in the spec
-- Determine batch size from API's max `limit` parameter
-- Check if API has WebSocket/SSE/Gateway - note for tail command
-- If the API doesn't support cursor filtering, fall back to full sync + local dedup
-
-**3. Search Specification:**
-- List which text fields to extract into FTS5
-- Define domain-specific search filters as SQL WHERE clauses
-- Map CLI flags to SQL: `--channel` -> `WHERE channel_id = ?`, `--author` -> `WHERE author_id = ?`
-- **VALIDATE:** Confirm these fields actually appear in API list/get responses
-
-**4. Compound Queries:**
-- Define 3-5 cross-entity queries (e.g., "messages by author in channel in last N days")
-- Validate join columns exist in both tables
-- These become Phase 4 workflow commands that use local DB instead of live API
-
-**5. Tail Strategy:**
-
-| Method | When to Use |
-|---|---|
-| WebSocket/Gateway | API has it (Discord Gateway, Slack RTM) |
-| SSE | API has it (GitHub, Linear webhooks) |
-| REST Polling | Fallback - GET with ?since= cursor |
-
-Decide which method this API supports. If WebSocket/SSE, the tail command should use it instead of REST polling.
-
-### Step 0.7e: Write the Data Layer Specification Artifact
-
-**Run the Artifact Writing plan generator** (see top of skill) with all Phase 0.7 analysis as input. Write to `~/cli-printing-press/docs/plans/<today>-feat-<api>-cli-data-layer-spec.md`.
-
-The artifact MUST include:
-- Entity classification table for every API resource
-- Data gravity scores with full breakdown per entity
-- Complete SQLite schema (CREATE TABLE + CREATE INDEX + FTS5)
-- Sync strategy with cursor validation proof
-- Domain-specific search filters mapped to SQL WHERE clauses
-- 3-5 compound cross-entity queries
-- Tail strategy decision with justification
-- Commands to build in Phase 4 Priority 0
-
-### PHASE GATE 0.7
-
-**STOP.** Verify ALL of these before proceeding:
-1. Entity classification table with type and volume estimates for every API resource
-2. At least 3 social signals with evidence scores >= 6
-3. Data gravity scores computed, with >= 1 primary entity (score >= 8)
-4. SQLite schema with proper columns (NOT generic JSON blobs) for each primary entity
-5. FTS5 virtual tables for entities with text fields
-6. Sync strategy with cursor fields validated against actual API filter params
-7. Domain-specific search filters mapped to SQL WHERE clauses
-8. At least 3 compound queries validated (joins work, columns exist)
-
-Tell the user: "Phase 0.7 complete: [N] primary entities for SQLite ([list]), [M] compound queries validated. Sync via [cursor type]. FTS5 on [fields]. Key prediction: [most valuable data-layer feature]. Proceeding to product thesis."
-
----
-
-# PHASE 0.8: PRODUCT THESIS
-
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
-
-Before generating any code, articulate why someone would install this CLI. If you can't answer these five questions in one sentence each, the research phases missed something - go back.
-
-### Step 0.8a: Answer Five Questions
-
-1. **Who is this for?** (one sentence, specific persona)
-   - Bad: "Developers who use the GitHub API"
-   - Good: "Engineering managers who need cross-repo PR triage without a dashboard"
-
-2. **What's the comparison table?** (us vs incumbent, 5 rows minimum)
-   | Capability | Incumbent | Ours |
-   |-----------|-----------|------|
-   Fill this in with real capabilities from Phase 0 research.
-
-3. **What's the HN headline?** (one sentence that makes a developer click)
-   - Bad: "A new CLI for the GitHub API"
-   - Good: "I built a GitHub CLI that finds stale PRs and lets you SQL query your repos offline"
-
-4. **What's the name?**
-   - DEFAULT: `<api>-pp-cli` (e.g., `notion-pp-cli`, `linear-pp-cli`, `stripe-pp-cli`)
-   - The `-pp-` identifies it as a printing press product.
-   - This is discoverable (`brew search notion` finds `notion-pp-cli`).
-   - Creative names are allowed ONLY if the user explicitly requests one.
-   - The printing press is a code generator, not a branding agency.
-
-5. **What's the anti-scope and what does it cost?**
-
-   For each anti-scope item, answer:
-   - What % of potential users need this feature?
-   - Does any competitor with >100 stars offer it?
-   - If yes: this is NOT anti-scope, it's a backlog item. Move to Phase 4 Priority 1.
-
-   VALID anti-scope: "Not a TUI" (no competitor offers one either)
-   INVALID anti-scope: "Not a git integration" (the top competitor's killer feature - you just excluded the #1 reason people install it)
-
-### Step 0.8b: Write the Product Thesis
-
-Write one paragraph that combines the answers above. This paragraph should make a developer say "I need this." It goes in the README later.
-
-### PHASE GATE 0.8
-
-**STOP.** Verify:
-1. All 5 questions answered with specific, non-generic answers
-2. Product thesis paragraph written
-3. Name set to `<api>-pp-cli` (default) unless user specified otherwise
-4. Comparison table has at least one row where we clearly win
-
-Tell the user: "Product thesis: [1-sentence pitch]. Name: [name]. Key differentiator: [comparison table winner]. Proceeding to deep research."
-
----
-
-# PHASE 0.9: CHECK FOR PRIOR RESEARCH
-
-Before starting Phase 1 research from scratch, check if the user already did research:
-
-```bash
-ls ~/cli-printing-press/docs/plans/*<api-name>* ~/docs/plans/*<api-name>* 2>/dev/null
-```
-
-If found:
-1. **Read** every matching plan document
-2. Extract: competitive landscape, user pain points, tool rankings, product positioning
-3. **Skip redundant Phase 1 research** - if the prior plan already covers competitor analysis and demand signals, don't re-search. Focus Phase 1 on filling gaps (spec discovery, auth method, endpoint count).
-4. Note which Phase 1 steps are already answered by prior research.
-
-If not found: proceed to Phase 1 normally.
-
----
-
-# PHASE 1: DEEP RESEARCH
-
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
-
-Research the API landscape deeply. You need to understand the competitive terrain, user pain points, and strategic opportunity before generating anything.
-
-### Step 1.1: Search for the API spec
-
-Search for BOTH REST and GraphQL specs. Don't assume the API type - discover it.
-
-1. **WebSearch**: `"<API name>" openapi spec site:github.com`
-2. **WebSearch**: `"<API name>" openapi.yaml OR openapi.json specification`
-3. **WebSearch**: `"<API name>" graphql schema site:github.com`
-4. **WebSearch**: `"<API name>" API documentation developer reference`
-5. Try common URL patterns for the API docs landing page
-6. If a spec URL is found, **WebFetch** first 500 bytes to determine type:
-   - Starts with `{"openapi":` or `openapi:` -> OpenAPI/REST
-   - Contains `type Query {` or `schema {` -> GraphQL SDL
-   - Contains `"__schema"` -> GraphQL introspection result
-
-**Record the API type** (REST, GraphQL, or hybrid) for Phase 2's type check.
-
-If no spec found: plan to write one from docs in Phase 2. For GraphQL APIs, the developer docs or a schema URL fetched via introspection serves the same role as an OpenAPI spec - it defines entities, fields, types, and relationships.
-
-**Never refuse to proceed because you can't find a spec.** Write one from docs, or use GraphQL mode.
-
-### Step 1.2: Search for competing CLIs
-
-**WebSearch**: `"<API name>" CLI tool github`
-**WebSearch**: `"<API name>" command line client`
-
-Also search for non-wrapper tools discovered in Phase 0:
-**WebSearch**: `"<API name>" sync OR archive OR export site:github.com`
-
-For each competitor found, note repo URL, star count, language.
-
-### Step 1.3: Deep competitor analysis (TOP 2 competitors)
-
-For the top 2 competitors by stars, do ALL of the following:
-
-1. **WebFetch** their README - count commands, note features, assess quality
-2. **WebFetch** their GitHub repo main page - check:
-   - Last commit date (is it maintained?)
-   - Open issue count
-   - Number of contributors
-3. **WebSearch**: `site:github.com/<org>/<repo>/issues` - look for:
-   - User complaints about missing features
-   - Requests for specific functionality
-   - Pain points users report
-4. Record at least 2 specific user quotes or pain points.
-
-### Step 1.4: Check demand signals
-
-**WebSearch**: `"<API name>" "need a CLI" OR "command line" site:reddit.com OR site:news.ycombinator.com`
-
-### Step 1.5: Strategic justification
-
-Answer this question explicitly: **"Why should this CLI exist when [best competitor] already has [N] stars?"**
-
-The answer must be SPECIFIC. Not just "agent-native." Examples:
-- "[Competitor] hasn't been updated since [date] and doesn't support the latest API version"
-- "No existing CLI supports --json + --select + --dry-run for agent workflows"
-- "Users on [platform] are asking for [specific feature] which no CLI provides"
-- "[Competitor] has [N] open issues about [problem] we can solve"
-
-### Step 1.6: Write the research artifact
-
-**Write** to `~/cli-printing-press/docs/plans/<today>-feat-<api>-cli-research.md`:
-
-```markdown
----
-title: "Research: <API> CLI"
-type: feat
-status: active
-date: <today>
----
-
-# Research: <API> CLI
-
-## Spec Discovery
-- Official OpenAPI spec: <url or "none found - will write from docs">
-- Source: <where found>
-- Format: <OpenAPI 3.x / Swagger 2.0 / internal YAML>
-- Endpoint count: <N>
-
-## Competitors (Deep Analysis)
-
-### <Competitor 1> (<stars> stars)
-- Repo: <url>
-- Language: <lang>
-- Commands: <count>
-- Last commit: <date>
-- Open issues: <count>
-- Maintained: <yes/no>
-- Notable features: <list>
-- Weaknesses: <what users complain about>
-
-### <Competitor 2> (<stars> stars)
-- [same structure]
-
-## User Pain Points
-> "<quote from GitHub issue or Reddit>" - <source>
-> "<quote>" - <source>
-
-## Auth Method
-- Type: <api_key / oauth2 / bearer_token>
-- Env var convention: <what competitors use>
-
-## Demand Signals
-- <specific posts with URLs, or "none found">
-
-## Strategic Justification
-**Why this CLI should exist:** <specific answer, not just "agent-native">
-
-## Target
-- Command count: <N - match or beat best competitor>
-- Key differentiator: <specific features we'll have that competitors don't>
-- Quality bar: Quality Grade A (80+/100)
-```
-
-### PHASE GATE 1
-
-**STOP.** Verify ALL of these before proceeding:
-1. Research artifact exists with Spec Discovery section
-2. At least 2 competitors analyzed with maintenance status
-3. At least 2 user quotes or pain points documented
-4. Strategic justification answers "why should this exist?"
-5. Target command count is set
-
-**Write Phase 1 Artifact:** Run the Artifact Writing plan generator with all Phase 1 research as input. Write to `~/cli-printing-press/docs/plans/<today>-feat-<api>-cli-research.md`. Include: spec discovery, deep competitor analysis with quotes, demand signals, strategic justification, target command count.
-
-Tell the user: "Phase 1 complete: Found [spec/no spec], [N] competitors. Best: [name] ([stars] stars, [commands] commands, last commit [date]). Strategic angle: [1-sentence justification]. Proceeding to generation."
-
----
-
-# PHASE 2: GENERATE
-
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
-
-### Step 2.0: API Type Check
-
-Before generating, verify the spec matches the API:
-
-1. **If spec is OpenAPI/Swagger** -> proceed to REST generation (Step 2.1)
-
-2. **If spec is a GraphQL schema or the API is GraphQL-only** -> GRAPHQL MODE:
-
-   Tell the user: "This API is GraphQL-only. The generator produces REST scaffolding, so Phase 2 will create the project structure (go.mod, client, config, helpers) but skip endpoint generation. All commands will be hand-written in Phase 4 using a GraphQL client. Phases 0, 0.5, 0.7, 1, 3, 4, 4.5, and 5 run normally - the research, prediction engine, data layer, workflows, dogfood, and scoring are all API-type-agnostic."
-
-   **For GraphQL APIs, Phase 2 produces scaffolding only:**
-   - Create the project directory structure (`cmd/`, `internal/cli/`, `internal/client/`, `internal/config/`, `internal/store/`)
-   - Generate `go.mod` with cobra + SQLite dependencies + a GraphQL client (`github.com/hasura/go-graphql-client` or `github.com/machinebox/graphql`)
-   - Generate `root.go` with global flags (--json, --select, --dry-run, --stdin, --yes, --no-cache)
-   - Generate `config.go` with auth handling (API key via env var)
-   - Generate `client.go` with a GraphQL client wrapper (POST to the single endpoint with query/variables)
-   - Generate `helpers.go`, `doctor.go`, `auth.go`, `store.go` from templates
-   - Generate `main.go`
-   - DO NOT generate per-endpoint command files (there are no REST endpoints)
-
-   **The GraphQL client wrapper should look like:**
-   ```go
-   func (c *Client) Query(query string, variables map[string]any) (json.RawMessage, error) {
-       body := map[string]any{"query": query, "variables": variables}
-       return c.do("POST", "/graphql", nil, body)
-   }
-   ```
-
-   **Then proceed to Phase 2 Gate.** Phase 4 will build all commands by hand using the GraphQL schema.
-
-3. **If the spec describes REST endpoints but the API base URL contains `/graphql`** ->
-   Warn: "The spec describes REST endpoints but the API appears to be GraphQL. Double-check which is authoritative. If the REST spec is valid, proceed with REST generation. If GraphQL is the real API, switch to GraphQL mode."
-
-### Step 2.0b: Validate Module Path
-
-Before running the generator, check the module path it will use:
-
-1. Run: `git config user.name` - this becomes the org in the module path
-2. If this doesn't match your actual GitHub username, note the correct one
-3. The module path should be: `github.com/<correct-github-username>/<product-name-from-phase-0.8>`
-4. After generation, verify: `head -1 <api>-pp-cli/go.mod`
-5. If wrong, fix with:
-   ```bash
-   cd <api>-pp-cli
-   go mod edit -module github.com/<org>/<name>
-   find . -name '*.go' -exec sed -i '' "s|<old-module>|<new-module>|g" {} +
-   go build ./...
-   ```
-
-### Step 2.1: Get the spec ready
-
-**If OpenAPI spec found:**
-```bash
-curl -sL -o /tmp/printing-press-spec-<api>.json "<spec-url>" && head -c 200 /tmp/printing-press-spec-<api>.json
-```
-
-**If no spec (write from docs):**
-1. **WebFetch** the API docs
-2. **Read** `~/cli-printing-press/skills/printing-press/references/spec-format.md`
-3. Write YAML spec to `/tmp/<api>-spec.yaml` with ALL endpoints
-4. Include auth config matching research findings
-
-### Step 2.2: Check for existing output, remove if exists
-
-```bash
-cd ~/cli-printing-press && rm -rf library/<api>-cli 2>/dev/null; echo "CLEAN"
-```
-
-### Step 2.3: Run the generator
-
-```bash
-cd ~/cli-printing-press && ./printing-press generate \
-  --spec /tmp/printing-press-spec-<api>.json \
-  --output ./library/<api>-cli \
-  --force --lenient --validate 2>&1
-```
-
-### Step 2.4: Note skipped complex body fields
-
-**IMPORTANT:** When the generator outputs "warning: skipping body field X: complex type not supported", note EVERY skipped field. You will handle these in Phase 4.
-
-Run:
-```bash
-cd ~/cli-printing-press && ./printing-press generate --spec /tmp/printing-press-spec-<api>.json --output ./library/<api>-cli --force --lenient --validate 2>&1 | grep "skipping body field"
-```
-
-Save the list of skipped fields. These are NOT acceptable limitations - they are work items for Phase 4.
-
-### Step 2.5: Handle quality gate failures
-
-Max 3 retries. Read errors carefully and fix spec issues.
-
-### Step 2.7: Validate API Version Header
-
-The generated client may pin to an outdated API version. Fix it:
-
-1. Check what version the API docs say to use (search the API's changelog or docs)
-2. Check the generated client's version header:
-   ```bash
-   grep -n "Version" <api>-pp-cli/internal/client/client.go
-   ```
-3. If the API uses date-based version headers (like Notion, Stripe), use the LATEST documented version, not the spec's version field or the generator's template default.
-4. **Check for per-endpoint versioning.** Some APIs (e.g., Cal.com) use different version headers per resource — bookings may require `2024-08-13` while event-types requires `2024-06-14`. Test at least 2 different resource endpoints with the same version header. If one returns 404 or errors while the other succeeds, the API uses per-endpoint versioning. Implement routing logic in client.go:
-   ```go
-   apiVersion := "2024-06-14" // default
-   if strings.Contains(path, "/bookings") || strings.Contains(path, "/slots") {
-       apiVersion = "2024-08-13"
-   }
-   req.Header.Set("cal-api-version", apiVersion)
-   ```
-5. Update the header in client.go.
-6. **Test with the live API** (if token available from Phase 0.1) to confirm the version header is accepted on at least 2 different resource types.
-
-**Anti-shortcut:** "The generator's default is fine" - NO. Check it. "One version header works for all endpoints" — maybe not. Test two different resources.
-
-### Step 2.8: Validate Config Env Var Matches Phase 0.1
-
-The generator creates a config.go that looks for a specific env var name (e.g., `CAL_COM_USER_TOKEN`). This may not match the env var detected in Phase 0.1 (e.g., `CAL_COM_API_KEY`). If they don't match, live testing will fail with "auth: not configured" even though the user has the key set.
-
-1. Check what env var name(s) Phase 0.1 detected or the user provided
-2. Check what env var name config.go looks for: `grep "Getenv" <api>-pp-cli/internal/config/config.go`
-3. If they differ: patch config.go to accept both names (check the common one first)
-4. Also add any well-known env var names for this API (e.g., for Cal.com: `CAL_COM_API_KEY`, `CAL_API_KEY`, `CALCOM_API_KEY`)
-
-### Step 2.9: Smoke Test (if API key available)
-
-If an API key was provided in Phase 0.1, run a quick smoke test NOW — don't wait until Phase 5.5. This catches auth and version header issues before investing in Phase 3-4.
-
-1. Build the CLI: `go build -o ./<product-name> ./cmd/<product-name>`
-2. Run: `<product-name> doctor --json` — verify auth shows "configured"
-3. Run: `<product-name> me <get-subcommand> --json` or equivalent profile endpoint — verify HTTP 200
-4. Run one list endpoint for a different resource — verify no 404 (catches per-endpoint versioning issues)
-5. If any test fails: fix the issue (version header, env var, base URL) before proceeding
-
-This takes 30 seconds and can save hours of debugging in Phase 4.
-
-### PHASE GATE 2
-
-**STOP.** Verify:
-1. CLI directory exists
-2. `go build ./...` succeeds
-3. List of skipped complex body fields is saved for Phase 3
-4. Module path is correct (Step 2.0b)
-5. API version header is current (Step 2.7)
-6. Config env var matches Phase 0.1 detection (Step 2.8)
-7. Smoke test passes if API key available (Step 2.9)
-
-Tell the user: "Phase 2 complete: Generated <api>-pp-cli with [N] resources, [M] endpoints. [K] complex body fields noted for Phase 4. Proceeding to Non-Obvious Insight Review."
-
----
-
-# PHASE 3: NON-OBVIOUS INSIGHT REVIEW
-
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
-
-This phase has TWO parts: (A) code review for tactical fixes, and (B) Non-Obvious Insight Review for strategic assessment. Both are required.
-
-## Part A: Code Review
-
-### Step 3.1: Read the generated code
-
-You MUST **Read** these files (not just check they exist):
-
-- `library/<api>-cli/internal/cli/root.go`
-- `library/<api>-cli/README.md`
-- At least 3 resource command files
-
-### Step 3.2: Count commands and compare to target
-
-```bash
-cd ~/cli-printing-press/library/<api>-cli && grep -r "Use:" internal/cli/*.go | grep -v "root.go" | wc -l
-```
-
-Compare against target from Phase 1 research.
-
-### Step 3.3: Check help text quality
-
-- Descriptions: developer-friendly or raw spec jargon?
-- Examples: realistic values or placeholder garbage ("string", "0", "abc123")?
-- Root command: does it explain what the API does?
-
-### Step 3.4: Check for missing endpoints
-
-Compare spec endpoints against generated commands. Note any gaps.
-
-### Step 3.5: Check agent-native features
-
-Verify in root.go: --json, --select, --dry-run, --stdin, --yes, --no-cache, doctor.
-
-### Step 3.6: Check complex body fields
-
-For each field skipped by the generator (from Phase 2 Step 2.4):
-1. Is this field critical for the endpoint's purpose?
-2. Can the user work around it with `--stdin`?
-3. What example JSON would a user pipe in?
-
-## Part B: Non-Obvious Insight Review
-
-### Step 3.0: Run automated scorecard
-
-Before hand-scoring, run the automated scorecard to get objective baseline numbers:
+Generate the best useful CLI for an API without burning an hour on phase theater.
 
 ```bash
-cd ~/cli-printing-press && ./printing-press scorecard --dir ./library/<api>-cli
-```
-
-Use these numbers as the baseline. The hand-scoring in Step 3.7 should explain WHY each dimension got its score, not re-guess the number.
-
-### Step 3.7: Score against the quality bar
-
-Score each dimension 0-10. For EACH dimension, provide THREE things:
-1. **Current score** with justification
-2. **What 10/10 looks like** (reference gogcli or best-in-class)
-3. **What specific changes would raise the score** (actionable items)
-
-```markdown
-## Quality Assessment (Baseline)
-
-| Dimension | Score | What 10 Looks Like | How to Get There |
-|-----------|-------|-------------------|-----------------|
-| Output modes | X/10 | gogcli: --json, --yaml, --csv, --table, --select, --quiet, --template | Add --yaml output, add --template for custom formats |
-| Auth | X/10 | gogcli: OAuth browser flow, token storage, multiple profiles, doctor validates | Add OAuth flow, add profile switching |
-| Error handling | X/10 | gogcli: typed exits, retry with backoff, helpful suggestions, link to docs | Add "did you mean?" suggestions |
-| Terminal UX | X/10 | gogcli: progress spinners, color themes, pager for long output | Add progress spinners for pagination |
-| README | X/10 | gogcli: install, quickstart, every command with example, cookbook, FAQ | Add cookbook section, add FAQ |
-| Doctor | X/10 | gogcli: validates auth, API version, rate limits, config file health | Add API version check, config health |
-| Agent-native | X/10 | gogcli: --json, --select, --dry-run, --stdin, idempotent, typed exits, no TTY | Already strong if all flags present |
-| Local Cache | X/10 | gogcli: file cache + optional embedded DB (bolt/badger), --no-cache bypass, cache clear | [what changes would raise score] |
-| Breadth | X/10 | gogcli: 100+ commands covering every API endpoint + convenience wrappers | Add missing commands, add convenience wrappers |
-| Vision | X/10 | discrawl: SQLite + FTS5 + sync + search + tail + domain workflows | Add export, search, sync commands based on Phase 0 research |
-
-**Baseline Total: X/100 (Grade X)**
-```
-
-### Step 3.7b: Head-to-Head Competitor Feature Matrix
-
-The 10-dimension score (above) measures code quality against gogcli. This step measures feature completeness against the ACTUAL competition.
-
-For the top 2 competitors (from Phase 1):
-
-| Feature | Competitor A | Competitor B | Ours | Status |
-|---------|-------------|-------------|------|--------|
-| (list every command they have) | Y/N | Y/N | Y/N | HAVE / MISSING / BETTER |
-
-MISSING features with >50% user need become Phase 4 Priority 1 work items.
-BETTER features are our differentiators - highlight in README comparison table.
-
-**The gogcli score tells you if the code is good.**
-**The competitor matrix tells you if the product is good.**
-**You need both.** A CLI that scores 10/10 on output modes but can't do what the top competitor does is a well-polished toy.
-
-### Step 3.8: Write the GOAT improvement plan
-
-Based on the quality analysis AND the competitor feature matrix, identify:
-
-1. **TABLE STAKES gaps** (from Phase 0.6 + Step 3.7b - highest priority)
-2. **Top 5 highest-impact quality improvements** (will raise the score the most)
-3. **Commands to ADD** (not just rename - new functionality)
-4. **Complex body field examples** to add (top 3 endpoints where --stdin matters most)
-5. **What's achievable in Phase 4** vs what's future work
-
-### Step 3.9: Write the audit artifact
-
-**Write** to `~/cli-printing-press/docs/plans/<today>-fix-<api>-cli-audit.md`:
-
-Include ALL of:
-- Command comparison
-- Help text quality assessment
-- Agent-native checklist
-- Specific fixes needed (file paths + what to change)
-- Quality Assessment table (full)
-- GOAT improvement plan (top 5 + commands to add)
-- Complex body field plan
-
-### PHASE GATE 3
-
-**STOP.** Verify ALL of these:
-1. Audit artifact exists with Quality Assessment table
-2. Each quality dimension has: score, "what 10 looks like", and "how to get there"
-3. GOAT plan has at least 5 specific improvements
-4. Complex body fields have a plan (not just "limitation")
-5. Baseline total score is recorded
-
-**Write Phase 3 Artifact:** Run the Artifact Writing plan generator with all Phase 3 analysis as input. Write to `~/cli-printing-press/docs/plans/<today>-fix-<api>-cli-audit.md`. Include: scorecard baseline, full 11-dimension hand-scored table, GOAT improvement plan, complex body field plan, data layer integration notes from Phase 0.7.
-
-Tell the user: "Phase 3 complete: Baseline Quality Score: [X]/100 (Grade [X]). Found [N] tactical fixes + [M] GOAT improvements. Top improvement: [description]. Proceeding to GOAT build."
-
----
-
-# PHASE 4: GOAT BUILD
-
-## THIS PHASE IS MANDATORY.
-
-**The generator output is scaffolding, not the product. The data layer + workflows are the product.**
-
-**Read the Phase 0.7 Data Layer Specification and Phase 3 Audit artifacts before starting.**
-
-**GraphQL APIs:** For GraphQL APIs, Phase 2 only produced scaffolding (no generated commands). Phase 4 is where ALL commands get written by hand. Use the GraphQL schema + Phase 0.5 workflows + Phase 0.7 data layer spec to determine which queries/mutations to wrap as CLI commands. Each command sends a GraphQL query via the client wrapper. Prioritize workflow commands over CRUD wrappers - a `linear-cli stale --days 30 --team ENG` is more valuable than `linear-cli issues list`.
-
-Execute in this priority order. Do NOT skip Priority 0 to go straight to workflows.
-
-### Codex Delegation in Phase 4
-
-If CODEX_MODE is true, each Priority 0/1/2/3 task below is a **separate Codex call**:
-- Claude reads the Phase 0.7 spec and Phase 3 audit to decide WHAT to build
-- Claude assembles a Codex prompt for each task with: the specific file, current code context, expected behavior
-- Codex writes the code (one task = one Codex call, scoped to 1-2 files, <200 lines)
-- Claude reviews the diff, runs `go build ./... && go vet ./...`
-- If Codex fails: Claude writes that task directly (fallback)
-- After all tasks: run Proof of Behavior verification to catch any issues
-
-**Example Codex prompt for store.go rewrite:**
-```
-TASK: Rewrite store.go with domain-specific tables for Discord.
-
-FILES TO MODIFY:
-- discord-cli/internal/store/store.go
-
-CURRENT CODE (Open function signature):
-func Open(dbPath string) (*Store, error) { ... }
-
-EXPECTED CHANGE:
-Replace the generic resources table with domain-specific tables:
-- messages: id, channel_id, guild_id, author_id, content, timestamp, data JSON
-- members: guild_id, user_id, username, display_name, roles JSON, data JSON
-- channels: id, guild_id, name, type, parent_id, data JSON
-Add FTS5 on messages.content and members.username.
-Add UpsertMessage, UpsertMember, UpsertChannel methods.
-Add SearchMessages method using FTS5 MATCH.
-
-CONVENTIONS:
-- Use modernc.org/sqlite (pure Go, no CGO)
-- WAL mode + synchronous=NORMAL + mmap_size=268435456
-- FTS5 with content='table', content_rowid='rowid'
-
-CONSTRAINTS:
-- Do NOT run git commit/push/add
-- Keep under 400 lines (store.go can be longer than typical)
-- Run: cd discord-cli && go build ./... && go vet ./...
+/printing-press Notion
+/printing-press Discord codex
+/printing-press --spec ./openapi.yaml
+/printing-press emboss ./library/notion-cli
 ```
 
-### Priority 0: Data Layer Foundation (from Phase 0.7)
-
-**This is the most important work in the entire pipeline.** Replace the generic store with domain-specific tables.
-
-1. **Replace `internal/store/store.go`** with domain-specific schema from Phase 0.7 spec:
-   - CREATE TABLE with proper columns for each Primary entity (not JSON blobs)
-   - CREATE INDEX on foreign keys and temporal fields
-   - CREATE VIRTUAL TABLE ... USING fts5() on text fields
-   - UpsertMessage/UpsertMember/etc methods that extract fields from JSON
+## What Changed In v2
 
-2. **Rewrite `sync` command** with domain-aware sync:
-   - Use the cursor field identified in Phase 0.7 (e.g., snowflake ID, updatedAt)
-   - Add `--guild`/`--team`/`--project` scoping flags (domain-specific)
-   - Add `--since` time filter
-   - Paginate with the validated cursor params
+The old skill inflated the path to ship:
+- too many mandatory research documents before code existed
+- too many separate late-stage validation phases after code existed
+- too many chances to discover obvious failures late
 
-3. **Add domain-specific search filters:**
-   - Map Phase 0.7's filter table into search command flags
-   - `--channel`, `--author`, `--team`, etc -> SQL WHERE clauses
-   - FTS5 on extracted text content, not raw JSON
+This version uses one lean loop:
+1. Resolve the spec and write one research brief
+2. Generate
+3. Build the highest-value gaps
+4. Run one shipcheck block
+5. Optionally run live API smoke tests
 
-4. **Add `sql` command** for raw read-only queries:
-   ```go
-   func newSqlCmd(flags *rootFlags) *cobra.Command {
-       cmd := &cobra.Command{
-           Use:   "sql <query>",
-           Short: "Run read-only SQL queries against the local database",
-           RunE: func(cmd *cobra.Command, args []string) error {
-               // Open DB, execute query, print results as table or JSON
-           },
-       }
-   }
-   ```
+Artifacts are still written, but only the ones that materially help the next step.
 
-5. **Add entity-specific list commands** (e.g., `messages`, `members`):
-   - Query local SQLite, not the API
-   - Support `--channel`, `--author`, `--days`, `--hours`, `--since` filters
-   - Support `--sync` flag to trigger on-demand sync before querying
+## Modes
 
-6. **Update tail command** based on Phase 0.7's tail strategy:
-   - If WebSocket/Gateway: implement real-time connection
-   - If SSE: implement EventSource reader
-   - If REST polling: keep current implementation but use domain-aware cursors
+### Default
 
-### Priority 1: Table Stakes Features (from Phase 0.6)
+Normal mode. Claude does research, generation orchestration, implementation, and verification.
 
-**Build every feature classified as TABLE STAKES in Phase 0.6.** These are features that the top competitor has and that >50% of users expect. This is NOT optional polish - these are the features that determine whether anyone would switch from the incumbent.
+### Codex Mode
 
-For each table-stakes feature:
-1. Read how the competitor implements it (from Phase 1 research)
-2. Implement it - don't just match the competitor, make it BETTER
-3. Better means: works with --json, supports --dry-run, has --stdin, composes with our data layer where possible
-4. Register in root.go alongside the generated commands
+If the arguments include `codex` or `--codex`, offload pure code-writing tasks to Codex CLI.
 
-**Gate:** Every TABLE STAKES feature from Phase 0.6 must be implemented before proceeding to Priority 2. No exceptions.
+Use Codex for:
+- writing store/data-layer code
+- writing workflow commands
+- fixing dead flags / dead code / path issues
+- README cookbook edits
 
-### Priority 2: Power User Workflows (from Phase 0.5) - NOW powered by local DB
+Keep on Claude:
+- research and product positioning
+- choosing which gaps matter
+- verification results and ship decisions
 
-Implement the top 5-7 workflows identified in Phase 0.5 as real, hand-written Go commands. **Where possible, query the local SQLite database instead of making live API calls.** This makes workflows instant and avoids rate limits.
+If Codex fails 3 times in a row, stop delegating and finish locally.
 
-For each workflow:
-1. **Create a dedicated command file** (e.g., `internal/cli/stale.go`, `internal/cli/velocity.go`)
-2. **Use the generated client** to make real API calls
-3. **Combine 2+ API calls** into one user-facing operation
-4. **Add realistic examples** in --help that show the actual workflow
-5. **Support --json output** for agent consumption
-6. **Register in root.go** alongside the generated commands
+### Emboss Mode
 
-### Priority 3: Command Name Normalization + Apply Product Name
-
-**Step 3a: Normalize generated command names**
-
-The generator produces ugly operationId-derived names. Fix them:
-
-| Generated | Normalized | Rule |
-|-----------|-----------|------|
-| `retrieve-a*` | `get` | Strip "Retrieve a" prefix |
-| `delete-a*` | `delete` | Strip "Delete a" prefix |
-| `create-a*` | `create` | Strip "Create a" prefix |
-| `update-a*` | `update` | Strip "Update a" prefix |
-| `post` | `create` | HTTP method -> action |
-| `patch` | `update` | HTTP method -> action |
-| `get-self` | `me` | Special case |
-| `list-*` | `list` | Strip resource suffix |
-
-For each rename: update the `Use:` field, rename the file, verify `go build` passes.
-
-**Step 3b: Apply the product name everywhere**
-
-1. Rename `cmd/<generated-name>/` to `cmd/<product-name>/`
-2. Update root.go Use field and version template
-3. Update go.mod module path to `github.com/<org>/<product-name>`
-4. Update client.go User-Agent header
-5. `grep -r "<old-name>" . | grep -v "Generated by"` must return 0 hits — **pay special attention to DB path strings and config path strings** (e.g., `~/.local/share/<old-name>/data.db`, `~/.config/<old-name>/config.toml`). These are easy to miss and will cause the search/analytics/export commands to use a different database than the sync command. If the old name appears in any `filepath.Join` or `defaultDBPath` call, fix it.
-6. Update README examples
-
-**Step 3c: Validate API version header**
-
-1. Check what API version the spec uses / the API docs recommend
-2. Check what the generated client sends (`grep "Version" client.go`)
-3. If they differ, update client.go to the latest documented version
-4. If the API uses date-based versions (Notion, Stripe), use the LATEST
-
-### Priority 4: Scorecard Gap Fixes (DEMOTED + ANTI-GAMING)
-
-Run the scorecard. Fix REAL gaps. DO NOT GAME IT.
+If the arguments start with `emboss`, this is a second-pass improvement cycle for an existing generated CLI.
 
 ```bash
-cd ~/cli-printing-press && ./printing-press scorecard --dir ./library/<api>-cli
+/printing-press emboss ./library/notion-cli
 ```
 
-**ANTI-GAMING RULES:**
-- If a function exists only because the scorecard checks for a string pattern, DELETE IT.
-- If a flag is registered but never checked in any RunE, DELETE IT.
-- If an import exists only to put "store." in the file, DELETE IT.
-- A CLI that scores 60 but has every table-stakes feature beats one that scores 80 with type aliases.
-- The scorecard measures proxies for quality. Optimize for actual quality.
-
-Also fix:
-- Complex body field --stdin examples for top 3 endpoints
-- Lazy descriptions (1-2 word Short fields)
-- Placeholder examples ("abc123" -> realistic domain values)
-
-### Priority 5: Write Tests
-
-A CLI with 0 test files is not shippable.
-
-For each Primary entity in the data layer:
-1. Test UpsertX with valid data -> verify row in DB
-2. Test UpsertX with missing fields -> verify graceful handling
-3. Test SearchX with FTS5 -> verify results match
-
-For each workflow command:
-1. Seed DB with test fixtures
-2. Run the command's core query
-3. Verify result shape and counts
-
-Minimum: 1 test file per package (store, cli). Use table-driven tests matching Go conventions.
-
-### Priority 6: Distribution Scaffold
-
-1. Add `.goreleaser.yaml` for cross-platform binary builds
-2. Add Homebrew formula or tap
-3. Add install instructions for non-Go users to README
-4. Add `.github/workflows/ci.yml` (go test, go vet, goreleaser on tag)
-
-A CLI that can only be installed via `go install` is not a real CLI.
-
-### Priority 7: Polish
-
-Only after all above priorities are complete:
-1. README cookbook section **showcasing workflow commands AND table-stakes features** (not just API calls)
-2. FAQ section with domain-specific questions
-3. Comparison table in README showing where we beat each competitor
-
-### Step 4.4: Verify compilation
+Use the built-in audit command:
 
 ```bash
-cd ~/cli-printing-press/library/<api>-cli && go build ./... && go vet ./... && echo "ALL FIXES VERIFIED"
+cd ~/cli-printing-press && ./printing-press emboss --dir <cli-dir> --spec <spec-path> --audit-only
 ```
 
-### PHASE GATE 4
+Emboss is:
+1. audit baseline
+2. quick re-research
+3. top-5 gap analysis
+4. implement improvements
+5. re-audit and report delta
 
-**STOP.** Verify ALL priorities:
+Do not run emboss automatically.
 
-**Priority 0 (Data Layer):**
-1. Domain-specific SQLite tables (NOT generic JSON blobs)
-2. Sync command uses domain-aware cursors (validated in Phase 0.7)
-3. Search supports domain-specific filters (--channel, --author, --team, etc.)
-4. `sql` command exists for raw read-only queries
+## Rules
 
-**Priority 1 (Table Stakes):**
-5. Every TABLE STAKES feature from Phase 0.6 is implemented
-6. Table stakes features work with --json, --dry-run, --stdin where applicable
+- Optimize for time-to-ship, not time-to-document.
+- Reuse prior research whenever it is already good enough.
+- Do not split one idea across multiple mandatory artifacts.
+- Do not create a separate narrative phase for dogfood, dead-code audit, runtime verification, and final score. Treat them as one shipcheck block.
+- Run cheap, high-signal checks early.
+- Fix blockers and high-leverage failures first.
+- Reuse the same spec path across `generate`, `dogfood`, `verify`, and `scorecard`.
+- YAML, JSON, local paths, and URLs are all valid spec inputs for the verification tools.
+- Maximum 2 verification fix loops unless the user explicitly asks for more.
 
-**Priority 2 (Workflows):**
-7. At least 3 workflow commands implemented (from Phase 0.5)
-8. Workflow commands use local DB where possible (not just live API calls)
+## Outputs
 
-**Priority 3 (Names):**
-9. Command names normalized (get/create/update/delete, not retrieve-a/post/patch)
-10. Binary name matches product name from Phase 0.8
-11. Module path is correct
+Every run writes up to 4 concise artifacts in `~/cli-printing-press/docs/plans/`:
 
-**Priority 4-7:**
-12. `go build ./...` and `go vet ./...` pass
-13. At least 1 test file per package (store, cli)
-14. README cookbook includes data layer + workflow + table-stakes examples
+1. `<date>-feat-<api>-cli-brief.md`
+2. `<date>-fix-<api>-cli-build-log.md`
+3. `<date>-fix-<api>-cli-shipcheck.md`
+4. `<date>-fix-<api>-cli-live-smoke.md` (only if live testing runs)
 
-**Data Pipeline Trace (MANDATORY):** For each Primary entity from Phase 0.7, verify:
-- WRITE path exists: sync.go calls `db.UpsertX()` for this entity (file:line)
-- READ path exists: at least one command queries this entity's table (file:line)
-- SEARCH path exists (if FTS5): at least one command calls `db.SearchX()` (file:line)
-- If ANY Primary entity has no WRITE path, the data layer is broken. Fix before proceeding.
+These do not need to be 200+ lines. Keep them dense, evidence-backed, and directly useful.
 
-**Three-Benchmark Check (MANDATORY):**
+## Phase 0: Resolve And Reuse
 
-1. ARCHITECTURE (discrawl benchmark): "Does this CLI have a real data layer - domain-specific SQLite tables, FTS5 search, incremental sync, workflow commands that query local data?" If no, Priority 0 isn't done.
+Before new research:
 
-2. QUALITY (gogcli benchmark): "Does the code have proper output modes, typed errors, agent-native flags, doctor command, README with cookbook?" If gaps, Priority 4 scorecard fixes address them.
+1. Resolve the spec source.
+2. Check for prior research in:
+   - `~/cli-printing-press/docs/plans/*<api>*`
+   - `~/docs/plans/*<api>*`
+3. Reuse good prior work instead of redoing it.
+4. Detect whether an API key is already available.
 
-3. FEATURES (competitor benchmark): "Would a user of [top competitor from Phase 1] switch to this CLI?" If no: "What's the ONE feature that would flip them?" Build it now before proceeding.
+Token detection:
+- GitHub: `GITHUB_TOKEN`, `GH_TOKEN`, or `gh auth token`
+- Discord: `DISCORD_TOKEN`, `DISCORD_BOT_TOKEN`
+- Linear: `LINEAR_API_KEY`
+- Notion: `NOTION_TOKEN`
+- Stripe: `STRIPE_SECRET_KEY`
+- Generic: `API_KEY`, `API_TOKEN`
 
-All three must pass. Architecture without features is a toy. Features without architecture is a thin wrapper. Quality without either is polished nothing.
+If a token is available, ask once whether to use it for read-only live testing at the end. Do not block the build on token collection.
 
-**Write Phase 4 Artifact:** Run the Artifact Writing plan generator with all Phase 4 work as input. Write to `~/cli-printing-press/docs/plans/<today>-fix-<api>-cli-goat-build-log.md`. Include: data layer implementation details, workflow commands built, scorecard fixes, what was skipped, before/after scorecard comparison.
+## Phase 1: Research Brief
 
-Tell the user: "Phase 4 complete: Built [N] data layer tables + [M] workflow commands, applied [K] scorecard fixes. Data layer: [list tables]. Top workflow: [name]. Compilation verified. Proceeding to dogfood emulation."
+Write one build-driving brief, not a stack of phase essays.
 
----
+The brief must answer:
 
-# PHASE 4.5: DOGFOOD EMULATION
+1. What is this API actually used for?
+2. What are the top 3-5 power-user workflows?
+3. What are the top table-stakes competitor features?
+4. What data deserves a local store?
+5. Why would someone install this CLI instead of the incumbent?
+6. What is the product name and thesis?
 
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
+Research checklist:
+- Find the spec or docs source
+- Find the top 1-2 competitors
+- Find 2-3 concrete user pain points
+- Identify the highest-gravity entities
+- Pick the top 3-5 commands that matter most
 
-You don't have real API keys. But the OpenAPI spec already defines every request shape, response schema, error format, and pagination pattern. Test every generated command against spec-derived mock responses. Inspired by [Vercel's emulate](https://github.com/vercel-labs/emulate) - production-fidelity API simulation, zero config.
+Do not produce separate mandatory documents for:
+- workflow ideation
+- parity audit
+- data-layer prediction
+- product thesis
 
-**Read the OpenAPI spec (from Phase 2) and the Phase 4 GOAT Build Log artifact before starting.**
+Put them in the one brief.
 
-### Step 4.5a: Generate Synthetic Responses from Spec
+Write:
 
-For each endpoint in the spec, generate a realistic JSON response by reading the 200/201 response schema:
+`~/cli-printing-press/docs/plans/<today>-feat-<api>-cli-brief.md`
 
-**Field value heuristics** (domain-aware, not random):
+Suggested shape:
 
-| Field name pattern | Generated value |
-|---|---|
-| `id`, `*_id` | Realistic format for the API (e.g., Discord snowflake: `"1234567890123456789"`, UUID: `"550e8400-e29b-41d4-a716-446655440000"`) |
-| `name`, `username`, `title` | Realistic domain values (e.g., `"general"`, `"test-user"`, `"Bug: Login fails"`) |
-| `content`, `description`, `body` | `"Dogfood test content for validation"` |
-| `timestamp`, `created_at`, `updated_at` | `"2026-03-26T12:00:00.000Z"` |
-| `type` (enum in spec) | First enum value from the spec |
-| `url`, `avatar_url`, `icon_url` | `"https://example.com/test.png"` |
-| `email` | `"test@example.com"` |
-| `count`, `position`, `size` | `1` |
-| `boolean` fields | `true` |
-| Array fields | 2-3 items with the above heuristics |
-| Nested objects | Recursively generate from schema |
+```markdown
+# <API> CLI Brief
 
-Save mocks to `/tmp/<api>-cli-mocks/` for reuse.
+## API Identity
+- Domain:
+- Users:
+- Data profile:
 
-### Step 4.5b: Score Every Command on 5 Dimensions
+## Top Workflows
+1. ...
 
-For each generated command, score 0-10 on each dimension (50 max):
+## Table Stakes
+- ...
 
-**Dimension 1: Request Construction (0-10)**
+## Data Layer
+- Primary entities:
+- Sync cursor:
+- FTS/search:
 
-Run the command with `--dry-run` and inspect the output:
+## Product Thesis
+- Name:
+- Why it should exist:
 
-```bash
-<api>-cli <resource> <action> <required-args> --dry-run 2>&1
+## Build Priorities
+1. ...
+2. ...
+3. ...
 ```
 
-| Check | Points |
-|---|---|
-| Path params replaced (no `{param}` literals in URL) | 2 |
-| HTTP method matches spec | 2 |
-| Required query params present | 2 |
-| Body schema matches spec's requestBody | 2 |
-| Auth header present | 2 |
-
-**Dimension 2: Response Parsing (0-10)**
-
-Generate a synthetic response from the spec and verify the command can process it:
-
-| Check | Points |
-|---|---|
-| Can parse the spec's 200 response schema | 3 |
-| --json output is valid JSON | 2 |
-| --select works with fields from the response schema | 2 |
-| Table output renders without crash | 2 |
-| Error responses (401/404/429) produce correct exit codes | 1 |
-
-**Dimension 3: Schema Fidelity (0-10)**
-
-Compare generated flags against the spec's parameters:
-
-| Check | Points |
-|---|---|
-| All `required: true` params have CLI flags | 3 |
-| Flag types match spec types (string/int/bool) | 2 |
-| No hallucinated flags (every flag maps to a real spec param) | 3 |
-| Help descriptions come from spec, not invented | 2 |
-
-**Dimension 4: Example Quality (0-10)**
-
-Validate every example in --help and README:
-
-| Check | Points |
-|---|---|
-| Example IDs match realistic format (not "abc123") | 2 |
-| --stdin JSON matches spec's requestBody schema | 3 |
-| Required flags present in examples | 3 |
-| Example commands parse without usage error via --dry-run | 2 |
-
-**Dimension 5: Workflow Integrity (0-10)** (workflow commands only)
-
-| Check | Points |
-|---|---|
-| All API paths hit by the workflow exist in the spec | 3 |
-| Query params sent match spec's parameters | 2 |
-| Response fields accessed exist in the response schema | 3 |
-| Cross-entity joins reference valid fields | 2 |
-
-### Step 4.5c: Compute Aggregate Scores
-
-| Metric | Formula |
-|---|---|
-| **Per-command score** | Sum of 5 dimensions (0-50) |
-| **Pass rate** | % of commands scoring >= 35/50 (70%) |
-| **Critical failure count** | Commands scoring < 25/50 |
-| **Overall dogfood score** | Average across all tested commands |
-
-**Thresholds:**
-- **PASS:** Pass rate >= 90% AND 0 critical failures
-- **WARN:** Pass rate >= 70% AND <= 3 critical failures (auto-fix, then re-score)
-- **FAIL:** Pass rate < 70% OR > 3 critical failures (report issues, do NOT proceed)
-
-**Sampling for large CLIs (100+ commands):** Test ALL workflow commands + ALL commands with --stdin examples + random sample of 30 generated commands. Report sample size.
-
-### Step 4.5d: Write the Dogfood Report ("Here's what I learned")
-
-**Run the Artifact Writing plan generator** with all dogfood scoring results as input. Write to `~/cli-printing-press/docs/plans/<today>-fix-<api>-cli-dogfood-report.md`.
-
-The report MUST include three sections:
-
-**Section 1: "Here's what I learned"**
-- Per-command score table (sampled commands, all 5 dimensions)
-- Top 5 failures with root cause analysis
-- Hallucination list (flags/fields not in spec with evidence)
-- Pattern analysis: what categories of issues keep appearing?
-- Comparison: workflow commands vs generated commands - which score better?
-
-**Section 2: "Here's what I think we should fix"**
-- Prioritized fix list, ordered by impact (critical failures first)
-- For each fix: what's wrong, which file, what the fix is, expected score improvement
-- Mark each as AUTO-FIXABLE or NEEDS-MANUAL-FIX
-- Estimate: "fixing these [N] issues would raise pass rate from [X]% to [Y]%"
-
-**Section 3: "Here's what I think we should make"**
-- Features or commands that the dogfood revealed are missing
-- Endpoints that exist in the spec but have no generated command
-- Schema fields that should be in the data layer but aren't
-- Workflow ideas that emerged from understanding the API responses better
-
-### Step 4.5e: Fix Everything Fixable
-
-Now execute the fixes from the report. For each AUTO-FIXABLE issue:
-
-| Issue Type | Fix Action |
-|---|---|
-| Placeholder values ("abc123", "string") | Replace with realistic domain values from spec |
-| Missing required flags in examples | Add required flags with domain-realistic values |
-| --stdin JSON doesn't match requestBody | Regenerate from spec schema |
-| Lazy 1-word Short descriptions | Pull description from spec's endpoint summary |
-| Hallucinated flag (not in spec) | Remove the flag and its binding |
-| Wrong flag type (string instead of int) | Fix the cobra flag type |
-| Path param not substituted in example | Fix the example with a realistic ID |
-| Workflow hits nonexistent endpoint | Fix the path or remove the workflow |
-
-**After ALL fixes:**
-1. Run `go build ./...` and `go vet ./...` to verify fixes compile
-2. Re-run the FULL dogfood scoring (Steps 4.5b + 4.5c) on the same sample
-3. Compute the delta: before/after per-dimension and aggregate scores
-4. **Update the dogfood report artifact** with a new section:
-
-**Section 4: "Here's what we fixed" (appended after Step 4.5e)**
-- List of every fix applied with file path and description
-- Before/after scores per command that was fixed
-- Aggregate improvement: pass rate delta, critical failure delta, avg score delta
-- Remaining unfixed issues (NEEDS-MANUAL-FIX items) with reasons
-
-### Step 4.5f: Implement "Should Make" Recommendations (if time permits)
-
-Review the "Here's what I think we should make" section from the report. For each recommendation:
-
-1. Is this a quick win (< 10 min to implement)? -> Do it now
-2. Is this a significant feature (> 10 min)? -> Add to the dogfood report as "Future Work"
-3. Does this improve the quality score? -> Prioritize it
-
-After implementing quick wins, re-run `go build` and the dogfood on affected commands.
-
-### PHASE GATE 4.5
-
-**STOP.** Verify ALL of these before proceeding:
-1. Every workflow command scored on all 5 dimensions
-2. Sample of generated commands scored (30+ or all if < 100)
-3. Synthetic responses generated from spec (not invented)
-4. Per-command score table computed
-5. Dogfood report written with all 4 sections (learned, should fix, should make, fixed)
-6. All AUTO-FIXABLE issues resolved
-7. Re-score after fixes shows measurable improvement
-8. `go build ./...` and `go vet ./...` pass
-9. Final verdict: PASS or WARN (FAIL = stop and report, do NOT proceed)
-
-Tell the user: "Phase 4.5 complete: Dogfood score [X]/50 avg across [N] commands (was [X0] before fixes, +[D] improvement). Pass rate: [Y]% (was [Y0]%). Critical failures: [Z] (was [Z0]). Auto-fixed [K] issues. Implemented [J] quick-win recommendations. [PASS/WARN/FAIL]. Proceeding to hallucination audit."
-
----
-
-# PHASE 4.6: HALLUCINATION & DEAD CODE AUDIT
-
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
-
-The scorecard tests syntax. This phase tests semantics. Every flag, function, and table must be wired to real code paths. Dead code that exists only to trigger scorecard string matches is worse than missing code.
-
-### Step 4.6a: Dead Flag Audit
-
-For every flag registered in root.go's persistent flags (--json, --csv, --stdin, --quiet, --yes, etc.):
-1. Grep all RunE functions across internal/cli/*.go for `flags.<fieldName>`
-2. If a flag is declared but never checked in any RunE: it's a **dead flag**
-3. **FIX:** Either wire the flag into at least one command's RunE logic, OR remove the flag from root.go
-
-### Step 4.6b: Dead Function Audit
-
-For every function defined in helpers.go:
-1. Grep all other .go files in internal/cli/ for that function name
-2. If a function is defined but never called: it's a **dead function**
-3. **FIX:** Either call the function from a real command's code path, OR delete the function
-
-**WARNING:** Do NOT fix dead functions by adding calls that don't do anything useful. `_ = filterFields(nil)` in an init() block is gaming. The function must be called in a real output or error path.
-
-### Step 4.6c: Ghost Table Audit
-
-For every CREATE TABLE in store.go's migration:
-1. Grep sync.go for an INSERT or Upsert call targeting this table
-2. Grep all command files for a SELECT targeting this table
-3. If a table has no INSERT path: it's a **ghost table** - created but never populated
-4. **FIX:** Wire sync to populate the table via the domain-specific Upsert method, OR remove the table
-
-### Step 4.6d: Data Pipeline Trace
-
-For each Primary entity from Phase 0.7, trace the complete data flow:
-
-| Entity | WRITE path (sync -> UpsertX) | READ path (command -> SELECT) | SEARCH path (command -> SearchX) |
-|--------|------------------------------|-------------------------------|----------------------------------|
-
-Every Primary entity MUST have a WRITE path. If any Primary entity has no write path, the data layer is broken. Fix before proceeding.
+## Phase 2: Generate
 
-### PHASE GATE 4.6
+Use the resolved spec source and generate immediately.
 
-**STOP.** Verify:
-1. Dead flags: 0
-2. Dead functions: 0
-3. Ghost tables: 0
-4. Every Primary entity has WRITE + READ paths
-5. Present the data pipeline trace table to the user
-
-Tell the user: "Phase 4.6 complete: [N] dead flags fixed, [M] dead functions removed, [K] ghost tables wired. Data pipeline verified for [X] primary entities."
-
----
-
-# PHASE 4.8: RUNTIME VERIFICATION
-
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
-
-The scorecard measures files. This phase measures behavior. Build the CLI and test every command.
-
-### Step 4.8a: Run the Runtime Verifier
+OpenAPI / internal YAML:
 
 ```bash
-cd ~/cli-printing-press && ./printing-press verify \
-  --dir ./library/<api>-cli \
-  --spec /tmp/<api>-spec.json \
-  --threshold 80
+cd ~/cli-printing-press && ./printing-press generate \
+  --spec <spec-path-or-url> \
+  --output ./library/<api>-cli \
+  --force --lenient --validate
 ```
 
-If you collected an API key in Phase 0.1, add it:
+Docs-only:
 
 ```bash
-cd ~/cli-printing-press && ./printing-press verify \
-  --dir ./library/<api>-cli \
-  --spec /tmp/<api>-spec.json \
-  --api-key "$<API_ENV_VAR>" \
-  --env-var <API_ENV_VAR> \
-  --threshold 80
-```
-
-The verifier:
-1. Builds the CLI binary
-2. Starts a mock server (or uses the real API if key provided - read-only GETs only)
-3. Tests every discovered command: --help, --dry-run, --json execution
-4. Tests the data pipeline end-to-end: sync -> sql -> search -> health
-5. Scores each command (0-3) and computes aggregate pass rate
-
-### Step 4.8b: Interpret Results
-
-- **PASS** (>= 80% pass rate, data pipeline works, 0 critical): Proceed to Phase 5.
-- **WARN** (60-80%): Review failures. Fix the top 3 manually and re-run.
-- **FAIL** (< 60% or data pipeline broken): DO NOT proceed. Fix until at least WARN.
-
-For each failing command, the verifier reports which test failed (help/dry-run/execute). Fix the root cause:
-- Help fails = command not registered in root.go
-- Dry-run fails = dryRun flag not checked in RunE
-- Execute fails = wrong path, bad response parsing, or missing required flags
-
-### PHASE GATE 4.8
-
-**STOP.** Verify:
-1. `printing-press verify` ran to completion
-2. Pass rate >= 80%
-3. Data pipeline: sync populates tables, sql queries them, search finds results
-4. 0 critical failures
-
-Tell the user: "Runtime verification: [X]% pass rate ([N]/[M] commands). Data pipeline: [PASS/FAIL]. Mode: [live/mock]. Proceeding to agent readiness review."
-
----
-
-# PHASE 4.9: AGENT READINESS REVIEW LOOP
-
-## THIS PHASE IS MANDATORY. YOU MUST ATTEMPT IT.
-
-**You MUST dispatch the `compound-engineering:cli-agent-readiness-reviewer` agent.** Do not skip this phase because "it's faster to move on" or "the CLI is already good." The user wants this review to run when the agent is available.
-
-**The only acceptable outcomes are:**
-1. The agent ran and produced results (proceed with fixes)
-2. The agent dispatch failed because the plugin is not installed (warn prominently, proceed to Phase 5)
-3. The agent ran but produced an error or empty output (warn prominently, proceed to Phase 5)
-
-**"I decided to skip it" is NOT an acceptable outcome.** If you catch yourself about to move to Phase 5 without having attempted the agent dispatch, STOP and go back.
-
-The existing agent-native scorecard dimension checks for flags. This phase goes deeper — evaluating 7 principles (non-interactive automation, structured output, progressive help, actionable errors, safe retries, composability, bounded responses) with file-level fix recommendations.
-
-### Step 4.9a: Dispatch the Agent (MANDATORY — FOREGROUND ONLY)
-
-You MUST execute this Agent tool call **in the foreground** (NOT background). You need the results before you can proceed — do NOT use `run_in_background: true`.
-
-```
-Agent tool:
-  subagent_type: compound-engineering:review:cli-agent-readiness-reviewer
-  run_in_background: false
-  prompt: "Run the compound-engineering:cli-agent-readiness-reviewer agent on the <api> CLI in <output-dir>.
-           Do not look at code elsewhere in the repo outside of that folder."
-```
-
-**WAIT for the agent to return results before moving on.** Do NOT proceed to Phase 5 while this is running.
-
-**If the dispatch succeeds:** proceed to Step 4.9b with the results.
-
-**If the dispatch fails** (agent not found, plugin not installed, tool rejected):
-1. Print this warning to the user in a visible block:
-
-```
-⚠️  PHASE 4.9 SKIPPED — REVIEWER NOT AVAILABLE
-    The Compound Engineering `cli-agent-readiness-reviewer` agent could not be dispatched.
-    This reviewer evaluates whether the generated CLI is usable by AI agents
-    (exit codes, output routing, alias correctness, delete safety gates, etc.).
-    Reason: [error message]
-    To enable: install the compound-engineering plugin (v2.55.0+)
-    and register every-marketplace in ~/.claude/settings.json.
-    The CLI was NOT reviewed for agent readiness.
-```
-
-2. Proceed to Phase 5. Do NOT silently continue.
-
-### Step 4.9b: Process Initial Results
-
-**Set `pass_count = 0`.** The initial dispatch establishes the baseline — pass counting begins in Step 4.9d when you re-dispatch after fixes.
-
-The reviewer produces:
-- A scorecard table (7 principles x severity: Blocker/Friction/Optimization/None)
-- A "What's Working Well" section
-- A ranked list of recommended fixes with file:line references
-
-**If the reviewer returned results but they are empty or unparseable:**
-1. Print this warning to the user:
-
-```
-⚠️  PHASE 4.9 INCOMPLETE — REVIEWER RETURNED NO ACTIONABLE RESULTS
-    The Compound Engineering `cli-agent-readiness-reviewer` agent ran but produced
-    no parseable fix list. The CLI was NOT fully reviewed for agent readiness
-    (exit codes, output routing, alias correctness, delete safety gates, etc.).
-```
-
-2. Proceed to Phase 5. Do not loop.
-
-**If the reviewer returned a valid scorecard and fix list:** proceed to Step 4.9c.
-
-### Step 4.9c: Implement Fixes
-
-For each fix in the reviewer's ranked recommended fixes list:
-
-1. Read the fix description and target file:line
-2. If the referenced file does not exist or the line is out of bounds: skip this fix, log a warning
-3. If `CODEX_MODE` is true, treat this fix as a **separate Codex call** using the Codex Delegation Pattern from the top of this skill:
-   - Claude assembles a prompt with the exact reviewer finding, exact file:line, current code snippet, expected behavioral change, repo conventions, and the same constraints (`no git`, listed files only, `go build ./... && go vet ./...` at the end)
-   - Scope the call to the smallest possible patch, ideally 1 file and under 100 lines
-   - After Codex returns, immediately verify the target file still exists and is non-empty before reviewing the diff
-   - If Codex fails, empties/deletes the file, edits outside scope, or fails verification: revert that attempted change and apply the fix directly in Claude as a fallback
-4. If `CODEX_MODE` is false, make the code change directly in Claude
-5. Run `go build ./... && go vet ./...` to verify the change compiles and passes vet
-6. If build or vet fails: revert the change, skip this fix, continue to next
-7. Move to the next fix
-
-All listed fixes are attempted — the reviewer already ranks by impact.
-
-**After all fixes are attempted, proceed to Step 4.9d. Do NOT skip to Phase 5 or the Phase Gate.** You must re-dispatch the reviewer to verify fixes before evaluating termination.
-
-**Important:** The reviewer agent itself always runs inside Claude Code. Only the code-writing step is delegated. This preserves the same split as Phase 4: Claude reviews, prioritizes, and verifies; Codex writes the patch when the run started in Codex mode.
-
-### Step 4.9d: Re-dispatch Reviewer and Termination Check (MANDATORY)
-
-**You MUST re-dispatch the reviewer agent after implementing fixes.** Do NOT evaluate termination based on your own assessment of the fixes — only the reviewer's new scorecard counts. This re-dispatch is mandatory regardless of how confident you are that fixes resolved the issues.
-
-**If you are about to proceed to Phase 5 without re-dispatching the reviewer after Step 4.9c, STOP — you are skipping a mandatory step. Go back and dispatch the agent.**
-
-Increment `pass_count` by 1, then re-run the `compound-engineering:cli-agent-readiness-reviewer` agent on the same folder (same foreground dispatch as Step 4.9a — do NOT background). **WAIT for results.**
-
-Evaluate the new scorecard:
-
-- **Zero Blockers AND zero Frictions:** Pass. Proceed to Phase Gate 4.9.
-- **Blockers or Frictions remain AND `pass_count < 2`:** Return to Step 4.9c with the new fix list. After implementing those fixes, return here — increment `pass_count` and re-dispatch again.
-- **Blockers or Frictions remain AND `pass_count >= 2`:** Log remaining issues as known items. Proceed to Phase Gate 4.9.
-
-**If the agent becomes unavailable between passes** (plugin timeout, tool error):
-1. Print this warning to the user:
-
-```
-⚠️  PHASE 4.9 INTERRUPTED — REVIEWER LOST BETWEEN PASSES
-    The Compound Engineering `cli-agent-readiness-reviewer` agent became unavailable
-    after pass [N]. Fixes from pass [N] were applied. No further review possible.
+cd ~/cli-printing-press && ./printing-press generate \
+  --docs <docs-url> \
+  --name <api> \
+  --output ./library/<api>-cli \
+  --force --validate
 ```
 
-2. Proceed to Phase Gate 4.9. The pass count does not reset.
-
-### PHASE GATE 4.9
+GraphQL-only APIs:
+- Generate scaffolding only in Phase 2
+- Build real commands in Phase 3 using a GraphQL client wrapper
 
-**STOP.** Evaluate the result:
+After generation:
+- note skipped complex body fields
+- fix only blocking generation failures here
+- do not start broad polish work yet
 
-| Verdict | Condition | Action |
-|---------|-----------|--------|
-| **Pass** | Zero Blockers and zero Frictions after ≤ 2 passes | Proceed to Phase 5 |
-| **Warn** | Frictions remain after 2 passes, zero Blockers | Log Frictions as known issues, proceed to Phase 5 |
-| **Degrade** | Blockers remain after 2 passes | Log Blockers as known issues, proceed to Phase 5 |
-| **Skipped** | Agent unavailable (dispatch failed) | Warning already shown, proceed to Phase 5 |
+If generation fails:
+- fix the specific blocker
+- retry at most 2 times
+- prefer generator fixes over manual generated-code surgery when the failure is systemic
 
-Tell the user: "Agent readiness review: [PASS/WARN/DEGRADE/SKIPPED]. Blockers: [N]. Frictions: [N]. Optimizations: [N]. Passes: [N]/2. Proceeding to final report."
+## Phase 3: Build The Highest-Value Gaps
 
----
-
-# PHASE 5: FINAL QUALITY SCORE + REPORT
-
-## THIS PHASE IS MANDATORY. DO NOT SKIP IT.
-
-### Step 5.1: Ship Readiness Assessment
-
-Run the automated scorecard again to measure improvement:
-
-```bash
-cd ~/cli-printing-press && ./printing-press scorecard --dir ./library/<api>-cli
-```
+Build only the things most likely to change ship-readiness:
 
-Re-score ALL 10 dimensions. Show the DELTA from the baseline:
+Priority 1:
+- data layer foundations for the primary entities
+- sync/search/SQL path if the API has real data gravity
 
-```markdown
-## Ship Readiness Assessment (Post-Fix)
-
-| Dimension | Before | After | Delta | What Changed |
-|-----------|--------|-------|-------|-------------|
-| Output modes | X/10 | Y/10 | +Z | [specific change] |
-| Auth | X/10 | Y/10 | +Z | [specific change] |
-| Error handling | X/10 | Y/10 | +Z | [specific change] |
-| Terminal UX | X/10 | Y/10 | +Z | [specific change] |
-| README | X/10 | Y/10 | +Z | [specific change] |
-| Doctor | X/10 | Y/10 | +Z | [specific change] |
-| Agent-native | X/10 | Y/10 | +Z | [specific change] |
-| Local Cache | X/10 | Y/10 | +Z | [specific change] |
-| Breadth | X/10 | Y/10 | +Z | [specific change] |
-| Vision | X/10 | Y/10 | +Z | [specific change] |
-
-**Before: X/100 -> After: Y/100 (+Z points)**
-**Grade: [A/B/C]**
-```
+Priority 2:
+- top 3-5 power-user workflows from the brief
+- table-stakes competitor features users will notice immediately
 
-### Step 5.2: Remaining gaps
+Priority 3:
+- skipped complex request bodies that block important commands
+- naming cleanup for ugly operationId-derived commands
 
-For each dimension still < 8/10:
-- What would it take to reach 8+?
-- Is this a generator limitation or achievable with more fix time?
-- Tag as "future work" with a specific next step
+Priority 4:
+- tests for non-trivial store/workflow logic
 
-### Step 5.3: Present the final report
+Do not try to build every speculative workflow before verification. Get the high-signal surface working first, then verify.
 
-Show ALL of these sections:
+Write:
 
-**1. Summary:**
-```
-Generated <api>-cli with <N> resources and <M> commands.
-Resources: <comma-separated list>
-```
+`~/cli-printing-press/docs/plans/<today>-fix-<api>-cli-build-log.md`
 
-**2. Quality Score (Before/After):**
-```
-Quality Score: Before X/100 -> After Y/100 (+Z points) - Grade [A/B/C]
+Include:
+- what was built
+- what was intentionally deferred
+- skipped body fields that remain
+- any generator limitations found
 
-[Full before/after table from Step 5.1]
-```
+## Phase 4: Shipcheck
 
-**3. Competitor Comparison:**
-```
-Found <N> competing CLIs.
-Best competitor: <name> (<stars> stars, <commands> commands)
-Strategic advantage: <why ours is better - from Phase 1 research>
-We beat them on: <specific features>
-Remaining gap: <what they have that we don't, or "none">
-```
+Run one combined verification block.
 
-**4. Example Commands (with complex body examples):**
 ```bash
-cd ~/cli-printing-press/library/<api>-cli
-go install ./cmd/<api>-cli
-
-export <AUTH_ENV_VAR>="..."
-
-# Basic usage
-<api>-cli --help
-<api>-cli doctor
-<api>-cli <resource> list --json
-<api>-cli <resource> get <realistic-id>
-
-# Complex body fields (pipe JSON via stdin)
-echo '<realistic-json>' | <api>-cli <resource> create --stdin
-
-# Agent workflow
-<api>-cli <resource> list --json --select id,name | jq -r '.[].id'
+cd ~/cli-printing-press
+./printing-press dogfood   --dir ./library/<api>-cli --spec <same-spec>
+./printing-press verify    --dir ./library/<api>-cli --spec <same-spec> --fix
+./printing-press scorecard --dir ./library/<api>-cli --spec <same-spec>
 ```
 
-**5. Spec source and limitations**
-
-**6. Future work** (from remaining gaps)
-
----
-
-# PHASE 5.5: LIVE API TESTING (optional - requires API key from Phase 0.1)
+Interpretation:
+- `dogfood` catches dead flags, dead helpers, invalid paths, example drift, and broken data wiring
+- `verify` catches runtime breakage and runs the auto-fix loop for common failures
+- `scorecard` is the structural quality snapshot, not the source of truth by itself
 
-Skip this phase entirely if no API key was provided in Phase 0.1.
+Fix order:
+1. generation blockers or build breaks
+2. invalid paths and auth mismatches
+3. dead flags / dead functions / ghost tables
+4. broken dry-run and runtime command failures
+5. scorecard-only polish gaps
 
-## Safety Rules (NON-NEGOTIABLE)
+Ship threshold:
+- `verify` verdict is `PASS` or high `WARN` with 0 critical failures
+- `dogfood` no longer fails because of spec parsing, binary path, or skipped examples
+- `scorecard` is at least 65, or meaningfully improved and no core behavior is broken
 
-These rules CANNOT be overridden. Violation = immediate abort.
+Maximum 2 shipcheck loops by default.
 
-1. ONLY execute HTTP GET operations (list, get, search, doctor)
-2. NEVER execute POST, PUT, PATCH, DELETE (no creating, updating, deleting, posting, sending)
-3. NEVER pass --stdin with body content to any command
-4. NEVER call webhook execute, message create, channel post, or any mutation endpoint
-5. Timeout: 10 seconds per call, 2 minutes total for all testing
-6. Stop immediately on 401/403 (don't burn rate limits on bad auth)
-7. Print every command to stderr BEFORE executing it
-8. Use --limit 1 on all list calls (minimize API usage)
-9. Use --max-pages 5 on sync (tiny scope)
+Write:
 
-## Test Sequence
+`~/cli-printing-press/docs/plans/<today>-fix-<api>-cli-shipcheck.md`
 
-1. Set the API key as env var: `export <ENV_VAR_NAME>="<key>"`
-2. `<cli> doctor` - validates auth works (expect 200 OK)
-3. Pick 3 list endpoints, run each with `--limit 1 --json`
-4. From the first list result, extract one ID
-5. Run `<cli> <resource> get <id> --json` to validate single-resource fetch
-6. If data layer exists: `<cli> sync --max-pages 5` to validate sync with tiny scope
-7. If search exists: `<cli> search "a" --limit 1` to validate search
-8. Report results:
+Include:
+- command outputs and scores
+- top blockers found
+- fixes applied
+- before/after verify pass rate
+- before/after scorecard total
+- final ship recommendation: `ship`, `ship-with-gaps`, or `hold`
 
-```
-LIVE API TEST RESULTS
-=====================
-Auth:     PASS/FAIL (doctor response)
-List:     N/M passed (resource names)
-Get:      PASS/FAIL (resource + ID)
-Sync:     PASS/FAIL (pages synced, blocks synced) or SKIPPED
-Search:   PASS/FAIL (result count) or SKIPPED
-Parsing:  N errors (list any JSON parsing failures)
-
-Verdict:  PASS/WARN/FAIL
-```
-
-### Step 5.5g: Data Pipeline Smoke Test
-
-After sync, verify data actually flowed through:
-
-1. Query entity counts:
-   ```bash
-   <cli> sql "SELECT 'pages' as t, COUNT(*) as n FROM pages
-              UNION ALL SELECT 'blocks', COUNT(*) FROM blocks
-              UNION ALL SELECT 'users', COUNT(*) FROM users"
-   ```
-2. **If ANY primary entity (from Phase 0.7) has 0 rows:**
-   - Verdict: **WARN** (not PASS)
-   - Report: "Sync completed but [entity] has 0 rows. Possible causes: integration permissions, empty workspace, or sync bug."
-   - Suggest: "Share at least one resource with the integration and re-run sync."
-3. **If primary entities have rows, test the read path:**
-   - `<cli> search "a" --limit 1` should return results
-   - `<cli> stale --days 9999` should return all pages
-   - `<cli> health` should show non-zero totals
-4. **If search returns 0 results but rows exist:**
-   - FTS5 indexing is broken. This is a FAIL, not a WARN.
-
-**"0 rows synced" is NEVER a PASS.** A pipeline that moves no data is not tested.
-
-If ANY test fails (WARN or FAIL verdict), automatically enter Phase 5.7 Ship Loop:
-
-1. For each failure, classify the bug:
-   - **Auth failure (401/403)**: Check client.go auth header format against spec's securitySchemes
-   - **Path not found (404)**: Check the URL path in the command file against the spec
-   - **Parse error**: Check response struct tags against actual API response shape
-   - **Sync failure**: Check pagination params, cursor handling, rate limiting
-   - **Timeout**: Check if the endpoint exists and the base URL is correct
-
-2. Write a targeted fix plan listing each bug with file:line and proposed fix
-3. Present the plan to the user: "Live testing found N bugs. Here's the fix plan. Proceed?"
-4. If yes: fix all bugs (delegate each code-writing patch to Codex if `CODEX_MODE` is true, otherwise fix directly in Claude), then re-run Phase 5.5 live tests to verify
-5. If all tests pass after fix: proceed to Final Report with PASS
-6. If still failing: report remaining issues, max 2 fix cycles for live test bugs
-
----
-
-# PHASE 5.7: SHIP LOOP
+## Phase 5: Optional Live Smoke
 
-This phase runs automatically when Phase 5.5 live tests find bugs, OR when the user asks "is this shippable?"
+Only run this if a token is available and the user agreed.
 
-## Auto-trigger from Phase 5.5
+Use read-only smoke tests:
+- `--help`
+- one or two representative GET/list commands
+- sync/search/health path if a local data layer exists
 
-When live API testing finds bugs, this phase runs immediately (no user prompt needed).
-The fix plan is derived directly from the test failures - concrete bugs with concrete fixes.
+If live smoke finds bugs:
+- fix only the real bug
+- re-run the shipcheck block once
 
-## Auto-trigger from "is this shippable?"
+Write:
 
-When the user asks "is this shippable?", "can we ship this?", "is it ready?", or similar:
+`~/cli-printing-press/docs/plans/<today>-fix-<api>-cli-live-smoke.md`
 
-1. Run the Quality Scorecard + Proof of Behavior verification
-2. If API key is available: also run Phase 5.5 live tests
-3. Collect all issues into a single list
-4. If PASS (score >= 65, no critical issues, live tests pass): "Yes, ship it. Quality Score: X/100."
-5. If WARN (minor issues only): "Shippable with caveats: [list]. Quality Score: X/100."
-6. If FAIL (critical issues):
-   - Present: "Not yet. Found N issues. Top 3:"
-   - List each issue with severity, file, and proposed fix
-   - Ask: "Want me to fix these and re-test?"
-   - If yes: write fix plan -> apply fixes (delegate code-writing patches to Codex if `CODEX_MODE` is true) -> re-run verification + live tests -> present updated score
-   - If no: present issues for manual review
+## Fast Guidance
 
-## Fix Loop Rules
+### When to use `printing-press print`
 
-- Max 3 fix-loop iterations per session
-- Each iteration targets only the top 3 highest-impact issues
-- If `CODEX_MODE` is true, each fix is a separate Codex call using the same Codex Delegation Pattern as Phase 4 and Phase 4.9
-- Claude still owns the bug triage, fix plan, verification, and go/no-go decision; Codex only writes the scoped patch
-- After each fix: `go build ./... && go vet ./...` must pass
-- After each fix: re-run Proof of Behavior verification
-- After each fix: if API key available, re-run live tests
-- After 3 iterations: report remaining issues and stop (avoid infinite loops)
-- Each iteration should show: score before -> score after -> delta
+Use `./printing-press print <api>` only when the user explicitly wants a resumable on-disk pipeline with phase seeds. It is optional.
 
----
+The fast path for `/printing-press <API>` is:
+- brief
+- generate
+- build
+- shipcheck
 
-# PHASE 5.9: OFFER EMBOSS
+### When to stop researching
 
-After presenting the final report (Phase 5), use AskUserQuestion to ask:
+Stop when you can answer:
+- what to build first
+- what data to persist
+- what incumbent features cannot be missing
 
-"The CLI scored [X]/100 (Grade [Y]). Want me to run an emboss pass to improve it further? This re-researches the landscape, finds the top 5 improvements, builds them, and re-scores."
+If the next research step does not change those answers, stop and generate.
 
-Options:
-- "Yes, run emboss" -> proceed to Emboss Mode (top of this skill)
-- "No, I'm done" -> end the run
-- "I'll emboss later" -> tell user they can run `/printing-press emboss ./<api>-pp-cli`
+### What not to do
 
-**WAIT for the user's answer before proceeding.** Do NOT continue or end the run until answered. Emboss is a FOLLOW-UP, not an automatic step. The user decides.
+Do not:
+- write 5 separate mandatory research documents
+- defer all workflows to “future work”
+- skip verification because the CLI compiles
+- treat scorecard alone as ship proof
+- discover YAML/URL spec incompatibility late and manually convert specs if the tools can already consume them
+- rerun the whole late-phase gauntlet for cosmetic README polish
 
----
+### What counts as success
 
-## Writing Specs from Docs
-
-When no OpenAPI spec exists:
-
-1. **WebFetch** the API docs
-2. **Read** `~/cli-printing-press/skills/printing-press/references/spec-format.md`
-3. Read the docs and identify EVERY endpoint
-4. Write YAML spec to `/tmp/<api>-spec.yaml`
-5. Generate from it
-
-You ARE the brain. Read the docs yourself and write the spec.
-
-## Submit to Catalog
-
-`/printing-press submit <name>` - gather metadata, write `catalog/<name>.yaml`, create PR.
-
-## Safety Gates
-
-- Preview before generating
-- Output directory conflict: check before overwriting
-- Untrusted specs: note if not from known-specs registry
-- Max 3 retries on quality gate failure
-
-## External Tool Interference
-
-Linters, formatters, and pre-commit hooks may modify files during the session. This is expected — but be aware:
-
-- **After any external modification to a file you depend on, re-read it before writing dependent code.** A linter may change method signatures (e.g., `GetSyncState` returning 2 values → 4 values), add imports, rename variables, or restructure functions. Code you write against the pre-linter version will fail to compile.
-- **Check the system-reminder after each tool call.** Claude Code shows diffs from external modifications in system reminders — read them.
-- **If a file was modified by a hook, the hook's version wins.** Don't fight it by reverting to your version. Adapt your dependent code to match the new signatures.
-
-## Scorecard Limitations
-
-The scorecard measures file patterns, not behavior. Known blind spots:
-
-- **sync_correctness** and **data_pipeline_integrity** hardcode the filename `sync.go` — sync logic in other files (e.g., `channel_workflow.go`, `sync_cmd.go`) scores 0.
-- **workflows** and **insight** use narrow prefix lists biased toward project-management APIs — scheduling, payment, and communication workflow commands may not match.
-- **dead_code** produces false positives when flags are passed via struct rather than accessed directly.
-
-**When the scorecard and verify disagree, verify is more authoritative.** A CLI that scores 57/100 on the scorecard but passes 91% of runtime tests is better than one that scores 80/100 but crashes on first use. Report both numbers. Don't chase scorecard points at the expense of actual behavior.
-
-## Anti-Shortcut Rules
-
-These phrases indicate a phase was shortcut. If you catch yourself writing them, STOP and re-do the phase:
-
-- "This is a limitation of the generator" (fix it, don't accept it)
-- "Complex types not supported" (add --stdin examples)
-- "We'll skip this for now" (no skipping - do it or explain why it's impossible)
-- "The quality is good enough" (score it against the quality bar, prove it's good enough with numbers)
-- "Let's wrap up" (are all 5 phases complete with artifacts?)
-- "This API doesn't need local persistence" (Did you run Phase 0? Check the data profile. If search need is high, it needs persistence.)
-- "This is just an API wrapper" (Run Phase 0 again. What would a thoughtful developer build?)
-- "The API is GraphQL-only so we can't use printing-press" (Wrong. Skip the REST generator, hand-write commands with a GraphQL client in Phase 4. Every other phase runs normally.)
-- "I'll polish the README instead of building workflows" (Phase 4 Priority 1 is workflows. README is Priority 3. Do not skip ahead.)
-- "The Phase 0.5 workflows are future work" (They are the product. Build them now or the CLI is just an API wrapper.)
-- "316 commands is better than 12" (Depth beats breadth - discrawl proves this. But depth means building the RIGHT 12 commands, not the same 12 commands for every API. Check the competitor feature matrix from Phase 0.6.)
-- "The API doesn't need local persistence" (Check data gravity scores from Phase 0.7. If any entity scores >= 8, it needs SQLite with proper columns.)
-- "FTS5 is overkill for this API" (If any entity has 2+ text fields AND data gravity >= 8, it needs FTS5. That's how search works.)
-- "REST polling is fine for tail" (Check if the API has WebSocket/SSE/Gateway. If yes, use it. REST polling misses events and wastes rate limit budget.)
-- "The generic store is good enough" (Domain-native tables ALWAYS beat JSON blob tables. A `messages` table with `channel_id`, `author_id`, `content` columns enables joins and filters that JSON blobs can't. Write the schema.)
-- "I'll build the data layer later" (Phase 0.7 runs BEFORE generation. The data layer spec informs Phase 4 Priority 0. Build it first, then workflows use it.)
-- "The artifact is just documentation" (The 7 plan artifacts ARE the product. They capture reasoning, evidence, and decisions. The generated CLI is a side effect.)
-- "The CLI compiles so it works" (Compilation proves syntax, not semantics. A command that builds can still 400 on every real call. Run the dogfood.)
-- "We can't test without API keys" (The OpenAPI spec defines response schemas. Generate mocks from the spec. Test against them. Zero keys needed.)
-- "The dry-run looks right" (Dry-run validates request construction. You also need to feed synthetic responses to validate output parsing, --select, and table rendering.)
-- "I'll add a helpers.go with the patterns the scorecard checks for" (STOP. Every function in helpers.go MUST be called by at least one command. Dead code is worse than missing code. Phase 4.6 will catch this.)
-- "The error handling score is low, let me add error types" (STOP. Error types must be used in actual error paths. Adding newAuthError() that nobody calls is gaming, not engineering.)
-- "I'll add --csv and --quiet flags to root.go" (STOP. Every registered flag must be checked in at least one RunE function. Flags nobody reads are dead flags. Phase 4.6 catches this.)
-- "I'll add insight command files to match the scorecard prefixes" (STOP. Insight commands must query tables that are actually populated. A health command querying an empty database is theater.)
-- "I'll skip the dogfood/verify to save time" (Skipping testing is how you produce a CLI that scores 73/100 with a broken core feature. The GitHub run proved this. Run `printing-press verify`.)
-- "The scorecard is 73 so it's good enough" (The scorecard measures files, not behavior. A 73 scorecard with 0% verify pass rate is a CLI that looks good on paper and crashes on first use. Run verify.)
-- "I tested 5 commands manually, that's enough" (5/127 is 3.9%. That's not testing. Run `printing-press verify` which tests every command automatically in under 60 seconds.)
-- "The CLI compiles so it's ready to ship" (Compilation proves syntax. `printing-press verify` proves behavior. A CLI that compiles but 404s on sync is not shippable.)
-- "The generated command names are fine" (They're machine names from operationIds. Normalize them: retrieve-a -> get, post -> create. Phase 4 Priority 3.)
-- "The module path is close enough" (It's a Go import path. It must be exact or `go install` fails for everyone.)
-- "0 rows synced is still a PASS" (A pipeline that moves no data is not tested. It's WARN at best.)
-- "Users can go install it" (Most users don't have the Go toolchain. Add goreleaser. Phase 4 Priority 6.)
-- "I chose the name in Phase 0.8" (Choosing isn't applying. Grep for the old name. If it appears, the rename is incomplete.)
-- "The scorecard is the objective" (The scorecard measures proxies. The objective is: would a user of the top competitor switch? Check the three-benchmark gate.)
-- "We complement the incumbent, we don't compete" (Users don't want two CLIs. If the incumbent has a feature, you need it too - Phase 0.6 table stakes.)
-- "That feature is anti-scope" (If a competitor with >100 stars has it, it's not anti-scope. It's a backlog item. Phase 0.6 classification rules.)
-- "I'll skip the agent readiness review" (You MUST attempt the dispatch. If the agent isn't installed, the dispatch will fail and you warn the user. If it IS installed, it runs. You don't get to decide it's unnecessary.)
-- "The CLI is already agent-native enough" (The scorecard checks for flags. The agent readiness reviewer checks 7 deeper principles — alias correctness, output routing, delete safety gates, exit code semantics. These are different things. Dispatch it.)
-
-**Module path rule:**
-- The go.mod module path MUST be a valid Go import path with a real org name (e.g., `github.com/mvanhorn/discord-cli`). The literal string `USER` is never acceptable. The generator auto-derives from git config.
-
-**Time Budget Guidance:**
-- Phase 0-1 (Research + Parity Audit): 20% of total time
-- Phase 2 (Generate + Normalize Names): 5%
-- Phase 3 (Audit + Competitor Comparison): 5%
-- **Phase 4 Priority 0 (Data Layer): 15%** - The architecture foundation.
-- **Phase 4 Priority 1 (Table Stakes): 15%** - Match the competition. THIS IS THE PRODUCT.
-- Phase 4 Priority 2 (Workflows): 10%
-- Phase 4 Priority 3-7 (Names, Score, Tests, Distribution): 15%
-- Phase 4.5-4.8 (Dogfood + Verify): 10%
-- Phase 5 (Final Report + Emboss Offer): 5%
-
-Table stakes features get 15% - the same as the data layer. Because matching the competition IS the product.
-
-**Scorecard uses two tiers (100-point scale):**
-- Tier 1: Infrastructure (string-matching, 50 max) - does the skeleton have the right patterns?
-- Tier 2: Domain Correctness (semantic, 50 max) - does the code actually work?
-- Use `--spec` flag on scorecard command to enable Tier 2 validation against the OpenAPI spec.
-
-**Discrawl benchmark for communication APIs:**
-- After Phase 4, ask: "Would a discrawl user switch to this CLI?" If the answer is "no because [feature X]", that's your remaining Phase 4 work.
+Success is:
+- a generated CLI that gets to shipcheck without generator blockers
+- verification tools working against the same spec the user generated from
+- one or two fix loops, not a maze of re-entry phases
+- a CLI that is plausibly shippable today, not a perfect design memo

← 25b07be0 fix(skill): enforce 2-pass agent readiness reviewer loop (#2  ·  back to Cli Printing Press  ·  feat(skill): add Phase 1.5 Ecosystem Absorb Gate - build the 49b98a3b →