gitnexus 1.6.3-rc.40 → 1.6.3-rc.42

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 (84) 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/cli/index.js +3 -1
  9. package/dist/config/ignore-service.d.ts +9 -0
  10. package/dist/config/ignore-service.js +80 -13
  11. package/dist/core/ingestion/language-provider.d.ts +3 -2
  12. package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
  13. package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
  14. package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
  15. package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
  16. package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
  17. package/dist/core/ingestion/languages/csharp/arity.js +37 -0
  18. package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
  19. package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
  20. package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
  21. package/dist/core/ingestion/languages/csharp/captures.js +249 -0
  22. package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
  23. package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
  24. package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
  25. package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
  26. package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
  27. package/dist/core/ingestion/languages/csharp/index.js +82 -0
  28. package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
  29. package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
  30. package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
  31. package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
  32. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
  33. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
  34. package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
  35. package/dist/core/ingestion/languages/csharp/query.js +515 -0
  36. package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
  37. package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
  38. package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
  39. package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
  40. package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
  41. package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
  42. package/dist/core/ingestion/languages/csharp.js +14 -0
  43. package/dist/core/ingestion/languages/python/import-target.d.ts +4 -0
  44. package/dist/core/ingestion/languages/python/import-target.js +4 -0
  45. package/dist/core/ingestion/languages/python/merge-bindings.d.ts +2 -2
  46. package/dist/core/ingestion/languages/python/merge-bindings.js +1 -1
  47. package/dist/core/ingestion/languages/python/query.d.ts +1 -6
  48. package/dist/core/ingestion/languages/python/query.js +1 -6
  49. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +3 -3
  50. package/dist/core/ingestion/languages/python/receiver-binding.js +3 -3
  51. package/dist/core/ingestion/languages/python/scope-resolver.js +15 -15
  52. package/dist/core/ingestion/languages/python.js +2 -2
  53. package/dist/core/ingestion/model/method-registry.d.ts +9 -0
  54. package/dist/core/ingestion/model/method-registry.js +4 -0
  55. package/dist/core/ingestion/model/semantic-model.d.ts +39 -0
  56. package/dist/core/ingestion/model/semantic-model.js +39 -0
  57. package/dist/core/ingestion/registry-primary-flag.js +1 -0
  58. package/dist/core/ingestion/scope-extractor.js +20 -0
  59. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +216 -12
  60. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +149 -12
  61. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +1 -1
  62. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +9 -2
  63. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
  64. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +11 -0
  65. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +12 -0
  66. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +10 -0
  67. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +61 -0
  68. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +3 -1
  69. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +73 -3
  70. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
  71. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
  72. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +8 -4
  73. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +164 -37
  74. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +8 -1
  75. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
  76. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
  77. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +5 -1
  78. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +11 -0
  79. package/dist/core/ingestion/scope-resolution/pipeline/run.js +33 -7
  80. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +36 -9
  81. package/dist/core/ingestion/scope-resolution/scope/walkers.js +86 -24
  82. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +39 -33
  83. package/dist/core/ingestion/scope-resolution/workspace-index.js +39 -87
  84. 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"}
package/dist/cli/index.js CHANGED
@@ -30,7 +30,9 @@ program
30
30
  .option('--max-file-size <kb>', 'Skip files larger than this (KB). Default: 512. Hard cap: 32768 (tree-sitter limit).')
31
31
  .addHelpText('after', '\nEnvironment variables:\n' +
32
32
  ' GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)\n' +
33
- ' GITNEXUS_MAX_FILE_SIZE=N Override large-file skip threshold (KB). Default 512, max 32768.')
33
+ ' GITNEXUS_MAX_FILE_SIZE=N Override large-file skip threshold (KB). Default 512, max 32768.\n' +
34
+ '\nTip: `.gitnexusignore` supports `.gitignore`-style negation. Add e.g.\n' +
35
+ ' `!__tests__/` to index a directory that is auto-filtered by default (#771).')
34
36
  .action(createLazyAction(() => import('./analyze.js'), 'analyzeCommand'));
35
37
  program
36
38
  .command('index [path...]')
@@ -19,6 +19,15 @@ export declare const loadIgnoreRules: (repoPath: string, options?: IgnoreOptions
19
19
  *
20
20
  * Returns an IgnoreLike object for glob's `ignore` option,
21
21
  * enabling directory-level pruning during traversal.
22
+ *
23
+ * Precedence (#771): user's `.gitnexusignore` negation patterns take
24
+ * priority over the hardcoded list, matching `.gitignore` semantics.
25
+ * An explicit `!pattern` rule unignores descendants even when they
26
+ * would otherwise be blocked by DEFAULT_IGNORE_LIST — UNLESS a more
27
+ * specific rule in the same file re-ignores a subset (e.g.
28
+ * `!__tests__/` paired with `__tests__/generated/` blocks the child
29
+ * while leaving the parent negated). Last-match-wins is enforced by
30
+ * consulting `ig.ignores(rel)` after `hasExplicitUnignore`.
22
31
  */
23
32
  export declare const createIgnoreFilter: (repoPath: string, options?: IgnoreOptions) => Promise<{
24
33
  ignored(p: Path): boolean;
@@ -245,16 +245,20 @@ const IGNORED_FILES = new Set([
245
245
  '.env.test',
246
246
  '.env.example',
247
247
  ]);
248
- // NOTE: Negation patterns in .gitnexusignore (e.g. `!vendor/`) cannot override
249
- // entries in DEFAULT_IGNORE_LIST this is intentional. The hardcoded list protects
250
- // against indexing directories that are almost never source code (node_modules, .git, etc.).
251
- // Users who need to include such directories should remove them from the hardcoded list.
248
+ // The hardcoded DEFAULT_IGNORE_LIST is the "safety net" default: directories
249
+ // that are almost never source code (node_modules, .git, dist, __tests__,
250
+ // etc.). Users who legitimately need to index one of these can negate the
251
+ // hardcoded rule via a `!pattern` line in `.gitnexusignore` (#771) same
252
+ // semantics as `.gitignore` negation. That override is applied in
253
+ // `createIgnoreFilter` below; `shouldIgnorePath` itself stays a pure
254
+ // hardcoded-list check so its callers (wiki generator, tests) get
255
+ // deterministic results independent of per-repo config.
252
256
  export const shouldIgnorePath = (filePath) => {
253
257
  const normalizedPath = filePath.replace(/\\/g, '/');
254
258
  const parts = normalizedPath.split('/');
255
259
  const fileName = parts[parts.length - 1];
256
260
  const fileNameLower = fileName.toLowerCase();
257
- // Check if any path segment is in ignore list
261
+ // Check if any path segment is in the hardcoded ignore list.
258
262
  for (const part of parts) {
259
263
  if (DEFAULT_IGNORE_LIST.has(part)) {
260
264
  return true;
@@ -320,6 +324,44 @@ export const loadIgnoreRules = async (repoPath, options) => {
320
324
  }
321
325
  return hasRules ? ig : null;
322
326
  };
327
+ /**
328
+ * Walk ancestor segments of `rel` and check whether `.gitnexusignore`
329
+ * (or `.gitignore`) contains an explicit `!pattern` negation that
330
+ * applies. Returns true as soon as any segment — or the path itself —
331
+ * is matched by a negation rule.
332
+ *
333
+ * Why this exists (#771): the hardcoded DEFAULT_IGNORE_LIST would
334
+ * otherwise block indexing of directories like `__tests__/` even when
335
+ * the user has an explicit `!__tests__/` line in `.gitnexusignore`.
336
+ * Mirroring `.gitignore` negation semantics: a user's explicit
337
+ * unignore of a parent directory implicitly unignores everything
338
+ * underneath, so we walk the ancestor chain rather than only testing
339
+ * the leaf.
340
+ *
341
+ * The `ignore` package's `test(path)` returns `{ignored, unignored}`;
342
+ * `unignored: true` is the "a negation rule matched this path"
343
+ * signal. Children of a negated directory return
344
+ * `{ignored: false, unignored: false}` on a direct test, which is why
345
+ * we also walk the ancestors here.
346
+ */
347
+ const hasExplicitUnignore = (ig, rel) => {
348
+ // Direct match on the path (as a file).
349
+ if (ig.test(rel).unignored)
350
+ return true;
351
+ // Direct match on the path treated as a directory — `!dir/` matches
352
+ // here when rel is the directory itself.
353
+ if (ig.test(rel + '/').unignored)
354
+ return true;
355
+ // Walk ancestor segments. `!parent/` should propagate to every
356
+ // descendant the same way `.gitignore` negation propagates.
357
+ const parts = rel.split('/');
358
+ for (let i = parts.length - 1; i > 0; i--) {
359
+ const ancestor = parts.slice(0, i).join('/') + '/';
360
+ if (ig.test(ancestor).unignored)
361
+ return true;
362
+ }
363
+ return false;
364
+ };
323
365
  /**
324
366
  * Create a glob-compatible ignore filter combining:
325
367
  * - .gitignore / .gitnexusignore patterns (via `ignore` package)
@@ -327,6 +369,15 @@ export const loadIgnoreRules = async (repoPath, options) => {
327
369
  *
328
370
  * Returns an IgnoreLike object for glob's `ignore` option,
329
371
  * enabling directory-level pruning during traversal.
372
+ *
373
+ * Precedence (#771): user's `.gitnexusignore` negation patterns take
374
+ * priority over the hardcoded list, matching `.gitignore` semantics.
375
+ * An explicit `!pattern` rule unignores descendants even when they
376
+ * would otherwise be blocked by DEFAULT_IGNORE_LIST — UNLESS a more
377
+ * specific rule in the same file re-ignores a subset (e.g.
378
+ * `!__tests__/` paired with `__tests__/generated/` blocks the child
379
+ * while leaving the parent negated). Last-match-wins is enforced by
380
+ * consulting `ig.ignores(rel)` after `hasExplicitUnignore`.
330
381
  */
331
382
  export const createIgnoreFilter = async (repoPath, options) => {
332
383
  const ig = await loadIgnoreRules(repoPath, options);
@@ -337,6 +388,15 @@ export const createIgnoreFilter = async (repoPath, options) => {
337
388
  const rel = p.relative();
338
389
  if (!rel)
339
390
  return false;
391
+ // User's .gitnexusignore negation takes precedence over hardcoded
392
+ // rules (#771). If any ancestor or the path itself was explicitly
393
+ // unignored AND no more-specific rule re-ignores this exact path,
394
+ // allow it through. The `!ig.ignores(rel)` guard matches
395
+ // .gitignore's last-match-wins semantics: `!__tests__/` followed
396
+ // by `__tests__/generated/` negates the parent but still blocks
397
+ // the re-ignored child.
398
+ if (ig && hasExplicitUnignore(ig, rel) && !ig.ignores(rel))
399
+ return false;
340
400
  // Check .gitignore / .gitnexusignore patterns
341
401
  if (ig && ig.ignores(rel))
342
402
  return true;
@@ -344,10 +404,20 @@ export const createIgnoreFilter = async (repoPath, options) => {
344
404
  return shouldIgnorePath(rel);
345
405
  },
346
406
  childrenIgnored(p) {
347
- // Fast path: check directory name against hardcoded list.
348
407
  // Note: dot-directories (.git, .vscode, etc.) are primarily excluded by
349
- // glob's `dot: false` option in filesystem-walker.ts. This check is
350
- // defense-in-depth — do not remove `dot: false` assuming this covers it.
408
+ // glob's `dot: false` option in filesystem-walker.ts. The hardcoded
409
+ // list check below is defense-in-depth — do not remove `dot: false`
410
+ // assuming this covers it.
411
+ const rel = p.relative();
412
+ // User's .gitnexusignore negation takes precedence (#771) — if the
413
+ // user explicitly unignored this directory or any ancestor via a
414
+ // !pattern rule, allow descent even if the directory name is in
415
+ // DEFAULT_IGNORE_LIST. The `!ig.ignores(rel + '/')` guard keeps
416
+ // last-match-wins: `!__tests__/` + `__tests__/generated/` still
417
+ // blocks descent into `__tests__/generated/`.
418
+ if (ig && rel && hasExplicitUnignore(ig, rel) && !ig.ignores(rel + '/'))
419
+ return false;
420
+ // Hardcoded list: block descent into well-known noise directories.
351
421
  if (DEFAULT_IGNORE_LIST.has(p.name))
352
422
  return true;
353
423
  // Check against .gitignore / .gitnexusignore patterns.
@@ -358,11 +428,8 @@ export const createIgnoreFilter = async (repoPath, options) => {
358
428
  // Bare-name patterns (e.g. `local`) still match `local/` per gitignore spec:
359
429
  // the `ignore` package normalizes `dir` and `dir/` to match directories.
360
430
  // See: https://github.com/kaelzhang/node-ignore#2-filenames-and-dirnames
361
- if (ig) {
362
- const rel = p.relative();
363
- if (rel && ig.ignores(rel + '/'))
364
- return true;
365
- }
431
+ if (ig && rel && ig.ignores(rel + '/'))
432
+ return true;
366
433
  return false;
367
434
  },
368
435
  };
@@ -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[];