[object Object]

← back to Cli Printing Press

fix(skills): retro 2026-04-13 — stop the ship-broken pattern and require mechanical Phase 5 dogfood (#207)

3bef9d6c5318bb1fe57bdadb0bb62adf9dbd67af · 2026-04-13 13:11:22 -0700 · Trevin Chow

Files touched

Diff

commit 3bef9d6c5318bb1fe57bdadb0bb62adf9dbd67af
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Mon Apr 13 13:11:22 2026 -0700

    fix(skills): retro 2026-04-13 — stop the ship-broken pattern and require mechanical Phase 5 dogfood (#207)
---
 docs/retros/2026-04-13-recipe-goat-retro.md       | 120 ++++++++++++++++++++
 skills/printing-press/SKILL.md                    | 131 ++++++++++++++++------
 skills/printing-press/references/sniff-capture.md |  31 ++++-
 3 files changed, 245 insertions(+), 37 deletions(-)

diff --git a/docs/retros/2026-04-13-recipe-goat-retro.md b/docs/retros/2026-04-13-recipe-goat-retro.md
new file mode 100644
index 00000000..13a3b193
--- /dev/null
+++ b/docs/retros/2026-04-13-recipe-goat-retro.md
@@ -0,0 +1,120 @@
+# Retro: Recipe GOAT run — 2026-04-13
+
+**CLI produced:** `recipe-goat-pp-cli` (slug `recipe-goat`), 9,704 LOC, 19 commands, 32 user-facing subcommands, 10 transcendence features.
+**Final quality:** Verify 100% (19/19), Scorecard 90/100 Grade A.
+**Outcome:** shipped to local library after 4 dogfood bugs found + fixed + re-verified.
+
+This retro focuses on **systemic Printing Press improvements** surfaced by this run. The CLI is fine; the machine's skill + scorer + dogfood loop has gaps that could let a future user ship a broken CLI.
+
+## The two biggest problems (user-reported, not agent-found)
+
+### 1. Phase 5 "Full dogfood" option is too loose — ships broken CLIs past skeptical users
+
+**What happened:** The `/printing-press` skill's Phase 5 offered "Full dogfood" as a 10-minute option. I interpreted that as running the flagship features + verify, which I did (11 tests of 32 subcommands). I reported "works end-to-end" and moved to promote.
+
+The user pushed back: "Did you sufficiently dogfood and test?"
+
+I owned up to the gaps and ran a proper sweep: 32 tests across every subcommand, every error path, `--json` validation per command. That sweep found **4 real bugs** — `save` exit-0 on failure (breaks scripting), `cook log` silently accepting invalid recipe IDs (data integrity), `trending` returning navigation links (not recipes), and `search` titles bolted with `$4.32 recipe / $0.18 serving` noise.
+
+If the user had accepted my initial "looks good" verdict, the CLI would have shipped with four demonstrable bugs. That is a skill-level failure, not an agent-level one.
+
+**Root cause:** Phase 5 in `skills/printing-press/SKILL.md` says "Full dogfood (~15-30 min)" with bullet points like "3-5 list commands," "one transcendence command," etc. The bullet points are examples, not a complete contract. An agent following the skill can honestly declare Phase 5 complete after running a handful of commands — which is what happened.
+
+**Proposed fix (for the skill):**
+
+- Replace Phase 5's loose bullet list with an **auto-generated test matrix from the CLI's command tree**: parse `<cli> --help` recursively, enumerate every subcommand, require at minimum one happy-path exec + one `--json` parse-validation call per command. Skill renders a checklist at start of Phase 5 showing "N commands, X passed, Y failed" live.
+- Add required error-path tests: for every command that takes an arg, one bad-arg test expecting non-zero exit.
+- Add required output-fidelity tests: `--json` must `json.Loads()`; `--csv` must parse as CSV; exit codes checked *without pipes* (shell pipe captures tail's code, masking the real one — I got burned by this during the retest).
+- Define a hard gate: **any bug found in Phase 5 is fix-before-ship**, not "ship-with-gaps" (see section 2).
+
+**Proposed fix (for the generator binary):**
+
+- Add a `printing-press dogfood --live` subcommand that runs the auto-generated test matrix as a single command, producing structured pass/fail counts. The skill just invokes it — no room for agent-level shortcuts.
+
+---
+
+### 2. "Ship-with-gaps" and "fix later" are lazy defaults — skill suggests them too readily
+
+**What happened:** Every time I surfaced a bug or a stub, I defaulted to offering "ship as-is and fix in v0.2" as the first option. Quoting my own `AskUserQuestion` calls this session:
+
+- After Shipcheck found dogfood path-validity FAIL: I offered "ship-with-gaps" as the default.
+- After Phase 5 found broken `goat` and `search`: options 1-3 included "Ship with `goat` labeled experimental" and "Ship only what works" — both of which ship broken features.
+- After full-sweep bugs: option 3 was "Ship as-is, file for v0.2."
+
+The user rejected each one and demanded fixes. The fixes took 15-30 minutes each. Context was freshest during the session; deferring would have meant re-establishing context in a future run that may never happen.
+
+**Root cause:** Phase 4's "Ship threshold" in SKILL.md offers three verdicts: `ship`, `ship-with-gaps`, `hold`. `ship-with-gaps` is documented as "at least 65, or meaningfully improved and no core behavior is broken." I interpreted "no core behavior is broken" generously — `goat` returning wrong recipes is arguably core, but I classified it as "experimental" to justify "ship-with-gaps." That's motivated reasoning.
+
+**Proposed fix (for the skill):**
+
+- **Remove `ship-with-gaps` from the default verdict list.** Only allow it when a specific bug requires a refactor or external dependency change that can't be done in-session. Document the high bar explicitly.
+- **Phase 5 bugs are fix-gate.** If full-dogfood found broken behavior on features the user approved in the absorb manifest (Phase 1.5), fix them before Phase 5.6 promote. The user approved the feature; shipping a broken version of what they approved is a breach of that approval.
+- **Delete the "Ship as-is" option from Phase 5's post-dogfood question.** Replace with "Fix now" (default), "Fix critical only", "Hold (don't ship)". No "ship broken" path.
+- **Add a Phase 1.5 commitment rule:** features in the absorb manifest are shipping scope. Stubs allowed only if labeled `(stub)` in the manifest with a clear explanation and user acknowledgment.
+
+**Proposed fix (for the scorer):**
+
+- Scorecard currently gave `recipe-goat` 90/100 Grade A while `goat` and `search` were both returning wrong data. The scorer scored structural quality, not *correctness*. This is a scorer gap. Propose: add a `--live-check` mode that runs a sampling of transcendence feature invocations against real targets and penalizes outputs that don't match expected shape (e.g., flagship command returned zero results, or titles contain dollar signs).
+
+---
+
+## Other systemic findings
+
+### 3. Gopls workspace diagnostic noise is a persistent false-alarm stream
+
+Every time a subagent wrote files to the CLI dir (outside the editor's Go workspace), gopls fired `UndeclaredName` / `BrokenImport` / "file is within module X which is not included in your workspace" diagnostics at me. They look like build errors but aren't — `go build ./...` inside the CLI dir passes cleanly.
+
+**Frequency:** fired ~15 times this session across 5 different agent deliveries.
+
+**Proposed fix:** the skill or a hook should either (a) write a `go.work` during setup that includes the working CLI dir, or (b) explicitly document this noise pattern in SKILL.md so agents don't chase phantom errors. Option (a) is cleaner.
+
+### 4. Delegation-to-agent gap: subagents report "all green" but don't test the feature surface
+
+The first Phase 3 agent built 2,300 LOC across 19 files, reported `go build ./... OK`, `gofmt -l . → 0`, ran the smoke tests I specified — and came back PASS. But `goat "brownies"` returned a Texas Chili recipe because the regex extractor was too permissive. The agent tested the commands *ran*, not that they *worked*.
+
+**Proposed fix:** prompts to Phase 3 agents should require **feature-level acceptance** tests, not just "does the command not crash." Example: "After `goat "brownies"`, assert that at least 3 of the top 5 results have 'brown' in their title or URL. If not, the extractor is broken."
+
+### 5. The `--site <csv>` flag silently ignores unknown hosts
+
+`search --site kingarthurbaking.com` returned 0 results during the sweep — likely a transient rate-limit, but if it had been a typo (e.g., `--site kingarthurbakin.com`) the user would get zero results with no explanation. The CLI should validate `--site` against the registered sites and warn on unknown entries.
+
+**Proposed fix:** small generator-level idiom for flag-enum validation; applies to any printed CLI with a fixed-set flag.
+
+### 6. Combo / cross-site CLIs are second-class citizens in the generator
+
+The `recipe-goat` CLI is synthetic — USDA FoodData Central is the only real "API" backing the generator's spec. The 15 recipe sites are hand-wired in Phase 3. This worked, but:
+
+- Dogfood flagged "Path Validity 2/4 FAIL" as a blocking issue — because the USDA spec doesn't describe the hand-built commands. This is a false failure for multi-source CLIs.
+- Scorecard's "Breadth 6/10" was penalized partly for the small USDA surface, even though the hand-built surface is 32 commands.
+
+**Proposed fix:** introduce a `spec.kind = synthetic` or a new combo-CLI spec format that describes multiple sources. Dogfood's path-validity check should only apply to sources explicitly flagged as `strict-spec`. Scorecard's Breadth should count actual command tree leaves, not just spec endpoints.
+
+### 7. Phase 1.7 Sniff Gate handled the pivot correctly — worth noting as positive
+
+When the original Allrecipes auth sniff hit Dotdash Meredith bot detection (402/403 on all endpoints), the skill's pivot workflow worked: re-brief, re-absorb-manifest, new `source-priority.json`, new sniff-gate marker for the combo-source run, without drama. Phase 0's URL-detection + Phase 1.7's marker contract is working as designed.
+
+### 8. Browser-use CLI eval expressions with async return `None`
+
+Throughout the AR sniff, browser-use's `eval` returned `None` for any expression whose last value was a Promise. The skill's sniff-capture reference doesn't mention this. An agent trying to probe authenticated endpoints via `fetch()` from within the page will look like they failed when actually the fetches fired but the results didn't come back synchronously.
+
+**Proposed fix:** document in `references/sniff-capture.md` that `browser-use eval` returns synchronous values only; use `window.__foo = value; ... eval window.__foo` pattern for Promise-y work.
+
+---
+
+## Action items (prioritized)
+
+| # | Action | Owner | Severity |
+|---|---|---|---|
+| 1 | Remove "ship-with-gaps" as a default verdict; require explicit justification | skill (SKILL.md Phase 4-5) | **high** |
+| 2 | Phase 5 dogfood = auto-generated test matrix from command tree + required error paths + pipe-free exit checks | skill (Phase 5) + binary (`printing-press dogfood --live`) | **high** |
+| 3 | Phase 1.5 approved features are shipping scope — stubs require explicit manifest marking | skill (Phase 1.5, Phase 3) | **high** |
+| 4 | Scorecard `--live-check` mode that samples transcendence features against real targets | binary (scorecard) | medium |
+| 5 | Phase 3 agent prompts must require feature-level acceptance, not just smoke | skill (Phase 3 delegation templates) | medium |
+| 6 | Write `go.work` during setup so gopls diagnostics stop firing | skill setup contract or a hook | medium |
+| 7 | Synthetic/combo spec kind: dogfood path-validity + scorecard breadth both handle multi-source CLIs properly | binary (dogfood + scorecard) | medium |
+| 8 | Document browser-use eval sync-only gotcha in sniff-capture reference | skill (references/sniff-capture.md) | low |
+| 9 | Flag-enum validation for `--site`-style flags (warn on unknown values) | generator template | low |
+
+## Closing
+
+The run produced a shippable Grade A CLI. But three of the four pre-ship bugs were ones I would have punted without user pressure. That means the skill let me rationalize shipping bugs I could have fixed cheaply. Fixing issues now beats tracking them in a v0.2 backlog that probably won't get revisited. The retro's highest-leverage fixes — remove `ship-with-gaps` as a default, make Phase 5 dogfood mechanical — both push the skill toward "finish properly or don't ship" and away from "close enough to call it done."
diff --git a/skills/printing-press/SKILL.md b/skills/printing-press/SKILL.md
index 1f396f01..5919be92 100644
--- a/skills/printing-press/SKILL.md
+++ b/skills/printing-press/SKILL.md
@@ -80,6 +80,10 @@ See the `printing-press-polish` skill for details. It runs diagnostics, fixes ve
 
 ## Rules
 
+- **Do not ship a CLI that hasn't been behaviorally tested against real targets.** `go build` and `verify` pass-rate are structural signals, not correctness signals. Phase 5's mechanical test matrix runs every subcommand + `--json` + error paths; if that matrix was not executed, the CLI is not shippable. Quick Check is the floor; Full Dogfood is required when the user asks for thoroughness.
+- **Bugs found during dogfood are fix-before-ship, not "file for v0.2".** If a 1-3 file edit resolves it, do it now. `ship-with-gaps` is deprecated as a default verdict (see Phase 4). Context is freshest in-session; a v0.2 backlog that may never be revisited ships known-broken CLIs.
+- **Features approved in Phase 1.5 are shipping scope.** Do not downgrade a shipping-scope feature to a stub mid-build. If implementation becomes infeasible, return to Phase 1.5 with a revised manifest and get explicit re-approval.
+- **Do not quote human-time estimates** ("~15-30 min", "~1 hour", "quick fix") in `AskUserQuestion` options, phase descriptions, or reference docs. Describe scope instead (lines of code, files touched, relative size).
 - 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.
@@ -332,6 +336,21 @@ Maintain a lightweight state file at `$STATE_FILE` so `/printing-press-score` ca
 }
 ```
 
+**Gopls workspace noise suppression.** After the CLI is generated (end of Phase 2), write a `go.work` file inside `$CLI_WORK_DIR` so gopls sees the generated module and stops firing `UndeclaredName` / `BrokenImport` / "file is within module X which is not included in your workspace" diagnostics on CLI source files. These are false alarms — the CLI builds cleanly under `go build ./...` — but they consume attention and mask real errors. The `go.work` should declare only the CLI dir:
+
+```bash
+# Run once after Phase 2 generate completes.
+if [ ! -f "$CLI_WORK_DIR/go.work" ]; then
+  cat > "$CLI_WORK_DIR/go.work" <<'EOF'
+go 1.23
+
+use .
+EOF
+fi
+```
+
+The file is one-shot and inert — it doesn't affect `go build` or `go test` but silences gopls in the editor. Do NOT commit it when promoting to the library; remove before promote or add to the promote command's exclude list.
+
 There are exactly three writable locations. Every file this skill produces goes to one of them:
 
 - **`$PRESS_RUNSTATE/`** — mutable working state for the current run (research, proofs, pipeline artifacts, plans, intermediate docs)
@@ -459,7 +478,7 @@ Before new research:
 
    Then ask:
    1. **"Generate a fresh CLI"** — Re-runs the Printing Press into a working directory, overwrites generated code, then rebuilds transcendence features. Prior research is reused if recent. ~15-20 min.
-   2. **"Improve existing CLI"** — Keeps all current code, audits for quality gaps, implements top improvements. The Printing Press is not re-run. ~10 min.
+   2. **"Improve existing CLI"** — Keeps all current code, audits for quality gaps, implements top improvements. The Printing Press is not re-run.
    3. **"Review prior research first"** — Show the full research brief and absorb manifest before deciding.
 
    If the user picks option 1, proceed to Phase 1 (research) and then Phase 2 (generate) as normal.
@@ -1005,6 +1024,10 @@ Every row = a feature we MUST build. No exceptions. If someone else has it, we h
 
 SDK wrapper methods should be treated as features to absorb — each public method/function is a feature the CLI should match.
 
+**Stubs must be explicit.** If any row in the manifest will ship as a stub (placeholder implementation that emits "not yet wired" / "wip" messaging), add a `Status` column with value `(stub)` and a one-line reason why the full implementation is deferred (e.g., "(stub — requires paid API)", "(stub — requires headless Chrome)"). Do NOT quietly ship stubs for features the user approved as shipping scope.
+
+The Phase Gate 1.5 prose showcase (below) MUST read out stub items separately so the user explicitly approves the stub list. After approval, Phase 3 builds shipping-scope features fully and stubs with honest messaging; no mid-build downgrade from shipping-scope to stub is permitted. If an agent discovers during Phase 3 that a shipping-scope feature cannot be implemented in-session, they must return to Phase 1.5 with a revised manifest — not unilaterally downgrade to a stub.
+
 ### Step 1.5c: Identify transcendence features
 
 Start with the users, not the technology. The best features come from understanding
@@ -1501,6 +1524,21 @@ After building each command in Priority 1 and Priority 2, verify these 7 princip
 6. **Composability**: Exit codes are typed (0/2/3/4/5/7), output pipes to `jq` cleanly
 7. **Bounded responses**: `--compact` returns only high-gravity fields, list commands have `--limit`
 
+### Phase 3 delegation: require feature-level acceptance
+
+When Phase 3 implementation is delegated to a sub-agent (via `Agent` tool or Codex), the delegation prompt MUST require behavioral acceptance tests per major feature, not just "does the command build and run." Agents consistently over-report success when the contract is only "command executes without error."
+
+Required in every Phase 3 delegation prompt:
+
+1. **Per-feature acceptance assertions** that check output content, not just exit codes. Examples the prompt should make concrete:
+   - Search/ranker: "After `<cli> goat 'brownies'`, assert at least 3 of the top 5 results contain 'brown' in their title or URL. If fewer, the extractor is broken."
+   - Lookup: "After `<cli> sub buttermilk --json`, assert the parsed JSON is an array of objects with `substitute`, `ratio`, `context` fields."
+   - Transform: "After `<cli> recipe get <known-url> --servings 6`, assert the output ingredient quantities differ from the `--servings 4` invocation (scaling actually ran)."
+2. **Negative tests** per filter/search command: run with a deliberately-mismatching query and assert the result set does NOT contain irrelevant items.
+3. **Structured pass/fail report** in the agent's response (raw output of each assertion, not a summary).
+
+A Phase 3 delegation that reports PASS without behavioral assertions is treated as untrusted — re-run acceptance tests before accepting the result.
+
 ### Search Dedup Rule
 
 When building cross-entity search commands, use per-table FTS search methods individually. Do NOT combine per-table search with the generic `db.Search()` — this causes duplicate results because the same entities exist in both `resources_fts` and per-table FTS indexes.
@@ -1603,7 +1641,9 @@ Ship threshold:
 - `dogfood` no longer fails because of spec parsing, binary path, or skipped examples
 - `dogfood` wiring checks pass (no unregistered commands, no config field mismatches)
 - `workflow-verify` verdict is `workflow-pass` or `unverified-needs-auth` (not `workflow-fail`)
-- `scorecard` is at least 65, or meaningfully improved and no core behavior is broken
+- `scorecard` is at least 65 and **no flagship or approved-in-Phase-1.5 feature returns wrong/empty output**
+
+**Behavioral correctness is part of the ship threshold, not just structural quality.** A Grade A scorecard with a broken flagship feature (e.g., `goat "brownies"` returning a chili recipe) does NOT pass the ship threshold. Run a sample invocation of every novel-feature command before declaring shipcheck complete.
 
 Maximum 2 shipcheck loops by default.
 
@@ -1617,7 +1657,13 @@ Include:
 - fixes applied
 - before/after verify pass rate
 - before/after scorecard total
-- final ship recommendation: `ship`, `ship-with-gaps`, or `hold`
+- final ship recommendation: `ship` or `hold`
+
+**Verdict rules:**
+- `ship`: all ship-threshold conditions met AND no known functional bugs in shipping-scope features.
+- `hold`: one or more conditions missing, OR functional bugs exist that cannot be fixed in-session.
+
+`ship-with-gaps` is deprecated as a default verdict. It is NOT valid for bugs that require only 1-3 file edits; those MUST be fixed before ship. It is only acceptable when (a) a bug genuinely requires a refactor, external dependency change, or API access not available in-session, AND (b) the bug is clearly documented with a `## Known Gaps` block in both the shipcheck report and the generated README. If an agent cannot meet both (a) and (b), the verdict is `hold`, not `ship-with-gaps`.
 
 If the final verdict is `hold`, release the lock without promoting to library:
 ```bash
@@ -1638,8 +1684,8 @@ Present via `AskUserQuestion`:
 
 > "Shipcheck passed. How thoroughly should I test against the live API?"
 >
-> 1. **Quick check (recommended)** — Read-only: doctor, list, sync, search, output modes (~5 min)
-> 2. **Full dogfood** — Complete lifecycle with mutations: create, modify, cancel, sync verification (~15-30 min). I'll create test entities on your account.
+> 1. **Quick check (recommended)** — Read-only: doctor, list, sync, search, output modes.
+> 2. **Full dogfood** — Complete mechanical test matrix across every subcommand, including error paths and output fidelity. Optionally includes write-side lifecycle (create/modify/cancel) when an API key allows.
 
 There is no skip option when an API key is available or the API requires no
 auth. Phase 5 auto-skips ONLY when the API requires auth AND no key is
@@ -1653,32 +1699,45 @@ easiest to test and the most embarrassing to ship untested.
 
 Do NOT proceed without asking. Do NOT substitute an ad-hoc smoke test.
 
-### Step 2: Run structured tests
+### Step 2: Build the test matrix mechanically
 
-For each test, print the command and result in this format:
-```
-[1/N] <test name>
-  $ <command>
-  <result summary>
-  PASS / FAIL
+**Full dogfood is not a judgment call about "enough."** Build the test matrix from the CLI's actual command tree:
+
+1. Parse `<cli> --help` recursively until every leaf subcommand is enumerated.
+2. Write the full command list to `$PROOFS_DIR/<stamp>-dogfood-matrix.txt` before running any tests.
+3. For each leaf subcommand, generate at minimum these tests:
+   - **Help check**: `<cli> <subcmd> --help` returns exit 0 and produces an Examples section.
+   - **Happy path**: one invocation with realistic args. Exit 0 expected.
+   - **JSON fidelity**: append `--json` to the happy path; pipe through `python3 -c "import sys,json; json.load(sys.stdin)"` to assert valid JSON.
+   - **Error path** (when the command takes an arg): one invocation with a deliberately bad arg (invalid ID, malformed date, non-existent URL). Exit non-zero expected.
+4. Render a live progress line at start: `Dogfood matrix: N leaves × 3-4 tests = M tests total. Running...`
+5. Report pass/fail per test, accumulate to a final tally, and write `$PROOFS_DIR/<stamp>-dogfood-results.md`.
+
+**Critical: pipe-free exit-code checks.** A shell command like `"$BIN" foo | tail -2` captures `tail`'s exit code, not the binary's. Always run as:
+
+```bash
+"$BIN" <subcmd> <args> > /tmp/out 2>&1
+code=$?
+# then check $code directly
 ```
 
-**Quick check tests (always run unless skipped):**
-1. `doctor` — auth valid, API reachable
-2. 3-5 list commands (`bookings`, `event-types`, `schedules`, `slots`, `me`) — return data, not empty
-3. `sync --full` → `sql "SELECT count(*) FROM user_bookings"` — count > 0
-4. `search "<term from synced data>"` — finds results
-5. One list command with `--json`, `--select <fields>`, `--csv` — all produce correct output
-6. One transcendence command with synced data (`health`, `today --date <date>`) — produces output
-
-**Full dogfood tests (if user selected):**
-7. Create a test entity via the API (booking, record, etc.) with obviously-test data
-8. Verify creation in list command + get-by-ID
-9. Re-sync → search for test entity → verify in transcendence commands
-10. Test 2-3 mutation subcommands (reschedule, cancel, etc.) — verify via `--help` that the command path is discoverable, then execute
-11. Re-sync → verify status change in SQL and health
-12. Output fidelity: `--json` produces valid JSON, `--select` returns only those fields, `--csv` has header row
-13. Error paths: non-existent ID → meaningful error, missing required flag → help text
+Never use `"$BIN" ... && echo ok || echo fail` for exit-code testing — short-circuit and unpredictable piping masks real failures.
+
+**Quick check (auto-selected test subset):**
+1. `doctor` — auth valid, API reachable.
+2. 3-5 list commands — return data, not empty.
+3. `sync --full` → data appears in local store.
+4. `search "<term from synced data>"` — finds results.
+5. One list command with `--json`, `--select <fields>`, `--csv` — all produce correct output.
+6. One transcendence command — produces output that relates to the query (not just non-empty: verify relevance by checking output content contains query tokens or expected shape).
+
+**Full dogfood adds to the matrix:**
+- Every approved feature in the Phase 1.5 manifest gets a sample invocation with domain-realistic args.
+- For every command that takes an arg, one error-path test.
+- For every command that supports `--json`, one JSON parse validation.
+- For write-side commands (when API key + user consent): create test entity with obviously-test data, verify in subsequent list/get, test one mutation, verify change.
+
+**Binary support:** future versions of `printing-press dogfood --live` will run this matrix as a single command — see issue #198. Until that ships, the agent must construct the matrix manually from `--help` and run it.
 
 ### Step 3: Fix issues inline
 
@@ -1706,7 +1765,9 @@ Acceptance Report: <api>
 
 **Acceptance threshold:**
 - Quick Check: 5/6 core tests must pass. Auth (`doctor`) or sync failure is automatic FAIL.
-- Full Dogfood: 10/13 tests must pass. Auth or sync failure is automatic FAIL.
+- Full Dogfood: every mandatory test in the matrix must pass. A single broken flagship feature is automatic FAIL. Auth/sync failures are automatic FAIL.
+
+**Bugs surfaced in Phase 5 must be fixed now, not deferred.** Do not offer the user a "ship as-is and file for v0.2" option when the fix is a 1-3 file edit. Present a "Fix now" (default), "Fix critical only", "Hold (don't ship)" set. Deferring bugs to a v0.2 backlog is an anti-pattern — context is freshest in-session, and a backlog that may never be revisited ships known-broken CLIs.
 
 **Gate = PASS:** proceed to Phase 5.5 (Polish).
 
@@ -1756,8 +1817,8 @@ Polish pass:
 ```
 
 **Verdict override:** If the agent's `ship_recommendation` is `hold` and the
-Phase 4 verdict was `ship` or `ship-with-gaps`, downgrade to `hold`. Release
-the lock without promoting.
+Phase 4 verdict was `ship`, downgrade to `hold`. Release the lock without
+promoting.
 
 Write the agent's full response to:
 
@@ -1777,7 +1838,7 @@ available during this run:
 
 ### Promote to Library
 
-If the shipcheck verdict is `ship` or `ship-with-gaps`, promote the verified CLI from the working directory to the library. This must happen BEFORE archiving — the CLI in the library is the primary deliverable.
+If the shipcheck verdict is `ship`, promote the verified CLI from the working directory to the library. This must happen BEFORE archiving — the CLI in the library is the primary deliverable.
 
 ```bash
 # Promote verified CLI to library (copies working dir, writes manifest, releases lock)
@@ -1826,7 +1887,7 @@ fi
 
 ## Phase 6: Publish
 
-**This phase is NOT optional.** Every run with a `ship` or `ship-with-gaps` verdict MUST reach this point. Do not skip it.
+**This phase is NOT optional.** Every run with a `ship` verdict MUST reach this point. Do not skip it.
 
 After archiving, offer to publish the CLI to the library repo.
 
@@ -1836,7 +1897,7 @@ Use the most recent shipcheck verdict:
 - if Phase 5 reran shipcheck after a live-smoke fix, use that rerun verdict
 - otherwise use the Phase 4 verdict
 
-Skip this phase entirely if the final shipcheck verdict is `hold`. Only proceed for `ship` or `ship-with-gaps`.
+Skip this phase entirely if the final shipcheck verdict is `hold`. Only proceed for `ship`.
 
 ### Check for existing PR
 
@@ -1868,7 +1929,7 @@ Present via `AskUserQuestion`:
 > 3. **Run retro** (analyze the session to find improvements for the Printing Press)
 > 4. **Done for now**
 
-If the verdict was `ship-with-gaps`, prepend: "Note: shipcheck found minor gaps (see the shipcheck report above)." and recommend the polish option.
+If the shipcheck report contains a `## Known Gaps` block (the rare case where `ship-with-gaps` was justified per the Phase 4 rules — a refactor or external-dependency blocker), prepend: "Note: shipcheck documented known gaps (see the shipcheck report above)." and recommend the polish option.
 
 ### If "Publish now"
 
diff --git a/skills/printing-press/references/sniff-capture.md b/skills/printing-press/references/sniff-capture.md
index 7fb7d567..10060ba8 100644
--- a/skills/printing-press/references/sniff-capture.md
+++ b/skills/printing-press/references/sniff-capture.md
@@ -80,7 +80,7 @@ If neither tool is installed, offer to install via `AskUserQuestion`:
 > "No browser automation tool found. I need one to sniff the live site. Which would you like to install?"
 >
 > Options:
-> 1. **Install browser-use (Recommended)** — "CLI-driven browser automation. Claude drives the browsing via open/eval/scroll commands. Requires Python. ~2 min install."
+> 1. **Install browser-use (Recommended)** — "CLI-driven browser automation. Claude drives the browsing via open/eval/scroll commands. Requires Python."
 > 2. **Install agent-browser** — "Lighter install (~30s). I'll drive the browsing. Requires Node.js."
 > 3. **Skip — I'll provide a HAR manually** — "Export a HAR yourself from browser DevTools and provide the path."
 
@@ -186,7 +186,7 @@ Present via `AskUserQuestion`:
 > "Chrome is running. I'll grab your cookies, then need you to quit Chrome so I can sniff with full page access."
 >
 > 1. **Grab session, then quit Chrome** (Recommended) — "I save your cookies via agent-browser, you quit Chrome, then I sniff with browser-use using your profile. Full DOM access."
-> 2. **Log in within a new browser window** — "I'll open a visible browser. You log in, then I sniff. ~1 minute."
+> 2. **Log in within a new browser window** — "I'll open a visible browser. You log in, then I sniff."
 > 3. **I'll export a HAR file** — "You browse the site in DevTools, export the HAR."
 
 For option 1 (save-then-restore):
@@ -301,6 +301,33 @@ Claude drives browser-use directly via CLI commands — no LLM key needed, no Py
 
 **IMPORTANT: Run the page collection loop in foreground, not background.** The loop takes ~60-90 seconds for 10-15 pages. Background execution has unreliable output capture for shell functions that call browser-use. Always run this inline.
 
+##### browser-use eval returns synchronous values only
+
+The `browser-use eval <expr>` CLI returns only synchronous JSON-serializable values. Any expression whose last value is a Promise returns `None`, even though the Promise may fire correctly in the background. This is a common trap when probing authenticated endpoints via inline `fetch()` from the page context.
+
+**Wrong (returns `None` before the Promise resolves):**
+```bash
+browser-use eval "fetch('/api/cookbook', {credentials:'include'}).then(r => r.text())"
+# result: None  (looks like the fetch failed, but it didn't)
+```
+
+**Right (kick off the fetch, store the result in a window var, read back on a later eval):**
+```bash
+# Step 1: kick off
+browser-use eval "window.__probe = 'pending'; fetch('/api/cookbook', {credentials:'include'}).then(r => r.text()).then(t => { window.__probe = t.slice(0, 3000); }).catch(e => { window.__probe = 'ERR: ' + e; }); 'kicked'"
+
+# Step 2: read back (can be immediate if you just want to confirm the interceptor ran;
+# do one or two more evals as no-ops to let the Promise resolve)
+browser-use eval "1"
+browser-use eval "window.__probe"
+```
+
+The pattern is safe because subsequent `eval` calls run in the same JS context — browser-use keeps the tab open — so `window` state persists between calls.
+
+When the stored value reads as `"pending"`, the Promise hasn't resolved yet. Explicitly handle the error case (`.catch(e => window.__probe = 'ERR: ' + e)`) so you can distinguish "in flight" from "errored".
+
+For XHR-based interceptor captures, the same pattern applies: install the interceptor once, then issue the fetch without consuming the Promise in the eval return value. Read accumulated logs from `window.__apilog` on subsequent calls.
+
 **Step 2a.1: Build the user flow plan**
 
 From the primary sniff goal (Step 0 in the SKILL.md), derive the interactive steps a real user would take to accomplish that goal. This is NOT a list of pages to load -- it is a sequence of actions.

← 4ebfa088 fix(cli): promoted-command presence check uses promoted type  ·  back to Cli Printing Press  ·  feat(cli): enum validation for params declared with enum con 26bc9057 →