@danielblomma/cortex-mcp 1.3.2 → 1.4.1

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 (154) hide show
  1. package/README.md +62 -14
  2. package/bin/cortex.mjs +10 -1
  3. package/package.json +2 -2
  4. package/scaffold/mcp/package-lock.json +3 -7
  5. package/scaffold/mcp/package.json +1 -1
  6. package/scaffold/scripts/dashboard.mjs +15 -1
  7. package/scaffold/scripts/ingest.mjs +323 -50
  8. package/scaffold/scripts/parsers/bash-treesitter.mjs +229 -0
  9. package/scaffold/scripts/parsers/cpp-dispatch.mjs +56 -0
  10. package/scaffold/scripts/parsers/cpp-treesitter.mjs +333 -0
  11. package/scaffold/scripts/parsers/csharp.mjs +197 -10
  12. package/scaffold/scripts/parsers/dotnet/CSharpParser/CSharpParser.csproj +1 -0
  13. package/scaffold/scripts/parsers/dotnet/CSharpParser/Program.cs +126 -21
  14. package/scaffold/scripts/parsers/go-treesitter.mjs +283 -0
  15. package/scaffold/scripts/parsers/java-treesitter.mjs +250 -0
  16. package/scaffold/scripts/parsers/javascript/ast.mjs +118 -12
  17. package/scaffold/scripts/parsers/javascript/chunks.mjs +4 -0
  18. package/scaffold/scripts/parsers/javascript/patterns.mjs +6 -0
  19. package/scaffold/scripts/parsers/javascript.mjs +4 -19
  20. package/scaffold/scripts/parsers/node_modules/.package-lock.json +57 -0
  21. package/scaffold/scripts/parsers/node_modules/acorn/CHANGELOG.md +972 -0
  22. package/scaffold/scripts/parsers/node_modules/acorn/LICENSE +21 -0
  23. package/scaffold/scripts/parsers/node_modules/acorn/README.md +301 -0
  24. package/scaffold/scripts/parsers/node_modules/acorn/bin/acorn +4 -0
  25. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.mts +883 -0
  26. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.ts +883 -0
  27. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.js +6295 -0
  28. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.mjs +6266 -0
  29. package/scaffold/scripts/parsers/node_modules/acorn/dist/bin.js +90 -0
  30. package/scaffold/scripts/parsers/node_modules/acorn/package.json +50 -0
  31. package/scaffold/scripts/parsers/node_modules/acorn-typescript/CHANGELOG.md +421 -0
  32. package/scaffold/scripts/parsers/node_modules/acorn-typescript/LICENSE +21 -0
  33. package/scaffold/scripts/parsers/node_modules/acorn-typescript/README.md +81 -0
  34. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.d.ts +103 -0
  35. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js +78 -0
  36. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js.map +1 -0
  37. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.d.ts +167 -0
  38. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js +75 -0
  39. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js.map +1 -0
  40. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.d.ts +177 -0
  41. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js +56 -0
  42. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js.map +1 -0
  43. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.d.ts +198 -0
  44. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js +327 -0
  45. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js.map +1 -0
  46. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.d.ts +256 -0
  47. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js +256 -0
  48. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js.map +1 -0
  49. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.d.ts +472 -0
  50. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js +1 -0
  51. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js.map +1 -0
  52. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.mjs +1 -0
  53. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.d.ts +159 -0
  54. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js +2 -0
  55. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js.map +1 -0
  56. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.d.ts +10 -0
  57. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js +38 -0
  58. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js.map +1 -0
  59. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.d.ts +12 -0
  60. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js +29 -0
  61. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js.map +1 -0
  62. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.d.ts +2 -0
  63. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js +118 -0
  64. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js.map +1 -0
  65. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.d.ts +60 -0
  66. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js +2 -0
  67. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js.map +1 -0
  68. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.d.ts +2 -0
  69. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js +19 -0
  70. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js.map +1 -0
  71. package/scaffold/scripts/parsers/node_modules/acorn-typescript/package.json +53 -0
  72. package/scaffold/scripts/parsers/node_modules/acorn-typescript/tsconfig.json +19 -0
  73. package/scaffold/scripts/parsers/node_modules/acorn-walk/CHANGELOG.md +209 -0
  74. package/scaffold/scripts/parsers/node_modules/acorn-walk/LICENSE +21 -0
  75. package/scaffold/scripts/parsers/node_modules/acorn-walk/README.md +124 -0
  76. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.mts +152 -0
  77. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.ts +152 -0
  78. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.js +485 -0
  79. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.mjs +467 -0
  80. package/scaffold/scripts/parsers/node_modules/acorn-walk/package.json +50 -0
  81. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/LICENSE +24 -0
  82. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/README.md +23 -0
  83. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-bash.wasm +0 -0
  84. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c.wasm +0 -0
  85. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c_sharp.wasm +0 -0
  86. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-cpp.wasm +0 -0
  87. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-css.wasm +0 -0
  88. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-dart.wasm +0 -0
  89. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elisp.wasm +0 -0
  90. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elixir.wasm +0 -0
  91. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elm.wasm +0 -0
  92. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-embedded_template.wasm +0 -0
  93. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-go.wasm +0 -0
  94. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-html.wasm +0 -0
  95. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-java.wasm +0 -0
  96. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-javascript.wasm +0 -0
  97. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-json.wasm +0 -0
  98. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-kotlin.wasm +0 -0
  99. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-lua.wasm +0 -0
  100. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-objc.wasm +0 -0
  101. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ocaml.wasm +0 -0
  102. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-php.wasm +0 -0
  103. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-python.wasm +0 -0
  104. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ql.wasm +0 -0
  105. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rescript.wasm +0 -0
  106. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ruby.wasm +0 -0
  107. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rust.wasm +0 -0
  108. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-scala.wasm +0 -0
  109. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-solidity.wasm +0 -0
  110. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-swift.wasm +0 -0
  111. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-systemrdl.wasm +0 -0
  112. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tlaplus.wasm +0 -0
  113. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-toml.wasm +0 -0
  114. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tsx.wasm +0 -0
  115. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-typescript.wasm +0 -0
  116. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-vue.wasm +0 -0
  117. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-yaml.wasm +0 -0
  118. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-zig.wasm +0 -0
  119. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/package.json +64 -0
  120. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/LICENSE +21 -0
  121. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/README.md +198 -0
  122. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/package.json +37 -0
  123. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter-web.d.ts +242 -0
  124. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.js +1 -0
  125. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.wasm +0 -0
  126. package/scaffold/scripts/parsers/package-lock.json +19 -1
  127. package/scaffold/scripts/parsers/package.json +3 -1
  128. package/scaffold/scripts/parsers/python-treesitter.mjs +271 -0
  129. package/scaffold/scripts/parsers/ruby-treesitter.mjs +271 -0
  130. package/scaffold/scripts/parsers/rust-dispatch.mjs +43 -0
  131. package/scaffold/scripts/parsers/rust-treesitter.mjs +291 -0
  132. package/scaffold/scripts/parsers/tree-sitter/base.mjs +163 -0
  133. package/scaffold/scripts/parsers/tree-sitter/queries/bash.calls.scm +7 -0
  134. package/scaffold/scripts/parsers/tree-sitter/queries/bash.chunks.scm +6 -0
  135. package/scaffold/scripts/parsers/tree-sitter/queries/bash.imports.scm +5 -0
  136. package/scaffold/scripts/parsers/tree-sitter/queries/cpp.calls.scm +17 -0
  137. package/scaffold/scripts/parsers/tree-sitter/queries/cpp.chunks.scm +30 -0
  138. package/scaffold/scripts/parsers/tree-sitter/queries/cpp.imports.scm +6 -0
  139. package/scaffold/scripts/parsers/tree-sitter/queries/go.calls.scm +11 -0
  140. package/scaffold/scripts/parsers/tree-sitter/queries/go.chunks.scm +19 -0
  141. package/scaffold/scripts/parsers/tree-sitter/queries/go.imports.scm +6 -0
  142. package/scaffold/scripts/parsers/tree-sitter/queries/java.calls.scm +6 -0
  143. package/scaffold/scripts/parsers/tree-sitter/queries/java.chunks.scm +23 -0
  144. package/scaffold/scripts/parsers/tree-sitter/queries/java.imports.scm +6 -0
  145. package/scaffold/scripts/parsers/tree-sitter/queries/python.calls.scm +11 -0
  146. package/scaffold/scripts/parsers/tree-sitter/queries/python.chunks.scm +11 -0
  147. package/scaffold/scripts/parsers/tree-sitter/queries/python.imports.scm +13 -0
  148. package/scaffold/scripts/parsers/tree-sitter/queries/ruby.calls.scm +6 -0
  149. package/scaffold/scripts/parsers/tree-sitter/queries/ruby.chunks.scm +16 -0
  150. package/scaffold/scripts/parsers/tree-sitter/queries/ruby.imports.scm +8 -0
  151. package/scaffold/scripts/parsers/tree-sitter/queries/rust.calls.scm +31 -0
  152. package/scaffold/scripts/parsers/tree-sitter/queries/rust.chunks.scm +29 -0
  153. package/scaffold/scripts/parsers/tree-sitter/queries/rust.imports.scm +5 -0
  154. package/scaffold/scripts/parsers/vb6.mjs +395 -0
@@ -2,11 +2,16 @@
2
2
  /**
3
3
  * Conditional C# parser bridge for Cortex.
4
4
  *
5
- * Uses a Roslyn sidecar via `dotnet run` when a .NET runtime is available.
6
- * If no runtime exists, callers should skip structured chunk extraction and
7
- * fall back to plain file-level indexing.
5
+ * Uses a Roslyn sidecar via a pre-published DLL when a .NET SDK is available.
6
+ * On first use the sidecar is published to bin/Release/<tfm>/publish/ and the
7
+ * DLL path is cached; subsequent invocations skip the msbuild cycle and run
8
+ * `dotnet <dll>` directly — roughly 10× faster per call than `dotnet run`.
9
+ *
10
+ * If no runtime/SDK exists, callers should skip structured chunk extraction
11
+ * and fall back to plain file-level indexing.
8
12
  */
9
13
 
14
+ import fs from "node:fs";
10
15
  import path from "node:path";
11
16
  import { fileURLToPath } from "node:url";
12
17
  import { spawnSync } from "node:child_process";
@@ -15,8 +20,10 @@ const __filename = fileURLToPath(import.meta.url);
15
20
  const __dirname = path.dirname(__filename);
16
21
  const DEFAULT_DOTNET_COMMAND = "dotnet";
17
22
  const DEFAULT_PROJECT_PATH = path.join(__dirname, "dotnet", "CSharpParser", "CSharpParser.csproj");
23
+ const DEFAULT_TARGET_FRAMEWORK = "net10.0";
18
24
 
19
25
  let runtimeCache = null;
26
+ let publishCache = null;
20
27
 
21
28
  function getDotnetCommand() {
22
29
  const override = process.env.CORTEX_DOTNET_CMD;
@@ -28,8 +35,51 @@ function getProjectPath() {
28
35
  return override && override.trim().length > 0 ? override.trim() : DEFAULT_PROJECT_PATH;
29
36
  }
30
37
 
38
+ function getTargetFramework() {
39
+ const override = process.env.CORTEX_CSHARP_PARSER_TFM;
40
+ return override && override.trim().length > 0 ? override.trim() : DEFAULT_TARGET_FRAMEWORK;
41
+ }
42
+
43
+ function getPublishDir() {
44
+ const override = process.env.CORTEX_CSHARP_PUBLISH_DIR;
45
+ if (override && override.trim().length > 0) return override.trim();
46
+ const projectDir = path.dirname(getProjectPath());
47
+ return path.join(projectDir, "bin", "Release", getTargetFramework(), "publish");
48
+ }
49
+
50
+ function getDllPath() {
51
+ return path.join(getPublishDir(), "CSharpParser.dll");
52
+ }
53
+
54
+ function getMaxSourceMtime() {
55
+ const projectDir = path.dirname(getProjectPath());
56
+ const sources = [getProjectPath(), path.join(projectDir, "Program.cs")];
57
+ let max = 0;
58
+ for (const src of sources) {
59
+ try {
60
+ const mtime = fs.statSync(src).mtimeMs;
61
+ if (mtime > max) max = mtime;
62
+ } catch {
63
+ // missing source — treated as stale below
64
+ }
65
+ }
66
+ return max;
67
+ }
68
+
69
+ function needsPublish() {
70
+ const dll = getDllPath();
71
+ let dllMtime;
72
+ try {
73
+ dllMtime = fs.statSync(dll).mtimeMs;
74
+ } catch {
75
+ return true;
76
+ }
77
+ return getMaxSourceMtime() > dllMtime;
78
+ }
79
+
31
80
  export function resetCSharpParserRuntimeCache() {
32
81
  runtimeCache = null;
82
+ publishCache = null;
33
83
  }
34
84
 
35
85
  export function getCSharpParserRuntime() {
@@ -69,19 +119,69 @@ export function isCSharpParserAvailable() {
69
119
  return getCSharpParserRuntime().available;
70
120
  }
71
121
 
122
+ export function ensureCSharpParserPublished() {
123
+ if (publishCache) return publishCache;
124
+
125
+ const runtime = getCSharpParserRuntime();
126
+ if (!runtime.available) {
127
+ publishCache = { ok: false, reason: runtime.reason };
128
+ return publishCache;
129
+ }
130
+
131
+ const dllPath = getDllPath();
132
+ if (!needsPublish()) {
133
+ publishCache = { ok: true, dllPath };
134
+ return publishCache;
135
+ }
136
+
137
+ if (!process.env.CORTEX_QUIET) {
138
+ process.stderr.write("[cortex] Publishing Roslyn C# parser (one-time, ~15s)...\n");
139
+ }
140
+
141
+ const result = spawnSync(
142
+ runtime.command,
143
+ [
144
+ "publish",
145
+ runtime.projectPath,
146
+ "-c", "Release",
147
+ "-o", getPublishDir(),
148
+ "--nologo",
149
+ "-v", "quiet"
150
+ ],
151
+ { encoding: "utf8", timeout: 180000 }
152
+ );
153
+
154
+ if (result.error || result.status !== 0) {
155
+ publishCache = {
156
+ ok: false,
157
+ reason:
158
+ result.error?.message ||
159
+ result.stderr?.trim() ||
160
+ `dotnet publish failed with exit code ${result.status ?? "unknown"}`
161
+ };
162
+ return publishCache;
163
+ }
164
+
165
+ publishCache = { ok: true, dllPath };
166
+ return publishCache;
167
+ }
168
+
72
169
  export function parseCode(code, filePath, language = "csharp") {
73
170
  const runtime = getCSharpParserRuntime();
74
171
  if (!runtime.available) {
75
172
  return { chunks: [], errors: [] };
76
173
  }
77
174
 
175
+ const published = ensureCSharpParserPublished();
176
+ if (!published.ok) {
177
+ return {
178
+ chunks: [],
179
+ errors: [{ message: `C# parser publish failed: ${published.reason}` }]
180
+ };
181
+ }
182
+
78
183
  const args = [
79
- "run",
80
- "--project",
81
- runtime.projectPath,
82
- "--configuration",
83
- "Release",
84
- "--",
184
+ published.dllPath,
85
185
  "--stdin",
86
186
  "--file",
87
187
  filePath,
@@ -128,8 +228,95 @@ export function parseCode(code, filePath, language = "csharp") {
128
228
  }
129
229
  }
130
230
 
231
+ /**
232
+ * Batch-parse an entire C# project as one CSharpCompilation, enabling
233
+ * SemanticModel-based call resolution. Calls are emitted as fully-
234
+ * qualified names (e.g. "System.IO.File.ReadAllText") instead of
235
+ * short names. Unresolved calls fall back to the syntax name.
236
+ *
237
+ * @param {Array<{path: string, content: string}>} files
238
+ * @returns {Map<string, {chunks: Array, errors: Array}>}
239
+ */
240
+ export function parseProject(files) {
241
+ const runtime = getCSharpParserRuntime();
242
+ if (!runtime.available) {
243
+ const empty = new Map();
244
+ for (const file of files) {
245
+ empty.set(file.path, { chunks: [], errors: [] });
246
+ }
247
+ return empty;
248
+ }
249
+
250
+ const published = ensureCSharpParserPublished();
251
+ if (!published.ok) {
252
+ const errors = [{ message: `C# parser publish failed: ${published.reason}` }];
253
+ const fallback = new Map();
254
+ for (const file of files) {
255
+ fallback.set(file.path, { chunks: [], errors });
256
+ }
257
+ return fallback;
258
+ }
259
+
260
+ const args = [published.dllPath, "--batch"];
261
+
262
+ const payload = JSON.stringify({
263
+ files: files.map((f) => ({ path: f.path, source: f.content }))
264
+ });
265
+
266
+ const result = spawnSync(runtime.command, args, {
267
+ input: payload,
268
+ encoding: "utf8",
269
+ timeout: 120000,
270
+ maxBuffer: 256 * 1024 * 1024
271
+ });
272
+
273
+ if (result.error || result.status !== 0) {
274
+ const errors = [
275
+ {
276
+ message:
277
+ result.error?.message ||
278
+ result.stderr?.trim() ||
279
+ `C# batch parser failed with exit code ${result.status ?? "unknown"}`
280
+ }
281
+ ];
282
+ const fallback = new Map();
283
+ for (const file of files) {
284
+ fallback.set(file.path, { chunks: [], errors });
285
+ }
286
+ return fallback;
287
+ }
288
+
289
+ try {
290
+ const parsed = JSON.parse(result.stdout);
291
+ const out = new Map();
292
+ const perFile = parsed.files ?? {};
293
+ for (const file of files) {
294
+ const entry = perFile[file.path];
295
+ if (entry) {
296
+ out.set(file.path, {
297
+ chunks: Array.isArray(entry.chunks) ? entry.chunks : [],
298
+ errors: Array.isArray(entry.errors) ? entry.errors : []
299
+ });
300
+ } else {
301
+ out.set(file.path, { chunks: [], errors: [] });
302
+ }
303
+ }
304
+ return out;
305
+ } catch (error) {
306
+ const errors = [
307
+ {
308
+ message: `C# batch parser returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`
309
+ }
310
+ ];
311
+ const fallback = new Map();
312
+ for (const file of files) {
313
+ fallback.set(file.path, { chunks: [], errors });
314
+ }
315
+ return fallback;
316
+ }
317
+ }
318
+
131
319
  if (import.meta.url === `file://${process.argv[1]}`) {
132
- const fs = await import("node:fs");
133
320
  const filePath = process.argv[2];
134
321
 
135
322
  if (!filePath) {
@@ -9,5 +9,6 @@
9
9
 
10
10
  <ItemGroup>
11
11
  <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
12
+ <PackageReference Include="Basic.Reference.Assemblies.Net100" Version="1.8.5" />
12
13
  </ItemGroup>
13
14
  </Project>
@@ -4,9 +4,16 @@ using Microsoft.CodeAnalysis.CSharp;
4
4
  using Microsoft.CodeAnalysis.CSharp.Syntax;
5
5
 
6
6
  var options = ParseArgs(args);
7
+
8
+ if (options.Batch)
9
+ {
10
+ RunBatchMode();
11
+ return;
12
+ }
13
+
7
14
  if (string.IsNullOrWhiteSpace(options.FilePath))
8
15
  {
9
- Console.Error.WriteLine("Missing required --file argument.");
16
+ Console.Error.WriteLine("Missing required --file argument (or use --batch for project-wide mode).");
10
17
  Environment.Exit(1);
11
18
  }
12
19
 
@@ -14,12 +21,8 @@ var source = options.UseStdin
14
21
  ? Console.In.ReadToEnd()
15
22
  : File.ReadAllText(options.FilePath);
16
23
 
17
- var parseResult = ParseCSharp(source, options.FilePath, options.Language);
18
- var json = JsonSerializer.Serialize(parseResult, new JsonSerializerOptions
19
- {
20
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
21
- WriteIndented = false
22
- });
24
+ var parseResult = ParseCSharp(source, options.FilePath, options.Language, semanticModel: null);
25
+ var json = JsonSerializer.Serialize(parseResult, JsonOut());
23
26
 
24
27
  Console.WriteLine(json);
25
28
 
@@ -48,16 +51,31 @@ static ParseOptions ParseArgs(string[] args)
48
51
  options.Language = args[++index];
49
52
  }
50
53
  break;
54
+ case "--batch":
55
+ options.Batch = true;
56
+ break;
51
57
  }
52
58
  }
53
59
 
54
60
  return options;
55
61
  }
56
62
 
57
- static ParserOutput ParseCSharp(string source, string filePath, string language)
63
+ static JsonSerializerOptions JsonOut() => new()
64
+ {
65
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
66
+ WriteIndented = false
67
+ };
68
+
69
+ static JsonSerializerOptions JsonIn() => new()
70
+ {
71
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
72
+ PropertyNameCaseInsensitive = true
73
+ };
74
+
75
+ static ParserOutput ParseCSharp(string source, string filePath, string language, SemanticModel? semanticModel)
58
76
  {
59
- var tree = CSharpSyntaxTree.ParseText(source, path: filePath);
60
- var root = tree.GetCompilationUnitRoot();
77
+ var tree = semanticModel?.SyntaxTree ?? CSharpSyntaxTree.ParseText(source, path: filePath);
78
+ var root = (CompilationUnitSyntax)tree.GetRoot();
61
79
  var diagnostics = tree.GetDiagnostics()
62
80
  .Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
63
81
  .Select(diagnostic => new ParserError(
@@ -72,24 +90,73 @@ static ParserOutput ParseCSharp(string source, string filePath, string language)
72
90
  return new ParserOutput(new List<ChunkOutput>(), diagnostics);
73
91
  }
74
92
 
75
- var collector = new CSharpChunkCollector(tree, root, source, language);
93
+ var collector = new CSharpChunkCollector(tree, root, source, language, semanticModel);
76
94
  return new ParserOutput(collector.Collect(), diagnostics);
77
95
  }
78
96
 
97
+ static void RunBatchMode()
98
+ {
99
+ var input = Console.In.ReadToEnd();
100
+ BatchInput? batch;
101
+ try
102
+ {
103
+ batch = JsonSerializer.Deserialize<BatchInput>(input, JsonIn());
104
+ }
105
+ catch (JsonException ex)
106
+ {
107
+ Console.Error.WriteLine($"Invalid batch JSON: {ex.Message}");
108
+ Environment.Exit(1);
109
+ return;
110
+ }
111
+
112
+ if (batch?.Files == null || batch.Files.Count == 0)
113
+ {
114
+ var empty = new BatchOutput { Files = new Dictionary<string, ParserOutput>() };
115
+ Console.WriteLine(JsonSerializer.Serialize(empty, JsonOut()));
116
+ return;
117
+ }
118
+
119
+ var trees = batch.Files
120
+ .Where(f => !string.IsNullOrEmpty(f.Path))
121
+ .Select(f => CSharpSyntaxTree.ParseText(f.Source ?? string.Empty, path: f.Path!))
122
+ .ToList();
123
+
124
+ var references = Basic.Reference.Assemblies.Net100.References.All.ToList();
125
+ var compilation = CSharpCompilation.Create(
126
+ "Cortex",
127
+ trees,
128
+ references,
129
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
130
+ );
131
+
132
+ var result = new BatchOutput { Files = new Dictionary<string, ParserOutput>() };
133
+ foreach (var tree in trees)
134
+ {
135
+ var model = compilation.GetSemanticModel(tree);
136
+ var file = batch.Files.First(f => f.Path == tree.FilePath);
137
+ var parseResult = ParseCSharp(file.Source ?? string.Empty, tree.FilePath, "csharp", model);
138
+ result.Files[tree.FilePath] = parseResult;
139
+ }
140
+
141
+ Console.WriteLine(JsonSerializer.Serialize(result, JsonOut()));
142
+ }
143
+
79
144
  sealed class CSharpChunkCollector
80
145
  {
81
146
  private readonly SyntaxTree _tree;
82
147
  private readonly CompilationUnitSyntax _root;
83
148
  private readonly string _source;
84
149
  private readonly string _language;
150
+ private readonly SemanticModel? _model;
85
151
  private readonly string[] _imports;
86
152
 
87
- public CSharpChunkCollector(SyntaxTree tree, CompilationUnitSyntax root, string source, string language)
153
+ public CSharpChunkCollector(SyntaxTree tree, CompilationUnitSyntax root, string source, string language, SemanticModel? model)
88
154
  {
89
155
  _tree = tree;
90
156
  _root = root;
91
157
  _source = source;
92
158
  _language = language;
159
+ _model = model;
93
160
  _imports = CollectUsings(root);
94
161
  }
95
162
 
@@ -97,7 +164,6 @@ sealed class CSharpChunkCollector
97
164
  {
98
165
  var usings = new List<string>();
99
166
 
100
- // Top-level using directives (including global using)
101
167
  foreach (var directive in root.Usings)
102
168
  {
103
169
  var name = directive.Name?.ToString();
@@ -107,7 +173,6 @@ sealed class CSharpChunkCollector
107
173
  }
108
174
  }
109
175
 
110
- // Namespace-scoped using directives
111
176
  foreach (var member in root.Members)
112
177
  {
113
178
  if (member is BaseNamespaceDeclarationSyntax ns)
@@ -129,12 +194,10 @@ sealed class CSharpChunkCollector
129
194
  public List<ChunkOutput> Collect()
130
195
  {
131
196
  var chunks = new List<ChunkOutput>();
132
-
133
197
  foreach (var member in _root.Members)
134
198
  {
135
199
  CollectMember(chunks, member, null);
136
200
  }
137
-
138
201
  return chunks;
139
202
  }
140
203
 
@@ -357,25 +420,50 @@ sealed class CSharpChunkCollector
357
420
  return modifiers.Any(modifier => modifier.IsKind(SyntaxKind.PublicKeyword));
358
421
  }
359
422
 
360
- private static IReadOnlyCollection<string> GetCalls(SyntaxNode node)
423
+ private IReadOnlyCollection<string> GetCalls(SyntaxNode node)
361
424
  {
362
425
  return node.DescendantNodes()
363
426
  .OfType<InvocationExpressionSyntax>()
364
- .Select(invocation => invocation.Expression)
365
- .Select(GetInvocationName)
427
+ .Select(ResolveCallName)
366
428
  .Where(name => !string.IsNullOrWhiteSpace(name))
429
+ .Select(name => name!)
367
430
  .Distinct(StringComparer.Ordinal)
368
431
  .ToArray();
369
432
  }
370
433
 
371
- private static string? GetInvocationName(ExpressionSyntax expression)
434
+ private string? ResolveCallName(InvocationExpressionSyntax invocation)
435
+ {
436
+ if (_model != null)
437
+ {
438
+ var info = _model.GetSymbolInfo(invocation);
439
+ var method = info.Symbol as IMethodSymbol
440
+ ?? info.CandidateSymbols.OfType<IMethodSymbol>().FirstOrDefault();
441
+ if (method != null)
442
+ {
443
+ return FullyQualifiedMethodName(method);
444
+ }
445
+ }
446
+ return GetInvocationSyntaxName(invocation.Expression);
447
+ }
448
+
449
+ private static string FullyQualifiedMethodName(IMethodSymbol method)
450
+ {
451
+ var container = method.ContainingType?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) ?? "";
452
+ if (container.StartsWith("global::", StringComparison.Ordinal))
453
+ {
454
+ container = container.Substring("global::".Length);
455
+ }
456
+ return string.IsNullOrEmpty(container) ? method.Name : $"{container}.{method.Name}";
457
+ }
458
+
459
+ private static string? GetInvocationSyntaxName(ExpressionSyntax expression)
372
460
  {
373
461
  return expression switch
374
462
  {
375
463
  IdentifierNameSyntax identifier => identifier.Identifier.Text,
376
464
  GenericNameSyntax genericName => genericName.Identifier.Text,
377
465
  MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.Text,
378
- InvocationExpressionSyntax nestedInvocation => GetInvocationName(nestedInvocation.Expression),
466
+ InvocationExpressionSyntax nestedInvocation => GetInvocationSyntaxName(nestedInvocation.Expression),
379
467
  _ => null
380
468
  };
381
469
  }
@@ -384,6 +472,7 @@ sealed class CSharpChunkCollector
384
472
  sealed record ParseOptions
385
473
  {
386
474
  public bool UseStdin { get; set; }
475
+ public bool Batch { get; set; }
387
476
  public string FilePath { get; set; } = "";
388
477
  public string Language { get; set; } = "csharp";
389
478
  }
@@ -404,3 +493,19 @@ sealed record ChunkOutput(
404
493
  sealed record ParserError(string Message, int Line, int Column);
405
494
 
406
495
  sealed record ParserOutput(List<ChunkOutput> Chunks, List<ParserError> Errors);
496
+
497
+ sealed class BatchInput
498
+ {
499
+ public List<BatchFile> Files { get; set; } = new();
500
+ }
501
+
502
+ sealed class BatchFile
503
+ {
504
+ public string? Path { get; set; }
505
+ public string? Source { get; set; }
506
+ }
507
+
508
+ sealed class BatchOutput
509
+ {
510
+ public Dictionary<string, ParserOutput> Files { get; set; } = new();
511
+ }