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,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel parser stress & recovery tests (Step 5 of parallel parsing).
|
|
3
|
+
*
|
|
4
|
+
* Exercises the failure paths the audit flagged:
|
|
5
|
+
* - Per-job attempt limit: a "poison" file that crashes every worker it
|
|
6
|
+
* touches must be marked parse-error after `maxAttempts` worker deaths
|
|
7
|
+
* and the dispatch must complete (not hang).
|
|
8
|
+
* - Crashes are contained: a single sentinel-matched file inside a healthy
|
|
9
|
+
* workspace produces exactly one parse-error; every other file is parsed
|
|
10
|
+
* correctly and indexed by the indexer integration test.
|
|
11
|
+
* - Edge cases:
|
|
12
|
+
* - empty workspace runs cleanly under `parallel: true`
|
|
13
|
+
* - one-file workspace with jobs > 1 doesn't deadlock
|
|
14
|
+
* - `maxFileBytes` is honored (worker reports too-large; row pruned)
|
|
15
|
+
*
|
|
16
|
+
* The crash is injected via `SEER_WORKER_TEST_CRASH_ON` — when set, the worker
|
|
17
|
+
* `process.exit(13)`s on any parse job whose `abs` contains that substring.
|
|
18
|
+
* Production never sets the variable.
|
|
19
|
+
*
|
|
20
|
+
* Run with: npm run test:parallel-recovery
|
|
21
|
+
*/
|
|
22
|
+
import fs from 'fs';
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import os from 'os';
|
|
25
|
+
import { Indexer } from '../src/indexer/index';
|
|
26
|
+
import { Store } from '../src/db/store';
|
|
27
|
+
import { WorkerPool, WorkItem, PoolResult } from '../src/parser/workerpool';
|
|
28
|
+
|
|
29
|
+
const FIXTURES_DIR = path.join(__dirname, 'fixtures');
|
|
30
|
+
|
|
31
|
+
let passed = 0;
|
|
32
|
+
let failed = 0;
|
|
33
|
+
|
|
34
|
+
function assert(condition: boolean, message: string): void {
|
|
35
|
+
if (condition) {
|
|
36
|
+
console.log(` ✓ ${message}`);
|
|
37
|
+
passed++;
|
|
38
|
+
} else {
|
|
39
|
+
console.error(` ✗ ${message}`);
|
|
40
|
+
failed++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function tmpDb(label: string): string {
|
|
45
|
+
return path.join(os.tmpdir(), `seer-parallel-recovery-${label}-${Date.now()}.db`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Run ─────────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
async function run(): Promise<void> {
|
|
51
|
+
console.log('\nParallel Recovery Test (Step 5)');
|
|
52
|
+
console.log('=================================\n');
|
|
53
|
+
|
|
54
|
+
// ── 1. Empty workspace ───────────────────────────────────────────────────
|
|
55
|
+
console.log('── Empty workspace runs cleanly under parallel=true ──');
|
|
56
|
+
{
|
|
57
|
+
const root = path.join(os.tmpdir(), `seer-parallel-empty-${Date.now()}`);
|
|
58
|
+
fs.mkdirSync(root, { recursive: true });
|
|
59
|
+
const db = tmpDb('empty');
|
|
60
|
+
const store = new Store(db);
|
|
61
|
+
const indexer = new Indexer(store);
|
|
62
|
+
const res = await indexer.indexDirectory(root, { quiet: true, parallel: true });
|
|
63
|
+
store.close();
|
|
64
|
+
assert(res.filesIndexed === 0, 'empty workspace: filesIndexed=0');
|
|
65
|
+
assert(res.symbols === 0, 'empty workspace: symbols=0');
|
|
66
|
+
assert(res.pagerankRecomputed === false, 'empty workspace: no PageRank recompute');
|
|
67
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
68
|
+
try { fs.unlinkSync(db); } catch { /* */ }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── 2. Single-file workspace with jobs=8 doesn't deadlock ────────────────
|
|
72
|
+
console.log('\n── Single-file workspace, jobs=8 ──');
|
|
73
|
+
{
|
|
74
|
+
const root = path.join(os.tmpdir(), `seer-parallel-single-${Date.now()}`);
|
|
75
|
+
fs.mkdirSync(root, { recursive: true });
|
|
76
|
+
fs.copyFileSync(path.join(FIXTURES_DIR, 'sample.ts'), path.join(root, 'sample.ts'));
|
|
77
|
+
const db = tmpDb('single');
|
|
78
|
+
const store = new Store(db);
|
|
79
|
+
const indexer = new Indexer(store);
|
|
80
|
+
const res = await indexer.indexDirectory(root, { quiet: true, parallel: true, jobs: 8 });
|
|
81
|
+
store.close();
|
|
82
|
+
assert(res.filesIndexed === 1, 'single file: filesIndexed=1');
|
|
83
|
+
assert(res.symbols > 0, 'single file: symbols extracted');
|
|
84
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
85
|
+
try { fs.unlinkSync(db); } catch { /* */ }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── 3. maxFileBytes enforcement ──────────────────────────────────────────
|
|
89
|
+
console.log('\n── maxFileBytes enforcement (parallel) ──');
|
|
90
|
+
{
|
|
91
|
+
const root = path.join(os.tmpdir(), `seer-parallel-toolarge-${Date.now()}`);
|
|
92
|
+
fs.mkdirSync(root, { recursive: true });
|
|
93
|
+
fs.copyFileSync(path.join(FIXTURES_DIR, 'sample.ts'), path.join(root, 'small.ts'));
|
|
94
|
+
// Build a 50 KB file that exceeds the cap.
|
|
95
|
+
fs.writeFileSync(path.join(root, 'big.ts'), 'const x = 1;\n'.repeat(5000));
|
|
96
|
+
const db = tmpDb('toolarge');
|
|
97
|
+
const store = new Store(db);
|
|
98
|
+
const indexer = new Indexer(store);
|
|
99
|
+
const res = await indexer.indexDirectory(root, {
|
|
100
|
+
quiet: true, parallel: true, jobs: 2, maxFileBytes: 8192,
|
|
101
|
+
});
|
|
102
|
+
store.close();
|
|
103
|
+
assert(res.filesSkippedTooLarge === 1, `one file skipped as too-large (got ${res.filesSkippedTooLarge})`);
|
|
104
|
+
assert(res.filesIndexed === 1, `the other file was indexed (got ${res.filesIndexed})`);
|
|
105
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
106
|
+
try { fs.unlinkSync(db); } catch { /* */ }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── 4. Poison job hits attempt limit → parse-error (pool layer) ──────────
|
|
110
|
+
console.log('\n── Poison job hits maxAttempts → parse-error ──');
|
|
111
|
+
{
|
|
112
|
+
process.env.SEER_WORKER_TEST_CRASH_ON = 'POISON_SENTINEL';
|
|
113
|
+
try {
|
|
114
|
+
const pool = new WorkerPool({ jobs: 2, maxAttempts: 3 });
|
|
115
|
+
await pool.ready();
|
|
116
|
+
const goodPath = path.join(FIXTURES_DIR, 'sample.ts');
|
|
117
|
+
const poisonPath = path.join(os.tmpdir(), `POISON_SENTINEL-${Date.now()}.ts`);
|
|
118
|
+
// The poison file doesn't actually need to exist on disk — the worker
|
|
119
|
+
// crashes BEFORE it tries to read.
|
|
120
|
+
const items: WorkItem[] = [
|
|
121
|
+
{ abs: goodPath, lang: 'typescript', expectedHash: null, maxFileBytes: 0 },
|
|
122
|
+
{ abs: poisonPath, lang: 'typescript', expectedHash: null, maxFileBytes: 0 },
|
|
123
|
+
{ abs: goodPath, lang: 'typescript', expectedHash: null, maxFileBytes: 0 },
|
|
124
|
+
];
|
|
125
|
+
const results: PoolResult[] = [];
|
|
126
|
+
await pool.dispatch(items, (seq, result) => { results[seq] = result; });
|
|
127
|
+
assert(results.length === 3, 'all 3 items delivered (no hang)');
|
|
128
|
+
assert(results[0].kind === 'parsed', 'good file 0 parsed');
|
|
129
|
+
assert(results[1].kind === 'parse-error', `poison file synthesized parse-error (got ${results[1]?.kind})`);
|
|
130
|
+
assert(results[2].kind === 'parsed', 'good file 2 parsed (pool recovered after the crash)');
|
|
131
|
+
await pool.shutdown();
|
|
132
|
+
} finally {
|
|
133
|
+
delete process.env.SEER_WORKER_TEST_CRASH_ON;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── 5. Many crashes don't deadlock the pool ──────────────────────────────
|
|
138
|
+
console.log('\n── Many poison jobs interleaved with healthy ones ──');
|
|
139
|
+
{
|
|
140
|
+
process.env.SEER_WORKER_TEST_CRASH_ON = 'POISON_MANY';
|
|
141
|
+
try {
|
|
142
|
+
const pool = new WorkerPool({ jobs: 4, maxAttempts: 2 });
|
|
143
|
+
await pool.ready();
|
|
144
|
+
const goodPath = path.join(FIXTURES_DIR, 'sample.ts');
|
|
145
|
+
const items: WorkItem[] = [];
|
|
146
|
+
for (let i = 0; i < 12; i++) {
|
|
147
|
+
const isPoison = i % 3 === 0; // 4 of 12 are poison
|
|
148
|
+
items.push({
|
|
149
|
+
abs: isPoison
|
|
150
|
+
? path.join(os.tmpdir(), `POISON_MANY-${i}.ts`)
|
|
151
|
+
: goodPath,
|
|
152
|
+
lang: 'typescript', expectedHash: null, maxFileBytes: 0,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const kinds: string[] = [];
|
|
156
|
+
await pool.dispatch(items, (seq, result) => { kinds[seq] = result.kind; });
|
|
157
|
+
assert(kinds.length === 12, '12 items delivered (no hang under 4 poison + 8 healthy)');
|
|
158
|
+
const goodCount = kinds.filter(k => k === 'parsed').length;
|
|
159
|
+
const errorCount = kinds.filter(k => k === 'parse-error').length;
|
|
160
|
+
assert(goodCount === 8, `8 healthy parses (got ${goodCount})`);
|
|
161
|
+
assert(errorCount === 4, `4 poison parse-errors (got ${errorCount})`);
|
|
162
|
+
await pool.shutdown();
|
|
163
|
+
} finally {
|
|
164
|
+
delete process.env.SEER_WORKER_TEST_CRASH_ON;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── 6. Indexer integration: parallel run with one poison file ────────────
|
|
169
|
+
console.log('\n── Indexer integration: worker WASM reset count is aggregated ──');
|
|
170
|
+
{
|
|
171
|
+
const root = path.join(os.tmpdir(), `seer-parallel-reset-aggregation-${Date.now()}`);
|
|
172
|
+
fs.mkdirSync(root, { recursive: true });
|
|
173
|
+
fs.copyFileSync(path.join(FIXTURES_DIR, 'sample.ts'), path.join(root, 'FAKE_WASM_RESET_INDEXER.ts'));
|
|
174
|
+
process.env.SEER_WORKER_TEST_FAKE_WASM_RESET_ON = 'FAKE_WASM_RESET_INDEXER';
|
|
175
|
+
try {
|
|
176
|
+
const db = tmpDb('reset-aggregation');
|
|
177
|
+
const store = new Store(db);
|
|
178
|
+
const indexer = new Indexer(store);
|
|
179
|
+
const res = await indexer.indexDirectory(root, {
|
|
180
|
+
quiet: true, parallel: true, jobs: 1,
|
|
181
|
+
});
|
|
182
|
+
store.close();
|
|
183
|
+
assert(res.filesIndexed === 1, `reset aggregation fixture indexed (got ${res.filesIndexed})`);
|
|
184
|
+
assert(res.wasmResets === 1, `IndexResult aggregates worker-local wasm resets (got ${res.wasmResets})`);
|
|
185
|
+
try { fs.unlinkSync(db); } catch { /* */ }
|
|
186
|
+
} finally {
|
|
187
|
+
delete process.env.SEER_WORKER_TEST_FAKE_WASM_RESET_ON;
|
|
188
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── 7. Indexer integration: parallel run with one poison file ────────────
|
|
193
|
+
console.log('\n── Indexer integration: parallel + one poison file ──');
|
|
194
|
+
{
|
|
195
|
+
const root = path.join(os.tmpdir(), `seer-parallel-indexer-recovery-${Date.now()}`);
|
|
196
|
+
fs.mkdirSync(root, { recursive: true });
|
|
197
|
+
// Stage a couple of healthy fixtures plus the poison file.
|
|
198
|
+
for (const f of ['sample.ts', 'sample.py']) {
|
|
199
|
+
fs.copyFileSync(path.join(FIXTURES_DIR, f), path.join(root, f));
|
|
200
|
+
}
|
|
201
|
+
// The poison file MUST exist on disk so discoverFiles picks it up; the
|
|
202
|
+
// worker exits before opening it though, so content is irrelevant.
|
|
203
|
+
fs.writeFileSync(path.join(root, 'POISON_INDEXER.ts'), 'const x = 1;\n');
|
|
204
|
+
process.env.SEER_WORKER_TEST_CRASH_ON = 'POISON_INDEXER';
|
|
205
|
+
try {
|
|
206
|
+
const db = tmpDb('indexer-recovery');
|
|
207
|
+
const store = new Store(db);
|
|
208
|
+
const indexer = new Indexer(store);
|
|
209
|
+
const res = await indexer.indexDirectory(root, {
|
|
210
|
+
quiet: true, parallel: true, jobs: 2,
|
|
211
|
+
});
|
|
212
|
+
store.close();
|
|
213
|
+
assert(res.filesIndexed === 2, `2 healthy files indexed (got ${res.filesIndexed})`);
|
|
214
|
+
assert(res.filesParseError === 1, `1 file reported as parse-error (got ${res.filesParseError})`);
|
|
215
|
+
// The poison file's row should still exist (upsert with hash+lines was
|
|
216
|
+
// synthesized from the parse-error result), so it's not pruned.
|
|
217
|
+
const store2 = new Store(db);
|
|
218
|
+
const filesNow = store2.listFiles().map(f => f.relPath);
|
|
219
|
+
store2.close();
|
|
220
|
+
assert(filesNow.includes('POISON_INDEXER.ts'), 'poison file row preserved (not pruned)');
|
|
221
|
+
try { fs.unlinkSync(db); } catch { /* */ }
|
|
222
|
+
} finally {
|
|
223
|
+
delete process.env.SEER_WORKER_TEST_CRASH_ON;
|
|
224
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log('\n══════════════════════════════════════════════════════════════');
|
|
229
|
+
console.log(` Parallel-recovery results: ${passed} passed, ${failed} failed`);
|
|
230
|
+
if (failed > 0) {
|
|
231
|
+
console.error('\n PARALLEL-RECOVERY TESTS FAILED\n');
|
|
232
|
+
process.exit(1);
|
|
233
|
+
} else {
|
|
234
|
+
console.log('\n All parallel-recovery tests passed! ✓\n');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
run().catch(err => {
|
|
239
|
+
console.error('parallel-recovery test threw:', err);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perf probe for the findCallers() fix.
|
|
3
|
+
*
|
|
4
|
+
* Targets the case the user flagged: Unreal's `Num` with ~127k callers, which
|
|
5
|
+
* previously took ~745ms for the direct Store query and ~1.65s through the CLI.
|
|
6
|
+
* The <50ms target only makes sense with a LIMIT (the CLI's display window) —
|
|
7
|
+
* unbounded fetch of 127k rows is dominated by JS object allocation and cannot
|
|
8
|
+
* realistically hit 50ms regardless of SQL plan.
|
|
9
|
+
*
|
|
10
|
+
* Run: npx tsx tests/perf-callers.ts
|
|
11
|
+
*/
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import { Store } from '../src/db/store';
|
|
15
|
+
|
|
16
|
+
const DB_PATH = path.resolve(__dirname, 'outputs/dbs/unreal.db');
|
|
17
|
+
|
|
18
|
+
interface ProbeResult {
|
|
19
|
+
symbol: string;
|
|
20
|
+
countMs: number;
|
|
21
|
+
limit40Ms: number;
|
|
22
|
+
unboundedMs: number;
|
|
23
|
+
total: number;
|
|
24
|
+
limit40Rows: number;
|
|
25
|
+
unboundedRows: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function timeMs(fn: () => void): number {
|
|
29
|
+
const start = process.hrtime.bigint();
|
|
30
|
+
fn();
|
|
31
|
+
return Number(process.hrtime.bigint() - start) / 1e6;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function bestOf(n: number, fn: () => number): number {
|
|
35
|
+
let best = Infinity;
|
|
36
|
+
for (let i = 0; i < n; i++) best = Math.min(best, fn());
|
|
37
|
+
return best;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function probeOne(store: Store, symbol: string, runUnbounded: boolean): ProbeResult {
|
|
41
|
+
// Warm the prepared-statement cache once
|
|
42
|
+
store.countCallers(symbol);
|
|
43
|
+
store.findCallers(symbol, 40);
|
|
44
|
+
|
|
45
|
+
const total = store.countCallers(symbol);
|
|
46
|
+
const countMs = bestOf(5, () => timeMs(() => store.countCallers(symbol)));
|
|
47
|
+
let limit40Rows = 0;
|
|
48
|
+
const limit40Ms = bestOf(5, () => timeMs(() => {
|
|
49
|
+
limit40Rows = store.findCallers(symbol, 40).length;
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
let unboundedMs = 0;
|
|
53
|
+
let unboundedRows = total;
|
|
54
|
+
if (runUnbounded) {
|
|
55
|
+
unboundedMs = bestOf(2, () => timeMs(() => {
|
|
56
|
+
unboundedRows = store.findCallers(symbol).length;
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { symbol, countMs, limit40Ms, unboundedMs, total, limit40Rows, unboundedRows };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function main(): void {
|
|
64
|
+
if (!fs.existsSync(DB_PATH)) {
|
|
65
|
+
console.error(`No Unreal DB at ${DB_PATH}.`);
|
|
66
|
+
console.error('Run `npm run scale-test -- --only unreal` first.');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const store = new Store(DB_PATH);
|
|
71
|
+
|
|
72
|
+
// Discover the highest-fan-in symbols in the DB instead of hard-coding
|
|
73
|
+
// names — keeps the probe meaningful even if upstream Unreal API drifts.
|
|
74
|
+
const sym = (store as unknown as { ['db']: { prepare: (s: string) => { all: (...a: unknown[]) => unknown[] } } }).db;
|
|
75
|
+
type Row = Record<string, unknown>;
|
|
76
|
+
const top = (sym.prepare(
|
|
77
|
+
'SELECT to_name AS name, COUNT(*) AS c FROM edges GROUP BY to_name ORDER BY c DESC LIMIT 5'
|
|
78
|
+
).all() as Row[]).map(r => ({ name: String(r.name), c: Number(r.c) }));
|
|
79
|
+
|
|
80
|
+
console.log('\nPerf probe — Unreal high-fan-in findCallers');
|
|
81
|
+
console.log('────────────────────────────────────────────');
|
|
82
|
+
console.log(`DB: ${DB_PATH}\n`);
|
|
83
|
+
console.log('Top 5 callees by fan-in:');
|
|
84
|
+
for (const t of top) console.log(` ${t.name.padEnd(28)} ${t.c.toLocaleString()} callers`);
|
|
85
|
+
console.log('');
|
|
86
|
+
|
|
87
|
+
const results: ProbeResult[] = [];
|
|
88
|
+
for (const t of top) {
|
|
89
|
+
const heavy = t.c >= 10_000;
|
|
90
|
+
results.push(probeOne(store, t.name, !heavy /* skip unbounded on huge fan-in */));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Also probe `Num` explicitly if present (the user-cited symbol).
|
|
94
|
+
if (!results.some(r => r.symbol === 'Num')) {
|
|
95
|
+
const c = store.countCallers('Num');
|
|
96
|
+
if (c > 0) results.push(probeOne(store, 'Num', c < 10_000));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log('Results (best-of, ms):');
|
|
100
|
+
console.log(` ${'symbol'.padEnd(28)} ${'count'.padStart(10)} ${'countMs'.padStart(10)} ${'limit40Ms'.padStart(12)} ${'unboundedMs'.padStart(13)}`);
|
|
101
|
+
console.log(' ' + '─'.repeat(75));
|
|
102
|
+
for (const r of results) {
|
|
103
|
+
const unbStr = r.unboundedMs > 0
|
|
104
|
+
? r.unboundedMs.toFixed(2)
|
|
105
|
+
: 'skipped';
|
|
106
|
+
console.log(
|
|
107
|
+
` ${r.symbol.padEnd(28)} ${r.total.toLocaleString().padStart(10)} ${r.countMs.toFixed(2).padStart(10)} ${r.limit40Ms.toFixed(2).padStart(12)} ${String(unbStr).padStart(13)}`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// User-facing target: <50ms for the CLI's query roundtrip on the worst
|
|
112
|
+
// symbol. The CLI runs `countCallers + findCallers(symbol, limit)`, so we
|
|
113
|
+
// judge against the combined budget. countCallers alone is index-only but
|
|
114
|
+
// still scans matching B-tree leaves, so on the heaviest symbols it costs
|
|
115
|
+
// ~10ms — that's why we don't split it into a separate <5ms target.
|
|
116
|
+
const worstLimited = Math.max(...results.map(r => r.limit40Ms));
|
|
117
|
+
const worstCount = Math.max(...results.map(r => r.countMs));
|
|
118
|
+
const worstCombined = Math.max(...results.map(r => r.countMs + r.limit40Ms));
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(`Worst limit40Ms: ${worstLimited.toFixed(2)} ms`);
|
|
121
|
+
console.log(`Worst countMs: ${worstCount.toFixed(2)} ms (informational)`);
|
|
122
|
+
console.log(`Worst combined: ${worstCombined.toFixed(2)} ms (target <50ms — full CLI query roundtrip)`);
|
|
123
|
+
|
|
124
|
+
const ok = worstCombined < 50;
|
|
125
|
+
|
|
126
|
+
// Persist for later cross-checks
|
|
127
|
+
const outPath = path.resolve(__dirname, `outputs/perf-callers-${Date.now()}.json`);
|
|
128
|
+
fs.writeFileSync(outPath, JSON.stringify({
|
|
129
|
+
db: DB_PATH,
|
|
130
|
+
timestamp: new Date().toISOString(),
|
|
131
|
+
targets: { combinedMsMax: 50 },
|
|
132
|
+
results,
|
|
133
|
+
}, null, 2));
|
|
134
|
+
console.log(`\nSaved: ${path.relative(path.resolve(__dirname, '..'), outPath)}`);
|
|
135
|
+
|
|
136
|
+
store.close();
|
|
137
|
+
|
|
138
|
+
if (!ok) {
|
|
139
|
+
console.error('\nPerf targets MISSED.');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
console.log('\nPerf targets met.\n');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
main();
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-Sitter query candidate-collection parity test.
|
|
3
|
+
*
|
|
4
|
+
* Parses each fixture twice — once with the query-assisted candidate walker
|
|
5
|
+
* (default) and once with the baseline walker forced — and asserts the two
|
|
6
|
+
* extractions are identical for every category: definitions, references,
|
|
7
|
+
* imports, routes, config keys.
|
|
8
|
+
*
|
|
9
|
+
* If parity ever fails, the candidate node-type list is missing a type that
|
|
10
|
+
* the corresponding tryExtract* handler accepts.
|
|
11
|
+
*
|
|
12
|
+
* Run with: npx tsx tests/query-parity.ts (also runs from `npm test`)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import { parseFile, setForceBaselineWalker } from '../src/parser/index';
|
|
18
|
+
import type { FileExtraction } from '../src/types';
|
|
19
|
+
|
|
20
|
+
const FIXTURES_DIR = path.join(__dirname, 'fixtures');
|
|
21
|
+
|
|
22
|
+
let passed = 0;
|
|
23
|
+
let failed = 0;
|
|
24
|
+
|
|
25
|
+
function assert(condition: boolean, message: string): void {
|
|
26
|
+
if (condition) {
|
|
27
|
+
console.log(` ✓ ${message}`);
|
|
28
|
+
passed++;
|
|
29
|
+
} else {
|
|
30
|
+
console.error(` ✗ ${message}`);
|
|
31
|
+
failed++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function canonicalize(extraction: FileExtraction): string {
|
|
36
|
+
// Sort each list deterministically so order-of-traversal differences (which
|
|
37
|
+
// there shouldn't be — but defensively) don't fail the comparison.
|
|
38
|
+
const defs = [...extraction.definitions].sort((a, b) =>
|
|
39
|
+
(a.lineStart - b.lineStart) ||
|
|
40
|
+
(a.name.localeCompare(b.name)) ||
|
|
41
|
+
((a.qualifiedName ?? '').localeCompare(b.qualifiedName ?? ''))
|
|
42
|
+
);
|
|
43
|
+
const refs = [...extraction.references].sort((a, b) =>
|
|
44
|
+
(a.line - b.line) || a.calleeName.localeCompare(b.calleeName) || a.callerName.localeCompare(b.callerName)
|
|
45
|
+
);
|
|
46
|
+
const imports = [...extraction.importedModules].sort();
|
|
47
|
+
const routes = [...(extraction.routes ?? [])].sort((a, b) =>
|
|
48
|
+
(a.line - b.line) || a.method.localeCompare(b.method) || a.path.localeCompare(b.path)
|
|
49
|
+
);
|
|
50
|
+
const configKeys = [...(extraction.configKeys ?? [])].sort((a, b) =>
|
|
51
|
+
(a.line - b.line) || a.key.localeCompare(b.key)
|
|
52
|
+
);
|
|
53
|
+
return JSON.stringify({
|
|
54
|
+
language: extraction.language,
|
|
55
|
+
definitions: defs.map(d => ({
|
|
56
|
+
name: d.name,
|
|
57
|
+
qualifiedName: d.qualifiedName,
|
|
58
|
+
kind: d.kind,
|
|
59
|
+
lineStart: d.lineStart,
|
|
60
|
+
lineEnd: d.lineEnd,
|
|
61
|
+
cyclomatic: d.cyclomatic ?? null,
|
|
62
|
+
cognitive: d.cognitive ?? null,
|
|
63
|
+
maxNesting: d.maxNesting ?? null,
|
|
64
|
+
loc: d.loc ?? null,
|
|
65
|
+
})),
|
|
66
|
+
references: refs.map(r => ({
|
|
67
|
+
calleeName: r.calleeName,
|
|
68
|
+
callerName: r.callerName,
|
|
69
|
+
kind: r.kind,
|
|
70
|
+
line: r.line,
|
|
71
|
+
})),
|
|
72
|
+
imports,
|
|
73
|
+
routes: routes.map(r => ({
|
|
74
|
+
method: r.method,
|
|
75
|
+
path: r.path,
|
|
76
|
+
framework: r.framework,
|
|
77
|
+
handlerName: r.handlerName ?? null,
|
|
78
|
+
line: r.line,
|
|
79
|
+
})),
|
|
80
|
+
configKeys: configKeys.map(c => ({
|
|
81
|
+
key: c.key,
|
|
82
|
+
source: c.source,
|
|
83
|
+
callerName: c.callerName ?? '',
|
|
84
|
+
line: c.line,
|
|
85
|
+
})),
|
|
86
|
+
}, null, 2);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function compareFile(filePath: string): Promise<void> {
|
|
90
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
91
|
+
const rel = path.relative(FIXTURES_DIR, filePath);
|
|
92
|
+
|
|
93
|
+
setForceBaselineWalker(false);
|
|
94
|
+
const queryAssisted = await parseFile(content, filePath);
|
|
95
|
+
|
|
96
|
+
setForceBaselineWalker(true);
|
|
97
|
+
const baseline = await parseFile(content, filePath);
|
|
98
|
+
setForceBaselineWalker(false);
|
|
99
|
+
|
|
100
|
+
if (!queryAssisted && !baseline) {
|
|
101
|
+
// Both null (probably an unsupported extension) — skip without counting.
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
assert(
|
|
106
|
+
queryAssisted !== null && baseline !== null,
|
|
107
|
+
`[${rel}] both walkers succeed`,
|
|
108
|
+
);
|
|
109
|
+
if (!queryAssisted || !baseline) return;
|
|
110
|
+
|
|
111
|
+
const queryStr = canonicalize(queryAssisted);
|
|
112
|
+
const baselineStr = canonicalize(baseline);
|
|
113
|
+
|
|
114
|
+
const equal = queryStr === baselineStr;
|
|
115
|
+
assert(equal, `[${rel}] query-assisted ≡ baseline (${queryAssisted.definitions.length} defs, ${queryAssisted.references.length} refs, ${queryAssisted.importedModules.length} imports, ${queryAssisted.routes?.length ?? 0} routes, ${queryAssisted.configKeys?.length ?? 0} configKeys)`);
|
|
116
|
+
if (!equal) {
|
|
117
|
+
// Locate the first differing section for a useful failure message.
|
|
118
|
+
const diff = firstDiff(queryStr, baselineStr);
|
|
119
|
+
console.error(` First difference at offset ${diff.offset}:`);
|
|
120
|
+
console.error(` query: ${diff.queryLine}`);
|
|
121
|
+
console.error(` baseline: ${diff.baselineLine}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function firstDiff(a: string, b: string): { offset: number; queryLine: string; baselineLine: string } {
|
|
126
|
+
let off = 0;
|
|
127
|
+
while (off < a.length && off < b.length && a[off] === b[off]) off++;
|
|
128
|
+
const lineStart = Math.max(0, a.lastIndexOf('\n', off));
|
|
129
|
+
const aEnd = a.indexOf('\n', off);
|
|
130
|
+
const bEnd = b.indexOf('\n', off);
|
|
131
|
+
return {
|
|
132
|
+
offset: off,
|
|
133
|
+
queryLine: a.slice(lineStart, aEnd === -1 ? a.length : aEnd).trim(),
|
|
134
|
+
baselineLine: b.slice(lineStart, bEnd === -1 ? b.length : bEnd).trim(),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function walkFixtures(dir: string, out: string[]): Promise<void> {
|
|
139
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
140
|
+
const p = path.join(dir, entry.name);
|
|
141
|
+
if (entry.isDirectory()) {
|
|
142
|
+
await walkFixtures(p, out);
|
|
143
|
+
} else if (entry.isFile()) {
|
|
144
|
+
out.push(p);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function run(): Promise<void> {
|
|
150
|
+
console.log('\nSeer Query-Assisted Walker Parity Test');
|
|
151
|
+
console.log('========================================\n');
|
|
152
|
+
|
|
153
|
+
const all: string[] = [];
|
|
154
|
+
await walkFixtures(FIXTURES_DIR, all);
|
|
155
|
+
// Also include the Track-C/D fixtures so we exercise the route + configKey
|
|
156
|
+
// extractors against the same parity check.
|
|
157
|
+
const trackcdDir = path.join(__dirname, 'fixtures-trackcd');
|
|
158
|
+
if (fs.existsSync(trackcdDir)) await walkFixtures(trackcdDir, all);
|
|
159
|
+
|
|
160
|
+
for (const f of all) {
|
|
161
|
+
const ext = path.extname(f).toLowerCase();
|
|
162
|
+
if (!['.py', '.pyw', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
163
|
+
'.go', '.java', '.rs', '.c', '.cpp', '.cc', '.cxx', '.c++',
|
|
164
|
+
'.hpp', '.hh', '.h++', '.h', '.cs'].includes(ext)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
await compareFile(f);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log('\n══════════════════════════════════════════════════════════════');
|
|
171
|
+
console.log(` Results: ${passed} passed, ${failed} failed`);
|
|
172
|
+
|
|
173
|
+
if (failed > 0) {
|
|
174
|
+
console.error('\n QUERY PARITY FAILED\n');
|
|
175
|
+
process.exit(1);
|
|
176
|
+
} else {
|
|
177
|
+
console.log('\n All query-parity tests passed! ✓\n');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
run().catch(err => {
|
|
182
|
+
console.error('Query parity test threw:', err);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Micro-bench: compare query-assisted vs baseline walker on the same fixtures.
|
|
3
|
+
* Not part of the standard test suite — run manually when changing the query
|
|
4
|
+
* path: npx tsx tests/query-perf.ts
|
|
5
|
+
*/
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import { parseFile, setForceBaselineWalker } from '../src/parser/index';
|
|
9
|
+
|
|
10
|
+
const FIXTURES_DIR = path.join(__dirname, 'fixtures');
|
|
11
|
+
const REPEATS = 5;
|
|
12
|
+
|
|
13
|
+
async function walkFixtures(dir: string, out: string[]): Promise<void> {
|
|
14
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
15
|
+
const p = path.join(dir, entry.name);
|
|
16
|
+
if (entry.isDirectory()) await walkFixtures(p, out);
|
|
17
|
+
else out.push(p);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function time(label: string, files: string[]): Promise<number> {
|
|
22
|
+
// Warmup
|
|
23
|
+
for (const f of files) await parseFile(fs.readFileSync(f, 'utf8'), f);
|
|
24
|
+
const start = Date.now();
|
|
25
|
+
for (let i = 0; i < REPEATS; i++) {
|
|
26
|
+
for (const f of files) await parseFile(fs.readFileSync(f, 'utf8'), f);
|
|
27
|
+
}
|
|
28
|
+
const elapsed = Date.now() - start;
|
|
29
|
+
console.log(` ${label.padEnd(30)} ${elapsed}ms (${files.length} files × ${REPEATS} = ${files.length * REPEATS} parses)`);
|
|
30
|
+
return elapsed;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function run(): Promise<void> {
|
|
34
|
+
const all: string[] = [];
|
|
35
|
+
await walkFixtures(FIXTURES_DIR, all);
|
|
36
|
+
const tcd = path.join(__dirname, 'fixtures-trackcd');
|
|
37
|
+
if (fs.existsSync(tcd)) await walkFixtures(tcd, all);
|
|
38
|
+
const fixtures = all.filter(f => /\.(ts|tsx|js|py|go|java|rs|c|cpp|cs|h|hpp)$/.test(f));
|
|
39
|
+
console.log(`\nQuery vs baseline walker micro-bench`);
|
|
40
|
+
console.log(`====================================\n`);
|
|
41
|
+
console.log(` fixtures: ${fixtures.length}`);
|
|
42
|
+
|
|
43
|
+
setForceBaselineWalker(false);
|
|
44
|
+
const queryMs = await time('query-assisted', fixtures);
|
|
45
|
+
|
|
46
|
+
setForceBaselineWalker(true);
|
|
47
|
+
const baselineMs = await time('baseline', fixtures);
|
|
48
|
+
|
|
49
|
+
setForceBaselineWalker(false);
|
|
50
|
+
|
|
51
|
+
const ratio = queryMs / baselineMs;
|
|
52
|
+
console.log(`\n query / baseline = ${ratio.toFixed(2)}x (${ratio < 1 ? 'query faster' : 'baseline faster'})`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
run().catch(err => { console.error(err); process.exit(1); });
|