← back to Cli Printing Press
fix(skills): add Phase 3 starter templates for novel feature commands (#451)
00f83219a2f94e3cb8335121053df77f076a0c40 · 2026-04-30 16:33:12 -0700 · Trevin Chow
* fix(skills): add Phase 3 starter templates for novel feature commands
Cobra wiring is mechanical and consistent across novel features; the
actual feature work lives in the RunE body. The recipe-goat retro and
the PokéAPI retro both flagged that agents spend significant Phase 3
time on cobra-literal wiring (Use, Short, Long, Example, Annotations,
function signature, registration in root.go) before getting to the
domain logic — and occasionally typo it (Use vs Command name drift,
missing Example, wrong Annotations key, etc.).
This adds copy-pasteable starter templates to SKILL.md Phase 3:
- One cobra-wrapper template covering the common shape across the
espn / allrecipes / hackernews / kalshi / yahoo-finance / dub
novel features I sampled. Comments map NovelFeature fields to
cobra fields.
- Two RunE skeletons covering the dominant data-source patterns:
API-call (live data via flags.newClient + c.Get) and store-query
(offline data via store.OpenWithContext + db.QueryContext). Both
show the canonical dual-mode (JSON + human) output dispatch.
- A note on combining the two for hybrid features and on registering
multi-word commands (issues stale → newIssuesCmd's child).
What this is NOT: a generator change. The generator does not produce
these stub files. SKILL.md remains correct that "Priority 2 is always
hand-built." The agent's Phase 3 workflow is just: copy the wrapper,
copy the matching RunE skeleton, fill in the path/query/parsing.
Existing dogfood gates (checkNovelFeatures planned-vs-built,
reimplementation_check, scorecard, verify) continue to apply unchanged.
The templates raise the floor (consistent shape, fewer typos, faster
start) without changing the ceiling (correctness signals).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(skills): correct novel-feature template idioms to match real CLIs
Verified the templates against shipped novel features in espn, dub,
allrecipes, and kalshi. Three template idioms didn't match what real
CLIs use:
1. Store-query skeleton called dbPath() as a function — the actual
helper is defaultDBPath(name string), and the convention real
features follow is: declare a local dbPath flag, default with
defaultDBPath("<cli>-pp-cli") if empty, pass to store.Open. Updated
the skeleton to declare the flag adjacent to the cmd literal and
default inside RunE.
2. API-call skeleton wrapped output in wrapWithProvenance + printOutput
with a DataProvenance literal. Both wrapWithProvenance and
DataProvenance live behind {{- if .HasDataLayer}} in helpers.go.tmpl,
so a CLI without sync would not have them and the template wouldn't
compile. Real novel features (espn scores, espn streak) just call
json.NewEncoder().Encode(view) directly. Provenance wrapping is a
search/sql convention, not a universal novel-feature pattern.
Updated to use the encoder pattern.
3. JSON-mode check used `flags.asJSON || !isTerminal(...)` but real
features use `flags.asJSON || (!isTerminal(...) && !humanFriendly)` —
the humanFriendly persistent flag overrides piped-output detection
so users who explicitly pipe to a human-readable formatter (less,
bat) still get colored output. Aligned both skeletons with the real
pattern.
Net effect: the templates now compile cleanly when copied into a real
CLI's internal/cli directory. Verified by reading actual shipped novel
features in ~/printing-press/library/ rather than just inspecting the
generator templates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
M skills/printing-press/SKILL.md
Diff
commit 00f83219a2f94e3cb8335121053df77f076a0c40
Author: Trevin Chow <trevin@trevinchow.com>
Date: Thu Apr 30 16:33:12 2026 -0700
fix(skills): add Phase 3 starter templates for novel feature commands (#451)
* fix(skills): add Phase 3 starter templates for novel feature commands
Cobra wiring is mechanical and consistent across novel features; the
actual feature work lives in the RunE body. The recipe-goat retro and
the PokéAPI retro both flagged that agents spend significant Phase 3
time on cobra-literal wiring (Use, Short, Long, Example, Annotations,
function signature, registration in root.go) before getting to the
domain logic — and occasionally typo it (Use vs Command name drift,
missing Example, wrong Annotations key, etc.).
This adds copy-pasteable starter templates to SKILL.md Phase 3:
- One cobra-wrapper template covering the common shape across the
espn / allrecipes / hackernews / kalshi / yahoo-finance / dub
novel features I sampled. Comments map NovelFeature fields to
cobra fields.
- Two RunE skeletons covering the dominant data-source patterns:
API-call (live data via flags.newClient + c.Get) and store-query
(offline data via store.OpenWithContext + db.QueryContext). Both
show the canonical dual-mode (JSON + human) output dispatch.
- A note on combining the two for hybrid features and on registering
multi-word commands (issues stale → newIssuesCmd's child).
What this is NOT: a generator change. The generator does not produce
these stub files. SKILL.md remains correct that "Priority 2 is always
hand-built." The agent's Phase 3 workflow is just: copy the wrapper,
copy the matching RunE skeleton, fill in the path/query/parsing.
Existing dogfood gates (checkNovelFeatures planned-vs-built,
reimplementation_check, scorecard, verify) continue to apply unchanged.
The templates raise the floor (consistent shape, fewer typos, faster
start) without changing the ceiling (correctness signals).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(skills): correct novel-feature template idioms to match real CLIs
Verified the templates against shipped novel features in espn, dub,
allrecipes, and kalshi. Three template idioms didn't match what real
CLIs use:
1. Store-query skeleton called dbPath() as a function — the actual
helper is defaultDBPath(name string), and the convention real
features follow is: declare a local dbPath flag, default with
defaultDBPath("<cli>-pp-cli") if empty, pass to store.Open. Updated
the skeleton to declare the flag adjacent to the cmd literal and
default inside RunE.
2. API-call skeleton wrapped output in wrapWithProvenance + printOutput
with a DataProvenance literal. Both wrapWithProvenance and
DataProvenance live behind {{- if .HasDataLayer}} in helpers.go.tmpl,
so a CLI without sync would not have them and the template wouldn't
compile. Real novel features (espn scores, espn streak) just call
json.NewEncoder().Encode(view) directly. Provenance wrapping is a
search/sql convention, not a universal novel-feature pattern.
Updated to use the encoder pattern.
3. JSON-mode check used `flags.asJSON || !isTerminal(...)` but real
features use `flags.asJSON || (!isTerminal(...) && !humanFriendly)` —
the humanFriendly persistent flag overrides piped-output detection
so users who explicitly pipe to a human-readable formatter (less,
bat) still get colored output. Aligned both skeletons with the real
pattern.
Net effect: the templates now compile cleanly when copied into a real
CLI's internal/cli directory. Verified by reading actual shipped novel
features in ~/printing-press/library/ rather than just inspecting the
generator templates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
skills/printing-press/SKILL.md | 110 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 110 insertions(+)
diff --git a/skills/printing-press/SKILL.md b/skills/printing-press/SKILL.md
index d88d548f..0cccd864 100644
--- a/skills/printing-press/SKILL.md
+++ b/skills/printing-press/SKILL.md
@@ -1931,6 +1931,116 @@ Before moving to shipcheck, verify the build log against the absorb manifest:
The generator handles Priority 0 (data layer) and most of Priority 1 (absorbed API endpoints). Priority 2 (transcendence) is always hand-built — the generator does not produce these. If you skip Priority 2, the CLI ships without the features that differentiate it from every other tool.
+**Starter templates for novel commands.** Cobra wiring is mechanical and consistent across novel features; the actual feature work lives in the RunE body. Copy the wrapper below and one of the RunE skeletons that follows, fill in the placeholders from the absorb manifest's transcendence row (`Name`, `Command`, `Description`, `Example`, `WhyItMatters`), and replace the body comments with your implementation. Dogfood, verify, and scorecard still apply to the result — the templates raise the floor without changing what shipcheck checks.
+
+```go
+// internal/cli/<command>.go — replace <command> with the kebab leaf
+// of NovelFeature.Command (e.g., "issues stale" → "issues_stale.go").
+package cli
+
+import (
+ "github.com/spf13/cobra"
+ // add: "encoding/json", "fmt", "<module>/internal/store", etc. as needed
+)
+
+func newXxxCmd(flags *rootFlags) *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "<leaf-of-Command>", // e.g. "stale" for "issues stale"
+ Short: "<NovelFeature.Description, one line>", // truncate to ~70 chars
+ Long: "<optional: Description + WhyItMatters>", // omit if Short is enough
+ Example: " <cli>-pp-cli <Command> --json", // from NovelFeature.Example
+ Annotations: map[string]string{
+ // Set "mcp:read-only": "true" only when the command does NOT mutate
+ // external state (lookups, comparisons, aggregations, render views).
+ // Omit the whole map for commands that mutate (post, delete, write file).
+ "mcp:read-only": "true",
+ },
+ RunE: func(cmd *cobra.Command, args []string) error {
+ // Pick the matching RunE skeleton below (API-call or store-query),
+ // then implement the feature-specific path/query/parsing/formatting.
+ return nil
+ },
+ }
+ // cmd.Flags().StringVar(...) — add flags from the planned --flag list, if any
+ return cmd
+}
+
+// Multi-word Commands like "issues stale": this constructor is registered as
+// a child of the matching spec-resource parent (newIssuesCmd) — wire the
+// AddCommand call inside root.go via local-variable capture:
+// issuesCmd := newIssuesCmd(flags)
+// issuesCmd.AddCommand(newIssuesStaleCmd(flags))
+// rootCmd.AddCommand(issuesCmd)
+// Single-word Commands register directly: rootCmd.AddCommand(newXxxCmd(flags)).
+```
+
+**RunE skeleton — API-call shape** (live data via the generated client):
+
+```go
+RunE: func(cmd *cobra.Command, args []string) error {
+ c, err := flags.newClient()
+ if err != nil {
+ return err
+ }
+ // Replace path with the absorbed endpoint or hand-rolled URL. Use
+ // cliutil.FanoutRun for any --site/--source/--region CSV fan-out;
+ // re-implementing fanout inline is the recipe-goat silent-drop bug.
+ data, err := c.Get("/api/v1/path", nil)
+ if err != nil {
+ return fmt.Errorf("fetching <resource>: %w", err)
+ }
+ // Parse data into your feature's view. Use cliutil.CleanText for any
+ // text extracted from HTML or schema.org JSON-LD; re-implementing
+ // HTML-entity unescape inline is the ' bug class.
+ var view yourViewType // = parse(data)
+ if flags.asJSON || (!isTerminal(cmd.OutOrStdout()) && !humanFriendly) {
+ enc := json.NewEncoder(cmd.OutOrStdout())
+ enc.SetIndent("", " ")
+ return enc.Encode(view)
+ }
+ // Human/terminal output (table or pretty print).
+ return nil
+},
+```
+
+**RunE skeleton — store-query shape** (offline data via the local SQLite):
+
+```go
+// Declare these alongside the cmd literal, before return cmd:
+// var dbPath string
+// cmd.Flags().StringVar(&dbPath, "db", "", "Database path")
+
+RunE: func(cmd *cobra.Command, args []string) error {
+ if dbPath == "" {
+ dbPath = defaultDBPath("<cli>-pp-cli") // replace <cli> with the API slug
+ }
+ db, err := store.OpenWithContext(cmd.Context(), dbPath)
+ if err != nil {
+ return fmt.Errorf("opening database: %w", err)
+ }
+ defer db.Close()
+ // Replace the query with your aggregation. The store schema mirrors
+ // the synced resources; `printing-press dogfood --json` shows the
+ // table list. SQL must be SELECT-only; the search/sql gates reject
+ // mutating statements.
+ rows, err := db.DB().QueryContext(cmd.Context(), `SELECT id, data FROM <table> WHERE ...`)
+ if err != nil {
+ return fmt.Errorf("query: %w", err)
+ }
+ defer rows.Close()
+ var results []yourRowType // scan rows into the slice
+ if flags.asJSON || (!isTerminal(cmd.OutOrStdout()) && !humanFriendly) {
+ enc := json.NewEncoder(cmd.OutOrStdout())
+ enc.SetIndent("", " ")
+ return enc.Encode(results)
+ }
+ // Human/terminal output.
+ return nil
+},
+```
+
+For features that combine both (cache an API response in the store, or fall through to live when the local store is stale), nest one skeleton inside the other and use the `--data-source auto/local/live` flag pattern from the generated `sync` command.
+
**Shared helpers available to novel code:** The generator emits `internal/cliutil/` in every CLI. When authoring novel commands, prefer `cliutil.FanoutRun` for any aggregation command (any `--site`/`--source`/`--region` CSV fan-out) and `cliutil.CleanText` for any text extracted from HTML or schema.org JSON-LD. Re-implementing these inline is how recipe-goat's trending silent-drop and `'` entity bugs shipped.
**MCP exposure:** The generator emits `internal/mcp/cobratree/`, and the MCP binary mirrors the Cobra tree at startup. When you add, rename, or remove a user-facing Cobra command, the MCP surface follows automatically. Two annotations control how each command appears as an MCP tool:
← 6aee6205 fix(cli): emit mcp:read-only + plumb body fields in promoted
·
back to Cli Printing Press
·
fix(skills): tighten human-time-estimate cardinal rule + cle c78cb181 →