@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.
Files changed (213) hide show
  1. package/README.md +215 -0
  2. package/dist/cli/ai-context.d.ts +19 -0
  3. package/dist/cli/ai-context.js +168 -0
  4. package/dist/cli/analyze.d.ts +7 -0
  5. package/dist/cli/analyze.js +325 -0
  6. package/dist/cli/augment.d.ts +7 -0
  7. package/dist/cli/augment.js +27 -0
  8. package/dist/cli/clean.d.ts +5 -0
  9. package/dist/cli/clean.js +56 -0
  10. package/dist/cli/eval-server.d.ts +25 -0
  11. package/dist/cli/eval-server.js +365 -0
  12. package/dist/cli/index.d.ts +6 -0
  13. package/dist/cli/index.js +102 -0
  14. package/dist/cli/lazy-action.d.ts +6 -0
  15. package/dist/cli/lazy-action.js +19 -0
  16. package/dist/cli/list.d.ts +2 -0
  17. package/dist/cli/list.js +27 -0
  18. package/dist/cli/mcp.d.ts +8 -0
  19. package/dist/cli/mcp.js +35 -0
  20. package/dist/cli/refresh.d.ts +12 -0
  21. package/dist/cli/refresh.js +165 -0
  22. package/dist/cli/serve.d.ts +5 -0
  23. package/dist/cli/serve.js +8 -0
  24. package/dist/cli/setup.d.ts +6 -0
  25. package/dist/cli/setup.js +218 -0
  26. package/dist/cli/status.d.ts +2 -0
  27. package/dist/cli/status.js +33 -0
  28. package/dist/cli/tool.d.ts +28 -0
  29. package/dist/cli/tool.js +87 -0
  30. package/dist/config/ignore-service.d.ts +32 -0
  31. package/dist/config/ignore-service.js +282 -0
  32. package/dist/config/supported-languages.d.ts +23 -0
  33. package/dist/config/supported-languages.js +52 -0
  34. package/dist/core/augmentation/engine.d.ts +22 -0
  35. package/dist/core/augmentation/engine.js +232 -0
  36. package/dist/core/embeddings/embedder.d.ts +35 -0
  37. package/dist/core/embeddings/embedder.js +171 -0
  38. package/dist/core/embeddings/embedding-pipeline.d.ts +41 -0
  39. package/dist/core/embeddings/embedding-pipeline.js +402 -0
  40. package/dist/core/embeddings/index.d.ts +5 -0
  41. package/dist/core/embeddings/index.js +6 -0
  42. package/dist/core/embeddings/text-generator.d.ts +20 -0
  43. package/dist/core/embeddings/text-generator.js +159 -0
  44. package/dist/core/embeddings/types.d.ts +60 -0
  45. package/dist/core/embeddings/types.js +23 -0
  46. package/dist/core/graph/graph.d.ts +4 -0
  47. package/dist/core/graph/graph.js +65 -0
  48. package/dist/core/graph/types.d.ts +69 -0
  49. package/dist/core/graph/types.js +3 -0
  50. package/dist/core/incremental/child-process.d.ts +8 -0
  51. package/dist/core/incremental/child-process.js +649 -0
  52. package/dist/core/incremental/refresh-coordinator.d.ts +32 -0
  53. package/dist/core/incremental/refresh-coordinator.js +147 -0
  54. package/dist/core/incremental/types.d.ts +78 -0
  55. package/dist/core/incremental/types.js +153 -0
  56. package/dist/core/incremental/watcher.d.ts +63 -0
  57. package/dist/core/incremental/watcher.js +338 -0
  58. package/dist/core/ingestion/ast-cache.d.ts +12 -0
  59. package/dist/core/ingestion/ast-cache.js +34 -0
  60. package/dist/core/ingestion/call-processor.d.ts +34 -0
  61. package/dist/core/ingestion/call-processor.js +937 -0
  62. package/dist/core/ingestion/call-routing.d.ts +40 -0
  63. package/dist/core/ingestion/call-routing.js +97 -0
  64. package/dist/core/ingestion/cluster-enricher.d.ts +30 -0
  65. package/dist/core/ingestion/cluster-enricher.js +151 -0
  66. package/dist/core/ingestion/community-processor.d.ts +26 -0
  67. package/dist/core/ingestion/community-processor.js +272 -0
  68. package/dist/core/ingestion/constants.d.ts +5 -0
  69. package/dist/core/ingestion/constants.js +8 -0
  70. package/dist/core/ingestion/entry-point-scoring.d.ts +23 -0
  71. package/dist/core/ingestion/entry-point-scoring.js +317 -0
  72. package/dist/core/ingestion/export-detection.d.ts +11 -0
  73. package/dist/core/ingestion/export-detection.js +203 -0
  74. package/dist/core/ingestion/filesystem-walker.d.ts +18 -0
  75. package/dist/core/ingestion/filesystem-walker.js +64 -0
  76. package/dist/core/ingestion/framework-detection.d.ts +42 -0
  77. package/dist/core/ingestion/framework-detection.js +405 -0
  78. package/dist/core/ingestion/heritage-processor.d.ts +15 -0
  79. package/dist/core/ingestion/heritage-processor.js +237 -0
  80. package/dist/core/ingestion/import-processor.d.ts +31 -0
  81. package/dist/core/ingestion/import-processor.js +416 -0
  82. package/dist/core/ingestion/language-config.d.ts +32 -0
  83. package/dist/core/ingestion/language-config.js +161 -0
  84. package/dist/core/ingestion/mro-processor.d.ts +32 -0
  85. package/dist/core/ingestion/mro-processor.js +343 -0
  86. package/dist/core/ingestion/named-binding-extraction.d.ts +51 -0
  87. package/dist/core/ingestion/named-binding-extraction.js +343 -0
  88. package/dist/core/ingestion/parsing-processor.d.ts +20 -0
  89. package/dist/core/ingestion/parsing-processor.js +282 -0
  90. package/dist/core/ingestion/pipeline.d.ts +3 -0
  91. package/dist/core/ingestion/pipeline.js +416 -0
  92. package/dist/core/ingestion/process-processor.d.ts +42 -0
  93. package/dist/core/ingestion/process-processor.js +357 -0
  94. package/dist/core/ingestion/resolution-context.d.ts +40 -0
  95. package/dist/core/ingestion/resolution-context.js +171 -0
  96. package/dist/core/ingestion/resolvers/csharp.d.ts +10 -0
  97. package/dist/core/ingestion/resolvers/csharp.js +101 -0
  98. package/dist/core/ingestion/resolvers/go.d.ts +8 -0
  99. package/dist/core/ingestion/resolvers/go.js +33 -0
  100. package/dist/core/ingestion/resolvers/index.d.ts +14 -0
  101. package/dist/core/ingestion/resolvers/index.js +10 -0
  102. package/dist/core/ingestion/resolvers/jvm.d.ts +9 -0
  103. package/dist/core/ingestion/resolvers/jvm.js +74 -0
  104. package/dist/core/ingestion/resolvers/php.d.ts +7 -0
  105. package/dist/core/ingestion/resolvers/php.js +30 -0
  106. package/dist/core/ingestion/resolvers/ruby.d.ts +9 -0
  107. package/dist/core/ingestion/resolvers/ruby.js +13 -0
  108. package/dist/core/ingestion/resolvers/rust.d.ts +5 -0
  109. package/dist/core/ingestion/resolvers/rust.js +62 -0
  110. package/dist/core/ingestion/resolvers/standard.d.ts +16 -0
  111. package/dist/core/ingestion/resolvers/standard.js +144 -0
  112. package/dist/core/ingestion/resolvers/utils.d.ts +18 -0
  113. package/dist/core/ingestion/resolvers/utils.js +113 -0
  114. package/dist/core/ingestion/structure-processor.d.ts +4 -0
  115. package/dist/core/ingestion/structure-processor.js +39 -0
  116. package/dist/core/ingestion/symbol-table.d.ts +34 -0
  117. package/dist/core/ingestion/symbol-table.js +48 -0
  118. package/dist/core/ingestion/tree-sitter-queries.d.ts +20 -0
  119. package/dist/core/ingestion/tree-sitter-queries.js +691 -0
  120. package/dist/core/ingestion/type-env.d.ts +52 -0
  121. package/dist/core/ingestion/type-env.js +349 -0
  122. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +4 -0
  123. package/dist/core/ingestion/type-extractors/c-cpp.js +214 -0
  124. package/dist/core/ingestion/type-extractors/csharp.d.ts +4 -0
  125. package/dist/core/ingestion/type-extractors/csharp.js +224 -0
  126. package/dist/core/ingestion/type-extractors/go.d.ts +4 -0
  127. package/dist/core/ingestion/type-extractors/go.js +261 -0
  128. package/dist/core/ingestion/type-extractors/index.d.ts +20 -0
  129. package/dist/core/ingestion/type-extractors/index.js +30 -0
  130. package/dist/core/ingestion/type-extractors/jvm.d.ts +5 -0
  131. package/dist/core/ingestion/type-extractors/jvm.js +386 -0
  132. package/dist/core/ingestion/type-extractors/php.d.ts +4 -0
  133. package/dist/core/ingestion/type-extractors/php.js +280 -0
  134. package/dist/core/ingestion/type-extractors/python.d.ts +4 -0
  135. package/dist/core/ingestion/type-extractors/python.js +175 -0
  136. package/dist/core/ingestion/type-extractors/ruby.d.ts +12 -0
  137. package/dist/core/ingestion/type-extractors/ruby.js +218 -0
  138. package/dist/core/ingestion/type-extractors/rust.d.ts +4 -0
  139. package/dist/core/ingestion/type-extractors/rust.js +290 -0
  140. package/dist/core/ingestion/type-extractors/shared.d.ts +81 -0
  141. package/dist/core/ingestion/type-extractors/shared.js +322 -0
  142. package/dist/core/ingestion/type-extractors/swift.d.ts +4 -0
  143. package/dist/core/ingestion/type-extractors/swift.js +140 -0
  144. package/dist/core/ingestion/type-extractors/types.d.ts +111 -0
  145. package/dist/core/ingestion/type-extractors/types.js +4 -0
  146. package/dist/core/ingestion/type-extractors/typescript.d.ts +4 -0
  147. package/dist/core/ingestion/type-extractors/typescript.js +227 -0
  148. package/dist/core/ingestion/utils.d.ts +73 -0
  149. package/dist/core/ingestion/utils.js +992 -0
  150. package/dist/core/ingestion/workers/parse-worker.d.ts +99 -0
  151. package/dist/core/ingestion/workers/parse-worker.js +1055 -0
  152. package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
  153. package/dist/core/ingestion/workers/worker-pool.js +123 -0
  154. package/dist/core/lbug/csv-generator.d.ts +28 -0
  155. package/dist/core/lbug/csv-generator.js +355 -0
  156. package/dist/core/lbug/lbug-adapter.d.ts +96 -0
  157. package/dist/core/lbug/lbug-adapter.js +753 -0
  158. package/dist/core/lbug/schema.d.ts +46 -0
  159. package/dist/core/lbug/schema.js +402 -0
  160. package/dist/core/search/bm25-index.d.ts +20 -0
  161. package/dist/core/search/bm25-index.js +123 -0
  162. package/dist/core/search/hybrid-search.d.ts +32 -0
  163. package/dist/core/search/hybrid-search.js +131 -0
  164. package/dist/core/search/query-cache.d.ts +18 -0
  165. package/dist/core/search/query-cache.js +47 -0
  166. package/dist/core/search/query-expansion.d.ts +19 -0
  167. package/dist/core/search/query-expansion.js +75 -0
  168. package/dist/core/search/reranker.d.ts +29 -0
  169. package/dist/core/search/reranker.js +122 -0
  170. package/dist/core/search/types.d.ts +154 -0
  171. package/dist/core/search/types.js +51 -0
  172. package/dist/core/semantic/tsgo-service.d.ts +67 -0
  173. package/dist/core/semantic/tsgo-service.js +355 -0
  174. package/dist/core/tree-sitter/parser-loader.d.ts +12 -0
  175. package/dist/core/tree-sitter/parser-loader.js +71 -0
  176. package/dist/lib/memory-guard.d.ts +35 -0
  177. package/dist/lib/memory-guard.js +70 -0
  178. package/dist/lib/utils.d.ts +3 -0
  179. package/dist/lib/utils.js +6 -0
  180. package/dist/mcp/compatible-stdio-transport.d.ts +32 -0
  181. package/dist/mcp/compatible-stdio-transport.js +209 -0
  182. package/dist/mcp/core/embedder.d.ts +24 -0
  183. package/dist/mcp/core/embedder.js +168 -0
  184. package/dist/mcp/core/lbug-adapter.d.ts +29 -0
  185. package/dist/mcp/core/lbug-adapter.js +330 -0
  186. package/dist/mcp/local/local-backend.d.ts +188 -0
  187. package/dist/mcp/local/local-backend.js +2759 -0
  188. package/dist/mcp/resources.d.ts +22 -0
  189. package/dist/mcp/resources.js +379 -0
  190. package/dist/mcp/server.d.ts +10 -0
  191. package/dist/mcp/server.js +217 -0
  192. package/dist/mcp/staleness.d.ts +10 -0
  193. package/dist/mcp/staleness.js +25 -0
  194. package/dist/mcp/tools.d.ts +21 -0
  195. package/dist/mcp/tools.js +202 -0
  196. package/dist/server/api.d.ts +5 -0
  197. package/dist/server/api.js +340 -0
  198. package/dist/server/mcp-http.d.ts +7 -0
  199. package/dist/server/mcp-http.js +95 -0
  200. package/dist/storage/git.d.ts +6 -0
  201. package/dist/storage/git.js +35 -0
  202. package/dist/storage/repo-manager.d.ts +87 -0
  203. package/dist/storage/repo-manager.js +249 -0
  204. package/dist/types/pipeline.d.ts +35 -0
  205. package/dist/types/pipeline.js +20 -0
  206. package/hooks/claude/code-mapper-hook.cjs +238 -0
  207. package/hooks/claude/pre-tool-use.sh +79 -0
  208. package/hooks/claude/session-start.sh +42 -0
  209. package/models/mlx-embedder.py +185 -0
  210. package/package.json +100 -0
  211. package/scripts/patch-tree-sitter-swift.cjs +74 -0
  212. package/vendor/leiden/index.cjs +355 -0
  213. 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
+ };