gitnexus 1.4.5 → 1.4.6
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.
- package/dist/cli/eval-server.js +13 -5
- package/dist/cli/index.js +0 -0
- package/dist/cli/tool.d.ts +3 -2
- package/dist/cli/tool.js +48 -13
- package/dist/core/ingestion/call-processor.d.ts +0 -1
- package/dist/core/ingestion/call-processor.js +1 -132
- package/dist/core/ingestion/parsing-processor.js +5 -2
- package/dist/core/ingestion/symbol-table.d.ts +6 -0
- package/dist/core/ingestion/symbol-table.js +21 -1
- package/dist/core/ingestion/type-env.js +62 -10
- package/dist/core/ingestion/type-extractors/c-cpp.js +2 -2
- package/dist/core/ingestion/type-extractors/csharp.js +21 -7
- package/dist/core/ingestion/type-extractors/go.js +41 -10
- package/dist/core/ingestion/type-extractors/jvm.js +47 -20
- package/dist/core/ingestion/type-extractors/php.js +142 -4
- package/dist/core/ingestion/type-extractors/python.js +21 -7
- package/dist/core/ingestion/type-extractors/ruby.js +2 -2
- package/dist/core/ingestion/type-extractors/rust.js +25 -12
- package/dist/core/ingestion/type-extractors/shared.d.ts +1 -0
- package/dist/core/ingestion/type-extractors/shared.js +133 -1
- package/dist/core/ingestion/type-extractors/types.d.ts +44 -12
- package/dist/core/ingestion/type-extractors/typescript.js +22 -8
- package/dist/core/ingestion/workers/parse-worker.js +5 -2
- package/dist/mcp/local/local-backend.d.ts +1 -0
- package/dist/mcp/local/local-backend.js +23 -1
- package/hooks/claude/gitnexus-hook.cjs +0 -0
- package/hooks/claude/pre-tool-use.sh +0 -0
- package/hooks/claude/session-start.sh +0 -0
- package/package.json +3 -2
- package/scripts/patch-tree-sitter-swift.cjs +0 -0
|
@@ -132,7 +132,7 @@ export function resolveIterableElementType(iterableName, node, scopeEnv, declara
|
|
|
132
132
|
/** Known single-arg nullable wrapper types that unwrap to their inner type
|
|
133
133
|
* for receiver resolution. Optional<User> → "User", Option<User> → "User".
|
|
134
134
|
* Only nullable wrappers — NOT containers (List, Vec) or async wrappers (Promise, Future).
|
|
135
|
-
* See
|
|
135
|
+
* See WRAPPER_GENERICS below for the full set used in return-type inference. */
|
|
136
136
|
const NULLABLE_WRAPPER_TYPES = new Set([
|
|
137
137
|
'Optional', // Java
|
|
138
138
|
'Option', // Rust, Scala
|
|
@@ -569,3 +569,135 @@ export function extractElementTypeFromString(typeStr, pos = 'last') {
|
|
|
569
569
|
}
|
|
570
570
|
return undefined;
|
|
571
571
|
}
|
|
572
|
+
// ── Return type text helpers ─────────────────────────────────────────────
|
|
573
|
+
// extractReturnTypeName works on raw return-type text already stored in
|
|
574
|
+
// SymbolDefinition (e.g. "User", "Promise<User>", "User | null", "*User").
|
|
575
|
+
// Extracts the base user-defined type name.
|
|
576
|
+
/** Primitive / built-in types that should NOT produce a receiver binding. */
|
|
577
|
+
const PRIMITIVE_TYPES = new Set([
|
|
578
|
+
'string', 'number', 'boolean', 'void', 'int', 'float', 'double', 'long',
|
|
579
|
+
'short', 'byte', 'char', 'bool', 'str', 'i8', 'i16', 'i32', 'i64',
|
|
580
|
+
'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'usize', 'isize',
|
|
581
|
+
'undefined', 'null', 'None', 'nil',
|
|
582
|
+
]);
|
|
583
|
+
/**
|
|
584
|
+
* Extract a simple type name from raw return-type text.
|
|
585
|
+
* Handles common patterns:
|
|
586
|
+
* "User" → "User"
|
|
587
|
+
* "Promise<User>" → "User" (unwrap wrapper generics)
|
|
588
|
+
* "Option<User>" → "User"
|
|
589
|
+
* "Result<User, Error>" → "User" (first type arg)
|
|
590
|
+
* "User | null" → "User" (strip nullable union)
|
|
591
|
+
* "User?" → "User" (strip nullable suffix)
|
|
592
|
+
* "*User" → "User" (Go pointer)
|
|
593
|
+
* "&User" → "User" (Rust reference)
|
|
594
|
+
* Returns undefined for complex types or primitives.
|
|
595
|
+
*/
|
|
596
|
+
const WRAPPER_GENERICS = new Set([
|
|
597
|
+
'Promise', 'Observable', 'Future', 'CompletableFuture', 'Task', 'ValueTask', // async wrappers
|
|
598
|
+
'Option', 'Some', 'Optional', 'Maybe', // nullable wrappers
|
|
599
|
+
'Result', 'Either', // result wrappers
|
|
600
|
+
// Rust smart pointers (Deref to inner type)
|
|
601
|
+
'Rc', 'Arc', 'Weak', // pointer types
|
|
602
|
+
'MutexGuard', 'RwLockReadGuard', 'RwLockWriteGuard', // guard types
|
|
603
|
+
'Ref', 'RefMut', // RefCell guards
|
|
604
|
+
'Cow', // copy-on-write
|
|
605
|
+
// Containers (List, Array, Vec, Set, etc.) are intentionally excluded —
|
|
606
|
+
// methods are called on the container, not the element type.
|
|
607
|
+
// Non-wrapper generics return the base type (e.g., List) via the else branch.
|
|
608
|
+
]);
|
|
609
|
+
/**
|
|
610
|
+
* Extracts the first type argument from a comma-separated generic argument string,
|
|
611
|
+
* respecting nested angle brackets. For example:
|
|
612
|
+
* "Result<User, Error>" → "Result<User, Error>" (no top-level comma)
|
|
613
|
+
* "User, Error" → "User"
|
|
614
|
+
* "Map<K, V>, string" → "Map<K, V>"
|
|
615
|
+
*/
|
|
616
|
+
function extractFirstGenericArg(args) {
|
|
617
|
+
let depth = 0;
|
|
618
|
+
for (let i = 0; i < args.length; i++) {
|
|
619
|
+
if (args[i] === '<')
|
|
620
|
+
depth++;
|
|
621
|
+
else if (args[i] === '>')
|
|
622
|
+
depth--;
|
|
623
|
+
else if (args[i] === ',' && depth === 0)
|
|
624
|
+
return args.slice(0, i).trim();
|
|
625
|
+
}
|
|
626
|
+
return args.trim();
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Extract the first non-lifetime type argument from a generic argument string.
|
|
630
|
+
* Skips Rust lifetime parameters (e.g., `'a`, `'_`) to find the actual type.
|
|
631
|
+
* "'_, User" → "User"
|
|
632
|
+
* "'a, User" → "User"
|
|
633
|
+
* "User, Error" → "User" (no lifetime — delegates to extractFirstGenericArg)
|
|
634
|
+
*/
|
|
635
|
+
function extractFirstTypeArg(args) {
|
|
636
|
+
let remaining = args;
|
|
637
|
+
while (remaining) {
|
|
638
|
+
const first = extractFirstGenericArg(remaining);
|
|
639
|
+
if (!first.startsWith("'"))
|
|
640
|
+
return first;
|
|
641
|
+
// Skip past this lifetime arg + the comma separator
|
|
642
|
+
const commaIdx = remaining.indexOf(',', first.length);
|
|
643
|
+
if (commaIdx < 0)
|
|
644
|
+
return first; // only lifetimes — fall through
|
|
645
|
+
remaining = remaining.slice(commaIdx + 1).trim();
|
|
646
|
+
}
|
|
647
|
+
return args.trim();
|
|
648
|
+
}
|
|
649
|
+
const MAX_RETURN_TYPE_INPUT_LENGTH = 2048;
|
|
650
|
+
const MAX_RETURN_TYPE_LENGTH = 512;
|
|
651
|
+
export const extractReturnTypeName = (raw, depth = 0) => {
|
|
652
|
+
if (depth > 10)
|
|
653
|
+
return undefined;
|
|
654
|
+
if (raw.length > MAX_RETURN_TYPE_INPUT_LENGTH)
|
|
655
|
+
return undefined;
|
|
656
|
+
let text = raw.trim();
|
|
657
|
+
if (!text)
|
|
658
|
+
return undefined;
|
|
659
|
+
// Strip pointer/reference prefixes: *User, &User, &mut User
|
|
660
|
+
text = text.replace(/^[&*]+\s*(mut\s+)?/, '');
|
|
661
|
+
// Strip nullable suffix: User?
|
|
662
|
+
text = text.replace(/\?$/, '');
|
|
663
|
+
// Handle union types: "User | null" → "User"
|
|
664
|
+
if (text.includes('|')) {
|
|
665
|
+
const parts = text.split('|').map(p => p.trim()).filter(p => p !== 'null' && p !== 'undefined' && p !== 'void' && p !== 'None' && p !== 'nil');
|
|
666
|
+
if (parts.length === 1)
|
|
667
|
+
text = parts[0];
|
|
668
|
+
else
|
|
669
|
+
return undefined; // genuine union — too complex
|
|
670
|
+
}
|
|
671
|
+
// Handle generics: Promise<User> → unwrap if wrapper, else take base
|
|
672
|
+
const genericMatch = text.match(/^(\w+)\s*<(.+)>$/);
|
|
673
|
+
if (genericMatch) {
|
|
674
|
+
const [, base, args] = genericMatch;
|
|
675
|
+
if (WRAPPER_GENERICS.has(base)) {
|
|
676
|
+
// Take the first non-lifetime type argument, using bracket-balanced splitting
|
|
677
|
+
// so that nested generics like Result<User, Error> are not split at the inner
|
|
678
|
+
// comma. Lifetime parameters (Rust 'a, '_) are skipped.
|
|
679
|
+
const firstArg = extractFirstTypeArg(args);
|
|
680
|
+
return extractReturnTypeName(firstArg, depth + 1);
|
|
681
|
+
}
|
|
682
|
+
// Non-wrapper generic: return the base type (e.g., Map<K,V> → Map)
|
|
683
|
+
return PRIMITIVE_TYPES.has(base.toLowerCase()) ? undefined : base;
|
|
684
|
+
}
|
|
685
|
+
// Bare wrapper type without generic argument (e.g. Task, Promise, Option)
|
|
686
|
+
// should not produce a binding — these are meaningless without a type parameter
|
|
687
|
+
if (WRAPPER_GENERICS.has(text))
|
|
688
|
+
return undefined;
|
|
689
|
+
// Handle qualified names: models.User → User, Models::User → User, \App\Models\User → User
|
|
690
|
+
if (text.includes('::') || text.includes('.') || text.includes('\\')) {
|
|
691
|
+
text = text.split(/::|[.\\]/).pop();
|
|
692
|
+
}
|
|
693
|
+
// Final check: skip primitives
|
|
694
|
+
if (PRIMITIVE_TYPES.has(text) || PRIMITIVE_TYPES.has(text.toLowerCase()))
|
|
695
|
+
return undefined;
|
|
696
|
+
// Must start with uppercase (class/type convention) or be a valid identifier
|
|
697
|
+
if (!/^[A-Z_]\w*$/.test(text))
|
|
698
|
+
return undefined;
|
|
699
|
+
// If the final extracted type name is too long, reject it
|
|
700
|
+
if (text.length > MAX_RETURN_TYPE_LENGTH)
|
|
701
|
+
return undefined;
|
|
702
|
+
return text;
|
|
703
|
+
};
|
|
@@ -23,17 +23,48 @@ export type ConstructorBindingScanner = (node: SyntaxNode) => {
|
|
|
23
23
|
* Used for languages where return types are expressed in comments (e.g. YARD @return [Type])
|
|
24
24
|
* rather than in AST fields. Returns undefined if no return type can be determined. */
|
|
25
25
|
export type ReturnTypeExtractor = (node: SyntaxNode) => string | undefined;
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
export
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
/** Narrow lookup interface for resolving a callee name → return type name.
|
|
27
|
+
* Backed by SymbolTable.lookupFuzzyCallable; passed via ForLoopExtractorContext.
|
|
28
|
+
* Conservative: returns undefined when the callee is ambiguous (0 or 2+ matches). */
|
|
29
|
+
export interface ReturnTypeLookup {
|
|
30
|
+
/** Processed type name after stripping wrappers (e.g., 'User' from 'Promise<User>').
|
|
31
|
+
* Use for call-result variable bindings (`const b = foo()`). */
|
|
32
|
+
lookupReturnType(callee: string): string | undefined;
|
|
33
|
+
/** Raw return type as declared in the symbol (e.g., '[]User', 'List<User>').
|
|
34
|
+
* Use for iterable-element extraction (`for v := range foo()`). */
|
|
35
|
+
lookupRawReturnType(callee: string): string | undefined;
|
|
36
|
+
}
|
|
37
|
+
/** Context object passed to ForLoopExtractor.
|
|
38
|
+
* Groups the four parameters that were previously positional. */
|
|
39
|
+
export interface ForLoopExtractorContext {
|
|
40
|
+
/** Mutable type-env for the current scope — extractor writes bindings here */
|
|
41
|
+
scopeEnv: Map<string, string>;
|
|
42
|
+
/** Maps `scope\0varName` to the declaration's type annotation AST node */
|
|
43
|
+
declarationTypeNodes: ReadonlyMap<string, SyntaxNode>;
|
|
44
|
+
/** Current scope key, e.g. `"process@42"` */
|
|
45
|
+
scope: string;
|
|
46
|
+
/** Resolves a callee name to its declared return type (undefined = unknown/ambiguous) */
|
|
47
|
+
returnTypeLookup: ReturnTypeLookup;
|
|
48
|
+
}
|
|
49
|
+
/** Extracts loop variable type binding from a for-each statement. */
|
|
50
|
+
export type ForLoopExtractor = (node: SyntaxNode, ctx: ForLoopExtractorContext) => void;
|
|
51
|
+
/** Discriminated union for pending Tier-2 propagation items.
|
|
52
|
+
* - `copy` — `const b = a` (identifier alias, propagate a's type to b)
|
|
53
|
+
* - `callResult` — `const b = foo()` (bind b to foo's declared return type) */
|
|
54
|
+
export type PendingAssignment = {
|
|
55
|
+
kind: 'copy';
|
|
34
56
|
lhs: string;
|
|
35
57
|
rhs: string;
|
|
36
|
-
} |
|
|
58
|
+
} | {
|
|
59
|
+
kind: 'callResult';
|
|
60
|
+
lhs: string;
|
|
61
|
+
callee: string;
|
|
62
|
+
};
|
|
63
|
+
/** Extracts a pending assignment for Tier 2 propagation.
|
|
64
|
+
* Returns a PendingAssignment when the RHS is a bare identifier (`copy`) or a
|
|
65
|
+
* call expression (`callResult`) and the LHS has no resolved type yet.
|
|
66
|
+
* Returns undefined if the node is not a matching assignment. */
|
|
67
|
+
export type PendingAssignmentExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>) => PendingAssignment | undefined;
|
|
37
68
|
/** Extracts a typed variable binding from a pattern-matching construct.
|
|
38
69
|
* Returns { varName, typeName } for patterns that introduce NEW variables.
|
|
39
70
|
* Examples: `if let Some(user) = opt` (Rust), `x instanceof User user` (Java).
|
|
@@ -82,9 +113,10 @@ export interface LanguageTypeConfig {
|
|
|
82
113
|
extractReturnType?: ReturnTypeExtractor;
|
|
83
114
|
/** Extract loop variable → type binding from a for-each AST node. */
|
|
84
115
|
extractForLoopBinding?: ForLoopExtractor;
|
|
85
|
-
/** Extract
|
|
86
|
-
* Called on declaration/assignment nodes; returns
|
|
87
|
-
*
|
|
116
|
+
/** Extract pending assignment for Tier 2 propagation.
|
|
117
|
+
* Called on declaration/assignment nodes; returns a PendingAssignment when the RHS
|
|
118
|
+
* is a bare identifier (copy) or call expression (callResult) and the LHS has no
|
|
119
|
+
* resolved type yet. Language-specific because AST shapes differ widely. */
|
|
88
120
|
extractPendingAssignment?: PendingAssignmentExtractor;
|
|
89
121
|
/** Extract a typed variable binding from a pattern-matching construct.
|
|
90
122
|
* Called on every AST node; returns { varName, typeName } when the node introduces a new
|
|
@@ -345,7 +345,7 @@ const findTsIterableElementType = (iterableName, startNode, pos = 'last') => {
|
|
|
345
345
|
* 3. AST walk — walks up to the enclosing function's parameters to read User[] annotations directly
|
|
346
346
|
* Only handles `for...of`; `for...in` produces string keys, not element types.
|
|
347
347
|
*/
|
|
348
|
-
const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
|
|
348
|
+
const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, returnTypeLookup }) => {
|
|
349
349
|
if (node.type !== 'for_in_statement')
|
|
350
350
|
return;
|
|
351
351
|
// Confirm this is `for...of`, not `for...in`, by scanning unnamed children for the keyword text.
|
|
@@ -359,10 +359,11 @@ const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
|
|
|
359
359
|
}
|
|
360
360
|
if (!isForOf)
|
|
361
361
|
return;
|
|
362
|
-
// The iterable is the `right` field — may be identifier or call_expression.
|
|
362
|
+
// The iterable is the `right` field — may be identifier, member_expression, or call_expression.
|
|
363
363
|
const rightNode = node.childForFieldName('right');
|
|
364
364
|
let iterableName;
|
|
365
365
|
let methodName;
|
|
366
|
+
let callExprElementType;
|
|
366
367
|
if (rightNode?.type === 'identifier') {
|
|
367
368
|
iterableName = rightNode.text;
|
|
368
369
|
}
|
|
@@ -374,6 +375,7 @@ const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
|
|
|
374
375
|
else if (rightNode?.type === 'call_expression') {
|
|
375
376
|
// entries.values() → call_expression > function: member_expression > object + property
|
|
376
377
|
// this.repos.values() → nested member_expression: extract property from inner member
|
|
378
|
+
// getUsers() → call_expression > function: identifier (Phase 7.3 — return-type path)
|
|
377
379
|
const fn = rightNode.childForFieldName('function');
|
|
378
380
|
if (fn?.type === 'member_expression') {
|
|
379
381
|
const obj = fn.childForFieldName('object');
|
|
@@ -390,13 +392,25 @@ const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
|
|
|
390
392
|
if (prop?.type === 'property_identifier')
|
|
391
393
|
methodName = prop.text;
|
|
392
394
|
}
|
|
395
|
+
else if (fn?.type === 'identifier') {
|
|
396
|
+
// Direct function call: for (const user of getUsers())
|
|
397
|
+
const rawReturn = returnTypeLookup.lookupRawReturnType(fn.text);
|
|
398
|
+
if (rawReturn)
|
|
399
|
+
callExprElementType = extractElementTypeFromString(rawReturn);
|
|
400
|
+
}
|
|
393
401
|
}
|
|
394
|
-
if (!iterableName)
|
|
402
|
+
if (!iterableName && !callExprElementType)
|
|
395
403
|
return;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
404
|
+
let elementType;
|
|
405
|
+
if (callExprElementType) {
|
|
406
|
+
elementType = callExprElementType;
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
// Look up the container's base type name for descriptor-aware resolution
|
|
410
|
+
const containerTypeName = scopeEnv.get(iterableName);
|
|
411
|
+
const typeArgPos = methodToTypeArgPosition(methodName, containerTypeName);
|
|
412
|
+
elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractTsElementTypeFromAnnotation, findTsIterableElementType, typeArgPos);
|
|
413
|
+
}
|
|
400
414
|
if (!elementType)
|
|
401
415
|
return;
|
|
402
416
|
// The loop variable is the `left` field.
|
|
@@ -444,7 +458,7 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
444
458
|
if (scopeEnv.has(lhs))
|
|
445
459
|
continue;
|
|
446
460
|
if (valueNode.type === 'identifier')
|
|
447
|
-
return { lhs, rhs: valueNode.text };
|
|
461
|
+
return { kind: 'copy', lhs, rhs: valueNode.text };
|
|
448
462
|
}
|
|
449
463
|
return undefined;
|
|
450
464
|
};
|
|
@@ -953,10 +953,13 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
953
953
|
parameterCount = sig.parameterCount;
|
|
954
954
|
returnType = sig.returnType;
|
|
955
955
|
// Language-specific return type fallback (e.g. Ruby YARD @return [Type])
|
|
956
|
-
|
|
956
|
+
// Also upgrades uninformative AST types like PHP `array` with PHPDoc `@return User[]`
|
|
957
|
+
if ((!returnType || returnType === 'array' || returnType === 'iterable') && definitionNode) {
|
|
957
958
|
const tc = typeConfigs[language];
|
|
958
959
|
if (tc?.extractReturnType) {
|
|
959
|
-
|
|
960
|
+
const docReturn = tc.extractReturnType(definitionNode);
|
|
961
|
+
if (docReturn)
|
|
962
|
+
returnType = docReturn;
|
|
960
963
|
}
|
|
961
964
|
}
|
|
962
965
|
}
|
|
@@ -37,7 +37,7 @@ export const VALID_NODE_LABELS = new Set([
|
|
|
37
37
|
'Record', 'Delegate', 'Annotation', 'Constructor', 'Template', 'Module',
|
|
38
38
|
]);
|
|
39
39
|
/** Valid relation types for impact analysis filtering */
|
|
40
|
-
export const VALID_RELATION_TYPES = new Set(['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS']);
|
|
40
|
+
export const VALID_RELATION_TYPES = new Set(['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'OVERRIDES']);
|
|
41
41
|
/** Regex to detect write operations in user-supplied Cypher queries */
|
|
42
42
|
export const CYPHER_WRITE_RE = /\b(CREATE|DELETE|SET|MERGE|REMOVE|DROP|ALTER|COPY|DETACH)\b/i;
|
|
43
43
|
/** Check if a Cypher query contains write operations */
|
|
@@ -1189,6 +1189,22 @@ export class LocalBackend {
|
|
|
1189
1189
|
};
|
|
1190
1190
|
}
|
|
1191
1191
|
async impact(repo, params) {
|
|
1192
|
+
try {
|
|
1193
|
+
return await this._impactImpl(repo, params);
|
|
1194
|
+
}
|
|
1195
|
+
catch (err) {
|
|
1196
|
+
// Return structured error instead of crashing (#321)
|
|
1197
|
+
return {
|
|
1198
|
+
error: (err instanceof Error ? err.message : String(err)) || 'Impact analysis failed',
|
|
1199
|
+
target: { name: params.target },
|
|
1200
|
+
direction: params.direction,
|
|
1201
|
+
impactedCount: 0,
|
|
1202
|
+
risk: 'UNKNOWN',
|
|
1203
|
+
suggestion: 'The graph query failed — try gitnexus context <symbol> as a fallback',
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
async _impactImpl(repo, params) {
|
|
1192
1208
|
await this.ensureInitialized(repo.id);
|
|
1193
1209
|
const { target, direction } = params;
|
|
1194
1210
|
const maxDepth = params.maxDepth || 3;
|
|
@@ -1213,6 +1229,7 @@ export class LocalBackend {
|
|
|
1213
1229
|
const impacted = [];
|
|
1214
1230
|
const visited = new Set([symId]);
|
|
1215
1231
|
let frontier = [symId];
|
|
1232
|
+
let traversalComplete = true;
|
|
1216
1233
|
for (let depth = 1; depth <= maxDepth && frontier.length > 0; depth++) {
|
|
1217
1234
|
const nextFrontier = [];
|
|
1218
1235
|
// Batch frontier nodes into a single Cypher query per depth level
|
|
@@ -1244,6 +1261,10 @@ export class LocalBackend {
|
|
|
1244
1261
|
}
|
|
1245
1262
|
catch (e) {
|
|
1246
1263
|
logQueryError('impact:depth-traversal', e);
|
|
1264
|
+
// Break out of depth loop on query failure but return partial results
|
|
1265
|
+
// collected so far, rather than silently swallowing the error (#321)
|
|
1266
|
+
traversalComplete = false;
|
|
1267
|
+
break;
|
|
1247
1268
|
}
|
|
1248
1269
|
frontier = nextFrontier;
|
|
1249
1270
|
}
|
|
@@ -1321,6 +1342,7 @@ export class LocalBackend {
|
|
|
1321
1342
|
direction,
|
|
1322
1343
|
impactedCount: impacted.length,
|
|
1323
1344
|
risk,
|
|
1345
|
+
...(!traversalComplete && { partial: true }),
|
|
1324
1346
|
summary: {
|
|
1325
1347
|
direct: directCount,
|
|
1326
1348
|
processes_affected: processCount,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitnexus",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.6",
|
|
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",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"test:watch": "vitest",
|
|
46
46
|
"test:coverage": "vitest run --coverage",
|
|
47
47
|
"prepare": "npm run build",
|
|
48
|
-
"postinstall": "node scripts/patch-tree-sitter-swift.cjs"
|
|
48
|
+
"postinstall": "node scripts/patch-tree-sitter-swift.cjs",
|
|
49
|
+
"prepack": "npm run build && chmod +x dist/cli/index.js"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
52
|
"@huggingface/transformers": "^3.0.0",
|
|
File without changes
|