gitnexus 1.6.5-rc.33 → 1.6.5-rc.34

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.
@@ -68,15 +68,19 @@ export const cppScopeResolver = {
68
68
  // fallback and wildcard expansion can suppress them as unqualified
69
69
  // cross-file callables.
70
70
  populateCppNonGloballyVisible(parsed);
71
- // Resolve recorded template-class → dependent-base simple names to
72
- // class nodeIds for two-phase template lookup (U3 of plan
73
- // 2026-05-13-001).
74
- populateCppDependentBases(parsed);
75
71
  // Build the class-def → enclosing-namespace-qualified-name map used
76
72
  // by ADL (U2 of plan 2026-05-13-001) to identify each argument type's
77
73
  // associated namespace for Koenig lookup.
78
74
  populateCppAssociatedNamespaces(parsed);
79
75
  },
76
+ // Resolve recorded template-class → dependent-base simple names to
77
+ // class nodeIds for two-phase template lookup (U3 of plan
78
+ // 2026-05-13-001). Runs AFTER all files have had `populateOwners`
79
+ // applied so that cross-file base classes (e.g. Base in base.h,
80
+ // Derived in derived.h) are reachable in the workspace index.
81
+ populateWorkspaceOwners: (parsedFiles) => {
82
+ populateCppDependentBases(parsedFiles);
83
+ },
80
84
  // Simple `isSuperReceiver` returns false for C++. Real super
81
85
  // classification is caller-context-dependent and lives in
82
86
  // `isSuperReceiverInContext` below — without scope context the
@@ -11,8 +11,20 @@
11
11
  * This module records — during `emitCppScopeCaptures` — which template
12
12
  * class declarations have which dependent base class names (per file).
13
13
  * `populateCppDependentBases` then resolves those names to class nodeIds
14
- * using the workspace registry, building the per-class set the
15
- * `isDependentBaseMember` predicate consumes.
14
+ * using a workspace-wide registry, building the per-class set the
15
+ * `isCppDependentBaseMember` predicate consumes.
16
+ *
17
+ * Cross-file resolution: `Base<T>` may be declared in a different header
18
+ * than `Derived<T>`. `populateCppDependentBases` therefore runs as a
19
+ * workspace-wide pass (`populateWorkspaceOwners` hook) after every file
20
+ * has had `populateOwners` applied, so all class defs are reachable.
21
+ *
22
+ * Namespace disambiguation: when multiple classes share a simple name
23
+ * (e.g., `Box` in two namespaces), the resolver prefers the candidate
24
+ * whose qualified-name prefix (namespace path) matches the deriving
25
+ * class's prefix. If no namespace match is found, a unique simple-name
26
+ * match is accepted; ambiguous matches (multiple candidates, no
27
+ * namespace winner) are skipped conservatively.
16
28
  *
17
29
  * NOTE: module-level state, single-process-single-repo use only.
18
30
  * `clearFileLocalNames()` clears this state alongside file-local linkage
@@ -33,15 +45,19 @@ export declare function markCppDependentBase(filePath: string, className: string
33
45
  /** Clear two-phase-lookup state. Called from `clearFileLocalNames`. */
34
46
  export declare function clearCppDependentBases(): void;
35
47
  /**
36
- * Resolve recorded dependent-base simple names to class nodeIds using
37
- * the parsed file's localDefs. Run as part of `populateOwners` so the
38
- * resolved set is available before any resolution pass consults it.
48
+ * Resolve recorded dependent-base simple names to class nodeIds using a
49
+ * workspace-wide index. Run as `populateWorkspaceOwners` after every
50
+ * file has had `populateOwners` applied, so class defs from ALL files
51
+ * are reachable.
39
52
  *
40
- * Matches by simple name within the same file (the template class and
41
- * its base are typically declared in the same TU; cross-file template
42
- * bases are an edge case deferred to V2).
53
+ * Disambiguation strategy (multiple classes sharing a simple name):
54
+ * 1. Prefer the candidate whose qualified-name namespace prefix matches
55
+ * the deriving class's namespace prefix (same-namespace bias).
56
+ * 2. Fall back to accepting a unique simple-name match.
57
+ * 3. Skip when multiple candidates exist and no namespace match is
58
+ * found (conservative: avoids false associations).
43
59
  */
44
- export declare function populateCppDependentBases(parsed: ParsedFile): void;
60
+ export declare function populateCppDependentBases(parsedFiles: readonly ParsedFile[]): void;
45
61
  /**
46
62
  * Two-phase lookup predicate: is the candidate def a member of a
47
63
  * dependent base of the caller's enclosing template class?
@@ -11,8 +11,20 @@
11
11
  * This module records — during `emitCppScopeCaptures` — which template
12
12
  * class declarations have which dependent base class names (per file).
13
13
  * `populateCppDependentBases` then resolves those names to class nodeIds
14
- * using the workspace registry, building the per-class set the
15
- * `isDependentBaseMember` predicate consumes.
14
+ * using a workspace-wide registry, building the per-class set the
15
+ * `isCppDependentBaseMember` predicate consumes.
16
+ *
17
+ * Cross-file resolution: `Base<T>` may be declared in a different header
18
+ * than `Derived<T>`. `populateCppDependentBases` therefore runs as a
19
+ * workspace-wide pass (`populateWorkspaceOwners` hook) after every file
20
+ * has had `populateOwners` applied, so all class defs are reachable.
21
+ *
22
+ * Namespace disambiguation: when multiple classes share a simple name
23
+ * (e.g., `Box` in two namespaces), the resolver prefers the candidate
24
+ * whose qualified-name prefix (namespace path) matches the deriving
25
+ * class's prefix. If no namespace match is found, a unique simple-name
26
+ * match is accepted; ambiguous matches (multiple candidates, no
27
+ * namespace winner) are skipped conservatively.
16
28
  *
17
29
  * NOTE: module-level state, single-process-single-repo use only.
18
30
  * `clearFileLocalNames()` clears this state alongside file-local linkage
@@ -61,40 +73,97 @@ export function clearCppDependentBases() {
61
73
  dependentBaseNodeIds.clear();
62
74
  }
63
75
  /**
64
- * Resolve recorded dependent-base simple names to class nodeIds using
65
- * the parsed file's localDefs. Run as part of `populateOwners` so the
66
- * resolved set is available before any resolution pass consults it.
76
+ * Resolve recorded dependent-base simple names to class nodeIds using a
77
+ * workspace-wide index. Run as `populateWorkspaceOwners` after every
78
+ * file has had `populateOwners` applied, so class defs from ALL files
79
+ * are reachable.
67
80
  *
68
- * Matches by simple name within the same file (the template class and
69
- * its base are typically declared in the same TU; cross-file template
70
- * bases are an edge case deferred to V2).
81
+ * Disambiguation strategy (multiple classes sharing a simple name):
82
+ * 1. Prefer the candidate whose qualified-name namespace prefix matches
83
+ * the deriving class's namespace prefix (same-namespace bias).
84
+ * 2. Fall back to accepting a unique simple-name match.
85
+ * 3. Skip when multiple candidates exist and no namespace match is
86
+ * found (conservative: avoids false associations).
71
87
  */
72
- export function populateCppDependentBases(parsed) {
73
- const perFile = dependentBasesByFile.get(parsed.filePath);
74
- if (perFile === undefined)
88
+ export function populateCppDependentBases(parsedFiles) {
89
+ if (dependentBasesByFile.size === 0)
75
90
  return;
76
- // Build simple-name → nodeId index for this file's class-like defs.
77
- const classByName = new Map();
78
- for (const def of parsed.localDefs) {
79
- if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
80
- continue;
81
- const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
82
- if (simple !== '')
83
- classByName.set(simple, def.nodeId);
91
+ // Build workspace-wide index: simpleName → {nodeId, nsPrefix}[]
92
+ // nsPrefix is the dot-joined namespace path (qualifiedName without the
93
+ // last segment). Classes at global scope have nsPrefix = ''.
94
+ const classesBySimpleName = new Map();
95
+ for (const parsed of parsedFiles) {
96
+ for (const def of parsed.localDefs) {
97
+ if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
98
+ continue;
99
+ const qn = def.qualifiedName ?? '';
100
+ const lastDot = qn.lastIndexOf('.');
101
+ const simple = lastDot >= 0 ? qn.slice(lastDot + 1) : qn;
102
+ if (simple === '')
103
+ continue;
104
+ const nsPrefix = lastDot >= 0 ? qn.slice(0, lastDot) : '';
105
+ let entries = classesBySimpleName.get(simple);
106
+ if (entries === undefined) {
107
+ entries = [];
108
+ classesBySimpleName.set(simple, entries);
109
+ }
110
+ entries.push({ nodeId: def.nodeId, nsPrefix });
111
+ }
84
112
  }
85
- for (const [className, baseNames] of perFile) {
86
- const classNodeId = classByName.get(className);
87
- if (classNodeId === undefined)
113
+ // Build a filePath → ParsedFile lookup for fast per-file access.
114
+ const parsedByFile = new Map();
115
+ for (const parsed of parsedFiles)
116
+ parsedByFile.set(parsed.filePath, parsed);
117
+ for (const [filePath, perFile] of dependentBasesByFile) {
118
+ const parsed = parsedByFile.get(filePath);
119
+ if (parsed === undefined)
88
120
  continue;
89
- let bases = dependentBaseNodeIds.get(classNodeId);
90
- if (bases === undefined) {
91
- bases = new Set();
92
- dependentBaseNodeIds.set(classNodeId, bases);
121
+ // Build a simple-name → {nodeId, nsPrefix} map for THIS file's
122
+ // class-like defs so we can identify each template class precisely
123
+ // (avoids cross-file name collisions for the deriving class itself).
124
+ const localClassByName = new Map();
125
+ for (const def of parsed.localDefs) {
126
+ if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
127
+ continue;
128
+ const qn = def.qualifiedName ?? '';
129
+ const lastDot = qn.lastIndexOf('.');
130
+ const simple = lastDot >= 0 ? qn.slice(lastDot + 1) : qn;
131
+ if (simple === '')
132
+ continue;
133
+ const nsPrefix = lastDot >= 0 ? qn.slice(0, lastDot) : '';
134
+ localClassByName.set(simple, { nodeId: def.nodeId, nsPrefix });
93
135
  }
94
- for (const baseName of baseNames) {
95
- const baseNodeId = classByName.get(baseName);
96
- if (baseNodeId !== undefined)
97
- bases.add(baseNodeId);
136
+ for (const [className, baseNames] of perFile) {
137
+ const classEntry = localClassByName.get(className);
138
+ if (classEntry === undefined)
139
+ continue;
140
+ let bases = dependentBaseNodeIds.get(classEntry.nodeId);
141
+ if (bases === undefined) {
142
+ bases = new Set();
143
+ dependentBaseNodeIds.set(classEntry.nodeId, bases);
144
+ }
145
+ for (const baseName of baseNames) {
146
+ const candidates = classesBySimpleName.get(baseName);
147
+ if (candidates === undefined || candidates.length === 0)
148
+ continue;
149
+ if (candidates.length === 1) {
150
+ // Unique simple-name match — accept regardless of namespace.
151
+ bases.add(candidates[0].nodeId);
152
+ continue;
153
+ }
154
+ // Multiple classes share the same simple name — prefer the one
155
+ // whose namespace matches the deriving class's namespace.
156
+ // V1: exact dot-prefix match only. Cross-namespace inheritance
157
+ // (e.g., `ns::outer::Derived` extending bare `Inner` defined in
158
+ // `ns::outer::inner`) and inline-namespace cases are deferred to
159
+ // V2; the conservative skip-on-ambiguity below avoids false
160
+ // associations in those edge cases.
161
+ const nsMatch = candidates.find((c) => c.nsPrefix === classEntry.nsPrefix);
162
+ if (nsMatch !== undefined) {
163
+ bases.add(nsMatch.nodeId);
164
+ }
165
+ // else: ambiguous (multiple candidates, no namespace match) → skip.
166
+ }
98
167
  }
99
168
  }
100
169
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.5-rc.33",
3
+ "version": "1.6.5-rc.34",
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",