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,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP smoke test: spawn `seer mcp` as a subprocess, drive it over stdio,
|
|
3
|
+
* and verify each tool call returns sane JSON. The test acts as a minimal
|
|
4
|
+
* JSON-RPC 2.0 client.
|
|
5
|
+
*
|
|
6
|
+
* Run with: npx tsx tests/mcp-smoke.ts
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
|
|
14
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
15
|
+
const FIXTURES = path.join(ROOT, 'tests/fixtures');
|
|
16
|
+
const TMP_WS = path.join(os.tmpdir(), `seer-mcp-ws-${Date.now()}`);
|
|
17
|
+
const CLI = path.join(ROOT, 'dist/cli/index.js');
|
|
18
|
+
|
|
19
|
+
let passed = 0;
|
|
20
|
+
let failed = 0;
|
|
21
|
+
function ok(label: string): void { passed++; console.log(` ✓ ${label}`); }
|
|
22
|
+
function bad(label: string, extra?: unknown): void {
|
|
23
|
+
failed++;
|
|
24
|
+
console.error(` ✗ ${label}` + (extra !== undefined ? ` :: ${JSON.stringify(extra)}` : ''));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function main(): Promise<void> {
|
|
28
|
+
console.log('\nSeer MCP Smoke Test\n=====================\n');
|
|
29
|
+
|
|
30
|
+
// Build a tiny workspace from fixtures.
|
|
31
|
+
fs.mkdirSync(TMP_WS, { recursive: true });
|
|
32
|
+
for (const f of fs.readdirSync(FIXTURES)) {
|
|
33
|
+
fs.copyFileSync(path.join(FIXTURES, f), path.join(TMP_WS, f));
|
|
34
|
+
}
|
|
35
|
+
console.log(` Workspace: ${TMP_WS}`);
|
|
36
|
+
|
|
37
|
+
// Spawn the MCP server. Disable JIT/watcher for deterministic tests.
|
|
38
|
+
const proc = spawn(process.execPath, [CLI, 'mcp', '--workspace', TMP_WS, '--no-watch', '--no-jit'], {
|
|
39
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
40
|
+
});
|
|
41
|
+
proc.stderr.on('data', (d) => process.stderr.write(`[mcp-stderr] ${d}`));
|
|
42
|
+
|
|
43
|
+
// Buffer responses and dispatch by id.
|
|
44
|
+
let buf = '';
|
|
45
|
+
const pending = new Map<number, (msg: any) => void>();
|
|
46
|
+
proc.stdout.on('data', (chunk: Buffer) => {
|
|
47
|
+
buf += chunk.toString('utf8');
|
|
48
|
+
let nl: number;
|
|
49
|
+
while ((nl = buf.indexOf('\n')) >= 0) {
|
|
50
|
+
const line = buf.slice(0, nl).trim();
|
|
51
|
+
buf = buf.slice(nl + 1);
|
|
52
|
+
if (!line) continue;
|
|
53
|
+
let msg: any;
|
|
54
|
+
try { msg = JSON.parse(line); } catch { continue; }
|
|
55
|
+
if (msg.id != null && pending.has(msg.id)) {
|
|
56
|
+
pending.get(msg.id)!(msg);
|
|
57
|
+
pending.delete(msg.id);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
let nextId = 1;
|
|
63
|
+
function call(method: string, params: any): Promise<any> {
|
|
64
|
+
const id = nextId++;
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
pending.set(id, resolve);
|
|
67
|
+
const msg = JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n';
|
|
68
|
+
proc.stdin.write(msg);
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
if (pending.has(id)) { pending.delete(id); reject(new Error(`timeout: ${method}`)); }
|
|
71
|
+
}, 30_000);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Wait for the server to be ready by giving it a moment to spawn its
|
|
76
|
+
// initial index. We poll initialize until it succeeds.
|
|
77
|
+
let initOk = false;
|
|
78
|
+
for (let i = 0; i < 30; i++) {
|
|
79
|
+
try {
|
|
80
|
+
const r = await call('initialize', {
|
|
81
|
+
protocolVersion: '2024-11-05',
|
|
82
|
+
capabilities: {},
|
|
83
|
+
clientInfo: { name: 'mcp-smoke', version: '0.1.0' },
|
|
84
|
+
});
|
|
85
|
+
if (r.result) { initOk = true; break; }
|
|
86
|
+
} catch { /* not ready */ }
|
|
87
|
+
await new Promise(r => setTimeout(r, 500));
|
|
88
|
+
}
|
|
89
|
+
if (initOk) ok('initialize handshake'); else bad('initialize handshake (server never responded)');
|
|
90
|
+
|
|
91
|
+
// tools/list
|
|
92
|
+
const list = await call('tools/list', {});
|
|
93
|
+
const toolNames = (list.result?.tools ?? []).map((t: any) => t.name);
|
|
94
|
+
const expected = [
|
|
95
|
+
'seer_health', 'seer_stats', 'seer_symbols', 'seer_definition',
|
|
96
|
+
'seer_file_symbols', 'seer_callers', 'seer_callees', 'seer_search', 'seer_reindex',
|
|
97
|
+
];
|
|
98
|
+
for (const e of expected) {
|
|
99
|
+
if (toolNames.includes(e)) ok(`tools/list includes ${e}`);
|
|
100
|
+
else bad(`tools/list missing ${e}`, toolNames);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// seer_health
|
|
104
|
+
const health = await call('tools/call', { name: 'seer_health', arguments: {} });
|
|
105
|
+
const healthText = health.result?.content?.[0]?.text;
|
|
106
|
+
if (!healthText) { bad('seer_health returned no content'); }
|
|
107
|
+
else {
|
|
108
|
+
const parsed = JSON.parse(healthText);
|
|
109
|
+
if (parsed.schemaCurrent === true) ok('seer_health reports current schema');
|
|
110
|
+
else bad('seer_health schema not current', parsed.schemaVersion);
|
|
111
|
+
if (parsed.files > 0 && parsed.symbols > 0) ok(`seer_health files=${parsed.files} symbols=${parsed.symbols}`);
|
|
112
|
+
else bad('seer_health empty index', parsed);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// seer_symbols (top)
|
|
116
|
+
const top = await call('tools/call', { name: 'seer_symbols', arguments: { top: 5 } });
|
|
117
|
+
const topText = top.result?.content?.[0]?.text;
|
|
118
|
+
const topParsed = topText ? JSON.parse(topText) : null;
|
|
119
|
+
if (topParsed && Array.isArray(topParsed.items) && topParsed.items.length > 0) {
|
|
120
|
+
ok(`seer_symbols(top=5) returned ${topParsed.items.length} items`);
|
|
121
|
+
} else {
|
|
122
|
+
bad('seer_symbols(top=5) returned empty', topParsed);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// seer_symbols (query)
|
|
126
|
+
const q = await call('tools/call', { name: 'seer_symbols', arguments: { query: 'AuthService' } });
|
|
127
|
+
const qParsed = JSON.parse(q.result?.content?.[0]?.text ?? '{}');
|
|
128
|
+
if (qParsed.items?.some((i: any) => i.name === 'AuthService')) ok('seer_symbols(query=AuthService) found it');
|
|
129
|
+
else bad('seer_symbols(query=AuthService) did not find it', qParsed);
|
|
130
|
+
|
|
131
|
+
// seer_definition (exact)
|
|
132
|
+
const def = await call('tools/call', { name: 'seer_definition', arguments: { name: 'AuthService' } });
|
|
133
|
+
const defParsed = JSON.parse(def.result?.content?.[0]?.text ?? '{}');
|
|
134
|
+
if (defParsed.items?.length >= 1) ok(`seer_definition(AuthService) returned ${defParsed.items.length}`);
|
|
135
|
+
else bad('seer_definition(AuthService) empty', defParsed);
|
|
136
|
+
|
|
137
|
+
// seer_callers
|
|
138
|
+
const callers = await call('tools/call', { name: 'seer_callers', arguments: { symbol: 'AuthService' } });
|
|
139
|
+
const callersParsed = JSON.parse(callers.result?.content?.[0]?.text ?? '{}');
|
|
140
|
+
if (callersParsed.total >= 1) ok(`seer_callers(AuthService) total=${callersParsed.total}`);
|
|
141
|
+
else bad('seer_callers(AuthService) no callers', callersParsed);
|
|
142
|
+
|
|
143
|
+
// seer_callees
|
|
144
|
+
const callees = await call('tools/call', { name: 'seer_callees', arguments: { symbol: 'process_payment' } });
|
|
145
|
+
const calleesParsed = JSON.parse(callees.result?.content?.[0]?.text ?? '{}');
|
|
146
|
+
if (calleesParsed.total >= 1) ok(`seer_callees(process_payment) total=${calleesParsed.total}`);
|
|
147
|
+
else bad('seer_callees(process_payment) no callees', calleesParsed);
|
|
148
|
+
|
|
149
|
+
// seer_file_symbols
|
|
150
|
+
const fileSyms = await call('tools/call', {
|
|
151
|
+
name: 'seer_file_symbols',
|
|
152
|
+
arguments: { file: 'sample.ts' },
|
|
153
|
+
});
|
|
154
|
+
const fsParsed = JSON.parse(fileSyms.result?.content?.[0]?.text ?? '{}');
|
|
155
|
+
if (fsParsed.total >= 1) ok(`seer_file_symbols(sample.ts) total=${fsParsed.total}`);
|
|
156
|
+
else bad('seer_file_symbols(sample.ts) empty', fsParsed);
|
|
157
|
+
|
|
158
|
+
// seer_search
|
|
159
|
+
const search = await call('tools/call', { name: 'seer_search', arguments: { query: 'auth' } });
|
|
160
|
+
const sParsed = JSON.parse(search.result?.content?.[0]?.text ?? '{}');
|
|
161
|
+
if (sParsed.symbolHits?.returned >= 1) ok(`seer_search(auth) symbolHits=${sParsed.symbolHits.returned}`);
|
|
162
|
+
else bad('seer_search(auth) empty', sParsed);
|
|
163
|
+
|
|
164
|
+
// seer_reindex
|
|
165
|
+
const reindex = await call('tools/call', { name: 'seer_reindex', arguments: {} });
|
|
166
|
+
const rParsed = JSON.parse(reindex.result?.content?.[0]?.text ?? '{}');
|
|
167
|
+
if (typeof rParsed.elapsedMs === 'number') ok(`seer_reindex completed in ${rParsed.elapsedMs}ms`);
|
|
168
|
+
else bad('seer_reindex did not return elapsedMs', rParsed);
|
|
169
|
+
|
|
170
|
+
// seer_stats
|
|
171
|
+
const stats = await call('tools/call', { name: 'seer_stats', arguments: {} });
|
|
172
|
+
const statsParsed = JSON.parse(stats.result?.content?.[0]?.text ?? '{}');
|
|
173
|
+
if (statsParsed.files >= 5 && statsParsed.roles) ok(`seer_stats files=${statsParsed.files} role-aware`);
|
|
174
|
+
else bad('seer_stats missing role breakdown', statsParsed);
|
|
175
|
+
|
|
176
|
+
proc.stdin.end();
|
|
177
|
+
proc.kill();
|
|
178
|
+
await new Promise(r => setTimeout(r, 200));
|
|
179
|
+
|
|
180
|
+
// Cleanup
|
|
181
|
+
try { fs.rmSync(TMP_WS, { recursive: true, force: true }); } catch { /* */ }
|
|
182
|
+
|
|
183
|
+
console.log(`\n══════════════════════════════════════════════════════════════`);
|
|
184
|
+
console.log(` MCP results: ${passed} passed, ${failed} failed\n`);
|
|
185
|
+
if (failed > 0) process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
main().catch(err => {
|
|
189
|
+
console.error('MCP smoke crashed:', err);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP smoke test for Track-C/D tools. Spawns `seer mcp` against the
|
|
3
|
+
* fixtures-trackcd workspace and exercises every new tool over stdio.
|
|
4
|
+
*
|
|
5
|
+
* Run: npx tsx tests/mcp-trackcd.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
|
|
13
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
14
|
+
const FIX = path.join(ROOT, 'tests/fixtures-trackcd');
|
|
15
|
+
const TMP_WS = path.join(os.tmpdir(), `seer-mcp-cd-${Date.now()}`);
|
|
16
|
+
const CLI = path.join(ROOT, 'dist/cli/index.js');
|
|
17
|
+
|
|
18
|
+
let passed = 0;
|
|
19
|
+
let failed = 0;
|
|
20
|
+
const ok = (m: string): void => { passed++; console.log(` ✓ ${m}`); };
|
|
21
|
+
const bad = (m: string, x?: unknown): void => { failed++; console.error(` ✗ ${m}` + (x !== undefined ? ` :: ${JSON.stringify(x).slice(0, 200)}` : '')); };
|
|
22
|
+
|
|
23
|
+
function copyRecursive(src: string, dst: string): void {
|
|
24
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
25
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
26
|
+
const s = path.join(src, entry.name);
|
|
27
|
+
const d = path.join(dst, entry.name);
|
|
28
|
+
if (entry.isDirectory()) copyRecursive(s, d);
|
|
29
|
+
else fs.copyFileSync(s, d);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function main(): Promise<void> {
|
|
34
|
+
console.log('\nSeer MCP Track-C/D Smoke\n==========================\n');
|
|
35
|
+
copyRecursive(FIX, TMP_WS);
|
|
36
|
+
console.log(` Workspace: ${TMP_WS}`);
|
|
37
|
+
|
|
38
|
+
const proc = spawn(process.execPath, [CLI, 'mcp', '--workspace', TMP_WS, '--no-watch', '--no-jit'],
|
|
39
|
+
{ stdio: ['pipe', 'pipe', 'pipe'] });
|
|
40
|
+
proc.stderr.on('data', (d) => process.stderr.write(`[mcp-stderr] ${d}`));
|
|
41
|
+
|
|
42
|
+
let buf = '';
|
|
43
|
+
const pending = new Map<number, (msg: any) => void>();
|
|
44
|
+
proc.stdout.on('data', (chunk: Buffer) => {
|
|
45
|
+
buf += chunk.toString('utf8');
|
|
46
|
+
let nl: number;
|
|
47
|
+
while ((nl = buf.indexOf('\n')) >= 0) {
|
|
48
|
+
const line = buf.slice(0, nl).trim();
|
|
49
|
+
buf = buf.slice(nl + 1);
|
|
50
|
+
if (!line) continue;
|
|
51
|
+
let msg: any;
|
|
52
|
+
try { msg = JSON.parse(line); } catch { continue; }
|
|
53
|
+
if (msg.id != null && pending.has(msg.id)) {
|
|
54
|
+
pending.get(msg.id)!(msg);
|
|
55
|
+
pending.delete(msg.id);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
let nextId = 1;
|
|
61
|
+
const call = (method: string, params: any): Promise<any> => {
|
|
62
|
+
const id = nextId++;
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
pending.set(id, resolve);
|
|
65
|
+
proc.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
if (pending.has(id)) { pending.delete(id); reject(new Error(`timeout ${method}`)); }
|
|
68
|
+
}, 30_000);
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Wait for ready
|
|
73
|
+
let initOk = false;
|
|
74
|
+
for (let i = 0; i < 30; i++) {
|
|
75
|
+
try {
|
|
76
|
+
const r = await call('initialize', {
|
|
77
|
+
protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 't', version: '0' },
|
|
78
|
+
});
|
|
79
|
+
if (r.result) { initOk = true; break; }
|
|
80
|
+
} catch { /* */ }
|
|
81
|
+
await new Promise(r => setTimeout(r, 500));
|
|
82
|
+
}
|
|
83
|
+
if (initOk) ok('initialize'); else { bad('initialize'); process.exit(1); }
|
|
84
|
+
|
|
85
|
+
// tools/list — verify the new tools are advertised
|
|
86
|
+
const list = await call('tools/list', {});
|
|
87
|
+
const names: string[] = (list.result?.tools ?? []).map((t: any) => t.name);
|
|
88
|
+
const newTools = [
|
|
89
|
+
'seer_routes', 'seer_dependencies', 'seer_config',
|
|
90
|
+
'seer_complexity', 'seer_behavior', 'seer_trace_path',
|
|
91
|
+
'seer_architecture', 'seer_detect_changes', 'seer_churn',
|
|
92
|
+
'seer_history', 'seer_symbol_history_build',
|
|
93
|
+
];
|
|
94
|
+
for (const n of newTools) {
|
|
95
|
+
if (names.includes(n)) ok(`tools/list advertises ${n}`);
|
|
96
|
+
else bad(`tools/list missing ${n}`, names);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const callTool = async (name: string, args: any = {}): Promise<any> => {
|
|
100
|
+
const r = await call('tools/call', { name, arguments: args });
|
|
101
|
+
return JSON.parse(r.result?.content?.[0]?.text ?? '{}');
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// seer_routes
|
|
105
|
+
const routes = await callTool('seer_routes', { limit: 100 });
|
|
106
|
+
if (routes.total >= 10) ok(`seer_routes total=${routes.total}`); else bad('seer_routes empty', routes);
|
|
107
|
+
if (routes.items?.some((r: any) => r.framework === 'fastapi')) ok('seer_routes includes fastapi'); else bad('no fastapi route', routes);
|
|
108
|
+
|
|
109
|
+
// seer_dependencies
|
|
110
|
+
const deps = await callTool('seer_dependencies', { limit: 100 });
|
|
111
|
+
if (deps.total >= 8) ok(`seer_dependencies total=${deps.total}`); else bad('seer_dependencies low', deps);
|
|
112
|
+
if (deps.items?.some((d: any) => d.name === 'express')) ok('seer_dependencies includes express'); else bad('no express dep', deps);
|
|
113
|
+
|
|
114
|
+
// seer_config
|
|
115
|
+
const cfg = await callTool('seer_config', { limit: 100 });
|
|
116
|
+
if (cfg.total >= 4) ok(`seer_config total=${cfg.total}`); else bad('seer_config low', cfg);
|
|
117
|
+
if (cfg.items?.some((c: any) => c.key === 'DATABASE_URL')) ok('seer_config has DATABASE_URL'); else bad('no DATABASE_URL', cfg);
|
|
118
|
+
|
|
119
|
+
// seer_complexity
|
|
120
|
+
const cmpx = await callTool('seer_complexity', { by: 'cyclomatic', minValue: 3, limit: 20 });
|
|
121
|
+
if (cmpx.returned >= 1) ok(`seer_complexity returned=${cmpx.returned}`); else bad('seer_complexity empty', cmpx);
|
|
122
|
+
if (cmpx.items?.[0]?.cyclomatic >= 3) ok(`seer_complexity sorted desc (top=${cmpx.items[0].cyclomatic})`); else bad('sort wrong', cmpx);
|
|
123
|
+
|
|
124
|
+
// seer_behavior — login is exercised by the test
|
|
125
|
+
const beh = await callTool('seer_behavior', { symbol: 'login' });
|
|
126
|
+
if (beh.total >= 1) ok(`seer_behavior(login) total=${beh.total}`); else bad('seer_behavior empty', beh);
|
|
127
|
+
|
|
128
|
+
// seer_trace_path
|
|
129
|
+
const trace = await callTool('seer_trace_path', { from: 'login', to: 'validateCredentials' });
|
|
130
|
+
if (trace.found && trace.depth >= 1) ok(`seer_trace_path login → validateCredentials depth=${trace.depth}`);
|
|
131
|
+
else bad('seer_trace_path failed', trace);
|
|
132
|
+
|
|
133
|
+
// seer_architecture
|
|
134
|
+
const arch = await callTool('seer_architecture', {});
|
|
135
|
+
if (arch.totals?.routes >= 10) ok(`seer_architecture routes=${arch.totals.routes}`); else bad('arch routes', arch.totals);
|
|
136
|
+
if (arch.languages?.length >= 3) ok(`seer_architecture languages=${arch.languages.length}`); else bad('arch langs', arch.languages);
|
|
137
|
+
|
|
138
|
+
// seer_symbols with FTS (the new code path)
|
|
139
|
+
const sym = await callTool('seer_symbols', { query: 'validate', limit: 10 });
|
|
140
|
+
if (sym.items?.some((s: any) => s.name === 'validateCredentials'))
|
|
141
|
+
ok('seer_symbols FTS finds validateCredentials by "validate" (camelCase split)');
|
|
142
|
+
else bad('seer_symbols FTS broken', sym);
|
|
143
|
+
|
|
144
|
+
// seer_search
|
|
145
|
+
const search = await callTool('seer_search', { query: 'auth' });
|
|
146
|
+
if (search.symbolHits?.returned >= 1) ok(`seer_search(auth) symbolHits=${search.symbolHits.returned}`);
|
|
147
|
+
else bad('seer_search empty', search);
|
|
148
|
+
|
|
149
|
+
// seer_churn — not a git repo, should return zero (cleanly)
|
|
150
|
+
const ch = await callTool('seer_churn', {});
|
|
151
|
+
if (typeof ch.elapsedMs === 'number') ok(`seer_churn returned ms=${ch.elapsedMs}`);
|
|
152
|
+
else bad('seer_churn broken', ch);
|
|
153
|
+
|
|
154
|
+
// seer_health — should report routes, externalDeps, configKeys totals now
|
|
155
|
+
const health = await callTool('seer_health', {});
|
|
156
|
+
if (health.routes >= 10) ok(`seer_health routes=${health.routes}`); else bad('health routes', health);
|
|
157
|
+
if (health.externalDependencies >= 8) ok(`seer_health deps=${health.externalDependencies}`); else bad('health deps', health);
|
|
158
|
+
if (health.configKeys >= 4) ok(`seer_health configKeys=${health.configKeys}`); else bad('health configKeys', health);
|
|
159
|
+
|
|
160
|
+
proc.stdin.end(); proc.kill();
|
|
161
|
+
await new Promise(r => setTimeout(r, 200));
|
|
162
|
+
try { fs.rmSync(TMP_WS, { recursive: true, force: true }); } catch { /* */ }
|
|
163
|
+
|
|
164
|
+
console.log(`\n══════════════════════════════════════════════════════════════`);
|
|
165
|
+
console.log(` MCP Track-C/D: ${passed} passed, ${failed} failed`);
|
|
166
|
+
if (failed > 0) process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
main().catch(err => { console.error('mcp-trackcd crashed:', err); process.exit(1); });
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP smoke test for Track-E tools. Spawns `seer mcp` against the
|
|
3
|
+
* fixtures-tracke workspace and exercises every new tool over stdio.
|
|
4
|
+
*
|
|
5
|
+
* Run: npx tsx tests/mcp-tracke.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
|
|
13
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
14
|
+
const FIX = path.join(ROOT, 'tests/fixtures-tracke');
|
|
15
|
+
const TMP_WS = path.join(os.tmpdir(), `seer-mcp-e-${Date.now()}`);
|
|
16
|
+
const CLI = path.join(ROOT, 'dist/cli/index.js');
|
|
17
|
+
|
|
18
|
+
let passed = 0;
|
|
19
|
+
let failed = 0;
|
|
20
|
+
const ok = (m: string): void => { passed++; console.log(` ✓ ${m}`); };
|
|
21
|
+
const bad = (m: string, x?: unknown): void => { failed++; console.error(` ✗ ${m}` + (x !== undefined ? ` :: ${JSON.stringify(x).slice(0, 200)}` : '')); };
|
|
22
|
+
|
|
23
|
+
function copyRecursive(src: string, dst: string): void {
|
|
24
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
25
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
26
|
+
const s = path.join(src, entry.name);
|
|
27
|
+
const d = path.join(dst, entry.name);
|
|
28
|
+
if (entry.isDirectory()) copyRecursive(s, d);
|
|
29
|
+
else fs.copyFileSync(s, d);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function main(): Promise<void> {
|
|
34
|
+
console.log('\nSeer MCP Track-E Smoke\n==========================\n');
|
|
35
|
+
copyRecursive(FIX, TMP_WS);
|
|
36
|
+
console.log(` Workspace: ${TMP_WS}`);
|
|
37
|
+
|
|
38
|
+
const proc = spawn(process.execPath, [CLI, 'mcp', '--workspace', TMP_WS, '--no-watch', '--no-jit'],
|
|
39
|
+
{ stdio: ['pipe', 'pipe', 'pipe'] });
|
|
40
|
+
proc.stderr.on('data', (d) => process.stderr.write(`[mcp-stderr] ${d}`));
|
|
41
|
+
|
|
42
|
+
let buf = '';
|
|
43
|
+
const pending = new Map<number, (msg: any) => void>();
|
|
44
|
+
proc.stdout.on('data', (chunk: Buffer) => {
|
|
45
|
+
buf += chunk.toString('utf8');
|
|
46
|
+
let nl: number;
|
|
47
|
+
while ((nl = buf.indexOf('\n')) >= 0) {
|
|
48
|
+
const line = buf.slice(0, nl).trim();
|
|
49
|
+
buf = buf.slice(nl + 1);
|
|
50
|
+
if (!line) continue;
|
|
51
|
+
let msg: any;
|
|
52
|
+
try { msg = JSON.parse(line); } catch { continue; }
|
|
53
|
+
if (msg.id != null && pending.has(msg.id)) {
|
|
54
|
+
pending.get(msg.id)!(msg);
|
|
55
|
+
pending.delete(msg.id);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
let nextId = 1;
|
|
61
|
+
const call = (method: string, params: any): Promise<any> => {
|
|
62
|
+
const id = nextId++;
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
pending.set(id, resolve);
|
|
65
|
+
proc.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
if (pending.has(id)) { pending.delete(id); reject(new Error(`timeout ${method}`)); }
|
|
68
|
+
}, 30_000);
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Wait for ready
|
|
73
|
+
let initOk = false;
|
|
74
|
+
for (let i = 0; i < 30; i++) {
|
|
75
|
+
try {
|
|
76
|
+
const r = await call('initialize', {
|
|
77
|
+
protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 't', version: '0' },
|
|
78
|
+
});
|
|
79
|
+
if (r.result) { initOk = true; break; }
|
|
80
|
+
} catch { /* */ }
|
|
81
|
+
await new Promise(r => setTimeout(r, 500));
|
|
82
|
+
}
|
|
83
|
+
if (initOk) ok('initialize'); else { bad('initialize'); process.exit(1); }
|
|
84
|
+
|
|
85
|
+
// tools/list — verify Track-E tools are advertised
|
|
86
|
+
const list = await call('tools/list', {});
|
|
87
|
+
const names: string[] = (list.result?.tools ?? []).map((t: any) => t.name);
|
|
88
|
+
const trackeTools = [
|
|
89
|
+
'seer_modules', 'seer_module_members', 'seer_symbol_module',
|
|
90
|
+
'seer_module_dependencies', 'seer_modules_build',
|
|
91
|
+
'seer_trace_file_dependencies', 'seer_trace_module_dependencies',
|
|
92
|
+
'seer_trace_callers', 'seer_trace_callees',
|
|
93
|
+
'seer_risk', 'seer_context',
|
|
94
|
+
];
|
|
95
|
+
for (const n of trackeTools) {
|
|
96
|
+
if (names.includes(n)) ok(`tools/list advertises ${n}`);
|
|
97
|
+
else bad(`tools/list missing ${n}`, names);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const callTool = async (name: string, args: any = {}): Promise<any> => {
|
|
101
|
+
const r = await call('tools/call', { name, arguments: args });
|
|
102
|
+
return JSON.parse(r.result?.content?.[0]?.text ?? '{}');
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// seer_modules
|
|
106
|
+
const mods = await callTool('seer_modules', { limit: 10 });
|
|
107
|
+
if (mods.total >= 2) ok(`seer_modules total=${mods.total}`); else bad('seer_modules empty', mods);
|
|
108
|
+
if (mods.items?.some((m: any) => m.label === 'auth')) ok('seer_modules contains auth'); else bad('no auth module', mods);
|
|
109
|
+
|
|
110
|
+
const authModule = mods.items?.find((m: any) => m.label === 'auth');
|
|
111
|
+
const billingModule = mods.items?.find((m: any) => m.label === 'billing');
|
|
112
|
+
if (!authModule || !billingModule) { bad('auth/billing modules not found'); process.exit(1); }
|
|
113
|
+
|
|
114
|
+
// seer_module_members by id
|
|
115
|
+
const members = await callTool('seer_module_members', { id: authModule.id });
|
|
116
|
+
if (members.files?.total >= 2) ok(`seer_module_members(auth) files=${members.files.total}`);
|
|
117
|
+
else bad('auth module has < 2 files', members);
|
|
118
|
+
if (members.topSymbols?.items?.some((s: any) => s.name === 'AuthService'))
|
|
119
|
+
ok('seer_module_members topSymbols includes AuthService');
|
|
120
|
+
else bad('no AuthService in topSymbols', members.topSymbols);
|
|
121
|
+
|
|
122
|
+
// seer_module_members by label
|
|
123
|
+
const membersByLabel = await callTool('seer_module_members', { label: 'billing' });
|
|
124
|
+
if (membersByLabel.module?.label === 'billing') ok('seer_module_members works by label');
|
|
125
|
+
else bad('seer_module_members by label failed', membersByLabel);
|
|
126
|
+
|
|
127
|
+
// seer_module_members for missing label
|
|
128
|
+
const missingMember = await callTool('seer_module_members', { label: '__no_such_module__' });
|
|
129
|
+
if (missingMember.found === false) ok('seer_module_members returns found=false for unknown label');
|
|
130
|
+
else bad('seer_module_members did not surface missing label', missingMember);
|
|
131
|
+
|
|
132
|
+
// seer_symbol_module
|
|
133
|
+
const sm = await callTool('seer_symbol_module', { symbol: 'validateCredentials' });
|
|
134
|
+
if (sm.matches?.some((m: any) => m.module?.label === 'auth'))
|
|
135
|
+
ok('seer_symbol_module(validateCredentials) → auth');
|
|
136
|
+
else bad('seer_symbol_module wrong module', sm);
|
|
137
|
+
|
|
138
|
+
// seer_module_dependencies — billing → auth
|
|
139
|
+
const deps = await callTool('seer_module_dependencies', { label: 'billing', direction: 'out' });
|
|
140
|
+
if (deps.items?.some((d: any) => d.label === 'auth'))
|
|
141
|
+
ok('seer_module_dependencies(billing, out) includes auth');
|
|
142
|
+
else bad('billing→auth dep missing', deps);
|
|
143
|
+
|
|
144
|
+
// seer_module_dependencies — direction=in
|
|
145
|
+
const depsIn = await callTool('seer_module_dependencies', { label: 'auth', direction: 'in' });
|
|
146
|
+
if (depsIn.items?.some((d: any) => d.label === 'billing'))
|
|
147
|
+
ok('seer_module_dependencies(auth, in) includes billing');
|
|
148
|
+
else bad('auth←billing dep missing (in)', depsIn);
|
|
149
|
+
|
|
150
|
+
// seer_trace_file_dependencies — billing/Billing.ts → auth/AuthService.ts
|
|
151
|
+
const fdep = await callTool('seer_trace_file_dependencies', { file: 'billing/Billing.ts', maxDepth: 3 });
|
|
152
|
+
if (fdep.items?.some((c: any) => c.relPath?.includes('auth/AuthService')))
|
|
153
|
+
ok('seer_trace_file_dependencies reaches auth/AuthService.ts');
|
|
154
|
+
else bad('file closure missing auth', fdep);
|
|
155
|
+
|
|
156
|
+
// seer_trace_module_dependencies — billing → auth
|
|
157
|
+
const mdep = await callTool('seer_trace_module_dependencies', { label: 'billing', direction: 'out' });
|
|
158
|
+
if (mdep.items?.some((m: any) => m.label === 'auth'))
|
|
159
|
+
ok('seer_trace_module_dependencies(billing, out) reaches auth');
|
|
160
|
+
else bad('module trace missing auth', mdep);
|
|
161
|
+
|
|
162
|
+
// seer_trace_callers — validateCredentials has transitive callers
|
|
163
|
+
const trc = await callTool('seer_trace_callers', { symbol: 'validateCredentials', maxDepth: 4 });
|
|
164
|
+
if ((trc.total ?? 0) >= 1) ok(`seer_trace_callers total=${trc.total}`);
|
|
165
|
+
else bad('seer_trace_callers empty', trc);
|
|
166
|
+
|
|
167
|
+
// seer_trace_callees — chargeCustomer reaches validateCredentials
|
|
168
|
+
const trCe = await callTool('seer_trace_callees', { symbol: 'chargeCustomer', maxDepth: 5 });
|
|
169
|
+
if (trCe.items?.some((i: any) => i.name === 'validateCredentials'))
|
|
170
|
+
ok('seer_trace_callees(chargeCustomer) reaches validateCredentials');
|
|
171
|
+
else bad('forward trace did not reach target', trCe);
|
|
172
|
+
|
|
173
|
+
// seer_behavior 2.0 — direct + indirect counts
|
|
174
|
+
const beh = await callTool('seer_behavior', { symbol: 'validateCredentials', limit: 20 });
|
|
175
|
+
if ((beh.direct ?? 0) >= 1) ok(`seer_behavior direct=${beh.direct}`);
|
|
176
|
+
else bad('seer_behavior no direct tests', beh);
|
|
177
|
+
if ((beh.indirect ?? 0) >= 1) ok(`seer_behavior indirect=${beh.indirect}`);
|
|
178
|
+
else bad('seer_behavior no indirect tests', beh);
|
|
179
|
+
if (Array.isArray(beh.tests) && beh.tests[0]?.relationship === 'direct-call')
|
|
180
|
+
ok('seer_behavior ranks direct tests first');
|
|
181
|
+
else bad('seer_behavior ranking wrong', beh.tests);
|
|
182
|
+
|
|
183
|
+
// seer_risk
|
|
184
|
+
const risk = await callTool('seer_risk', { symbol: 'chargeCustomer' });
|
|
185
|
+
if (risk.signals?.routeExposed) ok('seer_risk(chargeCustomer) routeExposed=true');
|
|
186
|
+
else bad('seer_risk routeExposed false', risk.signals);
|
|
187
|
+
if (Array.isArray(risk.signalContributions) && risk.signalContributions.length >= 10)
|
|
188
|
+
ok(`seer_risk signalContributions=${risk.signalContributions.length}`);
|
|
189
|
+
else bad('seer_risk signals incomplete', risk.signalContributions);
|
|
190
|
+
if (['low', 'medium', 'high'].includes(risk.risk))
|
|
191
|
+
ok(`seer_risk verdict=${risk.risk}`);
|
|
192
|
+
else bad('seer_risk verdict invalid', risk);
|
|
193
|
+
|
|
194
|
+
// seer_context — must include every Track-E section
|
|
195
|
+
const ctx = await callTool('seer_context', { symbol: 'validateCredentials' });
|
|
196
|
+
if (ctx.symbol?.name === 'validateCredentials') ok('seer_context symbol returned');
|
|
197
|
+
else bad('seer_context missing symbol', ctx);
|
|
198
|
+
if (ctx.module?.label === 'auth') ok('seer_context.module=auth');
|
|
199
|
+
else bad('seer_context module wrong', ctx.module);
|
|
200
|
+
if (ctx.callers?.total >= 1) ok('seer_context.callers.total ≥ 1');
|
|
201
|
+
else bad('seer_context callers empty', ctx.callers);
|
|
202
|
+
if (Array.isArray(ctx.behavior?.preview)) ok('seer_context.behavior present');
|
|
203
|
+
else bad('seer_context behavior missing', ctx.behavior);
|
|
204
|
+
if (Array.isArray(ctx.risk?.signalContributions)) ok('seer_context.risk.signalContributions present');
|
|
205
|
+
else bad('seer_context risk missing', ctx.risk);
|
|
206
|
+
if (Array.isArray(ctx.blastRadius?.topAffected)) ok('seer_context.blastRadius present');
|
|
207
|
+
else bad('seer_context blastRadius missing', ctx.blastRadius);
|
|
208
|
+
|
|
209
|
+
// seer_modules_build — idempotent
|
|
210
|
+
const rebuild = await callTool('seer_modules_build', {});
|
|
211
|
+
if (typeof rebuild.modules === 'number' && rebuild.modules === mods.total)
|
|
212
|
+
ok(`seer_modules_build idempotent (${rebuild.modules} modules)`);
|
|
213
|
+
else bad('seer_modules_build not idempotent', rebuild);
|
|
214
|
+
|
|
215
|
+
// seer_health surfaces modules
|
|
216
|
+
const health = await callTool('seer_health', {});
|
|
217
|
+
if ((health.modules ?? 0) >= 2) ok(`seer_health modules=${health.modules}`);
|
|
218
|
+
else bad('seer_health.modules low', health);
|
|
219
|
+
|
|
220
|
+
proc.stdin.end(); proc.kill();
|
|
221
|
+
await new Promise(r => setTimeout(r, 200));
|
|
222
|
+
try { fs.rmSync(TMP_WS, { recursive: true, force: true }); } catch { /* */ }
|
|
223
|
+
|
|
224
|
+
console.log(`\n══════════════════════════════════════════════════════════════`);
|
|
225
|
+
console.log(` MCP Track-E: ${passed} passed, ${failed} failed`);
|
|
226
|
+
if (failed > 0) process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
main().catch(err => { console.error('mcp-tracke crashed:', err); process.exit(1); });
|