homegraph 1.4.1 → 1.5.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 (332) hide show
  1. package/README.md +48 -11
  2. package/dist/bin/homegraph.js +295 -72
  3. package/dist/bin/homegraph.js.map +1 -1
  4. package/dist/context/index.d.ts.map +1 -1
  5. package/dist/context/index.js +32 -11
  6. package/dist/context/index.js.map +1 -1
  7. package/dist/db/index.d.ts +143 -1
  8. package/dist/db/index.d.ts.map +1 -1
  9. package/dist/db/index.js +293 -16
  10. package/dist/db/index.js.map +1 -1
  11. package/dist/db/queries.d.ts +43 -4
  12. package/dist/db/queries.d.ts.map +1 -1
  13. package/dist/db/queries.js +138 -14
  14. package/dist/db/queries.js.map +1 -1
  15. package/dist/db/sqlite-adapter.d.ts +24 -17
  16. package/dist/db/sqlite-adapter.d.ts.map +1 -1
  17. package/dist/db/sqlite-adapter.js +292 -36
  18. package/dist/db/sqlite-adapter.js.map +1 -1
  19. package/dist/db/wal-valve.d.ts +15 -2
  20. package/dist/db/wal-valve.d.ts.map +1 -1
  21. package/dist/db/wal-valve.js +88 -6
  22. package/dist/db/wal-valve.js.map +1 -1
  23. package/dist/extraction/arkts-batch-worker.js +3 -2
  24. package/dist/extraction/arkts-batch-worker.js.map +1 -1
  25. package/dist/extraction/default-ignore.d.ts +39 -0
  26. package/dist/extraction/default-ignore.d.ts.map +1 -0
  27. package/dist/extraction/default-ignore.js +221 -0
  28. package/dist/extraction/default-ignore.js.map +1 -0
  29. package/dist/extraction/index.d.ts +9 -10
  30. package/dist/extraction/index.d.ts.map +1 -1
  31. package/dist/extraction/index.js +227 -323
  32. package/dist/extraction/index.js.map +1 -1
  33. package/dist/extraction/languages/arkts.d.ts +90 -6
  34. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  35. package/dist/extraction/languages/arkts.js +1579 -128
  36. package/dist/extraction/languages/arkts.js.map +1 -1
  37. package/dist/extraction/store-worker.d.ts +20 -0
  38. package/dist/extraction/store-worker.d.ts.map +1 -0
  39. package/dist/extraction/store-worker.js +102 -0
  40. package/dist/extraction/store-worker.js.map +1 -0
  41. package/dist/extraction/store-writer.d.ts +48 -0
  42. package/dist/extraction/store-writer.d.ts.map +1 -0
  43. package/dist/extraction/store-writer.js +167 -0
  44. package/dist/extraction/store-writer.js.map +1 -0
  45. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  46. package/dist/extraction/tree-sitter.js +47 -0
  47. package/dist/extraction/tree-sitter.js.map +1 -1
  48. package/dist/extraction/wasm-runtime-flags.d.ts +14 -4
  49. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  50. package/dist/extraction/wasm-runtime-flags.js +55 -7
  51. package/dist/extraction/wasm-runtime-flags.js.map +1 -1
  52. package/dist/index.d.ts +16 -5
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +135 -97
  55. package/dist/index.js.map +1 -1
  56. package/dist/mcp/query-worker.d.ts +2 -2
  57. package/dist/mcp/query-worker.js +2 -2
  58. package/dist/mcp/tools.d.ts.map +1 -1
  59. package/dist/mcp/tools.js +42 -26
  60. package/dist/mcp/tools.js.map +1 -1
  61. package/dist/resolution/callback-synthesizer.d.ts +52 -16
  62. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  63. package/dist/resolution/callback-synthesizer.js +234 -180
  64. package/dist/resolution/callback-synthesizer.js.map +1 -1
  65. package/dist/resolution/cooperative-yield.d.ts +10 -2
  66. package/dist/resolution/cooperative-yield.d.ts.map +1 -1
  67. package/dist/resolution/cooperative-yield.js +6 -4
  68. package/dist/resolution/cooperative-yield.js.map +1 -1
  69. package/dist/resolution/frameworks/go.d.ts.map +1 -1
  70. package/dist/resolution/frameworks/go.js +29 -6
  71. package/dist/resolution/frameworks/go.js.map +1 -1
  72. package/dist/resolution/import-resolver.d.ts +2 -3
  73. package/dist/resolution/import-resolver.d.ts.map +1 -1
  74. package/dist/resolution/import-resolver.js +166 -7
  75. package/dist/resolution/import-resolver.js.map +1 -1
  76. package/dist/resolution/index.d.ts +87 -2
  77. package/dist/resolution/index.d.ts.map +1 -1
  78. package/dist/resolution/index.js +739 -131
  79. package/dist/resolution/index.js.map +1 -1
  80. package/dist/resolution/memory-budget.d.ts +48 -0
  81. package/dist/resolution/memory-budget.d.ts.map +1 -0
  82. package/dist/resolution/memory-budget.js +162 -0
  83. package/dist/resolution/memory-budget.js.map +1 -0
  84. package/dist/resolution/name-matcher.d.ts +12 -1
  85. package/dist/resolution/name-matcher.d.ts.map +1 -1
  86. package/dist/resolution/name-matcher.js +439 -97
  87. package/dist/resolution/name-matcher.js.map +1 -1
  88. package/dist/resolution/resolver-pool.d.ts +106 -0
  89. package/dist/resolution/resolver-pool.d.ts.map +1 -0
  90. package/dist/resolution/resolver-pool.js +344 -0
  91. package/dist/resolution/resolver-pool.js.map +1 -0
  92. package/dist/resolution/resolver-worker.d.ts +17 -0
  93. package/dist/resolution/resolver-worker.d.ts.map +1 -0
  94. package/dist/resolution/resolver-worker.js +141 -0
  95. package/dist/resolution/resolver-worker.js.map +1 -0
  96. package/dist/resolution/types.d.ts +3 -0
  97. package/dist/resolution/types.d.ts.map +1 -1
  98. package/dist/spec/build/diff-parser.d.ts +55 -0
  99. package/dist/spec/build/diff-parser.d.ts.map +1 -0
  100. package/dist/spec/build/diff-parser.js +205 -0
  101. package/dist/spec/build/diff-parser.js.map +1 -0
  102. package/dist/spec/build/git-scanner.d.ts +93 -0
  103. package/dist/spec/build/git-scanner.d.ts.map +1 -0
  104. package/dist/spec/build/git-scanner.js +254 -0
  105. package/dist/spec/build/git-scanner.js.map +1 -0
  106. package/dist/spec/build/pipeline.d.ts +54 -0
  107. package/dist/spec/build/pipeline.d.ts.map +1 -0
  108. package/dist/spec/build/pipeline.js +170 -0
  109. package/dist/spec/build/pipeline.js.map +1 -0
  110. package/dist/spec/build/scan.d.ts +48 -0
  111. package/dist/spec/build/scan.d.ts.map +1 -0
  112. package/dist/spec/build/scan.js +89 -0
  113. package/dist/spec/build/scan.js.map +1 -0
  114. package/dist/spec/build/scope-resolver.d.ts +50 -0
  115. package/dist/spec/build/scope-resolver.d.ts.map +1 -0
  116. package/dist/spec/build/scope-resolver.js +105 -0
  117. package/dist/spec/build/scope-resolver.js.map +1 -0
  118. package/dist/spec/build/spec-extractor.d.ts +74 -0
  119. package/dist/spec/build/spec-extractor.d.ts.map +1 -0
  120. package/dist/spec/build/spec-extractor.js +344 -0
  121. package/dist/spec/build/spec-extractor.js.map +1 -0
  122. package/dist/spec/config.d.ts +35 -1
  123. package/dist/spec/config.d.ts.map +1 -1
  124. package/dist/spec/config.js +30 -5
  125. package/dist/spec/config.js.map +1 -1
  126. package/dist/spec/db/fts.d.ts.map +1 -1
  127. package/dist/spec/db/fts.js +19 -50
  128. package/dist/spec/db/fts.js.map +1 -1
  129. package/dist/spec/db/index.d.ts +5 -3
  130. package/dist/spec/db/index.d.ts.map +1 -1
  131. package/dist/spec/db/index.js +16 -1
  132. package/dist/spec/db/index.js.map +1 -1
  133. package/dist/spec/db/persist.d.ts +45 -0
  134. package/dist/spec/db/persist.d.ts.map +1 -0
  135. package/dist/spec/db/persist.js +66 -0
  136. package/dist/spec/db/persist.js.map +1 -0
  137. package/dist/spec/db/relations.d.ts +80 -1
  138. package/dist/spec/db/relations.d.ts.map +1 -1
  139. package/dist/spec/db/relations.js +175 -4
  140. package/dist/spec/db/relations.js.map +1 -1
  141. package/dist/spec/db/schema.d.ts +4 -0
  142. package/dist/spec/db/schema.d.ts.map +1 -1
  143. package/dist/spec/db/schema.js +4 -0
  144. package/dist/spec/db/schema.js.map +1 -1
  145. package/dist/spec/db/schema.sql +5 -1
  146. package/dist/spec/db/spec-node.d.ts +5 -0
  147. package/dist/spec/db/spec-node.d.ts.map +1 -1
  148. package/dist/spec/db/spec-node.js +11 -5
  149. package/dist/spec/db/spec-node.js.map +1 -1
  150. package/dist/spec/db/sql-utils.d.ts +11 -0
  151. package/dist/spec/db/sql-utils.d.ts.map +1 -0
  152. package/dist/spec/db/sql-utils.js +19 -0
  153. package/dist/spec/db/sql-utils.js.map +1 -0
  154. package/dist/spec/evolve/cluster-context.d.ts +30 -0
  155. package/dist/spec/evolve/cluster-context.d.ts.map +1 -0
  156. package/dist/spec/evolve/cluster-context.js +78 -0
  157. package/dist/spec/evolve/cluster-context.js.map +1 -0
  158. package/dist/spec/evolve/commit-spec-analyzer.d.ts +51 -0
  159. package/dist/spec/evolve/commit-spec-analyzer.d.ts.map +1 -0
  160. package/dist/spec/evolve/commit-spec-analyzer.js +88 -0
  161. package/dist/spec/evolve/commit-spec-analyzer.js.map +1 -0
  162. package/dist/spec/evolve/commit-spec-persister.d.ts +53 -0
  163. package/dist/spec/evolve/commit-spec-persister.d.ts.map +1 -0
  164. package/dist/spec/evolve/commit-spec-persister.js +93 -0
  165. package/dist/spec/evolve/commit-spec-persister.js.map +1 -0
  166. package/dist/spec/evolve/impact-locator.d.ts +23 -7
  167. package/dist/spec/evolve/impact-locator.d.ts.map +1 -1
  168. package/dist/spec/evolve/impact-locator.js +58 -14
  169. package/dist/spec/evolve/impact-locator.js.map +1 -1
  170. package/dist/spec/evolve/pipeline.d.ts +62 -25
  171. package/dist/spec/evolve/pipeline.d.ts.map +1 -1
  172. package/dist/spec/evolve/pipeline.js +436 -457
  173. package/dist/spec/evolve/pipeline.js.map +1 -1
  174. package/dist/spec/evolve/spec-rewriter.d.ts +13 -13
  175. package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
  176. package/dist/spec/evolve/spec-rewriter.js +45 -45
  177. package/dist/spec/evolve/spec-rewriter.js.map +1 -1
  178. package/dist/spec/git/commits.d.ts +85 -0
  179. package/dist/spec/git/commits.d.ts.map +1 -0
  180. package/dist/spec/git/commits.js +218 -0
  181. package/dist/spec/git/commits.js.map +1 -0
  182. package/dist/spec/git/exec.d.ts +13 -0
  183. package/dist/spec/git/exec.d.ts.map +1 -0
  184. package/dist/spec/git/exec.js +19 -0
  185. package/dist/spec/git/exec.js.map +1 -0
  186. package/dist/spec/git/index.d.ts +9 -0
  187. package/dist/spec/git/index.d.ts.map +1 -0
  188. package/dist/spec/git/index.js +22 -0
  189. package/dist/spec/git/index.js.map +1 -0
  190. package/dist/spec/git-utils.d.ts +8 -0
  191. package/dist/spec/git-utils.d.ts.map +1 -0
  192. package/dist/spec/git-utils.js +14 -0
  193. package/dist/spec/git-utils.js.map +1 -0
  194. package/dist/spec/graph/index.d.ts +8 -0
  195. package/dist/spec/graph/index.d.ts.map +1 -0
  196. package/dist/spec/graph/index.js +17 -0
  197. package/dist/spec/graph/index.js.map +1 -0
  198. package/dist/spec/graph/queries.d.ts +6 -35
  199. package/dist/spec/graph/queries.d.ts.map +1 -1
  200. package/dist/spec/graph/queries.js +71 -248
  201. package/dist/spec/graph/queries.js.map +1 -1
  202. package/dist/spec/llm/agent-client.d.ts +59 -0
  203. package/dist/spec/llm/agent-client.d.ts.map +1 -0
  204. package/dist/spec/llm/agent-client.js +213 -0
  205. package/dist/spec/llm/agent-client.js.map +1 -0
  206. package/dist/spec/llm/agents/claude-code.d.ts +32 -0
  207. package/dist/spec/llm/agents/claude-code.d.ts.map +1 -0
  208. package/dist/spec/llm/agents/claude-code.js +125 -0
  209. package/dist/spec/llm/agents/claude-code.js.map +1 -0
  210. package/dist/spec/llm/agents/codex.d.ts +32 -0
  211. package/dist/spec/llm/agents/codex.d.ts.map +1 -0
  212. package/dist/spec/llm/agents/codex.js +145 -0
  213. package/dist/spec/llm/agents/codex.js.map +1 -0
  214. package/dist/spec/llm/agents/detect-utils.d.ts +28 -0
  215. package/dist/spec/llm/agents/detect-utils.d.ts.map +1 -0
  216. package/dist/spec/llm/agents/detect-utils.js +91 -0
  217. package/dist/spec/llm/agents/detect-utils.js.map +1 -0
  218. package/dist/spec/llm/agents/deveco-code.d.ts +33 -0
  219. package/dist/spec/llm/agents/deveco-code.d.ts.map +1 -0
  220. package/dist/spec/llm/agents/deveco-code.js +165 -0
  221. package/dist/spec/llm/agents/deveco-code.js.map +1 -0
  222. package/dist/spec/llm/agents/index.d.ts +31 -0
  223. package/dist/spec/llm/agents/index.d.ts.map +1 -0
  224. package/dist/spec/llm/agents/index.js +65 -0
  225. package/dist/spec/llm/agents/index.js.map +1 -0
  226. package/dist/spec/llm/agents/types.d.ts +76 -0
  227. package/dist/spec/llm/agents/types.d.ts.map +1 -0
  228. package/dist/spec/llm/agents/types.js +15 -0
  229. package/dist/spec/llm/agents/types.js.map +1 -0
  230. package/dist/spec/llm/client.d.ts +31 -11
  231. package/dist/spec/llm/client.d.ts.map +1 -1
  232. package/dist/spec/llm/client.js +143 -81
  233. package/dist/spec/llm/client.js.map +1 -1
  234. package/dist/spec/llm/factory.d.ts +41 -0
  235. package/dist/spec/llm/factory.d.ts.map +1 -0
  236. package/dist/spec/llm/factory.js +80 -0
  237. package/dist/spec/llm/factory.js.map +1 -0
  238. package/dist/spec/llm/prompts.d.ts +24 -8
  239. package/dist/spec/llm/prompts.d.ts.map +1 -1
  240. package/dist/spec/llm/prompts.js +100 -50
  241. package/dist/spec/llm/prompts.js.map +1 -1
  242. package/dist/spec/llm/retry.d.ts +53 -0
  243. package/dist/spec/llm/retry.d.ts.map +1 -0
  244. package/dist/spec/llm/retry.js +153 -0
  245. package/dist/spec/llm/retry.js.map +1 -0
  246. package/dist/spec/mine/clusterer.d.ts +63 -0
  247. package/dist/spec/mine/clusterer.d.ts.map +1 -0
  248. package/dist/spec/mine/clusterer.js +904 -0
  249. package/dist/spec/mine/clusterer.js.map +1 -0
  250. package/dist/spec/mine/clustering/features.d.ts +38 -0
  251. package/dist/spec/mine/clustering/features.d.ts.map +1 -0
  252. package/dist/spec/mine/clustering/features.js +108 -0
  253. package/dist/spec/mine/clustering/features.js.map +1 -0
  254. package/dist/spec/mine/clustering/index.d.ts +62 -0
  255. package/dist/spec/mine/clustering/index.d.ts.map +1 -0
  256. package/dist/spec/mine/clustering/index.js +259 -0
  257. package/dist/spec/mine/clustering/index.js.map +1 -0
  258. package/dist/spec/mine/clustering/leiden.d.ts +61 -0
  259. package/dist/spec/mine/clustering/leiden.d.ts.map +1 -0
  260. package/dist/spec/mine/clustering/leiden.js +489 -0
  261. package/dist/spec/mine/clustering/leiden.js.map +1 -0
  262. package/dist/spec/mine/clustering/text-similarity.d.ts +22 -0
  263. package/dist/spec/mine/clustering/text-similarity.d.ts.map +1 -0
  264. package/dist/spec/mine/clustering/text-similarity.js +98 -0
  265. package/dist/spec/mine/clustering/text-similarity.js.map +1 -0
  266. package/dist/spec/mine/generator.d.ts +54 -0
  267. package/dist/spec/mine/generator.d.ts.map +1 -0
  268. package/dist/spec/mine/generator.js +267 -0
  269. package/dist/spec/mine/generator.js.map +1 -0
  270. package/dist/spec/mine/persist.d.ts +37 -0
  271. package/dist/spec/mine/persist.d.ts.map +1 -0
  272. package/dist/spec/mine/persist.js +170 -0
  273. package/dist/spec/mine/persist.js.map +1 -0
  274. package/dist/spec/mine/pipeline.d.ts +41 -0
  275. package/dist/spec/mine/pipeline.d.ts.map +1 -0
  276. package/dist/spec/mine/pipeline.js +236 -0
  277. package/dist/spec/mine/pipeline.js.map +1 -0
  278. package/dist/spec/mine/progress-handler.d.ts +22 -0
  279. package/dist/spec/mine/progress-handler.d.ts.map +1 -0
  280. package/dist/spec/mine/progress-handler.js +108 -0
  281. package/dist/spec/mine/progress-handler.js.map +1 -0
  282. package/dist/spec/mine/progress.d.ts +23 -0
  283. package/dist/spec/mine/progress.d.ts.map +1 -0
  284. package/dist/spec/mine/progress.js +12 -0
  285. package/dist/spec/mine/progress.js.map +1 -0
  286. package/dist/spec/mine/scanner.d.ts +59 -0
  287. package/dist/spec/mine/scanner.d.ts.map +1 -0
  288. package/dist/spec/mine/scanner.js +383 -0
  289. package/dist/spec/mine/scanner.js.map +1 -0
  290. package/dist/spec/types.d.ts +32 -0
  291. package/dist/spec/types.d.ts.map +1 -1
  292. package/dist/spec/ui/index.d.ts +9 -0
  293. package/dist/spec/ui/index.d.ts.map +1 -0
  294. package/dist/spec/ui/index.js +16 -0
  295. package/dist/spec/ui/index.js.map +1 -0
  296. package/dist/spec/ui/progress-handler.d.ts +33 -0
  297. package/dist/spec/ui/progress-handler.d.ts.map +1 -0
  298. package/dist/spec/ui/progress-handler.js +132 -0
  299. package/dist/spec/ui/progress-handler.js.map +1 -0
  300. package/dist/spec/ui/progress.d.ts +24 -0
  301. package/dist/spec/ui/progress.d.ts.map +1 -0
  302. package/dist/spec/ui/progress.js +13 -0
  303. package/dist/spec/ui/progress.js.map +1 -0
  304. package/dist/spec/utils/fs.d.ts +45 -0
  305. package/dist/spec/utils/fs.d.ts.map +1 -0
  306. package/dist/spec/utils/fs.js +163 -0
  307. package/dist/spec/utils/fs.js.map +1 -0
  308. package/dist/spec/utils/index.d.ts +12 -0
  309. package/dist/spec/utils/index.d.ts.map +1 -0
  310. package/dist/spec/utils/index.js +26 -0
  311. package/dist/spec/utils/index.js.map +1 -0
  312. package/dist/spec/utils/meta.d.ts +32 -0
  313. package/dist/spec/utils/meta.d.ts.map +1 -0
  314. package/dist/spec/utils/meta.js +122 -0
  315. package/dist/spec/utils/meta.js.map +1 -0
  316. package/dist/spec/utils/truncate.d.ts +86 -0
  317. package/dist/spec/utils/truncate.d.ts.map +1 -0
  318. package/dist/spec/utils/truncate.js +172 -0
  319. package/dist/spec/utils/truncate.js.map +1 -0
  320. package/dist/spec/utils.d.ts +4 -16
  321. package/dist/spec/utils.d.ts.map +1 -1
  322. package/dist/spec/utils.js +6 -58
  323. package/dist/spec/utils.js.map +1 -1
  324. package/dist/sync/watcher.d.ts +25 -1
  325. package/dist/sync/watcher.d.ts.map +1 -1
  326. package/dist/sync/watcher.js +76 -3
  327. package/dist/sync/watcher.js.map +1 -1
  328. package/dist/telemetry/index.js +3 -3
  329. package/dist/telemetry/index.js.map +1 -1
  330. package/dist/types.d.ts +3 -0
  331. package/dist/types.d.ts.map +1 -1
  332. package/package.json +7 -3
@@ -1,23 +1,59 @@
1
+ /**
2
+ * Callback / observer edge synthesis — Phase 1 + 2.
3
+ *
4
+ * Closes dynamic-dispatch holes where a dispatcher invokes callbacks registered
5
+ * elsewhere. Two channel shapes:
6
+ *
7
+ * (1) Field-backed observer (Phase 1):
8
+ * onUpdate(cb) { this.callbacks.add(cb); } // registrar
9
+ * triggerUpdate() { for (cb of this.callbacks) cb(); } // dispatcher
10
+ * scene.onUpdate(this.triggerRender) // registration
11
+ * → synthesize triggerUpdate → triggerRender
12
+ *
13
+ * (2) String-keyed EventEmitter (Phase 2):
14
+ * this.on('mount', function onmount(){...}) // registration
15
+ * fn.emit('mount', this) // dispatch
16
+ * → synthesize (method containing emit('mount')) → onmount
17
+ *
18
+ * Whole-graph pass after base resolution. High-precision/low-recall by design:
19
+ * named callbacks only; field channels paired by file+field; EventEmitter
20
+ * channels capped by event fan-out (generic names like 'error' skipped — they
21
+ * need receiver-type matching, deferred to Phase 3). All synthesized edges are
22
+ * tagged `provenance:'heuristic'`. See docs/design/callback-edge-synthesis.md.
23
+ */
24
+ import type { Edge } from '../types';
1
25
  import type { QueryBuilder } from '../db/queries';
2
26
  import type { ResolutionContext } from './types';
27
+ import { type MaybeYield } from './cooperative-yield';
28
+ /** `has(...)` shape passed to pass gates — true when the project contains any of the languages. */
29
+ type HasLang = (...ls: string[]) => boolean;
3
30
  /**
4
- * Synthesize dispatcher→callback edges (field observers + EventEmitters +
5
- * React re-render + JSX children + Vue templates + SvelteKit load + RN event
6
- * channel + Fabric native-impl + MyBatis Java↔XML + Gin middleware chain +
7
- * Redux-thunk dispatch chain + object-literal registry dispatch + RTK Query
8
- * generated-hook → endpoint + Pinia useStore().action() + Vuex string dispatch +
9
- * Celery task .delay()/.apply_async() → task body + Spring publishEvent → @EventListener +
10
- * MediatR Send/Publish → IRequestHandler/INotificationHandler +
11
- * Sidekiq Worker.perform_async → #perform + Laravel event(new X) → listener handle).
12
- * Returns the count added. Never throws into indexing — callers wrap in try/catch.
31
+ * One independent synthesis pass. Every pass scans the COMMITTED graph (plus
32
+ * source via ctx) and returns an edge list; nothing it produces is persisted
33
+ * until the ordered merge in synthesizeCallbackEdges — which is what makes
34
+ * execution order free and the passes safe to fan out across the resolver
35
+ * pool's read-only workers. `gate` short-circuits a pass whose language never
36
+ * appears in the project (its result is provably empty — see #1212).
13
37
  */
38
+ export interface SynthPassDef {
39
+ name: string;
40
+ gate: (has: HasLang) => boolean;
41
+ run: (queries: QueryBuilder, ctx: ResolutionContext, yieldToLoop: MaybeYield, subProgress?: (fraction: number) => void) => Promise<Edge[]>;
42
+ }
14
43
  /**
15
- * Number of progress steps synthesizeCallbackEdges reports: one per `__mark()`
16
- * call (every synthesis pass, plus the dedupe-merge and edge-insert steps).
17
- * Cosmetic only — drift just makes the progress bar end early or jump — and a
18
- * test pins it to the actual `__mark(` call count so adding a pass without
19
- * bumping this fails loudly instead of silently skewing the bar.
44
+ * The independent passes, in MERGE ORDER — the first-seen dedup in
45
+ * synthesizeCallbackEdges follows this array, so reordering entries changes
46
+ * which duplicate edge wins. The two Go pre-passes (cross-file method
47
+ * `contains`, implicit `implements`) are NOT here: they persist before these
48
+ * run because interfaceOverrideEdges reads their edges from the DB.
20
49
  */
21
- export declare const SYNTH_PROGRESS_STEPS = 40;
22
- export declare function synthesizeCallbackEdges(queries: QueryBuilder, ctx: ResolutionContext, onProgress?: (done: number, total: number) => void): Promise<number>;
50
+ export declare const SYNTH_PASSES: SynthPassDef[];
51
+ export declare const SYNTH_PROGRESS_STEPS: number;
52
+ export declare function synthesizeCallbackEdges(queries: QueryBuilder, ctx: ResolutionContext, onProgress?: (done: number, total: number) => void, pool?: {
53
+ runSynthPass(name: string): Promise<{
54
+ edges: Edge[];
55
+ ms: number;
56
+ }>;
57
+ } | null, backpressure?: () => Promise<void> | null): Promise<number>;
58
+ export {};
23
59
  //# sourceMappingURL=callback-synthesizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"callback-synthesizer.d.ts","sourceRoot":"","sources":["../../src/resolution/callback-synthesizer.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAi7GjD;;;;;;;;;;GAUG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,iBAAiB,EACtB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACjD,OAAO,CAAC,MAAM,CAAC,CAkLjB"}
1
+ {"version":3,"file":"callback-synthesizer.d.ts","sourceRoot":"","sources":["../../src/resolution/callback-synthesizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAkB,MAAM,UAAU,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AASjD,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AA64GrE,mGAAmG;AACnG,KAAK,OAAO,GAAG,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;AAE5C;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IAChC,GAAG,EAAE,CACH,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,iBAAiB,EACtB,WAAW,EAAE,UAAU,EACvB,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,KACrC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;CACtB;AA6FD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,YAAY,EA6DtC,CAAC;AAIF,eAAO,MAAM,oBAAoB,QAA0C,CAAC;AAC5E,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,iBAAiB,EACtB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,EAIlD,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,IAAI,EAIpF,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GACxC,OAAO,CAAC,MAAM,CAAC,CAmLjB"}
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SYNTH_PROGRESS_STEPS = void 0;
3
+ exports.SYNTH_PROGRESS_STEPS = exports.SYNTH_PASSES = void 0;
4
4
  exports.synthesizeCallbackEdges = synthesizeCallbackEdges;
5
5
  const generated_detection_1 = require("../extraction/generated-detection");
6
6
  const strip_comments_1 = require("./strip-comments");
7
7
  const c_fnptr_synthesizer_1 = require("./c-fnptr-synthesizer");
8
8
  const goframe_synthesizer_1 = require("./goframe-synthesizer");
9
- const cooperative_yield_1 = require("./cooperative-yield");
10
9
  const arkts_entry_1 = require("./frameworks/arkts-entry");
10
+ const cooperative_yield_1 = require("./cooperative-yield");
11
11
  const REGISTRAR_NAME = /^(on[A-Z]\w*|subscribe|addListener|addEventListener|register|watch|listen|addCallback)$/;
12
12
  const DISPATCHER_NAME = /(emit|trigger|notify|dispatch|fire|publish|flush)/i;
13
13
  const MAX_CALLBACKS_PER_CHANNEL = 40;
@@ -413,9 +413,28 @@ async function reactRenderEdges(queries, ctx, onYield) {
413
413
  let scanned255 = 0;
414
414
  const edges = [];
415
415
  const seen = new Set();
416
+ // A class can only emit here if it CONTAINS a method named `render` — so one
417
+ // indexed name lookup bounds the candidate set up front, and the class scan
418
+ // below skips everything else before its per-class edge/node queries. On a
419
+ // repo with few/no render methods (any non-React codebase) this collapses
420
+ // the pass from every-class fan-out to ~zero DB work, with identical output:
421
+ // the skipped classes fail the same `render` check today, just after paying
422
+ // for their children. (Not a language gate: `render` + `this.setState(` in
423
+ // Java — e.g. Litho — legitimately matches today and still does.)
424
+ const renderOwners = new Set();
425
+ for (const n of ctx.getNodesByName('render')) {
426
+ if (n.kind !== 'method')
427
+ continue;
428
+ for (const e of queries.getIncomingEdges(n.id, ['contains']))
429
+ renderOwners.add(e.source);
430
+ }
431
+ if (renderOwners.size === 0)
432
+ return edges;
416
433
  for (const cls of queries.iterateNodesByKind('class')) {
417
434
  if ((++scanned255 & 63) === 0)
418
435
  await onYield();
436
+ if (!renderOwners.has(cls.id))
437
+ continue;
419
438
  const children = queries.getOutgoingEdges(cls.id, ['contains'])
420
439
  .map((e) => queries.getNodeById(e.target))
421
440
  .filter((n) => !!n && n.kind === 'method');
@@ -1073,10 +1092,21 @@ async function interfaceOverrideEdges(queries, onYield) {
1073
1092
  let scanned255 = 0;
1074
1093
  const edges = [];
1075
1094
  const seen = new Set();
1076
- const methodsOf = (classId) => queries
1077
- .getOutgoingEdges(classId, ['contains'])
1078
- .map((e) => queries.getNodeById(e.target))
1079
- .filter((n) => !!n && n.kind === 'method');
1095
+ // Memoized: a popular base interface's method list is otherwise re-fetched
1096
+ // once per implementer (dubbo-style hub interfaces have hundreds), and the
1097
+ // memo only ever serves reads. Same rows, same order — byte-identical.
1098
+ const methodsMemo = new Map();
1099
+ const methodsOf = (classId) => {
1100
+ const hit = methodsMemo.get(classId);
1101
+ if (hit)
1102
+ return hit;
1103
+ const methods = queries
1104
+ .getOutgoingEdges(classId, ['contains'])
1105
+ .map((e) => queries.getNodeById(e.target))
1106
+ .filter((n) => !!n && n.kind === 'method');
1107
+ methodsMemo.set(classId, methods);
1108
+ return methods;
1109
+ };
1080
1110
  // Concrete-side kinds vary by language: `class` covers Java / Kotlin /
1081
1111
  // C# / TS / Swift-classes / Scala-classes; `struct` covers Swift value
1082
1112
  // types that conform to protocols. Iterate both.
@@ -1085,10 +1115,16 @@ async function interfaceOverrideEdges(queries, onYield) {
1085
1115
  for (const cls of queries.iterateNodesByKind(kind)) {
1086
1116
  if ((++scanned255 & 63) === 0)
1087
1117
  await onYield();
1118
+ // A class can only emit here if it HAS a supertype edge — check that
1119
+ // (one edge query) before materializing its methods: most classes in a
1120
+ // typical graph extend/implement nothing and skip in one hop.
1121
+ const sups = queries.getOutgoingEdges(cls.id, ['implements', 'extends']);
1122
+ if (sups.length === 0)
1123
+ continue;
1088
1124
  const implMethods = methodsOf(cls.id).filter((n) => IFACE_OVERRIDE_LANGS.has(n.language));
1089
1125
  if (implMethods.length === 0)
1090
1126
  continue;
1091
- for (const sup of queries.getOutgoingEdges(cls.id, ['implements', 'extends'])) {
1127
+ for (const sup of sups) {
1092
1128
  const base = queries.getNodeById(sup.target);
1093
1129
  if (!base || !IFACE_OVERRIDE_LANGS.has(base.language) || base.id === cls.id)
1094
1130
  continue;
@@ -1878,6 +1914,20 @@ async function mybatisJavaXmlEdges(queries, onYield) {
1878
1914
  let scanned255 = 0;
1879
1915
  const edges = [];
1880
1916
  const seen = new Set();
1917
+ // Collect the XML side FIRST (mapper `<select id=…>` statements extracted as
1918
+ // xml-language method nodes): if the project has none — every Java+XML repo
1919
+ // that doesn't use MyBatis — return before paying the full java-method
1920
+ // stream below. Same rowid stream order as matching inline, so the edge
1921
+ // output is byte-identical when mappers do exist.
1922
+ const xmlMethods = [];
1923
+ for (const m of queries.iterateNodesByKind('method')) {
1924
+ if ((++scanned255 & 63) === 0)
1925
+ await onYield();
1926
+ if (m.language === 'xml')
1927
+ xmlMethods.push(m);
1928
+ }
1929
+ if (xmlMethods.length === 0)
1930
+ return edges;
1881
1931
  // Index Java methods by `<ClassName>::<methodName>` for O(1) lookup.
1882
1932
  const javaIndex = new Map();
1883
1933
  for (const m of queries.iterateNodesByKind('method')) {
@@ -1897,11 +1947,9 @@ async function mybatisJavaXmlEdges(queries, onYield) {
1897
1947
  else
1898
1948
  javaIndex.set(key, [m]);
1899
1949
  }
1900
- for (const xml of queries.iterateNodesByKind('method')) {
1950
+ for (const xml of xmlMethods) {
1901
1951
  if ((++scanned255 & 63) === 0)
1902
1952
  await onYield();
1903
- if (xml.language !== 'xml')
1904
- continue;
1905
1953
  // Qualified name: `<namespace>::<id>`. Extract the simple class name.
1906
1954
  const colonIdx = xml.qualifiedName.lastIndexOf('::');
1907
1955
  if (colonIdx < 0)
@@ -3706,6 +3754,26 @@ async function laravelEventEdges(ctx, onYield) {
3706
3754
  }
3707
3755
  return edges;
3708
3756
  }
3757
+ /**
3758
+ * Synthesize dispatcher→callback edges (field observers + EventEmitters +
3759
+ * React re-render + JSX children + Vue templates + SvelteKit load + RN event
3760
+ * channel + Fabric native-impl + MyBatis Java↔XML + Gin middleware chain +
3761
+ * Redux-thunk dispatch chain + object-literal registry dispatch + RTK Query
3762
+ * generated-hook → endpoint + Pinia useStore().action() + Vuex string dispatch +
3763
+ * Celery task .delay()/.apply_async() → task body + Spring publishEvent → @EventListener +
3764
+ * MediatR Send/Publish → IRequestHandler/INotificationHandler +
3765
+ * Sidekiq Worker.perform_async → #perform + Laravel event(new X) → listener handle).
3766
+ * Returns the count added. Never throws into indexing — callers wrap in try/catch.
3767
+ */
3768
+ /**
3769
+ * Number of progress steps synthesizeCallbackEdges reports: one per `__mark()`
3770
+ * call (every synthesis pass, plus the dedupe-merge and edge-insert steps).
3771
+ * Cosmetic only — drift just makes the progress bar end early or jump — and a
3772
+ * test pins it to the actual step count (registry passes + the fixed
3773
+ * pre/post marks) so adding a pass without bumping this fails loudly instead
3774
+ * of silently skewing the bar.
3775
+ */
3776
+ const JS_FAMILY = ['typescript', 'javascript', 'tsx', 'jsx'];
3709
3777
  /** ArkTS startup + UI lifecycle: loadContent → first screen, Ability chain, aboutToAppear → build. */
3710
3778
  function arktsEntryEdges(queries, ctx) {
3711
3779
  const edges = [];
@@ -3791,26 +3859,88 @@ function arktsEntryEdges(queries, ctx) {
3791
3859
  }
3792
3860
  return edges;
3793
3861
  }
3862
+ const ALWAYS = () => true;
3794
3863
  /**
3795
- * Synthesize dispatcher→callback edges (field observers + EventEmitters +
3796
- * React re-render + JSX children + Vue templates + SvelteKit load + RN event
3797
- * channel + Fabric native-impl + MyBatis Java↔XML + Gin middleware chain +
3798
- * Redux-thunk dispatch chain + object-literal registry dispatch + RTK Query
3799
- * generated-hook → endpoint + Pinia useStore().action() + Vuex string dispatch +
3800
- * Celery task .delay()/.apply_async() → task body + Spring publishEvent → @EventListener +
3801
- * MediatR Send/Publish → IRequestHandler/INotificationHandler +
3802
- * Sidekiq Worker.perform_async → #perform + Laravel event(new X) → listener handle).
3803
- * Returns the count added. Never throws into indexing — callers wrap in try/catch.
3804
- */
3805
- /**
3806
- * Number of progress steps synthesizeCallbackEdges reports: one per `__mark()`
3807
- * call (every synthesis pass, plus the dedupe-merge and edge-insert steps).
3808
- * Cosmetic only — drift just makes the progress bar end early or jump — and a
3809
- * test pins it to the actual `__mark(` call count so adding a pass without
3810
- * bumping this fails loudly instead of silently skewing the bar.
3864
+ * The independent passes, in MERGE ORDER — the first-seen dedup in
3865
+ * synthesizeCallbackEdges follows this array, so reordering entries changes
3866
+ * which duplicate edge wins. The two Go pre-passes (cross-file method
3867
+ * `contains`, implicit `implements`) are NOT here: they persist before these
3868
+ * run because interfaceOverrideEdges reads their edges from the DB.
3811
3869
  */
3812
- exports.SYNTH_PROGRESS_STEPS = 40;
3813
- async function synthesizeCallbackEdges(queries, ctx, onProgress) {
3870
+ exports.SYNTH_PASSES = [
3871
+ { name: 'fieldEdges', gate: ALWAYS, run: (q, c, y) => fieldChannelEdges(q, c, y) },
3872
+ { name: 'closureCollEdges', gate: ALWAYS, run: (q, c, y) => closureCollectionEdges(q, c, y) },
3873
+ { name: 'emitterEdges', gate: ALWAYS, run: (_q, c, y) => eventEmitterEdges(c, y) },
3874
+ { name: 'renderEdges', gate: ALWAYS, run: (q, c, y) => reactRenderEdges(q, c, y) },
3875
+ { name: 'jsxEdges', gate: ALWAYS, run: (_q, c, y) => reactJsxChildEdges(c, y) },
3876
+ { name: 'vueEdges', gate: (has) => has('vue'), run: (_q, c, y) => vueTemplateEdges(c, y) },
3877
+ { name: 'svelteKitEdges', gate: (has) => has('svelte'), run: (_q, c, y) => svelteKitLoadEdges(c, y) },
3878
+ { name: 'pascalEdges', gate: ALWAYS, run: (_q, c, y) => pascalFormEdges(c, y) },
3879
+ { name: 'flutterEdges', gate: (has) => has('dart'), run: (q, c, y) => flutterBuildEdges(q, c, y) },
3880
+ { name: 'arkuiStateEdges', gate: (has) => has('arkts'), run: (q, c, y) => arkuiStateBuildEdges(q, c, y) },
3881
+ { name: 'arkuiEmitter', gate: (has) => has('arkts'), run: (_q, c, y) => arkuiEmitterEdges(c, y) },
3882
+ { name: 'arkuiRoutes', gate: (has) => has('arkts'), run: (_q, c, y) => arkuiRouterEdges(c, y) },
3883
+ { name: 'arktsStartupEdges', gate: (has) => has('arkts'), run: async (q, c) => arktsEntryEdges(q, c) },
3884
+ { name: 'cppEdges', gate: (has) => has('cpp'), run: (q, _c, y) => cppOverrideEdges(q, y) },
3885
+ {
3886
+ name: 'ifaceEdges',
3887
+ gate: (has) => has('java', 'kotlin', 'csharp', 'swift', 'scala', 'go', 'rust', 'arkts', ...JS_FAMILY),
3888
+ run: (q, _c, y) => interfaceOverrideEdges(q, y),
3889
+ },
3890
+ { name: 'kotlinExpectActual', gate: (has) => has('kotlin'), run: (q, _c, y) => kotlinExpectActualEdges(q, y) },
3891
+ { name: 'goGrpcEdges', gate: (has) => has('go'), run: (q, _c, y) => goGrpcStubImplEdges(q, y) },
3892
+ { name: 'rnEventEdgesList', gate: (has) => has(...JS_FAMILY), run: (_q, c, y) => rnEventEdges(c, y) },
3893
+ { name: 'fabricNativeEdges', gate: ALWAYS, run: (_q, c, y) => fabricNativeImplEdges(c, y) },
3894
+ // Expo module nodes (`expo-module:` ids) are emitted only from .swift/.kt
3895
+ // files, and a pair needs BOTH platforms — so without both languages the
3896
+ // pass's only collection loop is provably empty (it was streaming every
3897
+ // method row on pure-Java repos to find nothing).
3898
+ { name: 'expoXPlatEdges', gate: (has) => has('swift') && has('kotlin'), run: (q, _c, y) => expoCrossPlatformEdges(q, y) },
3899
+ // An RN cross-platform edge requires a JS-language caller on the native
3900
+ // method (`isBridge`) — no JS-family files means no JS-language nodes, so
3901
+ // the result is provably empty.
3902
+ { name: 'rnXPlatEdges', gate: (has) => has(...JS_FAMILY), run: (q, _c, y) => rnCrossPlatformEdges(q, y) },
3903
+ {
3904
+ name: 'mybatisEdges',
3905
+ gate: (has) => has('java', 'kotlin') && has('xml'),
3906
+ run: (q, _c, y) => mybatisJavaXmlEdges(q, y),
3907
+ },
3908
+ { name: 'ginEdges', gate: (has) => has('go'), run: (q, c, y) => ginMiddlewareChainEdges(q, c, y) },
3909
+ { name: 'thunkEdges', gate: (has) => has(...JS_FAMILY), run: (q, c, y) => reduxThunkEdges(q, c, y) },
3910
+ { name: 'registryEdges', gate: ALWAYS, run: (_q, c, y) => objectRegistryEdges(c, y) },
3911
+ { name: 'rtkEdges', gate: (has) => has(...JS_FAMILY), run: (q, c, y) => rtkQueryEdges(q, c, y) },
3912
+ { name: 'piniaEdges', gate: (has) => has('vue', ...JS_FAMILY), run: (_q, c, y) => piniaStoreEdges(c, y) },
3913
+ { name: 'vuexEdges', gate: (has) => has('vue', ...JS_FAMILY), run: (_q, c, y) => vuexDispatchEdges(c, y) },
3914
+ { name: 'celeryEdges', gate: (has) => has('python'), run: (_q, c, y) => celeryDispatchEdges(c, y) },
3915
+ { name: 'springEdges', gate: (has) => has('java'), run: (_q, c, y) => springEventEdges(c, y) },
3916
+ { name: 'mediatrEdges', gate: (has) => has('csharp'), run: (_q, c, y) => mediatrDispatchEdges(c, y) },
3917
+ { name: 'sidekiqEdges', gate: (has) => has('ruby'), run: (_q, c, y) => sidekiqDispatchEdges(c, y) },
3918
+ {
3919
+ name: 'erlangBehaviourEdges',
3920
+ gate: (has) => has('erlang'),
3921
+ run: (q, c, y) => erlangBehaviourDispatchEdges(q, c, y),
3922
+ },
3923
+ { name: 'laravelEdges', gate: (has) => has('php'), run: (_q, c, y) => laravelEventEdges(c, y) },
3924
+ {
3925
+ name: 'cFnPtrEdges',
3926
+ gate: (has) => has('c', 'cpp'),
3927
+ run: (q, c, y, sub) => (0, c_fnptr_synthesizer_1.cFnPointerDispatchEdges)(q, c, y, sub),
3928
+ },
3929
+ { name: 'goframeEdges', gate: (has) => has('go'), run: (_q, c, y) => (0, goframe_synthesizer_1.goframeRouteEdges)(c, y) },
3930
+ { name: 'nixOptionEdges', gate: (has) => has('nix'), run: (q, _c, y) => nixOptionPathEdges(q, y) },
3931
+ ];
3932
+ /** Fixed non-registry steps: goMethodContains, goImplements, dedupe-merge, insertMergedEdges. */
3933
+ const FIXED_SYNTH_STEPS = 4;
3934
+ exports.SYNTH_PROGRESS_STEPS = exports.SYNTH_PASSES.length + FIXED_SYNTH_STEPS;
3935
+ async function synthesizeCallbackEdges(queries, ctx, onProgress,
3936
+ // A live resolver pool to fan the independent passes across (structural type
3937
+ // so this file never imports the pool — resolver-worker imports THIS file).
3938
+ // Null/omitted → the sequential path, byte-identical to the pool path.
3939
+ pool,
3940
+ // WAL-valve writer backstop (WalCheckpointValve.backpressure), called at
3941
+ // pool-idle points in the edge-insert loops below — the passes themselves
3942
+ // only read; every write in this function happens with the pool idle.
3943
+ backpressure) {
3814
3944
  // Each sub-pass below is a whole-graph scan, and there are ~30 of them, all
3815
3945
  // running synchronously on the indexer's main thread. Their AGGREGATE can run
3816
3946
  // for well over a minute on a large repo — long enough for the #850 liveness
@@ -3865,17 +3995,24 @@ async function synthesizeCallbackEdges(queries, ctx, onProgress) {
3865
3995
  // Passes without an explicit language filter always run.
3866
3996
  const langs = queries.getDistinctFileLanguages();
3867
3997
  const has = (...ls) => ls.some((l) => langs.has(l));
3868
- const JS_FAMILY = ['typescript', 'javascript', 'tsx', 'jsx'];
3869
3998
  const NONE = [];
3870
3999
  // Cross-file Go method→type `contains` edges must be synthesized AND persisted
3871
4000
  // FIRST: a method declared in a different file from its receiver type is
3872
4001
  // otherwise orphaned from the struct, and goImplementsEdges (next) derives a
3873
4002
  // struct's method set from its `contains` edges — so without this it would
3874
4003
  // under-count the interfaces a cross-file struct satisfies. (#583)
4004
+ // Writer-side WAL backstop for the insert loops here (see the param doc):
4005
+ // one fstat when under the valve's hard cap, a parked full backfill past it.
4006
+ const foldIfOver = async () => {
4007
+ const bp = backpressure?.();
4008
+ if (bp)
4009
+ await bp;
4010
+ };
3875
4011
  const goMethodContains = has('go') ? await goCrossFileMethodContainsEdges(queries, yieldToLoop) : NONE;
3876
4012
  for (let i = 0; i < goMethodContains.length; i += 2000) {
3877
4013
  queries.insertEdges(goMethodContains.slice(i, i + 2000));
3878
4014
  await yieldToLoop();
4015
+ await foldIfOver();
3879
4016
  }
3880
4017
  await yieldToLoop();
3881
4018
  __mark('goMethodContains');
@@ -3887,162 +4024,78 @@ async function synthesizeCallbackEdges(queries, ctx, onProgress) {
3887
4024
  for (let i = 0; i < goImpl.length; i += 2000) {
3888
4025
  queries.insertEdges(goImpl.slice(i, i + 2000));
3889
4026
  await yieldToLoop();
4027
+ await foldIfOver();
3890
4028
  }
3891
4029
  await yieldToLoop();
3892
4030
  __mark('goImplements');
3893
- const fieldEdges = await fieldChannelEdges(queries, ctx, yieldToLoop);
3894
- await yieldToLoop();
3895
- __mark('fieldEdges');
3896
- const closureCollEdges = await closureCollectionEdges(queries, ctx, yieldToLoop);
3897
- await yieldToLoop();
3898
- __mark('closureCollEdges');
3899
- const emitterEdges = await eventEmitterEdges(ctx, yieldToLoop);
3900
- await yieldToLoop();
3901
- __mark('emitterEdges');
3902
- const renderEdges = await reactRenderEdges(queries, ctx, yieldToLoop);
3903
- await yieldToLoop();
3904
- __mark('renderEdges');
3905
- const jsxEdges = await reactJsxChildEdges(ctx, yieldToLoop);
3906
- await yieldToLoop();
3907
- __mark('jsxEdges');
3908
- const vueEdges = has('vue') ? await vueTemplateEdges(ctx, yieldToLoop) : NONE;
3909
- await yieldToLoop();
3910
- __mark('vueEdges');
3911
- const svelteKitEdges = has('svelte') ? await svelteKitLoadEdges(ctx, yieldToLoop) : NONE;
3912
- await yieldToLoop();
3913
- __mark('svelteKitEdges');
3914
- const pascalEdges = await pascalFormEdges(ctx, yieldToLoop);
3915
- await yieldToLoop();
3916
- __mark('pascalEdges');
3917
- const flutterEdges = has('dart') ? await flutterBuildEdges(queries, ctx, yieldToLoop) : NONE;
3918
- await yieldToLoop();
3919
- __mark('flutterEdges');
3920
- const arkuiStateEdges = has('arkts') ? await arkuiStateBuildEdges(queries, ctx, yieldToLoop) : NONE;
3921
- await yieldToLoop();
3922
- __mark('arkuiStateEdges');
3923
- const arkuiEmitter = has('arkts') ? await arkuiEmitterEdges(ctx, yieldToLoop) : NONE;
3924
- await yieldToLoop();
3925
- __mark('arkuiEmitter');
3926
- const arkuiRoutes = has('arkts') ? await arkuiRouterEdges(ctx, yieldToLoop) : NONE;
3927
- await yieldToLoop();
3928
- __mark('arkuiRoutes');
3929
- const cppEdges = has('cpp') ? await cppOverrideEdges(queries, yieldToLoop) : NONE;
3930
- await yieldToLoop();
3931
- __mark('cppEdges');
3932
- const ifaceEdges = has('java', 'kotlin', 'csharp', 'swift', 'scala', 'go', 'rust', 'arkts', ...JS_FAMILY)
3933
- ? await interfaceOverrideEdges(queries, yieldToLoop) : NONE;
3934
- await yieldToLoop();
3935
- __mark('ifaceEdges');
3936
- const kotlinExpectActual = has('kotlin') ? await kotlinExpectActualEdges(queries, yieldToLoop) : NONE;
3937
- await yieldToLoop();
3938
- __mark('kotlinExpectActual');
3939
- const goGrpcEdges = has('go') ? await goGrpcStubImplEdges(queries, yieldToLoop) : NONE;
3940
- await yieldToLoop();
3941
- __mark('goGrpcEdges');
3942
- const rnEventEdgesList = has(...JS_FAMILY) ? await rnEventEdges(ctx, yieldToLoop) : NONE;
3943
- await yieldToLoop();
3944
- __mark('rnEventEdgesList');
3945
- const fabricNativeEdges = await fabricNativeImplEdges(ctx, yieldToLoop);
3946
- await yieldToLoop();
3947
- __mark('fabricNativeEdges');
3948
- const expoXPlatEdges = await expoCrossPlatformEdges(queries, yieldToLoop);
3949
- await yieldToLoop();
3950
- __mark('expoXPlatEdges');
3951
- const rnXPlatEdges = await rnCrossPlatformEdges(queries, yieldToLoop);
3952
- await yieldToLoop();
3953
- __mark('rnXPlatEdges');
3954
- const mybatisEdges = has('java', 'kotlin') && has('xml') ? await mybatisJavaXmlEdges(queries, yieldToLoop) : NONE;
3955
- await yieldToLoop();
3956
- __mark('mybatisEdges');
3957
- const ginEdges = has('go') ? await ginMiddlewareChainEdges(queries, ctx, yieldToLoop) : NONE;
3958
- await yieldToLoop();
3959
- __mark('ginEdges');
3960
- const thunkEdges = has(...JS_FAMILY) ? await reduxThunkEdges(queries, ctx, yieldToLoop) : NONE;
3961
- await yieldToLoop();
3962
- __mark('thunkEdges');
3963
- const registryEdges = await objectRegistryEdges(ctx, yieldToLoop);
3964
- await yieldToLoop();
3965
- __mark('registryEdges');
3966
- const rtkEdges = has(...JS_FAMILY) ? await rtkQueryEdges(queries, ctx, yieldToLoop) : NONE;
3967
- await yieldToLoop();
3968
- __mark('rtkEdges');
3969
- const piniaEdges = has('vue', ...JS_FAMILY) ? await piniaStoreEdges(ctx, yieldToLoop) : NONE;
3970
- await yieldToLoop();
3971
- __mark('piniaEdges');
3972
- const vuexEdges = has('vue', ...JS_FAMILY) ? await vuexDispatchEdges(ctx, yieldToLoop) : NONE;
3973
- await yieldToLoop();
3974
- __mark('vuexEdges');
3975
- const celeryEdges = has('python') ? await celeryDispatchEdges(ctx, yieldToLoop) : NONE;
3976
- await yieldToLoop();
3977
- __mark('celeryEdges');
3978
- const springEdges = has('java') ? await springEventEdges(ctx, yieldToLoop) : NONE;
3979
- await yieldToLoop();
3980
- __mark('springEdges');
3981
- const mediatrEdges = has('csharp') ? await mediatrDispatchEdges(ctx, yieldToLoop) : NONE;
3982
- await yieldToLoop();
3983
- __mark('mediatrEdges');
3984
- const sidekiqEdges = has('ruby') ? await sidekiqDispatchEdges(ctx, yieldToLoop) : NONE;
3985
- await yieldToLoop();
3986
- __mark('sidekiqEdges');
3987
- const erlangBehaviourEdges = has('erlang') ? await erlangBehaviourDispatchEdges(queries, ctx, yieldToLoop) : NONE;
3988
- await yieldToLoop();
3989
- __mark('erlangBehaviourEdges');
3990
- const laravelEdges = has('php') ? await laravelEventEdges(ctx, yieldToLoop) : NONE;
3991
- await yieldToLoop();
3992
- __mark('laravelEdges');
3993
- const cFnPtrEdges = has('c', 'cpp') ? await (0, c_fnptr_synthesizer_1.cFnPointerDispatchEdges)(queries, ctx, yieldToLoop, subProgress) : NONE;
3994
- await yieldToLoop();
3995
- __mark('cFnPtrEdges');
3996
- const goframeEdges = has('go') ? await (0, goframe_synthesizer_1.goframeRouteEdges)(ctx, yieldToLoop) : NONE;
3997
- await yieldToLoop();
3998
- __mark('goframeEdges');
3999
- const nixOptionEdges = has('nix') ? await nixOptionPathEdges(queries, yieldToLoop) : NONE;
4000
- await yieldToLoop();
4001
- __mark('nixOptionEdges');
4002
- const arktsStartupEdges = has('arkts') ? arktsEntryEdges(queries, ctx) : NONE;
4003
- await yieldToLoop();
4004
- __mark('arktsStartupEdges');
4031
+ // Run the independent passes (see SYNTH_PASSES). Their results are merged in
4032
+ // REGISTRY ORDER below regardless of execution order, and none of their edges
4033
+ // persist until that merge — so every pass sees the same committed
4034
+ // post-resolution DB state whether it runs sequentially here or on a resolver
4035
+ // pool worker. With a live pool (already booted on ≥150k-ref repos), passes
4036
+ // fan out across its read-only workers and the per-pass wall-clock comes from
4037
+ // the worker; a pass that fails on a worker falls back to running on the main
4038
+ // thread, so a worker crash isolates to a retry instead of failing synthesis.
4039
+ const passEdges = new Array(exports.SYNTH_PASSES.length).fill(NONE);
4040
+ const markPass = (label, dt) => {
4041
+ if (process.env.HOMEGRAPH_SYNTH_TIMINGS && (dt > 250 || process.env.HOMEGRAPH_SYNTH_TIMINGS === 'all')) {
4042
+ console.error(`[synth-timing] ${label}: ${dt}ms`);
4043
+ }
4044
+ passesDone++;
4045
+ emit(passesDone);
4046
+ };
4047
+ const runPassOnMain = async (i) => {
4048
+ const pass = exports.SYNTH_PASSES[i];
4049
+ const t0 = Date.now();
4050
+ passEdges[i] = await pass.run(queries, ctx, yieldToLoop, subProgress);
4051
+ await yieldToLoop();
4052
+ markPass(pass.name, Date.now() - t0);
4053
+ };
4054
+ const gatedIn = [];
4055
+ for (let i = 0; i < exports.SYNTH_PASSES.length; i++) {
4056
+ if (exports.SYNTH_PASSES[i].gate(has))
4057
+ gatedIn.push(i);
4058
+ else
4059
+ markPass(exports.SYNTH_PASSES[i].name, 0);
4060
+ }
4061
+ // Above this node count, a pass that OOM-killed its worker must NOT be
4062
+ // retried on the main thread — the retry would OOM the whole process and
4063
+ // take the index with it (the #1212 failure class). Below it, a worker
4064
+ // failure is more likely a transient crash than a memory ceiling, and the
4065
+ // main-thread retry keeps coverage. Skipping loses only that pass's
4066
+ // synthesized edges; the index still completes.
4067
+ const MAIN_RETRY_MAX_NODES = 1_500_000;
4068
+ const graphNodes = queries.getNodeAndEdgeCount().nodes;
4069
+ if (pool && gatedIn.length > 1) {
4070
+ await Promise.all(gatedIn.map(async (i) => {
4071
+ const pass = exports.SYNTH_PASSES[i];
4072
+ try {
4073
+ const out = await pool.runSynthPass(pass.name);
4074
+ passEdges[i] = out.edges;
4075
+ markPass(pass.name, out.ms);
4076
+ }
4077
+ catch (err) {
4078
+ if (graphNodes > MAIN_RETRY_MAX_NODES) {
4079
+ // Worker died at a scale where the main-thread retry is a process
4080
+ // OOM risk: skip the pass, keep the index alive, and say so.
4081
+ console.error(`[synthesis] pass '${pass.name}' failed on a worker at ${graphNodes} nodes — skipped (edges from this pass are absent): ${err instanceof Error ? err.message : String(err)}`);
4082
+ markPass(`${pass.name} (skipped at scale)`, 0);
4083
+ return;
4084
+ }
4085
+ // Worker-side failure (crash, OOM, unknown pass after a version
4086
+ // mismatch): retry this one pass on the main thread.
4087
+ await runPassOnMain(i);
4088
+ }
4089
+ }));
4090
+ }
4091
+ else {
4092
+ for (const i of gatedIn) {
4093
+ await runPassOnMain(i);
4094
+ }
4095
+ }
4005
4096
  const merged = [];
4006
4097
  const seen = new Set();
4007
- for (const e of [
4008
- ...fieldEdges,
4009
- ...closureCollEdges,
4010
- ...emitterEdges,
4011
- ...renderEdges,
4012
- ...jsxEdges,
4013
- ...vueEdges,
4014
- ...svelteKitEdges,
4015
- ...pascalEdges,
4016
- ...flutterEdges,
4017
- ...arkuiStateEdges,
4018
- ...arkuiEmitter,
4019
- ...arkuiRoutes,
4020
- ...cppEdges,
4021
- ...ifaceEdges,
4022
- ...kotlinExpectActual,
4023
- ...goGrpcEdges,
4024
- ...rnEventEdgesList,
4025
- ...fabricNativeEdges,
4026
- ...expoXPlatEdges,
4027
- ...rnXPlatEdges,
4028
- ...mybatisEdges,
4029
- ...ginEdges,
4030
- ...thunkEdges,
4031
- ...registryEdges,
4032
- ...rtkEdges,
4033
- ...piniaEdges,
4034
- ...vuexEdges,
4035
- ...celeryEdges,
4036
- ...springEdges,
4037
- ...mediatrEdges,
4038
- ...sidekiqEdges,
4039
- ...erlangBehaviourEdges,
4040
- ...laravelEdges,
4041
- ...cFnPtrEdges,
4042
- ...goframeEdges,
4043
- ...nixOptionEdges,
4044
- ...arktsStartupEdges,
4045
- ]) {
4098
+ for (const e of passEdges.flat()) {
4046
4099
  const key = `${e.source}>${e.target}`;
4047
4100
  if (seen.has(key))
4048
4101
  continue;
@@ -4056,6 +4109,7 @@ async function synthesizeCallbackEdges(queries, ctx, onProgress) {
4056
4109
  for (let i = 0; i < merged.length; i += 2000) {
4057
4110
  queries.insertEdges(merged.slice(i, i + 2000));
4058
4111
  await yieldToLoop();
4112
+ await foldIfOver();
4059
4113
  }
4060
4114
  __mark('insertMergedEdges');
4061
4115
  return merged.length + goImpl.length + goMethodContains.length;