@danielblomma/cortex-mcp 2.2.2 → 2.2.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@danielblomma/cortex-mcp",
3
3
  "mcpName": "io.github.DanielBlomma/cortex",
4
- "version": "2.2.2",
4
+ "version": "2.2.3",
5
5
  "description": "Local, repo-scoped context platform for coding assistants. Semantic search, graph relationships, and architectural rule context.",
6
6
  "type": "module",
7
7
  "author": "Daniel Blomma",
@@ -64,7 +64,7 @@
64
64
  ],
65
65
  "scripts": {
66
66
  "pretest": "test -d scaffold/scripts/parsers/node_modules || npm --prefix scaffold/scripts/parsers install --no-fund --no-update-notifier --silent",
67
- "test": "node tests/context-regressions.test.mjs && node --test tests/ingest-units.test.mjs tests/ingest-parallel.test.mjs tests/ingest-worker-crash.test.mjs tests/javascript-parser.test.mjs tests/markdown-parser.test.mjs tests/sql-parser.test.mjs tests/config-parser.test.mjs tests/resources-parser.test.mjs tests/vbnet-parser.test.mjs tests/cpp-parser.test.mjs tests/dashboard.test.mjs tests/init-config.test.mjs tests/init-agents.test.mjs tests/multi-level.test.mjs tests/no-legacy-paths.test.mjs tests/tree-sitter-error-reporting.test.mjs tests/tree-sitter-body-cap.test.mjs tests/tree-sitter-exported.test.mjs tests/tree-sitter-robustness.test.mjs tests/bootstrapbench-stats.test.mjs tests/bootstrapbench-aggregate.test.mjs tests/bootstrapbench-cleanup.test.mjs tests/query-cli-shim.test.mjs",
67
+ "test": "node tests/context-regressions.test.mjs && node --test tests/ingest-units.test.mjs tests/ingest-parallel.test.mjs tests/ingest-worker-crash.test.mjs tests/javascript-parser.test.mjs tests/markdown-parser.test.mjs tests/sql-parser.test.mjs tests/config-parser.test.mjs tests/resources-parser.test.mjs tests/vbnet-parser.test.mjs tests/cpp-parser.test.mjs tests/dashboard.test.mjs tests/init-config.test.mjs tests/init-agents.test.mjs tests/multi-level.test.mjs tests/no-legacy-paths.test.mjs tests/tree-sitter-error-reporting.test.mjs tests/tree-sitter-body-cap.test.mjs tests/tree-sitter-exported.test.mjs tests/tree-sitter-robustness.test.mjs tests/bootstrapbench-run.test.mjs tests/bootstrapbench-stats.test.mjs tests/bootstrapbench-aggregate.test.mjs tests/bootstrapbench-cleanup.test.mjs tests/query-cli-shim.test.mjs",
68
68
  "release:sync-version": "node scripts/sync-release-version.mjs",
69
69
  "release:check-version-sync": "node scripts/sync-release-version.mjs --check",
70
70
  "prepublishOnly": "echo 'Ready to publish to npm'"
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import ryugraph, { type Connection, type PreparedStatement, type QueryResult, type RyuValue } from "ryugraph";
4
- import { readJsonl, asString, asNumber, asBoolean } from "./jsonl.js";
4
+ import { readJsonl, readJsonlRecords, asString, asNumber, asBoolean } from "./jsonl.js";
5
5
  import { CACHE_DIR, CONTEXT_DIR, DB_PATH } from "./paths.js";
6
6
  import { CSV_COPY_OPTIONS, writeCsv, toCopyPathLiteral, type CsvValue } from "./graphCsv.js";
7
7
  import type { JsonObject } from "./types.js";
@@ -127,232 +127,244 @@ function readEntityFile(fileName: string): JsonObject[] {
127
127
  return readJsonl(path.join(CACHE_DIR, fileName));
128
128
  }
129
129
 
130
- function parseFiles(raw: JsonObject[]): FileEntity[] {
131
- return raw
132
- .map((item) => {
133
- const id = asString(item.id);
134
- const filePath = asString(item.path);
135
- if (!id || !filePath) {
136
- return null;
137
- }
130
+ function readEntityRecords(fileName: string): Iterable<JsonObject> {
131
+ return readJsonlRecords(path.join(CACHE_DIR, fileName));
132
+ }
133
+
134
+ function* parseFiles(raw: Iterable<JsonObject>): Generator<FileEntity> {
135
+ for (const item of raw) {
136
+ const id = asString(item.id);
137
+ const filePath = asString(item.path);
138
+ if (!id || !filePath) {
139
+ continue;
140
+ }
138
141
 
139
- return {
140
- id,
141
- path: filePath,
142
- kind: asString(item.kind, "DOC"),
143
- excerpt: asString(item.excerpt),
144
- checksum: asString(item.checksum),
145
- updated_at: asString(item.updated_at),
146
- source_of_truth: asBoolean(item.source_of_truth, false),
147
- trust_level: asNumber(item.trust_level, 50),
148
- status: asString(item.status, "active")
149
- };
150
- })
151
- .filter((value): value is FileEntity => value !== null);
142
+ yield {
143
+ id,
144
+ path: filePath,
145
+ kind: asString(item.kind, "DOC"),
146
+ excerpt: asString(item.excerpt),
147
+ checksum: asString(item.checksum),
148
+ updated_at: asString(item.updated_at),
149
+ source_of_truth: asBoolean(item.source_of_truth, false),
150
+ trust_level: asNumber(item.trust_level, 50),
151
+ status: asString(item.status, "active")
152
+ };
153
+ }
152
154
  }
153
155
 
154
- function parseRules(raw: JsonObject[]): RuleEntity[] {
155
- return raw
156
- .map((item) => {
157
- const id = asString(item.id);
158
- if (!id) {
159
- return null;
160
- }
156
+ function* parseRules(raw: Iterable<JsonObject>): Generator<RuleEntity> {
157
+ for (const item of raw) {
158
+ const id = asString(item.id);
159
+ if (!id) {
160
+ continue;
161
+ }
161
162
 
162
- return {
163
- id,
164
- title: asString(item.title, id),
165
- body: asString(item.body),
166
- scope: asString(item.scope, "global"),
167
- updated_at: asString(item.updated_at),
168
- source_of_truth: asBoolean(item.source_of_truth, true),
169
- trust_level: asNumber(item.trust_level, 95),
170
- status: asString(item.status, "active"),
171
- priority: asNumber(item.priority, 0)
172
- };
173
- })
174
- .filter((value): value is RuleEntity => value !== null);
163
+ yield {
164
+ id,
165
+ title: asString(item.title, id),
166
+ body: asString(item.body),
167
+ scope: asString(item.scope, "global"),
168
+ updated_at: asString(item.updated_at),
169
+ source_of_truth: asBoolean(item.source_of_truth, true),
170
+ trust_level: asNumber(item.trust_level, 95),
171
+ status: asString(item.status, "active"),
172
+ priority: asNumber(item.priority, 0)
173
+ };
174
+ }
175
175
  }
176
176
 
177
- function parseAdrs(raw: JsonObject[]): AdrEntity[] {
178
- return raw
179
- .map((item) => {
180
- const id = asString(item.id);
181
- if (!id) {
182
- return null;
183
- }
177
+ function* parseAdrs(raw: Iterable<JsonObject>): Generator<AdrEntity> {
178
+ for (const item of raw) {
179
+ const id = asString(item.id);
180
+ if (!id) {
181
+ continue;
182
+ }
184
183
 
185
- return {
186
- id,
187
- path: asString(item.path),
188
- title: asString(item.title, id),
189
- body: asString(item.body),
190
- decision_date: asString(item.decision_date),
191
- supersedes_id: asString(item.supersedes_id),
192
- source_of_truth: asBoolean(item.source_of_truth, true),
193
- trust_level: asNumber(item.trust_level, 95),
194
- status: asString(item.status, "active")
195
- };
196
- })
197
- .filter((value): value is AdrEntity => value !== null);
184
+ yield {
185
+ id,
186
+ path: asString(item.path),
187
+ title: asString(item.title, id),
188
+ body: asString(item.body),
189
+ decision_date: asString(item.decision_date),
190
+ supersedes_id: asString(item.supersedes_id),
191
+ source_of_truth: asBoolean(item.source_of_truth, true),
192
+ trust_level: asNumber(item.trust_level, 95),
193
+ status: asString(item.status, "active")
194
+ };
195
+ }
198
196
  }
199
197
 
200
- function parseChunks(raw: JsonObject[]): ChunkEntity[] {
201
- return raw
202
- .map((item) => {
203
- const id = asString(item.id);
204
- const file_id = asString(item.file_id);
205
- const name = asString(item.name);
206
- if (!id || !file_id || !name) {
207
- return null;
208
- }
198
+ function* parseChunks(raw: Iterable<JsonObject>): Generator<ChunkEntity> {
199
+ for (const item of raw) {
200
+ const id = asString(item.id);
201
+ const file_id = asString(item.file_id);
202
+ const name = asString(item.name);
203
+ if (!id || !file_id || !name) {
204
+ continue;
205
+ }
209
206
 
210
- return {
211
- id,
212
- file_id,
213
- name,
214
- kind: asString(item.kind, "function"),
215
- signature: asString(item.signature),
216
- body: asString(item.body),
217
- description: asString(item.description),
218
- start_line: asNumber(item.start_line, 0),
219
- end_line: asNumber(item.end_line, 0),
220
- language: asString(item.language, "javascript"),
221
- exported: asBoolean(item.exported, false),
222
- checksum: asString(item.checksum),
223
- updated_at: asString(item.updated_at),
224
- source_of_truth: asBoolean(item.source_of_truth, true),
225
- trust_level: asNumber(item.trust_level, 80),
226
- status: asString(item.status, "active")
227
- };
228
- })
229
- .filter((value): value is ChunkEntity => value !== null);
207
+ yield {
208
+ id,
209
+ file_id,
210
+ name,
211
+ kind: asString(item.kind, "function"),
212
+ signature: asString(item.signature),
213
+ body: asString(item.body),
214
+ description: asString(item.description),
215
+ start_line: asNumber(item.start_line, 0),
216
+ end_line: asNumber(item.end_line, 0),
217
+ language: asString(item.language, "javascript"),
218
+ exported: asBoolean(item.exported, false),
219
+ checksum: asString(item.checksum),
220
+ updated_at: asString(item.updated_at),
221
+ source_of_truth: asBoolean(item.source_of_truth, true),
222
+ trust_level: asNumber(item.trust_level, 80),
223
+ status: asString(item.status, "active")
224
+ };
225
+ }
230
226
  }
231
227
 
232
- function parseModules(raw: JsonObject[]): ModuleEntity[] {
233
- return raw
234
- .map((item) => {
235
- const id = asString(item.id);
236
- if (!id) {
237
- return null;
238
- }
228
+ function* parseModules(raw: Iterable<JsonObject>): Generator<ModuleEntity> {
229
+ for (const item of raw) {
230
+ const id = asString(item.id);
231
+ if (!id) {
232
+ continue;
233
+ }
239
234
 
240
- return {
241
- id,
242
- path: asString(item.path),
243
- name: asString(item.name),
244
- summary: asString(item.summary),
245
- file_count: asNumber(item.file_count, 0),
246
- exported_symbols: asString(item.exported_symbols),
247
- updated_at: asString(item.updated_at),
248
- source_of_truth: asBoolean(item.source_of_truth, false),
249
- trust_level: asNumber(item.trust_level, 75),
250
- status: asString(item.status, "active")
251
- };
252
- })
253
- .filter((value): value is ModuleEntity => value !== null);
235
+ yield {
236
+ id,
237
+ path: asString(item.path),
238
+ name: asString(item.name),
239
+ summary: asString(item.summary),
240
+ file_count: asNumber(item.file_count, 0),
241
+ exported_symbols: asString(item.exported_symbols),
242
+ updated_at: asString(item.updated_at),
243
+ source_of_truth: asBoolean(item.source_of_truth, false),
244
+ trust_level: asNumber(item.trust_level, 75),
245
+ status: asString(item.status, "active")
246
+ };
247
+ }
254
248
  }
255
249
 
256
- function parseProjects(raw: JsonObject[]): ProjectEntity[] {
257
- return raw
258
- .map((item) => {
259
- const id = asString(item.id);
260
- if (!id) {
261
- return null;
262
- }
250
+ function* parseProjects(raw: Iterable<JsonObject>): Generator<ProjectEntity> {
251
+ for (const item of raw) {
252
+ const id = asString(item.id);
253
+ if (!id) {
254
+ continue;
255
+ }
263
256
 
264
- return {
265
- id,
266
- path: asString(item.path),
267
- name: asString(item.name),
268
- kind: asString(item.kind, "project"),
269
- language: asString(item.language, "dotnet"),
270
- target_framework: asString(item.target_framework),
271
- summary: asString(item.summary),
272
- file_count: asNumber(item.file_count, 0),
273
- updated_at: asString(item.updated_at),
274
- source_of_truth: asBoolean(item.source_of_truth, false),
275
- trust_level: asNumber(item.trust_level, 80),
276
- status: asString(item.status, "active")
277
- };
278
- })
279
- .filter((value): value is ProjectEntity => value !== null);
257
+ yield {
258
+ id,
259
+ path: asString(item.path),
260
+ name: asString(item.name),
261
+ kind: asString(item.kind, "project"),
262
+ language: asString(item.language, "dotnet"),
263
+ target_framework: asString(item.target_framework),
264
+ summary: asString(item.summary),
265
+ file_count: asNumber(item.file_count, 0),
266
+ updated_at: asString(item.updated_at),
267
+ source_of_truth: asBoolean(item.source_of_truth, false),
268
+ trust_level: asNumber(item.trust_level, 80),
269
+ status: asString(item.status, "active")
270
+ };
271
+ }
272
+ }
273
+
274
+ function* parseRelationRecords(raw: Iterable<JsonObject>, noteField: string): Generator<Relation> {
275
+ for (const item of raw) {
276
+ const from = asString(item.from);
277
+ const to = asString(item.to);
278
+ if (!from || !to) {
279
+ continue;
280
+ }
281
+
282
+ yield {
283
+ from,
284
+ to,
285
+ note: asString(item[noteField as keyof JsonObject])
286
+ };
287
+ }
280
288
  }
281
289
 
282
290
  function parseRelations(fileName: string, noteField: string): Relation[] {
283
- const raw = readEntityFile(fileName);
284
- return raw
285
- .map((item) => {
286
- const from = asString(item.from);
287
- const to = asString(item.to);
288
- if (!from || !to) {
289
- return null;
290
- }
291
+ return Array.from(parseRelationRecords(readEntityRecords(fileName), noteField));
292
+ }
293
+
294
+ function* relationRecords(fileName: string, noteField: string): Generator<Relation> {
295
+ yield* parseRelationRecords(readEntityRecords(fileName), noteField);
296
+ }
297
+
298
+ function* parseCallRelationRecords(raw: Iterable<JsonObject>): Generator<CallRelation> {
299
+ for (const item of raw) {
300
+ const from = asString(item.from);
301
+ const to = asString(item.to);
302
+ if (!from || !to) {
303
+ continue;
304
+ }
291
305
 
292
- return {
293
- from,
294
- to,
295
- note: asString(item[noteField as keyof JsonObject])
296
- };
297
- })
298
- .filter((value): value is Relation => value !== null);
306
+ yield {
307
+ from,
308
+ to,
309
+ call_type: asString(item.call_type, "direct")
310
+ };
311
+ }
299
312
  }
300
313
 
301
314
  function parseCallRelations(fileName: string): CallRelation[] {
302
- const raw = readEntityFile(fileName);
303
- return raw
304
- .map((item) => {
305
- const from = asString(item.from);
306
- const to = asString(item.to);
307
- if (!from || !to) {
308
- return null;
309
- }
315
+ return Array.from(parseCallRelationRecords(readEntityRecords(fileName)));
316
+ }
317
+
318
+ function* callRelationRecords(fileName: string): Generator<CallRelation> {
319
+ yield* parseCallRelationRecords(readEntityRecords(fileName));
320
+ }
321
+
322
+ function* parseImportRelationRecords(raw: Iterable<JsonObject>): Generator<ImportRelation> {
323
+ for (const item of raw) {
324
+ const from = asString(item.from);
325
+ const to = asString(item.to);
326
+ if (!from || !to) {
327
+ continue;
328
+ }
310
329
 
311
- return {
312
- from,
313
- to,
314
- call_type: asString(item.call_type, "direct")
315
- };
316
- })
317
- .filter((value): value is CallRelation => value !== null);
330
+ yield {
331
+ from,
332
+ to,
333
+ import_name: asString(item.import_name, "")
334
+ };
335
+ }
318
336
  }
319
337
 
320
338
  function parseImportRelations(fileName: string): ImportRelation[] {
321
- const raw = readEntityFile(fileName);
322
- return raw
323
- .map((item) => {
324
- const from = asString(item.from);
325
- const to = asString(item.to);
326
- if (!from || !to) {
327
- return null;
328
- }
339
+ return Array.from(parseImportRelationRecords(readEntityRecords(fileName)));
340
+ }
341
+
342
+ function* importRelationRecords(fileName: string): Generator<ImportRelation> {
343
+ yield* parseImportRelationRecords(readEntityRecords(fileName));
344
+ }
329
345
 
330
- return {
331
- from,
332
- to,
333
- import_name: asString(item.import_name, "")
334
- };
335
- })
336
- .filter((value): value is ImportRelation => value !== null);
346
+ function* parseSimpleRelationRecords(raw: Iterable<JsonObject>): Generator<Relation> {
347
+ for (const item of raw) {
348
+ const from = asString(item.from);
349
+ const to = asString(item.to);
350
+ if (!from || !to) {
351
+ continue;
352
+ }
353
+
354
+ yield {
355
+ from,
356
+ to,
357
+ note: ""
358
+ };
359
+ }
337
360
  }
338
361
 
339
362
  function parseSimpleRelations(fileName: string): Relation[] {
340
- const raw = readEntityFile(fileName);
341
- return raw
342
- .map((item) => {
343
- const from = asString(item.from);
344
- const to = asString(item.to);
345
- if (!from || !to) {
346
- return null;
347
- }
363
+ return Array.from(parseSimpleRelationRecords(readEntityRecords(fileName)));
364
+ }
348
365
 
349
- return {
350
- from,
351
- to,
352
- note: ""
353
- };
354
- })
355
- .filter((value): value is Relation => value !== null);
366
+ function* simpleRelationRecords(fileName: string): Generator<Relation> {
367
+ yield* parseSimpleRelationRecords(readEntityRecords(fileName));
356
368
  }
357
369
 
358
370
  async function rows(result: QueryResult | QueryResult[]): Promise<Record<string, unknown>[]> {
@@ -460,12 +472,12 @@ type GraphData = {
460
472
 
461
473
  function parseGraphData(): GraphData {
462
474
  return {
463
- fileEntities: parseFiles(readEntityFile("entities.file.jsonl")),
464
- ruleEntities: parseRules(readEntityFile("entities.rule.jsonl")),
465
- adrEntities: parseAdrs(readEntityFile("entities.adr.jsonl")),
466
- chunkEntities: parseChunks(readEntityFile("entities.chunk.jsonl")),
467
- moduleEntities: parseModules(readEntityFile("entities.module.jsonl")),
468
- projectEntities: parseProjects(readEntityFile("entities.project.jsonl")),
475
+ fileEntities: Array.from(parseFiles(readEntityFile("entities.file.jsonl"))),
476
+ ruleEntities: Array.from(parseRules(readEntityFile("entities.rule.jsonl"))),
477
+ adrEntities: Array.from(parseAdrs(readEntityFile("entities.adr.jsonl"))),
478
+ chunkEntities: Array.from(parseChunks(readEntityFile("entities.chunk.jsonl"))),
479
+ moduleEntities: Array.from(parseModules(readEntityFile("entities.module.jsonl"))),
480
+ projectEntities: Array.from(parseProjects(readEntityFile("entities.project.jsonl"))),
469
481
  constrains: parseRelations("relations.constrains.jsonl", "note"),
470
482
  implementsEdges: parseRelations("relations.implements.jsonl", "note"),
471
483
  supersedes: parseRelations("relations.supersedes.jsonl", "reason"),
@@ -506,9 +518,10 @@ function filterEdges<T extends { from: string; to: string }>(
506
518
  })();
507
519
  }
508
520
 
509
- function* ids<T extends { id: string }>(items: Iterable<T>): Iterable<string> {
521
+ function* collectIds<T extends { id: string }>(items: Iterable<T>, collected: Set<string>): Iterable<T> {
510
522
  for (const item of items) {
511
- yield item.id;
523
+ collected.add(item.id);
524
+ yield item;
512
525
  }
513
526
  }
514
527
 
@@ -537,28 +550,28 @@ async function copyTable(
537
550
  await conn.query(`COPY ${table} FROM "${csvForCopy}" ${CSV_COPY_OPTIONS};`);
538
551
  }
539
552
 
540
- // Bulk path: one COPY per table instead of a prepared statement per row.
541
- // Only used on the reset path, where the freshly created tables are empty.
542
- // Produces a byte-identical graph to rowByRowLoad (same parsed arrays, same
543
- // node-then-edge order, same dangling-edge filtering).
544
- async function bulkLoad(conn: Connection, data: GraphData): Promise<void> {
553
+ // Bulk path: one COPY per table instead of a prepared statement per row. Only
554
+ // used on the reset path, where the freshly created tables are empty. It reads
555
+ // node and relation JSONL streams directly into CSVs so the hot path retains
556
+ // only endpoint id sets rather than a full parsed GraphData object.
557
+ async function bulkLoad(conn: Connection): Promise<void> {
545
558
  fs.rmSync(GRAPH_IMPORT_DIR, { recursive: true, force: true });
546
559
  fs.mkdirSync(GRAPH_IMPORT_DIR, { recursive: true });
547
560
 
548
561
  try {
549
- const fileIds = new Set(ids(data.fileEntities));
550
- const ruleIds = new Set(ids(data.ruleEntities));
551
- const adrIds = new Set(ids(data.adrEntities));
552
- const chunkIds = new Set(ids(data.chunkEntities));
553
- const moduleIds = new Set(ids(data.moduleEntities));
554
- const projectIds = new Set(ids(data.projectEntities));
562
+ const fileIds = new Set<string>();
563
+ const ruleIds = new Set<string>();
564
+ const adrIds = new Set<string>();
565
+ const chunkIds = new Set<string>();
566
+ const moduleIds = new Set<string>();
567
+ const projectIds = new Set<string>();
555
568
 
556
569
  // Nodes first — edges reference them by primary key.
557
570
  await copyTable(
558
571
  conn,
559
572
  "File",
560
573
  ["id", "path", "kind", "excerpt", "checksum", "updated_at", "source_of_truth", "trust_level", "status"],
561
- csvRows(data.fileEntities, (e) => [
574
+ csvRows(collectIds(parseFiles(readEntityRecords("entities.file.jsonl")), fileIds), (e) => [
562
575
  e.id, e.path, e.kind, e.excerpt, e.checksum, e.updated_at, e.source_of_truth, e.trust_level, e.status
563
576
  ])
564
577
  );
@@ -566,7 +579,7 @@ async function bulkLoad(conn: Connection, data: GraphData): Promise<void> {
566
579
  conn,
567
580
  "Rule",
568
581
  ["id", "title", "body", "scope", "priority", "updated_at", "source_of_truth", "trust_level", "status"],
569
- csvRows(data.ruleEntities, (e) => [
582
+ csvRows(collectIds(parseRules(readEntityRecords("entities.rule.jsonl")), ruleIds), (e) => [
570
583
  e.id, e.title, e.body, e.scope, e.priority, e.updated_at, e.source_of_truth, e.trust_level, e.status
571
584
  ])
572
585
  );
@@ -574,7 +587,7 @@ async function bulkLoad(conn: Connection, data: GraphData): Promise<void> {
574
587
  conn,
575
588
  "ADR",
576
589
  ["id", "path", "title", "body", "decision_date", "supersedes_id", "source_of_truth", "trust_level", "status"],
577
- csvRows(data.adrEntities, (e) => [
590
+ csvRows(collectIds(parseAdrs(readEntityRecords("entities.adr.jsonl")), adrIds), (e) => [
578
591
  e.id, e.path, e.title, e.body, e.decision_date, e.supersedes_id, e.source_of_truth, e.trust_level, e.status
579
592
  ])
580
593
  );
@@ -585,7 +598,7 @@ async function bulkLoad(conn: Connection, data: GraphData): Promise<void> {
585
598
  "id", "file_id", "name", "kind", "signature", "body", "description", "start_line", "end_line",
586
599
  "language", "exported", "checksum", "updated_at", "source_of_truth", "trust_level", "status"
587
600
  ],
588
- csvRows(data.chunkEntities, (e) => [
601
+ csvRows(collectIds(parseChunks(readEntityRecords("entities.chunk.jsonl")), chunkIds), (e) => [
589
602
  e.id, e.file_id, e.name, e.kind, e.signature, e.body, e.description, e.start_line, e.end_line,
590
603
  e.language, e.exported, e.checksum, e.updated_at, e.source_of_truth, e.trust_level, e.status
591
604
  ])
@@ -594,7 +607,7 @@ async function bulkLoad(conn: Connection, data: GraphData): Promise<void> {
594
607
  conn,
595
608
  "Module",
596
609
  ["id", "path", "name", "summary", "file_count", "exported_symbols", "updated_at", "source_of_truth", "trust_level", "status"],
597
- csvRows(data.moduleEntities, (e) => [
610
+ csvRows(collectIds(parseModules(readEntityRecords("entities.module.jsonl")), moduleIds), (e) => [
598
611
  e.id, e.path, e.name, e.summary, e.file_count, e.exported_symbols, e.updated_at, e.source_of_truth, e.trust_level, e.status
599
612
  ])
600
613
  );
@@ -605,7 +618,7 @@ async function bulkLoad(conn: Connection, data: GraphData): Promise<void> {
605
618
  "id", "path", "name", "kind", "language", "target_framework", "summary", "file_count",
606
619
  "updated_at", "source_of_truth", "trust_level", "status"
607
620
  ],
608
- csvRows(data.projectEntities, (e) => [
621
+ csvRows(collectIds(parseProjects(readEntityRecords("entities.project.jsonl")), projectIds), (e) => [
609
622
  e.id, e.path, e.name, e.kind, e.language, e.target_framework, e.summary, e.file_count,
610
623
  e.updated_at, e.source_of_truth, e.trust_level, e.status
611
624
  ])
@@ -615,43 +628,43 @@ async function bulkLoad(conn: Connection, data: GraphData): Promise<void> {
615
628
  // (COPY into a rel table reads src key, dst key, then properties in schema
616
629
  // order), but emitting real names keeps the CSVs self-describing.
617
630
  await copyTable(conn, "CONSTRAINS", ["from", "to", "note"],
618
- csvRows(filterEdges(data.constrains, ruleIds, fileIds), (e) => [e.from, e.to, e.note]));
631
+ csvRows(filterEdges(relationRecords("relations.constrains.jsonl", "note"), ruleIds, fileIds), (e) => [e.from, e.to, e.note]));
619
632
  await copyTable(conn, "IMPLEMENTS", ["from", "to", "note"],
620
- csvRows(filterEdges(data.implementsEdges, fileIds, ruleIds), (e) => [e.from, e.to, e.note]));
633
+ csvRows(filterEdges(relationRecords("relations.implements.jsonl", "note"), fileIds, ruleIds), (e) => [e.from, e.to, e.note]));
621
634
  await copyTable(conn, "SUPERSEDES", ["from", "to", "reason"],
622
- csvRows(filterEdges(data.supersedes, adrIds, adrIds), (e) => [e.from, e.to, e.note]));
635
+ csvRows(filterEdges(relationRecords("relations.supersedes.jsonl", "reason"), adrIds, adrIds), (e) => [e.from, e.to, e.note]));
623
636
  await copyTable(conn, "DEFINES", ["from", "to"],
624
- csvRows(filterEdges(data.defines, fileIds, chunkIds), (e) => [e.from, e.to]));
637
+ csvRows(filterEdges(simpleRelationRecords("relations.defines.jsonl"), fileIds, chunkIds), (e) => [e.from, e.to]));
625
638
  await copyTable(conn, "CALLS", ["from", "to", "call_type"],
626
- csvRows(filterEdges(data.calls, chunkIds, chunkIds), (e) => [e.from, e.to, e.call_type]));
639
+ csvRows(filterEdges(callRelationRecords("relations.calls.jsonl"), chunkIds, chunkIds), (e) => [e.from, e.to, e.call_type]));
627
640
  await copyTable(conn, "IMPORTS", ["from", "to", "import_name"],
628
- csvRows(filterEdges(data.imports, chunkIds, fileIds), (e) => [e.from, e.to, e.import_name]));
641
+ csvRows(filterEdges(importRelationRecords("relations.imports.jsonl"), chunkIds, fileIds), (e) => [e.from, e.to, e.import_name]));
629
642
  await copyTable(conn, "CALLS_SQL", ["from", "to", "note"],
630
- csvRows(filterEdges(data.callsSql, fileIds, chunkIds), (e) => [e.from, e.to, e.note]));
643
+ csvRows(filterEdges(relationRecords("relations.calls_sql.jsonl", "note"), fileIds, chunkIds), (e) => [e.from, e.to, e.note]));
631
644
  await copyTable(conn, "USES_CONFIG_KEY", ["from", "to", "note"],
632
- csvRows(filterEdges(data.usesConfigKey, fileIds, chunkIds), (e) => [e.from, e.to, e.note]));
645
+ csvRows(filterEdges(relationRecords("relations.uses_config_key.jsonl", "note"), fileIds, chunkIds), (e) => [e.from, e.to, e.note]));
633
646
  await copyTable(conn, "USES_RESOURCE_KEY", ["from", "to", "note"],
634
- csvRows(filterEdges(data.usesResourceKey, fileIds, chunkIds), (e) => [e.from, e.to, e.note]));
647
+ csvRows(filterEdges(relationRecords("relations.uses_resource_key.jsonl", "note"), fileIds, chunkIds), (e) => [e.from, e.to, e.note]));
635
648
  await copyTable(conn, "USES_SETTING_KEY", ["from", "to", "note"],
636
- csvRows(filterEdges(data.usesSettingKey, fileIds, chunkIds), (e) => [e.from, e.to, e.note]));
649
+ csvRows(filterEdges(relationRecords("relations.uses_setting_key.jsonl", "note"), fileIds, chunkIds), (e) => [e.from, e.to, e.note]));
637
650
  await copyTable(conn, "CONTAINS", ["from", "to"],
638
- csvRows(filterEdges(data.contains, moduleIds, fileIds), (e) => [e.from, e.to]));
651
+ csvRows(filterEdges(simpleRelationRecords("relations.contains.jsonl"), moduleIds, fileIds), (e) => [e.from, e.to]));
639
652
  await copyTable(conn, "CONTAINS_MODULE", ["from", "to"],
640
- csvRows(filterEdges(data.containsModule, moduleIds, moduleIds), (e) => [e.from, e.to]));
653
+ csvRows(filterEdges(simpleRelationRecords("relations.contains_module.jsonl"), moduleIds, moduleIds), (e) => [e.from, e.to]));
641
654
  await copyTable(conn, "EXPORTS", ["from", "to"],
642
- csvRows(filterEdges(data.exports, moduleIds, chunkIds), (e) => [e.from, e.to]));
655
+ csvRows(filterEdges(simpleRelationRecords("relations.exports.jsonl"), moduleIds, chunkIds), (e) => [e.from, e.to]));
643
656
  await copyTable(conn, "INCLUDES_FILE", ["from", "to"],
644
- csvRows(filterEdges(data.includesFile, projectIds, fileIds), (e) => [e.from, e.to]));
657
+ csvRows(filterEdges(simpleRelationRecords("relations.includes_file.jsonl"), projectIds, fileIds), (e) => [e.from, e.to]));
645
658
  await copyTable(conn, "REFERENCES_PROJECT", ["from", "to", "note"],
646
- csvRows(filterEdges(data.referencesProject, projectIds, projectIds), (e) => [e.from, e.to, e.note]));
659
+ csvRows(filterEdges(relationRecords("relations.references_project.jsonl", "note"), projectIds, projectIds), (e) => [e.from, e.to, e.note]));
647
660
  await copyTable(conn, "USES_RESOURCE", ["from", "to", "note"],
648
- csvRows(filterEdges(data.usesResource, fileIds, fileIds), (e) => [e.from, e.to, e.note]));
661
+ csvRows(filterEdges(relationRecords("relations.uses_resource.jsonl", "note"), fileIds, fileIds), (e) => [e.from, e.to, e.note]));
649
662
  await copyTable(conn, "USES_SETTING", ["from", "to", "note"],
650
- csvRows(filterEdges(data.usesSetting, fileIds, fileIds), (e) => [e.from, e.to, e.note]));
663
+ csvRows(filterEdges(relationRecords("relations.uses_setting.jsonl", "note"), fileIds, fileIds), (e) => [e.from, e.to, e.note]));
651
664
  await copyTable(conn, "USES_CONFIG", ["from", "to", "note"],
652
- csvRows(filterEdges(data.usesConfig, fileIds, fileIds), (e) => [e.from, e.to, e.note]));
665
+ csvRows(filterEdges(relationRecords("relations.uses_config.jsonl", "note"), fileIds, fileIds), (e) => [e.from, e.to, e.note]));
653
666
  await copyTable(conn, "TRANSFORMS_CONFIG", ["from", "to", "note"],
654
- csvRows(filterEdges(data.transformsConfig, fileIds, fileIds), (e) => [e.from, e.to, e.note]));
667
+ csvRows(filterEdges(relationRecords("relations.transforms_config.jsonl", "note"), fileIds, fileIds), (e) => [e.from, e.to, e.note]));
655
668
  } finally {
656
669
  fs.rmSync(GRAPH_IMPORT_DIR, { recursive: true, force: true });
657
670
  }
@@ -942,13 +955,11 @@ async function main(): Promise<void> {
942
955
  const ontologyStatements = parseOntologyStatements(fs.readFileSync(ONTOLOGY_PATH, "utf8"));
943
956
  await executeStatements(conn, ontologyStatements);
944
957
 
945
- const data = parseGraphData();
946
-
947
958
  const bulkEnabled = reset && process.env.CORTEX_GRAPH_BULK_LOAD !== "never";
948
959
  let usedBulk = false;
949
960
  if (bulkEnabled) {
950
961
  try {
951
- await bulkLoad(conn, data);
962
+ await bulkLoad(conn);
952
963
  usedBulk = true;
953
964
  console.log("[graph-load] loaded via COPY bulk import");
954
965
  } catch (error) {
@@ -959,6 +970,7 @@ async function main(): Promise<void> {
959
970
  }
960
971
  }
961
972
  if (!usedBulk) {
973
+ const data = parseGraphData();
962
974
  await rowByRowLoad(conn, data);
963
975
  }
964
976