← back to Cli Printing Press
feat(skills): retro files parent issue + per-WU sub-issues (#555)
aae2af9fed95d1c31d7884e62215179b6440841c · 2026-05-03 19:39:17 -0700 · Trevin Chow
Restructures /printing-press-retro to file a parent issue plus one sub-issue
per work unit (linked via GitHub sub-issues REST API) instead of a single
monster issue, so each WU has its own trackable open/closed/assignee state.
Adds priority:P1/P2/P3 and comp:* labels (closed taxonomies, no agent
invention) to enable cross-retro filtering, and extends Phase 3 Step D to
emit a Related prior retros block per finding so contradictions and
alignments auto-cross-link in GitHub timelines. Threshold: 1 WU keeps the
single-issue shape with new labels; 2+ WUs use parent + sub-issues with
per-WU failure tracking (create-failed / link-failed / ok) surfaced in the
parent's WU table and the final summary.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
M skills/printing-press-retro/SKILL.mdM skills/printing-press-retro/references/issue-template.md
Diff
commit aae2af9fed95d1c31d7884e62215179b6440841c
Author: Trevin Chow <trevin@trevinchow.com>
Date: Sun May 3 19:39:17 2026 -0700
feat(skills): retro files parent issue + per-WU sub-issues (#555)
Restructures /printing-press-retro to file a parent issue plus one sub-issue
per work unit (linked via GitHub sub-issues REST API) instead of a single
monster issue, so each WU has its own trackable open/closed/assignee state.
Adds priority:P1/P2/P3 and comp:* labels (closed taxonomies, no agent
invention) to enable cross-retro filtering, and extends Phase 3 Step D to
emit a Related prior retros block per finding so contradictions and
alignments auto-cross-link in GitHub timelines. Threshold: 1 WU keeps the
single-issue shape with new labels; 2+ WUs use parent + sub-issues with
per-WU failure tracking (create-failed / link-failed / ok) surfaced in the
parent's WU table and the final summary.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
skills/printing-press-retro/SKILL.md | 157 +++++-
.../references/issue-template.md | 571 +++++++++++++++++----
2 files changed, 613 insertions(+), 115 deletions(-)
diff --git a/skills/printing-press-retro/SKILL.md b/skills/printing-press-retro/SKILL.md
index 06f878d6..675bc6b9 100644
--- a/skills/printing-press-retro/SKILL.md
+++ b/skills/printing-press-retro/SKILL.md
@@ -415,14 +415,21 @@ the Do/Skip tables, they go on the dropped-candidates list with the reason.
**4. Where in the Printing Press does this originate?**
-| Component | Path |
-|-----------|------|
-| Generator templates | `internal/generator/` |
-| Spec parser | `internal/spec/` |
-| OpenAPI parser | `internal/openapi/` |
-| Catalog | `catalog/` |
-| Main skill | `skills/printing-press/SKILL.md` |
-| Verify/dogfood/scorecard | CLI commands |
+Pick exactly one component. The `slug` column drives the `comp:<slug>` label
+applied to the WU sub-issue when filed (Phase 6), which is how agents filter
+related WUs across retros (`gh issue list --label comp:<slug>`).
+
+| Component | Slug | Path |
+|-----------|------|------|
+| Generator templates | `generator` | `internal/generator/` |
+| Spec parser | `spec-parser` | `internal/spec/` |
+| OpenAPI parser | `openapi-parser` | `internal/openapi/` |
+| Catalog | `catalog` | `catalog/` |
+| Main skill | `skill` | `skills/printing-press/SKILL.md` |
+| Verify/dogfood/scorecard | `scorer` | CLI commands |
+
+If a finding genuinely spans two components, pick the one where the durable
+fix lands. Don't multi-label.
**5. Blast radius and fallback cost — should the Printing Press handle this?**
@@ -454,6 +461,25 @@ to P3 with a "raised N times, still not justified" annotation, or reframe the fi
into a smaller incremental fix that addresses part of the friction. Recurrence at the
same priority is a triage failure, not stronger evidence.
+**Capture matched prior retros.** When the search returns hits, record each as a
+structured tuple — retro CLI name, retro file path (or GitHub issue number if the
+retro file's frontmatter contains one), and a one-word classification:
+
+- `aligned` — the prior retro proposed the same fix direction. Strengthens the case;
+ reference it in Step F.
+- `contradicts` — the prior retro proposed an *opposing* fix or chose a different
+ default. Surface this explicitly: a maintainer reading the new finding must see
+ the disagreement. State in one sentence why this retro reaches a different
+ conclusion (e.g., "prior retro saw single-paginator APIs; this one saw an
+ always-paginated API where the prior default would break").
+- `extends` — the prior retro raised an adjacent finding in the same component
+ area but a different specific fix. Useful context, doesn't change the case.
+
+These tuples flow forward into the per-finding template ("Related prior retros")
+and into the issue body when the finding becomes a sub-issue. GitHub auto-cross-
+links any `#N` issue number you write, so contradictions and alignments will show
+up in both retro timelines without further action.
+
**Step E: Assess fallback cost.** How reliably will Claude catch and fix this across every
future API? A "simple" edit Claude forgets 30% of the time means 30% ship with the defect.
@@ -563,6 +589,9 @@ Write the full retro document using this template:
- **Durable fix:** ...
- **Test:** How to verify (positive + negative)
- **Evidence:** Session moment that surfaced this
+- **Related prior retros:** *(from Phase 3 Step D; "None" if no matches)*
+ - `<api-slug>` retro #<issue-num-if-known> — `aligned` / `contradicts` / `extends`. <one-sentence note on what changed or what's shared>
+ - ...
## Prioritized Improvements
@@ -632,6 +661,11 @@ For each "Do" finding or group of related findings:
```markdown
### WU-1: <Title> (from F1, F3, ...)
+- **Priority:** P1 / P2 / P3 *(max priority among absorbed findings — P1 if any
+ absorbed finding is P1, else P2 if any is P2, else P3)*
+- **Component:** generator / openapi-parser / spec-parser / scorer / skill / catalog
+ *(must match one of the six fixed component slugs; drives the `comp:*` label
+ applied to the sub-issue when filed)*
- **Goal:** One sentence describing the outcome
- **Target:** <component and area, e.g., "Generator templates in internal/generator/">
- **Acceptance criteria:**
@@ -642,6 +676,12 @@ For each "Do" finding or group of related findings:
- **Complexity:** small / medium / large
```
+The six fixed component slugs are: `generator` (`internal/generator/`),
+`openapi-parser` (`internal/openapi/`), `spec-parser` (`internal/spec/`),
+`scorer` (verify / dogfood / scorecard), `skill` (`skills/printing-press/SKILL.md`),
+`catalog` (`catalog/`). If a WU genuinely spans two, pick the **primary** one — the
+component where the durable fix will land. Pick exactly one; don't multi-label.
+
**If running from inside the printing-press repo (`IN_REPO=true`):**
Resolve target file paths using Glob and Grep tool invocations on `$REPO_ROOT` to
make work units more precise. E.g., use Glob to find `internal/generator/*.go` files,
@@ -694,14 +734,20 @@ This is both the review target and the upload source.
*This step only runs if the Phase 5.6 issue gate passed (there are Printing Press findings to act on).*
Before uploading anything, show the user a friendly summary and ask for confirmation
-via `AskUserQuestion`.
+via `AskUserQuestion`. Tailor the wording to whether this is single-issue or
+parent-with-sub-issues mode (see `references/issue-template.md` Step 2 for the
+`WU_COUNT` dispatch).
+
+For **2+ WUs** (parent-with-sub-issues mode):
> **Ready to submit your retro.**
>
-> Here's what will happen:
+> Here's what will happen on [mvanhorn/cli-printing-press](https://github.com/mvanhorn/cli-printing-press):
>
-> - A GitHub issue will be created on [mvanhorn/cli-printing-press](https://github.com/mvanhorn/cli-printing-press) with your **<N> findings** and **<M> work units**
-> - Scrubbed artifact zips will be uploaded to catbox.moe and linked from the issue:
+> - **1 parent issue** with retro context (session stats, findings tables, artifact links)
+> - **<M> sub-issues** — one per work unit, each with its own priority, component, and finding details, so each fix can be tracked, assigned, and closed independently
+> - Labels: `retro`, `retro-parent` (parent only), `priority:P1`/`priority:P2`/`priority:P3` and `comp:*` (sub-issues), so future agents can filter all retro WUs by area or priority
+> - Scrubbed artifact zips uploaded to catbox.moe and linked from the parent:
> - **Manuscripts** (<size>) — research brief, shipcheck proof, build logs
> - **CLI source** (<size>) — the generated Go code (no binary, no vendor/) *(omit if not available)*
>
@@ -710,6 +756,22 @@ via `AskUserQuestion`.
>
> Everything is staged at `<$STAGING_DIR>` if you'd like to inspect the files first.
+For **1 WU** (single-issue mode):
+
+> **Ready to submit your retro.**
+>
+> Here's what will happen on [mvanhorn/cli-printing-press](https://github.com/mvanhorn/cli-printing-press):
+>
+> - **1 GitHub issue** with the retro context, work unit, and absorbed findings inline
+> - Labels: `retro`, `priority:P<n>`, `comp:<slug>`, so future agents can filter related work across retros
+> - Scrubbed artifact zips uploaded to catbox.moe and linked from the issue:
+> - **Manuscripts** (<size>) — research brief, shipcheck proof, build logs
+> - **CLI source** (<size>) — the generated Go code *(omit if not available)*
+>
+> **Top finding:** <one-sentence summary>
+>
+> Everything is staged at `<$STAGING_DIR>` if you'd like to inspect the files first.
+
Options:
1. **Submit** — upload artifacts and create the issue
2. **Let me review the files first** — I'll check the staging folder, then come back
@@ -727,15 +789,32 @@ folder, then jump to Step 6.
Run artifact-packaging.md Step 5 (the catbox upload) using the zips already in
`$STAGING_DIR`. This produces `$MANUSCRIPTS_URL` and `$CLI_SOURCE_URL`.
-### Step 4: Create GitHub issue
+### Step 4: Create GitHub issue(s)
+
+Read and apply [references/issue-template.md](references/issue-template.md). It
+covers:
-Read and apply [references/issue-template.md](references/issue-template.md).
+1. **Step 1: Ensure labels exist** — create-only `gh label create` safety net
+ for the 9 canonical labels (6 `comp:*` + `priority:P1`/`priority:P2`/`priority:P3`)
+ plus the `retro` / `retro-parent` markers. Run once per invocation. Never
+ edits existing labels.
+2. **Step 2: Dispatch on WU count** — `single` mode (1 WU, inline body) or
+ `parent-with-subs` mode (2+ WUs, parent issue + one sub-issue per WU linked
+ via the GitHub sub-issues REST API).
+3. The full body templates and the `gh api` calls for sub-issue linkage.
-Build the issue body from the retro findings (distilled summary — not the full retro
-document). Create the issue via `gh issue create --repo mvanhorn/cli-printing-press`.
+Each sub-issue carries its own `priority:P<n>` priority label and `comp:<slug>` component
+label. This is what enables `gh issue list --label comp:openapi-parser` to surface
+every retro WU in that area across every retro — the cross-retro discovery that
+single-issue retros couldn't support.
-If `gh` is not authenticated or issue creation fails, follow the graceful degradation
-path in the issue-template reference: save locally and print manual filing instructions.
+Each WU sub-issue body also includes the **Related prior retros** block populated
+in Phase 3 Step D, so contradictions and alignments with earlier retros are
+visible and auto-cross-linked in GitHub timelines.
+
+If `gh` is not authenticated, the sub-issues REST endpoint is unavailable, or any
+issue creation fails, follow the graceful degradation path in the issue-template
+reference: save locally and print manual filing instructions.
### Step 5: Local scratch copy
@@ -751,18 +830,50 @@ fi
### Step 6: Present results
-After the issue is created, show the user:
+After the issue(s) are created, show the user a summary tailored to `$ISSUE_MODE`.
+
+For **`parent-with-subs`** (`WU_COUNT >= 2`):
+
+> **Retro submitted!**
+>
+> Parent issue: <full $PARENT_URL>
+> Sub-issues (<M> WUs, P1 → P3 order):
+> - [P1] WU-1: <title> — <full WU-1 URL>
+> - [P1] WU-2: <title> — <full WU-2 URL>
+> - [P2] WU-3: <title> — <full WU-3 URL>
+> - ...
+>
+> Found <N> findings across <M> work units. Sub-issues are tagged with
+> `comp:<slug>` and `priority:P<n>` labels — agents can filter related WUs
+> across retros with `gh issue list --label comp:<slug>` or
+> `gh issue list --label priority:P1`.
+> *(if artifacts uploaded)* Artifacts: [manuscripts](<URL>) · [CLI source](<URL>)
+> Local copy: <$RETRO_SCRATCH_PATH>
+
+If `$FAILED_WUS` is non-empty (set by `references/issue-template.md` Step P3),
+append a warning block before the closing line:
+
+> ⚠️ Some WUs need attention:
+> - WU-2 (...) — issue creation failed
+> - WU-4 (#1234) — sub-issue link failed (cross-link in body remains)
+>
+> The parent issue's "Work Units" table marks failed creations as
+> "**FAILED — file manually**" so the gap is visible. Re-run the skill or file
+> the missing sub-issue(s) manually using the retro doc at <$RETRO_SCRATCH_PATH>.
+
+For **`single`** (`WU_COUNT == 1`):
> **Retro submitted!**
>
-> Issue: <full https:// URL>
+> Issue: <full $ISSUE_URL>
>
-> Found <N> findings across <M> work units.
+> 1 work unit (P<n>, comp:<slug>) absorbing <N> findings.
> *(if artifacts uploaded)* Artifacts: [manuscripts](<URL>) · [CLI source](<URL>)
> Local copy: <$RETRO_SCRATCH_PATH>
-If the issue wasn't created (user chose local-only, or gh failed), show the local
-save paths instead.
+If issue creation wasn't completed (user chose local-only, or gh failed), show
+the local save paths and the manual filing instructions printed by the
+issue-template fallback path.
### Step 7: Clean up staging folder
diff --git a/skills/printing-press-retro/references/issue-template.md b/skills/printing-press-retro/references/issue-template.md
index 2abf866e..7d1b2f8d 100644
--- a/skills/printing-press-retro/references/issue-template.md
+++ b/skills/printing-press-retro/references/issue-template.md
@@ -1,25 +1,256 @@
# GitHub Issue Template
-Read this file during Phase 6 when creating the GitHub issue from retro findings.
+Read this file during Phase 6 when creating GitHub issue(s) from retro findings.
+
+The shape depends on the work-unit count:
+
+- **0 WUs** — Phase 5.6 already gated this out; no issue gets filed.
+- **1 WU** — single issue; the WU is inlined in the issue body, no parent/child
+ hierarchy. Labels are still applied so cross-retro filtering still works.
+- **2+ WUs** — **parent issue** (retro context, summary tables, artifact links) +
+ **one sub-issue per WU** (full WU details + absorbed findings + prior-retro
+ links). Sub-issues are linked to the parent via the GitHub sub-issues REST
+ API. This is the structural fix for "monster issues that are impossible to
+ track" — each WU gets its own open/closed/assignee state.
## Formatting rules
-**Never use `#N` notation** for finding or work unit numbers in the issue body. GitHub
-auto-links `#1`, `#2`, etc. as issue/PR references. Use the `F` prefix for findings
-(e.g., `F1`, `F3`) — it's self-describing and avoids the collision.
+**Never use `#N` notation** for finding or work unit numbers in *summary* text where
+GitHub auto-linking would be confusing. Use the `F` prefix for findings (`F1`, `F3`)
+and `WU-` for work units. **Real GitHub issue references in the `Related prior retros`
+block and the parent's "Sub-issues" backfill table are intentional `#N`** — that's
+where we *want* GitHub to auto-link the timelines together.
+
+## Step 1: Ensure labels exist (idempotent, create-only)
+
+Run once per session before creating any issues. The repo is expected to already
+have the canonical label set; this step is a safety net for users running the
+skill against a fresh fork. **Create-only — never edit existing labels** (the
+maintainer may have set custom colors or descriptions; the skill must not
+clobber them).
+
+```bash
+REPO="mvanhorn/cli-printing-press"
+
+ensure_label() {
+ local name="$1" color="$2" desc="$3"
+ # Create only if missing; failure (label exists, no permissions, rate limit)
+ # is non-fatal — issue creation later will fail loudly if a required label is
+ # genuinely absent.
+ gh label create "$name" --repo "$REPO" --color "$color" --description "$desc" 2>/dev/null || true
+}
+
+# Component labels (6) — drive cross-retro discovery (`gh issue list --label comp:<slug>`)
+ensure_label "comp:generator" "5319e7" "Generator templates (internal/generator/)"
+ensure_label "comp:openapi-parser" "5319e7" "OpenAPI parser (internal/openapi/)"
+ensure_label "comp:spec-parser" "5319e7" "Internal spec parser (internal/spec/)"
+ensure_label "comp:scorer" "5319e7" "verify / dogfood / scorecard"
+ensure_label "comp:skill" "5319e7" "skills/printing-press/SKILL.md and related skill instructions"
+ensure_label "comp:catalog" "5319e7" "catalog/ entries"
+
+# Priority labels (3) — duplicate the title prefix to enable label-based filtering.
+ensure_label "priority:P1" "b60205" "Retro priority P1 (high)"
+ensure_label "priority:P2" "d93f0b" "Retro priority P2 (medium)"
+ensure_label "priority:P3" "fbca04" "Retro priority P3 (low)"
+
+# Marker labels
+ensure_label "retro" "0e8a16" "Issue produced by /printing-press-retro"
+ensure_label "retro-parent" "0e8a16" "Parent retro issue with sub-issue WUs"
+```
+
+### Priority labels
+
+Apply `priority:P<n>` matching the WU's internal priority. The label and the
+`[P<n>]` title prefix carry the same value — the label enables
+`gh issue list --label priority:P1` filtering across retros, the title prefix
+gives at-a-glance scanning in issue lists.
+
+## Step 2: Resolve the work-unit count and dispatch
+
+```bash
+WU_COUNT="${#WORK_UNITS[@]}" # populated from Phase 5.5
+
+if [ "$WU_COUNT" -eq 0 ]; then
+ echo "Phase 5.6 should have gated this out. Aborting."
+ exit 1
+elif [ "$WU_COUNT" -eq 1 ]; then
+ ISSUE_MODE="single"
+else
+ ISSUE_MODE="parent-with-subs"
+fi
+```
+
+## Single-issue mode (`WU_COUNT == 1`)
+
+### Title
+
+```
+Retro: <api-display-name> — 1 work unit (P<n>)
+```
+
+Example: `Retro: Cal.com — 1 work unit (P1)`
+
+### Body
+
+```markdown
+## Session Stats
+
+| Metric | Value |
+|--------|-------|
+| API | <api-display-name> |
+| Spec source | <catalog / browser-sniffed / docs / HAR> |
+| Scorecard | <score>/100 (<grade>) |
+| Verify pass rate | <X>% |
+| Fix loops | <N> |
+| Manual code edits | <N> |
+| Features built from scratch | <N> |
+| Triage | <K> candidates → 1 filed / <S> skipped / <X> dropped |
+
+## What the Printing Press Got Right
+
+- <pattern>
+- <pattern>
+
+## Work Unit
+
+### WU-1: <title>
+
+- **Priority:** P<n>
+- **Component:** <comp-slug>
+- **Complexity:** small / medium / large
+- **Goal:** <one sentence>
+- **Target:** <component and area>
+- **Acceptance criteria:**
+ - positive: ...
+ - negative: ...
+- **Scope boundary:** ...
+- **Dependencies:** <other WUs or "None">
+
+### Findings absorbed
+
+#### F<n>: <title> (P<n>, <category>)
+
+- **What happened:** ...
+- **Scorer correct?** ...
+- **Root cause:** ...
+- **Cross-API check:** ...
+- **Frequency:** every / most / subclass:<name> / this-API
+- **Fallback:** ...
+- **Worth a fix?** ...
+- **Inherent or fixable:** ...
+- **Durable fix:** ...
+- **Test:** positive + negative
+- **Evidence:** ...
+- **Related prior retros:**
+ - #<num> (`<api-slug>` retro, `aligned`/`contradicts`/`extends`) — <one-sentence note>
+ - *(or "None" if Phase 3 Step D found no matches)*
+
+*(Repeat for each absorbed finding.)*
+
+## Skipped
+
+| Finding | Title | Why it didn't make it (Step B / Step D / Step G) |
+|---------|-------|--------------------------------------------------|
+| F<n> | ... | ... |
+
+*(Omit if no findings were skipped.)*
+
+## Artifacts
+
+| Artifact | Link |
+|----------|------|
+| Retro document | <$RETRO_DOC_URL or "Upload failed — see below"> |
+| Manuscripts (research + proofs) | <$MANUSCRIPTS_URL or "Upload failed — see below"> |
+| Generated CLI source code | <$CLI_SOURCE_URL or "Upload failed — see below"> |
+
+---
+
+*Generated by `/printing-press-retro` · [CLI Printing Press](https://github.com/mvanhorn/cli-printing-press)*
+```
+
+### Create
+
+```bash
+ISSUE_URL=$(gh issue create \
+ --repo "$REPO" \
+ --title "$ISSUE_TITLE" \
+ --body "$BODY" \
+ --label retro \
+ --label "priority:P${PRIORITY_NUM}" \
+ --label "comp:${COMPONENT_SLUG}")
+```
+
+## Parent-with-sub-issues mode (`WU_COUNT >= 2`)
+
+### Step P1: Build the WU bodies first (in priority order)
+
+Sort WUs by priority: P1 first, then P2, then P3. Within a priority bucket, keep
+the order they appeared in Phase 5.5 (typically by ascending WU number, but the
+skill may have intentionally ordered them by dependency — preserve that).
+
+For each WU, build the sub-issue body:
+
+```markdown
+**Parent retro:** #<PARENT_NUMBER> *(backfilled after parent is created — see Step P3)*
+
+## Work Unit
+
+- **Priority:** P<n>
+- **Component:** <comp-slug>
+- **Complexity:** small / medium / large
+- **Goal:** <one sentence>
+- **Target:** <component and area>
+- **Acceptance criteria:**
+ - positive: ...
+ - negative: ...
+- **Scope boundary:** ...
+- **Dependencies:** <other WU sub-issue numbers, backfilled if known, otherwise "None">
+
+## Findings absorbed
+
+#### F<n>: <title> (P<n>, <category>)
+
+- **What happened:** ...
+- **Scorer correct?** ...
+- **Root cause:** ...
+- **Cross-API check:** ...
+- **Frequency:** every / most / subclass:<name> / this-API
+- **Fallback:** ...
+- **Worth a fix?** ...
+- **Inherent or fixable:** ...
+- **Durable fix:** ...
+- **Test:** positive + negative
+- **Evidence:** ...
+- **Related prior retros:**
+ - #<num> (`<api-slug>` retro, `aligned`/`contradicts`/`extends`) — <one-sentence note>
+ - *(or "None")*
+
+*(Repeat for each absorbed finding.)*
+
+---
+
+*Sub-issue of the [<api-display-name> retro](#<PARENT_NUMBER>) · Generated by `/printing-press-retro`*
+```
-## Issue title format
+Sub-issue title:
```
-Retro: <API display name> — <N> findings, <M> work units
+[P<n>] WU-<m>: <title>
```
-Example: `Retro: Cal.com — 7 findings, 3 work units`
+Examples: `[P1] WU-1: Emit Stripe-Version header from spec`, `[P2] WU-2: Default
+auth scaffold for cookie+CSRF APIs`.
+
+### Step P2: Create the parent issue
+
+Title:
-## Issue body structure
+```
+Retro: <api-display-name> — <N> findings, <M> work units
+```
-Build the issue body using a HEREDOC. The body is a **distilled summary** of the
-retro — not the full retro document. The full retro is linked via catbox artifacts.
+Body — note the explicit placeholder `<!-- WU_TABLE -->`. This is replaced in
+Step P5 once sub-issue URLs are known.
```markdown
## Session Stats
@@ -37,50 +268,47 @@ retro — not the full retro document. The full retro is linked via catbox artif
## What the Printing Press Got Right
-- <pattern to preserve>
-- <pattern to preserve>
+- <pattern>
+- <pattern>
-## P1 — High priority
+## Findings
-| Finding | Title | Component | Frequency | Fix summary |
-|---------|-------|-----------|-----------|-------------|
-| F1 | <one-line title> | <generator/parser/scorer/skill> | <every/most/subclass> | <one-line fix> |
+### P1 — High priority
-## P2 — Medium priority
+| Finding | Title | Component | Frequency | WU |
+|---------|-------|-----------|-----------|-----|
+| F1 | ... | comp:generator | every | WU-1 |
-| Finding | Title | Component | Frequency | Fix summary |
-|---------|-------|-----------|-----------|-------------|
-| F2 | ... | ... | ... | ... |
+### P2 — Medium priority
-## P3 — Low priority
+| Finding | Title | Component | Frequency | WU |
+|---------|-------|-----------|-----------|-----|
+| F2 | ... | comp:openapi-parser | most | WU-2 |
-| Finding | Title | Component | Frequency | Fix summary |
-|---------|-------|-----------|-----------|-------------|
-| F3 | ... | ... | ... | ... |
+### P3 — Low priority
-*Omit empty priority sections. Priority is based on frequency, fallback reliability,
-and complexity. Scorer bugs are ranked alongside other findings.*
+| Finding | Title | Component | Frequency | WU |
+|---------|-------|-----------|-----------|-----|
+| F3 | ... | comp:skill | subclass:browser-sniffed | WU-2 |
+
+*Omit empty priority sections.*
## Skipped
-| Finding | Title | Why it didn't make it (Step B / Step D / Step G) |
-|---------|-------|--------------------------------------------------|
-| F<n> | <one-line title> | <one-line reasoning, naming the Phase 3 step that failed> |
+| Finding | Title | Why it didn't make it |
+|---------|-------|------------------------|
+| F<n> | ... | Step B / Step D / Step G: ... |
-*Findings that survived triage but didn't clear Phase 3. Omit this section if no
-findings were skipped. Drops (rejected at Phase 2.5 triage) are recorded in the
-full retro document linked under Artifacts, not here — they're triage hygiene,
-not maintainer-facing signal.*
+*Omit if empty. Drops (rejected at Phase 2.5 triage) live in the full retro
+document linked under Artifacts — they're triage hygiene, not maintainer signal.*
## Work Units
-Summary table only — full acceptance criteria, scope boundaries, and resolved file
-paths are in the retro document (linked in Artifacts below).
+Each WU is filed as a sub-issue for independent tracking. GitHub renders the
+sub-issue list above the comments; the table below mirrors it for at-a-glance
+reading and search.
-| WU | Title | Component | Findings | Complexity |
-|----|-------|-----------|----------|------------|
-| WU-1 | <title> | <component> | F1, F3 | small / medium / large |
-| WU-2 | ... | ... | ... | ... |
+<!-- WU_TABLE -->
## Artifacts
@@ -90,99 +318,258 @@ paths are in the retro document (linked in Artifacts below).
| Manuscripts (research + proofs) | <$MANUSCRIPTS_URL or "Upload failed — see below"> |
| Generated CLI source code | <$CLI_SOURCE_URL or "Upload failed — see below"> |
-*If uploads failed:* Artifacts could not be uploaded to catbox.moe. The retro author
-has local copies. Ask them to attach manually if needed.
-
---
*Generated by `/printing-press-retro` · [CLI Printing Press](https://github.com/mvanhorn/cli-printing-press)*
```
-## Creating the issue
-
-Check `gh` auth first:
+Create the parent. The parent gets `retro` + `retro-parent` labels and **no
+priority/component labels** — those are per-WU concerns.
```bash
-if ! gh auth status 2>/dev/null; then
- echo "GitHub CLI is not authenticated. Cannot create issue."
- echo "Run 'gh auth login' to authenticate, or file the issue manually."
- # Set flag for graceful degradation
+PARENT_URL=$(gh issue create \
+ --repo "$REPO" \
+ --title "$PARENT_TITLE" \
+ --body "$PARENT_BODY" \
+ --label retro \
+ --label retro-parent)
+
+if ! echo "$PARENT_URL" | grep -q '^https://'; then
+ echo "WARNING: parent issue creation failed. Falling back to manual filing instructions."
GH_AVAILABLE=false
else
GH_AVAILABLE=true
+ PARENT_NUM=$(echo "$PARENT_URL" | grep -oE '[0-9]+$')
+ echo "Parent issue: $PARENT_URL"
fi
```
-Create the issue:
+If `GH_AVAILABLE=false`, skip Steps P3-P5 and continue with the graceful
+degradation path documented at the bottom of this file. Do not create WU issues
+without a parent issue number.
+
+### Step P3: Create each WU sub-issue and link via the sub-issues API
+
+Loop in priority order. For each WU, replace the parent placeholder in the WU
+body, create the issue, fetch its database ID, then POST to the sub-issues
+endpoint.
+
+Track each WU's outcome explicitly. The parent body's findings tables reference
+`WU-N` by ordinal, so a silent `continue` past a failed creation would leave the
+parent advertising sub-issues that don't exist. Instead, every WU contributes a
+row to the final WU table — successful ones link to their sub-issue, failed
+ones surface as `FAILED — file manually` so the maintainer sees the gap.
```bash
-if [ "$GH_AVAILABLE" = true ]; then
- ISSUE_URL=$(gh issue create \
- --repo mvanhorn/cli-printing-press \
+declare -a WU_URLS WU_NUMS WU_TITLES WU_PRIORITIES WU_COMP_SLUGS WU_COMPLEXITIES WU_STATUSES
+declare -a FAILED_WUS # human-readable failures for the final summary
+SUB_ISSUE_API_OK=true
+
+for wu_idx in "${!SORTED_WORK_UNITS[@]}"; do
+ WU="${SORTED_WORK_UNITS[$wu_idx]}"
+ # Each WU contributes: $WU_TITLE, $WU_BODY_TEMPLATE, $WU_PRIORITY_NUM,
+ # $WU_PRIORITY_LABEL, $WU_COMP_SLUG, and $WU_COMPLEXITY.
+
+ # Backfill the parent reference in the body
+ WU_BODY="${WU_BODY_TEMPLATE/<PARENT_NUMBER>/$PARENT_NUM}"
+
+ WU_URL=$(gh issue create \
+ --repo "$REPO" \
+ --title "$WU_TITLE" \
+ --body "$WU_BODY" \
--label retro \
- --title "$ISSUE_TITLE" \
- --body "$(cat <<'EOF'
-<constructed issue body from template above>
-EOF
-)" 2>&1)
-
- if echo "$ISSUE_URL" | grep -q "^https://"; then
- echo ""
- echo "Issue created: $ISSUE_URL"
+ --label "priority:P${WU_PRIORITY_NUM}" \
+ --label "comp:${WU_COMP_SLUG}")
+
+ if ! echo "$WU_URL" | grep -q '^https://'; then
+ echo "WARNING: WU sub-issue creation failed for: $WU_TITLE"
+ echo " Error: $WU_URL"
+ # Record the failure so the parent's WU table and the final summary
+ # surface it; do NOT silently continue.
+ WU_URLS+=("")
+ WU_NUMS+=("")
+ WU_TITLES+=("$WU_TITLE")
+ WU_PRIORITIES+=("$WU_PRIORITY_LABEL")
+ WU_COMP_SLUGS+=("$WU_COMP_SLUG")
+ WU_COMPLEXITIES+=("$WU_COMPLEXITY")
+ WU_STATUSES+=("create-failed")
+ FAILED_WUS+=("WU-$((wu_idx+1)) ($WU_TITLE) — issue creation failed")
+ continue
+ fi
+
+ WU_NUM=$(echo "$WU_URL" | grep -oE '[0-9]+$')
+ WU_STATUS="ok"
+
+ # Fetch the integer database ID — required by the sub-issues REST API.
+ # gh issue view --json id returns the GraphQL node ID (string), which the
+ # REST endpoint rejects. The REST endpoint returns the integer id we want.
+ WU_DB_ID=$(gh api "repos/$REPO/issues/$WU_NUM" --jq '.id' 2>/dev/null)
+
+ if [ -z "$WU_DB_ID" ] || [ "$WU_DB_ID" = "null" ]; then
+ echo "WARNING: could not fetch DB id for issue #$WU_NUM; skipping sub-issue link."
+ SUB_ISSUE_API_OK=false
+ WU_STATUS="link-failed"
+ FAILED_WUS+=("WU-$((wu_idx+1)) (#$WU_NUM) — sub-issue link skipped (DB id unavailable)")
else
- echo "WARNING: Failed to create GitHub issue. Error: $ISSUE_URL"
- GH_AVAILABLE=false
+ # Link as sub-issue. The REST endpoint exists on github.com and most GHES
+ # versions; older GHES instances return 404 here.
+ LINK_RESPONSE=$(gh api \
+ --method POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "/repos/$REPO/issues/$PARENT_NUM/sub_issues" \
+ -F "sub_issue_id=$WU_DB_ID" 2>&1)
+
+ if echo "$LINK_RESPONSE" | grep -qE '"number"|"id"'; then
+ echo "Linked WU sub-issue: $WU_URL"
+ else
+ echo "WARNING: sub-issue link failed for #$WU_NUM; body cross-link will be the only relationship."
+ echo " Response: $LINK_RESPONSE"
+ SUB_ISSUE_API_OK=false
+ WU_STATUS="link-failed"
+ FAILED_WUS+=("WU-$((wu_idx+1)) (#$WU_NUM) — sub-issue link failed (cross-link in body remains)")
+ fi
fi
-fi
+
+ WU_URLS+=("$WU_URL")
+ WU_NUMS+=("$WU_NUM")
+ WU_TITLES+=("$WU_TITLE")
+ WU_PRIORITIES+=("$WU_PRIORITY_LABEL")
+ WU_COMP_SLUGS+=("$WU_COMP_SLUG")
+ WU_COMPLEXITIES+=("$WU_COMPLEXITY")
+ WU_STATUSES+=("$WU_STATUS")
+done
```
-## Handling gh failure (R14)
+Three distinct failure modes, three distinct outcomes:
+
+| Mode | What happened | What the parent shows | What the user sees in Phase 6 Step 6 |
+|------|---------------|----------------------|--------------------------------------|
+| `create-failed` | `gh issue create` for the WU returned a non-URL | WU table row reads `FAILED — file manually` | `FAILED_WUS` summary names this WU |
+| `link-failed` | Issue created OK but sub-issues REST POST failed (or DB id fetch failed) | WU table row links the issue normally; native sub-issue panel won't include it | `FAILED_WUS` notes the issue exists but isn't natively linked |
+| `ok` | Issue created and linked | WU table row links the issue; native sub-issue panel includes it | nothing |
+
+### Step P4: Build the sub-issue table
+
+The table iterates over **every** WU, including failed ones. A failed WU's row
+reads `FAILED — file manually` in the Sub-issue column so the parent doesn't
+silently advertise sub-issues that don't exist.
+
+```bash
+WU_TABLE=$'| WU | Title | Priority | Component | Complexity | Sub-issue |\n'
+WU_TABLE+=$'|----|-------|----------|-----------|------------|-----------|\n'
+
+for i in "${!WU_TITLES[@]}"; do
+ if [ "${WU_STATUSES[$i]}" = "create-failed" ]; then
+ SUB_CELL="**FAILED — file manually**"
+ else
+ SUB_CELL="#${WU_NUMS[$i]}"
+ fi
+ WU_TABLE+="| WU-$((i+1)) | ${WU_TITLES[$i]} | ${WU_PRIORITIES[$i]} | comp:${WU_COMP_SLUGS[$i]} | ${WU_COMPLEXITIES[$i]} | ${SUB_CELL} |"$'\n'
+done
+```
-If `gh` is not available or issue creation fails:
+### Step P5: Edit the parent body to backfill the WU table
+
+```bash
+PARENT_BODY_FINAL="${PARENT_BODY//<!-- WU_TABLE -->/$WU_TABLE}"
+
+gh issue edit "$PARENT_NUM" --repo "$REPO" --body "$PARENT_BODY_FINAL"
+```
+
+If the edit fails (rate limit, permissions), the parent stays with its placeholder
+visible — readable enough that the user understands what's missing, and GitHub's
+native sub-issue panel still shows the linked WUs.
+
+### Step P6: Cross-reference behavior
+
+If `SUB_ISSUE_API_OK=false` (sub-issues endpoint unavailable, e.g., older GHES,
+permissions, feature not enabled), the WUs still cross-link to the parent via
+the `**Parent retro:** #<PARENT_NUMBER>` line in their body. The parent's WU
+table also still shows `#<num>` references, which GitHub auto-links. This means
+even with the sub-issues API completely broken, the relationship between parent
+and WUs is preserved as ordinary issue cross-references — the only thing lost
+is GitHub's native sub-issue rendering and progress bar.
+
+## Variables expected
+
+| Variable | Set by | Contains |
+|----------|--------|----------|
+| `$REPO` | This file Step 1 | Owner/repo string for `gh` |
+| `$RETRO_DOC_URL` | artifact-packaging.md | catbox URL for retro .md, or empty |
+| `$MANUSCRIPTS_URL` | artifact-packaging.md | catbox URL or empty |
+| `$CLI_SOURCE_URL` | artifact-packaging.md | catbox URL or empty |
+| `$RETRO_PROOF_PATH` | SKILL.md Phase 5 | Path to saved retro in manuscript proofs |
+| `$RETRO_SCRATCH_PATH` | SKILL.md Phase 5 | Path to temp retro copy under `/tmp/printing-press/retro/` |
+| `$WORK_UNITS` | SKILL.md Phase 5.5 | Array of WU records (title, priority, comp slug, complexity, body) |
+| `$SORTED_WORK_UNITS` | This file Step P1 | `$WORK_UNITS` sorted P1 → P3 |
+| All retro findings | SKILL.md Phase 4 | Used to populate parent findings tables and WU "Findings absorbed" sections |
+
+## Variables produced
+
+| Variable | Contains |
+|----------|----------|
+| `$PARENT_URL` | Parent issue URL (only in `parent-with-subs` mode) |
+| `$PARENT_NUM` | Parent issue number (only in `parent-with-subs` mode) |
+| `$WU_URLS` | Array of WU sub-issue URLs (empty string for failed creations; only in `parent-with-subs` mode) |
+| `$WU_STATUSES` | Array, one per WU: `ok` / `create-failed` / `link-failed` (only in `parent-with-subs` mode) |
+| `$FAILED_WUS` | Array of human-readable failure descriptions; empty if every WU succeeded |
+| `$SUB_ISSUE_API_OK` | `false` if any sub-issue link failed; `true` otherwise |
+| `$ISSUE_URL` | Single issue URL (only in `single` mode) |
+| `$ISSUE_MODE` | `single` or `parent-with-subs` — used by Phase 6 Step 6 to format presentation |
+
+## Handling `gh` failure (graceful degradation)
+
+Check `gh` auth at the start of Phase 6 Step 4:
+
+```bash
+if ! gh auth status 2>/dev/null; then
+ echo "GitHub CLI is not authenticated. Cannot create issue(s)."
+ GH_AVAILABLE=false
+else
+ GH_AVAILABLE=true
+fi
+```
+
+If `GH_AVAILABLE=false`, or if any of the issue-creation commands fail with a
+network/permissions error that the per-step fallbacks didn't already absorb, fall
+back to printing manual filing instructions:
```bash
if [ "$GH_AVAILABLE" = false ]; then
echo ""
- echo "Could not create GitHub issue automatically."
+ echo "Could not create GitHub issue(s) automatically."
echo ""
echo "To file the retro manually:"
echo " 1. Go to: https://github.com/mvanhorn/cli-printing-press/issues/new"
- echo " 2. Title: $ISSUE_TITLE"
- echo " 3. Copy the retro content from: $RETRO_PROOF_PATH"
+ echo " 2. Use the title and body from the retro document at:"
+ echo " $RETRO_PROOF_PATH"
if [ -n "$RETRO_SCRATCH_PATH" ] && [ -f "$RETRO_SCRATCH_PATH" ]; then
- echo " Temp copy: $RETRO_SCRATCH_PATH"
+ echo " $RETRO_SCRATCH_PATH"
fi
+ echo " 3. For the 2+ WU case, file one issue per WU and link them as sub-issues"
+ echo " via the issue page's 'Sub-issues' panel after creation."
if [ -n "$MANUSCRIPTS_URL" ]; then
echo " 4. Manuscripts: $MANUSCRIPTS_URL"
fi
if [ -n "$CLI_SOURCE_URL" ]; then
echo " 5. CLI source: $CLI_SOURCE_URL"
fi
- echo ""
- echo "The retro document has been saved locally to:"
- echo " $RETRO_PROOF_PATH"
- if [ -n "$RETRO_SCRATCH_PATH" ] && [ -f "$RETRO_SCRATCH_PATH" ]; then
- echo " $RETRO_SCRATCH_PATH"
- fi
fi
```
## Handling body size
-GitHub issue bodies have a practical limit (~65KB). The distilled summary should be
-well under this for typical retros. If `gh issue create` fails with a body-size error:
+GitHub issue bodies have a practical limit (~65KB). The breakout into sub-issues
+makes hitting this limit much less likely than the old monster-issue mode, but a
+single WU with many absorbed findings + long prior-retro chains could still
+approach it. If `gh issue create` rejects a body for size:
-1. Truncate the issue body to: Session Stats + Priority tables + Artifact links
-2. Add: "Full retro analysis available in the manuscripts artifact linked above."
-3. Retry the `gh issue create` with the truncated body.
+1. Truncate the absorbed findings within the WU body to: title + one-sentence
+ summary + `Evidence:` link.
+2. Add: "Full finding analysis available in the manuscripts artifact linked
+ from the parent issue."
+3. Retry.
-## Variables expected
-
-| Variable | Set by | Contains |
-|----------|--------|----------|
-| `$RETRO_DOC_URL` | artifact-packaging.md | catbox URL for retro .md, or empty |
-| `$MANUSCRIPTS_URL` | artifact-packaging.md | catbox URL or empty |
-| `$CLI_SOURCE_URL` | artifact-packaging.md | catbox URL or empty |
-| `$RETRO_PROOF_PATH` | SKILL.md Phase 5 | Path to saved retro in manuscript proofs |
-| `$RETRO_SCRATCH_PATH` | SKILL.md Phase 5 | Path to temp retro copy under `/tmp/printing-press/retro/` |
-| All retro findings | SKILL.md Phase 4 | The prioritized findings to populate tables |
+For the parent: drop "What the Printing Press Got Right" and the Skipped table
+first; keep Session Stats, Findings tables, the WU table, and Artifacts.
← f362dd44 feat(skills): /printing-press-reprint orchestrator (#553)
·
back to Cli Printing Press
·
fix(cli): gate publishing on Phase 5 proof (#558) 7a143818 →