homegraph 1.2.0 → 1.4.1

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 (284) hide show
  1. package/dist/bin/homegraph.js +3 -0
  2. package/dist/bin/homegraph.js.map +1 -1
  3. package/dist/context/index.d.ts.map +1 -1
  4. package/dist/context/index.js +85 -0
  5. package/dist/context/index.js.map +1 -1
  6. package/dist/db/index.d.ts +64 -5
  7. package/dist/db/index.d.ts.map +1 -1
  8. package/dist/db/index.js +178 -14
  9. package/dist/db/index.js.map +1 -1
  10. package/dist/db/migrations.d.ts +1 -1
  11. package/dist/db/migrations.d.ts.map +1 -1
  12. package/dist/db/migrations.js +19 -1
  13. package/dist/db/migrations.js.map +1 -1
  14. package/dist/db/queries.d.ts +117 -2
  15. package/dist/db/queries.d.ts.map +1 -1
  16. package/dist/db/queries.js +299 -6
  17. package/dist/db/queries.js.map +1 -1
  18. package/dist/db/schema.sql +32 -1
  19. package/dist/db/wal-valve.d.ts +106 -0
  20. package/dist/db/wal-valve.d.ts.map +1 -0
  21. package/dist/db/wal-valve.js +208 -0
  22. package/dist/db/wal-valve.js.map +1 -0
  23. package/dist/directory.d.ts +9 -5
  24. package/dist/directory.d.ts.map +1 -1
  25. package/dist/directory.js +210 -19
  26. package/dist/directory.js.map +1 -1
  27. package/dist/extraction/cfml-extractor.d.ts +107 -0
  28. package/dist/extraction/cfml-extractor.d.ts.map +1 -0
  29. package/dist/extraction/cfml-extractor.js +494 -0
  30. package/dist/extraction/cfml-extractor.js.map +1 -0
  31. package/dist/extraction/grammars.d.ts +25 -1
  32. package/dist/extraction/grammars.d.ts.map +1 -1
  33. package/dist/extraction/grammars.js +177 -24
  34. package/dist/extraction/grammars.js.map +1 -1
  35. package/dist/extraction/index.d.ts +43 -4
  36. package/dist/extraction/index.d.ts.map +1 -1
  37. package/dist/extraction/index.js +308 -5
  38. package/dist/extraction/index.js.map +1 -1
  39. package/dist/extraction/languages/c-cpp.d.ts +42 -0
  40. package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  41. package/dist/extraction/languages/c-cpp.js +369 -4
  42. package/dist/extraction/languages/c-cpp.js.map +1 -1
  43. package/dist/extraction/languages/cfquery.d.ts +12 -0
  44. package/dist/extraction/languages/cfquery.d.ts.map +1 -0
  45. package/dist/extraction/languages/cfquery.js +28 -0
  46. package/dist/extraction/languages/cfquery.js.map +1 -0
  47. package/dist/extraction/languages/cfscript.d.ts +3 -0
  48. package/dist/extraction/languages/cfscript.d.ts.map +1 -0
  49. package/dist/extraction/languages/cfscript.js +73 -0
  50. package/dist/extraction/languages/cfscript.js.map +1 -0
  51. package/dist/extraction/languages/cobol.d.ts +33 -0
  52. package/dist/extraction/languages/cobol.d.ts.map +1 -0
  53. package/dist/extraction/languages/cobol.js +499 -0
  54. package/dist/extraction/languages/cobol.js.map +1 -0
  55. package/dist/extraction/languages/erlang.d.ts +3 -0
  56. package/dist/extraction/languages/erlang.d.ts.map +1 -0
  57. package/dist/extraction/languages/erlang.js +350 -0
  58. package/dist/extraction/languages/erlang.js.map +1 -0
  59. package/dist/extraction/languages/index.d.ts.map +1 -1
  60. package/dist/extraction/languages/index.js +16 -0
  61. package/dist/extraction/languages/index.js.map +1 -1
  62. package/dist/extraction/languages/nix.d.ts +3 -0
  63. package/dist/extraction/languages/nix.d.ts.map +1 -0
  64. package/dist/extraction/languages/nix.js +294 -0
  65. package/dist/extraction/languages/nix.js.map +1 -0
  66. package/dist/extraction/languages/solidity.d.ts +3 -0
  67. package/dist/extraction/languages/solidity.d.ts.map +1 -0
  68. package/dist/extraction/languages/solidity.js +293 -0
  69. package/dist/extraction/languages/solidity.js.map +1 -0
  70. package/dist/extraction/languages/terraform.d.ts +3 -0
  71. package/dist/extraction/languages/terraform.d.ts.map +1 -0
  72. package/dist/extraction/languages/terraform.js +641 -0
  73. package/dist/extraction/languages/terraform.js.map +1 -0
  74. package/dist/extraction/languages/vbnet.d.ts +11 -0
  75. package/dist/extraction/languages/vbnet.d.ts.map +1 -0
  76. package/dist/extraction/languages/vbnet.js +141 -0
  77. package/dist/extraction/languages/vbnet.js.map +1 -0
  78. package/dist/extraction/mybatis-extractor.d.ts +30 -10
  79. package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  80. package/dist/extraction/mybatis-extractor.js +140 -38
  81. package/dist/extraction/mybatis-extractor.js.map +1 -1
  82. package/dist/extraction/parse-pool.d.ts +27 -1
  83. package/dist/extraction/parse-pool.d.ts.map +1 -1
  84. package/dist/extraction/parse-pool.js +75 -7
  85. package/dist/extraction/parse-pool.js.map +1 -1
  86. package/dist/extraction/parse-worker.js +9 -2
  87. package/dist/extraction/parse-worker.js.map +1 -1
  88. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  89. package/dist/extraction/tree-sitter-helpers.js +1 -0
  90. package/dist/extraction/tree-sitter-helpers.js.map +1 -1
  91. package/dist/extraction/tree-sitter-types.d.ts +3 -1
  92. package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  93. package/dist/extraction/tree-sitter.d.ts +38 -0
  94. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  95. package/dist/extraction/tree-sitter.js +754 -10
  96. package/dist/extraction/tree-sitter.js.map +1 -1
  97. package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  98. package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  99. package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  100. package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  101. package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  102. package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  103. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  104. package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  105. package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  106. package/dist/graph/traversal.d.ts.map +1 -1
  107. package/dist/graph/traversal.js +1 -1
  108. package/dist/graph/traversal.js.map +1 -1
  109. package/dist/index.d.ts +60 -2
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +348 -5
  112. package/dist/index.js.map +1 -1
  113. package/dist/installer/index.d.ts +42 -0
  114. package/dist/installer/index.d.ts.map +1 -1
  115. package/dist/installer/index.js +97 -2
  116. package/dist/installer/index.js.map +1 -1
  117. package/dist/mcp/daemon.d.ts +25 -3
  118. package/dist/mcp/daemon.d.ts.map +1 -1
  119. package/dist/mcp/daemon.js +62 -8
  120. package/dist/mcp/daemon.js.map +1 -1
  121. package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  122. package/dist/mcp/dynamic-boundaries.js +2 -1
  123. package/dist/mcp/dynamic-boundaries.js.map +1 -1
  124. package/dist/mcp/early-ppid.d.ts +26 -0
  125. package/dist/mcp/early-ppid.d.ts.map +1 -0
  126. package/dist/mcp/early-ppid.js +29 -0
  127. package/dist/mcp/early-ppid.js.map +1 -0
  128. package/dist/mcp/engine.d.ts +4 -5
  129. package/dist/mcp/engine.d.ts.map +1 -1
  130. package/dist/mcp/engine.js +27 -1
  131. package/dist/mcp/engine.js.map +1 -1
  132. package/dist/mcp/index.d.ts.map +1 -1
  133. package/dist/mcp/index.js +41 -6
  134. package/dist/mcp/index.js.map +1 -1
  135. package/dist/mcp/liveness-watchdog.d.ts +18 -1
  136. package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  137. package/dist/mcp/liveness-watchdog.js +73 -13
  138. package/dist/mcp/liveness-watchdog.js.map +1 -1
  139. package/dist/mcp/memory-budget.d.ts +30 -0
  140. package/dist/mcp/memory-budget.d.ts.map +1 -0
  141. package/dist/mcp/memory-budget.js +123 -0
  142. package/dist/mcp/memory-budget.js.map +1 -0
  143. package/dist/mcp/proxy.d.ts.map +1 -1
  144. package/dist/mcp/proxy.js +88 -4
  145. package/dist/mcp/proxy.js.map +1 -1
  146. package/dist/mcp/query-cache.d.ts +35 -4
  147. package/dist/mcp/query-cache.d.ts.map +1 -1
  148. package/dist/mcp/query-cache.js +89 -19
  149. package/dist/mcp/query-cache.js.map +1 -1
  150. package/dist/mcp/query-pool.d.ts +46 -12
  151. package/dist/mcp/query-pool.d.ts.map +1 -1
  152. package/dist/mcp/query-pool.js +100 -23
  153. package/dist/mcp/query-pool.js.map +1 -1
  154. package/dist/mcp/server-instructions.d.ts +4 -27
  155. package/dist/mcp/server-instructions.d.ts.map +1 -1
  156. package/dist/mcp/server-instructions.js +39 -84
  157. package/dist/mcp/server-instructions.js.map +1 -1
  158. package/dist/mcp/session.d.ts +14 -0
  159. package/dist/mcp/session.d.ts.map +1 -1
  160. package/dist/mcp/session.js +28 -1
  161. package/dist/mcp/session.js.map +1 -1
  162. package/dist/mcp/startup-handshake.d.ts +44 -0
  163. package/dist/mcp/startup-handshake.d.ts.map +1 -0
  164. package/dist/mcp/startup-handshake.js +73 -0
  165. package/dist/mcp/startup-handshake.js.map +1 -0
  166. package/dist/mcp/tools.d.ts +104 -4
  167. package/dist/mcp/tools.d.ts.map +1 -1
  168. package/dist/mcp/tools.js +2390 -191
  169. package/dist/mcp/tools.js.map +1 -1
  170. package/dist/mcp/transport.d.ts.map +1 -1
  171. package/dist/mcp/transport.js +18 -0
  172. package/dist/mcp/transport.js.map +1 -1
  173. package/dist/project-config.d.ts +38 -0
  174. package/dist/project-config.d.ts.map +1 -1
  175. package/dist/project-config.js +101 -2
  176. package/dist/project-config.js.map +1 -1
  177. package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
  178. package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  179. package/dist/resolution/c-fnptr-synthesizer.js +178 -146
  180. package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  181. package/dist/resolution/callback-synthesizer.d.ts +9 -1
  182. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  183. package/dist/resolution/callback-synthesizer.js +1164 -176
  184. package/dist/resolution/callback-synthesizer.js.map +1 -1
  185. package/dist/resolution/frameworks/cics.d.ts +20 -0
  186. package/dist/resolution/frameworks/cics.d.ts.map +1 -0
  187. package/dist/resolution/frameworks/cics.js +90 -0
  188. package/dist/resolution/frameworks/cics.js.map +1 -0
  189. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  190. package/dist/resolution/frameworks/index.js +3 -0
  191. package/dist/resolution/frameworks/index.js.map +1 -1
  192. package/dist/resolution/frameworks/java.d.ts.map +1 -1
  193. package/dist/resolution/frameworks/java.js +14 -6
  194. package/dist/resolution/frameworks/java.js.map +1 -1
  195. package/dist/resolution/frameworks/terraform.d.ts +38 -0
  196. package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  197. package/dist/resolution/frameworks/terraform.js +277 -0
  198. package/dist/resolution/frameworks/terraform.js.map +1 -0
  199. package/dist/resolution/goframe-synthesizer.d.ts +2 -1
  200. package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
  201. package/dist/resolution/goframe-synthesizer.js +8 -3
  202. package/dist/resolution/goframe-synthesizer.js.map +1 -1
  203. package/dist/resolution/import-resolver.d.ts +7 -0
  204. package/dist/resolution/import-resolver.d.ts.map +1 -1
  205. package/dist/resolution/import-resolver.js +129 -4
  206. package/dist/resolution/import-resolver.js.map +1 -1
  207. package/dist/resolution/index.d.ts +60 -7
  208. package/dist/resolution/index.d.ts.map +1 -1
  209. package/dist/resolution/index.js +430 -80
  210. package/dist/resolution/index.js.map +1 -1
  211. package/dist/resolution/name-matcher.d.ts +0 -3
  212. package/dist/resolution/name-matcher.d.ts.map +1 -1
  213. package/dist/resolution/name-matcher.js +354 -33
  214. package/dist/resolution/name-matcher.js.map +1 -1
  215. package/dist/resolution/strip-comments.d.ts +1 -1
  216. package/dist/resolution/strip-comments.d.ts.map +1 -1
  217. package/dist/resolution/strip-comments.js +49 -0
  218. package/dist/resolution/strip-comments.js.map +1 -1
  219. package/dist/resolution/types.d.ts +29 -0
  220. package/dist/resolution/types.d.ts.map +1 -1
  221. package/dist/resolution/workspace-packages.d.ts +10 -0
  222. package/dist/resolution/workspace-packages.d.ts.map +1 -1
  223. package/dist/resolution/workspace-packages.js +142 -4
  224. package/dist/resolution/workspace-packages.js.map +1 -1
  225. package/dist/search/identifier-segments.d.ts +60 -0
  226. package/dist/search/identifier-segments.d.ts.map +1 -0
  227. package/dist/search/identifier-segments.js +176 -0
  228. package/dist/search/identifier-segments.js.map +1 -0
  229. package/dist/search/query-utils.d.ts +201 -0
  230. package/dist/search/query-utils.d.ts.map +1 -1
  231. package/dist/search/query-utils.js +919 -2
  232. package/dist/search/query-utils.js.map +1 -1
  233. package/dist/sync/git-hooks.d.ts.map +1 -1
  234. package/dist/sync/git-hooks.js +2 -0
  235. package/dist/sync/git-hooks.js.map +1 -1
  236. package/dist/sync/watcher.d.ts +10 -5
  237. package/dist/sync/watcher.d.ts.map +1 -1
  238. package/dist/sync/watcher.js +51 -14
  239. package/dist/sync/watcher.js.map +1 -1
  240. package/dist/sync/worktree.d.ts.map +1 -1
  241. package/dist/sync/worktree.js +5 -0
  242. package/dist/sync/worktree.js.map +1 -1
  243. package/dist/types.d.ts +19 -1
  244. package/dist/types.d.ts.map +1 -1
  245. package/dist/types.js +10 -0
  246. package/dist/types.js.map +1 -1
  247. package/dist/upgrade/index.d.ts +32 -0
  248. package/dist/upgrade/index.d.ts.map +1 -1
  249. package/dist/upgrade/index.js +162 -14
  250. package/dist/upgrade/index.js.map +1 -1
  251. package/dist/upgrade/remove-binary.d.ts +87 -0
  252. package/dist/upgrade/remove-binary.d.ts.map +1 -0
  253. package/dist/upgrade/remove-binary.js +289 -0
  254. package/dist/upgrade/remove-binary.js.map +1 -0
  255. package/dist/upgrade/update-check.d.ts +92 -0
  256. package/dist/upgrade/update-check.d.ts.map +1 -0
  257. package/dist/upgrade/update-check.js +258 -0
  258. package/dist/upgrade/update-check.js.map +1 -0
  259. package/package.json +2 -2
  260. package/scripts/agent-eval/run-all.sh +6 -0
  261. package/scripts/build-bundle.sh +5 -0
  262. package/scripts/npm-shim.js +8 -1
  263. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  264. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  265. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
  266. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  267. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  268. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  269. package/scripts/exp_boundary_eval/data/agents.json +0 -109
  270. package/scripts/exp_boundary_eval/data/experiments.json +0 -140
  271. package/scripts/qa_eval/README.md +0 -407
  272. package/scripts/qa_eval/_test_deveco_probe.py +0 -41
  273. package/scripts/qa_eval/agent_runner.py +0 -526
  274. package/scripts/qa_eval/data/.gitignore +0 -4
  275. package/scripts/qa_eval/data/test-set.jsonl +0 -2
  276. package/scripts/qa_eval/eval_metrics.py +0 -274
  277. package/scripts/qa_eval/external_agent.py +0 -976
  278. package/scripts/qa_eval/llm_config.py +0 -92
  279. package/scripts/qa_eval/memory_monitor.py +0 -132
  280. package/scripts/qa_eval/my_answer_accuracy.py +0 -187
  281. package/scripts/qa_eval/requirements.txt +0 -2
  282. package/scripts/qa_eval/run_pipeline.py +0 -804
  283. package/scripts/qa_eval/stats_efficiency.py +0 -279
  284. package/scripts/qa_eval/stats_scores.py +0 -207
@@ -85,6 +85,7 @@ exports.cFnPointerDispatchEdges = cFnPointerDispatchEdges;
85
85
  * key + a real-function gate; a project with no fn-pointer dispatch is a no-op.
86
86
  */
87
87
  const path = __importStar(require("node:path"));
88
+ const lru_cache_1 = require("./lru-cache");
88
89
  const strip_comments_1 = require("./strip-comments");
89
90
  const C_CPP_EXT = /\.(c|h|cc|cpp|cxx|hpp|hh|hxx|cppm|ipp|inl|tcc)$/i;
90
91
  const FN_KINDS = new Set(['function', 'method']);
@@ -337,13 +338,30 @@ const C_TYPE_KEYWORDS = new Set([
337
338
  const INCLUDE_RE = /#[ \t]*include[ \t]+"([^"\n]+)"/g;
338
339
  /** Included files worth scanning for registration tables (e.g. a generated `.def`). */
339
340
  const INCLUDABLE_EXT = /\.(def|inc|h|hh|hpp|hxx|c|cc|cpp|cxx|ipp|tcc|tbl)$/i;
340
- function cFnPointerDispatchEdges(queries, ctx) {
341
+ async function cFnPointerDispatchEdges(_queries, ctx, onYield, onFraction) {
342
+ let scannedFiles = 0;
341
343
  const files = ctx.getAllFiles().filter((f) => C_CPP_EXT.test(f));
342
344
  if (files.length === 0)
343
345
  return [];
344
- // Cache raw + stripped source per file (read once, reused across passes).
345
- // Raw is needed for `#include "…"` directives strip blanks string contents.
346
- const rawCache = new Map();
346
+ // Within-pass progress: this is the pass that parks the "Linking dynamic
347
+ // dispatch" bar on C-heavy repos, so it reports a real fraction of its
348
+ // dominant work. `files` is swept once per file loop below (passes A, C, D,
349
+ // E — pass B is node-bound and comparatively brief), reported at the same
350
+ // per-16-files cadence as the cooperative yield.
351
+ const FILE_SWEEPS = 4;
352
+ const tick = async () => {
353
+ if ((++scannedFiles & 15) === 0) {
354
+ onFraction?.(scannedFiles / (files.length * FILE_SWEEPS));
355
+ await onYield();
356
+ }
357
+ };
358
+ // Cache raw + stripped source per file, LRU-BOUNDED. The old unbounded Maps
359
+ // retained every C/C++ file's raw AND stripped text for the whole pass —
360
+ // multiple GB on the Linux kernel, one of the two OOM culprits in #1212.
361
+ // Every sweep below iterates in `files` order, and node-kind scans return
362
+ // rows in file-commit order, so access is near-sequential and a small LRU
363
+ // hits; a miss just re-reads + re-strips.
364
+ const rawCache = new lru_cache_1.LRUCache(128);
347
365
  const raw = (file) => {
348
366
  if (rawCache.has(file))
349
367
  return rawCache.get(file);
@@ -351,10 +369,14 @@ function cFnPointerDispatchEdges(queries, ctx) {
351
369
  rawCache.set(file, r);
352
370
  return r;
353
371
  };
354
- const srcCache = new Map();
372
+ const srcCache = new lru_cache_1.LRUCache(128);
355
373
  const src = (file) => {
356
- if (srcCache.has(file))
357
- return srcCache.get(file);
374
+ // A cached '' (empty or unreadable file) returns '' where the miss path
375
+ // returns null for unreadable — every caller falsy-checks, so the two are
376
+ // interchangeable.
377
+ const hit = srcCache.get(file);
378
+ if (hit !== undefined)
379
+ return hit;
358
380
  const r = raw(file);
359
381
  const s = r == null ? '' : (0, strip_comments_1.stripCommentsForRegex)(r, 'c');
360
382
  srcCache.set(file, s);
@@ -380,6 +402,7 @@ function cFnPointerDispatchEdges(queries, ctx) {
380
402
  const fnPtrTypedefs = new Set();
381
403
  const fnTypeTypedefs = new Set();
382
404
  for (const file of files) {
405
+ await tick();
383
406
  const s = src(file);
384
407
  if (!s || !s.includes('typedef'))
385
408
  continue;
@@ -472,10 +495,12 @@ function cFnPointerDispatchEdges(queries, ctx) {
472
495
  if (fields.some((f) => f.isFnPtr))
473
496
  structLayout.set(name, fields);
474
497
  };
475
- for (const st of ctx.getNodesByKind('struct')) {
498
+ for (const st of (ctx.iterateNodesByKind?.('struct') ?? ctx.getNodesByKind('struct'))) {
499
+ if ((++scannedFiles & 255) === 0)
500
+ await onYield();
476
501
  if (!C_CPP_EXT.test(st.filePath))
477
502
  continue;
478
- const s = srcCache.get(st.filePath) ?? src(st.filePath);
503
+ const s = src(st.filePath);
479
504
  if (!s)
480
505
  continue;
481
506
  const body = sliceLines(s, st.startLine, st.endLine);
@@ -490,12 +515,9 @@ function cFnPointerDispatchEdges(queries, ctx) {
490
515
  // up by `#ifdef`) is discovered later during the unit scan. The `reg.size === 0`
491
516
  // guard after registration still short-circuits when nothing bridges.
492
517
  const fnPtrFieldOf = (struct, field) => !!structLayout.get(struct)?.some((f) => f.name === field && f.isFnPtr);
493
- // C/C++ function + method nodes, materialized once (bounded by C/C++ files).
494
- const cFns = [];
495
- for (const fn of iterateFns(queries)) {
496
- if (C_CPP_EXT.test(fn.filePath))
497
- cFns.push(fn);
498
- }
518
+ // C/C++ function + method nodes are STREAMED per sweep (see passes D/E)
519
+ // the old materialized `cFns` array held every function node on the repo
520
+ // (O(nodes) memory, part of the #1212 kernel OOM).
499
521
  // ---- function-name → node resolution (prefer a function in the same file) ----
500
522
  const resolveFn = (name, preferFile) => {
501
523
  const cands = ctx.getNodesByName(name).filter((n) => FN_KINDS.has(n.kind));
@@ -511,14 +533,14 @@ function cFnPointerDispatchEdges(queries, ctx) {
511
533
  return cands[0];
512
534
  };
513
535
  // ---- Pass C: registrations — Map<"struct.field", Set<funcNodeId>> ----
536
+ // Ids only — retaining the full Node per registration (the old `idToNode`)
537
+ // was write-only dead weight at O(registrations) memory.
514
538
  const reg = new Map();
515
- const idToNode = new Map();
516
539
  const addReg = (struct, field, fn) => {
517
540
  const key = `${struct}.${field}`;
518
541
  if (!reg.has(key))
519
542
  reg.set(key, new Set());
520
543
  reg.get(key).add(fn.id);
521
- idToNode.set(fn.id, fn);
522
544
  };
523
545
  // Bare arrays-of-fn-pointers (no struct): array VARIABLE name → per-file sets
524
546
  // of registered function ids. Multi-entry because a file-scope `static` table
@@ -537,7 +559,6 @@ function cFnPointerDispatchEdges(queries, ctx) {
537
559
  entries.push(e);
538
560
  }
539
561
  e.ids.add(fn.id);
540
- idToNode.set(fn.id, fn);
541
562
  };
542
563
  // A struct value `{ … }` (one element) — register its function entries to the
543
564
  // struct's fields, by `.field = fn` designators or by positional slot.
@@ -612,7 +633,8 @@ function cFnPointerDispatchEdges(queries, ctx) {
612
633
  }
613
634
  };
614
635
  // Per-file macro + include parsing (any file, indexed or not), cached.
615
- const fnMacroCache = new Map();
636
+ // Derived per-file caches, LRU-bounded like the content caches (#1212).
637
+ const fnMacroCache = new lru_cache_1.LRUCache(256);
616
638
  const fileFnMacros = (file) => {
617
639
  let m = fnMacroCache.get(file);
618
640
  if (!m) {
@@ -621,7 +643,7 @@ function cFnPointerDispatchEdges(queries, ctx) {
621
643
  }
622
644
  return m;
623
645
  };
624
- const objMacroCache = new Map();
646
+ const objMacroCache = new lru_cache_1.LRUCache(256);
625
647
  const fileObjMacros = (file) => {
626
648
  let m = objMacroCache.get(file);
627
649
  if (!m) {
@@ -630,7 +652,7 @@ function cFnPointerDispatchEdges(queries, ctx) {
630
652
  }
631
653
  return m;
632
654
  };
633
- const definedCache = new Map();
655
+ const definedCache = new lru_cache_1.LRUCache(256);
634
656
  const fileDefinedNames = (file) => {
635
657
  let d = definedCache.get(file);
636
658
  if (!d) {
@@ -639,7 +661,7 @@ function cFnPointerDispatchEdges(queries, ctx) {
639
661
  }
640
662
  return d;
641
663
  };
642
- const includeCache = new Map();
664
+ const includeCache = new lru_cache_1.LRUCache(1024);
643
665
  const localIncludesOf = (file) => {
644
666
  let out = includeCache.get(file);
645
667
  if (out)
@@ -681,45 +703,7 @@ function cFnPointerDispatchEdges(queries, ctx) {
681
703
  buildEnv(inc, depth - 1, seen, fn, obj, def);
682
704
  };
683
705
  const indexedSet = new Set(files);
684
- const units = [];
685
706
  const seenInclude = new Set();
686
- for (const file of files) {
687
- const env = new Map();
688
- const objEnv = new Map();
689
- const defined = new Set();
690
- buildEnv(file, 2, new Set(), env, objEnv, defined);
691
- const s = src(file);
692
- if (s)
693
- units.push({ text: s, file, env, objEnv });
694
- for (const target of localIncludesOf(file)) {
695
- if (seenInclude.has(`${file}>${target}`))
696
- continue;
697
- const incSrc = src(target);
698
- if (!incSrc)
699
- continue;
700
- if (indexedSet.has(target)) {
701
- // Re-scan an indexed header only when this includer unlocks guarded code.
702
- const ownDef = fileDefinedNames(target);
703
- const adds = [...defined].some((n) => !ownDef.has(n));
704
- if (!adds || !/#\s*if/.test(incSrc))
705
- continue;
706
- }
707
- seenInclude.add(`${file}>${target}`);
708
- // The include is pasted into the includer — evaluate its conditionals in
709
- // the includer's defined set (a no-op when it has none). Re-parse the
710
- // included file's OWN macros from that resolved text so a macro it defines
711
- // conditionally (vim's `EXCMD`, whose plain last-wins parse picks the enum
712
- // arm) overrides with the ARM THAT IS ACTUALLY ACTIVE here.
713
- const text = evalConditionals(incSrc, defined);
714
- const incEnv = new Map(env);
715
- for (const [k, v] of parseFunctionMacros(text))
716
- incEnv.set(k, v);
717
- const incObjEnv = new Map(objEnv);
718
- for (const [k, v] of parseObjectMacros(text))
719
- incObjEnv.set(k, v);
720
- units.push({ text, file: target, env: incEnv, objEnv: incObjEnv });
721
- }
722
- }
723
707
  // Global variable → struct type, for resolving a dispatch through a file-scope
724
708
  // table by subscript (`cmdnames[i].cmd_func(…)`).
725
709
  const globalVarType = new Map();
@@ -760,10 +744,13 @@ function cFnPointerDispatchEdges(queries, ctx) {
760
744
  // (`zend_rc_dtor_func_t t[]`) needs none. The typedef-set membership gate
761
745
  // (below) is what separates this from a plain data/struct array.
762
746
  const ARRAY_TABLE_RE = /(?:^|[;{}])\s*(?:(?:static|const|extern|register|volatile)\s+)*(\w+)\s+(\*\s*)?(\w+)\s*\[[^\]]*\]\s*=\s*\{/g;
763
- for (const unit of units) {
747
+ // Process ONE unit's text and discard it. The old shape built every unit up
748
+ // front (`const units: Unit[]`) — the full text of every C file plus its
749
+ // expanded includes held simultaneously, gigabytes on the kernel (#1212).
750
+ const processUnit = (unit) => {
764
751
  const s = unit.text;
765
752
  if (!s || !s.includes('{'))
766
- continue;
753
+ return;
767
754
  INLINE_STRUCT_RE.lastIndex = 0;
768
755
  let im;
769
756
  while ((im = INLINE_STRUCT_RE.exec(s))) {
@@ -793,7 +780,7 @@ function cFnPointerDispatchEdges(queries, ctx) {
793
780
  }
794
781
  }
795
782
  if (!s.includes('='))
796
- continue;
783
+ return;
797
784
  INIT_RE.lastIndex = 0;
798
785
  let m;
799
786
  while ((m = INIT_RE.exec(s))) {
@@ -829,6 +816,46 @@ function cFnPointerDispatchEdges(queries, ctx) {
829
816
  registerArrayValue(am[3], s.slice(open + 1, close), unit.file, unit.env);
830
817
  ARRAY_TABLE_RE.lastIndex = close;
831
818
  }
819
+ };
820
+ // ---- Pass C: registrations — stream each file (and its qualifying local
821
+ // includes) through processUnit, one at a time.
822
+ for (const file of files) {
823
+ await tick();
824
+ const env = new Map();
825
+ const objEnv = new Map();
826
+ const defined = new Set();
827
+ buildEnv(file, 2, new Set(), env, objEnv, defined);
828
+ const s = src(file);
829
+ if (s)
830
+ processUnit({ text: s, file, env, objEnv });
831
+ for (const target of localIncludesOf(file)) {
832
+ if (seenInclude.has(`${file}>${target}`))
833
+ continue;
834
+ const incSrc = src(target);
835
+ if (!incSrc)
836
+ continue;
837
+ if (indexedSet.has(target)) {
838
+ // Re-scan an indexed header only when this includer unlocks guarded code.
839
+ const ownDef = fileDefinedNames(target);
840
+ const adds = [...defined].some((n) => !ownDef.has(n));
841
+ if (!adds || !/#\s*if/.test(incSrc))
842
+ continue;
843
+ }
844
+ seenInclude.add(`${file}>${target}`);
845
+ // The include is pasted into the includer — evaluate its conditionals in
846
+ // the includer's defined set (a no-op when it has none). Re-parse the
847
+ // included file's OWN macros from that resolved text so a macro it defines
848
+ // conditionally (vim's `EXCMD`, whose plain last-wins parse picks the enum
849
+ // arm) overrides with the ARM THAT IS ACTUALLY ACTIVE here.
850
+ const text = evalConditionals(incSrc, defined);
851
+ const incEnv = new Map(env);
852
+ for (const [k, v] of parseFunctionMacros(text))
853
+ incEnv.set(k, v);
854
+ const incObjEnv = new Map(objEnv);
855
+ for (const [k, v] of parseObjectMacros(text))
856
+ incObjEnv.set(k, v);
857
+ processUnit({ text, file: target, env: incEnv, objEnv: incObjEnv });
858
+ }
832
859
  }
833
860
  // ---- receiver-type resolution within a function's source ----
834
861
  // `(?:struct )?TYPE [*]recv` declared in the params or body → TYPE (if a known
@@ -882,21 +909,26 @@ function cFnPointerDispatchEdges(queries, ctx) {
882
909
  // a fixpoint so a hook slot inherits a registry field's handlers.
883
910
  const FIELD_ASSIGN_RE = /(\w+)\s*(?:->|\.)\s*(\w+)\s*=\s*(\w+)\s*(?:->|\.)\s*(\w+)/g;
884
911
  const propagations = [];
885
- for (const fn of cFns) {
886
- const s = srcCache.get(fn.filePath);
887
- if (!s)
888
- continue;
889
- const body = sliceLines(s, fn.startLine, fn.endLine);
890
- if (!body.includes('='))
912
+ for (const file of files) {
913
+ await tick();
914
+ const s = src(file);
915
+ if (!s || !s.includes('='))
891
916
  continue;
892
- FIELD_ASSIGN_RE.lastIndex = 0;
893
- let m;
894
- while ((m = FIELD_ASSIGN_RE.exec(body))) {
895
- const [, lrecv, lfield, rrecv, rfield] = m;
896
- const lt = recvTypeIn(body, lrecv);
897
- const rt = recvTypeIn(body, rrecv);
898
- if (lt && rt && fnPtrFieldOf(lt, lfield) && fnPtrFieldOf(rt, rfield)) {
899
- propagations.push({ to: `${lt}.${lfield}`, from: `${rt}.${rfield}` });
917
+ for (const fn of ctx.getNodesInFile(file)) {
918
+ if (!FN_KINDS.has(fn.kind))
919
+ continue;
920
+ const body = sliceLines(s, fn.startLine, fn.endLine);
921
+ if (!body.includes('='))
922
+ continue;
923
+ FIELD_ASSIGN_RE.lastIndex = 0;
924
+ let m;
925
+ while ((m = FIELD_ASSIGN_RE.exec(body))) {
926
+ const [, lrecv, lfield, rrecv, rfield] = m;
927
+ const lt = recvTypeIn(body, lrecv);
928
+ const rt = recvTypeIn(body, rrecv);
929
+ if (lt && rt && fnPtrFieldOf(lt, lfield) && fnPtrFieldOf(rt, rfield)) {
930
+ propagations.push({ to: `${lt}.${lfield}`, from: `${rt}.${rfield}` });
931
+ }
900
932
  }
901
933
  }
902
934
  }
@@ -934,76 +966,42 @@ function cFnPointerDispatchEdges(queries, ctx) {
934
966
  const ARRAY_DISPATCH_RE = /(?:\(\s*\*\s*)?\b(\w+)\s*\[[^\][]*\]\s*\)?\s*\(/g;
935
967
  const edges = [];
936
968
  const seen = new Set();
937
- for (const fn of cFns) {
938
- const s = srcCache.get(fn.filePath);
969
+ for (const file of files) {
970
+ await tick();
971
+ const s = src(file);
939
972
  if (!s)
940
973
  continue;
941
- const body = sliceLines(s, fn.startLine, fn.endLine);
942
- DISPATCH_RE.lastIndex = 0;
943
- let m;
944
- let added = 0;
945
- while ((m = DISPATCH_RE.exec(body)) && added < FANOUT_CAP) {
946
- const baseChain = m[1].replace(/\s*(?:->|\.)\s*$/, '').trim(); // receiver, minus the trailing arrow
947
- const field = m[2];
948
- const owners = fieldToStructs.get(field);
949
- if (!owners || owners.size === 0)
950
- continue;
951
- // 1) resolve the receiver chain's struct type precisely (handles c->cmd->proc);
952
- // 2) else the last segment as a simple local/param of a fn-pointer-bearing struct;
953
- // 3) else fall back to a field name that belongs to exactly one struct.
954
- let struct = resolveChainType(body, baseChain);
955
- if (!struct || !owners.has(struct)) {
956
- const lastSeg = baseChain.replace(/\s*\[[^\]]*\]/g, '').split(/\s*(?:->|\.)\s*/).pop();
957
- const t = recvTypeIn(body, lastSeg);
958
- struct = t && owners.has(t) ? t : null;
959
- }
960
- if (!struct || !owners.has(struct))
961
- struct = owners.size === 1 ? [...owners][0] : null;
962
- if (!struct)
974
+ for (const fn of ctx.getNodesInFile(file)) {
975
+ if (!FN_KINDS.has(fn.kind))
963
976
  continue;
964
- const targets = reg.get(`${struct}.${field}`);
965
- if (!targets)
966
- continue;
967
- const line = fn.startLine + body.slice(0, m.index).split('\n').length - 1;
968
- for (const tid of targets) {
969
- if (tid === fn.id)
970
- continue;
971
- const key = `${fn.id}>${tid}`;
972
- if (seen.has(key))
977
+ const body = sliceLines(s, fn.startLine, fn.endLine);
978
+ DISPATCH_RE.lastIndex = 0;
979
+ let m;
980
+ let added = 0;
981
+ while ((m = DISPATCH_RE.exec(body)) && added < FANOUT_CAP) {
982
+ const baseChain = m[1].replace(/\s*(?:->|\.)\s*$/, '').trim(); // receiver, minus the trailing arrow
983
+ const field = m[2];
984
+ const owners = fieldToStructs.get(field);
985
+ if (!owners || owners.size === 0)
973
986
  continue;
974
- seen.add(key);
975
- edges.push({
976
- source: fn.id,
977
- target: tid,
978
- kind: 'calls',
979
- line,
980
- provenance: 'heuristic',
981
- metadata: {
982
- synthesizedBy: 'fn-pointer-dispatch',
983
- via: `${struct}.${field}`,
984
- registeredAt: `${fn.filePath}:${line}`,
985
- },
986
- });
987
- if (++added >= FANOUT_CAP)
988
- break;
989
- }
990
- }
991
- // ---- bare array-of-fn-pointers dispatch (`tbl[i](…)`) ----
992
- if (arrayReg.size && added < FANOUT_CAP) {
993
- ARRAY_DISPATCH_RE.lastIndex = 0;
994
- while ((m = ARRAY_DISPATCH_RE.exec(body)) && added < FANOUT_CAP) {
995
- const entries = arrayReg.get(m[1]);
996
- if (!entries)
987
+ // 1) resolve the receiver chain's struct type precisely (handles c->cmd->proc);
988
+ // 2) else the last segment as a simple local/param of a fn-pointer-bearing struct;
989
+ // 3) else fall back to a field name that belongs to exactly one struct.
990
+ let struct = resolveChainType(body, baseChain);
991
+ if (!struct || !owners.has(struct)) {
992
+ const lastSeg = baseChain.replace(/\s*\[[^\]]*\]/g, '').split(/\s*(?:->|\.)\s*/).pop();
993
+ const t = recvTypeIn(body, lastSeg);
994
+ struct = t && owners.has(t) ? t : null;
995
+ }
996
+ if (!struct || !owners.has(struct))
997
+ struct = owners.size === 1 ? [...owners][0] : null;
998
+ if (!struct)
997
999
  continue;
998
- // Same-file table wins on a name collision (two file-local `opcodes`);
999
- // a unique name resolves cross-file; otherwise ambiguous — bail.
1000
- const ids = entries.length === 1
1001
- ? entries[0].ids
1002
- : (entries.find((e) => e.file === fn.filePath)?.ids ?? null);
1003
- if (!ids)
1000
+ const targets = reg.get(`${struct}.${field}`);
1001
+ if (!targets)
1004
1002
  continue;
1005
1003
  const line = fn.startLine + body.slice(0, m.index).split('\n').length - 1;
1006
- for (const tid of ids) {
1004
+ for (const tid of targets) {
1007
1005
  if (tid === fn.id)
1008
1006
  continue;
1009
1007
  const key = `${fn.id}>${tid}`;
@@ -1018,7 +1016,7 @@ function cFnPointerDispatchEdges(queries, ctx) {
1018
1016
  provenance: 'heuristic',
1019
1017
  metadata: {
1020
1018
  synthesizedBy: 'fn-pointer-dispatch',
1021
- via: `${m[1]}[]`,
1019
+ via: `${struct}.${field}`,
1022
1020
  registeredAt: `${fn.filePath}:${line}`,
1023
1021
  },
1024
1022
  });
@@ -1026,13 +1024,47 @@ function cFnPointerDispatchEdges(queries, ctx) {
1026
1024
  break;
1027
1025
  }
1028
1026
  }
1027
+ // ---- bare array-of-fn-pointers dispatch (`tbl[i](…)`) ----
1028
+ if (arrayReg.size && added < FANOUT_CAP) {
1029
+ ARRAY_DISPATCH_RE.lastIndex = 0;
1030
+ while ((m = ARRAY_DISPATCH_RE.exec(body)) && added < FANOUT_CAP) {
1031
+ const entries = arrayReg.get(m[1]);
1032
+ if (!entries)
1033
+ continue;
1034
+ // Same-file table wins on a name collision (two file-local `opcodes`);
1035
+ // a unique name resolves cross-file; otherwise ambiguous — bail.
1036
+ const ids = entries.length === 1
1037
+ ? entries[0].ids
1038
+ : (entries.find((e) => e.file === fn.filePath)?.ids ?? null);
1039
+ if (!ids)
1040
+ continue;
1041
+ const line = fn.startLine + body.slice(0, m.index).split('\n').length - 1;
1042
+ for (const tid of ids) {
1043
+ if (tid === fn.id)
1044
+ continue;
1045
+ const key = `${fn.id}>${tid}`;
1046
+ if (seen.has(key))
1047
+ continue;
1048
+ seen.add(key);
1049
+ edges.push({
1050
+ source: fn.id,
1051
+ target: tid,
1052
+ kind: 'calls',
1053
+ line,
1054
+ provenance: 'heuristic',
1055
+ metadata: {
1056
+ synthesizedBy: 'fn-pointer-dispatch',
1057
+ via: `${m[1]}[]`,
1058
+ registeredAt: `${fn.filePath}:${line}`,
1059
+ },
1060
+ });
1061
+ if (++added >= FANOUT_CAP)
1062
+ break;
1063
+ }
1064
+ }
1065
+ }
1029
1066
  }
1030
1067
  }
1031
1068
  return edges;
1032
1069
  }
1033
- /** C/C++ function + method nodes, streamed (memory-safe on symbol-dense repos). */
1034
- function* iterateFns(queries) {
1035
- yield* queries.iterateNodesByKind('function');
1036
- yield* queries.iterateNodesByKind('method');
1037
- }
1038
1070
  //# sourceMappingURL=c-fnptr-synthesizer.js.map