@danielblomma/cortex-mcp 0.4.5 → 0.6.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.
Files changed (61) hide show
  1. package/README.md +38 -42
  2. package/bin/cortex.mjs +32 -60
  3. package/package.json +15 -3
  4. package/scaffold/.context/ontology.cypher +47 -0
  5. package/scaffold/.githooks/post-commit +14 -0
  6. package/scaffold/.githooks/post-rewrite +23 -0
  7. package/scaffold/mcp/package-lock.json +16 -16
  8. package/scaffold/mcp/package.json +3 -1
  9. package/scaffold/mcp/src/contextEntities.ts +311 -0
  10. package/scaffold/mcp/src/defaults.ts +6 -0
  11. package/scaffold/mcp/src/embed.ts +163 -37
  12. package/scaffold/mcp/src/frontmatter.ts +39 -0
  13. package/scaffold/mcp/src/graph.ts +253 -130
  14. package/scaffold/mcp/src/graphMetrics.ts +12 -0
  15. package/scaffold/mcp/src/impactPresentation.ts +202 -0
  16. package/scaffold/mcp/src/impactRanking.ts +237 -0
  17. package/scaffold/mcp/src/impactResponse.ts +47 -0
  18. package/scaffold/mcp/src/impactResults.ts +173 -0
  19. package/scaffold/mcp/src/impactSeed.ts +33 -0
  20. package/scaffold/mcp/src/impactTraversal.ts +83 -0
  21. package/scaffold/mcp/src/jsonl.ts +34 -0
  22. package/scaffold/mcp/src/loadGraph.ts +345 -86
  23. package/scaffold/mcp/src/paths.ts +17 -1
  24. package/scaffold/mcp/src/presets.ts +137 -0
  25. package/scaffold/mcp/src/relatedResponse.ts +30 -0
  26. package/scaffold/mcp/src/relatedTraversal.ts +101 -0
  27. package/scaffold/mcp/src/rules.ts +27 -0
  28. package/scaffold/mcp/src/search.ts +186 -455
  29. package/scaffold/mcp/src/searchCore.ts +274 -0
  30. package/scaffold/mcp/src/searchResults.ts +133 -0
  31. package/scaffold/mcp/src/server.ts +95 -3
  32. package/scaffold/mcp/src/types.ts +82 -3
  33. package/scaffold/scripts/context.sh +12 -46
  34. package/scaffold/scripts/dashboard.mjs +797 -0
  35. package/scaffold/scripts/dashboard.sh +13 -0
  36. package/scaffold/scripts/ingest.mjs +2219 -59
  37. package/scaffold/scripts/install-git-hooks.sh +3 -1
  38. package/scaffold/scripts/memory-compile.mjs +232 -0
  39. package/scaffold/scripts/memory-compile.sh +20 -0
  40. package/scaffold/scripts/memory-lint.mjs +375 -0
  41. package/scaffold/scripts/memory-lint.sh +20 -0
  42. package/scaffold/scripts/parsers/config.mjs +178 -0
  43. package/scaffold/scripts/parsers/cpp.mjs +316 -0
  44. package/scaffold/scripts/parsers/dotnet/VbNetParser/Program.cs +374 -0
  45. package/scaffold/scripts/parsers/dotnet/VbNetParser/VbNetParser.csproj +13 -0
  46. package/scaffold/scripts/parsers/javascript/ast.mjs +61 -0
  47. package/scaffold/scripts/parsers/javascript/calls.mjs +53 -0
  48. package/scaffold/scripts/parsers/javascript/chunks.mjs +388 -0
  49. package/scaffold/scripts/parsers/javascript/imports.mjs +162 -0
  50. package/scaffold/scripts/parsers/javascript/patterns.mjs +82 -0
  51. package/scaffold/scripts/parsers/javascript/scope-analysis.mjs +3 -0
  52. package/scaffold/scripts/parsers/javascript/scope-builder.mjs +305 -0
  53. package/scaffold/scripts/parsers/javascript/scope-resolver.mjs +82 -0
  54. package/scaffold/scripts/parsers/javascript.mjs +27 -350
  55. package/scaffold/scripts/parsers/resources.mjs +166 -0
  56. package/scaffold/scripts/parsers/sql.mjs +137 -0
  57. package/scaffold/scripts/parsers/vbnet.mjs +143 -0
  58. package/scaffold/scripts/status.sh +0 -7
  59. package/scaffold/scripts/capture-note.sh +0 -55
  60. package/scaffold/scripts/plan-state-engine.cjs +0 -310
  61. package/scaffold/scripts/plan-state.sh +0 -71
@@ -1,7 +1,9 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import ryugraph, { type Connection, type QueryResult } from "ryugraph";
4
+ import ryugraph, { type Connection, type PreparedStatement, type QueryResult, type RyuValue } from "ryugraph";
5
+ import { readJsonl, asString, asNumber, asBoolean } from "./jsonl.js";
6
+ import type { JsonObject } from "./types.js";
5
7
 
6
8
  const __filename = fileURLToPath(import.meta.url);
7
9
  const __dirname = path.dirname(__filename);
@@ -10,9 +12,19 @@ const CONTEXT_DIR = path.join(REPO_ROOT, ".context");
10
12
  const CACHE_DIR = path.join(CONTEXT_DIR, "cache");
11
13
  const DB_PATH = path.join(CONTEXT_DIR, "db", "graph.ryu");
12
14
  const ONTOLOGY_PATH = path.join(CONTEXT_DIR, "ontology.cypher");
13
-
14
- type JsonValue = string | number | boolean | null | JsonObject | JsonValue[];
15
- type JsonObject = { [key: string]: JsonValue };
15
+ const BATCH_SIZE = 50;
16
+
17
+ async function executeBatch(
18
+ conn: Connection,
19
+ statement: PreparedStatement,
20
+ items: Record<string, RyuValue>[],
21
+ batchSize = BATCH_SIZE
22
+ ): Promise<void> {
23
+ for (let i = 0; i < items.length; i += batchSize) {
24
+ const batch = items.slice(i, i + batchSize);
25
+ await Promise.all(batch.map((item) => conn.execute(statement, item)));
26
+ }
27
+ }
16
28
 
17
29
  type FileEntity = {
18
30
  id: string;
@@ -57,12 +69,44 @@ type ChunkEntity = {
57
69
  kind: string;
58
70
  signature: string;
59
71
  body: string;
72
+ description: string;
60
73
  start_line: number;
61
74
  end_line: number;
62
75
  language: string;
76
+ exported: boolean;
63
77
  checksum: string;
64
78
  updated_at: string;
79
+ source_of_truth: boolean;
65
80
  trust_level: number;
81
+ status: string;
82
+ };
83
+
84
+ type ModuleEntity = {
85
+ id: string;
86
+ path: string;
87
+ name: string;
88
+ summary: string;
89
+ file_count: number;
90
+ exported_symbols: string;
91
+ updated_at: string;
92
+ source_of_truth: boolean;
93
+ trust_level: number;
94
+ status: string;
95
+ };
96
+
97
+ type ProjectEntity = {
98
+ id: string;
99
+ path: string;
100
+ name: string;
101
+ kind: string;
102
+ language: string;
103
+ target_framework: string;
104
+ summary: string;
105
+ file_count: number;
106
+ updated_at: string;
107
+ source_of_truth: boolean;
108
+ trust_level: number;
109
+ status: string;
66
110
  };
67
111
 
68
112
  type Relation = {
@@ -83,38 +127,6 @@ type ImportRelation = {
83
127
  import_name: string;
84
128
  };
85
129
 
86
- function asString(value: JsonValue | undefined, fallback = ""): string {
87
- return typeof value === "string" ? value : fallback;
88
- }
89
-
90
- function asNumber(value: JsonValue | undefined, fallback = 0): number {
91
- return typeof value === "number" && Number.isFinite(value) ? value : fallback;
92
- }
93
-
94
- function asBoolean(value: JsonValue | undefined, fallback = false): boolean {
95
- return typeof value === "boolean" ? value : fallback;
96
- }
97
-
98
- function readJsonl(filePath: string): JsonObject[] {
99
- if (!fs.existsSync(filePath)) {
100
- return [];
101
- }
102
-
103
- return fs
104
- .readFileSync(filePath, "utf8")
105
- .split(/\r?\n/)
106
- .map((line) => line.trim())
107
- .filter(Boolean)
108
- .map((line) => {
109
- try {
110
- return JSON.parse(line) as JsonObject;
111
- } catch {
112
- return null;
113
- }
114
- })
115
- .filter((value): value is JsonObject => value !== null);
116
- }
117
-
118
130
  function readEntityFile(fileName: string): JsonObject[] {
119
131
  return readJsonl(path.join(CACHE_DIR, fileName));
120
132
  }
@@ -206,17 +218,71 @@ function parseChunks(raw: JsonObject[]): ChunkEntity[] {
206
218
  kind: asString(item.kind, "function"),
207
219
  signature: asString(item.signature),
208
220
  body: asString(item.body),
221
+ description: asString(item.description),
209
222
  start_line: asNumber(item.start_line, 0),
210
223
  end_line: asNumber(item.end_line, 0),
211
224
  language: asString(item.language, "javascript"),
225
+ exported: asBoolean(item.exported, false),
212
226
  checksum: asString(item.checksum),
213
227
  updated_at: asString(item.updated_at),
214
- trust_level: asNumber(item.trust_level, 80)
228
+ source_of_truth: asBoolean(item.source_of_truth, true),
229
+ trust_level: asNumber(item.trust_level, 80),
230
+ status: asString(item.status, "active")
215
231
  };
216
232
  })
217
233
  .filter((value): value is ChunkEntity => value !== null);
218
234
  }
219
235
 
236
+ function parseModules(raw: JsonObject[]): ModuleEntity[] {
237
+ return raw
238
+ .map((item) => {
239
+ const id = asString(item.id);
240
+ if (!id) {
241
+ return null;
242
+ }
243
+
244
+ return {
245
+ id,
246
+ path: asString(item.path),
247
+ name: asString(item.name),
248
+ summary: asString(item.summary),
249
+ file_count: asNumber(item.file_count, 0),
250
+ exported_symbols: asString(item.exported_symbols),
251
+ updated_at: asString(item.updated_at),
252
+ source_of_truth: asBoolean(item.source_of_truth, false),
253
+ trust_level: asNumber(item.trust_level, 75),
254
+ status: asString(item.status, "active")
255
+ };
256
+ })
257
+ .filter((value): value is ModuleEntity => value !== null);
258
+ }
259
+
260
+ function parseProjects(raw: JsonObject[]): ProjectEntity[] {
261
+ return raw
262
+ .map((item) => {
263
+ const id = asString(item.id);
264
+ if (!id) {
265
+ return null;
266
+ }
267
+
268
+ return {
269
+ id,
270
+ path: asString(item.path),
271
+ name: asString(item.name),
272
+ kind: asString(item.kind, "project"),
273
+ language: asString(item.language, "dotnet"),
274
+ target_framework: asString(item.target_framework),
275
+ summary: asString(item.summary),
276
+ file_count: asNumber(item.file_count, 0),
277
+ updated_at: asString(item.updated_at),
278
+ source_of_truth: asBoolean(item.source_of_truth, false),
279
+ trust_level: asNumber(item.trust_level, 80),
280
+ status: asString(item.status, "active")
281
+ };
282
+ })
283
+ .filter((value): value is ProjectEntity => value !== null);
284
+ }
285
+
220
286
  function parseRelations(fileName: string, noteField: string): Relation[] {
221
287
  const raw = readEntityFile(fileName);
222
288
  return raw
@@ -335,21 +401,36 @@ async function ensureRequiredFiles(): Promise<void> {
335
401
  }
336
402
  }
337
403
 
338
- function warnIfOptionalChunkFilesMissing(): void {
339
- const optionalChunkFiles = [
404
+ function warnIfOptionalFilesMissing(): void {
405
+ const optionalFiles = [
340
406
  "entities.chunk.jsonl",
341
407
  "relations.defines.jsonl",
342
408
  "relations.calls.jsonl",
343
- "relations.imports.jsonl"
409
+ "relations.calls_sql.jsonl",
410
+ "relations.uses_config_key.jsonl",
411
+ "relations.uses_resource_key.jsonl",
412
+ "relations.uses_setting_key.jsonl",
413
+ "relations.imports.jsonl",
414
+ "entities.module.jsonl",
415
+ "entities.project.jsonl",
416
+ "relations.contains.jsonl",
417
+ "relations.contains_module.jsonl",
418
+ "relations.exports.jsonl",
419
+ "relations.includes_file.jsonl",
420
+ "relations.references_project.jsonl",
421
+ "relations.uses_resource.jsonl",
422
+ "relations.uses_setting.jsonl",
423
+ "relations.uses_config.jsonl",
424
+ "relations.transforms_config.jsonl"
344
425
  ];
345
426
 
346
- const missing = optionalChunkFiles.filter((fileName) => !fs.existsSync(path.join(CACHE_DIR, fileName)));
427
+ const missing = optionalFiles.filter((fileName) => !fs.existsSync(path.join(CACHE_DIR, fileName)));
347
428
  if (missing.length === 0) {
348
429
  return;
349
430
  }
350
431
 
351
432
  console.warn(
352
- `[graph-load] warning: missing optional chunk files (${missing.join(", ")}); continuing without chunk nodes/relations`
433
+ `[graph-load] warning: missing optional files (${missing.join(", ")}); continuing without those nodes/relations`
353
434
  );
354
435
  }
355
436
 
@@ -358,7 +439,7 @@ async function main(): Promise<void> {
358
439
  const reset = !args.has("--no-reset");
359
440
 
360
441
  await ensureRequiredFiles();
361
- warnIfOptionalChunkFilesMissing();
442
+ warnIfOptionalFilesMissing();
362
443
 
363
444
  if (reset) {
364
445
  fs.rmSync(DB_PATH, { recursive: true, force: true });
@@ -371,15 +452,33 @@ async function main(): Promise<void> {
371
452
  const ontologyStatements = parseOntologyStatements(fs.readFileSync(ONTOLOGY_PATH, "utf8"));
372
453
  await executeStatements(conn, ontologyStatements);
373
454
 
455
+ // Delete all relations first, then all nodes, to avoid orphaned edges
374
456
  await conn.query("MATCH (a:ADR)-[r:SUPERSEDES]->(b:ADR) DELETE r;");
375
457
  await conn.query("MATCH (f:File)-[i:IMPLEMENTS]->(r:Rule) DELETE i;");
376
458
  await conn.query("MATCH (r:Rule)-[c:CONSTRAINS]->(f:File) DELETE c;");
377
459
  await conn.query("MATCH (f:File)-[d:DEFINES]->(c:Chunk) DELETE d;");
378
460
  await conn.query("MATCH (c1:Chunk)-[ca:CALLS]->(c2:Chunk) DELETE ca;");
379
461
  await conn.query("MATCH (c:Chunk)-[im:IMPORTS]->(f:File) DELETE im;");
462
+ await conn.query("MATCH (f:File)-[cs:CALLS_SQL]->(c:Chunk) DELETE cs;");
463
+ await conn.query("MATCH (f:File)-[uck:USES_CONFIG_KEY]->(c:Chunk) DELETE uck;");
464
+ await conn.query("MATCH (f:File)-[urk:USES_RESOURCE_KEY]->(c:Chunk) DELETE urk;");
465
+ await conn.query("MATCH (f:File)-[usk:USES_SETTING_KEY]->(c:Chunk) DELETE usk;");
466
+ await conn.query("MATCH (m:Module)-[co:CONTAINS]->(f:File) DELETE co;");
467
+ await conn.query("MATCH (m1:Module)-[cm:CONTAINS_MODULE]->(m2:Module) DELETE cm;");
468
+ await conn.query("MATCH (m:Module)-[ex:EXPORTS]->(c:Chunk) DELETE ex;");
469
+ await conn.query("MATCH (p:Project)-[inc:INCLUDES_FILE]->(f:File) DELETE inc;");
470
+ await conn.query("MATCH (p1:Project)-[rp:REFERENCES_PROJECT]->(p2:Project) DELETE rp;");
471
+ await conn.query("MATCH (f1:File)-[ur:USES_RESOURCE]->(f2:File) DELETE ur;");
472
+ await conn.query("MATCH (f1:File)-[us:USES_SETTING]->(f2:File) DELETE us;");
473
+ await conn.query("MATCH (f1:File)-[uc:USES_CONFIG]->(f2:File) DELETE uc;");
474
+ await conn.query("MATCH (f1:File)-[tc:TRANSFORMS_CONFIG]->(f2:File) DELETE tc;");
475
+
476
+ // Now delete all nodes
380
477
  await conn.query("MATCH (n:ADR) DELETE n;");
381
478
  await conn.query("MATCH (n:Rule) DELETE n;");
382
479
  await conn.query("MATCH (n:Chunk) DELETE n;");
480
+ await conn.query("MATCH (n:Module) DELETE n;");
481
+ await conn.query("MATCH (n:Project) DELETE n;");
383
482
  await conn.query("MATCH (n:File) DELETE n;");
384
483
 
385
484
  const fileEntities = parseFiles(readEntityFile("entities.file.jsonl"));
@@ -391,7 +490,22 @@ async function main(): Promise<void> {
391
490
  const supersedes = parseRelations("relations.supersedes.jsonl", "reason");
392
491
  const defines = parseSimpleRelations("relations.defines.jsonl");
393
492
  const calls = parseCallRelations("relations.calls.jsonl");
493
+ const callsSql = parseRelations("relations.calls_sql.jsonl", "note");
494
+ const usesConfigKey = parseRelations("relations.uses_config_key.jsonl", "note");
495
+ const usesResourceKey = parseRelations("relations.uses_resource_key.jsonl", "note");
496
+ const usesSettingKey = parseRelations("relations.uses_setting_key.jsonl", "note");
394
497
  const imports = parseImportRelations("relations.imports.jsonl");
498
+ const moduleEntities = parseModules(readEntityFile("entities.module.jsonl"));
499
+ const projectEntities = parseProjects(readEntityFile("entities.project.jsonl"));
500
+ const contains = parseSimpleRelations("relations.contains.jsonl");
501
+ const containsModule = parseSimpleRelations("relations.contains_module.jsonl");
502
+ const exports = parseSimpleRelations("relations.exports.jsonl");
503
+ const includesFile = parseSimpleRelations("relations.includes_file.jsonl");
504
+ const referencesProject = parseRelations("relations.references_project.jsonl", "note");
505
+ const usesResource = parseRelations("relations.uses_resource.jsonl", "note");
506
+ const usesSetting = parseRelations("relations.uses_setting.jsonl", "note");
507
+ const usesConfig = parseRelations("relations.uses_config.jsonl", "note");
508
+ const transformsConfig = parseRelations("relations.transforms_config.jsonl", "note");
395
509
 
396
510
  const insertFile = await conn.prepare(`
397
511
  CREATE (f:File {
@@ -443,12 +557,16 @@ async function main(): Promise<void> {
443
557
  kind: $kind,
444
558
  signature: $signature,
445
559
  body: $body,
560
+ description: $description,
446
561
  start_line: $start_line,
447
562
  end_line: $end_line,
448
563
  language: $language,
564
+ exported: $exported,
449
565
  checksum: $checksum,
450
566
  updated_at: $updated_at,
451
- trust_level: $trust_level
567
+ source_of_truth: $source_of_truth,
568
+ trust_level: $trust_level,
569
+ status: $status
452
570
  });
453
571
  `);
454
572
 
@@ -482,55 +600,135 @@ async function main(): Promise<void> {
482
600
  CREATE (c)-[:IMPORTS {import_name: $import_name}]->(f);
483
601
  `);
484
602
 
485
- for (const entity of fileEntities) {
486
- await conn.execute(insertFile, entity);
487
- }
603
+ const insertCallsSql = await conn.prepare(`
604
+ MATCH (f:File {id: $from}), (c:Chunk {id: $to})
605
+ CREATE (f)-[:CALLS_SQL {note: $note}]->(c);
606
+ `);
488
607
 
489
- for (const entity of ruleEntities) {
490
- await conn.execute(insertRule, {
491
- id: entity.id,
492
- title: entity.title,
493
- body: entity.body,
494
- scope: entity.scope,
495
- priority: entity.priority,
496
- updated_at: entity.updated_at,
497
- source_of_truth: entity.source_of_truth,
498
- trust_level: entity.trust_level,
499
- status: entity.status
608
+ const insertUsesConfigKey = await conn.prepare(`
609
+ MATCH (f:File {id: $from}), (c:Chunk {id: $to})
610
+ CREATE (f)-[:USES_CONFIG_KEY {note: $note}]->(c);
611
+ `);
612
+
613
+ const insertUsesResourceKey = await conn.prepare(`
614
+ MATCH (f:File {id: $from}), (c:Chunk {id: $to})
615
+ CREATE (f)-[:USES_RESOURCE_KEY {note: $note}]->(c);
616
+ `);
617
+
618
+ const insertUsesSettingKey = await conn.prepare(`
619
+ MATCH (f:File {id: $from}), (c:Chunk {id: $to})
620
+ CREATE (f)-[:USES_SETTING_KEY {note: $note}]->(c);
621
+ `);
622
+
623
+ const insertModule = await conn.prepare(`
624
+ CREATE (m:Module {
625
+ id: $id,
626
+ path: $path,
627
+ name: $name,
628
+ summary: $summary,
629
+ file_count: $file_count,
630
+ exported_symbols: $exported_symbols,
631
+ updated_at: $updated_at,
632
+ source_of_truth: $source_of_truth,
633
+ trust_level: $trust_level,
634
+ status: $status
500
635
  });
501
- }
636
+ `);
502
637
 
503
- for (const entity of adrEntities) {
504
- await conn.execute(insertAdr, entity);
505
- }
638
+ const insertProject = await conn.prepare(`
639
+ CREATE (p:Project {
640
+ id: $id,
641
+ path: $path,
642
+ name: $name,
643
+ kind: $kind,
644
+ language: $language,
645
+ target_framework: $target_framework,
646
+ summary: $summary,
647
+ file_count: $file_count,
648
+ updated_at: $updated_at,
649
+ source_of_truth: $source_of_truth,
650
+ trust_level: $trust_level,
651
+ status: $status
652
+ });
653
+ `);
506
654
 
507
- for (const entity of chunkEntities) {
508
- await conn.execute(insertChunk, entity);
509
- }
655
+ const insertContains = await conn.prepare(`
656
+ MATCH (m:Module {id: $from}), (f:File {id: $to})
657
+ CREATE (m)-[:CONTAINS]->(f);
658
+ `);
510
659
 
511
- for (const edge of defines) {
512
- await conn.execute(insertDefines, edge);
513
- }
660
+ const insertContainsModule = await conn.prepare(`
661
+ MATCH (m1:Module {id: $from}), (m2:Module {id: $to})
662
+ CREATE (m1)-[:CONTAINS_MODULE]->(m2);
663
+ `);
514
664
 
515
- for (const edge of calls) {
516
- await conn.execute(insertCalls, edge);
517
- }
665
+ const insertExports = await conn.prepare(`
666
+ MATCH (m:Module {id: $from}), (c:Chunk {id: $to})
667
+ CREATE (m)-[:EXPORTS]->(c);
668
+ `);
518
669
 
519
- for (const edge of imports) {
520
- await conn.execute(insertImports, edge);
521
- }
670
+ const insertIncludesFile = await conn.prepare(`
671
+ MATCH (p:Project {id: $from}), (f:File {id: $to})
672
+ CREATE (p)-[:INCLUDES_FILE]->(f);
673
+ `);
522
674
 
523
- for (const edge of constrains) {
524
- await conn.execute(insertConstrains, edge);
525
- }
675
+ const insertReferencesProject = await conn.prepare(`
676
+ MATCH (p1:Project {id: $from}), (p2:Project {id: $to})
677
+ CREATE (p1)-[:REFERENCES_PROJECT {note: $note}]->(p2);
678
+ `);
526
679
 
527
- for (const edge of implementsEdges) {
528
- await conn.execute(insertImplements, edge);
529
- }
680
+ const insertUsesResource = await conn.prepare(`
681
+ MATCH (f1:File {id: $from}), (f2:File {id: $to})
682
+ CREATE (f1)-[:USES_RESOURCE {note: $note}]->(f2);
683
+ `);
530
684
 
531
- for (const edge of supersedes) {
532
- await conn.execute(insertSupersedes, edge);
533
- }
685
+ const insertUsesSetting = await conn.prepare(`
686
+ MATCH (f1:File {id: $from}), (f2:File {id: $to})
687
+ CREATE (f1)-[:USES_SETTING {note: $note}]->(f2);
688
+ `);
689
+
690
+ const insertUsesConfig = await conn.prepare(`
691
+ MATCH (f1:File {id: $from}), (f2:File {id: $to})
692
+ CREATE (f1)-[:USES_CONFIG {note: $note}]->(f2);
693
+ `);
694
+
695
+ const insertTransformsConfig = await conn.prepare(`
696
+ MATCH (f1:File {id: $from}), (f2:File {id: $to})
697
+ CREATE (f1)-[:TRANSFORMS_CONFIG {note: $note}]->(f2);
698
+ `);
699
+
700
+ // Insert all nodes first (batched for performance)
701
+ await executeBatch(conn, insertFile, fileEntities);
702
+ await executeBatch(conn, insertRule, ruleEntities.map((e) => ({
703
+ id: e.id, title: e.title, body: e.body, scope: e.scope,
704
+ priority: e.priority, updated_at: e.updated_at,
705
+ source_of_truth: e.source_of_truth, trust_level: e.trust_level, status: e.status
706
+ })));
707
+ await executeBatch(conn, insertAdr, adrEntities);
708
+ await executeBatch(conn, insertChunk, chunkEntities);
709
+ await executeBatch(conn, insertModule, moduleEntities);
710
+ await executeBatch(conn, insertProject, projectEntities);
711
+
712
+ // Insert all edges (nodes must exist first)
713
+ await executeBatch(conn, insertDefines, defines);
714
+ await executeBatch(conn, insertCalls, calls);
715
+ await executeBatch(conn, insertImports, imports);
716
+ await executeBatch(conn, insertCallsSql, callsSql);
717
+ await executeBatch(conn, insertUsesConfigKey, usesConfigKey);
718
+ await executeBatch(conn, insertUsesResourceKey, usesResourceKey);
719
+ await executeBatch(conn, insertUsesSettingKey, usesSettingKey);
720
+ await executeBatch(conn, insertConstrains, constrains);
721
+ await executeBatch(conn, insertImplements, implementsEdges);
722
+ await executeBatch(conn, insertSupersedes, supersedes);
723
+ await executeBatch(conn, insertContains, contains);
724
+ await executeBatch(conn, insertContainsModule, containsModule);
725
+ await executeBatch(conn, insertExports, exports);
726
+ await executeBatch(conn, insertIncludesFile, includesFile);
727
+ await executeBatch(conn, insertReferencesProject, referencesProject);
728
+ await executeBatch(conn, insertUsesResource, usesResource);
729
+ await executeBatch(conn, insertUsesSetting, usesSetting);
730
+ await executeBatch(conn, insertUsesConfig, usesConfig);
731
+ await executeBatch(conn, insertTransformsConfig, transformsConfig);
534
732
 
535
733
  const fileCount = await rows(await conn.query("MATCH (f:File) RETURN count(*) AS count;"));
536
734
  const ruleCount = await rows(await conn.query("MATCH (r:Rule) RETURN count(*) AS count;"));
@@ -554,6 +752,49 @@ async function main(): Promise<void> {
554
752
  const importsCount = await rows(
555
753
  await conn.query("MATCH (:Chunk)-[im:IMPORTS]->(:File) RETURN count(im) AS count;")
556
754
  );
755
+ const callsSqlCount = await rows(
756
+ await conn.query("MATCH (:File)-[cs:CALLS_SQL]->(:Chunk) RETURN count(cs) AS count;")
757
+ );
758
+ const usesConfigKeyCount = await rows(
759
+ await conn.query("MATCH (:File)-[uck:USES_CONFIG_KEY]->(:Chunk) RETURN count(uck) AS count;")
760
+ );
761
+ const usesResourceKeyCount = await rows(
762
+ await conn.query("MATCH (:File)-[urk:USES_RESOURCE_KEY]->(:Chunk) RETURN count(urk) AS count;")
763
+ );
764
+ const usesSettingKeyCount = await rows(
765
+ await conn.query("MATCH (:File)-[usk:USES_SETTING_KEY]->(:Chunk) RETURN count(usk) AS count;")
766
+ );
767
+ const moduleCount = await rows(await conn.query("MATCH (m:Module) RETURN count(*) AS count;"));
768
+ const projectCount = await rows(await conn.query("MATCH (p:Project) RETURN count(*) AS count;"));
769
+ const containsCount = await rows(
770
+ await conn.query("MATCH (:Module)-[co:CONTAINS]->(:File) RETURN count(co) AS count;")
771
+ );
772
+ const containsModuleCount = await rows(
773
+ await conn.query("MATCH (:Module)-[cm:CONTAINS_MODULE]->(:Module) RETURN count(cm) AS count;")
774
+ );
775
+ const exportsCount = await rows(
776
+ await conn.query("MATCH (:Module)-[ex:EXPORTS]->(:Chunk) RETURN count(ex) AS count;")
777
+ );
778
+ const includesFileCount = await rows(
779
+ await conn.query("MATCH (:Project)-[inc:INCLUDES_FILE]->(:File) RETURN count(inc) AS count;")
780
+ );
781
+ const referencesProjectCount = await rows(
782
+ await conn.query(
783
+ "MATCH (:Project)-[rp:REFERENCES_PROJECT]->(:Project) RETURN count(rp) AS count;"
784
+ )
785
+ );
786
+ const usesResourceCount = await rows(
787
+ await conn.query("MATCH (:File)-[ur:USES_RESOURCE]->(:File) RETURN count(ur) AS count;")
788
+ );
789
+ const usesSettingCount = await rows(
790
+ await conn.query("MATCH (:File)-[us:USES_SETTING]->(:File) RETURN count(us) AS count;")
791
+ );
792
+ const usesConfigCount = await rows(
793
+ await conn.query("MATCH (:File)-[uc:USES_CONFIG]->(:File) RETURN count(uc) AS count;")
794
+ );
795
+ const transformsConfigCount = await rows(
796
+ await conn.query("MATCH (:File)-[tc:TRANSFORMS_CONFIG]->(:File) RETURN count(tc) AS count;")
797
+ );
557
798
 
558
799
  const summary = {
559
800
  generated_at: new Date().toISOString(),
@@ -568,7 +809,22 @@ async function main(): Promise<void> {
568
809
  supersedes: Number(supersedesCount[0]?.count ?? 0),
569
810
  defines: Number(definesCount[0]?.count ?? 0),
570
811
  calls: Number(callsCount[0]?.count ?? 0),
571
- imports: Number(importsCount[0]?.count ?? 0)
812
+ imports: Number(importsCount[0]?.count ?? 0),
813
+ calls_sql: Number(callsSqlCount[0]?.count ?? 0),
814
+ uses_config_key: Number(usesConfigKeyCount[0]?.count ?? 0),
815
+ uses_resource_key: Number(usesResourceKeyCount[0]?.count ?? 0),
816
+ uses_setting_key: Number(usesSettingKeyCount[0]?.count ?? 0),
817
+ modules: Number(moduleCount[0]?.count ?? 0),
818
+ projects: Number(projectCount[0]?.count ?? 0),
819
+ contains: Number(containsCount[0]?.count ?? 0),
820
+ contains_module: Number(containsModuleCount[0]?.count ?? 0),
821
+ exports: Number(exportsCount[0]?.count ?? 0),
822
+ includes_file: Number(includesFileCount[0]?.count ?? 0),
823
+ references_project: Number(referencesProjectCount[0]?.count ?? 0),
824
+ uses_resource: Number(usesResourceCount[0]?.count ?? 0),
825
+ uses_setting: Number(usesSettingCount[0]?.count ?? 0),
826
+ uses_config: Number(usesConfigCount[0]?.count ?? 0),
827
+ transforms_config: Number(transformsConfigCount[0]?.count ?? 0)
572
828
  }
573
829
  };
574
830
 
@@ -577,13 +833,16 @@ async function main(): Promise<void> {
577
833
 
578
834
  console.log(`[graph-load] db_path=${DB_PATH}`);
579
835
  console.log(
580
- `[graph-load] files=${summary.counts.files} rules=${summary.counts.rules} adrs=${summary.counts.adrs} chunks=${summary.counts.chunks}`
836
+ `[graph-load] files=${summary.counts.files} rules=${summary.counts.rules} adrs=${summary.counts.adrs} chunks=${summary.counts.chunks} modules=${summary.counts.modules} projects=${summary.counts.projects}`
581
837
  );
582
838
  console.log(
583
839
  `[graph-load] rels constrains=${summary.counts.constrains} implements=${summary.counts.implements} supersedes=${summary.counts.supersedes}`
584
840
  );
585
841
  console.log(
586
- `[graph-load] rels defines=${summary.counts.defines} calls=${summary.counts.calls} imports=${summary.counts.imports}`
842
+ `[graph-load] rels defines=${summary.counts.defines} calls=${summary.counts.calls} imports=${summary.counts.imports} calls_sql=${summary.counts.calls_sql} uses_config_key=${summary.counts.uses_config_key} uses_resource_key=${summary.counts.uses_resource_key} uses_setting_key=${summary.counts.uses_setting_key}`
843
+ );
844
+ console.log(
845
+ `[graph-load] rels contains=${summary.counts.contains} contains_module=${summary.counts.contains_module} exports=${summary.counts.exports} includes_file=${summary.counts.includes_file} references_project=${summary.counts.references_project} uses_resource=${summary.counts.uses_resource} uses_setting=${summary.counts.uses_setting} uses_config=${summary.counts.uses_config} transforms_config=${summary.counts.transforms_config}`
587
846
  );
588
847
  console.log(`[graph-load] manifest=${summaryPath}`);
589
848
 
@@ -24,11 +24,27 @@ export const PATHS = {
24
24
  adrEntities: path.join(CACHE_DIR, "entities.adr.jsonl"),
25
25
  ruleEntities: path.join(CACHE_DIR, "entities.rule.jsonl"),
26
26
  chunkEntities: path.join(CACHE_DIR, "entities.chunk.jsonl"),
27
+ projectEntities: path.join(CACHE_DIR, "entities.project.jsonl"),
27
28
  constrainsRelations: path.join(CACHE_DIR, "relations.constrains.jsonl"),
28
29
  implementsRelations: path.join(CACHE_DIR, "relations.implements.jsonl"),
29
30
  supersedesRelations: path.join(CACHE_DIR, "relations.supersedes.jsonl"),
30
31
  callsRelations: path.join(CACHE_DIR, "relations.calls.jsonl"),
31
- importsRelations: path.join(CACHE_DIR, "relations.imports.jsonl")
32
+ callsSqlRelations: path.join(CACHE_DIR, "relations.calls_sql.jsonl"),
33
+ usesConfigKeyRelations: path.join(CACHE_DIR, "relations.uses_config_key.jsonl"),
34
+ usesResourceKeyRelations: path.join(CACHE_DIR, "relations.uses_resource_key.jsonl"),
35
+ usesSettingKeyRelations: path.join(CACHE_DIR, "relations.uses_setting_key.jsonl"),
36
+ definesRelations: path.join(CACHE_DIR, "relations.defines.jsonl"),
37
+ importsRelations: path.join(CACHE_DIR, "relations.imports.jsonl"),
38
+ moduleEntities: path.join(CACHE_DIR, "entities.module.jsonl"),
39
+ containsRelations: path.join(CACHE_DIR, "relations.contains.jsonl"),
40
+ containsModuleRelations: path.join(CACHE_DIR, "relations.contains_module.jsonl"),
41
+ exportsRelations: path.join(CACHE_DIR, "relations.exports.jsonl"),
42
+ includesFileRelations: path.join(CACHE_DIR, "relations.includes_file.jsonl"),
43
+ referencesProjectRelations: path.join(CACHE_DIR, "relations.references_project.jsonl"),
44
+ usesResourceRelations: path.join(CACHE_DIR, "relations.uses_resource.jsonl"),
45
+ usesSettingRelations: path.join(CACHE_DIR, "relations.uses_setting.jsonl"),
46
+ usesConfigRelations: path.join(CACHE_DIR, "relations.uses_config.jsonl"),
47
+ transformsConfigRelations: path.join(CACHE_DIR, "relations.transforms_config.jsonl")
32
48
  };
33
49
 
34
50
  export const DEFAULT_RANKING: RankingWeights = {