gitnexus 1.6.10-rc.21 → 1.6.10-rc.211

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 (870) hide show
  1. package/README.md +212 -22
  2. package/dist/_shared/graph/types.d.ts +27 -8
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +4 -3
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/index.js +1 -1
  7. package/dist/_shared/index.js.map +1 -1
  8. package/dist/_shared/integrations/circuit-breaker.d.ts.map +1 -1
  9. package/dist/_shared/integrations/circuit-breaker.js.map +1 -1
  10. package/dist/_shared/integrations/resilient-fetch.d.ts +19 -1
  11. package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -1
  12. package/dist/_shared/integrations/resilient-fetch.js +23 -10
  13. package/dist/_shared/integrations/resilient-fetch.js.map +1 -1
  14. package/dist/_shared/language-detection.d.ts.map +1 -1
  15. package/dist/_shared/lbug/schema-constants.d.ts +2 -2
  16. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  17. package/dist/_shared/lbug/schema-constants.js +3 -0
  18. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  19. package/dist/_shared/scope-resolution/callable-flow-site.d.ts +110 -0
  20. package/dist/_shared/scope-resolution/callable-flow-site.d.ts.map +1 -0
  21. package/dist/_shared/scope-resolution/callable-flow-site.js +11 -0
  22. package/dist/_shared/scope-resolution/callable-flow-site.js.map +1 -0
  23. package/dist/_shared/scope-resolution/evidence-weights.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +6 -1
  25. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
  26. package/dist/_shared/scope-resolution/finalize-algorithm.js +316 -37
  27. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  28. package/dist/_shared/scope-resolution/parsed-file.d.ts +7 -0
  29. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  30. package/dist/_shared/scope-resolution/reference-site.d.ts +124 -1
  31. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
  32. package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -1
  33. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -1
  34. package/dist/_shared/scope-resolution/registries/lookup-core.js +58 -2
  35. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
  36. package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -1
  37. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  38. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  39. package/dist/_shared/scope-resolution/symbol-definition.d.ts +47 -0
  40. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  41. package/dist/_shared/scope-resolution/types.d.ts +297 -5
  42. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  43. package/dist/cli/ai-context.d.ts +10 -2
  44. package/dist/cli/ai-context.js +128 -61
  45. package/dist/cli/analyze-config.d.ts +1 -1
  46. package/dist/cli/analyze-options.d.ts +131 -0
  47. package/dist/cli/analyze-options.js +1 -0
  48. package/dist/cli/analyze.d.ts +25 -114
  49. package/dist/cli/analyze.js +194 -36
  50. package/dist/cli/cli-message.d.ts +1 -1
  51. package/dist/cli/detect-changes-format.js +19 -2
  52. package/dist/cli/doctor.d.ts +23 -0
  53. package/dist/cli/doctor.js +72 -9
  54. package/dist/cli/editor-targets.js +6 -0
  55. package/dist/cli/eval-server.d.ts +23 -1
  56. package/dist/cli/eval-server.js +128 -9
  57. package/dist/cli/format-symbol.d.ts +15 -0
  58. package/dist/cli/format-symbol.js +17 -0
  59. package/dist/cli/generated-skill.d.ts +17 -0
  60. package/dist/cli/generated-skill.js +1 -0
  61. package/dist/cli/group.js +23 -2
  62. package/dist/cli/help-i18n.js +3 -0
  63. package/dist/cli/i18n/en.d.ts +17 -8
  64. package/dist/cli/i18n/en.js +19 -8
  65. package/dist/cli/i18n/resources.d.ts +26 -8
  66. package/dist/cli/i18n/zh-CN.d.ts +9 -0
  67. package/dist/cli/i18n/zh-CN.js +17 -8
  68. package/dist/cli/index.js +16 -9
  69. package/dist/cli/lazy-action.d.ts +7 -0
  70. package/dist/cli/lazy-action.js +27 -0
  71. package/dist/cli/mcp.js +6 -3
  72. package/dist/cli/resolve-invocation.d.ts +7 -3
  73. package/dist/cli/resolve-invocation.js +7 -3
  74. package/dist/cli/setup.d.ts +8 -0
  75. package/dist/cli/setup.js +30 -7
  76. package/dist/cli/skill-gen.d.ts +1 -6
  77. package/dist/cli/skill-gen.js +88 -18
  78. package/dist/cli/standard-skills.d.ts +76 -0
  79. package/dist/cli/standard-skills.js +45 -0
  80. package/dist/cli/status.d.ts +4 -1
  81. package/dist/cli/status.js +57 -4
  82. package/dist/cli/tool.d.ts +1 -0
  83. package/dist/cli/tool.js +75 -7
  84. package/dist/cli/uninstall.js +7 -2
  85. package/dist/cli/wiki.d.ts +1 -0
  86. package/dist/cli/wiki.js +59 -10
  87. package/dist/config/ignore-service.d.ts +2 -0
  88. package/dist/config/ignore-service.js +31 -4
  89. package/dist/core/analysis-features.d.ts +21 -0
  90. package/dist/core/analysis-features.js +59 -0
  91. package/dist/core/analyzer-identity.d.ts +154 -0
  92. package/dist/core/analyzer-identity.js +2158 -0
  93. package/dist/core/augmentation/engine.js +95 -76
  94. package/dist/core/embedding-checkpoint.d.ts +143 -0
  95. package/dist/core/embedding-checkpoint.js +221 -0
  96. package/dist/core/embedding-count.d.ts +61 -0
  97. package/dist/core/embedding-count.js +69 -0
  98. package/dist/core/embeddings/ast-utils.js +9 -1
  99. package/dist/core/embeddings/embedder.d.ts +3 -2
  100. package/dist/core/embeddings/embedder.js +8 -5
  101. package/dist/core/embeddings/embedding-identity.d.ts +11 -0
  102. package/dist/core/embeddings/embedding-identity.js +24 -0
  103. package/dist/core/embeddings/embedding-pipeline.d.ts +24 -1
  104. package/dist/core/embeddings/embedding-pipeline.js +341 -19
  105. package/dist/core/embeddings/http-client.d.ts +10 -2
  106. package/dist/core/embeddings/http-client.js +282 -47
  107. package/dist/core/embeddings/node-module-compat.d.ts +4 -2
  108. package/dist/core/embeddings/node-module-compat.js +4 -2
  109. package/dist/core/embeddings/onnxruntime-common-resolver.js +5 -5
  110. package/dist/core/embeddings/onnxruntime-node-resolver.js +2 -2
  111. package/dist/core/embeddings/runtime-install.js +1 -1
  112. package/dist/core/git-staleness.js +1 -1
  113. package/dist/core/graph/graph.js +3 -0
  114. package/dist/core/graph/import-cycles.d.ts +194 -3
  115. package/dist/core/graph/import-cycles.js +522 -73
  116. package/dist/core/graph/types.d.ts +18 -0
  117. package/dist/core/group/bridge-db.js +5 -27
  118. package/dist/core/group/cross-impact.d.ts +21 -0
  119. package/dist/core/group/cross-impact.js +128 -15
  120. package/dist/core/group/cross-trace.js +27 -16
  121. package/dist/core/group/extractors/http-patterns/java.js +91 -14
  122. package/dist/core/group/extractors/http-patterns/node.js +19 -0
  123. package/dist/core/group/extractors/http-patterns/types.d.ts +8 -0
  124. package/dist/core/group/extractors/http-route-extractor.d.ts +1 -0
  125. package/dist/core/group/extractors/http-route-extractor.js +122 -48
  126. package/dist/core/group/extractors/include-extractor.js +20 -2
  127. package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
  128. package/dist/core/group/extractors/manifest-extractor.js +7 -7
  129. package/dist/core/group/service.js +10 -1
  130. package/dist/core/group/storage.js +2 -33
  131. package/dist/core/group/types.d.ts +18 -0
  132. package/dist/core/incremental/subgraph-extract.d.ts +9 -6
  133. package/dist/core/incremental/subgraph-extract.js +33 -10
  134. package/dist/core/index-freshness.d.ts +80 -0
  135. package/dist/core/index-freshness.js +120 -0
  136. package/dist/core/ingestion/call-processor.d.ts +13 -1
  137. package/dist/core/ingestion/call-processor.js +110 -3
  138. package/dist/core/ingestion/cfg/callee-cell-format.d.ts +42 -0
  139. package/dist/core/ingestion/cfg/callee-cell-format.js +42 -0
  140. package/dist/core/ingestion/cfg/emit.d.ts +8 -23
  141. package/dist/core/ingestion/cfg/emit.js +9 -23
  142. package/dist/core/ingestion/class-extractors/configs/go.js +10 -5
  143. package/dist/core/ingestion/class-extractors/configs/jvm.js +29 -0
  144. package/dist/core/ingestion/cluster-enricher.js +8 -4
  145. package/dist/core/ingestion/cobol/cobol-preprocessor.js +1 -1
  146. package/dist/core/ingestion/community-processor.d.ts +21 -3
  147. package/dist/core/ingestion/community-processor.js +88 -83
  148. package/dist/core/ingestion/di-extractors/index.d.ts +22 -34
  149. package/dist/core/ingestion/di-extractors/index.js +17 -18
  150. package/dist/core/ingestion/di-extractors/spring.d.ts +27 -5
  151. package/dist/core/ingestion/di-extractors/spring.js +87 -3
  152. package/dist/core/ingestion/di-extractors/types.d.ts +60 -0
  153. package/dist/core/ingestion/di-extractors/types.js +15 -0
  154. package/dist/core/ingestion/emit-references.js +4 -0
  155. package/dist/core/ingestion/filesystem-walker.js +16 -12
  156. package/dist/core/ingestion/finalize-orchestrator.js +1 -0
  157. package/dist/core/ingestion/frameworks/spring/analysis-features.d.ts +9 -0
  158. package/dist/core/ingestion/frameworks/spring/analysis-features.js +41 -0
  159. package/dist/core/ingestion/frameworks/spring/annotation-arguments.d.ts +13 -0
  160. package/dist/core/ingestion/frameworks/spring/annotation-arguments.js +227 -0
  161. package/dist/core/ingestion/frameworks/spring/aop-candidates.d.ts +15 -0
  162. package/dist/core/ingestion/frameworks/spring/aop-candidates.js +85 -0
  163. package/dist/core/ingestion/frameworks/spring/aop.d.ts +118 -0
  164. package/dist/core/ingestion/frameworks/spring/aop.js +476 -0
  165. package/dist/core/ingestion/frameworks/spring/auto-configuration.d.ts +8 -0
  166. package/dist/core/ingestion/frameworks/spring/auto-configuration.js +16 -0
  167. package/dist/core/ingestion/frameworks/spring/bean-candidates.d.ts +30 -0
  168. package/dist/core/ingestion/frameworks/spring/bean-candidates.js +182 -0
  169. package/dist/core/ingestion/frameworks/spring/bean-catalog.d.ts +12 -0
  170. package/dist/core/ingestion/frameworks/spring/bean-catalog.js +27 -0
  171. package/dist/core/ingestion/frameworks/spring/bean-factories.d.ts +33 -0
  172. package/dist/core/ingestion/frameworks/spring/bean-factories.js +65 -0
  173. package/dist/core/ingestion/frameworks/spring/conditionals.d.ts +25 -0
  174. package/dist/core/ingestion/frameworks/spring/conditionals.js +319 -0
  175. package/dist/core/ingestion/frameworks/spring/config-bindings.d.ts +24 -0
  176. package/dist/core/ingestion/frameworks/spring/config-bindings.js +115 -0
  177. package/dist/core/ingestion/frameworks/spring/di-metadata.d.ts +48 -0
  178. package/dist/core/ingestion/frameworks/spring/di-metadata.js +379 -0
  179. package/dist/core/ingestion/frameworks/spring/non-http-handlers.d.ts +28 -0
  180. package/dist/core/ingestion/frameworks/spring/non-http-handlers.js +152 -0
  181. package/dist/core/ingestion/frameworks/spring/resource-injection.d.ts +5 -0
  182. package/dist/core/ingestion/frameworks/spring/resource-injection.js +91 -0
  183. package/dist/core/ingestion/import-resolvers/configs/csharp.js +5 -1
  184. package/dist/core/ingestion/import-resolvers/configs/swift.js +13 -0
  185. package/dist/core/ingestion/import-resolvers/csharp.d.ts +9 -1
  186. package/dist/core/ingestion/import-resolvers/csharp.js +234 -27
  187. package/dist/core/ingestion/import-resolvers/go.d.ts +11 -1
  188. package/dist/core/ingestion/import-resolvers/go.js +44 -17
  189. package/dist/core/ingestion/import-resolvers/jvm.d.ts +2 -2
  190. package/dist/core/ingestion/import-resolvers/node-workspace-packages.d.ts +90 -0
  191. package/dist/core/ingestion/import-resolvers/node-workspace-packages.js +428 -0
  192. package/dist/core/ingestion/import-resolvers/package-dir-index.d.ts +124 -0
  193. package/dist/core/ingestion/import-resolvers/package-dir-index.js +223 -0
  194. package/dist/core/ingestion/import-resolvers/pass-cache.d.ts +50 -0
  195. package/dist/core/ingestion/import-resolvers/pass-cache.js +60 -0
  196. package/dist/core/ingestion/import-resolvers/per-file-set.d.ts +71 -0
  197. package/dist/core/ingestion/import-resolvers/per-file-set.js +11 -0
  198. package/dist/core/ingestion/import-resolvers/php.d.ts +1 -1
  199. package/dist/core/ingestion/import-resolvers/php.js +27 -8
  200. package/dist/core/ingestion/import-resolvers/python-file-index.d.ts +201 -0
  201. package/dist/core/ingestion/import-resolvers/python-file-index.js +302 -0
  202. package/dist/core/ingestion/import-resolvers/python.js +25 -6
  203. package/dist/core/ingestion/import-resolvers/ruby.d.ts +1 -1
  204. package/dist/core/ingestion/import-resolvers/rust.d.ts +2 -2
  205. package/dist/core/ingestion/import-resolvers/standard.d.ts +1 -1
  206. package/dist/core/ingestion/import-resolvers/types.d.ts +1 -12
  207. package/dist/core/ingestion/import-resolvers/utils.d.ts +65 -6
  208. package/dist/core/ingestion/import-resolvers/utils.js +215 -36
  209. package/dist/core/ingestion/import-resolvers/workspace-file-index.d.ts +72 -0
  210. package/dist/core/ingestion/import-resolvers/workspace-file-index.js +58 -0
  211. package/dist/core/ingestion/language-config.d.ts +20 -1
  212. package/dist/core/ingestion/language-config.js +18 -23
  213. package/dist/core/ingestion/language-provider.d.ts +133 -7
  214. package/dist/core/ingestion/languages/c/captures.js +63 -0
  215. package/dist/core/ingestion/languages/c/import-target.js +12 -16
  216. package/dist/core/ingestion/languages/c/query.js +17 -0
  217. package/dist/core/ingestion/languages/c/scope-resolver.js +26 -19
  218. package/dist/core/ingestion/languages/c/static-linkage.js +12 -16
  219. package/dist/core/ingestion/languages/c-cpp.js +13 -0
  220. package/dist/core/ingestion/languages/cobol/captures.js +93 -0
  221. package/dist/core/ingestion/languages/cobol/scope-resolver.js +34 -20
  222. package/dist/core/ingestion/languages/cobol.js +13 -0
  223. package/dist/core/ingestion/languages/cpp/adl.d.ts +2 -2
  224. package/dist/core/ingestion/languages/cpp/adl.js +67 -44
  225. package/dist/core/ingestion/languages/cpp/captures.js +222 -3
  226. package/dist/core/ingestion/languages/cpp/file-local-linkage.js +11 -15
  227. package/dist/core/ingestion/languages/cpp/inline-namespaces.d.ts +28 -8
  228. package/dist/core/ingestion/languages/cpp/inline-namespaces.js +242 -68
  229. package/dist/core/ingestion/languages/cpp/interpret.d.ts +2 -0
  230. package/dist/core/ingestion/languages/cpp/interpret.js +126 -6
  231. package/dist/core/ingestion/languages/cpp/query.js +106 -0
  232. package/dist/core/ingestion/languages/cpp/scope-resolver.js +62 -20
  233. package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +2 -15
  234. package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +18 -10
  235. package/dist/core/ingestion/languages/csharp/captures.js +44 -0
  236. package/dist/core/ingestion/languages/csharp/import-target.js +46 -72
  237. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +5 -1
  238. package/dist/core/ingestion/languages/csharp/query.js +25 -5
  239. package/dist/core/ingestion/languages/csharp/scope-resolver.js +75 -2
  240. package/dist/core/ingestion/languages/dart/captures.d.ts +10 -0
  241. package/dist/core/ingestion/languages/dart/captures.js +686 -16
  242. package/dist/core/ingestion/languages/dart/extension-type-preprocess.d.ts +7 -0
  243. package/dist/core/ingestion/languages/dart/extension-type-preprocess.js +30 -0
  244. package/dist/core/ingestion/languages/dart/import-target.js +32 -9
  245. package/dist/core/ingestion/languages/dart/index.d.ts +1 -1
  246. package/dist/core/ingestion/languages/dart/index.js +1 -1
  247. package/dist/core/ingestion/languages/dart/query.js +98 -1
  248. package/dist/core/ingestion/languages/dart/scope-resolver.js +14 -3
  249. package/dist/core/ingestion/languages/dart/simple-hooks.js +9 -0
  250. package/dist/core/ingestion/languages/dart.js +8 -1
  251. package/dist/core/ingestion/languages/go/captures.js +142 -3
  252. package/dist/core/ingestion/languages/go/generic-type-parameters.d.ts +31 -0
  253. package/dist/core/ingestion/languages/go/generic-type-parameters.js +140 -0
  254. package/dist/core/ingestion/languages/go/import-target.d.ts +3 -10
  255. package/dist/core/ingestion/languages/go/import-target.js +34 -30
  256. package/dist/core/ingestion/languages/go/index.d.ts +1 -1
  257. package/dist/core/ingestion/languages/go/interface-impls.d.ts +9 -1
  258. package/dist/core/ingestion/languages/go/interface-impls.js +693 -87
  259. package/dist/core/ingestion/languages/go/interpret.d.ts +2 -0
  260. package/dist/core/ingestion/languages/go/interpret.js +22 -8
  261. package/dist/core/ingestion/languages/go/method-owners.js +31 -12
  262. package/dist/core/ingestion/languages/go/package-clause.d.ts +46 -0
  263. package/dist/core/ingestion/languages/go/package-clause.js +75 -0
  264. package/dist/core/ingestion/languages/go/package-siblings.js +7 -11
  265. package/dist/core/ingestion/languages/go/query.js +56 -3
  266. package/dist/core/ingestion/languages/go/scope-resolver.js +39 -0
  267. package/dist/core/ingestion/languages/go.js +9 -0
  268. package/dist/core/ingestion/languages/java/analysis-features.d.ts +7 -0
  269. package/dist/core/ingestion/languages/java/analysis-features.js +25 -0
  270. package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +38 -0
  271. package/dist/core/ingestion/languages/java/capture-side-channel.js +124 -0
  272. package/dist/core/ingestion/languages/java/captures.d.ts +4 -3
  273. package/dist/core/ingestion/languages/java/captures.js +321 -49
  274. package/dist/core/ingestion/languages/java/import-target.d.ts +49 -9
  275. package/dist/core/ingestion/languages/java/import-target.js +68 -88
  276. package/dist/core/ingestion/languages/java/interpret.js +3 -2
  277. package/dist/core/ingestion/languages/java/module-resolution.d.ts +62 -0
  278. package/dist/core/ingestion/languages/java/module-resolution.js +142 -0
  279. package/dist/core/ingestion/languages/java/package-facts.d.ts +5 -0
  280. package/dist/core/ingestion/languages/java/package-facts.js +9 -0
  281. package/dist/core/ingestion/languages/java/package-siblings.d.ts +3 -16
  282. package/dist/core/ingestion/languages/java/package-siblings.js +8 -147
  283. package/dist/core/ingestion/languages/java/query.js +39 -3
  284. package/dist/core/ingestion/languages/java/record-components.d.ts +14 -0
  285. package/dist/core/ingestion/languages/java/record-components.js +185 -0
  286. package/dist/core/ingestion/languages/java/scope-resolver.js +35 -2
  287. package/dist/core/ingestion/languages/java/spring-aop.d.ts +12 -0
  288. package/dist/core/ingestion/languages/java/spring-aop.js +49 -0
  289. package/dist/core/ingestion/languages/java/spring-bean-metadata.d.ts +2 -0
  290. package/dist/core/ingestion/languages/java/spring-bean-metadata.js +8 -0
  291. package/dist/core/ingestion/languages/java/spring-conditionals.d.ts +11 -0
  292. package/dist/core/ingestion/languages/java/spring-conditionals.js +48 -0
  293. package/dist/core/ingestion/languages/java/spring-config-bindings.d.ts +23 -0
  294. package/dist/core/ingestion/languages/java/spring-config-bindings.js +215 -0
  295. package/dist/core/ingestion/languages/java/spring-di.d.ts +20 -0
  296. package/dist/core/ingestion/languages/java/spring-di.js +160 -0
  297. package/dist/core/ingestion/languages/java/spring-non-http-handlers.d.ts +7 -0
  298. package/dist/core/ingestion/languages/java/spring-non-http-handlers.js +32 -0
  299. package/dist/core/ingestion/languages/java.js +76 -3
  300. package/dist/core/ingestion/languages/javascript/captures.d.ts +6 -1
  301. package/dist/core/ingestion/languages/javascript/captures.js +278 -4
  302. package/dist/core/ingestion/languages/javascript/import-target.d.ts +17 -21
  303. package/dist/core/ingestion/languages/javascript/import-target.js +24 -41
  304. package/dist/core/ingestion/languages/javascript/index.d.ts +54 -5
  305. package/dist/core/ingestion/languages/javascript/index.js +54 -5
  306. package/dist/core/ingestion/languages/javascript/query.d.ts +1 -0
  307. package/dist/core/ingestion/languages/javascript/query.js +232 -5
  308. package/dist/core/ingestion/languages/javascript/scope-resolver.js +12 -0
  309. package/dist/core/ingestion/languages/javascript/simple-hooks.js +2 -1
  310. package/dist/core/ingestion/languages/jvm/package-facts.d.ts +35 -0
  311. package/dist/core/ingestion/languages/jvm/package-facts.js +40 -0
  312. package/dist/core/ingestion/languages/jvm/package-siblings.d.ts +14 -0
  313. package/dist/core/ingestion/languages/jvm/package-siblings.js +197 -0
  314. package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +40 -3
  315. package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +111 -8
  316. package/dist/core/ingestion/languages/kotlin/captures.js +112 -0
  317. package/dist/core/ingestion/languages/kotlin/import-target.d.ts +8 -1
  318. package/dist/core/ingestion/languages/kotlin/import-target.js +20 -123
  319. package/dist/core/ingestion/languages/kotlin/index.d.ts +1 -0
  320. package/dist/core/ingestion/languages/kotlin/index.js +1 -0
  321. package/dist/core/ingestion/languages/kotlin/module-resolution.d.ts +22 -0
  322. package/dist/core/ingestion/languages/kotlin/module-resolution.js +108 -0
  323. package/dist/core/ingestion/languages/kotlin/package-facts.d.ts +5 -0
  324. package/dist/core/ingestion/languages/kotlin/package-facts.js +9 -0
  325. package/dist/core/ingestion/languages/kotlin/package-siblings.d.ts +4 -0
  326. package/dist/core/ingestion/languages/kotlin/package-siblings.js +8 -0
  327. package/dist/core/ingestion/languages/kotlin/query.js +47 -2
  328. package/dist/core/ingestion/languages/kotlin/scope-resolver.js +36 -3
  329. package/dist/core/ingestion/languages/kotlin/spring-aop.d.ts +12 -0
  330. package/dist/core/ingestion/languages/kotlin/spring-aop.js +64 -0
  331. package/dist/core/ingestion/languages/kotlin/spring-bean-metadata.d.ts +2 -0
  332. package/dist/core/ingestion/languages/kotlin/spring-bean-metadata.js +8 -0
  333. package/dist/core/ingestion/languages/kotlin/spring-conditionals.d.ts +12 -0
  334. package/dist/core/ingestion/languages/kotlin/spring-conditionals.js +49 -0
  335. package/dist/core/ingestion/languages/kotlin/spring-di.d.ts +20 -0
  336. package/dist/core/ingestion/languages/kotlin/spring-di.js +299 -0
  337. package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.d.ts +10 -0
  338. package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.js +41 -0
  339. package/dist/core/ingestion/languages/kotlin.js +5 -6
  340. package/dist/core/ingestion/languages/php/captures.d.ts +5 -0
  341. package/dist/core/ingestion/languages/php/captures.js +359 -51
  342. package/dist/core/ingestion/languages/php/import-decomposer.js +12 -0
  343. package/dist/core/ingestion/languages/php/import-target.d.ts +2 -1
  344. package/dist/core/ingestion/languages/php/import-target.js +229 -7
  345. package/dist/core/ingestion/languages/php/interpret.js +10 -0
  346. package/dist/core/ingestion/languages/php/query.js +31 -0
  347. package/dist/core/ingestion/languages/php/scope-resolver.js +1 -1
  348. package/dist/core/ingestion/languages/python/arity-metadata.d.ts +1 -0
  349. package/dist/core/ingestion/languages/python/arity-metadata.js +1 -0
  350. package/dist/core/ingestion/languages/python/captures.d.ts +3 -4
  351. package/dist/core/ingestion/languages/python/captures.js +53 -5
  352. package/dist/core/ingestion/languages/python/import-decomposer.js +22 -1
  353. package/dist/core/ingestion/languages/python/import-target.d.ts +42 -1
  354. package/dist/core/ingestion/languages/python/import-target.js +277 -91
  355. package/dist/core/ingestion/languages/python/index.d.ts +1 -1
  356. package/dist/core/ingestion/languages/python/index.js +1 -1
  357. package/dist/core/ingestion/languages/python/interpret.d.ts +52 -0
  358. package/dist/core/ingestion/languages/python/interpret.js +249 -3
  359. package/dist/core/ingestion/languages/python/query.js +9 -0
  360. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +17 -2
  361. package/dist/core/ingestion/languages/python/receiver-binding.js +192 -2
  362. package/dist/core/ingestion/languages/python/scope-resolver.js +27 -4
  363. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +5 -5
  364. package/dist/core/ingestion/languages/python/simple-hooks.js +9 -5
  365. package/dist/core/ingestion/languages/ruby/captures.js +200 -0
  366. package/dist/core/ingestion/languages/ruby/import-target.js +7 -5
  367. package/dist/core/ingestion/languages/ruby/query.js +93 -0
  368. package/dist/core/ingestion/languages/ruby/scope-resolver.js +2 -0
  369. package/dist/core/ingestion/languages/ruby/simple-hooks.d.ts +1 -1
  370. package/dist/core/ingestion/languages/ruby/simple-hooks.js +22 -1
  371. package/dist/core/ingestion/languages/rust/captures.js +52 -1
  372. package/dist/core/ingestion/languages/rust/interpret.js +18 -0
  373. package/dist/core/ingestion/languages/rust/method-owners.js +11 -1
  374. package/dist/core/ingestion/languages/rust/module-path.d.ts +126 -0
  375. package/dist/core/ingestion/languages/rust/module-path.js +214 -0
  376. package/dist/core/ingestion/languages/rust/qualified-call.d.ts +41 -0
  377. package/dist/core/ingestion/languages/rust/qualified-call.js +481 -0
  378. package/dist/core/ingestion/languages/rust/query.js +48 -6
  379. package/dist/core/ingestion/languages/rust/range-binding.d.ts +1 -11
  380. package/dist/core/ingestion/languages/rust/range-binding.js +149 -63
  381. package/dist/core/ingestion/languages/rust/scope-resolver.js +21 -2
  382. package/dist/core/ingestion/languages/rust.js +17 -0
  383. package/dist/core/ingestion/languages/swift/captures.js +99 -3
  384. package/dist/core/ingestion/languages/swift/conditional-directive-preprocess.d.ts +48 -0
  385. package/dist/core/ingestion/languages/swift/conditional-directive-preprocess.js +253 -0
  386. package/dist/core/ingestion/languages/swift/import-target.js +4 -9
  387. package/dist/core/ingestion/languages/swift/query.js +17 -0
  388. package/dist/core/ingestion/languages/swift/receiver-binding.d.ts +8 -0
  389. package/dist/core/ingestion/languages/swift/receiver-binding.js +6 -2
  390. package/dist/core/ingestion/languages/swift.js +2 -0
  391. package/dist/core/ingestion/languages/typescript/captures.d.ts +90 -1
  392. package/dist/core/ingestion/languages/typescript/captures.js +288 -5
  393. package/dist/core/ingestion/languages/typescript/cjs-export-assignment.d.ts +134 -0
  394. package/dist/core/ingestion/languages/typescript/cjs-export-assignment.js +523 -0
  395. package/dist/core/ingestion/languages/typescript/cjs-module-exports.d.ts +54 -0
  396. package/dist/core/ingestion/languages/typescript/cjs-module-exports.js +191 -0
  397. package/dist/core/ingestion/languages/typescript/file-candidates.d.ts +29 -0
  398. package/dist/core/ingestion/languages/typescript/file-candidates.js +70 -0
  399. package/dist/core/ingestion/languages/typescript/import-decomposer.d.ts +28 -1
  400. package/dist/core/ingestion/languages/typescript/import-decomposer.js +125 -10
  401. package/dist/core/ingestion/languages/typescript/import-target.d.ts +25 -37
  402. package/dist/core/ingestion/languages/typescript/import-target.js +24 -26
  403. package/dist/core/ingestion/languages/typescript/interpret.d.ts +21 -1
  404. package/dist/core/ingestion/languages/typescript/interpret.js +45 -5
  405. package/dist/core/ingestion/languages/typescript/module-resolution.d.ts +48 -0
  406. package/dist/core/ingestion/languages/typescript/module-resolution.js +171 -0
  407. package/dist/core/ingestion/languages/typescript/nuxt-auto-imports.js +2 -1
  408. package/dist/core/ingestion/languages/typescript/query.d.ts +1 -0
  409. package/dist/core/ingestion/languages/typescript/query.js +340 -14
  410. package/dist/core/ingestion/languages/typescript/receiver-binding.d.ts +17 -0
  411. package/dist/core/ingestion/languages/typescript/receiver-binding.js +27 -0
  412. package/dist/core/ingestion/languages/typescript/scope-resolver.js +27 -40
  413. package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +0 -10
  414. package/dist/core/ingestion/languages/typescript/simple-hooks.js +22 -22
  415. package/dist/core/ingestion/languages/typescript/tsconfig.d.ts +59 -0
  416. package/dist/core/ingestion/languages/typescript/tsconfig.js +291 -0
  417. package/dist/core/ingestion/languages/typescript.js +87 -1
  418. package/dist/core/ingestion/languages/vue/import-target.d.ts +6 -22
  419. package/dist/core/ingestion/languages/vue/import-target.js +11 -47
  420. package/dist/core/ingestion/languages/vue/scope-resolver.js +4 -2
  421. package/dist/core/ingestion/local-symbol-pruner.js +11 -11
  422. package/dist/core/ingestion/method-extractors/configs/csharp.js +1 -0
  423. package/dist/core/ingestion/method-extractors/generic.js +1 -0
  424. package/dist/core/ingestion/method-types.d.ts +16 -0
  425. package/dist/core/ingestion/parsing-processor.js +32 -1
  426. package/dist/core/ingestion/pipeline-phases/di.d.ts +8 -33
  427. package/dist/core/ingestion/pipeline-phases/di.js +275 -157
  428. package/dist/core/ingestion/pipeline-phases/index.d.ts +3 -0
  429. package/dist/core/ingestion/pipeline-phases/index.js +3 -0
  430. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +15 -0
  431. package/dist/core/ingestion/pipeline-phases/parse-impl.js +158 -3
  432. package/dist/core/ingestion/pipeline-phases/parse.js +5 -0
  433. package/dist/core/ingestion/pipeline-phases/processes.d.ts +11 -2
  434. package/dist/core/ingestion/pipeline-phases/processes.js +102 -7
  435. package/dist/core/ingestion/pipeline-phases/routes.js +56 -10
  436. package/dist/core/ingestion/pipeline-phases/spring-aop.d.ts +34 -0
  437. package/dist/core/ingestion/pipeline-phases/spring-aop.js +439 -0
  438. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +34 -0
  439. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +255 -0
  440. package/dist/core/ingestion/pipeline-phases/spring-config.d.ts +37 -0
  441. package/dist/core/ingestion/pipeline-phases/spring-config.js +433 -0
  442. package/dist/core/ingestion/pipeline-phases/types.d.ts +7 -0
  443. package/dist/core/ingestion/pipeline.d.ts +27 -3
  444. package/dist/core/ingestion/pipeline.js +62 -12
  445. package/dist/core/ingestion/process-processor.d.ts +109 -2
  446. package/dist/core/ingestion/process-processor.js +464 -52
  447. package/dist/core/ingestion/resolve-references.d.ts +6 -1
  448. package/dist/core/ingestion/resolve-references.js +78 -6
  449. package/dist/core/ingestion/route-extractors/data-route-table.d.ts +25 -0
  450. package/dist/core/ingestion/route-extractors/data-route-table.js +864 -0
  451. package/dist/core/ingestion/route-extractors/dispatch-guard.d.ts +96 -0
  452. package/dist/core/ingestion/route-extractors/dispatch-guard.js +1184 -0
  453. package/dist/core/ingestion/route-extractors/spring-shared.d.ts +11 -0
  454. package/dist/core/ingestion/route-extractors/spring-shared.js +89 -0
  455. package/dist/core/ingestion/route-extractors/spring.js +109 -24
  456. package/dist/core/ingestion/scope-extractor-bridge.js +9 -1
  457. package/dist/core/ingestion/scope-extractor.d.ts +22 -7
  458. package/dist/core/ingestion/scope-extractor.js +617 -59
  459. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +381 -22
  460. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +16 -5
  461. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +7 -7
  462. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +8 -7
  463. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +15 -1
  464. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +53 -0
  465. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +20 -15
  466. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +275 -41
  467. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +123 -0
  468. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +171 -17
  469. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +56 -1
  470. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +130 -32
  471. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +30 -1
  472. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +46 -3
  473. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.d.ts +64 -0
  474. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.js +1233 -0
  475. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +107 -5
  476. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +590 -33
  477. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +19 -4
  478. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +160 -4
  479. package/dist/core/ingestion/scope-resolution/passes/imported-value-refs.d.ts +34 -0
  480. package/dist/core/ingestion/scope-resolution/passes/imported-value-refs.js +62 -0
  481. package/dist/core/ingestion/scope-resolution/passes/property-dispatch.d.ts +64 -0
  482. package/dist/core/ingestion/scope-resolution/passes/property-dispatch.js +118 -0
  483. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +109 -8
  484. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +931 -92
  485. package/dist/core/ingestion/scope-resolution/passes/return-shape-members.d.ts +58 -0
  486. package/dist/core/ingestion/scope-resolution/passes/return-shape-members.js +458 -0
  487. package/dist/core/ingestion/scope-resolution/passes/unique-name-properties.d.ts +180 -0
  488. package/dist/core/ingestion/scope-resolution/passes/unique-name-properties.js +418 -0
  489. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +34 -0
  490. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +82 -38
  491. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +61 -1
  492. package/dist/core/ingestion/scope-resolution/pipeline/run.js +384 -48
  493. package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +112 -2
  494. package/dist/core/ingestion/scope-resolution/resolution-outcome.js +38 -1
  495. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +25 -3
  496. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +36 -16
  497. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +210 -9
  498. package/dist/core/ingestion/scope-resolution/scope/walkers.js +968 -56
  499. package/dist/core/ingestion/scope-resolution/summary-maps.d.ts +28 -0
  500. package/dist/core/ingestion/scope-resolution/summary-maps.js +50 -0
  501. package/dist/core/ingestion/scope-resolution/undecided-satisfaction.d.ts +55 -0
  502. package/dist/core/ingestion/scope-resolution/undecided-satisfaction.js +37 -0
  503. package/dist/core/ingestion/scope-resolution/unresolved-receivers.d.ts +109 -0
  504. package/dist/core/ingestion/scope-resolution/unresolved-receivers.js +204 -0
  505. package/dist/core/ingestion/scope-resolution/utils/definition-id.d.ts +13 -0
  506. package/dist/core/ingestion/scope-resolution/utils/definition-id.js +19 -0
  507. package/dist/core/ingestion/scope-resolution/utils/generic-instantiation.d.ts +171 -0
  508. package/dist/core/ingestion/scope-resolution/utils/generic-instantiation.js +218 -0
  509. package/dist/core/ingestion/scope-resolution/workspace-index-types.d.ts +34 -0
  510. package/dist/core/ingestion/scope-resolution/workspace-index-types.js +15 -0
  511. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +7 -20
  512. package/dist/core/ingestion/taint/typescript-model.js +34 -2
  513. package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
  514. package/dist/core/ingestion/tree-sitter-queries.js +688 -5
  515. package/dist/core/ingestion/type-env.js +11 -7
  516. package/dist/core/ingestion/type-extractors/jvm.js +17 -2
  517. package/dist/core/ingestion/type-extractors/shared.d.ts +17 -0
  518. package/dist/core/ingestion/type-extractors/shared.js +16 -0
  519. package/dist/core/ingestion/type-extractors/types.d.ts +12 -0
  520. package/dist/core/ingestion/type-extractors/typescript.d.ts +20 -0
  521. package/dist/core/ingestion/type-extractors/typescript.js +26 -0
  522. package/dist/core/ingestion/utils/ast-helpers.d.ts +311 -14
  523. package/dist/core/ingestion/utils/ast-helpers.js +745 -48
  524. package/dist/core/ingestion/utils/call-analysis.d.ts +27 -22
  525. package/dist/core/ingestion/utils/call-analysis.js +202 -7
  526. package/dist/core/ingestion/utils/callable-flow-captures.d.ts +153 -0
  527. package/dist/core/ingestion/utils/callable-flow-captures.js +928 -0
  528. package/dist/core/ingestion/utils/callable-labels.d.ts +25 -0
  529. package/dist/core/ingestion/utils/callable-labels.js +31 -0
  530. package/dist/core/ingestion/utils/effective-ram.d.ts +40 -0
  531. package/dist/core/ingestion/utils/effective-ram.js +58 -0
  532. package/dist/core/ingestion/utils/line-base.d.ts +14 -0
  533. package/dist/core/ingestion/utils/line-base.js +14 -0
  534. package/dist/core/ingestion/utils/method-props.d.ts +27 -4
  535. package/dist/core/ingestion/utils/method-props.js +29 -4
  536. package/dist/core/ingestion/utils/receiver-chain-captures.d.ts +31 -0
  537. package/dist/core/ingestion/utils/receiver-chain-captures.js +57 -0
  538. package/dist/core/ingestion/utils/receiver-chain-codec.d.ts +89 -0
  539. package/dist/core/ingestion/utils/receiver-chain-codec.js +182 -0
  540. package/dist/core/ingestion/utils/scope-tree-walk.d.ts +22 -0
  541. package/dist/core/ingestion/utils/scope-tree-walk.js +33 -0
  542. package/dist/core/ingestion/utils/template-arguments.d.ts +96 -0
  543. package/dist/core/ingestion/utils/template-arguments.js +196 -0
  544. package/dist/core/ingestion/utils/type-parameters.d.ts +49 -0
  545. package/dist/core/ingestion/utils/type-parameters.js +206 -0
  546. package/dist/core/ingestion/workers/callable-id.d.ts +72 -0
  547. package/dist/core/ingestion/workers/callable-id.js +116 -0
  548. package/dist/core/ingestion/workers/parse-worker.d.ts +14 -1
  549. package/dist/core/ingestion/workers/parse-worker.js +409 -39
  550. package/dist/core/ingestion/workers/worker-pool.d.ts +34 -27
  551. package/dist/core/ingestion/workers/worker-pool.js +133 -25
  552. package/dist/core/lbug/csv-generator.d.ts +27 -0
  553. package/dist/core/lbug/csv-generator.js +80 -17
  554. package/dist/core/lbug/extension-load-error.d.ts +2 -2
  555. package/dist/core/lbug/extension-load-error.js +44 -27
  556. package/dist/core/lbug/extension-loader.d.ts +29 -0
  557. package/dist/core/lbug/extension-loader.js +32 -7
  558. package/dist/core/lbug/graph-emit-sink.d.ts +264 -0
  559. package/dist/core/lbug/graph-emit-sink.js +581 -0
  560. package/dist/core/lbug/lbug-adapter.d.ts +337 -3
  561. package/dist/core/lbug/lbug-adapter.js +772 -67
  562. package/dist/core/lbug/lbug-config.d.ts +93 -6
  563. package/dist/core/lbug/lbug-config.js +269 -9
  564. package/dist/core/lbug/native-check.d.ts +39 -0
  565. package/dist/core/lbug/native-check.js +323 -46
  566. package/dist/core/lbug/pdg-emit-sink.d.ts +13 -4
  567. package/dist/core/lbug/pdg-emit-sink.js +21 -111
  568. package/dist/core/lbug/pool-adapter.d.ts +23 -1
  569. package/dist/core/lbug/pool-adapter.js +115 -7
  570. package/dist/core/lbug/query-batch.d.ts +75 -0
  571. package/dist/core/lbug/query-batch.js +102 -0
  572. package/dist/core/lbug/rel-pair-routing.d.ts +160 -2
  573. package/dist/core/lbug/rel-pair-routing.js +228 -10
  574. package/dist/core/lbug/schema.d.ts +162 -3
  575. package/dist/core/lbug/schema.js +392 -157
  576. package/dist/core/lbug/sidecar-recovery.d.ts +1 -0
  577. package/dist/core/lbug/sidecar-recovery.js +1 -1
  578. package/dist/core/lbug/sync-csv-writer.d.ts +47 -0
  579. package/dist/core/lbug/sync-csv-writer.js +108 -0
  580. package/dist/core/lbug/wal-checkpoint-driver.js +3 -0
  581. package/dist/core/platform/capabilities.d.ts +0 -1
  582. package/dist/core/platform/capabilities.js +12 -7
  583. package/dist/core/run-analyze.d.ts +150 -3
  584. package/dist/core/run-analyze.js +1562 -151
  585. package/dist/core/search/bm25-index.d.ts +11 -0
  586. package/dist/core/search/bm25-index.js +60 -26
  587. package/dist/core/search/fts-indexes.d.ts +167 -2
  588. package/dist/core/search/fts-indexes.js +264 -10
  589. package/dist/core/wiki/graph-queries.js +145 -112
  590. package/dist/core/wiki/llm-client.d.ts +37 -5
  591. package/dist/core/wiki/llm-client.js +88 -17
  592. package/dist/core/wiki/prompts.d.ts +19 -0
  593. package/dist/core/wiki/prompts.js +20 -1
  594. package/dist/lib/utils.d.ts +113 -0
  595. package/dist/lib/utils.js +185 -0
  596. package/dist/mcp/http-transport.d.ts +5 -0
  597. package/dist/mcp/http-transport.js +19 -5
  598. package/dist/mcp/local/aop-metadata.d.ts +77 -0
  599. package/dist/mcp/local/aop-metadata.js +227 -0
  600. package/dist/mcp/local/bean-metadata.d.ts +3 -0
  601. package/dist/mcp/local/bean-metadata.js +46 -0
  602. package/dist/mcp/local/local-backend.d.ts +167 -6
  603. package/dist/mcp/local/local-backend.js +1756 -405
  604. package/dist/mcp/local/path-predicate.d.ts +19 -0
  605. package/dist/mcp/local/path-predicate.js +21 -0
  606. package/dist/mcp/local/pdg-impact.d.ts +132 -10
  607. package/dist/mcp/local/pdg-impact.js +391 -98
  608. package/dist/mcp/output-budget.d.ts +5 -0
  609. package/dist/mcp/output-budget.js +52 -0
  610. package/dist/mcp/read-only-policy.d.ts +12 -0
  611. package/dist/mcp/read-only-policy.js +102 -0
  612. package/dist/mcp/repository-policy.d.ts +35 -0
  613. package/dist/mcp/repository-policy.js +333 -0
  614. package/dist/mcp/resources.js +34 -7
  615. package/dist/mcp/server.d.ts +5 -2
  616. package/dist/mcp/server.js +48 -18
  617. package/dist/mcp/tools.d.ts +8 -8
  618. package/dist/mcp/tools.js +91 -20
  619. package/dist/server/analyze-job.d.ts +40 -2
  620. package/dist/server/analyze-job.js +30 -2
  621. package/dist/server/analyze-launch.d.ts +1 -1
  622. package/dist/server/analyze-launch.js +79 -10
  623. package/dist/server/analyze-worker-core.d.ts +3 -2
  624. package/dist/server/analyze-worker-core.js +15 -3
  625. package/dist/server/analyze-worker-ipc.d.ts +1 -1
  626. package/dist/server/analyze-worker-ipc.js +4 -0
  627. package/dist/server/analyze-worker-protocol.d.ts +59 -0
  628. package/dist/server/analyze-worker-protocol.js +1 -0
  629. package/dist/server/analyze-worker.d.ts +1 -17
  630. package/dist/server/analyze-worker.js +29 -6
  631. package/dist/server/api.d.ts +2 -11
  632. package/dist/server/api.js +200 -155
  633. package/dist/server/embed-run-outcome.d.ts +89 -0
  634. package/dist/server/embed-run-outcome.js +88 -0
  635. package/dist/server/git-clone.js +6 -2
  636. package/dist/server/mcp-http.d.ts +1 -1
  637. package/dist/server/mcp-http.js +4 -2
  638. package/dist/server/middleware.d.ts +116 -9
  639. package/dist/server/middleware.js +291 -22
  640. package/dist/server/sse-progress.d.ts +41 -0
  641. package/dist/server/sse-progress.js +125 -0
  642. package/dist/server/upload-ingest.js +2 -1
  643. package/dist/server/validation.js +2 -1
  644. package/dist/storage/branch-index.d.ts +11 -6
  645. package/dist/storage/branch-index.js +15 -7
  646. package/dist/storage/file-hash.js +2 -2
  647. package/dist/storage/fs-atomic.d.ts +25 -0
  648. package/dist/storage/fs-atomic.js +42 -0
  649. package/dist/storage/git.d.ts +165 -0
  650. package/dist/storage/git.js +352 -3
  651. package/dist/storage/index-lock.d.ts +83 -0
  652. package/dist/storage/index-lock.js +654 -0
  653. package/dist/storage/parse-cache.js +482 -1
  654. package/dist/storage/parsedfile-store.d.ts +8 -0
  655. package/dist/storage/parsedfile-store.js +235 -1
  656. package/dist/storage/repo-manager.d.ts +29 -309
  657. package/dist/storage/repo-manager.js +187 -165
  658. package/dist/storage/repo-meta.d.ts +530 -0
  659. package/dist/storage/repo-meta.js +100 -0
  660. package/dist/types/pipeline.d.ts +37 -0
  661. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +1 -1
  662. package/hooks/claude/gitnexus-hook.cjs +1 -1
  663. package/hooks/claude/resolve-analyze-cmd.cjs +80 -13
  664. package/package.json +20 -12
  665. package/scripts/cross-platform-shard.ts +143 -0
  666. package/scripts/cross-platform-tests.ts +94 -0
  667. package/scripts/ensure-fts.ts +14 -3
  668. package/scripts/run-cross-platform.ts +36 -10
  669. package/scripts/sync-plugin-manifests.mjs +192 -0
  670. package/skills/gitnexus-cli.md +3 -3
  671. package/skills/gitnexus-debugging.md +37 -7
  672. package/skills/gitnexus-exploring.md +26 -4
  673. package/skills/gitnexus-impact-analysis.md +71 -10
  674. package/skills/gitnexus-lfg/README.md +55 -0
  675. package/skills/gitnexus-lfg/SKILL.md +86 -0
  676. package/skills/gitnexus-plan/README.md +147 -0
  677. package/skills/gitnexus-plan/SKILL.md +348 -0
  678. package/skills/gitnexus-plan/references/context-ledger.md +137 -0
  679. package/skills/gitnexus-plan/references/context-pack.md +126 -0
  680. package/skills/gitnexus-plan/references/evidence-provenance.md +309 -0
  681. package/skills/gitnexus-plan/references/pdg-slice.md +109 -0
  682. package/skills/gitnexus-plan/references/plan-template.md +201 -0
  683. package/skills/gitnexus-plan/scripts/evidence-provenance.mjs +2366 -0
  684. package/skills/gitnexus-refactoring.md +48 -5
  685. package/skills/gitnexus-review/SKILL.md +279 -0
  686. package/skills/gitnexus-review/ci-personas/ci-adversarial-lens.md +42 -0
  687. package/skills/gitnexus-review/ci-personas/ci-blast-radius-lens.md +39 -0
  688. package/skills/gitnexus-review/ci-personas/ci-correctness-lens.md +37 -0
  689. package/skills/gitnexus-review/ci-personas/ci-coverage-lens.md +40 -0
  690. package/skills/gitnexus-review/ci-personas/ci-critic-lens.md +42 -0
  691. package/skills/gitnexus-review/ci-personas/ci-security-lens.md +39 -0
  692. package/skills/gitnexus-taint-analysis.md +13 -7
  693. package/skills/gitnexus-work/README.md +71 -0
  694. package/skills/gitnexus-work/SKILL.md +272 -0
  695. package/skills/gitnexus-work/references/evidence-provenance.md +309 -0
  696. package/skills/gitnexus-work/scripts/evidence-provenance.mjs +2366 -0
  697. package/vendor/leiden/utils.cjs +28 -1
  698. package/web/assets/ProcessFlowModal-Badh2Pbg.js +24 -0
  699. package/web/assets/__vite-browser-external-bMKyYQkO.js +1 -0
  700. package/web/assets/abnfDiagram-QNN2D4NM-Cq2djWWe.js +1 -0
  701. package/web/assets/agent-DIzEQ1JJ.js +626 -0
  702. package/web/assets/architecture-TIHT7OUA-V6N4J3SY-fTePvLoe.js +1 -0
  703. package/web/assets/architectureDiagram-OT7VKSJ7-mKEXxkeF.js +36 -0
  704. package/web/assets/blockDiagram-54WLDVWM-Dg_hrRWE.js +132 -0
  705. package/web/assets/{c4Diagram-Y2BXMSZH-DOXVs7-C.js → c4Diagram-X2LASM7V-CnhoT4aV.js} +6 -6
  706. package/web/assets/chunk-224SPVON-B8gxtXpy.js +1 -0
  707. package/web/assets/chunk-2AEHWXPW-CV4r6-1I.js +127 -0
  708. package/web/assets/chunk-2SREHG4O-Dip42xOq.js +1 -0
  709. package/web/assets/chunk-4RFN2BYJ-DHHWuC-n.js +1 -0
  710. package/web/assets/chunk-66DQ2XMT-CM0xjFAN.js +1 -0
  711. package/web/assets/chunk-6BELYETK-ChsYtfHW.js +1 -0
  712. package/web/assets/chunk-6L755F7B-CMhLBxi2.js +1 -0
  713. package/web/assets/chunk-7CWYLC5S-DRgGkkpF.js +2 -0
  714. package/web/assets/chunk-A7VWPJGB-B4O3oQdQ.js +1 -0
  715. package/web/assets/chunk-BI6VK774-D0oaWk8V.js +1 -0
  716. package/web/assets/chunk-D6VWDJW2-BxcBdZ9c.js +2 -0
  717. package/web/assets/chunk-DMV4VAQV-Bn6WXDSX.js +4 -0
  718. package/web/assets/chunk-E5QJAATJ-dh-5CtIV.js +2 -0
  719. package/web/assets/chunk-EQFTRU2I-1ix7TzW9.js +2 -0
  720. package/web/assets/chunk-FWYVLQTC-BgDItRzJ.js +1 -0
  721. package/web/assets/chunk-HNC4WDU7-DoU3KnA7.js +1 -0
  722. package/web/assets/chunk-L5GCOVLC-CE2w8Nm_.js +161 -0
  723. package/web/assets/chunk-LAHSG3N2-Bz4hcn0N.js +321 -0
  724. package/web/assets/chunk-LORZLZWG-B57uGw2N.js +88 -0
  725. package/web/assets/chunk-NLANEA3F-XpH_NPA5.js +62 -0
  726. package/web/assets/chunk-NV3KIAZN-5yvcpMiu.js +1 -0
  727. package/web/assets/chunk-OIYT25JQ-CQ6KOZGX.js +1 -0
  728. package/web/assets/chunk-PE7DX7ZZ-CCvRVj43.js +10 -0
  729. package/web/assets/chunk-Q67WD55A-BWCH2SVz.js +156 -0
  730. package/web/assets/chunk-RRFMTAIC-CMN0wcYA.js +1 -0
  731. package/web/assets/{chunk-RLI5ZMPA-CkXZ0y0U.js → chunk-SATU7PGQ-B1zhgOvc.js} +1 -1
  732. package/web/assets/chunk-STOV2HOB-GnExfzIX.js +1 -0
  733. package/web/assets/chunk-SZD42YQK-ijyKKVr1.js +70 -0
  734. package/web/assets/chunk-TFPGFQPA-Dj6a-0pC.js +231 -0
  735. package/web/assets/chunk-TGVD4F4B-BMc31Gj2.js +2 -0
  736. package/web/assets/chunk-TZUXEDM2-B7p9AcTw.js +1 -0
  737. package/web/assets/chunk-UAT7B5JY-DW-A_fpI.js +1 -0
  738. package/web/assets/{chunk-6764PJDD-C7xxk063.js → chunk-VE5CLXGZ-C1vB7wpY.js} +1 -1
  739. package/web/assets/chunk-VJFVEZTB-CX66F2ep.js +206 -0
  740. package/web/assets/chunk-VY5UBI4V-yxPQAWxw.js +1 -0
  741. package/web/assets/chunk-WJBAP47W-BckNkuEN.js +1 -0
  742. package/web/assets/chunk-XNMVGMAZ-Cg_LnPtj.js +1 -0
  743. package/web/assets/classDiagram-VPOSVPXH-BjOiXA_C.js +1 -0
  744. package/web/assets/classDiagram-v2-5VFNUXQA-BjOiXA_C.js +1 -0
  745. package/web/assets/{cose-bilkent-UX7MHV2Q-BmnZ7Mvj.js → cose-bilkent-RZA4XIPR-BHpK4Crp.js} +1 -1
  746. package/web/assets/cynefin-VYW2F7L2-2VAHYKR6-DAsF6GZv.js +1 -0
  747. package/web/assets/cynefinDiagram-TR7LZUCG-C3Iar9lZ.js +62 -0
  748. package/web/assets/dagre-K64A6Z3X-CUwi79mA.js +4 -0
  749. package/web/assets/diagram-2TLGJV2N-C2F5Nfym.js +30 -0
  750. package/web/assets/diagram-JCD7PQ7C-DC5-9FRr.js +24 -0
  751. package/web/assets/diagram-KFBZAVDW-Dfmf_4ti.js +3 -0
  752. package/web/assets/diagram-L7DVE3PN-GIRyVUtt.js +41 -0
  753. package/web/assets/diagram-YP3FGXHI-CyNFYtkd.js +24 -0
  754. package/web/assets/ebnfDiagram-DRHRQJWO-D8_CI4ZF.js +1 -0
  755. package/web/assets/erDiagram-SZLJLQMB-CohQjznC.js +85 -0
  756. package/web/assets/eventmodeling-45OFAUF4-AOUJWSLX-BvTc6iQi.js +1 -0
  757. package/web/assets/flowDiagram-BWE6NHOH-B6RnEvD9.js +1 -0
  758. package/web/assets/ganttDiagram-2QTCJSNI-Cks_mEcF.js +292 -0
  759. package/web/assets/gitGraph-TEB2WS4Q-U5VWYIBC-Dy7CCSZ5.js +1 -0
  760. package/web/assets/gitGraphDiagram-VVZAVV5G-CG41cIVc.js +106 -0
  761. package/web/assets/{index-B4eB4dNZ.js → index-BBNl0qfX.js} +51 -51
  762. package/web/assets/index-CJDpyH-n.css +2 -0
  763. package/web/assets/info-DKCQHKI2-DX6KST54-BLJg1ikX.js +1 -0
  764. package/web/assets/infoDiagram-FY4PZKRE-BJ3vPVwW.js +2 -0
  765. package/web/assets/{ishikawaDiagram-BNXS4ZKH-BLjivjXL.js → ishikawaDiagram-4JDSWWDM-DmqWtjkl.js} +6 -6
  766. package/web/assets/{journeyDiagram-M6C3CM3L-Cetgs3NH.js → journeyDiagram-QUS3FAKT-XAzVzrQW.js} +6 -6
  767. package/web/assets/{kanban-definition-75IXJCU3-DMlCaIpf.js → kanban-definition-D4L7SQKK-YurRnmYn.js} +27 -27
  768. package/web/assets/katex-C5OPUE3Q-Bnbavtmu.js +257 -0
  769. package/web/assets/{mindmap-definition-2TDM6QVE-B4GbXQBc.js → mindmap-definition-66QP5TD6-ChrWWnil.js} +29 -29
  770. package/web/assets/node-Ct_zna8U.js +8 -0
  771. package/web/assets/packet-7NZHBO7P-5WG45QPS-KUxcuvaA.js +1 -0
  772. package/web/assets/pegDiagram-YKIO5C6U-QGeQLEq2.js +1 -0
  773. package/web/assets/pie-RZYD4A2V-2OCZ2VTD-DPs08kJY.js +1 -0
  774. package/web/assets/pieDiagram-YPY3TKG5-BuT3MYzm.js +39 -0
  775. package/web/assets/preload-helper-Czpn1I53.js +1 -0
  776. package/web/assets/{quadrantDiagram-VICAPDV7-B1TQnlm0.js → quadrantDiagram-VO5QLCXA-B7098wSc.js} +6 -6
  777. package/web/assets/radar-I7S5WNFK-EPLSA35B-w5F0K3a7.js +1 -0
  778. package/web/assets/railroad-3IZDKUUU-PWFQV6Y6-Bu668yqY.js +1 -0
  779. package/web/assets/railroad-abnf-AHOZXSZD-JYYJX3BS-B6ghpETS.js +1 -0
  780. package/web/assets/railroad-ebnf-EBAXGLYW-RBZ4ONAP-D-XmHh3M.js +1 -0
  781. package/web/assets/railroad-peg-LSFZ7HO6-KHNPXGPG-mhkrhzEn.js +1 -0
  782. package/web/assets/railroadDiagram-M77LTMSB-AXu4vxk_.js +1 -0
  783. package/web/assets/requirementDiagram-RASO5HTO-B-l8InpK.js +84 -0
  784. package/web/assets/sankeyDiagram-F32LOJ6L-Kd7zjJgX.js +40 -0
  785. package/web/assets/sdk-CzHHH8tD.js +37 -0
  786. package/web/assets/sequenceDiagram-URATNSBD-BlcdY9DS.js +162 -0
  787. package/web/assets/sizeCapture-5GORAGP4-BEqQzsxs.js +1 -0
  788. package/web/assets/src-C5sx_gMp.js +1 -0
  789. package/web/assets/stateDiagram-DODFXNG5-DTdHc-fF.js +1 -0
  790. package/web/assets/stateDiagram-v2-JNMM5JRA-DioNLDDo.js +1 -0
  791. package/web/assets/swimlanes-OSWOYLSK-CA0ofdwY.js +2 -0
  792. package/web/assets/swimlanesDiagram-RHGX2RDE-PkwUdckC.js +8 -0
  793. package/web/assets/timeline-definition-WJXWRH4I-DUfp1R3E.js +120 -0
  794. package/web/assets/treeView-QDETBFTQ-EHYTSQNO-C5lpBgPm.js +1 -0
  795. package/web/assets/treemap-6X3UGDF4-CCWCF7EK-BB0ZyO-B.js +1 -0
  796. package/web/assets/vennDiagram-K3VCMQLI-Bj-FN-XS.js +34 -0
  797. package/web/assets/wardley-OPB4EBWU-6OWERDVT-CEA3Nq0j.js +1 -0
  798. package/web/assets/wardleyDiagram-XK6FUAWV-CVV0dHFr.js +78 -0
  799. package/web/assets/xychartDiagram-5VRUVLCW-CK_rbSWE.js +7 -0
  800. package/web/index.html +17 -15
  801. package/dist/core/ingestion/languages/python/index-stats.d.ts +0 -18
  802. package/dist/core/ingestion/languages/python/index-stats.js +0 -25
  803. package/skills/gitnexus-pr-review.md +0 -163
  804. package/web/assets/ProcessFlowModal-Dna7GkwO.js +0 -22
  805. package/web/assets/agent-DNYDy-KA.js +0 -660
  806. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BVWFvYz2.js +0 -1
  807. package/web/assets/architectureDiagram-UL44E2DR-BU5ZRITg.js +0 -36
  808. package/web/assets/blockDiagram-7IZFK4PR-Dut1BpTV.js +0 -132
  809. package/web/assets/chunk-2T2R6R2M-pkw2oXHH.js +0 -4
  810. package/web/assets/chunk-2UTLFMKG-CXlhF3Pt.js +0 -1
  811. package/web/assets/chunk-3SSMPTDK-rPsRM1x0.js +0 -321
  812. package/web/assets/chunk-4R4BOZG6-Be_F0JoX.js +0 -159
  813. package/web/assets/chunk-67TQ5CYL-zxhDWgyF.js +0 -124
  814. package/web/assets/chunk-7J6CGLKN-AmkN8jJN.js +0 -10
  815. package/web/assets/chunk-C62D2QBJ-3gdL8EKc.js +0 -1
  816. package/web/assets/chunk-CEXFNPSA-CjwtMDkc.js +0 -1
  817. package/web/assets/chunk-INKRHTLW-D9BM9HWZ.js +0 -70
  818. package/web/assets/chunk-J5EP6P6S-L19oz55R.js +0 -1
  819. package/web/assets/chunk-KGFNY3KK-tD2F6wxK.js +0 -62
  820. package/web/assets/chunk-KGYTTC2M-zm6n242I.js +0 -1
  821. package/web/assets/chunk-KNLZD3CH-BGlP6X9R.js +0 -1
  822. package/web/assets/chunk-LCXTWHL2-Ba9JK-6F.js +0 -231
  823. package/web/assets/chunk-LII3EMHJ-EGx-eBIu.js +0 -1
  824. package/web/assets/chunk-LRIF4GLE-C3_PqUBq.js +0 -1
  825. package/web/assets/chunk-QA3QBVWF-D4cSJBf6.js +0 -2
  826. package/web/assets/chunk-RERM46MO-B5dAdWpP.js +0 -1
  827. package/web/assets/chunk-RG4AUYOV-OqBBMwuQ.js +0 -206
  828. package/web/assets/chunk-RKZBBQEN-MyIQbGgD.js +0 -1
  829. package/web/assets/chunk-T2UQINTJ-BJusEiGe.js +0 -1
  830. package/web/assets/chunk-UP6H54XL-BG-n7qDp.js +0 -1
  831. package/web/assets/chunk-UXSXWOXI-CW1_r5cS.js +0 -1
  832. package/web/assets/chunk-UY5QBCOK-BCpUlARQ.js +0 -1
  833. package/web/assets/chunk-VU6ZFW4Y-BeXVk45_.js +0 -2
  834. package/web/assets/chunk-ZXARS5L4-BI_qR2Nm.js +0 -1
  835. package/web/assets/classDiagram-KGZ6W3CR-D9zSIg0x.js +0 -1
  836. package/web/assets/classDiagram-v2-72OJOZXJ-D9zSIg0x.js +0 -1
  837. package/web/assets/dagre-ND4H6XIP-CpcV5kbl.js +0 -4
  838. package/web/assets/diagram-3NCE3AQN-6SMxBRpj.js +0 -43
  839. package/web/assets/diagram-GF46GFSD-BZEdNcRZ.js +0 -24
  840. package/web/assets/diagram-HNR7UZ2L-DxKW2a2Y.js +0 -3
  841. package/web/assets/diagram-QXG6HAR7-HfTHcXX_.js +0 -24
  842. package/web/assets/diagram-WEQXMOUZ-CyijUS8r.js +0 -10
  843. package/web/assets/erDiagram-L5TCEMPS-D0fvpWEX.js +0 -85
  844. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-CgFBM7e-.js +0 -1
  845. package/web/assets/flowDiagram-H6V6AXG4-Bvr9bbhk.js +0 -162
  846. package/web/assets/ganttDiagram-JCBTUEKG-TzODrLzv.js +0 -292
  847. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DCJv7SII.js +0 -1
  848. package/web/assets/gitGraphDiagram-S2ZK5IYY-ONQSwJIY.js +0 -106
  849. package/web/assets/index-CX_fADmQ.css +0 -2
  850. package/web/assets/info-J43DQDTF-KCYPFFUO-BfNy-9Nd.js +0 -1
  851. package/web/assets/infoDiagram-3YFTVSEB-DIPVUIQ6.js +0 -2
  852. package/web/assets/katex-K3KEBU37-qRQBy-EZ.js +0 -261
  853. package/web/assets/packet-YPE3B663-LP52Z2RK-D4qkeG9k.js +0 -1
  854. package/web/assets/pie-LRSECV5Y-TCRJHUBD-B_yMKyQa.js +0 -1
  855. package/web/assets/pieDiagram-CU6KROY3-D3YC_xyg.js +0 -30
  856. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DeD6yN_0.js +0 -1
  857. package/web/assets/requirementDiagram-JXO7QTGE-f8wczLQf.js +0 -84
  858. package/web/assets/sankeyDiagram-URQDO5SZ-BTxzG5j0.js +0 -40
  859. package/web/assets/sequenceDiagram-VS2MUI6T-DuamM-IR.js +0 -162
  860. package/web/assets/src-BNCXICdA.js +0 -1
  861. package/web/assets/stateDiagram-7D4R322I-Ch2pEUcw.js +0 -1
  862. package/web/assets/stateDiagram-v2-36443NZ5-7ANuoZLL.js +0 -1
  863. package/web/assets/timeline-definition-O6YCAMPW-07BR8R8G.js +0 -120
  864. package/web/assets/treeView-BLDUP644-QA4HXRO3-CDuSYnd4.js +0 -1
  865. package/web/assets/treemap-LRROVOQU-LLAWBHMP-imazucyJ.js +0 -1
  866. package/web/assets/vennDiagram-MWXL3ELB-BQX2ossk.js +0 -34
  867. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-DqrT9tB8.js +0 -1
  868. package/web/assets/wardleyDiagram-CUQ6CDDI-CGIJBBiK.js +0 -78
  869. package/web/assets/xychartDiagram-N2JHSOCM-B3HNMuWp.js +0 -7
  870. /package/web/assets/{chunk-7W6UQGC5-pyKGssV3.js → chunk-LIEV3EAG-pyKGssV3.js} +0 -0
@@ -1,23 +1,36 @@
1
1
  import fs from 'fs/promises';
2
- import { createReadStream, createWriteStream, constants as fsConstants } from 'fs';
2
+ import { createReadStream, createWriteStream, existsSync, constants as fsConstants } from 'fs';
3
3
  import { createInterface } from 'readline';
4
4
  import { once } from 'events';
5
5
  import { finished } from 'stream/promises';
6
6
  import path from 'path';
7
7
  import lbug from '@ladybugdb/core';
8
8
  import { closeQueryResults } from './query-result-utils.js';
9
+ import { chunk } from '../../lib/utils.js';
10
+ import { warnIfQueryTextUnbounded } from './query-batch.js';
9
11
  import { escapeCypherString } from './cypher-escape.js';
10
12
  import { withConnLock } from './conn-lock.js';
11
13
  import { isWalDriverActive } from './wal-driver-state.js';
12
14
  import { NODE_TABLES, REL_TABLE_NAME, SCHEMA_QUERIES, EMBEDDING_TABLE_NAME, CREATE_VECTOR_INDEX_QUERY, STALE_HASH_SENTINEL, } from './schema.js';
15
+ // Analyze-only, but reached from MCP startup via `pool-adapter.js`. #2802
16
+ // proposed lazy-importing it; rejected — `core/search/bm25-index.ts` statically
17
+ // imports `normalizeFtsText` from `csv-generator.js`, and `local-backend.ts`
18
+ // dynamically imports bm25-index on the FTS query path, so deferring here
19
+ // relocates the startup cost to first query rather than removing it. The
20
+ // measured figures live in #2802; they were environment-bound, this is not.
13
21
  import { streamAllCSVsToDisk } from './csv-generator.js';
22
+ import { PDG_EDGE_TYPES } from './pdg-emit-sink.js';
14
23
  import { getNodeLabel as deriveNodeLabel } from './rel-pair-routing.js';
15
24
  import { EMBEDDABLE_LABELS } from '../embeddings/types.js';
16
- import { extensionManager } from './extension-loader.js';
17
- import { classifyDeleteAllError, closeLbugConnection, HANDLE_RELEASE_PROBE_ATTEMPTS, HANDLE_RELEASE_PROBE_DELAY_MS, isDbBusyError, isOpenRetryExhausted, isWalCorruptionError, openLbugConnection, sleep, toNativeSafePath, resolveNativeSafeStorageDir, WAL_RECOVERY_SUGGESTION, waitForWindowsHandleRelease, } from './lbug-config.js';
18
- import { finalizeLbugSidecarsAfterClose, guardWalQuarantine, isMissingShadowSidecarError, isReadOnlyShadowReplayError, lbugLockRemediation, preflightLbugSidecars, quarantineWalForMissingShadow, renameFailureMessage, shadowSidecarRecoveryMessage, } from './sidecar-recovery.js';
19
- import { isVectorExtensionSupportedByPlatform } from '../platform/capabilities.js';
25
+ import { extensionManager, getFtsCapability, resolveAnalyzeInstallPolicy, } from './extension-loader.js';
26
+ // Remedy classification for LOAD failures (#2374/#2383). Pure + node:fs only, so
27
+ // this adds no cycle: `extension-loader.ts` already depends on it.
28
+ import { diagnoseExtensionLoad } from './extension-load-error.js';
29
+ import { classifyDeleteAllError, closeLbugConnection, HANDLE_RELEASE_PROBE_ATTEMPTS, HANDLE_RELEASE_PROBE_DELAY_MS, isDbBusyError, isOpenRetryExhausted, isWalCorruptionError, bufferPoolExhaustionRemedy, openLbugConnection, sleep, toNativeSafePath, resolveNativeSafeStorageDir, WAL_RECOVERY_SUGGESTION, waitForWindowsHandleRelease, } from './lbug-config.js';
30
+ import { cleanQuarantinedMissingShadowWals, finalizeLbugSidecarsAfterClose, guardWalQuarantine, isMissingShadowSidecarError, isReadOnlyShadowReplayError, lbugLockRemediation, preflightLbugSidecars, quarantineWalForMissingShadow, renameFailureMessage, shadowSidecarRecoveryMessage, sidecarPreflightDisabled, } from './sidecar-recovery.js';
20
31
  import { logger } from '../logger.js';
32
+ import { SPRING_AUTO_CONFIGURATION_REASONS, SPRING_AUTO_CONFIGURATION_SYNTHETIC_ID_PREFIX, } from '../ingestion/frameworks/spring/auto-configuration.js';
33
+ import { SPRING_AOP_EVIDENCE_ID_PREFIX } from '../ingestion/frameworks/spring/aop.js';
21
34
  /**
22
35
  * Split a relationship CSV into per-label-pair files on disk.
23
36
  *
@@ -399,6 +412,12 @@ const readQueryRows = async (queryResult) => {
399
412
  throw firstError;
400
413
  return rows;
401
414
  };
415
+ // Deliberately NOT covered by `warnIfQueryTextUnbounded` (#2915): this is the
416
+ // write/DDL raw path, and `batchInsertNodesToLbug` inlines a node's `content`
417
+ // here, so any source file over the 64 KB text ceiling would trip the heuristic
418
+ // on a query that is entirely legitimate. The guard sits on the read entry
419
+ // points (`executePrepared`, `streamQuery`), which is where a caller-sized list
420
+ // gets spliced into query TEXT.
402
421
  const queryAndDrain = async (targetConn, cypher) => {
403
422
  const run = async () => {
404
423
  const queryResult = await targetConn.query(cypher);
@@ -409,6 +428,9 @@ const queryAndDrain = async (targetConn, cypher) => {
409
428
  // connections skip the lock — see isSharedSingletonConn.
410
429
  return isSharedSingletonConn(targetConn) ? withConnLock(run) : run();
411
430
  };
431
+ // determinism: probe — existence only. Every call site runs this through
432
+ // `queryAndDrain`, which drains and discards the rows; the ONLY observable is
433
+ // whether the read-only shadow replay throws, so no row identity is read.
412
434
  const READ_ONLY_SHADOW_REPLAY_PROBE = 'MATCH (n) RETURN n LIMIT 1';
413
435
  /**
414
436
  * Serve-side entry to the shared WAL-quarantine safety gate. Refuses (throws)
@@ -666,7 +688,8 @@ const doInitLbug = async (dbPath, readOnly = false) => {
666
688
  const realPath = await fs.realpath(dbPath);
667
689
  const parentDir = path.dirname(dbPath);
668
690
  const realParent = await fs.realpath(parentDir);
669
- if (!realPath.startsWith(realParent + path.sep) && realPath !== realParent) {
691
+ const safePrefix = realParent.endsWith(path.sep) ? realParent : realParent + path.sep;
692
+ if (!realPath.startsWith(safePrefix) && realPath !== realParent) {
670
693
  throw new Error(`Refusing to delete ${dbPath}: resolved path ${realPath} is outside storage directory`);
671
694
  }
672
695
  // Old-style directory database or empty leftover - remove it
@@ -688,6 +711,26 @@ const doInitLbug = async (dbPath, readOnly = false) => {
688
711
  // -------------------------------------------------------------------------
689
712
  const releaseInitLock = await acquireInitLock(dbPath);
690
713
  try {
714
+ // Reclaim missing-shadow WAL quarantines from a PRIOR crash (#2637).
715
+ // LadybugDB renames an unrecoverable WAL aside as
716
+ // `${dbPath}.wal.missing-shadow.<ts>-<rand>` (quarantineWalForMissingShadow)
717
+ // instead of deleting it. Once quarantined it is permanently detached from
718
+ // the live store and never reopened, so reclaiming it is safe regardless of
719
+ // whether the main DB file exists this run — unlike the orphan-sidecar
720
+ // cleanup below, this must NOT be gated on "main DB missing": a quarantine
721
+ // event and a healthy main DB are independent facts. Never let a reclaim
722
+ // failure (e.g. a transient EBUSY from an antivirus scan) block DB startup.
723
+ if (!sidecarPreflightDisabled()) {
724
+ try {
725
+ const reclaimed = await cleanQuarantinedMissingShadowWals(dbPath);
726
+ for (const file of reclaimed) {
727
+ logger.warn(`GitNexus: reclaimed quarantined WAL ${path.basename(file)} from a prior crash`);
728
+ }
729
+ }
730
+ catch (err) {
731
+ logger.warn(`GitNexus: failed to reclaim missing-shadow WAL quarantines: ${summarizeError(err)}`);
732
+ }
733
+ }
691
734
  // Crash-recovery cleanup: if the main DB file is missing, stale sidecars
692
735
  // from an interrupted run can block fresh opens indefinitely.
693
736
  try {
@@ -754,7 +797,13 @@ const doInitLbug = async (dbPath, readOnly = false) => {
754
797
  // FTS powers baseline search, so initialize it with the core DB. Read-only
755
798
  // serve/MCP paths must never run DDL or trigger network INSTALL; analyze owns
756
799
  // schema/index creation and extension installation.
757
- await loadFTSExtension(undefined, readOnly ? { policy: 'load-only' } : {});
800
+ //
801
+ // `quiet` on the writable branch: on a cold machine this pre-load is EXPECTED
802
+ // to miss (default policy is load-only, extension not yet on disk) and analyze
803
+ // Phase 3 installs it moments later in the same run. Warning here reported a
804
+ // degradation that never happened — the run went on to build every FTS index.
805
+ // Phase 3 (and the read-only branch) still warn for real failures.
806
+ await loadFTSExtension(undefined, readOnly ? { policy: 'load-only' } : { quiet: true });
758
807
  currentDbPath = dbPath;
759
808
  return { db, conn };
760
809
  };
@@ -780,6 +829,21 @@ const copyCsvWithRetry = async (targetConn, copyQuery, onError) => {
780
829
  }
781
830
  }
782
831
  };
832
+ /**
833
+ * A staging CSV named in the COPY manifest is gone by the time COPY runs.
834
+ *
835
+ * Only tables with `rows > 0` enter the manifest (see `csv-generator.ts`), so
836
+ * the file WAS written during this run and something removed it since. Raw,
837
+ * that surfaces as a LadybugDB "Binder exception: No file found that matches
838
+ * the pattern …" and then an ENOENT on the next file — two engine-level
839
+ * messages that name neither the cause nor a remedy, and which the field
840
+ * reports show operators hitting on a forced rebuild with nothing to act on.
841
+ */
842
+ export const missingStagingCsvError = (table, csvPath, rows) => new Error(`Staging CSV for ${table} is missing: ${csvPath}. It was written with ` +
843
+ `${rows.toLocaleString()} rows during this run, so it was removed mid-run — most often a ` +
844
+ `second \`gitnexus analyze\` on the same repo (both use .gitnexus/csv), or an external ` +
845
+ `cleanup of .gitnexus/. Ensure no other analyze is running, then re-run ` +
846
+ `\`gitnexus analyze --force\`.`);
783
847
  /**
784
848
  * Bulk-COPY every node CSV sequentially on the single writable connection
785
849
  * (LadybugDB allows one write txn at a time). Extracted from loadGraphToLbug so
@@ -788,15 +852,47 @@ const copyCsvWithRetry = async (targetConn, copyQuery, onError) => {
788
852
  * keeps the IGNORE_ERRORS=true retry; a hard failure throws (no node rows ⇒ the
789
853
  * relationship COPY would dangle on missing endpoints).
790
854
  */
855
+ /**
856
+ * Re-check a staging CSV a few times before declaring it gone.
857
+ *
858
+ * Review asked whether turning a silent degrade into a hard abort was
859
+ * deliberate. It is — a fallback that recovers zero rows is exactly the
860
+ * confident-empty failure this work is about, so failing loud is right. But the
861
+ * transient the error message itself names, a second concurrent `analyze`
862
+ * sharing `.gitnexus/csv`, is a RACE, and aborting a multi-minute rebuild on
863
+ * one stat() is a harsh answer to a file that may reappear microseconds later.
864
+ *
865
+ * Bounded and short: three extra looks over ~150ms total. Long enough to ride
866
+ * out a rename or a slow network filesystem, far too short to mask a file that
867
+ * is genuinely gone.
868
+ */
869
+ const stagingCsvExists = async (csvPath) => {
870
+ const RETRY_DELAYS_MS = [25, 50, 75];
871
+ if (existsSync(csvPath))
872
+ return true;
873
+ for (const delay of RETRY_DELAYS_MS) {
874
+ await new Promise((resolve) => setTimeout(resolve, delay));
875
+ if (existsSync(csvPath))
876
+ return true;
877
+ }
878
+ return false;
879
+ };
791
880
  const copyNodeCSVs = async (targetConn, nodeFileEntries, log, totalSteps) => {
792
881
  let stepsDone = 0;
793
882
  for (const [table, { csvPath, rows }] of nodeFileEntries) {
794
883
  stepsDone++;
795
884
  log(`Loading nodes ${stepsDone}/${totalSteps}: ${table} (${rows.toLocaleString()} rows)`);
885
+ if (!(await stagingCsvExists(csvPath)))
886
+ throw missingStagingCsvError(table, csvPath, rows);
796
887
  const copyQuery = getCopyQuery(table, normalizeCopyPath(csvPath));
797
888
  await copyCsvWithRetry(targetConn, copyQuery, (retryErr) => {
798
889
  const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
799
- throw new Error(`COPY failed for ${table}: ${retryMsg.slice(0, 200)}`);
890
+ // Pool exhaustion gets a remedy (#2631): the raw binder text gives the
891
+ // operator nothing to act on, and on non-4K-page hosts (Ascend aarch64,
892
+ // Apple Silicon) the pool bills up to pageSize/4KiB x faster than the
893
+ // sizing was calibrated for — name the knob and the mechanism.
894
+ const remedy = bufferPoolExhaustionRemedy(retryMsg);
895
+ throw new Error(`COPY failed for ${table}: ${retryMsg.slice(0, 200)}${remedy ? ` ${remedy}` : ''}`);
800
896
  });
801
897
  }
802
898
  };
@@ -821,7 +917,16 @@ export const loadGraphToLbug = async (graph, repoPath, storagePath, onProgress,
821
917
  * was on the in-memory `graph` holds zero BasicBlocks, so `streamAllCSVsToDisk`
822
918
  * emits none — the manifest is the sole source and there is no double-COPY.
823
919
  */
824
- pdgEmitManifest) => {
920
+ pdgEmitManifest,
921
+ /**
922
+ * Streamed structural-emit manifest (#2680). Unlike {@link pdgEmitManifest},
923
+ * these pair keys are NOT disjoint from the whole-graph emit's: a streamed
924
+ * `CALLS` edge is `Function|Function`, exactly like the retained edges
925
+ * `streamAllCSVsToDisk` just wrote. So these files are APPENDED as additional
926
+ * COPY jobs for the same pair rather than merged into `relsByPair` (a Map,
927
+ * which holds one CSV per pair and would silently drop one of them).
928
+ */
929
+ graphEmitManifest) => {
825
930
  if (!conn) {
826
931
  throw new Error('LadybugDB not initialized. Call initLbug first.');
827
932
  }
@@ -942,21 +1047,42 @@ pdgEmitManifest) => {
942
1047
  const { relsByPair, relHeader, skippedRels, totalValidRels } = csvResult;
943
1048
  let tCopyRels = tCopyNodes;
944
1049
  let tFallback = tCopyNodes;
945
- const insertedRels = totalValidRels;
1050
+ // One COPY job per CSV FILE, not per label pair. The whole-graph emit writes
1051
+ // at most one file per pair, but the streamed structural manifest (#2680) can
1052
+ // contribute a second file for a pair the whole-graph emit also wrote — both
1053
+ // must load. `relsByPair` stays a one-file-per-pair Map so the PDG merge above
1054
+ // and every other consumer are untouched.
1055
+ const copyJobs = [];
1056
+ for (const [pairKey, meta] of relsByPair) {
1057
+ copyJobs.push({ pairKey, csvPath: meta.csvPath, rows: meta.rows });
1058
+ }
1059
+ if (graphEmitManifest) {
1060
+ for (const [pairKey, meta] of graphEmitManifest.relsByPair) {
1061
+ copyJobs.push({ pairKey, csvPath: meta.csvPath, rows: meta.rows });
1062
+ }
1063
+ }
1064
+ const insertedRels = totalValidRels + (graphEmitManifest?.totalRows ?? 0);
946
1065
  const warnings = [];
1066
+ let poolRemedyIssued = false;
947
1067
  if (insertedRels > 0) {
948
- log(`Loading edges: ${insertedRels.toLocaleString()} across ${relsByPair.size} types`);
1068
+ log(`Loading edges: ${insertedRels.toLocaleString()} across ${copyJobs.length} CSV files`);
949
1069
  let pairIdx = 0;
950
1070
  let failedPairEdges = 0;
951
1071
  const failedPairCsvPaths = new Set();
952
- for (const [pairKey, { csvPath: pairCsvPath, rows }] of relsByPair) {
1072
+ for (const { pairKey, csvPath: pairCsvPath, rows } of copyJobs) {
953
1073
  pairIdx++;
954
1074
  const [fromLabel, toLabel] = pairKey.split('|');
1075
+ // Same guarantee as the node COPY: a pair file only reaches this loop
1076
+ // with rows on it, so an absent file means it vanished mid-run. This is
1077
+ // the `rel_Folder_File.csv` ENOENT the field reports end on.
1078
+ if (!(await stagingCsvExists(pairCsvPath))) {
1079
+ throw missingStagingCsvError(`${fromLabel} -> ${toLabel}`, pairCsvPath, rows);
1080
+ }
955
1081
  const normalizedPath = normalizeCopyPath(pairCsvPath);
956
1082
  // PARALLEL=false is load-bearing here too — see COPY_CSV_OPTS (#2203 / kuzudb/kuzu#5778).
957
1083
  const copyQuery = `COPY ${REL_TABLE_NAME} FROM "${normalizedPath}" (from="${fromLabel}", to="${toLabel}", HEADER=true, ESCAPE='"', DELIM=',', QUOTE='"', PARALLEL=false, auto_detect=false)`;
958
1084
  if (pairIdx % 5 === 0 || rows > 1000) {
959
- log(`Loading edges: ${pairIdx}/${relsByPair.size} types (${fromLabel} -> ${toLabel})`);
1085
+ log(`Loading edges: ${pairIdx}/${copyJobs.length} files (${fromLabel} -> ${toLabel})`);
960
1086
  }
961
1087
  // Use the captured `writeConn` (not the module-level `conn`) for the rel
962
1088
  // COPY, matching the node COPY above — one captured reference for the whole
@@ -966,6 +1092,17 @@ pdgEmitManifest) => {
966
1092
  await copyCsvWithRetry(writeConn, copyQuery, (retryErr) => {
967
1093
  const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
968
1094
  warnings.push(`${fromLabel}->${toLabel} (${rows} edges): ${retryMsg.slice(0, 80)}`);
1095
+ // One remedy per bulk load, not per pair (#2631): pool exhaustion
1096
+ // repeats for every remaining pair once it starts. logger.warn, not
1097
+ // just warnings.push — the returned warnings array has no consumer at
1098
+ // any call site, so a push alone would leave the remedy invisible
1099
+ // while the row-by-row fallback quietly degrades the load.
1100
+ const remedy = poolRemedyIssued ? undefined : bufferPoolExhaustionRemedy(retryMsg);
1101
+ if (remedy) {
1102
+ poolRemedyIssued = true;
1103
+ warnings.push(remedy);
1104
+ logger.warn(remedy);
1105
+ }
969
1106
  failedPairEdges += rows;
970
1107
  failedPairCsvPaths.add(pairCsvPath);
971
1108
  });
@@ -1106,6 +1243,12 @@ const formatCypherValue = (v) => {
1106
1243
  return String(v);
1107
1244
  return `'${escapeCypherString(String(v))}'`;
1108
1245
  };
1246
+ const formatCypherStringArray = (value) => {
1247
+ const items = Array.isArray(value)
1248
+ ? value.filter((item) => typeof item === 'string')
1249
+ : [];
1250
+ return `[${items.map(formatCypherValue).join(', ')}]`;
1251
+ };
1109
1252
  /**
1110
1253
  * Fallback: insert relationships one-by-one if COPY fails.
1111
1254
  *
@@ -1187,11 +1330,14 @@ export const getCopyQuery = (table, filePath) => {
1187
1330
  // `calleeIds` is its SOUND parallel (space-joined resolved callee ids, #2227).
1188
1331
  return `COPY ${t}(id, filePath, startLine, endLine, text, callees, calleeIds) FROM "${filePath}" ${COPY_CSV_OPTS}`;
1189
1332
  }
1333
+ if (table === 'Class') {
1334
+ return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description, frameworkAnnotations) FROM "${filePath}" ${COPY_CSV_OPTS}`;
1335
+ }
1190
1336
  if (table === 'Method') {
1191
1337
  return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description, parameterCount, returnType) FROM "${filePath}" ${COPY_CSV_OPTS}`;
1192
1338
  }
1193
1339
  if (table === 'Property') {
1194
- return `COPY ${t}(id, name, filePath, startLine, endLine, content, description, declaredType) FROM "${filePath}" ${COPY_CSV_OPTS}`;
1340
+ return `COPY ${t}(id, name, filePath, startLine, endLine, content, description, declaredType, isDetail) FROM "${filePath}" ${COPY_CSV_OPTS}`;
1195
1341
  }
1196
1342
  // TypeScript/JS code element tables have isExported; multi-language tables do not
1197
1343
  if (TABLES_WITH_EXPORTED.has(table)) {
@@ -1236,6 +1382,12 @@ export const insertNodeToLbug = async (label, properties, dbPath) => {
1236
1382
  // is the sound resolved-id parallel to the leaf-name `callees` set.
1237
1383
  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 || '')}})`;
1238
1384
  }
1385
+ else if (label === 'Class') {
1386
+ const descPart = properties.description
1387
+ ? `, description: ${formatCypherValue(properties.description)}`
1388
+ : '';
1389
+ 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)}})`;
1390
+ }
1239
1391
  else if (TABLES_WITH_EXPORTED.has(label)) {
1240
1392
  const descPart = properties.description
1241
1393
  ? `, description: ${formatCypherValue(properties.description)}`
@@ -1246,7 +1398,7 @@ export const insertNodeToLbug = async (label, properties, dbPath) => {
1246
1398
  const descPart = properties.description
1247
1399
  ? `, description: ${formatCypherValue(properties.description)}`
1248
1400
  : '';
1249
- 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 || '')}})`;
1401
+ 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 || '')}, isDetail: ${properties.isDetail === true}})`;
1250
1402
  }
1251
1403
  else {
1252
1404
  // Multi-language tables (Struct, Impl, Trait, Macro, etc.) — no isExported
@@ -1320,6 +1472,12 @@ export const batchInsertNodesToLbug = async (nodes, dbPath) => {
1320
1472
  // (#2227) is the sound resolved-id parallel to the `callees` set.
1321
1473
  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 || '')}`;
1322
1474
  }
1475
+ else if (label === 'Class') {
1476
+ const descPart = properties.description
1477
+ ? `, n.description = ${formatCypherValue(properties.description)}`
1478
+ : '';
1479
+ 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)}`;
1480
+ }
1323
1481
  else if (TABLES_WITH_EXPORTED.has(label)) {
1324
1482
  const descPart = properties.description
1325
1483
  ? `, n.description = ${formatCypherValue(properties.description)}`
@@ -1330,7 +1488,7 @@ export const batchInsertNodesToLbug = async (nodes, dbPath) => {
1330
1488
  const descPart = properties.description
1331
1489
  ? `, n.description = ${formatCypherValue(properties.description)}`
1332
1490
  : '';
1333
- 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 || '')}`;
1491
+ 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 || '')}, n.isDetail = ${properties.isDetail === true}`;
1334
1492
  }
1335
1493
  else {
1336
1494
  const descPart = properties.description
@@ -1352,10 +1510,15 @@ export const batchInsertNodesToLbug = async (nodes, dbPath) => {
1352
1510
  }
1353
1511
  return { inserted, failed };
1354
1512
  };
1513
+ // Guarded by `executePrepared` — a pure delegation, so warning here too would
1514
+ // double-report the same query text (#2915).
1355
1515
  export const executeQuery = async (cypher) => {
1356
1516
  return await executePrepared(cypher, {});
1357
1517
  };
1358
1518
  export const streamQuery = async (cypher, onRow) => {
1519
+ // The other raw `conn.query` read entry point (`executePrepared` covers the
1520
+ // prepared path, and `executeQuery` delegates to it). Never throws (#2915).
1521
+ warnIfQueryTextUnbounded(cypher, 'streamQuery', (message) => logger.warn(message));
1359
1522
  if (isWalDriverActive()) {
1360
1523
  // streamQuery reads rows on the singleton connection WITHOUT withConnLock; if
1361
1524
  // the WAL-checkpoint driver is live, those reads could race a CHECKPOINT — the
@@ -1401,6 +1564,8 @@ export const streamQuery = async (cypher, onRow) => {
1401
1564
  * Prevents Cypher injection by binding values as parameters.
1402
1565
  */
1403
1566
  export const executePrepared = async (cypher, params) => {
1567
+ // A `.length` compare on text we already hold; never throws (#2915).
1568
+ warnIfQueryTextUnbounded(cypher, 'executePrepared', (message) => logger.warn(message));
1404
1569
  const c = conn;
1405
1570
  if (!c) {
1406
1571
  throw new Error('LadybugDB not initialized. Call initLbug first.');
@@ -1423,8 +1588,8 @@ export const executeWithReusedStatement = async (cypher, paramsList) => {
1423
1588
  if (paramsList.length === 0)
1424
1589
  return;
1425
1590
  const SUB_BATCH_SIZE = 4;
1426
- for (let i = 0; i < paramsList.length; i += SUB_BATCH_SIZE) {
1427
- const subBatch = paramsList.slice(i, i + SUB_BATCH_SIZE);
1591
+ for (const [subBatchIndex, subBatch] of chunk(paramsList, SUB_BATCH_SIZE).entries()) {
1592
+ const firstRow = subBatchIndex * SUB_BATCH_SIZE;
1428
1593
  // One critical section per sub-batch: the prepare + its executes run with
1429
1594
  // exclusive access to the connection (so the WAL checkpoint driver cannot
1430
1595
  // interleave a CHECKPOINT mid-batch), while the lock is released between
@@ -1443,16 +1608,33 @@ export const executeWithReusedStatement = async (cypher, paramsList) => {
1443
1608
  catch (e) {
1444
1609
  const msg = e instanceof Error ? e.message : String(e);
1445
1610
  const queryPreview = cypher.replace(/\s+/g, ' ').slice(0, 120);
1446
- throw new Error(`Batch execution failed for rows ${i + 1}-${i + subBatch.length}: ${msg} (${queryPreview})`);
1611
+ throw new Error(`Batch execution failed for rows ${firstRow + 1}-${firstRow + subBatch.length}: ${msg} (${queryPreview})`);
1447
1612
  }
1448
1613
  // Note: LadybugDB PreparedStatement doesn't require explicit close()
1449
1614
  });
1450
1615
  }
1451
1616
  };
1617
+ /**
1618
+ * Node and edge totals for the open index.
1619
+ *
1620
+ * `edges` is `undefined` when the count could NOT BE TAKEN, and that is a
1621
+ * different fact from zero. It used to be initialised to 0 with the query in a
1622
+ * swallowing `catch`, so a WAL/lock contention throw during finalize — a
1623
+ * documented hazard on this exact call — returned a measured-looking 0. The
1624
+ * collapse check downstream then read a perfectly healthy index as a total
1625
+ * write collapse, which is precisely the confident-zero failure that check
1626
+ * exists to prevent.
1627
+ */
1452
1628
  export const getLbugStats = async () => {
1453
1629
  const c = conn;
1454
- if (!c)
1455
- return { nodes: 0, edges: 0 };
1630
+ if (!c) {
1631
+ return {
1632
+ nodes: 0,
1633
+ edges: undefined,
1634
+ structuralEdges: undefined,
1635
+ structuralEdgesError: 'no open connection',
1636
+ };
1637
+ }
1456
1638
  // Called during analyze finalize while the WAL-checkpoint driver is still
1457
1639
  // running; each count read takes the connection lock so it cannot execute
1458
1640
  // concurrently with a driver CHECKPOINT. Per-query locking lets the driver
@@ -1470,7 +1652,7 @@ export const getLbugStats = async () => {
1470
1652
  // ignore
1471
1653
  }
1472
1654
  }
1473
- let totalEdges = 0;
1655
+ let totalEdges;
1474
1656
  try {
1475
1657
  totalEdges = await withConnLock(async () => {
1476
1658
  const queryResult = await c.query(`MATCH ()-[r:${REL_TABLE_NAME}]->() RETURN count(r) AS cnt`);
@@ -1479,9 +1661,34 @@ export const getLbugStats = async () => {
1479
1661
  });
1480
1662
  }
1481
1663
  catch {
1482
- // ignore
1664
+ // Leave `totalEdges` undefined: the count was not obtained. Reporting 0
1665
+ // here is what made a throwing query indistinguishable from an empty table.
1666
+ }
1667
+ // Structural-only count for the collapse check. `TAINT_PATH` is deliberately
1668
+ // NOT in `PDG_EDGE_TYPES` — it is a whole-program Function→Function edge that
1669
+ // lives in the in-memory graph and is persisted by the normal emit, so it IS
1670
+ // structural and must stay counted on both sides.
1671
+ let structuralEdges;
1672
+ let structuralEdgesError;
1673
+ try {
1674
+ const excluded = [...PDG_EDGE_TYPES].map((t) => `'${t}'`).join(', ');
1675
+ structuralEdges = await withConnLock(async () => {
1676
+ const queryResult = await c.query(`MATCH ()-[r:${REL_TABLE_NAME}]->() WHERE NOT r.type IN [${excluded}] RETURN count(r) AS cnt`);
1677
+ const rows = await readQueryRows(queryResult);
1678
+ return rows.length > 0 ? Number(rows[0]?.cnt ?? rows[0]?.[0] ?? 0) : 0;
1679
+ });
1483
1680
  }
1484
- return { nodes: totalNodes, edges: totalEdges };
1681
+ catch (err) {
1682
+ // Same contract as `edges`: leave undefined rather than report a zero the
1683
+ // collapse check would read as a total wipeout. But NOT silent — the reason
1684
+ // travels back on the result and is logged by the caller, so "the guard
1685
+ // declined because it could not measure" is visible instead of looking
1686
+ // identical to "the guard ran and found nothing wrong".
1687
+ structuralEdgesError = err instanceof Error ? err.message : String(err);
1688
+ logger.warn({ err }, 'Structural relationship count failed; the graph-write-collapse check will have no ' +
1689
+ 'structural measurement from this run.');
1690
+ }
1691
+ return { nodes: totalNodes, edges: totalEdges, structuralEdges, structuralEdgesError };
1485
1692
  };
1486
1693
  /**
1487
1694
  * Load cached embeddings from LadybugDB before a rebuild.
@@ -1508,6 +1715,9 @@ export const loadCachedEmbeddings = async () => {
1508
1715
  // Old schema only had (nodeId, embedding); new schema adds (id, chunkIndex, startLine, endLine, contentHash).
1509
1716
  // If the query fails (column missing), we return empty cache to force a full rebuild.
1510
1717
  try {
1718
+ // determinism: probe — schema probe, not a sample. `readQueryRows` drains
1719
+ // the result and the rows are dropped on the floor; only whether the
1720
+ // new-schema columns parse decides the branch.
1511
1721
  const check = await c.query(`MATCH (e:${EMBEDDING_TABLE_NAME}) RETURN e.nodeId AS nodeId, e.chunkIndex AS chunkIndex LIMIT 1`);
1512
1722
  await readQueryRows(check);
1513
1723
  }
@@ -1884,15 +2094,17 @@ export const wipeLbugDbFiles = async (lbugPath) => {
1884
2094
  export const isLbugReady = () => conn !== null && db !== null;
1885
2095
  /**
1886
2096
  * Multi-label alternation over exactly the labels that can own embedding
1887
- * rows (embedding-pipeline.ts queries EMBEDDABLE_LABELS and nothing else),
1888
- * reserved keywords backtick-escaped via {@link escapeTableName}. Probed on
1889
- * @ladybugdb/core 0.18.0 (this shipping review, FIX 4): the full 19-label
2097
+ * rows: EMBEDDABLE_LABELS plus File, which embedding-pipeline.ts embeds as
2098
+ * the zero-symbol fallback for text-only repositories (#2454). Reserved
2099
+ * keywords are backtick-escaped via {@link escapeTableName}. Probed on
2100
+ * @ladybugdb/core 0.18.0 (this shipping review, FIX 4): the full multi-label
1890
2101
  * alternation parses, executes, and deletes exactly the joined rows —
1891
2102
  * replacing the unlabeled `MATCH (n)` that scanned EVERY node table per
1892
2103
  * chunk (BasicBlock-dominated under `--pdg`) when only embeddable labels
1893
- * can match an embedding row.
2104
+ * can match an embedding row. Including File is free for code repositories:
2105
+ * they never hold File embedding rows, so the extra label joins nothing.
1894
2106
  */
1895
- const embeddableLabelMatch = () => EMBEDDABLE_LABELS.map((l) => escapeTableName(l)).join('|');
2107
+ const embeddableLabelMatch = () => ['File', ...EMBEDDABLE_LABELS].map((l) => escapeTableName(l)).join('|');
1896
2108
  // LADYBUGDB-CONTRACT: matches @ladybugdb/core ^0.18.0 native binder text,
1897
2109
  // probe-recorded: `Binder exception: Table CodeEmbedding does not exist.`
1898
2110
  // When bumping LadybugDB, re-validate — `git grep "LADYBUGDB-CONTRACT"`
@@ -2028,9 +2240,8 @@ export const deleteNodesForFiles = async (filePaths, options = {}) => {
2028
2240
  }
2029
2241
  const targetConn = conn;
2030
2242
  let warnedMissingEmbeddingTable = false;
2031
- for (let i = 0; i < filePaths.length; i += DELETE_FILES_CHUNK_SIZE) {
2032
- const chunk = filePaths.slice(i, i + DELETE_FILES_CHUNK_SIZE);
2033
- const listLiteral = `[${chunk.map((p) => `'${escapeCypherString(p)}'`).join(', ')}]`;
2243
+ for (const [chunkIndex, batch] of chunk(filePaths, DELETE_FILES_CHUNK_SIZE).entries()) {
2244
+ const listLiteral = `[${batch.map((p) => `'${escapeCypherString(p)}'`).join(', ')}]`;
2034
2245
  // Embedding rows key on their OWNING NODE's id: generateId builds
2035
2246
  // label-first ids — `${label}:${name}` (src/lib/utils.ts) with qualified
2036
2247
  // names that embed the file path (e.g. `Function:src/f.ts:fn0:1`) — so
@@ -2070,7 +2281,7 @@ export const deleteNodesForFiles = async (filePaths, options = {}) => {
2070
2281
  const tn = escapeTableName(tableName);
2071
2282
  await queryAndDrain(targetConn, `MATCH (n:${tn}) WHERE n.filePath IN ${listLiteral} DETACH DELETE n`);
2072
2283
  }
2073
- options.onChunk?.(Math.min(i + DELETE_FILES_CHUNK_SIZE, filePaths.length), filePaths.length);
2284
+ options.onChunk?.(Math.min((chunkIndex + 1) * DELETE_FILES_CHUNK_SIZE, filePaths.length), filePaths.length);
2074
2285
  }
2075
2286
  };
2076
2287
  export const getEmbeddingTableName = () => EMBEDDING_TABLE_NAME;
@@ -2148,11 +2359,8 @@ export const queryImportersBatch = async (targetFilePaths, options = {}) => {
2148
2359
  throw new Error('LadybugDB not initialized. Call initLbug first.');
2149
2360
  }
2150
2361
  const importers = new Set();
2151
- for (let i = 0; i < targetFilePaths.length; i += DELETE_FILES_CHUNK_SIZE) {
2152
- // `i` only ever advances in whole chunk strides, so this is exact.
2153
- const chunkIndex = i / DELETE_FILES_CHUNK_SIZE;
2154
- const chunk = targetFilePaths.slice(i, i + DELETE_FILES_CHUNK_SIZE);
2155
- const listLiteral = `[${chunk.map((p) => `'${escapeCypherString(p)}'`).join(', ')}]`;
2362
+ for (const [chunkIndex, batch] of chunk(targetFilePaths, DELETE_FILES_CHUNK_SIZE).entries()) {
2363
+ const listLiteral = `[${batch.map((p) => `'${escapeCypherString(p)}'`).join(', ')}]`;
2156
2364
  const cypher = `
2157
2365
  MATCH (a)-[r:${REL_TABLE_NAME}]->(b)
2158
2366
  WHERE r.type = 'IMPORTS' AND b.filePath IN ${listLiteral}
@@ -2176,9 +2384,9 @@ export const queryImportersBatch = async (targetFilePaths, options = {}) => {
2176
2384
  // it would have surfaced keeps possibly-stale edges this run, and the
2177
2385
  // old bare `catch {}` left no trace of that anywhere. pino idiom:
2178
2386
  // `err` key — `error` serializes to `{}`.
2179
- logger.warn({ err }, `Incremental importer BFS: dropped chunk ${chunkIndex} (${chunk.length} target path(s)) — ` +
2387
+ logger.warn({ err }, `Incremental importer BFS: dropped chunk ${chunkIndex} (${batch.length} target path(s)) — ` +
2180
2388
  'importer expansion degrades for this run; affected importers may keep stale edges until the next full rebuild.');
2181
- options.onChunkFailure?.(chunkIndex, chunk.length, err);
2389
+ options.onChunkFailure?.(chunkIndex, batch.length, err);
2182
2390
  }
2183
2391
  finally {
2184
2392
  if (queryResult)
@@ -2234,9 +2442,9 @@ export const deleteAllCommunitiesAndProcesses = async () => {
2234
2442
  /**
2235
2443
  * Shared mechanics for the delete-all-relationships-of-one-type family
2236
2444
  * ({@link deleteAllInterprocTaintPaths}, {@link deleteAllCallSummaries},
2237
- * {@link deleteAllInjects}): count the typed CodeRelation rows, then DELETE
2238
- * them (relationship-level — these are edge types, not node labels, so
2239
- * endpoints are untouched).
2445
+ * {@link deleteAllInjects}, {@link deleteSpringAutoConfigurationDeclarations}):
2446
+ * count the matching CodeRelation rows, then DELETE them (relationship-level —
2447
+ * these are edge types, not node labels, so endpoints are untouched).
2240
2448
  *
2241
2449
  * count + DELETE run as one critical section on the singleton connection so a
2242
2450
  * concurrent WAL-checkpoint cannot corrupt native state mid-delete (#pdg).
@@ -2244,8 +2452,9 @@ export const deleteAllCommunitiesAndProcesses = async () => {
2244
2452
  * @param relType the CodeRelation `type` value to delete (e.g. 'INJECTS')
2245
2453
  * @param logTag the `[tag]` prefix on the abort error message
2246
2454
  * @param duplicateNoun what the abort message says would be duplicated
2455
+ * @param exactReasons optional reason allowlist for a shared relationship type
2247
2456
  */
2248
- const deleteAllRelationshipsOfType = async (relType, logTag, duplicateNoun) => {
2457
+ const deleteAllRelationshipsOfType = async (relType, logTag, duplicateNoun, exactReasons) => {
2249
2458
  const c = conn;
2250
2459
  if (!c) {
2251
2460
  throw new Error('LadybugDB not initialized. Call initLbug first.');
@@ -2253,13 +2462,19 @@ const deleteAllRelationshipsOfType = async (relType, logTag, duplicateNoun) => {
2253
2462
  return withConnLock(async () => {
2254
2463
  let edgesDeleted = 0;
2255
2464
  let countResult;
2465
+ const reasonFilter = exactReasons === undefined || exactReasons.length === 0
2466
+ ? ''
2467
+ : ` AND (${exactReasons
2468
+ .map((reason) => `r.reason = '${escapeCypherString(reason)}'`)
2469
+ .join(' OR ')})`;
2470
+ const predicate = `r.type = '${escapeCypherString(relType)}'${reasonFilter}`;
2256
2471
  try {
2257
- countResult = await c.query(`MATCH ()-[r:CodeRelation]->() WHERE r.type = '${relType}' RETURN count(r) AS cnt`);
2472
+ countResult = await c.query(`MATCH ()-[r:CodeRelation]->() WHERE ${predicate} RETURN count(r) AS cnt`);
2258
2473
  const result = Array.isArray(countResult) ? countResult[0] : countResult;
2259
2474
  const rows = await result.getAll();
2260
2475
  const count = Number(rows[0]?.cnt ?? rows[0]?.[0] ?? 0);
2261
2476
  if (count > 0) {
2262
- await closeQueryResults(await c.query(`MATCH ()-[r:CodeRelation]->() WHERE r.type = '${relType}' DELETE r`));
2477
+ await closeQueryResults(await c.query(`MATCH ()-[r:CodeRelation]->() WHERE ${predicate} DELETE r`));
2263
2478
  edgesDeleted = count;
2264
2479
  }
2265
2480
  }
@@ -2335,6 +2550,93 @@ export const deleteAllCallSummaries = async () => deleteAllRelationshipsOfType('
2335
2550
  * DELETE on the typed CodeRelation rows — endpoints are untouched.
2336
2551
  */
2337
2552
  export const deleteAllInjects = async () => deleteAllRelationshipsOfType('INJECTS', 'di', 'duplicate INJECTS edges');
2553
+ /**
2554
+ * Drop every Spring AOP `ADVISED_BY` relationship before incremental
2555
+ * writeback. Pointcut/annotation resolution is whole-program: adding a type in
2556
+ * a third file can shadow a wildcard annotation import or change a wildcard
2557
+ * execution match between two otherwise unchanged endpoint files.
2558
+ */
2559
+ export const deleteAllAdvisedBy = async () => deleteAllRelationshipsOfType('ADVISED_BY', 'spring-aop', 'duplicate ADVISED_BY edges');
2560
+ /** Drop all synthetic Spring AOP evidence nodes before incremental writeback. */
2561
+ export const deleteSpringAopEvidenceNodes = async () => {
2562
+ const c = conn;
2563
+ if (!c) {
2564
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2565
+ }
2566
+ return withConnLock(async () => {
2567
+ let countResult;
2568
+ const idPrefix = escapeCypherString(SPRING_AOP_EVIDENCE_ID_PREFIX);
2569
+ const predicate = `n.id STARTS WITH '${idPrefix}'`;
2570
+ try {
2571
+ countResult = await c.query(`MATCH (n:CodeElement) WHERE ${predicate} RETURN count(n) AS cnt`);
2572
+ const result = Array.isArray(countResult) ? countResult[0] : countResult;
2573
+ const rows = await result.getAll();
2574
+ const count = Number(rows[0]?.cnt ?? rows[0]?.[0] ?? 0);
2575
+ if (count > 0) {
2576
+ await closeQueryResults(await c.query(`MATCH (n:CodeElement) WHERE ${predicate} DETACH DELETE n`));
2577
+ }
2578
+ if (countResult)
2579
+ await closeQueryResults(countResult);
2580
+ return { nodesDeleted: count };
2581
+ }
2582
+ catch (err) {
2583
+ if (countResult)
2584
+ await closeQueryResults(countResult);
2585
+ if (classifyDeleteAllError(err) === 'benign-missing-table') {
2586
+ return { nodesDeleted: 0 };
2587
+ }
2588
+ const message = err instanceof Error ? err.message : String(err);
2589
+ throw new Error('[spring-aop] failed to clear synthetic evidence before incremental re-write ' +
2590
+ `(${message}) — aborting to avoid stale advice metadata; the next run will full-rebuild`);
2591
+ }
2592
+ });
2593
+ };
2594
+ /**
2595
+ * Drop Spring-owned auto-configuration `DECLARES` relationships before
2596
+ * incremental writeback. `DECLARES` is generic, so exact reason filtering is
2597
+ * required: other metadata systems must retain their own declarations.
2598
+ */
2599
+ export const deleteSpringAutoConfigurationDeclarations = async () => deleteAllRelationshipsOfType('DECLARES', 'spring-auto-configuration', 'duplicate auto-configuration declarations', SPRING_AUTO_CONFIGURATION_REASONS);
2600
+ /**
2601
+ * Drop synthetic source-unavailable auto-configuration Class nodes before
2602
+ * incremental writeback. The fresh full graph re-emits every still-needed
2603
+ * synthetic node; deleting first also removes placeholders that became stale
2604
+ * when a real source class appeared.
2605
+ */
2606
+ export const deleteSpringAutoConfigurationSyntheticClasses = async () => {
2607
+ const c = conn;
2608
+ if (!c) {
2609
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2610
+ }
2611
+ return withConnLock(async () => {
2612
+ let countResult;
2613
+ const idPrefix = escapeCypherString(SPRING_AUTO_CONFIGURATION_SYNTHETIC_ID_PREFIX);
2614
+ const predicate = `n.id STARTS WITH '${idPrefix}'`;
2615
+ try {
2616
+ countResult = await c.query(`MATCH (n:Class) WHERE ${predicate} RETURN count(n) AS cnt`);
2617
+ const result = Array.isArray(countResult) ? countResult[0] : countResult;
2618
+ const rows = await result.getAll();
2619
+ const count = Number(rows[0]?.cnt ?? rows[0]?.[0] ?? 0);
2620
+ if (count > 0) {
2621
+ await closeQueryResults(await c.query(`MATCH (n:Class) WHERE ${predicate} DETACH DELETE n`));
2622
+ }
2623
+ if (countResult)
2624
+ await closeQueryResults(countResult);
2625
+ return { nodesDeleted: count };
2626
+ }
2627
+ catch (err) {
2628
+ if (countResult)
2629
+ await closeQueryResults(countResult);
2630
+ if (classifyDeleteAllError(err) === 'benign-missing-table') {
2631
+ return { nodesDeleted: 0 };
2632
+ }
2633
+ const message = err instanceof Error ? err.message : String(err);
2634
+ throw new Error('[spring-auto-configuration] failed to clear synthetic Class nodes before ' +
2635
+ `incremental re-write (${message}) — aborting to avoid stale placeholders; ` +
2636
+ 'the next run will full-rebuild');
2637
+ }
2638
+ });
2639
+ };
2338
2640
  // ============================================================================
2339
2641
  // Full-Text Search (FTS) Functions
2340
2642
  // ============================================================================
@@ -2372,16 +2674,16 @@ export const loadVectorExtension = async (targetConn, opts = {}) => {
2372
2674
  const useModuleState = targetConn === undefined;
2373
2675
  if (useModuleState && vectorExtensionLoaded)
2374
2676
  return true;
2375
- // INSTALL VECTOR crashes with SIGSEGV on Windows: the KuzuDB native extension
2376
- // installer has an unhandled error path on Windows that raises a fatal signal
2377
- // that JS try/catch cannot intercept. Skip loading — vector/embedding search
2378
- // is unavailable but all graph index queries still work. Do NOT set
2379
- // vectorExtensionLoaded here: the flag means "successfully loaded", and a
2380
- // subsequent call would otherwise short-circuit to `return true` at the top.
2381
- if (process.platform === 'win32')
2382
- return false;
2383
- if (!isVectorExtensionSupportedByPlatform())
2384
- return false;
2677
+ // No platform gate. Windows was hard-refused here for years on the strength
2678
+ // of an early-era report that in-process INSTALL VECTOR could SIGSEGV
2679
+ // (#1365) — but the extension server ships win_amd64 VECTOR artifacts for
2680
+ // every 0.18.x extension version (probed live: v0.18.0 and v0.18.1 both
2681
+ // serve a real PE32+ DLL; the pinned 0.18.2 core resolves its extension
2682
+ // directory to 0.18.1, strace-verified), and INSTALL now runs in a spawned
2683
+ // child process (installDuckDbExtensionOutOfProcess), so even a crashing
2684
+ // installer kills only the child and degrades to `false` here. LOAD of a
2685
+ // present extension file is an ordinary in-process load whose failures
2686
+ // surface as catchable errors, exactly like FTS.
2385
2687
  const c = targetConn ?? conn;
2386
2688
  if (!c) {
2387
2689
  throw new Error('LadybugDB not initialized. Call initLbug first.');
@@ -2475,6 +2777,220 @@ export const createVectorIndex = async () => {
2475
2777
  throw e;
2476
2778
  }
2477
2779
  };
2780
+ /**
2781
+ * The three field reads every index-catalog consumer needs, each hedging the
2782
+ * named-record form against the positional one exactly once.
2783
+ *
2784
+ * They exist because the hedge used to be inlined at five call sites across two
2785
+ * modules (#2841 review), one of which — the `dropSearchFTSIndexes` sweep in
2786
+ * `core/search/fts-indexes.ts` — carried no LADYBUGDB-CONTRACT marker at all, so
2787
+ * the doc above claimed a grep that could not find it. Exported for that module;
2788
+ * everything version-coupled about the row shape now lives in this one block.
2789
+ */
2790
+ export const indexRowTable = (row) => row?.table_name ?? row?.[0];
2791
+ export const indexRowName = (row) => row?.index_name ?? row?.[1];
2792
+ export const indexRowType = (row) => row?.index_type ?? row?.[2];
2793
+ /**
2794
+ * Read the index catalog on the writable connection, or `undefined` when it
2795
+ * cannot be read.
2796
+ *
2797
+ * `SHOW_INDEXES` is readable WITHOUT any extension loaded and reports
2798
+ * `extension_loaded` per index, so the extension-gated-DML checks below settle
2799
+ * the common "this DB carries no such index" case with one local read and no
2800
+ * error-string sniffing. It runs through the unprepared `conn.query()` path
2801
+ * like every other `CALL` procedure here (#2114).
2802
+ *
2803
+ * `undefined` means "could not prove anything" and every caller must treat it
2804
+ * as fail-closed (assume an index may be present), never as "no indexes". All
2805
+ * three readers below honour that, `ftsIndexExistsInCatalog` included since
2806
+ * #2841 review H3. To hand ONE read to several gates, use
2807
+ * {@link readIndexCatalogSnapshot} — passing this `undefined` on cannot be
2808
+ * distinguished from passing nothing at all.
2809
+ */
2810
+ export const readIndexCatalogRows = async () => {
2811
+ const targetConn = conn;
2812
+ if (!targetConn) {
2813
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2814
+ }
2815
+ try {
2816
+ return (await withConnLock(async () => readQueryRows(await targetConn.query('CALL SHOW_INDEXES() RETURN *'))));
2817
+ }
2818
+ catch (err) {
2819
+ logger.warn({ err }, 'Could not read the LadybugDB index catalog (CALL SHOW_INDEXES()); ' +
2820
+ 'extension-gated DML checks must assume an index may be present.');
2821
+ return undefined;
2822
+ }
2823
+ };
2824
+ /**
2825
+ * The failed half of an {@link IndexCatalogSnapshot}: the caller DID read the
2826
+ * catalog and could not prove anything.
2827
+ *
2828
+ * It exists because `undefined` was overloaded (#2841 review §5.A). The gates
2829
+ * below took `indexRows?: IndexCatalogRow[]`, so "my read failed" and "I passed
2830
+ * you nothing" were the SAME value, and each gate's `?? (await
2831
+ * readIndexCatalogRows())` silently re-read the catalog — turning the one shared
2832
+ * read the call site documents into three round-trips and three identical
2833
+ * warnings on the failure path, with the two gates free to decide from DIFFERENT
2834
+ * snapshots. A distinct sentinel makes "read, unreadable" a value the parameter
2835
+ * can carry, so a supplied snapshot is never re-read.
2836
+ */
2837
+ export const INDEX_CATALOG_UNREADABLE = Symbol('gitnexus:index-catalog-unreadable');
2838
+ /**
2839
+ * {@link readIndexCatalogRows} in snapshot form — what callers should read once
2840
+ * and pass to EVERY extension-gated-DML gate in a run, so the "one shared
2841
+ * `SHOW_INDEXES` read" invariant holds on the failure branch too (#2841 review
2842
+ * §5.A). The `IndexCatalogRow[] | undefined` spelling stays available for
2843
+ * callers that only want the rows.
2844
+ */
2845
+ export const readIndexCatalogSnapshot = async () => (await readIndexCatalogRows()) ?? INDEX_CATALOG_UNREADABLE;
2846
+ /**
2847
+ * Resolve a gate's optional `indexRows` argument into the rows it must judge,
2848
+ * reading the catalog AT MOST ONCE and ONLY when the caller supplied nothing.
2849
+ *
2850
+ * The `??` is meaningful again (#2841 review §5.A): `undefined` in can now only
2851
+ * mean "no snapshot supplied", because a caller whose own read failed passes
2852
+ * {@link INDEX_CATALOG_UNREADABLE}, which is truthy and short-circuits it.
2853
+ * `undefined` OUT keeps its documented meaning — "could not prove anything",
2854
+ * which every caller of {@link readIndexCatalogRows} treats as fail-closed.
2855
+ *
2856
+ * Exported for the `dropSearchFTSIndexes` sweep in `core/search/fts-indexes.ts`,
2857
+ * which takes the same optional-snapshot parameter and must resolve it by the
2858
+ * same rules — including the "a supplied snapshot is never re-read" half.
2859
+ */
2860
+ export const resolveGateRows = async (indexRows) => {
2861
+ const snapshot = indexRows ?? (await readIndexCatalogSnapshot());
2862
+ return snapshot === INDEX_CATALOG_UNREADABLE ? undefined : snapshot;
2863
+ };
2864
+ /**
2865
+ * Make DML against {@link EMBEDDING_TABLE_NAME} legal on the writable
2866
+ * connection when it can be, and report whether it is.
2867
+ *
2868
+ * LadybugDB refuses EVERY mutation of a table carrying an HNSW index while
2869
+ * the VECTOR extension is not loaded on that connection: `DELETE` fails with
2870
+ * "Trying to delete from an index on table CodeEmbedding but its extension is
2871
+ * not loaded", `CREATE` with the matching "insert into an index" variant,
2872
+ * `DROP TABLE` is refused while the index references it, and `SET` — even on
2873
+ * a NON-indexed property — segfaults the process outright. Probed against
2874
+ * @ladybugdb/core 0.18.2 (the lockfile-pinned version) and 0.18.0 — every
2875
+ * result identical on both (#2623).
2876
+ *
2877
+ * Dropping the index is NOT an available recovery: `CALL DROP_VECTOR_INDEX`
2878
+ * is itself a VECTOR-extension function and resolves to "Catalog exception:
2879
+ * function DROP_VECTOR_INDEX is not defined" in exactly the state it would
2880
+ * need to rescue. Loading the extension is the only in-place repair, which is
2881
+ * why this returns a verdict instead of attempting a fixup.
2882
+ *
2883
+ * `true` = embedding-row DML is safe: either VECTOR is now loaded, or the
2884
+ * table carries no index to trip over. `false` = genuinely blocked (index
2885
+ * present, extension unloadable); the analyze orchestrator answers that by
2886
+ * escalating to the wipe-and-rebuild write plan instead of failing
2887
+ * mid-writeback.
2888
+ *
2889
+ * Cheap by construction: one {@link readIndexCatalogRows} read settles the
2890
+ * common "this repo never built an embedding index" case without touching the
2891
+ * extension machinery at all, so a VECTOR-less machine is not charged a bounded
2892
+ * INSTALL attempt on every incremental analyze. (That read's own mechanics and
2893
+ * fail-closed contract are documented there, not re-explained here — #2841
2894
+ * review §5.H.)
2895
+ *
2896
+ * @param indexRows An {@link IndexCatalogSnapshot} the caller already read, so
2897
+ * one `SHOW_INDEXES` read can settle every gate in a run. FRESHNESS CONTRACT:
2898
+ * the snapshot must have been taken on THIS connection with nothing in between
2899
+ * that creates or drops an index — the gate's verdict is only as current as the
2900
+ * rows it is handed. Pass {@link INDEX_CATALOG_UNREADABLE} (what
2901
+ * {@link readIndexCatalogSnapshot} returns) when your own read failed; that
2902
+ * fails closed here WITHOUT a second read. Omit the argument entirely to have
2903
+ * the gate read the catalog itself.
2904
+ */
2905
+ export const ensureEmbeddingRowDmlSafe = async (indexRows) => {
2906
+ // Unconditional precondition (#2841 review §5.B). This check used to run on
2907
+ // every call; adding `indexRows` moved it inside `readIndexCatalogRows`, where
2908
+ // a caller-supplied snapshot skips it — so a closed DB could be answered
2909
+ // `true` where it previously threw. The verdict is only meaningful for the
2910
+ // live writable connection, so assert that before looking at the argument.
2911
+ if (!conn) {
2912
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2913
+ }
2914
+ // Catalog FIRST. The overwhelmingly common case on a repo that never enabled
2915
+ // embeddings is "no index at all", and that is provable with one local read
2916
+ // — no extension needed. Loading first would make every incremental analyze
2917
+ // on a VECTOR-less machine pay a bounded out-of-process INSTALL attempt (the
2918
+ // `auto` policy) plus an "extension unavailable" warning, for a repo that
2919
+ // can never hit this hazard.
2920
+ const rows = await resolveGateRows(indexRows);
2921
+ // Any non-HASH index on the embedding table gates DML. Keyed on index TYPE,
2922
+ // not name, so an index built under a different name still counts; the
2923
+ // implicit primary-key HASH index is engine-internal and never gates.
2924
+ const indexGatesDml = rows === undefined ||
2925
+ rows.some((row) => {
2926
+ if (indexRowTable(row) !== EMBEDDING_TABLE_NAME)
2927
+ return false;
2928
+ return indexRowType(row) !== 'HASH';
2929
+ });
2930
+ if (!indexGatesDml)
2931
+ return true;
2932
+ return await loadVectorExtension(undefined, { policy: resolveAnalyzeInstallPolicy() });
2933
+ };
2934
+ /**
2935
+ * The FTS twin of {@link ensureEmbeddingRowDmlSafe} (#2841).
2936
+ *
2937
+ * LadybugDB refuses DML against a table carrying an FTS index while the FTS
2938
+ * extension is not loaded on that connection, and it refuses it at BIND time —
2939
+ * probed against @ladybugdb/core 0.18.3, a DETACH DELETE matching ZERO rows
2940
+ * fails just as hard as one matching thousands ("Binder exception: Trying to
2941
+ * delete from an index on table File but its extension is not loaded"). Every
2942
+ * table in `FTS_INDEXES` is therefore immutable until the extension loads, and
2943
+ * there is no narrower escape: `CALL DROP_FTS_INDEX` is itself an
2944
+ * FTS-extension function ("Catalog exception: function DROP_FTS_INDEX is not
2945
+ * defined" in exactly the state that would need rescuing), and LadybugDB has
2946
+ * no SQL `DROP INDEX` at all (both spellings are Parser exceptions). Rebuilding
2947
+ * the DB file is the only way to clear the indexes without the extension.
2948
+ *
2949
+ * `true` = FTS-indexed-table DML is safe: either FTS is now loaded, or the DB
2950
+ * carries no FTS index to trip over. `false` = genuinely blocked; the analyze
2951
+ * orchestrator answers that by escalating to the wipe-and-rebuild write plan
2952
+ * instead of dying mid-writeback with an engine error that never says "FTS".
2953
+ *
2954
+ * Catalog-first for the same reason as the VECTOR twin: a repo whose index
2955
+ * never carried FTS must not pay a bounded INSTALL attempt on every
2956
+ * incremental analyze. Keyed on index TYPE, so an index left over from an
2957
+ * older `FTS_INDEXES` (different name/table set) still counts.
2958
+ *
2959
+ * @param indexRows Same contract as {@link ensureEmbeddingRowDmlSafe}'s: an
2960
+ * {@link IndexCatalogSnapshot} read on THIS connection with no index created or
2961
+ * dropped since, so both gates decide from the SAME snapshot and the catalog is
2962
+ * read once per run. {@link INDEX_CATALOG_UNREADABLE} fails closed here without
2963
+ * a second read; omitting the argument makes the gate read for itself.
2964
+ */
2965
+ export const ensureFtsRowDmlSafe = async (indexRows) => {
2966
+ // Unconditional precondition, same regression as the VECTOR twin's (#2841
2967
+ // review §5.B): a caller-supplied snapshot must not let a closed DB be
2968
+ // answered `true`.
2969
+ if (!conn) {
2970
+ throw new Error('LadybugDB not initialized. Call initLbug first.');
2971
+ }
2972
+ const rows = await resolveGateRows(indexRows);
2973
+ // LADYBUGDB-CONTRACT: the `'FTS'` index_type spelling — see {@link IndexCatalogRow}.
2974
+ //
2975
+ // Polarity (#2841 review §6.A): a row whose type cannot be read gates DML.
2976
+ // A bare `=== 'FTS'` answers `undefined === 'FTS'` → false → *no gate*, i.e.
2977
+ // it falls OPEN in the one gate whose only job is preventing an unsafe write,
2978
+ // while the VECTOR twin above falls CLOSED for the same input. Deliberately
2979
+ // NOT expressed as the twin's `!== 'HASH'`: that predicate is safe there only
2980
+ // because it is scoped to `EMBEDDING_TABLE_NAME` first, whereas this gate is
2981
+ // table-agnostic, so `!== 'HASH'` would let the HNSW vector index gate FTS
2982
+ // DML and charge every embeddings-enabled repo an FTS load it does not need.
2983
+ const indexGatesDml = rows === undefined ||
2984
+ rows.some((row) => {
2985
+ const indexType = indexRowType(row);
2986
+ // Unreadable shape ⇒ "might be FTS" ⇒ gate. Only a positively-identified
2987
+ // non-FTS index is waved through.
2988
+ return indexType === undefined || indexType === 'FTS';
2989
+ });
2990
+ if (!indexGatesDml)
2991
+ return true;
2992
+ return await loadFTSExtension(undefined, { policy: resolveAnalyzeInstallPolicy() });
2993
+ };
2478
2994
  /**
2479
2995
  * Lazy-create an FTS index, caching the fact in-process.
2480
2996
  *
@@ -2510,6 +3026,89 @@ export const ensureFTSIndex = async (tableName, indexName, properties, stemmer =
2510
3026
  throw e;
2511
3027
  }
2512
3028
  };
3029
+ /**
3030
+ * Classify a `QUERY_FTS_INDEX` failure so a genuinely-missing index (normal —
3031
+ * this table's FTS index hasn't been built yet) is distinguished from a real
3032
+ * query-time error that would otherwise look identical (#2767), and from the
3033
+ * table itself being missing (schema drift / a corrupted or partial DB — a
3034
+ * much more serious condition than an unbuilt index).
3035
+ *
3036
+ * tri-review Residual-1: this used to be a second, independently-maintained
3037
+ * classifier living in `core/search/bm25-index.ts` (re-exported from there
3038
+ * for backward compatibility), duplicating this function's job for the
3039
+ * IDENTICAL `QUERY_FTS_INDEX` cypher call. `queryFTS` below now uses this
3040
+ * same classifier for its own catch instead of a bare, unanchored
3041
+ * `.includes('does not exist')` check that could not tell "index missing"
3042
+ * from "table missing" apart, and silently swallowed both alike.
3043
+ *
3044
+ * Three real message shapes were confirmed empirically against a live
3045
+ * `CALL QUERY_FTS_INDEX(...)`:
3046
+ * `"Prepare failed: Binder exception: Table <T> doesn't have an index with
3047
+ * name <name>."` — the table exists, only its FTS index is missing (normal,
3048
+ * benign — `missing-index`) — `"Prepare failed: Binder exception: Table <T>
3049
+ * does not exist."` — the TABLE ITSELF is missing (`missing-table`) — and a
3050
+ * `Catalog exception: function QUERY_FTS_INDEX is not defined...` when the
3051
+ * FTS extension isn't loaded at all (`other`; mirrors the confirmed
3052
+ * `DROP_FTS_INDEX` shape in {@link isBenignDropFtsIndexError}'s doc comment).
3053
+ *
3054
+ * Anchored to the exception class (after stripping the optional "Prepare
3055
+ * failed: " wrapper LadybugDB adds for statement-preparation failures),
3056
+ * mirroring `isBenignDropFtsIndexError`'s START-of-message anchor: a bare
3057
+ * substring search would misclassify a genuine, differently-classed error
3058
+ * (e.g. a `Runtime exception` from the FTS parser that echoes the user's
3059
+ * own search text back into its message) as benign whenever that echoed
3060
+ * text happened to contain "does not exist" — silently dropping a real
3061
+ * error, the exact #2767 failure mode this function exists to prevent.
3062
+ */
3063
+ export const classifyFtsQueryError = (message) => {
3064
+ const PREPARE_FAILED_PREFIX = 'Prepare failed: ';
3065
+ const body = message.startsWith(PREPARE_FAILED_PREFIX)
3066
+ ? message.slice(PREPARE_FAILED_PREFIX.length)
3067
+ : message;
3068
+ if (!body.startsWith('Binder exception:') && !body.startsWith('Catalog exception:')) {
3069
+ return 'other';
3070
+ }
3071
+ if (body.includes("doesn't have an index"))
3072
+ return 'missing-index';
3073
+ if (body.includes('does not exist'))
3074
+ return 'missing-table';
3075
+ return 'other';
3076
+ };
3077
+ /**
3078
+ * Build the `QUERY_FTS_INDEX` statement shared by BOTH FTS read paths —
3079
+ * `queryFTS` below and `queryFTSViaExecutor` in `core/search/bm25-index.ts`
3080
+ * (the MCP connection-pool path). The two ran byte-identical cypher from two
3081
+ * places, so every change had to be applied twice in lockstep — the `, node.id`
3082
+ * ORDER BY tiebreak for #2787 being the latest. Lives beside
3083
+ * {@link classifyFtsQueryError}, which was already shared for exactly this call.
3084
+ */
3085
+ /**
3086
+ * DETAIL SYMBOLS DO NOT COMPETE IN TEXT SEARCH.
3087
+ *
3088
+ * `Property.isDetail` marks the keys of an anonymous literal returned from a
3089
+ * function (R3-4): real symbols, worth walking and worth an impact analysis,
3090
+ * but not concepts a text search should surface on their own. Their names are
3091
+ * ordinary words (`message`, `value`, `timestamp`) and there are many of them,
3092
+ * so without this they consume the FTS call's own LIMIT and push out the
3093
+ * CALLABLES named after the same concept — measured, `query('message')` went
3094
+ * from two processes to none on the mini-repo fixture.
3095
+ *
3096
+ * Filtered HERE rather than after the call, because that is the only place it
3097
+ * works: rows crowded out by the LIMIT never reach the caller, so no amount of
3098
+ * re-ranking downstream can recover them. (Tried, and it recovered nothing.)
3099
+ *
3100
+ * Property-only, since no other table has the column, and `IS NULL`-tolerant so
3101
+ * an index written before this column existed still answers.
3102
+ */
3103
+ const FTS_DETAIL_FILTER = `
3104
+ WITH node, score
3105
+ WHERE node.isDetail IS NULL OR node.isDetail = false`;
3106
+ export const buildFtsQueryCypher = (tableName, indexName, limit, conjunctive = false) => `
3107
+ CALL QUERY_FTS_INDEX('${tableName}', '${indexName}', $query, conjunctive := ${conjunctive})${tableName === 'Property' ? FTS_DETAIL_FILTER : ''}
3108
+ RETURN node, score
3109
+ ORDER BY score DESC, node.id
3110
+ LIMIT ${limit}
3111
+ `;
2513
3112
  /**
2514
3113
  * Query a full-text search index
2515
3114
  * @param tableName - The node table name
@@ -2523,12 +3122,7 @@ export const queryFTS = async (tableName, indexName, query, limit = 20, conjunct
2523
3122
  if (!conn) {
2524
3123
  throw new Error('LadybugDB not initialized. Call initLbug first.');
2525
3124
  }
2526
- const cypher = `
2527
- CALL QUERY_FTS_INDEX('${tableName}', '${indexName}', $query, conjunctive := ${conjunctive})
2528
- RETURN node, score
2529
- ORDER BY score DESC
2530
- LIMIT ${limit}
2531
- `;
3125
+ const cypher = buildFtsQueryCypher(tableName, indexName, limit, conjunctive);
2532
3126
  try {
2533
3127
  const rows = await executePrepared(cypher, { query });
2534
3128
  return rows.map((row) => {
@@ -2544,15 +3138,83 @@ export const queryFTS = async (tableName, indexName, query, limit = 20, conjunct
2544
3138
  });
2545
3139
  }
2546
3140
  catch (e) {
2547
- // Return empty if index doesn't exist yet
2548
- if (e.message?.includes('does not exist')) {
3141
+ // Return empty only for a genuinely-missing index — the ordinary,
3142
+ // expected case. A missing TABLE (schema drift) or any other real error
3143
+ // rethrows instead of being silently swallowed (tri-review Residual-1 /
3144
+ // NEW-6 — this used to be a bare `.includes('does not exist')` check
3145
+ // that could not tell the two apart).
3146
+ const message = e instanceof Error ? e.message : String(e);
3147
+ if (classifyFtsQueryError(message) === 'missing-index') {
2549
3148
  return [];
2550
3149
  }
2551
3150
  throw e;
2552
3151
  }
2553
3152
  };
2554
3153
  /**
2555
- * Drop an FTS index
3154
+ * True for the two benign "nothing to drop" `DROP_FTS_INDEX` failures —
3155
+ * both catalog/binder exceptions, LadybugDB's classes for "this name isn't
3156
+ * bound to anything right now" (probe-verified end-to-end through
3157
+ * `dropFTSIndex`'s real `conn.query()` path against @ladybugdb/core
3158
+ * 0.18.x): the named index was never created (`Binder exception: Table <T>
3159
+ * doesn't have an index with name <name>.`), or the FTS extension/function
3160
+ * isn't registered at all (`Catalog exception: function DROP_FTS_INDEX is
3161
+ * not defined...`). A real engine failure — e.g. the `Runtime exception:
3162
+ * FTS index '<name>' is inconsistent: ...` class from #2589 — is a
3163
+ * DIFFERENT exception class (an execution-time failure, not a catalog/bind
3164
+ * lookup miss), so this returns false for it. Anchored to the START of the
3165
+ * message (not a bare substring search): every probed LadybugDB error leads
3166
+ * with its exception class, and anchoring means a future message that merely
3167
+ * mentions "Binder exception" or "Catalog exception" further in in the body
3168
+ * of an otherwise-genuine failure can't be misclassified as benign. Pure
3169
+ * string logic so it is unit-testable without a native LadybugDB connection.
3170
+ */
3171
+ export const isBenignDropFtsIndexError = (message) => message.startsWith('Binder exception:') || message.startsWith('Catalog exception:');
3172
+ /**
3173
+ * The half of {@link isBenignDropFtsIndexError}'s catalog case that means "the
3174
+ * FTS extension is not loaded", as opposed to "this index does not exist".
3175
+ * Benign only when there is genuinely nothing to drop — see `dropFTSIndex`.
3176
+ */
3177
+ const DROP_FTS_INDEX_UNDEFINED_SIGNATURE = 'function DROP_FTS_INDEX is not defined';
3178
+ /**
3179
+ * Whether `indexName` is currently present on `tableName` in the catalog.
3180
+ *
3181
+ * `unverifiable` ⇒ "an index may be present" (#2841 review H3), and the caller
3182
+ * must treat it exactly as it treats `present`. This used to answer a bare
3183
+ * `false` there — reporting an unprovable catalog as "index absent", the one
3184
+ * meaning {@link readIndexCatalogRows} explicitly forbids — which made
3185
+ * `dropFTSIndex` swallow the very error this guard exists to raise and handed
3186
+ * the caller a drop that never happened, reproducing the silent #2841 crash one
3187
+ * DML statement later. The asymmetric cost settles it: a false positive raises a
3188
+ * loud, FTS-naming, remedy-carrying error on a run that had already lost its
3189
+ * catalog; a false negative resumes a writeback that cannot succeed.
3190
+ */
3191
+ const ftsIndexPresenceInCatalog = async (tableName, indexName) => {
3192
+ const rows = await readIndexCatalogRows();
3193
+ if (rows === undefined)
3194
+ return 'unverifiable';
3195
+ return rows.some((row) => indexRowTable(row) === tableName && indexRowName(row) === indexName)
3196
+ ? 'present'
3197
+ : 'absent';
3198
+ };
3199
+ /**
3200
+ * Drop an FTS index. Tolerates only {@link isBenignDropFtsIndexError} —
3201
+ * anything else rethrows instead of being silently masked, which previously
3202
+ * let a corrupted index persist across analyze runs undetected.
3203
+ *
3204
+ * One benign class is conditional (#2841): `Catalog exception: function
3205
+ * DROP_FTS_INDEX is not defined` says the FTS extension is not loaded, which
3206
+ * is "nothing to drop" only when the named index does not exist. When it DOES
3207
+ * exist, swallowing that error reports a drop that never happened, and the
3208
+ * next insert/delete against that table dies at bind time with an engine
3209
+ * message that never mentions FTS — the #2841 crash. So the liveness question
3210
+ * is settled with a catalog read on the ERROR path only (the healthy path
3211
+ * still costs nothing) and a live-but-undroppable index is raised loudly,
3212
+ * naming FTS and both remedies — the load-side one CLASSIFIED, never
3213
+ * hand-rolled (#2841 review §5.G).
3214
+ *
3215
+ * A catalog that cannot be read blocks identically (see
3216
+ * {@link ftsIndexPresenceInCatalog}) but is reported as an inability to verify,
3217
+ * not as an assertion that the index exists.
2556
3218
  */
2557
3219
  export const dropFTSIndex = async (tableName, indexName) => {
2558
3220
  if (!conn) {
@@ -2561,8 +3223,51 @@ export const dropFTSIndex = async (tableName, indexName) => {
2561
3223
  try {
2562
3224
  await queryAndDrain(conn, `CALL DROP_FTS_INDEX('${tableName}', '${indexName}')`);
2563
3225
  }
2564
- catch {
2565
- // Index may not exist
3226
+ catch (e) {
3227
+ const msg = e instanceof Error ? e.message : String(e);
3228
+ if (!isBenignDropFtsIndexError(msg)) {
3229
+ throw e;
3230
+ }
3231
+ const presence = msg.includes(DROP_FTS_INDEX_UNDEFINED_SIGNATURE)
3232
+ ? await ftsIndexPresenceInCatalog(tableName, indexName)
3233
+ : 'absent';
3234
+ if (presence !== 'absent') {
3235
+ // Remedy via the shared classifier, UNCONDITIONALLY and never hand-rolled
3236
+ // (#2841 review §5.G). `extension-load-error.ts` exists because "set
3237
+ // GITNEXUS_LBUG_EXTENSION_INSTALL=auto" is the WRONG advice for the
3238
+ // missing-runtime-dependency class (Windows error 126 / OpenSSL, #2374) —
3239
+ // the file is already on disk and reinstalling is a no-op. This used to
3240
+ // honour the classifier for `missing_dependency` only and hand-write the
3241
+ // other three, which handed a corrupt / wrong-platform extension file the
3242
+ // very "allow one bounded install attempt" advice #2383 removed, and
3243
+ // discarded `missingFileRemedy` / `corruptFileRemedy` outright. Every one
3244
+ // of the four kinds already carries a class-correct remedy by
3245
+ // construction, so take it as-is.
3246
+ //
3247
+ // Prefer the diagnosis cached at mark-unavailable time (#2383 F3) so the
3248
+ // extension binary is not re-inspected, falling back to a fresh structural
3249
+ // diagnosis when nothing recorded one.
3250
+ const ftsCapability = getFtsCapability();
3251
+ const { remedy } = ftsCapability?.diagnosis ?? diagnoseExtensionLoad(ftsCapability?.reason);
3252
+ // Deliberately message-only: `remedy` is generated text (fixed system paths
3253
+ // at most), and LadybugDB's own path-bearing `reason` is NEVER interpolated
3254
+ // here — the #2374/#2375 redaction contract.
3255
+ //
3256
+ // `unverifiable` blocks exactly as hard as `present`, but must not be
3257
+ // WORDED as `present`: the one reachable path into it is a run whose
3258
+ // `ensureFtsRowDmlSafe` already answered "safe" because the catalog showed
3259
+ // no FTS index, after which this later read failed — so asserting the index
3260
+ // exists would contradict what the same run just proved (#2841 cleanup
3261
+ // review).
3262
+ const lead = presence === 'present'
3263
+ ? `FTS index '${indexName}' on table ${tableName} exists but the LadybugDB FTS ` +
3264
+ 'extension is not loaded, so it cannot be dropped in this environment.'
3265
+ : `FTS index '${indexName}' on table ${tableName} could not be verified as absent — ` +
3266
+ 'the LadybugDB index catalog could not be read — and the FTS extension is not ' +
3267
+ 'loaded, so the index could not be dropped either.';
3268
+ throw new Error(`${lead} Every insert and delete against that table fails while the index is present. ` +
3269
+ `${remedy} Otherwise rebuild the index without FTS via \`gitnexus analyze --force\`.`);
3270
+ }
2566
3271
  }
2567
3272
  finally {
2568
3273
  ensuredFTSIndexes.delete(ftsIndexKey(tableName, indexName));