homegraph 1.2.0 → 1.5.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 (560) hide show
  1. package/README.md +48 -11
  2. package/dist/bin/homegraph.js +298 -72
  3. package/dist/bin/homegraph.js.map +1 -1
  4. package/dist/context/index.d.ts.map +1 -1
  5. package/dist/context/index.js +117 -11
  6. package/dist/context/index.js.map +1 -1
  7. package/dist/db/index.d.ts +207 -6
  8. package/dist/db/index.d.ts.map +1 -1
  9. package/dist/db/index.js +461 -20
  10. package/dist/db/index.js.map +1 -1
  11. package/dist/db/migrations.d.ts +1 -1
  12. package/dist/db/migrations.d.ts.map +1 -1
  13. package/dist/db/migrations.js +19 -1
  14. package/dist/db/migrations.js.map +1 -1
  15. package/dist/db/queries.d.ts +157 -3
  16. package/dist/db/queries.d.ts.map +1 -1
  17. package/dist/db/queries.js +426 -9
  18. package/dist/db/queries.js.map +1 -1
  19. package/dist/db/schema.sql +32 -1
  20. package/dist/db/sqlite-adapter.d.ts +24 -17
  21. package/dist/db/sqlite-adapter.d.ts.map +1 -1
  22. package/dist/db/sqlite-adapter.js +292 -36
  23. package/dist/db/sqlite-adapter.js.map +1 -1
  24. package/dist/db/wal-valve.d.ts +119 -0
  25. package/dist/db/wal-valve.d.ts.map +1 -0
  26. package/dist/db/wal-valve.js +290 -0
  27. package/dist/db/wal-valve.js.map +1 -0
  28. package/dist/directory.d.ts +9 -5
  29. package/dist/directory.d.ts.map +1 -1
  30. package/dist/directory.js +210 -19
  31. package/dist/directory.js.map +1 -1
  32. package/dist/extraction/arkts-batch-worker.js +3 -2
  33. package/dist/extraction/arkts-batch-worker.js.map +1 -1
  34. package/dist/extraction/cfml-extractor.d.ts +107 -0
  35. package/dist/extraction/cfml-extractor.d.ts.map +1 -0
  36. package/dist/extraction/cfml-extractor.js +494 -0
  37. package/dist/extraction/cfml-extractor.js.map +1 -0
  38. package/dist/extraction/default-ignore.d.ts +39 -0
  39. package/dist/extraction/default-ignore.d.ts.map +1 -0
  40. package/dist/extraction/default-ignore.js +221 -0
  41. package/dist/extraction/default-ignore.js.map +1 -0
  42. package/dist/extraction/grammars.d.ts +25 -1
  43. package/dist/extraction/grammars.d.ts.map +1 -1
  44. package/dist/extraction/grammars.js +177 -24
  45. package/dist/extraction/grammars.js.map +1 -1
  46. package/dist/extraction/index.d.ts +52 -14
  47. package/dist/extraction/index.d.ts.map +1 -1
  48. package/dist/extraction/index.js +521 -314
  49. package/dist/extraction/index.js.map +1 -1
  50. package/dist/extraction/languages/arkts.d.ts +90 -6
  51. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  52. package/dist/extraction/languages/arkts.js +1579 -128
  53. package/dist/extraction/languages/arkts.js.map +1 -1
  54. package/dist/extraction/languages/c-cpp.d.ts +42 -0
  55. package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  56. package/dist/extraction/languages/c-cpp.js +369 -4
  57. package/dist/extraction/languages/c-cpp.js.map +1 -1
  58. package/dist/extraction/languages/cfquery.d.ts +12 -0
  59. package/dist/extraction/languages/cfquery.d.ts.map +1 -0
  60. package/dist/extraction/languages/cfquery.js +28 -0
  61. package/dist/extraction/languages/cfquery.js.map +1 -0
  62. package/dist/extraction/languages/cfscript.d.ts +3 -0
  63. package/dist/extraction/languages/cfscript.d.ts.map +1 -0
  64. package/dist/extraction/languages/cfscript.js +73 -0
  65. package/dist/extraction/languages/cfscript.js.map +1 -0
  66. package/dist/extraction/languages/cobol.d.ts +33 -0
  67. package/dist/extraction/languages/cobol.d.ts.map +1 -0
  68. package/dist/extraction/languages/cobol.js +499 -0
  69. package/dist/extraction/languages/cobol.js.map +1 -0
  70. package/dist/extraction/languages/erlang.d.ts +3 -0
  71. package/dist/extraction/languages/erlang.d.ts.map +1 -0
  72. package/dist/extraction/languages/erlang.js +350 -0
  73. package/dist/extraction/languages/erlang.js.map +1 -0
  74. package/dist/extraction/languages/index.d.ts.map +1 -1
  75. package/dist/extraction/languages/index.js +16 -0
  76. package/dist/extraction/languages/index.js.map +1 -1
  77. package/dist/extraction/languages/nix.d.ts +3 -0
  78. package/dist/extraction/languages/nix.d.ts.map +1 -0
  79. package/dist/extraction/languages/nix.js +294 -0
  80. package/dist/extraction/languages/nix.js.map +1 -0
  81. package/dist/extraction/languages/solidity.d.ts +3 -0
  82. package/dist/extraction/languages/solidity.d.ts.map +1 -0
  83. package/dist/extraction/languages/solidity.js +293 -0
  84. package/dist/extraction/languages/solidity.js.map +1 -0
  85. package/dist/extraction/languages/terraform.d.ts +3 -0
  86. package/dist/extraction/languages/terraform.d.ts.map +1 -0
  87. package/dist/extraction/languages/terraform.js +641 -0
  88. package/dist/extraction/languages/terraform.js.map +1 -0
  89. package/dist/extraction/languages/vbnet.d.ts +11 -0
  90. package/dist/extraction/languages/vbnet.d.ts.map +1 -0
  91. package/dist/extraction/languages/vbnet.js +141 -0
  92. package/dist/extraction/languages/vbnet.js.map +1 -0
  93. package/dist/extraction/mybatis-extractor.d.ts +30 -10
  94. package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  95. package/dist/extraction/mybatis-extractor.js +140 -38
  96. package/dist/extraction/mybatis-extractor.js.map +1 -1
  97. package/dist/extraction/parse-pool.d.ts +27 -1
  98. package/dist/extraction/parse-pool.d.ts.map +1 -1
  99. package/dist/extraction/parse-pool.js +75 -7
  100. package/dist/extraction/parse-pool.js.map +1 -1
  101. package/dist/extraction/parse-worker.js +9 -2
  102. package/dist/extraction/parse-worker.js.map +1 -1
  103. package/dist/extraction/store-worker.d.ts +20 -0
  104. package/dist/extraction/store-worker.d.ts.map +1 -0
  105. package/dist/extraction/store-worker.js +102 -0
  106. package/dist/extraction/store-worker.js.map +1 -0
  107. package/dist/extraction/store-writer.d.ts +48 -0
  108. package/dist/extraction/store-writer.d.ts.map +1 -0
  109. package/dist/extraction/store-writer.js +167 -0
  110. package/dist/extraction/store-writer.js.map +1 -0
  111. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  112. package/dist/extraction/tree-sitter-helpers.js +1 -0
  113. package/dist/extraction/tree-sitter-helpers.js.map +1 -1
  114. package/dist/extraction/tree-sitter-types.d.ts +3 -1
  115. package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  116. package/dist/extraction/tree-sitter.d.ts +38 -0
  117. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  118. package/dist/extraction/tree-sitter.js +801 -10
  119. package/dist/extraction/tree-sitter.js.map +1 -1
  120. package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  121. package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  122. package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  123. package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  124. package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  125. package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  126. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  127. package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  128. package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  129. package/dist/extraction/wasm-runtime-flags.d.ts +14 -4
  130. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  131. package/dist/extraction/wasm-runtime-flags.js +55 -7
  132. package/dist/extraction/wasm-runtime-flags.js.map +1 -1
  133. package/dist/graph/traversal.d.ts.map +1 -1
  134. package/dist/graph/traversal.js +1 -1
  135. package/dist/graph/traversal.js.map +1 -1
  136. package/dist/index.d.ts +74 -5
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +436 -55
  139. package/dist/index.js.map +1 -1
  140. package/dist/installer/index.d.ts +42 -0
  141. package/dist/installer/index.d.ts.map +1 -1
  142. package/dist/installer/index.js +97 -2
  143. package/dist/installer/index.js.map +1 -1
  144. package/dist/mcp/daemon.d.ts +25 -3
  145. package/dist/mcp/daemon.d.ts.map +1 -1
  146. package/dist/mcp/daemon.js +62 -8
  147. package/dist/mcp/daemon.js.map +1 -1
  148. package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  149. package/dist/mcp/dynamic-boundaries.js +2 -1
  150. package/dist/mcp/dynamic-boundaries.js.map +1 -1
  151. package/dist/mcp/early-ppid.d.ts +26 -0
  152. package/dist/mcp/early-ppid.d.ts.map +1 -0
  153. package/dist/mcp/early-ppid.js +29 -0
  154. package/dist/mcp/early-ppid.js.map +1 -0
  155. package/dist/mcp/engine.d.ts +4 -5
  156. package/dist/mcp/engine.d.ts.map +1 -1
  157. package/dist/mcp/engine.js +27 -1
  158. package/dist/mcp/engine.js.map +1 -1
  159. package/dist/mcp/index.d.ts.map +1 -1
  160. package/dist/mcp/index.js +41 -6
  161. package/dist/mcp/index.js.map +1 -1
  162. package/dist/mcp/liveness-watchdog.d.ts +18 -1
  163. package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  164. package/dist/mcp/liveness-watchdog.js +73 -13
  165. package/dist/mcp/liveness-watchdog.js.map +1 -1
  166. package/dist/mcp/memory-budget.d.ts +30 -0
  167. package/dist/mcp/memory-budget.d.ts.map +1 -0
  168. package/dist/mcp/memory-budget.js +123 -0
  169. package/dist/mcp/memory-budget.js.map +1 -0
  170. package/dist/mcp/proxy.d.ts.map +1 -1
  171. package/dist/mcp/proxy.js +88 -4
  172. package/dist/mcp/proxy.js.map +1 -1
  173. package/dist/mcp/query-cache.d.ts +35 -4
  174. package/dist/mcp/query-cache.d.ts.map +1 -1
  175. package/dist/mcp/query-cache.js +89 -19
  176. package/dist/mcp/query-cache.js.map +1 -1
  177. package/dist/mcp/query-pool.d.ts +46 -12
  178. package/dist/mcp/query-pool.d.ts.map +1 -1
  179. package/dist/mcp/query-pool.js +100 -23
  180. package/dist/mcp/query-pool.js.map +1 -1
  181. package/dist/mcp/query-worker.d.ts +2 -2
  182. package/dist/mcp/query-worker.js +2 -2
  183. package/dist/mcp/server-instructions.d.ts +4 -27
  184. package/dist/mcp/server-instructions.d.ts.map +1 -1
  185. package/dist/mcp/server-instructions.js +39 -84
  186. package/dist/mcp/server-instructions.js.map +1 -1
  187. package/dist/mcp/session.d.ts +14 -0
  188. package/dist/mcp/session.d.ts.map +1 -1
  189. package/dist/mcp/session.js +28 -1
  190. package/dist/mcp/session.js.map +1 -1
  191. package/dist/mcp/startup-handshake.d.ts +44 -0
  192. package/dist/mcp/startup-handshake.d.ts.map +1 -0
  193. package/dist/mcp/startup-handshake.js +73 -0
  194. package/dist/mcp/startup-handshake.js.map +1 -0
  195. package/dist/mcp/tools.d.ts +104 -4
  196. package/dist/mcp/tools.d.ts.map +1 -1
  197. package/dist/mcp/tools.js +2432 -217
  198. package/dist/mcp/tools.js.map +1 -1
  199. package/dist/mcp/transport.d.ts.map +1 -1
  200. package/dist/mcp/transport.js +18 -0
  201. package/dist/mcp/transport.js.map +1 -1
  202. package/dist/project-config.d.ts +38 -0
  203. package/dist/project-config.d.ts.map +1 -1
  204. package/dist/project-config.js +101 -2
  205. package/dist/project-config.js.map +1 -1
  206. package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
  207. package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  208. package/dist/resolution/c-fnptr-synthesizer.js +178 -146
  209. package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  210. package/dist/resolution/callback-synthesizer.d.ts +54 -10
  211. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  212. package/dist/resolution/callback-synthesizer.js +1299 -257
  213. package/dist/resolution/callback-synthesizer.js.map +1 -1
  214. package/dist/resolution/cooperative-yield.d.ts +10 -2
  215. package/dist/resolution/cooperative-yield.d.ts.map +1 -1
  216. package/dist/resolution/cooperative-yield.js +6 -4
  217. package/dist/resolution/cooperative-yield.js.map +1 -1
  218. package/dist/resolution/frameworks/cics.d.ts +20 -0
  219. package/dist/resolution/frameworks/cics.d.ts.map +1 -0
  220. package/dist/resolution/frameworks/cics.js +90 -0
  221. package/dist/resolution/frameworks/cics.js.map +1 -0
  222. package/dist/resolution/frameworks/go.d.ts.map +1 -1
  223. package/dist/resolution/frameworks/go.js +29 -6
  224. package/dist/resolution/frameworks/go.js.map +1 -1
  225. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  226. package/dist/resolution/frameworks/index.js +3 -0
  227. package/dist/resolution/frameworks/index.js.map +1 -1
  228. package/dist/resolution/frameworks/java.d.ts.map +1 -1
  229. package/dist/resolution/frameworks/java.js +14 -6
  230. package/dist/resolution/frameworks/java.js.map +1 -1
  231. package/dist/resolution/frameworks/terraform.d.ts +38 -0
  232. package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  233. package/dist/resolution/frameworks/terraform.js +277 -0
  234. package/dist/resolution/frameworks/terraform.js.map +1 -0
  235. package/dist/resolution/goframe-synthesizer.d.ts +2 -1
  236. package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
  237. package/dist/resolution/goframe-synthesizer.js +8 -3
  238. package/dist/resolution/goframe-synthesizer.js.map +1 -1
  239. package/dist/resolution/import-resolver.d.ts +9 -3
  240. package/dist/resolution/import-resolver.d.ts.map +1 -1
  241. package/dist/resolution/import-resolver.js +295 -11
  242. package/dist/resolution/import-resolver.js.map +1 -1
  243. package/dist/resolution/index.d.ts +146 -8
  244. package/dist/resolution/index.d.ts.map +1 -1
  245. package/dist/resolution/index.js +1089 -131
  246. package/dist/resolution/index.js.map +1 -1
  247. package/dist/resolution/memory-budget.d.ts +48 -0
  248. package/dist/resolution/memory-budget.d.ts.map +1 -0
  249. package/dist/resolution/memory-budget.js +162 -0
  250. package/dist/resolution/memory-budget.js.map +1 -0
  251. package/dist/resolution/name-matcher.d.ts +12 -4
  252. package/dist/resolution/name-matcher.d.ts.map +1 -1
  253. package/dist/resolution/name-matcher.js +793 -130
  254. package/dist/resolution/name-matcher.js.map +1 -1
  255. package/dist/resolution/resolver-pool.d.ts +106 -0
  256. package/dist/resolution/resolver-pool.d.ts.map +1 -0
  257. package/dist/resolution/resolver-pool.js +344 -0
  258. package/dist/resolution/resolver-pool.js.map +1 -0
  259. package/dist/resolution/resolver-worker.d.ts +17 -0
  260. package/dist/resolution/resolver-worker.d.ts.map +1 -0
  261. package/dist/resolution/resolver-worker.js +141 -0
  262. package/dist/resolution/resolver-worker.js.map +1 -0
  263. package/dist/resolution/strip-comments.d.ts +1 -1
  264. package/dist/resolution/strip-comments.d.ts.map +1 -1
  265. package/dist/resolution/strip-comments.js +49 -0
  266. package/dist/resolution/strip-comments.js.map +1 -1
  267. package/dist/resolution/types.d.ts +32 -0
  268. package/dist/resolution/types.d.ts.map +1 -1
  269. package/dist/resolution/workspace-packages.d.ts +10 -0
  270. package/dist/resolution/workspace-packages.d.ts.map +1 -1
  271. package/dist/resolution/workspace-packages.js +142 -4
  272. package/dist/resolution/workspace-packages.js.map +1 -1
  273. package/dist/search/identifier-segments.d.ts +60 -0
  274. package/dist/search/identifier-segments.d.ts.map +1 -0
  275. package/dist/search/identifier-segments.js +176 -0
  276. package/dist/search/identifier-segments.js.map +1 -0
  277. package/dist/search/query-utils.d.ts +201 -0
  278. package/dist/search/query-utils.d.ts.map +1 -1
  279. package/dist/search/query-utils.js +919 -2
  280. package/dist/search/query-utils.js.map +1 -1
  281. package/dist/spec/build/diff-parser.d.ts +55 -0
  282. package/dist/spec/build/diff-parser.d.ts.map +1 -0
  283. package/dist/spec/build/diff-parser.js +205 -0
  284. package/dist/spec/build/diff-parser.js.map +1 -0
  285. package/dist/spec/build/git-scanner.d.ts +93 -0
  286. package/dist/spec/build/git-scanner.d.ts.map +1 -0
  287. package/dist/spec/build/git-scanner.js +254 -0
  288. package/dist/spec/build/git-scanner.js.map +1 -0
  289. package/dist/spec/build/pipeline.d.ts +54 -0
  290. package/dist/spec/build/pipeline.d.ts.map +1 -0
  291. package/dist/spec/build/pipeline.js +170 -0
  292. package/dist/spec/build/pipeline.js.map +1 -0
  293. package/dist/spec/build/scan.d.ts +48 -0
  294. package/dist/spec/build/scan.d.ts.map +1 -0
  295. package/dist/spec/build/scan.js +89 -0
  296. package/dist/spec/build/scan.js.map +1 -0
  297. package/dist/spec/build/scope-resolver.d.ts +50 -0
  298. package/dist/spec/build/scope-resolver.d.ts.map +1 -0
  299. package/dist/spec/build/scope-resolver.js +105 -0
  300. package/dist/spec/build/scope-resolver.js.map +1 -0
  301. package/dist/spec/build/spec-extractor.d.ts +74 -0
  302. package/dist/spec/build/spec-extractor.d.ts.map +1 -0
  303. package/dist/spec/build/spec-extractor.js +344 -0
  304. package/dist/spec/build/spec-extractor.js.map +1 -0
  305. package/dist/spec/config.d.ts +35 -1
  306. package/dist/spec/config.d.ts.map +1 -1
  307. package/dist/spec/config.js +30 -5
  308. package/dist/spec/config.js.map +1 -1
  309. package/dist/spec/db/fts.d.ts.map +1 -1
  310. package/dist/spec/db/fts.js +19 -50
  311. package/dist/spec/db/fts.js.map +1 -1
  312. package/dist/spec/db/index.d.ts +5 -3
  313. package/dist/spec/db/index.d.ts.map +1 -1
  314. package/dist/spec/db/index.js +16 -1
  315. package/dist/spec/db/index.js.map +1 -1
  316. package/dist/spec/db/persist.d.ts +45 -0
  317. package/dist/spec/db/persist.d.ts.map +1 -0
  318. package/dist/spec/db/persist.js +66 -0
  319. package/dist/spec/db/persist.js.map +1 -0
  320. package/dist/spec/db/relations.d.ts +80 -1
  321. package/dist/spec/db/relations.d.ts.map +1 -1
  322. package/dist/spec/db/relations.js +175 -4
  323. package/dist/spec/db/relations.js.map +1 -1
  324. package/dist/spec/db/schema.d.ts +4 -0
  325. package/dist/spec/db/schema.d.ts.map +1 -1
  326. package/dist/spec/db/schema.js +4 -0
  327. package/dist/spec/db/schema.js.map +1 -1
  328. package/dist/spec/db/schema.sql +5 -1
  329. package/dist/spec/db/spec-node.d.ts +5 -0
  330. package/dist/spec/db/spec-node.d.ts.map +1 -1
  331. package/dist/spec/db/spec-node.js +11 -5
  332. package/dist/spec/db/spec-node.js.map +1 -1
  333. package/dist/spec/db/sql-utils.d.ts +11 -0
  334. package/dist/spec/db/sql-utils.d.ts.map +1 -0
  335. package/dist/spec/db/sql-utils.js +19 -0
  336. package/dist/spec/db/sql-utils.js.map +1 -0
  337. package/dist/spec/evolve/cluster-context.d.ts +30 -0
  338. package/dist/spec/evolve/cluster-context.d.ts.map +1 -0
  339. package/dist/spec/evolve/cluster-context.js +78 -0
  340. package/dist/spec/evolve/cluster-context.js.map +1 -0
  341. package/dist/spec/evolve/commit-spec-analyzer.d.ts +51 -0
  342. package/dist/spec/evolve/commit-spec-analyzer.d.ts.map +1 -0
  343. package/dist/spec/evolve/commit-spec-analyzer.js +88 -0
  344. package/dist/spec/evolve/commit-spec-analyzer.js.map +1 -0
  345. package/dist/spec/evolve/commit-spec-persister.d.ts +53 -0
  346. package/dist/spec/evolve/commit-spec-persister.d.ts.map +1 -0
  347. package/dist/spec/evolve/commit-spec-persister.js +93 -0
  348. package/dist/spec/evolve/commit-spec-persister.js.map +1 -0
  349. package/dist/spec/evolve/impact-locator.d.ts +23 -7
  350. package/dist/spec/evolve/impact-locator.d.ts.map +1 -1
  351. package/dist/spec/evolve/impact-locator.js +58 -14
  352. package/dist/spec/evolve/impact-locator.js.map +1 -1
  353. package/dist/spec/evolve/pipeline.d.ts +62 -25
  354. package/dist/spec/evolve/pipeline.d.ts.map +1 -1
  355. package/dist/spec/evolve/pipeline.js +436 -457
  356. package/dist/spec/evolve/pipeline.js.map +1 -1
  357. package/dist/spec/evolve/spec-rewriter.d.ts +13 -13
  358. package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
  359. package/dist/spec/evolve/spec-rewriter.js +45 -45
  360. package/dist/spec/evolve/spec-rewriter.js.map +1 -1
  361. package/dist/spec/git/commits.d.ts +85 -0
  362. package/dist/spec/git/commits.d.ts.map +1 -0
  363. package/dist/spec/git/commits.js +218 -0
  364. package/dist/spec/git/commits.js.map +1 -0
  365. package/dist/spec/git/exec.d.ts +13 -0
  366. package/dist/spec/git/exec.d.ts.map +1 -0
  367. package/dist/spec/git/exec.js +19 -0
  368. package/dist/spec/git/exec.js.map +1 -0
  369. package/dist/spec/git/index.d.ts +9 -0
  370. package/dist/spec/git/index.d.ts.map +1 -0
  371. package/dist/spec/git/index.js +22 -0
  372. package/dist/spec/git/index.js.map +1 -0
  373. package/dist/spec/git-utils.d.ts +8 -0
  374. package/dist/spec/git-utils.d.ts.map +1 -0
  375. package/dist/spec/git-utils.js +14 -0
  376. package/dist/spec/git-utils.js.map +1 -0
  377. package/dist/spec/graph/index.d.ts +8 -0
  378. package/dist/spec/graph/index.d.ts.map +1 -0
  379. package/dist/spec/graph/index.js +17 -0
  380. package/dist/spec/graph/index.js.map +1 -0
  381. package/dist/spec/graph/queries.d.ts +6 -35
  382. package/dist/spec/graph/queries.d.ts.map +1 -1
  383. package/dist/spec/graph/queries.js +71 -248
  384. package/dist/spec/graph/queries.js.map +1 -1
  385. package/dist/spec/llm/agent-client.d.ts +59 -0
  386. package/dist/spec/llm/agent-client.d.ts.map +1 -0
  387. package/dist/spec/llm/agent-client.js +213 -0
  388. package/dist/spec/llm/agent-client.js.map +1 -0
  389. package/dist/spec/llm/agents/claude-code.d.ts +32 -0
  390. package/dist/spec/llm/agents/claude-code.d.ts.map +1 -0
  391. package/dist/spec/llm/agents/claude-code.js +125 -0
  392. package/dist/spec/llm/agents/claude-code.js.map +1 -0
  393. package/dist/spec/llm/agents/codex.d.ts +32 -0
  394. package/dist/spec/llm/agents/codex.d.ts.map +1 -0
  395. package/dist/spec/llm/agents/codex.js +145 -0
  396. package/dist/spec/llm/agents/codex.js.map +1 -0
  397. package/dist/spec/llm/agents/detect-utils.d.ts +28 -0
  398. package/dist/spec/llm/agents/detect-utils.d.ts.map +1 -0
  399. package/dist/spec/llm/agents/detect-utils.js +91 -0
  400. package/dist/spec/llm/agents/detect-utils.js.map +1 -0
  401. package/dist/spec/llm/agents/deveco-code.d.ts +33 -0
  402. package/dist/spec/llm/agents/deveco-code.d.ts.map +1 -0
  403. package/dist/spec/llm/agents/deveco-code.js +165 -0
  404. package/dist/spec/llm/agents/deveco-code.js.map +1 -0
  405. package/dist/spec/llm/agents/index.d.ts +31 -0
  406. package/dist/spec/llm/agents/index.d.ts.map +1 -0
  407. package/dist/spec/llm/agents/index.js +65 -0
  408. package/dist/spec/llm/agents/index.js.map +1 -0
  409. package/dist/spec/llm/agents/types.d.ts +76 -0
  410. package/dist/spec/llm/agents/types.d.ts.map +1 -0
  411. package/dist/spec/llm/agents/types.js +15 -0
  412. package/dist/spec/llm/agents/types.js.map +1 -0
  413. package/dist/spec/llm/client.d.ts +31 -11
  414. package/dist/spec/llm/client.d.ts.map +1 -1
  415. package/dist/spec/llm/client.js +143 -81
  416. package/dist/spec/llm/client.js.map +1 -1
  417. package/dist/spec/llm/factory.d.ts +41 -0
  418. package/dist/spec/llm/factory.d.ts.map +1 -0
  419. package/dist/spec/llm/factory.js +80 -0
  420. package/dist/spec/llm/factory.js.map +1 -0
  421. package/dist/spec/llm/prompts.d.ts +24 -8
  422. package/dist/spec/llm/prompts.d.ts.map +1 -1
  423. package/dist/spec/llm/prompts.js +100 -50
  424. package/dist/spec/llm/prompts.js.map +1 -1
  425. package/dist/spec/llm/retry.d.ts +53 -0
  426. package/dist/spec/llm/retry.d.ts.map +1 -0
  427. package/dist/spec/llm/retry.js +153 -0
  428. package/dist/spec/llm/retry.js.map +1 -0
  429. package/dist/spec/mine/clusterer.d.ts +63 -0
  430. package/dist/spec/mine/clusterer.d.ts.map +1 -0
  431. package/dist/spec/mine/clusterer.js +904 -0
  432. package/dist/spec/mine/clusterer.js.map +1 -0
  433. package/dist/spec/mine/clustering/features.d.ts +38 -0
  434. package/dist/spec/mine/clustering/features.d.ts.map +1 -0
  435. package/dist/spec/mine/clustering/features.js +108 -0
  436. package/dist/spec/mine/clustering/features.js.map +1 -0
  437. package/dist/spec/mine/clustering/index.d.ts +62 -0
  438. package/dist/spec/mine/clustering/index.d.ts.map +1 -0
  439. package/dist/spec/mine/clustering/index.js +259 -0
  440. package/dist/spec/mine/clustering/index.js.map +1 -0
  441. package/dist/spec/mine/clustering/leiden.d.ts +61 -0
  442. package/dist/spec/mine/clustering/leiden.d.ts.map +1 -0
  443. package/dist/spec/mine/clustering/leiden.js +489 -0
  444. package/dist/spec/mine/clustering/leiden.js.map +1 -0
  445. package/dist/spec/mine/clustering/text-similarity.d.ts +22 -0
  446. package/dist/spec/mine/clustering/text-similarity.d.ts.map +1 -0
  447. package/dist/spec/mine/clustering/text-similarity.js +98 -0
  448. package/dist/spec/mine/clustering/text-similarity.js.map +1 -0
  449. package/dist/spec/mine/generator.d.ts +54 -0
  450. package/dist/spec/mine/generator.d.ts.map +1 -0
  451. package/dist/spec/mine/generator.js +267 -0
  452. package/dist/spec/mine/generator.js.map +1 -0
  453. package/dist/spec/mine/persist.d.ts +37 -0
  454. package/dist/spec/mine/persist.d.ts.map +1 -0
  455. package/dist/spec/mine/persist.js +170 -0
  456. package/dist/spec/mine/persist.js.map +1 -0
  457. package/dist/spec/mine/pipeline.d.ts +41 -0
  458. package/dist/spec/mine/pipeline.d.ts.map +1 -0
  459. package/dist/spec/mine/pipeline.js +236 -0
  460. package/dist/spec/mine/pipeline.js.map +1 -0
  461. package/dist/spec/mine/progress-handler.d.ts +22 -0
  462. package/dist/spec/mine/progress-handler.d.ts.map +1 -0
  463. package/dist/spec/mine/progress-handler.js +108 -0
  464. package/dist/spec/mine/progress-handler.js.map +1 -0
  465. package/dist/spec/mine/progress.d.ts +23 -0
  466. package/dist/spec/mine/progress.d.ts.map +1 -0
  467. package/dist/spec/mine/progress.js +12 -0
  468. package/dist/spec/mine/progress.js.map +1 -0
  469. package/dist/spec/mine/scanner.d.ts +59 -0
  470. package/dist/spec/mine/scanner.d.ts.map +1 -0
  471. package/dist/spec/mine/scanner.js +383 -0
  472. package/dist/spec/mine/scanner.js.map +1 -0
  473. package/dist/spec/types.d.ts +32 -0
  474. package/dist/spec/types.d.ts.map +1 -1
  475. package/dist/spec/ui/index.d.ts +9 -0
  476. package/dist/spec/ui/index.d.ts.map +1 -0
  477. package/dist/spec/ui/index.js +16 -0
  478. package/dist/spec/ui/index.js.map +1 -0
  479. package/dist/spec/ui/progress-handler.d.ts +33 -0
  480. package/dist/spec/ui/progress-handler.d.ts.map +1 -0
  481. package/dist/spec/ui/progress-handler.js +132 -0
  482. package/dist/spec/ui/progress-handler.js.map +1 -0
  483. package/dist/spec/ui/progress.d.ts +24 -0
  484. package/dist/spec/ui/progress.d.ts.map +1 -0
  485. package/dist/spec/ui/progress.js +13 -0
  486. package/dist/spec/ui/progress.js.map +1 -0
  487. package/dist/spec/utils/fs.d.ts +45 -0
  488. package/dist/spec/utils/fs.d.ts.map +1 -0
  489. package/dist/spec/utils/fs.js +163 -0
  490. package/dist/spec/utils/fs.js.map +1 -0
  491. package/dist/spec/utils/index.d.ts +12 -0
  492. package/dist/spec/utils/index.d.ts.map +1 -0
  493. package/dist/spec/utils/index.js +26 -0
  494. package/dist/spec/utils/index.js.map +1 -0
  495. package/dist/spec/utils/meta.d.ts +32 -0
  496. package/dist/spec/utils/meta.d.ts.map +1 -0
  497. package/dist/spec/utils/meta.js +122 -0
  498. package/dist/spec/utils/meta.js.map +1 -0
  499. package/dist/spec/utils/truncate.d.ts +86 -0
  500. package/dist/spec/utils/truncate.d.ts.map +1 -0
  501. package/dist/spec/utils/truncate.js +172 -0
  502. package/dist/spec/utils/truncate.js.map +1 -0
  503. package/dist/spec/utils.d.ts +4 -16
  504. package/dist/spec/utils.d.ts.map +1 -1
  505. package/dist/spec/utils.js +6 -58
  506. package/dist/spec/utils.js.map +1 -1
  507. package/dist/sync/git-hooks.d.ts.map +1 -1
  508. package/dist/sync/git-hooks.js +2 -0
  509. package/dist/sync/git-hooks.js.map +1 -1
  510. package/dist/sync/watcher.d.ts +35 -6
  511. package/dist/sync/watcher.d.ts.map +1 -1
  512. package/dist/sync/watcher.js +127 -17
  513. package/dist/sync/watcher.js.map +1 -1
  514. package/dist/sync/worktree.d.ts.map +1 -1
  515. package/dist/sync/worktree.js +5 -0
  516. package/dist/sync/worktree.js.map +1 -1
  517. package/dist/telemetry/index.js +3 -3
  518. package/dist/telemetry/index.js.map +1 -1
  519. package/dist/types.d.ts +22 -1
  520. package/dist/types.d.ts.map +1 -1
  521. package/dist/types.js +10 -0
  522. package/dist/types.js.map +1 -1
  523. package/dist/upgrade/index.d.ts +32 -0
  524. package/dist/upgrade/index.d.ts.map +1 -1
  525. package/dist/upgrade/index.js +162 -14
  526. package/dist/upgrade/index.js.map +1 -1
  527. package/dist/upgrade/remove-binary.d.ts +87 -0
  528. package/dist/upgrade/remove-binary.d.ts.map +1 -0
  529. package/dist/upgrade/remove-binary.js +289 -0
  530. package/dist/upgrade/remove-binary.js.map +1 -0
  531. package/dist/upgrade/update-check.d.ts +92 -0
  532. package/dist/upgrade/update-check.d.ts.map +1 -0
  533. package/dist/upgrade/update-check.js +258 -0
  534. package/dist/upgrade/update-check.js.map +1 -0
  535. package/package.json +6 -2
  536. package/scripts/agent-eval/run-all.sh +6 -0
  537. package/scripts/build-bundle.sh +5 -0
  538. package/scripts/npm-shim.js +8 -1
  539. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  540. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  541. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
  542. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  543. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  544. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  545. package/scripts/exp_boundary_eval/data/agents.json +0 -109
  546. package/scripts/exp_boundary_eval/data/experiments.json +0 -140
  547. package/scripts/qa_eval/README.md +0 -407
  548. package/scripts/qa_eval/_test_deveco_probe.py +0 -41
  549. package/scripts/qa_eval/agent_runner.py +0 -526
  550. package/scripts/qa_eval/data/.gitignore +0 -4
  551. package/scripts/qa_eval/data/test-set.jsonl +0 -2
  552. package/scripts/qa_eval/eval_metrics.py +0 -274
  553. package/scripts/qa_eval/external_agent.py +0 -976
  554. package/scripts/qa_eval/llm_config.py +0 -92
  555. package/scripts/qa_eval/memory_monitor.py +0 -132
  556. package/scripts/qa_eval/my_answer_accuracy.py +0 -187
  557. package/scripts/qa_eval/requirements.txt +0 -2
  558. package/scripts/qa_eval/run_pipeline.py +0 -804
  559. package/scripts/qa_eval/stats_efficiency.py +0 -279
  560. package/scripts/qa_eval/stats_scores.py +0 -207
@@ -41,11 +41,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
41
41
  return (mod && mod.__esModule) ? mod : { "default": mod };
42
42
  };
43
43
  Object.defineProperty(exports, "__esModule", { value: true });
44
- exports.loadAllGrammars = exports.loadGrammarsForLanguages = exports.initGrammars = exports.getSupportedLanguages = exports.isGrammarLoaded = exports.isLanguageSupported = exports.isSourceFile = exports.detectLanguage = exports.extractFromSource = exports.ExtractionOrchestrator = exports.ScopeIgnore = void 0;
44
+ exports.loadAllGrammars = exports.loadGrammarsForLanguages = exports.initGrammars = exports.getSupportedLanguages = exports.isGrammarLoaded = exports.isLanguageSupported = exports.isSourceFile = exports.detectLanguage = exports.extractFromSource = exports.ExtractionOrchestrator = exports.ScopeIgnore = exports.buildDefaultIgnore = void 0;
45
45
  exports.hashContent = hashContent;
46
- exports.buildDefaultIgnore = buildDefaultIgnore;
47
46
  exports.buildScopeIgnore = buildScopeIgnore;
48
47
  exports.discoverEmbeddedRepoRoots = discoverEmbeddedRepoRoots;
48
+ exports.findUnindexedIgnoredRepos = findUnindexedIgnoredRepos;
49
49
  exports.scanDirectory = scanDirectory;
50
50
  exports.scanDirectoryAsync = scanDirectoryAsync;
51
51
  const fs = __importStar(require("fs"));
@@ -56,12 +56,17 @@ const crypto = __importStar(require("crypto"));
56
56
  const child_process_1 = require("child_process");
57
57
  const tree_sitter_1 = require("./tree-sitter");
58
58
  const parse_pool_1 = require("./parse-pool");
59
+ const store_writer_1 = require("./store-writer");
60
+ const cooperative_yield_1 = require("../resolution/cooperative-yield");
59
61
  const grammars_1 = require("./grammars");
60
62
  const project_config_1 = require("../project-config");
61
63
  const directory_1 = require("../directory");
62
64
  const errors_1 = require("../errors");
63
65
  const utils_1 = require("../utils");
64
66
  const ignore_1 = __importDefault(require("ignore"));
67
+ const default_ignore_1 = require("./default-ignore");
68
+ var default_ignore_2 = require("./default-ignore");
69
+ Object.defineProperty(exports, "buildDefaultIgnore", { enumerable: true, get: function () { return default_ignore_2.buildDefaultIgnore; } });
65
70
  const frameworks_1 = require("../resolution/frameworks");
66
71
  const context_1 = require("./context");
67
72
  const arkts_1 = require("./languages/arkts");
@@ -108,176 +113,6 @@ function hashContent(content) {
108
113
  * symbols. 1 MB covers essentially all hand-written source.
109
114
  */
110
115
  const MAX_FILE_SIZE = 1024 * 1024;
111
- /**
112
- * Directory names that are dependency, build, cache, or tooling output across the
113
- * languages/frameworks HomeGraph supports — curated from the canonical
114
- * github/gitignore templates. Excluded by default so the graph reflects your code,
115
- * not third-party noise, without requiring a `.gitignore` (issue #407). The
116
- * exclusion applies uniformly (git or not, tracked or not); the only opt-in is an
117
- * explicit `.gitignore` negation (e.g. `!vendor/`). First-party-prone or generic
118
- * names (`packages`, `lib`, `app`, `bin`, `src`, `deps`, `env`, `tmp`, `storage`,
119
- * `Library`) are deliberately NOT listed, to avoid ever hiding real source.
120
- *
121
- * Only dirs that actually contain *indexable source* (or are enormous) earn a slot
122
- * — IDE/state dirs like `.idea`/`.vs` are omitted because HomeGraph indexes only
123
- * recognized source extensions, so they produce no symbols regardless.
124
- */
125
- const DEFAULT_IGNORE_DIRS = new Set([
126
- // JS / TS — dependency directories
127
- 'node_modules', 'bower_components', 'jspm_packages', 'web_modules',
128
- '.yarn', '.pnpm-store',
129
- // JS / TS — framework & bundler build / cache / deploy output
130
- '.next', '.nuxt', '.svelte-kit', '.turbo', '.vite', '.parcel-cache', '.angular',
131
- '.docusaurus', 'storybook-static', '.vinxi', '.nitro', 'out-tsc',
132
- '.vercel', '.netlify', '.wrangler',
133
- // Build output (common across ecosystems)
134
- 'dist', 'build', 'out', '.output',
135
- // Test / coverage
136
- 'coverage', '.nyc_output',
137
- // Python
138
- '__pycache__', '__pypackages__', '.venv', 'venv', '.pixi', '.pdm-build',
139
- '.mypy_cache', '.pytest_cache', '.ruff_cache', '.tox', '.nox', '.hypothesis',
140
- '.ipynb_checkpoints', '.eggs',
141
- // Rust / JVM (Maven, Gradle, Scala)
142
- 'target', '.gradle',
143
- // .NET
144
- 'obj',
145
- // Vendored deps (Go, PHP/Composer, Ruby/Bundler)
146
- 'vendor',
147
- // Swift / iOS
148
- '.build', 'Pods', 'Carthage', 'DerivedData', '.swiftpm',
149
- // Dart / Flutter
150
- '.dart_tool', '.pub-cache',
151
- // Native (Android NDK, C/C++ deps)
152
- '.cxx', '.externalNativeBuild', 'vcpkg_installed',
153
- // Scala tooling
154
- '.bloop', '.metals',
155
- // Lua / Luau (LuaRocks)
156
- 'lua_modules', '.luarocks',
157
- // Delphi / RAD Studio IDE backups (duplicate .pas source — would double-count)
158
- '__history', '__recovery',
159
- // Generic cache
160
- '.cache',
161
- ]);
162
- /**
163
- * Android resource directory types. A `res/` tree holds ONLY non-code resources —
164
- * layouts, drawables, value bags (strings/colors/styles), menus, navigation
165
- * graphs — split into one typed subdirectory per kind, optionally density/locale/
166
- * version-qualified (`values-es`, `drawable-hdpi`, `layout-v21`, …). None of it
167
- * yields an extractable code symbol, yet on an Android app it DOMINATES the tree
168
- * (one report: 26k XML files = 97% of the project, 0 symbols), bloating the DB,
169
- * slowing indexing, and skewing both the file count and `codegraph_explore`
170
- * results (#1047). So these are excluded by default. The structure is
171
- * self-identifying — a non-Android project has no `res/layout/` etc., so it's
172
- * untouched — and the only XML that DOES produce symbols (MyBatis mappers) lives
173
- * under `src/main/resources/`, never `res/`, so nothing useful is dropped.
174
- * `res/raw/` is deliberately NOT here: it holds arbitrary bundled assets that can
175
- * be code-ish (a `.sql` schema, a `.js`), so we leave it indexed. Override any of
176
- * these with a `.gitignore` negation (e.g. `!res/values/`).
177
- */
178
- const ANDROID_RES_TYPES = [
179
- 'anim', 'animator', 'color', 'drawable', 'font', 'layout',
180
- 'menu', 'mipmap', 'navigation', 'transition', 'values', 'xml',
181
- ];
182
- /** Gitignore-style patterns for the `ignore` matcher: the dirs above plus a few globs. */
183
- const DEFAULT_IGNORE_PATTERNS = [
184
- ...Array.from(DEFAULT_IGNORE_DIRS, (d) => `${d}/`),
185
- '*.egg-info/', // Python packaging metadata
186
- 'cmake-build-*/', // CLion / CMake build trees
187
- 'bazel-*/', // Bazel output symlink trees
188
- // Android resource dirs at any depth, with their qualifier variants (#1047).
189
- ...ANDROID_RES_TYPES.map((t) => `**/res/${t}*/`),
190
- ];
191
- /** True if `buf` decodes as strict UTF-8 (no invalid byte sequences). */
192
- function isValidUtf8(buf) {
193
- try {
194
- new TextDecoder('utf-8', { fatal: true }).decode(buf);
195
- return true;
196
- }
197
- catch {
198
- return false;
199
- }
200
- }
201
- /**
202
- * Read a `.gitignore` and return patterns safe to hand to the `ignore` matcher —
203
- * never throwing, even when the file isn't real gitignore text. Two failure
204
- * modes, both seen in the wild (issue #682):
205
- *
206
- * - The file isn't valid UTF-8 — e.g. transparently encrypted in place by
207
- * corporate DLP / endpoint-security software, leaving a UTF-16 header plus
208
- * ciphertext. None of it is meaningful patterns, so the whole file is skipped.
209
- * - The file is text but a single line can't be compiled to a regex by the
210
- * `ignore` library — `\\[` and friends throw "Unterminated character class".
211
- * Crucially the throw is LAZY (at match time, not `.add()`), so it would
212
- * otherwise escape mid-scan. That one pattern is dropped; the rest are kept.
213
- *
214
- * Either way a warning that NAMES the file is logged (the reporter couldn't tell
215
- * which `.gitignore` was at fault) and indexing continues instead of aborting.
216
- * Returns '' when there's nothing usable.
217
- */
218
- function readGitignorePatterns(giPath) {
219
- let buf;
220
- try {
221
- buf = fs.readFileSync(giPath);
222
- }
223
- catch {
224
- return ''; // unreadable (permissions / race) — treat as absent
225
- }
226
- // A NUL byte never appears in real gitignore text, and a fatal UTF-8 decode
227
- // catches the rest. Such a file isn't ignore patterns at all.
228
- if (buf.includes(0) || !isValidUtf8(buf)) {
229
- (0, errors_1.logWarn)('Ignoring a .gitignore that is not valid UTF-8 text — it may have been encrypted ' +
230
- 'in place by endpoint-security software. Indexing continues without it.', { file: giPath });
231
- return '';
232
- }
233
- const content = buf.toString('utf-8');
234
- // Fast path: one `.ignores()` call forces the library to compile EVERY rule,
235
- // so if it doesn't throw, the whole file is safe to use verbatim.
236
- try {
237
- (0, ignore_1.default)().add(content).ignores('.homegraph-probe');
238
- return content;
239
- }
240
- catch {
241
- // Fall through: a line is uncompilable — keep the good ones, drop the bad.
242
- }
243
- const kept = [];
244
- let dropped = 0;
245
- for (const line of content.split(/\r?\n/)) {
246
- try {
247
- (0, ignore_1.default)().add(line).ignores('.homegraph-probe');
248
- kept.push(line);
249
- }
250
- catch {
251
- dropped++;
252
- }
253
- }
254
- if (dropped > 0) {
255
- (0, errors_1.logWarn)(`Skipped ${dropped} unparseable pattern(s) in a .gitignore; the rest are applied.`, { file: giPath });
256
- }
257
- return kept.join('\n');
258
- }
259
- /**
260
- * An `ignore` matcher seeded with the built-in defaults, merged with the project's
261
- * root .gitignore so a negation there (e.g. `!vendor/`) overrides a default. Shared
262
- * by both enumeration paths so behavior is identical with or without git — and so
263
- * the defaults apply to tracked files too (committing a dependency dir doesn't make
264
- * it project code; the explicit `.gitignore` negation is the only opt-in).
265
- */
266
- function buildDefaultIgnore(rootDir) {
267
- const ig = (0, ignore_1.default)().add(DEFAULT_IGNORE_PATTERNS);
268
- const rootGitignore = path.join(rootDir, '.gitignore');
269
- if (fs.existsSync(rootGitignore))
270
- ig.add(readGitignorePatterns(rootGitignore));
271
- return ig;
272
- }
273
- /**
274
- * Defaults-only ignore matcher (no root `.gitignore` merged). Used wherever the
275
- * parent repo's own ignore rules must NOT apply — inside embedded child repos,
276
- * whose gitignore semantics their own `git ls-files` already enforced (#514).
277
- */
278
- function defaultsOnlyIgnore() {
279
- return (0, ignore_1.default)().add(DEFAULT_IGNORE_PATTERNS);
280
- }
281
116
  /**
282
117
  * Matcher for the project's `homegraph.json` `includeIgnored` patterns — the
283
118
  * explicit opt-in to index embedded git repos living inside gitignored
@@ -304,6 +139,160 @@ function loadExcludeMatcher(rootDir) {
304
139
  const patterns = (0, project_config_1.loadExcludePatterns)(rootDir);
305
140
  return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
306
141
  }
142
+ /**
143
+ * Matcher for the project's `homegraph.json` `include` patterns — first-party
144
+ * source to force INTO the index even when `.gitignore` drops it (the general
145
+ * whitelist `includeIgnored` never was — that one only revives *embedded git
146
+ * repos*). The case it exists for: a project under a second VCS (SVN/Perforce)
147
+ * `.gitignore`s its own real source so it stays out of Git, yet we still want it
148
+ * indexed. Returns `null` when nothing is force-included (the zero-config
149
+ * default → no overhead, no extra walk). Built once per scan/sync/scope
150
+ * operation from the scan root.
151
+ */
152
+ function loadIncludeMatcher(rootDir) {
153
+ const patterns = (0, project_config_1.loadIncludePatterns)(rootDir);
154
+ return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
155
+ }
156
+ /** Glob metacharacters that end the static (literal) prefix of an `include` pattern. */
157
+ const GLOB_META = /[*?[\]{}!]/;
158
+ /**
159
+ * The static directory prefix of each `include` pattern — the literal leading
160
+ * path up to the first glob segment — trailing-slashed, used to (a) walk only
161
+ * the opted-in subtrees in `collectIncludedFiles` and (b) let `ScopeIgnore` keep
162
+ * the watcher descending toward them. `Tools/` stays `Tools/`; a recursive
163
+ * `Tools/**` glob yields `Tools/`; `src/local/file.ts` yields `src/local/` (the
164
+ * file's dir); a pattern that starts with a glob (like a leading `**`) yields
165
+ * `''`, meaning "no static root — walk the whole tree". Duplicates and roots
166
+ * nested under a broader root are collapsed so each subtree is walked once.
167
+ */
168
+ function includeStaticRoots(patterns) {
169
+ const roots = new Set();
170
+ for (const pattern of patterns) {
171
+ let p = pattern.replace(/^\/+/, '');
172
+ const trailingSlash = p.endsWith('/');
173
+ if (trailingSlash)
174
+ p = p.slice(0, -1);
175
+ const segs = p.split('/').filter(Boolean);
176
+ const lead = [];
177
+ for (const s of segs) {
178
+ if (GLOB_META.test(s))
179
+ break;
180
+ lead.push(s);
181
+ }
182
+ const hadWildcard = lead.length < segs.length;
183
+ // A wholly-literal pattern with no trailing slash names a file (or a dir we
184
+ // can't tell apart) — drop its last segment so we walk the containing dir
185
+ // and let the matcher pick the file. A trailing slash or a glob means the
186
+ // remaining `lead` is already the directory to walk.
187
+ if (!hadWildcard && !trailingSlash && lead.length > 0)
188
+ lead.pop();
189
+ if (lead.length === 0) {
190
+ roots.clear();
191
+ roots.add('');
192
+ return ['']; // a top-level glob forces a whole-tree walk; nothing narrower matters
193
+ }
194
+ roots.add(lead.join('/') + '/');
195
+ }
196
+ // Collapse roots nested under a broader one (e.g. drop `a/b/` if `a/` is present).
197
+ const all = [...roots];
198
+ return all.filter((r) => !all.some((other) => other !== r && r.startsWith(other)));
199
+ }
200
+ /**
201
+ * Actively discover the source files an `include` whitelist forces in. `git
202
+ * ls-files` never lists gitignored files, so a filtered filesystem walk of just
203
+ * the opted-in subtrees (`includeStaticRoots`) is the only way to find them.
204
+ * Returns project-root-relative, normalized source-file paths.
205
+ *
206
+ * A file is collected when it MATCHES `include`, is NOT hit by `exclude` (an
207
+ * explicit exclude always wins), is a recognized source file, and does not live
208
+ * under a built-in default-ignored dir (`node_modules`, `dist`, …), `.git`, or
209
+ * HomeGraph's data dir — those are never resurfaced, mirroring `ScopeIgnore`.
210
+ * `.gitignore` is deliberately NOT consulted: overriding it is the whole point.
211
+ */
212
+ function collectIncludedFiles(rootDir, include, exclude, roots, overrides) {
213
+ const out = new Set();
214
+ const defaults = (0, default_ignore_1.defaultsOnlyIgnore)();
215
+ const visited = new Set();
216
+ const consider = (abs, rel, isDir) => {
217
+ if (isDir) {
218
+ if (defaults.ignores(rel + '/'))
219
+ return; // never node_modules/dist/… via include
220
+ // An explicit `exclude` always wins over `include`; prune the whole subtree
221
+ // here so a large excluded dir (a committed frontend's own vendored deps,
222
+ // build output, …) is never walked — the per-file guard below still catches
223
+ // anything a directory pattern doesn't, so this is a pure efficiency win.
224
+ if (exclude && exclude.ignores(rel + '/'))
225
+ return;
226
+ walk(abs);
227
+ }
228
+ else {
229
+ if (defaults.ignores(rel))
230
+ return;
231
+ if (!include.ignores(rel))
232
+ return;
233
+ if (exclude && exclude.ignores(rel))
234
+ return;
235
+ if (!(0, grammars_1.isSourceFile)(rel, overrides))
236
+ return;
237
+ out.add(rel);
238
+ }
239
+ };
240
+ function walk(absDir) {
241
+ let realDir;
242
+ try {
243
+ realDir = fs.realpathSync(absDir);
244
+ }
245
+ catch {
246
+ return;
247
+ }
248
+ if (visited.has(realDir))
249
+ return; // symlink-cycle guard
250
+ visited.add(realDir);
251
+ let entries;
252
+ try {
253
+ entries = fs.readdirSync(absDir, { withFileTypes: true });
254
+ }
255
+ catch {
256
+ return;
257
+ }
258
+ for (const entry of entries) {
259
+ if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
260
+ continue;
261
+ const abs = path.join(absDir, entry.name);
262
+ const rel = (0, utils_1.normalizePath)(path.relative(rootDir, abs));
263
+ if (!rel || rel.startsWith('..'))
264
+ continue;
265
+ if (entry.isSymbolicLink()) {
266
+ try {
267
+ const st = fs.statSync(fs.realpathSync(abs));
268
+ consider(abs, rel, st.isDirectory());
269
+ }
270
+ catch {
271
+ // broken symlink — skip
272
+ }
273
+ continue;
274
+ }
275
+ consider(abs, rel, entry.isDirectory());
276
+ }
277
+ }
278
+ for (const root of roots) {
279
+ walk(root === '' ? rootDir : path.join(rootDir, root));
280
+ }
281
+ return out;
282
+ }
283
+ /**
284
+ * The included source files (`homegraph.json` `include`) for a scan root, or an
285
+ * empty set when nothing is force-included. Centralizes loading the matcher,
286
+ * roots, exclude, and overrides so both enumeration paths (git and filesystem
287
+ * walk) add the same files.
288
+ */
289
+ function collectIncludedFilesForRoot(rootDir) {
290
+ const include = loadIncludeMatcher(rootDir);
291
+ if (!include)
292
+ return new Set();
293
+ const roots = includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir));
294
+ return collectIncludedFiles(rootDir, include, loadExcludeMatcher(rootDir), roots, (0, project_config_1.loadExtensionOverrides)(rootDir));
295
+ }
307
296
  /**
308
297
  * `git ls-files --directory` collapses a wholly-untracked/ignored directory into
309
298
  * one entry — and when the command's own cwd is such a directory (the indexed
@@ -389,7 +378,7 @@ function classifyGitDir(absDir) {
389
378
  */
390
379
  function findNestedGitRepos(absDir, relPrefix) {
391
380
  const found = [];
392
- const defaults = defaultsOnlyIgnore();
381
+ const defaults = (0, default_ignore_1.defaultsOnlyIgnore)();
393
382
  const queue = [
394
383
  { abs: absDir, rel: relPrefix, depth: 0 },
395
384
  ];
@@ -444,17 +433,31 @@ function findNestedGitRepos(absDir, relPrefix) {
444
433
  class ScopeIgnore {
445
434
  rootMatcher;
446
435
  exclude;
436
+ include;
437
+ includeRoots;
447
438
  embedded;
448
- defaults = defaultsOnlyIgnore();
439
+ defaults = (0, default_ignore_1.defaultsOnlyIgnore)();
449
440
  constructor(rootMatcher, embedded,
450
441
  /**
451
- * Project `codegraph.json` `exclude` patterns (#999), matched against the
442
+ * Project `homegraph.json` `exclude` patterns (#999), matched against the
452
443
  * full root-relative path. Wins over everything else — an explicit user
453
444
  * exclude applies even to tracked files and even inside embedded repos.
454
445
  */
455
- exclude = null) {
446
+ exclude = null,
447
+ /**
448
+ * Project `homegraph.json` `include` patterns — first-party source forced
449
+ * INTO the index despite `.gitignore`. When a path matches, it is NOT
450
+ * ignored (so the watcher watches it), overriding `.gitignore`/`rootMatcher`
451
+ * — but never `exclude` (checked first) and never a built-in default-ignored
452
+ * dir. `includeRoots` are the static prefixes so a gitignored ANCESTOR
453
+ * directory of an included subtree still isn't pruned by the directory
454
+ * walker/watcher.
455
+ */
456
+ include = null, includeRoots = []) {
456
457
  this.rootMatcher = rootMatcher;
457
458
  this.exclude = exclude;
459
+ this.include = include;
460
+ this.includeRoots = includeRoots;
458
461
  // Longest root first so paths in nested embedded repos hit the innermost matcher.
459
462
  this.embedded = [...embedded].sort((a, b) => b.root.length - a.root.length);
460
463
  }
@@ -464,6 +467,20 @@ class ScopeIgnore {
464
467
  // everywhere, including ancestors of embedded repos.
465
468
  if (this.exclude && this.exclude.ignores(rel))
466
469
  return true;
470
+ // User `include`: force first-party source in despite `.gitignore`. Never
471
+ // resurfaces a built-in default-ignored dir (node_modules/dist/…), so an
472
+ // include pattern can't accidentally pull in dependency/build trees.
473
+ if (this.include && !this.defaults.ignores(rel)) {
474
+ if (rel.endsWith('/')) {
475
+ // A directory on (or leading to) an included subtree must stay walkable
476
+ // so the watcher/walker descends to reach the forced-in files.
477
+ if (this.includeRoots.some((r) => r.startsWith(rel) || rel.startsWith(r)))
478
+ return false;
479
+ }
480
+ else if (this.include.ignores(rel)) {
481
+ return false;
482
+ }
483
+ }
467
484
  for (const { root, matcher } of this.embedded) {
468
485
  if (rel.startsWith(root)) {
469
486
  const inner = rel.slice(root.length);
@@ -490,7 +507,8 @@ exports.ScopeIgnore = ScopeIgnore;
490
507
  */
491
508
  function buildScopeIgnore(rootDir, embeddedRoots) {
492
509
  const roots = embeddedRoots ? [...embeddedRoots] : discoverEmbeddedRepoRoots(rootDir);
493
- return new ScopeIgnore(buildDefaultIgnore(rootDir), roots.map((root) => ({ root, matcher: buildDefaultIgnore(path.join(rootDir, root)) })), loadExcludeMatcher(rootDir));
510
+ const include = loadIncludeMatcher(rootDir);
511
+ return new ScopeIgnore((0, default_ignore_1.buildDefaultIgnore)(rootDir), roots.map((root) => ({ root, matcher: (0, default_ignore_1.buildDefaultIgnore)(path.join(rootDir, root)) })), loadExcludeMatcher(rootDir), include, include ? includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir)) : []);
494
512
  }
495
513
  /**
496
514
  * Whether an embedded repo found as a tracked gitlink (mode 160000, #1031/#1033)
@@ -503,7 +521,7 @@ function buildScopeIgnore(rootDir, embeddedRoots) {
503
521
  * under `node_modules` is never project code; not even an explicit opt-in
504
522
  * revives it (matches `findIgnoredEmbeddedRepos`).
505
523
  * 2. The parent repo's own `.gitignore` covers its path and the project did
506
- * NOT opt that path in via `codegraph.json` `includeIgnored`. The gitignore
524
+ * NOT opt that path in via `homegraph.json` `includeIgnored`. The gitignore
507
525
  * rule is the user's stated intent to keep that path out of scope, exactly
508
526
  * as for an UNtracked embedded repo — respect it by default, opt back in
509
527
  * with `includeIgnored` (#514, #970, #976).
@@ -538,7 +556,7 @@ function discoverEmbeddedRepoRoots(rootDir) {
538
556
  return [];
539
557
  }
540
558
  const out = [];
541
- const defaults = defaultsOnlyIgnore();
559
+ const defaults = (0, default_ignore_1.defaultsOnlyIgnore)();
542
560
  const includeIgnored = loadIncludeIgnoredMatcher(rootDir);
543
561
  const visit = (repoAbs, prefix) => {
544
562
  const candidates = [];
@@ -558,7 +576,7 @@ function discoverEmbeddedRepoRoots(rootDir) {
558
576
  // (#1031, #1033)
559
577
  try {
560
578
  const gitOpts = { cwd: repoAbs, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true };
561
- const repoIgnore = buildDefaultIgnore(repoAbs);
579
+ const repoIgnore = (0, default_ignore_1.buildDefaultIgnore)(repoAbs);
562
580
  for (const rel of listGitlinkRels(gitOpts)) {
563
581
  const relDir = rel.endsWith('/') ? rel : rel + '/';
564
582
  // A gitlink under a gitignored path is respected (not indexed) unless the
@@ -580,6 +598,50 @@ function discoverEmbeddedRepoRoots(rootDir) {
580
598
  visit(rootDir, '');
581
599
  return out;
582
600
  }
601
+ /**
602
+ * Cap on how many skipped gitignored repos the CLI hint enumerates — a huge
603
+ * gitignored data dir full of clones must never turn the hint scan into a long
604
+ * walk. Enough to make the point; the caller says "+N more" past this.
605
+ */
606
+ const UNINDEXED_IGNORED_REPO_HINT_CAP = 100;
607
+ /**
608
+ * The INVERSE of the gitignored side of {@link discoverEmbeddedRepoRoots}:
609
+ * nested git repositories under a gitignored directory that the project has NOT
610
+ * opted into via `homegraph.json` `includeIgnored`. These are real repos the
611
+ * default `init`/`index` deliberately skips because `.gitignore` excludes them
612
+ * (#970, #976) — most visibly the "super-repo `.gitignore`s its child repos"
613
+ * layout (#1156), where `init` at the parent correctly indexes ~nothing while
614
+ * `init` inside each child works. The CLI uses this to turn that silent empty
615
+ * index into an actionable hint: it names the skipped repos and offers to opt
616
+ * them in. Paths are `rootDir`-relative and trailing-slashed (valid
617
+ * `includeIgnored` patterns as-is). Returns `[]` for a non-git root (a
618
+ * filesystem walk already descends into nested repos there), skips built-in
619
+ * default-ignored dirs (`node_modules`, …), and is bounded so it never stalls
620
+ * on a giant ignored tree.
621
+ */
622
+ function findUnindexedIgnoredRepos(rootDir) {
623
+ try {
624
+ (0, child_process_1.execFileSync)('git', ['rev-parse', '--git-dir'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
625
+ }
626
+ catch {
627
+ return [];
628
+ }
629
+ const defaults = (0, default_ignore_1.defaultsOnlyIgnore)();
630
+ const includeIgnored = loadIncludeIgnoredMatcher(rootDir);
631
+ const repos = [];
632
+ for (const dir of listIgnoredDirs(rootDir)) {
633
+ if (defaults.ignores(dir))
634
+ continue; // node_modules etc. — never project code
635
+ if (includeIgnored?.ignores((0, utils_1.normalizePath)(dir)))
636
+ continue; // already opted in — nothing to nag about
637
+ for (const repo of findNestedGitRepos(path.join(rootDir, dir), dir)) {
638
+ repos.push(repo);
639
+ if (repos.length >= UNINDEXED_IGNORED_REPO_HINT_CAP)
640
+ return repos;
641
+ }
642
+ }
643
+ return repos;
644
+ }
583
645
  /**
584
646
  * Discover embedded repos hidden by `repoDir`'s OWN gitignore rules: for each
585
647
  * gitignored directory, search for nested `.git` roots. Returns repo paths
@@ -598,7 +660,7 @@ function discoverEmbeddedRepoRoots(rootDir) {
598
660
  function findIgnoredEmbeddedRepos(repoDir, includeIgnored, prefix) {
599
661
  if (!includeIgnored)
600
662
  return [];
601
- const defaults = defaultsOnlyIgnore();
663
+ const defaults = (0, default_ignore_1.defaultsOnlyIgnore)();
602
664
  const repos = [];
603
665
  for (const dir of listIgnoredDirs(repoDir)) {
604
666
  if (defaults.ignores(dir))
@@ -710,7 +772,7 @@ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored =
710
772
  const childDir = path.join(repoDir, rel);
711
773
  // A git worktree surfaces here as an opaque untracked dir too — skip it,
712
774
  // it's a duplicate working view of an already-indexed repo (#848).
713
- if (classifyGitDir(childDir) === 'embedded' && !defaultsOnlyIgnore().ignores(rel)) {
775
+ if (classifyGitDir(childDir) === 'embedded' && !(0, default_ignore_1.defaultsOnlyIgnore)().ignores(rel)) {
714
776
  embeddedRoots?.add((0, utils_1.normalizePath)(prefix + rel));
715
777
  collectGitFiles(childDir, prefix + rel, files, embeddedRoots, includeIgnored);
716
778
  }
@@ -727,8 +789,8 @@ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored =
727
789
  // (an uninitialized submodule — empty dir, no `.git`) has nothing to index and
728
790
  // is left alone, as is a submodule worktree (a duplicate view, #945). (#1031, #1033)
729
791
  if (gitlinkRels.length > 0) {
730
- const defaults = defaultsOnlyIgnore();
731
- const repoIgnore = buildDefaultIgnore(repoDir);
792
+ const defaults = (0, default_ignore_1.defaultsOnlyIgnore)();
793
+ const repoIgnore = (0, default_ignore_1.buildDefaultIgnore)(repoDir);
732
794
  for (const rel of gitlinkRels) {
733
795
  const relDir = rel.endsWith('/') ? rel : rel + '/';
734
796
  // A gitlink under a gitignored path is respected (not indexed) unless the
@@ -787,7 +849,11 @@ function getGitVisibleFiles(rootDir) {
787
849
  // not the parent's: the parent's .gitignore hides the child repo from git,
788
850
  // not from the index. (#514)
789
851
  const ig = buildScopeIgnore(rootDir, embeddedRoots);
790
- return new Set([...files].filter((f) => !ig.ignores(f)));
852
+ const visible = new Set([...files].filter((f) => !ig.ignores(f)));
853
+ // Force-include first-party source whitelisted in `homegraph.json` `include`.
854
+ for (const f of collectIncludedFilesForRoot(rootDir))
855
+ visible.add(f);
856
+ return visible;
791
857
  }
792
858
  catch {
793
859
  return null;
@@ -832,7 +898,7 @@ function collectGitStatus(repoDir, prefix, out, overrides, includeIgnored = null
832
898
  // repo-relative `rel` at each recursion level mirrors getGitVisibleFiles'
833
899
  // ScopeIgnore: every embedded repo is judged by ITS OWN rules, never the
834
900
  // parent's. (#766)
835
- const ig = buildDefaultIgnore(repoDir);
901
+ const ig = (0, default_ignore_1.buildDefaultIgnore)(repoDir);
836
902
  const untrackedDirs = [];
837
903
  for (const line of output.split('\n')) {
838
904
  if (line.length < 4)
@@ -955,7 +1021,7 @@ function scanDirectoryWalk(rootDir, onProgress) {
955
1021
  // readGitignorePatterns is defensive: a non-UTF-8 (DLP-encrypted) or
956
1022
  // uncompilable .gitignore is skipped/filtered with a warning, never thrown
957
1023
  // (issue #682) — so the per-file `.ignores()` calls below can't crash.
958
- const patterns = readGitignorePatterns(giPath);
1024
+ const patterns = (0, default_ignore_1.readGitignorePatterns)(giPath);
959
1025
  return patterns ? { dir, ig: (0, ignore_1.default)().add(patterns) } : null;
960
1026
  };
961
1027
  const isIgnored = (fullPath, isDir, matchers) => {
@@ -1042,7 +1108,7 @@ function scanDirectoryWalk(rootDir, onProgress) {
1042
1108
  }
1043
1109
  // Seed a base matcher with the built-in default ignores (merged with the root
1044
1110
  // .gitignore so a negation can override). Nested .gitignores still layer per-dir.
1045
- const baseMatchers = [{ dir: rootDir, ig: buildDefaultIgnore(rootDir) }];
1111
+ const baseMatchers = [{ dir: rootDir, ig: (0, default_ignore_1.buildDefaultIgnore)(rootDir) }];
1046
1112
  // Project `codegraph.json` `exclude` patterns (#999), rooted at the project so
1047
1113
  // `isIgnored` matches them against root-relative paths — same coverage the
1048
1114
  // git path gets via ScopeIgnore, for non-git projects.
@@ -1050,8 +1116,52 @@ function scanDirectoryWalk(rootDir, onProgress) {
1050
1116
  if (exclude)
1051
1117
  baseMatchers.push({ dir: rootDir, ig: exclude });
1052
1118
  walk(rootDir, baseMatchers);
1119
+ // Force-include first-party source whitelisted in `homegraph.json` `include`.
1120
+ const included = collectIncludedFilesForRoot(rootDir);
1121
+ if (included.size > 0) {
1122
+ const seen = new Set(files);
1123
+ for (const f of included) {
1124
+ if (!seen.has(f)) {
1125
+ files.push(f);
1126
+ seen.add(f);
1127
+ }
1128
+ }
1129
+ }
1053
1130
  return files;
1054
1131
  }
1132
+ /**
1133
+ * Extraction orchestrator
1134
+ */
1135
+ /**
1136
+ * Resurrect a resolution edge that is about to be dropped (its target symbol
1137
+ * was removed, renamed, or its whole file deleted) as the ORIGINAL unresolved
1138
+ * reference that created it, read from the refName/refKind stamp
1139
+ * `createEdges` writes into edge metadata. Inserted as status='pending', the
1140
+ * ref is consumed by the same sync's resolution sweep: it rebinds to an
1141
+ * alternative definition if one exists, or parks as status='failed' where the
1142
+ * #1240 retry finds it if the symbol later reappears.
1143
+ *
1144
+ * Returns null — drop silently, the pre-#1240 behavior — for edges without a
1145
+ * refName stamp (created before the stamp existed, or synthesized): rebuilding
1146
+ * a ref from the target's plain node name would strip the receiver/qualifier
1147
+ * context the original text carried (`h.greet` → `greet`) and could rebind
1148
+ * somewhere a full re-index never would. Silent beats wrong.
1149
+ */
1150
+ function resurrectRefFromDroppedEdge(e) {
1151
+ const refName = e.metadata?.refName;
1152
+ if (typeof refName !== 'string' || refName.length === 0)
1153
+ return null;
1154
+ const refKind = typeof e.metadata?.refKind === 'string' ? e.metadata.refKind : e.kind;
1155
+ return {
1156
+ fromNodeId: e.source,
1157
+ referenceName: refName,
1158
+ referenceKind: refKind,
1159
+ line: e.line ?? 0,
1160
+ column: e.column ?? 0,
1161
+ filePath: e.sourceFilePath,
1162
+ language: e.sourceLanguage,
1163
+ };
1164
+ }
1055
1165
  /**
1056
1166
  * Extraction orchestrator
1057
1167
  */
@@ -1144,7 +1254,7 @@ class ExtractionOrchestrator {
1144
1254
  /**
1145
1255
  * Index all files in the project
1146
1256
  */
1147
- async indexAll(onProgress, signal, verbose) {
1257
+ async indexAll(onProgress, signal, verbose, walBackpressure, storeWriterOpts) {
1148
1258
  await (0, grammars_1.initGrammars)();
1149
1259
  const startTime = Date.now();
1150
1260
  const errors = [];
@@ -1246,6 +1356,15 @@ class ExtractionOrchestrator {
1246
1356
  // In-process fallback: load grammars locally and parse on the main thread.
1247
1357
  await (0, grammars_1.loadGrammarsForLanguages)(neededLanguages);
1248
1358
  }
1359
+ const storeWorkerPath = path.join(__dirname, 'store-worker.js');
1360
+ let storeWriter = null;
1361
+ if (storeWriterOpts &&
1362
+ process.env.HOMEGRAPH_NO_STORE_WORKER !== '1' &&
1363
+ fs.existsSync(storeWorkerPath)) {
1364
+ storeWriter = new store_writer_1.StoreWriter(storeWorkerPath, storeWriterOpts.dbPath, storeWriterOpts.fastInit);
1365
+ log('Store writer thread active');
1366
+ }
1367
+ const STORE_WRITER_WINDOW = 64;
1249
1368
  /**
1250
1369
  * Parse one file: on the pool when available (the promise REJECTS on a worker
1251
1370
  * crash/timeout — the caller records it and the retry pass re-attempts), or
@@ -1275,12 +1394,21 @@ class ExtractionOrchestrator {
1275
1394
  let nextSeq = 0; // file-order sequence assigned at dispatch
1276
1395
  let nextToStore = 0; // cursor: next sequence to commit
1277
1396
  let aborted = false;
1278
- const storeResult = (filePath, content, stats, result) => {
1397
+ const commitYield = (0, cooperative_yield_1.createYielder)();
1398
+ const storeResult = async (filePath, content, stats, result) => {
1279
1399
  processed++;
1280
- // Store in database on main thread (SQLite is not thread-safe)
1400
+ const bp = walBackpressure?.();
1401
+ if (bp)
1402
+ await bp;
1281
1403
  if (result.nodes.length > 0 || result.errors.length === 0) {
1282
1404
  const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1283
- this.storeExtractionResult(filePath, content, language, stats, result);
1405
+ if (storeWriter) {
1406
+ storeWriter.send(this.buildFreshStoreBundle(filePath, content, language, stats, result));
1407
+ await storeWriter.waitBelow(STORE_WRITER_WINDOW);
1408
+ }
1409
+ else {
1410
+ await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
1411
+ }
1284
1412
  }
1285
1413
  if (result.errors.length > 0) {
1286
1414
  for (const err of result.errors) {
@@ -1298,9 +1426,6 @@ class ExtractionOrchestrator {
1298
1426
  filesErrored++;
1299
1427
  }
1300
1428
  else {
1301
- // Files with no symbols but no errors (yaml, twig, properties) are
1302
- // tracked at the file level — count them as indexed so the CLI doesn't
1303
- // misleadingly report "No files found to index".
1304
1429
  const lang = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1305
1430
  if ((0, grammars_1.isFileLevelOnlyLanguage)(lang)) {
1306
1431
  filesIndexed++;
@@ -1322,22 +1447,28 @@ class ExtractionOrchestrator {
1322
1447
  });
1323
1448
  onProgress?.({ phase: 'parsing', current: processed, total });
1324
1449
  };
1325
- // Commit buffered parses to the DB in file order, advancing the cursor over
1326
- // contiguous completed results. Runs after each parse settles (and once more
1327
- // after the drain). storeResult / recordParseFailure run here single-threaded,
1328
- // so shared counters and SQLite writes never race despite parallel parsing.
1450
+ let flushChain = Promise.resolve();
1451
+ let flushError = null;
1329
1452
  const flushOrdered = () => {
1330
- if (aborted)
1331
- return;
1332
- while (completed.has(nextToStore)) {
1333
- const item = completed.get(nextToStore);
1334
- completed.delete(nextToStore);
1335
- nextToStore++;
1336
- if (item.ok)
1337
- storeResult(item.filePath, item.content, item.stats, item.result);
1338
- else
1339
- recordParseFailure(item.filePath, item.err);
1340
- }
1453
+ flushChain = flushChain.then(async () => {
1454
+ if (aborted || flushError)
1455
+ return;
1456
+ try {
1457
+ while (completed.has(nextToStore)) {
1458
+ const item = completed.get(nextToStore);
1459
+ completed.delete(nextToStore);
1460
+ nextToStore++;
1461
+ if (item.ok)
1462
+ await storeResult(item.filePath, item.content, item.stats, item.result);
1463
+ else
1464
+ recordParseFailure(item.filePath, item.err);
1465
+ }
1466
+ }
1467
+ catch (err) {
1468
+ flushError = err;
1469
+ }
1470
+ });
1471
+ return flushChain;
1341
1472
  };
1342
1473
  // Dispatch one file's parse (parses run concurrently across the pool), tagged
1343
1474
  // with its file-order sequence so flushOrdered commits results in order. The
@@ -1360,10 +1491,15 @@ class ExtractionOrchestrator {
1360
1491
  // buffered), not just in-flight: a slow file sitting at the commit cursor
1361
1492
  // lets later parses finish and buffer, which would otherwise grow without
1362
1493
  // bound. Wait for parses to settle (each may advance the cursor) until the
1363
- // window has room. `inFlight.size > 0` guards against an empty race — the
1364
- // cursor file is always still in flight when the window is full.
1365
- while (nextSeq - nextToStore >= windowSize && inFlight.size > 0) {
1366
- await Promise.race(inFlight);
1494
+ // window has room. When nothing is in flight but the window is still full,
1495
+ // the async commit chain is what's behind — await it so the cursor
1496
+ // advances (buffered items hold whole file contents, so this bound is
1497
+ // load-bearing for memory).
1498
+ while (nextSeq - nextToStore >= windowSize) {
1499
+ if (inFlight.size > 0)
1500
+ await Promise.race(inFlight);
1501
+ else
1502
+ await flushOrdered();
1367
1503
  }
1368
1504
  };
1369
1505
  const firstEtsFile = files.find((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides) === 'arkts');
@@ -1468,9 +1604,25 @@ class ExtractionOrchestrator {
1468
1604
  // then commit any results the cursor hasn't reached yet.
1469
1605
  if (!aborted) {
1470
1606
  await Promise.all(inFlight);
1471
- flushOrdered();
1607
+ await flushOrdered();
1608
+ if (flushError) {
1609
+ if (storeWriter)
1610
+ await storeWriter.close();
1611
+ throw flushError;
1612
+ }
1613
+ if (storeWriter) {
1614
+ try {
1615
+ await storeWriter.drain();
1616
+ }
1617
+ finally {
1618
+ await storeWriter.close();
1619
+ storeWriter = null;
1620
+ }
1621
+ }
1472
1622
  }
1473
1623
  if (signal?.aborted || aborted) {
1624
+ if (storeWriter)
1625
+ await storeWriter.close();
1474
1626
  if (pool)
1475
1627
  await pool.destroy();
1476
1628
  return {
@@ -1478,6 +1630,7 @@ class ExtractionOrchestrator {
1478
1630
  filesIndexed,
1479
1631
  filesSkipped,
1480
1632
  filesErrored,
1633
+ filesDiscovered: total,
1481
1634
  nodesCreated: totalNodes,
1482
1635
  edgesCreated: totalEdges,
1483
1636
  errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
@@ -1531,7 +1684,7 @@ class ExtractionOrchestrator {
1531
1684
  if (result.nodes.length > 0 || result.errors.length === 0) {
1532
1685
  const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1533
1686
  const stats = await fsp.stat(path.join(this.rootDir, filePath));
1534
- this.storeExtractionResult(filePath, content, language, stats, result);
1687
+ await this.storeExtractionResult(filePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
1535
1688
  const idx = errors.indexOf(errEntry);
1536
1689
  if (idx >= 0)
1537
1690
  errors.splice(idx, 1);
@@ -1579,7 +1732,7 @@ class ExtractionOrchestrator {
1579
1732
  if (result.nodes.length > 0 || result.errors.length === 0) {
1580
1733
  const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
1581
1734
  const stats = await fsp.stat(path.join(this.rootDir, filePath));
1582
- this.storeExtractionResult(filePath, fullContent, language, stats, result);
1735
+ await this.storeExtractionResult(filePath, fullContent, language, stats, result, (0, cooperative_yield_1.createYielder)());
1583
1736
  const idx = errors.indexOf(errEntry);
1584
1737
  if (idx >= 0)
1585
1738
  errors.splice(idx, 1);
@@ -1600,6 +1753,7 @@ class ExtractionOrchestrator {
1600
1753
  filesIndexed,
1601
1754
  filesSkipped,
1602
1755
  filesErrored,
1756
+ filesDiscovered: total,
1603
1757
  nodesCreated: totalNodes,
1604
1758
  edgesCreated: totalEdges,
1605
1759
  errors,
@@ -1756,99 +1910,77 @@ class ExtractionOrchestrator {
1756
1910
  const result = (0, tree_sitter_1.extractFromSource)(relativePath, content, language, frameworkNames);
1757
1911
  // Store in database
1758
1912
  if (result.nodes.length > 0 || result.errors.length === 0) {
1759
- this.storeExtractionResult(relativePath, content, language, stats, result);
1913
+ await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
1760
1914
  }
1761
1915
  return result;
1762
1916
  }
1763
1917
  /**
1764
1918
  * Store extraction result in database
1765
1919
  */
1766
- storeExtractionResult(filePath, content, language, stats, result) {
1920
+ async storeExtractionResult(filePath, content, language, stats, result, onYield) {
1921
+ const STORE_CHUNK = 2000;
1767
1922
  const contentHash = hashContent(content);
1768
- // Check if file already exists and hasn't changed
1769
1923
  const existingFile = this.queries.getFileByPath(filePath);
1770
1924
  if (existingFile && existingFile.contentHash === contentHash) {
1771
- return; // No changes
1772
- }
1773
- // Snapshot incoming cross-file edges BEFORE deleting this file's nodes.
1774
- // `deleteFile` cascades to delete every edge whose source OR target is a
1775
- // node in this file (edges.FK ... ON DELETE CASCADE). Edges whose SOURCE is
1776
- // in this file are re-emitted by the extractor below, but edges whose SOURCE
1777
- // is in a *different* (unchanged) file are not — they would be silently
1778
- // dropped, which is issue #899: re-indexing a callee file severs `calls`/
1779
- // `references` edges from callers that import it via module-attribute
1780
- // access (`pkg.mod.fn(...)`).
1781
- //
1782
- // We snapshot the edge plus the target node's (name, kind) so we can
1783
- // re-resolve to the re-indexed target's NEW id. Node ids are
1784
- // `sha256(filePath:kind:name:line)`, so any line shift in the callee file
1785
- // (e.g. a docstring-only edit above the symbol) changes every target id and
1786
- // a naive re-insert by old id would silently drop every edge. Matching by
1787
- // (filePath, kind, name) is stable across line shifts; if the symbol was
1788
- // renamed/removed, no match is found and the edge stays dropped (correct).
1925
+ return;
1926
+ }
1789
1927
  const crossFileIncomingEdges = existingFile
1790
1928
  ? this.queries.getCrossFileIncomingEdgesWithTarget(filePath)
1791
1929
  : [];
1792
- // Delete existing data for this file
1793
1930
  if (existingFile) {
1794
1931
  this.queries.deleteFile(filePath);
1795
1932
  }
1796
- // Filter out nodes with missing required fields before insertion.
1797
- // This prevents FK violations when edges reference nodes that would
1798
- // be silently skipped by insertNode() (see issue #42).
1799
1933
  const validNodes = result.nodes.filter((n) => n.id && n.kind && n.name && n.filePath && n.language);
1800
- // Insert nodes
1801
- if (validNodes.length > 0) {
1802
- this.queries.insertNodes(validNodes);
1803
- }
1804
- // Filter edges to only reference nodes that were actually inserted
1805
- if (result.edges.length > 0) {
1806
- const insertedIds = new Set(validNodes.map((n) => n.id));
1807
- const validEdges = result.edges.filter((e) => insertedIds.has(e.source) && insertedIds.has(e.target));
1808
- if (validEdges.length > 0) {
1809
- this.queries.insertEdges(validEdges);
1934
+ const insertedIds = new Set(validNodes.map((n) => n.id));
1935
+ const validEdges = result.edges.filter((e) => insertedIds.has(e.source) && insertedIds.has(e.target));
1936
+ const validRefs = result.unresolvedReferences
1937
+ .filter((ref) => insertedIds.has(ref.fromNodeId))
1938
+ .map((ref) => ({
1939
+ ...ref,
1940
+ filePath: ref.filePath ?? filePath,
1941
+ language: ref.language ?? language,
1942
+ }));
1943
+ const fitsOneChunk = validNodes.length <= STORE_CHUNK &&
1944
+ validEdges.length <= STORE_CHUNK &&
1945
+ validRefs.length <= STORE_CHUNK;
1946
+ if (fitsOneChunk) {
1947
+ this.queries.storeFileBundle({
1948
+ nodes: validNodes,
1949
+ edges: validEdges,
1950
+ refs: validRefs,
1951
+ file: {
1952
+ path: filePath,
1953
+ contentHash,
1954
+ language,
1955
+ size: stats.size,
1956
+ modifiedAt: stats.mtimeMs,
1957
+ indexedAt: Date.now(),
1958
+ nodeCount: result.nodes.length,
1959
+ errors: result.errors.length > 0 ? result.errors : undefined,
1960
+ },
1961
+ });
1962
+ if (crossFileIncomingEdges.length > 0) {
1963
+ this.reattachCrossFileEdges(crossFileIncomingEdges, validNodes);
1810
1964
  }
1965
+ return;
1811
1966
  }
1812
- // Re-insert cross-file incoming edges snapshotted before the delete,
1813
- // re-resolving each edge's target to the re-indexed node's new id by
1814
- // (filePath, kind, name). Node ids include the source line, so any line
1815
- // shift in the callee file (e.g. a docstring-only edit above the symbol)
1816
- // changes every target id and a naive re-insert by old id would drop them
1817
- // all. `insertEdges` still filters to endpoints that exist, so edges whose
1818
- // caller (source) was deleted, or whose callee (target) was renamed/removed
1819
- // during the re-index (no match in `newTargetIds`), are dropped. This
1820
- // closes the #899 edge-drop on `sync`.
1821
- if (crossFileIncomingEdges.length > 0) {
1822
- const newNodesByKindName = new Map();
1823
- for (const n of validNodes) {
1824
- newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
1825
- }
1826
- const reinserted = [];
1827
- for (const e of crossFileIncomingEdges) {
1828
- const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
1829
- if (newTargetId) {
1830
- reinserted.push({ source: e.source, target: newTargetId, kind: e.kind, metadata: e.metadata, line: e.line, column: e.column, provenance: e.provenance });
1831
- }
1832
- }
1833
- if (reinserted.length > 0) {
1834
- this.queries.insertEdges(reinserted);
1835
- }
1967
+ for (let i = 0; i < validNodes.length; i += STORE_CHUNK) {
1968
+ this.queries.insertNodes(validNodes.slice(i, i + STORE_CHUNK));
1969
+ await onYield?.();
1836
1970
  }
1837
- // Insert unresolved references in batch with denormalized filePath/language
1838
- if (result.unresolvedReferences.length > 0) {
1839
- const insertedIds = new Set(validNodes.map((n) => n.id));
1840
- const refsWithContext = result.unresolvedReferences
1841
- .filter((ref) => insertedIds.has(ref.fromNodeId))
1842
- .map((ref) => ({
1843
- ...ref,
1844
- filePath: ref.filePath ?? filePath,
1845
- language: ref.language ?? language,
1846
- }));
1847
- if (refsWithContext.length > 0) {
1848
- this.queries.insertUnresolvedRefsBatch(refsWithContext);
1971
+ if (validEdges.length > 0) {
1972
+ for (let i = 0; i < validEdges.length; i += STORE_CHUNK) {
1973
+ this.queries.insertEdges(validEdges.slice(i, i + STORE_CHUNK));
1974
+ await onYield?.();
1849
1975
  }
1850
1976
  }
1851
- // Insert file record
1977
+ if (crossFileIncomingEdges.length > 0) {
1978
+ this.reattachCrossFileEdges(crossFileIncomingEdges, validNodes);
1979
+ }
1980
+ for (let i = 0; i < validRefs.length; i += STORE_CHUNK) {
1981
+ this.queries.insertUnresolvedRefsBatch(validRefs.slice(i, i + STORE_CHUNK));
1982
+ await onYield?.();
1983
+ }
1852
1984
  const fileRecord = {
1853
1985
  path: filePath,
1854
1986
  contentHash,
@@ -1861,6 +1993,46 @@ class ExtractionOrchestrator {
1861
1993
  };
1862
1994
  this.queries.upsertFile(fileRecord);
1863
1995
  }
1996
+ buildFileRecord(filePath, content, language, stats, nodeCount, resultErrors) {
1997
+ return {
1998
+ path: filePath,
1999
+ contentHash: hashContent(content),
2000
+ language,
2001
+ size: stats.size,
2002
+ modifiedAt: stats.mtimeMs,
2003
+ indexedAt: Date.now(),
2004
+ nodeCount,
2005
+ errors: resultErrors.length > 0 ? resultErrors : undefined,
2006
+ };
2007
+ }
2008
+ buildFreshStoreBundle(filePath, content, language, stats, result) {
2009
+ return (0, store_writer_1.finalizeStoreBundle)(result, filePath, language, this.buildFileRecord(filePath, content, language, stats, result.nodes.length, result.errors));
2010
+ }
2011
+ reattachCrossFileEdges(crossFileIncomingEdges, validNodes) {
2012
+ const newNodesByKindName = new Map();
2013
+ for (const n of validNodes) {
2014
+ newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
2015
+ }
2016
+ const reinserted = [];
2017
+ const resurrected = [];
2018
+ for (const e of crossFileIncomingEdges) {
2019
+ const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
2020
+ if (newTargetId) {
2021
+ reinserted.push({ source: e.source, target: newTargetId, kind: e.kind, metadata: e.metadata, line: e.line, column: e.column, provenance: e.provenance });
2022
+ }
2023
+ else {
2024
+ const ref = resurrectRefFromDroppedEdge(e);
2025
+ if (ref)
2026
+ resurrected.push(ref);
2027
+ }
2028
+ }
2029
+ if (reinserted.length > 0) {
2030
+ this.queries.insertEdges(reinserted);
2031
+ }
2032
+ if (resurrected.length > 0) {
2033
+ this.queries.insertUnresolvedRefsBatch(resurrected);
2034
+ }
2035
+ }
1864
2036
  /**
1865
2037
  * Sync the index with the current file state.
1866
2038
  *
@@ -1869,7 +2041,7 @@ class ExtractionOrchestrator {
1869
2041
  * changes. This works in non-git projects and catches committed changes from
1870
2042
  * `git pull`/`checkout`/`merge`/`rebase` that `git status` cannot see.
1871
2043
  */
1872
- async sync(onProgress) {
2044
+ async sync(onProgress, scopedPaths) {
1873
2045
  await (0, grammars_1.initGrammars)(); // Initialize WASM runtime (grammars loaded lazily below)
1874
2046
  const startTime = Date.now();
1875
2047
  let filesChecked = 0;
@@ -1884,31 +2056,48 @@ class ExtractionOrchestrator {
1884
2056
  total: 0,
1885
2057
  });
1886
2058
  const filesToIndex = [];
1887
- // === Filesystem reconcile (git-independent) ===
1888
- // The source of truth for "what changed" is the filesystem vs the indexed
1889
- // state — never git. We enumerate the current source files and reconcile
1890
- // each against the DB. A cheap (size, mtime) stat pre-filter skips unchanged
1891
- // files without reading or hashing them, so the expensive read+hash+parse
1892
- // only runs for files that actually changed. This catches edits/adds/deletes
1893
- // whether or not the project uses git, and crucially also catches committed
1894
- // changes from `git pull`/`checkout`/`merge`/`rebase` — which `git status`
1895
- // cannot see, because the working tree is clean afterward.
1896
- const currentFiles = await scanDirectoryAsync(this.rootDir);
1897
- filesChecked = currentFiles.length;
2059
+ let currentFiles;
2060
+ let trackedFiles;
2061
+ if (scopedPaths && scopedPaths.length > 0) {
2062
+ const unique = [...new Set(scopedPaths)];
2063
+ currentFiles = unique.filter((p) => fs.existsSync(path.join(this.rootDir, p)));
2064
+ trackedFiles = [];
2065
+ for (const p of unique) {
2066
+ const rec = this.queries.getFileByPath(p);
2067
+ if (rec)
2068
+ trackedFiles.push(rec);
2069
+ }
2070
+ filesChecked = unique.length;
2071
+ }
2072
+ else {
2073
+ currentFiles = await scanDirectoryAsync(this.rootDir);
2074
+ filesChecked = currentFiles.length;
2075
+ trackedFiles = this.queries.getAllFiles();
2076
+ }
1898
2077
  const currentSet = new Set(currentFiles);
1899
- const trackedFiles = this.queries.getAllFiles();
1900
2078
  const trackedMap = new Map();
1901
2079
  for (const f of trackedFiles) {
1902
2080
  trackedMap.set(f.path, f);
1903
2081
  }
1904
- // Removals: tracked in the DB but no longer a present source file. Check the
1905
- // filesystem directly — `scanDirectory` (via `git ls-files`) still lists a
1906
- // file deleted from disk but not yet staged, so set membership alone misses it.
1907
- // `reconcileChecks` drives the cooperative yield shared with the adds/mods loop
1908
- // below (see SYNC_RECONCILE_YIELD_INTERVAL / issue #905).
2082
+ const removedArkAnalyzerFiles = [];
1909
2083
  let reconcileChecks = 0;
1910
2084
  for (const tracked of trackedFiles) {
1911
2085
  if (!currentSet.has(tracked.path) || !fs.existsSync(path.join(this.rootDir, tracked.path))) {
2086
+ if (tracked.path.startsWith('@')) {
2087
+ continue;
2088
+ }
2089
+ const incoming = this.queries.getCrossFileIncomingEdgesWithTarget(tracked.path);
2090
+ if (incoming.length > 0) {
2091
+ const resurrected = incoming
2092
+ .map((e) => resurrectRefFromDroppedEdge(e))
2093
+ .filter((r) => r !== null);
2094
+ if (resurrected.length > 0) {
2095
+ this.queries.insertUnresolvedRefsBatch(resurrected);
2096
+ }
2097
+ }
2098
+ if ((0, arkts_1.isArkAnalyzerSourcePath)(tracked.path)) {
2099
+ removedArkAnalyzerFiles.push(tracked.path);
2100
+ }
1912
2101
  this.queries.deleteFile(tracked.path);
1913
2102
  filesRemoved++;
1914
2103
  }
@@ -1918,10 +2107,12 @@ class ExtractionOrchestrator {
1918
2107
  }
1919
2108
  // Adds / modifications.
1920
2109
  for (const filePath of currentFiles) {
1921
- // Same cooperative yield as the removals loop — this is the other O(files)
1922
- // synchronous-stat loop that wedges the main thread on a large repo (#905).
1923
- // Yield at the top of the body so the `continue` fast-paths below still hit it.
1924
2110
  if (++reconcileChecks % SYNC_RECONCILE_YIELD_INTERVAL === 0) {
2111
+ onProgress?.({
2112
+ phase: 'scanning',
2113
+ current: reconcileChecks,
2114
+ total: currentFiles.length,
2115
+ });
1925
2116
  await new Promise((resolve) => setImmediate(resolve));
1926
2117
  }
1927
2118
  const fullPath = path.join(this.rootDir, filePath);
@@ -1978,7 +2169,19 @@ class ExtractionOrchestrator {
1978
2169
  const total = filesToIndex.length;
1979
2170
  const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
1980
2171
  const needsArkTS = filesToIndex.some((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides) === 'arkts');
1981
- if (needsArkTS) {
2172
+ const arktsChangedForBatch = [
2173
+ ...removedArkAnalyzerFiles,
2174
+ ...filesToIndex.filter((f) => (0, arkts_1.isArkAnalyzerSourcePath)(f) ||
2175
+ f.endsWith('module.json5') ||
2176
+ f.endsWith('oh-package.json5') ||
2177
+ f === 'build-profile.json5' ||
2178
+ f.endsWith('/build-profile.json5')),
2179
+ ];
2180
+ // Removals alone can still dirty a Harmony module (RTA for remaining files).
2181
+ const needsArkTSBatch = needsArkTS ||
2182
+ removedArkAnalyzerFiles.length > 0 ||
2183
+ arktsChangedForBatch.some((f) => f === 'build-profile.json5' || f.endsWith('/build-profile.json5'));
2184
+ if (needsArkTSBatch && arktsChangedForBatch.length > 0) {
1982
2185
  (0, context_1.setArkTSBatchProgressCallback)((batchProgress) => {
1983
2186
  onProgress?.({
1984
2187
  phase: 'arkts-batch',
@@ -1988,8 +2191,12 @@ class ExtractionOrchestrator {
1988
2191
  subphase: batchProgress.subphase,
1989
2192
  });
1990
2193
  });
1991
- const firstEts = filesToIndex.find((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides) === 'arkts');
1992
- await (0, arkts_1.primeArkTSBatch)(this.rootDir, this.queries, firstEts);
2194
+ const firstEts = filesToIndex.find((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides) === 'arkts') ??
2195
+ arktsChangedForBatch.find((f) => (0, arkts_1.isArkAnalyzerSourcePath)(f)) ??
2196
+ arktsChangedForBatch[0];
2197
+ await (0, arkts_1.primeArkTSBatch)(this.rootDir, this.queries, firstEts, {
2198
+ changedFiles: arktsChangedForBatch,
2199
+ });
1993
2200
  }
1994
2201
  try {
1995
2202
  for (let i = 0; i < filesToIndex.length; i++) {
@@ -2014,7 +2221,7 @@ class ExtractionOrchestrator {
2014
2221
  }
2015
2222
  }
2016
2223
  finally {
2017
- if (needsArkTS) {
2224
+ if (needsArkTSBatch) {
2018
2225
  (0, context_1.setArkTSBatchProgressCallback)(null);
2019
2226
  }
2020
2227
  }