/printing-press-polish
Polish a generated CLI so it passes verification and is ready to publish.
The retro improves the Printing Press. Polish improves the generated CLI. This skill runs in a forked context (context: fork) so its diagnostic and fix loop doesn't pollute the caller — the diagnostic spam, fix iterations, and re-diagnose noise stay scoped to the polish session, and the caller receives a clean summary.
/printing-press-polish redfin
/printing-press-polish redfin-pp-cli
/printing-press-polish "$PRESS_LIBRARY/redfin"
When to run
After any /printing-press generation, especially when:
- The shipcheck verdict is
ship-with-gaps - The verify pass rate is below 80%
- The scorecard is below 85
- You want the CLI publish-ready in one pass
Can also be run standalone on any CLI in $PRESS_LIBRARY/.
Setup
# min-binary-version: 4.0.0
PRESS_HOME="${PRINTING_PRESS_HOME:-$HOME/printing-press}"
PRESS_LIBRARY="$PRESS_HOME/library"
_pp_check_disk_space() {
_pp_disk_warn_kb="${PRINTING_PRESS_DISK_WARN_KB:-3145728}"
_pp_disk_fail_kb="${PRINTING_PRESS_DISK_FAIL_KB:-524288}"
case "$_pp_disk_warn_kb$_pp_disk_fail_kb" in
""|*[!0-9]*) return 0 ;;
esac
_pp_disk_path="$PRESS_HOME"
while [ ! -e "$_pp_disk_path" ] && [ "$_pp_disk_path" != "/" ]; do
_pp_disk_path="$(dirname "$_pp_disk_path")"
done
_pp_disk_avail_kb="$(df -Pk "$_pp_disk_path" 2>/dev/null | awk 'NR == 2 { print $4; exit }')"
case "$_pp_disk_avail_kb" in
""|*[!0-9]*) return 0 ;;
esac
if [ "$_pp_disk_avail_kb" -lt "$_pp_disk_fail_kb" ]; then
echo ""
echo "[setup-error] Critically low disk space on the Printing Press workspace volume."
echo "PRESS_DISK_PATH=$_pp_disk_path"
echo "PRESS_DISK_AVAIL_KB=$_pp_disk_avail_kb"
echo "PRESS_DISK_FAIL_KB=$_pp_disk_fail_kb"
echo "Free disk space or set PRINTING_PRESS_HOME to a volume with more room, then re-run this skill."
echo ""
return 1
fi
if [ "$_pp_disk_avail_kb" -lt "$_pp_disk_warn_kb" ]; then
echo ""
echo "[low-disk] Printing Press workspace volume is low on free space."
echo "PRESS_DISK_PATH=$_pp_disk_path"
echo "PRESS_DISK_AVAIL_KB=$_pp_disk_avail_kb"
echo "PRESS_DISK_WARN_KB=$_pp_disk_warn_kb"
echo "This flow may need several GiB for generated files, Go build cache, module downloads, or repository clones."
echo ""
fi
}
_pp_check_disk_space || { return 1 2>/dev/null || exit 1; }
# Mid-pipeline callers may pass printing_press_bin: <abs-path> in the args
# bundle. Prefer it so forked polish runs keep using the parent skill's
# preflight-selected binary instead of re-resolving through PATH.
PRINTING_PRESS_BIN="${PRINTING_PRESS_BIN:-}"
if [ -z "$PRINTING_PRESS_BIN" ] && [ -n "${ARGUMENTS:-}" ]; then
PRINTING_PRESS_BIN="$(printf '%s\n' "$ARGUMENTS" | sed -nE 's/^[[:space:]]*printing_press_bin:[[:space:]]*(.+)$/\1/p' | head -1)"
fi
if [ -z "$PRINTING_PRESS_BIN" ]; then
PRINTING_PRESS_BIN="$(command -v cli-printing-press 2>/dev/null || true)"
fi
if [ -z "$PRINTING_PRESS_BIN" ]; then
echo "cli-printing-press binary not found."
echo "Install with: go install github.com/mvanhorn/cli-printing-press/v4/cmd/cli-printing-press@latest"
return 1 2>/dev/null || exit 1
fi
if ! command -v go >/dev/null 2>&1; then
echo ""
echo "[setup-error] Go toolchain not found."
echo ""
echo "This Printing Press flow runs Go-based build or validation commands."
echo "Install Go 1.26.5 or newer from https://go.dev/dl/, then verify with:"
echo " go version"
echo "Then re-run this skill."
echo ""
return 1 2>/dev/null || exit 1
fi
echo "PRINTING_PRESS_BIN=$PRINTING_PRESS_BIN"
_pp_semver_lt() {
awk -v a="$1" -v b="$2" 'BEGIN {
split(a, x, "."); split(b, y, ".")
for (i = 1; i <= 3; i++) {
if ((x[i] + 0) < (y[i] + 0)) exit 0
if ((x[i] + 0) > (y[i] + 0)) exit 1
}
exit 1
}'
}
_pp_go_version_norm() {
printf '%s\n' "$1" | sed -nE 's/.*go([0-9]+)\.([0-9]+)(\.([0-9]+))?.*/\1.\2.\4/p' | awk -F. 'NF >= 2 { printf "%d.%d.%d\n", $1, $2, ($3 == "" ? 0 : $3) }'
}
_pp_check_go_currency() {
_pp_go_installed="$(_pp_go_version_norm "$(go env GOVERSION 2>/dev/null)")"
_pp_go_required="$(_pp_go_version_norm "$(go version "$PRINTING_PRESS_BIN" 2>/dev/null)")"
if [ -z "$_pp_go_installed" ] || [ -z "$_pp_go_required" ] || ! _pp_semver_lt "$_pp_go_installed" "$_pp_go_required"; then
return 0
fi
echo ""
if [ "${GOTOOLCHAIN:-auto}" = "local" ]; then
echo "[setup-error] Go $_pp_go_required or newer is required by this cli-printing-press binary (installed: $_pp_go_installed)."
echo "GOTOOLCHAIN=local disables automatic toolchain downloads, so later Go quality gates would fail."
echo "Install Go $_pp_go_required or newer from https://go.dev/dl/, or unset GOTOOLCHAIN."
echo ""
return 1
fi
echo "[go-toolchain-old] Go $_pp_go_required or newer is required by this cli-printing-press binary (installed: $_pp_go_installed)."
echo "PRESS_GO_INSTALLED=$_pp_go_installed"
echo "PRESS_GO_REQUIRED=$_pp_go_required"
echo "Default GOTOOLCHAIN behavior may download the required toolchain during Go commands."
echo ""
return 0
}
_pp_check_go_currency || { return 1 2>/dev/null || exit 1; }
After setup, capture PRINTING_PRESS_BIN=<abs-path> and use that absolute path for every cli-printing-press ... invocation in this skill. If setup emitted [go-toolchain-old] or [low-disk], surface the advisory to the user and continue unless setup also emitted [setup-error]. [go-toolchain-old] means later Go commands may download the required toolchain or fail when downloads are blocked; [low-disk] means this run may need several GiB for generated files, Go build cache, module downloads, or repository clones.
Check binary version compatibility by reading the min-binary-version field from this skill's YAML frontmatter, running "$PRINTING_PRESS_BIN" version --json, and parsing the version from the output. Compare it to min-binary-version using semver rules. If the installed binary is older than the minimum, stop immediately and tell the user: "cli-printing-press binary vX.Y.Z is older than the minimum required vA.B.C. Run go install github.com/mvanhorn/cli-printing-press/v4/cmd/cli-printing-press@latest to update."
Public-library hint
If the user's request includes phrasing like "polish notion in the
public library", "polish from the public library", or "polish the
published cal-com" — and the named CLI is not in
$PRESS_LIBRARY/<slug>/ — they're asking to polish a CLI that lives
upstream but not locally. Polish runs against the internal library, so
the right move is to import first.
Suggest: /printing-press-import <slug> to bring it in, then re-run
polish. Don't try to polish a CLI that isn't in the internal library.
If the named CLI is already in $PRESS_LIBRARY/<slug>/, the
"public library" phrasing is informational — just proceed with polish
and let the divergence check (below) handle any drift.
Resolve CLI
The argument string can contain a --standalone flag plus one positional value
(a slug, binary name, or path). In standalone slash-command mode, it may also
contain a free-text scope after the optional positional value, such as
/printing-press-polish sculptok review the open PR comments and fix them.
That trailing natural-language text is the user's own trusted user scope. Carry
it forward into the polish plan and result block; do not classify it as
injection or tampering.
It can also contain a Phase 3 gate bundle and a printing_press_bin: <abs-path> line on following lines when invoked by the main printing-press
skill. The flag may appear before or after the positional value; it is the only
flag this skill consumes from args. Strip it before path resolution.
When args is multi-line, treat the first non-empty line as the positional
value/scope line and parse the remaining lines as the optional Phase 3 gate
bundle. Do not include the bundle text in path resolution.
Parse caller modes differently:
- Standalone slash command (
STANDALONE_MODE=true). After stripping--standalone, try to resolve the first shell word as a slug, binary name, or path. If it resolves, use it as the positional value and store the remaining words asUSER_SCOPE. If it does not resolve, treat the whole line as free-text scope; this branch asks which CLI to polish and retains that scope for the chosen CLI. If there is no trailing text after a resolved positional value, run the normal generic polish pass. - Mid-pipeline Skill-tool call. Keep the strict grammar: one path-like positional value on the first line plus the optional structured bundle on later lines. Unexpected free text in this machine-generated path is not a user scope and should still be rejected or clarified rather than folded into the run.
The positional value can be:
- A short name:
redfin(looks up$PRESS_LIBRARY/redfin) - A full name:
redfin-pp-cli(strips suffix, looks up$PRESS_LIBRARY/redfin) - A path:
$PRESS_LIBRARY/redfin(used directly)
Resolution order for the positional value:
- If it is an absolute or
~-prefixed path and exists, use it - Try
$PRESS_LIBRARY/<arg>(exact match — works for slug likeredfin) - If it has
-pp-clisuffix, strip it and try$PRESS_LIBRARY/<slug>(e.g.,redfin-pp-cli→redfin) - Fuzzy search:
ls $PRESS_LIBRARY/ | grep -i <arg>for close matches
Caller scenarios and the --standalone flag. Polish has two callers; they invoke it through different mechanisms, and the Publish Offer at the end of this skill fires only when STANDALONE_MODE is true. Determine STANDALONE_MODE from the caller mode and the flag, not from the resolved path.
- Standalone (user-invoked,
/printing-press-polish redfin). Invoked via the slash command. Treat asSTANDALONE_MODE=trueunconditionally — the slash-command form is the publish-intent surface, even when the user omits the flag. The arg is a slug or binary name; resolution lands on$PRESS_LIBRARY/<slug>/. This is the published copy and the right target. - Mid-pipeline (main printing-press skill Phase 5.5, hold-path "Polish to retry"). Invoked via the Skill tool with
args: "$CLI_WORK_DIR". The arg is an absolute path to~/printing-press/.runstate/.../runs/.../working/<api>-pp-cli/; resolution must hit rule 1.STANDALONE_MODE=falseby default — main SKILL owns the publish flow on this path, so polish defers. Do not paraphrase the arg to the slug — Phase 5.5 fires before the working CLI is promoted, so$PRESS_LIBRARY/<slug>/either doesn't exist or holds the prior run's stale CLI. - Skill-tool standalone override. A non-slash caller that genuinely wants polish to publish must opt in explicitly by including
--standaloneinargs(e.g.,args: "--standalone $PRESS_LIBRARY/redfin"). Without that token, polish never publishes from a Skill-tool invocation — even if the resolved path happens to live under$PRESS_LIBRARY/. The flag is the contract; the path is not.
This caller-mode-driven gate replaces the older path-substring heuristic (*.runstate/*). The heuristic broke when the main SKILL's Phase 5.5/5.6 ordering inverted, or when polish was invoked from a non-.runstate scratch layout: polish would see a $PRESS_LIBRARY/<slug>/ path, conclude "standalone," and fire its Publish Offer (fork, global git config, public PR) inside a mid-pipeline run. The flag is unambiguous and the safer default is no-publish.
Phase 3 gate bundle
Mid-pipeline callers pass these fields after the CLI path in args:
phase3_transcendence_rows_planned: <planned>
phase3_transcendence_rows_built: <built>
phase3_transcendence_rows_missing:
- <manifest row name or command>
prior_sub60_reprint: <true|false>
partial_transcendence_override: <none or build-log note path>
Parse the bundle before diagnostics and keep the values available for ship
logic. Missing bundle fields mean "no forced Phase 3 hold"; they do not block
standalone polish. If prior_sub60_reprint: true,
phase3_transcendence_rows_missing contains any row, and
partial_transcendence_override is empty or none, polish must emit
ship_recommendation: hold even if the local diagnostics are otherwise clean.
Add the missing rows to remaining_issues so the parent skill can show the
specific gate that blocked promotion.
The lock-status check in the next code block is the safety net for the mid-pipeline scenario: if a build lock is held for this CLI (under either name form), polish refuses to run. cli-printing-press lock normalizes slug ↔ binary-name internally, so the check works regardless of which form the basename produces.
If no match or multiple matches, present via AskUserQuestion. Show at most 4
matches sorted by modification time (most recent first) with human-friendly
relative timestamps (e.g., "generated 2 hours ago").
CLI_DIR="<resolved path>"
CLI_NAME="$(basename "$CLI_DIR")"
STANDALONE_MODE="<true|false>" # true iff slash-command invocation or --standalone in args; default false for Skill-tool invocations
# Check if there's an active build lock — polish edits would be overwritten
# when the running build promotes to library.
_lock_json=$("$PRINTING_PRESS_BIN" lock status --cli "$CLI_NAME" --json 2>/dev/null)
if echo "$_lock_json" | grep -q '"held".*true'; then
if echo "$_lock_json" | grep -q '"stale".*true'; then
echo "Warning: stale lock exists for $CLI_NAME (build may have crashed)."
echo "Proceeding with polish. Run '$PRINTING_PRESS_BIN lock release --cli $CLI_NAME' to clear."
else
echo "An active build is in progress for $CLI_NAME."
echo "Polish edits would be overwritten when the build promotes."
echo "Wait for the build to finish, then run polish."
exit 1
fi
fi
# Verify it's a valid Go CLI
if [ ! -f "$CLI_DIR/go.mod" ]; then
echo "Not a valid CLI directory: $CLI_DIR"
exit 1
fi
echo "Polishing: $CLI_NAME"
echo "Location: $CLI_DIR"
Find spec and research dir
API_SLUG="${CLI_NAME%-pp-cli}"
SPEC_PATH=""
for f in "$PRESS_HOME/manuscripts/$API_SLUG"/*/research/*.yaml "$PRESS_HOME/manuscripts/$API_SLUG"/*/research/*.json "$PRESS_HOME/manuscripts/$CLI_NAME"/*/research/*.yaml "$PRESS_HOME/manuscripts/$CLI_NAME"/*/research/*.json; do
if [ -f "$f" ]; then
SPEC_PATH="$f"
break
fi
done
# Build the spec flag once. Empty when no spec was found — diagnostic
# commands accept a missing --spec and degrade gracefully.
SPEC_FLAG=""
if [ -n "$SPEC_PATH" ]; then
SPEC_FLAG="--spec $SPEC_PATH"
fi
# Locate the research dir. dogfood's --research-dir triggers
# checkNovelFeatures, which writes novel_features_built back into
# research.json AND syncs the verified list into .printing-press.json.
# Without this flag, legacy CLIs whose manifest predates the
# novel_features schema fail publish-validate's transcendence gate.
#
# Two layouts to handle, keyed on $CLI_DIR path structure (NOT on the
# absence of a manuscripts entry — re-generating a previously-published
# API leaves stale manuscript entries from prior runs that would point
# scorecard at the wrong research.json):
# 1. Mid-pipeline polish (invoked from the main printing-press flow
# before promote): $CLI_DIR is under $PRESS_RUNSTATE/.../runs/<id>/working/<cli>
# (i.e. the path contains `.runstate/`), and research.json lives at
# $PRESS_RUNSTATE/.../runs/<id>/research.json — $CLI_DIR's grandparent.
# 2. Post-promote (standalone polish): research.json lives at
# manuscripts/<api>/<run-id>/research.json.
RESEARCH_DIR=""
MANIFEST_RUN_ID=""
if [ -f "$CLI_DIR/.printing-press.json" ]; then
if command -v jq >/dev/null 2>&1; then
MANIFEST_RUN_ID="$(jq -r '.run_id // empty' "$CLI_DIR/.printing-press.json" 2>/dev/null || true)"
fi
if [ -z "$MANIFEST_RUN_ID" ]; then
MANIFEST_RUN_ID="$(sed -nE 's/.*"run_id"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$CLI_DIR/.printing-press.json" | head -1)"
fi
fi
case "$CLI_DIR" in
*.runstate/*)
_grandparent="$(dirname "$(dirname "$CLI_DIR")")"
if [ -f "$_grandparent/research.json" ]; then
RESEARCH_DIR="$_grandparent"
fi
;;
*)
if [ -n "$MANIFEST_RUN_ID" ]; then
for base in "$PRESS_HOME/manuscripts/$API_SLUG" "$PRESS_HOME/manuscripts/$CLI_NAME"; do
if [ -f "$base/$MANIFEST_RUN_ID/research.json" ]; then
RESEARCH_DIR="$base/$MANIFEST_RUN_ID"
break
fi
done
fi
# Match publish package's fallback: API slug first, then CLI name, each
# using the lexicographically latest run id when the manifest has none.
if [ -z "$RESEARCH_DIR" ]; then
for base in "$PRESS_HOME/manuscripts/$API_SLUG" "$PRESS_HOME/manuscripts/$CLI_NAME"; do
if [ -d "$base" ]; then
_latest="$(find "$base" -mindepth 2 -maxdepth 2 -name research.json -type f 2>/dev/null | sort | tail -1)"
if [ -n "$_latest" ]; then
RESEARCH_DIR="$(dirname "$_latest")"
break
fi
fi
done
fi
;;
esac
# Use a bash array so the flag survives paths with spaces (e.g. when
# $HOME or $PRESS_RUNSTATE resolves through a path containing spaces).
RESEARCH_ARGS=()
if [ -n "$RESEARCH_DIR" ]; then
RESEARCH_ARGS=(--research-dir "$RESEARCH_DIR")
fi
# pii-audit runs against the CLI dir, but publish package later copies the
# same run archive under .manuscripts/<run-id> before enforcing the PII gate.
# Pass the run dir here so polish sees the narrative manuscript files with
# the same relative paths publish will scan.
PII_ARGS=()
if [ -n "$RESEARCH_DIR" ]; then
PII_ARGS=(--manuscripts-dir "$RESEARCH_DIR")
fi
Divergence check
Stop and run this step before Phase 1. Do not skip it. Do not proceed to diagnostics until you have completed the check and resolved any divergence.
The internal copy at $CLI_DIR can drift from the public library (mvanhorn/printing-press-library) copy if anyone edited the public repo directly after this CLI was last published. Polishing a stale internal copy and re-publishing later silently overwrites those public-only fixes — a real failure mode that shipped CLIs hit.
You must:
Locate the public library clone. Honor
$PRINTING_PRESS_LIBRARY_PUBLICif set; otherwise scan the user's filesystem however fits this platform. Validate every candidate by checking the git remote points atmvanhorn/printing-press-library— other directories may share the name (forks, accidental name collisions). If multiple valid clones exist, prefer the most recently modified; ask the user to disambiguate only if still unclear.Locate this CLI inside the clone.
find <clone>/library -type d -name "<api>-pp-cli"or equivalent.Run
diff -r <public-cli-dir> $CLI_DIRwith these exclusions, all of which are expected to diverge after publish:.printing-press-tools-polish.json(local ledger, not published).printing-press-pii-polish.json(local ledger, not published)go.modandgo.sum— publish rewrites the module path from<api>-pp-clitogithub.com/mvanhorn/printing-press-library/library/<category>/<api>- All
.gofiles where the only difference is the rewritten import path (the publish step propagates the new module path through every internal import). When inspecting.godiffs, scan for substantive changes — anything beyond the module-path prefix swap is real divergence.
Concretely:
diff -r --exclude=go.mod --exclude=go.sum --exclude=.printing-press-tools-polish.json --exclude=.printing-press-pii-polish.json <public-cli-dir> $CLI_DIR.Don't pass
--exclude='<api>-pp-cli'or--exclude='<api>-pp-mcp'— those names match both the root-level binary files and thecmd/<api>-pp-cli/andcmd/<api>-pp-mcp/source directories. Excluding by binary name silently skips the entirecmd/subtree, hiding real divergence inmain.go. The "Only in $CLI_DIR: -pp-cli" line for the built binary is one row of expected output, not noise worth filtering at the cost of completeness.Surface the result before continuing.
Outcomes:
- No clone found → user doesn't have public locally. State this explicitly ("public library not found locally; proceeding on internal as canonical") and continue.
- Clone found but doesn't contain this CLI → never published or under a different name. State this and continue.
- Found and diff is empty → in sync. State this and continue.
- Found and divergent → stop. Do not run Phase 1 diagnostics yet. List the divergent files for the user. Ask via AskUserQuestion: sync public→internal, or proceed without syncing. If the user picks sync, copy public's version of the divergent files into internal, then continue polish on the synced internal copy.
Before showing the sync prompt, check whether internal has files modified after its .printing-press.json timestamp (the user has been polishing locally without publishing). If yes, hedge the prompt explicitly: syncing will overwrite their pending local work. Let them decide whether to keep their local edits or pull public's.
After sync (or explicit skip), the rest of polish operates on $CLI_DIR as canonical. The eventual /printing-press-publish step pushes internal back to public; no second divergence check is needed there.
The check has run only when one of the four outcomes above is explicitly stated in your response. Silent omission counts as not having run it.
Phase 1: Baseline diagnostics
cd "$CLI_DIR"
# Build
go build -o "$CLI_NAME" ./cmd/"$CLI_NAME" 2>&1
# Diagnostics. SPEC_FLAG and RESEARCH_ARGS are set in the "Find spec
# and research dir" step above. RESEARCH_ARGS enables dogfood to
# verify novel features and sync them into .printing-press.json
# (required for publish-validate's transcendence gate).
"$PRINTING_PRESS_BIN" dogfood --dir "$CLI_DIR" $SPEC_FLAG "${RESEARCH_ARGS[@]}" 2>&1
"$PRINTING_PRESS_BIN" verify --dir "$CLI_DIR" $SPEC_FLAG --json 2>&1
"$PRINTING_PRESS_BIN" workflow-verify --dir "$CLI_DIR" --json > /tmp/polish-workflow-verify.json 2>&1 || true
"$PRINTING_PRESS_BIN" verify-skill --dir "$CLI_DIR" --json > /tmp/polish-verify-skill.json 2>&1 || true
# publish-validate is a publish-readiness gate, not a CLI-readiness gate.
# Mid-pipeline polish runs before the main SKILL's promote step and before
# the publish skill packages tools-manifest.json, so its prerequisites
# (manifest.printer from git config github.user, packaged tools manifest,
# phase5 acceptance proof relocated under $CLI_DIR/.manuscripts/<run>/proofs/)
# are not yet satisfied. Running publish-validate here would cascade
# parent-pipeline-owned failures into the polish ship_recommendation,
# which the main SKILL's Phase 5.5 verdict-override then turns into a
# CLI-level hold. Only run publish-validate when polish is the publish
# entry point (slash-command invocation or explicit --standalone).
if [ "$STANDALONE_MODE" = "true" ]; then
"$PRINTING_PRESS_BIN" publish validate --dir "$CLI_DIR" --json > /tmp/polish-publish-validate.json 2>&1 || true
fi
# --live-check samples novel-feature outputs and populates
# live_check.features[].warnings (Wave B entity detection) — required for
# the "Output entity warnings" row below to have data to read.
# RESEARCH_ARGS points scorecard at the run's research.json when the
# CLI lives under $PRESS_RUNSTATE/runs/<id>/working/<cli> (mid-pipeline
# polish). Without it, scorecard looks adjacent to the binary, doesn't
# find research.json, and reports `unable: true`.
"$PRINTING_PRESS_BIN" scorecard --dir "$CLI_DIR" $SPEC_FLAG "${RESEARCH_ARGS[@]}" --live-check --json > /tmp/polish-scorecard.json 2>&1 || true
"$PRINTING_PRESS_BIN" scorecard --dir "$CLI_DIR" $SPEC_FLAG 2>&1
"$PRINTING_PRESS_BIN" tools-audit "$CLI_DIR" --json > /tmp/polish-tools-audit-before.json 2>&1 || true
"$PRINTING_PRESS_BIN" pii-audit "$CLI_DIR" "${PII_ARGS[@]}" --json > /tmp/polish-pii-audit-before.json 2>&1 || true
go vet ./... 2>&1
if command -v gosec >/dev/null 2>&1; then
gosec -fmt=json -out=/tmp/polish-gosec-before.json ./... 2>&1 || true
else
go run github.com/securego/gosec/v2/cmd/gosec@v2.26.1 -fmt=json -out=/tmp/polish-gosec-before.json ./... 2>&1 || true
fi
verify-skill and workflow-verify run alongside dogfood/verify/scorecard so polish catches the same class of failures the public-library CI catches. publish-validate runs only when STANDALONE_MODE=true (slash-command or --standalone Skill-tool invocation). The publish-validate leg is a hard ship-gate for standalone polish: in that mode polish cannot recommend ship or ship-with-gaps while "$PRINTING_PRESS_BIN" publish validate reports passed: false. Mid-pipeline polish (STANDALONE_MODE=false) skips publish-validate entirely — its prerequisites (manifest.printer from git config github.user, packaged tools-manifest.json, phase5 acceptance proof relocated under $CLI_DIR/.manuscripts/<run>/proofs/) are parent-pipeline-owned and not yet satisfied at this point; the main SKILL's Phase 6 publish flow gates on publish-validate at the correct time. See "Ship logic" below for how this affects ship_recommendation.
Live matrix qualifier. After each scorecard --live-check --json run, read /tmp/polish-scorecard.json and record whether live_check actually exercised live samples. Treat it as exercised only when live_check.unable is false and at least one feature was evaluated (passed + failed > 0, or equivalent feature statuses). Treat live_check.unable: true, no live_check, no evaluated features, or missing credentials/token as not_exercised. A clean mock dogfood/verify run is still useful, but it is not a live matrix pass.
gosec runs as the off-the-shelf security static-analysis leg for hand-written Go. Prefer an installed gosec binary when present; otherwise use the pinned go run github.com/securego/gosec/v2/cmd/gosec@v2.26.1 fallback so a clean machine still gets a reproducible check without a separate setup step. Read /tmp/polish-gosec-before.json for the baseline finding count and issue details. If the command fails before writing JSON, treat the missing scan as a polish failure: add it to remaining_issues, set ship_recommendation: hold, and include the stderr summary so the next run can distinguish network/tooling failure from CLI defects. Prioritize findings in hand-authored files: whole files under internal/cli/, internal/syncer/, and internal/store/ whose first 20 lines lack the Generated by CLI Printing Press header are polish-owned novel-feature code. Findings in generator-emitted files are Printing Press retro candidates unless they can be fixed durably by changing the spec and regenerating; do not hand-edit generated files just to silence gosec.
If Phase 1 baseline reveals the underlying CLI needs re-discovery — broken HTML/SSR extraction, sparse capture (fewer than 5 unique endpoints in the source manuscript), wrong endpoint shapes, missing GraphQL operation hashes, or any signal that the CLI was generated from incomplete capture — polish does not normally do browser capture itself, but the shared playbook at skills/printing-press/references/browser-sniff-capture.md covers all available capture backends including the Claude chrome-MCP (mcp__claude-in-chrome__*) and computer-use (mcp__computer-use__*) when the runtime exposes them. Read Step 1 (tool detection), Step 2c.5 (failure-recovery menu), and Step 2e (chrome-MCP capture playbook) of that reference before improvising. Re-discovery from polish is rare but real; when it happens, use the shared backends — do not invent a new capture flow.
Parse findings into categories:
| Category | Source | What to look for |
|---|---|---|
| Verify failures | verify --json | Commands with score < 3 |
| SKILL static-check failures | verify-skill --json | Any findings[] with severity=error (flag-names, flag-commands, positional-args, unknown-command, canonical-sections). Hard ship-gate: ship cannot fire while these exist. |
| Workflow gaps | workflow-verify --json | Verdict workflow-fail. Soft gate: surface in remaining_issues and downgrade to hold when the workflow is the CLI's primary value. |
| Publish validation failures | publish validate --json | passed: false. Standalone polish only (runs only when STANDALONE_MODE=true); skipped in mid-pipeline polish where publish prerequisites aren't yet satisfied. When it runs, it's a hard ship-gate: ship cannot fire while publish validate fails. If the only failing check is missing phase5 acceptance, report phase5 acceptance required with the next-step command: authenticate, then run "$PRINTING_PRESS_BIN" dogfood --dir "$CLI_DIR" $SPEC_FLAG --live --level quick --write-acceptance <proofs-dir>/phase5-acceptance.json. Use the proofs directory from the validate error when present. |
| Security static-analysis failures | gosec JSON | Any Issues[] entry, especially G201/G202 SQL construction, G101 credential literals, or unsafe file/command execution. Hard ship-gate when the finding is in hand-authored novel-feature Go. |
| Dead code | dogfood | Dead functions, dead flags |
| Stale files | dogfood | Unregistered commands |
| Description issues | dogfood | Boilerplate root Short |
| README gaps | scorecard | README score < 8 |
| Example gaps | dogfood | Commands missing examples |
| Go vet issues | go vet | Any output |
| Output entity warnings | scorecard JSON | live_check.features[].warnings — raw HTML entities in human output |
| Output plausibility | Phase 4.85 | Findings from the agentic output review |
| MCP tool quality | tools-audit | Empty Short, thin Short, missing read-only annotations, thin MCP descriptions |
| Customer PII | pii-audit | Card last-4, email, phone, ZIP+4, postal-address shapes in high-risk files (manuscripts, fixtures, README) |
Environmental failures vs. CLI defects. Some Phase 1 outputs surface failures that aren't real CLI bugs and should not block ship:
scorecard --live-checkreportingSQLITE_BUSY, network timeouts,401from a mock or expired token, or HTTP errors that depend on the test workspace's permissions/state — these are test-environment issues, not CLI defects.verifymock-harness flakes on commands with binary output (e.g.,qrreturning a PNG that the substring matcher can't validate) or commands with optional positional args where dry-run output legitimately doesn't contain the verify probe string.- Learn-loop commands: the dogfood/verify matrix now covers the default-on learn surface (
teach,recall,learnings,playbook).teachandteach-playbookintentionally exit 2 when invoked bare, declared viapp:typed-exit-codes, so verify scores that as pass; do not "fix" the exit code.learnings statson a fresh print legitimately reports zeros and empty sections; that is an empty local store, not a defect.
Classify these as environmental in skipped_findings with the specific reason; do not spend Phase 2 cycles trying to "fix" them. The polish skill's ship logic already excludes live-check failures from gating, but the agent should still annotate them so reviewers can see they were considered and dismissed deliberately.
Phase 4.85 — Agentic output review (Wave B)
After the mechanical diagnostics above complete, invoke the printing-press-output-review sub-skill via the Skill tool. The sub-skill carries context: fork and owns the dispatch prompt, gate logic, and known blind spots — single source of truth shared with the main printing-press skill.
Skill(
skill: "cli-printing-press:printing-press-output-review",
args: "$CLI_DIR"
)
Parse the returned ---OUTPUT-REVIEW-RESULT--- block. status: WARN findings flow into the diagnostic categories above so Phase 2 fixes address both rule-based and plausibility issues. status: SKIP is informational — record but don't block.
Wave B gating applies: all findings are warnings, never blockers. Fix if obvious and cheap; document with a short comment if deferred.
Record baseline scores: scorecard total, verify pass rate, dogfood verdict, live matrix qualifier (exercised / not_exercised), go vet issue count, gosec finding count, output-review finding count.
Phase 2: Fix
Fix in priority order. After each priority level, update the lock heartbeat:
"$PRINTING_PRESS_BIN" lock update --cli "$CLI_NAME" --phase polish 2>/dev/null
Runtime variant default checklist
If a polish fix adds or changes a runtime mode, data-source option, auth tier, transport, or other user-visible default, document this short checklist before selecting the default:
- User-visible default: which behavior users get without extra flags or config.
- Compatibility risk: whether existing commands, scripts, MCP tools, or stored config change behavior.
- Verification command: the exact command that proves the default and the non-default escape hatch both work.
Keep the checklist in the polish notes or result block. Skip it for ordinary bug fixes that do not change runtime variants or defaults.
Cross-cutting API-call instrumentation
When polish builds a feature class that must observe every outbound API call,
such as a quota ledger, request log, or audit trail, instrument the generated
client middleware in internal/client/client.go instead of individual command
handlers. Prefer a shared pre-dispatch hook when one exists; otherwise cover
both do() and doRead(). The do() path handles standard endpoint mirrors,
sync iterations, and novel features that use the generated client, while
doRead() handles read-only operations that ride POST-like transports, such as
GraphQL queries, JSON-RPC reads, and POST-based searches marked
mcp:read-only.
Per-command hooks under-count because they only see the commands polish touched.
Novel-feature data routing
When polish adds or fixes a novel command that reads API response data into the local store, route data through the generated typed schemas instead of writing raw response JSON directly into tables:
- Read
internal/types/<resource>.goandinternal/store/<resource>.gofor the resource being cached. Use the typed insert/upsert helpers those files emit whenever they exist. If helpers are missing for a generated resource, create them before writing any persistence code. - Check the response shape against the spec schema and a real sample response before deciding the insert mapping is correct.
- Decode the API response into the typed struct before persistence. Do not
build
INSERT INTO ...statements from untypedmap[string]anyor rawjson.RawMessagevalues unless the table is a custom polish-owned table declared in hand-authored migrations; see the raw SQL exception below. - Verify the target table from the resource type, not from whichever query the
novel command happened to start with. A command that fetched
<child>records must insert<child>rows, not parent rows or a convenient adjacent table. - Normalize nested response identifiers before insert. If the API wraps the scalar id inside an object that also contains metadata, extract the scalar id field and store that value; never store the whole id object as a primary key or foreign-key reference.
Raw database/sql writes are acceptable only for custom polish-owned tables
declared in hand-authored migrations. In that case, keep the table schema
explicit in internal/store/, document why the generated resource helper does
not apply, and still decode nested identifiers to scalars before persistence.
Priority 0: MCP surface migration (legacy CLIs)
If Phase 1's dogfood reported MCP Surface: FAIL with a parity mismatch, the CLI was generated before the runtime cobratree walker existed and is still on the static internal/mcp/tools.go surface. The fix is mechanical:
"$PRINTING_PRESS_BIN" mcp-sync "$CLI_DIR"
That migrates the MCP surface to the runtime walker, regenerates tools-manifest.json and internal/mcp/tools.go, and applies any mcp-descriptions.json overrides. If it exits with mcp-sync refused and reprint required, stop this polish run and hand off to /printing-press-reprint; the target CLI's generated client is too old for the current MCP handler, so rewriting tools.go alone would break its build. Include in the handoff that the reprint flow must run its normal dogfood gate against the regenerated CLI and confirm MCP Surface: PASS before shipping. Do not rerun dogfood against the stale $CLI_DIR. After a successful mcp-sync, re-run dogfood here; the parity gate should flip to PASS. Running mcp-sync on a compatible CLI already using the runtime walker is a no-op refresh.
Skip this priority on CLIs where dogfood's MCP gate is already passing.
Priority 1: Security static-analysis failures
For each gosec finding in /tmp/polish-gosec-before.json:
- Read the cited file and confirm whether it is hand-authored. A whole file
under
internal/cli/,internal/syncer/, orinternal/store/whose first 20 lines lackGenerated by CLI Printing Pressis polish-owned novel-feature code. - If the finding is in hand-authored code, fix the root cause in source before working lower-priority polish items. Examples: replace SQL string assembly with parameterized queries, avoid shelling out with untrusted args, and move literals that look like credentials into config/env plumbing.
- If gosec flags generated code, do not hand-edit the generated file. Either
fix the upstream spec and regenerate, or add a
skipped_findingsentry that names it as a generator retro candidate with the rule id and file path. - If gosec reports a false positive, keep the suppression narrow and explain
it. Prefer a local
// #nosec G### -- reasononly when the code is actually safe and the reason is durable; otherwise fix the code.
Unresolved gosec findings in hand-authored novel-feature Go are hard blockers:
they must appear in remaining_issues, force ship_recommendation: hold, and
set further_polish_recommended: yes when the fix is plausibly mechanical.
Priority 2: Verify failures
For each command that fails verify dry-run or exec:
- Read the command file
- Find
Args: cobra.ExactArgs(N)or similar constraint - Remove the
Args:field - Add at the top of
RunE:if len(args) == 0 { return cmd.Help() } - For commands needing 2+ args, use
if len(args) < 2 - Check for dry-run nil-data crashes and add guards:
if flags.dryRun { return nil }
Priority 3: Dead code
- For each dead function flagged by dogfood, grep all
.gofiles to verify it's truly unused (not just its definition matching itself) - If truly unused: remove the function
- If used by another helper: leave it (false positive)
- After removal, remove unused imports
- Delete stale files (promoted commands not registered in root.go)
Priority 4: CLI description and metadata
- Read root command
Shortininternal/cli/root.go - If it contains boilerplate ("Reverse-engineered...", raw API title), rewrite:
Pattern:
"<Product> CLI with <capability-1>, <capability-2>, and <capability-3>" - Check commands for missing
Examplefields. Add realistic examples with domain-specific values.
Priority 5: README
Cardinal rule: run <cli> <cmd> --help for EVERY command you put in the
README. Never guess flag names, argument formats, or valid values. If you
write --start-time but the flag is --start, the README is wrong and
users will get errors on their first try.
Source-of-truth files for rendered sections
Before editing README.md, SKILL.md, or .printing-press.json, identify whether
the section is rendered from a source file. Dogfood and
…(truncated)