@danielblomma/cortex-mcp 2.1.1 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/bin/cortex.mjs +2 -0
- package/package.json +3 -2
- package/scaffold/mcp/build.mjs +92 -0
- package/scaffold/mcp/package.json +4 -4
- package/scaffold/mcp/src/embed.ts +181 -77
- package/scaffold/mcp/src/embedScheduler.ts +509 -0
- package/scaffold/mcp/src/embeddings.ts +31 -30
- package/scaffold/mcp/src/graphCsv.ts +65 -0
- package/scaffold/mcp/src/jsonl.ts +69 -14
- package/scaffold/mcp/src/loadGraph.ts +276 -40
- package/scaffold/mcp/src/lruCache.ts +41 -0
- package/scaffold/mcp/src/searchCore.ts +1 -1
- package/scaffold/mcp/src/searchResults.ts +3 -3
- package/scaffold/mcp/src/types.ts +6 -1
- package/scaffold/mcp/tests/embed-scheduler.test.mjs +474 -0
- package/scaffold/mcp/tests/embedding-jsonl.test.mjs +39 -0
- package/scaffold/mcp/tests/graph-bulk-load.test.mjs +258 -0
- package/scaffold/mcp/tests/graph-csv.test.mjs +118 -0
- package/scaffold/mcp/tests/lru-cache.test.mjs +37 -0
- package/scaffold/mcp/tests/vector-index.test.mjs +109 -0
- package/scaffold/mcp/tsconfig.json +3 -1
- package/scaffold/scripts/bootstrap.sh +34 -2
- package/scaffold/scripts/dashboard.mjs +1 -1
- package/scaffold/scripts/ingest-parsers.mjs +387 -0
- package/scaffold/scripts/ingest-worker.mjs +52 -0
- package/scaffold/scripts/ingest.mjs +550 -378
- package/scaffold/scripts/parsers/csharp.mjs +2 -1
- package/scaffold/scripts/parsers/javascript/ast.mjs +160 -8
- package/scaffold/scripts/parsers/javascript/chunks.mjs +129 -22
- package/scaffold/scripts/parsers/javascript/imports.mjs +38 -4
- package/scaffold/scripts/parsers/vbnet.mjs +2 -1
- package/scaffold/scripts/status.sh +50 -14
|
@@ -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 {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
1042
|
-
|
|
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
|
|
1052
|
-
|
|
1053
|
-
|
|
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 = {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
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
|
-
|
|
3215
|
-
fileRecords.
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
};
|