← back to Cli Printing Press
feat(skills): data-driven Phase 6 menu and hold-path support (#637)
0310d3fbd4d3b4288685fc5a97d46824a7af478d · 2026-05-05 14:23:31 -0700 · Trevin Chow
Rebuild /printing-press Phase 6 around polish's self-assessment. A new
further_polish_recommended field in the polish result block drives the
menu recommendation; "Polish again" is suppressed when polish judges
another pass unhelpful. Hold verdicts now route to a new
retro/polish-to-retry/done menu instead of silent skip. Add a
post-publish retro tail so users compounding learnings is opt-in
rather than a peer of publish.
Also fix three latent correctness gaps: ship-with-gaps now promotes
to library (Phase 6 routes it to publish, but Phase 5.6 was leaving
the working copy un-promoted); polish skips its standalone publish
offer in mid-pipeline mode (the working CLI is not yet in
\$PRESS_LIBRARY/<slug>/, so /printing-press-publish would resolve the
wrong copy); and ship-with-gaps now enforces a ## Known Gaps block
in README before the verdict fires, matching the publish prompt's
claim that gaps are documented for downstream users.
Files touched
M skills/printing-press-polish/SKILL.mdM skills/printing-press/SKILL.md
Diff
commit 0310d3fbd4d3b4288685fc5a97d46824a7af478d
Author: Trevin Chow <trevin@trevinchow.com>
Date: Tue May 5 14:23:31 2026 -0700
feat(skills): data-driven Phase 6 menu and hold-path support (#637)
Rebuild /printing-press Phase 6 around polish's self-assessment. A new
further_polish_recommended field in the polish result block drives the
menu recommendation; "Polish again" is suppressed when polish judges
another pass unhelpful. Hold verdicts now route to a new
retro/polish-to-retry/done menu instead of silent skip. Add a
post-publish retro tail so users compounding learnings is opt-in
rather than a peer of publish.
Also fix three latent correctness gaps: ship-with-gaps now promotes
to library (Phase 6 routes it to publish, but Phase 5.6 was leaving
the working copy un-promoted); polish skips its standalone publish
offer in mid-pipeline mode (the working CLI is not yet in
\$PRESS_LIBRARY/<slug>/, so /printing-press-publish would resolve the
wrong copy); and ship-with-gaps now enforces a ## Known Gaps block
in README before the verdict fires, matching the publish prompt's
claim that gaps are documented for downstream users.
---
skills/printing-press-polish/SKILL.md | 107 +++++++++++++++++++--
skills/printing-press/SKILL.md | 169 +++++++++++++++++++++++++++++-----
2 files changed, 245 insertions(+), 31 deletions(-)
diff --git a/skills/printing-press-polish/SKILL.md b/skills/printing-press-polish/SKILL.md
index 3ab22f8c..a9644755 100644
--- a/skills/printing-press-polish/SKILL.md
+++ b/skills/printing-press-polish/SKILL.md
@@ -468,7 +468,23 @@ Record the after scores. If verify-skill still has any `severity=error` findings
Compute the ship recommendation:
- **`ship`**: verify >= 80%, scorecard >= 75, no critical failures, **AND** verify-skill exits 0 (no SKILL/CLI mismatches), **AND** workflow-verify is not `workflow-fail`, **AND** tools-audit shows zero pending findings (every finding fixed or explicitly accepted with rationale). The SKILL/workflow gates are hard requirements: a CLI that ships with a SKILL that lies about it (verify-skill findings) gives agents broken instructions; a CLI whose primary workflow fails verification has not actually shipped.
-- **`ship-with-gaps`**: verify >= 65%, scorecard >= 65, non-critical gaps remain, **AND** the SKILL/workflow gates above hold. Reserved for the rare case where a refactor or external-dependency blocker prevents a clean fix; the gap must be documented in the remaining issues.
+- **`ship-with-gaps`**: verify >= 65%, scorecard >= 65, non-critical gaps remain, **AND** the SKILL/workflow gates above hold, **AND** the README has a `## Known Gaps` block that lists the user-facing gaps. Reserved for the rare case where a refactor or external-dependency blocker prevents a clean fix.
+
+ **README Known Gaps is mandatory for ship-with-gaps.** The published library copy is what downstream users see; if the verdict claims gaps exist but the README hides them, downstream users meet a CLI that misbehaves with no disclosure. Before emitting `ship_recommendation: ship-with-gaps`:
+
+ 1. Read the CLI's `README.md`. If a `## Known Gaps` section already exists (e.g., the main SKILL Phase 4 wrote it before polish ran), confirm it covers the user-facing items in `remaining_issues`. Add bullets for any newly surfaced user-facing gap polish discovered.
+ 2. If `## Known Gaps` is missing, write it — placed after `## Quick Start` (or before `## Usage`) to mirror the `## Unique Features` placement convention. One bullet per user-facing item from `remaining_issues`. Phrase from the user's perspective: what command misbehaves, what the workaround is. Example:
+
+ ```markdown
+ ## Known Gaps
+
+ - **`analytics export --csv`** returns truncated rows on workspaces with >10k events. Use `--json` and pipe to `jq` as a workaround until the underlying export endpoint is paginated.
+ ```
+
+ 3. Filter `remaining_issues` for user-facing entries when populating the section. Internal items (verify drift on a deprecated flag, MCP description tuning, polish-internal notes) do not belong in the public Known Gaps. If the agent cannot identify any user-facing gap from `remaining_issues`, the verdict is `ship`, not `ship-with-gaps`.
+ 4. List each Known Gaps write/update in `fixes_applied` so the caller can surface that this happened.
+
+ If polish cannot responsibly populate Known Gaps from the available evidence (e.g., `remaining_issues` is all internal jargon with no user-facing reading), downgrade the verdict to `hold` rather than ship without disclosure.
- **`hold`**: verify < 65% or scorecard < 65 or critical failures, **OR** verify-skill has unresolved findings, **OR** workflow-verify reports `workflow-fail` and the workflow is the CLI's primary value.
### Push higher without gaming
@@ -536,6 +552,8 @@ skipped_findings:
remaining_issues:
- <one-line description of each issue you tried to fix but couldn't>
ship_recommendation: <ship|ship-with-gaps|hold>
+further_polish_recommended: <yes|no>
+further_polish_reasoning: <one sentence explaining the call>
---END-POLISH-RESULT---
```
@@ -544,19 +562,81 @@ The three lists serve different purposes:
- **skipped_findings**: issues you found but deliberately did not fix, with reasoning (e.g., "verify classifies `stale` as read — scorer bug, not a CLI problem", "thin-short on `version` accepted as-is — accurate and brief"). The caller surfaces these so the user can decide whether to address them manually.
- **remaining_issues**: issues you tried to fix but couldn't resolve.
+### Picking `further_polish_recommended`
+
+Your judgment, not a count of `remaining_issues`. Set `yes` when another polish invocation has a real chance of closing what's left:
+
+- `remaining_issues` includes verify or dogfood failures you ran out of time on and a fresh pass with more attention per failure could plausibly resolve.
+- The fixes you did land may have unblocked dependent issues you couldn't reach this pass.
+- A SKILL/CLI mismatch needs a second look after this pass changed the source tree.
+
+Set `no` when another invocation would re-tread the same ground:
+
+- `remaining_issues` are decisions only the user can make (rename a flagship command, choose a default behavior, accept a structural trade-off).
+- You already attempted the fix in two different ways this pass and both failed for the same reason.
+- The blocker is external (API changed shape, rate-limited, missing credential) and not something a fresh polish run sees differently.
+- `remaining_issues` is empty AND `skipped_findings` are all environmental or structural — there is nothing left for polish to do.
+
+`further_polish_reasoning` is one sentence the caller surfaces verbatim. Make it specific ("verify failures on `analytics export` and `report show` looked closable but I gave up too early") rather than generic ("more polish might help"). Callers use this signal to decide whether to offer "Polish again" in their next prompt; a vague reason makes their prompt vague.
+
## Publish Offer
+**Skip this entire section in mid-pipeline mode.** Detect from `$CLI_DIR`: if the path is under `.runstate/` (i.e., `$PRESS_RUNSTATE/<scope>/runs/.../working/<api>-pp-cli/`), polish is being called from main SKILL Phase 5.5 or hold-path "Polish to retry," and the working CLI has not been promoted to library yet. `/printing-press-publish <slug>` resolves to `$PRESS_LIBRARY/<slug>/`, which is either empty or holds a stale prior run — invoking publish here would either fail to resolve or ship the wrong copy. The parent skill owns the publish flow on that path; just emit the result block and return.
+
+A simple check:
+
+```bash
+case "$CLI_DIR" in
+ *.runstate/*) echo "mid-pipeline; skipping Publish Offer"; return ;;
+esac
+```
+
+For standalone invocations (`$CLI_DIR` under `$PRESS_LIBRARY/<slug>/`), continue with the offer below.
+
If `ship` or `ship-with-gaps`:
-Present via `AskUserQuestion`:
+Construct the prompt from the result block. The shape is data-driven so the user is never asked to weigh "Polish again" against "Publish" when polish itself just decided another pass would not help.
+
+### Recommendation
-> "<CLI_NAME> polished: scorecard XX/100, verify XX%. Ready to publish?"
+Pick the recommended action from the polish result:
+
+- `ship` + `remaining_issues` empty → recommend **Publish**.
+- `ship` + `remaining_issues` non-empty + `further_polish_recommended: yes` → recommend **Polish again**.
+- `ship` + `remaining_issues` non-empty + `further_polish_recommended: no` → recommend **Publish** if the remaining issues do not touch the CLI's headline commands, otherwise surface the trade-off and let the user decide between **Publish** (as-is; README is not auto-updated for `ship` verdicts) and **Done**.
+- `ship-with-gaps` + `further_polish_recommended: yes` → recommend **Polish again**.
+- `ship-with-gaps` + `further_polish_recommended: no` → recommend **Publish** (the gap is already in README's `## Known Gaps` because polish's ship logic enforces that for `ship-with-gaps` — see "Ship logic" above) or **Done** if the gap is publish-blocking — agent judgment.
+
+### Menu
+
+Suppress the "Polish again" option entirely when `further_polish_recommended: no`. Keep "Publish" and "Done" always available.
+
+Surface `further_polish_reasoning` as context when polish opted out of recommending another pass — the user should see *why* polish is done.
+
+Present via `AskUserQuestion`. Two example shapes:
+
+**Polish converged clean** (`remaining_issues` empty, `further_polish_recommended: no`):
+
+> "<CLI_NAME> polished: scorecard XX/100, verify XX%. Polish ran cleanly — nothing more to fix.
+>
+> Recommendation: Publish.
+>
+> 1. **Publish now** (recommended) — validate, package, and open a PR
+> 2. **Done for now** — CLI is at ~/printing-press/library/<cli-name>"
+
+**Polish thinks another pass would help** (`remaining_issues` non-empty, `further_polish_recommended: yes`):
+
+> "<CLI_NAME> polished: scorecard XX/100, verify XX%. <N> issues remain.
+>
+> Polish notes: '<further_polish_reasoning>'
+>
+> Recommendation: Polish again before publishing.
>
-> 1. **Publish now** — validate, package, and open a PR to printing-press-library
-> 2. **Polish again** — run another fix pass on remaining issues
-> 3. **Done for now** — CLI is at ~/printing-press/library/<cli-name>
+> 1. **Polish again** (recommended) — close the remaining <N> issues
+> 2. **Publish now** — ship as-is
+> 3. **Done for now** — CLI is at ~/printing-press/library/<cli-name>"
-If remaining issues exist, prepend: "Note: some issues remain (see above)."
+The recommended option leads, carries the `(recommended)` label, and the leading `Recommendation:` line states the agent's call explicitly. Three reinforcing channels so the user does not have to infer from ordering.
### If "Publish now"
@@ -567,6 +647,19 @@ gh pr list --repo mvanhorn/printing-press-library --head "feat/$CLI_NAME" --stat
Then invoke `/printing-press-publish <cli-name>`.
+**After publish returns success**, offer retro as a soft tail. This mirrors the main `/printing-press` skill's Phase 6 behavior so users who reach publish through polish (mid-pipeline → polish-again → publish, or standalone polish → publish) get the same retro opportunity as users who reach publish directly through Phase 6.
+
+Present via `AskUserQuestion`:
+
+> "PR opened: <PR_URL>. Run a retro? It surfaces systemic gaps from this session (generator misses, scorer bugs, skill-doc drift) as a GitHub issue for the Printing Press maintainers. Every retro filed raises the floor for the next CLI — and your session context is freshest right now."
+>
+> 1. **No — I'm done** (default)
+> 2. **Yes — run retro now**
+
+If the user picks yes, invoke `/printing-press-retro`.
+
+(In mid-pipeline mode this whole section is unreachable — the Publish Offer guard at the top of this section returns early — so no extra check is needed here.)
+
### If "Polish again"
Re-run Phase 1 → Phase 2 → Phase 3 with the same CLI. Maximum 2 additional polish passes (3 total including the first).
diff --git a/skills/printing-press/SKILL.md b/skills/printing-press/SKILL.md
index 9ea83c9e..081f3b69 100644
--- a/skills/printing-press/SKILL.md
+++ b/skills/printing-press/SKILL.md
@@ -2584,7 +2584,7 @@ Before promoting, verify the Phase 5 JSON gate marker:
### Promote to Library
-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.
+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, and Phase 6's publish path expects `$PRESS_LIBRARY/<api>/` to hold the current run.
```bash
# Promote verified CLI to library (copies working dir, writes manifest, releases lock)
@@ -2593,6 +2593,8 @@ printing-press lock promote --cli <api>-pp-cli --dir "$CLI_WORK_DIR"
The `promote` command handles the full sequence: stages the working directory, atomically swaps it into `$PRESS_LIBRARY/<api>` (slug-keyed), writes the `.printing-press.json` manifest, updates the `CurrentRunPointer`, and releases the lock — all in one step. The `--cli` flag accepts the CLI binary name; the Go code translates to the slug-keyed library path internally.
+`ship-with-gaps` is promoted because the verdict means "the CLI is shippable with documented, non-blocking gaps" — the gaps are recorded in the README's `## Known Gaps` block and the user opts in via Phase 6's publish prompt. Treating ship-with-gaps as un-promotable would strand the verified working copy and leave the library on a stale prior run.
+
If the shipcheck verdict is `hold`, the lock was already released in Phase 4. Do NOT promote. The working copy stays in `$CLI_WORK_DIR` and is not copied to the library.
### Archive Manuscripts
@@ -2629,23 +2631,26 @@ if [ -d "$DISCOVERY_DIR" ]; then
fi
```
-**MANDATORY: After archiving, you MUST proceed to Phase 6 (Publish) below. Do not print a summary and stop. Do not treat archiving as the end of the run. The run ends when the user has been asked about publishing (or the verdict is `hold`).**
+**MANDATORY: After archiving, you MUST proceed to Phase 6 below. Do not print a summary and stop. Do not treat archiving as the end of the run. The run ends when the user has been asked about next steps via the ship-path or hold-path menu.**
-## Phase 6: Publish
+## Phase 6: Next Steps
-**This phase is NOT optional.** Every run with a `ship` verdict MUST reach this point. Do not skip it.
+**This phase is NOT optional.** Every run MUST reach this point — both `ship` and `hold` verdicts get a menu. Do not skip it.
-After archiving, offer to publish the CLI to the library repo.
+After archiving, offer the user the next action. The menu shape is determined by the shipcheck verdict and (for ship runs) by polish's self-assessment.
### Gate
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
+- if Phase 5.5 polish downgraded the verdict (`ship_recommendation: hold`), use the downgraded verdict
-Skip this phase entirely if the final shipcheck verdict is `hold`. Only proceed for `ship`.
+Route to the menu shape:
+- `ship` or `ship-with-gaps` → **ship-path menu** (below)
+- `hold` → **hold-path menu** (below). The CLI did not promote to library; the working copy stays in `$CLI_WORK_DIR`.
-### Check for existing PR
+### Check for existing PR (ship-path only)
Run a lightweight check for your own open publish PR. The `--author @me` filter avoids matching someone else's PR for the same API slug.
@@ -2655,43 +2660,159 @@ gh pr list --repo mvanhorn/printing-press-library --head "feat/<api>" --state op
If this fails (gh not authenticated, network error, etc.), continue without PR context — the publish skill will handle auth in its own Step 1.
-### Offer
+### Ship-path menu
-Present via `AskUserQuestion`:
+Read the polish result block emitted by Phase 5.5. The menu and recommendation are data-driven so the user is never asked to weigh "Polish again" against "Publish" when polish itself just decided another pass would not help.
+
+**Pick the recommended action:**
+
+| Polish state | Recommendation |
+|---|---|
+| `ship` + `remaining_issues` empty | **Publish** |
+| `ship` + `remaining_issues` non-empty + `further_polish_recommended: yes` | **Polish again** |
+| `ship` + `remaining_issues` non-empty + `further_polish_recommended: no` | **Publish** if the remaining issues do not touch the CLI's headline commands; surface the trade-off and let the user pick between **Publish** (as-is; README is not auto-updated) and **Done** otherwise |
+| `ship-with-gaps` + `further_polish_recommended: yes` | **Polish again** |
+| `ship-with-gaps` + `further_polish_recommended: no` | **Publish** (gaps already in README's `## Known Gaps`; polish's ship logic enforces this for `ship-with-gaps`) or **Done** — agent judgment from the actual gap content |
+
+**Suppress "Polish again" entirely when `further_polish_recommended: no`.** Polish has already decided another pass would not help; offering the option anyway is friction. Surface `further_polish_reasoning` as context so the user sees the call.
+
+**Always available:** Publish, Done. Retro is not on this menu — it is offered as a post-publish tail (see "If Publish now" below).
+
+Present via `AskUserQuestion`. The recommended option leads, carries the `(recommended)` label, and a leading `Recommendation:` line states the call explicitly. Three reinforcing channels so the user does not have to infer from ordering.
+
+**Substitute placeholders before showing the prompt.** The example prompts below use `<api>`, `<N>`, `<score>`, `<pass-rate>`, `<PR_URL>`, `<further_polish_reasoning>`, `$CLI_WORK_DIR`, and `$PRESS_LIBRARY/<api>` as fill-ins. Replace each with the concrete value (the API slug, the actual count, the parsed polish-result string, the expanded shell-variable path, etc.) so the user sees real names and paths, not literal placeholder text. The same rule applies to the hold-path menu below.
**If an existing open PR was found:**
-> "<api> passed shipcheck. There's an open publish PR (#N). Want to update it with this version?"
+The existing-PR branch still honors polish's `further_polish_recommended` signal. When polish thinks another pass would help, offer it as the recommended action ahead of the PR update — pushing a CLI with closable issues into an existing PR is no better than into a new one.
+
+When `further_polish_recommended: yes`:
+
+> "<api> passed shipcheck. There's an open publish PR (#<PR-number>). Polish flagged <type-qualified summary of remaining issues> as closable — '<further_polish_reasoning>'.
+>
+> Recommendation: Polish again before updating the PR.
+>
+> 1. **Polish again** (recommended) — close the remaining issues, then update the PR
+> 2. **Update PR #<PR-number>** — push this version to the existing PR as-is
+> 3. **No — I'm done**"
+
+When `further_polish_recommended: no` (or `remaining_issues` empty):
+
+> "<api> passed shipcheck. There's an open publish PR (#<PR-number>). Want to update it with this version?"
>
-> 1. **Yes — update PR #N** (re-validate, re-package, and push to the existing PR)
-> 2. **No — I'm done**
+> 1. **Yes — update PR #<PR-number>** (recommended) — re-validate, re-package, and push to the existing PR
+> 2. **No — I'm done**"
-**If no existing PR:**
+**Polish converged clean** (`remaining_issues` empty, `further_polish_recommended: no`):
-> "<api> passed shipcheck ([score]/100, verify [pass-rate]%). What do you want to do?"
+> "<api> passed shipcheck (<score>/100, verify <pass-rate>%). Polish ran cleanly — nothing more to fix.
>
-> 1. **Publish now** (validate, package, and open a PR)
-> 2. **Polish first** (run `/printing-press-polish` to fix verify failures, dead code, and README before publishing)
-> 3. **Run retro** (analyze the session to find improvements for the Printing Press)
-> 4. **Done for now**
+> Recommendation: Publish.
+>
+> 1. **Publish now** (recommended) — validate, package, and open a PR
+> 2. **Done for now**
+
+**Polish thinks another pass would help** (`further_polish_recommended: yes`):
-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.
+When summarizing remaining issues to the user, type-qualify the count instead of just printing `<N> issues`. Pull the categories from the polish result block (`remaining_issues` entries usually carry their type — verify failure, README gap, MCP description, etc.). For example: "2 verify failures and 1 README gap remain" rather than "3 issues remain."
+
+> "<api> passed shipcheck (<score>/100, verify <pass-rate>%). <type-qualified summary of remaining issues>.
+>
+> Polish notes: '<further_polish_reasoning>'
+>
+> Recommendation: Polish again before publishing.
+>
+> 1. **Polish again** (recommended) — close the remaining issues
+> 2. **Publish now** — ship as-is
+> 3. **Done for now**
+
+**Polish opted out of recommending more polish** (`remaining_issues` non-empty, `further_polish_recommended: no`):
+
+> "<api> passed shipcheck (<score>/100, verify <pass-rate>%). <type-qualified summary of remaining issues> — polish could not auto-resolve these.
+>
+> Polish notes: '<further_polish_reasoning>'
+>
+> Recommendation: <Publish | Done — your call from the gap content>.
+>
+> 1. **Publish now** — ship as-is. The remaining issues are not auto-added to README; if any are user-facing, you'll need to update the README's `## Known Gaps` section in the publish PR before merging
+> 2. **Done for now** — leave the CLI in <expanded $PRESS_LIBRARY/<api> path> and address remaining issues manually before any later publish
+
+This prompt fires only for `ship` verdicts (not `ship-with-gaps`), so polish has not auto-written `## Known Gaps`. Polish auto-writes the section only when the verdict is `ship-with-gaps` (see polish SKILL.md "Ship logic"). On a `ship` verdict with non-empty `remaining_issues`, polish has judged those issues acceptable to publish without a verdict bump — but the user may still want to surface user-facing items in README before merging the PR.
+
+If the shipcheck report contains a `## Known Gaps` block, prepend: "Note: shipcheck documented known gaps (see the shipcheck report above)."
### If "Publish now"
Invoke `/printing-press publish <api>`. The publish skill handles everything from there.
-### If "Polish first"
+**After publish returns success**, offer retro as a soft tail. This is where retro lives on the ship-path — it has no business being a peer of publish on the headline menu, but a post-publish optional offer lets users compound learnings without forcing the choice up front. Retro at this point sees the publish step as part of the session it analyzes.
-Invoke `/printing-press-polish <api>`. The polish skill runs diagnostics, fixes issues, reports the delta, and offers its own publish at the end.
+Present via `AskUserQuestion`:
-### If "Run retro"
+> "PR opened: <PR_URL>. Run a retro? It surfaces systemic gaps from this session (generator misses, scorer bugs, skill-doc drift) as a GitHub issue for the Printing Press maintainers. Every retro filed raises the floor for the next CLI — and your session context is freshest right now."
+>
+> 1. **No — I'm done** (default)
+> 2. **Yes — run retro now**
-Invoke `/printing-press-retro`. The retro skill analyzes the session for generator improvements.
+If the user picks yes, invoke `/printing-press-retro`. The retro skill analyzes the session for generator improvements.
+
+### If "Polish again"
+
+Invoke `/printing-press-polish <api>`. The polish skill runs another diagnostic-fix-rediagnose pass, reports the delta, and offers its own publish at the end with the same data-driven shape used here.
### If "Done for now"
-End normally. The CLI is in `$PRESS_LIBRARY/<api>` and the user can run `/printing-press publish` or `/printing-press-polish` later.
+End normally. The CLI is in `$PRESS_LIBRARY/<api>` and the user can run `/printing-press publish`, `/printing-press-polish`, or `/printing-press-retro` later.
+
+### Hold-path menu
+
+The CLI did not promote to library. The working copy is at `$CLI_WORK_DIR`; manuscripts and proofs are archived. Hold runs are the highest-value retro signal — something blocked the machine from reaching ship, and that signal is most valuable while session context is fresh.
+
+Present via `AskUserQuestion`:
+
+> "<api> couldn't pass shipcheck — <one-line reason from the shipcheck report or polish result>. The working copy is at <expanded $CLI_WORK_DIR path> and was not added to the library. What do you want to do?"
+>
+> 1. **Run retro** (recommended) — capture what blocked ship so the Printing Press maintainers can fix it for the next CLI you generate
+> 2. **Polish to retry** — run another polish pass and try again to reach ship
+> 3. **Done for now**
+
+Default the recommendation to **Run retro**. Override to **Polish to retry** when the polish result block specifically says another pass is likely to close the gap (`further_polish_recommended: yes`) — that signal means the CLI is on hold not because the machine is structurally short, but because the last polish pass ran out of time on issues it can plausibly close.
+
+#### If "Run retro"
+
+Invoke `/printing-press-retro`. The retro skill analyzes the session for generator improvements.
+
+#### If "Polish to retry"
+
+**Invoke polish via the Skill tool with `$CLI_WORK_DIR` as the arg:**
+
+```
+Skill(
+ skill: "cli-printing-press:printing-press-polish",
+ args: "$CLI_WORK_DIR"
+)
+```
+
+Two reasons for this exact form, both mirroring Phase 5.5:
+
+1. **Pass `$CLI_WORK_DIR` (absolute path), not the slug.** Hold runs leave the CLI in the working directory because Phase 5.6 did not promote — `$PRESS_LIBRARY/<slug>/` either does not exist or holds a stale prior run, and a slug-form invocation would polish that stale copy.
+2. **Use the Skill tool (forked context), not the `/printing-press-polish` slash command.** This matches Phase 5.5's invocation pattern — same shape, same expectations. The polish skill's Publish Offer is gated on caller mode (see polish SKILL.md "If Publish now"); when invoked via the Skill tool in forked context, polish defers the post-publish retro tail to the parent. Main SKILL owns the menu on this path.
+
+After polish returns, parse the result block and act on the new `ship_recommendation`:
+
+- **Polish landed on `ship` or `ship-with-gaps`** — the verdict transitioned out of hold. The working copy is still un-promoted; the library is stale. Run promote, then route to the ship-path menu (above):
+
+ ```bash
+ printing-press lock promote --cli <api>-pp-cli --dir "$CLI_WORK_DIR"
+ ```
+
+ Then re-enter the ship-path menu using polish's new result block. Skip the Phase 5.6 acceptance-gate JSON check — that gate was already satisfied when this run originally reached Phase 5.6, and polish does not regenerate it.
+
+- **Polish still on `hold`** — re-show this hold-path menu so the user can pick again. Do not loop polish automatically; the user may want retro or to give up after a failed retry.
+
+#### If "Done for now"
+
+End normally. The working copy stays in `$CLI_WORK_DIR` for potential future retry.
## Fast Guidance
← cb3ef873 fix(cli): require explicit retry no-ops (#635)
·
back to Cli Printing Press
·
fix(cli): score composed auth and shell continuations (#636) 2035b276 →