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
package/src/types.ts ADDED
@@ -0,0 +1,457 @@
1
+ // Core shared types for the Seer indexer
2
+
3
+ export type Language =
4
+ | 'python'
5
+ | 'javascript'
6
+ | 'typescript'
7
+ | 'go'
8
+ | 'java'
9
+ | 'rust'
10
+ | 'c'
11
+ | 'cpp'
12
+ | 'csharp'
13
+ // v9 Track-H — proto files contribute routes (gRPC) but no symbols.
14
+ // They go through a regex scanner in `protoScanner.ts`, not tree-sitter.
15
+ | 'proto';
16
+
17
+ export type SymbolKind =
18
+ | 'function'
19
+ | 'class'
20
+ | 'method'
21
+ | 'interface'
22
+ | 'struct'
23
+ | 'enum'
24
+ | 'type'
25
+ | 'constructor'
26
+ | 'variable';
27
+
28
+ export type EdgeKind = 'call' | 'import' | 'inherits' | 'implements' | 'tests';
29
+
30
+ /**
31
+ * What variety of symbol this row represents.
32
+ * - 'definition' — the canonical, body-bearing definition site. The default
33
+ * and the only role indexed by every extractor on every
34
+ * symbol historically. Rankable when the kind allows it.
35
+ * - 'declaration' — a forward declaration or prototype (no body). C/C++
36
+ * class-body method declarations and forward declarations
37
+ * fall here. Not rankable; excluded from agent-facing
38
+ * defaults unless `includeDeclarations=true`.
39
+ * - 'type_ref' — a bare use of a type name (no body, no declaration).
40
+ * Not yet emitted by any extractor — the slot exists so a
41
+ * future indexing mode can store reference sites without
42
+ * re-shaping the schema.
43
+ */
44
+ export type SymbolRole = 'definition' | 'declaration' | 'type_ref';
45
+
46
+ // A symbol definition extracted from source
47
+ export interface SymbolDef {
48
+ name: string;
49
+ /**
50
+ * Dotted path including all enclosing class/struct/impl scopes.
51
+ * Computed by the walker from the def stack — extractors should not set it.
52
+ * E.g. `Alpha.run`, `AuthService.login`, `PaymentService.process_payment`.
53
+ * Equals `name` for top-level definitions.
54
+ */
55
+ qualifiedName?: string;
56
+ kind: SymbolKind;
57
+ lineStart: number; // 0-indexed row
58
+ lineEnd: number;
59
+ colStart: number;
60
+ colEnd: number;
61
+ signature?: string; // first line of the definition, truncated
62
+ /**
63
+ * Complexity metrics, populated by language extractors via the walker.
64
+ * `loc`/`cyclomatic`/`cognitive`/`maxNesting` are non-null only for
65
+ * function-like symbols (function, method, constructor) where they make
66
+ * sense. For classes/structs/enums/types they stay null.
67
+ */
68
+ loc?: number;
69
+ cyclomatic?: number;
70
+ cognitive?: number;
71
+ maxNesting?: number;
72
+ /**
73
+ * Optional. When omitted, the Store treats this as `'definition'` —
74
+ * matches all pre-existing extractor behavior. C/C++ field_declaration
75
+ * (class-body method declarations) and forward declarations set this to
76
+ * `'declaration'` so default agent-facing queries can hide them.
77
+ */
78
+ symbolRole?: SymbolRole;
79
+ /**
80
+ * Extra owning-scope segments that are NOT on the lexical definition stack.
81
+ * Used for out-of-line definitions whose declarator names a qualifier that
82
+ * the walker can't see lexically — e.g. a C++ method defined at namespace
83
+ * scope as `T Vec<T>::dot(...) { ... }` sets `scopePath = ['Vec']` so its
84
+ * qualified name becomes `geo.Vec.dot` (the true owner) instead of `geo.dot`
85
+ * (which reads like a free function). The walker folds these segments into
86
+ * the qualified name and keys overload disambiguation on (scope + name), so
87
+ * `Foo::bar` and `Baz::bar` stay distinct instead of collapsing to bar / bar#1.
88
+ * Extractors set short names only; this is the one sanctioned scope hint.
89
+ */
90
+ scopePath?: string[];
91
+ }
92
+
93
+ // A reference (call/usage) extracted from source
94
+ export interface SymbolRef {
95
+ calleeName: string; // the name being called/referenced
96
+ callerName: string; // name of the enclosing function/method, or '' for module level
97
+ kind: EdgeKind;
98
+ line: number; // 0-indexed row
99
+ }
100
+
101
+ /**
102
+ * v9 Track-H — protocols supported by the service-link layer. HTTP was the
103
+ * only protocol at v8; v9 generalizes to RPC/messaging/streaming protocols.
104
+ * Protocol-specific fields are stored sparsely on the same row.
105
+ */
106
+ export type ServiceProtocol =
107
+ | 'http'
108
+ | 'trpc'
109
+ | 'graphql'
110
+ | 'grpc'
111
+ | 'kafka'
112
+ | 'sqs'
113
+ | 'sns'
114
+ | 'rabbitmq'
115
+ | 'nats'
116
+ | 'redis_pubsub'
117
+ | 'websocket'
118
+ | 'sse';
119
+
120
+ /**
121
+ * One route / endpoint detected during parsing.
122
+ *
123
+ * For HTTP (default): Express/Fastify/FastAPI/Flask/Spring routes. `method` +
124
+ * `path` carry the matchable contract; `framework` records the library.
125
+ *
126
+ * v9 Track-H: the same row shape now represents tRPC procedures, GraphQL
127
+ * resolvers, gRPC service methods, Kafka/SQS/RabbitMQ consumers, etc. The
128
+ * protocol-specific fields (operation/topic/queue/exchange/service/broker)
129
+ * are populated by the protocol's extractor and left undefined elsewhere.
130
+ *
131
+ * The handler is named when the route maps to a local function; the post-pass
132
+ * resolves `handlerName` → a `symbol_id` after all definitions are inserted.
133
+ */
134
+ export interface RouteDef {
135
+ method: string;
136
+ path: string;
137
+ framework: string;
138
+ handlerName?: string;
139
+ line: number;
140
+ /** v9 Track-H. Defaults to 'http' when omitted (every pre-v9 RouteDef). */
141
+ protocol?: ServiceProtocol;
142
+ /** tRPC procedure path ('user.getById'), GraphQL operation name, gRPC method. */
143
+ operation?: string;
144
+ /** Kafka / pub-sub topic the consumer subscribes to. */
145
+ topic?: string;
146
+ /** SQS / RabbitMQ queue the consumer reads from. */
147
+ queue?: string;
148
+ /** RabbitMQ exchange. */
149
+ exchange?: string;
150
+ /** gRPC service name; k8s service hostname for HTTP routes inside a service module. */
151
+ service?: string;
152
+ /** Broker host / cluster identifier (kafka:9092, sqs.us-east-1, etc.). */
153
+ broker?: string;
154
+ /** Protocol-specific catch-all already serialized as JSON. */
155
+ metadataJson?: string;
156
+ }
157
+
158
+ /** A static read of an environment variable or config key. */
159
+ export interface ConfigKeyRead {
160
+ key: string;
161
+ source: 'env' | 'config';
162
+ callerName?: string; // enclosing symbol qualified name, '' for module-level
163
+ line: number;
164
+ }
165
+
166
+ /**
167
+ * One outbound HTTP client call detected during parsing — fetch / axios /
168
+ * requests / http.Get / HttpClient.GetAsync / etc.
169
+ *
170
+ * Routes are HANDLERS (registered with the framework so an incoming request
171
+ * lands on a function); ServiceCalls are CLIENTS (your code dialing OUT to
172
+ * another service). They are deliberately separate concepts; the post-index
173
+ * resolver rendezvous-matches calls to routes to build service_links.
174
+ *
175
+ * `rawTarget` always carries the literal/expression as written — if the path
176
+ * can't be confidently extracted, normalizedPath stays undefined but the call
177
+ * is still recorded so seer can show "this code calls something via fetch()"
178
+ * even when the target can't be resolved.
179
+ *
180
+ * `callerName` is the enclosing function/method's qualified name at the call
181
+ * site; the walker sets it from the def stack, and the indexer backfills the
182
+ * resolved symbol id from the symbolIdMap.
183
+ */
184
+ export interface ServiceCallDef {
185
+ /** v9 Track-H — HTTP at v8; v9 generalizes to RPC / messaging / streaming. */
186
+ protocol: ServiceProtocol;
187
+ /** Upper-cased HTTP method when known: 'GET' | 'POST' | … | 'ANY' if unknown.
188
+ * For non-HTTP protocols, may carry the operation kind ('query'/'mutation'/'publish'). */
189
+ method?: string;
190
+ /** Original literal/expression text at the call site (truncated to 240 chars) */
191
+ rawTarget: string;
192
+ /** /api/users when extractable; undefined when dynamic and not recoverable */
193
+ normalizedPath?: string;
194
+ /** Hostname / service name (e.g. "payment-service") when present in the URL */
195
+ hostHint?: string;
196
+ /** Env-variable name used in URL building (e.g. "PAYMENT_URL") when seen */
197
+ envKey?: string;
198
+ /** Library that emitted this call: 'fetch' / 'axios' / 'requests' / 'http.Get' / 'trpc' / 'apollo' / … */
199
+ framework: string;
200
+ /** Enclosing symbol qualified name; '' for module-level reads */
201
+ callerName?: string;
202
+ line: number;
203
+ /**
204
+ * Extractor confidence (0..1). High (≥0.9) for unambiguous literal-path
205
+ * calls; lower when only a host or env key was recovered.
206
+ */
207
+ confidence: number;
208
+ // ── v9 Track-H protocol-specific fields. All optional; only set the ones
209
+ // that apply to the protocol you're emitting.
210
+ /** tRPC procedure path ('user.getById'), GraphQL operation name, gRPC method. */
211
+ operation?: string;
212
+ /** Kafka / pub-sub topic this call publishes to. */
213
+ topic?: string;
214
+ /** SQS / RabbitMQ queue this call publishes to. */
215
+ queue?: string;
216
+ /** RabbitMQ exchange. */
217
+ exchange?: string;
218
+ /** gRPC service name; k8s service hostname for outbound HTTP. */
219
+ service?: string;
220
+ /** Broker host / cluster identifier. */
221
+ broker?: string;
222
+ /** Protocol-specific catch-all (serialized JSON object). */
223
+ metadataJson?: string;
224
+ }
225
+
226
+ // Everything extracted from one file
227
+ export interface FileExtraction {
228
+ language: Language;
229
+ definitions: SymbolDef[];
230
+ references: SymbolRef[];
231
+ importedModules: string[]; // raw module/file paths imported
232
+ routes?: RouteDef[];
233
+ configKeys?: ConfigKeyRead[];
234
+ /** v8 Track G — HTTP/etc. client calls (outbound). Optional so legacy
235
+ * extractors that don't implement detection produce undefined / []. */
236
+ serviceCalls?: ServiceCallDef[];
237
+ }
238
+
239
+ // ── DB result types ────────────────────────────────────────────────────────────
240
+
241
+ export interface SymbolRow {
242
+ id: number;
243
+ name: string;
244
+ qualifiedName: string | null;
245
+ kind: string;
246
+ fileId: number;
247
+ filePath: string;
248
+ lineStart: number;
249
+ lineEnd: number;
250
+ signature: string | null;
251
+ pagerank: number;
252
+ loc?: number | null;
253
+ cyclomatic?: number | null;
254
+ cognitive?: number | null;
255
+ maxNesting?: number | null;
256
+ /**
257
+ * Stored variety of symbol. Null on pre-v5 DBs that haven't yet been
258
+ * re-indexed; the Store treats null as `'definition'` for filter logic.
259
+ */
260
+ symbolRole?: SymbolRole | null;
261
+ }
262
+
263
+ export interface CallerRow {
264
+ callerName: string;
265
+ callerQualifiedName: string | null;
266
+ callerKind: string;
267
+ callerFile: string;
268
+ callerLine: number;
269
+ edgeKind: string;
270
+ }
271
+
272
+ export interface CalleeRow {
273
+ calleeName: string;
274
+ calleeKind: string | null;
275
+ calleeFile: string | null;
276
+ calleeLineStart: number | null;
277
+ edgeKind: string;
278
+ }
279
+
280
+ export interface RouteRow {
281
+ id: number;
282
+ method: string;
283
+ path: string;
284
+ framework: string;
285
+ handlerName: string | null;
286
+ handlerId: number | null;
287
+ handlerSymbol: string | null;
288
+ handlerFile: string | null;
289
+ filePath: string;
290
+ line: number;
291
+ // v9 Track-H — NULL on pre-v9 DBs.
292
+ protocol?: string | null;
293
+ operation?: string | null;
294
+ topic?: string | null;
295
+ queue?: string | null;
296
+ exchange?: string | null;
297
+ service?: string | null;
298
+ broker?: string | null;
299
+ metadataJson?: string | null;
300
+ }
301
+
302
+ export interface ExternalDepRow {
303
+ id: number;
304
+ ecosystem: string;
305
+ name: string;
306
+ versionRange: string | null;
307
+ manifestPath: string;
308
+ isDev: number;
309
+ }
310
+
311
+ export interface ConfigKeyRow {
312
+ id: number;
313
+ key: string;
314
+ source: string;
315
+ filePath: string;
316
+ symbolId: number | null;
317
+ symbolName: string | null;
318
+ line: number;
319
+ }
320
+
321
+ export interface FileChurnRow {
322
+ fileId: number;
323
+ filePath: string;
324
+ commitCount: number;
325
+ lastCommitSha: string | null;
326
+ lastCommitAt: number | null;
327
+ topAuthor: string | null;
328
+ secondAuthor: string | null;
329
+ }
330
+
331
+ export interface SymbolHistoryRow {
332
+ id: number;
333
+ symbolId: number;
334
+ symbolKey: string;
335
+ commitSha: string;
336
+ authorName: string | null;
337
+ authorEmail: string | null;
338
+ committedAt: number;
339
+ message: string | null;
340
+ linesAdded: number;
341
+ linesRemoved: number;
342
+ prNumber: number | null;
343
+ prUrl: string | null;
344
+ matchStrategy: string;
345
+ confidence: number;
346
+ }
347
+
348
+ /**
349
+ * v8 Track G — row returned by Store.listServiceCalls(). The caller is the
350
+ * AST-attributed enclosing function/method; the call always carries the raw
351
+ * literal/expression text as written.
352
+ */
353
+ export interface ServiceCallRow {
354
+ id: number;
355
+ protocol: string;
356
+ method: string | null;
357
+ rawTarget: string;
358
+ normalizedPath: string | null;
359
+ hostHint: string | null;
360
+ envKey: string | null;
361
+ framework: string;
362
+ line: number;
363
+ confidence: number;
364
+ filePath: string;
365
+ callerSymbolId: number | null;
366
+ callerName: string | null;
367
+ callerQualifiedName: string | null;
368
+ callerKind: string | null;
369
+ // v9 Track-H — null on HTTP rows and pre-v9 DBs.
370
+ operation: string | null;
371
+ topic: string | null;
372
+ queue: string | null;
373
+ exchange: string | null;
374
+ service: string | null;
375
+ broker: string | null;
376
+ metadataJson: string | null;
377
+ }
378
+
379
+ /**
380
+ * v8 Track G — row returned by Store.listServiceLinks(). Each link rendezvous-
381
+ * matches one service_call (the caller side) with one route (the handler side)
382
+ * and carries the deterministic match_kind + confidence.
383
+ */
384
+ export interface ServiceLinkRow {
385
+ id: number;
386
+ callId: number;
387
+ routeId: number | null;
388
+ protocol: string;
389
+ matchKind: string;
390
+ confidence: number;
391
+ evidenceJson: string;
392
+ // Caller side (snapshot from service_calls.symbol_id)
393
+ callerSymbolId: number | null;
394
+ callerName: string | null;
395
+ callerQualifiedName: string | null;
396
+ callerFile: string | null;
397
+ callerLine: number;
398
+ // Call details (forwarded from service_calls so consumers don't have to
399
+ // re-join twice)
400
+ callMethod: string | null;
401
+ callRawTarget: string;
402
+ callNormalizedPath: string | null;
403
+ callFramework: string;
404
+ callEnvKey: string | null;
405
+ callHostHint: string | null;
406
+ // Handler side (route.handler_id resolved to symbol)
407
+ handlerSymbolId: number | null;
408
+ handlerName: string | null;
409
+ handlerQualifiedName: string | null;
410
+ handlerFile: string | null;
411
+ handlerLine: number | null;
412
+ // Route details
413
+ routeMethod: string | null;
414
+ routePath: string | null;
415
+ routeFramework: string | null;
416
+ // v9 Track-H — protocol-specific fields on both sides of the link, null when N/A.
417
+ callOperation: string | null;
418
+ callTopic: string | null;
419
+ callQueue: string | null;
420
+ callService: string | null;
421
+ routeOperation: string | null;
422
+ routeTopic: string | null;
423
+ routeQueue: string | null;
424
+ routeService: string | null;
425
+ }
426
+
427
+ export interface StatsRow {
428
+ files: number;
429
+ symbols: number;
430
+ edges: number;
431
+ resolvedEdges: number;
432
+ languages: Record<string, number>;
433
+ roles?: { project: number; vendor: number; generated: number; test: number };
434
+ routes?: number;
435
+ externalDependencies?: number;
436
+ configKeys?: number;
437
+ symbolHistory?: number;
438
+ /** Number of clustered modules; 0 if the clustering pass hasn't run. */
439
+ modules?: number;
440
+ /** v7: SCIP files imported into this DB; 0 on pre-v7. */
441
+ scipImports?: number;
442
+ /** v7: symbols with a non-null structural shape_hash. */
443
+ shapeHashed?: number;
444
+ /**
445
+ * v7: symbol + edge counts grouped by provenance. Always present at v7;
446
+ * agents can use it to see how much precision SCIP contributed vs the
447
+ * tree-sitter baseline.
448
+ */
449
+ provenance?: {
450
+ symbols: Record<string, number>;
451
+ edges: Record<string, number>;
452
+ };
453
+ /** v8 Track G — total service_calls rows (outbound HTTP/etc. clients). */
454
+ serviceCalls?: number;
455
+ /** v8 Track G — total service_links rows (caller↔handler rendezvous). */
456
+ serviceLinks?: number;
457
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * SeerBench — service-link benchmark.
3
+ *
4
+ * Each task is a deterministic question over the fixtures-service workspace.
5
+ * The runner indexes the fixtures once, then for each task computes:
6
+ * - actual: the result returned by Seer-Core (no AI)
7
+ * - expected: the JSON answer we hand-wrote per task
8
+ * - precision: |actual ∩ expected| / |actual| (0 if actual empty when expected non-empty)
9
+ * - recall: |actual ∩ expected| / |expected|
10
+ *
11
+ * The suite fails (exit 1) when:
12
+ * - precision < 0.9 OR recall < 0.9 on any task
13
+ * - latency over the full suite exceeds the cap (5s — generous for fixtures)
14
+ * - any task throws
15
+ *
16
+ * This is a tiny benchmark; its purpose is to catch regressions in the
17
+ * service-link resolver (precision/recall drops) and to demonstrate that the
18
+ * answers Seer produces for these specific questions are stable.
19
+ *
20
+ * Run: npx tsx tests/benchmark-service-links.ts
21
+ */
22
+
23
+ import path from 'path';
24
+ import fs from 'fs';
25
+ import os from 'os';
26
+ import { Indexer } from '../src/indexer/index';
27
+ import { Store } from '../src/db/store';
28
+
29
+ const FIX = path.join(__dirname, 'fixtures-service');
30
+ const TMP = path.join(os.tmpdir(), `seer-bench-${Date.now()}`);
31
+ fs.mkdirSync(TMP, { recursive: true });
32
+ const DB = path.join(TMP, 'bench.db');
33
+
34
+ interface BenchTask {
35
+ id: string;
36
+ question: string;
37
+ expected: Set<string>; // set of expected answer items (e.g. handler qnames)
38
+ run(store: Store): Set<string>;
39
+ }
40
+
41
+ const tasks: BenchTask[] = [
42
+ {
43
+ id: 'http-handler-for-checkout-charge',
44
+ question: 'What handler receives the gateway service\'s POST /api/charge call?',
45
+ expected: new Set(['chargeHandler']),
46
+ run(store) {
47
+ const rows = store.rawDb().prepare(
48
+ `SELECT sh.qualified_name AS qn
49
+ FROM service_links sl
50
+ JOIN routes r ON r.id = sl.route_id
51
+ JOIN service_calls sc ON sc.id = sl.call_id
52
+ LEFT JOIN symbols sh ON sh.id = sl.handler_symbol_id
53
+ WHERE r.path = '/api/charge' AND sc.method = 'POST'`,
54
+ ).all() as Array<{ qn: string }>;
55
+ return new Set(rows.map(r => r.qn).filter(Boolean));
56
+ },
57
+ },
58
+ {
59
+ id: 'trpc-handler-for-user-getbyid',
60
+ question: 'Which handler receives the trpc.user.getById.query() call?',
61
+ expected: new Set(['getUserById']),
62
+ run(store) {
63
+ const rows = store.rawDb().prepare(
64
+ `SELECT sh.qualified_name AS qn
65
+ FROM service_links sl
66
+ JOIN service_calls sc ON sc.id = sl.call_id
67
+ JOIN routes r ON r.id = sl.route_id
68
+ LEFT JOIN symbols sh ON sh.id = sl.handler_symbol_id
69
+ WHERE sl.protocol = 'trpc' AND r.operation = 'getById'`,
70
+ ).all() as Array<{ qn: string }>;
71
+ return new Set(rows.map(r => r.qn).filter(Boolean));
72
+ },
73
+ },
74
+ {
75
+ id: 'graphql-resolver-for-user-query',
76
+ question: 'Which resolver receives the GraphQL "user" query operation?',
77
+ expected: new Set(['userResolver']),
78
+ run(store) {
79
+ const rows = store.rawDb().prepare(
80
+ `SELECT sh.qualified_name AS qn
81
+ FROM service_links sl
82
+ JOIN routes r ON r.id = sl.route_id
83
+ LEFT JOIN symbols sh ON sh.id = sl.handler_symbol_id
84
+ WHERE sl.protocol = 'graphql' AND r.operation = 'user'`,
85
+ ).all() as Array<{ qn: string }>;
86
+ return new Set(rows.map(r => r.qn).filter(Boolean));
87
+ },
88
+ },
89
+ {
90
+ id: 'grpc-method-for-userservice-getuser',
91
+ question: 'Which gRPC rpc receives the UserService.GetUser client call?',
92
+ expected: new Set(['UserService/GetUser']),
93
+ run(store) {
94
+ const rows = store.rawDb().prepare(
95
+ `SELECT r.operation AS op
96
+ FROM service_links sl
97
+ JOIN routes r ON r.id = sl.route_id
98
+ WHERE sl.protocol = 'grpc' AND r.operation = 'UserService/GetUser'`,
99
+ ).all() as Array<{ op: string }>;
100
+ return new Set(rows.map(r => r.op));
101
+ },
102
+ },
103
+ {
104
+ id: 'kafka-producers-for-orders-topic',
105
+ question: 'Which functions publish to the Kafka "orders" topic?',
106
+ expected: new Set(['produceOrders']),
107
+ run(store) {
108
+ const rows = store.rawDb().prepare(
109
+ `SELECT s.qualified_name AS qn
110
+ FROM service_calls sc
111
+ LEFT JOIN symbols s ON s.id = sc.symbol_id
112
+ WHERE sc.protocol = 'kafka' AND sc.topic = 'orders'`,
113
+ ).all() as Array<{ qn: string }>;
114
+ return new Set(rows.map(r => r.qn).filter(Boolean));
115
+ },
116
+ },
117
+ {
118
+ id: 'kafka-consumers-for-orders-topic',
119
+ question: 'Which functions consume the Kafka "orders" topic?',
120
+ expected: new Set(['subscribeOrders']),
121
+ run(store) {
122
+ // Consumers register a route; we want the file/symbol that calls
123
+ // consumer.subscribe(...). We approximate by finding any symbol whose
124
+ // qualified name matches the file's known consumer.
125
+ const rows = store.rawDb().prepare(
126
+ `SELECT s.qualified_name AS qn
127
+ FROM routes r
128
+ JOIN files f ON f.id = r.file_id
129
+ JOIN symbols s ON s.file_id = r.file_id
130
+ AND s.line_start <= r.line
131
+ AND s.line_end >= r.line
132
+ WHERE r.protocol = 'kafka' AND r.topic = 'orders'
133
+ AND s.kind IN ('function','method')
134
+ ORDER BY (r.line - s.line_start) ASC LIMIT 1`,
135
+ ).all() as Array<{ qn: string }>;
136
+ return new Set(rows.map(r => r.qn).filter(Boolean));
137
+ },
138
+ },
139
+ {
140
+ id: 'sqs-consumers-for-job-queue',
141
+ question: 'Which consumers handle the "job-queue" SQS queue?',
142
+ expected: new Set(['consumeJob']),
143
+ run(store) {
144
+ const rows = store.rawDb().prepare(
145
+ `SELECT s.qualified_name AS qn
146
+ FROM routes r
147
+ JOIN files f ON f.id = r.file_id
148
+ JOIN symbols s ON s.file_id = r.file_id
149
+ AND s.line_start <= r.line
150
+ AND s.line_end >= r.line
151
+ WHERE r.protocol = 'sqs' AND r.queue = 'job-queue'
152
+ AND s.kind IN ('function','method')
153
+ ORDER BY (r.line - s.line_start) ASC LIMIT 1`,
154
+ ).all() as Array<{ qn: string }>;
155
+ return new Set(rows.map(r => r.qn).filter(Boolean));
156
+ },
157
+ },
158
+ {
159
+ id: 'service-host-link-payment',
160
+ question: 'Which calls were classified as service_host matches via the payment-service k8s host?',
161
+ expected: new Set(['paymentCall']),
162
+ run(store) {
163
+ const rows = store.rawDb().prepare(
164
+ `SELECT sc.qualified_name AS qn
165
+ FROM service_links sl
166
+ JOIN service_calls c ON c.id = sl.call_id
167
+ LEFT JOIN symbols sc ON sc.id = sl.caller_symbol_id
168
+ WHERE sl.match_kind = 'service_host' AND c.host_hint = 'payment-service'`,
169
+ ).all() as Array<{ qn: string }>;
170
+ return new Set(rows.map(r => r.qn).filter(Boolean));
171
+ },
172
+ },
173
+ ];
174
+
175
+ function intersect(a: Set<string>, b: Set<string>): Set<string> {
176
+ const out = new Set<string>();
177
+ for (const x of a) if (b.has(x)) out.add(x);
178
+ return out;
179
+ }
180
+
181
+ async function main(): Promise<void> {
182
+ console.log('\nSeerBench — service-link benchmark');
183
+ console.log('===================================\n');
184
+ const startSetup = Date.now();
185
+ const store = new Store(DB);
186
+ await new Indexer(store).indexDirectory(FIX, { quiet: true });
187
+ const setupMs = Date.now() - startSetup;
188
+ console.log(` setup: indexed fixtures-service in ${setupMs}ms\n`);
189
+
190
+ let totalLatency = 0;
191
+ let regressions = 0;
192
+ let toolCalls = 0;
193
+ let bytes = 0;
194
+
195
+ for (const t of tasks) {
196
+ const start = Date.now();
197
+ let actual: Set<string> = new Set();
198
+ let err: unknown = null;
199
+ try { actual = t.run(store); } catch (e) { err = e; }
200
+ const latency = Date.now() - start;
201
+ totalLatency += latency;
202
+ toolCalls += 1;
203
+ const actualJson = JSON.stringify(Array.from(actual).sort());
204
+ bytes += actualJson.length;
205
+ const inter = intersect(actual, t.expected);
206
+ const precision = actual.size === 0 ? (t.expected.size === 0 ? 1 : 0) : inter.size / actual.size;
207
+ const recall = t.expected.size === 0 ? 1 : inter.size / t.expected.size;
208
+ const okPrec = precision >= 0.9;
209
+ const okRec = recall >= 0.9;
210
+ const mark = (okPrec && okRec && !err) ? '✓' : '✗';
211
+ console.log(` ${mark} [${t.id}] precision=${precision.toFixed(2)} recall=${recall.toFixed(2)} latency=${latency}ms`);
212
+ if (err) {
213
+ console.log(` ! threw: ${err}`);
214
+ regressions++;
215
+ continue;
216
+ }
217
+ if (!okPrec || !okRec) {
218
+ console.log(` expected=${JSON.stringify([...t.expected])} actual=${actualJson}`);
219
+ regressions++;
220
+ }
221
+ }
222
+
223
+ console.log(`\nSummary:`);
224
+ console.log(` tasks: ${tasks.length}`);
225
+ console.log(` regressions: ${regressions}`);
226
+ console.log(` total latency: ${totalLatency}ms`);
227
+ console.log(` tool calls: ${toolCalls}`);
228
+ console.log(` output bytes: ${bytes}`);
229
+
230
+ store.close();
231
+ try { fs.rmSync(TMP, { recursive: true, force: true }); } catch { /* */ }
232
+
233
+ // Latency cap: 5 seconds for the full suite over a tiny fixture set is
234
+ // very generous; if we ever blow past it, something pathological has
235
+ // happened in the resolver.
236
+ if (totalLatency > 5000) {
237
+ console.error(`✗ total latency ${totalLatency}ms > 5000ms cap`);
238
+ process.exit(1);
239
+ }
240
+ if (regressions > 0) process.exit(1);
241
+ console.log(`\n ✓ all ${tasks.length} bench tasks within precision/recall ≥ 0.9`);
242
+ }
243
+
244
+ main().catch(err => { console.error('benchmark crashed:', err); process.exit(1); });