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
package/tests/trackcd.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track C + D feature tests.
|
|
3
|
+
*
|
|
4
|
+
* Indexes `tests/fixtures-trackcd/` once and exercises every new feature:
|
|
5
|
+
* - route extraction (Express, FastAPI, Flask, Spring)
|
|
6
|
+
* - env/config key extraction (process.env, os.getenv, System.getenv)
|
|
7
|
+
* - external dependency parsing (package.json, Cargo.toml, requirements.txt, go.mod)
|
|
8
|
+
* - complexity metrics (cyclomatic, cognitive, LOC, max_nesting)
|
|
9
|
+
* - test edge synthesis
|
|
10
|
+
* - FTS5 BM25 symbol search with camelCase/snake_case splitting
|
|
11
|
+
* - architecture aggregate
|
|
12
|
+
* - graph trace_path
|
|
13
|
+
*
|
|
14
|
+
* Run with: npx tsx tests/trackcd.ts
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import os from 'os';
|
|
20
|
+
import { Indexer } from '../src/indexer/index';
|
|
21
|
+
import { Store, ftsQuery, splitIdentifierTokens, makeSymbolKey } from '../src/db/store';
|
|
22
|
+
import { buildArchitecture } from '../src/indexer/architecture';
|
|
23
|
+
|
|
24
|
+
const FIXTURES = path.join(__dirname, 'fixtures-trackcd');
|
|
25
|
+
const TMP_DB = path.join(os.tmpdir(), `seer-trackcd-${Date.now()}.db`);
|
|
26
|
+
|
|
27
|
+
let passed = 0;
|
|
28
|
+
let failed = 0;
|
|
29
|
+
|
|
30
|
+
function assert(cond: boolean, msg: string): void {
|
|
31
|
+
if (cond) { console.log(` ✓ ${msg}`); passed++; }
|
|
32
|
+
else { console.error(` ✗ ${msg}`); failed++; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function assertEq<T>(actual: T, expected: T, msg: string): void {
|
|
36
|
+
assert(actual === expected, `${msg} (got ${JSON.stringify(actual)}, expected ${JSON.stringify(expected)})`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function assertContains(haystack: string[], needle: string, msg: string): void {
|
|
40
|
+
assert(haystack.includes(needle), `${msg} (in ${JSON.stringify(haystack)})`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function run(): Promise<void> {
|
|
44
|
+
console.log('\nSeer Track C + D Feature Tests');
|
|
45
|
+
console.log('================================\n');
|
|
46
|
+
|
|
47
|
+
if (!fs.existsSync(FIXTURES)) {
|
|
48
|
+
console.error(`Missing fixtures dir: ${FIXTURES}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const store = new Store(TMP_DB);
|
|
53
|
+
const indexer = new Indexer(store);
|
|
54
|
+
console.log(`Indexing ${FIXTURES}...`);
|
|
55
|
+
const r = await indexer.indexDirectory(FIXTURES, { quiet: true });
|
|
56
|
+
console.log(` files=${r.filesIndexed} symbols=${r.symbols} edges=${r.edges} extDeps=${r.externalDependencies} testEdges=${r.testEdgesAdded}\n`);
|
|
57
|
+
|
|
58
|
+
// ── Schema version ────────────────────────────────────────────────────────
|
|
59
|
+
console.log('── Schema ──');
|
|
60
|
+
const schema = store.schemaInfo();
|
|
61
|
+
assertEq(schema.current, true, 'schema is current');
|
|
62
|
+
assertEq(schema.dbVersion, 10, `schema version is v10`);
|
|
63
|
+
|
|
64
|
+
// ── Helper: tokenizer ─────────────────────────────────────────────────────
|
|
65
|
+
console.log('\n── Identifier tokenizer ──');
|
|
66
|
+
const t1 = splitIdentifierTokens('AuthServiceImpl').split(' ').sort();
|
|
67
|
+
assertContains(t1, 'authserviceimpl', 'tokenizer keeps original');
|
|
68
|
+
assertContains(t1, 'auth', 'camelCase split: Auth');
|
|
69
|
+
assertContains(t1, 'service', 'camelCase split: Service');
|
|
70
|
+
assertContains(t1, 'impl', 'camelCase split: Impl');
|
|
71
|
+
const t2 = splitIdentifierTokens('XMLParser').split(' ').sort();
|
|
72
|
+
assertContains(t2, 'xml', 'caps-acronym split: XML');
|
|
73
|
+
assertContains(t2, 'parser', 'caps-acronym split: Parser');
|
|
74
|
+
const t3 = splitIdentifierTokens('user_repo_manager').split(' ').sort();
|
|
75
|
+
assertContains(t3, 'user', 'snake_case split: user');
|
|
76
|
+
assertContains(t3, 'repo', 'snake_case split: repo');
|
|
77
|
+
assertContains(t3, 'manager', 'snake_case split: manager');
|
|
78
|
+
|
|
79
|
+
// ── Symbol key ────────────────────────────────────────────────────────────
|
|
80
|
+
console.log('\n── Symbol key ──');
|
|
81
|
+
assertEq(makeSymbolKey('function', 'foo'), 'function:foo', 'makeSymbolKey basic');
|
|
82
|
+
assertEq(makeSymbolKey('method', 'Auth.login'), 'method:Auth.login', 'makeSymbolKey qualified');
|
|
83
|
+
|
|
84
|
+
// ── FTS5 query builder ────────────────────────────────────────────────────
|
|
85
|
+
console.log('\n── FTS query builder ──');
|
|
86
|
+
const q = ftsQuery('AuthService');
|
|
87
|
+
assert(q !== null && q.includes('"auth"*'), `ftsQuery splits camelCase: ${q}`);
|
|
88
|
+
const q2 = ftsQuery('');
|
|
89
|
+
assertEq(q2, null, 'empty query → null');
|
|
90
|
+
|
|
91
|
+
// ── Route extraction ──────────────────────────────────────────────────────
|
|
92
|
+
console.log('\n── Route extraction ──');
|
|
93
|
+
const routes = store.listRoutes({ limit: 100 });
|
|
94
|
+
console.log(` Total routes: ${routes.length}`);
|
|
95
|
+
for (const r of routes) console.log(` ${r.method} ${r.path} (${r.framework}) → ${r.handlerSymbol ?? r.handlerName ?? '(none)'}`);
|
|
96
|
+
|
|
97
|
+
// Express
|
|
98
|
+
assert(routes.some(r => r.method === 'GET' && r.path === '/users' && r.framework === 'express'),
|
|
99
|
+
'Express GET /users detected');
|
|
100
|
+
assert(routes.some(r => r.method === 'POST' && r.path === '/users' && r.framework === 'express'),
|
|
101
|
+
'Express POST /users detected');
|
|
102
|
+
assert(routes.some(r => r.method === 'DELETE' && r.path === '/users/:id' && r.framework === 'express'),
|
|
103
|
+
'Express DELETE /users/:id detected');
|
|
104
|
+
assert(routes.some(r => r.method === 'PUT' && r.path === '/users/:id' && r.framework === 'express'),
|
|
105
|
+
'Express PUT /users/:id (inline handler) detected');
|
|
106
|
+
|
|
107
|
+
// Express handler resolution
|
|
108
|
+
const listUsersRoute = routes.find(r => r.method === 'GET' && r.path === '/users' && r.framework === 'express');
|
|
109
|
+
assert(listUsersRoute?.handlerName === 'listUsers', `Express handler name = listUsers`);
|
|
110
|
+
assert(listUsersRoute?.handlerId !== null, `Express handler id resolved`);
|
|
111
|
+
|
|
112
|
+
// FastAPI
|
|
113
|
+
assert(routes.some(r => r.method === 'GET' && r.path === '/items/{item_id}' && r.framework === 'fastapi'),
|
|
114
|
+
'FastAPI GET /items/{item_id} detected');
|
|
115
|
+
assert(routes.some(r => r.method === 'POST' && r.path === '/items' && r.framework === 'fastapi'),
|
|
116
|
+
'FastAPI POST /items detected');
|
|
117
|
+
const readItemRoute = routes.find(r => r.method === 'GET' && r.framework === 'fastapi');
|
|
118
|
+
assert(readItemRoute?.handlerName === 'read_item', `FastAPI handler name = read_item`);
|
|
119
|
+
assert(readItemRoute?.handlerId !== null, `FastAPI handler id resolved`);
|
|
120
|
+
|
|
121
|
+
// Flask
|
|
122
|
+
assert(routes.some(r => r.method === 'GET' && r.path === '/health' && r.framework === 'flask'),
|
|
123
|
+
'Flask GET /health detected');
|
|
124
|
+
assert(routes.some(r => r.method === 'GET' && r.path === '/users' && r.framework === 'flask'),
|
|
125
|
+
'Flask GET /users detected (methods=GET,POST)');
|
|
126
|
+
assert(routes.some(r => r.method === 'POST' && r.path === '/users' && r.framework === 'flask'),
|
|
127
|
+
'Flask POST /users detected (methods=GET,POST)');
|
|
128
|
+
|
|
129
|
+
// Spring — class-level @RequestMapping("/api") is a prefix; method routes
|
|
130
|
+
// should be concatenated, and the class-level annotation must NOT emit a
|
|
131
|
+
// route on its own.
|
|
132
|
+
assert(routes.some(r => r.method === 'GET' && r.path === '/api/users' && r.framework === 'spring'),
|
|
133
|
+
'Spring GET /api/users detected (class prefix applied)');
|
|
134
|
+
assert(routes.some(r => r.method === 'POST' && r.path === '/api/users' && r.framework === 'spring'),
|
|
135
|
+
'Spring POST /api/users detected (class prefix applied)');
|
|
136
|
+
assert(routes.some(r => r.method === 'DELETE' && r.path === '/api/users/{id}' && r.framework === 'spring'),
|
|
137
|
+
'Spring DELETE /api/users/{id} detected (class prefix applied)');
|
|
138
|
+
assert(routes.some(r => r.method === 'GET' && r.path === '/api/items' && r.framework === 'spring'),
|
|
139
|
+
'Spring @RequestMapping method=GET → /api/items detected (class prefix applied)');
|
|
140
|
+
// Regression: the class-level annotation itself must NOT emit a bare /api
|
|
141
|
+
// route. This was the symptom of the pre-fix bug — the extractor produced
|
|
142
|
+
// a phantom `GET /api → (no handler)` row.
|
|
143
|
+
assert(!routes.some(r => r.framework === 'spring' && r.path === '/api'),
|
|
144
|
+
'Spring class-level @RequestMapping("/api") does NOT emit a bogus /api route');
|
|
145
|
+
|
|
146
|
+
// Route filters
|
|
147
|
+
const onlyGet = store.listRoutes({ method: 'GET' });
|
|
148
|
+
assert(onlyGet.every(r => r.method === 'GET'), 'method filter works');
|
|
149
|
+
const onlySpring = store.listRoutes({ framework: 'spring' });
|
|
150
|
+
assert(onlySpring.every(r => r.framework === 'spring'), 'framework filter works');
|
|
151
|
+
|
|
152
|
+
// ── Config / env extraction ──────────────────────────────────────────────
|
|
153
|
+
console.log('\n── Config / env extraction ──');
|
|
154
|
+
const configs = store.listConfigKeys({ limit: 200 });
|
|
155
|
+
console.log(` Total config reads: ${configs.length}`);
|
|
156
|
+
const keys = configs.map(c => c.key).sort();
|
|
157
|
+
console.log(` Keys: ${JSON.stringify(keys)}`);
|
|
158
|
+
|
|
159
|
+
assertContains(keys, 'DATABASE_URL', 'process.env.DATABASE_URL extracted');
|
|
160
|
+
assertContains(keys, 'TIMEOUT_MS', 'process.env["TIMEOUT_MS"] extracted');
|
|
161
|
+
assertContains(keys, 'FEATURE_FLAG', 'os.environ.get FEATURE_FLAG extracted');
|
|
162
|
+
assertContains(keys, 'SECRET_KEY', 'os.environ[SECRET_KEY] extracted');
|
|
163
|
+
|
|
164
|
+
// Symbol-id backfill: most config reads should have an enclosing symbol.
|
|
165
|
+
const enclosedReads = configs.filter(c => c.symbolId !== null);
|
|
166
|
+
assert(enclosedReads.length > 0, `at least one config read has resolved enclosing symbol`);
|
|
167
|
+
// listUsers's DATABASE_URL must be attributed to listUsers.
|
|
168
|
+
const inListUsers = configs.find(c => c.key === 'DATABASE_URL' && c.symbolName === 'listUsers');
|
|
169
|
+
assert(inListUsers !== undefined, `DATABASE_URL in listUsers attributed correctly`);
|
|
170
|
+
// get_db_url contains os.getenv("DATABASE_URL")
|
|
171
|
+
const inGetDbUrl = configs.find(c => c.key === 'DATABASE_URL' && c.symbolName === 'get_db_url');
|
|
172
|
+
assert(inGetDbUrl !== undefined, 'os.getenv("DATABASE_URL") attributed to get_db_url');
|
|
173
|
+
|
|
174
|
+
// ── External dependency extraction ────────────────────────────────────────
|
|
175
|
+
console.log('\n── External deps ──');
|
|
176
|
+
const npm = store.listExternalDeps({ ecosystem: 'npm' });
|
|
177
|
+
console.log(` npm: ${npm.map(d => `${d.name}@${d.versionRange ?? ''}${d.isDev ? '*dev' : ''}`).join(', ')}`);
|
|
178
|
+
assert(npm.some(d => d.name === 'express'), 'package.json express dep');
|
|
179
|
+
assert(npm.some(d => d.name === 'lodash'), 'package.json lodash dep');
|
|
180
|
+
assert(npm.some(d => d.name === 'jest' && d.isDev === 1), 'package.json jest dev dep');
|
|
181
|
+
assert(npm.some(d => d.name === 'react'), 'package.json react peer dep');
|
|
182
|
+
|
|
183
|
+
const cargo = store.listExternalDeps({ ecosystem: 'cargo' });
|
|
184
|
+
console.log(` cargo: ${cargo.map(d => `${d.name}@${d.versionRange ?? ''}`).join(', ')}`);
|
|
185
|
+
assert(cargo.some(d => d.name === 'serde'), 'Cargo.toml serde');
|
|
186
|
+
assert(cargo.some(d => d.name === 'tokio' && d.versionRange === '1.0'), 'Cargo.toml tokio with version');
|
|
187
|
+
assert(cargo.some(d => d.name === 'criterion' && d.isDev === 1), 'Cargo.toml criterion dev dep');
|
|
188
|
+
|
|
189
|
+
const pypi = store.listExternalDeps({ ecosystem: 'pypi' });
|
|
190
|
+
console.log(` pypi: ${pypi.map(d => `${d.name}@${d.versionRange ?? ''}`).join(', ')}`);
|
|
191
|
+
assert(pypi.some(d => d.name === 'requests'), 'requirements.txt requests');
|
|
192
|
+
assert(pypi.some(d => d.name === 'fastapi' && (d.versionRange ?? '').includes('0.104.0')), 'requirements.txt fastapi version');
|
|
193
|
+
|
|
194
|
+
const goDeps = store.listExternalDeps({ ecosystem: 'go' });
|
|
195
|
+
console.log(` go: ${goDeps.map(d => `${d.name}@${d.versionRange ?? ''}`).join(', ')}`);
|
|
196
|
+
assert(goDeps.some(d => d.name === 'github.com/spf13/cobra'), 'go.mod cobra dep');
|
|
197
|
+
assert(goDeps.some(d => d.name === 'github.com/stretchr/testify'), 'go.mod testify dep');
|
|
198
|
+
|
|
199
|
+
// ── Complexity ────────────────────────────────────────────────────────────
|
|
200
|
+
console.log('\n── Complexity ──');
|
|
201
|
+
const simple = store.findSymbols('simple_function').filter(s => s.kind === 'function');
|
|
202
|
+
assert(simple.length >= 1, 'simple_function indexed');
|
|
203
|
+
assert(simple[0].cyclomatic === 1, `simple_function cyclomatic = 1 (got ${simple[0].cyclomatic})`);
|
|
204
|
+
|
|
205
|
+
const branchy = store.findSymbols('branchy_function').filter(s => s.kind === 'function');
|
|
206
|
+
assert(branchy.length >= 1, 'branchy_function indexed');
|
|
207
|
+
assert((branchy[0].cyclomatic ?? 0) >= 5, `branchy_function cyclomatic ≥ 5 (got ${branchy[0].cyclomatic})`);
|
|
208
|
+
assert((branchy[0].maxNesting ?? 0) >= 3, `branchy_function max_nesting ≥ 3 (got ${branchy[0].maxNesting})`);
|
|
209
|
+
|
|
210
|
+
const loopy = store.findSymbols('loopy_function').filter(s => s.kind === 'function');
|
|
211
|
+
assert(loopy.length >= 1, 'loopy_function indexed');
|
|
212
|
+
assert((loopy[0].cyclomatic ?? 0) >= 5, `loopy_function cyclomatic ≥ 5 (got ${loopy[0].cyclomatic})`);
|
|
213
|
+
assert((loopy[0].cognitive ?? 0) > (loopy[0].cyclomatic ?? 0), `loopy_function cognitive > cyclomatic (nested branches cost more)`);
|
|
214
|
+
assert((loopy[0].loc ?? 0) >= 10, `loopy_function loc ≥ 10 (got ${loopy[0].loc})`);
|
|
215
|
+
|
|
216
|
+
// Calculator.divide — multiple ifs, raise. Python extractor uses 'function' for methods too.
|
|
217
|
+
const divide = store.findSymbols('divide');
|
|
218
|
+
assert(divide.length >= 1, 'Calculator.divide indexed');
|
|
219
|
+
assert((divide[0].cyclomatic ?? 0) >= 4, `divide cyclomatic ≥ 4 (got ${divide[0].cyclomatic})`);
|
|
220
|
+
|
|
221
|
+
// Class itself is not "rankable for complexity" — classes don't carry metrics
|
|
222
|
+
const calcClass = store.findSymbols('Calculator').filter(s => s.kind === 'class');
|
|
223
|
+
assert(calcClass.length >= 1, 'Calculator class indexed');
|
|
224
|
+
assert(calcClass[0].cyclomatic == null, 'classes have null complexity');
|
|
225
|
+
|
|
226
|
+
// ── Test edge synthesis ──────────────────────────────────────────────────
|
|
227
|
+
console.log('\n── Test edge synthesis ──');
|
|
228
|
+
// The fixture has tests/auth_service.test.ts calling AuthService.login and validateCredentials.
|
|
229
|
+
// synthesizeTestEdges() should have populated edges of kind='tests' from those calls.
|
|
230
|
+
const testCount = store.rawDb().prepare("SELECT COUNT(*) AS c FROM edges WHERE kind = 'tests'").get() as { c: number };
|
|
231
|
+
console.log(` Total 'tests' edges: ${testCount.c}`);
|
|
232
|
+
assert(testCount.c >= 2, `at least 2 tests edges synthesized (got ${testCount.c})`);
|
|
233
|
+
|
|
234
|
+
const loginTestEdges = store.rawDb().prepare(`
|
|
235
|
+
SELECT s.name AS caller, e.to_name AS callee, f.role AS fromRole
|
|
236
|
+
FROM edges e JOIN symbols s ON s.id = e.from_id JOIN files f ON f.id = s.file_id
|
|
237
|
+
WHERE e.kind = 'tests' AND e.to_name = ?
|
|
238
|
+
`).all('login') as Array<{ caller: string; callee: string; fromRole: string }>;
|
|
239
|
+
assert(loginTestEdges.length >= 1, `tests edge: login() exercised by a test`);
|
|
240
|
+
assert(loginTestEdges.every(t => t.fromRole === 'test'), 'all tests edges come from test files');
|
|
241
|
+
|
|
242
|
+
// ── FTS5 BM25 search ──────────────────────────────────────────────────────
|
|
243
|
+
console.log('\n── FTS5 BM25 search ──');
|
|
244
|
+
const authHits = store.searchSymbolsFts('auth', { limit: 20 });
|
|
245
|
+
console.log(` searchSymbolsFts('auth') → ${authHits.length} hits`);
|
|
246
|
+
assert(authHits.some(h => h.name === 'AuthService'), 'FTS finds AuthService for "auth"');
|
|
247
|
+
|
|
248
|
+
// camelCase split lets 'service' match AuthService too
|
|
249
|
+
const serviceHits = store.searchSymbolsFts('service', { limit: 20 });
|
|
250
|
+
assert(serviceHits.some(h => h.name === 'AuthService'), 'FTS camelCase split: "service" → AuthService');
|
|
251
|
+
|
|
252
|
+
// snake_case split lets 'validate' match validate_credentials and validateCredentials
|
|
253
|
+
const validateHits = store.searchSymbolsFts('validate', { limit: 20 });
|
|
254
|
+
assert(validateHits.some(h => h.name === 'validateCredentials'),
|
|
255
|
+
`FTS finds validateCredentials for "validate" (camel-split)`);
|
|
256
|
+
|
|
257
|
+
// File-path FTS
|
|
258
|
+
const fileHits = store.searchFilesFts('auth');
|
|
259
|
+
assert(fileHits.some(f => f.relPath.toLowerCase().includes('auth')),
|
|
260
|
+
`searchFilesFts('auth') finds auth_service files`);
|
|
261
|
+
|
|
262
|
+
// ── Architecture aggregate ────────────────────────────────────────────────
|
|
263
|
+
console.log('\n── Architecture aggregate ──');
|
|
264
|
+
const arch = buildArchitecture(FIXTURES, store);
|
|
265
|
+
console.log(` Languages: ${arch.languages.map(l => `${l.language}=${l.files}`).join(', ')}`);
|
|
266
|
+
console.log(` Total routes: ${arch.totals.routes}, deps: ${arch.totals.externalDependencies}, configKeys: ${arch.totals.configKeys}`);
|
|
267
|
+
console.log(` Routes by framework: ${JSON.stringify(arch.routes.byFramework)}`);
|
|
268
|
+
console.log(` Top modules: ${arch.topModules.map(m => `${m.name}(${m.files})`).join(', ')}`);
|
|
269
|
+
console.log(` External deps (top 5): ${arch.externalDependencies.slice(0,5).map(d => d.name).join(', ')}`);
|
|
270
|
+
|
|
271
|
+
assert(arch.totals.routes >= 10, `architecture: ≥10 routes`);
|
|
272
|
+
assert(arch.totals.externalDependencies >= 8, `architecture: ≥8 external deps`);
|
|
273
|
+
assert(arch.totals.configKeys >= 4, `architecture: ≥4 config keys`);
|
|
274
|
+
assert(arch.languages.length >= 3, `architecture: ≥3 languages`);
|
|
275
|
+
assert(arch.routes.byFramework['express'] >= 1, 'architecture: express framework counted');
|
|
276
|
+
assert(arch.routes.byFramework['fastapi'] >= 1, 'architecture: fastapi framework counted');
|
|
277
|
+
assert(arch.routes.byFramework['spring'] >= 1, 'architecture: spring framework counted');
|
|
278
|
+
assert(arch.routes.byFramework['flask'] >= 1, 'architecture: flask framework counted');
|
|
279
|
+
|
|
280
|
+
// ── Graph trace_path ──────────────────────────────────────────────────────
|
|
281
|
+
console.log('\n── Graph trace_path ──');
|
|
282
|
+
// login → validateCredentials direct path (login calls validateCredentials)
|
|
283
|
+
const loginDef = store.getDefinition('login').find(s => s.kind === 'method');
|
|
284
|
+
const valDef = store.getDefinition('validateCredentials');
|
|
285
|
+
assert(loginDef !== undefined && valDef.length > 0, 'fixtures: login and validateCredentials defined');
|
|
286
|
+
const tracePath = store.tracePath(loginDef!.id, valDef[0].id, 4);
|
|
287
|
+
console.log(` tracePath login→validateCredentials = ${tracePath?.map(n => n.name).join(' → ')}`);
|
|
288
|
+
assert(tracePath !== null, 'trace_path finds login → validateCredentials');
|
|
289
|
+
assert(tracePath![0].name === 'login' && tracePath![tracePath!.length - 1].name === 'validateCredentials',
|
|
290
|
+
'trace_path endpoints correct');
|
|
291
|
+
|
|
292
|
+
// Identity path: a→a is a single node
|
|
293
|
+
const selfPath = store.tracePath(loginDef!.id, loginDef!.id, 1);
|
|
294
|
+
assert(selfPath !== null && selfPath.length === 1, 'trace_path(a, a) → single node');
|
|
295
|
+
|
|
296
|
+
// Unreachable path
|
|
297
|
+
const reverseBadPath = store.tracePath(valDef[0].id, loginDef!.id, 3);
|
|
298
|
+
// validateCredentials doesn't call login, so the path should be null.
|
|
299
|
+
assert(reverseBadPath === null, 'trace_path: no path validateCredentials → login');
|
|
300
|
+
|
|
301
|
+
// reverseReachable: who transitively calls validateCredentials?
|
|
302
|
+
const callers = store.reverseReachable(valDef[0].id, 3);
|
|
303
|
+
assert(callers.length >= 1, `reverseReachable(validateCredentials) finds ≥1 caller`);
|
|
304
|
+
|
|
305
|
+
// ── symbolsTouchingLines ──────────────────────────────────────────────────
|
|
306
|
+
console.log('\n── symbolsTouchingLines ──');
|
|
307
|
+
const authFile = store.listFiles().find(f => f.relPath.endsWith('auth_service.ts'))!;
|
|
308
|
+
assert(authFile !== undefined, 'auth_service.ts indexed');
|
|
309
|
+
// Pull syms whose range overlaps line 5 (somewhere inside login).
|
|
310
|
+
const touching = store.symbolsTouchingLines(authFile.id, [[2, 6]]);
|
|
311
|
+
assert(touching.some(s => s.name === 'AuthService' || s.name === 'login'),
|
|
312
|
+
'symbolsTouchingLines finds AuthService or login at lines 2-6');
|
|
313
|
+
|
|
314
|
+
// ── Cleanup ───────────────────────────────────────────────────────────────
|
|
315
|
+
store.close();
|
|
316
|
+
if (fs.existsSync(TMP_DB)) fs.unlinkSync(TMP_DB);
|
|
317
|
+
['-wal','-shm'].forEach(suf => { try { fs.unlinkSync(TMP_DB + suf); } catch { /* */ } });
|
|
318
|
+
|
|
319
|
+
console.log(`\n══════════════════════════════════════════════════════════════`);
|
|
320
|
+
console.log(` Results: ${passed} passed, ${failed} failed`);
|
|
321
|
+
if (failed > 0) { console.error('\n TRACK C/D TEST FAILED\n'); process.exit(1); }
|
|
322
|
+
else { console.log('\n All Track C+D tests passed! ✓\n'); }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
run().catch(err => { console.error('trackcd crashed:', err); process.exit(1); });
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track E short-name collision regression tests.
|
|
3
|
+
*
|
|
4
|
+
* Confirms that seer_behavior 2.0, seer_risk, and seer_context only
|
|
5
|
+
* consider the EXACT target symbol id when computing callers / callees /
|
|
6
|
+
* direct test edges — never the share-the-short-name siblings.
|
|
7
|
+
*
|
|
8
|
+
* The class of bug this guards against: pre-fix, the Track E helpers
|
|
9
|
+
* filtered edges by `to_name = target.name`, so `Alpha.run` and
|
|
10
|
+
* `Beta.run` were treated as the same symbol despite the indexer
|
|
11
|
+
* correctly resolving them to distinct ids. That meant a query for
|
|
12
|
+
* `Beta.run` returned `testAlphaRun` AND `testBetaRun`, attributed
|
|
13
|
+
* Alpha.run's callers/callees to Beta.run, and computed wrong risk.
|
|
14
|
+
*
|
|
15
|
+
* Run with: npx tsx tests/tracke-collisions.ts
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import fs from 'fs';
|
|
20
|
+
import os from 'os';
|
|
21
|
+
import { Indexer } from '../src/indexer/index';
|
|
22
|
+
import { Store } from '../src/db/store';
|
|
23
|
+
import { rankedBehavior } from '../src/indexer/behavior';
|
|
24
|
+
import { computeRisk } from '../src/indexer/risk';
|
|
25
|
+
import { buildContext } from '../src/indexer/context';
|
|
26
|
+
|
|
27
|
+
const FIX_DIR = path.join(os.tmpdir(), `seer-tracke-coll-fix-${Date.now()}`);
|
|
28
|
+
const TMP_DB = path.join(os.tmpdir(), `seer-tracke-coll-${Date.now()}.db`);
|
|
29
|
+
|
|
30
|
+
let passed = 0;
|
|
31
|
+
let failed = 0;
|
|
32
|
+
function assert(cond: boolean, msg: string): void {
|
|
33
|
+
if (cond) { console.log(` ✓ ${msg}`); passed++; }
|
|
34
|
+
else { console.error(` ✗ ${msg}`); failed++; }
|
|
35
|
+
}
|
|
36
|
+
function assertEq<T>(actual: T, expected: T, msg: string): void {
|
|
37
|
+
assert(actual === expected, `${msg} (got ${JSON.stringify(actual)}, expected ${JSON.stringify(expected)})`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeFixture(): void {
|
|
41
|
+
fs.mkdirSync(FIX_DIR, { recursive: true });
|
|
42
|
+
fs.mkdirSync(path.join(FIX_DIR, 'src'), { recursive: true });
|
|
43
|
+
fs.mkdirSync(path.join(FIX_DIR, 'tests'), { recursive: true });
|
|
44
|
+
// Two classes that share short method names (`run`, `helper`). They live
|
|
45
|
+
// in DIFFERENT files so the edge resolver — without type inference — can
|
|
46
|
+
// still attribute callers correctly via same-file (for in-file callers)
|
|
47
|
+
// and imported-file (for cross-file callers) resolution. This is the
|
|
48
|
+
// scenario where the id-based Track E helpers prove their worth: even
|
|
49
|
+
// when there are multiple `run` symbols across the DB, queries against a
|
|
50
|
+
// specific id never collapse with the other id's edges.
|
|
51
|
+
fs.writeFileSync(path.join(FIX_DIR, 'src', 'alpha.ts'), `
|
|
52
|
+
export class Alpha {
|
|
53
|
+
run(): number {
|
|
54
|
+
return this.helper() + 1;
|
|
55
|
+
}
|
|
56
|
+
helper(): number { return 11; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function alphaOnly(): number {
|
|
60
|
+
const a = new Alpha();
|
|
61
|
+
return a.run();
|
|
62
|
+
}
|
|
63
|
+
`);
|
|
64
|
+
fs.writeFileSync(path.join(FIX_DIR, 'src', 'beta.ts'), `
|
|
65
|
+
export class Beta {
|
|
66
|
+
run(): number {
|
|
67
|
+
return this.helper() + 2;
|
|
68
|
+
}
|
|
69
|
+
helper(): number { return 22; }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function betaOnly(): number {
|
|
73
|
+
const b = new Beta();
|
|
74
|
+
return b.run();
|
|
75
|
+
}
|
|
76
|
+
`);
|
|
77
|
+
fs.writeFileSync(path.join(FIX_DIR, 'tests', 'alpha.test.ts'), `
|
|
78
|
+
import { Alpha } from '../src/alpha';
|
|
79
|
+
|
|
80
|
+
function testAlphaRun() {
|
|
81
|
+
const a = new Alpha();
|
|
82
|
+
expect(a.run()).toBe(12);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function expect(_v: unknown) {
|
|
86
|
+
return { toBe(_e: unknown): void { /* */ } };
|
|
87
|
+
}
|
|
88
|
+
`);
|
|
89
|
+
fs.writeFileSync(path.join(FIX_DIR, 'tests', 'beta.test.ts'), `
|
|
90
|
+
import { Beta } from '../src/beta';
|
|
91
|
+
|
|
92
|
+
function testBetaRun() {
|
|
93
|
+
const b = new Beta();
|
|
94
|
+
expect(b.run()).toBe(24);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function expect(_v: unknown) {
|
|
98
|
+
return { toBe(_e: unknown): void { /* */ } };
|
|
99
|
+
}
|
|
100
|
+
`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function cleanup(): void {
|
|
104
|
+
try { fs.rmSync(FIX_DIR, { recursive: true, force: true }); } catch { /* */ }
|
|
105
|
+
if (fs.existsSync(TMP_DB)) fs.unlinkSync(TMP_DB);
|
|
106
|
+
['-wal', '-shm'].forEach(suf => { try { fs.unlinkSync(TMP_DB + suf); } catch { /* */ } });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function main(): Promise<void> {
|
|
110
|
+
console.log('\nSeer Track E — Short-Name Collision Regression');
|
|
111
|
+
console.log('================================================\n');
|
|
112
|
+
|
|
113
|
+
writeFixture();
|
|
114
|
+
const store = new Store(TMP_DB);
|
|
115
|
+
const indexer = new Indexer(store);
|
|
116
|
+
console.log(`Indexing ${FIX_DIR}...`);
|
|
117
|
+
const r = await indexer.indexDirectory(FIX_DIR, { quiet: true });
|
|
118
|
+
console.log(` files=${r.filesIndexed} symbols=${r.symbols} edges=${r.edges}\n`);
|
|
119
|
+
|
|
120
|
+
// Resolve the two distinct `run` methods by qualified name.
|
|
121
|
+
const runMatches = store.getDefinition('run');
|
|
122
|
+
const alphaRun = runMatches.find(s => s.qualifiedName === 'Alpha.run');
|
|
123
|
+
const betaRun = runMatches.find(s => s.qualifiedName === 'Beta.run');
|
|
124
|
+
assert(alphaRun !== undefined, 'Alpha.run is indexed');
|
|
125
|
+
assert(betaRun !== undefined, 'Beta.run is indexed');
|
|
126
|
+
assert(alphaRun!.id !== betaRun!.id, 'Alpha.run and Beta.run have distinct symbol ids');
|
|
127
|
+
|
|
128
|
+
// ── seer_behavior 2.0 — direct tests must be id-scoped ──────────────────
|
|
129
|
+
console.log('\n── seer_behavior collision isolation ──');
|
|
130
|
+
const behAlpha = rankedBehavior(store, alphaRun!.id, {
|
|
131
|
+
limit: 50, includeNamingConvention: false, includeSameFile: false, indirectDepth: 0,
|
|
132
|
+
});
|
|
133
|
+
const behBeta = rankedBehavior(store, betaRun!.id, {
|
|
134
|
+
limit: 50, includeNamingConvention: false, includeSameFile: false, indirectDepth: 0,
|
|
135
|
+
});
|
|
136
|
+
assert(behAlpha !== null && behBeta !== null, 'rankedBehavior returns a result for both');
|
|
137
|
+
if (behAlpha && behBeta) {
|
|
138
|
+
const alphaNames = behAlpha.tests.map(t => t.testSymbol.name).sort();
|
|
139
|
+
const betaNames = behBeta.tests.map(t => t.testSymbol.name).sort();
|
|
140
|
+
console.log(` behavior(Alpha.run) direct: ${alphaNames.join(', ')}`);
|
|
141
|
+
console.log(` behavior(Beta.run) direct: ${betaNames.join(', ')}`);
|
|
142
|
+
// The whole point: each method has exactly ONE direct test, not both.
|
|
143
|
+
assertEq(behAlpha.direct, 1, 'Alpha.run has exactly 1 direct test (not 2)');
|
|
144
|
+
assertEq(behBeta.direct, 1, 'Beta.run has exactly 1 direct test (not 2)');
|
|
145
|
+
assert(alphaNames.includes('testAlphaRun') && !alphaNames.includes('testBetaRun'),
|
|
146
|
+
'Alpha.run\'s direct tests are only testAlphaRun');
|
|
147
|
+
assert(betaNames.includes('testBetaRun') && !betaNames.includes('testAlphaRun'),
|
|
148
|
+
'Beta.run\'s direct tests are only testBetaRun');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── seer_risk — directCallers must be id-scoped ─────────────────────────
|
|
152
|
+
console.log('\n── seer_risk collision isolation ──');
|
|
153
|
+
const riskAlpha = computeRisk(store, alphaRun!.id);
|
|
154
|
+
const riskBeta = computeRisk(store, betaRun!.id);
|
|
155
|
+
assert(riskAlpha !== null && riskBeta !== null, 'computeRisk returns a result for both');
|
|
156
|
+
if (riskAlpha && riskBeta) {
|
|
157
|
+
console.log(` risk(Alpha.run): directCallers=${riskAlpha.signals.directCallers} directTests=${riskAlpha.signals.directTests}`);
|
|
158
|
+
console.log(` risk(Beta.run): directCallers=${riskBeta.signals.directCallers} directTests=${riskBeta.signals.directTests}`);
|
|
159
|
+
// Each run() has 2 direct callers: its in-file alphaOnly/betaOnly plus
|
|
160
|
+
// the test (testAlphaRun / testBetaRun). NOT the combined 4 from both
|
|
161
|
+
// short-name siblings — that's the bug we're guarding against.
|
|
162
|
+
assertEq(riskAlpha.signals.directCallers, 2,
|
|
163
|
+
'risk(Alpha.run).directCallers = 2 (alphaOnly + testAlphaRun) — not the cross-collapsed 4');
|
|
164
|
+
assertEq(riskBeta.signals.directCallers, 2,
|
|
165
|
+
'risk(Beta.run).directCallers = 2 (betaOnly + testBetaRun) — not the cross-collapsed 4');
|
|
166
|
+
// directTests must also be id-scoped (1 each).
|
|
167
|
+
assertEq(riskAlpha.signals.directTests, 1, 'risk(Alpha.run).directTests = 1');
|
|
168
|
+
assertEq(riskBeta.signals.directTests, 1, 'risk(Beta.run).directTests = 1');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── seer_context — callers / callees / blast radius must be id-scoped ──
|
|
172
|
+
console.log('\n── seer_context collision isolation ──');
|
|
173
|
+
const ctxAlpha = buildContext(store, alphaRun!.id);
|
|
174
|
+
const ctxBeta = buildContext(store, betaRun!.id);
|
|
175
|
+
assert(ctxAlpha !== null && ctxBeta !== null, 'buildContext returns packets for both');
|
|
176
|
+
if (ctxAlpha && ctxBeta) {
|
|
177
|
+
console.log(` context(Alpha.run): callers.total=${ctxAlpha.callers.total} callees.total=${ctxAlpha.callees.total} blast=${ctxAlpha.blastRadius.directCallers}+${ctxAlpha.blastRadius.transitiveCallers}`);
|
|
178
|
+
console.log(` context(Beta.run): callers.total=${ctxBeta.callers.total} callees.total=${ctxBeta.callees.total} blast=${ctxBeta.blastRadius.directCallers}+${ctxBeta.blastRadius.transitiveCallers}`);
|
|
179
|
+
// callers.total: 2 each (in-file caller + dedicated test).
|
|
180
|
+
assertEq(ctxAlpha.callers.total, 2, 'context(Alpha.run).callers.total = 2');
|
|
181
|
+
assertEq(ctxBeta.callers.total, 2, 'context(Beta.run).callers.total = 2');
|
|
182
|
+
// callers.preview must point at the right callers — no cross-class
|
|
183
|
+
// leakage. Alpha's callers must be {alphaOnly, testAlphaRun}; Beta's
|
|
184
|
+
// must be {betaOnly, testBetaRun}.
|
|
185
|
+
const alphaCallerNames = ctxAlpha.callers.preview.map(c => c.name).sort();
|
|
186
|
+
const betaCallerNames = ctxBeta.callers.preview.map(c => c.name).sort();
|
|
187
|
+
assert(alphaCallerNames.includes('alphaOnly') && alphaCallerNames.includes('testAlphaRun'),
|
|
188
|
+
'context(Alpha.run).callers.preview includes alphaOnly + testAlphaRun');
|
|
189
|
+
assert(!alphaCallerNames.includes('betaOnly') && !alphaCallerNames.includes('testBetaRun'),
|
|
190
|
+
'context(Alpha.run).callers.preview EXCLUDES betaOnly + testBetaRun (no cross-leak)');
|
|
191
|
+
assert(betaCallerNames.includes('betaOnly') && betaCallerNames.includes('testBetaRun'),
|
|
192
|
+
'context(Beta.run).callers.preview includes betaOnly + testBetaRun');
|
|
193
|
+
assert(!betaCallerNames.includes('alphaOnly') && !betaCallerNames.includes('testAlphaRun'),
|
|
194
|
+
'context(Beta.run).callers.preview EXCLUDES alphaOnly + testAlphaRun (no cross-leak)');
|
|
195
|
+
// callees: each run() calls only its own helper() — 1 each.
|
|
196
|
+
assertEq(ctxAlpha.callees.total, 1, 'context(Alpha.run).callees.total = 1');
|
|
197
|
+
assertEq(ctxBeta.callees.total, 1, 'context(Beta.run).callees.total = 1');
|
|
198
|
+
// blastRadius.directCallers must equal callers.total.
|
|
199
|
+
assertEq(ctxAlpha.blastRadius.directCallers, ctxAlpha.callers.total,
|
|
200
|
+
'blastRadius.directCallers(Alpha.run) matches callers.total');
|
|
201
|
+
assertEq(ctxBeta.blastRadius.directCallers, ctxBeta.callers.total,
|
|
202
|
+
'blastRadius.directCallers(Beta.run) matches callers.total');
|
|
203
|
+
// Behavior preview inside the packet: the DIRECT-CALL relationship
|
|
204
|
+
// must be id-scoped (the bug we're closing). Naming-convention is a
|
|
205
|
+
// deliberate signal — "testBetaRun" contains "Run" so it WILL appear
|
|
206
|
+
// as a naming-convention match for Alpha.run too, at lower
|
|
207
|
+
// specificity. That's expected; we just need to make sure the direct
|
|
208
|
+
// edges don't collapse.
|
|
209
|
+
const alphaDirectBeh = ctxAlpha.behavior.preview
|
|
210
|
+
.filter(t => t.relationship === 'direct-call').map(t => t.name);
|
|
211
|
+
const betaDirectBeh = ctxBeta.behavior.preview
|
|
212
|
+
.filter(t => t.relationship === 'direct-call').map(t => t.name);
|
|
213
|
+
assert(alphaDirectBeh.includes('testAlphaRun') && !alphaDirectBeh.includes('testBetaRun'),
|
|
214
|
+
'context(Alpha.run).behavior direct-call slice is testAlphaRun only');
|
|
215
|
+
assert(betaDirectBeh.includes('testBetaRun') && !betaDirectBeh.includes('testAlphaRun'),
|
|
216
|
+
'context(Beta.run).behavior direct-call slice is testBetaRun only');
|
|
217
|
+
// The top-ranked test (highest specificity) should be the direct match.
|
|
218
|
+
assertEq(ctxAlpha.behavior.preview[0]?.name, 'testAlphaRun',
|
|
219
|
+
'context(Alpha.run).behavior preview top-ranked = testAlphaRun (direct beats naming)');
|
|
220
|
+
assertEq(ctxBeta.behavior.preview[0]?.name, 'testBetaRun',
|
|
221
|
+
'context(Beta.run).behavior preview top-ranked = testBetaRun (direct beats naming)');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Sanity: existing legacy name-based APIs are intentionally broad. They
|
|
225
|
+
// KEEP counting every edge whose to_name = 'run' — Track E's id-based
|
|
226
|
+
// fix is additive, not a behavior change for the legacy by-name path.
|
|
227
|
+
// The fixture has 4 such call edges: alphaOnly→run, betaOnly→run,
|
|
228
|
+
// testAlphaRun→run, testBetaRun→run.
|
|
229
|
+
console.log('\n── Legacy name-based queries still broad ──');
|
|
230
|
+
assertEq(store.countCallers('run'), 4,
|
|
231
|
+
'countCallers(\'run\') still returns 4 edges (legacy broad short-name lookup unchanged)');
|
|
232
|
+
// And id-based count is correctly scoped to one symbol.
|
|
233
|
+
const alphaCount = store.countCallersById(alphaRun!.id);
|
|
234
|
+
const betaCount = store.countCallersById(betaRun!.id);
|
|
235
|
+
assertEq(alphaCount, 2, 'countCallersById(Alpha.run) = 2 (alphaOnly + testAlphaRun)');
|
|
236
|
+
assertEq(betaCount, 2, 'countCallersById(Beta.run) = 2 (betaOnly + testBetaRun)');
|
|
237
|
+
|
|
238
|
+
store.close();
|
|
239
|
+
cleanup();
|
|
240
|
+
|
|
241
|
+
console.log(`\n══════════════════════════════════════════════════════════════`);
|
|
242
|
+
console.log(` Results: ${passed} passed, ${failed} failed`);
|
|
243
|
+
if (failed > 0) {
|
|
244
|
+
console.error('\n TRACK E COLLISION TEST FAILED\n');
|
|
245
|
+
process.exit(1);
|
|
246
|
+
} else {
|
|
247
|
+
console.log('\n All Track E collision regressions pinned. ✓\n');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
main().catch(err => {
|
|
252
|
+
console.error('tracke-collisions crashed:', err);
|
|
253
|
+
try { cleanup(); } catch { /* */ }
|
|
254
|
+
process.exit(1);
|
|
255
|
+
});
|