Skip to content

fix(search-tool): narrow guard/counter false-positive triggers (sc-1359) - #345

Merged
norvalbv merged 2 commits into
mainfrom
benjinorval/sc-1359/search-tool-guard-hook-fires-o-pr
Aug 5, 2026
Merged

fix(search-tool): narrow guard/counter false-positive triggers (sc-1359)#345
norvalbv merged 2 commits into
mainfrom
benjinorval/sc-1359/search-tool-guard-hook-fires-o-pr

Conversation

@norvalbv

@norvalbv norvalbv commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Fixes sc-1359: search-tool-guard/search-tool-counter were firing false-positive advisories on exact-identifier greps, node_modules//tmp lookups, and echo arguments bleeding through in compound commands.
  • extractPattern/target detection rewritten as a quote-aware, per-invocation segment scanner: the bin name must appear as a whole shell word, a candidate match is rejected if it falls inside a quoted region, short-/long-form space-separated value-flags and -e/--regexp pattern-flags are handled correctly (bin-aware, since fd's -e/--extension means something different from grep's), and a bare ./.. target is treated the same as no operand.
  • The guard uses a single firstAdvisablePattern call instead of a separate exclusion-check-then-pattern-extraction pair, keeping which invocation of a compound command they refer to correlated.
  • classify() gains a narrow "code snippet shape" escape hatch: declaration-modifier keywords immediately followed by an identifier AS THE LAST WORD, and nothing else.
  • Guard/counter recognize out-of-index targets (node_modules, .git, the OS temp dir; the counter also scopes to scanRoots) and skip advising/counting accordingly, with an explicit no-op streak transition. Target detection covers grep/rg/ripgrep/ack/ag/fd (pattern-first argv) and find (paths-first argv) separately.
  • An edge-case pass (manual + this repo's own guard-review gate, across eleven prior ship attempts) found and fixed 17 further bugs in this same new code via TDD.
  • search-tool-lib.mts grew past the 500-line size ratchet; split the generic Bash-command-string parsing primitives into a new search-tool-shell.mts (with matching test coverage). No new domain registration needed.

Test plan

  • bun vitest run gate-engine/search-tool/ — 109 unit + e2e tests pass (across 3 files, all well under the 500-line cap)
  • node gate-engine/search-tool/eval/eval.mts --fail — 24/24 (100%), 0 false positives/negatives
  • Full repo suite — 3362+ tests pass, 0 failures
  • bun run typecheck / bun run lint / bun run lint:structure — clean
  • devkit ship's own guard-review gate (correctness-reviewer) — 17 real findings surfaced across eleven prior attempts, all fixed with regression tests before this run

Summary by CodeRabbit

  • Improvements
    • Search detection now better handles quoted, escaped, multiline, compound, and piped shell commands.
    • Search patterns are more accurately matched to their target paths, including grep, fd, and find searches.
    • Searches in excluded locations such as node_modules, .git, temporary directories, and paths outside configured scan roots no longer affect search guidance.
    • Literal code searches and conceptual queries with punctuation are classified more accurately.
    • Search evaluations now use configured scan roots for more reliable results.

## Summary
- Fixes sc-1359: `search-tool-guard`/`search-tool-counter` were firing false-positive advisories on exact-identifier greps, `node_modules`/`/tmp` lookups, and echo arguments bleeding through in compound commands.
- `extractPattern`/target detection rewritten as a quote-aware, per-invocation segment scanner: the bin name must appear as a whole shell word, a candidate match is rejected if it falls inside a quoted region, short-/long-form space-separated value-flags and `-e`/`--regexp` pattern-flags are handled correctly (bin-aware, since `fd`'s `-e`/`--extension` means something different from grep's), and a bare `.`/`..` target is treated the same as no operand.
- The guard uses a single `firstAdvisablePattern` call instead of a separate exclusion-check-then-pattern-extraction pair, keeping which invocation of a compound command they refer to correlated.
- `classify()` gains a narrow "code snippet shape" escape hatch: declaration-modifier keywords immediately followed by an identifier AS THE LAST WORD, and nothing else.
- Guard/counter recognize out-of-index targets (`node_modules`, `.git`, the OS temp dir; the counter also scopes to `scanRoots`) and skip advising/counting accordingly, with an explicit no-op streak transition. Target detection covers `grep`/`rg`/`ripgrep`/`ack`/`ag`/`fd` (pattern-first argv) and `find` (paths-first argv) separately.
- An edge-case pass (manual + this repo's own guard-review gate, across eleven prior ship attempts) found and fixed 17 further bugs in this same new code via TDD.
- `search-tool-lib.mts` grew past the 500-line size ratchet; split the generic Bash-command-string parsing primitives into a new `search-tool-shell.mts` (with matching test coverage). No new domain registration needed.

## Test plan
- [x] `bun vitest run gate-engine/search-tool/` — 109 unit + e2e tests pass (across 3 files, all well under the 500-line cap)
- [x] `node gate-engine/search-tool/eval/eval.mts --fail` — 24/24 (100%), 0 false positives/negatives
- [x] Full repo suite — 3362+ tests pass, 0 failures
- [x] `bun run typecheck` / `bun run lint` / `bun run lint:structure` — clean
- [x] `devkit ship`'s own guard-review gate (correctness-reviewer) — 17 real findings surfaced across eleven prior attempts, all fixed with regression tests before this run
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds quote-aware shell parsing, invocation-aware pattern extraction, target-scope filtering, and improved query classification. Guard and counter behavior now ignores excluded or out-of-scope searches. Tests and evaluation cases cover these paths.

Changes

Search classification and scope filtering

Layer / File(s) Summary
Quote-aware shell parsing
gate-engine/search-tool/search-tool-shell.mts, gate-engine/search-tool/__tests__/search-tool-shell.test.mts
Adds normalization, tokenization, quote-aware segmentation, search-command detection, and unquoted regex matching.
Pattern extraction and query classification
gate-engine/search-tool/search-tool-lib.mts, gate-engine/search-tool/__tests__/search-tool-lib.test.mts, gate-engine/search-tool/eval/queries.json
Adds invocation-aware pattern extraction and classification for literal code snippets and conceptual queries.
Target correlation and scan-root filtering
gate-engine/search-tool/search-tool-lib.mts, gate-engine/search-tool/__tests__/search-tool-targets.test.mts
Adds target detection for grep-family commands, fd, and find, with universal exclusion and configured scan-root checks.
Guard, counter, and evaluation integration
gate-engine/search-tool/search-tool-guard.mts, gate-engine/search-tool/search-tool-counter.mts, gate-engine/search-tool/__tests__/search-tool-hooks.test.mts, gate-engine/search-tool/eval/eval.mts, package.json
Uses the first advisable in-scope pattern, preserves streak state for excluded searches, derives evaluation scan roots, and adds the evaluation check script.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SearchHook
  participant SearchToolCounter
  participant SearchToolGuard
  participant SearchToolLib
  SearchHook->>SearchToolCounter: submit search command
  SearchToolCounter->>SearchToolLib: check excluded targets and scan roots
  SearchToolCounter->>SearchToolGuard: evaluate eligible search
  SearchToolGuard->>SearchToolLib: select first advisable pattern
  SearchToolCounter-->>SearchHook: update or preserve streak
Loading

Possibly related PRs

  • norvalbv/devkit#54: Related search-tool tests cover grep command execution and target handling.
  • norvalbv/devkit#110: Related scan-root resolution and exclusion logic use separate implementations.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: narrowing false-positive triggers in the search-tool guard and counter.
Docstring Coverage ✅ Passed Docstring coverage is 95.45% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch benjinorval/sc-1359/search-tool-guard-hook-fires-o-pr

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
gate-engine/search-tool/__tests__/search-tool-lib.test.mts (1)

445-452: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the returned fallback pattern in the malformed-quoting tests.

These tests say extractPattern returns a result, but only assert not.toThrow(), so a future change returning null would pass. Add an explicit expected pattern assertion for both the unterminated double-quote and single-quote cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gate-engine/search-tool/__tests__/search-tool-lib.test.mts` around lines 445
- 452, Update both malformed-quoting tests in the “extractPattern /
splitUnquotedSegments” suite to assert the exact fallback pattern returned by
extractPattern, while retaining the no-throw expectation. Use the same expected
pattern for the unterminated double-quote and single-quote cases if their
graceful behavior is identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gate-engine/search-tool/search-tool-guard.mts`:
- Around line 77-85: The firstAdvisablePattern call must exclude invocations
whose targets are outside the configured scanRoots, not only targets under
EXCLUDE_ROOTS. Update firstAdvisablePattern and its caller to receive and apply
scan-root scope per invocation, preserving correlated target-pattern selection.
Add coverage for an out-of-scope-only command and a compound command that skips
the out-of-scope invocation and selects the in-scope one.

---

Nitpick comments:
In `@gate-engine/search-tool/__tests__/search-tool-lib.test.mts`:
- Around line 445-452: Update both malformed-quoting tests in the
“extractPattern / splitUnquotedSegments” suite to assert the exact fallback
pattern returned by extractPattern, while retaining the no-throw expectation.
Use the same expected pattern for the unterminated double-quote and single-quote
cases if their graceful behavior is identical.
🪄 Autofix

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 Plus

Run ID: 62209f11-e275-4584-a136-e99617382324

📥 Commits

Reviewing files that changed from the base of the PR and between 7d9cd4a and 9f52bfa.

📒 Files selected for processing (9)
  • gate-engine/search-tool/__tests__/search-tool-hooks.test.mts
  • gate-engine/search-tool/__tests__/search-tool-lib.test.mts
  • gate-engine/search-tool/__tests__/search-tool-shell.test.mts
  • gate-engine/search-tool/eval/queries.json
  • gate-engine/search-tool/search-tool-counter.mts
  • gate-engine/search-tool/search-tool-guard.mts
  • gate-engine/search-tool/search-tool-lib.mts
  • gate-engine/search-tool/search-tool-shell.mts
  • package.json

Comment thread gate-engine/search-tool/search-tool-guard.mts Outdated
… just EXCLUDE_ROOTS

Addresses PR review feedback on #345:

- `firstAdvisablePattern` now also skips an invocation whose target is outside the consumer's configured `scanRoots` (previously it only checked the universal `EXCLUDE_ROOTS`), keeping the per-invocation correlation between exclusion-check and pattern-selection that this function exists for. `search-tool-guard.mts` passes `scanRoots` through from the same `resolveGuardConfig()` call already used for tool names.
- `eval/eval.mts` no longer hardcodes `src/` as its synthetic test target — it now derives the target from the resolved `scanRoots`, so the eval harness stays valid on any consumer (including devkit itself, whose own `scanRoots` is `["cli","gate-engine"]`, not `src`) now that the guard scopes to it.
- Found and fixed two real bugs while wiring this up (both surfaced by this repo's own guard-review gate mid-fix, both verified and fixed via TDD before this push):
  1. A pre-filter approach that stripped bare `.`/`..` cwd-ref targets before the exclusion checks ran broke MIXED target lists (e.g. `grep ... node_modules .`) — removing `.` left only `node_modules`, which then matched every remaining root and was wrongly classified as fully excluded. Fixed by moving the cwd-ref awareness INTO the match predicate itself (`allTargetsMissEveryRoot`) instead of pre-filtering, so a cwd-ref's presence correctly keeps a mixed-target invocation in scope.
  2. A mechanical slip while editing `search-tool-hooks.test.mts` — an edit to `runGuard()` accidentally dropped its `return` statement.
- Nitpick: tightened the two "unterminated quote" crash-safety tests to assert the exact fallback pattern returned, not just that the call doesn't throw.
- `search-tool-lib.test.mts` grew past the size ratchet again from this round's additions; split the out-of-index target-detection tests (isExcludedTarget/isOutOfScanRoots/firstAdvisablePattern/Windows paths/find-fd) into a new `search-tool-targets.test.mts`, keeping pattern-classification tests (extractPattern/classify) in `search-tool-lib.test.mts`.

## Test plan
- [x] `bun vitest run gate-engine/search-tool/` — 118 unit + e2e tests pass (across 4 files, all well under the 500-line cap)
- [x] `node gate-engine/search-tool/eval/eval.mts --fail` — 24/24 (100%), 0 false positives/negatives
- [x] Full repo suite — 3372 tests pass, 0 failures
- [x] `bun run typecheck` / `bun run lint` / `bun run lint:structure` — clean

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
gate-engine/search-tool/__tests__/search-tool-targets.test.mts (1)

99-105: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding the attached value-flag form.

Line 104 covers the space-separated form -A 3. The attached form -A3 is equally common and takes a different parsing path. If the parser supports it, add one assertion to pin the behavior.

♻️ Suggested extra assertion
     expect(isExcludedTarget('grep -A 3 cli node_modules/foo.js', ['node_modules'])).toBe(true);
+    // Attached form: the value rides on the flag token, so no separate value token is consumed.
+    expect(isExcludedTarget('grep -A3 cli node_modules/foo.js', ['node_modules'])).toBe(true);
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gate-engine/search-tool/__tests__/search-tool-targets.test.mts` around lines
99 - 105, Extend the test case around isExcludedTarget to also assert that the
attached value form -A3 does not misclassify the following pattern as a target,
while still recognizing the trailing node_modules/foo.js path as excluded.
Preserve the existing space-separated assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@gate-engine/search-tool/__tests__/search-tool-targets.test.mts`:
- Around line 99-105: Extend the test case around isExcludedTarget to also
assert that the attached value form -A3 does not misclassify the following
pattern as a target, while still recognizing the trailing node_modules/foo.js
path as excluded. Preserve the existing space-separated assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d0cfac1-1167-418c-ab37-729bccf47910

📥 Commits

Reviewing files that changed from the base of the PR and between 9f52bfa and c28a0b3.

📒 Files selected for processing (6)
  • gate-engine/search-tool/__tests__/search-tool-hooks.test.mts
  • gate-engine/search-tool/__tests__/search-tool-lib.test.mts
  • gate-engine/search-tool/__tests__/search-tool-targets.test.mts
  • gate-engine/search-tool/eval/eval.mts
  • gate-engine/search-tool/search-tool-guard.mts
  • gate-engine/search-tool/search-tool-lib.mts
🚧 Files skipped from review as they are similar to previous changes (3)
  • gate-engine/search-tool/search-tool-guard.mts
  • gate-engine/search-tool/tests/search-tool-hooks.test.mts
  • gate-engine/search-tool/search-tool-lib.mts

@norvalbv
norvalbv merged commit 69feca4 into main Aug 5, 2026
2 checks passed
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