Commit Graph
20 Commits
Author SHA1 Message Date
Martin Vogel d97c0ce55b fix: exclude client navigation from inferred HTTP calls
Refs #1250

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-13 00:33:07 +02:00
Martin Vogel 7ac6396efa Merge main into distill/1245-websocket-route-suffixes for the release batch
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-07 01:42:26 +02:00
Martin Vogel 2803d6a5fe Merge pull request #2255 from DeusData/distill/1245-route-literal-exts
fix(routes): a document or log path is not an HTTP route literal
2026-10-04 21:01:09 +02:00
Martin VogelandAndrew Hundt c5a3c6eae9 fix(routes): register FastAPI and Starlette WebSocket decorators
Distilled from #1245 by Andrew Hundt (a slice of 1ac3c136).

The route-registration suffix table knew .add_api_websocket_route but
not the decorator forms FastAPI and Starlette actually use:

    @router.websocket("/ws")
    @app.websocket_route("/events")

so a WebSocket endpoint produced no Route node on either resolver path
and was invisible to the graph. Two entries join the table, both
recorded as ANY like the existing WebSocket entry -- a socket upgrade is
not an HTTP verb. Both are needed: matching is a pure suffix compare and
.route does not match ...websocket_route, whose tail is _route.

Upstream also changed pass_calls.c, pass_parallel.c and
pipeline_internal.h. None of that is taken: main already carries the #952
sequential unresolved-callee route fallback its July base lacked, so the
table entries are the only missing piece.

RED before the fix, each form recorded on its own (the harness stops at
the first failing assert, so the two were swapped and re-run):
  pipeline_python_websocket_decorators_register_routes
      FAIL tests/test_pipeline.c:6163:
      count_nodes_named(s, project, "/ws") (0) not >= 1 (1)
      FAIL tests/test_pipeline.c:6163:
      count_nodes_named(s, project, "/events") (0) not >= 1 (1)
The @app.get("/health") control above those lines passed before and
after.

GREEN after: pipeline 282 passed; pipeline parallel extraction
edge_types_probe route_canon infrascan 779 passed, 0 failed.

The test drives the sequential resolver (one file). The parallel
resolver reads the same table through cbm_service_pattern_route_method
and its suite is green; a CBM_WORKERS=4 twin of this test is a small
follow-up.

Co-authored-by: Andrew Hundt <ATHundt@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-04 18:26:40 +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 VogelandAndrew Hundt 38d422f030 fix(routes): a document or log path is not an HTTP route literal
Distilled from #1245 by Andrew Hundt (a slice of 0370d52f).

detect_url_in_args in the parallel resolver runs for every resolved call
with no callee gating. A first argument like "/new/file.txt" clears
normalize_url_arg (it has a second slash and no whitespace) and then
reaches cbm_service_pattern_is_http_route_literal, whose filesystem
extension list knew about config, key and socket files but not about
documents or logs. So

    open_file("/new/file.txt")
    read_file("/docs/guide.md")
    write_file("/data/app.log")

each minted a Route node and an HTTP_CALLS edge -- the graph reported a
web endpoint where the program opens a file. Five extensions join the
list: .log .md .pdf .rst .txt, alphabetical order preserved.

Two things the tests are careful about, because the obvious fixture
would not have been RED. A bare Python builtin open(...) never resolves
to an indexed target, so it never reaches the URL detector at all; the
bug shows only for calls that resolve to a project symbol, which is what
the fixture defines. And /var/... is already rejected by the filesystem
ROOT list, so the fixture paths start with segments that list does not
know, leaving the extension guard as the only thing that can reject them.

RED before the fix:
  infrascan_http_route_literal_guard_rejects_document_extensions
      FAIL tests/test_infrascan.c:44:
      ASSERT(!(cbm_service_pattern_is_http_route_literal("/new/file.txt", "open")))
  pipeline_arg_url_rejects_document_file_paths
      FAIL tests/test_pipeline.c:6652:
      count_nodes_named(s, project, "/new/file.txt") == 1, expected 0 == 0

The pipeline test keeps a positive control above that line -- a genuine
requests.get("/api/items") still yields its Route node and HTTP_CALLS
edge -- so the guard cannot pass by rejecting everything.

GREEN after: infrascan 4 passed; pipeline 282 passed; extraction
edge_types_probe route_canon 420 passed.

Co-authored-by: Andrew Hundt <ATHundt@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-01 23:45:24 +02:00
Martin Vogel d3059efebd fix(pipeline): classify capitalized Axios calls as HTTP clients
With `import Axios from "axios"`, a common convention in TypeScript
projects, every `Axios.get/post/put/delete(url)` client call was indexed as
a route registration. The result was a `CALLS {via: route_registration}`
edge to a Route node instead of an HTTP_CALLS edge. The client's request
paths therefore posed as server routes, and no HTTP call site existed to
match a handler. microsoft/Game-Control-Puzzle-Event-Administration-Tools
has all 69 of its client calls in this shape.

Root cause: service-pattern matching is case-sensitive by design, so that
"requests" cannot match "Requests" or "myrequests". The HTTP library
table knew only the lowercase id `axios`. `Axios.get` therefore matched no
library, on either resolver. Resolution came back empty, because an
external package is never indexed. The call then fell through to the
callee-suffix fallback, which treats any `<x>.get/post/...(path)` as a route
registration.

Fix: add `Axios` next to `axios` in the HTTP library table. The table
already pairs case variants of one library (`Dio`/`dio`,
`Guzzle`/`guzzle`, `OkHttp`/`okhttp3`). The existing identifier-boundary
rule applies unchanged:
- `Axios.get` and `AxiosInstance.get` (axios's own instance type) match;
- a lowercase continuation such as `Axiosphere` does not.
Express/Koa/Fastify registrations such as `router.get(path, handler)` are
untouched.

Proof (release binary, macOS arm64,
microsoft/Game-Control-Puzzle-Event-Administration-Tools @4fe23196,
before -> after):
- route_registration CALLS 69 -> 0; all 69 were `Axios.*` calls in
  client/.
- All 69 reappear as typed HTTP_CALLS from the same caller to the same
  Route QN (`__route__PUT__/api/admin/users/{}`), and no typed HTTP_CALLS
  edge is lost.
- HTTP_CALLS 4 -> 142. Besides the 69 typed calls, 69 are the method-less
  arg_url twins that fix/http-typed-call-no-url-twin removes, and 4 are the
  existing `isOnPage` arg_url edges.
- CALLS 868 -> 799.
- SEMANTICALLY_RELATED 83 -> 174, deterministic across two runs of each
  binary. The semantic pass tokenizes CALLS-neighbour names, and the fake
  registration edges had fed each service function's URL path into its
  vector. Without them, the 69 `Axios` wrapper functions read as related.
Five sampled calls were checked against the client source, including
admin/users/service.ts:44 `Axios.put(`/api/admin/users/${participantId}/
${eventInstanceId}`)` -> `__route__PUT__/api/admin/users/{}/{}`.

On the pending ASP.NET route stack (fix/aspnet-route-case-inrepo, 8e734ee5),
together with the arg_url twin fix, the reclassified calls reach their C#
handlers:
- typed HTTP_CALLS 0 -> 69;
- DATA_FLOWS 0 -> 66, and HANDLES 94 -> 106 (+12 case_fold);
- sampled links match their controller source, for example
  `Axios.put(`/api/admin/users/${participantId}`)` ->
  `[Route("api/admin/users")] [HttpPut("{participantId}")] UpdateUserInfo`;
- the 3 calls left unlinked carry a query string in the URL literal.

Tests: RED 3/3 before the fix, green after, RED again on revert.
- pipeline_capitalized_axios_import_is_http_client (parallel resolver) and
  its _sequential twin: `import Axios from "axios"` with Axios.get, .post
  and .delete gives 0 route registrations and exactly one typed
  HTTP_CALLS per call, while an Express `router.get` registration in the
  same project stays a registration.
- infrascan service-pattern tables: `Axios.get`, `Axios.post` and
  `AxiosInstance.get` classify as HTTP, and `geo.Axiosphere.map` stays
  NONE.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-28 19:33:28 +02:00
Martin Vogel 92cb0ec211 fix(pipeline): key GraphQL Routes by operation name, not operation text (#598)
When a call resolved to a GraphQL client, emit_graphql_edge named the
Route after the whole first string argument, stripping only a leading
"query " or "mutation " (the mutation strip was also one byte short, so
names kept a leading space). An anonymous `query { ... }` therefore became
one long Route, and even a named operation carried its variables and
selection set in the name ("GetUser($id: ID!) { user(id: $id) { name } }").
Every distinct operation text minted its own Route node, and names were
cut off at 256 bytes.

Add cbm_service_pattern_graphql_operation(), a small GraphQL document
scanner. It skips comments, strings, block strings, `${...}` template
interpolations and leading fragment definitions, and reports the first
operation's type and name without copying any operation text. Routes are
now keyed as follows:

  named operation              __graphql__<Name>                name <Name>
  anonymous query / mutation / __graphql__<type>__anonymous     name (anonymous <type>)
  subscription, incl. the `{ ... }` query shorthand
  no GraphQL document          __graphql__operation__anonymous  name (anonymous operation)
  (URL, bare word, no string argument)

Keys are deterministic and bounded by the GraphQL Name grammar. The
GRAPHQL_CALLS edge keeps "operation" (now the Route name) and gains
"operation_type". The old fallback to the callee name ("client.query")
is gone: a callee is not an operation.

The sequential path (pass_calls.c) emits no GRAPHQL_CALLS at all, so it
had no copy of this logic to fix.

Proof on graphql-python/gql (374a70df, Python GraphQL client, index-only):
GraphQL Routes 170 -> 16, longest QN 266 -> 34 bytes, 106 -> 0 Routes
carrying operation text. GRAPHQL_CALLS 2643 -> 1158 with the same 984
distinct callers: edges are one per (caller, operation) pair, so calls
that used to hit distinct text-keyed Routes now share their operation's
Route.

Tests (edge_types_probe): graphql_route_keyed_by_operation_name_issue598
asserts the exact GraphQL Route set (QN + name) on the parallel path.
graphql_operation_identity_parser_issue598 covers braces inside comments,
strings and interpolations, fragments before the operation, anonymous
operations with variables, name truncation and non-document arguments.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-26 10:21:30 +02:00
Martin VogelandAndrew Hundt c314dea4c1 fix(service-patterns): match library ids on identifier boundaries
Distilled from #1245 by Andrew Hundt (d15071c9, the match_qn half).

match_qn was a raw strstr(qn, library_id). Short ids fired inside
unrelated words, and a service kind REPLACES the plain CALLS edge, so a
false match both invents a service edge and deletes a real call:

  "gin."    in plugin.           -> route registration; any "/"-prefixed
                                    first argument then minted a Route node
  "nconf"   in encryptionconfig  -> CONFIGURES for every call into the
                                    package
  "tonic"   in Monotonic         -> gRPC; unparseable, so NO edge at all
  "express" in expression        -> route registration
  "get_env" in get_envelope, get_environ -> CONFIGURES
  "dio" "surf" "curl" "resty" "phin" "hyper" "treq" "rocket" in studio,
  surface, curly, restyle, dolphin, hyperbolic, streq, rocketmq

The rule (qn_hit_on_boundary; every occurrence of an id is tried, not
only the first). A separator is any character that is not an ASCII
letter or digit.

  before: start of string | separator | the id itself starts with a
          separator ("@trpc/server") | the id starts with an uppercase
          letter -- a capital opens a new CamelCase word whatever
          precedes it (AsyncHttpClient, IHttpClientFactory, NSURLSession)
  after:  end of string | separator | the id itself ends in a separator
          ("gin.") | uppercase letter (GuzzleHttp, FeignClient,
          KafkaProducer) | digit (urllib2, Mint.HTTP2, amqp091-go)

Rejected: a lowercase- or digit-initial id glued to a preceding letter
or digit, and any id continued by a lowercase letter. Two deliberate
differences from the upstream hunk: upstream's before-rule needed a
GuzzleHttp table entry and would have dropped IHttpClientFactory and
NSURLSession, and without the digit after-boundary urllib2 and
amqp091-go stop matching. All three match on main today.

No recall is traded for this. No boundary rule can tell "grequests"
from "myrequests", so a library whose own name glues a prefix or suffix
onto another id gets an explicit entry with the kind (and broker) it had
on main -- 47 of them, found mechanically by running main's matcher and
the new one over 173 candidate names and listing every one that went
from a kind to NONE:

  HTTP      grequests txrequests redaxios gaxios libcurl curlpp curlcpp
            hyperlocal guzzlehttp
  ASYNC     aiokafka pykafka rskafka librdkafka rdkafkacpp aioamqp
            amqpstorm pamqp pyamqp amqprs amqpcpp pynats jnats aiomqtt
            amqtt hbmqtt umqtt mqttools emqtt rumqtt gomqtt libmosquitto
            mosquittopp pubsublite gocelery
  CONFIG    wgetenv getenvb qgetenv dotenvx phpdotenv
  ROUTE_REG apiflask honox fasthttprouter
  GRAPHQL   gqlparser gqlgenc aiogqlc

Left at NONE on purpose: names that are not clients of the id they
contain and were misclassified on main -- hypercorn, hyperlink,
hyperopt, openresty, gqlalchemy, celeryconfig, kafkacat, gnatsd,
pypubsub. One visible reclassification: hyperium/tonic was HTTP through
"hyper" inside "hyperium" and is now gRPC through "tonic".

RED, production reverted and the final tests kept:
  infrascan_service_pattern_match_rejects_ids_inside_words
      FAIL tests/test_infrascan.c:105:
      svc_case_mismatches(cases) == 28, expected 0 == 0
  4 passed, 1 failed
All 28 negatives mismatch on main. The 47 glued-library positives are
RED the other way round: against the boundary rule without the entries.
Each positive uses a name containing no second id, so it binds to its
own entry -- "aiokafka.AIOKafkaProducer" would have passed through
"KafkaProducer" and proven nothing.

GREEN: infrascan 5, pipeline 281, parallel 74, extraction 350,
edge_types_probe 59, route_canon 11, lang_contract 41, cross_repo 8,
registry 64, mcp 318 (4 Windows-only skips). 0 failed.

Effect on real code -- production binaries, main 92abefa3 against this
change. main against main differs by 0 edges and 0 nodes on all three
corpora, so every delta below is the change:

  django      CONFIGURES 76 -> 74; both calls return as CALLS
              (geom.get_envelope, WSGIRequestHandler.get_environ)
  typescript  CONFIGURES 12,026 -> 12,024; one bogus Route "/*type*/"
              gone with its CALLS and HANDLES; 3 calls return as CALLS
  kubernetes  CONFIGURES 7,962 -> 7,609: all 353 are "nconf" inside
              encryptionconfig / authorizationconfig /
              authenticationconfig, and all 353 return as CALLS.
              8 bogus Routes gone -- os.ReadFile of the serviceaccount
              namespace file, t.Run("/TwoWay"), "/tmp/test" -- with 26
              CALLS, 2 HANDLES and 21 TESTS edges that pointed at them.
              374 plain CALLS restored; 8 of them (validate.Monotonic and
              friends) had NO edge at all on main. GRPC_CALLS 60 -> 60.
              HTTP_CALLS 398 -> 407: nine calls main swallowed as route
              registrations now reach the existing arg_url heuristic like
              every other package.

88 of the 408 removed service-derived edges were read at the call site
-- all of django and typescript, 82 of 402 in kubernetes: 88 false
positives, 0 true positives lost. None of the three corpora uses a
glued-name library, so the 47 entries are covered by the unit test only.

Known limits: "_" is a separator, so optical_fiber.len still matches
"fiber." exactly as requests_get matches "requests" by design; a
lowercase id followed by a capital matches (kafkaProducer), which also
admits a dioXide-style name; a user wrapper glued without a separator
(mygetenv) is no longer classified; a glued-name library missing from
the list is NONE until someone adds it.

Co-authored-by: Andrew Hundt <ATHundt@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-22 00:48:43 +02:00
Martin Vogel 1a3425536b fix(graph): quality round after the v0.11.0 probe — deterministic resolution, findability, coverage, names
Nine findings from the 2026-09-16 graph-quality probe over the eleven bench corpora, each with a
test that fails without its fix and a before/after measurement on the real corpus.

Deterministic resolution (O9). Two indexes of one kernel tree differed in 5,821 IMPORTS and 632
CALLS edges. Three order/load dependencies carried it: the `#include` target was the first by-name
hit whose path ends with the include path — a bucket in worker merge order (include/linux/device.h
vs tools/virtio/linux/device.h); the registry broke suffix_match score ties by bucket position; the
LSP budget-share rule measured wall time. Include targets now prefer the File node, the least nested
path, then the lexicographic path; registry ties prefer the least nested QN, then the smaller QN; the
budget share is measured in thread CPU time like the budget itself. Two kernel indexes are now
identical (8,529,723 nodes, 1,974,940 CALLS, zero churn), as are two elasticsearch indexes.

Findability. search_graph's BM25 path honours the `label` filter and ranks an exact-name definition
above test methods that repeat the token (15/15 probe queries).

Coverage. The parse-coverage gap tree subtracts definitions the walker recovered inside an error
region (multi-line only); kernel/sched/core.c reports 476 flagged lines instead of 7,674.

Precision. Config-language callers (Makefile, CMake, YAML) no longer bind unique_name into code
(kernel Makefile -> C CALLS 195 -> 0). JS-family token names ({}, 1, (x: T) => U) are not
definitions; computed, quoted and escaped member keys stay (TypeScript 204 junk -> 0, 1,782 real
names kept). Names are cut at their first line (elasticsearch YAML 8,306 -> 0); a comment inside an
argument list is not an argument, so no Route node is named by a Java block comment; the wildcard
route `/*` is still a route. HTML/Vue/Svelte/Astro embedded member calls stay unbound.

Recall. A Python call through a self- or cls-rooted attribute keeps a specific unique_name member
target (Django +751, elasticsearch +3,288 CALLS, no new callee names).

Robustness. Edge properties never carry a split UTF-8 sequence (the 120-byte expression cut and the
JSON escaper both trim a partial code point). CLI array flags accept a JSON-array literal. Cypher
seeds a single-hop pattern from its selective end (kernel `MATCH (a)-[:CALLS]->(b) WHERE b.name =
'_printk'`: execution limit -> 7.9 s).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-21 20:29:27 +02:00
Yyunozor c86f1ffafd fix(pipeline): reject non-http slash arguments
Signed-off-by: Yyunozor <yyunozor@icloud.com>
2026-08-31 20:14:01 +02:00
Alexandros Pappas d288a21ec2 fix(pipeline): classify native fetch() as HTTP_CALLS, not local shadows
Closes #856: native `fetch()` calls (Node 18+ built-in, and the identical
browser/WHATWG API — no static way to tell them apart, and no need to:
both make a real outbound HTTP request) were invisible to cross-repo
intelligence. A bare `fetch(url)` has no import and typically no local
definition, so registry resolution comes back empty and the call was
silently dropped instead of becoming an HTTP_CALLS edge.

The obvious fix — add "fetch" to the http_libraries substring table in
service_patterns.c — is wrong: that table is consulted by an early,
unconditional check in both pass_calls.c and pass_parallel.c that runs
before/regardless of whether the callee resolves to a real local
definition (by design, so `axios.get(url)` still classifies even when the
registry mis-binds bare `get` to an unrelated local method). That's safe
for "axios"/"requests" because nobody names their own function that; it
is not safe for "fetch", which collides with a plausible local identifier
(`function fetch(){}` or `const fetch = () => {}`).

Instead, cbm_service_pattern_is_global_fetch() is a new, narrow, exact-
match check consulted only in the *empty-resolution* fallback in both
files — the existing #523 path for unindexed external libraries. By the
time that branch runs, the registry has already had its chance to
resolve "fetch" to a local/imported definition; only a genuine miss
reaches the new check. This also means a member call like `repo.fetch()`
is naturally excluded (its callee_name is "repo.fetch", not the bare
"fetch" this checks for).

pass_parallel.c's empty-resolution branch calls the low-level
emit_http_async_service_edge() directly rather than going through
emit_service_edge(), which re-derives its own classification from
res->qualified_name via cbm_service_pattern_match() — a call with
"fetch" would come back CBM_SVC_NONE there and silently fall through to
a plain CALLS edge.

Three new tests in test_pipeline.c:
- bare fetch() -> HTTP_CALLS, sequential path (< 50 files)
- bare fetch() -> HTTP_CALLS, forced through the parallel resolver
  (>= 50 files)
- a local `function fetch(){}` shadowing the global -> plain CALLS to
  the local definition, zero HTTP_CALLS (the false-positive this PR
  must not introduce), bundled with a `repo.fetch()` member-call check
  in the first test

Verified end-to-end, not just by inspection: prod build clean under
-Wall -Wextra -Werror; test build clean under ASan+UBSan; full suite
5936 passed, 1 skipped (pre-existing, unrelated), 0 failed; clang-format
clean on all touched files; lint-cppcheck's pre-existing failures
(extract_defs.c, compat_regex.c) reproduced identically on unmodified
main via stash/pop, confirming they predate this change.

Refs #592.

Signed-off-by: Alexandros Pappas <11921291+apappas1129@users.noreply.github.com>
2026-07-07 11:33:56 +08:00
Matt Van Horn f5ea36cc64 fix: Reject filesystem paths in HTTP_CALLS route extraction so client routes can join server routes (#485)
Signed-off-by: mvanhorn <mvanhorn@gmail.com>
2026-06-22 22:04:31 +02:00
Martin Vogel 6afb45427c Add grammar-only import parsers + pipeline edges (WRITES/CONFIGURES/DEPENDS_ON)
Imports: per-grammar parsers for hare/pascal/powershell/scheme/racket/starlark/tcl/teal/zsh + css/html/cmake/bitbake/kconfig/gn/just/nix/jsonnet/pkl/nickel/thrift/capnp/d/tablegen/crystal/fsharp/ada; node-extraction for D/PowerShell/Pascal/Julia/F#; INHERITS for those. Pipeline edges: register Variable/Field defs for WRITES/READS resolution; CONFIGURES pass consuming env_accesses; DEPENDS_ON from go.mod/requirements.txt; SQS dot-form QN. Golden updates: julia +Class, just +Function (correct new extraction).
2026-06-08 13:04:12 +02:00
Martin Vogel 7a6a8cb7a5 style: apply clang-format-20 across tree
Format drift had accumulated across recent commits (the dry-run lint enforces clang-format-20, which had not run on these commits yet). No functional changes — whitespace/layout only, verified by a clean cppcheck + clang-format + NOLINT lint-ci pass.
2026-05-29 00:34:18 +02:00
Martin Vogel 1af1dd8688 perf(service_patterns): per-worker TLS cache for cbm_service_pattern_match
cbm_service_pattern_match runs once per resolved CALL edge inside
emit_service_edge — that's 7 pattern lists × ~30 patterns × strstr
= ~210 strstrs per call. On kubernetes (~600k resolved CALL edges)
the SAME resolved QN ("fmt.Errorf", "context.Context.Done",
"errors.New", etc) repeats thousands of times across files. Caching
turns the linear pattern-list scan into one hash lookup after the
first miss.

API additions in service_patterns.h:
  void cbm_service_pattern_cache_begin(void);
  void cbm_service_pattern_cache_end(void);

Cache is TLS (no contention) scoped to the resolve_worker's lifetime
in the parallel_resolve phase. Capacity 8192 entries per worker —
covers typical project QN diversity without rehashing.

Encoding: enum value + 1 in the void* slot (0/NULL means "miss").
Two helpers svc_enum_to_ptr / svc_ptr_to_enum hide the trick.

Benchmark on kubernetes (12 workers, 20,650 files):
- parallel_resolve wall: 98,520 -> 90,104 ms (-8.5% vs Chunk 1
  baseline, -12.8% vs original 103,281 baseline)
- resolve_calls CPU:    992,427 -> 885,663 ms (-10.8% vs Chunk 1,
  -5.4% vs original — Chunk 1 added overhead that Chunk 2 recovers
  AND surpasses)
- resolve_usages CPU:    11,977 -> 10,944 ms (-8.6%)
- total_loop CPU:     1,005,409 -> 897,561 ms (-10.7%)
- Tests: 3616/0
- Graph: 297,082 -> 297,080 nodes (-2), 1,153,482 -> 1,156,097 edges
  (within 0.2%, semantic-preserving)
- zod/serilog sanity: edges within +/-3 of baseline
- Peak RSS: 7.46 -> 7.75 GB (+290 MB for 12 × 8192-entry caches)

Cumulative trajectory: original broken state ~35 min K8s ->
1.92 min wall (-49× total). The per-file is_import_reachable cache
(Chunk 1) + the per-worker service-pattern cache (Chunk 2) together
save ~13s of resolve wall time. Next chunks target the gbuf_insert_
edge hot path and remaining strstr scans.
2026-05-21 16:28:55 +02:00
test 81385d08eb Add gRPC/GraphQL/tRPC service detection with protobuf Route extraction
Pipeline: gRPC/GraphQL/tRPC client libs in service_patterns, edge emitters
in pass_parallel, __grpc__ Route nodes from .proto service defs in
pass_route_nodes, cross-repo matchers in pass_cross_repo. Also adds MQTT,
NATS, Dapr async patterns.

Protobuf extraction: service as Class, rpc as Function with rpc_name
fallback in func_name_node. Route nodes created by line-range matching
rpc Functions to enclosing service Classes.

Tested: Online Boutique protos produce 16 gRPC Route nodes.
2026-04-15 23:53:42 +02:00
Martin Vogel afba50e018 Generalized route registration + infra binding bridge
Route registration detection now works for unresolved local variables
(app.include_router, app.mount) by matching callee name suffix when
QN resolution fails. Scans ALL arguments for URL paths — handles
prefix=, path=, route= keyword args across frameworks.

New route_reg_suffixes: include_router, mount, add_url_rule,
register_blueprint, use, register, add_route, add_api_route.

Infrastructure bindings: YAML/JSON extraction captures topic→URL
pairs from subscription configs (push_endpoint, uri). Creates
INFRA_MAPS edges linking async topic Routes to endpoint URLs.

Improved match_infra_routes: matches prefix Routes from include_router,
supports service-only matching for generic "/" handlers, correct
Cloud Run hostname parsing for new project-number format.
2026-03-28 18:36:14 +01:00
Martin Vogel b00e1f74c2 AST-based route registration + remove prescan infrastructure
Route registration detection during AST extraction: callee QN matched
against router frameworks (gin, chi, Express, Laravel, Ktor, etc.),
handler reference captured as second_arg_name, Route + HANDLES edges
created during resolution. Replaces disk-reading route discovery for
non-decorator frameworks.

Remove prescan infrastructure entirely (-350 net lines):
- cbm_prescan_t, cbm_prescan_http_site_t, cbm_prescan_route_t,
  cbm_prescan_config_ref_t structs removed from pipeline_internal.h
- prescan_http_sites, prescan_routes, prescan_config_refs,
  prescan_add_route removed from pass_parallel.c
- prescan fast-paths removed from pass_httplinks.c and pass_configlink.c
- prescan allocation/cleanup removed from pipeline.c

All post-passes now work without prescan:
- HTTP/async edges: service_patterns during resolution
- Route registration: CBM_SVC_ROUTE_REG during resolution
- Decorator routes: gbuf properties (no disk reads)
- Config file refs: disk fallback (few files)
2026-03-28 13:59:42 +01:00
Martin Vogel 80680ea367 Cross-service communication discovery + RAM-first incremental indexing
AST-based detection of HTTP calls, async dispatch (Pub/Sub, Cloud Tasks,
Kafka, SQS, etc.), and config accesses via resolved qualified names.
Route nodes as cross-service rendezvous points with infra→handler matching.
Constant propagation for module-level string assignments. YAML infrastructure
URL extraction from Cloud Scheduler configs.

RAM-first incremental pipeline: load DB into graph buffer, purge changed
file nodes, extract directly into existing buffer (resolver sees all nodes),
dump back to disk. Zero edge gap on kubernetes/django/meilisearch/neovim.

- service_patterns.c: ~170 library patterns (90 HTTP, 50 async, 30 config)
- pass_route_nodes.c: Route node creation + infra URL matching
- extract_unified.c: string constant collection + string ref classification
- extract_calls.c: first_string_arg + keyword argument extraction
- pipeline_incremental.c: RAM-first load→purge→extract→resolve→dump
- graph_buffer.c: load_from_db, delete_by_file, foreach visitors
- C++ LSP crash fix: NULL guard in cbm_type_substitute
2026-03-28 13:59:42 +01:00