gitnexus 1.3.3 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/ai-context.js +23 -52
- package/dist/cli/analyze.js +4 -1
- package/dist/cli/index.js +1 -0
- package/dist/cli/mcp.js +11 -22
- package/dist/cli/serve.d.ts +1 -0
- package/dist/cli/serve.js +2 -1
- package/dist/cli/setup.js +2 -2
- package/dist/cli/wiki.js +6 -2
- package/dist/config/supported-languages.d.ts +3 -1
- package/dist/config/supported-languages.js +2 -2
- package/dist/core/embeddings/embedder.js +40 -1
- package/dist/core/graph/types.d.ts +2 -0
- package/dist/core/ingestion/call-processor.js +39 -0
- package/dist/core/ingestion/entry-point-scoring.js +49 -1
- package/dist/core/ingestion/framework-detection.d.ts +15 -4
- package/dist/core/ingestion/framework-detection.js +146 -5
- package/dist/core/ingestion/import-processor.js +140 -0
- package/dist/core/ingestion/parsing-processor.js +61 -8
- package/dist/core/ingestion/process-processor.js +7 -1
- package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -0
- package/dist/core/ingestion/tree-sitter-queries.js +414 -282
- package/dist/core/ingestion/utils.js +8 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +3 -0
- package/dist/core/ingestion/workers/parse-worker.js +240 -0
- package/dist/core/kuzu/csv-generator.js +4 -2
- package/dist/core/kuzu/kuzu-adapter.d.ts +9 -0
- package/dist/core/kuzu/kuzu-adapter.js +68 -9
- package/dist/core/kuzu/schema.d.ts +6 -6
- package/dist/core/kuzu/schema.js +16 -0
- package/dist/core/tree-sitter/parser-loader.js +4 -0
- package/dist/core/wiki/generator.js +2 -2
- package/dist/mcp/local/local-backend.js +25 -13
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.js +13 -2
- package/dist/mcp/staleness.js +2 -2
- package/dist/server/api.d.ts +7 -5
- package/dist/server/api.js +145 -127
- package/dist/server/mcp-http.d.ts +13 -0
- package/dist/server/mcp-http.js +100 -0
- package/package.json +6 -2
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/skills/gitnexus-cli.md +82 -0
- package/skills/{debugging.md → gitnexus-debugging.md} +12 -8
- package/skills/{exploring.md → gitnexus-exploring.md} +10 -7
- package/skills/gitnexus-guide.md +64 -0
- package/skills/{impact-analysis.md → gitnexus-impact-analysis.md} +14 -11
- package/skills/gitnexus-pr-review.md +163 -0
- package/skills/{refactoring.md → gitnexus-refactoring.md} +15 -7
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Framework Detection
|
|
3
3
|
*
|
|
4
|
-
* Detects frameworks from
|
|
5
|
-
*
|
|
4
|
+
* Detects frameworks from:
|
|
5
|
+
* 1) file path patterns
|
|
6
|
+
* 2) AST definition text (decorators/annotations/attributes)
|
|
7
|
+
* and provides entry point multipliers for process scoring.
|
|
6
8
|
*
|
|
7
9
|
* DESIGN: Returns null for unknown frameworks, which causes a 1.0 multiplier
|
|
8
10
|
* (no bonus, no penalty) - same behavior as before this feature.
|
|
@@ -146,6 +148,92 @@ export function detectFrameworkFromPath(filePath) {
|
|
|
146
148
|
if ((p.includes('/src/') && (p.endsWith('/app.c') || p.endsWith('/app.cpp')))) {
|
|
147
149
|
return { framework: 'c-cpp', entryPointMultiplier: 2.5, reason: 'c-app' };
|
|
148
150
|
}
|
|
151
|
+
// ========== PHP / LARAVEL FRAMEWORKS ==========
|
|
152
|
+
// Laravel routes (highest - these ARE the entry point definitions)
|
|
153
|
+
if (p.includes('/routes/') && p.endsWith('.php')) {
|
|
154
|
+
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-routes' };
|
|
155
|
+
}
|
|
156
|
+
// Laravel controllers (very high - receive HTTP requests)
|
|
157
|
+
if ((p.includes('/http/controllers/') || p.includes('/controllers/')) && p.endsWith('.php')) {
|
|
158
|
+
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller' };
|
|
159
|
+
}
|
|
160
|
+
// Laravel controller by file name convention
|
|
161
|
+
if (p.endsWith('controller.php')) {
|
|
162
|
+
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller-file' };
|
|
163
|
+
}
|
|
164
|
+
// Laravel console commands
|
|
165
|
+
if ((p.includes('/console/commands/') || p.includes('/commands/')) && p.endsWith('.php')) {
|
|
166
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-command' };
|
|
167
|
+
}
|
|
168
|
+
// Laravel jobs (queue entry points)
|
|
169
|
+
if (p.includes('/jobs/') && p.endsWith('.php')) {
|
|
170
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-job' };
|
|
171
|
+
}
|
|
172
|
+
// Laravel listeners (event-driven entry points)
|
|
173
|
+
if (p.includes('/listeners/') && p.endsWith('.php')) {
|
|
174
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-listener' };
|
|
175
|
+
}
|
|
176
|
+
// Laravel middleware
|
|
177
|
+
if (p.includes('/http/middleware/') && p.endsWith('.php')) {
|
|
178
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-middleware' };
|
|
179
|
+
}
|
|
180
|
+
// Laravel service providers
|
|
181
|
+
if (p.includes('/providers/') && p.endsWith('.php')) {
|
|
182
|
+
return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-provider' };
|
|
183
|
+
}
|
|
184
|
+
// Laravel policies
|
|
185
|
+
if (p.includes('/policies/') && p.endsWith('.php')) {
|
|
186
|
+
return { framework: 'laravel', entryPointMultiplier: 2.0, reason: 'laravel-policy' };
|
|
187
|
+
}
|
|
188
|
+
// Laravel models (important but not entry points per se)
|
|
189
|
+
if (p.includes('/models/') && p.endsWith('.php')) {
|
|
190
|
+
return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-model' };
|
|
191
|
+
}
|
|
192
|
+
// Laravel services (Service Repository pattern)
|
|
193
|
+
if (p.includes('/services/') && p.endsWith('.php')) {
|
|
194
|
+
return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-service' };
|
|
195
|
+
}
|
|
196
|
+
// Laravel repositories (Service Repository pattern)
|
|
197
|
+
if (p.includes('/repositories/') && p.endsWith('.php')) {
|
|
198
|
+
return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-repository' };
|
|
199
|
+
}
|
|
200
|
+
// ========== SWIFT / iOS ==========
|
|
201
|
+
// iOS App entry points (highest priority)
|
|
202
|
+
if (p.endsWith('/appdelegate.swift') || p.endsWith('/scenedelegate.swift') || p.endsWith('/app.swift')) {
|
|
203
|
+
return { framework: 'ios', entryPointMultiplier: 3.0, reason: 'ios-app-entry' };
|
|
204
|
+
}
|
|
205
|
+
// SwiftUI App entry (@main)
|
|
206
|
+
if (p.endsWith('app.swift') && p.includes('/sources/')) {
|
|
207
|
+
return { framework: 'swiftui', entryPointMultiplier: 3.0, reason: 'swiftui-app' };
|
|
208
|
+
}
|
|
209
|
+
// UIKit ViewControllers (high priority - screen entry points)
|
|
210
|
+
if ((p.includes('/viewcontrollers/') || p.includes('/controllers/') || p.includes('/screens/')) && p.endsWith('.swift')) {
|
|
211
|
+
return { framework: 'uikit', entryPointMultiplier: 2.5, reason: 'uikit-viewcontroller' };
|
|
212
|
+
}
|
|
213
|
+
// ViewController by filename convention
|
|
214
|
+
if (p.endsWith('viewcontroller.swift') || p.endsWith('vc.swift')) {
|
|
215
|
+
return { framework: 'uikit', entryPointMultiplier: 2.5, reason: 'uikit-viewcontroller-file' };
|
|
216
|
+
}
|
|
217
|
+
// Coordinator pattern (navigation entry points)
|
|
218
|
+
if (p.includes('/coordinators/') && p.endsWith('.swift')) {
|
|
219
|
+
return { framework: 'ios-coordinator', entryPointMultiplier: 2.5, reason: 'ios-coordinator' };
|
|
220
|
+
}
|
|
221
|
+
// Coordinator by filename
|
|
222
|
+
if (p.endsWith('coordinator.swift')) {
|
|
223
|
+
return { framework: 'ios-coordinator', entryPointMultiplier: 2.5, reason: 'ios-coordinator-file' };
|
|
224
|
+
}
|
|
225
|
+
// SwiftUI Views (moderate - reusable components)
|
|
226
|
+
if ((p.includes('/views/') || p.includes('/scenes/')) && p.endsWith('.swift')) {
|
|
227
|
+
return { framework: 'swiftui', entryPointMultiplier: 1.8, reason: 'swiftui-view' };
|
|
228
|
+
}
|
|
229
|
+
// Service layer
|
|
230
|
+
if (p.includes('/services/') && p.endsWith('.swift')) {
|
|
231
|
+
return { framework: 'ios-service', entryPointMultiplier: 1.8, reason: 'ios-service' };
|
|
232
|
+
}
|
|
233
|
+
// Router / navigation
|
|
234
|
+
if (p.includes('/router/') && p.endsWith('.swift')) {
|
|
235
|
+
return { framework: 'ios-router', entryPointMultiplier: 2.0, reason: 'ios-router' };
|
|
236
|
+
}
|
|
149
237
|
// ========== GENERIC PATTERNS ==========
|
|
150
238
|
// Any language: index files in API folders
|
|
151
239
|
if (p.includes('/api/') && (p.endsWith('/index.ts') || p.endsWith('/index.js') ||
|
|
@@ -156,11 +244,11 @@ export function detectFrameworkFromPath(filePath) {
|
|
|
156
244
|
return null;
|
|
157
245
|
}
|
|
158
246
|
// ============================================================================
|
|
159
|
-
//
|
|
247
|
+
// AST-BASED FRAMEWORK DETECTION
|
|
160
248
|
// ============================================================================
|
|
161
249
|
/**
|
|
162
|
-
* Patterns that indicate entry points within code
|
|
163
|
-
* These
|
|
250
|
+
* Patterns that indicate framework entry points within code definitions.
|
|
251
|
+
* These are matched against AST node text (class/method/function declaration text).
|
|
164
252
|
*/
|
|
165
253
|
export const FRAMEWORK_AST_PATTERNS = {
|
|
166
254
|
// JavaScript/TypeScript decorators
|
|
@@ -176,8 +264,61 @@ export const FRAMEWORK_AST_PATTERNS = {
|
|
|
176
264
|
'aspnet': ['[ApiController]', '[HttpGet]', '[HttpPost]', '[Route]'],
|
|
177
265
|
// Go patterns (function signatures)
|
|
178
266
|
'go-http': ['http.Handler', 'http.HandlerFunc', 'ServeHTTP'],
|
|
267
|
+
// PHP/Laravel
|
|
268
|
+
'laravel': ['Route::get', 'Route::post', 'Route::put', 'Route::delete',
|
|
269
|
+
'Route::resource', 'Route::apiResource', '#[Route('],
|
|
179
270
|
// Rust macros
|
|
180
271
|
'actix': ['#[get', '#[post', '#[put', '#[delete'],
|
|
181
272
|
'axum': ['Router::new'],
|
|
182
273
|
'rocket': ['#[get', '#[post'],
|
|
274
|
+
// Swift/iOS
|
|
275
|
+
'uikit': ['viewDidLoad', 'viewWillAppear', 'viewDidAppear', 'UIViewController'],
|
|
276
|
+
'swiftui': ['@main', 'WindowGroup', 'ContentView', '@StateObject', '@ObservedObject'],
|
|
277
|
+
'combine': ['sink', 'assign', 'Publisher', 'Subscriber'],
|
|
183
278
|
};
|
|
279
|
+
const AST_FRAMEWORK_PATTERNS_BY_LANGUAGE = {
|
|
280
|
+
javascript: [
|
|
281
|
+
{ framework: 'nestjs', entryPointMultiplier: 3.2, reason: 'nestjs-decorator', patterns: FRAMEWORK_AST_PATTERNS.nestjs },
|
|
282
|
+
],
|
|
283
|
+
typescript: [
|
|
284
|
+
{ framework: 'nestjs', entryPointMultiplier: 3.2, reason: 'nestjs-decorator', patterns: FRAMEWORK_AST_PATTERNS.nestjs },
|
|
285
|
+
],
|
|
286
|
+
python: [
|
|
287
|
+
{ framework: 'fastapi', entryPointMultiplier: 3.0, reason: 'fastapi-decorator', patterns: FRAMEWORK_AST_PATTERNS.fastapi },
|
|
288
|
+
{ framework: 'flask', entryPointMultiplier: 2.8, reason: 'flask-decorator', patterns: FRAMEWORK_AST_PATTERNS.flask },
|
|
289
|
+
],
|
|
290
|
+
java: [
|
|
291
|
+
{ framework: 'spring', entryPointMultiplier: 3.2, reason: 'spring-annotation', patterns: FRAMEWORK_AST_PATTERNS.spring },
|
|
292
|
+
{ framework: 'jaxrs', entryPointMultiplier: 3.0, reason: 'jaxrs-annotation', patterns: FRAMEWORK_AST_PATTERNS.jaxrs },
|
|
293
|
+
],
|
|
294
|
+
csharp: [
|
|
295
|
+
{ framework: 'aspnet', entryPointMultiplier: 3.2, reason: 'aspnet-attribute', patterns: FRAMEWORK_AST_PATTERNS.aspnet },
|
|
296
|
+
],
|
|
297
|
+
php: [
|
|
298
|
+
{ framework: 'laravel', entryPointMultiplier: 3.0, reason: 'php-route-attribute', patterns: FRAMEWORK_AST_PATTERNS.laravel },
|
|
299
|
+
],
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Detect framework entry points from AST definition text (decorators/annotations/attributes).
|
|
303
|
+
* Returns null if no known pattern is found.
|
|
304
|
+
*/
|
|
305
|
+
export function detectFrameworkFromAST(language, definitionText) {
|
|
306
|
+
if (!language || !definitionText)
|
|
307
|
+
return null;
|
|
308
|
+
const configs = AST_FRAMEWORK_PATTERNS_BY_LANGUAGE[language.toLowerCase()];
|
|
309
|
+
if (!configs || configs.length === 0)
|
|
310
|
+
return null;
|
|
311
|
+
const normalized = definitionText.toLowerCase();
|
|
312
|
+
for (const cfg of configs) {
|
|
313
|
+
for (const pattern of cfg.patterns) {
|
|
314
|
+
if (normalized.includes(pattern.toLowerCase())) {
|
|
315
|
+
return {
|
|
316
|
+
framework: cfg.framework,
|
|
317
|
+
entryPointMultiplier: cfg.entryPointMultiplier,
|
|
318
|
+
reason: cfg.reason,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
@@ -78,6 +78,57 @@ async function loadGoModulePath(repoRoot) {
|
|
|
78
78
|
}
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
|
+
async function loadComposerConfig(repoRoot) {
|
|
82
|
+
try {
|
|
83
|
+
const composerPath = path.join(repoRoot, 'composer.json');
|
|
84
|
+
const raw = await fs.readFile(composerPath, 'utf-8');
|
|
85
|
+
const composer = JSON.parse(raw);
|
|
86
|
+
const psr4Raw = composer.autoload?.['psr-4'] ?? {};
|
|
87
|
+
const psr4Dev = composer['autoload-dev']?.['psr-4'] ?? {};
|
|
88
|
+
const merged = { ...psr4Raw, ...psr4Dev };
|
|
89
|
+
const psr4 = new Map();
|
|
90
|
+
for (const [ns, dir] of Object.entries(merged)) {
|
|
91
|
+
const nsNorm = ns.replace(/\\+$/, '');
|
|
92
|
+
const dirNorm = dir.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
93
|
+
psr4.set(nsNorm, dirNorm);
|
|
94
|
+
}
|
|
95
|
+
if (isDev) {
|
|
96
|
+
console.log(`📦 Loaded ${psr4.size} PSR-4 mappings from composer.json`);
|
|
97
|
+
}
|
|
98
|
+
return { psr4 };
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function loadSwiftPackageConfig(repoRoot) {
|
|
105
|
+
// Swift imports are module-name based (e.g., `import SiuperModel`)
|
|
106
|
+
// SPM convention: Sources/<TargetName>/ or Package/Sources/<TargetName>/
|
|
107
|
+
// We scan for these directories to build a target map
|
|
108
|
+
const targets = new Map();
|
|
109
|
+
const sourceDirs = ['Sources', 'Package/Sources', 'src'];
|
|
110
|
+
for (const sourceDir of sourceDirs) {
|
|
111
|
+
try {
|
|
112
|
+
const fullPath = path.join(repoRoot, sourceDir);
|
|
113
|
+
const entries = await fs.readdir(fullPath, { withFileTypes: true });
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
if (entry.isDirectory()) {
|
|
116
|
+
targets.set(entry.name, sourceDir + '/' + entry.name);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Directory doesn't exist
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (targets.size > 0) {
|
|
125
|
+
if (isDev) {
|
|
126
|
+
console.log(`📦 Loaded ${targets.size} Swift package targets`);
|
|
127
|
+
}
|
|
128
|
+
return { targets };
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
81
132
|
// ============================================================================
|
|
82
133
|
// IMPORT PATH RESOLUTION
|
|
83
134
|
// ============================================================================
|
|
@@ -98,6 +149,10 @@ const EXTENSIONS = [
|
|
|
98
149
|
'.go',
|
|
99
150
|
// Rust
|
|
100
151
|
'.rs', '/mod.rs',
|
|
152
|
+
// PHP
|
|
153
|
+
'.php', '.phtml',
|
|
154
|
+
// Swift
|
|
155
|
+
'.swift',
|
|
101
156
|
];
|
|
102
157
|
/**
|
|
103
158
|
* Try to match a path (with extensions) against the known file set.
|
|
@@ -447,6 +502,39 @@ function resolveGoPackage(importPath, goModule, normalizedFileList, allFileList)
|
|
|
447
502
|
return matches;
|
|
448
503
|
}
|
|
449
504
|
// ============================================================================
|
|
505
|
+
// PHP PSR-4 IMPORT RESOLUTION
|
|
506
|
+
// ============================================================================
|
|
507
|
+
/**
|
|
508
|
+
* Resolve a PHP use-statement import path using PSR-4 mappings.
|
|
509
|
+
* e.g. "App\Http\Controllers\UserController" -> "app/Http/Controllers/UserController.php"
|
|
510
|
+
*/
|
|
511
|
+
function resolvePhpImport(importPath, composerConfig, allFiles, normalizedFileList, allFileList, index) {
|
|
512
|
+
// Normalize: replace backslashes with forward slashes
|
|
513
|
+
const normalized = importPath.replace(/\\/g, '/');
|
|
514
|
+
// Try PSR-4 resolution if composer.json was found
|
|
515
|
+
if (composerConfig) {
|
|
516
|
+
// Sort namespaces by length descending (longest match wins)
|
|
517
|
+
const sorted = [...composerConfig.psr4.entries()].sort((a, b) => b[0].length - a[0].length);
|
|
518
|
+
for (const [nsPrefix, dirPrefix] of sorted) {
|
|
519
|
+
const nsPrefixSlash = nsPrefix.replace(/\\/g, '/');
|
|
520
|
+
if (normalized.startsWith(nsPrefixSlash + '/') || normalized === nsPrefixSlash) {
|
|
521
|
+
const remainder = normalized.slice(nsPrefixSlash.length).replace(/^\//, '');
|
|
522
|
+
const filePath = dirPrefix + (remainder ? '/' + remainder : '') + '.php';
|
|
523
|
+
if (allFiles.has(filePath))
|
|
524
|
+
return filePath;
|
|
525
|
+
if (index) {
|
|
526
|
+
const result = index.getInsensitive(filePath);
|
|
527
|
+
if (result)
|
|
528
|
+
return result;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// Fallback: suffix matching (works without composer.json)
|
|
534
|
+
const pathParts = normalized.split('/').filter(Boolean);
|
|
535
|
+
return suffixResolve(pathParts, normalizedFileList, allFileList, index);
|
|
536
|
+
}
|
|
537
|
+
// ============================================================================
|
|
450
538
|
// MAIN IMPORT PROCESSOR
|
|
451
539
|
// ============================================================================
|
|
452
540
|
export const processImports = async (graph, files, astCache, importMap, onProgress, repoRoot, allPaths) => {
|
|
@@ -466,6 +554,8 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
466
554
|
const effectiveRoot = repoRoot || '';
|
|
467
555
|
const tsconfigPaths = await loadTsconfigPaths(effectiveRoot);
|
|
468
556
|
const goModule = await loadGoModulePath(effectiveRoot);
|
|
557
|
+
const composerConfig = await loadComposerConfig(effectiveRoot);
|
|
558
|
+
const swiftPackageConfig = await loadSwiftPackageConfig(effectiveRoot);
|
|
469
559
|
// Helper: add an IMPORTS edge + update import map
|
|
470
560
|
const addImportEdge = (filePath, resolvedPath) => {
|
|
471
561
|
const sourceId = generateId('File', filePath);
|
|
@@ -577,6 +667,32 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
577
667
|
}
|
|
578
668
|
// Fall through if no files found (package might be external)
|
|
579
669
|
}
|
|
670
|
+
// ---- PHP: handle namespace-based imports (use statements) ----
|
|
671
|
+
if (language === SupportedLanguages.PHP) {
|
|
672
|
+
const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
|
|
673
|
+
if (resolved) {
|
|
674
|
+
addImportEdge(file.path, resolved);
|
|
675
|
+
}
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
// ---- Swift: handle module imports ----
|
|
679
|
+
if (language === SupportedLanguages.Swift && swiftPackageConfig) {
|
|
680
|
+
// Swift imports are module names: `import SiuperModel`
|
|
681
|
+
// Resolve to the module's source directory → all .swift files in it
|
|
682
|
+
const targetDir = swiftPackageConfig.targets.get(rawImportPath);
|
|
683
|
+
if (targetDir) {
|
|
684
|
+
// Find all .swift files in this target directory
|
|
685
|
+
const dirPrefix = targetDir + '/';
|
|
686
|
+
for (const filePath2 of allFileList) {
|
|
687
|
+
if (filePath2.startsWith(dirPrefix) && filePath2.endsWith('.swift')) {
|
|
688
|
+
addImportEdge(file.path, filePath2);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
// External framework (Foundation, UIKit, etc.) — skip
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
580
696
|
// ---- Standard single-file resolution ----
|
|
581
697
|
const resolvedPath = resolveImportPath(file.path, rawImportPath, allFilePaths, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index);
|
|
582
698
|
if (resolvedPath) {
|
|
@@ -601,6 +717,8 @@ export const processImportsFromExtracted = async (graph, files, extractedImports
|
|
|
601
717
|
const effectiveRoot = repoRoot || '';
|
|
602
718
|
const tsconfigPaths = await loadTsconfigPaths(effectiveRoot);
|
|
603
719
|
const goModule = await loadGoModulePath(effectiveRoot);
|
|
720
|
+
const composerConfig = await loadComposerConfig(effectiveRoot);
|
|
721
|
+
const swiftPackageConfig = await loadSwiftPackageConfig(effectiveRoot);
|
|
604
722
|
const addImportEdge = (filePath, resolvedPath) => {
|
|
605
723
|
const sourceId = generateId('File', filePath);
|
|
606
724
|
const targetId = generateId('File', resolvedPath);
|
|
@@ -687,6 +805,28 @@ export const processImportsFromExtracted = async (graph, files, extractedImports
|
|
|
687
805
|
continue;
|
|
688
806
|
}
|
|
689
807
|
}
|
|
808
|
+
// PHP: handle namespace-based imports (use statements)
|
|
809
|
+
if (language === SupportedLanguages.PHP) {
|
|
810
|
+
const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
|
|
811
|
+
if (resolved) {
|
|
812
|
+
resolveCache.set(cacheKey, resolved);
|
|
813
|
+
addImportEdge(filePath, resolved);
|
|
814
|
+
}
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
// Swift: handle module imports
|
|
818
|
+
if (language === SupportedLanguages.Swift && swiftPackageConfig) {
|
|
819
|
+
const targetDir = swiftPackageConfig.targets.get(rawImportPath);
|
|
820
|
+
if (targetDir) {
|
|
821
|
+
const dirPrefix = targetDir + '/';
|
|
822
|
+
for (const fp of allFileList) {
|
|
823
|
+
if (fp.startsWith(dirPrefix) && fp.endsWith('.swift')) {
|
|
824
|
+
addImportEdge(filePath, fp);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
690
830
|
// Standard resolution (has its own internal cache)
|
|
691
831
|
const resolvedPath = resolveImportPath(filePath, rawImportPath, allFilePaths, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index);
|
|
692
832
|
if (resolvedPath) {
|
|
@@ -3,6 +3,38 @@ import { loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
|
|
|
3
3
|
import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
|
|
4
4
|
import { generateId } from '../../lib/utils.js';
|
|
5
5
|
import { getLanguageFromFilename, yieldToEventLoop } from './utils.js';
|
|
6
|
+
import { detectFrameworkFromAST } from './framework-detection.js';
|
|
7
|
+
const getDefinitionNodeFromCaptures = (captureMap) => {
|
|
8
|
+
const definitionKeys = [
|
|
9
|
+
'definition.function',
|
|
10
|
+
'definition.class',
|
|
11
|
+
'definition.interface',
|
|
12
|
+
'definition.method',
|
|
13
|
+
'definition.struct',
|
|
14
|
+
'definition.enum',
|
|
15
|
+
'definition.namespace',
|
|
16
|
+
'definition.module',
|
|
17
|
+
'definition.trait',
|
|
18
|
+
'definition.impl',
|
|
19
|
+
'definition.type',
|
|
20
|
+
'definition.const',
|
|
21
|
+
'definition.static',
|
|
22
|
+
'definition.typedef',
|
|
23
|
+
'definition.macro',
|
|
24
|
+
'definition.union',
|
|
25
|
+
'definition.property',
|
|
26
|
+
'definition.record',
|
|
27
|
+
'definition.delegate',
|
|
28
|
+
'definition.annotation',
|
|
29
|
+
'definition.constructor',
|
|
30
|
+
'definition.template',
|
|
31
|
+
];
|
|
32
|
+
for (const key of definitionKeys) {
|
|
33
|
+
if (captureMap[key])
|
|
34
|
+
return captureMap[key];
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
};
|
|
6
38
|
// ============================================================================
|
|
7
39
|
// EXPORT DETECTION - Language-specific visibility detection
|
|
8
40
|
// ============================================================================
|
|
@@ -94,6 +126,17 @@ const isNodeExported = (node, name, language) => {
|
|
|
94
126
|
case 'c':
|
|
95
127
|
case 'cpp':
|
|
96
128
|
return false;
|
|
129
|
+
// Swift: Check for 'public' or 'open' access modifiers
|
|
130
|
+
case 'swift':
|
|
131
|
+
while (current) {
|
|
132
|
+
if (current.type === 'modifiers' || current.type === 'visibility_modifier') {
|
|
133
|
+
const text = current.text || '';
|
|
134
|
+
if (text.includes('public') || text.includes('open'))
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
current = current.parent;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
97
140
|
default:
|
|
98
141
|
return false;
|
|
99
142
|
}
|
|
@@ -248,14 +291,24 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
|
|
|
248
291
|
const node = {
|
|
249
292
|
id: nodeId,
|
|
250
293
|
label: nodeLabel,
|
|
251
|
-
properties: {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
294
|
+
properties: (() => {
|
|
295
|
+
const definitionNode = getDefinitionNodeFromCaptures(captureMap);
|
|
296
|
+
const frameworkHint = definitionNode
|
|
297
|
+
? detectFrameworkFromAST(language, definitionNode.text || '')
|
|
298
|
+
: null;
|
|
299
|
+
return {
|
|
300
|
+
name: nodeName,
|
|
301
|
+
filePath: file.path,
|
|
302
|
+
startLine: nameNode.startPosition.row,
|
|
303
|
+
endLine: nameNode.endPosition.row,
|
|
304
|
+
language: language,
|
|
305
|
+
isExported: isNodeExported(nameNode, nodeName, language),
|
|
306
|
+
...(frameworkHint ? {
|
|
307
|
+
astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
|
|
308
|
+
astFrameworkReason: frameworkHint.reason,
|
|
309
|
+
} : {}),
|
|
310
|
+
};
|
|
311
|
+
})()
|
|
259
312
|
};
|
|
260
313
|
graph.addNode(node);
|
|
261
314
|
symbolTable.add(file.path, nodeName, nodeId, nodeLabel);
|
|
@@ -178,8 +178,14 @@ const findEntryPoints = (graph, reverseCallsEdges, callsEdges) => {
|
|
|
178
178
|
if (callees.length === 0)
|
|
179
179
|
continue;
|
|
180
180
|
// Calculate entry point score using new scoring system
|
|
181
|
-
const { score, reasons } = calculateEntryPointScore(node.properties.name, node.properties.language || 'javascript', node.properties.isExported ?? false, callers.length, callees.length, filePath // Pass filePath for framework detection
|
|
181
|
+
const { score: baseScore, reasons } = calculateEntryPointScore(node.properties.name, node.properties.language || 'javascript', node.properties.isExported ?? false, callers.length, callees.length, filePath // Pass filePath for framework detection
|
|
182
182
|
);
|
|
183
|
+
let score = baseScore;
|
|
184
|
+
const astFrameworkMultiplier = node.properties.astFrameworkMultiplier ?? 1.0;
|
|
185
|
+
if (astFrameworkMultiplier > 1.0) {
|
|
186
|
+
score *= astFrameworkMultiplier;
|
|
187
|
+
reasons.push(`framework-ast:${node.properties.astFrameworkReason || 'decorator'}`);
|
|
188
|
+
}
|
|
183
189
|
if (score > 0) {
|
|
184
190
|
entryPointCandidates.push({ id: node.id, score, reasons });
|
|
185
191
|
}
|
|
@@ -8,4 +8,6 @@ export declare const GO_QUERIES = "\n; Functions & Methods\n(function_declaratio
|
|
|
8
8
|
export declare const CPP_QUERIES = "\n; Classes, Structs, Namespaces\n(class_specifier name: (type_identifier) @name) @definition.class\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(namespace_definition name: (namespace_identifier) @name) @definition.namespace\n(enum_specifier name: (type_identifier) @name) @definition.enum\n\n; Functions & Methods\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @definition.method\n\n; Templates\n(template_declaration (class_specifier name: (type_identifier) @name)) @definition.template\n(template_declaration (function_definition declarator: (function_declarator declarator: (identifier) @name))) @definition.template\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (qualified_identifier name: (identifier) @call.name)) @call\n(call_expression function: (template_function name: (identifier) @call.name)) @call\n\n; Heritage\n(class_specifier name: (type_identifier) @heritage.class\n (base_class_clause (type_identifier) @heritage.extends)) @heritage\n(class_specifier name: (type_identifier) @heritage.class\n (base_class_clause (access_specifier) (type_identifier) @heritage.extends)) @heritage\n";
|
|
9
9
|
export declare const CSHARP_QUERIES = "\n; Types\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(struct_declaration name: (identifier) @name) @definition.struct\n(enum_declaration name: (identifier) @name) @definition.enum\n(record_declaration name: (identifier) @name) @definition.record\n(delegate_declaration name: (identifier) @name) @definition.delegate\n\n; Namespaces\n(namespace_declaration name: (identifier) @name) @definition.namespace\n(namespace_declaration name: (qualified_name) @name) @definition.namespace\n\n; Methods & Properties\n(method_declaration name: (identifier) @name) @definition.method\n(local_function_statement name: (identifier) @name) @definition.function\n(constructor_declaration name: (identifier) @name) @definition.constructor\n(property_declaration name: (identifier) @name) @definition.property\n\n; Using\n(using_directive (qualified_name) @import.source) @import\n(using_directive (identifier) @import.source) @import\n\n; Calls\n(invocation_expression function: (identifier) @call.name) @call\n(invocation_expression function: (member_access_expression name: (identifier) @call.name)) @call\n\n; Heritage\n(class_declaration name: (identifier) @heritage.class\n (base_list (simple_base_type (identifier) @heritage.extends))) @heritage\n(class_declaration name: (identifier) @heritage.class\n (base_list (simple_base_type (generic_name (identifier) @heritage.extends)))) @heritage\n";
|
|
10
10
|
export declare const RUST_QUERIES = "\n; Functions & Items\n(function_item name: (identifier) @name) @definition.function\n(struct_item name: (type_identifier) @name) @definition.struct\n(enum_item name: (type_identifier) @name) @definition.enum\n(trait_item name: (type_identifier) @name) @definition.trait\n(impl_item type: (type_identifier) @name) @definition.impl\n(mod_item name: (identifier) @name) @definition.module\n\n; Type aliases, const, static, macros\n(type_item name: (type_identifier) @name) @definition.type\n(const_item name: (identifier) @name) @definition.const\n(static_item name: (identifier) @name) @definition.static\n(macro_definition name: (identifier) @name) @definition.macro\n\n; Use statements\n(use_declaration argument: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (scoped_identifier name: (identifier) @call.name)) @call\n(call_expression function: (generic_function function: (identifier) @call.name)) @call\n\n; Heritage (trait implementation)\n(impl_item trait: (type_identifier) @heritage.trait type: (type_identifier) @heritage.class) @heritage\n(impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (type_identifier) @heritage.class) @heritage\n";
|
|
11
|
+
export declare const PHP_QUERIES = "\n; \u2500\u2500 Namespace \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(namespace_definition\n name: (namespace_name) @name) @definition.namespace\n\n; \u2500\u2500 Classes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @name) @definition.class\n\n; \u2500\u2500 Interfaces \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(interface_declaration\n name: (name) @name) @definition.interface\n\n; \u2500\u2500 Traits \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(trait_declaration\n name: (name) @name) @definition.trait\n\n; \u2500\u2500 Enums (PHP 8.1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(enum_declaration\n name: (name) @name) @definition.enum\n\n; \u2500\u2500 Top-level functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(function_definition\n name: (name) @name) @definition.function\n\n; \u2500\u2500 Methods (including constructors) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(method_declaration\n name: (name) @name) @definition.method\n\n; \u2500\u2500 Class properties (including Eloquent $fillable, $casts, etc.) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(property_declaration\n (property_element\n (variable_name\n (name) @name))) @definition.property\n\n; \u2500\u2500 Imports: use statements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; Simple: use App\\Models\\User;\n(namespace_use_declaration\n (namespace_use_clause\n (qualified_name) @import.source)) @import\n\n; \u2500\u2500 Function/method calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; Regular function call: foo()\n(function_call_expression\n function: (name) @call.name) @call\n\n; Method call: $obj->method()\n(member_call_expression\n name: (name) @call.name) @call\n\n; Nullsafe method call: $obj?->method()\n(nullsafe_member_call_expression\n name: (name) @call.name) @call\n\n; Static call: Foo::bar() (php_only uses scoped_call_expression)\n(scoped_call_expression\n name: (name) @call.name) @call\n\n; \u2500\u2500 Heritage: extends \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n (base_clause\n [(name) (qualified_name)] @heritage.extends)) @heritage\n\n; \u2500\u2500 Heritage: implements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n (class_interface_clause\n [(name) (qualified_name)] @heritage.implements)) @heritage.impl\n\n; \u2500\u2500 Heritage: use trait (must capture enclosing class name) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n body: (declaration_list\n (use_declaration\n [(name) (qualified_name)] @heritage.trait))) @heritage\n";
|
|
12
|
+
export declare const SWIFT_QUERIES = "\n; Classes\n(class_declaration \"class\" name: (type_identifier) @name) @definition.class\n\n; Structs\n(class_declaration \"struct\" name: (type_identifier) @name) @definition.struct\n\n; Enums\n(class_declaration \"enum\" name: (type_identifier) @name) @definition.enum\n\n; Extensions (mapped to class \u2014 no dedicated label in schema)\n(class_declaration \"extension\" name: (user_type (type_identifier) @name)) @definition.class\n\n; Actors\n(class_declaration \"actor\" name: (type_identifier) @name) @definition.class\n\n; Protocols (mapped to interface)\n(protocol_declaration name: (type_identifier) @name) @definition.interface\n\n; Type aliases\n(typealias_declaration name: (type_identifier) @name) @definition.type\n\n; Functions (top-level and methods)\n(function_declaration name: (simple_identifier) @name) @definition.function\n\n; Protocol method declarations\n(protocol_function_declaration name: (simple_identifier) @name) @definition.method\n\n; Initializers\n(init_declaration) @definition.constructor\n\n; Properties (stored and computed)\n(property_declaration (pattern (simple_identifier) @name)) @definition.property\n\n; Imports\n(import_declaration (identifier (simple_identifier) @import.source)) @import\n\n; Calls - direct function calls\n(call_expression (simple_identifier) @call.name) @call\n\n; Calls - member/navigation calls (obj.method())\n(call_expression (navigation_expression (navigation_suffix (simple_identifier) @call.name))) @call\n\n; Heritage - class/struct/enum inheritance and protocol conformance\n(class_declaration name: (type_identifier) @heritage.class\n (inheritance_specifier inherits_from: (user_type (type_identifier) @heritage.extends))) @heritage\n\n; Heritage - protocol inheritance\n(protocol_declaration name: (type_identifier) @heritage.class\n (inheritance_specifier inherits_from: (user_type (type_identifier) @heritage.extends))) @heritage\n";
|
|
11
13
|
export declare const LANGUAGE_QUERIES: Record<SupportedLanguages, string>;
|