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,1610 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SeerMcpServer = void 0;
7
+ exports.runMcp = runMcp;
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
11
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
12
+ const zod_1 = require("zod");
13
+ const store_js_1 = require("../db/store.js");
14
+ const index_js_1 = require("../indexer/index.js");
15
+ const freshness_js_1 = require("../indexer/freshness.js");
16
+ const watcher_js_1 = require("../indexer/watcher.js");
17
+ const architecture_js_1 = require("../indexer/architecture.js");
18
+ const detectchanges_js_1 = require("../indexer/detectchanges.js");
19
+ const churn_js_1 = require("../indexer/churn.js");
20
+ const symbolhistory_js_1 = require("../indexer/symbolhistory.js");
21
+ const modules_js_1 = require("../indexer/modules.js");
22
+ const behavior_js_1 = require("../indexer/behavior.js");
23
+ const risk_js_1 = require("../indexer/risk.js");
24
+ const context_js_1 = require("../indexer/context.js");
25
+ const export_js_1 = require("../bundle/export.js");
26
+ const import_js_1 = require("../bundle/import.js");
27
+ const external_js_1 = require("../bundle/external.js");
28
+ const contract_js_1 = require("../bundle/contract.js");
29
+ const preflight_js_1 = require("../indexer/preflight.js");
30
+ const continuity_js_1 = require("../indexer/continuity.js");
31
+ const import_js_2 = require("../scip/import.js");
32
+ const shapehash_js_1 = require("../indexer/shapehash.js");
33
+ const skeleton_js_1 = require("../indexer/skeleton.js");
34
+ class SeerMcpServer {
35
+ store;
36
+ indexer;
37
+ watcher = null;
38
+ mcp;
39
+ startedAt = Date.now();
40
+ workspace;
41
+ dbPath;
42
+ jitEnabled;
43
+ watchEnabled;
44
+ jitPromise = null;
45
+ constructor(options) {
46
+ this.workspace = path_1.default.resolve(options.workspace);
47
+ this.dbPath = options.dbPath ?? path_1.default.join(this.workspace, '.seer', 'graph.db');
48
+ this.jitEnabled = options.jit ?? true;
49
+ this.watchEnabled = options.watch ?? true;
50
+ this.mcp = new mcp_js_1.McpServer({ name: 'seer', version: '0.1.0' });
51
+ this.registerTools();
52
+ }
53
+ async start() {
54
+ const dir = path_1.default.dirname(this.dbPath);
55
+ if (!fs_1.default.existsSync(dir))
56
+ fs_1.default.mkdirSync(dir, { recursive: true });
57
+ this.store = new store_js_1.Store(this.dbPath);
58
+ this.indexer = new index_js_1.Indexer(this.store);
59
+ const stats = this.store.getStats();
60
+ if (stats.files === 0) {
61
+ process.stderr.write(`[seer-mcp] empty index; running initial index...\n`);
62
+ const r = await this.indexer.indexDirectory(this.workspace, { quiet: true });
63
+ process.stderr.write(`[seer-mcp] initial index: ${r.filesIndexed} files, ${r.symbols} symbols, ${r.elapsedMs}ms\n`);
64
+ }
65
+ if (this.watchEnabled) {
66
+ this.watcher = new watcher_js_1.SeerWatcher(this.workspace, this.store, this.indexer, {
67
+ log: (m) => process.stderr.write(`[watcher] ${m}\n`),
68
+ });
69
+ this.watcher.start();
70
+ }
71
+ const transport = new stdio_js_1.StdioServerTransport();
72
+ await this.mcp.connect(transport);
73
+ process.stderr.write(`[seer-mcp] ready workspace=${this.workspace}\n`);
74
+ }
75
+ async stop() {
76
+ if (this.watcher)
77
+ await this.watcher.stop();
78
+ try {
79
+ this.store.close();
80
+ }
81
+ catch { /* */ }
82
+ }
83
+ async ensureFresh() {
84
+ if (!this.jitEnabled)
85
+ return;
86
+ if (this.jitPromise) {
87
+ await this.jitPromise;
88
+ return;
89
+ }
90
+ this.jitPromise = (async () => {
91
+ try {
92
+ await (0, freshness_js_1.jitSync)(this.store, this.indexer, this.workspace, { maxDirty: 200 });
93
+ }
94
+ catch (err) {
95
+ process.stderr.write(`[seer-mcp] JIT failed: ${err}\n`);
96
+ }
97
+ finally {
98
+ this.jitPromise = null;
99
+ }
100
+ })();
101
+ await this.jitPromise;
102
+ }
103
+ text(obj) {
104
+ return { content: [{ type: 'text', text: JSON.stringify(obj, null, 2) }] };
105
+ }
106
+ /**
107
+ * Registry of every tool handler, keyed by name. Mirrors the MCP registration
108
+ * so seer_batch can dispatch internally without a second round-trip. The
109
+ * wrapper stores the raw handler and forwards to the SDK unchanged.
110
+ */
111
+ handlers = new Map();
112
+ registerTool(name, def, handler) {
113
+ this.handlers.set(name, handler);
114
+ this.mcp.registerTool(name, def, handler);
115
+ }
116
+ /**
117
+ * Up to 5 deterministic fuzzy suggestions (BM25 over the camel/snake-split
118
+ * FTS index) for a name that resolved to nothing. SUGGESTION-ONLY: callers
119
+ * surface these under a `didYouMean` key, never substitute them for a real
120
+ * lookup. Returning a guessed symbol as if exact would be exactly the
121
+ * "misleading information" Seer's contract forbids.
122
+ */
123
+ suggestSymbols(name) {
124
+ let rows;
125
+ try {
126
+ rows = this.store.searchSymbolsFts(name, { limit: 5 });
127
+ }
128
+ catch {
129
+ return [];
130
+ }
131
+ return rows.map(r => ({
132
+ name: r.name, qualifiedName: r.qualifiedName, kind: r.kind,
133
+ file: r.filePath, lineStart: r.lineStart,
134
+ }));
135
+ }
136
+ /**
137
+ * Emit a list response under a deterministic token budget. Items are assumed
138
+ * pre-sorted by relevance, so we prefix-trim: keep appending until the
139
+ * serialized payload would exceed `tokenBudget * 4` chars (~4 chars/token).
140
+ * Without a budget the output is byte-identical to the previous behavior.
141
+ */
142
+ budgetedText(base, items, tokenBudget, key = 'items') {
143
+ if (!tokenBudget || tokenBudget <= 0) {
144
+ return this.text({ ...base, returned: items.length, [key]: items });
145
+ }
146
+ const budgetChars = tokenBudget * 4;
147
+ const kept = [];
148
+ for (const it of items) {
149
+ kept.push(it);
150
+ const len = JSON.stringify({ ...base, returned: kept.length, [key]: kept }).length;
151
+ // Always keep at least one item; stop once we cross the budget.
152
+ if (len > budgetChars)
153
+ break;
154
+ }
155
+ const truncated = kept.length < items.length;
156
+ const out = { ...base, returned: kept.length, [key]: kept };
157
+ if (truncated) {
158
+ out.truncated = true;
159
+ out.omitted = items.length - kept.length;
160
+ out.tokenBudget = tokenBudget;
161
+ out.note = `Output trimmed to ~${tokenBudget} tokens (${kept.length}/${items.length} items shown). Raise tokenBudget, add a filter, or paginate for the rest.`;
162
+ }
163
+ return this.text(out);
164
+ }
165
+ // ── Lazy lifecycle resolution (AI-agent optimization §5a) ────────────────
166
+ // Derived passes (modules / shape-hash / symbol-history) normally run during
167
+ // indexing. When the DB was produced some other way (bundle import, partial
168
+ // index) the dependent tools used to silently return nothing until the agent
169
+ // hand-ran a *_build tool. These guards extend the JIT-freshness philosophy
170
+ // to those passes: build on first dependent query, once per process.
171
+ autoBuilt = { modules: false, shapes: false, history: false };
172
+ ensureModules() {
173
+ if (this.autoBuilt.modules)
174
+ return;
175
+ this.autoBuilt.modules = true;
176
+ try {
177
+ if (this.store.countModules() === 0)
178
+ (0, modules_js_1.buildModules)(this.store);
179
+ }
180
+ catch (err) {
181
+ process.stderr.write(`[seer-mcp] auto modules build skipped: ${err}\n`);
182
+ }
183
+ }
184
+ ensureShapeHashes() {
185
+ if (this.autoBuilt.shapes)
186
+ return;
187
+ this.autoBuilt.shapes = true;
188
+ try {
189
+ const row = this.store.rawDb()
190
+ .prepare('SELECT COUNT(*) AS c FROM symbols WHERE shape_hash IS NOT NULL')
191
+ .get();
192
+ if (Number(row.c) === 0)
193
+ (0, shapehash_js_1.buildShapeHashes)(this.store, {});
194
+ }
195
+ catch (err) {
196
+ process.stderr.write(`[seer-mcp] auto shape-hash build skipped: ${err}\n`);
197
+ }
198
+ }
199
+ async ensureSymbolHistory() {
200
+ if (this.autoBuilt.history)
201
+ return;
202
+ this.autoBuilt.history = true;
203
+ try {
204
+ const row = this.store.rawDb()
205
+ .prepare('SELECT COUNT(*) AS c FROM symbol_history')
206
+ .get();
207
+ if (Number(row.c) === 0) {
208
+ await (0, symbolhistory_js_1.buildSymbolHistory)(this.workspace, this.store, {
209
+ maxCommitsPerFile: 200, skipIfHeadUnchanged: true,
210
+ });
211
+ }
212
+ }
213
+ catch (err) {
214
+ process.stderr.write(`[seer-mcp] auto symbol-history build skipped: ${err}\n`);
215
+ }
216
+ }
217
+ registerTools() {
218
+ this.registerTool('seer_health', {
219
+ description: 'Server health, schema, file/symbol counts, watcher status. Cheap; no JIT.',
220
+ inputSchema: {},
221
+ }, async () => {
222
+ const schema = this.store.schemaInfo();
223
+ const stats = this.store.getStats();
224
+ const watcher = this.watcher ? this.watcher.syncStatus() : null;
225
+ return this.text({
226
+ workspace: this.workspace,
227
+ dbPath: this.dbPath,
228
+ schemaVersion: schema.dbVersion,
229
+ buildSchemaVersion: schema.buildVersion,
230
+ schemaCurrent: schema.current,
231
+ files: stats.files, symbols: stats.symbols, edges: stats.edges,
232
+ resolvedEdges: stats.resolvedEdges,
233
+ roles: stats.roles, languages: stats.languages,
234
+ routes: stats.routes,
235
+ externalDependencies: stats.externalDependencies,
236
+ configKeys: stats.configKeys,
237
+ symbolHistory: stats.symbolHistory,
238
+ modules: stats.modules ?? 0,
239
+ scipImports: stats.scipImports ?? 0,
240
+ shapeHashed: stats.shapeHashed ?? 0,
241
+ provenance: stats.provenance,
242
+ watcher, jitEnabled: this.jitEnabled,
243
+ uptimeMs: Date.now() - this.startedAt,
244
+ });
245
+ });
246
+ this.registerTool('seer_stats', {
247
+ description: 'Index statistics: counts, languages, roles, routes, deps, config keys. Runs JIT.',
248
+ inputSchema: {},
249
+ }, async () => {
250
+ await this.ensureFresh();
251
+ return this.text(this.store.getStats());
252
+ });
253
+ this.registerTool('seer_symbols', {
254
+ description: 'Search symbols by name (BM25 over name/qualified_name/signature with camelCase/snake_case split). Returns top by PageRank when query omitted. Excludes vendor/generated/test/declaration rows by default; pass include* to widen.',
255
+ inputSchema: {
256
+ query: zod_1.z.string().optional(),
257
+ top: zod_1.z.number().int().positive().max(500).optional(),
258
+ limit: zod_1.z.number().int().positive().max(500).optional(),
259
+ includeVendor: zod_1.z.boolean().optional(),
260
+ includeGenerated: zod_1.z.boolean().optional(),
261
+ includeTests: zod_1.z.boolean().optional(),
262
+ includeDeclarations: zod_1.z.boolean().optional(),
263
+ includeTypeRefs: zod_1.z.boolean().optional(),
264
+ tokenBudget: zod_1.z.number().int().positive().max(50000).optional()
265
+ .describe('Soft cap (~4 chars/token) that prefix-trims items, keeping the highest-ranked rows.'),
266
+ },
267
+ }, async ({ query, top, limit, includeVendor, includeGenerated, includeTests, includeDeclarations, includeTypeRefs, tokenBudget }) => {
268
+ await this.ensureFresh();
269
+ const opts = {
270
+ includeVendor: includeVendor ?? false,
271
+ includeGenerated: includeGenerated ?? false,
272
+ includeTests: includeTests ?? false,
273
+ includeDeclarations: includeDeclarations ?? false,
274
+ includeTypeRefs: includeTypeRefs ?? false,
275
+ };
276
+ let rows;
277
+ let total = null;
278
+ if (query) {
279
+ rows = this.store.searchSymbolsFts(query, { ...opts, limit: limit ?? 50 });
280
+ total = this.store.countSymbols(query, opts);
281
+ }
282
+ else {
283
+ rows = this.store.getTopSymbols(top ?? 20, opts);
284
+ }
285
+ if (query && rows.length === 0) {
286
+ const didYouMean = this.suggestSymbols(query);
287
+ return this.text({ total, returned: 0, items: [], source: 'tree-sitter',
288
+ ...(didYouMean.length > 0 ? { didYouMean } : {}) });
289
+ }
290
+ const items = rows.map(r => ({
291
+ id: r.id, name: r.name, qualifiedName: r.qualifiedName, kind: r.kind,
292
+ file: r.filePath, lineStart: r.lineStart, lineEnd: r.lineEnd,
293
+ pagerank: r.pagerank, signature: r.signature,
294
+ loc: r.loc, cyclomatic: r.cyclomatic, cognitive: r.cognitive,
295
+ symbolRole: r.symbolRole,
296
+ }));
297
+ return this.budgetedText({ total, source: 'tree-sitter' }, items, tokenBudget);
298
+ });
299
+ this.registerTool('seer_definition', {
300
+ description: 'Look up an exact symbol by name or qualified name. The optional `file` accepts an absolute path, the exact rel_path, OR a trailing path fragment on a segment boundary (e.g. "service.ts" or "auth/service.ts"). Excludes vendor/generated/test/declaration rows by default; pass include* to widen.',
301
+ inputSchema: {
302
+ name: zod_1.z.string(),
303
+ file: zod_1.z.string().optional(),
304
+ includeVendor: zod_1.z.boolean().optional(),
305
+ includeGenerated: zod_1.z.boolean().optional(),
306
+ includeTests: zod_1.z.boolean().optional(),
307
+ includeDeclarations: zod_1.z.boolean().optional(),
308
+ includeTypeRefs: zod_1.z.boolean().optional(),
309
+ tokenBudget: zod_1.z.number().int().positive().max(50000).optional()
310
+ .describe('Soft cap (~4 chars/token) that prefix-trims items, keeping the highest-PageRank rows.'),
311
+ },
312
+ }, async ({ name, file, includeVendor, includeGenerated, includeTests, includeDeclarations, includeTypeRefs, tokenBudget }) => {
313
+ await this.ensureFresh();
314
+ const rows = this.store.getDefinition(name, {
315
+ filePath: file,
316
+ includeVendor: includeVendor ?? false,
317
+ includeGenerated: includeGenerated ?? false,
318
+ includeTests: includeTests ?? false,
319
+ includeDeclarations: includeDeclarations ?? false,
320
+ includeTypeRefs: includeTypeRefs ?? false,
321
+ });
322
+ // Suggestion-only fuzzy fallback: never substitute, just hint.
323
+ if (rows.length === 0) {
324
+ const didYouMean = this.suggestSymbols(name);
325
+ return this.text({ total: 0, items: [], source: 'tree-sitter',
326
+ ...(didYouMean.length > 0 ? { didYouMean } : {}) });
327
+ }
328
+ const items = rows.map(r => ({
329
+ id: r.id, name: r.name, qualifiedName: r.qualifiedName, kind: r.kind,
330
+ file: r.filePath, lineStart: r.lineStart, lineEnd: r.lineEnd,
331
+ pagerank: r.pagerank, signature: r.signature,
332
+ loc: r.loc, cyclomatic: r.cyclomatic, cognitive: r.cognitive,
333
+ symbolRole: r.symbolRole,
334
+ }));
335
+ return this.budgetedText({ total: rows.length, source: 'tree-sitter' }, items, tokenBudget);
336
+ });
337
+ this.registerTool('seer_file_symbols', {
338
+ description: 'List symbols defined in a file (sorted by line).',
339
+ inputSchema: {
340
+ file: zod_1.z.string(),
341
+ limit: zod_1.z.number().int().positive().max(2000).optional(),
342
+ },
343
+ }, async ({ file, limit }) => {
344
+ await this.ensureFresh();
345
+ const rows = this.store.listSymbolsInFile(file, limit ?? 200);
346
+ return this.text({
347
+ file, total: rows.length,
348
+ items: rows.map(r => ({
349
+ id: r.id, name: r.name, qualifiedName: r.qualifiedName, kind: r.kind,
350
+ lineStart: r.lineStart, lineEnd: r.lineEnd, pagerank: r.pagerank,
351
+ signature: r.signature, loc: r.loc,
352
+ cyclomatic: r.cyclomatic, cognitive: r.cognitive,
353
+ })),
354
+ });
355
+ });
356
+ this.registerTool('seer_callers', {
357
+ description: 'Direct callers of a symbol, bounded preview + true total.',
358
+ inputSchema: {
359
+ symbol: zod_1.z.string(),
360
+ limit: zod_1.z.number().int().positive().max(500).optional(),
361
+ tokenBudget: zod_1.z.number().int().positive().max(50000).optional()
362
+ .describe('Soft cap (~4 chars/token) that prefix-trims the (already limit-bounded) caller list.'),
363
+ },
364
+ }, async ({ symbol, limit, tokenBudget }) => {
365
+ await this.ensureFresh();
366
+ const total = this.store.countCallers(symbol);
367
+ const items = this.store.findCallers(symbol, limit ?? 40).map(c => ({
368
+ callerName: c.callerName, callerQualifiedName: c.callerQualifiedName,
369
+ callerKind: c.callerKind, file: c.callerFile, line: c.callerLine,
370
+ edgeKind: c.edgeKind,
371
+ }));
372
+ if (total === 0) {
373
+ const didYouMean = this.suggestSymbols(symbol);
374
+ return this.text({ symbol, total: 0, returned: 0, items: [], source: 'tree-sitter',
375
+ ...(didYouMean.length > 0 ? { didYouMean } : {}) });
376
+ }
377
+ return this.budgetedText({ symbol, total, source: 'tree-sitter' }, items, tokenBudget);
378
+ });
379
+ this.registerTool('seer_callees', {
380
+ description: 'Direct callees of a symbol.',
381
+ inputSchema: {
382
+ symbol: zod_1.z.string(),
383
+ limit: zod_1.z.number().int().positive().max(500).optional(),
384
+ tokenBudget: zod_1.z.number().int().positive().max(50000).optional()
385
+ .describe('Soft cap (~4 chars/token) that prefix-trims the callee list.'),
386
+ },
387
+ }, async ({ symbol, limit, tokenBudget }) => {
388
+ await this.ensureFresh();
389
+ const all = this.store.findCallees(symbol);
390
+ const max = Math.min(all.length, limit ?? 40);
391
+ const items = all.slice(0, max).map(c => ({
392
+ calleeName: c.calleeName, calleeKind: c.calleeKind,
393
+ file: c.calleeFile, lineStart: c.calleeLineStart,
394
+ edgeKind: c.edgeKind,
395
+ source: c.calleeFile ? 'tree-sitter' : 'unresolved',
396
+ }));
397
+ return this.budgetedText({ symbol, total: all.length }, items, tokenBudget);
398
+ });
399
+ // Search: BM25 across symbols + files. Each symbol hit also gets enriched
400
+ // with the containing symbol when the match is non-symbol (e.g. file).
401
+ this.registerTool('seer_search', {
402
+ description: 'Combined BM25 search across symbol names and file paths. Use this first; follow up with seer_definition / seer_file_symbols. Excludes vendor/generated/test/declaration rows by default.',
403
+ inputSchema: {
404
+ query: zod_1.z.string().min(1),
405
+ limit: zod_1.z.number().int().positive().max(200).optional(),
406
+ includeVendor: zod_1.z.boolean().optional(),
407
+ includeGenerated: zod_1.z.boolean().optional(),
408
+ includeTests: zod_1.z.boolean().optional(),
409
+ includeDeclarations: zod_1.z.boolean().optional(),
410
+ includeTypeRefs: zod_1.z.boolean().optional(),
411
+ },
412
+ }, async ({ query, limit, includeVendor, includeGenerated, includeTests, includeDeclarations, includeTypeRefs }) => {
413
+ await this.ensureFresh();
414
+ const opts = {
415
+ includeVendor: includeVendor ?? false,
416
+ includeGenerated: includeGenerated ?? false,
417
+ includeTests: includeTests ?? false,
418
+ includeDeclarations: includeDeclarations ?? false,
419
+ includeTypeRefs: includeTypeRefs ?? false,
420
+ };
421
+ const symHits = this.store.searchSymbolsFts(query, { ...opts, limit: limit ?? 30 });
422
+ const symbolTotal = this.store.countSymbols(query, opts);
423
+ const fileHits = this.store.searchFilesFts(query, limit ?? 30, {
424
+ includeVendor: opts.includeVendor,
425
+ includeGenerated: opts.includeGenerated,
426
+ includeTests: opts.includeTests,
427
+ });
428
+ return this.text({
429
+ query,
430
+ symbolHits: {
431
+ total: symbolTotal, returned: symHits.length,
432
+ items: symHits.map(r => ({
433
+ id: r.id, name: r.name, qualifiedName: r.qualifiedName,
434
+ kind: r.kind, file: r.filePath, lineStart: r.lineStart,
435
+ pagerank: r.pagerank, symbolRole: r.symbolRole,
436
+ })),
437
+ },
438
+ fileHits: {
439
+ total: fileHits.length,
440
+ items: fileHits.map(f => ({ path: f.path, relPath: f.relPath, language: f.language, role: f.role })),
441
+ },
442
+ source: 'tree-sitter',
443
+ note: 'Search-first: call seer_definition or seer_file_symbols on the chosen hit.',
444
+ });
445
+ });
446
+ this.registerTool('seer_reindex', {
447
+ description: 'Reindex the workspace (incremental). Pass reset=true to wipe.',
448
+ inputSchema: { reset: zod_1.z.boolean().optional() },
449
+ }, async ({ reset }) => {
450
+ if (reset) {
451
+ this.store.close();
452
+ try {
453
+ fs_1.default.unlinkSync(this.dbPath);
454
+ }
455
+ catch { /* */ }
456
+ try {
457
+ fs_1.default.unlinkSync(this.dbPath + '-wal');
458
+ }
459
+ catch { /* */ }
460
+ try {
461
+ fs_1.default.unlinkSync(this.dbPath + '-shm');
462
+ }
463
+ catch { /* */ }
464
+ this.store = new store_js_1.Store(this.dbPath);
465
+ this.indexer = new index_js_1.Indexer(this.store);
466
+ if (this.watcher) {
467
+ await this.watcher.stop();
468
+ this.watcher = new watcher_js_1.SeerWatcher(this.workspace, this.store, this.indexer, {
469
+ log: (m) => process.stderr.write(`[watcher] ${m}\n`),
470
+ });
471
+ this.watcher.start();
472
+ }
473
+ }
474
+ const r = await this.indexer.indexDirectory(this.workspace, { quiet: true });
475
+ return this.text({
476
+ reset: Boolean(reset),
477
+ filesIndexed: r.filesIndexed,
478
+ filesReusedFromCache: r.filesReusedFromCache,
479
+ symbols: r.symbols, edges: r.edges, resolvedEdges: r.resolvedEdges,
480
+ externalDependencies: r.externalDependencies,
481
+ testEdgesAdded: r.testEdgesAdded,
482
+ routesResolved: r.routesResolved,
483
+ elapsedMs: r.elapsedMs,
484
+ pagerankRecomputed: r.pagerankRecomputed,
485
+ });
486
+ });
487
+ // ── Track-C tools ───────────────────────────────────────────────────────
488
+ this.registerTool('seer_routes', {
489
+ description: 'List HTTP routes detected in source (Express/Fastify/FastAPI/Flask/Spring).',
490
+ inputSchema: {
491
+ method: zod_1.z.string().optional(),
492
+ framework: zod_1.z.string().optional(),
493
+ pathSubstr: zod_1.z.string().optional(),
494
+ limit: zod_1.z.number().int().positive().max(500).optional(),
495
+ },
496
+ }, async ({ method, framework, pathSubstr, limit }) => {
497
+ await this.ensureFresh();
498
+ const rows = this.store.listRoutes({ method, framework, pathSubstr, limit: limit ?? 100 });
499
+ return this.text({
500
+ total: this.store.countRoutes(),
501
+ returned: rows.length,
502
+ items: rows,
503
+ source: 'tree-sitter',
504
+ });
505
+ });
506
+ this.registerTool('seer_service_calls', {
507
+ description: 'v9 Track H — List outbound HTTP/tRPC/GraphQL/gRPC/messaging service client calls. ' +
508
+ 'Each row is AST-attributed to its enclosing function/method. Pagination via limit/offset; ' +
509
+ 'filter by protocol, method, framework, path substring, caller symbol, or min confidence.',
510
+ inputSchema: {
511
+ protocol: zod_1.z.string().optional(),
512
+ method: zod_1.z.string().optional(),
513
+ framework: zod_1.z.string().optional(),
514
+ pathSubstr: zod_1.z.string().optional(),
515
+ callerSymbolId: zod_1.z.number().int().nonnegative().optional(),
516
+ minConfidence: zod_1.z.number().min(0).max(1).optional(),
517
+ limit: zod_1.z.number().int().positive().max(1000).optional(),
518
+ offset: zod_1.z.number().int().nonnegative().optional(),
519
+ summaryOnly: zod_1.z.boolean().optional(),
520
+ tokenBudget: zod_1.z.number().int().positive().max(50000).optional()
521
+ .describe('Soft cap (~4 chars/token) that prefix-trims the returned items.'),
522
+ },
523
+ }, async (args) => {
524
+ await this.ensureFresh();
525
+ const limit = args.limit ?? 100;
526
+ const rows = this.store.listServiceCalls({ ...args, limit });
527
+ const total = this.store.countServiceCalls();
528
+ if (args.summaryOnly) {
529
+ return this.text({ total, returned: rows.length, source: 'tree-sitter' });
530
+ }
531
+ return this.budgetedText({ total, offset: args.offset ?? 0, source: 'tree-sitter' }, rows, args.tokenBudget);
532
+ });
533
+ this.registerTool('seer_service_links', {
534
+ description: 'v9 Track H — List deterministic service-link rendezvous between client calls and route handlers. ' +
535
+ 'Each link carries match_kind (literal_path / env_base / service_host / route_pattern / ' +
536
+ 'trpc_procedure / graphql_operation / grpc_method / topic_match / queue_match / exchange_match), confidence, ' +
537
+ 'and an evidence_json blob enumerating ambiguity candidates. Filter by protocol, method, path, ' +
538
+ 'caller/handler symbol id, match_kind, or min confidence.',
539
+ inputSchema: {
540
+ protocol: zod_1.z.string().optional(),
541
+ method: zod_1.z.string().optional(),
542
+ pathSubstr: zod_1.z.string().optional(),
543
+ callerSymbolId: zod_1.z.number().int().nonnegative().optional(),
544
+ handlerSymbolId: zod_1.z.number().int().nonnegative().optional(),
545
+ matchKind: zod_1.z.string().optional(),
546
+ minConfidence: zod_1.z.number().min(0).max(1).optional(),
547
+ limit: zod_1.z.number().int().positive().max(1000).optional(),
548
+ offset: zod_1.z.number().int().nonnegative().optional(),
549
+ summaryOnly: zod_1.z.boolean().optional(),
550
+ tokenBudget: zod_1.z.number().int().positive().max(50000).optional()
551
+ .describe('Soft cap (~4 chars/token) that prefix-trims the returned items.'),
552
+ },
553
+ }, async (args) => {
554
+ await this.ensureFresh();
555
+ const limit = args.limit ?? 100;
556
+ const rows = this.store.listServiceLinks({ ...args, limit });
557
+ const total = this.store.countServiceLinks();
558
+ if (args.summaryOnly) {
559
+ return this.text({ total, returned: rows.length, source: 'tree-sitter' });
560
+ }
561
+ return this.budgetedText({ total, offset: args.offset ?? 0, source: 'tree-sitter' }, rows, args.tokenBudget);
562
+ });
563
+ this.registerTool('seer_trace_service_path', {
564
+ description: 'v8 Track G — Shortest service-link path between two symbols (bounded BFS). ' +
565
+ 'Treats each service_link as a directed edge caller→handler. Returns the chain of ' +
566
+ 'symbol ids and names; empty when unreachable within maxDepth.',
567
+ inputSchema: {
568
+ from: zod_1.z.string().describe('Source symbol name or qualified name'),
569
+ to: zod_1.z.string().describe('Target symbol name or qualified name'),
570
+ maxDepth: zod_1.z.number().int().positive().max(20).optional(),
571
+ },
572
+ }, async ({ from, to, maxDepth }) => {
573
+ await this.ensureFresh();
574
+ const fRows = this.store.getDefinition(from);
575
+ const tRows = this.store.getDefinition(to);
576
+ if (fRows.length === 0)
577
+ return this.text({ ok: false, error: `Source symbol not found: ${from}` });
578
+ if (tRows.length === 0)
579
+ return this.text({ ok: false, error: `Target symbol not found: ${to}` });
580
+ const ids = this.store.traceServicePath(fRows[0].id, tRows[0].id, maxDepth ?? 6);
581
+ if (ids.length === 0)
582
+ return this.text({ ok: true, found: false, path: [] });
583
+ const items = ids.map(id => {
584
+ const row = this.store.rawDb().prepare(`SELECT id, name, qualified_name AS qualifiedName, kind FROM symbols WHERE id = ?`).get(id);
585
+ return {
586
+ id: Number(row.id),
587
+ name: String(row.name),
588
+ qualifiedName: row.qualifiedName == null ? null : String(row.qualifiedName),
589
+ kind: String(row.kind),
590
+ };
591
+ });
592
+ return this.text({ ok: true, found: true, hops: items.length - 1, path: items });
593
+ });
594
+ this.registerTool('seer_trace_service_dependencies', {
595
+ description: 'v9 Track H — Bounded BFS over service-link edges from one symbol. ' +
596
+ 'Returns every handler reachable within maxDepth/maxNodes/maxFanout, ' +
597
+ 'each with its depth, the protocols carrying traffic, and the hop chain. ' +
598
+ '`cutoff` reports which limit fired (maxNodes / maxDepth / maxFanout) if any.',
599
+ inputSchema: {
600
+ from: zod_1.z.string().describe('Source symbol name or qualified name'),
601
+ maxDepth: zod_1.z.number().int().positive().max(20).optional(),
602
+ maxNodes: zod_1.z.number().int().positive().max(2000).optional(),
603
+ maxFanout: zod_1.z.number().int().positive().max(200).optional(),
604
+ },
605
+ }, async ({ from, maxDepth, maxNodes, maxFanout }) => {
606
+ await this.ensureFresh();
607
+ const fRows = this.store.getDefinition(from);
608
+ if (fRows.length === 0)
609
+ return this.text({ ok: false, error: `Source symbol not found: ${from}` });
610
+ const r = this.store.traceServiceDependencies(fRows[0].id, { maxDepth, maxNodes, maxFanout });
611
+ const items = r.reached.map(x => {
612
+ const row = this.store.rawDb().prepare(`SELECT id, name, qualified_name AS qualifiedName, kind FROM symbols WHERE id = ?`).get(x.symbolId);
613
+ return {
614
+ symbolId: x.symbolId,
615
+ name: row ? String(row.name) : null,
616
+ qualifiedName: row?.qualifiedName == null ? null : String(row.qualifiedName),
617
+ kind: row ? String(row.kind) : null,
618
+ depth: x.depth,
619
+ protocols: x.protocols,
620
+ matchKinds: x.matchKinds,
621
+ hops: x.hops,
622
+ };
623
+ });
624
+ return this.text({
625
+ ok: true,
626
+ from: { id: fRows[0].id, name: fRows[0].name, qualifiedName: fRows[0].qualifiedName },
627
+ reached: items.length,
628
+ cutoff: r.cutoff,
629
+ items,
630
+ });
631
+ });
632
+ this.registerTool('seer_trace_module_service_dependencies', {
633
+ description: 'v9 Track H — Bounded BFS over cross-module service-link edges from one ' +
634
+ 'module. Returns each downstream module with hop depth, the protocols ' +
635
+ 'carrying traffic, and the total cross-module link weight feeding it.',
636
+ inputSchema: {
637
+ moduleId: zod_1.z.number().int().nonnegative(),
638
+ maxDepth: zod_1.z.number().int().positive().max(10).optional(),
639
+ maxNodes: zod_1.z.number().int().positive().max(500).optional(),
640
+ },
641
+ }, async ({ moduleId, maxDepth, maxNodes }) => {
642
+ await this.ensureFresh();
643
+ const r = this.store.traceModuleServiceDependencies(moduleId, { maxDepth, maxNodes });
644
+ // Hydrate module metadata for the response so callers don't need a
645
+ // follow-up tool call.
646
+ const ids = r.reached.map(x => x.moduleId);
647
+ let metaById = new Map();
648
+ if (ids.length > 0) {
649
+ const rows = this.store.rawDb().prepare(`SELECT id, label, size_files AS sizeFiles FROM modules WHERE id IN (${ids.map(() => '?').join(',')})`).all(...ids);
650
+ for (const row of rows) {
651
+ metaById.set(Number(row.id), { label: String(row.label), sizeFiles: Number(row.sizeFiles) });
652
+ }
653
+ }
654
+ const items = r.reached.map(x => ({
655
+ moduleId: x.moduleId,
656
+ label: metaById.get(x.moduleId)?.label ?? null,
657
+ sizeFiles: metaById.get(x.moduleId)?.sizeFiles ?? 0,
658
+ depth: x.depth,
659
+ protocols: x.protocols,
660
+ viaLinks: x.viaLinks,
661
+ }));
662
+ return this.text({
663
+ ok: true,
664
+ fromModuleId: moduleId,
665
+ reached: items.length,
666
+ cutoff: r.cutoff,
667
+ items,
668
+ });
669
+ });
670
+ this.registerTool('seer_dependencies', {
671
+ description: 'List external dependencies declared in package manifests / lockfiles.',
672
+ inputSchema: {
673
+ ecosystem: zod_1.z.string().optional(),
674
+ nameSubstr: zod_1.z.string().optional(),
675
+ limit: zod_1.z.number().int().positive().max(2000).optional(),
676
+ },
677
+ }, async ({ ecosystem, nameSubstr, limit }) => {
678
+ await this.ensureFresh();
679
+ const rows = this.store.listExternalDeps({ ecosystem, nameSubstr, limit: limit ?? 200 });
680
+ return this.text({
681
+ total: this.store.countExternalDeps(),
682
+ returned: rows.length,
683
+ items: rows,
684
+ });
685
+ });
686
+ this.registerTool('seer_config', {
687
+ description: 'List static env/config reads detected in source (process.env, os.getenv, System.getenv).',
688
+ inputSchema: {
689
+ key: zod_1.z.string().optional(),
690
+ source: zod_1.z.string().optional(),
691
+ limit: zod_1.z.number().int().positive().max(2000).optional(),
692
+ },
693
+ }, async ({ key, source, limit }) => {
694
+ await this.ensureFresh();
695
+ const rows = this.store.listConfigKeys({ key, source, limit: limit ?? 200 });
696
+ return this.text({
697
+ total: this.store.countConfigKeys(),
698
+ returned: rows.length,
699
+ items: rows,
700
+ });
701
+ });
702
+ this.registerTool('seer_complexity', {
703
+ description: 'Rank functions/methods by complexity. Useful for risk-aware editing. Excludes vendor/generated/test/declaration rows by default.',
704
+ inputSchema: {
705
+ by: zod_1.z.enum(['cyclomatic', 'cognitive', 'loc', 'max_nesting']).optional(),
706
+ minValue: zod_1.z.number().int().nonnegative().optional(),
707
+ limit: zod_1.z.number().int().positive().max(500).optional(),
708
+ includeVendor: zod_1.z.boolean().optional(),
709
+ includeGenerated: zod_1.z.boolean().optional(),
710
+ includeTests: zod_1.z.boolean().optional(),
711
+ includeDeclarations: zod_1.z.boolean().optional(),
712
+ tokenBudget: zod_1.z.number().int().positive().max(50000).optional()
713
+ .describe('Soft cap (~4 chars/token) that prefix-trims items, keeping the most complex rows.'),
714
+ },
715
+ }, async ({ by, minValue, limit, includeVendor, includeGenerated, includeTests, includeDeclarations, tokenBudget }) => {
716
+ await this.ensureFresh();
717
+ const col = by ?? 'cyclomatic';
718
+ const min = minValue ?? 1;
719
+ const lim = limit ?? 50;
720
+ const conds = [`s.${col} >= ?`];
721
+ const args = [min];
722
+ if (!includeVendor)
723
+ conds.push('f.is_vendor = 0');
724
+ if (!includeGenerated)
725
+ conds.push('f.is_generated = 0');
726
+ if (!includeTests)
727
+ conds.push(`f.role <> 'test'`);
728
+ if (!includeDeclarations)
729
+ conds.push(`(s.symbol_role IS NULL OR s.symbol_role <> 'declaration')`);
730
+ args.push(lim);
731
+ const sql = `
732
+ SELECT s.id, s.name, s.qualified_name AS qualifiedName, s.kind,
733
+ f.path AS file, s.line_start AS lineStart, s.line_end AS lineEnd,
734
+ s.loc, s.cyclomatic, s.cognitive, s.max_nesting AS maxNesting,
735
+ s.pagerank
736
+ FROM symbols s JOIN files f ON f.id = s.file_id
737
+ WHERE ${conds.join(' AND ')}
738
+ ORDER BY s.${col} DESC, s.pagerank DESC
739
+ LIMIT ?
740
+ `;
741
+ const rows = this.store.rawDb().prepare(sql).all(...args);
742
+ return this.budgetedText({ by: col, minValue: min }, rows, tokenBudget);
743
+ });
744
+ this.registerTool('seer_behavior', {
745
+ description: 'Ranked behavioral contract for a symbol: direct/indirect/naming-convention/same-file tests with assertion counts, graph distance, and recency. Use this BEFORE editing a symbol to find the tests that describe its expected behavior.',
746
+ inputSchema: {
747
+ symbol: zod_1.z.string(),
748
+ limit: zod_1.z.number().int().positive().max(200).optional(),
749
+ indirectDepth: zod_1.z.number().int().nonnegative().max(4).optional()
750
+ .describe('BFS depth for indirect coverage (callers that transitively reach the symbol). 0 disables indirect.'),
751
+ includeNamingConvention: zod_1.z.boolean().optional(),
752
+ includeSameFile: zod_1.z.boolean().optional(),
753
+ },
754
+ }, async ({ symbol, limit, indirectDepth, includeNamingConvention, includeSameFile }) => {
755
+ await this.ensureFresh();
756
+ const result = (0, behavior_js_1.rankedBehavior)(this.store, symbol, {
757
+ limit: limit ?? 30,
758
+ indirectDepth: indirectDepth ?? 2,
759
+ includeNamingConvention: includeNamingConvention ?? true,
760
+ includeSameFile: includeSameFile ?? true,
761
+ });
762
+ if (!result) {
763
+ const didYouMean = this.suggestSymbols(symbol);
764
+ return this.text({ symbol, total: 0, direct: 0, indirect: 0, tests: [], reason: `no symbol "${symbol}"`,
765
+ ...(didYouMean.length > 0 ? { didYouMean } : {}) });
766
+ }
767
+ return this.text(result);
768
+ });
769
+ this.registerTool('seer_trace_path', {
770
+ description: 'Bounded BFS shortest call path from one symbol to another.',
771
+ inputSchema: {
772
+ from: zod_1.z.string(),
773
+ to: zod_1.z.string(),
774
+ maxDepth: zod_1.z.number().int().positive().max(12).optional(),
775
+ },
776
+ }, async ({ from, to, maxDepth }) => {
777
+ await this.ensureFresh();
778
+ const fromCandidates = this.store.getDefinition(from);
779
+ const toCandidates = this.store.getDefinition(to);
780
+ if (fromCandidates.length === 0)
781
+ return this.text({ found: false, reason: `no symbol "${from}"` });
782
+ if (toCandidates.length === 0)
783
+ return this.text({ found: false, reason: `no symbol "${to}"` });
784
+ // Try the highest-PageRank pair first.
785
+ for (const f of fromCandidates.slice(0, 5)) {
786
+ for (const t of toCandidates.slice(0, 5)) {
787
+ const p = this.store.tracePath(f.id, t.id, maxDepth ?? 6);
788
+ if (p)
789
+ return this.text({ found: true, depth: p.length - 1, path: p });
790
+ }
791
+ }
792
+ return this.text({ found: false, reason: `no path within depth ${maxDepth ?? 6}` });
793
+ });
794
+ this.registerTool('seer_trace_callers', {
795
+ description: 'Bounded reverse-reachable callers of a symbol (transitive blast radius). Returns each caller with the BFS depth at which it was found.',
796
+ inputSchema: {
797
+ symbol: zod_1.z.string(),
798
+ maxDepth: zod_1.z.number().int().positive().max(8).optional(),
799
+ maxNodes: zod_1.z.number().int().positive().max(50000).optional(),
800
+ limit: zod_1.z.number().int().positive().max(500).optional(),
801
+ },
802
+ }, async ({ symbol, maxDepth, maxNodes, limit }) => {
803
+ await this.ensureFresh();
804
+ const defs = this.store.getDefinition(symbol);
805
+ if (defs.length === 0)
806
+ return this.text({ found: false, reason: `no symbol "${symbol}"` });
807
+ const target = defs[0];
808
+ const hits = this.store.reverseReachableWithDepth(target.id, maxDepth ?? 4, maxNodes ?? 20000);
809
+ const lim = Math.min(hits.length, limit ?? 100);
810
+ const ids = hits.slice(0, lim).map(h => h.id);
811
+ if (ids.length === 0) {
812
+ return this.text({ symbol: { id: target.id, name: target.name }, maxDepth: maxDepth ?? 4, total: 0, items: [] });
813
+ }
814
+ const ph = ids.map(() => '?').join(',');
815
+ const rows = this.store.rawDb().prepare(`
816
+ SELECT s.id, s.name, s.qualified_name AS qualifiedName, s.kind,
817
+ f.path AS file, s.line_start AS lineStart, s.pagerank
818
+ FROM symbols s JOIN files f ON f.id = s.file_id
819
+ WHERE s.id IN (${ph})
820
+ `).all(...ids);
821
+ const byId = new Map(rows.map(r => [Number(r.id), r]));
822
+ const items = hits.slice(0, lim).map(h => {
823
+ const r = byId.get(h.id);
824
+ return r ? {
825
+ id: Number(r.id), name: String(r.name),
826
+ qualifiedName: r.qualifiedName == null ? null : String(r.qualifiedName),
827
+ kind: String(r.kind), file: String(r.file), lineStart: Number(r.lineStart),
828
+ pagerank: Number(r.pagerank), depth: h.depth,
829
+ } : { id: h.id, name: '', qualifiedName: null, kind: '', file: '', lineStart: 0, pagerank: 0, depth: h.depth };
830
+ });
831
+ items.sort((a, b) => a.depth - b.depth || b.pagerank - a.pagerank);
832
+ return this.text({
833
+ symbol: { id: target.id, name: target.name, qualifiedName: target.qualifiedName },
834
+ maxDepth: maxDepth ?? 4, total: hits.length, returned: items.length,
835
+ items, source: 'tree-sitter',
836
+ });
837
+ });
838
+ this.registerTool('seer_trace_callees', {
839
+ description: 'Bounded forward-reachable callees of a symbol (everything its call graph reaches within depth N).',
840
+ inputSchema: {
841
+ symbol: zod_1.z.string(),
842
+ maxDepth: zod_1.z.number().int().positive().max(8).optional(),
843
+ maxNodes: zod_1.z.number().int().positive().max(50000).optional(),
844
+ limit: zod_1.z.number().int().positive().max(500).optional(),
845
+ },
846
+ }, async ({ symbol, maxDepth, maxNodes, limit }) => {
847
+ await this.ensureFresh();
848
+ const defs = this.store.getDefinition(symbol);
849
+ if (defs.length === 0)
850
+ return this.text({ found: false, reason: `no symbol "${symbol}"` });
851
+ const target = defs[0];
852
+ const hits = this.store.forwardReachableWithDepth(target.id, maxDepth ?? 4, maxNodes ?? 20000);
853
+ const lim = Math.min(hits.length, limit ?? 100);
854
+ const ids = hits.slice(0, lim).map(h => h.id);
855
+ if (ids.length === 0) {
856
+ return this.text({ symbol: { id: target.id, name: target.name }, maxDepth: maxDepth ?? 4, total: 0, items: [] });
857
+ }
858
+ const ph = ids.map(() => '?').join(',');
859
+ const rows = this.store.rawDb().prepare(`
860
+ SELECT s.id, s.name, s.qualified_name AS qualifiedName, s.kind,
861
+ f.path AS file, s.line_start AS lineStart, s.pagerank
862
+ FROM symbols s JOIN files f ON f.id = s.file_id
863
+ WHERE s.id IN (${ph})
864
+ `).all(...ids);
865
+ const byId = new Map(rows.map(r => [Number(r.id), r]));
866
+ const items = hits.slice(0, lim).map(h => {
867
+ const r = byId.get(h.id);
868
+ return r ? {
869
+ id: Number(r.id), name: String(r.name),
870
+ qualifiedName: r.qualifiedName == null ? null : String(r.qualifiedName),
871
+ kind: String(r.kind), file: String(r.file), lineStart: Number(r.lineStart),
872
+ pagerank: Number(r.pagerank), depth: h.depth,
873
+ } : { id: h.id, name: '', qualifiedName: null, kind: '', file: '', lineStart: 0, pagerank: 0, depth: h.depth };
874
+ });
875
+ items.sort((a, b) => a.depth - b.depth || b.pagerank - a.pagerank);
876
+ return this.text({
877
+ symbol: { id: target.id, name: target.name, qualifiedName: target.qualifiedName },
878
+ maxDepth: maxDepth ?? 4, total: hits.length, returned: items.length,
879
+ items, source: 'tree-sitter',
880
+ });
881
+ });
882
+ this.registerTool('seer_architecture', {
883
+ description: 'One-page snapshot of the codebase: languages, modules, top symbols, entry points, hotspots, deps.',
884
+ inputSchema: {},
885
+ }, async () => {
886
+ await this.ensureFresh();
887
+ return this.text((0, architecture_js_1.buildArchitecture)(this.workspace, this.store));
888
+ });
889
+ this.registerTool('seer_detect_changes', {
890
+ description: 'Compute blast-radius of an uncommitted (or between-refs) diff. Direct + transitive callers.',
891
+ inputSchema: {
892
+ fromRef: zod_1.z.string().optional(),
893
+ toRef: zod_1.z.string().optional(),
894
+ callerDepth: zod_1.z.number().int().positive().max(6).optional(),
895
+ },
896
+ }, async ({ fromRef, toRef, callerDepth }) => {
897
+ await this.ensureFresh();
898
+ return this.text((0, detectchanges_js_1.detectChanges)(this.workspace, this.store, { fromRef, toRef, callerDepth }));
899
+ });
900
+ this.registerTool('seer_churn', {
901
+ description: 'Run a file-level git churn pass (commit counts, last commit, authors). Idempotent.',
902
+ inputSchema: {},
903
+ }, async () => {
904
+ return this.text(await (0, churn_js_1.collectChurn)(this.workspace, this.store));
905
+ });
906
+ // ── Track-D tools ───────────────────────────────────────────────────────
907
+ this.registerTool('seer_history', {
908
+ description: 'Per-symbol git history. Returns commits whose hunks overlap the symbol\'s line range.',
909
+ inputSchema: {
910
+ symbol: zod_1.z.string(),
911
+ limit: zod_1.z.number().int().positive().max(200).optional(),
912
+ since: zod_1.z.number().int().optional().describe('Unix-seconds lower bound on committed_at'),
913
+ file: zod_1.z.string().optional(),
914
+ },
915
+ }, async ({ symbol, limit, since, file }) => {
916
+ await this.ensureFresh();
917
+ await this.ensureSymbolHistory();
918
+ const candidates = this.store.getDefinition(symbol, { filePath: file });
919
+ const items = [];
920
+ for (const c of candidates.slice(0, 5)) {
921
+ const history = this.store.getSymbolHistory(c.id, { limit: limit ?? 50, since });
922
+ const total = this.store.countSymbolHistory(c.id);
923
+ const continuity = (0, continuity_js_1.getContinuityForSymbol)(this.store, c.id);
924
+ items.push({
925
+ symbol: { id: c.id, name: c.name, qualifiedName: c.qualifiedName, kind: c.kind, file: c.filePath },
926
+ total,
927
+ returned: history.length,
928
+ commits: history.map(h => ({
929
+ sha: h.commitSha,
930
+ author: h.authorName, email: h.authorEmail,
931
+ committedAt: h.committedAt,
932
+ message: h.message,
933
+ linesAdded: h.linesAdded, linesRemoved: h.linesRemoved,
934
+ prNumber: h.prNumber, prUrl: h.prUrl,
935
+ matchStrategy: h.matchStrategy, confidence: h.confidence,
936
+ })),
937
+ continuity,
938
+ });
939
+ }
940
+ return this.text({
941
+ symbol, returned: items.length, results: items,
942
+ note: 'Honest limits: file renames followed via --follow; symbol renames cut off history at the rename commit. Confidence drops with commit age.',
943
+ });
944
+ });
945
+ this.registerTool('seer_symbol_history_build', {
946
+ description: '(Advanced — usually unnecessary.) seer_history auto-builds this index on first use. Call only to force a refresh or set a custom maxCommitsPerFile. Can take minutes on large repos.',
947
+ inputSchema: {
948
+ maxCommitsPerFile: zod_1.z.number().int().positive().max(2000).optional(),
949
+ force: zod_1.z.boolean().optional(),
950
+ },
951
+ }, async ({ maxCommitsPerFile, force }) => {
952
+ const r = await (0, symbolhistory_js_1.buildSymbolHistory)(this.workspace, this.store, {
953
+ maxCommitsPerFile: maxCommitsPerFile ?? 200,
954
+ skipIfHeadUnchanged: !force,
955
+ });
956
+ return this.text(r);
957
+ });
958
+ // ── Track-E tools ───────────────────────────────────────────────────────
959
+ this.registerTool('seer_modules', {
960
+ description: 'List modules (Louvain clusters of files) — agents should start here to orient before reading files. Each module reports size, primary language, cohesion (intra-module edges / total), and centrality (sum of member PageRank).',
961
+ inputSchema: {
962
+ limit: zod_1.z.number().int().positive().max(500).optional(),
963
+ sortBy: zod_1.z.enum(['centrality', 'size', 'label']).optional(),
964
+ },
965
+ }, async ({ limit, sortBy }) => {
966
+ await this.ensureFresh();
967
+ this.ensureModules();
968
+ const modules = this.store.listModules({ limit: limit ?? 50, sortBy });
969
+ return this.text({
970
+ total: this.store.countModules(),
971
+ returned: modules.length,
972
+ items: modules,
973
+ source: 'tree-sitter',
974
+ });
975
+ });
976
+ this.registerTool('seer_module_members', {
977
+ description: 'List files and top-PageRank symbols inside a module. Address the module by `id` or `label`.',
978
+ inputSchema: {
979
+ id: zod_1.z.number().int().positive().optional(),
980
+ label: zod_1.z.string().optional(),
981
+ fileLimit: zod_1.z.number().int().positive().max(5000).optional(),
982
+ symbolLimit: zod_1.z.number().int().positive().max(500).optional(),
983
+ },
984
+ }, async ({ id, label, fileLimit, symbolLimit }) => {
985
+ await this.ensureFresh();
986
+ this.ensureModules();
987
+ const mod = id != null ? this.store.getModuleById(id)
988
+ : label != null ? this.store.getModuleByLabel(label)
989
+ : null;
990
+ if (!mod)
991
+ return this.text({ found: false, reason: id != null ? `no module #${id}` : `no module "${label}"` });
992
+ const files = this.store.listModuleMembers(mod.id, fileLimit ?? 500);
993
+ const symbols = this.store.listModuleTopSymbols(mod.id, symbolLimit ?? 25);
994
+ return this.text({
995
+ module: mod,
996
+ files: { total: files.length, items: files },
997
+ topSymbols: { returned: symbols.length, items: symbols.map(s => ({
998
+ id: s.id, name: s.name, qualifiedName: s.qualifiedName,
999
+ kind: s.kind, file: s.filePath, lineStart: s.lineStart,
1000
+ pagerank: s.pagerank,
1001
+ })) },
1002
+ source: 'tree-sitter',
1003
+ });
1004
+ });
1005
+ this.registerTool('seer_symbol_module', {
1006
+ description: 'Look up the module a symbol belongs to. Helpful for "what part of the codebase does X live in?".',
1007
+ inputSchema: {
1008
+ symbol: zod_1.z.string(),
1009
+ file: zod_1.z.string().optional(),
1010
+ },
1011
+ }, async ({ symbol, file }) => {
1012
+ await this.ensureFresh();
1013
+ this.ensureModules();
1014
+ const defs = this.store.getDefinition(symbol, { filePath: file });
1015
+ if (defs.length === 0) {
1016
+ const didYouMean = this.suggestSymbols(symbol);
1017
+ return this.text({ found: false, reason: `no symbol "${symbol}"`,
1018
+ ...(didYouMean.length > 0 ? { didYouMean } : {}) });
1019
+ }
1020
+ const out = [];
1021
+ for (const d of defs.slice(0, 5)) {
1022
+ const mod = this.store.moduleForFile(d.fileId);
1023
+ out.push({
1024
+ symbol: { id: d.id, name: d.name, qualifiedName: d.qualifiedName, kind: d.kind, file: d.filePath },
1025
+ module: mod,
1026
+ });
1027
+ }
1028
+ return this.text({ matches: out, source: 'tree-sitter' });
1029
+ });
1030
+ this.registerTool('seer_module_dependencies', {
1031
+ description: 'List module-to-module dependency edges. Direction "out" = modules this one calls/imports/tests into (default). "in" = modules that depend on this one. Edges are aggregated cross-module weights for calls / imports / tests.',
1032
+ inputSchema: {
1033
+ id: zod_1.z.number().int().positive().optional(),
1034
+ label: zod_1.z.string().optional(),
1035
+ direction: zod_1.z.enum(['in', 'out']).optional(),
1036
+ limit: zod_1.z.number().int().positive().max(500).optional(),
1037
+ },
1038
+ }, async ({ id, label, direction, limit }) => {
1039
+ await this.ensureFresh();
1040
+ this.ensureModules();
1041
+ const mod = id != null ? this.store.getModuleById(id)
1042
+ : label != null ? this.store.getModuleByLabel(label)
1043
+ : null;
1044
+ if (!mod)
1045
+ return this.text({ found: false, reason: id != null ? `no module #${id}` : `no module "${label}"` });
1046
+ const deps = this.store.moduleDependencies(mod.id, {
1047
+ direction: direction ?? 'out',
1048
+ limit: limit ?? 100,
1049
+ });
1050
+ return this.text({
1051
+ module: mod, direction: direction ?? 'out',
1052
+ returned: deps.length, items: deps, source: 'tree-sitter',
1053
+ });
1054
+ });
1055
+ this.registerTool('seer_trace_file_dependencies', {
1056
+ description: 'Bounded BFS over the resolved import graph starting at a file. Returns each reachable file with the depth at which it was first seen.',
1057
+ inputSchema: {
1058
+ file: zod_1.z.string(),
1059
+ maxDepth: zod_1.z.number().int().positive().max(8).optional(),
1060
+ maxNodes: zod_1.z.number().int().positive().max(20000).optional(),
1061
+ },
1062
+ }, async ({ file, maxDepth, maxNodes }) => {
1063
+ await this.ensureFresh();
1064
+ const files = this.store.listFiles();
1065
+ const norm = (p) => p.replace(/\\/g, '/').toLowerCase();
1066
+ const match = files.find(f => norm(f.path) === norm(file) || norm(f.relPath) === norm(file)
1067
+ || norm(f.path).endsWith(norm(file)) || norm(f.relPath).endsWith(norm(file)));
1068
+ if (!match)
1069
+ return this.text({ found: false, reason: `no indexed file matching "${file}"` });
1070
+ const closure = this.store.fileImportClosure(match.id, maxDepth ?? 4, maxNodes ?? 5000);
1071
+ closure.sort((a, b) => a.depth - b.depth || (a.relPath < b.relPath ? -1 : 1));
1072
+ return this.text({
1073
+ from: { id: match.id, path: match.path, relPath: match.relPath, language: match.language },
1074
+ maxDepth: maxDepth ?? 4,
1075
+ totalReachable: closure.length,
1076
+ items: closure.map(c => ({ id: c.id, relPath: c.relPath, language: c.language, depth: c.depth })),
1077
+ source: 'tree-sitter',
1078
+ });
1079
+ });
1080
+ this.registerTool('seer_trace_module_dependencies', {
1081
+ description: 'Bounded BFS over the module dependency graph. Returns each reachable module with the depth at which it was first seen.',
1082
+ inputSchema: {
1083
+ id: zod_1.z.number().int().positive().optional(),
1084
+ label: zod_1.z.string().optional(),
1085
+ maxDepth: zod_1.z.number().int().positive().max(8).optional(),
1086
+ direction: zod_1.z.enum(['in', 'out']).optional(),
1087
+ },
1088
+ }, async ({ id, label, maxDepth, direction }) => {
1089
+ await this.ensureFresh();
1090
+ this.ensureModules();
1091
+ const mod = id != null ? this.store.getModuleById(id)
1092
+ : label != null ? this.store.getModuleByLabel(label)
1093
+ : null;
1094
+ if (!mod)
1095
+ return this.text({ found: false, reason: id != null ? `no module #${id}` : `no module "${label}"` });
1096
+ const depth = Math.min(maxDepth ?? 4, 8);
1097
+ const dir = direction ?? 'out';
1098
+ const seen = new Map([[mod.id, 0]]);
1099
+ const queue = [{ id: mod.id, depth: 0 }];
1100
+ while (queue.length > 0) {
1101
+ const cur = queue.shift();
1102
+ if (cur.depth >= depth)
1103
+ continue;
1104
+ const deps = this.store.moduleDependencies(cur.id, { direction: dir, limit: 500 });
1105
+ for (const d of deps) {
1106
+ if (seen.has(d.moduleId))
1107
+ continue;
1108
+ seen.set(d.moduleId, cur.depth + 1);
1109
+ queue.push({ id: d.moduleId, depth: cur.depth + 1 });
1110
+ }
1111
+ }
1112
+ seen.delete(mod.id);
1113
+ const items = Array.from(seen.entries()).map(([mid, d]) => {
1114
+ const m = this.store.getModuleById(mid);
1115
+ return { id: mid, label: m?.label ?? null, depth: d };
1116
+ });
1117
+ items.sort((a, b) => a.depth - b.depth || ((a.label ?? '') < (b.label ?? '') ? -1 : 1));
1118
+ return this.text({
1119
+ from: mod, direction: dir, maxDepth: depth,
1120
+ totalReachable: items.length, items, source: 'tree-sitter',
1121
+ });
1122
+ });
1123
+ this.registerTool('seer_modules_build', {
1124
+ description: '(Advanced — usually unnecessary.) Module clustering (Louvain) runs automatically during indexing and auto-builds on first seer_modules* query. Call only to force a rebuild. Idempotent.',
1125
+ inputSchema: {},
1126
+ }, async () => {
1127
+ const r = (0, modules_js_1.buildModules)(this.store);
1128
+ return this.text(r);
1129
+ });
1130
+ this.registerTool('seer_risk', {
1131
+ description: 'Deterministic edit-risk profile for a symbol. Returns a decomposed score with per-signal contributions: fan-in, route exposure, test coverage, complexity, churn, config reads, and module-boundary crossings. The verdict (low/medium/high) is for triage; the signals are the evidence.',
1132
+ inputSchema: {
1133
+ symbol: zod_1.z.string(),
1134
+ callerDepth: zod_1.z.number().int().positive().max(6).optional(),
1135
+ },
1136
+ }, async ({ symbol, callerDepth }) => {
1137
+ await this.ensureFresh();
1138
+ const r = (0, risk_js_1.computeRisk)(this.store, symbol, { callerDepth: callerDepth ?? 3 });
1139
+ if (!r) {
1140
+ const didYouMean = this.suggestSymbols(symbol);
1141
+ return this.text({ found: false, reason: `no symbol "${symbol}"`,
1142
+ ...(didYouMean.length > 0 ? { didYouMean } : {}) });
1143
+ }
1144
+ return this.text(r);
1145
+ });
1146
+ // ── Track-F tools (portability + precision) ─────────────────────────────
1147
+ this.registerTool('seer_bundle_export', {
1148
+ description: 'Export the current index as a portable .seerbundle file. Use this in CI or to share a pre-built index with teammates so they skip the cold-start indexing cost.',
1149
+ inputSchema: {
1150
+ out: zod_1.z.string().optional().describe('Output path (default: <workspace>/.seer/index.seerbundle)'),
1151
+ compressionLevel: zod_1.z.number().int().min(0).max(9).optional(),
1152
+ builtAt: zod_1.z.number().int().optional().describe('Pin manifest.builtAt (Unix millis) for reproducible bundle bytes.'),
1153
+ },
1154
+ }, async ({ out, compressionLevel, builtAt }) => {
1155
+ const r = await (0, export_js_1.exportBundle)(this.dbPath, this.workspace, {
1156
+ out, compressionLevel, builtAt,
1157
+ });
1158
+ return this.text({
1159
+ bundlePath: r.bundlePath, bytes: r.bytes,
1160
+ manifest: r.manifest, elapsedMs: r.elapsedMs,
1161
+ });
1162
+ });
1163
+ this.registerTool('seer_bundle_info', {
1164
+ description: 'Read a bundle\'s manifest without unpacking the DB (schema version, file count, symbol/edge totals, SCIP layers).',
1165
+ inputSchema: { bundle: zod_1.z.string() },
1166
+ }, async ({ bundle }) => {
1167
+ try {
1168
+ return this.text((0, import_js_1.readBundleManifest)(path_1.default.resolve(bundle)));
1169
+ }
1170
+ catch (err) {
1171
+ return this.text({ ok: false, reason: err.message });
1172
+ }
1173
+ });
1174
+ this.registerTool('seer_bundle_import', {
1175
+ description: 'Import a .seerbundle. Defaults to destructive whole-index restore. Pass external=true to import additively as a read-only external layer (peer-repo evidence) that does not replace any local rows.',
1176
+ inputSchema: {
1177
+ bundle: zod_1.z.string(),
1178
+ overwrite: zod_1.z.boolean().optional(),
1179
+ skipIntegrityCheck: zod_1.z.boolean().optional(),
1180
+ skipSchemaCheck: zod_1.z.boolean().optional(),
1181
+ external: zod_1.z.boolean().optional().describe('Additive external import — never replaces the local DB.'),
1182
+ alias: zod_1.z.string().optional().describe('External-only: alias for the imported layer.'),
1183
+ force: zod_1.z.boolean().optional().describe('External-only: force re-import even if the same hash is already present.'),
1184
+ },
1185
+ }, async ({ bundle, overwrite, skipIntegrityCheck, skipSchemaCheck, external, alias, force }) => {
1186
+ if (external) {
1187
+ try {
1188
+ const r = await (0, external_js_1.importExternalBundle)(path_1.default.resolve(bundle), this.store, {
1189
+ alias, force,
1190
+ });
1191
+ return this.text({
1192
+ ok: true, external: true,
1193
+ bundleId: r.bundleId, externalProject: r.externalProject,
1194
+ externalHash: r.externalHash, schemaVersion: r.schemaVersion,
1195
+ routesImported: r.routesImported,
1196
+ serviceEndpointsImported: r.serviceEndpointsImported,
1197
+ alreadyImported: r.alreadyImported,
1198
+ elapsedMs: r.elapsedMs,
1199
+ });
1200
+ }
1201
+ catch (err) {
1202
+ return this.text({ ok: false, external: true, reason: err.message });
1203
+ }
1204
+ }
1205
+ try {
1206
+ // Closing the store ensures the file isn't locked when we overwrite.
1207
+ const wasWatchEnabled = this.watcher != null;
1208
+ if (this.watcher) {
1209
+ await this.watcher.stop();
1210
+ this.watcher = null;
1211
+ }
1212
+ this.store.close();
1213
+ // Use this.dbPath so a server started with `--db custom.db` keeps
1214
+ // serving the same file after import. Without this override the
1215
+ // bundle would land at <workspace>/.seer/graph.db (the default in
1216
+ // importBundle) while the server kept reading from `custom.db`.
1217
+ const r = await (0, import_js_1.importBundle)(path_1.default.resolve(bundle), {
1218
+ repoRoot: this.workspace, overwrite,
1219
+ skipIntegrityCheck, skipSchemaCheck,
1220
+ dbOut: this.dbPath,
1221
+ });
1222
+ // Re-open against the freshly imported DB.
1223
+ this.store = new store_js_1.Store(this.dbPath);
1224
+ this.indexer = new index_js_1.Indexer(this.store);
1225
+ if (wasWatchEnabled) {
1226
+ this.watcher = new watcher_js_1.SeerWatcher(this.workspace, this.store, this.indexer, {
1227
+ log: (m) => process.stderr.write(`[watcher] ${m}\n`),
1228
+ });
1229
+ this.watcher.start();
1230
+ }
1231
+ return this.text({
1232
+ ok: true, dbPath: r.dbPath, manifest: r.manifest, elapsedMs: r.elapsedMs,
1233
+ });
1234
+ }
1235
+ catch (err) {
1236
+ return this.text({ ok: false, reason: err.message });
1237
+ }
1238
+ });
1239
+ this.registerTool('seer_continuity', {
1240
+ description: 'v10 — Rename/move continuity candidates for a symbol. When the exact symbol_key history walk terminates at a rename/move boundary, this tool surfaces honest, confidence-labelled candidates for the previous identity (shape_hash exact / close match, same containing scope, similar name). Always advisory — confidence < 1.0 reflects ambiguity.',
1241
+ inputSchema: {
1242
+ symbol: zod_1.z.string(),
1243
+ file: zod_1.z.string().optional(),
1244
+ },
1245
+ }, async ({ symbol, file }) => {
1246
+ await this.ensureFresh();
1247
+ this.ensureShapeHashes();
1248
+ const defs = this.store.getDefinition(symbol, { filePath: file });
1249
+ if (defs.length === 0) {
1250
+ const didYouMean = this.suggestSymbols(symbol);
1251
+ return this.text({ ok: false, reason: `no symbol "${symbol}"`,
1252
+ ...(didYouMean.length > 0 ? { didYouMean } : {}) });
1253
+ }
1254
+ const items = defs.slice(0, 5).map(d => ({
1255
+ symbol: { id: d.id, name: d.name, qualifiedName: d.qualifiedName, kind: d.kind, file: d.filePath },
1256
+ candidates: (0, continuity_js_1.getContinuityForSymbol)(this.store, d.id),
1257
+ }));
1258
+ return this.text({ ok: true, results: items });
1259
+ });
1260
+ this.registerTool('seer_boundaries', {
1261
+ description: 'v10 — List monorepo package/service boundaries detected from manifests (package.json/pyproject.toml/Cargo.toml/go.mod/composer.json) and the services/* / packages/* / apps/* / libs/* fallback. Strictly advisory.',
1262
+ inputSchema: {
1263
+ limit: zod_1.z.number().int().positive().max(1000).optional(),
1264
+ },
1265
+ }, async ({ limit }) => {
1266
+ await this.ensureFresh();
1267
+ const items = this.store.listBoundaries(limit ?? 100);
1268
+ return this.text({
1269
+ total: this.store.countBoundaries(),
1270
+ returned: items.length,
1271
+ items,
1272
+ source: 'tree-sitter',
1273
+ });
1274
+ });
1275
+ this.registerTool('seer_boundary_for_file', {
1276
+ description: 'v10 — Look up the boundary that owns a file. Returns null when no boundary matched (file lives outside any detected package/service root).',
1277
+ inputSchema: {
1278
+ file: zod_1.z.string(),
1279
+ },
1280
+ }, async ({ file }) => {
1281
+ await this.ensureFresh();
1282
+ const files = this.store.listFiles();
1283
+ const norm = (p) => p.replace(/\\/g, '/').toLowerCase();
1284
+ const match = files.find(f => norm(f.path) === norm(file) || norm(f.relPath) === norm(file)
1285
+ || norm(f.path).endsWith(norm(file)) || norm(f.relPath).endsWith(norm(file)));
1286
+ if (!match)
1287
+ return this.text({ ok: false, reason: `no indexed file matching "${file}"` });
1288
+ const boundary = this.store.boundaryForFile(match.id);
1289
+ return this.text({
1290
+ ok: true,
1291
+ file: { id: match.id, relPath: match.relPath },
1292
+ boundary,
1293
+ });
1294
+ });
1295
+ this.registerTool('seer_boundary_dependencies', {
1296
+ description: 'v10 — Cross-boundary dependency edges from a given boundary (aggregated cross-boundary call/import/service-link weights).',
1297
+ inputSchema: {
1298
+ boundaryId: zod_1.z.number().int().nonnegative(),
1299
+ direction: zod_1.z.enum(['in', 'out']).optional(),
1300
+ limit: zod_1.z.number().int().positive().max(500).optional(),
1301
+ },
1302
+ }, async ({ boundaryId, direction, limit }) => {
1303
+ await this.ensureFresh();
1304
+ const items = this.store.boundaryDependencies(boundaryId, {
1305
+ direction: direction ?? 'out',
1306
+ limit: limit ?? 100,
1307
+ });
1308
+ return this.text({
1309
+ boundaryId, direction: direction ?? 'out',
1310
+ returned: items.length,
1311
+ items,
1312
+ });
1313
+ });
1314
+ this.registerTool('seer_preflight', {
1315
+ description: 'Compact "should I edit this?" evidence packet. Pass `symbol` for a single-symbol packet (risk, likely tests, service impact, history), or `fromRef`/`toRef` for a diff-range packet (touched symbols, aggregated risk, likely tests, service impact). Optional `oldBundle`/`newBundle` adds a contract diff to the packet. Output is structured facts only — no AI prose.',
1316
+ inputSchema: {
1317
+ symbol: zod_1.z.string().optional(),
1318
+ file: zod_1.z.string().optional(),
1319
+ fromRef: zod_1.z.string().optional(),
1320
+ toRef: zod_1.z.string().optional(),
1321
+ oldBundle: zod_1.z.string().optional(),
1322
+ newBundle: zod_1.z.string().optional(),
1323
+ maxSymbols: zod_1.z.number().int().positive().max(50).optional(),
1324
+ maxTests: zod_1.z.number().int().positive().max(50).optional(),
1325
+ maxHistory: zod_1.z.number().int().positive().max(50).optional(),
1326
+ callerDepth: zod_1.z.number().int().positive().max(6).optional(),
1327
+ },
1328
+ }, async (args) => {
1329
+ await this.ensureFresh();
1330
+ const r = await (0, preflight_js_1.preflight)(this.store, {
1331
+ symbol: args.symbol,
1332
+ filePath: args.file,
1333
+ fromRef: args.fromRef,
1334
+ toRef: args.toRef,
1335
+ workspace: this.workspace,
1336
+ oldBundle: args.oldBundle,
1337
+ newBundle: args.newBundle,
1338
+ maxSymbols: args.maxSymbols,
1339
+ maxTests: args.maxTests,
1340
+ maxHistory: args.maxHistory,
1341
+ callerDepth: args.callerDepth,
1342
+ });
1343
+ return this.text(r);
1344
+ });
1345
+ this.registerTool('seer_contract_diff', {
1346
+ description: 'Diff API/service contracts between two .seerbundle artifacts (routes, tRPC/GraphQL/gRPC operations, topics, queues). Advisory only — never raises an error for breaking changes. Pass includeAffectedCallers to enrich the diff with service-link evidence when both bundles contain it.',
1347
+ inputSchema: {
1348
+ oldBundle: zod_1.z.string(),
1349
+ newBundle: zod_1.z.string(),
1350
+ includeAffectedCallers: zod_1.z.boolean().optional(),
1351
+ },
1352
+ }, async ({ oldBundle, newBundle, includeAffectedCallers }) => {
1353
+ try {
1354
+ const diff = await (0, contract_js_1.contractDiff)(path_1.default.resolve(oldBundle), path_1.default.resolve(newBundle), { includeAffectedCallers });
1355
+ return this.text({ ok: true, ...diff });
1356
+ }
1357
+ catch (err) {
1358
+ return this.text({ ok: false, reason: err.message });
1359
+ }
1360
+ });
1361
+ this.registerTool('seer_external_bundles', {
1362
+ description: 'List external .seerbundle layers imported into this workspace. Each entry carries the source bundle path, external project alias, manifest hash, schemaVersion, and the rendezvous counts (routes / service endpoints) contributed by that layer.',
1363
+ inputSchema: {
1364
+ includeRoutes: zod_1.z.boolean().optional().describe('When true, also returns a bounded preview of the external routes contributed by each layer.'),
1365
+ routesPreviewLimit: zod_1.z.number().int().positive().max(500).optional(),
1366
+ },
1367
+ }, async ({ includeRoutes, routesPreviewLimit }) => {
1368
+ const layers = this.store.listExternalBundles();
1369
+ const previewLimit = routesPreviewLimit ?? 25;
1370
+ const items = layers.map(layer => {
1371
+ const base = {
1372
+ id: layer.id,
1373
+ sourceKind: layer.sourceKind,
1374
+ bundlePath: layer.bundlePath,
1375
+ externalProject: layer.externalProject,
1376
+ externalVersion: layer.externalVersion,
1377
+ externalHash: layer.externalHash,
1378
+ schemaVersion: layer.schemaVersion,
1379
+ importedAt: layer.importedAt,
1380
+ routesImported: layer.routesImported,
1381
+ serviceCallsImported: layer.serviceCallsImported,
1382
+ serviceLinksImported: layer.serviceLinksImported,
1383
+ };
1384
+ if (!includeRoutes)
1385
+ return base;
1386
+ const routes = this.store.listExternalRoutes({ bundleId: layer.id, limit: previewLimit });
1387
+ return { ...base, routesPreview: routes };
1388
+ });
1389
+ return this.text({
1390
+ total: items.length,
1391
+ items,
1392
+ source: 'external-bundle',
1393
+ });
1394
+ });
1395
+ this.registerTool('seer_scip_import', {
1396
+ description: 'Import a SCIP precision index. Adds source-labelled precise edges (provenance="scip") over the tree-sitter baseline. Tree-sitter rows are never deleted; overlapping rows are tagged "scip-merge" instead.',
1397
+ inputSchema: {
1398
+ scipPath: zod_1.z.string(),
1399
+ requireFileInIndex: zod_1.z.boolean().optional().describe('Skip SCIP docs whose file isn\'t already indexed (default: true)'),
1400
+ },
1401
+ }, async ({ scipPath, requireFileInIndex }) => {
1402
+ try {
1403
+ const r = await (0, import_js_2.importScip)(path_1.default.resolve(scipPath), this.store, {
1404
+ repoRoot: this.workspace,
1405
+ requireFileInIndex: requireFileInIndex ?? true,
1406
+ });
1407
+ return this.text(r);
1408
+ }
1409
+ catch (err) {
1410
+ return this.text({ ok: false, reason: err.message });
1411
+ }
1412
+ });
1413
+ this.registerTool('seer_scip_imports', {
1414
+ description: 'List every SCIP index that\'s been folded into this DB. Each entry includes the producer tool, sha256, and per-import symbol/ref counts so agents can see exactly which precision layers contributed.',
1415
+ inputSchema: {},
1416
+ }, async () => {
1417
+ return this.text({
1418
+ items: this.store.listScipImports(),
1419
+ provenance: this.store.getProvenanceCounts(),
1420
+ });
1421
+ });
1422
+ this.registerTool('seer_provenance', {
1423
+ description: 'Breakdown of symbols + edges by provenance (tree-sitter / scip / scip-merge). Lets agents tell which signals came from a precise indexer vs the tree-sitter baseline.',
1424
+ inputSchema: {},
1425
+ }, async () => {
1426
+ await this.ensureFresh();
1427
+ return this.text({
1428
+ provenance: this.store.getProvenanceCounts(),
1429
+ scipImports: this.store.listScipImports(),
1430
+ });
1431
+ });
1432
+ this.registerTool('seer_duplicates', {
1433
+ description: 'Find clusters of structurally near-duplicate functions/methods (SimHash over the body token shape, identifier-folded so renames still match). Returns each cluster sorted by size with Hamming distance from the cluster anchor.',
1434
+ inputSchema: {
1435
+ maxDistance: zod_1.z.number().int().nonnegative().max(32).optional()
1436
+ .describe('Max Hamming distance for clustering (default: 6).'),
1437
+ minLoc: zod_1.z.number().int().positive().optional()
1438
+ .describe('Minimum LOC for a symbol to count (default: 4).'),
1439
+ includeTests: zod_1.z.boolean().optional(),
1440
+ limit: zod_1.z.number().int().positive().max(1000).optional(),
1441
+ },
1442
+ }, async ({ maxDistance, minLoc, includeTests, limit }) => {
1443
+ await this.ensureFresh();
1444
+ this.ensureShapeHashes();
1445
+ const clusters = (0, shapehash_js_1.findDuplicates)(this.store, {
1446
+ maxDistance: maxDistance ?? 6,
1447
+ minLoc: minLoc ?? 4,
1448
+ includeTests: includeTests ?? false,
1449
+ maxClusters: limit ?? 50,
1450
+ });
1451
+ // bigint isn't JSON-serializable — render as hex.
1452
+ return this.text({
1453
+ clusters: clusters.length,
1454
+ items: clusters.map(c => ({
1455
+ fingerprint: c.fingerprint.toString(16),
1456
+ size: c.symbols.length,
1457
+ symbols: c.symbols,
1458
+ })),
1459
+ source: 'tree-sitter',
1460
+ });
1461
+ });
1462
+ this.registerTool('seer_shape_hash_build', {
1463
+ description: '(Advanced — usually unnecessary.) The shape-hash pass (Track-F SimHash) runs automatically during indexing and auto-builds on first seer_duplicates / seer_continuity query. Call only to force a re-hash. Idempotent.',
1464
+ inputSchema: {
1465
+ force: zod_1.z.boolean().optional().describe('Re-hash symbols that already have a hash.'),
1466
+ minLoc: zod_1.z.number().int().positive().optional(),
1467
+ },
1468
+ }, async ({ force, minLoc }) => {
1469
+ const r = (0, shapehash_js_1.buildShapeHashes)(this.store, { force, minLoc });
1470
+ return this.text(r);
1471
+ });
1472
+ this.registerTool('seer_context', {
1473
+ description: 'One compact pre-edit packet for a symbol: definition, callers, callees, routes, config, behavioral tests, recent history, complexity, module, blast radius, and deterministic risk. Use this as the first call before editing a symbol — then drill in with seer_callers / seer_history / seer_behavior as needed.',
1474
+ inputSchema: {
1475
+ symbol: zod_1.z.string(),
1476
+ file: zod_1.z.string().optional(),
1477
+ callerLimit: zod_1.z.number().int().positive().max(100).optional(),
1478
+ calleeLimit: zod_1.z.number().int().positive().max(100).optional(),
1479
+ testLimit: zod_1.z.number().int().positive().max(100).optional(),
1480
+ historyLimit: zod_1.z.number().int().positive().max(50).optional(),
1481
+ callerDepth: zod_1.z.number().int().positive().max(6).optional(),
1482
+ affectedLimit: zod_1.z.number().int().positive().max(100).optional(),
1483
+ },
1484
+ }, async ({ symbol, file, callerLimit, calleeLimit, testLimit, historyLimit, callerDepth, affectedLimit }) => {
1485
+ await this.ensureFresh();
1486
+ const packet = (0, context_js_1.buildContext)(this.store, symbol, {
1487
+ filePath: file,
1488
+ callerLimit, calleeLimit, testLimit, historyLimit,
1489
+ callerDepth, affectedLimit,
1490
+ });
1491
+ if (!packet) {
1492
+ const didYouMean = this.suggestSymbols(symbol);
1493
+ return this.text({ found: false, reason: `no symbol "${symbol}"`,
1494
+ ...(didYouMean.length > 0 ? { didYouMean } : {}) });
1495
+ }
1496
+ return this.text(packet);
1497
+ });
1498
+ // ── AI-agent optimization tools ─────────────────────────────────────────
1499
+ this.registerTool('seer_skeleton', {
1500
+ description: 'Render a file as a structural skeleton: every symbol signature is kept, bodies are collapsed to fold markers carrying the exact collapsed line count. Deterministic source elision (not AI summarization) — a token-cheap way to grasp a file\'s shape before reading it in full. Pass `focusSymbol` to expand one symbol\'s real body inline while everything else stays collapsed.',
1501
+ inputSchema: {
1502
+ file: zod_1.z.string().describe('Absolute path, exact rel_path, or a trailing path fragment on a / boundary.'),
1503
+ focusSymbol: zod_1.z.string().optional().describe('Expand this symbol\'s body verbatim; collapse the rest.'),
1504
+ },
1505
+ }, async ({ file, focusSymbol }) => {
1506
+ await this.ensureFresh();
1507
+ return this.text((0, skeleton_js_1.buildSkeleton)(this.store, file, { focusSymbol }));
1508
+ });
1509
+ this.registerTool('seer_trace', {
1510
+ description: 'Unified graph-trace entry point. Set `scope` and pass the matching `args`:\n' +
1511
+ '• callers {symbol, maxDepth?, maxNodes?, limit?} — transitive reverse callers (blast radius)\n' +
1512
+ '• callees {symbol, maxDepth?, maxNodes?, limit?} — transitive forward callees\n' +
1513
+ '• path {from, to, maxDepth?} — shortest call path A→B\n' +
1514
+ '• file {file, maxDepth?, maxNodes?} — import-graph closure from a file\n' +
1515
+ '• module {id|label, maxDepth?, direction?} — module dependency reachability\n' +
1516
+ '• service {from, maxDepth?, maxNodes?, maxFanout?} — service-link reachability\n' +
1517
+ '• service_path {from, to, maxDepth?} — shortest service-link path\n' +
1518
+ '• module_service {moduleId, maxDepth?, maxNodes?} — cross-module service-link reachability\n' +
1519
+ 'Delegates to the specific seer_trace_* tool (each still available for direct use).',
1520
+ inputSchema: {
1521
+ scope: zod_1.z.enum([
1522
+ 'callers', 'callees', 'path', 'file',
1523
+ 'module', 'service', 'service_path', 'module_service',
1524
+ ]),
1525
+ args: zod_1.z.any().optional(),
1526
+ },
1527
+ }, async ({ scope, args }) => {
1528
+ const map = {
1529
+ callers: 'seer_trace_callers',
1530
+ callees: 'seer_trace_callees',
1531
+ path: 'seer_trace_path',
1532
+ file: 'seer_trace_file_dependencies',
1533
+ module: 'seer_trace_module_dependencies',
1534
+ service: 'seer_trace_service_dependencies',
1535
+ service_path: 'seer_trace_service_path',
1536
+ module_service: 'seer_trace_module_service_dependencies',
1537
+ };
1538
+ const target = map[scope];
1539
+ const h = target ? this.handlers.get(target) : undefined;
1540
+ if (!h)
1541
+ return this.text({ ok: false, error: `unsupported scope "${scope}"` });
1542
+ // The umbrella accepts `args` as opaque (z.any()), so the delegate's own
1543
+ // required-param schema isn't enforced by the SDK here. Catch its throws
1544
+ // and return a clean, advisory error rather than a raw binding failure.
1545
+ try {
1546
+ return await h(args ?? {});
1547
+ }
1548
+ catch (err) {
1549
+ return this.text({ ok: false, scope, error: `seer_trace[${scope}] failed: ${err.message}` });
1550
+ }
1551
+ });
1552
+ this.registerTool('seer_batch', {
1553
+ description: 'Run several read-only Seer tools in one call and get all results back together. ' +
1554
+ 'Saves turns when the fan-out is known up front (e.g. definition + callers + behavior + risk for one symbol). ' +
1555
+ 'Each entry is {tool, args}. Calls run sequentially in one process; one failure never aborts the rest. ' +
1556
+ 'seer_batch cannot nest, and it is intended for read-only tools.',
1557
+ inputSchema: {
1558
+ calls: zod_1.z.array(zod_1.z.object({
1559
+ tool: zod_1.z.string(),
1560
+ args: zod_1.z.any().optional(),
1561
+ })).min(1).max(25),
1562
+ },
1563
+ }, async ({ calls }) => {
1564
+ const results = [];
1565
+ for (const c of calls) {
1566
+ const toolName = c && typeof c.tool === 'string' ? c.tool : null;
1567
+ if (!toolName || toolName === 'seer_batch') {
1568
+ results.push({ tool: toolName, ok: false, error: 'missing tool name or nested seer_batch (disallowed)' });
1569
+ continue;
1570
+ }
1571
+ const h = this.handlers.get(toolName);
1572
+ if (!h) {
1573
+ results.push({ tool: toolName, ok: false, error: `unknown tool "${toolName}"` });
1574
+ continue;
1575
+ }
1576
+ try {
1577
+ const r = await h(c.args ?? {});
1578
+ const raw = r?.content?.[0]?.text;
1579
+ let parsed;
1580
+ try {
1581
+ parsed = raw != null ? JSON.parse(raw) : null;
1582
+ }
1583
+ catch {
1584
+ parsed = raw ?? null;
1585
+ }
1586
+ results.push({ tool: toolName, ok: true, result: parsed });
1587
+ }
1588
+ catch (err) {
1589
+ results.push({ tool: toolName, ok: false, error: err.message });
1590
+ }
1591
+ }
1592
+ return this.text({ batch: true, count: results.length, results });
1593
+ });
1594
+ }
1595
+ }
1596
+ exports.SeerMcpServer = SeerMcpServer;
1597
+ async function runMcp(options) {
1598
+ const server = new SeerMcpServer(options);
1599
+ const shutdown = async () => {
1600
+ try {
1601
+ await server.stop();
1602
+ }
1603
+ catch { /* */ }
1604
+ process.exit(0);
1605
+ };
1606
+ process.on('SIGINT', shutdown);
1607
+ process.on('SIGTERM', shutdown);
1608
+ await server.start();
1609
+ }
1610
+ //# sourceMappingURL=server.js.map