Repository navigation
Conversation
nc-review: needs work — 1 blocking, 1 important, 2 nits@tusharui — there is a blocking item below. PR #1369 addresses a documented bug (issue #1341) where ripgrep's match-count kill budget could be exhausted by matches in default-ignored directories, leaving downstream JS filters with zero kept results. The described two-layer fix (only count kept matches; pass a merged ignore file to rg) is conceptually correct and aligns with the architecture in source/utils/file-search.ts. The changeset file is appropriate for a user-facing bug fix. However, only one of the seven claimed new tests ('a .nanocoderignore directory is pruned during traversal') is visible in source/utils/file-search.spec.ts on disk; the regression tests named for the bug (#1341 plain, #1341 with context, 🔴 blocking · The PR description claims seven new AVA tests: '#1341 plain', '#1341 with context', '**/-anchored rebase guard', 'walkProjectEntries pruning', 'rebaseIgnoreLine unit suite', '! re-include', and 'abort temp-dir cleanup'. Only one — 'a .nanocoderignore directory is pruned during traversal, not just filtered from results' (around line 411) — is visible in the spec file on disk. The two regression tests that would actually fail if the Layer 1 (kept-match-only counting) fix regressed — ones that build a fixture with many matches in a default-ignored directory (e.g. dist/) and a few matches in source/, then call Reproducing #1341 in a test looks like this (sketch — adapt to the actual API): mkdirSync(join(testDir, 'dist'), {recursive: true});
mkdirSync(join(testDir, 'src'), {recursive: true});
// dist/ contains 20 matches for the query
for (let i = 0; i < 20; i++) writeFileSync(join(testDir, 'dist', `f${i}.js`), 'searchTarget');
writeFileSync(join(testDir, 'src', 'app.ts'), 'searchTarget');
const result = await searchProjectContents('searchTarget', testDir, 5, false);
t.is(result.matches.length, 1);
t.is(result.matches[0]?.file, 'src/app.ts');A test like this would have failed on the base code (0 matches) and passes after the fix (1 match). Without it, the fix could regress silently. 🟠 important · The diff adds a new ⚪ nit · I cannot fully verify the correctness of the new ⚪ nit · The changeset is present and well-shaped (closes the issue, patch bump, names the right package). No issue here — flagging only because the rubric asks reviewers to confirm the changeset, and this one is correct. 🔴 blocking · 🟠 a reviewer would ask for a change · ⚪ optional Automated code review — correctness, security, design, tests, plus duplicates and scope. A human still decides; this is not a substitute for review and is not exhaustive. The required status checks separately cover lint, formatting, types, unused dependencies, the test suite and the build. This bot never merges. Maintainers can rerun with |
…aseIgnoreLine function
will-lamerton
left a comment
There was a problem hiding this comment.
Thanks @tusharui, the search fix itself looks right: counting only kept matches plus the merged --ignore-file addresses #1341, and the regression tests are there (the bot's blocking item missed them). Requesting changes mainly on scope.
Please split out of this PR:
source/tool-calling/xml-parser.ts- a real behaviour change to fallback tool-call parsing with no tests. It's a good fix (fenced blocks inside<content>and=in values like--color=autoare dropped on main), so it deserves its own PR with those cases as specs.- The
nosemgrepannotations - Semgrep is green on main without them, and thebash-sandbox.tsone sits on the wrong line for a line-level suppression. pnpm-workspace.yaml(ignoreGhsas: null->{}) - pnpm rewrite churn, please revert.scripts/test-all.mjs/test:all- separate tooling PR.
Smaller points on the search change:
--ignore-fileanchored rules (e.g./src/generated/) don't prune when the path goes through a symlink, since rg anchors at the resolved cwd. macOStmpdir()hits this, so the "cwd-anchored ... do not over-prune" test passes only via Layer 1 there. Results stay correct thanks to Layer 1, butrealpath-ing the search root would make Layer 2 do what its doc comment says.- "walkProjectEntries prunes ignored directories during traversal" passes on the JS filter alone; to prove traversal pruning it needs a signal like
maxRawFilesScanned, as the existing.nanocoderignoretest does. - In the
searchProjectContentsonLine, checkparsed.typebefore readingparsed.data.path(or usedata?.) so a line withoutdatacan't throw inside the stream handler.
Please also merge main for a fresh CI run; the current failure (UserInput redoes an undone edit with ctrl+y) is unrelated.
|
Hi @tusharui, thanks for this PR! It looks like a maintainer has left feedback Whenever you get a chance, could you take a look at the open comments? |
The fenced-block and attribute-style parsing change is a real behaviour change to fallback tool-call parsing, so it belongs in its own PR with those cases as specs. Revert it here to keep Nano-Collective#1369 scoped to the search fix (Nano-Collective#1341).
Semgrep is green on main without them and the bash-sandbox.ts one sat on the wrong line for a line-level suppression, so neither masks anything real.
ignoreGhsas -> {} is pnpm churn with no behavioural change in this PR.
The cross-platform runner is its own tooling change; this PR only needs the search fix.
rg's begin/end/summary records carry no data, so reading data.path first could throw inside the stdout stream handler - where an exception escapes as an uncaught error instead of failing the search. Marks data optional and checks type first.
… filter The old assertion also passed when rg streamed the ignored files and the JS filter dropped them afterwards. Dense ignored files plus maxRawFilesScanned make a streamed-then-filtered walk report truncated instead, so only traversal pruning passes - matching the .nanocoderignore test.
…hrough a symlink rg anchors --ignore-file rules at the cwd it resolves for itself, but reported paths stay under the root spelling it was handed. Whenever the two disagree - macOS tmpdir(), or any symlinked project root - an anchored rule such as /src/generated/ prunes nothing and Layer 2 silently degrades to the JS-side filter. Resolve both sides to the same real path, and rebase results back onto the caller's cwd so reported paths never leak the resolved parent.
There was a problem hiding this comment.
Thanks @tusharui, the scope is clean now and all the review points are in. I checked it: against main's file-search.ts 9 of the new tests fail, and #1341 reproduces with the real ripgrep (searching src/ with a root .gitignore that ignores generated/ gives "No matches found" on main and finds src/main.ts here). Types, lint, knip and Semgrep are clean, CI is green, and per-search overhead is about 5 ms.
Approving 🎉. @will-lamerton your review points (scope split, realpath for anchored rules, traversal-pruning test, narrowing the JSON line on type) look addressed to me; could you re-check?
|
Hi @tusharui, thanks for this PR! It looks like a maintainer has left feedback Whenever you get a chance, could you take a look at the open comments? |
|
Hi @tusharui, just a heads-up that this PR still has outstanding items If we don't hear back within about a week, we'll close it to keep the queue tidy. |
|
Closing this PR for now since there hasn't been any activity from @tusharui after the earlier heads-up.No worries at all - this isn't a rejection. If you'd like to pick it back up, just push a new commit or leave a comment and reopen it. Thanks for the contribution! |
Summary
Closes #1341.
search_file_contentscould return 0 matches (withtruncated: true)when ripgrep's early-kill match budget (
rgMaxCount) was consumed by matches insideignored directories. ripgrep still counts matches in ignored paths toward its
budget, so those lines were burned before reaching any kept result — and since the
only drop to ripgrep was the
--max-count, the JS filter downstream never got akept line at all.
Fix
Two layers, applied together:
Layer 1 — don't count ignored output against the budget
runRipgrepnow streams results viaonLineand increments the kill counter onlyfor kept (non-ignored)
type: "match"lines, againstrgMaxCount = maxResults + contextLines.Layer 2 — keep ignored paths out of ripgrep entirely
A merged ignore set (
DEFAULT_IGNORE_DIRS→.gitignore→.nanocoderignore) iswritten to a temp
--ignore-fileand wired into bothsearchProjectContentsandwalkProjectEntries, so rg never traverses/matches ignored directories in the first place.Correctness backstop: the existing downstream JS filter (
projectIgnore.ignores) still applies.Hardening (follow-up commit, from maintainer review)
**/-prefixed rules kept verbatim (git depth-agnostic semantics)mode: 0o600.gitignoreskips the temp file entirelyonLineis tri-state (keep | skip | stop);stdoutholds only kept lineswalkUnsortedFileStream/searchProjectContentsDev tooling (same branch)
pnpm test:allnow runs viascripts/test-all.mjs(cross-platform Node orchestrator,same 7 gates as
scripts/test.sh), so it works on Windows too.Testing
**/-anchored rebase guard,walkProjectEntriespruning,rebaseIgnoreLineunit suite,!re-include,abort temp-dir cleanup
test:format,test:lint,test:types,test:types:vscode,test:knip,test:auditavafailures are pre-existing Windows-environment issues(confirmed reproducible on pre-fix code) — unaffected by this change
Changeset
.changeset/search-ignored-dir-budget.md—patch— Closes #1341