gitnexus 1.6.5 → 1.6.6-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 (81) hide show
  1. package/dist/_shared/index.d.ts +2 -2
  2. package/dist/_shared/index.d.ts.map +1 -1
  3. package/dist/_shared/index.js.map +1 -1
  4. package/dist/_shared/scope-resolution/registries/context.d.ts +37 -0
  5. package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -1
  6. package/dist/_shared/scope-resolution/registries/context.js.map +1 -1
  7. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +4 -2
  8. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -1
  9. package/dist/_shared/scope-resolution/registries/lookup-core.js +8 -23
  10. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
  11. package/dist/_shared/scope-resolution/symbol-definition.d.ts +20 -0
  12. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  13. package/dist/cli/analyze.js +65 -3
  14. package/dist/cli/eval-server.d.ts +14 -3
  15. package/dist/cli/eval-server.js +79 -9
  16. package/dist/cli/index.js +3 -1
  17. package/dist/cli/serve.js +6 -1
  18. package/dist/cli/wiki.d.ts +1 -0
  19. package/dist/cli/wiki.js +32 -8
  20. package/dist/core/ingestion/language-provider.d.ts +29 -0
  21. package/dist/core/ingestion/languages/c-cpp.js +46 -0
  22. package/dist/core/ingestion/languages/cpp/arity-metadata.d.ts +17 -0
  23. package/dist/core/ingestion/languages/cpp/arity-metadata.js +51 -2
  24. package/dist/core/ingestion/languages/cpp/arity.d.ts +4 -1
  25. package/dist/core/ingestion/languages/cpp/arity.js +4 -1
  26. package/dist/core/ingestion/languages/cpp/captures.js +73 -0
  27. package/dist/core/ingestion/languages/cpp/constraint-extractor.d.ts +73 -0
  28. package/dist/core/ingestion/languages/cpp/constraint-extractor.js +308 -0
  29. package/dist/core/ingestion/languages/cpp/constraint-filter.d.ts +31 -0
  30. package/dist/core/ingestion/languages/cpp/constraint-filter.js +135 -0
  31. package/dist/core/ingestion/languages/cpp/scope-resolver.js +6 -0
  32. package/dist/core/ingestion/languages/cpp/type-classifier.d.ts +26 -0
  33. package/dist/core/ingestion/languages/cpp/type-classifier.js +49 -0
  34. package/dist/core/ingestion/model/field-registry.d.ts +17 -3
  35. package/dist/core/ingestion/model/field-registry.js +19 -4
  36. package/dist/core/ingestion/model/owned-members-lookup.d.ts +19 -0
  37. package/dist/core/ingestion/model/owned-members-lookup.js +43 -0
  38. package/dist/core/ingestion/model/symbol-table.d.ts +2 -1
  39. package/dist/core/ingestion/model/symbol-table.js +3 -0
  40. package/dist/core/ingestion/model/type-registry.d.ts +9 -0
  41. package/dist/core/ingestion/model/type-registry.js +18 -0
  42. package/dist/core/ingestion/parsing-processor.js +35 -2
  43. package/dist/core/ingestion/resolve-references.d.ts +3 -1
  44. package/dist/core/ingestion/resolve-references.js +5 -1
  45. package/dist/core/ingestion/scope-extractor.js +63 -0
  46. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +21 -1
  47. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
  48. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +14 -0
  49. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +12 -0
  50. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +11 -1
  51. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +83 -28
  52. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +30 -7
  53. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +49 -18
  54. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +1 -1
  55. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +10 -4
  56. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +4 -2
  57. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +48 -9
  58. package/dist/core/ingestion/scope-resolution/pipeline/run.js +3 -0
  59. package/dist/core/ingestion/utils/template-arguments.d.ts +19 -0
  60. package/dist/core/ingestion/utils/template-arguments.js +30 -0
  61. package/dist/core/ingestion/workers/parse-worker.d.ts +2 -1
  62. package/dist/core/ingestion/workers/parse-worker.js +1 -0
  63. package/dist/core/lbug/lbug-adapter.d.ts +3 -1
  64. package/dist/core/lbug/lbug-adapter.js +33 -19
  65. package/dist/core/lbug/lbug-config.d.ts +1 -1
  66. package/dist/core/lbug/lbug-config.js +1 -1
  67. package/dist/core/lbug/pool-adapter.d.ts +0 -7
  68. package/dist/core/lbug/pool-adapter.js +10 -35
  69. package/dist/core/lbug/query-params.d.ts +1 -0
  70. package/dist/core/lbug/query-params.js +21 -0
  71. package/dist/core/search/bm25-index.js +4 -6
  72. package/dist/core/wiki/generator.d.ts +13 -0
  73. package/dist/core/wiki/generator.js +45 -4
  74. package/dist/core/wiki/llm-client.d.ts +1 -1
  75. package/dist/core/wiki/llm-client.js +23 -6
  76. package/dist/mcp/local/local-backend.d.ts +18 -3
  77. package/dist/mcp/local/local-backend.js +105 -11
  78. package/dist/mcp/tools.js +10 -0
  79. package/dist/server/api.d.ts +3 -0
  80. package/dist/server/api.js +36 -24
  81. package/package.json +1 -1
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Owner-keyed member lookup for Step 2 (RFC #909 / PR #1656).
3
+ *
4
+ * Merges MethodRegistry + FieldRegistry hits for `(ownerDefId, memberName)`
5
+ * in O(1) map time per registry — no `defs.byId` scan. Callers that omit
6
+ * this helper and leave `ownedMembersByOwner` unset fall back to an O(|defs|)
7
+ * compatibility scan inside `lookupCore.collectOwnedMembers`.
8
+ */
9
+ const EMPTY = Object.freeze([]);
10
+ /**
11
+ * Production hook for `RegistryContext.ownedMembersByOwner`.
12
+ * Returns `[]` on miss (authoritative indexed empty) — never `undefined`.
13
+ *
14
+ * Merges hits from all three owner-keyed registries (methods, fields,
15
+ * nested types) under the same `(ownerDefId, memberName)` key. The
16
+ * caller's `acceptedKinds` filter in `lookupCore` picks the right subset.
17
+ */
18
+ export function lookupOwnedMembersByOwner(model, ownerDefId, memberName) {
19
+ const methods = model.methods.lookupAllByOwner(ownerDefId, memberName);
20
+ const fields = model.fields.lookupAllByOwner(ownerDefId, memberName);
21
+ const nestedTypes = model.types.lookupAllByOwner(ownerDefId, memberName);
22
+ const methodCount = methods.length;
23
+ const fieldCount = fields.length;
24
+ const typeCount = nestedTypes.length;
25
+ const total = methodCount + fieldCount + typeCount;
26
+ if (total === 0)
27
+ return EMPTY;
28
+ if (methodCount === total)
29
+ return methods;
30
+ if (fieldCount === total)
31
+ return fields;
32
+ if (typeCount === total)
33
+ return nestedTypes;
34
+ const merged = new Array(total);
35
+ let i = 0;
36
+ for (let j = 0; j < methodCount; j++)
37
+ merged[i++] = methods[j];
38
+ for (let j = 0; j < fieldCount; j++)
39
+ merged[i++] = fields[j];
40
+ for (let j = 0; j < typeCount; j++)
41
+ merged[i++] = nestedTypes[j];
42
+ return merged;
43
+ }
@@ -33,7 +33,7 @@
33
33
  * import from `./model/` here, you are going the wrong way — move the
34
34
  * logic up the dependency chain instead.
35
35
  */
36
- import type { NodeLabel, SymbolDefinition } from '../../../_shared/index.js';
36
+ import type { NodeLabel, ParameterTypeClass, SymbolDefinition } from '../../../_shared/index.js';
37
37
  /**
38
38
  * Class-like NodeLabels — used for qualifiedName fallback inside
39
39
  * `SymbolTable.add()` and (via import into `model/registration-table.ts`)
@@ -97,6 +97,7 @@ export interface AddMetadata {
97
97
  parameterCount?: number;
98
98
  requiredParameterCount?: number;
99
99
  parameterTypes?: string[];
100
+ parameterTypeClasses?: ParameterTypeClass[];
100
101
  returnType?: string;
101
102
  declaredType?: string;
102
103
  templateArguments?: string[];
@@ -128,6 +128,9 @@ export const createSymbolTable = () => {
128
128
  ...(metadata?.parameterTypes !== undefined
129
129
  ? { parameterTypes: metadata.parameterTypes }
130
130
  : {}),
131
+ ...(metadata?.parameterTypeClasses !== undefined
132
+ ? { parameterTypeClasses: metadata.parameterTypeClasses }
133
+ : {}),
131
134
  ...(metadata?.returnType !== undefined ? { returnType: metadata.returnType } : {}),
132
135
  ...(metadata?.declaredType !== undefined ? { declaredType: metadata.declaredType } : {}),
133
136
  ...(metadata?.templateArguments !== undefined
@@ -27,12 +27,21 @@ export interface TypeRegistry {
27
27
  * Returned array is a view into the live index — do not mutate.
28
28
  */
29
29
  lookupImplByName(name: string): readonly SymbolDefinition[];
30
+ /**
31
+ * Look up nested-type defs registered under `(ownerNodeId, simpleName)`
32
+ * in registration order. Returns `[]` on miss. Used by Step 2 Receiver/MRO
33
+ * resolution when the receiver's owner declares nested classes/structs/
34
+ * enums/typedefs/etc. that the caller's `acceptedKinds` includes.
35
+ */
36
+ lookupAllByOwner(ownerNodeId: string, simpleName: string): readonly SymbolDefinition[];
30
37
  }
31
38
  export interface MutableTypeRegistry extends TypeRegistry {
32
39
  /** Register a class-like type by name and qualified name. */
33
40
  registerClass(name: string, qualifiedName: string, def: SymbolDefinition): void;
34
41
  /** Register a Rust Impl block by name. */
35
42
  registerImpl(name: string, def: SymbolDefinition): void;
43
+ /** Register a nested type under its owner. Appends when the key already exists. */
44
+ registerByOwner(ownerNodeId: string, simpleName: string, def: SymbolDefinition): void;
36
45
  /** Clear all entries. */
37
46
  clear(): void;
38
47
  }
@@ -5,6 +5,7 @@
5
5
  * Eagerly-populated indexes keyed by symbol name and qualified name.
6
6
  * Also includes a separate index for Rust Impl blocks.
7
7
  */
8
+ const EMPTY = Object.freeze([]);
8
9
  // ---------------------------------------------------------------------------
9
10
  // Factory
10
11
  // ---------------------------------------------------------------------------
@@ -12,6 +13,7 @@ export const createTypeRegistry = () => {
12
13
  const classByName = new Map();
13
14
  const classByQualifiedName = new Map();
14
15
  const implByName = new Map();
16
+ const nestedByOwner = new Map();
15
17
  const lookupClassByName = (name) => {
16
18
  return classByName.get(name) ?? [];
17
19
  };
@@ -21,6 +23,9 @@ export const createTypeRegistry = () => {
21
23
  const lookupImplByName = (name) => {
22
24
  return implByName.get(name) ?? [];
23
25
  };
26
+ const lookupAllByOwner = (ownerNodeId, simpleName) => {
27
+ return nestedByOwner.get(`${ownerNodeId}\0${simpleName}`) ?? EMPTY;
28
+ };
24
29
  const registerClass = (name, qualifiedName, def) => {
25
30
  const existing = classByName.get(name);
26
31
  if (existing) {
@@ -46,17 +51,30 @@ export const createTypeRegistry = () => {
46
51
  implByName.set(name, [def]);
47
52
  }
48
53
  };
54
+ const registerByOwner = (ownerNodeId, simpleName, def) => {
55
+ const key = `${ownerNodeId}\0${simpleName}`;
56
+ const existing = nestedByOwner.get(key);
57
+ if (existing) {
58
+ existing.push(def);
59
+ }
60
+ else {
61
+ nestedByOwner.set(key, [def]);
62
+ }
63
+ };
49
64
  const clear = () => {
50
65
  classByName.clear();
51
66
  classByQualifiedName.clear();
52
67
  implByName.clear();
68
+ nestedByOwner.clear();
53
69
  };
54
70
  return {
55
71
  lookupClassByName,
56
72
  lookupClassByQualifiedName,
57
73
  lookupImplByName,
74
+ lookupAllByOwner,
58
75
  registerClass,
59
76
  registerImpl,
77
+ registerByOwner,
60
78
  clear,
61
79
  };
62
80
  };
@@ -11,7 +11,7 @@ import { getDefinitionNodeFromCaptures, findEnclosingClassInfo, getLabelFromCapt
11
11
  import { detectFrameworkFromAST } from './framework-detection.js';
12
12
  import { buildTypeEnv } from './type-env.js';
13
13
  import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, } from './utils/method-props.js';
14
- import { extractTemplateArguments, templateArgumentsIdTag } from './utils/template-arguments.js';
14
+ import { extractTemplateArguments, templateArgumentsIdTag, templateConstraintsIdTag, } from './utils/template-arguments.js';
15
15
  import { logger } from '../logger.js';
16
16
  import { getTreeSitterBufferSize, getTreeSitterContentByteLength, TREE_SITTER_MAX_BUFFER, } from './constants.js';
17
17
  // ============================================================================
@@ -57,6 +57,7 @@ export const mergeChunkResults = (graph, symbolTable, chunkResults) => {
57
57
  parameterCount: sym.parameterCount,
58
58
  requiredParameterCount: sym.requiredParameterCount,
59
59
  parameterTypes: sym.parameterTypes,
60
+ parameterTypeClasses: sym.parameterTypeClasses,
60
61
  returnType: sym.returnType,
61
62
  declaredType: sym.declaredType,
62
63
  templateArguments: sym.templateArguments,
@@ -499,7 +500,35 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
499
500
  classTemplateArguments.length > 0
500
501
  ? templateArgumentsIdTag(classTemplateArguments)
501
502
  : '';
502
- const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${classTemplateTag}${arityTag}`);
503
+ // SFINAE / `requires`-clause aware ID disambiguation (issue #1579).
504
+ // Function-template overloads with identical parameterTypes but
505
+ // mutually-exclusive constraints (e.g. `enable_if_t<is_integral_v<T>>`
506
+ // vs `enable_if_t<is_floating_point_v<T>>`) need distinct graph
507
+ // nodes so the constraint-filter step in `narrowOverloadCandidates`
508
+ // has two candidates to narrow between. Without this tag they
509
+ // collapse to a single Function node and the SFINAE call resolves
510
+ // to only one edge regardless of which overload's constraint holds.
511
+ // The provider hook is the right invocation point — parsing-processor
512
+ // sees raw tree-sitter matches without the `@`-prefixed synthetic
513
+ // captures `scope-extractor` consumes, so we delegate extraction to
514
+ // the language adapter (C++ implements this; other languages opt out).
515
+ let parsedTemplateConstraints = undefined;
516
+ let constraintsTag = '';
517
+ if ((nodeLabel === 'Function' || nodeLabel === 'Method') &&
518
+ provider.extractTemplateConstraints !== undefined &&
519
+ definitionNode !== null) {
520
+ try {
521
+ parsedTemplateConstraints = provider.extractTemplateConstraints(definitionNode);
522
+ if (parsedTemplateConstraints !== undefined) {
523
+ constraintsTag = templateConstraintsIdTag(parsedTemplateConstraints);
524
+ }
525
+ }
526
+ catch {
527
+ parsedTemplateConstraints = undefined;
528
+ constraintsTag = '';
529
+ }
530
+ }
531
+ const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${classTemplateTag}${arityTag}${constraintsTag}`);
503
532
  const classNodeForSymbol = definitionNodeForRange || definitionNode || nameNode;
504
533
  const qualifiedTypeName = extractedClassSymbol?.qualifiedName ??
505
534
  (classNodeForSymbol && provider.classExtractor?.isTypeDeclaration(classNodeForSymbol)
@@ -528,6 +557,9 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
528
557
  ...(classTemplateArguments !== undefined && classTemplateArguments.length > 0
529
558
  ? { templateArguments: classTemplateArguments }
530
559
  : {}),
560
+ ...(parsedTemplateConstraints !== undefined
561
+ ? { templateConstraints: parsedTemplateConstraints }
562
+ : {}),
531
563
  ...(frameworkHint
532
564
  ? {
533
565
  astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
@@ -579,6 +611,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
579
611
  parameterCount: methodProps.parameterCount,
580
612
  requiredParameterCount: methodProps.requiredParameterCount,
581
613
  parameterTypes: methodProps.parameterTypes,
614
+ parameterTypeClasses: methodProps.parameterTypeClasses,
582
615
  returnType: methodProps.returnType,
583
616
  declaredType,
584
617
  templateArguments: classTemplateArguments,
@@ -37,12 +37,14 @@
37
37
  * resolution but not emitted as multiple edges; callers that want
38
38
  * branch-on-ambiguity behavior should consume the registries directly.
39
39
  */
40
- import { CLASS_KINDS, FIELD_KINDS, METHOD_KINDS, type ReferenceIndex, type RegistryProviders } from '../../_shared/index.js';
40
+ import { CLASS_KINDS, FIELD_KINDS, METHOD_KINDS, type ReferenceIndex, type RegistryContext, type RegistryProviders } from '../../_shared/index.js';
41
41
  import type { ScopeResolutionIndexes } from './model/scope-resolution-indexes.js';
42
42
  export interface ResolveReferencesInput {
43
43
  readonly scopes: ScopeResolutionIndexes;
44
44
  /** Provider hooks consumed by the registries (e.g. `arityCompatibility`). */
45
45
  readonly providers?: RegistryProviders;
46
+ /** Required owner-keyed member lookup used by Step 2 receiver/MRO walks. */
47
+ readonly ownedMembersByOwner: RegistryContext['ownedMembersByOwner'];
46
48
  }
47
49
  export interface ResolveStats {
48
50
  readonly sitesProcessed: number;
@@ -51,6 +51,7 @@ export function resolveReferenceSites(input) {
51
51
  defs: scopes.defs,
52
52
  qualifiedNames: scopes.qualifiedNames,
53
53
  moduleScopes: scopes.moduleScopes,
54
+ ownedMembersByOwner: input.ownedMembersByOwner,
54
55
  methodDispatch: scopes.methodDispatch,
55
56
  providers,
56
57
  };
@@ -135,7 +136,10 @@ function lookupForSite(site, classRegistry, methodRegistry, fieldRegistry) {
135
136
  case 'write': {
136
137
  // Try field first; fall through to method then class so bare-name
137
138
  // reads of a function (e.g. `cb = save`) still resolve.
138
- const fieldHits = fieldRegistry.lookup(site.name, site.inScope);
139
+ const fieldOpts = {
140
+ ...(site.explicitReceiver !== undefined ? { explicitReceiver: site.explicitReceiver } : {}),
141
+ };
142
+ const fieldHits = fieldRegistry.lookup(site.name, site.inScope, fieldOpts);
139
143
  if (fieldHits.length > 0)
140
144
  return fieldHits;
141
145
  const methodHits = methodRegistry.lookup(site.name, site.inScope);
@@ -372,8 +372,10 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
372
372
  const parameterCount = parseIntCapture(match['@declaration.parameter-count']);
373
373
  const requiredParameterCount = parseIntCapture(match['@declaration.required-parameter-count']);
374
374
  const parameterTypes = parseJsonStringArrayCapture(match['@declaration.parameter-types']);
375
+ const parameterTypeClasses = parseJsonParameterTypeClassesCapture(match['@declaration.parameter-type-classes']);
375
376
  const declaredType = match['@declaration.field-type']?.text;
376
377
  const returnType = match['@declaration.return-type']?.text;
378
+ const templateConstraints = parseJsonCapture(match['@declaration.template-constraints']);
377
379
  return {
378
380
  nodeId: makeDefId(filePath, anchor.range, type, nameCap.text),
379
381
  filePath,
@@ -382,17 +384,77 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
382
384
  ...(parameterCount !== undefined ? { parameterCount } : {}),
383
385
  ...(requiredParameterCount !== undefined ? { requiredParameterCount } : {}),
384
386
  ...(parameterTypes !== undefined ? { parameterTypes } : {}),
387
+ ...(parameterTypeClasses !== undefined ? { parameterTypeClasses } : {}),
385
388
  ...(declaredType !== undefined ? { declaredType } : {}),
386
389
  ...(returnType !== undefined ? { returnType } : {}),
387
390
  ...(templateArguments !== undefined ? { templateArguments } : {}),
391
+ ...(templateConstraints !== undefined ? { templateConstraints } : {}),
388
392
  };
389
393
  }
394
+ /** Parse an opaque JSON payload synthesized by per-language captures
395
+ * (e.g. C++ `@declaration.template-constraints`). Producer owns the
396
+ * shape; shared code threads it through as `unknown` per the
397
+ * `SymbolDefinition.templateConstraints` contract. */
398
+ function parseJsonCapture(cap) {
399
+ if (cap === undefined)
400
+ return undefined;
401
+ try {
402
+ return JSON.parse(cap.text);
403
+ }
404
+ catch {
405
+ return undefined;
406
+ }
407
+ }
390
408
  function parseIntCapture(cap) {
391
409
  if (cap === undefined)
392
410
  return undefined;
393
411
  const n = Number.parseInt(cap.text, 10);
394
412
  return Number.isFinite(n) ? n : undefined;
395
413
  }
414
+ function parseJsonParameterTypeClassesCapture(cap) {
415
+ if (cap === undefined)
416
+ return undefined;
417
+ try {
418
+ const parsed = JSON.parse(cap.text);
419
+ if (!Array.isArray(parsed))
420
+ return undefined;
421
+ const out = [];
422
+ for (const item of parsed) {
423
+ if (item === null || typeof item !== 'object')
424
+ return undefined;
425
+ const o = item;
426
+ if (typeof o.base !== 'string')
427
+ return undefined;
428
+ if (o.cv !== 'none' &&
429
+ o.cv !== 'const' &&
430
+ o.cv !== 'volatile' &&
431
+ o.cv !== 'const volatile' &&
432
+ o.cv !== 'unknown') {
433
+ return undefined;
434
+ }
435
+ if (o.indirection !== 'value' &&
436
+ o.indirection !== 'lvalue-ref' &&
437
+ o.indirection !== 'rvalue-ref' &&
438
+ o.indirection !== 'pointer' &&
439
+ o.indirection !== 'unknown') {
440
+ return undefined;
441
+ }
442
+ if (typeof o.pointerDepth !== 'number' || !Number.isFinite(o.pointerDepth)) {
443
+ return undefined;
444
+ }
445
+ out.push({
446
+ base: o.base,
447
+ cv: o.cv,
448
+ indirection: o.indirection,
449
+ pointerDepth: o.pointerDepth,
450
+ });
451
+ }
452
+ return out;
453
+ }
454
+ catch {
455
+ return undefined;
456
+ }
457
+ }
396
458
  function parseJsonStringArrayCapture(cap) {
397
459
  if (cap === undefined)
398
460
  return undefined;
@@ -747,6 +809,7 @@ const KNOWN_SUB_TAGS = new Set([
747
809
  '@declaration.parameter-count',
748
810
  '@declaration.required-parameter-count',
749
811
  '@declaration.parameter-types',
812
+ '@declaration.template-constraints',
750
813
  ]);
751
814
  /**
752
815
  * Return the anchor capture for a match — the one whose name begins with
@@ -250,7 +250,7 @@
250
250
  * Plan that introduced most of these invariants:
251
251
  * `docs/plans/2026-04-20-001-refactor-emit-pipeline-generalization-plan.md`.
252
252
  */
253
- import type { BindingRef, Callsite, ParsedFile, ScopeId, SupportedLanguages, SymbolDefinition } from '../../../../_shared/index.js';
253
+ import type { BindingRef, Callsite, ConstraintContext, ParsedFile, ScopeId, SupportedLanguages, SymbolDefinition } from '../../../../_shared/index.js';
254
254
  import type { KnowledgeGraph } from '../../../graph/types.js';
255
255
  import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
256
256
  import { LanguageProvider } from '../../language-provider.js';
@@ -264,6 +264,9 @@ import type { ConversionRankFn } from '../passes/overload-narrowing.js';
264
264
  export type LinearizeStrategy = (classDefId: string, directParents: readonly string[], parentsByDefId: ReadonlyMap<string, readonly string[]>) => string[];
265
265
  /** Result of `ScopeResolver.arityCompatibility` — mirrors `RegistryProviders.arityCompatibility`. */
266
266
  export type ArityVerdict = 'compatible' | 'unknown' | 'incompatible';
267
+ /** Re-exported for ScopeResolver consumers — same shape as
268
+ * `RegistryProviders.constraintCompatibility`'s third parameter. */
269
+ export type { ConstraintContext } from '../../../../_shared/index.js';
267
270
  export interface ScopeResolver {
268
271
  /** Identity for telemetry + per-language flag check. */
269
272
  readonly language: SupportedLanguages;
@@ -337,6 +340,23 @@ export interface ScopeResolver {
337
340
  * `(def, callsite)` and need an adapter at the wiring site.
338
341
  */
339
342
  arityCompatibility(callsite: Callsite, def: SymbolDefinition): ArityVerdict;
343
+ /**
344
+ * Per-language constraint compatibility between a callsite and a
345
+ * candidate `def` that carries `templateConstraints` metadata.
346
+ * Mirrors `arityCompatibility` semantics: the three-valued verdict
347
+ * MUST treat `'unknown'` as keep-candidate (monotonicity — adding
348
+ * a predicate can only narrow correctly, never produce a wrong
349
+ * edge). Consulted by `narrowOverloadCandidates` after the arity
350
+ * and parameter-type filters.
351
+ *
352
+ * Optional. Languages without constrained-overload semantics
353
+ * (SFINAE, `requires` clauses, trait bounds, conditional types)
354
+ * leave this undefined and the constraint filter is a pass-through.
355
+ *
356
+ * C++ is the first consumer; see `languages/cpp/constraint-filter.ts`
357
+ * for the Tier-A predicate registry and Kleene 3-valued evaluator.
358
+ */
359
+ readonly constraintCompatibility?: (callsite: Callsite, def: SymbolDefinition, ctx: ConstraintContext) => ArityVerdict;
340
360
  /**
341
361
  * Compute the method-dispatch order for every Class def in the
342
362
  * workspace. Python uses depth-first first-seen via
@@ -41,6 +41,7 @@ export declare function resolveDefGraphId(filePath: string, def: {
41
41
  type?: NodeLabel;
42
42
  parameterTypes?: readonly string[];
43
43
  templateArguments?: readonly string[];
44
+ templateConstraints?: unknown;
44
45
  }, nodeLookup: GraphNodeLookup): string | undefined;
45
46
  /** Derive the simple (unqualified) name of a def from its `qualifiedName`. */
46
47
  export declare function simpleQualifiedName(def: SymbolDefinition): string | undefined;
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import { generateId } from '../../../../lib/utils.js';
20
20
  import { qualifiedKey, simpleKey } from '../graph-bridge/node-lookup.js';
21
+ import { templateConstraintsIdTag } from '../../utils/template-arguments.js';
21
22
  /**
22
23
  * Labels that may legitimately ANCHOR a CALLS/ACCESSES edge as the
23
24
  * source ("caller"). A Variable / Property can be the TARGET of an
@@ -68,6 +69,19 @@ export function resolveDefGraphId(filePath, def, nodeLookup) {
68
69
  if (qn === undefined || qn.length === 0)
69
70
  return undefined;
70
71
  if (def.type !== undefined) {
72
+ // SFINAE / `requires`-clause disambiguation (issue #1579) — try the
73
+ // constraint-fingerprinted key FIRST. Two function-template overloads
74
+ // with identical `parameterTypes` but mutually-exclusive SFINAE
75
+ // constraints route to their distinct graph nodes via this key.
76
+ // Must run before the parameter-types key because both overloads
77
+ // share the latter.
78
+ if ((def.type === 'Function' || def.type === 'Method') &&
79
+ def.templateConstraints !== undefined) {
80
+ const cKey = qualifiedKey(filePath, def.type, `${qn}${templateConstraintsIdTag(def.templateConstraints)}`);
81
+ const cHit = nodeLookup.get(cKey);
82
+ if (cHit !== undefined)
83
+ return cHit;
84
+ }
71
85
  // Overload disambiguation: when the def carries parameter types,
72
86
  // try the parameter-typed key first so same-name same-arity
73
87
  // overloads route to their distinct graph nodes.
@@ -17,6 +17,7 @@
17
17
  * `SymbolDefinition.nodeId` values into the legacy graph-node ID
18
18
  * format that downstream consumers (queries, edges, MCP) expect.
19
19
  */
20
+ import { templateConstraintsIdTag } from '../../utils/template-arguments.js';
20
21
  /**
21
22
  * Parse a qualified name out of a Function/Method node id.
22
23
  *
@@ -86,6 +87,17 @@ export function buildGraphNodeLookup(graph) {
86
87
  // Each overload is unique — set unconditionally.
87
88
  lookup.set(pKey, node.id);
88
89
  }
90
+ // SFINAE / `requires`-clause disambiguation (issue #1579) — register
91
+ // a constraint-fingerprinted key so resolveDefGraphId can locate the
92
+ // correct overload by hashing the def's `templateConstraints`. Mirrors
93
+ // the parameter-types key but keys on the opaque constraint payload
94
+ // instead, separating two `process<T>` overloads whose
95
+ // `parameterTypes=['T']` would otherwise collide.
96
+ const tConstraints = props.templateConstraints;
97
+ if (tConstraints !== undefined && (node.label === 'Function' || node.label === 'Method')) {
98
+ const cKey = qualifiedKey(props.filePath, node.label, `${qualified}${templateConstraintsIdTag(tConstraints)}`);
99
+ lookup.set(cKey, node.id);
100
+ }
89
101
  if ((node.label === 'Class' ||
90
102
  node.label === 'Struct' ||
91
103
  node.label === 'Interface' ||
@@ -22,6 +22,7 @@ import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexe
22
22
  import type { SemanticModel } from '../../model/semantic-model.js';
23
23
  import type { WorkspaceResolutionIndex } from '../workspace-index.js';
24
24
  import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
25
+ import type { ScopeResolver } from '../contract/scope-resolver.js';
25
26
  import { type ConversionRankFn } from './overload-narrowing.js';
26
27
  export declare function emitFreeCallFallback(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, _referenceIndex: {
27
28
  readonly bySourceScope: ReadonlyMap<ScopeId, readonly Reference[]>;
@@ -44,6 +45,12 @@ export declare function emitFreeCallFallback(graph: KnowledgeGraph, scopes: Scop
44
45
  };
45
46
  }, callerParsed: ParsedFile, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[]) => readonly SymbolDefinition[] | undefined;
46
47
  readonly conversionRankFn?: ConversionRankFn;
48
+ /** Optional per-language constraint hook threaded into
49
+ * `narrowOverloadCandidates`. Drops candidates whose template
50
+ * constraints (e.g. C++ `enable_if_t`, C++20 `requires`) provably
51
+ * fail at the call site. Three-valued; `'unknown'` keeps the
52
+ * candidate (monotonicity). */
53
+ readonly constraintCompatibility?: ScopeResolver['constraintCompatibility'];
47
54
  }): number;
48
55
  /** Walk up from the call-site scope to the enclosing class scope,
49
56
  * pick a method member by name with overload narrowing on arity +
@@ -63,4 +70,7 @@ export declare function pickImplicitThisOverload(site: {
63
70
  readonly name: string;
64
71
  readonly arity?: number;
65
72
  readonly argumentTypes?: readonly string[];
66
- }, scopes: ScopeResolutionIndexes, workspaceIndex: WorkspaceResolutionIndex, model: SemanticModel, conversionRankFn?: ConversionRankFn): SymbolDefinition | undefined;
73
+ }, scopes: ScopeResolutionIndexes, workspaceIndex: WorkspaceResolutionIndex, model: SemanticModel, hookCtx?: {
74
+ readonly conversionRankFn?: ConversionRankFn;
75
+ readonly constraintCompatibility?: ScopeResolver['constraintCompatibility'];
76
+ }): SymbolDefinition | undefined;