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,930 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.Indexer = void 0;
40
+ const fs_1 = __importDefault(require("fs"));
41
+ const crypto_1 = __importDefault(require("crypto"));
42
+ const path_1 = __importDefault(require("path"));
43
+ const discovery_js_1 = require("./discovery.js");
44
+ const index_js_1 = require("../parser/index.js");
45
+ const workerpool_js_1 = require("../parser/workerpool.js");
46
+ const pagerank_js_1 = require("../graph/pagerank.js");
47
+ const classify_js_1 = require("./classify.js");
48
+ const modules_js_1 = require("./modules.js");
49
+ const boundaries_js_1 = require("./boundaries.js");
50
+ const shapehash_js_1 = require("./shapehash.js");
51
+ const continuity_js_1 = require("./continuity.js");
52
+ const serviceLinks_js_1 = require("./serviceLinks.js");
53
+ const protoScanner_js_1 = require("./protoScanner.js");
54
+ const serviceHostScanner_js_1 = require("./serviceHostScanner.js");
55
+ const DEFAULT_MAX_FILE_BYTES = 0; // no cap by default — completeness first
56
+ const DEFAULT_IO_CONCURRENCY = 8; // matches the file-handle budget on most OSes comfortably
57
+ const DEFAULT_IO_PREFETCH_BYTES = 64 * 1024 * 1024; // 64 MiB
58
+ const PARALLEL_AUTO_MIN_FILES = 100; // below this, default to serial unless explicitly forced
59
+ // Filenames that are almost always generated boilerplate (Unreal Header Tool
60
+ // produces *.generated.h; protobufs produce *.pb.h / *.pb.cc; etc.). We skip
61
+ // them at the per-file level so the discovery glob can stay simple.
62
+ const SKIP_FILENAME_PATTERNS = [
63
+ /\.generated\.h$/i,
64
+ /\.gen\.cpp$/i,
65
+ /\.gen\.h$/i,
66
+ /\.pb\.cc$/,
67
+ /\.pb\.h$/,
68
+ ];
69
+ function shouldSkipFilename(relativePath) {
70
+ return SKIP_FILENAME_PATTERNS.some(re => re.test(relativePath));
71
+ }
72
+ // ── Byte-aware semaphore ────────────────────────────────────────────────────────
73
+ //
74
+ // Caps total bytes of file content held in the prefetch buffer. A waiter is
75
+ // admitted as soon as either (a) the new total fits within `capacity`, or
76
+ // (b) the budget is empty (so a single oversize file never deadlocks — we
77
+ // always allow at least one read in flight).
78
+ //
79
+ // The implementation is deliberately FIFO: we only wake the *head* waiter, so
80
+ // a flood of small reads can't perpetually starve a single large one queued
81
+ // behind them.
82
+ class ByteSemaphore {
83
+ capacity;
84
+ bytes = 0;
85
+ waiters = [];
86
+ constructor(capacity) {
87
+ this.capacity = capacity;
88
+ }
89
+ async acquire(requested) {
90
+ if (this.bytes === 0 || this.bytes + requested <= this.capacity) {
91
+ this.bytes += requested;
92
+ return;
93
+ }
94
+ await new Promise(resolve => {
95
+ this.waiters.push({ bytes: requested, resolve });
96
+ });
97
+ // `release` has already added `requested` to `this.bytes` on our behalf
98
+ // before resolving — see `release()` below.
99
+ }
100
+ release(returned) {
101
+ this.bytes -= returned;
102
+ if (this.bytes < 0)
103
+ this.bytes = 0; // defensive: never go negative
104
+ while (this.waiters.length > 0) {
105
+ const next = this.waiters[0];
106
+ if (this.bytes === 0 || this.bytes + next.bytes <= this.capacity) {
107
+ this.bytes += next.bytes;
108
+ this.waiters.shift();
109
+ next.resolve();
110
+ }
111
+ else {
112
+ break;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ class Indexer {
118
+ store;
119
+ constructor(store) {
120
+ this.store = store;
121
+ }
122
+ async indexDirectory(repoRoot, options = {}) {
123
+ const start = Date.now();
124
+ const absRoot = path_1.default.resolve(repoRoot);
125
+ const quiet = options.quiet && !options.verbose;
126
+ if (options.verbose) {
127
+ process.stdout.write(`\nDiscovering files in ${absRoot}...\n`);
128
+ }
129
+ const files = await (0, discovery_js_1.discoverFiles)(absRoot, {
130
+ includeVendor: options.includeVendor,
131
+ includeGenerated: options.includeGenerated,
132
+ mode: options.mode,
133
+ });
134
+ const total = files.length;
135
+ const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
136
+ const ioConcurrency = Math.max(1, options.ioConcurrency ?? DEFAULT_IO_CONCURRENCY);
137
+ const ioPrefetchBytes = Math.max(1, options.ioPrefetchBytes ?? DEFAULT_IO_PREFETCH_BYTES);
138
+ // Track every file_id we touch this run so we can prune ones left over
139
+ // from a previous run (e.g. files now hidden by a new ignore rule).
140
+ const touchedFileIds = new Set();
141
+ let indexed = 0;
142
+ let reusedFromCache = 0;
143
+ let skipped = 0;
144
+ let skippedTooLarge = 0;
145
+ let parseErrors = 0;
146
+ let workerWasmResets = 0;
147
+ // ── Pre-filter into a work queue ────────────────────────────────────────────
148
+ // Pure CPU work (string ops on the path). Cheap to do all at once so the
149
+ // async prefetcher's index space matches up cleanly with progress counters.
150
+ const work = [];
151
+ for (const file of files) {
152
+ const language = (0, index_js_1.detectLanguage)(file.absolutePath);
153
+ if (!language || shouldSkipFilename(file.relativePath)) {
154
+ skipped++;
155
+ continue;
156
+ }
157
+ work.push({ file, language });
158
+ }
159
+ // Existing pre-v8 DBs can have all source hashes cached but no
160
+ // service_calls rows yet. Force one full parse pass so Track-G evidence is
161
+ // backfilled, then mark completion in finishIndex().
162
+ const forceServiceCallBackfill = this.store.needsServiceCallBackfill();
163
+ // ── Batched transactions ──────────────────────────────────────────────────
164
+ // The Phase-1 design wrapped each file's inserts in its own SQLite
165
+ // transaction. That works but every commit fsyncs the WAL, which adds
166
+ // O(milliseconds) of overhead per file. For 40k+ file repos the commit
167
+ // overhead dominates the per-file budget. Batching N files per transaction
168
+ // amortizes the fsync cost N-fold.
169
+ //
170
+ // Trade-off: a fatal error mid-batch rolls back at most BATCH_SIZE files'
171
+ // worth of inserts (which we'd just re-do on the next run). We never lose
172
+ // user data — only re-do work — so a moderately large batch is safe.
173
+ //
174
+ // The hash-skip path participates in the batch too (its UPDATE indexed_at
175
+ // and DELETE-old-symbols statements were previously running as
176
+ // autocommitted singletons — now they share a transaction with the file's
177
+ // inserts).
178
+ const BATCH_SIZE = 200;
179
+ let batchOpen = false;
180
+ const openBatch = () => {
181
+ if (!batchOpen) {
182
+ this.store.begin();
183
+ batchOpen = true;
184
+ }
185
+ };
186
+ const closeBatch = () => {
187
+ if (batchOpen) {
188
+ this.store.commit();
189
+ batchOpen = false;
190
+ }
191
+ };
192
+ const rollbackBatch = () => {
193
+ if (batchOpen) {
194
+ try {
195
+ this.store.rollback();
196
+ }
197
+ catch { /* best effort */ }
198
+ batchOpen = false;
199
+ }
200
+ };
201
+ if (!options.verbose && !quiet) {
202
+ writeProgress(0, total, '');
203
+ }
204
+ // ── Parallel-parsing branch (worker pool) ───────────────────────────────────
205
+ //
206
+ // When enabled, each file's read + hash + parse runs in a worker_threads
207
+ // worker (its own WASM heap). The pool delivers results to the callback
208
+ // STRICTLY in input order, so symbol-id insertion order — and therefore
209
+ // every cross-run-stable scale-test invariant — is identical to the
210
+ // serial path. DB writes still run single-writer on the main thread.
211
+ //
212
+ // Result-kind contract (matches the serial branch's semantics exactly):
213
+ // parsed → upsertFileWithCache → touchedFileIds.add → insert all
214
+ // parse-error → upsertFileWithCache → touchedFileIds.add → no inserts
215
+ // cached → upsertFileWithCache → touchedFileIds.add → no inserts
216
+ // (worker confirmed hash === expectedHash; upsert sees
217
+ // the same hash and returns unchanged=true)
218
+ // too-large → counter only; file row NOT touched → pruned
219
+ // io-error → counter only; file row NOT touched → pruned
220
+ //
221
+ // The cached/parse-error upsert calls are CRITICAL: without them
222
+ // `touchedFileIds` would not contain those file ids and
223
+ // `pruneFilesNotIn(touchedFileIds)` below would delete every unchanged
224
+ // cached file from the DB.
225
+ // Auto-enabled for normal/large workspaces. Tiny workspaces stay serial by
226
+ // default to avoid worker startup/churn; force workers with `parallel: true`
227
+ // or `SEER_PARALLEL_PARSE=1`. Opt out with `parallel: false` or
228
+ // `SEER_PARALLEL_PARSE=0`.
229
+ const envParallel = typeof process !== 'undefined' && process.env != null
230
+ ? (process.env.SEER_PARALLEL_PARSE === '0' ? false
231
+ : process.env.SEER_PARALLEL_PARSE === '1' ? true
232
+ : undefined)
233
+ : undefined;
234
+ const parallelRequested = options.parallel ?? envParallel ?? true;
235
+ const parallelForced = options.parallel === true || envParallel === true;
236
+ const parallelEnabled = parallelRequested && (parallelForced || work.length >= PARALLEL_AUTO_MIN_FILES);
237
+ if (parallelEnabled && work.length > 0) {
238
+ // Snapshot known DB hashes so workers can skip parsing on cache hits.
239
+ const cacheMap = new Map();
240
+ for (const f of this.store.listFiles())
241
+ cacheMap.set(f.path, f.hash);
242
+ const poolItems = work.map(w => ({
243
+ abs: w.file.absolutePath,
244
+ lang: w.language,
245
+ expectedHash: forceServiceCallBackfill ? null : cacheMap.get(w.file.absolutePath) ?? null,
246
+ maxFileBytes,
247
+ }));
248
+ const pool = new workerpool_js_1.WorkerPool({ jobs: options.jobs });
249
+ try {
250
+ await pool.ready();
251
+ let processed = 0;
252
+ await pool.dispatch(poolItems, (seq, result) => {
253
+ processed++;
254
+ const w = work[seq];
255
+ const rel = w.file.relativePath;
256
+ // Counters-only branches (file row stays untouched → pruned).
257
+ if (result.kind === 'too-large') {
258
+ skippedTooLarge++;
259
+ if (options.verbose) {
260
+ process.stdout.write(` ⤬ ${rel} (${(result.size / 1024).toFixed(0)} KiB > ${(maxFileBytes / 1024).toFixed(0)} KiB cap)\n`);
261
+ }
262
+ else if (!quiet)
263
+ writeProgress(processed, total, rel);
264
+ if (processed % BATCH_SIZE === 0)
265
+ closeBatch();
266
+ return;
267
+ }
268
+ if (result.kind === 'io-error') {
269
+ skipped++;
270
+ if (options.verbose)
271
+ process.stdout.write(` ⚠ ${rel} (read error: ${result.error})\n`);
272
+ else if (!quiet)
273
+ writeProgress(processed, total, rel);
274
+ if (processed % BATCH_SIZE === 0)
275
+ closeBatch();
276
+ return;
277
+ }
278
+ // parsed / parse-error / cached all read the file successfully —
279
+ // we have hash + lines. Always upsert so touchedFileIds is updated.
280
+ const hash = result.hash;
281
+ const lines = result.lines;
282
+ openBatch();
283
+ const classification = (0, classify_js_1.classifyFile)(rel);
284
+ const upserted = forceServiceCallBackfill
285
+ ? { fileId: this.store.upsertFile(w.file.absolutePath, rel, w.language, hash, lines, classification), unchanged: false }
286
+ : this.store.upsertFileWithCache(w.file.absolutePath, rel, w.language, hash, lines, classification);
287
+ const { fileId, unchanged } = upserted;
288
+ touchedFileIds.add(fileId);
289
+ // Cache hit (worker's hash matched the DB's stored hash). Prior
290
+ // symbols/edges/imports/routes/configKeys stay as-is. Note: an
291
+ // explicit `cached` result always falls into this branch; a `parsed`
292
+ // result whose hash happens to match an in-flight DB update would
293
+ // also land here defensively (we never re-insert when unchanged).
294
+ if (unchanged) {
295
+ reusedFromCache++;
296
+ if (options.verbose)
297
+ process.stdout.write(` = ${rel} (cached)\n`);
298
+ else if (!quiet)
299
+ writeProgress(processed, total, rel);
300
+ if (processed % BATCH_SIZE === 0)
301
+ closeBatch();
302
+ return;
303
+ }
304
+ // Only `parsed` carries an extraction. `cached` lands in the
305
+ // unchanged-branch above; `parse-error` and any defensive fall-
306
+ // through here get treated as a parse error (file row exists,
307
+ // no symbols/edges emitted).
308
+ if (result.kind !== 'parsed') {
309
+ parseErrors++;
310
+ if (options.verbose)
311
+ process.stdout.write(` ⚠ ${rel} (parse error)\n`);
312
+ else if (!quiet)
313
+ writeProgress(processed, total, rel);
314
+ if (processed % BATCH_SIZE === 0)
315
+ closeBatch();
316
+ return;
317
+ }
318
+ // parsed: insert all symbols, edges, imports, routes, configKeys.
319
+ const extraction = result.extraction;
320
+ const symbolIdMap = new Map();
321
+ for (const def of extraction.definitions) {
322
+ const symId = this.store.insertSymbol(fileId, def);
323
+ const qname = def.qualifiedName ?? def.name;
324
+ if (!symbolIdMap.has(qname))
325
+ symbolIdMap.set(qname, symId);
326
+ }
327
+ for (const ref of extraction.references) {
328
+ const fromId = ref.callerName ? symbolIdMap.get(ref.callerName) : undefined;
329
+ if (fromId !== undefined) {
330
+ this.store.insertEdge(fromId, ref.calleeName, ref.kind, ref.line);
331
+ }
332
+ }
333
+ for (const mod of extraction.importedModules) {
334
+ this.store.insertFileImport(fileId, mod);
335
+ }
336
+ if (extraction.routes) {
337
+ for (const r of extraction.routes) {
338
+ this.store.insertRoute(fileId, r.method, r.path, r.framework, r.handlerName ?? null, r.line, {
339
+ protocol: r.protocol ?? 'http',
340
+ operation: r.operation ?? null,
341
+ topic: r.topic ?? null,
342
+ queue: r.queue ?? null,
343
+ exchange: r.exchange ?? null,
344
+ service: r.service ?? null,
345
+ broker: r.broker ?? null,
346
+ metadataJson: r.metadataJson ?? null,
347
+ });
348
+ }
349
+ }
350
+ if (extraction.configKeys) {
351
+ for (const c of extraction.configKeys) {
352
+ const enclosingId = c.callerName ? symbolIdMap.get(c.callerName) ?? null : null;
353
+ this.store.insertConfigKey(c.key, c.source, fileId, enclosingId, c.line);
354
+ }
355
+ }
356
+ if (extraction.serviceCalls) {
357
+ for (const sc of extraction.serviceCalls) {
358
+ const enclosingId = sc.callerName ? symbolIdMap.get(sc.callerName) ?? null : null;
359
+ // Only run HTTP-shaped normalization when the call is HTTP.
360
+ const norm = sc.protocol === 'http'
361
+ ? (0, serviceLinks_js_1.normalizeHttpTarget)(sc.rawTarget)
362
+ : { path: undefined, hostHint: undefined };
363
+ this.store.insertServiceCall({
364
+ fileId,
365
+ symbolId: enclosingId,
366
+ protocol: sc.protocol,
367
+ method: sc.method ?? null,
368
+ rawTarget: sc.rawTarget,
369
+ normalizedPath: sc.normalizedPath ?? norm.path ?? null,
370
+ hostHint: sc.hostHint ?? norm.hostHint ?? null,
371
+ envKey: sc.envKey ?? null,
372
+ framework: sc.framework,
373
+ line: sc.line,
374
+ confidence: sc.confidence,
375
+ operation: sc.operation ?? null,
376
+ topic: sc.topic ?? null,
377
+ queue: sc.queue ?? null,
378
+ exchange: sc.exchange ?? null,
379
+ service: sc.service ?? null,
380
+ broker: sc.broker ?? null,
381
+ metadataJson: sc.metadataJson ?? null,
382
+ });
383
+ }
384
+ }
385
+ if (processed % BATCH_SIZE === 0)
386
+ closeBatch();
387
+ indexed++;
388
+ if (options.verbose) {
389
+ process.stdout.write(` ✓ ${rel} (${extraction.definitions.length} symbols, ${extraction.references.length} refs)\n`);
390
+ }
391
+ else if (!quiet) {
392
+ writeProgress(processed, total, rel);
393
+ }
394
+ });
395
+ workerWasmResets = pool.wasmResetCount();
396
+ closeBatch();
397
+ }
398
+ catch (err) {
399
+ rollbackBatch();
400
+ await pool.terminate().catch(() => { });
401
+ throw err;
402
+ }
403
+ await pool.shutdown();
404
+ if (!options.verbose && !quiet)
405
+ process.stdout.write('\n');
406
+ // Skip the serial prefetcher block below.
407
+ return await this.finishIndex(absRoot, start, total, indexed, reusedFromCache, skipped, skippedTooLarge, parseErrors, touchedFileIds, { verbose: options.verbose, quiet: !!quiet, workerWasmResets });
408
+ }
409
+ // ── Bounded async prefetcher (serial branch) ────────────────────────────────
410
+ //
411
+ // Producer side: a fixed sliding window of up to `ioConcurrency` in-flight
412
+ // `prefetchOne()` calls, each one bounded by `byteSem` so cumulative
413
+ // buffered content never exceeds `ioPrefetchBytes`.
414
+ //
415
+ // Consumer side: the main loop awaits prefetched results IN ORDER (so
416
+ // batching/progress/determinism match the old sync loop exactly), parses
417
+ // serially (single shared WASM module — see parser/index.ts), and writes
418
+ // serially (single SQLite connection).
419
+ //
420
+ // The byte budget is released only AFTER parse completes, because parse
421
+ // reads from the in-memory string. That means while a slow file parses we
422
+ // hold its budget — which is intentional: backpressures the prefetcher
423
+ // exactly when the parser falls behind.
424
+ const byteSem = new ByteSemaphore(ioPrefetchBytes);
425
+ const prefetchOne = async (idx) => {
426
+ const item = work[idx];
427
+ const abs = item.file.absolutePath;
428
+ // Only stat when there's an actual size cap to enforce. Saves one
429
+ // syscall per file in the (default) `maxFileBytes === 0` mode.
430
+ if (maxFileBytes > 0) {
431
+ let size;
432
+ try {
433
+ size = (await fs_1.default.promises.stat(abs)).size;
434
+ }
435
+ catch {
436
+ return { kind: 'io-error', item };
437
+ }
438
+ if (size > maxFileBytes) {
439
+ return { kind: 'too-large', item, size };
440
+ }
441
+ await byteSem.acquire(Math.max(size, 1));
442
+ let content;
443
+ try {
444
+ content = await fs_1.default.promises.readFile(abs, 'utf8');
445
+ }
446
+ catch {
447
+ byteSem.release(Math.max(size, 1));
448
+ return { kind: 'io-error', item };
449
+ }
450
+ // Update the held budget if the on-disk size disagreed with the
451
+ // decoded string length (Buffer length, after UTF-8 → UTF-16). We
452
+ // re-anchor to the actual content length so future releases match.
453
+ const actual = Buffer.byteLength(content, 'utf8');
454
+ if (actual !== size) {
455
+ byteSem.release(Math.max(size, 1));
456
+ await byteSem.acquire(Math.max(actual, 1));
457
+ }
458
+ return { kind: 'ok', item, content, size: actual };
459
+ }
460
+ // No size cap → skip the stat entirely. We don't know the size up
461
+ // front, so reserve a conservative slot (1 byte), read, then re-acquire
462
+ // the true size. This keeps a single huge file from blocking us before
463
+ // we even know it's huge.
464
+ await byteSem.acquire(1);
465
+ let content;
466
+ try {
467
+ content = await fs_1.default.promises.readFile(abs, 'utf8');
468
+ }
469
+ catch {
470
+ byteSem.release(1);
471
+ return { kind: 'io-error', item };
472
+ }
473
+ const size = Buffer.byteLength(content, 'utf8');
474
+ // Re-anchor budget to actual size.
475
+ byteSem.release(1);
476
+ await byteSem.acquire(Math.max(size, 1));
477
+ return { kind: 'ok', item, content, size };
478
+ };
479
+ // Sliding window of in-flight prefetches, indexed by their position in `work`.
480
+ // We `slots.shift()` after awaiting so the array stays small (≤ ioConcurrency).
481
+ const slots = [];
482
+ let nextToLaunch = 0;
483
+ const launchUpTo = (window) => {
484
+ while (slots.length < window && nextToLaunch < work.length) {
485
+ slots.push(prefetchOne(nextToLaunch));
486
+ nextToLaunch++;
487
+ }
488
+ };
489
+ // Prime the pipeline.
490
+ launchUpTo(ioConcurrency);
491
+ let processed = 0;
492
+ try {
493
+ while (slots.length > 0) {
494
+ const prefetched = await slots.shift();
495
+ processed++;
496
+ // Whatever happens to this file, the moment we're done with the
497
+ // string we MUST release its byte budget so the next prefetch can
498
+ // start. We accumulate the released amount and release in `finally`
499
+ // at the end of each iteration.
500
+ const heldBytes = prefetched.kind === 'ok' ? Math.max(prefetched.size, 1) :
501
+ prefetched.kind === 'too-large' ? 0 : // never acquired
502
+ 0; // io-error already released
503
+ try {
504
+ if (prefetched.kind === 'too-large') {
505
+ skippedTooLarge++;
506
+ if (options.verbose) {
507
+ process.stdout.write(` ⤬ ${prefetched.item.file.relativePath} (${(prefetched.size / 1024).toFixed(0)} KiB > ${(maxFileBytes / 1024).toFixed(0)} KiB cap)\n`);
508
+ }
509
+ else if (!quiet) {
510
+ writeProgress(processed, total, prefetched.item.file.relativePath);
511
+ }
512
+ if (processed % BATCH_SIZE === 0)
513
+ closeBatch();
514
+ continue;
515
+ }
516
+ if (prefetched.kind === 'io-error') {
517
+ skipped++;
518
+ if (options.verbose) {
519
+ process.stdout.write(` ⚠ ${prefetched.item.file.relativePath} (read error)\n`);
520
+ }
521
+ else if (!quiet) {
522
+ writeProgress(processed, total, prefetched.item.file.relativePath);
523
+ }
524
+ if (processed % BATCH_SIZE === 0)
525
+ closeBatch();
526
+ continue;
527
+ }
528
+ const { item, content } = prefetched;
529
+ const { file, language } = item;
530
+ const hash = sha256(content);
531
+ const lines = content.split('\n').length;
532
+ openBatch();
533
+ const classification = (0, classify_js_1.classifyFile)(file.relativePath);
534
+ const upserted = forceServiceCallBackfill
535
+ ? { fileId: this.store.upsertFile(file.absolutePath, file.relativePath, language, hash, lines, classification), unchanged: false }
536
+ : this.store.upsertFileWithCache(file.absolutePath, file.relativePath, language, hash, lines, classification);
537
+ const { fileId, unchanged } = upserted;
538
+ touchedFileIds.add(fileId);
539
+ // Hash-based cache hit: same content as last run → keep symbols, edges,
540
+ // and file_imports as-is. Edge to_ids that point to symbols that have
541
+ // since been deleted got NULLed by the FK cascade, so resolveEdges()
542
+ // below will still re-link them.
543
+ if (unchanged) {
544
+ reusedFromCache++;
545
+ if (options.verbose) {
546
+ process.stdout.write(` = ${file.relativePath} (cached)\n`);
547
+ }
548
+ else if (!quiet) {
549
+ writeProgress(processed, total, file.relativePath);
550
+ }
551
+ if (processed % BATCH_SIZE === 0)
552
+ closeBatch();
553
+ continue;
554
+ }
555
+ const extraction = await (0, index_js_1.parseFile)(content, file.absolutePath, language);
556
+ if (!extraction) {
557
+ parseErrors++;
558
+ if (options.verbose) {
559
+ process.stdout.write(` ⚠ ${file.relativePath} (parse error)\n`);
560
+ }
561
+ else if (!quiet) {
562
+ writeProgress(processed, total, file.relativePath);
563
+ }
564
+ if (processed % BATCH_SIZE === 0)
565
+ closeBatch();
566
+ continue;
567
+ }
568
+ const symbolIdMap = new Map(); // qualifiedName → id
569
+ for (const def of extraction.definitions) {
570
+ const symId = this.store.insertSymbol(fileId, def);
571
+ const qname = def.qualifiedName ?? def.name;
572
+ if (!symbolIdMap.has(qname))
573
+ symbolIdMap.set(qname, symId);
574
+ }
575
+ for (const ref of extraction.references) {
576
+ const fromId = ref.callerName ? symbolIdMap.get(ref.callerName) : undefined;
577
+ if (fromId !== undefined) {
578
+ this.store.insertEdge(fromId, ref.calleeName, ref.kind, ref.line);
579
+ }
580
+ }
581
+ for (const mod of extraction.importedModules) {
582
+ this.store.insertFileImport(fileId, mod);
583
+ }
584
+ if (extraction.routes) {
585
+ for (const r of extraction.routes) {
586
+ this.store.insertRoute(fileId, r.method, r.path, r.framework, r.handlerName ?? null, r.line, {
587
+ protocol: r.protocol ?? 'http',
588
+ operation: r.operation ?? null,
589
+ topic: r.topic ?? null,
590
+ queue: r.queue ?? null,
591
+ exchange: r.exchange ?? null,
592
+ service: r.service ?? null,
593
+ broker: r.broker ?? null,
594
+ metadataJson: r.metadataJson ?? null,
595
+ });
596
+ }
597
+ }
598
+ if (extraction.configKeys) {
599
+ for (const c of extraction.configKeys) {
600
+ const enclosingId = c.callerName ? symbolIdMap.get(c.callerName) ?? null : null;
601
+ this.store.insertConfigKey(c.key, c.source, fileId, enclosingId, c.line);
602
+ }
603
+ }
604
+ if (extraction.serviceCalls) {
605
+ for (const sc of extraction.serviceCalls) {
606
+ const enclosingId = sc.callerName ? symbolIdMap.get(sc.callerName) ?? null : null;
607
+ // Only run HTTP-shaped normalization when the call is HTTP.
608
+ const norm = sc.protocol === 'http'
609
+ ? (0, serviceLinks_js_1.normalizeHttpTarget)(sc.rawTarget)
610
+ : { path: undefined, hostHint: undefined };
611
+ this.store.insertServiceCall({
612
+ fileId,
613
+ symbolId: enclosingId,
614
+ protocol: sc.protocol,
615
+ method: sc.method ?? null,
616
+ rawTarget: sc.rawTarget,
617
+ normalizedPath: sc.normalizedPath ?? norm.path ?? null,
618
+ hostHint: sc.hostHint ?? norm.hostHint ?? null,
619
+ envKey: sc.envKey ?? null,
620
+ framework: sc.framework,
621
+ line: sc.line,
622
+ confidence: sc.confidence,
623
+ operation: sc.operation ?? null,
624
+ topic: sc.topic ?? null,
625
+ queue: sc.queue ?? null,
626
+ exchange: sc.exchange ?? null,
627
+ service: sc.service ?? null,
628
+ broker: sc.broker ?? null,
629
+ metadataJson: sc.metadataJson ?? null,
630
+ });
631
+ }
632
+ }
633
+ if (processed % BATCH_SIZE === 0)
634
+ closeBatch();
635
+ indexed++;
636
+ if (options.verbose) {
637
+ const symCount = extraction.definitions.length;
638
+ const refCount = extraction.references.length;
639
+ process.stdout.write(` ✓ ${file.relativePath} (${symCount} symbols, ${refCount} refs)\n`);
640
+ }
641
+ else if (!quiet) {
642
+ writeProgress(processed, total, file.relativePath);
643
+ }
644
+ }
645
+ finally {
646
+ // Return this file's bytes to the prefetcher budget BEFORE launching
647
+ // the next slot, so the launch decision sees an up-to-date balance.
648
+ if (heldBytes > 0)
649
+ byteSem.release(heldBytes);
650
+ // Refill the sliding window now that one slot drained.
651
+ launchUpTo(ioConcurrency);
652
+ }
653
+ }
654
+ // Close the last partial batch before kicking off post-processing
655
+ // (resolveImports / resolveEdges / pruneFilesNotIn all start their own
656
+ // transactions and would crash if one is already open).
657
+ closeBatch();
658
+ }
659
+ catch (err) {
660
+ // Don't leave a transaction dangling — post-processing's BEGIN would
661
+ // throw and mask the original error.
662
+ rollbackBatch();
663
+ // Drain any in-flight prefetches so their byte budget is returned and
664
+ // open FDs / promises don't leak as unhandled rejections.
665
+ while (slots.length > 0) {
666
+ try {
667
+ const p = await slots.shift();
668
+ if (p.kind === 'ok')
669
+ byteSem.release(Math.max(p.size, 1));
670
+ }
671
+ catch { /* swallow */ }
672
+ }
673
+ throw err;
674
+ }
675
+ if (!options.verbose && !quiet)
676
+ process.stdout.write('\n');
677
+ return await this.finishIndex(absRoot, start, total, indexed, reusedFromCache, skipped, skippedTooLarge, parseErrors, touchedFileIds, { verbose: options.verbose, quiet: !!quiet });
678
+ }
679
+ /**
680
+ * Post-parse pipeline shared by the serial and parallel branches: prune
681
+ * stale files, resolve imports/edges/routes/config-keys, synthesize test
682
+ * edges, refresh external dependencies, lazily recompute PageRank, and
683
+ * assemble the `IndexResult`.
684
+ */
685
+ async finishIndex(absRoot, start, total, indexed, reusedFromCache, skipped, skippedTooLarge, parseErrors, touchedFileIds, opts) {
686
+ const { verbose, quiet } = opts;
687
+ // v9 Track-H: scan .proto files for gRPC service definitions BEFORE the
688
+ // stale-file prune and service-link resolver run. .proto files are not
689
+ // part of normal tree-sitter discovery, so they must be added to
690
+ // touchedFileIds here; otherwise cached re-indexes would prune and
691
+ // recreate proto rows every time.
692
+ try {
693
+ const protoScan = await (0, protoScanner_js_1.scanProtoFiles)(absRoot, this.store);
694
+ for (const fileId of protoScan.fileIds)
695
+ touchedFileIds.add(fileId);
696
+ }
697
+ catch (err) {
698
+ if (verbose)
699
+ process.stdout.write(` ⚠ proto scanner failed: ${err}\n`);
700
+ }
701
+ // Drop files that existed in a prior run but didn't show up this time
702
+ // (e.g. user added a new ignore rule, or files were removed from disk).
703
+ // FK cascades remove their symbols, edges, and file_imports too.
704
+ const prunedFiles = this.store.pruneFilesNotIn(touchedFileIds);
705
+ if (prunedFiles > 0 && !quiet) {
706
+ process.stdout.write(` Pruned ${prunedFiles.toLocaleString()} stale file(s) from prior run\n`);
707
+ }
708
+ // Post-processing passes
709
+ if (!quiet)
710
+ process.stdout.write(' Resolving imports...\n');
711
+ const resolvedImports = this.store.resolveImports();
712
+ if (!quiet)
713
+ process.stdout.write(' Resolving call edges...\n');
714
+ const resolution = this.store.resolveEdges();
715
+ // Track-C: link routes to handlers, config_keys to enclosing symbol,
716
+ // synthesize tests edges from test-file → non-test-file calls.
717
+ const routesResolved = this.store.resolveRouteHandlers();
718
+ const configKeysResolved = this.store.resolveConfigKeySymbols();
719
+ const testEdgesAdded = this.store.synthesizeTestEdges();
720
+ // v9 Track-H: scan k8s manifests + Docker Compose for service hostnames.
721
+ // Passed to the resolver as evidence — host_hint hits get a confidence
722
+ // boost and may be classified as `service_host` link matches.
723
+ let hostMap;
724
+ try {
725
+ hostMap = await (0, serviceHostScanner_js_1.scanServiceHosts)(absRoot);
726
+ }
727
+ catch (err) {
728
+ if (verbose)
729
+ process.stdout.write(` ⚠ service-host scanner failed: ${err}\n`);
730
+ }
731
+ // Track-G: deterministic service-link resolution. Runs every time, since
732
+ // any change in service_calls OR routes can shift link membership. The
733
+ // resolver itself wipes service_links before rebuilding so it's
734
+ // idempotent.
735
+ let serviceLinks = 0;
736
+ let serviceLinksByKind = {};
737
+ try {
738
+ const sr = (0, serviceLinks_js_1.resolveServiceLinks)(this.store, { hostMap });
739
+ serviceLinks = sr.linksInserted;
740
+ serviceLinksByKind = sr.byKind;
741
+ this.store.markServiceCallsBackfilled();
742
+ }
743
+ catch (err) {
744
+ if (verbose)
745
+ process.stdout.write(` ⚠ service-link resolution failed: ${err}\n`);
746
+ }
747
+ // External dependency extraction from manifests/lockfiles. This is cheap
748
+ // and idempotent — clear and re-insert every full pass so deletions are
749
+ // reflected. We pass absRoot so the extractor finds package.json /
750
+ // Cargo.toml / etc. at the repo root and walks down for monorepos.
751
+ try {
752
+ const { extractExternalDependencies } = await Promise.resolve().then(() => __importStar(require('./externaldeps.js')));
753
+ await extractExternalDependencies(absRoot, this.store);
754
+ }
755
+ catch (err) {
756
+ if (verbose) {
757
+ process.stdout.write(` ⚠ external dep extraction failed: ${err}\n`);
758
+ }
759
+ }
760
+ // ── Lazy PageRank ───────────────────────────────────────────────────────────
761
+ // PageRank values are a pure function of the resolved edge graph. If nothing
762
+ // in that graph changed this run, every previously-stored rank is still
763
+ // correct and we can skip the O(iterations × edges) recomputation.
764
+ //
765
+ // "Nothing changed" requires ALL of the following:
766
+ // - no file was newly indexed (no new symbols/edges/imports inserted)
767
+ // - no stale file was pruned (would have cascaded FK deletes,
768
+ // potentially NULLing inbound edge `to_id`s)
769
+ // - resolveEdges() promoted zero NULL `to_id`s to a real id
770
+ // - resolveImports() promoted zero NULL `resolved_file_id`s
771
+ //
772
+ // If any of those is nonzero, the symbol set OR the resolved-edge graph
773
+ // could have shifted, so we recompute. This is the same correctness
774
+ // contract that the scale-test's "top-symbol id stability" check enforces
775
+ // — drift there means the predicate below missed a case.
776
+ //
777
+ // Why this is safe even on first run: when the DB is fresh, `indexed > 0`
778
+ // (everything is new), so the predicate fires and PageRank is computed.
779
+ const graphChanged = indexed > 0 ||
780
+ prunedFiles > 0 ||
781
+ resolution.sameFile + resolution.imported + resolution.global > 0 ||
782
+ resolvedImports > 0;
783
+ let pagerankRecomputed = false;
784
+ if (graphChanged) {
785
+ if (!quiet)
786
+ process.stdout.write(' Computing PageRank...\n');
787
+ const symbolIds = this.store.getAllSymbolIds();
788
+ const edges = this.store.getAllEdges();
789
+ const ranks = (0, pagerank_js_1.computePageRank)(symbolIds, edges);
790
+ this.store.updatePageRanks(ranks);
791
+ pagerankRecomputed = true;
792
+ }
793
+ else if (!quiet) {
794
+ process.stdout.write(' Skipping PageRank (graph unchanged)\n');
795
+ }
796
+ // ── Lazy module clustering ──────────────────────────────────────────────
797
+ // Same skip predicate as PageRank: the cluster is a function of the file
798
+ // graph + symbol PageRank, both of which stay stable when nothing changed.
799
+ // Always build when modules table is empty so the first opt-in to v6 runs
800
+ // it once, even when the index itself was a no-op.
801
+ let modulesRecomputed = false;
802
+ if (graphChanged || !this.store.hasModulesData()) {
803
+ if (!quiet)
804
+ process.stdout.write(' Clustering modules...\n');
805
+ try {
806
+ (0, modules_js_1.buildModules)(this.store);
807
+ modulesRecomputed = true;
808
+ }
809
+ catch (err) {
810
+ if (verbose)
811
+ process.stdout.write(` ⚠ module clustering failed: ${err}\n`);
812
+ }
813
+ }
814
+ else if (!quiet) {
815
+ process.stdout.write(' Skipping module clustering (graph unchanged)\n');
816
+ }
817
+ // ── v10 boundary detection ──────────────────────────────────────────────
818
+ // Always run when graphChanged (new files / pruned files / new edges)
819
+ // OR when the boundaries table is empty (first opt-in after migrating
820
+ // an existing DB to v10).
821
+ let boundariesRecomputed = false;
822
+ try {
823
+ if (graphChanged || !this.store.hasBoundariesData()) {
824
+ if (!quiet)
825
+ process.stdout.write(' Detecting boundaries...\n');
826
+ const r = (0, boundaries_js_1.buildBoundaries)(absRoot, this.store);
827
+ this.store.replaceBoundaries(r.boundaries, r.edges);
828
+ boundariesRecomputed = true;
829
+ }
830
+ }
831
+ catch (err) {
832
+ if (verbose)
833
+ process.stdout.write(` ⚠ boundary detection failed: ${err}\n`);
834
+ }
835
+ void boundariesRecomputed;
836
+ // ── Lazy shape-hash pass (Track-F structural SimHash) ──────────────────
837
+ // Re-indexed files delete their old symbols (no shape_hash on the new
838
+ // rows yet) so graphChanged covers normal updates. We ALSO run when any
839
+ // eligible symbol is missing a hash even on a cached/no-op run — this is
840
+ // the case after a pre-v7 → v7 migration where every existing file is
841
+ // "cached" (content hash unchanged) but the new shape_hash column starts
842
+ // NULL on every row. Without this second predicate the backfill would
843
+ // never run and `seer_duplicates` would silently return nothing.
844
+ let shapeHashesAdded = 0;
845
+ const needsHashBackfill = this.store.hasMissingShapeHashes();
846
+ if (graphChanged || needsHashBackfill) {
847
+ if (!quiet) {
848
+ process.stdout.write(graphChanged
849
+ ? ' Computing shape hashes...\n'
850
+ : ' Backfilling shape hashes...\n');
851
+ }
852
+ try {
853
+ const r = (0, shapehash_js_1.buildShapeHashes)(this.store);
854
+ shapeHashesAdded = r.symbolsHashed;
855
+ }
856
+ catch (err) {
857
+ if (verbose)
858
+ process.stdout.write(` ⚠ shape-hash pass failed: ${err}\n`);
859
+ }
860
+ }
861
+ else if (!quiet) {
862
+ process.stdout.write(' Skipping shape hashes (graph unchanged, no backfill needed)\n');
863
+ }
864
+ // v10 — rename/move continuity heuristics. Runs whenever shape hashes
865
+ // were computed; opt-in mode (includeAllSymbols=true) is reserved for
866
+ // the explicit `seer continuity` CLI. The default pass only attaches
867
+ // candidates to symbols whose recorded history is shallow (< 1 commit).
868
+ if (graphChanged && this.store.hasV10()) {
869
+ try {
870
+ (0, continuity_js_1.buildContinuity)(this.store, { includeAllSymbols: true });
871
+ }
872
+ catch (err) {
873
+ if (verbose)
874
+ process.stdout.write(` ⚠ continuity pass failed: ${err}\n`);
875
+ }
876
+ }
877
+ const stats = this.store.getStats();
878
+ const elapsedMs = Date.now() - start;
879
+ return {
880
+ filesDiscovered: total,
881
+ filesIndexed: indexed,
882
+ filesReusedFromCache: reusedFromCache,
883
+ filesSkipped: skipped,
884
+ filesSkippedTooLarge: skippedTooLarge,
885
+ filesParseError: parseErrors,
886
+ wasmResets: (0, index_js_1.wasmResetCount)() + (opts.workerWasmResets ?? 0),
887
+ symbols: stats.symbols,
888
+ edges: stats.edges,
889
+ // stats.resolvedEdges is the running DB total; resolution.{sameFile,
890
+ // imported, global} below reports only the *delta* — what this run
891
+ // newly resolved (mostly nonzero on first run, near-zero on a cached
892
+ // re-run where everything was already resolved).
893
+ resolvedEdges: stats.resolvedEdges,
894
+ resolvedImports,
895
+ edgeResolution: {
896
+ sameFile: resolution.sameFile,
897
+ imported: resolution.imported,
898
+ global: resolution.global,
899
+ },
900
+ pagerankRecomputed,
901
+ routesResolved,
902
+ configKeysResolved,
903
+ testEdgesAdded,
904
+ externalDependencies: stats.externalDependencies,
905
+ modules: stats.modules,
906
+ modulesRecomputed,
907
+ shapeHashesAdded,
908
+ serviceLinks,
909
+ serviceLinksByKind,
910
+ elapsedMs,
911
+ };
912
+ }
913
+ }
914
+ exports.Indexer = Indexer;
915
+ // ── Utilities ──────────────────────────────────────────────────────────────────
916
+ function sha256(content) {
917
+ return crypto_1.default.createHash('sha256').update(content, 'utf8').digest('hex').slice(0, 16);
918
+ }
919
+ function writeProgress(current, total, label) {
920
+ if (!process.stdout.isTTY)
921
+ return;
922
+ const width = 28;
923
+ const pct = total > 0 ? current / total : 0;
924
+ const filled = Math.round(pct * width);
925
+ const bar = '█'.repeat(filled) + '░'.repeat(width - filled);
926
+ const pctStr = Math.round(pct * 100).toString().padStart(3);
927
+ const short = label.length > 35 ? '…' + label.slice(-34) : label.padEnd(35);
928
+ process.stdout.write(`\r [${bar}] ${pctStr}% (${current}/${total}) ${short}`);
929
+ }
930
+ //# sourceMappingURL=index.js.map