gitnexus 1.6.5-rc.8 → 1.6.5

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 (335) hide show
  1. package/dist/_shared/scope-resolution/finalize-algorithm.js +10 -2
  2. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  3. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +26 -0
  4. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
  5. package/dist/_shared/scope-resolution/method-dispatch-index.js +22 -3
  6. package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
  7. package/dist/_shared/scope-resolution/registries/lookup-core.js +18 -1
  8. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
  9. package/dist/_shared/scope-resolution/symbol-definition.d.ts +2 -0
  10. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  11. package/dist/cli/ai-context.d.ts +1 -0
  12. package/dist/cli/ai-context.js +70 -16
  13. package/dist/cli/analyze.d.ts +30 -2
  14. package/dist/cli/analyze.js +45 -5
  15. package/dist/cli/index.js +8 -1
  16. package/dist/cli/setup.js +18 -0
  17. package/dist/cli/wiki.d.ts +2 -0
  18. package/dist/cli/wiki.js +11 -0
  19. package/dist/core/augmentation/engine.js +24 -4
  20. package/dist/core/embeddings/http-client.js +22 -6
  21. package/dist/core/incremental/shadow-candidates.d.ts +44 -0
  22. package/dist/core/incremental/shadow-candidates.js +74 -0
  23. package/dist/core/incremental/subgraph-extract.d.ts +64 -0
  24. package/dist/core/incremental/subgraph-extract.js +111 -0
  25. package/dist/core/ingestion/call-processor.d.ts +12 -1
  26. package/dist/core/ingestion/call-processor.js +183 -47
  27. package/dist/core/ingestion/class-extractors/configs/c-cpp.js +38 -0
  28. package/dist/core/ingestion/class-extractors/generic.js +8 -0
  29. package/dist/core/ingestion/class-types.d.ts +15 -0
  30. package/dist/core/ingestion/community-processor.js +18 -0
  31. package/dist/core/ingestion/import-resolvers/standard.d.ts +5 -0
  32. package/dist/core/ingestion/import-resolvers/standard.js +31 -1
  33. package/dist/core/ingestion/import-resolvers/utils.js +4 -0
  34. package/dist/core/ingestion/languages/c-cpp.js +20 -2
  35. package/dist/core/ingestion/languages/cpp/adl.d.ts +141 -0
  36. package/dist/core/ingestion/languages/cpp/adl.js +478 -0
  37. package/dist/core/ingestion/languages/cpp/arity-metadata.d.ts +18 -0
  38. package/dist/core/ingestion/languages/cpp/arity-metadata.js +170 -0
  39. package/dist/core/ingestion/languages/cpp/arity.d.ts +17 -0
  40. package/dist/core/ingestion/languages/cpp/arity.js +31 -0
  41. package/dist/core/ingestion/languages/cpp/captures.d.ts +2 -0
  42. package/dist/core/ingestion/languages/cpp/captures.js +1131 -0
  43. package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +31 -0
  44. package/dist/core/ingestion/languages/cpp/conversion-rank.js +47 -0
  45. package/dist/core/ingestion/languages/cpp/file-local-linkage.d.ts +118 -0
  46. package/dist/core/ingestion/languages/cpp/file-local-linkage.js +266 -0
  47. package/dist/core/ingestion/languages/cpp/header-scan.d.ts +8 -0
  48. package/dist/core/ingestion/languages/cpp/header-scan.js +50 -0
  49. package/dist/core/ingestion/languages/cpp/import-decomposer.d.ts +19 -0
  50. package/dist/core/ingestion/languages/cpp/import-decomposer.js +111 -0
  51. package/dist/core/ingestion/languages/cpp/import-target.d.ts +10 -0
  52. package/dist/core/ingestion/languages/cpp/import-target.js +13 -0
  53. package/dist/core/ingestion/languages/cpp/index.d.ts +11 -0
  54. package/dist/core/ingestion/languages/cpp/index.js +11 -0
  55. package/dist/core/ingestion/languages/cpp/inline-namespaces.d.ts +67 -0
  56. package/dist/core/ingestion/languages/cpp/inline-namespaces.js +176 -0
  57. package/dist/core/ingestion/languages/cpp/interpret.d.ts +35 -0
  58. package/dist/core/ingestion/languages/cpp/interpret.js +113 -0
  59. package/dist/core/ingestion/languages/cpp/merge-bindings.d.ts +13 -0
  60. package/dist/core/ingestion/languages/cpp/merge-bindings.js +29 -0
  61. package/dist/core/ingestion/languages/cpp/query.d.ts +3 -0
  62. package/dist/core/ingestion/languages/cpp/query.js +508 -0
  63. package/dist/core/ingestion/languages/cpp/range-bindings.d.ts +20 -0
  64. package/dist/core/ingestion/languages/cpp/range-bindings.js +212 -0
  65. package/dist/core/ingestion/languages/cpp/scope-resolver.d.ts +17 -0
  66. package/dist/core/ingestion/languages/cpp/scope-resolver.js +246 -0
  67. package/dist/core/ingestion/languages/cpp/simple-hooks.d.ts +24 -0
  68. package/dist/core/ingestion/languages/cpp/simple-hooks.js +62 -0
  69. package/dist/core/ingestion/languages/cpp/two-phase-lookup.d.ts +76 -0
  70. package/dist/core/ingestion/languages/cpp/two-phase-lookup.js +195 -0
  71. package/dist/core/ingestion/languages/java/arity-metadata.d.ts +18 -0
  72. package/dist/core/ingestion/languages/java/arity-metadata.js +40 -0
  73. package/dist/core/ingestion/languages/java/arity.d.ts +10 -0
  74. package/dist/core/ingestion/languages/java/arity.js +24 -0
  75. package/dist/core/ingestion/languages/java/cache-stats.d.ts +15 -0
  76. package/dist/core/ingestion/languages/java/cache-stats.js +26 -0
  77. package/dist/core/ingestion/languages/java/captures.d.ts +17 -0
  78. package/dist/core/ingestion/languages/java/captures.js +187 -0
  79. package/dist/core/ingestion/languages/java/import-decomposer.d.ts +18 -0
  80. package/dist/core/ingestion/languages/java/import-decomposer.js +85 -0
  81. package/dist/core/ingestion/languages/java/import-target.d.ts +17 -0
  82. package/dist/core/ingestion/languages/java/import-target.js +100 -0
  83. package/dist/core/ingestion/languages/java/index.d.ts +29 -0
  84. package/dist/core/ingestion/languages/java/index.js +29 -0
  85. package/dist/core/ingestion/languages/java/interpret.d.ts +13 -0
  86. package/dist/core/ingestion/languages/java/interpret.js +131 -0
  87. package/dist/core/ingestion/languages/java/merge-bindings.d.ts +12 -0
  88. package/dist/core/ingestion/languages/java/merge-bindings.js +40 -0
  89. package/dist/core/ingestion/languages/java/query.d.ts +30 -0
  90. package/dist/core/ingestion/languages/java/query.js +192 -0
  91. package/dist/core/ingestion/languages/java/receiver-binding.d.ts +11 -0
  92. package/dist/core/ingestion/languages/java/receiver-binding.js +95 -0
  93. package/dist/core/ingestion/languages/java/scope-resolver.d.ts +50 -0
  94. package/dist/core/ingestion/languages/java/scope-resolver.js +74 -0
  95. package/dist/core/ingestion/languages/java/simple-hooks.d.ts +13 -0
  96. package/dist/core/ingestion/languages/java/simple-hooks.js +34 -0
  97. package/dist/core/ingestion/languages/java.js +11 -0
  98. package/dist/core/ingestion/languages/php/arity-metadata.d.ts +28 -0
  99. package/dist/core/ingestion/languages/php/arity-metadata.js +63 -0
  100. package/dist/core/ingestion/languages/php/arity.d.ts +25 -0
  101. package/dist/core/ingestion/languages/php/arity.js +40 -0
  102. package/dist/core/ingestion/languages/php/cache-stats.d.ts +15 -0
  103. package/dist/core/ingestion/languages/php/cache-stats.js +26 -0
  104. package/dist/core/ingestion/languages/php/captures.d.ts +34 -0
  105. package/dist/core/ingestion/languages/php/captures.js +739 -0
  106. package/dist/core/ingestion/languages/php/import-decomposer.d.ts +28 -0
  107. package/dist/core/ingestion/languages/php/import-decomposer.js +265 -0
  108. package/dist/core/ingestion/languages/php/import-target.d.ts +47 -0
  109. package/dist/core/ingestion/languages/php/import-target.js +100 -0
  110. package/dist/core/ingestion/languages/php/index.d.ts +68 -0
  111. package/dist/core/ingestion/languages/php/index.js +72 -0
  112. package/dist/core/ingestion/languages/php/interpret.d.ts +36 -0
  113. package/dist/core/ingestion/languages/php/interpret.js +241 -0
  114. package/dist/core/ingestion/languages/php/merge-bindings.d.ts +19 -0
  115. package/dist/core/ingestion/languages/php/merge-bindings.js +47 -0
  116. package/dist/core/ingestion/languages/php/namespace-siblings.d.ts +51 -0
  117. package/dist/core/ingestion/languages/php/namespace-siblings.js +288 -0
  118. package/dist/core/ingestion/languages/php/query.d.ts +32 -0
  119. package/dist/core/ingestion/languages/php/query.js +326 -0
  120. package/dist/core/ingestion/languages/php/receiver-binding.d.ts +36 -0
  121. package/dist/core/ingestion/languages/php/receiver-binding.js +128 -0
  122. package/dist/core/ingestion/languages/php/scope-resolver.d.ts +23 -0
  123. package/dist/core/ingestion/languages/php/scope-resolver.js +358 -0
  124. package/dist/core/ingestion/languages/php/simple-hooks.d.ts +42 -0
  125. package/dist/core/ingestion/languages/php/simple-hooks.js +111 -0
  126. package/dist/core/ingestion/languages/php.d.ts +0 -7
  127. package/dist/core/ingestion/languages/php.js +15 -0
  128. package/dist/core/ingestion/markdown-processor.js +6 -1
  129. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  130. package/dist/core/ingestion/model/symbol-table.js +3 -0
  131. package/dist/core/ingestion/parsing-processor.d.ts +22 -2
  132. package/dist/core/ingestion/parsing-processor.js +123 -50
  133. package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +54 -2
  134. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +5 -0
  135. package/dist/core/ingestion/pipeline-phases/parse-impl.js +119 -11
  136. package/dist/core/ingestion/pipeline-phases/parse.d.ts +14 -0
  137. package/dist/core/ingestion/pipeline.d.ts +13 -0
  138. package/dist/core/ingestion/registry-primary-flag.js +2 -0
  139. package/dist/core/ingestion/scope-extractor.js +4 -0
  140. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +167 -0
  141. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +3 -0
  142. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
  143. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +12 -0
  144. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +1 -1
  145. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +12 -2
  146. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +16 -0
  147. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +36 -0
  148. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +204 -13
  149. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +1 -1
  150. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +55 -4
  151. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +186 -5
  152. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +10 -1
  153. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +207 -9
  154. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +13 -1
  155. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +6 -0
  156. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +17 -0
  157. package/dist/core/ingestion/scope-resolution/pipeline/run.js +86 -8
  158. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +30 -0
  159. package/dist/core/ingestion/scope-resolution/scope/walkers.js +90 -8
  160. package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
  161. package/dist/core/ingestion/tree-sitter-queries.js +23 -0
  162. package/dist/core/ingestion/utils/ast-helpers.js +6 -1
  163. package/dist/core/ingestion/utils/template-arguments.d.ts +11 -0
  164. package/dist/core/ingestion/utils/template-arguments.js +65 -0
  165. package/dist/core/ingestion/workers/parse-worker.d.ts +3 -0
  166. package/dist/core/ingestion/workers/parse-worker.js +42 -1
  167. package/dist/core/lbug/lbug-adapter.d.ts +39 -0
  168. package/dist/core/lbug/lbug-adapter.js +348 -48
  169. package/dist/core/run-analyze.d.ts +2 -0
  170. package/dist/core/run-analyze.js +323 -29
  171. package/dist/core/search/hybrid-search.d.ts +3 -0
  172. package/dist/core/search/hybrid-search.js +23 -6
  173. package/dist/core/wiki/generator.js +4 -3
  174. package/dist/core/wiki/html-viewer.js +2 -1
  175. package/dist/core/wiki/llm-client.d.ts +4 -0
  176. package/dist/core/wiki/llm-client.js +3 -3
  177. package/dist/core/wiki/mermaid-sanitizer.d.ts +2 -0
  178. package/dist/core/wiki/mermaid-sanitizer.js +100 -0
  179. package/dist/mcp/local/local-backend.js +20 -7
  180. package/dist/storage/file-hash.d.ts +47 -0
  181. package/dist/storage/file-hash.js +86 -0
  182. package/dist/storage/parse-cache.d.ts +73 -0
  183. package/dist/storage/parse-cache.js +253 -0
  184. package/dist/storage/repo-manager.d.ts +41 -1
  185. package/dist/storage/repo-manager.js +61 -6
  186. package/hooks/claude/gitnexus-hook.cjs +42 -4
  187. package/hooks/claude/hook-db-lock-probe.cjs +238 -0
  188. package/hooks/claude/hook-lock.cjs +119 -0
  189. package/hooks/claude/win-rm-list-json.ps1 +76 -0
  190. package/package.json +1 -1
  191. package/scripts/build.js +6 -2
  192. package/vendor/node_modules/node-addon-api/node_addon_api.target.mk +16 -16
  193. package/vendor/node_modules/node-addon-api/node_addon_api_except.target.mk +16 -16
  194. package/vendor/node_modules/node-addon-api/node_addon_api_except_all.target.mk +16 -16
  195. package/vendor/node_modules/node-addon-api/node_addon_api_maybe.target.mk +16 -16
  196. package/web/assets/{agent-BpHQleHE.js → agent-D2kLt6Dl.js} +91 -91
  197. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +1 -0
  198. package/web/assets/architectureDiagram-UL44E2DR-DvIkEGkp.js +36 -0
  199. package/web/assets/blockDiagram-7IZFK4PR-B4niSLvR.js +132 -0
  200. package/web/assets/{c4Diagram-DFAF54RM-DeJV1rCn.js → c4Diagram-Y2BXMSZH-DIzEXalR.js} +6 -6
  201. package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +4 -0
  202. package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +1 -0
  203. package/web/assets/{chunk-7RZVMHOQ-BdIU-RGO.js → chunk-3SSMPTDK-BzGeeheI.js} +1 -1
  204. package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +159 -0
  205. package/web/assets/chunk-5IMINLNL-CnTLsXIV.js +1 -0
  206. package/web/assets/chunk-5VCL7Z4A-w1BCx98Z.js +1 -0
  207. package/web/assets/chunk-6764PJDD-ChwMM2z4.js +1 -0
  208. package/web/assets/chunk-67TQ5CYL-CChC2cAv.js +124 -0
  209. package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +10 -0
  210. package/web/assets/chunk-7W6UQGC5-CgFyfZ6k.js +1 -0
  211. package/web/assets/chunk-AQ6EADP3-CCFje6lL.js +1 -0
  212. package/web/assets/{chunk-KSICW3F5-C2tZmXwv.js → chunk-AZZRMDJM-5E9ARdDQ.js} +1 -1
  213. package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +1 -0
  214. package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +1 -0
  215. package/web/assets/chunk-INKRHTLW-ELpfuk9h.js +70 -0
  216. package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +1 -0
  217. package/web/assets/{chunk-AEOMTBSW-D7qjBMHW.js → chunk-JQRUD6KW-Dcq_Qnyy.js} +1 -1
  218. package/web/assets/chunk-KGFNY3KK-W-VR57xb.js +62 -0
  219. package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +1 -0
  220. package/web/assets/chunk-KNLZD3CH-BuZBOcso.js +1 -0
  221. package/web/assets/chunk-KRXBNO2N-DH2NPM7I.js +1 -0
  222. package/web/assets/chunk-LCXTWHL2-CqJ0oo1x.js +231 -0
  223. package/web/assets/chunk-LII3EMHJ-3X33tCpU.js +1 -0
  224. package/web/assets/chunk-QA3QBVWF-D5Ha3GgB.js +2 -0
  225. package/web/assets/chunk-RERM46MO-DQNbXtfw.js +1 -0
  226. package/web/assets/chunk-RG4AUYOV-D0M6vdq9.js +206 -0
  227. package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +1 -0
  228. package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +1 -0
  229. package/web/assets/chunk-T2UQINTJ-kGYG7ppP.js +1 -0
  230. package/web/assets/chunk-T5OCTHI4--9wWpVws.js +1 -0
  231. package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +1 -0
  232. package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +1 -0
  233. package/web/assets/chunk-UY5QBCOK-Du-c56gs.js +1 -0
  234. package/web/assets/chunk-W44A43WB-lrCpuupA.js +1 -0
  235. package/web/assets/chunk-ZXARS5L4-CSZRiyOf.js +1 -0
  236. package/web/assets/classDiagram-KGZ6W3CR-BB-saqC1.js +1 -0
  237. package/web/assets/classDiagram-v2-72OJOZXJ-Bp2bAlS5.js +1 -0
  238. package/web/assets/cose-bilkent-UX7MHV2Q-BHv2bbtl.js +1 -0
  239. package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +4 -0
  240. package/web/assets/diagram-3NCE3AQN-BUxnDPOY.js +43 -0
  241. package/web/assets/diagram-GF46GFSD-BJTyqqvI.js +24 -0
  242. package/web/assets/diagram-HNR7UZ2L-fUWreZzx.js +3 -0
  243. package/web/assets/diagram-QXG6HAR7-DilMHamP.js +24 -0
  244. package/web/assets/diagram-WEQXMOUZ-8KUrarg1.js +10 -0
  245. package/web/assets/erDiagram-L5TCEMPS-Doj9mSiW.js +85 -0
  246. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +1 -0
  247. package/web/assets/{flowDiagram-OTCZ4VVT-aAZw4c_M.js → flowDiagram-H6V6AXG4-BOSR-i3p.js} +12 -12
  248. package/web/assets/ganttDiagram-JCBTUEKG-BHQssTz6.js +292 -0
  249. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +1 -0
  250. package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +106 -0
  251. package/web/assets/index-j4-NuleT.js +623 -0
  252. package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +1 -0
  253. package/web/assets/infoDiagram-3YFTVSEB-Ce_TvtaV.js +2 -0
  254. package/web/assets/{ishikawaDiagram-YMYX4NHK-BF8AtDaj.js → ishikawaDiagram-BNXS4ZKH-4MXIDCSF.js} +6 -6
  255. package/web/assets/journeyDiagram-M6C3CM3L-DNPYCpvq.js +139 -0
  256. package/web/assets/kanban-definition-75IXJCU3-yYdfm8Kk.js +89 -0
  257. package/web/assets/{katex-GD7MH7QM-CJiOjBBJ.js → katex-K3KEBU37-CrVLz8l7.js} +1 -1
  258. package/web/assets/mindmap-definition-2TDM6QVE-DFNEKM77.js +96 -0
  259. package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +1 -0
  260. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +1 -0
  261. package/web/assets/pieDiagram-CU6KROY3-iZAqpvhd.js +30 -0
  262. package/web/assets/quadrantDiagram-VICAPDV7-bAm6Hi_5.js +7 -0
  263. package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +1 -0
  264. package/web/assets/requirementDiagram-JXO7QTGE-CtDijhnf.js +84 -0
  265. package/web/assets/sankeyDiagram-URQDO5SZ-LFJz53u5.js +40 -0
  266. package/web/assets/sequenceDiagram-VS2MUI6T-BdjvyCHM.js +162 -0
  267. package/web/assets/stateDiagram-7D4R322I-e5odN9fG.js +1 -0
  268. package/web/assets/stateDiagram-v2-36443NZ5-vPsvjR2D.js +1 -0
  269. package/web/assets/timeline-definition-O6YCAMPW-DPAksNQp.js +120 -0
  270. package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +1 -0
  271. package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +1 -0
  272. package/web/assets/vennDiagram-MWXL3ELB-Dtlfdt7J.js +34 -0
  273. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +1 -0
  274. package/web/assets/wardleyDiagram-CUQ6CDDI-B23uRMUC.js +78 -0
  275. package/web/assets/{xychartDiagram-ZHJ5623Y-DZ7fP1_3.js → xychartDiagram-N2JHSOCM-DxwHeksW.js} +6 -6
  276. package/web/index.html +13 -2
  277. package/web/assets/architecture-YZFGNWBL-S5CXDPWN-BUzkWgDl.js +0 -1
  278. package/web/assets/architectureDiagram-EMZXCZ2Q-CXbzok1G.js +0 -36
  279. package/web/assets/blockDiagram-IGV67L2C-DA1QJ4ZB.js +0 -132
  280. package/web/assets/chunk-3GS5O3IE-B8hpnia8.js +0 -231
  281. package/web/assets/chunk-3YCYZ6SJ-B-BVu21C.js +0 -1
  282. package/web/assets/chunk-6NTNNK5N-DW-jYSDr.js +0 -1
  283. package/web/assets/chunk-A34GCYZU-BbZl-IKy.js +0 -1
  284. package/web/assets/chunk-DJ7UZH7F-B5tMfF87.js +0 -1
  285. package/web/assets/chunk-DKKBVRCY-Djm-6K_e.js +0 -4
  286. package/web/assets/chunk-DU5LTGQ6-QPoynYcp.js +0 -1
  287. package/web/assets/chunk-FXACKDTF-jieSmbe2.js +0 -159
  288. package/web/assets/chunk-H3VCZNTA-8EMHZ3SV.js +0 -1
  289. package/web/assets/chunk-HN6EAY2L-CgE_agsD.js +0 -1
  290. package/web/assets/chunk-O5ABG6QK-DEZV1O19.js +0 -1
  291. package/web/assets/chunk-PK6DOVAG-BXv0JnsJ.js +0 -206
  292. package/web/assets/chunk-RNJOYNJ4-BiBkF9dN.js +0 -1
  293. package/web/assets/chunk-RWUO3TPN-DTWzAhQ1.js +0 -1
  294. package/web/assets/chunk-TBF5ZNIQ-BSl6s9lT.js +0 -1
  295. package/web/assets/chunk-TU3PZOEN-DE5Qhc0N.js +0 -1
  296. package/web/assets/chunk-TYMNRAUI-D1NDLIMG.js +0 -1
  297. package/web/assets/chunk-VELTKBKT-C9dVN39o.js +0 -1
  298. package/web/assets/chunk-W7ZLLLMY-Bu-cdLzs.js +0 -1
  299. package/web/assets/chunk-WSB5WSVC-Dor71SOz.js +0 -1
  300. package/web/assets/chunk-XGPFEOL4-CUonLRle.js +0 -1
  301. package/web/assets/classDiagram-PPOCWD7C-Cdd6UdVv.js +0 -1
  302. package/web/assets/classDiagram-v2-23LJLIIU-DNhxY2Ib.js +0 -1
  303. package/web/assets/cose-bilkent-PNC4W37J-DdZq7Qz_.js +0 -1
  304. package/web/assets/dagre-E77IOHMT-Cfj_6e17.js +0 -4
  305. package/web/assets/diagram-H7BISOXX-BNvpsrBv.js +0 -43
  306. package/web/assets/diagram-JC5VWROH-Cn0rtDbJ.js +0 -24
  307. package/web/assets/diagram-LXUTUG65-CHfoVKNm.js +0 -10
  308. package/web/assets/diagram-WEHSV5V5-BLxHksnu.js +0 -24
  309. package/web/assets/erDiagram-GCSMX5X6-DfdN6iqF.js +0 -85
  310. package/web/assets/ganttDiagram-MUNLMDZQ-BEfC_7Or.js +0 -292
  311. package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-B6ZeRLP0.js +0 -1
  312. package/web/assets/gitGraphDiagram-3HKGZ4G3-CG0kC8Q5.js +0 -106
  313. package/web/assets/index-3NUCUW2s.js +0 -886
  314. package/web/assets/info-OMHHGYJF-BF2H5H6G-BIOsZy0W.js +0 -1
  315. package/web/assets/infoDiagram-MN7RKWGX-DUoMH-pq.js +0 -2
  316. package/web/assets/journeyDiagram-SO5T7YLQ-BW8oKa3N.js +0 -139
  317. package/web/assets/kanban-definition-LJHFXRCJ-DzbOsR-2.js +0 -89
  318. package/web/assets/mindmap-definition-2EUWGEK5-BIXGQALq.js +0 -96
  319. package/web/assets/packet-4T2RLAQJ-EV4IVRXR-Bs7ysNMU.js +0 -1
  320. package/web/assets/pie-ZZUOXDRM-N23DN5KN-Csi8nnbH.js +0 -1
  321. package/web/assets/pieDiagram-3IATQBI2-QY-u7P6a.js +0 -30
  322. package/web/assets/quadrantDiagram-E256RVCF-Di4aNiU8.js +0 -7
  323. package/web/assets/radar-PYXPWWZC-P6TP7ZYP-DJlyXh2-.js +0 -1
  324. package/web/assets/requirementDiagram-M5DCFWZL-CzUUrhQD.js +0 -84
  325. package/web/assets/sankeyDiagram-L3NBLAOT-D2fbMiIU.js +0 -10
  326. package/web/assets/sequenceDiagram-ZOUHS735-BNdkqguh.js +0 -157
  327. package/web/assets/stateDiagram-MLPALWAM-51_Q2w2L.js +0 -1
  328. package/web/assets/stateDiagram-v2-B5LQ5ZB2-8nHs2aNE.js +0 -1
  329. package/web/assets/timeline-definition-5SPVSISX-DkMiGj4Z.js +0 -120
  330. package/web/assets/treeView-SZITEDCU-5DXDK3XO-Cy6P0_CZ.js +0 -1
  331. package/web/assets/treemap-W4RFUUIX-WYLRDWKO-C_pp1R7A.js +0 -1
  332. package/web/assets/vennDiagram-IE5QUKF5-BCxMmf9q.js +0 -34
  333. package/web/assets/wardley-RL74JXVD-BCRCBASE-CMStJhpn.js +0 -1
  334. package/web/assets/wardleyDiagram-XU3VSMPF-ijlLF77s.js +0 -20
  335. /package/web/assets/{context-builder-CqQNhRj1.js → context-builder-_HS0v2ma.js} +0 -0
@@ -10,10 +10,15 @@
10
10
  */
11
11
  import path from 'path';
12
12
  import fs from 'fs/promises';
13
+ import { execFileSync } from 'child_process';
13
14
  import { runPipelineFromRepo } from './ingestion/pipeline.js';
14
- import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, loadCachedEmbeddings, } from './lbug/lbug-adapter.js';
15
+ import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, loadCachedEmbeddings, deleteNodesForFile, deleteAllCommunitiesAndProcesses, queryImporters, } from './lbug/lbug-adapter.js';
15
16
  import { createSearchFTSIndexes } from './search/fts-indexes.js';
16
- import { getStoragePaths, saveMeta, loadMeta, ensureGitNexusIgnored, registerRepo, cleanupOldKuzuFiles, } from '../storage/repo-manager.js';
17
+ import { getStoragePaths, saveMeta, loadMeta, ensureGitNexusIgnored, registerRepo, cleanupOldKuzuFiles, INCREMENTAL_SCHEMA_VERSION, } from '../storage/repo-manager.js';
18
+ import { computeFileHashes, diffFileHashes } from '../storage/file-hash.js';
19
+ import { extractChangedSubgraph, computeEffectiveWriteSet, } from './incremental/subgraph-extract.js';
20
+ import { shadowCandidatesFor } from './incremental/shadow-candidates.js';
21
+ import { loadParseCache, saveParseCache, pruneCache } from '../storage/parse-cache.js';
17
22
  import { getCurrentCommit, getRemoteUrl, hasGitDir, getInferredRepoName, resolveRepoIdentityRoot, } from '../storage/git.js';
18
23
  import { generateAIContextFiles } from '../cli/ai-context.js';
19
24
  import { EMBEDDING_TABLE_NAME } from './lbug/schema.js';
@@ -63,22 +68,74 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
63
68
  const repoHasGit = hasGitDir(repoPath);
64
69
  const currentCommit = repoHasGit ? getCurrentCommit(repoPath) : '';
65
70
  const existingMeta = await loadMeta(storagePath);
71
+ // ── Crash recovery: dirty flag forces full rebuild ────────────────
72
+ // If the previous incremental run set incrementalInProgress and didn't
73
+ // clear it, the on-disk index may be in a half-state. Cheapest path
74
+ // back to a known-good index is to wipe + rebuild from scratch.
75
+ if (existingMeta?.incrementalInProgress) {
76
+ log('Previous incremental run did not complete cleanly (incrementalInProgress flag set); ' +
77
+ 'forcing full rebuild to restore a known-good index.');
78
+ options = { ...options, force: true };
79
+ // Reload meta after clearing the flag in-memory; we still want fileHashes
80
+ // for the post-rebuild meta carry-over, but force=true ensures the
81
+ // rebuild path executes.
82
+ }
66
83
  // ── Early-return: already up to date ──────────────────────────────
67
84
  if (existingMeta && !options.force && existingMeta.lastCommit === currentCommit) {
68
85
  // Non-git folders have currentCommit = '' — always rebuild since we can't detect changes
69
86
  if (currentCommit !== '') {
70
- await ensureGitNexusIgnored(repoPath);
71
- return {
72
- // `resolveRepoIdentityRoot` collapses worktree roots to the
73
- // canonical repo basename (#1259) but leaves arbitrary subdirs
74
- // and `--skip-git` paths unchanged (#1232/#1233 intent preserved).
75
- repoName: options.registryName ??
76
- getInferredRepoName(repoPath) ??
77
- path.basename(resolveRepoIdentityRoot(repoPath)),
78
- repoPath,
79
- stats: existingMeta.stats ?? {},
80
- alreadyUpToDate: true,
81
- };
87
+ // For git repos, even if HEAD matches lastCommit, the working tree
88
+ // may have uncommitted changes. Only short-circuit when the working
89
+ // tree is also clean — otherwise fall through to the incremental
90
+ // path which will hash-diff and update only changed files.
91
+ //
92
+ // We exclude paths that GitNexus itself writes during analyze:
93
+ // .gitnexus/ — db / parse cache / meta.json
94
+ // .claude/, .cursor/ — auto-generated agent skill files
95
+ // AGENTS.md, CLAUDE.md — auto-updated stats blocks
96
+ // Counting them as dirty would perpetually defeat the up-to-date
97
+ // fast path because the previous analyze just wrote them
98
+ // (regression vs PR #1233 behavior).
99
+ const dirty = (() => {
100
+ try {
101
+ const out = execFileSync('git', [
102
+ 'status',
103
+ '--porcelain',
104
+ '--',
105
+ '.',
106
+ ':(exclude).gitnexus',
107
+ ':(exclude).gitnexus/**',
108
+ ':(exclude).claude',
109
+ ':(exclude).claude/**',
110
+ ':(exclude).cursor',
111
+ ':(exclude).cursor/**',
112
+ ':(exclude)AGENTS.md',
113
+ ':(exclude)CLAUDE.md',
114
+ ], {
115
+ cwd: repoPath,
116
+ stdio: ['ignore', 'pipe', 'ignore'],
117
+ encoding: 'utf8',
118
+ });
119
+ return out.trim().length > 0;
120
+ }
121
+ catch {
122
+ return true; // conservative on git failure
123
+ }
124
+ })();
125
+ if (!dirty) {
126
+ await ensureGitNexusIgnored(repoPath);
127
+ return {
128
+ // `resolveRepoIdentityRoot` collapses worktree roots to the
129
+ // canonical repo basename (#1259) but leaves arbitrary subdirs
130
+ // and `--skip-git` paths unchanged (#1232/#1233 intent preserved).
131
+ repoName: options.registryName ??
132
+ getInferredRepoName(repoPath) ??
133
+ path.basename(resolveRepoIdentityRoot(repoPath)),
134
+ repoPath,
135
+ stats: existingMeta.stats ?? {},
136
+ alreadyUpToDate: true,
137
+ };
138
+ }
82
139
  }
83
140
  }
84
141
  // ── Cache embeddings from existing index before rebuild ────────────
@@ -114,6 +171,14 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
114
171
  `Pass --embeddings to also generate embeddings for new/changed nodes, ` +
115
172
  `or --drop-embeddings to wipe them.`);
116
173
  }
174
+ // We *always* load the embedding cache when one is requested (regardless
175
+ // of the predicted `willTryIncremental`). The post-pipeline branch may
176
+ // disagree with the prediction (e.g. when the pipeline produces zero
177
+ // File nodes, `isIncremental` flips false and the full-rebuild path
178
+ // wipes the DB) — loading unconditionally is cheap insurance against
179
+ // silently dropping embeddings on a mispredicted run. The re-insert
180
+ // step gates itself on the actual `isIncremental` value to avoid
181
+ // PK-conflicts when the incremental writeback path keeps the rows.
117
182
  if (shouldLoadCache && existingMeta) {
118
183
  try {
119
184
  progress('embeddings', 0, 'Caching embeddings...');
@@ -140,23 +205,77 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
140
205
  }
141
206
  }
142
207
  }
208
+ // ── Load incremental parse cache ──────────────────────────────────
209
+ // Content-addressed: safe to reuse across `--force` runs (chunks whose
210
+ // file contents haven't changed produce identical worker output).
211
+ // Loaded into a single ParseCache object that the pipeline mutates
212
+ // in-place (cache hits leave entries unchanged; misses add new ones).
213
+ const parseCache = await loadParseCache(storagePath);
143
214
  // ── Phase 1: Full Pipeline (0–60%) ────────────────────────────────
144
215
  const pipelineResult = await runPipelineFromRepo(repoPath, (p) => {
145
216
  const phaseLabel = PHASE_LABELS[p.phase] || p.phase;
146
217
  const scaled = Math.round(p.percent * 0.6);
147
- const message = p.detail ? `${p.message || phaseLabel} (${p.detail})` : p.message || phaseLabel;
218
+ const message = p.detail
219
+ ? `${p.message || phaseLabel} (${p.detail})`
220
+ : p.message || phaseLabel;
148
221
  progress(p.phase, scaled, message);
149
- });
222
+ }, { parseCache });
150
223
  // ── Phase 2: LadybugDB (60–85%) ──────────────────────────────────
151
224
  progress('lbug', 60, 'Loading into LadybugDB...');
152
- await closeLbug();
153
- const lbugFiles = [lbugPath, `${lbugPath}.wal`, `${lbugPath}.lock`];
154
- for (const f of lbugFiles) {
155
- try {
156
- await fs.rm(f, { recursive: true, force: true });
225
+ // Compute current per-file content hashes from the pipeline's File nodes.
226
+ // Used both to drive the incremental DB writeback (when eligible) and to
227
+ // populate meta.json.fileHashes for the next run.
228
+ const allFilePaths = [];
229
+ pipelineResult.graph.forEachNode((n) => {
230
+ if (n.label === 'File') {
231
+ const fp = n.properties?.filePath;
232
+ if (fp)
233
+ allFilePaths.push(fp);
157
234
  }
158
- catch {
159
- /* swallow */
235
+ });
236
+ const newFileHashes = await computeFileHashes(repoPath, allFilePaths);
237
+ // Decide incremental vs full at THIS point (post-pipeline, pre-DB).
238
+ // All eligibility conditions are checked here against the actual
239
+ // pipeline output — no separate pre-pipeline prediction to desync from
240
+ // (Bugbot review on PR #1479: a prediction that flipped post-pipeline
241
+ // could skip the embedding cache load and then take the full-rebuild
242
+ // path, silently losing embeddings).
243
+ const isIncremental = !options.force &&
244
+ !!existingMeta &&
245
+ existingMeta.schemaVersion === INCREMENTAL_SCHEMA_VERSION &&
246
+ !!existingMeta.fileHashes &&
247
+ Object.keys(existingMeta.fileHashes).length > 0 &&
248
+ repoHasGit &&
249
+ allFilePaths.length > 0;
250
+ const hashDiff = isIncremental
251
+ ? diffFileHashes(newFileHashes, existingMeta.fileHashes)
252
+ : undefined;
253
+ if (isIncremental && hashDiff) {
254
+ log(`Incremental: changed=${hashDiff.changed.length}, ` +
255
+ `added=${hashDiff.added.length}, ` +
256
+ `deleted=${hashDiff.deleted.length} ` +
257
+ `(skipping wipe + ${allFilePaths.length - hashDiff.toWrite.length} unchanged file rows preserved)`);
258
+ // Set the dirty flag BEFORE any destructive DB mutation. Cleared on
259
+ // success at the meta-save step.
260
+ await saveMeta(storagePath, {
261
+ ...existingMeta,
262
+ incrementalInProgress: {
263
+ startedAt: Date.now(),
264
+ toWriteCount: hashDiff.toWrite.length,
265
+ },
266
+ });
267
+ }
268
+ else {
269
+ // Full rebuild path: wipe DB files first.
270
+ await closeLbug();
271
+ const lbugFiles = [lbugPath, `${lbugPath}.wal`, `${lbugPath}.lock`];
272
+ for (const f of lbugFiles) {
273
+ try {
274
+ await fs.rm(f, { recursive: true, force: true });
275
+ }
276
+ catch {
277
+ /* swallow */
278
+ }
160
279
  }
161
280
  }
162
281
  await initLbug(lbugPath);
@@ -165,16 +284,158 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
165
284
  // is called even if an error occurs — the module-level singleton DB handle
166
285
  // must be released to avoid blocking subsequent invocations.
167
286
  let lbugMsgCount = 0;
168
- await loadGraphToLbug(pipelineResult.graph, pipelineResult.repoPath, storagePath, (msg) => {
169
- lbugMsgCount++;
170
- const pct = Math.min(84, 60 + Math.round((lbugMsgCount / (lbugMsgCount + 10)) * 24));
171
- progress('lbug', pct, msg);
172
- });
287
+ if (isIncremental && hashDiff) {
288
+ // ── Incremental DB writeback ───────────────────────────────────
289
+ // 0. Expand the writable set with transitive importers of
290
+ // changed/deleted files (bounded BFS).
291
+ //
292
+ // Reason (Bugbot/Claude review on PR #1479): when a barrel /
293
+ // re-export file C changes, cross-file resolution may update
294
+ // CALLS edges between two unchanged files A and B (A imports
295
+ // from C, C re-exports something from B). Those refined edges
296
+ // live in `ctx.graph` but would be excluded from the subgraph
297
+ // if neither endpoint is in the changed set. To catch this,
298
+ // files that imported (directly OR transitively, through
299
+ // other unchanged intermediaries) any changed file get pulled
300
+ // into the writable set so their rows are deleted + rewritten
301
+ // against the refined edges.
302
+ //
303
+ // BFS bound: MAX_IMPORTER_BFS_DEPTH. Practically sized to
304
+ // catch nested barrel chains (e.g. `index.ts → submodule/index.ts
305
+ // → submodule/impl.ts`) without ballooning into a near-full-
306
+ // rebuild on monorepos with deep re-export pyramids. Beyond
307
+ // this depth, the "incremental ≡ full-rebuild" invariant is
308
+ // self-acknowledged as best-effort; `--force` remains the
309
+ // escape hatch documented in GUARDRAILS.md.
310
+ //
311
+ // `queryImporters` reads `IMPORTS` from the pre-pipeline DB
312
+ // state, so the result is "files that USED TO import the
313
+ // target" — exactly the set whose previously-stored edges may
314
+ // no longer match what cross-file resolution produces this run.
315
+ const MAX_IMPORTER_BFS_DEPTH = 4;
316
+ const writableFiles = new Set(hashDiff.toWrite);
317
+ const directlyChangedCount = writableFiles.size;
318
+ // Shadow-seed: for ADDED files, queryImporters returns 0 (the new
319
+ // file has no IMPORTS rows in the pre-pipeline DB yet). But pre-
320
+ // existing unchanged files may have IMPORTS edges whose module-
321
+ // resolution claim the newcomer can steal under standard JS/TS
322
+ // resolution (Bugbot review on PR #1479). For each added file we
323
+ // derive the shadow candidates and, if the candidate was a known
324
+ // file in the prior meta, seed it into the BFS frontier so its
325
+ // importers — surfaced via queryImporters — get their CALLS edges
326
+ // re-resolved against the new file. See shadow-candidates.ts for
327
+ // the full pattern catalogue.
328
+ const priorFileSet = new Set(existingMeta?.fileHashes ? Object.keys(existingMeta.fileHashes) : []);
329
+ const shadowSeed = [];
330
+ for (const added of hashDiff.added) {
331
+ for (const cand of shadowCandidatesFor(added)) {
332
+ if (priorFileSet.has(cand) && !writableFiles.has(cand)) {
333
+ shadowSeed.push(cand);
334
+ }
335
+ }
336
+ }
337
+ {
338
+ let frontier = [...hashDiff.toWrite, ...hashDiff.deleted, ...shadowSeed];
339
+ for (let depth = 0; depth < MAX_IMPORTER_BFS_DEPTH && frontier.length > 0; depth++) {
340
+ const nextFrontier = [];
341
+ for (const f of frontier) {
342
+ try {
343
+ const importers = await queryImporters(f);
344
+ for (const i of importers) {
345
+ if (!writableFiles.has(i)) {
346
+ writableFiles.add(i);
347
+ nextFrontier.push(i);
348
+ }
349
+ }
350
+ }
351
+ catch {
352
+ /* per-file importer query failure → skip; correctness degrades on
353
+ that branch, but DB stays writable. */
354
+ }
355
+ }
356
+ frontier = nextFrontier;
357
+ }
358
+ }
359
+ const importerExpansion = writableFiles.size - directlyChangedCount;
360
+ if (importerExpansion > 0) {
361
+ log(`Incremental: +${importerExpansion} importer(s) added to writable set ` +
362
+ `(BFS depth ≤ ${MAX_IMPORTER_BFS_DEPTH}` +
363
+ (shadowSeed.length > 0 ? `, ${shadowSeed.length} shadow-seed(s)` : '') +
364
+ `)`);
365
+ }
366
+ // 1. Compute the EFFECTIVE write-set (Finding 1). Two layers,
367
+ // composed:
368
+ // (a) `writableFiles` — toWrite ∪ transitive importers of
369
+ // changed/deleted files (the bounded BFS above, reading
370
+ // IMPORTS from the pre-pipeline DB).
371
+ // (b) `computeEffectiveWriteSet` — walks the NEW graph's
372
+ // edges and pulls in any unchanged-side file that sits
373
+ // on a writable-boundary-crossing edge (catches refined
374
+ // cross-file CALLS edges that the pre-run DB couldn't
375
+ // predict, e.g. a barrel re-export shifting `foo` from
376
+ // B to D).
377
+ // The composed set is the input to BOTH deleteNodesForFile
378
+ // and extractChangedSubgraph — asymmetry between the two would
379
+ // leave stale rows or PK-conflict at COPY time.
380
+ const effectiveWriteSet = computeEffectiveWriteSet(pipelineResult.graph, writableFiles);
381
+ // Deduped: deleted entries may already appear via importer-BFS
382
+ // expansion (queryImporters can return a now-deleted path), which
383
+ // would otherwise call deleteNodesForFile twice for the same file
384
+ // (Bugbot LOW finding on PR #1479).
385
+ const filesToDelete = [...new Set([...effectiveWriteSet, ...hashDiff.deleted])];
386
+ for (let i = 0; i < filesToDelete.length; i++) {
387
+ const f = filesToDelete[i];
388
+ try {
389
+ await deleteNodesForFile(f);
390
+ }
391
+ catch {
392
+ /* file may not have rows (e.g. an unparseable file) — fine */
393
+ }
394
+ if (i % 20 === 0) {
395
+ progress('lbug', 62, `Removing rows for changed files (${i}/${filesToDelete.length})...`);
396
+ }
397
+ }
398
+ // 2. Drop graph-wide nodes (Community, Process). They'll be re-inserted
399
+ // from the fresh pipeline output below. Required for the
400
+ // "Leiden runs on the FULL graph" correctness invariant.
401
+ await deleteAllCommunitiesAndProcesses();
402
+ // 3. Extract the changed subgraph from the FULL ctx.graph and write
403
+ // only that. Unchanged-file rows in the DB stay untouched. Pass
404
+ // the SAME effectiveWriteSet so the subgraph and the deletes
405
+ // cover identical files (asymmetry would silently corrupt).
406
+ const subgraph = extractChangedSubgraph(pipelineResult.graph, effectiveWriteSet);
407
+ await loadGraphToLbug(subgraph, pipelineResult.repoPath, storagePath, (msg) => {
408
+ lbugMsgCount++;
409
+ const pct = Math.min(84, 65 + Math.round((lbugMsgCount / (lbugMsgCount + 10)) * 19));
410
+ progress('lbug', pct, msg);
411
+ });
412
+ }
413
+ else {
414
+ // ── Full rebuild ───────────────────────────────────────────────
415
+ await loadGraphToLbug(pipelineResult.graph, pipelineResult.repoPath, storagePath, (msg) => {
416
+ lbugMsgCount++;
417
+ const pct = Math.min(84, 60 + Math.round((lbugMsgCount / (lbugMsgCount + 10)) * 24));
418
+ progress('lbug', pct, msg);
419
+ });
420
+ }
173
421
  // ── Phase 3: FTS (85–90%) ─────────────────────────────────────────
174
422
  progress('fts', 85, 'Creating search indexes...');
175
423
  await createSearchFTSIndexes();
176
424
  progress('fts', 90, 'Search indexes ready');
177
425
  // ── Phase 3.5: Re-insert cached embeddings ────────────────────────
426
+ // Runs on BOTH the full-rebuild path and the incremental path:
427
+ // - Full rebuild: DB was wiped, every cached row needs to come back.
428
+ // - Incremental: changed-file rows were just deleted by
429
+ // deleteNodesForFile (which cascades to their
430
+ // embedding rows) — so their cached vectors need
431
+ // to come back too. Unchanged-file rows still
432
+ // exist; re-inserting their cached vectors would
433
+ // PK-conflict, but the per-batch try/catch below
434
+ // silently ignores those (matches the existing
435
+ // "some may fail if node was removed, that's
436
+ // fine" semantics). Bugbot review on PR #1479
437
+ // flagged that gating this on `!isIncremental`
438
+ // silently lost changed-file embeddings.
178
439
  if (cachedEmbeddings.length > 0) {
179
440
  const cachedDims = cachedEmbeddings[0].embedding.length;
180
441
  const { EMBEDDING_DIMS } = await import('./lbug/schema.js');
@@ -286,6 +547,11 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
286
547
  const runtimeCapabilities = getRuntimeCapabilities();
287
548
  const effectiveSemanticMode = semanticMode ??
288
549
  (runtimeCapabilities.semanticMode === 'vector-index' ? 'vector-index' : 'exact-scan');
550
+ // Convert the post-run file-hash map to the on-disk Record<string,string>
551
+ // shape consumed by RepoMeta.fileHashes.
552
+ const newFileHashesRecord = {};
553
+ for (const [k, v] of newFileHashes)
554
+ newFileHashesRecord[k] = v;
289
555
  const meta = {
290
556
  repoPath,
291
557
  lastCommit: currentCommit,
@@ -315,8 +581,32 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
315
581
  reason: runtimeCapabilities.reason,
316
582
  },
317
583
  },
584
+ // Incremental-indexing fields. Populated for git repos so the next
585
+ // analyze run can take the incremental DB-writeback path. Setting
586
+ // incrementalInProgress to undefined explicitly clears any prior
587
+ // dirty flag (full and incremental success paths converge here).
588
+ schemaVersion: hasGitDir(repoPath) ? INCREMENTAL_SCHEMA_VERSION : undefined,
589
+ fileHashes: hasGitDir(repoPath) ? newFileHashesRecord : undefined,
590
+ incrementalInProgress: undefined,
318
591
  };
319
592
  await saveMeta(storagePath, meta);
593
+ // Persist the incremental parse cache for the next run. Wraps in
594
+ // try/catch so a cache-write failure never breaks an otherwise
595
+ // successful indexing run. Prune stale chunk-hash entries first so
596
+ // the cache file size stays bounded across runs (chunks whose
597
+ // composition no longer matches anything in the current scan are
598
+ // dead weight; the parse phase populates `usedKeys` as it processes
599
+ // chunks).
600
+ try {
601
+ const pruned = pruneCache(parseCache, parseCache.usedKeys);
602
+ if (pruned > 0) {
603
+ log(`Parse cache: pruned ${pruned} stale chunk entries`);
604
+ }
605
+ await saveParseCache(storagePath, parseCache);
606
+ }
607
+ catch (e) {
608
+ log(`Warning: could not save parse cache (${e.message}); continuing.`);
609
+ }
320
610
  // Forward the --name alias and the registry-collision bypass bit.
321
611
  // `allowDuplicateName` is its own concern — independent from the
322
612
  // pipeline `force` above. The CLI maps it from
@@ -350,7 +640,11 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
350
640
  communities: pipelineResult.communityResult?.stats.totalCommunities,
351
641
  clusters: aggregatedClusterCount,
352
642
  processes: pipelineResult.processResult?.stats.totalProcesses,
353
- }, undefined, { skipAgentsMd: options.skipAgentsMd, noStats: options.noStats });
643
+ }, undefined, {
644
+ skipAgentsMd: options.skipAgentsMd,
645
+ skipSkills: options.skipSkills,
646
+ noStats: options.noStats,
647
+ });
354
648
  }
355
649
  catch {
356
650
  // Best-effort — don't fail the entire analysis for context file issues
@@ -46,5 +46,8 @@ export declare const formatHybridResults: (results: HybridSearchResult[]) => str
46
46
  * Execute BM25 + semantic search and merge with RRF.
47
47
  * Uses LadybugDB FTS for always-fresh BM25 results (no cached data).
48
48
  * The semanticSearch function is injected to keep this module environment-agnostic.
49
+ *
50
+ * When FTS is unavailable (e.g. read-only MCP connection, missing indexes),
51
+ * falls back to semantic-only results instead of crashing (#1489).
49
52
  */
50
53
  export declare const hybridSearch: (query: string, limit: number, executeQuery: (cypher: string) => Promise<any[]>, semanticSearch: (executeQuery: (cypher: string) => Promise<any[]>, query: string, k?: number) => Promise<SemanticSearchResult[]>) => Promise<HybridSearchResult[]>;
@@ -23,9 +23,14 @@ const RRF_K = 60;
23
23
  */
24
24
  export const mergeWithRRF = (bm25Results, semanticResults, limit = 10) => {
25
25
  const merged = new Map();
26
+ // Guard against undefined/null inputs (#1489) — when FTS is unavailable
27
+ // in the MCP process, bm25Results can arrive as undefined and the
28
+ // for-loop would throw "bm25Results is not iterable".
29
+ const safeBm25 = bm25Results ?? [];
30
+ const safeSemantic = semanticResults ?? [];
26
31
  // Process BM25 results
27
- for (let i = 0; i < bm25Results.length; i++) {
28
- const r = bm25Results[i];
32
+ for (let i = 0; i < safeBm25.length; i++) {
33
+ const r = safeBm25[i];
29
34
  const rrfScore = 1 / (RRF_K + i + 1); // i+1 because rank starts at 1
30
35
  merged.set(r.filePath, {
31
36
  filePath: r.filePath,
@@ -36,8 +41,8 @@ export const mergeWithRRF = (bm25Results, semanticResults, limit = 10) => {
36
41
  });
37
42
  }
38
43
  // Process semantic results and merge
39
- for (let i = 0; i < semanticResults.length; i++) {
40
- const r = semanticResults[i];
44
+ for (let i = 0; i < safeSemantic.length; i++) {
45
+ const r = safeSemantic[i];
41
46
  const rrfScore = 1 / (RRF_K + i + 1);
42
47
  const existing = merged.get(r.filePath);
43
48
  if (existing) {
@@ -110,10 +115,22 @@ export const formatHybridResults = (results) => {
110
115
  * Execute BM25 + semantic search and merge with RRF.
111
116
  * Uses LadybugDB FTS for always-fresh BM25 results (no cached data).
112
117
  * The semanticSearch function is injected to keep this module environment-agnostic.
118
+ *
119
+ * When FTS is unavailable (e.g. read-only MCP connection, missing indexes),
120
+ * falls back to semantic-only results instead of crashing (#1489).
113
121
  */
114
122
  export const hybridSearch = async (query, limit, executeQuery, semanticSearch) => {
115
- // Use LadybugDB FTS for always-fresh BM25 results
116
- const { results: bm25Results } = await searchFTSFromLbug(query, limit);
123
+ // Use LadybugDB FTS for always-fresh BM25 results.
124
+ // If FTS fails (e.g. extension not loaded in MCP process), fall back to
125
+ // semantic-only search instead of crashing with "bm25Results is not iterable".
126
+ let bm25Results = [];
127
+ try {
128
+ const ftsResponse = await searchFTSFromLbug(query, limit);
129
+ bm25Results = ftsResponse?.results ?? [];
130
+ }
131
+ catch {
132
+ // FTS unavailable — continue with semantic-only search
133
+ }
117
134
  const semanticResults = await semanticSearch(executeQuery, query, limit);
118
135
  return mergeWithRRF(bm25Results, semanticResults, limit);
119
136
  };
@@ -14,6 +14,7 @@ import path from 'path';
14
14
  import { execSync, execFileSync } from 'child_process';
15
15
  import { initWikiDb, closeWikiDb, touchWikiDb, getFilesWithExports, getAllFiles, getIntraModuleCallEdges, getInterModuleCallEdges, getProcessesForFiles, getAllProcesses, getInterModuleEdgesForOverview, } from './graph-queries.js';
16
16
  import { generateHTMLViewer } from './html-viewer.js';
17
+ import { sanitizeMermaidMarkdown } from './mermaid-sanitizer.js';
17
18
  import { callLLM, estimateTokens, } from './llm-client.js';
18
19
  import { callCursorLLM, resolveCursorConfig } from './cursor-client.js';
19
20
  import { GROUPING_SYSTEM_PROMPT, GROUPING_USER_PROMPT, MODULE_SYSTEM_PROMPT, MODULE_USER_PROMPT, PARENT_SYSTEM_PROMPT, PARENT_USER_PROMPT, OVERVIEW_SYSTEM_PROMPT, OVERVIEW_USER_PROMPT, fillTemplate, formatFileListForGrouping, formatDirectoryTree, formatCallEdges, formatProcesses, } from './prompts.js';
@@ -445,7 +446,7 @@ export class WikiGenerator {
445
446
  });
446
447
  const response = await this.invokeLLM(prompt, MODULE_SYSTEM_PROMPT, this.streamOpts(node.name));
447
448
  // Write page with front matter
448
- const pageContent = `# ${node.name}\n\n${response.content}`;
449
+ const pageContent = sanitizeMermaidMarkdown(`# ${node.name}\n\n${response.content}`);
449
450
  await fs.writeFile(path.join(this.wikiDir, `${node.slug}.md`), pageContent, 'utf-8');
450
451
  }
451
452
  /**
@@ -480,7 +481,7 @@ export class WikiGenerator {
480
481
  CROSS_PROCESSES: formatProcesses(processes),
481
482
  });
482
483
  const response = await this.invokeLLM(prompt, PARENT_SYSTEM_PROMPT, this.streamOpts(node.name));
483
- const pageContent = `# ${node.name}\n\n${response.content}`;
484
+ const pageContent = sanitizeMermaidMarkdown(`# ${node.name}\n\n${response.content}`);
484
485
  await fs.writeFile(path.join(this.wikiDir, `${node.slug}.md`), pageContent, 'utf-8');
485
486
  }
486
487
  // ─── Phase 3: Generate Overview ─────────────────────────────────────
@@ -516,7 +517,7 @@ export class WikiGenerator {
516
517
  TOP_PROCESSES: formatProcesses(topProcesses),
517
518
  });
518
519
  const response = await this.invokeLLM(prompt, OVERVIEW_SYSTEM_PROMPT, this.streamOpts('Generating overview', 88));
519
- const pageContent = `# ${path.basename(this.repoPath)} — Wiki\n\n${response.content}`;
520
+ const pageContent = sanitizeMermaidMarkdown(`# ${path.basename(this.repoPath)} — Wiki\n\n${response.content}`);
520
521
  await fs.writeFile(path.join(this.wikiDir, 'overview.md'), pageContent, 'utf-8');
521
522
  }
522
523
  // ─── Incremental Updates ────────────────────────────────────────────
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import fs from 'fs/promises';
8
8
  import path from 'path';
9
+ import { sanitizeMermaidMarkdown } from './mermaid-sanitizer.js';
9
10
  /**
10
11
  * Generate the wiki HTML viewer (index.html) from existing markdown pages.
11
12
  */
@@ -33,7 +34,7 @@ export async function generateHTMLViewer(wikiDir, projectName) {
33
34
  const dirEntries = await fs.readdir(wikiDir);
34
35
  for (const f of dirEntries.filter((f) => f.endsWith('.md'))) {
35
36
  const content = await fs.readFile(path.join(wikiDir, f), 'utf-8');
36
- pages[f.replace(/\.md$/, '')] = content;
37
+ pages[f.replace(/\.md$/, '')] = sanitizeMermaidMarkdown(content);
37
38
  }
38
39
  const html = buildHTML(projectName, moduleTree, pages, meta);
39
40
  const outputPath = path.join(wikiDir, 'index.html');
@@ -19,6 +19,10 @@ export interface LLMConfig {
19
19
  apiVersion?: string;
20
20
  /** When true, strips sampling params and uses max_completion_tokens instead of max_tokens */
21
21
  isReasoningModel?: boolean;
22
+ /** Per-attempt fetch timeout in ms (default: 60_000). */
23
+ requestTimeoutMs?: number;
24
+ /** Max fetch attempts before giving up (default: 3). */
25
+ maxAttempts?: number;
22
26
  }
23
27
  export interface LLMResponse {
24
28
  content: string;
@@ -170,11 +170,11 @@ export async function callLLM(prompt, config, systemPrompt, options) {
170
170
  // indefinitely on a frozen TCP connection — the per-call
171
171
  // signal is the only timeout `resilientFetch` honors;
172
172
  // `capDelayMs` only bounds the *backoff* between attempts.
173
- // 60s matches typical LLM completion budgets.
174
- signal: AbortSignal.timeout(60_000),
173
+ // Default 60s; raise via --timeout for slow models or large pages.
174
+ signal: AbortSignal.timeout(config.requestTimeoutMs ?? 60_000),
175
175
  }, {
176
176
  breakerKey: `wiki-llm-${new URL(url).host}`,
177
- retry: { maxAttempts: 3, baseDelayMs: 2_000, capDelayMs: 30_000 },
177
+ retry: { maxAttempts: config.maxAttempts ?? 3, baseDelayMs: 2_000, capDelayMs: 30_000 },
178
178
  });
179
179
  }
180
180
  catch (err) {
@@ -0,0 +1,2 @@
1
+ export declare function sanitizeMermaidMarkdown(markdown: string): string;
2
+ export declare function sanitizeMermaidDiagram(diagram: string): string;