@danielblomma/cortex-mcp 2.2.1 → 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 +2 -2
- package/scaffold/mcp/src/contextEntities.ts +30 -19
- package/scaffold/mcp/src/loadGraph.ts +254 -242
- package/scaffold/mcp/src/search.ts +18 -7
- package/scaffold/mcp/src/searchResults.ts +138 -94
- package/scaffold/mcp/tests/search-graph-score.test.mjs +74 -1
- package/scaffold/scripts/ingest.mjs +43 -4
|
@@ -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
|
|
131
|
-
return
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
341
|
-
|
|
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
|
-
|
|
350
|
-
|
|
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*
|
|
521
|
+
function* collectIds<T extends { id: string }>(items: Iterable<T>, collected: Set<string>): Iterable<T> {
|
|
510
522
|
for (const item of items) {
|
|
511
|
-
|
|
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
|
-
//
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
async function bulkLoad(conn: Connection
|
|
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(
|
|
550
|
-
const ruleIds = new Set(
|
|
551
|
-
const adrIds = new Set(
|
|
552
|
-
const chunkIds = new Set(
|
|
553
|
-
const moduleIds = new Set(
|
|
554
|
-
const projectIds = new Set(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|