@danielblomma/cortex-mcp 0.4.5 → 0.6.5
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 +38 -42
- package/bin/cortex.mjs +32 -60
- package/package.json +15 -3
- package/scaffold/.context/ontology.cypher +47 -0
- package/scaffold/.githooks/post-commit +14 -0
- package/scaffold/.githooks/post-rewrite +23 -0
- package/scaffold/mcp/package-lock.json +16 -16
- package/scaffold/mcp/package.json +4 -1
- package/scaffold/mcp/src/contextEntities.ts +311 -0
- package/scaffold/mcp/src/defaults.ts +6 -0
- package/scaffold/mcp/src/embed.ts +163 -37
- package/scaffold/mcp/src/frontmatter.ts +39 -0
- package/scaffold/mcp/src/graph.ts +253 -130
- package/scaffold/mcp/src/graphMetrics.ts +12 -0
- package/scaffold/mcp/src/impactPresentation.ts +202 -0
- package/scaffold/mcp/src/impactRanking.ts +237 -0
- package/scaffold/mcp/src/impactResponse.ts +47 -0
- package/scaffold/mcp/src/impactResults.ts +173 -0
- package/scaffold/mcp/src/impactSeed.ts +33 -0
- package/scaffold/mcp/src/impactTraversal.ts +83 -0
- package/scaffold/mcp/src/jsonl.ts +34 -0
- package/scaffold/mcp/src/loadGraph.ts +345 -86
- package/scaffold/mcp/src/paths.ts +17 -1
- package/scaffold/mcp/src/presets.ts +137 -0
- package/scaffold/mcp/src/relatedResponse.ts +30 -0
- package/scaffold/mcp/src/relatedTraversal.ts +101 -0
- package/scaffold/mcp/src/rules.ts +27 -0
- package/scaffold/mcp/src/search.ts +186 -455
- package/scaffold/mcp/src/searchCore.ts +274 -0
- package/scaffold/mcp/src/searchResults.ts +133 -0
- package/scaffold/mcp/src/server.ts +95 -3
- package/scaffold/mcp/src/types.ts +82 -3
- package/scaffold/scripts/context.sh +12 -46
- package/scaffold/scripts/dashboard.mjs +797 -0
- package/scaffold/scripts/dashboard.sh +13 -0
- package/scaffold/scripts/ingest.mjs +2227 -59
- package/scaffold/scripts/install-git-hooks.sh +3 -1
- package/scaffold/scripts/memory-compile.mjs +232 -0
- package/scaffold/scripts/memory-compile.sh +20 -0
- package/scaffold/scripts/memory-lint.mjs +375 -0
- package/scaffold/scripts/memory-lint.sh +20 -0
- package/scaffold/scripts/parsers/config.mjs +178 -0
- package/scaffold/scripts/parsers/cpp.mjs +316 -0
- package/scaffold/scripts/parsers/dotnet/VbNetParser/Program.cs +374 -0
- package/scaffold/scripts/parsers/dotnet/VbNetParser/VbNetParser.csproj +13 -0
- package/scaffold/scripts/parsers/javascript/ast.mjs +61 -0
- package/scaffold/scripts/parsers/javascript/calls.mjs +53 -0
- package/scaffold/scripts/parsers/javascript/chunks.mjs +388 -0
- package/scaffold/scripts/parsers/javascript/imports.mjs +162 -0
- package/scaffold/scripts/parsers/javascript/patterns.mjs +82 -0
- package/scaffold/scripts/parsers/javascript/scope-analysis.mjs +3 -0
- package/scaffold/scripts/parsers/javascript/scope-builder.mjs +305 -0
- package/scaffold/scripts/parsers/javascript/scope-resolver.mjs +82 -0
- package/scaffold/scripts/parsers/javascript.mjs +27 -350
- package/scaffold/scripts/parsers/resources.mjs +166 -0
- package/scaffold/scripts/parsers/rust.mjs +515 -0
- package/scaffold/scripts/parsers/sql.mjs +137 -0
- package/scaffold/scripts/parsers/vbnet.mjs +143 -0
- package/scaffold/scripts/status.sh +0 -7
- package/scaffold/scripts/capture-note.sh +0 -55
- package/scaffold/scripts/plan-state-engine.cjs +0 -310
- package/scaffold/scripts/plan-state.sh +0 -71
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import ryugraph, { type Connection, type Database, type QueryResult } from "ryugraph";
|
|
4
|
+
import { readJsonl, asString, asNumber, asBoolean } from "./jsonl.js";
|
|
4
5
|
import { DB_PATH, DEFAULT_RANKING, PATHS } from "./paths.js";
|
|
5
6
|
import type {
|
|
6
7
|
AdrRecord,
|
|
@@ -8,9 +9,11 @@ import type {
|
|
|
8
9
|
ContextData,
|
|
9
10
|
DocumentRecord,
|
|
10
11
|
JsonObject,
|
|
11
|
-
|
|
12
|
+
ModuleRecord,
|
|
13
|
+
ProjectRecord,
|
|
12
14
|
RankingWeights,
|
|
13
15
|
RelationRecord,
|
|
16
|
+
RelationType,
|
|
14
17
|
RuleRecord,
|
|
15
18
|
UnknownRow
|
|
16
19
|
} from "./types.js";
|
|
@@ -31,6 +34,33 @@ let ryuLastInitAttemptAt = 0;
|
|
|
31
34
|
let ryuGraphSignature: string | null = null;
|
|
32
35
|
|
|
33
36
|
const RYU_INIT_RETRY_INTERVAL_MS = 2000;
|
|
37
|
+
const REQUIRED_GRAPH_MANIFEST_COUNT_KEYS = [
|
|
38
|
+
"files",
|
|
39
|
+
"rules",
|
|
40
|
+
"adrs",
|
|
41
|
+
"chunks",
|
|
42
|
+
"constrains",
|
|
43
|
+
"implements",
|
|
44
|
+
"supersedes",
|
|
45
|
+
"defines",
|
|
46
|
+
"calls",
|
|
47
|
+
"imports",
|
|
48
|
+
"calls_sql",
|
|
49
|
+
"uses_config_key",
|
|
50
|
+
"uses_resource_key",
|
|
51
|
+
"uses_setting_key",
|
|
52
|
+
"modules",
|
|
53
|
+
"projects",
|
|
54
|
+
"contains",
|
|
55
|
+
"contains_module",
|
|
56
|
+
"exports",
|
|
57
|
+
"includes_file",
|
|
58
|
+
"references_project",
|
|
59
|
+
"uses_resource",
|
|
60
|
+
"uses_setting",
|
|
61
|
+
"uses_config",
|
|
62
|
+
"transforms_config"
|
|
63
|
+
] as const;
|
|
34
64
|
|
|
35
65
|
function readFileIfExists(filePath: string): string | null {
|
|
36
66
|
if (!fs.existsSync(filePath)) {
|
|
@@ -39,38 +69,6 @@ function readFileIfExists(filePath: string): string | null {
|
|
|
39
69
|
return fs.readFileSync(filePath, "utf8");
|
|
40
70
|
}
|
|
41
71
|
|
|
42
|
-
function readJsonl(filePath: string): JsonObject[] {
|
|
43
|
-
const raw = readFileIfExists(filePath);
|
|
44
|
-
if (!raw) {
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return raw
|
|
49
|
-
.split(/\r?\n/)
|
|
50
|
-
.map((line) => line.trim())
|
|
51
|
-
.filter(Boolean)
|
|
52
|
-
.map((line) => {
|
|
53
|
-
try {
|
|
54
|
-
return JSON.parse(line) as JsonObject;
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
.filter((value): value is JsonObject => value !== null);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function asString(value: JsonValue | undefined, fallback = ""): string {
|
|
63
|
-
return typeof value === "string" ? value : fallback;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function asNumber(value: JsonValue | undefined, fallback = 0): number {
|
|
67
|
-
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function asBoolean(value: JsonValue | undefined, fallback = false): boolean {
|
|
71
|
-
return typeof value === "boolean" ? value : fallback;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
72
|
function asStringUnknown(value: unknown, fallback = ""): string {
|
|
75
73
|
if (typeof value === "string") return value;
|
|
76
74
|
if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
|
|
@@ -164,9 +162,11 @@ function parseChunkEntities(raw: JsonObject[]): ChunkRecord[] {
|
|
|
164
162
|
kind: asString(item.kind, "chunk"),
|
|
165
163
|
signature: asString(item.signature),
|
|
166
164
|
body: asString(item.body),
|
|
165
|
+
description: asString(item.description),
|
|
167
166
|
start_line: asNumber(item.start_line),
|
|
168
167
|
end_line: asNumber(item.end_line),
|
|
169
168
|
language: asString(item.language),
|
|
169
|
+
exported: asBoolean(item.exported, false),
|
|
170
170
|
updated_at: asString(item.updated_at),
|
|
171
171
|
source_of_truth: asBoolean(item.source_of_truth, false),
|
|
172
172
|
trust_level: asNumber(item.trust_level, 60),
|
|
@@ -176,6 +176,56 @@ function parseChunkEntities(raw: JsonObject[]): ChunkRecord[] {
|
|
|
176
176
|
.filter((item): item is ChunkRecord => item !== null);
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
function parseModuleEntities(raw: JsonObject[]): ModuleRecord[] {
|
|
180
|
+
return raw
|
|
181
|
+
.map((item) => {
|
|
182
|
+
const id = asString(item.id);
|
|
183
|
+
if (!id) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
id,
|
|
189
|
+
path: asString(item.path),
|
|
190
|
+
name: asString(item.name),
|
|
191
|
+
summary: asString(item.summary),
|
|
192
|
+
file_count: asNumber(item.file_count, 0),
|
|
193
|
+
exported_symbols: asString(item.exported_symbols),
|
|
194
|
+
updated_at: asString(item.updated_at),
|
|
195
|
+
source_of_truth: asBoolean(item.source_of_truth, false),
|
|
196
|
+
trust_level: asNumber(item.trust_level, 75),
|
|
197
|
+
status: asString(item.status, "active")
|
|
198
|
+
};
|
|
199
|
+
})
|
|
200
|
+
.filter((item): item is ModuleRecord => item !== null);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function parseProjectEntities(raw: JsonObject[]): ProjectRecord[] {
|
|
204
|
+
return raw
|
|
205
|
+
.map((item) => {
|
|
206
|
+
const id = asString(item.id);
|
|
207
|
+
if (!id) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
id,
|
|
213
|
+
path: asString(item.path),
|
|
214
|
+
name: asString(item.name),
|
|
215
|
+
kind: asString(item.kind, "project"),
|
|
216
|
+
language: asString(item.language, "dotnet"),
|
|
217
|
+
target_framework: asString(item.target_framework),
|
|
218
|
+
summary: asString(item.summary),
|
|
219
|
+
file_count: asNumber(item.file_count, 0),
|
|
220
|
+
updated_at: asString(item.updated_at),
|
|
221
|
+
source_of_truth: asBoolean(item.source_of_truth, false),
|
|
222
|
+
trust_level: asNumber(item.trust_level, 80),
|
|
223
|
+
status: asString(item.status, "active")
|
|
224
|
+
};
|
|
225
|
+
})
|
|
226
|
+
.filter((item): item is ProjectRecord => item !== null);
|
|
227
|
+
}
|
|
228
|
+
|
|
179
229
|
function parseRuleEntities(raw: JsonObject[]): RuleRecord[] {
|
|
180
230
|
return raw
|
|
181
231
|
.map((item) => {
|
|
@@ -273,7 +323,7 @@ function parseRulesYaml(yamlText: string | null): RuleRecord[] {
|
|
|
273
323
|
|
|
274
324
|
function parseRelations(
|
|
275
325
|
raw: JsonObject[],
|
|
276
|
-
relation:
|
|
326
|
+
relation: RelationType,
|
|
277
327
|
noteFields: string[] = ["note", "reason"]
|
|
278
328
|
): RelationRecord[] {
|
|
279
329
|
return raw
|
|
@@ -379,6 +429,28 @@ function buildMissingDbMessage(): string {
|
|
|
379
429
|
return `RyuGraph DB not found at ${DB_PATH}. Run ${loadCommand} (or ${bootstrapCommand} on cold start).`;
|
|
380
430
|
}
|
|
381
431
|
|
|
432
|
+
function buildIncompatibleGraphMessage(missingKeys: string[]): string {
|
|
433
|
+
const loadCommand = "./scripts/context.sh graph-load";
|
|
434
|
+
const missing = missingKeys.join(", ");
|
|
435
|
+
return `RyuGraph manifest is missing schema keys (${missing}). Run ${loadCommand} to rebuild the graph DB.`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function readGraphManifestMissingKeys(): string[] {
|
|
439
|
+
if (!fs.existsSync(PATHS.graphManifest)) {
|
|
440
|
+
return [...REQUIRED_GRAPH_MANIFEST_COUNT_KEYS];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
const raw = JSON.parse(fs.readFileSync(PATHS.graphManifest, "utf8")) as {
|
|
445
|
+
counts?: Record<string, unknown>;
|
|
446
|
+
};
|
|
447
|
+
const counts = raw.counts ?? {};
|
|
448
|
+
return REQUIRED_GRAPH_MANIFEST_COUNT_KEYS.filter((key) => !(key in counts));
|
|
449
|
+
} catch {
|
|
450
|
+
return [...REQUIRED_GRAPH_MANIFEST_COUNT_KEYS];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
382
454
|
async function closeRyuGraphResources(): Promise<void> {
|
|
383
455
|
const currentConnection = ryuConnection;
|
|
384
456
|
const currentDb = ryuDb;
|
|
@@ -435,6 +507,12 @@ async function getRyuGraphConnection(forceReload = false): Promise<Connection |
|
|
|
435
507
|
return null;
|
|
436
508
|
}
|
|
437
509
|
|
|
510
|
+
const missingManifestKeys = readGraphManifestMissingKeys();
|
|
511
|
+
if (missingManifestKeys.length > 0) {
|
|
512
|
+
await resetRyuGraphState(buildIncompatibleGraphMessage(missingManifestKeys));
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
|
|
438
516
|
try {
|
|
439
517
|
const nextDb = new ryugraph.Database(DB_PATH, undefined, undefined, true);
|
|
440
518
|
const nextConnection = new ryugraph.Connection(nextDb);
|
|
@@ -539,9 +617,11 @@ function parseRyuGraphChunks(rows: UnknownRow[]): ChunkRecord[] {
|
|
|
539
617
|
kind: asStringUnknown(row.kind, "chunk"),
|
|
540
618
|
signature: asStringUnknown(row.signature),
|
|
541
619
|
body: asStringUnknown(row.body),
|
|
620
|
+
description: asStringUnknown(row.description),
|
|
542
621
|
start_line: asNumberUnknown(row.start_line),
|
|
543
622
|
end_line: asNumberUnknown(row.end_line),
|
|
544
623
|
language: asStringUnknown(row.language),
|
|
624
|
+
exported: asBooleanUnknown(row.exported, false),
|
|
545
625
|
updated_at: asStringUnknown(row.updated_at),
|
|
546
626
|
source_of_truth: asBooleanUnknown(row.source_of_truth, false),
|
|
547
627
|
trust_level: asNumberUnknown(row.trust_level, 60),
|
|
@@ -551,9 +631,59 @@ function parseRyuGraphChunks(rows: UnknownRow[]): ChunkRecord[] {
|
|
|
551
631
|
.filter((value): value is ChunkRecord => value !== null);
|
|
552
632
|
}
|
|
553
633
|
|
|
634
|
+
function parseRyuGraphModules(rows: UnknownRow[]): ModuleRecord[] {
|
|
635
|
+
return rows
|
|
636
|
+
.map((row) => {
|
|
637
|
+
const id = asStringUnknown(row.id);
|
|
638
|
+
if (!id) {
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return {
|
|
643
|
+
id,
|
|
644
|
+
path: asStringUnknown(row.path),
|
|
645
|
+
name: asStringUnknown(row.name),
|
|
646
|
+
summary: asStringUnknown(row.summary),
|
|
647
|
+
file_count: asNumberUnknown(row.file_count, 0),
|
|
648
|
+
exported_symbols: asStringUnknown(row.exported_symbols),
|
|
649
|
+
updated_at: asStringUnknown(row.updated_at),
|
|
650
|
+
source_of_truth: asBooleanUnknown(row.source_of_truth, false),
|
|
651
|
+
trust_level: asNumberUnknown(row.trust_level, 75),
|
|
652
|
+
status: asStringUnknown(row.status, "active")
|
|
653
|
+
};
|
|
654
|
+
})
|
|
655
|
+
.filter((value): value is ModuleRecord => value !== null);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function parseRyuGraphProjects(rows: UnknownRow[]): ProjectRecord[] {
|
|
659
|
+
return rows
|
|
660
|
+
.map((row) => {
|
|
661
|
+
const id = asStringUnknown(row.id);
|
|
662
|
+
if (!id) {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return {
|
|
667
|
+
id,
|
|
668
|
+
path: asStringUnknown(row.path),
|
|
669
|
+
name: asStringUnknown(row.name),
|
|
670
|
+
kind: asStringUnknown(row.kind, "project"),
|
|
671
|
+
language: asStringUnknown(row.language, "dotnet"),
|
|
672
|
+
target_framework: asStringUnknown(row.target_framework),
|
|
673
|
+
summary: asStringUnknown(row.summary),
|
|
674
|
+
file_count: asNumberUnknown(row.file_count, 0),
|
|
675
|
+
updated_at: asStringUnknown(row.updated_at),
|
|
676
|
+
source_of_truth: asBooleanUnknown(row.source_of_truth, false),
|
|
677
|
+
trust_level: asNumberUnknown(row.trust_level, 80),
|
|
678
|
+
status: asStringUnknown(row.status, "active")
|
|
679
|
+
};
|
|
680
|
+
})
|
|
681
|
+
.filter((value): value is ProjectRecord => value !== null);
|
|
682
|
+
}
|
|
683
|
+
|
|
554
684
|
function parseRyuGraphRelations(
|
|
555
685
|
rows: UnknownRow[],
|
|
556
|
-
relation:
|
|
686
|
+
relation: RelationType,
|
|
557
687
|
noteField: string
|
|
558
688
|
): RelationRecord[] {
|
|
559
689
|
return rows
|
|
@@ -578,15 +708,37 @@ export async function loadContextData(): Promise<ContextData> {
|
|
|
578
708
|
const cachedDocuments = parseDocuments(readJsonl(PATHS.documents));
|
|
579
709
|
const cachedAdrs = parseAdrs(readJsonl(PATHS.adrEntities));
|
|
580
710
|
const cachedChunks = parseChunkEntities(readJsonl(PATHS.chunkEntities));
|
|
711
|
+
const cachedModules = parseModuleEntities(readJsonl(PATHS.moduleEntities));
|
|
712
|
+
const cachedProjects = parseProjectEntities(readJsonl(PATHS.projectEntities));
|
|
581
713
|
const cachedChunkRelations = [
|
|
714
|
+
...parseRelations(readJsonl(PATHS.definesRelations), "DEFINES"),
|
|
582
715
|
...parseRelations(readJsonl(PATHS.callsRelations), "CALLS", ["call_type"]),
|
|
583
|
-
...parseRelations(readJsonl(PATHS.importsRelations), "IMPORTS", ["import_name"])
|
|
716
|
+
...parseRelations(readJsonl(PATHS.importsRelations), "IMPORTS", ["import_name"]),
|
|
717
|
+
...parseRelations(readJsonl(PATHS.callsSqlRelations), "CALLS_SQL"),
|
|
718
|
+
...parseRelations(readJsonl(PATHS.usesConfigKeyRelations), "USES_CONFIG_KEY"),
|
|
719
|
+
...parseRelations(readJsonl(PATHS.usesResourceKeyRelations), "USES_RESOURCE_KEY"),
|
|
720
|
+
...parseRelations(readJsonl(PATHS.usesSettingKeyRelations), "USES_SETTING_KEY")
|
|
721
|
+
];
|
|
722
|
+
const cachedModuleRelations = [
|
|
723
|
+
...parseRelations(readJsonl(PATHS.containsRelations), "CONTAINS"),
|
|
724
|
+
...parseRelations(readJsonl(PATHS.containsModuleRelations), "CONTAINS_MODULE"),
|
|
725
|
+
...parseRelations(readJsonl(PATHS.exportsRelations), "EXPORTS")
|
|
726
|
+
];
|
|
727
|
+
const cachedProjectRelations = [
|
|
728
|
+
...parseRelations(readJsonl(PATHS.includesFileRelations), "INCLUDES_FILE"),
|
|
729
|
+
...parseRelations(readJsonl(PATHS.referencesProjectRelations), "REFERENCES_PROJECT"),
|
|
730
|
+
...parseRelations(readJsonl(PATHS.usesResourceRelations), "USES_RESOURCE"),
|
|
731
|
+
...parseRelations(readJsonl(PATHS.usesSettingRelations), "USES_SETTING"),
|
|
732
|
+
...parseRelations(readJsonl(PATHS.usesConfigRelations), "USES_CONFIG"),
|
|
733
|
+
...parseRelations(readJsonl(PATHS.transformsConfigRelations), "TRANSFORMS_CONFIG")
|
|
584
734
|
];
|
|
585
735
|
const cachedRelations = [
|
|
586
736
|
...parseRelations(readJsonl(PATHS.constrainsRelations), "CONSTRAINS"),
|
|
587
737
|
...parseRelations(readJsonl(PATHS.implementsRelations), "IMPLEMENTS"),
|
|
588
738
|
...parseRelations(readJsonl(PATHS.supersedesRelations), "SUPERSEDES"),
|
|
589
|
-
...cachedChunkRelations
|
|
739
|
+
...cachedChunkRelations,
|
|
740
|
+
...cachedModuleRelations,
|
|
741
|
+
...cachedProjectRelations
|
|
590
742
|
];
|
|
591
743
|
|
|
592
744
|
const yamlRules = parseRulesYaml(readFileIfExists(PATHS.rulesYaml));
|
|
@@ -600,6 +752,8 @@ export async function loadContextData(): Promise<ContextData> {
|
|
|
600
752
|
adrs: cachedAdrs,
|
|
601
753
|
rules: cachedRules,
|
|
602
754
|
chunks: cachedChunks,
|
|
755
|
+
modules: cachedModules,
|
|
756
|
+
projects: cachedProjects,
|
|
603
757
|
relations: cachedRelations,
|
|
604
758
|
ranking,
|
|
605
759
|
source: "cache",
|
|
@@ -608,97 +762,42 @@ export async function loadContextData(): Promise<ContextData> {
|
|
|
608
762
|
}
|
|
609
763
|
|
|
610
764
|
try {
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
MATCH (a:ADR)
|
|
648
|
-
RETURN
|
|
649
|
-
a.id AS id,
|
|
650
|
-
a.path AS path,
|
|
651
|
-
a.title AS title,
|
|
652
|
-
a.body AS body,
|
|
653
|
-
a.decision_date AS decision_date,
|
|
654
|
-
a.supersedes_id AS supersedes_id,
|
|
655
|
-
a.source_of_truth AS source_of_truth,
|
|
656
|
-
a.trust_level AS trust_level,
|
|
657
|
-
a.status AS status;
|
|
658
|
-
`
|
|
659
|
-
),
|
|
660
|
-
queryRows(
|
|
661
|
-
connection,
|
|
662
|
-
`
|
|
663
|
-
MATCH (c:Chunk)
|
|
664
|
-
RETURN
|
|
665
|
-
c.id AS id,
|
|
666
|
-
c.file_id AS file_id,
|
|
667
|
-
c.name AS name,
|
|
668
|
-
c.kind AS kind,
|
|
669
|
-
c.signature AS signature,
|
|
670
|
-
c.body AS body,
|
|
671
|
-
c.start_line AS start_line,
|
|
672
|
-
c.end_line AS end_line,
|
|
673
|
-
c.language AS language,
|
|
674
|
-
c.updated_at AS updated_at,
|
|
675
|
-
c.source_of_truth AS source_of_truth,
|
|
676
|
-
c.trust_level AS trust_level,
|
|
677
|
-
c.status AS status;
|
|
678
|
-
`
|
|
679
|
-
),
|
|
680
|
-
queryRows(
|
|
681
|
-
connection,
|
|
682
|
-
`
|
|
683
|
-
MATCH (r:Rule)-[c:CONSTRAINS]->(f:File)
|
|
684
|
-
RETURN r.id AS from, f.id AS to, c.note AS note;
|
|
685
|
-
`
|
|
686
|
-
),
|
|
687
|
-
queryRows(
|
|
688
|
-
connection,
|
|
689
|
-
`
|
|
690
|
-
MATCH (f:File)-[i:IMPLEMENTS]->(r:Rule)
|
|
691
|
-
RETURN f.id AS from, r.id AS to, i.note AS note;
|
|
692
|
-
`
|
|
693
|
-
),
|
|
694
|
-
queryRows(
|
|
695
|
-
connection,
|
|
696
|
-
`
|
|
697
|
-
MATCH (a1:ADR)-[s:SUPERSEDES]->(a2:ADR)
|
|
698
|
-
RETURN a1.id AS from, a2.id AS to, s.reason AS note;
|
|
699
|
-
`
|
|
700
|
-
)
|
|
701
|
-
]);
|
|
765
|
+
const ryuQueries = await Promise.all([
|
|
766
|
+
queryRows(connection, `MATCH (f:File) RETURN f.id AS id, f.path AS path, f.kind AS kind, f.excerpt AS excerpt, f.updated_at AS updated_at, f.source_of_truth AS source_of_truth, f.trust_level AS trust_level, f.status AS status;`),
|
|
767
|
+
queryRows(connection, `MATCH (r:Rule) RETURN r.id AS id, r.title AS title, r.body AS body, r.scope AS scope, r.priority AS priority, r.updated_at AS updated_at, r.source_of_truth AS source_of_truth, r.trust_level AS trust_level, r.status AS status;`),
|
|
768
|
+
queryRows(connection, `MATCH (a:ADR) RETURN a.id AS id, a.path AS path, a.title AS title, a.body AS body, a.decision_date AS decision_date, a.supersedes_id AS supersedes_id, a.source_of_truth AS source_of_truth, a.trust_level AS trust_level, a.status AS status;`),
|
|
769
|
+
queryRows(connection, `MATCH (c:Chunk) RETURN c.id AS id, c.file_id AS file_id, c.name AS name, c.kind AS kind, c.signature AS signature, c.body AS body, c.description AS description, c.start_line AS start_line, c.end_line AS end_line, c.language AS language, c.exported AS exported, c.updated_at AS updated_at, c.source_of_truth AS source_of_truth, c.trust_level AS trust_level, c.status AS status;`),
|
|
770
|
+
queryRows(connection, `MATCH (m:Module) RETURN m.id AS id, m.path AS path, m.name AS name, m.summary AS summary, m.file_count AS file_count, m.exported_symbols AS exported_symbols, m.updated_at AS updated_at, m.source_of_truth AS source_of_truth, m.trust_level AS trust_level, m.status AS status;`),
|
|
771
|
+
queryRows(connection, `MATCH (p:Project) RETURN p.id AS id, p.path AS path, p.name AS name, p.kind AS kind, p.language AS language, p.target_framework AS target_framework, p.summary AS summary, p.file_count AS file_count, p.updated_at AS updated_at, p.source_of_truth AS source_of_truth, p.trust_level AS trust_level, p.status AS status;`),
|
|
772
|
+
queryRows(connection, `MATCH (r:Rule)-[c:CONSTRAINS]->(f:File) RETURN r.id AS from, f.id AS to, c.note AS note;`),
|
|
773
|
+
queryRows(connection, `MATCH (f:File)-[i:IMPLEMENTS]->(r:Rule) RETURN f.id AS from, r.id AS to, i.note AS note;`),
|
|
774
|
+
queryRows(connection, `MATCH (a1:ADR)-[s:SUPERSEDES]->(a2:ADR) RETURN a1.id AS from, a2.id AS to, s.reason AS note;`),
|
|
775
|
+
queryRows(connection, `MATCH (f:File)-[:DEFINES]->(c:Chunk) RETURN f.id AS from, c.id AS to;`),
|
|
776
|
+
queryRows(connection, `MATCH (c1:Chunk)-[ca:CALLS]->(c2:Chunk) RETURN c1.id AS from, c2.id AS to, ca.call_type AS call_type;`),
|
|
777
|
+
queryRows(connection, `MATCH (c:Chunk)-[im:IMPORTS]->(f:File) RETURN c.id AS from, f.id AS to, im.import_name AS import_name;`),
|
|
778
|
+
queryRows(connection, `MATCH (f:File)-[cs:CALLS_SQL]->(c:Chunk) RETURN f.id AS from, c.id AS to, cs.note AS note;`),
|
|
779
|
+
queryRows(connection, `MATCH (f:File)-[uck:USES_CONFIG_KEY]->(c:Chunk) RETURN f.id AS from, c.id AS to, uck.note AS note;`),
|
|
780
|
+
queryRows(connection, `MATCH (f:File)-[urk:USES_RESOURCE_KEY]->(c:Chunk) RETURN f.id AS from, c.id AS to, urk.note AS note;`),
|
|
781
|
+
queryRows(connection, `MATCH (f:File)-[usk:USES_SETTING_KEY]->(c:Chunk) RETURN f.id AS from, c.id AS to, usk.note AS note;`),
|
|
782
|
+
queryRows(connection, `MATCH (m:Module)-[:CONTAINS]->(f:File) RETURN m.id AS from, f.id AS to;`),
|
|
783
|
+
queryRows(connection, `MATCH (m1:Module)-[:CONTAINS_MODULE]->(m2:Module) RETURN m1.id AS from, m2.id AS to;`),
|
|
784
|
+
queryRows(connection, `MATCH (m:Module)-[:EXPORTS]->(c:Chunk) RETURN m.id AS from, c.id AS to;`),
|
|
785
|
+
queryRows(connection, `MATCH (p:Project)-[:INCLUDES_FILE]->(f:File) RETURN p.id AS from, f.id AS to;`),
|
|
786
|
+
queryRows(connection, `MATCH (p1:Project)-[rp:REFERENCES_PROJECT]->(p2:Project) RETURN p1.id AS from, p2.id AS to, rp.note AS note;`),
|
|
787
|
+
queryRows(connection, `MATCH (f1:File)-[ur:USES_RESOURCE]->(f2:File) RETURN f1.id AS from, f2.id AS to, ur.note AS note;`),
|
|
788
|
+
queryRows(connection, `MATCH (f1:File)-[us:USES_SETTING]->(f2:File) RETURN f1.id AS from, f2.id AS to, us.note AS note;`),
|
|
789
|
+
queryRows(connection, `MATCH (f1:File)-[uc:USES_CONFIG]->(f2:File) RETURN f1.id AS from, f2.id AS to, uc.note AS note;`),
|
|
790
|
+
queryRows(connection, `MATCH (f1:File)-[tc:TRANSFORMS_CONFIG]->(f2:File) RETURN f1.id AS from, f2.id AS to, tc.note AS note;`)
|
|
791
|
+
]);
|
|
792
|
+
|
|
793
|
+
// Named destructuring to avoid positional misalignment with 14 parallel queries
|
|
794
|
+
const [
|
|
795
|
+
fileRows, ruleRows, adrRows, chunkRows, moduleRows, projectRows, // entities
|
|
796
|
+
constrainsRows, implementsRows, supersedesRows, // core relations
|
|
797
|
+
definesRows, callsRows, importsRows, callsSqlRows, usesConfigKeyRows, usesResourceKeyRows, usesSettingKeyRows, // chunk relations
|
|
798
|
+
containsRows, containsModuleRows, exportsRows, // module relations
|
|
799
|
+
includesFileRows, referencesProjectRows, usesResourceRows, usesSettingRows, usesConfigRows, transformsConfigRows // project/file relations
|
|
800
|
+
] = ryuQueries;
|
|
702
801
|
|
|
703
802
|
const contentById = new Map(cachedDocuments.map((doc) => [doc.id, doc.content]));
|
|
704
803
|
|
|
@@ -706,10 +805,28 @@ export async function loadContextData(): Promise<ContextData> {
|
|
|
706
805
|
const ryuRules = parseRyuGraphRules(ruleRows);
|
|
707
806
|
const ryuAdrs = parseRyuGraphAdrs(adrRows);
|
|
708
807
|
const ryuChunks = parseRyuGraphChunks(chunkRows);
|
|
808
|
+
const ryuModules = parseRyuGraphModules(moduleRows);
|
|
809
|
+
const ryuProjects = parseRyuGraphProjects(projectRows);
|
|
709
810
|
const ryuRelations = [
|
|
710
811
|
...parseRyuGraphRelations(constrainsRows, "CONSTRAINS", "note"),
|
|
711
812
|
...parseRyuGraphRelations(implementsRows, "IMPLEMENTS", "note"),
|
|
712
|
-
...parseRyuGraphRelations(supersedesRows, "SUPERSEDES", "note")
|
|
813
|
+
...parseRyuGraphRelations(supersedesRows, "SUPERSEDES", "note"),
|
|
814
|
+
...parseRyuGraphRelations(definesRows, "DEFINES", "note"),
|
|
815
|
+
...parseRyuGraphRelations(callsRows, "CALLS", "call_type"),
|
|
816
|
+
...parseRyuGraphRelations(importsRows, "IMPORTS", "import_name"),
|
|
817
|
+
...parseRyuGraphRelations(callsSqlRows, "CALLS_SQL", "note"),
|
|
818
|
+
...parseRyuGraphRelations(usesConfigKeyRows, "USES_CONFIG_KEY", "note"),
|
|
819
|
+
...parseRyuGraphRelations(usesResourceKeyRows, "USES_RESOURCE_KEY", "note"),
|
|
820
|
+
...parseRyuGraphRelations(usesSettingKeyRows, "USES_SETTING_KEY", "note"),
|
|
821
|
+
...parseRyuGraphRelations(containsRows, "CONTAINS", "note"),
|
|
822
|
+
...parseRyuGraphRelations(containsModuleRows, "CONTAINS_MODULE", "note"),
|
|
823
|
+
...parseRyuGraphRelations(exportsRows, "EXPORTS", "note"),
|
|
824
|
+
...parseRyuGraphRelations(includesFileRows, "INCLUDES_FILE", "note"),
|
|
825
|
+
...parseRyuGraphRelations(referencesProjectRows, "REFERENCES_PROJECT", "note"),
|
|
826
|
+
...parseRyuGraphRelations(usesResourceRows, "USES_RESOURCE", "note"),
|
|
827
|
+
...parseRyuGraphRelations(usesSettingRows, "USES_SETTING", "note"),
|
|
828
|
+
...parseRyuGraphRelations(usesConfigRows, "USES_CONFIG", "note"),
|
|
829
|
+
...parseRyuGraphRelations(transformsConfigRows, "TRANSFORMS_CONFIG", "note")
|
|
713
830
|
];
|
|
714
831
|
|
|
715
832
|
return {
|
|
@@ -717,7 +834,11 @@ export async function loadContextData(): Promise<ContextData> {
|
|
|
717
834
|
adrs: ryuAdrs.length > 0 ? ryuAdrs : cachedAdrs,
|
|
718
835
|
rules: ryuRules.length > 0 ? ryuRules : cachedRules,
|
|
719
836
|
chunks: ryuChunks.length > 0 ? ryuChunks : cachedChunks,
|
|
720
|
-
|
|
837
|
+
modules: ryuModules.length > 0 ? ryuModules : cachedModules,
|
|
838
|
+
projects: ryuProjects.length > 0 ? ryuProjects : cachedProjects,
|
|
839
|
+
// Ryu now queries all relation types (core + chunk + module), so no need
|
|
840
|
+
// to merge cached chunk/module relations separately.
|
|
841
|
+
relations: ryuRelations.length > 0 ? ryuRelations : cachedRelations,
|
|
721
842
|
ranking,
|
|
722
843
|
source: "ryu"
|
|
723
844
|
};
|
|
@@ -732,6 +853,8 @@ export async function loadContextData(): Promise<ContextData> {
|
|
|
732
853
|
adrs: cachedAdrs,
|
|
733
854
|
rules: cachedRules,
|
|
734
855
|
chunks: cachedChunks,
|
|
856
|
+
modules: cachedModules,
|
|
857
|
+
projects: cachedProjects,
|
|
735
858
|
relations: cachedRelations,
|
|
736
859
|
ranking,
|
|
737
860
|
source: "cache",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RelationRecord } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export function relationDegree(relations: RelationRecord[]): Map<string, number> {
|
|
4
|
+
const degrees = new Map<string, number>();
|
|
5
|
+
|
|
6
|
+
for (const relation of relations) {
|
|
7
|
+
degrees.set(relation.from, (degrees.get(relation.from) ?? 0) + 1);
|
|
8
|
+
degrees.set(relation.to, (degrees.get(relation.to) ?? 0) + 1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return degrees;
|
|
12
|
+
}
|