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,304 @@
|
|
|
1
|
+
import { Store } from '../db/store.js';
|
|
2
|
+
import { rankedBehavior, BehaviorResult } from './behavior.js';
|
|
3
|
+
import type { SymbolRow } from '../types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Deterministic edit-risk profile for a symbol.
|
|
7
|
+
*
|
|
8
|
+
* Risk is decomposed: every signal that contributes is returned alongside
|
|
9
|
+
* the score so an agent can see WHY the verdict is "high" instead of
|
|
10
|
+
* trusting the number. The exit criteria for Track-E explicitly call this
|
|
11
|
+
* out: "seer_risk returns decomposed deterministic signals, not unexplained
|
|
12
|
+
* vibes."
|
|
13
|
+
*
|
|
14
|
+
* Signals (with deterministic weights):
|
|
15
|
+
* directCallers +1 per caller up to 30
|
|
16
|
+
* transitiveCallers +ln(1 + transitive) * 4 (bounded BFS, depth 3)
|
|
17
|
+
* routeExposed +20 if symbol is a route handler
|
|
18
|
+
* directTests -10 per direct test, capped at -30 (good coverage
|
|
19
|
+
* reduces risk)
|
|
20
|
+
* indirectTests -2 per indirect test, capped at -8
|
|
21
|
+
* assertionCount -2 per assertion across direct tests, capped at -10
|
|
22
|
+
* recentCommits +ln(1 + history.length) * 4
|
|
23
|
+
* distinctAuthors +3 per distinct author beyond 1, capped at +15
|
|
24
|
+
* cyclomatic max(0, cyclomatic - 8) * 1
|
|
25
|
+
* cognitive max(0, cognitive - 12) * 0.5
|
|
26
|
+
* configKeys +3 per distinct config key, capped at +15
|
|
27
|
+
* moduleBoundaryCrossings +2 per distinct neighboring module, capped at +20
|
|
28
|
+
*
|
|
29
|
+
* The score is bucketed into `low` (<20), `medium` (<50), or `high` (>=50).
|
|
30
|
+
* The thresholds are conservative — easy to bias toward "high" — so the
|
|
31
|
+
* agent gets shown evidence even when the verdict is in doubt.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
export interface RiskSignals {
|
|
35
|
+
directCallers: number;
|
|
36
|
+
transitiveCallers: number;
|
|
37
|
+
routeExposed: boolean;
|
|
38
|
+
routes: Array<{ method: string; path: string; framework: string }>;
|
|
39
|
+
directTests: number;
|
|
40
|
+
indirectTests: number;
|
|
41
|
+
assertionCount: number;
|
|
42
|
+
recentCommits: number;
|
|
43
|
+
distinctAuthors: number;
|
|
44
|
+
cyclomatic: number | null;
|
|
45
|
+
cognitive: number | null;
|
|
46
|
+
configKeys: number;
|
|
47
|
+
moduleBoundaryCrossings: number;
|
|
48
|
+
/**
|
|
49
|
+
* v8 Track-G — number of distinct outbound service calls this symbol makes
|
|
50
|
+
* (e.g. fetch/axios.get calls). Higher = symbol depends on more external
|
|
51
|
+
* services, which is a real risk surface for edits.
|
|
52
|
+
*/
|
|
53
|
+
outboundServiceCalls: number;
|
|
54
|
+
/**
|
|
55
|
+
* v8 Track-G — number of inbound service_links pointing at this symbol as
|
|
56
|
+
* the handler. Higher = changes here likely affect other services.
|
|
57
|
+
*/
|
|
58
|
+
inboundServiceLinks: number;
|
|
59
|
+
/**
|
|
60
|
+
* v8 Track-G — number of service_links whose handler lives in a different
|
|
61
|
+
* module than the caller. Cross-module client/handler dependency is a sign
|
|
62
|
+
* of architectural distance and risk.
|
|
63
|
+
*/
|
|
64
|
+
crossModuleServiceLinks: number;
|
|
65
|
+
/**
|
|
66
|
+
* v8 Track-G — service_links involving this symbol whose match_kind is
|
|
67
|
+
* route_pattern (not literal_path). Pattern-only matches carry residual
|
|
68
|
+
* ambiguity and are worth surfacing as a soft signal.
|
|
69
|
+
*/
|
|
70
|
+
ambiguousServiceLinks: number;
|
|
71
|
+
/**
|
|
72
|
+
* v10 — number of distinct neighboring boundaries this symbol's call graph
|
|
73
|
+
* reaches OUT of its own boundary. Advisory; never raises the verdict by
|
|
74
|
+
* itself.
|
|
75
|
+
*/
|
|
76
|
+
boundaryCrossings: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface RiskResult {
|
|
80
|
+
symbol: {
|
|
81
|
+
id: number;
|
|
82
|
+
name: string;
|
|
83
|
+
qualifiedName: string | null;
|
|
84
|
+
kind: string;
|
|
85
|
+
file: string;
|
|
86
|
+
lineStart: number;
|
|
87
|
+
lineEnd: number;
|
|
88
|
+
};
|
|
89
|
+
risk: 'low' | 'medium' | 'high';
|
|
90
|
+
score: number;
|
|
91
|
+
signals: RiskSignals;
|
|
92
|
+
signalContributions: Array<{ signal: string; value: number; contribution: number }>;
|
|
93
|
+
/** Module the symbol's file belongs to, when clustering has run. */
|
|
94
|
+
module: { id: number; label: string } | null;
|
|
95
|
+
/** v10 boundary the symbol's file belongs to. NULL when none detected. */
|
|
96
|
+
boundary: { id: number; label: string; kind: string; rootRelPath: string } | null;
|
|
97
|
+
source: 'tree-sitter';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface RiskOptions {
|
|
101
|
+
callerDepth?: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function computeRisk(
|
|
105
|
+
store: Store,
|
|
106
|
+
nameOrId: string | number,
|
|
107
|
+
options: RiskOptions = {},
|
|
108
|
+
): RiskResult | null {
|
|
109
|
+
const callerDepth = options.callerDepth ?? 3;
|
|
110
|
+
let target: SymbolRow | null = null;
|
|
111
|
+
if (typeof nameOrId === 'number') {
|
|
112
|
+
target = store.getSymbolById(nameOrId);
|
|
113
|
+
} else {
|
|
114
|
+
const defs = store.getDefinition(nameOrId);
|
|
115
|
+
if (defs.length === 0) return null;
|
|
116
|
+
target = defs[0];
|
|
117
|
+
}
|
|
118
|
+
if (!target) return null;
|
|
119
|
+
|
|
120
|
+
// Direct + transitive callers (call graph fan-in).
|
|
121
|
+
// ID-based count so short-name siblings (Alpha.run vs Beta.run) don't
|
|
122
|
+
// inflate this symbol's fan-in.
|
|
123
|
+
const directCallers = store.countCallersById(target.id);
|
|
124
|
+
const transitiveRows = store.reverseReachableWithDepth(target.id, callerDepth);
|
|
125
|
+
// Exclude direct callers from the transitive count so we don't double-count.
|
|
126
|
+
const transitiveCallers = transitiveRows.filter(r => r.depth > 1).length;
|
|
127
|
+
|
|
128
|
+
// Route exposure.
|
|
129
|
+
const routes = store.routesForHandler(target.id);
|
|
130
|
+
const routeExposed = routes.length > 0;
|
|
131
|
+
|
|
132
|
+
// Behavioral coverage — reuse the ranked behavior since it already does
|
|
133
|
+
// direct vs indirect deduplication and assertion counting.
|
|
134
|
+
const behavior = rankedBehavior(store, target.id, { limit: 200 });
|
|
135
|
+
const directTests = behavior?.direct ?? 0;
|
|
136
|
+
const indirectTests = behavior?.indirect ?? 0;
|
|
137
|
+
const assertionCount = sumAssertions(behavior);
|
|
138
|
+
|
|
139
|
+
// Symbol history.
|
|
140
|
+
const historyRows = store.getSymbolHistory(target.id, { limit: 200 });
|
|
141
|
+
const recentCommits = historyRows.length;
|
|
142
|
+
const distinctAuthors = new Set(
|
|
143
|
+
historyRows.map(h => (h.authorEmail ?? h.authorName ?? '').toLowerCase()).filter(s => s),
|
|
144
|
+
).size;
|
|
145
|
+
|
|
146
|
+
// Complexity.
|
|
147
|
+
const cyclomatic = target.cyclomatic ?? null;
|
|
148
|
+
const cognitive = target.cognitive ?? null;
|
|
149
|
+
|
|
150
|
+
// Config reads.
|
|
151
|
+
const configKeys = store.configKeysForSymbol(target.id).length;
|
|
152
|
+
|
|
153
|
+
// Module-boundary crossings: how many distinct neighbor modules this
|
|
154
|
+
// symbol's call graph reaches OUT of its own module.
|
|
155
|
+
const myFileModule = store.moduleForFile(target.fileId);
|
|
156
|
+
let moduleBoundaryCrossings = 0;
|
|
157
|
+
if (myFileModule) {
|
|
158
|
+
const neighborModules = new Set(
|
|
159
|
+
store.calleeModulesOf(target.id)
|
|
160
|
+
.filter(m => m.moduleId !== myFileModule.id)
|
|
161
|
+
.map(m => m.moduleId),
|
|
162
|
+
);
|
|
163
|
+
moduleBoundaryCrossings = neighborModules.size;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// v8 Track-G service-link signals.
|
|
167
|
+
let outboundServiceCalls = 0;
|
|
168
|
+
let inboundServiceLinks = 0;
|
|
169
|
+
let crossModuleServiceLinks = 0;
|
|
170
|
+
let ambiguousServiceLinks = 0;
|
|
171
|
+
try {
|
|
172
|
+
outboundServiceCalls = store.listServiceCalls({ callerSymbolId: target.id, limit: 1000 }).length;
|
|
173
|
+
} catch { /* */ }
|
|
174
|
+
try {
|
|
175
|
+
const inbound = store.serviceLinksForHandler(target.id, { limit: 1000 });
|
|
176
|
+
inboundServiceLinks = inbound.length;
|
|
177
|
+
ambiguousServiceLinks += inbound.filter(l => l.matchKind === 'route_pattern').length;
|
|
178
|
+
if (myFileModule) {
|
|
179
|
+
for (const link of inbound) {
|
|
180
|
+
if (link.callerSymbolId == null) continue;
|
|
181
|
+
const callerSym = store.getSymbolById(link.callerSymbolId);
|
|
182
|
+
if (!callerSym) continue;
|
|
183
|
+
const callerMod = store.moduleForFile(callerSym.fileId);
|
|
184
|
+
if (callerMod && callerMod.id !== myFileModule.id) crossModuleServiceLinks++;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch { /* */ }
|
|
188
|
+
try {
|
|
189
|
+
const outbound = store.serviceLinksForCaller(target.id, { limit: 1000 });
|
|
190
|
+
ambiguousServiceLinks += outbound.filter(l => l.matchKind === 'route_pattern').length;
|
|
191
|
+
if (myFileModule) {
|
|
192
|
+
for (const link of outbound) {
|
|
193
|
+
if (link.handlerSymbolId == null) continue;
|
|
194
|
+
const handlerSym = store.getSymbolById(link.handlerSymbolId);
|
|
195
|
+
if (!handlerSym) continue;
|
|
196
|
+
const handlerMod = store.moduleForFile(handlerSym.fileId);
|
|
197
|
+
if (handlerMod && handlerMod.id !== myFileModule.id) crossModuleServiceLinks++;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch { /* */ }
|
|
201
|
+
|
|
202
|
+
// v10 — boundary crossings (calls into another package/service boundary).
|
|
203
|
+
let boundaryCrossings = 0;
|
|
204
|
+
const myBoundary = store.boundaryForFile(target.fileId);
|
|
205
|
+
if (myBoundary) {
|
|
206
|
+
const neighborBoundaries = new Set(
|
|
207
|
+
store.calleeBoundariesOf(target.id)
|
|
208
|
+
.filter(b => b.boundaryId !== myBoundary.id)
|
|
209
|
+
.map(b => b.boundaryId),
|
|
210
|
+
);
|
|
211
|
+
boundaryCrossings = neighborBoundaries.size;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const signals: RiskSignals = {
|
|
215
|
+
directCallers, transitiveCallers, routeExposed, routes,
|
|
216
|
+
directTests, indirectTests, assertionCount,
|
|
217
|
+
recentCommits, distinctAuthors,
|
|
218
|
+
cyclomatic, cognitive,
|
|
219
|
+
configKeys, moduleBoundaryCrossings,
|
|
220
|
+
outboundServiceCalls, inboundServiceLinks,
|
|
221
|
+
crossModuleServiceLinks, ambiguousServiceLinks,
|
|
222
|
+
boundaryCrossings,
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const contributions = scoreContributions(signals);
|
|
226
|
+
const score = contributions.reduce((acc, c) => acc + c.contribution, 0);
|
|
227
|
+
const risk: 'low' | 'medium' | 'high' =
|
|
228
|
+
score >= 50 ? 'high' : score >= 20 ? 'medium' : 'low';
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
symbol: {
|
|
232
|
+
id: target.id,
|
|
233
|
+
name: target.name,
|
|
234
|
+
qualifiedName: target.qualifiedName,
|
|
235
|
+
kind: target.kind,
|
|
236
|
+
file: target.filePath,
|
|
237
|
+
lineStart: target.lineStart,
|
|
238
|
+
lineEnd: target.lineEnd,
|
|
239
|
+
},
|
|
240
|
+
risk, score, signals,
|
|
241
|
+
signalContributions: contributions,
|
|
242
|
+
module: myFileModule,
|
|
243
|
+
boundary: myBoundary,
|
|
244
|
+
source: 'tree-sitter',
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function sumAssertions(b: BehaviorResult | null): number {
|
|
249
|
+
if (!b) return 0;
|
|
250
|
+
let n = 0;
|
|
251
|
+
for (const t of b.tests) {
|
|
252
|
+
if (t.relationship === 'direct-call') n += t.assertionCount;
|
|
253
|
+
}
|
|
254
|
+
return n;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function scoreContributions(s: RiskSignals): Array<{ signal: string; value: number; contribution: number }> {
|
|
258
|
+
const out: Array<{ signal: string; value: number; contribution: number }> = [];
|
|
259
|
+
out.push({ signal: 'directCallers', value: s.directCallers,
|
|
260
|
+
contribution: Math.min(30, s.directCallers) });
|
|
261
|
+
out.push({ signal: 'transitiveCallers', value: s.transitiveCallers,
|
|
262
|
+
contribution: Math.log1p(s.transitiveCallers) * 4 });
|
|
263
|
+
out.push({ signal: 'routeExposed', value: s.routeExposed ? 1 : 0,
|
|
264
|
+
contribution: s.routeExposed ? 20 : 0 });
|
|
265
|
+
// Coverage reduces risk (negative contribution).
|
|
266
|
+
out.push({ signal: 'directTests', value: s.directTests,
|
|
267
|
+
contribution: -Math.min(30, s.directTests * 10) });
|
|
268
|
+
out.push({ signal: 'indirectTests', value: s.indirectTests,
|
|
269
|
+
contribution: -Math.min(8, s.indirectTests * 2) });
|
|
270
|
+
out.push({ signal: 'assertionCount', value: s.assertionCount,
|
|
271
|
+
contribution: -Math.min(10, s.assertionCount * 2) });
|
|
272
|
+
out.push({ signal: 'recentCommits', value: s.recentCommits,
|
|
273
|
+
contribution: Math.log1p(s.recentCommits) * 4 });
|
|
274
|
+
out.push({ signal: 'distinctAuthors', value: s.distinctAuthors,
|
|
275
|
+
contribution: s.distinctAuthors <= 1 ? 0 : Math.min(15, (s.distinctAuthors - 1) * 3) });
|
|
276
|
+
out.push({ signal: 'cyclomatic', value: s.cyclomatic ?? 0,
|
|
277
|
+
contribution: s.cyclomatic == null ? 0 : Math.max(0, s.cyclomatic - 8) });
|
|
278
|
+
out.push({ signal: 'cognitive', value: s.cognitive ?? 0,
|
|
279
|
+
contribution: s.cognitive == null ? 0 : Math.max(0, s.cognitive - 12) * 0.5 });
|
|
280
|
+
out.push({ signal: 'configKeys', value: s.configKeys,
|
|
281
|
+
contribution: Math.min(15, s.configKeys * 3) });
|
|
282
|
+
out.push({ signal: 'moduleBoundaryCrossings', value: s.moduleBoundaryCrossings,
|
|
283
|
+
contribution: Math.min(20, s.moduleBoundaryCrossings * 2) });
|
|
284
|
+
// v8 Track-G — service-link contributions. Conservative weights so they
|
|
285
|
+
// surface as evidence without dominating the score on small fixtures.
|
|
286
|
+
out.push({ signal: 'outboundServiceCalls', value: s.outboundServiceCalls,
|
|
287
|
+
contribution: Math.min(10, s.outboundServiceCalls * 2) });
|
|
288
|
+
out.push({ signal: 'inboundServiceLinks', value: s.inboundServiceLinks,
|
|
289
|
+
contribution: Math.min(15, s.inboundServiceLinks * 3) });
|
|
290
|
+
out.push({ signal: 'crossModuleServiceLinks', value: s.crossModuleServiceLinks,
|
|
291
|
+
contribution: Math.min(10, s.crossModuleServiceLinks * 2) });
|
|
292
|
+
out.push({ signal: 'ambiguousServiceLinks', value: s.ambiguousServiceLinks,
|
|
293
|
+
contribution: Math.min(5, s.ambiguousServiceLinks * 1) });
|
|
294
|
+
// v10 boundary crossings — advisory weight: 1.5 per crossing, capped at 12.
|
|
295
|
+
out.push({ signal: 'boundaryCrossings', value: s.boundaryCrossings,
|
|
296
|
+
contribution: Math.min(12, s.boundaryCrossings * 1.5) });
|
|
297
|
+
// Round contributions for stable output.
|
|
298
|
+
return out.map(c => ({ ...c, contribution: roundTo(c.contribution, 2) }));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function roundTo(n: number, decimals: number): number {
|
|
302
|
+
const f = Math.pow(10, decimals);
|
|
303
|
+
return Math.round(n * f) / f;
|
|
304
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v9 Track-H — extract service hostnames from Kubernetes manifests and
|
|
3
|
+
* Docker Compose files.
|
|
4
|
+
*
|
|
5
|
+
* The result is a {hostname → file_path[]} map: e.g. "payment-service" was
|
|
6
|
+
* declared in `k8s/payment.yaml`. The resolver uses this to boost confidence
|
|
7
|
+
* on HTTP service_calls whose host_hint matches one of these names — it's
|
|
8
|
+
* evidence, not proof. We only emit a `service_host` link when the call's
|
|
9
|
+
* PATH also matches a route in the workspace (host alone is too noisy).
|
|
10
|
+
*
|
|
11
|
+
* The scanner is intentionally regex-based (no YAML parser dependency) and
|
|
12
|
+
* forgiving — it extracts top-level `name:` values from any document that
|
|
13
|
+
* looks like a `kind: Service` declaration, plus the keys of `services:` in
|
|
14
|
+
* Docker Compose. Unknown YAMLs are skipped silently.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import glob from 'fast-glob';
|
|
20
|
+
|
|
21
|
+
export interface ServiceHostMap {
|
|
22
|
+
/** lowercase hostname → list of file paths where it was declared */
|
|
23
|
+
hosts: Map<string, string[]>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const K8S_SERVICE_RE =
|
|
27
|
+
/(?:^|\n)\s*kind\s*:\s*Service[\s\S]*?(?:^|\n)\s*metadata\s*:[\s\S]*?(?:^|\n)\s*name\s*:\s*["']?([A-Za-z0-9_-]+)["']?/g;
|
|
28
|
+
|
|
29
|
+
const COMPOSE_SERVICES_RE =
|
|
30
|
+
/(?:^|\n)services\s*:\s*\n((?:[ \t]+[A-Za-z0-9_-][^\n]*\n?)+)/g;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Scan a workspace for k8s/Docker service hostnames. Returns the empty map
|
|
34
|
+
* when none are found — callers should treat missing entries as "no boost".
|
|
35
|
+
*/
|
|
36
|
+
export async function scanServiceHosts(absRoot: string): Promise<ServiceHostMap> {
|
|
37
|
+
const entries = await glob([
|
|
38
|
+
'**/*.yaml', '**/*.yml',
|
|
39
|
+
'**/docker-compose*.yml', '**/docker-compose*.yaml',
|
|
40
|
+
], {
|
|
41
|
+
cwd: absRoot,
|
|
42
|
+
ignore: [
|
|
43
|
+
'node_modules/**', '.git/**', 'dist/**', 'build/**', 'out/**',
|
|
44
|
+
'vendor/**', '**/vendor/**', '**/__pycache__/**', '.next/**',
|
|
45
|
+
],
|
|
46
|
+
onlyFiles: true, followSymbolicLinks: false, dot: false,
|
|
47
|
+
});
|
|
48
|
+
entries.sort();
|
|
49
|
+
|
|
50
|
+
const hosts = new Map<string, string[]>();
|
|
51
|
+
const record = (name: string, filePath: string) => {
|
|
52
|
+
if (!name) return;
|
|
53
|
+
const key = name.toLowerCase();
|
|
54
|
+
const list = hosts.get(key);
|
|
55
|
+
if (list) { if (!list.includes(filePath)) list.push(filePath); }
|
|
56
|
+
else hosts.set(key, [filePath]);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
for (const rel of entries) {
|
|
60
|
+
const abs = path.join(absRoot, rel);
|
|
61
|
+
let src: string;
|
|
62
|
+
try { src = fs.readFileSync(abs, 'utf8'); }
|
|
63
|
+
catch { continue; }
|
|
64
|
+
|
|
65
|
+
// ── Kubernetes `kind: Service` ──────────────────────────────────────
|
|
66
|
+
K8S_SERVICE_RE.lastIndex = 0;
|
|
67
|
+
let m: RegExpExecArray | null;
|
|
68
|
+
while ((m = K8S_SERVICE_RE.exec(src)) !== null) record(m[1], rel);
|
|
69
|
+
|
|
70
|
+
// ── Docker Compose `services:` ─────────────────────────────────────
|
|
71
|
+
const isCompose = /(?:^|[/\\])docker-compose[^/\\]*\.ya?ml$/i.test(rel) ||
|
|
72
|
+
/(?:^|\n)version\s*:/m.test(src) && /(?:^|\n)services\s*:/m.test(src);
|
|
73
|
+
if (isCompose) {
|
|
74
|
+
COMPOSE_SERVICES_RE.lastIndex = 0;
|
|
75
|
+
let s: RegExpExecArray | null;
|
|
76
|
+
while ((s = COMPOSE_SERVICES_RE.exec(src)) !== null) {
|
|
77
|
+
const block = s[1];
|
|
78
|
+
// Pull top-level service keys: lines like " name:" with 2-4 space indent.
|
|
79
|
+
const lines = block.split('\n');
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
const km = line.match(/^([ \t]+)([A-Za-z0-9_-]+)\s*:/);
|
|
82
|
+
if (!km) continue;
|
|
83
|
+
// Only first-level keys (consistent indent across the services block).
|
|
84
|
+
// We accept indent ≤ 4 spaces / 1 tab.
|
|
85
|
+
if (km[1].length <= 4) record(km[2], rel);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { hosts };
|
|
92
|
+
}
|