gitnexus 1.6.5-rc.34 → 1.6.5-rc.36

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 (29) hide show
  1. package/dist/_shared/scope-resolution/symbol-definition.d.ts +2 -0
  2. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  3. package/dist/cli/setup.js +12 -0
  4. package/dist/core/ingestion/class-extractors/configs/c-cpp.js +38 -0
  5. package/dist/core/ingestion/class-extractors/generic.js +8 -0
  6. package/dist/core/ingestion/class-types.d.ts +15 -0
  7. package/dist/core/ingestion/import-resolvers/standard.js +7 -3
  8. package/dist/core/ingestion/languages/cpp/interpret.d.ts +6 -1
  9. package/dist/core/ingestion/languages/cpp/interpret.js +6 -9
  10. package/dist/core/ingestion/languages/cpp/query.js +31 -0
  11. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  12. package/dist/core/ingestion/model/symbol-table.js +3 -0
  13. package/dist/core/ingestion/parsing-processor.js +40 -1
  14. package/dist/core/ingestion/scope-extractor.js +4 -0
  15. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
  16. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +12 -0
  17. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +11 -0
  18. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +43 -1
  19. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  20. package/dist/core/ingestion/tree-sitter-queries.js +13 -0
  21. package/dist/core/ingestion/utils/ast-helpers.js +6 -1
  22. package/dist/core/ingestion/utils/template-arguments.d.ts +11 -0
  23. package/dist/core/ingestion/utils/template-arguments.js +65 -0
  24. package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
  25. package/dist/core/ingestion/workers/parse-worker.js +41 -1
  26. package/hooks/claude/gitnexus-hook.cjs +34 -3
  27. package/hooks/claude/hook-db-lock-probe.cjs +238 -0
  28. package/hooks/claude/win-rm-list-json.ps1 +76 -0
  29. package/package.json +1 -1
@@ -28,6 +28,8 @@ export interface SymbolDefinition {
28
28
  returnType?: string;
29
29
  /** Declared type for non-callable symbols — fields/properties (e.g. 'Address', 'List<User>') */
30
30
  declaredType?: string;
31
+ /** Generic/template specialization arguments for class-like symbols (e.g. ['User'], ['T*']). */
32
+ templateArguments?: string[];
31
33
  /** Links Method/Constructor/Property to owning Class/Struct/Trait nodeId */
32
34
  ownerId?: string;
33
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"symbol-definition.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/symbol-definition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB;;+FAE2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;iHAC6G;IAC7G,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;uFACmF;IACnF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
1
+ {"version":3,"file":"symbol-definition.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/symbol-definition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB;;+FAE2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;iHAC6G;IAC7G,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;uFACmF;IACnF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
package/dist/cli/setup.js CHANGED
@@ -314,6 +314,18 @@ async function installClaudeCodeHooks(result) {
314
314
  catch {
315
315
  // Helper not found in source — skip
316
316
  }
317
+ try {
318
+ await fs.copyFile(path.join(pluginHooksPath, 'hook-db-lock-probe.cjs'), path.join(destHooksDir, 'hook-db-lock-probe.cjs'));
319
+ }
320
+ catch {
321
+ // Helper not found in source — skip
322
+ }
323
+ try {
324
+ await fs.copyFile(path.join(pluginHooksPath, 'win-rm-list-json.ps1'), path.join(destHooksDir, 'win-rm-list-json.ps1'));
325
+ }
326
+ catch {
327
+ // Helper not found in source — skip
328
+ }
317
329
  const hookPath = path.join(destHooksDir, 'gitnexus-hook.cjs').replace(/\\/g, '/');
318
330
  // Escape backslashes FIRST, then quotes (CodeQL js/incomplete-sanitization).
319
331
  // The previous shape `replace(/"/g, '\\"')` alone would let `path\with"quote`
@@ -1,5 +1,27 @@
1
1
  // gitnexus/src/core/ingestion/class-extractors/configs/c-cpp.ts
2
2
  import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ import { extractTemplateArguments, stripTemplateArguments, } from '../../utils/template-arguments.js';
4
+ function shouldSkipCppTemplateDuplicateCapture(captureMap, definitionName, capturedName) {
5
+ if (captureMap['template-arguments'] !== undefined)
6
+ return false;
7
+ if (!definitionName)
8
+ return false;
9
+ const argsFromDefinitionName = extractTemplateArguments(definitionName);
10
+ if (argsFromDefinitionName === undefined)
11
+ return false;
12
+ const argsFromCaptureName = capturedName ? extractTemplateArguments(capturedName) : undefined;
13
+ // Generic class capture emits only `List`, while the specialization-aware
14
+ // capture emits `List` + `@declaration.template-arguments`. Skip the former
15
+ // when the declaration name itself is templated to avoid duplicate class defs.
16
+ return argsFromCaptureName === undefined;
17
+ }
18
+ function extractCppTemplateArgumentsWithFallback(captureMap, definitionName, capturedName) {
19
+ return ((captureMap['template-arguments']
20
+ ? extractTemplateArguments(captureMap['template-arguments'].text)
21
+ : undefined) ??
22
+ (definitionName ? extractTemplateArguments(definitionName) : undefined) ??
23
+ (capturedName ? extractTemplateArguments(capturedName) : undefined));
24
+ }
3
25
  export const cClassConfig = {
4
26
  language: SupportedLanguages.C,
5
27
  typeDeclarationNodes: ['struct_specifier', 'enum_specifier'],
@@ -8,4 +30,20 @@ export const cppClassConfig = {
8
30
  language: SupportedLanguages.CPlusPlus,
9
31
  typeDeclarationNodes: ['class_specifier', 'struct_specifier', 'enum_specifier'],
10
32
  ancestorScopeNodeTypes: ['namespace_definition', 'class_specifier', 'struct_specifier'],
33
+ extractName: (node) => {
34
+ const nameNode = node.childForFieldName?.('name');
35
+ if (!nameNode)
36
+ return undefined;
37
+ if (nameNode.type !== 'template_type')
38
+ return undefined;
39
+ return stripTemplateArguments(nameNode.text);
40
+ },
41
+ extractTemplateArguments: (node) => {
42
+ const nameNode = node.childForFieldName?.('name');
43
+ if (!nameNode || nameNode.type !== 'template_type')
44
+ return undefined;
45
+ return extractTemplateArguments(nameNode.text);
46
+ },
47
+ shouldSkipClassCapture: ({ captureMap, definitionNode, nameNode }) => shouldSkipCppTemplateDuplicateCapture(captureMap, definitionNode?.childForFieldName?.('name')?.text, nameNode?.text),
48
+ extractTemplateArgumentsFromCapture: ({ captureMap, definitionNode, nameNode }) => extractCppTemplateArgumentsWithFallback(captureMap, definitionNode?.childForFieldName?.('name')?.text, nameNode?.text),
11
49
  };
@@ -116,10 +116,12 @@ export function createClassExtractor(config) {
116
116
  (isClassLikeLabel(fallback?.type) ? fallback.type : undefined);
117
117
  if (!name || !type)
118
118
  return null;
119
+ const templateArguments = config.extractTemplateArguments?.(node);
119
120
  return {
120
121
  name,
121
122
  type,
122
123
  qualifiedName: buildQualifiedName(node, name) || name,
124
+ ...(templateArguments !== undefined ? { templateArguments } : {}),
123
125
  };
124
126
  };
125
127
  return {
@@ -131,5 +133,11 @@ export function createClassExtractor(config) {
131
133
  extractQualifiedName(node, simpleName) {
132
134
  return extract(node, { name: simpleName })?.qualifiedName ?? null;
133
135
  },
136
+ shouldSkipClassCapture(context) {
137
+ return config.shouldSkipClassCapture?.(context) ?? false;
138
+ },
139
+ extractTemplateArgumentsFromCapture(context) {
140
+ return config.extractTemplateArgumentsFromCapture?.(context);
141
+ },
134
142
  };
135
143
  }
@@ -5,6 +5,12 @@ export interface ExtractedClassSymbol {
5
5
  name: string;
6
6
  type: ClassLikeNodeLabel;
7
7
  qualifiedName: string;
8
+ templateArguments?: string[];
9
+ }
10
+ export interface ClassCaptureContext {
11
+ captureMap: Record<string, SyntaxNode>;
12
+ definitionNode: SyntaxNode | null;
13
+ nameNode: SyntaxNode | undefined;
8
14
  }
9
15
  /**
10
16
  * Cross-language qualified type names are normalized to dot-separated scope
@@ -21,6 +27,10 @@ export interface ClassExtractor {
21
27
  type?: NodeLabel | null;
22
28
  }): ExtractedClassSymbol | null;
23
29
  extractQualifiedName(node: SyntaxNode, simpleName: string): string | null;
30
+ shouldSkipClassCapture?(context: ClassCaptureContext & {
31
+ nodeLabel: ClassLikeNodeLabel;
32
+ }): boolean;
33
+ extractTemplateArgumentsFromCapture?(context: ClassCaptureContext): string[] | undefined;
24
34
  }
25
35
  export interface ClassExtractionConfig {
26
36
  language: SupportedLanguages;
@@ -31,4 +41,9 @@ export interface ClassExtractionConfig {
31
41
  extractName?: (node: SyntaxNode) => string | undefined;
32
42
  extractType?: (node: SyntaxNode) => ClassLikeNodeLabel | undefined;
33
43
  extractScopeSegments?: (node: SyntaxNode) => string[] | null | undefined;
44
+ extractTemplateArguments?: (node: SyntaxNode) => string[] | undefined;
45
+ shouldSkipClassCapture?(context: ClassCaptureContext & {
46
+ nodeLabel: ClassLikeNodeLabel;
47
+ }): boolean;
48
+ extractTemplateArgumentsFromCapture?(context: ClassCaptureContext): string[] | undefined;
34
49
  }
@@ -52,6 +52,13 @@ export const resolveImportPath = (currentFile, importPath, allFiles, allFileList
52
52
  const resolved = tryResolveWithExtensions(rewritten, allFiles);
53
53
  if (resolved)
54
54
  return cache(resolved);
55
+ // ESM fallback: strip .js/.jsx/.mjs/.cjs and retry with TS equivalents
56
+ const strippedAlias = stripJsExtension(rewritten);
57
+ if (strippedAlias !== null) {
58
+ const esmResolved = tryResolveWithExtensions(strippedAlias, allFiles);
59
+ if (esmResolved)
60
+ return cache(esmResolved);
61
+ }
55
62
  // Try suffix matching as fallback
56
63
  const parts = rewritten.split('/').filter(Boolean);
57
64
  const suffixResult = suffixResolve(parts, normalizedFileList, allFileList, index);
@@ -112,9 +119,6 @@ export const resolveImportPath = (currentFile, importPath, allFiles, allFileList
112
119
  return cache(resolved);
113
120
  // TypeScript ESM: imports use .js/.jsx/.mjs/.cjs but source files are
114
121
  // .ts/.tsx/.mts/.cts. Strip the JS-family extension and re-resolve.
115
- // NOTE: This fallback only applies to relative imports. Path alias imports
116
- // (e.g. @/utils.js via tsconfig paths) do not yet strip .js extensions —
117
- // that is a known limitation tracked for follow-up.
118
122
  if (language === SupportedLanguages.TypeScript || language === SupportedLanguages.JavaScript) {
119
123
  const stripped = stripJsExtension(basePath);
120
124
  if (stripped !== null) {
@@ -25,6 +25,11 @@ export declare function interpretCppImport(captures: CaptureMatch): ParsedImport
25
25
  export declare function interpretCppTypeBinding(captures: CaptureMatch): ParsedTypeBinding | null;
26
26
  /**
27
27
  * Normalize a C++ type name: strip pointer/array/reference syntax,
28
- * qualifiers, and template parameters (V1: generic-ignored).
28
+ * qualifiers, while preserving template arguments for specialization-aware
29
+ * receiver binding (`List<User>` vs `List<Order>`).
30
+ *
31
+ * Keeping template arguments here allows receiver-bound fallback to match
32
+ * specialization-specific class defs first; non-template behavior is preserved
33
+ * by base-name fallback in resolveClassBindingForName.
29
34
  */
30
35
  export declare function normalizeCppTypeName(text: string): string;
@@ -82,7 +82,12 @@ export function interpretCppTypeBinding(captures) {
82
82
  }
83
83
  /**
84
84
  * Normalize a C++ type name: strip pointer/array/reference syntax,
85
- * qualifiers, and template parameters (V1: generic-ignored).
85
+ * qualifiers, while preserving template arguments for specialization-aware
86
+ * receiver binding (`List<User>` vs `List<Order>`).
87
+ *
88
+ * Keeping template arguments here allows receiver-bound fallback to match
89
+ * specialization-specific class defs first; non-template behavior is preserved
90
+ * by base-name fallback in resolveClassBindingForName.
86
91
  */
87
92
  export function normalizeCppTypeName(text) {
88
93
  let t = text.trim();
@@ -90,14 +95,6 @@ export function normalizeCppTypeName(text) {
90
95
  t = t
91
96
  .replace(/\b(const|volatile|restrict|static|extern|inline|mutable|constexpr|consteval)\b/g, '')
92
97
  .trim();
93
- // Strip template parameters (loop handles nested: Map<List<int>> → Map)
94
- while (t.includes('<')) {
95
- const stripped = t.replace(/<[^<>]*>/g, '');
96
- if (stripped === t)
97
- break; // avoid infinite loop on malformed input
98
- t = stripped;
99
- }
100
- t = t.trim();
101
98
  // Strip pointer stars
102
99
  while (t.endsWith('*'))
103
100
  t = t.slice(0, -1).trim();
@@ -31,21 +31,47 @@ const CPP_SCOPE_QUERY = `
31
31
  name: (type_identifier) @declaration.name
32
32
  body: (field_declaration_list)) @declaration.class
33
33
 
34
+ (class_specifier
35
+ name: (template_type
36
+ (type_identifier) @declaration.name
37
+ (template_argument_list) @declaration.template-arguments)
38
+ body: (field_declaration_list)) @declaration.class
39
+
34
40
  (struct_specifier
35
41
  name: (type_identifier) @declaration.name
36
42
  body: (field_declaration_list)) @declaration.struct
37
43
 
44
+ (struct_specifier
45
+ name: (template_type
46
+ (type_identifier) @declaration.name
47
+ (template_argument_list) @declaration.template-arguments)
48
+ body: (field_declaration_list)) @declaration.struct
49
+
38
50
  ;; ─── Declarations — class / struct inside template_declaration ───────
39
51
  (template_declaration
40
52
  (class_specifier
41
53
  name: (type_identifier) @declaration.name
42
54
  body: (field_declaration_list)) @declaration.class)
43
55
 
56
+ (template_declaration
57
+ (class_specifier
58
+ name: (template_type
59
+ (type_identifier) @declaration.name
60
+ (template_argument_list) @declaration.template-arguments)
61
+ body: (field_declaration_list)) @declaration.class)
62
+
44
63
  (template_declaration
45
64
  (struct_specifier
46
65
  name: (type_identifier) @declaration.name
47
66
  body: (field_declaration_list)) @declaration.struct)
48
67
 
68
+ (template_declaration
69
+ (struct_specifier
70
+ name: (template_type
71
+ (type_identifier) @declaration.name
72
+ (template_argument_list) @declaration.template-arguments)
73
+ body: (field_declaration_list)) @declaration.struct)
74
+
49
75
  ;; ─── Declarations — enum ─────────────────────────────────────────────
50
76
  (enum_specifier
51
77
  name: (type_identifier) @declaration.name) @declaration.enum
@@ -222,6 +248,11 @@ const CPP_SCOPE_QUERY = `
222
248
  type: (type_identifier) @type-binding.type
223
249
  declarator: (identifier) @type-binding.name) @type-binding.annotation
224
250
 
251
+ ;; Covers: List<User> users;
252
+ (declaration
253
+ type: (template_type) @type-binding.type
254
+ declarator: (identifier) @type-binding.name) @type-binding.annotation
255
+
225
256
  ;; ─── Type bindings — pointer variable declaration ───────────────────
226
257
  ;; Covers: User* ptr = new User()
227
258
  (declaration
@@ -99,6 +99,7 @@ export interface AddMetadata {
99
99
  parameterTypes?: string[];
100
100
  returnType?: string;
101
101
  declaredType?: string;
102
+ templateArguments?: string[];
102
103
  ownerId?: string;
103
104
  qualifiedName?: string;
104
105
  }
@@ -130,6 +130,9 @@ export const createSymbolTable = () => {
130
130
  : {}),
131
131
  ...(metadata?.returnType !== undefined ? { returnType: metadata.returnType } : {}),
132
132
  ...(metadata?.declaredType !== undefined ? { declaredType: metadata.declaredType } : {}),
133
+ ...(metadata?.templateArguments !== undefined
134
+ ? { templateArguments: metadata.templateArguments }
135
+ : {}),
133
136
  ...(metadata?.ownerId !== undefined ? { ownerId: metadata.ownerId } : {}),
134
137
  };
135
138
  // A. File Index — unconditional.
@@ -11,6 +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
15
  import { logger } from '../logger.js';
15
16
  import { getTreeSitterBufferSize, getTreeSitterContentByteLength, TREE_SITTER_MAX_BUFFER, } from './constants.js';
16
17
  // ============================================================================
@@ -58,6 +59,7 @@ export const mergeChunkResults = (graph, symbolTable, chunkResults) => {
58
59
  parameterTypes: sym.parameterTypes,
59
60
  returnType: sym.returnType,
60
61
  declaredType: sym.declaredType,
62
+ templateArguments: sym.templateArguments,
61
63
  ownerId: sym.ownerId,
62
64
  qualifiedName: sym.qualifiedName,
63
65
  });
@@ -365,6 +367,20 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
365
367
  })
366
368
  : null;
367
369
  const nodeLabel = extractedClassSymbol?.type ?? defaultNodeLabel;
370
+ const isClassLikeLabel = nodeLabel === 'Class' ||
371
+ nodeLabel === 'Struct' ||
372
+ nodeLabel === 'Interface' ||
373
+ nodeLabel === 'Enum' ||
374
+ nodeLabel === 'Record';
375
+ if (isClassLikeLabel &&
376
+ provider.classExtractor?.shouldSkipClassCapture?.({
377
+ captureMap,
378
+ definitionNode,
379
+ nameNode,
380
+ nodeLabel,
381
+ }) === true) {
382
+ return;
383
+ }
368
384
  // Synthesize name for constructors without explicit @name capture (e.g. Swift init)
369
385
  if (!nameNode && nodeLabel !== 'Constructor' && !extractedClassSymbol)
370
386
  return;
@@ -464,7 +480,26 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
464
480
  arityTag += typeTagForId(cached.map, nodeName, arityForId, seqDefMethodInfo, language, cached.groups);
465
481
  arityTag += constTagForId(cached.map, nodeName, arityForId, seqDefMethodInfo, cached.groups);
466
482
  }
467
- const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${arityTag}`);
483
+ const classTemplateArguments = extractedClassSymbol?.templateArguments ??
484
+ provider.classExtractor?.extractTemplateArgumentsFromCapture?.({
485
+ captureMap,
486
+ definitionNode,
487
+ nameNode,
488
+ }) ??
489
+ (captureMap['template-arguments']
490
+ ? extractTemplateArguments(captureMap['template-arguments'].text)
491
+ : undefined) ??
492
+ (nameNode && nameNode.text ? extractTemplateArguments(nameNode.text) : undefined);
493
+ const classTemplateTag = (nodeLabel === 'Class' ||
494
+ nodeLabel === 'Struct' ||
495
+ nodeLabel === 'Interface' ||
496
+ nodeLabel === 'Enum' ||
497
+ nodeLabel === 'Record') &&
498
+ classTemplateArguments !== undefined &&
499
+ classTemplateArguments.length > 0
500
+ ? templateArgumentsIdTag(classTemplateArguments)
501
+ : '';
502
+ const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${classTemplateTag}${arityTag}`);
468
503
  const classNodeForSymbol = definitionNodeForRange || definitionNode || nameNode;
469
504
  const qualifiedTypeName = extractedClassSymbol?.qualifiedName ??
470
505
  (classNodeForSymbol && provider.classExtractor?.isTypeDeclaration(classNodeForSymbol)
@@ -490,6 +525,9 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
490
525
  ? isVueSetupTopLevel(nameNode || definitionNodeForRange)
491
526
  : cachedExportCheck(provider.exportChecker, nameNode || definitionNodeForRange, nodeName),
492
527
  ...(qualifiedTypeName !== undefined ? { qualifiedName: qualifiedTypeName } : {}),
528
+ ...(classTemplateArguments !== undefined && classTemplateArguments.length > 0
529
+ ? { templateArguments: classTemplateArguments }
530
+ : {}),
493
531
  ...(frameworkHint
494
532
  ? {
495
533
  astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
@@ -543,6 +581,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
543
581
  parameterTypes: methodProps.parameterTypes,
544
582
  returnType: methodProps.returnType,
545
583
  declaredType,
584
+ templateArguments: classTemplateArguments,
546
585
  ownerId: enclosingClassId ?? undefined,
547
586
  qualifiedName: qualifiedTypeName,
548
587
  });
@@ -59,6 +59,7 @@
59
59
  * - `ParsedFile.referenceSites` — pre-resolution usage facts.
60
60
  */
61
61
  import { buildPositionIndex, buildScopeTree, canParentScope, makeScopeId } from '../../_shared/index.js';
62
+ import { extractTemplateArguments } from './utils/template-arguments.js';
62
63
  // ─── Public entry point ─────────────────────────────────────────────────────
63
64
  /**
64
65
  * Drive the five extraction passes and return a `ParsedFile`.
@@ -362,6 +363,8 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
362
363
  return undefined;
363
364
  const qualifiedCap = match['@declaration.qualified_name'];
364
365
  const qualifiedName = qualifiedCap?.text;
366
+ const templateArguments = extractTemplateArguments(match['@declaration.template-arguments']?.text ?? '') ??
367
+ extractTemplateArguments(qualifiedName ?? nameCap.text);
365
368
  // Optional arity metadata — producers (e.g. Python emit-captures)
366
369
  // synthesize these on function/method declarations. Their absence is
367
370
  // the normal case for other producers; readers treat undefined as
@@ -381,6 +384,7 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
381
384
  ...(parameterTypes !== undefined ? { parameterTypes } : {}),
382
385
  ...(declaredType !== undefined ? { declaredType } : {}),
383
386
  ...(returnType !== undefined ? { returnType } : {}),
387
+ ...(templateArguments !== undefined ? { templateArguments } : {}),
384
388
  };
385
389
  }
386
390
  function parseIntCapture(cap) {
@@ -40,6 +40,7 @@ export declare function resolveDefGraphId(filePath: string, def: {
40
40
  qualifiedName?: string;
41
41
  type?: NodeLabel;
42
42
  parameterTypes?: readonly string[];
43
+ templateArguments?: readonly string[];
43
44
  }, nodeLookup: GraphNodeLookup): string | undefined;
44
45
  /** Derive the simple (unqualified) name of a def from its `qualifiedName`. */
45
46
  export declare function simpleQualifiedName(def: SymbolDefinition): string | undefined;
@@ -79,6 +79,18 @@ export function resolveDefGraphId(filePath, def, nodeLookup) {
79
79
  if (pHit !== undefined)
80
80
  return pHit;
81
81
  }
82
+ if ((def.type === 'Class' ||
83
+ def.type === 'Struct' ||
84
+ def.type === 'Interface' ||
85
+ def.type === 'Enum' ||
86
+ def.type === 'Record') &&
87
+ def.templateArguments !== undefined &&
88
+ def.templateArguments.length > 0) {
89
+ const tKey = qualifiedKey(filePath, def.type, `${qn}~${def.templateArguments.join(',')}`);
90
+ const tHit = nodeLookup.get(tKey);
91
+ if (tHit !== undefined)
92
+ return tHit;
93
+ }
82
94
  const qualifiedHit = nodeLookup.get(qualifiedKey(filePath, def.type, qn));
83
95
  if (qualifiedHit !== undefined)
84
96
  return qualifiedHit;
@@ -86,6 +86,17 @@ export function buildGraphNodeLookup(graph) {
86
86
  // Each overload is unique — set unconditionally.
87
87
  lookup.set(pKey, node.id);
88
88
  }
89
+ if ((node.label === 'Class' ||
90
+ node.label === 'Struct' ||
91
+ node.label === 'Interface' ||
92
+ node.label === 'Enum' ||
93
+ node.label === 'Record') &&
94
+ props.templateArguments !== undefined &&
95
+ props.templateArguments.length > 0) {
96
+ const tKey = qualifiedKey(props.filePath, node.label, `${qualified}~${props.templateArguments.join(',')}`);
97
+ if (!lookup.has(tKey))
98
+ lookup.set(tKey, node.id);
99
+ }
89
100
  }
90
101
  // Fallback key: simple name. First-wins within a file — used when
91
102
  // the caller doesn't know the qualifier (unqualified free-call
@@ -37,6 +37,48 @@ import { tryEmitEdge } from '../graph-bridge/edges.js';
37
37
  import { resolveCompoundReceiverClass } from '../passes/compound-receiver.js';
38
38
  import { resolveDefGraphId } from '../graph-bridge/ids.js';
39
39
  import { narrowOverloadCandidates, isOverloadAmbiguousAfterNormalization, } from './overload-narrowing.js';
40
+ import { extractTemplateArguments, stripTemplateArguments, } from '../../utils/template-arguments.js';
41
+ function normalizeTemplateArgToken(value) {
42
+ return value.replace(/\s+/g, '');
43
+ }
44
+ function resolveClassBindingForName(scopeId, rawClassName, scopes) {
45
+ const direct = findClassBindingInScope(scopeId, rawClassName, scopes);
46
+ if (direct !== undefined)
47
+ return direct;
48
+ if (!rawClassName.includes('<'))
49
+ return undefined;
50
+ const baseName = stripTemplateArguments(rawClassName).replace(/\s+/g, '');
51
+ if (baseName.length === 0)
52
+ return undefined;
53
+ const wantedArgs = extractTemplateArguments(rawClassName)?.map(normalizeTemplateArgToken);
54
+ if (wantedArgs !== undefined && wantedArgs.length > 0) {
55
+ // qualifiedNames is a Map and may not contain the stripped base name at all
56
+ // (e.g., unresolved type binding or only template-qualified entries), so
57
+ // default to [] before checking `.length`.
58
+ const qnameIds = scopes.qualifiedNames.get(baseName) ?? [];
59
+ if (qnameIds.length === 0) {
60
+ return findClassBindingInScope(scopeId, baseName, scopes);
61
+ }
62
+ const matches = [];
63
+ for (const id of qnameIds) {
64
+ const def = scopes.defs.get(id);
65
+ if (def === undefined || !isClassLike(def.type))
66
+ continue;
67
+ const defArgs = def.templateArguments?.map(normalizeTemplateArgToken);
68
+ if (defArgs !== undefined &&
69
+ defArgs.length === wantedArgs.length &&
70
+ defArgs.every((value, i) => value === wantedArgs[i])) {
71
+ matches.push(def);
72
+ }
73
+ }
74
+ if (matches.length === 1)
75
+ return matches[0];
76
+ // Scope extractor only records class definitions with bodies in C++, so
77
+ // forward declarations are not expected here. Keep fallback behavior for
78
+ // safety in non-ODR or mixed-language edge cases.
79
+ }
80
+ return findClassBindingInScope(scopeId, baseName, scopes);
81
+ }
40
82
  export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, handledSites, provider, index, model) {
41
83
  let emitted = 0;
42
84
  // Per-pass dedup so the multiple cases don't double-emit if two of
@@ -322,7 +364,7 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
322
364
  }
323
365
  // ── Case 4: simple typeBinding (`u: U`) ──────────────────────
324
366
  if (typeRef !== undefined && !typeRef.rawName.includes('.')) {
325
- let ownerDef = findClassBindingInScope(site.inScope, typeRef.rawName, scopes);
367
+ let ownerDef = resolveClassBindingForName(site.inScope, typeRef.rawName, scopes);
326
368
  // `findClassBindingInScope(..., typeRef.rawName)` only works when
327
369
  // rawName is itself a class symbol reachable through scope bindings.
328
370
  // For languages with namespace-style imports (Go), imported types
@@ -4,7 +4,7 @@ export declare const PYTHON_QUERIES = "\n(class_definition\n name: (identifier)
4
4
  export declare const JAVA_QUERIES = "\n; Classes, Interfaces, Enums, Annotations\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(enum_declaration name: (identifier) @name) @definition.enum\n(annotation_type_declaration name: (identifier) @name) @definition.annotation\n\n; Methods & Constructors\n(method_declaration name: (identifier) @name) @definition.method\n(constructor_declaration name: (identifier) @name) @definition.constructor\n\n; Fields \u2014 typed field declarations inside class bodies\n(field_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.property\n\n; Imports - capture any import declaration child as source\n(import_declaration (_) @import.source) @import\n\n; Calls\n(method_invocation name: (identifier) @call.name) @call\n(method_invocation object: (_) name: (identifier) @call.name) @call\n(method_reference) @call\n\n; Constructor calls: new Foo()\n(object_creation_expression type: (type_identifier) @call.name) @call\n\n; Local variable declarations inside method bodies\n(local_variable_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.variable\n\n; Heritage - extends class\n(class_declaration name: (identifier) @heritage.class\n (superclass (type_identifier) @heritage.extends)) @heritage\n\n; Heritage - implements interfaces\n(class_declaration name: (identifier) @heritage.class\n (super_interfaces (type_list (type_identifier) @heritage.implements))) @heritage.impl\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_access\n object: (_) @assignment.receiver\n field: (identifier) @assignment.property)\n right: (_)) @assignment\n";
5
5
  export declare const C_QUERIES = "\n; Functions (direct declarator)\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n\n; Functions returning pointers (pointer_declarator wraps function_declarator)\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n\n; Functions returning double pointers (nested pointer_declarator)\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name)))) @definition.function\n\n; Structs, Unions, Enums, Typedefs\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(union_specifier name: (type_identifier) @name) @definition.union\n(enum_specifier name: (type_identifier) @name) @definition.enum\n(type_definition declarator: (type_identifier) @name) @definition.typedef\n\n; Macros\n(preproc_function_def name: (identifier) @name) @definition.macro\n(preproc_def name: (identifier) @name) @definition.macro\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n\n; Variable declarations: int x = 5; or int x;\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name)) @definition.variable\n";
6
6
  export declare const GO_QUERIES = "\n; Functions & Methods\n(function_declaration name: (identifier) @name) @definition.function\n(method_declaration name: (field_identifier) @name) @definition.method\n\n; Types\n(type_declaration (type_spec name: (type_identifier) @name type: (struct_type))) @definition.struct\n(type_declaration (type_spec name: (type_identifier) @name type: (interface_type))) @definition.interface\n\n; Imports\n(import_declaration (import_spec path: (interpreted_string_literal) @import.source)) @import\n(import_declaration (import_spec_list (import_spec path: (interpreted_string_literal) @import.source))) @import\n\n; Struct fields \u2014 named field declarations inside struct types\n(field_declaration_list\n (field_declaration\n name: (field_identifier) @name) @definition.property)\n\n; Struct embedding (anonymous fields = inheritance)\n(type_declaration\n (type_spec\n name: (type_identifier) @heritage.class\n type: (struct_type\n (field_declaration_list\n (field_declaration\n type: (type_identifier) @heritage.extends))))) @definition.struct\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (selector_expression field: (field_identifier) @call.name)) @call\n\n; Const/var declarations\n(const_declaration (const_spec name: (identifier) @name)) @definition.const\n(var_declaration (var_spec name: (identifier) @name)) @definition.variable\n\n; Short variable declaration: x := 5\n(short_var_declaration left: (expression_list (identifier) @name)) @definition.variable\n\n; Struct literal construction: User{Name: \"Alice\"}\n(composite_literal type: (type_identifier) @call.name) @call\n\n; Write access: obj.field = value\n(assignment_statement\n left: (expression_list\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property))\n right: (_)) @assignment\n\n; Write access: obj.field++ / obj.field--\n(inc_statement\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)) @assignment\n(dec_statement\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)) @assignment\n";
7
- export declare const CPP_QUERIES = "\n; Classes, Structs, Namespaces\n(class_specifier name: (type_identifier) @name) @definition.class\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(namespace_definition name: (namespace_identifier) @name) @definition.namespace\n(enum_specifier name: (type_identifier) @name) @definition.enum\n\n; Typedefs and unions (common in C-style headers and mixed C/C++ code)\n(type_definition declarator: (type_identifier) @name) @definition.typedef\n(union_specifier name: (type_identifier) @name) @definition.union\n\n; Macros\n(preproc_function_def name: (identifier) @name) @definition.macro\n(preproc_def name: (identifier) @name) @definition.macro\n\n; Functions & Methods (direct declarator)\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @definition.method\n\n; Functions/methods returning pointers (pointer_declarator wraps function_declarator)\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n\n; Functions/methods returning double pointers (nested pointer_declarator)\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name)))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))))) @definition.method\n\n; Functions/methods returning references (reference_declarator wraps function_declarator)\n(function_definition declarator: (reference_declarator (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (reference_declarator (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n\n; Destructors (destructor_name is distinct from identifier in tree-sitter-cpp)\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (destructor_name) @name))) @definition.method\n\n; Function declarations / prototypes (common in headers)\n(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n\n; Class/struct data member fields (Address address; int count;)\n; Uses field_identifier to exclude method declarations (which use function_declarator)\n(field_declaration\n declarator: (field_identifier) @name) @definition.property\n\n; Pointer member fields (Address* address;)\n(field_declaration\n declarator: (pointer_declarator\n declarator: (field_identifier) @name)) @definition.property\n\n; Reference member fields (Address& address;)\n(field_declaration\n declarator: (reference_declarator\n (field_identifier) @name)) @definition.property\n\n; Inline class method declarations (inside class body, no body: void save();)\n; tree-sitter-cpp uses field_identifier (not identifier) for names inside class bodies\n(field_declaration declarator: (function_declarator declarator: [(field_identifier) (identifier)] @name)) @definition.method\n\n; Inline class method declarations returning a pointer (User* lookup();)\n(field_declaration declarator: (pointer_declarator declarator: (function_declarator declarator: [(field_identifier) (identifier)] @name))) @definition.method\n\n; Inline class method declarations returning a reference (User& lookup();)\n(field_declaration declarator: (reference_declarator (function_declarator declarator: [(field_identifier) (identifier)] @name))) @definition.method\n\n; Inline class method definitions (inside class body, with body: void Foo() { ... })\n(field_declaration_list\n (function_definition\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name) (destructor_name)] @name)) @definition.method)\n\n; Inline class methods returning a pointer type (User* lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (pointer_declarator\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Inline class methods returning a reference type (User& lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (reference_declarator\n (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Templates\n(template_declaration (class_specifier name: (type_identifier) @name)) @definition.template\n(template_declaration (function_definition declarator: (function_declarator declarator: (identifier) @name))) @definition.template\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (qualified_identifier name: (identifier) @call.name)) @call\n(call_expression function: (template_function name: (identifier) @call.name)) @call\n\n; Constructor calls: new User()\n(new_expression type: (type_identifier) @call.name) @call\n\n; Variable declarations: int x = 5; or auto x = 5;\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name)) @definition.variable\n\n; Heritage\n(class_specifier name: (type_identifier) @heritage.class\n (base_class_clause (type_identifier) @heritage.extends)) @heritage\n(class_specifier name: (type_identifier) @heritage.class\n (base_class_clause (access_specifier) (type_identifier) @heritage.extends)) @heritage\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_expression\n argument: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n\n";
7
+ export declare const CPP_QUERIES = "\n; Classes, Structs, Namespaces\n(class_specifier name: (type_identifier) @name) @definition.class\n(class_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments)) @definition.class\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(struct_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments)) @definition.struct\n(namespace_definition name: (namespace_identifier) @name) @definition.namespace\n(enum_specifier name: (type_identifier) @name) @definition.enum\n\n; Typedefs and unions (common in C-style headers and mixed C/C++ code)\n(type_definition declarator: (type_identifier) @name) @definition.typedef\n(union_specifier name: (type_identifier) @name) @definition.union\n\n; Macros\n(preproc_function_def name: (identifier) @name) @definition.macro\n(preproc_def name: (identifier) @name) @definition.macro\n\n; Functions & Methods (direct declarator)\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @definition.method\n\n; Functions/methods returning pointers (pointer_declarator wraps function_declarator)\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n\n; Functions/methods returning double pointers (nested pointer_declarator)\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name)))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))))) @definition.method\n\n; Functions/methods returning references (reference_declarator wraps function_declarator)\n(function_definition declarator: (reference_declarator (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (reference_declarator (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n\n; Destructors (destructor_name is distinct from identifier in tree-sitter-cpp)\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (destructor_name) @name))) @definition.method\n\n; Function declarations / prototypes (common in headers)\n(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n\n; Class/struct data member fields (Address address; int count;)\n; Uses field_identifier to exclude method declarations (which use function_declarator)\n(field_declaration\n declarator: (field_identifier) @name) @definition.property\n\n; Pointer member fields (Address* address;)\n(field_declaration\n declarator: (pointer_declarator\n declarator: (field_identifier) @name)) @definition.property\n\n; Reference member fields (Address& address;)\n(field_declaration\n declarator: (reference_declarator\n (field_identifier) @name)) @definition.property\n\n; Inline class method declarations (inside class body, no body: void save();)\n; tree-sitter-cpp uses field_identifier (not identifier) for names inside class bodies\n(field_declaration declarator: (function_declarator declarator: [(field_identifier) (identifier)] @name)) @definition.method\n\n; Inline class method declarations returning a pointer (User* lookup();)\n(field_declaration declarator: (pointer_declarator declarator: (function_declarator declarator: [(field_identifier) (identifier)] @name))) @definition.method\n\n; Inline class method declarations returning a reference (User& lookup();)\n(field_declaration declarator: (reference_declarator (function_declarator declarator: [(field_identifier) (identifier)] @name))) @definition.method\n\n; Inline class method definitions (inside class body, with body: void Foo() { ... })\n(field_declaration_list\n (function_definition\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name) (destructor_name)] @name)) @definition.method)\n\n; Inline class methods returning a pointer type (User* lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (pointer_declarator\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Inline class methods returning a reference type (User& lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (reference_declarator\n (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Templates\n(template_declaration (class_specifier name: (type_identifier) @name)) @definition.template\n(template_declaration\n (class_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments))) @definition.template\n(template_declaration (function_definition declarator: (function_declarator declarator: (identifier) @name))) @definition.template\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (qualified_identifier name: (identifier) @call.name)) @call\n(call_expression function: (template_function name: (identifier) @call.name)) @call\n\n; Constructor calls: new User()\n(new_expression type: (type_identifier) @call.name) @call\n\n; Variable declarations: int x = 5; or auto x = 5;\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name)) @definition.variable\n\n; Heritage\n(class_specifier name: (type_identifier) @heritage.class\n (base_class_clause (type_identifier) @heritage.extends)) @heritage\n(class_specifier name: (type_identifier) @heritage.class\n (base_class_clause (access_specifier) (type_identifier) @heritage.extends)) @heritage\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_expression\n argument: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n\n";
8
8
  export declare const CSHARP_QUERIES = "\n; Types\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(struct_declaration name: (identifier) @name) @definition.struct\n(enum_declaration name: (identifier) @name) @definition.enum\n(record_declaration name: (identifier) @name) @definition.record\n(delegate_declaration name: (identifier) @name) @definition.delegate\n\n; Namespaces (block form and C# 10+ file-scoped form)\n(namespace_declaration name: (identifier) @name) @definition.namespace\n(namespace_declaration name: (qualified_name) @name) @definition.namespace\n(file_scoped_namespace_declaration name: (identifier) @name) @definition.namespace\n(file_scoped_namespace_declaration name: (qualified_name) @name) @definition.namespace\n\n; Methods & Properties\n(method_declaration name: (identifier) @name) @definition.method\n(local_function_statement name: (identifier) @name) @definition.function\n(constructor_declaration name: (identifier) @name) @definition.constructor\n(property_declaration name: (identifier) @name) @definition.property\n\n; Primary constructors (C# 12): class User(string name, int age) { }\n(class_declaration name: (identifier) @name (parameter_list) @definition.constructor)\n(record_declaration name: (identifier) @name (parameter_list) @definition.constructor)\n\n; Using\n(using_directive (qualified_name) @import.source) @import\n(using_directive (identifier) @import.source) @import\n\n; Calls\n(invocation_expression function: (identifier) @call.name) @call\n(invocation_expression function: (member_access_expression name: (identifier) @call.name)) @call\n\n; Null-conditional method calls: user?.Save()\n; Parses as: invocation_expression \u2192 conditional_access_expression \u2192 member_binding_expression \u2192 identifier\n(invocation_expression\n function: (conditional_access_expression\n (member_binding_expression\n (identifier) @call.name))) @call\n\n; Constructor calls: new Foo() and new Foo { Props }\n(object_creation_expression type: (identifier) @call.name) @call\n\n; Target-typed new (C# 9): User u = new(\"x\", 5)\n(variable_declaration type: (identifier) @call.name (variable_declarator (implicit_object_creation_expression) @call))\n\n; Local variable declarations\n(local_declaration_statement\n (variable_declaration\n (variable_declarator\n (identifier) @name))) @definition.variable\n\n; Heritage\n(class_declaration name: (identifier) @heritage.class\n (base_list (identifier) @heritage.extends)) @heritage\n(class_declaration name: (identifier) @heritage.class\n (base_list (generic_name (identifier) @heritage.extends))) @heritage\n\n; Interface inheritance: interface IFoo : IBar / interface IFoo : IBar, IBaz\n; Without these patterns, interface-to-interface relationships are never\n; captured, so transitive \"class X implements IBar\" chains are broken.\n(interface_declaration name: (identifier) @heritage.class\n (base_list (identifier) @heritage.extends)) @heritage\n(interface_declaration name: (identifier) @heritage.class\n (base_list (generic_name (identifier) @heritage.extends))) @heritage\n\n; Write access: obj.field = value\n(assignment_expression\n left: (member_access_expression\n expression: (_) @assignment.receiver\n name: (identifier) @assignment.property)\n right: (_)) @assignment\n";
9
9
  export declare const RUST_QUERIES = "\n; Functions & Items\n(function_item name: (identifier) @name) @definition.function\n(function_signature_item name: (identifier) @name) @definition.function\n(struct_item name: (type_identifier) @name) @definition.struct\n(enum_item name: (type_identifier) @name) @definition.enum\n(trait_item name: (type_identifier) @name) @definition.trait\n(impl_item type: (type_identifier) @name !trait) @definition.impl\n(impl_item type: (generic_type type: (type_identifier) @name) !trait) @definition.impl\n(mod_item name: (identifier) @name) @definition.module\n\n; Type aliases, const, static, macros\n(type_item name: (type_identifier) @name) @definition.type\n(const_item name: (identifier) @name) @definition.const\n(static_item name: (identifier) @name) @definition.static\n(macro_definition name: (identifier) @name) @definition.macro\n\n; Use statements\n(use_declaration argument: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (scoped_identifier name: (identifier) @call.name)) @call\n(call_expression function: (generic_function function: (identifier) @call.name)) @call\n\n; Struct literal construction: User { name: value }\n(struct_expression name: (type_identifier) @call.name) @call\n\n; Struct fields \u2014 named field declarations inside struct bodies\n(field_declaration_list\n (field_declaration\n name: (field_identifier) @name) @definition.property)\n\n; Heritage (trait implementation) \u2014 all combinations of concrete/generic trait \u00D7 concrete/generic type\n(impl_item trait: (type_identifier) @heritage.trait type: (type_identifier) @heritage.class) @heritage\n(impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (type_identifier) @heritage.class) @heritage\n(impl_item trait: (type_identifier) @heritage.trait type: (generic_type type: (type_identifier) @heritage.class)) @heritage\n(impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (generic_type type: (type_identifier) @heritage.class)) @heritage\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_expression\n value: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n\n; Write access: obj.field += value (compound assignment)\n(compound_assignment_expr\n left: (field_expression\n value: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n";
10
10
  export declare const PHP_QUERIES = "\n; \u2500\u2500 Namespace \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(namespace_definition\n name: (namespace_name) @name) @definition.namespace\n\n; \u2500\u2500 Classes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @name) @definition.class\n\n; \u2500\u2500 Interfaces \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(interface_declaration\n name: (name) @name) @definition.interface\n\n; \u2500\u2500 Traits \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(trait_declaration\n name: (name) @name) @definition.trait\n\n; \u2500\u2500 Enums (PHP 8.1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(enum_declaration\n name: (name) @name) @definition.enum\n\n; \u2500\u2500 Top-level functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(function_definition\n name: (name) @name) @definition.function\n\n; \u2500\u2500 Methods (including constructors) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(method_declaration\n name: (name) @name) @definition.method\n\n; \u2500\u2500 Class properties (including Eloquent $fillable, $casts, etc.) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(property_declaration\n (property_element\n (variable_name\n (name) @name))) @definition.property\n\n; Constructor property promotion (PHP 8.0+: public Address $address in __construct)\n(method_declaration\n parameters: (formal_parameters\n (property_promotion_parameter\n name: (variable_name\n (name) @name)))) @definition.property\n\n; \u2500\u2500 Imports: use statements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; Simple: use App\\Models\\User;\n(namespace_use_declaration\n (namespace_use_clause\n (qualified_name) @import.source)) @import\n\n; \u2500\u2500 Function/method calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; Regular function call: foo()\n(function_call_expression\n function: (name) @call.name) @call\n\n; Method call: $obj->method()\n(member_call_expression\n name: (name) @call.name) @call\n\n; Nullsafe method call: $obj?->method()\n(nullsafe_member_call_expression\n name: (name) @call.name) @call\n\n; Static call: Foo::bar() (php_only uses scoped_call_expression)\n(scoped_call_expression\n name: (name) @call.name) @call\n\n; Constructor call: new User()\n(object_creation_expression (name) @call.name) @call\n\n; Const declarations at class scope\n(const_declaration\n (const_element\n (name) @name)) @definition.const\n\n; \u2500\u2500 Heritage: extends \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n (base_clause\n [(name) (qualified_name)] @heritage.extends)) @heritage\n\n; \u2500\u2500 Heritage: implements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n (class_interface_clause\n [(name) (qualified_name)] @heritage.implements)) @heritage.impl\n\n; \u2500\u2500 Heritage: use trait (must capture enclosing class name) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n body: (declaration_list\n (use_declaration\n [(name) (qualified_name)] @heritage.trait))) @heritage\n\n; \u2500\u2500 Heritage: trait uses another trait (transitive trait composition) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; PHP allows a trait body to contain \"use OtherTrait;\". The trait-uses-trait\n; IMPLEMENTS edge is required by buildPhpMro to compute the full transitive\n; trait closure (depth 3+ chains).\n(trait_declaration\n name: (name) @heritage.class\n body: (declaration_list\n (use_declaration\n [(name) (qualified_name)] @heritage.trait))) @heritage\n\n; PHP HTTP consumers: file_get_contents('/path'), curl_init('/path')\n(function_call_expression\n function: (name) @_php_http (#match? @_php_http \"^(file_get_contents|curl_init)$\")\n arguments: (arguments\n (argument (string (string_content) @http_client.url)))) @http_client\n\n; Write access: $obj->field = value\n(assignment_expression\n left: (member_access_expression\n object: (_) @assignment.receiver\n name: (name) @assignment.property)\n right: (_)) @assignment\n\n; Write access: ClassName::$field = value (static property)\n(assignment_expression\n left: (scoped_property_access_expression\n scope: (_) @assignment.receiver\n name: (variable_name (name) @assignment.property))\n right: (_)) @assignment\n";
@@ -673,7 +673,15 @@ export const GO_QUERIES = `
673
673
  export const CPP_QUERIES = `
674
674
  ; Classes, Structs, Namespaces
675
675
  (class_specifier name: (type_identifier) @name) @definition.class
676
+ (class_specifier
677
+ name: (template_type
678
+ (type_identifier) @name
679
+ (template_argument_list) @template-arguments)) @definition.class
676
680
  (struct_specifier name: (type_identifier) @name) @definition.struct
681
+ (struct_specifier
682
+ name: (template_type
683
+ (type_identifier) @name
684
+ (template_argument_list) @template-arguments)) @definition.struct
677
685
  (namespace_definition name: (namespace_identifier) @name) @definition.namespace
678
686
  (enum_specifier name: (type_identifier) @name) @definition.enum
679
687
 
@@ -755,6 +763,11 @@ export const CPP_QUERIES = `
755
763
 
756
764
  ; Templates
757
765
  (template_declaration (class_specifier name: (type_identifier) @name)) @definition.template
766
+ (template_declaration
767
+ (class_specifier
768
+ name: (template_type
769
+ (type_identifier) @name
770
+ (template_argument_list) @template-arguments))) @definition.template
758
771
  (template_declaration (function_definition declarator: (function_declarator declarator: (identifier) @name))) @definition.template
759
772
 
760
773
  ; Includes
@@ -1,4 +1,5 @@
1
1
  import { generateId } from '../../../lib/utils.js';
2
+ import { extractTemplateArguments, stripTemplateArguments, templateArgumentsIdTag, } from './template-arguments.js';
2
3
  /**
3
4
  * Ordered list of definition capture keys for tree-sitter query matches.
4
5
  * Used to extract the definition node from a capture map.
@@ -370,8 +371,12 @@ export const findEnclosingClassInfo = (node, filePath, resolveEnclosingOwner) =>
370
371
  current.children?.some((c) => c.type === 'interface')) {
371
372
  label = 'Interface';
372
373
  }
374
+ const templateArguments = extractTemplateArguments(nameNode.text);
375
+ const classIdName = templateArguments !== undefined
376
+ ? `${stripTemplateArguments(nameNode.text)}${templateArgumentsIdTag(templateArguments)}`
377
+ : nameNode.text;
373
378
  return {
374
- classId: generateId(label, `${filePath}:${nameNode.text}`),
379
+ classId: generateId(label, `${filePath}:${classIdName}`),
375
380
  className: nameNode.text,
376
381
  };
377
382
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Parse top-level generic/template arguments from a type-like string.
3
+ *
4
+ * Examples:
5
+ * - `List<int>` -> ['int']
6
+ * - `Map<string, vector<int>>` -> ['string', 'vector<int>']
7
+ * - `List<T*>` -> ['T*']
8
+ */
9
+ export declare function extractTemplateArguments(text: string): string[] | undefined;
10
+ export declare function stripTemplateArguments(text: string): string;
11
+ export declare function templateArgumentsIdTag(templateArguments?: readonly string[]): string;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Parse top-level generic/template arguments from a type-like string.
3
+ *
4
+ * Examples:
5
+ * - `List<int>` -> ['int']
6
+ * - `Map<string, vector<int>>` -> ['string', 'vector<int>']
7
+ * - `List<T*>` -> ['T*']
8
+ */
9
+ export function extractTemplateArguments(text) {
10
+ const start = text.indexOf('<');
11
+ if (start === -1)
12
+ return undefined;
13
+ let depth = 0;
14
+ let end = -1;
15
+ for (let i = start; i < text.length; i += 1) {
16
+ const ch = text[i];
17
+ if (ch === '<')
18
+ depth += 1;
19
+ else if (ch === '>') {
20
+ depth -= 1;
21
+ if (depth === 0) {
22
+ end = i;
23
+ break;
24
+ }
25
+ if (depth < 0)
26
+ return undefined;
27
+ }
28
+ }
29
+ if (end === -1)
30
+ return undefined;
31
+ const inner = text.slice(start + 1, end);
32
+ if (inner.trim().length === 0)
33
+ return undefined;
34
+ const args = [];
35
+ let tokenStart = 0;
36
+ let nested = 0;
37
+ for (let i = 0; i < inner.length; i += 1) {
38
+ const ch = inner[i];
39
+ if (ch === '<')
40
+ nested += 1;
41
+ else if (ch === '>')
42
+ nested -= 1;
43
+ else if (ch === ',' && nested === 0) {
44
+ const token = inner.slice(tokenStart, i).replace(/\s+/g, '');
45
+ if (token.length > 0)
46
+ args.push(token);
47
+ tokenStart = i + 1;
48
+ }
49
+ }
50
+ const last = inner.slice(tokenStart).replace(/\s+/g, '');
51
+ if (last.length > 0)
52
+ args.push(last);
53
+ return args.length > 0 ? args : undefined;
54
+ }
55
+ export function stripTemplateArguments(text) {
56
+ const start = text.indexOf('<');
57
+ if (start === -1)
58
+ return text;
59
+ return text.slice(0, start);
60
+ }
61
+ export function templateArgumentsIdTag(templateArguments) {
62
+ if (templateArguments === undefined || templateArguments.length === 0)
63
+ return '';
64
+ return `~${templateArguments.join(',')}`;
65
+ }
@@ -40,6 +40,7 @@ interface ParsedSymbol {
40
40
  parameterTypes?: string[];
41
41
  returnType?: string;
42
42
  declaredType?: string;
43
+ templateArguments?: string[];
43
44
  ownerId?: string;
44
45
  visibility?: string;
45
46
  isStatic?: boolean;
@@ -45,6 +45,7 @@ import { generateId } from '../../../lib/utils.js';
45
45
  import { preprocessImportPath } from '../import-processor.js';
46
46
  import { extractVueScript, extractTemplateComponents, isVueSetupTopLevel, } from '../vue-sfc-extractor.js';
47
47
  import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, } from '../utils/method-props.js';
48
+ import { extractTemplateArguments, templateArgumentsIdTag } from '../utils/template-arguments.js';
48
49
  import { extractParsedFile } from '../scope-extractor-bridge.js';
49
50
  import { logger } from '../../logger.js';
50
51
  // ============================================================================
@@ -1545,6 +1546,20 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1545
1546
  })
1546
1547
  : null;
1547
1548
  const nodeLabel = extractedClassSymbol?.type ?? defaultNodeLabel;
1549
+ const isClassLikeLabel = nodeLabel === 'Class' ||
1550
+ nodeLabel === 'Struct' ||
1551
+ nodeLabel === 'Interface' ||
1552
+ nodeLabel === 'Enum' ||
1553
+ nodeLabel === 'Record';
1554
+ if (isClassLikeLabel &&
1555
+ provider.classExtractor?.shouldSkipClassCapture?.({
1556
+ captureMap,
1557
+ definitionNode,
1558
+ nameNode,
1559
+ nodeLabel,
1560
+ }) === true) {
1561
+ continue;
1562
+ }
1548
1563
  // Dedup: variable captures (Const/Static/Variable) may overlap with higher-priority
1549
1564
  // captures (e.g. `const fn = () => {}` matches both @definition.function and @definition.const).
1550
1565
  // Skip variable captures whose definition node was already processed.
@@ -1634,7 +1649,26 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1634
1649
  arityTag += typeTagForId(defMethodMap, nodeName, arityForId, defMethodInfo, language, groups);
1635
1650
  arityTag += constTagForId(defMethodMap, nodeName, arityForId, defMethodInfo, groups);
1636
1651
  }
1637
- const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${arityTag}`);
1652
+ const classTemplateArguments = extractedClassSymbol?.templateArguments ??
1653
+ provider.classExtractor?.extractTemplateArgumentsFromCapture?.({
1654
+ captureMap,
1655
+ definitionNode,
1656
+ nameNode,
1657
+ }) ??
1658
+ (captureMap['template-arguments']
1659
+ ? extractTemplateArguments(captureMap['template-arguments'].text)
1660
+ : undefined) ??
1661
+ (nameNode && nameNode.text ? extractTemplateArguments(nameNode.text) : undefined);
1662
+ const classTemplateTag = (nodeLabel === 'Class' ||
1663
+ nodeLabel === 'Struct' ||
1664
+ nodeLabel === 'Interface' ||
1665
+ nodeLabel === 'Enum' ||
1666
+ nodeLabel === 'Record') &&
1667
+ classTemplateArguments !== undefined &&
1668
+ classTemplateArguments.length > 0
1669
+ ? templateArgumentsIdTag(classTemplateArguments)
1670
+ : '';
1671
+ const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${classTemplateTag}${arityTag}`);
1638
1672
  const classNodeForSymbol = definitionNode || nameNode;
1639
1673
  const qualifiedTypeName = extractedClassSymbol?.qualifiedName ??
1640
1674
  (classNodeForSymbol && provider.classExtractor?.isTypeDeclaration(classNodeForSymbol)
@@ -1743,6 +1777,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1743
1777
  ? isVueSetupTopLevel(nameNode || definitionNode)
1744
1778
  : cachedExportCheck(provider.exportChecker, nameNode || definitionNode, nodeName),
1745
1779
  ...(qualifiedTypeName !== undefined ? { qualifiedName: qualifiedTypeName } : {}),
1780
+ ...(classTemplateArguments !== undefined && classTemplateArguments.length > 0
1781
+ ? { templateArguments: classTemplateArguments }
1782
+ : {}),
1746
1783
  ...(frameworkHint
1747
1784
  ? {
1748
1785
  astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
@@ -1766,6 +1803,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1766
1803
  parameterTypes: methodProps.parameterTypes,
1767
1804
  returnType: methodProps.returnType,
1768
1805
  ...(declaredType !== undefined ? { declaredType } : {}),
1806
+ ...(classTemplateArguments !== undefined && classTemplateArguments.length > 0
1807
+ ? { templateArguments: classTemplateArguments }
1808
+ : {}),
1769
1809
  ...(enclosingClassId ? { ownerId: enclosingClassId } : {}),
1770
1810
  visibility: methodProps.visibility,
1771
1811
  isStatic: methodProps.isStatic,
@@ -15,6 +15,7 @@ const fs = require('fs');
15
15
  const path = require('path');
16
16
  const { spawnSync } = require('child_process');
17
17
  const { acquireHookSlot } = require('./hook-lock.cjs');
18
+ const { hasGitNexusDbLockedByGitNexusServer } = require('./hook-db-lock-probe.cjs');
18
19
 
19
20
  /**
20
21
  * Read JSON input from stdin synchronously.
@@ -103,6 +104,28 @@ function findGitNexusDir(startDir) {
103
104
  return null;
104
105
  }
105
106
 
107
+ function hasGitNexusServerOwner(gitNexusDir) {
108
+ return hasGitNexusDbLockedByGitNexusServer(path.join(gitNexusDir, 'lbug'), process.pid);
109
+ }
110
+
111
+ function extractAugmentContext(stderr) {
112
+ const output = (stderr || '').trim();
113
+ const marker = output.indexOf('[GitNexus]');
114
+ const debug = process.env.GITNEXUS_DEBUG === '1' || process.env.GITNEXUS_DEBUG === 'true';
115
+ if (debug && output.length > 0) {
116
+ // Emit the FULL discarded prefix (everything before the marker, or all of
117
+ // it when no marker is present) so suppressed diagnostics — KuzuDB lock
118
+ // warnings, parser errors, etc. — remain recoverable on the hook's own
119
+ // stderr. The untruncated payload lets operators see exactly what was
120
+ // filtered out instead of a 180-char JSON-quoted preview.
121
+ const discarded = marker === -1 ? output : output.slice(0, marker).trim();
122
+ if (discarded.length > 0) {
123
+ process.stderr.write(`[GitNexus hook] augment stderr discarded prefix:\n${discarded}\n`);
124
+ }
125
+ }
126
+ return marker === -1 ? '' : output.slice(marker).trim();
127
+ }
128
+
106
129
  /**
107
130
  * Extract search pattern from tool input.
108
131
  */
@@ -168,6 +191,10 @@ function extractPattern(toolName, toolInput) {
168
191
  * 3. Fall back to npx (returns empty string)
169
192
  */
170
193
  function resolveCliPath() {
194
+ const fromEnv = process.env.GITNEXUS_HOOK_CLI_PATH;
195
+ if (fromEnv !== undefined && String(fromEnv).trim() && fs.existsSync(String(fromEnv))) {
196
+ return String(fromEnv);
197
+ }
171
198
  let cliPath = path.resolve(__dirname, '..', '..', 'dist', 'cli', 'index.js');
172
199
  if (!fs.existsSync(cliPath)) {
173
200
  try {
@@ -218,6 +245,10 @@ function handlePreToolUse(input) {
218
245
 
219
246
  const pattern = extractPattern(toolName, toolInput);
220
247
  if (!pattern || pattern.length < 3) return;
248
+ if (hasGitNexusServerOwner(gitNexusDir)) {
249
+ process.stderr.write('[GitNexus] augment skipped: MCP server owns DB\n');
250
+ return;
251
+ }
221
252
 
222
253
  const release = acquireHookSlot(gitNexusDir);
223
254
  if (!release) return;
@@ -227,7 +258,7 @@ function handlePreToolUse(input) {
227
258
  try {
228
259
  const child = runGitNexusCli(cliPath, ['augment', '--', pattern], cwd, 7000);
229
260
  if (!child.error && child.status === 0) {
230
- result = child.stderr || '';
261
+ result = extractAugmentContext(child.stderr || '');
231
262
  }
232
263
  } catch {
233
264
  /* graceful failure */
@@ -235,8 +266,8 @@ function handlePreToolUse(input) {
235
266
  release();
236
267
  }
237
268
 
238
- if (result && result.trim()) {
239
- sendHookResponse('PreToolUse', result.trim());
269
+ if (result) {
270
+ sendHookResponse('PreToolUse', result);
240
271
  }
241
272
  }
242
273
 
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Cross-platform best-effort probe: does another process hold dbPath open
3
+ * with a command line that looks like a GitNexus MCP/serve server?
4
+ *
5
+ * Backends (no user-installed Sysinternals):
6
+ * - Linux: scan procfs under /proc (per-PID fd entries) via stat(2) (dev+inode); works without lsof;
7
+ * optional lsof fallback when proc scan finds nothing.
8
+ * - macOS / *BSD / etc.: trusted lsof + ps (absolute paths first).
9
+ * - Windows: Restart Manager (rstrtmgr) via bundled PowerShell script +
10
+ * Win32_Process for command lines; trusted powershell.exe under %SystemRoot%.
11
+ *
12
+ * Fail-open on most errors; fail-closed only on lsof ETIMEDOUT (Unix) or
13
+ * PowerShell ETIMEDOUT (Windows), matching the hook contract.
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { spawnSync } = require('child_process');
19
+
20
+ function isGitNexusServerCommand(command) {
21
+ const hasServerMode = /(?:^|\s)(mcp|serve)(?:\s|$)/.test(command);
22
+ const hasGitNexus =
23
+ /(?:^|[/\\\s])gitnexus(?:\.cmd)?(?:\s|$)/.test(command) ||
24
+ /node_modules[/\\]gitnexus[/\\]/.test(command);
25
+ return hasServerMode && hasGitNexus;
26
+ }
27
+
28
+ function resolveHookBinary(tool) {
29
+ const envKey = tool === 'lsof' ? 'GITNEXUS_HOOK_LSOF_PATH' : 'GITNEXUS_HOOK_PS_PATH';
30
+ const fromEnv = process.env[envKey];
31
+ if (fromEnv && String(fromEnv).trim() && fs.existsSync(String(fromEnv))) {
32
+ return String(fromEnv);
33
+ }
34
+ const candidates =
35
+ tool === 'lsof'
36
+ ? ['/usr/bin/lsof', '/usr/sbin/lsof', '/sbin/lsof', tool]
37
+ : ['/bin/ps', '/usr/bin/ps', tool];
38
+ for (const candidate of candidates) {
39
+ if (candidate === tool) return tool;
40
+ try {
41
+ if (fs.existsSync(candidate)) return candidate;
42
+ } catch {
43
+ /* ignore */
44
+ }
45
+ }
46
+ return tool;
47
+ }
48
+
49
+ function resolveWindowsPowerShellPath() {
50
+ const fromEnv = process.env.GITNEXUS_HOOK_POWERSHELL_PATH;
51
+ if (fromEnv && String(fromEnv).trim() && fs.existsSync(String(fromEnv).trim())) {
52
+ return String(fromEnv).trim();
53
+ }
54
+ const root = process.env.SystemRoot || 'C:\\Windows';
55
+ const ps = path.join(root, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
56
+ if (fs.existsSync(ps)) return ps;
57
+ const psWow = path.join(root, 'SysWOW64', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
58
+ if (fs.existsSync(psWow)) return psWow;
59
+ return 'powershell.exe';
60
+ }
61
+
62
+ // Sentinel:
63
+ // undefined = not loaded yet (try the read)
64
+ // string = encoded PowerShell command (successful load)
65
+ // null = load attempted and failed (do not retry; warning already emitted)
66
+ let windowsRmListPsEncodedCommandCache;
67
+ let windowsRmListPsLoadFailureWarned = false;
68
+ function getWindowsRmListEncodedCommand() {
69
+ if (windowsRmListPsEncodedCommandCache !== undefined) {
70
+ return windowsRmListPsEncodedCommandCache;
71
+ }
72
+ try {
73
+ const ps1Path = path.join(__dirname, 'win-rm-list-json.ps1');
74
+ const src = fs
75
+ .readFileSync(ps1Path, 'utf8')
76
+ .replace(/^\uFEFF/, '')
77
+ .replace(/\r\n/g, '\n');
78
+ windowsRmListPsEncodedCommandCache = Buffer.from(src, 'utf16le').toString('base64');
79
+ } catch (err) {
80
+ windowsRmListPsEncodedCommandCache = null;
81
+ if (
82
+ !windowsRmListPsLoadFailureWarned &&
83
+ (process.env.GITNEXUS_DEBUG === '1' || process.env.GITNEXUS_DEBUG === 'true')
84
+ ) {
85
+ windowsRmListPsLoadFailureWarned = true;
86
+ const msg = err && err.message ? String(err.message).slice(0, 200) : 'unknown';
87
+ process.stderr.write(`[GitNexus hook] win-rm-list-json.ps1 load failed: ${msg}\n`);
88
+ }
89
+ }
90
+ return windowsRmListPsEncodedCommandCache;
91
+ }
92
+
93
+ function hasGitNexusServerOwnerWindows(dbPathAbs, myPid) {
94
+ const encoded = getWindowsRmListEncodedCommand();
95
+ if (!encoded) return false;
96
+ const psExe = resolveWindowsPowerShellPath();
97
+ const r = spawnSync(
98
+ psExe,
99
+ [
100
+ '-NoProfile',
101
+ '-NonInteractive',
102
+ '-ExecutionPolicy',
103
+ 'Bypass',
104
+ '-STA',
105
+ '-EncodedCommand',
106
+ encoded,
107
+ ],
108
+ {
109
+ encoding: 'utf-8',
110
+ timeout: 6000,
111
+ stdio: ['ignore', 'pipe', 'ignore'],
112
+ env: { ...process.env, GITNEXUS_HOOK_RM_TARGET: dbPathAbs },
113
+ },
114
+ );
115
+ // ETIMEDOUT means the PowerShell probe didn't return in time; treat as 'unresponsive process holds DB' → fail-closed (skip augment).
116
+ if (r.error) return r.error.code === 'ETIMEDOUT';
117
+ if (r.status !== 0) return false;
118
+ let rows;
119
+ try {
120
+ rows = JSON.parse(String(r.stdout || '').trim() || '[]');
121
+ } catch {
122
+ return false;
123
+ }
124
+ if (!Array.isArray(rows)) return false;
125
+ for (const row of rows) {
126
+ const procId = Number(row.pid);
127
+ const cmd = String(row.cmd || '');
128
+ if (!Number.isFinite(procId) || procId === myPid) continue;
129
+ if (isGitNexusServerCommand(cmd)) return true;
130
+ }
131
+ return false;
132
+ }
133
+
134
+ function readLinuxCmdline(pidStr) {
135
+ try {
136
+ return fs.readFileSync(`/proc/${pidStr}/cmdline`, 'utf8').replace(/\0+/g, ' ').trim();
137
+ } catch {
138
+ return '';
139
+ }
140
+ }
141
+
142
+ function linuxProcScanFindGitNexusServer(dbPathAbs, myPid) {
143
+ const raw = process.env.GITNEXUS_HOOK_LINUX_PROC_BUDGET_MS;
144
+ const budget = Number(raw && String(raw).trim()) ? Number.parseInt(String(raw), 10) : 1200;
145
+ const start = Date.now();
146
+ let targetStat;
147
+ try {
148
+ targetStat = fs.statSync(dbPathAbs);
149
+ } catch {
150
+ return false;
151
+ }
152
+ let procEntries;
153
+ try {
154
+ procEntries = fs.readdirSync('/proc', { withFileTypes: true });
155
+ } catch {
156
+ return false;
157
+ }
158
+ for (const ent of procEntries) {
159
+ if (Date.now() - start > budget) return false;
160
+ if (!ent.isDirectory() || !/^\d+$/.test(ent.name)) continue;
161
+ const pid = Number.parseInt(ent.name, 10);
162
+ if (!Number.isFinite(pid) || pid === myPid) continue;
163
+ const fdDir = path.join('/proc', ent.name, 'fd');
164
+ let fds;
165
+ try {
166
+ fds = fs.readdirSync(fdDir);
167
+ } catch {
168
+ continue;
169
+ }
170
+ let holds = false;
171
+ for (const fd of fds) {
172
+ if (Date.now() - start > budget) return false;
173
+ try {
174
+ const st = fs.statSync(path.join(fdDir, fd));
175
+ if (st.dev === targetStat.dev && st.ino === targetStat.ino) {
176
+ holds = true;
177
+ break;
178
+ }
179
+ } catch {
180
+ /* ignore */
181
+ }
182
+ }
183
+ if (!holds) continue;
184
+ if (isGitNexusServerCommand(readLinuxCmdline(ent.name))) return true;
185
+ }
186
+ return false;
187
+ }
188
+
189
+ function unixLsofPsFindGitNexusServer(dbPathAbs, myPid) {
190
+ const lsofPath = resolveHookBinary('lsof');
191
+ const lsof = spawnSync(lsofPath, ['-nP', '-t', '--', dbPathAbs], {
192
+ encoding: 'utf-8',
193
+ timeout: 1000,
194
+ stdio: ['ignore', 'pipe', 'ignore'],
195
+ });
196
+ if (lsof.error) return lsof.error.code === 'ETIMEDOUT';
197
+
198
+ const pids = (lsof.stdout || '').split(/\s+/).filter(Boolean);
199
+ const psPath = resolveHookBinary('ps');
200
+ for (const pid of pids) {
201
+ if (Number(pid) === myPid) continue;
202
+ const ps = spawnSync(psPath, ['-p', pid, '-o', 'command='], {
203
+ encoding: 'utf-8',
204
+ timeout: 500,
205
+ stdio: ['ignore', 'pipe', 'ignore'],
206
+ });
207
+ if (ps.error) {
208
+ if (ps.error.code === 'ETIMEDOUT') return true;
209
+ continue;
210
+ }
211
+ if (isGitNexusServerCommand(ps.stdout || '')) return true;
212
+ }
213
+ return false;
214
+ }
215
+
216
+ /**
217
+ * @param {string} dbPath Absolute or relative path to the DB file (e.g. .../lbug).
218
+ * @param {number} myPid Current process PID (hook runner), excluded from matches.
219
+ */
220
+ function hasGitNexusDbLockedByGitNexusServer(dbPath, myPid) {
221
+ if (!fs.existsSync(dbPath)) return false;
222
+ const dbPathAbs = path.resolve(dbPath);
223
+
224
+ if (process.platform === 'win32') {
225
+ return hasGitNexusServerOwnerWindows(dbPathAbs, myPid);
226
+ }
227
+
228
+ if (process.platform === 'linux') {
229
+ if (linuxProcScanFindGitNexusServer(dbPathAbs, myPid)) return true;
230
+ return unixLsofPsFindGitNexusServer(dbPathAbs, myPid);
231
+ }
232
+
233
+ return unixLsofPsFindGitNexusServer(dbPathAbs, myPid);
234
+ }
235
+
236
+ module.exports = {
237
+ hasGitNexusDbLockedByGitNexusServer,
238
+ };
@@ -0,0 +1,76 @@
1
+ $ErrorActionPreference = 'Stop'
2
+ $target = $env:GITNEXUS_HOOK_RM_TARGET
3
+ if ([string]::IsNullOrWhiteSpace($target)) { Write-Output '[]'; exit 0 }
4
+ $target = (Resolve-Path -LiteralPath $target).ProviderPath
5
+
6
+ if (-not ([Management.Automation.PSTypeName]'GitNexusHookRm.Native').Type) {
7
+ Add-Type @'
8
+ using System;
9
+ using System.Runtime.InteropServices;
10
+ namespace GitNexusHookRm {
11
+ public static class Native {
12
+ public const int ErrorMoreData = 234;
13
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
14
+ public struct RM_UNIQUE_PROCESS {
15
+ public int dwProcessId;
16
+ public long ProcessStartTime;
17
+ }
18
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
19
+ public struct RM_PROCESS_INFO {
20
+ public RM_UNIQUE_PROCESS Process;
21
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
22
+ public string strAppName;
23
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
24
+ public string strServiceShortName;
25
+ public uint ApplicationType;
26
+ public uint AppStatus;
27
+ public uint TSSessionId;
28
+ public uint bRestartable;
29
+ }
30
+ [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
31
+ public static extern int RmStartSession(out uint pSessionHandle, uint dwSessionFlags, string strSessionKey);
32
+ [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
33
+ public static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFileNames, uint nApplications, IntPtr rgApplications, uint nServices, string[] rgsServiceNames);
34
+ [DllImport("rstrtmgr.dll")]
35
+ public static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps, ref uint lpdwRebootReasons);
36
+ [DllImport("rstrtmgr.dll")]
37
+ public static extern int RmEndSession(uint pSessionHandle);
38
+ }
39
+ }
40
+ '@
41
+ }
42
+
43
+ $h = [uint32]0
44
+ $key = [guid]::NewGuid().ToString('N')
45
+ $rmErr = [GitNexusHookRm.Native]::RmStartSession([ref]$h, 0, $key)
46
+ if ($rmErr -ne 0) { Write-Output '[]'; exit 0 }
47
+ $files = @($target)
48
+ $err = [GitNexusHookRm.Native]::RmRegisterResources($h, 1, $files, 0, [IntPtr]::Zero, 0, $null)
49
+ if ($err -ne 0) {
50
+ [void][GitNexusHookRm.Native]::RmEndSession($h)
51
+ Write-Output '[]'
52
+ exit 0
53
+ }
54
+ $need = [uint32]0
55
+ $n = [uint32]0
56
+ $reboot = [uint32]0
57
+ $err = [GitNexusHookRm.Native]::RmGetList($h, [ref]$need, [ref]$n, $null, [ref]$reboot)
58
+ if ($err -ne [GitNexusHookRm.Native]::ErrorMoreData) {
59
+ [void][GitNexusHookRm.Native]::RmEndSession($h)
60
+ Write-Output '[]'
61
+ exit 0
62
+ }
63
+ $n = $need
64
+ $buf = New-Object GitNexusHookRm.Native+RM_PROCESS_INFO[] ([int]$n)
65
+ $err = [GitNexusHookRm.Native]::RmGetList($h, [ref]$need, [ref]$n, $buf, [ref]$reboot)
66
+ [void][GitNexusHookRm.Native]::RmEndSession($h)
67
+ if ($err -ne 0) { Write-Output '[]'; exit 0 }
68
+
69
+ $out = @()
70
+ for ($i = 0; $i -lt [int]$n; $i++) {
71
+ $procId = $buf[$i].Process.dwProcessId
72
+ $p = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$procId" -ErrorAction SilentlyContinue
73
+ $cmd = if ($p) { $p.CommandLine } else { '' }
74
+ $out += [PSCustomObject]@{ pid = [int]$procId; cmd = $cmd }
75
+ }
76
+ ConvertTo-Json -InputObject @($out) -Compress
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.5-rc.34",
3
+ "version": "1.6.5-rc.36",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",