[object Object]

← 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

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 &#39; 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 `&#39;` 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 →