gitnexus 1.6.6-rc.54 → 1.6.6-rc.55

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.
@@ -293,6 +293,9 @@ export function populatePhpNamespaceSiblings(parsedFiles, indexes, inputs) {
293
293
  //
294
294
  // Additionally, mirror from files that are imported via `use` (different
295
295
  // namespace) so return types from dependencies are chain-followable too.
296
+ const parsedByPath = new Map();
297
+ for (const p of parsedFiles)
298
+ parsedByPath.set(p.filePath, p);
296
299
  for (const parsed of parsedFiles) {
297
300
  const moduleScope = parsed.scopes.find((s) => s.kind === 'Module');
298
301
  if (moduleScope === undefined)
@@ -328,7 +331,7 @@ export function populatePhpNamespaceSiblings(parsedFiles, indexes, inputs) {
328
331
  }
329
332
  // Mirror return-type bindings from accessible files.
330
333
  for (const srcFilePath of accessibleFiles) {
331
- const srcParsed = parsedFiles.find((p) => p.filePath === srcFilePath);
334
+ const srcParsed = parsedByPath.get(srcFilePath);
332
335
  if (srcParsed === undefined)
333
336
  continue;
334
337
  const srcModuleScope = srcParsed.scopes.find((s) => s.kind === 'Module');
@@ -78,9 +78,11 @@ export type ScopeExtractorHooks = Pick<LanguageProvider, 'resolveScopeKind' | 'b
78
78
  * Drive the five extraction passes and return a `ParsedFile`.
79
79
  *
80
80
  * Throws `ScopeTreeInvariantError` (from #912) when the provider emits
81
- * captures that violate structural scope invariants. The error surfaces
82
- * upward rather than being silently corrected a malformed capture set
83
- * is a bug in the provider's `emitScopeCaptures`, not a data condition
84
- * to tolerate.
81
+ * captures that violate structural scope invariants (e.g., overlapping
82
+ * sibling scopes). When no `@scope.module` capture is present, a
83
+ * synthetic Module scope is created spanning all captures, and orphan
84
+ * non-Module scopes are re-parented under it. This enables indexing of
85
+ * files where tree-sitter produces an ERROR root (e.g., complex .phtml
86
+ * templates with mixed PHP/HTML/JS).
85
87
  */
86
88
  export declare function extract(matches: readonly CaptureMatch[], filePath: string, provider: ScopeExtractorHooks): ParsedFile;
@@ -65,17 +65,28 @@ import { extractTemplateArguments } from './utils/template-arguments.js';
65
65
  * Drive the five extraction passes and return a `ParsedFile`.
66
66
  *
67
67
  * Throws `ScopeTreeInvariantError` (from #912) when the provider emits
68
- * captures that violate structural scope invariants. The error surfaces
69
- * upward rather than being silently corrected a malformed capture set
70
- * is a bug in the provider's `emitScopeCaptures`, not a data condition
71
- * to tolerate.
68
+ * captures that violate structural scope invariants (e.g., overlapping
69
+ * sibling scopes). When no `@scope.module` capture is present, a
70
+ * synthetic Module scope is created spanning all captures, and orphan
71
+ * non-Module scopes are re-parented under it. This enables indexing of
72
+ * files where tree-sitter produces an ERROR root (e.g., complex .phtml
73
+ * templates with mixed PHP/HTML/JS).
72
74
  */
73
75
  export function extract(matches, filePath, provider) {
74
76
  // Partition matches by topic up front — one linear pass over the input.
75
77
  const partitioned = partitionByTopic(matches);
76
78
  // ── Pass 1: build the scope tree ─────────────────────────────────────
77
79
  const scopeDrafts = pass1BuildScopes(partitioned.scope, filePath, provider);
78
- const moduleScope = ensureModuleScope(scopeDrafts, matches.length, filePath);
80
+ const moduleScope = ensureModuleScope(scopeDrafts, filePath, matches);
81
+ // Re-parent orphan drafts (parent === null, non-Module) under the
82
+ // Module scope. Replaces drafts with new ones carrying the correct
83
+ // parent — runs before content passes so bindings/ownedDefs are empty.
84
+ for (let i = 0; i < scopeDrafts.length; i++) {
85
+ const d = scopeDrafts[i];
86
+ if (d.parent === null && d.kind !== 'Module') {
87
+ scopeDrafts[i] = makeDraft(d.id, moduleScope.id, d.kind, d.range, d.filePath);
88
+ }
89
+ }
79
90
  const scopes = scopeDrafts.map(draftToScope);
80
91
  // buildScopeTree validates invariants (throws on violation) and exposes
81
92
  // the lookup contract consumed by Passes 2-5.
@@ -171,18 +182,29 @@ function topicOf(match) {
171
182
  }
172
183
  return 'unknown';
173
184
  }
174
- function ensureModuleScope(scopeDrafts, matchCount, filePath) {
185
+ function ensureModuleScope(scopeDrafts, filePath, allMatches) {
175
186
  const moduleScope = scopeDrafts.find((s) => s.kind === 'Module');
176
187
  if (moduleScope !== undefined)
177
188
  return moduleScope;
178
- if (scopeDrafts.length === 0 && matchCount === 0) {
179
- const range = { startLine: 0, startCol: 0, endLine: 0, endCol: 0 };
180
- const synthetic = makeDraft(makeScopeId({ filePath, range, kind: 'Module' }), null, 'Module', range, filePath);
181
- scopeDrafts.push(synthetic);
182
- return synthetic;
189
+ // Synthesize a Module scope spanning all captures in the file.
190
+ // Computed from ALL captures (scope, declaration, reference, etc.)
191
+ // so the range covers top-level references that appear after the
192
+ // last inner scope — not just inner Function/Class scopes.
193
+ let endLine = 0;
194
+ let endCol = 0;
195
+ for (const match of allMatches) {
196
+ for (const capture of Object.values(match)) {
197
+ if (capture.range.endLine > endLine ||
198
+ (capture.range.endLine === endLine && capture.range.endCol > endCol)) {
199
+ endLine = capture.range.endLine;
200
+ endCol = capture.range.endCol;
201
+ }
202
+ }
183
203
  }
184
- throw new Error(`ScopeExtractor: no Module scope found for '${filePath}'. ` +
185
- `Provider must emit at least one @scope.module capture per file.`);
204
+ const range = { startLine: 0, startCol: 0, endLine, endCol };
205
+ const synthetic = makeDraft(makeScopeId({ filePath, range, kind: 'Module' }), null, 'Module', range, filePath);
206
+ scopeDrafts.push(synthetic);
207
+ return synthetic;
186
208
  }
187
209
  function draftToScope(draft) {
188
210
  const frozenBindings = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.54",
3
+ "version": "1.6.6-rc.55",
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",