gitnexus 1.6.3 → 1.6.4-rc.10

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 (98) hide show
  1. package/dist/_shared/index.d.ts +1 -1
  2. package/dist/_shared/index.d.ts.map +1 -1
  3. package/dist/_shared/index.js +1 -1
  4. package/dist/_shared/index.js.map +1 -1
  5. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +22 -14
  6. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
  7. package/dist/_shared/scope-resolution/finalize-algorithm.js +298 -37
  8. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  9. package/dist/_shared/scope-resolution/scope-tree.d.ts +23 -1
  10. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  11. package/dist/_shared/scope-resolution/scope-tree.js +36 -2
  12. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  13. package/dist/_shared/scope-resolution/types.d.ts +47 -3
  14. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  15. package/dist/_shared/scope-resolution/types.js +10 -2
  16. package/dist/_shared/scope-resolution/types.js.map +1 -1
  17. package/dist/core/embeddings/embedder.js +2 -1
  18. package/dist/core/ingestion/call-processor.js +2 -2
  19. package/dist/core/ingestion/constants.d.ts +4 -3
  20. package/dist/core/ingestion/constants.js +8 -3
  21. package/dist/core/ingestion/finalize-orchestrator.js +6 -3
  22. package/dist/core/ingestion/heritage-processor.js +2 -2
  23. package/dist/core/ingestion/import-processor.js +1 -1
  24. package/dist/core/ingestion/languages/csharp/captures.js +4 -1
  25. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +14 -13
  26. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +62 -50
  27. package/dist/core/ingestion/languages/python/captures.js +9 -1
  28. package/dist/core/ingestion/languages/python/index.d.ts +1 -1
  29. package/dist/core/ingestion/languages/python/index.js +1 -1
  30. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +3 -1
  31. package/dist/core/ingestion/languages/python/simple-hooks.js +8 -0
  32. package/dist/core/ingestion/languages/python.js +2 -1
  33. package/dist/core/ingestion/languages/typescript/arity-metadata.d.ts +59 -0
  34. package/dist/core/ingestion/languages/typescript/arity-metadata.js +103 -0
  35. package/dist/core/ingestion/languages/typescript/arity.d.ts +37 -0
  36. package/dist/core/ingestion/languages/typescript/arity.js +54 -0
  37. package/dist/core/ingestion/languages/typescript/cache-stats.d.ts +17 -0
  38. package/dist/core/ingestion/languages/typescript/cache-stats.js +28 -0
  39. package/dist/core/ingestion/languages/typescript/captures.d.ts +28 -0
  40. package/dist/core/ingestion/languages/typescript/captures.js +451 -0
  41. package/dist/core/ingestion/languages/typescript/import-decomposer.d.ts +49 -0
  42. package/dist/core/ingestion/languages/typescript/import-decomposer.js +371 -0
  43. package/dist/core/ingestion/languages/typescript/import-target.d.ts +50 -0
  44. package/dist/core/ingestion/languages/typescript/import-target.js +61 -0
  45. package/dist/core/ingestion/languages/typescript/index.d.ts +94 -0
  46. package/dist/core/ingestion/languages/typescript/index.js +94 -0
  47. package/dist/core/ingestion/languages/typescript/interpret.d.ts +35 -0
  48. package/dist/core/ingestion/languages/typescript/interpret.js +317 -0
  49. package/dist/core/ingestion/languages/typescript/merge-bindings.d.ts +62 -0
  50. package/dist/core/ingestion/languages/typescript/merge-bindings.js +158 -0
  51. package/dist/core/ingestion/languages/typescript/query.d.ts +77 -0
  52. package/dist/core/ingestion/languages/typescript/query.js +778 -0
  53. package/dist/core/ingestion/languages/typescript/receiver-binding.d.ts +59 -0
  54. package/dist/core/ingestion/languages/typescript/receiver-binding.js +171 -0
  55. package/dist/core/ingestion/languages/typescript/scope-resolver.d.ts +16 -0
  56. package/dist/core/ingestion/languages/typescript/scope-resolver.js +113 -0
  57. package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +71 -0
  58. package/dist/core/ingestion/languages/typescript/simple-hooks.js +131 -0
  59. package/dist/core/ingestion/languages/typescript.js +19 -0
  60. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +14 -1
  61. package/dist/core/ingestion/parsing-processor.js +3 -3
  62. package/dist/core/ingestion/registry-primary-flag.d.ts +3 -1
  63. package/dist/core/ingestion/registry-primary-flag.js +4 -1
  64. package/dist/core/ingestion/scope-extractor-bridge.d.ts +5 -2
  65. package/dist/core/ingestion/scope-extractor-bridge.js +7 -2
  66. package/dist/core/ingestion/scope-extractor.js +19 -18
  67. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +73 -11
  68. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +48 -10
  69. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +283 -14
  70. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +23 -2
  71. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +109 -37
  72. package/dist/core/ingestion/scope-resolution/passes/mro.js +3 -1
  73. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +13 -5
  74. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +11 -2
  75. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  76. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +8 -0
  77. package/dist/core/ingestion/scope-resolution/pipeline/run.js +21 -5
  78. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.d.ts +39 -0
  79. package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +65 -0
  80. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +54 -11
  81. package/dist/core/ingestion/scope-resolution/scope/walkers.js +105 -30
  82. package/dist/core/ingestion/utils/ast-helpers.d.ts +2 -0
  83. package/dist/core/ingestion/utils/ast-helpers.js +12 -0
  84. package/dist/core/ingestion/utils/env.d.ts +10 -0
  85. package/dist/core/ingestion/utils/env.js +14 -0
  86. package/dist/core/ingestion/workers/parse-worker.js +3 -3
  87. package/dist/core/lbug/lbug-adapter.d.ts +3 -4
  88. package/dist/core/lbug/lbug-adapter.js +6 -9
  89. package/dist/core/run-analyze.js +4 -6
  90. package/dist/core/search/bm25-index.d.ts +0 -17
  91. package/dist/core/search/bm25-index.js +10 -118
  92. package/dist/core/search/fts-indexes.d.ts +1 -0
  93. package/dist/core/search/fts-indexes.js +7 -0
  94. package/dist/core/search/fts-schema.d.ts +6 -0
  95. package/dist/core/search/fts-schema.js +7 -0
  96. package/dist/mcp/core/embedder.js +3 -1
  97. package/package.json +1 -1
  98. package/skills/gitnexus-cli.md +1 -1
@@ -11,17 +11,18 @@
11
11
  * field-chain resolution fails at `findClassBindingInScope('User')`
12
12
  * in the Service.cs scope chain.
13
13
  *
14
- * Implementation: after the finalize pass populates `indexes.bindings`
15
- * (from explicit `using` directives), walk each file's tree-sitter
16
- * AST for `namespace_declaration` / `file_scoped_namespace_declaration`
17
- * and `using_directive` nodes. The orchestrator hands us its
18
- * `treeCache` so files already parsed by `extractParsedFile` are
19
- * re-used instead of re-parsed — `ParsedFile`'s underlying tree is
20
- * the single source of truth. Group classes by namespace, and inject
21
- * cross-file sibling classes into each Namespace scope's finalized
22
- * bindings with `origin: 'namespace'` — a tier below `local` so a
23
- * local declaration still shadows a cross-file sibling with the same
24
- * name.
14
+ * Implementation: after the finalize pass populates immutable
15
+ * `indexes.bindings` (from explicit `using` directives), walk each
16
+ * file's tree-sitter AST for `namespace_declaration` /
17
+ * `file_scoped_namespace_declaration` and `using_directive` nodes.
18
+ * The orchestrator hands us its `treeCache` so files already parsed
19
+ * by `extractParsedFile` are re-used instead of re-parsed —
20
+ * `ParsedFile`'s underlying tree is the single source of truth.
21
+ * Group classes by namespace, and append cross-file sibling classes
22
+ * into each Namespace scope's `bindingAugmentations` bucket with
23
+ * `origin: 'namespace'`. Finalized bindings remain first in
24
+ * `lookupBindingsAt`, and local lexical `Scope.bindings` remains the
25
+ * first-tier shadowing channel.
25
26
  *
26
27
  * The tree-sitter walk is authoritative: it sees `global using static`,
27
28
  * aliased `using static X = Y.Z;`, attributed namespace declarations,
@@ -30,13 +31,17 @@
30
31
  * coincidences).
31
32
  */
32
33
  import { getCsharpParser } from './query.js';
34
+ import { getTreeSitterBufferSize } from '../../constants.js';
33
35
  /** Build a structural view of a C# file by walking the tree-sitter
34
36
  * AST. Prefers `cachedTree` (handed in via `treeCache`) so we don't
35
37
  * re-parse files the orchestrator already parsed for `extractParsedFile`;
36
38
  * falls back to a fresh parse on cache miss. Parser singleton is
37
39
  * shared across calls. */
38
40
  function extractFileStructure(content, cachedTree) {
39
- const tree = cachedTree ?? getCsharpParser().parse(content);
41
+ const tree = cachedTree ??
42
+ getCsharpParser().parse(content, undefined, {
43
+ bufferSize: getTreeSitterBufferSize(content),
44
+ });
40
45
  const namespaces = [];
41
46
  const usingStaticPaths = [];
42
47
  const visit = (node) => {
@@ -81,8 +86,8 @@ function extractFileStructure(content, cachedTree) {
81
86
  return { namespaces, usingStaticPaths };
82
87
  }
83
88
  /**
84
- * Mutate `indexes.bindings` in-place, adding cross-file sibling class
85
- * defs to each Namespace scope. Class-like defs (Class / Interface /
89
+ * Append cross-file sibling class defs to each Namespace scope's
90
+ * `bindingAugmentations` bucket. Class-like defs (Class / Interface /
86
91
  * Struct / Record / Enum) are visible cross-file; method / field
87
92
  * members are not.
88
93
  */
@@ -161,12 +166,15 @@ export function populateCsharpNamespaceSiblings(parsedFiles, indexes, inputs) {
161
166
  }
162
167
  }
163
168
  }
164
- // Inject cross-file siblings into each namespace scope's finalized
165
- // bindings. `indexes.bindings` is typed `ReadonlyMap<ScopeId, ...>`
166
- // but is a plain Map at runtime; mutating here is the established
167
- // pattern (see `propagateImportedReturnTypes` which does the same
168
- // for module-scope typeBindings).
169
- const finalized = indexes.bindings;
169
+ // Inject cross-file siblings into each namespace scope's
170
+ // post-finalize augmentation channel (per I8). The
171
+ // `indexes.bindingAugmentations` map is the dedicated mutable
172
+ // append-only buffer for post-finalize hooks: inner `BindingRef[]`
173
+ // arrays here are NEVER frozen (unlike `indexes.bindings`, which
174
+ // `materializeBindings` freezes). Walkers consult both channels
175
+ // via `lookupBindingsAt`; we never need to consult or mutate
176
+ // `indexes.bindings`.
177
+ const augmentations = indexes.bindingAugmentations;
170
178
  // Cross-namespace type-binding propagation: for each file, mirror
171
179
  // method return-type bindings from same-namespace sibling files and
172
180
  // from files in namespaces the importer `using`s, into the
@@ -268,18 +276,14 @@ export function populateCsharpNamespaceSiblings(parsedFiles, indexes, inputs) {
268
276
  const simpleName = mq.includes('.') ? mq.slice(mq.lastIndexOf('.') + 1) : mq;
269
277
  if (simpleName === '')
270
278
  continue;
271
- // Add to `indexes.bindings[moduleScope]` so
272
- // `findCallableBindingInScope` picks it up.
273
- let scopeBindings = finalized.get(moduleScope.id);
274
- if (scopeBindings === undefined) {
275
- scopeBindings = new Map();
276
- finalized.set(moduleScope.id, scopeBindings);
277
- }
278
- const existing = scopeBindings.get(simpleName) ?? [];
279
- if (existing.some((b) => b.def.nodeId === memberDef.nodeId))
279
+ // Append to the augmentation bucket for the importer's module
280
+ // scope. `findCallableBindingInScope` reads via
281
+ // `lookupBindingsAt`, which fans out across `bindings` +
282
+ // `bindingAugmentations`.
283
+ const bucketArr = getAugmentationBucket(augmentations, moduleScope.id, simpleName);
284
+ if (bucketArr.some((b) => b.def.nodeId === memberDef.nodeId))
280
285
  continue;
281
- existing.push({ def: memberDef, origin: 'import' });
282
- scopeBindings.set(simpleName, existing);
286
+ bucketArr.push({ def: memberDef, origin: 'import' });
283
287
  }
284
288
  }
285
289
  }
@@ -310,16 +314,10 @@ export function populateCsharpNamespaceSiblings(parsedFiles, indexes, inputs) {
310
314
  const simpleName = q.includes('.') ? q.slice(q.lastIndexOf('.') + 1) : q;
311
315
  if (simpleName === '')
312
316
  continue;
313
- let scopeBindings = finalized.get(moduleScope.id);
314
- if (scopeBindings === undefined) {
315
- scopeBindings = new Map();
316
- finalized.set(moduleScope.id, scopeBindings);
317
- }
318
- const existing = scopeBindings.get(simpleName) ?? [];
319
- if (existing.some((b) => b.def.nodeId === def.nodeId))
317
+ const bucketArr = getAugmentationBucket(augmentations, moduleScope.id, simpleName);
318
+ if (bucketArr.some((b) => b.def.nodeId === def.nodeId))
320
319
  continue;
321
- existing.push({ def, origin: 'namespace' });
322
- scopeBindings.set(simpleName, existing);
320
+ bucketArr.push({ def, origin: 'namespace' });
323
321
  }
324
322
  }
325
323
  }
@@ -339,11 +337,6 @@ export function populateCsharpNamespaceSiblings(parsedFiles, indexes, inputs) {
339
337
  defsByName.set(key, arr);
340
338
  }
341
339
  for (const { scopeId, filePath } of bucket.scopes) {
342
- let scopeBindings = finalized.get(scopeId);
343
- if (scopeBindings === undefined) {
344
- scopeBindings = new Map();
345
- finalized.set(scopeId, scopeBindings);
346
- }
347
340
  for (const [name, defs] of defsByName) {
348
341
  // Skip names already present locally — `origin: 'local'` in
349
342
  // scope.bindings would naturally shadow the cross-file
@@ -351,20 +344,39 @@ export function populateCsharpNamespaceSiblings(parsedFiles, indexes, inputs) {
351
344
  const local = bucket.scopes.find((s) => s.filePath === filePath)?.scope.bindings.get(name);
352
345
  if (local !== undefined && local.some((b) => b.origin === 'local'))
353
346
  continue;
354
- const existing = scopeBindings.get(name) ?? [];
347
+ let bucketArr = null;
355
348
  for (const def of defs) {
356
349
  if (def.filePath === filePath)
357
350
  continue; // don't self-reference
358
- if (existing.some((b) => b.def.nodeId === def.nodeId))
351
+ if (bucketArr === null)
352
+ bucketArr = getAugmentationBucket(augmentations, scopeId, name);
353
+ if (bucketArr.some((b) => b.def.nodeId === def.nodeId))
359
354
  continue;
360
- existing.push({ def, origin: 'namespace' });
355
+ bucketArr.push({ def, origin: 'namespace' });
361
356
  }
362
- if (existing.length > 0)
363
- scopeBindings.set(name, existing);
364
357
  }
365
358
  }
366
359
  }
367
360
  }
361
+ /** Get-or-create a mutable inner bucket inside the `bindingAugmentations`
362
+ * channel. The inner arrays here are mutable by contract (see
363
+ * `ScopeResolutionIndexes.bindingAugmentations` doc + scope-resolver I8);
364
+ * callers may `push` directly. Allocating the outer/inner Maps lazily
365
+ * keeps the augmentation footprint zero for files with no cross-file
366
+ * fanout. */
367
+ function getAugmentationBucket(augmentations, scopeId, name) {
368
+ let scopeBindings = augmentations.get(scopeId);
369
+ if (scopeBindings === undefined) {
370
+ scopeBindings = new Map();
371
+ augmentations.set(scopeId, scopeBindings);
372
+ }
373
+ let bucketArr = scopeBindings.get(name);
374
+ if (bucketArr === undefined) {
375
+ bucketArr = [];
376
+ scopeBindings.set(name, bucketArr);
377
+ }
378
+ return bucketArr;
379
+ }
368
380
  function isTypeDef(def) {
369
381
  return (def.type === 'Class' ||
370
382
  def.type === 'Interface' ||
@@ -21,6 +21,8 @@ import { getPythonParser, getPythonScopeQuery } from './query.js';
21
21
  import { synthesizeReceiverTypeBinding } from './receiver-binding.js';
22
22
  import { computePythonArityMetadata } from './arity-metadata.js';
23
23
  import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
24
+ import { getTreeSitterBufferSize } from '../../constants.js';
25
+ import { pythonFunctionDefinitionLabel } from './simple-hooks.js';
24
26
  export function emitPythonScopeCaptures(sourceText, _filePath, cachedTree) {
25
27
  // Skip the parse when the caller (parse phase's ASTCache) already
26
28
  // produced a Tree for this source. Cache miss = re-parse, same as
@@ -29,7 +31,9 @@ export function emitPythonScopeCaptures(sourceText, _filePath, cachedTree) {
29
31
  // here at the use site.
30
32
  let tree = cachedTree;
31
33
  if (tree === undefined) {
32
- tree = getPythonParser().parse(sourceText);
34
+ tree = getPythonParser().parse(sourceText, undefined, {
35
+ bufferSize: getTreeSitterBufferSize(sourceText),
36
+ });
33
37
  recordCacheMiss();
34
38
  }
35
39
  else {
@@ -83,6 +87,10 @@ export function emitPythonScopeCaptures(sourceText, _filePath, cachedTree) {
83
87
  const anchorCap = grouped['@declaration.function'];
84
88
  const fnNode = findNodeAtRange(tree.rootNode, anchorCap.range, 'function_definition');
85
89
  if (fnNode !== null) {
90
+ if (pythonFunctionDefinitionLabel(fnNode, 'Function') === 'Method') {
91
+ delete grouped['@declaration.function'];
92
+ grouped['@declaration.method'] = { ...anchorCap, name: '@declaration.method' };
93
+ }
86
94
  const arity = computePythonArityMetadata(fnNode);
87
95
  if (arity.parameterCount !== undefined) {
88
96
  grouped['@declaration.parameter-count'] = syntheticCapture('@declaration.parameter-count', fnNode, String(arity.parameterCount));
@@ -77,4 +77,4 @@ export { interpretPythonImport, interpretPythonTypeBinding } from './interpret.j
77
77
  export { pythonMergeBindings } from './merge-bindings.js';
78
78
  export { pythonArityCompatibility } from './arity.js';
79
79
  export { resolvePythonImportTarget, type PythonResolveContext } from './import-target.js';
80
- export { pythonBindingScopeFor, pythonImportOwningScope, pythonReceiverBinding, } from './simple-hooks.js';
80
+ export { pythonBindingScopeFor, pythonFunctionDefinitionLabel, pythonImportOwningScope, pythonReceiverBinding, } from './simple-hooks.js';
@@ -77,4 +77,4 @@ export { interpretPythonImport, interpretPythonTypeBinding } from './interpret.j
77
77
  export { pythonMergeBindings } from './merge-bindings.js';
78
78
  export { pythonArityCompatibility } from './arity.js';
79
79
  export { resolvePythonImportTarget } from './import-target.js';
80
- export { pythonBindingScopeFor, pythonImportOwningScope, pythonReceiverBinding, } from './simple-hooks.js';
80
+ export { pythonBindingScopeFor, pythonFunctionDefinitionLabel, pythonImportOwningScope, pythonReceiverBinding, } from './simple-hooks.js';
@@ -5,7 +5,9 @@
5
5
  * "absence == default") so reviewers don't have to re-derive the
6
6
  * analysis.
7
7
  */
8
- import type { CaptureMatch, ParsedImport, Scope, ScopeId, ScopeTree, TypeRef } from '../../../../_shared/index.js';
8
+ import type { CaptureMatch, NodeLabel, ParsedImport, Scope, ScopeId, ScopeTree, TypeRef } from '../../../../_shared/index.js';
9
+ import type { SyntaxNode } from 'tree-sitter';
10
+ export declare function pythonFunctionDefinitionLabel(functionNode: SyntaxNode, defaultLabel: NodeLabel): NodeLabel;
9
11
  /** Python has no block scope, so the central extractor's "innermost
10
12
  * enclosing scope" default is already correct: `for x in …` creates
11
13
  * `x` in the enclosing function/module scope (because we never emit a
@@ -5,6 +5,14 @@
5
5
  * "absence == default") so reviewers don't have to re-derive the
6
6
  * analysis.
7
7
  */
8
+ import { findAncestorBeforeBoundary, FUNCTION_NODE_TYPES } from '../../utils/ast-helpers.js';
9
+ const PYTHON_METHOD_CONTAINER_TYPES = new Set(['class_definition']);
10
+ export function pythonFunctionDefinitionLabel(functionNode, defaultLabel) {
11
+ if (defaultLabel !== 'Function')
12
+ return defaultLabel;
13
+ const ancestor = findAncestorBeforeBoundary(functionNode, PYTHON_METHOD_CONTAINER_TYPES, FUNCTION_NODE_TYPES);
14
+ return ancestor === null ? 'Function' : 'Method';
15
+ }
8
16
  // ─── bindingScopeFor ──────────────────────────────────────────────────────
9
17
  /** Python has no block scope, so the central extractor's "innermost
10
18
  * enclosing scope" default is already correct: `for x in …` creates
@@ -28,7 +28,7 @@ import { pythonVariableConfig } from '../variable-extractors/configs/python.js';
28
28
  import { createCallExtractor } from '../call-extractors/generic.js';
29
29
  import { pythonCallConfig } from '../call-extractors/configs/python.js';
30
30
  import { createHeritageExtractor } from '../heritage-extractors/generic.js';
31
- import { emitPythonScopeCaptures, interpretPythonImport, interpretPythonTypeBinding, pythonArityCompatibility, pythonBindingScopeFor, pythonImportOwningScope, pythonMergeBindings, pythonReceiverBinding, resolvePythonImportTarget, } from './python/index.js';
31
+ import { emitPythonScopeCaptures, pythonFunctionDefinitionLabel, interpretPythonImport, interpretPythonTypeBinding, pythonArityCompatibility, pythonBindingScopeFor, pythonImportOwningScope, pythonMergeBindings, pythonReceiverBinding, resolvePythonImportTarget, } from './python/index.js';
32
32
  const BUILT_INS = new Set([
33
33
  'print',
34
34
  'len',
@@ -75,6 +75,7 @@ export const pythonProvider = defineLanguage({
75
75
  classExtractor: createClassExtractor(pythonClassConfig),
76
76
  heritageExtractor: createHeritageExtractor(SupportedLanguages.Python),
77
77
  builtInNames: BUILT_INS,
78
+ labelOverride: pythonFunctionDefinitionLabel,
78
79
  // ── RFC #909 Ring 3: scope-based resolution hooks (RFC §5) ──────────
79
80
  // Python is the first migration. See ./python/index.ts for the
80
81
  // full per-hook rationale and the canonical capture vocabulary in
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Extract TypeScript arity metadata from a method-like tree-sitter node —
3
+ * `method_definition`, `method_signature`, `abstract_method_signature`,
4
+ * `function_declaration`, `generator_function_declaration`, or
5
+ * `function_signature` (overload signature).
6
+ *
7
+ * Reuses `typescriptMethodConfig.extractParameters` so scope-extracted defs
8
+ * carry the same arity semantics as the legacy parse-worker path:
9
+ * - Rest parameters (`...args: T[]`) collapse `parameterCount` to
10
+ * `undefined`, which `typescriptArityCompatibility` treats as
11
+ * "max unknown" — the candidate stays eligible at
12
+ * `argCount >= required` (mirrors Python `*args` / C# `params`).
13
+ * - Optional (`p?: T`) and defaulted (`p: T = …`) parameters both
14
+ * contribute to `optionalCount`;
15
+ * `requiredParameterCount = total − optionalCount`.
16
+ * - `parameterTypes` collects declared type-annotation text for
17
+ * overload narrowing; TypeScript supports function overloading
18
+ * (`function f(x: string); function f(x: number); function f(x) {}`),
19
+ * so populated types let the registry disambiguate same-arity
20
+ * siblings by declared types.
21
+ * - A literal `'params'` marker is appended for variadic methods so
22
+ * `typescriptArityCompatibility` can detect rest params without
23
+ * re-reading the AST.
24
+ *
25
+ * ## Generics stripping
26
+ *
27
+ * TypeScript parameter types frequently contain generic instantiations
28
+ * (`User<string>`, `Array<User>`, `Promise<User[]>`). For overload
29
+ * narrowing by declared type, we want the "head" name — `User`,
30
+ * `Array`, `Promise` — so `arity-metadata` applies a light strip to
31
+ * each `parameterTypes[i]`:
32
+ *
33
+ * - `Foo<Bar>` → `Foo`
34
+ * - `Foo<Bar, Baz>` → `Foo`
35
+ * - `Foo[]` → `Foo`
36
+ * - `Foo<Bar>[]` → `Foo`
37
+ * - `Foo<Bar<Baz>>` → `Foo` (greedy — strip the outermost once)
38
+ * - plain `Foo` → `Foo`
39
+ *
40
+ * We do NOT strip unions / intersections at this layer — those stay
41
+ * intact because the registry's overload narrowing is a string
42
+ * equality check; union types shouldn't match anything and we prefer
43
+ * "unknown" to "accidental match". `undefined` / `null` in unions
44
+ * (TS strict mode) is handled by `interpret.ts`'s `stripNullableUnion`
45
+ * when the name would be consumed as a receiver type — that path is
46
+ * separate from this arity-metadata path.
47
+ *
48
+ * Generic type parameters on the function itself (`function f<T>(x: T)`)
49
+ * do NOT enter here — the method extractor reads the `parameters`
50
+ * field only, which contains value parameters, not type parameters.
51
+ */
52
+ import type { SyntaxNode } from '../../utils/ast-helpers.js';
53
+ interface TsArityMetadata {
54
+ readonly parameterCount: number | undefined;
55
+ readonly requiredParameterCount: number | undefined;
56
+ readonly parameterTypes: readonly string[] | undefined;
57
+ }
58
+ export declare function computeTsArityMetadata(fnNode: SyntaxNode): TsArityMetadata;
59
+ export {};
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Extract TypeScript arity metadata from a method-like tree-sitter node —
3
+ * `method_definition`, `method_signature`, `abstract_method_signature`,
4
+ * `function_declaration`, `generator_function_declaration`, or
5
+ * `function_signature` (overload signature).
6
+ *
7
+ * Reuses `typescriptMethodConfig.extractParameters` so scope-extracted defs
8
+ * carry the same arity semantics as the legacy parse-worker path:
9
+ * - Rest parameters (`...args: T[]`) collapse `parameterCount` to
10
+ * `undefined`, which `typescriptArityCompatibility` treats as
11
+ * "max unknown" — the candidate stays eligible at
12
+ * `argCount >= required` (mirrors Python `*args` / C# `params`).
13
+ * - Optional (`p?: T`) and defaulted (`p: T = …`) parameters both
14
+ * contribute to `optionalCount`;
15
+ * `requiredParameterCount = total − optionalCount`.
16
+ * - `parameterTypes` collects declared type-annotation text for
17
+ * overload narrowing; TypeScript supports function overloading
18
+ * (`function f(x: string); function f(x: number); function f(x) {}`),
19
+ * so populated types let the registry disambiguate same-arity
20
+ * siblings by declared types.
21
+ * - A literal `'params'` marker is appended for variadic methods so
22
+ * `typescriptArityCompatibility` can detect rest params without
23
+ * re-reading the AST.
24
+ *
25
+ * ## Generics stripping
26
+ *
27
+ * TypeScript parameter types frequently contain generic instantiations
28
+ * (`User<string>`, `Array<User>`, `Promise<User[]>`). For overload
29
+ * narrowing by declared type, we want the "head" name — `User`,
30
+ * `Array`, `Promise` — so `arity-metadata` applies a light strip to
31
+ * each `parameterTypes[i]`:
32
+ *
33
+ * - `Foo<Bar>` → `Foo`
34
+ * - `Foo<Bar, Baz>` → `Foo`
35
+ * - `Foo[]` → `Foo`
36
+ * - `Foo<Bar>[]` → `Foo`
37
+ * - `Foo<Bar<Baz>>` → `Foo` (greedy — strip the outermost once)
38
+ * - plain `Foo` → `Foo`
39
+ *
40
+ * We do NOT strip unions / intersections at this layer — those stay
41
+ * intact because the registry's overload narrowing is a string
42
+ * equality check; union types shouldn't match anything and we prefer
43
+ * "unknown" to "accidental match". `undefined` / `null` in unions
44
+ * (TS strict mode) is handled by `interpret.ts`'s `stripNullableUnion`
45
+ * when the name would be consumed as a receiver type — that path is
46
+ * separate from this arity-metadata path.
47
+ *
48
+ * Generic type parameters on the function itself (`function f<T>(x: T)`)
49
+ * do NOT enter here — the method extractor reads the `parameters`
50
+ * field only, which contains value parameters, not type parameters.
51
+ */
52
+ import { typescriptMethodConfig } from '../../method-extractors/configs/typescript-javascript.js';
53
+ export function computeTsArityMetadata(fnNode) {
54
+ const params = typescriptMethodConfig.extractParameters?.(fnNode) ?? [];
55
+ let hasRest = false;
56
+ let optionalCount = 0;
57
+ const types = [];
58
+ for (const p of params) {
59
+ if (p.isVariadic)
60
+ hasRest = true;
61
+ else if (p.isOptional)
62
+ optionalCount++;
63
+ const t = p.type !== null && p.type !== undefined ? stripGenericsAndArraySuffix(p.type) : '';
64
+ types.push(t);
65
+ }
66
+ if (hasRest)
67
+ types.push('params');
68
+ const total = params.length;
69
+ const parameterCount = hasRest ? undefined : total;
70
+ const requiredParameterCount = hasRest ? undefined : total - optionalCount;
71
+ // Only emit parameterTypes when at least one param carries a non-
72
+ // empty type name. An array of all empty strings adds noise to the
73
+ // registry without aiding narrowing — callers treat absence as
74
+ // "types unknown".
75
+ const hasAnyType = types.some((t) => t !== '' && t !== 'params');
76
+ const parameterTypes = hasAnyType || hasRest ? (types.length > 0 ? types : undefined) : undefined;
77
+ return {
78
+ parameterCount,
79
+ requiredParameterCount,
80
+ parameterTypes,
81
+ };
82
+ }
83
+ /**
84
+ * Light generic + array-suffix strip used only for registry overload
85
+ * narrowing. See file-level JSDoc for the exact transformation table.
86
+ *
87
+ * Handles nesting greedily at the outermost level:
88
+ * `Foo<Bar<Baz>>[]` — strip `[]` → `Foo<Bar<Baz>>`, then strip
89
+ * outermost `<>` → `Foo`.
90
+ */
91
+ function stripGenericsAndArraySuffix(raw) {
92
+ let t = raw.trim();
93
+ // Repeatedly peel trailing `[]` pairs, then peel the outermost `<…>`
94
+ // block once. We don't loop the `<>` peel since nesting is rare and
95
+ // the head name is already reached after one peel.
96
+ while (t.endsWith('[]'))
97
+ t = t.slice(0, -2).trim();
98
+ const lt = t.indexOf('<');
99
+ if (lt > 0 && t.endsWith('>')) {
100
+ t = t.slice(0, lt).trim();
101
+ }
102
+ return t;
103
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * TypeScript arity check, accommodating rest parameters and optional
3
+ * (`p?: T`) / defaulted (`p: T = …`) parameters.
4
+ *
5
+ * TypeScript-specific semantics vs C#:
6
+ *
7
+ * - **Optional** — `p?: T` collapses to `isOptional` in the extractor
8
+ * and contributes to `optionalCount`, so `requiredParameterCount`
9
+ * excludes it. Same wire shape as a default-valued parameter.
10
+ * - **Rest** — `...args: T[]` makes `parameterCount` undefined (max
11
+ * unknown) and `parameterTypes` carries a literal `'params'` marker
12
+ * so this hook can detect variadic calls without re-reading the AST
13
+ * (mirrors the C# convention for cross-language consistency).
14
+ * - **Generics** — function-level generic type parameters (`<T, U>`)
15
+ * do NOT count toward arity; the method-extractor reads the
16
+ * `parameters` field and ignores `type_parameters`, so generic
17
+ * count never enters the metadata.
18
+ *
19
+ * The metadata shape (`parameterCount`, `requiredParameterCount`,
20
+ * `parameterTypes`) is synthesized by `arity-metadata.ts` and stored
21
+ * on `SymbolDefinition`. This file consumes that metadata.
22
+ *
23
+ * Verdicts:
24
+ * - `'compatible'` — `requiredParameterCount <= argCount <=
25
+ * parameterCount`, OR the def has rest params
26
+ * (any `argCount >= required`).
27
+ * - `'incompatible'` — argCount is below required, OR above max with
28
+ * no rest params.
29
+ * - `'unknown'` — metadata is absent / incomplete (treated as
30
+ * neutral by the registry).
31
+ *
32
+ * `'incompatible'` is a soft signal in `Registry.lookup` (penalized
33
+ * but still considered when no compatible candidate exists), per
34
+ * RFC §4.
35
+ */
36
+ import type { Callsite, SymbolDefinition } from '../../../../_shared/index.js';
37
+ export declare function typescriptArityCompatibility(def: SymbolDefinition, callsite: Callsite): 'compatible' | 'unknown' | 'incompatible';
@@ -0,0 +1,54 @@
1
+ /**
2
+ * TypeScript arity check, accommodating rest parameters and optional
3
+ * (`p?: T`) / defaulted (`p: T = …`) parameters.
4
+ *
5
+ * TypeScript-specific semantics vs C#:
6
+ *
7
+ * - **Optional** — `p?: T` collapses to `isOptional` in the extractor
8
+ * and contributes to `optionalCount`, so `requiredParameterCount`
9
+ * excludes it. Same wire shape as a default-valued parameter.
10
+ * - **Rest** — `...args: T[]` makes `parameterCount` undefined (max
11
+ * unknown) and `parameterTypes` carries a literal `'params'` marker
12
+ * so this hook can detect variadic calls without re-reading the AST
13
+ * (mirrors the C# convention for cross-language consistency).
14
+ * - **Generics** — function-level generic type parameters (`<T, U>`)
15
+ * do NOT count toward arity; the method-extractor reads the
16
+ * `parameters` field and ignores `type_parameters`, so generic
17
+ * count never enters the metadata.
18
+ *
19
+ * The metadata shape (`parameterCount`, `requiredParameterCount`,
20
+ * `parameterTypes`) is synthesized by `arity-metadata.ts` and stored
21
+ * on `SymbolDefinition`. This file consumes that metadata.
22
+ *
23
+ * Verdicts:
24
+ * - `'compatible'` — `requiredParameterCount <= argCount <=
25
+ * parameterCount`, OR the def has rest params
26
+ * (any `argCount >= required`).
27
+ * - `'incompatible'` — argCount is below required, OR above max with
28
+ * no rest params.
29
+ * - `'unknown'` — metadata is absent / incomplete (treated as
30
+ * neutral by the registry).
31
+ *
32
+ * `'incompatible'` is a soft signal in `Registry.lookup` (penalized
33
+ * but still considered when no compatible candidate exists), per
34
+ * RFC §4.
35
+ */
36
+ export function typescriptArityCompatibility(def, callsite) {
37
+ const max = def.parameterCount;
38
+ const min = def.requiredParameterCount;
39
+ if (max === undefined && min === undefined)
40
+ return 'unknown';
41
+ const argCount = callsite.arity;
42
+ if (!Number.isFinite(argCount) || argCount < 0)
43
+ return 'unknown';
44
+ // Variadic detection: the `arity-metadata` synthesizer appends the
45
+ // literal `'params'` marker to `parameterTypes` when the def has a
46
+ // rest parameter, to avoid re-parsing the AST here.
47
+ const hasRest = def.parameterTypes !== undefined &&
48
+ def.parameterTypes.some((t) => t === 'params' || t.startsWith('params '));
49
+ if (min !== undefined && argCount < min)
50
+ return 'incompatible';
51
+ if (max !== undefined && argCount > max && !hasRest)
52
+ return 'incompatible';
53
+ return 'compatible';
54
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Dev-mode counters for the TypeScript cross-phase scope-captures parse cache.
3
+ *
4
+ * Gated by `PROF_SCOPE_RESOLUTION=1`. In production the module-level `PROF`
5
+ * constant is `false` and V8 folds every increment site into dead code, so the
6
+ * hot path in `captures.ts` stays branch-free.
7
+ *
8
+ * Extracted from `captures.ts` so the production hot-path module doesn't carry
9
+ * a module-global counter and its reset/export surface.
10
+ */
11
+ export declare function recordCacheHit(): void;
12
+ export declare function recordCacheMiss(): void;
13
+ export declare function getTypescriptCaptureCacheStats(): {
14
+ hits: number;
15
+ misses: number;
16
+ };
17
+ export declare function resetTypescriptCaptureCacheStats(): void;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Dev-mode counters for the TypeScript cross-phase scope-captures parse cache.
3
+ *
4
+ * Gated by `PROF_SCOPE_RESOLUTION=1`. In production the module-level `PROF`
5
+ * constant is `false` and V8 folds every increment site into dead code, so the
6
+ * hot path in `captures.ts` stays branch-free.
7
+ *
8
+ * Extracted from `captures.ts` so the production hot-path module doesn't carry
9
+ * a module-global counter and its reset/export surface.
10
+ */
11
+ const PROF = process.env.PROF_SCOPE_RESOLUTION === '1';
12
+ let CACHE_HITS = 0;
13
+ let CACHE_MISSES = 0;
14
+ export function recordCacheHit() {
15
+ if (PROF)
16
+ CACHE_HITS++;
17
+ }
18
+ export function recordCacheMiss() {
19
+ if (PROF)
20
+ CACHE_MISSES++;
21
+ }
22
+ export function getTypescriptCaptureCacheStats() {
23
+ return { hits: CACHE_HITS, misses: CACHE_MISSES };
24
+ }
25
+ export function resetTypescriptCaptureCacheStats() {
26
+ CACHE_HITS = 0;
27
+ CACHE_MISSES = 0;
28
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `emitScopeCaptures` for TypeScript.
3
+ *
4
+ * Drives the TypeScript scope query against tree-sitter-typescript and groups
5
+ * raw matches into `CaptureMatch[]` for the central extractor. Layers
6
+ * synthesized streams on top:
7
+ *
8
+ * 1. **Import decomposition** — each `import_statement` / re-export is
9
+ * re-emitted with `@import.kind/source/name/alias/typeOnly` markers so
10
+ * `interpretTsImport` can recover the `ParsedImport` shape without
11
+ * re-parsing raw text (see `import-decomposer.ts`). Unit 2 adds this;
12
+ * until then, raw `@import.statement` matches flow through as-is.
13
+ * 2. **Dynamic imports** — `import('./m')` is re-emitted as a
14
+ * decomposed `@import.statement` with `@import.kind=dynamic` so the
15
+ * central extractor treats it uniformly with static imports.
16
+ * 3. **Function-decl arity metadata** (Unit 5) — `@declaration.parameter-count`
17
+ * / `@declaration.required-parameter-count` / `@declaration.parameter-types`
18
+ * synthesized onto function-like declarations so the registry can narrow
19
+ * overloads.
20
+ * 4. **Callsite arity metadata** (Unit 5) — `@reference.arity` /
21
+ * `@reference.parameter-types` on every callsite.
22
+ * 5. **Receiver-binding synthesis** (Unit 3) — `this` type anchors on
23
+ * instance methods, with arrow-function lexical-this walk-up.
24
+ *
25
+ * Pure given the input source text. No I/O, no globals consulted.
26
+ */
27
+ import type { CaptureMatch } from '../../../../_shared/index.js';
28
+ export declare function emitTsScopeCaptures(sourceText: string, filePath: string, cachedTree?: unknown): readonly CaptureMatch[];