Commit Graph
3397 Commits
Author SHA1 Message Date
Martin Vogel 20fd3aadb5 Merge pull request #2511 from DeusData/fix/issue-1364-distinct-before-cap
fix(cypher): count DISTINCT rows against the row cap
2026-10-04 16:24:03 +02:00
Martin Vogel 873a966504 Merge pull request #2510 from DeusData/fix/issue-1484-close-range-v2
fix(subprocess): close inherited descriptors with kernel range operation
2026-10-04 16:23:58 +02:00
Martin Vogel 02abf31016 Merge pull request #2509 from DeusData/fix/issue-1186-psr4-imports-v2
fix(pipeline): resolve PSR-4 imports to exact class files
2026-10-04 16:23:52 +02:00
Martin Vogel dffec08c40 Merge pull request #2498 from DeusData/fix/issue-1277-py-crossfile-fields-v2
fix(python): retain typed fields across file boundaries
2026-10-04 16:23:46 +02:00
Martin Vogel 3a26acf402 Merge pull request #2496 from DeusData/fix/issue-1357-default-branch-v2
fix(mcp): detect changes from the repository default branch
2026-10-04 16:23:38 +02:00
Martin Vogel 79363adbc7 Merge pull request #2495 from DeusData/fix/issue-598-graphql-route-name
fix(pipeline): key GraphQL routes by operation name
2026-10-04 16:23:32 +02:00
Martin Vogel 5a9f6e7637 Merge pull request #2494 from DeusData/fix/issue-1300-silent-worker-exit
fix(index): report clean worker exits without a response
2026-10-04 16:23:27 +02:00
Martin Vogel bad3e482e2 Merge pull request #2469 from DeusData/fix/issue-1419-sqlite-write-amplification
fix(store): cut WAL write amplification on 64 KiB-page indexes (#1419)
2026-10-04 16:23:09 +02:00
Martin Vogel b5649e9823 Merge pull request #2468 from DeusData/fix/issue-1167-1180-client-detect
fix(cli): client detection for OpenCode, Hermes, Claude (#1167, #1180)
2026-10-04 16:23:04 +02:00
Martin Vogel 8c12de9b16 Merge pull request #2467 from DeusData/fix/issue-2228-toml-foreign-tables
fix(install): keep foreign tables between the managed TOML markers (#2228)
2026-10-04 16:22:57 +02:00
Martin Vogel a5d2ea0e18 Merge pull request #2466 from DeusData/fix/issue-1153-instance-receivers
fix(cpp): resolve Class::member calls across files (#1153)
2026-10-04 16:22:51 +02:00
Martin Vogel 3aef1c0bd8 Merge pull request #2464 from DeusData/fix/route-registration-before-xlang-suppression
fix(pipeline): route registrations survive the cross-language guard
2026-10-04 16:22:44 +02:00
Martin Vogel 971afc069d Merge pull request #2463 from DeusData/fix/http-axios-capitalized-import
fix(pipeline): classify capitalized `Axios` calls as HTTP clients
2026-10-04 16:22:26 +02:00
Martin Vogel 59a44d80a9 Merge pull request #2462 from DeusData/fix/http-typed-call-no-url-twin
fix(pipeline): no method-less arg_url twin for a typed HTTP call
2026-10-04 16:22:20 +02:00
Martin Vogel c4e1ad4c87 Merge pull request #2448 from DeusData/fix/hook-augment-log-path
fix(hook-augment): log under the cache dir, never at a cut-off path
2026-10-04 16:22:11 +02:00
Martin Vogel 6644fc433d Merge pull request #2447 from DeusData/fix/issue-1146-laravel-withrouting-prefix
fix(routes): compose the Laravel 11+ withRouting api prefix into Route paths (#1146)
2026-10-04 16:22:05 +02:00
Martin Vogel f6fe11dde9 Merge pull request #2446 from DeusData/fix/issue-1690-skill-project-name
fix(mcp): resolve a directory project arg to its owning project (#1690)
2026-10-04 16:21:59 +02:00
Martin Vogel 01fd6cb5e6 Merge pull request #2342 from DeusData/feat/callable-identity-plumbing
feat(identity): callable identity plumbing, no language enabled (#2061)
2026-10-04 16:21:48 +02:00
Martin Vogel db1dcf4600 Merge pull request #2333 from DeusData/fix/issue-1948
feat(watcher): opt-in polling of non-git project roots (#1948)
2026-10-04 16:21:40 +02:00
Martin Vogel 510fc2d695 Merge pull request #2329 from DeusData/fix/issue-1748
fix(csharp): refresh grammar for collection expressions in conditionals (#1748)
2026-10-04 16:21:07 +02:00
Martin Vogel bf2daa3603 fix(lsp): detach field arrays before overlay refinement
Copy inherited field-type pointer arrays into the writable registry arena
before Python refines an existing field. Keep shared type objects intact
and avoid retaining per-file scratch types in the sealed fallback.

Add deterministic shared-state and later-overlay coverage, retaining the
sequential Python field regression and adding a forced-parallel variant.

Refs #1277

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-03 18:25:03 +02:00
Martin Vogel 91ba8a3aa9 fix(subprocess): close inherited descriptors with kernel range operation
Use the Linux close_range syscall or BSD closefrom before exec, retaining
the bounded close loop when the range operation is unavailable. Add
coverage of descriptor closure, strategy selection, and forced fallback.

Refs #1484

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-03 18:09:22 +02:00
Martin Vogel 830f0ccfae fix(pipeline): resolve PSR-4 imports to exact class files
Preserve import kinds and resolve class imports through Composer prefixes
to the existing target class file, including autoload-dev mappings.

Refs #1186

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-03 17:56:11 +02:00
Martin Vogel 519c765e1f fix(python): retain typed fields across file boundaries
Carry declared instance-field types through extraction and result
compaction into the shared Python type registry so chained calls through
imported classes resolve their receiver types.

Refs #1277

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-03 01:37:27 +02:00
Martin Vogel 72f206d18f fix(mcp): resolve repository default for detect_changes
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-03 00:49:21 +02:00
Martin Vogel d13c0ecb73 test(cli): mark Claude Code installations in hook fixtures
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-03 00:21:00 +02:00
Martin Vogel 8292e3e807 test: size hook log temp buffers for Windows paths
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-02 20:51:31 +02:00
Martin Vogel 810d677d41 test(smoke): follow the #1180 Hermes home and Claude detection
The client-detection fix moved the Windows Hermes home to
%LOCALAPPDATA%\hermes and stopped counting a bare ~/.claude directory as a
Claude Code install. The smoke script followed only part of the way.

Hermes: 8x and 8x-i read the new HERMES_HOME_DIR, but 8x-ii still looked
for the pre_llm_call hook in ~/.hermes/config.yaml, and the uninstall
absence checks 9m and 9n still named ~/.hermes. On Linux and macOS
HERMES_HOME_DIR is ~/.hermes, so the stale paths pointed at the right
directory and nothing showed. Only on Windows do the two differ: 8x-ii
failed on a hook that install had written to the new home, and 9m and 9n
passed without looking at any file install had produced. All three now use
HERMES_HOME_DIR. The assertions are unchanged.

Claude Code: the fixtures of 9b-2, 9b-3, 9b-8 and both Phase 13 legs
created only an empty ~/.claude, which is no longer a Claude Code home.
Wherever no claude executable is installed, 9b-2 then finds no .claude.json
entry and 13e finds no agent config, and 9b-3, 9b-8 and the install.ps1 leg
stop exercising the Claude paths without saying so. The Linux test
container reproduces both failures. The Windows job never got that far,
because 8x-ii stops it in Phase 8. The fixtures now seed the .claude.json
user config through one helper, the same seed the unit tests use.

9b-1b pins the new rule end to end: where the empty-home control shows no
Claude Code, a home with only a bare ~/.claude gets no Claude config. A
venue that has a claude executable skips it, with the reason at the case.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-02 06:45:39 +02:00
Martin Vogel 96c3f41cf3 Merge pull request #2337 from DeusData/fix/issue-2003
fix(tests): ignore an inherited git repository environment (#2003)
2026-10-02 05:03:00 +02:00
Martin Vogel cb7d937638 Merge pull request #2249 from DeusData/distill/2109-ci-pins-and-sbom
ci: pin the noble-dependent runners, and attest the SBOM without the wrapper
2026-10-02 00:23:20 +02:00
Martin Vogel fc70534e81 Merge pull request #2425 from DeusData/fix/trace-path-qn-fallback-leak
fix(mcp): free the empty name-lookup result before trace_path's QN fallback
2026-10-02 00:06:01 +02:00
Martin Vogel 7ddbe66542 fix(routes): compose the Laravel 11+ withRouting api prefix into Route paths (#1146)
Laravel 11+ registers its route files in bootstrap/app.php through
Application::configure()->withRouting(web: ..., api: ..., apiPrefix: ...)
and mounts every file passed as `api:` under `apiPrefix` (default 'api').
That prefix lives in a different file than the routes, so per-file
extraction -- which already composes the in-file Route::prefix()->group()
chain (#952) -- minted `GET /users/me` for a route Laravel serves at
`GET /api/users/me`. The Route qualified_name is the join key for
cross-repo HTTP matching, so real callers of /api/... never linked, and an
api route could silently merge with a same-path web route.

Fix: a small Laravel mount resolver, src/pipeline/laravel_routing.c. For
each PHP file that registers a path-shaped route, both route passes (the
sequential calls pass and the parallel resolver) look up the nearest
ancestor bootstrap/app.php once, parse it with the PHP grammar, and, when
its withRouting(api: ...) names that file, prepend the apiPrefix to every
Route minted from it. Only source facts are used: an `api:` entry must be a
__DIR__ / dirname(__DIR__) concatenation or a base_path() call over a
string literal (single path or array), and a given apiPrefix must be a
string literal. A non-literal apiPrefix, positional arguments or a
`using:` callback leave the paths untouched, the `web:` file is never
prefixed, and nothing is inferred from file names. The lookup keys only on
the repo path and the file path, so full, incremental and delta runs
compose the same identity.

Proof (origin/main 1f9b4db1 vs this change, same machine):
- krayin/laravel-crm @03ebedd7: GET /user -> GET /api/user; the other
  four Route nodes are unchanged.
- filamentphp/demo @d5b618a1: GET /user -> GET /api/user.
- laravelio/laravel.io @24be4893: the api.php POST '/' inside
  prefix('articles') was merged into web.php's POST /articles node; it is
  now its own POST /api/articles (39 -> 40 Route nodes, every web route
  unchanged).
- Cross-repo: a TypeScript client calling axios.get('/api/users/me') and
  axios.post('/api/orders') against a Laravel 11 server:
  CROSS_HTTP_CALLS 0 -> 2.

Tests (edge_types_probe): default api prefix plus the composed
qualified_name, a custom multi-segment apiPrefix with the array form of
`api:`, the `web:` file stays unprefixed, a Laravel <= 10
RouteServiceProvider control stays as it is, a non-literal apiPrefix is
left alone, and the parallel resolver with the app in a monorepo
subdirectory. The four bug tests were RED 3x before the fix, pass with
it, and go RED again with only the composition reverted.

Fixes #1146

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-01 22:57:48 +02:00
Martin Vogel bedbc0ac00 Merge pull request #2345 from DeusData/fix/issue-2127
fix(pipeline): never bind an external Python import to a same-named project symbol (#2127)
2026-10-01 22:38:18 +02:00
Martin Vogel 7d4a12cb0a Merge pull request #2350 from DeusData/docs/cli-daemon-behavior
docs: one-shot cli commands use the shared daemon
2026-10-01 22:02:55 +02:00
Martin Vogel 922531a130 fix(csharp): refresh grammar for collection expressions in conditionals (#1748)
The vendored tree-sitter-c-sharp (88366631d598) predated upstream's C# 12
collection-expression support (tree-sitter-c-sharp #402, first released in
v0.23.4). A `[...]` literal could only be parsed as an
element_binding_expression, so `c ? [] : x`, wrapped `? []` ternaries and
both-branch collection conditionals had no valid parse. Error recovery then
ate the surrounding statement, the file was reported as parse_partial, and
nearby declarations could fall out of the graph.

Refresh the generated grammar bundle byte-for-byte from upstream tag v0.23.5
(cac6d5fb595f5811a076336682d5d595ac1c9e85). The ABI stays at 15, the MIT
LICENSE is byte-identical, and scanner.c is content-identical. The only new
node types are collection_expression, collection_element,
expression_element and spread_element. No node type is removed or renamed,
and the C# extractor and LSP depend on none of the old or new names.
MANIFEST.md and the vendored checksums are updated.

Add parse_coverage regressions for the reported variants plus a `? null`
control. A second test checks that calls inside a collection expression
(element and spread) are still extracted. A malformed-source guard checks
that broken C# is still flagged.

On dotnet/runtime (60108ba6), 4 files are no longer parse_partial and no
file becomes newly partial. Utf8JsonWriterTests.cs gets its class back, and
216 orphaned Functions are re-attached as Methods. Two files that were
already partial for unrelated unsupported syntax (the preview `closed`
modifier, `async` as an identifier) recover differently and lose 46 and
10 definitions.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-01 20:13:24 +02:00
Martin Vogel c61b3806bf Merge pull request #2449 from DeusData/fix/build-seams-and-cx-tmp
fix(build): make prod objects follow TEST_SEAMS; stop cx tmp leak
2026-10-01 19:19:26 +02:00
Martin Vogel 0bed0d372d Merge pull request #2327 from DeusData/fix/issue-1736
fix(grammar): accept a lone '&' in JSX strings and text (#1736)
2026-10-01 19:19:16 +02:00
Martin Vogel 28e24c6e51 Merge pull request #2324 from DeusData/fix/issue-1720
fix(install): adopt the Codex MCP table after Codex rewrites it (#1720)
2026-10-01 19:19:10 +02:00
Martin Vogel f67243ff0e Merge pull request #2320 from DeusData/fix/issue-1428
fix(extract): form Route nodes from NestJS @Controller/@Get decorators (#1428)
2026-10-01 19:19:04 +02:00
Martin Vogel ea7e01e91e Merge pull request #2445 from DeusData/fix/cypher-optional-join-overflow
fix(cypher): grow the OPTIONAL fallback buffer in the rel cross-join
2026-10-01 19:18:59 +02:00
Martin Vogel 77ea3bb95c Merge pull request #2407 from DeusData/fix/issue-1354
fix(ts-lsp): type imported class receivers for cross-file method calls (#1354)
2026-10-01 19:09:53 +02:00
Martin Vogel 3f0f49cd6c Merge pull request #2344 from DeusData/fix/issue-2134
fix(mcp): reuse the project that owns a root on unnamed re-index (#2134)
2026-10-01 19:09:46 +02:00
Martin Vogel 0be78a53a5 Merge pull request #2451 from DeusData/fix/pypi-publish-urllib3-2.8.0
build(pypi): bump urllib3 to 2.8.0 in the publish toolchain pins
2026-10-01 19:09:39 +02:00
Martin Vogel 41b1aeacfe fix(mcp): resolve the root owner for unnamed index status polls
With #2134, an unnamed index_repository of a root that another indexed
project already owns adopts that owner's name, and the daemon keys the
job by it (application_index_project_key reads the forwarded "name").
The unnamed status poll that the #2144 async reply asks for ("the same
repo_path and status: true") still derived its key from the path alone
in index_status_project_key. For an owned root it never found the job
it had just started and answered "no index job or index is known for
project '<path-derived>'"; an ambiguous root got that same misleading
answer instead of the loud error the start returns.

index_status_project_key now runs the start path's
index_root_owner_resolve on the same canonical repo_path: an explicit
name still wins, a single other owner is the key, several owners fail
with the same error naming them, otherwise the path-derived key stays.
The status error buffer grows to hold that owner list.

The daemon's other readers of the key (job dedupe in
application_job_subscribe_locked, the synchronous cut-short marker set
and taken around application_index_run_sync) derive it from the
forwarded args, which already carry the owner name, so they agree with
the start; status was the only reader that diverged.

Refs #2134, #2144

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-01 00:30:20 +02:00
Martin Vogel 5538355530 chore(lint): clang-format the callable_sig.h include comment in store.c
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-01 00:10:39 +02:00
Martin Vogel 11867dfa74 feat(identity): callable identity plumbing, no language enabled (#2061)
Overloads that share a base QN (Java/C#/C++/Kotlin/Swift/Scala/ObjC) merge
into one graph node today, and that node collects the CALLS of every
overload. The agreed cure is one signature-qualified callable identity for
all overloading languages:

    callable_qn := base_qn [<tparams>] (params) [cvref]

This change lands the plumbing with every language still at
CBM_CALLABLE_ID_NONE, so the graph is byte-identical; each language is
switched on later in its own change (with its index-format bump).

- internal/cbm/callable_sig.{c,h}: the per-language mode side table
  (NONE|TYPED|LABELED_TYPED|LABELED|ARITY; kept out of CBMLangSpec like
  cbm_string_dispatch_suffixes, since ~160 positional rows would otherwise
  all need the member under -Wmissing-field-initializers), the shared
  builder cbm_callable_sig[_mode] for the def and the call-scope QN, and
  the inverse cbm_qn_callable_base_len[_named]. Suffix contract: no '.',
  '::' or '->'; whitespace-normalized; comments/annotations/attributes
  dropped; qualified types keep their last segment; '->' spelled '=>';
  variadic '[]' (Java) / '~'; C++ top-level cv dropped, arrays decay,
  cvref kept; <= 200 bytes, longer ones keep whole leading entries and end
  in '#<fnv1a64 of the full suffix>)'. The builder is iterative (cursor
  walk, bounded declarator loop) and self-checks against the inverse.
- The inverse is anchored to the node's bare name wherever the name is
  known: real QNs already end in ')' without being callables (a Java field
  whose name is its declarator `X = f()`, make `$(VAR)` targets), and only
  the anchor makes the inverse exact on them.
- Leaf splitters strip a suffix first (no-op today): registry by-name key
  (indexed by the bare name for a suffixed callable), cbm_lsp_bare_segment,
  cbm_pipeline_qn_class_method_tail, pxc_last_component, pxc_qn_leaf,
  go_index_visit_node, collect_prod_defs, sg_qn_prefix_len.
- New unused fields: CBMDefinition.qn_sig_off, CBMResolvedCall.callee_sig
  (relocated by result compaction), CBMRegisteredFunc.sig, CBMLSPDef.sig.
  The lsp_surface codec is unchanged (no format bump in this change).
- get_code_snippet and trace_path gain base-QN tiers
  (cbm_store_find_nodes_by_qn_base) that find nothing until suffixes exist.
- store: cbm_store_find_nodes_by_qn_suffix built its LIKE pattern in a
  512-byte buffer (a long suffix was truncated into a pattern that matched
  nothing) and did not escape '_'/'%' (`my_func` also matched `myXfunc`).
  The pattern is now heap-built, escaped, and matched with ESCAPE '\'.

Proof: full sorted node+edge+lsp_surface dumps are byte-identical before
and after on 7 corpora (this repo, elasticsearch libs, Exposed, dotnet
System.Linq, System.Collections, coreclr/gc, a C corpus).

Refs #2061

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-01 00:10:39 +02:00
Martin Vogel eb99608357 fix(store): cut WAL write amplification on 64 KiB-page indexes (#1419)
Every index is written with 64 KiB pages (sqlite_writer.c), but read-write
connections kept SQLite's default 2000 KiB page cache, which holds about 31
of those pages. A closure-repair delta purges and patches rows across the
node and edge tables and all of their indexes, so the transaction kept
spilling dirty pages into the WAL and rewriting them in place. A one-line
body change on a 294 MB django index wrote 5.2 GiB to the WAL (about 75k
in-place page rewrites) and 5.7 GiB of logical writes in total. Read-write
on-disk connections now use a 64 MiB cache (1024 pages, the budget bulk mode
already used), and end_bulk restores that instead of the 2000 KiB default.
Query connections keep the default.

cbm_pipeline_publish_staged also forced synchronous=FULL on the private
staging database, so every metadata and FTS commit fsynced the WAL. A crash
before the atomic rename discards the stage, and
cbm_store_seal_for_atomic_publish() still raises the connection to FULL for
the TRUNCATE checkpoint and DELETE-mode switch that make the published file
durable, so those per-commit fsyncs protected nothing. The commits now run at
the store's NORMAL level, which SQLite documents as corruption-safe under
WAL. No corruption or atomicity guarantee changes: the live database is
still only replaced by a sealed, integrity-checked file via rename.

Measured on macOS, main vs this change, same machine and corpora:
- django one-line edit (closure repair): WAL writes 5.2 GiB -> 0.52 GiB,
  logical writes 5.76 GiB -> 1.08 GiB, WAL fsyncs 13 -> 8
- django full index: WAL writes 58 MiB -> 47.5 MiB, WAL fsyncs 9 -> 2
- php (65 MB index) one-line edit: WAL writes 82.5 MiB -> 32.3 MiB, spill
  rewrites 468 -> 0
- kubernetes (1.7 GB index) full index: WAL writes 669 MiB -> 390 MiB, WAL
  fsyncs 13 -> 6

The comments that put a healthy WAL at ~4 MiB assumed 4 KiB pages; at 64 KiB
the 1000-page autocheckpoint is ~64 MiB, still well under the 256 MiB
journal_size_limit.

Tests: store_pragmas/store_role_pragmas_issue1419 pins the effective pragmas
of each store role (read-write, bulk, seal, query), and
pipeline_semantic_manifest_repro/
pipeline_staged_publish_commits_below_full_sync_issue1419 reads the effective
synchronous level from a WAL hook on every connection that a full index plus
a closure-repair reindex opens, and requires that no commit runs at FULL.

Refs #1419

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-30 23:52:06 +02:00
Martin Vogel cf72f1a657 fix(tests): ignore an inherited git repository environment (#2003)
Git exports repository-local variables (GIT_DIR, GIT_INDEX_FILE,
GIT_WORK_TREE, ...) into hooks, and they take precedence over
`git -C <dir>`. Every fixture git command in the test runner therefore
acted on the caller's real repository whenever the suite ran with those
variables set: with GIT_DIR alone, fixture assertions failed and commits,
branches and linked worktrees were written into the caller's clone; with
the full hook set, fixture commits failed and git-dependent tests degraded
to "git not available" SKIPs. The pre-commit hook already clears the
variables (7883507d), but any other caller (a shell with GIT_DIR set, an
editor hook, `GIT_DIR=... scripts/test.sh`) still hit it.

The test runner and the repro runner now clear git's local-env list
(`git rev-parse --local-env-vars`) once at startup, before any fixture or
re-exec'd child spawns git, so every fixture scopes to its own repository
no matter who invokes the suite.

tests/test_git_env_isolation_contract.sh (Step 3a of scripts/test.sh)
drives the real runner's git_context suite with GIT_DIR, and with the full
hook set, aimed at a throwaway decoy repository. It requires the same
pass/skip summary as a clean run and a decoy left untouched (no refs, no
linked worktrees).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-30 23:50:03 +02:00
Martin Vogel 492ae13557 chore(lint): release the #2127 import-filter strings through safe_free
The memory-core ratchet flagged src/pipeline/pass_pkgmap.c growing by 2 raw
allocator sites (82 -> 84). Both are frees of strings returned by
cbm_pipeline_fqn_compute and cbm_pipeline_resolve_module, which the core
never counted, so they now go through safe_free as the rest of the codebase
does rather than raw free or cbm_free. Behaviour is unchanged;
pass_pkgmap.c is back at its baseline of 82.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-30 23:41:55 +02:00
Martin Vogel 5acd91a39e fix(pipeline): never bind an external Python import to a same-named project symbol (#2127)
`from unittest.mock import patch` names a module outside the project.
Strategy 1 of cbm_pipeline_resolve_import_node cannot resolve it, so the
Strategy-3 symbol-name fallback bound the import to the only project
definition whose leaf is `patch` -- a REST view's PkgConfigView.patch --
and every `patch(...)` call became an import_map CALLS edge at confidence
0.95. Once that IMPORTS edge is gone the same call falls through to the
weak short-name strategies (unique_name 0.75, suffix_match), which bind it
again; `mock.patch(...)`, `json.loads(...)`, `os.path.join(...)` reached
project methods the same way, because the member guard exempts
import-rooted receivers.

Two per-language (Python-only) rules, both keyed on the import's module
chain rather than on any spelling list:

- Import side (pass_pkgmap.c, shared by both drivers): for an import
  whose module is not in the project, a Strategy-3 hit must spell the
  import's module chain as an ordered subsequence of its QN (a sys.path
  root or src/ layout the resolver missed still qualifies), and whatever
  names a module (an enclosing path segment, any segment of a plain
  `import x`) may only bind a Module/File node. Imports of project
  modules are unchanged (they may re-export from anywhere).
- Call side (pass_calls.c + pass_parallel.c, identical gates): a call
  whose root identifier is bound by an external import (no IMPORTS edge
  materialized it) and whose weak-strategy target contradicts that
  import's chain is not emitted as a plain CALLS edge. import_map /
  same_module / lsp_* edges are never touched.

django/django (parallel pipeline): CALLS 62,430 -> 60,123, TESTS
29,872 -> 28,571, IMPORTS 16,068 -> 15,491. Removed CALLS are
member calls through stdlib imports (1,900: os.path.join ->
defaultfilters.join, json.loads -> signing.loads, mock.patch ->
RedirectView.patch) and bare calls of stdlib names (398: date ->
defaultfilters.date, urlsplit -> MultiPartParser.parse); a random
25-edge sample was 25/25 false. Six IMPORTS are re-targeted to the real
definition (template_tests.utils.setup, multiple_database TestRouter,
pr_quality errors.Message).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-30 23:41:55 +02:00