seer-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +3 -0
- package/LICENSE +176 -0
- package/README.md +272 -0
- package/README_dev.md +199 -0
- package/dist/bundle/ci.d.ts +47 -0
- package/dist/bundle/ci.d.ts.map +1 -0
- package/dist/bundle/ci.js +113 -0
- package/dist/bundle/ci.js.map +1 -0
- package/dist/bundle/contract.d.ts +111 -0
- package/dist/bundle/contract.d.ts.map +1 -0
- package/dist/bundle/contract.js +352 -0
- package/dist/bundle/contract.js.map +1 -0
- package/dist/bundle/export.d.ts +36 -0
- package/dist/bundle/export.d.ts.map +1 -0
- package/dist/bundle/export.js +152 -0
- package/dist/bundle/export.js.map +1 -0
- package/dist/bundle/external.d.ts +66 -0
- package/dist/bundle/external.d.ts.map +1 -0
- package/dist/bundle/external.js +238 -0
- package/dist/bundle/external.js.map +1 -0
- package/dist/bundle/format.d.ts +94 -0
- package/dist/bundle/format.d.ts.map +1 -0
- package/dist/bundle/format.js +42 -0
- package/dist/bundle/format.js.map +1 -0
- package/dist/bundle/import.d.ts +49 -0
- package/dist/bundle/import.d.ts.map +1 -0
- package/dist/bundle/import.js +116 -0
- package/dist/bundle/import.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1402 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +48 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +284 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/db/schema.d.ts +3 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +616 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/store.d.ts +1011 -0
- package/dist/db/store.d.ts.map +1 -0
- package/dist/db/store.js +3888 -0
- package/dist/db/store.js.map +1 -0
- package/dist/graph/pagerank.d.ts +9 -0
- package/dist/graph/pagerank.d.ts.map +1 -0
- package/dist/graph/pagerank.js +47 -0
- package/dist/graph/pagerank.js.map +1 -0
- package/dist/indexer/architecture.d.ts +72 -0
- package/dist/indexer/architecture.d.ts.map +1 -0
- package/dist/indexer/architecture.js +112 -0
- package/dist/indexer/architecture.js.map +1 -0
- package/dist/indexer/behavior.d.ts +75 -0
- package/dist/indexer/behavior.d.ts.map +1 -0
- package/dist/indexer/behavior.js +395 -0
- package/dist/indexer/behavior.js.map +1 -0
- package/dist/indexer/boundaries.d.ts +60 -0
- package/dist/indexer/boundaries.d.ts.map +1 -0
- package/dist/indexer/boundaries.js +366 -0
- package/dist/indexer/boundaries.js.map +1 -0
- package/dist/indexer/churn.d.ts +15 -0
- package/dist/indexer/churn.d.ts.map +1 -0
- package/dist/indexer/churn.js +49 -0
- package/dist/indexer/churn.js.map +1 -0
- package/dist/indexer/classify.d.ts +9 -0
- package/dist/indexer/classify.d.ts.map +1 -0
- package/dist/indexer/classify.js +90 -0
- package/dist/indexer/classify.js.map +1 -0
- package/dist/indexer/context.d.ts +176 -0
- package/dist/indexer/context.d.ts.map +1 -0
- package/dist/indexer/context.js +193 -0
- package/dist/indexer/context.js.map +1 -0
- package/dist/indexer/continuity.d.ts +67 -0
- package/dist/indexer/continuity.d.ts.map +1 -0
- package/dist/indexer/continuity.js +288 -0
- package/dist/indexer/continuity.js.map +1 -0
- package/dist/indexer/detectchanges.d.ts +32 -0
- package/dist/indexer/detectchanges.d.ts.map +1 -0
- package/dist/indexer/detectchanges.js +74 -0
- package/dist/indexer/detectchanges.js.map +1 -0
- package/dist/indexer/discovery.d.ts +37 -0
- package/dist/indexer/discovery.d.ts.map +1 -0
- package/dist/indexer/discovery.js +136 -0
- package/dist/indexer/discovery.js.map +1 -0
- package/dist/indexer/externaldeps.d.ts +18 -0
- package/dist/indexer/externaldeps.d.ts.map +1 -0
- package/dist/indexer/externaldeps.js +288 -0
- package/dist/indexer/externaldeps.js.map +1 -0
- package/dist/indexer/freshness.d.ts +48 -0
- package/dist/indexer/freshness.d.ts.map +1 -0
- package/dist/indexer/freshness.js +128 -0
- package/dist/indexer/freshness.js.map +1 -0
- package/dist/indexer/git.d.ts +144 -0
- package/dist/indexer/git.d.ts.map +1 -0
- package/dist/indexer/git.js +444 -0
- package/dist/indexer/git.js.map +1 -0
- package/dist/indexer/index.d.ts +145 -0
- package/dist/indexer/index.d.ts.map +1 -0
- package/dist/indexer/index.js +930 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/modules.d.ts +62 -0
- package/dist/indexer/modules.d.ts.map +1 -0
- package/dist/indexer/modules.js +293 -0
- package/dist/indexer/modules.js.map +1 -0
- package/dist/indexer/preflight.d.ts +154 -0
- package/dist/indexer/preflight.d.ts.map +1 -0
- package/dist/indexer/preflight.js +399 -0
- package/dist/indexer/preflight.js.map +1 -0
- package/dist/indexer/protoScanner.d.ts +34 -0
- package/dist/indexer/protoScanner.d.ts.map +1 -0
- package/dist/indexer/protoScanner.js +133 -0
- package/dist/indexer/protoScanner.js.map +1 -0
- package/dist/indexer/risk.d.ts +115 -0
- package/dist/indexer/risk.d.ts.map +1 -0
- package/dist/indexer/risk.js +194 -0
- package/dist/indexer/risk.js.map +1 -0
- package/dist/indexer/serviceHostScanner.d.ts +25 -0
- package/dist/indexer/serviceHostScanner.d.ts.map +1 -0
- package/dist/indexer/serviceHostScanner.js +95 -0
- package/dist/indexer/serviceHostScanner.js.map +1 -0
- package/dist/indexer/serviceLinks.d.ts +105 -0
- package/dist/indexer/serviceLinks.d.ts.map +1 -0
- package/dist/indexer/serviceLinks.js +509 -0
- package/dist/indexer/serviceLinks.js.map +1 -0
- package/dist/indexer/shapehash.d.ts +98 -0
- package/dist/indexer/shapehash.d.ts.map +1 -0
- package/dist/indexer/shapehash.js +354 -0
- package/dist/indexer/shapehash.js.map +1 -0
- package/dist/indexer/skeleton.d.ts +15 -0
- package/dist/indexer/skeleton.d.ts.map +1 -0
- package/dist/indexer/skeleton.js +136 -0
- package/dist/indexer/skeleton.js.map +1 -0
- package/dist/indexer/symbolhistory.d.ts +41 -0
- package/dist/indexer/symbolhistory.d.ts.map +1 -0
- package/dist/indexer/symbolhistory.js +124 -0
- package/dist/indexer/symbolhistory.js.map +1 -0
- package/dist/indexer/watcher.d.ts +68 -0
- package/dist/indexer/watcher.d.ts.map +1 -0
- package/dist/indexer/watcher.js +179 -0
- package/dist/indexer/watcher.js.map +1 -0
- package/dist/mcp/server.d.ts +80 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +1610 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/parser/index.d.ts +8 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +33 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/languages/cpp.d.ts +3 -0
- package/dist/parser/languages/cpp.d.ts.map +1 -0
- package/dist/parser/languages/cpp.js +350 -0
- package/dist/parser/languages/cpp.js.map +1 -0
- package/dist/parser/languages/csharp.d.ts +3 -0
- package/dist/parser/languages/csharp.d.ts.map +1 -0
- package/dist/parser/languages/csharp.js +239 -0
- package/dist/parser/languages/csharp.js.map +1 -0
- package/dist/parser/languages/go.d.ts +3 -0
- package/dist/parser/languages/go.d.ts.map +1 -0
- package/dist/parser/languages/go.js +259 -0
- package/dist/parser/languages/go.js.map +1 -0
- package/dist/parser/languages/java.d.ts +3 -0
- package/dist/parser/languages/java.d.ts.map +1 -0
- package/dist/parser/languages/java.js +391 -0
- package/dist/parser/languages/java.js.map +1 -0
- package/dist/parser/languages/python.d.ts +3 -0
- package/dist/parser/languages/python.d.ts.map +1 -0
- package/dist/parser/languages/python.js +396 -0
- package/dist/parser/languages/python.js.map +1 -0
- package/dist/parser/languages/rust.d.ts +3 -0
- package/dist/parser/languages/rust.d.ts.map +1 -0
- package/dist/parser/languages/rust.js +159 -0
- package/dist/parser/languages/rust.js.map +1 -0
- package/dist/parser/languages/typescript.d.ts +3 -0
- package/dist/parser/languages/typescript.d.ts.map +1 -0
- package/dist/parser/languages/typescript.js +1442 -0
- package/dist/parser/languages/typescript.js.map +1 -0
- package/dist/parser/parserContext.d.ts +77 -0
- package/dist/parser/parserContext.d.ts.map +1 -0
- package/dist/parser/parserContext.js +354 -0
- package/dist/parser/parserContext.js.map +1 -0
- package/dist/parser/walker.d.ts +81 -0
- package/dist/parser/walker.d.ts.map +1 -0
- package/dist/parser/walker.js +217 -0
- package/dist/parser/walker.js.map +1 -0
- package/dist/parser/worker.d.ts +66 -0
- package/dist/parser/worker.d.ts.map +1 -0
- package/dist/parser/worker.js +129 -0
- package/dist/parser/worker.js.map +1 -0
- package/dist/parser/workerpool.d.ts +107 -0
- package/dist/parser/workerpool.d.ts.map +1 -0
- package/dist/parser/workerpool.js +383 -0
- package/dist/parser/workerpool.js.map +1 -0
- package/dist/scip/format.d.ts +87 -0
- package/dist/scip/format.d.ts.map +1 -0
- package/dist/scip/format.js +31 -0
- package/dist/scip/format.js.map +1 -0
- package/dist/scip/import.d.ts +37 -0
- package/dist/scip/import.d.ts.map +1 -0
- package/dist/scip/import.js +180 -0
- package/dist/scip/import.js.map +1 -0
- package/dist/types.d.ts +392 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/docs/architecture.md +105 -0
- package/docs/benchmarks/methodology.md +134 -0
- package/docs/benchmarks/raw-results.md +71 -0
- package/docs/benchmarks.md +74 -0
- package/docs/cli.md +148 -0
- package/docs/examples/behavior-tests.md +70 -0
- package/docs/examples/change-history.md +85 -0
- package/docs/examples/pre-edit-context.md +81 -0
- package/docs/examples/service-links.md +88 -0
- package/docs/examples.md +80 -0
- package/docs/faq.md +70 -0
- package/docs/internals.md +104 -0
- package/docs/languages.md +70 -0
- package/docs/limits.md +52 -0
- package/docs/mcp.md +199 -0
- package/docs/quickstart.md +119 -0
- package/docs/testing.md +123 -0
- package/docs/tools.md +115 -0
- package/package.json +52 -0
- package/research-codebase.md +578 -0
- package/seer-cli-docs.md +326 -0
- package/seer-master-guide.md +246 -0
- package/src/bundle/ci.ts +141 -0
- package/src/bundle/contract.ts +387 -0
- package/src/bundle/export.ts +175 -0
- package/src/bundle/external.ts +285 -0
- package/src/bundle/format.ts +92 -0
- package/src/bundle/import.ts +157 -0
- package/src/cli/index.ts +1249 -0
- package/src/cli/init.ts +389 -0
- package/src/db/schema.ts +614 -0
- package/src/db/store.ts +4306 -0
- package/src/graph/pagerank.ts +53 -0
- package/src/indexer/architecture.ts +148 -0
- package/src/indexer/behavior.ts +466 -0
- package/src/indexer/boundaries.ts +374 -0
- package/src/indexer/churn.ts +58 -0
- package/src/indexer/classify.ts +96 -0
- package/src/indexer/context.ts +340 -0
- package/src/indexer/continuity.ts +322 -0
- package/src/indexer/detectchanges.ts +94 -0
- package/src/indexer/discovery.ts +176 -0
- package/src/indexer/externaldeps.ts +243 -0
- package/src/indexer/freshness.ts +166 -0
- package/src/indexer/git.ts +453 -0
- package/src/indexer/index.ts +1092 -0
- package/src/indexer/modules.ts +358 -0
- package/src/indexer/preflight.ts +548 -0
- package/src/indexer/protoScanner.ts +147 -0
- package/src/indexer/risk.ts +304 -0
- package/src/indexer/serviceHostScanner.ts +92 -0
- package/src/indexer/serviceLinks.ts +543 -0
- package/src/indexer/shapehash.ts +370 -0
- package/src/indexer/skeleton.ts +169 -0
- package/src/indexer/symbolhistory.ts +172 -0
- package/src/indexer/watcher.ts +206 -0
- package/src/mcp/server.ts +1659 -0
- package/src/parser/index.ts +37 -0
- package/src/parser/languages/cpp.ts +361 -0
- package/src/parser/languages/csharp.ts +235 -0
- package/src/parser/languages/go.ts +259 -0
- package/src/parser/languages/java.ts +382 -0
- package/src/parser/languages/python.ts +370 -0
- package/src/parser/languages/rust.ts +164 -0
- package/src/parser/languages/typescript.ts +1435 -0
- package/src/parser/parserContext.ts +392 -0
- package/src/parser/walker.ts +306 -0
- package/src/parser/worker.ts +181 -0
- package/src/parser/workerpool.ts +448 -0
- package/src/scip/format.ts +83 -0
- package/src/scip/import.ts +216 -0
- package/src/types.ts +457 -0
- package/tests/benchmark-service-links.ts +244 -0
- package/tests/bug-regressions.ts +626 -0
- package/tests/filters.ts +264 -0
- package/tests/fixtures/Counter.tsx +38 -0
- package/tests/fixtures/caller.ts +7 -0
- package/tests/fixtures/collisions.ts +23 -0
- package/tests/fixtures/local_helper.ts +5 -0
- package/tests/fixtures/overloads.java +17 -0
- package/tests/fixtures/remote_helper.ts +4 -0
- package/tests/fixtures/sample.c +15 -0
- package/tests/fixtures/sample.cpp +47 -0
- package/tests/fixtures/sample.cs +62 -0
- package/tests/fixtures/sample.go +68 -0
- package/tests/fixtures/sample.h +30 -0
- package/tests/fixtures/sample.java +85 -0
- package/tests/fixtures/sample.py +46 -0
- package/tests/fixtures/sample.rs +78 -0
- package/tests/fixtures/sample.ts +76 -0
- package/tests/fixtures-service/HttpClients.cs +30 -0
- package/tests/fixtures-service/HttpClients.java +24 -0
- package/tests/fixtures-service/billing.ts +15 -0
- package/tests/fixtures-service/docker-compose.yml +15 -0
- package/tests/fixtures-service/gateway.ts +10 -0
- package/tests/fixtures-service/get_user.ts +11 -0
- package/tests/fixtures-service/graphql_client.ts +63 -0
- package/tests/fixtures-service/graphql_server.ts +30 -0
- package/tests/fixtures-service/grpc_client.go +30 -0
- package/tests/fixtures-service/http_clients.go +23 -0
- package/tests/fixtures-service/http_clients.py +38 -0
- package/tests/fixtures-service/http_clients.ts +49 -0
- package/tests/fixtures-service/k8s/payment-service.yaml +22 -0
- package/tests/fixtures-service/k8s_calls.ts +20 -0
- package/tests/fixtures-service/messaging.ts +87 -0
- package/tests/fixtures-service/trpc_client.ts +39 -0
- package/tests/fixtures-service/trpc_server.ts +39 -0
- package/tests/fixtures-service/user_service.proto +33 -0
- package/tests/fixtures-trackcd/Cargo.toml +11 -0
- package/tests/fixtures-trackcd/SpringController.java +36 -0
- package/tests/fixtures-trackcd/auth_service.ts +19 -0
- package/tests/fixtures-trackcd/complex_module.py +50 -0
- package/tests/fixtures-trackcd/express_app.js +30 -0
- package/tests/fixtures-trackcd/fastapi_app.py +49 -0
- package/tests/fixtures-trackcd/fastify_object_routes.js +32 -0
- package/tests/fixtures-trackcd/go.mod +8 -0
- package/tests/fixtures-trackcd/package.json +15 -0
- package/tests/fixtures-trackcd/requirements.txt +4 -0
- package/tests/fixtures-trackcd/tests/auth_service.test.ts +13 -0
- package/tests/fixtures-tracke/auth/AuthService.ts +23 -0
- package/tests/fixtures-tracke/auth/crypto.ts +7 -0
- package/tests/fixtures-tracke/billing/Billing.ts +20 -0
- package/tests/fixtures-tracke/billing/Invoice.ts +10 -0
- package/tests/fixtures-tracke/billing/server.ts +17 -0
- package/tests/fixtures-tracke/package.json +7 -0
- package/tests/fixtures-tracke/tests/auth.test.ts +23 -0
- package/tests/fixtures-tracke/tests/billing.test.ts +14 -0
- package/tests/fixtures-trackf/package.json +5 -0
- package/tests/fixtures-trackf/src/auth.ts +26 -0
- package/tests/fixtures-trackf/src/handlers.ts +35 -0
- package/tests/fixtures-tracki/billing/routes.ts +12 -0
- package/tests/fixtures-tracki/gateway/client.ts +13 -0
- package/tests/git-features.ts +267 -0
- package/tests/init.ts +141 -0
- package/tests/mcp-jit.ts +130 -0
- package/tests/mcp-smoke.ts +191 -0
- package/tests/mcp-trackcd.ts +169 -0
- package/tests/mcp-tracke.ts +229 -0
- package/tests/mcp-trackf.ts +330 -0
- package/tests/mcp-trackg.ts +219 -0
- package/tests/mcp-tracki.ts +174 -0
- package/tests/mcp-watcher.ts +126 -0
- package/tests/optspec.ts +194 -0
- package/tests/parallel-index.ts +333 -0
- package/tests/parallel-read.ts +125 -0
- package/tests/parallel-recovery.ts +241 -0
- package/tests/perf-callers.ts +145 -0
- package/tests/query-parity.ts +184 -0
- package/tests/query-perf.ts +55 -0
- package/tests/scale-parallel-parity.ts +225 -0
- package/tests/scale-test.ts +523 -0
- package/tests/smoke.ts +396 -0
- package/tests/trackcd.ts +325 -0
- package/tests/tracke-collisions.ts +255 -0
- package/tests/tracke.ts +314 -0
- package/tests/trackf-bugs.ts +406 -0
- package/tests/trackf.ts +390 -0
- package/tests/trackg.ts +1372 -0
- package/tests/tracki-boundaries.ts +202 -0
- package/tests/tracki-continuity.ts +253 -0
- package/tests/tracki-contract-diff.ts +249 -0
- package/tests/tracki-external-bundles.ts +341 -0
- package/tests/tracki-preflight.ts +251 -0
- package/tests/verify-roles.ts +51 -0
- package/tests/worker-parity.ts +286 -0
- package/tests/worker-pool.ts +262 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Track G — service-link resolver and helpers.
|
|
4
|
+
*
|
|
5
|
+
* Two concerns live here, kept independent so they can be unit-tested in
|
|
6
|
+
* isolation:
|
|
7
|
+
*
|
|
8
|
+
* 1. `normalizeHttpTarget(raw)` — turn a string captured at a client call
|
|
9
|
+
* site (literal URL, template path, env-prefixed concat) into a
|
|
10
|
+
* `{ path, hostHint, envKey }` triple where each field is undefined when
|
|
11
|
+
* it can't be confidently recovered. Pure function; no DB access.
|
|
12
|
+
*
|
|
13
|
+
* 2. `routePatternsMatch(callPath, routePath)` — decide whether a literal
|
|
14
|
+
* caller path matches a (possibly parameterised) framework route.
|
|
15
|
+
* Returns a `{ matched, confidence, reason }` result so the resolver
|
|
16
|
+
* can ranke evidence without recomputing it.
|
|
17
|
+
*
|
|
18
|
+
* 3. `resolveServiceLinks(store)` — the actual post-index resolver.
|
|
19
|
+
* Wipes prior service_links, scans service_calls + routes, and inserts
|
|
20
|
+
* one row per confident rendezvous.
|
|
21
|
+
*
|
|
22
|
+
* Deterministic by construction: the resolver runs candidates in id order
|
|
23
|
+
* and only emits a top match (with ambiguity recorded as evidence) so two
|
|
24
|
+
* runs of the same DB produce the same service_links rows.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.normalizeHttpTarget = normalizeHttpTarget;
|
|
28
|
+
exports.routePatternsMatch = routePatternsMatch;
|
|
29
|
+
exports.methodMatchScore = methodMatchScore;
|
|
30
|
+
exports.resolveServiceLinks = resolveServiceLinks;
|
|
31
|
+
exports.normalizeRoutePath = normalizeRoutePath;
|
|
32
|
+
/**
|
|
33
|
+
* Normalize an HTTP target captured at a client call site.
|
|
34
|
+
*
|
|
35
|
+
* Rules:
|
|
36
|
+
* - strip scheme + host when literal (`https://payment/api/charge` → `/api/charge`, host = `payment`)
|
|
37
|
+
* - keep route paths starting with '/'
|
|
38
|
+
* - drop query string and fragment
|
|
39
|
+
* - normalize trailing slash (but keep root '/'): '/api/users/' → '/api/users'
|
|
40
|
+
* - drop empty paths
|
|
41
|
+
* - do NOT collapse dynamic segments — that's the route-pattern matcher's job.
|
|
42
|
+
*/
|
|
43
|
+
function normalizeHttpTarget(raw) {
|
|
44
|
+
if (!raw || typeof raw !== 'string')
|
|
45
|
+
return {};
|
|
46
|
+
let s = raw.trim();
|
|
47
|
+
if (!s)
|
|
48
|
+
return {};
|
|
49
|
+
let hostHint;
|
|
50
|
+
// Strip scheme + host if present.
|
|
51
|
+
const schemeMatch = s.match(/^(https?):\/\/([^/?#]+)(.*)$/i);
|
|
52
|
+
if (schemeMatch) {
|
|
53
|
+
hostHint = schemeMatch[2];
|
|
54
|
+
s = schemeMatch[3] || '/';
|
|
55
|
+
}
|
|
56
|
+
else if (/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9_./%-]+/.test(s) && !s.startsWith('/')) {
|
|
57
|
+
// Bare host/path with no scheme (rare but seen in some libs).
|
|
58
|
+
const firstSlash = s.indexOf('/');
|
|
59
|
+
if (firstSlash > 0) {
|
|
60
|
+
hostHint = s.slice(0, firstSlash);
|
|
61
|
+
s = s.slice(firstSlash);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Strip query + fragment.
|
|
65
|
+
const q = s.indexOf('?');
|
|
66
|
+
if (q >= 0)
|
|
67
|
+
s = s.slice(0, q);
|
|
68
|
+
const h = s.indexOf('#');
|
|
69
|
+
if (h >= 0)
|
|
70
|
+
s = s.slice(0, h);
|
|
71
|
+
// Trailing slash normalization (keep root).
|
|
72
|
+
if (s.length > 1 && s.endsWith('/'))
|
|
73
|
+
s = s.slice(0, -1);
|
|
74
|
+
let path;
|
|
75
|
+
if (s.startsWith('/'))
|
|
76
|
+
path = s;
|
|
77
|
+
const out = {};
|
|
78
|
+
if (path)
|
|
79
|
+
out.path = path;
|
|
80
|
+
if (hostHint)
|
|
81
|
+
out.hostHint = hostHint;
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Decide whether `callPath` (literal) satisfies a (possibly parameterised)
|
|
86
|
+
* `routePath` like `/users/:id`, `/users/{id}`, `/items/<int:n>`.
|
|
87
|
+
*
|
|
88
|
+
* No method check here — the resolver applies method comparison around this
|
|
89
|
+
* with method-mismatch falling back to a lower confidence.
|
|
90
|
+
*
|
|
91
|
+
* Returns matched=false when the segment count differs OR a literal segment
|
|
92
|
+
* differs. Parameter segments (`:x`, `{x}`, `<...:x>`) match any one segment.
|
|
93
|
+
*/
|
|
94
|
+
function routePatternsMatch(callPath, routePath) {
|
|
95
|
+
if (!callPath || !routePath)
|
|
96
|
+
return { matched: false, confidence: 0, reason: 'empty' };
|
|
97
|
+
// Exact literal match.
|
|
98
|
+
if (callPath === routePath) {
|
|
99
|
+
return { matched: true, confidence: 0.95, reason: 'literal_path' };
|
|
100
|
+
}
|
|
101
|
+
const callSegs = callPath.split('/').filter(Boolean);
|
|
102
|
+
const routeSegs = routePath.split('/').filter(Boolean);
|
|
103
|
+
if (callSegs.length !== routeSegs.length) {
|
|
104
|
+
return { matched: false, confidence: 0, reason: 'segment_count' };
|
|
105
|
+
}
|
|
106
|
+
let paramHits = 0;
|
|
107
|
+
for (let i = 0; i < callSegs.length; i++) {
|
|
108
|
+
const r = routeSegs[i];
|
|
109
|
+
const c = callSegs[i];
|
|
110
|
+
if (isParamSegment(r)) {
|
|
111
|
+
paramHits++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (r !== c)
|
|
115
|
+
return { matched: false, confidence: 0, reason: 'segment_mismatch' };
|
|
116
|
+
}
|
|
117
|
+
if (paramHits === 0) {
|
|
118
|
+
// Same literal segment count but no params — earlier exact check should
|
|
119
|
+
// have caught this. Treat as no match so we don't accidentally rank
|
|
120
|
+
// /a/b == /a/b twice.
|
|
121
|
+
return { matched: false, confidence: 0, reason: 'duplicate_literal' };
|
|
122
|
+
}
|
|
123
|
+
return { matched: true, confidence: 0.85, reason: 'route_pattern' };
|
|
124
|
+
}
|
|
125
|
+
function isParamSegment(seg) {
|
|
126
|
+
// Express :id, FastAPI {id}, Spring {id}, Flask <int:id>
|
|
127
|
+
if (seg.startsWith(':'))
|
|
128
|
+
return true;
|
|
129
|
+
if (seg.startsWith('{') && seg.endsWith('}'))
|
|
130
|
+
return true;
|
|
131
|
+
if (seg.startsWith('<') && seg.endsWith('>'))
|
|
132
|
+
return true;
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Compare two HTTP methods, treating 'ANY' (client unknown method) and route's
|
|
137
|
+
* absence of method as wildcards. Returns:
|
|
138
|
+
* 1.0 — methods match exactly (including both 'ANY')
|
|
139
|
+
* 0.9 — caller method known + route is ANY (or vice versa)
|
|
140
|
+
* 0.0 — explicit mismatch (e.g. POST vs GET)
|
|
141
|
+
*/
|
|
142
|
+
function methodMatchScore(callMethod, routeMethod) {
|
|
143
|
+
const cm = (callMethod ?? 'ANY').toUpperCase();
|
|
144
|
+
const rm = (routeMethod ?? 'ANY').toUpperCase();
|
|
145
|
+
if (cm === rm)
|
|
146
|
+
return 1.0;
|
|
147
|
+
if (cm === 'ANY' || rm === 'ANY')
|
|
148
|
+
return 0.9;
|
|
149
|
+
return 0.0;
|
|
150
|
+
}
|
|
151
|
+
// v9 Track-H — hard cap on how many candidate routes we consider for a single
|
|
152
|
+
// service_call. A symbol with 25+ matching routes is almost always ambiguous
|
|
153
|
+
// (think a generic /users/:id route in 30 microservices); we keep determinism
|
|
154
|
+
// by sorting first and then truncating, and record `truncated: true` in the
|
|
155
|
+
// link's evidence so the agent can see the cutoff happened.
|
|
156
|
+
const MAX_CANDIDATES_PER_CALL = 25;
|
|
157
|
+
// Cap on how many candidates we serialize into evidence_json. Smaller than the
|
|
158
|
+
// cap above so a link row stays compact even when many routes matched.
|
|
159
|
+
const MAX_EVIDENCE_CANDIDATES = 5;
|
|
160
|
+
/**
|
|
161
|
+
* Wipe service_links and rebuild from current service_calls + routes.
|
|
162
|
+
* Deterministic: orders candidates by id ascending and ties are broken by id.
|
|
163
|
+
*
|
|
164
|
+
* Match strategy:
|
|
165
|
+
* 1. literal_path — call.path == route.path, method compatible
|
|
166
|
+
* 2. route_pattern — call.path matches a parameterised route, method compatible
|
|
167
|
+
* 3. env_base — call carried an env key; we record the link only when
|
|
168
|
+
* the path *also* matches a route (env alone is not enough).
|
|
169
|
+
*
|
|
170
|
+
* S3 (service_host) — k8s/Docker host resolution — is provided by
|
|
171
|
+
* `scanServiceHosts()` and passed in as optional evidence.
|
|
172
|
+
*/
|
|
173
|
+
function resolveServiceLinks(store, options = {}) {
|
|
174
|
+
const raw = store.rawDb();
|
|
175
|
+
raw.exec('DELETE FROM service_links');
|
|
176
|
+
// v9: detect whether the v9 generalized columns exist. listServiceCalls
|
|
177
|
+
// reads them but we use a raw SELECT here for speed.
|
|
178
|
+
const hasV9Cols = hasColumn(raw, 'service_calls', 'operation');
|
|
179
|
+
const v9Cols = hasV9Cols ? ', operation, topic, queue, service' : '';
|
|
180
|
+
const calls = raw.prepare(`SELECT id, symbol_id, protocol, method, raw_target, normalized_path,
|
|
181
|
+
host_hint, env_key, framework ${v9Cols}
|
|
182
|
+
FROM service_calls
|
|
183
|
+
ORDER BY id ASC`).all();
|
|
184
|
+
// v9 Track-H — build a doc-identifier → field-name map from gql-doc sentinel
|
|
185
|
+
// rows. Lets us rewrite `client.query({ query: GET_USER })` whose operation
|
|
186
|
+
// is the const name to the operation field parsed from its gql body.
|
|
187
|
+
const gqlDocMap = new Map();
|
|
188
|
+
for (const c of calls) {
|
|
189
|
+
if (c.framework !== 'gql-doc' || !c.operation || !c.raw_target)
|
|
190
|
+
continue;
|
|
191
|
+
gqlDocMap.set(c.raw_target, { operation: c.operation, method: c.method });
|
|
192
|
+
}
|
|
193
|
+
const hasV9RouteCols = hasColumn(raw, 'routes', 'protocol');
|
|
194
|
+
const v9RouteCols = hasV9RouteCols
|
|
195
|
+
? ', protocol, operation, topic, queue, service'
|
|
196
|
+
: '';
|
|
197
|
+
const routes = raw.prepare(`SELECT id, method, path, framework, handler_id ${v9RouteCols}
|
|
198
|
+
FROM routes
|
|
199
|
+
ORDER BY id ASC`).all();
|
|
200
|
+
// Backfill protocol/operation for pre-v9 row shape — every pre-v9 route is HTTP.
|
|
201
|
+
if (!hasV9RouteCols) {
|
|
202
|
+
for (const r of routes) {
|
|
203
|
+
r.protocol = 'http';
|
|
204
|
+
r.operation = null;
|
|
205
|
+
r.topic = null;
|
|
206
|
+
r.queue = null;
|
|
207
|
+
r.service = null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Index routes by exact path for cheap lookup (HTTP only), and by operation
|
|
211
|
+
// (tRPC / GraphQL / gRPC), and keep the full list for parameterised matching.
|
|
212
|
+
const byExactPath = new Map();
|
|
213
|
+
const byOperation = new Map(); // key = `${protocol}:${operation}`
|
|
214
|
+
const byTopic = new Map(); // key = topic
|
|
215
|
+
const byQueue = new Map(); // key = queue
|
|
216
|
+
for (const r of routes) {
|
|
217
|
+
const proto = r.protocol ?? 'http';
|
|
218
|
+
if (proto === 'http' && r.path) {
|
|
219
|
+
const norm = normalizeRoutePath(r.path);
|
|
220
|
+
const list = byExactPath.get(norm);
|
|
221
|
+
if (list)
|
|
222
|
+
list.push(r);
|
|
223
|
+
else
|
|
224
|
+
byExactPath.set(norm, [r]);
|
|
225
|
+
}
|
|
226
|
+
if (r.operation) {
|
|
227
|
+
const key = `${proto}:${r.operation}`;
|
|
228
|
+
const list = byOperation.get(key);
|
|
229
|
+
if (list)
|
|
230
|
+
list.push(r);
|
|
231
|
+
else
|
|
232
|
+
byOperation.set(key, [r]);
|
|
233
|
+
}
|
|
234
|
+
if (r.topic) {
|
|
235
|
+
const list = byTopic.get(r.topic);
|
|
236
|
+
if (list)
|
|
237
|
+
list.push(r);
|
|
238
|
+
else
|
|
239
|
+
byTopic.set(r.topic, [r]);
|
|
240
|
+
}
|
|
241
|
+
if (r.queue) {
|
|
242
|
+
const list = byQueue.get(r.queue);
|
|
243
|
+
if (list)
|
|
244
|
+
list.push(r);
|
|
245
|
+
else
|
|
246
|
+
byQueue.set(r.queue, [r]);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const insertLink = store.makeServiceLinkInserter();
|
|
250
|
+
const byKind = {
|
|
251
|
+
literal_path: 0, env_base: 0, service_host: 0, route_pattern: 0,
|
|
252
|
+
trpc_procedure: 0, graphql_operation: 0, grpc_method: 0,
|
|
253
|
+
topic_match: 0, queue_match: 0, exchange_match: 0,
|
|
254
|
+
};
|
|
255
|
+
let considered = 0;
|
|
256
|
+
let inserted = 0;
|
|
257
|
+
let truncatedCalls = 0;
|
|
258
|
+
for (const c of calls) {
|
|
259
|
+
const candidates = [];
|
|
260
|
+
if (c.protocol === 'http') {
|
|
261
|
+
if (!c.normalized_path)
|
|
262
|
+
continue;
|
|
263
|
+
considered++;
|
|
264
|
+
// Known service host? Used as a confidence boost AND as the match_kind
|
|
265
|
+
// when the host carries strictly more signal than the path alone.
|
|
266
|
+
const knownHost = c.host_hint
|
|
267
|
+
? options.hostMap?.hosts.has(c.host_hint.toLowerCase())
|
|
268
|
+
: false;
|
|
269
|
+
const hostBoost = knownHost ? 1.05 : 1.0;
|
|
270
|
+
// Pass 1 — literal exact path matches (HTTP only).
|
|
271
|
+
const exacts = byExactPath.get(c.normalized_path) ?? [];
|
|
272
|
+
for (const r of exacts) {
|
|
273
|
+
// Only HTTP routes; tRPC operation could in theory collide with a path
|
|
274
|
+
// string but we filter by route.protocol to keep concerns separate.
|
|
275
|
+
if ((r.protocol ?? 'http') !== 'http')
|
|
276
|
+
continue;
|
|
277
|
+
const ms = methodMatchScore(c.method, r.method);
|
|
278
|
+
if (ms === 0)
|
|
279
|
+
continue;
|
|
280
|
+
const conf = Math.min(1.0, 0.95 * ms * hostBoost);
|
|
281
|
+
// Prefer 'service_host' when we have a known k8s/Docker host AND the
|
|
282
|
+
// call has no env_key — both the host name and a workspace route
|
|
283
|
+
// independently agree. Otherwise fall back to env_base / literal_path.
|
|
284
|
+
const matchKind = knownHost && !c.env_key
|
|
285
|
+
? 'service_host'
|
|
286
|
+
: (c.env_key ? 'env_base' : 'literal_path');
|
|
287
|
+
candidates.push({
|
|
288
|
+
route: r, confidence: conf, matchKind,
|
|
289
|
+
reason: knownHost ? 'literal_path+service_host' : 'literal_path',
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// Pass 2 — parameterised route matches. Only if no exact hit.
|
|
293
|
+
if (candidates.length === 0) {
|
|
294
|
+
for (const r of routes) {
|
|
295
|
+
if ((r.protocol ?? 'http') !== 'http')
|
|
296
|
+
continue;
|
|
297
|
+
const pm = routePatternsMatch(c.normalized_path, normalizeRoutePath(r.path));
|
|
298
|
+
if (!pm.matched)
|
|
299
|
+
continue;
|
|
300
|
+
const ms = methodMatchScore(c.method, r.method);
|
|
301
|
+
if (ms === 0)
|
|
302
|
+
continue;
|
|
303
|
+
const conf = Math.min(1.0, pm.confidence * ms * hostBoost);
|
|
304
|
+
candidates.push({
|
|
305
|
+
route: r, confidence: conf,
|
|
306
|
+
matchKind: knownHost && !c.env_key ? 'service_host' : 'route_pattern',
|
|
307
|
+
reason: knownHost ? `${pm.reason}+service_host` : pm.reason,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else if (c.protocol === 'trpc') {
|
|
313
|
+
if (!c.operation)
|
|
314
|
+
continue;
|
|
315
|
+
considered++;
|
|
316
|
+
// tRPC clients carry the full nested procedure path like 'user.getById';
|
|
317
|
+
// server-side routes carry only their immediate procedure key like
|
|
318
|
+
// 'getById'. We match either way: prefer exact full-path equality, then
|
|
319
|
+
// fall back to last-segment match.
|
|
320
|
+
const exacts = byOperation.get(`trpc:${c.operation}`) ?? [];
|
|
321
|
+
for (const r of exacts) {
|
|
322
|
+
const ms = methodMatchScore(c.method, r.method);
|
|
323
|
+
// tRPC method match: query vs query etc.; fall back to ANY-as-wildcard.
|
|
324
|
+
const conf = 0.95 * (ms === 0 ? 0.85 : ms);
|
|
325
|
+
candidates.push({
|
|
326
|
+
route: r, confidence: conf,
|
|
327
|
+
matchKind: 'trpc_procedure', reason: 'exact_operation',
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (candidates.length === 0) {
|
|
331
|
+
// Last-segment fallback: client 'user.getById' matches server route
|
|
332
|
+
// whose operation is 'getById' (a procedure inside a sub-router).
|
|
333
|
+
const lastSeg = c.operation.split('.').pop();
|
|
334
|
+
const tail = byOperation.get(`trpc:${lastSeg}`) ?? [];
|
|
335
|
+
for (const r of tail) {
|
|
336
|
+
const ms = methodMatchScore(c.method, r.method);
|
|
337
|
+
const conf = 0.7 * (ms === 0 ? 0.85 : ms);
|
|
338
|
+
candidates.push({
|
|
339
|
+
route: r, confidence: conf,
|
|
340
|
+
matchKind: 'trpc_procedure', reason: 'last_segment_operation',
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else if (c.protocol === 'graphql') {
|
|
346
|
+
// gql-doc sentinel rows: skip — they're document definitions, not calls.
|
|
347
|
+
if (c.framework === 'gql-doc')
|
|
348
|
+
continue;
|
|
349
|
+
if (!c.operation)
|
|
350
|
+
continue;
|
|
351
|
+
considered++;
|
|
352
|
+
// Resolve operation: prefer the parsed field name. If the operation is a
|
|
353
|
+
// known document-identifier (e.g. GET_USER) and we have a sentinel for
|
|
354
|
+
// it, use the sentinel's field name instead.
|
|
355
|
+
let effectiveOp = c.operation;
|
|
356
|
+
let effectiveMethod = c.method;
|
|
357
|
+
const doc = gqlDocMap.get(c.operation);
|
|
358
|
+
if (doc) {
|
|
359
|
+
effectiveOp = doc.operation;
|
|
360
|
+
if (!effectiveMethod && doc.method)
|
|
361
|
+
effectiveMethod = doc.method;
|
|
362
|
+
}
|
|
363
|
+
const exacts = byOperation.get(`graphql:${effectiveOp}`) ?? [];
|
|
364
|
+
for (const r of exacts) {
|
|
365
|
+
const ms = methodMatchScore(effectiveMethod, r.method);
|
|
366
|
+
const conf = 0.9 * (ms === 0 ? 0.85 : ms);
|
|
367
|
+
candidates.push({
|
|
368
|
+
route: r, confidence: conf,
|
|
369
|
+
matchKind: 'graphql_operation',
|
|
370
|
+
reason: doc ? 'gql_doc_field' : 'exact_operation',
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else if (c.protocol === 'grpc') {
|
|
375
|
+
// Match by service + method, encoded as 'Service/Method' or just 'Method'.
|
|
376
|
+
const op = c.operation;
|
|
377
|
+
if (!op)
|
|
378
|
+
continue;
|
|
379
|
+
considered++;
|
|
380
|
+
const exacts = byOperation.get(`grpc:${op}`) ?? [];
|
|
381
|
+
for (const r of exacts) {
|
|
382
|
+
candidates.push({
|
|
383
|
+
route: r, confidence: 0.95,
|
|
384
|
+
matchKind: 'grpc_method', reason: 'exact_grpc_method',
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
else if (c.protocol === 'kafka' || c.protocol === 'sns' ||
|
|
389
|
+
c.protocol === 'nats' || c.protocol === 'redis_pubsub') {
|
|
390
|
+
if (!c.topic)
|
|
391
|
+
continue;
|
|
392
|
+
considered++;
|
|
393
|
+
const consumers = byTopic.get(c.topic) ?? [];
|
|
394
|
+
for (const r of consumers) {
|
|
395
|
+
if ((r.protocol ?? '') !== c.protocol)
|
|
396
|
+
continue;
|
|
397
|
+
candidates.push({
|
|
398
|
+
route: r, confidence: 0.9,
|
|
399
|
+
matchKind: 'topic_match', reason: 'topic_match',
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
else if (c.protocol === 'sqs' || c.protocol === 'rabbitmq') {
|
|
404
|
+
if (!c.queue)
|
|
405
|
+
continue;
|
|
406
|
+
considered++;
|
|
407
|
+
const consumers = byQueue.get(c.queue) ?? [];
|
|
408
|
+
for (const r of consumers) {
|
|
409
|
+
if ((r.protocol ?? '') !== c.protocol)
|
|
410
|
+
continue;
|
|
411
|
+
candidates.push({
|
|
412
|
+
route: r, confidence: 0.9,
|
|
413
|
+
matchKind: 'queue_match', reason: 'queue_match',
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
// websocket / sse / unknown — skip for now; future plans can add them.
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (candidates.length === 0)
|
|
422
|
+
continue;
|
|
423
|
+
// Sort by (confidence DESC, route_id ASC, match_kind ASC) so the top pick
|
|
424
|
+
// — and the ambiguity ordering — is deterministic across runs. match_kind
|
|
425
|
+
// is a tertiary tie-break in case two routes have identical id+confidence
|
|
426
|
+
// (shouldn't happen, but defensive).
|
|
427
|
+
candidates.sort((a, b) => {
|
|
428
|
+
if (b.confidence !== a.confidence)
|
|
429
|
+
return b.confidence - a.confidence;
|
|
430
|
+
if (a.route.id !== b.route.id)
|
|
431
|
+
return a.route.id - b.route.id;
|
|
432
|
+
return a.matchKind < b.matchKind ? -1 : a.matchKind > b.matchKind ? 1 : 0;
|
|
433
|
+
});
|
|
434
|
+
// Cap candidates considered. Truncation is recorded in evidence so the
|
|
435
|
+
// agent can see the cutoff fired. Capping AFTER sorting preserves the
|
|
436
|
+
// best matches.
|
|
437
|
+
const totalCandidates = candidates.length;
|
|
438
|
+
let didTruncate = false;
|
|
439
|
+
if (candidates.length > MAX_CANDIDATES_PER_CALL) {
|
|
440
|
+
candidates.length = MAX_CANDIDATES_PER_CALL;
|
|
441
|
+
didTruncate = true;
|
|
442
|
+
truncatedCalls++;
|
|
443
|
+
}
|
|
444
|
+
const top = candidates[0];
|
|
445
|
+
const ambiguity = candidates.length > 1
|
|
446
|
+
? candidates.slice(1, 1 + MAX_EVIDENCE_CANDIDATES).map(x => ({
|
|
447
|
+
route_id: x.route.id,
|
|
448
|
+
confidence: Number(x.confidence.toFixed(3)),
|
|
449
|
+
reason: x.reason,
|
|
450
|
+
match_kind: x.matchKind,
|
|
451
|
+
}))
|
|
452
|
+
: [];
|
|
453
|
+
const evidence = JSON.stringify({
|
|
454
|
+
reason: top.reason,
|
|
455
|
+
method_call: c.method,
|
|
456
|
+
method_route: top.route.method,
|
|
457
|
+
env_key: c.env_key ?? null,
|
|
458
|
+
host_hint: c.host_hint ?? null,
|
|
459
|
+
raw_target: c.raw_target,
|
|
460
|
+
operation: c.operation ?? null,
|
|
461
|
+
topic: c.topic ?? null,
|
|
462
|
+
queue: c.queue ?? null,
|
|
463
|
+
service: c.service ?? null,
|
|
464
|
+
total_candidates: totalCandidates,
|
|
465
|
+
truncated: didTruncate,
|
|
466
|
+
ambiguity_candidates: ambiguity,
|
|
467
|
+
});
|
|
468
|
+
insertLink({
|
|
469
|
+
callId: c.id,
|
|
470
|
+
routeId: top.route.id,
|
|
471
|
+
callerSymbolId: c.symbol_id,
|
|
472
|
+
handlerSymbolId: top.route.handler_id,
|
|
473
|
+
protocol: c.protocol,
|
|
474
|
+
matchKind: top.matchKind,
|
|
475
|
+
confidence: Number(top.confidence.toFixed(3)),
|
|
476
|
+
evidenceJson: evidence,
|
|
477
|
+
});
|
|
478
|
+
byKind[top.matchKind] = (byKind[top.matchKind] ?? 0) + 1;
|
|
479
|
+
inserted++;
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
callsConsidered: considered,
|
|
483
|
+
linksInserted: inserted,
|
|
484
|
+
byKind: byKind,
|
|
485
|
+
truncated: truncatedCalls,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
/** Cheap PRAGMA-based column-existence check; used so the resolver can run
|
|
489
|
+
* unchanged against a pre-v9 DB shape. */
|
|
490
|
+
function hasColumn(raw, table, column) {
|
|
491
|
+
try {
|
|
492
|
+
const rows = raw.prepare(`PRAGMA table_info(${table})`).all();
|
|
493
|
+
return rows.some(r => r.name === column);
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/** Apply the same normalization to a route path as to a call path so the
|
|
500
|
+
* byExactPath comparison is symmetric (strip trailing slash, etc). */
|
|
501
|
+
function normalizeRoutePath(p) {
|
|
502
|
+
if (!p)
|
|
503
|
+
return '';
|
|
504
|
+
let s = p.trim();
|
|
505
|
+
if (s.length > 1 && s.endsWith('/'))
|
|
506
|
+
s = s.slice(0, -1);
|
|
507
|
+
return s;
|
|
508
|
+
}
|
|
509
|
+
//# sourceMappingURL=serviceLinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceLinks.js","sourceRoot":"","sources":["../../src/indexer/serviceLinks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Store } from '../db/store.js';
|
|
2
|
+
/** Tokens we recognize when computing the shape hash. */
|
|
3
|
+
type TokenKind = 'NAME' | 'NUMBER' | 'STRING' | 'KEYWORD' | 'OP';
|
|
4
|
+
interface ShapeToken {
|
|
5
|
+
kind: TokenKind;
|
|
6
|
+
/**
|
|
7
|
+
* For keywords/operators we keep the lexeme so `if` ≠ `for` ≠ `while`.
|
|
8
|
+
* For names/numbers/strings we drop the lexeme to fold them together —
|
|
9
|
+
* structure first, content second.
|
|
10
|
+
*/
|
|
11
|
+
text: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A tiny language-agnostic tokenizer. We don't need to be a full lexer —
|
|
15
|
+
* the goal is "structurally meaningful tokens that survive renames." A
|
|
16
|
+
* char-class scan over the source body suffices:
|
|
17
|
+
* - identifier-start runs → NAME (folded)
|
|
18
|
+
* - digit runs → NUMBER (folded)
|
|
19
|
+
* - quoted strings → STRING (folded; we just skip until the closing quote)
|
|
20
|
+
* - operators / punctuation → OP (lexeme kept)
|
|
21
|
+
*
|
|
22
|
+
* Keywords aren't language-specific in this tokenizer — they appear as NAME
|
|
23
|
+
* tokens. That's intentional: a Python `if` and a JS `if` have the same
|
|
24
|
+
* structural role, and at the shape-hash level we want them to collide.
|
|
25
|
+
* Real keywords still get distinguished from random identifiers because the
|
|
26
|
+
* SURROUNDING operators differ ( `if (` vs `def foo(` ).
|
|
27
|
+
*/
|
|
28
|
+
export declare function tokenize(source: string): ShapeToken[];
|
|
29
|
+
/**
|
|
30
|
+
* Compute the 64-bit structural SimHash over a function body source string.
|
|
31
|
+
* Returns NULL when the source is too small to be meaningfully compared.
|
|
32
|
+
*
|
|
33
|
+
* Algorithm (standard Charikar SimHash):
|
|
34
|
+
* 1. Tokenize, fold names/numbers/strings, keep operator lexemes.
|
|
35
|
+
* 2. Slide a 3-gram window over the tokens.
|
|
36
|
+
* 3. For each n-gram compute a stable 64-bit hash (FNV-1a is plenty here).
|
|
37
|
+
* 4. For each bit position, sum +1 if set in the gram-hash, -1 if unset.
|
|
38
|
+
* 5. Output bit i is 1 iff sum_i > 0.
|
|
39
|
+
*/
|
|
40
|
+
export declare function computeShapeHash(body: string, minTokens?: number): bigint | null;
|
|
41
|
+
/** Hamming distance between two 64-bit bigints. */
|
|
42
|
+
export declare function hammingDistance(a: bigint, b: bigint): number;
|
|
43
|
+
export interface BuildShapeHashResult {
|
|
44
|
+
symbolsHashed: number;
|
|
45
|
+
symbolsSkipped: number;
|
|
46
|
+
elapsedMs: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Compute shape hashes for every function-like symbol in the DB by reading
|
|
50
|
+
* its file and slicing out the body. We re-read each file once and slice all
|
|
51
|
+
* its function bodies in one pass.
|
|
52
|
+
*
|
|
53
|
+
* Idempotent: skips symbols that already have a shape_hash and whose file
|
|
54
|
+
* hash hasn't changed since the last pass (Store.upsertFileWithCache will
|
|
55
|
+
* have NULLed the column for re-parsed files automatically because the row
|
|
56
|
+
* gets deleted-and-reinserted).
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildShapeHashes(store: Store, options?: {
|
|
59
|
+
minLoc?: number;
|
|
60
|
+
force?: boolean;
|
|
61
|
+
log?: (m: string) => void;
|
|
62
|
+
}): BuildShapeHashResult;
|
|
63
|
+
export interface DuplicateCluster {
|
|
64
|
+
fingerprint: bigint;
|
|
65
|
+
symbols: Array<{
|
|
66
|
+
id: number;
|
|
67
|
+
name: string;
|
|
68
|
+
qualifiedName: string | null;
|
|
69
|
+
kind: string;
|
|
70
|
+
file: string;
|
|
71
|
+
lineStart: number;
|
|
72
|
+
lineEnd: number;
|
|
73
|
+
loc: number | null;
|
|
74
|
+
hammingFromAnchor: number;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
export interface FindDuplicatesOptions {
|
|
78
|
+
/** Maximum Hamming distance two symbols may differ to count as duplicates. */
|
|
79
|
+
maxDistance?: number;
|
|
80
|
+
/** Minimum LOC for a symbol to be considered. */
|
|
81
|
+
minLoc?: number;
|
|
82
|
+
/** Include test files. */
|
|
83
|
+
includeTests?: boolean;
|
|
84
|
+
/** Hard cap on clusters returned. */
|
|
85
|
+
maxClusters?: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Find clusters of structurally near-duplicate symbols.
|
|
89
|
+
*
|
|
90
|
+
* Implementation: pairwise Hamming distance over the candidate set. For
|
|
91
|
+
* codebases up to ~20k functions this stays well under a second; bigger
|
|
92
|
+
* codebases can pre-bucket on the top 16 bits of the hash (we don't do that
|
|
93
|
+
* here yet — the current scale works). The output is grouped into clusters
|
|
94
|
+
* via simple transitive-closure union-find on the (distance ≤ N) graph.
|
|
95
|
+
*/
|
|
96
|
+
export declare function findDuplicates(store: Store, options?: FindDuplicatesOptions): DuplicateCluster[];
|
|
97
|
+
export {};
|
|
98
|
+
//# sourceMappingURL=shapehash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shapehash.d.ts","sourceRoot":"","sources":["../../src/indexer/shapehash.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAiCvC,yDAAyD;AACzD,KAAK,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC;AAEjE,UAAU,UAAU;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE,CA0DrD;AASD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAI,GAAG,MAAM,GAAG,IAAI,CAoB3E;AAcD,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAC5E,oBAAoB,CAsDtB;AAUD,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,KAAK,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QACrE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QACrE,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,qBAA0B,GAChD,gBAAgB,EAAE,CAsEpB"}
|