@bharanidev/bhgraph-darwin-arm64 1.0.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 (842) hide show
  1. package/bin/bhgraph +19 -0
  2. package/lib/dist/bin/bhgraph.d.ts +26 -0
  3. package/lib/dist/bin/bhgraph.d.ts.map +1 -0
  4. package/lib/dist/bin/bhgraph.js +2207 -0
  5. package/lib/dist/bin/bhgraph.js.map +1 -0
  6. package/lib/dist/bin/command-supervision.d.ts +12 -0
  7. package/lib/dist/bin/command-supervision.d.ts.map +1 -0
  8. package/lib/dist/bin/command-supervision.js +89 -0
  9. package/lib/dist/bin/command-supervision.js.map +1 -0
  10. package/lib/dist/bin/fatal-handler.d.ts +20 -0
  11. package/lib/dist/bin/fatal-handler.d.ts.map +1 -0
  12. package/lib/dist/bin/fatal-handler.js +118 -0
  13. package/lib/dist/bin/fatal-handler.js.map +1 -0
  14. package/lib/dist/bin/node-version-check.d.ts +37 -0
  15. package/lib/dist/bin/node-version-check.d.ts.map +1 -0
  16. package/lib/dist/bin/node-version-check.js +79 -0
  17. package/lib/dist/bin/node-version-check.js.map +1 -0
  18. package/lib/dist/bin/uninstall.d.ts +14 -0
  19. package/lib/dist/bin/uninstall.d.ts.map +1 -0
  20. package/lib/dist/bin/uninstall.js +36 -0
  21. package/lib/dist/bin/uninstall.js.map +1 -0
  22. package/lib/dist/context/formatter.d.ts +30 -0
  23. package/lib/dist/context/formatter.d.ts.map +1 -0
  24. package/lib/dist/context/formatter.js +263 -0
  25. package/lib/dist/context/formatter.js.map +1 -0
  26. package/lib/dist/context/index.d.ts +119 -0
  27. package/lib/dist/context/index.d.ts.map +1 -0
  28. package/lib/dist/context/index.js +1296 -0
  29. package/lib/dist/context/index.js.map +1 -0
  30. package/lib/dist/context/markers.d.ts +19 -0
  31. package/lib/dist/context/markers.d.ts.map +1 -0
  32. package/lib/dist/context/markers.js +22 -0
  33. package/lib/dist/context/markers.js.map +1 -0
  34. package/lib/dist/db/index.d.ts +142 -0
  35. package/lib/dist/db/index.d.ts.map +1 -0
  36. package/lib/dist/db/index.js +335 -0
  37. package/lib/dist/db/index.js.map +1 -0
  38. package/lib/dist/db/migrations.d.ts +44 -0
  39. package/lib/dist/db/migrations.d.ts.map +1 -0
  40. package/lib/dist/db/migrations.js +182 -0
  41. package/lib/dist/db/migrations.js.map +1 -0
  42. package/lib/dist/db/queries.d.ts +452 -0
  43. package/lib/dist/db/queries.d.ts.map +1 -0
  44. package/lib/dist/db/queries.js +1740 -0
  45. package/lib/dist/db/queries.js.map +1 -0
  46. package/lib/dist/db/schema.sql +182 -0
  47. package/lib/dist/db/sqlite-adapter.d.ts +53 -0
  48. package/lib/dist/db/sqlite-adapter.d.ts.map +1 -0
  49. package/lib/dist/db/sqlite-adapter.js +117 -0
  50. package/lib/dist/db/sqlite-adapter.js.map +1 -0
  51. package/lib/dist/directory.d.ts +183 -0
  52. package/lib/dist/directory.d.ts.map +1 -0
  53. package/lib/dist/directory.js +797 -0
  54. package/lib/dist/directory.js.map +1 -0
  55. package/lib/dist/errors.d.ts +136 -0
  56. package/lib/dist/errors.d.ts.map +1 -0
  57. package/lib/dist/errors.js +219 -0
  58. package/lib/dist/errors.js.map +1 -0
  59. package/lib/dist/extraction/astro-extractor.d.ts +79 -0
  60. package/lib/dist/extraction/astro-extractor.d.ts.map +1 -0
  61. package/lib/dist/extraction/astro-extractor.js +320 -0
  62. package/lib/dist/extraction/astro-extractor.js.map +1 -0
  63. package/lib/dist/extraction/cfml-extractor.d.ts +107 -0
  64. package/lib/dist/extraction/cfml-extractor.d.ts.map +1 -0
  65. package/lib/dist/extraction/cfml-extractor.js +494 -0
  66. package/lib/dist/extraction/cfml-extractor.js.map +1 -0
  67. package/lib/dist/extraction/dfm-extractor.d.ts +31 -0
  68. package/lib/dist/extraction/dfm-extractor.d.ts.map +1 -0
  69. package/lib/dist/extraction/dfm-extractor.js +151 -0
  70. package/lib/dist/extraction/dfm-extractor.js.map +1 -0
  71. package/lib/dist/extraction/extraction-version.d.ts +25 -0
  72. package/lib/dist/extraction/extraction-version.d.ts.map +1 -0
  73. package/lib/dist/extraction/extraction-version.js +28 -0
  74. package/lib/dist/extraction/extraction-version.js.map +1 -0
  75. package/lib/dist/extraction/function-ref.d.ts +118 -0
  76. package/lib/dist/extraction/function-ref.d.ts.map +1 -0
  77. package/lib/dist/extraction/function-ref.js +727 -0
  78. package/lib/dist/extraction/function-ref.js.map +1 -0
  79. package/lib/dist/extraction/generated-detection.d.ts +30 -0
  80. package/lib/dist/extraction/generated-detection.d.ts.map +1 -0
  81. package/lib/dist/extraction/generated-detection.js +83 -0
  82. package/lib/dist/extraction/generated-detection.js.map +1 -0
  83. package/lib/dist/extraction/grammars.d.ts +123 -0
  84. package/lib/dist/extraction/grammars.d.ts.map +1 -0
  85. package/lib/dist/extraction/grammars.js +589 -0
  86. package/lib/dist/extraction/grammars.js.map +1 -0
  87. package/lib/dist/extraction/index.d.ts +232 -0
  88. package/lib/dist/extraction/index.d.ts.map +1 -0
  89. package/lib/dist/extraction/index.js +2269 -0
  90. package/lib/dist/extraction/index.js.map +1 -0
  91. package/lib/dist/extraction/languages/arkts.d.ts +3 -0
  92. package/lib/dist/extraction/languages/arkts.d.ts.map +1 -0
  93. package/lib/dist/extraction/languages/arkts.js +127 -0
  94. package/lib/dist/extraction/languages/arkts.js.map +1 -0
  95. package/lib/dist/extraction/languages/c-cpp.d.ts +110 -0
  96. package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -0
  97. package/lib/dist/extraction/languages/c-cpp.js +837 -0
  98. package/lib/dist/extraction/languages/c-cpp.js.map +1 -0
  99. package/lib/dist/extraction/languages/cfquery.d.ts +12 -0
  100. package/lib/dist/extraction/languages/cfquery.d.ts.map +1 -0
  101. package/lib/dist/extraction/languages/cfquery.js +28 -0
  102. package/lib/dist/extraction/languages/cfquery.js.map +1 -0
  103. package/lib/dist/extraction/languages/cfscript.d.ts +3 -0
  104. package/lib/dist/extraction/languages/cfscript.d.ts.map +1 -0
  105. package/lib/dist/extraction/languages/cfscript.js +73 -0
  106. package/lib/dist/extraction/languages/cfscript.js.map +1 -0
  107. package/lib/dist/extraction/languages/cobol.d.ts +33 -0
  108. package/lib/dist/extraction/languages/cobol.d.ts.map +1 -0
  109. package/lib/dist/extraction/languages/cobol.js +499 -0
  110. package/lib/dist/extraction/languages/cobol.js.map +1 -0
  111. package/lib/dist/extraction/languages/csharp.d.ts +25 -0
  112. package/lib/dist/extraction/languages/csharp.d.ts.map +1 -0
  113. package/lib/dist/extraction/languages/csharp.js +175 -0
  114. package/lib/dist/extraction/languages/csharp.js.map +1 -0
  115. package/lib/dist/extraction/languages/dart.d.ts +3 -0
  116. package/lib/dist/extraction/languages/dart.d.ts.map +1 -0
  117. package/lib/dist/extraction/languages/dart.js +374 -0
  118. package/lib/dist/extraction/languages/dart.js.map +1 -0
  119. package/lib/dist/extraction/languages/erlang.d.ts +3 -0
  120. package/lib/dist/extraction/languages/erlang.d.ts.map +1 -0
  121. package/lib/dist/extraction/languages/erlang.js +350 -0
  122. package/lib/dist/extraction/languages/erlang.js.map +1 -0
  123. package/lib/dist/extraction/languages/go.d.ts +3 -0
  124. package/lib/dist/extraction/languages/go.d.ts.map +1 -0
  125. package/lib/dist/extraction/languages/go.js +111 -0
  126. package/lib/dist/extraction/languages/go.js.map +1 -0
  127. package/lib/dist/extraction/languages/index.d.ts +10 -0
  128. package/lib/dist/extraction/languages/index.d.ts.map +1 -0
  129. package/lib/dist/extraction/languages/index.js +71 -0
  130. package/lib/dist/extraction/languages/index.js.map +1 -0
  131. package/lib/dist/extraction/languages/java.d.ts +3 -0
  132. package/lib/dist/extraction/languages/java.d.ts.map +1 -0
  133. package/lib/dist/extraction/languages/java.js +315 -0
  134. package/lib/dist/extraction/languages/java.js.map +1 -0
  135. package/lib/dist/extraction/languages/javascript.d.ts +3 -0
  136. package/lib/dist/extraction/languages/javascript.d.ts.map +1 -0
  137. package/lib/dist/extraction/languages/javascript.js +106 -0
  138. package/lib/dist/extraction/languages/javascript.js.map +1 -0
  139. package/lib/dist/extraction/languages/kotlin.d.ts +3 -0
  140. package/lib/dist/extraction/languages/kotlin.d.ts.map +1 -0
  141. package/lib/dist/extraction/languages/kotlin.js +379 -0
  142. package/lib/dist/extraction/languages/kotlin.js.map +1 -0
  143. package/lib/dist/extraction/languages/lua.d.ts +3 -0
  144. package/lib/dist/extraction/languages/lua.d.ts.map +1 -0
  145. package/lib/dist/extraction/languages/lua.js +150 -0
  146. package/lib/dist/extraction/languages/lua.js.map +1 -0
  147. package/lib/dist/extraction/languages/luau.d.ts +3 -0
  148. package/lib/dist/extraction/languages/luau.d.ts.map +1 -0
  149. package/lib/dist/extraction/languages/luau.js +37 -0
  150. package/lib/dist/extraction/languages/luau.js.map +1 -0
  151. package/lib/dist/extraction/languages/nix.d.ts +3 -0
  152. package/lib/dist/extraction/languages/nix.d.ts.map +1 -0
  153. package/lib/dist/extraction/languages/nix.js +294 -0
  154. package/lib/dist/extraction/languages/nix.js.map +1 -0
  155. package/lib/dist/extraction/languages/objc.d.ts +3 -0
  156. package/lib/dist/extraction/languages/objc.d.ts.map +1 -0
  157. package/lib/dist/extraction/languages/objc.js +175 -0
  158. package/lib/dist/extraction/languages/objc.js.map +1 -0
  159. package/lib/dist/extraction/languages/pascal.d.ts +3 -0
  160. package/lib/dist/extraction/languages/pascal.d.ts.map +1 -0
  161. package/lib/dist/extraction/languages/pascal.js +77 -0
  162. package/lib/dist/extraction/languages/pascal.js.map +1 -0
  163. package/lib/dist/extraction/languages/php.d.ts +3 -0
  164. package/lib/dist/extraction/languages/php.d.ts.map +1 -0
  165. package/lib/dist/extraction/languages/php.js +196 -0
  166. package/lib/dist/extraction/languages/php.js.map +1 -0
  167. package/lib/dist/extraction/languages/python.d.ts +3 -0
  168. package/lib/dist/extraction/languages/python.d.ts.map +1 -0
  169. package/lib/dist/extraction/languages/python.js +56 -0
  170. package/lib/dist/extraction/languages/python.js.map +1 -0
  171. package/lib/dist/extraction/languages/r.d.ts +3 -0
  172. package/lib/dist/extraction/languages/r.d.ts.map +1 -0
  173. package/lib/dist/extraction/languages/r.js +314 -0
  174. package/lib/dist/extraction/languages/r.js.map +1 -0
  175. package/lib/dist/extraction/languages/ruby.d.ts +3 -0
  176. package/lib/dist/extraction/languages/ruby.d.ts.map +1 -0
  177. package/lib/dist/extraction/languages/ruby.js +149 -0
  178. package/lib/dist/extraction/languages/ruby.js.map +1 -0
  179. package/lib/dist/extraction/languages/rust.d.ts +3 -0
  180. package/lib/dist/extraction/languages/rust.d.ts.map +1 -0
  181. package/lib/dist/extraction/languages/rust.js +142 -0
  182. package/lib/dist/extraction/languages/rust.js.map +1 -0
  183. package/lib/dist/extraction/languages/scala.d.ts +3 -0
  184. package/lib/dist/extraction/languages/scala.d.ts.map +1 -0
  185. package/lib/dist/extraction/languages/scala.js +209 -0
  186. package/lib/dist/extraction/languages/scala.js.map +1 -0
  187. package/lib/dist/extraction/languages/solidity.d.ts +3 -0
  188. package/lib/dist/extraction/languages/solidity.d.ts.map +1 -0
  189. package/lib/dist/extraction/languages/solidity.js +293 -0
  190. package/lib/dist/extraction/languages/solidity.js.map +1 -0
  191. package/lib/dist/extraction/languages/swift.d.ts +3 -0
  192. package/lib/dist/extraction/languages/swift.d.ts.map +1 -0
  193. package/lib/dist/extraction/languages/swift.js +152 -0
  194. package/lib/dist/extraction/languages/swift.js.map +1 -0
  195. package/lib/dist/extraction/languages/terraform.d.ts +3 -0
  196. package/lib/dist/extraction/languages/terraform.d.ts.map +1 -0
  197. package/lib/dist/extraction/languages/terraform.js +641 -0
  198. package/lib/dist/extraction/languages/terraform.js.map +1 -0
  199. package/lib/dist/extraction/languages/typescript.d.ts +16 -0
  200. package/lib/dist/extraction/languages/typescript.d.ts.map +1 -0
  201. package/lib/dist/extraction/languages/typescript.js +167 -0
  202. package/lib/dist/extraction/languages/typescript.js.map +1 -0
  203. package/lib/dist/extraction/languages/vbnet.d.ts +11 -0
  204. package/lib/dist/extraction/languages/vbnet.d.ts.map +1 -0
  205. package/lib/dist/extraction/languages/vbnet.js +141 -0
  206. package/lib/dist/extraction/languages/vbnet.js.map +1 -0
  207. package/lib/dist/extraction/liquid-extractor.d.ts +59 -0
  208. package/lib/dist/extraction/liquid-extractor.d.ts.map +1 -0
  209. package/lib/dist/extraction/liquid-extractor.js +357 -0
  210. package/lib/dist/extraction/liquid-extractor.js.map +1 -0
  211. package/lib/dist/extraction/mybatis-extractor.d.ts +68 -0
  212. package/lib/dist/extraction/mybatis-extractor.d.ts.map +1 -0
  213. package/lib/dist/extraction/mybatis-extractor.js +300 -0
  214. package/lib/dist/extraction/mybatis-extractor.js.map +1 -0
  215. package/lib/dist/extraction/parse-pool.d.ts +126 -0
  216. package/lib/dist/extraction/parse-pool.d.ts.map +1 -0
  217. package/lib/dist/extraction/parse-pool.js +319 -0
  218. package/lib/dist/extraction/parse-pool.js.map +1 -0
  219. package/lib/dist/extraction/parse-worker.d.ts +8 -0
  220. package/lib/dist/extraction/parse-worker.d.ts.map +1 -0
  221. package/lib/dist/extraction/parse-worker.js +97 -0
  222. package/lib/dist/extraction/parse-worker.js.map +1 -0
  223. package/lib/dist/extraction/razor-extractor.d.ts +42 -0
  224. package/lib/dist/extraction/razor-extractor.d.ts.map +1 -0
  225. package/lib/dist/extraction/razor-extractor.js +285 -0
  226. package/lib/dist/extraction/razor-extractor.js.map +1 -0
  227. package/lib/dist/extraction/svelte-extractor.d.ts +56 -0
  228. package/lib/dist/extraction/svelte-extractor.d.ts.map +1 -0
  229. package/lib/dist/extraction/svelte-extractor.js +275 -0
  230. package/lib/dist/extraction/svelte-extractor.js.map +1 -0
  231. package/lib/dist/extraction/tree-sitter-helpers.d.ts +28 -0
  232. package/lib/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
  233. package/lib/dist/extraction/tree-sitter-helpers.js +153 -0
  234. package/lib/dist/extraction/tree-sitter-helpers.js.map +1 -0
  235. package/lib/dist/extraction/tree-sitter-types.d.ts +258 -0
  236. package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -0
  237. package/lib/dist/extraction/tree-sitter-types.js +10 -0
  238. package/lib/dist/extraction/tree-sitter-types.js.map +1 -0
  239. package/lib/dist/extraction/tree-sitter.d.ts +706 -0
  240. package/lib/dist/extraction/tree-sitter.d.ts.map +1 -0
  241. package/lib/dist/extraction/tree-sitter.js +6506 -0
  242. package/lib/dist/extraction/tree-sitter.js.map +1 -0
  243. package/lib/dist/extraction/vue-extractor.d.ts +51 -0
  244. package/lib/dist/extraction/vue-extractor.d.ts.map +1 -0
  245. package/lib/dist/extraction/vue-extractor.js +254 -0
  246. package/lib/dist/extraction/vue-extractor.js.map +1 -0
  247. package/lib/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  248. package/lib/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  249. package/lib/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  250. package/lib/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  251. package/lib/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  252. package/lib/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  253. package/lib/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  254. package/lib/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
  255. package/lib/dist/extraction/wasm/tree-sitter-luau.wasm +0 -0
  256. package/lib/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  257. package/lib/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  258. package/lib/dist/extraction/wasm/tree-sitter-r.wasm +0 -0
  259. package/lib/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  260. package/lib/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  261. package/lib/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  262. package/lib/dist/extraction/wasm-runtime-flags.d.ts +38 -0
  263. package/lib/dist/extraction/wasm-runtime-flags.d.ts.map +1 -0
  264. package/lib/dist/extraction/wasm-runtime-flags.js +106 -0
  265. package/lib/dist/extraction/wasm-runtime-flags.js.map +1 -0
  266. package/lib/dist/graph/index.d.ts +8 -0
  267. package/lib/dist/graph/index.d.ts.map +1 -0
  268. package/lib/dist/graph/index.js +13 -0
  269. package/lib/dist/graph/index.js.map +1 -0
  270. package/lib/dist/graph/queries.d.ts +106 -0
  271. package/lib/dist/graph/queries.d.ts.map +1 -0
  272. package/lib/dist/graph/queries.js +339 -0
  273. package/lib/dist/graph/queries.js.map +1 -0
  274. package/lib/dist/graph/traversal.d.ts +127 -0
  275. package/lib/dist/graph/traversal.d.ts.map +1 -0
  276. package/lib/dist/graph/traversal.js +599 -0
  277. package/lib/dist/graph/traversal.js.map +1 -0
  278. package/lib/dist/index.d.ts +642 -0
  279. package/lib/dist/index.d.ts.map +1 -0
  280. package/lib/dist/index.js +1387 -0
  281. package/lib/dist/index.js.map +1 -0
  282. package/lib/dist/installer/config-writer.d.ts +28 -0
  283. package/lib/dist/installer/config-writer.d.ts.map +1 -0
  284. package/lib/dist/installer/config-writer.js +91 -0
  285. package/lib/dist/installer/config-writer.js.map +1 -0
  286. package/lib/dist/installer/index.d.ts +100 -0
  287. package/lib/dist/installer/index.d.ts.map +1 -0
  288. package/lib/dist/installer/index.js +526 -0
  289. package/lib/dist/installer/index.js.map +1 -0
  290. package/lib/dist/installer/instructions-template.d.ts +41 -0
  291. package/lib/dist/installer/instructions-template.d.ts.map +1 -0
  292. package/lib/dist/installer/instructions-template.js +53 -0
  293. package/lib/dist/installer/instructions-template.js.map +1 -0
  294. package/lib/dist/installer/targets/antigravity.d.ts +57 -0
  295. package/lib/dist/installer/targets/antigravity.d.ts.map +1 -0
  296. package/lib/dist/installer/targets/antigravity.js +308 -0
  297. package/lib/dist/installer/targets/antigravity.js.map +1 -0
  298. package/lib/dist/installer/targets/claude.d.ts +62 -0
  299. package/lib/dist/installer/targets/claude.d.ts.map +1 -0
  300. package/lib/dist/installer/targets/claude.js +454 -0
  301. package/lib/dist/installer/targets/claude.js.map +1 -0
  302. package/lib/dist/installer/targets/codex.d.ts +18 -0
  303. package/lib/dist/installer/targets/codex.d.ts.map +1 -0
  304. package/lib/dist/installer/targets/codex.js +185 -0
  305. package/lib/dist/installer/targets/codex.js.map +1 -0
  306. package/lib/dist/installer/targets/cursor.d.ts +35 -0
  307. package/lib/dist/installer/targets/cursor.d.ts.map +1 -0
  308. package/lib/dist/installer/targets/cursor.js +254 -0
  309. package/lib/dist/installer/targets/cursor.js.map +1 -0
  310. package/lib/dist/installer/targets/gemini.d.ts +26 -0
  311. package/lib/dist/installer/targets/gemini.d.ts.map +1 -0
  312. package/lib/dist/installer/targets/gemini.js +165 -0
  313. package/lib/dist/installer/targets/gemini.js.map +1 -0
  314. package/lib/dist/installer/targets/hermes.d.ts +18 -0
  315. package/lib/dist/installer/targets/hermes.d.ts.map +1 -0
  316. package/lib/dist/installer/targets/hermes.js +359 -0
  317. package/lib/dist/installer/targets/hermes.js.map +1 -0
  318. package/lib/dist/installer/targets/kiro.d.ts +27 -0
  319. package/lib/dist/installer/targets/kiro.d.ts.map +1 -0
  320. package/lib/dist/installer/targets/kiro.js +178 -0
  321. package/lib/dist/installer/targets/kiro.js.map +1 -0
  322. package/lib/dist/installer/targets/opencode.d.ts +38 -0
  323. package/lib/dist/installer/targets/opencode.d.ts.map +1 -0
  324. package/lib/dist/installer/targets/opencode.js +288 -0
  325. package/lib/dist/installer/targets/opencode.js.map +1 -0
  326. package/lib/dist/installer/targets/registry.d.ts +35 -0
  327. package/lib/dist/installer/targets/registry.d.ts.map +1 -0
  328. package/lib/dist/installer/targets/registry.js +91 -0
  329. package/lib/dist/installer/targets/registry.js.map +1 -0
  330. package/lib/dist/installer/targets/shared.d.ts +101 -0
  331. package/lib/dist/installer/targets/shared.d.ts.map +1 -0
  332. package/lib/dist/installer/targets/shared.js +264 -0
  333. package/lib/dist/installer/targets/shared.js.map +1 -0
  334. package/lib/dist/installer/targets/toml.d.ts +52 -0
  335. package/lib/dist/installer/targets/toml.d.ts.map +1 -0
  336. package/lib/dist/installer/targets/toml.js +147 -0
  337. package/lib/dist/installer/targets/toml.js.map +1 -0
  338. package/lib/dist/installer/targets/types.d.ts +108 -0
  339. package/lib/dist/installer/targets/types.d.ts.map +1 -0
  340. package/lib/dist/installer/targets/types.js +16 -0
  341. package/lib/dist/installer/targets/types.js.map +1 -0
  342. package/lib/dist/mcp/daemon-manager.d.ts +42 -0
  343. package/lib/dist/mcp/daemon-manager.d.ts.map +1 -0
  344. package/lib/dist/mcp/daemon-manager.js +129 -0
  345. package/lib/dist/mcp/daemon-manager.js.map +1 -0
  346. package/lib/dist/mcp/daemon-paths.d.ts +73 -0
  347. package/lib/dist/mcp/daemon-paths.d.ts.map +1 -0
  348. package/lib/dist/mcp/daemon-paths.js +165 -0
  349. package/lib/dist/mcp/daemon-paths.js.map +1 -0
  350. package/lib/dist/mcp/daemon-registry.d.ts +47 -0
  351. package/lib/dist/mcp/daemon-registry.d.ts.map +1 -0
  352. package/lib/dist/mcp/daemon-registry.js +233 -0
  353. package/lib/dist/mcp/daemon-registry.js.map +1 -0
  354. package/lib/dist/mcp/daemon.d.ts +290 -0
  355. package/lib/dist/mcp/daemon.d.ts.map +1 -0
  356. package/lib/dist/mcp/daemon.js +840 -0
  357. package/lib/dist/mcp/daemon.js.map +1 -0
  358. package/lib/dist/mcp/dynamic-boundaries.d.ts +41 -0
  359. package/lib/dist/mcp/dynamic-boundaries.d.ts.map +1 -0
  360. package/lib/dist/mcp/dynamic-boundaries.js +360 -0
  361. package/lib/dist/mcp/dynamic-boundaries.js.map +1 -0
  362. package/lib/dist/mcp/early-ppid.d.ts +26 -0
  363. package/lib/dist/mcp/early-ppid.d.ts.map +1 -0
  364. package/lib/dist/mcp/early-ppid.js +29 -0
  365. package/lib/dist/mcp/early-ppid.js.map +1 -0
  366. package/lib/dist/mcp/engine.d.ts +122 -0
  367. package/lib/dist/mcp/engine.d.ts.map +1 -0
  368. package/lib/dist/mcp/engine.js +350 -0
  369. package/lib/dist/mcp/engine.js.map +1 -0
  370. package/lib/dist/mcp/index.d.ts +113 -0
  371. package/lib/dist/mcp/index.d.ts.map +1 -0
  372. package/lib/dist/mcp/index.js +498 -0
  373. package/lib/dist/mcp/index.js.map +1 -0
  374. package/lib/dist/mcp/liveness-watchdog.d.ts +18 -0
  375. package/lib/dist/mcp/liveness-watchdog.d.ts.map +1 -0
  376. package/lib/dist/mcp/liveness-watchdog.js +207 -0
  377. package/lib/dist/mcp/liveness-watchdog.js.map +1 -0
  378. package/lib/dist/mcp/ppid-watchdog.d.ts +62 -0
  379. package/lib/dist/mcp/ppid-watchdog.d.ts.map +1 -0
  380. package/lib/dist/mcp/ppid-watchdog.js +64 -0
  381. package/lib/dist/mcp/ppid-watchdog.js.map +1 -0
  382. package/lib/dist/mcp/proxy.d.ts +87 -0
  383. package/lib/dist/mcp/proxy.d.ts.map +1 -0
  384. package/lib/dist/mcp/proxy.js +659 -0
  385. package/lib/dist/mcp/proxy.js.map +1 -0
  386. package/lib/dist/mcp/query-pool.d.ts +94 -0
  387. package/lib/dist/mcp/query-pool.d.ts.map +1 -0
  388. package/lib/dist/mcp/query-pool.js +297 -0
  389. package/lib/dist/mcp/query-pool.js.map +1 -0
  390. package/lib/dist/mcp/query-worker.d.ts +24 -0
  391. package/lib/dist/mcp/query-worker.d.ts.map +1 -0
  392. package/lib/dist/mcp/query-worker.js +87 -0
  393. package/lib/dist/mcp/query-worker.js.map +1 -0
  394. package/lib/dist/mcp/server-instructions.d.ts +34 -0
  395. package/lib/dist/mcp/server-instructions.d.ts.map +1 -0
  396. package/lib/dist/mcp/server-instructions.js +106 -0
  397. package/lib/dist/mcp/server-instructions.js.map +1 -0
  398. package/lib/dist/mcp/session.d.ts +79 -0
  399. package/lib/dist/mcp/session.d.ts.map +1 -0
  400. package/lib/dist/mcp/session.js +330 -0
  401. package/lib/dist/mcp/session.js.map +1 -0
  402. package/lib/dist/mcp/startup-handshake.d.ts +44 -0
  403. package/lib/dist/mcp/startup-handshake.d.ts.map +1 -0
  404. package/lib/dist/mcp/startup-handshake.js +73 -0
  405. package/lib/dist/mcp/startup-handshake.js.map +1 -0
  406. package/lib/dist/mcp/stdin-teardown.d.ts +27 -0
  407. package/lib/dist/mcp/stdin-teardown.d.ts.map +1 -0
  408. package/lib/dist/mcp/stdin-teardown.js +49 -0
  409. package/lib/dist/mcp/stdin-teardown.js.map +1 -0
  410. package/lib/dist/mcp/tools.d.ts +613 -0
  411. package/lib/dist/mcp/tools.d.ts.map +1 -0
  412. package/lib/dist/mcp/tools.js +4424 -0
  413. package/lib/dist/mcp/tools.js.map +1 -0
  414. package/lib/dist/mcp/transport.d.ts +188 -0
  415. package/lib/dist/mcp/transport.d.ts.map +1 -0
  416. package/lib/dist/mcp/transport.js +359 -0
  417. package/lib/dist/mcp/transport.js.map +1 -0
  418. package/lib/dist/mcp/version.d.ts +19 -0
  419. package/lib/dist/mcp/version.d.ts.map +1 -0
  420. package/lib/dist/mcp/version.js +71 -0
  421. package/lib/dist/mcp/version.js.map +1 -0
  422. package/lib/dist/project-config.d.ts +94 -0
  423. package/lib/dist/project-config.d.ts.map +1 -0
  424. package/lib/dist/project-config.js +374 -0
  425. package/lib/dist/project-config.js.map +1 -0
  426. package/lib/dist/resolution/c-fnptr-synthesizer.d.ts +5 -0
  427. package/lib/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -0
  428. package/lib/dist/resolution/c-fnptr-synthesizer.js +1038 -0
  429. package/lib/dist/resolution/c-fnptr-synthesizer.js.map +1 -0
  430. package/lib/dist/resolution/callback-synthesizer.d.ts +15 -0
  431. package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -0
  432. package/lib/dist/resolution/callback-synthesizer.js +3658 -0
  433. package/lib/dist/resolution/callback-synthesizer.js.map +1 -0
  434. package/lib/dist/resolution/cooperative-yield.d.ts +32 -0
  435. package/lib/dist/resolution/cooperative-yield.d.ts.map +1 -0
  436. package/lib/dist/resolution/cooperative-yield.js +42 -0
  437. package/lib/dist/resolution/cooperative-yield.js.map +1 -0
  438. package/lib/dist/resolution/frameworks/astro.d.ts +9 -0
  439. package/lib/dist/resolution/frameworks/astro.d.ts.map +1 -0
  440. package/lib/dist/resolution/frameworks/astro.js +169 -0
  441. package/lib/dist/resolution/frameworks/astro.js.map +1 -0
  442. package/lib/dist/resolution/frameworks/cargo-workspace.d.ts +18 -0
  443. package/lib/dist/resolution/frameworks/cargo-workspace.d.ts.map +1 -0
  444. package/lib/dist/resolution/frameworks/cargo-workspace.js +225 -0
  445. package/lib/dist/resolution/frameworks/cargo-workspace.js.map +1 -0
  446. package/lib/dist/resolution/frameworks/cics.d.ts +20 -0
  447. package/lib/dist/resolution/frameworks/cics.d.ts.map +1 -0
  448. package/lib/dist/resolution/frameworks/cics.js +90 -0
  449. package/lib/dist/resolution/frameworks/cics.js.map +1 -0
  450. package/lib/dist/resolution/frameworks/csharp.d.ts +8 -0
  451. package/lib/dist/resolution/frameworks/csharp.d.ts.map +1 -0
  452. package/lib/dist/resolution/frameworks/csharp.js +241 -0
  453. package/lib/dist/resolution/frameworks/csharp.js.map +1 -0
  454. package/lib/dist/resolution/frameworks/drupal.d.ts +51 -0
  455. package/lib/dist/resolution/frameworks/drupal.d.ts.map +1 -0
  456. package/lib/dist/resolution/frameworks/drupal.js +367 -0
  457. package/lib/dist/resolution/frameworks/drupal.js.map +1 -0
  458. package/lib/dist/resolution/frameworks/expo-modules.d.ts +3 -0
  459. package/lib/dist/resolution/frameworks/expo-modules.d.ts.map +1 -0
  460. package/lib/dist/resolution/frameworks/expo-modules.js +148 -0
  461. package/lib/dist/resolution/frameworks/expo-modules.js.map +1 -0
  462. package/lib/dist/resolution/frameworks/express.d.ts +8 -0
  463. package/lib/dist/resolution/frameworks/express.d.ts.map +1 -0
  464. package/lib/dist/resolution/frameworks/express.js +308 -0
  465. package/lib/dist/resolution/frameworks/express.js.map +1 -0
  466. package/lib/dist/resolution/frameworks/fabric.d.ts +3 -0
  467. package/lib/dist/resolution/frameworks/fabric.d.ts.map +1 -0
  468. package/lib/dist/resolution/frameworks/fabric.js +354 -0
  469. package/lib/dist/resolution/frameworks/fabric.js.map +1 -0
  470. package/lib/dist/resolution/frameworks/go.d.ts +8 -0
  471. package/lib/dist/resolution/frameworks/go.d.ts.map +1 -0
  472. package/lib/dist/resolution/frameworks/go.js +161 -0
  473. package/lib/dist/resolution/frameworks/go.js.map +1 -0
  474. package/lib/dist/resolution/frameworks/goframe.d.ts +41 -0
  475. package/lib/dist/resolution/frameworks/goframe.d.ts.map +1 -0
  476. package/lib/dist/resolution/frameworks/goframe.js +112 -0
  477. package/lib/dist/resolution/frameworks/goframe.js.map +1 -0
  478. package/lib/dist/resolution/frameworks/index.d.ts +50 -0
  479. package/lib/dist/resolution/frameworks/index.d.ts.map +1 -0
  480. package/lib/dist/resolution/frameworks/index.js +175 -0
  481. package/lib/dist/resolution/frameworks/index.js.map +1 -0
  482. package/lib/dist/resolution/frameworks/java.d.ts +8 -0
  483. package/lib/dist/resolution/frameworks/java.d.ts.map +1 -0
  484. package/lib/dist/resolution/frameworks/java.js +517 -0
  485. package/lib/dist/resolution/frameworks/java.js.map +1 -0
  486. package/lib/dist/resolution/frameworks/laravel.d.ts +13 -0
  487. package/lib/dist/resolution/frameworks/laravel.d.ts.map +1 -0
  488. package/lib/dist/resolution/frameworks/laravel.js +257 -0
  489. package/lib/dist/resolution/frameworks/laravel.js.map +1 -0
  490. package/lib/dist/resolution/frameworks/nestjs.d.ts +26 -0
  491. package/lib/dist/resolution/frameworks/nestjs.d.ts.map +1 -0
  492. package/lib/dist/resolution/frameworks/nestjs.js +698 -0
  493. package/lib/dist/resolution/frameworks/nestjs.js.map +1 -0
  494. package/lib/dist/resolution/frameworks/play.d.ts +19 -0
  495. package/lib/dist/resolution/frameworks/play.d.ts.map +1 -0
  496. package/lib/dist/resolution/frameworks/play.js +111 -0
  497. package/lib/dist/resolution/frameworks/play.js.map +1 -0
  498. package/lib/dist/resolution/frameworks/python.d.ts +10 -0
  499. package/lib/dist/resolution/frameworks/python.d.ts.map +1 -0
  500. package/lib/dist/resolution/frameworks/python.js +400 -0
  501. package/lib/dist/resolution/frameworks/python.js.map +1 -0
  502. package/lib/dist/resolution/frameworks/react-native.d.ts +3 -0
  503. package/lib/dist/resolution/frameworks/react-native.d.ts.map +1 -0
  504. package/lib/dist/resolution/frameworks/react-native.js +410 -0
  505. package/lib/dist/resolution/frameworks/react-native.js.map +1 -0
  506. package/lib/dist/resolution/frameworks/react.d.ts +8 -0
  507. package/lib/dist/resolution/frameworks/react.d.ts.map +1 -0
  508. package/lib/dist/resolution/frameworks/react.js +334 -0
  509. package/lib/dist/resolution/frameworks/react.js.map +1 -0
  510. package/lib/dist/resolution/frameworks/ruby.d.ts +8 -0
  511. package/lib/dist/resolution/frameworks/ruby.d.ts.map +1 -0
  512. package/lib/dist/resolution/frameworks/ruby.js +302 -0
  513. package/lib/dist/resolution/frameworks/ruby.js.map +1 -0
  514. package/lib/dist/resolution/frameworks/rust.d.ts +8 -0
  515. package/lib/dist/resolution/frameworks/rust.d.ts.map +1 -0
  516. package/lib/dist/resolution/frameworks/rust.js +304 -0
  517. package/lib/dist/resolution/frameworks/rust.js.map +1 -0
  518. package/lib/dist/resolution/frameworks/svelte.d.ts +9 -0
  519. package/lib/dist/resolution/frameworks/svelte.d.ts.map +1 -0
  520. package/lib/dist/resolution/frameworks/svelte.js +253 -0
  521. package/lib/dist/resolution/frameworks/svelte.js.map +1 -0
  522. package/lib/dist/resolution/frameworks/swift-objc.d.ts +37 -0
  523. package/lib/dist/resolution/frameworks/swift-objc.d.ts.map +1 -0
  524. package/lib/dist/resolution/frameworks/swift-objc.js +252 -0
  525. package/lib/dist/resolution/frameworks/swift-objc.js.map +1 -0
  526. package/lib/dist/resolution/frameworks/swift.d.ts +10 -0
  527. package/lib/dist/resolution/frameworks/swift.d.ts.map +1 -0
  528. package/lib/dist/resolution/frameworks/swift.js +400 -0
  529. package/lib/dist/resolution/frameworks/swift.js.map +1 -0
  530. package/lib/dist/resolution/frameworks/terraform.d.ts +38 -0
  531. package/lib/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  532. package/lib/dist/resolution/frameworks/terraform.js +277 -0
  533. package/lib/dist/resolution/frameworks/terraform.js.map +1 -0
  534. package/lib/dist/resolution/frameworks/vue.d.ts +9 -0
  535. package/lib/dist/resolution/frameworks/vue.d.ts.map +1 -0
  536. package/lib/dist/resolution/frameworks/vue.js +303 -0
  537. package/lib/dist/resolution/frameworks/vue.js.map +1 -0
  538. package/lib/dist/resolution/go-module.d.ts +26 -0
  539. package/lib/dist/resolution/go-module.d.ts.map +1 -0
  540. package/lib/dist/resolution/go-module.js +78 -0
  541. package/lib/dist/resolution/go-module.js.map +1 -0
  542. package/lib/dist/resolution/goframe-synthesizer.d.ts +28 -0
  543. package/lib/dist/resolution/goframe-synthesizer.d.ts.map +1 -0
  544. package/lib/dist/resolution/goframe-synthesizer.js +158 -0
  545. package/lib/dist/resolution/goframe-synthesizer.js.map +1 -0
  546. package/lib/dist/resolution/import-resolver.d.ts +85 -0
  547. package/lib/dist/resolution/import-resolver.d.ts.map +1 -0
  548. package/lib/dist/resolution/import-resolver.js +1974 -0
  549. package/lib/dist/resolution/import-resolver.js.map +1 -0
  550. package/lib/dist/resolution/index.d.ts +238 -0
  551. package/lib/dist/resolution/index.d.ts.map +1 -0
  552. package/lib/dist/resolution/index.js +1633 -0
  553. package/lib/dist/resolution/index.js.map +1 -0
  554. package/lib/dist/resolution/lru-cache.d.ts +24 -0
  555. package/lib/dist/resolution/lru-cache.d.ts.map +1 -0
  556. package/lib/dist/resolution/lru-cache.js +62 -0
  557. package/lib/dist/resolution/lru-cache.js.map +1 -0
  558. package/lib/dist/resolution/name-matcher.d.ts +112 -0
  559. package/lib/dist/resolution/name-matcher.d.ts.map +1 -0
  560. package/lib/dist/resolution/name-matcher.js +1701 -0
  561. package/lib/dist/resolution/name-matcher.js.map +1 -0
  562. package/lib/dist/resolution/path-aliases.d.ts +68 -0
  563. package/lib/dist/resolution/path-aliases.d.ts.map +1 -0
  564. package/lib/dist/resolution/path-aliases.js +238 -0
  565. package/lib/dist/resolution/path-aliases.js.map +1 -0
  566. package/lib/dist/resolution/strip-comments.d.ts +27 -0
  567. package/lib/dist/resolution/strip-comments.d.ts.map +1 -0
  568. package/lib/dist/resolution/strip-comments.js +492 -0
  569. package/lib/dist/resolution/strip-comments.js.map +1 -0
  570. package/lib/dist/resolution/swift-objc-bridge.d.ts +134 -0
  571. package/lib/dist/resolution/swift-objc-bridge.d.ts.map +1 -0
  572. package/lib/dist/resolution/swift-objc-bridge.js +256 -0
  573. package/lib/dist/resolution/swift-objc-bridge.js.map +1 -0
  574. package/lib/dist/resolution/types.d.ts +253 -0
  575. package/lib/dist/resolution/types.d.ts.map +1 -0
  576. package/lib/dist/resolution/types.js +8 -0
  577. package/lib/dist/resolution/types.js.map +1 -0
  578. package/lib/dist/resolution/workspace-packages.d.ts +58 -0
  579. package/lib/dist/resolution/workspace-packages.d.ts.map +1 -0
  580. package/lib/dist/resolution/workspace-packages.js +346 -0
  581. package/lib/dist/resolution/workspace-packages.js.map +1 -0
  582. package/lib/dist/search/identifier-segments.d.ts +60 -0
  583. package/lib/dist/search/identifier-segments.d.ts.map +1 -0
  584. package/lib/dist/search/identifier-segments.js +176 -0
  585. package/lib/dist/search/identifier-segments.js.map +1 -0
  586. package/lib/dist/search/query-parser.d.ts +57 -0
  587. package/lib/dist/search/query-parser.d.ts.map +1 -0
  588. package/lib/dist/search/query-parser.js +177 -0
  589. package/lib/dist/search/query-parser.js.map +1 -0
  590. package/lib/dist/search/query-utils.d.ts +87 -0
  591. package/lib/dist/search/query-utils.d.ts.map +1 -0
  592. package/lib/dist/search/query-utils.js +449 -0
  593. package/lib/dist/search/query-utils.js.map +1 -0
  594. package/lib/dist/sync/git-hooks.d.ts +45 -0
  595. package/lib/dist/sync/git-hooks.d.ts.map +1 -0
  596. package/lib/dist/sync/git-hooks.js +227 -0
  597. package/lib/dist/sync/git-hooks.js.map +1 -0
  598. package/lib/dist/sync/index.d.ts +19 -0
  599. package/lib/dist/sync/index.d.ts.map +1 -0
  600. package/lib/dist/sync/index.js +35 -0
  601. package/lib/dist/sync/index.js.map +1 -0
  602. package/lib/dist/sync/watch-policy.d.ts +48 -0
  603. package/lib/dist/sync/watch-policy.d.ts.map +1 -0
  604. package/lib/dist/sync/watch-policy.js +124 -0
  605. package/lib/dist/sync/watch-policy.js.map +1 -0
  606. package/lib/dist/sync/watcher.d.ts +355 -0
  607. package/lib/dist/sync/watcher.d.ts.map +1 -0
  608. package/lib/dist/sync/watcher.js +848 -0
  609. package/lib/dist/sync/watcher.js.map +1 -0
  610. package/lib/dist/sync/worktree.d.ts +63 -0
  611. package/lib/dist/sync/worktree.d.ts.map +1 -0
  612. package/lib/dist/sync/worktree.js +182 -0
  613. package/lib/dist/sync/worktree.js.map +1 -0
  614. package/lib/dist/telemetry/index.d.ts +143 -0
  615. package/lib/dist/telemetry/index.d.ts.map +1 -0
  616. package/lib/dist/telemetry/index.js +541 -0
  617. package/lib/dist/telemetry/index.js.map +1 -0
  618. package/lib/dist/types.d.ts +415 -0
  619. package/lib/dist/types.d.ts.map +1 -0
  620. package/lib/dist/types.js +91 -0
  621. package/lib/dist/types.js.map +1 -0
  622. package/lib/dist/ui/glyphs.d.ts +42 -0
  623. package/lib/dist/ui/glyphs.d.ts.map +1 -0
  624. package/lib/dist/ui/glyphs.js +78 -0
  625. package/lib/dist/ui/glyphs.js.map +1 -0
  626. package/lib/dist/ui/shimmer-progress.d.ts +11 -0
  627. package/lib/dist/ui/shimmer-progress.d.ts.map +1 -0
  628. package/lib/dist/ui/shimmer-progress.js +90 -0
  629. package/lib/dist/ui/shimmer-progress.js.map +1 -0
  630. package/lib/dist/ui/shimmer-worker.d.ts +2 -0
  631. package/lib/dist/ui/shimmer-worker.d.ts.map +1 -0
  632. package/lib/dist/ui/shimmer-worker.js +118 -0
  633. package/lib/dist/ui/shimmer-worker.js.map +1 -0
  634. package/lib/dist/ui/types.d.ts +17 -0
  635. package/lib/dist/ui/types.d.ts.map +1 -0
  636. package/lib/dist/ui/types.js +3 -0
  637. package/lib/dist/ui/types.js.map +1 -0
  638. package/lib/dist/upgrade/index.d.ts +132 -0
  639. package/lib/dist/upgrade/index.d.ts.map +1 -0
  640. package/lib/dist/upgrade/index.js +498 -0
  641. package/lib/dist/upgrade/index.js.map +1 -0
  642. package/lib/dist/utils.d.ts +224 -0
  643. package/lib/dist/utils.d.ts.map +1 -0
  644. package/lib/dist/utils.js +583 -0
  645. package/lib/dist/utils.js.map +1 -0
  646. package/lib/node_modules/.package-lock.json +125 -0
  647. package/lib/node_modules/@clack/core/CHANGELOG.md +348 -0
  648. package/lib/node_modules/@clack/core/LICENSE +9 -0
  649. package/lib/node_modules/@clack/core/README.md +22 -0
  650. package/lib/node_modules/@clack/core/dist/index.d.mts +363 -0
  651. package/lib/node_modules/@clack/core/dist/index.mjs +17 -0
  652. package/lib/node_modules/@clack/core/dist/index.mjs.map +1 -0
  653. package/lib/node_modules/@clack/core/package.json +63 -0
  654. package/lib/node_modules/@clack/prompts/CHANGELOG.md +591 -0
  655. package/lib/node_modules/@clack/prompts/LICENSE +9 -0
  656. package/lib/node_modules/@clack/prompts/README.md +375 -0
  657. package/lib/node_modules/@clack/prompts/dist/index.d.mts +396 -0
  658. package/lib/node_modules/@clack/prompts/dist/index.mjs +144 -0
  659. package/lib/node_modules/@clack/prompts/dist/index.mjs.map +1 -0
  660. package/lib/node_modules/@clack/prompts/package.json +68 -0
  661. package/lib/node_modules/commander/LICENSE +22 -0
  662. package/lib/node_modules/commander/Readme.md +1176 -0
  663. package/lib/node_modules/commander/esm.mjs +16 -0
  664. package/lib/node_modules/commander/index.js +24 -0
  665. package/lib/node_modules/commander/lib/argument.js +150 -0
  666. package/lib/node_modules/commander/lib/command.js +2777 -0
  667. package/lib/node_modules/commander/lib/error.js +39 -0
  668. package/lib/node_modules/commander/lib/help.js +747 -0
  669. package/lib/node_modules/commander/lib/option.js +380 -0
  670. package/lib/node_modules/commander/lib/suggestSimilar.js +101 -0
  671. package/lib/node_modules/commander/package-support.json +19 -0
  672. package/lib/node_modules/commander/package.json +82 -0
  673. package/lib/node_modules/commander/typings/esm.d.mts +3 -0
  674. package/lib/node_modules/commander/typings/index.d.ts +1113 -0
  675. package/lib/node_modules/fast-string-truncated-width/dist/index.d.ts +4 -0
  676. package/lib/node_modules/fast-string-truncated-width/dist/index.js +111 -0
  677. package/lib/node_modules/fast-string-truncated-width/dist/types.d.ts +19 -0
  678. package/lib/node_modules/fast-string-truncated-width/dist/types.js +2 -0
  679. package/lib/node_modules/fast-string-truncated-width/dist/utils.d.ts +4 -0
  680. package/lib/node_modules/fast-string-truncated-width/dist/utils.js +20 -0
  681. package/lib/node_modules/fast-string-truncated-width/license +21 -0
  682. package/lib/node_modules/fast-string-truncated-width/package.json +35 -0
  683. package/lib/node_modules/fast-string-truncated-width/readme.md +59 -0
  684. package/lib/node_modules/fast-string-width/dist/index.d.ts +4 -0
  685. package/lib/node_modules/fast-string-width/dist/index.js +14 -0
  686. package/lib/node_modules/fast-string-width/license +21 -0
  687. package/lib/node_modules/fast-string-width/package.json +34 -0
  688. package/lib/node_modules/fast-string-width/readme.md +42 -0
  689. package/lib/node_modules/fast-wrap-ansi/LICENSE +23 -0
  690. package/lib/node_modules/fast-wrap-ansi/README.md +26 -0
  691. package/lib/node_modules/fast-wrap-ansi/lib/main.d.ts +6 -0
  692. package/lib/node_modules/fast-wrap-ansi/lib/main.js +219 -0
  693. package/lib/node_modules/fast-wrap-ansi/lib/main.js.map +1 -0
  694. package/lib/node_modules/fast-wrap-ansi/package.json +51 -0
  695. package/lib/node_modules/ignore/LICENSE-MIT +21 -0
  696. package/lib/node_modules/ignore/README.md +452 -0
  697. package/lib/node_modules/ignore/index.d.ts +81 -0
  698. package/lib/node_modules/ignore/index.js +784 -0
  699. package/lib/node_modules/ignore/legacy.js +681 -0
  700. package/lib/node_modules/ignore/package.json +87 -0
  701. package/lib/node_modules/jsonc-parser/CHANGELOG.md +76 -0
  702. package/lib/node_modules/jsonc-parser/LICENSE.md +21 -0
  703. package/lib/node_modules/jsonc-parser/README.md +364 -0
  704. package/lib/node_modules/jsonc-parser/SECURITY.md +41 -0
  705. package/lib/node_modules/jsonc-parser/lib/esm/impl/edit.js +185 -0
  706. package/lib/node_modules/jsonc-parser/lib/esm/impl/format.js +261 -0
  707. package/lib/node_modules/jsonc-parser/lib/esm/impl/parser.js +659 -0
  708. package/lib/node_modules/jsonc-parser/lib/esm/impl/scanner.js +443 -0
  709. package/lib/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +29 -0
  710. package/lib/node_modules/jsonc-parser/lib/esm/main.d.ts +351 -0
  711. package/lib/node_modules/jsonc-parser/lib/esm/main.js +178 -0
  712. package/lib/node_modules/jsonc-parser/lib/umd/impl/edit.js +201 -0
  713. package/lib/node_modules/jsonc-parser/lib/umd/impl/format.js +275 -0
  714. package/lib/node_modules/jsonc-parser/lib/umd/impl/parser.js +682 -0
  715. package/lib/node_modules/jsonc-parser/lib/umd/impl/scanner.js +456 -0
  716. package/lib/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +42 -0
  717. package/lib/node_modules/jsonc-parser/lib/umd/main.d.ts +351 -0
  718. package/lib/node_modules/jsonc-parser/lib/umd/main.js +194 -0
  719. package/lib/node_modules/jsonc-parser/package.json +37 -0
  720. package/lib/node_modules/picomatch/LICENSE +21 -0
  721. package/lib/node_modules/picomatch/README.md +738 -0
  722. package/lib/node_modules/picomatch/index.js +17 -0
  723. package/lib/node_modules/picomatch/lib/constants.js +180 -0
  724. package/lib/node_modules/picomatch/lib/parse.js +1085 -0
  725. package/lib/node_modules/picomatch/lib/picomatch.js +341 -0
  726. package/lib/node_modules/picomatch/lib/scan.js +391 -0
  727. package/lib/node_modules/picomatch/lib/utils.js +72 -0
  728. package/lib/node_modules/picomatch/package.json +83 -0
  729. package/lib/node_modules/picomatch/posix.js +3 -0
  730. package/lib/node_modules/sisteransi/license +21 -0
  731. package/lib/node_modules/sisteransi/package.json +34 -0
  732. package/lib/node_modules/sisteransi/readme.md +113 -0
  733. package/lib/node_modules/sisteransi/src/index.js +58 -0
  734. package/lib/node_modules/sisteransi/src/sisteransi.d.ts +35 -0
  735. package/lib/node_modules/tree-sitter-wasms/LICENSE +24 -0
  736. package/lib/node_modules/tree-sitter-wasms/README.md +23 -0
  737. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-bash.wasm +0 -0
  738. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-c.wasm +0 -0
  739. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-c_sharp.wasm +0 -0
  740. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-cpp.wasm +0 -0
  741. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-css.wasm +0 -0
  742. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-dart.wasm +0 -0
  743. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-elisp.wasm +0 -0
  744. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-elixir.wasm +0 -0
  745. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-elm.wasm +0 -0
  746. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-embedded_template.wasm +0 -0
  747. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-go.wasm +0 -0
  748. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-html.wasm +0 -0
  749. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-java.wasm +0 -0
  750. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-javascript.wasm +0 -0
  751. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-json.wasm +0 -0
  752. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-kotlin.wasm +0 -0
  753. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-lua.wasm +0 -0
  754. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-objc.wasm +0 -0
  755. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-ocaml.wasm +0 -0
  756. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-php.wasm +0 -0
  757. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-python.wasm +0 -0
  758. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-ql.wasm +0 -0
  759. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-rescript.wasm +0 -0
  760. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-ruby.wasm +0 -0
  761. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-rust.wasm +0 -0
  762. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-scala.wasm +0 -0
  763. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-solidity.wasm +0 -0
  764. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-swift.wasm +0 -0
  765. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-systemrdl.wasm +0 -0
  766. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-tlaplus.wasm +0 -0
  767. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-toml.wasm +0 -0
  768. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-tsx.wasm +0 -0
  769. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-typescript.wasm +0 -0
  770. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-vue.wasm +0 -0
  771. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-yaml.wasm +0 -0
  772. package/lib/node_modules/tree-sitter-wasms/out/tree-sitter-zig.wasm +0 -0
  773. package/lib/node_modules/tree-sitter-wasms/package.json +64 -0
  774. package/lib/node_modules/web-tree-sitter/LICENSE +21 -0
  775. package/lib/node_modules/web-tree-sitter/README.md +269 -0
  776. package/lib/node_modules/web-tree-sitter/debug/tree-sitter.cjs +4558 -0
  777. package/lib/node_modules/web-tree-sitter/debug/tree-sitter.cjs.map +7 -0
  778. package/lib/node_modules/web-tree-sitter/debug/tree-sitter.js +4516 -0
  779. package/lib/node_modules/web-tree-sitter/debug/tree-sitter.js.map +7 -0
  780. package/lib/node_modules/web-tree-sitter/debug/tree-sitter.wasm +0 -0
  781. package/lib/node_modules/web-tree-sitter/debug/tree-sitter.wasm.map +31 -0
  782. package/lib/node_modules/web-tree-sitter/lib/alloc.c +48 -0
  783. package/lib/node_modules/web-tree-sitter/lib/alloc.h +41 -0
  784. package/lib/node_modules/web-tree-sitter/lib/array.h +291 -0
  785. package/lib/node_modules/web-tree-sitter/lib/atomic.h +68 -0
  786. package/lib/node_modules/web-tree-sitter/lib/clock.h +146 -0
  787. package/lib/node_modules/web-tree-sitter/lib/error_costs.h +11 -0
  788. package/lib/node_modules/web-tree-sitter/lib/get_changed_ranges.c +523 -0
  789. package/lib/node_modules/web-tree-sitter/lib/get_changed_ranges.h +36 -0
  790. package/lib/node_modules/web-tree-sitter/lib/host.h +21 -0
  791. package/lib/node_modules/web-tree-sitter/lib/language.c +293 -0
  792. package/lib/node_modules/web-tree-sitter/lib/language.h +293 -0
  793. package/lib/node_modules/web-tree-sitter/lib/length.h +52 -0
  794. package/lib/node_modules/web-tree-sitter/lib/lexer.c +483 -0
  795. package/lib/node_modules/web-tree-sitter/lib/lexer.h +54 -0
  796. package/lib/node_modules/web-tree-sitter/lib/lib.c +12 -0
  797. package/lib/node_modules/web-tree-sitter/lib/node.c +875 -0
  798. package/lib/node_modules/web-tree-sitter/lib/parser.c +2297 -0
  799. package/lib/node_modules/web-tree-sitter/lib/parser.h +286 -0
  800. package/lib/node_modules/web-tree-sitter/lib/point.h +48 -0
  801. package/lib/node_modules/web-tree-sitter/lib/query.c +4347 -0
  802. package/lib/node_modules/web-tree-sitter/lib/reduce_action.h +34 -0
  803. package/lib/node_modules/web-tree-sitter/lib/reusable_node.h +95 -0
  804. package/lib/node_modules/web-tree-sitter/lib/stack.c +912 -0
  805. package/lib/node_modules/web-tree-sitter/lib/stack.h +133 -0
  806. package/lib/node_modules/web-tree-sitter/lib/subtree.c +1034 -0
  807. package/lib/node_modules/web-tree-sitter/lib/subtree.h +399 -0
  808. package/lib/node_modules/web-tree-sitter/lib/tree-sitter.c +987 -0
  809. package/lib/node_modules/web-tree-sitter/lib/tree-sitter.cjs +2988 -0
  810. package/lib/node_modules/web-tree-sitter/lib/tree-sitter.wasm +0 -0
  811. package/lib/node_modules/web-tree-sitter/lib/tree-sitter.wasm.map +1 -0
  812. package/lib/node_modules/web-tree-sitter/lib/tree.c +170 -0
  813. package/lib/node_modules/web-tree-sitter/lib/tree.h +31 -0
  814. package/lib/node_modules/web-tree-sitter/lib/tree_cursor.c +716 -0
  815. package/lib/node_modules/web-tree-sitter/lib/tree_cursor.h +48 -0
  816. package/lib/node_modules/web-tree-sitter/lib/ts_assert.h +11 -0
  817. package/lib/node_modules/web-tree-sitter/lib/unicode.h +75 -0
  818. package/lib/node_modules/web-tree-sitter/lib/wasm_store.c +1937 -0
  819. package/lib/node_modules/web-tree-sitter/lib/wasm_store.h +31 -0
  820. package/lib/node_modules/web-tree-sitter/package.json +98 -0
  821. package/lib/node_modules/web-tree-sitter/src/bindings.ts +26 -0
  822. package/lib/node_modules/web-tree-sitter/src/constants.ts +133 -0
  823. package/lib/node_modules/web-tree-sitter/src/index.ts +31 -0
  824. package/lib/node_modules/web-tree-sitter/src/language.ts +291 -0
  825. package/lib/node_modules/web-tree-sitter/src/lookahead_iterator.ts +75 -0
  826. package/lib/node_modules/web-tree-sitter/src/marshal.ts +176 -0
  827. package/lib/node_modules/web-tree-sitter/src/node.ts +646 -0
  828. package/lib/node_modules/web-tree-sitter/src/parser.ts +325 -0
  829. package/lib/node_modules/web-tree-sitter/src/query.ts +973 -0
  830. package/lib/node_modules/web-tree-sitter/src/tree.ts +145 -0
  831. package/lib/node_modules/web-tree-sitter/src/tree_cursor.ts +318 -0
  832. package/lib/node_modules/web-tree-sitter/tree-sitter.cjs +4031 -0
  833. package/lib/node_modules/web-tree-sitter/tree-sitter.cjs.map +7 -0
  834. package/lib/node_modules/web-tree-sitter/tree-sitter.js +3980 -0
  835. package/lib/node_modules/web-tree-sitter/tree-sitter.js.map +7 -0
  836. package/lib/node_modules/web-tree-sitter/tree-sitter.wasm +0 -0
  837. package/lib/node_modules/web-tree-sitter/tree-sitter.wasm.map +30 -0
  838. package/lib/node_modules/web-tree-sitter/web-tree-sitter.d.ts +1030 -0
  839. package/lib/node_modules/web-tree-sitter/web-tree-sitter.d.ts.map +56 -0
  840. package/lib/package.json +64 -0
  841. package/node +0 -0
  842. package/package.json +17 -0
@@ -0,0 +1,2269 @@
1
+ "use strict";
2
+ /**
3
+ * Extraction Orchestrator
4
+ *
5
+ * Coordinates file scanning, parsing, and database storage.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ var __importDefault = (this && this.__importDefault) || function (mod) {
41
+ return (mod && mod.__esModule) ? mod : { "default": mod };
42
+ };
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;
45
+ exports.hashContent = hashContent;
46
+ exports.buildDefaultIgnore = buildDefaultIgnore;
47
+ exports.buildScopeIgnore = buildScopeIgnore;
48
+ exports.discoverEmbeddedRepoRoots = discoverEmbeddedRepoRoots;
49
+ exports.findUnindexedIgnoredRepos = findUnindexedIgnoredRepos;
50
+ exports.scanDirectory = scanDirectory;
51
+ exports.scanDirectoryAsync = scanDirectoryAsync;
52
+ const fs = __importStar(require("fs"));
53
+ const fsp = __importStar(require("fs/promises"));
54
+ const path = __importStar(require("path"));
55
+ const os = __importStar(require("os"));
56
+ const crypto = __importStar(require("crypto"));
57
+ const child_process_1 = require("child_process");
58
+ const tree_sitter_1 = require("./tree-sitter");
59
+ const parse_pool_1 = require("./parse-pool");
60
+ const grammars_1 = require("./grammars");
61
+ const project_config_1 = require("../project-config");
62
+ const directory_1 = require("../directory");
63
+ const errors_1 = require("../errors");
64
+ const utils_1 = require("../utils");
65
+ const ignore_1 = __importDefault(require("ignore"));
66
+ const frameworks_1 = require("../resolution/frameworks");
67
+ /**
68
+ * Number of files to read in parallel during indexing.
69
+ * File reads are I/O-bound; batching overlaps I/O wait with CPU parse work.
70
+ */
71
+ const FILE_IO_BATCH_SIZE = 10;
72
+ /**
73
+ * How many files the `sync()` reconcile processes between cooperative yields to
74
+ * the event loop. The reconcile runs two O(files) loops of synchronous `fs`
75
+ * calls (existsSync for removals, statSync for adds/mods); on a very large repo
76
+ * (~100k files) an un-yielded run wedges the main thread for minutes, which both
77
+ * trips the liveness watchdog (it SIGKILLs a process whose loop stops turning)
78
+ * and blocks the first MCP tool call behind the catch-up gate (issue #905).
79
+ * Yielding every N files keeps the socket, the watchdog heartbeat, and any
80
+ * concurrent read query responsive while the reconcile runs.
81
+ */
82
+ const SYNC_RECONCILE_YIELD_INTERVAL = 1000;
83
+ // PARSER_RESET_INTERVAL moved to parse-worker.ts (runs in worker thread)
84
+ /**
85
+ * Maximum time (ms) to wait for a single file to parse in the worker thread.
86
+ * If tree-sitter hangs or WASM runs out of memory, this prevents the entire
87
+ * indexing run from freezing. The worker is restarted after a timeout.
88
+ */
89
+ const PARSE_TIMEOUT_MS = 10_000;
90
+ /**
91
+ * Number of files to parse before recycling the worker thread.
92
+ * WASM linear memory can grow but NEVER shrink (WebAssembly spec limitation).
93
+ * The only way to reclaim tree-sitter's WASM heap is to destroy the entire
94
+ * V8 isolate by terminating the worker thread and spawning a fresh one.
95
+ * This interval balances memory usage against the cost of reloading grammars.
96
+ */
97
+ const WORKER_RECYCLE_INTERVAL = 250;
98
+ /**
99
+ * Calculate SHA256 hash of file contents
100
+ */
101
+ function hashContent(content) {
102
+ return crypto.createHash('sha256').update(content).digest('hex');
103
+ }
104
+ /**
105
+ * Skip files larger than this (bytes). Generated bundles, minified JS, and
106
+ * vendored blobs blow the WASM heap and the worker-recycle budget for no useful
107
+ * symbols. 1 MB covers essentially all hand-written source.
108
+ */
109
+ const MAX_FILE_SIZE = 1024 * 1024;
110
+ /**
111
+ * Directory names that are dependency, build, cache, or tooling output across the
112
+ * languages/frameworks BHGraph supports — curated from the canonical
113
+ * github/gitignore templates. Excluded by default so the graph reflects your code,
114
+ * not third-party noise, without requiring a `.gitignore` (issue #407). The
115
+ * exclusion applies uniformly (git or not, tracked or not); the only opt-in is an
116
+ * explicit `.gitignore` negation (e.g. `!vendor/`). First-party-prone or generic
117
+ * names (`packages`, `lib`, `app`, `bin`, `src`, `deps`, `env`, `tmp`, `storage`,
118
+ * `Library`) are deliberately NOT listed, to avoid ever hiding real source.
119
+ *
120
+ * Only dirs that actually contain *indexable source* (or are enormous) earn a slot
121
+ * — IDE/state dirs like `.idea`/`.vs` are omitted because BHGraph indexes only
122
+ * recognized source extensions, so they produce no symbols regardless.
123
+ */
124
+ const DEFAULT_IGNORE_DIRS = new Set([
125
+ // JS / TS — dependency directories
126
+ 'node_modules', 'bower_components', 'jspm_packages', 'web_modules',
127
+ '.yarn', '.pnpm-store',
128
+ // JS / TS — framework & bundler build / cache / deploy output
129
+ '.next', '.nuxt', '.svelte-kit', '.turbo', '.vite', '.parcel-cache', '.angular',
130
+ '.docusaurus', 'storybook-static', '.vinxi', '.nitro', 'out-tsc',
131
+ '.vercel', '.netlify', '.wrangler',
132
+ // Build output (common across ecosystems)
133
+ 'dist', 'build', 'out', '.output',
134
+ // Test / coverage
135
+ 'coverage', '.nyc_output',
136
+ // Python
137
+ '__pycache__', '__pypackages__', '.venv', 'venv', '.pixi', '.pdm-build',
138
+ '.mypy_cache', '.pytest_cache', '.ruff_cache', '.tox', '.nox', '.hypothesis',
139
+ '.ipynb_checkpoints', '.eggs',
140
+ // Rust / JVM (Maven, Gradle, Scala)
141
+ 'target', '.gradle',
142
+ // .NET
143
+ 'obj',
144
+ // Vendored deps (Go, PHP/Composer, Ruby/Bundler)
145
+ 'vendor',
146
+ // Swift / iOS
147
+ '.build', 'Pods', 'Carthage', 'DerivedData', '.swiftpm',
148
+ // Dart / Flutter
149
+ '.dart_tool', '.pub-cache',
150
+ // Native (Android NDK, C/C++ deps)
151
+ '.cxx', '.externalNativeBuild', 'vcpkg_installed',
152
+ // Scala tooling
153
+ '.bloop', '.metals',
154
+ // Lua / Luau (LuaRocks)
155
+ 'lua_modules', '.luarocks',
156
+ // Delphi / RAD Studio IDE backups (duplicate .pas source — would double-count)
157
+ '__history', '__recovery',
158
+ // Generic cache
159
+ '.cache',
160
+ ]);
161
+ /**
162
+ * Android resource directory types. A `res/` tree holds ONLY non-code resources —
163
+ * layouts, drawables, value bags (strings/colors/styles), menus, navigation
164
+ * graphs — split into one typed subdirectory per kind, optionally density/locale/
165
+ * version-qualified (`values-es`, `drawable-hdpi`, `layout-v21`, …). None of it
166
+ * yields an extractable code symbol, yet on an Android app it DOMINATES the tree
167
+ * (one report: 26k XML files = 97% of the project, 0 symbols), bloating the DB,
168
+ * slowing indexing, and skewing both the file count and `bhgraph_explore`
169
+ * results (#1047). So these are excluded by default. The structure is
170
+ * self-identifying — a non-Android project has no `res/layout/` etc., so it's
171
+ * untouched — and the only XML that DOES produce symbols (MyBatis mappers) lives
172
+ * under `src/main/resources/`, never `res/`, so nothing useful is dropped.
173
+ * `res/raw/` is deliberately NOT here: it holds arbitrary bundled assets that can
174
+ * be code-ish (a `.sql` schema, a `.js`), so we leave it indexed. Override any of
175
+ * these with a `.gitignore` negation (e.g. `!res/values/`).
176
+ */
177
+ const ANDROID_RES_TYPES = [
178
+ 'anim', 'animator', 'color', 'drawable', 'font', 'layout',
179
+ 'menu', 'mipmap', 'navigation', 'transition', 'values', 'xml',
180
+ ];
181
+ /** Gitignore-style patterns for the `ignore` matcher: the dirs above plus a few globs. */
182
+ const DEFAULT_IGNORE_PATTERNS = [
183
+ ...Array.from(DEFAULT_IGNORE_DIRS, (d) => `${d}/`),
184
+ '*.egg-info/', // Python packaging metadata
185
+ 'cmake-build-*/', // CLion / CMake build trees
186
+ 'bazel-*/', // Bazel output symlink trees
187
+ // Android resource dirs at any depth, with their qualifier variants (#1047).
188
+ ...ANDROID_RES_TYPES.map((t) => `**/res/${t}*/`),
189
+ ];
190
+ /** True if `buf` decodes as strict UTF-8 (no invalid byte sequences). */
191
+ function isValidUtf8(buf) {
192
+ try {
193
+ new TextDecoder('utf-8', { fatal: true }).decode(buf);
194
+ return true;
195
+ }
196
+ catch {
197
+ return false;
198
+ }
199
+ }
200
+ /**
201
+ * Read a `.gitignore` and return patterns safe to hand to the `ignore` matcher —
202
+ * never throwing, even when the file isn't real gitignore text. Two failure
203
+ * modes, both seen in the wild (issue #682):
204
+ *
205
+ * - The file isn't valid UTF-8 — e.g. transparently encrypted in place by
206
+ * corporate DLP / endpoint-security software, leaving a UTF-16 header plus
207
+ * ciphertext. None of it is meaningful patterns, so the whole file is skipped.
208
+ * - The file is text but a single line can't be compiled to a regex by the
209
+ * `ignore` library — `\\[` and friends throw "Unterminated character class".
210
+ * Crucially the throw is LAZY (at match time, not `.add()`), so it would
211
+ * otherwise escape mid-scan. That one pattern is dropped; the rest are kept.
212
+ *
213
+ * Either way a warning that NAMES the file is logged (the reporter couldn't tell
214
+ * which `.gitignore` was at fault) and indexing continues instead of aborting.
215
+ * Returns '' when there's nothing usable.
216
+ */
217
+ function readGitignorePatterns(giPath) {
218
+ let buf;
219
+ try {
220
+ buf = fs.readFileSync(giPath);
221
+ }
222
+ catch {
223
+ return ''; // unreadable (permissions / race) — treat as absent
224
+ }
225
+ // A NUL byte never appears in real gitignore text, and a fatal UTF-8 decode
226
+ // catches the rest. Such a file isn't ignore patterns at all.
227
+ if (buf.includes(0) || !isValidUtf8(buf)) {
228
+ (0, errors_1.logWarn)('Ignoring a .gitignore that is not valid UTF-8 text — it may have been encrypted ' +
229
+ 'in place by endpoint-security software. Indexing continues without it.', { file: giPath });
230
+ return '';
231
+ }
232
+ const content = buf.toString('utf-8');
233
+ // Fast path: one `.ignores()` call forces the library to compile EVERY rule,
234
+ // so if it doesn't throw, the whole file is safe to use verbatim.
235
+ try {
236
+ (0, ignore_1.default)().add(content).ignores('.bhgraph-probe');
237
+ return content;
238
+ }
239
+ catch {
240
+ // Fall through: a line is uncompilable — keep the good ones, drop the bad.
241
+ }
242
+ const kept = [];
243
+ let dropped = 0;
244
+ for (const line of content.split(/\r?\n/)) {
245
+ try {
246
+ (0, ignore_1.default)().add(line).ignores('.bhgraph-probe');
247
+ kept.push(line);
248
+ }
249
+ catch {
250
+ dropped++;
251
+ }
252
+ }
253
+ if (dropped > 0) {
254
+ (0, errors_1.logWarn)(`Skipped ${dropped} unparseable pattern(s) in a .gitignore; the rest are applied.`, { file: giPath });
255
+ }
256
+ return kept.join('\n');
257
+ }
258
+ /**
259
+ * An `ignore` matcher seeded with the built-in defaults, merged with the project's
260
+ * root .gitignore so a negation there (e.g. `!vendor/`) overrides a default. Shared
261
+ * by both enumeration paths so behavior is identical with or without git — and so
262
+ * the defaults apply to tracked files too (committing a dependency dir doesn't make
263
+ * it project code; the explicit `.gitignore` negation is the only opt-in).
264
+ */
265
+ function buildDefaultIgnore(rootDir) {
266
+ const ig = (0, ignore_1.default)().add(DEFAULT_IGNORE_PATTERNS);
267
+ const rootGitignore = path.join(rootDir, '.gitignore');
268
+ if (fs.existsSync(rootGitignore))
269
+ ig.add(readGitignorePatterns(rootGitignore));
270
+ return ig;
271
+ }
272
+ /**
273
+ * Defaults-only ignore matcher (no root `.gitignore` merged). Used wherever the
274
+ * parent repo's own ignore rules must NOT apply — inside embedded child repos,
275
+ * whose gitignore semantics their own `git ls-files` already enforced (#514).
276
+ */
277
+ function defaultsOnlyIgnore() {
278
+ return (0, ignore_1.default)().add(DEFAULT_IGNORE_PATTERNS);
279
+ }
280
+ /**
281
+ * Matcher for the project's `bhgraph.json` `includeIgnored` patterns — the
282
+ * explicit opt-in to index embedded git repos living inside gitignored
283
+ * directories (#622, #699). Returns `null` when the project opted in nothing,
284
+ * which is the zero-config DEFAULT: `.gitignore` is then fully respected and a
285
+ * gitignored directory (even one holding nested repos) is never walked or
286
+ * indexed (#970, #976). Built once per scan/sync/scope operation from the scan
287
+ * root and threaded down — never global, so multi-project daemons stay isolated.
288
+ */
289
+ function loadIncludeIgnoredMatcher(rootDir) {
290
+ const patterns = (0, project_config_1.loadIncludeIgnoredPatterns)(rootDir);
291
+ return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
292
+ }
293
+ /**
294
+ * Matcher for the project's `bhgraph.json` `exclude` patterns — paths to keep
295
+ * OUT of the index even when git-tracked, which `.gitignore` cannot do (#999).
296
+ * The escape hatch for a committed vendor/theme/SDK directory. Returns `null`
297
+ * when nothing is excluded (the zero-config default → no overhead). Matched
298
+ * against project-root-relative paths, so it applies uniformly across the whole
299
+ * workspace, including inside embedded repos (excluding `static/` means gone
300
+ * everywhere). Built once per scan/sync/scope operation from the scan root.
301
+ */
302
+ function loadExcludeMatcher(rootDir) {
303
+ const patterns = (0, project_config_1.loadExcludePatterns)(rootDir);
304
+ return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
305
+ }
306
+ /**
307
+ * Matcher for the project's `bhgraph.json` `include` patterns — first-party
308
+ * source to force INTO the index even when `.gitignore` drops it (the general
309
+ * whitelist `includeIgnored` never was — that one only revives *embedded git
310
+ * repos*). The case it exists for: a project under a second VCS (SVN/Perforce)
311
+ * `.gitignore`s its own real source so it stays out of Git, yet we still want it
312
+ * indexed. Returns `null` when nothing is force-included (the zero-config
313
+ * default → no overhead, no extra walk). Built once per scan/sync/scope
314
+ * operation from the scan root.
315
+ */
316
+ function loadIncludeMatcher(rootDir) {
317
+ const patterns = (0, project_config_1.loadIncludePatterns)(rootDir);
318
+ return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
319
+ }
320
+ /** Glob metacharacters that end the static (literal) prefix of an `include` pattern. */
321
+ const GLOB_META = /[*?[\]{}!]/;
322
+ /**
323
+ * The static directory prefix of each `include` pattern — the literal leading
324
+ * path up to the first glob segment — trailing-slashed, used to (a) walk only
325
+ * the opted-in subtrees in `collectIncludedFiles` and (b) let `ScopeIgnore` keep
326
+ * the watcher descending toward them. `Tools/` stays `Tools/`; a recursive
327
+ * `Tools/**` glob yields `Tools/`; `src/local/file.ts` yields `src/local/` (the
328
+ * file's dir); a pattern that starts with a glob (like a leading `**`) yields
329
+ * `''`, meaning "no static root — walk the whole tree". Duplicates and roots
330
+ * nested under a broader root are collapsed so each subtree is walked once.
331
+ */
332
+ function includeStaticRoots(patterns) {
333
+ const roots = new Set();
334
+ for (const pattern of patterns) {
335
+ let p = pattern.replace(/^\/+/, '');
336
+ const trailingSlash = p.endsWith('/');
337
+ if (trailingSlash)
338
+ p = p.slice(0, -1);
339
+ const segs = p.split('/').filter(Boolean);
340
+ const lead = [];
341
+ for (const s of segs) {
342
+ if (GLOB_META.test(s))
343
+ break;
344
+ lead.push(s);
345
+ }
346
+ const hadWildcard = lead.length < segs.length;
347
+ // A wholly-literal pattern with no trailing slash names a file (or a dir we
348
+ // can't tell apart) — drop its last segment so we walk the containing dir
349
+ // and let the matcher pick the file. A trailing slash or a glob means the
350
+ // remaining `lead` is already the directory to walk.
351
+ if (!hadWildcard && !trailingSlash && lead.length > 0)
352
+ lead.pop();
353
+ if (lead.length === 0) {
354
+ roots.clear();
355
+ roots.add('');
356
+ return ['']; // a top-level glob forces a whole-tree walk; nothing narrower matters
357
+ }
358
+ roots.add(lead.join('/') + '/');
359
+ }
360
+ // Collapse roots nested under a broader one (e.g. drop `a/b/` if `a/` is present).
361
+ const all = [...roots];
362
+ return all.filter((r) => !all.some((other) => other !== r && r.startsWith(other)));
363
+ }
364
+ /**
365
+ * Actively discover the source files an `include` whitelist forces in. `git
366
+ * ls-files` never lists gitignored files, so a filtered filesystem walk of just
367
+ * the opted-in subtrees (`includeStaticRoots`) is the only way to find them.
368
+ * Returns project-root-relative, normalized source-file paths.
369
+ *
370
+ * A file is collected when it MATCHES `include`, is NOT hit by `exclude` (an
371
+ * explicit exclude always wins), is a recognized source file, and does not live
372
+ * under a built-in default-ignored dir (`node_modules`, `dist`, …), `.git`, or
373
+ * BHGraph's data dir — those are never resurfaced, mirroring `ScopeIgnore`.
374
+ * `.gitignore` is deliberately NOT consulted: overriding it is the whole point.
375
+ */
376
+ function collectIncludedFiles(rootDir, include, exclude, roots, overrides) {
377
+ const out = new Set();
378
+ const defaults = defaultsOnlyIgnore();
379
+ const visited = new Set();
380
+ const consider = (abs, rel, isDir) => {
381
+ if (isDir) {
382
+ if (defaults.ignores(rel + '/'))
383
+ return; // never node_modules/dist/… via include
384
+ // An explicit `exclude` always wins over `include`; prune the whole subtree
385
+ // here so a large excluded dir (a committed frontend's own vendored deps,
386
+ // build output, …) is never walked — the per-file guard below still catches
387
+ // anything a directory pattern doesn't, so this is a pure efficiency win.
388
+ if (exclude && exclude.ignores(rel + '/'))
389
+ return;
390
+ walk(abs);
391
+ }
392
+ else {
393
+ if (defaults.ignores(rel))
394
+ return;
395
+ if (!include.ignores(rel))
396
+ return;
397
+ if (exclude && exclude.ignores(rel))
398
+ return;
399
+ if (!(0, grammars_1.isSourceFile)(rel, overrides))
400
+ return;
401
+ out.add(rel);
402
+ }
403
+ };
404
+ function walk(absDir) {
405
+ let realDir;
406
+ try {
407
+ realDir = fs.realpathSync(absDir);
408
+ }
409
+ catch {
410
+ return;
411
+ }
412
+ if (visited.has(realDir))
413
+ return; // symlink-cycle guard
414
+ visited.add(realDir);
415
+ let entries;
416
+ try {
417
+ entries = fs.readdirSync(absDir, { withFileTypes: true });
418
+ }
419
+ catch {
420
+ return;
421
+ }
422
+ for (const entry of entries) {
423
+ if (entry.name === '.git' || (0, directory_1.isBHGraphDataDir)(entry.name))
424
+ continue;
425
+ const abs = path.join(absDir, entry.name);
426
+ const rel = (0, utils_1.normalizePath)(path.relative(rootDir, abs));
427
+ if (!rel || rel.startsWith('..'))
428
+ continue;
429
+ if (entry.isSymbolicLink()) {
430
+ try {
431
+ const st = fs.statSync(fs.realpathSync(abs));
432
+ consider(abs, rel, st.isDirectory());
433
+ }
434
+ catch {
435
+ // broken symlink — skip
436
+ }
437
+ continue;
438
+ }
439
+ consider(abs, rel, entry.isDirectory());
440
+ }
441
+ }
442
+ for (const root of roots) {
443
+ walk(root === '' ? rootDir : path.join(rootDir, root));
444
+ }
445
+ return out;
446
+ }
447
+ /**
448
+ * The included source files (`bhgraph.json` `include`) for a scan root, or an
449
+ * empty set when nothing is force-included. Centralizes loading the matcher,
450
+ * roots, exclude, and overrides so both enumeration paths (git and filesystem
451
+ * walk) add the same files.
452
+ */
453
+ function collectIncludedFilesForRoot(rootDir) {
454
+ const include = loadIncludeMatcher(rootDir);
455
+ if (!include)
456
+ return new Set();
457
+ const roots = includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir));
458
+ return collectIncludedFiles(rootDir, include, loadExcludeMatcher(rootDir), roots, (0, project_config_1.loadExtensionOverrides)(rootDir));
459
+ }
460
+ /**
461
+ * `git ls-files --directory` collapses a wholly-untracked/ignored directory into
462
+ * one entry — and when the command's own cwd is such a directory (the indexed
463
+ * root is itself a git-ignored subdir of an enclosing repo), git emits the
464
+ * literal `./` meaning "this entire directory". That sentinel is not a real
465
+ * nested path: feeding it to the `ignore` matcher throws ("path should be a
466
+ * `path.relative()`d string, but got "./""), which used to abort `buildScopeIgnore`
467
+ * and so break the MCP daemon's watcher/auto-sync on connect; and joining it back
468
+ * onto `repoDir` would just re-point at the cwd. Drop it wherever we consume
469
+ * `--directory` output. (#936)
470
+ */
471
+ function isWholeCwdEntry(entry) {
472
+ return entry === './' || entry === '.' || entry === '';
473
+ }
474
+ /**
475
+ * List the gitignored DIRECTORIES of a repo (collapsed, trailing-slash form),
476
+ * relative to `repoDir`. These are invisible to every other `git ls-files` /
477
+ * `git status` mode — and in a multi-repo workspace they are exactly where the
478
+ * nested project repos live (a super-repo `.gitignore`s its child repos to keep
479
+ * `git status` quiet; that does not make them third-party code). (#514)
480
+ */
481
+ function listIgnoredDirs(repoDir) {
482
+ try {
483
+ const out = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-o', '-i', '--exclude-standard', '--directory'], { cwd: repoDir, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
484
+ return out.split('\0').filter((e) => e.endsWith('/') && !isWholeCwdEntry(e));
485
+ }
486
+ catch {
487
+ return [];
488
+ }
489
+ }
490
+ /** Max directory depth searched below an ignored dir for nested `.git` roots. */
491
+ const EMBEDDED_REPO_SEARCH_DEPTH = 4;
492
+ /** Max directories examined per search — a huge ignored data dir must never stall a scan/sync. */
493
+ const EMBEDDED_REPO_SEARCH_ENTRIES = 2000;
494
+ /**
495
+ * Classify a directory's `.git` entry for embedded-repo discovery.
496
+ *
497
+ * - A `.git` **directory** is an embedded clone — distinct first-party code a
498
+ * super-repo merely hides from git; index it (#193, #514).
499
+ * - A `.git` **file** is a pointer (`gitdir: …`). A git **worktree** points into
500
+ * the host repo's own `.git/worktrees/<name>`, so it is a second working view
501
+ * of a repo BHGraph already indexes — indexing it just duplicates the whole
502
+ * graph N times; skip it (#848). A **submodule worktree** points into
503
+ * `.git/modules/<module>/worktrees/<name>` — same duplication, so skip it too
504
+ * (#945). A **submodule** checkout points into `.git/modules/<module>` (no
505
+ * `worktrees/` segment) and is distinct code, so index it as before.
506
+ *
507
+ * Returns `'none'` when there is no `.git` entry here.
508
+ */
509
+ function classifyGitDir(absDir) {
510
+ let st;
511
+ try {
512
+ st = fs.statSync(path.join(absDir, '.git'));
513
+ }
514
+ catch {
515
+ return 'none';
516
+ }
517
+ if (st.isDirectory())
518
+ return 'embedded';
519
+ if (!st.isFile())
520
+ return 'none';
521
+ try {
522
+ const gitdir = fs.readFileSync(path.join(absDir, '.git'), 'utf8').match(/^gitdir:\s*(.+)$/m)?.[1]?.trim();
523
+ // A worktree's gitdir lives under some repo's `.git/worktrees/<name>` —
524
+ // either the top-level repo's (`.git/worktrees/`) or, for a worktree of a
525
+ // submodule, that submodule's gitdir (`.git/modules/<module>/worktrees/`).
526
+ // The optional `modules/<module>` segment covers the submodule case (#945).
527
+ // Match both separators so a Windows-style pointer is recognized too.
528
+ if (gitdir && /(^|[\\/])\.git[\\/](modules[\\/][^\\/]+[\\/])?worktrees[\\/]/.test(gitdir))
529
+ return 'worktree';
530
+ }
531
+ catch {
532
+ // Unreadable `.git` pointer — fall back to the prior "index it" behavior.
533
+ }
534
+ return 'embedded';
535
+ }
536
+ /**
537
+ * Find git repositories nested under `absDir` (inclusive), shallow bounded BFS.
538
+ * Stops descending at each repo root found — contents belong to that repo's own
539
+ * enumeration. Skips default-ignored dirs (`node_modules` can contain `.git`
540
+ * from npm git-dependencies — that never makes it project code) and BHGraph
541
+ * data dirs. Depth- and entry-capped so a huge ignored tree can't stall the scan.
542
+ */
543
+ function findNestedGitRepos(absDir, relPrefix) {
544
+ const found = [];
545
+ const defaults = defaultsOnlyIgnore();
546
+ const queue = [
547
+ { abs: absDir, rel: relPrefix, depth: 0 },
548
+ ];
549
+ let examined = 0;
550
+ while (queue.length > 0) {
551
+ const { abs, rel, depth } = queue.shift();
552
+ if (++examined > EMBEDDED_REPO_SEARCH_ENTRIES) {
553
+ (0, errors_1.logDebug)('Embedded-repo search entry cap hit — deeper repos (if any) not discovered', { under: relPrefix });
554
+ break;
555
+ }
556
+ const cls = classifyGitDir(abs);
557
+ if (cls === 'worktree') {
558
+ continue; // a git worktree duplicates an already-indexed repo (#848) — skip
559
+ }
560
+ if (cls === 'embedded') {
561
+ found.push(rel);
562
+ continue; // its own git handles everything below
563
+ }
564
+ if (depth >= EMBEDDED_REPO_SEARCH_DEPTH)
565
+ continue;
566
+ let entries;
567
+ try {
568
+ entries = fs.readdirSync(abs, { withFileTypes: true });
569
+ }
570
+ catch {
571
+ continue;
572
+ }
573
+ for (const entry of entries) {
574
+ if (!entry.isDirectory())
575
+ continue;
576
+ if (entry.name === '.git' || (0, directory_1.isBHGraphDataDir)(entry.name))
577
+ continue;
578
+ const childRel = rel + entry.name + '/';
579
+ if (defaults.ignores(childRel))
580
+ continue;
581
+ queue.push({ abs: path.join(abs, entry.name), rel: childRel, depth: depth + 1 });
582
+ }
583
+ }
584
+ return found;
585
+ }
586
+ /**
587
+ * Workspace-scope ignore matcher. Ordinary paths get the root's matcher
588
+ * (built-in defaults + root `.gitignore`); paths inside an EMBEDDED repo get
589
+ * that repo's own matcher (defaults + its root `.gitignore`) — the parent's
590
+ * `.gitignore` hides a child repo from git, not from the index (#514). A
591
+ * directory path (trailing slash) that is an ANCESTOR of an embedded root is
592
+ * never ignored, so directory-pruning callers (the Linux per-directory
593
+ * watcher) still descend to reach the embedded repos.
594
+ *
595
+ * Single source of truth for indexer and watcher scope — they must not diverge.
596
+ */
597
+ class ScopeIgnore {
598
+ rootMatcher;
599
+ exclude;
600
+ include;
601
+ includeRoots;
602
+ embedded;
603
+ defaults = defaultsOnlyIgnore();
604
+ constructor(rootMatcher, embedded,
605
+ /**
606
+ * Project `bhgraph.json` `exclude` patterns (#999), matched against the
607
+ * full root-relative path. Wins over everything else — an explicit user
608
+ * exclude applies even to tracked files and even inside embedded repos.
609
+ */
610
+ exclude = null,
611
+ /**
612
+ * Project `bhgraph.json` `include` patterns — first-party source forced
613
+ * INTO the index despite `.gitignore`. When a path matches, it is NOT
614
+ * ignored (so the watcher watches it), overriding `.gitignore`/`rootMatcher`
615
+ * — but never `exclude` (checked first) and never a built-in default-ignored
616
+ * dir. `includeRoots` are the static prefixes so a gitignored ANCESTOR
617
+ * directory of an included subtree still isn't pruned by the directory
618
+ * walker/watcher.
619
+ */
620
+ include = null, includeRoots = []) {
621
+ this.rootMatcher = rootMatcher;
622
+ this.exclude = exclude;
623
+ this.include = include;
624
+ this.includeRoots = includeRoots;
625
+ // Longest root first so paths in nested embedded repos hit the innermost matcher.
626
+ this.embedded = [...embedded].sort((a, b) => b.root.length - a.root.length);
627
+ }
628
+ ignores(rel) {
629
+ // User `exclude` (#999) is checked first and against the full root-relative
630
+ // path: it must drop git-TRACKED paths (which `.gitignore` can't) and apply
631
+ // everywhere, including ancestors of embedded repos.
632
+ if (this.exclude && this.exclude.ignores(rel))
633
+ return true;
634
+ // User `include`: force first-party source in despite `.gitignore`. Never
635
+ // resurfaces a built-in default-ignored dir (node_modules/dist/…), so an
636
+ // include pattern can't accidentally pull in dependency/build trees.
637
+ if (this.include && !this.defaults.ignores(rel)) {
638
+ if (rel.endsWith('/')) {
639
+ // A directory on (or leading to) an included subtree must stay walkable
640
+ // so the watcher/walker descends to reach the forced-in files.
641
+ if (this.includeRoots.some((r) => r.startsWith(rel) || rel.startsWith(r)))
642
+ return false;
643
+ }
644
+ else if (this.include.ignores(rel)) {
645
+ return false;
646
+ }
647
+ }
648
+ for (const { root, matcher } of this.embedded) {
649
+ if (rel.startsWith(root)) {
650
+ const inner = rel.slice(root.length);
651
+ if (inner === '')
652
+ return false;
653
+ // Built-in defaults apply to the FULL path uniformly (#407) — an
654
+ // embedded repo inside node_modules (an npm git-dependency) must stay
655
+ // excluded even though its own rules wouldn't ignore its files.
656
+ return this.defaults.ignores(rel) || matcher.ignores(inner);
657
+ }
658
+ }
659
+ // Never prune a directory that leads to an embedded repo.
660
+ if (rel.endsWith('/') && this.embedded.some(({ root }) => root.startsWith(rel))) {
661
+ return false;
662
+ }
663
+ return this.rootMatcher.ignores(rel);
664
+ }
665
+ }
666
+ exports.ScopeIgnore = ScopeIgnore;
667
+ /**
668
+ * Build the workspace-scope matcher. When the caller already knows the
669
+ * embedded roots (the scanner discovers them during collection), pass them to
670
+ * skip rediscovery; otherwise they're discovered here (the watcher path).
671
+ */
672
+ function buildScopeIgnore(rootDir, embeddedRoots) {
673
+ const roots = embeddedRoots ? [...embeddedRoots] : discoverEmbeddedRepoRoots(rootDir);
674
+ const include = loadIncludeMatcher(rootDir);
675
+ return new ScopeIgnore(buildDefaultIgnore(rootDir), roots.map((root) => ({ root, matcher: buildDefaultIgnore(path.join(rootDir, root)) })), loadExcludeMatcher(rootDir), include, include ? includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir)) : []);
676
+ }
677
+ /**
678
+ * Whether an embedded repo found as a tracked gitlink (mode 160000, #1031/#1033)
679
+ * must be SKIPPED rather than indexed. A gitlink is tracked, so `.gitignore`
680
+ * can't untrack it — but the discovery passes for it must still honor the same
681
+ * scope rules as every other path, or a gitignored reference/data dir full of
682
+ * `git add`ed clones gets pulled into the index against the user's stated intent
683
+ * (#1065). Two reasons to skip:
684
+ * 1. It sits in a built-in default-ignored location — an npm git-dependency
685
+ * under `node_modules` is never project code; not even an explicit opt-in
686
+ * revives it (matches `findIgnoredEmbeddedRepos`).
687
+ * 2. The parent repo's own `.gitignore` covers its path and the project did
688
+ * NOT opt that path in via `bhgraph.json` `includeIgnored`. The gitignore
689
+ * rule is the user's stated intent to keep that path out of scope, exactly
690
+ * as for an UNtracked embedded repo — respect it by default, opt back in
691
+ * with `includeIgnored` (#514, #970, #976).
692
+ * `relDir` is repoDir-relative (trailing-slashed); `prefix` is repoDir's
693
+ * scan-root-relative path so the `includeIgnored` pattern is matched on the full
694
+ * scan-root-relative path. `defaults` is `defaultsOnlyIgnore()` and `repoIgnore`
695
+ * is `buildDefaultIgnore(repoDir)` (defaults + the repo's own `.gitignore`),
696
+ * both passed in so they're built once per repo level rather than per gitlink.
697
+ */
698
+ function gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored) {
699
+ if (defaults.ignores(relDir))
700
+ return true; // default-ignored — never index, opt-in can't revive
701
+ if (!repoIgnore.ignores(relDir))
702
+ return false; // not ignored at all — index as before (#1031/#1033)
703
+ // Gitignored by the repo's own rules — skip unless the project opted it in.
704
+ return !includeIgnored?.ignores((0, utils_1.normalizePath)(prefix + relDir));
705
+ }
706
+ /**
707
+ * Standalone discovery of every embedded repo root under `rootDir` (relative,
708
+ * trailing-slashed) — the untracked kind (#193) always, and the gitignored kind
709
+ * (#514) only for directories the project opted in via `bhgraph.json`
710
+ * `includeIgnored` (#622, #699); otherwise `.gitignore` is respected and they
711
+ * are not discovered (#970, #976). Recursive (an embedded repo can embed further
712
+ * repos). Returns [] for non-git roots: the filesystem walk handles nested repos
713
+ * there already.
714
+ */
715
+ function discoverEmbeddedRepoRoots(rootDir) {
716
+ try {
717
+ (0, child_process_1.execFileSync)('git', ['rev-parse', '--git-dir'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
718
+ }
719
+ catch {
720
+ return [];
721
+ }
722
+ const out = [];
723
+ const defaults = defaultsOnlyIgnore();
724
+ const includeIgnored = loadIncludeIgnoredMatcher(rootDir);
725
+ const visit = (repoAbs, prefix) => {
726
+ const candidates = [];
727
+ try {
728
+ const o = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-o', '--exclude-standard', '--directory'], { cwd: repoAbs, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
729
+ for (const e of o.split('\0')) {
730
+ if (e.endsWith('/') && !isWholeCwdEntry(e) && !defaults.ignores(e)) {
731
+ candidates.push(...findNestedGitRepos(path.join(repoAbs, e), e));
732
+ }
733
+ }
734
+ }
735
+ catch { /* untracked listing failed — ignored-side discovery still runs */ }
736
+ // Unexpanded gitlinks (mode 160000) with a real checkout on disk — embedded
737
+ // repos `git add`ed without `.gitmodules`, or submodules not active here. The
738
+ // untracked listing above can't see them (they're tracked), so find them the
739
+ // same way collectGitFiles does, keeping watcher scope == indexer scope.
740
+ // (#1031, #1033)
741
+ try {
742
+ const staged = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-s', '--recurse-submodules'], { cwd: repoAbs, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
743
+ const repoIgnore = buildDefaultIgnore(repoAbs);
744
+ for (const entry of staged.split('\0')) {
745
+ if (!entry || entry.slice(0, 6) !== '160000')
746
+ continue;
747
+ const tab = entry.indexOf('\t');
748
+ if (tab === -1)
749
+ continue;
750
+ const rel = entry.slice(tab + 1);
751
+ const relDir = rel.endsWith('/') ? rel : rel + '/';
752
+ // A gitlink under a gitignored path is respected (not indexed) unless the
753
+ // project opted it in — same rule as the untracked-ignored kind (#1065).
754
+ if (gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored))
755
+ continue;
756
+ if (classifyGitDir(path.join(repoAbs, rel)) === 'embedded')
757
+ candidates.push(relDir);
758
+ }
759
+ }
760
+ catch { /* staged listing failed — other discovery still runs */ }
761
+ candidates.push(...findIgnoredEmbeddedRepos(repoAbs, includeIgnored, prefix));
762
+ for (const rel of candidates) {
763
+ const full = (0, utils_1.normalizePath)(prefix + rel);
764
+ out.push(full);
765
+ visit(path.join(repoAbs, rel), full);
766
+ }
767
+ };
768
+ visit(rootDir, '');
769
+ return out;
770
+ }
771
+ /**
772
+ * Cap on how many skipped gitignored repos the CLI hint enumerates — a huge
773
+ * gitignored data dir full of clones must never turn the hint scan into a long
774
+ * walk. Enough to make the point; the caller says "+N more" past this.
775
+ */
776
+ const UNINDEXED_IGNORED_REPO_HINT_CAP = 100;
777
+ /**
778
+ * The INVERSE of the gitignored side of {@link discoverEmbeddedRepoRoots}:
779
+ * nested git repositories under a gitignored directory that the project has NOT
780
+ * opted into via `bhgraph.json` `includeIgnored`. These are real repos the
781
+ * default `init`/`index` deliberately skips because `.gitignore` excludes them
782
+ * (#970, #976) — most visibly the "super-repo `.gitignore`s its child repos"
783
+ * layout (#1156), where `init` at the parent correctly indexes ~nothing while
784
+ * `init` inside each child works. The CLI uses this to turn that silent empty
785
+ * index into an actionable hint: it names the skipped repos and offers to opt
786
+ * them in. Paths are `rootDir`-relative and trailing-slashed (valid
787
+ * `includeIgnored` patterns as-is). Returns `[]` for a non-git root (a
788
+ * filesystem walk already descends into nested repos there), skips built-in
789
+ * default-ignored dirs (`node_modules`, …), and is bounded so it never stalls
790
+ * on a giant ignored tree.
791
+ */
792
+ function findUnindexedIgnoredRepos(rootDir) {
793
+ try {
794
+ (0, child_process_1.execFileSync)('git', ['rev-parse', '--git-dir'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
795
+ }
796
+ catch {
797
+ return [];
798
+ }
799
+ const defaults = defaultsOnlyIgnore();
800
+ const includeIgnored = loadIncludeIgnoredMatcher(rootDir);
801
+ const repos = [];
802
+ for (const dir of listIgnoredDirs(rootDir)) {
803
+ if (defaults.ignores(dir))
804
+ continue; // node_modules etc. — never project code
805
+ if (includeIgnored?.ignores((0, utils_1.normalizePath)(dir)))
806
+ continue; // already opted in — nothing to nag about
807
+ for (const repo of findNestedGitRepos(path.join(rootDir, dir), dir)) {
808
+ repos.push(repo);
809
+ if (repos.length >= UNINDEXED_IGNORED_REPO_HINT_CAP)
810
+ return repos;
811
+ }
812
+ }
813
+ return repos;
814
+ }
815
+ /**
816
+ * Discover embedded repos hidden by `repoDir`'s OWN gitignore rules: for each
817
+ * gitignored directory, search for nested `.git` roots. Returns repo paths
818
+ * relative to `repoDir`, trailing-slashed.
819
+ *
820
+ * OPT-IN ONLY. Walking into a gitignored directory contradicts what every other
821
+ * tool (and BHGraph's own `git ls-files` foundation) does — `.gitignore`
822
+ * excludes. So this returns `[]` unless the project opted the directory in via
823
+ * `bhgraph.json` `includeIgnored`; without that, a gitignored dir — including
824
+ * a huge reference/data dir full of nested clones — is left untouched (#970,
825
+ * #976). When opted in, it restores the super-repo-of-clones behavior (#622,
826
+ * #699). `prefix` is the scan-root-relative path of `repoDir`, so a pattern like
827
+ * `services/` opts that whole subtree in at any recursion depth. Built-in
828
+ * default excludes (`node_modules`, …) are always skipped.
829
+ */
830
+ function findIgnoredEmbeddedRepos(repoDir, includeIgnored, prefix) {
831
+ if (!includeIgnored)
832
+ return [];
833
+ const defaults = defaultsOnlyIgnore();
834
+ const repos = [];
835
+ for (const dir of listIgnoredDirs(repoDir)) {
836
+ if (defaults.ignores(dir))
837
+ continue;
838
+ if (!includeIgnored.ignores((0, utils_1.normalizePath)(prefix + dir)))
839
+ continue;
840
+ repos.push(...findNestedGitRepos(path.join(repoDir, dir), dir));
841
+ }
842
+ return repos;
843
+ }
844
+ /**
845
+ * Collect git-visible files (tracked + untracked, .gitignore-respected) from the
846
+ * git repository rooted at `repoDir`, adding each to `files` with `prefix`
847
+ * prepended so paths stay relative to the original scan root.
848
+ *
849
+ * Recurses into embedded git repositories — nested repos that are NOT submodules
850
+ * (independent clones living inside the workspace, common in CMake "super-repo"
851
+ * layouts). The parent repo's `git ls-files` cannot see into them: tracked output
852
+ * skips them entirely, and untracked output reports them only as an opaque
853
+ * "subdir/" entry (trailing slash) rather than expanding their files. Each
854
+ * embedded repo is its own git boundary, so we re-run `git ls-files` inside it.
855
+ * (See issue #193.) GITIGNORED embedded repos are invisible even to that; they
856
+ * are discovered separately via `findIgnoredEmbeddedRepos` (#514) but ONLY for
857
+ * directories the project opted in through `bhgraph.json` `includeIgnored`
858
+ * (`includeIgnored` here, threaded from the scan root) — by default `.gitignore`
859
+ * is respected and they stay out (#970, #976). Every embedded repo root (however
860
+ * found) is recorded in `embeddedRoots` so callers can exempt its files from the
861
+ * parent's own gitignore rules.
862
+ */
863
+ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored = null) {
864
+ const gitOpts = { cwd: repoDir, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true };
865
+ // Tracked files. --recurse-submodules pulls in files from active submodules,
866
+ // which the index would otherwise represent only as a commit pointer.
867
+ // Without this, monorepos using submodules index 0 files. (See issue #147.)
868
+ // Note: --recurse-submodules only supports -c/--cached and --stage modes — it
869
+ // can't be combined with -o, so untracked files are gathered separately below.
870
+ //
871
+ // We use --stage (-s) rather than -c so each entry carries its file mode. That
872
+ // lets us spot gitlink entries (mode 160000) that --recurse-submodules did NOT
873
+ // expand: a nested repo `git add`ed without a `.gitmodules` entry, or a
874
+ // submodule that isn't active/initialized in this checkout. Such a gitlink
875
+ // falls through every pass — it's tracked, so the untracked `-o` listing below
876
+ // never reports it, and --recurse-submodules only expands ACTIVE submodules —
877
+ // so its source would be silently skipped, leaving only the super-repo's own
878
+ // files indexed. We collect those gitlinks here and recurse into them below.
879
+ // (An active submodule is expanded inline by --recurse-submodules and so never
880
+ // surfaces as a 160000 entry — only the unhandled gitlinks do.) (#1031, #1033)
881
+ //
882
+ // -z gives NUL-separated, unquoted output so non-ASCII (e.g. CJK) paths
883
+ // survive verbatim. Without it git octal-escapes and double-quotes such paths
884
+ // (the core.quotepath default), and the quoted form never matches a real file
885
+ // on disk → those files are silently dropped from the index. (#541) With -s the
886
+ // path follows a TAB after the `<mode> <object> <stage>` prefix.
887
+ const gitlinkRels = [];
888
+ const tracked = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-s', '--recurse-submodules'], gitOpts);
889
+ for (const entry of tracked.split('\0')) {
890
+ if (!entry)
891
+ continue;
892
+ const tab = entry.indexOf('\t');
893
+ if (tab === -1)
894
+ continue; // --stage always emits "<mode> <object> <stage>\t<path>"
895
+ const rel = entry.slice(tab + 1);
896
+ if (entry.slice(0, 6) === '160000') {
897
+ gitlinkRels.push(rel); // an unexpanded gitlink — recursed into below, not a source file itself
898
+ continue;
899
+ }
900
+ files.add((0, utils_1.normalizePath)(prefix + rel));
901
+ }
902
+ // Untracked files (submodules manage their own untracked state). Embedded git
903
+ // repos surface here as a single "subdir/" entry that git refuses to descend
904
+ // into — recurse into those as their own repos so their source gets indexed.
905
+ const untracked = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-o', '--exclude-standard'], gitOpts);
906
+ for (const rel of untracked.split('\0')) {
907
+ if (!rel)
908
+ continue;
909
+ if (rel.endsWith('/')) {
910
+ // git only emits a trailing-slash directory entry for an embedded repo.
911
+ // Guard with a .git check anyway, and skip anything else exactly as git
912
+ // itself skips it (we never descend into a non-repo opaque dir). Never
913
+ // descend into default-ignored locations — an embedded repo inside
914
+ // node_modules is an npm git-dependency, not project code.
915
+ const childDir = path.join(repoDir, rel);
916
+ // A git worktree surfaces here as an opaque untracked dir too — skip it,
917
+ // it's a duplicate working view of an already-indexed repo (#848).
918
+ if (classifyGitDir(childDir) === 'embedded' && !defaultsOnlyIgnore().ignores(rel)) {
919
+ embeddedRoots?.add((0, utils_1.normalizePath)(prefix + rel));
920
+ collectGitFiles(childDir, prefix + rel, files, embeddedRoots, includeIgnored);
921
+ }
922
+ continue;
923
+ }
924
+ files.add((0, utils_1.normalizePath)(prefix + rel));
925
+ }
926
+ // Gitlink entries (mode 160000) that --recurse-submodules left unexpanded —
927
+ // an embedded repo `git add`ed without `.gitmodules`, or a submodule not
928
+ // active/initialized in this checkout. When such a gitlink has a real working
929
+ // tree on disk it is distinct first-party code we must index as its own
930
+ // embedded repo: the tracked pass skipped its contents and the untracked pass
931
+ // never sees it (it's tracked, not "other"). A gitlink with no checkout on disk
932
+ // (an uninitialized submodule — empty dir, no `.git`) has nothing to index and
933
+ // is left alone, as is a submodule worktree (a duplicate view, #945). (#1031, #1033)
934
+ if (gitlinkRels.length > 0) {
935
+ const defaults = defaultsOnlyIgnore();
936
+ const repoIgnore = buildDefaultIgnore(repoDir);
937
+ for (const rel of gitlinkRels) {
938
+ const relDir = rel.endsWith('/') ? rel : rel + '/';
939
+ // A gitlink under a gitignored path is respected (not indexed) unless the
940
+ // project opted it in via `includeIgnored` — keep tracked gitlinks under
941
+ // the same scope rule as the untracked-ignored kind below (#1065).
942
+ if (gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored))
943
+ continue;
944
+ const childDir = path.join(repoDir, rel);
945
+ // 'embedded' = a real .git checkout on disk; 'worktree' and 'none' are skipped.
946
+ if (classifyGitDir(childDir) !== 'embedded')
947
+ continue;
948
+ embeddedRoots?.add((0, utils_1.normalizePath)(prefix + relDir));
949
+ collectGitFiles(childDir, prefix + relDir, files, embeddedRoots, includeIgnored);
950
+ }
951
+ }
952
+ // Embedded repos hidden by THIS repo's ignore rules (`/packages/` in a
953
+ // super-repo .gitignore) never appear in any listing above. By default they
954
+ // stay hidden — `.gitignore` is respected (#970, #976). They are recursed into
955
+ // only when the project opted the directory in via `bhgraph.json`
956
+ // `includeIgnored` (#622, #699), which `findIgnoredEmbeddedRepos` enforces.
957
+ for (const rel of findIgnoredEmbeddedRepos(repoDir, includeIgnored, prefix)) {
958
+ embeddedRoots?.add((0, utils_1.normalizePath)(prefix + rel));
959
+ collectGitFiles(path.join(repoDir, rel), prefix + rel, files, embeddedRoots, includeIgnored);
960
+ }
961
+ }
962
+ /**
963
+ * Get all files visible to git (tracked + untracked but not ignored).
964
+ * Respects .gitignore at all levels (root, subdirectories) and descends into
965
+ * embedded (nested, non-submodule) git repos. Returns null on failure
966
+ * (non-git project) so callers can fall back to a filesystem walk.
967
+ */
968
+ function getGitVisibleFiles(rootDir) {
969
+ try {
970
+ // Check if the project directory is gitignored by a parent repo.
971
+ // When rootDir lives inside a parent git repo that ignores it,
972
+ // `git ls-files` returns nothing — fall back to filesystem walk.
973
+ const gitRoot = (0, child_process_1.execFileSync)('git', ['rev-parse', '--show-toplevel'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }).trim();
974
+ if (path.resolve(gitRoot) !== path.resolve(rootDir)) {
975
+ try {
976
+ // git check-ignore exits 0 if the path IS ignored, 1 if not
977
+ (0, child_process_1.execFileSync)('git', ['check-ignore', '-q', path.resolve(rootDir)], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
978
+ // Directory is gitignored by parent repo — fall back to filesystem walk
979
+ return null;
980
+ }
981
+ catch {
982
+ // Not ignored — safe to use git ls-files
983
+ }
984
+ }
985
+ const files = new Set();
986
+ const embeddedRoots = new Set();
987
+ collectGitFiles(rootDir, '', files, embeddedRoots, loadIncludeIgnoredMatcher(rootDir));
988
+ // Apply built-in default ignores uniformly — to tracked files too, since
989
+ // committing a dependency/build dir doesn't make it project code. A
990
+ // `.gitignore` negation (e.g. `!vendor/`) is the explicit opt-in. (issue #407)
991
+ // Files inside an EMBEDDED repo are matched against that repo's own rules,
992
+ // not the parent's: the parent's .gitignore hides the child repo from git,
993
+ // not from the index. (#514)
994
+ const ig = buildScopeIgnore(rootDir, embeddedRoots);
995
+ const visible = new Set([...files].filter((f) => !ig.ignores(f)));
996
+ // Force-include first-party source the project whitelisted in
997
+ // `bhgraph.json` `include`. These are gitignored, so `git ls-files` never
998
+ // listed them above — discover them directly off disk and add them. (The
999
+ // common SVN+Git dual-VCS case: source committed to SVN, gitignored out of
1000
+ // Git, but still wanted in the graph.)
1001
+ for (const f of collectIncludedFilesForRoot(rootDir))
1002
+ visible.add(f);
1003
+ return visible;
1004
+ }
1005
+ catch {
1006
+ return null;
1007
+ }
1008
+ }
1009
+ /**
1010
+ * Use `git status` to detect changed files instead of scanning every file.
1011
+ * Returns null on failure so callers fall back to full scan.
1012
+ *
1013
+ * Recurses into embedded repos — the untracked kind (#193: the parent's status
1014
+ * collapses them to an opaque `?? subdir/` entry) always, and the gitignored
1015
+ * kind (#514: they never appear in the parent's status at all) only for
1016
+ * directories opted in via `bhgraph.json` `includeIgnored` (#622, #699) —
1017
+ * running `git status` inside each, so changes in a multi-repo workspace sync
1018
+ * without a full rescan. By default a gitignored dir is left alone, matching the
1019
+ * full-index scan (#970, #976). Deleting an ENTIRE embedded repo dir is the one
1020
+ * case this cannot see (the child status that would report the deletions is gone
1021
+ * with it); a full `bhgraph index` reconciles that.
1022
+ */
1023
+ function getGitChangedFiles(rootDir) {
1024
+ try {
1025
+ const changes = { modified: [], added: [], deleted: [] };
1026
+ // Custom extension → language overrides from the project's bhgraph.json,
1027
+ // so change detection sees the same custom-extension files the full index does.
1028
+ const overrides = (0, project_config_1.loadExtensionOverrides)(rootDir);
1029
+ collectGitStatus(rootDir, '', changes, overrides, loadIncludeIgnoredMatcher(rootDir), loadExcludeMatcher(rootDir));
1030
+ return changes;
1031
+ }
1032
+ catch {
1033
+ return null;
1034
+ }
1035
+ }
1036
+ function collectGitStatus(repoDir, prefix, out, overrides, includeIgnored = null, exclude = null) {
1037
+ const output = (0, child_process_1.execFileSync)('git', ['status', '--porcelain', '--no-renames'], { cwd: repoDir, encoding: 'utf-8', timeout: 10000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
1038
+ // This repo's own ignore rules — built-in defaults (#407) plus its .gitignore.
1039
+ // Change detection must exclude the SAME files the full index does, but git
1040
+ // status hides neither: it ignores nothing for *tracked* paths, and the
1041
+ // built-in defaults aren't gitignore at all. Without this filter a committed
1042
+ // vendor/ dir, or a tracked file under a .gitignored dir, surfaces here as a
1043
+ // change — so `bhgraph status` (which reads getChangedFiles) reports a
1044
+ // pending edit the full index never tracks and `sync` never clears. Matching
1045
+ // repo-relative `rel` at each recursion level mirrors getGitVisibleFiles'
1046
+ // ScopeIgnore: every embedded repo is judged by ITS OWN rules, never the
1047
+ // parent's. (#766)
1048
+ const ig = buildDefaultIgnore(repoDir);
1049
+ const untrackedDirs = [];
1050
+ for (const line of output.split('\n')) {
1051
+ if (line.length < 4)
1052
+ continue; // Minimum: "XY file"
1053
+ const statusCode = line.substring(0, 2);
1054
+ const rel = (0, utils_1.normalizePath)(line.substring(3));
1055
+ // Untracked directory entries (trailing slash) may hide an embedded repo —
1056
+ // collect for the recursion below instead of treating as a file.
1057
+ if (statusCode === '??' && rel.endsWith('/')) {
1058
+ untrackedDirs.push(rel);
1059
+ continue;
1060
+ }
1061
+ const filePath = (0, utils_1.normalizePath)(prefix + rel);
1062
+ if (!(0, grammars_1.isSourceFile)(filePath, overrides))
1063
+ continue;
1064
+ if (statusCode.includes('D')) {
1065
+ // Deletions stay unfiltered: getChangedFiles acts on one only when the
1066
+ // path is already tracked in the DB, where removal is always correct — and
1067
+ // that lets a newly-excluded dir's stale rows clean themselves up. (#766)
1068
+ out.deleted.push(filePath);
1069
+ continue;
1070
+ }
1071
+ // Added (`??`) / modified files inside an excluded dir must not enter the
1072
+ // index — match against the repo-relative path, same as the full scan. (#766)
1073
+ if (ig.ignores(rel))
1074
+ continue;
1075
+ // User `bhgraph.json` `exclude` (#999) is project-root-relative, so it's
1076
+ // matched against the full path — sync must not re-add a tracked file the
1077
+ // full index now keeps out. Deletions above stay unfiltered so a file that
1078
+ // WAS indexed before an exclude was added still cleans itself out.
1079
+ if (exclude && exclude.ignores(filePath))
1080
+ continue;
1081
+ if (statusCode === '??') {
1082
+ out.added.push(filePath);
1083
+ }
1084
+ else {
1085
+ // M, MM, AM, A (staged), etc. — treat as modified
1086
+ out.modified.push(filePath);
1087
+ }
1088
+ }
1089
+ // Recurse embedded repos found under untracked dirs (at the dir itself or
1090
+ // nested deeper). Gitignored dirs are walked only for the directories the
1091
+ // project opted in via `includeIgnored`; by default `.gitignore` is respected
1092
+ // and they are left alone (#970, #976), mirroring the full-index scan.
1093
+ for (const rel of untrackedDirs) {
1094
+ for (const repoRel of findNestedGitRepos(path.join(repoDir, rel), rel)) {
1095
+ collectGitStatus(path.join(repoDir, repoRel), prefix + repoRel, out, overrides, includeIgnored, exclude);
1096
+ }
1097
+ }
1098
+ for (const rel of findIgnoredEmbeddedRepos(repoDir, includeIgnored, prefix)) {
1099
+ collectGitStatus(path.join(repoDir, rel), prefix + rel, out, overrides, includeIgnored, exclude);
1100
+ }
1101
+ }
1102
+ /**
1103
+ * Recursively scan a directory for source files.
1104
+ *
1105
+ * In git repos, uses `git ls-files` (inherently respects .gitignore at all
1106
+ * levels), then keeps files with a supported source extension. For non-git
1107
+ * projects, falls back to a filesystem walk that parses .gitignore itself.
1108
+ */
1109
+ function scanDirectory(rootDir, onProgress) {
1110
+ // Custom extension → language overrides from the project's bhgraph.json.
1111
+ const overrides = (0, project_config_1.loadExtensionOverrides)(rootDir);
1112
+ // Fast path: use git to get all visible files (respects .gitignore everywhere)
1113
+ const gitFiles = getGitVisibleFiles(rootDir);
1114
+ if (gitFiles) {
1115
+ const files = [];
1116
+ let count = 0;
1117
+ for (const filePath of gitFiles) {
1118
+ if ((0, grammars_1.isSourceFile)(filePath, overrides)) {
1119
+ files.push(filePath);
1120
+ count++;
1121
+ onProgress?.(count, filePath);
1122
+ }
1123
+ }
1124
+ return files;
1125
+ }
1126
+ // Fallback: walk filesystem for non-git projects
1127
+ return scanDirectoryWalk(rootDir, onProgress);
1128
+ }
1129
+ /**
1130
+ * Async variant of scanDirectory that yields to the event loop periodically,
1131
+ * allowing worker threads to receive and render progress messages.
1132
+ */
1133
+ async function scanDirectoryAsync(rootDir, onProgress) {
1134
+ // Custom extension → language overrides from the project's bhgraph.json.
1135
+ const overrides = (0, project_config_1.loadExtensionOverrides)(rootDir);
1136
+ const gitFiles = getGitVisibleFiles(rootDir);
1137
+ if (gitFiles) {
1138
+ const files = [];
1139
+ let count = 0;
1140
+ for (const filePath of gitFiles) {
1141
+ if ((0, grammars_1.isSourceFile)(filePath, overrides)) {
1142
+ files.push(filePath);
1143
+ count++;
1144
+ onProgress?.(count, filePath);
1145
+ // Yield every 100 files so worker threads can render progress
1146
+ if (count % 100 === 0) {
1147
+ await new Promise(r => setImmediate(r));
1148
+ }
1149
+ }
1150
+ }
1151
+ return files;
1152
+ }
1153
+ return scanDirectoryWalk(rootDir, onProgress);
1154
+ }
1155
+ /**
1156
+ * Filesystem walk fallback for non-git projects.
1157
+ */
1158
+ function scanDirectoryWalk(rootDir, onProgress) {
1159
+ const files = [];
1160
+ let count = 0;
1161
+ const visitedDirs = new Set();
1162
+ // Custom extension → language overrides from the project's bhgraph.json.
1163
+ const overrides = (0, project_config_1.loadExtensionOverrides)(rootDir);
1164
+ const loadIgnore = (dir) => {
1165
+ const giPath = path.join(dir, '.gitignore');
1166
+ if (!fs.existsSync(giPath))
1167
+ return null;
1168
+ // readGitignorePatterns is defensive: a non-UTF-8 (DLP-encrypted) or
1169
+ // uncompilable .gitignore is skipped/filtered with a warning, never thrown
1170
+ // (issue #682) — so the per-file `.ignores()` calls below can't crash.
1171
+ const patterns = readGitignorePatterns(giPath);
1172
+ return patterns ? { dir, ig: (0, ignore_1.default)().add(patterns) } : null;
1173
+ };
1174
+ const isIgnored = (fullPath, isDir, matchers) => {
1175
+ for (const { dir, ig } of matchers) {
1176
+ let rel = (0, utils_1.normalizePath)(path.relative(dir, fullPath));
1177
+ if (!rel || rel.startsWith('..'))
1178
+ continue; // not under this matcher's dir
1179
+ if (isDir)
1180
+ rel += '/'; // dir-only rules (e.g. `build/`) only match with the slash
1181
+ if (ig.ignores(rel))
1182
+ return true;
1183
+ }
1184
+ return false;
1185
+ };
1186
+ function walk(dir, matchers) {
1187
+ let realDir;
1188
+ try {
1189
+ realDir = fs.realpathSync(dir);
1190
+ }
1191
+ catch {
1192
+ (0, errors_1.logDebug)('Skipping unresolvable directory', { dir });
1193
+ return;
1194
+ }
1195
+ if (visitedDirs.has(realDir)) {
1196
+ (0, errors_1.logDebug)('Skipping already-visited directory (symlink cycle)', { dir, realDir });
1197
+ return;
1198
+ }
1199
+ visitedDirs.add(realDir);
1200
+ // This directory's own .gitignore (if present) applies to everything below it.
1201
+ // The root's .gitignore is already merged into the seeded base matcher (so a
1202
+ // negation there can override a built-in default), so skip it here.
1203
+ const own = dir === rootDir ? null : loadIgnore(dir);
1204
+ const active = own ? [...matchers, own] : matchers;
1205
+ let entries;
1206
+ try {
1207
+ entries = fs.readdirSync(dir, { withFileTypes: true });
1208
+ }
1209
+ catch (error) {
1210
+ (0, errors_1.logDebug)('Skipping unreadable directory', { dir, error: String(error) });
1211
+ return;
1212
+ }
1213
+ for (const entry of entries) {
1214
+ // Never descend into git internals or any BHGraph data directory
1215
+ // (the active one or a sibling another environment created — #636).
1216
+ if (entry.name === '.git' || (0, directory_1.isBHGraphDataDir)(entry.name))
1217
+ continue;
1218
+ const fullPath = path.join(dir, entry.name);
1219
+ const relativePath = (0, utils_1.normalizePath)(path.relative(rootDir, fullPath));
1220
+ if (entry.isSymbolicLink()) {
1221
+ try {
1222
+ const realTarget = fs.realpathSync(fullPath);
1223
+ const stat = fs.statSync(realTarget);
1224
+ if (stat.isDirectory()) {
1225
+ if (!isIgnored(fullPath, true, active)) {
1226
+ walk(fullPath, active);
1227
+ }
1228
+ }
1229
+ else if (stat.isFile()) {
1230
+ if (!isIgnored(fullPath, false, active) && (0, grammars_1.isSourceFile)(relativePath, overrides)) {
1231
+ files.push(relativePath);
1232
+ count++;
1233
+ onProgress?.(count, relativePath);
1234
+ }
1235
+ }
1236
+ }
1237
+ catch {
1238
+ (0, errors_1.logDebug)('Skipping broken symlink', { path: fullPath });
1239
+ }
1240
+ continue;
1241
+ }
1242
+ if (entry.isDirectory()) {
1243
+ if (!isIgnored(fullPath, true, active)) {
1244
+ walk(fullPath, active);
1245
+ }
1246
+ }
1247
+ else if (entry.isFile()) {
1248
+ if (!isIgnored(fullPath, false, active) && (0, grammars_1.isSourceFile)(relativePath, overrides)) {
1249
+ files.push(relativePath);
1250
+ count++;
1251
+ onProgress?.(count, relativePath);
1252
+ }
1253
+ }
1254
+ }
1255
+ }
1256
+ // Seed a base matcher with the built-in default ignores (merged with the root
1257
+ // .gitignore so a negation can override). Nested .gitignores still layer per-dir.
1258
+ const baseMatchers = [{ dir: rootDir, ig: buildDefaultIgnore(rootDir) }];
1259
+ // Project `bhgraph.json` `exclude` patterns (#999), rooted at the project so
1260
+ // `isIgnored` matches them against root-relative paths — same coverage the
1261
+ // git path gets via ScopeIgnore, for non-git projects.
1262
+ const exclude = loadExcludeMatcher(rootDir);
1263
+ if (exclude)
1264
+ baseMatchers.push({ dir: rootDir, ig: exclude });
1265
+ walk(rootDir, baseMatchers);
1266
+ // Force-include first-party source whitelisted in `bhgraph.json` `include`
1267
+ // — the walk above honours `.gitignore`, so anything gitignored was dropped;
1268
+ // add it back here (deduped). Mirrors the git path's union.
1269
+ const included = collectIncludedFilesForRoot(rootDir);
1270
+ if (included.size > 0) {
1271
+ const seen = new Set(files);
1272
+ for (const f of included) {
1273
+ if (!seen.has(f)) {
1274
+ files.push(f);
1275
+ seen.add(f);
1276
+ }
1277
+ }
1278
+ }
1279
+ return files;
1280
+ }
1281
+ /**
1282
+ * Extraction orchestrator
1283
+ */
1284
+ class ExtractionOrchestrator {
1285
+ rootDir;
1286
+ queries;
1287
+ /**
1288
+ * Names of frameworks detected for this project, populated by indexAll().
1289
+ * Passed to extractFromSource so framework-specific extractors (route nodes,
1290
+ * middleware, etc.) run after the tree-sitter pass. Cleared if detection
1291
+ * hasn't run yet so single-file re-index paths can detect on the spot.
1292
+ */
1293
+ detectedFrameworkNames = null;
1294
+ constructor(rootDir, queries) {
1295
+ this.rootDir = rootDir;
1296
+ this.queries = queries;
1297
+ }
1298
+ /**
1299
+ * Build a filesystem-backed ResolutionContext sufficient for framework
1300
+ * detection. Graph-query methods (getNodesByName etc.) return empty because
1301
+ * the DB hasn't been populated yet, but detect() only uses readFile,
1302
+ * fileExists, and getAllFiles, so that's fine.
1303
+ */
1304
+ buildDetectionContext(files) {
1305
+ const rootDir = this.rootDir;
1306
+ return {
1307
+ getNodesInFile: () => [],
1308
+ getNodesByName: () => [],
1309
+ getNodesByQualifiedName: () => [],
1310
+ getNodesByKind: () => [],
1311
+ getNodesByLowerName: () => [],
1312
+ getImportMappings: () => [],
1313
+ getAllFiles: () => files,
1314
+ getProjectRoot: () => rootDir,
1315
+ fileExists: (relativePath) => {
1316
+ const full = (0, utils_1.validatePathWithinRoot)(rootDir, relativePath);
1317
+ if (!full)
1318
+ return false;
1319
+ try {
1320
+ return fs.existsSync(full);
1321
+ }
1322
+ catch {
1323
+ return false;
1324
+ }
1325
+ },
1326
+ readFile: (relativePath) => {
1327
+ const full = (0, utils_1.validatePathWithinRoot)(rootDir, relativePath);
1328
+ if (!full)
1329
+ return null;
1330
+ try {
1331
+ return fs.readFileSync(full, 'utf-8');
1332
+ }
1333
+ catch {
1334
+ return null;
1335
+ }
1336
+ },
1337
+ // Monorepo support — needed by framework detect()s that probe
1338
+ // subpackage manifests (e.g. fabric-view looking at
1339
+ // packages/<sub>/package.json when the root manifest is just a
1340
+ // workspace declaration). Matches the resolver-context shape.
1341
+ listDirectories: (relativePath) => {
1342
+ const target = relativePath === '.' || relativePath === ''
1343
+ ? rootDir
1344
+ : path.join(rootDir, relativePath);
1345
+ try {
1346
+ return fs
1347
+ .readdirSync(target, { withFileTypes: true })
1348
+ .filter((entry) => entry.isDirectory())
1349
+ .map((entry) => entry.name);
1350
+ }
1351
+ catch {
1352
+ return [];
1353
+ }
1354
+ },
1355
+ };
1356
+ }
1357
+ /**
1358
+ * Detect frameworks on demand using the current scanned files (or a fresh
1359
+ * scan if none are provided). Cached on the orchestrator so repeat calls
1360
+ * inside a single run don't re-scan.
1361
+ */
1362
+ ensureDetectedFrameworks(files) {
1363
+ if (this.detectedFrameworkNames !== null)
1364
+ return this.detectedFrameworkNames;
1365
+ const fileList = files ?? scanDirectory(this.rootDir);
1366
+ const context = this.buildDetectionContext(fileList);
1367
+ this.detectedFrameworkNames = (0, frameworks_1.detectFrameworks)(context).map((r) => r.name);
1368
+ return this.detectedFrameworkNames;
1369
+ }
1370
+ /**
1371
+ * Index all files in the project
1372
+ */
1373
+ async indexAll(onProgress, signal, verbose) {
1374
+ await (0, grammars_1.initGrammars)();
1375
+ const startTime = Date.now();
1376
+ const errors = [];
1377
+ let filesIndexed = 0;
1378
+ let filesSkipped = 0;
1379
+ let filesErrored = 0;
1380
+ let totalNodes = 0;
1381
+ let totalEdges = 0;
1382
+ // Custom extension → language overrides from the project's bhgraph.json.
1383
+ // Threaded into language detection so custom-extension files load the right
1384
+ // grammar and store under the mapped language.
1385
+ const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
1386
+ const log = verbose
1387
+ ? (msg) => { console.log(`[worker] ${msg}`); }
1388
+ : (_msg) => { };
1389
+ // Phase 1: Scan for files
1390
+ onProgress?.({
1391
+ phase: 'scanning',
1392
+ current: 0,
1393
+ total: 0,
1394
+ });
1395
+ const files = await scanDirectoryAsync(this.rootDir, (current, file) => {
1396
+ onProgress?.({
1397
+ phase: 'scanning',
1398
+ current,
1399
+ total: 0,
1400
+ currentFile: file,
1401
+ });
1402
+ });
1403
+ // Detect frameworks once per indexAll run using the scanned file list.
1404
+ // Names are passed to each parse call so framework-specific extractors
1405
+ // (route nodes, middleware, etc.) run after the tree-sitter pass.
1406
+ // Framework detection is reset each run so adding e.g. requirements.txt
1407
+ // between runs is picked up without restarting the process.
1408
+ this.detectedFrameworkNames = null;
1409
+ const frameworkNames = this.ensureDetectedFrameworks(files);
1410
+ if (signal?.aborted) {
1411
+ return {
1412
+ success: false,
1413
+ filesIndexed: 0,
1414
+ filesSkipped: 0,
1415
+ filesErrored: 0,
1416
+ nodesCreated: 0,
1417
+ edgesCreated: 0,
1418
+ errors: [{ message: 'Aborted', severity: 'error' }],
1419
+ durationMs: Date.now() - startTime,
1420
+ };
1421
+ }
1422
+ // Phase 2: Parse files in a worker thread (keeps main thread unblocked for UI)
1423
+ const total = files.length;
1424
+ let processed = 0;
1425
+ // Emit parsing phase immediately so the progress bar appears during worker setup.
1426
+ // The yield lets the shimmer worker flush the phase transition to stdout before
1427
+ // the main thread starts synchronous grammar detection work.
1428
+ onProgress?.({
1429
+ phase: 'parsing',
1430
+ current: 0,
1431
+ total,
1432
+ });
1433
+ await new Promise(resolve => setImmediate(resolve));
1434
+ // Detect needed languages and load grammars in the parse worker
1435
+ const neededLanguages = [...new Set(files.map((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides)))];
1436
+ // .h files default to 'c' but may be C++ — ensure cpp grammar is loaded when c is needed
1437
+ if (neededLanguages.includes('c') && !neededLanguages.includes('cpp')) {
1438
+ neededLanguages.push('cpp');
1439
+ }
1440
+ // Parse files on a pool of worker threads (keeps the main thread free for UI
1441
+ // and uses every core). Falls back to in-process parsing when the compiled
1442
+ // worker is unavailable (e.g. running from source in tests).
1443
+ const parseWorkerPath = path.join(__dirname, 'parse-worker.js');
1444
+ const useWorker = fs.existsSync(parseWorkerPath);
1445
+ let pool = null;
1446
+ if (useWorker) {
1447
+ // BHGRAPH_PARSE_WORKERS: explicit worker count; 1 = the old single-worker
1448
+ // behaviour (the conservative rollback). Unset → clamp(cores-1, 1, 8).
1449
+ const poolSize = (0, parse_pool_1.resolveParsePoolSize)(process.env.BHGRAPH_PARSE_WORKERS, os.cpus().length);
1450
+ pool = new parse_pool_1.ParseWorkerPool({
1451
+ languages: neededLanguages,
1452
+ size: poolSize,
1453
+ workerScriptPath: parseWorkerPath,
1454
+ recycleInterval: WORKER_RECYCLE_INTERVAL,
1455
+ parseTimeoutMs: PARSE_TIMEOUT_MS,
1456
+ log,
1457
+ });
1458
+ log(`Parse worker pool: ${poolSize} worker(s)`);
1459
+ }
1460
+ else {
1461
+ // In-process fallback: load grammars locally and parse on the main thread.
1462
+ await (0, grammars_1.loadGrammarsForLanguages)(neededLanguages);
1463
+ }
1464
+ /**
1465
+ * Parse one file: on the pool when available (the promise REJECTS on a worker
1466
+ * crash/timeout — the caller records it and the retry pass re-attempts), or
1467
+ * in-process synchronously as the no-worker fallback. The language is resolved
1468
+ * here on the main thread, where the bhgraph.json overrides are loaded.
1469
+ */
1470
+ const parseFile = (filePath, content) => {
1471
+ const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1472
+ if (!pool)
1473
+ return Promise.resolve((0, tree_sitter_1.extractFromSource)(filePath, content, language, frameworkNames));
1474
+ return pool.requestParse({ filePath, content, language, frameworkNames });
1475
+ };
1476
+ // --- Bounded rolling-window dispatch, ordered commit ---
1477
+ // Reads stay batched/parallel; parses run concurrently across the pool; the
1478
+ // SQLite store stays on the main thread (it isn't thread-safe). Crucially we
1479
+ // COMMIT results in original file order, not parse-completion order: the
1480
+ // resolution phase (run after indexing) resolves an ambiguous reference to one
1481
+ // of several same-named candidates by the nodes' DB insertion order, so a
1482
+ // stable commit order keeps the resulting graph deterministic — byte-identical
1483
+ // to the single-worker path — instead of drifting with parse timing. The
1484
+ // `completed` buffer holds at most ~windowSize out-of-order results, so memory
1485
+ // stays bounded.
1486
+ const windowSize = pool ? Math.max(4, pool.size * 2) : 1;
1487
+ const inFlight = new Set();
1488
+ const completed = new Map();
1489
+ let nextSeq = 0; // file-order sequence assigned at dispatch
1490
+ let nextToStore = 0; // cursor: next sequence to commit
1491
+ let aborted = false;
1492
+ const storeResult = (filePath, content, stats, result) => {
1493
+ processed++;
1494
+ // Store in database on main thread (SQLite is not thread-safe)
1495
+ if (result.nodes.length > 0 || result.errors.length === 0) {
1496
+ const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1497
+ this.storeExtractionResult(filePath, content, language, stats, result);
1498
+ }
1499
+ if (result.errors.length > 0) {
1500
+ for (const err of result.errors) {
1501
+ if (!err.filePath)
1502
+ err.filePath = filePath;
1503
+ }
1504
+ errors.push(...result.errors);
1505
+ }
1506
+ if (result.nodes.length > 0) {
1507
+ filesIndexed++;
1508
+ totalNodes += result.nodes.length;
1509
+ totalEdges += result.edges.length;
1510
+ }
1511
+ else if (result.errors.some((e) => e.severity === 'error')) {
1512
+ filesErrored++;
1513
+ }
1514
+ else {
1515
+ // Files with no symbols but no errors (yaml, twig, properties) are
1516
+ // tracked at the file level — count them as indexed so the CLI doesn't
1517
+ // misleadingly report "No files found to index".
1518
+ const lang = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1519
+ if ((0, grammars_1.isFileLevelOnlyLanguage)(lang)) {
1520
+ filesIndexed++;
1521
+ }
1522
+ else {
1523
+ filesSkipped++;
1524
+ }
1525
+ }
1526
+ onProgress?.({ phase: 'parsing', current: processed, total, currentFile: filePath });
1527
+ };
1528
+ const recordParseFailure = (filePath, err) => {
1529
+ processed++;
1530
+ filesErrored++;
1531
+ errors.push({
1532
+ message: err instanceof Error ? err.message : String(err),
1533
+ filePath,
1534
+ severity: 'error',
1535
+ code: 'parse_error',
1536
+ });
1537
+ onProgress?.({ phase: 'parsing', current: processed, total });
1538
+ };
1539
+ // Commit buffered parses to the DB in file order, advancing the cursor over
1540
+ // contiguous completed results. Runs after each parse settles (and once more
1541
+ // after the drain). storeResult / recordParseFailure run here single-threaded,
1542
+ // so shared counters and SQLite writes never race despite parallel parsing.
1543
+ const flushOrdered = () => {
1544
+ if (aborted)
1545
+ return;
1546
+ while (completed.has(nextToStore)) {
1547
+ const item = completed.get(nextToStore);
1548
+ completed.delete(nextToStore);
1549
+ nextToStore++;
1550
+ if (item.ok)
1551
+ storeResult(item.filePath, item.content, item.stats, item.result);
1552
+ else
1553
+ recordParseFailure(item.filePath, item.err);
1554
+ }
1555
+ };
1556
+ // Dispatch one file's parse (parses run concurrently across the pool), tagged
1557
+ // with its file-order sequence so flushOrdered commits results in order. The
1558
+ // backpressure below bounds how far parsing runs ahead of the in-order commit.
1559
+ const feed = async (filePath, content, stats) => {
1560
+ const seq = nextSeq++;
1561
+ const p = (async () => {
1562
+ try {
1563
+ const result = await parseFile(filePath, content);
1564
+ completed.set(seq, { ok: true, filePath, content, stats, result });
1565
+ }
1566
+ catch (parseErr) {
1567
+ completed.set(seq, { ok: false, filePath, err: parseErr });
1568
+ }
1569
+ flushOrdered();
1570
+ })();
1571
+ const tracked = p.finally(() => { inFlight.delete(tracked); });
1572
+ inFlight.add(tracked);
1573
+ // Backpressure on the dispatched-but-not-yet-committed count (in-flight +
1574
+ // buffered), not just in-flight: a slow file sitting at the commit cursor
1575
+ // lets later parses finish and buffer, which would otherwise grow without
1576
+ // bound. Wait for parses to settle (each may advance the cursor) until the
1577
+ // window has room. `inFlight.size > 0` guards against an empty race — the
1578
+ // cursor file is always still in flight when the window is full.
1579
+ while (nextSeq - nextToStore >= windowSize && inFlight.size > 0) {
1580
+ await Promise.race(inFlight);
1581
+ }
1582
+ };
1583
+ for (let i = 0; i < files.length; i += FILE_IO_BATCH_SIZE) {
1584
+ if (signal?.aborted) {
1585
+ aborted = true;
1586
+ break;
1587
+ }
1588
+ const batch = files.slice(i, i + FILE_IO_BATCH_SIZE);
1589
+ // Read files in parallel (with path validation before any I/O)
1590
+ const fileContents = await Promise.all(batch.map(async (fp) => {
1591
+ try {
1592
+ // Indexing read: follow in-root symlinks the directory walk already
1593
+ // descended into (the `../` guard still applies) so files reached
1594
+ // via an in-root symlink-to-outside still index (#935).
1595
+ const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, fp, { allowSymlinkEscape: true });
1596
+ if (!fullPath) {
1597
+ (0, errors_1.logWarn)('Path traversal blocked in batch reader', { filePath: fp });
1598
+ return { filePath: fp, content: null, stats: null, error: new Error('Path traversal blocked') };
1599
+ }
1600
+ const content = await fsp.readFile(fullPath, 'utf-8');
1601
+ const stats = await fsp.stat(fullPath);
1602
+ return { filePath: fp, content, stats, error: null };
1603
+ }
1604
+ catch (err) {
1605
+ return { filePath: fp, content: null, stats: null, error: err };
1606
+ }
1607
+ }));
1608
+ // Dispatch each readable file into the bounded parse window; the window
1609
+ // stores results on the main thread as they arrive.
1610
+ for (const { filePath, content, stats, error } of fileContents) {
1611
+ if (signal?.aborted) {
1612
+ aborted = true;
1613
+ break;
1614
+ }
1615
+ if (error || content === null || stats === null) {
1616
+ processed++;
1617
+ filesErrored++;
1618
+ errors.push({
1619
+ message: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
1620
+ filePath,
1621
+ severity: 'error',
1622
+ code: 'read_error',
1623
+ });
1624
+ onProgress?.({ phase: 'parsing', current: processed, total });
1625
+ continue;
1626
+ }
1627
+ // Honour MAX_FILE_SIZE. Without this check, vendored generated
1628
+ // headers, minified bundles, and other multi-MB files get indexed,
1629
+ // wasting WASM heap and the worker recycle budget on inputs with no
1630
+ // useful symbols. The single-file extractFile path already enforces
1631
+ // this; the bulk path used to silently skip the check.
1632
+ if (stats.size > MAX_FILE_SIZE) {
1633
+ processed++;
1634
+ filesSkipped++;
1635
+ errors.push({
1636
+ message: `File exceeds max size (${stats.size} > ${MAX_FILE_SIZE})`,
1637
+ filePath,
1638
+ severity: 'warning',
1639
+ code: 'size_exceeded',
1640
+ });
1641
+ onProgress?.({ phase: 'parsing', current: processed, total });
1642
+ continue;
1643
+ }
1644
+ // Parse on the pool (main thread stays unblocked). Errors/timeouts are
1645
+ // handled inside feed() → recordParseFailure, feeding the retry pass.
1646
+ await feed(filePath, content, stats);
1647
+ }
1648
+ if (aborted)
1649
+ break;
1650
+ }
1651
+ // Drain parses still in flight (skip on abort — we tear down below instead),
1652
+ // then commit any results the cursor hasn't reached yet.
1653
+ if (!aborted) {
1654
+ await Promise.all(inFlight);
1655
+ flushOrdered();
1656
+ }
1657
+ if (signal?.aborted || aborted) {
1658
+ if (pool)
1659
+ await pool.destroy();
1660
+ return {
1661
+ success: false,
1662
+ filesIndexed,
1663
+ filesSkipped,
1664
+ filesErrored,
1665
+ filesDiscovered: total,
1666
+ nodesCreated: totalNodes,
1667
+ edgesCreated: totalEdges,
1668
+ errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
1669
+ durationMs: Date.now() - startTime,
1670
+ };
1671
+ }
1672
+ // Report 100% so the progress bar doesn't hang at 99%
1673
+ onProgress?.({
1674
+ phase: 'parsing',
1675
+ current: total,
1676
+ total,
1677
+ });
1678
+ // Yield so the shimmer worker's buffered stdout writes can flush.
1679
+ // Worker thread stdout is proxied through the main thread's event loop,
1680
+ // so synchronous work here blocks the animation from rendering.
1681
+ await new Promise(resolve => setImmediate(resolve));
1682
+ // Retry pass: files that failed due to WASM memory corruption may succeed
1683
+ // on a fresh worker with a clean heap. Recycle before each attempt so
1684
+ // every file gets the absolute cleanest WASM state possible.
1685
+ const retryableErrors = errors.filter((e) => e.code === 'parse_error' && e.filePath &&
1686
+ (e.message.includes('Worker exited') || e.message.includes('memory access out of bounds')));
1687
+ if (retryableErrors.length > 0 && pool) {
1688
+ log(`Retrying ${retryableErrors.length} files that failed due to WASM memory errors...`);
1689
+ // Fresh WASM heaps for the retry phase. A retry that still crashes its
1690
+ // worker makes the pool respawn it, so later retries keep landing on clean
1691
+ // workers too.
1692
+ pool.recycleAll();
1693
+ const stillFailing = [];
1694
+ for (const errEntry of retryableErrors) {
1695
+ const filePath = errEntry.filePath;
1696
+ if (signal?.aborted)
1697
+ break;
1698
+ let content;
1699
+ try {
1700
+ const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
1701
+ if (!fullPath)
1702
+ continue;
1703
+ content = await fsp.readFile(fullPath, 'utf-8');
1704
+ }
1705
+ catch {
1706
+ continue;
1707
+ }
1708
+ let result;
1709
+ try {
1710
+ result = await parseFile(filePath, content);
1711
+ }
1712
+ catch {
1713
+ stillFailing.push(errEntry);
1714
+ continue;
1715
+ }
1716
+ if (result.nodes.length > 0 || result.errors.length === 0) {
1717
+ const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1718
+ const stats = await fsp.stat(path.join(this.rootDir, filePath));
1719
+ this.storeExtractionResult(filePath, content, language, stats, result);
1720
+ const idx = errors.indexOf(errEntry);
1721
+ if (idx >= 0)
1722
+ errors.splice(idx, 1);
1723
+ filesErrored--;
1724
+ filesIndexed++;
1725
+ totalNodes += result.nodes.length;
1726
+ totalEdges += result.edges.length;
1727
+ log(`Retry OK: ${filePath} (${result.nodes.length} nodes)`);
1728
+ }
1729
+ }
1730
+ // Last resort: for files that still crash on a clean worker, strip
1731
+ // comment-only lines to reduce WASM memory pressure. Many compiler
1732
+ // test files are 90%+ comments (CHECK directives) that don't contribute
1733
+ // code nodes but consume parser memory.
1734
+ if (stillFailing.length > 0) {
1735
+ log(`${stillFailing.length} files still failing — retrying with comments stripped...`);
1736
+ pool.recycleAll();
1737
+ for (const errEntry of stillFailing) {
1738
+ const filePath = errEntry.filePath;
1739
+ if (signal?.aborted)
1740
+ break;
1741
+ let fullContent;
1742
+ try {
1743
+ const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
1744
+ if (!fullPath)
1745
+ continue;
1746
+ fullContent = await fsp.readFile(fullPath, 'utf-8');
1747
+ }
1748
+ catch {
1749
+ continue;
1750
+ }
1751
+ // Strip lines that are entirely comments (preserving line numbers
1752
+ // by replacing with empty lines so node positions stay correct)
1753
+ const stripped = fullContent
1754
+ .split('\n')
1755
+ .map(line => /^\s*\/\//.test(line) ? '' : line)
1756
+ .join('\n');
1757
+ let result;
1758
+ try {
1759
+ result = await parseFile(filePath, stripped);
1760
+ }
1761
+ catch {
1762
+ continue;
1763
+ }
1764
+ if (result.nodes.length > 0 || result.errors.length === 0) {
1765
+ const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
1766
+ const stats = await fsp.stat(path.join(this.rootDir, filePath));
1767
+ this.storeExtractionResult(filePath, fullContent, language, stats, result);
1768
+ const idx = errors.indexOf(errEntry);
1769
+ if (idx >= 0)
1770
+ errors.splice(idx, 1);
1771
+ filesErrored--;
1772
+ filesIndexed++;
1773
+ totalNodes += result.nodes.length;
1774
+ totalEdges += result.edges.length;
1775
+ log(`Retry (stripped) OK: ${filePath} (${result.nodes.length} nodes)`);
1776
+ }
1777
+ }
1778
+ }
1779
+ }
1780
+ // Shut down the parse worker pool.
1781
+ if (pool)
1782
+ await pool.destroy();
1783
+ return {
1784
+ success: filesIndexed > 0 || errors.filter((e) => e.severity === 'error').length === 0,
1785
+ filesIndexed,
1786
+ filesSkipped,
1787
+ filesErrored,
1788
+ filesDiscovered: total,
1789
+ nodesCreated: totalNodes,
1790
+ edgesCreated: totalEdges,
1791
+ errors,
1792
+ durationMs: Date.now() - startTime,
1793
+ };
1794
+ }
1795
+ /**
1796
+ * Index specific files
1797
+ */
1798
+ async indexFiles(filePaths) {
1799
+ const startTime = Date.now();
1800
+ const errors = [];
1801
+ let filesIndexed = 0;
1802
+ let filesSkipped = 0;
1803
+ let filesErrored = 0;
1804
+ let totalNodes = 0;
1805
+ let totalEdges = 0;
1806
+ for (const filePath of filePaths) {
1807
+ const result = await this.indexFile(filePath);
1808
+ if (result.errors.length > 0) {
1809
+ errors.push(...result.errors);
1810
+ }
1811
+ if (result.nodes.length > 0) {
1812
+ filesIndexed++;
1813
+ totalNodes += result.nodes.length;
1814
+ totalEdges += result.edges.length;
1815
+ }
1816
+ else if (result.errors.some((e) => e.severity === 'error')) {
1817
+ filesErrored++;
1818
+ }
1819
+ else {
1820
+ const tracked = this.queries.getFileByPath(filePath);
1821
+ if (tracked && (0, grammars_1.isFileLevelOnlyLanguage)(tracked.language)) {
1822
+ filesIndexed++;
1823
+ }
1824
+ else {
1825
+ filesSkipped++;
1826
+ }
1827
+ }
1828
+ }
1829
+ return {
1830
+ success: filesIndexed > 0 || errors.filter((e) => e.severity === 'error').length === 0,
1831
+ filesIndexed,
1832
+ filesSkipped,
1833
+ filesErrored,
1834
+ nodesCreated: totalNodes,
1835
+ edgesCreated: totalEdges,
1836
+ errors,
1837
+ durationMs: Date.now() - startTime,
1838
+ };
1839
+ }
1840
+ /**
1841
+ * Index a single file
1842
+ */
1843
+ async indexFile(relativePath) {
1844
+ // Indexing read: follow in-root symlinks (the `../` guard still applies), #935.
1845
+ const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, relativePath, { allowSymlinkEscape: true });
1846
+ if (!fullPath) {
1847
+ return {
1848
+ nodes: [],
1849
+ edges: [],
1850
+ unresolvedReferences: [],
1851
+ errors: [{ message: `Path traversal blocked: ${relativePath}`, filePath: relativePath, severity: 'error', code: 'path_traversal' }],
1852
+ durationMs: 0,
1853
+ };
1854
+ }
1855
+ // Read file content and stats
1856
+ let content;
1857
+ let stats;
1858
+ try {
1859
+ stats = await fsp.stat(fullPath);
1860
+ content = await fsp.readFile(fullPath, 'utf-8');
1861
+ }
1862
+ catch (error) {
1863
+ return {
1864
+ nodes: [],
1865
+ edges: [],
1866
+ unresolvedReferences: [],
1867
+ errors: [
1868
+ {
1869
+ message: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
1870
+ filePath: relativePath,
1871
+ severity: 'error',
1872
+ code: 'read_error',
1873
+ },
1874
+ ],
1875
+ durationMs: 0,
1876
+ };
1877
+ }
1878
+ return this.indexFileWithContent(relativePath, content, stats);
1879
+ }
1880
+ /**
1881
+ * Index a single file with pre-read content and stats.
1882
+ * Used by the parallel batch reader to avoid redundant file I/O.
1883
+ */
1884
+ async indexFileWithContent(relativePath, content, stats) {
1885
+ // Prevent `../` traversal; follow in-root symlinks like the directory walk (#935).
1886
+ const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, relativePath, { allowSymlinkEscape: true });
1887
+ if (!fullPath) {
1888
+ (0, errors_1.logWarn)('Path traversal blocked in indexFileWithContent', { relativePath });
1889
+ return {
1890
+ nodes: [],
1891
+ edges: [],
1892
+ unresolvedReferences: [],
1893
+ errors: [{ message: 'Path traversal blocked', filePath: relativePath, severity: 'error', code: 'path_traversal' }],
1894
+ durationMs: 0,
1895
+ };
1896
+ }
1897
+ // Check file size
1898
+ if (stats.size > MAX_FILE_SIZE) {
1899
+ return {
1900
+ nodes: [],
1901
+ edges: [],
1902
+ unresolvedReferences: [],
1903
+ errors: [
1904
+ {
1905
+ message: `File exceeds max size (${stats.size} > ${MAX_FILE_SIZE})`,
1906
+ filePath: relativePath,
1907
+ severity: 'warning',
1908
+ code: 'size_exceeded',
1909
+ },
1910
+ ],
1911
+ durationMs: 0,
1912
+ };
1913
+ }
1914
+ // Detect language (honoring the project's bhgraph.json extension overrides)
1915
+ const language = (0, grammars_1.detectLanguage)(relativePath, content, (0, project_config_1.loadExtensionOverrides)(this.rootDir));
1916
+ if (!(0, grammars_1.isLanguageSupported)(language)) {
1917
+ return {
1918
+ nodes: [],
1919
+ edges: [],
1920
+ unresolvedReferences: [],
1921
+ errors: [],
1922
+ durationMs: 0,
1923
+ };
1924
+ }
1925
+ // Extract from source. Use cached framework names if indexAll has run,
1926
+ // otherwise detect on the spot so single-file re-index paths still emit
1927
+ // route nodes / middleware / etc.
1928
+ const frameworkNames = this.ensureDetectedFrameworks();
1929
+ const result = (0, tree_sitter_1.extractFromSource)(relativePath, content, language, frameworkNames);
1930
+ // Store in database
1931
+ if (result.nodes.length > 0 || result.errors.length === 0) {
1932
+ this.storeExtractionResult(relativePath, content, language, stats, result);
1933
+ }
1934
+ return result;
1935
+ }
1936
+ /**
1937
+ * Store extraction result in database
1938
+ */
1939
+ storeExtractionResult(filePath, content, language, stats, result) {
1940
+ const contentHash = hashContent(content);
1941
+ // Check if file already exists and hasn't changed
1942
+ const existingFile = this.queries.getFileByPath(filePath);
1943
+ if (existingFile && existingFile.contentHash === contentHash) {
1944
+ return; // No changes
1945
+ }
1946
+ // Snapshot incoming cross-file edges BEFORE deleting this file's nodes.
1947
+ // `deleteFile` cascades to delete every edge whose source OR target is a
1948
+ // node in this file (edges.FK ... ON DELETE CASCADE). Edges whose SOURCE is
1949
+ // in this file are re-emitted by the extractor below, but edges whose SOURCE
1950
+ // is in a *different* (unchanged) file are not — they would be silently
1951
+ // dropped, which is issue #899: re-indexing a callee file severs `calls`/
1952
+ // `references` edges from callers that import it via module-attribute
1953
+ // access (`pkg.mod.fn(...)`).
1954
+ //
1955
+ // We snapshot the edge plus the target node's (name, kind) so we can
1956
+ // re-resolve to the re-indexed target's NEW id. Node ids are
1957
+ // `sha256(filePath:kind:name:line)`, so any line shift in the callee file
1958
+ // (e.g. a docstring-only edit above the symbol) changes every target id and
1959
+ // a naive re-insert by old id would silently drop every edge. Matching by
1960
+ // (filePath, kind, name) is stable across line shifts; if the symbol was
1961
+ // renamed/removed, no match is found and the edge stays dropped (correct).
1962
+ const crossFileIncomingEdges = existingFile
1963
+ ? this.queries.getCrossFileIncomingEdgesWithTarget(filePath)
1964
+ : [];
1965
+ // Delete existing data for this file
1966
+ if (existingFile) {
1967
+ this.queries.deleteFile(filePath);
1968
+ }
1969
+ // Filter out nodes with missing required fields before insertion.
1970
+ // This prevents FK violations when edges reference nodes that would
1971
+ // be silently skipped by insertNode() (see issue #42).
1972
+ const validNodes = result.nodes.filter((n) => n.id && n.kind && n.name && n.filePath && n.language);
1973
+ // Insert nodes
1974
+ if (validNodes.length > 0) {
1975
+ this.queries.insertNodes(validNodes);
1976
+ }
1977
+ // Filter edges to only reference nodes that were actually inserted
1978
+ if (result.edges.length > 0) {
1979
+ const insertedIds = new Set(validNodes.map((n) => n.id));
1980
+ const validEdges = result.edges.filter((e) => insertedIds.has(e.source) && insertedIds.has(e.target));
1981
+ if (validEdges.length > 0) {
1982
+ this.queries.insertEdges(validEdges);
1983
+ }
1984
+ }
1985
+ // Re-insert cross-file incoming edges snapshotted before the delete,
1986
+ // re-resolving each edge's target to the re-indexed node's new id by
1987
+ // (filePath, kind, name). Node ids include the source line, so any line
1988
+ // shift in the callee file (e.g. a docstring-only edit above the symbol)
1989
+ // changes every target id and a naive re-insert by old id would drop them
1990
+ // all. `insertEdges` still filters to endpoints that exist, so edges whose
1991
+ // caller (source) was deleted, or whose callee (target) was renamed/removed
1992
+ // during the re-index (no match in `newTargetIds`), are dropped. This
1993
+ // closes the #899 edge-drop on `sync`.
1994
+ if (crossFileIncomingEdges.length > 0) {
1995
+ const newNodesByKindName = new Map();
1996
+ for (const n of validNodes) {
1997
+ newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
1998
+ }
1999
+ const reinserted = [];
2000
+ for (const e of crossFileIncomingEdges) {
2001
+ const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
2002
+ if (newTargetId) {
2003
+ reinserted.push({ source: e.source, target: newTargetId, kind: e.kind, metadata: e.metadata, line: e.line, column: e.column, provenance: e.provenance });
2004
+ }
2005
+ }
2006
+ if (reinserted.length > 0) {
2007
+ this.queries.insertEdges(reinserted);
2008
+ }
2009
+ }
2010
+ // Insert unresolved references in batch with denormalized filePath/language
2011
+ if (result.unresolvedReferences.length > 0) {
2012
+ const insertedIds = new Set(validNodes.map((n) => n.id));
2013
+ const refsWithContext = result.unresolvedReferences
2014
+ .filter((ref) => insertedIds.has(ref.fromNodeId))
2015
+ .map((ref) => ({
2016
+ ...ref,
2017
+ filePath: ref.filePath ?? filePath,
2018
+ language: ref.language ?? language,
2019
+ }));
2020
+ if (refsWithContext.length > 0) {
2021
+ this.queries.insertUnresolvedRefsBatch(refsWithContext);
2022
+ }
2023
+ }
2024
+ // Insert file record
2025
+ const fileRecord = {
2026
+ path: filePath,
2027
+ contentHash,
2028
+ language,
2029
+ size: stats.size,
2030
+ modifiedAt: stats.mtimeMs,
2031
+ indexedAt: Date.now(),
2032
+ nodeCount: result.nodes.length,
2033
+ errors: result.errors.length > 0 ? result.errors : undefined,
2034
+ };
2035
+ this.queries.upsertFile(fileRecord);
2036
+ }
2037
+ /**
2038
+ * Sync the index with the current file state.
2039
+ *
2040
+ * Change detection is filesystem-based, never git: a (size, mtime) stat
2041
+ * pre-filter skips unchanged files, then a content-hash compare confirms real
2042
+ * changes. This works in non-git projects and catches committed changes from
2043
+ * `git pull`/`checkout`/`merge`/`rebase` that `git status` cannot see.
2044
+ */
2045
+ async sync(onProgress) {
2046
+ await (0, grammars_1.initGrammars)(); // Initialize WASM runtime (grammars loaded lazily below)
2047
+ const startTime = Date.now();
2048
+ let filesChecked = 0;
2049
+ let filesAdded = 0;
2050
+ let filesModified = 0;
2051
+ let filesRemoved = 0;
2052
+ let nodesUpdated = 0;
2053
+ const changedFilePaths = [];
2054
+ onProgress?.({
2055
+ phase: 'scanning',
2056
+ current: 0,
2057
+ total: 0,
2058
+ });
2059
+ const filesToIndex = [];
2060
+ // === Filesystem reconcile (git-independent) ===
2061
+ // The source of truth for "what changed" is the filesystem vs the indexed
2062
+ // state — never git. We enumerate the current source files and reconcile
2063
+ // each against the DB. A cheap (size, mtime) stat pre-filter skips unchanged
2064
+ // files without reading or hashing them, so the expensive read+hash+parse
2065
+ // only runs for files that actually changed. This catches edits/adds/deletes
2066
+ // whether or not the project uses git, and crucially also catches committed
2067
+ // changes from `git pull`/`checkout`/`merge`/`rebase` — which `git status`
2068
+ // cannot see, because the working tree is clean afterward.
2069
+ const currentFiles = await scanDirectoryAsync(this.rootDir);
2070
+ filesChecked = currentFiles.length;
2071
+ const currentSet = new Set(currentFiles);
2072
+ const trackedFiles = this.queries.getAllFiles();
2073
+ const trackedMap = new Map();
2074
+ for (const f of trackedFiles) {
2075
+ trackedMap.set(f.path, f);
2076
+ }
2077
+ // Removals: tracked in the DB but no longer a present source file. Check the
2078
+ // filesystem directly — `scanDirectory` (via `git ls-files`) still lists a
2079
+ // file deleted from disk but not yet staged, so set membership alone misses it.
2080
+ // `reconcileChecks` drives the cooperative yield shared with the adds/mods loop
2081
+ // below (see SYNC_RECONCILE_YIELD_INTERVAL / issue #905).
2082
+ let reconcileChecks = 0;
2083
+ for (const tracked of trackedFiles) {
2084
+ if (!currentSet.has(tracked.path) || !fs.existsSync(path.join(this.rootDir, tracked.path))) {
2085
+ this.queries.deleteFile(tracked.path);
2086
+ filesRemoved++;
2087
+ }
2088
+ if (++reconcileChecks % SYNC_RECONCILE_YIELD_INTERVAL === 0) {
2089
+ await new Promise((resolve) => setImmediate(resolve));
2090
+ }
2091
+ }
2092
+ // Adds / modifications.
2093
+ for (const filePath of currentFiles) {
2094
+ // Same cooperative yield as the removals loop — this is the other O(files)
2095
+ // synchronous-stat loop that wedges the main thread on a large repo (#905).
2096
+ // Yield at the top of the body so the `continue` fast-paths below still hit it.
2097
+ if (++reconcileChecks % SYNC_RECONCILE_YIELD_INTERVAL === 0) {
2098
+ await new Promise((resolve) => setImmediate(resolve));
2099
+ }
2100
+ const fullPath = path.join(this.rootDir, filePath);
2101
+ const tracked = trackedMap.get(filePath);
2102
+ // Cheap pre-filter: an already-indexed file whose size AND mtime both match
2103
+ // the DB is unchanged — skip it without reading or hashing. (A content
2104
+ // change that preserves both exactly is the blind spot every mtime-based
2105
+ // incremental tool accepts; `index --force` is the escape hatch. Git bumps
2106
+ // mtime on every file it writes during checkout/merge, so pulls are caught.)
2107
+ if (tracked) {
2108
+ try {
2109
+ const stat = fs.statSync(fullPath);
2110
+ if (stat.size === tracked.size && Math.floor(stat.mtimeMs) === Math.floor(tracked.modifiedAt)) {
2111
+ continue;
2112
+ }
2113
+ }
2114
+ catch (error) {
2115
+ (0, errors_1.logDebug)('Skipping unstattable file during sync', { filePath, error: String(error) });
2116
+ continue;
2117
+ }
2118
+ }
2119
+ // New, or size/mtime changed — read + hash to confirm a real content change.
2120
+ let content;
2121
+ try {
2122
+ content = fs.readFileSync(fullPath, 'utf-8');
2123
+ }
2124
+ catch (error) {
2125
+ (0, errors_1.logDebug)('Skipping unreadable file during sync', { filePath, error: String(error) });
2126
+ continue;
2127
+ }
2128
+ const contentHash = hashContent(content);
2129
+ if (!tracked) {
2130
+ filesToIndex.push(filePath);
2131
+ changedFilePaths.push(filePath);
2132
+ filesAdded++;
2133
+ }
2134
+ else if (tracked.contentHash !== contentHash) {
2135
+ filesToIndex.push(filePath);
2136
+ changedFilePaths.push(filePath);
2137
+ filesModified++;
2138
+ }
2139
+ }
2140
+ // Load only grammars needed for changed files
2141
+ if (filesToIndex.length > 0) {
2142
+ const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
2143
+ const neededLanguages = [...new Set(filesToIndex.map((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides)))];
2144
+ // .h files default to 'c' but may be C++ — ensure cpp grammar is loaded
2145
+ if (neededLanguages.includes('c') && !neededLanguages.includes('cpp')) {
2146
+ neededLanguages.push('cpp');
2147
+ }
2148
+ await (0, grammars_1.loadGrammarsForLanguages)(neededLanguages);
2149
+ }
2150
+ // Index changed files
2151
+ const total = filesToIndex.length;
2152
+ for (let i = 0; i < filesToIndex.length; i++) {
2153
+ const filePath = filesToIndex[i];
2154
+ onProgress?.({
2155
+ phase: 'parsing',
2156
+ current: i + 1,
2157
+ total,
2158
+ currentFile: filePath,
2159
+ });
2160
+ const result = await this.indexFile(filePath);
2161
+ nodesUpdated += result.nodes.length;
2162
+ }
2163
+ return {
2164
+ filesChecked,
2165
+ filesAdded,
2166
+ filesModified,
2167
+ filesRemoved,
2168
+ nodesUpdated,
2169
+ durationMs: Date.now() - startTime,
2170
+ changedFilePaths: changedFilePaths.length > 0 ? changedFilePaths : undefined,
2171
+ };
2172
+ }
2173
+ /**
2174
+ * Get files that have changed since last index.
2175
+ * Uses git status as a fast path when available, falling back to full scan.
2176
+ */
2177
+ getChangedFiles() {
2178
+ const gitChanges = getGitChangedFiles(this.rootDir);
2179
+ if (gitChanges) {
2180
+ // === Git fast path ===
2181
+ const added = [];
2182
+ const modified = [];
2183
+ const removed = [];
2184
+ // Deleted files — only report if tracked in DB
2185
+ for (const filePath of gitChanges.deleted) {
2186
+ const tracked = this.queries.getFileByPath(filePath);
2187
+ if (tracked) {
2188
+ removed.push(filePath);
2189
+ }
2190
+ }
2191
+ // Modified + added files — read + hash, compare with DB. Untracked (`??`)
2192
+ // files stay untracked in git even after indexing, so they must be
2193
+ // hash-compared like modified files instead of always counting as added —
2194
+ // otherwise status reports them as pending forever. (See issue #206.)
2195
+ for (const filePath of [...gitChanges.modified, ...gitChanges.added]) {
2196
+ const fullPath = path.join(this.rootDir, filePath);
2197
+ let content;
2198
+ try {
2199
+ content = fs.readFileSync(fullPath, 'utf-8');
2200
+ }
2201
+ catch (error) {
2202
+ (0, errors_1.logDebug)('Skipping unreadable file while detecting changes', { filePath, error: String(error) });
2203
+ continue;
2204
+ }
2205
+ const contentHash = hashContent(content);
2206
+ const tracked = this.queries.getFileByPath(filePath);
2207
+ if (!tracked) {
2208
+ added.push(filePath);
2209
+ }
2210
+ else if (tracked.contentHash !== contentHash) {
2211
+ modified.push(filePath);
2212
+ }
2213
+ }
2214
+ return { added, modified, removed };
2215
+ }
2216
+ // === Fallback: full scan (non-git project or git failure) ===
2217
+ const currentFiles = new Set(scanDirectory(this.rootDir));
2218
+ const trackedFiles = this.queries.getAllFiles();
2219
+ // Build Map for O(1) lookups
2220
+ const trackedMap = new Map();
2221
+ for (const f of trackedFiles) {
2222
+ trackedMap.set(f.path, f);
2223
+ }
2224
+ const added = [];
2225
+ const modified = [];
2226
+ const removed = [];
2227
+ // Find removed files
2228
+ for (const tracked of trackedFiles) {
2229
+ if (!currentFiles.has(tracked.path)) {
2230
+ removed.push(tracked.path);
2231
+ }
2232
+ }
2233
+ // Find added and modified files
2234
+ for (const filePath of currentFiles) {
2235
+ const fullPath = path.join(this.rootDir, filePath);
2236
+ let content;
2237
+ try {
2238
+ content = fs.readFileSync(fullPath, 'utf-8');
2239
+ }
2240
+ catch (error) {
2241
+ (0, errors_1.logDebug)('Skipping unreadable file while detecting changes', { filePath, error: String(error) });
2242
+ continue;
2243
+ }
2244
+ const contentHash = hashContent(content);
2245
+ const tracked = trackedMap.get(filePath);
2246
+ if (!tracked) {
2247
+ added.push(filePath);
2248
+ }
2249
+ else if (tracked.contentHash !== contentHash) {
2250
+ modified.push(filePath);
2251
+ }
2252
+ }
2253
+ return { added, modified, removed };
2254
+ }
2255
+ }
2256
+ exports.ExtractionOrchestrator = ExtractionOrchestrator;
2257
+ // Re-export useful types and functions
2258
+ var tree_sitter_2 = require("./tree-sitter");
2259
+ Object.defineProperty(exports, "extractFromSource", { enumerable: true, get: function () { return tree_sitter_2.extractFromSource; } });
2260
+ var grammars_2 = require("./grammars");
2261
+ Object.defineProperty(exports, "detectLanguage", { enumerable: true, get: function () { return grammars_2.detectLanguage; } });
2262
+ Object.defineProperty(exports, "isSourceFile", { enumerable: true, get: function () { return grammars_2.isSourceFile; } });
2263
+ Object.defineProperty(exports, "isLanguageSupported", { enumerable: true, get: function () { return grammars_2.isLanguageSupported; } });
2264
+ Object.defineProperty(exports, "isGrammarLoaded", { enumerable: true, get: function () { return grammars_2.isGrammarLoaded; } });
2265
+ Object.defineProperty(exports, "getSupportedLanguages", { enumerable: true, get: function () { return grammars_2.getSupportedLanguages; } });
2266
+ Object.defineProperty(exports, "initGrammars", { enumerable: true, get: function () { return grammars_2.initGrammars; } });
2267
+ Object.defineProperty(exports, "loadGrammarsForLanguages", { enumerable: true, get: function () { return grammars_2.loadGrammarsForLanguages; } });
2268
+ Object.defineProperty(exports, "loadAllGrammars", { enumerable: true, get: function () { return grammars_2.loadAllGrammars; } });
2269
+ //# sourceMappingURL=index.js.map