gitnexus 1.6.4-rc.2 → 1.6.4-rc.21

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 (243) hide show
  1. package/README.md +35 -0
  2. package/dist/_shared/index.d.ts +1 -1
  3. package/dist/_shared/index.d.ts.map +1 -1
  4. package/dist/_shared/index.js +1 -1
  5. package/dist/_shared/index.js.map +1 -1
  6. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +22 -14
  7. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
  8. package/dist/_shared/scope-resolution/finalize-algorithm.js +298 -37
  9. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  10. package/dist/_shared/scope-resolution/scope-tree.d.ts +23 -1
  11. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  12. package/dist/_shared/scope-resolution/scope-tree.js +36 -2
  13. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  14. package/dist/_shared/scope-resolution/types.d.ts +47 -3
  15. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  16. package/dist/_shared/scope-resolution/types.js +10 -2
  17. package/dist/_shared/scope-resolution/types.js.map +1 -1
  18. package/dist/cli/analyze.d.ts +6 -0
  19. package/dist/cli/analyze.js +35 -0
  20. package/dist/cli/doctor.d.ts +1 -0
  21. package/dist/cli/doctor.js +31 -0
  22. package/dist/cli/index.js +13 -0
  23. package/dist/cli/setup.js +2 -2
  24. package/dist/core/embeddings/config.d.ts +2 -0
  25. package/dist/core/embeddings/config.js +36 -0
  26. package/dist/core/embeddings/embedder.js +11 -6
  27. package/dist/core/embeddings/embedding-pipeline.d.ts +7 -1
  28. package/dist/core/embeddings/embedding-pipeline.js +93 -29
  29. package/dist/core/embeddings/exact-search.d.ts +15 -0
  30. package/dist/core/embeddings/exact-search.js +27 -0
  31. package/dist/core/embeddings/types.d.ts +4 -0
  32. package/dist/core/embeddings/types.js +2 -0
  33. package/dist/core/group/config-parser.js +2 -0
  34. package/dist/core/group/matching.d.ts +3 -3
  35. package/dist/core/group/matching.js +46 -6
  36. package/dist/core/group/storage.js +2 -0
  37. package/dist/core/group/sync.js +1 -1
  38. package/dist/core/group/types.d.ts +18 -0
  39. package/dist/core/ingestion/call-processor.d.ts +3 -3
  40. package/dist/core/ingestion/call-processor.js +58 -65
  41. package/dist/core/ingestion/constants.d.ts +4 -3
  42. package/dist/core/ingestion/constants.js +8 -3
  43. package/dist/core/ingestion/finalize-orchestrator.js +6 -3
  44. package/dist/core/ingestion/heritage-processor.js +2 -2
  45. package/dist/core/ingestion/import-processor.js +1 -1
  46. package/dist/core/ingestion/language-provider.d.ts +8 -0
  47. package/dist/core/ingestion/languages/csharp/captures.js +4 -1
  48. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +14 -13
  49. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +62 -50
  50. package/dist/core/ingestion/languages/python/captures.js +9 -1
  51. package/dist/core/ingestion/languages/python/index.d.ts +1 -1
  52. package/dist/core/ingestion/languages/python/index.js +1 -1
  53. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +3 -1
  54. package/dist/core/ingestion/languages/python/simple-hooks.js +8 -0
  55. package/dist/core/ingestion/languages/python.js +28 -1
  56. package/dist/core/ingestion/languages/swift.js +14 -0
  57. package/dist/core/ingestion/languages/typescript/arity-metadata.d.ts +59 -0
  58. package/dist/core/ingestion/languages/typescript/arity-metadata.js +103 -0
  59. package/dist/core/ingestion/languages/typescript/arity.d.ts +37 -0
  60. package/dist/core/ingestion/languages/typescript/arity.js +54 -0
  61. package/dist/core/ingestion/languages/typescript/cache-stats.d.ts +17 -0
  62. package/dist/core/ingestion/languages/typescript/cache-stats.js +28 -0
  63. package/dist/core/ingestion/languages/typescript/captures.d.ts +28 -0
  64. package/dist/core/ingestion/languages/typescript/captures.js +451 -0
  65. package/dist/core/ingestion/languages/typescript/import-decomposer.d.ts +49 -0
  66. package/dist/core/ingestion/languages/typescript/import-decomposer.js +371 -0
  67. package/dist/core/ingestion/languages/typescript/import-target.d.ts +50 -0
  68. package/dist/core/ingestion/languages/typescript/import-target.js +61 -0
  69. package/dist/core/ingestion/languages/typescript/index.d.ts +94 -0
  70. package/dist/core/ingestion/languages/typescript/index.js +94 -0
  71. package/dist/core/ingestion/languages/typescript/interpret.d.ts +35 -0
  72. package/dist/core/ingestion/languages/typescript/interpret.js +317 -0
  73. package/dist/core/ingestion/languages/typescript/merge-bindings.d.ts +62 -0
  74. package/dist/core/ingestion/languages/typescript/merge-bindings.js +158 -0
  75. package/dist/core/ingestion/languages/typescript/query.d.ts +77 -0
  76. package/dist/core/ingestion/languages/typescript/query.js +778 -0
  77. package/dist/core/ingestion/languages/typescript/receiver-binding.d.ts +59 -0
  78. package/dist/core/ingestion/languages/typescript/receiver-binding.js +171 -0
  79. package/dist/core/ingestion/languages/typescript/scope-resolver.d.ts +16 -0
  80. package/dist/core/ingestion/languages/typescript/scope-resolver.js +113 -0
  81. package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +71 -0
  82. package/dist/core/ingestion/languages/typescript/simple-hooks.js +131 -0
  83. package/dist/core/ingestion/languages/typescript.js +19 -0
  84. package/dist/core/ingestion/method-extractors/configs/swift.js +3 -4
  85. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +14 -1
  86. package/dist/core/ingestion/parsing-processor.js +20 -9
  87. package/dist/core/ingestion/pipeline-phases/processes.js +9 -4
  88. package/dist/core/ingestion/pipeline-phases/tools.d.ts +1 -0
  89. package/dist/core/ingestion/pipeline-phases/tools.js +10 -4
  90. package/dist/core/ingestion/registry-primary-flag.d.ts +3 -1
  91. package/dist/core/ingestion/registry-primary-flag.js +4 -1
  92. package/dist/core/ingestion/scope-extractor-bridge.d.ts +5 -2
  93. package/dist/core/ingestion/scope-extractor-bridge.js +7 -2
  94. package/dist/core/ingestion/scope-extractor.js +19 -18
  95. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +73 -11
  96. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +48 -10
  97. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +283 -14
  98. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +23 -2
  99. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +109 -37
  100. package/dist/core/ingestion/scope-resolution/passes/mro.js +3 -1
  101. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +13 -5
  102. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +11 -2
  103. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  104. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +8 -0
  105. package/dist/core/ingestion/scope-resolution/pipeline/run.js +21 -5
  106. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.d.ts +39 -0
  107. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +65 -0
  108. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +54 -11
  109. package/dist/core/ingestion/scope-resolution/scope/walkers.js +105 -30
  110. package/dist/core/ingestion/type-extractors/swift.js +7 -4
  111. package/dist/core/ingestion/utils/ast-helpers.d.ts +2 -0
  112. package/dist/core/ingestion/utils/ast-helpers.js +12 -0
  113. package/dist/core/ingestion/utils/env.d.ts +10 -0
  114. package/dist/core/ingestion/utils/env.js +14 -0
  115. package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
  116. package/dist/core/ingestion/workers/parse-worker.js +15 -9
  117. package/dist/core/ingestion/workers/worker-pool.d.ts +11 -4
  118. package/dist/core/ingestion/workers/worker-pool.js +244 -48
  119. package/dist/core/lbug/extension-loader.d.ts +86 -0
  120. package/dist/core/lbug/extension-loader.js +184 -0
  121. package/dist/core/lbug/lbug-adapter.d.ts +18 -17
  122. package/dist/core/lbug/lbug-adapter.js +45 -73
  123. package/dist/core/lbug/pool-adapter.js +10 -28
  124. package/dist/core/platform/capabilities.d.ts +24 -0
  125. package/dist/core/platform/capabilities.js +54 -0
  126. package/dist/core/run-analyze.js +36 -9
  127. package/dist/core/search/bm25-index.d.ts +0 -17
  128. package/dist/core/search/bm25-index.js +10 -118
  129. package/dist/core/search/fts-indexes.d.ts +1 -0
  130. package/dist/core/search/fts-indexes.js +7 -0
  131. package/dist/core/search/fts-schema.d.ts +6 -0
  132. package/dist/core/search/fts-schema.js +7 -0
  133. package/dist/mcp/core/embedder.js +11 -4
  134. package/dist/mcp/local/local-backend.js +50 -15
  135. package/dist/server/api.d.ts +5 -0
  136. package/dist/server/api.js +113 -0
  137. package/hooks/claude/gitnexus-hook.cjs +11 -1
  138. package/package.json +6 -5
  139. package/scripts/build-tree-sitter-dart.cjs +42 -0
  140. package/scripts/build-tree-sitter-proto.cjs +1 -1
  141. package/scripts/build.js +22 -2
  142. package/scripts/install-duckdb-extension.mjs +37 -0
  143. package/vendor/tree-sitter-dart/README.md +18 -0
  144. package/vendor/tree-sitter-dart/binding.gyp +31 -0
  145. package/vendor/tree-sitter-dart/bindings/node/binding.cc +20 -0
  146. package/vendor/tree-sitter-dart/bindings/node/index.d.ts +28 -0
  147. package/vendor/tree-sitter-dart/bindings/node/index.js +7 -0
  148. package/vendor/tree-sitter-dart/grammar.js +2895 -0
  149. package/vendor/tree-sitter-dart/package.json +18 -0
  150. package/vendor/tree-sitter-dart/queries/highlights.scm +246 -0
  151. package/vendor/tree-sitter-dart/queries/tags.scm +92 -0
  152. package/vendor/tree-sitter-dart/queries/test.scm +1 -0
  153. package/vendor/tree-sitter-dart/src/grammar.json +12459 -0
  154. package/vendor/tree-sitter-dart/src/node-types.json +15055 -0
  155. package/vendor/tree-sitter-dart/src/parser.c +196127 -0
  156. package/vendor/tree-sitter-dart/src/scanner.c +130 -0
  157. package/vendor/tree-sitter-dart/src/tree_sitter/alloc.h +54 -0
  158. package/vendor/tree-sitter-dart/src/tree_sitter/array.h +290 -0
  159. package/vendor/tree-sitter-dart/src/tree_sitter/parser.h +265 -0
  160. package/vendor/tree-sitter-swift/LICENSE +21 -0
  161. package/vendor/tree-sitter-swift/README.md +139 -0
  162. package/vendor/tree-sitter-swift/bindings/node/index.d.ts +28 -0
  163. package/vendor/tree-sitter-swift/bindings/node/index.js +7 -0
  164. package/vendor/tree-sitter-swift/package.json +28 -0
  165. package/vendor/tree-sitter-swift/prebuilds/darwin-arm64/tree-sitter-swift.node +0 -0
  166. package/vendor/tree-sitter-swift/prebuilds/darwin-x64/tree-sitter-swift.node +0 -0
  167. package/vendor/tree-sitter-swift/prebuilds/linux-arm64/tree-sitter-swift.node +0 -0
  168. package/vendor/tree-sitter-swift/prebuilds/linux-x64/tree-sitter-swift.node +0 -0
  169. package/vendor/tree-sitter-swift/prebuilds/win32-arm64/tree-sitter-swift.node +0 -0
  170. package/vendor/tree-sitter-swift/prebuilds/win32-x64/tree-sitter-swift.node +0 -0
  171. package/vendor/tree-sitter-swift/src/node-types.json +30694 -0
  172. package/web/assets/agent-DaprsFSX.js +597 -0
  173. package/web/assets/architecture-YZFGNWBL-S5CXDPWN-DEdGaPg2.js +1 -0
  174. package/web/assets/architectureDiagram-EMZXCZ2Q-Domyk_gO.js +36 -0
  175. package/web/assets/blockDiagram-IGV67L2C-B_2kD7tM.js +132 -0
  176. package/web/assets/c4Diagram-DFAF54RM-BhJJW8Gg.js +10 -0
  177. package/web/assets/chunk-3GS5O3IE-jlWIjPsl.js +231 -0
  178. package/web/assets/chunk-3YCYZ6SJ-Blq_IzZs.js +1 -0
  179. package/web/assets/chunk-6NTNNK5N-DyPc58pp.js +1 -0
  180. package/web/assets/chunk-7RZVMHOQ-BdIU-RGO.js +321 -0
  181. package/web/assets/chunk-A34GCYZU-BI2i_LdU.js +1 -0
  182. package/web/assets/chunk-AEOMTBSW-D7qjBMHW.js +1 -0
  183. package/web/assets/chunk-CilyBKbf.js +1 -0
  184. package/web/assets/chunk-DJ7UZH7F-i11ywiBl.js +1 -0
  185. package/web/assets/chunk-DKKBVRCY-1SffGI1N.js +4 -0
  186. package/web/assets/chunk-DU5LTGQ6-DaPeiwD5.js +1 -0
  187. package/web/assets/chunk-FXACKDTF-uhhi2PC2.js +159 -0
  188. package/web/assets/chunk-H3VCZNTA-IchcISDt.js +1 -0
  189. package/web/assets/chunk-HN6EAY2L-D7ZFMNrB.js +1 -0
  190. package/web/assets/chunk-KSICW3F5-C2tZmXwv.js +15 -0
  191. package/web/assets/chunk-O5ABG6QK-Bt-Km84H.js +1 -0
  192. package/web/assets/chunk-PK6DOVAG-ChlWY0BQ.js +206 -0
  193. package/web/assets/chunk-RNJOYNJ4-B724K7cW.js +1 -0
  194. package/web/assets/chunk-RWUO3TPN-DYn1XriD.js +1 -0
  195. package/web/assets/chunk-TBF5ZNIQ-DKtDz6ae.js +1 -0
  196. package/web/assets/chunk-TU3PZOEN-DE5Qhc0N.js +1 -0
  197. package/web/assets/chunk-TYMNRAUI-g1h33cq-.js +1 -0
  198. package/web/assets/chunk-VELTKBKT-C9dVN39o.js +1 -0
  199. package/web/assets/chunk-W7ZLLLMY-Du-Hb9yb.js +1 -0
  200. package/web/assets/chunk-WSB5WSVC-B123clsZ.js +1 -0
  201. package/web/assets/chunk-XGPFEOL4-BR7Eue38.js +1 -0
  202. package/web/assets/classDiagram-PPOCWD7C-BglfKSs_.js +1 -0
  203. package/web/assets/classDiagram-v2-23LJLIIU-BSzTM28O.js +1 -0
  204. package/web/assets/context-builder-CqQNhRj1.js +15 -0
  205. package/web/assets/cose-bilkent-PNC4W37J-DCfErU-A.js +1 -0
  206. package/web/assets/dagre-E77IOHMT-tDRRhDoN.js +4 -0
  207. package/web/assets/diagram-H7BISOXX-CUVHlmAh.js +43 -0
  208. package/web/assets/diagram-JC5VWROH-BoyOxulB.js +24 -0
  209. package/web/assets/diagram-LXUTUG65-osr9hb7N.js +10 -0
  210. package/web/assets/diagram-WEHSV5V5-d8nUqS39.js +24 -0
  211. package/web/assets/erDiagram-GCSMX5X6-b-IwOhPS.js +85 -0
  212. package/web/assets/flowDiagram-OTCZ4VVT-Ott2Q0AP.js +162 -0
  213. package/web/assets/ganttDiagram-MUNLMDZQ-BYtgN_5s.js +292 -0
  214. package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-CFyBIGZq.js +1 -0
  215. package/web/assets/gitGraphDiagram-3HKGZ4G3-CsVD2gn4.js +106 -0
  216. package/web/assets/index-BleGLU8S.css +2 -0
  217. package/web/assets/index-C_xK08EW.js +885 -0
  218. package/web/assets/info-OMHHGYJF-BF2H5H6G-yjAxKEzh.js +1 -0
  219. package/web/assets/infoDiagram-MN7RKWGX-DXK0Unn5.js +2 -0
  220. package/web/assets/ishikawaDiagram-YMYX4NHK-CXsnC2FA.js +70 -0
  221. package/web/assets/journeyDiagram-SO5T7YLQ-BzZ07B-X.js +139 -0
  222. package/web/assets/kanban-definition-LJHFXRCJ-C6_EpAd9.js +89 -0
  223. package/web/assets/katex-GD7MH7QM-CJiOjBBJ.js +261 -0
  224. package/web/assets/mindmap-definition-2EUWGEK5-CCYGWZ1m.js +96 -0
  225. package/web/assets/packet-4T2RLAQJ-EV4IVRXR-B8k4E3IT.js +1 -0
  226. package/web/assets/pie-ZZUOXDRM-N23DN5KN-DdvfY118.js +1 -0
  227. package/web/assets/pieDiagram-3IATQBI2-RyvRlQb4.js +30 -0
  228. package/web/assets/quadrantDiagram-E256RVCF-Bfb6sxCx.js +7 -0
  229. package/web/assets/radar-PYXPWWZC-P6TP7ZYP-1EEDC_yU.js +1 -0
  230. package/web/assets/requirementDiagram-M5DCFWZL-DjvHDyvN.js +84 -0
  231. package/web/assets/sankeyDiagram-L3NBLAOT-CBCbbl8s.js +10 -0
  232. package/web/assets/sequenceDiagram-ZOUHS735-BscU8TUR.js +157 -0
  233. package/web/assets/stateDiagram-MLPALWAM-CJusEK2D.js +1 -0
  234. package/web/assets/stateDiagram-v2-B5LQ5ZB2-DImJ3PXD.js +1 -0
  235. package/web/assets/timeline-definition-5SPVSISX-DigPA1X8.js +120 -0
  236. package/web/assets/treeView-SZITEDCU-5DXDK3XO-CzPDt3aG.js +1 -0
  237. package/web/assets/treemap-W4RFUUIX-WYLRDWKO-B9Iqiorr.js +1 -0
  238. package/web/assets/vennDiagram-IE5QUKF5-C91UkZIf.js +34 -0
  239. package/web/assets/wardley-RL74JXVD-BCRCBASE-x42Qw7hp.js +1 -0
  240. package/web/assets/wardleyDiagram-XU3VSMPF-DloBhI0U.js +20 -0
  241. package/web/assets/xychartDiagram-ZHJ5623Y-BGWJvgwI.js +7 -0
  242. package/web/index.html +21 -0
  243. package/scripts/patch-tree-sitter-swift.cjs +0 -78
@@ -13,10 +13,21 @@ import { initEmbedder, embedBatch, embedText, embeddingToArray, isEmbedderReady,
13
13
  import { generateEmbeddingText } from './text-generator.js';
14
14
  import { chunkNode, characterChunk } from './chunker.js';
15
15
  import { extractStructuralNames } from './structural-extractor.js';
16
- import { DEFAULT_EMBEDDING_CONFIG, EMBEDDABLE_LABELS, isShortLabel, LABEL_METHOD, LABELS_WITH_EXPORTED, STRUCTURAL_LABELS, collectBestChunks, } from './types.js';
16
+ import { EMBEDDABLE_LABELS, isShortLabel, LABEL_METHOD, LABELS_WITH_EXPORTED, STRUCTURAL_LABELS, collectBestChunks, } from './types.js';
17
+ import { resolveEmbeddingConfig } from './config.js';
18
+ import { rankExactEmbeddingRows } from './exact-search.js';
17
19
  import { EMBEDDING_TABLE_NAME, EMBEDDING_INDEX_NAME, CREATE_VECTOR_INDEX_QUERY, STALE_HASH_SENTINEL, } from '../lbug/schema.js';
18
20
  import { loadVectorExtension } from '../lbug/lbug-adapter.js';
21
+ import { getExactScanLimit } from '../platform/capabilities.js';
19
22
  const isDev = process.env.NODE_ENV === 'development';
23
+ const vectorUnavailableMessage = 'VECTOR extension is unavailable for this LadybugDB runtime; semantic search will use exact scan when embeddings exist.';
24
+ const ensureVectorExtensionAvailable = async () => {
25
+ const vectorReady = await loadVectorExtension();
26
+ if (!vectorReady) {
27
+ return false;
28
+ }
29
+ return true;
30
+ };
20
31
  /**
21
32
  * Bump this when the embedding text template changes in a way that should
22
33
  * invalidate existing vectors, such as metadata/header shape changes,
@@ -132,15 +143,17 @@ export const batchInsertEmbeddings = async (executeWithReusedStatement, updates)
132
143
 
133
144
  */
134
145
  const createVectorIndex = async (executeQuery) => {
135
- // Delegate to the adapter which tracks loaded state and handles DB reconnect resets
136
- await loadVectorExtension();
146
+ if (!(await ensureVectorExtensionAvailable()))
147
+ return false;
137
148
  try {
138
149
  await executeQuery(CREATE_VECTOR_INDEX_QUERY);
150
+ return true;
139
151
  }
140
152
  catch (error) {
141
153
  if (isDev) {
142
154
  console.warn('Vector index creation warning:', error);
143
155
  }
156
+ return false;
144
157
  }
145
158
  };
146
159
  /**
@@ -158,8 +171,12 @@ const createVectorIndex = async (executeQuery) => {
158
171
 
159
172
  */
160
173
  export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatement, onProgress, config = {}, skipNodeIds, context, existingEmbeddings) => {
161
- const finalConfig = { ...DEFAULT_EMBEDDING_CONFIG, ...config };
174
+ const finalConfig = resolveEmbeddingConfig(config);
175
+ let totalChunks = 0;
162
176
  try {
177
+ const vectorAvailable = await ensureVectorExtensionAvailable();
178
+ if (!vectorAvailable && isDev)
179
+ console.warn(vectorUnavailableMessage);
163
180
  // Phase 1: Load embedding model
164
181
  onProgress({
165
182
  phase: 'loading-model',
@@ -247,21 +264,25 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
247
264
  // Ensure the vector index exists even when no new nodes need embedding.
248
265
  // A prior crash or first-time incremental run may have left CodeEmbedding
249
266
  // rows without ever reaching index creation.
250
- await createVectorIndex(executeQuery);
267
+ const vectorIndexReady = await createVectorIndex(executeQuery);
251
268
  onProgress({
252
269
  phase: 'ready',
253
270
  percent: 100,
254
271
  nodesProcessed: 0,
255
272
  totalNodes: 0,
256
273
  });
257
- return;
274
+ return {
275
+ nodesProcessed: 0,
276
+ chunksProcessed: 0,
277
+ vectorIndexReady,
278
+ semanticMode: vectorIndexReady ? 'vector-index' : 'exact-scan',
279
+ };
258
280
  }
259
281
  // Phase 3: Chunk + embed nodes
260
282
  const batchSize = finalConfig.batchSize;
261
283
  const chunkSize = finalConfig.chunkSize;
262
284
  const overlap = finalConfig.overlap;
263
285
  let processedNodes = 0;
264
- let totalChunks = 0;
265
286
  onProgress({
266
287
  phase: 'embedding',
267
288
  percent: 20,
@@ -323,7 +344,7 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
323
344
  }
324
345
  }
325
346
  // Embed chunk texts in sub-batches to control memory
326
- const EMBED_SUB_BATCH = 8;
347
+ const EMBED_SUB_BATCH = finalConfig.subBatchSize;
327
348
  for (let si = 0; si < allTexts.length; si += EMBED_SUB_BATCH) {
328
349
  const subTexts = allTexts.slice(si, si + EMBED_SUB_BATCH);
329
350
  const subUpdates = allUpdates.slice(si, si + EMBED_SUB_BATCH);
@@ -363,7 +384,7 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
363
384
  if (isDev) {
364
385
  console.log('📇 Creating vector index...');
365
386
  }
366
- await createVectorIndex(executeQuery);
387
+ const vectorIndexReady = await createVectorIndex(executeQuery);
367
388
  onProgress({
368
389
  phase: 'ready',
369
390
  percent: 100,
@@ -373,6 +394,12 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
373
394
  if (isDev) {
374
395
  console.log(`✅ Embedding pipeline complete! (${totalChunks} chunks from ${totalNodes} nodes)`);
375
396
  }
397
+ return {
398
+ nodesProcessed: totalNodes,
399
+ chunksProcessed: totalChunks,
400
+ vectorIndexReady,
401
+ semanticMode: vectorIndexReady ? 'vector-index' : 'exact-scan',
402
+ };
376
403
  }
377
404
  catch (error) {
378
405
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
@@ -397,26 +424,63 @@ export const semanticSearch = async (executeQuery, query, k = 10, maxDistance =
397
424
  const queryEmbedding = await embedText(query);
398
425
  const queryVec = embeddingToArray(queryEmbedding);
399
426
  const queryVecStr = `[${queryVec.join(',')}]`;
400
- const bestChunks = await collectBestChunks(k, async (fetchLimit) => {
401
- const vectorQuery = `
402
- CALL QUERY_VECTOR_INDEX('${EMBEDDING_TABLE_NAME}', '${EMBEDDING_INDEX_NAME}',
403
- CAST(${queryVecStr} AS FLOAT[${queryVec.length}]), ${fetchLimit})
404
- YIELD node AS emb, distance
405
- WITH emb, distance
406
- WHERE distance < ${maxDistance}
407
- RETURN emb.nodeId AS nodeId, emb.chunkIndex AS chunkIndex,
408
- emb.startLine AS startLine, emb.endLine AS endLine, distance
409
- ORDER BY distance
410
- `;
411
- const embResults = await executeQuery(vectorQuery);
412
- return embResults.map((row) => ({
413
- nodeId: row.nodeId ?? row[0],
414
- chunkIndex: row.chunkIndex ?? row[1] ?? 0,
415
- startLine: row.startLine ?? row[2] ?? 0,
416
- endLine: row.endLine ?? row[3] ?? 0,
417
- distance: row.distance ?? row[4],
418
- }));
419
- });
427
+ let bestChunks = new Map();
428
+ if (await loadVectorExtension()) {
429
+ try {
430
+ bestChunks = await collectBestChunks(k, async (fetchLimit) => {
431
+ const vectorQuery = `
432
+ CALL QUERY_VECTOR_INDEX('${EMBEDDING_TABLE_NAME}', '${EMBEDDING_INDEX_NAME}',
433
+ CAST(${queryVecStr} AS FLOAT[${queryVec.length}]), ${fetchLimit})
434
+ YIELD node AS emb, distance
435
+ WITH emb, distance
436
+ WHERE distance < ${maxDistance}
437
+ RETURN emb.nodeId AS nodeId, emb.chunkIndex AS chunkIndex,
438
+ emb.startLine AS startLine, emb.endLine AS endLine, distance
439
+ ORDER BY distance
440
+ `;
441
+ const embResults = await executeQuery(vectorQuery);
442
+ return embResults.map((row) => ({
443
+ nodeId: row.nodeId ?? row[0],
444
+ chunkIndex: row.chunkIndex ?? row[1] ?? 0,
445
+ startLine: row.startLine ?? row[2] ?? 0,
446
+ endLine: row.endLine ?? row[3] ?? 0,
447
+ distance: row.distance ?? row[4],
448
+ }));
449
+ });
450
+ }
451
+ catch {
452
+ bestChunks = new Map();
453
+ }
454
+ }
455
+ if (bestChunks.size === 0) {
456
+ const countRows = await executeQuery(`MATCH (e:${EMBEDDING_TABLE_NAME}) RETURN count(e) AS cnt`);
457
+ const countRow = countRows[0];
458
+ const embeddingCount = Number(countRow?.cnt ?? countRow?.[0] ?? 0);
459
+ const exactLimit = getExactScanLimit();
460
+ if (embeddingCount > 0 && embeddingCount <= exactLimit) {
461
+ const rows = await executeQuery(`
462
+ MATCH (e:${EMBEDDING_TABLE_NAME})
463
+ RETURN e.nodeId AS nodeId, e.chunkIndex AS chunkIndex,
464
+ e.startLine AS startLine, e.endLine AS endLine, e.embedding AS embedding
465
+ `);
466
+ const exactRows = rows.map((row) => ({
467
+ nodeId: row.nodeId ?? row[0],
468
+ chunkIndex: row.chunkIndex ?? row[1] ?? 0,
469
+ startLine: row.startLine ?? row[2] ?? 0,
470
+ endLine: row.endLine ?? row[3] ?? 0,
471
+ embedding: row.embedding ?? row[4] ?? [],
472
+ }));
473
+ bestChunks = new Map(rankExactEmbeddingRows(exactRows, queryVec, k, maxDistance).map((row) => [
474
+ row.nodeId,
475
+ {
476
+ distance: row.distance,
477
+ chunkIndex: row.chunkIndex,
478
+ startLine: row.startLine,
479
+ endLine: row.endLine,
480
+ },
481
+ ]));
482
+ }
483
+ }
420
484
  if (bestChunks.size === 0) {
421
485
  return [];
422
486
  }
@@ -0,0 +1,15 @@
1
+ export interface ExactEmbeddingRow {
2
+ nodeId: string;
3
+ chunkIndex: number;
4
+ startLine: number;
5
+ endLine: number;
6
+ embedding: readonly number[];
7
+ }
8
+ export interface ExactSearchChunk {
9
+ nodeId: string;
10
+ chunkIndex: number;
11
+ startLine: number;
12
+ endLine: number;
13
+ distance: number;
14
+ }
15
+ export declare const rankExactEmbeddingRows: (rows: readonly ExactEmbeddingRow[], queryEmbedding: readonly number[], limit: number, maxDistance: number) => ExactSearchChunk[];
@@ -0,0 +1,27 @@
1
+ const cosineDistance = (a, b) => {
2
+ let dot = 0;
3
+ let aNorm = 0;
4
+ let bNorm = 0;
5
+ const len = Math.min(a.length, b.length);
6
+ for (let i = 0; i < len; i++) {
7
+ const av = a[i] ?? 0;
8
+ const bv = b[i] ?? 0;
9
+ dot += av * bv;
10
+ aNorm += av * av;
11
+ bNorm += bv * bv;
12
+ }
13
+ if (aNorm === 0 || bNorm === 0)
14
+ return 1;
15
+ return 1 - dot / (Math.sqrt(aNorm) * Math.sqrt(bNorm));
16
+ };
17
+ export const rankExactEmbeddingRows = (rows, queryEmbedding, limit, maxDistance) => rows
18
+ .map((row) => ({
19
+ nodeId: row.nodeId,
20
+ chunkIndex: row.chunkIndex,
21
+ startLine: row.startLine,
22
+ endLine: row.endLine,
23
+ distance: cosineDistance(row.embedding, queryEmbedding),
24
+ }))
25
+ .filter((row) => row.distance < maxDistance)
26
+ .sort((a, b) => a.distance - b.distance)
27
+ .slice(0, limit);
@@ -102,6 +102,10 @@ export interface EmbeddingConfig {
102
102
  modelId: string;
103
103
  /** Number of nodes to embed in each batch */
104
104
  batchSize: number;
105
+ /** Number of chunks passed to one local/HTTP embedding call */
106
+ subBatchSize: number;
107
+ /** Maximum ONNX Runtime CPU threads for local inference */
108
+ threads: number;
105
109
  /** Embedding vector dimensions */
106
110
  dimensions: number;
107
111
  /** Device to use for inference: 'auto' tries GPU first (DirectML on Windows, CUDA on Linux), falls back to CPU */
@@ -147,6 +147,8 @@ export const CHUNKING_RULES = {
147
147
  export const DEFAULT_EMBEDDING_CONFIG = {
148
148
  modelId: 'Snowflake/snowflake-arctic-embed-xs',
149
149
  batchSize: 16,
150
+ subBatchSize: 8,
151
+ threads: 2,
150
152
  dimensions: 384,
151
153
  device: 'auto',
152
154
  maxSnippetLength: 500,
@@ -14,6 +14,8 @@ const DEFAULT_MATCHING = {
14
14
  bm25_threshold: 0.7,
15
15
  embedding_threshold: 0.65,
16
16
  max_candidates_per_step: 3,
17
+ exclude_links_paths: [],
18
+ exclude_links_param_only_paths: false,
17
19
  };
18
20
  export function parseGroupConfig(yamlContent) {
19
21
  const raw = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
@@ -1,4 +1,4 @@
1
- import type { StoredContract, CrossLink } from './types.js';
1
+ import type { StoredContract, CrossLink, MatchingConfig } from './types.js';
2
2
  export interface MatchResult {
3
3
  matched: CrossLink[];
4
4
  unmatched: StoredContract[];
@@ -8,6 +8,6 @@ export interface WildcardMatchResult {
8
8
  remaining: StoredContract[];
9
9
  }
10
10
  export declare function normalizeContractId(id: string): string;
11
- export declare function buildProviderIndex(contracts: StoredContract[]): Map<string, StoredContract[]>;
12
- export declare function runExactMatch(contracts: StoredContract[], providerIndex?: Map<string, StoredContract[]>): MatchResult;
11
+ export declare function buildProviderIndex(contracts: StoredContract[], matchingConfig?: MatchingConfig): Map<string, StoredContract[]>;
12
+ export declare function runExactMatch(contracts: StoredContract[], providerIndex?: Map<string, StoredContract[]>, matchingConfig?: MatchingConfig): MatchResult;
13
13
  export declare function runWildcardMatch(unmatched: StoredContract[], providerIndex: Map<string, StoredContract[]>): WildcardMatchResult;
@@ -1,6 +1,43 @@
1
1
  function isGrpcWildcard(cid) {
2
2
  return cid.startsWith('grpc::') && cid.endsWith('/*');
3
3
  }
4
+ /**
5
+ * Detect HTTP contracts that are too generic or infrastructure-level to
6
+ * produce meaningful cross-repo links. These are still extracted (useful
7
+ * for documentation / route maps) but excluded from cross-link matching.
8
+ *
9
+ * Two categories:
10
+ * 1. Health-check / readiness endpoints — every service has one, matching
11
+ * them produces N×M false links.
12
+ * 2. Param-only paths — routes like `/{param}` or `/{param}/{param}` that
13
+ * collapse to a single catch-all after normalization. These match any
14
+ * service with a similar shape, producing false positives.
15
+ *
16
+ * Both are configurable via matching.exclude_links_paths and
17
+ * matching.exclude_links_param_only_paths in group.yaml.
18
+ */
19
+ function buildNoisyContractFilter(matchingConfig) {
20
+ const excludePaths = matchingConfig?.exclude_links_paths?.length
21
+ ? new Set(matchingConfig.exclude_links_paths.map((p) => p.replace(/\/+$/, '')))
22
+ : new Set();
23
+ const excludeParamOnly = matchingConfig?.exclude_links_param_only_paths === true;
24
+ return function isNoisyHttpContract(contractId) {
25
+ if (!contractId.startsWith('http::'))
26
+ return false;
27
+ const parts = contractId.split('::');
28
+ if (parts.length < 3)
29
+ return false;
30
+ const pathPart = parts.slice(2).join('::').replace(/\/+$/, '');
31
+ if (excludePaths.has(pathPart))
32
+ return true;
33
+ if (excludeParamOnly) {
34
+ const segments = pathPart.split('/').filter(Boolean);
35
+ if (segments.length > 0 && segments.every((s) => s === '{param}'))
36
+ return true;
37
+ }
38
+ return false;
39
+ };
40
+ }
4
41
  export function normalizeContractId(id) {
5
42
  const colonIdx = id.indexOf('::');
6
43
  if (colonIdx === -1)
@@ -74,8 +111,9 @@ function findMatchingKeys(contractId, index) {
74
111
  }
75
112
  return [];
76
113
  }
77
- export function buildProviderIndex(contracts) {
78
- const providers = contracts.filter((c) => c.role === 'provider');
114
+ export function buildProviderIndex(contracts, matchingConfig) {
115
+ const isNoisy = buildNoisyContractFilter(matchingConfig);
116
+ const providers = contracts.filter((c) => c.role === 'provider' && !isNoisy(c.contractId));
79
117
  const index = new Map();
80
118
  for (const p of providers) {
81
119
  const key = normalizeContractId(p.contractId);
@@ -85,10 +123,10 @@ export function buildProviderIndex(contracts) {
85
123
  }
86
124
  return index;
87
125
  }
88
- export function runExactMatch(contracts, providerIndex) {
89
- const index = providerIndex ?? buildProviderIndex(contracts);
90
- // Skip gRPC wildcard consumers they go to wildcard pass only
91
- const consumers = contracts.filter((c) => c.role === 'consumer' && !isGrpcWildcard(c.contractId));
126
+ export function runExactMatch(contracts, providerIndex, matchingConfig) {
127
+ const isNoisy = buildNoisyContractFilter(matchingConfig);
128
+ const index = providerIndex ?? buildProviderIndex(contracts, matchingConfig);
129
+ const consumers = contracts.filter((c) => c.role === 'consumer' && !isGrpcWildcard(c.contractId) && !isNoisy(c.contractId));
92
130
  const matched = [];
93
131
  const matchedConsumerIds = new Set();
94
132
  const matchedProviderIds = new Set();
@@ -129,6 +167,8 @@ export function runExactMatch(contracts, providerIndex) {
129
167
  const normalUnmatched = contracts.filter((c) => {
130
168
  if (isGrpcWildcard(c.contractId))
131
169
  return false; // excluded from exact, handled separately
170
+ if (isNoisy(c.contractId))
171
+ return false; // excluded from matching — don't surface as unmatched
132
172
  const id = `${c.repo}::${c.contractId}`;
133
173
  return c.role === 'provider' ? !matchedProviderIds.has(id) : !matchedConsumerIds.has(id);
134
174
  });
@@ -85,6 +85,8 @@ matching:
85
85
  bm25_threshold: 0.7
86
86
  embedding_threshold: 0.65
87
87
  max_candidates_per_step: 3
88
+ # exclude_links_paths: [/ping, /health, /healthcheck]
89
+ # exclude_links_param_only_paths: false
88
90
  `;
89
91
  await fsp.writeFile(path.join(groupDir, 'group.yaml'), template, 'utf-8');
90
92
  return groupDir;
@@ -168,7 +168,7 @@ export async function syncGroup(config, opts) {
168
168
  console.log(` manifest: ${manifestCrossLinks.length} cross-links from ${config.links.length} declared links`);
169
169
  }
170
170
  }
171
- const { matched, unmatched } = runExactMatch(autoContracts);
171
+ const { matched, unmatched } = runExactMatch(autoContracts, undefined, config.matching);
172
172
  // Dedupe cross-links. Manifest contracts participate in runExactMatch, so a
173
173
  // manifest-declared link can also emit a matchType:'exact' CrossLink with the
174
174
  // same endpoints. Prefer the manifest version — it reflects operator intent
@@ -29,6 +29,24 @@ export interface MatchingConfig {
29
29
  bm25_threshold: number;
30
30
  embedding_threshold: number;
31
31
  max_candidates_per_step: number;
32
+ /**
33
+ * HTTP paths to exclude from cross-link matching. Contracts at these paths
34
+ * are still extracted and visible in the registry, but they don't produce
35
+ * cross-repo links. Useful for health-check endpoints (`/ping`, `/health`)
36
+ * that every service exposes and would otherwise create N×M false links.
37
+ * Trailing slashes are normalized before comparison.
38
+ * @default []
39
+ */
40
+ exclude_links_paths?: string[];
41
+ /**
42
+ * When `true`, exclude HTTP routes where every path segment is `{param}`
43
+ * (e.g. `/{param}`, `/{param}/{param}`) from cross-link matching. Mixed
44
+ * routes like `/users/{param}` are not affected. These param-only routes
45
+ * collapse to a single catch-all after normalization and produce false
46
+ * positives across unrelated services.
47
+ * @default false
48
+ */
49
+ exclude_links_param_only_paths?: boolean;
32
50
  }
33
51
  export interface SymbolRef {
34
52
  filePath: string;
@@ -130,9 +130,9 @@ export declare const resolveMemberCall: (ownerType: string, methodName: string,
130
130
  * resolution via `ctx.resolve()`.
131
131
  *
132
132
  * Used for `foo()`, `doStuff()` — unqualified calls with no receiver.
133
- * Also handles Swift/Kotlin implicit constructors (`User()` without `new`)
134
- * by delegating to {@link resolveStaticCall} when the tiered pool contains
135
- * class-like targets.
133
+ * Also handles implicit constructors (`User()` without `new`) by delegating
134
+ * to {@link resolveStaticCall} when the tiered pool contains class-like
135
+ * targets.
136
136
  *
137
137
  * {@link resolveCallTarget} delegates here for `callForm === 'free'`.
138
138
  *
@@ -3,7 +3,7 @@ import { CLASS_TYPES, CALL_TARGET_TYPES, lookupMethodByOwnerWithMRO } from './mo
3
3
  * DAG stage 4 fallback: used when `selectDispatch` is absent or returns null.
4
4
  * Preserves pre-DAG dispatch semantics:
5
5
  * - 'constructor' → constructor branch
6
- * - 'free' → free branch (admits Swift/Kotlin class-target fast path)
6
+ * - 'free' → free branch (admits class-target fast path)
7
7
  * - 'member' or undefined → owner-scoped branch
8
8
  *
9
9
  * `undefined` callForm MUST route through owner-scoped (not free) so bare
@@ -613,7 +613,7 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
613
613
  if (!tree) {
614
614
  try {
615
615
  tree = parser.parse(file.content, undefined, {
616
- bufferSize: getTreeSitterBufferSize(file.content.length),
616
+ bufferSize: getTreeSitterBufferSize(file.content),
617
617
  });
618
618
  }
619
619
  catch (parseError) {
@@ -1275,41 +1275,19 @@ const disambiguateByOverloadOrArgTypes = (pool, overloadHints, preComputedArgTyp
1275
1275
  return matchCandidatesByArgTypes(pool, preComputedArgTypes);
1276
1276
  return null;
1277
1277
  };
1278
- /**
1279
- * Collapse Swift-extension duplicate Class/Struct candidates to the primary
1280
- * definition, preferring the shortest file path.
1281
- *
1282
- * Swift extensions (`extension User { ... }` in a separate file) create
1283
- * multiple `Class` nodes sharing the same symbol name — one for the primary
1284
- * declaration and one per extension file. When overload disambiguation and
1285
- * receiver narrowing both fail to converge on a single candidate, this
1286
- * heuristic picks the primary definition based on the assumption that it
1287
- * lives at the shortest file path (e.g. `User.swift` over `UserExtensions.swift`).
1288
- *
1289
- * Intentionally narrower than {@link INSTANTIABLE_CLASS_TYPES}: only `Class`
1290
- * and `Struct` are considered, not `Record`. Swift extensions only produce
1291
- * `Class` duplicates in practice, and C#/Kotlin records do not exhibit the
1292
- * same multi-file-definition pattern, so widening this set risks accidental
1293
- * dedup of legitimately distinct record types.
1294
- *
1295
- * Returns a `ResolveResult` when the heuristic fires, `null` when the
1296
- * candidate pool does not match the shape (mixed types, non-Class/Struct
1297
- * kinds, or `length <= 1`). Callers should fall through to their own null
1298
- * return when this helper returns `null`.
1299
- *
1300
- * Used by `resolveFreeCall`. Having a single source of truth prevents
1301
- * duplication if the heuristic is ever tuned.
1302
- */
1303
- const dedupSwiftExtensionCandidates = (candidates, tier) => {
1304
- if (candidates.length <= 1)
1305
- return null;
1306
- const allSameType = candidates.every((c) => c.type === candidates[0].type);
1307
- if (!allSameType)
1308
- return null;
1309
- if (candidates[0].type !== 'Class' && candidates[0].type !== 'Struct')
1278
+ const orderProviderSameNameTypeCandidates = (candidates, typeName, filePath) => {
1279
+ const language = getLanguageFromFilename(filePath);
1280
+ if (language == null)
1310
1281
  return null;
1311
- const sorted = [...candidates].sort((a, b) => a.filePath.length - b.filePath.length);
1312
- return toResolveResult(sorted[0], tier);
1282
+ return (getProvider(language).orderSameNameTypeCandidates?.({
1283
+ typeName,
1284
+ callSiteFilePath: filePath,
1285
+ candidates,
1286
+ }) ?? null);
1287
+ };
1288
+ const resolveProviderPrimaryTypeCandidate = (candidates, tier, typeName, filePath) => {
1289
+ const ordered = orderProviderSameNameTypeCandidates(candidates, typeName, filePath);
1290
+ return ordered && ordered.length > 0 ? toResolveResult(ordered[0], tier) : null;
1313
1291
  };
1314
1292
  /**
1315
1293
  * Thin dispatcher that routes a call to the appropriate specialized resolver.
@@ -1742,6 +1720,25 @@ ancestryView) => {
1742
1720
  break;
1743
1721
  }
1744
1722
  }
1723
+ if (!firstDef && !ambiguous) {
1724
+ const orderedTypeCandidates = orderProviderSameNameTypeCandidates(ctx.model.types.lookupClassByName(receiverTypeName), receiverTypeName, filePath);
1725
+ if (orderedTypeCandidates) {
1726
+ for (const candidate of orderedTypeCandidates) {
1727
+ const def = canWalkMRO
1728
+ ? lookupMethodByOwnerWithMRO(candidate.nodeId, methodName, heritageMap, ctx.model, mroStrategy, argCount)
1729
+ : ctx.model.methods.lookupMethodByOwner(candidate.nodeId, methodName, argCount);
1730
+ if (!def)
1731
+ continue;
1732
+ if (!firstDef) {
1733
+ firstDef = def;
1734
+ }
1735
+ else if (def.nodeId !== firstDef.nodeId) {
1736
+ ambiguous = true;
1737
+ break;
1738
+ }
1739
+ }
1740
+ }
1741
+ }
1745
1742
  if (!firstDef || ambiguous)
1746
1743
  return undefined;
1747
1744
  return { def: firstDef, tier: typeResolved.tier };
@@ -1791,9 +1788,9 @@ export const resolveMemberCall = (ownerType, methodName, currentFile, ctx, herit
1791
1788
  * resolution via `ctx.resolve()`.
1792
1789
  *
1793
1790
  * Used for `foo()`, `doStuff()` — unqualified calls with no receiver.
1794
- * Also handles Swift/Kotlin implicit constructors (`User()` without `new`)
1795
- * by delegating to {@link resolveStaticCall} when the tiered pool contains
1796
- * class-like targets.
1791
+ * Also handles implicit constructors (`User()` without `new`) by delegating
1792
+ * to {@link resolveStaticCall} when the tiered pool contains class-like
1793
+ * targets.
1797
1794
  *
1798
1795
  * {@link resolveCallTarget} delegates here for `callForm === 'free'`.
1799
1796
  *
@@ -1816,33 +1813,30 @@ export const resolveFreeCall = (calledName, filePath, ctx, argCount, tieredOverr
1816
1813
  if (!tiered)
1817
1814
  return null;
1818
1815
  let filteredCandidates = filterCallableCandidates(tiered.candidates, argCount, 'free');
1819
- // Class-target fast path: Swift/Kotlin `User()` — free-form call targeting a
1820
- // class. Delegates to resolveStaticCall for O(1) class + constructor lookup.
1816
+ // Class-target fast path: free-form call targeting a class. Delegates to
1817
+ // resolveStaticCall for O(1) class + constructor lookup.
1821
1818
  // The `.some()` trigger must stay aligned with `INSTANTIABLE_CLASS_TYPES` —
1822
1819
  // any type admitted here that is not in that set will cause resolveStaticCall
1823
1820
  // to return null, wasting two lookup passes per call. `Enum` is deliberately
1824
- // excluded; `Record` is included so C# records and Kotlin data classes reach
1825
- // the fast path.
1821
+ // excluded; `Record` is included so record-like class targets reach the fast
1822
+ // path.
1826
1823
  // Align with INSTANTIABLE_CLASS_TYPES by reusing the set directly rather
1827
1824
  // than enumerating literal strings. This converts an invariant that was
1828
1825
  // previously enforced by a comment ("keep this list aligned with
1829
1826
  // INSTANTIABLE_CLASS_TYPES") into one enforced structurally — any future
1830
- // extension of the set (e.g. Kotlin `object`) propagates here automatically.
1831
- // The `dedupSwiftExtensionCandidates` helper used in the tail of this
1832
- // function deliberately uses a narrower literal `'Class' | 'Struct'` check
1833
- // Swift extensions only produce Class duplicates in practice, so Record
1834
- // is excluded there by design. Do not collapse that helper into
1835
- // INSTANTIABLE_CLASS_TYPES.
1827
+ // extension of the set propagates here automatically.
1828
+ // Language providers can still choose a primary same-name type candidate in
1829
+ // the tail of this function when their grammars index one logical type
1830
+ // multiple times.
1836
1831
  const hasClassTarget = filteredCandidates.length === 0 &&
1837
1832
  tiered.candidates.some((c) => INSTANTIABLE_CLASS_TYPES.has(c.type));
1838
1833
  if (hasClassTarget) {
1839
1834
  const staticResult = resolveStaticCall(calledName, filePath, ctx, argCount, tiered);
1840
1835
  if (staticResult)
1841
1836
  return staticResult;
1842
- // Retry with constructor form: Swift/Kotlin constructor calls look like
1843
- // free function calls (no `new` keyword). If resolveStaticCall didn't
1844
- // match, re-filter with constructor form so CONSTRUCTOR_TARGET_TYPES
1845
- // applies.
1837
+ // Retry with constructor form for languages whose constructor calls look
1838
+ // like free function calls. If resolveStaticCall didn't match, re-filter
1839
+ // with constructor form so CONSTRUCTOR_TARGET_TYPES applies.
1846
1840
  //
1847
1841
  // The retry fires for every null return from `resolveStaticCall`, which
1848
1842
  // can happen for three distinct reasons — all three are handled below:
@@ -1856,9 +1850,8 @@ export const resolveFreeCall = (calledName, filePath, ctx, argCount, tieredOverr
1856
1850
  // (b) Homonym ambiguity — two or more instantiable class candidates
1857
1851
  // share the name (e.g. `User` in two files, same tier). The
1858
1852
  // retry repopulates `filteredCandidates` with both Classes and
1859
- // they flow into `dedupSwiftExtensionCandidates` below, which
1860
- // either picks the shortest-path primary or null-routes.
1861
- // Covered by the R7 Swift-extension dedup test.
1853
+ // they flow into the provider same-name candidate hook below, which
1854
+ // can pick a primary definition or null-route.
1862
1855
  //
1863
1856
  // (c) `resolveStaticCall` step 4 bailed because the tiered pool
1864
1857
  // contains ownerless `Constructor` nodes (some extractors emit
@@ -1882,11 +1875,9 @@ export const resolveFreeCall = (calledName, filePath, ctx, argCount, tieredOverr
1882
1875
  return toResolveResult(disambiguated, tiered.tier);
1883
1876
  }
1884
1877
  if (filteredCandidates.length !== 1) {
1885
- // See `dedupSwiftExtensionCandidates` shared helper, single source of
1886
- // truth for the Swift-extension same-name collision heuristic.
1887
- const deduped = dedupSwiftExtensionCandidates(filteredCandidates, tiered.tier);
1888
- if (deduped)
1889
- return deduped;
1878
+ const primary = resolveProviderPrimaryTypeCandidate(filteredCandidates, tiered.tier, calledName, filePath);
1879
+ if (primary)
1880
+ return primary;
1890
1881
  return null;
1891
1882
  }
1892
1883
  return toResolveResult(filteredCandidates[0], tiered.tier);
@@ -2034,9 +2025,11 @@ export const resolveStaticCall = (className, currentFile, ctx, argCount, tieredO
2034
2025
  // Interface / Trait / Impl). Null-route via the fall-through `return
2035
2026
  // null` — this is the dominant Codex-fix case.
2036
2027
  // length === 1 → a single instantiable candidate remains, return it.
2037
- // length > 1 → two or more instantiable classes share the name (e.g.
2038
- // homonym classes across files with no import narrowing). Fall through
2039
- // to `return null` so the caller null-routes rather than guess.
2028
+ // length > 1 → let the call-site provider choose a primary when it can
2029
+ // prove the candidates are one logical type; otherwise null-route.
2030
+ const primary = resolveProviderPrimaryTypeCandidate(instantiableCandidates, typeResolved.tier, className, currentFile);
2031
+ if (primary)
2032
+ return primary;
2040
2033
  if (instantiableCandidates.length === 1) {
2041
2034
  return toResolveResult(instantiableCandidates[0], typeResolved.tier);
2042
2035
  }
@@ -2591,7 +2584,7 @@ export const extractFetchCallsFromFiles = async (files, astCache) => {
2591
2584
  if (!tree) {
2592
2585
  try {
2593
2586
  tree = parser.parse(file.content, undefined, {
2594
- bufferSize: getTreeSitterBufferSize(file.content.length),
2587
+ bufferSize: getTreeSitterBufferSize(file.content),
2595
2588
  });
2596
2589
  }
2597
2590
  catch {
@@ -10,7 +10,8 @@ export declare const TREE_SITTER_BUFFER_SIZE: number;
10
10
  export declare const TREE_SITTER_MAX_BUFFER: number;
11
11
  /**
12
12
  * Compute adaptive buffer size for tree-sitter parsing.
13
- * Uses file size, clamped between 512 KB and 32 MB.
14
- * Previous 256 KB fixed limit silently skipped files > ~200 KB (e.g., imgui.h at 411 KB).
13
+ * Uses 2x UTF-8 byte size, clamped between 512 KB and 32 MB.
14
+ * Keeps tree-sitter's byte-sized buffer above large ASCII and multibyte sources.
15
15
  */
16
- export declare const getTreeSitterBufferSize: (contentLength: number) => number;
16
+ export declare const getTreeSitterContentByteLength: (sourceText: string) => number;
17
+ export declare const getTreeSitterBufferSize: (sourceText: string) => number;