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,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track I — Feature 3: Preflight Context.
|
|
3
|
+
*
|
|
4
|
+
* Symbol mode:
|
|
5
|
+
* - returns a packet for a known symbol containing risk, likely tests,
|
|
6
|
+
* service impact, history, and warnings.
|
|
7
|
+
* - missing symbol returns ok=false but command does not crash.
|
|
8
|
+
*
|
|
9
|
+
* Range mode (--from/--to):
|
|
10
|
+
* - a fixture branch with a single function change has its symbol mapped
|
|
11
|
+
* correctly into touchedSymbols.
|
|
12
|
+
*
|
|
13
|
+
* Run: npx tsx tests/tracki-preflight.ts
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import os from 'os';
|
|
19
|
+
import { spawnSync } from 'child_process';
|
|
20
|
+
import { Indexer } from '../src/indexer/index';
|
|
21
|
+
import { Store } from '../src/db/store';
|
|
22
|
+
import { preflight } from '../src/indexer/preflight';
|
|
23
|
+
|
|
24
|
+
const TMP = path.join(os.tmpdir(), `seer-tracki-preflight-${Date.now()}`);
|
|
25
|
+
const REPO = path.join(TMP, 'repo');
|
|
26
|
+
const DB = path.join(TMP, 'graph.db');
|
|
27
|
+
|
|
28
|
+
let passed = 0;
|
|
29
|
+
let failed = 0;
|
|
30
|
+
function assert(cond: boolean, msg: string): void {
|
|
31
|
+
if (cond) { console.log(` ✓ ${msg}`); passed++; }
|
|
32
|
+
else { console.error(` ✗ ${msg}`); failed++; }
|
|
33
|
+
}
|
|
34
|
+
function assertEq<T>(actual: T, expected: T, msg: string): void {
|
|
35
|
+
assert(actual === expected,
|
|
36
|
+
`${msg} (got ${JSON.stringify(actual)}, expected ${JSON.stringify(expected)})`);
|
|
37
|
+
}
|
|
38
|
+
function cleanup(): void {
|
|
39
|
+
try { fs.rmSync(TMP, { recursive: true, force: true }); } catch { /* */ }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function git(...args: string[]): { stdout: string; status: number; stderr: string } {
|
|
43
|
+
const r = spawnSync('git', ['-C', REPO, ...args], { encoding: 'utf8' });
|
|
44
|
+
return { stdout: r.stdout ?? '', status: r.status ?? 1, stderr: r.stderr ?? '' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function commit(message: string, author = 'Alice <alice@example.com>'): string {
|
|
48
|
+
git('add', '.');
|
|
49
|
+
const r = spawnSync(
|
|
50
|
+
'git',
|
|
51
|
+
['-C', REPO, '-c', `user.email=${author.replace(/^.* <(.+)>$/, '$1')}`,
|
|
52
|
+
'-c', `user.name=${author.replace(/ <.+>$/, '')}`,
|
|
53
|
+
'commit', '-m', message, '--no-gpg-sign'],
|
|
54
|
+
{ encoding: 'utf8' },
|
|
55
|
+
);
|
|
56
|
+
if (r.status !== 0) throw new Error(`git commit failed: ${r.stderr}`);
|
|
57
|
+
return git('rev-parse', 'HEAD').stdout.trim();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function write(rel: string, content: string): void {
|
|
61
|
+
const full = path.join(REPO, rel);
|
|
62
|
+
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
63
|
+
fs.writeFileSync(full, content, 'utf8');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function main(): Promise<void> {
|
|
67
|
+
console.log('\nSeer Track I — Feature 3: Preflight Context');
|
|
68
|
+
console.log('============================================\n');
|
|
69
|
+
cleanup();
|
|
70
|
+
fs.mkdirSync(REPO, { recursive: true });
|
|
71
|
+
|
|
72
|
+
spawnSync('git', ['-C', REPO, 'init', '-q', '-b', 'main'], { encoding: 'utf8' });
|
|
73
|
+
git('config', 'commit.gpgsign', 'false');
|
|
74
|
+
|
|
75
|
+
// Commit 1: initial files — `validateToken`, its caller, and a test for it.
|
|
76
|
+
write('src/auth.ts', `
|
|
77
|
+
export function validateToken(token: string): boolean {
|
|
78
|
+
if (!token) return false;
|
|
79
|
+
return token.length > 8;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function authenticate(req: { token: string }): boolean {
|
|
83
|
+
return validateToken(req.token);
|
|
84
|
+
}
|
|
85
|
+
`.trimStart());
|
|
86
|
+
write('src/api.ts', `
|
|
87
|
+
import { authenticate } from './auth';
|
|
88
|
+
declare const app: any;
|
|
89
|
+
export function loginHandler(req: any, res: any) {
|
|
90
|
+
if (!authenticate(req)) return res.status(401).send();
|
|
91
|
+
return res.send({ ok: true });
|
|
92
|
+
}
|
|
93
|
+
app.post('/api/login', loginHandler);
|
|
94
|
+
`.trimStart());
|
|
95
|
+
write('tests/auth.test.ts', `
|
|
96
|
+
import { validateToken } from '../src/auth';
|
|
97
|
+
|
|
98
|
+
function testValidateRejectsEmpty(): void {
|
|
99
|
+
const ok = validateToken('');
|
|
100
|
+
expect(ok).toBe(false);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function testValidateRejectsShort(): void {
|
|
104
|
+
const ok = validateToken('abc');
|
|
105
|
+
expect(ok).toBe(false);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function testValidateAcceptsLong(): void {
|
|
109
|
+
const ok = validateToken('abcdefghi');
|
|
110
|
+
expect(ok).toBe(true);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function expect(_v: unknown) {
|
|
114
|
+
return { toBe(_e: unknown): void { /* */ } };
|
|
115
|
+
}
|
|
116
|
+
`.trimStart());
|
|
117
|
+
const sha1 = commit('Initial: auth + api + tests');
|
|
118
|
+
|
|
119
|
+
// Index the repo.
|
|
120
|
+
const store = new Store(DB);
|
|
121
|
+
try {
|
|
122
|
+
const idx = new Indexer(store);
|
|
123
|
+
await idx.indexDirectory(REPO, { quiet: true });
|
|
124
|
+
|
|
125
|
+
// ── Symbol-mode preflight on validateToken ────────────────────────────
|
|
126
|
+
console.log('── Symbol-mode preflight ──');
|
|
127
|
+
const r = await preflight(store, { symbol: 'validateToken', workspace: REPO });
|
|
128
|
+
assertEq(r.ok, true, 'preflight ok=true for known symbol');
|
|
129
|
+
assertEq(r.mode, 'symbol', 'mode=symbol');
|
|
130
|
+
assert(r.symbol?.name === 'validateToken', 'symbol.name === "validateToken"');
|
|
131
|
+
assert(Array.isArray(r.touchedSymbols) && r.touchedSymbols.length === 1,
|
|
132
|
+
'touchedSymbols length === 1');
|
|
133
|
+
assert(['low', 'medium', 'high'].includes(r.risk.overall),
|
|
134
|
+
'risk.overall is one of low/medium/high');
|
|
135
|
+
assert(r.risk.perSymbol.length === 1, 'risk.perSymbol has one entry');
|
|
136
|
+
assert(r.risk.perSymbol[0].topContributors.length > 0,
|
|
137
|
+
'risk top contributors are listed');
|
|
138
|
+
assert(r.likelyTests.length >= 1,
|
|
139
|
+
`likelyTests includes at least one test (got ${r.likelyTests.length})`);
|
|
140
|
+
const validateTokenTest = r.likelyTests.find(t =>
|
|
141
|
+
t.testSymbol.file.includes('auth.test'));
|
|
142
|
+
assert(validateTokenTest != null,
|
|
143
|
+
'likelyTests references the validateToken test file');
|
|
144
|
+
|
|
145
|
+
// The symbol is not route-exposed, no service impact expected directly.
|
|
146
|
+
// (loginHandler exposes /api/login but validateToken is the leaf.)
|
|
147
|
+
|
|
148
|
+
// ── Symbol-mode service impact preserves the real protocol (regression) ─
|
|
149
|
+
// The ContextPacket preview drops protocol; symbol-mode preflight used to
|
|
150
|
+
// hardcode 'http', mislabeling tRPC/gRPC/Kafka links. Inject a gRPC link
|
|
151
|
+
// into loginHandler and assert the protocol survives.
|
|
152
|
+
console.log('\n── Symbol-mode service impact protocol fidelity ──');
|
|
153
|
+
{
|
|
154
|
+
const raw = store.rawDb();
|
|
155
|
+
const handler = store.getDefinition('loginHandler')[0];
|
|
156
|
+
const caller = store.getDefinition('authenticate')[0];
|
|
157
|
+
assert(handler != null && caller != null, 'handler + caller symbols resolved');
|
|
158
|
+
const handlerFileId = store.getSymbolById(handler.id)!.fileId;
|
|
159
|
+
const callRes = raw.prepare(`
|
|
160
|
+
INSERT INTO service_calls
|
|
161
|
+
(file_id, symbol_id, protocol, method, raw_target, normalized_path,
|
|
162
|
+
framework, line, confidence, operation, service)
|
|
163
|
+
VALUES (?, ?, 'grpc', NULL, 'AuthService/Login', NULL, 'grpc', 0, 1.0, 'Login', 'AuthService')
|
|
164
|
+
`).run(handlerFileId, caller.id);
|
|
165
|
+
const callId = Number(callRes.lastInsertRowid);
|
|
166
|
+
// route_id NULL — serviceLinksForHandler LEFT-JOINs routes, and the
|
|
167
|
+
// protocol we assert on comes from service_links.protocol directly.
|
|
168
|
+
raw.prepare(`
|
|
169
|
+
INSERT INTO service_links
|
|
170
|
+
(call_id, route_id, caller_symbol_id, handler_symbol_id, protocol,
|
|
171
|
+
match_kind, confidence, evidence_json)
|
|
172
|
+
VALUES (?, NULL, ?, ?, 'grpc', 'manual', 0.9, '{}')
|
|
173
|
+
`).run(callId, caller.id, handler.id);
|
|
174
|
+
|
|
175
|
+
const pf = await preflight(store, { symbol: 'loginHandler', workspace: REPO });
|
|
176
|
+
assertEq(pf.ok, true, 'preflight ok=true for loginHandler');
|
|
177
|
+
const grpcInbound = pf.serviceImpact.inbound.find(i => i.protocol === 'grpc');
|
|
178
|
+
assert(grpcInbound != null,
|
|
179
|
+
`inbound gRPC link reports protocol=grpc (got ${JSON.stringify(pf.serviceImpact.inbound.map(i => i.protocol))})`);
|
|
180
|
+
assert(!pf.serviceImpact.inbound.some(i => i.protocol === 'http'),
|
|
181
|
+
'no inbound link is mislabeled as http');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── Missing-symbol preflight does NOT crash ───────────────────────────
|
|
185
|
+
console.log('\n── Missing-symbol preflight ──');
|
|
186
|
+
const miss = await preflight(store, { symbol: 'doesNotExist', workspace: REPO });
|
|
187
|
+
assertEq(miss.ok, false, 'preflight ok=false when symbol is missing');
|
|
188
|
+
assert(miss.warnings.length >= 1, 'preflight returns a warning when symbol is missing');
|
|
189
|
+
assertEq(miss.mode, 'symbol', 'mode=symbol on missing-symbol path');
|
|
190
|
+
|
|
191
|
+
// ── Range-mode preflight (uncommitted changes touching validateToken) ─
|
|
192
|
+
console.log('\n── Range-mode preflight ──');
|
|
193
|
+
// Edit validateToken (uncommitted change).
|
|
194
|
+
write('src/auth.ts', `
|
|
195
|
+
export function validateToken(token: string): boolean {
|
|
196
|
+
// tightened: require non-empty AND minimum length of 12
|
|
197
|
+
if (!token) return false;
|
|
198
|
+
return token.length >= 12;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function authenticate(req: { token: string }): boolean {
|
|
202
|
+
return validateToken(req.token);
|
|
203
|
+
}
|
|
204
|
+
`.trimStart());
|
|
205
|
+
|
|
206
|
+
const rr = await preflight(store, {
|
|
207
|
+
range: true, workspace: REPO,
|
|
208
|
+
});
|
|
209
|
+
assertEq(rr.ok, true, 'range preflight ok=true');
|
|
210
|
+
assertEq(rr.mode, 'range', 'mode=range');
|
|
211
|
+
assert(rr.range != null, 'range metadata is present');
|
|
212
|
+
assert(rr.touchedSymbols.length >= 1,
|
|
213
|
+
`touchedSymbols contains the changed function (got ${rr.touchedSymbols.length})`);
|
|
214
|
+
const touchedNames = rr.touchedSymbols.map(s => s.name);
|
|
215
|
+
assert(touchedNames.includes('validateToken'),
|
|
216
|
+
`touchedSymbols includes validateToken (got ${touchedNames.join(',')})`);
|
|
217
|
+
assert(rr.likelyTests.length >= 1,
|
|
218
|
+
'range preflight aggregates likelyTests from touched symbols');
|
|
219
|
+
// The risk.perSymbol entry for validateToken must exist.
|
|
220
|
+
const vt = rr.risk.perSymbol.find(p => p.symbol.name === 'validateToken');
|
|
221
|
+
assert(vt != null, 'risk.perSymbol contains validateToken');
|
|
222
|
+
|
|
223
|
+
// ── Output is bounded by maxSymbols / maxTests / maxHistory ───────────
|
|
224
|
+
const bounded = await preflight(store, {
|
|
225
|
+
range: true, workspace: REPO,
|
|
226
|
+
maxSymbols: 1, maxTests: 1, maxHistory: 1,
|
|
227
|
+
});
|
|
228
|
+
assert(bounded.touchedSymbols.length <= 1, 'bounded touchedSymbols ≤ 1');
|
|
229
|
+
assert(bounded.likelyTests.length <= 1, 'bounded likelyTests ≤ 1');
|
|
230
|
+
assert(bounded.history.length <= 1, 'bounded history ≤ 1');
|
|
231
|
+
|
|
232
|
+
// ── No-source-mutation invariant ─────────────────────────────────────
|
|
233
|
+
// preflight should never call into write helpers — the simplest check is
|
|
234
|
+
// that the on-disk auth.ts is still our (modified) content.
|
|
235
|
+
const onDisk = fs.readFileSync(path.join(REPO, 'src/auth.ts'), 'utf8');
|
|
236
|
+
assert(onDisk.includes('length >= 12'),
|
|
237
|
+
'preflight did not mutate the source under test');
|
|
238
|
+
} finally { store.close(); }
|
|
239
|
+
void sha1;
|
|
240
|
+
|
|
241
|
+
console.log('\n────────────────────────────');
|
|
242
|
+
console.log(`Passed: ${passed} Failed: ${failed}`);
|
|
243
|
+
cleanup();
|
|
244
|
+
if (failed > 0) process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
main().catch(err => {
|
|
248
|
+
console.error(err);
|
|
249
|
+
cleanup();
|
|
250
|
+
process.exit(1);
|
|
251
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-scale verification: walk every produced DB and print
|
|
3
|
+
* - schema_version
|
|
4
|
+
* - file role breakdown
|
|
5
|
+
* - top-3 symbols (and whether any are in a vendored path)
|
|
6
|
+
*
|
|
7
|
+
* This catches regressions in classification / project-first ranking
|
|
8
|
+
* that the scale-test invariants don't quite cover.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import { Store } from '../src/db/store';
|
|
14
|
+
|
|
15
|
+
const NAMES = ['helix', 'client-go', 'react', 'godot', 'linux', 'typescript', 'unreal', 'cbm'];
|
|
16
|
+
|
|
17
|
+
function vendoredLooking(filePath: string): boolean {
|
|
18
|
+
const norm = filePath.replace(/\\/g, '/');
|
|
19
|
+
return /(^|\/)(vendor|vendored|thirdparty|third_party|external|node_modules)\//i.test(norm);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function main(): Promise<void> {
|
|
23
|
+
console.log('\nSeer DB Role Verification\n===========================\n');
|
|
24
|
+
for (const name of NAMES) {
|
|
25
|
+
const dbPath = path.join(__dirname, 'outputs', 'dbs', `${name}.db`);
|
|
26
|
+
if (!fs.existsSync(dbPath)) { console.log(` ${name.padEnd(12)} (no DB)`); continue; }
|
|
27
|
+
const s = Store.openReadOnly(dbPath);
|
|
28
|
+
try {
|
|
29
|
+
const schema = s.schemaInfo();
|
|
30
|
+
const stats = s.getStats();
|
|
31
|
+
const top3 = s.getTopSymbols(3);
|
|
32
|
+
const vendored = top3.filter(t => vendoredLooking(t.filePath));
|
|
33
|
+
const status = vendored.length === 0 ? 'OK' : `WARN ${vendored.length} top-3 vendored`;
|
|
34
|
+
console.log(` ${name.padEnd(12)}`);
|
|
35
|
+
console.log(` schema=${schema.dbVersion}/${schema.buildVersion}${schema.current ? ' ✓' : ' ⚠'}`);
|
|
36
|
+
console.log(` files=${stats.files.toLocaleString()} symbols=${stats.symbols.toLocaleString()} edges=${stats.edges.toLocaleString()}`);
|
|
37
|
+
console.log(` roles: project=${stats.roles?.project} vendor=${stats.roles?.vendor} generated=${stats.roles?.generated} test=${stats.roles?.test}`);
|
|
38
|
+
console.log(` top3:`);
|
|
39
|
+
for (const t of top3) {
|
|
40
|
+
const rel = t.filePath.replace(/\\/g, '/').split('/').slice(-3).join('/');
|
|
41
|
+
const flag = vendoredLooking(t.filePath) ? ' ← VENDORED' : '';
|
|
42
|
+
console.log(` ${t.name.padEnd(28)} pagerank=${t.pagerank.toFixed(5)} ${rel}${flag}`);
|
|
43
|
+
}
|
|
44
|
+
console.log(` => ${status}\n`);
|
|
45
|
+
} finally {
|
|
46
|
+
s.close();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
main().catch(err => { console.error(err); process.exit(1); });
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker-parity test (Step 2 of parallel parsing).
|
|
3
|
+
*
|
|
4
|
+
* Verifies that parsing a file inside a worker_threads `Worker` produces
|
|
5
|
+
* byte-identical results to parsing the same file in-process via
|
|
6
|
+
* `parseFile()`. Also exercises the cached / too-large / io-error result
|
|
7
|
+
* kinds and clean shutdown.
|
|
8
|
+
*
|
|
9
|
+
* The worker is the compiled `dist/parser/worker.js` artifact — this test
|
|
10
|
+
* requires `npm run build` to have run first (the `test:worker-parity`
|
|
11
|
+
* package script chains them).
|
|
12
|
+
*
|
|
13
|
+
* Run with: npm run test:worker-parity
|
|
14
|
+
*/
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import os from 'os';
|
|
18
|
+
import crypto from 'crypto';
|
|
19
|
+
import { Worker } from 'worker_threads';
|
|
20
|
+
import { parseFile, detectLanguage } from '../src/parser/index';
|
|
21
|
+
import type { Language, FileExtraction } from '../src/types';
|
|
22
|
+
import type { WorkerInput, WorkerOutput } from '../src/parser/worker';
|
|
23
|
+
|
|
24
|
+
const FIXTURES_DIR = path.join(__dirname, 'fixtures');
|
|
25
|
+
const FIXTURES_TRACKCD = path.join(__dirname, 'fixtures-trackcd');
|
|
26
|
+
const WORKER_PATH = path.join(__dirname, '..', 'dist', 'parser', 'worker.js');
|
|
27
|
+
|
|
28
|
+
let passed = 0;
|
|
29
|
+
let failed = 0;
|
|
30
|
+
|
|
31
|
+
function assert(condition: boolean, message: string): void {
|
|
32
|
+
if (condition) {
|
|
33
|
+
console.log(` ✓ ${message}`);
|
|
34
|
+
passed++;
|
|
35
|
+
} else {
|
|
36
|
+
console.error(` ✗ ${message}`);
|
|
37
|
+
failed++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sha256Short(content: string): string {
|
|
42
|
+
return crypto.createHash('sha256').update(content, 'utf8').digest('hex').slice(0, 16);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Walk a directory and collect all files (recursive). */
|
|
46
|
+
function listFiles(dir: string): string[] {
|
|
47
|
+
const out: string[] = [];
|
|
48
|
+
if (!fs.existsSync(dir)) return out;
|
|
49
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
50
|
+
const p = path.join(dir, entry.name);
|
|
51
|
+
if (entry.isDirectory()) out.push(...listFiles(p));
|
|
52
|
+
else out.push(p);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Canonicalize a FileExtraction for byte-equal comparison. JSON.stringify
|
|
59
|
+
* with sorted keys is enough here — the walker emits arrays in a
|
|
60
|
+
* deterministic traversal order so we don't need to sort them.
|
|
61
|
+
*/
|
|
62
|
+
function canon(obj: unknown): string {
|
|
63
|
+
return JSON.stringify(obj, (_k, v) => {
|
|
64
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
65
|
+
const sorted: Record<string, unknown> = {};
|
|
66
|
+
for (const k of Object.keys(v as Record<string, unknown>).sort()) {
|
|
67
|
+
sorted[k] = (v as Record<string, unknown>)[k];
|
|
68
|
+
}
|
|
69
|
+
return sorted;
|
|
70
|
+
}
|
|
71
|
+
return v;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Worker controller ────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
interface PendingResolver {
|
|
78
|
+
resolve: (out: WorkerOutput) => void;
|
|
79
|
+
reject: (err: Error) => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class WorkerController {
|
|
83
|
+
private worker: Worker;
|
|
84
|
+
private pending = new Map<number, PendingResolver>();
|
|
85
|
+
private ready: Promise<void>;
|
|
86
|
+
private exitPromise: Promise<number>;
|
|
87
|
+
private exitCode: number | null = null;
|
|
88
|
+
|
|
89
|
+
constructor(workerPath: string) {
|
|
90
|
+
this.worker = new Worker(workerPath);
|
|
91
|
+
let readyResolve: () => void = () => {};
|
|
92
|
+
this.ready = new Promise<void>(r => { readyResolve = r; });
|
|
93
|
+
let exitResolve: (code: number) => void = () => {};
|
|
94
|
+
this.exitPromise = new Promise<number>(r => { exitResolve = r; });
|
|
95
|
+
|
|
96
|
+
this.worker.on('message', (msg: WorkerOutput) => {
|
|
97
|
+
if (msg.kind === 'ready') { readyResolve(); return; }
|
|
98
|
+
if (msg.kind === 'shutdown-ack') return; // we observe via 'exit'
|
|
99
|
+
const seq = (msg as { seq: number }).seq;
|
|
100
|
+
const pending = this.pending.get(seq);
|
|
101
|
+
if (!pending) {
|
|
102
|
+
console.error(`unexpected result for seq=${seq}: ${msg.kind}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.pending.delete(seq);
|
|
106
|
+
pending.resolve(msg);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.worker.on('error', err => {
|
|
110
|
+
for (const [, p] of this.pending) p.reject(err);
|
|
111
|
+
this.pending.clear();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this.worker.on('exit', code => {
|
|
115
|
+
this.exitCode = code;
|
|
116
|
+
exitResolve(code);
|
|
117
|
+
const reason = new Error(`worker exited with code ${code}`);
|
|
118
|
+
for (const [, p] of this.pending) p.reject(reason);
|
|
119
|
+
this.pending.clear();
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
awaitReady(): Promise<void> { return this.ready; }
|
|
124
|
+
awaitExit(): Promise<number> { return this.exitPromise; }
|
|
125
|
+
getExitCode(): number | null { return this.exitCode; }
|
|
126
|
+
|
|
127
|
+
send(job: Extract<WorkerInput, { kind: 'parse' }>): Promise<WorkerOutput> {
|
|
128
|
+
return new Promise<WorkerOutput>((resolve, reject) => {
|
|
129
|
+
this.pending.set(job.seq, { resolve, reject });
|
|
130
|
+
this.worker.postMessage(job);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
shutdown(): void {
|
|
135
|
+
this.worker.postMessage({ kind: 'shutdown' } satisfies WorkerInput);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
terminate(): Promise<number> {
|
|
139
|
+
return this.worker.terminate();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Run ──────────────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
async function run(): Promise<void> {
|
|
146
|
+
console.log('\nWorker Parity Test (Step 2)');
|
|
147
|
+
console.log('============================\n');
|
|
148
|
+
|
|
149
|
+
if (!fs.existsSync(WORKER_PATH)) {
|
|
150
|
+
console.error(` ✗ ${WORKER_PATH} not found — run \`npm run build\` first.`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
console.log(` worker: ${WORKER_PATH}`);
|
|
154
|
+
|
|
155
|
+
const ctrl = new WorkerController(WORKER_PATH);
|
|
156
|
+
await ctrl.awaitReady();
|
|
157
|
+
console.log(' worker ready\n');
|
|
158
|
+
|
|
159
|
+
// ── 1. Parity over every fixture ───────────────────────────────────────────
|
|
160
|
+
console.log('── Per-fixture parity (worker vs in-process parseFile) ──');
|
|
161
|
+
|
|
162
|
+
const all = [...listFiles(FIXTURES_DIR), ...listFiles(FIXTURES_TRACKCD)];
|
|
163
|
+
let seq = 0;
|
|
164
|
+
let perFixtureChecks = 0;
|
|
165
|
+
for (const abs of all) {
|
|
166
|
+
const lang = detectLanguage(abs);
|
|
167
|
+
if (!lang) continue;
|
|
168
|
+
perFixtureChecks++;
|
|
169
|
+
seq++;
|
|
170
|
+
const content = fs.readFileSync(abs, 'utf8');
|
|
171
|
+
const reference = await parseFile(content, abs, lang);
|
|
172
|
+
const result = await ctrl.send({
|
|
173
|
+
kind: 'parse', seq, abs, lang, expectedHash: null, maxFileBytes: 0,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const ok = reference !== null
|
|
177
|
+
&& result.kind === 'parsed'
|
|
178
|
+
&& canon((result as Extract<WorkerOutput, { kind: 'parsed' }>).extraction) === canon(reference);
|
|
179
|
+
if (!ok) {
|
|
180
|
+
console.error(` seq=${seq} file=${path.relative(__dirname, abs)} kind=${result.kind}`);
|
|
181
|
+
if (result.kind === 'parsed' && reference !== null) {
|
|
182
|
+
// Print first 200 chars of diff for forensic value
|
|
183
|
+
const r = canon((result as Extract<WorkerOutput, { kind: 'parsed' }>).extraction);
|
|
184
|
+
const ref = canon(reference);
|
|
185
|
+
for (let i = 0; i < Math.min(r.length, ref.length); i++) {
|
|
186
|
+
if (r[i] !== ref[i]) {
|
|
187
|
+
console.error(` first diff at char ${i}:`);
|
|
188
|
+
console.error(` ref: …${ref.slice(Math.max(0, i - 40), i + 40)}…`);
|
|
189
|
+
console.error(` got: …${r.slice(Math.max(0, i - 40), i + 40)}…`);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
assert(ok, `parity ${path.relative(__dirname, abs)}`);
|
|
196
|
+
}
|
|
197
|
+
console.log(` (checked ${perFixtureChecks} fixtures)\n`);
|
|
198
|
+
|
|
199
|
+
// ── 2. Cached branch ───────────────────────────────────────────────────────
|
|
200
|
+
console.log('── Cached branch (expectedHash === actual hash → skip parse) ──');
|
|
201
|
+
|
|
202
|
+
// Pick a known fixture and exercise both cache-hit and cache-miss.
|
|
203
|
+
const tsFixture = path.join(FIXTURES_DIR, 'sample.ts');
|
|
204
|
+
if (fs.existsSync(tsFixture)) {
|
|
205
|
+
const content = fs.readFileSync(tsFixture, 'utf8');
|
|
206
|
+
const knownHash = sha256Short(content);
|
|
207
|
+
|
|
208
|
+
seq++;
|
|
209
|
+
const hit = await ctrl.send({
|
|
210
|
+
kind: 'parse', seq, abs: tsFixture, lang: 'typescript',
|
|
211
|
+
expectedHash: knownHash, maxFileBytes: 0,
|
|
212
|
+
});
|
|
213
|
+
assert(hit.kind === 'cached', `expectedHash matches → result.kind === 'cached'`);
|
|
214
|
+
if (hit.kind === 'cached') {
|
|
215
|
+
assert(hit.hash === knownHash, `cached result echoes the computed hash`);
|
|
216
|
+
assert(hit.lines > 0, `cached result reports line count`);
|
|
217
|
+
assert(hit.size > 0, `cached result reports byte size`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
seq++;
|
|
221
|
+
const miss = await ctrl.send({
|
|
222
|
+
kind: 'parse', seq, abs: tsFixture, lang: 'typescript',
|
|
223
|
+
expectedHash: 'deadbeefdeadbeef', maxFileBytes: 0,
|
|
224
|
+
});
|
|
225
|
+
assert(miss.kind === 'parsed', `expectedHash mismatch → result.kind === 'parsed'`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── 3. Too-large branch ────────────────────────────────────────────────────
|
|
229
|
+
console.log('\n── Too-large branch (maxFileBytes < size) ──');
|
|
230
|
+
|
|
231
|
+
if (fs.existsSync(tsFixture)) {
|
|
232
|
+
seq++;
|
|
233
|
+
const big = await ctrl.send({
|
|
234
|
+
kind: 'parse', seq, abs: tsFixture, lang: 'typescript',
|
|
235
|
+
expectedHash: null, maxFileBytes: 1,
|
|
236
|
+
});
|
|
237
|
+
assert(big.kind === 'too-large', `maxFileBytes=1 → 'too-large'`);
|
|
238
|
+
if (big.kind === 'too-large') {
|
|
239
|
+
assert(big.size > 1, `too-large reports the actual size (${big.size}B > 1B cap)`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── 4. IO-error branch ─────────────────────────────────────────────────────
|
|
244
|
+
console.log('\n── IO-error branch (path does not exist) ──');
|
|
245
|
+
|
|
246
|
+
seq++;
|
|
247
|
+
const ghost = await ctrl.send({
|
|
248
|
+
kind: 'parse', seq,
|
|
249
|
+
abs: path.join(os.tmpdir(), 'seer-worker-parity-nonexistent-' + Date.now() + '.ts'),
|
|
250
|
+
lang: 'typescript', expectedHash: null, maxFileBytes: 0,
|
|
251
|
+
});
|
|
252
|
+
assert(ghost.kind === 'io-error', `nonexistent path → 'io-error'`);
|
|
253
|
+
|
|
254
|
+
// ── 5. Worker is still alive after an io-error ─────────────────────────────
|
|
255
|
+
console.log('\n── Worker recovers from io-error (still parses next job) ──');
|
|
256
|
+
|
|
257
|
+
if (fs.existsSync(tsFixture)) {
|
|
258
|
+
seq++;
|
|
259
|
+
const after = await ctrl.send({
|
|
260
|
+
kind: 'parse', seq, abs: tsFixture, lang: 'typescript',
|
|
261
|
+
expectedHash: null, maxFileBytes: 0,
|
|
262
|
+
});
|
|
263
|
+
assert(after.kind === 'parsed', `worker still parses after an io-error`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ── 6. Clean shutdown ──────────────────────────────────────────────────────
|
|
267
|
+
console.log('\n── Clean shutdown ──');
|
|
268
|
+
ctrl.shutdown();
|
|
269
|
+
const code = await ctrl.awaitExit();
|
|
270
|
+
assert(code === 0, `worker exits with code 0 after shutdown (got ${code})`);
|
|
271
|
+
|
|
272
|
+
// ── Done ───────────────────────────────────────────────────────────────────
|
|
273
|
+
console.log('\n══════════════════════════════════════════════════════════════');
|
|
274
|
+
console.log(` Worker-parity results: ${passed} passed, ${failed} failed`);
|
|
275
|
+
if (failed > 0) {
|
|
276
|
+
console.error('\n WORKER-PARITY TESTS FAILED\n');
|
|
277
|
+
process.exit(1);
|
|
278
|
+
} else {
|
|
279
|
+
console.log('\n All worker-parity tests passed! ✓\n');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
run().catch(err => {
|
|
284
|
+
console.error('worker-parity test threw:', err);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
});
|