gitnexus 1.4.6 → 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 (99) 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 +4 -3
  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 +55 -2
  24. package/dist/core/ingestion/call-processor.js +673 -108
  25. package/dist/core/ingestion/call-routing.d.ts +23 -2
  26. package/dist/core/ingestion/call-routing.js +21 -0
  27. package/dist/core/ingestion/entry-point-scoring.js +36 -26
  28. package/dist/core/ingestion/framework-detection.d.ts +10 -2
  29. package/dist/core/ingestion/framework-detection.js +49 -12
  30. package/dist/core/ingestion/heritage-processor.js +47 -49
  31. package/dist/core/ingestion/import-processor.d.ts +1 -1
  32. package/dist/core/ingestion/import-processor.js +103 -194
  33. package/dist/core/ingestion/import-resolution.d.ts +101 -0
  34. package/dist/core/ingestion/import-resolution.js +251 -0
  35. package/dist/core/ingestion/language-config.d.ts +3 -0
  36. package/dist/core/ingestion/language-config.js +13 -0
  37. package/dist/core/ingestion/markdown-processor.d.ts +17 -0
  38. package/dist/core/ingestion/markdown-processor.js +124 -0
  39. package/dist/core/ingestion/mro-processor.js +8 -3
  40. package/dist/core/ingestion/named-binding-extraction.d.ts +9 -43
  41. package/dist/core/ingestion/named-binding-extraction.js +89 -79
  42. package/dist/core/ingestion/parsing-processor.d.ts +3 -2
  43. package/dist/core/ingestion/parsing-processor.js +27 -60
  44. package/dist/core/ingestion/pipeline.d.ts +10 -0
  45. package/dist/core/ingestion/pipeline.js +425 -4
  46. package/dist/core/ingestion/resolution-context.d.ts +5 -0
  47. package/dist/core/ingestion/resolution-context.js +7 -4
  48. package/dist/core/ingestion/resolvers/index.d.ts +1 -1
  49. package/dist/core/ingestion/resolvers/index.js +1 -1
  50. package/dist/core/ingestion/resolvers/jvm.d.ts +2 -1
  51. package/dist/core/ingestion/resolvers/jvm.js +25 -9
  52. package/dist/core/ingestion/resolvers/php.d.ts +14 -0
  53. package/dist/core/ingestion/resolvers/php.js +43 -3
  54. package/dist/core/ingestion/resolvers/utils.d.ts +5 -0
  55. package/dist/core/ingestion/resolvers/utils.js +16 -0
  56. package/dist/core/ingestion/symbol-table.d.ts +29 -3
  57. package/dist/core/ingestion/symbol-table.js +42 -9
  58. package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -12
  59. package/dist/core/ingestion/tree-sitter-queries.js +243 -2
  60. package/dist/core/ingestion/type-env.d.ts +28 -1
  61. package/dist/core/ingestion/type-env.js +451 -72
  62. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
  63. package/dist/core/ingestion/type-extractors/c-cpp.js +146 -2
  64. package/dist/core/ingestion/type-extractors/csharp.js +189 -16
  65. package/dist/core/ingestion/type-extractors/go.js +45 -0
  66. package/dist/core/ingestion/type-extractors/index.d.ts +1 -1
  67. package/dist/core/ingestion/type-extractors/index.js +1 -1
  68. package/dist/core/ingestion/type-extractors/jvm.js +244 -69
  69. package/dist/core/ingestion/type-extractors/php.js +31 -4
  70. package/dist/core/ingestion/type-extractors/python.js +89 -17
  71. package/dist/core/ingestion/type-extractors/ruby.js +17 -2
  72. package/dist/core/ingestion/type-extractors/rust.js +72 -4
  73. package/dist/core/ingestion/type-extractors/shared.d.ts +12 -2
  74. package/dist/core/ingestion/type-extractors/shared.js +115 -13
  75. package/dist/core/ingestion/type-extractors/swift.js +7 -6
  76. package/dist/core/ingestion/type-extractors/types.d.ts +54 -11
  77. package/dist/core/ingestion/type-extractors/typescript.js +171 -9
  78. package/dist/core/ingestion/utils.d.ts +2 -95
  79. package/dist/core/ingestion/utils.js +3 -892
  80. package/dist/core/ingestion/workers/parse-worker.d.ts +36 -11
  81. package/dist/core/ingestion/workers/parse-worker.js +116 -95
  82. package/dist/core/lbug/csv-generator.js +18 -1
  83. package/dist/core/lbug/lbug-adapter.d.ts +12 -0
  84. package/dist/core/lbug/lbug-adapter.js +71 -4
  85. package/dist/core/lbug/schema.d.ts +6 -4
  86. package/dist/core/lbug/schema.js +27 -3
  87. package/dist/mcp/core/embedder.js +11 -3
  88. package/dist/mcp/core/lbug-adapter.d.ts +22 -0
  89. package/dist/mcp/core/lbug-adapter.js +178 -23
  90. package/dist/mcp/local/local-backend.d.ts +22 -0
  91. package/dist/mcp/local/local-backend.js +136 -32
  92. package/dist/mcp/resources.js +13 -0
  93. package/dist/mcp/server.js +26 -4
  94. package/dist/mcp/tools.js +17 -7
  95. package/dist/server/api.d.ts +19 -1
  96. package/dist/server/api.js +66 -6
  97. package/dist/storage/git.d.ts +12 -0
  98. package/dist/storage/git.js +21 -0
  99. package/package.json +12 -4
@@ -10,12 +10,31 @@
10
10
  * two packages have separate build targets (Node native vs WASM/browser).
11
11
  * Keep both copies in sync until a shared package is introduced.
12
12
  */
13
- import { SupportedLanguages } from '../../config/supported-languages.js';
14
13
  /** null = this call was not routed; fall through to default call handling */
15
14
  export type CallRoutingResult = RubyCallRouting | null;
15
+ /**
16
+ * Per-language call router.
17
+ * IMPORTANT: Call-routed imports bypass preprocessImportPath(), so any router that
18
+ * returns an importPath MUST validate it independently (length cap, control-char
19
+ * rejection). See routeRubyCall for the reference implementation.
20
+ */
16
21
  export type CallRouter = (calledName: string, callNode: any) => CallRoutingResult;
17
22
  /** Per-language call routing. noRouting = no special routing (normal call processing) */
18
- export declare const callRouters: Record<SupportedLanguages, CallRouter>;
23
+ export declare const callRouters: {
24
+ javascript: CallRouter;
25
+ typescript: CallRouter;
26
+ python: CallRouter;
27
+ java: CallRouter;
28
+ kotlin: CallRouter;
29
+ go: CallRouter;
30
+ rust: CallRouter;
31
+ csharp: CallRouter;
32
+ php: CallRouter;
33
+ swift: CallRouter;
34
+ cpp: CallRouter;
35
+ c: CallRouter;
36
+ ruby: typeof routeRubyCall;
37
+ };
19
38
  export type RubyCallRouting = {
20
39
  kind: 'import';
21
40
  importPath: string;
@@ -42,6 +61,8 @@ export interface RubyPropertyItem {
42
61
  accessorType: RubyAccessorType;
43
62
  startLine: number;
44
63
  endLine: number;
64
+ /** YARD @return [Type] annotation preceding the attr_accessor call */
65
+ declaredType?: string;
45
66
  }
46
67
  /**
47
68
  * Classify a Ruby call node and extract its semantic payload.
@@ -89,6 +89,26 @@ export function routeRubyCall(calledName, callNode) {
89
89
  }
90
90
  // ── attr_accessor / attr_reader / attr_writer → property definitions ───
91
91
  if (calledName === 'attr_accessor' || calledName === 'attr_reader' || calledName === 'attr_writer') {
92
+ // Extract YARD @return [Type] from preceding comment (e.g. `# @return [Address]`)
93
+ let yardType;
94
+ let sibling = callNode.previousSibling;
95
+ while (sibling) {
96
+ if (sibling.type === 'comment') {
97
+ const match = /@return\s+\[([^\]]+)\]/.exec(sibling.text);
98
+ if (match) {
99
+ const raw = match[1].trim();
100
+ // Extract simple type name: "User", "Array<User>" → "User"
101
+ const simple = raw.match(/^([A-Z]\w*)/);
102
+ if (simple)
103
+ yardType = simple[1];
104
+ break;
105
+ }
106
+ }
107
+ else if (sibling.isNamed) {
108
+ break; // stop at non-comment named sibling
109
+ }
110
+ sibling = sibling.previousSibling;
111
+ }
92
112
  const items = [];
93
113
  const argList = callNode.childForFieldName?.('arguments');
94
114
  for (const arg of (argList?.children ?? [])) {
@@ -98,6 +118,7 @@ export function routeRubyCall(calledName, callNode) {
98
118
  accessorType: calledName,
99
119
  startLine: arg.startPosition.row,
100
120
  endLine: arg.endPosition.row,
121
+ ...(yardType ? { declaredType: yardType } : {}),
101
122
  });
102
123
  }
103
124
  }
@@ -12,28 +12,31 @@
12
12
  import { detectFrameworkFromPath } from './framework-detection.js';
13
13
  import { SupportedLanguages } from '../../config/supported-languages.js';
14
14
  // ============================================================================
15
- // NAME PATTERNS - All 11 supported languages
15
+ // NAME PATTERNS - All 13 supported languages
16
16
  // ============================================================================
17
17
  /**
18
- * Common entry point naming patterns by language
19
- * These patterns indicate functions that are likely feature entry points
18
+ * Common entry point naming patterns by language.
19
+ * These patterns indicate functions that are likely feature entry points.
20
+ *
21
+ * Universal patterns are separated from per-language patterns so the per-language
22
+ * table can use `satisfies Record<SupportedLanguages, RegExp[]>` for compile-time
23
+ * exhaustiveness — the compiler catches any missing language entry.
20
24
  */
25
+ const UNIVERSAL_ENTRY_POINT_PATTERNS = [
26
+ /^(main|init|bootstrap|start|run|setup|configure)$/i,
27
+ /^handle[A-Z]/, // handleLogin, handleSubmit
28
+ /^on[A-Z]/, // onClick, onSubmit
29
+ /Handler$/, // RequestHandler
30
+ /Controller$/, // UserController
31
+ /^process[A-Z]/, // processPayment
32
+ /^execute[A-Z]/, // executeQuery
33
+ /^perform[A-Z]/, // performAction
34
+ /^dispatch[A-Z]/, // dispatchEvent
35
+ /^trigger[A-Z]/, // triggerAction
36
+ /^fire[A-Z]/, // fireEvent
37
+ /^emit[A-Z]/, // emitEvent
38
+ ];
21
39
  const ENTRY_POINT_PATTERNS = {
22
- // Universal patterns (apply to all languages)
23
- '*': [
24
- /^(main|init|bootstrap|start|run|setup|configure)$/i,
25
- /^handle[A-Z]/, // handleLogin, handleSubmit
26
- /^on[A-Z]/, // onClick, onSubmit
27
- /Handler$/, // RequestHandler
28
- /Controller$/, // UserController
29
- /^process[A-Z]/, // processPayment
30
- /^execute[A-Z]/, // executeQuery
31
- /^perform[A-Z]/, // performAction
32
- /^dispatch[A-Z]/, // dispatchEvent
33
- /^trigger[A-Z]/, // triggerAction
34
- /^fire[A-Z]/, // fireEvent
35
- /^emit[A-Z]/, // emitEvent
36
- ],
37
40
  // JavaScript/TypeScript
38
41
  [SupportedLanguages.JavaScript]: [
39
42
  /^use[A-Z]/, // React hooks (useEffect, etc.)
@@ -55,6 +58,16 @@ const ENTRY_POINT_PATTERNS = {
55
58
  /^build[A-Z]/, // Builder patterns
56
59
  /Service$/, // UserService
57
60
  ],
61
+ // Kotlin
62
+ [SupportedLanguages.Kotlin]: [
63
+ /^on(Create|Start|Resume|Pause|Stop|Destroy)$/, // Android lifecycle
64
+ /^do[A-Z]/, // doGet, doPost (shared JVM Servlet pattern)
65
+ /^create[A-Z]/, // Factory patterns
66
+ /^build[A-Z]/, // Builder patterns
67
+ /ViewModel$/, // MVVM pattern (Android)
68
+ /^module$/, // Ktor module entry point
69
+ /Service$/, // Service classes
70
+ ],
58
71
  // C#
59
72
  [SupportedLanguages.CSharp]: [
60
73
  /^(Get|Post|Put|Delete|Patch)/, // ASP.NET action methods
@@ -186,13 +199,10 @@ const ENTRY_POINT_PATTERNS = {
186
199
  ],
187
200
  };
188
201
  /** Pre-computed merged patterns (universal + language-specific) to avoid per-call array allocation. */
189
- const MERGED_ENTRY_POINT_PATTERNS = {};
190
- const UNIVERSAL_PATTERNS = ENTRY_POINT_PATTERNS['*'] || [];
191
- for (const [lang, patterns] of Object.entries(ENTRY_POINT_PATTERNS)) {
192
- if (lang === '*')
193
- continue;
194
- MERGED_ENTRY_POINT_PATTERNS[lang] = [...UNIVERSAL_PATTERNS, ...patterns];
195
- }
202
+ const MERGED_ENTRY_POINT_PATTERNS = Object.fromEntries(Object.keys(ENTRY_POINT_PATTERNS).map(lang => [
203
+ lang,
204
+ [...UNIVERSAL_ENTRY_POINT_PATTERNS, ...ENTRY_POINT_PATTERNS[lang]],
205
+ ]));
196
206
  // ============================================================================
197
207
  // UTILITY PATTERNS - Functions that should be penalized
198
208
  // ============================================================================
@@ -258,7 +268,7 @@ export function calculateEntryPointScore(name, language, isExported, callerCount
258
268
  }
259
269
  else {
260
270
  // Check positive patterns
261
- const allPatterns = MERGED_ENTRY_POINT_PATTERNS[language] || UNIVERSAL_PATTERNS;
271
+ const allPatterns = MERGED_ENTRY_POINT_PATTERNS[language];
262
272
  if (allPatterns.some(p => p.test(name))) {
263
273
  nameMultiplier = 1.5; // Bonus for matching entry point pattern
264
274
  reasons.push('entry-pattern');
@@ -9,6 +9,7 @@
9
9
  * DESIGN: Returns null for unknown frameworks, which causes a 1.0 multiplier
10
10
  * (no bonus, no penalty) - same behavior as before this feature.
11
11
  */
12
+ import { SupportedLanguages } from '../../config/supported-languages.js';
12
13
  export interface FrameworkHint {
13
14
  framework: string;
14
15
  entryPointMultiplier: number;
@@ -37,15 +38,22 @@ export declare const FRAMEWORK_AST_PATTERNS: {
37
38
  blazor: string[];
38
39
  efcore: string[];
39
40
  'go-http': string[];
41
+ gin: string[];
42
+ echo: string[];
43
+ fiber: string[];
44
+ 'go-grpc': string[];
40
45
  laravel: string[];
41
46
  actix: string[];
42
47
  axum: string[];
43
48
  rocket: string[];
49
+ tokio: string[];
50
+ qt: string[];
44
51
  uikit: string[];
45
52
  swiftui: string[];
46
- combine: string[];
53
+ vapor: string[];
54
+ rails: string[];
55
+ sinatra: string[];
47
56
  };
48
- import { SupportedLanguages } from '../../config/supported-languages.js';
49
57
  /**
50
58
  * Detect framework entry points from AST definition text (decorators/annotations/attributes).
51
59
  * Returns null if no known pattern is found.
@@ -9,6 +9,7 @@
9
9
  * DESIGN: Returns null for unknown frameworks, which causes a 1.0 multiplier
10
10
  * (no bonus, no penalty) - same behavior as before this feature.
11
11
  */
12
+ import { SupportedLanguages } from '../../config/supported-languages.js';
12
13
  // ============================================================================
13
14
  // PATH-BASED FRAMEWORK DETECTION
14
15
  // ============================================================================
@@ -180,8 +181,8 @@ export function detectFrameworkFromPath(filePath) {
180
181
  if (p.includes('/controllers/') && p.endsWith('.go')) {
181
182
  return { framework: 'go-mvc', entryPointMultiplier: 2.5, reason: 'go-controller' };
182
183
  }
183
- // Go main.go files (THE entry point)
184
- if (p.endsWith('/main.go') || p.endsWith('/cmd/') && p.endsWith('.go')) {
184
+ // Go main.go files (THE entry point) — only match main.go, not arbitrary .go files under cmd/
185
+ if (p.endsWith('/main.go')) {
185
186
  return { framework: 'go', entryPointMultiplier: 3.0, reason: 'go-main' };
186
187
  }
187
188
  // ========== RUST FRAMEWORKS ==========
@@ -333,21 +334,31 @@ export const FRAMEWORK_AST_PATTERNS = {
333
334
  'signalr': ['[HubMethodName]', ': Hub', ': Hub<'],
334
335
  'blazor': ['@page', '[Parameter]', '@inject'],
335
336
  'efcore': ['DbContext', 'DbSet<', 'OnModelCreating'],
336
- // Go patterns (function signatures)
337
- 'go-http': ['http.Handler', 'http.HandlerFunc', 'ServeHTTP'],
337
+ // Go patterns (function signatures include framework types)
338
+ 'go-http': ['http.Handler', 'http.HandlerFunc', 'ServeHTTP', 'http.ResponseWriter', 'http.Request'],
339
+ 'gin': ['gin.Context', 'gin.Default', 'gin.New'],
340
+ 'echo': ['echo.Context', 'echo.New'],
341
+ 'fiber': ['fiber.Ctx', 'fiber.New', 'fiber.App'],
342
+ 'go-grpc': ['grpc.Server', 'RegisterServer', 'pb.Unimplemented'],
338
343
  // PHP/Laravel
339
344
  'laravel': ['Route::get', 'Route::post', 'Route::put', 'Route::delete',
340
345
  'Route::resource', 'Route::apiResource', '#[Route('],
341
- // Rust macros
342
- 'actix': ['#[get', '#[post', '#[put', '#[delete'],
343
- 'axum': ['Router::new'],
344
- 'rocket': ['#[get', '#[post'],
346
+ // Rust macros (proc-macro attributes in definition text)
347
+ 'actix': ['#[get', '#[post', '#[put', '#[delete', '#[actix_web', 'HttpRequest', 'HttpResponse'],
348
+ 'axum': ['Router::new', 'axum::extract', 'axum::routing'],
349
+ 'rocket': ['#[get', '#[post', '#[launch', 'rocket::'],
350
+ 'tokio': ['#[tokio::main]', '#[tokio::test]'],
351
+ // C++ patterns (Qt, Boost)
352
+ 'qt': ['Q_OBJECT', 'Q_INVOKABLE', 'Q_PROPERTY', 'Q_SIGNALS', 'Q_SLOTS', 'Q_SIGNAL', 'Q_SLOT', 'QWidget', 'QApplication'],
345
353
  // Swift/iOS
346
- 'uikit': ['viewDidLoad', 'viewWillAppear', 'viewDidAppear', 'UIViewController'],
347
- 'swiftui': ['@main', 'WindowGroup', 'ContentView', '@StateObject', '@ObservedObject'],
348
- 'combine': ['sink', 'assign', 'Publisher', 'Subscriber'],
354
+ 'uikit': ['viewDidLoad', 'viewWillAppear', 'viewDidAppear', 'UIViewController', '@IBOutlet', '@IBAction', '@objc'],
355
+ 'swiftui': ['@main', 'WindowGroup', 'ContentView', '@StateObject', '@ObservedObject', '@EnvironmentObject', '@Published'],
356
+ 'vapor': ['app.get', 'app.post', 'req.content.decode', 'Vapor'],
357
+ // Ruby patterns (class-level macros in definition text)
358
+ 'rails': ['ApplicationController', 'ApplicationRecord', 'ActiveRecord::Base',
359
+ 'before_action', 'after_action', 'has_many', 'belongs_to', 'has_one', 'validates'],
360
+ 'sinatra': ['Sinatra::Base', 'Sinatra::Application'],
349
361
  };
350
- import { SupportedLanguages } from '../../config/supported-languages.js';
351
362
  const AST_FRAMEWORK_PATTERNS_BY_LANGUAGE = {
352
363
  [SupportedLanguages.JavaScript]: [
353
364
  { framework: 'nestjs', entryPointMultiplier: 3.2, reason: 'nestjs-decorator', patterns: FRAMEWORK_AST_PATTERNS.nestjs },
@@ -378,6 +389,32 @@ const AST_FRAMEWORK_PATTERNS_BY_LANGUAGE = {
378
389
  [SupportedLanguages.PHP]: [
379
390
  { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'php-route-attribute', patterns: FRAMEWORK_AST_PATTERNS.laravel },
380
391
  ],
392
+ [SupportedLanguages.Go]: [
393
+ { framework: 'go-http', entryPointMultiplier: 2.5, reason: 'go-http-handler', patterns: FRAMEWORK_AST_PATTERNS['go-http'] },
394
+ { framework: 'gin', entryPointMultiplier: 3.0, reason: 'gin-handler', patterns: FRAMEWORK_AST_PATTERNS.gin },
395
+ { framework: 'echo', entryPointMultiplier: 3.0, reason: 'echo-handler', patterns: FRAMEWORK_AST_PATTERNS.echo },
396
+ { framework: 'fiber', entryPointMultiplier: 3.0, reason: 'fiber-handler', patterns: FRAMEWORK_AST_PATTERNS.fiber },
397
+ { framework: 'go-grpc', entryPointMultiplier: 2.8, reason: 'grpc-service', patterns: FRAMEWORK_AST_PATTERNS['go-grpc'] },
398
+ ],
399
+ [SupportedLanguages.Rust]: [
400
+ { framework: 'actix-web', entryPointMultiplier: 3.0, reason: 'actix-attribute', patterns: FRAMEWORK_AST_PATTERNS.actix },
401
+ { framework: 'axum', entryPointMultiplier: 3.0, reason: 'axum-routing', patterns: FRAMEWORK_AST_PATTERNS.axum },
402
+ { framework: 'rocket', entryPointMultiplier: 3.0, reason: 'rocket-attribute', patterns: FRAMEWORK_AST_PATTERNS.rocket },
403
+ { framework: 'tokio', entryPointMultiplier: 2.5, reason: 'tokio-runtime', patterns: FRAMEWORK_AST_PATTERNS.tokio },
404
+ ],
405
+ [SupportedLanguages.C]: [], // C has no framework-specific AST patterns (POSIX/socket patterns are in entry-point-scoring)
406
+ [SupportedLanguages.CPlusPlus]: [
407
+ { framework: 'qt', entryPointMultiplier: 2.8, reason: 'qt-macro', patterns: FRAMEWORK_AST_PATTERNS.qt },
408
+ ],
409
+ [SupportedLanguages.Swift]: [
410
+ { framework: 'uikit', entryPointMultiplier: 2.5, reason: 'uikit-lifecycle', patterns: FRAMEWORK_AST_PATTERNS.uikit },
411
+ { framework: 'swiftui', entryPointMultiplier: 2.8, reason: 'swiftui-pattern', patterns: FRAMEWORK_AST_PATTERNS.swiftui },
412
+ { framework: 'vapor', entryPointMultiplier: 3.0, reason: 'vapor-routing', patterns: FRAMEWORK_AST_PATTERNS.vapor },
413
+ ],
414
+ [SupportedLanguages.Ruby]: [
415
+ { framework: 'rails', entryPointMultiplier: 3.0, reason: 'rails-pattern', patterns: FRAMEWORK_AST_PATTERNS.rails },
416
+ { framework: 'sinatra', entryPointMultiplier: 2.8, reason: 'sinatra-pattern', patterns: FRAMEWORK_AST_PATTERNS.sinatra },
417
+ ],
381
418
  };
382
419
  /** Pre-lowercased patterns for O(1) pattern matching at runtime */
383
420
  const AST_PATTERNS_LOWERED = Object.fromEntries(Object.entries(AST_FRAMEWORK_PATTERNS_BY_LANGUAGE).map(([lang, cfgs]) => [
@@ -20,6 +20,7 @@ import { generateId } from '../../lib/utils.js';
20
20
  import { getLanguageFromFilename, isVerboseIngestionEnabled, yieldToEventLoop } from './utils.js';
21
21
  import { SupportedLanguages } from '../../config/supported-languages.js';
22
22
  import { getTreeSitterBufferSize } from './constants.js';
23
+ import { TIER_CONFIDENCE } from './resolution-context.js';
23
24
  /** C#/Java convention: interfaces start with I followed by an uppercase letter */
24
25
  const INTERFACE_NAME_RE = /^I[A-Z]/;
25
26
  /**
@@ -50,20 +51,17 @@ const resolveExtendsType = (parentName, currentFilePath, ctx, language) => {
50
51
  }
51
52
  return { type: 'EXTENDS', idPrefix: 'Class' };
52
53
  };
53
- /**
54
- * Resolve a symbol ID for heritage, with fallback to generated ID.
55
- * Uses ctx.resolve() → pick first candidate's nodeId → generate synthetic ID.
56
- */
57
54
  const resolveHeritageId = (name, filePath, ctx, fallbackLabel, fallbackKey) => {
58
55
  const resolved = ctx.resolve(name, filePath);
59
56
  if (resolved && resolved.candidates.length > 0) {
60
57
  // For global with multiple candidates, refuse (a wrong edge is worse than no edge)
61
58
  if (resolved.tier === 'global' && resolved.candidates.length > 1) {
62
- return generateId(fallbackLabel, fallbackKey ?? name);
59
+ return { id: generateId(fallbackLabel, fallbackKey ?? name), confidence: TIER_CONFIDENCE['global'] };
63
60
  }
64
- return resolved.candidates[0].nodeId;
61
+ return { id: resolved.candidates[0].nodeId, confidence: TIER_CONFIDENCE[resolved.tier] };
65
62
  }
66
- return generateId(fallbackLabel, fallbackKey ?? name);
63
+ // Unresolved: use global-tier confidence as fallback
64
+ return { id: generateId(fallbackLabel, fallbackKey ?? name), confidence: TIER_CONFIDENCE['global'] };
67
65
  };
68
66
  export const processHeritage = async (graph, files, astCache, ctx, onProgress) => {
69
67
  const parser = await loadParser();
@@ -132,15 +130,15 @@ export const processHeritage = async (graph, files, astCache, ctx, onProgress) =
132
130
  const className = captureMap['heritage.class'].text;
133
131
  const parentClassName = captureMap['heritage.extends'].text;
134
132
  const { type: relType, idPrefix } = resolveExtendsType(parentClassName, file.path, ctx, language);
135
- const childId = resolveHeritageId(className, file.path, ctx, 'Class', `${file.path}:${className}`);
136
- const parentId = resolveHeritageId(parentClassName, file.path, ctx, idPrefix);
137
- if (childId && parentId && childId !== parentId) {
133
+ const child = resolveHeritageId(className, file.path, ctx, 'Class', `${file.path}:${className}`);
134
+ const parent = resolveHeritageId(parentClassName, file.path, ctx, idPrefix);
135
+ if (child.id && parent.id && child.id !== parent.id) {
138
136
  graph.addRelationship({
139
- id: generateId(relType, `${childId}->${parentId}`),
140
- sourceId: childId,
141
- targetId: parentId,
137
+ id: generateId(relType, `${child.id}->${parent.id}`),
138
+ sourceId: child.id,
139
+ targetId: parent.id,
142
140
  type: relType,
143
- confidence: 1.0,
141
+ confidence: Math.sqrt(child.confidence * parent.confidence),
144
142
  reason: '',
145
143
  });
146
144
  }
@@ -149,15 +147,15 @@ export const processHeritage = async (graph, files, astCache, ctx, onProgress) =
149
147
  if (captureMap['heritage.class'] && captureMap['heritage.implements']) {
150
148
  const className = captureMap['heritage.class'].text;
151
149
  const interfaceName = captureMap['heritage.implements'].text;
152
- const classId = resolveHeritageId(className, file.path, ctx, 'Class', `${file.path}:${className}`);
153
- const interfaceId = resolveHeritageId(interfaceName, file.path, ctx, 'Interface');
154
- if (classId && interfaceId) {
150
+ const cls = resolveHeritageId(className, file.path, ctx, 'Class', `${file.path}:${className}`);
151
+ const iface = resolveHeritageId(interfaceName, file.path, ctx, 'Interface');
152
+ if (cls.id && iface.id) {
155
153
  graph.addRelationship({
156
- id: generateId('IMPLEMENTS', `${classId}->${interfaceId}`),
157
- sourceId: classId,
158
- targetId: interfaceId,
154
+ id: generateId('IMPLEMENTS', `${cls.id}->${iface.id}`),
155
+ sourceId: cls.id,
156
+ targetId: iface.id,
159
157
  type: 'IMPLEMENTS',
160
- confidence: 1.0,
158
+ confidence: Math.sqrt(cls.confidence * iface.confidence),
161
159
  reason: '',
162
160
  });
163
161
  }
@@ -166,15 +164,15 @@ export const processHeritage = async (graph, files, astCache, ctx, onProgress) =
166
164
  if (captureMap['heritage.trait'] && captureMap['heritage.class']) {
167
165
  const structName = captureMap['heritage.class'].text;
168
166
  const traitName = captureMap['heritage.trait'].text;
169
- const structId = resolveHeritageId(structName, file.path, ctx, 'Struct', `${file.path}:${structName}`);
170
- const traitId = resolveHeritageId(traitName, file.path, ctx, 'Trait');
171
- if (structId && traitId) {
167
+ const strct = resolveHeritageId(structName, file.path, ctx, 'Struct', `${file.path}:${structName}`);
168
+ const trait = resolveHeritageId(traitName, file.path, ctx, 'Trait');
169
+ if (strct.id && trait.id) {
172
170
  graph.addRelationship({
173
- id: generateId('IMPLEMENTS', `${structId}->${traitId}`),
174
- sourceId: structId,
175
- targetId: traitId,
171
+ id: generateId('IMPLEMENTS', `${strct.id}->${trait.id}`),
172
+ sourceId: strct.id,
173
+ targetId: trait.id,
176
174
  type: 'IMPLEMENTS',
177
- confidence: 1.0,
175
+ confidence: Math.sqrt(strct.confidence * trait.confidence),
178
176
  reason: 'trait-impl',
179
177
  });
180
178
  }
@@ -205,43 +203,43 @@ export const processHeritageFromExtracted = async (graph, extractedHeritage, ctx
205
203
  if (!fileLanguage)
206
204
  continue;
207
205
  const { type: relType, idPrefix } = resolveExtendsType(h.parentName, h.filePath, ctx, fileLanguage);
208
- const childId = resolveHeritageId(h.className, h.filePath, ctx, 'Class', `${h.filePath}:${h.className}`);
209
- const parentId = resolveHeritageId(h.parentName, h.filePath, ctx, idPrefix);
210
- if (childId && parentId && childId !== parentId) {
206
+ const child = resolveHeritageId(h.className, h.filePath, ctx, 'Class', `${h.filePath}:${h.className}`);
207
+ const parent = resolveHeritageId(h.parentName, h.filePath, ctx, idPrefix);
208
+ if (child.id && parent.id && child.id !== parent.id) {
211
209
  graph.addRelationship({
212
- id: generateId(relType, `${childId}->${parentId}`),
213
- sourceId: childId,
214
- targetId: parentId,
210
+ id: generateId(relType, `${child.id}->${parent.id}`),
211
+ sourceId: child.id,
212
+ targetId: parent.id,
215
213
  type: relType,
216
- confidence: 1.0,
214
+ confidence: Math.sqrt(child.confidence * parent.confidence),
217
215
  reason: '',
218
216
  });
219
217
  }
220
218
  }
221
219
  else if (h.kind === 'implements') {
222
- const classId = resolveHeritageId(h.className, h.filePath, ctx, 'Class', `${h.filePath}:${h.className}`);
223
- const interfaceId = resolveHeritageId(h.parentName, h.filePath, ctx, 'Interface');
224
- if (classId && interfaceId) {
220
+ const cls = resolveHeritageId(h.className, h.filePath, ctx, 'Class', `${h.filePath}:${h.className}`);
221
+ const iface = resolveHeritageId(h.parentName, h.filePath, ctx, 'Interface');
222
+ if (cls.id && iface.id) {
225
223
  graph.addRelationship({
226
- id: generateId('IMPLEMENTS', `${classId}->${interfaceId}`),
227
- sourceId: classId,
228
- targetId: interfaceId,
224
+ id: generateId('IMPLEMENTS', `${cls.id}->${iface.id}`),
225
+ sourceId: cls.id,
226
+ targetId: iface.id,
229
227
  type: 'IMPLEMENTS',
230
- confidence: 1.0,
228
+ confidence: Math.sqrt(cls.confidence * iface.confidence),
231
229
  reason: '',
232
230
  });
233
231
  }
234
232
  }
235
233
  else if (h.kind === 'trait-impl' || h.kind === 'include' || h.kind === 'extend' || h.kind === 'prepend') {
236
- const structId = resolveHeritageId(h.className, h.filePath, ctx, 'Struct', `${h.filePath}:${h.className}`);
237
- const traitId = resolveHeritageId(h.parentName, h.filePath, ctx, 'Trait');
238
- if (structId && traitId) {
234
+ const strct = resolveHeritageId(h.className, h.filePath, ctx, 'Struct', `${h.filePath}:${h.className}`);
235
+ const trait = resolveHeritageId(h.parentName, h.filePath, ctx, 'Trait');
236
+ if (strct.id && trait.id) {
239
237
  graph.addRelationship({
240
- id: generateId('IMPLEMENTS', `${structId}->${traitId}:${h.kind}`),
241
- sourceId: structId,
242
- targetId: traitId,
238
+ id: generateId('IMPLEMENTS', `${strct.id}->${trait.id}:${h.kind}`),
239
+ sourceId: strct.id,
240
+ targetId: trait.id,
243
241
  type: 'IMPLEMENTS',
244
- confidence: 1.0,
242
+ confidence: Math.sqrt(strct.confidence * trait.confidence),
245
243
  reason: h.kind,
246
244
  });
247
245
  }
@@ -21,7 +21,7 @@ export interface ImportResolutionContext {
21
21
  allFilePaths: Set<string>;
22
22
  allFileList: string[];
23
23
  normalizedFileList: string[];
24
- suffixIndex: SuffixIndex;
24
+ index: SuffixIndex;
25
25
  resolveCache: Map<string, string | null>;
26
26
  }
27
27
  export declare function buildImportResolutionContext(allPaths: string[]): ImportResolutionContext;