Skip to main content
skill CX Codex

printing-press-polish — Claude Code skill: how to install

Runs diagnostics on generated CLIs, fixes test failures automatically, and prepares them to publish. Useful when verify pass rate drops below 80% or scorecard falls under 85.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add mvanhorn/cli-printing-press --skill printing-press-polish -a claude-code
Open on GitHub

What printing-press-polish does

This skill cleans up and tests Go CLIs generated by Printing Press. It runs diagnostics including dogfood and verify, alongside static analysis with go vet and gosec. The agent fixes detected failures directly, pruning dead code and updating README documentation. At the end, it reports a before-and-after comparison and offers to publish.

Execution takes place inside a forked context (context: fork). This isolates diagnostic iterations and noisy build logs from your main session. The caller receives only a concise summary. You can run the command directly on projects stored under $PRESS_LIBRARY/. It targets scenarios where the verify pass rate drops below 80% or shipcheck flags ship-with-gaps.

Without Go and a cli-printing-press binary at version 4.0.0 or newer, setup halts immediately. Disk space is checked before execution. If available space falls below 524288 KB, the script fails right away. Go build caches and module downloads consume significant disk room.

What it helps with

  • fix verification failures and static analysis warnings in generated CLIs
  • remove dead code from compiled Go projects
  • update README docs and MCP tool definitions
  • evaluate CLI readiness against gosec and scorecard gates

Example prompts for the agent

  • /printing-press-polish redfin
  • Fix verify issues and make redfin-pp-cli publish-ready
  • Clean up the CLI in "$PRESS_LIBRARY/redfin"

How to install printing-press-polish

1Skills CLI (Vercel)

npx skills add mvanhorn/cli-printing-press --skill printing-press-polish -a claude-code

2Manually: put the files in place

~/.claude/skills/printing-press-polish/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/printing-press-polish/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about printing-press-polish

How to install printing-press-polish in Claude Code?

Run npx skills add mvanhorn/cli-printing-press --skill printing-press-polish -a claude-code. Alternatively, you can place the file manually at ~/.claude/skills/printing-press-polish/SKILL.md.

What does printing-press-polish do?

It runs diagnostics like dogfood, verify, scorecard, go vet, and gosec against a Go CLI. The skill fixes verification failures, removes dead code, polishes documentation, and outputs a diff before offering to publish.

What are the system requirements for printing-press-polish?

You need Go version 1.26.10 or newer and the cli-printing-press binary with version 4.0.0 or higher. The partition for $PRINTING_PRESS_HOME must also have at least 524288 KB of free disk space.

Source text

SKILL.md · MIT
---
name: printing-press-polish
description: >
  Polish a generated CLI to pass verification and become publish-ready. Runs
  diagnostics (dogfood, verify, scorecard, go vet, gosec), automatically fixes
  all issues (verify failures, static-analysis findings, dead code,
  descriptions, README, MCP tool quality), reports the before/after delta, and
  offers to publish. Use after any /printing-press run, or on any CLI in
  $PRESS_LIBRARY/. Trigger phrases: "polish", "improve the CLI", "fix verify",
  "make it publish-ready", "clean up the CLI", "get this ready to ship".
context: fork
min-binary-version: "4.0.0"
allowed-tools:
  - Bash
  - Read
  - Glob
  - Grep
  - Write
  - Edit
  - AskUserQuestion
created_by: user
---

# /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.

```bash
/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

```bash
# 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 'BEGIN {
    if ((getline header) <= 0 || (getline record) <= 0) exit
    field_count = split(record, fields)
    if (field_count >= 4) print fields[4]
  }')"
  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.10 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() {
  if [ -z "${PP_SEMVER_A:-}" ] || [ -z "${PP_SEMVER_B:-}" ]; then
    echo "[setup-error] semver comparison inputs are missing." >&2
    return 2
  fi
  awk -v a="${PP_SEMVER_A:-}" -v b="${PP_SEMVER_B:-}" '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' "${PP_GO_VERSION_INPUT:-}" | sed -nE 's/.*go([0-9]+)\.([0-9]+)(\.([0-9]+))?.*/\1.\2.\4/p' | sed -E 's/\.$/.0/'
}

_pp_check_go_currency() {
  _pp_go_installed="$(PP_GO_VERSION_INPUT="$(go env GOVERSION 2>/dev/null)" _pp_go_version_norm)"
  _pp_go_required="$(PP_GO_VERSION_INPUT="$(go version "$PRINTING_PRESS_BIN" 2>/dev/null)" _pp_go_version_norm)"
  PP_SEMVER_A="$_pp_go_installed"
  PP_SEMVER_B="$_pp_go_required"
  if [ -z "$_pp_go_installed" ] || [ -z "$_pp_go_required" ] || ! _pp_semver_lt; 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 as `USER_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:
1. If it is an absolute or `~`-prefixed path and exists, use it
2. Try `$PRESS_LIBRARY/<arg>` (exact match — works for slug like `redfin`)
3. If it has `-pp-cli` suffix, strip it and try `$PRESS_LIBRARY/<slug>` (e.g., `redfin-pp-cli` → `redfin`)
4. 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 as `STANDALONE_MODE=true` unconditionally — 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=false` by 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 `--standalone` in `args` (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`:

```yaml
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").

```bash
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

```bash
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:**

1. **Locate the public library clone.** Honor `$PRINTING_PRESS_LIBRARY_PUBLIC` if set; otherwise scan the user's filesystem however fits this platform. Validate every candidate by checking the git remote points at `mvanhorn/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.
2. **Locate this CLI inside the clone.** `find <clone>/library -type d -name "<api>-pp-cli"` or equivalent.
3. **Run `diff -r <public-cli-dir> $CLI_DIR`** with 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.mod` and `go.sum` — publish rewrites the module path from `<api>-pp-cli` to `github.com/mvanhorn/printing-press-library/library/<category>/<api>`
   - All `.go` files where the only difference is the rewritten import path (the publish step propagates the new module path through every internal import). When inspecting `.go` diffs, 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** the `cmd/<api>-pp-cli/` and `cmd/<api>-pp-mcp/` source directories. Excluding by binary name silently skips the entire `cmd/` subtree, hiding real divergence in `main.go`. The "Only in $CLI_DIR: <api>-pp-cli" line for the built binary is one row of expected output, not noise worth filtering at the cost of completeness.
4. **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

```bash
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
# Released gosec bundles a golang.org/x/tools that cannot read newer Go export
# data, so on a newer toolchain it silently skips packages. polish_gosec uses an
# installed gosec only when it loads every package; otherwise it builds the
# pinned gosec against a current x/tools in a private temp dir. Each block runs
# in its own shell, so the after-fix scan repeats this definition verbatim.
polish_gosec() {
  local out="$*" built
  if command -v gosec >/dev/null 2>&1; then
    gosec -fmt=json -out="$out" ./... 2>&1 || true
    [ "$(jq '(."Golang errors" // {}) | length' "$out" 2>/dev/null || echo 1)" = 0 ] && return 0
    echo "polish_gosec: installed gosec could not load every package; building the pinned fallback" >&2
  fi
  built=$(mktemp -d) || return 1
  if ! (cd "$built" && export GOWORK=off && go mod init polish-gosec && go get github.com/securego/gosec/v2/cmd/[email protected] golang.org/x/[email protected] && go build -o gosec github.com/securego/gosec/v2/cmd/gosec) >"$built/build.log" 2>&1; then
    echo "polish_gosec: could not build the gosec fallback:" >&2
    tail -n 20 "$built/build.log" >&2
    rm -rf "$built"
    return 1
  fi
  "$built/gosec" -fmt=json -out="$out" ./... 2>&1 || true
  rm -rf "$built"
}
polish_gosec /tmp/polish-gosec-before.json
```

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; `polish_gosec` (defined verbatim in both the baseline and after-fix blocks) falls back to building the pinned `github.com/securego/gosec/v2/cmd/[email protected]` against `golang.org/x/[email protected]` when no binary is installed or the installed one cannot load every package, so a clean machine on a current Go toolchain still gets a full, 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 (including a fallback build failure, which `polish_gosec` reports on stderr with a non-zero return), or the JSON still has a non-empty `"Golang errors"` map (packages gosec could not type-check, so their findings are missing), treat the scan as a polish failure: add it to `remaining_issues`, set `ship_recommendation: hold`, and include the stderr or error 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. When re-discovery is required, use the capture backends the runtime already exposes (Claude chrome-MCP `mcp__claude-in-chrome__*` and computer-use `mcp__computer-use__*`) rather than inventing a new capture flow. Re-...

© mvanhorn · MIT · mvanhorn/cli-printing-press

Similar skills

All skills for Code review and refactoring →

From the same set9

printing-press Generates shippable Go CLI binaries from OpenAPI, HAR, or Postman specs through an automated 21-phase research, build, and test loop. CX Codex +1 7,600installs printing-press-publish Publishes a generated local CLI to mvanhorn/printing-press-library as a pull request. The skill stops and reports the reason when testing is blocked. CX Codex +1 6,900installs printing-press-import Pulls a published CLI from the public library into your internal copy, module path reverted and manuscripts alongside. Useful when the CLI isn't on your machine. CX Codex +1 6,500installs printing-press-retro Runs a structured retrospective after a Printing Press CLI generation run, redacts sensitive data, and drafts GitHub issues for core tool defects. CX Codex +1 6,500installs printing-press-score Scores a generated CLI against the Steinberger bar and compares two CLIs. Use it when checking output from cli-printing-press. CX Codex +1 6,400installs printing-press-output-review An internal reviewer skill for printed CLIs. It catches plausibility bugs in command output that rule-based checks and live verification miss. CX Codex +1 6,400installs printing-press-reprint Regenerates an existing CLI from scratch under Printing Press while preserving prior research and patches. Useful when upgrading the generator or MCP surface. CX Codex +1 6,400installs printing-press-amend Amends published CLI tools using session logs or explicit prompts, scrubs personal data, and opens pull requests with library patches. CX Codex +1 5,500installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor