@danielblomma/cortex-mcp 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +203 -0
  2. package/bin/cortex.mjs +621 -0
  3. package/docs/MCP_MARKETPLACE.md +160 -0
  4. package/package.json +42 -0
  5. package/scaffold/.context/config.yaml +21 -0
  6. package/scaffold/.context/ontology.cypher +63 -0
  7. package/scaffold/.context/rules.yaml +25 -0
  8. package/scaffold/.githooks/_cortex-update-runner.sh +58 -0
  9. package/scaffold/.githooks/post-checkout +22 -0
  10. package/scaffold/.githooks/post-merge +14 -0
  11. package/scaffold/docs/architecture.md +22 -0
  12. package/scaffold/mcp/package-lock.json +2623 -0
  13. package/scaffold/mcp/package.json +29 -0
  14. package/scaffold/mcp/src/embed.ts +416 -0
  15. package/scaffold/mcp/src/embeddings.ts +192 -0
  16. package/scaffold/mcp/src/graph.ts +666 -0
  17. package/scaffold/mcp/src/loadGraph.ts +597 -0
  18. package/scaffold/mcp/src/paths.ts +33 -0
  19. package/scaffold/mcp/src/search.ts +412 -0
  20. package/scaffold/mcp/src/server.ts +98 -0
  21. package/scaffold/mcp/src/types.ts +109 -0
  22. package/scaffold/mcp/tests/server.test.mjs +60 -0
  23. package/scaffold/mcp/tsconfig.json +13 -0
  24. package/scaffold/scripts/bootstrap.sh +57 -0
  25. package/scaffold/scripts/capture-note.sh +55 -0
  26. package/scaffold/scripts/context.sh +109 -0
  27. package/scaffold/scripts/embed.sh +15 -0
  28. package/scaffold/scripts/ingest.mjs +1118 -0
  29. package/scaffold/scripts/ingest.sh +20 -0
  30. package/scaffold/scripts/install-git-hooks.sh +21 -0
  31. package/scaffold/scripts/load-kuzu.sh +6 -0
  32. package/scaffold/scripts/load-ryu.sh +18 -0
  33. package/scaffold/scripts/parsers/javascript.mjs +390 -0
  34. package/scaffold/scripts/parsers/package-lock.json +51 -0
  35. package/scaffold/scripts/parsers/package.json +17 -0
  36. package/scaffold/scripts/plan-state-engine.cjs +310 -0
  37. package/scaffold/scripts/plan-state.sh +71 -0
  38. package/scaffold/scripts/refresh.sh +9 -0
  39. package/scaffold/scripts/status.sh +282 -0
  40. package/scaffold/scripts/update-context.sh +18 -0
  41. package/scaffold/scripts/watch.sh +374 -0
@@ -0,0 +1,597 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import ryugraph, { type Connection, type QueryResult } from "ryugraph";
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const REPO_ROOT = path.resolve(__dirname, "../..");
9
+ const CONTEXT_DIR = path.join(REPO_ROOT, ".context");
10
+ const CACHE_DIR = path.join(CONTEXT_DIR, "cache");
11
+ const DB_PATH = path.join(CONTEXT_DIR, "db", "graph.ryu");
12
+ 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 };
16
+
17
+ type FileEntity = {
18
+ id: string;
19
+ path: string;
20
+ kind: string;
21
+ excerpt: string;
22
+ checksum: string;
23
+ updated_at: string;
24
+ source_of_truth: boolean;
25
+ trust_level: number;
26
+ status: string;
27
+ };
28
+
29
+ type RuleEntity = {
30
+ id: string;
31
+ title: string;
32
+ body: string;
33
+ scope: string;
34
+ updated_at: string;
35
+ source_of_truth: boolean;
36
+ trust_level: number;
37
+ status: string;
38
+ priority: number;
39
+ };
40
+
41
+ type AdrEntity = {
42
+ id: string;
43
+ path: string;
44
+ title: string;
45
+ body: string;
46
+ decision_date: string;
47
+ supersedes_id: string;
48
+ source_of_truth: boolean;
49
+ trust_level: number;
50
+ status: string;
51
+ };
52
+
53
+ type ChunkEntity = {
54
+ id: string;
55
+ file_id: string;
56
+ name: string;
57
+ kind: string;
58
+ signature: string;
59
+ body: string;
60
+ start_line: number;
61
+ end_line: number;
62
+ language: string;
63
+ checksum: string;
64
+ updated_at: string;
65
+ trust_level: number;
66
+ };
67
+
68
+ type Relation = {
69
+ from: string;
70
+ to: string;
71
+ note: string;
72
+ };
73
+
74
+ type CallRelation = {
75
+ from: string;
76
+ to: string;
77
+ call_type: string;
78
+ };
79
+
80
+ type ImportRelation = {
81
+ from: string;
82
+ to: string;
83
+ import_name: string;
84
+ };
85
+
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
+ function readEntityFile(fileName: string): JsonObject[] {
119
+ return readJsonl(path.join(CACHE_DIR, fileName));
120
+ }
121
+
122
+ function parseFiles(raw: JsonObject[]): FileEntity[] {
123
+ return raw
124
+ .map((item) => {
125
+ const id = asString(item.id);
126
+ const filePath = asString(item.path);
127
+ if (!id || !filePath) {
128
+ return null;
129
+ }
130
+
131
+ return {
132
+ id,
133
+ path: filePath,
134
+ kind: asString(item.kind, "DOC"),
135
+ excerpt: asString(item.excerpt),
136
+ checksum: asString(item.checksum),
137
+ updated_at: asString(item.updated_at),
138
+ source_of_truth: asBoolean(item.source_of_truth, false),
139
+ trust_level: asNumber(item.trust_level, 50),
140
+ status: asString(item.status, "active")
141
+ };
142
+ })
143
+ .filter((value): value is FileEntity => value !== null);
144
+ }
145
+
146
+ function parseRules(raw: JsonObject[]): RuleEntity[] {
147
+ return raw
148
+ .map((item) => {
149
+ const id = asString(item.id);
150
+ if (!id) {
151
+ return null;
152
+ }
153
+
154
+ return {
155
+ id,
156
+ title: asString(item.title, id),
157
+ body: asString(item.body),
158
+ scope: asString(item.scope, "global"),
159
+ updated_at: asString(item.updated_at),
160
+ source_of_truth: asBoolean(item.source_of_truth, true),
161
+ trust_level: asNumber(item.trust_level, 95),
162
+ status: asString(item.status, "active"),
163
+ priority: asNumber(item.priority, 0)
164
+ };
165
+ })
166
+ .filter((value): value is RuleEntity => value !== null);
167
+ }
168
+
169
+ function parseAdrs(raw: JsonObject[]): AdrEntity[] {
170
+ return raw
171
+ .map((item) => {
172
+ const id = asString(item.id);
173
+ if (!id) {
174
+ return null;
175
+ }
176
+
177
+ return {
178
+ id,
179
+ path: asString(item.path),
180
+ title: asString(item.title, id),
181
+ body: asString(item.body),
182
+ decision_date: asString(item.decision_date),
183
+ supersedes_id: asString(item.supersedes_id),
184
+ source_of_truth: asBoolean(item.source_of_truth, true),
185
+ trust_level: asNumber(item.trust_level, 95),
186
+ status: asString(item.status, "active")
187
+ };
188
+ })
189
+ .filter((value): value is AdrEntity => value !== null);
190
+ }
191
+
192
+ function parseChunks(raw: JsonObject[]): ChunkEntity[] {
193
+ return raw
194
+ .map((item) => {
195
+ const id = asString(item.id);
196
+ const file_id = asString(item.file_id);
197
+ const name = asString(item.name);
198
+ if (!id || !file_id || !name) {
199
+ return null;
200
+ }
201
+
202
+ return {
203
+ id,
204
+ file_id,
205
+ name,
206
+ kind: asString(item.kind, "function"),
207
+ signature: asString(item.signature),
208
+ body: asString(item.body),
209
+ start_line: asNumber(item.start_line, 0),
210
+ end_line: asNumber(item.end_line, 0),
211
+ language: asString(item.language, "javascript"),
212
+ checksum: asString(item.checksum),
213
+ updated_at: asString(item.updated_at),
214
+ trust_level: asNumber(item.trust_level, 80)
215
+ };
216
+ })
217
+ .filter((value): value is ChunkEntity => value !== null);
218
+ }
219
+
220
+ function parseRelations(fileName: string, noteField: string): Relation[] {
221
+ const raw = readEntityFile(fileName);
222
+ return raw
223
+ .map((item) => {
224
+ const from = asString(item.from);
225
+ const to = asString(item.to);
226
+ if (!from || !to) {
227
+ return null;
228
+ }
229
+
230
+ return {
231
+ from,
232
+ to,
233
+ note: asString(item[noteField as keyof JsonObject])
234
+ };
235
+ })
236
+ .filter((value): value is Relation => value !== null);
237
+ }
238
+
239
+ function parseCallRelations(fileName: string): CallRelation[] {
240
+ const raw = readEntityFile(fileName);
241
+ return raw
242
+ .map((item) => {
243
+ const from = asString(item.from);
244
+ const to = asString(item.to);
245
+ if (!from || !to) {
246
+ return null;
247
+ }
248
+
249
+ return {
250
+ from,
251
+ to,
252
+ call_type: asString(item.call_type, "direct")
253
+ };
254
+ })
255
+ .filter((value): value is CallRelation => value !== null);
256
+ }
257
+
258
+ function parseImportRelations(fileName: string): ImportRelation[] {
259
+ const raw = readEntityFile(fileName);
260
+ return raw
261
+ .map((item) => {
262
+ const from = asString(item.from);
263
+ const to = asString(item.to);
264
+ if (!from || !to) {
265
+ return null;
266
+ }
267
+
268
+ return {
269
+ from,
270
+ to,
271
+ import_name: asString(item.import_name, "")
272
+ };
273
+ })
274
+ .filter((value): value is ImportRelation => value !== null);
275
+ }
276
+
277
+ function parseSimpleRelations(fileName: string): Relation[] {
278
+ const raw = readEntityFile(fileName);
279
+ return raw
280
+ .map((item) => {
281
+ const from = asString(item.from);
282
+ const to = asString(item.to);
283
+ if (!from || !to) {
284
+ return null;
285
+ }
286
+
287
+ return {
288
+ from,
289
+ to,
290
+ note: ""
291
+ };
292
+ })
293
+ .filter((value): value is Relation => value !== null);
294
+ }
295
+
296
+ async function rows(result: QueryResult | QueryResult[]): Promise<Record<string, unknown>[]> {
297
+ const resolved = Array.isArray(result) ? result[result.length - 1] : result;
298
+ return resolved.getAll();
299
+ }
300
+
301
+ function parseOntologyStatements(ontologyText: string): string[] {
302
+ const withoutComments = ontologyText
303
+ .split(/\r?\n/)
304
+ .filter((line) => !line.trim().startsWith("//"))
305
+ .join("\n");
306
+
307
+ return withoutComments
308
+ .split(";")
309
+ .map((statement) => statement.trim())
310
+ .filter(Boolean)
311
+ .map((statement) => `${statement};`);
312
+ }
313
+
314
+ async function executeStatements(conn: Connection, statements: string[]): Promise<void> {
315
+ for (const statement of statements) {
316
+ await conn.query(statement);
317
+ }
318
+ }
319
+
320
+ async function ensureRequiredFiles(): Promise<void> {
321
+ const required = [
322
+ path.join(CACHE_DIR, "entities.file.jsonl"),
323
+ path.join(CACHE_DIR, "entities.rule.jsonl"),
324
+ path.join(CACHE_DIR, "entities.adr.jsonl"),
325
+ path.join(CACHE_DIR, "relations.constrains.jsonl"),
326
+ path.join(CACHE_DIR, "relations.implements.jsonl"),
327
+ path.join(CACHE_DIR, "relations.supersedes.jsonl"),
328
+ ONTOLOGY_PATH
329
+ ];
330
+
331
+ for (const filePath of required) {
332
+ if (!fs.existsSync(filePath)) {
333
+ throw new Error(`Missing required file: ${filePath}`);
334
+ }
335
+ }
336
+ }
337
+
338
+ function warnIfOptionalChunkFilesMissing(): void {
339
+ const optionalChunkFiles = [
340
+ "entities.chunk.jsonl",
341
+ "relations.defines.jsonl",
342
+ "relations.calls.jsonl",
343
+ "relations.imports.jsonl"
344
+ ];
345
+
346
+ const missing = optionalChunkFiles.filter((fileName) => !fs.existsSync(path.join(CACHE_DIR, fileName)));
347
+ if (missing.length === 0) {
348
+ return;
349
+ }
350
+
351
+ console.warn(
352
+ `[graph-load] warning: missing optional chunk files (${missing.join(", ")}); continuing without chunk nodes/relations`
353
+ );
354
+ }
355
+
356
+ async function main(): Promise<void> {
357
+ const args = new Set(process.argv.slice(2));
358
+ const reset = !args.has("--no-reset");
359
+
360
+ await ensureRequiredFiles();
361
+ warnIfOptionalChunkFilesMissing();
362
+
363
+ if (reset) {
364
+ fs.rmSync(DB_PATH, { recursive: true, force: true });
365
+ }
366
+ fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
367
+
368
+ const db = new ryugraph.Database(DB_PATH);
369
+ const conn = new ryugraph.Connection(db);
370
+
371
+ const ontologyStatements = parseOntologyStatements(fs.readFileSync(ONTOLOGY_PATH, "utf8"));
372
+ await executeStatements(conn, ontologyStatements);
373
+
374
+ await conn.query("MATCH (a:ADR)-[r:SUPERSEDES]->(b:ADR) DELETE r;");
375
+ await conn.query("MATCH (f:File)-[i:IMPLEMENTS]->(r:Rule) DELETE i;");
376
+ await conn.query("MATCH (r:Rule)-[c:CONSTRAINS]->(f:File) DELETE c;");
377
+ await conn.query("MATCH (f:File)-[d:DEFINES]->(c:Chunk) DELETE d;");
378
+ await conn.query("MATCH (c1:Chunk)-[ca:CALLS]->(c2:Chunk) DELETE ca;");
379
+ await conn.query("MATCH (c:Chunk)-[im:IMPORTS]->(f:File) DELETE im;");
380
+ await conn.query("MATCH (n:ADR) DELETE n;");
381
+ await conn.query("MATCH (n:Rule) DELETE n;");
382
+ await conn.query("MATCH (n:Chunk) DELETE n;");
383
+ await conn.query("MATCH (n:File) DELETE n;");
384
+
385
+ const fileEntities = parseFiles(readEntityFile("entities.file.jsonl"));
386
+ const ruleEntities = parseRules(readEntityFile("entities.rule.jsonl"));
387
+ const adrEntities = parseAdrs(readEntityFile("entities.adr.jsonl"));
388
+ const chunkEntities = parseChunks(readEntityFile("entities.chunk.jsonl"));
389
+ const constrains = parseRelations("relations.constrains.jsonl", "note");
390
+ const implementsEdges = parseRelations("relations.implements.jsonl", "note");
391
+ const supersedes = parseRelations("relations.supersedes.jsonl", "reason");
392
+ const defines = parseSimpleRelations("relations.defines.jsonl");
393
+ const calls = parseCallRelations("relations.calls.jsonl");
394
+ const imports = parseImportRelations("relations.imports.jsonl");
395
+
396
+ const insertFile = await conn.prepare(`
397
+ CREATE (f:File {
398
+ id: $id,
399
+ path: $path,
400
+ kind: $kind,
401
+ excerpt: $excerpt,
402
+ checksum: $checksum,
403
+ updated_at: $updated_at,
404
+ source_of_truth: $source_of_truth,
405
+ trust_level: $trust_level,
406
+ status: $status
407
+ });
408
+ `);
409
+
410
+ const insertRule = await conn.prepare(`
411
+ CREATE (r:Rule {
412
+ id: $id,
413
+ title: $title,
414
+ body: $body,
415
+ scope: $scope,
416
+ priority: $priority,
417
+ updated_at: $updated_at,
418
+ source_of_truth: $source_of_truth,
419
+ trust_level: $trust_level,
420
+ status: $status
421
+ });
422
+ `);
423
+
424
+ const insertAdr = await conn.prepare(`
425
+ CREATE (a:ADR {
426
+ id: $id,
427
+ path: $path,
428
+ title: $title,
429
+ body: $body,
430
+ decision_date: $decision_date,
431
+ supersedes_id: $supersedes_id,
432
+ source_of_truth: $source_of_truth,
433
+ trust_level: $trust_level,
434
+ status: $status
435
+ });
436
+ `);
437
+
438
+ const insertChunk = await conn.prepare(`
439
+ CREATE (c:Chunk {
440
+ id: $id,
441
+ file_id: $file_id,
442
+ name: $name,
443
+ kind: $kind,
444
+ signature: $signature,
445
+ body: $body,
446
+ start_line: $start_line,
447
+ end_line: $end_line,
448
+ language: $language,
449
+ checksum: $checksum,
450
+ updated_at: $updated_at,
451
+ trust_level: $trust_level
452
+ });
453
+ `);
454
+
455
+ const insertConstrains = await conn.prepare(`
456
+ MATCH (r:Rule {id: $from}), (f:File {id: $to})
457
+ CREATE (r)-[:CONSTRAINS {note: $note}]->(f);
458
+ `);
459
+
460
+ const insertImplements = await conn.prepare(`
461
+ MATCH (f:File {id: $from}), (r:Rule {id: $to})
462
+ CREATE (f)-[:IMPLEMENTS {note: $note}]->(r);
463
+ `);
464
+
465
+ const insertSupersedes = await conn.prepare(`
466
+ MATCH (a1:ADR {id: $from}), (a2:ADR {id: $to})
467
+ CREATE (a1)-[:SUPERSEDES {reason: $note}]->(a2);
468
+ `);
469
+
470
+ const insertDefines = await conn.prepare(`
471
+ MATCH (f:File {id: $from}), (c:Chunk {id: $to})
472
+ CREATE (f)-[:DEFINES]->(c);
473
+ `);
474
+
475
+ const insertCalls = await conn.prepare(`
476
+ MATCH (c1:Chunk {id: $from}), (c2:Chunk {id: $to})
477
+ CREATE (c1)-[:CALLS {call_type: $call_type}]->(c2);
478
+ `);
479
+
480
+ const insertImports = await conn.prepare(`
481
+ MATCH (c:Chunk {id: $from}), (f:File {id: $to})
482
+ CREATE (c)-[:IMPORTS {import_name: $import_name}]->(f);
483
+ `);
484
+
485
+ for (const entity of fileEntities) {
486
+ await conn.execute(insertFile, entity);
487
+ }
488
+
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
500
+ });
501
+ }
502
+
503
+ for (const entity of adrEntities) {
504
+ await conn.execute(insertAdr, entity);
505
+ }
506
+
507
+ for (const entity of chunkEntities) {
508
+ await conn.execute(insertChunk, entity);
509
+ }
510
+
511
+ for (const edge of defines) {
512
+ await conn.execute(insertDefines, edge);
513
+ }
514
+
515
+ for (const edge of calls) {
516
+ await conn.execute(insertCalls, edge);
517
+ }
518
+
519
+ for (const edge of imports) {
520
+ await conn.execute(insertImports, edge);
521
+ }
522
+
523
+ for (const edge of constrains) {
524
+ await conn.execute(insertConstrains, edge);
525
+ }
526
+
527
+ for (const edge of implementsEdges) {
528
+ await conn.execute(insertImplements, edge);
529
+ }
530
+
531
+ for (const edge of supersedes) {
532
+ await conn.execute(insertSupersedes, edge);
533
+ }
534
+
535
+ const fileCount = await rows(await conn.query("MATCH (f:File) RETURN count(*) AS count;"));
536
+ const ruleCount = await rows(await conn.query("MATCH (r:Rule) RETURN count(*) AS count;"));
537
+ const adrCount = await rows(await conn.query("MATCH (a:ADR) RETURN count(*) AS count;"));
538
+ const chunkCount = await rows(await conn.query("MATCH (c:Chunk) RETURN count(*) AS count;"));
539
+ const constrainsCount = await rows(
540
+ await conn.query("MATCH (:Rule)-[c:CONSTRAINS]->(:File) RETURN count(c) AS count;")
541
+ );
542
+ const implementsCount = await rows(
543
+ await conn.query("MATCH (:File)-[i:IMPLEMENTS]->(:Rule) RETURN count(i) AS count;")
544
+ );
545
+ const supersedesCount = await rows(
546
+ await conn.query("MATCH (:ADR)-[s:SUPERSEDES]->(:ADR) RETURN count(s) AS count;")
547
+ );
548
+ const definesCount = await rows(
549
+ await conn.query("MATCH (:File)-[d:DEFINES]->(:Chunk) RETURN count(d) AS count;")
550
+ );
551
+ const callsCount = await rows(
552
+ await conn.query("MATCH (:Chunk)-[ca:CALLS]->(:Chunk) RETURN count(ca) AS count;")
553
+ );
554
+ const importsCount = await rows(
555
+ await conn.query("MATCH (:Chunk)-[im:IMPORTS]->(:File) RETURN count(im) AS count;")
556
+ );
557
+
558
+ const summary = {
559
+ generated_at: new Date().toISOString(),
560
+ db_path: DB_PATH,
561
+ counts: {
562
+ files: Number(fileCount[0]?.count ?? 0),
563
+ rules: Number(ruleCount[0]?.count ?? 0),
564
+ adrs: Number(adrCount[0]?.count ?? 0),
565
+ chunks: Number(chunkCount[0]?.count ?? 0),
566
+ constrains: Number(constrainsCount[0]?.count ?? 0),
567
+ implements: Number(implementsCount[0]?.count ?? 0),
568
+ supersedes: Number(supersedesCount[0]?.count ?? 0),
569
+ defines: Number(definesCount[0]?.count ?? 0),
570
+ calls: Number(callsCount[0]?.count ?? 0),
571
+ imports: Number(importsCount[0]?.count ?? 0)
572
+ }
573
+ };
574
+
575
+ const summaryPath = path.join(CACHE_DIR, "graph-manifest.json");
576
+ fs.writeFileSync(summaryPath, `${JSON.stringify(summary, null, 2)}\n`, "utf8");
577
+
578
+ console.log(`[graph-load] db_path=${DB_PATH}`);
579
+ console.log(
580
+ `[graph-load] files=${summary.counts.files} rules=${summary.counts.rules} adrs=${summary.counts.adrs} chunks=${summary.counts.chunks}`
581
+ );
582
+ console.log(
583
+ `[graph-load] rels constrains=${summary.counts.constrains} implements=${summary.counts.implements} supersedes=${summary.counts.supersedes}`
584
+ );
585
+ console.log(
586
+ `[graph-load] rels defines=${summary.counts.defines} calls=${summary.counts.calls} imports=${summary.counts.imports}`
587
+ );
588
+ console.log(`[graph-load] manifest=${summaryPath}`);
589
+
590
+ // RyuGraph Node addon can crash on explicit close in some environments.
591
+ // Let process teardown handle resource cleanup.
592
+ }
593
+
594
+ main().catch((error) => {
595
+ process.stderr.write(`${error instanceof Error ? error.message : "Unknown error"}\n`);
596
+ process.exit(1);
597
+ });
@@ -0,0 +1,33 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import type { RankingWeights } from "./types.js";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ export const REPO_ROOT = path.resolve(__dirname, "../..");
9
+ export const CONTEXT_DIR = path.join(REPO_ROOT, ".context");
10
+ export const CACHE_DIR = path.join(CONTEXT_DIR, "cache");
11
+ export const DB_PATH = path.join(CONTEXT_DIR, "db", "graph.ryu");
12
+
13
+ export const PATHS = {
14
+ config: path.join(CONTEXT_DIR, "config.yaml"),
15
+ rulesYaml: path.join(CONTEXT_DIR, "rules.yaml"),
16
+ graphManifest: path.join(CACHE_DIR, "graph-manifest.json"),
17
+ embeddingsManifest: path.join(CONTEXT_DIR, "embeddings", "manifest.json"),
18
+ embeddingsEntities: path.join(CONTEXT_DIR, "embeddings", "entities.jsonl"),
19
+ embeddingsModelCache: path.join(CONTEXT_DIR, "embeddings", "models"),
20
+ documents: path.join(CACHE_DIR, "documents.jsonl"),
21
+ adrEntities: path.join(CACHE_DIR, "entities.adr.jsonl"),
22
+ ruleEntities: path.join(CACHE_DIR, "entities.rule.jsonl"),
23
+ constrainsRelations: path.join(CACHE_DIR, "relations.constrains.jsonl"),
24
+ implementsRelations: path.join(CACHE_DIR, "relations.implements.jsonl"),
25
+ supersedesRelations: path.join(CACHE_DIR, "relations.supersedes.jsonl")
26
+ };
27
+
28
+ export const DEFAULT_RANKING: RankingWeights = {
29
+ semantic: 0.4,
30
+ graph: 0.25,
31
+ trust: 0.2,
32
+ recency: 0.15
33
+ };