@danielblomma/cortex-mcp 2.1.1 → 2.2.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 (150) hide show
  1. package/README.md +72 -24
  2. package/bin/cortex.mjs +50 -21
  3. package/mcp-registry-submission.json +63 -0
  4. package/package.json +19 -5
  5. package/scaffold/AGENTS.md +13 -9
  6. package/scaffold/CLAUDE.md +13 -9
  7. package/scaffold/docs/architecture.md +1 -1
  8. package/scaffold/mcp/build.mjs +92 -0
  9. package/scaffold/mcp/package.json +4 -4
  10. package/scaffold/mcp/src/cli/query.ts +455 -0
  11. package/scaffold/mcp/src/embed.ts +181 -77
  12. package/scaffold/mcp/src/embedScheduler.ts +509 -0
  13. package/scaffold/mcp/src/embeddings.ts +31 -30
  14. package/scaffold/mcp/src/graphCsv.ts +65 -0
  15. package/scaffold/mcp/src/jsonl.ts +69 -14
  16. package/scaffold/mcp/src/loadGraph.ts +276 -40
  17. package/scaffold/mcp/src/lruCache.ts +41 -0
  18. package/scaffold/mcp/src/searchCore.ts +1 -1
  19. package/scaffold/mcp/src/searchResults.ts +3 -3
  20. package/scaffold/mcp/src/types.ts +6 -1
  21. package/scaffold/mcp/tests/embed-scheduler.test.mjs +474 -0
  22. package/scaffold/mcp/tests/embedding-jsonl.test.mjs +39 -0
  23. package/scaffold/mcp/tests/graph-bulk-load.test.mjs +258 -0
  24. package/scaffold/mcp/tests/graph-csv.test.mjs +118 -0
  25. package/scaffold/mcp/tests/lru-cache.test.mjs +37 -0
  26. package/scaffold/mcp/tests/query-cli.test.mjs +115 -0
  27. package/scaffold/mcp/tests/vector-index.test.mjs +109 -0
  28. package/scaffold/mcp/tsconfig.json +3 -1
  29. package/scaffold/scripts/bootstrap.sh +37 -4
  30. package/scaffold/scripts/dashboard.mjs +1 -1
  31. package/scaffold/scripts/doctor.sh +9 -8
  32. package/scaffold/scripts/embed.sh +3 -2
  33. package/scaffold/scripts/ingest-parsers.mjs +387 -0
  34. package/scaffold/scripts/ingest-worker.mjs +52 -0
  35. package/scaffold/scripts/ingest.mjs +550 -378
  36. package/scaffold/scripts/load-ryu.sh +3 -2
  37. package/scaffold/scripts/memory-compile.mjs +5 -2
  38. package/scaffold/scripts/memory-lint.mjs +5 -2
  39. package/scaffold/scripts/parsers/csharp.mjs +2 -1
  40. package/scaffold/scripts/parsers/javascript/ast.mjs +160 -8
  41. package/scaffold/scripts/parsers/javascript/chunks.mjs +129 -22
  42. package/scaffold/scripts/parsers/javascript/imports.mjs +38 -4
  43. package/scaffold/scripts/parsers/vbnet.mjs +2 -1
  44. package/scaffold/scripts/status.sh +50 -14
  45. package/scaffold/scripts/parsers/node_modules/.package-lock.json +0 -56
  46. package/scaffold/scripts/parsers/node_modules/acorn/CHANGELOG.md +0 -972
  47. package/scaffold/scripts/parsers/node_modules/acorn/LICENSE +0 -21
  48. package/scaffold/scripts/parsers/node_modules/acorn/README.md +0 -301
  49. package/scaffold/scripts/parsers/node_modules/acorn/bin/acorn +0 -4
  50. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.mts +0 -883
  51. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.ts +0 -883
  52. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.js +0 -6295
  53. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.mjs +0 -6266
  54. package/scaffold/scripts/parsers/node_modules/acorn/dist/bin.js +0 -90
  55. package/scaffold/scripts/parsers/node_modules/acorn/package.json +0 -50
  56. package/scaffold/scripts/parsers/node_modules/acorn-typescript/CHANGELOG.md +0 -421
  57. package/scaffold/scripts/parsers/node_modules/acorn-typescript/LICENSE +0 -21
  58. package/scaffold/scripts/parsers/node_modules/acorn-typescript/README.md +0 -81
  59. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.d.ts +0 -103
  60. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js +0 -78
  61. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js.map +0 -1
  62. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.d.ts +0 -167
  63. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js +0 -75
  64. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js.map +0 -1
  65. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.d.ts +0 -177
  66. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js +0 -56
  67. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js.map +0 -1
  68. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.d.ts +0 -198
  69. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js +0 -327
  70. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js.map +0 -1
  71. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.d.ts +0 -256
  72. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js +0 -256
  73. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js.map +0 -1
  74. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.d.ts +0 -472
  75. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js +0 -1
  76. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js.map +0 -1
  77. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.mjs +0 -1
  78. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.d.ts +0 -159
  79. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js +0 -2
  80. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js.map +0 -1
  81. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.d.ts +0 -10
  82. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js +0 -38
  83. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js.map +0 -1
  84. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.d.ts +0 -12
  85. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js +0 -29
  86. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js.map +0 -1
  87. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.d.ts +0 -2
  88. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js +0 -118
  89. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js.map +0 -1
  90. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.d.ts +0 -60
  91. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js +0 -2
  92. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js.map +0 -1
  93. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.d.ts +0 -2
  94. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js +0 -19
  95. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js.map +0 -1
  96. package/scaffold/scripts/parsers/node_modules/acorn-typescript/package.json +0 -53
  97. package/scaffold/scripts/parsers/node_modules/acorn-typescript/tsconfig.json +0 -19
  98. package/scaffold/scripts/parsers/node_modules/acorn-walk/CHANGELOG.md +0 -209
  99. package/scaffold/scripts/parsers/node_modules/acorn-walk/LICENSE +0 -21
  100. package/scaffold/scripts/parsers/node_modules/acorn-walk/README.md +0 -124
  101. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.mts +0 -152
  102. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.ts +0 -152
  103. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.js +0 -485
  104. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.mjs +0 -467
  105. package/scaffold/scripts/parsers/node_modules/acorn-walk/package.json +0 -50
  106. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/LICENSE +0 -24
  107. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/README.md +0 -23
  108. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-bash.wasm +0 -0
  109. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c.wasm +0 -0
  110. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c_sharp.wasm +0 -0
  111. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-cpp.wasm +0 -0
  112. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-css.wasm +0 -0
  113. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-dart.wasm +0 -0
  114. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elisp.wasm +0 -0
  115. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elixir.wasm +0 -0
  116. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elm.wasm +0 -0
  117. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-embedded_template.wasm +0 -0
  118. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-go.wasm +0 -0
  119. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-html.wasm +0 -0
  120. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-java.wasm +0 -0
  121. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-javascript.wasm +0 -0
  122. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-json.wasm +0 -0
  123. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-kotlin.wasm +0 -0
  124. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-lua.wasm +0 -0
  125. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-objc.wasm +0 -0
  126. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ocaml.wasm +0 -0
  127. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-php.wasm +0 -0
  128. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-python.wasm +0 -0
  129. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ql.wasm +0 -0
  130. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rescript.wasm +0 -0
  131. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ruby.wasm +0 -0
  132. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rust.wasm +0 -0
  133. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-scala.wasm +0 -0
  134. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-solidity.wasm +0 -0
  135. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-swift.wasm +0 -0
  136. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-systemrdl.wasm +0 -0
  137. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tlaplus.wasm +0 -0
  138. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-toml.wasm +0 -0
  139. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tsx.wasm +0 -0
  140. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-typescript.wasm +0 -0
  141. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-vue.wasm +0 -0
  142. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-yaml.wasm +0 -0
  143. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-zig.wasm +0 -0
  144. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/package.json +0 -64
  145. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/LICENSE +0 -21
  146. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/README.md +0 -198
  147. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/package.json +0 -37
  148. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter-web.d.ts +0 -242
  149. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.js +0 -1
  150. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.wasm +0 -0
@@ -1,97 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import crypto from "node:crypto";
3
3
  import fs from "node:fs";
4
+ import os from "node:os";
4
5
  import path from "node:path";
5
6
  import { fileURLToPath } from "node:url";
6
7
  import { execSync } from "node:child_process";
7
- import { parseCode } from "./parsers/javascript.mjs";
8
-
9
- const parseJavaScriptCode = parseCode;
10
- let parseVbNetCode = null;
11
- let parseCSharpCode = null;
12
- let parseCSharpProject = null;
13
- let parseCppCode = null;
14
- let parseConfigCode = null;
15
- let parseResourcesCode = null;
16
- let parseSqlCode = null;
17
- let parseRustCode = null;
18
- let parsePythonCode = null;
19
- let parseGoCode = null;
20
- let parseJavaCode = null;
21
- let parseRubyCode = null;
22
- let parseBashCode = null;
23
- let parseVb6Code = null;
24
- let parseMarkdownCode = null;
25
- let isVbNetParserAvailable = () => false;
26
- let isCSharpParserAvailable = () => false;
27
- let isCppParserAvailable = () => false;
28
- let getCSharpParserRuntime = () => ({ available: false, reason: "parser module not loaded" });
29
-
30
- async function loadOptionalParsers() {
31
- const loaders = [
32
- import("./parsers/vbnet.mjs").then((module) => {
33
- parseVbNetCode = module.parseCode;
34
- isVbNetParserAvailable =
35
- typeof module.isVbNetParserAvailable === "function"
36
- ? module.isVbNetParserAvailable
37
- : () => typeof module.parseCode === "function";
38
- }),
39
- import("./parsers/csharp.mjs").then((module) => {
40
- parseCSharpCode = module.parseCode;
41
- parseCSharpProject = module.parseProject ?? null;
42
- getCSharpParserRuntime =
43
- typeof module.getCSharpParserRuntime === "function"
44
- ? module.getCSharpParserRuntime
45
- : () => ({ available: typeof module.parseCode === "function", reason: "runtime details unavailable" });
46
- isCSharpParserAvailable =
47
- typeof module.isCSharpParserAvailable === "function"
48
- ? module.isCSharpParserAvailable
49
- : () => typeof module.parseCode === "function";
50
- }),
51
- import("./parsers/cpp-dispatch.mjs").then((module) => {
52
- parseCppCode = module.parseCode;
53
- isCppParserAvailable =
54
- typeof module.isCppParserAvailable === "function"
55
- ? module.isCppParserAvailable
56
- : () => typeof module.parseCode === "function";
57
- }),
58
- import("./parsers/config.mjs").then((module) => {
59
- parseConfigCode = module.parseCode;
60
- }),
61
- import("./parsers/resources.mjs").then((module) => {
62
- parseResourcesCode = module.parseCode;
63
- }),
64
- import("./parsers/sql.mjs").then((module) => {
65
- parseSqlCode = module.parseCode;
66
- }),
67
- import("./parsers/rust-dispatch.mjs").then((module) => {
68
- parseRustCode = module.parseCode;
69
- }),
70
- import("./parsers/python-treesitter.mjs").then((module) => {
71
- parsePythonCode = module.parseCode;
72
- }),
73
- import("./parsers/go-treesitter.mjs").then((module) => {
74
- parseGoCode = module.parseCode;
75
- }),
76
- import("./parsers/java-treesitter.mjs").then((module) => {
77
- parseJavaCode = module.parseCode;
78
- }),
79
- import("./parsers/ruby-treesitter.mjs").then((module) => {
80
- parseRubyCode = module.parseCode;
81
- }),
82
- import("./parsers/bash-treesitter.mjs").then((module) => {
83
- parseBashCode = module.parseCode;
84
- }),
85
- import("./parsers/vb6.mjs").then((module) => {
86
- parseVb6Code = module.parseCode;
87
- }),
88
- import("./parsers/markdown.mjs").then((module) => {
89
- parseMarkdownCode = module.parseCode;
90
- })
91
- ];
92
-
93
- await Promise.allSettled(loaders);
94
- }
8
+ import { Worker } from "node:worker_threads";
9
+ import {
10
+ loadParsers,
11
+ getChunkParserForExtension,
12
+ parseCSharpProject,
13
+ hasCSharpProjectParser,
14
+ isCSharpParserAvailable,
15
+ getCSharpParserRuntime,
16
+ PARALLEL_SAFE_LANGUAGES
17
+ } from "./ingest-parsers.mjs";
95
18
 
96
19
  const __filename = fileURLToPath(import.meta.url);
97
20
  const __dirname = path.dirname(__filename);
@@ -115,6 +38,8 @@ const SUPPORTED_TEXT_EXTENSIONS = new Set([
115
38
  ".csv",
116
39
  ".ts",
117
40
  ".tsx",
41
+ ".mts",
42
+ ".cts",
118
43
  ".js",
119
44
  ".jsx",
120
45
  ".mjs",
@@ -173,6 +98,8 @@ const STRUCTURED_NON_CODE_CHUNK_EXTENSIONS = new Set([".config", ".resx", ".sett
173
98
  const CODE_FILE_EXTENSIONS = new Set([
174
99
  ".ts",
175
100
  ".tsx",
101
+ ".mts",
102
+ ".cts",
176
103
  ".js",
177
104
  ".jsx",
178
105
  ".mjs",
@@ -247,253 +174,6 @@ const CONFIG_KEY_REFERENCE_PATTERNS = [
247
174
  /\bGetAppSetting\(\s*"([^"\r\n]+)"\s*\)/g
248
175
  ];
249
176
 
250
- const CHUNK_PARSERS = new Map([
251
- [
252
- ".js",
253
- {
254
- language: "javascript",
255
- parse: parseJavaScriptCode
256
- }
257
- ],
258
- [
259
- ".mjs",
260
- {
261
- language: "javascript",
262
- parse: parseJavaScriptCode
263
- }
264
- ],
265
- [
266
- ".cjs",
267
- {
268
- language: "javascript",
269
- parse: parseJavaScriptCode
270
- }
271
- ],
272
- [
273
- ".ts",
274
- {
275
- language: "typescript",
276
- parse: parseJavaScriptCode
277
- }
278
- ],
279
- [
280
- ".vb",
281
- {
282
- language: "vbnet",
283
- parse: (...args) => parseVbNetCode(...args),
284
- isAvailable: () =>
285
- typeof parseVbNetCode === "function" && isVbNetParserAvailable()
286
- }
287
- ],
288
- [
289
- ".cs",
290
- {
291
- language: "csharp",
292
- parse: (...args) => parseCSharpCode(...args),
293
- isAvailable: () =>
294
- typeof parseCSharpCode === "function" && isCSharpParserAvailable()
295
- }
296
- ],
297
- [
298
- ".sql",
299
- {
300
- language: "sql",
301
- parse: (...args) => parseSqlCode(...args),
302
- isAvailable: () => typeof parseSqlCode === "function"
303
- }
304
- ],
305
- [
306
- ".md",
307
- {
308
- language: "markdown",
309
- parse: (...args) => parseMarkdownCode(...args),
310
- isAvailable: () => typeof parseMarkdownCode === "function"
311
- }
312
- ],
313
- [
314
- ".mdx",
315
- {
316
- language: "markdown",
317
- parse: (...args) => parseMarkdownCode(...args),
318
- isAvailable: () => typeof parseMarkdownCode === "function"
319
- }
320
- ],
321
- [
322
- ".config",
323
- {
324
- language: "config",
325
- parse: (...args) => parseConfigCode(...args),
326
- isAvailable: () => typeof parseConfigCode === "function"
327
- }
328
- ],
329
- [
330
- ".resx",
331
- {
332
- language: "resource",
333
- parse: (...args) => parseResourcesCode(...args),
334
- isAvailable: () => typeof parseResourcesCode === "function"
335
- }
336
- ],
337
- [
338
- ".settings",
339
- {
340
- language: "settings",
341
- parse: (...args) => parseResourcesCode(...args),
342
- isAvailable: () => typeof parseResourcesCode === "function"
343
- }
344
- ],
345
- [
346
- ".c",
347
- {
348
- language: "c",
349
- parse: (...args) => parseCppCode(...args),
350
- isAvailable: () =>
351
- typeof parseCppCode === "function" && isCppParserAvailable()
352
- }
353
- ],
354
- [
355
- ".h",
356
- {
357
- language: "c",
358
- parse: (...args) => parseCppCode(...args),
359
- isAvailable: () =>
360
- typeof parseCppCode === "function" && isCppParserAvailable()
361
- }
362
- ],
363
- [
364
- ".cpp",
365
- {
366
- language: "cpp",
367
- parse: (...args) => parseCppCode(...args),
368
- isAvailable: () =>
369
- typeof parseCppCode === "function" && isCppParserAvailable()
370
- }
371
- ],
372
- [
373
- ".cc",
374
- {
375
- language: "cpp",
376
- parse: (...args) => parseCppCode(...args),
377
- isAvailable: () =>
378
- typeof parseCppCode === "function" && isCppParserAvailable()
379
- }
380
- ],
381
- [
382
- ".hpp",
383
- {
384
- language: "cpp",
385
- parse: (...args) => parseCppCode(...args),
386
- isAvailable: () =>
387
- typeof parseCppCode === "function" && isCppParserAvailable()
388
- }
389
- ],
390
- [
391
- ".hh",
392
- {
393
- language: "cpp",
394
- parse: (...args) => parseCppCode(...args),
395
- isAvailable: () =>
396
- typeof parseCppCode === "function" && isCppParserAvailable()
397
- }
398
- ],
399
- [
400
- ".rs",
401
- {
402
- language: "rust",
403
- parse: (...args) => parseRustCode(...args),
404
- isAvailable: () => typeof parseRustCode === "function"
405
- }
406
- ],
407
- [
408
- ".py",
409
- {
410
- language: "python",
411
- parse: (...args) => parsePythonCode(...args),
412
- isAvailable: () => typeof parsePythonCode === "function"
413
- }
414
- ],
415
- [
416
- ".go",
417
- {
418
- language: "go",
419
- parse: (...args) => parseGoCode(...args),
420
- isAvailable: () => typeof parseGoCode === "function"
421
- }
422
- ],
423
- [
424
- ".java",
425
- {
426
- language: "java",
427
- parse: (...args) => parseJavaCode(...args),
428
- isAvailable: () => typeof parseJavaCode === "function"
429
- }
430
- ],
431
- [
432
- ".rb",
433
- {
434
- language: "ruby",
435
- parse: (...args) => parseRubyCode(...args),
436
- isAvailable: () => typeof parseRubyCode === "function"
437
- }
438
- ],
439
- [
440
- ".sh",
441
- {
442
- language: "bash",
443
- parse: (...args) => parseBashCode(...args),
444
- isAvailable: () => typeof parseBashCode === "function"
445
- }
446
- ],
447
- [
448
- ".bash",
449
- {
450
- language: "bash",
451
- parse: (...args) => parseBashCode(...args),
452
- isAvailable: () => typeof parseBashCode === "function"
453
- }
454
- ],
455
- [
456
- ".zsh",
457
- {
458
- language: "bash",
459
- parse: (...args) => parseBashCode(...args),
460
- isAvailable: () => typeof parseBashCode === "function"
461
- }
462
- ],
463
- [
464
- ".bas",
465
- {
466
- language: "vb6",
467
- parse: (...args) => parseVb6Code(...args),
468
- isAvailable: () => typeof parseVb6Code === "function"
469
- }
470
- ],
471
- [
472
- ".cls",
473
- {
474
- language: "vb6",
475
- parse: (...args) => parseVb6Code(...args),
476
- isAvailable: () => typeof parseVb6Code === "function"
477
- }
478
- ],
479
- [
480
- ".frm",
481
- {
482
- language: "vb6",
483
- parse: (...args) => parseVb6Code(...args),
484
- isAvailable: () => typeof parseVb6Code === "function"
485
- }
486
- ],
487
- [
488
- ".ctl",
489
- {
490
- language: "vb6",
491
- parse: (...args) => parseVb6Code(...args),
492
- isAvailable: () => typeof parseVb6Code === "function"
493
- }
494
- ]
495
- ]);
496
-
497
177
  const SKIP_DIRECTORIES = new Set([
498
178
  ".git",
499
179
  ".idea",
@@ -517,9 +197,9 @@ const DEFAULT_CHUNK_WINDOW_LINES = 80;
517
197
  const DEFAULT_CHUNK_OVERLAP_LINES = 16;
518
198
  const DEFAULT_CHUNK_SPLIT_MIN_LINES = 120;
519
199
  const DEFAULT_CHUNK_MAX_WINDOWS = 8;
520
- const IMPORT_RESOLUTION_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
200
+ const IMPORT_RESOLUTION_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
521
201
  const IMPORT_RUNTIME_JS_EXTENSIONS = new Set([".js", ".jsx", ".mjs", ".cjs"]);
522
- const IMPORT_RUNTIME_JS_RESOLUTION_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
202
+ const IMPORT_RUNTIME_JS_RESOLUTION_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
523
203
  const CPP_IMPORT_RESOLUTION_EXTENSIONS = [".h", ".hh", ".hpp", ".c", ".cc", ".cpp"];
524
204
 
525
205
  const STOP_WORDS = new Set([
@@ -659,6 +339,40 @@ function parseNonNegativeIntegerEnv(name, fallback) {
659
339
  return Math.floor(parsed);
660
340
  }
661
341
 
342
+ function isTruthyEnv(value) {
343
+ if (typeof value !== "string") {
344
+ return false;
345
+ }
346
+ const normalized = value.trim().toLowerCase();
347
+ return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "no" && normalized !== "off";
348
+ }
349
+
350
+ function createIngestMemoryTrace() {
351
+ const enabled = isTruthyEnv(process.env.CORTEX_INGEST_TRACE_MEMORY);
352
+
353
+ return {
354
+ enabled,
355
+ checkpoint(label, counts = {}) {
356
+ if (!enabled) {
357
+ return;
358
+ }
359
+
360
+ const memory = process.memoryUsage();
361
+ process.stderr.write(
362
+ `${JSON.stringify({
363
+ type: "cortex.ingest.memory",
364
+ label,
365
+ rss_bytes: memory.rss,
366
+ rss_mb: Number((memory.rss / 1024 / 1024).toFixed(2)),
367
+ heap_used_bytes: memory.heapUsed,
368
+ external_bytes: memory.external,
369
+ counts
370
+ })}\n`
371
+ );
372
+ }
373
+ };
374
+ }
375
+
662
376
  function parseSourcePaths(configText) {
663
377
  const sourcePaths = [];
664
378
  const lines = configText.split(/\r?\n/);
@@ -965,10 +679,6 @@ function detectKind(relPath) {
965
679
  return "CODE";
966
680
  }
967
681
 
968
- function getChunkParserForExtension(ext) {
969
- return CHUNK_PARSERS.get(ext) ?? null;
970
- }
971
-
972
682
  function trustLevelForKind(kind) {
973
683
  if (kind === "ADR") return 95;
974
684
  if (kind === "CODE") return 80;
@@ -1038,8 +748,14 @@ function findSupersedesReferences(content) {
1038
748
  }
1039
749
 
1040
750
  function writeJsonl(filePath, records) {
1041
- const body = records.map((record) => JSON.stringify(record)).join("\n");
1042
- fs.writeFileSync(filePath, body ? `${body}\n` : "", "utf8");
751
+ const fd = fs.openSync(filePath, "w");
752
+ try {
753
+ for (const record of records) {
754
+ fs.writeSync(fd, `${JSON.stringify(record)}\n`, undefined, "utf8");
755
+ }
756
+ } finally {
757
+ fs.closeSync(fd);
758
+ }
1043
759
  }
1044
760
 
1045
761
  function sanitizeTsvCell(value) {
@@ -1048,11 +764,21 @@ function sanitizeTsvCell(value) {
1048
764
  }
1049
765
 
1050
766
  function writeTsv(filePath, headers, rows) {
1051
- const lines = [headers.join("\t")];
1052
- for (const row of rows) {
1053
- lines.push(row.map((value) => sanitizeTsvCell(value)).join("\t"));
767
+ const fd = fs.openSync(filePath, "w");
768
+ try {
769
+ fs.writeSync(fd, `${headers.join("\t")}\n`, undefined, "utf8");
770
+ for (const row of rows) {
771
+ fs.writeSync(fd, `${row.map((value) => sanitizeTsvCell(value)).join("\t")}\n`, undefined, "utf8");
772
+ }
773
+ } finally {
774
+ fs.closeSync(fd);
775
+ }
776
+ }
777
+
778
+ function* mapRows(records, project) {
779
+ for (const record of records) {
780
+ yield project(record);
1054
781
  }
1055
- fs.writeFileSync(filePath, `${lines.join("\n")}\n`, "utf8");
1056
782
  }
1057
783
 
1058
784
  function readJsonlSafe(filePath) {
@@ -2389,7 +2115,16 @@ function generateModuleSummary(dir, files, exportNames, repoRoot = REPO_ROOT) {
2389
2115
  const exts = new Set(codeFiles.map(f => path.extname(f.path).toLowerCase()));
2390
2116
  if (exts.size === 1) {
2391
2117
  const ext = [...exts][0];
2392
- const extNames = { ".ts": "TypeScript", ".js": "JavaScript", ".mjs": "JavaScript (ESM)", ".tsx": "TypeScript React" };
2118
+ const extNames = {
2119
+ ".ts": "TypeScript",
2120
+ ".tsx": "TypeScript React",
2121
+ ".mts": "TypeScript ESM",
2122
+ ".cts": "TypeScript CommonJS",
2123
+ ".js": "JavaScript",
2124
+ ".jsx": "JavaScript React",
2125
+ ".mjs": "JavaScript ESM",
2126
+ ".cjs": "JavaScript CommonJS"
2127
+ };
2393
2128
  if (extNames[ext]) parts.push(`${extNames[ext]} source files`);
2394
2129
  }
2395
2130
 
@@ -2528,9 +2263,266 @@ function splitChunkIntoWindows(chunkRecord, options) {
2528
2263
  return windows;
2529
2264
  }
2530
2265
 
2266
+ function resolveIngestWorkerCount(taskCount) {
2267
+ const raw = process.env.CORTEX_INGEST_WORKERS;
2268
+ const configured = raw !== undefined ? Number.parseInt(raw, 10) : Number.NaN;
2269
+ const cpuBudget = Math.max(1, (os.availableParallelism?.() ?? os.cpus().length) - 1);
2270
+ const desired = Number.isFinite(configured) && configured >= 0 ? configured : Math.min(cpuBudget, 8);
2271
+ if (desired <= 1) return 1;
2272
+ // Worker spin-up plus per-worker WASM grammar init dominates on small or
2273
+ // incremental runs; stay sequential until there is enough work to amortize.
2274
+ if (taskCount < 50) return 1;
2275
+ return Math.min(desired, taskCount);
2276
+ }
2277
+
2278
+ function createEmptyWorkerParseStream(tasks, workerCount) {
2279
+ const stats = () => ({
2280
+ worker_tasks: tasks.length,
2281
+ worker_count: Number.isFinite(workerCount) ? workerCount : 0,
2282
+ worker_tasks_assigned: 0,
2283
+ worker_tasks_settled: 0,
2284
+ worker_tasks_unsettled_fallback: tasks.length,
2285
+ worker_results: 0,
2286
+ worker_results_consumed: 0,
2287
+ worker_results_retained: 0,
2288
+ worker_results_retained_peak: 0,
2289
+ worker_results_pending: 0,
2290
+ worker_results_missing: tasks.length,
2291
+ worker_waiters: 0
2292
+ });
2293
+ return {
2294
+ hasTask: () => false,
2295
+ stats,
2296
+ take: async () => undefined,
2297
+ drain: async () => stats()
2298
+ };
2299
+ }
2300
+
2301
+ function startWorkerParseStream(tasks, { workerCount, verbose, workerUrl } = {}) {
2302
+ if (tasks.length === 0) {
2303
+ return createEmptyWorkerParseStream(tasks, workerCount);
2304
+ }
2305
+
2306
+ const resolvedWorkerUrl = workerUrl ?? new URL("./ingest-worker.mjs", import.meta.url);
2307
+ const poolSize = Math.min(workerCount, tasks.length);
2308
+ // Defensive: an undefined/0/negative/NaN workerCount yields poolSize < 1, in
2309
+ // which case no workers are ever spawned and the pool would never resolve.
2310
+ // Return empty so the caller parses everything inline. (Math.min(undefined, n)
2311
+ // is NaN, which is why this is `!(>= 1)` rather than `< 1`.)
2312
+ if (!(poolSize >= 1)) {
2313
+ return createEmptyWorkerParseStream(tasks, workerCount);
2314
+ }
2315
+
2316
+ const taskIds = new Set(tasks.map((task) => task.id));
2317
+ const results = new Map();
2318
+ const missingResults = new Set();
2319
+ const waiters = new Map();
2320
+ const workers = [];
2321
+ const inflight = new Map(); // worker -> taskId being parsed, or null when idle
2322
+ let nextTask = 0;
2323
+ let alive = poolSize;
2324
+ let finished = false;
2325
+ let resolveDone;
2326
+ const done = new Promise((resolve) => {
2327
+ resolveDone = resolve;
2328
+ });
2329
+ const state = {
2330
+ assigned: 0,
2331
+ settled: 0,
2332
+ successful: 0,
2333
+ consumed: 0,
2334
+ retainedPeak: 0
2335
+ };
2336
+
2337
+ const stats = () => ({
2338
+ worker_tasks: tasks.length,
2339
+ worker_count: poolSize,
2340
+ worker_tasks_assigned: state.assigned,
2341
+ worker_tasks_settled: state.settled,
2342
+ worker_tasks_unsettled_fallback: finished ? Math.max(0, tasks.length - state.settled) : 0,
2343
+ worker_results: state.successful,
2344
+ worker_results_consumed: state.consumed,
2345
+ worker_results_retained: results.size,
2346
+ worker_results_retained_peak: state.retainedPeak,
2347
+ worker_results_pending: finished ? 0 : Math.max(0, tasks.length - state.settled),
2348
+ worker_results_missing: tasks.length - state.successful,
2349
+ worker_waiters: waiters.size
2350
+ });
2351
+
2352
+ const finish = () => {
2353
+ if (finished) return;
2354
+ finished = true;
2355
+ for (const resolve of waiters.values()) {
2356
+ resolve(undefined);
2357
+ }
2358
+ waiters.clear();
2359
+ resolveDone();
2360
+ };
2361
+
2362
+ // A task is "settled" once it has a result, was skipped, or its worker
2363
+ // died holding it. Finish when every task is settled, or when no worker is
2364
+ // left alive to make progress (any still-queued tasks then parse inline).
2365
+ const maybeFinish = () => {
2366
+ if (state.settled >= tasks.length || alive <= 0) {
2367
+ finish();
2368
+ }
2369
+ };
2370
+
2371
+ const resolveWaiter = (taskId, result) => {
2372
+ const resolve = waiters.get(taskId);
2373
+ if (!resolve) {
2374
+ return false;
2375
+ }
2376
+ waiters.delete(taskId);
2377
+ if (result !== undefined) {
2378
+ state.consumed += 1;
2379
+ }
2380
+ resolve(result);
2381
+ return true;
2382
+ };
2383
+
2384
+ const settleTask = (taskId, result) => {
2385
+ state.settled += 1;
2386
+ if (result !== undefined) {
2387
+ state.successful += 1;
2388
+ if (!resolveWaiter(taskId, result)) {
2389
+ results.set(taskId, result);
2390
+ state.retainedPeak = Math.max(state.retainedPeak, results.size);
2391
+ }
2392
+ } else {
2393
+ missingResults.add(taskId);
2394
+ resolveWaiter(taskId, undefined);
2395
+ }
2396
+ maybeFinish();
2397
+ };
2398
+
2399
+ const assign = (worker) => {
2400
+ if (nextTask >= tasks.length) {
2401
+ inflight.set(worker, null);
2402
+ worker.postMessage({ type: "shutdown" });
2403
+ return;
2404
+ }
2405
+ const task = tasks[nextTask++];
2406
+ state.assigned += 1;
2407
+ inflight.set(worker, task.id);
2408
+ worker.postMessage({
2409
+ taskId: task.id,
2410
+ ext: task.ext,
2411
+ content: task.content,
2412
+ absolutePath: task.absolutePath,
2413
+ contentLimit: task.contentLimit,
2414
+ filePath: task.path
2415
+ });
2416
+ };
2417
+
2418
+ const onMessage = (worker, message) => {
2419
+ if (finished) return;
2420
+ inflight.set(worker, null);
2421
+ if (message.ok) {
2422
+ settleTask(message.taskId, message.result);
2423
+ } else {
2424
+ if (verbose) {
2425
+ console.log(`[ingest] worker skipped ${message.taskId}: ${message.reason}`);
2426
+ }
2427
+ settleTask(message.taskId, undefined);
2428
+ }
2429
+ if (!finished) {
2430
+ assign(worker);
2431
+ }
2432
+ };
2433
+
2434
+ const onExit = (worker) => {
2435
+ if (finished) return;
2436
+ alive -= 1;
2437
+ const taskId = inflight.get(worker);
2438
+ if (taskId != null) {
2439
+ // Worker exited mid-parse without posting a result (OOM, native abort,
2440
+ // process.exit) and without an 'error' event. Count its in-flight task
2441
+ // as settled so it falls back to inline parsing rather than leaving the
2442
+ // pool waiting on a dead worker forever.
2443
+ if (verbose) {
2444
+ console.log(`[ingest] worker exited mid-task ${taskId}; will parse inline`);
2445
+ }
2446
+ inflight.set(worker, null);
2447
+ settleTask(taskId, undefined);
2448
+ return;
2449
+ }
2450
+ maybeFinish();
2451
+ };
2452
+
2453
+ for (let i = 0; i < poolSize; i += 1) {
2454
+ const worker = new Worker(resolvedWorkerUrl);
2455
+ workers.push(worker);
2456
+ worker.on("message", (message) => onMessage(worker, message));
2457
+ worker.on("error", (error) => {
2458
+ // Uncaught exception in the worker. The 'exit' event that always
2459
+ // follows does the task accounting (idempotent via inflight), so we
2460
+ // only log here to avoid double-counting.
2461
+ if (verbose) {
2462
+ console.log(`[ingest] worker error: ${error.message}`);
2463
+ }
2464
+ });
2465
+ worker.on("exit", () => onExit(worker));
2466
+ }
2467
+
2468
+ for (const worker of workers) {
2469
+ assign(worker);
2470
+ }
2471
+
2472
+ return {
2473
+ hasTask(taskId) {
2474
+ return taskIds.has(taskId);
2475
+ },
2476
+ stats,
2477
+ async take(taskId) {
2478
+ if (!taskIds.has(taskId)) {
2479
+ return undefined;
2480
+ }
2481
+ if (results.has(taskId)) {
2482
+ const result = results.get(taskId);
2483
+ results.delete(taskId);
2484
+ state.consumed += 1;
2485
+ return result;
2486
+ }
2487
+ if (missingResults.has(taskId) || finished) {
2488
+ return undefined;
2489
+ }
2490
+ return new Promise((resolve) => {
2491
+ waiters.set(taskId, resolve);
2492
+ });
2493
+ },
2494
+ async drain() {
2495
+ await done;
2496
+ await Promise.all(workers.map((worker) => worker.terminate().catch(() => {})));
2497
+ return stats();
2498
+ }
2499
+ };
2500
+ }
2501
+
2502
+ // Parse files in a worker pool, returning Map<fileId, parseResult> for the
2503
+ // tasks that completed. Any task not present (worker miss, skip, or a fatal
2504
+ // worker error) is left to the caller's inline parse — the same parse
2505
+ // function on the same content, so output is identical either way. Never
2506
+ // rejects: a fatal worker error resolves with the partial results collected
2507
+ // so far.
2508
+ async function parseFilesInWorkers(tasks, options = {}) {
2509
+ const stream = startWorkerParseStream(tasks, options);
2510
+ const results = new Map();
2511
+ for (const task of tasks) {
2512
+ const result = await stream.take(task.id);
2513
+ if (result) {
2514
+ results.set(task.id, result);
2515
+ }
2516
+ }
2517
+
2518
+ await stream.drain();
2519
+ return results;
2520
+ }
2521
+
2531
2522
  async function main() {
2532
- await loadOptionalParsers();
2523
+ await loadParsers();
2533
2524
  const { mode, verbose } = parseArgs(process.argv);
2525
+ const memoryTrace = createIngestMemoryTrace();
2534
2526
  const configPath = path.join(CONTEXT_DIR, "config.yaml");
2535
2527
  const rulesPath = path.join(CONTEXT_DIR, "rules.yaml");
2536
2528
 
@@ -2551,6 +2543,11 @@ async function main() {
2551
2543
  }
2552
2544
 
2553
2545
  const rules = parseRules(fs.readFileSync(rulesPath, "utf8"));
2546
+ memoryTrace.checkpoint("scan:start", {
2547
+ mode,
2548
+ source_paths: sourcePaths.length,
2549
+ rules: rules.length
2550
+ });
2554
2551
  const { candidates, incrementalMode, deletedRelPaths } = collectCandidateFiles(sourcePaths, mode);
2555
2552
  const chunkWindowLines = parsePositiveIntegerEnv(
2556
2553
  "CORTEX_CHUNK_WINDOW_LINES",
@@ -2704,6 +2701,16 @@ async function main() {
2704
2701
 
2705
2702
  const fileRecords = [...fileRecordMap.values()].sort((a, b) => a.path.localeCompare(b.path));
2706
2703
  const adrRecords = [...adrRecordMap.values()].sort((a, b) => a.path.localeCompare(b.path));
2704
+ memoryTrace.checkpoint("scan:file_records", {
2705
+ candidates: candidates.size,
2706
+ incremental_mode: incrementalMode,
2707
+ deleted_paths: deletedRelPaths.length,
2708
+ files: fileRecords.length,
2709
+ adrs: adrRecords.length,
2710
+ skipped_unsupported: skipped.unsupported,
2711
+ skipped_too_large: skipped.tooLarge,
2712
+ skipped_binary: skipped.binary
2713
+ });
2707
2714
  const csharpFileCount = fileRecords.filter((record) => path.extname(record.path).toLowerCase() === ".cs").length;
2708
2715
  const csharpRuntime = csharpFileCount > 0 ? getCSharpParserRuntime() : null;
2709
2716
  const indexedFileIds = new Set(fileRecords.map((record) => record.id));
@@ -2726,6 +2733,14 @@ async function main() {
2726
2733
  importsRelationMap: new Map(),
2727
2734
  callsSqlRelationMap: new Map()
2728
2735
  };
2736
+ memoryTrace.checkpoint("hydration:complete", {
2737
+ incremental_mode: incrementalMode,
2738
+ cached_chunks: chunkRecordMap.size,
2739
+ cached_defines_relations: definesRelationMap.size,
2740
+ cached_calls_relations: callsRelationMap.size,
2741
+ cached_imports_relations: importsRelationMap.size,
2742
+ cached_calls_sql_relations: callsSqlRelationMap.size
2743
+ });
2729
2744
 
2730
2745
  const cachedChunkFileIds = new Set(
2731
2746
  [...chunkRecordMap.values()].map((record) => String(record.file_id ?? "")).filter(Boolean)
@@ -2754,7 +2769,7 @@ async function main() {
2754
2769
  // if batch isn't usable.
2755
2770
  const csharpBatchCache = new Map();
2756
2771
  if (
2757
- typeof parseCSharpProject === "function" &&
2772
+ hasCSharpProjectParser() &&
2758
2773
  isCSharpParserAvailable() &&
2759
2774
  process.env.CORTEX_CSHARP_BATCH !== "never"
2760
2775
  ) {
@@ -2780,6 +2795,9 @@ async function main() {
2780
2795
  }
2781
2796
  }
2782
2797
 
2798
+ // Determine which files need parsing (single pass over the gates, shared by
2799
+ // the worker dispatch and the merge loop below so the two cannot diverge).
2800
+ const parseEligible = new Map();
2783
2801
  for (const fileRecord of fileRecords) {
2784
2802
  const ext = path.extname(fileRecord.path).toLowerCase();
2785
2803
  const parser = getChunkParserForExtension(ext);
@@ -2793,6 +2811,57 @@ async function main() {
2793
2811
  if (!shouldParseFile) {
2794
2812
  continue;
2795
2813
  }
2814
+ parseEligible.set(fileRecord.id, { parser, ext });
2815
+ }
2816
+ memoryTrace.checkpoint("parse:eligible", {
2817
+ files: fileRecords.length,
2818
+ parse_eligible: parseEligible.size,
2819
+ csharp_batch_cached: csharpBatchCache.size
2820
+ });
2821
+
2822
+ // Parse parallel-safe files (tree-sitter/JS, no subprocess, no cross-file
2823
+ // state) in a worker pool. C# batch-cached files and any worker miss fall
2824
+ // through to inline parsing in the loop, so the result is byte-identical to
2825
+ // the sequential path regardless of where a file actually parsed.
2826
+ const workerTasks = fileRecords
2827
+ .filter((fileRecord) => {
2828
+ const eligible = parseEligible.get(fileRecord.id);
2829
+ if (!eligible) return false;
2830
+ if (!PARALLEL_SAFE_LANGUAGES.has(eligible.parser.language)) return false;
2831
+ if (eligible.parser.language === "csharp" && csharpBatchCache.has(fileRecord.path)) return false;
2832
+ return true;
2833
+ })
2834
+ .map((fileRecord) => ({
2835
+ id: fileRecord.id,
2836
+ ext: parseEligible.get(fileRecord.id).ext,
2837
+ absolutePath: path.resolve(REPO_ROOT, fileRecord.path),
2838
+ contentLimit: MAX_CONTENT_CHARS,
2839
+ path: fileRecord.path
2840
+ }));
2841
+ const workerCount = resolveIngestWorkerCount(workerTasks.length);
2842
+ memoryTrace.checkpoint("parse:workers_start", {
2843
+ worker_tasks: workerTasks.length,
2844
+ worker_count: workerCount
2845
+ });
2846
+ const workerStream =
2847
+ workerCount > 1 ? startWorkerParseStream(workerTasks, { workerCount, verbose }) : null;
2848
+ if (!workerStream) {
2849
+ memoryTrace.checkpoint("parse:workers_complete", {
2850
+ worker_tasks: workerTasks.length,
2851
+ worker_count: workerCount,
2852
+ worker_results: 0,
2853
+ worker_results_consumed: 0,
2854
+ worker_results_retained: 0,
2855
+ worker_results_retained_peak: 0,
2856
+ worker_results_pending: 0,
2857
+ worker_results_missing: workerTasks.length
2858
+ });
2859
+ }
2860
+
2861
+ for (const fileRecord of fileRecords) {
2862
+ const eligible = parseEligible.get(fileRecord.id);
2863
+ if (!eligible) continue;
2864
+ const { parser } = eligible;
2796
2865
 
2797
2866
  removeChunkStateForFile(
2798
2867
  fileRecord.id,
@@ -2804,9 +2873,17 @@ async function main() {
2804
2873
  );
2805
2874
 
2806
2875
  try {
2807
- const parseResult = parser.language === "csharp" && csharpBatchCache.has(fileRecord.path)
2808
- ? csharpBatchCache.get(fileRecord.path)
2809
- : await parser.parse(fileRecord.content, fileRecord.path, parser.language);
2876
+ let parseResult;
2877
+ if (parser.language === "csharp" && csharpBatchCache.has(fileRecord.path)) {
2878
+ parseResult = csharpBatchCache.get(fileRecord.path);
2879
+ } else if (workerStream?.hasTask(fileRecord.id)) {
2880
+ parseResult = await workerStream.take(fileRecord.id);
2881
+ if (!parseResult) {
2882
+ parseResult = await parser.parse(fileRecord.content, fileRecord.path, parser.language);
2883
+ }
2884
+ } else {
2885
+ parseResult = await parser.parse(fileRecord.content, fileRecord.path, parser.language);
2886
+ }
2810
2887
 
2811
2888
  if (parseResult.errors.length > 0 && verbose) {
2812
2889
  console.log(`[ingest] parse errors in ${fileRecord.path}:`, parseResult.errors[0].message);
@@ -2955,6 +3032,27 @@ async function main() {
2955
3032
  }
2956
3033
  }
2957
3034
  }
3035
+ const finalWorkerStats = workerStream ? await workerStream.drain() : null;
3036
+ if (finalWorkerStats) {
3037
+ memoryTrace.checkpoint("parse:workers_complete", finalWorkerStats);
3038
+ if (verbose) {
3039
+ console.log(
3040
+ `[ingest] parsed ${finalWorkerStats.worker_results}/${workerTasks.length} files across ${workerCount} workers`
3041
+ );
3042
+ }
3043
+ }
3044
+ memoryTrace.checkpoint("parse:merge_complete", {
3045
+ chunk_map: chunkRecordMap.size,
3046
+ defines_relations: definesRelationMap.size,
3047
+ calls_relations: callsRelationMap.size,
3048
+ imports_relations: importsRelationMap.size,
3049
+ calls_sql_relations: callsSqlRelationMap.size,
3050
+ deferred_sql_edges: deferredSqlCallEdges.length,
3051
+ windowed_chunks: windowedChunkCount,
3052
+ worker_results_retained: finalWorkerStats?.worker_results_retained ?? 0,
3053
+ worker_results_retained_peak: finalWorkerStats?.worker_results_retained_peak ?? 0,
3054
+ worker_results_pending: finalWorkerStats?.worker_results_pending ?? 0
3055
+ });
2958
3056
 
2959
3057
  const chunkRecords = [...chunkRecordMap.values()].sort((a, b) => String(a.id).localeCompare(String(b.id)));
2960
3058
  ({
@@ -3084,6 +3182,17 @@ async function main() {
3084
3182
  const validUsesSettingKeyRelations = [...usesSettingKeyRelationMap.values()].filter(
3085
3183
  (rel) => indexedFileIds.has(rel.from) && chunkIdSet.has(rel.to)
3086
3184
  );
3185
+ memoryTrace.checkpoint("materialize:chunks_relations", {
3186
+ chunks: chunkRecords.length,
3187
+ chunk_ids: chunkIdSet.size,
3188
+ relations_defines: validDefinesRelations.length,
3189
+ relations_calls: validCallsRelations.length,
3190
+ relations_imports: validImportsRelations.length,
3191
+ relations_calls_sql: validCallsSqlRelations.length,
3192
+ relations_uses_config_key: validUsesConfigKeyRelations.length,
3193
+ relations_uses_resource_key: validUsesResourceKeyRelations.length,
3194
+ relations_uses_setting_key: validUsesSettingKeyRelations.length
3195
+ });
3087
3196
 
3088
3197
  if (verbose && chunkRecords.length > 0) {
3089
3198
  console.log(`[ingest] extracted ${chunkRecords.length} chunks from ${fileRecords.filter(f => f.kind === "CODE").length} code files`);
@@ -3146,6 +3255,19 @@ async function main() {
3146
3255
  ...sectionHandlerRelations
3147
3256
  ]);
3148
3257
  const configTransformRelations = generateConfigTransformRelations(fileRecords);
3258
+ memoryTrace.checkpoint("materialize:modules_projects_relations", {
3259
+ modules: moduleRecords.length,
3260
+ projects: projectRecords.length,
3261
+ relations_contains: moduleContainsRelations.length,
3262
+ relations_contains_module: moduleContainsModuleRelations.length,
3263
+ relations_exports: moduleExportsRelations.length,
3264
+ relations_includes_file: projectIncludesFileRelations.length,
3265
+ relations_references_project: projectReferencesProjectRelations.length,
3266
+ relations_uses_resource: usesResourceRelations.length,
3267
+ relations_uses_setting: usesSettingRelations.length,
3268
+ relations_uses_config: usesConfigRelations.length,
3269
+ relations_transforms_config: configTransformRelations.length
3270
+ });
3149
3271
 
3150
3272
  if (verbose && moduleRecords.length > 0) {
3151
3273
  console.log(`[ingest] modules=${moduleRecords.length} contains=${moduleContainsRelations.length} contains_module=${moduleContainsModuleRelations.length} exports=${moduleExportsRelations.length}`);
@@ -3211,9 +3333,10 @@ async function main() {
3211
3333
  const implementsRelations = [];
3212
3334
  const constrainsSeen = new Set();
3213
3335
  const implementsSeen = new Set();
3214
- const lowerContentByFileId = new Map(
3215
- fileRecords.map((fileRecord) => [fileRecord.id, fileRecord.content.toLowerCase()])
3216
- );
3336
+ memoryTrace.checkpoint("tokens:rule_matching_start", {
3337
+ files: fileRecords.length,
3338
+ rules: ruleRecords.length
3339
+ });
3217
3340
  const tokenByFileId = new Map(fileRecords.map((fileRecord) => [fileRecord.id, fileTokenSet(fileRecord)]));
3218
3341
 
3219
3342
  for (const ruleRecord of ruleRecords) {
@@ -3221,7 +3344,7 @@ async function main() {
3221
3344
  const ruleKeywords = normalizeRuleTokens(ruleRecord);
3222
3345
 
3223
3346
  for (const fileRecord of fileRecords) {
3224
- const lower = lowerContentByFileId.get(fileRecord.id) ?? "";
3347
+ const lower = fileRecord.content.toLowerCase();
3225
3348
  const explicitMention = lower.includes(needle);
3226
3349
  const tokens = tokenByFileId.get(fileRecord.id) ?? new Set();
3227
3350
  const matchedKeywords = ruleKeywords.filter((keyword) => tokens.has(keyword));
@@ -3259,7 +3382,20 @@ async function main() {
3259
3382
  }
3260
3383
  }
3261
3384
  }
3385
+ memoryTrace.checkpoint("tokens:rule_matching_complete", {
3386
+ file_token_sets: tokenByFileId.size,
3387
+ rules: ruleRecords.length,
3388
+ relations_constrains: constrainsRelations.length,
3389
+ relations_implements: implementsRelations.length,
3390
+ relations_supersedes: supersedesRelations.length
3391
+ });
3262
3392
 
3393
+ memoryTrace.checkpoint("writes:cache_start", {
3394
+ files: fileRecords.length,
3395
+ adrs: adrRecords.length,
3396
+ rules: ruleRecords.length,
3397
+ chunks: chunkRecords.length
3398
+ });
3263
3399
  writeJsonl(path.join(CACHE_DIR, "documents.jsonl"), fileRecords);
3264
3400
  writeJsonl(path.join(CACHE_DIR, "entities.file.jsonl"), fileRecords);
3265
3401
  writeJsonl(path.join(CACHE_DIR, "entities.adr.jsonl"), adrRecords);
@@ -3289,7 +3425,15 @@ async function main() {
3289
3425
  path.join(CACHE_DIR, "relations.references_project.jsonl"),
3290
3426
  projectReferencesProjectRelations
3291
3427
  );
3428
+ memoryTrace.checkpoint("writes:cache_complete", {
3429
+ jsonl_files: 26,
3430
+ files: fileRecords.length,
3431
+ chunks: chunkRecords.length
3432
+ });
3292
3433
 
3434
+ memoryTrace.checkpoint("writes:db_start", {
3435
+ tsv_files: 21
3436
+ });
3293
3437
  writeTsv(
3294
3438
  path.join(DB_IMPORT_DIR, "file_nodes.tsv"),
3295
3439
  [
@@ -3303,7 +3447,7 @@ async function main() {
3303
3447
  "trust_level",
3304
3448
  "status"
3305
3449
  ],
3306
- fileRecords.map((record) => [
3450
+ mapRows(fileRecords, (record) => [
3307
3451
  record.id,
3308
3452
  record.path,
3309
3453
  record.kind,
@@ -3329,7 +3473,7 @@ async function main() {
3329
3473
  "trust_level",
3330
3474
  "status"
3331
3475
  ],
3332
- ruleRecords.map((record) => [
3476
+ mapRows(ruleRecords, (record) => [
3333
3477
  record.id,
3334
3478
  record.title,
3335
3479
  record.body,
@@ -3355,7 +3499,7 @@ async function main() {
3355
3499
  "trust_level",
3356
3500
  "status"
3357
3501
  ],
3358
- adrRecords.map((record) => [
3502
+ mapRows(adrRecords, (record) => [
3359
3503
  record.id,
3360
3504
  record.path,
3361
3505
  record.title,
@@ -3371,19 +3515,19 @@ async function main() {
3371
3515
  writeTsv(
3372
3516
  path.join(DB_IMPORT_DIR, "constrains_rel.tsv"),
3373
3517
  ["from", "to", "note"],
3374
- constrainsRelations.map((record) => [record.from, record.to, record.note])
3518
+ mapRows(constrainsRelations, (record) => [record.from, record.to, record.note])
3375
3519
  );
3376
3520
 
3377
3521
  writeTsv(
3378
3522
  path.join(DB_IMPORT_DIR, "implements_rel.tsv"),
3379
3523
  ["from", "to", "note"],
3380
- implementsRelations.map((record) => [record.from, record.to, record.note])
3524
+ mapRows(implementsRelations, (record) => [record.from, record.to, record.note])
3381
3525
  );
3382
3526
 
3383
3527
  writeTsv(
3384
3528
  path.join(DB_IMPORT_DIR, "supersedes_rel.tsv"),
3385
3529
  ["from", "to", "reason"],
3386
- supersedesRelations.map((record) => [record.from, record.to, record.reason])
3530
+ mapRows(supersedesRelations, (record) => [record.from, record.to, record.reason])
3387
3531
  );
3388
3532
 
3389
3533
  writeTsv(
@@ -3402,7 +3546,7 @@ async function main() {
3402
3546
  "updated_at",
3403
3547
  "trust_level"
3404
3548
  ],
3405
- chunkRecords.map((record) => [
3549
+ mapRows(chunkRecords, (record) => [
3406
3550
  record.id,
3407
3551
  record.file_id,
3408
3552
  record.name,
@@ -3421,43 +3565,43 @@ async function main() {
3421
3565
  writeTsv(
3422
3566
  path.join(DB_IMPORT_DIR, "defines_rel.tsv"),
3423
3567
  ["from", "to"],
3424
- validDefinesRelations.map((record) => [record.from, record.to])
3568
+ mapRows(validDefinesRelations, (record) => [record.from, record.to])
3425
3569
  );
3426
3570
 
3427
3571
  writeTsv(
3428
3572
  path.join(DB_IMPORT_DIR, "calls_rel.tsv"),
3429
3573
  ["from", "to", "call_type"],
3430
- validCallsRelations.map((record) => [record.from, record.to, record.call_type])
3574
+ mapRows(validCallsRelations, (record) => [record.from, record.to, record.call_type])
3431
3575
  );
3432
3576
 
3433
3577
  writeTsv(
3434
3578
  path.join(DB_IMPORT_DIR, "imports_rel.tsv"),
3435
3579
  ["from", "to", "import_name"],
3436
- validImportsRelations.map((record) => [record.from, record.to, record.import_name])
3580
+ mapRows(validImportsRelations, (record) => [record.from, record.to, record.import_name])
3437
3581
  );
3438
3582
 
3439
3583
  writeTsv(
3440
3584
  path.join(DB_IMPORT_DIR, "calls_sql_rel.tsv"),
3441
3585
  ["from", "to", "note"],
3442
- validCallsSqlRelations.map((record) => [record.from, record.to, record.note])
3586
+ mapRows(validCallsSqlRelations, (record) => [record.from, record.to, record.note])
3443
3587
  );
3444
3588
 
3445
3589
  writeTsv(
3446
3590
  path.join(DB_IMPORT_DIR, "uses_config_key_rel.tsv"),
3447
3591
  ["from", "to", "note"],
3448
- validUsesConfigKeyRelations.map((record) => [record.from, record.to, record.note])
3592
+ mapRows(validUsesConfigKeyRelations, (record) => [record.from, record.to, record.note])
3449
3593
  );
3450
3594
 
3451
3595
  writeTsv(
3452
3596
  path.join(DB_IMPORT_DIR, "uses_resource_key_rel.tsv"),
3453
3597
  ["from", "to", "note"],
3454
- validUsesResourceKeyRelations.map((record) => [record.from, record.to, record.note])
3598
+ mapRows(validUsesResourceKeyRelations, (record) => [record.from, record.to, record.note])
3455
3599
  );
3456
3600
 
3457
3601
  writeTsv(
3458
3602
  path.join(DB_IMPORT_DIR, "uses_setting_key_rel.tsv"),
3459
3603
  ["from", "to", "note"],
3460
- validUsesSettingKeyRelations.map((record) => [record.from, record.to, record.note])
3604
+ mapRows(validUsesSettingKeyRelations, (record) => [record.from, record.to, record.note])
3461
3605
  );
3462
3606
 
3463
3607
  writeTsv(
@@ -3476,7 +3620,7 @@ async function main() {
3476
3620
  "trust_level",
3477
3621
  "status"
3478
3622
  ],
3479
- projectRecords.map((record) => [
3623
+ mapRows(projectRecords, (record) => [
3480
3624
  record.id,
3481
3625
  record.path,
3482
3626
  record.name,
@@ -3495,38 +3639,41 @@ async function main() {
3495
3639
  writeTsv(
3496
3640
  path.join(DB_IMPORT_DIR, "includes_file_rel.tsv"),
3497
3641
  ["from", "to"],
3498
- projectIncludesFileRelations.map((record) => [record.from, record.to])
3642
+ mapRows(projectIncludesFileRelations, (record) => [record.from, record.to])
3499
3643
  );
3500
3644
 
3501
3645
  writeTsv(
3502
3646
  path.join(DB_IMPORT_DIR, "references_project_rel.tsv"),
3503
3647
  ["from", "to", "note"],
3504
- projectReferencesProjectRelations.map((record) => [record.from, record.to, record.note])
3648
+ mapRows(projectReferencesProjectRelations, (record) => [record.from, record.to, record.note])
3505
3649
  );
3506
3650
 
3507
3651
  writeTsv(
3508
3652
  path.join(DB_IMPORT_DIR, "uses_resource_rel.tsv"),
3509
3653
  ["from", "to", "note"],
3510
- usesResourceRelations.map((record) => [record.from, record.to, record.note])
3654
+ mapRows(usesResourceRelations, (record) => [record.from, record.to, record.note])
3511
3655
  );
3512
3656
 
3513
3657
  writeTsv(
3514
3658
  path.join(DB_IMPORT_DIR, "uses_setting_rel.tsv"),
3515
3659
  ["from", "to", "note"],
3516
- usesSettingRelations.map((record) => [record.from, record.to, record.note])
3660
+ mapRows(usesSettingRelations, (record) => [record.from, record.to, record.note])
3517
3661
  );
3518
3662
 
3519
3663
  writeTsv(
3520
3664
  path.join(DB_IMPORT_DIR, "uses_config_rel.tsv"),
3521
3665
  ["from", "to", "note"],
3522
- usesConfigRelations.map((record) => [record.from, record.to, record.note])
3666
+ mapRows(usesConfigRelations, (record) => [record.from, record.to, record.note])
3523
3667
  );
3524
3668
 
3525
3669
  writeTsv(
3526
3670
  path.join(DB_IMPORT_DIR, "transforms_config_rel.tsv"),
3527
3671
  ["from", "to", "note"],
3528
- configTransformRelations.map((record) => [record.from, record.to, record.note])
3672
+ mapRows(configTransformRelations, (record) => [record.from, record.to, record.note])
3529
3673
  );
3674
+ memoryTrace.checkpoint("writes:db_complete", {
3675
+ tsv_files: 21
3676
+ });
3530
3677
 
3531
3678
  const manifest = {
3532
3679
  generated_at: new Date().toISOString(),
@@ -3567,6 +3714,30 @@ async function main() {
3567
3714
  };
3568
3715
 
3569
3716
  fs.writeFileSync(path.join(CACHE_DIR, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
3717
+ memoryTrace.checkpoint("writes:manifest_complete", {
3718
+ files: manifest.counts.files,
3719
+ chunks: manifest.counts.chunks,
3720
+ total_relations:
3721
+ manifest.counts.relations_constrains +
3722
+ manifest.counts.relations_implements +
3723
+ manifest.counts.relations_supersedes +
3724
+ manifest.counts.relations_defines +
3725
+ manifest.counts.relations_calls +
3726
+ manifest.counts.relations_imports +
3727
+ manifest.counts.relations_calls_sql +
3728
+ manifest.counts.relations_uses_config_key +
3729
+ manifest.counts.relations_uses_resource_key +
3730
+ manifest.counts.relations_uses_setting_key +
3731
+ manifest.counts.relations_contains +
3732
+ manifest.counts.relations_contains_module +
3733
+ manifest.counts.relations_exports +
3734
+ manifest.counts.relations_includes_file +
3735
+ manifest.counts.relations_references_project +
3736
+ manifest.counts.relations_uses_resource +
3737
+ manifest.counts.relations_uses_setting +
3738
+ manifest.counts.relations_uses_config +
3739
+ manifest.counts.relations_transforms_config
3740
+ });
3570
3741
 
3571
3742
  console.log(`[ingest] mode=${mode}`);
3572
3743
  if (incrementalMode) {
@@ -3616,5 +3787,6 @@ export {
3616
3787
  generateProjects,
3617
3788
  generateSectionHandlerRelations,
3618
3789
  getChunkParserForExtension,
3790
+ parseFilesInWorkers,
3619
3791
  resolveRelativeImportTargetId
3620
3792
  };