[object Object]

← back to Cli Printing Press

Document publish PR novel commands and body-file usage (#808)

296093c4fc02a7c1b774addb9150cd851153fc32 · 2026-05-09 10:32:31 -0700 · Trevin Chow

Files touched

Diff

commit 296093c4fc02a7c1b774addb9150cd851153fc32
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Sat May 9 10:32:31 2026 -0700

    Document publish PR novel commands and body-file usage (#808)
---
 internal/pipeline/contracts_test.go    | 24 +++++++++++
 skills/printing-press-publish/SKILL.md | 75 ++++++++++++++++++++++++++++++++--
 2 files changed, 95 insertions(+), 4 deletions(-)

diff --git a/internal/pipeline/contracts_test.go b/internal/pipeline/contracts_test.go
index 4b5cb846..ed3bf55c 100644
--- a/internal/pipeline/contracts_test.go
+++ b/internal/pipeline/contracts_test.go
@@ -165,6 +165,30 @@ func TestPolishSkillHardGatesPublishValidate(t *testing.T) {
 	assert.Contains(t, skill, "ship cannot fire while publish validate fails")
 }
 
+func TestPublishSkillPRBodyIncludesStableNovelCommands(t *testing.T) {
+	skill := readContractFile(t, filepath.Join("..", "..", "skills", "printing-press-publish", "SKILL.md"))
+
+	snapshotState := strings.Index(skill, "PREEXISTING_MERGED_PATHS=$(ls")
+	packageCopy := strings.Index(skill, `cp -r "$STAGING_DIR/library/<category>/<cli-name>"`)
+	require.NotEqual(t, -1, snapshotState)
+	require.NotEqual(t, -1, packageCopy)
+	assert.Less(t, snapshotState, packageCopy)
+
+	assert.Contains(t, skill, "The manifest's `novel_features` array from the packaged CLI after Step 6")
+	assert.Contains(t, skill, "Do not derive\nthis section from README prose, SKILL prose, root help, or memory of the run")
+	assert.Contains(t, skill, "Step 6 has already copied the\nnew package into that path")
+	assert.Contains(t, skill, "PREEXISTING_MERGED_COLLISION=true")
+	assert.Contains(t, skill, "### Publication Path")
+	assert.Contains(t, skill, "### Novel Commands")
+	assert.Contains(t, skill, "| Command | Name | Description |")
+	assert.Contains(t, skill, "`New print`")
+	assert.Contains(t, skill, "`Update existing PR #<N>`")
+	assert.Contains(t, skill, "`Reprint/replace`")
+	assert.Contains(t, skill, "`Alongside print`")
+	assert.Contains(t, skill, "--body-file \"$PR_BODY_FILE\"")
+	assert.NotContains(t, skill, "--body \"<constructed PR body>\"")
+}
+
 func TestREADMEOutputContract(t *testing.T) {
 	readme := readContractFile(t, filepath.Join("..", "..", "README.md"))
 
diff --git a/skills/printing-press-publish/SKILL.md b/skills/printing-press-publish/SKILL.md
index 8d365f56..c71f3c3d 100644
--- a/skills/printing-press-publish/SKILL.md
+++ b/skills/printing-press-publish/SKILL.md
@@ -359,6 +359,21 @@ If there are uncommitted changes, ask the user via AskUserQuestion:
 
 If reset, run `git checkout -- . && git clean -fd`.
 
+### Pre-package publication-state snapshot
+
+Before Step 6 mutates the managed clone, record whether this API slug already
+exists in the public library tree. Step 6 removes and replaces
+`library/*/<api-slug>`, so any collision or publication-path decision made
+after packaging must use this pre-package snapshot, not a fresh `ls`.
+
+```bash
+PREEXISTING_MERGED_PATHS=$(ls "$PUBLISH_REPO_DIR/library"/*/"<api-slug>" 2>/dev/null || true)
+PREEXISTING_MERGED_COLLISION=false
+if [ -n "$PREEXISTING_MERGED_PATHS" ]; then
+  PREEXISTING_MERGED_COLLISION=true
+fi
+```
+
 ## Step 6: Package
 
 Read `$PUBLISH_CONFIG` to get `module_path_base`. Construct the full module path using the API slug (not the CLI name):
@@ -448,10 +463,15 @@ Run these checks in sequence:
 **1. Check merged CLIs in managed clone:**
 
 ```bash
-ls "$PUBLISH_REPO_DIR/library"/*/"<api-slug>" 2>/dev/null
+MERGED_COLLISION="$PREEXISTING_MERGED_COLLISION"
+MERGED_PATHS="$PREEXISTING_MERGED_PATHS"
 ```
 
-If found, record `MERGED_COLLISION=true` and note the category path.
+Use the pre-package snapshot from Step 5. Do not re-run `ls
+"$PUBLISH_REPO_DIR/library"/*/"<api-slug>"` here: Step 6 has already copied the
+new package into that path, so a fresh `ls` would make every new print look like
+a merged collision. If `MERGED_COLLISION=true`, note the category path from
+`MERGED_PATHS`.
 
 **2. Check all open PRs (any author):**
 
@@ -712,12 +732,36 @@ Read `access` and `gh_user` from `$PUBLISH_CONFIG`. These determine how `gh pr c
 
 Build the PR description from:
 - The manifest (`description`, `api_name`, `category`, `printing_press_version`, `spec_url`)
+- The manifest's `novel_features` array from the packaged CLI after Step 6
 - The `help_output` captured in Step 4
 - The CLI's README (first 2-3 paragraphs, or note that README is missing)
 - Links to `.manuscripts/<run-id>/research/` and `.manuscripts/<run-id>/proofs/` within the PR branch
 - The validation results from Step 4
 - A Gaps section listing any missing manifest fields
 
+Read `novel_features` from
+`$PUBLISH_REPO_DIR/library/<category>/<api-slug>/.printing-press.json` after
+packaging and mirror regeneration. Preserve the manifest order. Do not derive
+this section from README prose, SKILL prose, root help, or memory of the run:
+those surfaces may be summarized or hand-edited, while the packaged manifest is
+the publish-time source of truth. For each entry, include the command, name, and
+description. If the array is empty, write `No novel commands recorded in
+.printing-press.json.` and include the missing field in **Gaps**; do not omit the
+section.
+
+Also include a publication-path line so new prints, reprints, PR updates, and
+collision renames are distinguishable:
+- `New print` — no merged CLI and no existing PR matched this slug.
+- `Update existing PR #<N>` — this publish refreshes an open PR.
+- `Reprint/replace` — a merged library CLI existed before this publish and the
+  selected path replaces it. This must be based on
+  `PREEXISTING_MERGED_COLLISION=true`, not on the post-package tree.
+- `Alongside print` — this publish renamed the API slug to avoid a collision;
+  include the original slug.
+If `/printing-press-reprint` handed off a degraded reprint with no prior
+public-library source, use `New print` and add the degraded-reprint note only if
+that context is available from the handoff.
+
 **MANDATORY: Before constructing the PR body, scrub all workspace PII.** The library
 repo is public. Scan any live test results, acceptance data, or manuscript excerpts
 for organization names, team member names, and email addresses. Replace with generic
@@ -725,6 +769,11 @@ descriptions ("the workspace", "5 team members", "12 users"). Team keys (e.g., "
 are OK but org names (e.g., "Acme Corp") are not. See `references/secret-protection.md`
 in the printing-press skill for the full policy.
 
+Write the constructed PR body to a temporary Markdown file and pass it with
+`--body-file`. Do this for both PR creation and PR updates. Do not inline the
+body in a shell argument; large fenced help output, Markdown tables, and
+backticks are too easy to mangle.
+
 **PR description template:**
 
 ```markdown
@@ -737,6 +786,10 @@ in the printing-press skill for the full policy.
 **API:** <api_name> | **Category:** <category> | **Press version:** <printing_press_version>
 **Spec:** <spec_url or "Not specified">
 
+### Publication Path
+
+<New print | Update existing PR #N | Reprint/replace | Alongside print from <original-api-slug>>
+
 ### CLI Shape
 
 \`\`\`bash
@@ -744,6 +797,12 @@ $ <cli-name> --help
 <help_output from validation>
 \`\`\`
 
+### Novel Commands
+
+| Command | Name | Description |
+|---------|------|-------------|
+| `<command>` | <name> | <description> |
+
 ### What This CLI Does
 
 <First 2-3 paragraphs from README.md in the CLI directory, or "README not found">
@@ -776,9 +835,12 @@ $ <cli-name> --help
 
 ```bash
 cd "$PUBLISH_REPO_DIR"
+PR_BODY_FILE="$(mktemp)"
+# Write the constructed PR body Markdown to "$PR_BODY_FILE".
 gh pr edit "$EXISTING_PR_NUMBER" \
   --repo mvanhorn/printing-press-library \
-  --body "<constructed PR body>"
+  --body-file "$PR_BODY_FILE"
+rm -f "$PR_BODY_FILE"
 ```
 
 Display the full PR URL: "Updated PR: <EXISTING_PR_URL>" (use the full `https://` URL, not shorthand).
@@ -798,12 +860,17 @@ else
   PR_HEAD_REF="feat/<api-slug>"
 fi
 
+PR_BODY_FILE="$(mktemp)"
+# Write the constructed PR body Markdown to "$PR_BODY_FILE".
+
 gh pr create \
   --repo mvanhorn/printing-press-library \
   --head "$PR_HEAD_REF" \
   --base main \
   --title "feat(<api-slug>): add <api-slug>" \
-  --body "<constructed PR body>"
+  --body-file "$PR_BODY_FILE"
+
+rm -f "$PR_BODY_FILE"
 ```
 
 Display the full PR URL (e.g., `https://github.com/mvanhorn/printing-press-library/pull/10`), not the shorthand `org/repo#N` format. The full URL is clickable in all terminals and contexts.

← 079cf52b docs(cli): clarify catalog entry inclusion rules (#807)  ·  back to Cli Printing Press  ·  chore(ci): Gate main CI by changed file scope and add a skil 6138e88e →