From 0b84496cfd7b1d79fcfe890c9884a16b2467130d Mon Sep 17 00:00:00 2001 From: "Christian C. Berclaz" Date: Sat, 2 May 2026 17:24:12 +0200 Subject: [PATCH] refactor(ingestion): consolidate per-language patterns into LanguageProvider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move entry-point name patterns and AST framework detection patterns from shared maps in entry-point-scoring.ts and framework-detection.ts into each LanguageProvider. The shared files now build their lookup tables dynamically from the provider registry at module load. This aligns with the architecture principle that shared pipeline code must not name languages. Adding a new language no longer requires modifying entry-point-scoring.ts or framework-detection.ts — the provider file is the single source of truth for all language-specific data. New LanguageProvider fields: - entryPointPatterns: RegExp[] (default: []) - astFrameworkPatterns: AstFrameworkPatternConfig[] (default: []) --- .../src/core/ingestion/entry-point-scoring.ts | 207 +-------- .../src/core/ingestion/framework-detection.ts | 397 +----------------- .../src/core/ingestion/language-provider.ts | 18 + .../src/core/ingestion/languages/c-cpp.ts | 71 ++++ .../src/core/ingestion/languages/cobol.ts | 2 + .../src/core/ingestion/languages/csharp.ts | 50 +++ gitnexus/src/core/ingestion/languages/dart.ts | 37 ++ gitnexus/src/core/ingestion/languages/go.ts | 40 ++ gitnexus/src/core/ingestion/languages/java.ts | 22 + .../src/core/ingestion/languages/kotlin.ts | 42 ++ gitnexus/src/core/ingestion/languages/php.ts | 36 ++ .../src/core/ingestion/languages/python.ts | 16 + gitnexus/src/core/ingestion/languages/ruby.ts | 26 ++ gitnexus/src/core/ingestion/languages/rust.ts | 36 ++ .../src/core/ingestion/languages/swift.ts | 55 +++ .../core/ingestion/languages/typescript.ts | 47 +++ gitnexus/src/core/ingestion/languages/vue.ts | 2 + .../test/unit/framework-detection.test.ts | 33 +- 18 files changed, 538 insertions(+), 599 deletions(-) diff --git a/gitnexus/src/core/ingestion/entry-point-scoring.ts b/gitnexus/src/core/ingestion/entry-point-scoring.ts index f8d85ae48..58cf9389c 100644 --- a/gitnexus/src/core/ingestion/entry-point-scoring.ts +++ b/gitnexus/src/core/ingestion/entry-point-scoring.ts @@ -12,18 +12,15 @@ import { detectFrameworkFromPath } from './framework-detection.js'; import { SupportedLanguages } from 'gitnexus-shared'; +import { providers } from './languages/index.js'; // ============================================================================ -// NAME PATTERNS - All 13 supported languages +// NAME PATTERNS // ============================================================================ /** - * Common entry point naming patterns by language. - * These patterns indicate functions that are likely feature entry points. - * - * Universal patterns are separated from per-language patterns so the per-language - * table can use `satisfies Record` for compile-time - * exhaustiveness — the compiler catches any missing language entry. + * Universal entry point naming patterns shared across all languages. + * Per-language patterns live on each LanguageProvider.entryPointPatterns. */ const UNIVERSAL_ENTRY_POINT_PATTERNS: RegExp[] = [ /^(main|init|bootstrap|start|run|setup|configure)$/i, @@ -40,201 +37,11 @@ const UNIVERSAL_ENTRY_POINT_PATTERNS: RegExp[] = [ /^emit[A-Z]/, // emitEvent ]; -export const ENTRY_POINT_PATTERNS = { - // JavaScript/TypeScript - [SupportedLanguages.JavaScript]: [ - /^use[A-Z]/, // React hooks (useEffect, etc.) - ], - [SupportedLanguages.TypeScript]: [ - /^use[A-Z]/, // React hooks - ], - - // Python - [SupportedLanguages.Python]: [ - /^app$/, // Flask/FastAPI app - /^(get|post|put|delete|patch)_/i, // REST conventions - /^api_/, // API functions - /^view_/, // Django views - ], - - // Java - [SupportedLanguages.Java]: [ - /^do[A-Z]/, // doGet, doPost (Servlets) - /^create[A-Z]/, // Factory patterns - /^build[A-Z]/, // Builder patterns - /Service$/, // UserService - ], - - // Kotlin - [SupportedLanguages.Kotlin]: [ - /^on(Create|Start|Resume|Pause|Stop|Destroy)$/, // Android lifecycle - /^do[A-Z]/, // doGet, doPost (shared JVM Servlet pattern) - /^create[A-Z]/, // Factory patterns - /^build[A-Z]/, // Builder patterns - /ViewModel$/, // MVVM pattern (Android) - /^module$/, // Ktor module entry point - /Service$/, // Service classes - ], - - // C# - [SupportedLanguages.CSharp]: [ - /^(Get|Post|Put|Delete|Patch)/, // ASP.NET action methods - /Action$/, // MVC actions - /^On[A-Z]/, // Event handlers / Blazor lifecycle - /Async$/, // Async entry points - /^Configure$/, // Startup.Configure - /^ConfigureServices$/, // Startup.ConfigureServices - /^Handle$/, // MediatR / generic handler - /^Execute$/, // Command pattern - /^Invoke$/, // Middleware Invoke - /^Map[A-Z]/, // Minimal API MapGet, MapPost - /Service$/, // Service classes - /^Seed/, // Database seeding - ], - - // Go - [SupportedLanguages.Go]: [ - /Handler$/, // http.Handler pattern - /^Serve/, // ServeHTTP - /^New[A-Z]/, // Constructor pattern (returns new instance) - /^Make[A-Z]/, // Make functions - ], - - // Rust - [SupportedLanguages.Rust]: [ - /^(get|post|put|delete)_handler$/i, - /^handle_/, // handle_request - /^new$/, // Constructor pattern - /^run$/, // run entry point - /^spawn/, // Async spawn - ], - - // C - explicit main() boost plus common C entry point conventions - [SupportedLanguages.C]: [ - /^main$/, // THE entry point - /^init_/, // init_server, init_client - /_init$/, // module_init, server_init - /^start_/, // start_server - /_start$/, // thread_start - /^run_/, // run_loop - /_run$/, // event_run - /^stop_/, // stop_server - /_stop$/, // service_stop - /^open_/, // open_connection - /_open$/, // file_open - /^close_/, // close_connection - /_close$/, // socket_close - /^create_/, // create_session - /_create$/, // object_create - /^destroy_/, // destroy_session - /_destroy$/, // object_destroy - /^handle_/, // handle_request - /_handler$/, // signal_handler - /_callback$/, // event_callback - /^cmd_/, // tmux: cmd_new_window, cmd_attach_session - /^server_/, // server_start, server_loop - /^client_/, // client_connect - /^session_/, // session_create - /^window_/, // window_resize (tmux) - /^key_/, // key_press - /^input_/, // input_parse - /^output_/, // output_write - /^notify_/, // notify_client - /^control_/, // control_start - ], - - // C++ - same as C plus OOP/template patterns - [SupportedLanguages.CPlusPlus]: [ - /^main$/, // THE entry point - /^init_/, - /_init$/, - /^Create[A-Z]/, // Factory patterns - /^create_/, - /^Run$/, // Run methods - /^run$/, - /^Start$/, // Start methods - /^start$/, - /^handle_/, - /_handler$/, - /_callback$/, - /^OnEvent/, // Event callbacks - /^on_/, - /::Run$/, // Class::Run - /::Start$/, // Class::Start - /::Init$/, // Class::Init - /::Execute$/, // Class::Execute - ], - - // Swift / iOS - [SupportedLanguages.Swift]: [ - /^viewDidLoad$/, // UIKit lifecycle - /^viewWillAppear$/, // UIKit lifecycle - /^viewDidAppear$/, // UIKit lifecycle - /^viewWillDisappear$/, // UIKit lifecycle - /^viewDidDisappear$/, // UIKit lifecycle - /^application\(/, // AppDelegate methods - /^scene\(/, // SceneDelegate methods - /^body$/, // SwiftUI View.body - /Coordinator$/, // Coordinator pattern - /^sceneDidBecomeActive$/, // SceneDelegate lifecycle - /^sceneWillResignActive$/, // SceneDelegate lifecycle - /^didFinishLaunchingWithOptions$/, // AppDelegate - /ViewController$/, // ViewController classes - /^configure[A-Z]/, // Configuration methods - /^setup[A-Z]/, // Setup methods - /^makeBody$/, // SwiftUI ViewModifier - ], - - // PHP / Laravel - [SupportedLanguages.PHP]: [ - /Controller$/, // UserController (class name convention) - /^handle$/, // Job::handle(), Listener::handle() - /^execute$/, // Command::execute() - /^boot$/, // ServiceProvider::boot() - /^register$/, // ServiceProvider::register() - /^__invoke$/, // Invokable controllers/actions - /^(index|show|store|update|destroy|create|edit)$/, // RESTful resource methods - /^(get|post|put|delete|patch)[A-Z]/, // Explicit HTTP method actions - /^run$/, // Command/Job run() - /^fire$/, // Event fire() - /^dispatch$/, // Dispatchable jobs - /Service$/, // UserService (Service layer) - /Repository$/, // UserRepository (Repository pattern) - /^find$/, // Repository::find() - /^findAll$/, // Repository::findAll() - /^save$/, // Repository::save() - /^delete$/, // Repository::delete() - ], - - // Ruby - [SupportedLanguages.Ruby]: [ - /^call$/, // Service objects (MyService.call) - /^perform$/, // Background jobs (Sidekiq, ActiveJob) - /^execute$/, // Command pattern - ], - - // Dart / Flutter - [SupportedLanguages.Dart]: [ - /^main$/, // App entry - /^build$/, // Widget.build — fundamental Flutter render entry point - /^createState$/, // StatefulWidget.createState - /^initState$/, // State lifecycle initialization - /^dispose$/, // State lifecycle teardown - /^didChangeDependencies$/, // State lifecycle — InheritedWidget changes - /^didUpdateWidget$/, // State lifecycle — widget rebuild with new config - /^runApp$/, // App entry point - /^onEvent$/, // BLoC event handler - /^mapEventToState$/, // Legacy BLoC pattern - ], - [SupportedLanguages.Vue]: [], // Vue uses TypeScript queries — entry points handled via TS patterns - [SupportedLanguages.Cobol]: [], // Standalone regex processor — no tree-sitter entry points -} satisfies Record; - -/** Pre-computed merged patterns (universal + language-specific) to avoid per-call array allocation. */ +/** Pre-computed merged patterns (universal + language-specific) from providers. */ const MERGED_ENTRY_POINT_PATTERNS = Object.fromEntries( - Object.values(SupportedLanguages).map((lang) => [ + Object.entries(providers).map(([lang, provider]) => [ lang, - [...UNIVERSAL_ENTRY_POINT_PATTERNS, ...(ENTRY_POINT_PATTERNS[lang] ?? [])], + [...UNIVERSAL_ENTRY_POINT_PATTERNS, ...(provider.entryPointPatterns ?? [])], ]), ) as Record; diff --git a/gitnexus/src/core/ingestion/framework-detection.ts b/gitnexus/src/core/ingestion/framework-detection.ts index 739f22967..fca116c22 100644 --- a/gitnexus/src/core/ingestion/framework-detection.ts +++ b/gitnexus/src/core/ingestion/framework-detection.ts @@ -11,6 +11,8 @@ */ import { SupportedLanguages } from 'gitnexus-shared'; +import type { AstFrameworkPatternConfig } from './language-provider.js'; +import { providers } from './languages/index.js'; // ============================================================================ // TYPES @@ -518,395 +520,14 @@ export function detectFrameworkFromPath(filePath: string): FrameworkHint | null // AST-BASED FRAMEWORK DETECTION // ============================================================================ -/** - * Patterns that indicate framework entry points within code definitions. - * These are matched against AST node text (class/method/function declaration text). - */ -export const FRAMEWORK_AST_PATTERNS = { - // JavaScript/TypeScript decorators - nestjs: ['@Controller', '@Get', '@Post', '@Put', '@Delete', '@Patch'], - 'expo-router': [ - 'router.push', - 'router.replace', - 'router.navigate', - 'useRouter', - 'useLocalSearchParams', - 'useSegments', - 'expo-router', - ], - express: ['app.get', 'app.post', 'app.put', 'app.delete', 'router.get', 'router.post'], - - // Python decorators - fastapi: ['@app.get', '@app.post', '@app.put', '@app.delete', '@router.get'], - flask: ['@app.route', '@blueprint.route'], - - // Java annotations - spring: ['@RestController', '@Controller', '@GetMapping', '@PostMapping', '@RequestMapping'], - jaxrs: ['@Path', '@GET', '@POST', '@PUT', '@DELETE'], - - // C# attributes - aspnet: [ - '[ApiController]', - '[HttpGet]', - '[HttpPost]', - '[HttpPut]', - '[HttpDelete]', - '[Route]', - '[Authorize]', - '[AllowAnonymous]', - ], - signalr: ['[HubMethodName]', ': Hub', ': Hub<'], - blazor: ['@page', '[Parameter]', '@inject'], - efcore: ['DbContext', 'DbSet<', 'OnModelCreating'], - - // Go patterns (function signatures include framework types) - 'go-http': [ - 'http.Handler', - 'http.HandlerFunc', - 'ServeHTTP', - 'http.ResponseWriter', - 'http.Request', - ], - gin: ['gin.Context', 'gin.Default', 'gin.New'], - echo: ['echo.Context', 'echo.New'], - fiber: ['fiber.Ctx', 'fiber.New', 'fiber.App'], - 'go-grpc': ['grpc.Server', 'RegisterServer', 'pb.Unimplemented'], - - // ORM patterns - prisma: ['prisma.', 'PrismaClient', '@prisma/client'], - supabase: ['supabase.from', 'createClient', '@supabase/supabase-js'], - - // PHP/Laravel - laravel: [ - 'Route::get', - 'Route::post', - 'Route::put', - 'Route::delete', - 'Route::resource', - 'Route::apiResource', - '#[Route(', - ], - - // Rust macros (proc-macro attributes in definition text) - actix: ['#[get', '#[post', '#[put', '#[delete', '#[actix_web', 'HttpRequest', 'HttpResponse'], - axum: ['Router::new', 'axum::extract', 'axum::routing'], - rocket: ['#[get', '#[post', '#[launch', 'rocket::'], - tokio: ['#[tokio::main]', '#[tokio::test]'], - - // C++ patterns (Qt, Boost) - qt: [ - 'Q_OBJECT', - 'Q_INVOKABLE', - 'Q_PROPERTY', - 'Q_SIGNALS', - 'Q_SLOTS', - 'Q_SIGNAL', - 'Q_SLOT', - 'QWidget', - 'QApplication', - ], - - // Swift/iOS - uikit: [ - 'viewDidLoad', - 'viewWillAppear', - 'viewDidAppear', - 'UIViewController', - '@IBOutlet', - '@IBAction', - '@objc', - ], - swiftui: [ - '@main', - 'WindowGroup', - 'ContentView', - '@StateObject', - '@ObservedObject', - '@EnvironmentObject', - '@Published', - ], - vapor: ['app.get', 'app.post', 'req.content.decode', 'Vapor'], - - // Ruby patterns (class-level macros in definition text) - rails: [ - 'ApplicationController', - 'ApplicationRecord', - 'ActiveRecord::Base', - 'before_action', - 'after_action', - 'has_many', - 'belongs_to', - 'has_one', - 'validates', - ], - sinatra: ['Sinatra::Base', 'Sinatra::Application'], - - // Dart/Flutter - flutter: [ - 'StatelessWidget', - 'StatefulWidget', - 'BuildContext', - 'Widget build', - 'ChangeNotifier', - 'GetxController', - 'Cubit<', - 'Bloc<', - 'ConsumerWidget', - ], - riverpod: ['@riverpod', 'ref.watch', 'ref.read', 'AsyncNotifier', 'Notifier'], -}; - -interface AstFrameworkPatternConfig { - framework: string; - entryPointMultiplier: number; - reason: string; - patterns: string[]; -} - -export const AST_FRAMEWORK_PATTERNS_BY_LANGUAGE = { - [SupportedLanguages.JavaScript]: [ - { - framework: 'nestjs', - entryPointMultiplier: 3.2, - reason: 'nestjs-decorator', - patterns: FRAMEWORK_AST_PATTERNS.nestjs, - }, - { - framework: 'expo-router', - entryPointMultiplier: 2.5, - reason: 'expo-router-navigation', - patterns: FRAMEWORK_AST_PATTERNS['expo-router'], - }, - ], - [SupportedLanguages.TypeScript]: [ - { - framework: 'nestjs', - entryPointMultiplier: 3.2, - reason: 'nestjs-decorator', - patterns: FRAMEWORK_AST_PATTERNS.nestjs, - }, - { - framework: 'expo-router', - entryPointMultiplier: 2.5, - reason: 'expo-router-navigation', - patterns: FRAMEWORK_AST_PATTERNS['expo-router'], - }, - ], - [SupportedLanguages.Python]: [ - { - framework: 'fastapi', - entryPointMultiplier: 3.0, - reason: 'fastapi-decorator', - patterns: FRAMEWORK_AST_PATTERNS.fastapi, - }, - { - framework: 'flask', - entryPointMultiplier: 2.8, - reason: 'flask-decorator', - patterns: FRAMEWORK_AST_PATTERNS.flask, - }, - ], - [SupportedLanguages.Java]: [ - { - framework: 'spring', - entryPointMultiplier: 3.2, - reason: 'spring-annotation', - patterns: FRAMEWORK_AST_PATTERNS.spring, - }, - { - framework: 'jaxrs', - entryPointMultiplier: 3.0, - reason: 'jaxrs-annotation', - patterns: FRAMEWORK_AST_PATTERNS.jaxrs, - }, - ], - [SupportedLanguages.Kotlin]: [ - { - framework: 'spring-kotlin', - entryPointMultiplier: 3.2, - reason: 'spring-kotlin-annotation', - patterns: FRAMEWORK_AST_PATTERNS.spring, - }, - { - framework: 'jaxrs', - entryPointMultiplier: 3.0, - reason: 'jaxrs-annotation', - patterns: FRAMEWORK_AST_PATTERNS.jaxrs, - }, - { - framework: 'ktor', - entryPointMultiplier: 2.8, - reason: 'ktor-routing', - patterns: ['routing', 'embeddedServer', 'Application.module'], - }, - { - framework: 'android-kotlin', - entryPointMultiplier: 2.5, - reason: 'android-annotation', - patterns: ['@AndroidEntryPoint', 'AppCompatActivity', 'Fragment('], - }, - ], - [SupportedLanguages.CSharp]: [ - { - framework: 'aspnet', - entryPointMultiplier: 3.2, - reason: 'aspnet-attribute', - patterns: FRAMEWORK_AST_PATTERNS.aspnet, - }, - { - framework: 'signalr', - entryPointMultiplier: 2.8, - reason: 'signalr-attribute', - patterns: FRAMEWORK_AST_PATTERNS.signalr, - }, - { - framework: 'blazor', - entryPointMultiplier: 2.5, - reason: 'blazor-attribute', - patterns: FRAMEWORK_AST_PATTERNS.blazor, - }, - { - framework: 'efcore', - entryPointMultiplier: 2.0, - reason: 'efcore-pattern', - patterns: FRAMEWORK_AST_PATTERNS.efcore, - }, - ], - [SupportedLanguages.PHP]: [ - { - framework: 'laravel', - entryPointMultiplier: 3.0, - reason: 'php-route-attribute', - patterns: FRAMEWORK_AST_PATTERNS.laravel, - }, - ], - [SupportedLanguages.Go]: [ - { - framework: 'go-http', - entryPointMultiplier: 2.5, - reason: 'go-http-handler', - patterns: FRAMEWORK_AST_PATTERNS['go-http'], - }, - { - framework: 'gin', - entryPointMultiplier: 3.0, - reason: 'gin-handler', - patterns: FRAMEWORK_AST_PATTERNS.gin, - }, - { - framework: 'echo', - entryPointMultiplier: 3.0, - reason: 'echo-handler', - patterns: FRAMEWORK_AST_PATTERNS.echo, - }, - { - framework: 'fiber', - entryPointMultiplier: 3.0, - reason: 'fiber-handler', - patterns: FRAMEWORK_AST_PATTERNS.fiber, - }, - { - framework: 'go-grpc', - entryPointMultiplier: 2.8, - reason: 'grpc-service', - patterns: FRAMEWORK_AST_PATTERNS['go-grpc'], - }, - ], - [SupportedLanguages.Rust]: [ - { - framework: 'actix-web', - entryPointMultiplier: 3.0, - reason: 'actix-attribute', - patterns: FRAMEWORK_AST_PATTERNS.actix, - }, - { - framework: 'axum', - entryPointMultiplier: 3.0, - reason: 'axum-routing', - patterns: FRAMEWORK_AST_PATTERNS.axum, - }, - { - framework: 'rocket', - entryPointMultiplier: 3.0, - reason: 'rocket-attribute', - patterns: FRAMEWORK_AST_PATTERNS.rocket, - }, - { - framework: 'tokio', - entryPointMultiplier: 2.5, - reason: 'tokio-runtime', - patterns: FRAMEWORK_AST_PATTERNS.tokio, - }, - ], - [SupportedLanguages.C]: [], // C has no framework-specific AST patterns (POSIX/socket patterns are in entry-point-scoring) - [SupportedLanguages.CPlusPlus]: [ - { - framework: 'qt', - entryPointMultiplier: 2.8, - reason: 'qt-macro', - patterns: FRAMEWORK_AST_PATTERNS.qt, - }, - ], - [SupportedLanguages.Swift]: [ - { - framework: 'uikit', - entryPointMultiplier: 2.5, - reason: 'uikit-lifecycle', - patterns: FRAMEWORK_AST_PATTERNS.uikit, - }, - { - framework: 'swiftui', - entryPointMultiplier: 2.8, - reason: 'swiftui-pattern', - patterns: FRAMEWORK_AST_PATTERNS.swiftui, - }, - { - framework: 'vapor', - entryPointMultiplier: 3.0, - reason: 'vapor-routing', - patterns: FRAMEWORK_AST_PATTERNS.vapor, - }, - ], - [SupportedLanguages.Ruby]: [ - { - framework: 'rails', - entryPointMultiplier: 3.0, - reason: 'rails-pattern', - patterns: FRAMEWORK_AST_PATTERNS.rails, - }, - { - framework: 'sinatra', - entryPointMultiplier: 2.8, - reason: 'sinatra-pattern', - patterns: FRAMEWORK_AST_PATTERNS.sinatra, - }, - ], - [SupportedLanguages.Dart]: [ - { - framework: 'flutter', - entryPointMultiplier: 2.5, - reason: 'flutter-widget', - patterns: FRAMEWORK_AST_PATTERNS.flutter, - }, - { - framework: 'riverpod', - entryPointMultiplier: 2.8, - reason: 'riverpod-pattern', - patterns: FRAMEWORK_AST_PATTERNS.riverpod, - }, - ], - [SupportedLanguages.Vue]: [], // Vue uses TypeScript AST framework detection - [SupportedLanguages.Cobol]: [], // Standalone regex processor — no AST framework patterns -} satisfies Record; - -/** Pre-lowercased patterns for O(1) pattern matching at runtime */ -const AST_PATTERNS_LOWERED: Record< - string, - Array<{ framework: string; entryPointMultiplier: number; reason: string; patterns: string[] }> -> = Object.fromEntries( - Object.entries(AST_FRAMEWORK_PATTERNS_BY_LANGUAGE).map(([lang, cfgs]) => [ +/** Pre-lowercased patterns for O(1) pattern matching at runtime — built from providers. */ +const AST_PATTERNS_LOWERED: Record = Object.fromEntries( + Object.entries(providers).map(([lang, provider]) => [ lang, - cfgs.map((cfg) => ({ ...cfg, patterns: cfg.patterns.map((p) => p.toLowerCase()) })), + (provider.astFrameworkPatterns ?? []).map((cfg) => ({ + ...cfg, + patterns: cfg.patterns.map((p) => p.toLowerCase()), + })), ]), ); diff --git a/gitnexus/src/core/ingestion/language-provider.ts b/gitnexus/src/core/ingestion/language-provider.ts index 92566d30e..12ce0839b 100644 --- a/gitnexus/src/core/ingestion/language-provider.ts +++ b/gitnexus/src/core/ingestion/language-provider.ts @@ -78,6 +78,14 @@ export type ImportSemantics = | 'namespace' | 'explicit-reexport'; +/** Configuration for AST-based framework detection patterns. */ +export interface AstFrameworkPatternConfig { + framework: string; + entryPointMultiplier: number; + reason: string; + patterns: string[]; +} + /** * Everything a language needs to provide. * Required fields must be explicitly set; optional fields have defaults @@ -89,6 +97,16 @@ interface LanguageProviderConfig { /** File extensions that map to this language (e.g., ['.ts', '.tsx']) */ readonly extensions: readonly string[]; + /** Entry-point function name patterns specific to this language. + * Merged with universal patterns at runtime for process detection scoring. + * Default: [] (only universal patterns apply). */ + readonly entryPointPatterns?: readonly RegExp[]; + + /** AST-based framework detection patterns for this language. + * Used by detectFrameworkFromAST to identify framework entry points. + * Default: [] (no AST framework detection for this language). */ + readonly astFrameworkPatterns?: readonly AstFrameworkPatternConfig[]; + // ── Parser ──────────────────────────────────────────────────────── /** Parse strategy: 'tree-sitter' (default) uses AST parsing via tree-sitter. * 'standalone' means the language has its own regex-based processor and diff --git a/gitnexus/src/core/ingestion/languages/c-cpp.ts b/gitnexus/src/core/ingestion/languages/c-cpp.ts index 37e6da732..907b1da6d 100644 --- a/gitnexus/src/core/ingestion/languages/c-cpp.ts +++ b/gitnexus/src/core/ingestion/languages/c-cpp.ts @@ -12,6 +12,7 @@ import { SupportedLanguages } from 'gitnexus-shared'; import { createClassExtractor } from '../class-extractors/generic.js'; import { cClassConfig, cppClassConfig } from '../class-extractors/configs/c-cpp.js'; import { defineLanguage } from '../language-provider.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import { typeConfig as cCppConfig } from '../type-extractors/c-cpp.js'; import { cCppExportChecker } from '../export-detection.js'; import { createImportResolver } from '../import-resolvers/resolver-factory.js'; @@ -317,6 +318,38 @@ const cppLabelOverride: NonNullable = ( export const cProvider = defineLanguage({ id: SupportedLanguages.C, extensions: ['.c'], + entryPointPatterns: [ + /^main$/, + /^init_/, + /_init$/, + /^start_/, + /_start$/, + /^run_/, + /_run$/, + /^stop_/, + /_stop$/, + /^open_/, + /_open$/, + /^close_/, + /_close$/, + /^create_/, + /_create$/, + /^destroy_/, + /_destroy$/, + /^handle_/, + /_handler$/, + /_callback$/, + /^cmd_/, + /^server_/, + /^client_/, + /^session_/, + /^window_/, + /^key_/, + /^input_/, + /^output_/, + /^notify_/, + /^control_/, + ], treeSitterQueries: C_QUERIES, typeConfig: cCppConfig, exportChecker: cCppExportChecker, @@ -338,6 +371,44 @@ export const cProvider = defineLanguage({ export const cppProvider = defineLanguage({ id: SupportedLanguages.CPlusPlus, extensions: ['.cpp', '.cc', '.cxx', '.h', '.hpp', '.hxx', '.hh'], + entryPointPatterns: [ + /^main$/, + /^init_/, + /_init$/, + /^Create[A-Z]/, + /^create_/, + /^Run$/, + /^run$/, + /^Start$/, + /^start$/, + /^handle_/, + /_handler$/, + /_callback$/, + /^OnEvent/, + /^on_/, + /::Run$/, + /::Start$/, + /::Init$/, + /::Execute$/, + ], + astFrameworkPatterns: [ + { + framework: 'qt', + entryPointMultiplier: 2.8, + reason: 'qt-macro', + patterns: [ + 'Q_OBJECT', + 'Q_INVOKABLE', + 'Q_PROPERTY', + 'Q_SIGNALS', + 'Q_SLOTS', + 'Q_SIGNAL', + 'Q_SLOT', + 'QWidget', + 'QApplication', + ], + }, + ], treeSitterQueries: CPP_QUERIES, typeConfig: cCppConfig, exportChecker: cCppExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/cobol.ts b/gitnexus/src/core/ingestion/languages/cobol.ts index 78ee51ecc..6e54c2309 100644 --- a/gitnexus/src/core/ingestion/languages/cobol.ts +++ b/gitnexus/src/core/ingestion/languages/cobol.ts @@ -16,6 +16,8 @@ export const cobolProvider = defineLanguage({ id: SupportedLanguages.Cobol, parseStrategy: 'standalone', extensions: [], // COBOL files detected by cobol-processor's isCobolFile/isJclFile + entryPointPatterns: [], + astFrameworkPatterns: [], treeSitterQueries: '', typeConfig: { declarationNodeTypes: new Set(), diff --git a/gitnexus/src/core/ingestion/languages/csharp.ts b/gitnexus/src/core/ingestion/languages/csharp.ts index 837a7517b..32bf08779 100644 --- a/gitnexus/src/core/ingestion/languages/csharp.ts +++ b/gitnexus/src/core/ingestion/languages/csharp.ts @@ -16,6 +16,7 @@ import { createImportResolver } from '../import-resolvers/resolver-factory.js'; import { csharpImportConfig } from '../import-resolvers/configs/csharp.js'; import { extractCSharpNamedBindings } from '../named-bindings/csharp.js'; import { CSHARP_QUERIES } from '../tree-sitter-queries.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import { createCallExtractor } from '../call-extractors/generic.js'; import { csharpCallConfig } from '../call-extractors/configs/csharp.js'; import { createFieldExtractor } from '../field-extractors/generic.js'; @@ -135,6 +136,55 @@ const BUILT_INS: ReadonlySet = new Set([ export const csharpProvider = defineLanguage({ id: SupportedLanguages.CSharp, extensions: ['.cs'], + entryPointPatterns: [ + /^(Get|Post|Put|Delete|Patch)/, + /Action$/, + /^On[A-Z]/, + /Async$/, + /^Configure$/, + /^ConfigureServices$/, + /^Handle$/, + /^Execute$/, + /^Invoke$/, + /^Map[A-Z]/, + /Service$/, + /^Seed/, + ], + astFrameworkPatterns: [ + { + framework: 'aspnet', + entryPointMultiplier: 3.2, + reason: 'aspnet-attribute', + patterns: [ + '[ApiController]', + '[HttpGet]', + '[HttpPost]', + '[HttpPut]', + '[HttpDelete]', + '[Route]', + '[Authorize]', + '[AllowAnonymous]', + ], + }, + { + framework: 'signalr', + entryPointMultiplier: 2.8, + reason: 'signalr-attribute', + patterns: ['[HubMethodName]', ': Hub', ': Hub<'], + }, + { + framework: 'blazor', + entryPointMultiplier: 2.5, + reason: 'blazor-attribute', + patterns: ['@page', '[Parameter]', '@inject'], + }, + { + framework: 'efcore', + entryPointMultiplier: 2.0, + reason: 'efcore-pattern', + patterns: ['DbContext', 'DbSet<', 'OnModelCreating'], + }, + ] satisfies AstFrameworkPatternConfig[], treeSitterQueries: CSHARP_QUERIES, typeConfig: csharpConfig, exportChecker: csharpExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/dart.ts b/gitnexus/src/core/ingestion/languages/dart.ts index 130d15deb..29a63fd3a 100644 --- a/gitnexus/src/core/ingestion/languages/dart.ts +++ b/gitnexus/src/core/ingestion/languages/dart.ts @@ -17,6 +17,7 @@ import { SupportedLanguages } from 'gitnexus-shared'; import { createClassExtractor } from '../class-extractors/generic.js'; import { dartClassConfig } from '../class-extractors/configs/dart.js'; import { defineLanguage } from '../language-provider.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import { typeConfig as dartConfig } from '../type-extractors/dart.js'; import { dartExportChecker } from '../export-detection.js'; import { createImportResolver } from '../import-resolvers/resolver-factory.js'; @@ -93,6 +94,42 @@ const BUILT_INS: ReadonlySet = new Set([ export const dartProvider = defineLanguage({ id: SupportedLanguages.Dart, extensions: ['.dart'], + entryPointPatterns: [ + /^main$/, + /^build$/, + /^createState$/, + /^initState$/, + /^dispose$/, + /^didChangeDependencies$/, + /^didUpdateWidget$/, + /^runApp$/, + /^onEvent$/, + /^mapEventToState$/, + ], + astFrameworkPatterns: [ + { + framework: 'flutter', + entryPointMultiplier: 2.5, + reason: 'flutter-widget', + patterns: [ + 'StatelessWidget', + 'StatefulWidget', + 'BuildContext', + 'Widget build', + 'ChangeNotifier', + 'GetxController', + 'Cubit<', + 'Bloc<', + 'ConsumerWidget', + ], + }, + { + framework: 'riverpod', + entryPointMultiplier: 2.8, + reason: 'riverpod-pattern', + patterns: ['@riverpod', 'ref.watch', 'ref.read', 'AsyncNotifier', 'Notifier'], + }, + ], treeSitterQueries: DART_QUERIES, typeConfig: dartConfig, exportChecker: dartExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/go.ts b/gitnexus/src/core/ingestion/languages/go.ts index 245dc197b..64b9e359a 100644 --- a/gitnexus/src/core/ingestion/languages/go.ts +++ b/gitnexus/src/core/ingestion/languages/go.ts @@ -18,6 +18,7 @@ import { goExportChecker } from '../export-detection.js'; import { createImportResolver } from '../import-resolvers/resolver-factory.js'; import { goImportConfig } from '../import-resolvers/configs/go.js'; import { GO_QUERIES } from '../tree-sitter-queries.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import { createFieldExtractor } from '../field-extractors/generic.js'; import { goConfig as goFieldConfig } from '../field-extractors/configs/go.js'; import { createMethodExtractor } from '../method-extractors/generic.js'; @@ -32,6 +33,45 @@ import { goHeritageConfig } from '../heritage-extractors/configs/go.js'; export const goProvider = defineLanguage({ id: SupportedLanguages.Go, extensions: ['.go'], + entryPointPatterns: [/Handler$/, /^Serve/, /^New[A-Z]/, /^Make[A-Z]/], + astFrameworkPatterns: [ + { + framework: 'go-http', + entryPointMultiplier: 2.5, + reason: 'go-http-handler', + patterns: [ + 'http.Handler', + 'http.HandlerFunc', + 'ServeHTTP', + 'http.ResponseWriter', + 'http.Request', + ], + }, + { + framework: 'gin', + entryPointMultiplier: 3.0, + reason: 'gin-handler', + patterns: ['gin.Context', 'gin.Default', 'gin.New'], + }, + { + framework: 'echo', + entryPointMultiplier: 3.0, + reason: 'echo-handler', + patterns: ['echo.Context', 'echo.New'], + }, + { + framework: 'fiber', + entryPointMultiplier: 3.0, + reason: 'fiber-handler', + patterns: ['fiber.Ctx', 'fiber.New', 'fiber.App'], + }, + { + framework: 'go-grpc', + entryPointMultiplier: 2.8, + reason: 'grpc-service', + patterns: ['grpc.Server', 'RegisterServer', 'pb.Unimplemented'], + }, + ] satisfies AstFrameworkPatternConfig[], treeSitterQueries: GO_QUERIES, typeConfig: goConfig, exportChecker: goExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/java.ts b/gitnexus/src/core/ingestion/languages/java.ts index 4781219d3..96139ccda 100644 --- a/gitnexus/src/core/ingestion/languages/java.ts +++ b/gitnexus/src/core/ingestion/languages/java.ts @@ -11,6 +11,7 @@ import { SupportedLanguages } from 'gitnexus-shared'; import { createClassExtractor } from '../class-extractors/generic.js'; import { javaClassConfig } from '../class-extractors/configs/jvm.js'; import { defineLanguage } from '../language-provider.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import { javaTypeConfig } from '../type-extractors/jvm.js'; import { javaExportChecker } from '../export-detection.js'; import { createImportResolver } from '../import-resolvers/resolver-factory.js'; @@ -30,6 +31,27 @@ import { createHeritageExtractor } from '../heritage-extractors/generic.js'; export const javaProvider = defineLanguage({ id: SupportedLanguages.Java, extensions: ['.java'], + entryPointPatterns: [/^do[A-Z]/, /^create[A-Z]/, /^build[A-Z]/, /Service$/], + astFrameworkPatterns: [ + { + framework: 'spring', + entryPointMultiplier: 3.2, + reason: 'spring-annotation', + patterns: [ + '@RestController', + '@Controller', + '@GetMapping', + '@PostMapping', + '@RequestMapping', + ], + }, + { + framework: 'jaxrs', + entryPointMultiplier: 3.0, + reason: 'jaxrs-annotation', + patterns: ['@Path', '@GET', '@POST', '@PUT', '@DELETE'], + }, + ] satisfies AstFrameworkPatternConfig[], treeSitterQueries: JAVA_QUERIES, typeConfig: javaTypeConfig, exportChecker: javaExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/kotlin.ts b/gitnexus/src/core/ingestion/languages/kotlin.ts index dd65e5d42..78d8726e6 100644 --- a/gitnexus/src/core/ingestion/languages/kotlin.ts +++ b/gitnexus/src/core/ingestion/languages/kotlin.ts @@ -18,6 +18,7 @@ import { kotlinImportConfig } from '../import-resolvers/configs/jvm.js'; import { extractKotlinNamedBindings } from '../named-bindings/kotlin.js'; import { appendKotlinWildcard } from '../import-resolvers/jvm.js'; import { KOTLIN_QUERIES } from '../tree-sitter-queries.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import type { SyntaxNode } from '../utils/ast-helpers.js'; import { createCallExtractor } from '../call-extractors/generic.js'; import { kotlinCallConfig } from '../call-extractors/configs/jvm.js'; @@ -105,6 +106,47 @@ const BUILT_INS: ReadonlySet = new Set([ export const kotlinProvider = defineLanguage({ id: SupportedLanguages.Kotlin, extensions: ['.kt', '.kts'], + entryPointPatterns: [ + /^on(Create|Start|Resume|Pause|Stop|Destroy)$/, + /^do[A-Z]/, + /^create[A-Z]/, + /^build[A-Z]/, + /ViewModel$/, + /^module$/, + /Service$/, + ], + astFrameworkPatterns: [ + { + framework: 'spring-kotlin', + entryPointMultiplier: 3.2, + reason: 'spring-kotlin-annotation', + patterns: [ + '@RestController', + '@Controller', + '@GetMapping', + '@PostMapping', + '@RequestMapping', + ], + }, + { + framework: 'jaxrs', + entryPointMultiplier: 3.0, + reason: 'jaxrs-annotation', + patterns: ['@Path', '@GET', '@POST', '@PUT', '@DELETE'], + }, + { + framework: 'ktor', + entryPointMultiplier: 2.8, + reason: 'ktor-routing', + patterns: ['routing', 'embeddedServer', 'Application.module'], + }, + { + framework: 'android-kotlin', + entryPointMultiplier: 2.5, + reason: 'android-annotation', + patterns: ['@AndroidEntryPoint', 'AppCompatActivity', 'Fragment('], + }, + ] satisfies AstFrameworkPatternConfig[], treeSitterQueries: KOTLIN_QUERIES, typeConfig: kotlinTypeConfig, exportChecker: kotlinExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/php.ts b/gitnexus/src/core/ingestion/languages/php.ts index 736a63468..3689f297f 100644 --- a/gitnexus/src/core/ingestion/languages/php.ts +++ b/gitnexus/src/core/ingestion/languages/php.ts @@ -10,6 +10,7 @@ import { SupportedLanguages } from 'gitnexus-shared'; import { createClassExtractor } from '../class-extractors/generic.js'; import { phpClassConfig } from '../class-extractors/configs/php.js'; import { defineLanguage } from '../language-provider.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import { typeConfig as phpConfig } from '../type-extractors/php.js'; import { phpExportChecker } from '../export-detection.js'; import { createImportResolver } from '../import-resolvers/resolver-factory.js'; @@ -239,6 +240,41 @@ function isPhpRouteFile(filePath: string): boolean { export const phpProvider = defineLanguage({ id: SupportedLanguages.PHP, extensions: ['.php', '.phtml', '.php3', '.php4', '.php5', '.php8'], + entryPointPatterns: [ + /Controller$/, + /^handle$/, + /^execute$/, + /^boot$/, + /^register$/, + /^__invoke$/, + /^(index|show|store|update|destroy|create|edit)$/, + /^(get|post|put|delete|patch)[A-Z]/, + /^run$/, + /^fire$/, + /^dispatch$/, + /Service$/, + /Repository$/, + /^find$/, + /^findAll$/, + /^save$/, + /^delete$/, + ], + astFrameworkPatterns: [ + { + framework: 'laravel', + entryPointMultiplier: 3.0, + reason: 'php-route-attribute', + patterns: [ + 'Route::get', + 'Route::post', + 'Route::put', + 'Route::delete', + 'Route::resource', + 'Route::apiResource', + '#[Route(', + ], + }, + ], treeSitterQueries: PHP_QUERIES, typeConfig: phpConfig, exportChecker: phpExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/python.ts b/gitnexus/src/core/ingestion/languages/python.ts index 2b37aab1b..a0e834030 100644 --- a/gitnexus/src/core/ingestion/languages/python.ts +++ b/gitnexus/src/core/ingestion/languages/python.ts @@ -15,6 +15,7 @@ import { SupportedLanguages } from 'gitnexus-shared'; import { createClassExtractor } from '../class-extractors/generic.js'; import { pythonClassConfig } from '../class-extractors/configs/python.js'; import { defineLanguage } from '../language-provider.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import { typeConfig as pythonConfig } from '../type-extractors/python.js'; import { pythonExportChecker } from '../export-detection.js'; import { createImportResolver } from '../import-resolvers/resolver-factory.js'; @@ -106,6 +107,21 @@ function normalizePythonStringLiteral(text: string): string | undefined { export const pythonProvider = defineLanguage({ id: SupportedLanguages.Python, extensions: ['.py'], + entryPointPatterns: [/^app$/, /^(get|post|put|delete|patch)_/i, /^api_/, /^view_/], + astFrameworkPatterns: [ + { + framework: 'fastapi', + entryPointMultiplier: 3.0, + reason: 'fastapi-decorator', + patterns: ['@app.get', '@app.post', '@app.put', '@app.delete', '@router.get'], + }, + { + framework: 'flask', + entryPointMultiplier: 2.8, + reason: 'flask-decorator', + patterns: ['@app.route', '@blueprint.route'], + }, + ] satisfies AstFrameworkPatternConfig[], treeSitterQueries: PYTHON_QUERIES, typeConfig: pythonConfig, exportChecker: pythonExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/ruby.ts b/gitnexus/src/core/ingestion/languages/ruby.ts index e538f3fd2..6ea6f5b8b 100644 --- a/gitnexus/src/core/ingestion/languages/ruby.ts +++ b/gitnexus/src/core/ingestion/languages/ruby.ts @@ -12,6 +12,7 @@ import type { NodeLabel } from 'gitnexus-shared'; import { createClassExtractor } from '../class-extractors/generic.js'; import { rubyClassConfig } from '../class-extractors/configs/ruby.js'; import { defineLanguage } from '../language-provider.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import type { SyntaxNode } from '../utils/ast-helpers.js'; import { typeConfig as rubyConfig } from '../type-extractors/ruby.js'; import { routeRubyCall } from '../call-routing.js'; @@ -151,6 +152,31 @@ const rubyResolveEnclosingOwner = (node: SyntaxNode): SyntaxNode | null => { export const rubyProvider = defineLanguage({ id: SupportedLanguages.Ruby, extensions: ['.rb', '.rake', '.gemspec'], + entryPointPatterns: [/^call$/, /^perform$/, /^execute$/], + astFrameworkPatterns: [ + { + framework: 'rails', + entryPointMultiplier: 3.0, + reason: 'rails-pattern', + patterns: [ + 'ApplicationController', + 'ApplicationRecord', + 'ActiveRecord::Base', + 'before_action', + 'after_action', + 'has_many', + 'belongs_to', + 'has_one', + 'validates', + ], + }, + { + framework: 'sinatra', + entryPointMultiplier: 2.8, + reason: 'sinatra-pattern', + patterns: ['Sinatra::Base', 'Sinatra::Application'], + }, + ], treeSitterQueries: RUBY_QUERIES, typeConfig: rubyConfig, exportChecker: rubyExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/rust.ts b/gitnexus/src/core/ingestion/languages/rust.ts index ac0cd5788..e7638e4ea 100644 --- a/gitnexus/src/core/ingestion/languages/rust.ts +++ b/gitnexus/src/core/ingestion/languages/rust.ts @@ -22,6 +22,7 @@ import { createImportResolver } from '../import-resolvers/resolver-factory.js'; import { rustImportConfig } from '../import-resolvers/configs/rust.js'; import { extractRustNamedBindings } from '../named-bindings/rust.js'; import { RUST_QUERIES } from '../tree-sitter-queries.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import { createFieldExtractor } from '../field-extractors/generic.js'; import { rustConfig as rustFieldConfig } from '../field-extractors/configs/rust.js'; import { createMethodExtractor } from '../method-extractors/generic.js'; @@ -121,6 +122,41 @@ const BUILT_INS: ReadonlySet = new Set([ export const rustProvider = defineLanguage({ id: SupportedLanguages.Rust, extensions: ['.rs'], + entryPointPatterns: [/^(get|post|put|delete)_handler$/i, /^handle_/, /^new$/, /^run$/, /^spawn/], + astFrameworkPatterns: [ + { + framework: 'actix-web', + entryPointMultiplier: 3.0, + reason: 'actix-attribute', + patterns: [ + '#[get', + '#[post', + '#[put', + '#[delete', + '#[actix_web', + 'HttpRequest', + 'HttpResponse', + ], + }, + { + framework: 'axum', + entryPointMultiplier: 3.0, + reason: 'axum-routing', + patterns: ['Router::new', 'axum::extract', 'axum::routing'], + }, + { + framework: 'rocket', + entryPointMultiplier: 3.0, + reason: 'rocket-attribute', + patterns: ['#[get', '#[post', '#[launch', 'rocket::'], + }, + { + framework: 'tokio', + entryPointMultiplier: 2.5, + reason: 'tokio-runtime', + patterns: ['#[tokio::main]', '#[tokio::test]'], + }, + ] satisfies AstFrameworkPatternConfig[], treeSitterQueries: RUST_QUERIES, typeConfig: rustConfig, exportChecker: rustExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/swift.ts b/gitnexus/src/core/ingestion/languages/swift.ts index 7b74386a8..654e7b638 100644 --- a/gitnexus/src/core/ingestion/languages/swift.ts +++ b/gitnexus/src/core/ingestion/languages/swift.ts @@ -15,6 +15,7 @@ import type { NodeLabel, SymbolDefinition } from 'gitnexus-shared'; import { createClassExtractor } from '../class-extractors/generic.js'; import { swiftClassConfig } from '../class-extractors/configs/swift.js'; import { defineLanguage } from '../language-provider.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import { typeConfig as swiftConfig } from '../type-extractors/swift.js'; import { swiftExportChecker } from '../export-detection.js'; import { createImportResolver } from '../import-resolvers/resolver-factory.js'; @@ -259,6 +260,60 @@ const BUILT_INS: ReadonlySet = new Set([ export const swiftProvider = defineLanguage({ id: SupportedLanguages.Swift, extensions: ['.swift'], + entryPointPatterns: [ + /^viewDidLoad$/, + /^viewWillAppear$/, + /^viewDidAppear$/, + /^viewWillDisappear$/, + /^viewDidDisappear$/, + /^application\(/, + /^scene\(/, + /^body$/, + /Coordinator$/, + /^sceneDidBecomeActive$/, + /^sceneWillResignActive$/, + /^didFinishLaunchingWithOptions$/, + /ViewController$/, + /^configure[A-Z]/, + /^setup[A-Z]/, + /^makeBody$/, + ], + astFrameworkPatterns: [ + { + framework: 'uikit', + entryPointMultiplier: 2.5, + reason: 'uikit-lifecycle', + patterns: [ + 'viewDidLoad', + 'viewWillAppear', + 'viewDidAppear', + 'UIViewController', + '@IBOutlet', + '@IBAction', + '@objc', + ], + }, + { + framework: 'swiftui', + entryPointMultiplier: 2.8, + reason: 'swiftui-pattern', + patterns: [ + '@main', + 'WindowGroup', + 'ContentView', + '@StateObject', + '@ObservedObject', + '@EnvironmentObject', + '@Published', + ], + }, + { + framework: 'vapor', + entryPointMultiplier: 3.0, + reason: 'vapor-routing', + patterns: ['app.get', 'app.post', 'req.content.decode', 'Vapor'], + }, + ], treeSitterQueries: SWIFT_QUERIES, typeConfig: swiftConfig, exportChecker: swiftExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/typescript.ts b/gitnexus/src/core/ingestion/languages/typescript.ts index e2ec9d23f..57751ee41 100644 --- a/gitnexus/src/core/ingestion/languages/typescript.ts +++ b/gitnexus/src/core/ingestion/languages/typescript.ts @@ -10,6 +10,7 @@ import { SupportedLanguages } from 'gitnexus-shared'; import type { NodeLabel } from 'gitnexus-shared'; import { defineLanguage } from '../language-provider.js'; +import type { AstFrameworkPatternConfig } from '../language-provider.js'; import { createClassExtractor } from '../class-extractors/generic.js'; import { typescriptClassConfig, @@ -217,6 +218,29 @@ export const BUILT_INS: ReadonlySet = new Set([ export const typescriptProvider = defineLanguage({ id: SupportedLanguages.TypeScript, extensions: ['.ts', '.tsx'], + entryPointPatterns: [/^use[A-Z]/], + astFrameworkPatterns: [ + { + framework: 'nestjs', + entryPointMultiplier: 3.2, + reason: 'nestjs-decorator', + patterns: ['@Controller', '@Get', '@Post', '@Put', '@Delete', '@Patch'], + }, + { + framework: 'expo-router', + entryPointMultiplier: 2.5, + reason: 'expo-router-navigation', + patterns: [ + 'router.push', + 'router.replace', + 'router.navigate', + 'useRouter', + 'useLocalSearchParams', + 'useSegments', + 'expo-router', + ], + }, + ] satisfies AstFrameworkPatternConfig[], treeSitterQueries: TYPESCRIPT_QUERIES, typeConfig: typescriptConfig, exportChecker: tsExportChecker, @@ -256,6 +280,29 @@ export const typescriptProvider = defineLanguage({ export const javascriptProvider = defineLanguage({ id: SupportedLanguages.JavaScript, extensions: ['.js', '.jsx'], + entryPointPatterns: [/^use[A-Z]/], + astFrameworkPatterns: [ + { + framework: 'nestjs', + entryPointMultiplier: 3.2, + reason: 'nestjs-decorator', + patterns: ['@Controller', '@Get', '@Post', '@Put', '@Delete', '@Patch'], + }, + { + framework: 'expo-router', + entryPointMultiplier: 2.5, + reason: 'expo-router-navigation', + patterns: [ + 'router.push', + 'router.replace', + 'router.navigate', + 'useRouter', + 'useLocalSearchParams', + 'useSegments', + 'expo-router', + ], + }, + ] satisfies AstFrameworkPatternConfig[], treeSitterQueries: JAVASCRIPT_QUERIES, typeConfig: typescriptConfig, exportChecker: tsExportChecker, diff --git a/gitnexus/src/core/ingestion/languages/vue.ts b/gitnexus/src/core/ingestion/languages/vue.ts index 51ab37882..f08fe9050 100644 --- a/gitnexus/src/core/ingestion/languages/vue.ts +++ b/gitnexus/src/core/ingestion/languages/vue.ts @@ -68,6 +68,8 @@ const vueClassExtractor = createClassExtractor(vueClassConfig); export const vueProvider = defineLanguage({ id: SupportedLanguages.Vue, extensions: ['.vue'], + entryPointPatterns: [], + astFrameworkPatterns: [], treeSitterQueries: TYPESCRIPT_QUERIES, typeConfig: typescriptConfig, exportChecker: tsExportChecker, diff --git a/gitnexus/test/unit/framework-detection.test.ts b/gitnexus/test/unit/framework-detection.test.ts index ce9d76f10..4082fd750 100644 --- a/gitnexus/test/unit/framework-detection.test.ts +++ b/gitnexus/test/unit/framework-detection.test.ts @@ -2,8 +2,8 @@ import { describe, it, expect } from 'vitest'; import { detectFrameworkFromPath, detectFrameworkFromAST, - FRAMEWORK_AST_PATTERNS, } from '../../src/core/ingestion/framework-detection.js'; +import { providers } from '../../src/core/ingestion/languages/index.js'; describe('detectFrameworkFromPath', () => { describe('Next.js', () => { @@ -359,24 +359,26 @@ describe('detectFrameworkFromAST', () => { }); }); -describe('FRAMEWORK_AST_PATTERNS', () => { - it('has patterns for all expected frameworks', () => { - const expectedFrameworks = [ +describe('provider astFrameworkPatterns', () => { + it('covers all expected frameworks across providers', () => { + const expectedFrameworks = new Set([ 'nestjs', 'expo-router', - 'express', 'fastapi', 'flask', 'spring', 'jaxrs', 'aspnet', + 'signalr', + 'blazor', + 'efcore', 'go-http', 'gin', 'echo', 'fiber', 'go-grpc', 'laravel', - 'actix', + 'actix-web', 'axum', 'rocket', 'tokio', @@ -386,12 +388,21 @@ describe('FRAMEWORK_AST_PATTERNS', () => { 'vapor', 'rails', 'sinatra', - ]; + 'spring-kotlin', + 'ktor', + 'android-kotlin', + 'flutter', + 'riverpod', + ]); + const foundFrameworks = new Set(); + for (const provider of Object.values(providers)) { + for (const cfg of provider.astFrameworkPatterns ?? []) { + foundFrameworks.add(cfg.framework); + expect(cfg.patterns.length).toBeGreaterThan(0); + } + } for (const fw of expectedFrameworks) { - expect(FRAMEWORK_AST_PATTERNS).toHaveProperty(fw); - expect( - FRAMEWORK_AST_PATTERNS[fw as keyof typeof FRAMEWORK_AST_PATTERNS].length, - ).toBeGreaterThan(0); + expect(foundFrameworks.has(fw), `missing framework: ${fw}`).toBe(true); } }); });