gitnexus 1.6.12-rc.19 → 1.6.12-rc.20

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.
@@ -5,7 +5,7 @@ type GraphqlOperationKind = 'query' | 'mutation' | 'subscription';
5
5
  type GeneratedSymbolIndex = Map<string, Parser.SyntaxNode[]>;
6
6
  export declare const RESOLVE_METHOD_QUERY = "\nMATCH (n)\nWHERE labels(n) IN ['Method','Function','Property','CodeElement']\n AND n.name = $name AND n.filePath = $filePath AND n.startLine = $startLine AND n.id <> ''\nRETURN n.id AS uid, n.name AS name, n.filePath AS filePath\nORDER BY n.id ASC\nLIMIT 2";
7
7
  export declare const RESOLVE_GENERATED_SYMBOL_QUERY = "\nMATCH (n)\nWHERE labels(n) IN ['Const','Variable','Function','Method','CodeElement']\n AND n.name = $name AND n.filePath <> '' AND n.id <> ''\nRETURN n.id AS uid, n.name AS name, n.filePath AS filePath\nORDER BY n.id ASC\nLIMIT 2";
8
- export declare function hasGeneratedDocumentProof(initializer: Parser.SyntaxNode, operationKind: GraphqlOperationKind, operationName: string, requiredFields: readonly string[]): boolean;
8
+ export declare function hasGeneratedDocumentProof(initializer: Parser.SyntaxNode, operationKind: GraphqlOperationKind, operationName: string, requiredFields: readonly string[], declarators?: GeneratedSymbolIndex): boolean;
9
9
  export declare function indexGeneratedDeclarators(root: Parser.SyntaxNode): GeneratedSymbolIndex;
10
10
  export declare class GraphqlExtractor implements ContractExtractor {
11
11
  type: "graphql";
@@ -58,6 +58,130 @@ function unquote(text) {
58
58
  const value = trimmed.slice(1, -1);
59
59
  return value.includes('${') ? null : value;
60
60
  }
61
+ const SIMPLE_TEMPLATE_ESCAPES = {
62
+ b: '\b',
63
+ f: '\f',
64
+ n: '\n',
65
+ r: '\r',
66
+ t: '\t',
67
+ v: '\v',
68
+ '0': '\0',
69
+ "'": "'",
70
+ '"': '"',
71
+ '\\': '\\',
72
+ '`': '`',
73
+ };
74
+ /** Graph Method `startLine` is the 0-based wrapper row (see line-base.ts). */
75
+ function providerMemberStartLine(member) {
76
+ // Class-field arrows are `@declaration.property`; parse-worker falls back to
77
+ // the `public_field_definition` wrapper (decorator row when it is a child),
78
+ // not the initializer. Do not probe the `value` child.
79
+ return member.startPosition.row;
80
+ }
81
+ /** tree-sitter `escape_sequence.text` is the source spelling (`\\n`), not the JS value. */
82
+ function decodeEscapeSequence(text) {
83
+ if (!text.startsWith('\\') || text.length < 2)
84
+ return null;
85
+ const escaped = text.slice(1);
86
+ // Tagged-template cooked value: LineContinuation is empty; `\8`/`\9` and
87
+ // LegacyOctalEscapeSequence (`\1`–`\7`, `\00`…) make cooked undefined.
88
+ if (/^[\n\r\u2028\u2029]$/.test(escaped) || escaped === '\r\n')
89
+ return '';
90
+ if (/^[1-9]$/.test(escaped) || /^[0-7]{2,3}$/.test(escaped))
91
+ return null;
92
+ if (escaped.length === 1)
93
+ return SIMPLE_TEMPLATE_ESCAPES[escaped] ?? escaped;
94
+ if (escaped[0] === 'x' && /^[0-9A-Fa-f]{2}$/.test(escaped.slice(1))) {
95
+ return String.fromCharCode(Number.parseInt(escaped.slice(1), 16));
96
+ }
97
+ if (escaped.startsWith('u{') && escaped.endsWith('}')) {
98
+ const hex = escaped.slice(2, -1);
99
+ if (!/^[0-9A-Fa-f]{1,6}$/.test(hex))
100
+ return null;
101
+ const codePoint = Number.parseInt(hex, 16);
102
+ if (codePoint > 0x10ffff)
103
+ return null;
104
+ return String.fromCodePoint(codePoint);
105
+ }
106
+ if (escaped[0] === 'u' && /^[0-9A-Fa-f]{4}$/.test(escaped.slice(1))) {
107
+ return String.fromCharCode(Number.parseInt(escaped.slice(1), 16));
108
+ }
109
+ return null;
110
+ }
111
+ function substitutionIdentifier(substitution) {
112
+ if (substitution.namedChildren.length !== 1)
113
+ return null;
114
+ const expr = unwrapExpression(substitution.namedChildren[0]);
115
+ return expr.type === 'identifier' ? expr.text : null;
116
+ }
117
+ function isGraphqlTagCall(call) {
118
+ const callee = call.childForFieldName('function');
119
+ return callee?.type === 'identifier' && callee.text === 'gql';
120
+ }
121
+ function pascalCaseGraphqlName(name) {
122
+ return name
123
+ .split(/[^A-Za-z0-9]+/)
124
+ .filter((part) => part.length > 0)
125
+ .map((part) => `${part[0].toUpperCase()}${part.slice(1)}`)
126
+ .join('');
127
+ }
128
+ function uniqueStaticSource(name, declarators, resolving, cache) {
129
+ if (cache.has(name))
130
+ return cache.get(name) ?? null;
131
+ if (resolving.has(name) || resolving.size >= MAX_GRAPHQL_TRAVERSAL_DEPTH)
132
+ return null;
133
+ const values = declarators.get(name) ?? [];
134
+ if (values.length === 0) {
135
+ cache.set(name, null);
136
+ return null;
137
+ }
138
+ resolving.add(name);
139
+ const sources = new Set();
140
+ for (const value of values) {
141
+ const source = staticGraphqlSource(value, declarators, resolving, cache);
142
+ // A dynamic or unprovable sibling makes the name ambiguous — do not pick
143
+ // the one static spelling and ignore the rest.
144
+ if (source === null) {
145
+ resolving.delete(name);
146
+ cache.set(name, null);
147
+ return null;
148
+ }
149
+ sources.add(source);
150
+ }
151
+ resolving.delete(name);
152
+ const unique = sources.size === 1 ? [...sources][0] : null;
153
+ cache.set(name, unique);
154
+ return unique;
155
+ }
156
+ function interpolatedTemplateSource(template, declarators, resolving, cache) {
157
+ let out = '';
158
+ for (const child of template.namedChildren) {
159
+ if (child.type === 'string_fragment') {
160
+ out += child.text;
161
+ }
162
+ else if (child.type === 'escape_sequence') {
163
+ const decoded = decodeEscapeSequence(child.text);
164
+ if (decoded === null)
165
+ return null;
166
+ out += decoded;
167
+ }
168
+ else if (child.type === 'template_substitution') {
169
+ const name = substitutionIdentifier(child);
170
+ if (!name)
171
+ return null;
172
+ const inlined = uniqueStaticSource(name, declarators, resolving, cache);
173
+ if (inlined === null)
174
+ return null;
175
+ out += inlined;
176
+ }
177
+ else {
178
+ return null;
179
+ }
180
+ if (out.length > MAX_GRAPHQL_TOKENS)
181
+ return null;
182
+ }
183
+ return out;
184
+ }
61
185
  function unwrapExpression(node) {
62
186
  let current = node;
63
187
  while (['as_expression', 'satisfies_expression', 'parenthesized_expression'].includes(current.type) &&
@@ -183,7 +307,7 @@ function parsedDocumentProof(source, operationKind, operationName, requiredField
183
307
  }
184
308
  return false;
185
309
  }
186
- function staticGraphqlSource(initializer) {
310
+ function staticGraphqlSource(initializer, declarators, resolving = new Set(), cache = new Map()) {
187
311
  const value = unwrapExpression(initializer);
188
312
  if (value.type === 'string') {
189
313
  if (value.text.startsWith('"')) {
@@ -196,11 +320,22 @@ function staticGraphqlSource(initializer) {
196
320
  }
197
321
  return unquote(value.text);
198
322
  }
199
- if (value.type === 'template_string')
200
- return unquote(value.text);
323
+ if (value.type === 'template_string') {
324
+ const hasSubstitution = value.namedChildren.some((child) => child.type === 'template_substitution');
325
+ if (!hasSubstitution)
326
+ return unquote(value.text);
327
+ return declarators ? interpolatedTemplateSource(value, declarators, resolving, cache) : null;
328
+ }
201
329
  if (value.type === 'call_expression') {
202
330
  const template = value.namedChildren.find((child) => child.type === 'template_string');
203
- return template ? unquote(template.text) : null;
331
+ if (!template)
332
+ return null;
333
+ const hasSubstitution = template.namedChildren.some((child) => child.type === 'template_substitution');
334
+ // Interpolated reconstruction is the cooked template. Only `gql` is
335
+ // treated as preserving that source; String.raw / unknown tags stay fail-closed.
336
+ if (hasSubstitution && !isGraphqlTagCall(value))
337
+ return null;
338
+ return staticGraphqlSource(template, declarators, resolving, cache);
204
339
  }
205
340
  if (value.type !== 'new_expression')
206
341
  return null;
@@ -209,12 +344,12 @@ function staticGraphqlSource(initializer) {
209
344
  return null;
210
345
  const args = value.childForFieldName('arguments');
211
346
  const first = args?.namedChildren[0];
212
- return first ? staticGraphqlSource(first) : null;
347
+ return first ? staticGraphqlSource(first, declarators, resolving, cache) : null;
213
348
  }
214
- export function hasGeneratedDocumentProof(initializer, operationKind, operationName, requiredFields) {
349
+ export function hasGeneratedDocumentProof(initializer, operationKind, operationName, requiredFields, declarators) {
215
350
  if (!withinGeneratedAstBudget(initializer))
216
351
  return false;
217
- const staticSource = staticGraphqlSource(initializer);
352
+ const staticSource = staticGraphqlSource(initializer, declarators);
218
353
  if (staticSource !== null) {
219
354
  return parsedDocumentProof(staticSource, operationKind, operationName, requiredFields);
220
355
  }
@@ -406,7 +541,11 @@ function rootFields(selectionSet, fragments) {
406
541
  }
407
542
  function generatedCandidates(operation) {
408
543
  const name = operation.name?.value;
409
- return name ? [`${name}Document`] : [];
544
+ if (!name)
545
+ return [];
546
+ const exact = `${name}Document`;
547
+ const pascal = `${pascalCaseGraphqlName(name)}Document`;
548
+ return exact === pascal ? [exact] : [exact, pascal];
410
549
  }
411
550
  async function generatedDocumentMatches(repoPath, symbol, operationKind, operationName, requiredFields, cache) {
412
551
  const normalizedPath = symbol.filePath.replace(/\\/g, '/');
@@ -417,7 +556,7 @@ async function generatedDocumentMatches(repoPath, symbol, operationKind, operati
417
556
  }
418
557
  const index = await pendingIndex;
419
558
  const values = index?.get(symbol.name) ?? [];
420
- return values.some((value) => hasGeneratedDocumentProof(value, operationKind, operationName, requiredFields));
559
+ return values.some((value) => hasGeneratedDocumentProof(value, operationKind, operationName, requiredFields, index ?? undefined));
421
560
  }
422
561
  async function buildGeneratedSymbolIndex(repoPath, filePath) {
423
562
  const source = await readSafeBounded(repoPath, filePath, getMaxFileSizeBytes());
@@ -551,10 +690,7 @@ export class GraphqlExtractor {
551
690
  const symbol = uniqueRealSymbol(await dbExecutor(RESOLVE_METHOD_QUERY, {
552
691
  name: methodName,
553
692
  filePath,
554
- startLine: member.type === 'public_field_definition'
555
- ? (member.childForFieldName('value')?.startPosition.row ??
556
- member.startPosition.row) + 1
557
- : member.startPosition.row + 1,
693
+ startLine: providerMemberStartLine(member),
558
694
  }));
559
695
  if (!symbol)
560
696
  continue;
@@ -268,13 +268,14 @@ export async function loadNodeWorkspacePackages(repoRoot) {
268
268
  const scope = await loadWorkspaceScope(repoRoot);
269
269
  const byName = new Map();
270
270
  const queue = [{ dir: repoRoot, depth: 0 }];
271
+ let queueHead = 0;
271
272
  let dirsScanned = 0;
272
- while (queue.length > 0) {
273
+ while (queueHead < queue.length) {
273
274
  if (dirsScanned >= SCAN_MAX_DIRS) {
274
275
  logger.warn(`[node] package.json scan of ${repoRoot} hit the ${SCAN_MAX_DIRS}-directory cap; workspace packages below it will not resolve`);
275
276
  break;
276
277
  }
277
- const { dir, depth } = queue.shift();
278
+ const { dir, depth } = queue[queueHead++];
278
279
  dirsScanned++;
279
280
  let entries;
280
281
  try {
@@ -234,9 +234,10 @@ async function firstReadableConfig(base) {
234
234
  async function findTsconfigFiles(repoRoot) {
235
235
  const found = [];
236
236
  const queue = [{ dir: repoRoot, depth: 0 }];
237
+ let queueHead = 0;
237
238
  let dirsScanned = 0;
238
- while (queue.length > 0 && dirsScanned < SCAN_MAX_DIRS) {
239
- const { dir, depth } = queue.shift();
239
+ while (queueHead < queue.length && dirsScanned < SCAN_MAX_DIRS) {
240
+ const { dir, depth } = queue[queueHead++];
240
241
  dirsScanned++;
241
242
  let entries;
242
243
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.12-rc.19",
3
+ "version": "1.6.12-rc.20",
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",