@cdoing/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/dist/agents/coordinator.d.ts +114 -0
  2. package/dist/agents/coordinator.d.ts.map +1 -0
  3. package/dist/agents/coordinator.js +158 -0
  4. package/dist/agents/coordinator.js.map +1 -0
  5. package/dist/context-providers/clipboard.d.ts +13 -0
  6. package/dist/context-providers/clipboard.d.ts.map +1 -0
  7. package/dist/context-providers/clipboard.js +53 -0
  8. package/dist/context-providers/clipboard.js.map +1 -0
  9. package/dist/context-providers/codebase.d.ts +46 -0
  10. package/dist/context-providers/codebase.d.ts.map +1 -0
  11. package/dist/context-providers/codebase.js +273 -0
  12. package/dist/context-providers/codebase.js.map +1 -0
  13. package/dist/context-providers/diff.d.ts +18 -0
  14. package/dist/context-providers/diff.d.ts.map +1 -0
  15. package/dist/context-providers/diff.js +63 -0
  16. package/dist/context-providers/diff.js.map +1 -0
  17. package/dist/context-providers/docs.d.ts +21 -0
  18. package/dist/context-providers/docs.d.ts.map +1 -0
  19. package/dist/context-providers/docs.js +180 -0
  20. package/dist/context-providers/docs.js.map +1 -0
  21. package/dist/context-providers/file-include.d.ts +13 -0
  22. package/dist/context-providers/file-include.d.ts.map +1 -0
  23. package/dist/context-providers/file-include.js +82 -0
  24. package/dist/context-providers/file-include.js.map +1 -0
  25. package/dist/context-providers/folder.d.ts +19 -0
  26. package/dist/context-providers/folder.d.ts.map +1 -0
  27. package/dist/context-providers/folder.js +130 -0
  28. package/dist/context-providers/folder.js.map +1 -0
  29. package/dist/context-providers/git.d.ts +19 -0
  30. package/dist/context-providers/git.d.ts.map +1 -0
  31. package/dist/context-providers/git.js +74 -0
  32. package/dist/context-providers/git.js.map +1 -0
  33. package/dist/context-providers/index.d.ts +26 -0
  34. package/dist/context-providers/index.d.ts.map +1 -0
  35. package/dist/context-providers/index.js +37 -0
  36. package/dist/context-providers/index.js.map +1 -0
  37. package/dist/context-providers/open-files.d.ts +25 -0
  38. package/dist/context-providers/open-files.d.ts.map +1 -0
  39. package/dist/context-providers/open-files.js +134 -0
  40. package/dist/context-providers/open-files.js.map +1 -0
  41. package/dist/context-providers/problems.d.ts +24 -0
  42. package/dist/context-providers/problems.d.ts.map +1 -0
  43. package/dist/context-providers/problems.js +97 -0
  44. package/dist/context-providers/problems.js.map +1 -0
  45. package/dist/context-providers/registry.d.ts +61 -0
  46. package/dist/context-providers/registry.d.ts.map +1 -0
  47. package/dist/context-providers/registry.js +92 -0
  48. package/dist/context-providers/registry.js.map +1 -0
  49. package/dist/context-providers/terminal.d.ts +25 -0
  50. package/dist/context-providers/terminal.d.ts.map +1 -0
  51. package/dist/context-providers/terminal.js +55 -0
  52. package/dist/context-providers/terminal.js.map +1 -0
  53. package/dist/context-providers/tree.d.ts +29 -0
  54. package/dist/context-providers/tree.d.ts.map +1 -0
  55. package/dist/context-providers/tree.js +172 -0
  56. package/dist/context-providers/tree.js.map +1 -0
  57. package/dist/context-providers/types.d.ts +72 -0
  58. package/dist/context-providers/types.d.ts.map +1 -0
  59. package/dist/context-providers/types.js +10 -0
  60. package/dist/context-providers/types.js.map +1 -0
  61. package/dist/context-providers/url.d.ts +27 -0
  62. package/dist/context-providers/url.d.ts.map +1 -0
  63. package/dist/context-providers/url.js +131 -0
  64. package/dist/context-providers/url.js.map +1 -0
  65. package/dist/effort/index.d.ts +78 -0
  66. package/dist/effort/index.d.ts.map +1 -0
  67. package/dist/effort/index.js +146 -0
  68. package/dist/effort/index.js.map +1 -0
  69. package/dist/hooks/index.d.ts +47 -0
  70. package/dist/hooks/index.d.ts.map +1 -0
  71. package/dist/hooks/index.js +151 -0
  72. package/dist/hooks/index.js.map +1 -0
  73. package/dist/index.d.ts +75 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +152 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/indexing/chunker.d.ts +25 -0
  78. package/dist/indexing/chunker.d.ts.map +1 -0
  79. package/dist/indexing/chunker.js +217 -0
  80. package/dist/indexing/chunker.js.map +1 -0
  81. package/dist/indexing/database.d.ts +49 -0
  82. package/dist/indexing/database.d.ts.map +1 -0
  83. package/dist/indexing/database.js +287 -0
  84. package/dist/indexing/database.js.map +1 -0
  85. package/dist/indexing/index.d.ts +9 -0
  86. package/dist/indexing/index.d.ts.map +1 -0
  87. package/dist/indexing/index.js +13 -0
  88. package/dist/indexing/index.js.map +1 -0
  89. package/dist/indexing/indexer.d.ts +63 -0
  90. package/dist/indexing/indexer.d.ts.map +1 -0
  91. package/dist/indexing/indexer.js +352 -0
  92. package/dist/indexing/indexer.js.map +1 -0
  93. package/dist/indexing/recent-edits-cache.d.ts +77 -0
  94. package/dist/indexing/recent-edits-cache.d.ts.map +1 -0
  95. package/dist/indexing/recent-edits-cache.js +123 -0
  96. package/dist/indexing/recent-edits-cache.js.map +1 -0
  97. package/dist/indexing/types.d.ts +39 -0
  98. package/dist/indexing/types.d.ts.map +1 -0
  99. package/dist/indexing/types.js +6 -0
  100. package/dist/indexing/types.js.map +1 -0
  101. package/dist/mcp/index.d.ts +33 -0
  102. package/dist/mcp/index.d.ts.map +1 -0
  103. package/dist/mcp/index.js +37 -0
  104. package/dist/mcp/index.js.map +1 -0
  105. package/dist/mcp/manager.d.ts +123 -0
  106. package/dist/mcp/manager.d.ts.map +1 -0
  107. package/dist/mcp/manager.js +331 -0
  108. package/dist/mcp/manager.js.map +1 -0
  109. package/dist/oauth.d.ts +33 -0
  110. package/dist/oauth.d.ts.map +1 -0
  111. package/dist/oauth.js +312 -0
  112. package/dist/oauth.js.map +1 -0
  113. package/dist/permissions/index.d.ts +216 -0
  114. package/dist/permissions/index.d.ts.map +1 -0
  115. package/dist/permissions/index.js +938 -0
  116. package/dist/permissions/index.js.map +1 -0
  117. package/dist/plan/index.d.ts +20 -0
  118. package/dist/plan/index.d.ts.map +1 -0
  119. package/dist/plan/index.js +24 -0
  120. package/dist/plan/index.js.map +1 -0
  121. package/dist/plan/manager.d.ts +101 -0
  122. package/dist/plan/manager.d.ts.map +1 -0
  123. package/dist/plan/manager.js +170 -0
  124. package/dist/plan/manager.js.map +1 -0
  125. package/dist/rules/index.d.ts +28 -0
  126. package/dist/rules/index.d.ts.map +1 -0
  127. package/dist/rules/index.js +31 -0
  128. package/dist/rules/index.js.map +1 -0
  129. package/dist/rules/manager.d.ts +77 -0
  130. package/dist/rules/manager.d.ts.map +1 -0
  131. package/dist/rules/manager.js +279 -0
  132. package/dist/rules/manager.js.map +1 -0
  133. package/dist/rules/types.d.ts +34 -0
  134. package/dist/rules/types.d.ts.map +1 -0
  135. package/dist/rules/types.js +9 -0
  136. package/dist/rules/types.js.map +1 -0
  137. package/dist/sandbox/filesystem.d.ts +20 -0
  138. package/dist/sandbox/filesystem.d.ts.map +1 -0
  139. package/dist/sandbox/filesystem.js +141 -0
  140. package/dist/sandbox/filesystem.js.map +1 -0
  141. package/dist/sandbox/index.d.ts +4 -0
  142. package/dist/sandbox/index.d.ts.map +1 -0
  143. package/dist/sandbox/index.js +8 -0
  144. package/dist/sandbox/index.js.map +1 -0
  145. package/dist/sandbox/manager.d.ts +47 -0
  146. package/dist/sandbox/manager.d.ts.map +1 -0
  147. package/dist/sandbox/manager.js +220 -0
  148. package/dist/sandbox/manager.js.map +1 -0
  149. package/dist/sandbox/network.d.ts +14 -0
  150. package/dist/sandbox/network.d.ts.map +1 -0
  151. package/dist/sandbox/network.js +87 -0
  152. package/dist/sandbox/network.js.map +1 -0
  153. package/dist/sandbox/types.d.ts +42 -0
  154. package/dist/sandbox/types.d.ts.map +1 -0
  155. package/dist/sandbox/types.js +25 -0
  156. package/dist/sandbox/types.js.map +1 -0
  157. package/dist/tools/ast-edit.d.ts +57 -0
  158. package/dist/tools/ast-edit.d.ts.map +1 -0
  159. package/dist/tools/ast-edit.js +443 -0
  160. package/dist/tools/ast-edit.js.map +1 -0
  161. package/dist/tools/code-verify.d.ts +8 -0
  162. package/dist/tools/code-verify.d.ts.map +1 -0
  163. package/dist/tools/code-verify.js +159 -0
  164. package/dist/tools/code-verify.js.map +1 -0
  165. package/dist/tools/codebase-search.d.ts +17 -0
  166. package/dist/tools/codebase-search.d.ts.map +1 -0
  167. package/dist/tools/codebase-search.js +104 -0
  168. package/dist/tools/codebase-search.js.map +1 -0
  169. package/dist/tools/file-delete.d.ts +26 -0
  170. package/dist/tools/file-delete.d.ts.map +1 -0
  171. package/dist/tools/file-delete.js +179 -0
  172. package/dist/tools/file-delete.js.map +1 -0
  173. package/dist/tools/file-edit.d.ts +10 -0
  174. package/dist/tools/file-edit.d.ts.map +1 -0
  175. package/dist/tools/file-edit.js +138 -0
  176. package/dist/tools/file-edit.js.map +1 -0
  177. package/dist/tools/file-read.d.ts +12 -0
  178. package/dist/tools/file-read.d.ts.map +1 -0
  179. package/dist/tools/file-read.js +211 -0
  180. package/dist/tools/file-read.js.map +1 -0
  181. package/dist/tools/file-run.d.ts +10 -0
  182. package/dist/tools/file-run.d.ts.map +1 -0
  183. package/dist/tools/file-run.js +179 -0
  184. package/dist/tools/file-run.js.map +1 -0
  185. package/dist/tools/file-write.d.ts +10 -0
  186. package/dist/tools/file-write.d.ts.map +1 -0
  187. package/dist/tools/file-write.js +134 -0
  188. package/dist/tools/file-write.js.map +1 -0
  189. package/dist/tools/glob-search.d.ts +8 -0
  190. package/dist/tools/glob-search.d.ts.map +1 -0
  191. package/dist/tools/glob-search.js +108 -0
  192. package/dist/tools/glob-search.js.map +1 -0
  193. package/dist/tools/grep-search.d.ts +8 -0
  194. package/dist/tools/grep-search.d.ts.map +1 -0
  195. package/dist/tools/grep-search.js +139 -0
  196. package/dist/tools/grep-search.js.map +1 -0
  197. package/dist/tools/list-dir.d.ts +16 -0
  198. package/dist/tools/list-dir.d.ts.map +1 -0
  199. package/dist/tools/list-dir.js +183 -0
  200. package/dist/tools/list-dir.js.map +1 -0
  201. package/dist/tools/multi-edit.d.ts +16 -0
  202. package/dist/tools/multi-edit.d.ts.map +1 -0
  203. package/dist/tools/multi-edit.js +163 -0
  204. package/dist/tools/multi-edit.js.map +1 -0
  205. package/dist/tools/notebook-edit.d.ts +31 -0
  206. package/dist/tools/notebook-edit.d.ts.map +1 -0
  207. package/dist/tools/notebook-edit.js +321 -0
  208. package/dist/tools/notebook-edit.js.map +1 -0
  209. package/dist/tools/registry.d.ts +16 -0
  210. package/dist/tools/registry.d.ts.map +1 -0
  211. package/dist/tools/registry.js +41 -0
  212. package/dist/tools/registry.js.map +1 -0
  213. package/dist/tools/shell-exec.d.ts +12 -0
  214. package/dist/tools/shell-exec.d.ts.map +1 -0
  215. package/dist/tools/shell-exec.js +261 -0
  216. package/dist/tools/shell-exec.js.map +1 -0
  217. package/dist/tools/sub-agent-manager.d.ts +57 -0
  218. package/dist/tools/sub-agent-manager.d.ts.map +1 -0
  219. package/dist/tools/sub-agent-manager.js +153 -0
  220. package/dist/tools/sub-agent-manager.js.map +1 -0
  221. package/dist/tools/sub-agent-status.d.ts +12 -0
  222. package/dist/tools/sub-agent-status.d.ts.map +1 -0
  223. package/dist/tools/sub-agent-status.js +59 -0
  224. package/dist/tools/sub-agent-status.js.map +1 -0
  225. package/dist/tools/sub-agent-terminate.d.ts +12 -0
  226. package/dist/tools/sub-agent-terminate.d.ts.map +1 -0
  227. package/dist/tools/sub-agent-terminate.js +55 -0
  228. package/dist/tools/sub-agent-terminate.js.map +1 -0
  229. package/dist/tools/sub-agent.d.ts +34 -0
  230. package/dist/tools/sub-agent.d.ts.map +1 -0
  231. package/dist/tools/sub-agent.js +140 -0
  232. package/dist/tools/sub-agent.js.map +1 -0
  233. package/dist/tools/system-info.d.ts +24 -0
  234. package/dist/tools/system-info.d.ts.map +1 -0
  235. package/dist/tools/system-info.js +220 -0
  236. package/dist/tools/system-info.js.map +1 -0
  237. package/dist/tools/todo.d.ts +16 -0
  238. package/dist/tools/todo.d.ts.map +1 -0
  239. package/dist/tools/todo.js +144 -0
  240. package/dist/tools/todo.js.map +1 -0
  241. package/dist/tools/types.d.ts +20 -0
  242. package/dist/tools/types.d.ts.map +1 -0
  243. package/dist/tools/types.js +3 -0
  244. package/dist/tools/types.js.map +1 -0
  245. package/dist/tools/view-diff.d.ts +11 -0
  246. package/dist/tools/view-diff.d.ts.map +1 -0
  247. package/dist/tools/view-diff.js +88 -0
  248. package/dist/tools/view-diff.js.map +1 -0
  249. package/dist/tools/view-repo-map.d.ts +18 -0
  250. package/dist/tools/view-repo-map.d.ts.map +1 -0
  251. package/dist/tools/view-repo-map.js +245 -0
  252. package/dist/tools/view-repo-map.js.map +1 -0
  253. package/dist/tools/web-fetch.d.ts +13 -0
  254. package/dist/tools/web-fetch.d.ts.map +1 -0
  255. package/dist/tools/web-fetch.js +106 -0
  256. package/dist/tools/web-fetch.js.map +1 -0
  257. package/dist/tools/web-search.d.ts +10 -0
  258. package/dist/tools/web-search.d.ts.map +1 -0
  259. package/dist/tools/web-search.js +106 -0
  260. package/dist/tools/web-search.js.map +1 -0
  261. package/dist/utils/gitignore.d.ts +10 -0
  262. package/dist/utils/gitignore.d.ts.map +1 -0
  263. package/dist/utils/gitignore.js +104 -0
  264. package/dist/utils/gitignore.js.map +1 -0
  265. package/dist/utils/lazy-apply.d.ts +45 -0
  266. package/dist/utils/lazy-apply.d.ts.map +1 -0
  267. package/dist/utils/lazy-apply.js +164 -0
  268. package/dist/utils/lazy-apply.js.map +1 -0
  269. package/dist/utils/memory.d.ts +36 -0
  270. package/dist/utils/memory.d.ts.map +1 -0
  271. package/dist/utils/memory.js +136 -0
  272. package/dist/utils/memory.js.map +1 -0
  273. package/dist/utils/path-matching.d.ts +24 -0
  274. package/dist/utils/path-matching.d.ts.map +1 -0
  275. package/dist/utils/path-matching.js +116 -0
  276. package/dist/utils/path-matching.js.map +1 -0
  277. package/dist/utils/path-safety.d.ts +13 -0
  278. package/dist/utils/path-safety.d.ts.map +1 -0
  279. package/dist/utils/path-safety.js +54 -0
  280. package/dist/utils/path-safety.js.map +1 -0
  281. package/dist/utils/project-config.d.ts +18 -0
  282. package/dist/utils/project-config.d.ts.map +1 -0
  283. package/dist/utils/project-config.js +76 -0
  284. package/dist/utils/project-config.js.map +1 -0
  285. package/dist/utils/search-match.d.ts +63 -0
  286. package/dist/utils/search-match.d.ts.map +1 -0
  287. package/dist/utils/search-match.js +426 -0
  288. package/dist/utils/search-match.js.map +1 -0
  289. package/dist/utils/shell-paths.d.ts +17 -0
  290. package/dist/utils/shell-paths.d.ts.map +1 -0
  291. package/dist/utils/shell-paths.js +107 -0
  292. package/dist/utils/shell-paths.js.map +1 -0
  293. package/dist/utils/streaming-diff.d.ts +45 -0
  294. package/dist/utils/streaming-diff.d.ts.map +1 -0
  295. package/dist/utils/streaming-diff.js +230 -0
  296. package/dist/utils/streaming-diff.js.map +1 -0
  297. package/dist/utils/todo.d.ts +47 -0
  298. package/dist/utils/todo.d.ts.map +1 -0
  299. package/dist/utils/todo.js +102 -0
  300. package/dist/utils/todo.js.map +1 -0
  301. package/package.json +23 -0
  302. package/src/agents/coordinator.ts +240 -0
  303. package/src/context-providers/clipboard.ts +48 -0
  304. package/src/context-providers/codebase.ts +274 -0
  305. package/src/context-providers/diff.ts +66 -0
  306. package/src/context-providers/docs.ts +160 -0
  307. package/src/context-providers/file-include.ts +54 -0
  308. package/src/context-providers/folder.ts +106 -0
  309. package/src/context-providers/git.ts +72 -0
  310. package/src/context-providers/index.ts +26 -0
  311. package/src/context-providers/open-files.ts +113 -0
  312. package/src/context-providers/problems.ts +100 -0
  313. package/src/context-providers/registry.ts +99 -0
  314. package/src/context-providers/terminal.ts +58 -0
  315. package/src/context-providers/tree.ts +161 -0
  316. package/src/context-providers/types.ts +84 -0
  317. package/src/context-providers/url.ts +138 -0
  318. package/src/effort/index.ts +177 -0
  319. package/src/hooks/index.ts +148 -0
  320. package/src/index.ts +114 -0
  321. package/src/indexing/README.md +267 -0
  322. package/src/indexing/chunker.ts +206 -0
  323. package/src/indexing/database.ts +299 -0
  324. package/src/indexing/index.ts +15 -0
  325. package/src/indexing/indexer.ts +383 -0
  326. package/src/indexing/recent-edits-cache.ts +150 -0
  327. package/src/indexing/types.ts +44 -0
  328. package/src/mcp/index.ts +33 -0
  329. package/src/mcp/manager.ts +385 -0
  330. package/src/oauth.ts +330 -0
  331. package/src/permissions/index.ts +1011 -0
  332. package/src/plan/index.ts +20 -0
  333. package/src/plan/manager.ts +233 -0
  334. package/src/rules/index.ts +28 -0
  335. package/src/rules/manager.ts +276 -0
  336. package/src/rules/types.ts +40 -0
  337. package/src/sandbox/filesystem.ts +135 -0
  338. package/src/sandbox/index.ts +9 -0
  339. package/src/sandbox/manager.ts +213 -0
  340. package/src/sandbox/network.ts +101 -0
  341. package/src/sandbox/types.ts +63 -0
  342. package/src/tools/ast-edit.ts +493 -0
  343. package/src/tools/code-verify.ts +143 -0
  344. package/src/tools/codebase-search.ts +117 -0
  345. package/src/tools/file-delete.ts +155 -0
  346. package/src/tools/file-edit.ts +115 -0
  347. package/src/tools/file-read.ts +195 -0
  348. package/src/tools/file-run.ts +158 -0
  349. package/src/tools/file-write.ts +104 -0
  350. package/src/tools/glob-search.ts +80 -0
  351. package/src/tools/grep-search.ts +120 -0
  352. package/src/tools/list-dir.ts +172 -0
  353. package/src/tools/multi-edit.ts +138 -0
  354. package/src/tools/notebook-edit.ts +342 -0
  355. package/src/tools/registry.ts +43 -0
  356. package/src/tools/shell-exec.ts +251 -0
  357. package/src/tools/sub-agent-manager.ts +183 -0
  358. package/src/tools/sub-agent-status.ts +67 -0
  359. package/src/tools/sub-agent-terminate.ts +62 -0
  360. package/src/tools/sub-agent.ts +162 -0
  361. package/src/tools/system-info.ts +248 -0
  362. package/src/tools/todo.ts +149 -0
  363. package/src/tools/types.ts +21 -0
  364. package/src/tools/view-diff.ts +99 -0
  365. package/src/tools/view-repo-map.ts +249 -0
  366. package/src/tools/web-fetch.ts +118 -0
  367. package/src/tools/web-search.ts +129 -0
  368. package/src/utils/gitignore.ts +73 -0
  369. package/src/utils/lazy-apply.ts +189 -0
  370. package/src/utils/memory.ts +124 -0
  371. package/src/utils/path-matching.ts +84 -0
  372. package/src/utils/path-safety.ts +19 -0
  373. package/src/utils/project-config.ts +41 -0
  374. package/src/utils/search-match.ts +495 -0
  375. package/src/utils/shell-paths.ts +79 -0
  376. package/src/utils/streaming-diff.ts +260 -0
  377. package/src/utils/todo.ts +115 -0
  378. package/tsconfig.json +18 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Index Database — SQLite storage for codebase index.
3
+ *
4
+ * Tables:
5
+ * - chunks: file chunks with content and line ranges
6
+ * - fts: FTS5 virtual table for full-text search (trigram tokenizer)
7
+ * - fts_metadata: links FTS entries to chunks
8
+ * - embeddings: vector embeddings stored as JSON arrays
9
+ * - index_catalog: tracks which files have been indexed (for incremental updates)
10
+ */
11
+
12
+ import Database from "better-sqlite3";
13
+ import * as path from "path";
14
+ import * as os from "os";
15
+ import * as fs from "fs";
16
+ import * as crypto from "crypto";
17
+ import type { ChunkWithMeta, SearchResult, IndexStats } from "./types";
18
+
19
+ const INDEX_DIR = path.join(os.homedir(), ".cdoing");
20
+ const INDEX_FILE = path.join(INDEX_DIR, "index.sqlite");
21
+
22
+ export class IndexDatabase {
23
+ private db: Database.Database;
24
+
25
+ constructor(dbPath?: string) {
26
+ const filePath = dbPath || INDEX_FILE;
27
+ const dir = path.dirname(filePath);
28
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
29
+
30
+ this.db = new Database(filePath);
31
+ this.db.pragma("journal_mode = WAL");
32
+ this.db.pragma("synchronous = NORMAL");
33
+ this.initTables();
34
+ }
35
+
36
+ private initTables(): void {
37
+ this.db.exec(`
38
+ CREATE TABLE IF NOT EXISTS chunks (
39
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
40
+ path TEXT NOT NULL,
41
+ cacheKey TEXT NOT NULL,
42
+ content TEXT NOT NULL,
43
+ startLine INTEGER NOT NULL,
44
+ endLine INTEGER NOT NULL,
45
+ idx INTEGER NOT NULL DEFAULT 0,
46
+ UNIQUE(path, cacheKey, startLine, endLine)
47
+ );
48
+
49
+ CREATE INDEX IF NOT EXISTS idx_chunks_path ON chunks(path);
50
+ CREATE INDEX IF NOT EXISTS idx_chunks_cacheKey ON chunks(cacheKey);
51
+
52
+ CREATE VIRTUAL TABLE IF NOT EXISTS fts USING fts5(
53
+ path,
54
+ content,
55
+ tokenize = 'trigram'
56
+ );
57
+
58
+ CREATE TABLE IF NOT EXISTS fts_metadata (
59
+ id INTEGER PRIMARY KEY,
60
+ path TEXT NOT NULL,
61
+ cacheKey TEXT NOT NULL,
62
+ chunkId INTEGER NOT NULL,
63
+ FOREIGN KEY (chunkId) REFERENCES chunks(id) ON DELETE CASCADE
64
+ );
65
+
66
+ CREATE TABLE IF NOT EXISTS embeddings (
67
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
68
+ chunkId INTEGER NOT NULL,
69
+ path TEXT NOT NULL,
70
+ cacheKey TEXT NOT NULL,
71
+ vector TEXT NOT NULL,
72
+ model TEXT NOT NULL,
73
+ FOREIGN KEY (chunkId) REFERENCES chunks(id) ON DELETE CASCADE,
74
+ UNIQUE(chunkId, model)
75
+ );
76
+
77
+ CREATE TABLE IF NOT EXISTS index_catalog (
78
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
79
+ path TEXT NOT NULL,
80
+ cacheKey TEXT NOT NULL,
81
+ lastUpdated INTEGER NOT NULL,
82
+ directory TEXT NOT NULL,
83
+ UNIQUE(path, directory)
84
+ );
85
+ `);
86
+ }
87
+
88
+ // ── Cache key ─────────────────────────────────────────────────────────────
89
+
90
+ static cacheKey(content: string): string {
91
+ return crypto.createHash("sha256").update(content).digest("hex").substring(0, 16);
92
+ }
93
+
94
+ // ── Chunk operations ──────────────────────────────────────────────────────
95
+
96
+ insertChunks(chunks: ChunkWithMeta[]): number[] {
97
+ const insert = this.db.prepare(
98
+ `INSERT OR IGNORE INTO chunks (path, cacheKey, content, startLine, endLine, idx) VALUES (?, ?, ?, ?, ?, ?)`
99
+ );
100
+ const ids: number[] = [];
101
+
102
+ const tx = this.db.transaction(() => {
103
+ for (let i = 0; i < chunks.length; i++) {
104
+ const c = chunks[i];
105
+ const info = insert.run(c.path, c.cacheKey, c.content, c.startLine, c.endLine, i);
106
+ ids.push(Number(info.lastInsertRowid));
107
+ }
108
+ });
109
+ tx();
110
+ return ids;
111
+ }
112
+
113
+ deleteChunksByPath(filePath: string): void {
114
+ // Get chunk IDs first for cascading cleanup
115
+ const chunkIds = this.db.prepare("SELECT id FROM chunks WHERE path = ?").all(filePath) as { id: number }[];
116
+ if (chunkIds.length === 0) return;
117
+
118
+ const tx = this.db.transaction(() => {
119
+ for (const { id } of chunkIds) {
120
+ this.db.prepare("DELETE FROM fts_metadata WHERE chunkId = ?").run(id);
121
+ this.db.prepare("DELETE FROM embeddings WHERE chunkId = ?").run(id);
122
+ }
123
+ this.db.prepare("DELETE FROM chunks WHERE path = ?").run(filePath);
124
+ // FTS cleanup — delete by path
125
+ this.db.prepare("DELETE FROM fts WHERE path = ?").run(filePath);
126
+ });
127
+ tx();
128
+ }
129
+
130
+ // ── FTS operations ────────────────────────────────────────────────────────
131
+
132
+ insertFts(chunkId: number, filePath: string, content: string, cacheKey: string): void {
133
+ // Insert into FTS virtual table
134
+ const ftsInsert = this.db.prepare("INSERT INTO fts (rowid, path, content) VALUES (?, ?, ?)");
135
+ const metaInsert = this.db.prepare(
136
+ "INSERT INTO fts_metadata (id, path, cacheKey, chunkId) VALUES (?, ?, ?, ?)"
137
+ );
138
+
139
+ // Use chunkId as rowid for direct mapping
140
+ const rowid = chunkId;
141
+ ftsInsert.run(rowid, filePath, content);
142
+ metaInsert.run(rowid, filePath, cacheKey, chunkId);
143
+ }
144
+
145
+ /**
146
+ * Full-text search with BM25 ranking.
147
+ * Path matches get a 10x boost.
148
+ */
149
+ searchFts(query: string, limit = 25, directory?: string): SearchResult[] {
150
+ // Escape special FTS5 characters
151
+ const escaped = query.replace(/[?"]/g, "");
152
+ if (!escaped.trim()) return [];
153
+
154
+ let sql = `
155
+ SELECT fts_metadata.chunkId, fts_metadata.path, fts.content,
156
+ rank AS score, chunks.startLine, chunks.endLine
157
+ FROM fts
158
+ JOIN fts_metadata ON fts.rowid = fts_metadata.id
159
+ JOIN chunks ON fts_metadata.chunkId = chunks.id
160
+ WHERE fts MATCH ?
161
+ `;
162
+
163
+ const params: any[] = [escaped];
164
+
165
+ if (directory) {
166
+ sql += " AND fts_metadata.path LIKE ?";
167
+ params.push(directory + "%");
168
+ }
169
+
170
+ sql += " ORDER BY bm25(fts, 10.0) LIMIT ?";
171
+ params.push(limit);
172
+
173
+ try {
174
+ const rows = this.db.prepare(sql).all(...params) as any[];
175
+ return rows.map((r) => ({
176
+ path: r.path,
177
+ content: r.content,
178
+ startLine: r.startLine,
179
+ endLine: r.endLine,
180
+ score: Math.abs(r.score),
181
+ source: "fts" as const,
182
+ }));
183
+ } catch {
184
+ // FTS query syntax error — return empty
185
+ return [];
186
+ }
187
+ }
188
+
189
+ // ── Embedding operations ──────────────────────────────────────────────────
190
+
191
+ insertEmbedding(chunkId: number, filePath: string, cacheKey: string, vector: number[], model: string): void {
192
+ this.db.prepare(
193
+ "INSERT OR REPLACE INTO embeddings (chunkId, path, cacheKey, vector, model) VALUES (?, ?, ?, ?, ?)"
194
+ ).run(chunkId, filePath, cacheKey, JSON.stringify(vector), model);
195
+ }
196
+
197
+ /**
198
+ * Retrieve all embeddings for similarity search.
199
+ * Returns chunks with their vectors for in-process cosine similarity.
200
+ */
201
+ getEmbeddings(model: string, directory?: string): Array<{
202
+ chunkId: number;
203
+ path: string;
204
+ content: string;
205
+ vector: number[];
206
+ startLine: number;
207
+ endLine: number;
208
+ }> {
209
+ let sql = `
210
+ SELECT e.chunkId, e.path, c.content, e.vector, c.startLine, c.endLine
211
+ FROM embeddings e
212
+ JOIN chunks c ON e.chunkId = c.id
213
+ WHERE e.model = ?
214
+ `;
215
+ const params: any[] = [model];
216
+
217
+ if (directory) {
218
+ sql += " AND e.path LIKE ?";
219
+ params.push(directory + "%");
220
+ }
221
+
222
+ const rows = this.db.prepare(sql).all(...params) as any[];
223
+ return rows.map((r) => ({
224
+ ...r,
225
+ vector: JSON.parse(r.vector),
226
+ }));
227
+ }
228
+
229
+ // ── Catalog operations (incremental updates) ─────────────────────────────
230
+
231
+ getCatalogEntry(filePath: string, directory: string): { cacheKey: string; lastUpdated: number } | null {
232
+ const row = this.db.prepare(
233
+ "SELECT cacheKey, lastUpdated FROM index_catalog WHERE path = ? AND directory = ?"
234
+ ).get(filePath, directory) as any;
235
+ return row || null;
236
+ }
237
+
238
+ updateCatalog(filePath: string, cacheKey: string, directory: string): void {
239
+ this.db.prepare(
240
+ `INSERT OR REPLACE INTO index_catalog (path, cacheKey, lastUpdated, directory)
241
+ VALUES (?, ?, ?, ?)`
242
+ ).run(filePath, cacheKey, Date.now(), directory);
243
+ }
244
+
245
+ removeCatalogEntry(filePath: string, directory: string): void {
246
+ this.db.prepare("DELETE FROM index_catalog WHERE path = ? AND directory = ?").run(filePath, directory);
247
+ }
248
+
249
+ getCatalogPaths(directory: string): Map<string, string> {
250
+ const rows = this.db.prepare(
251
+ "SELECT path, cacheKey FROM index_catalog WHERE directory = ?"
252
+ ).all(directory) as { path: string; cacheKey: string }[];
253
+
254
+ const map = new Map<string, string>();
255
+ for (const row of rows) map.set(row.path, row.cacheKey);
256
+ return map;
257
+ }
258
+
259
+ // ── Stats ─────────────────────────────────────────────────────────────────
260
+
261
+ getStats(): IndexStats {
262
+ const chunks = (this.db.prepare("SELECT COUNT(*) as c FROM chunks").get() as any)?.c || 0;
263
+ const fts = (this.db.prepare("SELECT COUNT(*) as c FROM fts_metadata").get() as any)?.c || 0;
264
+ const emb = (this.db.prepare("SELECT COUNT(*) as c FROM embeddings").get() as any)?.c || 0;
265
+ const files = (this.db.prepare("SELECT COUNT(DISTINCT path) as c FROM index_catalog").get() as any)?.c || 0;
266
+ const lastRow = this.db.prepare("SELECT MAX(lastUpdated) as m FROM index_catalog").get() as any;
267
+
268
+ let sizeBytes = 0;
269
+ try {
270
+ const dbPath = this.db.name;
271
+ if (fs.existsSync(dbPath)) sizeBytes = fs.statSync(dbPath).size;
272
+ } catch {}
273
+
274
+ return {
275
+ totalFiles: files,
276
+ totalChunks: chunks,
277
+ ftsEntries: fts,
278
+ embeddingEntries: emb,
279
+ lastIndexed: lastRow?.m || 0,
280
+ indexSizeBytes: sizeBytes,
281
+ };
282
+ }
283
+
284
+ // ── Lifecycle ─────────────────────────────────────────────────────────────
285
+
286
+ close(): void {
287
+ this.db.close();
288
+ }
289
+
290
+ clearAll(): void {
291
+ this.db.exec(`
292
+ DELETE FROM embeddings;
293
+ DELETE FROM fts_metadata;
294
+ DELETE FROM fts;
295
+ DELETE FROM chunks;
296
+ DELETE FROM index_catalog;
297
+ `);
298
+ }
299
+ }
@@ -0,0 +1,15 @@
1
+ export { CodebaseIndexer } from "./indexer";
2
+ export { RecentEditsCache } from "./recent-edits-cache";
3
+ export type { CachedEdit } from "./recent-edits-cache";
4
+ export type { EmbeddingProvider } from "./indexer";
5
+ export { IndexDatabase } from "./database";
6
+ export { chunkDocument, shouldChunk } from "./chunker";
7
+ export type { Chunk } from "./chunker";
8
+ export type {
9
+ IndexTag,
10
+ ChunkWithMeta,
11
+ SearchResult,
12
+ IndexingProgress,
13
+ IndexingProgressCallback,
14
+ IndexStats,
15
+ } from "./types";
@@ -0,0 +1,383 @@
1
+ /**
2
+ * Codebase Indexer — orchestrates indexing of an entire codebase.
3
+ *
4
+ * Pipeline:
5
+ * 1. Scan files (respects .gitignore, skips binaries/large files)
6
+ * 2. Compute cache keys (SHA-256 of content)
7
+ * 3. Diff against catalog (find new/modified/deleted files)
8
+ * 4. Chunk new/modified files
9
+ * 5. Insert chunks into SQLite
10
+ * 6. Build FTS5 index
11
+ * 7. Optionally compute embeddings (if provider configured)
12
+ *
13
+ * Supports incremental updates — only re-indexes changed files.
14
+ */
15
+
16
+ import * as fs from "fs";
17
+ import * as path from "path";
18
+ import { glob } from "glob";
19
+ import { IndexDatabase } from "./database";
20
+ import { chunkDocument, shouldChunk } from "./chunker";
21
+ import { loadIgnorePatterns } from "../utils/gitignore";
22
+ import type { ChunkWithMeta, IndexingProgress, IndexingProgressCallback } from "./types";
23
+
24
+ /** File extensions to index */
25
+ const INDEXABLE_EXTENSIONS = new Set([
26
+ // Code
27
+ ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
28
+ ".py", ".rb", ".go", ".rs", ".java", ".kt", ".scala",
29
+ ".c", ".cpp", ".h", ".hpp", ".cs", ".swift",
30
+ ".lua", ".php", ".pl", ".sh", ".bash", ".zsh",
31
+ // Config/Data
32
+ ".json", ".yaml", ".yml", ".toml", ".xml",
33
+ ".html", ".css", ".scss", ".less", ".vue", ".svelte",
34
+ // Docs
35
+ ".md", ".mdx", ".txt", ".rst",
36
+ // Other
37
+ ".sql", ".graphql", ".gql", ".proto",
38
+ ".env.example", ".gitignore", ".dockerignore",
39
+ "Dockerfile", "Makefile", "Gemfile",
40
+ ]);
41
+
42
+ const SKIP_DIRS = new Set([
43
+ "node_modules", ".git", "dist", "build", "out", ".next", ".nuxt",
44
+ "__pycache__", ".cache", ".turbo", "coverage", "venv", ".venv",
45
+ "target", "vendor", ".idea", ".vscode",
46
+ ]);
47
+
48
+ const MAX_FILE_SIZE = 1024 * 1024; // 1MB
49
+ const BATCH_SIZE = 200;
50
+
51
+ export interface EmbeddingProvider {
52
+ modelId: string;
53
+ embed(texts: string[]): Promise<number[][]>;
54
+ maxBatchSize?: number;
55
+ }
56
+
57
+ export class CodebaseIndexer {
58
+ private db: IndexDatabase;
59
+ private workingDir: string;
60
+ private embeddingProvider?: EmbeddingProvider;
61
+
62
+ constructor(workingDir: string, embeddingProvider?: EmbeddingProvider, dbPath?: string) {
63
+ this.workingDir = path.resolve(workingDir);
64
+ this.db = new IndexDatabase(dbPath);
65
+ this.embeddingProvider = embeddingProvider;
66
+ }
67
+
68
+ /**
69
+ * Index the entire codebase (incremental — only processes changed files).
70
+ */
71
+ async index(onProgress?: IndexingProgressCallback): Promise<{
72
+ added: number;
73
+ updated: number;
74
+ deleted: number;
75
+ totalChunks: number;
76
+ }> {
77
+ const report = (phase: string, current: number, total: number, message: string) => {
78
+ onProgress?.({ phase, current, total, message });
79
+ };
80
+
81
+ // 1. Scan files
82
+ report("scan", 0, 1, "Scanning files...");
83
+ const files = await this.scanFiles();
84
+ report("scan", 1, 1, `Found ${files.length} files`);
85
+
86
+ // 2. Diff against catalog
87
+ report("diff", 0, 1, "Computing changes...");
88
+ const catalogPaths = this.db.getCatalogPaths(this.workingDir);
89
+ const currentPaths = new Map<string, string>();
90
+
91
+ for (const filePath of files) {
92
+ try {
93
+ const content = fs.readFileSync(filePath, "utf-8");
94
+ const key = IndexDatabase.cacheKey(content);
95
+ currentPaths.set(filePath, key);
96
+ } catch {
97
+ // Skip unreadable files
98
+ }
99
+ }
100
+
101
+ // Classify: new, modified, deleted, unchanged
102
+ const toAdd: string[] = [];
103
+ const toUpdate: string[] = [];
104
+ const toDelete: string[] = [];
105
+
106
+ for (const [filePath, cacheKey] of currentPaths) {
107
+ const existing = catalogPaths.get(filePath);
108
+ if (!existing) {
109
+ toAdd.push(filePath);
110
+ } else if (existing !== cacheKey) {
111
+ toUpdate.push(filePath);
112
+ }
113
+ // else: unchanged — skip
114
+ }
115
+
116
+ for (const filePath of catalogPaths.keys()) {
117
+ if (!currentPaths.has(filePath)) {
118
+ toDelete.push(filePath);
119
+ }
120
+ }
121
+
122
+ report("diff", 1, 1, `Changes: ${toAdd.length} new, ${toUpdate.length} modified, ${toDelete.length} deleted`);
123
+
124
+ // 3. Delete removed files
125
+ for (const filePath of toDelete) {
126
+ this.db.deleteChunksByPath(filePath);
127
+ this.db.removeCatalogEntry(filePath, this.workingDir);
128
+ }
129
+
130
+ // 4. Delete modified files (will re-index)
131
+ for (const filePath of toUpdate) {
132
+ this.db.deleteChunksByPath(filePath);
133
+ }
134
+
135
+ // 5. Process new + modified files in batches
136
+ const toProcess = [...toAdd, ...toUpdate];
137
+ let totalChunks = 0;
138
+
139
+ for (let batch = 0; batch < toProcess.length; batch += BATCH_SIZE) {
140
+ const batchFiles = toProcess.slice(batch, batch + BATCH_SIZE);
141
+ report("index", batch, toProcess.length, `Indexing batch ${Math.floor(batch / BATCH_SIZE) + 1}...`);
142
+
143
+ for (const filePath of batchFiles) {
144
+ const cacheKey = currentPaths.get(filePath)!;
145
+ const content = fs.readFileSync(filePath, "utf-8");
146
+ const relPath = path.relative(this.workingDir, filePath);
147
+
148
+ if (!shouldChunk(filePath, content.length)) continue;
149
+
150
+ // Chunk the file
151
+ const rawChunks = chunkDocument(filePath, content);
152
+ if (rawChunks.length === 0) continue;
153
+
154
+ const chunks: ChunkWithMeta[] = rawChunks.map((c) => ({
155
+ content: c.content,
156
+ path: relPath,
157
+ startLine: c.startLine,
158
+ endLine: c.endLine,
159
+ cacheKey,
160
+ }));
161
+
162
+ // Insert chunks
163
+ const chunkIds = this.db.insertChunks(chunks);
164
+ totalChunks += chunkIds.length;
165
+
166
+ // Build FTS index
167
+ for (let i = 0; i < chunkIds.length; i++) {
168
+ if (chunkIds[i] > 0) {
169
+ this.db.insertFts(chunkIds[i], relPath, chunks[i].content, cacheKey);
170
+ }
171
+ }
172
+
173
+ // Update catalog
174
+ this.db.updateCatalog(filePath, cacheKey, this.workingDir);
175
+ }
176
+ }
177
+
178
+ // 6. Compute embeddings (if provider available)
179
+ if (this.embeddingProvider && toProcess.length > 0) {
180
+ report("embeddings", 0, 1, "Computing embeddings...");
181
+ await this.computeEmbeddings(toProcess, currentPaths);
182
+ report("embeddings", 1, 1, "Embeddings complete");
183
+ }
184
+
185
+ report("done", 1, 1, "Indexing complete");
186
+
187
+ return {
188
+ added: toAdd.length,
189
+ updated: toUpdate.length,
190
+ deleted: toDelete.length,
191
+ totalChunks,
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Search the index using full-text search (BM25).
197
+ */
198
+ searchFts(query: string, limit = 25, directory?: string): import("./types").SearchResult[] {
199
+ return this.db.searchFts(query, limit, directory);
200
+ }
201
+
202
+ /**
203
+ * Search using vector similarity (cosine similarity).
204
+ * Requires an embedding provider.
205
+ */
206
+ async searchSemantic(query: string, limit = 25, directory?: string): Promise<import("./types").SearchResult[]> {
207
+ if (!this.embeddingProvider) return [];
208
+
209
+ const [queryVector] = await this.embeddingProvider.embed([query]);
210
+ if (!queryVector) return [];
211
+
212
+ const embeddings = this.db.getEmbeddings(this.embeddingProvider.modelId, directory);
213
+ if (embeddings.length === 0) return [];
214
+
215
+ // Compute cosine similarity
216
+ const scored = embeddings.map((e) => ({
217
+ ...e,
218
+ score: cosineSimilarity(queryVector, e.vector),
219
+ }));
220
+
221
+ // Sort by similarity (highest first) and take top-k
222
+ scored.sort((a, b) => b.score - a.score);
223
+
224
+ return scored.slice(0, limit).map((s) => ({
225
+ path: s.path,
226
+ content: s.content,
227
+ startLine: s.startLine,
228
+ endLine: s.endLine,
229
+ score: s.score,
230
+ source: "embedding" as const,
231
+ }));
232
+ }
233
+
234
+ /**
235
+ * Combined search: FTS (25%) + Embeddings (75%), deduplicated.
236
+ */
237
+ async search(query: string, limit = 25, directory?: string): Promise<import("./types").SearchResult[]> {
238
+ const ftsLimit = Math.ceil(limit * 0.35);
239
+ const embLimit = Math.ceil(limit * 0.65);
240
+
241
+ const [ftsResults, embResults] = await Promise.all([
242
+ Promise.resolve(this.searchFts(query, ftsLimit, directory)),
243
+ this.searchSemantic(query, embLimit, directory),
244
+ ]);
245
+
246
+ // Deduplicate by path + startLine
247
+ const seen = new Set<string>();
248
+ const combined: import("./types").SearchResult[] = [];
249
+
250
+ for (const r of [...embResults, ...ftsResults]) {
251
+ const key = `${r.path}:${r.startLine}`;
252
+ if (seen.has(key)) continue;
253
+ seen.add(key);
254
+ combined.push(r);
255
+ }
256
+
257
+ return combined.slice(0, limit);
258
+ }
259
+
260
+ /**
261
+ * Get index statistics.
262
+ */
263
+ getStats(): import("./types").IndexStats {
264
+ return this.db.getStats();
265
+ }
266
+
267
+ /**
268
+ * Clear the entire index.
269
+ */
270
+ clearIndex(): void {
271
+ this.db.clearAll();
272
+ }
273
+
274
+ /**
275
+ * Close the database connection.
276
+ */
277
+ close(): void {
278
+ this.db.close();
279
+ }
280
+
281
+ // ── Private methods ───────────────────────────────────────────────────────
282
+
283
+ private async scanFiles(): Promise<string[]> {
284
+ const ignorePatterns = loadIgnorePatterns(this.workingDir);
285
+
286
+ const allFiles = await glob("**/*", {
287
+ cwd: this.workingDir,
288
+ absolute: true,
289
+ nodir: true,
290
+ ignore: [
291
+ ...SKIP_DIRS.values(),
292
+ ...ignorePatterns.map((p) => `**/${p}/**`),
293
+ ].map(p => `**/${p}`),
294
+ });
295
+
296
+ return allFiles.filter((f) => {
297
+ const ext = path.extname(f).toLowerCase();
298
+ const basename = path.basename(f);
299
+
300
+ // Check extension or known config filenames
301
+ if (!INDEXABLE_EXTENSIONS.has(ext) && !INDEXABLE_EXTENSIONS.has(basename)) return false;
302
+
303
+ // Check file size
304
+ try {
305
+ const stat = fs.statSync(f);
306
+ if (stat.size > MAX_FILE_SIZE || stat.size === 0) return false;
307
+ } catch {
308
+ return false;
309
+ }
310
+
311
+ // Skip dirs in path
312
+ const rel = path.relative(this.workingDir, f);
313
+ const parts = rel.split(path.sep);
314
+ if (parts.some((p) => SKIP_DIRS.has(p))) return false;
315
+
316
+ return true;
317
+ });
318
+ }
319
+
320
+ private async computeEmbeddings(
321
+ filePaths: string[],
322
+ cacheKeys: Map<string, string>,
323
+ ): Promise<void> {
324
+ if (!this.embeddingProvider) return;
325
+
326
+ const model = this.embeddingProvider.modelId;
327
+ const batchSize = this.embeddingProvider.maxBatchSize || 64;
328
+
329
+ // Collect all chunks that need embeddings
330
+ const chunksToEmbed: Array<{ chunkId: number; path: string; content: string; cacheKey: string }> = [];
331
+
332
+ for (const filePath of filePaths) {
333
+ const relPath = path.relative(this.workingDir, filePath);
334
+ const cacheKey = cacheKeys.get(filePath)!;
335
+
336
+ // Get chunks for this file
337
+ const rows = this.db["db"].prepare(
338
+ "SELECT id, content FROM chunks WHERE path = ? AND cacheKey = ?"
339
+ ).all(relPath, cacheKey) as { id: number; content: string }[];
340
+
341
+ for (const row of rows) {
342
+ chunksToEmbed.push({ chunkId: row.id, path: relPath, content: row.content, cacheKey });
343
+ }
344
+ }
345
+
346
+ // Batch embed
347
+ for (let i = 0; i < chunksToEmbed.length; i += batchSize) {
348
+ const batch = chunksToEmbed.slice(i, i + batchSize);
349
+ const texts = batch.map((c) => c.content);
350
+
351
+ try {
352
+ const vectors = await this.embeddingProvider.embed(texts);
353
+
354
+ for (let j = 0; j < batch.length; j++) {
355
+ if (vectors[j]) {
356
+ this.db.insertEmbedding(
357
+ batch[j].chunkId,
358
+ batch[j].path,
359
+ batch[j].cacheKey,
360
+ vectors[j],
361
+ model,
362
+ );
363
+ }
364
+ }
365
+ } catch {
366
+ // Embedding failed for this batch — continue with next
367
+ }
368
+ }
369
+ }
370
+ }
371
+
372
+ /** Cosine similarity between two vectors */
373
+ function cosineSimilarity(a: number[], b: number[]): number {
374
+ if (a.length !== b.length) return 0;
375
+ let dot = 0, magA = 0, magB = 0;
376
+ for (let i = 0; i < a.length; i++) {
377
+ dot += a[i] * b[i];
378
+ magA += a[i] * a[i];
379
+ magB += b[i] * b[i];
380
+ }
381
+ const denom = Math.sqrt(magA) * Math.sqrt(magB);
382
+ return denom === 0 ? 0 : dot / denom;
383
+ }