[object Object]

← back to Cli Printing Press

fix(cli): retro #421 WU-4, WU-6, WU-7 — search empty JSON, live-check tokenizer, raw database/sql carve-out (#446)

ba22f30adacf032a4a180a339859e6d664d094c4 · 2026-04-30 11:59:09 -0700 · Trevin Chow

* fix(cli): search --json emits {"results":[],"meta":{...}} on no matches

The generated search command's outputSearchResults short-circuited on
empty results with a stderr "No results" line and return nil, leaving
stdout silent in --json (or piped) mode. Agents piping through
json.loads / jq saw empty input and failed to parse.

Reorder so the JSON-mode check runs first: when --json or stdout is
piped, always emit a valid envelope (data is `[]` on no matches, the
real array otherwise). The human-mode branch keeps the existing stderr
"No results" line for terminal users.

Adds a generator test pinning the contract: the template must contain
both the empty-array data branch and the jsonMode check, and the
jsonMode check must precede the human-mode "No results" stderr line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cli): live-check tokenizer splits on commas + consults command path

Two retro findings on the live-check relevance heuristic:

F9 — outputMentionsQuery split queries on whitespace only, so a
comma-separated list query like "pikachu,charizard,blastoise" became
one token. JSON-array outputs separated names with quote+comma+quote,
so the substring match never fired and live-check failed spuriously.
Switch to splitting on whitespace OR commas so each name is checked
independently.

F10 — extractQueryToken inspected only `args []string` and used a
hardcoded English-verb denylist to skip command-name positionals. Verbs
not in the denylist (e.g., `find`, `top`, `browse`) were treated as
search queries, and the relevance check failed because there's no
reason for the rendered output to literally echo the verb.

Pass NovelFeature.Command (the cobra command path being exercised) into
extractQueryToken. If the last positional matches a word from the
command path, it's a subcommand name, not a query — return "" and
skip the relevance check. The static commonCommandVerb fallback stays
for cases where the command path isn't available; the new check fires
first.

Tests cover both: comma-separated query token-splitting, and
command-path-aware suppression of trailing verbs not in the static
denylist (find, top, browse).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cli): reimplementation_check accepts raw database/sql carve-out

Novel commands that bypass the generated store package and operate on
the printed CLI's local SQLite file directly through database/sql were
flagged as hand-rolled responses, even though they read the same local
data the store package wraps. The carve-out comment block at the top
of the file explicitly named only `internal/store` access; raw
database/sql was an unintended false positive.

Extend hasStoreSignal to also accept files that import "database/sql"
AND call sql.Open / sql.OpenDB. Both must be present together — the
import alone could appear for unrelated reasons. Method calls like
db.Query / db.Exec are intentionally NOT matched as a signal because
those names collide with too many other receivers (HTTP clients, cobra
commands) to be reliable. The sql.Open call is the high-specificity
anchor.

Tests cover both directions: a novel command with database/sql import
plus sql.Open passes the carve-out, while a file with the import alone
(no Open call) still gets flagged as a hand-rolled response.

Updates AGENTS.md "Anti-reimplementation" carve-outs to mention the
raw-database/sql case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(cli): apply simplify findings to retro WUs 4/6/7

Four cleanups from a /simplify pass:

1. search.go.tmpl: collapse the var/if-else around json.Marshal. The
   filter step uses make([]json.RawMessage, 0, ...) so results is
   non-nil even when empty; json.Marshal renders that as `[]` directly
   without the explicit short-circuit. Generator test updated to assert
   on the ordering invariant rather than the now-removed literal.

2. live_check.go extractQueryToken: move the commandPath docstring
   paragraph above the Examples block so the doc reads top-down
   (intent → examples). Examples now show both args and commandPath
   for each case.

3. live_check.go extractQueryToken: replace the per-call cmdWords map
   (allocated for 1-3 entries) with a single linear scan via
   strings.FieldsSeq. Cleaner at this scale and avoids a tiny
   allocation per feature check.

4. reimplementation_check.go hasStoreSignal: collapse the early-return
   shape into a single boolean expression. Trim the rawSQLImportRe /
   rawSQLOpenCallRe doc-comments to lead with the WHY (paired signal,
   not standalone) and drop the narrative restating the file-level
   doc. Fix the file-level comment claim about "operating on *sql.DB"
   — the regex deliberately doesn't match arbitrary db.* method calls.

No behavior change. 2531 tests pass, lint clean, golden 9/9.

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 ba22f30adacf032a4a180a339859e6d664d094c4
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Thu Apr 30 11:59:09 2026 -0700

    fix(cli): retro #421 WU-4, WU-6, WU-7 — search empty JSON, live-check tokenizer, raw database/sql carve-out (#446)
    
    * fix(cli): search --json emits {"results":[],"meta":{...}} on no matches
    
    The generated search command's outputSearchResults short-circuited on
    empty results with a stderr "No results" line and return nil, leaving
    stdout silent in --json (or piped) mode. Agents piping through
    json.loads / jq saw empty input and failed to parse.
    
    Reorder so the JSON-mode check runs first: when --json or stdout is
    piped, always emit a valid envelope (data is `[]` on no matches, the
    real array otherwise). The human-mode branch keeps the existing stderr
    "No results" line for terminal users.
    
    Adds a generator test pinning the contract: the template must contain
    both the empty-array data branch and the jsonMode check, and the
    jsonMode check must precede the human-mode "No results" stderr line.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * fix(cli): live-check tokenizer splits on commas + consults command path
    
    Two retro findings on the live-check relevance heuristic:
    
    F9 — outputMentionsQuery split queries on whitespace only, so a
    comma-separated list query like "pikachu,charizard,blastoise" became
    one token. JSON-array outputs separated names with quote+comma+quote,
    so the substring match never fired and live-check failed spuriously.
    Switch to splitting on whitespace OR commas so each name is checked
    independently.
    
    F10 — extractQueryToken inspected only `args []string` and used a
    hardcoded English-verb denylist to skip command-name positionals. Verbs
    not in the denylist (e.g., `find`, `top`, `browse`) were treated as
    search queries, and the relevance check failed because there's no
    reason for the rendered output to literally echo the verb.
    
    Pass NovelFeature.Command (the cobra command path being exercised) into
    extractQueryToken. If the last positional matches a word from the
    command path, it's a subcommand name, not a query — return "" and
    skip the relevance check. The static commonCommandVerb fallback stays
    for cases where the command path isn't available; the new check fires
    first.
    
    Tests cover both: comma-separated query token-splitting, and
    command-path-aware suppression of trailing verbs not in the static
    denylist (find, top, browse).
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * fix(cli): reimplementation_check accepts raw database/sql carve-out
    
    Novel commands that bypass the generated store package and operate on
    the printed CLI's local SQLite file directly through database/sql were
    flagged as hand-rolled responses, even though they read the same local
    data the store package wraps. The carve-out comment block at the top
    of the file explicitly named only `internal/store` access; raw
    database/sql was an unintended false positive.
    
    Extend hasStoreSignal to also accept files that import "database/sql"
    AND call sql.Open / sql.OpenDB. Both must be present together — the
    import alone could appear for unrelated reasons. Method calls like
    db.Query / db.Exec are intentionally NOT matched as a signal because
    those names collide with too many other receivers (HTTP clients, cobra
    commands) to be reliable. The sql.Open call is the high-specificity
    anchor.
    
    Tests cover both directions: a novel command with database/sql import
    plus sql.Open passes the carve-out, while a file with the import alone
    (no Open call) still gets flagged as a hand-rolled response.
    
    Updates AGENTS.md "Anti-reimplementation" carve-outs to mention the
    raw-database/sql case.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * refactor(cli): apply simplify findings to retro WUs 4/6/7
    
    Four cleanups from a /simplify pass:
    
    1. search.go.tmpl: collapse the var/if-else around json.Marshal. The
       filter step uses make([]json.RawMessage, 0, ...) so results is
       non-nil even when empty; json.Marshal renders that as `[]` directly
       without the explicit short-circuit. Generator test updated to assert
       on the ordering invariant rather than the now-removed literal.
    
    2. live_check.go extractQueryToken: move the commandPath docstring
       paragraph above the Examples block so the doc reads top-down
       (intent → examples). Examples now show both args and commandPath
       for each case.
    
    3. live_check.go extractQueryToken: replace the per-call cmdWords map
       (allocated for 1-3 entries) with a single linear scan via
       strings.FieldsSeq. Cleaner at this scale and avoids a tiny
       allocation per feature check.
    
    4. reimplementation_check.go hasStoreSignal: collapse the early-return
       shape into a single boolean expression. Trim the rawSQLImportRe /
       rawSQLOpenCallRe doc-comments to lead with the WHY (paired signal,
       not standalone) and drop the narrative restating the file-level
       doc. Fix the file-level comment claim about "operating on *sql.DB"
       — the regex deliberately doesn't match arbitrary db.* method calls.
    
    No behavior change. 2531 tests pass, lint clean, golden 9/9.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 AGENTS.md                                        |  1 +
 internal/generator/generator_test.go             | 24 ++++++
 internal/generator/templates/search.go.tmpl      | 23 +++---
 internal/pipeline/live_check.go                  | 42 +++++++----
 internal/pipeline/live_check_test.go             | 60 ++++++++++-----
 internal/pipeline/reimplementation_check.go      | 21 +++++-
 internal/pipeline/reimplementation_check_test.go | 94 ++++++++++++++++++++++++
 7 files changed, 222 insertions(+), 43 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index ac798e31..12860454 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -30,6 +30,7 @@ Reject:
 Carve-outs:
 
 - Commands that read from `internal/store` (the `stale`, `bottleneck`, `health`, `reconcile` family) — local-data, not fake API calls
+- Commands that bypass the store package and operate on the local SQLite file directly through `database/sql` (import + `sql.Open`/`sql.OpenDB`) — same data, thinner surface
 - Commands that cache an API response in the store after calling it — both a client call and a store call is fine
 - Commands whose data is the curated content itself (substitution tables, holiday lists, currency metadata) — opt in via `// pp:novel-static-reference` directive in the command's source file
 
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index 71fca805..3394365c 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -5325,3 +5325,27 @@ func TestTemplatesEmitReadOnlyAnnotation(t *testing.T) {
 		})
 	}
 }
+
+// TestSearchTemplateEmitsEmptyJSONEnvelope pins the contract: the
+// generated `search` command in --json (or piped) mode must always emit
+// a valid JSON envelope, including on no matches. Agents pipe stdout
+// through json.loads / jq and a silent return-nil breaks parsing.
+func TestSearchTemplateEmitsEmptyJSONEnvelope(t *testing.T) {
+	t.Parallel()
+	data, err := os.ReadFile(filepath.Join("templates", "search.go.tmpl"))
+	require.NoError(t, err)
+	body := string(data)
+
+	assert.Contains(t, body, `jsonMode := flags.asJSON || !isTerminal(cmd.OutOrStdout())`,
+		"search.go.tmpl must compute jsonMode so the envelope path runs even on no matches")
+
+	// Ordering pin: the jsonMode block must come before the human-mode
+	// "No results" stderr line. Reversing the order would skip the JSON
+	// envelope on empty results — the original failure mode.
+	jsonModeIdx := strings.Index(body, "jsonMode := flags.asJSON")
+	noResultsIdx := strings.Index(body, `"No results (source: %s)\n"`)
+	require.GreaterOrEqual(t, jsonModeIdx, 0)
+	require.GreaterOrEqual(t, noResultsIdx, 0)
+	assert.Less(t, jsonModeIdx, noResultsIdx,
+		"jsonMode check must come before the human-mode 'No results' stderr line; otherwise the JSON-envelope path is skipped on empty results")
+}
diff --git a/internal/generator/templates/search.go.tmpl b/internal/generator/templates/search.go.tmpl
index 07116bc7..bb67679f 100644
--- a/internal/generator/templates/search.go.tmpl
+++ b/internal/generator/templates/search.go.tmpl
@@ -243,26 +243,31 @@ func outputSearchResults(cmd *cobra.Command, flags *rootFlags, results []json.Ra
 		results = results[:limit]
 	}
 
-	if len(results) == 0 {
-		fmt.Fprintf(cmd.ErrOrStderr(), "No results (source: %s)\n", prov.Source)
-		return nil
-	}
-
-	// Print provenance to stderr for human output
-	printProvenance(cmd, len(results), prov)
+	jsonMode := flags.asJSON || !isTerminal(cmd.OutOrStdout())
 
-	if flags.asJSON || !isTerminal(cmd.OutOrStdout()) {
+	// JSON mode always emits a valid envelope, including on no matches —
+	// agents pipe stdout through json.loads / jq and need parseable output
+	// regardless of result count. The filtered slice is built via make
+	// above, so it's non-nil even when empty; json.Marshal renders that
+	// as `[]` rather than `null`.
+	if jsonMode {
 		data, err := json.Marshal(results)
 		if err != nil {
 			return err
 		}
-		wrapped, err := wrapWithProvenance(json.RawMessage(data), prov)
+		wrapped, err := wrapWithProvenance(data, prov)
 		if err != nil {
 			return err
 		}
 		return printOutput(cmd.OutOrStdout(), wrapped, true)
 	}
 
+	if len(results) == 0 {
+		fmt.Fprintf(cmd.ErrOrStderr(), "No results (source: %s)\n", prov.Source)
+		return nil
+	}
+
+	printProvenance(cmd, len(results), prov)
 	for _, r := range results {
 		fmt.Fprintln(cmd.OutOrStdout(), string(r))
 	}
diff --git a/internal/pipeline/live_check.go b/internal/pipeline/live_check.go
index 10b82db8..a1dab997 100644
--- a/internal/pipeline/live_check.go
+++ b/internal/pipeline/live_check.go
@@ -14,6 +14,7 @@ import (
 	"strings"
 	"sync"
 	"time"
+	"unicode"
 )
 
 // LiveStatus is the outcome of one feature's live check.
@@ -345,7 +346,7 @@ func runOneFeatureCheck(cliDir, binaryPath string, f NovelFeature, timeout time.
 		return fail("empty output")
 	}
 
-	if query := extractQueryToken(args); query != "" {
+	if query := extractQueryToken(args, f.Command); query != "" {
 		if !outputMentionsQuery(stdoutCap.String(), query) {
 			return fail(fmt.Sprintf("output does not contain any token from query %q", query))
 		}
@@ -500,21 +501,20 @@ func shellSplit(s string) ([]string, error) {
 //
 // URLs and IDs are intentionally excluded: the CLI's output for a URL-based
 // command (recipe get <url>) wouldn't contain the URL itself, so matching
-// against it produces false negatives.
+// against it produces false negatives. commandPath is the cobra command
+// path being exercised (e.g. "leaderboard top"); positionals that match a
+// word from it are treated as subcommand names, not queries — without
+// this, `leaderboard top` would treat `top` as a search query and fail
+// the relevance heuristic against output that has no reason to echo it.
 //
 // Examples:
 //
-//	["goat", "brownies", "--limit", "5"]  → "brownies"
-//	["sub", "buttermilk"]                 → "buttermilk"
-//	["recipe", "get", "https://foo/bar"]  → "" (URL, skip relevance check)
-//	["cookbook", "list", "--json"]        → "" (no query)
-//
-// TODO: commands like `list pending` where "pending" is a status keyword
-// won't have the status in their rendered output, producing a spurious
-// relevance failure. If this starts biting, consider a denylist of common
-// non-content positionals or reading a dedicated "relevance arg" pointer
-// from NovelFeature metadata.
-func extractQueryToken(args []string) string {
+//	args=["goat", "brownies", "--limit", "5"], cmd="goat"           → "brownies"
+//	args=["sub", "buttermilk"], cmd="sub"                            → "buttermilk"
+//	args=["recipe", "get", "https://foo/bar"], cmd="recipe get"      → "" (URL, skip)
+//	args=["cookbook", "list", "--json"], cmd="cookbook list"         → "" (no query)
+//	args=["leaderboard", "top"], cmd="leaderboard top"               → "" (subcommand name)
+func extractQueryToken(args []string, commandPath string) string {
 	var positionals []string
 	for _, arg := range args {
 		if strings.HasPrefix(arg, "-") {
@@ -526,6 +526,12 @@ func extractQueryToken(args []string) string {
 		return ""
 	}
 	candidate := positionals[len(positionals)-1]
+	candidateLower := strings.ToLower(candidate)
+	for word := range strings.FieldsSeq(strings.ToLower(commandPath)) {
+		if word == candidateLower {
+			return ""
+		}
+	}
 	if looksLikeURLOrID(candidate) {
 		return ""
 	}
@@ -576,11 +582,15 @@ func looksLikeURLOrID(s string) bool {
 }
 
 // outputMentionsQuery is case-insensitive; splits the query on whitespace
-// and succeeds if any token (with singular/plural tolerance) appears in the
-// output. Mirrors the permissive relevance check used inside generated CLIs.
+// and commas, then succeeds if any token (with singular/plural tolerance)
+// appears in the output. Mirrors the permissive relevance check used
+// inside generated CLIs. Splitting on commas catches comma-separated
+// list queries like `pikachu,charizard,blastoise` whose individual names
+// appear in JSON-array outputs separated by quote+comma+quote.
 func outputMentionsQuery(output, query string) bool {
 	lowered := strings.ToLower(output)
-	for tok := range strings.FieldsSeq(strings.ToLower(query)) {
+	splitOnQueryDelim := func(r rune) bool { return unicode.IsSpace(r) || r == ',' }
+	for _, tok := range strings.FieldsFunc(strings.ToLower(query), splitOnQueryDelim) {
 		tok = strings.TrimFunc(tok, func(r rune) bool { return r == '"' || r == '\'' })
 		if len(tok) < 3 {
 			continue
diff --git a/internal/pipeline/live_check_test.go b/internal/pipeline/live_check_test.go
index 50c00907..723576b6 100644
--- a/internal/pipeline/live_check_test.go
+++ b/internal/pipeline/live_check_test.go
@@ -275,29 +275,43 @@ func TestShellSplit(t *testing.T) {
 // an acceptable cost for a stateless heuristic.
 func TestExtractQueryToken(t *testing.T) {
 	cases := []struct {
-		args []string
-		want string
+		args        []string
+		commandPath string
+		want        string
 	}{
-		{[]string{"goat", "brownies", "--limit", "5"}, "brownies"},
-		{[]string{"sub", "buttermilk"}, "buttermilk"},
-		{[]string{"recipe", "get", "https://example.com/recipe"}, ""},
-		{[]string{"recipe", "open", "42"}, ""},
-		{[]string{"tonight", "--max-time", "30m"}, ""},
-		{[]string{"cookbook", "list", "--json"}, ""},
-		{[]string{"cookbook"}, ""},
+		{[]string{"goat", "brownies", "--limit", "5"}, "goat", "brownies"},
+		{[]string{"sub", "buttermilk"}, "sub", "buttermilk"},
+		{[]string{"recipe", "get", "https://example.com/recipe"}, "recipe get", ""},
+		{[]string{"recipe", "open", "42"}, "recipe open", ""},
+		{[]string{"tonight", "--max-time", "30m"}, "tonight", ""},
+		{[]string{"cookbook", "list", "--json"}, "cookbook list", ""},
+		{[]string{"cookbook"}, "cookbook", ""},
 		// Verb-shaped command paths: trailing word names the view, not a
 		// search query. Without this, the relevance check would fail
 		// commands like `slices today --json` whose structured output has
-		// no reason to echo the verb.
-		{[]string{"slices", "today", "--json"}, ""},
-		{[]string{"store", "tonight", "--json"}, ""},
-		{[]string{"events", "now"}, ""},
-		{[]string{"orders", "pending", "--json"}, ""},
-		{[]string{"deals", "current"}, ""},
+		// no reason to echo the verb. The commonCommandVerb fallback
+		// covers these even without the cobra-path lookup.
+		{[]string{"slices", "today", "--json"}, "slices today", ""},
+		{[]string{"store", "tonight", "--json"}, "store tonight", ""},
+		{[]string{"events", "now"}, "events now", ""},
+		{[]string{"orders", "pending", "--json"}, "orders pending", ""},
+		{[]string{"deals", "current"}, "deals current", ""},
+
+		// Cobra-tree-aware: command paths whose trailing word is an
+		// English-shaped verb that is NOT in the static commonCommandVerb
+		// list (e.g., `find`, `top`). The commandPath argument tells us
+		// the word names a subcommand, so it's not a query.
+		{[]string{"leaderboard", "top"}, "leaderboard top", ""},
+		{[]string{"recipes", "find"}, "recipes find", ""},
+		{[]string{"hot", "browse"}, "hot browse", ""},
+
+		// But a positional that LOOKS like a command-path word but isn't
+		// in the commandPath should still be treated as a query.
+		{[]string{"recipes", "find", "ramen"}, "recipes find", "ramen"},
 	}
 	for _, tc := range cases {
-		got := extractQueryToken(tc.args)
-		require.Equal(t, tc.want, got, "args=%v", tc.args)
+		got := extractQueryToken(tc.args, tc.commandPath)
+		require.Equal(t, tc.want, got, "args=%v cmd=%q", tc.args, tc.commandPath)
 	}
 }
 
@@ -308,6 +322,18 @@ func TestOutputMentionsQuery(t *testing.T) {
 	require.False(t, outputMentionsQuery("Texas Chili Recipes", "brownies"))
 	// Tokens under 3 chars are ignored (too generic).
 	require.False(t, outputMentionsQuery("irrelevant", "to"))
+
+	// Comma-separated query tokens — each name should be checked
+	// independently against the output. Without comma-splitting, the
+	// whole "pikachu,charizard,blastoise" string was treated as one
+	// token and never matched a JSON-array shape like
+	// `["pikachu","charizard","blastoise"]` (quote+comma+quote
+	// separators break the substring match).
+	require.True(t, outputMentionsQuery(`["pikachu","charizard","blastoise"]`, "pikachu,charizard,blastoise"))
+	require.True(t, outputMentionsQuery("blastoise sighting", "pikachu,charizard,blastoise"))
+	// Mixed delimiters in the query (commas + spaces) still tokenize
+	// independently so a single-name match counts.
+	require.True(t, outputMentionsQuery("found pikachu", "pikachu, charizard"))
 }
 
 // TestLiveCheckMarshalJSON verifies the custom marshaller emits pass_rate_pct.
diff --git a/internal/pipeline/reimplementation_check.go b/internal/pipeline/reimplementation_check.go
index dce797f9..413022e4 100644
--- a/internal/pipeline/reimplementation_check.go
+++ b/internal/pipeline/reimplementation_check.go
@@ -27,6 +27,11 @@ import (
 // this check because their files call `store.Open` and consult the
 // store package. That is correctly a local-data command, not a
 // hand-rolled response.
+//
+// Raw `database/sql` access against the local SQLite file is also a
+// legitimate local-data signal: a file that imports `database/sql`
+// AND calls `sql.Open`/`sql.OpenDB` is reading the same data the
+// store package wraps through a thinner surface.
 type ReimplementationCheckResult struct {
 	// Checked is the number of built novel-feature commands inspected.
 	Checked int `json:"checked"`
@@ -78,6 +83,18 @@ var (
 	// helper.
 	storeTypeRe = regexp.MustCompile(`\b\*?store\.Store\b`)
 
+	// rawSQLImportRe catches the standard library database/sql import.
+	// The import alone is not a strong signal — it can be present for
+	// unrelated reasons — so hasStoreSignal pairs it with rawSQLOpenCallRe.
+	rawSQLImportRe = regexp.MustCompile(`"database/sql"`)
+
+	// rawSQLOpenCallRe catches the canonical sql.Open / sql.OpenDB
+	// entry points. Method calls like db.Query are intentionally NOT
+	// matched: names like Query, QueryRow, Exec collide with too many
+	// other receivers (HTTP clients, cobra commands) to be a clean
+	// signal on their own.
+	rawSQLOpenCallRe = regexp.MustCompile(`\bsql\.(Open|OpenDB)\s*\(`)
+
 	// clientImportRe catches the generated client package import:
 	// `"<module>/internal/client"`. Not every client call requires this
 	// (the command can go through `flags.newClient`), but its presence
@@ -300,7 +317,9 @@ func classifyReimplementation(files []string, fileContent map[string]string, sto
 }
 
 func hasStoreSignal(content string) bool {
-	return storeImportRe.MatchString(content) || storeCallRe.MatchString(content)
+	return storeImportRe.MatchString(content) ||
+		storeCallRe.MatchString(content) ||
+		(rawSQLImportRe.MatchString(content) && rawSQLOpenCallRe.MatchString(content))
 }
 
 func storeHelperNames(fileContent map[string]string) map[string]bool {
diff --git a/internal/pipeline/reimplementation_check_test.go b/internal/pipeline/reimplementation_check_test.go
index 940b83e2..a5975ec1 100644
--- a/internal/pipeline/reimplementation_check_test.go
+++ b/internal/pipeline/reimplementation_check_test.go
@@ -164,6 +164,100 @@ func newTrendCmd(flags *rootFlags) *cobra.Command {
 	}
 }
 
+// TestCheckReimplementation_RawDatabaseSQL_Exempted covers the carve-out
+// for novel commands that bypass the generated store package and operate
+// on the printed CLI's local SQLite file directly through database/sql.
+// Reading the same local data through a thinner surface still counts as
+// a legitimate local-data signal.
+func TestCheckReimplementation_RawDatabaseSQL_Exempted(t *testing.T) {
+	files := map[string]string{
+		"sqlquery.go": `package cli
+
+import (
+	"database/sql"
+
+	"github.com/spf13/cobra"
+)
+
+func newSQLQueryCmd(flags *rootFlags) *cobra.Command {
+	return &cobra.Command{
+		Use: "sqlquery",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			db, err := sql.Open("sqlite", "x.db")
+			if err != nil {
+				return err
+			}
+			defer db.Close()
+			rows, err := db.Query("SELECT 1")
+			if err != nil {
+				return err
+			}
+			defer rows.Close()
+			return nil
+		},
+	}
+}
+`,
+	}
+	cliDir, pipelineDir := seedReimplementationFixture(t, files, []NovelFeature{
+		{Name: "SQLQuery", Command: "sqlquery"},
+	})
+
+	got := checkReimplementation(cliDir, pipelineDir)
+	if got.Checked != 1 {
+		t.Fatalf("Checked: want 1, got %d", got.Checked)
+	}
+	if got.ExemptedViaStore != 1 {
+		t.Fatalf("ExemptedViaStore: want 1 (raw database/sql counts as local-data signal), got %d", got.ExemptedViaStore)
+	}
+	if len(got.Suspicious) != 0 {
+		t.Fatalf("Suspicious: want 0, got %d", len(got.Suspicious))
+	}
+}
+
+// TestCheckReimplementation_DatabaseSQLImportOnly_Flagged pins the
+// negative: an unrelated import of database/sql without a sql.Open call
+// is not enough to claim a local-data signal. The file still gets
+// flagged as a hand-rolled response.
+func TestCheckReimplementation_DatabaseSQLImportOnly_Flagged(t *testing.T) {
+	files := map[string]string{
+		"fake.go": `package cli
+
+import (
+	"database/sql"
+
+	"github.com/spf13/cobra"
+)
+
+var _ = sql.ErrNoRows
+
+func newFakeCmd(flags *rootFlags) *cobra.Command {
+	return &cobra.Command{
+		Use: "fake",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			cmd.Println("hardcoded response")
+			return nil
+		},
+	}
+}
+`,
+	}
+	cliDir, pipelineDir := seedReimplementationFixture(t, files, []NovelFeature{
+		{Name: "Fake", Command: "fake"},
+	})
+
+	got := checkReimplementation(cliDir, pipelineDir)
+	if got.Checked != 1 {
+		t.Fatalf("Checked: want 1, got %d", got.Checked)
+	}
+	if got.ExemptedViaStore != 0 {
+		t.Fatalf("ExemptedViaStore: want 0 (import alone is not enough), got %d", got.ExemptedViaStore)
+	}
+	if len(got.Suspicious) != 1 {
+		t.Fatalf("Suspicious: want 1, got %d", len(got.Suspicious))
+	}
+}
+
 func TestCheckReimplementation_FormatterInStoreFile_NotExempted(t *testing.T) {
 	files := map[string]string{
 		"types.go": `package cli

← 493a1b15 fix(cli): thread ctx through wrapper functions in generated  ·  back to Cli Printing Press  ·  fix(cli): migrate 15 templates off flags.printJSON + dogfood 1e4798f1 →