From 2ae293aa28a8de1dac512b51e5ad3280fbd1a46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 01:03:44 +0300 Subject: [PATCH 01/12] feat(php): add PHP to SupportedLanguages enum and file extension detection Uncomment PHP = 'php' in both CLI and web SupportedLanguages enums. Add .php, .phtml, .php3/.4/.5/.8 extension detection to getLanguageFromFilename() in both packages. Co-Authored-By: Claude Sonnet 4.6 --- gitnexus-web/src/config/supported-languages.ts | 2 +- gitnexus-web/src/core/ingestion/utils.ts | 6 ++++++ gitnexus/src/config/supported-languages.ts | 2 +- gitnexus/src/core/ingestion/utils.ts | 6 ++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/gitnexus-web/src/config/supported-languages.ts b/gitnexus-web/src/config/supported-languages.ts index 15df37c54..a9bcd8248 100644 --- a/gitnexus-web/src/config/supported-languages.ts +++ b/gitnexus-web/src/config/supported-languages.ts @@ -8,7 +8,7 @@ export enum SupportedLanguages { CSharp = 'csharp', Go = 'go', Rust = 'rust', - // PHP = 'php', + PHP = 'php', // Ruby = 'ruby', // Swift = 'swift', } \ No newline at end of file diff --git a/gitnexus-web/src/core/ingestion/utils.ts b/gitnexus-web/src/core/ingestion/utils.ts index 959eb55dc..c53fa4248 100644 --- a/gitnexus-web/src/core/ingestion/utils.ts +++ b/gitnexus-web/src/core/ingestion/utils.ts @@ -25,6 +25,12 @@ export const getLanguageFromFilename = (filename: string): SupportedLanguages | if (filename.endsWith('.go')) return SupportedLanguages.Go; // Rust if (filename.endsWith('.rs')) return SupportedLanguages.Rust; + // PHP (all common extensions) + if (filename.endsWith('.php') || filename.endsWith('.phtml') || + filename.endsWith('.php3') || filename.endsWith('.php4') || + filename.endsWith('.php5') || filename.endsWith('.php8')) { + return SupportedLanguages.PHP; + } return null; }; diff --git a/gitnexus/src/config/supported-languages.ts b/gitnexus/src/config/supported-languages.ts index 15df37c54..a9bcd8248 100644 --- a/gitnexus/src/config/supported-languages.ts +++ b/gitnexus/src/config/supported-languages.ts @@ -8,7 +8,7 @@ export enum SupportedLanguages { CSharp = 'csharp', Go = 'go', Rust = 'rust', - // PHP = 'php', + PHP = 'php', // Ruby = 'ruby', // Swift = 'swift', } \ No newline at end of file diff --git a/gitnexus/src/core/ingestion/utils.ts b/gitnexus/src/core/ingestion/utils.ts index 620fac7ac..12b4c6b3e 100644 --- a/gitnexus/src/core/ingestion/utils.ts +++ b/gitnexus/src/core/ingestion/utils.ts @@ -31,6 +31,12 @@ export const getLanguageFromFilename = (filename: string): SupportedLanguages | if (filename.endsWith('.go')) return SupportedLanguages.Go; // Rust if (filename.endsWith('.rs')) return SupportedLanguages.Rust; + // PHP (all common extensions) + if (filename.endsWith('.php') || filename.endsWith('.phtml') || + filename.endsWith('.php3') || filename.endsWith('.php4') || + filename.endsWith('.php5') || filename.endsWith('.php8')) { + return SupportedLanguages.PHP; + } return null; }; From 6be9b5c9bbfc6943c738bc6bc35d5667cdbb708b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 01:03:57 +0300 Subject: [PATCH 02/12] feat(php): install tree-sitter-php dependency Add tree-sitter-php ^0.23.0 to gitnexus/package.json. Package exports { php, php_only } grammars; we use php_only for pure PHP files. Co-Authored-By: Claude Sonnet 4.6 --- gitnexus/package-lock.json | 33 +++++++++++++++++++++++++++++++-- gitnexus/package.json | 1 + package-lock.json | 2 +- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/gitnexus/package-lock.json b/gitnexus/package-lock.json index 86415b4d9..10ef755c9 100644 --- a/gitnexus/package-lock.json +++ b/gitnexus/package-lock.json @@ -1,12 +1,12 @@ { "name": "gitnexus", - "version": "1.1.9", + "version": "1.2.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gitnexus", - "version": "1.1.9", + "version": "1.2.8", "license": "PolyForm-Noncommercial-1.0.0", "dependencies": { "@huggingface/transformers": "^3.0.0", @@ -30,6 +30,7 @@ "tree-sitter-go": "^0.21.0", "tree-sitter-java": "^0.21.0", "tree-sitter-javascript": "^0.21.0", + "tree-sitter-php": "^0.23.0", "tree-sitter-python": "^0.21.0", "tree-sitter-rust": "^0.21.0", "tree-sitter-typescript": "^0.21.0", @@ -4378,6 +4379,34 @@ "node": "^18 || ^20 || >= 21" } }, + "node_modules/tree-sitter-php": { + "version": "0.23.12", + "resolved": "https://registry.npmjs.org/tree-sitter-php/-/tree-sitter-php-0.23.12.tgz", + "integrity": "sha512-VwkBVOahhC2NYXK/Fuqq30NxuL/6c2hmbxEF4jrB7AyR5rLc7nT27mzF3qoi+pqx9Gy2AbXnGezF7h4MeM6YRA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.2.2", + "node-gyp-build": "^4.8.2" + }, + "peerDependencies": { + "tree-sitter": "^0.21.1" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/tree-sitter-php/node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, "node_modules/tree-sitter-python": { "version": "0.21.0", "resolved": "https://registry.npmjs.org/tree-sitter-python/-/tree-sitter-python-0.21.0.tgz", diff --git a/gitnexus/package.json b/gitnexus/package.json index a4c3ea1ce..c2ed13fcf 100644 --- a/gitnexus/package.json +++ b/gitnexus/package.json @@ -64,6 +64,7 @@ "tree-sitter-javascript": "^0.21.0", "tree-sitter-python": "^0.21.0", "tree-sitter-rust": "^0.21.0", + "tree-sitter-php": "^0.23.0", "tree-sitter-typescript": "^0.21.0", "typescript": "^5.4.5", "uuid": "^13.0.0" diff --git a/package-lock.json b/package-lock.json index f4c3d0c4b..9c0b3071f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "GitnexusV2", + "name": "GitNexus", "lockfileVersion": 3, "requires": true, "packages": {} From 14e41a2fa012f35135d1906d7457be5a717bdb39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 01:04:06 +0300 Subject: [PATCH 03/12] feat(php): register PHP tree-sitter grammar in parser loader and parse worker - Import PHP from tree-sitter-php and use PHP.php_only in languageMap - Add anonymous_function_creation_expression to FUNCTION_NODE_TYPES - Add PHP export detection (public modifier / top-level = exported) - Add PHP built-in functions to BUILT_INS to reduce call graph noise Co-Authored-By: Claude Sonnet 4.6 --- .../core/ingestion/workers/parse-worker.ts | 38 +++++++++++++++++++ .../src/core/tree-sitter/parser-loader.ts | 2 + 2 files changed, 40 insertions(+) diff --git a/gitnexus/src/core/ingestion/workers/parse-worker.ts b/gitnexus/src/core/ingestion/workers/parse-worker.ts index 31e4d02e3..8c683c3b6 100644 --- a/gitnexus/src/core/ingestion/workers/parse-worker.ts +++ b/gitnexus/src/core/ingestion/workers/parse-worker.ts @@ -9,6 +9,7 @@ import CPP from 'tree-sitter-cpp'; import CSharp from 'tree-sitter-c-sharp'; import Go from 'tree-sitter-go'; import Rust from 'tree-sitter-rust'; +import PHP from 'tree-sitter-php'; import { SupportedLanguages } from '../../../config/supported-languages.js'; import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js'; import { getLanguageFromFilename } from '../utils.js'; @@ -100,6 +101,7 @@ const languageMap: Record = { [SupportedLanguages.CSharp]: CSharp, [SupportedLanguages.Go]: Go, [SupportedLanguages.Rust]: Rust, + [SupportedLanguages.PHP]: PHP.php_only, }; const setLanguage = (language: SupportedLanguages, filePath: string): void => { @@ -185,6 +187,24 @@ const isNodeExported = (node: any, name: string, language: string): boolean => { case 'cpp': return false; + case 'php': + // Top-level classes/interfaces/traits are always accessible + // Methods/properties are exported only if they have 'public' modifier + while (current) { + if (current.type === 'class_declaration' || + current.type === 'interface_declaration' || + current.type === 'trait_declaration' || + current.type === 'enum_declaration') { + return true; + } + if (current.type === 'visibility_modifier') { + return current.text === 'public'; + } + current = current.parent; + } + // Top-level functions (no parent class) are globally accessible + return true; + default: return false; } @@ -200,6 +220,7 @@ const FUNCTION_NODE_TYPES = new Set([ 'function_definition', 'async_function_declaration', 'async_arrow_function', 'method_declaration', 'constructor_declaration', 'local_function_statement', 'function_item', 'impl_item', + 'anonymous_function_creation_expression', // PHP anonymous functions ]); /** Walk up AST to find enclosing function, return its generateId or null for top-level */ @@ -274,6 +295,23 @@ const BUILT_INS = new Set([ 'open', 'read', 'write', 'close', 'append', 'extend', 'update', 'super', 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr', 'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs', + // PHP built-ins + 'echo', 'isset', 'empty', 'unset', 'list', 'array', 'compact', 'extract', + 'count', 'strlen', 'strpos', 'strrpos', 'substr', 'strtolower', 'strtoupper', 'trim', + 'ltrim', 'rtrim', 'str_replace', 'str_contains', 'str_starts_with', 'str_ends_with', + 'sprintf', 'vsprintf', 'printf', 'number_format', + 'array_map', 'array_filter', 'array_reduce', 'array_push', 'array_pop', 'array_shift', + 'array_unshift', 'array_slice', 'array_splice', 'array_merge', 'array_keys', 'array_values', + 'array_key_exists', 'in_array', 'array_search', 'array_unique', 'usort', 'rsort', + 'json_encode', 'json_decode', 'serialize', 'unserialize', + 'intval', 'floatval', 'strval', 'boolval', 'is_null', 'is_string', 'is_int', 'is_array', + 'is_object', 'is_numeric', 'is_bool', 'is_float', + 'var_dump', 'print_r', 'var_export', + 'date', 'time', 'strtotime', 'mktime', 'microtime', + 'file_exists', 'file_get_contents', 'file_put_contents', 'is_file', 'is_dir', + 'preg_match', 'preg_match_all', 'preg_replace', 'preg_split', + 'header', 'session_start', 'session_destroy', 'ob_start', 'ob_end_clean', 'ob_get_clean', + 'dd', 'dump', ]); // ============================================================================ diff --git a/gitnexus/src/core/tree-sitter/parser-loader.ts b/gitnexus/src/core/tree-sitter/parser-loader.ts index cdca3003d..e92898424 100644 --- a/gitnexus/src/core/tree-sitter/parser-loader.ts +++ b/gitnexus/src/core/tree-sitter/parser-loader.ts @@ -8,6 +8,7 @@ import CPP from 'tree-sitter-cpp'; import CSharp from 'tree-sitter-c-sharp'; import Go from 'tree-sitter-go'; import Rust from 'tree-sitter-rust'; +import PHP from 'tree-sitter-php'; import { SupportedLanguages } from '../../config/supported-languages.js'; let parser: Parser | null = null; @@ -23,6 +24,7 @@ const languageMap: Record = { [SupportedLanguages.CSharp]: CSharp, [SupportedLanguages.Go]: Go, [SupportedLanguages.Rust]: Rust, + [SupportedLanguages.PHP]: PHP.php_only, }; export const loadParser = async (): Promise => { From 33e32672a7e4c90436351344a68271635d0d4704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 01:04:32 +0300 Subject: [PATCH 04/12] feat(php): add PHP tree-sitter queries for all PHP constructs Add PHP_QUERIES covering: namespaces, classes, interfaces, traits, enums (PHP 8.1), functions, methods, use-statement imports, function/ method/static/nullsafe calls, and heritage (extends, implements, trait use). Trait query captures enclosing class name to satisfy parse-worker heritage logic. Co-Authored-By: Claude Sonnet 4.6 --- .../src/core/ingestion/tree-sitter-queries.ts | 74 +++++++++++++++++++ .../src/core/ingestion/tree-sitter-queries.ts | 74 +++++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts b/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts index a931b4a40..413262ba5 100644 --- a/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts +++ b/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts @@ -317,6 +317,79 @@ export const RUST_QUERIES = ` (impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (type_identifier) @heritage.class) @heritage `; +// PHP queries - works with tree-sitter-php (php_only grammar) +export const PHP_QUERIES = ` +; ── Namespace ──────────────────────────────────────────────────────────────── +(namespace_definition + name: (namespace_name) @name) @definition.namespace + +; ── Classes ────────────────────────────────────────────────────────────────── +(class_declaration + name: (name) @name) @definition.class + +; ── Interfaces ─────────────────────────────────────────────────────────────── +(interface_declaration + name: (name) @name) @definition.interface + +; ── Traits ─────────────────────────────────────────────────────────────────── +(trait_declaration + name: (name) @name) @definition.trait + +; ── Enums (PHP 8.1) ────────────────────────────────────────────────────────── +(enum_declaration + name: (name) @name) @definition.enum + +; ── Top-level functions ─────────────────────────────────────────────────────── +(function_definition + name: (name) @name) @definition.function + +; ── Methods (including constructors) ───────────────────────────────────────── +(method_declaration + name: (name) @name) @definition.method + +; ── Imports: use statements ────────────────────────────────────────────────── +; Simple: use App\\Models\\User; +(namespace_use_declaration + (namespace_use_clause + (qualified_name) @import.source)) @import + +; ── Function/method calls ──────────────────────────────────────────────────── +; Regular function call: foo() +(function_call_expression + function: (name) @call.name) @call + +; Method call: $obj->method() +(member_call_expression + name: (name) @call.name) @call + +; Nullsafe method call: $obj?->method() +(nullsafe_member_call_expression + name: (name) @call.name) @call + +; Static call: Foo::bar() +(static_call_expression + name: (name) @call.name) @call + +; ── Heritage: extends ──────────────────────────────────────────────────────── +(class_declaration + name: (name) @heritage.class + (base_clause + (qualified_name) @heritage.extends)) @heritage + +; ── Heritage: implements ───────────────────────────────────────────────────── +(class_declaration + name: (name) @heritage.class + (class_implements + (qualified_name) @heritage.implements)) @heritage.impl + +; ── Heritage: use trait (must capture enclosing class name) ────────────────── +(class_declaration + name: (name) @heritage.class + body: (declaration_list + (use_declaration + (qualified_name) @heritage.trait))) @heritage +`; + export const LANGUAGE_QUERIES: Record = { [SupportedLanguages.TypeScript]: TYPESCRIPT_QUERIES, [SupportedLanguages.JavaScript]: JAVASCRIPT_QUERIES, @@ -327,5 +400,6 @@ export const LANGUAGE_QUERIES: Record = { [SupportedLanguages.CPlusPlus]: CPP_QUERIES, [SupportedLanguages.CSharp]: CSHARP_QUERIES, [SupportedLanguages.Rust]: RUST_QUERIES, + [SupportedLanguages.PHP]: PHP_QUERIES, }; \ No newline at end of file diff --git a/gitnexus/src/core/ingestion/tree-sitter-queries.ts b/gitnexus/src/core/ingestion/tree-sitter-queries.ts index f8bcd7add..3cc7d686b 100644 --- a/gitnexus/src/core/ingestion/tree-sitter-queries.ts +++ b/gitnexus/src/core/ingestion/tree-sitter-queries.ts @@ -317,6 +317,79 @@ export const RUST_QUERIES = ` (impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (type_identifier) @heritage.class) @heritage `; +// PHP queries - works with tree-sitter-php (php_only grammar) +export const PHP_QUERIES = ` +; ── Namespace ──────────────────────────────────────────────────────────────── +(namespace_definition + name: (namespace_name) @name) @definition.namespace + +; ── Classes ────────────────────────────────────────────────────────────────── +(class_declaration + name: (name) @name) @definition.class + +; ── Interfaces ─────────────────────────────────────────────────────────────── +(interface_declaration + name: (name) @name) @definition.interface + +; ── Traits ─────────────────────────────────────────────────────────────────── +(trait_declaration + name: (name) @name) @definition.trait + +; ── Enums (PHP 8.1) ────────────────────────────────────────────────────────── +(enum_declaration + name: (name) @name) @definition.enum + +; ── Top-level functions ─────────────────────────────────────────────────────── +(function_definition + name: (name) @name) @definition.function + +; ── Methods (including constructors) ───────────────────────────────────────── +(method_declaration + name: (name) @name) @definition.method + +; ── Imports: use statements ────────────────────────────────────────────────── +; Simple: use App\\Models\\User; +(namespace_use_declaration + (namespace_use_clause + (qualified_name) @import.source)) @import + +; ── Function/method calls ──────────────────────────────────────────────────── +; Regular function call: foo() +(function_call_expression + function: (name) @call.name) @call + +; Method call: $obj->method() +(member_call_expression + name: (name) @call.name) @call + +; Nullsafe method call: $obj?->method() +(nullsafe_member_call_expression + name: (name) @call.name) @call + +; Static call: Foo::bar() +(static_call_expression + name: (name) @call.name) @call + +; ── Heritage: extends ──────────────────────────────────────────────────────── +(class_declaration + name: (name) @heritage.class + (base_clause + (qualified_name) @heritage.extends)) @heritage + +; ── Heritage: implements ───────────────────────────────────────────────────── +(class_declaration + name: (name) @heritage.class + (class_implements + (qualified_name) @heritage.implements)) @heritage.impl + +; ── Heritage: use trait (must capture enclosing class name) ────────────────── +(class_declaration + name: (name) @heritage.class + body: (declaration_list + (use_declaration + (qualified_name) @heritage.trait))) @heritage +`; + export const LANGUAGE_QUERIES: Record = { [SupportedLanguages.TypeScript]: TYPESCRIPT_QUERIES, [SupportedLanguages.JavaScript]: JAVASCRIPT_QUERIES, @@ -327,5 +400,6 @@ export const LANGUAGE_QUERIES: Record = { [SupportedLanguages.CPlusPlus]: CPP_QUERIES, [SupportedLanguages.CSharp]: CSHARP_QUERIES, [SupportedLanguages.Rust]: RUST_QUERIES, + [SupportedLanguages.PHP]: PHP_QUERIES, }; \ No newline at end of file From 136e83e76d5144734a8478057d1951d79cdc261d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 01:04:39 +0300 Subject: [PATCH 05/12] feat(php): add PHP/PSR-4 import resolution with composer.json support - Add .php, .phtml to EXTENSIONS array - Add ComposerConfig interface and loadComposerConfig() (mirrors TsconfigPaths pattern) - Add resolvePhpImport() with PSR-4 longest-match resolution + suffix fallback - Wire PHP handling into processImports() and processImportsFromExtracted() Co-Authored-By: Claude Sonnet 4.6 --- .../src/core/ingestion/import-processor.ts | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/gitnexus/src/core/ingestion/import-processor.ts b/gitnexus/src/core/ingestion/import-processor.ts index 54b5ea070..301f6d7fd 100644 --- a/gitnexus/src/core/ingestion/import-processor.ts +++ b/gitnexus/src/core/ingestion/import-processor.ts @@ -101,6 +101,37 @@ async function loadGoModulePath(repoRoot: string): Promise directory (e.g., "App\\" -> "app/") */ + psr4: Map; +} + +async function loadComposerConfig(repoRoot: string): Promise { + try { + const composerPath = path.join(repoRoot, 'composer.json'); + const raw = await fs.readFile(composerPath, 'utf-8'); + const composer = JSON.parse(raw); + const psr4Raw = composer.autoload?.['psr-4'] ?? {}; + const psr4Dev = composer['autoload-dev']?.['psr-4'] ?? {}; + const merged = { ...psr4Raw, ...psr4Dev }; + + const psr4 = new Map(); + for (const [ns, dir] of Object.entries(merged)) { + const nsNorm = (ns as string).replace(/\\+$/, ''); + const dirNorm = (dir as string).replace(/\\/g, '/').replace(/\/+$/, ''); + psr4.set(nsNorm, dirNorm); + } + + if (isDev) { + console.log(`📦 Loaded ${psr4.size} PSR-4 mappings from composer.json`); + } + return { psr4 }; + } catch { + return null; + } +} + // ============================================================================ // IMPORT PATH RESOLUTION // ============================================================================ @@ -122,6 +153,8 @@ const EXTENSIONS = [ '.go', // Rust '.rs', '/mod.rs', + // PHP + '.php', '.phtml', ]; /** @@ -551,6 +584,48 @@ function resolveGoPackage( return matches; } +// ============================================================================ +// PHP PSR-4 IMPORT RESOLUTION +// ============================================================================ + +/** + * Resolve a PHP use-statement import path using PSR-4 mappings. + * e.g. "App\Http\Controllers\UserController" -> "app/Http/Controllers/UserController.php" + */ +function resolvePhpImport( + importPath: string, + composerConfig: ComposerConfig | null, + allFiles: Set, + normalizedFileList: string[], + allFileList: string[], + index?: SuffixIndex, +): string | null { + // Normalize: replace backslashes with forward slashes + const normalized = importPath.replace(/\\/g, '/'); + + // Try PSR-4 resolution if composer.json was found + if (composerConfig) { + // Sort namespaces by length descending (longest match wins) + const sorted = [...composerConfig.psr4.entries()].sort((a, b) => b[0].length - a[0].length); + for (const [nsPrefix, dirPrefix] of sorted) { + const nsPrefixSlash = nsPrefix.replace(/\\/g, '/'); + if (normalized.startsWith(nsPrefixSlash + '/') || normalized === nsPrefixSlash) { + const remainder = normalized.slice(nsPrefixSlash.length).replace(/^\//, ''); + const filePath = dirPrefix + (remainder ? '/' + remainder : '') + '.php'; + if (allFiles.has(filePath)) return filePath; + if (index) { + const result = index.getInsensitive(filePath); + if (result) return result; + } + } + } + } + + // Fallback: suffix matching (works without composer.json) + const pathParts = normalized.split('/').filter(Boolean); + return suffixResolve(pathParts, normalizedFileList, allFileList, index); +} + // ============================================================================ // MAIN IMPORT PROCESSOR // ============================================================================ @@ -581,6 +656,7 @@ export const processImports = async ( const effectiveRoot = repoRoot || ''; const tsconfigPaths = await loadTsconfigPaths(effectiveRoot); const goModule = await loadGoModulePath(effectiveRoot); + const composerConfig = await loadComposerConfig(effectiveRoot); // Helper: add an IMPORTS edge + update import map const addImportEdge = (filePath: string, resolvedPath: string) => { @@ -705,6 +781,15 @@ export const processImports = async ( // Fall through if no files found (package might be external) } + // ---- PHP: handle namespace-based imports (use statements) ---- + if (language === SupportedLanguages.PHP) { + const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index); + if (resolved) { + addImportEdge(file.path, resolved); + } + return; + } + // ---- Standard single-file resolution ---- const resolvedPath = resolveImportPath( file.path, @@ -757,6 +842,7 @@ export const processImportsFromExtracted = async ( const effectiveRoot = repoRoot || ''; const tsconfigPaths = await loadTsconfigPaths(effectiveRoot); const goModule = await loadGoModulePath(effectiveRoot); + const composerConfig = await loadComposerConfig(effectiveRoot); const addImportEdge = (filePath: string, resolvedPath: string) => { const sourceId = generateId('File', filePath); @@ -856,6 +942,16 @@ export const processImportsFromExtracted = async ( } } + // PHP: handle namespace-based imports (use statements) + if (language === SupportedLanguages.PHP) { + const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index); + if (resolved) { + resolveCache.set(cacheKey, resolved); + addImportEdge(filePath, resolved); + } + continue; + } + // Standard resolution (has its own internal cache) const resolvedPath = resolveImportPath( filePath, From 897d4cbfef954fc7c808c3bf92ce5066bbd3b3c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 01:04:49 +0300 Subject: [PATCH 06/12] feat(laravel): add Laravel framework path detection and entry point scoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Laravel route/controller/job/listener/middleware/provider/policy/model path patterns with appropriate multipliers (1.5–3.0) - Add 'laravel' entry to FRAMEWORK_AST_PATTERNS for documentation - Add PHP ENTRY_POINT_PATTERNS: RESTful methods, __invoke, handle, boot, etc. - Add PHP/Laravel test file detection (Test.php, Spec.php, tests/feature/, tests/unit/) Co-Authored-By: Claude Sonnet 4.6 --- .../src/core/ingestion/entry-point-scoring.ts | 22 ++++++- .../src/core/ingestion/framework-detection.ts | 63 ++++++++++++++++++- .../src/core/ingestion/entry-point-scoring.ts | 22 ++++++- .../src/core/ingestion/framework-detection.ts | 61 ++++++++++++++++++ 4 files changed, 165 insertions(+), 3 deletions(-) diff --git a/gitnexus-web/src/core/ingestion/entry-point-scoring.ts b/gitnexus-web/src/core/ingestion/entry-point-scoring.ts index 1ef3d3ddc..a9b589aac 100644 --- a/gitnexus-web/src/core/ingestion/entry-point-scoring.ts +++ b/gitnexus-web/src/core/ingestion/entry-point-scoring.ts @@ -102,6 +102,21 @@ const ENTRY_POINT_PATTERNS: Record = { /^Run$/, // Run methods /^Start$/, // Start methods ], + + // PHP / Laravel + '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 + ], }; // ============================================================================ @@ -252,7 +267,12 @@ export function isTestFile(filePath: string): boolean { p.includes('/tests/') || // C# test patterns p.includes('.tests/') || - p.includes('tests.cs') + p.includes('tests.cs') || + // PHP/Laravel test patterns + p.endsWith('test.php') || + p.endsWith('spec.php') || + p.includes('/tests/feature/') || + p.includes('/tests/unit/') ); } diff --git a/gitnexus-web/src/core/ingestion/framework-detection.ts b/gitnexus-web/src/core/ingestion/framework-detection.ts index d3c75ab87..4e1b73d30 100644 --- a/gitnexus-web/src/core/ingestion/framework-detection.ts +++ b/gitnexus-web/src/core/ingestion/framework-detection.ts @@ -195,8 +195,65 @@ export function detectFrameworkFromPath(filePath: string): FrameworkHint | null return { framework: 'c-cpp', entryPointMultiplier: 2.5, reason: 'c-app' }; } + // ========== PHP / LARAVEL FRAMEWORKS ========== + + // Laravel routes (highest - these ARE the entry point definitions) + if (p.includes('/routes/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-routes' }; + } + + // Laravel controllers (very high - receive HTTP requests) + if ((p.includes('/http/controllers/') || p.includes('/controllers/')) && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller' }; + } + + // Laravel controller by file name convention + if (p.endsWith('controller.php')) { + return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller-file' }; + } + + // Laravel console commands + if ((p.includes('/console/commands/') || p.includes('/commands/')) && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-command' }; + } + + // Laravel jobs (queue entry points) + if (p.includes('/jobs/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-job' }; + } + + // Laravel listeners (event-driven entry points) + if (p.includes('/listeners/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-listener' }; + } + + // Laravel middleware + if (p.includes('/http/middleware/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-middleware' }; + } + + // Laravel service providers + if (p.includes('/providers/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-provider' }; + } + + // Laravel policies + if (p.includes('/policies/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 2.0, reason: 'laravel-policy' }; + } + + // Laravel models (important but not entry points per se) + if (p.includes('/models/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-model' }; + } + + // Generic PHP MVC: files ending with Controller.php + if (p.endsWith('controller.php')) { + return { framework: 'php-mvc', entryPointMultiplier: 2.5, reason: 'php-controller-file' }; + } + // ========== GENERIC PATTERNS ========== - + // Any language: index files in API folders if (p.includes('/api/') && ( p.endsWith('/index.ts') || p.endsWith('/index.js') || @@ -236,6 +293,10 @@ export const FRAMEWORK_AST_PATTERNS = { // Go patterns (function signatures) 'go-http': ['http.Handler', 'http.HandlerFunc', 'ServeHTTP'], + // PHP/Laravel + 'laravel': ['Route::get', 'Route::post', 'Route::put', 'Route::delete', + 'Route::resource', 'Route::apiResource', '#[Route('], + // Rust macros 'actix': ['#[get', '#[post', '#[put', '#[delete'], 'axum': ['Router::new'], diff --git a/gitnexus/src/core/ingestion/entry-point-scoring.ts b/gitnexus/src/core/ingestion/entry-point-scoring.ts index 55d0b1035..ac8e9098d 100644 --- a/gitnexus/src/core/ingestion/entry-point-scoring.ts +++ b/gitnexus/src/core/ingestion/entry-point-scoring.ts @@ -102,6 +102,21 @@ const ENTRY_POINT_PATTERNS: Record = { /^Run$/, // Run methods /^Start$/, // Start methods ], + + // PHP / Laravel + '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 + ], }; // ============================================================================ @@ -252,7 +267,12 @@ export function isTestFile(filePath: string): boolean { p.includes('/tests/') || // C# test patterns p.includes('.tests/') || - p.includes('tests.cs') + p.includes('tests.cs') || + // PHP/Laravel test patterns + p.endsWith('test.php') || + p.endsWith('spec.php') || + p.includes('/tests/feature/') || + p.includes('/tests/unit/') ); } diff --git a/gitnexus/src/core/ingestion/framework-detection.ts b/gitnexus/src/core/ingestion/framework-detection.ts index d3c75ab87..9296ec203 100644 --- a/gitnexus/src/core/ingestion/framework-detection.ts +++ b/gitnexus/src/core/ingestion/framework-detection.ts @@ -195,6 +195,63 @@ export function detectFrameworkFromPath(filePath: string): FrameworkHint | null return { framework: 'c-cpp', entryPointMultiplier: 2.5, reason: 'c-app' }; } + // ========== PHP / LARAVEL FRAMEWORKS ========== + + // Laravel routes (highest - these ARE the entry point definitions) + if (p.includes('/routes/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-routes' }; + } + + // Laravel controllers (very high - receive HTTP requests) + if ((p.includes('/http/controllers/') || p.includes('/controllers/')) && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller' }; + } + + // Laravel controller by file name convention + if (p.endsWith('controller.php')) { + return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller-file' }; + } + + // Laravel console commands + if ((p.includes('/console/commands/') || p.includes('/commands/')) && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-command' }; + } + + // Laravel jobs (queue entry points) + if (p.includes('/jobs/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-job' }; + } + + // Laravel listeners (event-driven entry points) + if (p.includes('/listeners/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-listener' }; + } + + // Laravel middleware + if (p.includes('/http/middleware/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-middleware' }; + } + + // Laravel service providers + if (p.includes('/providers/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-provider' }; + } + + // Laravel policies + if (p.includes('/policies/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 2.0, reason: 'laravel-policy' }; + } + + // Laravel models (important but not entry points per se) + if (p.includes('/models/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-model' }; + } + + // Generic PHP MVC: files ending with Controller.php + if (p.endsWith('controller.php')) { + return { framework: 'php-mvc', entryPointMultiplier: 2.5, reason: 'php-controller-file' }; + } + // ========== GENERIC PATTERNS ========== // Any language: index files in API folders @@ -236,6 +293,10 @@ export const FRAMEWORK_AST_PATTERNS = { // Go patterns (function signatures) 'go-http': ['http.Handler', 'http.HandlerFunc', 'ServeHTTP'], + // PHP/Laravel + 'laravel': ['Route::get', 'Route::post', 'Route::put', 'Route::delete', + 'Route::resource', 'Route::apiResource', '#[Route('], + // Rust macros 'actix': ['#[get', '#[post', '#[put', '#[delete'], 'axum': ['Router::new'], From da84d3924840ae4e00a4c1117fdfed1f241d5191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 01:05:02 +0300 Subject: [PATCH 07/12] feat(php): add PHP WASM support for web UI - Copy tree-sitter-php_only.wasm to public/wasm/php/tree-sitter-php.wasm - Register PHP WASM path in web parser-loader languageFileMap Co-Authored-By: Claude Sonnet 4.6 --- .../public/wasm/php/tree-sitter-php.wasm | Bin 0 -> 757278 bytes .../src/core/tree-sitter/parser-loader.ts | 1 + 2 files changed, 1 insertion(+) create mode 100644 gitnexus-web/public/wasm/php/tree-sitter-php.wasm diff --git a/gitnexus-web/public/wasm/php/tree-sitter-php.wasm b/gitnexus-web/public/wasm/php/tree-sitter-php.wasm new file mode 100644 index 0000000000000000000000000000000000000000..12f3c445c08aa6b4c74c6e73fbf56bbb65b1936b GIT binary patch literal 757278 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�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

Y=%o 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 h [SupportedLanguages.CSharp]: '/wasm/csharp/tree-sitter-csharp.wasm', [SupportedLanguages.Go]: '/wasm/go/tree-sitter-go.wasm', [SupportedLanguages.Rust]: '/wasm/rust/tree-sitter-rust.wasm', + [SupportedLanguages.PHP]: '/wasm/php/tree-sitter-php.wasm', }; return languageFileMap[language]; From cbd65839e6a6b2d8df3dfc885525d3ef1ceb08c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 01:13:26 +0300 Subject: [PATCH 08/12] fix(php): correct PHP query node type names for php_only grammar - Replace static_call_expression -> scoped_call_expression (php_only name) - Replace class_implements -> class_interface_clause - Use [(name) (qualified_name)] alternatives in heritage patterns to match both simple names (Controller) and fully-qualified names (App\Models\User) All patterns verified against tree-sitter-php node-types.json and live parse. Co-Authored-By: Claude Sonnet 4.6 --- .../src/core/ingestion/tree-sitter-queries.ts | 12 ++++++------ gitnexus/src/core/ingestion/tree-sitter-queries.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts b/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts index 413262ba5..a7abb0a71 100644 --- a/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts +++ b/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts @@ -366,28 +366,28 @@ export const PHP_QUERIES = ` (nullsafe_member_call_expression name: (name) @call.name) @call -; Static call: Foo::bar() -(static_call_expression +; Static call: Foo::bar() (php_only uses scoped_call_expression) +(scoped_call_expression name: (name) @call.name) @call ; ── Heritage: extends ──────────────────────────────────────────────────────── (class_declaration name: (name) @heritage.class (base_clause - (qualified_name) @heritage.extends)) @heritage + [(name) (qualified_name)] @heritage.extends)) @heritage ; ── Heritage: implements ───────────────────────────────────────────────────── (class_declaration name: (name) @heritage.class - (class_implements - (qualified_name) @heritage.implements)) @heritage.impl + (class_interface_clause + [(name) (qualified_name)] @heritage.implements)) @heritage.impl ; ── Heritage: use trait (must capture enclosing class name) ────────────────── (class_declaration name: (name) @heritage.class body: (declaration_list (use_declaration - (qualified_name) @heritage.trait))) @heritage + [(name) (qualified_name)] @heritage.trait))) @heritage `; export const LANGUAGE_QUERIES: Record = { diff --git a/gitnexus/src/core/ingestion/tree-sitter-queries.ts b/gitnexus/src/core/ingestion/tree-sitter-queries.ts index 3cc7d686b..dc2756453 100644 --- a/gitnexus/src/core/ingestion/tree-sitter-queries.ts +++ b/gitnexus/src/core/ingestion/tree-sitter-queries.ts @@ -366,28 +366,28 @@ export const PHP_QUERIES = ` (nullsafe_member_call_expression name: (name) @call.name) @call -; Static call: Foo::bar() -(static_call_expression +; Static call: Foo::bar() (php_only uses scoped_call_expression) +(scoped_call_expression name: (name) @call.name) @call ; ── Heritage: extends ──────────────────────────────────────────────────────── (class_declaration name: (name) @heritage.class (base_clause - (qualified_name) @heritage.extends)) @heritage + [(name) (qualified_name)] @heritage.extends)) @heritage ; ── Heritage: implements ───────────────────────────────────────────────────── (class_declaration name: (name) @heritage.class - (class_implements - (qualified_name) @heritage.implements)) @heritage.impl + (class_interface_clause + [(name) (qualified_name)] @heritage.implements)) @heritage.impl ; ── Heritage: use trait (must capture enclosing class name) ────────────────── (class_declaration name: (name) @heritage.class body: (declaration_list (use_declaration - (qualified_name) @heritage.trait))) @heritage + [(name) (qualified_name)] @heritage.trait))) @heritage `; export const LANGUAGE_QUERIES: Record = { From 556ba91290b6607dd234f82716385c12b3c509bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 01:28:42 +0300 Subject: [PATCH 09/12] feat(laravel): add Service/Repository pattern support - Add /services/ (1.8x) and /repositories/ (1.5x) path multipliers - Add Service$, Repository$, find, findAll, save, delete to PHP entry point patterns Co-Authored-By: Claude Sonnet 4.6 --- gitnexus-web/src/core/ingestion/entry-point-scoring.ts | 6 ++++++ gitnexus-web/src/core/ingestion/framework-detection.ts | 10 ++++++++++ gitnexus/src/core/ingestion/entry-point-scoring.ts | 6 ++++++ gitnexus/src/core/ingestion/framework-detection.ts | 10 ++++++++++ 4 files changed, 32 insertions(+) diff --git a/gitnexus-web/src/core/ingestion/entry-point-scoring.ts b/gitnexus-web/src/core/ingestion/entry-point-scoring.ts index a9b589aac..9285e407d 100644 --- a/gitnexus-web/src/core/ingestion/entry-point-scoring.ts +++ b/gitnexus-web/src/core/ingestion/entry-point-scoring.ts @@ -116,6 +116,12 @@ const ENTRY_POINT_PATTERNS: Record = { /^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() ], }; diff --git a/gitnexus-web/src/core/ingestion/framework-detection.ts b/gitnexus-web/src/core/ingestion/framework-detection.ts index 4e1b73d30..e529649ca 100644 --- a/gitnexus-web/src/core/ingestion/framework-detection.ts +++ b/gitnexus-web/src/core/ingestion/framework-detection.ts @@ -247,6 +247,16 @@ export function detectFrameworkFromPath(filePath: string): FrameworkHint | null return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-model' }; } + // Laravel services (Service Repository pattern) + if (p.includes('/services/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-service' }; + } + + // Laravel repositories (Service Repository pattern) + if (p.includes('/repositories/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-repository' }; + } + // Generic PHP MVC: files ending with Controller.php if (p.endsWith('controller.php')) { return { framework: 'php-mvc', entryPointMultiplier: 2.5, reason: 'php-controller-file' }; diff --git a/gitnexus/src/core/ingestion/entry-point-scoring.ts b/gitnexus/src/core/ingestion/entry-point-scoring.ts index ac8e9098d..ed328cc13 100644 --- a/gitnexus/src/core/ingestion/entry-point-scoring.ts +++ b/gitnexus/src/core/ingestion/entry-point-scoring.ts @@ -116,6 +116,12 @@ const ENTRY_POINT_PATTERNS: Record = { /^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() ], }; diff --git a/gitnexus/src/core/ingestion/framework-detection.ts b/gitnexus/src/core/ingestion/framework-detection.ts index 9296ec203..7c334b956 100644 --- a/gitnexus/src/core/ingestion/framework-detection.ts +++ b/gitnexus/src/core/ingestion/framework-detection.ts @@ -247,6 +247,16 @@ export function detectFrameworkFromPath(filePath: string): FrameworkHint | null return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-model' }; } + // Laravel services (Service Repository pattern) + if (p.includes('/services/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-service' }; + } + + // Laravel repositories (Service Repository pattern) + if (p.includes('/repositories/') && p.endsWith('.php')) { + return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-repository' }; + } + // Generic PHP MVC: files ending with Controller.php if (p.endsWith('controller.php')) { return { framework: 'php-mvc', entryPointMultiplier: 2.5, reason: 'php-controller-file' }; From bb8aa0c5c5ccbf8396f3815533fd7c81a6550176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 10:31:35 +0300 Subject: [PATCH 10/12] feat(php): track Eloquent model properties and relationships with structured metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add PHP property_declaration query to capture class properties ($fillable, $casts, etc.) - Extract array values from Eloquent model properties as description field: - $fillable → comma-separated field names (e.g. "name, email, password") - $casts → key:type pairs (e.g. "email_verified_at:datetime, active:boolean") - $hidden, $guarded, $with, $appends similarly - Detect Eloquent relationship methods (hasMany, hasOne, belongsTo, etc.) and annotate them with relation type and target model (e.g. "hasMany(Post)") Co-Authored-By: Claude Sonnet 4.6 --- .../src/core/ingestion/tree-sitter-queries.ts | 6 + .../src/core/ingestion/tree-sitter-queries.ts | 6 + .../core/ingestion/workers/parse-worker.ts | 113 ++++++++++++++++++ 3 files changed, 125 insertions(+) diff --git a/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts b/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts index a7abb0a71..5cb2d46ff 100644 --- a/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts +++ b/gitnexus-web/src/core/ingestion/tree-sitter-queries.ts @@ -347,6 +347,12 @@ export const PHP_QUERIES = ` (method_declaration name: (name) @name) @definition.method +; ── Class properties (including Eloquent $fillable, $casts, etc.) ──────────── +(property_declaration + (property_element + (variable_name + (name) @name))) @definition.property + ; ── Imports: use statements ────────────────────────────────────────────────── ; Simple: use App\\Models\\User; (namespace_use_declaration diff --git a/gitnexus/src/core/ingestion/tree-sitter-queries.ts b/gitnexus/src/core/ingestion/tree-sitter-queries.ts index dc2756453..ff4f8f28f 100644 --- a/gitnexus/src/core/ingestion/tree-sitter-queries.ts +++ b/gitnexus/src/core/ingestion/tree-sitter-queries.ts @@ -347,6 +347,12 @@ export const PHP_QUERIES = ` (method_declaration name: (name) @name) @definition.method +; ── Class properties (including Eloquent $fillable, $casts, etc.) ──────────── +(property_declaration + (property_element + (variable_name + (name) @name))) @definition.property + ; ── Imports: use statements ────────────────────────────────────────────────── ; Simple: use App\\Models\\User; (namespace_use_declaration diff --git a/gitnexus/src/core/ingestion/workers/parse-worker.ts b/gitnexus/src/core/ingestion/workers/parse-worker.ts index 8c683c3b6..f4541d0d2 100644 --- a/gitnexus/src/core/ingestion/workers/parse-worker.ts +++ b/gitnexus/src/core/ingestion/workers/parse-worker.ts @@ -424,6 +424,109 @@ const processBatch = (files: ParseWorkerInput[], onProgress?: (filesProcessed: n return result; }; +// ============================================================================ +// PHP Eloquent metadata extraction +// ============================================================================ + +/** Eloquent model properties whose array values are worth indexing */ +const ELOQUENT_ARRAY_PROPS = new Set(['fillable', 'casts', 'hidden', 'guarded', 'with', 'appends']); + +/** Eloquent relationship method names */ +const ELOQUENT_RELATIONS = new Set([ + 'hasMany', 'hasOne', 'belongsTo', 'belongsToMany', + 'morphTo', 'morphMany', 'morphOne', 'morphToMany', 'morphedByMany', + 'hasManyThrough', 'hasOneThrough', +]); + +function findDescendant(node: any, type: string): any { + if (node.type === type) return node; + for (const child of (node.children ?? [])) { + const found = findDescendant(child, type); + if (found) return found; + } + return null; +} + +function extractStringContent(node: any): string | null { + if (!node) return null; + const content = node.children?.find((c: any) => c.type === 'string_content'); + if (content) return content.text; + if (node.type === 'string_content') return node.text; + return null; +} + +/** + * For a PHP property_declaration node, extract array values as a description string. + * Returns null if not an Eloquent model property or no array values found. + */ +function extractPhpPropertyDescription(propName: string, propDeclNode: any): string | null { + if (!ELOQUENT_ARRAY_PROPS.has(propName)) return null; + + const arrayNode = findDescendant(propDeclNode, 'array_creation_expression'); + if (!arrayNode) return null; + + const items: string[] = []; + for (const child of (arrayNode.children ?? [])) { + if (child.type !== 'array_element_initializer') continue; + const children = child.children ?? []; + const arrowIdx = children.findIndex((c: any) => c.type === '=>'); + if (arrowIdx !== -1) { + // key => value pair (used in $casts) + const key = extractStringContent(children[arrowIdx - 1]); + const val = extractStringContent(children[arrowIdx + 1]); + if (key && val) items.push(`${key}:${val}`); + } else { + // Simple value (used in $fillable, $hidden, etc.) + const val = extractStringContent(children[0]); + if (val) items.push(val); + } + } + + return items.length > 0 ? items.join(', ') : null; +} + +/** + * For a PHP method_declaration node, detect if it defines an Eloquent relationship. + * Returns description like "hasMany(Post)" or null. + */ +function extractEloquentRelationDescription(methodNode: any): string | null { + function findRelationCall(node: any): any { + if (node.type === 'member_call_expression') { + const children = node.children ?? []; + const objectNode = children.find((c: any) => c.type === 'variable_name' && c.text === '$this'); + const nameNode = children.find((c: any) => c.type === 'name'); + if (objectNode && nameNode && ELOQUENT_RELATIONS.has(nameNode.text)) return node; + } + for (const child of (node.children ?? [])) { + const found = findRelationCall(child); + if (found) return found; + } + return null; + } + + const callNode = findRelationCall(methodNode); + if (!callNode) return null; + + const relType = callNode.children?.find((c: any) => c.type === 'name')?.text; + const argsNode = callNode.children?.find((c: any) => c.type === 'arguments'); + let targetModel: string | null = null; + if (argsNode) { + const firstArg = argsNode.children?.find((c: any) => c.type === 'argument'); + if (firstArg) { + const classConstant = firstArg.children?.find((c: any) => + c.type === 'class_constant_access_expression' + ); + if (classConstant) { + targetModel = classConstant.children?.find((c: any) => c.type === 'name')?.text ?? null; + } + } + } + + if (relType && targetModel) return `${relType}(${targetModel})`; + if (relType) return relType; + return null; +} + const processFileGroup = ( files: ParseWorkerInput[], language: SupportedLanguages, @@ -530,6 +633,15 @@ const processFileGroup = ( const nodeName = nameNode.text; const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`); + let description: string | undefined; + if (language === SupportedLanguages.PHP) { + if (nodeLabel === 'Property' && captureMap['definition.property']) { + description = extractPhpPropertyDescription(nodeName, captureMap['definition.property']) ?? undefined; + } else if (nodeLabel === 'Method' && captureMap['definition.method']) { + description = extractEloquentRelationDescription(captureMap['definition.method']) ?? undefined; + } + } + result.nodes.push({ id: nodeId, label: nodeLabel, @@ -540,6 +652,7 @@ const processFileGroup = ( endLine: nameNode.endPosition.row, language: language, isExported: isNodeExported(nameNode, nodeName, language), + ...(description !== undefined ? { description } : {}), }, }); From 305c5a743c4e3ffc00219b505524c479285f56fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 10:31:53 +0300 Subject: [PATCH 11/12] feat(graph): persist multi-language nodes to KuzuDB and add description field - Add description STRING column to all code element schemas (Function, Class, Interface, Method, CodeElement, and all CODE_ELEMENT_BASE types including Property) - Add generateMultiLangNodeCSV() to write Struct, Enum, Trait, Property, etc. nodes to KuzuDB (previously they existed in-memory only, never reached the DB) - Update generateAllCSVs() to generate CSVs for all 19 multi-language types - Fix getCopyQuery() to use correct column list per table type: - Multi-language tables: id,name,filePath,startLine,endLine,content,description - Core tables: id,name,filePath,startLine,endLine,isExported,content,description - Update batchInsertNodesToKuzu and insertNodeToKuzu to include description field Co-Authored-By: Claude Sonnet 4.6 --- gitnexus/src/core/kuzu/csv-generator.ts | 50 ++++++++++++++++++++++--- gitnexus/src/core/kuzu/kuzu-adapter.ts | 14 +++++-- gitnexus/src/core/kuzu/schema.ts | 7 ++++ 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/gitnexus/src/core/kuzu/csv-generator.ts b/gitnexus/src/core/kuzu/csv-generator.ts index 266309570..735ebab02 100644 --- a/gitnexus/src/core/kuzu/csv-generator.ts +++ b/gitnexus/src/core/kuzu/csv-generator.ts @@ -180,16 +180,16 @@ const generateFolderCSV = (nodes: GraphNode[]): string => { /** * Generate CSV for code element nodes (Function, Class, Interface, Method, CodeElement) - * Headers: id,name,filePath,startLine,endLine,isExported,content + * Headers: id,name,filePath,startLine,endLine,isExported,content,description */ const generateCodeElementCSV = ( nodes: GraphNode[], label: NodeLabel, fileContents: Map ): string => { - const headers = ['id', 'name', 'filePath', 'startLine', 'endLine', 'isExported', 'content']; + const headers = ['id', 'name', 'filePath', 'startLine', 'endLine', 'isExported', 'content', 'description']; const rows: string[] = [headers.join(',')]; - + for (const node of nodes) { if (node.label !== label) continue; const content = extractContent(node, fileContents); @@ -201,9 +201,10 @@ const generateCodeElementCSV = ( escapeCSVNumber(node.properties.endLine, -1), node.properties.isExported ? 'true' : 'false', escapeCSVField(content), + escapeCSVField((node.properties as any).description || ''), ].join(',')); } - + return rows.join('\n'); }; @@ -267,6 +268,35 @@ const generateProcessCSV = (nodes: GraphNode[]): string => { return rows.join('\n'); }; +/** + * Generate CSV for multi-language node tables (Struct, Enum, Trait, Property, etc.) + * These use CODE_ELEMENT_BASE schema: id,name,filePath,startLine,endLine,content,description + */ +const generateMultiLangNodeCSV = ( + nodes: GraphNode[], + label: string, + fileContents: Map +): string => { + const headers = ['id', 'name', 'filePath', 'startLine', 'endLine', 'content', 'description']; + const rows: string[] = [headers.join(',')]; + + for (const node of nodes) { + if (node.label !== label) continue; + const content = extractContent(node, fileContents); + rows.push([ + escapeCSVField(node.id), + escapeCSVField(node.properties.name || ''), + escapeCSVField(node.properties.filePath || ''), + escapeCSVNumber(node.properties.startLine, -1), + escapeCSVNumber(node.properties.endLine, -1), + escapeCSVField(content), + escapeCSVField((node.properties as any).description || ''), + ].join(',')); + } + + return rows.join('\n'); +}; + /** * Generate CSV for the single CodeRelation table * Headers: from,to,type,confidence,reason @@ -317,7 +347,17 @@ export const generateAllCSVs = ( nodeCSVs.set('CodeElement', generateCodeElementCSV(nodes, 'CodeElement', fileContents)); nodeCSVs.set('Community', generateCommunityCSV(nodes)); nodeCSVs.set('Process', generateProcessCSV(nodes)); - + + // Multi-language node types (CODE_ELEMENT_BASE schema: id,name,filePath,startLine,endLine,content,description) + const multiLangTypes = [ + 'Struct', 'Enum', 'Macro', 'Typedef', 'Union', 'Namespace', 'Trait', 'Impl', + 'TypeAlias', 'Const', 'Static', 'Property', 'Record', 'Delegate', 'Annotation', + 'Constructor', 'Template', 'Module', + ] as const; + for (const mlType of multiLangTypes) { + nodeCSVs.set(mlType, generateMultiLangNodeCSV(nodes, mlType, fileContents)); + } + // Generate single relation CSV const relCSV = generateRelationCSV(graph); diff --git a/gitnexus/src/core/kuzu/kuzu-adapter.ts b/gitnexus/src/core/kuzu/kuzu-adapter.ts index 9988b56ed..09e52a265 100644 --- a/gitnexus/src/core/kuzu/kuzu-adapter.ts +++ b/gitnexus/src/core/kuzu/kuzu-adapter.ts @@ -282,8 +282,12 @@ const getCopyQuery = (table: NodeTableName, filePath: string): string => { if (table === 'Process') { return `COPY ${t}(id, label, heuristicLabel, processType, stepCount, communities, entryPointId, terminalId) FROM "${filePath}" ${COPY_CSV_OPTS}`; } - // Code element tables (Function, Class, Interface, Method, CodeElement, and multi-language) - return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content) FROM "${filePath}" ${COPY_CSV_OPTS}`; + // Multi-language code element tables (CODE_ELEMENT_BASE: no isExported, has description) + if (BACKTICK_TABLES.has(table)) { + return `COPY ${t}(id, name, filePath, startLine, endLine, content, description) FROM "${filePath}" ${COPY_CSV_OPTS}`; + } + // Core code element tables (Function, Class, Interface, Method, CodeElement) + return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description) FROM "${filePath}" ${COPY_CSV_OPTS}`; }; /** @@ -320,7 +324,8 @@ export const insertNodeToKuzu = async ( query = `CREATE (n:Folder {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}})`; } else { // Function, Class, Method, Interface, etc. - standard code element schema - query = `CREATE (n:${label} {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, content: ${escapeValue(properties.content || '')}})`; + const descStr = properties.description ? `, description: ${escapeValue(properties.description)}` : ''; + query = `CREATE (n:${label} {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, content: ${escapeValue(properties.content || '')}${descStr}})`; } // Use per-query connection if dbPath provided (avoids lock conflicts) @@ -385,7 +390,8 @@ export const batchInsertNodesToKuzu = async ( } else if (label === 'Folder') { query = `MERGE (n:Folder {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}`; } else { - query = `MERGE (n:${label} {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.content = ${escapeValue(properties.content || '')}`; + const descPart = properties.description ? `, n.description = ${escapeValue(properties.description)}` : ''; + query = `MERGE (n:${label} {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.content = ${escapeValue(properties.content || '')}${descPart}`; } await tempConn.query(query); diff --git a/gitnexus/src/core/kuzu/schema.ts b/gitnexus/src/core/kuzu/schema.ts index c0f3b394a..a50b52338 100644 --- a/gitnexus/src/core/kuzu/schema.ts +++ b/gitnexus/src/core/kuzu/schema.ts @@ -64,6 +64,7 @@ CREATE NODE TABLE Function ( endLine INT64, isExported BOOLEAN, content STRING, + description STRING, PRIMARY KEY (id) )`; @@ -76,6 +77,7 @@ CREATE NODE TABLE Class ( endLine INT64, isExported BOOLEAN, content STRING, + description STRING, PRIMARY KEY (id) )`; @@ -88,6 +90,7 @@ CREATE NODE TABLE Interface ( endLine INT64, isExported BOOLEAN, content STRING, + description STRING, PRIMARY KEY (id) )`; @@ -100,6 +103,7 @@ CREATE NODE TABLE Method ( endLine INT64, isExported BOOLEAN, content STRING, + description STRING, PRIMARY KEY (id) )`; @@ -112,6 +116,7 @@ CREATE NODE TABLE CodeElement ( endLine INT64, isExported BOOLEAN, content STRING, + description STRING, PRIMARY KEY (id) )`; @@ -154,6 +159,7 @@ CREATE NODE TABLE Process ( // ============================================================================ // Generic code element with startLine/endLine for C, C++, Rust, Go, Java, C# +// description: optional metadata (e.g. Eloquent $fillable fields, relationship targets) const CODE_ELEMENT_BASE = (name: string) => ` CREATE NODE TABLE \`${name}\` ( id STRING, @@ -162,6 +168,7 @@ CREATE NODE TABLE \`${name}\` ( startLine INT64, endLine INT64, content STRING, + description STRING, PRIMARY KEY (id) )`; From ad2bf7a5cf0c8ceef43246e0aa2c06f3260b0373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCne=C5=9F=20Bizim?= Date: Wed, 25 Feb 2026 11:33:54 +0300 Subject: [PATCH 12/12] fix(schema): add missing CodeRelation pairs for Class->Trait and Method->Property Laravel projects commonly use traits (HasFactory, SoftDeletes, etc.) on classes and methods that reference model properties. These relation pairs were missing from the CodeRelation table, causing edges to be dropped during indexing. Co-Authored-By: Claude Sonnet 4.6 --- gitnexus/src/core/kuzu/schema.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gitnexus/src/core/kuzu/schema.ts b/gitnexus/src/core/kuzu/schema.ts index a50b52338..af438a526 100644 --- a/gitnexus/src/core/kuzu/schema.ts +++ b/gitnexus/src/core/kuzu/schema.ts @@ -250,6 +250,7 @@ CREATE REL TABLE ${REL_TABLE_NAME} ( FROM Class TO \`Enum\`, FROM Class TO \`Annotation\`, FROM Class TO \`Constructor\`, + FROM Class TO \`Trait\`, FROM Method TO Function, FROM Method TO Method, FROM Method TO Class, @@ -264,6 +265,7 @@ CREATE REL TABLE ${REL_TABLE_NAME} ( FROM Method TO \`Impl\`, FROM Method TO Interface, FROM Method TO \`Constructor\`, + FROM Method TO \`Property\`, FROM \`Template\` TO \`Template\`, FROM \`Template\` TO Function, FROM \`Template\` TO Method,