Commit Graph
328 Commits
Author SHA1 Message Date
Martin Vogel 8423b0b274 test(repro): -Itests fix + reproductions for #495, #521, #382
- Makefile: add -Itests to the test-repro-runner compile so repro files in the
  tests/repro/ subdir resolve "test_framework.h" (they sit one dir deeper than
  the existing tests/*.c, which find it relative to their own directory).
- #495: cfg-gated twin functions collapse — two mutually-exclusive #[cfg] Rust
  fns get identical qualified_name and the UNIQUE(project,qualified_name) upsert
  overwrites one; asserts the twins get distinct QNs.
- #521: Route nodes minted from URL string literals in infra/config files
  (try_upsert_infra_route has no source-vs-config guard); indexes a YAML-only
  fixture and asserts zero Route nodes.
- #382: Java class-level + marker_annotation decorators dropped; asserts @Entity/
  @RestController on the class and @Override on the method (strengthens the weak
  existing #382 method-only test).

All three assert correct behaviour and are RED on current code.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-26 00:25:40 +02:00
Martin Vogel 6e70a8b019 test(repro): scaffold cumulative bug-reproduction suite + cross-platform board
Stand up a separate, non-gating reproduce-first suite that holds one RED case
per open bug issue (the redness is the deliverable + the regression guard):

- tests/repro/ + repro_main.c -> `make test-repro` (its own runner with its own
  main + counters; deliberately EXCLUDED from ALL_TEST_SRCS so the gating
  `make test` / ci-ok required check stays green and PRs are not wedged)
- repro_extraction.c: first reproduction, #554 -- a C++ out-of-line method's
  inner CALLS edge must attribute to the class-qualified Method QN, not the
  Module; ties the call's enclosing_func_qn to the method definition's own
  qualified_name so a class-qualifier drop (the live root cause) fails it
- scripts/repro.sh: build+run the board; a build/link failure fails the job,
  while expected test redness is reported as the board state (job stays green)
- .github/workflows/bug-repro.yml: workflow_dispatch (platform filter) + qa/**
  push; runs the board on linux x2 / macos x2 / windows so many bug vectors can
  be reproduced on many platforms at once

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-26 00:09:23 +02:00
Martin Vogel 4953fcaf21 test(extract): guard C++ out-of-line method/ctor/dtor enclosing attribution
After the C declarator-name walker was de-duplicated into one shared helper
(cbm_resolve_c_declarator_name_node), the C/C++ enclosing-function resolver now
resolves qualified names (Foo::bar) via resolve_qualified_name() and no longer
treats type_identifier as a terminal name. Add reproduce-first coverage so the
#438 fix cannot silently regress on the qualified-declarator path:

- cpp_out_of_line_method_caller_attribution: a call inside `void Foo::bar()` must
  attribute to the method, not the module.
- cpp_out_of_line_ctor_dtor_caller_attribution: calls inside `Foo::Foo()` and
  `Foo::~Foo()` must attribute to the special member, not the module.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-25 21:42:31 +02:00
Martin Vogel 66b3e3004b Merge pull request #614 from linze0721/berserk/fd632085-2b69-4b14-930b-47025d80d973
fix: get_architecture should honor path scope
2026-06-25 21:18:49 +02:00
Martin Vogel 677582ccf8 Merge pull request #539 from RithvikReddy0-0/fix/516-adr-persistence
fix(pipeline): preserve ADR across full re-index (#516)fix(pipeline): preserve ADR across full re-index (#516)
2026-06-25 21:18:33 +02:00
Martin Vogel 7d11db8ee4 Merge pull request #527 from Ravandevil25/fix/json-escape
fix(foundation): properly escape JSON control characters as \u00XX
2026-06-25 21:18:10 +02:00
Martin Vogel 1c3c5573c4 Merge pull request #463 from KerseyFabrications/fix/c-calls-function-source
fix(extract): attribute C/C++ CALLS edges to the enclosing function
2026-06-25 21:17:52 +02:00
Martin Vogel a76e97daaa Merge pull request #449 from isc-tdyar/fix/class-lines-zero
fix(extract): Class.lines always 0 for all languages
2026-06-25 21:17:35 +02:00
Kris Kersey 13c3429651 fix(extract): attribute C/C++ CALLS edges to the enclosing function
A CALLS edge whose caller is a C/C++/CUDA/GLSL function was sourced to the
file's Module node instead of the calling Function. "Find callers of X"
returned a file path, outbound trace_path returned empty, and
(:Function)-[:CALLS]->(:Function) queries missed for these languages.

Root cause: the enclosing-function resolvers read only tree-sitter's `name`
field, but a `function_definition` node has none — the name lives in the
declarator chain (pointer/function/parenthesized/array declarators). So
func_node_name() (internal/cbm/helpers.c) and resolve_func_name_node()
(internal/cbm/extract_unified.c) returned NULL, the enclosing scope fell
back to the module QN, and the edge was attributed to the Module node. This
is the C counterpart to #220, which fixed the definition-naming path but not
the enclosing-call path.

Fix: descend the declarator chain to the innermost name node (mirroring
resolve_c_declarator_name in extract_defs.c, including qualified and operator
names) when a function_definition lacks a `name` field. Adds the regression
test c_caller_attribution asserting a C call's enclosing_func_qn is the
function, not the module.

Fixes #438

Signed-off-by: Kris Kersey <kris@kerseyfabrications.com>
2026-06-24 12:42:38 -04:00
saurav kumar 078e9e2d85 Merge branch 'main' into fix/json-escape 2026-06-24 20:36:59 +05:30
Saurav Kumar fdcafbb53f fix(foundation): properly escape JSON control characters as \u00XX
Signed-off-by: Saurav Kumar <sauravsk2507@gmail.com>
2026-06-24 20:33:52 +05:30
Berserk Agent 55058c05f0 fix(arch): normalize path trailing slashes; align MCP path_scoped with store (#604)
Signed-off-by: Berserk Agent <berserk@agent.local>
2026-06-24 12:54:45 +00:00
Berserk Agent adf602ec94 fix(mcp): document path on get_architecture and emit root/scoped totals (#604)
Signed-off-by: Berserk Agent <berserk@agent.local>
2026-06-24 12:50:49 +00:00
Berserk Agent 9332593b26 Store path scoping for get_architecture
Signed-off-by: Berserk Agent <berserk@agent.local>
2026-06-24 12:38:11 +00:00
RithvikReddy0-0 98c4fc345a fix(pipeline): harden ADR preservation and add regression test (#516)
Addresses review on #539.

- Free p->saved_adr in cbm_pipeline_free so it is not leaked on error
  paths that exit before the restore in dump_and_persist_hashes (e.g. a
  cbm_gbuf_dump_to_sqlite failure).
- Check cbm_store_adr_store's return value on restore and log an error
  instead of silently dropping the ADR (the original #516 symptom).
- Add reproduce-first test pipeline_adr_survives_full_reindex: index,
  store an ADR, force a full re-index by adding files, assert the ADR
  survives unchanged. Passes against the fix.

Signed-off-by: RithvikReddy0-0 <rithvikreddymukkara@gmail.com>
2026-06-24 01:27:32 +00:00
Martin Vogel 170d5a82ab fix(ci): allow-list MSYS2 toolchain URL in binary string audit
The MSYS2 CLANG64 toolchain bakes its package-tracker URL
(https://github.com/msys2/MINGW-packages) into the static Windows .exe via
libc++/compiler-rt. The Layer 2 binary string audit flagged it as an
unauthorized URL, failing smoke-windows (both the standard and ui variants)
while Linux smoke stayed green. Add it to ALLOWED_URLS alongside the other
compiler/linker toolchain URLs (gcc.gnu.org, sourceware.org, launchpad).

Add tests/test_security_strings_allowlist.sh as a reproduce-first regression
guard: it reproduces the BLOCKED failure with a binary fixture and includes a
negative control proving a genuinely unauthorized URL is still rejected. Wired
into scripts/test.sh as Step 6.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-24 03:15:36 +02:00
Thomas Dyar 36e127c113 fix(extract): Class node lines property always 0
extract_class_def() sets start_line and end_line from the tree-sitter
node span, but left def.lines = 0 (zeroed by memset at function entry).
The equivalent function push_method_def() correctly computes
  def.lines = end_line - start_line + 1
The same one-liner was simply missing from the class extraction path.

This affects all languages — Python, TypeScript, Go, and any other
language where Class nodes are extracted via extract_class_def().

Regression test: after indexing a project, at least one Class node must
have c.lines > 0.

Signed-off-by: Thomas Dyar <tdyar@intersystems.com>
2026-06-23 19:14:09 -04:00
Martin Vogel 3d4236e861 Merge pull request #486 from mvanhorn/fix/400-windows-persistence-artifact-silent-failure
fix: Surface a real error when persistence artifact export silently fails on Windows
2026-06-23 23:52:36 +02:00
Martin Vogel 090a1a72d5 Merge pull request #542 from mvanhorn/fix/499-global-core-excludesfile-ignore-source
fix: honor git global core.excludesFile as an ignore source
2026-06-23 23:46:24 +02:00
Martin Vogel f82c6aee68 Merge pull request #526 from jstar0/fix/snippet-valid-utf8
fix(mcp): return valid UTF-8 snippets
2026-06-23 23:46:20 +02:00
Martin Vogel 96c1fc38fb Merge pull request #465 from KerseyFabrications/fix/cypher-with-node-props
fix(cypher): preserve node properties through WITH aggregation
2026-06-23 23:46:16 +02:00
Martin Vogel bbaca2a848 Merge pull request #447 from isc-tdyar/fix/string-dispatch-dead-code
fix(extract): string-dispatch CALLS edges never emitted — detection was in dead code
2026-06-23 23:46:13 +02:00
Martin Vogel 56d36ba17e Merge pull request #572 from mvanhorn/fix/558-search-graph-degree-inherits-edges
fix: count INHERITS/IMPLEMENTS edges in search_graph degree
2026-06-23 23:08:13 +02:00
Martin Vogel 879c3cbe5b Merge pull request #566 from jsw6701/fix/kotlin-spring-routes
fix(extract): extract Spring routes from Kotlin controllers
2026-06-23 23:08:09 +02:00
Martin Vogel 008f6e3873 Merge pull request #506 from imateusdev/fix/route-param-canonicalization
fix(routes): canonicalize path parameters so client calls match handlers (#505)
2026-06-23 23:08:05 +02:00
Martin Vogel 4dd6ebd69e Merge pull request #456 from nguiaSoren/fix/334-committed-count-capture
fix(pipeline): make the #334 plausibility gate fire (capture committed_nodes before dump)
2026-06-23 23:08:01 +02:00
imateusdev 8b813675be fix(routes): canonicalize path parameters so client calls match handlers
Route QNs (__route__METHOD__/path) are the rendezvous key for joining a client
HTTP_CALLS to the server handler that serves it, but they were built from the raw
path. Frameworks write path parameters with different placeholder syntaxes
(":id" Express/clients, "{id}" Axum/Spring/OpenAPI, "<id>" Flask/Rocket), so a
client call and its handler produced different QNs and never joined. Static
routes joined; parameterized routes (most of a REST surface) did not.

Add cbm_route_canon_path() which collapses each parameter token (":name",
"{name}", "<name>", "${...}") to a single "{}" token, and apply it to the path
at every HTTP-route QN construction site (pass_route_nodes, pass_parallel,
pass_calls, pass_cross_repo). Broker/async/infra QNs are left untouched. Only the
QN (identity/dedup key) is canonicalized; the Route node display name keeps the
original path. Parameter names are intentionally discarded so the same logical
endpoint matches across services that name the variable differently.

Adds tests/test_route_canon.c (11 cases). Fixes #505.

Signed-off-by: imateusdev <imateusdev@gmail.com>
2026-06-23 13:52:08 -03:00
Matt Van Horn 0714e46346 fix: address self-review findings — keep node_degree CALLS-only
cbm_store_node_degree backs the MCP get_code_snippet callers/callees
counts and the Cypher in_degree/out_degree virtual property, which are
deliberately CALLS-scoped. Restore it to CALLS-only and scope the
INHERITS/IMPLEMENTS broadening to cbm_store_search alone, which is the
search_graph surface issue #558 is about. Adjust the new tests so
node_degree assertions expect CALLS-only counts while the search_graph
result assertions expect the broadened structural degree.

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-06-23 04:08:36 -07:00
Matt Van Horn 65b8425648 fix: count INHERITS/IMPLEMENTS edges in search_graph degree
search_graph reported in_degree/out_degree of 0 for Class nodes that
have real INHERITS edges, because the degree computation in both
cbm_store_node_degree (Cypher virtual properties) and cbm_store_search
(result population and min_degree/max_degree filter) only counted CALLS
(and USAGE) edges. A min_degree filter therefore silently excluded
well-connected classes.

Broaden the edge-type predicate in both surfaces to
IN ('CALLS', 'USAGE', 'INHERITS', 'IMPLEMENTS') so search_graph and
Cypher report the same edge-type-agnostic degree and stay consistent.

Add regression tests covering: a parent class with N INHERITS children,
combined CALLS+INHERITS degree without double counting, min_degree now
including inheritance-only classes, and isolated nodes still reporting
zero.

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-06-23 04:08:36 -07:00
jsw6701 7aacf380dd fix(extract): extract Spring routes from Kotlin controllers
try_route_from_annotation read the annotation name via the "name" field
(ts_node_child_by_field_name). tree-sitter-kotlin annotation nodes have no
"name" field: the name lives in a nested type_identifier under (user_type)
or (constructor_invocation (user_type ...)). The lookup returned null, so
every Kotlin @RestController route was dropped (0 decorator-source routes)
while Java worked. Args are likewise under constructor_invocation as
value_arguments (each wrapped in value_argument), not Java argument_list.

Add annotation_name_node()/annotation_args_node() that fall back to the
Kotlin AST shape (Java path unchanged) and unwrap value_argument when
scanning for the route path. Kotlin @GetMapping/@PostMapping/etc. now yield
Route nodes and HANDLES edges. Adds handles_spring_kotlin regression test.

Signed-off-by: jsw6701 <86887824+jsw6701@users.noreply.github.com>
2026-06-23 03:59:45 +00:00
Thomas Dyar 0b94f44f96 fix(extract): string-dispatch calls in dead code path, never linked to target
Python calls of the form iris_obj.classMethodValue('Pkg.Class', 'Method')
emitted no CALLS edge. The detection existed in walk_calls, which is dead
code — the pipeline uses cbm_extract_unified → handle_calls, not walk_calls.

Move the IRIS Native API string-dispatch detection to handle_calls. When a
Python call's callee ends in .classMethodValue, .classMethodVoid,
.classMethodBoolean, or .classMethodObject, and both string-literal arguments
can be extracted, emit a synthetic CALLS edge to Arg1.Arg2.

The pattern generalizes beyond this specific API: any library that takes
class/method names as string literals follows the same shape.

Regression test: python_iris_classMethodValue in test_extraction.c.

Signed-off-by: Thomas Dyar <tdyar@intersystems.com>
2026-06-22 19:56:46 -04:00
Martin Vogel 5ea5579d2b fix(discover): make cbm_gitignore_merge atomic on allocation failure
cbm_gitignore_merge copied src patterns into dst one strdup at a time and
returned void on failure, leaving dst with a partial set of patterns and no
signal to the caller. If the dropped pattern was the critical exclude (the
.git/info/exclude entry that prevents an OOM walk), the bug could silently
recur with some patterns applied and others dropped.

Make the merge atomic: on any allocation failure roll back the partial copies
so dst is left exactly as it was, and return bool so callers can tell. The
.gitignore caller now degrades gracefully (exclude patterns skipped, as if the
file were absent) instead of ending up with a half-merged matcher.

Adds a reproduce-first test that injects a strdup failure mid-merge via a test
seam and asserts dst is unchanged; it is red without the rollback.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-22 23:51:49 +02:00
Matt Van Horn 44d5174511 fix: address review feedback on artifact export error surfacing
- Declare cbm_artifact_export_last_error() in artifact.h and drop the ad-hoc
  forward declarations in pipeline.c and test_artifact.c so there is a single
  shared declaration.
- Document that dump_and_persist_hashes now intentionally propagates a failed
  artifact export as a pipeline error (previously silently ignored), so the
  behavior change is recorded in-tree.
- Use MoveFileExA(MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) for the
  Windows replace in write_file_atomic instead of unlink+rename; it replaces
  the destination atomically (credit @Ayush7Ranjan, from #492).

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-06-22 14:46:29 -07:00
soren a0a9c26809 fix(pipeline): capture committed_nodes before dump frees the index (#334)
The #334 plausibility gate compares committed (extracted) node count
against persisted rows, but committed_nodes was read AFTER
cbm_gbuf_dump_to_sqlite(), which calls release_gbuf_indexes() and frees
node_by_qn. cbm_gbuf_node_count() therefore returned 0, committed_nodes
fell at/below CBM_DUMP_VERIFY_MIN_FLOOR, and the gate never fired (a
healthy index reported expected_nodes:0 while expected_edges was correct,
since edges.count survives the dump).

- pipeline.c: capture committed node/edge counts before the dump.
- pipeline_incremental.c: set committed counts on the incremental path too
  (via new cbm_pipeline_set_committed_counts), so the gate covers
  incremental/reparse reindexes — the scenario #334 actually reproduces.
- test_pipeline.c: drive the real pipeline and assert committed_nodes > 0
  and == persisted (the existing dump_verify tests fed synthetic counts and
  could not catch the capture-ordering bug).

Signed-off-by: soren <nguiasoren@gmail.com>
2026-06-23 06:39:49 +09:00
Matt Van Horn 9f66e428f8 fix: resolve core.excludesFile only from user-owned global git configs
Drop the repo-local .git/config pass in resolve_global_excludes_path() so
an untrusted repo cannot point core.excludesFile at an arbitrary file (e.g.
~/.ssh/id_rsa) and have cbm open and read it merely by being indexed. git is
safe here because it only trusts a repo's .git/config after a deliberate
clone/init; the indexer has no such trust step. Global excludes now resolve
only from the user-owned ~/.gitconfig and $XDG_CONFIG_HOME/git/config. A
repo's own ignores remain handled by .gitignore / .cbmignore.

Add a guard test asserting a repo-local .git/config whose excludesFile points
outside the repo is ignored, not read.

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-06-22 14:39:26 -07:00
Sam Li 559993a853 Add post-dump plausibility gate returning status:degraded (#334)
Compare persisted SQLite node counts to in-memory dump counts after
index_repository completes so partial WAL/durability loss surfaces as
status:"degraded" instead of silent indexed.

Signed-off-by: Sam Li <yangsec888@gmail.com>
2026-06-23 06:38:45 +09:00
Martin Vogel acb7eaaa05 Merge pull request #528 from win4r/fix/incremental-inbound-cross-file-edges
fix(pipeline): preserve inbound cross-file edges on incremental reindex
2026-06-22 23:28:28 +02:00
Martin Vogel ff89931070 Merge pull request #475 from rainder/fix/args-json-stack-overflow
fix(pipeline): prevent stack-buffer-overflow in append_args_json
2026-06-22 23:28:10 +02:00
Martin Vogel 6e0f7f7926 Merge pull request #466 from KerseyFabrications/fix/name-resolution
fix(mcp): prefer definitions and report ambiguity in name resolution
2026-06-22 23:28:07 +02:00
Martin Vogel c3b5b00796 Merge pull request #464 from KerseyFabrications/fix/detect-changes-since
fix(mcp): honor detect_changes 'since' parameter
2026-06-22 23:28:03 +02:00
Martin Vogel 0ad4d2d029 Merge pull request #448 from isc-tdyar/fix/deploy-dir-always-skip
fix(discover): deploy/ directories indexed as duplicate source in full mode
2026-06-22 23:27:09 +02:00
Martin Vogel 97cbfce055 Merge pull request #412 from isc-tdyar/fix/cypher-label-filter-truncation
fix(cypher): label-filtered edge traversal silently truncates at 10 results
2026-06-22 23:26:43 +02:00
Pcristin 0618eaa99a fix: honor file_pattern in bm25 graph search (#555)
Signed-off-by: pcristin <xxxokzxxx@protonmail.com>
2026-06-22 22:05:29 +02:00
Kiril Klein, PhDandClaude Opus 4.8 df8bc9254d fix(mcp): trace_path cross_service follows CROSS_* edges (#522) (#538)
trace_path mode=cross_service resolved its default edge-type list to
{HTTP_CALLS, ASYNC_CALLS, DATA_FLOWS, CALLS} — none of the CROSS_* edge
types the cross-repo pass actually writes. Since cbm_store_bfs filters
edges with an exact `WHERE e.type IN (...)` clause, cross-service traces
silently skipped every cross hop even when the edge was present and
visible via query_graph.

Add the six CROSS_* types (CROSS_HTTP_CALLS, CROSS_ASYNC_CALLS,
CROSS_CHANNEL, CROSS_GRPC_CALLS, CROSS_GRAPHQL_CALLS, CROSS_TRPC_CALLS)
to the cross_service default list — exactly the set pass_cross_repo.c
emits and cross_repo_delete_edges() cleans up. The count is now computed
from the array size (sizeof) so it can't drift; the dead MCP_N_DEFAULTS_4
constant is removed. Tool description updated to match.

Adds integ_mcp_trace_path_cross_service: seeds a CROSS_HTTP_CALLS edge
between two indexed functions with no CALLS relationship and asserts
cross_service surfaces the hop while calls mode does not.

Signed-off-by: kirilklein <kikl@di.ku.dk>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:05:18 +02:00
Alex 8ba286324c fix(pipeline): persist call-site line on CALLS edges (#508)
* fix(pipeline): persist call-site line on CALLS edges

Persist CBMCall.start_line as a "line" property on CALLS edges in both resolution paths (finalize_and_emit + calls_emit_edge), so call-site lines are queryable instead of needing grep. Scoped to CALLS only: route/config edge props feed full-only predump passes the incremental path does not re-run, so changing them desyncs full vs incremental indexing. Verified: full suite matches unpatched main; line present on CALLS both paths.

Fixes #503

Signed-off-by: alexisperinger-ux <alexis.peringer@iss-stoxx.com>

* test(pipeline): assert CALLS edge carries call-site line

Indexes a Go fixture where Helper() is called on line 8 and asserts the Main->Helper CALLS edge persists a line property (line:8) in its JSON props.

Refs #503.

Signed-off-by: alexisperinger-ux <alexis.peringer@iss-stoxx.com>

---------

Signed-off-by: alexisperinger-ux <alexis.peringer@iss-stoxx.com>
2026-06-22 22:05:09 +02:00
Shaurya 11009f2d94 fix(discover): honor .git/info/exclude during file discovery (#493)
* fix(discover): honor .git/info/exclude during file discovery

index_repository only loaded .gitignore when building the exclusion
matcher. .git/info/exclude — the per-clone exclude file that Git treats
as authoritative — was never read, so paths excluded only there were
walked in full. On repos with Sandcastle worktrees this caused the
indexer to traverse 661K files / 170 GB and OOM (issue #489).

Add cbm_gitignore_merge() to append patterns from one matcher into
another. In cbm_discover_ex, after loading .gitignore, also load
.git/info/exclude and merge its patterns in. If only the exclude file
exists, use it directly. No downstream call paths change.

Add three unit tests for cbm_gitignore_merge and two integration tests
that reproduce the issue scenario (exclude-only and exclude-stacked-
with-gitignore).

Signed-off-by: ShauryaaSharma <shauryasofficial27@gmail.com>

* style(discover): fix clang-format alignment in cbm_gitignore_merge

Signed-off-by: ShauryaaSharma <shauryasofficial27@gmail.com>

---------

Signed-off-by: ShauryaaSharma <shauryasofficial27@gmail.com>
2026-06-22 22:04:42 +02:00
Matt Van Horn f5ea36cc64 fix: Reject filesystem paths in HTTP_CALLS route extraction so client routes can join server routes (#485)
Signed-off-by: mvanhorn <mvanhorn@gmail.com>
2026-06-22 22:04:31 +02:00
c8f9f94e08 fix(search): ignore empty-string label in search_graph name_pattern path (#483)
* fix(search): ignore empty-string label in search_graph name_pattern path

search_where_basic guarded the label filter on NULL only (`if (params->label)`),
so a search passing label="" appended a literal `n.label = ''` clause that
matches no node — silently returning zero results. The BM25 query path already
treats an empty label as absent, so name_pattern/qn_pattern searches and BM25
searches behaved inconsistently for the same `"label":""` payload (issue #481):
agents fell back to grep because structural class/service discovery returned
nothing.

Guard the clause on `params->label && params->label[0]` so an empty-string
label is a no-op, identical to an omitted label and to the BM25 path.

Adds store_search_empty_label_ignored: name_pattern + label="" returns the same
match as name_pattern alone, while a non-empty label still filters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* test(search): address QA round 1 — cover qn_pattern + empty label

QA round 1 noted store_search_empty_label_ignored asserted only the
name_pattern path though the comment referenced qn_pattern too. Add a
qn_pattern + label="" assertion so a future change that special-cased the
qn_pattern branch can't silently reintroduce the empty-label bug. Both paths
share search_where_basic, so this locks in the shared guard.

Test-only; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

---------

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:04:19 +02:00
cd660270cb fix(resolver): disambiguate qualified cross-file calls by full namespace tail (#479)
* fix(resolver): disambiguate qualified cross-file calls by full namespace tail

cbm_registry_resolve reduced a package/namespace-qualified callee
(Foo::Bar::sub) to its bare simple name before the multi-candidate name
lookup. When that bare name was defined in several packages, the bare-name
scorer (best_by_import_distance) picked one winner deterministically and
routed every caller to it — collapsing distinct packages' same-named subs
onto a single definition and orphaning the rest. Impact is heaviest for Perl,
which has no cross-file LSP and relies entirely on this generic resolver, but
the defect applies to any "::"- or "."-qualified callee.

Add qualified_suffix_match: when a callee is qualified and its bare name has
multiple candidates, pick the sole candidate whose qualified tail matches at a
segment boundary (normalizing "::" to "."). Returns the unique match with a new
high-confidence "qualified_suffix" strategy; falls through to the existing
bare-name scoring when zero or several candidates match. Language agnostic —
bare callees contain no separator and are unaffected.

Adds resolve_qualified_disambiguates_same_name covering per-package routing of
a name defined in three packages, plus the bare-call (no-disambiguation) case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* test(resolver): address QA round 1 (qualified-suffix edge coverage)

Harden resolve_qualified_disambiguates_same_name and add a new test per the
QA round 1 advisory notes, guarding the fall-through paths against silent
regression:
- dotted callee form (App.Beta.save) resolves via qualified_suffix
- qualified callee matching no candidate tail falls through (not qualified_suffix)
- resolve_qualified_ambiguous_tail_falls_through: two candidates sharing the
  same qualified tail are ambiguous → fall through to bare-name scoring

Test-only; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

---------

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:04:06 +02:00
92b4506cd5 fix(perl): eliminate false-positive Perl CALLS edges (builtins, framework method calls, config strings) (#477)
* fix(perl): stop extracting config strings as call targets

The Perl branch of extract_scripting_callee blindly returned the text of
child(0) of every call node. In config-heavy Perl (.cgi/.pl with embedded
log4perl-style config), tree-sitter-perl misparses dotted config tokens
(e.g. "log4perl.appender.File.utf8") into call-shaped nodes, and that
dotted string was emitted as a callee_name, later matched by the generic
short-name resolver to unrelated project subs.

Now the Perl branch pulls the real name token (method/function field, else
child(0)) and validates it as a bare Perl sub/method identifier via
perl_is_identifier_callee: must start with a letter or '_' and contain only
[A-Za-z0-9_:] (allowing the '::' package separator). Any '.', whitespace,
quote, or '/' disqualifies it and NULL is returned so no CALLS edge forms.
Gated to CBM_LANG_PERL; other languages are untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* fix(resolver): don't match Perl builtins to project subs

Perl builtins (push/shift/keys/sprintf/...) carry no language or call-kind
awareness through the generic name-matcher in cbm_registry_resolve. When a
project defines a sub whose name collides with a builtin, an invocation of
the builtin was wired to that sub by same-module / suffix matching - a
false-positive CALLS edge.

Adds cbm_perl_is_builtin (curated, sorted bsearch set of 94 perlfunc core
builtins) and applies it in both call-resolution passes (sequential
resolve_single_call and parallel resolve_file_calls), gated on the file
language == CBM_LANG_PERL and only AFTER LSP resolution has declined, so a
genuine LSP-resolved call is never suppressed. The file language is threaded
into both resolvers via a new trailing CBMLanguage parameter; every other
language reaches cbm_registry_resolve unchanged (byte-identical behavior).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* fix(resolver): suppress generic short-name matching for Perl method calls

A Perl method call ($obj->m / Class->m) carries no receiver type at the
structural tier, so the generic short-name matcher in cbm_registry_resolve
would wire $dbh->commit / $cgi->param / $logger->log to any project sub
sharing the bare method name - the dominant source of false-positive CALLS
edges in CPAN/framework-heavy Perl. Resolving such a call correctly is the
LSP's job, not the bare-name matcher's.

Adds a CBMCall.is_method flag (zero-init false, so all other languages and
existing call sites are unaffected). method_call_expression is added to the
Perl call node set and handle_calls sets is_method=true only for that node
type when the file language is Perl. Both call-resolution passes then skip
generic resolution for Perl method calls (combined with the builtin guard
from the prior commit). Genuine intra-project function calls (non-method,
non-builtin) still resolve as before. LSP-resolved method calls are
unaffected because the guard runs only after LSP resolution declines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* test(perl): cover call-graph noise fixes (builtins, methods, config strings)

Hermetic tests for the three Perl call-graph noise fixes:

test_extraction.c (extraction tier):
  - config string is never emitted as a callee; genuine call still extracted
  - builtin calls (push/keys) extracted but never flagged is_method
  - arrow/method calls ($self->commit / $dbh->commit) set is_method=true,
    while the genuine function call (helper) does not
  - a JS method call never sets is_method (flag is Perl-only — other
    languages unaffected)

test_registry.c (resolver tier):
  - cbm_perl_is_builtin recognizes core builtins (incl. first/last of the
    sorted set) and rejects project subs, case variants, empty, and NULL

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* fix(perl): address QA round 1

Round 1 (Claude panel + DO DeepSeek) findings:
- Rework the Perl noise guard so it suppresses only WEAK generic matches
  (suffix_match/unique_name) and KEEPS high-confidence same_module/import_map.
  The prior guard ran before cbm_registry_resolve and dropped genuine same-file
  calls to builtin-named subs (e.g. a project sub log/index/open called as a
  bare function) that pre-PR resolved via same_module. Extracted the decision
  into pure, unit-tested cbm_perl_suppress_generic_match() shared by the
  sequential (pass_calls.c) and parallel (pass_parallel.c) resolvers; corrected
  the inaccurate comments (Perl has no LSP/textual stage before the guard).
- Tighten perl_is_identifier_callee to require '::' pairs (reject a lone ':',
  ':::', or trailing '::').
- Add resolver-contract tests covering weak-match suppression, same_module/
  import_map retention, genuine-call survival, non-Perl no-op, and NULL strategy.

Verified on a real Perl monorepo: .cgi builtin/CPAN/config-string noise stays
eliminated while same_module edges to builtin-named subs are recovered.

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* fix(perl): address QA round 2

Round 2 (Claude panel) caught a regression introduced by the round-1 refactor:
cbm_perl_suppress_generic_match whitelisted only the exact strategies
"same_module" and "import_map", but resolve_import_map can also return
"import_map_suffix" (confidence 0.85 — a genuine import-based resolution, not a
weak short-name guess). A '::'-qualified Perl builtin/method call resolved via
the import-suffix fallback was therefore dropped, contradicting the helper's
documented contract and partially missing acceptance criterion (d).

Add import_map_suffix to the kept (high-confidence) set so only the weak
short-name strategies (suffix_match / unique_name) are suppressed; update the
doc comment and add a unit-test case asserting import_map_suffix is retained.

Deferred as advisory (non-blocking, noted on the PR): a hypothetical leading-'::'
(main:: shorthand) under-extraction in perl_is_identifier_callee, and a
colon-edge-case coverage gap (logic correct by inspection).

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

---------

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:03:53 +02:00