@zuvia-software-solutions/code-mapper 1.4.0
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 +215 -0
- package/dist/cli/ai-context.d.ts +19 -0
- package/dist/cli/ai-context.js +168 -0
- package/dist/cli/analyze.d.ts +7 -0
- package/dist/cli/analyze.js +325 -0
- package/dist/cli/augment.d.ts +7 -0
- package/dist/cli/augment.js +27 -0
- package/dist/cli/clean.d.ts +5 -0
- package/dist/cli/clean.js +56 -0
- package/dist/cli/eval-server.d.ts +25 -0
- package/dist/cli/eval-server.js +365 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +102 -0
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +19 -0
- package/dist/cli/list.d.ts +2 -0
- package/dist/cli/list.js +27 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +35 -0
- package/dist/cli/refresh.d.ts +12 -0
- package/dist/cli/refresh.js +165 -0
- package/dist/cli/serve.d.ts +5 -0
- package/dist/cli/serve.js +8 -0
- package/dist/cli/setup.d.ts +6 -0
- package/dist/cli/setup.js +218 -0
- package/dist/cli/status.d.ts +2 -0
- package/dist/cli/status.js +33 -0
- package/dist/cli/tool.d.ts +28 -0
- package/dist/cli/tool.js +87 -0
- package/dist/config/ignore-service.d.ts +32 -0
- package/dist/config/ignore-service.js +282 -0
- package/dist/config/supported-languages.d.ts +23 -0
- package/dist/config/supported-languages.js +52 -0
- package/dist/core/augmentation/engine.d.ts +22 -0
- package/dist/core/augmentation/engine.js +232 -0
- package/dist/core/embeddings/embedder.d.ts +35 -0
- package/dist/core/embeddings/embedder.js +171 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +41 -0
- package/dist/core/embeddings/embedding-pipeline.js +402 -0
- package/dist/core/embeddings/index.d.ts +5 -0
- package/dist/core/embeddings/index.js +6 -0
- package/dist/core/embeddings/text-generator.d.ts +20 -0
- package/dist/core/embeddings/text-generator.js +159 -0
- package/dist/core/embeddings/types.d.ts +60 -0
- package/dist/core/embeddings/types.js +23 -0
- package/dist/core/graph/graph.d.ts +4 -0
- package/dist/core/graph/graph.js +65 -0
- package/dist/core/graph/types.d.ts +69 -0
- package/dist/core/graph/types.js +3 -0
- package/dist/core/incremental/child-process.d.ts +8 -0
- package/dist/core/incremental/child-process.js +649 -0
- package/dist/core/incremental/refresh-coordinator.d.ts +32 -0
- package/dist/core/incremental/refresh-coordinator.js +147 -0
- package/dist/core/incremental/types.d.ts +78 -0
- package/dist/core/incremental/types.js +153 -0
- package/dist/core/incremental/watcher.d.ts +63 -0
- package/dist/core/incremental/watcher.js +338 -0
- package/dist/core/ingestion/ast-cache.d.ts +12 -0
- package/dist/core/ingestion/ast-cache.js +34 -0
- package/dist/core/ingestion/call-processor.d.ts +34 -0
- package/dist/core/ingestion/call-processor.js +937 -0
- package/dist/core/ingestion/call-routing.d.ts +40 -0
- package/dist/core/ingestion/call-routing.js +97 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +30 -0
- package/dist/core/ingestion/cluster-enricher.js +151 -0
- package/dist/core/ingestion/community-processor.d.ts +26 -0
- package/dist/core/ingestion/community-processor.js +272 -0
- package/dist/core/ingestion/constants.d.ts +5 -0
- package/dist/core/ingestion/constants.js +8 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +23 -0
- package/dist/core/ingestion/entry-point-scoring.js +317 -0
- package/dist/core/ingestion/export-detection.d.ts +11 -0
- package/dist/core/ingestion/export-detection.js +203 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +18 -0
- package/dist/core/ingestion/filesystem-walker.js +64 -0
- package/dist/core/ingestion/framework-detection.d.ts +42 -0
- package/dist/core/ingestion/framework-detection.js +405 -0
- package/dist/core/ingestion/heritage-processor.d.ts +15 -0
- package/dist/core/ingestion/heritage-processor.js +237 -0
- package/dist/core/ingestion/import-processor.d.ts +31 -0
- package/dist/core/ingestion/import-processor.js +416 -0
- package/dist/core/ingestion/language-config.d.ts +32 -0
- package/dist/core/ingestion/language-config.js +161 -0
- package/dist/core/ingestion/mro-processor.d.ts +32 -0
- package/dist/core/ingestion/mro-processor.js +343 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +51 -0
- package/dist/core/ingestion/named-binding-extraction.js +343 -0
- package/dist/core/ingestion/parsing-processor.d.ts +20 -0
- package/dist/core/ingestion/parsing-processor.js +282 -0
- package/dist/core/ingestion/pipeline.d.ts +3 -0
- package/dist/core/ingestion/pipeline.js +416 -0
- package/dist/core/ingestion/process-processor.d.ts +42 -0
- package/dist/core/ingestion/process-processor.js +357 -0
- package/dist/core/ingestion/resolution-context.d.ts +40 -0
- package/dist/core/ingestion/resolution-context.js +171 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +10 -0
- package/dist/core/ingestion/resolvers/csharp.js +101 -0
- package/dist/core/ingestion/resolvers/go.d.ts +8 -0
- package/dist/core/ingestion/resolvers/go.js +33 -0
- package/dist/core/ingestion/resolvers/index.d.ts +14 -0
- package/dist/core/ingestion/resolvers/index.js +10 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +9 -0
- package/dist/core/ingestion/resolvers/jvm.js +74 -0
- package/dist/core/ingestion/resolvers/php.d.ts +7 -0
- package/dist/core/ingestion/resolvers/php.js +30 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +9 -0
- package/dist/core/ingestion/resolvers/ruby.js +13 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +5 -0
- package/dist/core/ingestion/resolvers/rust.js +62 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +16 -0
- package/dist/core/ingestion/resolvers/standard.js +144 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +18 -0
- package/dist/core/ingestion/resolvers/utils.js +113 -0
- package/dist/core/ingestion/structure-processor.d.ts +4 -0
- package/dist/core/ingestion/structure-processor.js +39 -0
- package/dist/core/ingestion/symbol-table.d.ts +34 -0
- package/dist/core/ingestion/symbol-table.js +48 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +20 -0
- package/dist/core/ingestion/tree-sitter-queries.js +691 -0
- package/dist/core/ingestion/type-env.d.ts +52 -0
- package/dist/core/ingestion/type-env.js +349 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +214 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/csharp.js +224 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/go.js +261 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +20 -0
- package/dist/core/ingestion/type-extractors/index.js +30 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +5 -0
- package/dist/core/ingestion/type-extractors/jvm.js +386 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/php.js +280 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/python.js +175 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +12 -0
- package/dist/core/ingestion/type-extractors/ruby.js +218 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/rust.js +290 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +81 -0
- package/dist/core/ingestion/type-extractors/shared.js +322 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/swift.js +140 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +111 -0
- package/dist/core/ingestion/type-extractors/types.js +4 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/typescript.js +227 -0
- package/dist/core/ingestion/utils.d.ts +73 -0
- package/dist/core/ingestion/utils.js +992 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +99 -0
- package/dist/core/ingestion/workers/parse-worker.js +1055 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
- package/dist/core/ingestion/workers/worker-pool.js +123 -0
- package/dist/core/lbug/csv-generator.d.ts +28 -0
- package/dist/core/lbug/csv-generator.js +355 -0
- package/dist/core/lbug/lbug-adapter.d.ts +96 -0
- package/dist/core/lbug/lbug-adapter.js +753 -0
- package/dist/core/lbug/schema.d.ts +46 -0
- package/dist/core/lbug/schema.js +402 -0
- package/dist/core/search/bm25-index.d.ts +20 -0
- package/dist/core/search/bm25-index.js +123 -0
- package/dist/core/search/hybrid-search.d.ts +32 -0
- package/dist/core/search/hybrid-search.js +131 -0
- package/dist/core/search/query-cache.d.ts +18 -0
- package/dist/core/search/query-cache.js +47 -0
- package/dist/core/search/query-expansion.d.ts +19 -0
- package/dist/core/search/query-expansion.js +75 -0
- package/dist/core/search/reranker.d.ts +29 -0
- package/dist/core/search/reranker.js +122 -0
- package/dist/core/search/types.d.ts +154 -0
- package/dist/core/search/types.js +51 -0
- package/dist/core/semantic/tsgo-service.d.ts +67 -0
- package/dist/core/semantic/tsgo-service.js +355 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +12 -0
- package/dist/core/tree-sitter/parser-loader.js +71 -0
- package/dist/lib/memory-guard.d.ts +35 -0
- package/dist/lib/memory-guard.js +70 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.js +6 -0
- package/dist/mcp/compatible-stdio-transport.d.ts +32 -0
- package/dist/mcp/compatible-stdio-transport.js +209 -0
- package/dist/mcp/core/embedder.d.ts +24 -0
- package/dist/mcp/core/embedder.js +168 -0
- package/dist/mcp/core/lbug-adapter.d.ts +29 -0
- package/dist/mcp/core/lbug-adapter.js +330 -0
- package/dist/mcp/local/local-backend.d.ts +188 -0
- package/dist/mcp/local/local-backend.js +2759 -0
- package/dist/mcp/resources.d.ts +22 -0
- package/dist/mcp/resources.js +379 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +217 -0
- package/dist/mcp/staleness.d.ts +10 -0
- package/dist/mcp/staleness.js +25 -0
- package/dist/mcp/tools.d.ts +21 -0
- package/dist/mcp/tools.js +202 -0
- package/dist/server/api.d.ts +5 -0
- package/dist/server/api.js +340 -0
- package/dist/server/mcp-http.d.ts +7 -0
- package/dist/server/mcp-http.js +95 -0
- package/dist/storage/git.d.ts +6 -0
- package/dist/storage/git.js +35 -0
- package/dist/storage/repo-manager.d.ts +87 -0
- package/dist/storage/repo-manager.js +249 -0
- package/dist/types/pipeline.d.ts +35 -0
- package/dist/types/pipeline.js +20 -0
- package/hooks/claude/code-mapper-hook.cjs +238 -0
- package/hooks/claude/pre-tool-use.sh +79 -0
- package/hooks/claude/session-start.sh +42 -0
- package/models/mlx-embedder.py +185 -0
- package/package.json +100 -0
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
// code-mapper/src/core/ingestion/framework-detection.ts
|
|
2
|
+
/** @file framework-detection.ts @description Detects frameworks from file path patterns and AST definition text (decorators/annotations/attributes), returning entry point multipliers for process scoring. Returns null for unknown frameworks, yielding a 1.0 multiplier (no bonus, no penalty) */
|
|
3
|
+
/** Detect framework from file path conventions, returning an entry point multiplier or null */
|
|
4
|
+
export function detectFrameworkFromPath(filePath) {
|
|
5
|
+
// Normalize path separators and ensure leading slash for consistent matching
|
|
6
|
+
let p = filePath.toLowerCase().replace(/\\/g, '/');
|
|
7
|
+
if (!p.startsWith('/')) {
|
|
8
|
+
p = '/' + p;
|
|
9
|
+
}
|
|
10
|
+
// -- JavaScript / TypeScript --
|
|
11
|
+
// Next.js - Pages Router
|
|
12
|
+
if (p.includes('/pages/') && !p.includes('/_') && !p.includes('/api/')) {
|
|
13
|
+
if (p.endsWith('.tsx') || p.endsWith('.ts') || p.endsWith('.jsx') || p.endsWith('.js')) {
|
|
14
|
+
return { framework: 'nextjs-pages', entryPointMultiplier: 3.0, reason: 'nextjs-page' };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Next.js - App Router (page.tsx files)
|
|
18
|
+
if (p.includes('/app/') && (p.endsWith('page.tsx') || p.endsWith('page.ts') ||
|
|
19
|
+
p.endsWith('page.jsx') || p.endsWith('page.js'))) {
|
|
20
|
+
return { framework: 'nextjs-app', entryPointMultiplier: 3.0, reason: 'nextjs-app-page' };
|
|
21
|
+
}
|
|
22
|
+
// Next.js - API Routes
|
|
23
|
+
if (p.includes('/pages/api/') || (p.includes('/app/') && p.includes('/api/') && p.endsWith('route.ts'))) {
|
|
24
|
+
return { framework: 'nextjs-api', entryPointMultiplier: 3.0, reason: 'nextjs-api-route' };
|
|
25
|
+
}
|
|
26
|
+
// Next.js - Layout files (moderate boost)
|
|
27
|
+
if (p.includes('/app/') && (p.endsWith('layout.tsx') || p.endsWith('layout.ts'))) {
|
|
28
|
+
return { framework: 'nextjs-app', entryPointMultiplier: 2.0, reason: 'nextjs-layout' };
|
|
29
|
+
}
|
|
30
|
+
// Express / Node.js routes
|
|
31
|
+
if (p.includes('/routes/') && (p.endsWith('.ts') || p.endsWith('.js'))) {
|
|
32
|
+
return { framework: 'express', entryPointMultiplier: 2.5, reason: 'routes-folder' };
|
|
33
|
+
}
|
|
34
|
+
// Generic controllers (MVC pattern)
|
|
35
|
+
if (p.includes('/controllers/') && (p.endsWith('.ts') || p.endsWith('.js'))) {
|
|
36
|
+
return { framework: 'mvc', entryPointMultiplier: 2.5, reason: 'controllers-folder' };
|
|
37
|
+
}
|
|
38
|
+
// Generic handlers
|
|
39
|
+
if (p.includes('/handlers/') && (p.endsWith('.ts') || p.endsWith('.js'))) {
|
|
40
|
+
return { framework: 'handlers', entryPointMultiplier: 2.5, reason: 'handlers-folder' };
|
|
41
|
+
}
|
|
42
|
+
// React components (lower priority)
|
|
43
|
+
if ((p.includes('/components/') || p.includes('/views/')) &&
|
|
44
|
+
(p.endsWith('.tsx') || p.endsWith('.jsx'))) {
|
|
45
|
+
// Only boost PascalCase filenames (likely a component, not a util)
|
|
46
|
+
const fileName = p.split('/').pop() || '';
|
|
47
|
+
if (/^[A-Z]/.test(fileName)) {
|
|
48
|
+
return { framework: 'react', entryPointMultiplier: 1.5, reason: 'react-component' };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// -- Python --
|
|
52
|
+
// Django views
|
|
53
|
+
if (p.endsWith('views.py')) {
|
|
54
|
+
return { framework: 'django', entryPointMultiplier: 3.0, reason: 'django-views' };
|
|
55
|
+
}
|
|
56
|
+
// Django URL configs
|
|
57
|
+
if (p.endsWith('urls.py')) {
|
|
58
|
+
return { framework: 'django', entryPointMultiplier: 2.0, reason: 'django-urls' };
|
|
59
|
+
}
|
|
60
|
+
// FastAPI / Flask routers
|
|
61
|
+
if ((p.includes('/routers/') || p.includes('/endpoints/') || p.includes('/routes/')) &&
|
|
62
|
+
p.endsWith('.py')) {
|
|
63
|
+
return { framework: 'fastapi', entryPointMultiplier: 2.5, reason: 'api-routers' };
|
|
64
|
+
}
|
|
65
|
+
// Python API folder
|
|
66
|
+
if (p.includes('/api/') && p.endsWith('.py') && !p.endsWith('__init__.py')) {
|
|
67
|
+
return { framework: 'python-api', entryPointMultiplier: 2.0, reason: 'api-folder' };
|
|
68
|
+
}
|
|
69
|
+
// -- Java --
|
|
70
|
+
// Spring Boot controllers
|
|
71
|
+
if ((p.includes('/controller/') || p.includes('/controllers/')) && p.endsWith('.java')) {
|
|
72
|
+
return { framework: 'spring', entryPointMultiplier: 3.0, reason: 'spring-controller' };
|
|
73
|
+
}
|
|
74
|
+
// Spring Boot - files ending in Controller.java
|
|
75
|
+
if (p.endsWith('controller.java')) {
|
|
76
|
+
return { framework: 'spring', entryPointMultiplier: 3.0, reason: 'spring-controller-file' };
|
|
77
|
+
}
|
|
78
|
+
// Java service layer
|
|
79
|
+
if ((p.includes('/service/') || p.includes('/services/')) && p.endsWith('.java')) {
|
|
80
|
+
return { framework: 'java-service', entryPointMultiplier: 1.8, reason: 'java-service' };
|
|
81
|
+
}
|
|
82
|
+
// -- Kotlin --
|
|
83
|
+
// Spring Boot Kotlin controllers
|
|
84
|
+
if ((p.includes('/controller/') || p.includes('/controllers/')) && p.endsWith('.kt')) {
|
|
85
|
+
return { framework: 'spring-kotlin', entryPointMultiplier: 3.0, reason: 'spring-kotlin-controller' };
|
|
86
|
+
}
|
|
87
|
+
// Spring Boot - files ending in Controller.kt
|
|
88
|
+
if (p.endsWith('controller.kt')) {
|
|
89
|
+
return { framework: 'spring-kotlin', entryPointMultiplier: 3.0, reason: 'spring-kotlin-controller-file' };
|
|
90
|
+
}
|
|
91
|
+
// Ktor routes
|
|
92
|
+
if (p.includes('/routes/') && p.endsWith('.kt')) {
|
|
93
|
+
return { framework: 'ktor', entryPointMultiplier: 2.5, reason: 'ktor-routes' };
|
|
94
|
+
}
|
|
95
|
+
// Ktor plugins folder or Routing.kt files
|
|
96
|
+
if (p.includes('/plugins/') && p.endsWith('.kt')) {
|
|
97
|
+
return { framework: 'ktor', entryPointMultiplier: 2.0, reason: 'ktor-plugin' };
|
|
98
|
+
}
|
|
99
|
+
if (p.endsWith('routing.kt') || p.endsWith('routes.kt')) {
|
|
100
|
+
return { framework: 'ktor', entryPointMultiplier: 2.5, reason: 'ktor-routing-file' };
|
|
101
|
+
}
|
|
102
|
+
// Android Activities, Fragments
|
|
103
|
+
if ((p.includes('/activity/') || p.includes('/ui/')) && p.endsWith('.kt')) {
|
|
104
|
+
return { framework: 'android-kotlin', entryPointMultiplier: 2.5, reason: 'android-ui' };
|
|
105
|
+
}
|
|
106
|
+
if (p.endsWith('activity.kt') || p.endsWith('fragment.kt')) {
|
|
107
|
+
return { framework: 'android-kotlin', entryPointMultiplier: 2.5, reason: 'android-component' };
|
|
108
|
+
}
|
|
109
|
+
// Kotlin main entry point
|
|
110
|
+
if (p.endsWith('/main.kt')) {
|
|
111
|
+
return { framework: 'kotlin', entryPointMultiplier: 3.0, reason: 'kotlin-main' };
|
|
112
|
+
}
|
|
113
|
+
// Kotlin Application entry point (common naming)
|
|
114
|
+
if (p.endsWith('/application.kt')) {
|
|
115
|
+
return { framework: 'kotlin', entryPointMultiplier: 2.5, reason: 'kotlin-application' };
|
|
116
|
+
}
|
|
117
|
+
// -- C# / .NET --
|
|
118
|
+
// ASP.NET Controllers
|
|
119
|
+
if (p.includes('/controllers/') && p.endsWith('.cs')) {
|
|
120
|
+
return { framework: 'aspnet', entryPointMultiplier: 3.0, reason: 'aspnet-controller' };
|
|
121
|
+
}
|
|
122
|
+
// ASP.NET - files ending in Controller.cs
|
|
123
|
+
if (p.endsWith('controller.cs')) {
|
|
124
|
+
return { framework: 'aspnet', entryPointMultiplier: 3.0, reason: 'aspnet-controller-file' };
|
|
125
|
+
}
|
|
126
|
+
// ASP.NET Services
|
|
127
|
+
if ((p.includes('/services/') || p.includes('/service/')) && p.endsWith('.cs')) {
|
|
128
|
+
return { framework: 'aspnet', entryPointMultiplier: 1.8, reason: 'aspnet-service' };
|
|
129
|
+
}
|
|
130
|
+
// ASP.NET Middleware
|
|
131
|
+
if (p.includes('/middleware/') && p.endsWith('.cs')) {
|
|
132
|
+
return { framework: 'aspnet', entryPointMultiplier: 2.5, reason: 'aspnet-middleware' };
|
|
133
|
+
}
|
|
134
|
+
// SignalR Hubs
|
|
135
|
+
if (p.includes('/hubs/') && p.endsWith('.cs')) {
|
|
136
|
+
return { framework: 'signalr', entryPointMultiplier: 2.5, reason: 'signalr-hub' };
|
|
137
|
+
}
|
|
138
|
+
if (p.endsWith('hub.cs')) {
|
|
139
|
+
return { framework: 'signalr', entryPointMultiplier: 2.5, reason: 'signalr-hub-file' };
|
|
140
|
+
}
|
|
141
|
+
// Minimal API / Program.cs / Startup.cs
|
|
142
|
+
if (p.endsWith('/program.cs') || p.endsWith('/startup.cs')) {
|
|
143
|
+
return { framework: 'aspnet', entryPointMultiplier: 3.0, reason: 'aspnet-entry' };
|
|
144
|
+
}
|
|
145
|
+
// Background services / Hosted services
|
|
146
|
+
if ((p.includes('/backgroundservices/') || p.includes('/hostedservices/')) && p.endsWith('.cs')) {
|
|
147
|
+
return { framework: 'aspnet', entryPointMultiplier: 2.0, reason: 'aspnet-background-service' };
|
|
148
|
+
}
|
|
149
|
+
// Blazor pages
|
|
150
|
+
if (p.includes('/pages/') && p.endsWith('.razor')) {
|
|
151
|
+
return { framework: 'blazor', entryPointMultiplier: 2.5, reason: 'blazor-page' };
|
|
152
|
+
}
|
|
153
|
+
// -- Go --
|
|
154
|
+
// Go handlers
|
|
155
|
+
if ((p.includes('/handlers/') || p.includes('/handler/')) && p.endsWith('.go')) {
|
|
156
|
+
return { framework: 'go-http', entryPointMultiplier: 2.5, reason: 'go-handlers' };
|
|
157
|
+
}
|
|
158
|
+
// Go routes
|
|
159
|
+
if (p.includes('/routes/') && p.endsWith('.go')) {
|
|
160
|
+
return { framework: 'go-http', entryPointMultiplier: 2.5, reason: 'go-routes' };
|
|
161
|
+
}
|
|
162
|
+
// Go controllers
|
|
163
|
+
if (p.includes('/controllers/') && p.endsWith('.go')) {
|
|
164
|
+
return { framework: 'go-mvc', entryPointMultiplier: 2.5, reason: 'go-controller' };
|
|
165
|
+
}
|
|
166
|
+
// Go main.go files
|
|
167
|
+
if (p.endsWith('/main.go') || p.endsWith('/cmd/') && p.endsWith('.go')) {
|
|
168
|
+
return { framework: 'go', entryPointMultiplier: 3.0, reason: 'go-main' };
|
|
169
|
+
}
|
|
170
|
+
// -- Rust --
|
|
171
|
+
// Rust handlers/routes
|
|
172
|
+
if ((p.includes('/handlers/') || p.includes('/routes/')) && p.endsWith('.rs')) {
|
|
173
|
+
return { framework: 'rust-web', entryPointMultiplier: 2.5, reason: 'rust-handlers' };
|
|
174
|
+
}
|
|
175
|
+
// Rust main.rs
|
|
176
|
+
if (p.endsWith('/main.rs')) {
|
|
177
|
+
return { framework: 'rust', entryPointMultiplier: 3.0, reason: 'rust-main' };
|
|
178
|
+
}
|
|
179
|
+
// Rust bin folder (executables)
|
|
180
|
+
if (p.includes('/bin/') && p.endsWith('.rs')) {
|
|
181
|
+
return { framework: 'rust', entryPointMultiplier: 2.5, reason: 'rust-bin' };
|
|
182
|
+
}
|
|
183
|
+
// -- C / C++ --
|
|
184
|
+
// C/C++ main files
|
|
185
|
+
if (p.endsWith('/main.c') || p.endsWith('/main.cpp') || p.endsWith('/main.cc')) {
|
|
186
|
+
return { framework: 'c-cpp', entryPointMultiplier: 3.0, reason: 'c-main' };
|
|
187
|
+
}
|
|
188
|
+
// C/C++ src folder entry points (if named specifically)
|
|
189
|
+
if ((p.includes('/src/') && (p.endsWith('/app.c') || p.endsWith('/app.cpp')))) {
|
|
190
|
+
return { framework: 'c-cpp', entryPointMultiplier: 2.5, reason: 'c-app' };
|
|
191
|
+
}
|
|
192
|
+
// -- PHP / Laravel --
|
|
193
|
+
// Laravel routes (highest priority)
|
|
194
|
+
if (p.includes('/routes/') && p.endsWith('.php')) {
|
|
195
|
+
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-routes' };
|
|
196
|
+
}
|
|
197
|
+
// Laravel controllers
|
|
198
|
+
if ((p.includes('/http/controllers/') || p.includes('/controllers/')) && p.endsWith('.php')) {
|
|
199
|
+
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller' };
|
|
200
|
+
}
|
|
201
|
+
// Laravel controller by file name convention
|
|
202
|
+
if (p.endsWith('controller.php')) {
|
|
203
|
+
return { framework: 'laravel', entryPointMultiplier: 3.0, reason: 'laravel-controller-file' };
|
|
204
|
+
}
|
|
205
|
+
// Laravel console commands
|
|
206
|
+
if ((p.includes('/console/commands/') || p.includes('/commands/')) && p.endsWith('.php')) {
|
|
207
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-command' };
|
|
208
|
+
}
|
|
209
|
+
// Laravel jobs
|
|
210
|
+
if (p.includes('/jobs/') && p.endsWith('.php')) {
|
|
211
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-job' };
|
|
212
|
+
}
|
|
213
|
+
// Laravel listeners
|
|
214
|
+
if (p.includes('/listeners/') && p.endsWith('.php')) {
|
|
215
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-listener' };
|
|
216
|
+
}
|
|
217
|
+
// Laravel middleware
|
|
218
|
+
if (p.includes('/http/middleware/') && p.endsWith('.php')) {
|
|
219
|
+
return { framework: 'laravel', entryPointMultiplier: 2.5, reason: 'laravel-middleware' };
|
|
220
|
+
}
|
|
221
|
+
// Laravel service providers
|
|
222
|
+
if (p.includes('/providers/') && p.endsWith('.php')) {
|
|
223
|
+
return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-provider' };
|
|
224
|
+
}
|
|
225
|
+
// Laravel policies
|
|
226
|
+
if (p.includes('/policies/') && p.endsWith('.php')) {
|
|
227
|
+
return { framework: 'laravel', entryPointMultiplier: 2.0, reason: 'laravel-policy' };
|
|
228
|
+
}
|
|
229
|
+
// Laravel models
|
|
230
|
+
if (p.includes('/models/') && p.endsWith('.php')) {
|
|
231
|
+
return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-model' };
|
|
232
|
+
}
|
|
233
|
+
// Laravel services
|
|
234
|
+
if (p.includes('/services/') && p.endsWith('.php')) {
|
|
235
|
+
return { framework: 'laravel', entryPointMultiplier: 1.8, reason: 'laravel-service' };
|
|
236
|
+
}
|
|
237
|
+
// Laravel repositories
|
|
238
|
+
if (p.includes('/repositories/') && p.endsWith('.php')) {
|
|
239
|
+
return { framework: 'laravel', entryPointMultiplier: 1.5, reason: 'laravel-repository' };
|
|
240
|
+
}
|
|
241
|
+
// -- Ruby --
|
|
242
|
+
// Ruby: bin/ or exe/ (CLI entry points)
|
|
243
|
+
if ((p.includes('/bin/') || p.includes('/exe/')) && p.endsWith('.rb')) {
|
|
244
|
+
return { framework: 'ruby', entryPointMultiplier: 2.5, reason: 'ruby-executable' };
|
|
245
|
+
}
|
|
246
|
+
// Ruby: Rakefile or *.rake (task definitions)
|
|
247
|
+
if (p.endsWith('/rakefile') || p.endsWith('.rake')) {
|
|
248
|
+
return { framework: 'ruby', entryPointMultiplier: 1.5, reason: 'ruby-rake' };
|
|
249
|
+
}
|
|
250
|
+
// -- Swift / iOS --
|
|
251
|
+
// iOS App entry points
|
|
252
|
+
if (p.endsWith('/appdelegate.swift') || p.endsWith('/scenedelegate.swift') || p.endsWith('/app.swift')) {
|
|
253
|
+
return { framework: 'ios', entryPointMultiplier: 3.0, reason: 'ios-app-entry' };
|
|
254
|
+
}
|
|
255
|
+
// SwiftUI App entry (@main)
|
|
256
|
+
if (p.endsWith('app.swift') && p.includes('/sources/')) {
|
|
257
|
+
return { framework: 'swiftui', entryPointMultiplier: 3.0, reason: 'swiftui-app' };
|
|
258
|
+
}
|
|
259
|
+
// UIKit ViewControllers
|
|
260
|
+
if ((p.includes('/viewcontrollers/') || p.includes('/controllers/') || p.includes('/screens/')) && p.endsWith('.swift')) {
|
|
261
|
+
return { framework: 'uikit', entryPointMultiplier: 2.5, reason: 'uikit-viewcontroller' };
|
|
262
|
+
}
|
|
263
|
+
// ViewController by filename convention
|
|
264
|
+
if (p.endsWith('viewcontroller.swift') || p.endsWith('vc.swift')) {
|
|
265
|
+
return { framework: 'uikit', entryPointMultiplier: 2.5, reason: 'uikit-viewcontroller-file' };
|
|
266
|
+
}
|
|
267
|
+
// Coordinator pattern
|
|
268
|
+
if (p.includes('/coordinators/') && p.endsWith('.swift')) {
|
|
269
|
+
return { framework: 'ios-coordinator', entryPointMultiplier: 2.5, reason: 'ios-coordinator' };
|
|
270
|
+
}
|
|
271
|
+
// Coordinator by filename
|
|
272
|
+
if (p.endsWith('coordinator.swift')) {
|
|
273
|
+
return { framework: 'ios-coordinator', entryPointMultiplier: 2.5, reason: 'ios-coordinator-file' };
|
|
274
|
+
}
|
|
275
|
+
// SwiftUI Views
|
|
276
|
+
if ((p.includes('/views/') || p.includes('/scenes/')) && p.endsWith('.swift')) {
|
|
277
|
+
return { framework: 'swiftui', entryPointMultiplier: 1.8, reason: 'swiftui-view' };
|
|
278
|
+
}
|
|
279
|
+
// Service layer
|
|
280
|
+
if (p.includes('/services/') && p.endsWith('.swift')) {
|
|
281
|
+
return { framework: 'ios-service', entryPointMultiplier: 1.8, reason: 'ios-service' };
|
|
282
|
+
}
|
|
283
|
+
// Router / navigation
|
|
284
|
+
if (p.includes('/router/') && p.endsWith('.swift')) {
|
|
285
|
+
return { framework: 'ios-router', entryPointMultiplier: 2.0, reason: 'ios-router' };
|
|
286
|
+
}
|
|
287
|
+
// -- Generic patterns --
|
|
288
|
+
// Any language: index files in API folders
|
|
289
|
+
if (p.includes('/api/') && (p.endsWith('/index.ts') || p.endsWith('/index.js') ||
|
|
290
|
+
p.endsWith('/__init__.py'))) {
|
|
291
|
+
return { framework: 'api', entryPointMultiplier: 1.8, reason: 'api-index' };
|
|
292
|
+
}
|
|
293
|
+
// No framework detected — null yields 1.0 multiplier
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
// Patterns matched against AST node text (class/method/function declaration text)
|
|
297
|
+
export const FRAMEWORK_AST_PATTERNS = {
|
|
298
|
+
// JavaScript/TypeScript decorators
|
|
299
|
+
'nestjs': ['@Controller', '@Get', '@Post', '@Put', '@Delete', '@Patch'],
|
|
300
|
+
'express': ['app.get', 'app.post', 'app.put', 'app.delete', 'router.get', 'router.post'],
|
|
301
|
+
// Python decorators
|
|
302
|
+
'fastapi': ['@app.get', '@app.post', '@app.put', '@app.delete', '@router.get'],
|
|
303
|
+
'flask': ['@app.route', '@blueprint.route'],
|
|
304
|
+
// Java annotations
|
|
305
|
+
'spring': ['@RestController', '@Controller', '@GetMapping', '@PostMapping', '@RequestMapping'],
|
|
306
|
+
'jaxrs': ['@Path', '@GET', '@POST', '@PUT', '@DELETE'],
|
|
307
|
+
// C# attributes
|
|
308
|
+
'aspnet': ['[ApiController]', '[HttpGet]', '[HttpPost]', '[HttpPut]', '[HttpDelete]',
|
|
309
|
+
'[Route]', '[Authorize]', '[AllowAnonymous]'],
|
|
310
|
+
'signalr': ['[HubMethodName]', ': Hub', ': Hub<'],
|
|
311
|
+
'blazor': ['@page', '[Parameter]', '@inject'],
|
|
312
|
+
'efcore': ['DbContext', 'DbSet<', 'OnModelCreating'],
|
|
313
|
+
// Go patterns (function signatures)
|
|
314
|
+
'go-http': ['http.Handler', 'http.HandlerFunc', 'ServeHTTP'],
|
|
315
|
+
// PHP/Laravel
|
|
316
|
+
'laravel': ['Route::get', 'Route::post', 'Route::put', 'Route::delete',
|
|
317
|
+
'Route::resource', 'Route::apiResource', '#[Route('],
|
|
318
|
+
// Rust macros
|
|
319
|
+
'actix': ['#[get', '#[post', '#[put', '#[delete'],
|
|
320
|
+
'axum': ['Router::new'],
|
|
321
|
+
'rocket': ['#[get', '#[post'],
|
|
322
|
+
// Swift/iOS
|
|
323
|
+
'uikit': ['viewDidLoad', 'viewWillAppear', 'viewDidAppear', 'UIViewController'],
|
|
324
|
+
'swiftui': ['@main', 'WindowGroup', 'ContentView', '@StateObject', '@ObservedObject'],
|
|
325
|
+
'combine': ['sink', 'assign', 'Publisher', 'Subscriber'],
|
|
326
|
+
// DI framework decorators/annotations (entryPointMultiplier 1.0 — not entry points, just DI metadata)
|
|
327
|
+
'nestjs-di': ['@Injectable'],
|
|
328
|
+
'angular-di': ['@Injectable', '@NgModule'],
|
|
329
|
+
'spring-di': ['@Service', '@Component', '@Repository', '@Bean', '@Configuration', '@Autowired'],
|
|
330
|
+
'dagger-di': ['@Inject', '@Provides', '@Module'],
|
|
331
|
+
'aspnet-di': ['AddScoped', 'AddTransient', 'AddSingleton'],
|
|
332
|
+
'python-di': ['@inject', '@provider'],
|
|
333
|
+
};
|
|
334
|
+
import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
335
|
+
const AST_FRAMEWORK_PATTERNS_BY_LANGUAGE = {
|
|
336
|
+
[SupportedLanguages.JavaScript]: [
|
|
337
|
+
{ framework: 'nestjs', entryPointMultiplier: 3.2, reason: 'nestjs-decorator', patterns: FRAMEWORK_AST_PATTERNS.nestjs },
|
|
338
|
+
{ framework: 'nestjs-di', entryPointMultiplier: 1.0, reason: 'nestjs-di-injectable', patterns: FRAMEWORK_AST_PATTERNS['nestjs-di'] },
|
|
339
|
+
],
|
|
340
|
+
[SupportedLanguages.TypeScript]: [
|
|
341
|
+
{ framework: 'nestjs', entryPointMultiplier: 3.2, reason: 'nestjs-decorator', patterns: FRAMEWORK_AST_PATTERNS.nestjs },
|
|
342
|
+
{ framework: 'nestjs-di', entryPointMultiplier: 1.0, reason: 'nestjs-di-injectable', patterns: FRAMEWORK_AST_PATTERNS['nestjs-di'] },
|
|
343
|
+
{ framework: 'angular-di', entryPointMultiplier: 1.0, reason: 'angular-di-injectable', patterns: FRAMEWORK_AST_PATTERNS['angular-di'] },
|
|
344
|
+
],
|
|
345
|
+
[SupportedLanguages.Python]: [
|
|
346
|
+
{ framework: 'fastapi', entryPointMultiplier: 3.0, reason: 'fastapi-decorator', patterns: FRAMEWORK_AST_PATTERNS.fastapi },
|
|
347
|
+
{ framework: 'flask', entryPointMultiplier: 2.8, reason: 'flask-decorator', patterns: FRAMEWORK_AST_PATTERNS.flask },
|
|
348
|
+
{ framework: 'python-di', entryPointMultiplier: 1.0, reason: 'python-di-decorator', patterns: FRAMEWORK_AST_PATTERNS['python-di'] },
|
|
349
|
+
],
|
|
350
|
+
[SupportedLanguages.Java]: [
|
|
351
|
+
{ framework: 'spring', entryPointMultiplier: 3.2, reason: 'spring-annotation', patterns: FRAMEWORK_AST_PATTERNS.spring },
|
|
352
|
+
{ framework: 'jaxrs', entryPointMultiplier: 3.0, reason: 'jaxrs-annotation', patterns: FRAMEWORK_AST_PATTERNS.jaxrs },
|
|
353
|
+
{ framework: 'spring-di', entryPointMultiplier: 1.0, reason: 'spring-di-bean', patterns: FRAMEWORK_AST_PATTERNS['spring-di'] },
|
|
354
|
+
{ framework: 'dagger-di', entryPointMultiplier: 1.0, reason: 'dagger-di-provides', patterns: FRAMEWORK_AST_PATTERNS['dagger-di'] },
|
|
355
|
+
],
|
|
356
|
+
[SupportedLanguages.Kotlin]: [
|
|
357
|
+
{ framework: 'spring-kotlin', entryPointMultiplier: 3.2, reason: 'spring-kotlin-annotation', patterns: FRAMEWORK_AST_PATTERNS.spring },
|
|
358
|
+
{ framework: 'jaxrs', entryPointMultiplier: 3.0, reason: 'jaxrs-annotation', patterns: FRAMEWORK_AST_PATTERNS.jaxrs },
|
|
359
|
+
{ framework: 'ktor', entryPointMultiplier: 2.8, reason: 'ktor-routing', patterns: ['routing', 'embeddedServer', 'Application.module'] },
|
|
360
|
+
{ framework: 'android-kotlin', entryPointMultiplier: 2.5, reason: 'android-annotation', patterns: ['@AndroidEntryPoint', 'AppCompatActivity', 'Fragment('] },
|
|
361
|
+
{ framework: 'spring-di', entryPointMultiplier: 1.0, reason: 'spring-di-bean', patterns: FRAMEWORK_AST_PATTERNS['spring-di'] },
|
|
362
|
+
{ framework: 'dagger-di', entryPointMultiplier: 1.0, reason: 'dagger-di-provides', patterns: FRAMEWORK_AST_PATTERNS['dagger-di'] },
|
|
363
|
+
],
|
|
364
|
+
[SupportedLanguages.CSharp]: [
|
|
365
|
+
{ framework: 'aspnet', entryPointMultiplier: 3.2, reason: 'aspnet-attribute', patterns: FRAMEWORK_AST_PATTERNS.aspnet },
|
|
366
|
+
{ framework: 'signalr', entryPointMultiplier: 2.8, reason: 'signalr-attribute', patterns: FRAMEWORK_AST_PATTERNS.signalr },
|
|
367
|
+
{ framework: 'blazor', entryPointMultiplier: 2.5, reason: 'blazor-attribute', patterns: FRAMEWORK_AST_PATTERNS.blazor },
|
|
368
|
+
{ framework: 'efcore', entryPointMultiplier: 2.0, reason: 'efcore-pattern', patterns: FRAMEWORK_AST_PATTERNS.efcore },
|
|
369
|
+
{ framework: 'aspnet-di', entryPointMultiplier: 1.0, reason: 'aspnet-di-registration', patterns: FRAMEWORK_AST_PATTERNS['aspnet-di'] },
|
|
370
|
+
],
|
|
371
|
+
[SupportedLanguages.PHP]: [
|
|
372
|
+
{ framework: 'laravel', entryPointMultiplier: 3.0, reason: 'php-route-attribute', patterns: FRAMEWORK_AST_PATTERNS.laravel },
|
|
373
|
+
],
|
|
374
|
+
};
|
|
375
|
+
// Pre-lowercased patterns for O(1) matching at runtime
|
|
376
|
+
const AST_PATTERNS_LOWERED = Object.fromEntries(Object.entries(AST_FRAMEWORK_PATTERNS_BY_LANGUAGE).map(([lang, cfgs]) => [
|
|
377
|
+
lang,
|
|
378
|
+
cfgs.map(cfg => ({ ...cfg, patterns: cfg.patterns.map(p => p.toLowerCase()) })),
|
|
379
|
+
]));
|
|
380
|
+
/**
|
|
381
|
+
* Detect framework entry points from AST definition text (decorators/annotations/attributes)
|
|
382
|
+
* @param language - Source language
|
|
383
|
+
* @param definitionText - AST node text (callers should slice to ~300 chars)
|
|
384
|
+
* @returns FrameworkHint or null if no known pattern matches
|
|
385
|
+
*/
|
|
386
|
+
export function detectFrameworkFromAST(language, definitionText) {
|
|
387
|
+
if (!language || !definitionText)
|
|
388
|
+
return null;
|
|
389
|
+
const configs = AST_PATTERNS_LOWERED[language.toLowerCase()];
|
|
390
|
+
if (!configs || configs.length === 0)
|
|
391
|
+
return null;
|
|
392
|
+
const normalized = definitionText.toLowerCase();
|
|
393
|
+
for (const cfg of configs) {
|
|
394
|
+
for (const pattern of cfg.patterns) {
|
|
395
|
+
if (normalized.includes(pattern)) {
|
|
396
|
+
return {
|
|
397
|
+
framework: cfg.framework,
|
|
398
|
+
entryPointMultiplier: cfg.entryPointMultiplier,
|
|
399
|
+
reason: cfg.reason,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @file heritage-processor.ts @description Extracts class inheritance relationships (EXTENDS/IMPLEMENTS) from AST matches. Resolves the correct edge type via symbol table lookup, falling back to language-gated heuristics for unresolved symbols: C#/Java use I[A-Z] convention, Swift defaults to IMPLEMENTS, all others default to EXTENDS */
|
|
2
|
+
import { KnowledgeGraph } from '../graph/types.js';
|
|
3
|
+
import { ASTCache } from './ast-cache.js';
|
|
4
|
+
import type { ExtractedHeritage } from './workers/parse-worker.js';
|
|
5
|
+
import type { ResolutionContext } from './resolution-context.js';
|
|
6
|
+
export declare const processHeritage: (graph: KnowledgeGraph, files: {
|
|
7
|
+
path: string;
|
|
8
|
+
content: string;
|
|
9
|
+
}[], astCache: ASTCache, ctx: ResolutionContext, onProgress?: (current: number, total: number) => void) => Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Fast path: resolve pre-extracted heritage from workers
|
|
12
|
+
*
|
|
13
|
+
* No AST parsing needed — workers already extracted className, parentName, and kind
|
|
14
|
+
*/
|
|
15
|
+
export declare const processHeritageFromExtracted: (graph: KnowledgeGraph, extractedHeritage: ExtractedHeritage[], ctx: ResolutionContext, onProgress?: (current: number, total: number) => void) => Promise<void>;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// code-mapper/src/core/ingestion/heritage-processor.ts
|
|
2
|
+
/** @file heritage-processor.ts @description Extracts class inheritance relationships (EXTENDS/IMPLEMENTS) from AST matches. Resolves the correct edge type via symbol table lookup, falling back to language-gated heuristics for unresolved symbols: C#/Java use I[A-Z] convention, Swift defaults to IMPLEMENTS, all others default to EXTENDS */
|
|
3
|
+
import Parser from 'tree-sitter';
|
|
4
|
+
import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
|
|
5
|
+
import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
|
|
6
|
+
import { generateId } from '../../lib/utils.js';
|
|
7
|
+
import { getLanguageFromFilename, isVerboseIngestionEnabled, yieldToEventLoop } from './utils.js';
|
|
8
|
+
import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
9
|
+
import { getTreeSitterBufferSize } from './constants.js';
|
|
10
|
+
// C#/Java convention: interfaces start with I followed by an uppercase letter
|
|
11
|
+
const INTERFACE_NAME_RE = /^I[A-Z]/;
|
|
12
|
+
/**
|
|
13
|
+
* Determine whether a heritage.extends capture is actually IMPLEMENTS
|
|
14
|
+
*
|
|
15
|
+
* Uses symbol table first (authoritative); falls back to language-gated heuristics
|
|
16
|
+
* for unresolved symbols (C#/Java: I[A-Z], Swift: default IMPLEMENTS, others: EXTENDS)
|
|
17
|
+
*/
|
|
18
|
+
const resolveExtendsType = (parentName, currentFilePath, ctx, language) => {
|
|
19
|
+
const resolved = ctx.resolve(parentName, currentFilePath);
|
|
20
|
+
if (resolved && resolved.candidates.length > 0) {
|
|
21
|
+
const isInterface = resolved.candidates[0].type === 'Interface';
|
|
22
|
+
return isInterface
|
|
23
|
+
? { type: 'IMPLEMENTS', idPrefix: 'Interface' }
|
|
24
|
+
: { type: 'EXTENDS', idPrefix: 'Class' };
|
|
25
|
+
}
|
|
26
|
+
// Unresolved symbol — fall back to language-specific heuristic
|
|
27
|
+
if (language === SupportedLanguages.CSharp || language === SupportedLanguages.Java) {
|
|
28
|
+
if (INTERFACE_NAME_RE.test(parentName)) {
|
|
29
|
+
return { type: 'IMPLEMENTS', idPrefix: 'Interface' };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (language === SupportedLanguages.Swift) {
|
|
33
|
+
// Protocol conformance is more common than class inheritance in Swift
|
|
34
|
+
return { type: 'IMPLEMENTS', idPrefix: 'Interface' };
|
|
35
|
+
}
|
|
36
|
+
return { type: 'EXTENDS', idPrefix: 'Class' };
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a symbol ID for heritage, falling back to a generated ID
|
|
40
|
+
*
|
|
41
|
+
* Tries ctx.resolve() first, then falls back to generateId()
|
|
42
|
+
*/
|
|
43
|
+
const resolveHeritageId = (name, filePath, ctx, fallbackLabel, fallbackKey) => {
|
|
44
|
+
const resolved = ctx.resolve(name, filePath);
|
|
45
|
+
if (resolved && resolved.candidates.length > 0) {
|
|
46
|
+
// Refuse ambiguous global matches — a wrong edge is worse than no edge
|
|
47
|
+
if (resolved.tier === 'global' && resolved.candidates.length > 1) {
|
|
48
|
+
return generateId(fallbackLabel, fallbackKey ?? name);
|
|
49
|
+
}
|
|
50
|
+
return resolved.candidates[0].nodeId;
|
|
51
|
+
}
|
|
52
|
+
return generateId(fallbackLabel, fallbackKey ?? name);
|
|
53
|
+
};
|
|
54
|
+
export const processHeritage = async (graph, files, astCache, ctx, onProgress) => {
|
|
55
|
+
const parser = await loadParser();
|
|
56
|
+
const logSkipped = isVerboseIngestionEnabled();
|
|
57
|
+
const skippedByLang = logSkipped ? new Map() : null;
|
|
58
|
+
for (let i = 0; i < files.length; i++) {
|
|
59
|
+
const file = files[i];
|
|
60
|
+
onProgress?.(i + 1, files.length);
|
|
61
|
+
if (i % 20 === 0)
|
|
62
|
+
await yieldToEventLoop();
|
|
63
|
+
// Check language support
|
|
64
|
+
const language = getLanguageFromFilename(file.path);
|
|
65
|
+
if (!language)
|
|
66
|
+
continue;
|
|
67
|
+
if (!isLanguageAvailable(language)) {
|
|
68
|
+
if (skippedByLang) {
|
|
69
|
+
skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
|
|
70
|
+
}
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const queryStr = LANGUAGE_QUERIES[language];
|
|
74
|
+
if (!queryStr)
|
|
75
|
+
continue;
|
|
76
|
+
// Load the language
|
|
77
|
+
await loadLanguage(language, file.path);
|
|
78
|
+
// Get AST
|
|
79
|
+
let tree = astCache.get(file.path);
|
|
80
|
+
if (!tree) {
|
|
81
|
+
try {
|
|
82
|
+
tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
|
|
83
|
+
}
|
|
84
|
+
catch (parseError) {
|
|
85
|
+
// Skip files that can't be parsed
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// Cache re-parsed tree for future phases
|
|
89
|
+
astCache.set(file.path, tree);
|
|
90
|
+
}
|
|
91
|
+
let query;
|
|
92
|
+
let matches;
|
|
93
|
+
try {
|
|
94
|
+
const language = parser.getLanguage();
|
|
95
|
+
query = new Parser.Query(language, queryStr);
|
|
96
|
+
matches = query.matches(tree.rootNode);
|
|
97
|
+
}
|
|
98
|
+
catch (queryError) {
|
|
99
|
+
console.warn(`Heritage query error for ${file.path}:`, queryError);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// Process heritage matches
|
|
103
|
+
matches.forEach(match => {
|
|
104
|
+
const captureMap = {};
|
|
105
|
+
match.captures.forEach(c => {
|
|
106
|
+
captureMap[c.name] = c.node;
|
|
107
|
+
});
|
|
108
|
+
// EXTENDS or IMPLEMENTS: resolve via symbol table for languages
|
|
109
|
+
// where tree-sitter can't distinguish classes from interfaces (C#, Java)
|
|
110
|
+
if (captureMap['heritage.class'] && captureMap['heritage.extends']) {
|
|
111
|
+
// Go struct embedding: skip named fields, only anonymous fields are embedded
|
|
112
|
+
const extendsNode = captureMap['heritage.extends'];
|
|
113
|
+
const fieldDecl = extendsNode.parent;
|
|
114
|
+
if (fieldDecl?.type === 'field_declaration' && fieldDecl.childForFieldName('name')) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const className = captureMap['heritage.class'].text;
|
|
118
|
+
const parentClassName = captureMap['heritage.extends'].text;
|
|
119
|
+
const { type: relType, idPrefix } = resolveExtendsType(parentClassName, file.path, ctx, language);
|
|
120
|
+
const childId = resolveHeritageId(className, file.path, ctx, 'Class', `${file.path}:${className}`);
|
|
121
|
+
const parentId = resolveHeritageId(parentClassName, file.path, ctx, idPrefix);
|
|
122
|
+
if (childId && parentId && childId !== parentId) {
|
|
123
|
+
graph.addRelationship({
|
|
124
|
+
id: generateId(relType, `${childId}->${parentId}`),
|
|
125
|
+
sourceId: childId,
|
|
126
|
+
targetId: parentId,
|
|
127
|
+
type: relType,
|
|
128
|
+
confidence: 1.0,
|
|
129
|
+
reason: '',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// IMPLEMENTS: Class implements Interface (TypeScript only)
|
|
134
|
+
if (captureMap['heritage.class'] && captureMap['heritage.implements']) {
|
|
135
|
+
const className = captureMap['heritage.class'].text;
|
|
136
|
+
const interfaceName = captureMap['heritage.implements'].text;
|
|
137
|
+
const classId = resolveHeritageId(className, file.path, ctx, 'Class', `${file.path}:${className}`);
|
|
138
|
+
const interfaceId = resolveHeritageId(interfaceName, file.path, ctx, 'Interface');
|
|
139
|
+
if (classId && interfaceId) {
|
|
140
|
+
graph.addRelationship({
|
|
141
|
+
id: generateId('IMPLEMENTS', `${classId}->${interfaceId}`),
|
|
142
|
+
sourceId: classId,
|
|
143
|
+
targetId: interfaceId,
|
|
144
|
+
type: 'IMPLEMENTS',
|
|
145
|
+
confidence: 1.0,
|
|
146
|
+
reason: '',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// IMPLEMENTS (Rust): impl Trait for Struct
|
|
151
|
+
if (captureMap['heritage.trait'] && captureMap['heritage.class']) {
|
|
152
|
+
const structName = captureMap['heritage.class'].text;
|
|
153
|
+
const traitName = captureMap['heritage.trait'].text;
|
|
154
|
+
const structId = resolveHeritageId(structName, file.path, ctx, 'Struct', `${file.path}:${structName}`);
|
|
155
|
+
const traitId = resolveHeritageId(traitName, file.path, ctx, 'Trait');
|
|
156
|
+
if (structId && traitId) {
|
|
157
|
+
graph.addRelationship({
|
|
158
|
+
id: generateId('IMPLEMENTS', `${structId}->${traitId}`),
|
|
159
|
+
sourceId: structId,
|
|
160
|
+
targetId: traitId,
|
|
161
|
+
type: 'IMPLEMENTS',
|
|
162
|
+
confidence: 1.0,
|
|
163
|
+
reason: 'trait-impl',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
// Tree owned by the LRU cache — no manual delete needed
|
|
169
|
+
}
|
|
170
|
+
if (skippedByLang && skippedByLang.size > 0) {
|
|
171
|
+
for (const [lang, count] of skippedByLang.entries()) {
|
|
172
|
+
console.warn(`[ingestion] Skipped ${count} ${lang} file(s) in heritage processing — ${lang} parser not available.`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Fast path: resolve pre-extracted heritage from workers
|
|
178
|
+
*
|
|
179
|
+
* No AST parsing needed — workers already extracted className, parentName, and kind
|
|
180
|
+
*/
|
|
181
|
+
export const processHeritageFromExtracted = async (graph, extractedHeritage, ctx, onProgress) => {
|
|
182
|
+
const total = extractedHeritage.length;
|
|
183
|
+
for (let i = 0; i < extractedHeritage.length; i++) {
|
|
184
|
+
if (i % 500 === 0) {
|
|
185
|
+
onProgress?.(i, total);
|
|
186
|
+
await yieldToEventLoop();
|
|
187
|
+
}
|
|
188
|
+
const h = extractedHeritage[i];
|
|
189
|
+
if (h.kind === 'extends') {
|
|
190
|
+
const fileLanguage = getLanguageFromFilename(h.filePath);
|
|
191
|
+
if (!fileLanguage)
|
|
192
|
+
continue;
|
|
193
|
+
const { type: relType, idPrefix } = resolveExtendsType(h.parentName, h.filePath, ctx, fileLanguage);
|
|
194
|
+
const childId = resolveHeritageId(h.className, h.filePath, ctx, 'Class', `${h.filePath}:${h.className}`);
|
|
195
|
+
const parentId = resolveHeritageId(h.parentName, h.filePath, ctx, idPrefix);
|
|
196
|
+
if (childId && parentId && childId !== parentId) {
|
|
197
|
+
graph.addRelationship({
|
|
198
|
+
id: generateId(relType, `${childId}->${parentId}`),
|
|
199
|
+
sourceId: childId,
|
|
200
|
+
targetId: parentId,
|
|
201
|
+
type: relType,
|
|
202
|
+
confidence: 1.0,
|
|
203
|
+
reason: '',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (h.kind === 'implements') {
|
|
208
|
+
const classId = resolveHeritageId(h.className, h.filePath, ctx, 'Class', `${h.filePath}:${h.className}`);
|
|
209
|
+
const interfaceId = resolveHeritageId(h.parentName, h.filePath, ctx, 'Interface');
|
|
210
|
+
if (classId && interfaceId) {
|
|
211
|
+
graph.addRelationship({
|
|
212
|
+
id: generateId('IMPLEMENTS', `${classId}->${interfaceId}`),
|
|
213
|
+
sourceId: classId,
|
|
214
|
+
targetId: interfaceId,
|
|
215
|
+
type: 'IMPLEMENTS',
|
|
216
|
+
confidence: 1.0,
|
|
217
|
+
reason: '',
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else if (h.kind === 'trait-impl' || h.kind === 'include' || h.kind === 'extend' || h.kind === 'prepend') {
|
|
222
|
+
const structId = resolveHeritageId(h.className, h.filePath, ctx, 'Struct', `${h.filePath}:${h.className}`);
|
|
223
|
+
const traitId = resolveHeritageId(h.parentName, h.filePath, ctx, 'Trait');
|
|
224
|
+
if (structId && traitId) {
|
|
225
|
+
graph.addRelationship({
|
|
226
|
+
id: generateId('IMPLEMENTS', `${structId}->${traitId}:${h.kind}`),
|
|
227
|
+
sourceId: structId,
|
|
228
|
+
targetId: traitId,
|
|
229
|
+
type: 'IMPLEMENTS',
|
|
230
|
+
confidence: 1.0,
|
|
231
|
+
reason: h.kind,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
onProgress?.(total, total);
|
|
237
|
+
};
|