grepmind-core 0.1.0-alpha

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 (370) hide show
  1. package/LICENSE +190 -0
  2. package/dist/config/types.d.ts +174 -0
  3. package/dist/config/types.d.ts.map +1 -0
  4. package/dist/config/types.js +137 -0
  5. package/dist/config/types.js.map +1 -0
  6. package/dist/git.d.ts +98 -0
  7. package/dist/git.d.ts.map +1 -0
  8. package/dist/git.js +298 -0
  9. package/dist/git.js.map +1 -0
  10. package/dist/git.test.d.ts +7 -0
  11. package/dist/git.test.d.ts.map +1 -0
  12. package/dist/git.test.js +242 -0
  13. package/dist/git.test.js.map +1 -0
  14. package/dist/index.d.ts +44 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +67 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/indexer/branch.d.ts +121 -0
  19. package/dist/indexer/branch.d.ts.map +1 -0
  20. package/dist/indexer/branch.js +451 -0
  21. package/dist/indexer/branch.js.map +1 -0
  22. package/dist/indexer/chunker.d.ts +9 -0
  23. package/dist/indexer/chunker.d.ts.map +1 -0
  24. package/dist/indexer/chunker.js +70 -0
  25. package/dist/indexer/chunker.js.map +1 -0
  26. package/dist/indexer/chunker.test.d.ts +2 -0
  27. package/dist/indexer/chunker.test.d.ts.map +1 -0
  28. package/dist/indexer/chunker.test.js +180 -0
  29. package/dist/indexer/chunker.test.js.map +1 -0
  30. package/dist/indexer/code/branch.d.ts +155 -0
  31. package/dist/indexer/code/branch.d.ts.map +1 -0
  32. package/dist/indexer/code/branch.js +550 -0
  33. package/dist/indexer/code/branch.js.map +1 -0
  34. package/dist/indexer/code/branch.test.d.ts +7 -0
  35. package/dist/indexer/code/branch.test.d.ts.map +1 -0
  36. package/dist/indexer/code/branch.test.js +241 -0
  37. package/dist/indexer/code/branch.test.js.map +1 -0
  38. package/dist/indexer/code/chunker.d.ts +61 -0
  39. package/dist/indexer/code/chunker.d.ts.map +1 -0
  40. package/dist/indexer/code/chunker.js +311 -0
  41. package/dist/indexer/code/chunker.js.map +1 -0
  42. package/dist/indexer/code/chunker.test.d.ts +2 -0
  43. package/dist/indexer/code/chunker.test.d.ts.map +1 -0
  44. package/dist/indexer/code/chunker.test.js +552 -0
  45. package/dist/indexer/code/chunker.test.js.map +1 -0
  46. package/dist/indexer/code/fts.test.d.ts +2 -0
  47. package/dist/indexer/code/fts.test.d.ts.map +1 -0
  48. package/dist/indexer/code/fts.test.js +14 -0
  49. package/dist/indexer/code/fts.test.js.map +1 -0
  50. package/dist/indexer/code/graph/embedded.d.ts +11 -0
  51. package/dist/indexer/code/graph/embedded.d.ts.map +1 -0
  52. package/dist/indexer/code/graph/embedded.js +152 -0
  53. package/dist/indexer/code/graph/embedded.js.map +1 -0
  54. package/dist/indexer/code/graph/embedded.test.d.ts +2 -0
  55. package/dist/indexer/code/graph/embedded.test.d.ts.map +1 -0
  56. package/dist/indexer/code/graph/embedded.test.js +105 -0
  57. package/dist/indexer/code/graph/embedded.test.js.map +1 -0
  58. package/dist/indexer/code/graph/facts.d.ts +11 -0
  59. package/dist/indexer/code/graph/facts.d.ts.map +1 -0
  60. package/dist/indexer/code/graph/facts.js +456 -0
  61. package/dist/indexer/code/graph/facts.js.map +1 -0
  62. package/dist/indexer/code/graph/facts.test.d.ts +2 -0
  63. package/dist/indexer/code/graph/facts.test.d.ts.map +1 -0
  64. package/dist/indexer/code/graph/facts.test.js +181 -0
  65. package/dist/indexer/code/graph/facts.test.js.map +1 -0
  66. package/dist/indexer/code/graph/id.d.ts +14 -0
  67. package/dist/indexer/code/graph/id.d.ts.map +1 -0
  68. package/dist/indexer/code/graph/id.js +40 -0
  69. package/dist/indexer/code/graph/id.js.map +1 -0
  70. package/dist/indexer/code/graph/id.test.d.ts +2 -0
  71. package/dist/indexer/code/graph/id.test.d.ts.map +1 -0
  72. package/dist/indexer/code/graph/id.test.js +86 -0
  73. package/dist/indexer/code/graph/id.test.js.map +1 -0
  74. package/dist/indexer/code/graph/index.d.ts +133 -0
  75. package/dist/indexer/code/graph/index.d.ts.map +1 -0
  76. package/dist/indexer/code/graph/index.js +1876 -0
  77. package/dist/indexer/code/graph/index.js.map +1 -0
  78. package/dist/indexer/code/graph/index.test.d.ts +2 -0
  79. package/dist/indexer/code/graph/index.test.d.ts.map +1 -0
  80. package/dist/indexer/code/graph/index.test.js +210 -0
  81. package/dist/indexer/code/graph/index.test.js.map +1 -0
  82. package/dist/indexer/code/graph/queries.d.ts +22 -0
  83. package/dist/indexer/code/graph/queries.d.ts.map +1 -0
  84. package/dist/indexer/code/graph/queries.js +79 -0
  85. package/dist/indexer/code/graph/queries.js.map +1 -0
  86. package/dist/indexer/code/graph/queries.test.d.ts +2 -0
  87. package/dist/indexer/code/graph/queries.test.d.ts.map +1 -0
  88. package/dist/indexer/code/graph/queries.test.js +108 -0
  89. package/dist/indexer/code/graph/queries.test.js.map +1 -0
  90. package/dist/indexer/code/graph/resolver.d.ts +136 -0
  91. package/dist/indexer/code/graph/resolver.d.ts.map +1 -0
  92. package/dist/indexer/code/graph/resolver.js +839 -0
  93. package/dist/indexer/code/graph/resolver.js.map +1 -0
  94. package/dist/indexer/code/graph/resolver.test.d.ts +2 -0
  95. package/dist/indexer/code/graph/resolver.test.d.ts.map +1 -0
  96. package/dist/indexer/code/graph/resolver.test.js +482 -0
  97. package/dist/indexer/code/graph/resolver.test.js.map +1 -0
  98. package/dist/indexer/code/graph/semantic.d.ts +33 -0
  99. package/dist/indexer/code/graph/semantic.d.ts.map +1 -0
  100. package/dist/indexer/code/graph/semantic.js +279 -0
  101. package/dist/indexer/code/graph/semantic.js.map +1 -0
  102. package/dist/indexer/code/graph/semantic.test.d.ts +2 -0
  103. package/dist/indexer/code/graph/semantic.test.d.ts.map +1 -0
  104. package/dist/indexer/code/graph/semantic.test.js +127 -0
  105. package/dist/indexer/code/graph/semantic.test.js.map +1 -0
  106. package/dist/indexer/code/index.d.ts +404 -0
  107. package/dist/indexer/code/index.d.ts.map +1 -0
  108. package/dist/indexer/code/index.js +2070 -0
  109. package/dist/indexer/code/index.js.map +1 -0
  110. package/dist/indexer/code/languages/bash.d.ts +14 -0
  111. package/dist/indexer/code/languages/bash.d.ts.map +1 -0
  112. package/dist/indexer/code/languages/bash.js +125 -0
  113. package/dist/indexer/code/languages/bash.js.map +1 -0
  114. package/dist/indexer/code/languages/css.d.ts +16 -0
  115. package/dist/indexer/code/languages/css.d.ts.map +1 -0
  116. package/dist/indexer/code/languages/css.js +204 -0
  117. package/dist/indexer/code/languages/css.js.map +1 -0
  118. package/dist/indexer/code/languages/generic.d.ts +61 -0
  119. package/dist/indexer/code/languages/generic.d.ts.map +1 -0
  120. package/dist/indexer/code/languages/generic.js +150 -0
  121. package/dist/indexer/code/languages/generic.js.map +1 -0
  122. package/dist/indexer/code/languages/graphql.d.ts +13 -0
  123. package/dist/indexer/code/languages/graphql.d.ts.map +1 -0
  124. package/dist/indexer/code/languages/graphql.js +180 -0
  125. package/dist/indexer/code/languages/graphql.js.map +1 -0
  126. package/dist/indexer/code/languages/html.d.ts +16 -0
  127. package/dist/indexer/code/languages/html.d.ts.map +1 -0
  128. package/dist/indexer/code/languages/html.js +138 -0
  129. package/dist/indexer/code/languages/html.js.map +1 -0
  130. package/dist/indexer/code/languages/index.d.ts +9 -0
  131. package/dist/indexer/code/languages/index.d.ts.map +1 -0
  132. package/dist/indexer/code/languages/index.js +12 -0
  133. package/dist/indexer/code/languages/index.js.map +1 -0
  134. package/dist/indexer/code/languages/json.d.ts +12 -0
  135. package/dist/indexer/code/languages/json.d.ts.map +1 -0
  136. package/dist/indexer/code/languages/json.js +66 -0
  137. package/dist/indexer/code/languages/json.js.map +1 -0
  138. package/dist/indexer/code/languages/registry.d.ts +78 -0
  139. package/dist/indexer/code/languages/registry.d.ts.map +1 -0
  140. package/dist/indexer/code/languages/registry.js +72 -0
  141. package/dist/indexer/code/languages/registry.js.map +1 -0
  142. package/dist/indexer/code/languages/typescript.d.ts +39 -0
  143. package/dist/indexer/code/languages/typescript.d.ts.map +1 -0
  144. package/dist/indexer/code/languages/typescript.js +300 -0
  145. package/dist/indexer/code/languages/typescript.js.map +1 -0
  146. package/dist/indexer/code/languages/yaml.d.ts +13 -0
  147. package/dist/indexer/code/languages/yaml.d.ts.map +1 -0
  148. package/dist/indexer/code/languages/yaml.js +90 -0
  149. package/dist/indexer/code/languages/yaml.js.map +1 -0
  150. package/dist/indexer/code/parser.d.ts +26 -0
  151. package/dist/indexer/code/parser.d.ts.map +1 -0
  152. package/dist/indexer/code/parser.js +332 -0
  153. package/dist/indexer/code/parser.js.map +1 -0
  154. package/dist/indexer/code/retry.d.ts +58 -0
  155. package/dist/indexer/code/retry.d.ts.map +1 -0
  156. package/dist/indexer/code/retry.js +192 -0
  157. package/dist/indexer/code/retry.js.map +1 -0
  158. package/dist/indexer/code/tree/builder.d.ts +30 -0
  159. package/dist/indexer/code/tree/builder.d.ts.map +1 -0
  160. package/dist/indexer/code/tree/builder.js +132 -0
  161. package/dist/indexer/code/tree/builder.js.map +1 -0
  162. package/dist/indexer/code/tree/builder.test.d.ts +2 -0
  163. package/dist/indexer/code/tree/builder.test.d.ts.map +1 -0
  164. package/dist/indexer/code/tree/builder.test.js +31 -0
  165. package/dist/indexer/code/tree/builder.test.js.map +1 -0
  166. package/dist/indexer/code/tree/cache.d.ts +22 -0
  167. package/dist/indexer/code/tree/cache.d.ts.map +1 -0
  168. package/dist/indexer/code/tree/cache.js +85 -0
  169. package/dist/indexer/code/tree/cache.js.map +1 -0
  170. package/dist/indexer/code/tree/context.d.ts +32 -0
  171. package/dist/indexer/code/tree/context.d.ts.map +1 -0
  172. package/dist/indexer/code/tree/context.js +78 -0
  173. package/dist/indexer/code/tree/context.js.map +1 -0
  174. package/dist/indexer/code/tree/embedding.d.ts +9 -0
  175. package/dist/indexer/code/tree/embedding.d.ts.map +1 -0
  176. package/dist/indexer/code/tree/embedding.js +53 -0
  177. package/dist/indexer/code/tree/embedding.js.map +1 -0
  178. package/dist/indexer/code/tree/embedding.test.d.ts +2 -0
  179. package/dist/indexer/code/tree/embedding.test.d.ts.map +1 -0
  180. package/dist/indexer/code/tree/embedding.test.js +57 -0
  181. package/dist/indexer/code/tree/embedding.test.js.map +1 -0
  182. package/dist/indexer/code/tree/id.d.ts +3 -0
  183. package/dist/indexer/code/tree/id.d.ts.map +1 -0
  184. package/dist/indexer/code/tree/id.js +8 -0
  185. package/dist/indexer/code/tree/id.js.map +1 -0
  186. package/dist/indexer/code/tree/index.d.ts +113 -0
  187. package/dist/indexer/code/tree/index.d.ts.map +1 -0
  188. package/dist/indexer/code/tree/index.js +1146 -0
  189. package/dist/indexer/code/tree/index.js.map +1 -0
  190. package/dist/indexer/code/tree/rename.d.ts +13 -0
  191. package/dist/indexer/code/tree/rename.d.ts.map +1 -0
  192. package/dist/indexer/code/tree/rename.js +46 -0
  193. package/dist/indexer/code/tree/rename.js.map +1 -0
  194. package/dist/indexer/code/tree/repomap.d.ts +29 -0
  195. package/dist/indexer/code/tree/repomap.d.ts.map +1 -0
  196. package/dist/indexer/code/tree/repomap.js +95 -0
  197. package/dist/indexer/code/tree/repomap.js.map +1 -0
  198. package/dist/indexer/code/tree/repomap.test.d.ts +2 -0
  199. package/dist/indexer/code/tree/repomap.test.d.ts.map +1 -0
  200. package/dist/indexer/code/tree/repomap.test.js +93 -0
  201. package/dist/indexer/code/tree/repomap.test.js.map +1 -0
  202. package/dist/indexer/code/tree/stats.d.ts +26 -0
  203. package/dist/indexer/code/tree/stats.d.ts.map +1 -0
  204. package/dist/indexer/code/tree/stats.js +49 -0
  205. package/dist/indexer/code/tree/stats.js.map +1 -0
  206. package/dist/indexer/code/tree/types.d.ts +186 -0
  207. package/dist/indexer/code/tree/types.d.ts.map +1 -0
  208. package/dist/indexer/code/tree/types.js +10 -0
  209. package/dist/indexer/code/tree/types.js.map +1 -0
  210. package/dist/indexer/code/wal.d.ts +144 -0
  211. package/dist/indexer/code/wal.d.ts.map +1 -0
  212. package/dist/indexer/code/wal.js +283 -0
  213. package/dist/indexer/code/wal.js.map +1 -0
  214. package/dist/indexer/embeddings.d.ts +113 -0
  215. package/dist/indexer/embeddings.d.ts.map +1 -0
  216. package/dist/indexer/embeddings.js +477 -0
  217. package/dist/indexer/embeddings.js.map +1 -0
  218. package/dist/indexer/git-sync.d.ts +117 -0
  219. package/dist/indexer/git-sync.d.ts.map +1 -0
  220. package/dist/indexer/git-sync.js +398 -0
  221. package/dist/indexer/git-sync.js.map +1 -0
  222. package/dist/indexer/index.d.ts +175 -0
  223. package/dist/indexer/index.d.ts.map +1 -0
  224. package/dist/indexer/index.js +1096 -0
  225. package/dist/indexer/index.js.map +1 -0
  226. package/dist/indexer/mocks/mock-reranker.d.ts +12 -0
  227. package/dist/indexer/mocks/mock-reranker.d.ts.map +1 -0
  228. package/dist/indexer/mocks/mock-reranker.js +26 -0
  229. package/dist/indexer/mocks/mock-reranker.js.map +1 -0
  230. package/dist/indexer/parser.d.ts +8 -0
  231. package/dist/indexer/parser.d.ts.map +1 -0
  232. package/dist/indexer/parser.js +44 -0
  233. package/dist/indexer/parser.js.map +1 -0
  234. package/dist/indexer/parser.test.d.ts +2 -0
  235. package/dist/indexer/parser.test.d.ts.map +1 -0
  236. package/dist/indexer/parser.test.js +197 -0
  237. package/dist/indexer/parser.test.js.map +1 -0
  238. package/dist/indexer/reranking.d.ts +71 -0
  239. package/dist/indexer/reranking.d.ts.map +1 -0
  240. package/dist/indexer/reranking.integration.test.d.ts +2 -0
  241. package/dist/indexer/reranking.integration.test.d.ts.map +1 -0
  242. package/dist/indexer/reranking.integration.test.js +104 -0
  243. package/dist/indexer/reranking.integration.test.js.map +1 -0
  244. package/dist/indexer/reranking.js +256 -0
  245. package/dist/indexer/reranking.js.map +1 -0
  246. package/dist/indexer/reranking.test.d.ts +2 -0
  247. package/dist/indexer/reranking.test.d.ts.map +1 -0
  248. package/dist/indexer/reranking.test.js +130 -0
  249. package/dist/indexer/reranking.test.js.map +1 -0
  250. package/dist/indexer/wal/file-storage.d.ts +60 -0
  251. package/dist/indexer/wal/file-storage.d.ts.map +1 -0
  252. package/dist/indexer/wal/file-storage.js +277 -0
  253. package/dist/indexer/wal/file-storage.js.map +1 -0
  254. package/dist/indexer/wal/file-storage.test.d.ts +8 -0
  255. package/dist/indexer/wal/file-storage.test.d.ts.map +1 -0
  256. package/dist/indexer/wal/file-storage.test.js +444 -0
  257. package/dist/indexer/wal/file-storage.test.js.map +1 -0
  258. package/dist/indexer/wal/index.d.ts +41 -0
  259. package/dist/indexer/wal/index.d.ts.map +1 -0
  260. package/dist/indexer/wal/index.js +61 -0
  261. package/dist/indexer/wal/index.js.map +1 -0
  262. package/dist/indexer/wal/integration.test.d.ts +11 -0
  263. package/dist/indexer/wal/integration.test.d.ts.map +1 -0
  264. package/dist/indexer/wal/integration.test.js +378 -0
  265. package/dist/indexer/wal/integration.test.js.map +1 -0
  266. package/dist/indexer/wal/lancedb-storage.d.ts +72 -0
  267. package/dist/indexer/wal/lancedb-storage.d.ts.map +1 -0
  268. package/dist/indexer/wal/lancedb-storage.js +462 -0
  269. package/dist/indexer/wal/lancedb-storage.js.map +1 -0
  270. package/dist/indexer/wal/lancedb-storage.test.d.ts +8 -0
  271. package/dist/indexer/wal/lancedb-storage.test.d.ts.map +1 -0
  272. package/dist/indexer/wal/lancedb-storage.test.js +415 -0
  273. package/dist/indexer/wal/lancedb-storage.test.js.map +1 -0
  274. package/dist/indexer/wal/sync-wal.d.ts +144 -0
  275. package/dist/indexer/wal/sync-wal.d.ts.map +1 -0
  276. package/dist/indexer/wal/sync-wal.js +863 -0
  277. package/dist/indexer/wal/sync-wal.js.map +1 -0
  278. package/dist/indexer/wal/sync-wal.test.d.ts +8 -0
  279. package/dist/indexer/wal/sync-wal.test.d.ts.map +1 -0
  280. package/dist/indexer/wal/sync-wal.test.js +752 -0
  281. package/dist/indexer/wal/sync-wal.test.js.map +1 -0
  282. package/dist/indexer/wal/types.d.ts +167 -0
  283. package/dist/indexer/wal/types.d.ts.map +1 -0
  284. package/dist/indexer/wal/types.js +12 -0
  285. package/dist/indexer/wal/types.js.map +1 -0
  286. package/dist/indexer/watcher.d.ts +36 -0
  287. package/dist/indexer/watcher.d.ts.map +1 -0
  288. package/dist/indexer/watcher.js +110 -0
  289. package/dist/indexer/watcher.js.map +1 -0
  290. package/dist/search/explore.d.ts +62 -0
  291. package/dist/search/explore.d.ts.map +1 -0
  292. package/dist/search/explore.js +111 -0
  293. package/dist/search/explore.js.map +1 -0
  294. package/dist/search/fts.d.ts +23 -0
  295. package/dist/search/fts.d.ts.map +1 -0
  296. package/dist/search/fts.js +64 -0
  297. package/dist/search/fts.js.map +1 -0
  298. package/dist/search/fts.test.d.ts +2 -0
  299. package/dist/search/fts.test.d.ts.map +1 -0
  300. package/dist/search/fts.test.js +27 -0
  301. package/dist/search/fts.test.js.map +1 -0
  302. package/dist/search/grep.d.ts +75 -0
  303. package/dist/search/grep.d.ts.map +1 -0
  304. package/dist/search/grep.js +96 -0
  305. package/dist/search/grep.js.map +1 -0
  306. package/dist/search/grep.test.d.ts +2 -0
  307. package/dist/search/grep.test.d.ts.map +1 -0
  308. package/dist/search/grep.test.js +178 -0
  309. package/dist/search/grep.test.js.map +1 -0
  310. package/dist/search/hybrid-grep.d.ts +43 -0
  311. package/dist/search/hybrid-grep.d.ts.map +1 -0
  312. package/dist/search/hybrid-grep.js +130 -0
  313. package/dist/search/hybrid-grep.js.map +1 -0
  314. package/dist/search/hybrid-grep.test.d.ts +2 -0
  315. package/dist/search/hybrid-grep.test.d.ts.map +1 -0
  316. package/dist/search/hybrid-grep.test.js +133 -0
  317. package/dist/search/hybrid-grep.test.js.map +1 -0
  318. package/dist/search/rg-executor.d.ts +63 -0
  319. package/dist/search/rg-executor.d.ts.map +1 -0
  320. package/dist/search/rg-executor.js +146 -0
  321. package/dist/search/rg-executor.js.map +1 -0
  322. package/dist/search/rg-executor.test.d.ts +2 -0
  323. package/dist/search/rg-executor.test.d.ts.map +1 -0
  324. package/dist/search/rg-executor.test.js +104 -0
  325. package/dist/search/rg-executor.test.js.map +1 -0
  326. package/dist/search/rg-parser/extractor.d.ts +14 -0
  327. package/dist/search/rg-parser/extractor.d.ts.map +1 -0
  328. package/dist/search/rg-parser/extractor.js +82 -0
  329. package/dist/search/rg-parser/extractor.js.map +1 -0
  330. package/dist/search/rg-parser/extractor.test.d.ts +2 -0
  331. package/dist/search/rg-parser/extractor.test.d.ts.map +1 -0
  332. package/dist/search/rg-parser/extractor.test.js +35 -0
  333. package/dist/search/rg-parser/extractor.test.js.map +1 -0
  334. package/dist/search/rg-parser/fts-builder.d.ts +7 -0
  335. package/dist/search/rg-parser/fts-builder.d.ts.map +1 -0
  336. package/dist/search/rg-parser/fts-builder.js +18 -0
  337. package/dist/search/rg-parser/fts-builder.js.map +1 -0
  338. package/dist/search/rg-parser/fts-builder.test.d.ts +2 -0
  339. package/dist/search/rg-parser/fts-builder.test.d.ts.map +1 -0
  340. package/dist/search/rg-parser/fts-builder.test.js +26 -0
  341. package/dist/search/rg-parser/fts-builder.test.js.map +1 -0
  342. package/dist/search/rg-parser/index.d.ts +36 -0
  343. package/dist/search/rg-parser/index.d.ts.map +1 -0
  344. package/dist/search/rg-parser/index.js +83 -0
  345. package/dist/search/rg-parser/index.js.map +1 -0
  346. package/dist/search/rg-parser/index.test.d.ts +2 -0
  347. package/dist/search/rg-parser/index.test.d.ts.map +1 -0
  348. package/dist/search/rg-parser/index.test.js +34 -0
  349. package/dist/search/rg-parser/index.test.js.map +1 -0
  350. package/dist/search/rg-parser/strategy.d.ts +14 -0
  351. package/dist/search/rg-parser/strategy.d.ts.map +1 -0
  352. package/dist/search/rg-parser/strategy.js +31 -0
  353. package/dist/search/rg-parser/strategy.js.map +1 -0
  354. package/dist/search/rg-parser/strategy.test.d.ts +2 -0
  355. package/dist/search/rg-parser/strategy.test.d.ts.map +1 -0
  356. package/dist/search/rg-parser/strategy.test.js +29 -0
  357. package/dist/search/rg-parser/strategy.test.js.map +1 -0
  358. package/dist/types.d.ts +345 -0
  359. package/dist/types.d.ts.map +1 -0
  360. package/dist/types.js +7 -0
  361. package/dist/types.js.map +1 -0
  362. package/dist/utils/vault.d.ts +84 -0
  363. package/dist/utils/vault.d.ts.map +1 -0
  364. package/dist/utils/vault.js +138 -0
  365. package/dist/utils/vault.js.map +1 -0
  366. package/dist/utils/vault.test.d.ts +2 -0
  367. package/dist/utils/vault.test.d.ts.map +1 -0
  368. package/dist/utils/vault.test.js +153 -0
  369. package/dist/utils/vault.test.js.map +1 -0
  370. package/package.json +69 -0
@@ -0,0 +1,1876 @@
1
+ import * as lancedb from '@lancedb/lancedb';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { glob } from 'glob';
5
+ import { createError } from '../../../types.js';
6
+ import { generateContentHash } from '../chunker.js';
7
+ import { extractFacts } from './facts.js';
8
+ import { createEdgeId, createExportId } from './id.js';
9
+ import { extractEmbedded, parseCSSContent, parseGraphQLContent } from './embedded.js';
10
+ import { buildNodeEmbeddingText } from './queries.js';
11
+ import { ExportMapBuilder, loadTSConfig, resolveModule, clearResolverCache, WorkspaceResolver } from './resolver.js';
12
+ import { analyzeAllFilesSemantics, loadTypeScript } from './semantic.js';
13
+ /** Convert CodeEdge to LanceDB record */
14
+ function toEdgeRecord(edge) {
15
+ return {
16
+ id: edge.id,
17
+ source_id: edge.sourceId,
18
+ target_id: edge.targetId,
19
+ edge_type: edge.edgeType,
20
+ source_file: edge.sourceFile,
21
+ target_file: edge.targetFile ?? '',
22
+ target_module: edge.targetModule ?? '',
23
+ confidence: edge.confidence,
24
+ metadata: edge.metadata ?? '',
25
+ };
26
+ }
27
+ /** Convert LanceDB record to CodeEdge */
28
+ function fromEdgeRecord(r) {
29
+ return {
30
+ id: r.id,
31
+ sourceId: r.source_id,
32
+ targetId: r.target_id,
33
+ edgeType: r.edge_type,
34
+ sourceFile: r.source_file,
35
+ targetFile: r.target_file || null,
36
+ targetModule: r.target_module || null,
37
+ confidence: r.confidence,
38
+ metadata: r.metadata || null,
39
+ };
40
+ }
41
+ /** Convert CodeExport to LanceDB record */
42
+ function toExportRecord(exp) {
43
+ return {
44
+ id: exp.id,
45
+ file_id: exp.fileId,
46
+ export_name: exp.exportName,
47
+ symbol_id: exp.symbolId,
48
+ is_reexport: exp.isReexport,
49
+ original_file: exp.originalFile ?? '',
50
+ };
51
+ }
52
+ /** Convert LanceDB record to CodeExport */
53
+ function fromExportRecord(r) {
54
+ return {
55
+ id: r.id,
56
+ fileId: r.file_id,
57
+ exportName: r.export_name,
58
+ symbolId: r.symbol_id,
59
+ isReexport: r.is_reexport,
60
+ originalFile: r.original_file || null,
61
+ };
62
+ }
63
+ /** Convert CodeModule to LanceDB record */
64
+ function toModuleRecord(mod) {
65
+ return {
66
+ id: mod.id,
67
+ type: mod.type,
68
+ package_name: mod.packageName ?? '',
69
+ path: mod.path ?? '',
70
+ version: mod.version ?? '',
71
+ exports: mod.exports,
72
+ };
73
+ }
74
+ /** Convert LanceDB record to CodeModule */
75
+ function fromModuleRecord(r) {
76
+ return {
77
+ id: r.id,
78
+ type: r.type,
79
+ packageName: r.package_name || null,
80
+ path: r.path || null,
81
+ version: r.version || null,
82
+ exports: r.exports,
83
+ };
84
+ }
85
+ /** Convert EmbeddedChunk (camelCase) to EmbeddedRecord (snake_case) for LanceDB */
86
+ function toEmbeddedRecord(chunk) {
87
+ return {
88
+ id: chunk.id,
89
+ host_file_id: chunk.hostFileId,
90
+ host_symbol_id: chunk.hostSymbolId ?? '',
91
+ embedded_type: chunk.embeddedType,
92
+ content: chunk.content,
93
+ start_offset: chunk.startOffset,
94
+ end_offset: chunk.endOffset,
95
+ start_line: chunk.startLine,
96
+ end_line: chunk.endLine,
97
+ };
98
+ }
99
+ const DEFAULT_INCLUDE = [
100
+ '**/*.ts',
101
+ '**/*.tsx',
102
+ '**/*.js',
103
+ '**/*.jsx',
104
+ '**/*.mjs',
105
+ '**/*.cjs',
106
+ ];
107
+ const DEFAULT_EXCLUDE = [
108
+ '**/node_modules/**',
109
+ '**/dist/**',
110
+ '**/build/**',
111
+ '**/.git/**',
112
+ ];
113
+ export const QUALITY_PRESETS = {
114
+ fast: {
115
+ useTypeChecker: false,
116
+ maxClosureDepth: 0,
117
+ maxClosureFiles: 0,
118
+ embedEmbedded: false,
119
+ },
120
+ balanced: {
121
+ useTypeChecker: true,
122
+ maxClosureDepth: 2,
123
+ maxClosureFiles: 30,
124
+ embedEmbedded: true,
125
+ },
126
+ accurate: {
127
+ useTypeChecker: true,
128
+ maxClosureDepth: 5,
129
+ maxClosureFiles: 100,
130
+ embedEmbedded: true,
131
+ },
132
+ };
133
+ function normalizePath(filePath) {
134
+ return filePath.replace(/\\/g, '/');
135
+ }
136
+ function stringifyMetadata(value) {
137
+ if (value == null)
138
+ return null;
139
+ try {
140
+ return JSON.stringify(value);
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ }
146
+ function parseMetadata(value) {
147
+ if (!value)
148
+ return {};
149
+ try {
150
+ return JSON.parse(value);
151
+ }
152
+ catch {
153
+ return {};
154
+ }
155
+ }
156
+ function edgeTypeCount() {
157
+ return {
158
+ IMPORTS_FILE: 0,
159
+ IMPORTS_SYMBOL: 0,
160
+ EXPORTS: 0,
161
+ DEFINES: 0,
162
+ REFS: 0,
163
+ CALLS: 0,
164
+ EXTENDS: 0,
165
+ IMPLEMENTS: 0,
166
+ EMBEDS: 0,
167
+ USES_FRAGMENT: 0,
168
+ };
169
+ }
170
+ function buildStatsFromPrepared(filesIndexed, symbols, edges, embeddedChunks) {
171
+ const edgeTypes = edgeTypeCount();
172
+ for (const edge of edges) {
173
+ edgeTypes[edge.edgeType] += 1;
174
+ }
175
+ const embeddedByType = {
176
+ graphql: 0,
177
+ css: 0,
178
+ styled: 0,
179
+ };
180
+ for (const chunk of embeddedChunks) {
181
+ // Use camelCase from EmbeddedChunk
182
+ embeddedByType[chunk.embeddedType] += 1;
183
+ }
184
+ return {
185
+ filesIndexed,
186
+ symbols,
187
+ edges: edges.length,
188
+ edgeTypes,
189
+ unresolvedImports: edges.filter((edge) => edge.edgeType === 'IMPORTS_FILE' && edge.confidence === 'unresolved').length,
190
+ embeddedChunks: embeddedChunks.length,
191
+ embeddedByType,
192
+ };
193
+ }
194
+ export class ReverseIndexBuilder {
195
+ index = new Map();
196
+ buildFromEdges(edges) {
197
+ this.index.clear();
198
+ for (const edge of edges) {
199
+ if (edge.edgeType !== 'IMPORTS_FILE' || !edge.targetFile)
200
+ continue;
201
+ const importers = this.index.get(edge.targetFile) ?? new Set();
202
+ importers.add(edge.sourceFile);
203
+ this.index.set(edge.targetFile, importers);
204
+ }
205
+ }
206
+ updateForFile(fileId, oldImports, newImports) {
207
+ for (const oldTarget of oldImports) {
208
+ this.index.get(oldTarget)?.delete(fileId);
209
+ }
210
+ for (const newTarget of newImports) {
211
+ const importers = this.index.get(newTarget) ?? new Set();
212
+ importers.add(fileId);
213
+ this.index.set(newTarget, importers);
214
+ }
215
+ }
216
+ getImporters(fileId) {
217
+ return this.index.get(fileId) ?? new Set();
218
+ }
219
+ getTransitiveImporters(fileId, maxDepth = 2) {
220
+ const result = new Set();
221
+ const queue = [{ file: fileId, depth: 0 }];
222
+ while (queue.length > 0) {
223
+ const item = queue.shift();
224
+ if (!item)
225
+ break;
226
+ if (item.depth >= maxDepth)
227
+ continue;
228
+ for (const importer of this.getImporters(item.file)) {
229
+ if (result.has(importer))
230
+ continue;
231
+ result.add(importer);
232
+ queue.push({ file: importer, depth: item.depth + 1 });
233
+ }
234
+ }
235
+ return result;
236
+ }
237
+ }
238
+ export async function detectChanges(currentFiles, previousIndex, reverseIndex, transitiveDepth = 2) {
239
+ const modified = [];
240
+ const deleted = [];
241
+ for (const [filePath, meta] of currentFiles) {
242
+ const prev = previousIndex.get(filePath);
243
+ if (!prev || prev.contentHash !== meta.contentHash) {
244
+ modified.push(filePath);
245
+ }
246
+ }
247
+ for (const filePath of previousIndex.keys()) {
248
+ if (!currentFiles.has(filePath)) {
249
+ deleted.push(filePath);
250
+ }
251
+ }
252
+ // Use transitive importers to handle re-exports properly
253
+ // When file A re-exports from B, and B changes, importers of A are also affected
254
+ const affectedSet = new Set();
255
+ const modifiedSet = new Set(modified);
256
+ const deletedSet = new Set(deleted);
257
+ for (const fileId of [...modified, ...deleted]) {
258
+ // Get transitive importers (handles re-export chains)
259
+ const transitiveImporters = reverseIndex.getTransitiveImporters(fileId, transitiveDepth);
260
+ for (const importer of transitiveImporters) {
261
+ if (modifiedSet.has(importer) || deletedSet.has(importer))
262
+ continue;
263
+ affectedSet.add(importer);
264
+ }
265
+ }
266
+ return {
267
+ modified,
268
+ deleted,
269
+ affected: [...affectedSet],
270
+ };
271
+ }
272
+ export class GraphIndexer {
273
+ rootPath;
274
+ dbPath;
275
+ codeConfig;
276
+ parser;
277
+ storageOptions;
278
+ db = null;
279
+ initialized = false;
280
+ chunksTable = null;
281
+ edgesTable = null;
282
+ modulesTable = null;
283
+ exportsTable = null;
284
+ embeddedTable = null;
285
+ graphFileTable = null;
286
+ reverseIndex = new ReverseIndexBuilder();
287
+ tsConfigLoaded = false;
288
+ tsConfig = null;
289
+ moduleResolveCache = new Map();
290
+ // Multi-tsconfig support: cache configs by directory
291
+ tsConfigCache = new Map();
292
+ tsConfigsDiscovered = false;
293
+ // Workspace resolution for monorepo cross-package imports
294
+ workspaceResolver = null;
295
+ constructor(deps) {
296
+ this.rootPath = path.resolve(deps.rootPath);
297
+ this.dbPath = deps.dbPath;
298
+ this.codeConfig = deps.codeConfig;
299
+ this.parser = deps.parser;
300
+ this.storageOptions = deps.storageOptions;
301
+ }
302
+ async initialize() {
303
+ if (this.initialized)
304
+ return;
305
+ const isRemoteDbPath = /^[a-z][a-z0-9+.-]*:\/\//i.test(this.dbPath);
306
+ if (!isRemoteDbPath) {
307
+ await fs.mkdir(this.dbPath, { recursive: true });
308
+ }
309
+ this.db = await lancedb.connect(this.dbPath, {
310
+ storageOptions: this.storageOptions,
311
+ });
312
+ this.initialized = true;
313
+ // Auto-create all tables at startup
314
+ await this.ensureAllTables();
315
+ // Discover workspace packages for cross-package resolution
316
+ this.workspaceResolver = new WorkspaceResolver(this.rootPath);
317
+ await this.workspaceResolver.discover();
318
+ }
319
+ /**
320
+ * Ensure all graph tables exist with proper schema.
321
+ * Safe to call multiple times (idempotent).
322
+ */
323
+ async ensureAllTables() {
324
+ if (!this.initialized) {
325
+ await this.initialize();
326
+ return; // initialize() already called ensureAllTables()
327
+ }
328
+ await this.ensureEdgesTable();
329
+ await this.ensureExportsTable();
330
+ await this.ensureModulesTable();
331
+ await this.ensureEmbeddedTable();
332
+ await this.ensureGraphFileTable();
333
+ }
334
+ async getTsConfig() {
335
+ if (this.tsConfigLoaded)
336
+ return this.tsConfig;
337
+ this.tsConfigLoaded = true;
338
+ this.tsConfig = await loadTSConfig(this.rootPath, this.codeConfig.graph.resolution.tsConfigPath);
339
+ return this.tsConfig;
340
+ }
341
+ /**
342
+ * Discover all tsconfig.json files in the project.
343
+ * Call once before processing to populate the cache.
344
+ */
345
+ async discoverAllTsConfigs() {
346
+ if (this.tsConfigsDiscovered)
347
+ return;
348
+ this.tsConfigsDiscovered = true;
349
+ try {
350
+ const tsConfigFiles = await glob(path.join(this.rootPath, '**/tsconfig.json'), {
351
+ ignore: ['**/node_modules/**'],
352
+ nodir: true,
353
+ });
354
+ for (const configPath of tsConfigFiles) {
355
+ const dir = normalizePath(path.relative(this.rootPath, path.dirname(configPath)));
356
+ const config = await loadTSConfig(this.rootPath, path.join(dir, 'tsconfig.json'));
357
+ if (config) {
358
+ this.tsConfigCache.set(dir || '.', config);
359
+ }
360
+ }
361
+ }
362
+ catch {
363
+ // Ignore errors during discovery
364
+ }
365
+ }
366
+ /**
367
+ * Get the tsconfig for a specific file by walking up directories.
368
+ * Falls back to root tsconfig or null if none found.
369
+ */
370
+ async getTsConfigForFile(fileId) {
371
+ // Ensure configs are discovered
372
+ await this.discoverAllTsConfigs();
373
+ // Walk up from file's directory to find closest tsconfig
374
+ let dir = normalizePath(path.dirname(fileId));
375
+ while (true) {
376
+ const cachedConfig = this.tsConfigCache.get(dir);
377
+ if (cachedConfig !== undefined) {
378
+ return cachedConfig;
379
+ }
380
+ // Try root
381
+ if (dir === '.' || dir === '') {
382
+ break;
383
+ }
384
+ // Go up one level
385
+ const parent = normalizePath(path.dirname(dir));
386
+ if (parent === dir)
387
+ break;
388
+ dir = parent;
389
+ }
390
+ // Fallback to root tsconfig (from config or auto-detect)
391
+ return this.getTsConfig();
392
+ }
393
+ async replaceTable(tableName, seedRow, rows) {
394
+ const table = await this.db.createTable(tableName, [seedRow, ...rows], { mode: 'overwrite' });
395
+ if ('id' in seedRow) {
396
+ await table.delete("id = '__init__'");
397
+ }
398
+ else if ('path' in seedRow) {
399
+ await table.delete("path = '__init__'");
400
+ }
401
+ return table;
402
+ }
403
+ async ensureChunksTable() {
404
+ if (this.chunksTable)
405
+ return this.chunksTable;
406
+ this.chunksTable = await this.db.openTable('code_chunks');
407
+ return this.chunksTable;
408
+ }
409
+ async ensureEdgesTable() {
410
+ if (this.edgesTable)
411
+ return this.edgesTable;
412
+ try {
413
+ this.edgesTable = await this.db.openTable('code_edges');
414
+ }
415
+ catch {
416
+ // Table doesn't exist, create it
417
+ this.edgesTable = await this.db.createTable('code_edges', [{
418
+ id: '__init__',
419
+ source_id: '',
420
+ target_id: '',
421
+ edge_type: 'IMPORTS_FILE',
422
+ source_file: '',
423
+ target_file: '',
424
+ target_module: '',
425
+ confidence: 'resolved',
426
+ metadata: '',
427
+ }], { mode: 'overwrite' });
428
+ await this.edgesTable.delete("id = '__init__'");
429
+ }
430
+ return this.edgesTable;
431
+ }
432
+ async ensureExportsTable() {
433
+ if (this.exportsTable)
434
+ return this.exportsTable;
435
+ try {
436
+ this.exportsTable = await this.db.openTable('code_exports');
437
+ }
438
+ catch {
439
+ this.exportsTable = await this.db.createTable('code_exports', [{
440
+ id: '__init__',
441
+ file_id: '',
442
+ export_name: '',
443
+ symbol_id: '',
444
+ is_reexport: false,
445
+ original_file: '',
446
+ }], { mode: 'overwrite' });
447
+ await this.exportsTable.delete("id = '__init__'");
448
+ }
449
+ return this.exportsTable;
450
+ }
451
+ async ensureEmbeddedTable() {
452
+ if (this.embeddedTable)
453
+ return this.embeddedTable;
454
+ try {
455
+ this.embeddedTable = await this.db.openTable('code_embedded');
456
+ }
457
+ catch {
458
+ this.embeddedTable = await this.db.createTable('code_embedded', [{
459
+ id: '__init__',
460
+ host_file_id: '',
461
+ host_symbol_id: '',
462
+ embedded_type: 'graphql',
463
+ content: '',
464
+ start_offset: 0,
465
+ end_offset: 0,
466
+ start_line: 0,
467
+ end_line: 0,
468
+ }], { mode: 'overwrite' });
469
+ await this.embeddedTable.delete("id = '__init__'");
470
+ }
471
+ return this.embeddedTable;
472
+ }
473
+ async ensureGraphFileTable() {
474
+ if (this.graphFileTable)
475
+ return this.graphFileTable;
476
+ try {
477
+ this.graphFileTable = await this.db.openTable('code_graph_file_index');
478
+ }
479
+ catch {
480
+ // Use snake_case column names for LanceDB SQL compatibility
481
+ this.graphFileTable = await this.db.createTable('code_graph_file_index', [{
482
+ path: '__init__',
483
+ content_hash: '',
484
+ indexed_at: 0,
485
+ }], { mode: 'overwrite' });
486
+ await this.graphFileTable.delete("path = '__init__'");
487
+ }
488
+ return this.graphFileTable;
489
+ }
490
+ async ensureModulesTable() {
491
+ if (this.modulesTable)
492
+ return this.modulesTable;
493
+ try {
494
+ this.modulesTable = await this.db.openTable('code_modules');
495
+ }
496
+ catch {
497
+ // Use snake_case column names for LanceDB SQL compatibility
498
+ this.modulesTable = await this.db.createTable('code_modules', [{
499
+ id: '__init__',
500
+ type: 'npm',
501
+ package_name: '',
502
+ path: '',
503
+ version: '',
504
+ exports: '[]',
505
+ }], { mode: 'overwrite' });
506
+ await this.modulesTable.delete("id = '__init__'");
507
+ }
508
+ return this.modulesTable;
509
+ }
510
+ async addEdges(edges) {
511
+ if (edges.length === 0)
512
+ return;
513
+ const table = await this.ensureEdgesTable();
514
+ // Convert to snake_case records for LanceDB
515
+ await table.add(edges.map(toEdgeRecord));
516
+ }
517
+ async addExports(exports) {
518
+ if (exports.length === 0)
519
+ return;
520
+ const table = await this.ensureExportsTable();
521
+ // Convert to snake_case records for LanceDB
522
+ await table.add(exports.map(toExportRecord));
523
+ }
524
+ async addEmbedded(chunks) {
525
+ if (chunks.length === 0)
526
+ return;
527
+ const table = await this.ensureEmbeddedTable();
528
+ // Convert EmbeddedChunk (camelCase) to EmbeddedRecord (snake_case) for LanceDB
529
+ await table.add(chunks.map(toEmbeddedRecord));
530
+ }
531
+ async addModules(modules) {
532
+ if (modules.length === 0)
533
+ return;
534
+ const table = await this.ensureModulesTable();
535
+ // Filter out modules that already exist
536
+ const existing = new Set();
537
+ const existingRows = await table.query().select(['id']).toArray();
538
+ for (const row of existingRows) {
539
+ existing.add(row.id);
540
+ }
541
+ const newModules = modules.filter(m => !existing.has(m.id));
542
+ if (newModules.length > 0) {
543
+ // Convert to snake_case records for LanceDB
544
+ await table.add(newModules.map(toModuleRecord));
545
+ }
546
+ }
547
+ async updateGraphFileRecords(recordsOrFileId, contentHash, mtime) {
548
+ const table = await this.ensureGraphFileTable();
549
+ // Overload: single record
550
+ if (typeof recordsOrFileId === 'string') {
551
+ const path = recordsOrFileId;
552
+ await table.delete(`path = '${path.replace(/'/g, "''")}'`);
553
+ await table.add([{ path, content_hash: contentHash, indexed_at: mtime }]);
554
+ return;
555
+ }
556
+ // Overload: array of records (already in snake_case)
557
+ const records = recordsOrFileId;
558
+ if (records.length === 0)
559
+ return;
560
+ for (const record of records) {
561
+ await table.delete(`path = '${record.path.replace(/'/g, "''")}'`);
562
+ }
563
+ await table.add(records);
564
+ }
565
+ async listCodeFiles() {
566
+ const include = this.codeConfig.include ?? DEFAULT_INCLUDE;
567
+ const exclude = this.codeConfig.exclude ?? DEFAULT_EXCLUDE;
568
+ const files = [];
569
+ for (const pattern of include) {
570
+ const matches = await glob(path.join(this.rootPath, pattern), {
571
+ ignore: exclude,
572
+ nodir: true,
573
+ });
574
+ files.push(...matches);
575
+ }
576
+ return [...new Set(files)]
577
+ .map((filePath) => normalizePath(path.relative(this.rootPath, filePath)));
578
+ }
579
+ /**
580
+ * Parse a single file on-demand for graph analysis (without embeddings).
581
+ * Used when import resolution discovers files outside the indexed set.
582
+ * Returns the chunks or null if file cannot be parsed.
583
+ */
584
+ async parseFileOnDemand(fileId) {
585
+ const absolutePath = path.resolve(this.rootPath, fileId);
586
+ // Check if file is within project and not excluded
587
+ if (!this.isFileAllowedForGraph(fileId)) {
588
+ return null;
589
+ }
590
+ try {
591
+ const content = await fs.readFile(absolutePath, 'utf-8');
592
+ const language = this.parser.getLanguageForFile(absolutePath);
593
+ if (!language)
594
+ return null;
595
+ const tree = await this.parser.parse(content, language);
596
+ if (!tree)
597
+ return null;
598
+ // Use tree-sitter to extract symbols (lightweight, no embeddings)
599
+ const extractedSymbols = this.parser.extractSymbols(tree, language);
600
+ const chunks = [];
601
+ for (const sym of extractedSymbols) {
602
+ const chunk = {
603
+ id: `${fileId}:${sym.name}:${sym.startLine}`,
604
+ path: fileId,
605
+ language,
606
+ symbolType: sym.type,
607
+ symbolName: sym.name,
608
+ signature: null,
609
+ parentSymbol: null,
610
+ scope: [],
611
+ content: '', // Empty - not needed for graph analysis
612
+ startLine: sym.startLine,
613
+ endLine: sym.endLine,
614
+ docstring: null,
615
+ modified: Date.now(),
616
+ contentHash: '',
617
+ };
618
+ chunks.push(chunk);
619
+ }
620
+ if (chunks.length > 0) {
621
+ chunks.sort((a, b) => a.startLine - b.startLine || a.endLine - b.endLine);
622
+ return chunks;
623
+ }
624
+ return null;
625
+ }
626
+ catch {
627
+ return null;
628
+ }
629
+ }
630
+ /**
631
+ * Check if a file is allowed for graph analysis.
632
+ * Files must be within project root (or workspace root for monorepos) and not in node_modules.
633
+ */
634
+ isFileAllowedForGraph(fileId) {
635
+ // Skip node_modules
636
+ if (fileId.includes('node_modules')) {
637
+ return false;
638
+ }
639
+ // Must be a supported file type
640
+ if (!/\.(ts|tsx|js|jsx|mjs|cjs)$/.test(fileId)) {
641
+ return false;
642
+ }
643
+ // Absolute paths are not allowed
644
+ if (path.isAbsolute(fileId)) {
645
+ return false;
646
+ }
647
+ // Check if within project root
648
+ if (!fileId.startsWith('..')) {
649
+ return true;
650
+ }
651
+ // For paths starting with .., check if they're within workspace root
652
+ const workspaceRoot = this.workspaceResolver?.getWorkspaceRoot();
653
+ if (!workspaceRoot) {
654
+ return false;
655
+ }
656
+ // Resolve the absolute path and check if it's within workspace root
657
+ const absolutePath = path.resolve(this.rootPath, fileId);
658
+ const relativeToWorkspace = path.relative(workspaceRoot, absolutePath);
659
+ // Must be within workspace root and not in node_modules
660
+ return !relativeToWorkspace.startsWith('..') &&
661
+ !path.isAbsolute(relativeToWorkspace) &&
662
+ !relativeToWorkspace.includes('node_modules');
663
+ }
664
+ /**
665
+ * Ensure files are parsed for graph analysis.
666
+ * Parses any files that are not already in chunksByFile.
667
+ */
668
+ async ensureFilesParsedForGraph(fileIds, chunksByFile, symbolToFile) {
669
+ for (const fileId of fileIds) {
670
+ if (chunksByFile.has(fileId))
671
+ continue;
672
+ const chunks = await this.parseFileOnDemand(fileId);
673
+ if (chunks) {
674
+ chunksByFile.set(fileId, chunks);
675
+ // Update symbolToFile map
676
+ for (const chunk of chunks) {
677
+ if (chunk.symbolType !== 'file') {
678
+ symbolToFile.set(chunk.id, fileId);
679
+ }
680
+ }
681
+ }
682
+ }
683
+ }
684
+ async loadFileMetadata(files) {
685
+ const metadata = new Map();
686
+ for (const fileId of files) {
687
+ const absolutePath = path.resolve(this.rootPath, fileId);
688
+ try {
689
+ const [stat, content] = await Promise.all([
690
+ fs.stat(absolutePath),
691
+ fs.readFile(absolutePath, 'utf-8'),
692
+ ]);
693
+ metadata.set(fileId, {
694
+ contentHash: generateContentHash(content),
695
+ mtime: stat.mtimeMs,
696
+ });
697
+ }
698
+ catch {
699
+ // ignore unreadable files
700
+ }
701
+ }
702
+ return metadata;
703
+ }
704
+ async loadPreviousGraphFileIndex() {
705
+ if (!this.graphFileTable) {
706
+ try {
707
+ this.graphFileTable = await this.db.openTable('code_graph_file_index');
708
+ }
709
+ catch {
710
+ return new Map();
711
+ }
712
+ }
713
+ const rows = await this.graphFileTable.query().toArray();
714
+ const result = new Map();
715
+ for (const row of rows) {
716
+ const filePath = row.path;
717
+ // Read snake_case columns from LanceDB
718
+ result.set(filePath, {
719
+ contentHash: row.content_hash,
720
+ mtime: row.indexed_at,
721
+ });
722
+ }
723
+ return result;
724
+ }
725
+ async loadChunksByFile() {
726
+ const table = await this.ensureChunksTable();
727
+ const rows = await table.query().toArray();
728
+ const byFile = new Map();
729
+ for (const row of rows) {
730
+ // Read from snake_case columns (code_chunks table was migrated)
731
+ const chunk = {
732
+ id: row.id,
733
+ path: row.path,
734
+ language: row.language,
735
+ symbolType: row.symbol_type,
736
+ symbolName: row.symbol_name,
737
+ signature: row.signature,
738
+ parentSymbol: row.parent_symbol,
739
+ scope: JSON.parse(row.scope || '[]'),
740
+ content: row.content,
741
+ startLine: row.start_line,
742
+ endLine: row.end_line,
743
+ docstring: row.docstring,
744
+ modified: row.modified,
745
+ contentHash: row.content_hash,
746
+ };
747
+ const arr = byFile.get(chunk.path) ?? [];
748
+ arr.push(chunk);
749
+ byFile.set(chunk.path, arr);
750
+ }
751
+ for (const chunks of byFile.values()) {
752
+ chunks.sort((a, b) => a.startLine - b.startLine || a.endLine - b.endLine);
753
+ }
754
+ return byFile;
755
+ }
756
+ async resolveImport(spec, basePath) {
757
+ const cacheKey = `${basePath}::${spec}`;
758
+ const cached = this.moduleResolveCache.get(cacheKey);
759
+ if (cached)
760
+ return cached;
761
+ // Use tsconfig closest to the importing file for proper path resolution
762
+ const resolved = await resolveModule(spec, {
763
+ basePath,
764
+ rootPath: this.rootPath,
765
+ tsConfig: await this.getTsConfigForFile(basePath),
766
+ nodeModulesPath: path.join(this.rootPath, 'node_modules'),
767
+ respectTSPaths: this.codeConfig.graph.resolution.respectTSPaths,
768
+ workspaceResolver: this.workspaceResolver ?? undefined,
769
+ });
770
+ this.moduleResolveCache.set(cacheKey, resolved);
771
+ return resolved;
772
+ }
773
+ async buildFacts(files, chunksByFile, embedEnabled, onProgress) {
774
+ const factsPerFile = new Map();
775
+ const fileMetadata = new Map();
776
+ const embeddedChunks = [];
777
+ for (let i = 0; i < files.length; i++) {
778
+ const fileId = files[i];
779
+ onProgress?.(i, files.length, 'parsing');
780
+ const absolutePath = path.resolve(this.rootPath, fileId);
781
+ try {
782
+ const [stat, content] = await Promise.all([
783
+ fs.stat(absolutePath),
784
+ fs.readFile(absolutePath, 'utf-8'),
785
+ ]);
786
+ const language = this.parser.getLanguageForFile(absolutePath);
787
+ const chunks = chunksByFile.get(fileId) ?? [];
788
+ const facts = await extractFacts(content, fileId, {
789
+ language,
790
+ parser: this.parser,
791
+ chunks,
792
+ });
793
+ factsPerFile.set(fileId, facts);
794
+ fileMetadata.set(fileId, {
795
+ contentHash: generateContentHash(content),
796
+ mtime: stat.mtimeMs,
797
+ });
798
+ if (embedEnabled && (language === 'typescript' || language === 'tsx' || language === 'javascript' || language === 'jsx')) {
799
+ const embedded = await extractEmbedded({
800
+ fileId,
801
+ fileContent: content,
802
+ chunks,
803
+ });
804
+ for (const chunk of embedded) {
805
+ if (chunk.embeddedType === 'graphql' && !this.codeConfig.graph.embedded.graphql) {
806
+ continue;
807
+ }
808
+ if ((chunk.embeddedType === 'css' || chunk.embeddedType === 'styled') && !this.codeConfig.graph.embedded.css) {
809
+ continue;
810
+ }
811
+ if (chunk.embeddedType === 'graphql') {
812
+ parseGraphQLContent(chunk.content);
813
+ }
814
+ else {
815
+ parseCSSContent(chunk.content);
816
+ }
817
+ // Keep as EmbeddedChunk (camelCase) for internal use
818
+ embeddedChunks.push(chunk);
819
+ }
820
+ }
821
+ }
822
+ catch {
823
+ // Skip unreadable files.
824
+ }
825
+ }
826
+ return { factsPerFile, fileMetadata, embeddedChunks };
827
+ }
828
+ buildSemanticEdges(semanticFacts, fileId, symbolToFile) {
829
+ const edges = [];
830
+ const push = (edgeType, sourceId, targetId, line) => {
831
+ edges.push({
832
+ id: createEdgeId(sourceId, targetId, edgeType),
833
+ sourceId,
834
+ targetId,
835
+ edgeType,
836
+ sourceFile: fileId,
837
+ targetFile: symbolToFile.get(targetId) ?? null,
838
+ targetModule: null,
839
+ confidence: 'resolved',
840
+ metadata: stringifyMetadata({ line }),
841
+ });
842
+ };
843
+ for (const rel of semanticFacts.refs) {
844
+ push('REFS', rel.sourceSymbol, rel.targetSymbol, rel.line);
845
+ }
846
+ for (const rel of semanticFacts.calls) {
847
+ push('CALLS', rel.sourceSymbol, rel.targetSymbol, rel.line);
848
+ }
849
+ for (const rel of semanticFacts.extends) {
850
+ push('EXTENDS', rel.sourceSymbol, rel.targetSymbol, rel.line);
851
+ }
852
+ for (const rel of semanticFacts.implements) {
853
+ push('IMPLEMENTS', rel.sourceSymbol, rel.targetSymbol, rel.line);
854
+ }
855
+ return edges;
856
+ }
857
+ async buildGraphFromFacts(factsPerFile, chunksByFile, qualityConfig, embeddedChunks, filesToProcess, onProgress) {
858
+ const edges = [];
859
+ const exports = [];
860
+ const modules = new Map();
861
+ const fileGraph = new Map();
862
+ const symbolToFile = new Map();
863
+ for (const [fileId, chunks] of chunksByFile) {
864
+ for (const chunk of chunks) {
865
+ if (chunk.symbolType === 'file')
866
+ continue;
867
+ symbolToFile.set(chunk.id, fileId);
868
+ }
869
+ }
870
+ const resolveOptions = {
871
+ basePath: '',
872
+ rootPath: this.rootPath,
873
+ tsConfig: await this.getTsConfig(),
874
+ nodeModulesPath: path.join(this.rootPath, 'node_modules'),
875
+ respectTSPaths: this.codeConfig.graph.resolution.respectTSPaths,
876
+ workspaceResolver: this.workspaceResolver ?? undefined,
877
+ };
878
+ // Build export map from ALL files (needed for re-export resolution)
879
+ const exportMapBuilder = new ExportMapBuilder(resolveOptions);
880
+ await exportMapBuilder.buildExportMap(factsPerFile);
881
+ // Phase 1: Process definitions and exports (synchronous, no I/O)
882
+ for (const [fileId, facts] of factsPerFile) {
883
+ if (filesToProcess && !filesToProcess.has(fileId))
884
+ continue;
885
+ for (const definition of facts.definitions) {
886
+ edges.push({
887
+ id: createEdgeId(fileId, definition.symbolId, 'DEFINES'),
888
+ sourceId: fileId,
889
+ targetId: definition.symbolId,
890
+ edgeType: 'DEFINES',
891
+ sourceFile: fileId,
892
+ targetFile: fileId,
893
+ targetModule: null,
894
+ confidence: 'resolved',
895
+ metadata: stringifyMetadata({ line: definition.line, kind: definition.kind }),
896
+ });
897
+ }
898
+ for (const exp of facts.exports) {
899
+ edges.push({
900
+ id: createEdgeId(fileId, exp.symbolId, 'EXPORTS'),
901
+ sourceId: fileId,
902
+ targetId: exp.symbolId,
903
+ edgeType: 'EXPORTS',
904
+ sourceFile: fileId,
905
+ targetFile: symbolToFile.get(exp.symbolId) ?? fileId,
906
+ targetModule: null,
907
+ confidence: 'resolved',
908
+ metadata: stringifyMetadata({ name: exp.name, line: exp.line }),
909
+ });
910
+ exports.push({
911
+ id: createExportId(fileId, exp.name),
912
+ fileId,
913
+ exportName: exp.name,
914
+ symbolId: exp.symbolId,
915
+ isReexport: exp.kind === 'reexport',
916
+ originalFile: null,
917
+ });
918
+ }
919
+ }
920
+ const importTasks = [];
921
+ for (const [fileId, facts] of factsPerFile) {
922
+ if (filesToProcess && !filesToProcess.has(fileId))
923
+ continue;
924
+ for (const imp of facts.imports) {
925
+ importTasks.push({ fileId, imp });
926
+ }
927
+ }
928
+ // Resolve imports in parallel with concurrency limit
929
+ onProgress?.(0, importTasks.length, 'resolving');
930
+ const CONCURRENCY = 50;
931
+ const resolvedImports = [];
932
+ for (let i = 0; i < importTasks.length; i += CONCURRENCY) {
933
+ const batch = importTasks.slice(i, i + CONCURRENCY);
934
+ const batchResults = await Promise.all(batch.map(async (task) => ({
935
+ task,
936
+ result: await this.resolveImport(task.imp.spec, task.fileId),
937
+ })));
938
+ resolvedImports.push(...batchResults);
939
+ onProgress?.(Math.min(i + CONCURRENCY, importTasks.length), importTasks.length, 'resolving');
940
+ }
941
+ // Phase 3: Build edges from resolved imports
942
+ const localImportsByFile = new Map();
943
+ for (const { task, result: resolved } of resolvedImports) {
944
+ const { fileId, imp } = task;
945
+ if (!localImportsByFile.has(fileId)) {
946
+ localImportsByFile.set(fileId, new Set());
947
+ }
948
+ const localImports = localImportsByFile.get(fileId);
949
+ if (resolved.type === 'local' && resolved.fileId) {
950
+ localImports.add(resolved.fileId);
951
+ edges.push({
952
+ id: createEdgeId(fileId, resolved.fileId, 'IMPORTS_FILE'),
953
+ sourceId: fileId,
954
+ targetId: resolved.fileId,
955
+ edgeType: 'IMPORTS_FILE',
956
+ sourceFile: fileId,
957
+ targetFile: resolved.fileId,
958
+ targetModule: resolved.module ?? null,
959
+ confidence: 'resolved',
960
+ metadata: stringifyMetadata({ spec: imp.spec, line: imp.line }),
961
+ });
962
+ for (const name of imp.names) {
963
+ const targetSymbol = exportMapBuilder.resolveImport(resolved.fileId, name);
964
+ if (!targetSymbol)
965
+ continue;
966
+ edges.push({
967
+ id: createEdgeId(fileId, targetSymbol, 'IMPORTS_SYMBOL'),
968
+ sourceId: fileId,
969
+ targetId: targetSymbol,
970
+ edgeType: 'IMPORTS_SYMBOL',
971
+ sourceFile: fileId,
972
+ targetFile: resolved.fileId,
973
+ targetModule: null,
974
+ confidence: 'resolved',
975
+ metadata: stringifyMetadata({ name, alias: imp.alias, line: imp.line }),
976
+ });
977
+ }
978
+ continue;
979
+ }
980
+ if (resolved.type === 'workspace') {
981
+ const moduleId = resolved.module ?? imp.spec;
982
+ const workspaceTargetId = resolved.subpath
983
+ ? `WORKSPACE:${moduleId}:${resolved.subpath}`
984
+ : `WORKSPACE:${moduleId}`;
985
+ edges.push({
986
+ id: createEdgeId(fileId, workspaceTargetId, 'IMPORTS_FILE'),
987
+ sourceId: fileId,
988
+ targetId: workspaceTargetId,
989
+ edgeType: 'IMPORTS_FILE',
990
+ sourceFile: fileId,
991
+ targetFile: null,
992
+ targetModule: moduleId,
993
+ confidence: 'resolved',
994
+ metadata: stringifyMetadata({ spec: imp.spec, line: imp.line, subpath: resolved.subpath }),
995
+ });
996
+ if (!modules.has(workspaceTargetId)) {
997
+ modules.set(workspaceTargetId, {
998
+ id: workspaceTargetId,
999
+ type: 'workspace',
1000
+ packageName: moduleId,
1001
+ path: resolved.path ?? null,
1002
+ version: null,
1003
+ exports: '[]',
1004
+ });
1005
+ }
1006
+ continue;
1007
+ }
1008
+ if (resolved.type === 'npm' || resolved.type === 'builtin') {
1009
+ const moduleId = resolved.module ?? imp.spec;
1010
+ edges.push({
1011
+ id: createEdgeId(fileId, moduleId, 'IMPORTS_FILE'),
1012
+ sourceId: fileId,
1013
+ targetId: moduleId,
1014
+ edgeType: 'IMPORTS_FILE',
1015
+ sourceFile: fileId,
1016
+ targetFile: null,
1017
+ targetModule: moduleId,
1018
+ confidence: 'resolved',
1019
+ metadata: stringifyMetadata({ spec: imp.spec, line: imp.line, subpath: resolved.subpath }),
1020
+ });
1021
+ if (!modules.has(moduleId)) {
1022
+ modules.set(moduleId, {
1023
+ id: moduleId,
1024
+ type: resolved.type === 'builtin' ? 'builtin' : 'npm',
1025
+ packageName: resolved.type === 'npm' ? moduleId : null,
1026
+ path: null,
1027
+ version: null,
1028
+ exports: '[]',
1029
+ });
1030
+ }
1031
+ continue;
1032
+ }
1033
+ edges.push({
1034
+ id: createEdgeId(fileId, `UNRESOLVED:${imp.spec}`, 'IMPORTS_FILE'),
1035
+ sourceId: fileId,
1036
+ targetId: `UNRESOLVED:${imp.spec}`,
1037
+ edgeType: 'IMPORTS_FILE',
1038
+ sourceFile: fileId,
1039
+ targetFile: null,
1040
+ targetModule: null,
1041
+ confidence: 'unresolved',
1042
+ metadata: stringifyMetadata({ spec: imp.spec, line: imp.line, error: resolved.error }),
1043
+ });
1044
+ }
1045
+ // Build fileGraph from collected local imports
1046
+ for (const [fileId, localImports] of localImportsByFile) {
1047
+ fileGraph.set(fileId, [...localImports]);
1048
+ }
1049
+ // Filter embedded chunks if processing specific files (using camelCase properties)
1050
+ const filteredEmbedded = filesToProcess
1051
+ ? embeddedChunks.filter(e => filesToProcess.has(e.hostFileId))
1052
+ : embeddedChunks;
1053
+ for (const embedded of filteredEmbedded) {
1054
+ const hostSymbol = embedded.hostSymbolId ?? embedded.hostFileId;
1055
+ edges.push({
1056
+ id: createEdgeId(hostSymbol, embedded.id, 'EMBEDS'),
1057
+ sourceId: hostSymbol,
1058
+ targetId: embedded.id,
1059
+ edgeType: 'EMBEDS',
1060
+ sourceFile: embedded.hostFileId,
1061
+ targetFile: embedded.hostFileId,
1062
+ targetModule: null,
1063
+ confidence: 'resolved',
1064
+ metadata: stringifyMetadata({ embeddedType: embedded.embeddedType, line: embedded.startLine }),
1065
+ });
1066
+ }
1067
+ const fragmentToNodeId = new Map();
1068
+ for (const embedded of filteredEmbedded) {
1069
+ if (embedded.embeddedType !== 'graphql')
1070
+ continue;
1071
+ const gqlFacts = parseGraphQLContent(embedded.content);
1072
+ if (!gqlFacts)
1073
+ continue;
1074
+ for (const fragment of gqlFacts.fragments) {
1075
+ fragmentToNodeId.set(fragment.name, `${embedded.id}#fragment:${fragment.name}`);
1076
+ }
1077
+ }
1078
+ for (const embedded of filteredEmbedded) {
1079
+ if (embedded.embeddedType !== 'graphql')
1080
+ continue;
1081
+ const gqlFacts = parseGraphQLContent(embedded.content);
1082
+ if (!gqlFacts)
1083
+ continue;
1084
+ for (const operation of gqlFacts.operations) {
1085
+ const operationId = `${embedded.id}#operation:${operation.name ?? 'anonymous'}`;
1086
+ for (const usedFragment of gqlFacts.usedFragments) {
1087
+ const fragmentId = fragmentToNodeId.get(usedFragment);
1088
+ if (!fragmentId)
1089
+ continue;
1090
+ edges.push({
1091
+ id: createEdgeId(operationId, fragmentId, 'USES_FRAGMENT'),
1092
+ sourceId: operationId,
1093
+ targetId: fragmentId,
1094
+ edgeType: 'USES_FRAGMENT',
1095
+ sourceFile: embedded.hostFileId,
1096
+ targetFile: embedded.hostFileId,
1097
+ targetModule: null,
1098
+ confidence: 'resolved',
1099
+ metadata: stringifyMetadata({ fragment: usedFragment }),
1100
+ });
1101
+ }
1102
+ }
1103
+ }
1104
+ if (qualityConfig.useTypeChecker && this.codeConfig.graph.semantic.enabled) {
1105
+ const ts = await loadTypeScript();
1106
+ if (!ts && this.codeConfig.graph.quality === 'accurate') {
1107
+ throw new Error('Quality mode "accurate" requires TypeScript. Install it: npm install typescript');
1108
+ }
1109
+ if (ts) {
1110
+ // On-demand parsing: collect all resolved local files from import resolution
1111
+ // and parse any that aren't already in chunksByFile
1112
+ const resolvedLocalFiles = new Set();
1113
+ for (const { result } of resolvedImports) {
1114
+ if (result.type === 'local' && result.fileId) {
1115
+ resolvedLocalFiles.add(result.fileId);
1116
+ }
1117
+ }
1118
+ // Also include files from reexport chains (e.g., index.ts -> embedding/factory.ts)
1119
+ const reexportFiles = exportMapBuilder.getResolvedLocalFiles();
1120
+ for (const fileId of reexportFiles) {
1121
+ resolvedLocalFiles.add(fileId);
1122
+ }
1123
+ // Parse files discovered through imports (outside original include patterns)
1124
+ await this.ensureFilesParsedForGraph([...resolvedLocalFiles], chunksByFile, symbolToFile);
1125
+ // Filter files for semantic analysis - include both indexed and on-demand parsed files
1126
+ const allFiles = [...chunksByFile.keys()].filter((fileId) => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(fileId));
1127
+ const files = filesToProcess
1128
+ ? allFiles.filter(f => filesToProcess.has(f))
1129
+ : allFiles;
1130
+ // Use shared TypeScript Program for all files (much faster than per-file analysis)
1131
+ onProgress?.(0, files.length, 'semantic');
1132
+ const allSemanticFacts = await analyzeAllFilesSemantics({
1133
+ fileIds: files,
1134
+ rootPath: this.rootPath,
1135
+ chunksByFile,
1136
+ onProgress: (current, total) => {
1137
+ onProgress?.(current, total, 'semantic');
1138
+ },
1139
+ });
1140
+ let semanticProcessed = 0;
1141
+ for (const [fileId, semanticFacts] of allSemanticFacts) {
1142
+ edges.push(...this.buildSemanticEdges(semanticFacts, fileId, symbolToFile));
1143
+ semanticProcessed++;
1144
+ }
1145
+ onProgress?.(files.length, files.length, 'semantic');
1146
+ }
1147
+ }
1148
+ // Phase: Building final graph structure
1149
+ onProgress?.(0, 1, 'building');
1150
+ const dedupedEdges = new Map();
1151
+ for (const edge of edges) {
1152
+ dedupedEdges.set(edge.id, edge);
1153
+ }
1154
+ onProgress?.(1, 1, 'building');
1155
+ return {
1156
+ edges: [...dedupedEdges.values()],
1157
+ modules: [...modules.values()],
1158
+ exports,
1159
+ };
1160
+ }
1161
+ buildEmbeddingRecords(files) {
1162
+ const records = [];
1163
+ for (const file of files) {
1164
+ const imports = [...new Set(file.facts.imports.map((imp) => imp.spec).filter(Boolean))];
1165
+ for (const chunk of file.chunks) {
1166
+ if (chunk.symbolType === 'file')
1167
+ continue;
1168
+ records.push({
1169
+ chunkId: chunk.id,
1170
+ fileId: file.fileId,
1171
+ symbolName: chunk.symbolName,
1172
+ symbolType: chunk.symbolType,
1173
+ startLine: chunk.startLine,
1174
+ endLine: chunk.endLine,
1175
+ imports,
1176
+ embeddingText: buildNodeEmbeddingText(chunk, file.facts),
1177
+ });
1178
+ }
1179
+ }
1180
+ return records.sort((a, b) => a.fileId.localeCompare(b.fileId) || a.startLine - b.startLine);
1181
+ }
1182
+ async prepareGraph(quality = this.codeConfig.graph.quality, preParsedChunksByFile, onProgress) {
1183
+ await this.initialize();
1184
+ this.moduleResolveCache.clear();
1185
+ clearResolverCache();
1186
+ const qualityConfig = QUALITY_PRESETS[quality] ?? QUALITY_PRESETS.balanced;
1187
+ // Phase 1: Loading chunks
1188
+ onProgress?.(0, 1, 'loading');
1189
+ const chunksByFile = preParsedChunksByFile
1190
+ ? new Map(preParsedChunksByFile)
1191
+ : await this.loadChunksByFile();
1192
+ const files = preParsedChunksByFile
1193
+ ? [...new Set(chunksByFile.keys())]
1194
+ : await this.listCodeFiles();
1195
+ onProgress?.(1, 1, 'loading');
1196
+ // Phase 2: Parsing and extracting facts
1197
+ onProgress?.(0, files.length, 'parsing');
1198
+ const { factsPerFile, fileMetadata, embeddedChunks } = await this.buildFacts(files, chunksByFile, qualityConfig.embedEmbedded && (this.codeConfig.graph.embedded.css || this.codeConfig.graph.embedded.graphql), onProgress);
1199
+ onProgress?.(files.length, files.length, 'parsing');
1200
+ // Phase 3-5: Building graph (resolving, semantic, building)
1201
+ const graph = await this.buildGraphFromFacts(factsPerFile, chunksByFile, qualityConfig, embeddedChunks, undefined, onProgress);
1202
+ const embeddedByFile = new Map();
1203
+ for (const chunk of embeddedChunks) {
1204
+ // Use camelCase property from EmbeddedChunk
1205
+ const arr = embeddedByFile.get(chunk.hostFileId) ?? [];
1206
+ arr.push(chunk);
1207
+ embeddedByFile.set(chunk.hostFileId, arr);
1208
+ }
1209
+ const preparedFiles = [];
1210
+ const orderedFiles = [...new Set(files)].sort((a, b) => a.localeCompare(b));
1211
+ for (const fileId of orderedFiles) {
1212
+ const facts = factsPerFile.get(fileId);
1213
+ const metadata = fileMetadata.get(fileId);
1214
+ if (!facts || !metadata)
1215
+ continue;
1216
+ const chunks = chunksByFile.get(fileId) ?? [];
1217
+ preparedFiles.push({
1218
+ fileId,
1219
+ language: chunks[0]?.language ?? null,
1220
+ contentHash: metadata.contentHash,
1221
+ mtime: metadata.mtime,
1222
+ chunks,
1223
+ facts,
1224
+ embedded: embeddedByFile.get(fileId) ?? [],
1225
+ });
1226
+ }
1227
+ const symbols = preparedFiles.reduce((acc, file) => acc + file.chunks.length, 0);
1228
+ const stats = buildStatsFromPrepared(preparedFiles.length, symbols, graph.edges, embeddedChunks);
1229
+ return {
1230
+ quality,
1231
+ files: preparedFiles,
1232
+ edges: graph.edges,
1233
+ modules: graph.modules,
1234
+ exports: graph.exports,
1235
+ embeddingRecords: this.buildEmbeddingRecords(preparedFiles),
1236
+ stats,
1237
+ parseErrors: [],
1238
+ };
1239
+ }
1240
+ async indexAll(quality = this.codeConfig.graph.quality, options = {}) {
1241
+ const { onProgress } = options;
1242
+ // Phase 1: Preparing graph (parsing, building facts, resolving, semantic)
1243
+ const prepared = await this.prepareGraph(quality, undefined, onProgress);
1244
+ this.reverseIndex.buildFromEdges(prepared.edges);
1245
+ // Phase 2: Indexing tables (5 tables total)
1246
+ const totalTables = 5;
1247
+ let tablesIndexed = 0;
1248
+ // Use snake_case column names for all LanceDB tables
1249
+ this.edgesTable = await this.replaceTable('code_edges', {
1250
+ id: '__init__',
1251
+ source_id: '',
1252
+ target_id: '',
1253
+ edge_type: 'IMPORTS_FILE',
1254
+ source_file: '',
1255
+ target_file: '',
1256
+ target_module: '',
1257
+ confidence: 'resolved',
1258
+ metadata: '',
1259
+ }, prepared.edges.map(toEdgeRecord));
1260
+ onProgress?.(++tablesIndexed, totalTables, 'indexing');
1261
+ this.modulesTable = await this.replaceTable('code_modules', {
1262
+ id: '__init__',
1263
+ type: 'npm',
1264
+ package_name: '',
1265
+ path: '',
1266
+ version: '',
1267
+ exports: '[]',
1268
+ }, prepared.modules.map(toModuleRecord));
1269
+ onProgress?.(++tablesIndexed, totalTables, 'indexing');
1270
+ this.exportsTable = await this.replaceTable('code_exports', {
1271
+ id: '__init__',
1272
+ file_id: '',
1273
+ export_name: '',
1274
+ symbol_id: '',
1275
+ is_reexport: false,
1276
+ original_file: '',
1277
+ }, prepared.exports.map(toExportRecord));
1278
+ onProgress?.(++tablesIndexed, totalTables, 'indexing');
1279
+ // Convert EmbeddedChunk (camelCase) to EmbeddedRecord (snake_case) for LanceDB
1280
+ const embeddedRecords = prepared.files.flatMap((file) => file.embedded).map(toEmbeddedRecord);
1281
+ this.embeddedTable = await this.replaceTable('code_embedded', {
1282
+ id: '__init__',
1283
+ host_file_id: '',
1284
+ host_symbol_id: '',
1285
+ embedded_type: 'graphql',
1286
+ content: '',
1287
+ start_offset: 0,
1288
+ end_offset: 0,
1289
+ start_line: 0,
1290
+ end_line: 0,
1291
+ }, embeddedRecords);
1292
+ onProgress?.(++tablesIndexed, totalTables, 'indexing');
1293
+ const graphFileRows = prepared.files.map((file) => ({
1294
+ path: file.fileId,
1295
+ content_hash: file.contentHash,
1296
+ indexed_at: file.mtime,
1297
+ }));
1298
+ this.graphFileTable = await this.replaceTable('code_graph_file_index', {
1299
+ path: '__init__',
1300
+ content_hash: '',
1301
+ indexed_at: 0,
1302
+ }, graphFileRows);
1303
+ onProgress?.(++tablesIndexed, totalTables, 'indexing');
1304
+ return prepared.stats;
1305
+ }
1306
+ async incrementalUpdate() {
1307
+ await this.initialize();
1308
+ this.moduleResolveCache.clear();
1309
+ clearResolverCache();
1310
+ const files = await this.listCodeFiles();
1311
+ const current = await this.loadFileMetadata(files);
1312
+ const previous = await this.loadPreviousGraphFileIndex();
1313
+ const existingEdges = await this.getAllEdges();
1314
+ this.reverseIndex.buildFromEdges(existingEdges);
1315
+ const changes = await detectChanges(current, previous, this.reverseIndex);
1316
+ if (changes.modified.length === 0 && changes.deleted.length === 0 && changes.affected.length === 0) {
1317
+ return { updated: 0, deleted: 0, affected: 0 };
1318
+ }
1319
+ // Calculate total files to update
1320
+ const filesToUpdate = new Set([...changes.modified, ...changes.affected]);
1321
+ const totalFiles = files.length;
1322
+ // If more than 30% of files changed, do full reindex (more efficient)
1323
+ if (filesToUpdate.size > totalFiles * 0.3) {
1324
+ await this.indexAll();
1325
+ return {
1326
+ updated: changes.modified.length,
1327
+ deleted: changes.deleted.length,
1328
+ affected: changes.affected.length,
1329
+ };
1330
+ }
1331
+ // Incremental update: only process changed files
1332
+ const qualityConfig = QUALITY_PRESETS[this.codeConfig.graph.quality] ?? QUALITY_PRESETS.balanced;
1333
+ // Step 1: Delete old data for files that will be updated or deleted
1334
+ const filesToDelete = new Set([...filesToUpdate, ...changes.deleted]);
1335
+ for (const fileId of filesToDelete) {
1336
+ await this.deleteByFile(fileId);
1337
+ }
1338
+ // Step 2: Load existing chunks for ALL files (needed for symbolToFile mapping)
1339
+ const chunksByFile = await this.loadChunksByFile();
1340
+ // Step 3: Build facts for ALL files (needed for ExportMapBuilder to resolve re-exports)
1341
+ // This is fast as it doesn't involve embeddings
1342
+ const { factsPerFile, fileMetadata, embeddedChunks } = await this.buildFacts(files, chunksByFile, qualityConfig.embedEmbedded && (this.codeConfig.graph.embedded.css || this.codeConfig.graph.embedded.graphql));
1343
+ // Step 4: Build graph only for modified + affected files
1344
+ const graph = await this.buildGraphFromFacts(factsPerFile, chunksByFile, qualityConfig, embeddedChunks, filesToUpdate // Only process these files
1345
+ );
1346
+ // Step 5: Add new edges, exports, modules, embedded
1347
+ await this.addEdges(graph.edges);
1348
+ await this.addExports(graph.exports);
1349
+ await this.addModules(graph.modules);
1350
+ // Filter embedded for updated files only (using camelCase property)
1351
+ const embeddedForUpdatedFiles = embeddedChunks.filter(e => filesToUpdate.has(e.hostFileId));
1352
+ await this.addEmbedded(embeddedForUpdatedFiles);
1353
+ // Step 6: Update graph file index for modified + affected files (snake_case columns)
1354
+ const graphFileRecords = [];
1355
+ for (const fileId of filesToUpdate) {
1356
+ const metadata = fileMetadata.get(fileId);
1357
+ if (metadata) {
1358
+ graphFileRecords.push({
1359
+ path: fileId,
1360
+ content_hash: metadata.contentHash,
1361
+ indexed_at: metadata.mtime,
1362
+ });
1363
+ }
1364
+ }
1365
+ await this.updateGraphFileRecords(graphFileRecords);
1366
+ // Step 7: Update reverse index with new edges
1367
+ this.reverseIndex.buildFromEdges(await this.getAllEdges());
1368
+ return {
1369
+ updated: changes.modified.length,
1370
+ deleted: changes.deleted.length,
1371
+ affected: changes.affected.length,
1372
+ };
1373
+ }
1374
+ async updateFiles(changedFiles) {
1375
+ if (changedFiles.length === 0) {
1376
+ return { updated: 0, deleted: 0, affected: 0 };
1377
+ }
1378
+ await this.initialize();
1379
+ await this.ensureAllTables();
1380
+ this.moduleResolveCache.clear();
1381
+ clearResolverCache();
1382
+ // Build reverse index from existing edges to find affected files
1383
+ const existingEdges = await this.getAllEdges();
1384
+ this.reverseIndex.buildFromEdges(existingEdges);
1385
+ // Find affected files (importers of changed files)
1386
+ const affectedSet = new Set();
1387
+ const changedSet = new Set(changedFiles.map(f => normalizePath(f)));
1388
+ for (const fileId of changedSet) {
1389
+ const transitiveImporters = this.reverseIndex.getTransitiveImporters(fileId, 2);
1390
+ for (const importer of transitiveImporters) {
1391
+ if (!changedSet.has(importer)) {
1392
+ affectedSet.add(importer);
1393
+ }
1394
+ }
1395
+ }
1396
+ const filesToUpdate = new Set([...changedSet, ...affectedSet]);
1397
+ const allFiles = await this.listCodeFiles();
1398
+ // If more than 30% of files affected, do full reindex
1399
+ if (filesToUpdate.size > allFiles.length * 0.3) {
1400
+ await this.indexAll();
1401
+ return { updated: changedFiles.length, deleted: 0, affected: affectedSet.size };
1402
+ }
1403
+ // Incremental update
1404
+ const qualityConfig = QUALITY_PRESETS[this.codeConfig.graph.quality] ?? QUALITY_PRESETS.balanced;
1405
+ // Delete old data for files to update
1406
+ for (const fileId of filesToUpdate) {
1407
+ await this.deleteByFile(fileId);
1408
+ }
1409
+ // Load chunks and build facts
1410
+ const chunksByFile = await this.loadChunksByFile();
1411
+ const { factsPerFile, fileMetadata, embeddedChunks } = await this.buildFacts(allFiles, chunksByFile, qualityConfig.embedEmbedded && (this.codeConfig.graph.embedded.css || this.codeConfig.graph.embedded.graphql));
1412
+ // Build graph only for updated files
1413
+ const graph = await this.buildGraphFromFacts(factsPerFile, chunksByFile, qualityConfig, embeddedChunks, filesToUpdate);
1414
+ // Add new data
1415
+ await this.addEdges(graph.edges);
1416
+ await this.addExports(graph.exports);
1417
+ await this.addModules(graph.modules);
1418
+ // Filter embedded for updated files (using camelCase property)
1419
+ const embeddedForUpdatedFiles = embeddedChunks.filter(e => filesToUpdate.has(e.hostFileId));
1420
+ await this.addEmbedded(embeddedForUpdatedFiles);
1421
+ // Update file index (snake_case columns)
1422
+ const graphFileRecords = [];
1423
+ for (const fileId of filesToUpdate) {
1424
+ const metadata = fileMetadata.get(fileId);
1425
+ if (metadata) {
1426
+ graphFileRecords.push({
1427
+ path: fileId,
1428
+ content_hash: metadata.contentHash,
1429
+ indexed_at: metadata.mtime,
1430
+ });
1431
+ }
1432
+ }
1433
+ await this.updateGraphFileRecords(graphFileRecords);
1434
+ // Update reverse index
1435
+ this.reverseIndex.buildFromEdges(await this.getAllEdges());
1436
+ return { updated: changedFiles.length, deleted: 0, affected: affectedSet.size };
1437
+ }
1438
+ async removeFile(filePath) {
1439
+ await this.initialize();
1440
+ const normalized = normalizePath(filePath);
1441
+ await this.deleteByFile(normalized);
1442
+ }
1443
+ async deleteByFile(fileId) {
1444
+ if (!this.edgesTable) {
1445
+ try {
1446
+ this.edgesTable = await this.db.openTable('code_edges');
1447
+ }
1448
+ catch {
1449
+ return;
1450
+ }
1451
+ }
1452
+ // Use snake_case column names for LanceDB SQL queries
1453
+ await this.edgesTable.delete(`source_file = '${fileId.replace(/'/g, "''")}'`);
1454
+ await this.edgesTable.delete(`target_file = '${fileId.replace(/'/g, "''")}'`);
1455
+ if (this.exportsTable) {
1456
+ await this.exportsTable.delete(`file_id = '${fileId.replace(/'/g, "''")}'`);
1457
+ }
1458
+ if (this.embeddedTable) {
1459
+ await this.embeddedTable.delete(`host_file_id = '${fileId.replace(/'/g, "''")}'`);
1460
+ }
1461
+ if (this.graphFileTable) {
1462
+ await this.graphFileTable.delete(`path = '${fileId.replace(/'/g, "''")}'`);
1463
+ }
1464
+ }
1465
+ async clear() {
1466
+ await this.initialize();
1467
+ const tables = await this.db.tableNames();
1468
+ for (const name of ['code_edges', 'code_modules', 'code_exports', 'code_embedded', 'code_graph_file_index']) {
1469
+ if (tables.includes(name)) {
1470
+ await this.db.dropTable(name);
1471
+ }
1472
+ }
1473
+ this.chunksTable = null;
1474
+ this.edgesTable = null;
1475
+ this.modulesTable = null;
1476
+ this.exportsTable = null;
1477
+ this.embeddedTable = null;
1478
+ this.graphFileTable = null;
1479
+ this.reverseIndex = new ReverseIndexBuilder();
1480
+ }
1481
+ async getAllEdges() {
1482
+ await this.initialize();
1483
+ if (!this.edgesTable) {
1484
+ try {
1485
+ this.edgesTable = await this.db.openTable('code_edges');
1486
+ }
1487
+ catch {
1488
+ return [];
1489
+ }
1490
+ }
1491
+ const rows = await this.edgesTable.query().toArray();
1492
+ // Convert from snake_case LanceDB records to camelCase CodeEdge
1493
+ return rows.map(fromEdgeRecord);
1494
+ }
1495
+ async getChunkById(chunkId) {
1496
+ const table = await this.ensureChunksTable();
1497
+ const rows = await table.query().where(`id = '${chunkId.replace(/'/g, "''")}'`).toArray();
1498
+ const row = rows[0];
1499
+ if (!row)
1500
+ return null;
1501
+ // Read from snake_case columns
1502
+ return {
1503
+ id: row.id,
1504
+ path: row.path,
1505
+ language: row.language,
1506
+ symbolType: row.symbol_type,
1507
+ symbolName: row.symbol_name,
1508
+ signature: row.signature,
1509
+ parentSymbol: row.parent_symbol,
1510
+ scope: JSON.parse(row.scope || '[]'),
1511
+ content: row.content,
1512
+ startLine: row.start_line,
1513
+ endLine: row.end_line,
1514
+ docstring: row.docstring,
1515
+ modified: row.modified,
1516
+ contentHash: row.content_hash,
1517
+ };
1518
+ }
1519
+ async findSymbolChunk(symbolId, symbolName) {
1520
+ if (symbolId) {
1521
+ return this.getChunkById(symbolId);
1522
+ }
1523
+ if (!symbolName)
1524
+ return null;
1525
+ const table = await this.ensureChunksTable();
1526
+ // Use snake_case column name in SQL query
1527
+ const rows = await table
1528
+ .query()
1529
+ .where(`symbol_name = '${symbolName.replace(/'/g, "''")}'`)
1530
+ .limit(1)
1531
+ .toArray();
1532
+ if (rows.length === 0)
1533
+ return null;
1534
+ const row = rows[0];
1535
+ // Read from snake_case columns
1536
+ return {
1537
+ id: row.id,
1538
+ path: row.path,
1539
+ language: row.language,
1540
+ symbolType: row.symbol_type,
1541
+ symbolName: row.symbol_name,
1542
+ signature: row.signature,
1543
+ parentSymbol: row.parent_symbol,
1544
+ scope: JSON.parse(row.scope || '[]'),
1545
+ content: row.content,
1546
+ startLine: row.start_line,
1547
+ endLine: row.end_line,
1548
+ docstring: row.docstring,
1549
+ modified: row.modified,
1550
+ contentHash: row.content_hash,
1551
+ };
1552
+ }
1553
+ async readContextLine(fileId, line) {
1554
+ try {
1555
+ const content = await fs.readFile(path.resolve(this.rootPath, fileId), 'utf-8');
1556
+ const lines = content.split('\n');
1557
+ return (lines[line - 1] ?? '').trim();
1558
+ }
1559
+ catch {
1560
+ return '';
1561
+ }
1562
+ }
1563
+ async graphRefs(input) {
1564
+ await this.initialize();
1565
+ const symbol = await this.findSymbolChunk(input.symbolId, input.symbolName);
1566
+ if (!symbol) {
1567
+ return createError('GRAPH_SYMBOL_NOT_FOUND', 'Symbol not found');
1568
+ }
1569
+ const includeImports = input.includeImports ?? true;
1570
+ const limit = input.limit ?? 50;
1571
+ const edges = await this.getAllEdges();
1572
+ const allowed = new Map([
1573
+ ['REFS', 'ref'],
1574
+ ['CALLS', 'call'],
1575
+ ['EXTENDS', 'extend'],
1576
+ ['IMPLEMENTS', 'implement'],
1577
+ ['IMPORTS_SYMBOL', 'import'],
1578
+ ]);
1579
+ const references = [];
1580
+ for (const edge of edges) {
1581
+ if (edge.targetId !== symbol.id)
1582
+ continue;
1583
+ if (!allowed.has(edge.edgeType))
1584
+ continue;
1585
+ if (!includeImports && edge.edgeType === 'IMPORTS_SYMBOL')
1586
+ continue;
1587
+ if (input.file && edge.sourceFile !== input.file)
1588
+ continue;
1589
+ const meta = parseMetadata(edge.metadata);
1590
+ const line = Number(meta.line ?? 1);
1591
+ const context = await this.readContextLine(edge.sourceFile, line);
1592
+ references.push({
1593
+ file: edge.sourceFile,
1594
+ line,
1595
+ context,
1596
+ type: allowed.get(edge.edgeType),
1597
+ });
1598
+ }
1599
+ references.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
1600
+ const limited = references.slice(0, limit);
1601
+ return {
1602
+ symbol,
1603
+ references: limited,
1604
+ totalCount: references.length,
1605
+ };
1606
+ }
1607
+ async buildCallNodes(rootId, direction, depth, limit) {
1608
+ const edges = (await this.getAllEdges()).filter((edge) => edge.edgeType === 'CALLS');
1609
+ const queue = [{ id: rootId, depth: 0 }];
1610
+ const visited = new Set([rootId]);
1611
+ const nodes = [];
1612
+ while (queue.length > 0 && nodes.length < limit) {
1613
+ const current = queue.shift();
1614
+ if (!current)
1615
+ break;
1616
+ if (current.depth >= depth)
1617
+ continue;
1618
+ const relevant = edges.filter((edge) => direction === 'callers'
1619
+ ? edge.targetId === current.id
1620
+ : edge.sourceId === current.id);
1621
+ for (const edge of relevant) {
1622
+ const nextId = direction === 'callers' ? edge.sourceId : edge.targetId;
1623
+ if (visited.has(nextId))
1624
+ continue;
1625
+ visited.add(nextId);
1626
+ const chunk = await this.getChunkById(nextId);
1627
+ if (!chunk)
1628
+ continue;
1629
+ const meta = parseMetadata(edge.metadata);
1630
+ nodes.push({
1631
+ symbol: chunk,
1632
+ depth: current.depth + 1,
1633
+ callSites: [{
1634
+ file: edge.sourceFile,
1635
+ line: Number(meta.line ?? 1),
1636
+ }],
1637
+ children: [],
1638
+ });
1639
+ queue.push({ id: nextId, depth: current.depth + 1 });
1640
+ }
1641
+ }
1642
+ return nodes;
1643
+ }
1644
+ async graphCalls(input) {
1645
+ await this.initialize();
1646
+ const symbol = await this.findSymbolChunk(input.symbolId, input.symbolName);
1647
+ if (!symbol) {
1648
+ return createError('GRAPH_SYMBOL_NOT_FOUND', 'Symbol not found');
1649
+ }
1650
+ const depth = input.depth ?? 2;
1651
+ const limit = input.limit ?? 50;
1652
+ const direction = input.direction;
1653
+ const callers = direction === 'callees'
1654
+ ? []
1655
+ : await this.buildCallNodes(symbol.id, 'callers', depth, limit);
1656
+ const callees = direction === 'callers'
1657
+ ? []
1658
+ : await this.buildCallNodes(symbol.id, 'callees', depth, limit);
1659
+ return {
1660
+ root: symbol,
1661
+ callers,
1662
+ callees,
1663
+ };
1664
+ }
1665
+ async dependencySymbols(sourceFile, targetFile) {
1666
+ const edges = await this.getAllEdges();
1667
+ return edges
1668
+ .filter((edge) => edge.edgeType === 'IMPORTS_SYMBOL' &&
1669
+ edge.sourceFile === sourceFile &&
1670
+ edge.targetFile === targetFile)
1671
+ .map((edge) => {
1672
+ const meta = parseMetadata(edge.metadata);
1673
+ return String(meta.name ?? meta.alias ?? edge.targetId);
1674
+ })
1675
+ .filter(Boolean);
1676
+ }
1677
+ async traverseImports(fileId, includeExternal, maxDepth) {
1678
+ const edges = (await this.getAllEdges()).filter((edge) => edge.edgeType === 'IMPORTS_FILE');
1679
+ const queue = [{ file: fileId, depth: 0 }];
1680
+ const visited = new Set([fileId]);
1681
+ const result = [];
1682
+ while (queue.length > 0) {
1683
+ const current = queue.shift();
1684
+ if (!current)
1685
+ break;
1686
+ if (current.depth >= maxDepth)
1687
+ continue;
1688
+ const outgoing = edges.filter((edge) => edge.sourceFile === current.file);
1689
+ for (const edge of outgoing) {
1690
+ const nextFile = edge.targetFile ?? edge.targetModule ?? edge.targetId;
1691
+ if (!includeExternal && !edge.targetFile)
1692
+ continue;
1693
+ if (!nextFile || visited.has(nextFile))
1694
+ continue;
1695
+ visited.add(nextFile);
1696
+ const symbols = edge.targetFile
1697
+ ? await this.dependencySymbols(current.file, edge.targetFile)
1698
+ : [];
1699
+ result.push({
1700
+ file: nextFile,
1701
+ type: edge.targetFile ? 'local' : edge.targetModule && edge.targetModule.startsWith('node:') ? 'builtin' : 'npm',
1702
+ symbols,
1703
+ depth: current.depth + 1,
1704
+ children: [],
1705
+ });
1706
+ if (edge.targetFile) {
1707
+ queue.push({ file: edge.targetFile, depth: current.depth + 1 });
1708
+ }
1709
+ }
1710
+ }
1711
+ return result;
1712
+ }
1713
+ async traverseImporters(fileId, maxDepth) {
1714
+ const edges = (await this.getAllEdges()).filter((edge) => edge.edgeType === 'IMPORTS_FILE');
1715
+ const queue = [{ file: fileId, depth: 0 }];
1716
+ const visited = new Set([fileId]);
1717
+ const result = [];
1718
+ while (queue.length > 0) {
1719
+ const current = queue.shift();
1720
+ if (!current)
1721
+ break;
1722
+ if (current.depth >= maxDepth)
1723
+ continue;
1724
+ const incoming = edges.filter((edge) => edge.targetFile === current.file);
1725
+ for (const edge of incoming) {
1726
+ if (visited.has(edge.sourceFile))
1727
+ continue;
1728
+ visited.add(edge.sourceFile);
1729
+ result.push({
1730
+ file: edge.sourceFile,
1731
+ type: 'local',
1732
+ symbols: [],
1733
+ depth: current.depth + 1,
1734
+ children: [],
1735
+ });
1736
+ queue.push({ file: edge.sourceFile, depth: current.depth + 1 });
1737
+ }
1738
+ }
1739
+ return result;
1740
+ }
1741
+ async graphDeps(input) {
1742
+ await this.initialize();
1743
+ const depth = input.depth ?? 1;
1744
+ const includeExternal = input.includeExternal ?? false;
1745
+ // Normalize file path to match stored paths in database
1746
+ const fileId = normalizePath(input.file);
1747
+ const imports = input.direction === 'importedBy'
1748
+ ? []
1749
+ : await this.traverseImports(fileId, includeExternal, depth);
1750
+ const importedBy = input.direction === 'imports'
1751
+ ? []
1752
+ : await this.traverseImporters(fileId, depth);
1753
+ return {
1754
+ file: fileId,
1755
+ imports,
1756
+ importedBy,
1757
+ };
1758
+ }
1759
+ async collectHierarchyIds(rootId, edgeType, incoming) {
1760
+ const edges = (await this.getAllEdges()).filter((edge) => edge.edgeType === edgeType);
1761
+ const queue = [rootId];
1762
+ const result = new Set();
1763
+ while (queue.length > 0) {
1764
+ const current = queue.shift();
1765
+ if (!current)
1766
+ break;
1767
+ const matches = edges.filter((edge) => incoming ? edge.targetId === current : edge.sourceId === current);
1768
+ for (const edge of matches) {
1769
+ const next = incoming ? edge.sourceId : edge.targetId;
1770
+ if (next === rootId || result.has(next))
1771
+ continue;
1772
+ result.add(next);
1773
+ queue.push(next);
1774
+ }
1775
+ }
1776
+ return [...result];
1777
+ }
1778
+ async getChunksByIds(ids) {
1779
+ const chunks = [];
1780
+ for (const id of ids) {
1781
+ const chunk = await this.getChunkById(id);
1782
+ if (chunk)
1783
+ chunks.push(chunk);
1784
+ }
1785
+ return chunks;
1786
+ }
1787
+ async graphHierarchy(input) {
1788
+ await this.initialize();
1789
+ const symbol = await this.findSymbolChunk(input.symbolId, input.symbolName);
1790
+ if (!symbol) {
1791
+ return createError('GRAPH_SYMBOL_NOT_FOUND', 'Symbol not found');
1792
+ }
1793
+ const extendsIds = await this.collectHierarchyIds(symbol.id, 'EXTENDS', false);
1794
+ const implementsIds = await this.collectHierarchyIds(symbol.id, 'IMPLEMENTS', false);
1795
+ const extendedByIds = await this.collectHierarchyIds(symbol.id, 'EXTENDS', true);
1796
+ const implementedByIds = await this.collectHierarchyIds(symbol.id, 'IMPLEMENTS', true);
1797
+ return {
1798
+ symbol,
1799
+ extends: await this.getChunksByIds(extendsIds),
1800
+ implements: await this.getChunksByIds(implementsIds),
1801
+ extendedBy: await this.getChunksByIds(extendedByIds),
1802
+ implementedBy: await this.getChunksByIds(implementedByIds),
1803
+ };
1804
+ }
1805
+ async getStats() {
1806
+ await this.initialize();
1807
+ const edges = await this.getAllEdges();
1808
+ const edgeTypes = edgeTypeCount();
1809
+ for (const edge of edges) {
1810
+ edgeTypes[edge.edgeType] += 1;
1811
+ }
1812
+ const chunksTable = await this.ensureChunksTable();
1813
+ const chunksCount = await chunksTable.countRows();
1814
+ let graphFiles = 0;
1815
+ if (this.graphFileTable) {
1816
+ graphFiles = await this.graphFileTable.countRows();
1817
+ }
1818
+ else {
1819
+ try {
1820
+ this.graphFileTable = await this.db.openTable('code_graph_file_index');
1821
+ graphFiles = await this.graphFileTable.countRows();
1822
+ }
1823
+ catch {
1824
+ graphFiles = 0;
1825
+ }
1826
+ }
1827
+ let embeddedRows = [];
1828
+ if (!this.embeddedTable) {
1829
+ try {
1830
+ this.embeddedTable = await this.db.openTable('code_embedded');
1831
+ }
1832
+ catch {
1833
+ this.embeddedTable = null;
1834
+ }
1835
+ }
1836
+ if (this.embeddedTable) {
1837
+ embeddedRows = (await this.embeddedTable.query().toArray());
1838
+ }
1839
+ const embeddedByType = {
1840
+ graphql: 0,
1841
+ css: 0,
1842
+ styled: 0,
1843
+ };
1844
+ for (const row of embeddedRows) {
1845
+ // Use snake_case column name from LanceDB record
1846
+ const embeddedType = row.embedded_type;
1847
+ if (embeddedType) {
1848
+ embeddedByType[embeddedType] += 1;
1849
+ }
1850
+ }
1851
+ return {
1852
+ filesIndexed: graphFiles,
1853
+ symbols: chunksCount,
1854
+ edges: edges.length,
1855
+ edgeTypes,
1856
+ unresolvedImports: edges.filter((edge) => edge.edgeType === 'IMPORTS_FILE' && edge.confidence === 'unresolved').length,
1857
+ embeddedChunks: embeddedRows.length,
1858
+ embeddedByType,
1859
+ };
1860
+ }
1861
+ async exportGraph(format = 'json') {
1862
+ const edges = await this.getAllEdges();
1863
+ if (format === 'json') {
1864
+ return JSON.stringify(edges, null, 2);
1865
+ }
1866
+ const lines = ['digraph CodeGraph {'];
1867
+ for (const edge of edges) {
1868
+ const source = edge.sourceId.replace(/"/g, '\\"');
1869
+ const target = edge.targetId.replace(/"/g, '\\"');
1870
+ lines.push(` "${source}" -> "${target}" [label="${edge.edgeType}"];`);
1871
+ }
1872
+ lines.push('}');
1873
+ return lines.join('\n');
1874
+ }
1875
+ }
1876
+ //# sourceMappingURL=index.js.map