gitnexus 1.6.6-rc.98 → 1.6.6

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 (541) hide show
  1. package/README.md +122 -68
  2. package/dist/_shared/graph/types.d.ts +17 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +3 -5
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/index.js +2 -4
  7. package/dist/_shared/index.js.map +1 -1
  8. package/dist/_shared/scope-resolution/module-scope-index.d.ts +1 -2
  9. package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
  10. package/dist/_shared/scope-resolution/module-scope-index.js +1 -2
  11. package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
  12. package/dist/_shared/scope-resolution/parsed-file.d.ts +24 -0
  13. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  14. package/dist/_shared/scope-resolution/reference-site.d.ts +13 -1
  15. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
  16. package/dist/_shared/scope-resolution/registries/context.d.ts +1 -0
  17. package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -1
  18. package/dist/_shared/scope-resolution/registries/context.js +6 -0
  19. package/dist/_shared/scope-resolution/registries/context.js.map +1 -1
  20. package/dist/_shared/scope-resolution/registries/evidence.d.ts +2 -3
  21. package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -1
  22. package/dist/_shared/scope-resolution/registries/evidence.js +2 -3
  23. package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -1
  24. package/dist/_shared/scope-resolution/registries/macro-registry.d.ts +29 -0
  25. package/dist/_shared/scope-resolution/registries/macro-registry.d.ts.map +1 -0
  26. package/dist/_shared/scope-resolution/registries/macro-registry.js +32 -0
  27. package/dist/_shared/scope-resolution/registries/macro-registry.js.map +1 -0
  28. package/dist/_shared/scope-resolution/symbol-definition.d.ts +9 -0
  29. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  30. package/dist/_shared/scope-resolution/types.d.ts +1 -1
  31. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  32. package/dist/cli/ai-context.d.ts +35 -1
  33. package/dist/cli/ai-context.js +111 -12
  34. package/dist/cli/analyze-config.d.ts +92 -0
  35. package/dist/cli/analyze-config.js +364 -0
  36. package/dist/cli/analyze.d.ts +17 -1
  37. package/dist/cli/analyze.js +224 -71
  38. package/dist/cli/cli-message.d.ts +1 -1
  39. package/dist/cli/doctor.d.ts +18 -0
  40. package/dist/cli/doctor.js +32 -0
  41. package/dist/cli/i18n/en.d.ts +2 -2
  42. package/dist/cli/i18n/en.js +2 -2
  43. package/dist/cli/i18n/resources.d.ts +2 -2
  44. package/dist/cli/i18n/zh-CN.js +2 -2
  45. package/dist/cli/index.js +4 -2
  46. package/dist/cli/resolve-invocation.d.ts +21 -0
  47. package/dist/cli/resolve-invocation.js +68 -0
  48. package/dist/cli/setup.d.ts +33 -0
  49. package/dist/cli/setup.js +96 -35
  50. package/dist/cli/skill-gen.js +2 -2
  51. package/dist/cli/wiki.js +20 -4
  52. package/dist/core/embeddings/embedder.d.ts +1 -1
  53. package/dist/core/embeddings/embedder.js +14 -1
  54. package/dist/core/embeddings/runtime-support.d.ts +41 -0
  55. package/dist/core/embeddings/runtime-support.js +65 -0
  56. package/dist/core/group/extractors/grpc-patterns/proto.js +2 -1
  57. package/dist/core/group/extractors/http-patterns/java.js +15 -1
  58. package/dist/core/ingestion/call-processor.d.ts +57 -227
  59. package/dist/core/ingestion/call-processor.js +172 -2595
  60. package/dist/core/ingestion/call-routing.d.ts +6 -5
  61. package/dist/core/ingestion/call-routing.js +6 -6
  62. package/dist/core/ingestion/call-types.d.ts +0 -75
  63. package/dist/core/ingestion/class-extractors/configs/c-cpp.js +26 -1
  64. package/dist/core/ingestion/class-extractors/configs/ruby.js +3 -0
  65. package/dist/core/ingestion/class-extractors/generic.js +9 -11
  66. package/dist/core/ingestion/class-types.d.ts +23 -0
  67. package/dist/core/ingestion/cobol/cobol-preprocessor.d.ts +11 -0
  68. package/dist/core/ingestion/cobol/cobol-preprocessor.js +227 -73
  69. package/dist/core/ingestion/cobol-processor.d.ts +1 -0
  70. package/dist/core/ingestion/cobol-processor.js +51 -2
  71. package/dist/core/ingestion/emit-references.js +8 -2
  72. package/dist/core/ingestion/export-detection.d.ts +2 -1
  73. package/dist/core/ingestion/export-detection.js +2 -6
  74. package/dist/core/ingestion/field-extractors/configs/dart.d.ts +0 -6
  75. package/dist/core/ingestion/field-extractors/configs/dart.js +62 -44
  76. package/dist/core/ingestion/field-extractors/configs/go.d.ts +0 -9
  77. package/dist/core/ingestion/field-extractors/configs/go.js +40 -1
  78. package/dist/core/ingestion/field-extractors/configs/jvm.js +43 -4
  79. package/dist/core/ingestion/field-extractors/configs/php.js +1 -2
  80. package/dist/core/ingestion/field-extractors/configs/swift.d.ts +12 -1
  81. package/dist/core/ingestion/field-extractors/configs/swift.js +31 -6
  82. package/dist/core/ingestion/field-extractors/generic.d.ts +6 -0
  83. package/dist/core/ingestion/field-extractors/generic.js +5 -4
  84. package/dist/core/ingestion/field-extractors/typescript.js +0 -29
  85. package/dist/core/ingestion/filesystem-walker.d.ts +0 -9
  86. package/dist/core/ingestion/filesystem-walker.js +0 -11
  87. package/dist/core/ingestion/finalize-orchestrator.js +14 -2
  88. package/dist/core/ingestion/import-resolvers/configs/swift.d.ts +21 -0
  89. package/dist/core/ingestion/import-resolvers/configs/swift.js +72 -11
  90. package/dist/core/ingestion/import-resolvers/python.d.ts +1 -1
  91. package/dist/core/ingestion/import-resolvers/standard.d.ts +2 -2
  92. package/dist/core/ingestion/import-resolvers/standard.js +6 -5
  93. package/dist/core/ingestion/import-resolvers/utils.d.ts +2 -5
  94. package/dist/core/ingestion/import-resolvers/utils.js +1 -9
  95. package/dist/core/ingestion/language-provider.d.ts +39 -119
  96. package/dist/core/ingestion/language-provider.js +0 -2
  97. package/dist/core/ingestion/languages/c/capture-side-channel.d.ts +62 -0
  98. package/dist/core/ingestion/languages/c/capture-side-channel.js +65 -0
  99. package/dist/core/ingestion/languages/c/captures.js +41 -23
  100. package/dist/core/ingestion/languages/c/import-decomposer.d.ts +9 -0
  101. package/dist/core/ingestion/languages/c/import-decomposer.js +15 -0
  102. package/dist/core/ingestion/languages/c/import-target.js +52 -9
  103. package/dist/core/ingestion/languages/c/index.d.ts +2 -1
  104. package/dist/core/ingestion/languages/c/index.js +2 -1
  105. package/dist/core/ingestion/languages/c/query.js +15 -0
  106. package/dist/core/ingestion/languages/c/scope-resolver.js +47 -4
  107. package/dist/core/ingestion/languages/c/static-linkage.d.ts +8 -0
  108. package/dist/core/ingestion/languages/c/static-linkage.js +46 -1
  109. package/dist/core/ingestion/languages/c-cpp.js +16 -7
  110. package/dist/core/ingestion/languages/cobol/captures.js +25 -2
  111. package/dist/core/ingestion/languages/cpp/adl.d.ts +88 -4
  112. package/dist/core/ingestion/languages/cpp/adl.js +380 -160
  113. package/dist/core/ingestion/languages/cpp/arity-metadata.js +2 -8
  114. package/dist/core/ingestion/languages/cpp/capture-side-channel.d.ts +65 -0
  115. package/dist/core/ingestion/languages/cpp/capture-side-channel.js +78 -0
  116. package/dist/core/ingestion/languages/cpp/captures.js +162 -54
  117. package/dist/core/ingestion/languages/cpp/file-local-linkage.d.ts +18 -52
  118. package/dist/core/ingestion/languages/cpp/file-local-linkage.js +49 -1
  119. package/dist/core/ingestion/languages/cpp/import-decomposer.d.ts +7 -0
  120. package/dist/core/ingestion/languages/cpp/import-decomposer.js +12 -0
  121. package/dist/core/ingestion/languages/cpp/index.d.ts +1 -0
  122. package/dist/core/ingestion/languages/cpp/index.js +1 -0
  123. package/dist/core/ingestion/languages/cpp/inline-namespaces.d.ts +8 -0
  124. package/dist/core/ingestion/languages/cpp/inline-namespaces.js +21 -0
  125. package/dist/core/ingestion/languages/cpp/query.js +21 -0
  126. package/dist/core/ingestion/languages/cpp/range-bindings.js +20 -5
  127. package/dist/core/ingestion/languages/cpp/scope-resolver.js +52 -5
  128. package/dist/core/ingestion/languages/cpp/two-phase-lookup.d.ts +20 -0
  129. package/dist/core/ingestion/languages/cpp/two-phase-lookup.js +83 -3
  130. package/dist/core/ingestion/languages/csharp/captures.js +205 -41
  131. package/dist/core/ingestion/languages/csharp/index.d.ts +3 -4
  132. package/dist/core/ingestion/languages/csharp/index.js +3 -4
  133. package/dist/core/ingestion/languages/csharp/interpret.js +17 -3
  134. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +17 -7
  135. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +129 -124
  136. package/dist/core/ingestion/languages/csharp/qualified-type-names.d.ts +14 -0
  137. package/dist/core/ingestion/languages/csharp/qualified-type-names.js +59 -0
  138. package/dist/core/ingestion/languages/csharp/query.js +13 -0
  139. package/dist/core/ingestion/languages/csharp/receiver-binding.js +10 -6
  140. package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -1
  141. package/dist/core/ingestion/languages/csharp.js +0 -5
  142. package/dist/core/ingestion/languages/dart/arity-metadata.d.ts +25 -0
  143. package/dist/core/ingestion/languages/dart/arity-metadata.js +35 -0
  144. package/dist/core/ingestion/languages/dart/arity.d.ts +19 -0
  145. package/dist/core/ingestion/languages/dart/arity.js +31 -0
  146. package/dist/core/ingestion/languages/dart/built-ins.d.ts +11 -0
  147. package/dist/core/ingestion/languages/dart/built-ins.js +34 -0
  148. package/dist/core/ingestion/languages/dart/cache-stats.d.ts +13 -0
  149. package/dist/core/ingestion/languages/dart/cache-stats.js +24 -0
  150. package/dist/core/ingestion/languages/dart/captures.d.ts +29 -0
  151. package/dist/core/ingestion/languages/dart/captures.js +445 -0
  152. package/dist/core/ingestion/languages/dart/expand-wildcards.d.ts +13 -0
  153. package/dist/core/ingestion/languages/dart/expand-wildcards.js +31 -0
  154. package/dist/core/ingestion/languages/dart/import-target.d.ts +15 -0
  155. package/dist/core/ingestion/languages/dart/import-target.js +65 -0
  156. package/dist/core/ingestion/languages/dart/index.d.ts +28 -0
  157. package/dist/core/ingestion/languages/dart/index.js +28 -0
  158. package/dist/core/ingestion/languages/dart/interpret.d.ts +24 -0
  159. package/dist/core/ingestion/languages/dart/interpret.js +95 -0
  160. package/dist/core/ingestion/languages/dart/merge-bindings.d.ts +10 -0
  161. package/dist/core/ingestion/languages/dart/merge-bindings.js +39 -0
  162. package/dist/core/ingestion/languages/dart/query.d.ts +26 -0
  163. package/dist/core/ingestion/languages/dart/query.js +142 -0
  164. package/dist/core/ingestion/languages/dart/receiver-binding.d.ts +24 -0
  165. package/dist/core/ingestion/languages/dart/receiver-binding.js +87 -0
  166. package/dist/core/ingestion/languages/dart/scope-resolver.d.ts +29 -0
  167. package/dist/core/ingestion/languages/dart/scope-resolver.js +194 -0
  168. package/dist/core/ingestion/languages/dart/signature-bindings.d.ts +23 -0
  169. package/dist/core/ingestion/languages/dart/signature-bindings.js +51 -0
  170. package/dist/core/ingestion/languages/dart/simple-hooks.d.ts +20 -0
  171. package/dist/core/ingestion/languages/dart/simple-hooks.js +47 -0
  172. package/dist/core/ingestion/languages/dart.d.ts +0 -1
  173. package/dist/core/ingestion/languages/dart.js +15 -29
  174. package/dist/core/ingestion/languages/go/arity-metadata.d.ts +1 -0
  175. package/dist/core/ingestion/languages/go/arity-metadata.js +31 -1
  176. package/dist/core/ingestion/languages/go/captures.js +247 -26
  177. package/dist/core/ingestion/languages/go/interface-impls.js +476 -50
  178. package/dist/core/ingestion/languages/go/interpret.js +21 -11
  179. package/dist/core/ingestion/languages/go/method-owners.js +20 -8
  180. package/dist/core/ingestion/languages/go/query.js +15 -7
  181. package/dist/core/ingestion/languages/go/range-binding.js +24 -9
  182. package/dist/core/ingestion/languages/go/receiver-binding.js +1 -1
  183. package/dist/core/ingestion/languages/go/scope-resolver.js +2 -2
  184. package/dist/core/ingestion/languages/go/simple-hooks.js +10 -5
  185. package/dist/core/ingestion/languages/go/type-binding.d.ts +1 -0
  186. package/dist/core/ingestion/languages/go/type-binding.js +40 -2
  187. package/dist/core/ingestion/languages/go.d.ts +0 -1
  188. package/dist/core/ingestion/languages/go.js +0 -5
  189. package/dist/core/ingestion/languages/index.d.ts +0 -5
  190. package/dist/core/ingestion/languages/index.js +0 -3
  191. package/dist/core/ingestion/languages/java/captures.js +252 -23
  192. package/dist/core/ingestion/languages/java/interpret.js +12 -6
  193. package/dist/core/ingestion/languages/java/package-siblings.js +18 -5
  194. package/dist/core/ingestion/languages/java/query.js +16 -1
  195. package/dist/core/ingestion/languages/java/scope-resolver.d.ts +2 -8
  196. package/dist/core/ingestion/languages/java/scope-resolver.js +2 -8
  197. package/dist/core/ingestion/languages/java.js +0 -5
  198. package/dist/core/ingestion/languages/javascript/captures.js +185 -11
  199. package/dist/core/ingestion/languages/javascript/query.js +107 -1
  200. package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +57 -0
  201. package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +60 -0
  202. package/dist/core/ingestion/languages/kotlin/captures.js +213 -22
  203. package/dist/core/ingestion/languages/kotlin/companion-scopes.d.ts +7 -0
  204. package/dist/core/ingestion/languages/kotlin/companion-scopes.js +10 -0
  205. package/dist/core/ingestion/languages/kotlin/index.d.ts +1 -0
  206. package/dist/core/ingestion/languages/kotlin/index.js +1 -0
  207. package/dist/core/ingestion/languages/kotlin/query.js +30 -0
  208. package/dist/core/ingestion/languages/kotlin/scope-resolver.d.ts +5 -7
  209. package/dist/core/ingestion/languages/kotlin/scope-resolver.js +22 -7
  210. package/dist/core/ingestion/languages/kotlin.js +8 -5
  211. package/dist/core/ingestion/languages/php/captures.js +135 -19
  212. package/dist/core/ingestion/languages/php/import-decomposer.js +21 -63
  213. package/dist/core/ingestion/languages/php/index.d.ts +3 -4
  214. package/dist/core/ingestion/languages/php/index.js +3 -4
  215. package/dist/core/ingestion/languages/php/query.js +5 -0
  216. package/dist/core/ingestion/languages/php/receiver-binding.js +8 -4
  217. package/dist/core/ingestion/languages/php.js +0 -4
  218. package/dist/core/ingestion/languages/python/captures.js +128 -13
  219. package/dist/core/ingestion/languages/python/depends-references.js +1 -1
  220. package/dist/core/ingestion/languages/python/import-target.d.ts +4 -5
  221. package/dist/core/ingestion/languages/python/import-target.js +108 -16
  222. package/dist/core/ingestion/languages/python/index-stats.d.ts +18 -0
  223. package/dist/core/ingestion/languages/python/index-stats.js +25 -0
  224. package/dist/core/ingestion/languages/python/index.d.ts +3 -4
  225. package/dist/core/ingestion/languages/python/index.js +3 -4
  226. package/dist/core/ingestion/languages/python/query.js +17 -0
  227. package/dist/core/ingestion/languages/python/scope-resolver.js +7 -6
  228. package/dist/core/ingestion/languages/python.d.ts +0 -2
  229. package/dist/core/ingestion/languages/python.js +0 -7
  230. package/dist/core/ingestion/languages/ruby/captures.js +174 -31
  231. package/dist/core/ingestion/languages/ruby/import-target.js +2 -1
  232. package/dist/core/ingestion/languages/ruby/interpret.js +2 -1
  233. package/dist/core/ingestion/languages/ruby/query.js +10 -0
  234. package/dist/core/ingestion/languages/ruby/scope-resolver.js +61 -11
  235. package/dist/core/ingestion/languages/ruby.js +4 -53
  236. package/dist/core/ingestion/languages/rust/captures.js +95 -13
  237. package/dist/core/ingestion/languages/rust/query.js +64 -1
  238. package/dist/core/ingestion/languages/rust/range-binding.js +40 -12
  239. package/dist/core/ingestion/languages/rust/receiver-binding.js +9 -0
  240. package/dist/core/ingestion/languages/rust/scope-resolver.js +75 -1
  241. package/dist/core/ingestion/languages/rust.d.ts +0 -2
  242. package/dist/core/ingestion/languages/rust.js +0 -6
  243. package/dist/core/ingestion/languages/swift/arity-metadata.d.ts +24 -0
  244. package/dist/core/ingestion/languages/swift/arity-metadata.js +41 -0
  245. package/dist/core/ingestion/languages/swift/arity.d.ts +29 -0
  246. package/dist/core/ingestion/languages/swift/arity.js +42 -0
  247. package/dist/core/ingestion/languages/swift/base-type.d.ts +20 -0
  248. package/dist/core/ingestion/languages/swift/base-type.js +27 -0
  249. package/dist/core/ingestion/languages/swift/cache-stats.d.ts +15 -0
  250. package/dist/core/ingestion/languages/swift/cache-stats.js +26 -0
  251. package/dist/core/ingestion/languages/swift/captures.d.ts +35 -0
  252. package/dist/core/ingestion/languages/swift/captures.js +495 -0
  253. package/dist/core/ingestion/languages/swift/implicit-imports.d.ts +34 -0
  254. package/dist/core/ingestion/languages/swift/implicit-imports.js +57 -0
  255. package/dist/core/ingestion/languages/swift/import-decomposer.d.ts +24 -0
  256. package/dist/core/ingestion/languages/swift/import-decomposer.js +84 -0
  257. package/dist/core/ingestion/languages/swift/import-target.d.ts +33 -0
  258. package/dist/core/ingestion/languages/swift/import-target.js +81 -0
  259. package/dist/core/ingestion/languages/swift/index.d.ts +37 -0
  260. package/dist/core/ingestion/languages/swift/index.js +37 -0
  261. package/dist/core/ingestion/languages/swift/interpret.d.ts +15 -0
  262. package/dist/core/ingestion/languages/swift/interpret.js +89 -0
  263. package/dist/core/ingestion/languages/swift/merge-bindings.d.ts +20 -0
  264. package/dist/core/ingestion/languages/swift/merge-bindings.js +48 -0
  265. package/dist/core/ingestion/languages/swift/query.d.ts +46 -0
  266. package/dist/core/ingestion/languages/swift/query.js +194 -0
  267. package/dist/core/ingestion/languages/swift/receiver-binding.d.ts +40 -0
  268. package/dist/core/ingestion/languages/swift/receiver-binding.js +161 -0
  269. package/dist/core/ingestion/languages/swift/scope-resolver.d.ts +50 -0
  270. package/dist/core/ingestion/languages/swift/scope-resolver.js +189 -0
  271. package/dist/core/ingestion/languages/swift/sibling-type-bindings.d.ts +34 -0
  272. package/dist/core/ingestion/languages/swift/sibling-type-bindings.js +65 -0
  273. package/dist/core/ingestion/languages/swift/signature-bindings.d.ts +25 -0
  274. package/dist/core/ingestion/languages/swift/signature-bindings.js +66 -0
  275. package/dist/core/ingestion/languages/swift/simple-hooks.d.ts +37 -0
  276. package/dist/core/ingestion/languages/swift/simple-hooks.js +60 -0
  277. package/dist/core/ingestion/languages/swift/target-grouping.d.ts +51 -0
  278. package/dist/core/ingestion/languages/swift/target-grouping.js +93 -0
  279. package/dist/core/ingestion/languages/swift/target-siblings.d.ts +30 -0
  280. package/dist/core/ingestion/languages/swift/target-siblings.js +71 -0
  281. package/dist/core/ingestion/languages/swift.d.ts +0 -5
  282. package/dist/core/ingestion/languages/swift.js +11 -87
  283. package/dist/core/ingestion/languages/typescript/array-callback.d.ts +3 -29
  284. package/dist/core/ingestion/languages/typescript/array-callback.js +4 -43
  285. package/dist/core/ingestion/languages/typescript/captures.js +147 -17
  286. package/dist/core/ingestion/languages/typescript/index.d.ts +3 -4
  287. package/dist/core/ingestion/languages/typescript/index.js +3 -4
  288. package/dist/core/ingestion/languages/typescript/query.d.ts +2 -3
  289. package/dist/core/ingestion/languages/typescript/query.js +125 -4
  290. package/dist/core/ingestion/languages/typescript/receiver-binding.js +0 -1
  291. package/dist/core/ingestion/languages/typescript.d.ts +2 -3
  292. package/dist/core/ingestion/languages/typescript.js +46 -23
  293. package/dist/core/ingestion/languages/vue/captures.d.ts +46 -0
  294. package/dist/core/ingestion/languages/vue/captures.js +67 -0
  295. package/dist/core/ingestion/languages/vue/import-target.d.ts +27 -0
  296. package/dist/core/ingestion/languages/vue/import-target.js +58 -0
  297. package/dist/core/ingestion/languages/vue/index.d.ts +49 -0
  298. package/dist/core/ingestion/languages/vue/index.js +49 -0
  299. package/dist/core/ingestion/languages/vue/scope-resolver.d.ts +55 -0
  300. package/dist/core/ingestion/languages/vue/scope-resolver.js +292 -0
  301. package/dist/core/ingestion/languages/vue.js +12 -4
  302. package/dist/core/ingestion/local-symbol-pruner.d.ts +11 -0
  303. package/dist/core/ingestion/local-symbol-pruner.js +63 -0
  304. package/dist/core/ingestion/method-extractors/configs/dart.js +14 -1
  305. package/dist/core/ingestion/method-extractors/configs/jvm.js +37 -20
  306. package/dist/core/ingestion/method-extractors/configs/php.js +0 -1
  307. package/dist/core/ingestion/method-extractors/configs/swift.js +11 -4
  308. package/dist/core/ingestion/method-types.d.ts +2 -2
  309. package/dist/core/ingestion/model/index.d.ts +0 -3
  310. package/dist/core/ingestion/model/index.js +0 -8
  311. package/dist/core/ingestion/model/resolve.d.ts +7 -43
  312. package/dist/core/ingestion/model/resolve.js +7 -226
  313. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +46 -1
  314. package/dist/core/ingestion/model/semantic-model.d.ts +4 -6
  315. package/dist/core/ingestion/model/semantic-model.js +1 -1
  316. package/dist/core/ingestion/model/symbol-table.d.ts +3 -4
  317. package/dist/core/ingestion/model/symbol-table.js +3 -4
  318. package/dist/core/ingestion/parsing-processor.d.ts +30 -24
  319. package/dist/core/ingestion/parsing-processor.js +76 -640
  320. package/dist/core/ingestion/pipeline-phases/communities.d.ts +1 -1
  321. package/dist/core/ingestion/pipeline-phases/communities.js +5 -2
  322. package/dist/core/ingestion/pipeline-phases/cross-file.d.ts +9 -8
  323. package/dist/core/ingestion/pipeline-phases/cross-file.js +16 -12
  324. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  325. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  326. package/dist/core/ingestion/pipeline-phases/mro.d.ts +1 -1
  327. package/dist/core/ingestion/pipeline-phases/mro.js +2 -2
  328. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +44 -22
  329. package/dist/core/ingestion/pipeline-phases/parse-impl.js +426 -565
  330. package/dist/core/ingestion/pipeline-phases/parse.d.ts +13 -33
  331. package/dist/core/ingestion/pipeline-phases/parse.js +2 -2
  332. package/dist/core/ingestion/pipeline-phases/processes.d.ts +1 -1
  333. package/dist/core/ingestion/pipeline-phases/processes.js +5 -3
  334. package/dist/core/ingestion/pipeline-phases/prune-local-symbols.d.ts +14 -0
  335. package/dist/core/ingestion/pipeline-phases/prune-local-symbols.js +26 -0
  336. package/dist/core/ingestion/pipeline.d.ts +27 -19
  337. package/dist/core/ingestion/pipeline.js +4 -2
  338. package/dist/core/ingestion/resolve-references.js +15 -3
  339. package/dist/core/ingestion/route-extractors/laravel.d.ts +10 -0
  340. package/dist/core/ingestion/route-extractors/laravel.js +88 -9
  341. package/dist/core/ingestion/scope-extractor-bridge.d.ts +2 -1
  342. package/dist/core/ingestion/scope-extractor-bridge.js +2 -2
  343. package/dist/core/ingestion/scope-extractor.js +13 -0
  344. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +181 -15
  345. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +21 -10
  346. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +10 -2
  347. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +7 -1
  348. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +58 -6
  349. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +24 -5
  350. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +68 -1
  351. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +189 -29
  352. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +21 -1
  353. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +1 -1
  354. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +8 -11
  355. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +171 -50
  356. package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +3 -4
  357. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +9 -4
  358. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +28 -5
  359. package/dist/core/ingestion/scope-resolution/pipeline/run.js +191 -24
  360. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +31 -0
  361. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +72 -0
  362. package/dist/core/ingestion/scope-resolution/scope/walkers.js +440 -39
  363. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +18 -2
  364. package/dist/core/ingestion/scope-resolution/workspace-index.js +94 -7
  365. package/dist/core/ingestion/tree-sitter-queries.d.ts +14 -14
  366. package/dist/core/ingestion/tree-sitter-queries.js +380 -158
  367. package/dist/core/ingestion/ts-js-hoc-utils.d.ts +12 -0
  368. package/dist/core/ingestion/ts-js-hoc-utils.js +105 -0
  369. package/dist/core/ingestion/type-env.d.ts +2 -1
  370. package/dist/core/ingestion/type-env.js +6 -8
  371. package/dist/core/ingestion/type-extractors/c-cpp.js +8 -10
  372. package/dist/core/ingestion/type-extractors/csharp.js +18 -34
  373. package/dist/core/ingestion/type-extractors/go.js +2 -10
  374. package/dist/core/ingestion/type-extractors/jvm.js +6 -5
  375. package/dist/core/ingestion/type-extractors/php.js +1 -1
  376. package/dist/core/ingestion/type-extractors/python.js +1 -1
  377. package/dist/core/ingestion/type-extractors/ruby.js +2 -2
  378. package/dist/core/ingestion/type-extractors/rust.js +0 -16
  379. package/dist/core/ingestion/type-extractors/shared.d.ts +2 -2
  380. package/dist/core/ingestion/type-extractors/shared.js +25 -11
  381. package/dist/core/ingestion/type-extractors/swift.js +5 -5
  382. package/dist/core/ingestion/type-extractors/types.d.ts +1 -1
  383. package/dist/core/ingestion/type-extractors/typescript.js +1 -1
  384. package/dist/core/ingestion/utils/ast-helpers.d.ts +119 -1
  385. package/dist/core/ingestion/utils/ast-helpers.js +260 -13
  386. package/dist/core/ingestion/utils/callable-labels.d.ts +13 -0
  387. package/dist/core/ingestion/utils/callable-labels.js +14 -0
  388. package/dist/core/ingestion/utils/deferred-resolution-profile.d.ts +10 -0
  389. package/dist/core/ingestion/utils/deferred-resolution-profile.js +30 -0
  390. package/dist/core/ingestion/utils/heap-probe.d.ts +20 -0
  391. package/dist/core/ingestion/utils/heap-probe.js +39 -0
  392. package/dist/core/ingestion/utils/heritage-marker.d.ts +33 -0
  393. package/dist/core/ingestion/utils/heritage-marker.js +41 -0
  394. package/dist/core/ingestion/utils/qualified-name.d.ts +37 -0
  395. package/dist/core/ingestion/utils/qualified-name.js +67 -0
  396. package/dist/core/ingestion/variable-extractors/configs/c-cpp.js +49 -0
  397. package/dist/core/ingestion/variable-extractors/configs/dart.js +76 -62
  398. package/dist/core/ingestion/variable-extractors/configs/go.js +74 -7
  399. package/dist/core/ingestion/variable-extractors/configs/jvm.js +35 -8
  400. package/dist/core/ingestion/variable-extractors/generic.js +40 -12
  401. package/dist/core/ingestion/variable-types.d.ts +9 -0
  402. package/dist/core/ingestion/vue-sfc-extractor.d.ts +97 -7
  403. package/dist/core/ingestion/vue-sfc-extractor.js +433 -11
  404. package/dist/core/ingestion/workers/parse-worker.d.ts +0 -11
  405. package/dist/core/ingestion/workers/parse-worker.js +358 -187
  406. package/dist/core/ingestion/workers/worker-pool.d.ts +78 -9
  407. package/dist/core/ingestion/workers/worker-pool.js +346 -49
  408. package/dist/core/lbug/csv-generator.js +20 -2
  409. package/dist/core/lbug/lbug-adapter.js +29 -10
  410. package/dist/core/lbug/native-check.js +17 -5
  411. package/dist/core/lbug/pool-adapter.d.ts +0 -19
  412. package/dist/core/lbug/pool-adapter.js +40 -77
  413. package/dist/core/lbug/query-result-utils.d.ts +19 -0
  414. package/dist/core/lbug/query-result-utils.js +28 -0
  415. package/dist/core/run-analyze.d.ts +9 -2
  416. package/dist/core/run-analyze.js +24 -3
  417. package/dist/core/tree-sitter/parser-loader.d.ts +13 -0
  418. package/dist/core/tree-sitter/parser-loader.js +4 -0
  419. package/dist/core/tree-sitter/safe-parse.d.ts +70 -3
  420. package/dist/core/tree-sitter/safe-parse.js +214 -11
  421. package/dist/core/wiki/generator.js +13 -5
  422. package/dist/core/wiki/graph-queries.d.ts +3 -0
  423. package/dist/core/wiki/graph-queries.js +9 -1
  424. package/dist/core/wiki/llm-client.d.ts +1 -1
  425. package/dist/core/wiki/llm-client.js +7 -2
  426. package/dist/core/wiki/local-cli-client.d.ts +2 -1
  427. package/dist/core/wiki/local-cli-client.js +62 -4
  428. package/dist/mcp/compatible-stdio-transport.d.ts +3 -0
  429. package/dist/mcp/compatible-stdio-transport.js +22 -0
  430. package/dist/mcp/core/embedder.d.ts +1 -1
  431. package/dist/mcp/core/embedder.js +13 -1
  432. package/dist/mcp/local/local-backend.d.ts +37 -5
  433. package/dist/mcp/local/local-backend.js +227 -129
  434. package/dist/mcp/resources.js +3 -3
  435. package/dist/mcp/server.js +9 -1
  436. package/dist/storage/git.d.ts +11 -0
  437. package/dist/storage/git.js +30 -0
  438. package/dist/storage/parse-cache.d.ts +23 -1
  439. package/dist/storage/parse-cache.js +138 -32
  440. package/dist/storage/parsedfile-store.d.ts +142 -0
  441. package/dist/storage/parsedfile-store.js +367 -0
  442. package/dist/storage/repo-manager.d.ts +2 -1
  443. package/dist/storage/scope-index-store.d.ts +114 -0
  444. package/dist/storage/scope-index-store.js +236 -0
  445. package/dist/types/pipeline.d.ts +6 -3
  446. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +2 -1
  447. package/hooks/claude/gitnexus-hook.cjs +2 -1
  448. package/hooks/claude/resolve-analyze-cmd.cjs +323 -0
  449. package/package.json +4 -5
  450. package/scripts/bench-scope-resolution.ts +1 -3
  451. package/scripts/cross-platform-tests.ts +4 -1
  452. package/skills/gitnexus-cli.md +8 -6
  453. package/skills/gitnexus-debugging.md +15 -15
  454. package/skills/gitnexus-exploring.md +11 -11
  455. package/skills/gitnexus-guide.md +1 -1
  456. package/skills/gitnexus-impact-analysis.md +10 -10
  457. package/skills/gitnexus-pr-review.md +19 -19
  458. package/skills/gitnexus-refactoring.md +25 -25
  459. package/web/assets/{agent-BRX-afm3.js → agent-EkX7webX.js} +156 -131
  460. package/web/assets/{architectureDiagram-UL44E2DR-B1oYB8-c.js → architectureDiagram-UL44E2DR-BoczH8cv.js} +1 -1
  461. package/web/assets/{chunk-LCXTWHL2-SPC6pgWI.js → chunk-LCXTWHL2-By5HB3b-.js} +1 -1
  462. package/web/assets/{chunk-RG4AUYOV-DZXr4Yv3.js → chunk-RG4AUYOV-BQE_DkpL.js} +1 -1
  463. package/web/assets/{classDiagram-KGZ6W3CR-BqpOj8L9.js → classDiagram-KGZ6W3CR-Dj_02GUH.js} +1 -1
  464. package/web/assets/{classDiagram-v2-72OJOZXJ-FmElywK2.js → classDiagram-v2-72OJOZXJ-D-NArMpf.js} +1 -1
  465. package/web/assets/{diagram-3NCE3AQN-BWtJXMDq.js → diagram-3NCE3AQN-VXtXDO95.js} +1 -1
  466. package/web/assets/{diagram-GF46GFSD-Hh-fChxU.js → diagram-GF46GFSD-B1qab2Ah.js} +1 -1
  467. package/web/assets/{diagram-QXG6HAR7-DYeQHxGU.js → diagram-QXG6HAR7-BSCHGzzT.js} +1 -1
  468. package/web/assets/{diagram-WEQXMOUZ-DYGU4T0y.js → diagram-WEQXMOUZ-DHtknmNT.js} +1 -1
  469. package/web/assets/{erDiagram-L5TCEMPS-bN3wlP2A.js → erDiagram-L5TCEMPS-Db-IYTmm.js} +1 -1
  470. package/web/assets/{flowDiagram-H6V6AXG4-YACVtF3-.js → flowDiagram-H6V6AXG4-BHDoyVoW.js} +1 -1
  471. package/web/assets/{index-BhnAdWTt.js → index-BR0IhgbR.js} +10 -10
  472. package/web/assets/{infoDiagram-3YFTVSEB-BWBzD28-.js → infoDiagram-3YFTVSEB-6h8wbWEe.js} +1 -1
  473. package/web/assets/{ishikawaDiagram-BNXS4ZKH-B8qYT-j9.js → ishikawaDiagram-BNXS4ZKH-Dx2OkPk1.js} +1 -1
  474. package/web/assets/{kanban-definition-75IXJCU3-BeoPr5Tl.js → kanban-definition-75IXJCU3-CIIEZfqV.js} +1 -1
  475. package/web/assets/{mindmap-definition-2TDM6QVE-CdLLN_so.js → mindmap-definition-2TDM6QVE-DUAqydRq.js} +1 -1
  476. package/web/assets/{pieDiagram-CU6KROY3-DEd2a_k_.js → pieDiagram-CU6KROY3-BntYuYZa.js} +1 -1
  477. package/web/assets/{requirementDiagram-JXO7QTGE-QNeTo5C_.js → requirementDiagram-JXO7QTGE-QCD6I29n.js} +1 -1
  478. package/web/assets/{sequenceDiagram-VS2MUI6T-BHC7q7yJ.js → sequenceDiagram-VS2MUI6T-DScFwLz2.js} +1 -1
  479. package/web/assets/{stateDiagram-7D4R322I-KONnc2Px.js → stateDiagram-7D4R322I-AkRAm6Hl.js} +1 -1
  480. package/web/assets/{stateDiagram-v2-36443NZ5-C2QfuMlu.js → stateDiagram-v2-36443NZ5-BCK1vigR.js} +1 -1
  481. package/web/assets/{timeline-definition-O6YCAMPW-D4t4NJx1.js → timeline-definition-O6YCAMPW-BvpIVXjX.js} +1 -1
  482. package/web/assets/{vennDiagram-MWXL3ELB-6bctC9Yg.js → vennDiagram-MWXL3ELB-BYGWk6dK.js} +1 -1
  483. package/web/assets/{wardleyDiagram-CUQ6CDDI-BPZYNTvC.js → wardleyDiagram-CUQ6CDDI-Dk1z7H2T.js} +1 -1
  484. package/web/assets/{xychartDiagram-N2JHSOCM-lBI9O6DW.js → xychartDiagram-N2JHSOCM-2N6NCSWN.js} +1 -1
  485. package/web/index.html +1 -1
  486. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +0 -63
  487. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +0 -1
  488. package/dist/_shared/scope-resolution/shadow/aggregate.js +0 -122
  489. package/dist/_shared/scope-resolution/shadow/aggregate.js.map +0 -1
  490. package/dist/_shared/scope-resolution/shadow/diff.d.ts +0 -59
  491. package/dist/_shared/scope-resolution/shadow/diff.d.ts.map +0 -1
  492. package/dist/_shared/scope-resolution/shadow/diff.js +0 -79
  493. package/dist/_shared/scope-resolution/shadow/diff.js.map +0 -1
  494. package/dist/core/ingestion/ast-cache.d.ts +0 -26
  495. package/dist/core/ingestion/ast-cache.js +0 -48
  496. package/dist/core/ingestion/heritage-extractors/configs/go.d.ts +0 -13
  497. package/dist/core/ingestion/heritage-extractors/configs/go.js +0 -20
  498. package/dist/core/ingestion/heritage-extractors/configs/ruby.d.ts +0 -18
  499. package/dist/core/ingestion/heritage-extractors/configs/ruby.js +0 -65
  500. package/dist/core/ingestion/heritage-extractors/generic.d.ts +0 -23
  501. package/dist/core/ingestion/heritage-extractors/generic.js +0 -47
  502. package/dist/core/ingestion/heritage-processor.d.ts +0 -54
  503. package/dist/core/ingestion/heritage-processor.js +0 -367
  504. package/dist/core/ingestion/heritage-types.d.ts +0 -73
  505. package/dist/core/ingestion/heritage-types.js +0 -2
  506. package/dist/core/ingestion/import-processor.d.ts +0 -23
  507. package/dist/core/ingestion/import-processor.js +0 -377
  508. package/dist/core/ingestion/model/heritage-map.d.ts +0 -105
  509. package/dist/core/ingestion/model/heritage-map.js +0 -294
  510. package/dist/core/ingestion/model/resolution-context.d.ts +0 -93
  511. package/dist/core/ingestion/model/resolution-context.js +0 -337
  512. package/dist/core/ingestion/named-bindings/csharp.d.ts +0 -3
  513. package/dist/core/ingestion/named-bindings/csharp.js +0 -37
  514. package/dist/core/ingestion/named-bindings/java.d.ts +0 -3
  515. package/dist/core/ingestion/named-bindings/java.js +0 -29
  516. package/dist/core/ingestion/named-bindings/kotlin.d.ts +0 -3
  517. package/dist/core/ingestion/named-bindings/kotlin.js +0 -36
  518. package/dist/core/ingestion/named-bindings/php.d.ts +0 -3
  519. package/dist/core/ingestion/named-bindings/php.js +0 -61
  520. package/dist/core/ingestion/named-bindings/python.d.ts +0 -3
  521. package/dist/core/ingestion/named-bindings/python.js +0 -49
  522. package/dist/core/ingestion/named-bindings/rust.d.ts +0 -3
  523. package/dist/core/ingestion/named-bindings/rust.js +0 -66
  524. package/dist/core/ingestion/named-bindings/types.d.ts +0 -16
  525. package/dist/core/ingestion/named-bindings/types.js +0 -6
  526. package/dist/core/ingestion/named-bindings/typescript.d.ts +0 -3
  527. package/dist/core/ingestion/named-bindings/typescript.js +0 -58
  528. package/dist/core/ingestion/pipeline-phases/cross-file-impl.d.ts +0 -17
  529. package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +0 -209
  530. package/dist/core/ingestion/pipeline-phases/orm-extraction.d.ts +0 -22
  531. package/dist/core/ingestion/pipeline-phases/orm-extraction.js +0 -92
  532. package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.d.ts +0 -70
  533. package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.js +0 -312
  534. package/dist/core/ingestion/registry-primary-flag.d.ts +0 -89
  535. package/dist/core/ingestion/registry-primary-flag.js +0 -122
  536. package/dist/core/ingestion/shadow-harness.d.ts +0 -113
  537. package/dist/core/ingestion/shadow-harness.js +0 -148
  538. package/dist/core/ingestion/utils/ruby-self-call.d.ts +0 -52
  539. package/dist/core/ingestion/utils/ruby-self-call.js +0 -59
  540. package/scripts/ci-list-migrated-languages.ts +0 -24
  541. package/scripts/run-parity.ts +0 -128
@@ -2,9 +2,12 @@
2
2
  * Parse implementation — chunked parse + resolve loop.
3
3
  *
4
4
  * This is the core parsing engine of the ingestion pipeline. It reads
5
- * source files in byte-budget chunks (~20MB each), parses via worker
6
- * pool (or sequential fallback), resolves imports/calls/heritage per
7
- * chunk, and synthesizes wildcard import bindings.
5
+ * source files in byte-budget chunks (~20MB each), parses via the worker
6
+ * pool (the sole parse path — there is no sequential fallback), and emits
7
+ * route CALLS edges. Import,
8
+ * call, and inheritance resolution are owned by the scope-resolution
9
+ * phase, not here (RING4-1 #942 removed the legacy call DAG; RING4-2 #943
10
+ * removed the legacy per-file import resolution + wildcard synthesis).
8
11
  *
9
12
  * Consumed by the parse phase (`parse.ts`) — the phase file handles
10
13
  * dependency wiring while the heavy implementation lives here.
@@ -12,29 +15,22 @@
12
15
  * @module
13
16
  */
14
17
  import { BindingAccumulator, enrichExportedTypeMap, } from '../binding-accumulator.js';
15
- import { processParsing, mergeChunkResults } from '../parsing-processor.js';
16
- import { fileContentHash, computeChunkHash } from '../../../storage/parse-cache.js';
17
- import { processImports, processImportsFromExtracted, buildImportResolutionContext, } from '../import-processor.js';
18
- import { EMPTY_INDEX } from '../import-resolvers/utils.js';
19
- import { processCalls, processCallsFromExtracted, processAssignmentsFromExtracted, processRoutesFromExtracted, seedCrossFileReceiverTypes, buildExportedTypeMapFromGraph, } from '../call-processor.js';
20
- import { buildHeritageMap } from '../model/heritage-map.js';
21
- import { processHeritage, processHeritageFromExtracted, extractExtractedHeritageFromFiles, getHeritageStrategyForLanguage, } from '../heritage-processor.js';
22
- import { createResolutionContext } from '../model/resolution-context.js';
23
- import { createASTCache } from '../ast-cache.js';
18
+ import { mergeChunkResults, dispatchChunkParse } from '../parsing-processor.js';
19
+ import { fileContentHash, computeChunkHash, loadParseCacheChunk, persistParseCacheChunk, PARSE_CACHE_VERSION, } from '../../../storage/parse-cache.js';
20
+ import { clearParsedFileStore, persistParsedFileChunk, getDurableParsedFileDir, loadDurableParsedFileIndex, restoreDurableParsedFileShard, } from '../../../storage/parsedfile-store.js';
21
+ import { processRoutesFromExtracted, buildExportedTypeMapFromGraph, } from '../call-processor.js';
22
+ import { createSemanticModel } from '../model/index.js';
24
23
  import { getLanguageFromFilename } from '../../../_shared/index.js';
25
- import { isRegistryPrimary } from '../registry-primary-flag.js';
26
24
  import { readFileContents } from '../filesystem-walker.js';
27
25
  import { isLanguageAvailable } from '../../tree-sitter/parser-loader.js';
28
- import { createWorkerPool, WorkerPoolInitializationError } from '../workers/worker-pool.js';
29
- import { extractFetchCallsFromFiles } from '../call-processor.js';
26
+ import { createWorkerPool, workerPoolDisabledByEnv, resolveAutoPoolSize, WorkerPoolInitializationError, WorkerPoolDisabledError, } from '../workers/worker-pool.js';
30
27
  import fs from 'node:fs';
31
28
  import path from 'node:path';
32
29
  import { fileURLToPath, pathToFileURL } from 'node:url';
33
30
  import { isDev } from '../utils/env.js';
34
31
  import { isVerboseIngestionEnabled } from '../utils/verbose.js';
35
32
  import { endTimer, isDeferredResolutionProfileEnabled, logDeferredProfile, startTimer, } from '../utils/deferred-resolution-profile.js';
36
- import { synthesizeWildcardImportBindings, needsSynthesis } from './wildcard-synthesis.js';
37
- import { extractORMQueriesInline } from './orm-extraction.js';
33
+ import { isDebugHeapEnabled, logHeapProbe } from '../utils/heap-probe.js';
38
34
  import { logger } from '../../logger.js';
39
35
  // ── Constants ──────────────────────────────────────────────────────────────
40
36
  /** Max bytes of source content to load per parse chunk.
@@ -57,29 +53,107 @@ import { logger } from '../../logger.js';
57
53
  * import time and meant per-call option threading silently no-op'd.
58
54
  */
59
55
  const DEFAULT_CHUNK_BYTE_BUDGET = 2 * 1024 * 1024;
60
- function resolveChunkByteBudget(options) {
56
+ /**
57
+ * Per-worker share of a chunk's byte budget when auto-scaling (#worker-idle).
58
+ *
59
+ * A chunk is a single `WorkerPool.dispatch` unit; the pool fans a chunk's files
60
+ * into sub-batch jobs and assigns them to idle workers (`wakeIdleSlots`). When
61
+ * the chunk budget (2 MB) was far below the 8 MB sub-batch cap, every chunk
62
+ * produced exactly ONE job → ONE busy worker while the other N-1 sat idle. To
63
+ * keep all workers fed, the auto chunk budget now scales as
64
+ * `poolSize × CHUNK_BYTES_PER_WORKER`, so each dispatch carries enough work to
65
+ * fan across the whole pool. Sequential / explicit-budget runs are unaffected.
66
+ */
67
+ const CHUNK_BYTES_PER_WORKER = 2 * 1024 * 1024;
68
+ /**
69
+ * Target jobs-per-worker per dispatch. More jobs than workers gives the pool's
70
+ * idle-slot assignment room to load-balance (a slow job doesn't strand a worker
71
+ * while the rest finish early). Drives the derived `subBatchMaxBytes`.
72
+ */
73
+ const TARGET_JOBS_PER_WORKER = 3;
74
+ /** Floor for a derived sub-batch so jobs don't shrink to per-file IPC churn. */
75
+ const MIN_SUB_BATCH_BYTES = 256 * 1024;
76
+ function resolveChunkByteBudget(options, effectivePoolSize = 1) {
61
77
  const opt = options?.chunkByteBudget;
62
78
  if (typeof opt === 'number' && Number.isFinite(opt) && opt > 0)
63
79
  return opt;
64
80
  const env = Number(process.env.GITNEXUS_CHUNK_BYTE_BUDGET);
65
81
  if (Number.isFinite(env) && env > 0)
66
82
  return env;
67
- return DEFAULT_CHUNK_BYTE_BUDGET;
83
+ // Auto: size each chunk so a dispatch can fan across the whole pool. A
84
+ // single-worker (tiny-repo) run keeps the original 2 MB invalidation floor.
85
+ return Math.max(DEFAULT_CHUNK_BYTE_BUDGET, effectivePoolSize * CHUNK_BYTES_PER_WORKER);
86
+ }
87
+ /**
88
+ * Handle a worker-pool startup failure by FAILING FAST with the captured cause
89
+ * (#1741). The pool self-heals *transient* worker crashes on its own — a
90
+ * bounded, jittered startup restart loop (see worker-pool.ts) — so this is
91
+ * reached only when that self-heal is EXHAUSTED, or a deterministic crash-loop
92
+ * was detected, or the pool could not even be constructed. In every such case
93
+ * the workers genuinely cannot start.
94
+ *
95
+ * There is no sequential parser to silently degrade to — that fallback was
96
+ * removed (and it had masked a worker-startup regression as a 2-hour "stuck"
97
+ * run in #1741, rc99: a dropped `logger.warn` plus an unbounded sequential
98
+ * grind). GitNexus surfaces the real crash and aborts so the operator fixes the
99
+ * worker startup (commonly a missing build). The pool's own crash
100
+ * classification (`crashClass` on WorkerPoolInitializationError) sharpens the
101
+ * message.
102
+ *
103
+ * @throws always — an actionable Error carrying the captured worker crash.
104
+ * @internal Exported for unit tests; production callers are the parse loop's
105
+ * two worker-startup catch sites below.
106
+ */
107
+ export function handleWorkerStartupFailure(err) {
108
+ const isInit = err instanceof WorkerPoolInitializationError;
109
+ const readinessFailures = isInit ? err.readinessFailures : [];
110
+ const crashClass = isInit ? err.crashClass : undefined;
111
+ // Surface the real cause verbatim: readiness failures for an init crash, or
112
+ // the construction error message (e.g. "Worker script not found: …") when the
113
+ // pool never got to spawn workers.
114
+ const failureDetail = readinessFailures.length > 0
115
+ ? ` Underlying worker failure(s): ${readinessFailures.join(' | ')}`
116
+ : isInit
117
+ ? ''
118
+ : ` Underlying error: ${err.message}`;
119
+ // Always surface the real crash — never let a startup failure pass silently.
120
+ logger.error({ err: err.message, readinessFailures, crashClass }, 'Worker pool failed to start — workers could not start (bounded self-heal exhausted).');
121
+ const cause = crashClass === 'deterministic-startup'
122
+ ? `every worker crashed identically during startup (a deterministic ` +
123
+ `crash-loop — retrying cannot help), so the pool has no usable workers.`
124
+ : isInit
125
+ ? `workers exhausted the bounded startup retry budget without reporting ` +
126
+ `ready, so the pool has no usable workers.`
127
+ : `the worker pool could not be constructed.`;
128
+ // Class-aware fix hint: a missing/broken native binding is the likely cause
129
+ // when workers crashed during init, but it is the WRONG guess for a pool that
130
+ // never constructed (commonly a missing build / unresolvable worker path).
131
+ const fixHint = isInit
132
+ ? `Fix the worker startup failure shown above (often a missing/broken native ` +
133
+ `binding or a top-of-script import error in parse-worker).`
134
+ : `Fix the worker pool construction error shown above (commonly a missing ` +
135
+ `build, so dist/ has no parse-worker, or an unresolvable worker path).`;
136
+ throw new Error(`Worker pool failed to start: ${cause}${failureDetail}\n\n` +
137
+ `The worker pool is GitNexus's only parse path — there is no sequential ` +
138
+ `fallback to hide this crash behind (silently degrading masked a ` +
139
+ `worker-startup regression as a 2-hour "stuck" run in #1741). Fix:\n` +
140
+ ` • ${fixHint}`);
68
141
  }
69
142
  /**
70
143
  * Chunked parse + resolve loop.
71
144
  *
72
- * Reads source in byte-budget chunks (~20MB each). For each chunk:
73
- * 1. Parse via worker pool (or sequential fallback)
74
- * 2. Resolve imports from extracted data
75
- * 3. Synthesize wildcard import bindings (Go/Ruby/C++/Swift/Python)
76
- * 4. Resolve heritage + routes per chunk; defer worker CALLS until all chunks
77
- * have contributed heritage so interface-dispatch implementor map is complete
78
- * 5. Collect TypeEnv bindings for cross-file propagation
145
+ * Reads source in byte-budget chunks (~20MB each):
146
+ * 1. Parse each chunk via the worker pool (the sole parse path)
147
+ * 2. After all chunks parse, emit route CALLS edges (deferred so resolution
148
+ * sees the full repo graph) and collect the exported-type map
149
+ * 3. Collect TypeEnv bindings for cross-file propagation
150
+ *
151
+ * Import, call, and inheritance edges are emitted by the scope-resolution
152
+ * phase, not here (RING4-1 #942 / RING4-2 #943 removed the legacy passes).
79
153
  */
80
154
  export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, totalFiles, repoPath, pipelineStart, onProgress, options) {
81
- const ctx = createResolutionContext();
82
- const symbolTable = ctx.model.symbols;
155
+ const model = createSemanticModel();
156
+ const symbolTable = model.symbols;
83
157
  const parseableScanned = scannedFiles.filter((f) => {
84
158
  const lang = getLanguageFromFilename(f.path);
85
159
  return lang && isLanguageAvailable(lang);
@@ -104,6 +178,7 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
104
178
  // Linux-built cache misses on macOS for the same repo.
105
179
  parseableScanned.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
106
180
  const totalParseable = parseableScanned.length;
181
+ const totalBytes = parseableScanned.reduce((sum, f) => sum + f.size, 0);
107
182
  if (totalParseable === 0) {
108
183
  onProgress({
109
184
  phase: 'parsing',
@@ -115,6 +190,27 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
115
190
  stats: { filesProcessed: 0, totalFiles: 0, nodesCreated: graph.nodeCount },
116
191
  });
117
192
  }
193
+ // Sequential parsing has been removed: the worker pool (quarantine +
194
+ // respawn/recycle + circuit breaker) is the sole parse path. The three
195
+ // channels that used to select an in-process parser are now hard errors, so
196
+ // an operator who set one gets an actionable message instead of a silently
197
+ // slower (now nonexistent) fallback. Validated before any chunk work; a
198
+ // zero-parseable-file repo is exempt (nothing to parse).
199
+ if (totalParseable > 0) {
200
+ const requestedPoolSize = options?.workerPoolSize;
201
+ const disabledByEnv = requestedPoolSize === undefined && workerPoolDisabledByEnv();
202
+ if (options?.skipWorkers || requestedPoolSize === 0 || disabledByEnv) {
203
+ const reason = options?.skipWorkers
204
+ ? '`skipWorkers: true` was passed'
205
+ : requestedPoolSize === 0
206
+ ? '`--workers 0` (workerPoolSize=0) was requested'
207
+ : '`GITNEXUS_WORKER_POOL_SIZE=0` is set';
208
+ throw new WorkerPoolDisabledError(`Worker-pool parsing cannot be disabled (${reason}). GitNexus no longer ` +
209
+ `has a sequential parser — the worker pool self-heals via quarantine + ` +
210
+ `respawn, so there is no slower path to fall back to. Pass ` +
211
+ `\`--workers <N>\` with N>=1, or omit it for an auto-sized pool.`);
212
+ }
213
+ }
118
214
  // Build byte-budget chunks. The budget is resolved per-call (U14): options
119
215
  // first, then env, then the built-in default. Pre-U14 this was a
120
216
  // module-load IIFE constant, which froze the env value at import time
@@ -122,7 +218,28 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
122
218
  // runs. Resolving in the function body restores per-call configurability
123
219
  // and matches the pattern used by resolveAutoPoolSize and the U1
124
220
  // parseChunkConcurrency resolver.
125
- const chunkByteBudget = resolveChunkByteBudget(options);
221
+ // Effective worker count, computed up-front so the chunk budget can scale to
222
+ // keep the whole pool busy (#worker-idle). The pool is ALWAYS used (sequential
223
+ // parsing was removed; the disabled channels threw above). Size it to the
224
+ // work: an explicit `--workers <N>` pins the size; otherwise the cores-based
225
+ // auto size is capped by the repo's worth of work (~one worker per
226
+ // CHUNK_BYTES_PER_WORKER of source) so a tiny repo spawns ~1 worker instead of
227
+ // a full pool, replacing the job the deleted small-repo threshold used to do.
228
+ // KTD-3 of the remove-sequential plan; the cap formula is intentionally coarse
229
+ // (tuning deferred).
230
+ const explicitPoolSize = options?.workerPoolSize;
231
+ const workProportionalCap = Math.max(1, Math.ceil(totalBytes / CHUNK_BYTES_PER_WORKER));
232
+ const effectivePoolSize = explicitPoolSize && explicitPoolSize > 0
233
+ ? explicitPoolSize
234
+ : Math.min(resolveAutoPoolSize(), workProportionalCap);
235
+ const chunkByteBudget = resolveChunkByteBudget(options, effectivePoolSize);
236
+ // Sub-batch size so each chunk fans into ~`TARGET_JOBS_PER_WORKER` jobs per
237
+ // worker, giving the pool's idle-slot assignment room to load-balance. An
238
+ // explicit `GITNEXUS_WORKER_SUB_BATCH_MAX_BYTES` operator override wins.
239
+ const subBatchEnv = Number(process.env.GITNEXUS_WORKER_SUB_BATCH_MAX_BYTES);
240
+ const dispatchSubBatchMaxBytes = Number.isFinite(subBatchEnv) && subBatchEnv > 0
241
+ ? subBatchEnv
242
+ : Math.max(MIN_SUB_BATCH_BYTES, Math.ceil(chunkByteBudget / (effectivePoolSize * TARGET_JOBS_PER_WORKER)));
126
243
  const chunks = [];
127
244
  let currentChunk = [];
128
245
  let currentBytes = 0;
@@ -154,37 +271,25 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
154
271
  stats: { filesProcessed: 0, totalFiles: totalParseable, nodesCreated: graph.nodeCount },
155
272
  });
156
273
  }
157
- // Don't spawn workers for tiny repos overhead exceeds benefit.
158
- // Test suites may lower the thresholds via `options.workerThresholdsForTest`
159
- // to exercise the worker-pool path with small fixtures; see PipelineOptions.
160
- const MIN_FILES_FOR_WORKERS = options?.workerThresholdsForTest?.minFiles ?? 15;
161
- const MIN_BYTES_FOR_WORKERS = options?.workerThresholdsForTest?.minBytes ?? 512 * 1024;
162
- const totalBytes = parseableScanned.reduce((s, f) => s + f.size, 0);
163
- // Create worker pool lazily, reuse across cache-miss chunks.
274
+ // Create the worker pool lazily, reusing it across cache-miss chunks.
164
275
  //
165
- // `workerPoolSize === 0` is a programmatic equivalent of `skipWorkers:
166
- // true` per the `PipelineOptions.workerPoolSize` contract. Short-
167
- // circuiting here avoids constructing a useless pool. The pool is
168
- // intentionally NOT created before parse-cache lookup: a warm-cache
169
- // all-hit run should replay cached worker output without loading
170
- // parse-worker.js or any tree-sitter/N-API native bindings.
171
- const shouldUseWorkers = !options?.skipWorkers &&
172
- options?.workerPoolSize !== 0 &&
173
- (totalParseable >= MIN_FILES_FOR_WORKERS || totalBytes >= MIN_BYTES_FOR_WORKERS);
276
+ // KTD-8 the pool is intentionally NOT created before the parse-cache
277
+ // lookup: a warm-cache all-hit run must replay cached worker output without
278
+ // loading parse-worker.js or any tree-sitter/N-API native bindings. So
279
+ // `getOrCreateWorkerPool` is called only from inside the chunk loop, on the
280
+ // first cache MISS. There is no longer a "should we use workers?" gate:
281
+ // sequential parsing was removed and the disabled channels (`--workers 0` /
282
+ // env=0 / `skipWorkers`) threw above, so for any repo with parseable files
283
+ // the pool is always the parse path.
174
284
  let workerPool;
175
- let workerPoolDisabled = false;
176
285
  const getOrCreateWorkerPool = () => {
177
- if (!shouldUseWorkers || workerPoolDisabled)
178
- return undefined;
179
286
  if (workerPool)
180
287
  return workerPool;
181
288
  try {
182
- // U20.U3 test-only injection: integration tests pass a custom
183
- // worker script URL via `workerUrlForTest` (mirrors the
184
- // `workerThresholdsForTest` precedent) so they can drive the
185
- // chunk-loop with deterministically-misbehaving workers without
186
- // mocking the module import graph. When unset, the normal src/
187
- // → dist/ resolution runs.
289
+ // Test-only injection: integration tests pass a custom worker script URL
290
+ // via `workerUrlForTest` so they can drive the chunk-loop with
291
+ // deterministically-misbehaving workers without mocking the module import
292
+ // graph. When unset, the normal src/ → dist/ resolution runs.
188
293
  let workerUrl = options?.workerUrlForTest ?? new URL('../workers/parse-worker.js', import.meta.url);
189
294
  // When running under vitest, import.meta.url points to src/ where no .js exists.
190
295
  // Fall back to the compiled dist/ worker so the pool can spawn real worker threads.
@@ -195,44 +300,35 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
195
300
  workerUrl = pathToFileURL(distWorker);
196
301
  }
197
302
  }
198
- workerPool = createWorkerPool(workerUrl, options?.workerPoolSize);
303
+ // Thread the ParsedFile store path into the pool so workers write their
304
+ // own shards (#1983 parallel serialization). `parsedFileStorePath` is
305
+ // declared below but this closure only runs from inside the chunk loop,
306
+ // after it is initialized; `undefined` drives the worker no-store
307
+ // fallback (return ParsedFiles in the result).
308
+ workerPool = createWorkerPool(workerUrl, effectivePoolSize, {
309
+ parsedFileStoreStoragePath: parsedFileStorePath,
310
+ // Durable, content-addressed shard dir for warm-cache reuse (#2038).
311
+ // Initialized below before the chunk loop (same deferred-init pattern
312
+ // as `parsedFileStorePath`); this closure only runs from the loop.
313
+ durableParsedFileStoragePath: durableParsedFileDir,
314
+ // Fan each chunk across the whole pool (#worker-idle): without this a
315
+ // chunk smaller than the 8 MB sub-batch cap became a single job on a
316
+ // single worker. Honors an explicit `subBatchMaxBytes` / env override.
317
+ subBatchMaxBytes: dispatchSubBatchMaxBytes,
318
+ });
199
319
  return workerPool;
200
320
  }
201
321
  catch (err) {
202
- workerPoolDisabled = true;
203
- logger.warn({ err: err.message }, 'Worker pool creation failed, using sequential fallback:');
204
- return undefined;
322
+ // Pool *construction* failed (e.g. the worker script is missing — a
323
+ // broken install). Fail fast with the cause (#1741). There is no
324
+ // sequential parser to fall back to; the operator must fix the worker
325
+ // startup (commonly a missing build so dist/ has no parse-worker).
326
+ handleWorkerStartupFailure(err);
205
327
  }
206
328
  };
207
329
  let filesParsedSoFar = 0;
208
- // Two caches with different lifetimes:
209
- // - `astCache` (chunk-local, cleared between chunks) — call /
210
- // heritage / import processors read it during parse to avoid
211
- // re-parsing within the same chunk.
212
- // - `scopeTreeCache` (total-parseable-sized, never cleared by
213
- // parse-impl) — exposed via ParseOutput so scope-resolution can
214
- // skip a second tree-sitter parse. Worker-mode parses don't
215
- // populate either; consumers fall back to a fresh parse.
216
- // See plan docs/plans/2026-04-20-002-perf-parse-heritage-mro-plan.md (Unit 4).
217
- const maxChunkFiles = chunks.reduce((max, c) => Math.max(max, c.length), 0);
218
- let astCache = createASTCache(maxChunkFiles);
219
- const scopeTreeCache = createASTCache(Math.max(parseableScanned.length, 1));
220
- // Build import resolution context once — suffix index, file lists, resolve cache.
221
- const importCtx = buildImportResolutionContext(allPaths);
222
- const allPathObjects = allPaths.map((p) => ({ path: p }));
223
- const sequentialChunkPaths = [];
224
- const chunkNeedsSynthesis = chunks.map((paths) => paths.some((p) => {
225
- const lang = getLanguageFromFilename(p);
226
- return lang != null && needsSynthesis(lang);
227
- }));
228
330
  const exportedTypeMap = new Map();
229
331
  const bindingAccumulator = new BindingAccumulator();
230
- // Tracks whether per-chunk or fallback wildcard-binding synthesis already
231
- // ran, so the unconditional final call below can be skipped when redundant.
232
- // synthesizeWildcardImportBindings is graph-global; once any chunk runs it
233
- // after parsing wildcard files, later non-wildcard chunks add no work for
234
- // it, and later wildcard chunks re-run it themselves.
235
- let hasSynthesized = false;
236
332
  const allFetchCalls = [];
237
333
  const allFetchWrapperDefs = [];
238
334
  const allExtractedRoutes = [];
@@ -242,20 +338,6 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
242
338
  const allRouterModuleAliases = [];
243
339
  const allToolDefs = [];
244
340
  const allORMQueries = [];
245
- const deferredWorkerCalls = [];
246
- const deferredWorkerHeritage = [];
247
- const deferredConstructorBindings = [];
248
- const deferredAssignments = [];
249
- // Imports accumulated across chunks. Previously processed per-chunk
250
- // via `processImportsFromExtracted` inside the chunk loop, which
251
- // forced workers to sit idle on the main thread's extraction pass
252
- // between chunk dispatches (4-5% CPU utilization symptom). Deferring
253
- // to a single end-of-loop pass lets the worker pool start chunk N+1
254
- // immediately after chunk N's worker dispatch returns. Resolution is
255
- // strictly-more-information at end-of-loop because graph now has
256
- // every chunk's symbols — improves cross-chunk import targets.
257
- const deferredWorkerImports = [];
258
- let anyChunkNeedsWildcardSynth = false;
259
341
  // Aggregated per-file ParsedFile artifacts produced by workers' calls
260
342
  // to `extractParsedFile`. Threaded through to the scope-resolution
261
343
  // phase so it can SKIP its own re-extraction on cache hits — this is
@@ -267,6 +349,29 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
267
349
  // run's, replay the cached ParseWorkerResult[] instead of dispatching
268
350
  // to workers. See gitnexus/src/storage/parse-cache.ts.
269
351
  const parseCache = options?.parseCache;
352
+ // Disk-backed ParsedFile store (#1983): when a storage path is available we
353
+ // flush worker-produced ParsedFiles to disk per chunk (instead of retaining
354
+ // them in `allParsedFiles`) and scope-resolution streams them back per
355
+ // language — avoiding both the ~1× semantic-model RAM cost of holding them
356
+ // and, critically, the unbounded native tree-sitter re-parse leak that
357
+ // scope-resolution's main-thread re-extraction otherwise accumulates. When
358
+ // there is no storage path (tests / direct pipeline calls), we fall back to
359
+ // retaining them in `allParsedFiles` (small-repo path, preserves prior
360
+ // behavior). Cleared up-front so a prior run's shards never leak in.
361
+ const parsedFileStorePath = parseCache?.storagePath;
362
+ if (parsedFileStorePath)
363
+ await clearParsedFileStore(parsedFileStorePath);
364
+ // Durable, content-addressed ParsedFile store (#2038 warm-cache coverage) —
365
+ // a sibling of the run-scoped store, NOT cleared per run. Workers write a
366
+ // shard per chunk hash; on a warm parse-cache hit we restore the chunk's
367
+ // shards into the run-scoped store so scope-resolution streams them without
368
+ // re-parsing. `durableHitKeys` is the prior run's index, version-gated by
369
+ // PARSE_CACHE_VERSION (a mismatch ⇒ empty ⇒ every chunk re-dispatches, which
370
+ // repopulates the durable store — never the main-thread extract fallback).
371
+ const durableParsedFileDir = parsedFileStorePath !== undefined ? getDurableParsedFileDir(parsedFileStorePath) : undefined;
372
+ const durableHitKeys = durableParsedFileDir !== undefined
373
+ ? await loadDurableParsedFileIndex(durableParsedFileDir, PARSE_CACHE_VERSION)
374
+ : new Set();
270
375
  let chunkCacheHits = 0;
271
376
  let chunkCacheMisses = 0;
272
377
  try {
@@ -305,7 +410,125 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
305
410
  // body, which re-read process.env on every iteration even though
306
411
  // the env can't change mid-run.
307
412
  const verboseThroughputLog = isDev || isVerboseIngestionEnabled();
413
+ const heapProbeEveryN = isDebugHeapEnabled() ? 25 : 0;
414
+ let pendingWorkerChunk = null;
415
+ // Apply one chunk's merged worker data: per-chunk aggregation into the
416
+ // run-level accumulators + the throughput log. Shared by the cache-hit
417
+ // (inline) and worker (deferred) paths. The `| null` guard is defensive —
418
+ // every live caller passes real worker data now that sequential parsing
419
+ // (which was the only path that passed null) is gone.
420
+ const applyChunkResults = async (chunkWorkerData, chunkIdx, chunkFiles, chunkStartMs) => {
421
+ if (chunkWorkerData) {
422
+ if (chunkWorkerData.parsedFiles?.length) {
423
+ if (parsedFileStorePath) {
424
+ await persistParsedFileChunk(parsedFileStorePath, `chunk-${chunkIdx}`, chunkWorkerData.parsedFiles);
425
+ }
426
+ else {
427
+ for (const item of chunkWorkerData.parsedFiles)
428
+ allParsedFiles.push(item);
429
+ }
430
+ }
431
+ if (chunkWorkerData.fileScopeBindings?.length) {
432
+ for (const { filePath, bindings } of chunkWorkerData.fileScopeBindings) {
433
+ if (typeof filePath !== 'string' || filePath.length === 0)
434
+ continue;
435
+ if (!Array.isArray(bindings))
436
+ continue;
437
+ const entries = [];
438
+ for (const tuple of bindings) {
439
+ if (!Array.isArray(tuple) || tuple.length !== 2)
440
+ continue;
441
+ const [varName, typeName] = tuple;
442
+ if (typeof varName !== 'string' || typeof typeName !== 'string')
443
+ continue;
444
+ entries.push({ scope: '', varName, typeName });
445
+ }
446
+ if (entries.length > 0) {
447
+ bindingAccumulator.appendFile(filePath, entries);
448
+ }
449
+ }
450
+ }
451
+ if (chunkWorkerData.fetchCalls?.length) {
452
+ for (const item of chunkWorkerData.fetchCalls)
453
+ allFetchCalls.push(item);
454
+ }
455
+ if (chunkWorkerData.fetchWrapperDefs?.length) {
456
+ for (const item of chunkWorkerData.fetchWrapperDefs)
457
+ allFetchWrapperDefs.push(item);
458
+ }
459
+ if (chunkWorkerData.routes?.length) {
460
+ for (const item of chunkWorkerData.routes)
461
+ allExtractedRoutes.push(item);
462
+ }
463
+ if (chunkWorkerData.decoratorRoutes?.length) {
464
+ for (const item of chunkWorkerData.decoratorRoutes)
465
+ allDecoratorRoutes.push(item);
466
+ }
467
+ if (chunkWorkerData.routerIncludes?.length) {
468
+ for (const item of chunkWorkerData.routerIncludes)
469
+ allRouterIncludes.push(item);
470
+ }
471
+ if (chunkWorkerData.routerImports?.length) {
472
+ for (const item of chunkWorkerData.routerImports)
473
+ allRouterImports.push(item);
474
+ }
475
+ if (chunkWorkerData.routerModuleAliases?.length) {
476
+ for (const item of chunkWorkerData.routerModuleAliases)
477
+ allRouterModuleAliases.push(item);
478
+ }
479
+ if (chunkWorkerData.toolDefs?.length) {
480
+ for (const item of chunkWorkerData.toolDefs)
481
+ allToolDefs.push(item);
482
+ }
483
+ if (chunkWorkerData.ormQueries?.length) {
484
+ for (const item of chunkWorkerData.ormQueries)
485
+ allORMQueries.push(item);
486
+ }
487
+ }
488
+ filesParsedSoFar += chunkFiles.length;
489
+ if (verboseThroughputLog && chunkStartMs !== null) {
490
+ const elapsedMs = Date.now() - chunkStartMs;
491
+ const filesPerSec = elapsedMs > 0 ? (chunkFiles.length * 1000) / elapsedMs : 0;
492
+ const stats = workerPool?.getStats?.();
493
+ const poolFrag = stats
494
+ ? ` pool: ${stats.activeSlots}/${stats.size} active, ` +
495
+ `${stats.quarantined} quarantined${stats.poolBroken ? ', BROKEN' : ''}`
496
+ : ' (cache replay)';
497
+ logger.info(`📊 chunk ${chunkIdx + 1}/${numChunks}: ${chunkFiles.length} files in ${elapsedMs}ms ` +
498
+ `(${filesPerSec.toFixed(1)} files/s)${poolFrag}`);
499
+ }
500
+ };
501
+ // Merge + finalize a parked worker chunk: graph merge (the overlapped
502
+ // main-thread step) → parse-cache write-guard → run-level aggregation.
503
+ const finalizeWorkerChunk = async (p) => {
504
+ const chunkWorkerData = mergeChunkResults(graph, symbolTable, p.rawResults, exportedTypeMap);
505
+ // Persist raw results for this chunk hash (skipping when any chunk file
506
+ // was worker-quarantined, so the narrower rawResults isn't cached under
507
+ // the full-chunk key — see the original inline note / U20.U2).
508
+ if (parseCache && p.chunkHash && p.rawResults.length > 0) {
509
+ const quarantineSet = new Set(workerPool?.getQuarantinedPaths?.() ?? []);
510
+ const chunkHadQuarantine = p.chunkFiles.some((f) => quarantineSet.has(f.path));
511
+ if (chunkHadQuarantine) {
512
+ if (isDev) {
513
+ const quarantinedInChunk = p.chunkFiles.filter((f) => quarantineSet.has(f.path)).length;
514
+ logger.info(`📦 parse-cache SKIP: chunk ${p.chunkIdx + 1}/${numChunks} ` +
515
+ `had ${quarantinedInChunk} worker-quarantined file(s); ` +
516
+ `next run will rediscover (${p.chunkHash.slice(0, 8)})`);
517
+ }
518
+ }
519
+ else {
520
+ await persistParseCacheChunk(parseCache, p.chunkHash, p.rawResults);
521
+ if (isDev) {
522
+ logger.info(`📦 parse-cache MISS+store: chunk ${p.chunkIdx + 1}/${numChunks} (${p.chunkFiles.length} files, ${p.chunkHash.slice(0, 8)})`);
523
+ }
524
+ }
525
+ }
526
+ await applyChunkResults(chunkWorkerData, p.chunkIdx, p.chunkFiles, p.chunkStartMs);
527
+ };
308
528
  for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
529
+ if (heapProbeEveryN > 0 && chunkIdx > 0 && chunkIdx % heapProbeEveryN === 0) {
530
+ logHeapProbe(`parse-chunk-${chunkIdx}`, `nodes=${graph.nodeCount} parsedFiles=${allParsedFiles.length}`);
531
+ }
309
532
  const chunkPaths = chunks[chunkIdx];
310
533
  // Start wall-clock for the per-chunk throughput log emitted at end
311
534
  // of this iteration. The gate is computed once above; here we just
@@ -337,18 +560,30 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
337
560
  }));
338
561
  chunkHash = computeChunkHash(entries);
339
562
  }
340
- let chunkWorkerData;
341
- const cachedRaw = chunkHash && parseCache ? parseCache.entries.get(chunkHash) : undefined;
563
+ const cachedRaw = chunkHash && parseCache ? await loadParseCacheChunk(parseCache, chunkHash) : undefined;
342
564
  // Track every chunk hash we touched so the orchestrator can
343
565
  // prune stale entries (chunks whose composition no longer
344
566
  // corresponds to a live chunk in the current scan) before saving.
345
567
  if (parseCache && chunkHash)
346
568
  parseCache.usedKeys.add(chunkHash);
347
- if (cachedRaw && cachedRaw.length > 0) {
348
- // Cache hit: replay the cached worker output through the same
349
- // merge logic the live worker path uses.
569
+ // A parse-cache hit may skip the workers ONLY if the chunk's ParsedFiles
570
+ // are recoverable without a main-thread re-parse: restored from a durable
571
+ // shard (store path) or carried in the cached result (no-store path). If a
572
+ // cached chunk's durable shards are missing — first run after the durable
573
+ // store was introduced, or a pruned/version-stale shard — fall through to
574
+ // a worker re-dispatch to repopulate them. NEVER let scope-resolution
575
+ // re-extract on the main thread (the #1983 OOM the durable store closes).
576
+ const durableHit = chunkHash !== null && durableParsedFileDir !== undefined && durableHitKeys.has(chunkHash);
577
+ if (cachedRaw && cachedRaw.length > 0 && (durableHit || parsedFileStorePath === undefined)) {
578
+ // Cache hit: replay cached worker output. Finalize any parked worker
579
+ // chunk FIRST so deferred aggregation stays in chunk order, then merge
580
+ // + apply this hit inline (no worker dispatch to overlap).
581
+ if (pendingWorkerChunk) {
582
+ await finalizeWorkerChunk(pendingWorkerChunk);
583
+ pendingWorkerChunk = null;
584
+ }
350
585
  chunkCacheHits++;
351
- chunkWorkerData = mergeChunkResults(graph, symbolTable, cachedRaw);
586
+ const chunkWorkerData = mergeChunkResults(graph, symbolTable, cachedRaw, exportedTypeMap);
352
587
  if (isDev) {
353
588
  logger.info(`📦 parse-cache HIT: chunk ${chunkIdx + 1}/${numChunks} (${chunkFiles.length} files, ${chunkHash?.slice(0, 8) ?? 'unknown'})`);
354
589
  }
@@ -368,12 +603,22 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
368
603
  nodesCreated: graph.nodeCount,
369
604
  },
370
605
  });
606
+ // Restore the chunk's durable ParsedFile shards into the run-scoped
607
+ // store so scope-resolution finds full coverage with ZERO main-thread
608
+ // re-parse. A verbatim byte copy — byte-identical to a cold run.
609
+ if (durableHit && durableParsedFileDir && parsedFileStorePath && chunkHash) {
610
+ const restored = await restoreDurableParsedFileShard(durableParsedFileDir, parsedFileStorePath, chunkHash);
611
+ if (restored === 0) {
612
+ logger.warn(`parsedfile-cache: durable shards missing for cached chunk ` +
613
+ `${chunkHash.slice(0, 8)} — scope-resolution will re-extract these files`);
614
+ }
615
+ }
616
+ await applyChunkResults(chunkWorkerData, chunkIdx, chunkFiles, chunkStartMs);
371
617
  }
372
618
  else {
373
619
  // Cache miss: dispatch to workers, capture the raw results, store
374
620
  // them under the chunk hash for the next run.
375
621
  chunkCacheMisses++;
376
- const rawResults = [];
377
622
  const progressForChunk = (current, _total, filePath) => {
378
623
  const globalCurrent = filesParsedSoFar + current;
379
624
  // Parse phase covers 20-70 (M2). Deferred extraction handles 70-95.
@@ -391,315 +636,82 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
391
636
  });
392
637
  };
393
638
  const activeWorkerPool = getOrCreateWorkerPool();
639
+ // Worker path — PIPELINE: kick off this chunk's dispatch, merge the
640
+ // PREVIOUS chunk while these workers parse, then park this chunk for
641
+ // the next iteration to merge (overlapping its parse). The deferred
642
+ // merge + parse-cache write-guard + aggregation all run in
643
+ // `finalizeWorkerChunk`, in chunk order. The pool is the sole parse
644
+ // path — `getOrCreateWorkerPool` returns a pool or throws.
645
+ const dispatchPromise = dispatchChunkParse(chunkFiles, activeWorkerPool, progressForChunk, undefined, chunkHash ?? undefined);
646
+ // Mark handled so a rejection during the overlap drain below isn't
647
+ // flagged as unhandled; the `await` re-throws it for real handling.
648
+ dispatchPromise.catch(() => { });
649
+ if (pendingWorkerChunk) {
650
+ await finalizeWorkerChunk(pendingWorkerChunk);
651
+ pendingWorkerChunk = null;
652
+ }
653
+ let chunkResults;
394
654
  try {
395
- chunkWorkerData = await processParsing(graph, chunkFiles, symbolTable, astCache, scopeTreeCache, progressForChunk, activeWorkerPool,
396
- // Capture raw results only when we have a cache to write to —
397
- // otherwise we'd retain extra arrays for nothing.
398
- parseCache && chunkHash && activeWorkerPool ? rawResults : undefined);
655
+ chunkResults = await dispatchPromise;
399
656
  }
400
657
  catch (err) {
401
658
  if (!(err instanceof WorkerPoolInitializationError))
402
659
  throw err;
403
- logger.warn({
404
- err: err.message,
405
- readinessFailures: err.readinessFailures,
406
- }, 'Worker pool initialization failed, using sequential fallback:');
407
- rawResults.length = 0;
408
- workerPoolDisabled = true;
409
- const failedPool = workerPool;
410
- workerPool = undefined;
411
- await failedPool?.terminate().catch(() => undefined);
412
- chunkWorkerData = await processParsing(graph, chunkFiles, symbolTable, astCache, scopeTreeCache, progressForChunk, undefined, undefined);
413
- }
414
- // Persist the raw results for this chunk hash. Sequential path
415
- // doesn't populate rawResults (it writes directly to graph), so
416
- // small repos without worker pool simply don't cache. That's fine.
417
- //
418
- // U20.U2: refuse the write when any chunk file is in the
419
- // worker pool's cumulative quarantine snapshot. The chunkHash
420
- // is computed from EVERY file in the chunk, but the pool's
421
- // Layer 3 quarantine filters quarantined files out of dispatch
422
- // — so `rawResults` is narrower than the chunkHash key implies.
423
- // Caching it would silently replay incomplete results on the
424
- // next run with unchanged content (the corruption class Codex's
425
- // adversarial review of PR #1693 flagged).
426
- //
427
- // Skipping the write means the next analyze gets a cache miss
428
- // for this chunk and re-dispatches against a fresh worker pool
429
- // (quarantine is session-scoped — `createQuarantine` is called
430
- // per-pool at worker-pool.ts), giving the quarantined file
431
- // another chance. If quarantine fires again, U20.U1's
432
- // sequential gap-fill still produces a complete graph for this
433
- // run; the cache just stays empty for this chunk until a fully-
434
- // clean dispatch lands.
435
- if (parseCache && chunkHash && rawResults.length > 0) {
436
- const quarantineSnapshot = workerPool?.getQuarantinedPaths?.() ?? [];
437
- const quarantineSet = new Set(quarantineSnapshot);
438
- const chunkHadQuarantine = chunkFiles.some((f) => quarantineSet.has(f.path));
439
- if (chunkHadQuarantine) {
440
- if (isDev) {
441
- const quarantinedInChunk = chunkFiles.filter((f) => quarantineSet.has(f.path)).length;
442
- logger.info(`📦 parse-cache SKIP: chunk ${chunkIdx + 1}/${numChunks} ` +
443
- `had ${quarantinedInChunk} worker-quarantined file(s); ` +
444
- `next run will rediscover (${chunkHash.slice(0, 8)})`);
445
- }
446
- }
447
- else {
448
- parseCache.entries.set(chunkHash, rawResults);
449
- if (isDev) {
450
- logger.info(`📦 parse-cache MISS+store: chunk ${chunkIdx + 1}/${numChunks} (${chunkFiles.length} files, ${chunkHash.slice(0, 8)})`);
451
- }
452
- }
660
+ // Every worker crashed during startup and the pool's bounded
661
+ // self-heal was exhausted. Fail fast (#1741) — there is no sequential
662
+ // parser to degrade to. `handleWorkerStartupFailure` always throws, so
663
+ // `chunkResults` stays definitely assigned for the parked chunk below.
664
+ handleWorkerStartupFailure(err);
453
665
  }
454
- }
455
- // Per-chunk extraction passes (processImportsFromExtracted,
456
- // processHeritageFromExtracted, processRoutesFromExtracted,
457
- // synthesizeWildcardImportBindings, seedCrossFileReceiverTypes)
458
- // moved out of the chunk loop into a single end-of-loop pass below.
459
- // Reason: per-chunk extraction blocked the chunk loop on
460
- // main-thread work between worker dispatches — workers sat idle
461
- // and total CPU utilization plateaued at 4-5% on multi-core boxes.
462
- // Deferring keeps workers busy chunk-after-chunk; resolution sees
463
- // strictly-more-information (full repo graph) so cross-chunk import
464
- // and heritage targets resolve at least as well as before.
465
- if (chunkWorkerData) {
466
- if (chunkNeedsSynthesis[chunkIdx]) {
467
- anyChunkNeedsWildcardSynth = true;
468
- }
469
- const skipFile = new Set();
470
- const checkFile = new Set();
471
- const shouldAccumulate = (filePath) => {
472
- if (checkFile.has(filePath))
473
- return true;
474
- if (skipFile.has(filePath))
475
- return false;
476
- const lang = getLanguageFromFilename(filePath);
477
- if (lang !== null && isRegistryPrimary(lang)) {
478
- skipFile.add(filePath);
479
- return false;
480
- }
481
- checkFile.add(filePath);
482
- return true;
666
+ pendingWorkerChunk = {
667
+ rawResults: chunkResults,
668
+ chunkIdx,
669
+ chunkHash,
670
+ chunkFiles,
671
+ chunkStartMs,
483
672
  };
484
- for (const item of chunkWorkerData.imports) {
485
- if (shouldAccumulate(item.filePath))
486
- deferredWorkerImports.push(item);
487
- }
488
- for (const item of chunkWorkerData.calls) {
489
- if (shouldAccumulate(item.filePath))
490
- deferredWorkerCalls.push(item);
491
- }
492
- for (const item of chunkWorkerData.heritage) {
493
- if (shouldAccumulate(item.filePath))
494
- deferredWorkerHeritage.push(item);
495
- }
496
- for (const item of chunkWorkerData.constructorBindings) {
497
- if (shouldAccumulate(item.filePath))
498
- deferredConstructorBindings.push(item);
499
- }
500
- // Aggregate worker-produced ParsedFile artifacts so scope-
501
- // resolution can use them as a re-extraction cache (skips its
502
- // own tree-sitter re-parse on warm runs).
503
- if (chunkWorkerData.parsedFiles?.length) {
504
- for (const item of chunkWorkerData.parsedFiles)
505
- allParsedFiles.push(item);
506
- }
507
- if (chunkWorkerData.assignments?.length) {
508
- for (const item of chunkWorkerData.assignments) {
509
- if (shouldAccumulate(item.filePath))
510
- deferredAssignments.push(item);
511
- }
512
- }
513
- if (chunkWorkerData.fileScopeBindings?.length) {
514
- for (const { filePath, bindings } of chunkWorkerData.fileScopeBindings) {
515
- if (typeof filePath !== 'string' || filePath.length === 0)
516
- continue;
517
- if (!Array.isArray(bindings))
518
- continue;
519
- const entries = [];
520
- for (const tuple of bindings) {
521
- if (!Array.isArray(tuple) || tuple.length !== 2)
522
- continue;
523
- const [varName, typeName] = tuple;
524
- if (typeof varName !== 'string' || typeof typeName !== 'string')
525
- continue;
526
- entries.push({ scope: '', varName, typeName });
527
- }
528
- if (entries.length > 0) {
529
- bindingAccumulator.appendFile(filePath, entries);
530
- }
531
- }
532
- }
533
- if (chunkWorkerData.fetchCalls?.length) {
534
- for (const item of chunkWorkerData.fetchCalls)
535
- allFetchCalls.push(item);
536
- }
537
- if (chunkWorkerData.fetchWrapperDefs?.length) {
538
- for (const item of chunkWorkerData.fetchWrapperDefs)
539
- allFetchWrapperDefs.push(item);
540
- }
541
- if (chunkWorkerData.routes?.length) {
542
- for (const item of chunkWorkerData.routes)
543
- allExtractedRoutes.push(item);
544
- }
545
- if (chunkWorkerData.decoratorRoutes?.length) {
546
- for (const item of chunkWorkerData.decoratorRoutes)
547
- allDecoratorRoutes.push(item);
548
- }
549
- if (chunkWorkerData.routerIncludes?.length) {
550
- for (const item of chunkWorkerData.routerIncludes)
551
- allRouterIncludes.push(item);
552
- }
553
- if (chunkWorkerData.routerImports?.length) {
554
- for (const item of chunkWorkerData.routerImports)
555
- allRouterImports.push(item);
556
- }
557
- if (chunkWorkerData.routerModuleAliases?.length) {
558
- for (const item of chunkWorkerData.routerModuleAliases)
559
- allRouterModuleAliases.push(item);
560
- }
561
- if (chunkWorkerData.toolDefs?.length) {
562
- for (const item of chunkWorkerData.toolDefs)
563
- allToolDefs.push(item);
564
- }
565
- if (chunkWorkerData.ormQueries?.length) {
566
- for (const item of chunkWorkerData.ormQueries)
567
- allORMQueries.push(item);
568
- }
569
- }
570
- else {
571
- await processImports(graph, chunkFiles, astCache, ctx, undefined, repoPath, allPaths);
572
- sequentialChunkPaths.push(chunkPaths);
573
- }
574
- filesParsedSoFar += chunkFiles.length;
575
- astCache.clear();
576
- // Throughput observability (U3): emit a per-chunk metrics line
577
- // under verbose ingestion mode so operators can verify CPU
578
- // utilization moved + tune `--workers` / batch sizes without
579
- // guessing. Cheap snapshot — just reads pool closure state.
580
- if (verboseThroughputLog && chunkStartMs !== null) {
581
- const elapsedMs = Date.now() - chunkStartMs;
582
- const filesPerSec = elapsedMs > 0 ? (chunkFiles.length * 1000) / elapsedMs : 0;
583
- const stats = workerPool?.getStats?.();
584
- const poolFrag = stats
585
- ? ` pool: ${stats.activeSlots}/${stats.size} active, ` +
586
- `${stats.quarantined} quarantined${stats.poolBroken ? ', BROKEN' : ''}`
587
- : ' (sequential)';
588
- logger.info(`📊 chunk ${chunkIdx + 1}/${numChunks}: ${chunkFiles.length} files in ${elapsedMs}ms ` +
589
- `(${filesPerSec.toFixed(1)} files/s)${poolFrag}`);
590
673
  }
674
+ // (Per-chunk aggregation + parse-cache write + throughput log now run in
675
+ // `applyChunkResults` / `finalizeWorkerChunk` — see the merge-pipelining
676
+ // block above. Route/import/inheritance edges are emitted later: route
677
+ // resolution in the single end-of-loop pass below, the rest by the
678
+ // scope-resolution phase, RING4-2 #943.)
679
+ }
680
+ // Drain the final parked worker chunk — the last pipelined chunk has no
681
+ // successor to overlap its merge with, so merge + finalize it here.
682
+ if (pendingWorkerChunk) {
683
+ await finalizeWorkerChunk(pendingWorkerChunk);
684
+ pendingWorkerChunk = null;
591
685
  }
592
686
  if (isDev && parseCache && (chunkCacheHits > 0 || chunkCacheMisses > 0)) {
593
687
  logger.info(`📦 parse-cache summary: ${chunkCacheHits} chunk hit(s), ${chunkCacheMisses} miss(es) across ${numChunks} chunk(s)`);
594
688
  }
689
+ logHeapProbe('post-parse-chunks', `routes=${allExtractedRoutes.length} nodes=${graph.nodeCount} parsedFiles=${allParsedFiles.length}`);
595
690
  // Deferred end-of-loop extraction (moved out of the per-chunk block):
596
- // 1. processImportsFromExtracted on all chunks' imports
597
- // 2. synthesizeWildcardImportBindings (if any chunk had wildcards)
598
- // 3. seedCrossFileReceiverTypes on deferred calls (depends on
599
- // namedImportMap populated by step 1)
600
- // 4. processHeritageFromExtracted on all chunks' heritage
601
- // 5. processRoutesFromExtracted on all chunks' routes
602
- // Same logic as the prior per-chunk passes, just batched resolution
603
- // sees the full repo graph instead of just current-and-earlier chunks.
604
- // Deferred extraction band (M2 from PR #1693 review): the 4 stages below
605
- // each get their own 5-10 point slice of the 70-95 range so percent
606
- // advances monotonically through the (potentially long) resolution work
607
- // instead of holding flat at 82. Stages that are skipped (zero-length
608
- // input) leave their band as a no-op jump — the next stage still starts
609
- // at its own band, preserving monotonicity.
610
- // imports: 70 -> 75 (5)
611
- // heritage: 75 -> 80 (5)
691
+ // 1. route resolution on all chunks' routes
692
+ // Resolution sees the full repo graph instead of just current-and-earlier
693
+ // chunks. Import, call, and inheritance edges are emitted by the
694
+ // scope-resolution phase, not here (RING4-1 #942 removed the legacy call
695
+ // DAG; RING4-2 #943 removed the legacy import-map resolution + wildcard
696
+ // synthesis). Progress band: the route stage gets a slice of the 70-95
697
+ // range; a zero-length input leaves its band as a no-op jump.
612
698
  // routes: 80 -> 85 (5)
613
- // calls: 85 -> 95 (10)
614
699
  const deferredProfile = isDeferredResolutionProfileEnabled();
615
700
  if (deferredProfile) {
616
- logDeferredProfile(`deferred band start: imports=${deferredWorkerImports.length} heritage=${deferredWorkerHeritage.length} ` +
617
- `calls=${deferredWorkerCalls.length} routes=${allExtractedRoutes.length}`);
701
+ logDeferredProfile(`deferred band start: routes=${allExtractedRoutes.length}`);
618
702
  }
619
- if (deferredWorkerImports.length > 0) {
620
- const tImports = startTimer(deferredProfile);
621
- await processImportsFromExtracted(graph, allPathObjects, deferredWorkerImports, ctx, (current, total) => {
622
- const ratio = total > 0 ? current / total : 1;
623
- onProgress({
624
- phase: 'parsing',
625
- percent: 70 + Math.round(ratio * 5),
626
- message: 'Resolving imports (all chunks)...',
627
- detail: `${current}/${total} files`,
628
- stats: {
629
- filesProcessed: filesParsedSoFar,
630
- totalFiles: totalParseable,
631
- nodesCreated: graph.nodeCount,
632
- },
633
- });
634
- }, repoPath, importCtx);
635
- endTimer(tImports, (ms) => `processImportsFromExtracted: ${ms.toFixed(0)}ms (${deferredWorkerImports.length} import batches before drain)`);
636
- // U15 (lightweight M1): processImportsFromExtracted is the sole
637
- // consumer of `deferredWorkerImports`. Free the array now so the
638
- // GC can reclaim the per-file ExtractedImport records before the
639
- // heavier downstream stages run (heritage, routes, calls). Peak
640
- // accumulator memory drops from O(repo) to O(repo - imports) for
641
- // the remainder of the deferred phase. The future per-chunk
642
- // streaming upgrade can rewrite this with the same correctness
643
- // contract once profile data shows it's warranted.
644
- deferredWorkerImports.length = 0;
645
- }
646
- if (anyChunkNeedsWildcardSynth) {
647
- const tWildcard = startTimer(deferredProfile);
648
- synthesizeWildcardImportBindings(graph, ctx);
649
- hasSynthesized = true;
650
- endTimer(tWildcard, (ms) => `synthesizeWildcardImportBindings: ${ms.toFixed(0)}ms`);
651
- }
652
- // L5 from PR #1693 review: populate `exportedTypeMap` from the in-progress
653
- // graph BEFORE `seedCrossFileReceiverTypes` runs. Previously the seeding
654
- // branch below was reached with `exportedTypeMap.size === 0` in the
655
- // worker path (the map was only built at the post-parse block far below,
656
- // AFTER the seeding branch), so the seed dead-coded itself silently and
657
- // call resolution never got the cross-file receiver-type enrichment.
658
- // The post-parse builder still runs as a defensive fallback on the
659
- // sequential path; its `size === 0` guard means we don't pay the cost
660
- // twice on the worker path.
703
+ // Populate `exportedTypeMap` from the in-progress graph so the post-parse
704
+ // enrichment pass (enrichExportedTypeMap) sees cross-file export types.
661
705
  if (exportedTypeMap.size === 0 && graph.nodeCount > 0) {
662
- const graphExports = buildExportedTypeMapFromGraph(graph, ctx.model.symbols);
706
+ logHeapProbe('pre-buildExportedTypeMapFromGraph');
707
+ const graphExports = buildExportedTypeMapFromGraph(graph, model.symbols);
663
708
  for (const [fp, exports] of graphExports)
664
709
  exportedTypeMap.set(fp, exports);
665
- }
666
- if (exportedTypeMap.size > 0 && ctx.namedImportMap.size > 0 && deferredWorkerCalls.length > 0) {
667
- const { enrichedCount } = seedCrossFileReceiverTypes(deferredWorkerCalls, ctx.namedImportMap, exportedTypeMap);
668
- if (enrichedCount > 0) {
669
- // Two independent gates, not else-if: when both isDev AND
670
- // deferredProfile are active, BOTH lines fire — log scrapers keyed
671
- // on the original "🔗 E1" emoji marker keep matching, AND operators
672
- // grepping the [deferred-profile] prefix see no gap between the
673
- // wildcard-synth and heritage timings.
674
- if (isDev) {
675
- logger.info(`🔗 E1: Seeded ${enrichedCount} cross-file receiver types (all chunks)`);
676
- }
677
- if (deferredProfile) {
678
- logDeferredProfile(`E1: seeded ${enrichedCount} cross-file receiver types (all chunks)`);
679
- }
680
- }
681
- }
682
- if (deferredWorkerHeritage.length > 0) {
683
- const tHeritage = startTimer(deferredProfile);
684
- await processHeritageFromExtracted(graph, deferredWorkerHeritage, ctx, (current, total) => {
685
- const ratio = total > 0 ? current / total : 1;
686
- onProgress({
687
- phase: 'parsing',
688
- percent: 75 + Math.round(ratio * 5),
689
- message: 'Resolving heritage (all chunks)...',
690
- detail: `${current}/${total} records`,
691
- stats: {
692
- filesProcessed: filesParsedSoFar,
693
- totalFiles: totalParseable,
694
- nodesCreated: graph.nodeCount,
695
- },
696
- });
697
- });
698
- endTimer(tHeritage, (ms) => `processHeritageFromExtracted: ${ms.toFixed(0)}ms (${deferredWorkerHeritage.length} records)`);
710
+ logHeapProbe('post-buildExportedTypeMapFromGraph');
699
711
  }
700
712
  if (allExtractedRoutes.length > 0) {
701
713
  const tRoutes = startTimer(deferredProfile);
702
- await processRoutesFromExtracted(graph, allExtractedRoutes, ctx, (current, total) => {
714
+ await processRoutesFromExtracted(graph, allExtractedRoutes, model, (current, total) => {
703
715
  const ratio = total > 0 ? current / total : 1;
704
716
  onProgress({
705
717
  phase: 'parsing',
@@ -715,159 +727,27 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
715
727
  });
716
728
  endTimer(tRoutes, (ms) => `processRoutesFromExtracted: ${ms.toFixed(0)}ms (${allExtractedRoutes.length} routes)`);
717
729
  }
718
- let fullWorkerHeritageMap;
719
- if (deferredWorkerHeritage.length > 0) {
720
- const tBuildHeritage = startTimer(deferredProfile);
721
- fullWorkerHeritageMap = buildHeritageMap(deferredWorkerHeritage, ctx, getHeritageStrategyForLanguage);
722
- endTimer(tBuildHeritage, (ms) => `buildHeritageMap wall: ${ms.toFixed(0)}ms`);
723
- }
724
- else if (deferredProfile) {
725
- logDeferredProfile('buildHeritageMap: skipped (no heritage records)');
726
- }
727
- // U15 (lightweight M1): buildHeritageMap is the LAST consumer of the
728
- // raw `deferredWorkerHeritage` records — processCallsFromExtracted
729
- // below reads from the derived `fullWorkerHeritageMap` instead. Free
730
- // the raw heritage array now so the GC can reclaim it before the
731
- // (potentially long) call-resolution stage. processHeritageFromExtracted
732
- // earlier was a read-only consumer (pushed to graph, didn't drain).
733
- deferredWorkerHeritage.length = 0;
734
- if (deferredWorkerCalls.length > 0) {
735
- if (deferredProfile) {
736
- logDeferredProfile(`processCallsFromExtracted: starting (${deferredWorkerCalls.length} call sites, heritageMap=${fullWorkerHeritageMap !== undefined})`);
737
- }
738
- const tCalls = startTimer(deferredProfile);
739
- await processCallsFromExtracted(graph, deferredWorkerCalls, ctx, (current, total) => {
740
- const ratio = total > 0 ? current / total : 1;
741
- onProgress({
742
- phase: 'parsing',
743
- // Calls is the longest deferred stage on real repos — give it the
744
- // 10-point tail 85-95 so the progress bar visibly advances during
745
- // call resolution instead of holding at 82 (M2).
746
- percent: 85 + Math.round(ratio * 10),
747
- message: 'Resolving calls (all chunks)...',
748
- detail: `${current}/${total} files`,
749
- stats: {
750
- filesProcessed: filesParsedSoFar,
751
- totalFiles: totalParseable,
752
- nodesCreated: graph.nodeCount,
753
- },
754
- });
755
- }, deferredConstructorBindings.length > 0 ? deferredConstructorBindings : undefined, fullWorkerHeritageMap, bindingAccumulator);
756
- endTimer(tCalls, (ms) => `processCallsFromExtracted: ${ms.toFixed(0)}ms total`);
757
- }
758
- if (deferredAssignments.length > 0) {
759
- processAssignmentsFromExtracted(graph, deferredAssignments, ctx, deferredConstructorBindings.length > 0 ? deferredConstructorBindings : undefined, bindingAccumulator);
760
- }
761
- // U15 (lightweight M1): all three arrays have had their last consumer
762
- // by the time we reach this point — processCallsFromExtracted drained
763
- // `deferredWorkerCalls` and read `deferredConstructorBindings`;
764
- // processAssignmentsFromExtracted drained `deferredAssignments` and
765
- // also read `deferredConstructorBindings`. Free them now so the
766
- // function-scope references die before downstream graph-build /
767
- // scope-resolution starts using its own working memory. Note: arrays
768
- // returned in the function result object (allFetchCalls,
769
- // allExtractedRoutes, allDecoratorRoutes, allToolDefs, allORMQueries,
770
- // allParsedFiles) intentionally stay live — downstream consumers
771
- // need them.
772
- deferredWorkerCalls.length = 0;
773
- deferredConstructorBindings.length = 0;
774
- deferredAssignments.length = 0;
775
730
  }
776
731
  finally {
777
732
  await workerPool?.terminate();
778
733
  }
779
- // Sequential fallback chunks.
780
- //
781
- // U6: wrap the fallback loop and the finalize/enrich steps in a try/finally
782
- // so cleanup still runs on a mid-fallback throw. The `finally` guarantees:
783
- // 1. `astCache.clear()` releases any tree-sitter trees held by the most
784
- // recently allocated per-chunk cache, mirroring the per-chunk
785
- // `astCache.clear()` calls on the happy path.
786
- // 2. `bindingAccumulator.finalize()` runs before `crossFile` disposes the
787
- // accumulator downstream — callers that inspect partial TypeEnv state
788
- // (or consume it via `enrichExportedTypeMap` on a partial recovery)
789
- // still see a finalized accumulator.
790
- // 3. `enrichExportedTypeMap` runs so any bindings already accumulated
791
- // are propagated into `exportedTypeMap` even if the fallback aborted.
792
- //
793
- // Disposal of the accumulator remains with `crossFile` (owned by U2). We do
794
- // NOT call `bindingAccumulator.dispose()` here.
734
+ // Fetch calls + ORM queries were already extracted inside each worker
735
+ // (returned in ParseWorkerResult, aggregated per chunk in applyChunkResults).
736
+ // With sequential parsing removed there is no post-loop drain to run — only
737
+ // the TypeEnv finalize + enrichment that the drain's `finally` used to host.
738
+ // Finalize the accumulator and propagate any fixpoint-inferred exports before
739
+ // `crossFile` disposes it downstream. Wrapped in try/catch so a cleanup
740
+ // failure never masks a real parse error; disposal stays with `crossFile`.
795
741
  try {
796
- if (sequentialChunkPaths.length > 0) {
797
- synthesizeWildcardImportBindings(graph, ctx);
798
- hasSynthesized = true;
799
- }
800
- const allSequentialHeritage = [];
801
- const cachedSequentialChunkFiles = [];
802
- for (const chunkPaths of sequentialChunkPaths) {
803
- const chunkContents = await readFileContents(repoPath, chunkPaths);
804
- const chunkFiles = [];
805
- for (const p of chunkPaths) {
806
- const content = chunkContents.get(p);
807
- if (content !== undefined)
808
- chunkFiles.push({ path: p, content });
809
- }
810
- cachedSequentialChunkFiles.push(chunkFiles);
811
- astCache = createASTCache(chunkFiles.length);
812
- const sequentialHeritage = await extractExtractedHeritageFromFiles(chunkFiles, astCache);
813
- for (const h of sequentialHeritage)
814
- allSequentialHeritage.push(h);
815
- astCache.clear();
816
- }
817
- const sequentialHeritageMap = allSequentialHeritage.length > 0
818
- ? buildHeritageMap(allSequentialHeritage, ctx, getHeritageStrategyForLanguage)
819
- : undefined;
820
- for (let chunkIdx = 0; chunkIdx < sequentialChunkPaths.length; chunkIdx++) {
821
- const chunkFiles = cachedSequentialChunkFiles[chunkIdx];
822
- astCache = createASTCache(chunkFiles.length);
823
- const rubyHeritage = await processCalls(graph, chunkFiles, astCache, ctx, undefined, exportedTypeMap, undefined, undefined, undefined, sequentialHeritageMap, bindingAccumulator);
824
- await processHeritage(graph, chunkFiles, astCache, ctx);
825
- if (rubyHeritage.length > 0) {
826
- await processHeritageFromExtracted(graph, rubyHeritage, ctx);
827
- }
828
- const chunkFetchCalls = await extractFetchCallsFromFiles(chunkFiles, astCache);
829
- if (chunkFetchCalls.length > 0) {
830
- for (const item of chunkFetchCalls)
831
- allFetchCalls.push(item);
832
- }
833
- for (const f of chunkFiles) {
834
- extractORMQueriesInline(f.path, f.content, allORMQueries);
835
- }
836
- astCache.clear();
837
- cachedSequentialChunkFiles[chunkIdx] = [];
838
- }
839
- // Log resolution cache stats
840
- if (isDev) {
841
- const rcStats = ctx.getStats();
842
- const total = rcStats.cacheHits + rcStats.cacheMisses;
843
- const hitRate = total > 0 ? ((rcStats.cacheHits / total) * 100).toFixed(1) : '0';
844
- logger.info(`🔍 Resolution cache: ${rcStats.cacheHits} hits, ${rcStats.cacheMisses} misses (${hitRate}% hit rate)`);
742
+ bindingAccumulator.finalize();
743
+ const enriched = enrichExportedTypeMap(bindingAccumulator, graph, exportedTypeMap);
744
+ if (isDev && enriched > 0) {
745
+ logger.info(`🔗 Worker TypeEnv enrichment: ${enriched} fixpoint-inferred exports added to ExportedTypeMap`);
845
746
  }
846
747
  }
847
- finally {
848
- // Clearing an already-empty cache is a no-op, so this is idempotent-safe
849
- // on the happy path where every per-chunk block already cleared astCache.
850
- astCache.clear();
851
- // Run finalize + enrichment inside try/catch so a cleanup failure never
852
- // masks the original fallback error. finalize must precede crossFile's
853
- // dispose (U2) and enrichExportedTypeMap depends on finalized bindings.
854
- try {
855
- bindingAccumulator.finalize();
856
- const enriched = enrichExportedTypeMap(bindingAccumulator, graph, exportedTypeMap);
857
- if (isDev && enriched > 0) {
858
- logger.info(`🔗 Worker TypeEnv enrichment: ${enriched} fixpoint-inferred exports added to ExportedTypeMap`);
859
- }
860
- }
861
- catch (enrichErr) {
862
- if (isDev) {
863
- logger.warn({ err: enrichErr.message }, 'Post-fallback finalize/enrich failed during cleanup:');
864
- }
865
- }
866
- }
867
- if (!hasSynthesized) {
868
- const synthesized = synthesizeWildcardImportBindings(graph, ctx);
869
- if (isDev && synthesized > 0) {
870
- logger.info(`🔗 Synthesized ${synthesized} additional wildcard import bindings (Go/Ruby/C++/Swift/Python)`);
748
+ catch (enrichErr) {
749
+ if (isDev) {
750
+ logger.warn({ err: enrichErr.message }, 'Post-parse finalize/enrich failed during cleanup:');
871
751
  }
872
752
  }
873
753
  // Worker-path enrichment: if exportedTypeMap is empty (e.g. the worker pool
@@ -877,22 +757,10 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
877
757
  // crossFile receives a fully-populated map and never needs to mutate it for
878
758
  // initial-graph enrichment.
879
759
  if (exportedTypeMap.size === 0 && graph.nodeCount > 0) {
880
- const graphExports = buildExportedTypeMapFromGraph(graph, ctx.model.symbols);
760
+ const graphExports = buildExportedTypeMapFromGraph(graph, model.symbols);
881
761
  for (const [fp, exports] of graphExports)
882
762
  exportedTypeMap.set(fp, exports);
883
763
  }
884
- allPathObjects.length = 0;
885
- // Safe to reset importCtx caches here: `importCtx` (ImportResolutionContext)
886
- // is a scratch workspace used only during import path resolution. The
887
- // `resolutionContext` (`ctx`) returned below is a distinct object — it owns
888
- // the fully-populated, post-parse `importMap` / `namedImportMap` /
889
- // `packageMap` / `moduleAliasMap` / `model`, and never references
890
- // `importCtx`. Cross-file re-resolution in cross-file-impl.ts consumes only
891
- // `ctx` (via `processCalls`), so clearing the suffix index / resolveCache /
892
- // normalizedFileList here cannot lose import matches downstream.
893
- importCtx.resolveCache.clear();
894
- importCtx.index = EMPTY_INDEX;
895
- importCtx.normalizedFileList = [];
896
764
  // FastAPI router-prefix resolution (cross-file).
897
765
  //
898
766
  // Workers emit two kinds of records per Python file:
@@ -1030,6 +898,7 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
1030
898
  allDecoratorRoutes.push(dr);
1031
899
  }
1032
900
  }
901
+ logHeapProbe('parse-impl-return', `exportedTypeMap=${exportedTypeMap.size} parsedFiles=${allParsedFiles.length} nodes=${graph.nodeCount}`);
1033
902
  return {
1034
903
  exportedTypeMap,
1035
904
  allFetchCalls,
@@ -1039,23 +908,15 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
1039
908
  allToolDefs,
1040
909
  allORMQueries,
1041
910
  bindingAccumulator,
1042
- resolutionContext: ctx,
1043
- // Whether a worker pool was actually live for this run. False means the
1044
- // sequential fallback handled every chunk (either due to `skipWorkers`,
1045
- // the file-count/byte thresholds, or a pool-creation failure).
911
+ model,
912
+ // Whether a worker pool was actually constructed for this run. False means
913
+ // no pool was needed: a warm all-cache-hit run replays cached worker output
914
+ // without spawning workers, or there were no parseable files.
1046
915
  usedWorkerPool: workerPool !== undefined,
1047
- // Surface the persistent scope cache so downstream phases
1048
- // (scope-resolution) can skip re-parsing files that the
1049
- // sequential path already parsed. Survives chunk boundaries; the
1050
- // chunk-local `astCache` above is intentionally NOT exposed
1051
- // because parse-impl clears it between chunks.
1052
- scopeTreeCache,
1053
916
  // Per-file ParsedFile artifacts produced by workers' calls to
1054
- // `extractParsedFile`. Empty when only the sequential path ran
1055
- // (sequential doesn't go through the worker, and extracts ParsedFile
1056
- // inline rather than emitting it). Consumed by scope-resolution as
1057
- // a re-extraction cache: when the file's ParsedFile is here,
1058
- // scope-resolution skips its own `extractParsedFile` call.
917
+ // `extractParsedFile`. Consumed by scope-resolution as a re-extraction
918
+ // cache: when the file's ParsedFile is here, scope-resolution skips its own
919
+ // `extractParsedFile` call.
1059
920
  parsedFiles: allParsedFiles,
1060
921
  };
1061
922
  }