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.
Files changed (371) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/LICENSE +176 -0
  3. package/README.md +272 -0
  4. package/README_dev.md +199 -0
  5. package/dist/bundle/ci.d.ts +47 -0
  6. package/dist/bundle/ci.d.ts.map +1 -0
  7. package/dist/bundle/ci.js +113 -0
  8. package/dist/bundle/ci.js.map +1 -0
  9. package/dist/bundle/contract.d.ts +111 -0
  10. package/dist/bundle/contract.d.ts.map +1 -0
  11. package/dist/bundle/contract.js +352 -0
  12. package/dist/bundle/contract.js.map +1 -0
  13. package/dist/bundle/export.d.ts +36 -0
  14. package/dist/bundle/export.d.ts.map +1 -0
  15. package/dist/bundle/export.js +152 -0
  16. package/dist/bundle/export.js.map +1 -0
  17. package/dist/bundle/external.d.ts +66 -0
  18. package/dist/bundle/external.d.ts.map +1 -0
  19. package/dist/bundle/external.js +238 -0
  20. package/dist/bundle/external.js.map +1 -0
  21. package/dist/bundle/format.d.ts +94 -0
  22. package/dist/bundle/format.d.ts.map +1 -0
  23. package/dist/bundle/format.js +42 -0
  24. package/dist/bundle/format.js.map +1 -0
  25. package/dist/bundle/import.d.ts +49 -0
  26. package/dist/bundle/import.d.ts.map +1 -0
  27. package/dist/bundle/import.js +116 -0
  28. package/dist/bundle/import.js.map +1 -0
  29. package/dist/cli/index.d.ts +3 -0
  30. package/dist/cli/index.d.ts.map +1 -0
  31. package/dist/cli/index.js +1402 -0
  32. package/dist/cli/index.js.map +1 -0
  33. package/dist/cli/init.d.ts +48 -0
  34. package/dist/cli/init.d.ts.map +1 -0
  35. package/dist/cli/init.js +284 -0
  36. package/dist/cli/init.js.map +1 -0
  37. package/dist/db/schema.d.ts +3 -0
  38. package/dist/db/schema.d.ts.map +1 -0
  39. package/dist/db/schema.js +616 -0
  40. package/dist/db/schema.js.map +1 -0
  41. package/dist/db/store.d.ts +1011 -0
  42. package/dist/db/store.d.ts.map +1 -0
  43. package/dist/db/store.js +3888 -0
  44. package/dist/db/store.js.map +1 -0
  45. package/dist/graph/pagerank.d.ts +9 -0
  46. package/dist/graph/pagerank.d.ts.map +1 -0
  47. package/dist/graph/pagerank.js +47 -0
  48. package/dist/graph/pagerank.js.map +1 -0
  49. package/dist/indexer/architecture.d.ts +72 -0
  50. package/dist/indexer/architecture.d.ts.map +1 -0
  51. package/dist/indexer/architecture.js +112 -0
  52. package/dist/indexer/architecture.js.map +1 -0
  53. package/dist/indexer/behavior.d.ts +75 -0
  54. package/dist/indexer/behavior.d.ts.map +1 -0
  55. package/dist/indexer/behavior.js +395 -0
  56. package/dist/indexer/behavior.js.map +1 -0
  57. package/dist/indexer/boundaries.d.ts +60 -0
  58. package/dist/indexer/boundaries.d.ts.map +1 -0
  59. package/dist/indexer/boundaries.js +366 -0
  60. package/dist/indexer/boundaries.js.map +1 -0
  61. package/dist/indexer/churn.d.ts +15 -0
  62. package/dist/indexer/churn.d.ts.map +1 -0
  63. package/dist/indexer/churn.js +49 -0
  64. package/dist/indexer/churn.js.map +1 -0
  65. package/dist/indexer/classify.d.ts +9 -0
  66. package/dist/indexer/classify.d.ts.map +1 -0
  67. package/dist/indexer/classify.js +90 -0
  68. package/dist/indexer/classify.js.map +1 -0
  69. package/dist/indexer/context.d.ts +176 -0
  70. package/dist/indexer/context.d.ts.map +1 -0
  71. package/dist/indexer/context.js +193 -0
  72. package/dist/indexer/context.js.map +1 -0
  73. package/dist/indexer/continuity.d.ts +67 -0
  74. package/dist/indexer/continuity.d.ts.map +1 -0
  75. package/dist/indexer/continuity.js +288 -0
  76. package/dist/indexer/continuity.js.map +1 -0
  77. package/dist/indexer/detectchanges.d.ts +32 -0
  78. package/dist/indexer/detectchanges.d.ts.map +1 -0
  79. package/dist/indexer/detectchanges.js +74 -0
  80. package/dist/indexer/detectchanges.js.map +1 -0
  81. package/dist/indexer/discovery.d.ts +37 -0
  82. package/dist/indexer/discovery.d.ts.map +1 -0
  83. package/dist/indexer/discovery.js +136 -0
  84. package/dist/indexer/discovery.js.map +1 -0
  85. package/dist/indexer/externaldeps.d.ts +18 -0
  86. package/dist/indexer/externaldeps.d.ts.map +1 -0
  87. package/dist/indexer/externaldeps.js +288 -0
  88. package/dist/indexer/externaldeps.js.map +1 -0
  89. package/dist/indexer/freshness.d.ts +48 -0
  90. package/dist/indexer/freshness.d.ts.map +1 -0
  91. package/dist/indexer/freshness.js +128 -0
  92. package/dist/indexer/freshness.js.map +1 -0
  93. package/dist/indexer/git.d.ts +144 -0
  94. package/dist/indexer/git.d.ts.map +1 -0
  95. package/dist/indexer/git.js +444 -0
  96. package/dist/indexer/git.js.map +1 -0
  97. package/dist/indexer/index.d.ts +145 -0
  98. package/dist/indexer/index.d.ts.map +1 -0
  99. package/dist/indexer/index.js +930 -0
  100. package/dist/indexer/index.js.map +1 -0
  101. package/dist/indexer/modules.d.ts +62 -0
  102. package/dist/indexer/modules.d.ts.map +1 -0
  103. package/dist/indexer/modules.js +293 -0
  104. package/dist/indexer/modules.js.map +1 -0
  105. package/dist/indexer/preflight.d.ts +154 -0
  106. package/dist/indexer/preflight.d.ts.map +1 -0
  107. package/dist/indexer/preflight.js +399 -0
  108. package/dist/indexer/preflight.js.map +1 -0
  109. package/dist/indexer/protoScanner.d.ts +34 -0
  110. package/dist/indexer/protoScanner.d.ts.map +1 -0
  111. package/dist/indexer/protoScanner.js +133 -0
  112. package/dist/indexer/protoScanner.js.map +1 -0
  113. package/dist/indexer/risk.d.ts +115 -0
  114. package/dist/indexer/risk.d.ts.map +1 -0
  115. package/dist/indexer/risk.js +194 -0
  116. package/dist/indexer/risk.js.map +1 -0
  117. package/dist/indexer/serviceHostScanner.d.ts +25 -0
  118. package/dist/indexer/serviceHostScanner.d.ts.map +1 -0
  119. package/dist/indexer/serviceHostScanner.js +95 -0
  120. package/dist/indexer/serviceHostScanner.js.map +1 -0
  121. package/dist/indexer/serviceLinks.d.ts +105 -0
  122. package/dist/indexer/serviceLinks.d.ts.map +1 -0
  123. package/dist/indexer/serviceLinks.js +509 -0
  124. package/dist/indexer/serviceLinks.js.map +1 -0
  125. package/dist/indexer/shapehash.d.ts +98 -0
  126. package/dist/indexer/shapehash.d.ts.map +1 -0
  127. package/dist/indexer/shapehash.js +354 -0
  128. package/dist/indexer/shapehash.js.map +1 -0
  129. package/dist/indexer/skeleton.d.ts +15 -0
  130. package/dist/indexer/skeleton.d.ts.map +1 -0
  131. package/dist/indexer/skeleton.js +136 -0
  132. package/dist/indexer/skeleton.js.map +1 -0
  133. package/dist/indexer/symbolhistory.d.ts +41 -0
  134. package/dist/indexer/symbolhistory.d.ts.map +1 -0
  135. package/dist/indexer/symbolhistory.js +124 -0
  136. package/dist/indexer/symbolhistory.js.map +1 -0
  137. package/dist/indexer/watcher.d.ts +68 -0
  138. package/dist/indexer/watcher.d.ts.map +1 -0
  139. package/dist/indexer/watcher.js +179 -0
  140. package/dist/indexer/watcher.js.map +1 -0
  141. package/dist/mcp/server.d.ts +80 -0
  142. package/dist/mcp/server.d.ts.map +1 -0
  143. package/dist/mcp/server.js +1610 -0
  144. package/dist/mcp/server.js.map +1 -0
  145. package/dist/parser/index.d.ts +8 -0
  146. package/dist/parser/index.d.ts.map +1 -0
  147. package/dist/parser/index.js +33 -0
  148. package/dist/parser/index.js.map +1 -0
  149. package/dist/parser/languages/cpp.d.ts +3 -0
  150. package/dist/parser/languages/cpp.d.ts.map +1 -0
  151. package/dist/parser/languages/cpp.js +350 -0
  152. package/dist/parser/languages/cpp.js.map +1 -0
  153. package/dist/parser/languages/csharp.d.ts +3 -0
  154. package/dist/parser/languages/csharp.d.ts.map +1 -0
  155. package/dist/parser/languages/csharp.js +239 -0
  156. package/dist/parser/languages/csharp.js.map +1 -0
  157. package/dist/parser/languages/go.d.ts +3 -0
  158. package/dist/parser/languages/go.d.ts.map +1 -0
  159. package/dist/parser/languages/go.js +259 -0
  160. package/dist/parser/languages/go.js.map +1 -0
  161. package/dist/parser/languages/java.d.ts +3 -0
  162. package/dist/parser/languages/java.d.ts.map +1 -0
  163. package/dist/parser/languages/java.js +391 -0
  164. package/dist/parser/languages/java.js.map +1 -0
  165. package/dist/parser/languages/python.d.ts +3 -0
  166. package/dist/parser/languages/python.d.ts.map +1 -0
  167. package/dist/parser/languages/python.js +396 -0
  168. package/dist/parser/languages/python.js.map +1 -0
  169. package/dist/parser/languages/rust.d.ts +3 -0
  170. package/dist/parser/languages/rust.d.ts.map +1 -0
  171. package/dist/parser/languages/rust.js +159 -0
  172. package/dist/parser/languages/rust.js.map +1 -0
  173. package/dist/parser/languages/typescript.d.ts +3 -0
  174. package/dist/parser/languages/typescript.d.ts.map +1 -0
  175. package/dist/parser/languages/typescript.js +1442 -0
  176. package/dist/parser/languages/typescript.js.map +1 -0
  177. package/dist/parser/parserContext.d.ts +77 -0
  178. package/dist/parser/parserContext.d.ts.map +1 -0
  179. package/dist/parser/parserContext.js +354 -0
  180. package/dist/parser/parserContext.js.map +1 -0
  181. package/dist/parser/walker.d.ts +81 -0
  182. package/dist/parser/walker.d.ts.map +1 -0
  183. package/dist/parser/walker.js +217 -0
  184. package/dist/parser/walker.js.map +1 -0
  185. package/dist/parser/worker.d.ts +66 -0
  186. package/dist/parser/worker.d.ts.map +1 -0
  187. package/dist/parser/worker.js +129 -0
  188. package/dist/parser/worker.js.map +1 -0
  189. package/dist/parser/workerpool.d.ts +107 -0
  190. package/dist/parser/workerpool.d.ts.map +1 -0
  191. package/dist/parser/workerpool.js +383 -0
  192. package/dist/parser/workerpool.js.map +1 -0
  193. package/dist/scip/format.d.ts +87 -0
  194. package/dist/scip/format.d.ts.map +1 -0
  195. package/dist/scip/format.js +31 -0
  196. package/dist/scip/format.js.map +1 -0
  197. package/dist/scip/import.d.ts +37 -0
  198. package/dist/scip/import.d.ts.map +1 -0
  199. package/dist/scip/import.js +180 -0
  200. package/dist/scip/import.js.map +1 -0
  201. package/dist/types.d.ts +392 -0
  202. package/dist/types.d.ts.map +1 -0
  203. package/dist/types.js +4 -0
  204. package/dist/types.js.map +1 -0
  205. package/docs/architecture.md +105 -0
  206. package/docs/benchmarks/methodology.md +134 -0
  207. package/docs/benchmarks/raw-results.md +71 -0
  208. package/docs/benchmarks.md +74 -0
  209. package/docs/cli.md +148 -0
  210. package/docs/examples/behavior-tests.md +70 -0
  211. package/docs/examples/change-history.md +85 -0
  212. package/docs/examples/pre-edit-context.md +81 -0
  213. package/docs/examples/service-links.md +88 -0
  214. package/docs/examples.md +80 -0
  215. package/docs/faq.md +70 -0
  216. package/docs/internals.md +104 -0
  217. package/docs/languages.md +70 -0
  218. package/docs/limits.md +52 -0
  219. package/docs/mcp.md +199 -0
  220. package/docs/quickstart.md +119 -0
  221. package/docs/testing.md +123 -0
  222. package/docs/tools.md +115 -0
  223. package/package.json +52 -0
  224. package/research-codebase.md +578 -0
  225. package/seer-cli-docs.md +326 -0
  226. package/seer-master-guide.md +246 -0
  227. package/src/bundle/ci.ts +141 -0
  228. package/src/bundle/contract.ts +387 -0
  229. package/src/bundle/export.ts +175 -0
  230. package/src/bundle/external.ts +285 -0
  231. package/src/bundle/format.ts +92 -0
  232. package/src/bundle/import.ts +157 -0
  233. package/src/cli/index.ts +1249 -0
  234. package/src/cli/init.ts +389 -0
  235. package/src/db/schema.ts +614 -0
  236. package/src/db/store.ts +4306 -0
  237. package/src/graph/pagerank.ts +53 -0
  238. package/src/indexer/architecture.ts +148 -0
  239. package/src/indexer/behavior.ts +466 -0
  240. package/src/indexer/boundaries.ts +374 -0
  241. package/src/indexer/churn.ts +58 -0
  242. package/src/indexer/classify.ts +96 -0
  243. package/src/indexer/context.ts +340 -0
  244. package/src/indexer/continuity.ts +322 -0
  245. package/src/indexer/detectchanges.ts +94 -0
  246. package/src/indexer/discovery.ts +176 -0
  247. package/src/indexer/externaldeps.ts +243 -0
  248. package/src/indexer/freshness.ts +166 -0
  249. package/src/indexer/git.ts +453 -0
  250. package/src/indexer/index.ts +1092 -0
  251. package/src/indexer/modules.ts +358 -0
  252. package/src/indexer/preflight.ts +548 -0
  253. package/src/indexer/protoScanner.ts +147 -0
  254. package/src/indexer/risk.ts +304 -0
  255. package/src/indexer/serviceHostScanner.ts +92 -0
  256. package/src/indexer/serviceLinks.ts +543 -0
  257. package/src/indexer/shapehash.ts +370 -0
  258. package/src/indexer/skeleton.ts +169 -0
  259. package/src/indexer/symbolhistory.ts +172 -0
  260. package/src/indexer/watcher.ts +206 -0
  261. package/src/mcp/server.ts +1659 -0
  262. package/src/parser/index.ts +37 -0
  263. package/src/parser/languages/cpp.ts +361 -0
  264. package/src/parser/languages/csharp.ts +235 -0
  265. package/src/parser/languages/go.ts +259 -0
  266. package/src/parser/languages/java.ts +382 -0
  267. package/src/parser/languages/python.ts +370 -0
  268. package/src/parser/languages/rust.ts +164 -0
  269. package/src/parser/languages/typescript.ts +1435 -0
  270. package/src/parser/parserContext.ts +392 -0
  271. package/src/parser/walker.ts +306 -0
  272. package/src/parser/worker.ts +181 -0
  273. package/src/parser/workerpool.ts +448 -0
  274. package/src/scip/format.ts +83 -0
  275. package/src/scip/import.ts +216 -0
  276. package/src/types.ts +457 -0
  277. package/tests/benchmark-service-links.ts +244 -0
  278. package/tests/bug-regressions.ts +626 -0
  279. package/tests/filters.ts +264 -0
  280. package/tests/fixtures/Counter.tsx +38 -0
  281. package/tests/fixtures/caller.ts +7 -0
  282. package/tests/fixtures/collisions.ts +23 -0
  283. package/tests/fixtures/local_helper.ts +5 -0
  284. package/tests/fixtures/overloads.java +17 -0
  285. package/tests/fixtures/remote_helper.ts +4 -0
  286. package/tests/fixtures/sample.c +15 -0
  287. package/tests/fixtures/sample.cpp +47 -0
  288. package/tests/fixtures/sample.cs +62 -0
  289. package/tests/fixtures/sample.go +68 -0
  290. package/tests/fixtures/sample.h +30 -0
  291. package/tests/fixtures/sample.java +85 -0
  292. package/tests/fixtures/sample.py +46 -0
  293. package/tests/fixtures/sample.rs +78 -0
  294. package/tests/fixtures/sample.ts +76 -0
  295. package/tests/fixtures-service/HttpClients.cs +30 -0
  296. package/tests/fixtures-service/HttpClients.java +24 -0
  297. package/tests/fixtures-service/billing.ts +15 -0
  298. package/tests/fixtures-service/docker-compose.yml +15 -0
  299. package/tests/fixtures-service/gateway.ts +10 -0
  300. package/tests/fixtures-service/get_user.ts +11 -0
  301. package/tests/fixtures-service/graphql_client.ts +63 -0
  302. package/tests/fixtures-service/graphql_server.ts +30 -0
  303. package/tests/fixtures-service/grpc_client.go +30 -0
  304. package/tests/fixtures-service/http_clients.go +23 -0
  305. package/tests/fixtures-service/http_clients.py +38 -0
  306. package/tests/fixtures-service/http_clients.ts +49 -0
  307. package/tests/fixtures-service/k8s/payment-service.yaml +22 -0
  308. package/tests/fixtures-service/k8s_calls.ts +20 -0
  309. package/tests/fixtures-service/messaging.ts +87 -0
  310. package/tests/fixtures-service/trpc_client.ts +39 -0
  311. package/tests/fixtures-service/trpc_server.ts +39 -0
  312. package/tests/fixtures-service/user_service.proto +33 -0
  313. package/tests/fixtures-trackcd/Cargo.toml +11 -0
  314. package/tests/fixtures-trackcd/SpringController.java +36 -0
  315. package/tests/fixtures-trackcd/auth_service.ts +19 -0
  316. package/tests/fixtures-trackcd/complex_module.py +50 -0
  317. package/tests/fixtures-trackcd/express_app.js +30 -0
  318. package/tests/fixtures-trackcd/fastapi_app.py +49 -0
  319. package/tests/fixtures-trackcd/fastify_object_routes.js +32 -0
  320. package/tests/fixtures-trackcd/go.mod +8 -0
  321. package/tests/fixtures-trackcd/package.json +15 -0
  322. package/tests/fixtures-trackcd/requirements.txt +4 -0
  323. package/tests/fixtures-trackcd/tests/auth_service.test.ts +13 -0
  324. package/tests/fixtures-tracke/auth/AuthService.ts +23 -0
  325. package/tests/fixtures-tracke/auth/crypto.ts +7 -0
  326. package/tests/fixtures-tracke/billing/Billing.ts +20 -0
  327. package/tests/fixtures-tracke/billing/Invoice.ts +10 -0
  328. package/tests/fixtures-tracke/billing/server.ts +17 -0
  329. package/tests/fixtures-tracke/package.json +7 -0
  330. package/tests/fixtures-tracke/tests/auth.test.ts +23 -0
  331. package/tests/fixtures-tracke/tests/billing.test.ts +14 -0
  332. package/tests/fixtures-trackf/package.json +5 -0
  333. package/tests/fixtures-trackf/src/auth.ts +26 -0
  334. package/tests/fixtures-trackf/src/handlers.ts +35 -0
  335. package/tests/fixtures-tracki/billing/routes.ts +12 -0
  336. package/tests/fixtures-tracki/gateway/client.ts +13 -0
  337. package/tests/git-features.ts +267 -0
  338. package/tests/init.ts +141 -0
  339. package/tests/mcp-jit.ts +130 -0
  340. package/tests/mcp-smoke.ts +191 -0
  341. package/tests/mcp-trackcd.ts +169 -0
  342. package/tests/mcp-tracke.ts +229 -0
  343. package/tests/mcp-trackf.ts +330 -0
  344. package/tests/mcp-trackg.ts +219 -0
  345. package/tests/mcp-tracki.ts +174 -0
  346. package/tests/mcp-watcher.ts +126 -0
  347. package/tests/optspec.ts +194 -0
  348. package/tests/parallel-index.ts +333 -0
  349. package/tests/parallel-read.ts +125 -0
  350. package/tests/parallel-recovery.ts +241 -0
  351. package/tests/perf-callers.ts +145 -0
  352. package/tests/query-parity.ts +184 -0
  353. package/tests/query-perf.ts +55 -0
  354. package/tests/scale-parallel-parity.ts +225 -0
  355. package/tests/scale-test.ts +523 -0
  356. package/tests/smoke.ts +396 -0
  357. package/tests/trackcd.ts +325 -0
  358. package/tests/tracke-collisions.ts +255 -0
  359. package/tests/tracke.ts +314 -0
  360. package/tests/trackf-bugs.ts +406 -0
  361. package/tests/trackf.ts +390 -0
  362. package/tests/trackg.ts +1372 -0
  363. package/tests/tracki-boundaries.ts +202 -0
  364. package/tests/tracki-continuity.ts +253 -0
  365. package/tests/tracki-contract-diff.ts +249 -0
  366. package/tests/tracki-external-bundles.ts +341 -0
  367. package/tests/tracki-preflight.ts +251 -0
  368. package/tests/verify-roles.ts +51 -0
  369. package/tests/worker-parity.ts +286 -0
  370. package/tests/worker-pool.ts +262 -0
  371. 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); });