@aroman22/codegraph-vba-darwin-arm64 1.5.2 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/bin/codegraph +5 -0
  2. package/lib/dist/bin/codegraph.d.ts +1 -1
  3. package/lib/dist/bin/codegraph.d.ts.map +1 -1
  4. package/lib/dist/bin/codegraph.js +307 -81
  5. package/lib/dist/bin/codegraph.js.map +1 -1
  6. package/lib/dist/bin/command-supervision.d.ts +38 -1
  7. package/lib/dist/bin/command-supervision.d.ts.map +1 -1
  8. package/lib/dist/bin/command-supervision.js +22 -3
  9. package/lib/dist/bin/command-supervision.js.map +1 -1
  10. package/lib/dist/db/index.d.ts +84 -5
  11. package/lib/dist/db/index.d.ts.map +1 -1
  12. package/lib/dist/db/index.js +209 -12
  13. package/lib/dist/db/index.js.map +1 -1
  14. package/lib/dist/db/migrations.d.ts +1 -1
  15. package/lib/dist/db/migrations.d.ts.map +1 -1
  16. package/lib/dist/db/migrations.js +80 -3
  17. package/lib/dist/db/migrations.js.map +1 -1
  18. package/lib/dist/db/queries.d.ts +116 -4
  19. package/lib/dist/db/queries.d.ts.map +1 -1
  20. package/lib/dist/db/queries.js +294 -7
  21. package/lib/dist/db/queries.js.map +1 -1
  22. package/lib/dist/db/schema.sql +43 -1
  23. package/lib/dist/db/wal-valve.d.ts +106 -0
  24. package/lib/dist/db/wal-valve.d.ts.map +1 -0
  25. package/lib/dist/db/wal-valve.js +208 -0
  26. package/lib/dist/db/wal-valve.js.map +1 -0
  27. package/lib/dist/directory.d.ts +9 -5
  28. package/lib/dist/directory.d.ts.map +1 -1
  29. package/lib/dist/directory.js +210 -19
  30. package/lib/dist/directory.js.map +1 -1
  31. package/lib/dist/extraction/cfml-extractor.d.ts +107 -0
  32. package/lib/dist/extraction/cfml-extractor.d.ts.map +1 -0
  33. package/lib/dist/extraction/cfml-extractor.js +494 -0
  34. package/lib/dist/extraction/cfml-extractor.js.map +1 -0
  35. package/lib/dist/extraction/grammars.d.ts +25 -1
  36. package/lib/dist/extraction/grammars.d.ts.map +1 -1
  37. package/lib/dist/extraction/grammars.js +177 -23
  38. package/lib/dist/extraction/grammars.js.map +1 -1
  39. package/lib/dist/extraction/index.d.ts +47 -2
  40. package/lib/dist/extraction/index.d.ts.map +1 -1
  41. package/lib/dist/extraction/index.js +568 -51
  42. package/lib/dist/extraction/index.js.map +1 -1
  43. package/lib/dist/extraction/languages/arkts.d.ts +3 -0
  44. package/lib/dist/extraction/languages/arkts.d.ts.map +1 -0
  45. package/lib/dist/extraction/languages/arkts.js +127 -0
  46. package/lib/dist/extraction/languages/arkts.js.map +1 -0
  47. package/lib/dist/extraction/languages/c-cpp.d.ts +98 -0
  48. package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  49. package/lib/dist/extraction/languages/c-cpp.js +563 -1
  50. package/lib/dist/extraction/languages/c-cpp.js.map +1 -1
  51. package/lib/dist/extraction/languages/cfquery.d.ts +12 -0
  52. package/lib/dist/extraction/languages/cfquery.d.ts.map +1 -0
  53. package/lib/dist/extraction/languages/cfquery.js +28 -0
  54. package/lib/dist/extraction/languages/cfquery.js.map +1 -0
  55. package/lib/dist/extraction/languages/cfscript.d.ts +3 -0
  56. package/lib/dist/extraction/languages/cfscript.d.ts.map +1 -0
  57. package/lib/dist/extraction/languages/cfscript.js +73 -0
  58. package/lib/dist/extraction/languages/cfscript.js.map +1 -0
  59. package/lib/dist/extraction/languages/cobol.d.ts +33 -0
  60. package/lib/dist/extraction/languages/cobol.d.ts.map +1 -0
  61. package/lib/dist/extraction/languages/cobol.js +499 -0
  62. package/lib/dist/extraction/languages/cobol.js.map +1 -0
  63. package/lib/dist/extraction/languages/erlang.d.ts +3 -0
  64. package/lib/dist/extraction/languages/erlang.d.ts.map +1 -0
  65. package/lib/dist/extraction/languages/erlang.js +350 -0
  66. package/lib/dist/extraction/languages/erlang.js.map +1 -0
  67. package/lib/dist/extraction/languages/index.d.ts.map +1 -1
  68. package/lib/dist/extraction/languages/index.js +18 -0
  69. package/lib/dist/extraction/languages/index.js.map +1 -1
  70. package/lib/dist/extraction/languages/nix.d.ts +3 -0
  71. package/lib/dist/extraction/languages/nix.d.ts.map +1 -0
  72. package/lib/dist/extraction/languages/nix.js +294 -0
  73. package/lib/dist/extraction/languages/nix.js.map +1 -0
  74. package/lib/dist/extraction/languages/solidity.d.ts +3 -0
  75. package/lib/dist/extraction/languages/solidity.d.ts.map +1 -0
  76. package/lib/dist/extraction/languages/solidity.js +293 -0
  77. package/lib/dist/extraction/languages/solidity.js.map +1 -0
  78. package/lib/dist/extraction/languages/terraform.d.ts +3 -0
  79. package/lib/dist/extraction/languages/terraform.d.ts.map +1 -0
  80. package/lib/dist/extraction/languages/terraform.js +641 -0
  81. package/lib/dist/extraction/languages/terraform.js.map +1 -0
  82. package/lib/dist/extraction/languages/vbnet.d.ts +11 -0
  83. package/lib/dist/extraction/languages/vbnet.d.ts.map +1 -0
  84. package/lib/dist/extraction/languages/vbnet.js +141 -0
  85. package/lib/dist/extraction/languages/vbnet.js.map +1 -0
  86. package/lib/dist/extraction/mybatis-extractor.d.ts +30 -10
  87. package/lib/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  88. package/lib/dist/extraction/mybatis-extractor.js +140 -38
  89. package/lib/dist/extraction/mybatis-extractor.js.map +1 -1
  90. package/lib/dist/extraction/parse-pool.d.ts +28 -1
  91. package/lib/dist/extraction/parse-pool.d.ts.map +1 -1
  92. package/lib/dist/extraction/parse-pool.js +76 -7
  93. package/lib/dist/extraction/parse-pool.js.map +1 -1
  94. package/lib/dist/extraction/parse-worker.js +11 -4
  95. package/lib/dist/extraction/parse-worker.js.map +1 -1
  96. package/lib/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  97. package/lib/dist/extraction/tree-sitter-helpers.js +1 -0
  98. package/lib/dist/extraction/tree-sitter-helpers.js.map +1 -1
  99. package/lib/dist/extraction/tree-sitter-types.d.ts +20 -1
  100. package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  101. package/lib/dist/extraction/tree-sitter.d.ts +60 -1
  102. package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
  103. package/lib/dist/extraction/tree-sitter.js +905 -20
  104. package/lib/dist/extraction/tree-sitter.js.map +1 -1
  105. package/lib/dist/extraction/vba/call-sweep.d.ts +3 -0
  106. package/lib/dist/extraction/vba/call-sweep.d.ts.map +1 -0
  107. package/lib/dist/extraction/vba/call-sweep.js +206 -0
  108. package/lib/dist/extraction/vba/call-sweep.js.map +1 -0
  109. package/lib/dist/extraction/vba/calls.d.ts +55 -0
  110. package/lib/dist/extraction/vba/calls.d.ts.map +1 -0
  111. package/lib/dist/extraction/vba/calls.js +439 -0
  112. package/lib/dist/extraction/vba/calls.js.map +1 -0
  113. package/lib/dist/extraction/vba/constants.d.ts +42 -0
  114. package/lib/dist/extraction/vba/constants.d.ts.map +1 -0
  115. package/lib/dist/extraction/vba/constants.js +112 -0
  116. package/lib/dist/extraction/vba/constants.js.map +1 -0
  117. package/lib/dist/extraction/vba/context.d.ts +175 -0
  118. package/lib/dist/extraction/vba/context.d.ts.map +1 -0
  119. package/lib/dist/extraction/vba/context.js +268 -0
  120. package/lib/dist/extraction/vba/context.js.map +1 -0
  121. package/lib/dist/extraction/vba/controls.d.ts +24 -0
  122. package/lib/dist/extraction/vba/controls.d.ts.map +1 -0
  123. package/lib/dist/extraction/vba/controls.js +89 -0
  124. package/lib/dist/extraction/vba/controls.js.map +1 -0
  125. package/lib/dist/extraction/vba/declarations.d.ts +3 -0
  126. package/lib/dist/extraction/vba/declarations.d.ts.map +1 -0
  127. package/lib/dist/extraction/vba/declarations.js +154 -0
  128. package/lib/dist/extraction/vba/declarations.js.map +1 -0
  129. package/lib/dist/extraction/vba/dims.d.ts +3 -0
  130. package/lib/dist/extraction/vba/dims.d.ts.map +1 -0
  131. package/lib/dist/extraction/vba/dims.js +186 -0
  132. package/lib/dist/extraction/vba/dims.js.map +1 -0
  133. package/lib/dist/extraction/vba/docmd.d.ts +16 -0
  134. package/lib/dist/extraction/vba/docmd.d.ts.map +1 -0
  135. package/lib/dist/extraction/vba/docmd.js +179 -0
  136. package/lib/dist/extraction/vba/docmd.js.map +1 -0
  137. package/lib/dist/extraction/vba/enums-consts.d.ts +15 -0
  138. package/lib/dist/extraction/vba/enums-consts.d.ts.map +1 -0
  139. package/lib/dist/extraction/vba/enums-consts.js +178 -0
  140. package/lib/dist/extraction/vba/enums-consts.js.map +1 -0
  141. package/lib/dist/extraction/vba/implements.d.ts +3 -0
  142. package/lib/dist/extraction/vba/implements.d.ts.map +1 -0
  143. package/lib/dist/extraction/vba/implements.js +52 -0
  144. package/lib/dist/extraction/vba/implements.js.map +1 -0
  145. package/lib/dist/extraction/vba/procedures.d.ts +3 -0
  146. package/lib/dist/extraction/vba/procedures.d.ts.map +1 -0
  147. package/lib/dist/extraction/vba/procedures.js +239 -0
  148. package/lib/dist/extraction/vba/procedures.js.map +1 -0
  149. package/lib/dist/extraction/vba/sql-wrapper.d.ts +17 -0
  150. package/lib/dist/extraction/vba/sql-wrapper.d.ts.map +1 -0
  151. package/lib/dist/extraction/vba/sql-wrapper.js +212 -0
  152. package/lib/dist/extraction/vba/sql-wrapper.js.map +1 -0
  153. package/lib/dist/extraction/vba/tempvars.d.ts +10 -0
  154. package/lib/dist/extraction/vba/tempvars.d.ts.map +1 -0
  155. package/lib/dist/extraction/vba/tempvars.js +138 -0
  156. package/lib/dist/extraction/vba/tempvars.js.map +1 -0
  157. package/lib/dist/extraction/vba/text-utils.d.ts +73 -0
  158. package/lib/dist/extraction/vba/text-utils.d.ts.map +1 -0
  159. package/lib/dist/extraction/vba/text-utils.js +195 -0
  160. package/lib/dist/extraction/vba/text-utils.js.map +1 -0
  161. package/lib/dist/extraction/vba-extractor.d.ts +3 -951
  162. package/lib/dist/extraction/vba-extractor.d.ts.map +1 -1
  163. package/lib/dist/extraction/vba-extractor.js +46 -2934
  164. package/lib/dist/extraction/vba-extractor.js.map +1 -1
  165. package/lib/dist/extraction/vba-preprocess.d.ts +1 -1
  166. package/lib/dist/extraction/vba-preprocess.d.ts.map +1 -1
  167. package/lib/dist/extraction/vba-preprocess.js +300 -88
  168. package/lib/dist/extraction/vba-preprocess.js.map +1 -1
  169. package/lib/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  170. package/lib/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  171. package/lib/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  172. package/lib/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  173. package/lib/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  174. package/lib/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  175. package/lib/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  176. package/lib/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  177. package/lib/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  178. package/lib/dist/graph/traversal.d.ts.map +1 -1
  179. package/lib/dist/graph/traversal.js +76 -17
  180. package/lib/dist/graph/traversal.js.map +1 -1
  181. package/lib/dist/index.d.ts +81 -1
  182. package/lib/dist/index.d.ts.map +1 -1
  183. package/lib/dist/index.js +460 -10
  184. package/lib/dist/index.js.map +1 -1
  185. package/lib/dist/installer/index.d.ts +42 -0
  186. package/lib/dist/installer/index.d.ts.map +1 -1
  187. package/lib/dist/installer/index.js +97 -2
  188. package/lib/dist/installer/index.js.map +1 -1
  189. package/lib/dist/mcp/daemon.d.ts +35 -3
  190. package/lib/dist/mcp/daemon.d.ts.map +1 -1
  191. package/lib/dist/mcp/daemon.js +101 -9
  192. package/lib/dist/mcp/daemon.js.map +1 -1
  193. package/lib/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  194. package/lib/dist/mcp/dynamic-boundaries.js +2 -1
  195. package/lib/dist/mcp/dynamic-boundaries.js.map +1 -1
  196. package/lib/dist/mcp/early-ppid.d.ts +26 -0
  197. package/lib/dist/mcp/early-ppid.d.ts.map +1 -0
  198. package/lib/dist/mcp/early-ppid.js +29 -0
  199. package/lib/dist/mcp/early-ppid.js.map +1 -0
  200. package/lib/dist/mcp/index.d.ts.map +1 -1
  201. package/lib/dist/mcp/index.js +29 -4
  202. package/lib/dist/mcp/index.js.map +1 -1
  203. package/lib/dist/mcp/liveness-watchdog.d.ts +18 -1
  204. package/lib/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  205. package/lib/dist/mcp/liveness-watchdog.js +73 -13
  206. package/lib/dist/mcp/liveness-watchdog.js.map +1 -1
  207. package/lib/dist/mcp/proxy.d.ts.map +1 -1
  208. package/lib/dist/mcp/proxy.js +30 -4
  209. package/lib/dist/mcp/proxy.js.map +1 -1
  210. package/lib/dist/mcp/query-pool.d.ts +14 -0
  211. package/lib/dist/mcp/query-pool.d.ts.map +1 -1
  212. package/lib/dist/mcp/query-pool.js +18 -0
  213. package/lib/dist/mcp/query-pool.js.map +1 -1
  214. package/lib/dist/mcp/session.d.ts +14 -0
  215. package/lib/dist/mcp/session.d.ts.map +1 -1
  216. package/lib/dist/mcp/session.js +28 -1
  217. package/lib/dist/mcp/session.js.map +1 -1
  218. package/lib/dist/mcp/startup-handshake.d.ts +44 -0
  219. package/lib/dist/mcp/startup-handshake.d.ts.map +1 -0
  220. package/lib/dist/mcp/startup-handshake.js +73 -0
  221. package/lib/dist/mcp/startup-handshake.js.map +1 -0
  222. package/lib/dist/mcp/tools.d.ts +22 -0
  223. package/lib/dist/mcp/tools.d.ts.map +1 -1
  224. package/lib/dist/mcp/tools.js +289 -23
  225. package/lib/dist/mcp/tools.js.map +1 -1
  226. package/lib/dist/mcp/transport.d.ts.map +1 -1
  227. package/lib/dist/mcp/transport.js +18 -0
  228. package/lib/dist/mcp/transport.js.map +1 -1
  229. package/lib/dist/project-config.d.ts +44 -0
  230. package/lib/dist/project-config.d.ts.map +1 -1
  231. package/lib/dist/project-config.js +151 -2
  232. package/lib/dist/project-config.js.map +1 -1
  233. package/lib/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
  234. package/lib/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  235. package/lib/dist/resolution/c-fnptr-synthesizer.js +170 -146
  236. package/lib/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  237. package/lib/dist/resolution/callback-synthesizer.d.ts +1 -1
  238. package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  239. package/lib/dist/resolution/callback-synthesizer.js +1109 -97
  240. package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
  241. package/lib/dist/resolution/cooperative-yield.d.ts +32 -0
  242. package/lib/dist/resolution/cooperative-yield.d.ts.map +1 -0
  243. package/lib/dist/resolution/cooperative-yield.js +42 -0
  244. package/lib/dist/resolution/cooperative-yield.js.map +1 -0
  245. package/lib/dist/resolution/frameworks/cics.d.ts +20 -0
  246. package/lib/dist/resolution/frameworks/cics.d.ts.map +1 -0
  247. package/lib/dist/resolution/frameworks/cics.js +90 -0
  248. package/lib/dist/resolution/frameworks/cics.js.map +1 -0
  249. package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
  250. package/lib/dist/resolution/frameworks/index.js +6 -0
  251. package/lib/dist/resolution/frameworks/index.js.map +1 -1
  252. package/lib/dist/resolution/frameworks/java.d.ts.map +1 -1
  253. package/lib/dist/resolution/frameworks/java.js +14 -6
  254. package/lib/dist/resolution/frameworks/java.js.map +1 -1
  255. package/lib/dist/resolution/frameworks/terraform.d.ts +38 -0
  256. package/lib/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  257. package/lib/dist/resolution/frameworks/terraform.js +277 -0
  258. package/lib/dist/resolution/frameworks/terraform.js.map +1 -0
  259. package/lib/dist/resolution/goframe-synthesizer.d.ts +2 -1
  260. package/lib/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
  261. package/lib/dist/resolution/goframe-synthesizer.js +8 -3
  262. package/lib/dist/resolution/goframe-synthesizer.js.map +1 -1
  263. package/lib/dist/resolution/import-resolver.d.ts +7 -0
  264. package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
  265. package/lib/dist/resolution/import-resolver.js +129 -4
  266. package/lib/dist/resolution/import-resolver.js.map +1 -1
  267. package/lib/dist/resolution/index.d.ts +64 -2
  268. package/lib/dist/resolution/index.d.ts.map +1 -1
  269. package/lib/dist/resolution/index.js +476 -58
  270. package/lib/dist/resolution/index.js.map +1 -1
  271. package/lib/dist/resolution/name-matcher.d.ts +22 -3
  272. package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
  273. package/lib/dist/resolution/name-matcher.js +613 -39
  274. package/lib/dist/resolution/name-matcher.js.map +1 -1
  275. package/lib/dist/resolution/strip-comments.d.ts +1 -1
  276. package/lib/dist/resolution/strip-comments.d.ts.map +1 -1
  277. package/lib/dist/resolution/strip-comments.js +49 -0
  278. package/lib/dist/resolution/strip-comments.js.map +1 -1
  279. package/lib/dist/resolution/types.d.ts +29 -0
  280. package/lib/dist/resolution/types.d.ts.map +1 -1
  281. package/lib/dist/resolution/workspace-packages.d.ts +10 -0
  282. package/lib/dist/resolution/workspace-packages.d.ts.map +1 -1
  283. package/lib/dist/resolution/workspace-packages.js +142 -4
  284. package/lib/dist/resolution/workspace-packages.js.map +1 -1
  285. package/lib/dist/search/identifier-segments.d.ts +60 -0
  286. package/lib/dist/search/identifier-segments.d.ts.map +1 -0
  287. package/lib/dist/search/identifier-segments.js +176 -0
  288. package/lib/dist/search/identifier-segments.js.map +1 -0
  289. package/lib/dist/sync/git-hooks.d.ts.map +1 -1
  290. package/lib/dist/sync/git-hooks.js +2 -0
  291. package/lib/dist/sync/git-hooks.js.map +1 -1
  292. package/lib/dist/sync/watcher.d.ts +10 -5
  293. package/lib/dist/sync/watcher.d.ts.map +1 -1
  294. package/lib/dist/sync/watcher.js +51 -14
  295. package/lib/dist/sync/watcher.js.map +1 -1
  296. package/lib/dist/sync/worktree.d.ts +9 -0
  297. package/lib/dist/sync/worktree.d.ts.map +1 -1
  298. package/lib/dist/sync/worktree.js +45 -0
  299. package/lib/dist/sync/worktree.js.map +1 -1
  300. package/lib/dist/types.d.ts +25 -2
  301. package/lib/dist/types.d.ts.map +1 -1
  302. package/lib/dist/types.js +10 -0
  303. package/lib/dist/types.js.map +1 -1
  304. package/lib/dist/upgrade/index.d.ts +34 -2
  305. package/lib/dist/upgrade/index.d.ts.map +1 -1
  306. package/lib/dist/upgrade/index.js +164 -15
  307. package/lib/dist/upgrade/index.js.map +1 -1
  308. package/lib/dist/upgrade/remove-binary.d.ts +87 -0
  309. package/lib/dist/upgrade/remove-binary.d.ts.map +1 -0
  310. package/lib/dist/upgrade/remove-binary.js +289 -0
  311. package/lib/dist/upgrade/remove-binary.js.map +1 -0
  312. package/lib/dist/upgrade/update-check.d.ts +92 -0
  313. package/lib/dist/upgrade/update-check.d.ts.map +1 -0
  314. package/lib/dist/upgrade/update-check.js +258 -0
  315. package/lib/dist/upgrade/update-check.js.map +1 -0
  316. package/lib/node_modules/.modules.yaml +1 -1
  317. package/lib/node_modules/.pnpm-workspace-state-v1.json +1 -1
  318. package/lib/package.json +1 -1
  319. package/package.json +1 -1
  320. package/lib/dist/reasoning/config.d.ts +0 -45
  321. package/lib/dist/reasoning/config.d.ts.map +0 -1
  322. package/lib/dist/reasoning/config.js +0 -171
  323. package/lib/dist/reasoning/config.js.map +0 -1
  324. package/lib/dist/reasoning/credentials.d.ts +0 -5
  325. package/lib/dist/reasoning/credentials.d.ts.map +0 -1
  326. package/lib/dist/reasoning/credentials.js +0 -83
  327. package/lib/dist/reasoning/credentials.js.map +0 -1
  328. package/lib/dist/reasoning/login.d.ts +0 -21
  329. package/lib/dist/reasoning/login.d.ts.map +0 -1
  330. package/lib/dist/reasoning/login.js +0 -85
  331. package/lib/dist/reasoning/login.js.map +0 -1
  332. package/lib/dist/reasoning/reasoner.d.ts +0 -43
  333. package/lib/dist/reasoning/reasoner.d.ts.map +0 -1
  334. package/lib/dist/reasoning/reasoner.js +0 -308
  335. package/lib/dist/reasoning/reasoner.js.map +0 -1
@@ -46,6 +46,7 @@ exports.hashContent = hashContent;
46
46
  exports.buildDefaultIgnore = buildDefaultIgnore;
47
47
  exports.buildScopeIgnore = buildScopeIgnore;
48
48
  exports.discoverEmbeddedRepoRoots = discoverEmbeddedRepoRoots;
49
+ exports.findUnindexedIgnoredRepos = findUnindexedIgnoredRepos;
49
50
  exports.scanDirectory = scanDirectory;
50
51
  exports.scanDirectoryAsync = scanDirectoryAsync;
51
52
  const fs = __importStar(require("fs"));
@@ -64,6 +65,7 @@ const errors_1 = require("../errors");
64
65
  const utils_1 = require("../utils");
65
66
  const ignore_1 = __importDefault(require("ignore"));
66
67
  const frameworks_1 = require("../resolution/frameworks");
68
+ const cooperative_yield_1 = require("../resolution/cooperative-yield");
67
69
  /**
68
70
  * Number of files to read in parallel during indexing.
69
71
  * File reads are I/O-bound; batching overlaps I/O wait with CPU parse work.
@@ -84,9 +86,10 @@ const SYNC_RECONCILE_YIELD_INTERVAL = 1000;
84
86
  /**
85
87
  * Maximum time (ms) to wait for a single file to parse in the worker thread.
86
88
  * 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.
89
+ * indexing run from freezing. The worker is restarted after a (hard) timeout.
90
+ * Env-overridable via CODEGRAPH_PARSE_TIMEOUT_MS for slow storage (#1231).
88
91
  */
89
- const PARSE_TIMEOUT_MS = 10_000;
92
+ const PARSE_TIMEOUT_MS = (0, parse_pool_1.resolveParseTimeoutMs)(process.env.CODEGRAPH_PARSE_TIMEOUT_MS);
90
93
  /**
91
94
  * Number of files to parse before recycling the worker thread.
92
95
  * WASM linear memory can grow but NEVER shrink (WebAssembly spec limitation).
@@ -176,12 +179,34 @@ const DEFAULT_IGNORE_DIRS = new Set([
176
179
  // Generic cache
177
180
  '.cache',
178
181
  ]);
182
+ /**
183
+ * Android resource directory types. A `res/` tree holds ONLY non-code resources —
184
+ * layouts, drawables, value bags (strings/colors/styles), menus, navigation
185
+ * graphs — split into one typed subdirectory per kind, optionally density/locale/
186
+ * version-qualified (`values-es`, `drawable-hdpi`, `layout-v21`, …). None of it
187
+ * yields an extractable code symbol, yet on an Android app it DOMINATES the tree
188
+ * (one report: 26k XML files = 97% of the project, 0 symbols), bloating the DB,
189
+ * slowing indexing, and skewing both the file count and `codegraph_explore`
190
+ * results (#1047). So these are excluded by default. The structure is
191
+ * self-identifying — a non-Android project has no `res/layout/` etc., so it's
192
+ * untouched — and the only XML that DOES produce symbols (MyBatis mappers) lives
193
+ * under `src/main/resources/`, never `res/`, so nothing useful is dropped.
194
+ * `res/raw/` is deliberately NOT here: it holds arbitrary bundled assets that can
195
+ * be code-ish (a `.sql` schema, a `.js`), so we leave it indexed. Override any of
196
+ * these with a `.gitignore` negation (e.g. `!res/values/`).
197
+ */
198
+ const ANDROID_RES_TYPES = [
199
+ 'anim', 'animator', 'color', 'drawable', 'font', 'layout',
200
+ 'menu', 'mipmap', 'navigation', 'transition', 'values', 'xml',
201
+ ];
179
202
  /** Gitignore-style patterns for the `ignore` matcher: the dirs above plus a few globs. */
180
203
  const DEFAULT_IGNORE_PATTERNS = [
181
204
  ...Array.from(DEFAULT_IGNORE_DIRS, (d) => `${d}/`),
182
205
  '*.egg-info/', // Python packaging metadata
183
206
  'cmake-build-*/', // CLion / CMake build trees
184
207
  'bazel-*/', // Bazel output symlink trees
208
+ // Android resource dirs at any depth, with their qualifier variants (#1047).
209
+ ...ANDROID_RES_TYPES.map((t) => `**/res/${t}*/`),
185
210
  ];
186
211
  /** True if `buf` decodes as strict UTF-8 (no invalid byte sequences). */
187
212
  function isValidUtf8(buf) {
@@ -299,6 +324,160 @@ function loadExcludeMatcher(rootDir) {
299
324
  const patterns = (0, project_config_1.loadExcludePatterns)(rootDir);
300
325
  return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
301
326
  }
327
+ /**
328
+ * Matcher for the project's `codegraph.json` `include` patterns — first-party
329
+ * source to force INTO the index even when `.gitignore` drops it (the general
330
+ * whitelist `includeIgnored` never was — that one only revives *embedded git
331
+ * repos*). The case it exists for: a project under a second VCS (SVN/Perforce)
332
+ * `.gitignore`s its own real source so it stays out of Git, yet we still want it
333
+ * indexed. Returns `null` when nothing is force-included (the zero-config
334
+ * default → no overhead, no extra walk). Built once per scan/sync/scope
335
+ * operation from the scan root.
336
+ */
337
+ function loadIncludeMatcher(rootDir) {
338
+ const patterns = (0, project_config_1.loadIncludePatterns)(rootDir);
339
+ return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
340
+ }
341
+ /** Glob metacharacters that end the static (literal) prefix of an `include` pattern. */
342
+ const GLOB_META = /[*?[\]{}!]/;
343
+ /**
344
+ * The static directory prefix of each `include` pattern — the literal leading
345
+ * path up to the first glob segment — trailing-slashed, used to (a) walk only
346
+ * the opted-in subtrees in `collectIncludedFiles` and (b) let `ScopeIgnore` keep
347
+ * the watcher descending toward them. `Tools/` stays `Tools/`; a recursive
348
+ * `Tools/**` glob yields `Tools/`; `src/local/file.ts` yields `src/local/` (the
349
+ * file's dir); a pattern that starts with a glob (like a leading `**`) yields
350
+ * `''`, meaning "no static root — walk the whole tree". Duplicates and roots
351
+ * nested under a broader root are collapsed so each subtree is walked once.
352
+ */
353
+ function includeStaticRoots(patterns) {
354
+ const roots = new Set();
355
+ for (const pattern of patterns) {
356
+ let p = pattern.replace(/^\/+/, '');
357
+ const trailingSlash = p.endsWith('/');
358
+ if (trailingSlash)
359
+ p = p.slice(0, -1);
360
+ const segs = p.split('/').filter(Boolean);
361
+ const lead = [];
362
+ for (const s of segs) {
363
+ if (GLOB_META.test(s))
364
+ break;
365
+ lead.push(s);
366
+ }
367
+ const hadWildcard = lead.length < segs.length;
368
+ // A wholly-literal pattern with no trailing slash names a file (or a dir we
369
+ // can't tell apart) — drop its last segment so we walk the containing dir
370
+ // and let the matcher pick the file. A trailing slash or a glob means the
371
+ // remaining `lead` is already the directory to walk.
372
+ if (!hadWildcard && !trailingSlash && lead.length > 0)
373
+ lead.pop();
374
+ if (lead.length === 0) {
375
+ roots.clear();
376
+ roots.add('');
377
+ return ['']; // a top-level glob forces a whole-tree walk; nothing narrower matters
378
+ }
379
+ roots.add(lead.join('/') + '/');
380
+ }
381
+ // Collapse roots nested under a broader one (e.g. drop `a/b/` if `a/` is present).
382
+ const all = [...roots];
383
+ return all.filter((r) => !all.some((other) => other !== r && r.startsWith(other)));
384
+ }
385
+ /**
386
+ * Actively discover the source files an `include` whitelist forces in. `git
387
+ * ls-files` never lists gitignored files, so a filtered filesystem walk of just
388
+ * the opted-in subtrees (`includeStaticRoots`) is the only way to find them.
389
+ * Returns project-root-relative, normalized source-file paths.
390
+ *
391
+ * A file is collected when it MATCHES `include`, is NOT hit by `exclude` (an
392
+ * explicit exclude always wins), is a recognized source file, and does not live
393
+ * under a built-in default-ignored dir (`node_modules`, `dist`, …), `.git`, or
394
+ * CodeGraph's data dir — those are never resurfaced, mirroring `ScopeIgnore`.
395
+ * `.gitignore` is deliberately NOT consulted: overriding it is the whole point.
396
+ */
397
+ function collectIncludedFiles(rootDir, include, exclude, roots, overrides) {
398
+ const out = new Set();
399
+ const defaults = defaultsOnlyIgnore();
400
+ const visited = new Set();
401
+ const consider = (abs, rel, isDir) => {
402
+ if (isDir) {
403
+ if (defaults.ignores(rel + '/'))
404
+ return; // never node_modules/dist/… via include
405
+ // An explicit `exclude` always wins over `include`; prune the whole subtree
406
+ // here so a large excluded dir (a committed frontend's own vendored deps,
407
+ // build output, …) is never walked — the per-file guard below still catches
408
+ // anything a directory pattern doesn't, so this is a pure efficiency win.
409
+ if (exclude && exclude.ignores(rel + '/'))
410
+ return;
411
+ walk(abs);
412
+ }
413
+ else {
414
+ if (defaults.ignores(rel))
415
+ return;
416
+ if (!include.ignores(rel))
417
+ return;
418
+ if (exclude && exclude.ignores(rel))
419
+ return;
420
+ if (!(0, grammars_1.isSourceFile)(rel, overrides))
421
+ return;
422
+ out.add(rel);
423
+ }
424
+ };
425
+ function walk(absDir) {
426
+ let realDir;
427
+ try {
428
+ realDir = fs.realpathSync(absDir);
429
+ }
430
+ catch {
431
+ return;
432
+ }
433
+ if (visited.has(realDir))
434
+ return; // symlink-cycle guard
435
+ visited.add(realDir);
436
+ let entries;
437
+ try {
438
+ entries = fs.readdirSync(absDir, { withFileTypes: true });
439
+ }
440
+ catch {
441
+ return;
442
+ }
443
+ for (const entry of entries) {
444
+ if (entry.name === '.git' || (0, directory_1.isCodeGraphDataDir)(entry.name))
445
+ continue;
446
+ const abs = path.join(absDir, entry.name);
447
+ const rel = (0, utils_1.normalizePath)(path.relative(rootDir, abs));
448
+ if (!rel || rel.startsWith('..'))
449
+ continue;
450
+ if (entry.isSymbolicLink()) {
451
+ try {
452
+ const st = fs.statSync(fs.realpathSync(abs));
453
+ consider(abs, rel, st.isDirectory());
454
+ }
455
+ catch {
456
+ // broken symlink — skip
457
+ }
458
+ continue;
459
+ }
460
+ consider(abs, rel, entry.isDirectory());
461
+ }
462
+ }
463
+ for (const root of roots) {
464
+ walk(root === '' ? rootDir : path.join(rootDir, root));
465
+ }
466
+ return out;
467
+ }
468
+ /**
469
+ * The included source files (`codegraph.json` `include`) for a scan root, or an
470
+ * empty set when nothing is force-included. Centralizes loading the matcher,
471
+ * roots, exclude, and overrides so both enumeration paths (git and filesystem
472
+ * walk) add the same files.
473
+ */
474
+ function collectIncludedFilesForRoot(rootDir) {
475
+ const include = loadIncludeMatcher(rootDir);
476
+ if (!include)
477
+ return new Set();
478
+ const roots = includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir));
479
+ return collectIncludedFiles(rootDir, include, loadExcludeMatcher(rootDir), roots, (0, project_config_1.loadExtensionOverrides)(rootDir));
480
+ }
302
481
  /**
303
482
  * `git ls-files --directory` collapses a wholly-untracked/ignored directory into
304
483
  * one entry — and when the command's own cwd is such a directory (the indexed
@@ -439,6 +618,8 @@ function findNestedGitRepos(absDir, relPrefix) {
439
618
  class ScopeIgnore {
440
619
  rootMatcher;
441
620
  exclude;
621
+ include;
622
+ includeRoots;
442
623
  embedded;
443
624
  defaults = defaultsOnlyIgnore();
444
625
  constructor(rootMatcher, embedded,
@@ -447,9 +628,21 @@ class ScopeIgnore {
447
628
  * full root-relative path. Wins over everything else — an explicit user
448
629
  * exclude applies even to tracked files and even inside embedded repos.
449
630
  */
450
- exclude = null) {
631
+ exclude = null,
632
+ /**
633
+ * Project `codegraph.json` `include` patterns — first-party source forced
634
+ * INTO the index despite `.gitignore`. When a path matches, it is NOT
635
+ * ignored (so the watcher watches it), overriding `.gitignore`/`rootMatcher`
636
+ * — but never `exclude` (checked first) and never a built-in default-ignored
637
+ * dir. `includeRoots` are the static prefixes so a gitignored ANCESTOR
638
+ * directory of an included subtree still isn't pruned by the directory
639
+ * walker/watcher.
640
+ */
641
+ include = null, includeRoots = []) {
451
642
  this.rootMatcher = rootMatcher;
452
643
  this.exclude = exclude;
644
+ this.include = include;
645
+ this.includeRoots = includeRoots;
453
646
  // Longest root first so paths in nested embedded repos hit the innermost matcher.
454
647
  this.embedded = [...embedded].sort((a, b) => b.root.length - a.root.length);
455
648
  }
@@ -459,6 +652,20 @@ class ScopeIgnore {
459
652
  // everywhere, including ancestors of embedded repos.
460
653
  if (this.exclude && this.exclude.ignores(rel))
461
654
  return true;
655
+ // User `include`: force first-party source in despite `.gitignore`. Never
656
+ // resurfaces a built-in default-ignored dir (node_modules/dist/…), so an
657
+ // include pattern can't accidentally pull in dependency/build trees.
658
+ if (this.include && !this.defaults.ignores(rel)) {
659
+ if (rel.endsWith('/')) {
660
+ // A directory on (or leading to) an included subtree must stay walkable
661
+ // so the watcher/walker descends to reach the forced-in files.
662
+ if (this.includeRoots.some((r) => r.startsWith(rel) || rel.startsWith(r)))
663
+ return false;
664
+ }
665
+ else if (this.include.ignores(rel)) {
666
+ return false;
667
+ }
668
+ }
462
669
  for (const { root, matcher } of this.embedded) {
463
670
  if (rel.startsWith(root)) {
464
671
  const inner = rel.slice(root.length);
@@ -485,7 +692,37 @@ exports.ScopeIgnore = ScopeIgnore;
485
692
  */
486
693
  function buildScopeIgnore(rootDir, embeddedRoots) {
487
694
  const roots = embeddedRoots ? [...embeddedRoots] : discoverEmbeddedRepoRoots(rootDir);
488
- return new ScopeIgnore(buildDefaultIgnore(rootDir), roots.map((root) => ({ root, matcher: buildDefaultIgnore(path.join(rootDir, root)) })), loadExcludeMatcher(rootDir));
695
+ const include = loadIncludeMatcher(rootDir);
696
+ 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)) : []);
697
+ }
698
+ /**
699
+ * Whether an embedded repo found as a tracked gitlink (mode 160000, #1031/#1033)
700
+ * must be SKIPPED rather than indexed. A gitlink is tracked, so `.gitignore`
701
+ * can't untrack it — but the discovery passes for it must still honor the same
702
+ * scope rules as every other path, or a gitignored reference/data dir full of
703
+ * `git add`ed clones gets pulled into the index against the user's stated intent
704
+ * (#1065). Two reasons to skip:
705
+ * 1. It sits in a built-in default-ignored location — an npm git-dependency
706
+ * under `node_modules` is never project code; not even an explicit opt-in
707
+ * revives it (matches `findIgnoredEmbeddedRepos`).
708
+ * 2. The parent repo's own `.gitignore` covers its path and the project did
709
+ * NOT opt that path in via `codegraph.json` `includeIgnored`. The gitignore
710
+ * rule is the user's stated intent to keep that path out of scope, exactly
711
+ * as for an UNtracked embedded repo — respect it by default, opt back in
712
+ * with `includeIgnored` (#514, #970, #976).
713
+ * `relDir` is repoDir-relative (trailing-slashed); `prefix` is repoDir's
714
+ * scan-root-relative path so the `includeIgnored` pattern is matched on the full
715
+ * scan-root-relative path. `defaults` is `defaultsOnlyIgnore()` and `repoIgnore`
716
+ * is `buildDefaultIgnore(repoDir)` (defaults + the repo's own `.gitignore`),
717
+ * both passed in so they're built once per repo level rather than per gitlink.
718
+ */
719
+ function gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored) {
720
+ if (defaults.ignores(relDir))
721
+ return true; // default-ignored — never index, opt-in can't revive
722
+ if (!repoIgnore.ignores(relDir))
723
+ return false; // not ignored at all — index as before (#1031/#1033)
724
+ // Gitignored by the repo's own rules — skip unless the project opted it in.
725
+ return !includeIgnored?.ignores((0, utils_1.normalizePath)(prefix + relDir));
489
726
  }
490
727
  /**
491
728
  * Standalone discovery of every embedded repo root under `rootDir` (relative,
@@ -517,6 +754,31 @@ function discoverEmbeddedRepoRoots(rootDir) {
517
754
  }
518
755
  }
519
756
  catch { /* untracked listing failed — ignored-side discovery still runs */ }
757
+ // Unexpanded gitlinks (mode 160000) with a real checkout on disk — embedded
758
+ // repos `git add`ed without `.gitmodules`, or submodules not active here. The
759
+ // untracked listing above can't see them (they're tracked), so find them the
760
+ // same way collectGitFiles does, keeping watcher scope == indexer scope.
761
+ // (#1031, #1033)
762
+ try {
763
+ 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 });
764
+ const repoIgnore = buildDefaultIgnore(repoAbs);
765
+ for (const entry of staged.split('\0')) {
766
+ if (!entry || entry.slice(0, 6) !== '160000')
767
+ continue;
768
+ const tab = entry.indexOf('\t');
769
+ if (tab === -1)
770
+ continue;
771
+ const rel = entry.slice(tab + 1);
772
+ const relDir = rel.endsWith('/') ? rel : rel + '/';
773
+ // A gitlink under a gitignored path is respected (not indexed) unless the
774
+ // project opted it in — same rule as the untracked-ignored kind (#1065).
775
+ if (gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored))
776
+ continue;
777
+ if (classifyGitDir(path.join(repoAbs, rel)) === 'embedded')
778
+ candidates.push(relDir);
779
+ }
780
+ }
781
+ catch { /* staged listing failed — other discovery still runs */ }
520
782
  candidates.push(...findIgnoredEmbeddedRepos(repoAbs, includeIgnored, prefix));
521
783
  for (const rel of candidates) {
522
784
  const full = (0, utils_1.normalizePath)(prefix + rel);
@@ -527,6 +789,50 @@ function discoverEmbeddedRepoRoots(rootDir) {
527
789
  visit(rootDir, '');
528
790
  return out;
529
791
  }
792
+ /**
793
+ * Cap on how many skipped gitignored repos the CLI hint enumerates — a huge
794
+ * gitignored data dir full of clones must never turn the hint scan into a long
795
+ * walk. Enough to make the point; the caller says "+N more" past this.
796
+ */
797
+ const UNINDEXED_IGNORED_REPO_HINT_CAP = 100;
798
+ /**
799
+ * The INVERSE of the gitignored side of {@link discoverEmbeddedRepoRoots}:
800
+ * nested git repositories under a gitignored directory that the project has NOT
801
+ * opted into via `codegraph.json` `includeIgnored`. These are real repos the
802
+ * default `init`/`index` deliberately skips because `.gitignore` excludes them
803
+ * (#970, #976) — most visibly the "super-repo `.gitignore`s its child repos"
804
+ * layout (#1156), where `init` at the parent correctly indexes ~nothing while
805
+ * `init` inside each child works. The CLI uses this to turn that silent empty
806
+ * index into an actionable hint: it names the skipped repos and offers to opt
807
+ * them in. Paths are `rootDir`-relative and trailing-slashed (valid
808
+ * `includeIgnored` patterns as-is). Returns `[]` for a non-git root (a
809
+ * filesystem walk already descends into nested repos there), skips built-in
810
+ * default-ignored dirs (`node_modules`, …), and is bounded so it never stalls
811
+ * on a giant ignored tree.
812
+ */
813
+ function findUnindexedIgnoredRepos(rootDir) {
814
+ try {
815
+ (0, child_process_1.execFileSync)('git', ['rev-parse', '--git-dir'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
816
+ }
817
+ catch {
818
+ return [];
819
+ }
820
+ const defaults = defaultsOnlyIgnore();
821
+ const includeIgnored = loadIncludeIgnoredMatcher(rootDir);
822
+ const repos = [];
823
+ for (const dir of listIgnoredDirs(rootDir)) {
824
+ if (defaults.ignores(dir))
825
+ continue; // node_modules etc. — never project code
826
+ if (includeIgnored?.ignores((0, utils_1.normalizePath)(dir)))
827
+ continue; // already opted in — nothing to nag about
828
+ for (const repo of findNestedGitRepos(path.join(rootDir, dir), dir)) {
829
+ repos.push(repo);
830
+ if (repos.length >= UNINDEXED_IGNORED_REPO_HINT_CAP)
831
+ return repos;
832
+ }
833
+ }
834
+ return repos;
835
+ }
530
836
  /**
531
837
  * Discover embedded repos hidden by `repoDir`'s OWN gitignore rules: for each
532
838
  * gitignored directory, search for nested `.git` roots. Returns repo paths
@@ -582,14 +888,37 @@ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored =
582
888
  // Without this, monorepos using submodules index 0 files. (See issue #147.)
583
889
  // Note: --recurse-submodules only supports -c/--cached and --stage modes — it
584
890
  // can't be combined with -o, so untracked files are gathered separately below.
891
+ //
892
+ // We use --stage (-s) rather than -c so each entry carries its file mode. That
893
+ // lets us spot gitlink entries (mode 160000) that --recurse-submodules did NOT
894
+ // expand: a nested repo `git add`ed without a `.gitmodules` entry, or a
895
+ // submodule that isn't active/initialized in this checkout. Such a gitlink
896
+ // falls through every pass — it's tracked, so the untracked `-o` listing below
897
+ // never reports it, and --recurse-submodules only expands ACTIVE submodules —
898
+ // so its source would be silently skipped, leaving only the super-repo's own
899
+ // files indexed. We collect those gitlinks here and recurse into them below.
900
+ // (An active submodule is expanded inline by --recurse-submodules and so never
901
+ // surfaces as a 160000 entry — only the unhandled gitlinks do.) (#1031, #1033)
902
+ //
585
903
  // -z gives NUL-separated, unquoted output so non-ASCII (e.g. CJK) paths
586
904
  // survive verbatim. Without it git octal-escapes and double-quotes such paths
587
905
  // (the core.quotepath default), and the quoted form never matches a real file
588
- // on disk → those files are silently dropped from the index. (#541)
589
- const tracked = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-c', '--recurse-submodules'], gitOpts);
590
- for (const rel of tracked.split('\0')) {
591
- if (rel)
592
- files.add((0, utils_1.normalizePath)(prefix + rel));
906
+ // on disk → those files are silently dropped from the index. (#541) With -s the
907
+ // path follows a TAB after the `<mode> <object> <stage>` prefix.
908
+ const gitlinkRels = [];
909
+ const tracked = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-s', '--recurse-submodules'], gitOpts);
910
+ for (const entry of tracked.split('\0')) {
911
+ if (!entry)
912
+ continue;
913
+ const tab = entry.indexOf('\t');
914
+ if (tab === -1)
915
+ continue; // --stage always emits "<mode> <object> <stage>\t<path>"
916
+ const rel = entry.slice(tab + 1);
917
+ if (entry.slice(0, 6) === '160000') {
918
+ gitlinkRels.push(rel); // an unexpanded gitlink — recursed into below, not a source file itself
919
+ continue;
920
+ }
921
+ files.add((0, utils_1.normalizePath)(prefix + rel));
593
922
  }
594
923
  // Untracked files (submodules manage their own untracked state). Embedded git
595
924
  // repos surface here as a single "subdir/" entry that git refuses to descend
@@ -615,6 +944,32 @@ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored =
615
944
  }
616
945
  files.add((0, utils_1.normalizePath)(prefix + rel));
617
946
  }
947
+ // Gitlink entries (mode 160000) that --recurse-submodules left unexpanded —
948
+ // an embedded repo `git add`ed without `.gitmodules`, or a submodule not
949
+ // active/initialized in this checkout. When such a gitlink has a real working
950
+ // tree on disk it is distinct first-party code we must index as its own
951
+ // embedded repo: the tracked pass skipped its contents and the untracked pass
952
+ // never sees it (it's tracked, not "other"). A gitlink with no checkout on disk
953
+ // (an uninitialized submodule — empty dir, no `.git`) has nothing to index and
954
+ // is left alone, as is a submodule worktree (a duplicate view, #945). (#1031, #1033)
955
+ if (gitlinkRels.length > 0) {
956
+ const defaults = defaultsOnlyIgnore();
957
+ const repoIgnore = buildDefaultIgnore(repoDir);
958
+ for (const rel of gitlinkRels) {
959
+ const relDir = rel.endsWith('/') ? rel : rel + '/';
960
+ // A gitlink under a gitignored path is respected (not indexed) unless the
961
+ // project opted it in via `includeIgnored` — keep tracked gitlinks under
962
+ // the same scope rule as the untracked-ignored kind below (#1065).
963
+ if (gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored))
964
+ continue;
965
+ const childDir = path.join(repoDir, rel);
966
+ // 'embedded' = a real .git checkout on disk; 'worktree' and 'none' are skipped.
967
+ if (classifyGitDir(childDir) !== 'embedded')
968
+ continue;
969
+ embeddedRoots?.add((0, utils_1.normalizePath)(prefix + relDir));
970
+ collectGitFiles(childDir, prefix + relDir, files, embeddedRoots, includeIgnored);
971
+ }
972
+ }
618
973
  // Embedded repos hidden by THIS repo's ignore rules (`/packages/` in a
619
974
  // super-repo .gitignore) never appear in any listing above. By default they
620
975
  // stay hidden — `.gitignore` is respected (#970, #976). They are recursed into
@@ -658,7 +1013,15 @@ function getGitVisibleFiles(rootDir) {
658
1013
  // not the parent's: the parent's .gitignore hides the child repo from git,
659
1014
  // not from the index. (#514)
660
1015
  const ig = buildScopeIgnore(rootDir, embeddedRoots);
661
- return new Set([...files].filter((f) => !ig.ignores(f)));
1016
+ const visible = new Set([...files].filter((f) => !ig.ignores(f)));
1017
+ // Force-include first-party source the project whitelisted in
1018
+ // `codegraph.json` `include`. These are gitignored, so `git ls-files` never
1019
+ // listed them above — discover them directly off disk and add them. (The
1020
+ // common SVN+Git dual-VCS case: source committed to SVN, gitignored out of
1021
+ // Git, but still wanted in the graph.)
1022
+ for (const f of collectIncludedFilesForRoot(rootDir))
1023
+ visible.add(f);
1024
+ return visible;
662
1025
  }
663
1026
  catch {
664
1027
  return null;
@@ -981,8 +1344,51 @@ function scanDirectoryWalk(rootDir, onProgress) {
981
1344
  if (exclude)
982
1345
  baseMatchers.push({ dir: rootDir, ig: exclude });
983
1346
  walk(rootDir, baseMatchers);
1347
+ // Force-include first-party source whitelisted in `codegraph.json` `include`
1348
+ // — the walk above honours `.gitignore`, so anything gitignored was dropped;
1349
+ // add it back here (deduped). Mirrors the git path's union.
1350
+ const included = collectIncludedFilesForRoot(rootDir);
1351
+ if (included.size > 0) {
1352
+ const seen = new Set(files);
1353
+ for (const f of included) {
1354
+ if (!seen.has(f)) {
1355
+ files.push(f);
1356
+ seen.add(f);
1357
+ }
1358
+ }
1359
+ }
984
1360
  return files;
985
1361
  }
1362
+ /**
1363
+ * Resurrect a resolution edge that is about to be dropped (its target symbol
1364
+ * was removed, renamed, or its whole file deleted) as the ORIGINAL unresolved
1365
+ * reference that created it, read from the refName/refKind stamp
1366
+ * `createEdges` writes into edge metadata. Inserted as status='pending', the
1367
+ * ref is consumed by the same sync's resolution sweep: it rebinds to an
1368
+ * alternative definition if one exists, or parks as status='failed' where the
1369
+ * #1240 retry finds it if the symbol later reappears.
1370
+ *
1371
+ * Returns null — drop silently, the pre-#1240 behavior — for edges without a
1372
+ * refName stamp (created before the stamp existed, or synthesized): rebuilding
1373
+ * a ref from the target's plain node name would strip the receiver/qualifier
1374
+ * context the original text carried (`h.greet` → `greet`) and could rebind
1375
+ * somewhere a full re-index never would. Silent beats wrong.
1376
+ */
1377
+ function resurrectRefFromDroppedEdge(e) {
1378
+ const refName = e.metadata?.refName;
1379
+ if (typeof refName !== 'string' || refName.length === 0)
1380
+ return null;
1381
+ const refKind = typeof e.metadata?.refKind === 'string' ? e.metadata.refKind : e.kind;
1382
+ return {
1383
+ fromNodeId: e.source,
1384
+ referenceName: refName,
1385
+ referenceKind: refKind,
1386
+ line: e.line ?? 0,
1387
+ column: e.column ?? 0,
1388
+ filePath: e.sourceFilePath,
1389
+ language: e.sourceLanguage,
1390
+ };
1391
+ }
986
1392
  /**
987
1393
  * Extraction orchestrator
988
1394
  */
@@ -1075,7 +1481,12 @@ class ExtractionOrchestrator {
1075
1481
  /**
1076
1482
  * Index all files in the project
1077
1483
  */
1078
- async indexAll(onProgress, signal, verbose) {
1484
+ async indexAll(onProgress, signal, verbose,
1485
+ // Writer-side backstop for deferred WAL checkpointing (#1231): returns
1486
+ // null in the normal case, or a promise to await (at this safe,
1487
+ // between-transactions boundary) when the WAL has outrun the off-thread
1488
+ // checkpointer past its hard cap. See db/wal-valve.ts.
1489
+ walBackpressure) {
1079
1490
  await (0, grammars_1.initGrammars)();
1080
1491
  const startTime = Date.now();
1081
1492
  const errors = [];
@@ -1088,6 +1499,7 @@ class ExtractionOrchestrator {
1088
1499
  // Threaded into language detection so custom-extension files load the right
1089
1500
  // grammar and store under the mapped language.
1090
1501
  const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
1502
+ const vbaTargets = (0, project_config_1.loadVbaConfig)(this.rootDir).targets;
1091
1503
  const log = verbose
1092
1504
  ? (msg) => { console.log(`[worker] ${msg}`); }
1093
1505
  : (_msg) => { };
@@ -1097,6 +1509,10 @@ class ExtractionOrchestrator {
1097
1509
  current: 0,
1098
1510
  total: 0,
1099
1511
  });
1512
+ // Phase attribution to stderr (same opt-in as the synthesis timings):
1513
+ // early-run 5-10s single stalls were observed on 95k-file repos but never
1514
+ // attributed — these labels settle scan vs framework-detect vs grammars.
1515
+ const tScan = Date.now();
1100
1516
  const files = await scanDirectoryAsync(this.rootDir, (current, file) => {
1101
1517
  onProgress?.({
1102
1518
  phase: 'scanning',
@@ -1105,13 +1521,18 @@ class ExtractionOrchestrator {
1105
1521
  currentFile: file,
1106
1522
  });
1107
1523
  });
1524
+ if (process.env.CODEGRAPH_SYNTH_TIMINGS)
1525
+ console.error(`[phase-timing] scan: ${Date.now() - tScan}ms (${files.length} files)`);
1108
1526
  // Detect frameworks once per indexAll run using the scanned file list.
1109
1527
  // Names are passed to each parse call so framework-specific extractors
1110
1528
  // (route nodes, middleware, etc.) run after the tree-sitter pass.
1111
1529
  // Framework detection is reset each run so adding e.g. requirements.txt
1112
1530
  // between runs is picked up without restarting the process.
1113
1531
  this.detectedFrameworkNames = null;
1532
+ const tFw = Date.now();
1114
1533
  const frameworkNames = this.ensureDetectedFrameworks(files);
1534
+ if (process.env.CODEGRAPH_SYNTH_TIMINGS)
1535
+ console.error(`[phase-timing] framework-detect: ${Date.now() - tFw}ms`);
1115
1536
  if (signal?.aborted) {
1116
1537
  return {
1117
1538
  success: false,
@@ -1152,6 +1573,11 @@ class ExtractionOrchestrator {
1152
1573
  // CODEGRAPH_PARSE_WORKERS: explicit worker count; 1 = the old single-worker
1153
1574
  // behaviour (the conservative rollback). Unset → clamp(cores-1, 1, 8).
1154
1575
  const poolSize = (0, parse_pool_1.resolveParsePoolSize)(process.env.CODEGRAPH_PARSE_WORKERS, os.cpus().length);
1576
+ // Read each needed grammar's WASM ONCE here and hand the bytes to every
1577
+ // worker, so spawns/respawns load grammars from memory instead of
1578
+ // re-reading them from disk (#1231: on an HDD, respawn re-reads amplify
1579
+ // the very I/O contention that caused the respawn).
1580
+ const grammarBuffers = await (0, grammars_1.readGrammarWasmBytes)(neededLanguages);
1155
1581
  pool = new parse_pool_1.ParseWorkerPool({
1156
1582
  languages: neededLanguages,
1157
1583
  size: poolSize,
@@ -1159,6 +1585,7 @@ class ExtractionOrchestrator {
1159
1585
  recycleInterval: WORKER_RECYCLE_INTERVAL,
1160
1586
  parseTimeoutMs: PARSE_TIMEOUT_MS,
1161
1587
  log,
1588
+ grammarBuffers,
1162
1589
  });
1163
1590
  log(`Parse worker pool: ${poolSize} worker(s)`);
1164
1591
  }
@@ -1175,8 +1602,8 @@ class ExtractionOrchestrator {
1175
1602
  const parseFile = (filePath, content) => {
1176
1603
  const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1177
1604
  if (!pool)
1178
- return Promise.resolve((0, tree_sitter_1.extractFromSource)(filePath, content, language, frameworkNames));
1179
- return pool.requestParse({ filePath, content, language, frameworkNames });
1605
+ return Promise.resolve((0, tree_sitter_1.extractFromSource)(filePath, content, language, frameworkNames, vbaTargets));
1606
+ return pool.requestParse({ filePath, content, language, frameworkNames, vbaTargets });
1180
1607
  };
1181
1608
  // --- Bounded rolling-window dispatch, ordered commit ---
1182
1609
  // Reads stay batched/parallel; parses run concurrently across the pool; the
@@ -1194,12 +1621,23 @@ class ExtractionOrchestrator {
1194
1621
  let nextSeq = 0; // file-order sequence assigned at dispatch
1195
1622
  let nextToStore = 0; // cursor: next sequence to commit
1196
1623
  let aborted = false;
1197
- const storeResult = (filePath, content, stats, result) => {
1624
+ // Yielder for the in-order commit path: a single giant generated file's
1625
+ // store is otherwise one unyielding multi-second transaction span on the
1626
+ // main thread (5–14s single stalls measured on llvm-project), starving
1627
+ // the #850 watchdog heartbeat on slow hardware.
1628
+ const commitYield = (0, cooperative_yield_1.createYielder)();
1629
+ const storeResult = async (filePath, content, stats, result) => {
1198
1630
  processed++;
1631
+ // WAL hard-cap backstop: between files (never mid-transaction), pause
1632
+ // the store until the off-thread checkpoint catches up. Resolves to
1633
+ // null in the normal case — a single size check, no cost.
1634
+ const bp = walBackpressure?.();
1635
+ if (bp)
1636
+ await bp;
1199
1637
  // Store in database on main thread (SQLite is not thread-safe)
1200
1638
  if (result.nodes.length > 0 || result.errors.length === 0) {
1201
1639
  const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1202
- this.storeExtractionResult(filePath, content, language, stats, result);
1640
+ await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
1203
1641
  }
1204
1642
  if (result.errors.length > 0) {
1205
1643
  for (const err of result.errors) {
@@ -1243,20 +1681,35 @@ class ExtractionOrchestrator {
1243
1681
  };
1244
1682
  // Commit buffered parses to the DB in file order, advancing the cursor over
1245
1683
  // contiguous completed results. Runs after each parse settles (and once more
1246
- // after the drain). storeResult / recordParseFailure run here single-threaded,
1247
- // so shared counters and SQLite writes never race despite parallel parsing.
1684
+ // after the drain). storeResult is now async (it yields between chunked
1685
+ // inserts), so commits are SERIALIZED on a promise chain concurrent parse
1686
+ // completions append to the chain instead of interleaving mid-store, which
1687
+ // preserves both the file-order commit invariant (#1015: resolution
1688
+ // disambiguates same-named candidates by insertion order) and the
1689
+ // single-writer discipline for SQLite. Errors are recorded and re-thrown
1690
+ // at the drain, matching the old synchronous propagation.
1691
+ let flushChain = Promise.resolve();
1692
+ let flushError = null;
1248
1693
  const flushOrdered = () => {
1249
- if (aborted)
1250
- return;
1251
- while (completed.has(nextToStore)) {
1252
- const item = completed.get(nextToStore);
1253
- completed.delete(nextToStore);
1254
- nextToStore++;
1255
- if (item.ok)
1256
- storeResult(item.filePath, item.content, item.stats, item.result);
1257
- else
1258
- recordParseFailure(item.filePath, item.err);
1259
- }
1694
+ flushChain = flushChain.then(async () => {
1695
+ if (aborted || flushError)
1696
+ return;
1697
+ try {
1698
+ while (completed.has(nextToStore)) {
1699
+ const item = completed.get(nextToStore);
1700
+ completed.delete(nextToStore);
1701
+ nextToStore++;
1702
+ if (item.ok)
1703
+ await storeResult(item.filePath, item.content, item.stats, item.result);
1704
+ else
1705
+ recordParseFailure(item.filePath, item.err);
1706
+ }
1707
+ }
1708
+ catch (err) {
1709
+ flushError = err;
1710
+ }
1711
+ });
1712
+ return flushChain;
1260
1713
  };
1261
1714
  // Dispatch one file's parse (parses run concurrently across the pool), tagged
1262
1715
  // with its file-order sequence so flushOrdered commits results in order. The
@@ -1279,10 +1732,15 @@ class ExtractionOrchestrator {
1279
1732
  // buffered), not just in-flight: a slow file sitting at the commit cursor
1280
1733
  // lets later parses finish and buffer, which would otherwise grow without
1281
1734
  // bound. Wait for parses to settle (each may advance the cursor) until the
1282
- // window has room. `inFlight.size > 0` guards against an empty race the
1283
- // cursor file is always still in flight when the window is full.
1284
- while (nextSeq - nextToStore >= windowSize && inFlight.size > 0) {
1285
- await Promise.race(inFlight);
1735
+ // window has room. When nothing is in flight but the window is still full,
1736
+ // the async commit chain is what's behind await it so the cursor
1737
+ // advances (buffered items hold whole file contents, so this bound is
1738
+ // load-bearing for memory).
1739
+ while (nextSeq - nextToStore >= windowSize) {
1740
+ if (inFlight.size > 0)
1741
+ await Promise.race(inFlight);
1742
+ else
1743
+ await flushOrdered();
1286
1744
  }
1287
1745
  };
1288
1746
  for (let i = 0; i < files.length; i += FILE_IO_BATCH_SIZE) {
@@ -1357,7 +1815,9 @@ class ExtractionOrchestrator {
1357
1815
  // then commit any results the cursor hasn't reached yet.
1358
1816
  if (!aborted) {
1359
1817
  await Promise.all(inFlight);
1360
- flushOrdered();
1818
+ await flushOrdered();
1819
+ if (flushError)
1820
+ throw flushError;
1361
1821
  }
1362
1822
  if (signal?.aborted || aborted) {
1363
1823
  if (pool)
@@ -1367,6 +1827,7 @@ class ExtractionOrchestrator {
1367
1827
  filesIndexed,
1368
1828
  filesSkipped,
1369
1829
  filesErrored,
1830
+ filesDiscovered: total,
1370
1831
  nodesCreated: totalNodes,
1371
1832
  edgesCreated: totalEdges,
1372
1833
  errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
@@ -1385,11 +1846,16 @@ class ExtractionOrchestrator {
1385
1846
  await new Promise(resolve => setImmediate(resolve));
1386
1847
  // Retry pass: files that failed due to WASM memory corruption may succeed
1387
1848
  // on a fresh worker with a clean heap. Recycle before each attempt so
1388
- // every file gets the absolute cleanest WASM state possible.
1849
+ // every file gets the absolute cleanest WASM state possible. Timeouts are
1850
+ // retried too (#1231): most are main-thread-stall artifacts, not slow
1851
+ // parses, and this pass parses one file at a time with the store strictly
1852
+ // after each parse resolves, so the stall window can't recur here.
1389
1853
  const retryableErrors = errors.filter((e) => e.code === 'parse_error' && e.filePath &&
1390
- (e.message.includes('Worker exited') || e.message.includes('memory access out of bounds')));
1854
+ (e.message.includes('Worker exited') ||
1855
+ e.message.includes('memory access out of bounds') ||
1856
+ e.message.includes('timed out')));
1391
1857
  if (retryableErrors.length > 0 && pool) {
1392
- log(`Retrying ${retryableErrors.length} files that failed due to WASM memory errors...`);
1858
+ log(`Retrying ${retryableErrors.length} files that failed due to WASM memory errors or timeouts...`);
1393
1859
  // Fresh WASM heaps for the retry phase. A retry that still crashes its
1394
1860
  // worker makes the pool respawn it, so later retries keep landing on clean
1395
1861
  // workers too.
@@ -1420,7 +1886,7 @@ class ExtractionOrchestrator {
1420
1886
  if (result.nodes.length > 0 || result.errors.length === 0) {
1421
1887
  const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1422
1888
  const stats = await fsp.stat(path.join(this.rootDir, filePath));
1423
- this.storeExtractionResult(filePath, content, language, stats, result);
1889
+ await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
1424
1890
  const idx = errors.indexOf(errEntry);
1425
1891
  if (idx >= 0)
1426
1892
  errors.splice(idx, 1);
@@ -1468,7 +1934,7 @@ class ExtractionOrchestrator {
1468
1934
  if (result.nodes.length > 0 || result.errors.length === 0) {
1469
1935
  const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
1470
1936
  const stats = await fsp.stat(path.join(this.rootDir, filePath));
1471
- this.storeExtractionResult(filePath, fullContent, language, stats, result);
1937
+ await this.storeExtractionResult(filePath, fullContent, language, stats, result, commitYield);
1472
1938
  const idx = errors.indexOf(errEntry);
1473
1939
  if (idx >= 0)
1474
1940
  errors.splice(idx, 1);
@@ -1489,6 +1955,7 @@ class ExtractionOrchestrator {
1489
1955
  filesIndexed,
1490
1956
  filesSkipped,
1491
1957
  filesErrored,
1958
+ filesDiscovered: total,
1492
1959
  nodesCreated: totalNodes,
1493
1960
  edgesCreated: totalEdges,
1494
1961
  errors,
@@ -1632,14 +2099,21 @@ class ExtractionOrchestrator {
1632
2099
  const result = (0, tree_sitter_1.extractFromSource)(relativePath, content, language, frameworkNames);
1633
2100
  // Store in database
1634
2101
  if (result.nodes.length > 0 || result.errors.length === 0) {
1635
- this.storeExtractionResult(relativePath, content, language, stats, result);
2102
+ await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
1636
2103
  }
1637
2104
  return result;
1638
2105
  }
1639
2106
  /**
1640
2107
  * Store extraction result in database
1641
2108
  */
1642
- storeExtractionResult(filePath, content, language, stats, result) {
2109
+ async storeExtractionResult(filePath, content, language, stats, result, onYield) {
2110
+ // Bulk inserts run in bounded sub-transactions with a yield between, so a
2111
+ // giant generated file (tens of thousands of symbols) can't block the
2112
+ // event loop — and the #850 watchdog heartbeat — for the whole store.
2113
+ // The file was NEVER one atomic transaction (each insert call has its
2114
+ // own), and the files-table record still lands last, so crash recovery
2115
+ // is unchanged: a partially-stored file has no record and re-indexes.
2116
+ const STORE_CHUNK = 2000;
1643
2117
  const contentHash = hashContent(content);
1644
2118
  // Check if file already exists and hasn't changed
1645
2119
  const existingFile = this.queries.getFileByPath(filePath);
@@ -1671,16 +2145,18 @@ class ExtractionOrchestrator {
1671
2145
  // This prevents FK violations when edges reference nodes that would
1672
2146
  // be silently skipped by insertNode() (see issue #42).
1673
2147
  const validNodes = result.nodes.filter((n) => n.id && n.kind && n.name && n.filePath && n.language);
1674
- // Insert nodes
1675
- if (validNodes.length > 0) {
1676
- this.queries.insertNodes(validNodes);
2148
+ // Insert nodes (chunked — see STORE_CHUNK above)
2149
+ for (let i = 0; i < validNodes.length; i += STORE_CHUNK) {
2150
+ this.queries.insertNodes(validNodes.slice(i, i + STORE_CHUNK));
2151
+ await onYield?.();
1677
2152
  }
1678
2153
  // Filter edges to only reference nodes that were actually inserted
1679
2154
  if (result.edges.length > 0) {
1680
2155
  const insertedIds = new Set(validNodes.map((n) => n.id));
1681
2156
  const validEdges = result.edges.filter((e) => insertedIds.has(e.source) && insertedIds.has(e.target));
1682
- if (validEdges.length > 0) {
1683
- this.queries.insertEdges(validEdges);
2157
+ for (let i = 0; i < validEdges.length; i += STORE_CHUNK) {
2158
+ this.queries.insertEdges(validEdges.slice(i, i + STORE_CHUNK));
2159
+ await onYield?.();
1684
2160
  }
1685
2161
  }
1686
2162
  // Re-insert cross-file incoming edges snapshotted before the delete,
@@ -1688,25 +2164,43 @@ class ExtractionOrchestrator {
1688
2164
  // (filePath, kind, name). Node ids include the source line, so any line
1689
2165
  // shift in the callee file (e.g. a docstring-only edit above the symbol)
1690
2166
  // changes every target id and a naive re-insert by old id would drop them
1691
- // all. `insertEdges` still filters to endpoints that exist, so edges whose
1692
- // caller (source) was deleted, or whose callee (target) was renamed/removed
1693
- // during the re-index (no match in `newTargetIds`), are dropped. This
1694
- // closes the #899 edge-drop on `sync`.
2167
+ // all. `insertEdges` still filters to endpoints that exist. This closes
2168
+ // the #899 edge-drop on `sync`.
2169
+ //
2170
+ // Edges whose callee (target) was renamed/removed during the re-index (no
2171
+ // match in `newNodesByKindName`) are not silently dropped anymore: each is
2172
+ // resurrected as its ORIGINAL unresolved ref (stamped on the edge as
2173
+ // metadata.refName/refKind at creation) so the same sync's resolution
2174
+ // sweep can rebind it to an alternative definition elsewhere, or park it
2175
+ // as status='failed' to be retried when the symbol reappears — the
2176
+ // removal-side counterpart of #1240. Edges without refName (built before
2177
+ // the stamp existed, or synthesized) still drop silently: reconstructing
2178
+ // a ref from the target's plain name would strip receiver/qualifier
2179
+ // context and risk a rebind a full re-index would never make.
1695
2180
  if (crossFileIncomingEdges.length > 0) {
1696
2181
  const newNodesByKindName = new Map();
1697
2182
  for (const n of validNodes) {
1698
2183
  newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
1699
2184
  }
1700
2185
  const reinserted = [];
2186
+ const resurrected = [];
1701
2187
  for (const e of crossFileIncomingEdges) {
1702
2188
  const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
1703
2189
  if (newTargetId) {
1704
2190
  reinserted.push({ source: e.source, target: newTargetId, kind: e.kind, metadata: e.metadata, line: e.line, column: e.column, provenance: e.provenance });
1705
2191
  }
2192
+ else {
2193
+ const ref = resurrectRefFromDroppedEdge(e);
2194
+ if (ref)
2195
+ resurrected.push(ref);
2196
+ }
1706
2197
  }
1707
2198
  if (reinserted.length > 0) {
1708
2199
  this.queries.insertEdges(reinserted);
1709
2200
  }
2201
+ if (resurrected.length > 0) {
2202
+ this.queries.insertUnresolvedRefsBatch(resurrected);
2203
+ }
1710
2204
  }
1711
2205
  // Insert unresolved references in batch with denormalized filePath/language
1712
2206
  if (result.unresolvedReferences.length > 0) {
@@ -1718,8 +2212,9 @@ class ExtractionOrchestrator {
1718
2212
  filePath: ref.filePath ?? filePath,
1719
2213
  language: ref.language ?? language,
1720
2214
  }));
1721
- if (refsWithContext.length > 0) {
1722
- this.queries.insertUnresolvedRefsBatch(refsWithContext);
2215
+ for (let i = 0; i < refsWithContext.length; i += STORE_CHUNK) {
2216
+ this.queries.insertUnresolvedRefsBatch(refsWithContext.slice(i, i + STORE_CHUNK));
2217
+ await onYield?.();
1723
2218
  }
1724
2219
  }
1725
2220
  // Insert file record
@@ -1767,10 +2262,16 @@ class ExtractionOrchestrator {
1767
2262
  // whether or not the project uses git, and crucially also catches committed
1768
2263
  // changes from `git pull`/`checkout`/`merge`/`rebase` — which `git status`
1769
2264
  // cannot see, because the working tree is clean afterward.
2265
+ const tSyncScan = Date.now();
1770
2266
  const currentFiles = await scanDirectoryAsync(this.rootDir);
2267
+ if (process.env.CODEGRAPH_SYNTH_TIMINGS)
2268
+ console.error(`[phase-timing] sync-scan: ${Date.now() - tSyncScan}ms (${currentFiles.length} files)`);
1771
2269
  filesChecked = currentFiles.length;
1772
2270
  const currentSet = new Set(currentFiles);
2271
+ const tTracked = Date.now();
1773
2272
  const trackedFiles = this.queries.getAllFiles();
2273
+ if (process.env.CODEGRAPH_SYNTH_TIMINGS)
2274
+ console.error(`[phase-timing] sync-tracked-load: ${Date.now() - tTracked}ms (${trackedFiles.length} tracked)`);
1774
2275
  const trackedMap = new Map();
1775
2276
  for (const f of trackedFiles) {
1776
2277
  trackedMap.set(f.path, f);
@@ -1783,6 +2284,22 @@ class ExtractionOrchestrator {
1783
2284
  let reconcileChecks = 0;
1784
2285
  for (const tracked of trackedFiles) {
1785
2286
  if (!currentSet.has(tracked.path) || !fs.existsSync(path.join(this.rootDir, tracked.path))) {
2287
+ // Before the cascade deletes them, resurrect incoming cross-file
2288
+ // resolution edges as their original refs (#1240 removal case): the
2289
+ // callers live in files this sync will NOT revisit, so this is their
2290
+ // only chance to rebind to an alternative definition — or to park as
2291
+ // failed until the symbol reappears somewhere. (A deleted file whose
2292
+ // CALLERS are also being deleted is fine: their nodes cascade later
2293
+ // in this loop and take the resurrected rows with them.)
2294
+ const incoming = this.queries.getCrossFileIncomingEdgesWithTarget(tracked.path);
2295
+ if (incoming.length > 0) {
2296
+ const resurrected = incoming
2297
+ .map((e) => resurrectRefFromDroppedEdge(e))
2298
+ .filter((r) => r !== null);
2299
+ if (resurrected.length > 0) {
2300
+ this.queries.insertUnresolvedRefsBatch(resurrected);
2301
+ }
2302
+ }
1786
2303
  this.queries.deleteFile(tracked.path);
1787
2304
  filesRemoved++;
1788
2305
  }