Skip to content

feat(wren): add store-tip hint after successful query#1508

Merged
goldmedal merged 3 commits intoCanner:feat/cli-0.2.0from
goldmedal:feat/store-tip
Apr 4, 2026
Merged

feat(wren): add store-tip hint after successful query#1508
goldmedal merged 3 commits intoCanner:feat/cli-0.2.0from
goldmedal:feat/store-tip

Conversation

@goldmedal
Copy link
Copy Markdown
Contributor

@goldmedal goldmedal commented Apr 2, 2026

Summary

  • After a successful wren --sql or wren query --sql execution, print a wren memory store hint to stderr so agents and humans get an explicit nudge to save the NL-SQL pair
  • A --quiet / -q flag suppresses the tip for automation pipelines (stdout remains clean regardless)
  • Exploratory peek queries (bare SELECT … LIMIT with no WHERE / GROUP BY / HAVING / aggregate / CTE) are filtered via a lightweight sqlglot AST check and never shown the tip

New files

File Purpose
wren/src/wren/sql_classify.py is_exploratory(sql) — sqlglot AST heuristic
wren/tests/unit/test_sql_classify.py 13 unit tests for heuristic edge cases
wren/tests/unit/test_cli_store_tip.py 9 CLI integration tests (mocked engine)

Test plan

  • just test-unit — all 22 new tests pass
  • just lint — ruff format + check clean
  • Verify tip appears on stderr after a real query (wren --sql "SELECT COUNT(*) FROM orders")
  • Verify --quiet suppresses tip (wren --sql "..." -q)
  • Verify exploratory query shows no tip (wren --sql "SELECT * FROM orders LIMIT 5")

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a --quiet flag to suppress optional CLI informational tips for cleaner scripting.
    • CLI now emits a reusable "save this query" tip for non-exploratory queries (tip appears on stderr and includes a shell-ready suggestion; suppressed by --quiet).
  • Tests

    • Added unit tests validating tip emission, suppression, content, and query classification.

Print a `wren memory store` hint to stderr after a non-exploratory query
succeeds, so agents and humans get an explicit nudge to save the NL-SQL pair.
A `--quiet`/`-q` flag suppresses the tip for automation pipelines. Exploratory
peek queries (bare SELECT + LIMIT, no WHERE/GROUP BY/HAVING/aggregate/CTE) are
filtered via a lightweight sqlglot AST check and never shown the tip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ffa51c30-c825-4b7b-ad9f-6d38558bd5e2

📥 Commits

Reviewing files that changed from the base of the PR and between 666325f and 49cca0d.

📒 Files selected for processing (3)
  • wren/src/wren/sql_classify.py
  • wren/tests/unit/test_cli_store_tip.py
  • wren/tests/unit/test_sql_classify.py
✅ Files skipped from review due to trivial changes (2)
  • wren/tests/unit/test_sql_classify.py
  • wren/tests/unit/test_cli_store_tip.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • wren/src/wren/sql_classify.py

📝 Walkthrough

Walkthrough

Adds a CLI --quiet/-q option and prints a conditional stderr "wren memory store" tip after queries; introduces wren.sql_classify.is_exploratory(sql) to classify SELECTs without WHERE/GROUP/HAVING/aggregates/CTE/UNION as exploratory and suppress the tip when appropriate or when quiet is set.

Changes

Cohort / File(s) Summary
CLI Store-Tip
wren/src/wren/cli.py
Added --quiet/-q option (type alias QuietOpt) to main and query. Added helpers _print_store_tip() and _maybe_print_store_tip(); after printing results the CLI calls the classifier and, if non-exploratory and not quiet, writes a stderr "wren memory store" command with the SQL (single quotes escaped).
SQL Classification
wren/src/wren/sql_classify.py
New is_exploratory(sql: str) -> bool using sqlglot: returns True for a single top-level SELECT that has no top-level WHERE, GROUP BY, HAVING, CTE (WITH), UNION, or aggregate functions; returns False for parse errors, multi-statement, non-SELECTs, or queries containing those constructs.
CLI Store-Tip Tests
wren/tests/unit/test_cli_store_tip.py
New tests using CliRunner and a mocked engine to assert the tip is emitted to stderr for analytical SQL, suppressed for exploratory SQL and when --quiet is used; verifies SQL appears in tip and single quotes are escaped.
SQL Classification Tests
wren/tests/unit/test_sql_classify.py
New parametrized tests for is_exploratory() covering exploratory SELECT variants (with/without LIMIT, DISTINCT, inner subquery LIMIT), and non-exploratory cases (aggregates, WHERE, GROUP BY, HAVING, UNION, WITH), plus parse-failure and empty-string checks.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI
    participant Engine
    participant Classifier
    participant StdErr

    User->>CLI: invoke command / query
    CLI->>Engine: execute SQL
    Engine-->>CLI: return results
    CLI->>User: print results (stdout)
    CLI->>Classifier: is_exploratory(sql)
    Classifier-->>CLI: exploratory? (True/False)
    alt not exploratory and not quiet
        CLI->>StdErr: print "wren memory store" tip (escaped SQL)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hop through lines of SQL bright,
A quiet flag to hush the night,
Tips for heavy queries left in bark,
Gentle peeks stay light and dark,
I tuck your query, soft and slight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(wren): add store-tip hint after successful query' is specific and directly summarizes the main change: adding a hint feature after queries.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
wren/src/wren/cli.py (1)

266-270: Deduplicate post-query tip gating into a helper.

Both command paths repeat the same quiet + is_exploratory logic. Extracting this keeps behavior aligned and easier to maintain.

Proposed refactor
+def _maybe_print_store_tip(sql: str, quiet: bool) -> None:
+    if quiet:
+        return
+    from wren.sql_classify import is_exploratory  # noqa: PLC0415
+    if not is_exploratory(sql):
+        _print_store_tip(sql)

@@
-    if not quiet:
-        from wren.sql_classify import is_exploratory  # noqa: PLC0415
-
-        if not is_exploratory(sql):
-            _print_store_tip(sql)
+    _maybe_print_store_tip(sql, quiet)

@@
-    if not quiet:
-        from wren.sql_classify import is_exploratory  # noqa: PLC0415
-
-        if not is_exploratory(sql):
-            _print_store_tip(sql)
+    _maybe_print_store_tip(sql, quiet)

Also applies to: 294-298

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wren/src/wren/cli.py` around lines 266 - 270, Both command paths duplicate
the gating logic that checks quiet and is_exploratory before calling
_print_store_tip; extract this into a small helper (e.g.,
_should_print_store_tip or maybe print_store_tip_if_applicable) that takes the
sql and quiet flag, imports/uses is_exploratory and calls _print_store_tip when
appropriate, and then replace the duplicated blocks at the two locations (the
current checks around _print_store_tip at lines shown) with a single call to
that helper to keep behavior aligned and easier to maintain.
wren/tests/unit/test_cli_store_tip.py (1)

34-34: Use explicit None handling for mock result fallback.

result or _MOCK_TABLE can overwrite intentional falsey test values. Prefer is None for deterministic fixture behavior.

Proposed tweak
-    mock_engine.query.return_value = result or _MOCK_TABLE
+    mock_engine.query.return_value = _MOCK_TABLE if result is None else result
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wren/tests/unit/test_cli_store_tip.py` at line 34, The test currently sets
mock_engine.query.return_value = result or _MOCK_TABLE which treats falsey but
valid values (e.g., empty list, 0, False) as missing; change the fallback to
explicit None checking so mock_engine.query.return_value is set to result if
result is not None, otherwise to _MOCK_TABLE (reference symbols:
mock_engine.query.return_value, result, _MOCK_TABLE).
wren/tests/unit/test_sql_classify.py (1)

13-37: Add a regression case for nested LIMIT in subqueries.

Please add a case where only an inner subquery has LIMIT; this guards the classifier against descendant-vs-top-level clause confusion.

Suggested test case
     [
@@
         # SUM
         ("SELECT SUM(total) FROM orders", False),
+        # Inner LIMIT only (outer SELECT has no LIMIT)
+        ("SELECT * FROM (SELECT * FROM orders LIMIT 5) t", False),
     ],
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wren/tests/unit/test_sql_classify.py` around lines 13 - 37, Add a regression
case to the test tuple list that ensures LIMIT inside a nested subquery does not
mark the top-level query exploratory: add a case where the top-level SELECT has
no LIMIT but contains a subquery with LIMIT (for example a query selecting from
"(SELECT ... LIMIT 1)") and assert the classifier returns False; update the list
of cases in test_sql_classify.py (the parameterized test/cases list shown in the
diff) with this new tuple.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wren/tests/unit/test_cli_store_tip.py`:
- Around line 120-129: The test test_tip_escapes_single_quotes_in_sql currently
has a weak/invalid assertion (a stray `}` and not matching the actual escaped
payload). Update the assertion to check for the correctly escaped --sql argument
in result.stderr by asserting the exact escaped payload generated by the CLI,
e.g. that the stderr contains the string "--sql 'SELECT * FROM orders WHERE
status = '\\''open'\\''" (remove the stray `}` and replace the loose
"'\\''open'\\''" check with a full exact match of the escaped --sql value so
regressions in quote-escaping will fail the test).

---

Nitpick comments:
In `@wren/src/wren/cli.py`:
- Around line 266-270: Both command paths duplicate the gating logic that checks
quiet and is_exploratory before calling _print_store_tip; extract this into a
small helper (e.g., _should_print_store_tip or maybe
print_store_tip_if_applicable) that takes the sql and quiet flag, imports/uses
is_exploratory and calls _print_store_tip when appropriate, and then replace the
duplicated blocks at the two locations (the current checks around
_print_store_tip at lines shown) with a single call to that helper to keep
behavior aligned and easier to maintain.

In `@wren/tests/unit/test_cli_store_tip.py`:
- Line 34: The test currently sets mock_engine.query.return_value = result or
_MOCK_TABLE which treats falsey but valid values (e.g., empty list, 0, False) as
missing; change the fallback to explicit None checking so
mock_engine.query.return_value is set to result if result is not None, otherwise
to _MOCK_TABLE (reference symbols: mock_engine.query.return_value, result,
_MOCK_TABLE).

In `@wren/tests/unit/test_sql_classify.py`:
- Around line 13-37: Add a regression case to the test tuple list that ensures
LIMIT inside a nested subquery does not mark the top-level query exploratory:
add a case where the top-level SELECT has no LIMIT but contains a subquery with
LIMIT (for example a query selecting from "(SELECT ... LIMIT 1)") and assert the
classifier returns False; update the list of cases in test_sql_classify.py (the
parameterized test/cases list shown in the diff) with this new tuple.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b5af503-4053-403e-a80b-60a926c6682f

📥 Commits

Reviewing files that changed from the base of the PR and between 8f15e8c and 4aaf000.

📒 Files selected for processing (4)
  • wren/src/wren/cli.py
  • wren/src/wren/sql_classify.py
  • wren/tests/unit/test_cli_store_tip.py
  • wren/tests/unit/test_sql_classify.py

goldmedal and others added 2 commits April 2, 2026 14:59
- Extract _maybe_print_store_tip() helper to deduplicate the quiet+is_exploratory
  gating in main() and query()
- Fix is_exploratory() to check top-level clause args (stmt.args) instead of
  find() so an inner LIMIT inside a subquery doesn't incorrectly mark the outer
  query as exploratory; CTE detection now uses stmt.args["with_"] key
- Fix mock result fallback to use explicit is None check
- Strengthen quote-escaping assertion to match exact --sql payload in stderr
- Add regression test: SELECT * FROM (subquery LIMIT n) returns False

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A plain SELECT with no WHERE/GROUP BY/HAVING/aggregate is exploratory
whether or not it has LIMIT — both are just "show me what's there".
Remove the LIMIT requirement from the is_exploratory heuristic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@goldmedal goldmedal requested a review from douenergy April 2, 2026 07:34
@goldmedal goldmedal changed the base branch from main to feat/cli-0.2.0 April 4, 2026 12:21
@goldmedal goldmedal merged commit 62da428 into Canner:feat/cli-0.2.0 Apr 4, 2026
9 checks passed
@goldmedal goldmedal deleted the feat/store-tip branch April 4, 2026 12:22
goldmedal added a commit that referenced this pull request Apr 4, 2026
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant