[object Object]

← back to Cli Printing Press

feat(skills): add /printing-press-amend skill — dogfood + direct-input modes (#1490)

c31632b3fbb2fb918e1f29cf0ca1b72a35ca1d56 · 2026-05-16 13:14:54 -0700 · Matt Van Horn

* feat(cli): add verify-internal-skill subcommand

Lints internal-skill SKILL.md files (frontmatter + canonical sections).
Distinct from verify-skill, which validates a printed CLI's SKILL.md
against its Go source — internal skills (polish, retro, publish, amend)
have no internal/cli/ to verify against.

Checks: frontmatter-parse, frontmatter-required (name, description,
allowed-tools), name-matches-dir, allowed-tools-shape, body-has-heading
(warn). All three existing internal skills pass cleanly.

* feat(skills): scaffold printing-press-amend skill

New skill that wraps the dogfood-to-PR loop for a printed CLI in
mvanhorn/printing-press-library. Mines the active session transcript
for friction, scopes with the user, plans + executes the fix, scrubs
PII, opens a PR. Two user-in-loop checkpoints (scope, PR draft).

This commit lands the SKILL.md skeleton (frontmatter + setup contract +
phase-header placeholders) plus the brainstorm requirements doc and
implementation plan that drove it. Phase bodies (U2-U7) land in
follow-up commits.

Adds skills/printing-press-amend/SKILL.md to the
TestSkillSetupBlocksMatchWorkspaceContract test slice — setup-contract
parity with publish/score/catalog enforced.

* fix(cli): use strings.SplitSeq in verify-internal-skill

Satisfies golangci-lint modernize hint. Pure refactor — same behavior,
no test changes needed.

* feat(skills): flesh out printing-press-amend phase bodies + references

Adds the substantive Phase 1-7 bodies to skills/printing-press-amend/
SKILL.md, plus three reference files:

- transcript-parsing.md (Phase 1: read active session transcript,
  extract friction signals, classify bug-vs-feature, auto-detect
  target CLI, confirm with user)
- pii-scrubbing.md (Phase 5: three-layer scrub — credentials reused
  from retro/secret-scrubbing.md, entities from a user-maintained
  stop-list at ~/.printing-press/amend-config.yaml, plus first-mention
  defense for unrecognized capitalized phrases)
- library-pr-plumbing.md (Phase 6+7: managed-clone bootstrap, branch
  collision detection, issue ownership, push + gh pr create with
  durable HEAD_SHA evidence URLs — adapted from publish Steps 5/7/8)

Phase 4 operates directly on the managed clone of mvanhorn/printing-
press-library at $PRESS_HOME/.publish-repo-$PRESS_SCOPE (per the
plan's pre-implementation decision), enforces the public library's
mandatory `// PATCH(...)` source-comment + .printing-press-patches.json
contract, and runs `printing-press publish validate` with up-to-3
retry iterations.

Two user-in-loop checkpoints: scope confirmation after capture (U4),
PR draft review before any gh command fires (U7).

* docs(cli): add /printing-press-amend to README skill catalog

One-line entry under Publish, in the same details/summary shape as the
other skills.

* feat(skills): expand printing-press-amend frontmatter for direct-input mode

Broaden description to cover both dogfood and direct-input input modes.
Add trigger phrases for user-supplied asks (rename, add feeds, sniff for
new APIs, amend with these ideas).

U1 of docs/plans/2026-05-16-001-feat-printing-press-amend-direct-input-mode-plan.md

* feat(skills): add Phase 0 input mode detection to printing-press-amend

Insert a new "## Phase 0 — Input Mode Detection" section between Setup
and Phase 1. Documents the detection rubric (dogfood, direct, both,
ambiguous), the AskUserQuestion fallback, the default-dogfood preservation
rule, and the runstate persistence path. Phase 1's existing dogfood body
is untouched; U3 introduces the direct-input sub-section.

U2 of docs/plans/2026-05-16-001-feat-printing-press-amend-direct-input-mode-plan.md

* feat(skills): split printing-press-amend Phase 1 into mode-conditional sub-sections

Rename "## Phase 1 — Friction Capture" to "## Phase 1 — Capture" and
split into ### 1a (dogfood, existing body lifted verbatim) and ### 1b
(direct-input, new). Document the shared typed finding list shape with
new "provenance" field. Reserve ### 1b.i for the sniff subroutine
(filled in by U4).

transcript-parsing.md gets a scope note pointing to direct-input-parsing.md
for the new mode; its body is unchanged.

U3 of docs/plans/2026-05-16-001-feat-printing-press-amend-direct-input-mode-plan.md

* feat(skills): add sniff finding subroutine to printing-press-amend Phase 1b

Document the opt-in sniff path that runs printing-press crowd-sniff (and
optionally browser-sniff with a user-supplied HAR) against the target
CLI's source URL, then converts each discovered candidate endpoint to a
Tier-3 add-endpoint finding with provenance: sniff.

Includes the six steps (resolve URL, crowd-sniff, optional browser-sniff,
convert to findings, degraded-path table, provenance surface at Phase 3).
Sniff is gated on an explicit kind: sniff ask — never auto-invoked.

U4 of docs/plans/2026-05-16-001-feat-printing-press-amend-direct-input-mode-plan.md

* feat(skills): add direct-input-parsing reference for printing-press-amend

New reference doc loaded by Phase 1b. Documents the ask-to-finding
parsing rubric (six finding kinds), the finding shape (with new
provenance field), target-CLI resolution rules (regex extraction + Phase 0
fallback), and edge cases (multi-CLI, ambiguous verbs, bare URLs,
conflicting kinds, combined-mode merging).

Mirrors transcript-parsing.md structure so Phase 1a and Phase 1b reference
docs feel parallel.

U5 of docs/plans/2026-05-16-001-feat-printing-press-amend-direct-input-mode-plan.md

* docs(skills): v0.2 addenda to amend brainstorm + plan; add direct-input plan

Append a v0.2 amendment section to the original brainstorm and original
v0.1 plan pointing at the new direct-input plan. Preserves both originals
as the dogfood-mode design record; the v0.2 design (input-mode detection,
direct-input parsing, sniff finding type) lives in its own plan file.

Add docs/plans/2026-05-16-002-feat-printing-press-amend-direct-input-mode-plan.md
covering U1-U6 of the v0.2 expansion. Sequence number 002 to avoid
collision with the parallel-session bugbounty-goat plan filed today.

U6 of docs/plans/2026-05-16-002-feat-printing-press-amend-direct-input-mode-plan.md

* fix(skills): count only newly-added PATCH markers in printing-press-amend parity check

Greptile P1: the parity check used grep -rc to count // PATCH(...) markers
across all Go files in $CLI_DIR, including markers from prior amend runs.
That cumulative count compared against the per-run declared patch_count
silently passed when prior history made the running total meet or exceed
the declared count, even with zero new markers added.

Switch to git format-patch --stdout + grep '^+.*// PATCH(' so the count
reflects only newly-added markers in this run's diff against upstream.
format-patch is used over git diff to stay robust against environments
where git diff is intercepted by a pager/shim that reformats unified-diff
output.

Resolves Greptile finding on PR #1490.

* fix(skills): relabel printing-press-amend PR-body Evidence block

Greptile P1: the Evidence block labeled .printing-press-patches.json as
"Plan doc" and the next line said "see the plan doc at the path above"
- the URL points at the patches JSON, not the plan doc, which lives
locally at $PRESS_MANUSCRIPTS/<slug>/<run-id>/proofs/... and is never
committed to the public library.

Relabel the URL as "Patch record", point per-finding rationale at the
Findings table + patch record, and note the local plan doc location as
context for the original printer (not as a clickable artifact for
external reviewers). The plan doc stays local by design - it's a
PII-scrubbed audit record, not part of the PR contract.

Resolves Greptile finding on PR #1490. Origin R27's requirement for a
full GitHub URL to the plan doc is unsatisfiable as written and is
silently revised here; flag for retro follow-up.

* fix(skills): hard-stop existing-open-PR path in library-pr-plumbing

Greptile P2: when existing_open was non-empty the snippet printed a
warning and a # AskUserQuestion: ... shell comment, then unconditionally
ran git checkout -b "$BRANCH_NAME" - which fails with a confusing
already-exists error because the local branch from the open PR exists.

Replace the inert comment with a hard exit 1, a concrete two-option
resolution menu printed to the user, and a follow-up prose block that
documents the AskUserQuestion-then-re-enter contract the skill driver
must honor. Literal shell-flow execution now stops at the right place
with an actionable message.

Resolves Greptile finding on PR #1490.

* fix(cli): mark --dir as required on verify-internal-skill

Greptile P2: --dir was validated via a manual `if dir == ""` check in
RunE, which works but doesn't surface the constraint in Cobra's --help
output and runs the validation later than necessary.

Replace with cmd.MarkFlagRequired("dir") after the StringVar
declaration. Missing --dir now produces "required flag(s) \"dir\" not
set" via Cobra's standard flow and the flag is annotated as required
in --help output. The manual check is removed as redundant.

Resolves Greptile finding on PR #1490.

* fix(skills): make 90-days-ago date portable in printing-press-amend

Greptile P1: `date -v-90d` is BSD/macOS-only. On Linux it emits
"date: invalid option -- 'v'" to stderr and produces empty stdout,
leaving the GitHub search qualifier as `merged:>` with no value.
The recently-merged-PR dedup guard then silently returned no results,
defeating Phase 2a's purpose on any non-macOS machine.

Replace with a three-way fallback: GNU `date -d '90 days ago'` first,
BSD `date -v-90d` second, python3 timezone-aware UTC date as final
fallback. Hard exit 1 if all three fail rather than letting the dedup
guard silently drop out.

Resolves Greptile finding on PR #1490.

* fix(skills): assign dogfood_status=N/A in direct-input mode

Greptile P1 (re-review 18:50): the PR body template printed `- Dogfood:
$dogfood_status` and the Phase 8 RESULT block included `dogfood_status:
<PASS|FAIL|N/A>`, but Phase 4's output spec omitted the variable
entirely and no phase instruction assigned it in direct-input mode.
Every PR from a direct-input run would show an empty `- Dogfood:` line.

Add dogfood_status to Phase 4's output spec with a per-mode contract:
  - MODE=dogfood: PASS|FAIL from the dogfood validation step
  - MODE=direct:  always N/A (no transcript to dogfood against)
  - MODE=both:    PASS|FAIL on the transcript half of the findings
Default must be set by end of Phase 4 so Phase 7/8 never see empty.

Also add defensive ${dogfood_status:-N/A} in the PR body template as
belt-and-suspenders against any future phase forgetting to assign it.

Resolves Greptile finding on PR #1490.

* fix(skills): force-checkout main on managed-clone refresh

Greptile P1 (x2, SKILL.md:460 + library-pr-plumbing.md:86): if a prior
run aborted between Phase 4's file edits and Phase 7's commit, the
managed clone is left on an amend branch with uncommitted changes in
$CLI_DIR. The refresh block's `git checkout main` then fails because
those edits would be overwritten, and the subsequent `git reset --hard
upstream/main` never runs - leaving the clone stuck in a broken state.

Add -f to discard local edits. The managed clone is documented as a
scratch surface owned by the skill; any leftover edits are by definition
abort residue that must be discarded before the next run reuses it.
This is consistent with the existing reset --hard immediately after.

Resolves Greptile finding on PR #1490.

* fix(skills): use working-tree git diff for parity check (not format-patch)

Greptile P1: my prior G1 fix used git format-patch --stdout
upstream/main..HEAD, but the parity check runs in Phase 4 Step 6 -
BEFORE Phase 7's commit. The edits live only in the working tree, so
upstream/main..HEAD is an empty commit range and format-patch emits
nothing. new_patch_markers is always 0:

  - When patches_entry > 0 (any real patch run): 0 < patches_entry is
    true and exit 1 fires unconditionally, blocking every valid run.
  - When patches_entry = 0: the check silently passes regardless.

Switch back to working-tree git diff (the right tool for pre-commit
state). Add --no-pager + --no-color + --no-ext-diff to defeat
colorized output and any configured diff.external tool that would
reformat the diff away from unified-diff shape.

Smoke-tested the corrected snippet against a fresh repo with uncommitted
PATCH-bearing edits: returns 1 for the 1 new marker, as expected.

Resolves Greptile finding on PR #1490 (refines G1's prior fix).

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

Files touched

Diff

commit c31632b3fbb2fb918e1f29cf0ca1b72a35ca1d56
Author: Matt Van Horn <mvanhorn@users.noreply.github.com>
Date:   Sat May 16 13:14:54 2026 -0700

    feat(skills): add /printing-press-amend skill — dogfood + direct-input modes (#1490)
    
    * feat(cli): add verify-internal-skill subcommand
    
    Lints internal-skill SKILL.md files (frontmatter + canonical sections).
    Distinct from verify-skill, which validates a printed CLI's SKILL.md
    against its Go source — internal skills (polish, retro, publish, amend)
    have no internal/cli/ to verify against.
    
    Checks: frontmatter-parse, frontmatter-required (name, description,
    allowed-tools), name-matches-dir, allowed-tools-shape, body-has-heading
    (warn). All three existing internal skills pass cleanly.
    
    * feat(skills): scaffold printing-press-amend skill
    
    New skill that wraps the dogfood-to-PR loop for a printed CLI in
    mvanhorn/printing-press-library. Mines the active session transcript
    for friction, scopes with the user, plans + executes the fix, scrubs
    PII, opens a PR. Two user-in-loop checkpoints (scope, PR draft).
    
    This commit lands the SKILL.md skeleton (frontmatter + setup contract +
    phase-header placeholders) plus the brainstorm requirements doc and
    implementation plan that drove it. Phase bodies (U2-U7) land in
    follow-up commits.
    
    Adds skills/printing-press-amend/SKILL.md to the
    TestSkillSetupBlocksMatchWorkspaceContract test slice — setup-contract
    parity with publish/score/catalog enforced.
    
    * fix(cli): use strings.SplitSeq in verify-internal-skill
    
    Satisfies golangci-lint modernize hint. Pure refactor — same behavior,
    no test changes needed.
    
    * feat(skills): flesh out printing-press-amend phase bodies + references
    
    Adds the substantive Phase 1-7 bodies to skills/printing-press-amend/
    SKILL.md, plus three reference files:
    
    - transcript-parsing.md (Phase 1: read active session transcript,
      extract friction signals, classify bug-vs-feature, auto-detect
      target CLI, confirm with user)
    - pii-scrubbing.md (Phase 5: three-layer scrub — credentials reused
      from retro/secret-scrubbing.md, entities from a user-maintained
      stop-list at ~/.printing-press/amend-config.yaml, plus first-mention
      defense for unrecognized capitalized phrases)
    - library-pr-plumbing.md (Phase 6+7: managed-clone bootstrap, branch
      collision detection, issue ownership, push + gh pr create with
      durable HEAD_SHA evidence URLs — adapted from publish Steps 5/7/8)
    
    Phase 4 operates directly on the managed clone of mvanhorn/printing-
    press-library at $PRESS_HOME/.publish-repo-$PRESS_SCOPE (per the
    plan's pre-implementation decision), enforces the public library's
    mandatory `// PATCH(...)` source-comment + .printing-press-patches.json
    contract, and runs `printing-press publish validate` with up-to-3
    retry iterations.
    
    Two user-in-loop checkpoints: scope confirmation after capture (U4),
    PR draft review before any gh command fires (U7).
    
    * docs(cli): add /printing-press-amend to README skill catalog
    
    One-line entry under Publish, in the same details/summary shape as the
    other skills.
    
    * feat(skills): expand printing-press-amend frontmatter for direct-input mode
    
    Broaden description to cover both dogfood and direct-input input modes.
    Add trigger phrases for user-supplied asks (rename, add feeds, sniff for
    new APIs, amend with these ideas).
    
    U1 of docs/plans/2026-05-16-001-feat-printing-press-amend-direct-input-mode-plan.md
    
    * feat(skills): add Phase 0 input mode detection to printing-press-amend
    
    Insert a new "## Phase 0 — Input Mode Detection" section between Setup
    and Phase 1. Documents the detection rubric (dogfood, direct, both,
    ambiguous), the AskUserQuestion fallback, the default-dogfood preservation
    rule, and the runstate persistence path. Phase 1's existing dogfood body
    is untouched; U3 introduces the direct-input sub-section.
    
    U2 of docs/plans/2026-05-16-001-feat-printing-press-amend-direct-input-mode-plan.md
    
    * feat(skills): split printing-press-amend Phase 1 into mode-conditional sub-sections
    
    Rename "## Phase 1 — Friction Capture" to "## Phase 1 — Capture" and
    split into ### 1a (dogfood, existing body lifted verbatim) and ### 1b
    (direct-input, new). Document the shared typed finding list shape with
    new "provenance" field. Reserve ### 1b.i for the sniff subroutine
    (filled in by U4).
    
    transcript-parsing.md gets a scope note pointing to direct-input-parsing.md
    for the new mode; its body is unchanged.
    
    U3 of docs/plans/2026-05-16-001-feat-printing-press-amend-direct-input-mode-plan.md
    
    * feat(skills): add sniff finding subroutine to printing-press-amend Phase 1b
    
    Document the opt-in sniff path that runs printing-press crowd-sniff (and
    optionally browser-sniff with a user-supplied HAR) against the target
    CLI's source URL, then converts each discovered candidate endpoint to a
    Tier-3 add-endpoint finding with provenance: sniff.
    
    Includes the six steps (resolve URL, crowd-sniff, optional browser-sniff,
    convert to findings, degraded-path table, provenance surface at Phase 3).
    Sniff is gated on an explicit kind: sniff ask — never auto-invoked.
    
    U4 of docs/plans/2026-05-16-001-feat-printing-press-amend-direct-input-mode-plan.md
    
    * feat(skills): add direct-input-parsing reference for printing-press-amend
    
    New reference doc loaded by Phase 1b. Documents the ask-to-finding
    parsing rubric (six finding kinds), the finding shape (with new
    provenance field), target-CLI resolution rules (regex extraction + Phase 0
    fallback), and edge cases (multi-CLI, ambiguous verbs, bare URLs,
    conflicting kinds, combined-mode merging).
    
    Mirrors transcript-parsing.md structure so Phase 1a and Phase 1b reference
    docs feel parallel.
    
    U5 of docs/plans/2026-05-16-001-feat-printing-press-amend-direct-input-mode-plan.md
    
    * docs(skills): v0.2 addenda to amend brainstorm + plan; add direct-input plan
    
    Append a v0.2 amendment section to the original brainstorm and original
    v0.1 plan pointing at the new direct-input plan. Preserves both originals
    as the dogfood-mode design record; the v0.2 design (input-mode detection,
    direct-input parsing, sniff finding type) lives in its own plan file.
    
    Add docs/plans/2026-05-16-002-feat-printing-press-amend-direct-input-mode-plan.md
    covering U1-U6 of the v0.2 expansion. Sequence number 002 to avoid
    collision with the parallel-session bugbounty-goat plan filed today.
    
    U6 of docs/plans/2026-05-16-002-feat-printing-press-amend-direct-input-mode-plan.md
    
    * fix(skills): count only newly-added PATCH markers in printing-press-amend parity check
    
    Greptile P1: the parity check used grep -rc to count // PATCH(...) markers
    across all Go files in $CLI_DIR, including markers from prior amend runs.
    That cumulative count compared against the per-run declared patch_count
    silently passed when prior history made the running total meet or exceed
    the declared count, even with zero new markers added.
    
    Switch to git format-patch --stdout + grep '^+.*// PATCH(' so the count
    reflects only newly-added markers in this run's diff against upstream.
    format-patch is used over git diff to stay robust against environments
    where git diff is intercepted by a pager/shim that reformats unified-diff
    output.
    
    Resolves Greptile finding on PR #1490.
    
    * fix(skills): relabel printing-press-amend PR-body Evidence block
    
    Greptile P1: the Evidence block labeled .printing-press-patches.json as
    "Plan doc" and the next line said "see the plan doc at the path above"
    - the URL points at the patches JSON, not the plan doc, which lives
    locally at $PRESS_MANUSCRIPTS/<slug>/<run-id>/proofs/... and is never
    committed to the public library.
    
    Relabel the URL as "Patch record", point per-finding rationale at the
    Findings table + patch record, and note the local plan doc location as
    context for the original printer (not as a clickable artifact for
    external reviewers). The plan doc stays local by design - it's a
    PII-scrubbed audit record, not part of the PR contract.
    
    Resolves Greptile finding on PR #1490. Origin R27's requirement for a
    full GitHub URL to the plan doc is unsatisfiable as written and is
    silently revised here; flag for retro follow-up.
    
    * fix(skills): hard-stop existing-open-PR path in library-pr-plumbing
    
    Greptile P2: when existing_open was non-empty the snippet printed a
    warning and a # AskUserQuestion: ... shell comment, then unconditionally
    ran git checkout -b "$BRANCH_NAME" - which fails with a confusing
    already-exists error because the local branch from the open PR exists.
    
    Replace the inert comment with a hard exit 1, a concrete two-option
    resolution menu printed to the user, and a follow-up prose block that
    documents the AskUserQuestion-then-re-enter contract the skill driver
    must honor. Literal shell-flow execution now stops at the right place
    with an actionable message.
    
    Resolves Greptile finding on PR #1490.
    
    * fix(cli): mark --dir as required on verify-internal-skill
    
    Greptile P2: --dir was validated via a manual `if dir == ""` check in
    RunE, which works but doesn't surface the constraint in Cobra's --help
    output and runs the validation later than necessary.
    
    Replace with cmd.MarkFlagRequired("dir") after the StringVar
    declaration. Missing --dir now produces "required flag(s) \"dir\" not
    set" via Cobra's standard flow and the flag is annotated as required
    in --help output. The manual check is removed as redundant.
    
    Resolves Greptile finding on PR #1490.
    
    * fix(skills): make 90-days-ago date portable in printing-press-amend
    
    Greptile P1: `date -v-90d` is BSD/macOS-only. On Linux it emits
    "date: invalid option -- 'v'" to stderr and produces empty stdout,
    leaving the GitHub search qualifier as `merged:>` with no value.
    The recently-merged-PR dedup guard then silently returned no results,
    defeating Phase 2a's purpose on any non-macOS machine.
    
    Replace with a three-way fallback: GNU `date -d '90 days ago'` first,
    BSD `date -v-90d` second, python3 timezone-aware UTC date as final
    fallback. Hard exit 1 if all three fail rather than letting the dedup
    guard silently drop out.
    
    Resolves Greptile finding on PR #1490.
    
    * fix(skills): assign dogfood_status=N/A in direct-input mode
    
    Greptile P1 (re-review 18:50): the PR body template printed `- Dogfood:
    $dogfood_status` and the Phase 8 RESULT block included `dogfood_status:
    <PASS|FAIL|N/A>`, but Phase 4's output spec omitted the variable
    entirely and no phase instruction assigned it in direct-input mode.
    Every PR from a direct-input run would show an empty `- Dogfood:` line.
    
    Add dogfood_status to Phase 4's output spec with a per-mode contract:
      - MODE=dogfood: PASS|FAIL from the dogfood validation step
      - MODE=direct:  always N/A (no transcript to dogfood against)
      - MODE=both:    PASS|FAIL on the transcript half of the findings
    Default must be set by end of Phase 4 so Phase 7/8 never see empty.
    
    Also add defensive ${dogfood_status:-N/A} in the PR body template as
    belt-and-suspenders against any future phase forgetting to assign it.
    
    Resolves Greptile finding on PR #1490.
    
    * fix(skills): force-checkout main on managed-clone refresh
    
    Greptile P1 (x2, SKILL.md:460 + library-pr-plumbing.md:86): if a prior
    run aborted between Phase 4's file edits and Phase 7's commit, the
    managed clone is left on an amend branch with uncommitted changes in
    $CLI_DIR. The refresh block's `git checkout main` then fails because
    those edits would be overwritten, and the subsequent `git reset --hard
    upstream/main` never runs - leaving the clone stuck in a broken state.
    
    Add -f to discard local edits. The managed clone is documented as a
    scratch surface owned by the skill; any leftover edits are by definition
    abort residue that must be discarded before the next run reuses it.
    This is consistent with the existing reset --hard immediately after.
    
    Resolves Greptile finding on PR #1490.
    
    * fix(skills): use working-tree git diff for parity check (not format-patch)
    
    Greptile P1: my prior G1 fix used git format-patch --stdout
    upstream/main..HEAD, but the parity check runs in Phase 4 Step 6 -
    BEFORE Phase 7's commit. The edits live only in the working tree, so
    upstream/main..HEAD is an empty commit range and format-patch emits
    nothing. new_patch_markers is always 0:
    
      - When patches_entry > 0 (any real patch run): 0 < patches_entry is
        true and exit 1 fires unconditionally, blocking every valid run.
      - When patches_entry = 0: the check silently passes regardless.
    
    Switch back to working-tree git diff (the right tool for pre-commit
    state). Add --no-pager + --no-color + --no-ext-diff to defeat
    colorized output and any configured diff.external tool that would
    reformat the diff away from unified-diff shape.
    
    Smoke-tested the corrected snippet against a fresh repo with uncommitted
    PATCH-bearing edits: returns 1 for the 1 new marker, as expected.
    
    Resolves Greptile finding on PR #1490 (refines G1's prior fix).
    
    ---------
    
    Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
    Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
---
 README.md                                          |  12 +
 ...2026-05-15-printing-press-amend-requirements.md | 114 ++++
 ...6-05-15-feat-printing-press-amend-skill-plan.md | 417 ++++++++++++
 ...-printing-press-amend-direct-input-mode-plan.md | 377 +++++++++++
 internal/cli/root.go                               |   1 +
 internal/cli/verify_internal_skill.go              | 259 ++++++++
 internal/cli/verify_internal_skill_test.go         | 226 +++++++
 internal/pipeline/contracts_test.go                |   1 +
 skills/printing-press-amend/SKILL.md               | 721 +++++++++++++++++++++
 skills/printing-press-amend/references/.gitkeep    |   0
 .../references/direct-input-parsing.md             |  77 +++
 .../references/library-pr-plumbing.md              | 299 +++++++++
 .../references/pii-scrubbing.md                    | 107 +++
 .../references/transcript-parsing.md               | 108 +++
 14 files changed, 2719 insertions(+)

diff --git a/README.md b/README.md
index dfe47273..b34301bb 100644
--- a/README.md
+++ b/README.md
@@ -146,6 +146,18 @@ When you're happy with a CLI, publish it to the library:
 
 </details>
 
+<details>
+<summary><b>Amend a published CLI from a dogfood session</b></summary>
+
+After dogfooding a published CLI in a Claude Code session, turn the friction you hit (missing flags, hand-rolled API payloads, silent-null returns) into a PR for the public library. Mines the active session transcript, scopes the patch with you, plans + executes the fix autonomously, scrubs PII, and opens a PR — two checkpoints (scope, PR draft):
+
+```bash
+/printing-press-amend                # auto-detect target CLI from session
+/printing-press-amend superhuman     # explicit target
+```
+
+</details>
+
 ## Why these CLIs win
 
 Most generators wrap endpoints and stop. Printing Press generates CLIs that understand the domain.
diff --git a/docs/brainstorms/2026-05-15-printing-press-amend-requirements.md b/docs/brainstorms/2026-05-15-printing-press-amend-requirements.md
new file mode 100644
index 00000000..c4266cb6
--- /dev/null
+++ b/docs/brainstorms/2026-05-15-printing-press-amend-requirements.md
@@ -0,0 +1,114 @@
+---
+date: 2026-05-15
+topic: printing-press-amend-skill
+---
+
+# printing-press-amend: Dogfood-to-PR Loop for Printed CLIs
+
+## Problem Frame
+
+When a user dogfoods a printed CLI in a Claude Code session, they hit friction — missing flags, hand-rolled API payloads, silent-null returns, expired-token confusion, missing folder coverage. Today the path from "found it" to "PR open" is manual and stitched together: synthesize the friction into tiers, hand it to `/ce-plan`, hand the plan to `/ce-work`, drive the public-library fork-and-PR flow by hand, scrub PII before anything leaves local. Each step is real work, the conventions vary across users, and the synthesis quality depends on the user remembering everything they hit.
+
+That manual path should be a single skill. The user fires `/printing-press-amend` after a dogfood session, the skill mines the active Claude Code transcript for friction, scopes the patch with the user, plans + executes the fix autonomously, and opens a PII-scrubbed PR against `mvanhorn/printing-press-library` using the same plumbing `/printing-press-publish` already established.
+
+The skill lives in this repo (the machine) and acts on a printed CLI in the public library. It is sibling to `/printing-press-publish` (which adds a new CLI), `/printing-press-polish` (which improves a CLI pre-publish), and `/printing-press-retro` (which reflects on the machine itself). None of them cover post-publish CLI amendments driven by real-session friction.
+
+## Requirements
+
+**Skill Identity**
+- R1. Standalone Claude Code skill at `skills/printing-press-amend/SKILL.md`
+- R2. Invocable as `/printing-press-amend` with no required arguments; optional `<cli-name-or-path>` to scope explicitly
+- R3. Self-contained — no dependency on the compound-engineering plugin or any other agent toolkit. Bundles its own planning + execution flow shaped like the rest of the family.
+- R4. Name resolution: when a CLI is named explicitly, accept short name (`superhuman`), full name (`superhuman-pp-cli`), or path (`~/printing-press/library/superhuman-pp-cli`) — same UX as `/printing-press-polish` R3
+- R5. Auto-detection: when no CLI is named, scan the active Claude Code session transcript for `<cli-name>-pp-cli` invocations and use the most-touched CLI as the default; confirm with the user before proceeding
+
+**Friction Capture Phase**
+- R6. Read the active Claude Code session transcript file (`~/.claude/projects/<dir>/<session>.jsonl` or equivalent) for the current session
+- R7. Extract friction signals: non-zero exit codes, error messages, hand-rolled API payloads (e.g., direct `userdata.writeMessage` POSTs), retry-after-failure patterns, "X doesn't exist" / "X returns 400" type comments, missing-flag references, silent-null returns
+- R8. Categorize each finding as **bug** (CLI behavior is wrong) or **feature** (CLI behavior is missing) with a one-line rationale
+- R9. Cross-reference open + recently-merged PRs in `mvanhorn/printing-press-library` to suppress findings already shipped or in flight; report any matches and skip them
+- R10. Stale-binary check: confirm the running CLI binary matches the latest published module version; if stale, instruct the user to update and abort the patch run (mirrors the `go install @latest` rule)
+
+**Scope Confirmation Checkpoint (User-in-Loop #1)**
+- R11. Present the captured findings as a tiered list: bugs (Tier 1), missing features that solve immediate pain (Tier 2), polish/architecture improvements (Tier 3). Use `AskUserQuestion`.
+- R12. The user picks scope (one of: bugs only, bugs + Tier 2, all tiers, custom selection). Proceed only after confirmation.
+- R13. Findings the user excludes go to a deferred list saved alongside the plan, not into the active patch.
+
+**Plan + Execute Phase (Autonomous)**
+- R14. Generate a plan document with implementation units for the confirmed scope, naming files in the target printed CLI, test scenarios per unit, and dependencies. Plan lives at `~/printing-press/manuscripts/<api-slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>.md` per family convention, plus mirrored to `/tmp/printing-press/patch/` for local copy
+- R15. Execute the plan against the local printed CLI checkout (`~/printing-press/library/<api-slug>/`), respecting the family's machine-vs-printed-CLI rules: changes that should generalize go to a follow-up retro, changes that are CLI-specific stay in the patch
+- R16. Build and test the modified CLI: `go build`, `go test ./...`, `go vet ./...`, plus `printing-press dogfood` against the modified binary if applicable
+- R17. If any build/test fails, fix and retry up to 3 times; on persistent failure, surface the failure to the user with logs and pause (do not auto-open the PR)
+
+**PII Scrub Phase**
+- R18. Scrub PII from all artifacts that will leave the local machine: the plan doc body, the PR title and body, any test fixtures or example outputs added to the CLI
+- R19. PII categories include: real email addresses, person names, company names matching a stop-list (configurable via `~/.printing-press/amend-config.yaml`), real meeting/email content quoted from the session
+- R20. Replace scrubbed values with shape-preserving tokens (`<email-1>`, `<person-1>`, `<company-1>`) so reviewers can see the original intent. Do not silently delete content.
+- R21. The skill MUST NOT touch the printed CLI's source code with PII (Go source is presumed PII-free; this is a defense-in-depth check)
+
+**PR Draft Review Checkpoint (User-in-Loop #2)**
+- R22. Show the user the PR title, body, file diff summary, and the structured `---PATCH-RESULT---` block before any `gh` command fires
+- R23. The user picks one of: open PR as drafted, edit then open, hold (save plan + diff for later), or abort. Use `AskUserQuestion`.
+- R24. Highlight any unscrubbed-PII risk findings explicitly in the review (e.g., "the plan doc references 'Esper Labs' — confirm this is intentional or scrub")
+
+**PR Open Phase**
+- R25. Reuse `/printing-press-publish`'s fork-clone-branch-push-PR plumbing where possible (call into the same shared scripts/helpers rather than reimplementing)
+- R26. PR title format: `fix(<api-slug>): <one-line summary>` for bugs, `feat(<api-slug>): <one-line summary>` for features, matching the public library's commit/PR conventions
+- R27. PR body sections: **Summary** (1-3 sentences), **Findings** (table of bug/feature with brief rationale), **Changes** (file-level diff summary), **Verification** (build/test/dogfood results), **Evidence** (full-URL links to before/after artifacts captured during the run)
+- R28. Apply labels `comp:<api-slug>` and `priority:P<n>` per family convention; tier 1 bugs get `priority:P1`, tier 2 features get `priority:P2`, tier 3 improvements get `priority:P3`
+- R29. Issue ownership: search for an existing open issue matching the findings; if one exists, link it and assign self in the PR; if none exists, open one with the captured findings before the PR (per the public library's contributor convention)
+- R30. Emit structured `---PATCH-RESULT---` block on completion with PR URL, branch name, scope tier, files changed, build/test status, and PII scrub summary
+
+**Output Artifact**
+- R31. Local plan + run log saved to `~/printing-press/manuscripts/<api-slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>.md`
+- R32. Mirrored copy to `/tmp/printing-press/patch/` for quick reference
+- R33. Deferred-findings list (R13) saved alongside the plan as `<timestamp>-amend-<cli-name>-deferred.md` so future patches can pick them up
+
+## Success Criteria
+
+- A user finishing a session like the Superhuman dogfood from 2026-05-15 (12+ findings: missing folder coverage, hand-rolled drafts, silent AI-proxy nulls, expired-token confusion) fires `/printing-press-amend`, picks scope, reviews the PR draft, and lands an open PR against `mvanhorn/printing-press-library` in under 10 minutes — without manually invoking `/ce-plan`, `/ce-work`, `gh`, or any scrub tool
+- The skill detects and skips a finding that would re-propose work already shipped in a recent PR (the "PR #571 already shipped" failure mode from 2026-05-15)
+- Zero PII appears in the PR body, plan doc, or any committed test fixture across a representative sample of patch runs
+- A second user without compound-engineering installed can run `/printing-press-amend` end-to-end without errors
+
+## Scope Boundaries
+
+- Limited to printed CLIs in `mvanhorn/printing-press-library` — does not patch the cli-printing-press machine repo, does not patch arbitrary third-party CLIs, does not target other public-library forks
+- Triggers at the end of a Claude Code session (or any time the active session transcript is readable) — does not run live mid-session, does not observe in the background
+- Does not generate net-new CLIs (that's `/printing-press` + `/printing-press-publish`)
+- Does not improve a pre-publish CLI's quality (that's `/printing-press-polish`)
+- Does not reflect on the machine itself or file machine-improvement issues (that's `/printing-press-retro`)
+- Does not auto-merge the PR; always opens for human review
+
+## Key Decisions
+
+- **Wraps the full loop, not just PR packaging**: One command from session-end to PR-open. Two user checkpoints (scope after capture, PR draft before open) keep the user in the loop without making them drive each phase.
+- **Self-contained, no compound-engineering dependency**: Bundles its own planning + execution rather than calling `/ce-plan` and `/ce-work`. PP-library users without that plugin still get the full workflow.
+- **Auto-detect target CLI from the session transcript by default**: User can override with an explicit argument, but the killer experience is `/printing-press-amend` with no args at end of session.
+- **Reuse `/printing-press-publish`'s fork/PR plumbing**: Don't reimplement the gh/git dance; share helpers so conventions stay aligned across the family.
+- **PII scrub is default-on with a stop-list, not opt-in**: Real customer/person/company content from the session must never leak into a public PR. Replace with shape-preserving tokens so reviewers can see intent.
+- **Stale-binary + duplicate-PR check before scope confirmation**: Borrowed from `/printing-press-polish`'s divergence pattern. Catches the "you proposed what PR #571 already shipped" failure before wasting the user's review time.
+- **One PR per `/printing-press-amend` run**: Even when bugs and features both surface in a session. Keeps PR review cheap; the user can run the skill twice if they want atomic landings.
+
+## Outstanding Questions
+
+### Deferred to Planning
+- [Affects R6][Technical] What's the canonical path to read the active Claude Code session transcript? Is there a stable API or do we walk `~/.claude/projects/`?
+- [Affects R7][Needs research] What signal extraction technique is most robust for "user manually worked around a missing flag" — pattern-match on bash command sequences, look at agent text annotations, both?
+- [Affects R9][Technical] PR cross-reference: search `mvanhorn/printing-press-library` open+recent PRs by what — keyword from finding, file path, scope tag? How fuzzy?
+- [Affects R10][Technical] How does the skill know what "the latest published module version" of a printed CLI is? Is there a version registry, or does it query GitHub releases?
+- [Affects R18-R21][Technical] PII detection mechanism: regex patterns, embed-based similarity, named-entity recognition, or a stop-list-only baseline for v1?
+- [Affects R25][Technical] Which helpers in `/printing-press-publish` are extractable into shared scripts vs need to stay in that skill? May require refactor in publish.
+- [Affects R29][Process] Does `mvanhorn/printing-press-library`'s contributor convention require an issue before the PR? Confirm against that repo's AGENTS.md.
+
+## v0.2 Amendment — direct-input mode (2026-05-16)
+
+The original brainstorm above frames the skill as dogfood-only — Phase 1 always mines the active session transcript for friction. The 2026-05-16 Digg-CLI amend surfaced a gap: when the user already knows what they want changed (rename a command, add named feeds, sniff for new endpoints), the dogfood-only framing fails. The skill auto-selects an unrelated recent transcript, produces a noisy or empty finding list, and halts at the transcript-confirmation modal.
+
+The skill is extended to a second input mode (`direct-input`) plus a first-class sniff finding type. Both modes converge at Phase 2 onward — the typed finding list is mode-agnostic. The original dogfood-mode behavior is preserved bit-for-bit.
+
+New requirements R34-R38 and the design are captured in `docs/plans/2026-05-16-002-feat-printing-press-amend-direct-input-mode-plan.md`. The expansion lands in the same PR (#1490) — the v0.1 skill had not merged yet.
+
+## Next Steps
+
+`/ce-plan` (which is what just got invoked) — implementation planning for this skill.
diff --git a/docs/plans/2026-05-15-feat-printing-press-amend-skill-plan.md b/docs/plans/2026-05-15-feat-printing-press-amend-skill-plan.md
new file mode 100644
index 00000000..6c82c874
--- /dev/null
+++ b/docs/plans/2026-05-15-feat-printing-press-amend-skill-plan.md
@@ -0,0 +1,417 @@
+---
+title: "feat(skills): printing-press-amend — dogfood-to-PR skill for printed CLIs"
+type: feat
+status: active
+created: 2026-05-15
+origin: docs/brainstorms/2026-05-15-printing-press-amend-requirements.md
+---
+
+# feat(skills): printing-press-amend — dogfood-to-PR skill for printed CLIs
+
+## Summary
+
+Add a new `/printing-press-amend` skill to this repo (the machine) that wraps the dogfood-to-PR loop for printed CLIs in the public library. The skill mines the active Claude Code session transcript for friction, scopes the patch with the user, autonomously plans + executes the fix, scrubs PII, and opens a PR against `mvanhorn/printing-press-library` — reusing the family's setup contract, `printing-press publish validate` plumbing, and the public library's mandatory patch-tracking contract (`// PATCH(...)` source comments + `.printing-press-patches.json`). Two user-in-loop checkpoints (scope after capture, PR draft before open); everything else runs unattended.
+
+---
+
+## Problem Frame
+
+When a user dogfoods a printed CLI in a Claude Code session, they hit friction — missing flags, hand-rolled API payloads, silent-null returns, expired-token confusion, missing folder coverage. Today the path from "found it" to "PR open" is manual: synthesize friction into tiers, hand to `/ce-plan`, hand the plan to `/ce-work`, drive the public-library fork-and-PR flow by hand, scrub PII before anything ships. Conventions vary across users, synthesis quality depends on memory, and PR-time gotchas (was this already shipped? does the fix need a `// PATCH(...)` record?) get missed.
+
+The brainstorm document at `docs/brainstorms/2026-05-15-printing-press-amend-requirements.md` defines the WHAT — a self-contained skill that wraps the loop with two checkpoints. This plan defines the HOW: skill placement under `skills/printing-press-amend/`, phase ordering, references file shape, integration with existing publish plumbing, and the public library's patch-record contract.
+
+---
+
+## Requirements (carried from origin)
+
+Origin requirements R1–R33 are carried forward in full (see origin: `docs/brainstorms/2026-05-15-printing-press-amend-requirements.md`). They map to implementation units below as follows:
+
+- **R1–R5 (skill identity, name resolution, auto-detect)** → U1, U2
+- **R6–R10 (friction capture, categorization, cross-reference, stale-binary)** → U2, U3
+- **R11–R13 (scope confirmation checkpoint)** → U4
+- **R14–R17 (plan + execute + validate)** → U5
+- **R18–R21 (PII scrub)** → U6
+- **R22–R24 (PR draft checkpoint)** → U7
+- **R25–R30 (PR open, labels, issue ownership, RESULT block)** → U7, U8
+- **R31–R33 (output artifacts)** → U5, U8
+
+The success criteria from origin (Superhuman-shaped session ships PR in under 10 minutes, PR #571-shaped duplicates get caught, zero PII leaks, second user without compound-engineering can run end-to-end) drive the test scenarios per unit.
+
+---
+
+## Pre-Implementation Decisions (resolved 2026-05-15 from doc-review findings)
+
+- **Skill name: `/printing-press-amend`** (not `/printing-press-patch`) — avoids collision with the existing `printing-press patch` binary subcommand for AST-injection. The artifact this skill produces is still semantically a "patch" (in the git/PR sense) but the slash-skill's identifier is `amend` for disambiguation.
+- **U1 verifier: build a new `printing-press verify-internal-skill` Go subcommand** (new U9) — `printing-press verify-skill` requires `internal/cli/` source and is wrong-tool for internal skills. The new subcommand lints SKILL.md frontmatter, canonical sections, and allowed-tools shape; reusable across polish/retro/publish/amend.
+- **Operate directly on the managed clone of `mvanhorn/printing-press-library`** — U5 edits files inside `$PRESS_HOME/.publish-repo-$PRESS_SCOPE/library/<category>/<slug>/` (the same managed clone `/printing-press-publish` uses). Skips `$PRESS_LIBRARY` entirely. No translation step, no divergence handling, no carry-forward of existing `.printing-press-patches.json`. U7's PR open is just `git push` against the managed clone.
+
+---
+
+## Key Technical Decisions
+
+- **Copy publish plumbing into `references/library-pr-plumbing.md`, defer the publish-side extraction.** `/printing-press-publish` currently has zero extracted helpers — Step 5 (managed clone), Step 7 (collision detection), Step 8 (branch/commit/push/PR) all live inline in its SKILL.md. Refactoring publish to share helpers doubles this PR's scope. Copy the patterns into patch's references file now, document the duplication as a planned follow-up retro item. The two surfaces will drift if not maintained, but the alternative is blocking patch on a refactor.
+- **Friction extraction lives in skill prose + agent judgment, not a new binary subcommand.** A `printing-press patch-friction-scan` Go subcommand would be testable but requires generator changes and golden fixtures; the friction-extraction logic is genuinely judgment-shaped (categorizing "the user typed `superhuman-pp-cli drafts new` and it returned 400" as "missing feature: drafts new helper" requires understanding intent, not pattern-matching). Prose + AskUserQuestion confirmation handles edge cases better. Revisit as a binary subcommand in v0.2 if v0.1 friction quality is shaky.
+- **PII scrub reuses `skills/printing-press-retro/references/secret-scrubbing.md` patterns instead of duplicating.** Patch's `references/pii-scrubbing.md` will reference retro's module + add the patch-specific stop-list (companies, person names) and shape-preserving tokens. Keeps the scrubbing logic in one place; both skills evolve together.
+- **Bake the public library's mandatory contract directly into U7.** Per `~/printing-press-library/AGENTS.md`: every patched site needs a `// PATCH(<short reason>)` source comment AND an entry in the CLI's `.printing-press-patches.json`. These are NOT optional and the library's `verify-library-conventions` workflow will reject PRs without them. U7 enforces both before opening the PR.
+- **Use `printing-press publish validate --dir <cli-dir> --json` as the consolidated build/test command.** It already runs manifest, phase5, govulncheck (scoped), go vet, build, --help, --version. U5 calls this instead of stringing together separate `go build`/`go test`/`go vet` invocations.
+- **One PR per `/printing-press-amend` run.** Even when bugs and features both surface in a session. Confirmed in brainstorm; bake into U4's scope-tier menu (user picks one tier per run, can re-run the skill for additional scope).
+- **`context: fork` skill, `user-invocable: true`.** Self-contained per R3, slash-invocable per R2. Same posture as `/printing-press-polish`.
+
+---
+
+## Scope Boundaries
+
+- Limited to printed CLIs in `mvanhorn/printing-press-library` — no machine-repo patches (those go to `/printing-press-retro`), no third-party CLIs, no other public-library forks
+- End-of-session trigger via reading the active transcript file — no live mid-session capture, no background observation daemon
+- Does not generate net-new CLIs (`/printing-press` + `/printing-press-publish`)
+- Does not improve a pre-publish CLI's quality (`/printing-press-polish`)
+- Does not reflect on the machine itself or file machine-improvement issues (`/printing-press-retro`)
+- Does not auto-merge — always opens a PR for human review
+
+### Deferred to Follow-Up Work
+
+- Extract `/printing-press-publish` Step 5/7/8 into shared scripts/helpers and migrate both publish + patch to use them (resolves the copy-paste duplication from Key Technical Decisions)
+- `printing-press patch-friction-scan` binary subcommand for deterministic, golden-tested friction extraction
+- Multi-PR mode (split bug-PR + feature-PR for atomic landability)
+- Cross-CLI patches (one session that touched multiple CLIs)
+- Live mid-session friction capture (skill listens to session events as they happen)
+- Greptile pre-check integration (auto-fix Greptile P0/P1 findings before PR opens)
+
+---
+
+## System-Wide Impact
+
+- **`internal/pipeline/contracts_test.go`** — `TestSkillSetupBlocksMatchWorkspaceContract` test slice gains `printing-press-amend/SKILL.md`. Without this addition, setup contract drift would land silently.
+- **`mvanhorn/printing-press-library`** — Every patch PR adds entries to a CLI's `.printing-press-patches.json` and adds `// PATCH(...)` comments to source. The `verify-library-conventions` workflow on that repo gates patches; the skill must produce conformant PRs.
+- **`/printing-press-publish`** — No source change in this PR, but the skill becomes a load-bearing reference for `references/library-pr-plumbing.md`. Future publish-side refactors must coordinate with patch.
+- **`/printing-press-retro`** — `references/secret-scrubbing.md` becomes shared infrastructure. Changes there now affect patch behavior.
+- **README + skill index** — Add `printing-press-amend` to `docs/SKILLS.md` skill catalog and the README skills section.
+
+---
+
+## Implementation Units
+
+### U1. Skill scaffold + setup contract + frontmatter
+
+**Goal:** Create the skill directory, write a working SKILL.md skeleton with the canonical frontmatter and setup contract, register it for contract testing.
+
+**Requirements:** R1, R2, R3, R10 (version check is part of setup)
+
+**Dependencies:** None
+
+**Files:**
+- `skills/printing-press-amend/SKILL.md` (new)
+- `skills/printing-press-amend/references/.gitkeep` (new — placeholder for U2/U6/U7 references)
+- `internal/pipeline/contracts_test.go` (modify — add `printing-press-amend/SKILL.md` to test slice)
+- `internal/pipeline/contracts_test.go` (test file already exists; verify the new entry passes)
+
+**Approach:**
+- Frontmatter: `name: printing-press-amend`, `description: |` with multi-line trigger phrases ("patch the CLI", "submit a patch", "fix what I just dogfooded", etc.), `allowed-tools: [Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion]`, `version: 0.1.0`, `min-binary-version: "4.0.0"`, `context: fork`, `user-invocable: true`
+- Copy the `<!-- PRESS_SETUP_CONTRACT_START -->` ... `<!-- PRESS_SETUP_CONTRACT_END -->` block verbatim from `skills/printing-press-publish/SKILL.md` lines 40–92 (the canonical reference)
+- After the contract, add the version-check sequence (parse `<PRINTING_PRESS_BIN> version --json`, compare to `min-binary-version`, abort with the canonical "Run go install … @latest" message if older) — pattern from publish lines 94–96
+- SKILL.md body skeleton: introduction (machine-vs-printed-CLI framing per AGENTS.md, "the Printing Press" canonical name), then placeholder phase headers (Phase 1 Friction Capture, Phase 2 Cross-Reference + Stale-Binary, Phase 3 Scope Confirmation, Phase 4 Plan + Execute, Phase 5 PII Scrub, Phase 6 PR Draft Review, Phase 7 PR Open, Phase 8 Output) — each filled in by subsequent units
+
+**Patterns to follow:**
+- `skills/printing-press-publish/SKILL.md` lines 1–96 (frontmatter + setup contract + version check)
+- `skills/printing-press-polish/SKILL.md` (overall body shape, phase organization)
+
+**Test scenarios:**
+- `TestSkillSetupBlocksMatchWorkspaceContract` passes after adding `printing-press-amend/SKILL.md` to the test slice — confirms contract parity with the other 4 skills already in the test
+- Negative: introduce a deliberate setup-contract drift in the new SKILL.md (drop `pwd -P`), confirm the test fails with a clear diff
+- `printing-press verify-skill --dir skills/printing-press-amend --json` returns no errors against the skeleton (no flag-name drift, no canonical-section drift)
+- Frontmatter parses: `python3 -c 'import yaml; yaml.safe_load(open("skills/printing-press-amend/SKILL.md").read().split("---")[1])'` succeeds and has all required fields
+
+**Verification:** `go test ./internal/pipeline/...` passes with new entry; `printing-press verify-skill` clean against the skeleton; `printing-press` binary recognizes the new skill in its catalog if the catalog auto-discovers (otherwise document any catalog-update step).
+
+---
+
+### U2. Friction capture phase (transcript parsing, signal extraction, categorization)
+
+**Goal:** Read the active Claude Code session transcript, extract friction signals tied to a specific printed CLI invocation, categorize each as bug or feature with a one-line rationale.
+
+**Requirements:** R5, R6, R7, R8
+
+**Dependencies:** U1
+
+**Files:**
+- `skills/printing-press-amend/SKILL.md` (modify — Phase 1 body)
+- `skills/printing-press-amend/references/transcript-parsing.md` (new)
+
+**Approach:**
+- Inline in SKILL.md Phase 1: brief instructions to read the active transcript, sample-extract logic at the prose-judgment level (this is judgment work per Key Technical Decisions, not a binary subcommand)
+- `references/transcript-parsing.md` contains: canonical paths to look at (`~/.claude/projects/<dir>/<session>.jsonl` and platform variants documented in deferred questions), signal extraction taxonomy (non-zero exit codes, error messages, hand-rolled API payloads, retry-after-failure, "X doesn't exist" agent commentary, missing-flag references, silent-null returns), bug-vs-feature classification rubric with examples drawn from the 2026-05-15 Superhuman session
+- Auto-detect target CLI: scan extracted signals for `<cli-name>-pp-cli` invocations, count by frequency, propose the most-touched CLI as default; if `<cli-name-or-path>` arg is provided, use that and skip auto-detect
+- Confirm CLI selection with `AskUserQuestion` before continuing
+
+**Patterns to follow:**
+- `skills/printing-press-retro/references/` directory structure (retro is the closest sibling for "extract and synthesize")
+- `skills/printing-press/references/setup-checks.md` (judgment-shape prose)
+
+**Test scenarios:**
+- Happy path: a transcript file with 12 distinct frictions against `superhuman-pp-cli` parses into a finding list with at least 8 categorized items (mirrors the 2026-05-15 dogfood run)
+- Auto-detect: a transcript that touched both `superhuman-pp-cli` and `granola-pp-cli` proposes the more-frequent one and asks the user to confirm
+- Edge case: empty or unreadable transcript — skill emits a clear "no active session transcript found, pass `<cli-name>` explicitly" message and exits cleanly
+- Edge case: transcript with zero CLI invocations — skill reports "no `<cli>-pp-cli` invocations found in the active session" and exits
+- Bug vs feature: a session where `superhuman-pp-cli drafts new` returned 400 categorizes as bug; a session where the user hand-rolled a `userdata.writeMessage` payload because `drafts new` doesn't exist categorizes as feature with rationale "no `drafts new` command in CLI"
+- Path resolution: `<cli-name>` short form (`superhuman`), `<full-name>` (`superhuman-pp-cli`), and absolute path (`~/printing-press/library/superhuman`) all resolve to the same target directory (per R4)
+
+**Verification:** Running the skill against a captured-fixture transcript file (saved under `testdata/skill-fixtures/patch-superhuman-2026-05-15.jsonl` if needed) produces the expected finding list; the user-confirmation prompt fires before any later phase.
+
+---
+
+### U3. Pre-checkpoint guards (PR cross-reference, stale-binary check)
+
+**Goal:** Before showing the user the scope menu, suppress findings that would re-propose work already shipped in a recent PR, and abort if the running CLI binary is stale relative to the latest published version.
+
+**Requirements:** R9, R10 (R10 covered partially in U1; this unit handles the printed-CLI binary check, distinct from `printing-press` binary itself)
+
+**Dependencies:** U2
+
+**Files:**
+- `skills/printing-press-amend/SKILL.md` (modify — Phase 2 body)
+
+**Approach:**
+- Inline in SKILL.md Phase 2: for each finding from U2, search `mvanhorn/printing-press-library` open + recently-merged PRs (last 90 days) using `gh pr list --repo mvanhorn/printing-press-library --search "<keyword>" --state all --limit 20 --json number,title,state,mergedAt,headRefName`. Match by file path and keyword extracted from the finding rationale.
+- For each match, present to the user inline ("Finding X may be addressed by PR #Y — `<title>`. Skip?"); user confirms skip or keeps.
+- Stale-binary: query the printed CLI's published version (read from `~/printing-press-library/library/<category>/<api-slug>/.printing-press.json` if locally cloned, else `gh api repos/mvanhorn/printing-press-library/contents/library/<category>/<api-slug>/.printing-press.json`). Compare to local `<cli>-pp-cli version --json` output. If local is older, instruct user to `go install @latest` and abort the patch run cleanly.
+
+**Patterns to follow:**
+- `skills/printing-press-publish/SKILL.md` Step 7 collision detection (lines 488–650) — same `gh pr list` shape
+- `skills/printing-press-polish/SKILL.md` divergence-check pattern
+
+**Test scenarios:**
+- Happy path: zero open PRs match the findings, all findings pass through to U4
+- Match path: a finding "missing `--type sent` flag" matches an existing open PR titled "feat(superhuman): add sent folder support" — finding gets surfaced as a possible-duplicate, user can skip or proceed
+- Recently-merged: a finding matches a PR merged 30 days ago — flagged as "already shipped, skip recommended" with a higher confidence than open-PR matches
+- Stale binary: local `superhuman-pp-cli` reports v0.3.1, library `.printing-press.json` shows v0.4.0 — skill prints upgrade instructions and aborts before reaching U4
+- Network failure: `gh` API call times out — skill prints a warning, asks the user whether to proceed without cross-reference (no silent failure)
+- Edge case: printed CLI not yet in the public library (local-only) — stale-binary check skipped with a note, cross-reference returns no matches by definition
+
+**Verification:** Running against the 2026-05-15 fixture would catch a `granola-pp-cli` auto-refresh-style proposal that PR #571 already shipped (the canonical failure-mode-from-the-brainstorm test).
+
+---
+
+### U4. Scope confirmation checkpoint + deferred-findings list
+
+**Goal:** Present the surviving findings (post-U3 filtering) as a tiered list, let the user pick scope via `AskUserQuestion`, persist excluded findings as a deferred list for future patches.
+
+**Requirements:** R11, R12, R13
+
+**Dependencies:** U3
+
+**Files:**
+- `skills/printing-press-amend/SKILL.md` (modify — Phase 3 body)
+
+**Approach:**
+- Tier the findings: Tier 1 = bugs (CLI returns wrong result, errors, broken behavior), Tier 2 = missing features that solve immediate session pain, Tier 3 = polish/architecture/UX improvements
+- Present a 4-option `AskUserQuestion`: (a) bugs only (Tier 1), (b) bugs + immediate features (Tier 1 + Tier 2), (c) all tiers (Tier 1 + 2 + 3), (d) custom selection — drop into a multi-select for individual findings
+- Persist the user-excluded findings to `$PRESS_MANUSCRIPTS/<api-slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>-deferred.md` so a future `/printing-press-amend` run can re-surface them
+- Display chosen scope back to user as a confirmed list before proceeding to U5
+
+**Patterns to follow:**
+- `skills/printing-press-polish/SKILL.md` post-fix publish-offer prompt (similar 4-option AskUserQuestion shape)
+- `skills/printing-press-retro/SKILL.md` triage gate (Phase 2.5)
+
+**Test scenarios:**
+- Happy path: 12 findings split 6/4/2 across tiers; user picks "bugs + immediate features", 10 findings move forward, 2 Tier-3 items go to deferred list with full rationale preserved
+- Custom selection: user opens the multi-select and unchecks 3 specific findings; only checked findings move forward, unchecked items go to deferred
+- Empty after filtering: U3 suppressed every finding (all duplicates); skill reports "no novel patches found this session" and exits cleanly without an empty PR
+- Deferred list re-surfacing: a second `/printing-press-amend` run on the same CLI reads the prior deferred list (if present) and offers to include any items still relevant
+- Edge case: user picks "abort" via Other — skill exits cleanly, no deferred list written, no PR opened
+- AskUserQuestion options must be self-contained per AGENTS.md (no description-only meaning)
+
+**Verification:** Selected scope reaches U5 as a structured list; deferred list file exists at the expected path with each excluded finding's category, rationale, and timestamp.
+
+---
+
+### U5. Plan emission + autonomous execute + validate + retry
+
+**Goal:** Generate a per-run plan document for the confirmed scope, execute the fixes against the local printed CLI checkout, run the consolidated validator, retry on failure up to 3 times, surface persistent failures to the user.
+
+**Requirements:** R14, R15, R16, R17, R31, R32
+
+**Dependencies:** U4
+
+**Files:**
+- `skills/printing-press-amend/SKILL.md` (modify — Phase 4 body)
+
+**Approach:**
+- Plan emission: write a markdown plan to `$PRESS_MANUSCRIPTS/<api-slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>.md` with the confirmed findings as work items, target file paths in `$PRESS_LIBRARY/<api-slug>/`, expected behavior changes, and test scenarios per item. Mirror to `/tmp/printing-press/patch/` for quick reference. Plan format follows the family's per-run-plan shape (not the full ce-plan template — this is a run-log, not a durable design plan)
+- Execute: walk the plan items in dependency order; for each, edit the target files, update `.printing-press-patches.json` with the patch entry (per `~/printing-press-library/AGENTS.md`), and add `// PATCH(<short reason>)` source comments at the changed sites
+- Validate: after the full set of edits, run `<PRINTING_PRESS_BIN> publish validate --dir "$PRESS_LIBRARY/<api-slug>" --json`. This single call covers manifest, phase5, govulncheck, go vet, build, --help, --version per the publish skill convention.
+- Retry: if validate returns errors, parse the error categories, attempt targeted fixes (up to 3 iterations total). If still failing after iteration 3, surface the final error log to the user, save the in-progress plan + diff to a holding location, and pause without proceeding to U6/U7
+- Per AGENTS.md machine-vs-printed-CLI rule: changes that should generalize across all CLIs go to a deferred retro item, changes that are CLI-specific stay in the patch. Surface this judgment for borderline cases.
+
+**Patterns to follow:**
+- `skills/printing-press-polish/SKILL.md` Phase 2 fix loop (lines 238–247 for the validate invocation, lines 280+ for the retry pattern)
+- `~/printing-press-library/AGENTS.md` "How to record a hand-edit" section for the `// PATCH(...)` + `.printing-press-patches.json` contract
+
+**Test scenarios:**
+- Happy path: 5 confirmed findings, all 5 fixes pass `printing-press publish validate --json` on first attempt; plan + diff saved; ready for U6
+- Retry success: first validate returns 2 verify failures; second iteration fixes them; third iteration not needed
+- Retry exhaustion: validate keeps failing after 3 iterations; skill saves plan + diff to `$PRESS_MANUSCRIPTS/<api-slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>-INCOMPLETE.md`, displays final error log, asks user to inspect (no automatic PR open)
+- Patch records: `.printing-press-patches.json` gets new entries with `date`, `summary`, `files`, `findings_addressed` keys; `// PATCH(...)` comments appear at exactly the changed sites (verify by `grep "// PATCH" $PRESS_LIBRARY/<api-slug>/`)
+- Machine-vs-CLI judgment: a finding that requires a generator template change is flagged as "machine-level — defer to retro" and excluded from the patch with a note in the deferred list
+- Build failure: `go build` fails with a syntax error from the agent's edit; the validate-iteration retry catches and recovers
+- Test failure: `go test` fails because the agent didn't update an existing test for the changed behavior; retry iteration adds the missing test or asks user
+
+**Verification:** `printing-press publish validate --dir "$PRESS_LIBRARY/<api-slug>" --json` returns clean status; `.printing-press-patches.json` is well-formed; `grep -c "// PATCH" $PRESS_LIBRARY/<api-slug>` returns ≥ 1; plan doc exists at the expected path with all confirmed findings represented.
+
+---
+
+### U6. PII scrub phase (reuse retro patterns + shape-preserving tokens)
+
+**Goal:** Scrub PII from all artifacts that will leave the local machine (plan doc body, PR title and body, any test fixtures added to the CLI). Replace with shape-preserving tokens; never silently delete.
+
+**Requirements:** R18, R19, R20, R21
+
+**Dependencies:** U5
+
+**Files:**
+- `skills/printing-press-amend/SKILL.md` (modify — Phase 5 body)
+- `skills/printing-press-amend/references/pii-scrubbing.md` (new)
+
+**Approach:**
+- `references/pii-scrubbing.md` references `skills/printing-press-retro/references/secret-scrubbing.md` for the regex baseline (emails, API keys, tokens), then layers patch-specific patterns: company names from a stop-list at `~/.printing-press/amend-config.yaml` (user-maintained), person names matched against the same config, real meeting/email content quoted from the session transcript
+- Scrub targets in priority order: (1) the per-run plan doc, (2) the PR title + body draft, (3) any test fixtures or example outputs newly added to the CLI source (defense-in-depth — Go source is presumed PII-free, but check anyway)
+- Replacement tokens are shape-preserving: `<email-1>`, `<email-2>`, `<person-1>`, `<company-1>` — same token across appearances of the same source value within one run, distinct tokens for distinct sources. Never silently delete; always replace.
+- After scrubbing, emit a summary to the user: "X PII tokens replaced across Y artifacts" — and store the scrub report in the run's plan doc footer
+- Pre-PII-scrub backup: before scrubbing, copy each target to `<path>.pre-pii-scrub` (mirrors polish's pre-fix backup convention) so the user can audit what was changed
+- Defense-in-depth check on Go source: if any PII pattern matches a `.go` file in `$PRESS_LIBRARY/<api-slug>/`, treat as a bug, surface to user, do not auto-modify Go source — pause for manual review
+
+**Patterns to follow:**
+- `skills/printing-press-retro/references/secret-scrubbing.md` (regex baseline + token-replacement pattern)
+- `skills/printing-press/references/secret-protection.md` (defense-in-depth posture)
+- `skills/printing-press-polish/SKILL.md` `.pre-pii-scrub/` style pre-mutation backup
+
+**Test scenarios:**
+- Happy path: plan doc references "Esper Labs" and "Randy Wells" from the session; after scrub, plan reads `<company-1>` and `<person-1>`; pre-PII-scrub backup exists; report says "2 tokens replaced across 1 artifact"
+- Multiple instances: "Esper Labs" appears 5 times in one artifact and 3 times in another — all 8 instances become `<company-1>` (same token), one company entry in the report
+- Multiple distinct values: "Esper Labs" and "Nothing" both appear — become `<company-1>` and `<company-2>` respectively; report shows 2 distinct values
+- Email shape: `mvh@esperlabs.ai` becomes `<email-1>`; `randy@zoox.com` becomes `<email-2>`
+- Stop-list miss: a company name not in the config doesn't get scrubbed — surface to user as "did you mean to scrub `<X>`? add to stop-list?" prompt
+- Defense-in-depth hit: PII matches Go source — skill pauses, surfaces the file + line, does not auto-modify, requires user resolution before proceeding to U7
+- Empty PII case: artifacts contain no PII — skill skips scrub silently, advances to U7
+- Stop-list missing: `~/.printing-press/amend-config.yaml` doesn't exist — skill creates a default with a starter list and a comment explaining the format
+
+**Verification:** Diff between pre-scrub and post-scrub artifacts shows only token substitutions; no Go source under `$PRESS_LIBRARY/<api-slug>/` was modified by this phase; scrub report is appended to the plan doc footer.
+
+---
+
+### U7. PR draft checkpoint + library PR open + patch contract enforcement
+
+**Goal:** Show the user a complete PR-draft preview before any `gh` command fires; on confirmation, drive the fork-clone-branch-commit-push-PR flow; enforce the public library's `// PATCH(...)` + `.printing-press-patches.json` contract; emit the structured `---PATCH-RESULT---` block.
+
+**Requirements:** R22, R23, R24, R25, R26, R27, R28, R29, R30
+
+**Dependencies:** U6
+
+**Files:**
+- `skills/printing-press-amend/SKILL.md` (modify — Phase 6 + Phase 7 body)
+- `skills/printing-press-amend/references/library-pr-plumbing.md` (new — copy from publish Step 5/7/8 patterns)
+
+**Approach:**
+- **PR draft preview (Phase 6):** assemble the PR title (`fix(<api-slug>): <summary>` for bugs, `feat(<api-slug>): <summary>` for features; first-tier wins when mixed), body (Summary / Findings table / Changes / Verification / Evidence sections per R27), file diff summary (output of `git diff --stat`), labels (`comp:<api-slug>`, `priority:P<n>`), and the structured `---PATCH-RESULT---` block. Display all of this inline before any `gh` call.
+- **Review checkpoint (still Phase 6):** `AskUserQuestion` with 4 options: open PR as drafted / edit then open / hold (save plan + diff for later resume) / abort. Highlight any unscrubbed-PII risk findings (e.g., "the plan doc references `<company-name-not-in-stoplist>` — confirm intentional or scrub before continuing")
+- **Open phase (Phase 7):** if user picks open or edit-then-open:
+  - Issue ownership: `gh issue list --repo mvanhorn/printing-press-library --search "<api-slug> <keyword>" --state open` — if a matching issue exists, link in the PR body and assign self; if none, open one with the captured findings before the PR
+  - Fork/clone: per `references/library-pr-plumbing.md` (copied from publish Step 5) — managed clone at `$PRESS_HOME/.publish-repo-$PRESS_SCOPE`, push-vs-fork detection, SSH-vs-HTTPS detection
+  - Branch: `amend/<api-slug>-<short-summary>` (timestamped if branch exists); commit message follows family convention with `fix(<api-slug>):` or `feat(<api-slug>):` prefix
+  - Push + PR create: `gh pr create --head <user>:branch --base main --body-file <tmpfile>`. Capture `HEAD_SHA` for durable evidence URLs
+  - Apply labels: `gh pr edit <pr-number> --add-label "comp:<api-slug>" --add-label "priority:P<n>"`
+- **Output (Phase 8 emission):** `---PATCH-RESULT---` ... `---END-PATCH-RESULT---` block with `pr_url`, `pr_number`, `branch_name`, `api_slug`, `scope_tier`, `files_changed: []`, `build_status`, `test_status`, `dogfood_status`, `pii_scrub_summary`, `findings_addressed: []`, `findings_deferred: []`, `deferred_list_path`, `plan_doc_path`. Format mirrors `---POLISH-RESULT---` shape.
+- **Greptile awareness:** mention in SKILL.md that PR will receive a Greptile auto-review. Don't auto-fix, don't poll — surface as a final tip in the user-facing output.
+
+**Patterns to follow:**
+- `skills/printing-press-publish/SKILL.md` Step 5 (managed clone, lines 244–397), Step 7 (collision detection, lines 488–650), Step 8 (branch/commit/push/PR, lines 671–925)
+- `skills/printing-press-polish/SKILL.md` lines 626–648 (`---POLISH-RESULT---` block shape)
+- `~/printing-press-library/AGENTS.md` (PR title/label/issue conventions; `// PATCH(...)` + `.printing-press-patches.json` contract)
+
+**Test scenarios:**
+- Happy path: draft preview shows complete PR; user picks "open as drafted"; fork detected (no push access), managed clone created at `$PRESS_HOME/.publish-repo-$PRESS_SCOPE`, branch `amend/superhuman-folder-coverage` pushed, PR opens against `mvanhorn/printing-press-library`, labels applied, `---PATCH-RESULT---` block emitted with the PR URL
+- Edit then open: user picks edit option, modifies the PR body inline, confirms, PR opens with the edited body
+- Hold: user picks hold; plan + diff saved to `$PRESS_MANUSCRIPTS/<api-slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>-HELD.md`; no PR opened; `---PATCH-RESULT---` emits with `status: held` and the resume path
+- Abort: user picks abort; nothing saved beyond the plan doc from U5 (which exists with `status: aborted`); no PR; clear exit message
+- Issue ownership (existing): a matching issue is found; PR body links it and self is assigned in the comment
+- Issue ownership (none): no matching issue; skill opens one first, then the PR, linking them
+- Branch collision: a branch named `amend/superhuman-folder-coverage` exists from a prior run; skill timestamps the new branch (`amend/superhuman-folder-coverage-2026-05-15-1422`) per publish Step 7
+- Push access vs fork: when push access exists, skill pushes directly; when not, fork-detection routes through user's fork
+- Label application failure: `gh pr edit` fails (e.g., label doesn't exist on repo) — skill warns but doesn't roll back the PR
+- Unscrubbed PII risk surfaced in checkpoint: U6 reported a defense-in-depth hit — checkpoint surfaces this prominently; user must resolve before proceeding
+- Patch contract verified: post-PR open, the PR diff includes `.printing-press-patches.json` updates AND `// PATCH(...)` comments at every changed code site (catches the failure mode where one is added and the other isn't)
+- Greptile mention: final user output includes "Greptile will auto-review your PR; check `gh api repos/mvanhorn/printing-press-library/pulls/<N>/comments` for inline findings"
+
+**Verification:** PR is open at a real URL; labels are applied; `---PATCH-RESULT---` block parses cleanly; the public library's `verify-library-conventions` workflow (next CI run after the PR opens) passes for the patch.
+
+---
+
+### U8. README + skill index update
+
+**Goal:** Make the new skill discoverable via the repo's documentation surfaces.
+
+**Requirements:** Implicit — skills must be listed in the catalog to be found; this is convention-following work, not new feature behavior.
+
+**Dependencies:** U1–U7 (skill must be fully implemented before being indexed; the catalog one-liner draws from the frontmatter description but the README cross-reference and the SKILLS.md entry should describe the delivered skill, not the skeleton)
+
+**Files:**
+- `docs/SKILLS.md` (modify — add `printing-press-amend` row to the skill table)
+- `README.md` (modify — add to skill list section if present)
+
+**Approach:**
+- Add a one-liner row to the `docs/SKILLS.md` skill catalog: name, slash-command, one-sentence summary, link to SKILL.md
+- Mirror in README.md if there's a skills section
+- Frontmatter description (from U1) is the source of truth for the summary; copy it down
+
+**Patterns to follow:**
+- Existing rows in `docs/SKILLS.md` for printing-press-polish, printing-press-retro, printing-press-publish
+
+**Test scenarios:**
+- Test expectation: none -- pure documentation; verified by visual inspection during PR review
+
+**Verification:** `docs/SKILLS.md` lists the new skill; README.md (if applicable) lists it; markdown links resolve.
+
+---
+
+## Open Questions / Deferred to Implementation
+
+- **Active session transcript path** (origin: question after R6) — confirm canonical path during implementation by inspecting `~/.claude/projects/` against the running session. May need platform-conditional logic for non-macOS.
+- **Friction signal extraction precision** (origin: question after R7) — start with prose pattern guidance + agent judgment; revisit precision after the first 3 real-session runs.
+- **PR cross-reference search syntax** (origin: question after R9) — implement keyword + file-path search initially; tune fuzziness based on false-positive rate.
+- **Stale binary version source** (origin: question after R10) — use `.printing-press.json` from the public library as the authoritative version; document fallback for local-only CLIs.
+- **PII detection precision** (origin: question after R18) — stop-list + regex baseline for v1 per Key Technical Decisions; named-entity recognition deferred to v0.2.
+- **Greptile pre-check integration** — origin doc didn't include this; library AGENTS.md confirms Greptile fires on every PR. v0.1 just mentions it in user output; auto-fix integration deferred to follow-up.
+- **`amend-config.yaml` schema** — define during U6 implementation; a starter shape is `{ stoplist: { companies: [...], people: [...] } }` but the exact schema can iterate.
+
+---
+
+## Risks
+
+- **Drift between patch's `references/library-pr-plumbing.md` and publish's inline Step 5/7/8.** Mitigation: deferred-to-follow-up retro item explicitly tracks the extraction. Until then, any change to publish must coordinate with patch.
+- **PII stop-list incompleteness.** A user maintaining `~/.printing-press/amend-config.yaml` may miss new entities. Mitigation: defense-in-depth check on Go source + interactive prompt for unrecognized capitalized strings during U6. Real test in production use.
+- **Greptile reviews patch PRs too.** Greptile may flag patches as bugs. Mitigation: surface the Greptile follow-up step to the user; don't try to pre-empt. v0.2 could auto-fix P0/P1.
+- **Public library AGENTS.md changes.** The `// PATCH(...)` + `.printing-press-patches.json` contract is enforced by `verify-library-conventions`. If that contract evolves, patch's U7 logic must follow. Mitigation: U7 reads patch-record format from a single source (the library AGENTS.md reference); update the reference, not scattered logic.
+- **Auto-detect picks the wrong CLI.** Long sessions may touch many CLIs. Mitigation: U2's confirmation step + explicit-arg override. Don't auto-proceed without user confirm.
+- **`printing-press publish validate` is the single point of validation.** If it has gaps, patch ships unvalidated changes. Mitigation: rely on the same trust model as `/printing-press-publish`; if validate gains gaps, both skills benefit from fixing it.
+
+---
+
+## Verification Strategy
+
+The skill ships when:
+
+- All 7 implementation units pass their per-unit test scenarios
+- `TestSkillSetupBlocksMatchWorkspaceContract` passes with the new entry (U1)
+- `printing-press verify-skill --dir skills/printing-press-amend` is clean (U1)
+- A real-session smoke test against a captured fixture (the 2026-05-15 Superhuman session is the canonical case) produces a valid PR draft against a test fork of the public library, with PII scrubbed and patch records present
+- The PR diff written by the smoke test passes `verify-library-conventions` workflow on the public library
+- `docs/SKILLS.md` and README list the new skill (U8)
+
+---
+
+## v0.2 Amendment — direct-input mode (2026-05-16)
+
+This plan documents the v0.1 design (dogfood-only). The 2026-05-16 Digg-CLI amend revealed that dogfood-only is too narrow: when the user already knows the changes they want, the transcript-mining path fails to capture them.
+
+A v0.2 amendment adds a second input mode (`direct-input`) plus a first-class sniff finding type. Both modes converge at Phase 2 onward. The v0.1 dogfood behavior is preserved bit-for-bit; the expansion is additive.
+
+See `docs/plans/2026-05-16-002-feat-printing-press-amend-direct-input-mode-plan.md` for the v0.2 design, new requirements R34-R38, and the implementation units (U1-U6) that land alongside the v0.1 commits on the same PR (#1490).
diff --git a/docs/plans/2026-05-16-002-feat-printing-press-amend-direct-input-mode-plan.md b/docs/plans/2026-05-16-002-feat-printing-press-amend-direct-input-mode-plan.md
new file mode 100644
index 00000000..9e167f58
--- /dev/null
+++ b/docs/plans/2026-05-16-002-feat-printing-press-amend-direct-input-mode-plan.md
@@ -0,0 +1,377 @@
+---
+title: "feat(skills): printing-press-amend — add direct-input mode + sniff finding type"
+type: feat
+status: active
+created: 2026-05-16
+origin: docs/plans/2026-05-15-feat-printing-press-amend-skill-plan.md
+related_pr: https://github.com/mvanhorn/cli-printing-press/pull/1490
+related_brainstorm: docs/brainstorms/2026-05-15-printing-press-amend-requirements.md
+---
+
+# feat(skills): printing-press-amend — add direct-input mode + sniff finding type
+
+## Summary
+
+Extend the unmerged `/printing-press-amend` skill (PR #1490, branch `feat/printing-press-amend`) so it accepts two input sources instead of one:
+
+1. **Dogfood mode** (already built) — mine the active Claude Code session transcript for friction.
+2. **Direct-input mode** (this plan) — accept user-supplied asks in the slash-command prompt: rename a command, add commands or feeds, fix a named bug, optionally sniff the source site for new endpoints.
+
+Both modes converge at Phase 2 onward. Findings have identical shape; Phase 3 tiering, Phase 4 plan+execute, Phase 5 scrub, Phase 6 PR draft, and Phase 7 PR open are mode-agnostic. The expansion is additive — dogfood mode behavior does not change.
+
+The trigger is the 2026-05-16 Digg-CLI amend, where the user supplied a concrete delta (rename, add four feed URLs, sniff for more) and the skill instead halted hunting for a transcript. Direct-input mode makes that ask first-class.
+
+This plan ships in the same PR (#1490) — the skill has not landed yet and the broader scope replaces the current dogfood-only framing.
+
+---
+
+## Problem Frame
+
+The current `SKILL.md` Phase 1 (`Friction Capture`) opens with: *"Resolve the active session transcript file ... walk the transcript and extract friction signals."* There is no input path that does not require a transcript.
+
+When the user already knows what they want changed — verbatim asks like "rename Digg 1000 to Digg, add these four feed URLs, sniff for new endpoints" — the skill has nowhere to bind those asks. It auto-selects the most-recent transcript (likely unrelated to the target CLI), produces a noisy or empty finding list, and halts at the U1 transcript-confirmation checkpoint. This is exactly what happened during the 2026-05-16 Digg dogfood — the skill scanned five unrelated transcripts and reported back that none was a clean amend-from-dogfood signal.
+
+The fix is one new phase ahead of the current Phase 1 plus one alternative capture branch. The rest of the skill (Phase 2 guards, Phase 3 tiered scope, Phase 4 plan+execute, Phase 5 scrub, Phase 6 PR draft, Phase 7 PR open) already operates on a typed finding list — it does not care whether the findings came from a transcript or a user prompt.
+
+---
+
+## Requirements (carried from origin + new)
+
+Carried verbatim from `docs/brainstorms/2026-05-15-printing-press-amend-requirements.md`:
+
+- R1-R5 (skill identity, naming, name resolution, auto-detect) — unchanged.
+- R6-R10 (friction capture, classification, cross-reference, stale-binary check) — apply in dogfood mode; cross-reference + stale-binary check apply in both modes (R9, R10 promoted to mode-agnostic).
+- R11-R13 (scope confirmation, deferred list) — unchanged, mode-agnostic.
+- R14-R17 (plan + execute) — unchanged, mode-agnostic.
+- R18-R21 (PII scrub) — unchanged, mode-agnostic.
+- R22-R24 (PR draft) — unchanged.
+- R25-R30 (PR open) — unchanged.
+- R31-R33 (output artifacts) — unchanged.
+
+New requirements introduced by this plan:
+
+- R34. The skill accepts two input modes: **dogfood** (current) and **direct-input** (new). Mode is detected from the slash-command invocation context; the user is asked only when detection is ambiguous.
+- R35. Direct-input mode parses user-supplied asks from the slash-command prompt into structured findings. Each ask maps to one finding with `kind: {rename, add-command, add-endpoint, add-feed, fix-bug, sniff}`, `evidence: <verbatim user phrasing>`, and an inferred classification (`bug` or `feature`).
+- R36. Sniff is a first-class finding type. When the user explicitly asks to sniff (phrases like "sniff for new APIs", "find new endpoints"), the skill runs `printing-press browser-sniff` and/or `crowd-sniff` against the target CLI's source site (resolved from `.printing-press.json`) and merges each discovered candidate endpoint as an additional finding. Sniff is opt-in; the skill does not run it by default.
+- R37. Mode does not leak past Phase 1. Phases 2-7 are mode-agnostic; their interfaces consume the typed finding list only.
+- R38. The original dogfood-mode UX is preserved bit-for-bit. A user who invokes `/printing-press-amend` after a session with no direct asks in the prompt sees identical behavior to today.
+
+---
+
+## Key Technical Decisions
+
+- **Mode detection is heuristic + askable, not flag-based.** No new CLI flag on the slash command. The agent reads the slash-command prompt and conversation context, decides dogfood vs direct-input, and asks only when ambiguous. Matches the family's UX: zero structured arguments on slash invocations, agent reads intent.
+- **Direct-input asks are parsed in prose, not in a structured DSL.** The user types `/printing-press-amend rename X to Y, add feeds A B C, sniff for more` and the agent maps phrases to finding kinds via a small parsing rubric documented in `references/direct-input-parsing.md`. No JSON, no flag soup. Misclassification falls back to the U4 scope confirmation modal, which already exists.
+- **Sniff is opt-in per run.** Running `browser-sniff` requires a Chrome session and noticeable wall time; running `crowd-sniff` hits external APIs and writes files under `~/printing-press/manuscripts/`. Surprising it onto users on every direct-input run is bad UX. Trigger only when the user's prompt explicitly names sniffing.
+- **Both modes converge at Phase 2.** Phase 2's PR cross-reference check and stale-binary check apply equally — a duplicate PR is a duplicate regardless of how the finding was sourced; a stale local binary still means the user is about to amend the wrong code. The published-version check stays mode-agnostic.
+- **Append-don't-rewrite for upstream docs.** The original brainstorm (`2026-05-15-printing-press-amend-requirements.md`) and original plan (`2026-05-15-feat-printing-press-amend-skill-plan.md`) stay as the dogfood-mode design record. This plan attaches as a follow-up artifact; the originals get a one-paragraph addendum pointing here, not a rewrite.
+- **All work lands in PR #1490, not a new PR.** The skill is not merged yet; broadening its scope inside the unmerged PR keeps history coherent and avoids a second review cycle. The PR title and body update to reflect both modes.
+
+---
+
+## Scope Boundaries
+
+**In scope:**
+- Frontmatter expansion (description + trigger phrases) to advertise direct-input mode.
+- New `Phase 0 — Input Mode Detection` section in `SKILL.md`.
+- Restructured `Phase 1` to branch on mode: dogfood path (existing) vs direct-input path (new).
+- New `references/direct-input-parsing.md` documenting the ask-to-finding parsing rubric.
+- Sniff finding type integrated into the direct-input branch.
+- Addendum on the original brainstorm and original plan pointing to this plan.
+- PR #1490 title + body refresh.
+
+**Outside this plan:**
+
+### Deferred to Follow-Up Work
+- A structured-input mode using YAML or JSON for non-Claude-Code harnesses. The direct-input parser works from prose; if a future user wants machine-driven amend (CI, cron), that's a separate plan.
+- Sniff result caching beyond what `browser-sniff` / `crowd-sniff` already do. Cross-run sniff dedup is a separate sniff-layer improvement.
+- Bumping `printing-press patch` (the AST-injecting Go subcommand, unrelated to this skill) to consume the new finding kinds. Different mechanism, different surface.
+
+**True non-goals:**
+- Auto-merging the resulting PR — always opens for human review (carried from origin Scope Boundaries).
+- Running mid-session — still triggers at the end of a session or on explicit invocation (carried from origin).
+- Generating net-new CLIs — that's `/printing-press`, not `/printing-press-amend` (carried from origin).
+
+---
+
+## System-Wide Impact
+
+- **`SKILL.md`** is the primary surface. ~80 lines of additions, no deletions, no behavioral changes to existing phases.
+- **`references/transcript-parsing.md`** gets a one-line scope note ("applies in dogfood mode only") but its body is untouched.
+- **New file: `references/direct-input-parsing.md`** mirrors the structure of `transcript-parsing.md` for the new mode.
+- **`printing-press browser-sniff` / `crowd-sniff`** are invoked from the skill in sniff-finding-type runs. No changes to those subcommands; they are called as-is. If a target CLI's `.printing-press.json` lacks `source_url`/`spec_url`, sniff degrades gracefully (the skill asks the user for a URL or skips the sniff finding).
+- **`internal/cli/verify_internal_skill.go`** — verified to enforce only frontmatter shape + body-has-heading. Adding Phase 0 does not break the linter. No code change needed.
+- **`internal/pipeline/contracts_test.go` (`TestSkillSetupBlocksMatchWorkspaceContract`)** — setup block in `SKILL.md` is untouched, so this test continues to pass. Verified by re-running `go test ./internal/pipeline/...` after each unit.
+- **PR #1490** — title and body update to advertise both modes. Existing commits stay; new commits added on the same branch.
+
+---
+
+## Implementation Units
+
+### U1. Frontmatter expansion + trigger phrases
+
+**Goal:** Broaden `SKILL.md` frontmatter so the skill advertises direct-input mode and the trigger-phrase catalog reflects the new entry points.
+
+**Requirements:** R34, R38.
+
+**Dependencies:** none.
+
+**Files:**
+- `skills/printing-press-amend/SKILL.md` (frontmatter block, lines 1-25)
+
+**Approach:**
+- Description rewrites from "Mines the active Claude Code session transcript for friction..." to: "Amend a published CLI from either a dogfood session (mines the active Claude Code transcript for friction) OR direct user-supplied asks (rename a command, add commands or feeds, fix a named bug, optionally sniff the source site for new endpoints). Confirms scope with the user, plans + executes the fix autonomously, scrubs PII, and opens a PR against `mvanhorn/printing-press-library`. Two user-in-loop checkpoints: scope after capture, PR draft before open."
+- Trigger phrases — keep existing ("amend the CLI", "submit a patch", "fix what I just dogfooded", "open a PR for this CLI", "patch this CLI"). Add: "add features to my CLI", "rename this command", "add these feeds to <cli>", "sniff for new APIs in <cli>", "amend with these ideas".
+- `min-binary-version` unchanged at 4.0.0. `allowed-tools` unchanged.
+
+**Patterns to follow:**
+- `skills/printing-press-polish/SKILL.md` frontmatter for trigger-phrase shape.
+
+**Test scenarios:**
+- `printing-press verify-internal-skill --dir skills/printing-press-amend` exits 0.
+- YAML frontmatter still parses (any YAML linter or `python -c "import yaml; yaml.safe_load(open(...))"`).
+- `grep "^name: printing-press-amend"` in `SKILL.md` still matches.
+- `Test expectation: none -- frontmatter-only edit; behavior tests live in U2-U4.`
+
+**Verification:** `printing-press verify-internal-skill --dir skills/printing-press-amend` returns clean. New trigger phrases appear in `description`.
+
+---
+
+### U2. Phase 0 — Input Mode Detection
+
+**Goal:** Insert a new `Phase 0 — Input Mode Detection` section between `## Setup` and the existing `## Phase 1 — Friction Capture`. Decide dogfood vs direct-input, persist the mode for later phases, ask only when ambiguous.
+
+**Requirements:** R34, R37.
+
+**Dependencies:** U1.
+
+**Files:**
+- `skills/printing-press-amend/SKILL.md` (insert new section after the `## Setup` block, before `## Phase 1 — Friction Capture`)
+
+**Approach:**
+- New section header: `## Phase 0 — Input Mode Detection`.
+- Detection rubric (documented in the section, not in code):
+  - **Direct-input mode** when the slash-command prompt contains a concrete CLI name AND at least one of: verb signals (`rename`, `add`, `remove`, `fix`, `sniff`), explicit URLs, an enumerated list of feeds/commands/endpoints, or "these ideas / these features".
+  - **Dogfood mode** when the prompt is empty, or names a CLI without asks ("amend the superhuman CLI"), or explicitly references the session ("what I just dogfooded", "this session's friction").
+  - **Ambiguous** when only one signal is present (CLI name with no verbs, or verbs with no target CLI). Ask the user via `AskUserQuestion`:
+    > "Two ways to source findings for this amend. Which fits?
+    >   1. Mine the current session transcript (dogfood mode)
+    >   2. Use the asks I just typed (direct-input mode)
+    >   3. Both — combine transcript friction with my asks"
+  - Default when no slash-command prompt is present: dogfood mode (preserves the canonical UX).
+- Output: emit a `MODE=<dogfood|direct|both>` marker for later phases. Persist in the run-state directory (`$PRESS_RUNSTATE/current/mode.txt`) for resumability.
+- Document the "both" combined-mode escape hatch: when the user has friction in the session AND a few specific asks, run dogfood Phase 1 first, then append direct-input findings to the same finding list before Phase 2.
+
+**Patterns to follow:**
+- Detection-then-ask pattern matches `/printing-press-publish`'s Phase 1 push-vs-fork resolution (heuristic + askable, never silent).
+
+**Test scenarios:**
+- Direct-input detected: prompt = "rename X to Y in foo-pp-cli, add feeds A B C" → mode=direct, no transcript walk.
+- Dogfood detected: prompt = empty → mode=dogfood, transcript walked.
+- Ambiguous: prompt = "amend superhuman" → AskUserQuestion fires with 3-option modal.
+- Both: prompt = "I dogfooded this session and also want to add feature X" → mode=both, transcript + asks merged.
+- `printing-press verify-internal-skill --dir skills/printing-press-amend` exits 0 after the insertion.
+
+**Verification:** `verify-internal-skill` clean; `Phase 0 — Input Mode Detection` heading present immediately after `## Setup`; detection rubric documents all four branches (direct, dogfood, ambiguous, both).
+
+---
+
+### U3. Direct-input capture branch (Phase 1-DI)
+
+**Goal:** Restructure Phase 1 as mode-conditional. Existing transcript-mining body stays; add a peer branch for direct-input. Both branches emit the same typed finding list.
+
+**Requirements:** R35, R37, R38.
+
+**Dependencies:** U2.
+
+**Files:**
+- `skills/printing-press-amend/SKILL.md` (the `## Phase 1 — Friction Capture` section: split into two named sub-sections)
+- `skills/printing-press-amend/references/transcript-parsing.md` (top-of-file scope note: "applies when MODE=dogfood")
+
+**Approach:**
+- Rename `## Phase 1 — Friction Capture` to `## Phase 1 — Capture`.
+- Add two sub-sections:
+  - `### 1a. Dogfood mode (MODE=dogfood)` — current Phase 1 body lifts here verbatim.
+  - `### 1b. Direct-input mode (MODE=direct)` — new body, ~30 lines, points to `references/direct-input-parsing.md` for the parsing rubric.
+- Direct-input flow:
+  1. Read the slash-command prompt body and any agent-message context from the immediate invocation turn.
+  2. Apply the parsing rubric (rename → kind: rename; "add command X" → kind: add-command; URL list → kind: add-feed or add-endpoint; "sniff for…" → kind: sniff; "fix X" → kind: fix-bug).
+  3. For each ask, create a finding with `id: F<n>`, `kind`, `evidence` (verbatim user phrasing), `target_cli` (resolved from the CLI name in the prompt or Phase 0's name resolution), `classification` (kind:rename + add-* + sniff → feature; kind:fix-bug → bug).
+  4. Skip the U1 transcript-path confirmation modal entirely in this mode — there is no transcript to confirm.
+  5. Target-CLI resolution still runs (per origin R4): short name, full name, or path.
+- "Both" mode (MODE=both): run 1a first, then 1b; append direct-input findings to the dogfood-derived list with non-colliding IDs.
+
+**Patterns to follow:**
+- Finding shape matches Phase 1 dogfood output (see `references/transcript-parsing.md` for the schema). Reuse identical field names so Phase 2-7 do not need to branch.
+
+**Test scenarios:**
+- Single rename ask: prompt = "rename Digg 1000 to Digg in digg-pp-cli" → 1 finding, kind=rename, classification=feature.
+- Multi-feed add: prompt = "add https://digg.com/ai/github/stars, /new, /activity, /recent feeds to digg-pp-cli" → 4 findings, kind=add-feed, target_cli=digg-pp-cli.
+- Mixed asks + sniff: prompt = "rename A to B, add command X, sniff for new endpoints" → 3 findings (rename, add-command, sniff).
+- Bug ask: prompt = "fix the silent-null in `superhuman drafts list` in superhuman-pp-cli" → 1 finding, kind=fix-bug, classification=bug.
+- Both mode: prompt = "amend foo from this session + add command bar" → dogfood findings F1-Fn + direct findings F(n+1), F(n+2)…
+- Empty direct-input prompt: should not enter Phase 1b at all (Phase 0 routes to dogfood).
+
+**Verification:** `verify-internal-skill` clean. `Phase 1 — Capture` has exactly two named sub-sections. Existing transcript-mining text is intact in 1a (diff `references/transcript-parsing.md` shows only the scope note added).
+
+---
+
+### U4. Sniff finding type — inline browser-sniff / crowd-sniff integration
+
+**Goal:** When the user's asks include "sniff for new APIs" (or equivalent), run `printing-press browser-sniff` and/or `crowd-sniff` against the target CLI's source site and merge each discovered candidate endpoint as an additional finding.
+
+**Requirements:** R36.
+
+**Dependencies:** U3.
+
+**Files:**
+- `skills/printing-press-amend/SKILL.md` (extend `### 1b. Direct-input mode` with a "Sniff-finding subroutine" sub-section)
+- `skills/printing-press-amend/references/direct-input-parsing.md` (sniff rubric details)
+
+**Approach:**
+- Triggered when the parsing rubric tags any ask as `kind: sniff`.
+- Resolve the source site:
+  - Read `~/printing-press-library/library/<category>/<slug>/.printing-press.json` for `source_url` or `spec_url`. (Phase 1 already resolves category; reuse.)
+  - If missing, ask the user inline ("Sniff needs a target URL — paste one or skip the sniff finding?").
+- Run `<PRINTING_PRESS_BIN> crowd-sniff --site <url> --json > /tmp/amend-sniff-crowd.json` first (no browser, fast).
+- If the user invocation environment has Chrome MCP available AND the user opted in to deeper sniff, run `<PRINTING_PRESS_BIN> browser-sniff` (the existing browser-sniff CLI consumes a captured HAR; the skill does not orchestrate the capture itself in v0.2 — capture is user-driven or skipped).
+- Convert each discovered candidate endpoint to one finding: `kind: add-endpoint`, `classification: feature`, `evidence: "discovered via crowd-sniff: <endpoint>"`, `provenance: sniff`.
+- Tier these as Tier 3 (polish/architecture) by default; user can promote to Tier 2 at the Phase 3 scope-confirmation checkpoint.
+- Degraded paths:
+  - No `source_url` in `.printing-press.json` → ask user OR skip sniff finding with a logged note.
+  - `crowd-sniff` exits non-zero → log, skip, do not abort the whole amend run.
+  - Zero endpoints discovered → emit one "sniff ran, no new endpoints" entry to the deferred list, not the active findings.
+
+**Patterns to follow:**
+- `/printing-press` (the generate skill) calls `browser-sniff` and `crowd-sniff` in Phase 1.7 — mirror the invocation pattern (absolute-path binary, `--json`, graceful degradation on non-zero exit).
+
+**Test scenarios:**
+- Sniff ask + crowd-sniff finds 5 endpoints → 5 Tier-3 findings appended, all with `provenance: sniff`.
+- Sniff ask + crowd-sniff finds 0 → no findings added, one entry in deferred list.
+- Sniff ask + `.printing-press.json` lacks `source_url` → AskUserQuestion fires for URL OR skip path; sniff finding becomes deferred if user skips.
+- Sniff ask + `crowd-sniff` non-zero exit → log + skip, amend run continues with other findings.
+- No sniff ask → `crowd-sniff` is NOT invoked (verified by absence of `/tmp/amend-sniff-crowd.json`).
+
+**Verification:** Sniff runs only when explicitly requested. Discovered endpoints appear as separate findings with `provenance: sniff`. No invocation of sniff binaries in dogfood-only runs.
+
+---
+
+### U5. New reference doc: `references/direct-input-parsing.md`
+
+**Goal:** Document the prose-to-finding parsing rubric in detail, mirroring the structure of the existing `references/transcript-parsing.md` so the SKILL.md body stays compact.
+
+**Requirements:** R35, R36.
+
+**Dependencies:** U3, U4.
+
+**Files:**
+- `skills/printing-press-amend/references/direct-input-parsing.md` (new file)
+
+**Approach:**
+- Section structure mirrors `transcript-parsing.md`:
+  1. **Input** — the slash-command prompt body, plus the agent-message turn that fired the skill (which often carries elaborations).
+  2. **Parsing rubric — verbs to finding kinds:**
+     - "rename X to Y" / "call it X instead of Y" → `kind: rename`, `classification: feature`.
+     - "add command X" / "add subcommand X" → `kind: add-command`, `classification: feature`.
+     - "add feed <url>" / "add these feeds: <url>, <url>" → one `kind: add-feed` finding per URL.
+     - "add endpoint <url>" / explicit API path → `kind: add-endpoint`.
+     - "fix X" / "X is broken" / "X returns null" → `kind: fix-bug`, `classification: bug`.
+     - "sniff for new APIs" / "find new endpoints" / "discover more" → `kind: sniff`.
+  3. **Finding shape** — same `id`, `kind`, `classification`, `evidence`, `target_cli`, `rationale` schema as dogfood findings. New optional `provenance` field (`user-ask` for direct, `transcript` for dogfood, `sniff` for sniff-derived).
+  4. **Target-CLI resolution** — same rules as origin R4 (short name, full name, path). When the user names the CLI inside the prompt, extract via regex (`<slug>-pp-cli` or "the <slug> CLI"); if absent, fall back to Phase 0 auto-detection or ask.
+  5. **Edge cases** — multi-CLI asks ("amend foo-pp-cli and bar-pp-cli") → split into two runs (out of scope for v0.2; ask user to pick one). Ambiguous verbs ("update X") → ask. URLs without context → ask whether they're feeds, endpoints, or sniff targets.
+
+**Patterns to follow:**
+- `references/transcript-parsing.md` for section ordering and tone.
+
+**Test scenarios:**
+- Doc renders cleanly in GitHub (no broken markdown).
+- All five finding kinds (`rename`, `add-command`, `add-feed`, `add-endpoint`, `fix-bug`, `sniff`) have at least one example phrasing.
+- Multi-CLI edge case is explicitly listed.
+- `Test expectation: none -- reference doc only; behavior tests live in U3 and U4.`
+
+**Verification:** File exists, ~80-120 lines, structure mirrors `transcript-parsing.md`. Referenced by `SKILL.md` Phase 1b.
+
+---
+
+### U6. Upstream doc addendum + PR #1490 refresh
+
+**Goal:** Add a short v0.2 addendum to the original brainstorm and original plan pointing at this plan, and refresh PR #1490's title and body to advertise both modes.
+
+**Requirements:** none (housekeeping for traceability).
+
+**Dependencies:** U1-U5 (changes need to exist before the PR description references them).
+
+**Files:**
+- `docs/brainstorms/2026-05-15-printing-press-amend-requirements.md` (append a section)
+- `docs/plans/2026-05-15-feat-printing-press-amend-skill-plan.md` (append a section)
+- PR #1490 title + body (via `gh pr edit 1490`)
+
+**Approach:**
+- Brainstorm addendum (append at end, before `## Next Steps`):
+  ```
+  ## v0.2 Amendment — direct-input mode (2026-05-16)
+
+  The original brainstorm above frames the skill as dogfood-only. The Digg-CLI
+  amend on 2026-05-16 surfaced a gap: when the user already knows what they
+  want changed, the dogfood-only framing fails. The skill is extended to a
+  second input mode (direct-input) plus a sniff finding type. See
+  `docs/plans/2026-05-16-001-feat-printing-press-amend-direct-input-mode-plan.md`
+  for the design.
+  ```
+- Plan addendum: similar two-paragraph note pointing here.
+- PR #1490 refresh:
+  - Title: "feat(skills): add /printing-press-amend skill — dogfood + direct-input modes"
+  - Body: append a "## v0.2 — direct-input mode" section describing the new mode in 3-5 bullets and linking the new plan + brainstorm addendum.
+
+**Patterns to follow:**
+- Origin-preserving addendum pattern (don't rewrite prior plans; append a v0.2 section) — matches `docs/brainstorms/2026-03-29-publish-skill-requirements.md` and its follow-up addenda.
+
+**Test scenarios:**
+- `gh pr view 1490 --json title,body` shows the new title and the new section.
+- Addenda are appended, not interleaved (original sections unchanged).
+- Links between docs resolve (relative-path links work in GitHub preview).
+- `Test expectation: none -- doc updates; no behavior tests.`
+
+**Verification:** PR #1490 title and body reflect both modes; brainstorm and plan addenda point at this plan; no original section content was deleted or rewritten.
+
+---
+
+## Open Questions / Deferred to Implementation
+
+- **Q1.** Should "both" mode (dogfood + direct-input combined) ship in v0.2 or be deferred to v0.3? Plan currently ships it but only as a thin path (dogfood first, then append direct findings). If implementation reveals merge conflicts in the finding list (e.g., a transcript finding and a direct-input finding describe the same bug), defer to v0.3 and surface as a known limitation in U2.
+- **Q2.** Should `browser-sniff` be auto-invoked when Chrome MCP is available, or always require explicit opt-in? Plan currently requires explicit opt-in. If user dogfood reveals this is friction, relax in v0.3.
+- **Q3.** Where exactly does the slash-command prompt text live in the Claude Code session model? The agent currently reads it from the conversation context; if a stable transcript-path API exists, U2's detection could read it directly. Defer to implementation — the conversation-context read should work.
+- **Q4.** Does `gh pr edit 1490 --title --body-file` preserve existing PR comments and review state? Verify before firing the edit during U6.
+
+---
+
+## Risks
+
+- **R1. Verifier surprise.** If `internal/cli/verify_internal_skill.go` or a CI lint enforces canonical phase names elsewhere (e.g., requires `Phase 1 — Friction Capture` verbatim), the Phase 0 insertion and Phase 1 rename break. Verified manually that the current verifier only checks frontmatter + body-has-heading, but a downstream gate could exist. Mitigation: run `printing-press verify-internal-skill --dir skills/printing-press-amend` after U2 and after U3 before proceeding.
+- **R2. Direct-input parsing brittleness.** Ambiguous user prompts could mis-classify a feature ask as a bug or vice versa. Mitigation: the U4 (Phase 3) scope-confirmation modal already shows tiered findings with rationale; the user catches and corrects there. Worst case is one extra confirmation cycle, not a wrong PR.
+- **R3. Sniff failure modes.** `crowd-sniff` depends on external APIs (GitHub search, npm). Rate-limit failures or 5xx responses could halt an amend run. Mitigation: U4's degraded-path branches treat non-zero sniff exit as "skip the sniff finding, continue with other findings" — never abort the whole run.
+- **R4. PR description drift.** Editing PR #1490 mid-flight could collide with reviewer comments referencing the old title. Mitigation: do U6's PR edit last, after U1-U5 commits are pushed. Existing comments on PR #1490 are non-blocking review notes, not approval gates.
+- **R5. Dogfood-mode regression.** Any structural change to `Phase 1` risks subtly breaking dogfood-mode behavior. Mitigation: U3 lifts the existing Phase 1 body verbatim into `### 1a` — diff-only-additions; the dogfood text is preserved character-for-character.
+
+---
+
+## Verification Strategy
+
+End-to-end verification is the 2026-05-16 Digg amend itself:
+
+1. After U1-U5 land on `feat/printing-press-amend`, fire `/printing-press-amend` with the user's original Digg ask ("rename Digg 1000 to Digg, add the four github feeds, sniff for new").
+2. Confirm Phase 0 detects direct-input mode (no transcript modal fires).
+3. Confirm Phase 1b emits 5+ findings (1 rename, 4 add-feed, 1 sniff that expands further).
+4. Confirm Phase 2 cross-references PRs without halting on duplicates.
+5. Confirm Phase 3 tiering presents the findings to the user.
+6. Confirm Phase 4-7 plan + execute + scrub + PR open without code changes to those phases.
+
+Per-unit verification:
+- After U1: `printing-press verify-internal-skill --dir skills/printing-press-amend` → exit 0.
+- After U2: same verifier → exit 0. `grep "## Phase 0" skills/printing-press-amend/SKILL.md` → match.
+- After U3: same verifier → exit 0. `grep "### 1a\\|### 1b" skills/printing-press-amend/SKILL.md` → two matches.
+- After U4: same verifier → exit 0. Inspect SKILL.md for "Sniff-finding subroutine" sub-section.
+- After U5: `cat skills/printing-press-amend/references/direct-input-parsing.md | wc -l` → 80-120 lines.
+- After U6: `gh pr view 1490 --json title` → contains "dogfood + direct-input".
+- Repo-wide: `go test ./internal/pipeline/...` → `TestSkillSetupBlocksMatchWorkspaceContract` passes (setup block untouched).
diff --git a/internal/cli/root.go b/internal/cli/root.go
index 00c93ce4..3afcff02 100644
--- a/internal/cli/root.go
+++ b/internal/cli/root.go
@@ -51,6 +51,7 @@ func Execute() error {
 	rootCmd.AddCommand(newValidateNarrativeCmd())
 	rootCmd.AddCommand(newVerifyCmd())
 	rootCmd.AddCommand(newVerifySkillCmd())
+	rootCmd.AddCommand(newVerifyInternalSkillCmd())
 	rootCmd.AddCommand(newEmbossCmd())
 	rootCmd.AddCommand(newPatchCmd())
 	rootCmd.AddCommand(newVisionCmd())
diff --git a/internal/cli/verify_internal_skill.go b/internal/cli/verify_internal_skill.go
new file mode 100644
index 00000000..741d6da8
--- /dev/null
+++ b/internal/cli/verify_internal_skill.go
@@ -0,0 +1,259 @@
+package cli
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/spf13/cobra"
+	"gopkg.in/yaml.v3"
+)
+
+// internalSkillFrontmatter is the subset of fields verify-internal-skill checks.
+// Other fields (e.g. user-invocable, context) are tolerated but not enforced —
+// the goal is to catch drift on the load-bearing fields without forcing a rigid
+// template across polish, retro, publish, amend, etc.
+type internalSkillFrontmatter struct {
+	Name             string   `yaml:"name"`
+	Description      string   `yaml:"description"`
+	AllowedTools     []string `yaml:"allowed-tools"`
+	Version          string   `yaml:"version"`
+	MinBinaryVersion string   `yaml:"min-binary-version"`
+	Context          string   `yaml:"context"`
+	UserInvocable    *bool    `yaml:"user-invocable"`
+}
+
+// internalSkillFinding mirrors canonicalFinding's shape so JSON output stays
+// shape-stable across the verify-* family. Downstream consumers (skill polish,
+// CI report aggregators) can parse one schema for both.
+type internalSkillFinding struct {
+	Check    string `json:"check"`
+	Severity string `json:"severity"`
+	Detail   string `json:"detail"`
+	Evidence string `json:"evidence,omitempty"`
+}
+
+type internalSkillReport struct {
+	SkillDir  string                 `json:"skill_dir"`
+	SkillPath string                 `json:"skill_path"`
+	ChecksRun []string               `json:"checks_run"`
+	Findings  []internalSkillFinding `json:"findings"`
+}
+
+// runVerifyInternalSkillChecks runs the static lint pass against an internal
+// SKILL.md. Returns the report and a hasError flag — hasError is true when at
+// least one finding has severity "error" (warn-level findings do not fail the
+// verification).
+func runVerifyInternalSkillChecks(dir string) (internalSkillReport, bool, error) {
+	abs, err := filepath.Abs(dir)
+	if err != nil {
+		return internalSkillReport{}, false, fmt.Errorf("resolving --dir: %w", err)
+	}
+	skillPath := filepath.Join(abs, "SKILL.md")
+	if _, err := os.Stat(skillPath); err != nil {
+		return internalSkillReport{}, false, &ExitError{Code: ExitInputError, Err: fmt.Errorf("no SKILL.md in %s", abs)}
+	}
+
+	report := internalSkillReport{
+		SkillDir:  abs,
+		SkillPath: skillPath,
+		ChecksRun: []string{"frontmatter-parse", "frontmatter-required", "name-matches-dir", "allowed-tools-shape", "body-has-heading"},
+		Findings:  []internalSkillFinding{},
+	}
+
+	skillBytes, err := os.ReadFile(skillPath)
+	if err != nil {
+		return report, false, fmt.Errorf("reading SKILL.md: %w", err)
+	}
+	skill := string(skillBytes)
+
+	frontmatter, body, ok := splitFrontmatter(skill)
+	if !ok {
+		report.Findings = append(report.Findings, internalSkillFinding{
+			Check:    "frontmatter-parse",
+			Severity: "error",
+			Detail:   "SKILL.md does not start with a YAML frontmatter block (--- ... ---). Internal skills require frontmatter.",
+		})
+		return report, true, nil
+	}
+
+	var fm internalSkillFrontmatter
+	if err := yaml.Unmarshal([]byte(frontmatter), &fm); err != nil {
+		report.Findings = append(report.Findings, internalSkillFinding{
+			Check:    "frontmatter-parse",
+			Severity: "error",
+			Detail:   "YAML frontmatter does not parse",
+			Evidence: err.Error(),
+		})
+		return report, true, nil
+	}
+
+	// frontmatter-required: name, description, allowed-tools
+	if strings.TrimSpace(fm.Name) == "" {
+		report.Findings = append(report.Findings, internalSkillFinding{
+			Check:    "frontmatter-required",
+			Severity: "error",
+			Detail:   "frontmatter is missing required field: name",
+		})
+	}
+	if strings.TrimSpace(fm.Description) == "" {
+		report.Findings = append(report.Findings, internalSkillFinding{
+			Check:    "frontmatter-required",
+			Severity: "error",
+			Detail:   "frontmatter is missing required field: description",
+		})
+	}
+	if len(fm.AllowedTools) == 0 {
+		report.Findings = append(report.Findings, internalSkillFinding{
+			Check:    "frontmatter-required",
+			Severity: "error",
+			Detail:   "frontmatter is missing required field: allowed-tools (must be non-empty list)",
+		})
+	}
+
+	// name-matches-dir: name should match the basename of the skill directory
+	if fm.Name != "" {
+		expected := filepath.Base(abs)
+		if fm.Name != expected {
+			report.Findings = append(report.Findings, internalSkillFinding{
+				Check:    "name-matches-dir",
+				Severity: "error",
+				Detail:   fmt.Sprintf("frontmatter `name: %s` does not match directory basename `%s`", fm.Name, expected),
+			})
+		}
+	}
+
+	// allowed-tools-shape: each tool entry must be a non-empty string
+	for i, tool := range fm.AllowedTools {
+		if strings.TrimSpace(tool) == "" {
+			report.Findings = append(report.Findings, internalSkillFinding{
+				Check:    "allowed-tools-shape",
+				Severity: "error",
+				Detail:   fmt.Sprintf("allowed-tools entry %d is empty", i),
+			})
+		}
+	}
+
+	// body-has-heading: the SKILL.md body should have at least one H1 heading
+	if !hasH1Heading(body) {
+		report.Findings = append(report.Findings, internalSkillFinding{
+			Check:    "body-has-heading",
+			Severity: "warn",
+			Detail:   "SKILL.md body has no H1 (`#`) heading; recommended convention is one H1 introducing the skill",
+		})
+	}
+
+	hasError := false
+	for _, f := range report.Findings {
+		if f.Severity == "error" {
+			hasError = true
+			break
+		}
+	}
+	return report, hasError, nil
+}
+
+// splitFrontmatter parses leading `--- ... ---` YAML frontmatter. Returns
+// (frontmatter, body, true) when frontmatter is present, otherwise
+// ("", original, false). Only `\n---\n` is recognized as the closing marker.
+func splitFrontmatter(s string) (string, string, bool) {
+	if !strings.HasPrefix(s, "---\n") && !strings.HasPrefix(s, "---\r\n") {
+		return "", s, false
+	}
+	rest := strings.TrimPrefix(s, "---\n")
+	rest = strings.TrimPrefix(rest, "---\r\n")
+	end := strings.Index(rest, "\n---\n")
+	if end < 0 {
+		end = strings.Index(rest, "\r\n---\r\n")
+		if end < 0 {
+			return "", s, false
+		}
+	}
+	frontmatter := rest[:end]
+	body := rest[end:]
+	body = strings.TrimPrefix(body, "\n---\n")
+	body = strings.TrimPrefix(body, "\r\n---\r\n")
+	return frontmatter, body, true
+}
+
+// hasH1Heading reports whether body contains at least one line starting with `# `.
+func hasH1Heading(body string) bool {
+	for line := range strings.SplitSeq(body, "\n") {
+		if strings.HasPrefix(line, "# ") {
+			return true
+		}
+	}
+	return false
+}
+
+func newVerifyInternalSkillCmd() *cobra.Command {
+	var (
+		dir    string
+		asJSON bool
+	)
+
+	cmd := &cobra.Command{
+		Use:           "verify-internal-skill",
+		Short:         "Lint an internal SKILL.md (frontmatter + canonical sections)",
+		SilenceUsage:  true,
+		SilenceErrors: true,
+		Long: `Run static lint checks against an internal-skill SKILL.md (e.g. printing-press-polish, printing-press-retro, printing-press-amend):
+
+  1. frontmatter-parse — leading --- ... --- block exists and parses as YAML
+  2. frontmatter-required — name, description, and allowed-tools are present
+  3. name-matches-dir — frontmatter name field matches the skill directory basename
+  4. allowed-tools-shape — every allowed-tools entry is a non-empty string
+  5. body-has-heading (warn) — body contains at least one H1 heading
+
+Distinct from verify-skill, which validates a printed CLI's SKILL.md against the CLI's Go source. verify-internal-skill is for skills that are NOT tied to a printed CLI — the polish/retro/publish/amend family lives in skills/ and has no internal/cli/ source to verify against.`,
+		Example: `  printing-press verify-internal-skill --dir skills/printing-press-polish
+  printing-press verify-internal-skill --dir skills/printing-press-amend --json`,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			report, hasError, err := runVerifyInternalSkillChecks(dir)
+			if err != nil {
+				return err
+			}
+
+			if asJSON {
+				enc := json.NewEncoder(os.Stdout)
+				enc.SetIndent("", "  ")
+				if err := enc.Encode(report); err != nil {
+					return fmt.Errorf("encoding JSON: %w", err)
+				}
+			} else {
+				fmt.Fprintf(os.Stdout, "verify-internal-skill: %s\n", report.SkillPath)
+				if len(report.Findings) == 0 {
+					fmt.Fprintln(os.Stdout, "  ✓ all checks passed")
+				} else {
+					for _, f := range report.Findings {
+						symbol := "✘"
+						if f.Severity == "warn" {
+							symbol = "⚠"
+						}
+						fmt.Fprintf(os.Stdout, "  %s [%s] %s: %s\n", symbol, f.Severity, f.Check, f.Detail)
+						if f.Evidence != "" {
+							fmt.Fprintf(os.Stdout, "      evidence: %s\n", f.Evidence)
+						}
+					}
+				}
+			}
+
+			if hasError {
+				return &ExitError{
+					Code:   1,
+					Err:    fmt.Errorf("internal SKILL verification failed"),
+					Silent: true,
+				}
+			}
+			return nil
+		},
+	}
+
+	cmd.Flags().StringVar(&dir, "dir", "", "Path to the internal-skill directory (contains SKILL.md)")
+	_ = cmd.MarkFlagRequired("dir")
+	cmd.Flags().BoolVar(&asJSON, "json", false, "Output as JSON")
+
+	return cmd
+}
diff --git a/internal/cli/verify_internal_skill_test.go b/internal/cli/verify_internal_skill_test.go
new file mode 100644
index 00000000..1d6e525b
--- /dev/null
+++ b/internal/cli/verify_internal_skill_test.go
@@ -0,0 +1,226 @@
+package cli
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func writeInternalSkillFixture(t *testing.T, dir, content string) {
+	t.Helper()
+	require.NoError(t, os.MkdirAll(dir, 0o755))
+	require.NoError(t, os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte(content), 0o644))
+}
+
+func TestVerifyInternalSkill_HappyPath(t *testing.T) {
+	t.Parallel()
+	dir := filepath.Join(t.TempDir(), "printing-press-fixture")
+	writeInternalSkillFixture(t, dir, `---
+name: printing-press-fixture
+description: A fixture skill for testing
+allowed-tools:
+  - Bash
+  - Read
+---
+
+# /printing-press-fixture
+
+A fixture skill body.
+`)
+
+	report, hasError, err := runVerifyInternalSkillChecks(dir)
+	require.NoError(t, err)
+	assert.False(t, hasError, "expected no error-level findings, got %+v", report.Findings)
+	assert.Empty(t, report.Findings, "expected no findings, got %+v", report.Findings)
+}
+
+func TestVerifyInternalSkill_MissingFrontmatter(t *testing.T) {
+	t.Parallel()
+	dir := filepath.Join(t.TempDir(), "printing-press-fixture")
+	writeInternalSkillFixture(t, dir, `# A skill without frontmatter
+
+This skill has no YAML frontmatter at all.
+`)
+
+	report, hasError, err := runVerifyInternalSkillChecks(dir)
+	require.NoError(t, err)
+	assert.True(t, hasError)
+	require.Len(t, report.Findings, 1)
+	assert.Equal(t, "frontmatter-parse", report.Findings[0].Check)
+	assert.Equal(t, "error", report.Findings[0].Severity)
+}
+
+func TestVerifyInternalSkill_MissingRequiredFields(t *testing.T) {
+	t.Parallel()
+	dir := filepath.Join(t.TempDir(), "printing-press-fixture")
+	writeInternalSkillFixture(t, dir, `---
+name: printing-press-fixture
+---
+
+# Body
+`)
+
+	report, hasError, err := runVerifyInternalSkillChecks(dir)
+	require.NoError(t, err)
+	assert.True(t, hasError)
+	count := 0
+	for _, f := range report.Findings {
+		if f.Check == "frontmatter-required" {
+			count++
+		}
+	}
+	assert.Equal(t, 2, count, "expected 2 frontmatter-required findings (description, allowed-tools), got %+v", report.Findings)
+}
+
+func TestVerifyInternalSkill_NameMismatch(t *testing.T) {
+	t.Parallel()
+	dir := filepath.Join(t.TempDir(), "printing-press-fixture")
+	writeInternalSkillFixture(t, dir, `---
+name: completely-different-name
+description: A fixture skill
+allowed-tools:
+  - Bash
+---
+
+# Body
+`)
+
+	report, hasError, err := runVerifyInternalSkillChecks(dir)
+	require.NoError(t, err)
+	assert.True(t, hasError)
+	found := false
+	for _, f := range report.Findings {
+		if f.Check == "name-matches-dir" && f.Severity == "error" {
+			found = true
+			assert.Contains(t, f.Detail, "completely-different-name")
+			assert.Contains(t, f.Detail, "printing-press-fixture")
+		}
+	}
+	assert.True(t, found, "expected name-matches-dir error finding, got %+v", report.Findings)
+}
+
+func TestVerifyInternalSkill_EmptyAllowedToolsEntry(t *testing.T) {
+	t.Parallel()
+	dir := filepath.Join(t.TempDir(), "printing-press-fixture")
+	writeInternalSkillFixture(t, dir, `---
+name: printing-press-fixture
+description: A fixture
+allowed-tools:
+  - Bash
+  - ""
+  - Read
+---
+
+# Body
+`)
+
+	report, hasError, err := runVerifyInternalSkillChecks(dir)
+	require.NoError(t, err)
+	assert.True(t, hasError)
+	found := false
+	for _, f := range report.Findings {
+		if f.Check == "allowed-tools-shape" {
+			found = true
+			assert.Equal(t, "error", f.Severity)
+		}
+	}
+	assert.True(t, found, "expected allowed-tools-shape finding, got %+v", report.Findings)
+}
+
+func TestVerifyInternalSkill_NoH1Heading(t *testing.T) {
+	t.Parallel()
+	dir := filepath.Join(t.TempDir(), "printing-press-fixture")
+	writeInternalSkillFixture(t, dir, `---
+name: printing-press-fixture
+description: A fixture
+allowed-tools:
+  - Bash
+---
+
+## Just an H2
+
+No H1 in the body.
+`)
+
+	report, hasError, err := runVerifyInternalSkillChecks(dir)
+	require.NoError(t, err)
+	assert.False(t, hasError, "body-has-heading is warn-level, should not fail")
+	found := false
+	for _, f := range report.Findings {
+		if f.Check == "body-has-heading" {
+			found = true
+			assert.Equal(t, "warn", f.Severity)
+		}
+	}
+	assert.True(t, found, "expected body-has-heading warning, got %+v", report.Findings)
+}
+
+func TestVerifyInternalSkill_MissingSkillFile(t *testing.T) {
+	t.Parallel()
+	dir := filepath.Join(t.TempDir(), "empty-dir")
+	require.NoError(t, os.MkdirAll(dir, 0o755))
+
+	_, _, err := runVerifyInternalSkillChecks(dir)
+	require.Error(t, err)
+	assert.Contains(t, err.Error(), "no SKILL.md")
+}
+
+func TestSplitFrontmatter(t *testing.T) {
+	t.Parallel()
+	tests := []struct {
+		name        string
+		input       string
+		wantFM      string
+		wantBodyHas string
+		wantOk      bool
+	}{
+		{
+			name:        "happy path",
+			input:       "---\nname: foo\ndescription: bar\n---\n\n# Body\n",
+			wantFM:      "name: foo\ndescription: bar",
+			wantBodyHas: "# Body",
+			wantOk:      true,
+		},
+		{
+			name:   "no frontmatter",
+			input:  "# Just a body\n\nNo frontmatter.\n",
+			wantOk: false,
+		},
+		{
+			name:   "frontmatter never closes",
+			input:  "---\nname: foo\nbody never closes\n",
+			wantOk: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			fm, body, ok := splitFrontmatter(tt.input)
+			assert.Equal(t, tt.wantOk, ok)
+			if tt.wantOk {
+				assert.Equal(t, tt.wantFM, strings.TrimSpace(fm))
+				assert.Contains(t, body, tt.wantBodyHas)
+			}
+		})
+	}
+}
+
+func TestHasH1Heading(t *testing.T) {
+	t.Parallel()
+	tests := []struct {
+		body string
+		want bool
+	}{
+		{"# Heading\n\nBody", true},
+		{"## Only H2\n", false},
+		{"text\n# In the middle\nmore", true},
+		{"", false},
+	}
+	for _, tt := range tests {
+		assert.Equal(t, tt.want, hasH1Heading(tt.body), "body=%q", tt.body)
+	}
+}
diff --git a/internal/pipeline/contracts_test.go b/internal/pipeline/contracts_test.go
index 7056728a..18ab4b36 100644
--- a/internal/pipeline/contracts_test.go
+++ b/internal/pipeline/contracts_test.go
@@ -52,6 +52,7 @@ func TestSkillSetupBlocksMatchWorkspaceContract(t *testing.T) {
 		{path: filepath.Join("..", "..", "skills", "printing-press-score", "SKILL.md"), expectsManuscripts: true},
 		{path: filepath.Join("..", "..", "skills", "printing-press-catalog", "SKILL.md"), expectsManuscripts: false},
 		{path: filepath.Join("..", "..", "skills", "printing-press-publish", "SKILL.md"), expectsManuscripts: true},
+		{path: filepath.Join("..", "..", "skills", "printing-press-amend", "SKILL.md"), expectsManuscripts: true},
 	}
 
 	for _, tt := range tests {
diff --git a/skills/printing-press-amend/SKILL.md b/skills/printing-press-amend/SKILL.md
new file mode 100644
index 00000000..553e1617
--- /dev/null
+++ b/skills/printing-press-amend/SKILL.md
@@ -0,0 +1,721 @@
+---
+name: printing-press-amend
+description: >
+  Amend a published CLI from one of two input sources: (1) dogfood mode mines
+  the active Claude Code session transcript for friction (missing flags, hand-
+  rolled API payloads, silent-null returns); (2) direct-input mode accepts
+  user-supplied asks (rename a command, add commands or feeds, fix a named bug,
+  optionally sniff the source site for new endpoints). Confirms scope with the
+  user, plans + executes the fix autonomously, scrubs PII, and opens a PR
+  against mvanhorn/printing-press-library. Two user-in-loop checkpoints: scope
+  after capture, PR draft before open.
+  Trigger phrases: "amend the CLI", "submit a patch", "fix what I just
+  dogfooded", "open a PR for this CLI", "patch this CLI", "add features to my
+  CLI", "rename this command", "add these feeds to <cli>", "sniff for new APIs
+  in <cli>", "amend with these ideas", "use printing-press-amend",
+  "run printing-press-amend".
+version: 0.2.0
+min-binary-version: "4.0.0"
+context: fork
+user-invocable: true
+allowed-tools:
+  - Bash
+  - Read
+  - Write
+  - Edit
+  - Glob
+  - Grep
+  - AskUserQuestion
+---
+
+# /printing-press-amend
+
+Turn a dogfood session into a PR for a printed CLI in the public library.
+
+```bash
+/printing-press-amend                 # auto-detect target CLI from session
+/printing-press-amend superhuman      # explicit short name
+/printing-press-amend superhuman-pp-cli
+/printing-press-amend ~/printing-press/library/superhuman
+```
+
+This skill lives in this repo (the machine) and acts on a printed CLI in the public library. It is sibling to `/printing-press-publish` (adds a new CLI), `/printing-press-polish` (improves a CLI pre-publish), and `/printing-press-retro` (reflects on the machine itself). None of those cover post-publish CLI amendments driven by real-session friction.
+
+The artifact this skill produces is semantically a "patch" (in the git/PR sense), tracked by the public library's `// PATCH(...)` source-comment convention and `.printing-press-patches.json` manifest. The slash-skill name is `amend` to disambiguate from the existing `printing-press patch` binary subcommand (which AST-injects pre-defined features — different mechanism, different intent).
+
+## Setup
+
+Before doing anything else:
+
+<!-- PRESS_SETUP_CONTRACT_START -->
+```bash
+# min-binary-version: 4.0.0
+
+# Derive scope first — needed for local build detection
+_scope_dir="$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")"
+_scope_dir="$(cd "$_scope_dir" && pwd -P)"
+
+# Prefer local build when running from inside the printing-press repo.
+_press_repo=false
+if [ -x "$_scope_dir/printing-press" ] && [ -d "$_scope_dir/cmd/printing-press" ]; then
+  _press_repo=true
+  export PATH="$_scope_dir:$PATH"
+  echo "Using local build: $_scope_dir/printing-press"
+elif ! command -v printing-press >/dev/null 2>&1; then
+  if [ -x "$HOME/go/bin/printing-press" ]; then
+    echo "printing-press found at ~/go/bin/printing-press but not on PATH."
+    echo "Add GOPATH/bin to your PATH:  export PATH=\"\$HOME/go/bin:\$PATH\""
+  else
+    echo "printing-press binary not found."
+    echo "Install with:  go install github.com/mvanhorn/cli-printing-press/v4/cmd/printing-press@latest"
+  fi
+  return 1 2>/dev/null || exit 1
+fi
+
+# Resolve and emit the absolute path the agent must use for every later
+# `printing-press` invocation. `export PATH` above only affects this one
+# Bash tool call; subsequent calls open a fresh shell and resolve bare
+# `printing-press` against the user's default PATH, where a stale global
+# can silently shadow the local build. The agent captures this marker and
+# substitutes the absolute path into every later invocation.
+if [ "$_press_repo" = "true" ]; then
+  PRINTING_PRESS_BIN="$_scope_dir/printing-press"
+else
+  PRINTING_PRESS_BIN="$(command -v printing-press 2>/dev/null || true)"
+fi
+echo "PRINTING_PRESS_BIN=$PRINTING_PRESS_BIN"
+
+PRESS_BASE="$(basename "$_scope_dir" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_-]/-/g; s/^-+//; s/-+$//')"
+if [ -z "$PRESS_BASE" ]; then
+  PRESS_BASE="workspace"
+fi
+
+PRESS_SCOPE="$PRESS_BASE-$(printf '%s' "$_scope_dir" | shasum -a 256 | cut -c1-8)"
+PRESS_HOME="$HOME/printing-press"
+PRESS_RUNSTATE="$PRESS_HOME/.runstate/$PRESS_SCOPE"
+PRESS_LIBRARY="$PRESS_HOME/library"
+PRESS_MANUSCRIPTS="$PRESS_HOME/manuscripts"
+PRESS_CURRENT="$PRESS_RUNSTATE/current"
+
+mkdir -p "$PRESS_RUNSTATE" "$PRESS_LIBRARY" "$PRESS_MANUSCRIPTS" "$PRESS_CURRENT"
+```
+<!-- PRESS_SETUP_CONTRACT_END -->
+
+After running the setup contract, capture the `PRINTING_PRESS_BIN=<abs-path>` line from stdout. **Every subsequent `printing-press ...` invocation in this skill must use that absolute path** (substitute the value, not the literal `$PRINTING_PRESS_BIN` token) — `export PATH` above only affects the single Bash tool call it runs in, so later calls open a fresh shell where bare `printing-press` resolves against the user's default `PATH` and a stale global can shadow the local build.
+
+After capturing the binary path, check binary version compatibility. Read the `min-binary-version` field from this skill's YAML frontmatter. Run `<PRINTING_PRESS_BIN> version --json` and parse 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: "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/printing-press@latest` to update."
+
+## Phase 0 — Input Mode Detection
+
+This skill accepts two input sources for the finding list it later patches: a Claude Code session transcript (dogfood mode, current behavior) and user-supplied asks in the slash-command prompt (direct-input mode, added in v0.2). The two modes diverge only in Phase 1; Phase 2 onward is mode-agnostic and consumes a typed finding list with identical shape regardless of source.
+
+Decide the mode before Phase 1 runs.
+
+### Detection rubric
+
+Read the slash-command prompt body and the immediate invocation turn from the conversation context. Classify into one of four branches:
+
+- **`MODE=direct`** — the prompt contains a concrete CLI name AND at least one direct-input signal:
+  - Action verbs targeting the CLI: `rename`, `add`, `remove`, `fix`, `sniff`, `discover`
+  - Explicit URLs the user wants added (e.g., `https://example.com/feed/x`)
+  - An enumerated list of feeds, commands, endpoints, or features
+  - Phrasing like "these ideas", "these features", "with the following"
+- **`MODE=dogfood`** — the prompt is empty, OR names a CLI without any asks ("amend the superhuman CLI"), OR explicitly references the session ("what I just dogfooded", "this session's friction", "from my session today")
+- **`MODE=both`** — the prompt clearly references both: a session AND specific asks ("I dogfooded this session and also want to add feature X", "in addition to the friction I hit, please add command Y")
+- **Ambiguous** — only one signal is present (CLI named with no verbs, or verbs with no target CLI, or asks worded so they could be friction reports OR new asks). Ask the user via `AskUserQuestion`:
+
+  > "Two ways to source findings for this amend. Which fits?
+  >   1. Mine the current session transcript (dogfood mode)
+  >   2. Use the asks I just typed (direct-input mode)
+  >   3. Both — combine transcript friction with my asks"
+
+Default when no slash-command prompt is present at all: `MODE=dogfood`. This preserves the canonical UX — `/printing-press-amend` with nothing after still works exactly as it did in v0.1.
+
+### Persist the mode
+
+Write the resolved mode to `$PRESS_RUNSTATE/current/mode.txt` so later phases (and a resumed run) can read it:
+
+```bash
+echo "$MODE" > "$PRESS_RUNSTATE/current/mode.txt"
+```
+
+### Output
+
+Phase 0 emits one line to Phase 1:
+
+```yaml
+mode: <dogfood|direct|both>
+```
+
+Phase 1 branches on this value — dogfood findings flow through `### 1a`, direct-input findings flow through `### 1b`, and combined runs execute both sub-sections in sequence. Phase 2 onward ignores the mode entirely — the finding list is the contract.
+
+## Phase 1 — Capture
+
+This phase produces a typed finding list. The list shape is identical across modes: each finding carries `id`, `kind`, `category`, `classification` (bug or feature), `evidence`, `target_cli`, `rationale`, and `provenance` (`transcript` for dogfood, `user-ask` for direct, `sniff` for sniff-derived). Phase 2 consumes the list verbatim.
+
+When `MODE=dogfood`, run only `### 1a`. When `MODE=direct`, run only `### 1b`. When `MODE=both`, run `### 1a` first, then `### 1b`, and merge the two finding lists with non-colliding IDs (1b continues numbering where 1a left off).
+
+### 1a. Dogfood mode (MODE=dogfood)
+
+Read `references/transcript-parsing.md` for the full procedure. Summary of what this sub-section does:
+
+1. **Resolve the active session transcript file** — derive `<project-dir-slug>` from the current working directory, list `~/.claude/projects/<slug>/*.jsonl` by mtime, pick the most-recently-modified. ALWAYS confirm the resolved path with the user via `AskUserQuestion` before reading — wrong-file selection ingests friction from the wrong session.
+
+2. **Walk the transcript and extract friction signals** — non-zero exit codes, error messages, hand-rolled API payloads (e.g. direct `curl` POSTs that should be a CLI command), retry-after-failure patterns, agent commentary like "X doesn't exist" / "X returns 400", missing-flag references, silent-null returns, auth confusion. Each signal carries timestamp + category + verbatim evidence + the `<slug>-pp-cli` it references.
+
+3. **Classify each signal as bug or feature** with a one-line rationale. Bug = CLI behavior is wrong; feature = CLI behavior is missing. The classification is the agent's best read; the user confirms or overrides at the U4 scope checkpoint.
+
+4. **Auto-detect target CLI** — count occurrences of each `<slug>-pp-cli` in the signals, propose the most-touched CLI as the default. Confirm with `AskUserQuestion` (single CLI: simple yes/no; multiple close: pick from list). When the user passed an explicit `<cli-name-or-path>` argument, skip auto-detect.
+
+5. **Resolve target paths** — accept short name, full name, or absolute path (per R4). Look up the public-library category by walking `~/printing-press-library/library/*/` for a matching directory. The category is needed by U7's PR open phase and is captured here so it doesn't have to be re-derived.
+
+Each finding emitted by 1a carries `provenance: transcript`. Output flows into Phase 2 as the structured finding list documented in `references/transcript-parsing.md`.
+
+### 1b. Direct-input mode (MODE=direct)
+
+Read `references/direct-input-parsing.md` for the full procedure (introduced in v0.2). Summary of what this sub-section does:
+
+1. **Read the slash-command prompt body** plus the immediate agent-message turn that fired the skill — these carry the user's verbatim asks (e.g., "rename Digg 1000 to Digg, add these four feeds: ..., sniff for new endpoints"). There is no transcript to confirm; skip the U1 transcript-path modal that 1a runs.
+
+2. **Resolve the target CLI** — same name-resolution rules as 1a step 4-5 (per R4), but the CLI is normally already named in the prompt itself. Extract via regex (`<slug>-pp-cli` or "the <slug> CLI"); if absent, ask the user.
+
+3. **Parse the asks into structured findings** using the rubric in `references/direct-input-parsing.md`. Each ask maps to one finding with a typed `kind` field:
+   - `rename` — "rename X to Y" / "call it X instead of Y" → `classification: feature`
+   - `add-command` — "add command X" / "add subcommand X" → `classification: feature`
+   - `add-feed` — "add feed <url>" / enumerated URLs the user wants added (one finding per URL) → `classification: feature`
+   - `add-endpoint` — "add endpoint <url>" / explicit API path → `classification: feature`
+   - `fix-bug` — "fix X" / "X is broken" / "X returns null" → `classification: bug`
+   - `sniff` — "sniff for new APIs" / "find new endpoints" / "discover more" → routes to the sniff subroutine in `### 1b.i`
+
+4. **Each finding records the user's verbatim phrasing** in `evidence` so the U4 scope confirmation modal shows the user what they actually wrote.
+
+5. **Edge cases** — multi-CLI asks split into two separate runs (out of scope for v0.2; ask the user to pick one). Ambiguous verbs (`update X` without specifics) trigger an `AskUserQuestion` clarification rather than a guess.
+
+Each finding emitted by 1b carries `provenance: user-ask` (or `provenance: sniff` for findings produced by the sniff subroutine). Output flows into Phase 2 as the same structured finding list shape used by 1a.
+
+### 1b.i. Sniff-finding subroutine
+
+Triggered when the parsing rubric tags any 1b ask as `kind: sniff` (phrases like "sniff for new APIs", "find new endpoints", "discover more endpoints in <site>"). Sniff is opt-in per run — never invoked unless the user named it. Skip this subroutine entirely when no sniff finding is present.
+
+**Step 1 — Resolve the target source URL.** Read the target CLI's published manifest at `~/printing-press-library/library/<category>/<slug>/.printing-press.json` and extract `source_url` (or `spec_url` as fallback). Category was resolved in 1b step 2.
+
+If neither field is set, ask the user inline:
+
+> "Sniff needs a target URL — paste the source site you want sniffed, or skip the sniff finding for this run?"
+
+If the user skips, drop the sniff finding from the active list and continue with the other 1b findings. If the user pastes a URL, use it for steps 2-3.
+
+**Step 2 — Run crowd-sniff first (fast, no browser).** Replace `<PRINTING_PRESS_BIN>` with the absolute path captured at setup:
+
+```bash
+<PRINTING_PRESS_BIN> crowd-sniff --site "$SOURCE_URL" --json > /tmp/amend-sniff-crowd.json
+crowd_exit=$?
+```
+
+`crowd-sniff` queries npm SDKs and GitHub code search to discover candidate endpoints — no browser required. Typical runtime is under a minute.
+
+**Step 3 — Optional browser-sniff (only when the user opted in deeper).** When the user's ask explicitly named browser-based discovery ("sniff with browser", "do a deep sniff") AND a captured HAR is already available, run:
+
+```bash
+<PRINTING_PRESS_BIN> browser-sniff --har "$HAR_PATH" --json > /tmp/amend-sniff-browser.json
+browser_exit=$?
+```
+
+This skill does not orchestrate HAR capture itself in v0.2 — capture is user-driven (the user opens the source site in Chrome, exports the HAR, and points the skill at it) or the deep sniff is skipped with a note. v0.3 may extend the skill to drive capture via the claude-in-chrome MCP; out of scope for v0.2.
+
+**Step 4 — Convert discoveries to findings.** For each candidate endpoint in the sniff output, append one finding to the 1b finding list:
+
+- `id: F<n>` (next available number after the parsed asks)
+- `kind: add-endpoint`
+- `classification: feature`
+- `evidence: "discovered via crowd-sniff: <endpoint-path>"` (or `browser-sniff` when applicable)
+- `target_cli: <slug>-pp-cli`
+- `rationale: <one-line summary from sniff output if available, otherwise "sniff candidate, user to confirm">`
+- `provenance: sniff`
+
+Tier these as Tier 3 (polish/architecture) at Phase 3 by default — the user reviews and can promote individual entries to Tier 2 if they're high-priority.
+
+**Step 5 — Degraded paths.**
+
+| Condition | Behavior |
+|-----------|----------|
+| `.printing-press.json` lacks `source_url` AND user skips when asked | Drop sniff finding; continue with other 1b findings; log "sniff skipped — no source URL". |
+| `crowd-sniff` exits non-zero | Log the error; skip sniff findings; continue with other 1b findings. Do NOT abort the amend run. |
+| `crowd-sniff` returns zero candidate endpoints | Emit one entry to the deferred-findings list ("sniff ran, no new endpoints discovered") rather than adding nothing — gives the user a record. |
+| Browser-sniff requested but no HAR available | Log; fall back to crowd-sniff results only. |
+
+**Step 6 — Surface provenance to the user.** At the Phase 3 scope-confirmation modal, sniff-derived findings are visually grouped under a `(sniff)` provenance tag so the user can decide whether to keep them as a group, e.g.:
+
+```
+Tier 3 — Polish / architecture (5)
+  F8  add-endpoint /v1/feeds/stars (sniff)
+  F9  add-endpoint /v1/feeds/new (sniff)
+  F10 add-endpoint /v1/feeds/activity (sniff)
+  ...
+```
+
+
+
+## Phase 2 — Pre-Checkpoint Guards
+
+Two guards run before the user sees the scope menu. Either can suppress findings or abort the run.
+
+### 2a. PR cross-reference (suppress duplicate proposals)
+
+For each finding from Phase 1, search open + recently-merged PRs in `mvanhorn/printing-press-library` for matches. The duplicate-detection criteria (in priority order): (1) the target CLI's directory path overlaps the PR's changed-file list, (2) keywords from the finding's category + rationale match the PR title or body.
+
+```bash
+# Replace <PRINTING_PRESS_BIN> use with the absolute path captured at setup.
+# This phase uses gh, not the press binary.
+
+# Open PRs touching this CLI
+gh pr list --repo mvanhorn/printing-press-library \
+  --search "in:title,body <slug>" --state open --limit 20 \
+  --json number,title,state,headRefName,files
+
+# Recently merged PRs (last 90 days) touching this CLI.
+# Compute "90 days ago" portably — `date -v-90d` is BSD/macOS only, `date -d`
+# is GNU/Linux only. Try GNU first, fall back to BSD, then to python3. If
+# every form fails, abort with an explicit error rather than letting the
+# dedup guard silently drop out with an empty `merged:>` qualifier.
+ninety_days_ago=$(date -u -d '90 days ago' +%Y-%m-%d 2>/dev/null \
+  || date -u -v-90d +%Y-%m-%d 2>/dev/null \
+  || python3 -c 'import datetime; print((datetime.datetime.now(datetime.UTC).date() - datetime.timedelta(days=90)).isoformat())' 2>/dev/null)
+if [ -z "$ninety_days_ago" ]; then
+  echo "ERROR: cannot compute 90-days-ago date — no GNU date, BSD date, or python3 available."
+  exit 1
+fi
+
+gh pr list --repo mvanhorn/printing-press-library \
+  --search "in:title,body <slug> merged:>$ninety_days_ago" \
+  --state merged --limit 20 \
+  --json number,title,state,mergedAt,headRefName,files
+```
+
+For each finding with a possible-duplicate match, present inline:
+
+> "Finding `F<n>` (`<category>`) may already be addressed by PR #<num> — `<title>` (<state>, <date>). Skip this finding?"
+
+User options: skip (drops to deferred), keep, or "show me PR #<num>" (opens `gh pr view <num> --repo mvanhorn/printing-press-library --web`). The default for clearly-merged matches is "skip"; for open PRs, default is "keep" (the user may want to add to the in-flight PR rather than open a new one).
+
+This guard catches the canonical failure mode from the 2026-05-15 dogfood: proposing auto-refresh for a Printing Press CLI when a similar PR had already shipped on a sibling CLI a few hours earlier. The cost of a false skip is low (the user can re-add via custom selection at U4); the cost of a false-negative duplicate is a rejected PR + reviewer time.
+
+### 2b. Stale-binary check (abort if the dogfooded binary lags published)
+
+Read the public library's `.printing-press.json` for the target CLI to find the published version. Compare to what the local printed CLI binary reports.
+
+```bash
+# Read published version (managed clone if available, else gh api)
+if [ -f "$HOME/printing-press-library/library/<category>/<slug>/.printing-press.json" ]; then
+  published=$(jq -r '.version // empty' "$HOME/printing-press-library/library/<category>/<slug>/.printing-press.json")
+else
+  published=$(gh api repos/mvanhorn/printing-press-library/contents/library/<category>/<slug>/.printing-press.json \
+    --jq '.content' | base64 -d | jq -r '.version // empty')
+fi
+
+# Read local binary version (if installed; the user dogfooded with this binary)
+local_ver=$(<slug>-pp-cli version --json 2>/dev/null | jq -r '.version // empty' || echo "")
+```
+
+If `local_ver` is older than `published` (semver comparison), abort cleanly:
+
+> "The `<slug>-pp-cli` binary you dogfooded is v`<local_ver>`, but the published library version is v`<published>`. The friction you hit may already be fixed in the published version. Run:
+>
+>     go install github.com/mvanhorn/<slug>-pp-cli@latest
+>
+> ...then re-run `/printing-press-amend` after re-dogfooding. Aborting this run."
+
+Edge cases: if `.printing-press.json` is missing or has no `version` field, skip the stale check with a note. If the CLI is local-only (not yet published), skip the check.
+
+### Output
+
+Phase 2 emits the (possibly trimmed) finding list to Phase 3:
+
+```yaml
+findings_kept:
+  - <finding from Phase 1>
+findings_suppressed:
+  - id: F3
+    reason: "Duplicate of PR #571 (merged 2026-05-13)"
+target_binary_check: { local: "1.0.0", published: "1.0.0", status: "current" }
+```
+
+## Phase 3 — Scope Confirmation Checkpoint (User-in-Loop #1)
+
+This is the first of two user checkpoints. Everything until now has been read-only discovery; this checkpoint commits scope.
+
+### Tier the surviving findings
+
+Group findings into three tiers:
+
+- **Tier 1 — Bugs** — every finding with `classification: bug`. CLI behavior is wrong; fixes restore correctness.
+- **Tier 2 — Missing features that solve immediate session pain** — `classification: feature` findings tied to a hand-rolled workaround the user actually built during the session (i.e. the user clearly needed it now, not theoretically).
+- **Tier 3 — Polish / architecture** — remaining `classification: feature` findings that are nice-to-have or architectural improvements without an immediate workaround in the session.
+
+Display the tiered list inline before the question:
+
+```
+Friction found for <slug>-pp-cli (12 signals, 2 suppressed as duplicates):
+
+Tier 1 — Bugs (4)
+  F1  drafts list returns 400 silently
+  F4  messages query returns data: null
+  F7  refresh-token expiry not surfaced in errors
+  F11 ai --query returns code 500
+
+Tier 2 — Missing features that solve session pain (4)
+  F2  no `drafts new` command (user hand-rolled writeMessage payload)
+  F5  no `--type sent` for threads list (user worked around with messages query)
+  F8  no `--remind-in <duration>` flag for send (user manually re-flagged drafts)
+  F10 no `bootstrap` to local SQLite (user did 50+ thread API calls)
+
+Tier 3 — Polish / architecture (2)
+  F12 `auth status` doesn't link to `auth login` when refresh expired
+  F13 doctor doesn't surface stale-binary warning vs. published version
+```
+
+### Pick scope via AskUserQuestion
+
+```
+Which scope should this patch cover?
+  1. Bugs only (Tier 1) — 4 findings
+  2. Bugs + immediate features (Tier 1 + Tier 2) — 8 findings
+  3. All tiers (Tier 1 + Tier 2 + Tier 3) — 10 findings
+  4. Custom selection — pick individual findings
+```
+
+The `AskUserQuestion` options must be self-contained (each label must convey what it does without relying on description text — some harnesses hide the description).
+
+For the **custom selection** path: present a multi-select with each finding's id + category + one-line rationale; confirm the user-checked subset before proceeding.
+
+### Persist the excluded findings
+
+For every finding NOT in the confirmed scope, append to a deferred-list markdown file at:
+
+```
+$PRESS_MANUSCRIPTS/<api-slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>-deferred.md
+```
+
+The `<run-id>` is a fresh timestamped id for this amend run (e.g. `amend-2026-05-15T1432`). Format the deferred file as a YAML preamble + a finding-per-section markdown body so a future `/printing-press-amend` run on the same CLI can re-surface the items.
+
+```yaml
+---
+date: 2026-05-15
+target_cli: superhuman-pp-cli
+amend_run_id: amend-2026-05-15T1432
+deferred_count: 2
+---
+```
+
+Then one section per deferred finding with: id, category, classification, rationale, evidence, reason-deferred (e.g. "user picked Tier 1 only"), and `still_relevant: unknown`.
+
+On a subsequent `/printing-press-amend` run, Phase 3 should look in `$PRESS_MANUSCRIPTS/<api-slug>/` for the most-recent `*-deferred.md` and offer the user the option to include any items still relevant in this run's scope. (Implementation note: this re-surfacing logic ships in v0.1; do not silently re-add — always present and confirm.)
+
+### Edge case: nothing to do
+
+If Phase 2 suppressed every finding (everything was a duplicate), Phase 3 reports cleanly and exits without opening the menu:
+
+> "All findings from this session were addressed by existing PRs. No novel patches found."
+
+### Output
+
+Phase 3 emits to Phase 4:
+
+```yaml
+scope_tier: bugs+features            # or bugs|all|custom
+findings_active: [...]               # the user-confirmed subset
+findings_deferred_path: <path>       # where the deferred file landed
+```
+
+## Phase 4 — Plan + Execute + Validate (Autonomous)
+
+This phase runs unattended between checkpoints 1 and 2. The user does not see fix-by-fix details; they review the final diff at the Phase 6 PR-draft checkpoint.
+
+### Step 1 — Set up the managed clone
+
+Per the Pre-Implementation Decision in the plan: this skill operates DIRECTLY on the managed clone of `mvanhorn/printing-press-library` rather than on `$PRESS_LIBRARY/<slug>/`. The managed clone is at:
+
+```
+$PRESS_HOME/.publish-repo-$PRESS_SCOPE
+```
+
+This is the same clone `/printing-press-publish` uses (Step 5 of that skill). Reuse it:
+
+```bash
+PUBLISH_REPO_DIR="$PRESS_HOME/.publish-repo-$PRESS_SCOPE"
+PUBLISH_CONFIG="$PRESS_HOME/.publish-config-$PRESS_SCOPE.json"
+
+if [ ! -d "$PUBLISH_REPO_DIR/.git" ]; then
+  # First-time setup: see references/library-pr-plumbing.md for the full
+  # detection (push-vs-fork access via gh api .../permissions.push,
+  # SSH-vs-HTTPS protocol detection, scoped-clone cleanup loop).
+  echo "Managed clone not present — bootstrapping..."
+  # ... (see library-pr-plumbing.md)
+else
+  # Refresh from upstream. -f on checkout discards any local edits left behind
+  # by a prior run that aborted between Phase 4's edits and Phase 7's commit —
+  # without -f, those uncommitted changes block the checkout and the subsequent
+  # reset --hard never runs.
+  cd "$PUBLISH_REPO_DIR"
+  git fetch upstream main
+  git checkout -f main
+  git reset --hard upstream/main
+fi
+```
+
+The CLI's directory inside the managed clone is `$PUBLISH_REPO_DIR/library/<category>/<slug>/`. The category was resolved in Phase 1 (or look it up with `find "$PUBLISH_REPO_DIR/library" -maxdepth 2 -name "<slug>" -type d`).
+
+```bash
+CLI_DIR="$PUBLISH_REPO_DIR/library/<category>/<slug>"
+```
+
+All edits in this phase happen INSIDE `$CLI_DIR`. Never touch `$PRESS_LIBRARY/<slug>/` — that's a different working copy and editing it would not flow to the PR.
+
+### Step 2 — Write the per-run plan doc
+
+Before editing code, materialize a plan markdown at:
+
+```
+$PRESS_MANUSCRIPTS/<slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>.md
+```
+
+Mirror to `/tmp/printing-press/amend/` for quick reference. The plan doc carries:
+
+- Frontmatter: `date`, `target_cli`, `amend_run_id`, `scope_tier`, `findings_count`
+- One section per active finding: id, category, classification, rationale, target files (`$CLI_DIR/...` paths), expected behavior change, test scenarios for this finding
+- Risks and dependencies between findings (if any)
+
+The plan is decision-shape, not execution-shape — implementer-time sequencing happens during Step 3.
+
+### Step 3 — Execute the plan (with the patch contract)
+
+For each finding in dependency order:
+
+1. Edit the target files under `$CLI_DIR/`. Honor AGENTS.md anti-reimplementation rules (no hand-rolled response builders; novel commands must call the real endpoint or read from the local store via `// pp:client-call` / `// pp:novel-static-reference` opt-outs only when truly justified).
+
+2. Add a `// PATCH(<short reason>)` source comment at every changed site. Format examples:
+
+   ```go
+   // PATCH(amend-2026-05-15: surface refresh-token expiry to user) — was silently retrying
+   func (c *Client) Refresh(ctx context.Context) error {
+       ...
+   }
+   ```
+
+3. Update `$CLI_DIR/.printing-press-patches.json`. Append an entry under `patches[]`:
+
+   ```json
+   {
+     "date": "2026-05-15",
+     "amend_run_id": "amend-2026-05-15T1432",
+     "summary": "fix(superhuman): surface refresh-token expiry; add drafts new + --type sent",
+     "files": [
+       "internal/auth/refresh.go",
+       "internal/cli/drafts.go",
+       "internal/cli/threads.go"
+     ],
+     "findings_addressed": ["F1", "F2", "F5", "F7"],
+     "patch_count": <total // PATCH comments added in this run>
+   }
+   ```
+
+   Both halves of the contract — `// PATCH(...)` source comments AND `.printing-press-patches.json` entries — are MANDATORY. The library's `verify-library-conventions` workflow rejects PRs where one is present without the other. See `~/printing-press-library/AGENTS.md` "How to record a hand-edit" for the authoritative spec.
+
+4. **Machine-vs-printed-CLI judgment** (per AGENTS.md): when a finding's fix would generalize to every printed CLI (e.g. "the generator should emit `--type sent` for any threads list command"), surface as a borderline case:
+
+   > "Finding F5 (`--type sent` missing) looks like a machine-level fix — the generator template `internal/generator/templates/threads.go.tmpl` should emit it for every CLI with this endpoint shape, not just `<slug>-pp-cli`. Defer to a `/printing-press-retro` follow-up, or proceed CLI-specific?"
+
+   When deferred, drop into the deferred-list with classification `machine-level`. When kept, add a comment in the patch noting the generalize-eventually intent.
+
+### Step 4 — Validate
+
+After all edits land, run the consolidated validator (replace `<PRINTING_PRESS_BIN>` with the absolute path captured at setup):
+
+```bash
+<PRINTING_PRESS_BIN> publish validate --dir "$CLI_DIR" --json > /tmp/amend-validate.json
+exit_code=$?
+```
+
+`publish validate` runs manifest, phase5, govulncheck (scoped to this CLI's module), `go vet`, `go build`, `--help`, `--version`. Exit 0 = clean.
+
+### Step 5 — Retry on failure (up to 3 iterations)
+
+If `publish validate` reports failures, parse the error categories from the JSON, attempt targeted fixes, re-run validate. Maximum 3 iterations total. After iteration 3:
+
+```bash
+# Save the in-progress plan + diff to a holding location
+HELD_PATH="$PRESS_MANUSCRIPTS/<slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>-INCOMPLETE.md"
+git -C "$PUBLISH_REPO_DIR" diff > "${HELD_PATH%.md}.diff"
+cp "$PLAN_PATH" "$HELD_PATH"
+```
+
+Surface the final error log to the user, do NOT auto-open the PR, exit. The user can resume by re-invoking the skill (Phase 1 detects the held plan and offers to resume).
+
+### Step 6 — Caveat: validate doesn't enforce the patch contract
+
+`publish validate` does NOT check `.printing-press-patches.json` ↔ `// PATCH(...)` parity. That contract is enforced by the public library's `verify-library-conventions` workflow only after the PR opens. To catch it locally, run a quick parity check before proceeding to Phase 5:
+
+```bash
+# Count only NEW // PATCH(...) markers added in this run by diffing against
+# upstream. A naive `grep -rc` over $CLI_DIR also counts markers added by
+# prior amend runs, which lets a zero-new-markers run pass when prior history
+# makes the cumulative count meet or exceed the per-run declared count.
+#
+# This check runs in Phase 4 Step 6 — BEFORE the Phase 7 commit. The edits
+# are still in the working tree, not in any commit, so `git diff` against
+# upstream/main (working-tree diff) is the right tool. `format-patch
+# upstream/main..HEAD` would scan committed history only, find no commits,
+# and emit nothing — silently returning 0 markers for every valid run.
+#
+# --no-pager + --no-color + --no-ext-diff defeats colorized output and any
+# configured `diff.external` tool that would reformat the diff away from
+# unified-diff shape and break the grep parse.
+new_patch_markers=$(git -C "$PUBLISH_REPO_DIR" --no-pager diff --no-color --no-ext-diff upstream/main -- "$CLI_DIR" \
+  | grep -cE '^\+.*// PATCH\(')
+patches_entry=$(jq '.patches[-1].patch_count // 0' "$CLI_DIR/.printing-press-patches.json")
+if [ "$new_patch_markers" -lt "$patches_entry" ]; then
+  echo "ERROR: .printing-press-patches.json claims $patches_entry patch markers added this run, found $new_patch_markers new // PATCH(...) comments in the diff."
+  exit 1
+fi
+```
+
+Mismatched contract → fix locally before continuing. (A follow-up retro item: lift this check into `printing-press publish validate` so future amend runs catch it natively.)
+
+### Output
+
+Phase 4 emits to Phase 5:
+
+```yaml
+plan_doc_path: <path>
+managed_clone_dir: <path>
+cli_dir_in_clone: <path>
+findings_addressed: [...]
+build_status: PASS|FAIL
+test_status: PASS|FAIL
+dogfood_status: PASS|FAIL|N/A    # PASS|FAIL when MODE=dogfood (or "both"); always N/A when MODE=direct
+validate_iterations: <n>
+patch_marker_count: <n>
+```
+
+**`dogfood_status` per mode.** When `MODE=dogfood`, the value reflects the result of the dogfood validation step that consumed the transcript-derived findings (PASS if the run produced a clean fix, FAIL if it surfaced a regression). When `MODE=direct`, there is no transcript to dogfood against — set `dogfood_status=N/A`. When `MODE=both`, dogfood validation still runs against the transcript half of the findings; set PASS/FAIL accordingly. This default must be set at the latest by the end of Phase 4 so Phase 7's PR body and Phase 8's RESULT block never emit an empty value.
+
+## Phase 5 — PII Scrub
+
+Read `references/pii-scrubbing.md` for the full procedure. Summary:
+
+The scrub has three layers, each operating on temp staging copies (NOT on the user's session transcript or the in-progress source code):
+
+1. **Credentials** — reuse the regex patterns from `skills/printing-press-retro/references/secret-scrubbing.md` (Stripe, GitHub PATs, bearer tokens, AWS keys, etc.) plus amend-specific additions for `Authorization`/`Cookie`/`X-API-Key` headers in hand-rolled API payloads quoted from the session transcript.
+2. **Entities** — companies, people, emails matched against the user-maintained stop-list at `~/.printing-press/amend-config.yaml`. Replace with shape-preserving tokens (`<company-1>`, `<person-1>`, `<email-1>`) that maintain identity across the artifact set so reviewers can still parse intent.
+3. **First-mention defense** — walk each artifact for capitalized phrases that look like proper nouns and were NOT in the stop-list. Surface to the user inline before the Phase 6 PR-draft display: "Found `Esper Labs` (3x in plan doc, 1x in PR body) — add to stop-list and scrub, or accept?"
+
+Targets, in priority order: PR title/body draft, per-run plan doc, deferred-findings list, any test fixtures or example outputs newly added to `$CLI_DIR`. For each target, copy to `<path>.pre-pii-scrub` BEFORE scrubbing so the user can audit what was changed.
+
+**Defense-in-depth**: walk every `*.go` file in `$CLI_DIR` for stop-list matches. If any match is found, treat as BLOCKING — pause and require user resolution before Phase 6. The agent should never have introduced PII into Go source; this check exists to catch agent error.
+
+**Stop-list creation**: if `~/.printing-press/amend-config.yaml` doesn't exist, the skill creates a default with a starter list and a comment explaining the format. File-mode validation (warn on world-writable, abort on alien-owned).
+
+The scrub report is written to `$PRESS_MANUSCRIPTS/<slug>/<run-id>/scrub-report.json` (NOT committed; for the user's audit). The user-facing summary at the end of the phase: "X tokens replaced across Y artifacts."
+
+## Phase 6 — PR Draft Review Checkpoint (User-in-Loop #2)
+
+This is the second and final user checkpoint. Everything that follows is unattended (push + PR-open + labels + RESULT block). Show the user EVERYTHING that's about to ship before any `gh` command fires.
+
+### Assemble the draft
+
+Compose the PR title, body, labels, and diff summary in memory. Title format follows the public library convention:
+
+- `fix(<api-slug>): <one-line summary>` when the scope is bugs-only
+- `feat(<api-slug>): <one-line summary>` when the scope includes features
+- `feat(<api-slug>): <one-line summary>` when mixed (feature wins because it's the bigger contract change)
+
+The `<one-line summary>` is composed from the most important 1-3 findings (e.g. `surface refresh-token expiry; add drafts new + --type sent`).
+
+PR body sections (per origin R27):
+
+1. **Summary** — 1-3 sentences naming the user pain and the shape of the fix
+2. **Findings** — table with ID, category, type (bug/feature), rationale
+3. **Changes** — output of `git diff --stat upstream/main..HEAD`
+4. **Verification** — build/test/dogfood/validate status from Phase 4
+5. **Evidence** — full GitHub URLs to the per-run plan doc and `.printing-press-patches.json` at the PR's HEAD SHA (captured AFTER push so links don't 404)
+6. **Closes #N** footer when an issue match was found in Step 6 of `library-pr-plumbing.md`
+
+Labels: `comp:<api-slug>` always; `priority:P1` for bugs-only scope, `priority:P2` for bugs+features, `priority:P3` for all-tiers.
+
+### Display before gh fires
+
+Show the user the title, body, label list, and `git diff --stat`. If Phase 5 surfaced unrecognized capitalized phrases that the user accepted as legitimate, RE-DISPLAY those inline now with the sentence each appears in:
+
+> "Reminder: PR body references `<phrase>` (you accepted as legitimate during Phase 5). Confirm before opening."
+
+### AskUserQuestion: open / edit / hold / abort
+
+```
+PR draft ready. What now?
+  1. Open PR as drafted (recommended)
+  2. Edit then open — drop into an interactive review of title/body
+  3. Hold — save plan + diff for later resume; nothing pushed
+  4. Abort — discard everything, no record kept
+```
+
+For **edit then open**: present the title and body as separate editable blocks, accept the user's revisions, re-display the full draft, confirm before proceeding.
+
+For **hold**: save the plan + diff to `$PRESS_MANUSCRIPTS/<slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>-HELD.md` and `${path%.md}.diff`. Emit a RESULT block with `status: held` and the resume path. A future `/printing-press-amend` run can detect held files and offer to resume.
+
+For **abort**: emit a brief confirmation. Plan doc from U5 stays (with `status: aborted` written into the frontmatter) so the user has a record of what was found, but nothing else is preserved. Managed clone is reset on next run.
+
+## Phase 7 — PR Open (Autonomous)
+
+If the user picked open or edit-then-open, run `references/library-pr-plumbing.md` Steps 5-7:
+
+1. **Step 5** — `git add "$CLI_DIR"` + commit with conventional message + the findings list
+2. **Step 6** — search for an existing issue matching the findings; link or open new; self-assign best-effort
+3. **Step 7** — push the branch (push-vs-fork access mode determined in Step 1), `gh pr create` with `--body-file`, capture HEAD_SHA, apply labels
+
+The fork/access detection, branch collision handling, and managed-clone refresh patterns are documented in detail in `references/library-pr-plumbing.md`. Do NOT inline those patterns here — the reference is the authoritative source.
+
+After the PR opens, surface the URL + Greptile note in the user-facing summary:
+
+> "PR open: <url>
+>
+> Greptile will review within ~2 minutes. Check inline comments:
+>
+>     gh api repos/mvanhorn/printing-press-library/pulls/<N>/comments
+>
+> P0/P1 findings are worth addressing before requesting human review."
+
+## Phase 8 — Output
+
+Emit the structured `---PATCH-RESULT---` block on completion. Format:
+
+```
+---PATCH-RESULT---
+pr_url: <url>
+pr_number: <n>
+branch_name: <name>
+api_slug: <slug>
+scope_tier: <bugs|bugs+features|all|custom>
+files_changed:
+- <file>
+build_status: <PASS|FAIL>
+test_status: <PASS|FAIL>
+dogfood_status: <PASS|FAIL|N/A>
+pii_scrub_summary: <N tokens replaced across M artifacts>
+findings_addressed:
+- <one-line-summary>
+findings_deferred:
+- <one-line-summary>
+deferred_list_path: <path>
+plan_doc_path: <path>
+---END-PATCH-RESULT---
+```
+
+## Verification of this skill itself
+
+The static lint pass for this SKILL.md runs via:
+
+```bash
+<PRINTING_PRESS_BIN> verify-internal-skill --dir skills/printing-press-amend
+```
+
+(See `internal/cli/verify_internal_skill.go` and the matching test file. The setup-contract parity check runs as a Go test in `internal/pipeline/contracts_test.go` — `TestSkillSetupBlocksMatchWorkspaceContract`.)
diff --git a/skills/printing-press-amend/references/.gitkeep b/skills/printing-press-amend/references/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/skills/printing-press-amend/references/direct-input-parsing.md b/skills/printing-press-amend/references/direct-input-parsing.md
new file mode 100644
index 00000000..f4bef2ad
--- /dev/null
+++ b/skills/printing-press-amend/references/direct-input-parsing.md
@@ -0,0 +1,77 @@
+# Direct-Input Parsing — Ask Capture for `/printing-press-amend`
+
+**Scope:** This reference applies when `MODE=direct` (Phase 0 detected user-supplied asks in the slash-command prompt) or when running the direct-input half of `MODE=both`. For session-friction mode (`MODE=dogfood`), see `transcript-parsing.md` instead — that mode walks a transcript and never reads the prompt body.
+
+This reference is loaded by Phase 1's `### 1b. Direct-input mode` sub-section of `printing-press-amend`. It defines how the agent parses the user's verbatim asks in the slash-command invocation and converts them to the typed finding list that Phase 2 consumes.
+
+## Input
+
+The agent reads two sources:
+
+1. **The slash-command prompt body** — everything the user typed after `/printing-press-amend ...` in the invocation that fired this skill. This is the primary signal.
+2. **The immediate agent-message turn** — the user's prior turn that fired the skill (when applicable). Sometimes the user names asks in conversational context just before invoking the skill; that context is in-scope here.
+
+Do NOT read the conversation transcript beyond the immediate invocation turn — that's `MODE=dogfood` behavior. Direct-input mode trusts the user's explicit prompt and does not infer asks from earlier conversational drift.
+
+## Parsing rubric — verbs to finding kinds
+
+Map each ask in the prompt to one finding using the following rubric. When a single prompt contains multiple asks (which is the common case), produce one finding per ask.
+
+| User phrasing | `kind` | `classification` | Notes |
+|---------------|--------|------------------|-------|
+| "rename X to Y", "call it X instead of Y", "should be named X not Y" | `rename` | `feature` | Renaming a command, subcommand, flag, or output label. Capture both the old and new names in `evidence`. |
+| "add command X", "add subcommand X", "add a Y subcommand" | `add-command` | `feature` | New top-level or nested Cobra command. |
+| "add feed <url>", "add these feeds: <url>, <url>", enumerated URLs | `add-feed` | `feature` | One finding per URL. `evidence` carries the full URL. |
+| "add endpoint <path>", "add the /v1/foo endpoint", explicit API path | `add-endpoint` | `feature` | Hand-named endpoint to wrap. `evidence` carries the path. |
+| "fix X", "X is broken", "X returns null", "X errors out", "broken: X" | `fix-bug` | `bug` | Behavior is wrong in the published CLI. |
+| "sniff for new APIs", "find new endpoints", "discover more", "what else is there in <site>" | `sniff` | `feature` | Triggers the sniff subroutine (`### 1b.i`); produces zero-to-many `add-endpoint` findings with `provenance: sniff`. |
+
+When a phrase fits multiple kinds (e.g., "add the X feed" — `add-feed` or `add-command`?), prefer the more specific kind based on context: a URL → `add-feed`; a noun like "command" or "subcommand" → `add-command`; an API path with a method → `add-endpoint`.
+
+## Finding shape
+
+Each finding emitted by 1b carries the same fields as 1a findings, with one new field (`provenance`):
+
+```yaml
+- id: F<n>                    # F1, F2, ... — continues numbering when MODE=both
+  kind: <rename|add-command|add-feed|add-endpoint|fix-bug>
+  category: <free-text categorical label, e.g. "command-rename", "feed-add">
+  classification: <bug|feature>
+  evidence: "<verbatim user phrasing>"
+  target_cli: <slug>-pp-cli
+  rationale: "<one-line agent summary of what this finding means>"
+  provenance: user-ask        # or "sniff" for sniff-derived findings
+```
+
+The `evidence` field carries the user's verbatim phrasing — not the agent's paraphrase — so the Phase 3 scope-confirmation modal shows the user exactly what they wrote. This makes mis-classification recoverable: the user sees their own words and can correct the agent's tier or kind at the U4 modal.
+
+## Target-CLI resolution
+
+When the user names the CLI inside the prompt, extract it via regex (in order):
+
+1. `<slug>-pp-cli` literal (e.g., `digg-pp-cli`)
+2. `the <slug> CLI` or `the <slug> cli` (e.g., `the digg CLI` → `digg-pp-cli`)
+3. `for <slug>` when followed by an ask verb (e.g., `for digg, add feed ...`)
+4. `<slug>` alone when the prompt has only one short-name candidate
+
+If no slug is named anywhere in the prompt, fall back to Phase 0 auto-detection: list recently-touched `<slug>-pp-cli` invocations in the immediate invocation turn, propose the most-touched, and confirm with `AskUserQuestion`. If even auto-detect can't resolve, ask the user.
+
+Once resolved, accept any of the three forms (short name, full name, path) per origin R4 — the resolution rules are identical to 1a step 4-5.
+
+## Edge cases
+
+**Multi-CLI asks** — Out of scope for v0.2. When the prompt names two or more distinct CLIs ("amend foo-pp-cli and bar-pp-cli"), ask the user to pick one and re-invoke for the other:
+
+> "This prompt names multiple CLIs (foo-pp-cli, bar-pp-cli). v0.2 amend handles one CLI per run. Which one should I scope to first?"
+
+**Ambiguous verbs** — "update X", "improve X", "make X better" without further specifics trigger an `AskUserQuestion` clarification rather than a guess. Offer two to three concrete kind options based on the surrounding context.
+
+**Bare URLs without context** — A URL in the prompt with no surrounding verb ("https://example.com/feed/x" alone) triggers a clarifying ask: is this a feed to add, an endpoint to wrap, or a sniff target?
+
+**Conflicting kinds in one ask** — "rename X to Y AND fix the bug in Y" splits into two findings: one `rename`, one `fix-bug`. Findings stay atomic; don't merge them into one mixed-kind entry.
+
+**Combined-mode merging (MODE=both)** — When 1b runs after 1a, 1b's finding IDs continue numbering from where 1a left off (1a emits F1..Fn; 1b emits F(n+1)..). Findings keep their own `provenance` regardless of which sub-section produced them. The Phase 3 modal groups by tier, not by mode — the user sees one merged list.
+
+## Output
+
+1b emits the same structured finding list shape as 1a. Phase 2 consumes the list without branching on `provenance`. Sniff findings (when present) are produced by the `### 1b.i` subroutine and appended to this same list before handoff.
diff --git a/skills/printing-press-amend/references/library-pr-plumbing.md b/skills/printing-press-amend/references/library-pr-plumbing.md
new file mode 100644
index 00000000..b728d948
--- /dev/null
+++ b/skills/printing-press-amend/references/library-pr-plumbing.md
@@ -0,0 +1,299 @@
+# Library PR Plumbing for `/printing-press-amend`
+
+This reference is loaded by Phase 6 and Phase 7. It carries the fork → managed-clone → branch → commit → push → PR-create patterns adapted from `/printing-press-publish` Steps 5, 7, and 8.
+
+**Drift advisory**: `/printing-press-publish` carries the canonical inline version of these patterns. This file is a copy adapted for amend's use case (existing-CLI patches, not new-CLI publishes). When publish's plumbing changes, this file may drift. A follow-up retro item will extract shared helpers into `scripts/`; until then, audit both surfaces together.
+
+The setup contract environment variables (`PRESS_HOME`, `PRESS_SCOPE`, etc.) are already exported by the time this reference runs — see the SKILL.md's setup contract block.
+
+---
+
+## Step 1 — Resolve managed clone access mode
+
+The managed clone lives at `$PRESS_HOME/.publish-repo-$PRESS_SCOPE`. The auxiliary config at `$PRESS_HOME/.publish-config-$PRESS_SCOPE.json` caches the access mode so detection runs once per scope.
+
+```bash
+PUBLISH_REPO_DIR="$PRESS_HOME/.publish-repo-$PRESS_SCOPE"
+PUBLISH_CONFIG="$PRESS_HOME/.publish-config-$PRESS_SCOPE.json"
+
+# Read cached config if present
+if [ -f "$PUBLISH_CONFIG" ]; then
+  managed_by=$(jq -r '.managed_by // empty' "$PUBLISH_CONFIG")
+  access=$(jq -r '.access // empty' "$PUBLISH_CONFIG")        # "push" or "fork"
+  gh_user=$(jq -r '.gh_user // empty' "$PUBLISH_CONFIG")
+  protocol=$(jq -r '.protocol // empty' "$PUBLISH_CONFIG")    # "ssh" or "https"
+fi
+
+# Resolve when missing
+if [ -z "$access" ]; then
+  gh_user=$(gh api user --jq .login)
+  push_perm=$(gh api repos/mvanhorn/printing-press-library --jq .permissions.push 2>/dev/null || echo false)
+  if [ "$push_perm" = "true" ]; then
+    access="push"
+  else
+    access="fork"
+  fi
+
+  # Protocol: prefer SSH if user has it set up
+  if ssh -T git@github.com 2>&1 | grep -q "successfully authenticated"; then
+    protocol="ssh"
+  else
+    protocol="https"
+  fi
+
+  managed_by="amend"
+  jq -n --arg by "$managed_by" --arg a "$access" --arg u "$gh_user" --arg p "$protocol" \
+    --arg cp "$PUBLISH_REPO_DIR" --arg sd "$_scope_dir" \
+    '{managed_by: $by, access: $a, gh_user: $u, protocol: $p, clone_path: $cp, scope_dir: $sd}' \
+    > "$PUBLISH_CONFIG"
+fi
+```
+
+Reference: publish SKILL.md Step 5 (lines 244-397).
+
+---
+
+## Step 2 — Bootstrap or refresh the managed clone
+
+```bash
+if [ ! -d "$PUBLISH_REPO_DIR/.git" ]; then
+  # First-time setup
+  if [ "$access" = "push" ]; then
+    if [ "$protocol" = "ssh" ]; then
+      git clone git@github.com:mvanhorn/printing-press-library.git "$PUBLISH_REPO_DIR"
+    else
+      git clone https://github.com/mvanhorn/printing-press-library.git "$PUBLISH_REPO_DIR"
+    fi
+    cd "$PUBLISH_REPO_DIR"
+    git remote add upstream git@github.com:mvanhorn/printing-press-library.git 2>/dev/null || true
+  else
+    # Fork-based: ensure user fork exists, clone fork, set upstream
+    gh repo fork mvanhorn/printing-press-library --clone=false --remote=false 2>/dev/null || true
+    if [ "$protocol" = "ssh" ]; then
+      git clone "git@github.com:$gh_user/printing-press-library.git" "$PUBLISH_REPO_DIR"
+    else
+      git clone "https://github.com/$gh_user/printing-press-library.git" "$PUBLISH_REPO_DIR"
+    fi
+    cd "$PUBLISH_REPO_DIR"
+    git remote add upstream "https://github.com/mvanhorn/printing-press-library.git"
+  fi
+else
+  # Refresh from upstream. -f on checkout discards any local edits left behind
+  # by a prior run that aborted between Phase 4's edits and Phase 7's commit —
+  # without -f, those uncommitted changes block the checkout and the subsequent
+  # reset --hard never runs, leaving the clone permanently stuck on an amend
+  # branch with conflicting state.
+  cd "$PUBLISH_REPO_DIR"
+  git fetch upstream main
+  git checkout -f main
+  git reset --hard upstream/main
+fi
+```
+
+The reset-hard + force-checkout on refresh is intentional: the managed clone is treated as a scratch surface, never as long-term local-state storage. Any local edits in it are by definition leftover state from an aborted run and must be discarded before the next run reuses the clone.
+
+---
+
+## Step 3 — Resolve target CLI directory inside the clone
+
+```bash
+# Category was resolved in Phase 1; if missing, look it up by walking
+if [ -z "$category" ]; then
+  category=$(find "$PUBLISH_REPO_DIR/library" -maxdepth 2 -name "$slug" -type d \
+    | head -1 | awk -F/ '{print $(NF-1)}')
+fi
+CLI_DIR="$PUBLISH_REPO_DIR/library/$category/$slug"
+[ -d "$CLI_DIR" ] || { echo "ERROR: target CLI dir not found: $CLI_DIR"; exit 1; }
+```
+
+This is what U5 edits.
+
+---
+
+## Step 4 — Branch creation with collision detection
+
+```bash
+SHORT_SUMMARY=$(echo "$pr_title" | sed -E 's/^(feat|fix)\([^)]+\):\s*//' | tr '[:upper:] ' '[:lower:]-' | sed -E 's/[^a-z0-9-]//g; s/-+/-/g; s/^-//; s/-$//' | cut -c1-40)
+BRANCH_NAME="amend/$slug-$SHORT_SUMMARY"
+
+# Check for existing branch (open PR, own merged branch zombie, or fresh)
+existing_open=$(gh pr list --repo mvanhorn/printing-press-library \
+  --head "$gh_user:$BRANCH_NAME" --state open --limit 1 --json number,title)
+existing_local=$(git branch --list "$BRANCH_NAME" | wc -l)
+
+if [ "$(echo "$existing_open" | jq 'length')" -gt 0 ]; then
+  # Open PR exists from this branch — surface to user and stop the shell flow.
+  # The calling skill must then resolve the conflict via AskUserQuestion
+  # (amend the existing PR by pushing to the same branch, or open new with
+  # a timestamped branch) before re-entering this snippet with the chosen path.
+  echo "ERROR: open PR already exists from $BRANCH_NAME:"
+  echo "$existing_open" | jq -r '.[0] | "  PR #\(.number): \(.title)"'
+  echo ""
+  echo "Resolve before continuing:"
+  echo "  1. Amend the existing PR — push to $BRANCH_NAME (skip this snippet's checkout)"
+  echo "  2. Open a new PR — re-run with a timestamp suffix on the branch name"
+  exit 1
+fi
+
+if [ "$existing_local" -gt 0 ]; then
+  # Local zombie from prior run — timestamp to avoid clobber
+  TIMESTAMP=$(date -u +%Y-%m-%dT%H%M)
+  BRANCH_NAME="amend/$slug-$SHORT_SUMMARY-$TIMESTAMP"
+fi
+
+git checkout -b "$BRANCH_NAME"
+```
+
+When the skill driver sees a non-zero exit from this block, it must invoke `AskUserQuestion` to surface the two-option choice (amend the existing PR vs. open a new timestamped one), then re-enter the snippet with the chosen path. The hard `exit 1` exists so a literal shell-flow execution stops here rather than failing later with a confusing `git checkout -b` error when the local branch already exists.
+
+Reference: publish SKILL.md Step 7 (lines 488-650) for the full collision matrix (open PR + own merged + zombie + branch-timestamping).
+
+---
+
+## Step 5 — Commit
+
+```bash
+# Stage every file changed in $CLI_DIR (the validate step ensured no off-target changes)
+git add "$CLI_DIR"
+
+# Conventional commit message
+git commit -m "$(cat <<EOF
+$pr_title
+
+$pr_summary
+
+Findings addressed:
+$(echo "$findings_active" | jq -r '.[] | "- \(.id): \(.category) — \(.rationale)"')
+
+amend run: $amend_run_id
+EOF
+)"
+```
+
+The PR title is composed in Phase 6's draft assembly (e.g. `fix(superhuman): surface refresh-token expiry; add drafts new + --type sent`).
+
+---
+
+## Step 6 — Issue ownership
+
+Per `~/printing-press-library/AGENTS.md`, contributors search for an existing issue before opening a PR:
+
+```bash
+issue_match=$(gh issue list --repo mvanhorn/printing-press-library \
+  --search "$slug $primary_keyword" --state open --limit 5 \
+  --json number,title,labels)
+
+if [ "$(echo "$issue_match" | jq 'length')" -gt 0 ]; then
+  # Surface candidates; ask user to pick one or open new
+  # ...
+  ISSUE_NUM=<chosen number>
+  PR_BODY_FOOTER="Closes #$ISSUE_NUM"
+else
+  # Open a new issue first
+  ISSUE_NUM=$(gh issue create --repo mvanhorn/printing-press-library \
+    --title "$pr_title" \
+    --body "$(cat <<EOF
+Captured during a /printing-press-amend run on $slug-pp-cli.
+
+Findings:
+$(echo "$findings_active" | jq -r '.[] | "- \(.id) (\(.classification)): \(.rationale)"')
+
+PR with the proposed fix follows.
+EOF
+)" --label "comp:$slug" \
+    | grep -oE '[0-9]+$')
+  PR_BODY_FOOTER="Closes #$ISSUE_NUM"
+fi
+
+# Self-assign the issue (best-effort; permissions may block)
+gh issue edit "$ISSUE_NUM" --repo mvanhorn/printing-press-library --add-assignee "$gh_user" 2>/dev/null || true
+```
+
+---
+
+## Step 7 — Push and PR-create
+
+```bash
+# Push the branch
+if [ "$access" = "push" ]; then
+  git push origin "$BRANCH_NAME"
+  PR_HEAD="$BRANCH_NAME"
+else
+  git push -u origin "$BRANCH_NAME"
+  PR_HEAD="$gh_user:$BRANCH_NAME"
+fi
+
+# Capture HEAD_SHA AFTER push so evidence URLs are durable
+HEAD_SHA=$(git rev-parse HEAD)
+
+# Compose PR body file
+PR_BODY_PATH=$(mktemp -t amend-pr-body)
+cat > "$PR_BODY_PATH" <<EOF
+## Summary
+
+$pr_summary
+
+## Findings
+
+| ID | Category | Type | Rationale |
+|---|---|---|---|
+$(echo "$findings_active" | jq -r '.[] | "| \(.id) | \(.category) | \(.classification) | \(.rationale) |"')
+
+## Changes
+
+$(git diff --stat upstream/main..HEAD)
+
+## Verification
+
+- Build: $build_status
+- Tests: $test_status
+- Dogfood: ${dogfood_status:-N/A}
+- \`printing-press publish validate\`: PASS (after $validate_iterations iteration(s))
+- Patch contract: $patch_marker_count // PATCH() comments, .printing-press-patches.json updated
+
+## Evidence
+
+- Patch record: https://github.com/$gh_user/printing-press-library/blob/$HEAD_SHA/library/$category/$slug/.printing-press-patches.json
+- Per-finding rationale: see the per-finding evidence captured in the patch record above and the Findings table earlier in this PR body
+- Local plan doc (not in the PR — PII-scrubbed local artifact): \`\$PRESS_MANUSCRIPTS/$slug/<run-id>/proofs/<timestamp>-amend-$slug.md\` (path provided here for the original printer's reference; the artifact stays local by design)
+
+$PR_BODY_FOOTER
+EOF
+
+# Open the PR
+PR_URL=$(gh pr create \
+  --repo mvanhorn/printing-press-library \
+  --head "$PR_HEAD" \
+  --base main \
+  --title "$pr_title" \
+  --body-file "$PR_BODY_PATH")
+
+PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
+
+# Apply labels
+gh pr edit "$PR_NUMBER" --repo mvanhorn/printing-press-library \
+  --add-label "comp:$slug" \
+  --add-label "priority:P${scope_priority}" 2>/dev/null || true
+```
+
+Reference: publish SKILL.md Step 8 (lines 671-925).
+
+---
+
+## Step 8 — Greptile awareness (informational)
+
+Every PR opened against `mvanhorn/printing-press-library` receives a Greptile auto-review. The skill does NOT auto-fix Greptile findings (deferred to v0.2). Tell the user in the final summary:
+
+> "Greptile will review your PR within ~2 minutes. Check inline comments via:
+>
+>     gh api repos/mvanhorn/printing-press-library/pulls/$PR_NUMBER/comments
+>
+> ...or in the GitHub UI. P0/P1 findings are worth addressing before requesting human review."
+
+---
+
+## Cleanup
+
+The managed clone stays in place for the next amend run on this scope (refresh-from-upstream on next bootstrap). Nothing to clean up.
+
+If the user explicitly aborts mid-run, leave the clone in whatever state it's in — the next run's reset-hard will restore it.
diff --git a/skills/printing-press-amend/references/pii-scrubbing.md b/skills/printing-press-amend/references/pii-scrubbing.md
new file mode 100644
index 00000000..245cd22e
--- /dev/null
+++ b/skills/printing-press-amend/references/pii-scrubbing.md
@@ -0,0 +1,107 @@
+# PII Scrubbing for `/printing-press-amend`
+
+This reference is loaded by Phase 5. The scrub mechanism has two layers — credentials (regex patterns, reused from `/printing-press-retro`) and entities (user-maintained stop-list, specific to amend).
+
+The scrub operates on **temp staging copies** of the artifacts that will leave the local machine — never on the user's original session transcript or the in-progress source code in the managed clone. Source code in `$CLI_DIR` is presumed PII-free by the agent's own restraint (the agent should never have introduced PII into Go source); this is verified as a defense-in-depth check, not as a primary control.
+
+## What gets scrubbed
+
+In priority order (highest leak risk first):
+
+1. **The PR title and body draft** (composed in Phase 6, scrubbed before display in Phase 6's checkpoint)
+2. **The per-run plan doc body** at `$PRESS_MANUSCRIPTS/<slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>.md` — this is the artifact reviewers may follow links to from the PR body's Evidence section
+3. **The deferred-findings list** at `$PRESS_MANUSCRIPTS/<slug>/<run-id>/proofs/<timestamp>-amend-<cli-name>-deferred.md` — same audience
+4. **Any test fixtures or example outputs** newly added to `$CLI_DIR` (defense-in-depth — the agent should not have added PII here, but verify)
+
+For each target, copy to `<path>.pre-pii-scrub` BEFORE scrubbing so the user can audit what changed.
+
+## Layer 1: Credentials (reuse retro patterns)
+
+Run the credential-pattern scan from `skills/printing-press-retro/references/secret-scrubbing.md` — it covers Stripe keys, GitHub PATs/OAuth, bearer tokens, generic API keys, AWS access keys, etc. Point at the same regex set; do not duplicate the patterns here so both skills evolve together.
+
+For Phase 5's purposes, the credential scan's redaction tags (`<REDACTED:bearer-token>`, etc.) become the shape-preserving tokens for credential entities. Same surface, same tag.
+
+**Critical addition for amend** that retro's patterns don't cover by default:
+- `Authorization: Bearer ...` headers in hand-rolled API payloads quoted from the session transcript. Retro's `bearer-token` pattern catches the value but only when the prefix is exactly `Bearer ` — verify amend's evidence quotes also normalize to that shape, OR add a broader `Authorization: <scheme> <opaque>` regex specific to amend.
+- `Cookie: ...` headers from session-replay payloads. These often contain session IDs that uniquely identify the user even if not technically secret.
+- `X-API-Key:` and similar header-based auth shapes.
+
+## Layer 2: Entities (companies, persons, custom stop-list)
+
+Read the user's stop-list at `~/.printing-press/amend-config.yaml`. If the file doesn't exist, create a default with a starter list and a comment explaining the format:
+
+```yaml
+# ~/.printing-press/amend-config.yaml
+# User-maintained stop-list for /printing-press-amend's PII scrub.
+# Add company and person names that should be replaced with shape-preserving
+# tokens before any artifact leaves the local machine.
+
+stoplist:
+  companies:
+    # - "Esper Labs"
+    # - "Acme Corp"
+  people:
+    # - "Matt Van Horn"
+    # - "Trevin Chow"
+  emails:
+    # Domain-level scrubbing — any email at this domain becomes <email-N>
+    # - "esperlabs.ai"
+    # - "company.com"
+
+# Behavior knobs
+behavior:
+  # When true, also flag capitalized non-stop-listed strings for user review
+  # before the PR draft is shown (defense against first-mention leaks).
+  prompt_unrecognized_capitalized: true
+```
+
+When the file exists, read it. Validate file mode (warn if world-writable; abort if owned by another user — symlink attack surface).
+
+For each artifact, replace stop-listed values with shape-preserving tokens:
+
+| Original shape | Token |
+|---|---|
+| Company name | `<company-1>`, `<company-2>`, ... |
+| Person name | `<person-1>`, `<person-2>`, ... |
+| Email address | `<email-1>`, `<email-2>`, ... |
+
+Same source value gets the same token across the run; distinct sources get distinct tokens. Track the mapping in a per-run scrub report (NOT committed; written to `$PRESS_MANUSCRIPTS/<slug>/<run-id>/scrub-report.json` for the user's audit).
+
+## Layer 3: Defense against first-mention leaks
+
+The stop-list is by definition incomplete on first encounter with a new entity. To catch this:
+
+1. After Layers 1+2, walk each artifact again and find capitalized multi-word phrases that look like proper nouns (e.g. matches `\b[A-Z][a-z]+ [A-Z][a-z]+\b`) and were NOT replaced by Layer 2.
+2. Filter out a known-safe allowlist (`GitHub`, `Slack`, `Linear`, `Claude`, `Anthropic`, common HTTP-shape words, the API/CLI vendor name, etc.).
+3. Surface remaining candidates inline before the Phase 6 PR-draft display:
+
+   > "Found unrecognized capitalized phrase: `Esper Labs` (appears 3 times in plan doc, 1 time in PR body draft). Add to stop-list and scrub, or accept as legitimate?"
+
+   Options: scrub (add to stop-list), accept (don't scrub), show context (display surrounding lines).
+
+This isn't perfect — uncapitalized PII (lowercase email handles, internal codenames) still slips through — but it catches the most common failure mode without forcing the user to maintain an exhaustive stop-list.
+
+## Defense-in-depth: Go source check
+
+Walk every `*.go` file in `$CLI_DIR` and scan for stop-list matches. If any match is found, treat as a hard error:
+
+> "BLOCKING: PII pattern matched Go source at `<file>:<line>`. The skill should not have introduced PII into Go source — please review the diff in `$CLI_DIR/<file>` and resolve before continuing. The patch will not proceed to Phase 6 until this clears."
+
+Do NOT auto-modify Go source. Pause and require user resolution.
+
+## Output
+
+Phase 5 emits to Phase 6:
+
+```yaml
+scrub_report_path: $PRESS_MANUSCRIPTS/<slug>/<run-id>/scrub-report.json
+artifacts_scrubbed:
+  - path: <plan-doc-path>
+    tokens_replaced: 7
+    backup: <plan-doc-path>.pre-pii-scrub
+  - ...
+unrecognized_phrases: []           # may be present if user accepted some
+go_source_check: clean             # or [list of blocking matches]
+```
+
+If `go_source_check` is non-empty, Phase 6 cannot proceed.
diff --git a/skills/printing-press-amend/references/transcript-parsing.md b/skills/printing-press-amend/references/transcript-parsing.md
new file mode 100644
index 00000000..cf66a810
--- /dev/null
+++ b/skills/printing-press-amend/references/transcript-parsing.md
@@ -0,0 +1,108 @@
+# Transcript Parsing — Friction Capture for `/printing-press-amend`
+
+**Scope:** This reference applies when `MODE=dogfood` (Phase 0 detected a session-friction invocation) or when running the dogfood half of `MODE=both`. For direct-input mode (`MODE=direct`), see `direct-input-parsing.md` instead — that mode parses the user's prompt and never touches the transcript.
+
+This reference is loaded by Phase 1's `### 1a. Dogfood mode` sub-section of `printing-press-amend`. It defines how the agent reads a Claude Code session transcript and extracts friction signals tied to a specific printed CLI invocation.
+
+## Where the active session transcript lives
+
+Claude Code stores per-session transcripts as JSONL files under `~/.claude/projects/<project-dir-slug>/<session-uuid>.jsonl`. The slug is derived from the working directory path (slashes replaced with `-`).
+
+Resolution order (use the first that resolves to a readable file):
+
+1. **Skill argument or environment** — if the user passed an explicit transcript path, use it.
+2. **Active session via working dir** — derive `<project-dir-slug>` from `pwd -P` (replace `/` with `-`, strip leading `-`), then list `~/.claude/projects/<slug>/*.jsonl` and pick the most-recently-modified. This is the heuristic; it can be wrong when multiple Claude Code panes are running in the same dir, so confirm with the user.
+3. **Fallback** — list `~/.claude/projects/` directories sorted by mtime, list each one's `*.jsonl` files, pick the most-recently-modified across all. This catches the case where the user invokes `/printing-press-amend` from a different working directory than the session was started in.
+
+After picking a candidate file, ALWAYS show the user the resolved path with `AskUserQuestion`:
+
+> "Detected active session at `<path>` (modified <relative-time>). Mine this session for friction, or pick a different transcript?"
+
+Options:
+- Use this transcript (recommended)
+- Pick a different file (drops into a list of recent JSONL files under `~/.claude/projects/`)
+- Cancel
+
+The confirmation is non-optional — wrong-file selection ingests friction from the wrong session and ships PRs for bugs the user never hit.
+
+## Signal extraction taxonomy
+
+The transcript is line-delimited JSON; each line is a turn in the conversation. Walk the file and extract these signal categories. Each signal carries: `timestamp` (from the turn), `category` (one of below), `evidence` (the verbatim quote that triggered it), and `target_cli` (when the signal references a specific `<slug>-pp-cli` invocation).
+
+| Category | Signal | Bug or feature? |
+|---|---|---|
+| **Non-zero exit code** | A `tool_result` block whose stderr indicates a non-zero exit on a `<cli>-pp-cli` invocation | Bug |
+| **Error message** | Lines like `Error: ...`, `failed:`, `panic:`, `HTTP 4xx`, `HTTP 5xx` returned from the CLI | Bug (usually) |
+| **Hand-rolled API payload** | Bash commands that POST/PUT directly to a URL (e.g. `curl -X POST https://api.example.com/...` or scripted JSON construction) instead of calling the CLI | Feature (the CLI doesn't expose what the user needed) |
+| **Retry-after-failure** | The same command run ≥ 2 times in a row with similar args, separated by manual edits or tool tweaks | Bug or feature (look at what changed) |
+| **Hand-rolled workaround comment** | Agent prose saying "X doesn't exist", "X returns 400", "I had to manually...", "going around the CLI", "no built-in for ..." | Feature when "doesn't exist", bug when "returns wrong" |
+| **Missing-flag reference** | Agent text mentioning a flag it tried that the CLI didn't accept, e.g. "tried --type sent but it's rejected" | Feature (missing flag) |
+| **Silent-null returns** | A CLI returns `data: null` or empty JSON when the user clearly expected content; agent commentary acknowledges the unexpected emptiness | Bug |
+| **Auth confusion** | Agent text mentioning expired tokens, refresh failures, "need to re-auth", confusing `auth status` output | Bug (poor error surfacing) |
+
+### Bug vs feature classification rubric
+
+For each signal, choose `bug` or `feature` with a one-line rationale:
+
+- **Bug** = the CLI behavior is *wrong* given what the CLI claims to do (broken endpoint, wrong return shape, error masked, contradicting `--help`).
+- **Feature** = the CLI behavior is *missing* — the user wanted to do something the CLI doesn't expose.
+
+When the same signal could be either (e.g. silent-null), prefer `feature` if the workaround was "construct the API call yourself" and `bug` if the workaround was "retry with different args".
+
+The classification is the agent's best read; the user confirms or overrides at the U4 scope checkpoint.
+
+## Auto-detect target CLI
+
+After extracting signals, count occurrences of each `<slug>-pp-cli` mentioned. The most-touched CLI is the proposed default target. When ties exist or the top two are close (within 1 mention of each other), present a small `AskUserQuestion` with the candidates:
+
+> "Which CLI is this patch for?"
+>
+> 1. `<slug-A>-pp-cli` (8 friction signals)
+> 2. `<slug-B>-pp-cli` (7 friction signals)
+> 3. Other (paste a CLI name)
+
+When only one CLI was touched, default to it but still confirm:
+
+> "Detected target: `<slug>-pp-cli` (12 friction signals). Proceed?"
+
+When the user explicitly passed a target as the skill argument, skip auto-detect entirely and use what they passed.
+
+### Path resolution for the chosen target
+
+Accept any of:
+- short name: `superhuman` → `~/printing-press/library/superhuman/`
+- full name: `superhuman-pp-cli` → strip `-pp-cli`, resolve as short name
+- absolute path: `~/printing-press/library/superhuman` → use as-is
+
+If the resolved path doesn't exist locally, search `~/printing-press-library/library/*/` for a directory whose name matches the slug, and fall back to that as the target. The skill operates on the managed clone (per the Pre-Implementation Decisions in the plan), so the local-library path is informational; the actual edits land in the managed clone created in U7.
+
+## Edge cases
+
+- **Empty / unreadable transcript** — emit "no active session transcript found at `<path>`; pass an explicit `<cli-name-or-path>` argument and re-run, or use `--transcript <path>` to point at a saved session" and exit cleanly.
+- **Transcript with zero `<slug>-pp-cli` invocations** — emit "no `<slug>-pp-cli` invocations found in this session; if you dogfooded a CLI in a different session, point me at that transcript" and exit.
+- **Transcript references a CLI that's not in the public library** — emit a warning and ask the user to confirm; the CLI may be local-only (pre-publish) or under a different slug.
+- **Signal extraction returns < 2 candidates** — proceed but note in the user-facing summary that the signal yield was low; the user may want to resume after more dogfooding.
+
+## Output shape
+
+Phase 1 emits a structured finding list to the next phase:
+
+```yaml
+target_cli: superhuman-pp-cli
+target_dir: ~/printing-press/library/superhuman   # may be informational; managed-clone path resolved later
+target_category: productivity                      # resolved from the public library, used by U7
+findings:
+  - id: F1
+    category: missing-folder-coverage
+    classification: feature
+    rationale: "User tried --type sent but only inbox/draft/etc. allowed"
+    evidence: "threads list --type sent → Error: invalid value for --type"
+  - id: F2
+    category: hand-rolled-payload
+    classification: feature
+    rationale: "drafts new doesn't exist; user POST'd userdata.writeMessage directly"
+    evidence: "curl ... -d '{\"messageId\": ..., \"writes\": [...]}'"
+  - ...
+```
+
+This list flows into U3 (cross-reference + stale-binary) and then U4 (scope confirmation).

← c10d0b55 fix(cli): move extra_commands SKILL.md block to its own top-  ·  back to Cli Printing Press  ·  fix(skills): add NULL-safe SQL scan guidance to Phase 3 stor 7396d66f →