gitnexus 1.4.7 → 1.4.8

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 (92) hide show
  1. package/README.md +22 -1
  2. package/dist/cli/ai-context.d.ts +1 -1
  3. package/dist/cli/ai-context.js +1 -1
  4. package/dist/cli/analyze.d.ts +2 -0
  5. package/dist/cli/analyze.js +54 -21
  6. package/dist/cli/index.js +2 -1
  7. package/dist/cli/setup.js +78 -1
  8. package/dist/config/supported-languages.d.ts +30 -0
  9. package/dist/config/supported-languages.js +30 -0
  10. package/dist/core/embeddings/embedder.d.ts +6 -1
  11. package/dist/core/embeddings/embedder.js +65 -5
  12. package/dist/core/embeddings/embedding-pipeline.js +11 -9
  13. package/dist/core/embeddings/http-client.d.ts +31 -0
  14. package/dist/core/embeddings/http-client.js +179 -0
  15. package/dist/core/embeddings/index.d.ts +1 -0
  16. package/dist/core/embeddings/index.js +1 -0
  17. package/dist/core/embeddings/types.d.ts +1 -1
  18. package/dist/core/graph/types.d.ts +2 -1
  19. package/dist/core/ingestion/ast-helpers.d.ts +80 -0
  20. package/dist/core/ingestion/ast-helpers.js +738 -0
  21. package/dist/core/ingestion/call-analysis.d.ts +73 -0
  22. package/dist/core/ingestion/call-analysis.js +490 -0
  23. package/dist/core/ingestion/call-processor.d.ts +48 -1
  24. package/dist/core/ingestion/call-processor.js +368 -7
  25. package/dist/core/ingestion/call-routing.d.ts +6 -0
  26. package/dist/core/ingestion/entry-point-scoring.js +36 -26
  27. package/dist/core/ingestion/framework-detection.d.ts +10 -2
  28. package/dist/core/ingestion/framework-detection.js +49 -12
  29. package/dist/core/ingestion/heritage-processor.js +47 -49
  30. package/dist/core/ingestion/import-processor.d.ts +1 -1
  31. package/dist/core/ingestion/import-processor.js +103 -194
  32. package/dist/core/ingestion/import-resolution.d.ts +101 -0
  33. package/dist/core/ingestion/import-resolution.js +251 -0
  34. package/dist/core/ingestion/language-config.d.ts +3 -0
  35. package/dist/core/ingestion/language-config.js +13 -0
  36. package/dist/core/ingestion/markdown-processor.d.ts +17 -0
  37. package/dist/core/ingestion/markdown-processor.js +124 -0
  38. package/dist/core/ingestion/mro-processor.js +8 -3
  39. package/dist/core/ingestion/named-binding-extraction.d.ts +9 -43
  40. package/dist/core/ingestion/named-binding-extraction.js +89 -79
  41. package/dist/core/ingestion/parsing-processor.d.ts +2 -2
  42. package/dist/core/ingestion/parsing-processor.js +14 -73
  43. package/dist/core/ingestion/pipeline.d.ts +10 -0
  44. package/dist/core/ingestion/pipeline.js +421 -4
  45. package/dist/core/ingestion/resolution-context.d.ts +5 -0
  46. package/dist/core/ingestion/resolution-context.js +7 -4
  47. package/dist/core/ingestion/resolvers/index.d.ts +1 -1
  48. package/dist/core/ingestion/resolvers/index.js +1 -1
  49. package/dist/core/ingestion/resolvers/jvm.d.ts +2 -1
  50. package/dist/core/ingestion/resolvers/jvm.js +25 -9
  51. package/dist/core/ingestion/resolvers/php.d.ts +14 -0
  52. package/dist/core/ingestion/resolvers/php.js +43 -3
  53. package/dist/core/ingestion/resolvers/utils.d.ts +5 -0
  54. package/dist/core/ingestion/resolvers/utils.js +16 -0
  55. package/dist/core/ingestion/symbol-table.d.ts +16 -0
  56. package/dist/core/ingestion/symbol-table.js +20 -6
  57. package/dist/core/ingestion/tree-sitter-queries.d.ts +4 -4
  58. package/dist/core/ingestion/tree-sitter-queries.js +43 -2
  59. package/dist/core/ingestion/type-env.d.ts +28 -1
  60. package/dist/core/ingestion/type-env.js +419 -96
  61. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
  62. package/dist/core/ingestion/type-extractors/c-cpp.js +119 -0
  63. package/dist/core/ingestion/type-extractors/csharp.js +149 -16
  64. package/dist/core/ingestion/type-extractors/index.d.ts +1 -1
  65. package/dist/core/ingestion/type-extractors/index.js +1 -1
  66. package/dist/core/ingestion/type-extractors/jvm.js +169 -66
  67. package/dist/core/ingestion/type-extractors/rust.js +35 -1
  68. package/dist/core/ingestion/type-extractors/shared.d.ts +0 -2
  69. package/dist/core/ingestion/type-extractors/shared.js +5 -10
  70. package/dist/core/ingestion/type-extractors/swift.js +7 -6
  71. package/dist/core/ingestion/type-extractors/types.d.ts +37 -7
  72. package/dist/core/ingestion/type-extractors/typescript.js +141 -9
  73. package/dist/core/ingestion/utils.d.ts +2 -120
  74. package/dist/core/ingestion/utils.js +3 -1051
  75. package/dist/core/ingestion/workers/parse-worker.d.ts +13 -4
  76. package/dist/core/ingestion/workers/parse-worker.js +66 -87
  77. package/dist/core/lbug/csv-generator.js +18 -1
  78. package/dist/core/lbug/lbug-adapter.d.ts +10 -0
  79. package/dist/core/lbug/lbug-adapter.js +69 -4
  80. package/dist/core/lbug/schema.d.ts +5 -3
  81. package/dist/core/lbug/schema.js +26 -2
  82. package/dist/mcp/core/embedder.js +11 -3
  83. package/dist/mcp/core/lbug-adapter.js +12 -1
  84. package/dist/mcp/local/local-backend.d.ts +22 -0
  85. package/dist/mcp/local/local-backend.js +133 -29
  86. package/dist/mcp/resources.js +2 -0
  87. package/dist/mcp/tools.js +2 -2
  88. package/dist/server/api.d.ts +19 -1
  89. package/dist/server/api.js +66 -6
  90. package/dist/storage/git.d.ts +12 -0
  91. package/dist/storage/git.js +21 -0
  92. package/package.json +10 -2
@@ -444,7 +444,8 @@ const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, re
444
444
  if (loopVarName)
445
445
  scopeEnv.set(loopVarName, elementType);
446
446
  };
447
- /** TS/JS: const alias = u → variable_declarator with name/value fields */
447
+ /** TS/JS: const alias = u → variable_declarator with name/value fields.
448
+ * Also handles destructuring: `const { a, b } = obj` → N fieldAccess items. */
448
449
  const extractPendingAssignment = (node, scopeEnv) => {
449
450
  for (let i = 0; i < node.namedChildCount; i++) {
450
451
  const child = node.namedChild(i);
@@ -454,6 +455,39 @@ const extractPendingAssignment = (node, scopeEnv) => {
454
455
  const valueNode = child.childForFieldName('value');
455
456
  if (!nameNode || !valueNode)
456
457
  continue;
458
+ // Object destructuring: `const { address, name } = user`
459
+ // Emits N fieldAccess items — one per destructured binding.
460
+ if (nameNode.type === 'object_pattern' && valueNode.type === 'identifier') {
461
+ const receiver = valueNode.text;
462
+ const items = [];
463
+ for (let j = 0; j < nameNode.namedChildCount; j++) {
464
+ const prop = nameNode.namedChild(j);
465
+ if (!prop)
466
+ continue;
467
+ if (prop.type === 'shorthand_property_identifier_pattern') {
468
+ // `const { name } = user` → shorthand: varName = fieldName
469
+ const varName = prop.text;
470
+ if (!scopeEnv.has(varName)) {
471
+ items.push({ kind: 'fieldAccess', lhs: varName, receiver, field: varName });
472
+ }
473
+ }
474
+ else if (prop.type === 'pair_pattern') {
475
+ // `const { address: addr } = user` → pair_pattern: key=field, value=varName
476
+ const keyNode = prop.childForFieldName('key');
477
+ const valNode = prop.childForFieldName('value');
478
+ if (keyNode && valNode) {
479
+ const fieldName = keyNode.text;
480
+ const varName = valNode.text;
481
+ if (!scopeEnv.has(varName)) {
482
+ items.push({ kind: 'fieldAccess', lhs: varName, receiver, field: fieldName });
483
+ }
484
+ }
485
+ }
486
+ }
487
+ if (items.length > 0)
488
+ return items;
489
+ continue;
490
+ }
457
491
  const lhs = nameNode.text;
458
492
  if (scopeEnv.has(lhs))
459
493
  continue;
@@ -492,22 +526,119 @@ const extractPendingAssignment = (node, scopeEnv) => {
492
526
  }
493
527
  return undefined;
494
528
  };
529
+ /** Null-check keywords that indicate a null-comparison in binary expressions. */
530
+ const NULL_CHECK_KEYWORDS = new Set(['null', 'undefined']);
531
+ /**
532
+ * Find the if-body (consequence) block for a null-check binary_expression.
533
+ * Walks up from the binary_expression through parenthesized_expression to if_statement,
534
+ * then returns the consequence block (statement_block).
535
+ *
536
+ * AST structure: if_statement > parenthesized_expression > binary_expression
537
+ * if_statement > statement_block (consequence)
538
+ */
539
+ const findIfConsequenceBlock = (binaryExpr) => {
540
+ // Walk up to find the if_statement (typically: binary_expression > parenthesized_expression > if_statement)
541
+ let current = binaryExpr.parent;
542
+ while (current) {
543
+ if (current.type === 'if_statement') {
544
+ // The consequence is the first statement_block child of if_statement
545
+ for (let i = 0; i < current.childCount; i++) {
546
+ const child = current.child(i);
547
+ if (child?.type === 'statement_block')
548
+ return child;
549
+ }
550
+ return undefined;
551
+ }
552
+ // Stop climbing at function/block boundaries — don't cross scope
553
+ if (current.type === 'function_declaration' || current.type === 'function_expression'
554
+ || current.type === 'arrow_function' || current.type === 'method_definition')
555
+ return undefined;
556
+ current = current.parent;
557
+ }
558
+ return undefined;
559
+ };
495
560
  /** TS instanceof narrowing: `x instanceof User` → bind x to User.
496
- * Only works when x has no prior type binding (e.g. x: unknown, untyped params).
497
- * Typed params (x: Animal) are blocked by the !scopeEnv.has() guard in buildTypeEnv.
498
- * Uses first-writer-wins, same as Rust match arm bindings. */
499
- const extractPatternBinding = (node) => {
561
+ * Also handles null-check narrowing: `x !== null`, `x != undefined` etc.
562
+ * instanceof: first-writer-wins (no prior type binding).
563
+ * null-check: position-indexed narrowing via narrowingRange. */
564
+ const extractPatternBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
500
565
  if (node.type !== 'binary_expression')
501
566
  return undefined;
502
- const op = node.children.find(c => !c.isNamed && c.text === 'instanceof');
567
+ // Check for instanceof first (existing behavior)
568
+ const instanceofOp = node.children.find(c => !c.isNamed && c.text === 'instanceof');
569
+ if (instanceofOp) {
570
+ const left = node.namedChild(0);
571
+ const right = node.namedChild(1);
572
+ if (left?.type !== 'identifier' || right?.type !== 'identifier')
573
+ return undefined;
574
+ return { varName: left.text, typeName: right.text };
575
+ }
576
+ // Null-check narrowing: x !== null, x != null, x !== undefined, x != undefined
577
+ const op = node.children.find(c => !c.isNamed && (c.text === '!==' || c.text === '!='));
503
578
  if (!op)
504
579
  return undefined;
505
- // binary_expression children are positional — no left/right fields
506
580
  const left = node.namedChild(0);
507
581
  const right = node.namedChild(1);
508
- if (left?.type !== 'identifier' || right?.type !== 'identifier')
582
+ if (!left || !right)
583
+ return undefined;
584
+ // Determine which side is the variable and which is null/undefined
585
+ let varNode;
586
+ let isNullCheck = false;
587
+ if (left.type === 'identifier' && NULL_CHECK_KEYWORDS.has(right.text)) {
588
+ varNode = left;
589
+ isNullCheck = true;
590
+ }
591
+ else if (right.type === 'identifier' && NULL_CHECK_KEYWORDS.has(left.text)) {
592
+ varNode = right;
593
+ isNullCheck = true;
594
+ }
595
+ if (!isNullCheck || !varNode)
509
596
  return undefined;
510
- return { varName: left.text, typeName: right.text };
597
+ const varName = varNode.text;
598
+ // Look up the variable's resolved type (already stripped of nullable by extractSimpleTypeName)
599
+ const resolvedType = scopeEnv.get(varName);
600
+ if (!resolvedType)
601
+ return undefined;
602
+ // Check if the original declaration type was nullable by looking at the raw AST type node.
603
+ // extractSimpleTypeName already strips nullable markers, so we need the original to know
604
+ // if narrowing is meaningful (i.e., the variable was declared as nullable).
605
+ const declTypeNode = declarationTypeNodes.get(`${scope}\0${varName}`);
606
+ if (!declTypeNode)
607
+ return undefined;
608
+ const declText = declTypeNode.text;
609
+ // Only narrow if the original declaration was nullable
610
+ if (!declText.includes('null') && !declText.includes('undefined'))
611
+ return undefined;
612
+ // Find the if-body block to scope the narrowing
613
+ const ifBody = findIfConsequenceBlock(node);
614
+ if (!ifBody)
615
+ return undefined;
616
+ return {
617
+ varName,
618
+ typeName: resolvedType,
619
+ narrowingRange: { startIndex: ifBody.startIndex, endIndex: ifBody.endIndex },
620
+ };
621
+ };
622
+ /** Infer the type of a literal AST node for TypeScript overload disambiguation. */
623
+ const inferTsLiteralType = (node) => {
624
+ switch (node.type) {
625
+ case 'number':
626
+ return 'number';
627
+ case 'string':
628
+ case 'template_string':
629
+ return 'string';
630
+ case 'true':
631
+ case 'false':
632
+ return 'boolean';
633
+ case 'null':
634
+ return 'null';
635
+ case 'undefined':
636
+ return 'undefined';
637
+ case 'regex':
638
+ return 'RegExp';
639
+ default:
640
+ return undefined;
641
+ }
511
642
  };
512
643
  export const typeConfig = {
513
644
  declarationNodeTypes: DECLARATION_NODE_TYPES,
@@ -521,4 +652,5 @@ export const typeConfig = {
521
652
  extractForLoopBinding,
522
653
  extractPendingAssignment,
523
654
  extractPatternBinding,
655
+ inferLiteralType: inferTsLiteralType,
524
656
  };
@@ -1,24 +1,4 @@
1
- import type Parser from 'tree-sitter';
2
1
  import { SupportedLanguages } from '../../config/supported-languages.js';
3
- /** Tree-sitter AST node. Re-exported for use across ingestion modules. */
4
- export type SyntaxNode = Parser.SyntaxNode;
5
- /**
6
- * Ordered list of definition capture keys for tree-sitter query matches.
7
- * Used to extract the definition node from a capture map.
8
- */
9
- export declare const DEFINITION_CAPTURE_KEYS: readonly ["definition.function", "definition.class", "definition.interface", "definition.method", "definition.struct", "definition.enum", "definition.namespace", "definition.module", "definition.trait", "definition.impl", "definition.type", "definition.const", "definition.static", "definition.typedef", "definition.macro", "definition.union", "definition.property", "definition.record", "definition.delegate", "definition.annotation", "definition.constructor", "definition.template"];
10
- /** Extract the definition node from a tree-sitter query capture map. */
11
- export declare const getDefinitionNodeFromCaptures: (captureMap: Record<string, any>) => SyntaxNode | null;
12
- /**
13
- * Node types that represent function/method definitions across languages.
14
- * Used to find the enclosing function for a call site.
15
- */
16
- export declare const FUNCTION_NODE_TYPES: Set<string>;
17
- /**
18
- * Node types for standard function declarations that need C/C++ declarator handling.
19
- * Used by extractFunctionName to determine how to extract the function name.
20
- */
21
- export declare const FUNCTION_DECLARATION_TYPES: Set<string>;
22
2
  /**
23
3
  * Built-in function/method names that should not be tracked as call targets.
24
4
  * Covers JS/TS, Python, Kotlin, C/C++, PHP, Swift standard library functions.
@@ -26,113 +6,15 @@ export declare const FUNCTION_DECLARATION_TYPES: Set<string>;
26
6
  export declare const BUILT_IN_NAMES: Set<string>;
27
7
  /** Check if a name is a built-in function or common noise that should be filtered out */
28
8
  export declare const isBuiltInOrNoise: (name: string) => boolean;
29
- /** AST node types that represent a class-like container (for HAS_METHOD edge extraction) */
30
- export declare const CLASS_CONTAINER_TYPES: Set<string>;
31
- export declare const CONTAINER_TYPE_TO_LABEL: Record<string, string>;
32
- /** Walk up AST to find enclosing class/struct/interface/impl, return its generateId or null.
33
- * For Go method_declaration nodes, extracts receiver type (e.g. `func (u *User) Save()` → User struct). */
34
- export declare const findEnclosingClassId: (node: any, filePath: string) => string | null;
35
- /**
36
- * Extract function name and label from a function_definition or similar AST node.
37
- * Handles C/C++ qualified_identifier (ClassName::MethodName) and other language patterns.
38
- */
39
- export declare const extractFunctionName: (node: SyntaxNode) => {
40
- funcName: string | null;
41
- label: string;
42
- };
43
9
  /**
44
10
  * Yield control to the event loop so spinners/progress can render.
45
11
  * Call periodically in hot loops to prevent UI freezes.
46
12
  */
47
13
  export declare const yieldToEventLoop: () => Promise<void>;
48
- /**
49
- * Find a child of `childType` within a sibling node of `siblingType`.
50
- * Used for Kotlin AST traversal where visibility_modifier lives inside a modifiers sibling.
51
- */
52
- export declare const findSiblingChild: (parent: any, siblingType: string, childType: string) => any | null;
53
14
  /**
54
15
  * Map file extension to SupportedLanguage enum
55
16
  */
56
17
  export declare const getLanguageFromFilename: (filename: string) => SupportedLanguages | null;
57
- export interface MethodSignature {
58
- parameterCount: number | undefined;
59
- returnType: string | undefined;
60
- }
61
- /**
62
- * Extract parameter count and return type text from an AST method/function node.
63
- * Works across languages by looking for common AST patterns.
64
- */
65
- export declare const extractMethodSignature: (node: SyntaxNode | null | undefined) => MethodSignature;
66
- /**
67
- * Count direct arguments for a call expression across common tree-sitter grammars.
68
- * Returns undefined when the argument container cannot be located cheaply.
69
- */
70
- export declare const countCallArguments: (callNode: SyntaxNode | null | undefined) => number | undefined;
71
- type CallForm = 'free' | 'member' | 'constructor';
72
- /**
73
- * Infer whether a captured call site is a free call, member call, or constructor.
74
- * Returns undefined if the form cannot be determined.
75
- *
76
- * Works by inspecting the AST structure between callNode (@call) and nameNode (@call.name).
77
- * No tree-sitter query changes needed — the distinction is in the node types.
78
- */
79
- export declare const inferCallForm: (callNode: SyntaxNode, nameNode: SyntaxNode) => CallForm | undefined;
80
- export declare const extractReceiverName: (nameNode: SyntaxNode) => string | undefined;
81
- /**
82
- * Extract the raw receiver AST node for a member call.
83
- * Unlike extractReceiverName, this returns the receiver node regardless of its type —
84
- * including call_expression / method_invocation nodes that appear in chained calls
85
- * like `svc.getUser().save()`.
86
- *
87
- * Returns undefined when the call is not a member call or when no receiver node
88
- * can be found (e.g. top-level free calls).
89
- */
90
- export declare const extractReceiverNode: (nameNode: SyntaxNode) => SyntaxNode | undefined;
91
18
  export declare const isVerboseIngestionEnabled: () => boolean;
92
- /** Node types representing call expressions across supported languages. */
93
- export declare const CALL_EXPRESSION_TYPES: Set<string>;
94
- /**
95
- * Hard limit on chain depth to prevent runaway recursion.
96
- * For `a.b().c().d()`, the chain has depth 2 (b and c before d).
97
- */
98
- export declare const MAX_CHAIN_DEPTH = 3;
99
- /**
100
- * Walk a receiver AST node that is itself a call expression, accumulating the
101
- * chain of intermediate method names up to MAX_CHAIN_DEPTH.
102
- *
103
- * For `svc.getUser().save()`, called with the receiver of `save` (getUser() call):
104
- * returns { chain: ['getUser'], baseReceiverName: 'svc' }
105
- *
106
- * For `a.b().c().d()`, called with the receiver of `d` (c() call):
107
- * returns { chain: ['b', 'c'], baseReceiverName: 'a' }
108
- */
109
- export declare function extractCallChain(receiverCallNode: SyntaxNode): {
110
- chain: string[];
111
- baseReceiverName: string | undefined;
112
- } | undefined;
113
- /** One step in a mixed receiver chain. */
114
- export type MixedChainStep = {
115
- kind: 'field' | 'call';
116
- name: string;
117
- };
118
- /**
119
- * Walk a receiver AST node that may interleave field accesses and method calls,
120
- * building a unified chain of steps up to MAX_CHAIN_DEPTH.
121
- *
122
- * For `svc.getUser().address.save()`, called with the receiver of `save`
123
- * (`svc.getUser().address`, a field access node):
124
- * returns { chain: [{ kind:'call', name:'getUser' }, { kind:'field', name:'address' }],
125
- * baseReceiverName: 'svc' }
126
- *
127
- * For `user.getAddress().city.getName()`, called with receiver of `getName`
128
- * (`user.getAddress().city`):
129
- * returns { chain: [{ kind:'call', name:'getAddress' }, { kind:'field', name:'city' }],
130
- * baseReceiverName: 'user' }
131
- *
132
- * Pure field chains and pure call chains are special cases (all steps same kind).
133
- */
134
- export declare function extractMixedChain(receiverNode: SyntaxNode): {
135
- chain: MixedChainStep[];
136
- baseReceiverName: string | undefined;
137
- } | undefined;
138
- export {};
19
+ export * from './ast-helpers.js';
20
+ export * from './call-analysis.js';