Skip to content

fix(mcp): match search_code file_pattern with a directory against the repo-relative path - #7

Open
halindrome wants to merge 56 commits into
mainfrom
fix/search-code-file-pattern-path
Open

halindrome wants to merge 56 commits into
mainfrom
fix/search-code-file-pattern-path

Conversation

@halindrome

Copy link
Copy Markdown
Owner

Summary

search_code matched file_pattern against the file basename only. Any pattern containing a directory, such as src/cli/*.c or apps/web/src/**/*.ts, silently returned 0 results, while search_graph matches the same pattern against the full repo-relative path.

Upstream issue: DeusData#2836

Change (src/mcp/mcp.c)

  • New portable matcher, search_code_path_glob_match. It runs in linear time and doesn't use fnmatch, which Windows lacks.
    • * and ** match across /, and **/ also matches zero directories.
    • ? matches one character.
    • A pattern with no wildcards is a substring match, the same as search_graph.
  • write_scoped_filelist: a file_pattern containing / is matched against the repo-relative path on every platform. Windows separators are normalised first. Patterns without / keep the existing basename behaviour.
  • Windows: / patterns are scanned in process from the already-filtered list, not by the PowerShell -like post-filter. cbm_search_code_file_pattern_can_prefilter already rejects /, so nothing is filtered twice.
  • When a / pattern yields zero matches, the response carries a hint pointing at path_filter, in both tree and JSON output.
  • The schema now describes file_pattern ("Glob on file name; with '/', on repo-relative path (** = any dirs).") and path_filter ("Regex on repo-relative path.").

Acceptance criteria

  1. A file_pattern containing / is matched against the repo-relative path, with ** crossing directories (including zero directories).
  2. A file_pattern without / keeps the current basename semantics.
  3. The behaviour is the same on POSIX and Windows for indexed (scoped) searches.
  4. A / pattern that yields 0 results returns a hint pointing at path_filter.
  5. The tool schema describes both file_pattern and path_filter.
  6. Existing mcp suite tests stay green.

Known limits

  • Unscoped searches (an unindexed root) still use find -name, so a / pattern there returns 0. The hint still fires.
  • * crosses /, and matching is case-sensitive. Both are consistent with search_graph and with the existing basename match.

Testing

  • New search_code_file_pattern_matches_repo_relative_path_issue2836 in tests/test_mcp.c, with fixture files src/main.c and src/cli/watcher.c:
    • src/cli/*.c → 1 match
    • src/**/*.c → 2
    • **/cli/*.c → 1
    • src/cli/ → 1
    • watcher.c → 1
    • *.c → 2
    • lib/*.c → 0, with the hint
  • Red-first check: with the original mcp.c, the new test fails, because the four directory patterns return 0 and there is no hint.
  • scripts/test.sh --suites mcp: 397 passed, 4 skipped, 0 failed.
  • The Windows path was changed by reading only; it hasn't been run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CDDUoWEZMNZ9fLvzp2pPGT

Jumaga2015 and others added 30 commits September 1, 2026 21:37
cbm_store_fetch_call_edges, cbm_leiden/cbm_louvain, and
cbm_layout_to_json document caller-owned memory ("caller frees
both" / "the caller frees it" / "Caller must free()") but exported
no matching free function, unlike the rest of the API
(cbm_store_find_nodes -> cbm_store_free_nodes,
cbm_layout_compute -> cbm_layout_free, etc.). From C a bare free()
works via static linking, but a caller across an FFI boundary (e.g.
C# P/Invoke) has no way to reach the DLL's internal free() unless
it is exported by name.

Adds three paired free functions following the existing pattern:
- cbm_store_free_call_edges(src, tgt)
- cbm_leiden_free(result)
- cbm_layout_free_json(json)

Updates the one internal production caller (mcp.c SCC analysis) to
use cbm_store_free_call_edges instead of a bare free(), and the
existing unit tests for cbm_leiden/cbm_louvain/cbm_layout_to_json
to use the new paired frees. Adds fetch_call_edges_basic,
fetch_call_edges_truncated, and fetch_call_edges_empty_project to
test_store_arch.c, covering cbm_store_fetch_call_edges /
cbm_store_free_call_edges, which had no prior test coverage.

Fixes DeusData#1762

Signed-off-by: Jumaga2015 <jumaga2015@gmail.com>
Signed-off-by: lorenzozanee <wyz0707@proton.me>
A record's accessors are implicit, so `record Point(double x, double y,
double z)` declares no `x()` and extraction emitted no node for one. The call
`p.x()` had nothing to resolve to, fell through to the short-name registry,
and bound to the first `x` in the project — typically an unrelated class's
private field. Measured on two indexed Java trees, that was 278 of 2444 and
2649 of 29509 CALLS edges.

Extraction now emits one Method definition per record component, skipping any
accessor the record declares itself (JLS 8.10.3), so the accessor has a node
and the call resolves to it.

And a Java call may no longer bind to a Variable or a Field. A Java call
expression never names data, so such an edge is a spelling collision, not a
call. The guard follows cbm_go_suppress_bare_field_ref — a pure, label-keyed
predicate — and is called from both pass_calls.c and pass_parallel.c so the
sequential and parallel resolvers stay identical. Java-gated: Kotlin
properties hold function values and C has function pointers, so both
legitimately call through data.

Signed-off-by: MopicMP <obshiq123@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…y build

The shared tier-2 registry registered Python defs one at a time without a
mid-build finalize, so every Method's receiver probe scanned all registered
types linearly: O(methods x types), single-threaded in lsp_cross_prepare.
Keep the per-def registration order and back only the existence probe with a
cbm_ht set of type QNs, as the Go and Rust builders do; the registry is
identical by construction.

On the Python 3.14 stdlib + site-packages the builder drops from 67-75 s to
0.20-0.25 s, with a byte-identical graph.

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
Brings the branch up to main at a222715. Two conflicts, both resolved by
keeping both sides:

- src/ui/layout3d.h: the new cbm_layout_free_json declaration sits
  before main's region-level layout section.
- tests/test_store_arch.c: the Leiden/Louvain tests release their
  results through the new cbm_leiden_free; main's
  leiden_result_memory_core_ownership test is kept.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The three exported release functions added for DeusData#1762 freed raw heap
blocks. Since the memory-core ratchet landed, the allocating side has to
go through the core as well, and on current main cbm_leiden already
allocates its result in CBM_MEM_CLASS_OTHER, so a raw free() in
cbm_leiden_free left the class counters drifting.

- cbm_store_fetch_call_edges: allocate and grow the source/target
  arrays with cbm_alloc/cbm_realloc in CBM_MEM_CLASS_STORE. An
  allocation failure now finalizes the statement, releases both arrays
  and returns CBM_STORE_ERR instead of writing through a NULL array.
- cbm_store_free_call_edges, cbm_leiden_free and cbm_layout_free_json
  release with cbm_free in the class the matching allocation used.
- cbm_layout_to_json writes its output through cbm_json_writer_allocator,
  so the string is an OTHER-class core block; the UI server's five
  release sites now call cbm_layout_free_json.
- The header comments name the paired free instead of "caller frees".
- Tests: ownership checks for all three pairs measure the class counters
  across the allocating call and the paired free, so a raw allocation on
  either side fails; the call-edge check grows past the initial capacity.

Refs DeusData#1762

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings DeusData#2420 (O(1) type membership in the shared Python cross-registry
build, DeusData#2413) up to date with main a222715.

Conflicts in internal/cbm/lsp/py_lsp.c, resolved as follows:

- Includes: main already includes src/foundation/hash_table.h (module
  re-exports) and added lsp_node_iter.h. Main's lines are kept; the
  branch's duplicate include is dropped.

- py_register_lsp_defs: main changed it to return bool (false when the
  package scope cannot be normalized). The merged signature keeps main's
  bool return and adds this branch's PyKnownTypes *known parameter.

- cbm_run_py_lsp_cross_status: main's failure handling is kept and
  passes known = NULL, as the branch did for the per-file path.

- cbm_py_build_cross_registry: main added an early `return NULL` when a
  def fails to register. The merged loop passes &known and frees the
  type-QN set before that early return, so the set does not leak on the
  failure path.

- The test seam cbm_py_lsp_test_register_defs (added on main) passes
  known = NULL.

tests/test_py_lsp.c and tests/test_py_lsp_scale.c merged cleanly.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Replace the wall-clock ratio test pylsp_scale_cross_registry_build_linear
(py_lsp_scale suite) with pylsp_cross_registry_build_work_is_linear in
the py_lsp suite. The new test builds the same generated input (n
classes, each followed by two methods, in extraction order) and reads
the seam-build CBM_LSP_WORK counter instead of a clock, so its verdict
does not depend on host load, and it runs in PR CI, which skips the
perf suites.

4x the classes must cost at most ~4x the counted work. With the
type-QN set the build examines no registry candidates (0 at 1000 and
4000 classes); with the scanning receiver probe the work grows from
2,809,000 to 23,236,000 and the test fails.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings DeusData#2244 (agent detection through the UTF-8 path wrapper) up to
date with main a222715. The branch was based on 45c45be, 1,323
commits behind.

The merge is clean. The merged tree adds exactly this branch's 22
lines to main and removes 6, in src/cli/cli.c and tests/test_cli.c.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The Windows regression test for DeusData#2145 created its profile directory
with test_mkdirp and read the result as a boolean. test_mkdirp returns
0 on success, so the test failed as soon as the directory existed and
never reached the detection it was written to check.

Inverting the check is not enough on its own. test_mkdirp uses
cbm_mkdir, which is the narrow _mkdir on Windows. Under an ANSI code
page such as 1252 it does not create the intended name: it creates a
directory whose name is the UTF-8 bytes read as code-page characters.
The old narrow stat finds that mangled directory and the new wide
lookup correctly does not, so the test would pass without the fix and
fail with it. th_mkdir_p goes through the wide API and creates the
real name, the way a non-ASCII Windows profile actually looks.

Since 2b329fe, Claude Code is detected from settings.json,
.claude.json or the claude CLI, through cbm_file_exists, which is
already wide on Windows; a bare .claude directory no longer marks it
as detected. The test now asserts Cursor, which is detected by its
directory alone, so the assertion goes through dir_exists, the
function this branch changes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Make this repository a Claude Code plugin marketplace so users can find
and add the server from inside Claude Code:

  claude plugin marketplace add DeusData/codebase-memory-mcp
  claude plugin install codebase-memory@codebase-memory

This is slice 1 of DeusData#1188: discovery only. The plugin registers the MCP
server and nothing else; the skill, the graph agents and the context
hooks still come from `codebase-memory-mcp install`.

- .claude-plugin/marketplace.json: one plugin, sourced from ./plugin.
- plugin/.claude-plugin/plugin.json: version pinned to the current
  release. Without a pinned version every commit to main would be a new
  plugin version for installed users.
- plugin/.mcp.json: stdio server `npx -y codebase-memory-mcp@0.12.1`.
  The pin keeps main from advertising a server version npm does not
  have; the npm package downloads the release binary on first start and
  verifies it against the release checksums.
- .gitignore: the root .mcp.json stays ignored; plugin/.mcp.json is
  published.
- README: install section, and "use one or the other": with both the
  plugin and `install`, Claude Code runs the server twice under two tool
  namespaces.
- tests/test_version_metadata_contract.sh: both plugin files are
  release-tracking surfaces; a new claim form reads the npm package spec
  (`codebase-memory-mcp@X.Y.Z`), and the unregistered-surface sweep now
  covers plugin/ and .claude-plugin/. The post-release sync that bumps
  server.json must bump these pins too, or this contract goes red.

No language count is stated anywhere in the plugin metadata, so the
language-count contract has nothing new to track.

Refs DeusData#1188

Co-authored-by: Kody <kaidi.shi.1121@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Preserve the current C/C++ field-binding policy while integrating the Java
record accessor change. Handle varargs record components with array return
types and reject data targets before sequential LSP call emission, matching
the parallel path. Add focused regressions for both cases.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Pick up the shared CI contract repair while preserving the approved plugin changes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A record's implicit accessors are members the compiler writes and the
source never spells. Their nodes now carry the property generated=record,
and every registry registration path files them in the exact
(qualified-name) map only, never in the short-name index. The Java
type-aware resolver still binds p.y() on a Point to Point.y; a call such
as v.y() on a type from outside the project no longer binds the accessor
through a unique_name / suffix_match / field_type_hint / fuzzy guess.

- extract_defs.c: each implicit accessor carries extra_props
  generated=record (key CBM_PROP_GENERATED, cbm.h).
- registry.c: cbm_registry_add_qn_only, and the pure predicates
  cbm_def_props_generated / cbm_node_props_generated.
- pass_definitions.c, pass_parallel.c, pipeline_incremental.c: the three
  registration sites route a generated member to the exact map only.
- pipeline_delta.c: the incremental preseed proxy keeps the mark, so an
  incremental re-index registers the member exactly as a full one does
  (the proxy carried only a few properties and the accessor came back
  into the short-name index).

Tests: pipeline_java_record_accessors_generated_qn_only (sequential,
parallel and incremental venues) and two registry unit tests.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The import resolver's symbol-name fallback tries the trailing segments of
an import path that names no project symbol, and a package segment that
merely spells a record accessor bound the import to it. On elasticsearch,
`import javax.crypto.spec.PBEKeySpec` bound a record's spec() accessor,
and `new PBEKeySpec(...)`, annotations and type uses then followed the
import onto it: 6,302 IMPORTS edges into accessors, plus CALLS, USAGE,
DECORATES and THROWS through the import map.

A generated member is registered by qualified name only, and no Java
import can name an instance accessor, so the fallback now skips it exactly
as it skips a Python def-local (DeusData#1548).

The record pipeline fixture gains an import whose package segment spells
an accessor. Without the change it fails in the sequential, parallel and
incremental venues: an IMPORTS edge into Point.y, and make() -> Point.y.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
DeusData and others added 18 commits October 27, 2026 19:18
When a JVM LSP callee has no exact node and no Class.method tail match,
cbm_pipeline_lsp_target_node takes the one project callable with that
leaf name. A record accessor became such a callable: on elasticsearch
`Objects.nonNull(x)` (37 CALLS) and `filter(Objects::nonNull)` (230
CALL_REFERENCE) bound a test record's nonNull() accessor.

A generated member is registered by qualified name only. It still joins
through its owner's Class.method tail, but the leaf-name fallback no
longer considers it, either as the unique callable or as a rival that
makes a same-named project callable ambiguous, so every other bind is
decided exactly as before the accessors existed.

The record pipeline fixture gains a record Seen(boolean nonNull) with
`Objects.nonNull(o)` and `filter(Objects::nonNull)` callers. Without the
change it fails in the sequential, parallel and incremental venues.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Windows headers define near and far as empty macros, so the locals named
near and far in the generated-accessor pipeline test did not compile on
the Windows leg. Rename them to near_m and far_m; the node names stay.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Conflict in src/pipeline/pass_calls.c: DeusData#2305 moved the inline LSP-target
emission of resolve_single_call into calls_emit_lsp_target() and added
the unresolved-call evidence around emit_classified_edge. The Java
data-member veto of this branch is ported into that structure:

- calls_emit_lsp_target: the veto runs once the target node is known,
  before the resolution, the evidence and any spawn or edge. It returns
  true (handled), so as before no edge is emitted and no registry
  fallback runs after it.
- resolve_single_call: the veto stays in front of main's
  candidate_qn/calls_emitted bookkeeping, matching the parallel
  resolver, whose veto also precedes that bookkeeping.

pass_parallel.c and the remaining files merged cleanly.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Recognize explicit C-family code in .inc files before extraction while
preserving BitBake recipe bodies and ObjectScript markers. Exercise disk
and byte classifiers, callback/linkage extraction and fast/full indexing.

Signed-off-by: knewstimek <knewstimek@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@halindrome

Copy link
Copy Markdown
Owner Author

QA Round 1

Scope: origin/main..HEAD on fix/search-code-file-pattern-path (closes DeusData#2836). The panel had 3 lenses: contract-security, regression-edges and test-quality. All 3 landed.

Contract Verification

# Criterion Verdict Evidence
1 A file_pattern containing / is matched against the repo-relative path, with ** crossing directories (zero included), consistent with cbm_glob_to_like ✅ pass search_code_path_glob_match (src/mcp/mcp.c:14826) is applied in write_scoped_filelist (mcp.c:16503-16511). */** cross /, **/ matches zero dirs, and a pattern with no wildcard is a substring match, the same as search_where_basic (DeusData#200). Test cases: src/cli/*.c, src/**/*.c, **/cli/*.c, src/cli/. Minor divergences that do not break the criterion: the match is case-sensitive where LIKE ignores ASCII case, and it is slightly stricter around /**.
2 A file_pattern without / keeps basename semantics ✅ pass The basename fnmatch (mcp.c:16519) and the Windows prefilter are unchanged when !path_glob. Test cases: watcher.c, *.c.
3 Behaviour is the same on POSIX and Windows for scoped searches ✅ pass The path glob runs in C before the #ifdef split, after separator normalisation. On Windows a / pattern is kept on the in-process scan (mcp.c:17536-17538), and the test is not platform-skipped. Not run on Windows in this review.
4 A / pattern yielding 0 results returns a path_filter hint ✅ pass metrics.hint_path_filter (mcp.c:17710) is rendered in both the JSON and toon outputs, and the lib/*.c case asserts it. See Finding 1 for the wording.
5 The tool schema describes file_pattern and path_filter ✅ pass mcp.c:732-734.
6 Existing mcp suite stays green; new tests are red without the change ⚠ partial Red-without-change was confirmed by reading the code: basename fnmatch returns 0 for every / case and never emits the hint. Green was not verified, because lenses do not run the suite. The CI result is the evidence for this half.
7 Accepted limit: unscoped searches still use find -name ✅ pass cbm_search_code_build_grep_cmd's unscoped branch is unchanged.

Finding 1 — path_filter hint claims file_pattern "matched nothing" when files matched but the search text did not

[claude:contract-security|regression-edges|test-quality] · minor · confirmed · src/mcp/mcp.c:17710

  • Problem: metrics.hint_path_filter = file_pattern && strchr(file_pattern, '/') && gm_count == 0; is keyed on grep matches, not on whether the glob selected any files. SEARCH_CODE_PATH_HINT (mcp.c:14816) tells the caller the file_pattern "matched nothing" and points at path_filter. With a correct glob such as src/cli/*.c and a needle that does not occur, the caller is told to rewrite a correct filter.
  • Expected: Fire the hint only when the / glob selected no files (scoped && scoped_written == 0, plus the unscoped find -name case), or reword it so it does not claim the glob matched nothing.
  • Impact: A misleading diagnostic that wastes an agent round-trip. Results are never wrong, and the AC wording is literally met. About a one-line fix. All three lenses found this independently.

Finding 2 — Hint rendering in the default tree/toon output is untested

[claude:test-quality] · minor · confirmed · tests/test_mcp.c:11724-11735

  • Problem: Every case in search_code_file_pattern_matches_repo_relative_path_issue2836 sends "format":"json". The new hint branch in assemble_search_output_toon (mcp.c:15479-15481) handles the default output format and is never exercised, so deleting it leaves the suite green.
  • Expected: One tree-format case in the existing loop.
  • Impact: Low. The branch is a 3-line mirror of the JSON one.

Summary

Severity Confirmed
Critical 0
Major 0
Minor 2

No blocking findings. Contract: 6 criteria pass. 1 is partial, because suite-green is left to CI.

Observations

None.


SAST review not applicable

Target default has security_stage: false in the qa-cycle config. No CI
security stage is wired for this target, so no SAST/SCA delta is computed.


QA performed by Claude Code (claude-opus-5-5), manager + 3-lens panel

Dispositions (orchestrator, Step 3B)

  • Fixed (minor): the hint said the / pattern "matched nothing", even when it had selected files that simply didn't contain the search text. The wording now reads: "no results; a file_pattern with '/' must match the whole repo-relative path (e.g. src/**/*.c); to scope by folder you can also use path_filter …". That is accurate in both cases. The hint still keys on zero matches, because the unscoped find path has no file count to key on.
  • Not fixed (carried forward, minor): no test covers the hint in the tree (toon) output format. Both renderers read the same metrics.hint_path_filter flag, and the JSON branch is tested.

Verification

CC=/usr/bin/clang CXX=/usr/bin/clang++ SDKROOT=… scripts/test.sh --suites mcp → 397 passed, 4 skipped, 0 failed (exit 0). The existing test asserts the hint is present without pinning its wording, so it stays green. This is narrower than CI's default leg; the local default scripts/test.sh stops at contract Step 0t, which unmodified main also fails in this environment.

QA-Fix-Commit: 73ff216


Posted by the developer account (QA-agent token unavailable). Review by Claude Opus 5.5 (Anthropic) in Claude Code via qa-panel.

@halindrome

Copy link
Copy Markdown
Owner Author

QA Round 2

Scope: origin/main..HEAD on fix/search-code-file-pattern-path, at 73ff216f. That includes the round-1 fix commit 73ff216f, which rewords the path_filter hint. The PR closes DeusData#2836. The panel had 3 lenses: contract-security, regression-edges and test-quality. All 3 landed, and all ran on claude-opus-5-5 with CMM + context-mode navigation. The working tree was unchanged across the panel.

Contract Verification

# Criterion Verdict Evidence
1 A file_pattern containing / is matched against the repo-relative path, with ** crossing directories (zero included), consistent with cbm_glob_to_like ✅ pass search_code_path_glob_match (src/mcp/mcp.c ~14826) is applied in write_scoped_filelist when the pattern contains /. */** cross /, **/ matches zero dirs, and a pattern with no wildcard is a substring match, the same as search_where_basic. It is equal to or slightly stricter than cbm_glob_to_like (src/store/store.c:6910) around /**. Test cases: src/cli/*.c, src/**/*.c, **/cli/*.c, src/cli/.
2 A file_pattern without / keeps basename semantics ✅ pass The basename fnmatch is gated only on !path_glob. The Windows prefilter (cbm_search_code_file_pattern_can_prefilter) is unchanged. Test cases: watcher.c, *.c.
3 Behaviour is the same on POSIX and Windows for scoped searches ✅ pass The path glob runs before the #ifdef split, after separator normalisation. On Windows, handle_search_code keeps / patterns on the in-process scan. The test is not platform-skipped. Verified by reading; not run on Windows.
4 A / pattern yielding 0 results returns a path_filter hint ✅ pass metrics.hint_path_filter is rendered in both the JSON (mcp.c:15722) and toon (mcp.c:15480) outputs, with no duplicate hint key. After the round-1 rewording, SEARCH_CODE_PATH_HINT (mcp.c:14816) is accurate whether the glob selected no files or the selected files lacked the text. The lib/*.c case asserts use path_filter is present, and the other cases assert it is absent.
5 The tool schema describes file_pattern and path_filter ✅ pass mcp.c ~732-734.
6 Existing mcp suite stays green; new tests are red without the change ✅ pass Red without the change: confirmed by reading the code. Basename fnmatch rejects every / case and the old code emits no hint. Green: lenses do not run the suite. The orchestrator ran scripts/test.sh --suites mcp at this head: 397 passed, 4 skipped, 0 failed.
7 Accepted limit: unscoped searches still use find -name ✅ pass The unscoped branch of cbm_search_code_build_grep_cmd is unchanged. A / pattern there yields 0 results and the hint fires, which is the documented limit.

Summary

Severity Confirmed
Critical 0
Major 0
Minor 0

No new findings. The round-1 fix (73ff216f) was reviewed as part of the full diff and introduced no regressions. All 7 contract criteria pass.

Observations

  • TOON-format path_filter hint branch has no test (known, deferred from round 1 Finding 2) · minor · tests/test_mcp.c:11729-11733. Every case sends "format":"json", so the hint in assemble_search_output_toon (mcp.c:15480), the default format, is not exercised. This is low risk because it mirrors the JSON branch, which reads the same flag. It was raised again by test-quality and is listed here as carried forward, not as a new finding.

SAST review not applicable

Target default has security_stage: false in the qa-cycle config. No CI
security stage is wired for this target, so no SAST/SCA delta is computed.


QA performed by Claude Code (claude-opus-5-5), manager + 3-lens panel

Cycle outcome: round 2 is clean. There are no findings, all contract criteria pass, and the round-1 fix is reviewed. Carried forward: no tree/toon-format test for the hint. Not approved: no QA-agent token is available, and approval is the human reviewer's call.


Posted by the developer account (QA-agent token unavailable). Review by Claude Opus 5.5 (Anthropic) in Claude Code via qa-panel.

… repo-relative path

search_code matched file_pattern against the file basename only, so any
pattern containing a directory (src/cli/*.c, apps/web/src/**/*.ts)
silently returned 0 results, while search_graph's file_pattern matches
the full repo-relative path.

A file_pattern containing '/' is now matched in C against the
repo-relative path of every indexed file, on all platforms, with
search_graph-equivalent semantics: '*' and '**' match across '/', '**/'
also matches zero directories, '?' matches one character, and a pattern
without wildcards is a substring match. Patterns without '/' keep the
existing basename behaviour. On Windows such patterns are scanned in
process from the already-filtered list instead of the PowerShell -like
post-filter. A search with a '/' pattern that returns nothing now carries
a hint explaining whole-path matching and pointing at path_filter, and
the schema describes both parameters.

Refs DeusData#2836

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
…e-agent-detection

fix(cli): support non-ASCII Windows agent paths
…-functions

fix(store,ui): export paired free functions for FFI callers (DeusData#1762)
…n-marketplace

feat(plugin): Claude Code plugin marketplace, discovery slice (DeusData#1188 slice 1)
…r-calls

fix(java): resolve record accessor calls, and never bind a call to data
…gistry-index

perf(py-lsp): O(1) type membership in the shared Python cross-registry build
…ting

fix(discover): select C++ for C-family .inc fragments
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>

This branch has not been deployed

No deployments
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.

7 participants