@aroman22/codegraph-vba-darwin-arm64 1.15.0 → 1.16.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 (213) hide show
  1. package/lib/dist/bin/codegraph.js +13 -7
  2. package/lib/dist/bin/codegraph.js.map +1 -1
  3. package/lib/dist/bin/daemon-release.d.ts +7 -0
  4. package/lib/dist/bin/daemon-release.d.ts.map +1 -0
  5. package/lib/dist/bin/daemon-release.js +18 -0
  6. package/lib/dist/bin/daemon-release.js.map +1 -0
  7. package/lib/dist/db/queries.d.ts +30 -0
  8. package/lib/dist/db/queries.d.ts.map +1 -1
  9. package/lib/dist/db/queries.js +51 -1
  10. package/lib/dist/db/queries.js.map +1 -1
  11. package/lib/dist/extraction/access-erd-extractor.d.ts +57 -0
  12. package/lib/dist/extraction/access-erd-extractor.d.ts.map +1 -0
  13. package/lib/dist/extraction/access-erd-extractor.js +389 -0
  14. package/lib/dist/extraction/access-erd-extractor.js.map +1 -0
  15. package/lib/dist/extraction/extraction-version.d.ts +1 -1
  16. package/lib/dist/extraction/extraction-version.js +1 -1
  17. package/lib/dist/extraction/grammars.d.ts +20 -0
  18. package/lib/dist/extraction/grammars.d.ts.map +1 -1
  19. package/lib/dist/extraction/grammars.js +34 -0
  20. package/lib/dist/extraction/grammars.js.map +1 -1
  21. package/lib/dist/extraction/index.d.ts +1 -0
  22. package/lib/dist/extraction/index.d.ts.map +1 -1
  23. package/lib/dist/extraction/index.js +371 -351
  24. package/lib/dist/extraction/index.js.map +1 -1
  25. package/lib/dist/extraction/parse-pool.d.ts +8 -3
  26. package/lib/dist/extraction/parse-pool.d.ts.map +1 -1
  27. package/lib/dist/extraction/parse-pool.js +1 -2
  28. package/lib/dist/extraction/parse-pool.js.map +1 -1
  29. package/lib/dist/extraction/parse-worker.js +7 -6
  30. package/lib/dist/extraction/parse-worker.js.map +1 -1
  31. package/lib/dist/extraction/sql-query-extractor.d.ts +16 -14
  32. package/lib/dist/extraction/sql-query-extractor.d.ts.map +1 -1
  33. package/lib/dist/extraction/sql-query-extractor.js +43 -31
  34. package/lib/dist/extraction/sql-query-extractor.js.map +1 -1
  35. package/lib/dist/extraction/sql-table-scan.d.ts +184 -0
  36. package/lib/dist/extraction/sql-table-scan.d.ts.map +1 -0
  37. package/lib/dist/extraction/sql-table-scan.js +288 -0
  38. package/lib/dist/extraction/sql-table-scan.js.map +1 -0
  39. package/lib/dist/extraction/tree-sitter.d.ts +10 -1
  40. package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
  41. package/lib/dist/extraction/tree-sitter.js +34 -3
  42. package/lib/dist/extraction/tree-sitter.js.map +1 -1
  43. package/lib/dist/extraction/vba/call-sweep.d.ts +2 -7
  44. package/lib/dist/extraction/vba/call-sweep.d.ts.map +1 -1
  45. package/lib/dist/extraction/vba/call-sweep.js +144 -90
  46. package/lib/dist/extraction/vba/call-sweep.js.map +1 -1
  47. package/lib/dist/extraction/vba/calls.d.ts +24 -4
  48. package/lib/dist/extraction/vba/calls.d.ts.map +1 -1
  49. package/lib/dist/extraction/vba/calls.js +42 -12
  50. package/lib/dist/extraction/vba/calls.js.map +1 -1
  51. package/lib/dist/extraction/vba/constants.d.ts +7 -14
  52. package/lib/dist/extraction/vba/constants.d.ts.map +1 -1
  53. package/lib/dist/extraction/vba/constants.js +8 -36
  54. package/lib/dist/extraction/vba/constants.js.map +1 -1
  55. package/lib/dist/extraction/vba/context.d.ts +443 -7
  56. package/lib/dist/extraction/vba/context.d.ts.map +1 -1
  57. package/lib/dist/extraction/vba/context.js +325 -29
  58. package/lib/dist/extraction/vba/context.js.map +1 -1
  59. package/lib/dist/extraction/vba/controls.d.ts +18 -1
  60. package/lib/dist/extraction/vba/controls.d.ts.map +1 -1
  61. package/lib/dist/extraction/vba/controls.js +99 -27
  62. package/lib/dist/extraction/vba/controls.js.map +1 -1
  63. package/lib/dist/extraction/vba/declarations.d.ts +1 -6
  64. package/lib/dist/extraction/vba/declarations.d.ts.map +1 -1
  65. package/lib/dist/extraction/vba/declarations.js +5 -39
  66. package/lib/dist/extraction/vba/declarations.js.map +1 -1
  67. package/lib/dist/extraction/vba/dims.d.ts +11 -7
  68. package/lib/dist/extraction/vba/dims.d.ts.map +1 -1
  69. package/lib/dist/extraction/vba/dims.js +265 -29
  70. package/lib/dist/extraction/vba/dims.js.map +1 -1
  71. package/lib/dist/extraction/vba/docmd.d.ts +29 -2
  72. package/lib/dist/extraction/vba/docmd.d.ts.map +1 -1
  73. package/lib/dist/extraction/vba/docmd.js +297 -18
  74. package/lib/dist/extraction/vba/docmd.js.map +1 -1
  75. package/lib/dist/extraction/vba/enums-consts.d.ts +7 -14
  76. package/lib/dist/extraction/vba/enums-consts.d.ts.map +1 -1
  77. package/lib/dist/extraction/vba/enums-consts.js +11 -45
  78. package/lib/dist/extraction/vba/enums-consts.js.map +1 -1
  79. package/lib/dist/extraction/vba/error-channel.d.ts +57 -0
  80. package/lib/dist/extraction/vba/error-channel.d.ts.map +1 -0
  81. package/lib/dist/extraction/vba/error-channel.js +128 -0
  82. package/lib/dist/extraction/vba/error-channel.js.map +1 -0
  83. package/lib/dist/extraction/vba/errors.d.ts +64 -0
  84. package/lib/dist/extraction/vba/errors.d.ts.map +1 -0
  85. package/lib/dist/extraction/vba/errors.js +775 -0
  86. package/lib/dist/extraction/vba/errors.js.map +1 -0
  87. package/lib/dist/extraction/vba/filesystem-statements.d.ts +23 -0
  88. package/lib/dist/extraction/vba/filesystem-statements.d.ts.map +1 -0
  89. package/lib/dist/extraction/vba/filesystem-statements.js +70 -0
  90. package/lib/dist/extraction/vba/filesystem-statements.js.map +1 -0
  91. package/lib/dist/extraction/vba/implements.d.ts +1 -6
  92. package/lib/dist/extraction/vba/implements.d.ts.map +1 -1
  93. package/lib/dist/extraction/vba/implements.js +1 -22
  94. package/lib/dist/extraction/vba/implements.js.map +1 -1
  95. package/lib/dist/extraction/vba/labels.d.ts +26 -0
  96. package/lib/dist/extraction/vba/labels.d.ts.map +1 -0
  97. package/lib/dist/extraction/vba/labels.js +180 -0
  98. package/lib/dist/extraction/vba/labels.js.map +1 -0
  99. package/lib/dist/extraction/vba/module-vars.d.ts +36 -0
  100. package/lib/dist/extraction/vba/module-vars.d.ts.map +1 -0
  101. package/lib/dist/extraction/vba/module-vars.js +145 -0
  102. package/lib/dist/extraction/vba/module-vars.js.map +1 -0
  103. package/lib/dist/extraction/vba/options.d.ts +88 -0
  104. package/lib/dist/extraction/vba/options.d.ts.map +1 -0
  105. package/lib/dist/extraction/vba/options.js +39 -0
  106. package/lib/dist/extraction/vba/options.js.map +1 -0
  107. package/lib/dist/extraction/vba/parameters.d.ts +35 -0
  108. package/lib/dist/extraction/vba/parameters.d.ts.map +1 -0
  109. package/lib/dist/extraction/vba/parameters.js +110 -0
  110. package/lib/dist/extraction/vba/parameters.js.map +1 -0
  111. package/lib/dist/extraction/vba/procedures.d.ts +1 -9
  112. package/lib/dist/extraction/vba/procedures.d.ts.map +1 -1
  113. package/lib/dist/extraction/vba/procedures.js +174 -112
  114. package/lib/dist/extraction/vba/procedures.js.map +1 -1
  115. package/lib/dist/extraction/vba/rules.d.ts +10 -5
  116. package/lib/dist/extraction/vba/rules.d.ts.map +1 -1
  117. package/lib/dist/extraction/vba/rules.js +19 -0
  118. package/lib/dist/extraction/vba/rules.js.map +1 -1
  119. package/lib/dist/extraction/vba/runtime-objects.d.ts +59 -0
  120. package/lib/dist/extraction/vba/runtime-objects.d.ts.map +1 -0
  121. package/lib/dist/extraction/vba/runtime-objects.js +106 -0
  122. package/lib/dist/extraction/vba/runtime-objects.js.map +1 -0
  123. package/lib/dist/extraction/vba/signature.d.ts +57 -0
  124. package/lib/dist/extraction/vba/signature.d.ts.map +1 -0
  125. package/lib/dist/extraction/vba/signature.js +164 -0
  126. package/lib/dist/extraction/vba/signature.js.map +1 -0
  127. package/lib/dist/extraction/vba/sql-wrapper.d.ts +76 -3
  128. package/lib/dist/extraction/vba/sql-wrapper.d.ts.map +1 -1
  129. package/lib/dist/extraction/vba/sql-wrapper.js +725 -151
  130. package/lib/dist/extraction/vba/sql-wrapper.js.map +1 -1
  131. package/lib/dist/extraction/vba/tempvars.d.ts.map +1 -1
  132. package/lib/dist/extraction/vba/tempvars.js +4 -0
  133. package/lib/dist/extraction/vba/tempvars.js.map +1 -1
  134. package/lib/dist/extraction/vba/text-utils.d.ts +62 -2
  135. package/lib/dist/extraction/vba/text-utils.d.ts.map +1 -1
  136. package/lib/dist/extraction/vba/text-utils.js +84 -6
  137. package/lib/dist/extraction/vba/text-utils.js.map +1 -1
  138. package/lib/dist/extraction/vba-extractor.d.ts +18 -1
  139. package/lib/dist/extraction/vba-extractor.d.ts.map +1 -1
  140. package/lib/dist/extraction/vba-extractor.js +111 -16
  141. package/lib/dist/extraction/vba-extractor.js.map +1 -1
  142. package/lib/dist/extraction/vba-form-extractor.d.ts +28 -14
  143. package/lib/dist/extraction/vba-form-extractor.d.ts.map +1 -1
  144. package/lib/dist/extraction/vba-form-extractor.js +56 -21
  145. package/lib/dist/extraction/vba-form-extractor.js.map +1 -1
  146. package/lib/dist/extraction/vba-preprocess.d.ts +89 -3
  147. package/lib/dist/extraction/vba-preprocess.d.ts.map +1 -1
  148. package/lib/dist/extraction/vba-preprocess.js +221 -57
  149. package/lib/dist/extraction/vba-preprocess.js.map +1 -1
  150. package/lib/dist/extraction/vba-source.d.ts +0 -10
  151. package/lib/dist/extraction/vba-source.d.ts.map +1 -1
  152. package/lib/dist/extraction/vba-source.js +0 -13
  153. package/lib/dist/extraction/vba-source.js.map +1 -1
  154. package/lib/dist/extraction/vba-test-manifest-extractor.d.ts +0 -6
  155. package/lib/dist/extraction/vba-test-manifest-extractor.d.ts.map +1 -1
  156. package/lib/dist/extraction/vba-test-manifest-extractor.js +0 -1
  157. package/lib/dist/extraction/vba-test-manifest-extractor.js.map +1 -1
  158. package/lib/dist/index.d.ts.map +1 -1
  159. package/lib/dist/index.js +9 -0
  160. package/lib/dist/index.js.map +1 -1
  161. package/lib/dist/mcp/daemon-manager.d.ts.map +1 -1
  162. package/lib/dist/mcp/daemon-manager.js +7 -3
  163. package/lib/dist/mcp/daemon-manager.js.map +1 -1
  164. package/lib/dist/mcp/daemon-paths.d.ts +6 -0
  165. package/lib/dist/mcp/daemon-paths.d.ts.map +1 -1
  166. package/lib/dist/mcp/daemon-paths.js +15 -0
  167. package/lib/dist/mcp/daemon-paths.js.map +1 -1
  168. package/lib/dist/mcp/daemon-registry.d.ts +82 -7
  169. package/lib/dist/mcp/daemon-registry.d.ts.map +1 -1
  170. package/lib/dist/mcp/daemon-registry.js +557 -45
  171. package/lib/dist/mcp/daemon-registry.js.map +1 -1
  172. package/lib/dist/mcp/daemon-watchdog.d.ts +12 -0
  173. package/lib/dist/mcp/daemon-watchdog.d.ts.map +1 -1
  174. package/lib/dist/mcp/daemon-watchdog.js +37 -7
  175. package/lib/dist/mcp/daemon-watchdog.js.map +1 -1
  176. package/lib/dist/mcp/daemon.d.ts +20 -1
  177. package/lib/dist/mcp/daemon.d.ts.map +1 -1
  178. package/lib/dist/mcp/daemon.js +123 -30
  179. package/lib/dist/mcp/daemon.js.map +1 -1
  180. package/lib/dist/mcp/index.d.ts.map +1 -1
  181. package/lib/dist/mcp/index.js +13 -2
  182. package/lib/dist/mcp/index.js.map +1 -1
  183. package/lib/dist/mcp/proxy.d.ts +32 -0
  184. package/lib/dist/mcp/proxy.d.ts.map +1 -1
  185. package/lib/dist/mcp/proxy.js +163 -31
  186. package/lib/dist/mcp/proxy.js.map +1 -1
  187. package/lib/dist/mcp/tools.d.ts.map +1 -1
  188. package/lib/dist/mcp/tools.js +29 -1
  189. package/lib/dist/mcp/tools.js.map +1 -1
  190. package/lib/dist/project-config.d.ts +43 -2
  191. package/lib/dist/project-config.d.ts.map +1 -1
  192. package/lib/dist/project-config.js +86 -2
  193. package/lib/dist/project-config.js.map +1 -1
  194. package/lib/dist/resolution/index.d.ts +47 -2
  195. package/lib/dist/resolution/index.d.ts.map +1 -1
  196. package/lib/dist/resolution/index.js +160 -5
  197. package/lib/dist/resolution/index.js.map +1 -1
  198. package/lib/dist/resolution/name-matcher.d.ts +25 -0
  199. package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
  200. package/lib/dist/resolution/name-matcher.js +59 -0
  201. package/lib/dist/resolution/name-matcher.js.map +1 -1
  202. package/lib/dist/resolution/vba-runtime-objects.d.ts +11 -11
  203. package/lib/dist/resolution/vba-runtime-objects.d.ts.map +1 -1
  204. package/lib/dist/resolution/vba-runtime-objects.js +18 -38
  205. package/lib/dist/resolution/vba-runtime-objects.js.map +1 -1
  206. package/lib/dist/types.d.ts +13 -5
  207. package/lib/dist/types.d.ts.map +1 -1
  208. package/lib/dist/types.js +17 -0
  209. package/lib/dist/types.js.map +1 -1
  210. package/lib/node_modules/.modules.yaml +1 -1
  211. package/lib/node_modules/.pnpm-workspace-state-v1.json +1 -1
  212. package/lib/package.json +2 -1
  213. package/package.json +1 -1
@@ -1502,9 +1502,22 @@ class ExtractionOrchestrator {
1502
1502
  // Issue #152: pull the optional `vba.maxRaiseFanout` knob from the
1503
1503
  // same `codegraph.json` → `vba` block the targets live in. The VBA
1504
1504
  // extractor applies the per-file fanout gate at this threshold.
1505
+ // Issue #243: the knobs travel as ONE structured-cloneable options object
1506
+ // from here to the extractor, on the pool path and the in-process path
1507
+ // alike, so a new knob is a field here instead of a new positional in
1508
+ // five signatures.
1505
1509
  const vbaConfig = (0, project_config_1.loadVbaConfig)(this.rootDir);
1506
- const vbaTargets = vbaConfig.targets;
1507
- const maxRaiseFanout = vbaConfig.maxRaiseFanout;
1510
+ const vbaOptions = {
1511
+ targets: vbaConfig.targets,
1512
+ maxRaiseFanout: vbaConfig.maxRaiseFanout,
1513
+ // Issue #244: extra SQL execution-site receivers. Plain strings — the
1514
+ // compiled matcher is built inside the extractor because a RegExp
1515
+ // cannot cross the `structuredClone` worker boundary.
1516
+ sqlWrappers: vbaConfig.sqlWrappers,
1517
+ // Issue #261: the project's error-channel variable names. Plain strings
1518
+ // for the same worker-boundary reason as `sqlWrappers`.
1519
+ errorChannel: vbaConfig.errorChannel,
1520
+ };
1508
1521
  // Issue #154 — gate the 3 Dysflow-specific VBA sub-extractors. `true`
1509
1522
  // (the default) keeps the pre-refactor behavior; `false` opts out so
1510
1523
  // form/report/manifest/sequence files are tracked as just a `file`
@@ -1579,376 +1592,328 @@ class ExtractionOrchestrator {
1579
1592
  const parseWorkerPath = path.join(__dirname, 'parse-worker.js');
1580
1593
  const useWorker = fs.existsSync(parseWorkerPath);
1581
1594
  let pool = null;
1582
- if (useWorker) {
1583
- // CODEGRAPH_PARSE_WORKERS: explicit worker count; 1 = the old single-worker
1584
- // behaviour (the conservative rollback). Unset clamp(cores-1, 1, 8).
1585
- const poolSize = (0, parse_pool_1.resolveParsePoolSize)(process.env.CODEGRAPH_PARSE_WORKERS, os.cpus().length);
1586
- // Read each needed grammar's WASM ONCE here and hand the bytes to every
1587
- // worker, so spawns/respawns load grammars from memory instead of
1588
- // re-reading them from disk (#1231: on an HDD, respawn re-reads amplify
1589
- // the very I/O contention that caused the respawn).
1590
- const grammarBuffers = await (0, grammars_1.readGrammarWasmBytes)(neededLanguages);
1591
- pool = new parse_pool_1.ParseWorkerPool({
1592
- languages: neededLanguages,
1593
- size: poolSize,
1594
- workerScriptPath: parseWorkerPath,
1595
- recycleInterval: WORKER_RECYCLE_INTERVAL,
1596
- parseTimeoutMs: PARSE_TIMEOUT_MS,
1597
- log,
1598
- grammarBuffers,
1599
- });
1600
- log(`Parse worker pool: ${poolSize} worker(s)`);
1601
- }
1602
- else {
1603
- // In-process fallback: load grammars locally and parse on the main thread.
1604
- await (0, grammars_1.loadGrammarsForLanguages)(neededLanguages);
1605
- }
1606
- /**
1607
- * Parse one file: on the pool when available (the promise REJECTS on a worker
1608
- * crash/timeout — the caller records it and the retry pass re-attempts), or
1609
- * in-process synchronously as the no-worker fallback. The language is resolved
1610
- * here on the main thread, where the codegraph.json overrides are loaded.
1611
- */
1612
- const parseFile = (filePath, content) => {
1613
- const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1614
- // Issue #152: thread the optional `vba.maxRaiseFanout` knob through
1615
- // the worker boundary (or the in-process fallback) so every VBA
1616
- // file sees the same gate.
1617
- // Issue #154: also thread the optional `vba.dysflowExport` flag.
1618
- if (!pool)
1619
- return Promise.resolve((0, tree_sitter_1.extractFromSource)(filePath, content, language, frameworkNames, vbaTargets, maxRaiseFanout, dysflowExport));
1620
- return pool.requestParse({ filePath, content, language, frameworkNames, vbaTargets, maxRaiseFanout, dysflowExport });
1621
- };
1622
- // --- Bounded rolling-window dispatch, ordered commit ---
1623
- // Reads stay batched/parallel; parses run concurrently across the pool; the
1624
- // SQLite store stays on the main thread (it isn't thread-safe). Crucially we
1625
- // COMMIT results in original file order, not parse-completion order: the
1626
- // resolution phase (run after indexing) resolves an ambiguous reference to one
1627
- // of several same-named candidates by the nodes' DB insertion order, so a
1628
- // stable commit order keeps the resulting graph deterministic — byte-identical
1629
- // to the single-worker path — instead of drifting with parse timing. The
1630
- // `completed` buffer holds at most ~windowSize out-of-order results, so memory
1631
- // stays bounded.
1632
- const windowSize = pool ? Math.max(4, pool.size * 2) : 1;
1633
- const inFlight = new Set();
1634
- const completed = new Map();
1635
- let nextSeq = 0; // file-order sequence assigned at dispatch
1636
- let nextToStore = 0; // cursor: next sequence to commit
1637
- let aborted = false;
1638
- // Yielder for the in-order commit path: a single giant generated file's
1639
- // store is otherwise one unyielding multi-second transaction span on the
1640
- // main thread (5–14s single stalls measured on llvm-project), starving
1641
- // the #850 watchdog heartbeat on slow hardware.
1642
- const commitYield = (0, cooperative_yield_1.createYielder)();
1643
- const storeResult = async (filePath, content, stats, result) => {
1644
- processed++;
1645
- // WAL hard-cap backstop: between files (never mid-transaction), pause
1646
- // the store until the off-thread checkpoint catches up. Resolves to
1647
- // null in the normal case — a single size check, no cost.
1648
- const bp = walBackpressure?.();
1649
- if (bp)
1650
- await bp;
1651
- // Store in database on main thread (SQLite is not thread-safe)
1652
- if (result.nodes.length > 0 || result.errors.length === 0) {
1653
- const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1654
- await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
1655
- }
1656
- if (result.errors.length > 0) {
1657
- for (const err of result.errors) {
1658
- if (!err.filePath)
1659
- err.filePath = filePath;
1660
- }
1661
- errors.push(...result.errors);
1662
- }
1663
- if (result.nodes.length > 0) {
1664
- filesIndexed++;
1665
- totalNodes += result.nodes.length;
1666
- totalEdges += result.edges.length;
1667
- }
1668
- else if (result.errors.some((e) => e.severity === 'error')) {
1669
- filesErrored++;
1595
+ try {
1596
+ if (useWorker) {
1597
+ // CODEGRAPH_PARSE_WORKERS: explicit worker count; 1 = the old single-worker
1598
+ // behaviour (the conservative rollback). Unset → clamp(cores-1, 1, 8).
1599
+ const poolSize = (0, parse_pool_1.resolveParsePoolSize)(process.env.CODEGRAPH_PARSE_WORKERS, os.cpus().length);
1600
+ // Read each needed grammar's WASM ONCE here and hand the bytes to every
1601
+ // worker, so spawns/respawns load grammars from memory instead of
1602
+ // re-reading them from disk (#1231: on an HDD, respawn re-reads amplify
1603
+ // the very I/O contention that caused the respawn).
1604
+ const grammarBuffers = await (0, grammars_1.readGrammarWasmBytes)(neededLanguages);
1605
+ pool = new parse_pool_1.ParseWorkerPool({
1606
+ languages: neededLanguages,
1607
+ size: poolSize,
1608
+ workerScriptPath: parseWorkerPath,
1609
+ recycleInterval: WORKER_RECYCLE_INTERVAL,
1610
+ parseTimeoutMs: PARSE_TIMEOUT_MS,
1611
+ log,
1612
+ grammarBuffers,
1613
+ });
1614
+ log(`Parse worker pool: ${poolSize} worker(s)`);
1670
1615
  }
1671
1616
  else {
1672
- // Files with no symbols but no errors (yaml, twig, properties) are
1673
- // tracked at the file level — count them as indexed so the CLI doesn't
1674
- // misleadingly report "No files found to index".
1675
- const lang = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1676
- if ((0, grammars_1.isFileLevelOnlyLanguage)(lang)) {
1677
- filesIndexed++;
1678
- }
1679
- else {
1680
- filesSkipped++;
1681
- }
1617
+ // In-process fallback: load grammars locally and parse on the main thread.
1618
+ await (0, grammars_1.loadGrammarsForLanguages)(neededLanguages);
1682
1619
  }
1683
- onProgress?.({ phase: 'parsing', current: processed, total, currentFile: filePath });
1684
- };
1685
- const recordParseFailure = (filePath, err) => {
1686
- processed++;
1687
- filesErrored++;
1688
- errors.push({
1689
- message: err instanceof Error ? err.message : String(err),
1690
- filePath,
1691
- severity: 'error',
1692
- code: 'parse_error',
1693
- });
1694
- onProgress?.({ phase: 'parsing', current: processed, total });
1695
- };
1696
- // Commit buffered parses to the DB in file order, advancing the cursor over
1697
- // contiguous completed results. Runs after each parse settles (and once more
1698
- // after the drain). storeResult is now async (it yields between chunked
1699
- // inserts), so commits are SERIALIZED on a promise chain — concurrent parse
1700
- // completions append to the chain instead of interleaving mid-store, which
1701
- // preserves both the file-order commit invariant (#1015: resolution
1702
- // disambiguates same-named candidates by insertion order) and the
1703
- // single-writer discipline for SQLite. Errors are recorded and re-thrown
1704
- // at the drain, matching the old synchronous propagation.
1705
- let flushChain = Promise.resolve();
1706
- let flushError = null;
1707
- const flushOrdered = () => {
1708
- flushChain = flushChain.then(async () => {
1709
- if (aborted || flushError)
1710
- return;
1711
- try {
1712
- while (completed.has(nextToStore)) {
1713
- const item = completed.get(nextToStore);
1714
- completed.delete(nextToStore);
1715
- nextToStore++;
1716
- if (item.ok)
1717
- await storeResult(item.filePath, item.content, item.stats, item.result);
1718
- else
1719
- recordParseFailure(item.filePath, item.err);
1720
- }
1620
+ /**
1621
+ * Parse one file: on the pool when available (the promise REJECTS on a worker
1622
+ * crash/timeout the caller records it and the retry pass re-attempts), or
1623
+ * in-process synchronously as the no-worker fallback. The language is resolved
1624
+ * here on the main thread, where the codegraph.json overrides are loaded.
1625
+ */
1626
+ const parseFile = (filePath, content) => {
1627
+ const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1628
+ // Issue #152: thread the optional `vba.maxRaiseFanout` knob through
1629
+ // the worker boundary (or the in-process fallback) so every VBA
1630
+ // file sees the same gate.
1631
+ // Issue #154: also thread the optional `vba.dysflowExport` flag.
1632
+ // Issue #243: both knobs now ride in the `vbaOptions` object.
1633
+ if (!pool)
1634
+ return Promise.resolve((0, tree_sitter_1.extractFromSource)(filePath, content, language, frameworkNames, undefined, undefined, dysflowExport, vbaOptions));
1635
+ return pool.requestParse({ filePath, content, language, frameworkNames, vbaOptions, dysflowExport });
1636
+ };
1637
+ // --- Bounded rolling-window dispatch, ordered commit ---
1638
+ // Reads stay batched/parallel; parses run concurrently across the pool; the
1639
+ // SQLite store stays on the main thread (it isn't thread-safe). Crucially we
1640
+ // COMMIT results in original file order, not parse-completion order: the
1641
+ // resolution phase (run after indexing) resolves an ambiguous reference to one
1642
+ // of several same-named candidates by the nodes' DB insertion order, so a
1643
+ // stable commit order keeps the resulting graph deterministic — byte-identical
1644
+ // to the single-worker path — instead of drifting with parse timing. The
1645
+ // `completed` buffer holds at most ~windowSize out-of-order results, so memory
1646
+ // stays bounded.
1647
+ const windowSize = pool ? Math.max(4, pool.size * 2) : 1;
1648
+ const inFlight = new Set();
1649
+ const completed = new Map();
1650
+ let nextSeq = 0; // file-order sequence assigned at dispatch
1651
+ let nextToStore = 0; // cursor: next sequence to commit
1652
+ let aborted = false;
1653
+ // Yielder for the in-order commit path: a single giant generated file's
1654
+ // store is otherwise one unyielding multi-second transaction span on the
1655
+ // main thread (5–14s single stalls measured on llvm-project), starving
1656
+ // the #850 watchdog heartbeat on slow hardware.
1657
+ const commitYield = (0, cooperative_yield_1.createYielder)();
1658
+ const storeResult = async (filePath, content, stats, result) => {
1659
+ processed++;
1660
+ // WAL hard-cap backstop: between files (never mid-transaction), pause
1661
+ // the store until the off-thread checkpoint catches up. Resolves to
1662
+ // null in the normal case — a single size check, no cost.
1663
+ const bp = walBackpressure?.();
1664
+ if (bp)
1665
+ await bp;
1666
+ // Store in database on main thread (SQLite is not thread-safe)
1667
+ if (result.nodes.length > 0 || result.errors.length === 0) {
1668
+ const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1669
+ await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
1721
1670
  }
1722
- catch (err) {
1723
- flushError = err;
1671
+ if (result.errors.length > 0) {
1672
+ for (const err of result.errors) {
1673
+ if (!err.filePath)
1674
+ err.filePath = filePath;
1675
+ }
1676
+ errors.push(...result.errors);
1724
1677
  }
1725
- });
1726
- return flushChain;
1727
- };
1728
- // Dispatch one file's parse (parses run concurrently across the pool), tagged
1729
- // with its file-order sequence so flushOrdered commits results in order. The
1730
- // backpressure below bounds how far parsing runs ahead of the in-order commit.
1731
- const feed = async (filePath, content, stats) => {
1732
- const seq = nextSeq++;
1733
- const p = (async () => {
1734
- try {
1735
- const result = await parseFile(filePath, content);
1736
- completed.set(seq, { ok: true, filePath, content, stats, result });
1678
+ if (result.nodes.length > 0) {
1679
+ filesIndexed++;
1680
+ totalNodes += result.nodes.length;
1681
+ totalEdges += result.edges.length;
1737
1682
  }
1738
- catch (parseErr) {
1739
- completed.set(seq, { ok: false, filePath, err: parseErr });
1683
+ else if (result.errors.some((e) => e.severity === 'error')) {
1684
+ filesErrored++;
1740
1685
  }
1741
- flushOrdered();
1742
- })();
1743
- const tracked = p.finally(() => { inFlight.delete(tracked); });
1744
- inFlight.add(tracked);
1745
- // Backpressure on the dispatched-but-not-yet-committed count (in-flight +
1746
- // buffered), not just in-flight: a slow file sitting at the commit cursor
1747
- // lets later parses finish and buffer, which would otherwise grow without
1748
- // bound. Wait for parses to settle (each may advance the cursor) until the
1749
- // window has room. When nothing is in flight but the window is still full,
1750
- // the async commit chain is what's behind — await it so the cursor
1751
- // advances (buffered items hold whole file contents, so this bound is
1752
- // load-bearing for memory).
1753
- while (nextSeq - nextToStore >= windowSize) {
1754
- if (inFlight.size > 0)
1755
- await Promise.race(inFlight);
1756
- else
1757
- await flushOrdered();
1758
- }
1759
- };
1760
- for (let i = 0; i < files.length; i += FILE_IO_BATCH_SIZE) {
1761
- if (signal?.aborted) {
1762
- aborted = true;
1763
- break;
1764
- }
1765
- const batch = files.slice(i, i + FILE_IO_BATCH_SIZE);
1766
- // Read files in parallel (with path validation before any I/O)
1767
- const fileContents = await Promise.all(batch.map(async (fp) => {
1768
- try {
1769
- // Indexing read: follow in-root symlinks the directory walk already
1770
- // descended into (the `../` guard still applies) so files reached
1771
- // via an in-root symlink-to-outside still index (#935).
1772
- const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, fp, { allowSymlinkEscape: true });
1773
- if (!fullPath) {
1774
- (0, errors_1.logWarn)('Path traversal blocked in batch reader', { filePath: fp });
1775
- return { filePath: fp, content: null, stats: null, error: new Error('Path traversal blocked') };
1686
+ else {
1687
+ // Files with no symbols but no errors (yaml, twig, properties) are
1688
+ // tracked at the file level count them as indexed so the CLI doesn't
1689
+ // misleadingly report "No files found to index".
1690
+ const lang = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1691
+ if ((0, grammars_1.isFileLevelOnlyLanguage)(lang)) {
1692
+ filesIndexed++;
1693
+ }
1694
+ else {
1695
+ filesSkipped++;
1776
1696
  }
1777
- const content = await readForExtraction(fullPath);
1778
- const stats = await fsp.stat(fullPath);
1779
- return { filePath: fp, content, stats, error: null };
1780
1697
  }
1781
- catch (err) {
1782
- return { filePath: fp, content: null, stats: null, error: err };
1698
+ onProgress?.({ phase: 'parsing', current: processed, total, currentFile: filePath });
1699
+ };
1700
+ const recordParseFailure = (filePath, err) => {
1701
+ processed++;
1702
+ filesErrored++;
1703
+ errors.push({
1704
+ message: err instanceof Error ? err.message : String(err),
1705
+ filePath,
1706
+ severity: 'error',
1707
+ code: 'parse_error',
1708
+ });
1709
+ onProgress?.({ phase: 'parsing', current: processed, total });
1710
+ };
1711
+ // Commit buffered parses to the DB in file order, advancing the cursor over
1712
+ // contiguous completed results. Runs after each parse settles (and once more
1713
+ // after the drain). storeResult is now async (it yields between chunked
1714
+ // inserts), so commits are SERIALIZED on a promise chain — concurrent parse
1715
+ // completions append to the chain instead of interleaving mid-store, which
1716
+ // preserves both the file-order commit invariant (#1015: resolution
1717
+ // disambiguates same-named candidates by insertion order) and the
1718
+ // single-writer discipline for SQLite. Errors are recorded and re-thrown
1719
+ // at the drain, matching the old synchronous propagation.
1720
+ let flushChain = Promise.resolve();
1721
+ let flushError = null;
1722
+ const flushOrdered = () => {
1723
+ flushChain = flushChain.then(async () => {
1724
+ if (aborted || flushError)
1725
+ return;
1726
+ try {
1727
+ while (completed.has(nextToStore)) {
1728
+ const item = completed.get(nextToStore);
1729
+ completed.delete(nextToStore);
1730
+ nextToStore++;
1731
+ if (item.ok)
1732
+ await storeResult(item.filePath, item.content, item.stats, item.result);
1733
+ else
1734
+ recordParseFailure(item.filePath, item.err);
1735
+ }
1736
+ }
1737
+ catch (err) {
1738
+ flushError = err;
1739
+ }
1740
+ });
1741
+ return flushChain;
1742
+ };
1743
+ // Dispatch one file's parse (parses run concurrently across the pool), tagged
1744
+ // with its file-order sequence so flushOrdered commits results in order. The
1745
+ // backpressure below bounds how far parsing runs ahead of the in-order commit.
1746
+ const feed = async (filePath, content, stats) => {
1747
+ const seq = nextSeq++;
1748
+ const p = (async () => {
1749
+ try {
1750
+ const result = await parseFile(filePath, content);
1751
+ completed.set(seq, { ok: true, filePath, content, stats, result });
1752
+ }
1753
+ catch (parseErr) {
1754
+ completed.set(seq, { ok: false, filePath, err: parseErr });
1755
+ }
1756
+ flushOrdered();
1757
+ })();
1758
+ const tracked = p.finally(() => { inFlight.delete(tracked); });
1759
+ inFlight.add(tracked);
1760
+ // Backpressure on the dispatched-but-not-yet-committed count (in-flight +
1761
+ // buffered), not just in-flight: a slow file sitting at the commit cursor
1762
+ // lets later parses finish and buffer, which would otherwise grow without
1763
+ // bound. Wait for parses to settle (each may advance the cursor) until the
1764
+ // window has room. When nothing is in flight but the window is still full,
1765
+ // the async commit chain is what's behind — await it so the cursor
1766
+ // advances (buffered items hold whole file contents, so this bound is
1767
+ // load-bearing for memory).
1768
+ while (nextSeq - nextToStore >= windowSize) {
1769
+ if (inFlight.size > 0)
1770
+ await Promise.race(inFlight);
1771
+ else
1772
+ await flushOrdered();
1783
1773
  }
1784
- }));
1785
- // Dispatch each readable file into the bounded parse window; the window
1786
- // stores results on the main thread as they arrive.
1787
- for (const { filePath, content, stats, error } of fileContents) {
1774
+ };
1775
+ for (let i = 0; i < files.length; i += FILE_IO_BATCH_SIZE) {
1788
1776
  if (signal?.aborted) {
1789
1777
  aborted = true;
1790
1778
  break;
1791
1779
  }
1792
- if (error || content === null || stats === null) {
1793
- processed++;
1794
- filesErrored++;
1795
- errors.push({
1796
- message: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
1797
- filePath,
1798
- severity: 'error',
1799
- code: 'read_error',
1800
- });
1801
- onProgress?.({ phase: 'parsing', current: processed, total });
1802
- continue;
1803
- }
1804
- // Honour MAX_FILE_SIZE. Without this check, vendored generated
1805
- // headers, minified bundles, and other multi-MB files get indexed,
1806
- // wasting WASM heap and the worker recycle budget on inputs with no
1807
- // useful symbols. The single-file extractFile path already enforces
1808
- // this; the bulk path used to silently skip the check.
1809
- if (stats.size > MAX_FILE_SIZE) {
1810
- processed++;
1811
- filesSkipped++;
1812
- errors.push({
1813
- message: `File exceeds max size (${stats.size} > ${MAX_FILE_SIZE})`,
1814
- filePath,
1815
- severity: 'warning',
1816
- code: 'size_exceeded',
1817
- });
1818
- onProgress?.({ phase: 'parsing', current: processed, total });
1819
- continue;
1820
- }
1821
- // Parse on the pool (main thread stays unblocked). Errors/timeouts are
1822
- // handled inside feed() → recordParseFailure, feeding the retry pass.
1823
- await feed(filePath, content, stats);
1824
- }
1825
- if (aborted)
1826
- break;
1827
- }
1828
- // Drain parses still in flight (skip on abort we tear down below instead),
1829
- // then commit any results the cursor hasn't reached yet.
1830
- if (!aborted) {
1831
- await Promise.all(inFlight);
1832
- await flushOrdered();
1833
- if (flushError)
1834
- throw flushError;
1835
- }
1836
- if (signal?.aborted || aborted) {
1837
- if (pool)
1838
- await pool.destroy();
1839
- return {
1840
- success: false,
1841
- filesIndexed,
1842
- filesSkipped,
1843
- filesErrored,
1844
- filesDiscovered: total,
1845
- nodesCreated: totalNodes,
1846
- edgesCreated: totalEdges,
1847
- errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
1848
- durationMs: Date.now() - startTime,
1849
- };
1850
- }
1851
- // Report 100% so the progress bar doesn't hang at 99%
1852
- onProgress?.({
1853
- phase: 'parsing',
1854
- current: total,
1855
- total,
1856
- });
1857
- // Yield so the shimmer worker's buffered stdout writes can flush.
1858
- // Worker thread stdout is proxied through the main thread's event loop,
1859
- // so synchronous work here blocks the animation from rendering.
1860
- await new Promise(resolve => setImmediate(resolve));
1861
- // Retry pass: files that failed due to WASM memory corruption may succeed
1862
- // on a fresh worker with a clean heap. Recycle before each attempt so
1863
- // every file gets the absolute cleanest WASM state possible. Timeouts are
1864
- // retried too (#1231): most are main-thread-stall artifacts, not slow
1865
- // parses, and this pass parses one file at a time with the store strictly
1866
- // after each parse resolves, so the stall window can't recur here.
1867
- const retryableErrors = errors.filter((e) => e.code === 'parse_error' && e.filePath &&
1868
- (e.message.includes('Worker exited') ||
1869
- e.message.includes('memory access out of bounds') ||
1870
- e.message.includes('timed out')));
1871
- if (retryableErrors.length > 0 && pool) {
1872
- log(`Retrying ${retryableErrors.length} files that failed due to WASM memory errors or timeouts...`);
1873
- // Fresh WASM heaps for the retry phase. A retry that still crashes its
1874
- // worker makes the pool respawn it, so later retries keep landing on clean
1875
- // workers too.
1876
- pool.recycleAll();
1877
- const stillFailing = [];
1878
- for (const errEntry of retryableErrors) {
1879
- const filePath = errEntry.filePath;
1880
- if (signal?.aborted)
1881
- break;
1882
- let content;
1883
- try {
1884
- const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
1885
- if (!fullPath)
1780
+ const batch = files.slice(i, i + FILE_IO_BATCH_SIZE);
1781
+ // Read files in parallel (with path validation before any I/O)
1782
+ const fileContents = await Promise.all(batch.map(async (fp) => {
1783
+ try {
1784
+ // Indexing read: follow in-root symlinks the directory walk already
1785
+ // descended into (the `../` guard still applies) so files reached
1786
+ // via an in-root symlink-to-outside still index (#935).
1787
+ const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, fp, { allowSymlinkEscape: true });
1788
+ if (!fullPath) {
1789
+ (0, errors_1.logWarn)('Path traversal blocked in batch reader', { filePath: fp });
1790
+ return { filePath: fp, content: null, stats: null, error: new Error('Path traversal blocked') };
1791
+ }
1792
+ const content = await readForExtraction(fullPath);
1793
+ const stats = await fsp.stat(fullPath);
1794
+ return { filePath: fp, content, stats, error: null };
1795
+ }
1796
+ catch (err) {
1797
+ return { filePath: fp, content: null, stats: null, error: err };
1798
+ }
1799
+ }));
1800
+ // Dispatch each readable file into the bounded parse window; the window
1801
+ // stores results on the main thread as they arrive.
1802
+ for (const { filePath, content, stats, error } of fileContents) {
1803
+ if (signal?.aborted) {
1804
+ aborted = true;
1805
+ break;
1806
+ }
1807
+ if (error || content === null || stats === null) {
1808
+ processed++;
1809
+ filesErrored++;
1810
+ errors.push({
1811
+ message: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
1812
+ filePath,
1813
+ severity: 'error',
1814
+ code: 'read_error',
1815
+ });
1816
+ onProgress?.({ phase: 'parsing', current: processed, total });
1886
1817
  continue;
1887
- content = await readForExtraction(fullPath);
1888
- }
1889
- catch {
1890
- continue;
1891
- }
1892
- let result;
1893
- try {
1894
- result = await parseFile(filePath, content);
1895
- }
1896
- catch {
1897
- stillFailing.push(errEntry);
1898
- continue;
1899
- }
1900
- if (result.nodes.length > 0 || result.errors.length === 0) {
1901
- const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1902
- const stats = await fsp.stat(path.join(this.rootDir, filePath));
1903
- await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
1904
- const idx = errors.indexOf(errEntry);
1905
- if (idx >= 0)
1906
- errors.splice(idx, 1);
1907
- filesErrored--;
1908
- filesIndexed++;
1909
- totalNodes += result.nodes.length;
1910
- totalEdges += result.edges.length;
1911
- log(`Retry OK: ${filePath} (${result.nodes.length} nodes)`);
1818
+ }
1819
+ // Honour MAX_FILE_SIZE. Without this check, vendored generated
1820
+ // headers, minified bundles, and other multi-MB files get indexed,
1821
+ // wasting WASM heap and the worker recycle budget on inputs with no
1822
+ // useful symbols. The single-file extractFile path already enforces
1823
+ // this; the bulk path used to silently skip the check.
1824
+ if (stats.size > MAX_FILE_SIZE) {
1825
+ processed++;
1826
+ filesSkipped++;
1827
+ errors.push({
1828
+ message: `File exceeds max size (${stats.size} > ${MAX_FILE_SIZE})`,
1829
+ filePath,
1830
+ severity: 'warning',
1831
+ code: 'size_exceeded',
1832
+ });
1833
+ onProgress?.({ phase: 'parsing', current: processed, total });
1834
+ continue;
1835
+ }
1836
+ // Parse on the pool (main thread stays unblocked). Errors/timeouts are
1837
+ // handled inside feed() → recordParseFailure, feeding the retry pass.
1838
+ await feed(filePath, content, stats);
1912
1839
  }
1840
+ if (aborted)
1841
+ break;
1913
1842
  }
1914
- // Last resort: for files that still crash on a clean worker, strip
1915
- // comment-only lines to reduce WASM memory pressure. Many compiler
1916
- // test files are 90%+ comments (CHECK directives) that don't contribute
1917
- // code nodes but consume parser memory.
1918
- if (stillFailing.length > 0) {
1919
- log(`${stillFailing.length} files still failing — retrying with comments stripped...`);
1843
+ // Drain parses still in flight (skip on abort we tear down below instead),
1844
+ // then commit any results the cursor hasn't reached yet.
1845
+ if (!aborted) {
1846
+ await Promise.all(inFlight);
1847
+ await flushOrdered();
1848
+ if (flushError)
1849
+ throw flushError;
1850
+ }
1851
+ if (signal?.aborted || aborted) {
1852
+ return {
1853
+ success: false,
1854
+ filesIndexed,
1855
+ filesSkipped,
1856
+ filesErrored,
1857
+ filesDiscovered: total,
1858
+ nodesCreated: totalNodes,
1859
+ edgesCreated: totalEdges,
1860
+ errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
1861
+ durationMs: Date.now() - startTime,
1862
+ };
1863
+ }
1864
+ // Report 100% so the progress bar doesn't hang at 99%
1865
+ onProgress?.({
1866
+ phase: 'parsing',
1867
+ current: total,
1868
+ total,
1869
+ });
1870
+ // Yield so the shimmer worker's buffered stdout writes can flush.
1871
+ // Worker thread stdout is proxied through the main thread's event loop,
1872
+ // so synchronous work here blocks the animation from rendering.
1873
+ await new Promise(resolve => setImmediate(resolve));
1874
+ // Retry pass: files that failed due to WASM memory corruption may succeed
1875
+ // on a fresh worker with a clean heap. Recycle before each attempt so
1876
+ // every file gets the absolute cleanest WASM state possible. Timeouts are
1877
+ // retried too (#1231): most are main-thread-stall artifacts, not slow
1878
+ // parses, and this pass parses one file at a time with the store strictly
1879
+ // after each parse resolves, so the stall window can't recur here.
1880
+ const retryableErrors = errors.filter((e) => e.code === 'parse_error' && e.filePath &&
1881
+ (e.message.includes('Worker exited') ||
1882
+ e.message.includes('memory access out of bounds') ||
1883
+ e.message.includes('timed out')));
1884
+ if (retryableErrors.length > 0 && pool) {
1885
+ log(`Retrying ${retryableErrors.length} files that failed due to WASM memory errors or timeouts...`);
1886
+ // Fresh WASM heaps for the retry phase. A retry that still crashes its
1887
+ // worker makes the pool respawn it, so later retries keep landing on clean
1888
+ // workers too.
1920
1889
  pool.recycleAll();
1921
- for (const errEntry of stillFailing) {
1890
+ const stillFailing = [];
1891
+ for (const errEntry of retryableErrors) {
1922
1892
  const filePath = errEntry.filePath;
1923
1893
  if (signal?.aborted)
1924
1894
  break;
1925
- let fullContent;
1895
+ let content;
1926
1896
  try {
1927
1897
  const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
1928
1898
  if (!fullPath)
1929
1899
  continue;
1930
- fullContent = await readForExtraction(fullPath);
1900
+ content = await readForExtraction(fullPath);
1931
1901
  }
1932
1902
  catch {
1933
1903
  continue;
1934
1904
  }
1935
- // Strip lines that are entirely comments (preserving line numbers
1936
- // by replacing with empty lines so node positions stay correct)
1937
- const stripped = fullContent
1938
- .split('\n')
1939
- .map(line => /^\s*\/\//.test(line) ? '' : line)
1940
- .join('\n');
1941
1905
  let result;
1942
1906
  try {
1943
- result = await parseFile(filePath, stripped);
1907
+ result = await parseFile(filePath, content);
1944
1908
  }
1945
1909
  catch {
1910
+ stillFailing.push(errEntry);
1946
1911
  continue;
1947
1912
  }
1948
1913
  if (result.nodes.length > 0 || result.errors.length === 0) {
1949
- const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
1914
+ const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1950
1915
  const stats = await fsp.stat(path.join(this.rootDir, filePath));
1951
- await this.storeExtractionResult(filePath, fullContent, language, stats, result, commitYield);
1916
+ await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
1952
1917
  const idx = errors.indexOf(errEntry);
1953
1918
  if (idx >= 0)
1954
1919
  errors.splice(idx, 1);
@@ -1956,25 +1921,75 @@ class ExtractionOrchestrator {
1956
1921
  filesIndexed++;
1957
1922
  totalNodes += result.nodes.length;
1958
1923
  totalEdges += result.edges.length;
1959
- log(`Retry (stripped) OK: ${filePath} (${result.nodes.length} nodes)`);
1924
+ log(`Retry OK: ${filePath} (${result.nodes.length} nodes)`);
1925
+ }
1926
+ }
1927
+ // Last resort: for files that still crash on a clean worker, strip
1928
+ // comment-only lines to reduce WASM memory pressure. Many compiler
1929
+ // test files are 90%+ comments (CHECK directives) that don't contribute
1930
+ // code nodes but consume parser memory.
1931
+ if (stillFailing.length > 0) {
1932
+ log(`${stillFailing.length} files still failing — retrying with comments stripped...`);
1933
+ pool.recycleAll();
1934
+ for (const errEntry of stillFailing) {
1935
+ const filePath = errEntry.filePath;
1936
+ if (signal?.aborted)
1937
+ break;
1938
+ let fullContent;
1939
+ try {
1940
+ const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
1941
+ if (!fullPath)
1942
+ continue;
1943
+ fullContent = await readForExtraction(fullPath);
1944
+ }
1945
+ catch {
1946
+ continue;
1947
+ }
1948
+ // Strip lines that are entirely comments (preserving line numbers
1949
+ // by replacing with empty lines so node positions stay correct)
1950
+ const stripped = fullContent
1951
+ .split('\n')
1952
+ .map(line => /^\s*\/\//.test(line) ? '' : line)
1953
+ .join('\n');
1954
+ let result;
1955
+ try {
1956
+ result = await parseFile(filePath, stripped);
1957
+ }
1958
+ catch {
1959
+ continue;
1960
+ }
1961
+ if (result.nodes.length > 0 || result.errors.length === 0) {
1962
+ const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
1963
+ const stats = await fsp.stat(path.join(this.rootDir, filePath));
1964
+ await this.storeExtractionResult(filePath, fullContent, language, stats, result, commitYield);
1965
+ const idx = errors.indexOf(errEntry);
1966
+ if (idx >= 0)
1967
+ errors.splice(idx, 1);
1968
+ filesErrored--;
1969
+ filesIndexed++;
1970
+ totalNodes += result.nodes.length;
1971
+ totalEdges += result.edges.length;
1972
+ log(`Retry (stripped) OK: ${filePath} (${result.nodes.length} nodes)`);
1973
+ }
1960
1974
  }
1961
1975
  }
1962
1976
  }
1977
+ return {
1978
+ success: filesIndexed > 0 || errors.filter((e) => e.severity === 'error').length === 0,
1979
+ filesIndexed,
1980
+ filesSkipped,
1981
+ filesErrored,
1982
+ filesDiscovered: total,
1983
+ nodesCreated: totalNodes,
1984
+ edgesCreated: totalEdges,
1985
+ errors,
1986
+ durationMs: Date.now() - startTime,
1987
+ };
1988
+ }
1989
+ finally {
1990
+ if (pool)
1991
+ await pool.destroy();
1963
1992
  }
1964
- // Shut down the parse worker pool.
1965
- if (pool)
1966
- await pool.destroy();
1967
- return {
1968
- success: filesIndexed > 0 || errors.filter((e) => e.severity === 'error').length === 0,
1969
- filesIndexed,
1970
- filesSkipped,
1971
- filesErrored,
1972
- filesDiscovered: total,
1973
- nodesCreated: totalNodes,
1974
- edgesCreated: totalEdges,
1975
- errors,
1976
- durationMs: Date.now() - startTime,
1977
- };
1978
1993
  }
1979
1994
  /**
1980
1995
  * Index specific files
@@ -2116,7 +2131,12 @@ class ExtractionOrchestrator {
2116
2131
  // Issue #154: also thread the `vba.dysflowExport` flag so the
2117
2132
  // single-file re-index path honours the same opt-out the full scan does.
2118
2133
  const dysflowExport = (0, project_config_1.loadDysflowExportConfig)(this.rootDir);
2119
- const result = (0, tree_sitter_1.extractFromSource)(relativePath, content, language, frameworkNames, vbaReindexConfig.targets, vbaReindexConfig.maxRaiseFanout, dysflowExport);
2134
+ const result = (0, tree_sitter_1.extractFromSource)(relativePath, content, language, frameworkNames, undefined, undefined, dysflowExport, {
2135
+ targets: vbaReindexConfig.targets,
2136
+ maxRaiseFanout: vbaReindexConfig.maxRaiseFanout,
2137
+ sqlWrappers: vbaReindexConfig.sqlWrappers,
2138
+ errorChannel: vbaReindexConfig.errorChannel,
2139
+ });
2120
2140
  // Store in database
2121
2141
  if (result.nodes.length > 0 || result.errors.length === 0) {
2122
2142
  await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());