cgraphx 2.0.2 → 2.0.6

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 (754) hide show
  1. package/dist/.claude-template/FLOW-MAP.md +13 -3
  2. package/dist/.claude-template/agents/task-history.md +186 -0
  3. package/dist/.claude-template/commands/end.md +1 -0
  4. package/dist/.claude-template/skills/cgraphx-guide/SKILL.md +5 -3
  5. package/dist/.claude-template/skills/cgraphx-guide/how-to-use.html +8 -8
  6. package/dist/.claude-template/skills/cgraphx-guide/how-to-use.md +8 -8
  7. package/dist/.claude-template/skills/clarify-requirements/SKILL.md +13 -9
  8. package/dist/.claude-template/skills/db-query/SKILL.md +19 -2
  9. package/dist/.claude-template/skills/iboc-check/SKILL.md +51 -23
  10. package/dist/.claude-template/skills/iboc-check/filler-prompt.md +3 -4
  11. package/dist/.claude-template/skills/run-api-test/SKILL.md +15 -15
  12. package/dist/.claude-template/skills/run-api-test/assets/{template-test-report.md → template-/346/216/245/345/217/243/346/265/213/350/257/225/346/212/245/345/221/212.md} +4 -4
  13. package/dist/.claude-template/skills/run-api-test/references/bru-run.md +1 -1
  14. package/dist/.claude-template/skills/run-api-test/references/db-verification.md +2 -2
  15. package/dist/.claude-template/skills/run-api-test/references/report-format.md +6 -6
  16. package/dist/.claude-template/skills/run-api-test/references/service-readiness.md +1 -1
  17. package/dist/.claude-template/skills/run-api-test/references/test-scope.md +6 -6
  18. package/dist/.claude-template/skills/run-ui-test/SKILL.md +186 -0
  19. package/dist/.claude-template/skills/run-ui-test/references/report-format.md +77 -0
  20. package/dist/.claude-template/skills/subagent-implement/SKILL.md +3 -2
  21. package/dist/.claude-template/skills/write-api/SKILL.md +18 -18
  22. package/dist/.claude-template/skills/write-api/assets/{template-api-spec.md → template-/346/216/245/345/217/243/346/265/213/350/257/225-spec.md} +1 -1
  23. package/dist/.claude-template/skills/write-api/references/{api-spec-format.md → /346/216/245/345/217/243/346/265/213/350/257/225-spec-format.md} +3 -3
  24. package/dist/.claude-template/skills/write-plan/SKILL.md +4 -2
  25. package/dist/.claude-template/skills/write-ui-test/SKILL.md +125 -0
  26. package/dist/.claude-template/skills/write-ui-test/references//347/224/250/344/276/213/350/247/204/346/240/274-format.md +94 -0
  27. package/dist/core/code/engine/cli/analyze-config.js +320 -0
  28. package/dist/core/code/engine/cli/analyze.js +1086 -0
  29. package/dist/core/code/engine/cli/cli-message.js +83 -0
  30. package/dist/core/code/engine/cli/detect-changes-format.js +58 -0
  31. package/dist/core/code/engine/cli/embedding-dims.js +43 -0
  32. package/dist/core/code/engine/cli/format-elapsed.js +12 -0
  33. package/dist/core/code/engine/cli/help-i18n.js +144 -0
  34. package/dist/core/code/engine/cli/i18n/en.js +110 -0
  35. package/dist/core/code/engine/cli/i18n/index.js +47 -0
  36. package/dist/core/code/engine/cli/i18n/resources.js +9 -0
  37. package/dist/core/code/engine/cli/i18n/zh-CN.js +110 -0
  38. package/dist/core/code/engine/cli/lazy-action.js +67 -0
  39. package/dist/core/code/engine/cli/optional-grammars.js +136 -0
  40. package/dist/core/code/engine/cli/resolve-invocation.js +76 -0
  41. package/dist/core/code/engine/cli/status.js +156 -0
  42. package/dist/core/code/engine/cli/tool.js +384 -0
  43. package/dist/core/code/engine/config/ignore-service.js +511 -0
  44. package/dist/core/code/engine/config/supported-languages.js +17 -0
  45. package/dist/core/code/engine/core/analysis-features.js +64 -0
  46. package/dist/core/code/engine/core/analyzer-identity.js +2171 -0
  47. package/dist/core/code/engine/core/git-staleness.js +180 -0
  48. package/dist/core/code/engine/core/graph/graph.js +180 -0
  49. package/dist/core/code/engine/core/graph/import-cycles.js +106 -0
  50. package/dist/core/code/engine/core/graph/types.js +2 -0
  51. package/dist/core/code/engine/core/index-freshness.js +12 -0
  52. package/dist/core/code/engine/core/ingestion/binding-accumulator.js +341 -0
  53. package/dist/core/code/engine/core/ingestion/call-extractors/configs/c-cpp.js +168 -0
  54. package/dist/core/code/engine/core/ingestion/call-extractors/configs/csharp.js +9 -0
  55. package/dist/core/code/engine/core/ingestion/call-extractors/configs/dart.js +8 -0
  56. package/dist/core/code/engine/core/ingestion/call-extractors/configs/go.js +8 -0
  57. package/dist/core/code/engine/core/ingestion/call-extractors/configs/jvm.js +54 -0
  58. package/dist/core/code/engine/core/ingestion/call-extractors/configs/php.js +8 -0
  59. package/dist/core/code/engine/core/ingestion/call-extractors/configs/python.js +8 -0
  60. package/dist/core/code/engine/core/ingestion/call-extractors/configs/ruby.js +8 -0
  61. package/dist/core/code/engine/core/ingestion/call-extractors/configs/rust.js +8 -0
  62. package/dist/core/code/engine/core/ingestion/call-extractors/configs/swift.js +8 -0
  63. package/dist/core/code/engine/core/ingestion/call-extractors/configs/typescript-javascript.js +11 -0
  64. package/dist/core/code/engine/core/ingestion/call-extractors/generic.js +62 -0
  65. package/dist/core/code/engine/core/ingestion/call-processor.js +503 -0
  66. package/dist/core/code/engine/core/ingestion/call-routing.js +98 -0
  67. package/dist/core/code/engine/core/ingestion/call-types.js +3 -0
  68. package/dist/core/code/engine/core/ingestion/cfg/callee-cell-format.js +45 -0
  69. package/dist/core/code/engine/core/ingestion/cfg/cfg-builder.js +202 -0
  70. package/dist/core/code/engine/core/ingestion/cfg/collect.js +81 -0
  71. package/dist/core/code/engine/core/ingestion/cfg/control-dependence.js +185 -0
  72. package/dist/core/code/engine/core/ingestion/cfg/control-flow-context.js +130 -0
  73. package/dist/core/code/engine/core/ingestion/cfg/emit.js +646 -0
  74. package/dist/core/code/engine/core/ingestion/cfg/post-dominators.js +182 -0
  75. package/dist/core/code/engine/core/ingestion/cfg/reaching-def-reason-codec.js +139 -0
  76. package/dist/core/code/engine/core/ingestion/cfg/reaching-defs-graph.js +322 -0
  77. package/dist/core/code/engine/core/ingestion/cfg/reaching-defs.js +792 -0
  78. package/dist/core/code/engine/core/ingestion/cfg/synthetic-escape.js +305 -0
  79. package/dist/core/code/engine/core/ingestion/cfg/traversal-result.js +6 -0
  80. package/dist/core/code/engine/core/ingestion/cfg/types.js +14 -0
  81. package/dist/core/code/engine/core/ingestion/cfg/visitors/c-cpp-harvest.js +545 -0
  82. package/dist/core/code/engine/core/ingestion/cfg/visitors/c-cpp.js +590 -0
  83. package/dist/core/code/engine/core/ingestion/cfg/visitors/call-site-harvest.js +356 -0
  84. package/dist/core/code/engine/core/ingestion/cfg/visitors/csharp-harvest.js +593 -0
  85. package/dist/core/code/engine/core/ingestion/cfg/visitors/csharp.js +871 -0
  86. package/dist/core/code/engine/core/ingestion/cfg/visitors/dart-harvest.js +874 -0
  87. package/dist/core/code/engine/core/ingestion/cfg/visitors/dart.js +840 -0
  88. package/dist/core/code/engine/core/ingestion/cfg/visitors/go-harvest.js +625 -0
  89. package/dist/core/code/engine/core/ingestion/cfg/visitors/go.js +642 -0
  90. package/dist/core/code/engine/core/ingestion/cfg/visitors/java-harvest.js +517 -0
  91. package/dist/core/code/engine/core/ingestion/cfg/visitors/java.js +816 -0
  92. package/dist/core/code/engine/core/ingestion/cfg/visitors/kotlin-harvest.js +723 -0
  93. package/dist/core/code/engine/core/ingestion/cfg/visitors/kotlin.js +813 -0
  94. package/dist/core/code/engine/core/ingestion/cfg/visitors/php-harvest.js +630 -0
  95. package/dist/core/code/engine/core/ingestion/cfg/visitors/php.js +725 -0
  96. package/dist/core/code/engine/core/ingestion/cfg/visitors/python-harvest.js +776 -0
  97. package/dist/core/code/engine/core/ingestion/cfg/visitors/python.js +562 -0
  98. package/dist/core/code/engine/core/ingestion/cfg/visitors/ruby-harvest.js +591 -0
  99. package/dist/core/code/engine/core/ingestion/cfg/visitors/ruby.js +760 -0
  100. package/dist/core/code/engine/core/ingestion/cfg/visitors/rust-harvest.js +877 -0
  101. package/dist/core/code/engine/core/ingestion/cfg/visitors/rust.js +562 -0
  102. package/dist/core/code/engine/core/ingestion/cfg/visitors/scope-tree-harvest.js +120 -0
  103. package/dist/core/code/engine/core/ingestion/cfg/visitors/swift-harvest.js +683 -0
  104. package/dist/core/code/engine/core/ingestion/cfg/visitors/swift.js +791 -0
  105. package/dist/core/code/engine/core/ingestion/cfg/visitors/typescript-harvest.js +1060 -0
  106. package/dist/core/code/engine/core/ingestion/cfg/visitors/typescript.js +587 -0
  107. package/dist/core/code/engine/core/ingestion/class-extractors/configs/c-cpp.js +77 -0
  108. package/dist/core/code/engine/core/ingestion/class-extractors/configs/csharp.js +24 -0
  109. package/dist/core/code/engine/core/ingestion/class-extractors/configs/dart.js +10 -0
  110. package/dist/core/code/engine/core/ingestion/class-extractors/configs/go.js +28 -0
  111. package/dist/core/code/engine/core/ingestion/class-extractors/configs/jvm.js +67 -0
  112. package/dist/core/code/engine/core/ingestion/class-extractors/configs/php.js +10 -0
  113. package/dist/core/code/engine/core/ingestion/class-extractors/configs/python.js +10 -0
  114. package/dist/core/code/engine/core/ingestion/class-extractors/configs/ruby.js +13 -0
  115. package/dist/core/code/engine/core/ingestion/class-extractors/configs/rust.js +10 -0
  116. package/dist/core/code/engine/core/ingestion/class-extractors/configs/swift.js +21 -0
  117. package/dist/core/code/engine/core/ingestion/class-extractors/configs/typescript-javascript.js +31 -0
  118. package/dist/core/code/engine/core/ingestion/class-extractors/generic.js +144 -0
  119. package/dist/core/code/engine/core/ingestion/class-types.js +2 -0
  120. package/dist/core/code/engine/core/ingestion/cluster-enricher.js +174 -0
  121. package/dist/core/code/engine/core/ingestion/community-processor.js +604 -0
  122. package/dist/core/code/engine/core/ingestion/constants.js +26 -0
  123. package/dist/core/code/engine/core/ingestion/cpp-ue-preprocessor.js +263 -0
  124. package/dist/core/code/engine/core/ingestion/csharp-namespace-gate.js +133 -0
  125. package/dist/core/code/engine/core/ingestion/di-extractors/index.js +38 -0
  126. package/dist/core/code/engine/core/ingestion/di-extractors/spring.js +310 -0
  127. package/dist/core/code/engine/core/ingestion/emit-references.js +244 -0
  128. package/dist/core/code/engine/core/ingestion/entry-point-scoring.js +201 -0
  129. package/dist/core/code/engine/core/ingestion/export-detection.js +244 -0
  130. package/dist/core/code/engine/core/ingestion/field-extractor.js +29 -0
  131. package/dist/core/code/engine/core/ingestion/field-extractors/configs/c-cpp.js +107 -0
  132. package/dist/core/code/engine/core/ingestion/field-extractors/configs/csharp.js +124 -0
  133. package/dist/core/code/engine/core/ingestion/field-extractors/configs/dart.js +99 -0
  134. package/dist/core/code/engine/core/ingestion/field-extractors/configs/go.js +102 -0
  135. package/dist/core/code/engine/core/ingestion/field-extractors/configs/helpers.js +198 -0
  136. package/dist/core/code/engine/core/ingestion/field-extractors/configs/jvm.js +172 -0
  137. package/dist/core/code/engine/core/ingestion/field-extractors/configs/php.js +67 -0
  138. package/dist/core/code/engine/core/ingestion/field-extractors/configs/python.js +94 -0
  139. package/dist/core/code/engine/core/ingestion/field-extractors/configs/ruby.js +79 -0
  140. package/dist/core/code/engine/core/ingestion/field-extractors/configs/rust.js +55 -0
  141. package/dist/core/code/engine/core/ingestion/field-extractors/configs/swift.js +93 -0
  142. package/dist/core/code/engine/core/ingestion/field-extractors/configs/typescript-javascript.js +59 -0
  143. package/dist/core/code/engine/core/ingestion/field-extractors/generic.js +147 -0
  144. package/dist/core/code/engine/core/ingestion/field-extractors/typescript.js +266 -0
  145. package/dist/core/code/engine/core/ingestion/field-types.js +3 -0
  146. package/dist/core/code/engine/core/ingestion/filesystem-walker.js +136 -0
  147. package/dist/core/code/engine/core/ingestion/finalize-orchestrator.js +159 -0
  148. package/dist/core/code/engine/core/ingestion/framework-detection.js +432 -0
  149. package/dist/core/code/engine/core/ingestion/frameworks/spring/analysis-features.js +38 -0
  150. package/dist/core/code/engine/core/ingestion/frameworks/spring/annotation-arguments.js +234 -0
  151. package/dist/core/code/engine/core/ingestion/frameworks/spring/aop-candidates.js +88 -0
  152. package/dist/core/code/engine/core/ingestion/frameworks/spring/aop.js +487 -0
  153. package/dist/core/code/engine/core/ingestion/frameworks/spring/auto-configuration.js +21 -0
  154. package/dist/core/code/engine/core/ingestion/frameworks/spring/bean-candidates.js +189 -0
  155. package/dist/core/code/engine/core/ingestion/frameworks/spring/bean-catalog.js +32 -0
  156. package/dist/core/code/engine/core/ingestion/frameworks/spring/bean-factories.js +73 -0
  157. package/dist/core/code/engine/core/ingestion/frameworks/spring/conditionals.js +323 -0
  158. package/dist/core/code/engine/core/ingestion/frameworks/spring/config-bindings.js +119 -0
  159. package/dist/core/code/engine/core/ingestion/frameworks/spring/di-metadata.js +385 -0
  160. package/dist/core/code/engine/core/ingestion/frameworks/spring/resource-injection.js +96 -0
  161. package/dist/core/code/engine/core/ingestion/import-resolvers/configs/c-cpp.js +17 -0
  162. package/dist/core/code/engine/core/ingestion/import-resolvers/configs/csharp.js +46 -0
  163. package/dist/core/code/engine/core/ingestion/import-resolvers/configs/dart.js +59 -0
  164. package/dist/core/code/engine/core/ingestion/import-resolvers/configs/go.js +30 -0
  165. package/dist/core/code/engine/core/ingestion/import-resolvers/configs/jvm.js +73 -0
  166. package/dist/core/code/engine/core/ingestion/import-resolvers/configs/php.js +19 -0
  167. package/dist/core/code/engine/core/ingestion/import-resolvers/configs/python.js +45 -0
  168. package/dist/core/code/engine/core/ingestion/import-resolvers/configs/ruby.js +20 -0
  169. package/dist/core/code/engine/core/ingestion/import-resolvers/configs/rust.js +58 -0
  170. package/dist/core/code/engine/core/ingestion/import-resolvers/configs/swift.js +94 -0
  171. package/dist/core/code/engine/core/ingestion/import-resolvers/configs/typescript-javascript.js +26 -0
  172. package/dist/core/code/engine/core/ingestion/import-resolvers/csharp.js +128 -0
  173. package/dist/core/code/engine/core/ingestion/import-resolvers/go.js +50 -0
  174. package/dist/core/code/engine/core/ingestion/import-resolvers/jvm.js +112 -0
  175. package/dist/core/code/engine/core/ingestion/import-resolvers/php.js +80 -0
  176. package/dist/core/code/engine/core/ingestion/import-resolvers/python.js +75 -0
  177. package/dist/core/code/engine/core/ingestion/import-resolvers/resolver-factory.js +36 -0
  178. package/dist/core/code/engine/core/ingestion/import-resolvers/ruby.js +20 -0
  179. package/dist/core/code/engine/core/ingestion/import-resolvers/rust.js +79 -0
  180. package/dist/core/code/engine/core/ingestion/import-resolvers/standard.js +180 -0
  181. package/dist/core/code/engine/core/ingestion/import-resolvers/types.js +7 -0
  182. package/dist/core/code/engine/core/ingestion/import-resolvers/utils.js +153 -0
  183. package/dist/core/code/engine/core/ingestion/import-target-adapter.js +99 -0
  184. package/dist/core/code/engine/core/ingestion/language-config.js +391 -0
  185. package/dist/core/code/engine/core/ingestion/language-provider.js +25 -0
  186. package/dist/core/code/engine/core/ingestion/languages/c/arity-metadata.js +98 -0
  187. package/dist/core/code/engine/core/ingestion/languages/c/arity.js +21 -0
  188. package/dist/core/code/engine/core/ingestion/languages/c/capture-side-channel.js +69 -0
  189. package/dist/core/code/engine/core/ingestion/languages/c/captures.js +189 -0
  190. package/dist/core/code/engine/core/ingestion/languages/c/header-scan.js +58 -0
  191. package/dist/core/code/engine/core/ingestion/languages/c/import-decomposer.js +68 -0
  192. package/dist/core/code/engine/core/ingestion/languages/c/import-target.js +103 -0
  193. package/dist/core/code/engine/core/ingestion/languages/c/index.js +33 -0
  194. package/dist/core/code/engine/core/ingestion/languages/c/interpret.js +53 -0
  195. package/dist/core/code/engine/core/ingestion/languages/c/merge-bindings.js +26 -0
  196. package/dist/core/code/engine/core/ingestion/languages/c/query.js +210 -0
  197. package/dist/core/code/engine/core/ingestion/languages/c/scope-resolver.js +112 -0
  198. package/dist/core/code/engine/core/ingestion/languages/c/simple-hooks.js +24 -0
  199. package/dist/core/code/engine/core/ingestion/languages/c/static-linkage.js +109 -0
  200. package/dist/core/code/engine/core/ingestion/languages/c-cpp.js +506 -0
  201. package/dist/core/code/engine/core/ingestion/languages/cpp/adl.js +804 -0
  202. package/dist/core/code/engine/core/ingestion/languages/cpp/arity-metadata.js +258 -0
  203. package/dist/core/code/engine/core/ingestion/languages/cpp/arity.js +37 -0
  204. package/dist/core/code/engine/core/ingestion/languages/cpp/capture-side-channel.js +89 -0
  205. package/dist/core/code/engine/core/ingestion/languages/cpp/captures.js +1975 -0
  206. package/dist/core/code/engine/core/ingestion/languages/cpp/constraint-extractor.js +311 -0
  207. package/dist/core/code/engine/core/ingestion/languages/cpp/constraint-filter.js +210 -0
  208. package/dist/core/code/engine/core/ingestion/languages/cpp/conversion-rank.js +163 -0
  209. package/dist/core/code/engine/core/ingestion/languages/cpp/file-local-linkage.js +327 -0
  210. package/dist/core/code/engine/core/ingestion/languages/cpp/header-scan.js +53 -0
  211. package/dist/core/code/engine/core/ingestion/languages/cpp/import-decomposer.js +134 -0
  212. package/dist/core/code/engine/core/ingestion/languages/cpp/import-target.js +16 -0
  213. package/dist/core/code/engine/core/ingestion/languages/cpp/index.js +33 -0
  214. package/dist/core/code/engine/core/ingestion/languages/cpp/inline-namespaces.js +379 -0
  215. package/dist/core/code/engine/core/ingestion/languages/cpp/interpret.js +239 -0
  216. package/dist/core/code/engine/core/ingestion/languages/cpp/member-lookup.js +470 -0
  217. package/dist/core/code/engine/core/ingestion/languages/cpp/merge-bindings.js +32 -0
  218. package/dist/core/code/engine/core/ingestion/languages/cpp/query.js +763 -0
  219. package/dist/core/code/engine/core/ingestion/languages/cpp/range-bindings.js +230 -0
  220. package/dist/core/code/engine/core/ingestion/languages/cpp/scope-resolver.js +354 -0
  221. package/dist/core/code/engine/core/ingestion/languages/cpp/simple-hooks.js +67 -0
  222. package/dist/core/code/engine/core/ingestion/languages/cpp/two-phase-lookup.js +348 -0
  223. package/dist/core/code/engine/core/ingestion/languages/cpp/type-classifier.js +56 -0
  224. package/dist/core/code/engine/core/ingestion/languages/cpp/user-defined-conversions.js +128 -0
  225. package/dist/core/code/engine/core/ingestion/languages/csharp/accessor-unwrap.js +67 -0
  226. package/dist/core/code/engine/core/ingestion/languages/csharp/arity-metadata.js +49 -0
  227. package/dist/core/code/engine/core/ingestion/languages/csharp/arity.js +40 -0
  228. package/dist/core/code/engine/core/ingestion/languages/csharp/cache-stats.js +32 -0
  229. package/dist/core/code/engine/core/ingestion/languages/csharp/captures.js +557 -0
  230. package/dist/core/code/engine/core/ingestion/languages/csharp/import-decomposer.js +96 -0
  231. package/dist/core/code/engine/core/ingestion/languages/csharp/import-target.js +176 -0
  232. package/dist/core/code/engine/core/ingestion/languages/csharp/index.js +95 -0
  233. package/dist/core/code/engine/core/ingestion/languages/csharp/interpret.js +150 -0
  234. package/dist/core/code/engine/core/ingestion/languages/csharp/merge-bindings.js +58 -0
  235. package/dist/core/code/engine/core/ingestion/languages/csharp/namespace-siblings.js +708 -0
  236. package/dist/core/code/engine/core/ingestion/languages/csharp/qualified-type-names.js +62 -0
  237. package/dist/core/code/engine/core/ingestion/languages/csharp/query.js +578 -0
  238. package/dist/core/code/engine/core/ingestion/languages/csharp/receiver-binding.js +142 -0
  239. package/dist/core/code/engine/core/ingestion/languages/csharp/resolution-config.js +18 -0
  240. package/dist/core/code/engine/core/ingestion/languages/csharp/scope-resolver.js +84 -0
  241. package/dist/core/code/engine/core/ingestion/languages/csharp/simple-hooks.js +81 -0
  242. package/dist/core/code/engine/core/ingestion/languages/csharp.js +204 -0
  243. package/dist/core/code/engine/core/ingestion/languages/dart/arity-metadata.js +38 -0
  244. package/dist/core/code/engine/core/ingestion/languages/dart/arity.js +34 -0
  245. package/dist/core/code/engine/core/ingestion/languages/dart/built-ins.js +37 -0
  246. package/dist/core/code/engine/core/ingestion/languages/dart/cache-stats.js +30 -0
  247. package/dist/core/code/engine/core/ingestion/languages/dart/captures.js +1096 -0
  248. package/dist/core/code/engine/core/ingestion/languages/dart/expand-wildcards.js +34 -0
  249. package/dist/core/code/engine/core/ingestion/languages/dart/extension-type-preprocess.js +33 -0
  250. package/dist/core/code/engine/core/ingestion/languages/dart/import-target.js +68 -0
  251. package/dist/core/code/engine/core/ingestion/languages/dart/index.js +45 -0
  252. package/dist/core/code/engine/core/ingestion/languages/dart/interpret.js +101 -0
  253. package/dist/core/code/engine/core/ingestion/languages/dart/merge-bindings.js +42 -0
  254. package/dist/core/code/engine/core/ingestion/languages/dart/query.js +246 -0
  255. package/dist/core/code/engine/core/ingestion/languages/dart/receiver-binding.js +90 -0
  256. package/dist/core/code/engine/core/ingestion/languages/dart/scope-resolver.js +197 -0
  257. package/dist/core/code/engine/core/ingestion/languages/dart/signature-bindings.js +54 -0
  258. package/dist/core/code/engine/core/ingestion/languages/dart/simple-hooks.js +61 -0
  259. package/dist/core/code/engine/core/ingestion/languages/dart.js +138 -0
  260. package/dist/core/code/engine/core/ingestion/languages/go/arity-metadata.js +71 -0
  261. package/dist/core/code/engine/core/ingestion/languages/go/arity.js +17 -0
  262. package/dist/core/code/engine/core/ingestion/languages/go/cache-stats.js +21 -0
  263. package/dist/core/code/engine/core/ingestion/languages/go/captures.js +492 -0
  264. package/dist/core/code/engine/core/ingestion/languages/go/expand-wildcards.js +97 -0
  265. package/dist/core/code/engine/core/ingestion/languages/go/generic-type-parameters.js +146 -0
  266. package/dist/core/code/engine/core/ingestion/languages/go/import-decomposer.js +47 -0
  267. package/dist/core/code/engine/core/ingestion/languages/go/import-target.js +70 -0
  268. package/dist/core/code/engine/core/ingestion/languages/go/index.js +39 -0
  269. package/dist/core/code/engine/core/ingestion/languages/go/interface-impls.js +955 -0
  270. package/dist/core/code/engine/core/ingestion/languages/go/interpret.js +177 -0
  271. package/dist/core/code/engine/core/ingestion/languages/go/merge-bindings.js +21 -0
  272. package/dist/core/code/engine/core/ingestion/languages/go/method-owners.js +131 -0
  273. package/dist/core/code/engine/core/ingestion/languages/go/namespace-mirror.js +56 -0
  274. package/dist/core/code/engine/core/ingestion/languages/go/package-clause.js +79 -0
  275. package/dist/core/code/engine/core/ingestion/languages/go/package-siblings.js +83 -0
  276. package/dist/core/code/engine/core/ingestion/languages/go/query.js +298 -0
  277. package/dist/core/code/engine/core/ingestion/languages/go/range-binding.js +127 -0
  278. package/dist/core/code/engine/core/ingestion/languages/go/receiver-binding.js +24 -0
  279. package/dist/core/code/engine/core/ingestion/languages/go/scope-resolver.js +75 -0
  280. package/dist/core/code/engine/core/ingestion/languages/go/simple-hooks.js +31 -0
  281. package/dist/core/code/engine/core/ingestion/languages/go/type-binding.js +279 -0
  282. package/dist/core/code/engine/core/ingestion/languages/go.js +160 -0
  283. package/dist/core/code/engine/core/ingestion/languages/index.js +66 -0
  284. package/dist/core/code/engine/core/ingestion/languages/java/analysis-features.js +16 -0
  285. package/dist/core/code/engine/core/ingestion/languages/java/arity-metadata.js +43 -0
  286. package/dist/core/code/engine/core/ingestion/languages/java/arity.js +27 -0
  287. package/dist/core/code/engine/core/ingestion/languages/java/cache-stats.js +32 -0
  288. package/dist/core/code/engine/core/ingestion/languages/java/capture-side-channel.js +123 -0
  289. package/dist/core/code/engine/core/ingestion/languages/java/captures.js +791 -0
  290. package/dist/core/code/engine/core/ingestion/languages/java/import-decomposer.js +88 -0
  291. package/dist/core/code/engine/core/ingestion/languages/java/import-target.js +103 -0
  292. package/dist/core/code/engine/core/ingestion/languages/java/index.js +43 -0
  293. package/dist/core/code/engine/core/ingestion/languages/java/interpret.js +146 -0
  294. package/dist/core/code/engine/core/ingestion/languages/java/merge-bindings.js +43 -0
  295. package/dist/core/code/engine/core/ingestion/languages/java/package-facts.js +16 -0
  296. package/dist/core/code/engine/core/ingestion/languages/java/package-siblings.js +11 -0
  297. package/dist/core/code/engine/core/ingestion/languages/java/query.js +319 -0
  298. package/dist/core/code/engine/core/ingestion/languages/java/receiver-binding.js +98 -0
  299. package/dist/core/code/engine/core/ingestion/languages/java/scope-resolver.js +213 -0
  300. package/dist/core/code/engine/core/ingestion/languages/java/simple-hooks.js +39 -0
  301. package/dist/core/code/engine/core/ingestion/languages/java/spring-aop.js +53 -0
  302. package/dist/core/code/engine/core/ingestion/languages/java/spring-bean-metadata.js +11 -0
  303. package/dist/core/code/engine/core/ingestion/languages/java/spring-conditionals.js +52 -0
  304. package/dist/core/code/engine/core/ingestion/languages/java/spring-config-bindings.js +222 -0
  305. package/dist/core/code/engine/core/ingestion/languages/java/spring-di.js +165 -0
  306. package/dist/core/code/engine/core/ingestion/languages/java.js +192 -0
  307. package/dist/core/code/engine/core/ingestion/languages/javascript/arity.js +15 -0
  308. package/dist/core/code/engine/core/ingestion/languages/javascript/captures.js +1122 -0
  309. package/dist/core/code/engine/core/ingestion/languages/javascript/import-target.js +56 -0
  310. package/dist/core/code/engine/core/ingestion/languages/javascript/index.js +109 -0
  311. package/dist/core/code/engine/core/ingestion/languages/javascript/interpret.js +45 -0
  312. package/dist/core/code/engine/core/ingestion/languages/javascript/merge-bindings.js +21 -0
  313. package/dist/core/code/engine/core/ingestion/languages/javascript/query.js +659 -0
  314. package/dist/core/code/engine/core/ingestion/languages/javascript/scope-resolver.js +78 -0
  315. package/dist/core/code/engine/core/ingestion/languages/javascript/simple-hooks.js +44 -0
  316. package/dist/core/code/engine/core/ingestion/languages/jvm/package-facts.js +46 -0
  317. package/dist/core/code/engine/core/ingestion/languages/jvm/package-siblings.js +200 -0
  318. package/dist/core/code/engine/core/ingestion/languages/kotlin/arity-metadata.js +23 -0
  319. package/dist/core/code/engine/core/ingestion/languages/kotlin/arity.js +18 -0
  320. package/dist/core/code/engine/core/ingestion/languages/kotlin/cache-stats.js +21 -0
  321. package/dist/core/code/engine/core/ingestion/languages/kotlin/capture-side-channel.js +160 -0
  322. package/dist/core/code/engine/core/ingestion/languages/kotlin/captures.js +1262 -0
  323. package/dist/core/code/engine/core/ingestion/languages/kotlin/companion-scopes.js +72 -0
  324. package/dist/core/code/engine/core/ingestion/languages/kotlin/import-decomposer.js +40 -0
  325. package/dist/core/code/engine/core/ingestion/languages/kotlin/import-target.js +135 -0
  326. package/dist/core/code/engine/core/ingestion/languages/kotlin/index.js +26 -0
  327. package/dist/core/code/engine/core/ingestion/languages/kotlin/interpret.js +75 -0
  328. package/dist/core/code/engine/core/ingestion/languages/kotlin/merge-bindings.js +28 -0
  329. package/dist/core/code/engine/core/ingestion/languages/kotlin/owners.js +134 -0
  330. package/dist/core/code/engine/core/ingestion/languages/kotlin/package-facts.js +16 -0
  331. package/dist/core/code/engine/core/ingestion/languages/kotlin/package-siblings.js +11 -0
  332. package/dist/core/code/engine/core/ingestion/languages/kotlin/query.js +243 -0
  333. package/dist/core/code/engine/core/ingestion/languages/kotlin/receiver-binding.js +103 -0
  334. package/dist/core/code/engine/core/ingestion/languages/kotlin/scope-resolver.js +207 -0
  335. package/dist/core/code/engine/core/ingestion/languages/kotlin/simple-hooks.js +42 -0
  336. package/dist/core/code/engine/core/ingestion/languages/kotlin/spring-aop.js +68 -0
  337. package/dist/core/code/engine/core/ingestion/languages/kotlin/spring-bean-metadata.js +11 -0
  338. package/dist/core/code/engine/core/ingestion/languages/kotlin/spring-conditionals.js +53 -0
  339. package/dist/core/code/engine/core/ingestion/languages/kotlin/spring-di.js +304 -0
  340. package/dist/core/code/engine/core/ingestion/languages/kotlin.js +189 -0
  341. package/dist/core/code/engine/core/ingestion/languages/php/arity-metadata.js +66 -0
  342. package/dist/core/code/engine/core/ingestion/languages/php/arity.js +43 -0
  343. package/dist/core/code/engine/core/ingestion/languages/php/cache-stats.js +32 -0
  344. package/dist/core/code/engine/core/ingestion/languages/php/captures.js +1166 -0
  345. package/dist/core/code/engine/core/ingestion/languages/php/import-decomposer.js +238 -0
  346. package/dist/core/code/engine/core/ingestion/languages/php/import-target.js +213 -0
  347. package/dist/core/code/engine/core/ingestion/languages/php/index.js +85 -0
  348. package/dist/core/code/engine/core/ingestion/languages/php/interpret.js +256 -0
  349. package/dist/core/code/engine/core/ingestion/languages/php/merge-bindings.js +50 -0
  350. package/dist/core/code/engine/core/ingestion/languages/php/namespace-siblings.js +353 -0
  351. package/dist/core/code/engine/core/ingestion/languages/php/query.js +391 -0
  352. package/dist/core/code/engine/core/ingestion/languages/php/receiver-binding.js +135 -0
  353. package/dist/core/code/engine/core/ingestion/languages/php/scope-resolver.js +361 -0
  354. package/dist/core/code/engine/core/ingestion/languages/php/simple-hooks.js +116 -0
  355. package/dist/core/code/engine/core/ingestion/languages/php.js +302 -0
  356. package/dist/core/code/engine/core/ingestion/languages/python/arity-metadata.js +49 -0
  357. package/dist/core/code/engine/core/ingestion/languages/python/arity.js +41 -0
  358. package/dist/core/code/engine/core/ingestion/languages/python/cache-stats.js +34 -0
  359. package/dist/core/code/engine/core/ingestion/languages/python/captures.js +299 -0
  360. package/dist/core/code/engine/core/ingestion/languages/python/depends-references.js +68 -0
  361. package/dist/core/code/engine/core/ingestion/languages/python/import-decomposer.js +115 -0
  362. package/dist/core/code/engine/core/ingestion/languages/python/import-target.js +440 -0
  363. package/dist/core/code/engine/core/ingestion/languages/python/index-stats.js +30 -0
  364. package/dist/core/code/engine/core/ingestion/languages/python/index.js +96 -0
  365. package/dist/core/code/engine/core/ingestion/languages/python/interpret.js +430 -0
  366. package/dist/core/code/engine/core/ingestion/languages/python/merge-bindings.js +47 -0
  367. package/dist/core/code/engine/core/ingestion/languages/python/query.js +323 -0
  368. package/dist/core/code/engine/core/ingestion/languages/python/receiver-binding.js +310 -0
  369. package/dist/core/code/engine/core/ingestion/languages/python/scope-resolver.js +80 -0
  370. package/dist/core/code/engine/core/ingestion/languages/python/simple-hooks.js +53 -0
  371. package/dist/core/code/engine/core/ingestion/languages/python.js +140 -0
  372. package/dist/core/code/engine/core/ingestion/languages/ruby/arity.js +41 -0
  373. package/dist/core/code/engine/core/ingestion/languages/ruby/cache-stats.js +21 -0
  374. package/dist/core/code/engine/core/ingestion/languages/ruby/captures.js +864 -0
  375. package/dist/core/code/engine/core/ingestion/languages/ruby/import-target.js +88 -0
  376. package/dist/core/code/engine/core/ingestion/languages/ruby/index.js +29 -0
  377. package/dist/core/code/engine/core/ingestion/languages/ruby/interpret.js +115 -0
  378. package/dist/core/code/engine/core/ingestion/languages/ruby/merge-bindings.js +21 -0
  379. package/dist/core/code/engine/core/ingestion/languages/ruby/query.js +349 -0
  380. package/dist/core/code/engine/core/ingestion/languages/ruby/receiver-binding.js +70 -0
  381. package/dist/core/code/engine/core/ingestion/languages/ruby/scope-resolver.js +263 -0
  382. package/dist/core/code/engine/core/ingestion/languages/ruby/simple-hooks.js +68 -0
  383. package/dist/core/code/engine/core/ingestion/languages/ruby.js +215 -0
  384. package/dist/core/code/engine/core/ingestion/languages/rust/arity.js +16 -0
  385. package/dist/core/code/engine/core/ingestion/languages/rust/cache-stats.js +21 -0
  386. package/dist/core/code/engine/core/ingestion/languages/rust/captures.js +304 -0
  387. package/dist/core/code/engine/core/ingestion/languages/rust/import-decomposer.js +167 -0
  388. package/dist/core/code/engine/core/ingestion/languages/rust/import-target.js +108 -0
  389. package/dist/core/code/engine/core/ingestion/languages/rust/index.js +29 -0
  390. package/dist/core/code/engine/core/ingestion/languages/rust/interpret.js +201 -0
  391. package/dist/core/code/engine/core/ingestion/languages/rust/merge-bindings.js +21 -0
  392. package/dist/core/code/engine/core/ingestion/languages/rust/method-owners.js +76 -0
  393. package/dist/core/code/engine/core/ingestion/languages/rust/module-path.js +222 -0
  394. package/dist/core/code/engine/core/ingestion/languages/rust/qualified-call.js +482 -0
  395. package/dist/core/code/engine/core/ingestion/languages/rust/query.js +280 -0
  396. package/dist/core/code/engine/core/ingestion/languages/rust/range-binding.js +687 -0
  397. package/dist/core/code/engine/core/ingestion/languages/rust/receiver-binding.js +148 -0
  398. package/dist/core/code/engine/core/ingestion/languages/rust/scope-resolver.js +151 -0
  399. package/dist/core/code/engine/core/ingestion/languages/rust/simple-hooks.js +32 -0
  400. package/dist/core/code/engine/core/ingestion/languages/rust.js +183 -0
  401. package/dist/core/code/engine/core/ingestion/languages/swift/arity-metadata.js +44 -0
  402. package/dist/core/code/engine/core/ingestion/languages/swift/arity.js +45 -0
  403. package/dist/core/code/engine/core/ingestion/languages/swift/base-type.js +30 -0
  404. package/dist/core/code/engine/core/ingestion/languages/swift/cache-stats.js +32 -0
  405. package/dist/core/code/engine/core/ingestion/languages/swift/captures.js +594 -0
  406. package/dist/core/code/engine/core/ingestion/languages/swift/conditional-directive-preprocess.js +256 -0
  407. package/dist/core/code/engine/core/ingestion/languages/swift/implicit-imports.js +60 -0
  408. package/dist/core/code/engine/core/ingestion/languages/swift/import-decomposer.js +87 -0
  409. package/dist/core/code/engine/core/ingestion/languages/swift/import-target.js +84 -0
  410. package/dist/core/code/engine/core/ingestion/languages/swift/index.js +56 -0
  411. package/dist/core/code/engine/core/ingestion/languages/swift/interpret.js +93 -0
  412. package/dist/core/code/engine/core/ingestion/languages/swift/merge-bindings.js +51 -0
  413. package/dist/core/code/engine/core/ingestion/languages/swift/query.js +226 -0
  414. package/dist/core/code/engine/core/ingestion/languages/swift/receiver-binding.js +169 -0
  415. package/dist/core/code/engine/core/ingestion/languages/swift/scope-resolver.js +192 -0
  416. package/dist/core/code/engine/core/ingestion/languages/swift/sibling-type-bindings.js +68 -0
  417. package/dist/core/code/engine/core/ingestion/languages/swift/signature-bindings.js +69 -0
  418. package/dist/core/code/engine/core/ingestion/languages/swift/simple-hooks.js +65 -0
  419. package/dist/core/code/engine/core/ingestion/languages/swift/target-grouping.js +97 -0
  420. package/dist/core/code/engine/core/ingestion/languages/swift/target-siblings.js +74 -0
  421. package/dist/core/code/engine/core/ingestion/languages/swift.js +246 -0
  422. package/dist/core/code/engine/core/ingestion/languages/typescript/arity-metadata.js +106 -0
  423. package/dist/core/code/engine/core/ingestion/languages/typescript/arity.js +57 -0
  424. package/dist/core/code/engine/core/ingestion/languages/typescript/array-callback.js +58 -0
  425. package/dist/core/code/engine/core/ingestion/languages/typescript/cache-stats.js +34 -0
  426. package/dist/core/code/engine/core/ingestion/languages/typescript/captures.js +956 -0
  427. package/dist/core/code/engine/core/ingestion/languages/typescript/cjs-export-assignment.js +535 -0
  428. package/dist/core/code/engine/core/ingestion/languages/typescript/cjs-module-exports.js +196 -0
  429. package/dist/core/code/engine/core/ingestion/languages/typescript/import-decomposer.js +374 -0
  430. package/dist/core/code/engine/core/ingestion/languages/typescript/import-target.js +65 -0
  431. package/dist/core/code/engine/core/ingestion/languages/typescript/index.js +108 -0
  432. package/dist/core/code/engine/core/ingestion/languages/typescript/interpret.js +344 -0
  433. package/dist/core/code/engine/core/ingestion/languages/typescript/merge-bindings.js +161 -0
  434. package/dist/core/code/engine/core/ingestion/languages/typescript/nuxt-auto-imports.js +325 -0
  435. package/dist/core/code/engine/core/ingestion/languages/typescript/query.js +1328 -0
  436. package/dist/core/code/engine/core/ingestion/languages/typescript/receiver-binding.js +201 -0
  437. package/dist/core/code/engine/core/ingestion/languages/typescript/scope-resolver.js +293 -0
  438. package/dist/core/code/engine/core/ingestion/languages/typescript/simple-hooks.js +139 -0
  439. package/dist/core/code/engine/core/ingestion/languages/typescript.js +455 -0
  440. package/dist/core/code/engine/core/ingestion/languages/vue/captures.js +70 -0
  441. package/dist/core/code/engine/core/ingestion/languages/vue/import-target.js +61 -0
  442. package/dist/core/code/engine/core/ingestion/languages/vue/index.js +55 -0
  443. package/dist/core/code/engine/core/ingestion/languages/vue/scope-resolver.js +295 -0
  444. package/dist/core/code/engine/core/ingestion/languages/vue.js +96 -0
  445. package/dist/core/code/engine/core/ingestion/local-symbol-pruner.js +68 -0
  446. package/dist/core/code/engine/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
  447. package/dist/core/code/engine/core/ingestion/method-extractors/configs/csharp.js +290 -0
  448. package/dist/core/code/engine/core/ingestion/method-extractors/configs/dart.js +392 -0
  449. package/dist/core/code/engine/core/ingestion/method-extractors/configs/go.js +179 -0
  450. package/dist/core/code/engine/core/ingestion/method-extractors/configs/jvm.js +350 -0
  451. package/dist/core/code/engine/core/ingestion/method-extractors/configs/php.js +306 -0
  452. package/dist/core/code/engine/core/ingestion/method-extractors/configs/python.js +312 -0
  453. package/dist/core/code/engine/core/ingestion/method-extractors/configs/ruby.js +289 -0
  454. package/dist/core/code/engine/core/ingestion/method-extractors/configs/rust.js +198 -0
  455. package/dist/core/code/engine/core/ingestion/method-extractors/configs/swift.js +286 -0
  456. package/dist/core/code/engine/core/ingestion/method-extractors/configs/typescript-javascript.js +341 -0
  457. package/dist/core/code/engine/core/ingestion/method-extractors/generic.js +209 -0
  458. package/dist/core/code/engine/core/ingestion/method-types.js +3 -0
  459. package/dist/core/code/engine/core/ingestion/model/field-registry.js +41 -0
  460. package/dist/core/code/engine/core/ingestion/model/index.js +52 -0
  461. package/dist/core/code/engine/core/ingestion/model/method-registry.js +138 -0
  462. package/dist/core/code/engine/core/ingestion/model/owned-members-lookup.js +46 -0
  463. package/dist/core/code/engine/core/ingestion/model/registration-table.js +234 -0
  464. package/dist/core/code/engine/core/ingestion/model/resolve.js +183 -0
  465. package/dist/core/code/engine/core/ingestion/model/scope-resolution-indexes.js +43 -0
  466. package/dist/core/code/engine/core/ingestion/model/semantic-model.js +179 -0
  467. package/dist/core/code/engine/core/ingestion/model/symbol-table.js +216 -0
  468. package/dist/core/code/engine/core/ingestion/model/type-registry.js +84 -0
  469. package/dist/core/code/engine/core/ingestion/mro-processor.js +709 -0
  470. package/dist/core/code/engine/core/ingestion/parsing-processor.js +312 -0
  471. package/dist/core/code/engine/core/ingestion/pipeline-phases/communities.js +69 -0
  472. package/dist/core/code/engine/core/ingestion/pipeline-phases/cross-file.js +71 -0
  473. package/dist/core/code/engine/core/ingestion/pipeline-phases/di.js +338 -0
  474. package/dist/core/code/engine/core/ingestion/pipeline-phases/http-api-calls.js +312 -0
  475. package/dist/core/code/engine/core/ingestion/pipeline-phases/index.js +54 -0
  476. package/dist/core/code/engine/core/ingestion/pipeline-phases/mro.js +40 -0
  477. package/dist/core/code/engine/core/ingestion/pipeline-phases/orm.js +78 -0
  478. package/dist/core/code/engine/core/ingestion/pipeline-phases/parse-impl.js +1286 -0
  479. package/dist/core/code/engine/core/ingestion/pipeline-phases/parse.js +41 -0
  480. package/dist/core/code/engine/core/ingestion/pipeline-phases/processes.js +193 -0
  481. package/dist/core/code/engine/core/ingestion/pipeline-phases/prune-local-symbols.js +29 -0
  482. package/dist/core/code/engine/core/ingestion/pipeline-phases/registry.js +52 -0
  483. package/dist/core/code/engine/core/ingestion/pipeline-phases/routes.js +409 -0
  484. package/dist/core/code/engine/core/ingestion/pipeline-phases/rpc-edges.js +301 -0
  485. package/dist/core/code/engine/core/ingestion/pipeline-phases/runner.js +207 -0
  486. package/dist/core/code/engine/core/ingestion/pipeline-phases/scan.js +49 -0
  487. package/dist/core/code/engine/core/ingestion/pipeline-phases/spring-aop.js +442 -0
  488. package/dist/core/code/engine/core/ingestion/pipeline-phases/spring-auto-configuration.js +264 -0
  489. package/dist/core/code/engine/core/ingestion/pipeline-phases/spring-config.js +440 -0
  490. package/dist/core/code/engine/core/ingestion/pipeline-phases/structure.js +38 -0
  491. package/dist/core/code/engine/core/ingestion/pipeline-phases/tools.js +89 -0
  492. package/dist/core/code/engine/core/ingestion/pipeline-phases/types.js +40 -0
  493. package/dist/core/code/engine/core/ingestion/pipeline.js +152 -0
  494. package/dist/core/code/engine/core/ingestion/process-processor.js +325 -0
  495. package/dist/core/code/engine/core/ingestion/resolve-references.js +205 -0
  496. package/dist/core/code/engine/core/ingestion/route-extractors/constant-resolver.js +135 -0
  497. package/dist/core/code/engine/core/ingestion/route-extractors/django-root-discovery.js +221 -0
  498. package/dist/core/code/engine/core/ingestion/route-extractors/django.js +428 -0
  499. package/dist/core/code/engine/core/ingestion/route-extractors/expo.js +39 -0
  500. package/dist/core/code/engine/core/ingestion/route-extractors/fastapi-router-bindings.js +264 -0
  501. package/dist/core/code/engine/core/ingestion/route-extractors/laravel.js +501 -0
  502. package/dist/core/code/engine/core/ingestion/route-extractors/middleware.js +175 -0
  503. package/dist/core/code/engine/core/ingestion/route-extractors/nextjs.js +81 -0
  504. package/dist/core/code/engine/core/ingestion/route-extractors/php.js +25 -0
  505. package/dist/core/code/engine/core/ingestion/route-extractors/python-const-resolver.js +307 -0
  506. package/dist/core/code/engine/core/ingestion/route-extractors/response-shapes.js +299 -0
  507. package/dist/core/code/engine/core/ingestion/route-extractors/route-path.js +71 -0
  508. package/dist/core/code/engine/core/ingestion/route-extractors/spring-shared.js +310 -0
  509. package/dist/core/code/engine/core/ingestion/route-extractors/spring.js +441 -0
  510. package/dist/core/code/engine/core/ingestion/scope-extractor-bridge.js +60 -0
  511. package/dist/core/code/engine/core/ingestion/scope-extractor.js +1373 -0
  512. package/dist/core/code/engine/core/ingestion/scope-resolution/contract/scope-resolver.js +282 -0
  513. package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +72 -0
  514. package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/edges.js +194 -0
  515. package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/ids.js +472 -0
  516. package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +49 -0
  517. package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +43 -0
  518. package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +274 -0
  519. package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +93 -0
  520. package/dist/core/code/engine/core/ingestion/scope-resolution/passes/callable-value-flow.js +1240 -0
  521. package/dist/core/code/engine/core/ingestion/scope-resolution/passes/compound-receiver.js +1174 -0
  522. package/dist/core/code/engine/core/ingestion/scope-resolution/passes/free-call-fallback.js +873 -0
  523. package/dist/core/code/engine/core/ingestion/scope-resolution/passes/imported-return-types.js +226 -0
  524. package/dist/core/code/engine/core/ingestion/scope-resolution/passes/mro.js +107 -0
  525. package/dist/core/code/engine/core/ingestion/scope-resolution/passes/overload-narrowing.js +441 -0
  526. package/dist/core/code/engine/core/ingestion/scope-resolution/passes/property-dispatch.js +122 -0
  527. package/dist/core/code/engine/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +1720 -0
  528. package/dist/core/code/engine/core/ingestion/scope-resolution/pipeline/phase.js +395 -0
  529. package/dist/core/code/engine/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +208 -0
  530. package/dist/core/code/engine/core/ingestion/scope-resolution/pipeline/registry.js +49 -0
  531. package/dist/core/code/engine/core/ingestion/scope-resolution/pipeline/run.js +632 -0
  532. package/dist/core/code/engine/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +112 -0
  533. package/dist/core/code/engine/core/ingestion/scope-resolution/resolution-outcome.js +41 -0
  534. package/dist/core/code/engine/core/ingestion/scope-resolution/scope/namespace-targets.js +81 -0
  535. package/dist/core/code/engine/core/ingestion/scope-resolution/scope/walkers.js +1835 -0
  536. package/dist/core/code/engine/core/ingestion/scope-resolution/unresolved-receivers.js +242 -0
  537. package/dist/core/code/engine/core/ingestion/scope-resolution/utils/definition-id.js +22 -0
  538. package/dist/core/code/engine/core/ingestion/scope-resolution/workspace-index.js +151 -0
  539. package/dist/core/code/engine/core/ingestion/structure-processor.js +40 -0
  540. package/dist/core/code/engine/core/ingestion/tree-sitter-queries.js +2244 -0
  541. package/dist/core/code/engine/core/ingestion/ts-js-hoc-utils.js +115 -0
  542. package/dist/core/code/engine/core/ingestion/type-env.js +1136 -0
  543. package/dist/core/code/engine/core/ingestion/type-extractors/c-cpp.js +555 -0
  544. package/dist/core/code/engine/core/ingestion/type-extractors/csharp.js +570 -0
  545. package/dist/core/code/engine/core/ingestion/type-extractors/dart.js +372 -0
  546. package/dist/core/code/engine/core/ingestion/type-extractors/go.js +508 -0
  547. package/dist/core/code/engine/core/ingestion/type-extractors/jvm.js +875 -0
  548. package/dist/core/code/engine/core/ingestion/type-extractors/php.js +537 -0
  549. package/dist/core/code/engine/core/ingestion/type-extractors/python.js +477 -0
  550. package/dist/core/code/engine/core/ingestion/type-extractors/ruby.js +380 -0
  551. package/dist/core/code/engine/core/ingestion/type-extractors/rust.js +502 -0
  552. package/dist/core/code/engine/core/ingestion/type-extractors/shared.js +843 -0
  553. package/dist/core/code/engine/core/ingestion/type-extractors/swift.js +490 -0
  554. package/dist/core/code/engine/core/ingestion/type-extractors/types.js +2 -0
  555. package/dist/core/code/engine/core/ingestion/type-extractors/typescript.js +690 -0
  556. package/dist/core/code/engine/core/ingestion/utils/ast-helpers.js +1693 -0
  557. package/dist/core/code/engine/core/ingestion/utils/call-analysis.js +779 -0
  558. package/dist/core/code/engine/core/ingestion/utils/callable-flow-captures.js +932 -0
  559. package/dist/core/code/engine/core/ingestion/utils/callable-labels.js +49 -0
  560. package/dist/core/code/engine/core/ingestion/utils/deferred-resolution-profile.js +151 -0
  561. package/dist/core/code/engine/core/ingestion/utils/effective-ram.js +67 -0
  562. package/dist/core/code/engine/core/ingestion/utils/env.js +60 -0
  563. package/dist/core/code/engine/core/ingestion/utils/event-loop.js +9 -0
  564. package/dist/core/code/engine/core/ingestion/utils/graph-sort.js +103 -0
  565. package/dist/core/code/engine/core/ingestion/utils/heap-probe.js +45 -0
  566. package/dist/core/code/engine/core/ingestion/utils/heritage-marker.js +47 -0
  567. package/dist/core/code/engine/core/ingestion/utils/line-base.js +24 -0
  568. package/dist/core/code/engine/core/ingestion/utils/max-file-size.js +59 -0
  569. package/dist/core/code/engine/core/ingestion/utils/method-props.js +198 -0
  570. package/dist/core/code/engine/core/ingestion/utils/qualified-name.js +73 -0
  571. package/dist/core/code/engine/core/ingestion/utils/receiver-chain-captures.js +60 -0
  572. package/dist/core/code/engine/core/ingestion/utils/receiver-chain-codec.js +188 -0
  573. package/dist/core/code/engine/core/ingestion/utils/scope-tree-walk.js +36 -0
  574. package/dist/core/code/engine/core/ingestion/utils/symbol-labels.js +48 -0
  575. package/dist/core/code/engine/core/ingestion/utils/template-arguments.js +187 -0
  576. package/dist/core/code/engine/core/ingestion/utils/type-parameters.js +209 -0
  577. package/dist/core/code/engine/core/ingestion/utils/verbose.js +6 -0
  578. package/dist/core/code/engine/core/ingestion/variable-extractors/configs/c-cpp.js +133 -0
  579. package/dist/core/code/engine/core/ingestion/variable-extractors/configs/csharp.js +66 -0
  580. package/dist/core/code/engine/core/ingestion/variable-extractors/configs/dart.js +111 -0
  581. package/dist/core/code/engine/core/ingestion/variable-extractors/configs/go.js +153 -0
  582. package/dist/core/code/engine/core/ingestion/variable-extractors/configs/jvm.js +145 -0
  583. package/dist/core/code/engine/core/ingestion/variable-extractors/configs/php.js +61 -0
  584. package/dist/core/code/engine/core/ingestion/variable-extractors/configs/python.js +104 -0
  585. package/dist/core/code/engine/core/ingestion/variable-extractors/configs/ruby.js +55 -0
  586. package/dist/core/code/engine/core/ingestion/variable-extractors/configs/rust.js +79 -0
  587. package/dist/core/code/engine/core/ingestion/variable-extractors/configs/swift.js +91 -0
  588. package/dist/core/code/engine/core/ingestion/variable-extractors/configs/typescript-javascript.js +86 -0
  589. package/dist/core/code/engine/core/ingestion/variable-extractors/generic.js +111 -0
  590. package/dist/core/code/engine/core/ingestion/variable-types.js +3 -0
  591. package/dist/core/code/engine/core/ingestion/vue-sfc-extractor.js +544 -0
  592. package/dist/core/code/engine/core/ingestion/workers/callable-id.js +122 -0
  593. package/dist/core/code/engine/core/ingestion/workers/clone-safety.js +470 -0
  594. package/dist/core/code/engine/core/ingestion/workers/parse-worker.js +2456 -0
  595. package/dist/core/code/engine/core/ingestion/workers/post-result.js +90 -0
  596. package/dist/core/code/engine/core/ingestion/workers/quarantine.js +41 -0
  597. package/dist/core/code/engine/core/ingestion/workers/result-merge.js +59 -0
  598. package/dist/core/code/engine/core/ingestion/workers/worker-pool.js +1725 -0
  599. package/dist/core/code/engine/core/ingestion/workspace-config.js +139 -0
  600. package/dist/core/code/engine/core/lbug/conn-lock.js +72 -0
  601. package/dist/core/code/engine/core/lbug/csv-generator.js +710 -0
  602. package/dist/core/code/engine/core/lbug/cypher-escape.js +24 -0
  603. package/dist/core/code/engine/core/lbug/extension-load-error.js +339 -0
  604. package/dist/core/code/engine/core/lbug/extension-loader.js +261 -0
  605. package/dist/core/code/engine/core/lbug/graph-emit-sink.js +584 -0
  606. package/dist/core/code/engine/core/lbug/lbug-adapter.js +2703 -0
  607. package/dist/core/code/engine/core/lbug/lbug-config.js +1029 -0
  608. package/dist/core/code/engine/core/lbug/native-check.js +435 -0
  609. package/dist/core/code/engine/core/lbug/pool-adapter.js +973 -0
  610. package/dist/core/code/engine/core/lbug/query-params.js +25 -0
  611. package/dist/core/code/engine/core/lbug/query-result-utils.js +31 -0
  612. package/dist/core/code/engine/core/lbug/rel-pair-routing.js +373 -0
  613. package/dist/core/code/engine/core/lbug/schema.js +771 -0
  614. package/dist/core/code/engine/core/lbug/shutdown-helpers.js +40 -0
  615. package/dist/core/code/engine/core/lbug/sidecar-recovery.js +716 -0
  616. package/dist/core/code/engine/core/lbug/stdio-capture.js +49 -0
  617. package/dist/core/code/engine/core/lbug/sync-csv-writer.js +115 -0
  618. package/dist/core/code/engine/core/lbug/wal-checkpoint-driver.js +215 -0
  619. package/dist/core/code/engine/core/lbug/wal-driver-state.js +28 -0
  620. package/dist/core/code/engine/core/logger.js +339 -0
  621. package/dist/core/code/engine/core/platform/capabilities.js +91 -0
  622. package/dist/core/code/engine/core/run-analyze.js +1098 -0
  623. package/dist/core/code/engine/core/tree-sitter/parser-loader.js +281 -0
  624. package/dist/core/code/engine/core/tree-sitter/safe-parse.js +258 -0
  625. package/dist/core/code/engine/core/tree-sitter/vendored-grammars.js +64 -0
  626. package/dist/core/code/engine/lib/utils.js +121 -0
  627. package/dist/core/code/engine/mcp/core/lbug-adapter.js +27 -0
  628. package/dist/core/code/engine/mcp/local/aop-metadata.js +230 -0
  629. package/dist/core/code/engine/mcp/local/bean-metadata.js +49 -0
  630. package/dist/core/code/engine/mcp/local/limits.js +15 -0
  631. package/dist/core/code/engine/mcp/local/line-display.js +6 -0
  632. package/dist/core/code/engine/mcp/local/local-backend.js +4142 -0
  633. package/dist/core/code/engine/storage/branch-index.js +72 -0
  634. package/dist/core/code/engine/storage/file-hash.js +95 -0
  635. package/dist/core/code/engine/storage/fs-atomic.js +34 -0
  636. package/dist/core/code/engine/storage/git.js +555 -0
  637. package/dist/core/code/engine/storage/index-lock.js +664 -0
  638. package/dist/core/code/engine/storage/parse-cache.js +650 -0
  639. package/dist/core/code/engine/storage/parsedfile-store.js +620 -0
  640. package/dist/core/code/engine/storage/repo-manager.js +1061 -0
  641. package/dist/core/code/engine/storage/scope-index-store.js +247 -0
  642. package/dist/core/code/engine/types/pipeline.js +2 -0
  643. package/dist/core/code/scripts/install-duckdb-extension.mjs +125 -0
  644. package/dist/core/code/scripts/resolve-analyze-cmd.cjs +346 -0
  645. package/dist/core/code/shared/graph/types.js +8 -0
  646. package/dist/core/code/shared/index.js +105 -0
  647. package/dist/core/code/shared/integrations/circuit-breaker.js +242 -0
  648. package/dist/core/code/shared/integrations/resilient-fetch.js +224 -0
  649. package/dist/core/code/shared/integrations/retry.js +70 -0
  650. package/dist/core/code/shared/integrations/understand-quickly.js +145 -0
  651. package/dist/core/code/shared/language-detection.js +162 -0
  652. package/dist/core/code/shared/languages.js +27 -0
  653. package/dist/core/code/shared/lbug/schema-constants.js +98 -0
  654. package/dist/core/code/shared/mro-strategy.js +2 -0
  655. package/dist/core/code/shared/pipeline.js +5 -0
  656. package/dist/core/code/shared/scope-resolution/callable-flow-site.js +11 -0
  657. package/dist/core/code/shared/scope-resolution/def-index.js +53 -0
  658. package/dist/core/code/shared/scope-resolution/evidence-weights.js +87 -0
  659. package/dist/core/code/shared/scope-resolution/finalize-algorithm.js +807 -0
  660. package/dist/core/code/shared/scope-resolution/language-classification.js +46 -0
  661. package/dist/core/code/shared/scope-resolution/method-dispatch-index.js +100 -0
  662. package/dist/core/code/shared/scope-resolution/module-scope-index.js +59 -0
  663. package/dist/core/code/shared/scope-resolution/origin-priority.js +23 -0
  664. package/dist/core/code/shared/scope-resolution/parsed-file.js +54 -0
  665. package/dist/core/code/shared/scope-resolution/position-index.js +136 -0
  666. package/dist/core/code/shared/scope-resolution/qualified-name-index.js +77 -0
  667. package/dist/core/code/shared/scope-resolution/reference-site.js +24 -0
  668. package/dist/core/code/shared/scope-resolution/registries/class-registry.js +32 -0
  669. package/dist/core/code/shared/scope-resolution/registries/context.js +52 -0
  670. package/dist/core/code/shared/scope-resolution/registries/evidence.js +152 -0
  671. package/dist/core/code/shared/scope-resolution/registries/field-registry.js +33 -0
  672. package/dist/core/code/shared/scope-resolution/registries/lookup-core.js +392 -0
  673. package/dist/core/code/shared/scope-resolution/registries/lookup-qualified.js +58 -0
  674. package/dist/core/code/shared/scope-resolution/registries/macro-registry.js +34 -0
  675. package/dist/core/code/shared/scope-resolution/registries/method-registry.js +34 -0
  676. package/dist/core/code/shared/scope-resolution/registries/tie-breaks.js +63 -0
  677. package/dist/core/code/shared/scope-resolution/resolve-type-ref.js +128 -0
  678. package/dist/core/code/shared/scope-resolution/scope-id.js +49 -0
  679. package/dist/core/code/shared/scope-resolution/scope-tree.js +225 -0
  680. package/dist/core/code/shared/scope-resolution/symbol-definition.js +12 -0
  681. package/dist/core/code/shared/scope-resolution/types.js +25 -0
  682. package/dist/core/code/shared/test-helpers.js +17 -0
  683. package/dist/core/code/vendor/leiden/index.cjs +355 -0
  684. package/dist/core/code/vendor/leiden/utils.cjs +419 -0
  685. package/dist/core/dbquery/cli.d.ts.map +1 -1
  686. package/dist/core/dbquery/cli.js +5 -4
  687. package/dist/core/dbquery/cli.js.map +1 -1
  688. package/dist/core/dbquery/config.d.ts.map +1 -1
  689. package/dist/core/dbquery/config.js +39 -3
  690. package/dist/core/dbquery/config.js.map +1 -1
  691. package/dist/core/dbquery/drivers/http.d.ts +23 -0
  692. package/dist/core/dbquery/drivers/http.d.ts.map +1 -0
  693. package/dist/core/dbquery/drivers/http.js +125 -0
  694. package/dist/core/dbquery/drivers/http.js.map +1 -0
  695. package/dist/core/dbquery/drivers/mysql.js +1 -1
  696. package/dist/core/dbquery/drivers/mysql.js.map +1 -1
  697. package/dist/core/dbquery/drivers/postgres.js +1 -1
  698. package/dist/core/dbquery/drivers/postgres.js.map +1 -1
  699. package/dist/core/dbquery/executor.d.ts.map +1 -1
  700. package/dist/core/dbquery/executor.js +46 -12
  701. package/dist/core/dbquery/executor.js.map +1 -1
  702. package/dist/core/dbquery/format.d.ts.map +1 -1
  703. package/dist/core/dbquery/format.js +2 -1
  704. package/dist/core/dbquery/format.js.map +1 -1
  705. package/dist/core/dbquery/init.d.ts.map +1 -1
  706. package/dist/core/dbquery/init.js +7 -0
  707. package/dist/core/dbquery/init.js.map +1 -1
  708. package/dist/core/dbquery/types.d.ts +55 -7
  709. package/dist/core/dbquery/types.d.ts.map +1 -1
  710. package/dist/core/directory.d.ts +17 -0
  711. package/dist/core/directory.d.ts.map +1 -1
  712. package/dist/core/directory.js +37 -0
  713. package/dist/core/directory.js.map +1 -1
  714. package/dist/core/features/cli.d.ts.map +1 -1
  715. package/dist/core/features/cli.js +3 -2
  716. package/dist/core/features/cli.js.map +1 -1
  717. package/dist/core/features/scanner.d.ts +1 -1
  718. package/dist/core/features/scanner.d.ts.map +1 -1
  719. package/dist/core/features/scanner.js +20 -4
  720. package/dist/core/features/scanner.js.map +1 -1
  721. package/dist/core/markdown/cli.d.ts.map +1 -1
  722. package/dist/core/markdown/cli.js +9 -8
  723. package/dist/core/markdown/cli.js.map +1 -1
  724. package/dist/core/timeline/cli.d.ts.map +1 -1
  725. package/dist/core/timeline/cli.js +13 -6
  726. package/dist/core/timeline/cli.js.map +1 -1
  727. package/dist/core/timeline/debris.d.ts +20 -0
  728. package/dist/core/timeline/debris.d.ts.map +1 -0
  729. package/dist/core/timeline/debris.js +124 -0
  730. package/dist/core/timeline/debris.js.map +1 -0
  731. package/dist/core/timeline/hook-runner.d.ts.map +1 -1
  732. package/dist/core/timeline/hook-runner.js +3 -1
  733. package/dist/core/timeline/hook-runner.js.map +1 -1
  734. package/dist/core/timeline/hooks.d.ts.map +1 -1
  735. package/dist/core/timeline/hooks.js +13 -5
  736. package/dist/core/timeline/hooks.js.map +1 -1
  737. package/dist/core/timeline/installer.d.ts +2 -0
  738. package/dist/core/timeline/installer.d.ts.map +1 -1
  739. package/dist/core/timeline/installer.js +12 -0
  740. package/dist/core/timeline/installer.js.map +1 -1
  741. package/dist/core/timeline/project-root.d.ts +21 -0
  742. package/dist/core/timeline/project-root.d.ts.map +1 -0
  743. package/dist/core/timeline/project-root.js +83 -0
  744. package/dist/core/timeline/project-root.js.map +1 -0
  745. package/dist/web/register.d.ts.map +1 -1
  746. package/dist/web/register.js +2 -1
  747. package/dist/web/register.js.map +1 -1
  748. package/package.json +1 -1
  749. package/dist/.claude-template/.cgraphx/timeline-current-turn.json +0 -1
  750. package/dist/.claude-template/skills/.cgraphx/timeline-current-turn.json +0 -1
  751. package/dist/.claude-template/skills/handdrawn-deck/.cgraphx/timeline-current-turn.json +0 -1
  752. package/dist/.claude-template/skills/handdrawn-deck/references/.cgraphx/timeline-current-turn.json +0 -1
  753. package/dist/.claude-template/skills/handdrawn-sketch-engine/.cgraphx/timeline-current-turn.json +0 -1
  754. /package/dist/.claude-template/skills/run-api-test/assets/{template-test-verify.jsonl → template-/346/216/245/345/217/243/346/265/213/350/257/225/351/252/214/350/257/201.jsonl"} +0 -0
@@ -0,0 +1,2703 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.resolveGateRows = exports.readIndexCatalogSnapshot = exports.INDEX_CATALOG_UNREADABLE = exports.readIndexCatalogRows = exports.indexRowType = exports.indexRowTable = exports.createVectorIndex = exports.loadVectorExtension = exports.deleteSpringAutoConfigurationSyntheticClasses = exports.deleteSpringAutoConfigurationDeclarations = exports.deleteSpringAopEvidenceNodes = exports.deleteAllAdvisedBy = exports.deleteAllInjects = exports.deleteAllCallSummaries = exports.deleteAllInterprocTaintPaths = exports.deleteAllCommunitiesAndProcesses = exports.queryImportersBatch = exports.queryImporters = exports.getEmbeddingTableName = exports.deleteNodesForFiles = exports.DELETE_FILES_CHUNK_SIZE = exports.deleteNodesForFile = exports.isLbugReady = exports.wipeLbugDbFiles = exports.LbugWipeError = exports.closeLbug = exports.closeLbugBeforeExit = exports.safeClose = exports.tryFlushWAL = exports.flushWAL = exports.fetchExistingEmbeddingHashes = exports.loadCachedEmbeddings = exports.getLbugStats = exports.executeWithReusedStatement = exports.executePrepared = exports.streamQuery = exports.executeQuery = exports.batchInsertNodesToLbug = exports.insertNodeToLbug = exports.getCopyQuery = exports.fallbackRelationshipInserts = exports.COPY_CSV_OPTS = exports.loadGraphToLbug = exports.withLbugDb = exports.initLbug = exports._initLockPathForTest = exports.acquireInitLock = exports.isReadOnlyDbError = exports.getDatabase = exports.splitRelCsvByLabelPair = void 0;
40
+ exports.ensureEmbeddingRowDmlSafe = void 0;
41
+ const promises_1 = __importDefault(require("fs/promises"));
42
+ const fs_1 = require("fs");
43
+ const readline_1 = require("readline");
44
+ const events_1 = require("events");
45
+ const promises_2 = require("stream/promises");
46
+ const path_1 = __importDefault(require("path"));
47
+ const lbug = __importStar(require("@ladybugdb/core"));
48
+ const query_result_utils_js_1 = require("./query-result-utils.js");
49
+ const cypher_escape_js_1 = require("./cypher-escape.js");
50
+ const conn_lock_js_1 = require("./conn-lock.js");
51
+ const wal_driver_state_js_1 = require("./wal-driver-state.js");
52
+ const schema_js_1 = require("./schema.js");
53
+ // Analyze-only, but reached from backend startup via `pool-adapter.js`. #2802
54
+ // proposed lazy-importing it; rejected — deferring here relocates the startup
55
+ // cost to first query rather than removing it. The measured figures live in
56
+ // #2802; they were environment-bound, this is not.
57
+ const csv_generator_js_1 = require("./csv-generator.js");
58
+ const rel_pair_routing_js_1 = require("./rel-pair-routing.js");
59
+ // embeddings 子系统已删除——EMBEDDABLE_LABELS 在此局部定义以维持文件路径节点删除
60
+ // 查询的标签匹配(增量写回已删,但全量重建的清表路径仍需枚举可嵌入标签)。
61
+ const EMBEDDABLE_LABELS = [
62
+ 'Function', 'Method', 'Constructor', 'Class', 'Interface', 'Struct',
63
+ 'Enum', 'Trait', 'Impl', 'Macro',
64
+ 'TypeAlias', 'TypeDef', 'Const', 'Property', 'Record', 'Union', 'Static', 'Variable',
65
+ // Kept in sync with DOC_BEARING_LABELS (ast-helpers.ts) so every doc-bearing
66
+ // label's leading-doc description remains semantically matchable (#2270).
67
+ 'Namespace',
68
+ ];
69
+ const extension_loader_js_1 = require("./extension-loader.js");
70
+ const lbug_config_js_1 = require("./lbug-config.js");
71
+ const sidecar_recovery_js_1 = require("./sidecar-recovery.js");
72
+ const logger_js_1 = require("../logger.js");
73
+ const auto_configuration_js_1 = require("../ingestion/frameworks/spring/auto-configuration.js");
74
+ const aop_js_1 = require("../ingestion/frameworks/spring/aop.js");
75
+ /**
76
+ * Split a relationship CSV into per-label-pair files on disk.
77
+ *
78
+ * @internal RETAINED AS A DIFFERENTIAL ORACLE. As of #2203 U2, production emit
79
+ * routes relationships to per-pair files directly during the single pass (see
80
+ * RelPairRouter in `rel-pair-routing.ts`), so this function has NO production
81
+ * callers — it is kept ONLY so the byte-identity test in
82
+ * `test/integration/csv-pipeline.test.ts` ("direct per-pair emit matches the
83
+ * split oracle") can diff the direct-emit output against this proven path. Do
84
+ * NOT delete it as dead code without also removing that test and accepting the
85
+ * loss of the byte-identity guard (and likewise `test/unit/rel-csv-split.test.ts`).
86
+ *
87
+ * Streams the CSV line-by-line, routing each relationship to a file named
88
+ * `rel_{fromLabel}_{toLabel}.csv`. Handles backpressure correctly: only one
89
+ * drain listener per stream at a time, and readline resumes only when ALL
90
+ * backpressured streams have drained.
91
+ *
92
+ * @param csvPath Path to the combined relationship CSV
93
+ * @param csvDir Directory to write per-pair CSV files
94
+ * @param validTables Set of valid node table names
95
+ * @param getNodeLabel Function to extract the label from a node ID
96
+ * @param wsFactory Optional WriteStream factory (defaults to fs.createWriteStream)
97
+ */
98
+ const splitRelCsvByLabelPair = async (csvPath, csvDir, validTables, getNodeLabel, wsFactory = (p) => (0, fs_1.createWriteStream)(p, 'utf-8')) => {
99
+ let relHeader = '';
100
+ const relsByPairMeta = new Map();
101
+ const pairWriteStreams = new Map();
102
+ let skippedRels = 0;
103
+ let totalValidRels = 0;
104
+ const inputStream = (0, fs_1.createReadStream)(csvPath, 'utf-8');
105
+ const rl = (0, readline_1.createInterface)({ input: inputStream, crlfDelay: Infinity });
106
+ // If any pair WriteStream errors (disk full, EMFILE, etc.) or the input
107
+ // stream fails, we need to abort the pending `once(ws, 'drain')` await.
108
+ // An AbortController gives us one signal to cancel all pending waits
109
+ // without a custom state machine.
110
+ const abortOnError = new AbortController();
111
+ let streamError = null;
112
+ const markStreamError = (err) => {
113
+ streamError ??= err;
114
+ abortOnError.abort(err);
115
+ };
116
+ try {
117
+ // `for await (const line of rl)` replaces the old manual
118
+ // on('line')/pause()/resume()/waitingForDrain state machine: readline's
119
+ // async iterator naturally serializes line delivery with our awaits, so
120
+ // at most one ws can be in backpressure at a time and we just await its
121
+ // 'drain' event.
122
+ let isFirst = true;
123
+ for await (const line of rl) {
124
+ if (streamError)
125
+ throw streamError;
126
+ if (isFirst) {
127
+ relHeader = line;
128
+ isFirst = false;
129
+ continue;
130
+ }
131
+ if (!line.trim())
132
+ continue;
133
+ const match = line.match(/"([^"]*)","([^"]*)"/);
134
+ if (!match) {
135
+ skippedRels++;
136
+ continue;
137
+ }
138
+ const fromLabel = getNodeLabel(match[1]);
139
+ const toLabel = getNodeLabel(match[2]);
140
+ if (!validTables.has(fromLabel) || !validTables.has(toLabel)) {
141
+ skippedRels++;
142
+ continue;
143
+ }
144
+ const pairKey = `${fromLabel}|${toLabel}`;
145
+ let ws = pairWriteStreams.get(pairKey);
146
+ if (!ws) {
147
+ const pairCsvPath = path_1.default.join(csvDir, `rel_${fromLabel}_${toLabel}.csv`);
148
+ ws = wsFactory(pairCsvPath);
149
+ ws.on('error', markStreamError);
150
+ pairWriteStreams.set(pairKey, ws);
151
+ relsByPairMeta.set(pairKey, { csvPath: pairCsvPath, rows: 0 });
152
+ if (!ws.write(relHeader + '\n')) {
153
+ await (0, events_1.once)(ws, 'drain', { signal: abortOnError.signal });
154
+ }
155
+ }
156
+ if (!ws.write(line + '\n')) {
157
+ await (0, events_1.once)(ws, 'drain', { signal: abortOnError.signal });
158
+ }
159
+ relsByPairMeta.get(pairKey).rows++;
160
+ totalValidRels++;
161
+ }
162
+ if (streamError)
163
+ throw streamError;
164
+ }
165
+ catch (err) {
166
+ // Tear down everything so no fd is left dangling. If the abort was caused
167
+ // by a stream error, rethrow that error (more actionable than AbortError).
168
+ for (const ws of pairWriteStreams.values())
169
+ ws.destroy();
170
+ inputStream.destroy();
171
+ throw streamError ?? err;
172
+ }
173
+ finally {
174
+ // Readline 'close' fires before the underlying fs.ReadStream releases its
175
+ // fd — on Windows that race caused ENOTEMPTY on the parent dir.
176
+ // stream/promises.finished is the stdlib "wait until this stream is fully
177
+ // closed" primitive and handles both success and error paths.
178
+ await (0, promises_2.finished)(inputStream).catch(() => { });
179
+ }
180
+ return { relHeader, relsByPairMeta, pairWriteStreams, skippedRels, totalValidRels };
181
+ };
182
+ exports.splitRelCsvByLabelPair = splitRelCsvByLabelPair;
183
+ let db = null;
184
+ let conn = null;
185
+ // Serialize every operation on the shared singleton `conn`. LadybugDB's
186
+ // Connection is single-writer and is NOT safe for concurrent query execution;
187
+ // the periodic WAL-checkpoint driver overlapping a long `--pdg` COPY on this
188
+ // connection corrupted native state (`double free or corruption`). Each
189
+ // singleton-`conn` helper below runs its full query + drain inside withConnLock.
190
+ // Invariant: a wrapped helper MUST NOT call another wrapped helper (re-entry
191
+ // self-deadlocks); all current holders are leaf-level. `streamQuery` is
192
+ // deliberately NOT wrapped — its per-row callback can re-enter the adapter and
193
+ // it only runs on the read path where the checkpoint driver is inactive.
194
+ // See conn-lock.ts for the full rationale.
195
+ //
196
+ // The gate that decides whether an op must take withConnLock: only operations on
197
+ // the shared singleton `conn` serialize. Per-file / temp connections (distinct
198
+ // native objects with no shared engine state) must NOT block on — or be blocked
199
+ // by — the singleton's lock. Reads the live `conn` binding at call time (it's
200
+ // reassigned only at open/close, never mid-load).
201
+ const isSharedSingletonConn = (c) => c === conn;
202
+ let currentDbPath = null;
203
+ let currentDbReadOnly = false;
204
+ let vectorExtensionLoaded = false;
205
+ // In-process guard so a repeated createVectorIndex() within one connection
206
+ // lifetime skips the DB round-trip. Reset wherever vectorExtensionLoaded
207
+ // resets, so it can never stay true against a swapped or closed connection.
208
+ let vectorIndexEnsured = false;
209
+ /**
210
+ * Check if an error indicates a missing column or table (schema-level problem)
211
+ * rather than a transient/connection error. Used for legacy DB fallback logic.
212
+ */
213
+ const isMissingColumnOrTableError = (msg) => msg.includes('does not exist') ||
214
+ // Kuzu-specific: "(table|column|property) ... not found" — narrow enough to avoid
215
+ // matching transient errors like "connection not found" or "key not found".
216
+ /(table|column|property).*not found/i.test(msg);
217
+ /** Expose the current Database for pool adapter reuse in tests. */
218
+ const getDatabase = () => db;
219
+ exports.getDatabase = getDatabase;
220
+ // Global session lock for operations that touch module-level lbug globals.
221
+ // This guarantees no DB switch can happen while an operation is running.
222
+ let sessionLock = Promise.resolve();
223
+ /** Number of times to retry on a BUSY / lock-held error before giving up. */
224
+ const DB_LOCK_RETRY_ATTEMPTS = 3;
225
+ /** Base back-off in ms between BUSY retries (multiplied by attempt number). */
226
+ const DB_LOCK_RETRY_DELAY_MS = 500;
227
+ /**
228
+ * Return true when the error message indicates a write was attempted against
229
+ * a read-only LadybugDB connection. The read pool opens DBs read-only, so any
230
+ * path that calls a `CREATE_*` procedure there will surface this. Owners of
231
+ * the writable analyze path should ignore this error — index creation is owned
232
+ * by `cgraph analyze` and either already happened or will happen on the next run.
233
+ */
234
+ const isReadOnlyDbError = (err) => {
235
+ // Walk the `cause` chain (bounded) so a wrapped read-only error — e.g. the
236
+ // pool adapter's `new Error('…read-only.', { cause: nativeReadOnlyErr })` —
237
+ // is still detected by callers that only see the wrapper (#2068 follow-up).
238
+ // The same strict regex is re-applied at each level, so a non-read-only
239
+ // chain stays false; the depth bound guards a cyclic `cause`.
240
+ let cur = err;
241
+ for (let depth = 0; depth < 5 && cur != null; depth++) {
242
+ const msg = cur instanceof Error ? cur.message : String(cur);
243
+ if (/read-only database/i.test(msg))
244
+ return true;
245
+ cur = cur instanceof Error ? cur.cause : undefined;
246
+ }
247
+ return false;
248
+ };
249
+ exports.isReadOnlyDbError = isReadOnlyDbError;
250
+ const isMissingFileError = (err) => {
251
+ const errno = err;
252
+ return errno?.code === 'ENOENT';
253
+ };
254
+ const extractErrnoCode = (err) => {
255
+ const errno = err;
256
+ return errno?.code;
257
+ };
258
+ const MAX_LOGGED_ERROR_MESSAGE_LENGTH = 160;
259
+ const summarizeError = (err) => (err instanceof Error ? err.message : String(err)).slice(0, MAX_LOGGED_ERROR_MESSAGE_LENGTH);
260
+ // ---------------------------------------------------------------------------
261
+ // Cross-process init lock
262
+ //
263
+ // Prevents a TOCTOU race in orphan sidecar cleanup: between checking that
264
+ // the main DB file is missing and unlinking sidecars, another process could
265
+ // create a fresh DB. The lock file (`${dbPath}.init.lock`) is created with
266
+ // O_CREAT | O_EXCL (atomic create-or-fail) and contains the owning PID +
267
+ // timestamp so stale locks from crashed processes can be reclaimed.
268
+ // ---------------------------------------------------------------------------
269
+ /** Maximum age (ms) before an init lock is considered stale. */
270
+ const INIT_LOCK_STALE_MS = 30_000;
271
+ /** Maximum attempts to acquire the init lock before giving up. */
272
+ const INIT_LOCK_MAX_ATTEMPTS = 6;
273
+ /** Delay between lock-acquisition retries (ms). */
274
+ const INIT_LOCK_RETRY_DELAY_MS = 500;
275
+ const initLockPath = (dbPath) => `${dbPath}.init.lock`;
276
+ /**
277
+ * Returns true when the process identified by `pid` is still running.
278
+ * Uses `process.kill(pid, 0)` which sends signal 0 (a no-op probe) —
279
+ * it throws ESRCH when the process does not exist.
280
+ */
281
+ const isProcessAlive = (pid) => {
282
+ try {
283
+ process.kill(pid, 0);
284
+ return true;
285
+ }
286
+ catch {
287
+ return false;
288
+ }
289
+ };
290
+ /**
291
+ * Try to break a stale lock whose owning process has exited.
292
+ * Returns `true` if the stale lock was removed (caller should retry acquire).
293
+ * Returns `false` if the lock is still valid (another live process owns it).
294
+ */
295
+ const tryBreakStaleLock = async (lockPath) => {
296
+ try {
297
+ const content = await promises_1.default.readFile(lockPath, 'utf-8');
298
+ const parsed = JSON.parse(content);
299
+ // If the owning process is still alive AND the lock is not stale, don't break.
300
+ if (typeof parsed.pid === 'number' && isProcessAlive(parsed.pid)) {
301
+ // Even a live process's lock can be stale if it's been held too long
302
+ // (e.g. the process is hung). Check the timestamp.
303
+ if (typeof parsed.ts === 'number' && Date.now() - parsed.ts < INIT_LOCK_STALE_MS) {
304
+ return false;
305
+ }
306
+ }
307
+ // PID is gone or lock exceeded INIT_LOCK_STALE_MS — reclaim it.
308
+ await promises_1.default.unlink(lockPath);
309
+ logger_js_1.logger.warn(`GitNexus: removed stale init lock (pid=${parsed.pid ?? '?'}, age=${typeof parsed.ts === 'number' ? `${Date.now() - parsed.ts}ms` : '?'})`);
310
+ return true;
311
+ }
312
+ catch (err) {
313
+ // Lock file disappeared between our read and unlink, or is unreadable.
314
+ // Either way, let the caller retry the acquire.
315
+ if (isMissingFileError(err))
316
+ return true;
317
+ // Permission error or corrupt content — log and let caller retry.
318
+ const code = extractErrnoCode(err);
319
+ logger_js_1.logger.warn(`GitNexus: unable to inspect init lock (${code ?? 'UNKNOWN'}): ${summarizeError(err)}`);
320
+ return false;
321
+ }
322
+ };
323
+ /**
324
+ * Acquire a cross-process init lock for `dbPath`.
325
+ * Uses `O_CREAT | O_EXCL` for atomic create-or-fail semantics.
326
+ *
327
+ * Returns a release function that removes the lock file. The release
328
+ * function is idempotent and safe to call even if the lock was already
329
+ * cleaned up externally.
330
+ *
331
+ * Throws if the lock cannot be acquired after `INIT_LOCK_MAX_ATTEMPTS`.
332
+ */
333
+ const acquireInitLock = async (dbPath) => {
334
+ const lockPath = initLockPath(dbPath);
335
+ const payload = JSON.stringify({ pid: process.pid, ts: Date.now() });
336
+ // Ensure the parent directory exists before creating the lock file.
337
+ // On a fresh repo the `.cgraphx/code/` directory may not exist yet, and
338
+ // fs.open with O_CREAT | O_EXCL would fail with ENOENT.
339
+ await promises_1.default.mkdir(path_1.default.dirname(lockPath), { recursive: true });
340
+ for (let attempt = 1; attempt <= INIT_LOCK_MAX_ATTEMPTS; attempt++) {
341
+ try {
342
+ const handle = await promises_1.default.open(lockPath, fs_1.constants.O_CREAT | fs_1.constants.O_EXCL | fs_1.constants.O_WRONLY);
343
+ await handle.writeFile(payload);
344
+ await handle.close();
345
+ // Return the idempotent release function
346
+ return async () => {
347
+ try {
348
+ await promises_1.default.unlink(lockPath);
349
+ }
350
+ catch (err) {
351
+ if (!isMissingFileError(err)) {
352
+ const code = extractErrnoCode(err);
353
+ logger_js_1.logger.warn(`GitNexus: failed to release init lock (${code ?? 'UNKNOWN'}): ${summarizeError(err)}`);
354
+ }
355
+ }
356
+ };
357
+ }
358
+ catch (err) {
359
+ if (err?.code !== 'EEXIST') {
360
+ throw err; // Unexpected error — propagate immediately
361
+ }
362
+ // Lock file exists — check if it's stale
363
+ const broken = await tryBreakStaleLock(lockPath);
364
+ if (broken && attempt < INIT_LOCK_MAX_ATTEMPTS) {
365
+ continue; // Stale lock removed — retry immediately
366
+ }
367
+ if (attempt === INIT_LOCK_MAX_ATTEMPTS) {
368
+ throw new Error(`GitNexus: unable to acquire init lock after ${INIT_LOCK_MAX_ATTEMPTS} attempts — ` +
369
+ `another gitnexus process may be initializing the same database (${lockPath})`);
370
+ }
371
+ // Live process holds the lock — wait and retry
372
+ await new Promise((resolve) => setTimeout(resolve, INIT_LOCK_RETRY_DELAY_MS));
373
+ }
374
+ }
375
+ // Unreachable — loop always throws or returns
376
+ throw new Error('GitNexus: init lock acquisition failed unexpectedly');
377
+ };
378
+ exports.acquireInitLock = acquireInitLock;
379
+ /** Exported for testing — returns the lock file path for a given dbPath. */
380
+ exports._initLockPathForTest = initLockPath;
381
+ const runWithSessionLock = async (operation) => {
382
+ const previous = sessionLock;
383
+ let release = null;
384
+ sessionLock = new Promise((resolve) => {
385
+ release = resolve;
386
+ });
387
+ await previous;
388
+ try {
389
+ return await operation();
390
+ }
391
+ finally {
392
+ release?.();
393
+ }
394
+ };
395
+ const normalizeCopyPath = (filePath) => (0, lbug_config_js_1.toNativeSafePath)(filePath).replace(/\\/g, '/');
396
+ // Single-result convenience wrapper over the shared best-effort closer
397
+ // (drainQueryResult / readQueryRows close one cursor at a time).
398
+ const closeQueryResult = async (result) => {
399
+ await (0, query_result_utils_js_1.closeQueryResults)(result);
400
+ };
401
+ const drainQueryResult = async (queryResult) => {
402
+ const results = Array.isArray(queryResult) ? queryResult : [queryResult];
403
+ let firstError;
404
+ let hasError = false;
405
+ for (const result of results) {
406
+ try {
407
+ await result.getAll();
408
+ }
409
+ catch (err) {
410
+ if (!hasError) {
411
+ firstError = err;
412
+ hasError = true;
413
+ }
414
+ }
415
+ finally {
416
+ await closeQueryResult(result);
417
+ }
418
+ }
419
+ if (hasError)
420
+ throw firstError;
421
+ };
422
+ const readQueryRows = async (queryResult) => {
423
+ const results = Array.isArray(queryResult) ? queryResult : [queryResult];
424
+ let rows = [];
425
+ let firstError;
426
+ let hasError = false;
427
+ for (let i = 0; i < results.length; i++) {
428
+ const result = results[i];
429
+ try {
430
+ const resultRows = await result.getAll();
431
+ if (i === 0)
432
+ rows = resultRows;
433
+ }
434
+ catch (err) {
435
+ if (!hasError) {
436
+ firstError = err;
437
+ hasError = true;
438
+ }
439
+ }
440
+ finally {
441
+ await closeQueryResult(result);
442
+ }
443
+ }
444
+ if (hasError)
445
+ throw firstError;
446
+ return rows;
447
+ };
448
+ const queryAndDrain = async (targetConn, cypher) => {
449
+ const run = async () => {
450
+ const queryResult = await targetConn.query(cypher);
451
+ await drainQueryResult(queryResult);
452
+ };
453
+ // Serialize only when this runs on the shared singleton connection (the bulk
454
+ // node/relationship COPY captures `writeConn = conn`); per-file / temp
455
+ // connections skip the lock — see isSharedSingletonConn.
456
+ return isSharedSingletonConn(targetConn) ? (0, conn_lock_js_1.withConnLock)(run) : run();
457
+ };
458
+ // determinism: probe — existence only. Every call site runs this through
459
+ // `queryAndDrain`, which drains and discards the rows; the ONLY observable is
460
+ // whether the read-only shadow replay throws, so no row identity is read.
461
+ const READ_ONLY_SHADOW_REPLAY_PROBE = 'MATCH (n) RETURN n LIMIT 1';
462
+ /**
463
+ * Serve-side entry to the shared WAL-quarantine safety gate. Refuses (throws)
464
+ * when the `.shadow` is present on disk or the orphan WAL is too large to
465
+ * safely discard; returns silently otherwise. The policy itself lives in
466
+ * `guardWalQuarantine` (sidecar-recovery.ts) so serve and the MCP pool share
467
+ * one source of truth (PR #1747 review D2; issue #2382 review, Finding B).
468
+ */
469
+ const refuseLargeWalQuarantine = async (dbPath, mode, triggeringErr) => {
470
+ await (0, sidecar_recovery_js_1.guardWalQuarantine)(dbPath, mode, triggeringErr, logger_js_1.logger);
471
+ };
472
+ const reopenReadOnlyAfterMissingShadow = async (dbPath, err) => {
473
+ await refuseLargeWalQuarantine(dbPath, 'read-only', err);
474
+ try {
475
+ await (0, sidecar_recovery_js_1.quarantineWalForMissingShadow)(dbPath, {
476
+ logger: logger_js_1.logger,
477
+ level: 'warn',
478
+ reason: 'read-only recovery',
479
+ });
480
+ }
481
+ catch (renameErr) {
482
+ throw new Error((0, sidecar_recovery_js_1.renameFailureMessage)(dbPath, renameErr));
483
+ }
484
+ const reopened = await (0, lbug_config_js_1.openLbugConnection)(lbug, dbPath, { readOnly: true });
485
+ try {
486
+ await queryAndDrain(reopened.conn, READ_ONLY_SHADOW_REPLAY_PROBE);
487
+ return reopened;
488
+ }
489
+ catch (retryErr) {
490
+ await (0, lbug_config_js_1.closeLbugConnection)(reopened);
491
+ if ((0, sidecar_recovery_js_1.isMissingShadowSidecarError)(retryErr) || (0, sidecar_recovery_js_1.isReadOnlyShadowReplayError)(retryErr)) {
492
+ throw new Error((0, sidecar_recovery_js_1.shadowSidecarRecoveryMessage)(dbPath, retryErr));
493
+ }
494
+ throw retryErr;
495
+ }
496
+ };
497
+ const reopenWritableAfterMissingShadow = async (dbPath, err) => {
498
+ await refuseLargeWalQuarantine(dbPath, 'writable', err);
499
+ try {
500
+ await (0, sidecar_recovery_js_1.quarantineWalForMissingShadow)(dbPath, {
501
+ logger: logger_js_1.logger,
502
+ level: 'warn',
503
+ reason: 'writable recovery',
504
+ });
505
+ }
506
+ catch (renameErr) {
507
+ throw new Error((0, sidecar_recovery_js_1.renameFailureMessage)(dbPath, renameErr));
508
+ }
509
+ return await (0, lbug_config_js_1.openLbugConnection)(lbug, dbPath);
510
+ };
511
+ const ensureReadOnlyConnectionUsable = async (dbPath, handle) => {
512
+ let shadowReplayErr;
513
+ try {
514
+ await queryAndDrain(handle.conn, READ_ONLY_SHADOW_REPLAY_PROBE);
515
+ return handle;
516
+ }
517
+ catch (err) {
518
+ if ((0, sidecar_recovery_js_1.isMissingShadowSidecarError)(err)) {
519
+ await (0, lbug_config_js_1.closeLbugConnection)(handle);
520
+ return await reopenReadOnlyAfterMissingShadow(dbPath, err);
521
+ }
522
+ if (!(0, sidecar_recovery_js_1.isReadOnlyShadowReplayError)(err)) {
523
+ await (0, lbug_config_js_1.closeLbugConnection)(handle);
524
+ throw err;
525
+ }
526
+ shadowReplayErr = err;
527
+ }
528
+ await (0, lbug_config_js_1.closeLbugConnection)(handle);
529
+ let writable;
530
+ try {
531
+ writable = await (0, lbug_config_js_1.openLbugConnection)(lbug, dbPath);
532
+ }
533
+ catch (openErr) {
534
+ const code = extractErrnoCode(openErr);
535
+ if (code === 'EROFS' || code === 'EACCES' || code === 'EPERM') {
536
+ throw new Error((0, sidecar_recovery_js_1.shadowSidecarRecoveryMessage)(dbPath, shadowReplayErr) +
537
+ '\n The workspace appears to be read-only — mount it read-write to perform shadow replay recovery,' +
538
+ ' or re-run `cgraph analyze` on a writable filesystem to rebuild the index.');
539
+ }
540
+ throw openErr;
541
+ }
542
+ let missingShadowError;
543
+ try {
544
+ await queryAndDrain(writable.conn, READ_ONLY_SHADOW_REPLAY_PROBE);
545
+ }
546
+ catch (err) {
547
+ if ((0, sidecar_recovery_js_1.isMissingShadowSidecarError)(err)) {
548
+ missingShadowError = err;
549
+ }
550
+ else {
551
+ throw err;
552
+ }
553
+ }
554
+ finally {
555
+ await (0, lbug_config_js_1.closeLbugConnection)(writable);
556
+ }
557
+ if (missingShadowError) {
558
+ return await reopenReadOnlyAfterMissingShadow(dbPath, missingShadowError);
559
+ }
560
+ const reopened = await (0, lbug_config_js_1.openLbugConnection)(lbug, dbPath, { readOnly: true });
561
+ try {
562
+ await queryAndDrain(reopened.conn, READ_ONLY_SHADOW_REPLAY_PROBE);
563
+ return reopened;
564
+ }
565
+ catch (err) {
566
+ await (0, lbug_config_js_1.closeLbugConnection)(reopened);
567
+ if ((0, sidecar_recovery_js_1.isMissingShadowSidecarError)(err)) {
568
+ throw new Error((0, sidecar_recovery_js_1.shadowSidecarRecoveryMessage)(dbPath, err));
569
+ }
570
+ throw err;
571
+ }
572
+ };
573
+ const resetOpenConnectionState = () => {
574
+ currentDbPath = null;
575
+ vectorExtensionLoaded = false;
576
+ vectorIndexEnsured = false;
577
+ };
578
+ const runSchemaCreationQueries = async (dbPath) => {
579
+ for (const schemaQuery of schema_js_1.SCHEMA_QUERIES) {
580
+ try {
581
+ await queryAndDrain(conn, schemaQuery);
582
+ }
583
+ catch (err) {
584
+ if ((0, sidecar_recovery_js_1.isMissingShadowSidecarError)(err)) {
585
+ return err;
586
+ }
587
+ const msg = err instanceof Error ? err.message : String(err);
588
+ // Suppression list:
589
+ // - "already exists": expected idempotent re-create on existing DBs
590
+ // - "could not set lock on file": LadybugDB v0.18.0 emits this on
591
+ // Windows when CREATE NODE TABLE runs against a path that was
592
+ // just opened (the WAL handle from a fresh Database briefly
593
+ // contests the table's first-write lock). The table is created
594
+ // anyway and any genuine cross-process lock contention surfaces
595
+ // on the next operation via withLbugDb's retry. Logging it here
596
+ // would just be noise in CI.
597
+ //
598
+ // WAL corruption: the first DDL write after DB open triggers WAL
599
+ // replay — if the WAL file was left in a corrupt state by an
600
+ // interrupted previous run, the native engine throws here. Rather
601
+ // than logging a WARN and continuing in a broken state, close the
602
+ // DB cleanly and surface an actionable error so the caller (serve,
603
+ // MCP, analyze) can exit with a clear recovery message.
604
+ if ((0, lbug_config_js_1.isWalCorruptionError)(err)) {
605
+ await (0, exports.safeClose)();
606
+ resetOpenConnectionState();
607
+ throw new Error(`LadybugDB WAL corruption detected at ${dbPath}. ${lbug_config_js_1.WAL_RECOVERY_SUGGESTION}\n` +
608
+ ` Original error: ${msg.slice(0, 200)}`);
609
+ }
610
+ if (!msg.includes('already exists') && !(0, lbug_config_js_1.isDbBusyError)(err) && !(0, exports.isReadOnlyDbError)(err)) {
611
+ logger_js_1.logger.warn(`⚠️ Schema creation warning: ${msg.slice(0, 120)}`);
612
+ }
613
+ }
614
+ }
615
+ return null;
616
+ };
617
+ const initLbug = async (dbPath) => {
618
+ return runWithSessionLock(() => ensureLbugInitialized(dbPath));
619
+ };
620
+ exports.initLbug = initLbug;
621
+ /**
622
+ * Execute multiple queries against one repo DB atomically.
623
+ * While the callback runs, no other request can switch the active DB.
624
+ *
625
+ * Automatically retries up to DB_LOCK_RETRY_ATTEMPTS times when the
626
+ * database is busy (e.g. `cgraph analyze` holds the write lock).
627
+ * Each retry waits DB_LOCK_RETRY_DELAY_MS * attempt milliseconds.
628
+ */
629
+ const withLbugDb = async (dbPath, operation, options = {}) => {
630
+ let lastError;
631
+ const readOnly = options.readOnly === true;
632
+ for (let attempt = 1; attempt <= DB_LOCK_RETRY_ATTEMPTS; attempt++) {
633
+ try {
634
+ return await runWithSessionLock(async () => {
635
+ await ensureLbugInitialized(dbPath, readOnly);
636
+ return operation();
637
+ });
638
+ }
639
+ catch (err) {
640
+ lastError = err;
641
+ // Skip outer retry when the inner open-retry already exhausted: the
642
+ // ~1.5s open-time budget was just spent, repeating the full reset+
643
+ // reopen cycle would only add 4-5s of tail latency without changing
644
+ // the outcome (both layers consult the same isDbBusyError matcher).
645
+ if (!(0, lbug_config_js_1.isDbBusyError)(err) || (0, lbug_config_js_1.isOpenRetryExhausted)(err) || attempt === DB_LOCK_RETRY_ATTEMPTS) {
646
+ throw err;
647
+ }
648
+ // Close stale connection inside the session lock to prevent race conditions
649
+ // with concurrent operations that might acquire the lock between cleanup steps
650
+ await runWithSessionLock(async () => {
651
+ await (0, exports.safeClose)();
652
+ currentDbPath = null;
653
+ vectorExtensionLoaded = false;
654
+ vectorIndexEnsured = false;
655
+ });
656
+ // Sleep outside the lock — no need to block others while waiting
657
+ await new Promise((resolve) => setTimeout(resolve, DB_LOCK_RETRY_DELAY_MS * attempt));
658
+ }
659
+ }
660
+ // This line is unreachable — the loop either returns or throws inside,
661
+ // but TypeScript needs an explicit throw to satisfy the return type.
662
+ throw lastError;
663
+ };
664
+ exports.withLbugDb = withLbugDb;
665
+ const ensureLbugInitialized = async (dbPath, readOnly = false) => {
666
+ if (conn && currentDbPath === dbPath && currentDbReadOnly === readOnly) {
667
+ return { db, conn };
668
+ }
669
+ await doInitLbug(dbPath, readOnly);
670
+ return { db, conn };
671
+ };
672
+ const doInitLbug = async (dbPath, readOnly = false) => {
673
+ // Different database requested — close the old one first
674
+ if (conn || db) {
675
+ await (0, exports.safeClose)();
676
+ currentDbPath = null;
677
+ vectorExtensionLoaded = false;
678
+ vectorIndexEnsured = false;
679
+ }
680
+ // ---------------------------------------------------------------------------
681
+ // Read-only fast path: skip all filesystem mutations (path cleanup, init
682
+ // lock, orphan sidecar removal, mkdir) so the open succeeds on read-only
683
+ // filesystems such as Docker `:ro` bind mounts. The init lock exists to
684
+ // prevent a TOCTOU race during DB *creation* — read-only opens never
685
+ // create databases and don't need the lock.
686
+ // ---------------------------------------------------------------------------
687
+ if (readOnly) {
688
+ await (0, sidecar_recovery_js_1.preflightLbugSidecars)(dbPath, {
689
+ mode: 'read-only',
690
+ logger: logger_js_1.logger,
691
+ allowQuarantine: false,
692
+ });
693
+ const opened = await (0, lbug_config_js_1.openLbugConnection)(lbug, dbPath, { readOnly: true });
694
+ const usable = await ensureReadOnlyConnectionUsable(dbPath, opened);
695
+ db = usable.db;
696
+ conn = usable.conn;
697
+ currentDbReadOnly = true;
698
+ }
699
+ else {
700
+ // LadybugDB stores the database as a single file (not a directory).
701
+ // If the path already exists, it must be a valid LadybugDB database file.
702
+ // Remove stale empty directories or files from older versions.
703
+ try {
704
+ const stat = await promises_1.default.lstat(dbPath);
705
+ if (stat.isSymbolicLink()) {
706
+ // Never follow symlinks — just remove the link itself
707
+ await promises_1.default.unlink(dbPath);
708
+ }
709
+ else if (stat.isDirectory()) {
710
+ // Verify path is within expected storage directory before deleting
711
+ const realPath = await promises_1.default.realpath(dbPath);
712
+ const parentDir = path_1.default.dirname(dbPath);
713
+ const realParent = await promises_1.default.realpath(parentDir);
714
+ const safePrefix = realParent.endsWith(path_1.default.sep) ? realParent : realParent + path_1.default.sep;
715
+ if (!realPath.startsWith(safePrefix) && realPath !== realParent) {
716
+ throw new Error(`Refusing to delete ${dbPath}: resolved path ${realPath} is outside storage directory`);
717
+ }
718
+ // Old-style directory database or empty leftover - remove it
719
+ await promises_1.default.rm(dbPath, { recursive: true, force: true });
720
+ }
721
+ // If it's a file, assume it's an existing LadybugDB database - LadybugDB will open it
722
+ }
723
+ catch (err) {
724
+ if (!isMissingFileError(err)) {
725
+ throw err;
726
+ }
727
+ // Path doesn't exist, which is what LadybugDB wants for a new database
728
+ }
729
+ // -------------------------------------------------------------------------
730
+ // Cross-process critical section: acquire init lock, clean orphan sidecars,
731
+ // and open the database. The lock prevents a TOCTOU race where another
732
+ // process could create a fresh DB between our access() check and the
733
+ // unlink() of stale sidecars.
734
+ // -------------------------------------------------------------------------
735
+ const releaseInitLock = await (0, exports.acquireInitLock)(dbPath);
736
+ try {
737
+ // Reclaim missing-shadow WAL quarantines from a PRIOR crash (#2637).
738
+ // LadybugDB renames an unrecoverable WAL aside as
739
+ // `${dbPath}.wal.missing-shadow.<ts>-<rand>` (quarantineWalForMissingShadow)
740
+ // instead of deleting it. Once quarantined it is permanently detached from
741
+ // the live store and never reopened, so reclaiming it is safe regardless of
742
+ // whether the main DB file exists this run — unlike the orphan-sidecar
743
+ // cleanup below, this must NOT be gated on "main DB missing": a quarantine
744
+ // event and a healthy main DB are independent facts. Never let a reclaim
745
+ // failure (e.g. a transient EBUSY from an antivirus scan) block DB startup.
746
+ if (!(0, sidecar_recovery_js_1.sidecarPreflightDisabled)()) {
747
+ try {
748
+ const reclaimed = await (0, sidecar_recovery_js_1.cleanQuarantinedMissingShadowWals)(dbPath);
749
+ for (const file of reclaimed) {
750
+ logger_js_1.logger.warn(`GitNexus: reclaimed quarantined WAL ${path_1.default.basename(file)} from a prior crash`);
751
+ }
752
+ }
753
+ catch (err) {
754
+ logger_js_1.logger.warn(`GitNexus: failed to reclaim missing-shadow WAL quarantines: ${summarizeError(err)}`);
755
+ }
756
+ }
757
+ // Crash-recovery cleanup: if the main DB file is missing, stale sidecars
758
+ // from an interrupted run can block fresh opens indefinitely.
759
+ try {
760
+ await promises_1.default.access(dbPath);
761
+ }
762
+ catch (err) {
763
+ if (isMissingFileError(err)) {
764
+ // `.shadow` is documented by LadybugDB checkpointing and `.wal.checkpoint`
765
+ // was observed in the #1618 crash loop that motivated this recovery path.
766
+ const orphanSidecars = [`${dbPath}.shadow`, `${dbPath}.wal.checkpoint`];
767
+ for (const sidecar of orphanSidecars) {
768
+ try {
769
+ await promises_1.default.unlink(sidecar);
770
+ logger_js_1.logger.warn(`GitNexus: removed orphan sidecar ${path_1.default.basename(sidecar)} (no main DB file present)`);
771
+ }
772
+ catch (err) {
773
+ if (isMissingFileError(err)) {
774
+ continue;
775
+ }
776
+ const code = extractErrnoCode(err);
777
+ logger_js_1.logger.warn(`GitNexus: failed to remove orphan sidecar ${path_1.default.basename(sidecar)} (${code ?? 'UNKNOWN'}) while main DB file is missing; LadybugDB open may still fail: ${summarizeError(err)}`);
778
+ }
779
+ }
780
+ }
781
+ else {
782
+ const code = extractErrnoCode(err);
783
+ logger_js_1.logger.warn(`GitNexus: unable to verify main DB file before orphan sidecar cleanup (${code ?? 'UNKNOWN'}); skipping cleanup: ${summarizeError(err)}`);
784
+ }
785
+ }
786
+ // Ensure parent directory exists
787
+ const parentDir = path_1.default.dirname(dbPath);
788
+ await promises_1.default.mkdir(parentDir, { recursive: true });
789
+ await (0, sidecar_recovery_js_1.preflightLbugSidecars)(dbPath, {
790
+ mode: 'write',
791
+ logger: logger_js_1.logger,
792
+ allowQuarantine: true,
793
+ });
794
+ const opened = await (0, lbug_config_js_1.openLbugConnection)(lbug, dbPath);
795
+ db = opened.db;
796
+ conn = opened.conn;
797
+ currentDbReadOnly = false;
798
+ }
799
+ finally {
800
+ await releaseInitLock();
801
+ }
802
+ }
803
+ if (!readOnly) {
804
+ const missingShadowError = await runSchemaCreationQueries(dbPath);
805
+ if (missingShadowError) {
806
+ await (0, exports.safeClose)();
807
+ resetOpenConnectionState();
808
+ const reopened = await reopenWritableAfterMissingShadow(dbPath, missingShadowError);
809
+ db = reopened.db;
810
+ conn = reopened.conn;
811
+ currentDbReadOnly = false;
812
+ const retryMissingShadowError = await runSchemaCreationQueries(dbPath);
813
+ if (retryMissingShadowError) {
814
+ await (0, exports.safeClose)();
815
+ resetOpenConnectionState();
816
+ throw new Error((0, sidecar_recovery_js_1.shadowSidecarRecoveryMessage)(dbPath, retryMissingShadowError));
817
+ }
818
+ }
819
+ }
820
+ currentDbPath = dbPath;
821
+ return { db, conn };
822
+ };
823
+ /**
824
+ * Run a COPY, retrying once with IGNORE_ERRORS=true (which skips row-level
825
+ * errors) on first failure. On a second failure, hand the RAW retry error to
826
+ * `onError` — each call site formats + slices its own message (#2226 F5: node
827
+ * COPY slices to 200 chars and throws; relationship COPY slices to 80 and warns,
828
+ * so the helper must not pre-format and lose that distinction). `onError` may
829
+ * throw to propagate the failure.
830
+ */
831
+ const copyCsvWithRetry = async (targetConn, copyQuery, onError) => {
832
+ try {
833
+ await queryAndDrain(targetConn, copyQuery);
834
+ }
835
+ catch {
836
+ try {
837
+ const retryQuery = copyQuery.replace('auto_detect=false)', 'auto_detect=false, IGNORE_ERRORS=true)');
838
+ await queryAndDrain(targetConn, retryQuery);
839
+ }
840
+ catch (retryErr) {
841
+ onError(retryErr);
842
+ }
843
+ }
844
+ };
845
+ /**
846
+ * Bulk-COPY every node CSV sequentially on the single writable connection
847
+ * (LadybugDB allows one write txn at a time). Extracted from loadGraphToLbug so
848
+ * it can run either at the node-phase boundary — overlapping the relationship
849
+ * emit pass (#2203) — or after emit in the serial escape-hatch path. Each COPY
850
+ * keeps the IGNORE_ERRORS=true retry; a hard failure throws (no node rows ⇒ the
851
+ * relationship COPY would dangle on missing endpoints).
852
+ */
853
+ const copyNodeCSVs = async (targetConn, nodeFileEntries, log, totalSteps) => {
854
+ let stepsDone = 0;
855
+ for (const [table, { csvPath, rows }] of nodeFileEntries) {
856
+ stepsDone++;
857
+ log(`Loading nodes ${stepsDone}/${totalSteps}: ${table} (${rows.toLocaleString()} rows)`);
858
+ const copyQuery = (0, exports.getCopyQuery)(table, normalizeCopyPath(csvPath));
859
+ await copyCsvWithRetry(targetConn, copyQuery, (retryErr) => {
860
+ const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
861
+ // Pool exhaustion gets a remedy (#2631): the raw binder text gives the
862
+ // operator nothing to act on, and on non-4K-page hosts (Ascend aarch64,
863
+ // Apple Silicon) the pool bills up to pageSize/4KiB x faster than the
864
+ // sizing was calibrated for — name the knob and the mechanism.
865
+ const remedy = (0, lbug_config_js_1.bufferPoolExhaustionRemedy)(retryMsg);
866
+ throw new Error(`COPY failed for ${table}: ${retryMsg.slice(0, 200)}${remedy ? ` ${remedy}` : ''}`);
867
+ });
868
+ }
869
+ };
870
+ /**
871
+ * Persist a KnowledgeGraph: stream CSVs, then bulk-COPY nodes (overlapped with
872
+ * relationship emit — see the body) and relationships.
873
+ *
874
+ * NOT TRANSACTIONAL (#2226). Each `COPY` commits independently and there is no
875
+ * surrounding transaction, so a failure partway through — a node `COPY` that
876
+ * throws at the FK barrier, or a relationship `COPY` failure —
877
+ * leaves a partially-loaded DB. The caller surfaces the error; recovery is a
878
+ * re-analyze (a full rebuild), not a partial retry. Callers must not
879
+ * assume the DB is either fully loaded or untouched after a rejection.
880
+ */
881
+ const loadGraphToLbug = async (graph, repoPath, storagePath, onProgress,
882
+ /**
883
+ * Streamed structural-emit manifest (#2680). These pair keys are NOT disjoint
884
+ * from the whole-graph emit's: a streamed `CALLS` edge is `Function|Function`,
885
+ * exactly like the retained edges `streamAllCSVsToDisk` just wrote. So these
886
+ * files are APPENDED as additional COPY jobs for the same pair rather than
887
+ * merged into `relsByPair` (a Map, which holds one CSV per pair and would
888
+ * silently drop one of them).
889
+ */
890
+ graphEmitManifest) => {
891
+ if (!conn) {
892
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
893
+ }
894
+ const log = onProgress || (() => { });
895
+ // ── #2203 persistence-path profiling ──────────────────────────────────
896
+ // Mirrors the PROF_SCOPE_RESOLUTION pattern (scope-resolution/pipeline/
897
+ // run.ts): zero-cost when off — process.hrtime.bigint() is only read under
898
+ // PROF_LBUG_LOAD=1, and the summary is logged behind the same gate. Fills
899
+ // the gap that the DB-persistence path is un-timed today (the analyze
900
+ // "emit" number is the scope-resolution emit bucket, not this COPY path).
901
+ const PROF = process.env.PROF_LBUG_LOAD === '1';
902
+ // Escape hatch / differential oracle (#2203): force the legacy strictly-serial
903
+ // load order (emit everything, THEN COPY nodes, THEN COPY rels) instead of the
904
+ // default node-COPY ‖ rel-emit overlap. Lets an operator revert the behavior at
905
+ // runtime, and lets a test load the same graph both ways and assert identical
906
+ // persisted content.
907
+ const SERIAL = process.env.CGRAPH_SERIAL_LBUG_LOAD === '1';
908
+ const mark = () => (PROF ? process.hrtime.bigint() : 0n);
909
+ const span = (a, b) => (Number(b - a) / 1e6).toFixed(1);
910
+ const tStart = mark();
911
+ const csvDir = (0, lbug_config_js_1.resolveNativeSafeStorageDir)(storagePath, 'csv');
912
+ // The single writable connection (LadybugDB is single-writer). Captured as a
913
+ // const so the node-COPY closure has a non-null reference — TS cannot narrow
914
+ // the reassignable module-level `conn` across the callback boundary.
915
+ const writeConn = conn;
916
+ const validTables = new Set(schema_js_1.NODE_TABLES);
917
+ // Node COPY is the only DB write that can overlap relationship CSV emit: the
918
+ // rel pass writes new rel_*.csv files and never touches `conn`, while node COPY
919
+ // uses `conn` and never touches the rel files. We start node COPY at the
920
+ // node-phase boundary and let the rel pass run concurrently — the only
921
+ // single-writer-safe parallelism (#2203). The rel COPY still waits for node
922
+ // COPY (FK precondition), so the DB load order is unchanged.
923
+ let nodeCopyPromise;
924
+ let nodeCopyError;
925
+ const beginNodeCopy = (nodeFilesMap) => {
926
+ const entries = [...nodeFilesMap.entries()];
927
+ // copyNodeCSVs logs node progress as step/total; it processes only node
928
+ // tables (the rel COPY has its own "Loading edges" progress line), so the
929
+ // denominator is the node-table count — not +1 reserving a rel step.
930
+ // .catch captures the failure so an overlapped (mid-emit) rejection cannot
931
+ // surface as an unhandled rejection; it is rethrown at the FK barrier below.
932
+ nodeCopyPromise = copyNodeCSVs(writeConn, entries, log, entries.length).catch((e) => {
933
+ nodeCopyError = e;
934
+ });
935
+ };
936
+ log('Streaming CSVs to disk...');
937
+ let csvResult;
938
+ try {
939
+ csvResult = SERIAL
940
+ ? await (0, csv_generator_js_1.streamAllCSVsToDisk)(graph, repoPath, csvDir)
941
+ : await (0, csv_generator_js_1.streamAllCSVsToDisk)(graph, repoPath, csvDir, beginNodeCopy);
942
+ }
943
+ catch (emitErr) {
944
+ // Relationship emit failed. In overlap mode a node COPY may be in flight —
945
+ // settle it (the .catch above means this never rejects) before rethrowing so
946
+ // it cannot leak as an unhandled rejection.
947
+ if (nodeCopyPromise)
948
+ await nodeCopyPromise;
949
+ // If node COPY ALSO failed, emitErr wins the throw — log the swallowed node
950
+ // error so a half-loaded DB isn't misattributed to the emit failure alone.
951
+ if (nodeCopyError) {
952
+ logger_js_1.logger.warn({ err: nodeCopyError }, '[lbug-load] node COPY also failed while relationship emit was failing');
953
+ }
954
+ throw emitErr;
955
+ }
956
+ const tCsv = mark();
957
+ // Serial path: all CSVs are on disk and node COPY has not started — start it
958
+ // here so the barrier below blocks on it exactly as the legacy path did.
959
+ if (SERIAL)
960
+ beginNodeCopy(csvResult.nodeFiles);
961
+ // FK barrier: node rows must exist before the relationship COPY resolves their
962
+ // endpoints. In overlap mode most of node COPY was hidden behind rel emit, so
963
+ // this await is the *residual* node-COPY time (≈0 when fully overlapped).
964
+ if (nodeCopyPromise)
965
+ await nodeCopyPromise;
966
+ if (nodeCopyError) {
967
+ throw nodeCopyError instanceof Error ? nodeCopyError : new Error(String(nodeCopyError));
968
+ }
969
+ const tCopyNodes = mark();
970
+ // Bulk COPY relationships. They were already routed to per-FROM→TO-label-pair
971
+ // files during the emit pass (#2203 U2) — there is no monolithic relations.csv
972
+ // to re-read/re-split here; we COPY each pair file directly.
973
+ const { relsByPair, relHeader, skippedRels, totalValidRels } = csvResult;
974
+ let tCopyRels = tCopyNodes;
975
+ let tFallback = tCopyNodes;
976
+ // One COPY job per CSV FILE, not per label pair. The whole-graph emit writes
977
+ // at most one file per pair, but the streamed structural manifest (#2680) can
978
+ // contribute a second file for a pair the whole-graph emit also wrote — both
979
+ // must load. `relsByPair` stays a one-file-per-pair Map so the PDG merge above
980
+ // and every other consumer are untouched.
981
+ const copyJobs = [];
982
+ for (const [pairKey, meta] of relsByPair) {
983
+ copyJobs.push({ pairKey, csvPath: meta.csvPath, rows: meta.rows });
984
+ }
985
+ if (graphEmitManifest) {
986
+ for (const [pairKey, meta] of graphEmitManifest.relsByPair) {
987
+ copyJobs.push({ pairKey, csvPath: meta.csvPath, rows: meta.rows });
988
+ }
989
+ }
990
+ const insertedRels = totalValidRels + (graphEmitManifest?.totalRows ?? 0);
991
+ const warnings = [];
992
+ let poolRemedyIssued = false;
993
+ if (insertedRels > 0) {
994
+ log(`Loading edges: ${insertedRels.toLocaleString()} across ${copyJobs.length} CSV files`);
995
+ let pairIdx = 0;
996
+ let failedPairEdges = 0;
997
+ const failedPairCsvPaths = new Set();
998
+ for (const { pairKey, csvPath: pairCsvPath, rows } of copyJobs) {
999
+ pairIdx++;
1000
+ const [fromLabel, toLabel] = pairKey.split('|');
1001
+ const normalizedPath = normalizeCopyPath(pairCsvPath);
1002
+ // PARALLEL=false is load-bearing here too — see COPY_CSV_OPTS (#2203 / kuzudb/kuzu#5778).
1003
+ const copyQuery = `COPY ${schema_js_1.REL_TABLE_NAME} FROM "${normalizedPath}" (from="${fromLabel}", to="${toLabel}", HEADER=true, ESCAPE='"', DELIM=',', QUOTE='"', PARALLEL=false, auto_detect=false)`;
1004
+ if (pairIdx % 5 === 0 || rows > 1000) {
1005
+ log(`Loading edges: ${pairIdx}/${copyJobs.length} files (${fromLabel} -> ${toLabel})`);
1006
+ }
1007
+ // Use the captured `writeConn` (not the module-level `conn`) for the rel
1008
+ // COPY, matching the node COPY above — one captured reference for the whole
1009
+ // bulk load (#2264 review P3). Same object during analyze (`conn` is only
1010
+ // reassigned at open/close under the session lock, never mid-load), so the
1011
+ // queryAndDrain `targetConn === conn` lock gate still engages.
1012
+ await copyCsvWithRetry(writeConn, copyQuery, (retryErr) => {
1013
+ const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
1014
+ warnings.push(`${fromLabel}->${toLabel} (${rows} edges): ${retryMsg.slice(0, 80)}`);
1015
+ // One remedy per bulk load, not per pair (#2631): pool exhaustion
1016
+ // repeats for every remaining pair once it starts. logger.warn, not
1017
+ // just warnings.push — the returned warnings array has no consumer at
1018
+ // any call site, so a push alone would leave the remedy invisible
1019
+ // while the row-by-row fallback quietly degrades the load.
1020
+ const remedy = poolRemedyIssued ? undefined : (0, lbug_config_js_1.bufferPoolExhaustionRemedy)(retryMsg);
1021
+ if (remedy) {
1022
+ poolRemedyIssued = true;
1023
+ warnings.push(remedy);
1024
+ logger_js_1.logger.warn(remedy);
1025
+ }
1026
+ failedPairEdges += rows;
1027
+ failedPairCsvPaths.add(pairCsvPath);
1028
+ });
1029
+ // Only delete if not in failedPairCsvPaths (needed for fallback)
1030
+ if (!failedPairCsvPaths.has(pairCsvPath)) {
1031
+ try {
1032
+ await promises_1.default.unlink(pairCsvPath);
1033
+ }
1034
+ catch { }
1035
+ }
1036
+ }
1037
+ tCopyRels = mark();
1038
+ if (failedPairCsvPaths.size > 0) {
1039
+ log(`Inserting ${failedPairEdges} edges individually (missing schema pairs)`);
1040
+ // Read failed pair files and merge for fallback inserts
1041
+ const allLines = [relHeader];
1042
+ for (const failedPath of failedPairCsvPaths) {
1043
+ try {
1044
+ const content = await promises_1.default.readFile(failedPath, 'utf-8');
1045
+ const lines = content.split('\n');
1046
+ // Skip header line (first) and empty lines
1047
+ for (let i = 1; i < lines.length; i++) {
1048
+ if (lines[i].trim())
1049
+ allLines.push(lines[i]);
1050
+ }
1051
+ }
1052
+ catch { }
1053
+ try {
1054
+ await promises_1.default.unlink(failedPath);
1055
+ }
1056
+ catch { }
1057
+ }
1058
+ if (allLines.length > 1) {
1059
+ await (0, exports.fallbackRelationshipInserts)(allLines, validTables, rel_pair_routing_js_1.getNodeLabel);
1060
+ }
1061
+ }
1062
+ tFallback = mark();
1063
+ }
1064
+ // Cleanup all CSVs (per-pair rel files are unlinked in the COPY loop above;
1065
+ // the remaining sweep below catches node CSVs + any leftover pair files).
1066
+ for (const [, { csvPath }] of csvResult.nodeFiles) {
1067
+ try {
1068
+ await promises_1.default.unlink(csvPath);
1069
+ }
1070
+ catch { }
1071
+ }
1072
+ try {
1073
+ const remaining = await promises_1.default.readdir(csvDir);
1074
+ for (const f of remaining) {
1075
+ try {
1076
+ await promises_1.default.unlink(path_1.default.join(csvDir, f));
1077
+ }
1078
+ catch { }
1079
+ }
1080
+ }
1081
+ catch { }
1082
+ try {
1083
+ await promises_1.default.rmdir(csvDir);
1084
+ }
1085
+ catch { }
1086
+ if (PROF) {
1087
+ const tEnd = mark();
1088
+ let totalNodeRows = 0;
1089
+ for (const [, { rows }] of csvResult.nodeFiles)
1090
+ totalNodeRows += rows;
1091
+ // `mode` records which load path ran. In overlap mode `csv-emit` is the wall
1092
+ // to streamAllCSVsToDisk's return (node COPY overlapped part of it) and
1093
+ // `copy-nodes` is the RESIDUAL node-COPY await after emit returned — it
1094
+ // trends to 0 as the overlap hides node COPY behind relationship emit. In
1095
+ // serial mode the buckets carry their legacy, disjoint meaning.
1096
+ logger_js_1.logger.warn(`[lbug-load prof] mode=${SERIAL ? 'serial' : 'overlap'} csv-emit=${span(tStart, tCsv)}ms ` +
1097
+ `copy-nodes=${span(tCsv, tCopyNodes)}ms copy-rels=${span(tCopyNodes, tCopyRels)}ms ` +
1098
+ `fallback=${span(tCopyRels, tFallback)}ms total=${span(tStart, tEnd)}ms ` +
1099
+ `(${totalNodeRows} nodes, ${insertedRels} rels)`);
1100
+ }
1101
+ return { success: true, insertedRels, skippedRels, warnings };
1102
+ };
1103
+ exports.loadGraphToLbug = loadGraphToLbug;
1104
+ // LadybugDB default ESCAPE is '\' (backslash), but our CSV uses RFC 4180 escaping ("" for literal quotes).
1105
+ // Source code content is full of backslashes which confuse the auto-detection.
1106
+ // We MUST explicitly set ESCAPE='"' to use RFC 4180 escaping, and disable auto_detect to prevent
1107
+ // LadybugDB from overriding our settings based on sample rows.
1108
+ //
1109
+ // PARALLEL=false IS LOAD-BEARING FOR CORRECTNESS — DO NOT FLIP IT (#2203).
1110
+ // LadybugDB's parallel CSV reader (Kuzu-derived; default PARALLEL=true) splits the
1111
+ // file into byte ranges parsed concurrently, and CANNOT determine line boundaries
1112
+ // when a quoted field contains an embedded newline — it errors with "Quoted newlines
1113
+ // are not supported in parallel CSV reader. Please specify PARALLEL=FALSE", or worse,
1114
+ // mis-parses silently (upstream kuzudb/kuzu#5778, still open). Our `content`/`text`
1115
+ // columns hold source code, so quoted multiline fields are guaranteed. PARALLEL=false
1116
+ // is therefore required, not conservative. The multiline-quoted round-trip in
1117
+ // test/integration/copy-parallel-invariant.test.ts fails loudly if this is ever flipped.
1118
+ // Exported so that test asserts the invariant statically as well.
1119
+ exports.COPY_CSV_OPTS = `(HEADER=true, ESCAPE='"', DELIM=',', QUOTE='"', PARALLEL=false, auto_detect=false)`;
1120
+ // Multi-language table names that were created with backticks in CODE_ELEMENT_BASE
1121
+ // and must always be referenced with backticks in queries
1122
+ const BACKTICK_TABLES = new Set([
1123
+ 'Struct',
1124
+ 'Enum',
1125
+ 'Macro',
1126
+ 'Typedef',
1127
+ 'Union',
1128
+ 'Namespace',
1129
+ 'Trait',
1130
+ 'Impl',
1131
+ 'TypeAlias',
1132
+ 'Const',
1133
+ 'Static',
1134
+ 'Property',
1135
+ 'Record',
1136
+ 'Delegate',
1137
+ 'Annotation',
1138
+ 'Constructor',
1139
+ 'Template',
1140
+ 'Module',
1141
+ ]);
1142
+ const escapeTableName = (table) => {
1143
+ return BACKTICK_TABLES.has(table) ? `\`${table}\`` : table;
1144
+ };
1145
+ /**
1146
+ * Format one JS value as a Cypher literal for the adapter's string-built
1147
+ * statements: NULL/undefined → `NULL`, numbers pass through unquoted,
1148
+ * everything else becomes a single-quoted string literal escaped via
1149
+ * {@link escapeCypherString} (backslashes first, then quotes).
1150
+ *
1151
+ * Replaces three per-function closures that used SQL-style `''` doubling —
1152
+ * LadybugDB REJECTS doubling, so every value containing a quote made the
1153
+ * whole statement a parser error, invisible wherever the call site swallowed
1154
+ * per-row failures (#2409 escaping sweep, completed for tri-review
1155
+ * 4669518496 P2-2). Those closures also rewrote literal `\n`/`\r` into
1156
+ * two-character escape sequences; raw LF/CR are legal inside LadybugDB
1157
+ * single-quoted literals (live-probed on @ladybugdb/core 0.18.0), so the
1158
+ * replaces are gone and content now round-trips byte-identical.
1159
+ */
1160
+ const formatCypherValue = (v) => {
1161
+ if (v === null || v === undefined)
1162
+ return 'NULL';
1163
+ if (typeof v === 'number')
1164
+ return String(v);
1165
+ return `'${(0, cypher_escape_js_1.escapeCypherString)(String(v))}'`;
1166
+ };
1167
+ const formatCypherStringArray = (value) => {
1168
+ const items = Array.isArray(value)
1169
+ ? value.filter((item) => typeof item === 'string')
1170
+ : [];
1171
+ return `[${items.map(formatCypherValue).join(', ')}]`;
1172
+ };
1173
+ /**
1174
+ * Fallback: insert relationships one-by-one if COPY fails.
1175
+ *
1176
+ * Exported for the quoted-id round-trip tests in
1177
+ * `test/integration/lbug-core-adapter.test.ts` (the `DELETE_FILES_CHUNK_SIZE`
1178
+ * exported-for-tests precedent); production callers stay in this module.
1179
+ * Bails silently when the adapter singleton is closed.
1180
+ *
1181
+ * KNOWN PRE-EXISTING NARROWING (distinct from the `''` escaping bug, NOT
1182
+ * fixed here): the row regex below matches CSV fields with `[^"]*`, so an id
1183
+ * containing a double quote (CSV-escaped as `""`) never matches and the edge
1184
+ * is skipped. Tracked as part of the quote-in-id divergence documented in
1185
+ * `rel-pair-routing.ts`.
1186
+ */
1187
+ const fallbackRelationshipInserts = async (validRelLines, validTables, getNodeLabel) => {
1188
+ if (!conn)
1189
+ return;
1190
+ const escapeLabel = (label) => {
1191
+ return BACKTICK_TABLES.has(label) ? `\`${label}\`` : label;
1192
+ };
1193
+ for (let i = 1; i < validRelLines.length; i++) {
1194
+ const line = validRelLines[i];
1195
+ try {
1196
+ const match = line.match(/"([^"]*)","([^"]*)","([^"]*)",([0-9.]+),"([^"]*)",([0-9-]+)/);
1197
+ if (!match)
1198
+ continue;
1199
+ const [, fromId, toId, relType, confidenceStr, reason, stepStr] = match;
1200
+ const fromLabel = getNodeLabel(fromId);
1201
+ const toLabel = getNodeLabel(toId);
1202
+ if (!validTables.has(fromLabel) || !validTables.has(toLabel))
1203
+ continue;
1204
+ const confidence = parseFloat(confidenceStr) || 1.0;
1205
+ const step = parseInt(stepStr) || 0;
1206
+ await queryAndDrain(conn, `
1207
+ MATCH (a:${escapeLabel(fromLabel)} {id: ${formatCypherValue(fromId)} }),
1208
+ (b:${escapeLabel(toLabel)} {id: ${formatCypherValue(toId)} })
1209
+ CREATE (a)-[:${schema_js_1.REL_TABLE_NAME} {type: ${formatCypherValue(relType)}, confidence: ${confidence}, reason: ${formatCypherValue(reason)}, step: ${step}}]->(b)
1210
+ `);
1211
+ }
1212
+ catch {
1213
+ // skip
1214
+ }
1215
+ }
1216
+ };
1217
+ exports.fallbackRelationshipInserts = fallbackRelationshipInserts;
1218
+ /** Tables with isExported column (TypeScript/JS-native types) */
1219
+ const TABLES_WITH_EXPORTED = new Set([
1220
+ 'Function',
1221
+ 'Class',
1222
+ 'Interface',
1223
+ 'Method',
1224
+ 'CodeElement',
1225
+ ]);
1226
+ const getCopyQuery = (table, filePath) => {
1227
+ const t = escapeTableName(table);
1228
+ if (table === 'File') {
1229
+ return `COPY ${t}(id, name, filePath, content) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1230
+ }
1231
+ if (table === 'Folder') {
1232
+ return `COPY ${t}(id, name, filePath) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1233
+ }
1234
+ if (table === 'Community') {
1235
+ return `COPY ${t}(id, label, heuristicLabel, keywords, description, enrichedBy, cohesion, symbolCount) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1236
+ }
1237
+ if (table === 'Process') {
1238
+ return `COPY ${t}(id, label, heuristicLabel, processType, stepCount, communities, entryPointId, terminalId) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1239
+ }
1240
+ if (table === 'Section') {
1241
+ return `COPY ${t}(id, name, filePath, startLine, endLine, level, content, description) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1242
+ }
1243
+ if (table === 'Route') {
1244
+ return `COPY ${t}(id, name, filePath, responseKeys, errorKeys, middleware, method, handlerSymbolId) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1245
+ }
1246
+ if (table === 'Tool') {
1247
+ return `COPY ${t}(id, name, filePath, description) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1248
+ }
1249
+ if (table === 'BasicBlock') {
1250
+ // Taint/PDG substrate (issue #2080) — no name column. `callees` is the
1251
+ // statement-precise inter-procedural reach substrate (space-joined leaf names);
1252
+ // `calleeIds` is its SOUND parallel (space-joined resolved callee ids, #2227).
1253
+ return `COPY ${t}(id, filePath, startLine, endLine, text, callees, calleeIds) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1254
+ }
1255
+ if (table === 'Class') {
1256
+ return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description, frameworkAnnotations) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1257
+ }
1258
+ if (table === 'Method') {
1259
+ return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description, parameterCount, returnType) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1260
+ }
1261
+ if (table === 'Property') {
1262
+ return `COPY ${t}(id, name, filePath, startLine, endLine, content, description, declaredType) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1263
+ }
1264
+ // TypeScript/JS code element tables have isExported; multi-language tables do not
1265
+ if (TABLES_WITH_EXPORTED.has(table)) {
1266
+ return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1267
+ }
1268
+ // Multi-language tables (Struct, Impl, Trait, Macro, etc.)
1269
+ return `COPY ${t}(id, name, filePath, startLine, endLine, content, description) FROM "${filePath}" ${exports.COPY_CSV_OPTS}`;
1270
+ };
1271
+ exports.getCopyQuery = getCopyQuery;
1272
+ /**
1273
+ * Insert a single node to LadybugDB
1274
+ * @param label - Node type (File, Function, Class, etc.)
1275
+ * @param properties - Node properties
1276
+ * @param dbPath - Path to LadybugDB database (optional if already initialized)
1277
+ */
1278
+ const insertNodeToLbug = async (label, properties, dbPath) => {
1279
+ // Use provided dbPath or fall back to module-level db
1280
+ const targetDbPath = dbPath || (db ? undefined : null);
1281
+ if (!targetDbPath && !db) {
1282
+ throw new Error('LadybugDB not initialized. Provide dbPath or call initLbug first.');
1283
+ }
1284
+ try {
1285
+ // Values go through the module-scope formatCypherValue — the old local
1286
+ // closure used `''` doubling, which LadybugDB rejects (#2409 escaping
1287
+ // sweep, tri-review 4669518496 P2-2).
1288
+ // Build INSERT query based on node type
1289
+ const t = escapeTableName(label);
1290
+ let query;
1291
+ if (label === 'File') {
1292
+ query = `CREATE (n:File {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, content: ${formatCypherValue(properties.content || '')}})`;
1293
+ }
1294
+ else if (label === 'Folder') {
1295
+ query = `CREATE (n:Folder {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}})`;
1296
+ }
1297
+ else if (label === 'Section') {
1298
+ const descPart = properties.description
1299
+ ? `, description: ${formatCypherValue(properties.description)}`
1300
+ : '';
1301
+ query = `CREATE (n:Section {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, level: ${properties.level || 1}, content: ${formatCypherValue(properties.content || '')}${descPart}})`;
1302
+ }
1303
+ else if (label === 'BasicBlock') {
1304
+ // Taint/PDG substrate (issue #2080) — no name column. `calleeIds` (#2227)
1305
+ // is the sound resolved-id parallel to the leaf-name `callees` set.
1306
+ query = `CREATE (n:BasicBlock {id: ${formatCypherValue(properties.id)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, text: ${formatCypherValue(properties.text || '')}, callees: ${formatCypherValue(properties.callees || '')}, calleeIds: ${formatCypherValue(properties.calleeIds || '')}})`;
1307
+ }
1308
+ else if (label === 'Class') {
1309
+ const descPart = properties.description
1310
+ ? `, description: ${formatCypherValue(properties.description)}`
1311
+ : '';
1312
+ query = `CREATE (n:Class {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, isExported: ${!!properties.isExported}, content: ${formatCypherValue(properties.content || '')}${descPart}, frameworkAnnotations: ${formatCypherStringArray(properties.frameworkAnnotations)}})`;
1313
+ }
1314
+ else if (TABLES_WITH_EXPORTED.has(label)) {
1315
+ const descPart = properties.description
1316
+ ? `, description: ${formatCypherValue(properties.description)}`
1317
+ : '';
1318
+ query = `CREATE (n:${t} {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, isExported: ${!!properties.isExported}, content: ${formatCypherValue(properties.content || '')}${descPart}})`;
1319
+ }
1320
+ else if (label === 'Property') {
1321
+ const descPart = properties.description
1322
+ ? `, description: ${formatCypherValue(properties.description)}`
1323
+ : '';
1324
+ query = `CREATE (n:${t} {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, content: ${formatCypherValue(properties.content || '')}${descPart}, declaredType: ${formatCypherValue(properties.declaredType || '')}})`;
1325
+ }
1326
+ else {
1327
+ // Multi-language tables (Struct, Impl, Trait, Macro, etc.) — no isExported
1328
+ const descPart = properties.description
1329
+ ? `, description: ${formatCypherValue(properties.description)}`
1330
+ : '';
1331
+ query = `CREATE (n:${t} {id: ${formatCypherValue(properties.id)}, name: ${formatCypherValue(properties.name)}, filePath: ${formatCypherValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, content: ${formatCypherValue(properties.content || '')}${descPart}})`;
1332
+ }
1333
+ // Use per-query connection if dbPath provided (avoids lock conflicts)
1334
+ if (targetDbPath) {
1335
+ const tempHandle = await (0, lbug_config_js_1.openLbugConnection)(lbug, targetDbPath);
1336
+ try {
1337
+ await queryAndDrain(tempHandle.conn, query);
1338
+ return true;
1339
+ }
1340
+ finally {
1341
+ await (0, lbug_config_js_1.closeLbugConnection)(tempHandle);
1342
+ }
1343
+ }
1344
+ else if (conn) {
1345
+ // Use existing persistent connection (when called from analyze)
1346
+ await queryAndDrain(conn, query);
1347
+ return true;
1348
+ }
1349
+ return false;
1350
+ }
1351
+ catch (e) {
1352
+ // Node may already exist or other error
1353
+ logger_js_1.logger.error({ err: e.message }, `Failed to insert ${label} node:`);
1354
+ return false;
1355
+ }
1356
+ };
1357
+ exports.insertNodeToLbug = insertNodeToLbug;
1358
+ /**
1359
+ * Batch insert multiple nodes to LadybugDB using a single connection
1360
+ * @param nodes - Array of {label, properties} to insert
1361
+ * @param dbPath - Path to LadybugDB database
1362
+ * @returns Object with success count and error count
1363
+ */
1364
+ const batchInsertNodesToLbug = async (nodes, dbPath) => {
1365
+ if (nodes.length === 0)
1366
+ return { inserted: 0, failed: 0 };
1367
+ // Values go through the module-scope formatCypherValue — the old local
1368
+ // closure used `''` doubling, which LadybugDB rejects; the per-node catch
1369
+ // below counted every quoted value as a silent `failed` (#2409 escaping
1370
+ // sweep, tri-review 4669518496 P2-2).
1371
+ // Open a single connection for all inserts
1372
+ const tempHandle = await (0, lbug_config_js_1.openLbugConnection)(lbug, dbPath);
1373
+ const tempConn = tempHandle.conn;
1374
+ let inserted = 0;
1375
+ let failed = 0;
1376
+ try {
1377
+ for (const { label, properties } of nodes) {
1378
+ try {
1379
+ let query;
1380
+ // Use MERGE instead of CREATE for upsert behavior (handles duplicates gracefully)
1381
+ const t = escapeTableName(label);
1382
+ if (label === 'File') {
1383
+ query = `MERGE (n:File {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.content = ${formatCypherValue(properties.content || '')}`;
1384
+ }
1385
+ else if (label === 'Folder') {
1386
+ query = `MERGE (n:Folder {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}`;
1387
+ }
1388
+ else if (label === 'Section') {
1389
+ const descPart = properties.description
1390
+ ? `, n.description = ${formatCypherValue(properties.description)}`
1391
+ : '';
1392
+ query = `MERGE (n:Section {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.level = ${properties.level || 1}, n.content = ${formatCypherValue(properties.content || '')}${descPart}`;
1393
+ }
1394
+ else if (label === 'BasicBlock') {
1395
+ // Taint/PDG substrate (issue #2080) — no name column. `calleeIds`
1396
+ // (#2227) is the sound resolved-id parallel to the `callees` set.
1397
+ query = `MERGE (n:BasicBlock {id: ${formatCypherValue(properties.id)}}) SET n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.text = ${formatCypherValue(properties.text || '')}, n.callees = ${formatCypherValue(properties.callees || '')}, n.calleeIds = ${formatCypherValue(properties.calleeIds || '')}`;
1398
+ }
1399
+ else if (label === 'Class') {
1400
+ const descPart = properties.description
1401
+ ? `, n.description = ${formatCypherValue(properties.description)}`
1402
+ : '';
1403
+ query = `MERGE (n:Class {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.isExported = ${!!properties.isExported}, n.content = ${formatCypherValue(properties.content || '')}${descPart}, n.frameworkAnnotations = ${formatCypherStringArray(properties.frameworkAnnotations)}`;
1404
+ }
1405
+ else if (TABLES_WITH_EXPORTED.has(label)) {
1406
+ const descPart = properties.description
1407
+ ? `, n.description = ${formatCypherValue(properties.description)}`
1408
+ : '';
1409
+ query = `MERGE (n:${t} {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.isExported = ${!!properties.isExported}, n.content = ${formatCypherValue(properties.content || '')}${descPart}`;
1410
+ }
1411
+ else if (label === 'Property') {
1412
+ const descPart = properties.description
1413
+ ? `, n.description = ${formatCypherValue(properties.description)}`
1414
+ : '';
1415
+ query = `MERGE (n:${t} {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.content = ${formatCypherValue(properties.content || '')}${descPart}, n.declaredType = ${formatCypherValue(properties.declaredType || '')}`;
1416
+ }
1417
+ else {
1418
+ const descPart = properties.description
1419
+ ? `, n.description = ${formatCypherValue(properties.description)}`
1420
+ : '';
1421
+ query = `MERGE (n:${t} {id: ${formatCypherValue(properties.id)}}) SET n.name = ${formatCypherValue(properties.name)}, n.filePath = ${formatCypherValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.content = ${formatCypherValue(properties.content || '')}${descPart}`;
1422
+ }
1423
+ await queryAndDrain(tempConn, query);
1424
+ inserted++;
1425
+ }
1426
+ catch (e) {
1427
+ // Don't console.error here - it corrupts MCP JSON-RPC on stderr
1428
+ failed++;
1429
+ }
1430
+ }
1431
+ }
1432
+ finally {
1433
+ await (0, lbug_config_js_1.closeLbugConnection)(tempHandle);
1434
+ }
1435
+ return { inserted, failed };
1436
+ };
1437
+ exports.batchInsertNodesToLbug = batchInsertNodesToLbug;
1438
+ const executeQuery = async (cypher) => {
1439
+ return await (0, exports.executePrepared)(cypher, {});
1440
+ };
1441
+ exports.executeQuery = executeQuery;
1442
+ const streamQuery = async (cypher, onRow) => {
1443
+ if ((0, wal_driver_state_js_1.isWalDriverActive)()) {
1444
+ // streamQuery reads rows on the singleton connection WITHOUT withConnLock; if
1445
+ // the WAL-checkpoint driver is live, those reads could race a CHECKPOINT — the
1446
+ // #2264 corruption window. Today the serve/read path never runs the driver
1447
+ // (analyze runs in a forked worker), so this fails loud only if a future
1448
+ // in-process analyze overlaps a stream. Run analysis in a worker, or stop the
1449
+ // driver before streaming. See conn-lock.ts.
1450
+ throw new Error('streamQuery cannot run while the WAL-checkpoint driver is active (it would ' +
1451
+ 'race a CHECKPOINT on the unlocked read connection — #2264).');
1452
+ }
1453
+ if (!conn) {
1454
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
1455
+ }
1456
+ const queryResult = await conn.query(cypher);
1457
+ const results = Array.isArray(queryResult) ? queryResult : [queryResult];
1458
+ const result = results[0];
1459
+ let rowCount = 0;
1460
+ let streamError;
1461
+ try {
1462
+ while (await result.hasNext()) {
1463
+ const row = await result.getNext();
1464
+ await onRow(row);
1465
+ rowCount++;
1466
+ }
1467
+ return rowCount;
1468
+ }
1469
+ catch (err) {
1470
+ streamError = err;
1471
+ throw err;
1472
+ }
1473
+ finally {
1474
+ try {
1475
+ await drainQueryResult(results);
1476
+ }
1477
+ catch (err) {
1478
+ if (streamError === undefined)
1479
+ throw err;
1480
+ }
1481
+ }
1482
+ };
1483
+ exports.streamQuery = streamQuery;
1484
+ /**
1485
+ * Execute a single parameterized query (prepare/execute pattern).
1486
+ * Prevents Cypher injection by binding values as parameters.
1487
+ */
1488
+ const executePrepared = async (cypher, params) => {
1489
+ const c = conn;
1490
+ if (!c) {
1491
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
1492
+ }
1493
+ return (0, conn_lock_js_1.withConnLock)(async () => {
1494
+ const stmt = await c.prepare(cypher);
1495
+ if (!stmt.isSuccess()) {
1496
+ const errMsg = await stmt.getErrorMessage();
1497
+ throw new Error(`Prepare failed: ${errMsg}`);
1498
+ }
1499
+ const queryResult = await c.execute(stmt, params);
1500
+ return await readQueryRows(queryResult);
1501
+ });
1502
+ };
1503
+ exports.executePrepared = executePrepared;
1504
+ const executeWithReusedStatement = async (cypher, paramsList) => {
1505
+ const c = conn;
1506
+ if (!c) {
1507
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
1508
+ }
1509
+ if (paramsList.length === 0)
1510
+ return;
1511
+ const SUB_BATCH_SIZE = 4;
1512
+ for (let i = 0; i < paramsList.length; i += SUB_BATCH_SIZE) {
1513
+ const subBatch = paramsList.slice(i, i + SUB_BATCH_SIZE);
1514
+ // One critical section per sub-batch: the prepare + its executes run with
1515
+ // exclusive access to the connection (so the WAL checkpoint driver cannot
1516
+ // interleave a CHECKPOINT mid-batch), while the lock is released between
1517
+ // sub-batches to let the driver checkpoint during a long writeback.
1518
+ await (0, conn_lock_js_1.withConnLock)(async () => {
1519
+ const stmt = await c.prepare(cypher);
1520
+ if (!stmt.isSuccess()) {
1521
+ const errMsg = await stmt.getErrorMessage();
1522
+ throw new Error(`Prepare failed: ${errMsg}`);
1523
+ }
1524
+ try {
1525
+ for (const params of subBatch) {
1526
+ await drainQueryResult(await c.execute(stmt, params));
1527
+ }
1528
+ }
1529
+ catch (e) {
1530
+ const msg = e instanceof Error ? e.message : String(e);
1531
+ const queryPreview = cypher.replace(/\s+/g, ' ').slice(0, 120);
1532
+ throw new Error(`Batch execution failed for rows ${i + 1}-${i + subBatch.length}: ${msg} (${queryPreview})`);
1533
+ }
1534
+ // Note: LadybugDB PreparedStatement doesn't require explicit close()
1535
+ });
1536
+ }
1537
+ };
1538
+ exports.executeWithReusedStatement = executeWithReusedStatement;
1539
+ const getLbugStats = async () => {
1540
+ const c = conn;
1541
+ if (!c)
1542
+ return { nodes: 0, edges: 0 };
1543
+ // Called during analyze finalize while the WAL-checkpoint driver is still
1544
+ // running; each count read takes the connection lock so it cannot execute
1545
+ // concurrently with a driver CHECKPOINT. Per-query locking lets the driver
1546
+ // checkpoint between table counts rather than waiting for the whole sweep.
1547
+ let totalNodes = 0;
1548
+ for (const tableName of schema_js_1.NODE_TABLES) {
1549
+ try {
1550
+ totalNodes += await (0, conn_lock_js_1.withConnLock)(async () => {
1551
+ const queryResult = await c.query(`MATCH (n:${escapeTableName(tableName)}) RETURN count(n) AS cnt`);
1552
+ const nodeRows = await readQueryRows(queryResult);
1553
+ return nodeRows.length > 0 ? Number(nodeRows[0]?.cnt ?? nodeRows[0]?.[0] ?? 0) : 0;
1554
+ });
1555
+ }
1556
+ catch {
1557
+ // ignore
1558
+ }
1559
+ }
1560
+ let totalEdges = 0;
1561
+ try {
1562
+ totalEdges = await (0, conn_lock_js_1.withConnLock)(async () => {
1563
+ const queryResult = await c.query(`MATCH ()-[r:${schema_js_1.REL_TABLE_NAME}]->() RETURN count(r) AS cnt`);
1564
+ const edgeRows = await readQueryRows(queryResult);
1565
+ return edgeRows.length > 0 ? Number(edgeRows[0]?.cnt ?? edgeRows[0]?.[0] ?? 0) : 0;
1566
+ });
1567
+ }
1568
+ catch {
1569
+ // ignore
1570
+ }
1571
+ return { nodes: totalNodes, edges: totalEdges };
1572
+ };
1573
+ exports.getLbugStats = getLbugStats;
1574
+ /**
1575
+ * embeddings 子系统已删除——恒返回空缓存。
1576
+ */
1577
+ const loadCachedEmbeddings = async () => {
1578
+ return { embeddingNodeIds: new Set(), embeddings: [] };
1579
+ };
1580
+ exports.loadCachedEmbeddings = loadCachedEmbeddings;
1581
+ /**
1582
+ * Fetch existing embedding hashes from CodeEmbedding table for incremental embedding.
1583
+ * Returns a Map<nodeId, contentHash> suitable for passing to `runEmbeddingPipeline`.
1584
+ * Handles legacy DBs without the `contentHash` column (all rows treated as stale with empty hash).
1585
+ * Returns undefined if the CodeEmbedding table does not exist.
1586
+ *
1587
+ * @param execQuery - Cypher query executor (typically pool-adapter's `executeQuery`)
1588
+ */
1589
+ const fetchExistingEmbeddingHashes = async (execQuery) => {
1590
+ try {
1591
+ const rows = await execQuery(`MATCH (e:${schema_js_1.EMBEDDING_TABLE_NAME}) RETURN e.nodeId AS nodeId, e.chunkIndex AS chunkIndex, e.startLine AS startLine, e.endLine AS endLine, e.contentHash AS contentHash`);
1592
+ if (!rows || rows.length === 0)
1593
+ return undefined;
1594
+ const map = new Map();
1595
+ for (const r of rows) {
1596
+ const nodeId = r.nodeId ?? r[0];
1597
+ const chunkIndex = r.chunkIndex ?? r[1];
1598
+ const startLine = r.startLine ?? r[2];
1599
+ const endLine = r.endLine ?? r[3];
1600
+ const hash = r.contentHash ?? r[4] ?? schema_js_1.STALE_HASH_SENTINEL;
1601
+ if (nodeId) {
1602
+ const hasChunkMetadata = chunkIndex !== undefined &&
1603
+ chunkIndex !== null &&
1604
+ startLine !== undefined &&
1605
+ startLine !== null &&
1606
+ endLine !== undefined &&
1607
+ endLine !== null;
1608
+ // Empty/null contentHash or missing chunk metadata means legacy row — treat as stale.
1609
+ map.set(nodeId, hasChunkMetadata && hash ? hash : schema_js_1.STALE_HASH_SENTINEL);
1610
+ }
1611
+ }
1612
+ return map;
1613
+ }
1614
+ catch (err) {
1615
+ const msg = err?.message ?? '';
1616
+ if (isMissingColumnOrTableError(msg)) {
1617
+ // Legacy rows missing chunk-aware columns — treat every row as stale.
1618
+ try {
1619
+ const rows = await execQuery(`MATCH (e:${schema_js_1.EMBEDDING_TABLE_NAME}) RETURN e.nodeId AS nodeId`);
1620
+ if (!rows || rows.length === 0)
1621
+ return undefined;
1622
+ const map = new Map();
1623
+ for (const r of rows) {
1624
+ const nodeId = r.nodeId ?? r[0];
1625
+ if (nodeId)
1626
+ map.set(nodeId, schema_js_1.STALE_HASH_SENTINEL);
1627
+ }
1628
+ logger_js_1.logger.info(`[embed] ${map.size} nodes in legacy DB (missing chunk-aware columns) — all treated as stale`);
1629
+ return map;
1630
+ }
1631
+ catch (fallbackErr) {
1632
+ const fallbackMsg = fallbackErr?.message ?? '';
1633
+ if (isMissingColumnOrTableError(fallbackMsg)) {
1634
+ logger_js_1.logger.info(`[embed] CodeEmbedding table not yet present — full embedding run (${fallbackMsg})`);
1635
+ return undefined;
1636
+ }
1637
+ throw fallbackErr;
1638
+ }
1639
+ }
1640
+ throw err;
1641
+ }
1642
+ };
1643
+ exports.fetchExistingEmbeddingHashes = fetchExistingEmbeddingHashes;
1644
+ /**
1645
+ * Flush the WAL so all pending writes are visible to subsequent readers.
1646
+ *
1647
+ * Best-effort: swallows errors from older LadybugDB versions or schemaless
1648
+ * databases that do not support the CHECKPOINT command. A no-op when there
1649
+ * is nothing pending, so safe (and cheap) to call unconditionally after any
1650
+ * write path.
1651
+ *
1652
+ * Use this instead of safeClose when the connection must stay open
1653
+ * (e.g. the /api/embed handler that keeps serving queries after flushing).
1654
+ *
1655
+ * @see safeClose — CHECKPOINT + connection/database close
1656
+ */
1657
+ const flushWAL = async () => {
1658
+ const c = conn;
1659
+ if (!c)
1660
+ return;
1661
+ try {
1662
+ await (0, conn_lock_js_1.withConnLock)(async () => {
1663
+ const checkpointResult = await c.query('CHECKPOINT');
1664
+ await drainQueryResult(checkpointResult);
1665
+ });
1666
+ }
1667
+ catch (err) {
1668
+ logger_js_1.logger.debug(`GitNexus: LadybugDB CHECKPOINT skipped/failed during WAL flush: ${summarizeError(err)}`);
1669
+ }
1670
+ };
1671
+ exports.flushWAL = flushWAL;
1672
+ /**
1673
+ * Issue a manual `CHECKPOINT` against the current connection and surface
1674
+ * any engine error to the caller. Unlike {@link flushWAL}, this variant
1675
+ * does NOT swallow Ladybug rename/remove IO failures — the manual
1676
+ * checkpoint driver (`wal-checkpoint-driver.ts`) relies on the rejection
1677
+ * to drive its bounded retry loop. Returns `false` when no connection is
1678
+ * open (the caller treats this as a no-op success — there is no WAL to
1679
+ * flush). Returns `true` after a successful CHECKPOINT + drain.
1680
+ *
1681
+ * The split from `flushWAL` is deliberate: every other CHECKPOINT site
1682
+ * (server flush, safeClose) is best-effort and prefers a silent skip;
1683
+ * the manual driver, by contrast, must observe failures to decide
1684
+ * whether to retry.
1685
+ */
1686
+ const tryFlushWAL = async () => {
1687
+ const c = conn;
1688
+ if (!c)
1689
+ return false;
1690
+ // Runs on the periodic WAL-checkpoint driver. The lock makes this CHECKPOINT
1691
+ // wait for any in-flight COPY / writeback on the singleton connection instead
1692
+ // of executing concurrently with it (the `analyze --pdg` heap-corruption bug).
1693
+ await (0, conn_lock_js_1.withConnLock)(async () => {
1694
+ const checkpointResult = await c.query('CHECKPOINT');
1695
+ await drainQueryResult(checkpointResult);
1696
+ });
1697
+ return true;
1698
+ };
1699
+ exports.tryFlushWAL = tryFlushWAL;
1700
+ /**
1701
+ * Flush the WAL and close the connection and database handles.
1702
+ *
1703
+ * Consolidates the CHECKPOINT + close pattern into a single function so
1704
+ * callers never call conn.close() or db.close() directly (#1376).
1705
+ * An ESLint no-restricted-syntax rule enforces this — see eslint.config.mjs.
1706
+ *
1707
+ * @see flushWAL — CHECKPOINT-only (connection stays open)
1708
+ * @see closeLbug — safeClose + module state reset (full teardown)
1709
+ */
1710
+ const safeClose = async () => {
1711
+ await (0, exports.flushWAL)();
1712
+ // Capture before close — currentDbPath stays set so the Windows post-close
1713
+ // probe below knows which file to wait on.
1714
+ const closingDbPath = currentDbPath;
1715
+ if (conn) {
1716
+ try {
1717
+ // eslint-disable-next-line no-restricted-syntax -- sole authorised close site
1718
+ await conn.close();
1719
+ }
1720
+ catch {
1721
+ /* best-effort */
1722
+ }
1723
+ conn = null;
1724
+ }
1725
+ if (db) {
1726
+ try {
1727
+ // eslint-disable-next-line no-restricted-syntax -- sole authorised close site
1728
+ await db.close();
1729
+ }
1730
+ catch {
1731
+ /* best-effort */
1732
+ }
1733
+ db = null;
1734
+ }
1735
+ // Windows: libuv reports `db.close()` resolved before the kernel has
1736
+ // released the file handle. A subsequent `new Database(samePath)` in
1737
+ // the same process can race the release. The probe (lbug-config.ts)
1738
+ // forces any residual lock to surface as EBUSY/EPERM/EACCES so the
1739
+ // open-time retry absorbs the lag.
1740
+ if (process.platform === 'win32' && closingDbPath) {
1741
+ const released = await (0, lbug_config_js_1.waitForWindowsHandleRelease)(closingDbPath);
1742
+ if (!released) {
1743
+ // Probe exhausted with a lock code still in flight. The next
1744
+ // openLbugConnection will absorb whatever residual lag remains, but
1745
+ // a chronic warning helps operators spot AV interference (Windows
1746
+ // Defender holding the file far past the 250ms budget).
1747
+ logger_js_1.logger.warn({ dbPath: closingDbPath }, '⚠️ LadybugDB file handle still locked after close (Windows). If this repeats, check antivirus/Defender exclusions for the GitNexus storage directory.');
1748
+ }
1749
+ }
1750
+ if (closingDbPath) {
1751
+ await (0, sidecar_recovery_js_1.finalizeLbugSidecarsAfterClose)(closingDbPath, { logger: logger_js_1.logger });
1752
+ }
1753
+ };
1754
+ exports.safeClose = safeClose;
1755
+ /**
1756
+ * CHECKPOINT for durability, then DELIBERATELY skip the native connection/database
1757
+ * teardown. The name encodes the contract — there is no boolean flag to misuse:
1758
+ * call this ONLY from a path that guarantees a `process.exit` immediately after
1759
+ * (the CLI analyze success/SIGINT paths and the forked worker).
1760
+ *
1761
+ * LadybugDB's ClientContext/Connection destructor can double-free after large
1762
+ * --pdg writes (gdb: `double free or corruption` in ClientContext::~ClientContext
1763
+ * via NodeConnection::Close), aborting the process AFTER a fully-written,
1764
+ * checkpointed index. flushWAL already persisted the data; process exit reclaims
1765
+ * the native handles. We leave the handles referenced and module state intact so a
1766
+ * GC finalizer cannot run the same destructor before exit, and any post-analyze
1767
+ * read reuses the live connection. Mirrors the pool adapter's fire-and-forget
1768
+ * native teardown (pool-adapter.ts) and the ONNX native-cleanup philosophy.
1769
+ * Workaround for a LadybugDB engine bug (to be reported upstream).
1770
+ *
1771
+ * SAFETY: only valid when a process.exit is guaranteed to follow. Long-lived
1772
+ * callers (MCP server, tests) leave `skipNativeCloseOnExit` unset, so
1773
+ * runFullAnalysis closes for real via {@link closeLbug} — never this.
1774
+ */
1775
+ const closeLbugBeforeExit = async () => {
1776
+ await (0, exports.flushWAL)();
1777
+ // NOTE (#2264): unlike safeClose, this deliberately does NOT run
1778
+ // finalizeLbugSidecarsAfterClose. That step inspects/quarantines orphan WAL +
1779
+ // sidecar files and is designed to run AFTER the native close has released the
1780
+ // WAL handle; running it here — with the connection still open — would risk a
1781
+ // Windows file-lock on the in-use WAL for no benefit. The CHECKPOINT above
1782
+ // already made the index durable, and the next run's preflightLbugSidecars
1783
+ // reconciles any residual WAL on open. The deferred sidecar housekeeping is the
1784
+ // accepted trade-off of skipping the native close to dodge the destructor
1785
+ // double-free.
1786
+ };
1787
+ exports.closeLbugBeforeExit = closeLbugBeforeExit;
1788
+ const closeLbug = async () => {
1789
+ await (0, exports.safeClose)();
1790
+ currentDbPath = null;
1791
+ vectorExtensionLoaded = false;
1792
+ vectorIndexEnsured = false;
1793
+ };
1794
+ exports.closeLbug = closeLbug;
1795
+ /**
1796
+ * Thrown by {@link wipeLbugDbFiles} when a data-bearing member of the
1797
+ * LadybugDB file family is still present after the bounded
1798
+ * remove-and-verify retries (#2409, tri-review 4669518496 P2-4), and by
1799
+ * run-analyze's dirty-recovery block when the crashed run's sidecars can
1800
+ * neither be parked nor removed (this shipping review, FIX 1 — same lock
1801
+ * class, same remediation, and the CLI already renders this type).
1802
+ *
1803
+ * Classify by TYPE (`err instanceof LbugWipeError`) — the repo norm from
1804
+ * #2385 — never by message text. The MESSAGE is nonetheless fully
1805
+ * self-contained (headline + blocked paths + remediation) because
1806
+ * `cgraph serve` forwards only `err.message` over worker IPC
1807
+ * (analyze-worker-core.ts), so the serve surface has nothing but this
1808
+ * string to show the user. The holder framing deliberately covers the
1809
+ * own-process case (FIX 2, finder A): the blocking handle is often a
1810
+ * lingering one from THIS process's just-closed DB or a transient AV scan
1811
+ * — not necessarily another process — so an immediate re-run often
1812
+ * succeeds.
1813
+ */
1814
+ class LbugWipeError extends Error {
1815
+ /** Paths still present (or unverifiable) after all retries. */
1816
+ survivors;
1817
+ constructor(survivors, options) {
1818
+ super(`${options?.headline ??
1819
+ `Failed to remove the LadybugDB index files — still present after ` +
1820
+ `${lbug_config_js_1.HANDLE_RELEASE_PROBE_ATTEMPTS} attempts:`}\n` +
1821
+ survivors.map((p) => ` - ${p}`).join('\n') +
1822
+ `\nThe blocking handle may be another process, a lingering handle from this ` +
1823
+ `process's just-closed database, or an antivirus scan — an immediate re-run ` +
1824
+ `often succeeds. If it persists, ${(0, sidecar_recovery_js_1.lbugLockRemediation)('re-run the analyze')}.`);
1825
+ this.name = 'LbugWipeError';
1826
+ this.survivors = survivors;
1827
+ }
1828
+ }
1829
+ exports.LbugWipeError = LbugWipeError;
1830
+ /**
1831
+ * Remove the LadybugDB file family and VERIFY each member is really gone.
1832
+ *
1833
+ * Owns the canonical 4-file family list — `<lbugPath>`, `.wal`, `.shadow`,
1834
+ * `.lock` — so run-analyze's two wipe sites (full rebuild + the #2409
1835
+ * escalation valve) can never drift apart. `.shadow` is included because a
1836
+ * checkpoint-in-flight crash leaves a shadow sidecar, and a stale shadow next
1837
+ * to a freshly created DB file is replay poison on the next open (#2409).
1838
+ *
1839
+ * Verification contract (tri-review 4669518496 P2-4 — the old inline loops
1840
+ * swallowed rm failures and let `initLbug` reopen a still-populated DB the
1841
+ * run believed it wiped): after `fs.rm({ recursive, force })`, each path is
1842
+ * probed and counts as GONE only when the probe rejects with **ENOENT**. A
1843
+ * resolving probe, or a rejection in the EPERM/EBUSY/EACCES class (Windows
1844
+ * delete-pending / handle-release lag — see HANDLE_RELEASE_LOCK_CODES in
1845
+ * lbug-config.ts), or any other code means the path is not verifiably gone:
1846
+ * it is retried on the shared handle-release budget
1847
+ * (HANDLE_RELEASE_PROBE_ATTEMPTS × linear HANDLE_RELEASE_PROBE_DELAY_MS,
1848
+ * lbug-config.ts — the previous private mirror constants were
1849
+ * documentation-coupled copies) and then handled by CLASS (this shipping
1850
+ * review, FIX 2):
1851
+ *
1852
+ * - DATA-BEARING members (`<lbugPath>`, `.wal`, `.shadow`) — a survivor
1853
+ * means the reopen would resurrect rows this run believes wiped: throw
1854
+ * a typed {@link LbugWipeError}.
1855
+ * - `.lock` — contentless: `initLbug` recreates it, and a genuinely held
1856
+ * lock surfaces as initLbug's own lock-busy classification (a better
1857
+ * error than this one). A `.lock`-only survivor (an AV-held
1858
+ * delete-pending handle outlasting the budget previously failed a
1859
+ * perfectly sound rebuild) logs a warning and CONTINUES.
1860
+ *
1861
+ * Linux unlinked-but-open (name gone, holder keeps the old inode) probes
1862
+ * ENOENT and is accepted by design — both production wipe sites run after a
1863
+ * real `closeLbug()`.
1864
+ *
1865
+ * Deliberately OUT of this contract: `cleanupOldKuzuFiles`
1866
+ * (repo-manager.ts) sweeps the LEGACY kuzu-era file family during storage
1867
+ * migration — different family, best-effort by design; and
1868
+ * `sweepStaleSidecars` (lbug-config.ts) is a test-fixture-gated open-retry
1869
+ * fallback that must never delete production files. Neither wipes the live
1870
+ * DB the run is about to recreate, so neither needs (or may share) the
1871
+ * loud-failure contract here.
1872
+ */
1873
+ const wipeLbugDbFiles = async (lbugPath) => {
1874
+ const lockPath = `${lbugPath}.lock`;
1875
+ const family = [lbugPath, `${lbugPath}.wal`, `${lbugPath}.shadow`, lockPath];
1876
+ let survivors = [];
1877
+ for (let attempt = 1; attempt <= lbug_config_js_1.HANDLE_RELEASE_PROBE_ATTEMPTS; attempt++) {
1878
+ survivors = [];
1879
+ for (const f of family) {
1880
+ try {
1881
+ await promises_1.default.rm(f, { recursive: true, force: true });
1882
+ }
1883
+ catch {
1884
+ // `force: true` swallows ENOENT, so a rejection is a real failure —
1885
+ // but the ENOENT-probe below stays authoritative either way (another
1886
+ // process may have removed the path between the rm and the probe).
1887
+ }
1888
+ const gone = await promises_1.default.access(f).then(() => false, // still present
1889
+ (err) => err?.code === 'ENOENT');
1890
+ if (!gone)
1891
+ survivors.push(f);
1892
+ }
1893
+ if (survivors.length === 0)
1894
+ return;
1895
+ if (attempt < lbug_config_js_1.HANDLE_RELEASE_PROBE_ATTEMPTS) {
1896
+ await (0, lbug_config_js_1.sleep)(lbug_config_js_1.HANDLE_RELEASE_PROBE_DELAY_MS * attempt);
1897
+ }
1898
+ }
1899
+ // Class split (FIX 2): the contentless `.lock` never fails the wipe.
1900
+ const dataSurvivors = survivors.filter((f) => f !== lockPath);
1901
+ if (survivors.includes(lockPath)) {
1902
+ logger_js_1.logger.warn(`GitNexus: ${lockPath} is still present after the wipe retries — continuing: the ` +
1903
+ 'lock file is contentless and initLbug recreates it; a genuinely held lock will ' +
1904
+ "surface as the reopen's own lock-busy error.");
1905
+ }
1906
+ if (dataSurvivors.length > 0) {
1907
+ throw new LbugWipeError(dataSurvivors);
1908
+ }
1909
+ };
1910
+ exports.wipeLbugDbFiles = wipeLbugDbFiles;
1911
+ const isLbugReady = () => conn !== null && db !== null;
1912
+ exports.isLbugReady = isLbugReady;
1913
+ /**
1914
+ * Multi-label alternation over exactly the labels that can own embedding
1915
+ * rows: EMBEDDABLE_LABELS plus File, which embedding-pipeline.ts embeds as
1916
+ * the zero-symbol fallback for text-only repositories (#2454). Reserved
1917
+ * keywords are backtick-escaped via {@link escapeTableName}. Probed on
1918
+ * @ladybugdb/core 0.18.0 (this shipping review, FIX 4): the full multi-label
1919
+ * alternation parses, executes, and deletes exactly the joined rows —
1920
+ * replacing the unlabeled `MATCH (n)` that scanned EVERY node table per
1921
+ * chunk (BasicBlock-dominated under `--pdg`) when only embeddable labels
1922
+ * can match an embedding row. Including File is free for code repositories:
1923
+ * they never hold File embedding rows, so the extra label joins nothing.
1924
+ */
1925
+ const embeddableLabelMatch = () => ['File', ...EMBEDDABLE_LABELS].map((l) => escapeTableName(l)).join('|');
1926
+ // LADYBUGDB-CONTRACT: matches @ladybugdb/core ^0.18.0 native binder text,
1927
+ // probe-recorded: `Binder exception: Table CodeEmbedding does not exist.`
1928
+ // When bumping LadybugDB, re-validate — `git grep "LADYBUGDB-CONTRACT"`
1929
+ // enumerates every version-coupled spot.
1930
+ const isMissingEmbeddingTableError = (err) => {
1931
+ const msg = err instanceof Error ? err.message : String(err);
1932
+ return msg.includes(`Table ${schema_js_1.EMBEDDING_TABLE_NAME} does not exist`);
1933
+ };
1934
+ /**
1935
+ * Delete all nodes (and their relationships) for a specific file from LadybugDB
1936
+ * @param filePath - The file path to delete nodes for
1937
+ * @param dbPath - Optional path to LadybugDB for per-query connection
1938
+ * @returns Object with counts of deleted nodes
1939
+ */
1940
+ const deleteNodesForFile = async (filePath, dbPath) => {
1941
+ const usePerQuery = !!dbPath;
1942
+ // Set up connection (either use existing or create per-query)
1943
+ let tempHandle = null;
1944
+ let tempConn = null;
1945
+ let targetConn = conn;
1946
+ if (usePerQuery) {
1947
+ tempHandle = await (0, lbug_config_js_1.openLbugConnection)(lbug, dbPath);
1948
+ tempConn = tempHandle.conn;
1949
+ targetConn = tempConn;
1950
+ }
1951
+ else if (!conn) {
1952
+ throw new Error('LadybugDB not initialized. Provide dbPath or call initLbug first.');
1953
+ }
1954
+ try {
1955
+ let deletedNodes = 0;
1956
+ const escapedPath = (0, cypher_escape_js_1.escapeCypherString)(filePath);
1957
+ // Delete the file's embedding rows FIRST, while their owning nodes are
1958
+ // still present: node ids are label-first — generateId = `${label}:${name}`
1959
+ // (src/lib/utils.ts) with qualified names that embed the file path — so
1960
+ // the old `e.nodeId STARTS WITH '<filePath>'` shape never matched a row
1961
+ // (tri-review 4669518496 P2-1). Join through the nodes on exact id
1962
+ // equality instead, scoped to the embeddable labels (FIX 4 — see
1963
+ // embeddableLabelMatch); ordering is load-bearing — after the DETACH
1964
+ // DELETE loop below the join would match nothing.
1965
+ try {
1966
+ await queryAndDrain(targetConn, `MATCH (n:${embeddableLabelMatch()}) WHERE n.filePath = '${escapedPath}' ` +
1967
+ `MATCH (e:${schema_js_1.EMBEDDING_TABLE_NAME}) WHERE e.nodeId = n.id DELETE e`);
1968
+ }
1969
+ catch (err) {
1970
+ // Deliberately legacy-permissive (pinned contract:
1971
+ // lbug-conn-serialization U5 and lbug-core-adapter expect this variant
1972
+ // to resolve `{deletedNodes: 0}` even on a bogus dbPath): the singular
1973
+ // variant swallows per-statement failures wholesale — its per-table
1974
+ // loop below does the same — so a partial rethrow here would be
1975
+ // incoherent with the rest of the function. The STRICT
1976
+ // rethrow-except-missing-table policy lives in deleteNodesForFiles,
1977
+ // the #2409 incremental writeback path (FIX 4). The one case worth a
1978
+ // diagnostic is the missing embedding table.
1979
+ if (isMissingEmbeddingTableError(err)) {
1980
+ logger_js_1.logger.warn({ err }, `deleteNodesForFile: ${schema_js_1.EMBEDDING_TABLE_NAME} table does not exist — ` +
1981
+ 'skipping embedding-row deletes for this DB.');
1982
+ }
1983
+ }
1984
+ // Delete nodes from each table that has filePath
1985
+ // DETACH DELETE removes the node and all its relationships
1986
+ for (const tableName of schema_js_1.NODE_TABLES) {
1987
+ // Skip tables that don't have filePath (Community, Process)
1988
+ if (tableName === 'Community' || tableName === 'Process')
1989
+ continue;
1990
+ try {
1991
+ // First count how many we'll delete. On the singleton connection this
1992
+ // count runs inside withConnLock (incremental --pdg writeback executes
1993
+ // while the WAL driver is live); per-query/temp connections skip the
1994
+ // lock, matching queryAndDrain's `targetConn === conn` gate — the sibling
1995
+ // DETACH DELETE below already routes through it. (#2264)
1996
+ const tn = escapeTableName(tableName);
1997
+ const countCypher = `MATCH (n:${tn}) WHERE n.filePath = '${escapedPath}' RETURN count(n) AS cnt`;
1998
+ const runCount = async () => readQueryRows(await targetConn.query(countCypher));
1999
+ const rows = isSharedSingletonConn(targetConn)
2000
+ ? await (0, conn_lock_js_1.withConnLock)(runCount)
2001
+ : await runCount();
2002
+ const count = Number(rows[0]?.cnt ?? rows[0]?.[0] ?? 0);
2003
+ if (count > 0) {
2004
+ // Delete nodes (and implicitly their relationships via DETACH)
2005
+ await queryAndDrain(targetConn, `MATCH (n:${tn}) WHERE n.filePath = '${escapedPath}' DETACH DELETE n`);
2006
+ deletedNodes += count;
2007
+ }
2008
+ }
2009
+ catch (e) {
2010
+ // Some tables may not support this query, skip
2011
+ }
2012
+ }
2013
+ return { deletedNodes };
2014
+ }
2015
+ finally {
2016
+ // Close per-query connection if used
2017
+ if (tempHandle)
2018
+ await (0, lbug_config_js_1.closeLbugConnection)(tempHandle);
2019
+ }
2020
+ };
2021
+ exports.deleteNodesForFile = deleteNodesForFile;
2022
+ /**
2023
+ * Chunk size for {@link deleteNodesForFiles}. 200 paths keeps each
2024
+ * statement ~13KB (well inside parser limits) while a ~700-file write set
2025
+ * still collapses from ~13,000 statements to 124: 31 statements per chunk
2026
+ * (1 CodeEmbedding join-delete + 30 filePath-bearing node tables — the
2027
+ * 32-table NODE_TABLES roster minus Community/Process) × 4 chunks. The
2028
+ * original "~40" claim under-counted the per-chunk statement fan-out
2029
+ * (tri-review 4669518496 accuracy sweep).
2030
+ */
2031
+ exports.DELETE_FILES_CHUNK_SIZE = 200;
2032
+ /**
2033
+ * Batched variant of {@link deleteNodesForFile} for the incremental
2034
+ * writeback (#2409). One `DETACH DELETE … WHERE n.filePath IN […]` per
2035
+ * node table per chunk of paths, instead of a count + delete per table
2036
+ * per FILE. The per-file loop issued ~13,000 single-row write
2037
+ * transactions on a ~700-file write set — a WAL-append storm that made
2038
+ * the incremental path slower than a full rebuild and is the write
2039
+ * pattern behind the native mid-writeback deaths reported in #2409.
2040
+ *
2041
+ * NO general error swallowing: a zero-match chunk is a no-op success by
2042
+ * construction (every node table except Community/Process has a filePath
2043
+ * column), so anything thrown here is a real engine failure the caller
2044
+ * must see — silently skipping was exactly how #2409 hid its root cause.
2045
+ * The single tolerated exception (FIX 4) is the missing-embedding-table
2046
+ * binder error on the embedding join-delete: a DB created without
2047
+ * EMBEDDING_SCHEMA cannot own embedding rows, so skipping that one
2048
+ * statement is sound, while failing would brick every incremental run on
2049
+ * such a DB until `--force`. Statement count per chunk is unchanged by the
2050
+ * multi-label join: 1 embedding join-delete + 30 node-table deletes = 31
2051
+ * (the rejected per-label fallback shape would have been 19 + 30 = 49).
2052
+ * Singleton-connection only: the analyze writeback owns the write lock,
2053
+ * and `queryAndDrain` routes through `withConnLock` for it (the WAL
2054
+ * checkpoint driver is live during this).
2055
+ */
2056
+ const deleteNodesForFiles = async (filePaths, options = {}) => {
2057
+ if (!conn) {
2058
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2059
+ }
2060
+ const targetConn = conn;
2061
+ let warnedMissingEmbeddingTable = false;
2062
+ for (let i = 0; i < filePaths.length; i += exports.DELETE_FILES_CHUNK_SIZE) {
2063
+ const chunk = filePaths.slice(i, i + exports.DELETE_FILES_CHUNK_SIZE);
2064
+ const listLiteral = `[${chunk.map((p) => `'${(0, cypher_escape_js_1.escapeCypherString)(p)}'`).join(', ')}]`;
2065
+ // Embedding rows key on their OWNING NODE's id: generateId builds
2066
+ // label-first ids — `${label}:${name}` (src/lib/utils.ts) with qualified
2067
+ // names that embed the file path (e.g. `Function:src/f.ts:fn0:1`) — so
2068
+ // the previous bare-path `e.nodeId STARTS WITH '<filePath>'` OR-chain
2069
+ // could never match anything (tri-review 4669518496 P2-1: the embedding
2070
+ // delete was a no-op). Join through the nodes instead: one multi-label
2071
+ // MATCH over exactly the embeddable labels (FIX 4, probe-proven on
2072
+ // 0.18.0 — see embeddableLabelMatch; the old unlabeled `MATCH (n)`
2073
+ // scanned every node table per chunk, BasicBlock-dominated under
2074
+ // `--pdg`, when only embeddable labels can own rows), and
2075
+ // `e.nodeId = n.id` equality is exact — no `File:a.ts` / `File:a.tsx`
2076
+ // prefix collisions. ORDER IS LOAD-BEARING: this must run BEFORE the
2077
+ // DETACH DELETE loop below — once the nodes are gone the join matches
2078
+ // nothing (empirically verified against @ladybugdb/core 0.18.0).
2079
+ try {
2080
+ await queryAndDrain(targetConn, `MATCH (n:${embeddableLabelMatch()}) WHERE n.filePath IN ${listLiteral} ` +
2081
+ `MATCH (e:${schema_js_1.EMBEDDING_TABLE_NAME}) WHERE e.nodeId = n.id DELETE e`);
2082
+ }
2083
+ catch (err) {
2084
+ // Tolerate exactly the missing-embedding-table binder error: a
2085
+ // build-variant DB without EMBEDDING_SCHEMA would otherwise brick
2086
+ // every incremental run until `--force` (FIX 4). The no-swallow
2087
+ // policy stays for every real failure — anything else rethrows.
2088
+ if (!isMissingEmbeddingTableError(err))
2089
+ throw err;
2090
+ if (!warnedMissingEmbeddingTable) {
2091
+ warnedMissingEmbeddingTable = true;
2092
+ logger_js_1.logger.warn({ err }, `deleteNodesForFiles: ${schema_js_1.EMBEDDING_TABLE_NAME} table does not exist — ` +
2093
+ 'skipping embedding-row deletes for this writeback.');
2094
+ }
2095
+ }
2096
+ for (const tableName of schema_js_1.NODE_TABLES) {
2097
+ // Community/Process are graph-wide (no filePath); the orchestrator
2098
+ // drops them wholesale via deleteAllCommunitiesAndProcesses.
2099
+ if (tableName === 'Community' || tableName === 'Process')
2100
+ continue;
2101
+ const tn = escapeTableName(tableName);
2102
+ await queryAndDrain(targetConn, `MATCH (n:${tn}) WHERE n.filePath IN ${listLiteral} DETACH DELETE n`);
2103
+ }
2104
+ options.onChunk?.(Math.min(i + exports.DELETE_FILES_CHUNK_SIZE, filePaths.length), filePaths.length);
2105
+ }
2106
+ };
2107
+ exports.deleteNodesForFiles = deleteNodesForFiles;
2108
+ const getEmbeddingTableName = () => schema_js_1.EMBEDDING_TABLE_NAME;
2109
+ exports.getEmbeddingTableName = getEmbeddingTableName;
2110
+ /**
2111
+ * Return the distinct repo-relative paths of files that import
2112
+ * `targetFilePath` according to the IMPORTS edges currently in the
2113
+ * DB. Used by the incremental writeback path to expand the
2114
+ * "files-to-rewrite" set so that files importing a changed file get
2115
+ * their edges (which may have been refined by cross-file resolution)
2116
+ * re-emitted, rather than left stale in the DB.
2117
+ *
2118
+ * The DB query reads the *previous* run's state — pre-pipeline, before
2119
+ * any nodes are deleted — so the returned importers are "files that
2120
+ * USED TO import the target". That's the right set to invalidate:
2121
+ * those are the files whose edges in the DB might no longer match
2122
+ * what cross-file resolution produces given the changed file's new
2123
+ * exports.
2124
+ */
2125
+ const queryImporters = async (targetFilePath) => {
2126
+ const c = conn;
2127
+ if (!c) {
2128
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2129
+ }
2130
+ const escaped = (0, cypher_escape_js_1.escapeCypherString)(targetFilePath);
2131
+ const cypher = `
2132
+ MATCH (a)-[r:${schema_js_1.REL_TABLE_NAME}]->(b)
2133
+ WHERE r.type = 'IMPORTS' AND b.filePath = '${escaped}'
2134
+ RETURN DISTINCT a.filePath AS importer
2135
+ `;
2136
+ // Runs inside the connection lock: queryImporters is called in the importer-BFS
2137
+ // loop during incremental --pdg writeback while the WAL driver is live, so an
2138
+ // unlocked conn.query here could race a concurrent CHECKPOINT on the singleton.
2139
+ return (0, conn_lock_js_1.withConnLock)(async () => {
2140
+ let queryResult;
2141
+ try {
2142
+ queryResult = await c.query(cypher);
2143
+ const result = Array.isArray(queryResult) ? queryResult[0] : queryResult;
2144
+ const rows = await result.getAll();
2145
+ const out = [];
2146
+ for (const row of rows) {
2147
+ const v = row.importer;
2148
+ if (typeof v === 'string' && v.length > 0)
2149
+ out.push(v);
2150
+ }
2151
+ return out;
2152
+ }
2153
+ catch {
2154
+ return [];
2155
+ }
2156
+ finally {
2157
+ if (queryResult)
2158
+ await (0, query_result_utils_js_1.closeQueryResults)(queryResult);
2159
+ }
2160
+ });
2161
+ };
2162
+ exports.queryImporters = queryImporters;
2163
+ /**
2164
+ * Batched variant of {@link queryImporters} for the incremental importer
2165
+ * BFS (#2409): distinct importers of ANY of the target paths, one query per
2166
+ * chunk per BFS depth instead of one query per frontier FILE (a ~700-file
2167
+ * frontier was ~700 sequential round-trips, each taking the connection lock
2168
+ * against the live WAL checkpoint driver — ~5.6s of the writeback measured).
2169
+ *
2170
+ * Same contract as the singular form: reads the pre-pipeline DB state and
2171
+ * swallows per-chunk query failures into a smaller result (correctness
2172
+ * degrades on that branch — under-expansion means possibly-stale edges —
2173
+ * but the DB stays writable and the writeback proceeds). Unlike the singular
2174
+ * form the degradation is not silent (tri-review 4669518496 P2-5): every
2175
+ * dropped chunk is logged and reported through `options.onChunkFailure`, so
2176
+ * the orchestrator can count it into the #2410 crash diagnostics
2177
+ * (`incrementalInProgress.droppedImporterChunks`).
2178
+ */
2179
+ const queryImportersBatch = async (targetFilePaths, options = {}) => {
2180
+ const c = conn;
2181
+ if (!c) {
2182
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2183
+ }
2184
+ const importers = new Set();
2185
+ for (let i = 0; i < targetFilePaths.length; i += exports.DELETE_FILES_CHUNK_SIZE) {
2186
+ // `i` only ever advances in whole chunk strides, so this is exact.
2187
+ const chunkIndex = i / exports.DELETE_FILES_CHUNK_SIZE;
2188
+ const chunk = targetFilePaths.slice(i, i + exports.DELETE_FILES_CHUNK_SIZE);
2189
+ const listLiteral = `[${chunk.map((p) => `'${(0, cypher_escape_js_1.escapeCypherString)(p)}'`).join(', ')}]`;
2190
+ const cypher = `
2191
+ MATCH (a)-[r:${schema_js_1.REL_TABLE_NAME}]->(b)
2192
+ WHERE r.type = 'IMPORTS' AND b.filePath IN ${listLiteral}
2193
+ RETURN DISTINCT a.filePath AS importer
2194
+ `;
2195
+ await (0, conn_lock_js_1.withConnLock)(async () => {
2196
+ let queryResult;
2197
+ try {
2198
+ queryResult = await c.query(cypher);
2199
+ const result = Array.isArray(queryResult) ? queryResult[0] : queryResult;
2200
+ const rows = await result.getAll();
2201
+ for (const row of rows) {
2202
+ const v = row.importer;
2203
+ if (typeof v === 'string' && v.length > 0)
2204
+ importers.add(v);
2205
+ }
2206
+ }
2207
+ catch (err) {
2208
+ // Degrade-don't-fail, mirroring queryImporters — but LOUDLY
2209
+ // (tri-review 4669518496 P2-5): a dropped chunk means every importer
2210
+ // it would have surfaced keeps possibly-stale edges this run, and the
2211
+ // old bare `catch {}` left no trace of that anywhere. pino idiom:
2212
+ // `err` key — `error` serializes to `{}`.
2213
+ logger_js_1.logger.warn({ err }, `Incremental importer BFS: dropped chunk ${chunkIndex} (${chunk.length} target path(s)) — ` +
2214
+ 'importer expansion degrades for this run; affected importers may keep stale edges until the next full rebuild.');
2215
+ options.onChunkFailure?.(chunkIndex, chunk.length, err);
2216
+ }
2217
+ finally {
2218
+ if (queryResult)
2219
+ await (0, query_result_utils_js_1.closeQueryResults)(queryResult);
2220
+ }
2221
+ });
2222
+ }
2223
+ // Cypher without ORDER BY is unordered — sort so downstream chunking and
2224
+ // logs are stable run-to-run (matches diffFileHashes' sorted outputs).
2225
+ return [...importers].sort();
2226
+ };
2227
+ exports.queryImportersBatch = queryImportersBatch;
2228
+ /**
2229
+ * Drop every Community and Process node (and their MEMBER_OF /
2230
+ * STEP_IN_PROCESS edges via DETACH DELETE). Used at the start of an
2231
+ * incremental run so the communities and processes phases regenerate
2232
+ * them from scratch on the merged graph — required for the
2233
+ * "Leiden runs on the FULL graph" correctness invariant.
2234
+ */
2235
+ const deleteAllCommunitiesAndProcesses = async () => {
2236
+ const c = conn;
2237
+ if (!c) {
2238
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2239
+ }
2240
+ // count + DETACH DELETE run inside the connection lock so they cannot execute
2241
+ // concurrently with the WAL-checkpoint driver's CHECKPOINT on the singleton
2242
+ // connection. This runs during incremental --pdg writeback while the driver is
2243
+ // live; mirrors the wrapped deleteAllInterprocTaintPaths / deleteAllCallSummaries.
2244
+ return (0, conn_lock_js_1.withConnLock)(async () => {
2245
+ let nodesDeleted = 0;
2246
+ for (const label of ['Community', 'Process']) {
2247
+ let countResult;
2248
+ try {
2249
+ countResult = await c.query(`MATCH (n:${label}) RETURN count(n) AS cnt`);
2250
+ const result = Array.isArray(countResult) ? countResult[0] : countResult;
2251
+ const rows = await result.getAll();
2252
+ const count = Number(rows[0]?.cnt ?? rows[0]?.[0] ?? 0);
2253
+ if (count > 0) {
2254
+ await (0, query_result_utils_js_1.closeQueryResults)(await c.query(`MATCH (n:${label}) DETACH DELETE n`));
2255
+ nodesDeleted += count;
2256
+ }
2257
+ }
2258
+ catch {
2259
+ // Table may not exist yet on a freshly-initialized DB — fine.
2260
+ }
2261
+ finally {
2262
+ if (countResult)
2263
+ await (0, query_result_utils_js_1.closeQueryResults)(countResult);
2264
+ }
2265
+ }
2266
+ return { nodesDeleted };
2267
+ });
2268
+ };
2269
+ exports.deleteAllCommunitiesAndProcesses = deleteAllCommunitiesAndProcesses;
2270
+ /**
2271
+ * Shared mechanics for the delete-all-relationships-of-one-type family
2272
+ * ({@link deleteAllInterprocTaintPaths}, {@link deleteAllCallSummaries},
2273
+ * {@link deleteAllInjects}, {@link deleteSpringAutoConfigurationDeclarations}):
2274
+ * count the matching CodeRelation rows, then DELETE them (relationship-level —
2275
+ * these are edge types, not node labels, so endpoints are untouched).
2276
+ *
2277
+ * count + DELETE run as one critical section on the singleton connection so a
2278
+ * concurrent WAL-checkpoint cannot corrupt native state mid-delete (#pdg).
2279
+ *
2280
+ * @param relType the CodeRelation `type` value to delete (e.g. 'INJECTS')
2281
+ * @param logTag the `[tag]` prefix on the abort error message
2282
+ * @param duplicateNoun what the abort message says would be duplicated
2283
+ * @param exactReasons optional reason allowlist for a shared relationship type
2284
+ */
2285
+ const deleteAllRelationshipsOfType = async (relType, logTag, duplicateNoun, exactReasons) => {
2286
+ const c = conn;
2287
+ if (!c) {
2288
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2289
+ }
2290
+ return (0, conn_lock_js_1.withConnLock)(async () => {
2291
+ let edgesDeleted = 0;
2292
+ let countResult;
2293
+ const reasonFilter = exactReasons === undefined || exactReasons.length === 0
2294
+ ? ''
2295
+ : ` AND (${exactReasons
2296
+ .map((reason) => `r.reason = '${(0, cypher_escape_js_1.escapeCypherString)(reason)}'`)
2297
+ .join(' OR ')})`;
2298
+ const predicate = `r.type = '${(0, cypher_escape_js_1.escapeCypherString)(relType)}'${reasonFilter}`;
2299
+ try {
2300
+ countResult = await c.query(`MATCH ()-[r:CodeRelation]->() WHERE ${predicate} RETURN count(r) AS cnt`);
2301
+ const result = Array.isArray(countResult) ? countResult[0] : countResult;
2302
+ const rows = await result.getAll();
2303
+ const count = Number(rows[0]?.cnt ?? rows[0]?.[0] ?? 0);
2304
+ if (count > 0) {
2305
+ await (0, query_result_utils_js_1.closeQueryResults)(await c.query(`MATCH ()-[r:CodeRelation]->() WHERE ${predicate} DELETE r`));
2306
+ edgesDeleted = count;
2307
+ }
2308
+ }
2309
+ catch (err) {
2310
+ // A missing table on a freshly-initialized DB is the benign, expected case
2311
+ // (the count query above is what throws) — stay silent. Any OTHER failure
2312
+ // (lock, disk, native error) would leave stale rows that the subsequent
2313
+ // re-extract then DUPLICATES (CodeRelation has no PK), so it must ABORT
2314
+ // the writeback (#2084 review P2-5): re-throw so the caller's crash-
2315
+ // recovery dirty flag forces a clean full rebuild on the next run, rather
2316
+ // than silently writing duplicate rows. The benign-vs-rethrow branch is
2317
+ // pure, extracted, and pinned by unit tests: `classifyDeleteAllError`
2318
+ // (lbug-config.ts, test/unit/lbug-delete-all-error.test.ts).
2319
+ const msg = err instanceof Error ? err.message : String(err);
2320
+ if ((0, lbug_config_js_1.classifyDeleteAllError)(err) === 'benign-missing-table') {
2321
+ if (countResult)
2322
+ await (0, query_result_utils_js_1.closeQueryResults)(countResult);
2323
+ return { edgesDeleted };
2324
+ }
2325
+ if (countResult)
2326
+ await (0, query_result_utils_js_1.closeQueryResults)(countResult);
2327
+ throw new Error(`[${logTag}] failed to clear existing ${relType} edges before incremental ` +
2328
+ `re-write (${msg}) — aborting to avoid ${duplicateNoun}; ` +
2329
+ `the next run will full-rebuild`);
2330
+ }
2331
+ if (countResult)
2332
+ await (0, query_result_utils_js_1.closeQueryResults)(countResult);
2333
+ return { edgesDeleted };
2334
+ });
2335
+ };
2336
+ /**
2337
+ * Drop every interprocedural `TAINT_PATH` relationship (#2084 M4 U6). Used at
2338
+ * the start of an incremental `--pdg` writeback so the `taintSummaries` phase
2339
+ * re-materialises them from scratch on the FULL recomputed graph.
2340
+ *
2341
+ * TAINT_PATH validity is a WHOLE-PROGRAM property (a flow A→C can be
2342
+ * invalidated by a change to an INTERMEDIATE function whose file is neither A
2343
+ * nor C). The endpoint-writability extract rule (`extractChangedSubgraph`)
2344
+ * cannot see that — an A→C edge between two unchanged files would be skipped
2345
+ * and a stale finding would survive. So, exactly like Community/Process, the
2346
+ * sound move is delete-all-then-rebuild: cheap because TAINT_PATH is sparse
2347
+ * (per-run capped), and the compute side already rebuilds every summary each
2348
+ * run. Relationship-level (TAINT_PATH is an edge type, not a node label), so a
2349
+ * plain DELETE on the typed CodeRelation rows — endpoints are untouched.
2350
+ */
2351
+ const deleteAllInterprocTaintPaths = async () => deleteAllRelationshipsOfType('TAINT_PATH', 'taint-interproc', 'duplicate cross-function findings');
2352
+ exports.deleteAllInterprocTaintPaths = deleteAllInterprocTaintPaths;
2353
+ /**
2354
+ * Drop every `CALL_SUMMARY` relationship (PDG FU-C, U-C3). Used at the start of
2355
+ * an incremental `--pdg` writeback so the `callSummaries` phase re-materialises
2356
+ * them from scratch on the FULL recomputed graph.
2357
+ *
2358
+ * Mirrors {@link deleteAllInterprocTaintPaths}: CALL_SUMMARY is a self-loop edge
2359
+ * type (not a node label), so a plain DELETE on the typed CodeRelation rows
2360
+ * leaves endpoints untouched. `extractChangedSubgraph` re-includes ALL of them
2361
+ * from the fresh graph (`isGraphWideRelType`), so delete-all-then-rebuild keeps
2362
+ * an unchanged function's summary from being lost.
2363
+ */
2364
+ const deleteAllCallSummaries = async () => deleteAllRelationshipsOfType('CALL_SUMMARY', 'call-summary', 'duplicate summaries');
2365
+ exports.deleteAllCallSummaries = deleteAllCallSummaries;
2366
+ /**
2367
+ * Drop every `INJECTS` relationship (DI collection injection, #2200). Used at
2368
+ * the start of an incremental writeback — UNCONDITIONALLY, unlike the
2369
+ * pdg-gated twins above, because the `di` phase runs on every persisting
2370
+ * analyze — so the phase re-materialises them from scratch on the FULL
2371
+ * recomputed graph.
2372
+ *
2373
+ * Mirrors {@link deleteAllInterprocTaintPaths}: INJECTS validity is a
2374
+ * whole-program property (a change to the interface, or a new/removed
2375
+ * implementer, on a THIRD file creates/invalidates edges between two
2376
+ * untouched files), so endpoint-writability extraction can't refresh them.
2377
+ * `extractChangedSubgraph` re-includes ALL of them from the fresh graph
2378
+ * (`isGraphWideRelType`), so delete-all-then-rebuild is the sound move.
2379
+ * Relationship-level (INJECTS is an edge type, not a node label), so a plain
2380
+ * DELETE on the typed CodeRelation rows — endpoints are untouched.
2381
+ */
2382
+ const deleteAllInjects = async () => deleteAllRelationshipsOfType('INJECTS', 'di', 'duplicate INJECTS edges');
2383
+ exports.deleteAllInjects = deleteAllInjects;
2384
+ /**
2385
+ * Drop every Spring AOP `ADVISED_BY` relationship before incremental
2386
+ * writeback. Pointcut/annotation resolution is whole-program: adding a type in
2387
+ * a third file can shadow a wildcard annotation import or change a wildcard
2388
+ * execution match between two otherwise unchanged endpoint files.
2389
+ */
2390
+ const deleteAllAdvisedBy = async () => deleteAllRelationshipsOfType('ADVISED_BY', 'spring-aop', 'duplicate ADVISED_BY edges');
2391
+ exports.deleteAllAdvisedBy = deleteAllAdvisedBy;
2392
+ /** Drop all synthetic Spring AOP evidence nodes before incremental writeback. */
2393
+ const deleteSpringAopEvidenceNodes = async () => {
2394
+ const c = conn;
2395
+ if (!c) {
2396
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2397
+ }
2398
+ return (0, conn_lock_js_1.withConnLock)(async () => {
2399
+ let countResult;
2400
+ const idPrefix = (0, cypher_escape_js_1.escapeCypherString)(aop_js_1.SPRING_AOP_EVIDENCE_ID_PREFIX);
2401
+ const predicate = `n.id STARTS WITH '${idPrefix}'`;
2402
+ try {
2403
+ countResult = await c.query(`MATCH (n:CodeElement) WHERE ${predicate} RETURN count(n) AS cnt`);
2404
+ const result = Array.isArray(countResult) ? countResult[0] : countResult;
2405
+ const rows = await result.getAll();
2406
+ const count = Number(rows[0]?.cnt ?? rows[0]?.[0] ?? 0);
2407
+ if (count > 0) {
2408
+ await (0, query_result_utils_js_1.closeQueryResults)(await c.query(`MATCH (n:CodeElement) WHERE ${predicate} DETACH DELETE n`));
2409
+ }
2410
+ if (countResult)
2411
+ await (0, query_result_utils_js_1.closeQueryResults)(countResult);
2412
+ return { nodesDeleted: count };
2413
+ }
2414
+ catch (err) {
2415
+ if (countResult)
2416
+ await (0, query_result_utils_js_1.closeQueryResults)(countResult);
2417
+ if ((0, lbug_config_js_1.classifyDeleteAllError)(err) === 'benign-missing-table') {
2418
+ return { nodesDeleted: 0 };
2419
+ }
2420
+ const message = err instanceof Error ? err.message : String(err);
2421
+ throw new Error('[spring-aop] failed to clear synthetic evidence before incremental re-write ' +
2422
+ `(${message}) — aborting to avoid stale advice metadata; the next run will full-rebuild`);
2423
+ }
2424
+ });
2425
+ };
2426
+ exports.deleteSpringAopEvidenceNodes = deleteSpringAopEvidenceNodes;
2427
+ /**
2428
+ * Drop Spring-owned auto-configuration `DECLARES` relationships before
2429
+ * incremental writeback. `DECLARES` is generic, so exact reason filtering is
2430
+ * required: other metadata systems must retain their own declarations.
2431
+ */
2432
+ const deleteSpringAutoConfigurationDeclarations = async () => deleteAllRelationshipsOfType('DECLARES', 'spring-auto-configuration', 'duplicate auto-configuration declarations', auto_configuration_js_1.SPRING_AUTO_CONFIGURATION_REASONS);
2433
+ exports.deleteSpringAutoConfigurationDeclarations = deleteSpringAutoConfigurationDeclarations;
2434
+ /**
2435
+ * Drop synthetic source-unavailable auto-configuration Class nodes before
2436
+ * incremental writeback. The fresh full graph re-emits every still-needed
2437
+ * synthetic node; deleting first also removes placeholders that became stale
2438
+ * when a real source class appeared.
2439
+ */
2440
+ const deleteSpringAutoConfigurationSyntheticClasses = async () => {
2441
+ const c = conn;
2442
+ if (!c) {
2443
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2444
+ }
2445
+ return (0, conn_lock_js_1.withConnLock)(async () => {
2446
+ let countResult;
2447
+ const idPrefix = (0, cypher_escape_js_1.escapeCypherString)(auto_configuration_js_1.SPRING_AUTO_CONFIGURATION_SYNTHETIC_ID_PREFIX);
2448
+ const predicate = `n.id STARTS WITH '${idPrefix}'`;
2449
+ try {
2450
+ countResult = await c.query(`MATCH (n:Class) WHERE ${predicate} RETURN count(n) AS cnt`);
2451
+ const result = Array.isArray(countResult) ? countResult[0] : countResult;
2452
+ const rows = await result.getAll();
2453
+ const count = Number(rows[0]?.cnt ?? rows[0]?.[0] ?? 0);
2454
+ if (count > 0) {
2455
+ await (0, query_result_utils_js_1.closeQueryResults)(await c.query(`MATCH (n:Class) WHERE ${predicate} DETACH DELETE n`));
2456
+ }
2457
+ if (countResult)
2458
+ await (0, query_result_utils_js_1.closeQueryResults)(countResult);
2459
+ return { nodesDeleted: count };
2460
+ }
2461
+ catch (err) {
2462
+ if (countResult)
2463
+ await (0, query_result_utils_js_1.closeQueryResults)(countResult);
2464
+ if ((0, lbug_config_js_1.classifyDeleteAllError)(err) === 'benign-missing-table') {
2465
+ return { nodesDeleted: 0 };
2466
+ }
2467
+ const message = err instanceof Error ? err.message : String(err);
2468
+ throw new Error('[spring-auto-configuration] failed to clear synthetic Class nodes before ' +
2469
+ `incremental re-write (${message}) — aborting to avoid stale placeholders; ` +
2470
+ 'the next run will full-rebuild');
2471
+ }
2472
+ });
2473
+ };
2474
+ exports.deleteSpringAutoConfigurationSyntheticClasses = deleteSpringAutoConfigurationSyntheticClasses;
2475
+ // ============================================================================
2476
+ // VECTOR Extension Functions
2477
+ // ============================================================================
2478
+ /**
2479
+ * Load the VECTOR extension on the supplied connection (or the singleton
2480
+ * writable connection when none is given). Returns false when VECTOR is
2481
+ * unavailable so semantic search can fall back to exact scan.
2482
+ */
2483
+ const loadVectorExtension = async (targetConn, opts = {}) => {
2484
+ const useModuleState = targetConn === undefined;
2485
+ if (useModuleState && vectorExtensionLoaded)
2486
+ return true;
2487
+ // No platform gate. Windows was hard-refused here for years on the strength
2488
+ // of an early-era report that in-process INSTALL VECTOR could SIGSEGV
2489
+ // (#1365) — but the extension server ships win_amd64 VECTOR artifacts for
2490
+ // every 0.18.x extension version (probed live: v0.18.0 and v0.18.1 both
2491
+ // serve a real PE32+ DLL; the pinned 0.18.2 core resolves its extension
2492
+ // directory to 0.18.1, strace-verified), and INSTALL now runs in a spawned
2493
+ // child process (installDuckDbExtensionOutOfProcess), so even a crashing
2494
+ // installer kills only the child and degrades to `false` here. LOAD of a
2495
+ // present extension file is an ordinary in-process load whose failures
2496
+ // surface as catchable errors.
2497
+ const c = targetConn ?? conn;
2498
+ if (!c) {
2499
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2500
+ }
2501
+ const loaded = await extension_loader_js_1.extensionManager.ensure((sql) => queryAndDrain(c, sql), 'VECTOR', 'VECTOR', opts);
2502
+ if (loaded && useModuleState)
2503
+ vectorExtensionLoaded = true;
2504
+ return loaded;
2505
+ };
2506
+ exports.loadVectorExtension = loadVectorExtension;
2507
+ /**
2508
+ * Create the HNSW vector index on the CodeEmbedding table.
2509
+ *
2510
+ * MUST run via `conn.query()` (here through `queryAndDrain`), NOT through the
2511
+ * prepared `executeQuery`/`conn.prepare()` path: `CALL CREATE_VECTOR_INDEX(...)`
2512
+ * compiles to multiple statements, which LadybugDB cannot prepare — it fails
2513
+ * with "Connection Exception: We do not support prepare multiple statements."
2514
+ * Routing index creation through `executeQuery` (prepared) is exactly what
2515
+ * broke vector-index creation during `analyze` (#2114; the singleton
2516
+ * `executeQuery` was switched to the prepared path in #1655).
2517
+ *
2518
+ * Returns `true` on success (or when the index already exists — idempotent so
2519
+ * incremental re-runs don't spuriously downgrade to exact scan), `false` when
2520
+ * the VECTOR extension is unavailable or the connection is read-only. Any other
2521
+ * failure propagates so the caller can log it.
2522
+ */
2523
+ const createVectorIndex = async () => {
2524
+ if (!conn) {
2525
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2526
+ }
2527
+ // Already built on this connection — skip the round-trip.
2528
+ if (vectorIndexEnsured)
2529
+ return true;
2530
+ if (!(await (0, exports.loadVectorExtension)())) {
2531
+ return false;
2532
+ }
2533
+ try {
2534
+ await queryAndDrain(conn, schema_js_1.CREATE_VECTOR_INDEX_QUERY);
2535
+ vectorIndexEnsured = true;
2536
+ return true;
2537
+ }
2538
+ catch (e) {
2539
+ const msg = e instanceof Error ? e.message : String(e);
2540
+ // Idempotent: a prior analyze already built the HNSW index.
2541
+ if (msg.includes('already exists')) {
2542
+ vectorIndexEnsured = true;
2543
+ return true;
2544
+ }
2545
+ // Read-only DB (e.g. the MCP query pool): writable analyze owns creation.
2546
+ if ((0, exports.isReadOnlyDbError)(e))
2547
+ return false;
2548
+ throw e;
2549
+ }
2550
+ };
2551
+ exports.createVectorIndex = createVectorIndex;
2552
+ /**
2553
+ * The field reads every index-catalog consumer needs, each hedging the
2554
+ * named-record form against the positional one exactly once.
2555
+ *
2556
+ * They exist because the hedge used to be inlined at several call sites
2557
+ * (#2841 review). Everything version-coupled about the row shape now lives in
2558
+ * this one block.
2559
+ */
2560
+ const indexRowTable = (row) => row?.table_name ?? row?.[0];
2561
+ exports.indexRowTable = indexRowTable;
2562
+ const indexRowType = (row) => row?.index_type ?? row?.[2];
2563
+ exports.indexRowType = indexRowType;
2564
+ /**
2565
+ * Read the index catalog on the writable connection, or `undefined` when it
2566
+ * cannot be read.
2567
+ *
2568
+ * `SHOW_INDEXES` is readable WITHOUT any extension loaded and reports
2569
+ * `extension_loaded` per index, so the extension-gated-DML checks below settle
2570
+ * the common "this DB carries no such index" case with one local read and no
2571
+ * error-string sniffing. It runs through the unprepared `conn.query()` path
2572
+ * like every other `CALL` procedure here (#2114).
2573
+ *
2574
+ * `undefined` means "could not prove anything" and every caller must treat it
2575
+ * as fail-closed (assume an index may be present), never as "no indexes". All
2576
+ * readers below honour that (#2841 review H3). To hand ONE read to several
2577
+ * gates, use {@link readIndexCatalogSnapshot} — passing this `undefined` on
2578
+ * cannot be distinguished from passing nothing at all.
2579
+ */
2580
+ const readIndexCatalogRows = async () => {
2581
+ const targetConn = conn;
2582
+ if (!targetConn) {
2583
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2584
+ }
2585
+ try {
2586
+ return (await (0, conn_lock_js_1.withConnLock)(async () => readQueryRows(await targetConn.query('CALL SHOW_INDEXES() RETURN *'))));
2587
+ }
2588
+ catch (err) {
2589
+ logger_js_1.logger.warn({ err }, 'Could not read the LadybugDB index catalog (CALL SHOW_INDEXES()); ' +
2590
+ 'extension-gated DML checks must assume an index may be present.');
2591
+ return undefined;
2592
+ }
2593
+ };
2594
+ exports.readIndexCatalogRows = readIndexCatalogRows;
2595
+ /**
2596
+ * The failed half of an {@link IndexCatalogSnapshot}: the caller DID read the
2597
+ * catalog and could not prove anything.
2598
+ *
2599
+ * It exists because `undefined` was overloaded (#2841 review §5.A). The gates
2600
+ * below took `indexRows?: IndexCatalogRow[]`, so "my read failed" and "I passed
2601
+ * you nothing" were the SAME value, and each gate's `?? (await
2602
+ * readIndexCatalogRows())` silently re-read the catalog — turning the one shared
2603
+ * read the call site documents into three round-trips and three identical
2604
+ * warnings on the failure path, with the two gates free to decide from DIFFERENT
2605
+ * snapshots. A distinct sentinel makes "read, unreadable" a value the parameter
2606
+ * can carry, so a supplied snapshot is never re-read.
2607
+ */
2608
+ exports.INDEX_CATALOG_UNREADABLE = Symbol('cgraph:index-catalog-unreadable');
2609
+ /**
2610
+ * {@link readIndexCatalogRows} in snapshot form — what callers should read once
2611
+ * and pass to EVERY extension-gated-DML gate in a run, so the "one shared
2612
+ * `SHOW_INDEXES` read" invariant holds on the failure branch too (#2841 review
2613
+ * §5.A). The `IndexCatalogRow[] | undefined` spelling stays available for
2614
+ * callers that only want the rows.
2615
+ */
2616
+ const readIndexCatalogSnapshot = async () => (await (0, exports.readIndexCatalogRows)()) ?? exports.INDEX_CATALOG_UNREADABLE;
2617
+ exports.readIndexCatalogSnapshot = readIndexCatalogSnapshot;
2618
+ /**
2619
+ * Resolve a gate's optional `indexRows` argument into the rows it must judge,
2620
+ * reading the catalog AT MOST ONCE and ONLY when the caller supplied nothing.
2621
+ *
2622
+ * The `??` is meaningful again (#2841 review §5.A): `undefined` in can now only
2623
+ * mean "no snapshot supplied", because a caller whose own read failed passes
2624
+ * {@link INDEX_CATALOG_UNREADABLE}, which is truthy and short-circuits it.
2625
+ * `undefined` OUT keeps its documented meaning — "could not prove anything",
2626
+ * which every caller of {@link readIndexCatalogRows} treats as fail-closed.
2627
+ */
2628
+ const resolveGateRows = async (indexRows) => {
2629
+ const snapshot = indexRows ?? (await (0, exports.readIndexCatalogSnapshot)());
2630
+ return snapshot === exports.INDEX_CATALOG_UNREADABLE ? undefined : snapshot;
2631
+ };
2632
+ exports.resolveGateRows = resolveGateRows;
2633
+ /**
2634
+ * Make DML against {@link EMBEDDING_TABLE_NAME} legal on the writable
2635
+ * connection when it can be, and report whether it is.
2636
+ *
2637
+ * LadybugDB refuses EVERY mutation of a table carrying an HNSW index while
2638
+ * the VECTOR extension is not loaded on that connection: `DELETE` fails with
2639
+ * "Trying to delete from an index on table CodeEmbedding but its extension is
2640
+ * not loaded", `CREATE` with the matching "insert into an index" variant,
2641
+ * `DROP TABLE` is refused while the index references it, and `SET` — even on
2642
+ * a NON-indexed property — segfaults the process outright. Probed against
2643
+ * @ladybugdb/core 0.18.2 (the lockfile-pinned version) and 0.18.0 — every
2644
+ * result identical on both (#2623).
2645
+ *
2646
+ * Dropping the index is NOT an available recovery: `CALL DROP_VECTOR_INDEX`
2647
+ * is itself a VECTOR-extension function and resolves to "Catalog exception:
2648
+ * function DROP_VECTOR_INDEX is not defined" in exactly the state it would
2649
+ * need to rescue. Loading the extension is the only in-place repair, which is
2650
+ * why this returns a verdict instead of attempting a fixup.
2651
+ *
2652
+ * `true` = embedding-row DML is safe: either VECTOR is now loaded, or the
2653
+ * table carries no index to trip over. `false` = genuinely blocked (index
2654
+ * present, extension unloadable); the analyze orchestrator answers that by
2655
+ * escalating to the wipe-and-rebuild write plan instead of failing
2656
+ * mid-writeback.
2657
+ *
2658
+ * Cheap by construction: one {@link readIndexCatalogRows} read settles the
2659
+ * common "this repo never built an embedding index" case without touching the
2660
+ * extension machinery at all, so a VECTOR-less machine is not charged a bounded
2661
+ * INSTALL attempt on every incremental analyze. (That read's own mechanics and
2662
+ * fail-closed contract are documented there, not re-explained here — #2841
2663
+ * review §5.H.)
2664
+ *
2665
+ * @param indexRows An {@link IndexCatalogSnapshot} the caller already read, so
2666
+ * one `SHOW_INDEXES` read can settle every gate in a run. FRESHNESS CONTRACT:
2667
+ * the snapshot must have been taken on THIS connection with nothing in between
2668
+ * that creates or drops an index — the gate's verdict is only as current as the
2669
+ * rows it is handed. Pass {@link INDEX_CATALOG_UNREADABLE} (what
2670
+ * {@link readIndexCatalogSnapshot} returns) when your own read failed; that
2671
+ * fails closed here WITHOUT a second read. Omit the argument entirely to have
2672
+ * the gate read the catalog itself.
2673
+ */
2674
+ const ensureEmbeddingRowDmlSafe = async (indexRows) => {
2675
+ // Unconditional precondition (#2841 review §5.B). This check used to run on
2676
+ // every call; adding `indexRows` moved it inside `readIndexCatalogRows`, where
2677
+ // a caller-supplied snapshot skips it — so a closed DB could be answered
2678
+ // `true` where it previously threw. The verdict is only meaningful for the
2679
+ // live writable connection, so assert that before looking at the argument.
2680
+ if (!conn) {
2681
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2682
+ }
2683
+ // Catalog FIRST. The overwhelmingly common case on a repo that never enabled
2684
+ // embeddings is "no index at all", and that is provable with one local read
2685
+ // — no extension needed. Loading first would make every incremental analyze
2686
+ // on a VECTOR-less machine pay a bounded out-of-process INSTALL attempt (the
2687
+ // `auto` policy) plus an "extension unavailable" warning, for a repo that
2688
+ // can never hit this hazard.
2689
+ const rows = await (0, exports.resolveGateRows)(indexRows);
2690
+ // Any non-HASH index on the embedding table gates DML. Keyed on index TYPE,
2691
+ // not name, so an index built under a different name still counts; the
2692
+ // implicit primary-key HASH index is engine-internal and never gates.
2693
+ const indexGatesDml = rows === undefined ||
2694
+ rows.some((row) => {
2695
+ if ((0, exports.indexRowTable)(row) !== schema_js_1.EMBEDDING_TABLE_NAME)
2696
+ return false;
2697
+ return (0, exports.indexRowType)(row) !== 'HASH';
2698
+ });
2699
+ if (!indexGatesDml)
2700
+ return true;
2701
+ return await (0, exports.loadVectorExtension)(undefined, { policy: (0, extension_loader_js_1.resolveAnalyzeInstallPolicy)() });
2702
+ };
2703
+ exports.ensureEmbeddingRowDmlSafe = ensureEmbeddingRowDmlSafe;