Commit Graph
100 Commits
Author SHA1 Message Date
Martin Vogel 7c8b1360ac fix(python): a namespace handed on or aliased only to be read writes nothing
sqlalchemy's package inits (sqlalchemy, sqlalchemy.orm, sqlalchemy.sql) and
orm/collections.py end with `__go(locals())`. Any module-level locals() handed
to a call failed the whole file as a dynamic namespace effect, so every import
there was unaudited and every export UNSUPPORTED: 14,464 real CALLS edges of
sqlalchemy abstained as python_stabilized_export_binding, and their children
(`from sqlalchemy import event`) as python_export_member_dispatch_unproven.

Three rules, each true of Python:

- `f(locals())` / `f(vars())` at module level writes nothing when f is the
  module's one undecorated def, bound before the call, whose body never spells
  the parameter receiving the namespace, nor locals, vars, eval, exec or a
  frame's locals. The body's own effects are walked as anywhere else.
- eval/exec of one plain string literal that is a single identifier only reads
  it (pydantic/v1/utils.py `eval('__IPYTHON__')`).
- `alias = globals()` in a function writes nothing when every other spelling
  of the alias there is a membership test, an item read or a read-only method,
  and the function has no other way to reach it (sqlalchemy exc.py and
  ext/automap.py). A module-level alias stays an effect: it is a module
  attribute another file can write through.

Referee against SCIP, python, 10 repos: recall 70.5% -> 82.0%, precision
99.3% -> 99.2%. sqlalchemy 42.9% -> 67.6%, pydantic 85.4% -> 90.6%; click,
flask, poetry, django, celery, httpx, scrapy, starlette unchanged. Every new
sqlalchemy SCIP disagreement was listed and read: builtin/typing name
collisions, same-line chain mappings, and narrowed-type selections.

The pipeline gap python-reexport.go_reexport is now required: __go never
spells lcls. Its parameter-write decoy stays forbidden.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 06:21:26 +01:00
Martin Vogel 0ecf116e61 fix(go): a module rooted at the repository root imports as the Project node
A Go import targets the node of its package's directory, and the strict
import path accepted only Folder nodes. The repository root has none: the
Project node is that directory, and its QN is the module of every root
file. caddy keeps go.mod and `package caddy` at the root, imported as
"github.com/caddyserver/caddy/v2", so that import never resolved and every
caddy.X call, and everything typed through one, stayed unknown under the
generic shared_binding_type_fixed_point reason. No cap was involved.

go_package_node accepts the Project node for the root directory in the
module-path strategy, the duplicate-module fallback and the ownership
check, which reads the package directory ("" for the root) from it. The
ownership rule itself is unchanged: a module without a replace directive
or go.work entry still takes the path from the module cache.

caddy against SCIP: recall 83.45% -> 95.89%, real lost 794 -> 200, never
found 229 -> 54, new false 0 -> 0. traefik, minio, cli, helm, prometheus
and etcd unchanged.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 06:08:26 +01:00
Martin Vogel a1c437432f style(dart): mark the barrel walk's read-only callback for cppcheck
Its sibling callback writes through the same parameter, so the shared type cannot take const.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 05:05:41 +01:00
Martin Vogel 2d7e422e7c feat(cpp): forward-declared classes, scoped enum values and three parse repairs
A forward-declared C++ class in view binds to the one definition of that qualified name, by the one-definition rule; with several definitions, to the one whose file includes the declaration. C tags have no linkage and stay abstained. A forward in view outranks a short-name match in another namespace, which removes false edges such as lldb's Stream to llvm::yaml::Stream. Color::RED, gfx::Blend::ADD and typedef'd enum values bind.

Repairs: an #include in an enum body beside a #define no longer breaks the rest of the file; a brace opened in one #ifndef block and closed in another, as DeclSpec.h does, no longer costs the file its namespace; #include inside a namespace is read. Whole llvm-project: CALLS 647,135 to 654,085, USAGE 970,146 to 1,015,783, rc 0, 603 s, no quarantine. Changed relations sampled from source: no wrong edge.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 04:04:05 +01:00
Martin Vogel 5af6035371 clang-format this lane's changes
Layout only, on the six files whose new lines clang-format would wrap differently. The suites ran on the tree before this commit.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 04:03:09 +01:00
Martin Vogel dc4d2ab709 The k8s growth test goes with the reverted change; drop a stray empty file
The k8s change of f8bbdc07c, reverted in f344f72a7, left the same-copies quadratic where it was: the pass took 1,192 -> 5,010 -> 17,024 ms at 2,000 -> 4,000 -> 8,000 same-named copies with it and 1,217 -> 4,791 ms without it, and its growth test stayed at ratio 4.00. The references were already cheap (refs=8000 edges=8000); the cost is in another step of the pass. The test goes with the change.

internal/cbm/lsp/lsp_all.c was an empty file a touch created, picked up by 750b68951.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 04:03:09 +01:00
Martin Vogel c36bd896c5 Revert "k8s: a reference reads only what can change its answer"
This reverts commit f8bbdc07ce1ab7ca1e1fc27d78d9bd5a1d413179.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 04:03:09 +01:00
Martin Vogel 9c6f2f76cd Dart: a name asks only the imports that can answer it
Each name of a file walked every import, imports times names. The imports that can change a name's answer are those whose library, or something it exports, declares the name, and those whose library the build's configuration picks; every other import adds nothing wherever it stands. They are found from the libraries declaring the name, up through who exports them, and asked in written order, so each reason stays the one the first deciding import gives. A prefix and an outside import with no filter are looked up by name.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 04:03:09 +01:00
Martin Vogel 5a6267ae7d k8s: a reference reads only what can change its answer
Each reference gathered and sorted every same-named object of its bundles. A USAGE reference draws its edge only when exactly one object answers it, so it now stops at the second answer, which is the same rule written without the gather. Any other reference with a copy in its own file means that copy, so it reads only its own file's objects, indexed by file and name. Same-named copies of one directory are linear now.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 04:03:09 +01:00
Martin Vogel 059fbdbaa2 Swift: other files' privates are stepped over, and a name nobody declares walks a type once
A chain of one name held every file's private declaration of it, and each lookup read them all: private let logger in every file of a module cost files squared. The table now keeps, for a chain holding a file-local declaration, the others in order and each file's first own one, and the seven loops that skip invisible privates read that view: the same declarations in the same order, the others never read.

A name no declaration of the project has, and no closed protocol's member, can only find whether a type's chain leaves the project, which is the type's answer and not the name's. It is walked once per file per type. This keeps extension A: P bases linear now that the chain walk has no bound.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 04:03:09 +01:00
Martin Vogel 3b823ccf2f Lists a file or a class can outgrow grow; a file's size decides nothing
Bases and mixins of Dart, PHP, Swift, Scala, Ruby and Perl classes, a PHP variable's writes, a Python function's returns, the files a shell process sources, the C# using-directive walk and the headers a C translation unit reaches were fixed arrays: past them a declaration was dropped or its class went open. They grow on the walk's arena now. A shell command asks the files that declare its name instead of every file of the process.

The binding walks, the source adapters and the proof passes each pass over a file a bounded number of times, and their node and visit counts voided a large generated file whole. Only their nesting guards stay. A Rust trait's method names are no longer cut at 63.

Tests: a class of 70 traits, a variable of 12 writes, a class of 40 bases, a process of 300 sourced files, the seen set's growth and depth guard, and the extension-named imports of item 4, which bind nothing. The chain test of item 1 now measures the walk alone, and the barrel's class is no substring of another needle.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 04:03:09 +01:00
Martin Vogel aecf66b116 A chain walk's length decides nothing
CBM_SOURCE_CHAIN_MAX was a fixed array of 128 names: past it a class chain or a Dart barrel abstained because a counter ran out. The seen set now starts inline and moves to a table on the walk's arena, with constant lookups. Recursion was bounded by that array too, so each recursing walker now keeps a depth, with a guard of 1024 levels for the stack.

A Dart barrel read its export list for every name of every file, which only the old bound kept near linear. A name's walk now starts from the libraries declaring it and goes up through who exports them, without recursion, so a barrel's width costs nothing.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 04:02:55 +01:00
Martin Vogel 3ca4d91b96 fix(go): a definition with no name is no definition, not the key 12:(null)
GCC's production build refused the null %s, with -Werror. Also the Lua and Shell adapters' cppcheck items: an always-false check and three scopes that can narrow.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 03:08:35 +01:00
Martin Vogel 0507bbf295 Merge the audit of late code: no more quadratics there, and Elixir and Erlang references
Elixir and Erlang made a composite value a CALL_REFERENCE; a dozen walks that grew with the square of the input now read an index once, with the graph unchanged on 12 fingerprints across nine repositories.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 02:25:05 +01:00
Martin Vogel 20e8dff429 perf(resolver): ask a file's imports by name for each call's spawn check
Each call's spawn check read the file's import rows to find the one its callee's head names, in every language with a spawn table, on both drivers. A Tcl script of 32,000 `source` lines asked that 32,000 times and spent 1.1 s on it, and a call to a name a table lists read the rows once more for its module.

A file's calls now ask a table built on the first question: a local name's first row with a path, and for each module a table names, whether a row imports it. It lasts one file on one thread and answers only for the rows it was made from. Anything else, a name too long for the buffer, or memory running out, reads the rows as before.

complexity_spawn_match_is_linear_in_a_files_imports covers a repeated callee, distinct callees and a module check. complexity_spawn_file_index_answers_as_the_rows_do compares the two routes across languages and callees, with rows lacking a path, repeated names and rows of another file. Numbers and identity: the lane report.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 02:21:12 +01:00
Martin Vogel ee084972e9 perf(resolver): clear a conflicting import name's entries once
The last dot ends an import's local name, so every `source ./a.sh` row is named sh, and every `part 'a.dart'` dart. All of a file's rows then conflict, and each one cleared all of the name's IMPORTS entries, which the row before it had just cleared. One shell file of 16,000 `source` lines spent 3.8 s in its import map, and one Dart library of 16,000 parts 3.7 s.

Each name now clears them once. Nothing else can add such an entry after the first row: edges are all read before the rows, and a name no edge mapped has none. The map holds what it held.

complexity_import_map_same_local_name_is_linear asserts the map's contents at both sizes. Numbers and identity: the lane report.

The extension as a local name is recorded there, not changed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 02:21:11 +01:00
Martin Vogel 47cfe71f31 test(swift): the no-parameter closure fixture lists every edge it makes
The oracle treats an edge nobody listed as wrong: the struct values and the function in the shorthand case are real, and the second body's call is the file's second occurrence.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 02:09:54 +01:00
Martin Vogel 76d0adb9d6 test(cli): the macOS update tests outgrew a release's archive limit
They ship the test runner itself as the fake release, and it is now 525,426,472 bytes against a limit of 524,288,000, so the update reported a binary missing from the archive. The limit is right for a release and stays. These two tests lift it for themselves through a seam, and a new test pins that an archive over the limit is refused, which nothing did.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 02:09:54 +01:00
Martin Vogel c263d9656d fix(store): a failed resolver stage keeps its SQL until the writer finalizes
The direct writer's tables point at CREATE text the stage owns, and finalize runs whether the stage worked or not. On failure the stage was freed first, so finalize built sqlite_master from freed memory: heap-use-after-free in importance and pipeline_semantic_manifest_repro, whose rejected publish reaches it. The caller already frees the stage on every path, so it now gets the stage back either way.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 02:09:53 +01:00
Martin Vogel 6802d8e8b6 perf(resolver): stop walking a Swift type's bases once the answer is open
A name that is not a type's own member asks each of its bases, so a type with many conforming extensions cost bases x names: 16,000 `extension A: P` took 157 s in resolve. Past the chain's bound of 128, every further base answers open, so the loop goes on only to reach the answer it already has.

It now stops there, once the bound has passed and the answer is already open. Nothing past that point can change the result: a base can no longer be found or be ambiguous, and an open answer returns the same whatever `declared` or `result` hold. The `extension_only` scan also stops at its first declared record.

complexity_swift_type_bases_are_near_linear: 3.84 on the old walk, 2.00 now.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 01:27:42 +01:00
Martin Vogel eccd026782 perf(resolver): read a Dart barrel's exports as a walk reaches them
Every file that imports a barrel read the barrel's whole export list again, though one name's walk stops at the chain's bound of 128 libraries. With the barrel listing the project, that is files x exports: 16,000 libraries took 39.7 s in resolve.

Exports are now read when a walk first reaches them, and a walk stops once the bound is passed, since no further library can then be visited or change its answer. So `dart_export_graph_not_walked` is still what a name past the bound gets.

Left as it was: an export that a `show` or `hide` rules out costs a filter check per name and does not count toward the bound.

complexity_dart_barrel_exports_are_near_linear: 3.71 on the old read, 1.94 now.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 01:27:36 +01:00
Martin Vogel 681208abcc perf(extract): look back for a flat label once per sibling
NASM and ObjectScript routines are flat: each statement looks back over its earlier siblings for the label above it. A long stretch under one label was quadratic in steps, and each step was itself a walk from the first child.

The walk reaches siblings in order, so a search now stops at the sibling the last search under this parent started from, and what that one found holds for everything before it. One memo per ancestor level, for NASM's climb. Which label a statement gets is unchanged.

complexity_flat_preceding_label_is_linear: NASM 4.00 on the old walk, 2.00 now; ObjectScript 2.00 now.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 01:27:30 +01:00
Martin Vogel 113b301713 perf(extract): read a Go block's bindings once for its route calls
For every route call, go_find_binding walked back over the statements before it to find what the receiver was bound to, and each step back was itself a walk from the block's first child. A function of 16,000 `r.GET("/x", h)` lines spent 91 s in extraction, and 16,000 `g := r.Group("/g")` pairs 207 s.

A block's bindings are now read once, the first time a route call in it asks: every name each statement binds, by name and then in source order, in `scratch`, so it ends with the file. A question is a binary search for the last binding before the statement that asks. Which statement answers is unchanged: rebinding, shadowing, `var` lists, tuples, a name with no value and function boundaries all behave as before, and running out of memory still gives no prefix rather than a partial one.

complexity_go_route_bindings_are_near_linear, old walk -> now: see the lane report for each row. Identity: 12 fingerprints identical on gitea (1,054 `m.X("/...")` registrations in two files) and on a generated tree of 834 routes that composes each of those forms. gitea registers on one receiver and has no `g := m.Group(...)` binding in those files, so the generated tree is what covers the group forms.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 01:27:24 +01:00
Martin Vogel 53a2869c77 perf(resolver): find a name's definition by line without walking its list
A source adapter asks which definition of a name belongs to each declaration, and the answer walked every definition of that name in the file. A name can repeat all through a file, as a script's `x=1`, a chunk's `local ok` or `init` in generated code do, so the join was quadratic in the repeats. Every adapter goes through these three calls.

A name's definitions are now sorted by first line, with a link from each to the nearest earlier one still open where it starts. The answer is a binary search, then that path, which is as long as the nesting there. The tie-break is unchanged: the tightest span, and of equal spans the latest in the file's own order. `cbm_source_def_qn_of_name` keeps its answer per name.

complexity_source_same_named_definitions_are_near_linear covers Lua functions and locals, Shell, Vim, Tcl, Swift, Dart and Haskell. See the lane report for each row's ratio on the old walk. Graph output is unchanged: 12 fingerprints identical on neovim, redis, koel, Alamofire, bloc, plug, cowboy, gitea and a generated tree of nested same-named definitions.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 01:27:17 +01:00
Martin Vogel ef19c95c0c perf(extract): answer previous siblings from the syntax index
ts_node_prev_sibling and ts_node_prev_named_sibling walk from the parent's first child to the node, so asking once per child of a wide parent is quadratic in sibling steps alone, and a walk back is worse. Sites that do it per definition or per statement include the decorators before a method, a Dart function_body's signature, a C `TEST(x) {}` body and a Dart selector's callee.

Both now route through the index, like ts_node_parent and ts_node_child, so every file that includes syntax_index.h gets them, including a call added later. The answer comes from the list the index already holds. A zero-width node keeps tree-sitter's own answer: its place among byte offsets is what ts_node__prev_sibling decides, and the list cannot.

complexity_syntax_index_prev_siblings_answer_as_tree_sitter_does compares both calls with tree-sitter on every node of eight sources, parse errors and MISSING tokens included. complexity_prev_sibling_walk_is_linear: plain 4.00, indexed 2.00.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 01:27:09 +01:00
Martin Vogel 2a8771d3fe fix(resolver): an Elixir capture or Erlang fun among branches is USAGE
The extras path in resolve_calls called any callable a site names a CALL_REFERENCE, with no composite check. So `flag && &a/0`, `if flag, do: &a/0, else: &b/0` and `case Flag of true -> fun a/0; false -> fun b/0 end` each gave CALL_REFERENCE edges, which call_reference_language_complex_contract forbids: the value is one of several. This is the Perl bug in the other carrier.

The adapter now says so, as a carrier's may_be_call_reference does: an extra site carries `composite`, and such a site is USAGE. It holds under `&&`, `||`, `and`, `or`, `andalso`, `orelse`, and among the branches of `if`, `unless`, `case`, `cond`, `with`, `try`, `receive` and `maybe`, parentheses and blocks aside. A direct argument, a bound name, a return, a list or tuple entry, a message sent and a closure's body are one value again and stay CALL_REFERENCE, including inside a branch.

Ruby and Dart are not affected, and the contract suite now pins them. Ruby's `method(:a)` binds nothing, so it is no edge. A Dart tear-off is USAGE in every form, which also means a direct one never gets CALL_REFERENCE: a recall gap, not a false edge.

RED with the two assignments reverted, on the oracle with 2c3967d88: elixir-composite wrong=17, erlang-composite wrong=15, and both new contract rows fail on `reference=1/1`.

plug, cowboy and bloc: all 12 fingerprints identical before and after, so none of their 8, 19 and 23 CALL_REFERENCE edges was composite. No recall is traded on them.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 01:04:28 +01:00
Martin Vogel f710839a3f test(resolver): pin the bodyless tag standing aside from the climb's memo
`struct X;` directly under `#ifdef` has the directive as its parent, and only the translation unit above it says the tag is being declared. The directive's own name climbs to the root first and settles that path, so without `!bodyless_tag &&` the tag's climb stops at the directive and leaves a usage row for a declaration. The guard came in during a merge, by reasoning, with no test.

Removing the guard fails this at `lone_under_ifdef`: 1 row, expected 0.

The decoys count raw rows: an active branch is read twice, raw and preprocessed, so `#ifndef OTHER` gives two rows in all.

Not covered, because it is not the guard's doing: `struct X;` under `#else` stays a usage row with or without the guard. Its `alternative` field trips is_value_field first.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 01:04:07 +01:00
Martin Vogel 6b97b569db fix(swift): a function type with no parameters keeps its signature
swift_closure_signature returned a pointer to a local array for () -> Void, and the signature was stored and read long after, so what a closure's parameters meant came from a dead frame. That signature is everywhere in Swift. cppcheck found it; the test goes red under detect_stack_use_after_return=1, which the normal test run turns off.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-03 01:01:08 +01:00
Martin Vogel f3f50e4fb1 test(resolver): the oracle audits an edge that lacks the exempting key
The exemption for edges with their own producer compared json_extract(...) with =. For an edge without that key the comparison is NULL, NOT (NULL) is NULL, and the row left the unattested-edge audit: every CALLS edge to a Function lacking strategy, every USAGE into a Table, View or Model lacking via. IS keeps them in. resolver_quality_oracle_rejects_mutated_occurrences_and_projection caught it with an edge that has no occurrence row.

A Kotlin parameter's written type is USES_TYPE of the class, which the graph now states; the fixture still held it as a USAGE known gap.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 23:57:21 +01:00
Martin Vogel 33ee32d348 fix(pipeline): the binding-cache and parse workers release what they cached per thread
Both drain workers ended without cbm_destroy_thread_parser, so the 24 KiB field-name cache of each worker thread was lost on every index, which a long-running server pays for. LeakSanitizer on Linux shows it across 29 suite runs; macOS has no LSan.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 23:54:53 +01:00
Martin Vogel 2c3967d88c test(resolver): the oracle audits an edge that lacks the exempting key
The exemption for edges with their own producer compared json_extract(...) with =. For an edge without that key the comparison is NULL, NOT (NULL) is NULL, and the row left the unattested-edge audit: every CALLS edge to a Function lacking strategy, every USAGE into a Table, View or Model lacking via. IS keeps them in. resolver_quality_oracle_rejects_mutated_occurrences_and_projection caught it with an edge that has no occurrence row.

A Kotlin parameter's written type is USES_TYPE of the class, which the graph now states; the fixture still held it as a USAGE known gap.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 23:54:53 +01:00
Martin Vogel ac3a3b0f4b fix(cpp): an opaque enum in a block no longer crashes the worker
enum E : int; and enum class E; inside a block reached c_bind_local_tag_and_enumerators with no body, which then read the enumerators of a null node. A worker crash has its file quarantined and the index still reports success, so one such file was dropped silently; a second exhausted the retries and failed the whole index, as clang's own tests did on llvm-project.

The lone-tag rule keeps its meaning: the name still hides the outer enum from there on.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 23:21:15 +01:00
Martin Vogel 4cf24b147c feat(resolver): Lua, Vimscript, Shell, Tcl, Make and Vue edges from real rules
Lua CALLS on neovim go from 4 to 19,685 against 20,307 at baseline, whose count holds wrong targets: lexical scope and value flow through aliases, function M.f, return M, require via package.path forms or a unique lua, src or lib root, and __index with @class parents. Whole-file gates on load, _G, goto and ... are gone, because none can rebind a local; _ENV, setfenv and debug.setlocal still abstain. New adapters give Vimscript, Shell, Tcl and Make proven edges: neovim vim CALLS 0 to 3,365, redis Tcl CALLS 0 to 2,815, with a function table per process following source.

A Vue or Svelte script is a module whatever its markup says: koel Vue CALLS 2 to 1,484, IMPORTS 0 to 1,650. Members of an imported object literal, and const { open } = useThing() where every return names it, resolve. An adapter may now declare parse errors local: 74% of Tcl files have one. About 490 edges read from source, one wrong and fixed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 23:09:26 +01:00
Martin Vogel 4ee39037f7 Merge the performance work onto the integrated tree
Graph fingerprints stayed identical on 27 of 27 repositories across 15 languages on the integrated tree, while elasticsearch went from 367 s to 207 s, TypeScript 248 s to 70 s, kilocode 112 s to 34 s and django 43 s to 15 s. Elasticsearch's charged peak falls from 29.8 GB, 7.3 GB over budget, to 16.6 GB within it. A 990 KB Kotlin file of unclosed nested objects goes from over 50 minutes to 4.3 s, a bug main had too.

Resolved so both sides' behaviour survives: the projection deletes every controlled type in one walk while leaving edges its exemption claims. The Dart callee rule reads through the walk's linear sibling lookup. The binding climb's memo stands aside for a bodyless C tag, whose answer can lie above a settled ancestor, as in struct X; directly under an #ifdef. The source adapter keeps the repairing parse, so it reads the tree extraction read.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 23:07:48 +01:00
Martin Vogel 3a2c7b3da6 test(complexity): grow the TypeScript narrowing fixture across functions
A narrowing covers its whole function, so the search for the one covering a receiver only gets long across functions narrowing the same name. The first fixture kept every narrowing in one function and stayed at 1.61 on the old scan, so it could not fail. Across functions the old scan reaches 3.65.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:57:58 +01:00
Martin Vogel 4539147ef7 feat(resolver): recover Swift, Dart and PHP edges parse gates had voided
Alamofire's voided files go from 20 of 101 to 0, Vapor's from 49 of 277 to 6, through a same-length Swift parse view. Baseline edges kept on Alamofire: CALLS 1,049 to 3,027 of 4,698, USAGE 188 to 2,804 of 9,020; of the 978 CALLS still lost with nothing like them left, 28 of 32 read were fake. Swift private types are keyed per file, which also fixes a false edge between two files declaring one private name. Dart class headers, constructor defaults, super and exports resolve. PHP adds a project trait beside an outside one, and function return types.

Swift property QNs now carry their class, as Kotlin's do. Swift init, deinit, subscript and enum cases gain nodes, as do Dart mixin, abstract and accessor members. Swift IMPORTS is now a supported relation.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:47:39 +01:00
Martin Vogel e4f967e31f Merge the cross-lane fixes from the first full Linux run
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:46:44 +01:00
Martin Vogel 9c6cead06b test(complexity): pin each path made linear with a growth test on work counters
Each test measures a counter at n and 2n and asserts a ratio near 2, so no clock decides it. Each was run against the old scan and failed at a ratio of about 3 to 4. Equivalence tests pin the indexes to the answers of what they replaced: the syntax index to tree-sitter, name rows and the registry tail to a scan, the parse memo to a fresh parse.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:04:49 +01:00
Martin Vogel 28055a727d fix(extract): keep unparseable and deeply nested files near linear
A 990 KB Kotlin file of 90,000 unclosed `object A {` lines held an index run for over 50 minutes, and main has the same cost. Declarations left open sit side by side under one error node, so each question about a child walks that node from its start. Extraction has an index for this, but the passes that parse again did not: Kotlin's, C's and C++'s cross passes and Rust's source facts now open one, and the TypeScript, Kotlin and C proofs, compiled apart, now ask through it. Dart's next-sibling test uses the walk's cursor. That file now indexes in about 4 seconds.

The same lines closed are real depth. Each identifier's usage classifiers climbed to the root, and each definition's decorator lookup started from it. A climb that reaches the root undecided now settles its path for everything below it, so the next one stops where it meets that path: a level's answer depends only on the path through it, and a node with any extent lies in one child of its parent. Decorators ask the index for the previous sibling. What is left at great depth is the qualified names themselves, whose stored length grows with depth.

Each path went from about 4x to about 2x work when the input doubled. Answers are unchanged: all 12 graph fingerprints match on every repo measured.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:04:48 +01:00
Martin Vogel 040dc31fab perf(resolver): look up TypeScript proof imports and Rust uses by name
The TypeScript proof scanned a file's imports for each callee and annotation, and every narrowing of a path for each receiver on it. TypeScript's own checker.ts names over a thousand imports in 54,000 lines. Imports now use the file's rows, and a narrowing lookup is one binary search: entries sorted by path and start carry the furthest end so far, and one covers a position exactly when that end passes it.

A Rust module's uses were walked for each local item found, each imported name and each trait a method call asked about. Long lists now answer from rows by local name and by last segment, in list order.

Each of these went from about 4x to about 2x work when the input doubled. Answers are unchanged.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:04:48 +01:00
Martin Vogel 35154ab9f2 fix(pipeline): spill before resolve when the strict resolver would pass the budget
The projection that decides whether results spill was fitted before the strict resolver, at 160 bytes per reference. Resolve now grows by up to 3,250 bytes per reference, so elasticsearch projected 2,534 MB against 26,479 MB real, kept its results in memory, and reached 29,755 MB charged against a budget of 22,488 MB. No phase after extraction can spill.

The refit is 3,400 bytes per reference, fitted to Java, the most expensive language measured. Elasticsearch now spills by itself and peaks at 16,833 MB charged, with no slowdown and all 12 graph fingerprints the same as in memory. Spilling changes when results park, not what is resolved, so this is no work cap. It over-reads other languages, which only costs disk reads: kubernetes projects 18,479 MB for 8,384 MB real, and still keeps results in memory at the full budget.

Nothing updates the constant. A later change that costs more per reference has to refit it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:04:47 +01:00
Martin Vogel e641460b3c perf(pipeline): build one binding index per file and prepare parses in parallel
The shared resolver built each file's binding index up to four times, and the preparation passes reparsed every file on one thread. One cached index now serves them, and a parse batch parses ahead on all workers. Python's root facts and the builtins' file list stop walking per file (both 4.00x to about 2x), and the struct field fold joins by class instead of scanning a file's definitions per struct (4.00x to 2.00x). lsp_cross_prepare on django goes from 7.4 s to under 1 s.

C# reference-assembly files keep their deferred pass, which parses them one at a time as before.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:04:46 +01:00
Martin Vogel 2958401623 perf(lsp): answer import and registry questions by name
The language passes scanned a file's imports for each use and the registry for each definition while it filled, so their work grew with the square of a large file. Rows chained by name now answer those questions in the order a scan found them, and a registry can index what was added since its last index in a scratch arena, with the same first-registered answers. Each path below went from about 4x to about 2x work when a file doubled: imports in TypeScript, JavaScript, Java, Kotlin, PHP, Rust and Go, Python's imports and statements, locals in Rust and the Java, Kotlin and C# walkers, Rust module children and impls, TypeScript methods per type, Java registration and overload patches, Rust derives, Go generics and C# signature patches.

Answers are unchanged: rows hold the same order, and duplicate names still resolve to the first registration where a scan did.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:04:46 +01:00
Martin Vogel 0091031542 perf(store): write resolver evidence straight into b-tree pages
Writing evidence row by row through SQL was the largest single cost of an index: 12.9 s of writes on django, 35.6 s on kubernetes. Rows now go straight to b-tree pages and digests run in parallel, the proof pool keeps a memo of the texts of a session, and hot keys no longer go through snprintf, which takes a process-wide locale lock on macOS. Writes take under 0.4 s on django and under 0.9 s on kubernetes. Databases shrink: kubernetes 4,364 to 4,162 MB, django 932 to 869 MB. Nothing readers use is removed; the same rows are stored.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:04:45 +01:00
Martin Vogel dcdf7a489a perf(extract): read each file's syntax through an index and share its parse within a worker
ts_node_parent and ts_node_child(n, i) walk from the root or the first child, so any pass over a flat file was quadratic in its top-level items: child steps grew 3.99x when flat.py doubled, 3.98x for flat.ts, 3.94x for flat.php. Passes now read a per-tree index built on first use, which answers exactly what tree-sitter answers, and every step count above grows 2.00x. The index is scoped over the whole of extraction. A worker keeps its parse of a file for the other passes over that file, so a file is no longer parsed once per pass. C#'s file-scoped namespace is looked for once per file instead of once per class, and the doc memo keeps a wide root cached.

No graph content changes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:04:44 +01:00
Martin Vogel 8ee198fc4b fix(resolver): C tag uses, a stranded static's linkage, and bounded macro walks
A stranded static function lost its static flag and read as externally linked, so a call could bind across files: fixed with a decoy. Tag uses such as struct cfs_rq *cfs_rq now bind, kernel subtree USAGE 44,251 to 113,857, along with cast macros as receivers, C++ unions as classes, and two-headed function repair. Macro hand-off walks keep a seen set on macro and position: on doubling depth the work counter goes 35 to 47 where it was 155 to 8,225 without one.

Withdrawn as not provable: a rule binding a macro in one configuration branch and a function in another, because the call exists in only one, costing kernel subtree CALLS about 1,041; and a raw define beside a missing header, which that header may undefine, costing neovim about 5,500 edges that look true. Canonical lint passes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:00:41 +01:00
Martin Vogel 17096867bb feat(resolver): keep real edges made outside the resolver, and fix what voided them
Projection deleted every controlled edge the resolver had not recorded, including route registrations, Helm includes, Kubernetes references, SQL lineage and Ansible notifies. A narrow exemption now needs the producer's own via marker and the target's label together; a forged-looking CALLS still goes, by decoy test. Over 47 repositories CALLS into Route go from 4,315 at baseline and 0 before this to 1,146, without client calls and string methods: django 1,206 to 0. Helm includes return to 3,365 and Kubernetes references to 3,936.

One empty .tf or terragrunt.hcl no longer aborts the whole index. One templated Ansible handler no longer voids every notify in the project, nor one unfollowable .env value every $ image in a compose project. Go method-value and ASP.NET method-group handlers no longer bind by name.

For review: a receiver named router or app, with a literal path and a handler, counts as evidence for a Route, which is name-based and an exception to CALLS meaning a declaration, as is a Helm define. A dbt model target in SQL is kept without package namespace proof. The test audits skip the projection table's rows.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 22:00:09 +01:00
Martin Vogel 7e502898be fix(resolver): free the kind cache of a resolve worker
source_damage_scan runs on a resolve worker when a source adapter reads a file with parse damage, and it fills the thread-local bitset cache of cbm_kind_in_set. extract_worker frees that cache at thread exit; resolve_worker_impl did not, so each such worker leaked it. LeakSanitizer on Linux reported 152 bytes in 3 objects from ks_build in resolver_source_langs, once the much larger OCaml cursor leak was gone. The first run's report was cut before it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 21:43:29 +01:00
Martin Vogel f0d2473343 test(pipeline): a Rust tuple constructor call targets its struct
rust-std-receiver reported wrong=2 projection_errors=1 because its two constructor rows had no target identity, written when the graph had none for a constructor. The go-rust lane's rule now emits CALLS to the struct, and it is true: a tuple struct's name is its constructor function in the value namespace. `EvidenceTier(1)` and `EvidenceTier(2)` bind the one `pub struct EvidenceTier(pub u8)` in that module, and resolver_rust_tuple_struct_constructor_is_a_call pins the named, unit, value and shadowed cases that must not.

The rows now name the Struct node and are required. Naming it brings real_caller's return type into the audit, so `-> EvidenceTier` is attested as USAGE and USES_TYPE, as the matrix does for class mentions.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 21:23:18 +01:00
Martin Vogel e0d3eb6bf4 test(pipeline): promote the Perl row of callback parity
callback-parity failed on recovered_gaps=1. The log's QUALITY_MISS for javascript-script-shadow-control was only the last line before the summary: that row is an unchanged known gap, and the row that recovered is callback-perl-coderef. `perlReferenceAccept(\&perlReferenceTarget)` is a direct coderef argument to a sub of the file, and the Perl adapter now proves it, so the CALL_REFERENCE is true.

With the row required, callback-parity reads genuine=10 found=7 missing=3 recovered_gaps=0 on both routes. The sequential floor assertion also covers this pair now, so the exclusion list goes from six to five.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 21:23:06 +01:00
Martin Vogel ab04546fa2 fix(resolver): a Perl code reference under an operator is USAGE
The dynamic lane made resolve_usages call every admitted Perl carrier a CALL_REFERENCE, through a language clause beside the comment saying composite expressions stay USAGE. So `$flag ? \&handler : \&alternate` got two CALL_REFERENCE edges, which call_reference_language_complex_contract forbids for every language with callable references: the value is one of several.

The carrier now says it, as in the other languages: perl_lsp.c leaves may_be_call_reference false under a conditional, a binary operator or a low-precedence logical operator, parentheses aside, and the clause is gone. `my $r = \&own`, a table entry and direct arguments stay CALL_REFERENCE. mojo has 37 such edges and none is composite. resolver_source_langs gets positive and negative rows for each form.

Tests corrected, because the rule is right and the rows were not:

- The registrar in both Perl fixtures was `sub accept`. accept is a builtin and only an import overrides one (perlsub), so the adapter was right to abstain and `CALLS refs.accept` was never genuine. `perl -c` on the old fixture says "Not enough arguments for accept"; with two arguments the builtin runs and the sub does not. It is now `sub submit`, same length, and those rows are required. builtin-not-overridden-by-a-sub keeps the decoy.
- The conditional's USAGE rows named `handler` without `\&`. The carrier and every other Perl row use the `\&handler` span, and the rows were never found, so nothing had checked them. Corrected and required.
- perl_exact_coderef_is_call_reference-1 is recovered and true: promoted.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 21:22:52 +01:00
Martin Vogel e1ff392bb2 test(resolver): promote the Scala operator and generic call rows
The dynamic lane's Scala rules recovered these, and the matrix asks for promotion. Each row was read against its fixture and the index:

- `a + b` with `a: Vec2`, `def +(other: Vec2): Vec2`: `a.+(b)`, Vec2.+.
- `(a + b).dot(b)`: `+` declares Vec2 as its result, Vec2.dot.
- `first(ns)` with `def first[T](xs: List[T])`: the top-level def. A generic def had no arity before, so its calls abstained.

No false edge came with them: `x + other.x` on a Double field abstains as scala_value_of_unknown_type, and `Some(...)` as scala_name_not_in_project.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 21:22:47 +01:00
Martin Vogel 76aaffbebc fix(resolver): delete the cursor of the OCaml structure walk
ocaml_collect_structure created a tree cursor and had no ts_tree_cursor_delete at all. Its one exit after the cursor is the fall-through at the end of the loop, so every struct of every OCaml file leaked one. LeakSanitizer on Linux reported it at adapter_ocaml.c:603 from resolver_source_langs. The other three cursors in the file are already paired.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 21:22:44 +01:00
Martin Vogel e5d6aae0a2 feat(resolver): Kotlin type references and C# named arguments
Kotlin USES_TYPE goes from 0 to 6,131 on Exposed: extraction records type refs, leaving out a supertype's or annotation's head that INHERITS or DECORATES already carries. Call type arguments such as Box<Argument>() now emit. A C# named argument lands on the parameter it names, and a name the declaration lacks rules it out, so overloads such as new Reader(byte[], isFinal: true, state) settle. The default literal fits any parameter. A one-line Kotlin object reads as a class through a same-length parse view.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 20:53:10 +01:00
Martin Vogel 448ffa0e93 fix(resolver): free the Python written and parent-loaded tables
cbm_shared_resolver_build creates both and cbm_shared_resolver_free never released them, so every index leaked two hash tables. LeakSanitizer reported it on Linux across 77 suites; macOS does not run it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 19:42:42 +01:00
Martin Vogel 7c9b3a8170 fix(resolver): findings from linting the integrated tree
rust_macros kept pointers to its locals in the caller's scan after returning; it now runs on a copy and clears them. path_alias.c allocates through the memory core, 40 raw sites down to 1, with cbm_path_alias_resolve alone staying raw because its callers free its result. Four copies of one import-map cleanup become one helper, 63 raw sites down to 53 in pass_pkgmap.c. The rest reshapes code cppcheck could not follow, without changing behaviour.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 19:01:26 +01:00
Martin Vogel 2e7f37c844 feat(resolver): recover C and C++ edges macros, unnamed aggregates and templates had hidden
CALLS: redis 28,295 to 32,377, neovim 29,542 to 31,306, whole llvm-project 432,362 to 644,451; USAGE on redis 42,133 to 51,931. Macro replacements are read at the use when every visible definition agrees. Local arrays bind a type, unnamed aggregates get members, ns::Name binds outside its namespace, and out-of-line template members and member templates get nodes.

New graph content: Class nodes named Owner$member. QN changes: out-of-line template members and ns::Foo::bar are Methods. A write through a macro member path records USAGE, not WRITES. False functions such as one spanning 1,445 lines of jemalloc's ctl.c are gone.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 18:09:45 +01:00
Martin Vogel 0c4a757972 feat(resolver): recover Java, Kotlin and C# edges reference assemblies and lost spellings had voided
dotnet/runtime CALLS go from 392,440 to 773,575 and USAGE from 709,951 to 1,688,346; elasticsearch CALLS from 1,308,131 to 1,342,695 and Exposed from 20,357 to 23,603. The same type in ref/ and src/ now means the implementation stands for the contract. A reader means its own file's declaration or the only one in its build project, and .projitems counts as a project. A same-length parse view reads spellings the grammar loses: Vector128.cs goes from 0 to 172 methods. Lambdas take their parameters from the callee's single abstract method.

A CALLS edge can now start from a Field node, and calls in C# conversion operators, indexers and property bodies belong to those members. Five wrong edge kinds found while sampling are fixed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 18:09:22 +01:00
Martin Vogel c1eb542277 feat(resolver): model references in Ruby, Elixir, OCaml, Scala, Zig, Perl and Haskell
USAGE goes from none to thousands of SCIP-confirmed pairs on Ruby (rubocop 3,322, vagrant 4,488) and to 890 sites on Elixir's plug. dune CALLS sites rise from 6,891 to 43,659: .mli files are read inside sig, includes reach paths below them, and functor parameters are bindings. Elixir's use reads __using__, Haskell's T(..) matches, Zig reads a field's type with its own file's visibility, Scala class-body vals carry their class in the QN.

A Ruby block keeps self only under a core iterator or a project method whose declaration does not rebind it, followed through a forwarded &block, so blocks under unknown gem methods abstain. Several OCaml and Scala false edges from the earlier tree are gone. CBM_INDEX_FORMAT_VERSION moves to 5 because OCaml, Zig and Scala QNs changed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 18:09:12 +01:00
Martin Vogel c3dbdf21f8 feat(resolver): recover Go and Rust edges one manifest or macro had voided
CALLS recall against SCIP: cli/cli 35.4% to 99.9%, cargo 20.4% to 98.6%, tokio 26.6% to 91.8%, meilisearch 97.1% to 98.4%, prometheus 97.1% to 97.8%, helm 98.2% to 98.6%. A test go.mod declaring the repository's own module path no longer voids every import of it, and replace directives decide. Grammar errors from Go new(expr) and Rust str![[...]] no longer void the file: cargo had lost 477 of 1,374 files. Rust macro_rules definitions are read per package, and calls in nested items belong to the enclosing function.

A Go conversion T(x) is USES_TYPE, not CALLS, though SCIP counts it as a call pair.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 18:08:27 +01:00
Martin Vogel 2237635469 feat(resolver): recover TypeScript and JavaScript edges gates and workspaces had voided
CALLS recall against SCIP: vue 62.9% to 98.0%, vite 62.2% to 97.4%, nest 90.9% to 96.6%, precision 100% and no site contradicting SCIP. An identifier merely named Function or eval no longer voids a file. Workspace packages, tsconfig paths, baseUrl and exports conditions resolve from their manifests. Receivers come from what the file spells. Five false-edge classes found by decoys are closed, one of which predates this round: this in an object literal read as the enclosing class.

A package.json, pnpm-workspace.yaml, tsconfig or extends base edit now declines incremental repair.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 18:08:06 +01:00
Martin Vogel 8a0afb5fed feat(resolver): recover Python edges a project's layout and ordinary writes had voided
CALLS recall against SCIP on six repositories rises from 80.7% to 95.5%, precision holding at 99.5%. A manifest now states source roots, so a src layout reaches its own package. A write blocks only the name it spells, not a whole package. Decorators keep the declaration unless the source proves the function dropped. Three false edges the baseline also had are fixed: a conditional star's first arm, t.x = make() reading the declared x, and a descriptor read as the instance.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 18:07:57 +01:00
Martin Vogel d614236668 test(resolver): forge a C include list, and split the Python suite for the Windows cap
C writes no binding list to alter now, so the authority test forges one: a property naming a wrong target must still carry no authority. The Python suite ran about 500 full indexes through both drivers and passed the 900 s per-suite cap on Windows, so it is three suites, and rows of one fixture share its index. No assertion changes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 18:07:01 +01:00
Martin Vogel d26e646309 fix(pipeline): a C file's node keeps no list of its includes
Nothing reads it, and a generated header with tens of thousands of includes made one File node 9 MB and its database 13 times the baseline's. Under strict_string_checks that string also made the FTS backfill's LIKE quadratic, so the diagnostic CI lane timed out at 900 s. The test now pins the node's size.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 07:46:27 +01:00
Martin Vogel f6556b7b05 test: fixture lifetimes the Linux and Windows legs exposed
js_fixture returned its arena by value while the index kept a pointer to it, which only worked where the copy was elided. The parallel harness never freed the Rust project and ECMAScript surfaces. Templates sized exactly to their text overflow on Windows, where cbm_mkdtemp rewrites /tmp/ to the longer temp root in place.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 01:13:25 +01:00
Martin Vogel e821b3e084 fix(cbm): initialize the unrepaired root by assignment, for GCC's missing-braces check
GCC treats the conditional compound literal inside the context's designated initializer as missing braces, and the Linux build fails on it. A zeroed node is already null, so only a repaired parse assigns one.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 00:47:47 +01:00
Martin Vogel 4a514ea909 fix(windows): a local named near collides with the Windows macro
Windows headers define near and far as empty macros, so the production build failed there.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-02 00:09:51 +01:00
Martin Vogel 415d9af266 fix(resolver): what outlives a spilled result is copied out of it
Over the memory budget each result is parked on disk and read back only for a moment. The Java, Kotlin and C# declaration index kept qualified names from the freed result, and a resolve worker read them. TypeScript's merged label did the same. Both are found by ASan with CBM_MEM_SPILL=1; every resolver suite now runs clean that way.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 23:44:39 +01:00
Martin Vogel 91880fbcbf style(cbm): one helper for the C# view's directive test
The same three comparisons read as one helper, and cppcheck no longer infers a constant from the indent loop. Behaviour is unchanged.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 22:59:48 +01:00
Martin Vogel 3fcfd5f9bf fix(resolver): the HTTP default audit reads a flat file's top level in one pass
Fetching a child by index walks from the first child, so the audit was quadratic in the statement count, and it runs serially for every ECMAScript file. TypeScript's baselines held indexing that repository for over 28 minutes; it now takes 182 s. The C# reference-assembly fixture binds neither type now that the parse view keeps both namespaces.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 22:30:17 +01:00
Martin Vogel 3b59e3934b Merge main df2b0685 into the strict shared resolver
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 21:33:49 +01:00
Martin Vogel 8aabc9e943 fix(kotlin): a callable reference inside a larger value stays a usage
Only a direct argument, parenthesized value, standalone reference or initializer hands over one callable. Branches and pairs do not, which is what this test required before it was flipped and what a Java ternary does. A Go result annotation is USES_TYPE alone, like TypeScript, so its USAGE row goes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 21:31:50 +01:00
Martin Vogel ee9fc6a0e6 Merge lane/t3: conditional variants, doc mention scopes, parallel harness runs static selection
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 21:30:26 +01:00
Martin Vogel 9fb5651ca3 Merge lane/t2: matrix reds, C# operators get their own node
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 21:30:26 +01:00
Martin Vogel a64ae09b43 Merge lane/t1: probe reds, parallel JVM walk on call-free files, Go element writes and annotation tokens
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 21:30:26 +01:00
Martin Vogel 592d4f83e2 test(parallel): the Java and Kotlin rows the declaration index binds are required
With the harness building the index production builds, the reference argument,
the same-package access and the constructor call all bind, and each is what the
rules select: Java and Kotlin share com.example, and no constructor is declared,
so the class stands for the implicit one, as test_resolver_oo.c pins.

The Kotlin alias gap is named after its real cause. The file parses completely, so
the parse-gap name and text were stale. The alias call stays unbound once the file
also declares a class with a member.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:36:08 +01:00
Martin Vogel 0fc6bdcb84 fix(doc-links): the C# scope reads what the parse view hid
The parse view keeps one branch of each #if, which is enough for the graph's nodes.
The scope read that same tree, so a namespace or a type another branch names
differently looked placed and complete. A reference valid in the other build was
then reported as doc rot, where it used to be a graph gap.

When the view hid a branch, keep the file's own parse for the scope. A view that
only blanked `ref` hides nothing and stays in use. RED:
doc_mentions_cs_scope_parse_errors, doc_mentions_resolver_parse_errors.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:36:07 +01:00
Martin Vogel 0774ecde2a fix(doc-links): an unnamed Kotlin companion reads its own definition
An unnamed companion now has a node, but the scope looked a definition up only by
a name token, which it does not have. [Type.Companion] stayed a graph gap with the
node right there. RED: doc_mentions_jvm_kotlin_scope_blob,
doc_mentions_jvm_kotlin_companion_inherits.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:36:07 +01:00
Martin Vogel 679a03fbfe test(doc-links): Kotlin pins follow the emitted names; C# fixtures keep an unreadable type
Kotlin nested types are named under their outer class and an unnamed companion
has nodes, so the pins move to those names, each confirmed against a real index.
One of them, a zero count on gk.K4.Wrapped.m, passed vacuously under a stale
suffix.

A nested class and a member function of one name now share a qualified name, and
so do a member function and a property. The later declaration owns the node, so
[Holder.Part] and a doc on the earlier twin are graph gaps. No edge goes to the
wrong twin. Both are pinned as known gaps.

The C# parse view reads `ref partial struct`, so those fixtures write it with a
comment between the words, which the grammar still cannot read and the view does
not repair. The plain spelling gets its own positive.

RED until the two doc-link fixes that follow: the slot of an unnamed companion, and
the C# scope of a file whose parse view hid a branch of a conditional.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:36:07 +01:00
Martin Vogel 9a5f62ed91 test(resolver): Kotlin USAGE rows name only the result annotation
Kotlin records no USES_TYPE occurrence for a result type, so USAGE names the class there. A
parameter annotation emits neither, so ad60078d4's parameter rows required an edge production does
not make. Dropped those two rows.

Also corrects ad60078d4's message: the PHP reason string was renamed in b821d1506, not aeb5971dc.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:34:25 +01:00
Martin Vogel 852cb8859f test(resolver): attest class mentions and cover C# operator scopes
A matrix row on a class target brings that class's other roles into the harness
audit. `make(): Widget` and `anon(): User` name the class they also construct, so
the signature's USAGE, and USES_TYPE in Scala and Swift, become required rows at
that exact token.

mkc_c2_csharp_operator_overloads pins the operator scope for overloads stored as
variants, for a spaced `operator -` and for the word form `operator true`.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:24:05 +01:00
Martin Vogel ad60078d4b test(resolver): promote recovered gaps and correct stale fixtures in the t1 suites
Each row was checked against its fixture source.

Promoted to required, the target is what the language selects: same-class C# await call, Kotlin
suspend call, Kotlin sealed `is` branches with their forbids kept, Java enum methods, the static
interface target with its forbid kept, Kotlin operator plus, the imported Celery instance's
send_task with the foreign decoy kept, RestTemplate literal routes.

Corrected:
- writes_go named a `cache.` package part that a Go root file does not have, so neither the row
  nor its decoy forbid could match anything. Both now bind.
- Python and Kotlin constructor rows name the class node. A Kotlin primary constructor has no node
  of its own and Python's class call selects the class, as test_pipeline requires.
- FunC and Meson import probes required 0/0 spans. The carrier now records the literal's exact
  occurrence, and the probes require that occurrence.
- es_php_include_occurrence_spans_and_sealing_guards pinned a reason string renamed in aeb5971dc.

Reads are USAGE: Go, Java and C# rows list it as required. Their READS rows stay named gaps because
nothing emits READS. Java's count++ stays a gap: its assignment kinds omit update_expression.
Celery's ASYNC_CALLS rows stay gaps with an updated reason: CALLS is proved, the service
classifier does not see that proof.

Adds es_go_annotation_types_are_not_value_usage and es_go_index_assignment_reads_operand_and_index
for the Go role rules. No forbid was removed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:22:55 +01:00
Martin Vogel 1a58bf2745 fix(extract): Go element stores read their operand; annotations are USES_TYPE alone
`cache[key] = v` stores an element through the operand's map or slice and rebinds nothing, but it
published WRITES for cache and nothing at all for key. Both are reads now, so they are USAGE. An
array variable is written in place by this form as well; its element type is not known here, so
it reads as USAGE too, which is a missing write and not a false one.

A Go parameter, result, var or conversion type also published USAGE on the same token that
type_refs already carries as USES_TYPE, which es_usage_crossfile_go forbids and the TypeScript
adapter already refuses. One token now has one role. A composite literal builds a value of its
type, a receiver has no USES_TYPE occurrence, and USES_TYPE reads a qualified type's whole
spelling rather than its member token, so those three stay USAGE.

RED: cp_reads_writes_go_global (forbid Set WRITES cache) and es_usage_crossfile_go (forbid
annotation-is-not-value-usage).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:22:54 +01:00
Martin Vogel 8100a22feb fix(pipeline): run the Java, Kotlin and C# strict walk on a file with no calls
The parallel route decides per file whether cross-file analysis runs, and a file with no call
left to resolve was skipped. For Java, Kotlin and C# that also skipped the strict walk, which is
the only source of their field and type rows, so a class that only reads and writes its own
fields lost every USAGE, WRITES and type row in the parallel route. The sequential route has no
such filter, so the two routes disagreed.

C and C++ already had this exception. Extend it to the strict-walk languages.

RED: writes_java, writes_csharp, cp_reads_writes_java_static_field and
cp_reads_writes_cs_static_field found the write in the sequential route and lost it in the
parallel one.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:22:37 +01:00
Martin Vogel 2616bd96e0 test(resolver): promote conditional, C#, Kotlin, PHP and Python rows that now resolve
Each row was checked against the fixture source and the language's own rules.

Conditional variants (Go build tags, Swift, Erlang, Elixir, Haskell): one node
holds every branch's span, so a body call is that node's call under its authored
variant, and a caller reaches that one node in every configuration. Targets
anchor on the node's stored span. Haskell later branches stay a known gap: the
source adapter walks the raw parse, which holds only the first branch of a #if.

C# same-name classes: all seven member rows promote, the constructor call under
`using Contoso.Threading` gets its own required row, and the edge checks now pin
the bound target with the decoy still refused. `new Thing()` on a partial type is
a known gap: no one node is that type.

The parallel harness now builds the declaration index production builds, so Java,
Kotlin and C# static selection runs on that route too.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:19:04 +01:00
Martin Vogel 2840080f69 fix(extract): a C# operator declaration is a method with its own node
operator_declaration was no function kind, so `operator+` had no node and every
call and usage in its body was attributed to the file. The resolver then published
`Vec CALLS Vec2.Vec2` from a body that belongs to a named member.

The operator is now a Method named by its token, spelled like C++'s `operator+`,
through the one resolver the def side and the call scope share. Overloads merge
into one node with variants, as other C# methods do.

RED: mkc_c2_csharp_operator_plus reported scope=Vec for the constructor call. The
binary operator site `a + b` still abstains, and conversion operators, property
accessors and indexers still have no node of their own.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:17:29 +01:00
Martin Vogel d6335b5239 test(resolver): correct stale constructor, field-initializer and Go literal rows
Rows written before the language rules existed. Each was re-derived from its
fixture source:

- Java, Kotlin, C#, Scala, Swift: a constructor call with no constructor node
  binds the class, marked as a class call. Rows expecting a Method or nothing are
  now required rows on the class declaration.
- Field initializers run in no callable node. The file-level node is their scope,
  so the LRP oracle accepts a 0/0 range for a row naming that node, as rq_target
  already does.
- Go struct literal: the type name is both USAGE and USES_TYPE at both of its
  sites, and a literal key names a field.
- Recovered receiver and fluent rows in those scenarios become required.
- probe_kotlin_data_class and probe_cs_class_methods pinned both rows missing,
  though both are required and found.

mkc_c2_csharp_operator_plus and the new mkc_c2_csharp_operator_overloads stay red
here: a call inside a C# operator is attributed to the file. The next commit fixes
that in production.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 20:05:54 +01:00
Martin Vogel af4f10c785 test(resolver): promote recovered gaps and correct fixtures after integration
Rows the integrated resolver now proves become required. Fixes: a Python class statement reads its bases where it stands, so a later sibling is no base; a Kotlin callable reference in an if branch stays a usage; cppcheck findings on the integrated tree, including the OO walkers keeping a stack address in the walk. Swift package entries now count the two membership facts.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 19:47:08 +01:00
Martin Vogel 609e13df8b fix(resolver): integration reds after merging the language lanes
A configured define's body keeps its meaning in the expanded parse, so -DAPI=Junk does not read class Junk Foo as class Foo. An ambiguous TypeScript proof is two distinct functions extraction named flat, not one merged identity. The _Thread_local pointer form now parses, so its pin says so. The module index and filtered defs allocate through the memory core. Format production files the merges left unformatted.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 19:27:33 +01:00
Martin Vogel d77454026e Merge lane/php-generic: PHP and source adapters for ten languages without one
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 19:07:44 +01:00
Martin Vogel cdc56c5f45 Merge lane/c-cpp: translation-unit visibility, include resolution and C++ members
C-family takes the attribute-macro parse, everything else the C# repairing parse. The Rust project model already replaced the walker in this lane's pkgmap hunk, so only build_include_path_context survives. c_variants joins the identity rule; the read-as-value-reference lookup is guarded to C-family because other lanes were measured without it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 19:01:42 +01:00
Martin Vogel 4b1cccc8fa Merge lane/rust: Rust project model, name resolution, method selection and flow
The project model replaces the old import-context walker, keeping the JVM lane's scoping of a context failure to Rust and C-family. Rust's identity rule runs before the merged-variant rule for Rust targets. Call, usage, write and type dispatch each gain a Rust arm beside Go's.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 18:59:32 +01:00
Martin Vogel cf2f5c3bb6 Merge lane/python: true Python scopes, members, decorators and search paths
Python and ECMAScript each own separate paths through the binding index, so conflicts are unions. BINDING_DECORATED moves to the next free bit; Python lookups pass JS_SPACE_ANY; too_early keeps the ECMAScript closure rule, and the Python-guarded deferred site may still excuse it. Commit 456bdbb30's message says about 16 wrong edges; the measured figure is 11.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 18:56:49 +01:00
Martin Vogel 112c8b0de7 test(probe): six C calls bind the definition a local prototype declares
`Ops make_ops(void);` in main.c with the definition in ops.c. The goldens named the prototype, which
has no graph node, and recorded a gap, so the correct edge counted as a false positive. A prototype
declares the external name, and the one definition of that name is the target.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 18:54:31 +01:00
Martin Vogel 8594f8c518 Merge lane/core-go: remove work budgets and identity conflicts, true Go static rules
Union with the other lanes. Merged same-kind declarations are one graph identity in core-go's rule; JVM overloads and TypeScript merged value/type stay on their own paths, and the JavaScript span rule still applies only to a source in this file. Evidence writing keeps the perf-scale writer, proven edge-identical. The lane's commit d38554e3d is labelled WIP but its code is final; the label could not be amended.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 18:53:05 +01:00
Martin Vogel 424f5ccb95 Merge lane/js-ts-2: JavaScript and TypeScript modules, exports, scopes and receivers
Conflicts are unions: the JVM lane's overload_set covers Java, Kotlin and C#, and the merged-target and Class-callable rules here cover TypeScript, JavaScript, Vue and Svelte.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 18:52:06 +01:00
Martin Vogel d8d3c11951 test(pipeline): promote C and C++ rows the translation-unit rules now bind
Each of these pinned an abstention as a known gap, and the 18:41 run reported the exact expected
target as found. 20 field rows in c_member_fields_quality, cpp.enum.green and the four C++ static
factory rows become required.

Nine rows are new: parameter types and reads the rules now bind, read against the fixture source
one by one. `int use_beta(Beta b)` uses Beta. `a->count` in a function inside a preprocessor branch
reads Alpha.count. `g_alpha.count` on a static of written type reads Alpha.count.

In the conditional variant tests the calls in both branches and the call to `pick` now bind. A
caller's target is the one node both branches share, whose stored span is the last branch, so the
golden names that declaration.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 18:51:52 +01:00
Martin Vogel df2b068532 Merge pull request #2507 from DeusData/dependabot/github_actions/actions/attest-4.2.2
build(deps): bump actions/attest from 4.1.0 to 4.2.2
2026-11-01 18:48:55 +01:00
Martin Vogel 71dac3c1b2 fix(c,cpp): keep the recursive walk's frame small, configured defines keep their meaning
Two failures from the 18:41 test run.

clsp_issue1527_long_binary_chain_is_not_nesting overflowed the stack under ASan. The new member
and value rules added locals to c_resolve_calls_in_node_inner, which is in every frame of a walk up
to C_LSP_MAX_WALK_DEPTH deep. What one node does now sits in a separate function kept out of line,
and it returns before the walk goes into the children. No logic changed: redis, neovim and the LLVM
subset give the same edge counts before and after.

extract_cpp_export_macro_explicit_define_priority_issue1989 pins that a define the caller provided
wins. A word the caller defined is no longer left out of the raw parse.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 18:48:43 +01:00
Martin Vogel 599375a354 Merge lane/perf-scale: no digest or id slot for a composed fact
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 18:46:41 +01:00