[object Object]

← back to Cli Printing Press

fix(cli): move extra_commands SKILL.md block to its own top-level section (#1524)

c10d0b553823e8a63428ed8473d36182c7de1d48 · 2026-05-16 13:02:01 -0700 · Trevin Chow

* fix(cli): move extra_commands SKILL.md block to its own top-level section

extra_commands declared in internal-YAML specs (shopify, producthunt,
…) used to render under a **Hand-written commands** subsection nested
inside ## Command Reference. The Cobra emitter never reads the field,
so those rows were promises the generator could not back. verify-skill
walks ## Command Reference for inline backticks shaped like
`<binary> <cmd>` and correctly fails the unknown-command check on
every entry.

Lift the block into its own top-level ## Hand-written Extensions
section with an explainer line that says they require separate
wiring. verify-skill's _extract_inline_commands is scoped to the
Command Reference body (regex in scripts/verify-skill/verify_skill.py),
so a sibling section is invisible to the unknown-command walker.
Generating from catalog/specs/producthunt-spec.yaml and running
`printing-press verify-skill --dir <out>` now exits 0.

Closes #1451

Sweep tracking issue for already-published CLIs (shopify, producthunt):
mvanhorn/printing-press-library#616

* fix(cli): place Hand-written Extensions after Finding the right command

Greptile flagged that inserting `## Hand-written Extensions` between
the Command Reference body and Freshness Contract silently reparented
`### Finding the right command` under the new section for any CLI
with extra_commands but no Freshness Contract (extra_commands +
HasDataLayer=false case).

Markdown nests every `###` under the most recent `##`, so the fix is
positional: render the Extensions block after the `### Finding the
right command` subsection (and before `## Recipes`). `### Finding`
stays a child of `## Command Reference` for every combination of
ExtraCommands + Freshness state. Added a regression assertion that
locks the relative ordering.

End-to-end re-verified: producthunt regens with `## Command Reference`
→ `### Finding the right command` → `## Hand-written Extensions` →
`## Auth Setup`, and verify-skill still exits 0.

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

Files touched

Diff

commit c10d0b553823e8a63428ed8473d36182c7de1d48
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Sat May 16 13:02:01 2026 -0700

    fix(cli): move extra_commands SKILL.md block to its own top-level section (#1524)
    
    * fix(cli): move extra_commands SKILL.md block to its own top-level section
    
    extra_commands declared in internal-YAML specs (shopify, producthunt,
    …) used to render under a **Hand-written commands** subsection nested
    inside ## Command Reference. The Cobra emitter never reads the field,
    so those rows were promises the generator could not back. verify-skill
    walks ## Command Reference for inline backticks shaped like
    `<binary> <cmd>` and correctly fails the unknown-command check on
    every entry.
    
    Lift the block into its own top-level ## Hand-written Extensions
    section with an explainer line that says they require separate
    wiring. verify-skill's _extract_inline_commands is scoped to the
    Command Reference body (regex in scripts/verify-skill/verify_skill.py),
    so a sibling section is invisible to the unknown-command walker.
    Generating from catalog/specs/producthunt-spec.yaml and running
    `printing-press verify-skill --dir <out>` now exits 0.
    
    Closes #1451
    
    Sweep tracking issue for already-published CLIs (shopify, producthunt):
    mvanhorn/printing-press-library#616
    
    * fix(cli): place Hand-written Extensions after Finding the right command
    
    Greptile flagged that inserting `## Hand-written Extensions` between
    the Command Reference body and Freshness Contract silently reparented
    `### Finding the right command` under the new section for any CLI
    with extra_commands but no Freshness Contract (extra_commands +
    HasDataLayer=false case).
    
    Markdown nests every `###` under the most recent `##`, so the fix is
    positional: render the Extensions block after the `### Finding the
    right command` subsection (and before `## Recipes`). `### Finding`
    stays a child of `## Command Reference` for every combination of
    ExtraCommands + Freshness state. Added a regression assertion that
    locks the relative ordering.
    
    End-to-end re-verified: producthunt regens with `## Command Reference`
    → `### Finding the right command` → `## Hand-written Extensions` →
    `## Auth Setup`, and verify-skill still exits 0.
    
    ---------
    
    Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
---
 internal/generator/skill_test.go           | 56 +++++++++++++++++++++++++-----
 internal/generator/templates/skill.md.tmpl | 16 +++++----
 2 files changed, 57 insertions(+), 15 deletions(-)

diff --git a/internal/generator/skill_test.go b/internal/generator/skill_test.go
index f21b35a6..6e6c20d4 100644
--- a/internal/generator/skill_test.go
+++ b/internal/generator/skill_test.go
@@ -3,6 +3,7 @@ package generator
 import (
 	"os"
 	"path/filepath"
+	"regexp"
 	"strings"
 	"testing"
 
@@ -358,11 +359,11 @@ func TestSkillRendersAuthBranchPerType(t *testing.T) {
 }
 
 // TestSkillRendersExtraCommands asserts that hand-written commands declared
-// in spec.ExtraCommands appear in the generated SKILL.md Command Reference,
-// after the spec-driven resources, with binary prefix and optional args.
-// This closes the drift class where SKILL.md silently omitted hand-written
-// commands like `today`, `streak`, `rivals` because the template only iterated
-// .Resources.
+// in spec.ExtraCommands appear in their own `## Hand-written Extensions`
+// section (NOT inside `## Command Reference`), with binary prefix and optional
+// args. The separate section keeps verify-skill's unknown-command walker
+// (scoped to ## Command Reference) from treating extra_commands as canonical
+// Cobra paths — that drift was the root cause of #1451.
 func TestSkillRendersExtraCommands(t *testing.T) {
 	t.Parallel()
 
@@ -380,16 +381,55 @@ func TestSkillRendersExtraCommands(t *testing.T) {
 	require.NoError(t, err)
 	content := string(skill)
 
-	assert.Contains(t, content, "**Hand-written commands**",
-		"Command Reference should include a Hand-written commands subsection when ExtraCommands present")
+	assert.Contains(t, content, "## Hand-written Extensions",
+		"ExtraCommands should be surfaced in their own top-level section, not under ## Command Reference")
 	assert.Contains(t, content, "`sports-pp-cli trending`",
-		"extra command without args should render as just binary + name")
+		"extra command without args should render as binary + name in the Extensions section")
 	assert.Contains(t, content, "`sports-pp-cli boxscore <event_id>`",
 		"extra command with args should render args after the name")
 	assert.Contains(t, content, "Most-followed athletes and teams across all leagues",
 		"extra command description should appear in the rendered output")
 	assert.Contains(t, content, "`sports-pp-cli h2h <team1> <team2>`",
 		"extra command with multi-arg signature should render verbatim")
+
+	// Regression guard for #1451: the Extensions section must live OUTSIDE
+	// the Command Reference section so verify-skill's _extract_inline_commands
+	// walker (scoped to ## Command Reference) does not flag these as
+	// unknown-command findings. Asserted by locating each section's offset
+	// and confirming Extensions starts after Reference ends.
+	cmdRefIdx := strings.Index(content, "## Command Reference")
+	require.NotEqual(t, -1, cmdRefIdx, "Command Reference section is expected to exist")
+	extIdx := strings.Index(content, "## Hand-written Extensions")
+	require.NotEqual(t, -1, extIdx)
+	require.Greater(t, extIdx, cmdRefIdx,
+		"Hand-written Extensions must be a sibling section after Command Reference, not nested inside it")
+
+	// Belt-and-suspenders: confirm the Command Reference section body
+	// (everything between its heading and the next top-level heading)
+	// does NOT contain any extra_commands path. Mirrors the Python
+	// walker's scoping (regex in scripts/verify-skill/verify_skill.py:74)
+	// without lookahead, which Go's RE2 doesn't support.
+	cmdRefHeadingRE := regexp.MustCompile(`(?m)^##\s+Command\s+Reference\s*$`)
+	nextSectionRE := regexp.MustCompile(`(?m)^##\s+`)
+	loc := cmdRefHeadingRE.FindStringIndex(content)
+	require.NotNil(t, loc, "Command Reference section heading should match the walker's regex")
+	afterHeading := content[loc[1]:]
+	if next := nextSectionRE.FindStringIndex(afterHeading); next != nil {
+		afterHeading = afterHeading[:next[0]]
+	}
+	for _, name := range []string{"sports-pp-cli trending", "sports-pp-cli boxscore", "sports-pp-cli h2h"} {
+		assert.NotContains(t, afterHeading, name,
+			"extra command %q must not appear under Command Reference; the unknown-command walker would flag it", name)
+	}
+
+	// Markdown nests every `###` under the most recent `##`. Placing
+	// `## Hand-written Extensions` BEFORE `### Finding the right command`
+	// would silently reparent that subsection. Assert the ordering so a
+	// future template edit doesn't regress the structure.
+	findingIdx := strings.Index(content, "### Finding the right command")
+	require.NotEqual(t, -1, findingIdx, "Finding the right command subsection should exist")
+	require.Greater(t, extIdx, findingIdx,
+		"Hand-written Extensions must come after ### Finding the right command so it doesn't reparent that subsection")
 }
 
 // TestSkillFrontmatterMetadataIsClawHubCompliantNestedYAML asserts that the
diff --git a/internal/generator/templates/skill.md.tmpl b/internal/generator/templates/skill.md.tmpl
index cd03910b..b4b57b90 100644
--- a/internal/generator/templates/skill.md.tmpl
+++ b/internal/generator/templates/skill.md.tmpl
@@ -144,13 +144,6 @@ This CLI was generated with browser-observed traffic context.
 {{- end}}
 {{- end}}
 {{end}}
-{{- if .ExtraCommands}}
-
-**Hand-written commands**
-{{range .ExtraCommands}}
-- `{{$.Name}}-pp-cli {{.Name}}{{if .Args}} {{.Args}}{{end}}` — {{oneline .Description}}
-{{- end}}
-{{end}}
 {{- if and .HasDataLayer .Cache.Enabled}}
 
 ## Freshness Contract
@@ -176,6 +169,15 @@ When you know what you want to do but not which command does it, ask the CLI dir
 ```
 
 `which` resolves a natural-language capability query to the best matching command from this CLI's curated feature index. Exit code `0` means at least one match; exit code `2` means no confident match — fall back to `--help` or use a narrower query.
+{{- if .ExtraCommands}}
+
+## Hand-written Extensions
+
+These commands are declared by the spec author and require separate hand-written wiring; the generator does not emit Cobra registration for them. They are listed here for discoverability and are intentionally outside `## Command Reference` so the verify-skill unknown-command check does not treat them as generator-owned paths.
+{{range .ExtraCommands}}
+- `{{$.Name}}-pp-cli {{.Name}}{{if .Args}} {{.Args}}{{end}}` — {{oneline .Description}}
+{{- end}}
+{{- end}}
 {{- if and .Narrative .Narrative.Recipes}}
 
 ## Recipes

← 99637941 fix(skills): require codex completion marker before treating  ·  back to Cli Printing Press  ·  feat(skills): add /printing-press-amend skill — dogfood + di c31632b3 →