opencode-rag-plugin 1.12.17 → 1.14.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/ReadMe.md +15 -13
  2. package/dist/api.d.ts +54 -2
  3. package/dist/api.js +41 -1
  4. package/dist/api.js.map +1 -1
  5. package/dist/chunker/base.js +2 -0
  6. package/dist/chunker/base.js.map +1 -1
  7. package/dist/chunker/bash.d.ts +5 -0
  8. package/dist/chunker/bash.js +5 -0
  9. package/dist/chunker/bash.js.map +1 -1
  10. package/dist/chunker/c.d.ts +6 -0
  11. package/dist/chunker/c.js +6 -0
  12. package/dist/chunker/c.js.map +1 -1
  13. package/dist/chunker/cpp.d.ts +6 -0
  14. package/dist/chunker/cpp.js +6 -0
  15. package/dist/chunker/cpp.js.map +1 -1
  16. package/dist/chunker/csharp.d.ts +6 -0
  17. package/dist/chunker/csharp.js +6 -0
  18. package/dist/chunker/csharp.js.map +1 -1
  19. package/dist/chunker/css.d.ts +6 -0
  20. package/dist/chunker/css.js +6 -0
  21. package/dist/chunker/css.js.map +1 -1
  22. package/dist/chunker/doc.d.ts +18 -0
  23. package/dist/chunker/doc.js +18 -0
  24. package/dist/chunker/doc.js.map +1 -1
  25. package/dist/chunker/dockerfile.d.ts +6 -0
  26. package/dist/chunker/dockerfile.js +6 -0
  27. package/dist/chunker/dockerfile.js.map +1 -1
  28. package/dist/chunker/docx.d.ts +18 -0
  29. package/dist/chunker/docx.js +18 -0
  30. package/dist/chunker/docx.js.map +1 -1
  31. package/dist/chunker/excel.d.ts +19 -0
  32. package/dist/chunker/excel.js +19 -0
  33. package/dist/chunker/excel.js.map +1 -1
  34. package/dist/chunker/go.d.ts +5 -0
  35. package/dist/chunker/go.js +5 -0
  36. package/dist/chunker/go.js.map +1 -1
  37. package/dist/chunker/grammar.d.ts +62 -0
  38. package/dist/chunker/grammar.js +133 -0
  39. package/dist/chunker/grammar.js.map +1 -1
  40. package/dist/chunker/html.d.ts +5 -0
  41. package/dist/chunker/html.js +5 -0
  42. package/dist/chunker/html.js.map +1 -1
  43. package/dist/chunker/image.d.ts +31 -0
  44. package/dist/chunker/image.js +50 -4
  45. package/dist/chunker/image.js.map +1 -1
  46. package/dist/chunker/ini.d.ts +5 -0
  47. package/dist/chunker/ini.js +5 -0
  48. package/dist/chunker/ini.js.map +1 -1
  49. package/dist/chunker/java.d.ts +5 -0
  50. package/dist/chunker/java.js +5 -0
  51. package/dist/chunker/java.js.map +1 -1
  52. package/dist/chunker/javascript.d.ts +6 -0
  53. package/dist/chunker/javascript.js +6 -0
  54. package/dist/chunker/javascript.js.map +1 -1
  55. package/dist/chunker/json.d.ts +5 -0
  56. package/dist/chunker/json.js +5 -0
  57. package/dist/chunker/json.js.map +1 -1
  58. package/dist/chunker/kotlin.d.ts +6 -0
  59. package/dist/chunker/kotlin.js +6 -0
  60. package/dist/chunker/kotlin.js.map +1 -1
  61. package/dist/chunker/loader.d.ts +11 -0
  62. package/dist/chunker/loader.js +20 -0
  63. package/dist/chunker/loader.js.map +1 -1
  64. package/dist/chunker/markdown.d.ts +5 -0
  65. package/dist/chunker/markdown.js +5 -0
  66. package/dist/chunker/markdown.js.map +1 -1
  67. package/dist/chunker/pdf.d.ts +19 -0
  68. package/dist/chunker/pdf.js +30 -0
  69. package/dist/chunker/pdf.js.map +1 -1
  70. package/dist/chunker/php.d.ts +5 -0
  71. package/dist/chunker/php.js +5 -0
  72. package/dist/chunker/php.js.map +1 -1
  73. package/dist/chunker/powershell.d.ts +5 -0
  74. package/dist/chunker/powershell.js +5 -0
  75. package/dist/chunker/powershell.js.map +1 -1
  76. package/dist/chunker/python.d.ts +5 -0
  77. package/dist/chunker/python.js +5 -0
  78. package/dist/chunker/python.js.map +1 -1
  79. package/dist/chunker/razor.d.ts +12 -0
  80. package/dist/chunker/razor.js +25 -0
  81. package/dist/chunker/razor.js.map +1 -1
  82. package/dist/chunker/ruby.d.ts +5 -0
  83. package/dist/chunker/ruby.js +5 -0
  84. package/dist/chunker/ruby.js.map +1 -1
  85. package/dist/chunker/rust.d.ts +6 -0
  86. package/dist/chunker/rust.js +6 -0
  87. package/dist/chunker/rust.js.map +1 -1
  88. package/dist/chunker/sln.d.ts +11 -0
  89. package/dist/chunker/sln.js +11 -0
  90. package/dist/chunker/sln.js.map +1 -1
  91. package/dist/chunker/sql.d.ts +5 -0
  92. package/dist/chunker/sql.js +5 -0
  93. package/dist/chunker/sql.js.map +1 -1
  94. package/dist/chunker/ssl.d.ts +12 -0
  95. package/dist/chunker/ssl.js +21 -2
  96. package/dist/chunker/ssl.js.map +1 -1
  97. package/dist/chunker/swift.d.ts +5 -0
  98. package/dist/chunker/swift.js +5 -0
  99. package/dist/chunker/swift.js.map +1 -1
  100. package/dist/chunker/tex.d.ts +12 -0
  101. package/dist/chunker/tex.js +12 -0
  102. package/dist/chunker/tex.js.map +1 -1
  103. package/dist/chunker/toml.d.ts +5 -0
  104. package/dist/chunker/toml.js +5 -0
  105. package/dist/chunker/toml.js.map +1 -1
  106. package/dist/chunker/typescript.d.ts +6 -0
  107. package/dist/chunker/typescript.js +6 -0
  108. package/dist/chunker/typescript.js.map +1 -1
  109. package/dist/chunker/xml.d.ts +5 -0
  110. package/dist/chunker/xml.js +5 -0
  111. package/dist/chunker/xml.js.map +1 -1
  112. package/dist/chunker/yaml.d.ts +5 -0
  113. package/dist/chunker/yaml.js +5 -0
  114. package/dist/chunker/yaml.js.map +1 -1
  115. package/dist/cli/commands/clear.d.ts +12 -0
  116. package/dist/cli/commands/clear.js +81 -0
  117. package/dist/cli/commands/clear.js.map +1 -0
  118. package/dist/cli/commands/describe-image.d.ts +14 -0
  119. package/dist/cli/commands/describe-image.js +68 -0
  120. package/dist/cli/commands/describe-image.js.map +1 -0
  121. package/dist/cli/commands/dump.d.ts +13 -0
  122. package/dist/cli/commands/dump.js +55 -0
  123. package/dist/cli/commands/dump.js.map +1 -0
  124. package/dist/cli/commands/eval.d.ts +32 -0
  125. package/dist/cli/commands/eval.js +158 -0
  126. package/dist/cli/commands/eval.js.map +1 -0
  127. package/dist/cli/commands/index-command.d.ts +17 -0
  128. package/dist/cli/commands/index-command.js +167 -0
  129. package/dist/cli/commands/index-command.js.map +1 -0
  130. package/dist/cli/commands/index.d.ts +16 -0
  131. package/dist/cli/commands/index.js +17 -0
  132. package/dist/cli/commands/index.js.map +1 -0
  133. package/dist/cli/commands/init-helpers.d.ts +101 -0
  134. package/dist/cli/commands/init-helpers.js +397 -0
  135. package/dist/cli/commands/init-helpers.js.map +1 -0
  136. package/dist/cli/commands/init.d.ts +19 -0
  137. package/dist/cli/commands/init.js +251 -0
  138. package/dist/cli/commands/init.js.map +1 -0
  139. package/dist/cli/commands/list.d.ts +13 -0
  140. package/dist/cli/commands/list.js +46 -0
  141. package/dist/cli/commands/list.js.map +1 -0
  142. package/dist/cli/commands/mcp.d.ts +14 -0
  143. package/dist/cli/commands/mcp.js +35 -0
  144. package/dist/cli/commands/mcp.js.map +1 -0
  145. package/dist/cli/commands/query.d.ts +14 -0
  146. package/dist/cli/commands/query.js +86 -0
  147. package/dist/cli/commands/query.js.map +1 -0
  148. package/dist/cli/commands/show.d.ts +13 -0
  149. package/dist/cli/commands/show.js +51 -0
  150. package/dist/cli/commands/show.js.map +1 -0
  151. package/dist/cli/commands/status.d.ts +14 -0
  152. package/dist/cli/commands/status.js +104 -0
  153. package/dist/cli/commands/status.js.map +1 -0
  154. package/dist/cli/commands/ui.d.ts +14 -0
  155. package/dist/cli/commands/ui.js +69 -0
  156. package/dist/cli/commands/ui.js.map +1 -0
  157. package/dist/cli/commands/update.d.ts +14 -0
  158. package/dist/cli/commands/update.js +76 -0
  159. package/dist/cli/commands/update.js.map +1 -0
  160. package/dist/cli/format.d.ts +112 -0
  161. package/dist/cli/format.js +184 -0
  162. package/dist/cli/format.js.map +1 -0
  163. package/dist/cli/helpers.d.ts +51 -0
  164. package/dist/cli/helpers.js +86 -0
  165. package/dist/cli/helpers.js.map +1 -0
  166. package/dist/cli/index.d.ts +28 -0
  167. package/dist/cli/index.js +104 -0
  168. package/dist/cli/index.js.map +1 -0
  169. package/dist/cli/progress.d.ts +39 -0
  170. package/dist/cli/progress.js +137 -0
  171. package/dist/cli/progress.js.map +1 -0
  172. package/dist/cli/types.d.ts +40 -0
  173. package/dist/cli/types.js +3 -0
  174. package/dist/cli/types.js.map +1 -0
  175. package/dist/cli.d.ts +11 -4
  176. package/dist/cli.js +9 -1253
  177. package/dist/cli.js.map +1 -1
  178. package/dist/content/doc.d.ts +1 -1
  179. package/dist/content/doc.js +1 -1
  180. package/dist/content/doc.js.map +1 -1
  181. package/dist/content/docx.d.ts +1 -1
  182. package/dist/content/docx.js +1 -1
  183. package/dist/content/docx.js.map +1 -1
  184. package/dist/content/excel.d.ts +1 -1
  185. package/dist/content/excel.js +1 -1
  186. package/dist/content/excel.js.map +1 -1
  187. package/dist/content/pdf.d.ts +1 -1
  188. package/dist/content/pdf.js +1 -1
  189. package/dist/content/pdf.js.map +1 -1
  190. package/dist/content/reader.d.ts +3 -1
  191. package/dist/content/reader.js +42 -10
  192. package/dist/content/reader.js.map +1 -1
  193. package/dist/core/bootstrap.d.ts +18 -0
  194. package/dist/core/bootstrap.js +9 -15
  195. package/dist/core/bootstrap.js.map +1 -1
  196. package/dist/core/config.d.ts +144 -0
  197. package/dist/core/config.js +65 -14
  198. package/dist/core/config.js.map +1 -1
  199. package/dist/core/doc-progress.d.ts +15 -0
  200. package/dist/core/doc-progress.js +61 -0
  201. package/dist/core/doc-progress.js.map +1 -0
  202. package/dist/core/fileLogger.d.ts +7 -0
  203. package/dist/core/fileLogger.js +2 -0
  204. package/dist/core/fileLogger.js.map +1 -1
  205. package/dist/core/interfaces.d.ts +52 -1
  206. package/dist/core/manifest.d.ts +25 -0
  207. package/dist/core/manifest.js +15 -1
  208. package/dist/core/manifest.js.map +1 -1
  209. package/dist/core/provider-defaults.d.ts +8 -0
  210. package/dist/core/provider-defaults.js +3 -0
  211. package/dist/core/provider-defaults.js.map +1 -1
  212. package/dist/core/rag-injection-flag.d.ts +3 -0
  213. package/dist/core/rag-injection-flag.js +2 -0
  214. package/dist/core/rag-injection-flag.js.map +1 -1
  215. package/dist/core/resolve-api-key.d.ts +1 -0
  216. package/dist/core/resolve-api-key.js +1 -0
  217. package/dist/core/resolve-api-key.js.map +1 -1
  218. package/dist/core/runtime-overrides.d.ts +4 -0
  219. package/dist/core/runtime-overrides.js +4 -1
  220. package/dist/core/runtime-overrides.js.map +1 -1
  221. package/dist/describer/anthropic.d.ts +22 -2
  222. package/dist/describer/anthropic.js +42 -110
  223. package/dist/describer/anthropic.js.map +1 -1
  224. package/dist/describer/describer.d.ts +21 -4
  225. package/dist/describer/describer.js +52 -111
  226. package/dist/describer/describer.js.map +1 -1
  227. package/dist/describer/gemini.d.ts +1 -2
  228. package/dist/describer/gemini.js +20 -113
  229. package/dist/describer/gemini.js.map +1 -1
  230. package/dist/describer/shared.d.ts +3 -0
  231. package/dist/describer/shared.js +19 -0
  232. package/dist/describer/shared.js.map +1 -0
  233. package/dist/embedder/factory.d.ts +25 -0
  234. package/dist/embedder/factory.js +25 -0
  235. package/dist/embedder/factory.js.map +1 -1
  236. package/dist/embedder/health.d.ts +10 -0
  237. package/dist/embedder/health.js +26 -14
  238. package/dist/embedder/health.js.map +1 -1
  239. package/dist/embedder/http.d.ts +47 -0
  240. package/dist/embedder/http.js +61 -3
  241. package/dist/embedder/http.js.map +1 -1
  242. package/dist/eval/index.d.ts +6 -0
  243. package/dist/eval/index.js.map +1 -1
  244. package/dist/eval/run-token-test.js +1 -5
  245. package/dist/eval/run-token-test.js.map +1 -1
  246. package/dist/eval/session-logger.d.ts +5 -0
  247. package/dist/eval/session-logger.js +4 -0
  248. package/dist/eval/session-logger.js.map +1 -1
  249. package/dist/eval/storage.d.ts +9 -0
  250. package/dist/eval/storage.js +9 -5
  251. package/dist/eval/storage.js.map +1 -1
  252. package/dist/eval/token-analysis.d.ts +8 -0
  253. package/dist/eval/token-analysis.js +6 -2
  254. package/dist/eval/token-analysis.js.map +1 -1
  255. package/dist/eval/token-counter.d.ts +1 -3
  256. package/dist/eval/token-counter.js +4 -5
  257. package/dist/eval/token-counter.js.map +1 -1
  258. package/dist/eval/types.d.ts +6 -0
  259. package/dist/eval/types.js +2 -0
  260. package/dist/eval/types.js.map +1 -1
  261. package/dist/index.d.ts +11 -0
  262. package/dist/index.js +12 -3
  263. package/dist/index.js.map +1 -1
  264. package/dist/indexer/description-stage.js +11 -2
  265. package/dist/indexer/description-stage.js.map +1 -1
  266. package/dist/indexer/embed-stage.js +0 -8
  267. package/dist/indexer/embed-stage.js.map +1 -1
  268. package/dist/indexer/pipeline.d.ts +27 -3
  269. package/dist/indexer/pipeline.js +314 -143
  270. package/dist/indexer/pipeline.js.map +1 -1
  271. package/dist/indexer/stats.d.ts +35 -0
  272. package/dist/indexer/stats.js +9 -0
  273. package/dist/indexer/stats.js.map +1 -1
  274. package/dist/indexer/watch.d.ts +25 -0
  275. package/dist/indexer/watch.js +21 -0
  276. package/dist/indexer/watch.js.map +1 -1
  277. package/dist/indexer/worker.d.ts +90 -2
  278. package/dist/indexer/worker.js +109 -24
  279. package/dist/indexer/worker.js.map +1 -1
  280. package/dist/indexer.d.ts +11 -1
  281. package/dist/indexer.js +9 -0
  282. package/dist/indexer.js.map +1 -1
  283. package/dist/mcp/cli.d.ts +3 -0
  284. package/dist/mcp/cli.js +1 -0
  285. package/dist/mcp/cli.js.map +1 -1
  286. package/dist/mcp/handlers.d.ts +41 -0
  287. package/dist/mcp/handlers.js +40 -1
  288. package/dist/mcp/handlers.js.map +1 -1
  289. package/dist/mcp/server.d.ts +8 -0
  290. package/dist/mcp/server.js +18 -1
  291. package/dist/mcp/server.js.map +1 -1
  292. package/dist/opencode/create-read-tool.js +1 -0
  293. package/dist/opencode/create-read-tool.js.map +1 -1
  294. package/dist/opencode/tools.d.ts +8 -17
  295. package/dist/opencode/tools.js +74 -109
  296. package/dist/opencode/tools.js.map +1 -1
  297. package/dist/plugin-entry.d.ts +6 -0
  298. package/dist/plugin-entry.js +6 -0
  299. package/dist/plugin-entry.js.map +1 -1
  300. package/dist/plugin.d.ts +37 -0
  301. package/dist/plugin.js +265 -40
  302. package/dist/plugin.js.map +1 -1
  303. package/dist/retriever/keyword-index.js +9 -9
  304. package/dist/retriever/keyword-index.js.map +1 -1
  305. package/dist/retriever/retriever.js +14 -4
  306. package/dist/retriever/retriever.js.map +1 -1
  307. package/dist/tui.d.ts +5 -0
  308. package/dist/tui.js +100 -12
  309. package/dist/tui.js.map +1 -1
  310. package/dist/types/opencode-plugin.d.ts +4 -1
  311. package/dist/updater.d.ts +23 -1
  312. package/dist/updater.js +55 -16
  313. package/dist/updater.js.map +1 -1
  314. package/dist/vectorstore/factory.d.ts +13 -0
  315. package/dist/vectorstore/factory.js +13 -0
  316. package/dist/vectorstore/factory.js.map +1 -1
  317. package/dist/vectorstore/lancedb.d.ts +78 -1
  318. package/dist/vectorstore/lancedb.js +195 -21
  319. package/dist/vectorstore/lancedb.js.map +1 -1
  320. package/dist/vectorstore/memory.d.ts +5 -0
  321. package/dist/vectorstore/memory.js +11 -0
  322. package/dist/vectorstore/memory.js.map +1 -1
  323. package/dist/watcher.d.ts +24 -0
  324. package/dist/watcher.js +17 -35
  325. package/dist/watcher.js.map +1 -1
  326. package/dist/web/api.d.ts +37 -0
  327. package/dist/web/api.js +54 -0
  328. package/dist/web/api.js.map +1 -1
  329. package/dist/web/server.d.ts +15 -0
  330. package/dist/web/server.js +14 -0
  331. package/dist/web/server.js.map +1 -1
  332. package/dist/web/static.d.ts +8 -0
  333. package/dist/web/static.js +8 -0
  334. package/dist/web/static.js.map +1 -1
  335. package/package.json +11 -15
@@ -1 +1 @@
1
- {"version":3,"file":"embed-stage.js","sourceRoot":"","sources":["../../src/indexer/embed-stage.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AASpD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAChC,MAAM,EACN,QAAQ,EACR,SAAS,EACT,WAAW,GACQ;IACnB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IAElC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAC1B,KAAK,CAAC,KAAK,IAAI,EAAE;QACf,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CACH,CACF,CAAC;IAEF,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,YAAY,GAAa,EAAE,CAAC;IAElC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;QACzB,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAC5D,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IAED,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;QAEhG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAE,CAAC;YAClC,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACvE,MAAM,CAAC,QAAQ,CAAE,CAAC,SAAS,GAAG,GAAe,CAAC;YAChD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"embed-stage.js","sourceRoot":"","sources":["../../src/indexer/embed-stage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AASpD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAChC,MAAM,EACN,QAAQ,EACR,SAAS,EACT,WAAW,GACQ;IACnB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,YAAY,GAAa,EAAE,CAAC;IAElC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;QACzB,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAC5D,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IAED,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;QAEhG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAE,CAAC;YAClC,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACvE,MAAM,CAAC,QAAQ,CAAE,CAAC,SAAS,GAAG,GAAe,CAAC;YAChD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,23 +1,47 @@
1
1
  import type { RagConfig } from "../core/config.js";
2
- import type { DescriptionProvider, EmbeddingProvider, KeywordIndex, VectorStore } from "../core/interfaces.js";
2
+ import type { DescriptionProvider, EmbeddingProvider, IndexProgress, KeywordIndex, VectorStore } from "../core/interfaces.js";
3
3
  import { type IndexRunStats, type IndexStatusSummary } from "./stats.js";
4
4
  export type { WatchPassScheduler } from "./watch.js";
5
5
  export { createWatchPassScheduler, createWatchIgnore } from "./watch.js";
6
+ /** Options for configuring a single index pass. */
6
7
  export interface RunIndexPassOptions {
8
+ /** Workspace root directory. */
7
9
  cwd: string;
10
+ /** Path to the vector store data directory. */
8
11
  storePath: string;
12
+ /** Full RAG configuration for the workspace. */
9
13
  config: RagConfig;
14
+ /** Vector store instance for persisting chunks. */
10
15
  store: VectorStore;
16
+ /** Embedding provider for generating vector representations. */
11
17
  embedder: EmbeddingProvider;
18
+ /** When true, ignore the existing manifest and re-index everything. */
12
19
  force?: boolean;
20
+ /** Optional partial logger (missing methods default to no-ops). */
13
21
  logger?: Partial<Logger>;
22
+ /** Optional keyword index to populate during the pass. */
14
23
  keywordIndex?: KeywordIndex;
24
+ /** Optional provider for AI-generated chunk descriptions. */
15
25
  descriptionProvider?: DescriptionProvider;
26
+ /** Optional progress tracker for reporting file-level progress. */
27
+ progress?: IndexProgress;
28
+ /** Optional abort signal – when fired, the pass finishes the current file then stops. */
29
+ abortSignal?: AbortSignal;
30
+ /** Embedding vector dimension — needed when creating a temporary store for atomic rebuilds. */
31
+ dimension?: number;
16
32
  }
17
- interface Logger {
33
+ export interface Logger {
18
34
  info(message: string): void;
19
35
  warn(message: string): void;
20
36
  debug(message: string): void;
21
37
  }
38
+ /**
39
+ * Execute a full index pass: scan workspace, prepare files, generate embeddings
40
+ * (and optionally AI descriptions), and store results into the vector store.
41
+ * Uses a lock file to prevent concurrent passes.
42
+ *
43
+ * @param options - Configuration for the index pass.
44
+ * @returns Aggregate statistics for the pass.
45
+ */
22
46
  export declare function runIndexPass(options: RunIndexPassOptions): Promise<IndexRunStats>;
23
- export declare function getIndexStatusSummary(cwd: string, storePath: string, config: RagConfig, store: VectorStore): Promise<IndexStatusSummary>;
47
+ export declare function getIndexStatusSummary(cwd: string, storePath: string, config: RagConfig, store: VectorStore, skipScan?: boolean): Promise<IndexStatusSummary>;
@@ -1,10 +1,13 @@
1
+ import fs from "node:fs/promises";
1
2
  import path from "node:path";
2
3
  import pLimit from "p-limit";
3
4
  import { scanWorkspaceFiles } from "../content/reader.js";
4
5
  import { loadManifest, saveManifest, normalizeFilePath } from "../core/manifest.js";
5
6
  import { embedBatch } from "../embedder/factory.js";
7
+ import { createVectorStore } from "../vectorstore/factory.js";
8
+ import { swapStoreDirectories } from "../vectorstore/lancedb.js";
6
9
  import { createIndexStats } from "./stats.js";
7
- import { prepareFile } from "./worker.js";
10
+ import { prepareFile, buildTextsToEmbed, storeFileChunks } from "./worker.js";
8
11
  import { getCurrentCommit, getChangedFilesSince, getUntrackedFiles, getRepoRoot } from "./git-diff.js";
9
12
  export { createWatchPassScheduler, createWatchIgnore } from "./watch.js";
10
13
  function createLogger(logger) {
@@ -29,17 +32,77 @@ function tryUpdateLastGitCommit(cwd, manifest) {
29
32
  return false;
30
33
  }
31
34
  }
35
+ const LOCK_FILE = "index.lock";
36
+ const LOCK_MAX_AGE_MS = 5 * 60 * 1000;
37
+ function isPidAlive(pid) {
38
+ try {
39
+ process.kill(pid, 0);
40
+ return true;
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ /**
47
+ * Execute a full index pass: scan workspace, prepare files, generate embeddings
48
+ * (and optionally AI descriptions), and store results into the vector store.
49
+ * Uses a lock file to prevent concurrent passes.
50
+ *
51
+ * @param options - Configuration for the index pass.
52
+ * @returns Aggregate statistics for the pass.
53
+ */
32
54
  export async function runIndexPass(options) {
33
55
  const logger = createLogger(options.logger);
56
+ const lockPath = path.join(options.storePath, LOCK_FILE);
57
+ try {
58
+ const raw = await fs.readFile(lockPath, "utf-8");
59
+ const lock = JSON.parse(raw);
60
+ const age = lock.startedAt ? Date.now() - lock.startedAt : Infinity;
61
+ if (lock.pid && !isPidAlive(lock.pid)) {
62
+ logger.debug(`Stale lock from dead process ${lock.pid} — continuing`);
63
+ }
64
+ else if (lock.startedAt && age < LOCK_MAX_AGE_MS) {
65
+ logger.warn(`Another index pass is running (PID ${lock.pid ?? "unknown"}). Skipping.`);
66
+ return createIndexStats(0, "missing");
67
+ }
68
+ }
69
+ catch {
70
+ // No lock file — proceed
71
+ }
72
+ try {
73
+ await fs.writeFile(lockPath, JSON.stringify({ pid: process.pid, startedAt: Date.now() }), "utf-8");
74
+ }
75
+ catch {
76
+ // Best-effort lock
77
+ }
78
+ try {
79
+ return await runIndexPassInner(options, logger);
80
+ }
81
+ finally {
82
+ try {
83
+ await fs.unlink(lockPath);
84
+ }
85
+ catch { }
86
+ }
87
+ }
88
+ async function runIndexPassInner(options, logger) {
34
89
  const loadResult = await loadManifest(options.storePath);
35
90
  const manifest = loadResult.manifest;
36
91
  let manifestStatus = loadResult.status;
37
92
  let rebuildPerformed = false;
38
- logger.debug(`Manifest loaded: ${manifestStatus}, ${Object.keys(manifest.files).length} entries`);
93
+ logger.info(`Manifest loaded: ${manifestStatus}, ${Object.keys(manifest.files).length} entries`);
39
94
  if (options.force) {
40
95
  manifestStatus = "missing";
41
96
  logger.debug("Force mode: ignoring manifest");
42
97
  }
98
+ // Clear files that had description failures so they are fully re-indexed
99
+ const descFailedPaths = Object.keys(manifest.files).filter((p) => manifest.files[p]?.descriptionFailed);
100
+ if (descFailedPaths.length > 0) {
101
+ logger.info(` ${descFailedPaths.length} file(s) marked as description-failed — re-indexing`);
102
+ for (const p of descFailedPaths) {
103
+ delete manifest.files[p];
104
+ }
105
+ }
43
106
  let filterPaths;
44
107
  let gitDeletedPaths = [];
45
108
  if (!options.force && manifestStatus === "ok" && manifest.lastGitCommit) {
@@ -59,8 +122,10 @@ export async function runIndexPass(options) {
59
122
  }
60
123
  }
61
124
  }
125
+ const scanStart = Date.now();
62
126
  const workspaceFiles = await scanWorkspaceFiles(options.cwd, options.config, logger, options.force ? undefined : manifest, filterPaths);
63
- logger.debug(`Workspace scan complete: ${workspaceFiles.length} files`);
127
+ const scanSec = ((Date.now() - scanStart) / 1000).toFixed(1);
128
+ logger.info(`Workspace scan complete: ${workspaceFiles.length} files in ${scanSec}s`);
64
129
  const existingCount = await options.store.count();
65
130
  // Detect data loss: if the store has far fewer chunks than the manifest expects,
66
131
  // treat it as a corrupt store (e.g. schema migration dropped the old table).
@@ -76,8 +141,10 @@ export async function runIndexPass(options) {
76
141
  manifestStatus = "missing";
77
142
  }
78
143
  }
144
+ // Effective store used throughout the pass — may be a temp store for atomic rebuild.
145
+ let effectiveStore = options.store;
146
+ let tempStorePath;
79
147
  if (options.force || (manifestStatus !== "ok" && existingCount > 0)) {
80
- await options.store.clear();
81
148
  options.keywordIndex?.clear();
82
149
  for (const key of Object.keys(manifest.files)) {
83
150
  delete manifest.files[key];
@@ -88,6 +155,23 @@ export async function runIndexPass(options) {
88
155
  logger.warn("Manifest missing or corrupt; rebuilding full index.");
89
156
  }
90
157
  manifestStatus = options.force ? "missing" : manifestStatus;
158
+ // Build the new index into a temporary store first, then atomically
159
+ // swap on completion. Original data stays untouched if the process
160
+ // is aborted (Ctrl+C, crash) before the swap completes.
161
+ if (options.dimension) {
162
+ tempStorePath = options.storePath + "_tmp";
163
+ try {
164
+ await fs.rm(tempStorePath, { recursive: true, force: true });
165
+ }
166
+ catch { /* may not exist */ }
167
+ effectiveStore = createVectorStore(options.config, tempStorePath, options.dimension);
168
+ logger.debug(`Rebuilding index in temporary store at ${tempStorePath}`);
169
+ }
170
+ else {
171
+ // Fallback — in-memory store or no dimension: clear in place.
172
+ logger.warn("No embedding dimension available; falling back to in-place clear.");
173
+ await options.store.clear();
174
+ }
91
175
  }
92
176
  const stats = createIndexStats(workspaceFiles.length, manifestStatus);
93
177
  stats.rebuildPerformed = rebuildPerformed;
@@ -103,7 +187,8 @@ export async function runIndexPass(options) {
103
187
  const currentPaths = new Set(workspaceFiles.map((file) => file.normalizedPath));
104
188
  let stalePaths;
105
189
  if (filterPaths) {
106
- stalePaths = gitDeletedPaths.map((p) => normalizeFilePath(path.resolve(options.cwd, p)));
190
+ const repoRoot = getRepoRoot(options.cwd) ?? options.cwd;
191
+ stalePaths = gitDeletedPaths.map((p) => normalizeFilePath(path.resolve(repoRoot, p)));
107
192
  }
108
193
  else {
109
194
  stalePaths = Object.keys(manifest.files).filter((p) => !currentPaths.has(p));
@@ -112,7 +197,7 @@ export async function runIndexPass(options) {
112
197
  logger.debug(`Removing ${stalePaths.length} stale files from index...`);
113
198
  const deleteLimit = pLimit(options.config.indexing.concurrency);
114
199
  await Promise.all(stalePaths.map((p) => deleteLimit(async () => {
115
- await options.store.deleteByFilePath(p);
200
+ await effectiveStore.deleteByFilePath(p);
116
201
  options.keywordIndex?.removeByFilePath(p);
117
202
  delete manifest.files[p];
118
203
  stats.deletedFiles++;
@@ -136,143 +221,236 @@ export async function runIndexPass(options) {
136
221
  }
137
222
  }
138
223
  if (stats.deletedFiles > 0) {
139
- logger.info(`Removed ${stats.deletedFiles} deleted files from index.`);
224
+ logger.debug(`Removed ${stats.deletedFiles} deleted files from index.`);
140
225
  }
141
- logger.info(`Processing ${workspaceFiles.length} files (${newCount} new, ${modifiedCount} modified, ${unchangedCount} unchanged)...`);
142
- // ── Phase 1: prepare all files (chunk + description + keyword + textToEmbed) ──
226
+ logger.debug(`Processing ${workspaceFiles.length} files (${newCount} new, ${modifiedCount} modified, ${unchangedCount} unchanged)...`);
227
+ // ── Phase 1: prepare all files (chunk + keyword, defer descriptions if provider present) ──
143
228
  const limit = pLimit(options.config.indexing.concurrency);
144
- let completed = 0;
145
- let started = 0;
146
- const total = workspaceFiles.length;
229
+ const deferDescriptions = !!options.descriptionProvider;
147
230
  const prepared = await Promise.all(workspaceFiles.map((file) => limit(async () => {
148
- const fileLabel = file.normalizedPath;
149
- started++;
150
- logger.info(` Preparing file: ${started} - ${fileLabel}`);
151
- const prep = await prepareFile(file, options.cwd, manifest.files[file.normalizedPath], options.config, options.keywordIndex, options.descriptionProvider, logger);
152
- completed++;
153
- if (total > 20 && completed % 50 === 0) {
154
- logger.info(` Preparing file ${completed}/${total}...`);
231
+ const fileLabel = path.relative(options.cwd, file.normalizedPath).replace(/\\/g, "/");
232
+ const isActive = !file.isEmpty && !file.isTooSmall &&
233
+ (!manifest.files[file.normalizedPath] || manifest.files[file.normalizedPath].hash !== file.hash);
234
+ if (isActive) {
235
+ options.progress?.startFile(fileLabel);
155
236
  }
156
- return prep;
157
- })));
158
- // ── Handle deletions from store for files being re-processed ──
159
- const deletionLimit = pLimit(options.config.indexing.concurrency);
160
- await Promise.all(prepared.map((prep) => deletionLimit(async () => {
161
- if (prep.earlyResult?.isRemoved) {
162
- await options.store.deleteByFilePath(prep.normalizedPath);
163
- options.keywordIndex?.removeByFilePath(prep.normalizedPath);
164
- return;
165
- }
166
- if (prep.isModified && prep.chunks && prep.chunks.length > 0) {
167
- await options.store.deleteByFilePath(prep.normalizedPath);
168
- options.keywordIndex?.removeByFilePath(prep.normalizedPath);
237
+ const prep = await prepareFile(file, options.cwd, manifest.files[file.normalizedPath], options.config, options.keywordIndex, options.descriptionProvider, logger, deferDescriptions);
238
+ if (prep.earlyResult && isActive) {
239
+ options.progress?.finishFile(fileLabel);
169
240
  }
241
+ return prep;
170
242
  })));
171
- // ── Phase 2: global embedding batch ──
172
- const allTextToEmbed = [];
173
- const textPrepIndex = [];
174
- for (let i = 0; i < prepared.length; i++) {
175
- const prep = prepared[i];
176
- if (prep.chunks && prep.textToEmbed && prep.textToEmbed.length > 0) {
177
- for (const text of prep.textToEmbed) {
178
- allTextToEmbed.push(text);
179
- textPrepIndex.push(i);
243
+ // ── Phase 1.5: global description generation (single pool, no nested concurrency) ──
244
+ if (deferDescriptions) {
245
+ const deferredPreps = prepared.filter((p) => p.chunks && p.chunks.length > 0 && p.relPath !== undefined);
246
+ if (deferredPreps.length > 0) {
247
+ const allChunks = [];
248
+ for (const prep of deferredPreps) {
249
+ for (const chunk of prep.chunks) {
250
+ if (chunk.metadata.contentType !== "image") {
251
+ allChunks.push(chunk);
252
+ }
253
+ }
254
+ }
255
+ // Advance progress to Description stage before descriptions start
256
+ for (const prep of deferredPreps) {
257
+ options.progress?.finishStage(prep.fileLabel);
258
+ }
259
+ if (allChunks.length > 0) {
260
+ try {
261
+ const batchResult = await options.descriptionProvider.generateBatchDescriptions(allChunks, (msg) => logger.debug(msg));
262
+ for (const chunk of allChunks) {
263
+ const desc = batchResult.get(chunk.id);
264
+ if (desc && desc.trim().length > 0) {
265
+ chunk.description = desc;
266
+ }
267
+ }
268
+ }
269
+ catch (err) {
270
+ logger.warn(` Global description generation failed: ${err.message}`);
271
+ for (const prep of deferredPreps) {
272
+ if (prep.chunks.some((c) => c.metadata.contentType !== "image")) {
273
+ prep.descriptionFailed = true;
274
+ }
275
+ }
276
+ }
277
+ }
278
+ // Phase 1.6: build textToEmbed with descriptions injected
279
+ for (const prep of deferredPreps) {
280
+ prep.textToEmbed = buildTextsToEmbed(prep.chunks, prep.relPath, prep.metaHeader ?? "", prep.docPrefix ?? "", prep.isImageFile ?? false);
180
281
  }
181
282
  }
182
283
  }
183
- let allEmbeddings = [];
184
- if (allTextToEmbed.length > 0) {
185
- const embedConcurrency = options.config.indexing.embedConcurrency ?? 1;
186
- logger.info(` Embedding ${allTextToEmbed.length} chunks in global batch (concurrency: ${embedConcurrency})...`);
187
- allEmbeddings = await embedBatch(options.embedder, allTextToEmbed, options.config.indexing.embedBatchSize, "document", embedConcurrency);
188
- logger.debug(` Embedding batch complete: ${allEmbeddings.length} vectors`);
284
+ // ── Phase 2+3: per-file delete, embed + store (in parallel) ──
285
+ // Deletions for modified/re-removed files happen per-worker, right before
286
+ // embedding, so that an abort mid-embed doesn't orphan old entries.
287
+ const isOllama = options.embedder.name === "ollama";
288
+ const ollamaMaxBatch = options.config.indexing.ollamaMaxBatchSize ?? 4000;
289
+ const defaultBatchSize = options.config.indexing.embedBatchSize;
290
+ const defaultConcurrency = options.config.indexing.embedConcurrency ?? 1;
291
+ // File metadata look-up for manifest entries
292
+ const fileMeta = new Map(workspaceFiles.map((f) => [f.normalizedPath, { mtime: f.mtime, size: f.size }]));
293
+ // Serialised manifest-save queue — prevents concurrent write races and acts
294
+ // as a checkpoint for Ctrl+C resilience. Each worker appends to this chain
295
+ // after a successful store, so previously completed files are never lost.
296
+ let manifestSaveChain = Promise.resolve(undefined);
297
+ function enqueueManifestSave() {
298
+ manifestSaveChain = manifestSaveChain.then(() => saveManifest(options.storePath, manifest));
189
299
  }
190
- // ── Distribute embeddings back to prepared files ──
191
- const embedByPrep = new Map();
192
- for (let i = 0; i < textPrepIndex.length; i++) {
193
- const prepIdx = textPrepIndex[i];
194
- const emb = allEmbeddings[i];
195
- if (!embedByPrep.has(prepIdx)) {
196
- embedByPrep.set(prepIdx, []);
197
- }
198
- if (emb) {
199
- embedByPrep.get(prepIdx).push(emb);
300
+ const aborted = () => options.abortSignal?.aborted ?? false;
301
+ const embedStoreLimit = pLimit(options.config.indexing.concurrency);
302
+ const workerResults = await Promise.all(prepared.map((prep) => embedStoreLimit(async () => {
303
+ // ── Check abort signal before any work ──
304
+ if (aborted()) {
305
+ return { normalizedPath: prep.normalizedPath, skipped: true };
200
306
  }
201
- }
202
- // ── Phase 3: store all files with their embeddings (batched) ──
203
- logger.debug(`Phase 3: storing chunks for ${prepared.length} files...`);
204
- const allValidChunks = [];
205
- const workerResults = prepared.map((prep, idx) => {
307
+ // ── Early results (empty / too-small / unchanged / chunking failure) ──
206
308
  if (prep.earlyResult) {
309
+ // Remove stale manifest/store entries for files that no longer produce chunks
310
+ if (prep.earlyResult.isRemoved) {
311
+ await effectiveStore.deleteByFilePath(prep.normalizedPath);
312
+ options.keywordIndex?.removeByFilePath(prep.normalizedPath);
313
+ delete manifest.files[prep.normalizedPath];
314
+ enqueueManifestSave();
315
+ }
207
316
  return prep.earlyResult;
208
317
  }
209
- if (!prep.chunks || !prep.textToEmbed) {
318
+ if (!prep.chunks || !prep.textToEmbed || prep.textToEmbed.length === 0) {
319
+ options.progress?.finishFile(prep.fileLabel);
210
320
  return {
211
- normalizedPath: prep.normalizedPath, hash: prep.hash, chunkCount: 0, fileLabel: prep.fileLabel,
212
- isNew: false, isModified: false, isUnchanged: false, isEmpty: false, isTooSmall: false, isRemoved: true, hadChunks: false,
321
+ normalizedPath: prep.normalizedPath, hash: prep.hash, chunkCount: 0,
322
+ fileLabel: prep.fileLabel,
323
+ isNew: false, isModified: false, isUnchanged: false, isEmpty: false,
324
+ isTooSmall: false, isRemoved: true, hadChunks: false,
325
+ descriptionFailed: prep.descriptionFailed,
213
326
  };
214
327
  }
215
- const embeddings = embedByPrep.get(idx) ?? [];
216
- for (let i = 0; i < prep.chunks.length; i++) {
217
- const emb = embeddings[i];
218
- if (Array.isArray(emb) && emb.length > 0 && typeof emb[0] === "number") {
219
- prep.chunks[i].embedding = emb;
328
+ // ── Embed ──
329
+ const batchSize = isOllama
330
+ ? Math.min(prep.textToEmbed.length, ollamaMaxBatch)
331
+ : defaultBatchSize;
332
+ const concurrency = isOllama ? 1 : defaultConcurrency;
333
+ options.progress?.finishStage(prep.fileLabel);
334
+ let embeddings;
335
+ try {
336
+ embeddings = await embedBatch(options.embedder, prep.textToEmbed, batchSize, "document", concurrency);
337
+ }
338
+ catch (err) {
339
+ logger.warn(` ${prep.fileLabel} (embedding failed: ${err.message})`);
340
+ options.progress?.failFile(prep.fileLabel);
341
+ return {
342
+ normalizedPath: prep.normalizedPath, hash: prep.hash, chunkCount: 0,
343
+ fileLabel: prep.fileLabel,
344
+ isNew: false, isModified: false, isUnchanged: false, isEmpty: false,
345
+ isTooSmall: false, isRemoved: true, hadChunks: false,
346
+ descriptionFailed: prep.descriptionFailed,
347
+ };
348
+ }
349
+ // ── Store (new data first; orphan cleanup is handled inside the store) ──
350
+ const result = await storeFileChunks(prep, embeddings, effectiveStore, logger);
351
+ // ── Update manifest in-memory and enqueue an atomic save ──
352
+ if (result.chunkCount > 0 && !result.isRemoved) {
353
+ const meta = fileMeta.get(result.normalizedPath);
354
+ manifest.files[result.normalizedPath] = {
355
+ hash: result.hash,
356
+ chunkCount: result.chunkCount,
357
+ indexedAt: Date.now(),
358
+ mtime: meta?.mtime,
359
+ size: meta?.size,
360
+ descriptionFailed: result.descriptionFailed,
361
+ };
362
+ enqueueManifestSave();
363
+ }
364
+ else if (result.isRemoved) {
365
+ delete manifest.files[result.normalizedPath];
366
+ enqueueManifestSave();
367
+ }
368
+ options.progress?.finishFile(prep.fileLabel);
369
+ return result;
370
+ })));
371
+ // Strip skipped sentinels
372
+ const finalResults = [];
373
+ for (const r of workerResults) {
374
+ if (r.skipped)
375
+ break;
376
+ finalResults.push(r);
377
+ }
378
+ // Drain any in-flight manifest saves so all file entries are durable
379
+ await manifestSaveChain;
380
+ // Update mtime/size for unchanged files (speeds up the next scan)
381
+ for (const { normalizedPath, mtime, size } of workspaceFiles) {
382
+ const entry = manifest.files[normalizedPath];
383
+ if (entry && (mtime !== undefined || size !== undefined)) {
384
+ entry.mtime = mtime;
385
+ entry.size = size;
386
+ }
387
+ }
388
+ // ── Aggregate stats ──
389
+ aggregateStats(stats, finalResults);
390
+ // Update timestamps; advance lastGitCommit ONLY on a complete pass
391
+ manifest.lastIndexedAt = Date.now();
392
+ if (!aborted()) {
393
+ tryUpdateLastGitCommit(options.cwd, manifest);
394
+ }
395
+ // ── Atomically promote temp store if a full rebuild was performed ──
396
+ if (tempStorePath) {
397
+ if (!aborted()) {
398
+ try {
399
+ await effectiveStore.close();
400
+ await options.store.close();
401
+ // Swap the newly-built temp directory into the real path
402
+ await swapStoreDirectories(tempStorePath, options.storePath);
403
+ // Re-open the original store handle so callers can search the new data
404
+ if (typeof options.store.reopen === "function") {
405
+ await options.store.reopen(options.storePath);
406
+ }
407
+ logger.debug(`Promoted temporary store ${tempStorePath} → ${options.storePath}`);
220
408
  }
221
- else {
222
- prep.chunks[i].embedding = undefined;
409
+ catch (err) {
410
+ logger.warn(`Could not promote temporary store: ${err.message}. ` +
411
+ `Original data preserved at ${options.storePath}`);
412
+ try {
413
+ await fs.rm(tempStorePath, { recursive: true, force: true });
414
+ }
415
+ catch { }
223
416
  }
224
417
  }
225
- const validChunks = prep.chunks.filter((c) => c.embedding && c.embedding.length > 0);
226
- allValidChunks.push(...validChunks);
227
- logger.info(` ${prep.fileLabel} (${prep.chunks.length} chunks${prep.isModified ? ", modified" : ", new"})`);
228
- return {
229
- normalizedPath: prep.normalizedPath,
230
- hash: prep.hash,
231
- chunkCount: prep.chunks.length,
232
- fileLabel: prep.fileLabel,
233
- isNew: !prep.isModified,
234
- isModified: prep.isModified,
235
- isUnchanged: false,
236
- isEmpty: false,
237
- isTooSmall: false,
238
- isRemoved: false,
239
- hadChunks: prep.chunks.length > 0,
240
- };
241
- });
242
- if (allValidChunks.length > 0) {
243
- const STORE_BATCH = 1000;
244
- for (let i = 0; i < allValidChunks.length; i += STORE_BATCH) {
245
- const batch = allValidChunks.slice(i, i + STORE_BATCH);
246
- await options.store.addChunks(batch);
418
+ else {
419
+ // Aborted — discard temp, keep original data intact
420
+ effectiveStore.close().catch(() => { });
421
+ try {
422
+ await fs.rm(tempStorePath, { recursive: true, force: true });
423
+ }
424
+ catch { }
425
+ logger.debug("Index pass cancelled; discarded temporary store.");
247
426
  }
248
- logger.debug(` Stored ${allValidChunks.length} chunks in batched writes`);
249
427
  }
250
- // ── Aggregate stats and update manifest ──
251
- aggregateStats(stats, workerResults, manifest, workspaceFiles);
252
- manifest.lastIndexedAt = Date.now();
253
- tryUpdateLastGitCommit(options.cwd, manifest);
428
+ // Save manifest and keyword index (always to the real store path — after
429
+ // a successful swap this points to the new data; after an abort it's the old).
254
430
  await saveManifest(options.storePath, manifest);
255
431
  await options.keywordIndex?.save(options.storePath);
256
- stats.finalCount = await options.store.count();
432
+ // Count from the store — after a successful swap, the original handle has
433
+ // been reopened pointing to the new directory.
434
+ try {
435
+ stats.finalCount = await options.store.count();
436
+ }
437
+ catch {
438
+ stats.finalCount = (tempStorePath ? 0 : stats.totalChunks);
439
+ }
257
440
  return stats;
258
441
  }
259
- function aggregateStats(stats, results, manifest, workspaceFiles) {
260
- const fileMeta = new Map(workspaceFiles.map((f) => [f.normalizedPath, { mtime: f.mtime, size: f.size }]));
442
+ function aggregateStats(stats, results) {
261
443
  for (const result of results) {
262
444
  if (result.isEmpty) {
263
445
  stats.skippedEmptyFiles++;
264
- if (result.isRemoved) {
265
- delete manifest.files[result.normalizedPath];
446
+ if (result.isRemoved)
266
447
  stats.removedFiles++;
267
- }
268
448
  continue;
269
449
  }
270
450
  if (result.isTooSmall) {
271
451
  stats.skippedSmallFiles++;
272
- if (result.isRemoved) {
273
- delete manifest.files[result.normalizedPath];
452
+ if (result.isRemoved)
274
453
  stats.removedFiles++;
275
- }
276
454
  continue;
277
455
  }
278
456
  if (result.isUnchanged) {
@@ -280,7 +458,6 @@ function aggregateStats(stats, results, manifest, workspaceFiles) {
280
458
  continue;
281
459
  }
282
460
  if (result.isRemoved) {
283
- delete manifest.files[result.normalizedPath];
284
461
  stats.removedFiles++;
285
462
  continue;
286
463
  }
@@ -291,65 +468,59 @@ function aggregateStats(stats, results, manifest, workspaceFiles) {
291
468
  stats.newFiles++;
292
469
  }
293
470
  if (result.chunkCount > 0) {
294
- const meta = fileMeta.get(result.normalizedPath);
295
- manifest.files[result.normalizedPath] = {
296
- hash: result.hash,
297
- chunkCount: result.chunkCount,
298
- indexedAt: Date.now(),
299
- mtime: meta?.mtime,
300
- size: meta?.size,
301
- };
471
+ if (result.descriptionFailed) {
472
+ stats.descriptionFailedFiles++;
473
+ }
302
474
  stats.totalChunks += result.chunkCount;
303
475
  stats.batchesFlushed++;
304
476
  }
305
477
  }
306
- for (const { normalizedPath, mtime, size } of workspaceFiles) {
307
- const entry = manifest.files[normalizedPath];
308
- if (entry && (mtime !== undefined || size !== undefined)) {
309
- entry.mtime = mtime;
310
- entry.size = size;
311
- }
312
- }
313
478
  }
314
- export async function getIndexStatusSummary(cwd, storePath, config, store) {
479
+ export async function getIndexStatusSummary(cwd, storePath, config, store, skipScan) {
315
480
  const loadResult = await loadManifest(storePath);
316
481
  const manifest = loadResult.manifest;
317
- const workspaceFiles = await scanWorkspaceFiles(cwd, config, undefined, manifest);
318
482
  const storeCount = await store.count();
319
483
  if (loadResult.status !== "ok") {
320
484
  return {
321
485
  manifestStatus: loadResult.status,
322
486
  manifestEntries: 0,
323
487
  upToDateFiles: 0,
324
- pendingFiles: workspaceFiles.length,
488
+ pendingFiles: 0,
325
489
  rebuildRequired: storeCount > 0,
326
490
  storeChunkCount: storeCount,
327
491
  manifestExpectedChunks: 0,
328
492
  };
329
493
  }
330
- const currentPaths = new Set(workspaceFiles.map((file) => file.normalizedPath));
331
494
  let upToDateFiles = 0;
332
495
  let pendingFiles = 0;
333
- for (const file of workspaceFiles) {
334
- const previous = manifest.files[file.normalizedPath];
335
- if (file.isEmpty || file.isTooSmall) {
336
- if (previous)
496
+ let manifestExpectedChunks = 0;
497
+ if (!skipScan) {
498
+ const workspaceFiles = await scanWorkspaceFiles(cwd, config, undefined, manifest);
499
+ const currentPaths = new Set(workspaceFiles.map((file) => file.normalizedPath));
500
+ for (const file of workspaceFiles) {
501
+ const previous = manifest.files[file.normalizedPath];
502
+ if (file.isEmpty || file.isTooSmall) {
503
+ if (previous)
504
+ pendingFiles++;
505
+ continue;
506
+ }
507
+ if (previous && previous.hash === file.hash) {
508
+ upToDateFiles++;
509
+ }
510
+ else {
337
511
  pendingFiles++;
338
- continue;
339
- }
340
- if (previous && previous.hash === file.hash) {
341
- upToDateFiles++;
512
+ }
342
513
  }
343
- else {
344
- pendingFiles++;
514
+ for (const indexedPath of Object.keys(manifest.files)) {
515
+ if (!currentPaths.has(indexedPath)) {
516
+ pendingFiles++;
517
+ }
345
518
  }
519
+ manifestExpectedChunks = Object.values(manifest.files).reduce((sum, entry) => sum + entry.chunkCount, 0);
346
520
  }
347
- for (const indexedPath of Object.keys(manifest.files)) {
348
- if (!currentPaths.has(indexedPath)) {
349
- pendingFiles++;
350
- }
521
+ else {
522
+ manifestExpectedChunks = Object.values(manifest.files).reduce((sum, entry) => sum + entry.chunkCount, 0);
351
523
  }
352
- const manifestExpectedChunks = Object.values(manifest.files).reduce((sum, entry) => sum + entry.chunkCount, 0);
353
524
  return {
354
525
  manifestStatus: loadResult.status,
355
526
  manifestEntries: Object.keys(manifest.files).length,