gitnexus 1.6.3-rc.40 → 1.6.3-rc.41

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 (81) hide show
  1. package/README.md +16 -0
  2. package/dist/_shared/scope-resolution/parsed-file.d.ts +12 -0
  3. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  4. package/dist/_shared/scope-resolution/parsed-file.js +12 -0
  5. package/dist/_shared/scope-resolution/parsed-file.js.map +1 -1
  6. package/dist/_shared/scope-resolution/reference-site.d.ts +8 -0
  7. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
  8. package/dist/core/ingestion/language-provider.d.ts +3 -2
  9. package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
  10. package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
  11. package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
  12. package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
  13. package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
  14. package/dist/core/ingestion/languages/csharp/arity.js +37 -0
  15. package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
  16. package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
  17. package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
  18. package/dist/core/ingestion/languages/csharp/captures.js +249 -0
  19. package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
  20. package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
  21. package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
  22. package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
  23. package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
  24. package/dist/core/ingestion/languages/csharp/index.js +82 -0
  25. package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
  26. package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
  27. package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
  28. package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
  29. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
  30. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
  31. package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
  32. package/dist/core/ingestion/languages/csharp/query.js +515 -0
  33. package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
  34. package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
  35. package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
  36. package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
  37. package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
  38. package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
  39. package/dist/core/ingestion/languages/csharp.js +14 -0
  40. package/dist/core/ingestion/languages/python/import-target.d.ts +4 -0
  41. package/dist/core/ingestion/languages/python/import-target.js +4 -0
  42. package/dist/core/ingestion/languages/python/merge-bindings.d.ts +2 -2
  43. package/dist/core/ingestion/languages/python/merge-bindings.js +1 -1
  44. package/dist/core/ingestion/languages/python/query.d.ts +1 -6
  45. package/dist/core/ingestion/languages/python/query.js +1 -6
  46. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +3 -3
  47. package/dist/core/ingestion/languages/python/receiver-binding.js +3 -3
  48. package/dist/core/ingestion/languages/python/scope-resolver.js +15 -15
  49. package/dist/core/ingestion/languages/python.js +2 -2
  50. package/dist/core/ingestion/model/method-registry.d.ts +9 -0
  51. package/dist/core/ingestion/model/method-registry.js +4 -0
  52. package/dist/core/ingestion/model/semantic-model.d.ts +39 -0
  53. package/dist/core/ingestion/model/semantic-model.js +39 -0
  54. package/dist/core/ingestion/registry-primary-flag.js +1 -0
  55. package/dist/core/ingestion/scope-extractor.js +20 -0
  56. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +216 -12
  57. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +149 -12
  58. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +1 -1
  59. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +9 -2
  60. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
  61. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +11 -0
  62. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +12 -0
  63. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +10 -0
  64. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +61 -0
  65. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +3 -1
  66. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +73 -3
  67. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
  68. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
  69. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +8 -4
  70. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +164 -37
  71. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +8 -1
  72. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
  73. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
  74. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +5 -1
  75. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +11 -0
  76. package/dist/core/ingestion/scope-resolution/pipeline/run.js +33 -7
  77. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +36 -9
  78. package/dist/core/ingestion/scope-resolution/scope/walkers.js +86 -24
  79. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +39 -33
  80. package/dist/core/ingestion/scope-resolution/workspace-index.js +39 -87
  81. package/package.json +1 -1
package/README.md CHANGED
@@ -155,6 +155,7 @@ gitnexus analyze --force # Force full re-index
155
155
  gitnexus analyze --embeddings # Enable embedding generation (slower, better search)
156
156
  gitnexus analyze --skip-agents-md # Preserve custom AGENTS.md/CLAUDE.md gitnexus section edits
157
157
  gitnexus analyze --verbose # Log skipped files when parsers are unavailable
158
+ gitnexus analyze --max-file-size 1024 # Skip files larger than N KB (default: 512, cap: 32768)
158
159
  gitnexus mcp # Start MCP server (stdio) — serves all indexed repos
159
160
  gitnexus serve # Start local HTTP server (multi-repo) for web UI
160
161
  gitnexus index # Register an existing .gitnexus/ folder into the global registry
@@ -307,6 +308,21 @@ echo "vendor/" >> .gitnexusignore
307
308
  echo "dist/" >> .gitnexusignore
308
309
  ```
309
310
 
311
+ ### Large files are being skipped
312
+
313
+ By default the walker skips files larger than **512 KB** (see log line `Skipped N large files (>512KB)`). Raise the threshold via either the CLI flag or the environment variable — both accept a value in **KB**:
314
+
315
+ ```bash
316
+ # CLI flag (takes precedence over the env var)
317
+ npx gitnexus analyze --max-file-size 2048 # skip only files > 2 MB
318
+
319
+ # Environment variable (persists across commands)
320
+ export GITNEXUS_MAX_FILE_SIZE=2048
321
+ npx gitnexus analyze
322
+ ```
323
+
324
+ Values above **32768 KB (32 MB)** are clamped to the tree-sitter parser ceiling; invalid values fall back to the 512 KB default with a one-time warning. When an override is active, `analyze` prints the effective threshold in its startup banner (e.g. `GITNEXUS_MAX_FILE_SIZE: effective threshold 2048KB (default 512KB)`).
325
+
310
326
  ## Privacy
311
327
 
312
328
  - All processing happens locally on your machine
@@ -37,6 +37,18 @@
37
37
  * `localDefs`. A `ParsedFile` is trivially convertible to a `FinalizeFile`
38
38
  * by picking those four fields, so the finalize orchestrator threads
39
39
  * ParsedFile through to the shared algorithm without shape-shifting.
40
+ *
41
+ * ## Source-of-truth invariant
42
+ *
43
+ * `ParsedFile` is the single semantic model consumed by both the legacy
44
+ * DAG (`gitnexus/src/core/ingestion/` outside `scope-resolution/`) and
45
+ * the scope-resolution pipeline (`gitnexus/src/core/ingestion/scope-resolution/`).
46
+ * Downstream passes MUST NOT build a parallel parse representation; if
47
+ * a pass needs AST-level facts that `ParsedFile` doesn't expose, it
48
+ * should reuse the orchestrator's `treeCache` rather than re-invoke
49
+ * `parser.parse(...)` on its own. See the
50
+ * `ScopeResolver` contract (`gitnexus/src/core/ingestion/scope-resolution/contract/scope-resolver.ts`)
51
+ * for the full list of invariants downstream consumers rely on.
40
52
  */
41
53
  import type { Scope, ScopeId } from './types.js';
42
54
  import type { ParsedImport } from './types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"parsed-file.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/parsed-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,SAAS,YAAY,EAAE,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,QAAQ,CAAC,cAAc,EAAE,SAAS,aAAa,EAAE,CAAC;CACnD"}
1
+ {"version":3,"file":"parsed-file.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/parsed-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,SAAS,YAAY,EAAE,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,QAAQ,CAAC,cAAc,EAAE,SAAS,aAAa,EAAE,CAAC;CACnD"}
@@ -37,6 +37,18 @@
37
37
  * `localDefs`. A `ParsedFile` is trivially convertible to a `FinalizeFile`
38
38
  * by picking those four fields, so the finalize orchestrator threads
39
39
  * ParsedFile through to the shared algorithm without shape-shifting.
40
+ *
41
+ * ## Source-of-truth invariant
42
+ *
43
+ * `ParsedFile` is the single semantic model consumed by both the legacy
44
+ * DAG (`gitnexus/src/core/ingestion/` outside `scope-resolution/`) and
45
+ * the scope-resolution pipeline (`gitnexus/src/core/ingestion/scope-resolution/`).
46
+ * Downstream passes MUST NOT build a parallel parse representation; if
47
+ * a pass needs AST-level facts that `ParsedFile` doesn't expose, it
48
+ * should reuse the orchestrator's `treeCache` rather than re-invoke
49
+ * `parser.parse(...)` on its own. See the
50
+ * `ScopeResolver` contract (`gitnexus/src/core/ingestion/scope-resolution/contract/scope-resolver.ts`)
51
+ * for the full list of invariants downstream consumers rely on.
40
52
  */
41
53
  export {};
42
54
  //# sourceMappingURL=parsed-file.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parsed-file.js","sourceRoot":"","sources":["../../src/scope-resolution/parsed-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG"}
1
+ {"version":3,"file":"parsed-file.js","sourceRoot":"","sources":["../../src/scope-resolution/parsed-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG"}
@@ -63,5 +63,13 @@ export interface ReferenceSite {
63
63
  };
64
64
  /** Argument count at the call site; used by `provider.arityCompatibility`. */
65
65
  readonly arity?: number;
66
+ /**
67
+ * Inferred argument types at the call site, one per argument. An
68
+ * empty-string entry means "unknown" — consumers narrowing overload
69
+ * candidates treat unknown as any-match. Populated by languages
70
+ * that can derive types from literals / constructor expressions
71
+ * (C#: `42` → `'int'`, `"alice"` → `'string'`).
72
+ */
73
+ readonly argumentTypes?: readonly string[];
66
74
  }
67
75
  //# sourceMappingURL=reference-site.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reference-site.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/reference-site.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,MAAM,GACN,OAAO,GACP,gBAAgB,GAChB,UAAU,GACV,YAAY,CAAC;AAEjB;;;;;;;;;GASG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;AAEnE,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,kCAAkC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB"}
1
+ {"version":3,"file":"reference-site.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/reference-site.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,MAAM,GACN,OAAO,GACP,gBAAgB,GAChB,UAAU,GACV,YAAY,CAAC;AAEjB;;;;;;;;;GASG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;AAEnE,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,kCAAkC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C"}
@@ -224,8 +224,9 @@ interface LanguageProviderConfig {
224
224
  readonly builtInNames?: ReadonlySet<string>;
225
225
  /**
226
226
  * Emit scope captures from raw source, **pre-grouped per tree-sitter
227
- * query match**. Tree-sitter-based providers run a `scopes.scm` query
228
- * and emit one `CaptureMatch` per query match; standalone providers
227
+ * query match**. Tree-sitter-based providers run a scope query
228
+ * (embedded as a string constant in each language's `query.ts`) and
229
+ * emit one `CaptureMatch` per query match; standalone providers
229
230
  * (COBOL) emit matches from a regex tagger. The return shape is
230
231
  * parser-agnostic: the central `ScopeExtractor` consumes
231
232
  * `CaptureMatch[]` without knowing which parser produced them.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * C# collection-accessor unwrapping.
3
+ *
4
+ * When the compound-receiver resolver encounters a trailing
5
+ * `.Values` / `.Keys` on a dotted member-access chain, it calls the
6
+ * provider's `unwrapCollectionAccessor` hook to find the element
7
+ * type. This module supplies the C# implementation — recognizing
8
+ * Dictionary-family generics and returning the value or key type.
9
+ *
10
+ * Other languages (Python, Java, TypeScript) use method-call syntax
11
+ * for the same access (`.values()` / `.keys()`), which the compound-
12
+ * receiver's call-expression branch already handles; they leave this
13
+ * hook undefined.
14
+ */
15
+ /**
16
+ * Resolve `data.Values` / `data.Keys` on a Dictionary-like receiver
17
+ * to its element-type simple name. Returns `undefined` for any
18
+ * receiver / accessor combination we don't recognize, letting the
19
+ * compound-receiver pass fall through to the regular field walk.
20
+ */
21
+ export declare function unwrapCsharpCollectionAccessor(receiverType: string, accessor: string): string | undefined;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * C# collection-accessor unwrapping.
3
+ *
4
+ * When the compound-receiver resolver encounters a trailing
5
+ * `.Values` / `.Keys` on a dotted member-access chain, it calls the
6
+ * provider's `unwrapCollectionAccessor` hook to find the element
7
+ * type. This module supplies the C# implementation — recognizing
8
+ * Dictionary-family generics and returning the value or key type.
9
+ *
10
+ * Other languages (Python, Java, TypeScript) use method-call syntax
11
+ * for the same access (`.values()` / `.keys()`), which the compound-
12
+ * receiver's call-expression branch already handles; they leave this
13
+ * hook undefined.
14
+ */
15
+ /** Extract (K, V) from `Dictionary<K, V>` / `IDictionary<K, V>` /
16
+ * `IReadOnlyDictionary<K, V>` / `SortedDictionary<K, V>` /
17
+ * `ConcurrentDictionary<K, V>` / `ImmutableDictionary<K, V>`.
18
+ * Returns undefined if the type name doesn't match or the argument
19
+ * list isn't exactly two top-level args. */
20
+ function extractDictionaryArgs(rawName) {
21
+ const match = rawName.match(/^(?:[A-Za-z_][A-Za-z0-9_.]*\.)?(?:Dictionary|IDictionary|IReadOnlyDictionary|SortedDictionary|ConcurrentDictionary|ImmutableDictionary)<(.+)>$/);
22
+ if (match === null)
23
+ return undefined;
24
+ const inner = match[1];
25
+ // Split on the top-level comma (tolerate nested `<...>`).
26
+ let depth = 0;
27
+ let commaIdx = -1;
28
+ for (let i = 0; i < inner.length; i++) {
29
+ const ch = inner[i];
30
+ if (ch === '<')
31
+ depth++;
32
+ else if (ch === '>')
33
+ depth--;
34
+ else if (ch === ',' && depth === 0) {
35
+ commaIdx = i;
36
+ break;
37
+ }
38
+ }
39
+ if (commaIdx === -1)
40
+ return undefined;
41
+ return { key: inner.slice(0, commaIdx).trim(), value: inner.slice(commaIdx + 1).trim() };
42
+ }
43
+ /**
44
+ * Resolve `data.Values` / `data.Keys` on a Dictionary-like receiver
45
+ * to its element-type simple name. Returns `undefined` for any
46
+ * receiver / accessor combination we don't recognize, letting the
47
+ * compound-receiver pass fall through to the regular field walk.
48
+ */
49
+ export function unwrapCsharpCollectionAccessor(receiverType, accessor) {
50
+ if (accessor !== 'Values' && accessor !== 'Keys')
51
+ return undefined;
52
+ const args = extractDictionaryArgs(receiverType);
53
+ if (args === undefined)
54
+ return undefined;
55
+ return accessor === 'Values' ? args.value : args.key;
56
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Extract C# arity metadata from a method-like tree-sitter node —
3
+ * `method_declaration`, `constructor_declaration`, `destructor_declaration`,
4
+ * `operator_declaration`, `conversion_operator_declaration`, or
5
+ * `local_function_statement`.
6
+ *
7
+ * Reuses `csharpMethodConfig.extractParameters` so scope-extracted defs
8
+ * carry the same arity semantics as the legacy parse-worker path:
9
+ * - `params` variadic collapses `parameterCount` to `undefined`,
10
+ * which `csharpArityCompatibility` then treats as "max unknown" —
11
+ * the candidate stays eligible at `argCount >= required`.
12
+ * - Defaulted parameters (`= expr`) contribute to `optionalCount`;
13
+ * `requiredParameterCount = total − optionalCount`.
14
+ * - `parameterTypes` collects declared type names (with `ref`/`out`/
15
+ * `in` prefix) for overload narrowing; a literal `'params'` marker
16
+ * is appended for variadic methods so `csharpArityCompatibility`
17
+ * can detect them without re-reading the AST.
18
+ */
19
+ import type { SyntaxNode } from '../../utils/ast-helpers.js';
20
+ interface CsharpArityMetadata {
21
+ readonly parameterCount: number | undefined;
22
+ readonly requiredParameterCount: number | undefined;
23
+ readonly parameterTypes: readonly string[] | undefined;
24
+ }
25
+ export declare function computeCsharpArityMetadata(fnNode: SyntaxNode): CsharpArityMetadata;
26
+ export {};
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Extract C# arity metadata from a method-like tree-sitter node —
3
+ * `method_declaration`, `constructor_declaration`, `destructor_declaration`,
4
+ * `operator_declaration`, `conversion_operator_declaration`, or
5
+ * `local_function_statement`.
6
+ *
7
+ * Reuses `csharpMethodConfig.extractParameters` so scope-extracted defs
8
+ * carry the same arity semantics as the legacy parse-worker path:
9
+ * - `params` variadic collapses `parameterCount` to `undefined`,
10
+ * which `csharpArityCompatibility` then treats as "max unknown" —
11
+ * the candidate stays eligible at `argCount >= required`.
12
+ * - Defaulted parameters (`= expr`) contribute to `optionalCount`;
13
+ * `requiredParameterCount = total − optionalCount`.
14
+ * - `parameterTypes` collects declared type names (with `ref`/`out`/
15
+ * `in` prefix) for overload narrowing; a literal `'params'` marker
16
+ * is appended for variadic methods so `csharpArityCompatibility`
17
+ * can detect them without re-reading the AST.
18
+ */
19
+ import { csharpMethodConfig } from '../../method-extractors/configs/csharp.js';
20
+ export function computeCsharpArityMetadata(fnNode) {
21
+ const params = csharpMethodConfig.extractParameters?.(fnNode) ?? [];
22
+ let hasVariadic = false;
23
+ let optionalCount = 0;
24
+ const types = [];
25
+ for (const p of params) {
26
+ if (p.isVariadic)
27
+ hasVariadic = true;
28
+ else if (p.isOptional)
29
+ optionalCount++;
30
+ if (p.type !== null)
31
+ types.push(p.type);
32
+ }
33
+ if (hasVariadic)
34
+ types.push('params');
35
+ const total = params.length;
36
+ // `params int[] args` declares one formal param but accepts any arg
37
+ // count ≥ required — mirror Python's treatment of `*args` and leave
38
+ // `parameterCount` undefined so the registry treats max as unknown.
39
+ const parameterCount = hasVariadic ? undefined : total;
40
+ const requiredParameterCount = hasVariadic ? undefined : total - optionalCount;
41
+ return {
42
+ parameterCount,
43
+ requiredParameterCount,
44
+ parameterTypes: types.length > 0 ? types : undefined,
45
+ };
46
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * C# arity check, accommodating `params` variadic and default parameters.
3
+ *
4
+ * The `def` metadata we care about (synthesized by `arity-metadata.ts`):
5
+ * - `parameterCount` — total formal parameters; `undefined`
6
+ * when the method has `params T[]` variadic.
7
+ * - `requiredParameterCount` — min required (excludes defaulted params
8
+ * and `params` variadic).
9
+ * - `parameterTypes` — declared type strings; contains the
10
+ * literal `'params'` when the method is
11
+ * variadic.
12
+ *
13
+ * Verdicts:
14
+ * - `'compatible'` — `requiredParameterCount <= argCount <= parameterCount`,
15
+ * OR the def takes `params` (then any `argCount >= required`).
16
+ * - `'incompatible'` — argCount is below required, OR above max with no variadic.
17
+ * - `'unknown'` — metadata is absent / incomplete.
18
+ *
19
+ * `'incompatible'` is a soft signal in `Registry.lookup` (penalized but
20
+ * still considered when no compatible candidate exists), per RFC §4.
21
+ */
22
+ import type { Callsite, SymbolDefinition } from '../../../../_shared/index.js';
23
+ export declare function csharpArityCompatibility(def: SymbolDefinition, callsite: Callsite): 'compatible' | 'unknown' | 'incompatible';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * C# arity check, accommodating `params` variadic and default parameters.
3
+ *
4
+ * The `def` metadata we care about (synthesized by `arity-metadata.ts`):
5
+ * - `parameterCount` — total formal parameters; `undefined`
6
+ * when the method has `params T[]` variadic.
7
+ * - `requiredParameterCount` — min required (excludes defaulted params
8
+ * and `params` variadic).
9
+ * - `parameterTypes` — declared type strings; contains the
10
+ * literal `'params'` when the method is
11
+ * variadic.
12
+ *
13
+ * Verdicts:
14
+ * - `'compatible'` — `requiredParameterCount <= argCount <= parameterCount`,
15
+ * OR the def takes `params` (then any `argCount >= required`).
16
+ * - `'incompatible'` — argCount is below required, OR above max with no variadic.
17
+ * - `'unknown'` — metadata is absent / incomplete.
18
+ *
19
+ * `'incompatible'` is a soft signal in `Registry.lookup` (penalized but
20
+ * still considered when no compatible candidate exists), per RFC §4.
21
+ */
22
+ export function csharpArityCompatibility(def, callsite) {
23
+ const max = def.parameterCount;
24
+ const min = def.requiredParameterCount;
25
+ if (max === undefined && min === undefined)
26
+ return 'unknown';
27
+ const argCount = callsite.arity;
28
+ if (!Number.isFinite(argCount) || argCount < 0)
29
+ return 'unknown';
30
+ const hasVarArgs = def.parameterTypes !== undefined &&
31
+ def.parameterTypes.some((t) => t === 'params' || t.startsWith('params '));
32
+ if (min !== undefined && argCount < min)
33
+ return 'incompatible';
34
+ if (max !== undefined && argCount > max && !hasVarArgs)
35
+ return 'incompatible';
36
+ return 'compatible';
37
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Dev-mode counters for the cross-phase scope-captures parse cache
3
+ * (C# mirror of `languages/python/cache-stats.ts`).
4
+ *
5
+ * Gated by `PROF_SCOPE_RESOLUTION=1`. Production builds fold every
6
+ * increment into dead code via the module-level `PROF` constant, so
7
+ * the hot path in `captures.ts` stays branch-free.
8
+ */
9
+ export declare function recordCacheHit(): void;
10
+ export declare function recordCacheMiss(): void;
11
+ export declare function getCsharpCaptureCacheStats(): {
12
+ hits: number;
13
+ misses: number;
14
+ };
15
+ export declare function resetCsharpCaptureCacheStats(): void;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Dev-mode counters for the cross-phase scope-captures parse cache
3
+ * (C# mirror of `languages/python/cache-stats.ts`).
4
+ *
5
+ * Gated by `PROF_SCOPE_RESOLUTION=1`. Production builds fold every
6
+ * increment into dead code via the module-level `PROF` constant, so
7
+ * the hot path in `captures.ts` stays branch-free.
8
+ */
9
+ const PROF = process.env.PROF_SCOPE_RESOLUTION === '1';
10
+ let CACHE_HITS = 0;
11
+ let CACHE_MISSES = 0;
12
+ export function recordCacheHit() {
13
+ if (PROF)
14
+ CACHE_HITS++;
15
+ }
16
+ export function recordCacheMiss() {
17
+ if (PROF)
18
+ CACHE_MISSES++;
19
+ }
20
+ export function getCsharpCaptureCacheStats() {
21
+ return { hits: CACHE_HITS, misses: CACHE_MISSES };
22
+ }
23
+ export function resetCsharpCaptureCacheStats() {
24
+ CACHE_HITS = 0;
25
+ CACHE_MISSES = 0;
26
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `emitScopeCaptures` for C#.
3
+ *
4
+ * Drives the C# scope query against tree-sitter-c-sharp and groups raw
5
+ * matches into `CaptureMatch[]` for the central extractor. Layers one
6
+ * synthesized stream on top today:
7
+ *
8
+ * 1. **Decomposed using directives** — each `using_directive` is
9
+ * re-emitted with `@import.kind/source/name/alias` markers so
10
+ * `interpretCsharpImport` can recover the ParsedImport shape
11
+ * without re-parsing raw text (see `import-decomposer.ts`).
12
+ *
13
+ * Receiver-binding synthesis (`this` / `base` type anchors) and arity
14
+ * metadata synthesis (Unit 5) layer on top later.
15
+ *
16
+ * Pure given the input source text. No I/O, no globals consulted.
17
+ */
18
+ import type { CaptureMatch } from '../../../../_shared/index.js';
19
+ export declare function emitCsharpScopeCaptures(sourceText: string, _filePath: string, cachedTree?: unknown): readonly CaptureMatch[];
@@ -0,0 +1,249 @@
1
+ /**
2
+ * `emitScopeCaptures` for C#.
3
+ *
4
+ * Drives the C# scope query against tree-sitter-c-sharp and groups raw
5
+ * matches into `CaptureMatch[]` for the central extractor. Layers one
6
+ * synthesized stream on top today:
7
+ *
8
+ * 1. **Decomposed using directives** — each `using_directive` is
9
+ * re-emitted with `@import.kind/source/name/alias` markers so
10
+ * `interpretCsharpImport` can recover the ParsedImport shape
11
+ * without re-parsing raw text (see `import-decomposer.ts`).
12
+ *
13
+ * Receiver-binding synthesis (`this` / `base` type anchors) and arity
14
+ * metadata synthesis (Unit 5) layer on top later.
15
+ *
16
+ * Pure given the input source text. No I/O, no globals consulted.
17
+ */
18
+ import { findNodeAtRange, nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
19
+ import { splitUsingDirective } from './import-decomposer.js';
20
+ import { computeCsharpArityMetadata } from './arity-metadata.js';
21
+ import { synthesizeCsharpReceiverBinding } from './receiver-binding.js';
22
+ import { getCsharpParser, getCsharpScopeQuery } from './query.js';
23
+ import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
24
+ /** Declaration anchors that carry function-like arity metadata. */
25
+ const FUNCTION_DECL_TAGS = [
26
+ '@declaration.method',
27
+ '@declaration.constructor',
28
+ '@declaration.function',
29
+ ];
30
+ /** tree-sitter-c-sharp node types that the method extractor accepts. */
31
+ const FUNCTION_NODE_TYPES = [
32
+ 'method_declaration',
33
+ 'constructor_declaration',
34
+ 'destructor_declaration',
35
+ 'operator_declaration',
36
+ 'conversion_operator_declaration',
37
+ 'local_function_statement',
38
+ ];
39
+ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
40
+ // Skip the parse when the caller (parse phase's scopeTreeCache)
41
+ // already produced a Tree for this source. Cache miss = re-parse,
42
+ // same as before. The cachedTree parameter is typed as `unknown` at
43
+ // the LanguageProvider contract layer; cast here at the use site.
44
+ let tree = cachedTree;
45
+ if (tree === undefined) {
46
+ tree = getCsharpParser().parse(sourceText);
47
+ recordCacheMiss();
48
+ }
49
+ else {
50
+ recordCacheHit();
51
+ }
52
+ const rawMatches = getCsharpScopeQuery().matches(tree.rootNode);
53
+ const out = [];
54
+ for (const m of rawMatches) {
55
+ // Group captures by their tag name. Tree-sitter strips the leading
56
+ // `@`; we put it back so the central extractor's prefix lookups
57
+ // (`@scope.`, `@declaration.`, …) work.
58
+ const grouped = {};
59
+ for (const c of m.captures) {
60
+ const tag = '@' + c.name;
61
+ grouped[tag] = nodeToCapture(tag, c.node);
62
+ }
63
+ if (Object.keys(grouped).length === 0)
64
+ continue;
65
+ // Decompose each `using_directive` so `interpretCsharpImport` sees
66
+ // the kind/source/name/alias markers it consumes. Raw query match
67
+ // only carries the @import.statement anchor.
68
+ if (grouped['@import.statement'] !== undefined) {
69
+ const stmtCapture = grouped['@import.statement'];
70
+ const stmtNode = findNodeAtRange(tree.rootNode, stmtCapture.range, 'using_directive');
71
+ if (stmtNode !== null) {
72
+ const decomposed = splitUsingDirective(stmtNode);
73
+ if (decomposed !== null) {
74
+ out.push(decomposed);
75
+ continue;
76
+ }
77
+ }
78
+ // Defensive fallback: emit the raw match so the extractor at
79
+ // least sees an anchor, even without markers.
80
+ out.push(grouped);
81
+ continue;
82
+ }
83
+ // Synthesize `this` / `base` receiver type-bindings on every
84
+ // instance method-like. Tree-sitter can't cleanly express "the
85
+ // implicit receiver of a non-static member of a class/struct/
86
+ // record/interface" via a static `.scm` pattern, so we walk up
87
+ // the AST in code. Mirrors Python's `self`/`cls` synthesis on
88
+ // `@scope.function` matches.
89
+ if (grouped['@scope.function'] !== undefined) {
90
+ out.push(grouped);
91
+ const anchor = grouped['@scope.function'];
92
+ const fnNode = findFunctionNode(tree.rootNode, anchor.range);
93
+ if (fnNode !== null) {
94
+ for (const synth of synthesizeCsharpReceiverBinding(fnNode)) {
95
+ out.push(synth);
96
+ }
97
+ }
98
+ continue;
99
+ }
100
+ // Synthesize arity metadata on function-like declarations so the
101
+ // registry can narrow overloads (C# relies heavily on this). Mirrors
102
+ // Python's captures.ts pattern — one anchor per match, so we find
103
+ // the first tag that matches.
104
+ const declTag = FUNCTION_DECL_TAGS.find((t) => grouped[t] !== undefined);
105
+ if (declTag !== undefined) {
106
+ const anchor = grouped[declTag];
107
+ const fnNode = findFunctionNode(tree.rootNode, anchor.range);
108
+ if (fnNode !== null) {
109
+ const arity = computeCsharpArityMetadata(fnNode);
110
+ if (arity.parameterCount !== undefined) {
111
+ grouped['@declaration.parameter-count'] = syntheticCapture('@declaration.parameter-count', fnNode, String(arity.parameterCount));
112
+ }
113
+ if (arity.requiredParameterCount !== undefined) {
114
+ grouped['@declaration.required-parameter-count'] = syntheticCapture('@declaration.required-parameter-count', fnNode, String(arity.requiredParameterCount));
115
+ }
116
+ if (arity.parameterTypes !== undefined) {
117
+ grouped['@declaration.parameter-types'] = syntheticCapture('@declaration.parameter-types', fnNode, JSON.stringify(arity.parameterTypes));
118
+ }
119
+ }
120
+ }
121
+ // Synthesize `@reference.arity` on every callsite so the
122
+ // registry's arity filter can narrow overloads. Count the
123
+ // `argument` named children of the backing `argument_list`.
124
+ // Python doesn't synthesize this today; C# needs it because the
125
+ // language has method overloading and the suite asserts overload
126
+ // resolution.
127
+ const callTag = ['@reference.call.free', '@reference.call.member', '@reference.call.constructor'].find((t) => grouped[t] !== undefined);
128
+ if (callTag !== undefined && grouped['@reference.arity'] === undefined) {
129
+ const anchor = grouped[callTag];
130
+ const callNode = findNodeAtRange(tree.rootNode, anchor.range, 'invocation_expression') ??
131
+ findNodeAtRange(tree.rootNode, anchor.range, 'object_creation_expression');
132
+ if (callNode !== null) {
133
+ const argList = callNode.childForFieldName('arguments');
134
+ const args = argList === null
135
+ ? []
136
+ : argList.namedChildren.filter((c) => c !== null && c.type === 'argument');
137
+ grouped['@reference.arity'] = syntheticCapture('@reference.arity', callNode, String(args.length));
138
+ // Infer argument types from literal nodes so overload
139
+ // disambiguation can narrow same-arity candidates by param
140
+ // type. Non-literal arguments emit empty string to indicate
141
+ // "unknown" — consumers treat unknown as any-match.
142
+ const argTypes = args.map((arg) => inferArgType(arg));
143
+ grouped['@reference.parameter-types'] = syntheticCapture('@reference.parameter-types', callNode, JSON.stringify(argTypes));
144
+ }
145
+ }
146
+ out.push(grouped);
147
+ // Synthesize primary-constructor declarations on class/record
148
+ // declarations that carry a `parameter_list` child (C# 12 syntax
149
+ // `public class User(string name, int age) { ... }` or
150
+ // `public record Person(string FirstName, string LastName)`).
151
+ // Legacy `csharpMethodConfig.extractPrimaryConstructor` runs via
152
+ // the parse phase; the scope-resolution path needs its own emit so
153
+ // `new User(...)` resolves to a Constructor def in memberByOwner.
154
+ if (grouped['@declaration.class'] !== undefined ||
155
+ grouped['@declaration.record'] !== undefined) {
156
+ const anchor = grouped['@declaration.class'] ?? grouped['@declaration.record'];
157
+ const typeNode = findNodeAtRange(tree.rootNode, anchor.range, 'class_declaration') ??
158
+ findNodeAtRange(tree.rootNode, anchor.range, 'record_declaration');
159
+ if (typeNode !== null) {
160
+ const synth = synthesizePrimaryConstructor(typeNode);
161
+ if (synth !== null)
162
+ out.push(synth);
163
+ }
164
+ }
165
+ }
166
+ return out;
167
+ }
168
+ /** C# 12 primary constructor: `class X(a, b) { }` / `record X(a, b)`.
169
+ * The parameters are a bare `parameter_list` named child of the type
170
+ * declaration (no `constructor_declaration` node). Emit a synthetic
171
+ * @declaration.constructor match so the extractor creates a
172
+ * Constructor def in memberByOwner — free-call-fallback's
173
+ * `pickConstructorOrClass` then targets it for `new X(...)` calls. */
174
+ function synthesizePrimaryConstructor(typeNode) {
175
+ // Skip types with an explicit constructor_declaration — that would
176
+ // create duplicate defs.
177
+ const body = typeNode.childForFieldName('body');
178
+ if (body !== null) {
179
+ for (let i = 0; i < body.namedChildCount; i++) {
180
+ const child = body.namedChild(i);
181
+ if (child !== null && child.type === 'constructor_declaration')
182
+ return null;
183
+ }
184
+ }
185
+ let paramList = null;
186
+ for (let i = 0; i < typeNode.namedChildCount; i++) {
187
+ const child = typeNode.namedChild(i);
188
+ if (child !== null && child.type === 'parameter_list') {
189
+ paramList = child;
190
+ break;
191
+ }
192
+ }
193
+ if (paramList === null)
194
+ return null;
195
+ const nameNode = typeNode.childForFieldName('name');
196
+ if (nameNode === null)
197
+ return null;
198
+ const paramCount = paramList.namedChildren.filter((c) => c !== null && c.type === 'parameter').length;
199
+ const m = {
200
+ '@declaration.constructor': nodeToCapture('@declaration.constructor', paramList),
201
+ '@declaration.name': syntheticCapture('@declaration.name', nameNode, nameNode.text),
202
+ '@declaration.parameter-count': syntheticCapture('@declaration.parameter-count', paramList, String(paramCount)),
203
+ '@declaration.required-parameter-count': syntheticCapture('@declaration.required-parameter-count', paramList, String(paramCount)),
204
+ };
205
+ return m;
206
+ }
207
+ /** Infer a C# argument's static type from literal / constructor
208
+ * patterns. Returns `''` when the arg has no statically-derivable
209
+ * type (e.g. identifier — would require full type inference). */
210
+ function inferArgType(argNode) {
211
+ // `argument > expression` — tree-sitter-c-sharp wraps the value.
212
+ const expr = argNode.namedChild(0);
213
+ if (expr === null)
214
+ return '';
215
+ switch (expr.type) {
216
+ case 'integer_literal':
217
+ return 'int';
218
+ case 'real_literal':
219
+ return 'double';
220
+ case 'string_literal':
221
+ case 'verbatim_string_literal':
222
+ case 'interpolated_string_expression':
223
+ case 'raw_string_literal':
224
+ return 'string';
225
+ case 'character_literal':
226
+ return 'char';
227
+ case 'boolean_literal':
228
+ return 'bool';
229
+ case 'null_literal':
230
+ return 'null';
231
+ case 'object_creation_expression': {
232
+ const typeNode = expr.childForFieldName('type');
233
+ return typeNode?.text ?? '';
234
+ }
235
+ default:
236
+ return '';
237
+ }
238
+ }
239
+ /** Find the first C# function-like node at the given range. The
240
+ * declaration anchor range covers the whole method/constructor/etc.
241
+ * node, but the tag alone doesn't tell us which node type. */
242
+ function findFunctionNode(rootNode, range) {
243
+ for (const nodeType of FUNCTION_NODE_TYPES) {
244
+ const n = findNodeAtRange(rootNode, range, nodeType);
245
+ if (n !== null)
246
+ return n;
247
+ }
248
+ return null;
249
+ }