@duytransipher/gitnexus 1.4.6-sipher.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 (224) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +261 -0
  3. package/dist/cli/ai-context.d.ts +23 -0
  4. package/dist/cli/ai-context.js +265 -0
  5. package/dist/cli/analyze.d.ts +12 -0
  6. package/dist/cli/analyze.js +345 -0
  7. package/dist/cli/augment.d.ts +13 -0
  8. package/dist/cli/augment.js +33 -0
  9. package/dist/cli/clean.d.ts +10 -0
  10. package/dist/cli/clean.js +60 -0
  11. package/dist/cli/eval-server.d.ts +37 -0
  12. package/dist/cli/eval-server.js +389 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +137 -0
  15. package/dist/cli/lazy-action.d.ts +6 -0
  16. package/dist/cli/lazy-action.js +18 -0
  17. package/dist/cli/list.d.ts +6 -0
  18. package/dist/cli/list.js +30 -0
  19. package/dist/cli/mcp.d.ts +8 -0
  20. package/dist/cli/mcp.js +36 -0
  21. package/dist/cli/serve.d.ts +4 -0
  22. package/dist/cli/serve.js +6 -0
  23. package/dist/cli/setup.d.ts +8 -0
  24. package/dist/cli/setup.js +367 -0
  25. package/dist/cli/sipher-patched.d.ts +2 -0
  26. package/dist/cli/sipher-patched.js +77 -0
  27. package/dist/cli/skill-gen.d.ts +26 -0
  28. package/dist/cli/skill-gen.js +549 -0
  29. package/dist/cli/status.d.ts +6 -0
  30. package/dist/cli/status.js +36 -0
  31. package/dist/cli/tool.d.ts +60 -0
  32. package/dist/cli/tool.js +180 -0
  33. package/dist/cli/wiki.d.ts +15 -0
  34. package/dist/cli/wiki.js +365 -0
  35. package/dist/config/ignore-service.d.ts +26 -0
  36. package/dist/config/ignore-service.js +284 -0
  37. package/dist/config/supported-languages.d.ts +15 -0
  38. package/dist/config/supported-languages.js +16 -0
  39. package/dist/core/augmentation/engine.d.ts +26 -0
  40. package/dist/core/augmentation/engine.js +240 -0
  41. package/dist/core/embeddings/embedder.d.ts +60 -0
  42. package/dist/core/embeddings/embedder.js +251 -0
  43. package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
  44. package/dist/core/embeddings/embedding-pipeline.js +356 -0
  45. package/dist/core/embeddings/index.d.ts +9 -0
  46. package/dist/core/embeddings/index.js +9 -0
  47. package/dist/core/embeddings/text-generator.d.ts +24 -0
  48. package/dist/core/embeddings/text-generator.js +182 -0
  49. package/dist/core/embeddings/types.d.ts +87 -0
  50. package/dist/core/embeddings/types.js +32 -0
  51. package/dist/core/graph/graph.d.ts +2 -0
  52. package/dist/core/graph/graph.js +66 -0
  53. package/dist/core/graph/types.d.ts +66 -0
  54. package/dist/core/graph/types.js +1 -0
  55. package/dist/core/ingestion/ast-cache.d.ts +11 -0
  56. package/dist/core/ingestion/ast-cache.js +35 -0
  57. package/dist/core/ingestion/call-processor.d.ts +23 -0
  58. package/dist/core/ingestion/call-processor.js +793 -0
  59. package/dist/core/ingestion/call-routing.d.ts +68 -0
  60. package/dist/core/ingestion/call-routing.js +129 -0
  61. package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
  62. package/dist/core/ingestion/cluster-enricher.js +170 -0
  63. package/dist/core/ingestion/community-processor.d.ts +39 -0
  64. package/dist/core/ingestion/community-processor.js +312 -0
  65. package/dist/core/ingestion/constants.d.ts +16 -0
  66. package/dist/core/ingestion/constants.js +16 -0
  67. package/dist/core/ingestion/entry-point-scoring.d.ts +40 -0
  68. package/dist/core/ingestion/entry-point-scoring.js +353 -0
  69. package/dist/core/ingestion/export-detection.d.ts +18 -0
  70. package/dist/core/ingestion/export-detection.js +231 -0
  71. package/dist/core/ingestion/filesystem-walker.d.ts +28 -0
  72. package/dist/core/ingestion/filesystem-walker.js +81 -0
  73. package/dist/core/ingestion/framework-detection.d.ts +54 -0
  74. package/dist/core/ingestion/framework-detection.js +411 -0
  75. package/dist/core/ingestion/heritage-processor.d.ts +28 -0
  76. package/dist/core/ingestion/heritage-processor.js +251 -0
  77. package/dist/core/ingestion/import-processor.d.ts +34 -0
  78. package/dist/core/ingestion/import-processor.js +398 -0
  79. package/dist/core/ingestion/language-config.d.ts +46 -0
  80. package/dist/core/ingestion/language-config.js +167 -0
  81. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  82. package/dist/core/ingestion/mro-processor.js +369 -0
  83. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  84. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  85. package/dist/core/ingestion/parsing-processor.d.ts +19 -0
  86. package/dist/core/ingestion/parsing-processor.js +315 -0
  87. package/dist/core/ingestion/pipeline.d.ts +6 -0
  88. package/dist/core/ingestion/pipeline.js +401 -0
  89. package/dist/core/ingestion/process-processor.d.ts +51 -0
  90. package/dist/core/ingestion/process-processor.js +315 -0
  91. package/dist/core/ingestion/resolution-context.d.ts +53 -0
  92. package/dist/core/ingestion/resolution-context.js +132 -0
  93. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  94. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  95. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  96. package/dist/core/ingestion/resolvers/go.js +42 -0
  97. package/dist/core/ingestion/resolvers/index.d.ts +18 -0
  98. package/dist/core/ingestion/resolvers/index.js +13 -0
  99. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  100. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  101. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  102. package/dist/core/ingestion/resolvers/php.js +35 -0
  103. package/dist/core/ingestion/resolvers/python.d.ts +19 -0
  104. package/dist/core/ingestion/resolvers/python.js +52 -0
  105. package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
  106. package/dist/core/ingestion/resolvers/ruby.js +15 -0
  107. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  108. package/dist/core/ingestion/resolvers/rust.js +73 -0
  109. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  110. package/dist/core/ingestion/resolvers/standard.js +123 -0
  111. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  112. package/dist/core/ingestion/resolvers/utils.js +122 -0
  113. package/dist/core/ingestion/structure-processor.d.ts +2 -0
  114. package/dist/core/ingestion/structure-processor.js +36 -0
  115. package/dist/core/ingestion/symbol-table.d.ts +63 -0
  116. package/dist/core/ingestion/symbol-table.js +85 -0
  117. package/dist/core/ingestion/tree-sitter-queries.d.ts +15 -0
  118. package/dist/core/ingestion/tree-sitter-queries.js +888 -0
  119. package/dist/core/ingestion/type-env.d.ts +49 -0
  120. package/dist/core/ingestion/type-env.js +613 -0
  121. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  122. package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
  123. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  124. package/dist/core/ingestion/type-extractors/csharp.js +383 -0
  125. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  126. package/dist/core/ingestion/type-extractors/go.js +467 -0
  127. package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
  128. package/dist/core/ingestion/type-extractors/index.js +31 -0
  129. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  130. package/dist/core/ingestion/type-extractors/jvm.js +681 -0
  131. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  132. package/dist/core/ingestion/type-extractors/php.js +549 -0
  133. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  134. package/dist/core/ingestion/type-extractors/python.js +455 -0
  135. package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
  136. package/dist/core/ingestion/type-extractors/ruby.js +389 -0
  137. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  138. package/dist/core/ingestion/type-extractors/rust.js +456 -0
  139. package/dist/core/ingestion/type-extractors/shared.d.ts +145 -0
  140. package/dist/core/ingestion/type-extractors/shared.js +810 -0
  141. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  142. package/dist/core/ingestion/type-extractors/swift.js +137 -0
  143. package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
  144. package/dist/core/ingestion/type-extractors/types.js +1 -0
  145. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  146. package/dist/core/ingestion/type-extractors/typescript.js +494 -0
  147. package/dist/core/ingestion/utils.d.ts +138 -0
  148. package/dist/core/ingestion/utils.js +1290 -0
  149. package/dist/core/ingestion/workers/parse-worker.d.ts +122 -0
  150. package/dist/core/ingestion/workers/parse-worker.js +1126 -0
  151. package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
  152. package/dist/core/ingestion/workers/worker-pool.js +128 -0
  153. package/dist/core/lbug/csv-generator.d.ts +33 -0
  154. package/dist/core/lbug/csv-generator.js +366 -0
  155. package/dist/core/lbug/lbug-adapter.d.ts +103 -0
  156. package/dist/core/lbug/lbug-adapter.js +769 -0
  157. package/dist/core/lbug/schema.d.ts +53 -0
  158. package/dist/core/lbug/schema.js +430 -0
  159. package/dist/core/search/bm25-index.d.ts +23 -0
  160. package/dist/core/search/bm25-index.js +96 -0
  161. package/dist/core/search/hybrid-search.d.ts +49 -0
  162. package/dist/core/search/hybrid-search.js +118 -0
  163. package/dist/core/tree-sitter/parser-loader.d.ts +5 -0
  164. package/dist/core/tree-sitter/parser-loader.js +63 -0
  165. package/dist/core/wiki/generator.d.ts +120 -0
  166. package/dist/core/wiki/generator.js +939 -0
  167. package/dist/core/wiki/graph-queries.d.ts +80 -0
  168. package/dist/core/wiki/graph-queries.js +238 -0
  169. package/dist/core/wiki/html-viewer.d.ts +10 -0
  170. package/dist/core/wiki/html-viewer.js +297 -0
  171. package/dist/core/wiki/llm-client.d.ts +43 -0
  172. package/dist/core/wiki/llm-client.js +186 -0
  173. package/dist/core/wiki/prompts.d.ts +53 -0
  174. package/dist/core/wiki/prompts.js +174 -0
  175. package/dist/lib/utils.d.ts +1 -0
  176. package/dist/lib/utils.js +3 -0
  177. package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
  178. package/dist/mcp/compatible-stdio-transport.js +200 -0
  179. package/dist/mcp/core/embedder.d.ts +27 -0
  180. package/dist/mcp/core/embedder.js +108 -0
  181. package/dist/mcp/core/lbug-adapter.d.ts +57 -0
  182. package/dist/mcp/core/lbug-adapter.js +455 -0
  183. package/dist/mcp/local/local-backend.d.ts +181 -0
  184. package/dist/mcp/local/local-backend.js +1722 -0
  185. package/dist/mcp/resources.d.ts +31 -0
  186. package/dist/mcp/resources.js +411 -0
  187. package/dist/mcp/server.d.ts +23 -0
  188. package/dist/mcp/server.js +296 -0
  189. package/dist/mcp/staleness.d.ts +15 -0
  190. package/dist/mcp/staleness.js +29 -0
  191. package/dist/mcp/tools.d.ts +24 -0
  192. package/dist/mcp/tools.js +292 -0
  193. package/dist/server/api.d.ts +10 -0
  194. package/dist/server/api.js +344 -0
  195. package/dist/server/mcp-http.d.ts +13 -0
  196. package/dist/server/mcp-http.js +100 -0
  197. package/dist/storage/git.d.ts +6 -0
  198. package/dist/storage/git.js +35 -0
  199. package/dist/storage/repo-manager.d.ts +138 -0
  200. package/dist/storage/repo-manager.js +299 -0
  201. package/dist/types/pipeline.d.ts +32 -0
  202. package/dist/types/pipeline.js +18 -0
  203. package/dist/unreal/bridge.d.ts +4 -0
  204. package/dist/unreal/bridge.js +113 -0
  205. package/dist/unreal/config.d.ts +6 -0
  206. package/dist/unreal/config.js +55 -0
  207. package/dist/unreal/types.d.ts +105 -0
  208. package/dist/unreal/types.js +1 -0
  209. package/hooks/claude/gitnexus-hook.cjs +238 -0
  210. package/hooks/claude/pre-tool-use.sh +79 -0
  211. package/hooks/claude/session-start.sh +42 -0
  212. package/package.json +100 -0
  213. package/scripts/ensure-cli-executable.cjs +21 -0
  214. package/scripts/patch-tree-sitter-swift.cjs +74 -0
  215. package/scripts/setup-unreal-gitnexus.ps1 +191 -0
  216. package/skills/gitnexus-cli.md +82 -0
  217. package/skills/gitnexus-debugging.md +89 -0
  218. package/skills/gitnexus-exploring.md +78 -0
  219. package/skills/gitnexus-guide.md +64 -0
  220. package/skills/gitnexus-impact-analysis.md +97 -0
  221. package/skills/gitnexus-pr-review.md +163 -0
  222. package/skills/gitnexus-refactoring.md +121 -0
  223. package/vendor/leiden/index.cjs +355 -0
  224. package/vendor/leiden/utils.cjs +392 -0
@@ -0,0 +1,1126 @@
1
+ import { parentPort } from 'node:worker_threads';
2
+ import Parser from 'tree-sitter';
3
+ import JavaScript from 'tree-sitter-javascript';
4
+ import TypeScript from 'tree-sitter-typescript';
5
+ import Python from 'tree-sitter-python';
6
+ import Java from 'tree-sitter-java';
7
+ import C from 'tree-sitter-c';
8
+ import CPP from 'tree-sitter-cpp';
9
+ import CSharp from 'tree-sitter-c-sharp';
10
+ import Go from 'tree-sitter-go';
11
+ import Rust from 'tree-sitter-rust';
12
+ import PHP from 'tree-sitter-php';
13
+ import Ruby from 'tree-sitter-ruby';
14
+ import { createRequire } from 'node:module';
15
+ import { SupportedLanguages } from '../../../config/supported-languages.js';
16
+ import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js';
17
+ import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from '../constants.js';
18
+ // tree-sitter-swift is an optionalDependency — may not be installed
19
+ const _require = createRequire(import.meta.url);
20
+ let Swift = null;
21
+ try {
22
+ Swift = _require('tree-sitter-swift');
23
+ }
24
+ catch { }
25
+ // tree-sitter-kotlin is an optionalDependency — may not be installed
26
+ let Kotlin = null;
27
+ try {
28
+ Kotlin = _require('tree-sitter-kotlin');
29
+ }
30
+ catch { }
31
+ import { getLanguageFromFilename, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, getDefinitionNodeFromCaptures, findEnclosingClassId, extractMethodSignature, countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, extractMixedChain, } from '../utils.js';
32
+ import { buildTypeEnv } from '../type-env.js';
33
+ import { isNodeExported } from '../export-detection.js';
34
+ import { detectFrameworkFromAST } from '../framework-detection.js';
35
+ import { typeConfigs } from '../type-extractors/index.js';
36
+ import { generateId } from '../../../lib/utils.js';
37
+ import { extractNamedBindings } from '../named-binding-extraction.js';
38
+ import { appendKotlinWildcard } from '../resolvers/index.js';
39
+ import { callRouters } from '../call-routing.js';
40
+ import { extractPropertyDeclaredType } from '../type-extractors/shared.js';
41
+ // ============================================================================
42
+ // Worker-local parser + language map
43
+ // ============================================================================
44
+ const parser = new Parser();
45
+ const languageMap = {
46
+ [SupportedLanguages.JavaScript]: JavaScript,
47
+ [SupportedLanguages.TypeScript]: TypeScript.typescript,
48
+ [`${SupportedLanguages.TypeScript}:tsx`]: TypeScript.tsx,
49
+ [SupportedLanguages.Python]: Python,
50
+ [SupportedLanguages.Java]: Java,
51
+ [SupportedLanguages.C]: C,
52
+ [SupportedLanguages.CPlusPlus]: CPP,
53
+ [SupportedLanguages.CSharp]: CSharp,
54
+ [SupportedLanguages.Go]: Go,
55
+ [SupportedLanguages.Rust]: Rust,
56
+ ...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
57
+ [SupportedLanguages.PHP]: PHP.php_only,
58
+ [SupportedLanguages.Ruby]: Ruby,
59
+ ...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
60
+ };
61
+ /**
62
+ * Check if a language grammar is available in this worker.
63
+ * Duplicated from parser-loader.ts because workers can't import from the main thread.
64
+ * Extra filePath parameter needed to distinguish .tsx from .ts (different grammars
65
+ * under the same SupportedLanguages.TypeScript key).
66
+ */
67
+ const isLanguageAvailable = (language, filePath) => {
68
+ const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
69
+ ? `${language}:tsx`
70
+ : language;
71
+ return key in languageMap && languageMap[key] != null;
72
+ };
73
+ const setLanguage = (language, filePath) => {
74
+ const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
75
+ ? `${language}:tsx`
76
+ : language;
77
+ const lang = languageMap[key];
78
+ if (!lang)
79
+ throw new Error(`Unsupported language: ${language}`);
80
+ parser.setLanguage(lang);
81
+ };
82
+ // isNodeExported imported from ../export-detection.js (shared module)
83
+ // ============================================================================
84
+ // Enclosing function detection (for call extraction)
85
+ // ============================================================================
86
+ /** Walk up AST to find enclosing function, return its generateId or null for top-level */
87
+ const findEnclosingFunctionId = (node, filePath) => {
88
+ let current = node.parent;
89
+ while (current) {
90
+ if (FUNCTION_NODE_TYPES.has(current.type)) {
91
+ const { funcName, label } = extractFunctionName(current);
92
+ if (funcName) {
93
+ return generateId(label, `${filePath}:${funcName}`);
94
+ }
95
+ }
96
+ current = current.parent;
97
+ }
98
+ return null;
99
+ };
100
+ // ============================================================================
101
+ // Label detection from capture map
102
+ // ============================================================================
103
+ const getLabelFromCaptures = (captureMap) => {
104
+ // Skip imports (handled separately) and calls
105
+ if (captureMap['import'] || captureMap['call'])
106
+ return null;
107
+ if (!captureMap['name'])
108
+ return null;
109
+ if (captureMap['definition.function'])
110
+ return 'Function';
111
+ if (captureMap['definition.class'])
112
+ return 'Class';
113
+ if (captureMap['definition.interface'])
114
+ return 'Interface';
115
+ if (captureMap['definition.method'])
116
+ return 'Method';
117
+ if (captureMap['definition.struct'])
118
+ return 'Struct';
119
+ if (captureMap['definition.enum'])
120
+ return 'Enum';
121
+ if (captureMap['definition.namespace'])
122
+ return 'Namespace';
123
+ if (captureMap['definition.module'])
124
+ return 'Module';
125
+ if (captureMap['definition.trait'])
126
+ return 'Trait';
127
+ if (captureMap['definition.impl'])
128
+ return 'Impl';
129
+ if (captureMap['definition.type'])
130
+ return 'TypeAlias';
131
+ if (captureMap['definition.const'])
132
+ return 'Const';
133
+ if (captureMap['definition.static'])
134
+ return 'Static';
135
+ if (captureMap['definition.typedef'])
136
+ return 'Typedef';
137
+ if (captureMap['definition.macro'])
138
+ return 'Macro';
139
+ if (captureMap['definition.union'])
140
+ return 'Union';
141
+ if (captureMap['definition.property'])
142
+ return 'Property';
143
+ if (captureMap['definition.record'])
144
+ return 'Record';
145
+ if (captureMap['definition.delegate'])
146
+ return 'Delegate';
147
+ if (captureMap['definition.annotation'])
148
+ return 'Annotation';
149
+ if (captureMap['definition.constructor'])
150
+ return 'Constructor';
151
+ if (captureMap['definition.template'])
152
+ return 'Template';
153
+ return 'CodeElement';
154
+ };
155
+ // DEFINITION_CAPTURE_KEYS and getDefinitionNodeFromCaptures imported from ../utils.js
156
+ // ============================================================================
157
+ // Process a batch of files
158
+ // ============================================================================
159
+ const processBatch = (files, onProgress) => {
160
+ const result = {
161
+ nodes: [],
162
+ relationships: [],
163
+ symbols: [],
164
+ imports: [],
165
+ calls: [],
166
+ assignments: [],
167
+ heritage: [],
168
+ routes: [],
169
+ constructorBindings: [],
170
+ skippedLanguages: {},
171
+ fileCount: 0,
172
+ };
173
+ // Group by language to minimize setLanguage calls
174
+ const byLanguage = new Map();
175
+ for (const file of files) {
176
+ const lang = getLanguageFromFilename(file.path);
177
+ if (!lang)
178
+ continue;
179
+ let list = byLanguage.get(lang);
180
+ if (!list) {
181
+ list = [];
182
+ byLanguage.set(lang, list);
183
+ }
184
+ list.push(file);
185
+ }
186
+ let totalProcessed = 0;
187
+ let lastReported = 0;
188
+ const PROGRESS_INTERVAL = 100; // report every 100 files
189
+ const onFileProcessed = onProgress ? () => {
190
+ totalProcessed++;
191
+ if (totalProcessed - lastReported >= PROGRESS_INTERVAL) {
192
+ lastReported = totalProcessed;
193
+ onProgress(totalProcessed);
194
+ }
195
+ } : undefined;
196
+ for (const [language, langFiles] of byLanguage) {
197
+ const queryString = LANGUAGE_QUERIES[language];
198
+ if (!queryString)
199
+ continue;
200
+ // Track if we need to handle tsx separately
201
+ const tsxFiles = [];
202
+ const regularFiles = [];
203
+ if (language === SupportedLanguages.TypeScript) {
204
+ for (const f of langFiles) {
205
+ if (f.path.endsWith('.tsx')) {
206
+ tsxFiles.push(f);
207
+ }
208
+ else {
209
+ regularFiles.push(f);
210
+ }
211
+ }
212
+ }
213
+ else {
214
+ regularFiles.push(...langFiles);
215
+ }
216
+ // Process regular files for this language
217
+ if (regularFiles.length > 0) {
218
+ if (isLanguageAvailable(language, regularFiles[0].path)) {
219
+ try {
220
+ setLanguage(language, regularFiles[0].path);
221
+ processFileGroup(regularFiles, language, queryString, result, onFileProcessed);
222
+ }
223
+ catch {
224
+ // parser unavailable — skip this language group
225
+ }
226
+ }
227
+ else {
228
+ result.skippedLanguages[language] = (result.skippedLanguages[language] || 0) + regularFiles.length;
229
+ }
230
+ }
231
+ // Process tsx files separately (different grammar)
232
+ if (tsxFiles.length > 0) {
233
+ if (isLanguageAvailable(language, tsxFiles[0].path)) {
234
+ try {
235
+ setLanguage(language, tsxFiles[0].path);
236
+ processFileGroup(tsxFiles, language, queryString, result, onFileProcessed);
237
+ }
238
+ catch {
239
+ // parser unavailable — skip this language group
240
+ }
241
+ }
242
+ else {
243
+ result.skippedLanguages[language] = (result.skippedLanguages[language] || 0) + tsxFiles.length;
244
+ }
245
+ }
246
+ }
247
+ return result;
248
+ };
249
+ // ============================================================================
250
+ // PHP Eloquent metadata extraction
251
+ // ============================================================================
252
+ /** Eloquent model properties whose array values are worth indexing */
253
+ const ELOQUENT_ARRAY_PROPS = new Set(['fillable', 'casts', 'hidden', 'guarded', 'with', 'appends']);
254
+ /** Eloquent relationship method names */
255
+ const ELOQUENT_RELATIONS = new Set([
256
+ 'hasMany', 'hasOne', 'belongsTo', 'belongsToMany',
257
+ 'morphTo', 'morphMany', 'morphOne', 'morphToMany', 'morphedByMany',
258
+ 'hasManyThrough', 'hasOneThrough',
259
+ ]);
260
+ function findDescendant(node, type) {
261
+ if (node.type === type)
262
+ return node;
263
+ for (const child of (node.children ?? [])) {
264
+ const found = findDescendant(child, type);
265
+ if (found)
266
+ return found;
267
+ }
268
+ return null;
269
+ }
270
+ function extractStringContent(node) {
271
+ if (!node)
272
+ return null;
273
+ const content = node.children?.find((c) => c.type === 'string_content');
274
+ if (content)
275
+ return content.text;
276
+ if (node.type === 'string_content')
277
+ return node.text;
278
+ return null;
279
+ }
280
+ /**
281
+ * For a PHP property_declaration node, extract array values as a description string.
282
+ * Returns null if not an Eloquent model property or no array values found.
283
+ */
284
+ function extractPhpPropertyDescription(propName, propDeclNode) {
285
+ if (!ELOQUENT_ARRAY_PROPS.has(propName))
286
+ return null;
287
+ const arrayNode = findDescendant(propDeclNode, 'array_creation_expression');
288
+ if (!arrayNode)
289
+ return null;
290
+ const items = [];
291
+ for (const child of (arrayNode.children ?? [])) {
292
+ if (child.type !== 'array_element_initializer')
293
+ continue;
294
+ const children = child.children ?? [];
295
+ const arrowIdx = children.findIndex((c) => c.type === '=>');
296
+ if (arrowIdx !== -1) {
297
+ // key => value pair (used in $casts)
298
+ const key = extractStringContent(children[arrowIdx - 1]);
299
+ const val = extractStringContent(children[arrowIdx + 1]);
300
+ if (key && val)
301
+ items.push(`${key}:${val}`);
302
+ }
303
+ else {
304
+ // Simple value (used in $fillable, $hidden, etc.)
305
+ const val = extractStringContent(children[0]);
306
+ if (val)
307
+ items.push(val);
308
+ }
309
+ }
310
+ return items.length > 0 ? items.join(', ') : null;
311
+ }
312
+ /**
313
+ * For a PHP method_declaration node, detect if it defines an Eloquent relationship.
314
+ * Returns description like "hasMany(Post)" or null.
315
+ */
316
+ function extractEloquentRelationDescription(methodNode) {
317
+ function findRelationCall(node) {
318
+ if (node.type === 'member_call_expression') {
319
+ const children = node.children ?? [];
320
+ const objectNode = children.find((c) => c.type === 'variable_name' && c.text === '$this');
321
+ const nameNode = children.find((c) => c.type === 'name');
322
+ if (objectNode && nameNode && ELOQUENT_RELATIONS.has(nameNode.text))
323
+ return node;
324
+ }
325
+ for (const child of (node.children ?? [])) {
326
+ const found = findRelationCall(child);
327
+ if (found)
328
+ return found;
329
+ }
330
+ return null;
331
+ }
332
+ const callNode = findRelationCall(methodNode);
333
+ if (!callNode)
334
+ return null;
335
+ const relType = callNode.children?.find((c) => c.type === 'name')?.text;
336
+ const argsNode = callNode.children?.find((c) => c.type === 'arguments');
337
+ let targetModel = null;
338
+ if (argsNode) {
339
+ const firstArg = argsNode.children?.find((c) => c.type === 'argument');
340
+ if (firstArg) {
341
+ const classConstant = firstArg.children?.find((c) => c.type === 'class_constant_access_expression');
342
+ if (classConstant) {
343
+ targetModel = classConstant.children?.find((c) => c.type === 'name')?.text ?? null;
344
+ }
345
+ }
346
+ }
347
+ if (relType && targetModel)
348
+ return `${relType}(${targetModel})`;
349
+ if (relType)
350
+ return relType;
351
+ return null;
352
+ }
353
+ const ROUTE_HTTP_METHODS = new Set([
354
+ 'get', 'post', 'put', 'patch', 'delete', 'options', 'any', 'match',
355
+ ]);
356
+ const ROUTE_RESOURCE_METHODS = new Set(['resource', 'apiResource']);
357
+ const RESOURCE_ACTIONS = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy'];
358
+ const API_RESOURCE_ACTIONS = ['index', 'store', 'show', 'update', 'destroy'];
359
+ /** Check if node is a scoped_call_expression with object 'Route' */
360
+ function isRouteStaticCall(node) {
361
+ if (node.type !== 'scoped_call_expression')
362
+ return false;
363
+ const obj = node.childForFieldName?.('object') ?? node.children?.[0];
364
+ return obj?.text === 'Route';
365
+ }
366
+ /** Get the method name from a scoped_call_expression or member_call_expression */
367
+ function getCallMethodName(node) {
368
+ const nameNode = node.childForFieldName?.('name') ??
369
+ node.children?.find((c) => c.type === 'name');
370
+ return nameNode?.text ?? null;
371
+ }
372
+ /** Get the arguments node from a call expression */
373
+ function getArguments(node) {
374
+ return node.children?.find((c) => c.type === 'arguments') ?? null;
375
+ }
376
+ /** Find the closure body inside arguments */
377
+ function findClosureBody(argsNode) {
378
+ if (!argsNode)
379
+ return null;
380
+ for (const child of argsNode.children ?? []) {
381
+ if (child.type === 'argument') {
382
+ for (const inner of child.children ?? []) {
383
+ if (inner.type === 'anonymous_function' ||
384
+ inner.type === 'arrow_function') {
385
+ return inner.childForFieldName?.('body') ??
386
+ inner.children?.find((c) => c.type === 'compound_statement');
387
+ }
388
+ }
389
+ }
390
+ if (child.type === 'anonymous_function' ||
391
+ child.type === 'arrow_function') {
392
+ return child.childForFieldName?.('body') ??
393
+ child.children?.find((c) => c.type === 'compound_statement');
394
+ }
395
+ }
396
+ return null;
397
+ }
398
+ /** Extract first string argument from arguments node */
399
+ function extractFirstStringArg(argsNode) {
400
+ if (!argsNode)
401
+ return null;
402
+ for (const child of argsNode.children ?? []) {
403
+ const target = child.type === 'argument' ? child.children?.[0] : child;
404
+ if (!target)
405
+ continue;
406
+ if (target.type === 'string' || target.type === 'encapsed_string') {
407
+ return extractStringContent(target);
408
+ }
409
+ }
410
+ return null;
411
+ }
412
+ /** Extract middleware from arguments — handles string or array */
413
+ function extractMiddlewareArg(argsNode) {
414
+ if (!argsNode)
415
+ return [];
416
+ for (const child of argsNode.children ?? []) {
417
+ const target = child.type === 'argument' ? child.children?.[0] : child;
418
+ if (!target)
419
+ continue;
420
+ if (target.type === 'string' || target.type === 'encapsed_string') {
421
+ const val = extractStringContent(target);
422
+ return val ? [val] : [];
423
+ }
424
+ if (target.type === 'array_creation_expression') {
425
+ const items = [];
426
+ for (const el of target.children ?? []) {
427
+ if (el.type === 'array_element_initializer') {
428
+ const str = el.children?.find((c) => c.type === 'string' || c.type === 'encapsed_string');
429
+ const val = str ? extractStringContent(str) : null;
430
+ if (val)
431
+ items.push(val);
432
+ }
433
+ }
434
+ return items;
435
+ }
436
+ }
437
+ return [];
438
+ }
439
+ /** Extract Controller::class from arguments */
440
+ function extractClassArg(argsNode) {
441
+ if (!argsNode)
442
+ return null;
443
+ for (const child of argsNode.children ?? []) {
444
+ const target = child.type === 'argument' ? child.children?.[0] : child;
445
+ if (target?.type === 'class_constant_access_expression') {
446
+ return target.children?.find((c) => c.type === 'name')?.text ?? null;
447
+ }
448
+ }
449
+ return null;
450
+ }
451
+ /** Extract controller class name from arguments: [Controller::class, 'method'] or 'Controller@method' */
452
+ function extractControllerTarget(argsNode) {
453
+ if (!argsNode)
454
+ return { controller: null, method: null };
455
+ const args = [];
456
+ for (const child of argsNode.children ?? []) {
457
+ if (child.type === 'argument')
458
+ args.push(child.children?.[0]);
459
+ else if (child.type !== '(' && child.type !== ')' && child.type !== ',')
460
+ args.push(child);
461
+ }
462
+ // Second arg is the handler
463
+ const handlerNode = args[1];
464
+ if (!handlerNode)
465
+ return { controller: null, method: null };
466
+ // Array syntax: [UserController::class, 'index']
467
+ if (handlerNode.type === 'array_creation_expression') {
468
+ let controller = null;
469
+ let method = null;
470
+ const elements = [];
471
+ for (const el of handlerNode.children ?? []) {
472
+ if (el.type === 'array_element_initializer')
473
+ elements.push(el);
474
+ }
475
+ if (elements[0]) {
476
+ const classAccess = findDescendant(elements[0], 'class_constant_access_expression');
477
+ if (classAccess) {
478
+ controller = classAccess.children?.find((c) => c.type === 'name')?.text ?? null;
479
+ }
480
+ }
481
+ if (elements[1]) {
482
+ const str = findDescendant(elements[1], 'string');
483
+ method = str ? extractStringContent(str) : null;
484
+ }
485
+ return { controller, method };
486
+ }
487
+ // String syntax: 'UserController@index'
488
+ if (handlerNode.type === 'string' || handlerNode.type === 'encapsed_string') {
489
+ const text = extractStringContent(handlerNode);
490
+ if (text?.includes('@')) {
491
+ const [controller, method] = text.split('@');
492
+ return { controller, method };
493
+ }
494
+ }
495
+ // Class reference: UserController::class (invokable controller)
496
+ if (handlerNode.type === 'class_constant_access_expression') {
497
+ const controller = handlerNode.children?.find((c) => c.type === 'name')?.text ?? null;
498
+ return { controller, method: '__invoke' };
499
+ }
500
+ return { controller: null, method: null };
501
+ }
502
+ /**
503
+ * Unwrap a chained call like Route::middleware('auth')->prefix('api')->group(fn)
504
+ */
505
+ function unwrapRouteChain(node) {
506
+ if (node.type !== 'member_call_expression')
507
+ return null;
508
+ const terminalMethod = getCallMethodName(node);
509
+ if (!terminalMethod)
510
+ return null;
511
+ const terminalArgs = getArguments(node);
512
+ const attributes = [];
513
+ let current = node.children?.[0];
514
+ while (current) {
515
+ if (current.type === 'member_call_expression') {
516
+ const method = getCallMethodName(current);
517
+ const args = getArguments(current);
518
+ if (method)
519
+ attributes.unshift({ method, argsNode: args });
520
+ current = current.children?.[0];
521
+ }
522
+ else if (current.type === 'scoped_call_expression') {
523
+ const obj = current.childForFieldName?.('object') ?? current.children?.[0];
524
+ if (obj?.text !== 'Route')
525
+ return null;
526
+ const method = getCallMethodName(current);
527
+ const args = getArguments(current);
528
+ if (method)
529
+ attributes.unshift({ method, argsNode: args });
530
+ return { isRouteFacade: true, terminalMethod, attributes, terminalArgs, node };
531
+ }
532
+ else {
533
+ break;
534
+ }
535
+ }
536
+ return null;
537
+ }
538
+ /** Parse Route::group(['middleware' => ..., 'prefix' => ...], fn) array syntax */
539
+ function parseArrayGroupArgs(argsNode) {
540
+ const ctx = { middleware: [], prefix: null, controller: null };
541
+ if (!argsNode)
542
+ return ctx;
543
+ for (const child of argsNode.children ?? []) {
544
+ const target = child.type === 'argument' ? child.children?.[0] : child;
545
+ if (target?.type === 'array_creation_expression') {
546
+ for (const el of target.children ?? []) {
547
+ if (el.type !== 'array_element_initializer')
548
+ continue;
549
+ const children = el.children ?? [];
550
+ const arrowIdx = children.findIndex((c) => c.type === '=>');
551
+ if (arrowIdx === -1)
552
+ continue;
553
+ const key = extractStringContent(children[arrowIdx - 1]);
554
+ const val = children[arrowIdx + 1];
555
+ if (key === 'middleware') {
556
+ if (val?.type === 'string') {
557
+ const s = extractStringContent(val);
558
+ if (s)
559
+ ctx.middleware.push(s);
560
+ }
561
+ else if (val?.type === 'array_creation_expression') {
562
+ for (const item of val.children ?? []) {
563
+ if (item.type === 'array_element_initializer') {
564
+ const str = item.children?.find((c) => c.type === 'string');
565
+ const s = str ? extractStringContent(str) : null;
566
+ if (s)
567
+ ctx.middleware.push(s);
568
+ }
569
+ }
570
+ }
571
+ }
572
+ else if (key === 'prefix') {
573
+ ctx.prefix = extractStringContent(val) ?? null;
574
+ }
575
+ else if (key === 'controller') {
576
+ if (val?.type === 'class_constant_access_expression') {
577
+ ctx.controller = val.children?.find((c) => c.type === 'name')?.text ?? null;
578
+ }
579
+ }
580
+ }
581
+ }
582
+ }
583
+ return ctx;
584
+ }
585
+ function extractLaravelRoutes(tree, filePath) {
586
+ const routes = [];
587
+ function resolveStack(stack) {
588
+ const middleware = [];
589
+ let prefix = null;
590
+ let controller = null;
591
+ for (const ctx of stack) {
592
+ middleware.push(...ctx.middleware);
593
+ if (ctx.prefix)
594
+ prefix = prefix ? `${prefix}/${ctx.prefix}`.replace(/\/+/g, '/') : ctx.prefix;
595
+ if (ctx.controller)
596
+ controller = ctx.controller;
597
+ }
598
+ return { middleware, prefix, controller };
599
+ }
600
+ function emitRoute(httpMethod, argsNode, lineNumber, groupStack, chainAttrs) {
601
+ const effective = resolveStack(groupStack);
602
+ for (const attr of chainAttrs) {
603
+ if (attr.method === 'middleware')
604
+ effective.middleware.push(...extractMiddlewareArg(attr.argsNode));
605
+ if (attr.method === 'prefix') {
606
+ const p = extractFirstStringArg(attr.argsNode);
607
+ if (p)
608
+ effective.prefix = effective.prefix ? `${effective.prefix}/${p}` : p;
609
+ }
610
+ if (attr.method === 'controller') {
611
+ const cls = extractClassArg(attr.argsNode);
612
+ if (cls)
613
+ effective.controller = cls;
614
+ }
615
+ }
616
+ const routePath = extractFirstStringArg(argsNode);
617
+ if (ROUTE_RESOURCE_METHODS.has(httpMethod)) {
618
+ const target = extractControllerTarget(argsNode);
619
+ const actions = httpMethod === 'apiResource' ? API_RESOURCE_ACTIONS : RESOURCE_ACTIONS;
620
+ for (const action of actions) {
621
+ routes.push({
622
+ filePath, httpMethod, routePath,
623
+ controllerName: target.controller ?? effective.controller,
624
+ methodName: action,
625
+ middleware: [...effective.middleware],
626
+ prefix: effective.prefix,
627
+ lineNumber,
628
+ });
629
+ }
630
+ }
631
+ else {
632
+ const target = extractControllerTarget(argsNode);
633
+ routes.push({
634
+ filePath, httpMethod, routePath,
635
+ controllerName: target.controller ?? effective.controller,
636
+ methodName: target.method,
637
+ middleware: [...effective.middleware],
638
+ prefix: effective.prefix,
639
+ lineNumber,
640
+ });
641
+ }
642
+ }
643
+ function walk(node, groupStack) {
644
+ // Case 1: Simple Route::get(...), Route::post(...), etc.
645
+ if (isRouteStaticCall(node)) {
646
+ const method = getCallMethodName(node);
647
+ if (method && (ROUTE_HTTP_METHODS.has(method) || ROUTE_RESOURCE_METHODS.has(method))) {
648
+ emitRoute(method, getArguments(node), node.startPosition.row, groupStack, []);
649
+ return;
650
+ }
651
+ if (method === 'group') {
652
+ const argsNode = getArguments(node);
653
+ const groupCtx = parseArrayGroupArgs(argsNode);
654
+ const body = findClosureBody(argsNode);
655
+ if (body) {
656
+ groupStack.push(groupCtx);
657
+ walkChildren(body, groupStack);
658
+ groupStack.pop();
659
+ }
660
+ return;
661
+ }
662
+ }
663
+ // Case 2: Fluent chain — Route::middleware(...)->group(...) or Route::middleware(...)->get(...)
664
+ const chain = unwrapRouteChain(node);
665
+ if (chain) {
666
+ if (chain.terminalMethod === 'group') {
667
+ const groupCtx = { middleware: [], prefix: null, controller: null };
668
+ for (const attr of chain.attributes) {
669
+ if (attr.method === 'middleware')
670
+ groupCtx.middleware.push(...extractMiddlewareArg(attr.argsNode));
671
+ if (attr.method === 'prefix')
672
+ groupCtx.prefix = extractFirstStringArg(attr.argsNode);
673
+ if (attr.method === 'controller')
674
+ groupCtx.controller = extractClassArg(attr.argsNode);
675
+ }
676
+ const body = findClosureBody(chain.terminalArgs);
677
+ if (body) {
678
+ groupStack.push(groupCtx);
679
+ walkChildren(body, groupStack);
680
+ groupStack.pop();
681
+ }
682
+ return;
683
+ }
684
+ if (ROUTE_HTTP_METHODS.has(chain.terminalMethod) || ROUTE_RESOURCE_METHODS.has(chain.terminalMethod)) {
685
+ emitRoute(chain.terminalMethod, chain.terminalArgs, node.startPosition.row, groupStack, chain.attributes);
686
+ return;
687
+ }
688
+ }
689
+ // Default: recurse into children
690
+ walkChildren(node, groupStack);
691
+ }
692
+ function walkChildren(node, groupStack) {
693
+ for (const child of node.children ?? []) {
694
+ walk(child, groupStack);
695
+ }
696
+ }
697
+ walk(tree.rootNode, []);
698
+ return routes;
699
+ }
700
+ const processFileGroup = (files, language, queryString, result, onFileProcessed) => {
701
+ let query;
702
+ try {
703
+ const lang = parser.getLanguage();
704
+ query = new Parser.Query(lang, queryString);
705
+ }
706
+ catch (err) {
707
+ const message = `Query compilation failed for ${language}: ${err instanceof Error ? err.message : String(err)}`;
708
+ if (parentPort) {
709
+ parentPort.postMessage({ type: 'warning', message });
710
+ }
711
+ else {
712
+ console.warn(message);
713
+ }
714
+ return;
715
+ }
716
+ for (const file of files) {
717
+ // Skip files larger than the max tree-sitter buffer (32 MB)
718
+ if (file.content.length > TREE_SITTER_MAX_BUFFER)
719
+ continue;
720
+ let tree;
721
+ try {
722
+ tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
723
+ }
724
+ catch (err) {
725
+ console.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
726
+ continue;
727
+ }
728
+ result.fileCount++;
729
+ onFileProcessed?.();
730
+ // Build per-file type environment + constructor bindings in a single AST walk.
731
+ // Constructor bindings are verified against the SymbolTable in processCallsFromExtracted.
732
+ const typeEnv = buildTypeEnv(tree, language);
733
+ const callRouter = callRouters[language];
734
+ if (typeEnv.constructorBindings.length > 0) {
735
+ result.constructorBindings.push({ filePath: file.path, bindings: [...typeEnv.constructorBindings] });
736
+ }
737
+ let matches;
738
+ try {
739
+ matches = query.matches(tree.rootNode);
740
+ }
741
+ catch (err) {
742
+ console.warn(`Query execution failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
743
+ continue;
744
+ }
745
+ for (const match of matches) {
746
+ const captureMap = {};
747
+ for (const c of match.captures) {
748
+ captureMap[c.name] = c.node;
749
+ }
750
+ // Extract import paths before skipping
751
+ if (captureMap['import'] && captureMap['import.source']) {
752
+ const rawImportPath = language === SupportedLanguages.Kotlin
753
+ ? appendKotlinWildcard(captureMap['import.source'].text.replace(/['"<>]/g, ''), captureMap['import'])
754
+ : captureMap['import.source'].text.replace(/['"<>]/g, '');
755
+ const namedBindings = extractNamedBindings(captureMap['import'], language);
756
+ result.imports.push({
757
+ filePath: file.path,
758
+ rawImportPath,
759
+ language: language,
760
+ ...(namedBindings ? { namedBindings } : {}),
761
+ });
762
+ continue;
763
+ }
764
+ // Extract assignment sites (field write access)
765
+ if (captureMap['assignment'] && captureMap['assignment.receiver'] && captureMap['assignment.property']) {
766
+ const receiverText = captureMap['assignment.receiver'].text;
767
+ const propertyName = captureMap['assignment.property'].text;
768
+ if (receiverText && propertyName) {
769
+ const srcId = findEnclosingFunctionId(captureMap['assignment'], file.path)
770
+ || generateId('File', file.path);
771
+ let receiverTypeName;
772
+ if (typeEnv) {
773
+ receiverTypeName = typeEnv.lookup(receiverText, captureMap['assignment']) ?? undefined;
774
+ }
775
+ result.assignments.push({
776
+ filePath: file.path,
777
+ sourceId: srcId,
778
+ receiverText,
779
+ propertyName,
780
+ ...(receiverTypeName ? { receiverTypeName } : {}),
781
+ });
782
+ }
783
+ if (!captureMap['call'])
784
+ continue;
785
+ }
786
+ // Extract call sites
787
+ if (captureMap['call']) {
788
+ const callNameNode = captureMap['call.name'];
789
+ if (callNameNode) {
790
+ const calledName = callNameNode.text;
791
+ // Dispatch: route language-specific calls (heritage, properties, imports)
792
+ const routed = callRouter(calledName, captureMap['call']);
793
+ if (routed) {
794
+ if (routed.kind === 'skip')
795
+ continue;
796
+ if (routed.kind === 'import') {
797
+ result.imports.push({
798
+ filePath: file.path,
799
+ rawImportPath: routed.importPath,
800
+ language,
801
+ });
802
+ continue;
803
+ }
804
+ if (routed.kind === 'heritage') {
805
+ for (const item of routed.items) {
806
+ result.heritage.push({
807
+ filePath: file.path,
808
+ className: item.enclosingClass,
809
+ parentName: item.mixinName,
810
+ kind: item.heritageKind,
811
+ });
812
+ }
813
+ continue;
814
+ }
815
+ if (routed.kind === 'properties') {
816
+ const propEnclosingClassId = findEnclosingClassId(captureMap['call'], file.path);
817
+ for (const item of routed.items) {
818
+ const nodeId = generateId('Property', `${file.path}:${item.propName}`);
819
+ result.nodes.push({
820
+ id: nodeId,
821
+ label: 'Property',
822
+ properties: {
823
+ name: item.propName,
824
+ filePath: file.path,
825
+ startLine: item.startLine,
826
+ endLine: item.endLine,
827
+ language,
828
+ isExported: true,
829
+ description: item.accessorType,
830
+ },
831
+ });
832
+ result.symbols.push({
833
+ filePath: file.path,
834
+ name: item.propName,
835
+ nodeId,
836
+ type: 'Property',
837
+ ...(propEnclosingClassId ? { ownerId: propEnclosingClassId } : {}),
838
+ ...(item.declaredType ? { declaredType: item.declaredType } : {}),
839
+ });
840
+ const fileId = generateId('File', file.path);
841
+ const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
842
+ result.relationships.push({
843
+ id: relId,
844
+ sourceId: fileId,
845
+ targetId: nodeId,
846
+ type: 'DEFINES',
847
+ confidence: 1.0,
848
+ reason: '',
849
+ });
850
+ if (propEnclosingClassId) {
851
+ result.relationships.push({
852
+ id: generateId('HAS_PROPERTY', `${propEnclosingClassId}->${nodeId}`),
853
+ sourceId: propEnclosingClassId,
854
+ targetId: nodeId,
855
+ type: 'HAS_PROPERTY',
856
+ confidence: 1.0,
857
+ reason: '',
858
+ });
859
+ }
860
+ }
861
+ continue;
862
+ }
863
+ // kind === 'call' — fall through to normal call processing below
864
+ }
865
+ if (!isBuiltInOrNoise(calledName)) {
866
+ const callNode = captureMap['call'];
867
+ const sourceId = findEnclosingFunctionId(callNode, file.path)
868
+ || generateId('File', file.path);
869
+ const callForm = inferCallForm(callNode, callNameNode);
870
+ let receiverName = callForm === 'member' ? extractReceiverName(callNameNode) : undefined;
871
+ let receiverTypeName = receiverName ? typeEnv.lookup(receiverName, callNode) : undefined;
872
+ let receiverMixedChain;
873
+ // When the receiver is a complex expression (call chain, field chain, or mixed),
874
+ // extractReceiverName returns undefined. Walk the receiver node to build a unified
875
+ // mixed chain for deferred resolution in processCallsFromExtracted.
876
+ if (callForm === 'member' && receiverName === undefined && !receiverTypeName) {
877
+ const receiverNode = extractReceiverNode(callNameNode);
878
+ if (receiverNode) {
879
+ const extracted = extractMixedChain(receiverNode);
880
+ if (extracted && extracted.chain.length > 0) {
881
+ receiverMixedChain = extracted.chain;
882
+ receiverName = extracted.baseReceiverName;
883
+ // Try the type environment immediately for the base receiver
884
+ // (covers explicitly-typed locals and annotated parameters).
885
+ if (receiverName) {
886
+ receiverTypeName = typeEnv.lookup(receiverName, callNode);
887
+ }
888
+ }
889
+ }
890
+ }
891
+ result.calls.push({
892
+ filePath: file.path,
893
+ calledName,
894
+ sourceId,
895
+ argCount: countCallArguments(callNode),
896
+ ...(callForm !== undefined ? { callForm } : {}),
897
+ ...(receiverName !== undefined ? { receiverName } : {}),
898
+ ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
899
+ ...(receiverMixedChain !== undefined ? { receiverMixedChain } : {}),
900
+ });
901
+ }
902
+ }
903
+ continue;
904
+ }
905
+ // Extract heritage (extends/implements)
906
+ if (captureMap['heritage.class']) {
907
+ if (captureMap['heritage.extends']) {
908
+ // Go struct embedding: the query matches ALL field_declarations with
909
+ // type_identifier, but only anonymous fields (no name) are embedded.
910
+ // Named fields like `Breed string` also match — skip them.
911
+ const extendsNode = captureMap['heritage.extends'];
912
+ const fieldDecl = extendsNode.parent;
913
+ const isNamedField = fieldDecl?.type === 'field_declaration'
914
+ && fieldDecl.childForFieldName('name');
915
+ if (!isNamedField) {
916
+ result.heritage.push({
917
+ filePath: file.path,
918
+ className: captureMap['heritage.class'].text,
919
+ parentName: captureMap['heritage.extends'].text,
920
+ kind: 'extends',
921
+ });
922
+ }
923
+ }
924
+ if (captureMap['heritage.implements']) {
925
+ result.heritage.push({
926
+ filePath: file.path,
927
+ className: captureMap['heritage.class'].text,
928
+ parentName: captureMap['heritage.implements'].text,
929
+ kind: 'implements',
930
+ });
931
+ }
932
+ if (captureMap['heritage.trait']) {
933
+ result.heritage.push({
934
+ filePath: file.path,
935
+ className: captureMap['heritage.class'].text,
936
+ parentName: captureMap['heritage.trait'].text,
937
+ kind: 'trait-impl',
938
+ });
939
+ }
940
+ if (captureMap['heritage.extends'] || captureMap['heritage.implements'] || captureMap['heritage.trait']) {
941
+ continue;
942
+ }
943
+ }
944
+ const nodeLabel = getLabelFromCaptures(captureMap);
945
+ if (!nodeLabel)
946
+ continue;
947
+ // C/C++: @definition.function is broad and also matches inline class methods (inside
948
+ // a class/struct body). Those are already captured by @definition.method, so skip
949
+ // the duplicate Function entry to prevent double-indexing in globalIndex.
950
+ if ((language === SupportedLanguages.CPlusPlus || language === SupportedLanguages.C) &&
951
+ nodeLabel === 'Function') {
952
+ let ancestor = captureMap['definition.function']?.parent;
953
+ while (ancestor) {
954
+ if (ancestor.type === 'class_specifier' || ancestor.type === 'struct_specifier') {
955
+ break; // inside a class body — duplicate of @definition.method
956
+ }
957
+ ancestor = ancestor.parent;
958
+ }
959
+ if (ancestor)
960
+ continue; // found a class/struct ancestor → skip
961
+ }
962
+ const nameNode = captureMap['name'];
963
+ // Synthesize name for constructors without explicit @name capture (e.g. Swift init)
964
+ if (!nameNode && nodeLabel !== 'Constructor')
965
+ continue;
966
+ const nodeName = nameNode ? nameNode.text : 'init';
967
+ const definitionNode = getDefinitionNodeFromCaptures(captureMap);
968
+ const startLine = definitionNode ? definitionNode.startPosition.row : (nameNode ? nameNode.startPosition.row : 0);
969
+ const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
970
+ let description;
971
+ if (language === SupportedLanguages.PHP) {
972
+ if (nodeLabel === 'Property' && captureMap['definition.property']) {
973
+ description = extractPhpPropertyDescription(nodeName, captureMap['definition.property']) ?? undefined;
974
+ }
975
+ else if (nodeLabel === 'Method' && captureMap['definition.method']) {
976
+ description = extractEloquentRelationDescription(captureMap['definition.method']) ?? undefined;
977
+ }
978
+ }
979
+ const frameworkHint = definitionNode
980
+ ? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
981
+ : null;
982
+ let parameterCount;
983
+ let returnType;
984
+ let declaredType;
985
+ if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
986
+ const sig = extractMethodSignature(definitionNode);
987
+ parameterCount = sig.parameterCount;
988
+ returnType = sig.returnType;
989
+ // Language-specific return type fallback (e.g. Ruby YARD @return [Type])
990
+ // Also upgrades uninformative AST types like PHP `array` with PHPDoc `@return User[]`
991
+ if ((!returnType || returnType === 'array' || returnType === 'iterable') && definitionNode) {
992
+ const tc = typeConfigs[language];
993
+ if (tc?.extractReturnType) {
994
+ const docReturn = tc.extractReturnType(definitionNode);
995
+ if (docReturn)
996
+ returnType = docReturn;
997
+ }
998
+ }
999
+ }
1000
+ else if (nodeLabel === 'Property' && definitionNode) {
1001
+ // Extract the declared type for property/field nodes.
1002
+ // Walk the definition node for type annotation children.
1003
+ declaredType = extractPropertyDeclaredType(definitionNode);
1004
+ }
1005
+ result.nodes.push({
1006
+ id: nodeId,
1007
+ label: nodeLabel,
1008
+ properties: {
1009
+ name: nodeName,
1010
+ filePath: file.path,
1011
+ startLine: definitionNode ? definitionNode.startPosition.row : startLine,
1012
+ endLine: definitionNode ? definitionNode.endPosition.row : startLine,
1013
+ language: language,
1014
+ isExported: isNodeExported(nameNode || definitionNode, nodeName, language),
1015
+ ...(frameworkHint ? {
1016
+ astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
1017
+ astFrameworkReason: frameworkHint.reason,
1018
+ } : {}),
1019
+ ...(description !== undefined ? { description } : {}),
1020
+ ...(parameterCount !== undefined ? { parameterCount } : {}),
1021
+ ...(returnType !== undefined ? { returnType } : {}),
1022
+ },
1023
+ });
1024
+ // Compute enclosing class for Method/Constructor/Property/Function — used for both ownerId and HAS_METHOD
1025
+ // Function is included because Kotlin/Rust/Python capture class methods as Function nodes
1026
+ const needsOwner = nodeLabel === 'Method' || nodeLabel === 'Constructor' || nodeLabel === 'Property' || nodeLabel === 'Function';
1027
+ const enclosingClassId = needsOwner ? findEnclosingClassId(nameNode || definitionNode, file.path) : null;
1028
+ result.symbols.push({
1029
+ filePath: file.path,
1030
+ name: nodeName,
1031
+ nodeId,
1032
+ type: nodeLabel,
1033
+ ...(parameterCount !== undefined ? { parameterCount } : {}),
1034
+ ...(returnType !== undefined ? { returnType } : {}),
1035
+ ...(declaredType !== undefined ? { declaredType } : {}),
1036
+ ...(enclosingClassId ? { ownerId: enclosingClassId } : {}),
1037
+ });
1038
+ const fileId = generateId('File', file.path);
1039
+ const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
1040
+ result.relationships.push({
1041
+ id: relId,
1042
+ sourceId: fileId,
1043
+ targetId: nodeId,
1044
+ type: 'DEFINES',
1045
+ confidence: 1.0,
1046
+ reason: '',
1047
+ });
1048
+ // ── HAS_METHOD / HAS_PROPERTY: link member to enclosing class ──
1049
+ if (enclosingClassId) {
1050
+ const memberEdgeType = nodeLabel === 'Property' ? 'HAS_PROPERTY' : 'HAS_METHOD';
1051
+ result.relationships.push({
1052
+ id: generateId(memberEdgeType, `${enclosingClassId}->${nodeId}`),
1053
+ sourceId: enclosingClassId,
1054
+ targetId: nodeId,
1055
+ type: memberEdgeType,
1056
+ confidence: 1.0,
1057
+ reason: '',
1058
+ });
1059
+ }
1060
+ }
1061
+ // Extract Laravel routes from route files via procedural AST walk
1062
+ if (language === SupportedLanguages.PHP && (file.path.includes('/routes/') || file.path.startsWith('routes/')) && file.path.endsWith('.php')) {
1063
+ const extractedRoutes = extractLaravelRoutes(tree, file.path);
1064
+ result.routes.push(...extractedRoutes);
1065
+ }
1066
+ }
1067
+ };
1068
+ // ============================================================================
1069
+ // Worker message handler — supports sub-batch streaming
1070
+ // ============================================================================
1071
+ /** Accumulated result across sub-batches */
1072
+ let accumulated = {
1073
+ nodes: [], relationships: [], symbols: [],
1074
+ imports: [], calls: [], assignments: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0,
1075
+ };
1076
+ let cumulativeProcessed = 0;
1077
+ const mergeResult = (target, src) => {
1078
+ target.nodes.push(...src.nodes);
1079
+ target.relationships.push(...src.relationships);
1080
+ target.symbols.push(...src.symbols);
1081
+ target.imports.push(...src.imports);
1082
+ target.calls.push(...src.calls);
1083
+ target.assignments.push(...src.assignments);
1084
+ target.heritage.push(...src.heritage);
1085
+ target.routes.push(...src.routes);
1086
+ target.constructorBindings.push(...src.constructorBindings);
1087
+ for (const [lang, count] of Object.entries(src.skippedLanguages)) {
1088
+ target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
1089
+ }
1090
+ target.fileCount += src.fileCount;
1091
+ };
1092
+ parentPort.on('message', (msg) => {
1093
+ try {
1094
+ // Sub-batch mode: { type: 'sub-batch', files: [...] }
1095
+ if (msg && msg.type === 'sub-batch') {
1096
+ const result = processBatch(msg.files, (filesProcessed) => {
1097
+ parentPort.postMessage({ type: 'progress', filesProcessed: cumulativeProcessed + filesProcessed });
1098
+ });
1099
+ cumulativeProcessed += result.fileCount;
1100
+ mergeResult(accumulated, result);
1101
+ // Signal ready for next sub-batch
1102
+ parentPort.postMessage({ type: 'sub-batch-done' });
1103
+ return;
1104
+ }
1105
+ // Flush: send accumulated results
1106
+ if (msg && msg.type === 'flush') {
1107
+ parentPort.postMessage({ type: 'result', data: accumulated });
1108
+ // Reset for potential reuse
1109
+ accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], assignments: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0 };
1110
+ cumulativeProcessed = 0;
1111
+ return;
1112
+ }
1113
+ // Legacy single-message mode (backward compat): array of files
1114
+ if (Array.isArray(msg)) {
1115
+ const result = processBatch(msg, (filesProcessed) => {
1116
+ parentPort.postMessage({ type: 'progress', filesProcessed });
1117
+ });
1118
+ parentPort.postMessage({ type: 'result', data: result });
1119
+ return;
1120
+ }
1121
+ }
1122
+ catch (err) {
1123
+ const message = err instanceof Error ? err.message : String(err);
1124
+ parentPort.postMessage({ type: 'error', error: message });
1125
+ }
1126
+ });