@swarmvaultai/engine 0.1.18 → 0.1.19
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 +15 -2
- package/dist/index.d.ts +70 -3
- package/dist/index.js +1034 -44
- package/dist/viewer/assets/index-DWUwoLny.js +330 -0
- package/dist/viewer/index.html +1 -1
- package/dist/viewer/lib.d.ts +60 -1
- package/dist/viewer/lib.js +33 -0
- package/package.json +1 -1
- package/dist/viewer/assets/index-sMKSYq5V.js +0 -330
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ function buildManagedBlock(target) {
|
|
|
38
38
|
"- Read `swarmvault.schema.md` before compile or query style work. It is the canonical schema path.",
|
|
39
39
|
"- Treat `raw/` as immutable source input.",
|
|
40
40
|
"- Treat `wiki/` as generated markdown owned by the agent and compiler workflow.",
|
|
41
|
-
"- Read `wiki/
|
|
41
|
+
"- Read `wiki/graph/report.md` before broad file searching when it exists; otherwise start with `wiki/index.md`.",
|
|
42
42
|
"- Preserve frontmatter fields including `page_id`, `source_ids`, `node_ids`, `freshness`, and `source_hashes`.",
|
|
43
43
|
"- Save high-value answers back into `wiki/outputs/` instead of leaving them only in chat.",
|
|
44
44
|
"- Prefer `swarmvault ingest`, `swarmvault compile`, `swarmvault query`, and `swarmvault lint` for SwarmVault maintenance tasks.",
|
|
@@ -148,6 +148,16 @@ import { createRequire } from "module";
|
|
|
148
148
|
import path2 from "path";
|
|
149
149
|
var require2 = createRequire(import.meta.url);
|
|
150
150
|
var TREE_SITTER_PACKAGE_ROOT = path2.dirname(path2.dirname(require2.resolve("@vscode/tree-sitter-wasm")));
|
|
151
|
+
var RATIONALE_MARKERS = ["NOTE:", "IMPORTANT:", "HACK:", "WHY:", "RATIONALE:"];
|
|
152
|
+
function stripKnownCommentPrefix(line) {
|
|
153
|
+
let next = line.trim();
|
|
154
|
+
for (const prefix of ["/**", "/*", "*/", "//", "#", "--", "*"]) {
|
|
155
|
+
if (next.startsWith(prefix)) {
|
|
156
|
+
next = next.slice(prefix.length).trimStart();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return next;
|
|
160
|
+
}
|
|
151
161
|
var treeSitterModulePromise;
|
|
152
162
|
var treeSitterInitPromise;
|
|
153
163
|
var languageCache = /* @__PURE__ */ new Map();
|
|
@@ -279,9 +289,107 @@ function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportL
|
|
|
279
289
|
diagnostics
|
|
280
290
|
};
|
|
281
291
|
}
|
|
292
|
+
function cleanCommentText(value) {
|
|
293
|
+
return normalizeWhitespace(
|
|
294
|
+
value.split(/\r?\n/).map((line) => stripKnownCommentPrefix(line)).join("\n").trim()
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
function normalizeRationaleText(value) {
|
|
298
|
+
let next = normalizeWhitespace(value.trim());
|
|
299
|
+
const upper = next.toUpperCase();
|
|
300
|
+
for (const marker of RATIONALE_MARKERS) {
|
|
301
|
+
if (upper.startsWith(marker)) {
|
|
302
|
+
next = next.slice(marker.length).trimStart();
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return next;
|
|
307
|
+
}
|
|
308
|
+
function rationaleKindFromText(text) {
|
|
309
|
+
const upper = text.toUpperCase();
|
|
310
|
+
return RATIONALE_MARKERS.some((marker) => upper.startsWith(marker)) ? "marker" : "comment";
|
|
311
|
+
}
|
|
312
|
+
function isLikelyRationaleText(value) {
|
|
313
|
+
if (value.length < 20) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
const upper = value.toUpperCase();
|
|
317
|
+
if (RATIONALE_MARKERS.some((marker) => upper.startsWith(marker))) {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
const lower = value.toLowerCase();
|
|
321
|
+
return ["why", "because", "tradeoff", "important", "avoid", "workaround", "reason", "so that", "in order to"].some(
|
|
322
|
+
(needle) => lower.includes(needle)
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
function makeRationale(manifest, index, text, kind, symbolName) {
|
|
326
|
+
const normalized = normalizeRationaleText(cleanCommentText(text));
|
|
327
|
+
if (!isLikelyRationaleText(normalized)) {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
id: `rationale:${manifest.sourceId}:${index}`,
|
|
332
|
+
text: truncate(normalized, 280),
|
|
333
|
+
citation: manifest.sourceId,
|
|
334
|
+
kind,
|
|
335
|
+
symbolName
|
|
336
|
+
};
|
|
337
|
+
}
|
|
282
338
|
function nodeText(node) {
|
|
283
339
|
return node?.text ?? "";
|
|
284
340
|
}
|
|
341
|
+
function moduleDocstringNode(rootNode) {
|
|
342
|
+
const first = rootNode.namedChildren[0];
|
|
343
|
+
if (!first || first.type !== "expression_statement") {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
return first.namedChildren.find((child) => child?.type === "string" || child?.type === "concatenated_string") ?? null;
|
|
347
|
+
}
|
|
348
|
+
function bodyDocstringNode(node) {
|
|
349
|
+
const body = node?.childForFieldName("body");
|
|
350
|
+
if (!body) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
const first = body.namedChildren[0];
|
|
354
|
+
if (!first || first.type !== "expression_statement") {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
return first.namedChildren.find((child) => child?.type === "string" || child?.type === "concatenated_string") ?? null;
|
|
358
|
+
}
|
|
359
|
+
function unquoteDocstringText(value) {
|
|
360
|
+
return value.trim().replace(/^("""|'''|"|')/, "").replace(/("""|'''|"|')$/, "");
|
|
361
|
+
}
|
|
362
|
+
function commentNodes(rootNode) {
|
|
363
|
+
return rootNode.descendantsOfType("comment").filter((node) => node !== null);
|
|
364
|
+
}
|
|
365
|
+
function extractTreeSitterRationales(manifest, language, rootNode) {
|
|
366
|
+
const results = [];
|
|
367
|
+
let index = 0;
|
|
368
|
+
const push = (text, kind, symbolName) => {
|
|
369
|
+
const rationale = makeRationale(manifest, index + 1, text, kind, symbolName);
|
|
370
|
+
if (rationale) {
|
|
371
|
+
results.push(rationale);
|
|
372
|
+
index += 1;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
if (language === "python") {
|
|
376
|
+
const moduleDocstring = moduleDocstringNode(rootNode);
|
|
377
|
+
if (moduleDocstring) {
|
|
378
|
+
push(unquoteDocstringText(moduleDocstring.text), "docstring");
|
|
379
|
+
}
|
|
380
|
+
for (const node of rootNode.descendantsOfType(["class_definition", "function_definition"]).filter((item) => item !== null)) {
|
|
381
|
+
const name = extractIdentifier(node.childForFieldName("name"));
|
|
382
|
+
const docstring = bodyDocstringNode(node);
|
|
383
|
+
if (docstring) {
|
|
384
|
+
push(unquoteDocstringText(docstring.text), "docstring", name);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
for (const commentNode of commentNodes(rootNode)) {
|
|
389
|
+
push(commentNode.text, rationaleKindFromText(commentNode.text));
|
|
390
|
+
}
|
|
391
|
+
return uniqueBy(results, (item) => `${item.symbolName ?? ""}:${item.text.toLowerCase()}`);
|
|
392
|
+
}
|
|
285
393
|
function findNamedChild(node, type) {
|
|
286
394
|
return node?.namedChildren.find((child) => child?.type === type) ?? null;
|
|
287
395
|
}
|
|
@@ -950,41 +1058,45 @@ async function analyzeTreeSitterCode(manifest, content, language) {
|
|
|
950
1058
|
parser.setLanguage(await loadLanguage(language));
|
|
951
1059
|
const tree = parser.parse(content);
|
|
952
1060
|
if (!tree) {
|
|
953
|
-
return
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1061
|
+
return {
|
|
1062
|
+
code: finalizeCodeAnalysis(
|
|
1063
|
+
manifest,
|
|
1064
|
+
language,
|
|
1065
|
+
[],
|
|
1066
|
+
[],
|
|
1067
|
+
[],
|
|
1068
|
+
[
|
|
1069
|
+
{
|
|
1070
|
+
code: 9e3,
|
|
1071
|
+
category: "error",
|
|
1072
|
+
message: `Failed to parse ${language} source.`,
|
|
1073
|
+
line: 1,
|
|
1074
|
+
column: 1
|
|
1075
|
+
}
|
|
1076
|
+
]
|
|
1077
|
+
),
|
|
1078
|
+
rationales: []
|
|
1079
|
+
};
|
|
969
1080
|
}
|
|
970
1081
|
try {
|
|
971
1082
|
const diagnostics = diagnosticsFromTree(tree.rootNode);
|
|
1083
|
+
const rationales = extractTreeSitterRationales(manifest, language, tree.rootNode);
|
|
972
1084
|
switch (language) {
|
|
973
1085
|
case "python":
|
|
974
|
-
return pythonCodeAnalysis(manifest, tree.rootNode, diagnostics);
|
|
1086
|
+
return { code: pythonCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
|
|
975
1087
|
case "go":
|
|
976
|
-
return goCodeAnalysis(manifest, tree.rootNode, diagnostics);
|
|
1088
|
+
return { code: goCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
|
|
977
1089
|
case "rust":
|
|
978
|
-
return rustCodeAnalysis(manifest, tree.rootNode, diagnostics);
|
|
1090
|
+
return { code: rustCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
|
|
979
1091
|
case "java":
|
|
980
|
-
return javaCodeAnalysis(manifest, tree.rootNode, diagnostics);
|
|
1092
|
+
return { code: javaCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
|
|
981
1093
|
case "csharp":
|
|
982
|
-
return csharpCodeAnalysis(manifest, tree.rootNode, diagnostics);
|
|
1094
|
+
return { code: csharpCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
|
|
983
1095
|
case "php":
|
|
984
|
-
return phpCodeAnalysis(manifest, tree.rootNode, diagnostics);
|
|
1096
|
+
return { code: phpCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
|
|
985
1097
|
case "c":
|
|
986
1098
|
case "cpp":
|
|
987
|
-
return cFamilyCodeAnalysis(manifest, language, tree.rootNode, diagnostics);
|
|
1099
|
+
return { code: cFamilyCodeAnalysis(manifest, language, tree.rootNode, diagnostics), rationales };
|
|
988
1100
|
}
|
|
989
1101
|
} finally {
|
|
990
1102
|
tree.delete();
|
|
@@ -1183,6 +1295,62 @@ function normalizeSymbolReference2(value) {
|
|
|
1183
1295
|
const lastSegment = trimmed.split(/::|\./).filter(Boolean).at(-1) ?? trimmed;
|
|
1184
1296
|
return lastSegment.replace(/[,:;]+$/g, "").trim();
|
|
1185
1297
|
}
|
|
1298
|
+
var RATIONALE_MARKERS2 = ["NOTE:", "IMPORTANT:", "HACK:", "WHY:", "RATIONALE:"];
|
|
1299
|
+
function stripKnownCommentPrefix2(line) {
|
|
1300
|
+
let next = line.trim();
|
|
1301
|
+
for (const prefix of ["/**", "/*", "*/", "//", "#", "*"]) {
|
|
1302
|
+
if (next.startsWith(prefix)) {
|
|
1303
|
+
next = next.slice(prefix.length).trimStart();
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return next;
|
|
1307
|
+
}
|
|
1308
|
+
function cleanCommentText2(value) {
|
|
1309
|
+
return normalizeWhitespace(
|
|
1310
|
+
value.split(/\r?\n/).map((line) => stripKnownCommentPrefix2(line)).join("\n").trim()
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
function rationaleKindFromText2(text) {
|
|
1314
|
+
const upper = text.toUpperCase();
|
|
1315
|
+
return RATIONALE_MARKERS2.some((marker) => upper.startsWith(marker)) ? "marker" : "comment";
|
|
1316
|
+
}
|
|
1317
|
+
function normalizeRationaleText2(value) {
|
|
1318
|
+
let next = normalizeWhitespace(value.trim());
|
|
1319
|
+
const upper = next.toUpperCase();
|
|
1320
|
+
for (const marker of RATIONALE_MARKERS2) {
|
|
1321
|
+
if (upper.startsWith(marker)) {
|
|
1322
|
+
next = next.slice(marker.length).trimStart();
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return next;
|
|
1327
|
+
}
|
|
1328
|
+
function isLikelyRationaleText2(value) {
|
|
1329
|
+
if (value.length < 20) {
|
|
1330
|
+
return false;
|
|
1331
|
+
}
|
|
1332
|
+
const upper = value.toUpperCase();
|
|
1333
|
+
if (RATIONALE_MARKERS2.some((marker) => upper.startsWith(marker))) {
|
|
1334
|
+
return true;
|
|
1335
|
+
}
|
|
1336
|
+
const lower = value.toLowerCase();
|
|
1337
|
+
return ["why", "because", "tradeoff", "important", "avoid", "workaround", "reason", "so that", "in order to"].some(
|
|
1338
|
+
(needle) => lower.includes(needle)
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
function makeRationale2(manifest, index, text, kind, symbolName) {
|
|
1342
|
+
const normalized = normalizeRationaleText2(cleanCommentText2(text));
|
|
1343
|
+
if (!isLikelyRationaleText2(normalized)) {
|
|
1344
|
+
return null;
|
|
1345
|
+
}
|
|
1346
|
+
return {
|
|
1347
|
+
id: `rationale:${manifest.sourceId}:${index}`,
|
|
1348
|
+
text: truncate(normalized, 280),
|
|
1349
|
+
citation: manifest.sourceId,
|
|
1350
|
+
kind,
|
|
1351
|
+
symbolName
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1186
1354
|
function stripCodeExtension2(filePath) {
|
|
1187
1355
|
return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
|
|
1188
1356
|
}
|
|
@@ -1231,6 +1399,46 @@ function finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, export
|
|
|
1231
1399
|
diagnostics
|
|
1232
1400
|
};
|
|
1233
1401
|
}
|
|
1402
|
+
function statementRationaleSymbolName(statement) {
|
|
1403
|
+
if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isEnumDeclaration(statement)) && statement.name) {
|
|
1404
|
+
return statement.name.text;
|
|
1405
|
+
}
|
|
1406
|
+
if (ts.isTypeAliasDeclaration(statement)) {
|
|
1407
|
+
return statement.name.text;
|
|
1408
|
+
}
|
|
1409
|
+
if (ts.isVariableStatement(statement)) {
|
|
1410
|
+
const first = statement.declarationList.declarations[0];
|
|
1411
|
+
return first && ts.isIdentifier(first.name) ? first.name.text : void 0;
|
|
1412
|
+
}
|
|
1413
|
+
return void 0;
|
|
1414
|
+
}
|
|
1415
|
+
function extractTypeScriptRationales(manifest, content, sourceFile) {
|
|
1416
|
+
const rationales = [];
|
|
1417
|
+
let index = 0;
|
|
1418
|
+
const pushRationale = (rawText, symbolName, kind) => {
|
|
1419
|
+
const rationale = makeRationale2(manifest, index + 1, rawText, kind ?? rationaleKindFromText2(rawText), symbolName);
|
|
1420
|
+
if (rationale) {
|
|
1421
|
+
rationales.push(rationale);
|
|
1422
|
+
index += 1;
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
const firstStatement = sourceFile.statements[0];
|
|
1426
|
+
if (firstStatement) {
|
|
1427
|
+
for (const range of ts.getLeadingCommentRanges(content, firstStatement.getFullStart()) ?? []) {
|
|
1428
|
+
pushRationale(content.slice(range.pos, range.end));
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
for (const statement of sourceFile.statements) {
|
|
1432
|
+
const symbolName = statementRationaleSymbolName(statement);
|
|
1433
|
+
for (const jsDoc of statement.jsDoc ?? []) {
|
|
1434
|
+
pushRationale(jsDoc.getText(sourceFile), symbolName, "docstring");
|
|
1435
|
+
}
|
|
1436
|
+
for (const range of ts.getLeadingCommentRanges(content, statement.getFullStart()) ?? []) {
|
|
1437
|
+
pushRationale(content.slice(range.pos, range.end), symbolName);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return uniqueBy(rationales, (item) => `${item.symbolName ?? ""}:${item.text.toLowerCase()}`);
|
|
1441
|
+
}
|
|
1234
1442
|
function analyzeTypeScriptLikeCode(manifest, content) {
|
|
1235
1443
|
const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType) ?? "typescript";
|
|
1236
1444
|
const sourceFile = ts.createSourceFile(
|
|
@@ -1443,7 +1651,10 @@ function analyzeTypeScriptLikeCode(manifest, content) {
|
|
|
1443
1651
|
column: (position?.character ?? 0) + 1
|
|
1444
1652
|
};
|
|
1445
1653
|
});
|
|
1446
|
-
return
|
|
1654
|
+
return {
|
|
1655
|
+
code: finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, exportLabels, diagnostics),
|
|
1656
|
+
rationales: extractTypeScriptRationales(manifest, content, sourceFile)
|
|
1657
|
+
};
|
|
1447
1658
|
}
|
|
1448
1659
|
function inferCodeLanguage(filePath, mimeType = "") {
|
|
1449
1660
|
const extension = path3.extname(filePath).toLowerCase();
|
|
@@ -1806,9 +2017,9 @@ function escapeRegExp2(value) {
|
|
|
1806
2017
|
}
|
|
1807
2018
|
async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
1808
2019
|
const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType) ?? "typescript";
|
|
1809
|
-
const code = language === "javascript" || language === "jsx" || language === "typescript" || language === "tsx" ? analyzeTypeScriptLikeCode(manifest, extractedText) : await analyzeTreeSitterCode(manifest, extractedText, language);
|
|
2020
|
+
const { code, rationales } = language === "javascript" || language === "jsx" || language === "typescript" || language === "tsx" ? analyzeTypeScriptLikeCode(manifest, extractedText) : await analyzeTreeSitterCode(manifest, extractedText, language);
|
|
1810
2021
|
return {
|
|
1811
|
-
analysisVersion:
|
|
2022
|
+
analysisVersion: 4,
|
|
1812
2023
|
sourceId: manifest.sourceId,
|
|
1813
2024
|
sourceHash: manifest.contentHash,
|
|
1814
2025
|
schemaHash,
|
|
@@ -1818,6 +2029,7 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
|
1818
2029
|
entities: [],
|
|
1819
2030
|
claims: codeClaims(manifest, code),
|
|
1820
2031
|
questions: codeQuestions(manifest, code),
|
|
2032
|
+
rationales,
|
|
1821
2033
|
code,
|
|
1822
2034
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1823
2035
|
};
|
|
@@ -2871,7 +3083,7 @@ import { z as z6 } from "zod";
|
|
|
2871
3083
|
// src/analysis.ts
|
|
2872
3084
|
import path7 from "path";
|
|
2873
3085
|
import { z } from "zod";
|
|
2874
|
-
var ANALYSIS_FORMAT_VERSION =
|
|
3086
|
+
var ANALYSIS_FORMAT_VERSION = 4;
|
|
2875
3087
|
var sourceAnalysisSchema = z.object({
|
|
2876
3088
|
title: z.string().min(1),
|
|
2877
3089
|
summary: z.string().min(1),
|
|
@@ -2989,6 +3201,7 @@ function heuristicAnalysis(manifest, text, schemaHash) {
|
|
|
2989
3201
|
citation: manifest.sourceId
|
|
2990
3202
|
})),
|
|
2991
3203
|
questions: concepts.slice(0, 3).map((term) => `How does ${term.name} relate to ${manifest.title}?`),
|
|
3204
|
+
rationales: [],
|
|
2992
3205
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2993
3206
|
};
|
|
2994
3207
|
}
|
|
@@ -3042,6 +3255,7 @@ ${truncate(text, 18e3)}`
|
|
|
3042
3255
|
citation: claim.citation
|
|
3043
3256
|
})),
|
|
3044
3257
|
questions: parsed.questions,
|
|
3258
|
+
rationales: [],
|
|
3045
3259
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3046
3260
|
};
|
|
3047
3261
|
}
|
|
@@ -3067,6 +3281,7 @@ async function analyzeSource(manifest, extractedText, provider, paths, schema) {
|
|
|
3067
3281
|
entities: [],
|
|
3068
3282
|
claims: [],
|
|
3069
3283
|
questions: [],
|
|
3284
|
+
rationales: [],
|
|
3070
3285
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3071
3286
|
};
|
|
3072
3287
|
} else if (provider.type === "heuristic") {
|
|
@@ -3695,6 +3910,309 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
|
|
|
3695
3910
|
);
|
|
3696
3911
|
}
|
|
3697
3912
|
|
|
3913
|
+
// src/graph-tools.ts
|
|
3914
|
+
function normalizeTarget(value) {
|
|
3915
|
+
return normalizeWhitespace(value).toLowerCase();
|
|
3916
|
+
}
|
|
3917
|
+
function nodeById(graph) {
|
|
3918
|
+
return new Map(graph.nodes.map((node) => [node.id, node]));
|
|
3919
|
+
}
|
|
3920
|
+
function pageById(graph) {
|
|
3921
|
+
return new Map(graph.pages.map((page) => [page.id, page]));
|
|
3922
|
+
}
|
|
3923
|
+
function scoreMatch(query, candidate) {
|
|
3924
|
+
const normalizedQuery = normalizeTarget(query);
|
|
3925
|
+
const normalizedCandidate = normalizeTarget(candidate);
|
|
3926
|
+
if (!normalizedQuery || !normalizedCandidate) {
|
|
3927
|
+
return 0;
|
|
3928
|
+
}
|
|
3929
|
+
if (normalizedCandidate === normalizedQuery) {
|
|
3930
|
+
return 100;
|
|
3931
|
+
}
|
|
3932
|
+
if (normalizedCandidate.startsWith(normalizedQuery)) {
|
|
3933
|
+
return 80;
|
|
3934
|
+
}
|
|
3935
|
+
if (normalizedCandidate.includes(normalizedQuery)) {
|
|
3936
|
+
return 60;
|
|
3937
|
+
}
|
|
3938
|
+
const queryTokens = normalizedQuery.split(/\s+/).filter(Boolean);
|
|
3939
|
+
const candidateTokens = new Set(normalizedCandidate.split(/\s+/).filter(Boolean));
|
|
3940
|
+
const overlap = queryTokens.filter((token) => candidateTokens.has(token)).length;
|
|
3941
|
+
return overlap ? overlap * 10 : 0;
|
|
3942
|
+
}
|
|
3943
|
+
function primaryNodeForPage(graph, page) {
|
|
3944
|
+
const byId = nodeById(graph);
|
|
3945
|
+
return page.nodeIds.map((nodeId) => byId.get(nodeId)).find((node) => Boolean(node));
|
|
3946
|
+
}
|
|
3947
|
+
function pageSearchMatches(graph, question, searchResults) {
|
|
3948
|
+
const pages = pageById(graph);
|
|
3949
|
+
return searchResults.map((result) => {
|
|
3950
|
+
const page = pages.get(result.pageId);
|
|
3951
|
+
const score = Math.max(scoreMatch(question, result.title), scoreMatch(question, result.path));
|
|
3952
|
+
if (!page || score <= 0) {
|
|
3953
|
+
return null;
|
|
3954
|
+
}
|
|
3955
|
+
return {
|
|
3956
|
+
type: "page",
|
|
3957
|
+
id: page.id,
|
|
3958
|
+
label: page.title,
|
|
3959
|
+
score
|
|
3960
|
+
};
|
|
3961
|
+
}).filter((match) => Boolean(match));
|
|
3962
|
+
}
|
|
3963
|
+
function nodeMatches(graph, query) {
|
|
3964
|
+
return graph.nodes.map((node) => ({
|
|
3965
|
+
type: "node",
|
|
3966
|
+
id: node.id,
|
|
3967
|
+
label: node.label,
|
|
3968
|
+
score: Math.max(scoreMatch(query, node.label), scoreMatch(query, node.id))
|
|
3969
|
+
})).filter((match) => match.score > 0).sort((left, right) => right.score - left.score || left.label.localeCompare(right.label));
|
|
3970
|
+
}
|
|
3971
|
+
function graphAdjacency(graph) {
|
|
3972
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
3973
|
+
const push = (nodeId, item) => {
|
|
3974
|
+
if (!adjacency.has(nodeId)) {
|
|
3975
|
+
adjacency.set(nodeId, []);
|
|
3976
|
+
}
|
|
3977
|
+
adjacency.get(nodeId)?.push(item);
|
|
3978
|
+
};
|
|
3979
|
+
for (const edge of graph.edges) {
|
|
3980
|
+
push(edge.source, { edge, nodeId: edge.target, direction: "outgoing" });
|
|
3981
|
+
push(edge.target, { edge, nodeId: edge.source, direction: "incoming" });
|
|
3982
|
+
}
|
|
3983
|
+
for (const [nodeId, items] of adjacency.entries()) {
|
|
3984
|
+
items.sort((left, right) => right.edge.confidence - left.edge.confidence || left.edge.relation.localeCompare(right.edge.relation));
|
|
3985
|
+
adjacency.set(nodeId, items);
|
|
3986
|
+
}
|
|
3987
|
+
return adjacency;
|
|
3988
|
+
}
|
|
3989
|
+
function resolveNode(graph, target) {
|
|
3990
|
+
const normalized = normalizeTarget(target);
|
|
3991
|
+
const byId = nodeById(graph);
|
|
3992
|
+
if (byId.has(target)) {
|
|
3993
|
+
return byId.get(target);
|
|
3994
|
+
}
|
|
3995
|
+
const exact = graph.nodes.find((node) => normalizeTarget(node.label) === normalized || normalizeTarget(node.id) === normalized);
|
|
3996
|
+
if (exact) {
|
|
3997
|
+
return exact;
|
|
3998
|
+
}
|
|
3999
|
+
const pages = graph.pages.map((page) => ({
|
|
4000
|
+
page,
|
|
4001
|
+
score: Math.max(scoreMatch(target, page.title), scoreMatch(target, page.path))
|
|
4002
|
+
})).filter((item) => item.score > 0).sort((left, right) => right.score - left.score || left.page.title.localeCompare(right.page.title));
|
|
4003
|
+
if (pages[0]) {
|
|
4004
|
+
return primaryNodeForPage(graph, pages[0].page);
|
|
4005
|
+
}
|
|
4006
|
+
return graph.nodes.map((node) => ({ node, score: Math.max(scoreMatch(target, node.label), scoreMatch(target, node.id)) })).filter((item) => item.score > 0).sort((left, right) => right.score - left.score || left.node.label.localeCompare(right.node.label))[0]?.node;
|
|
4007
|
+
}
|
|
4008
|
+
function communityLabel(graph, communityId) {
|
|
4009
|
+
if (!communityId) {
|
|
4010
|
+
return void 0;
|
|
4011
|
+
}
|
|
4012
|
+
const community = graph.communities?.find((item) => item.id === communityId);
|
|
4013
|
+
return community ? { id: community.id, label: community.label } : void 0;
|
|
4014
|
+
}
|
|
4015
|
+
function queryGraph(graph, question, searchResults, options) {
|
|
4016
|
+
const traversal = options?.traversal ?? "bfs";
|
|
4017
|
+
const budget = Math.max(3, Math.min(options?.budget ?? 12, 50));
|
|
4018
|
+
const matches = uniqueBy(
|
|
4019
|
+
[...pageSearchMatches(graph, question, searchResults), ...nodeMatches(graph, question)],
|
|
4020
|
+
(match) => `${match.type}:${match.id}`
|
|
4021
|
+
).sort((left, right) => right.score - left.score || left.label.localeCompare(right.label)).slice(0, 12);
|
|
4022
|
+
const pages = pageById(graph);
|
|
4023
|
+
const seeds = uniqueBy(
|
|
4024
|
+
[
|
|
4025
|
+
...searchResults.flatMap((result) => pages.get(result.pageId)?.nodeIds ?? []),
|
|
4026
|
+
...matches.filter((match) => match.type === "node").map((match) => match.id)
|
|
4027
|
+
],
|
|
4028
|
+
(item) => item
|
|
4029
|
+
).filter(Boolean);
|
|
4030
|
+
const adjacency = graphAdjacency(graph);
|
|
4031
|
+
const visitedNodeIds = [];
|
|
4032
|
+
const visitedEdgeIds = /* @__PURE__ */ new Set();
|
|
4033
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4034
|
+
const frontier = [...seeds];
|
|
4035
|
+
while (frontier.length && visitedNodeIds.length < budget) {
|
|
4036
|
+
const current = traversal === "dfs" ? frontier.pop() : frontier.shift();
|
|
4037
|
+
if (!current || seen.has(current)) {
|
|
4038
|
+
continue;
|
|
4039
|
+
}
|
|
4040
|
+
seen.add(current);
|
|
4041
|
+
visitedNodeIds.push(current);
|
|
4042
|
+
for (const neighbor of adjacency.get(current) ?? []) {
|
|
4043
|
+
visitedEdgeIds.add(neighbor.edge.id);
|
|
4044
|
+
if (!seen.has(neighbor.nodeId)) {
|
|
4045
|
+
frontier.push(neighbor.nodeId);
|
|
4046
|
+
}
|
|
4047
|
+
if (visitedNodeIds.length + frontier.length >= budget * 2) {
|
|
4048
|
+
break;
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
const nodes = nodeById(graph);
|
|
4053
|
+
const pageIds = uniqueBy(
|
|
4054
|
+
[
|
|
4055
|
+
...searchResults.map((result) => result.pageId),
|
|
4056
|
+
...visitedNodeIds.flatMap((nodeId) => {
|
|
4057
|
+
const node = nodes.get(nodeId);
|
|
4058
|
+
return node?.pageId ? [node.pageId] : [];
|
|
4059
|
+
})
|
|
4060
|
+
],
|
|
4061
|
+
(item) => item
|
|
4062
|
+
);
|
|
4063
|
+
const communities = uniqueBy(
|
|
4064
|
+
visitedNodeIds.map((nodeId) => nodes.get(nodeId)?.communityId).filter((communityId) => Boolean(communityId)),
|
|
4065
|
+
(item) => item
|
|
4066
|
+
);
|
|
4067
|
+
return {
|
|
4068
|
+
question,
|
|
4069
|
+
traversal,
|
|
4070
|
+
seedNodeIds: seeds,
|
|
4071
|
+
seedPageIds: uniqueBy(
|
|
4072
|
+
searchResults.map((result) => result.pageId),
|
|
4073
|
+
(item) => item
|
|
4074
|
+
),
|
|
4075
|
+
visitedNodeIds,
|
|
4076
|
+
visitedEdgeIds: [...visitedEdgeIds],
|
|
4077
|
+
pageIds,
|
|
4078
|
+
communities,
|
|
4079
|
+
matches,
|
|
4080
|
+
summary: [
|
|
4081
|
+
`Seeds: ${seeds.join(", ") || "none"}`,
|
|
4082
|
+
`Visited nodes: ${visitedNodeIds.length}`,
|
|
4083
|
+
`Visited edges: ${visitedEdgeIds.size}`,
|
|
4084
|
+
`Communities: ${communities.join(", ") || "none"}`,
|
|
4085
|
+
`Pages: ${pageIds.join(", ") || "none"}`
|
|
4086
|
+
].join("\n")
|
|
4087
|
+
};
|
|
4088
|
+
}
|
|
4089
|
+
function shortestGraphPath(graph, from, to) {
|
|
4090
|
+
const start = resolveNode(graph, from);
|
|
4091
|
+
const end = resolveNode(graph, to);
|
|
4092
|
+
if (!start || !end) {
|
|
4093
|
+
return {
|
|
4094
|
+
from,
|
|
4095
|
+
to,
|
|
4096
|
+
resolvedFromNodeId: start?.id,
|
|
4097
|
+
resolvedToNodeId: end?.id,
|
|
4098
|
+
found: false,
|
|
4099
|
+
nodeIds: [],
|
|
4100
|
+
edgeIds: [],
|
|
4101
|
+
pageIds: [],
|
|
4102
|
+
summary: "Could not resolve one or both graph targets."
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
const adjacency = graphAdjacency(graph);
|
|
4106
|
+
const queue = [start.id];
|
|
4107
|
+
const visited = /* @__PURE__ */ new Set([start.id]);
|
|
4108
|
+
const previous = /* @__PURE__ */ new Map();
|
|
4109
|
+
while (queue.length) {
|
|
4110
|
+
const current2 = queue.shift();
|
|
4111
|
+
if (current2 === end.id) {
|
|
4112
|
+
break;
|
|
4113
|
+
}
|
|
4114
|
+
for (const neighbor of adjacency.get(current2) ?? []) {
|
|
4115
|
+
if (visited.has(neighbor.nodeId)) {
|
|
4116
|
+
continue;
|
|
4117
|
+
}
|
|
4118
|
+
visited.add(neighbor.nodeId);
|
|
4119
|
+
previous.set(neighbor.nodeId, { nodeId: current2, edgeId: neighbor.edge.id });
|
|
4120
|
+
queue.push(neighbor.nodeId);
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
if (!visited.has(end.id)) {
|
|
4124
|
+
return {
|
|
4125
|
+
from,
|
|
4126
|
+
to,
|
|
4127
|
+
resolvedFromNodeId: start.id,
|
|
4128
|
+
resolvedToNodeId: end.id,
|
|
4129
|
+
found: false,
|
|
4130
|
+
nodeIds: [],
|
|
4131
|
+
edgeIds: [],
|
|
4132
|
+
pageIds: [],
|
|
4133
|
+
summary: `No path found between ${start.label} and ${end.label}.`
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
const nodeIds = [];
|
|
4137
|
+
const edgeIds = [];
|
|
4138
|
+
let current = end.id;
|
|
4139
|
+
while (current !== start.id) {
|
|
4140
|
+
nodeIds.push(current);
|
|
4141
|
+
const prev = previous.get(current);
|
|
4142
|
+
if (!prev) {
|
|
4143
|
+
break;
|
|
4144
|
+
}
|
|
4145
|
+
edgeIds.push(prev.edgeId);
|
|
4146
|
+
current = prev.nodeId;
|
|
4147
|
+
}
|
|
4148
|
+
nodeIds.push(start.id);
|
|
4149
|
+
nodeIds.reverse();
|
|
4150
|
+
edgeIds.reverse();
|
|
4151
|
+
const nodes = nodeById(graph);
|
|
4152
|
+
const pageIds = uniqueBy(
|
|
4153
|
+
nodeIds.flatMap((nodeId) => {
|
|
4154
|
+
const node = nodes.get(nodeId);
|
|
4155
|
+
return node?.pageId ? [node.pageId] : [];
|
|
4156
|
+
}),
|
|
4157
|
+
(item) => item
|
|
4158
|
+
);
|
|
4159
|
+
return {
|
|
4160
|
+
from,
|
|
4161
|
+
to,
|
|
4162
|
+
resolvedFromNodeId: start.id,
|
|
4163
|
+
resolvedToNodeId: end.id,
|
|
4164
|
+
found: true,
|
|
4165
|
+
nodeIds,
|
|
4166
|
+
edgeIds,
|
|
4167
|
+
pageIds,
|
|
4168
|
+
summary: nodeIds.map((nodeId) => nodes.get(nodeId)?.label ?? nodeId).join(" -> ")
|
|
4169
|
+
};
|
|
4170
|
+
}
|
|
4171
|
+
function explainGraphTarget(graph, target) {
|
|
4172
|
+
const node = resolveNode(graph, target);
|
|
4173
|
+
if (!node) {
|
|
4174
|
+
throw new Error(`Could not resolve graph target: ${target}`);
|
|
4175
|
+
}
|
|
4176
|
+
const pages = pageById(graph);
|
|
4177
|
+
const page = node.pageId ? pages.get(node.pageId) : void 0;
|
|
4178
|
+
const neighbors = [];
|
|
4179
|
+
const nodes = nodeById(graph);
|
|
4180
|
+
for (const neighbor of graphAdjacency(graph).get(node.id) ?? []) {
|
|
4181
|
+
const targetNode = nodes.get(neighbor.nodeId);
|
|
4182
|
+
if (!targetNode) {
|
|
4183
|
+
continue;
|
|
4184
|
+
}
|
|
4185
|
+
neighbors.push({
|
|
4186
|
+
nodeId: targetNode.id,
|
|
4187
|
+
label: targetNode.label,
|
|
4188
|
+
type: targetNode.type,
|
|
4189
|
+
pageId: targetNode.pageId,
|
|
4190
|
+
relation: neighbor.edge.relation,
|
|
4191
|
+
direction: neighbor.direction,
|
|
4192
|
+
confidence: neighbor.edge.confidence,
|
|
4193
|
+
evidenceClass: neighbor.edge.evidenceClass
|
|
4194
|
+
});
|
|
4195
|
+
}
|
|
4196
|
+
neighbors.sort((left, right) => right.confidence - left.confidence || left.label.localeCompare(right.label));
|
|
4197
|
+
return {
|
|
4198
|
+
target,
|
|
4199
|
+
node,
|
|
4200
|
+
page,
|
|
4201
|
+
community: communityLabel(graph, node.communityId),
|
|
4202
|
+
neighbors,
|
|
4203
|
+
summary: [
|
|
4204
|
+
`Node: ${node.label}`,
|
|
4205
|
+
`Type: ${node.type}`,
|
|
4206
|
+
`Community: ${node.communityId ?? "none"}`,
|
|
4207
|
+
`Neighbors: ${neighbors.length}`,
|
|
4208
|
+
`Page: ${page?.path ?? "none"}`
|
|
4209
|
+
].join("\n")
|
|
4210
|
+
};
|
|
4211
|
+
}
|
|
4212
|
+
function topGodNodes(graph, limit = 10) {
|
|
4213
|
+
return graph.nodes.filter((node) => node.isGodNode).sort((left, right) => (right.degree ?? 0) - (left.degree ?? 0)).slice(0, limit);
|
|
4214
|
+
}
|
|
4215
|
+
|
|
3698
4216
|
// src/markdown.ts
|
|
3699
4217
|
import matter3 from "gray-matter";
|
|
3700
4218
|
function uniqueStrings(values) {
|
|
@@ -3719,6 +4237,10 @@ function pagePathFor(kind, slug) {
|
|
|
3719
4237
|
return `entities/${slug}.md`;
|
|
3720
4238
|
case "output":
|
|
3721
4239
|
return `outputs/${slug}.md`;
|
|
4240
|
+
case "graph_report":
|
|
4241
|
+
return "graph/report.md";
|
|
4242
|
+
case "community_summary":
|
|
4243
|
+
return `graph/communities/${slug}.md`;
|
|
3722
4244
|
default:
|
|
3723
4245
|
return `${slug}.md`;
|
|
3724
4246
|
}
|
|
@@ -3729,6 +4251,10 @@ function candidatePagePathFor(kind, slug) {
|
|
|
3729
4251
|
function pageLink(page) {
|
|
3730
4252
|
return `[[${page.path.replace(/\.md$/, "")}|${page.title}]]`;
|
|
3731
4253
|
}
|
|
4254
|
+
function graphNodeLink(node, pagesById) {
|
|
4255
|
+
const page = node.pageId ? pagesById.get(node.pageId) : void 0;
|
|
4256
|
+
return page ? pageLink(page) : `\`${node.label}\``;
|
|
4257
|
+
}
|
|
3732
4258
|
function assetMarkdownPath(assetPath) {
|
|
3733
4259
|
return `./${assetPath.replace(/^outputs\//, "")}`;
|
|
3734
4260
|
}
|
|
@@ -4084,6 +4610,7 @@ function buildIndexPage(pages, schemaHash, metadata, projectPages = []) {
|
|
|
4084
4610
|
const candidates = pages.filter((page) => page.status === "candidate");
|
|
4085
4611
|
const outputs = pages.filter((page) => page.kind === "output");
|
|
4086
4612
|
const insights = pages.filter((page) => page.kind === "insight");
|
|
4613
|
+
const graphPages = pages.filter((page) => page.kind === "graph_report" || page.kind === "community_summary");
|
|
4087
4614
|
return [
|
|
4088
4615
|
"---",
|
|
4089
4616
|
"page_id: index",
|
|
@@ -4128,6 +4655,10 @@ function buildIndexPage(pages, schemaHash, metadata, projectPages = []) {
|
|
|
4128
4655
|
"",
|
|
4129
4656
|
...outputs.length ? outputs.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No saved outputs yet."],
|
|
4130
4657
|
"",
|
|
4658
|
+
"## Graph",
|
|
4659
|
+
"",
|
|
4660
|
+
...graphPages.length ? graphPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No graph reports yet."],
|
|
4661
|
+
"",
|
|
4131
4662
|
"## Projects",
|
|
4132
4663
|
"",
|
|
4133
4664
|
...projectPages.length ? projectPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No projects configured."],
|
|
@@ -4167,6 +4698,215 @@ function buildSectionIndex(kind, pages, schemaHash, metadata, projectIds = []) {
|
|
|
4167
4698
|
}
|
|
4168
4699
|
);
|
|
4169
4700
|
}
|
|
4701
|
+
function communityPagePath(communityId) {
|
|
4702
|
+
return pagePathFor("community_summary", communityId.replace(/^community:/, ""));
|
|
4703
|
+
}
|
|
4704
|
+
function nodeSummary(node) {
|
|
4705
|
+
const degree = typeof node.degree === "number" ? `degree=${node.degree}` : "";
|
|
4706
|
+
const bridge = typeof node.bridgeScore === "number" ? `bridge=${node.bridgeScore}` : "";
|
|
4707
|
+
return [node.type, degree, bridge].filter(Boolean).join(", ");
|
|
4708
|
+
}
|
|
4709
|
+
function crossCommunityEdges(graph) {
|
|
4710
|
+
const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
4711
|
+
return graph.edges.filter((edge) => {
|
|
4712
|
+
const source = nodesById.get(edge.source);
|
|
4713
|
+
const target = nodesById.get(edge.target);
|
|
4714
|
+
return source?.communityId && target?.communityId && source.communityId !== target.communityId;
|
|
4715
|
+
}).sort((left, right) => right.confidence - left.confidence || left.relation.localeCompare(right.relation));
|
|
4716
|
+
}
|
|
4717
|
+
function suggestedGraphQuestions(graph) {
|
|
4718
|
+
const thinCommunities = (graph.communities ?? []).filter((community) => community.nodeIds.length <= 2);
|
|
4719
|
+
const bridgeNodes = graph.nodes.filter((node) => (node.bridgeScore ?? 0) > 0).sort((left, right) => (right.bridgeScore ?? 0) - (left.bridgeScore ?? 0)).slice(0, 3);
|
|
4720
|
+
return uniqueStrings([
|
|
4721
|
+
...thinCommunities.map((community) => `What sources would strengthen community ${community.label}?`),
|
|
4722
|
+
...bridgeNodes.map((node) => `Why does ${node.label} connect multiple communities in the vault?`)
|
|
4723
|
+
]).slice(0, 6);
|
|
4724
|
+
}
|
|
4725
|
+
function buildGraphReportPage(input) {
|
|
4726
|
+
const pageId = "graph:report";
|
|
4727
|
+
const pathValue = pagePathFor("graph_report", "report");
|
|
4728
|
+
const pagesById = new Map(input.graph.pages.map((page) => [page.id, page]));
|
|
4729
|
+
const nodesById = new Map(input.graph.nodes.map((node) => [node.id, node]));
|
|
4730
|
+
const godNodes = input.graph.nodes.filter((node) => node.isGodNode).sort((left, right) => (right.degree ?? 0) - (left.degree ?? 0)).slice(0, 8);
|
|
4731
|
+
const bridgeNodes = input.graph.nodes.filter((node) => (node.bridgeScore ?? 0) > 0).sort((left, right) => (right.bridgeScore ?? 0) - (left.bridgeScore ?? 0)).slice(0, 8);
|
|
4732
|
+
const surprisingEdges = crossCommunityEdges(input.graph).slice(0, 8);
|
|
4733
|
+
const thinCommunities = (input.graph.communities ?? []).filter((community) => community.nodeIds.length <= 2);
|
|
4734
|
+
const relatedNodeIds = uniqueStrings([...godNodes, ...bridgeNodes].map((node) => node.id));
|
|
4735
|
+
const relatedPageIds = uniqueStrings([
|
|
4736
|
+
...godNodes.map((node) => node.pageId ?? ""),
|
|
4737
|
+
...bridgeNodes.map((node) => node.pageId ?? ""),
|
|
4738
|
+
...input.communityPages.map((page) => page.id)
|
|
4739
|
+
]);
|
|
4740
|
+
const relatedSourceIds = uniqueStrings(relatedNodeIds.flatMap((nodeId) => nodesById.get(nodeId)?.sourceIds ?? []));
|
|
4741
|
+
const frontmatter = {
|
|
4742
|
+
page_id: pageId,
|
|
4743
|
+
kind: "graph_report",
|
|
4744
|
+
title: "Graph Report",
|
|
4745
|
+
tags: ["graph", "report"],
|
|
4746
|
+
source_ids: relatedSourceIds,
|
|
4747
|
+
project_ids: [],
|
|
4748
|
+
node_ids: relatedNodeIds,
|
|
4749
|
+
freshness: "fresh",
|
|
4750
|
+
status: input.metadata.status,
|
|
4751
|
+
confidence: input.metadata.confidence,
|
|
4752
|
+
created_at: input.metadata.createdAt,
|
|
4753
|
+
updated_at: input.metadata.updatedAt,
|
|
4754
|
+
compiled_from: input.metadata.compiledFrom,
|
|
4755
|
+
managed_by: input.metadata.managedBy,
|
|
4756
|
+
backlinks: [],
|
|
4757
|
+
schema_hash: input.schemaHash,
|
|
4758
|
+
source_hashes: {},
|
|
4759
|
+
related_page_ids: relatedPageIds,
|
|
4760
|
+
related_node_ids: relatedNodeIds,
|
|
4761
|
+
related_source_ids: relatedSourceIds
|
|
4762
|
+
};
|
|
4763
|
+
const body = [
|
|
4764
|
+
"# Graph Report",
|
|
4765
|
+
"",
|
|
4766
|
+
"## Overview",
|
|
4767
|
+
"",
|
|
4768
|
+
`- Nodes: ${input.graph.nodes.length}`,
|
|
4769
|
+
`- Edges: ${input.graph.edges.length}`,
|
|
4770
|
+
`- Pages: ${input.graph.pages.length}`,
|
|
4771
|
+
`- Communities: ${input.graph.communities?.length ?? 0}`,
|
|
4772
|
+
"",
|
|
4773
|
+
"## God Nodes",
|
|
4774
|
+
"",
|
|
4775
|
+
...godNodes.length ? godNodes.map((node) => `- ${graphNodeLink(node, pagesById)} (${nodeSummary(node)})`) : ["- No high-connectivity nodes detected."],
|
|
4776
|
+
"",
|
|
4777
|
+
"## Bridge Nodes",
|
|
4778
|
+
"",
|
|
4779
|
+
...bridgeNodes.length ? bridgeNodes.map((node) => `- ${graphNodeLink(node, pagesById)} (${nodeSummary(node)})`) : ["- No cross-community bridge nodes detected."],
|
|
4780
|
+
"",
|
|
4781
|
+
"## Communities",
|
|
4782
|
+
"",
|
|
4783
|
+
...input.communityPages.length ? input.communityPages.map((page) => `- ${pageLink(page)}`) : ["- No community summaries generated yet."],
|
|
4784
|
+
"",
|
|
4785
|
+
"## Thin Communities",
|
|
4786
|
+
"",
|
|
4787
|
+
...thinCommunities.length ? thinCommunities.map((community) => `- ${community.label} (${community.nodeIds.length} node(s))`) : ["- No thin communities detected."],
|
|
4788
|
+
"",
|
|
4789
|
+
"## Cross-Community Surprises",
|
|
4790
|
+
"",
|
|
4791
|
+
...surprisingEdges.length ? surprisingEdges.map((edge) => {
|
|
4792
|
+
const source = nodesById.get(edge.source);
|
|
4793
|
+
const target = nodesById.get(edge.target);
|
|
4794
|
+
return `- ${source ? graphNodeLink(source, pagesById) : `\`${edge.source}\``} ${edge.relation} ${target ? graphNodeLink(target, pagesById) : `\`${edge.target}\``} (${edge.evidenceClass}, ${edge.confidence.toFixed(2)})`;
|
|
4795
|
+
}) : ["- No cross-community links detected."],
|
|
4796
|
+
"",
|
|
4797
|
+
"## Suggested Follow-Up Questions",
|
|
4798
|
+
"",
|
|
4799
|
+
...suggestedGraphQuestions(input.graph).map((question) => `- ${question}`),
|
|
4800
|
+
""
|
|
4801
|
+
].join("\n");
|
|
4802
|
+
return {
|
|
4803
|
+
page: {
|
|
4804
|
+
id: pageId,
|
|
4805
|
+
path: pathValue,
|
|
4806
|
+
title: "Graph Report",
|
|
4807
|
+
kind: "graph_report",
|
|
4808
|
+
sourceIds: relatedSourceIds,
|
|
4809
|
+
projectIds: [],
|
|
4810
|
+
nodeIds: relatedNodeIds,
|
|
4811
|
+
freshness: "fresh",
|
|
4812
|
+
status: input.metadata.status,
|
|
4813
|
+
confidence: input.metadata.confidence,
|
|
4814
|
+
backlinks: [],
|
|
4815
|
+
schemaHash: input.schemaHash,
|
|
4816
|
+
sourceHashes: {},
|
|
4817
|
+
relatedPageIds,
|
|
4818
|
+
relatedNodeIds,
|
|
4819
|
+
relatedSourceIds,
|
|
4820
|
+
createdAt: input.metadata.createdAt,
|
|
4821
|
+
updatedAt: input.metadata.updatedAt,
|
|
4822
|
+
compiledFrom: input.metadata.compiledFrom,
|
|
4823
|
+
managedBy: input.metadata.managedBy
|
|
4824
|
+
},
|
|
4825
|
+
content: matter3.stringify(body, frontmatter)
|
|
4826
|
+
};
|
|
4827
|
+
}
|
|
4828
|
+
function buildCommunitySummaryPage(input) {
|
|
4829
|
+
const pageId = `graph:${input.community.id}`;
|
|
4830
|
+
const pathValue = communityPagePath(input.community.id);
|
|
4831
|
+
const nodesById = new Map(input.graph.nodes.map((node) => [node.id, node]));
|
|
4832
|
+
const pagesById = new Map(input.graph.pages.map((page) => [page.id, page]));
|
|
4833
|
+
const communityNodes = input.community.nodeIds.map((nodeId) => nodesById.get(nodeId)).filter((node) => Boolean(node));
|
|
4834
|
+
const communityPageIds = uniqueStrings(communityNodes.map((node) => node.pageId ?? ""));
|
|
4835
|
+
const communityPages = communityPageIds.map((id) => pagesById.get(id)).filter((page) => Boolean(page));
|
|
4836
|
+
const externalEdges = input.graph.edges.filter((edge) => {
|
|
4837
|
+
const source = nodesById.get(edge.source);
|
|
4838
|
+
const target = nodesById.get(edge.target);
|
|
4839
|
+
return source?.communityId === input.community.id && target?.communityId && target.communityId !== input.community.id;
|
|
4840
|
+
}).slice(0, 8);
|
|
4841
|
+
const relatedSourceIds = uniqueStrings(communityNodes.flatMap((node) => node.sourceIds));
|
|
4842
|
+
const frontmatter = {
|
|
4843
|
+
page_id: pageId,
|
|
4844
|
+
kind: "community_summary",
|
|
4845
|
+
title: `Community: ${input.community.label}`,
|
|
4846
|
+
tags: ["graph", "community"],
|
|
4847
|
+
source_ids: relatedSourceIds,
|
|
4848
|
+
project_ids: [],
|
|
4849
|
+
node_ids: input.community.nodeIds,
|
|
4850
|
+
freshness: "fresh",
|
|
4851
|
+
status: input.metadata.status,
|
|
4852
|
+
confidence: input.metadata.confidence,
|
|
4853
|
+
created_at: input.metadata.createdAt,
|
|
4854
|
+
updated_at: input.metadata.updatedAt,
|
|
4855
|
+
compiled_from: input.metadata.compiledFrom,
|
|
4856
|
+
managed_by: input.metadata.managedBy,
|
|
4857
|
+
backlinks: ["graph:report"],
|
|
4858
|
+
schema_hash: input.schemaHash,
|
|
4859
|
+
source_hashes: {},
|
|
4860
|
+
related_page_ids: uniqueStrings(["graph:report", ...communityPageIds]),
|
|
4861
|
+
related_node_ids: input.community.nodeIds,
|
|
4862
|
+
related_source_ids: relatedSourceIds
|
|
4863
|
+
};
|
|
4864
|
+
const body = [
|
|
4865
|
+
`# Community: ${input.community.label}`,
|
|
4866
|
+
"",
|
|
4867
|
+
"## Nodes",
|
|
4868
|
+
"",
|
|
4869
|
+
...communityNodes.map((node) => `- ${graphNodeLink(node, pagesById)} (${nodeSummary(node)})`),
|
|
4870
|
+
"",
|
|
4871
|
+
"## Pages",
|
|
4872
|
+
"",
|
|
4873
|
+
...communityPages.length ? communityPages.map((page) => `- ${pageLink(page)}`) : ["- No canonical pages linked."],
|
|
4874
|
+
"",
|
|
4875
|
+
"## External Links",
|
|
4876
|
+
"",
|
|
4877
|
+
...externalEdges.length ? externalEdges.map((edge) => {
|
|
4878
|
+
const source = nodesById.get(edge.source);
|
|
4879
|
+
const target = nodesById.get(edge.target);
|
|
4880
|
+
return `- ${source ? graphNodeLink(source, pagesById) : `\`${edge.source}\``} ${edge.relation} ${target ? graphNodeLink(target, pagesById) : `\`${edge.target}\``} (${edge.evidenceClass})`;
|
|
4881
|
+
}) : ["- No external links detected."],
|
|
4882
|
+
""
|
|
4883
|
+
].join("\n");
|
|
4884
|
+
return {
|
|
4885
|
+
page: {
|
|
4886
|
+
id: pageId,
|
|
4887
|
+
path: pathValue,
|
|
4888
|
+
title: `Community: ${input.community.label}`,
|
|
4889
|
+
kind: "community_summary",
|
|
4890
|
+
sourceIds: relatedSourceIds,
|
|
4891
|
+
projectIds: [],
|
|
4892
|
+
nodeIds: input.community.nodeIds,
|
|
4893
|
+
freshness: "fresh",
|
|
4894
|
+
status: input.metadata.status,
|
|
4895
|
+
confidence: input.metadata.confidence,
|
|
4896
|
+
backlinks: ["graph:report"],
|
|
4897
|
+
schemaHash: input.schemaHash,
|
|
4898
|
+
sourceHashes: {},
|
|
4899
|
+
relatedPageIds: uniqueStrings(["graph:report", ...communityPageIds]),
|
|
4900
|
+
relatedNodeIds: input.community.nodeIds,
|
|
4901
|
+
relatedSourceIds,
|
|
4902
|
+
createdAt: input.metadata.createdAt,
|
|
4903
|
+
updatedAt: input.metadata.updatedAt,
|
|
4904
|
+
compiledFrom: input.metadata.compiledFrom,
|
|
4905
|
+
managedBy: input.metadata.managedBy
|
|
4906
|
+
},
|
|
4907
|
+
content: matter3.stringify(body, frontmatter)
|
|
4908
|
+
};
|
|
4909
|
+
}
|
|
4170
4910
|
function buildProjectsIndex(projectPages, schemaHash, metadata) {
|
|
4171
4911
|
return matter3.stringify(
|
|
4172
4912
|
[
|
|
@@ -5016,7 +5756,7 @@ function toFtsQuery(query) {
|
|
|
5016
5756
|
return tokens.join(" OR ");
|
|
5017
5757
|
}
|
|
5018
5758
|
function normalizeKind(value) {
|
|
5019
|
-
return value === "index" || value === "source" || value === "module" || value === "concept" || value === "entity" || value === "output" || value === "insight" ? value : void 0;
|
|
5759
|
+
return value === "index" || value === "source" || value === "module" || value === "concept" || value === "entity" || value === "output" || value === "insight" || value === "graph_report" || value === "community_summary" ? value : void 0;
|
|
5020
5760
|
}
|
|
5021
5761
|
function normalizeStatus(value) {
|
|
5022
5762
|
return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : void 0;
|
|
@@ -5690,7 +6430,8 @@ function candidateActivePath(page) {
|
|
|
5690
6430
|
return activeAggregatePath(page.kind, pageSlug(page));
|
|
5691
6431
|
}
|
|
5692
6432
|
function buildCommunityId(seed, index) {
|
|
5693
|
-
|
|
6433
|
+
const slug = slugify(seed) || "cluster";
|
|
6434
|
+
return `community:${slug}-${index + 1}`;
|
|
5694
6435
|
}
|
|
5695
6436
|
function pageHashes(pages) {
|
|
5696
6437
|
return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
|
|
@@ -5869,6 +6610,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
5869
6610
|
const entityMap = /* @__PURE__ */ new Map();
|
|
5870
6611
|
const moduleMap = /* @__PURE__ */ new Map();
|
|
5871
6612
|
const symbolMap = /* @__PURE__ */ new Map();
|
|
6613
|
+
const rationaleMap = /* @__PURE__ */ new Map();
|
|
5872
6614
|
const edges = [];
|
|
5873
6615
|
const edgesById = /* @__PURE__ */ new Set();
|
|
5874
6616
|
const pushEdge = (edge) => {
|
|
@@ -5900,6 +6642,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
5900
6642
|
target: concept.id,
|
|
5901
6643
|
relation: "mentions",
|
|
5902
6644
|
status: "extracted",
|
|
6645
|
+
evidenceClass: "extracted",
|
|
5903
6646
|
confidence: edgeConfidence(analysis.claims, concept.name),
|
|
5904
6647
|
provenance: [analysis.sourceId]
|
|
5905
6648
|
});
|
|
@@ -5923,6 +6666,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
5923
6666
|
target: entity.id,
|
|
5924
6667
|
relation: "mentions",
|
|
5925
6668
|
status: "extracted",
|
|
6669
|
+
evidenceClass: "extracted",
|
|
5926
6670
|
confidence: edgeConfidence(analysis.claims, entity.name),
|
|
5927
6671
|
provenance: [analysis.sourceId]
|
|
5928
6672
|
});
|
|
@@ -5951,6 +6695,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
5951
6695
|
target: moduleId,
|
|
5952
6696
|
relation: "contains_code",
|
|
5953
6697
|
status: "extracted",
|
|
6698
|
+
evidenceClass: "extracted",
|
|
5954
6699
|
confidence: 1,
|
|
5955
6700
|
provenance: [analysis.sourceId]
|
|
5956
6701
|
});
|
|
@@ -5974,6 +6719,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
5974
6719
|
target: symbol.id,
|
|
5975
6720
|
relation: "defines",
|
|
5976
6721
|
status: "extracted",
|
|
6722
|
+
evidenceClass: "extracted",
|
|
5977
6723
|
confidence: 1,
|
|
5978
6724
|
provenance: [analysis.sourceId]
|
|
5979
6725
|
});
|
|
@@ -5984,12 +6730,39 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
5984
6730
|
target: symbol.id,
|
|
5985
6731
|
relation: "exports",
|
|
5986
6732
|
status: "extracted",
|
|
6733
|
+
evidenceClass: "extracted",
|
|
5987
6734
|
confidence: 1,
|
|
5988
6735
|
provenance: [analysis.sourceId]
|
|
5989
6736
|
});
|
|
5990
6737
|
}
|
|
5991
6738
|
}
|
|
5992
6739
|
const symbolIdsByName = new Map(analysis.code.symbols.map((symbol) => [symbol.name, symbol.id]));
|
|
6740
|
+
for (const rationale of analysis.rationales) {
|
|
6741
|
+
const targetSymbolId = rationale.symbolName ? symbolIdsByName.get(rationale.symbolName) : void 0;
|
|
6742
|
+
const targetId = targetSymbolId ?? moduleId;
|
|
6743
|
+
rationaleMap.set(rationale.id, {
|
|
6744
|
+
id: rationale.id,
|
|
6745
|
+
type: "rationale",
|
|
6746
|
+
label: truncate(rationale.text, 80),
|
|
6747
|
+
pageId: moduleId,
|
|
6748
|
+
freshness: "fresh",
|
|
6749
|
+
confidence: 1,
|
|
6750
|
+
sourceIds: [analysis.sourceId],
|
|
6751
|
+
projectIds: scopedProjectIdsFromSources([analysis.sourceId], sourceProjects),
|
|
6752
|
+
language: analysis.code.language,
|
|
6753
|
+
moduleId
|
|
6754
|
+
});
|
|
6755
|
+
pushEdge({
|
|
6756
|
+
id: `${rationale.id}->${targetId}:rationale_for`,
|
|
6757
|
+
source: rationale.id,
|
|
6758
|
+
target: targetId,
|
|
6759
|
+
relation: "rationale_for",
|
|
6760
|
+
status: "extracted",
|
|
6761
|
+
evidenceClass: "extracted",
|
|
6762
|
+
confidence: 1,
|
|
6763
|
+
provenance: [analysis.sourceId]
|
|
6764
|
+
});
|
|
6765
|
+
}
|
|
5993
6766
|
const importedSymbolIdsByName = /* @__PURE__ */ new Map();
|
|
5994
6767
|
for (const codeImport of analysis.code.imports.filter((item) => !item.isExternal)) {
|
|
5995
6768
|
const targetSourceId = codeImport.resolvedSourceId;
|
|
@@ -6027,6 +6800,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
6027
6800
|
target: targetId,
|
|
6028
6801
|
relation: "calls",
|
|
6029
6802
|
status: "extracted",
|
|
6803
|
+
evidenceClass: "extracted",
|
|
6030
6804
|
confidence: 1,
|
|
6031
6805
|
provenance: [analysis.sourceId]
|
|
6032
6806
|
});
|
|
@@ -6042,6 +6816,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
6042
6816
|
target: targetId,
|
|
6043
6817
|
relation: "extends",
|
|
6044
6818
|
status: "extracted",
|
|
6819
|
+
evidenceClass: "extracted",
|
|
6045
6820
|
confidence: 1,
|
|
6046
6821
|
provenance: [analysis.sourceId]
|
|
6047
6822
|
});
|
|
@@ -6057,6 +6832,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
6057
6832
|
target: targetId,
|
|
6058
6833
|
relation: "implements",
|
|
6059
6834
|
status: "extracted",
|
|
6835
|
+
evidenceClass: "extracted",
|
|
6060
6836
|
confidence: 1,
|
|
6061
6837
|
provenance: [analysis.sourceId]
|
|
6062
6838
|
});
|
|
@@ -6074,6 +6850,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
6074
6850
|
target: targetModuleId,
|
|
6075
6851
|
relation: codeImport.reExport ? "exports" : "imports",
|
|
6076
6852
|
status: "extracted",
|
|
6853
|
+
evidenceClass: "extracted",
|
|
6077
6854
|
confidence: 1,
|
|
6078
6855
|
provenance: [analysis.sourceId, targetSourceId]
|
|
6079
6856
|
});
|
|
@@ -6113,13 +6890,21 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
6113
6890
|
target: `source:${negativeClaim.sourceId}`,
|
|
6114
6891
|
relation: "conflicted_with",
|
|
6115
6892
|
status: "conflicted",
|
|
6893
|
+
evidenceClass: "ambiguous",
|
|
6116
6894
|
confidence: conflictConfidence(positiveClaim.claim, negativeClaim.claim),
|
|
6117
6895
|
provenance: [positiveClaim.sourceId, negativeClaim.sourceId]
|
|
6118
6896
|
});
|
|
6119
6897
|
}
|
|
6120
6898
|
}
|
|
6121
6899
|
}
|
|
6122
|
-
const graphNodes = [
|
|
6900
|
+
const graphNodes = [
|
|
6901
|
+
...sourceNodes,
|
|
6902
|
+
...moduleMap.values(),
|
|
6903
|
+
...symbolMap.values(),
|
|
6904
|
+
...rationaleMap.values(),
|
|
6905
|
+
...conceptMap.values(),
|
|
6906
|
+
...entityMap.values()
|
|
6907
|
+
];
|
|
6123
6908
|
const metrics = deriveGraphMetrics(graphNodes, edges);
|
|
6124
6909
|
return {
|
|
6125
6910
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6130,6 +6915,46 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
6130
6915
|
pages
|
|
6131
6916
|
};
|
|
6132
6917
|
}
|
|
6918
|
+
async function buildGraphOrientationPages(graph, paths, schemaHash) {
|
|
6919
|
+
const communityRecords = [];
|
|
6920
|
+
for (const community of graph.communities ?? []) {
|
|
6921
|
+
const absolutePath = path14.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
|
|
6922
|
+
communityRecords.push(
|
|
6923
|
+
await buildManagedGraphPage(
|
|
6924
|
+
absolutePath,
|
|
6925
|
+
{
|
|
6926
|
+
managedBy: "system",
|
|
6927
|
+
compiledFrom: uniqueStrings2(
|
|
6928
|
+
community.nodeIds.flatMap((nodeId) => graph.nodes.find((node) => node.id === nodeId)?.sourceIds ?? [])
|
|
6929
|
+
),
|
|
6930
|
+
confidence: 1
|
|
6931
|
+
},
|
|
6932
|
+
(metadata) => buildCommunitySummaryPage({
|
|
6933
|
+
graph,
|
|
6934
|
+
community,
|
|
6935
|
+
schemaHash,
|
|
6936
|
+
metadata
|
|
6937
|
+
})
|
|
6938
|
+
)
|
|
6939
|
+
);
|
|
6940
|
+
}
|
|
6941
|
+
const reportAbsolutePath = path14.join(paths.wikiDir, "graph", "report.md");
|
|
6942
|
+
const reportRecord = await buildManagedGraphPage(
|
|
6943
|
+
reportAbsolutePath,
|
|
6944
|
+
{
|
|
6945
|
+
managedBy: "system",
|
|
6946
|
+
compiledFrom: uniqueStrings2(graph.pages.flatMap((page) => page.sourceIds)),
|
|
6947
|
+
confidence: 1
|
|
6948
|
+
},
|
|
6949
|
+
(metadata) => buildGraphReportPage({
|
|
6950
|
+
graph,
|
|
6951
|
+
schemaHash,
|
|
6952
|
+
metadata,
|
|
6953
|
+
communityPages: communityRecords.map((record) => record.page)
|
|
6954
|
+
})
|
|
6955
|
+
);
|
|
6956
|
+
return [reportRecord, ...communityRecords];
|
|
6957
|
+
}
|
|
6133
6958
|
async function writePage(wikiDir, relativePath, content, changedPages) {
|
|
6134
6959
|
const absolutePath = path14.resolve(wikiDir, relativePath);
|
|
6135
6960
|
const changed = await writeFileIfChanged(absolutePath, content);
|
|
@@ -6494,8 +7319,15 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
6494
7319
|
}
|
|
6495
7320
|
}
|
|
6496
7321
|
const compiledPages = records.map((record) => record.page);
|
|
6497
|
-
const
|
|
6498
|
-
const
|
|
7322
|
+
const basePages = [...compiledPages, ...input.outputPages, ...input.insightPages];
|
|
7323
|
+
const baseGraph = buildGraph(input.manifests, input.analyses, basePages, input.sourceProjects, input.codeIndex);
|
|
7324
|
+
const graphOrientationRecords = await buildGraphOrientationPages(baseGraph, paths, globalSchemaHash);
|
|
7325
|
+
records.push(...graphOrientationRecords);
|
|
7326
|
+
const allPages = [...basePages, ...graphOrientationRecords.map((record) => record.page)];
|
|
7327
|
+
const graph = {
|
|
7328
|
+
...baseGraph,
|
|
7329
|
+
pages: allPages
|
|
7330
|
+
};
|
|
6499
7331
|
const activeConceptPages = allPages.filter((page) => page.kind === "concept" && page.status !== "candidate");
|
|
6500
7332
|
const activeEntityPages = allPages.filter((page) => page.kind === "entity" && page.status !== "candidate");
|
|
6501
7333
|
const modulePages = allPages.filter((page) => page.kind === "module");
|
|
@@ -6595,7 +7427,8 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
6595
7427
|
["concepts/index.md", "concepts", activeConceptPages],
|
|
6596
7428
|
["entities/index.md", "entities", activeEntityPages],
|
|
6597
7429
|
["outputs/index.md", "outputs", allPages.filter((page) => page.kind === "output")],
|
|
6598
|
-
["candidates/index.md", "candidates", candidatePages]
|
|
7430
|
+
["candidates/index.md", "candidates", candidatePages],
|
|
7431
|
+
["graph/index.md", "graph", allPages.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
|
|
6599
7432
|
]) {
|
|
6600
7433
|
records.push({
|
|
6601
7434
|
page: emptyGraphPage({
|
|
@@ -6690,6 +7523,23 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
6690
7523
|
const { config, paths } = await loadVaultConfig(rootDir);
|
|
6691
7524
|
const schemas = await loadVaultSchemas(rootDir);
|
|
6692
7525
|
const globalSchemaHash = schemas.effective.global.hash;
|
|
7526
|
+
const currentGraph = await readJsonFile(paths.graphPath);
|
|
7527
|
+
const basePages = pages.filter((page) => page.kind !== "graph_report" && page.kind !== "community_summary");
|
|
7528
|
+
const graphOrientationRecords = currentGraph ? await buildGraphOrientationPages(
|
|
7529
|
+
{
|
|
7530
|
+
...currentGraph,
|
|
7531
|
+
pages: basePages
|
|
7532
|
+
},
|
|
7533
|
+
paths,
|
|
7534
|
+
globalSchemaHash
|
|
7535
|
+
) : [];
|
|
7536
|
+
const pagesWithGraph = sortGraphPages([...basePages, ...graphOrientationRecords.map((record) => record.page)]);
|
|
7537
|
+
if (currentGraph) {
|
|
7538
|
+
await writeJsonFile(paths.graphPath, {
|
|
7539
|
+
...currentGraph,
|
|
7540
|
+
pages: pagesWithGraph
|
|
7541
|
+
});
|
|
7542
|
+
}
|
|
6693
7543
|
const configuredProjects = projectEntries(config);
|
|
6694
7544
|
const projectIndexRefs = configuredProjects.map(
|
|
6695
7545
|
(project) => emptyGraphPage({
|
|
@@ -6711,6 +7561,8 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
6711
7561
|
ensureDir(path14.join(paths.wikiDir, "concepts")),
|
|
6712
7562
|
ensureDir(path14.join(paths.wikiDir, "entities")),
|
|
6713
7563
|
ensureDir(path14.join(paths.wikiDir, "outputs")),
|
|
7564
|
+
ensureDir(path14.join(paths.wikiDir, "graph")),
|
|
7565
|
+
ensureDir(path14.join(paths.wikiDir, "graph", "communities")),
|
|
6714
7566
|
ensureDir(path14.join(paths.wikiDir, "projects")),
|
|
6715
7567
|
ensureDir(path14.join(paths.wikiDir, "candidates"))
|
|
6716
7568
|
]);
|
|
@@ -6760,18 +7612,19 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
6760
7612
|
rootIndexPath,
|
|
6761
7613
|
{
|
|
6762
7614
|
managedBy: "system",
|
|
6763
|
-
compiledFrom: indexCompiledFrom(
|
|
7615
|
+
compiledFrom: indexCompiledFrom(pagesWithGraph)
|
|
6764
7616
|
},
|
|
6765
|
-
(metadata) => buildIndexPage(
|
|
7617
|
+
(metadata) => buildIndexPage(pagesWithGraph, globalSchemaHash, metadata, projectIndexRefs)
|
|
6766
7618
|
)
|
|
6767
7619
|
);
|
|
6768
7620
|
for (const [relativePath, kind, sectionPages] of [
|
|
6769
|
-
["sources/index.md", "sources",
|
|
6770
|
-
["code/index.md", "code",
|
|
6771
|
-
["concepts/index.md", "concepts",
|
|
6772
|
-
["entities/index.md", "entities",
|
|
6773
|
-
["outputs/index.md", "outputs",
|
|
6774
|
-
["candidates/index.md", "candidates",
|
|
7621
|
+
["sources/index.md", "sources", pagesWithGraph.filter((page) => page.kind === "source")],
|
|
7622
|
+
["code/index.md", "code", pagesWithGraph.filter((page) => page.kind === "module")],
|
|
7623
|
+
["concepts/index.md", "concepts", pagesWithGraph.filter((page) => page.kind === "concept" && page.status !== "candidate")],
|
|
7624
|
+
["entities/index.md", "entities", pagesWithGraph.filter((page) => page.kind === "entity" && page.status !== "candidate")],
|
|
7625
|
+
["outputs/index.md", "outputs", pagesWithGraph.filter((page) => page.kind === "output")],
|
|
7626
|
+
["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
|
|
7627
|
+
["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
|
|
6775
7628
|
]) {
|
|
6776
7629
|
const absolutePath = path14.join(paths.wikiDir, relativePath);
|
|
6777
7630
|
await writeFileIfChanged(
|
|
@@ -6786,6 +7639,9 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
6786
7639
|
)
|
|
6787
7640
|
);
|
|
6788
7641
|
}
|
|
7642
|
+
for (const record of graphOrientationRecords) {
|
|
7643
|
+
await writeFileIfChanged(path14.join(paths.wikiDir, record.page.path), record.content);
|
|
7644
|
+
}
|
|
6789
7645
|
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path14.relative(paths.wikiDir, absolutePath)));
|
|
6790
7646
|
const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
|
|
6791
7647
|
"projects/index.md",
|
|
@@ -6794,7 +7650,12 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
6794
7650
|
await Promise.all(
|
|
6795
7651
|
existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs11.rm(path14.join(paths.wikiDir, relativePath), { force: true }))
|
|
6796
7652
|
);
|
|
6797
|
-
await
|
|
7653
|
+
const existingGraphPages = (await listFilesRecursive(path14.join(paths.wikiDir, "graph").replace(/\/$/, "")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path14.relative(paths.wikiDir, absolutePath)));
|
|
7654
|
+
const allowedGraphPages = /* @__PURE__ */ new Set(["graph/index.md", ...graphOrientationRecords.map((record) => record.page.path)]);
|
|
7655
|
+
await Promise.all(
|
|
7656
|
+
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs11.rm(path14.join(paths.wikiDir, relativePath), { force: true }))
|
|
7657
|
+
);
|
|
7658
|
+
await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
|
|
6798
7659
|
}
|
|
6799
7660
|
async function prepareOutputPageSave(rootDir, input) {
|
|
6800
7661
|
const { paths } = await loadVaultConfig(rootDir);
|
|
@@ -8104,6 +8965,35 @@ async function searchVault(rootDir, query, limit = 5) {
|
|
|
8104
8965
|
}
|
|
8105
8966
|
return searchPages(paths.searchDbPath, query, limit);
|
|
8106
8967
|
}
|
|
8968
|
+
async function ensureCompiledGraph(rootDir) {
|
|
8969
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
8970
|
+
if (!await fileExists(paths.searchDbPath) || !await fileExists(paths.graphPath)) {
|
|
8971
|
+
await compileVault(rootDir, {});
|
|
8972
|
+
}
|
|
8973
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
8974
|
+
if (!graph) {
|
|
8975
|
+
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
8976
|
+
}
|
|
8977
|
+
return graph;
|
|
8978
|
+
}
|
|
8979
|
+
async function queryGraphVault(rootDir, question, options = {}) {
|
|
8980
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
8981
|
+
const graph = await ensureCompiledGraph(rootDir);
|
|
8982
|
+
const searchResults = searchPages(paths.searchDbPath, question, { limit: Math.max(5, options.budget ?? 10) });
|
|
8983
|
+
return queryGraph(graph, question, searchResults, options);
|
|
8984
|
+
}
|
|
8985
|
+
async function pathGraphVault(rootDir, from, to) {
|
|
8986
|
+
const graph = await ensureCompiledGraph(rootDir);
|
|
8987
|
+
return shortestGraphPath(graph, from, to);
|
|
8988
|
+
}
|
|
8989
|
+
async function explainGraphVault(rootDir, target) {
|
|
8990
|
+
const graph = await ensureCompiledGraph(rootDir);
|
|
8991
|
+
return explainGraphTarget(graph, target);
|
|
8992
|
+
}
|
|
8993
|
+
async function listGodNodes(rootDir, limit = 10) {
|
|
8994
|
+
const graph = await ensureCompiledGraph(rootDir);
|
|
8995
|
+
return topGodNodes(graph, limit);
|
|
8996
|
+
}
|
|
8107
8997
|
async function listPages(rootDir) {
|
|
8108
8998
|
const { paths } = await loadVaultConfig(rootDir);
|
|
8109
8999
|
const graph = await readJsonFile(paths.graphPath);
|
|
@@ -8263,7 +9153,7 @@ async function bootstrapDemo(rootDir, input) {
|
|
|
8263
9153
|
}
|
|
8264
9154
|
|
|
8265
9155
|
// src/mcp.ts
|
|
8266
|
-
var SERVER_VERSION = "0.1.
|
|
9156
|
+
var SERVER_VERSION = "0.1.19";
|
|
8267
9157
|
async function createMcpServer(rootDir) {
|
|
8268
9158
|
const server = new McpServer({
|
|
8269
9159
|
name: "swarmvault",
|
|
@@ -8323,6 +9213,74 @@ async function createMcpServer(rootDir) {
|
|
|
8323
9213
|
return asToolText(limit ? manifests.slice(0, limit) : manifests);
|
|
8324
9214
|
}
|
|
8325
9215
|
);
|
|
9216
|
+
server.registerTool(
|
|
9217
|
+
"query_graph",
|
|
9218
|
+
{
|
|
9219
|
+
description: "Traverse the local graph from search seeds without calling a model provider.",
|
|
9220
|
+
inputSchema: {
|
|
9221
|
+
question: z7.string().min(1).describe("Question or graph search seed"),
|
|
9222
|
+
traversal: z7.enum(["bfs", "dfs"]).optional().describe("Traversal strategy"),
|
|
9223
|
+
budget: z7.number().int().min(3).max(50).optional().describe("Maximum nodes to summarize")
|
|
9224
|
+
}
|
|
9225
|
+
},
|
|
9226
|
+
async ({ question, traversal, budget }) => {
|
|
9227
|
+
const result = await queryGraphVault(rootDir, question, {
|
|
9228
|
+
traversal,
|
|
9229
|
+
budget
|
|
9230
|
+
});
|
|
9231
|
+
return asToolText(result);
|
|
9232
|
+
}
|
|
9233
|
+
);
|
|
9234
|
+
server.registerTool(
|
|
9235
|
+
"get_node",
|
|
9236
|
+
{
|
|
9237
|
+
description: "Explain a graph node, its page, community, and neighbors.",
|
|
9238
|
+
inputSchema: {
|
|
9239
|
+
target: z7.string().min(1).describe("Node or page label/id")
|
|
9240
|
+
}
|
|
9241
|
+
},
|
|
9242
|
+
async ({ target }) => {
|
|
9243
|
+
return asToolText(await explainGraphVault(rootDir, target));
|
|
9244
|
+
}
|
|
9245
|
+
);
|
|
9246
|
+
server.registerTool(
|
|
9247
|
+
"get_neighbors",
|
|
9248
|
+
{
|
|
9249
|
+
description: "Return the neighbors of a graph node or page target.",
|
|
9250
|
+
inputSchema: {
|
|
9251
|
+
target: z7.string().min(1).describe("Node or page label/id")
|
|
9252
|
+
}
|
|
9253
|
+
},
|
|
9254
|
+
async ({ target }) => {
|
|
9255
|
+
const explanation = await explainGraphVault(rootDir, target);
|
|
9256
|
+
return asToolText(explanation.neighbors);
|
|
9257
|
+
}
|
|
9258
|
+
);
|
|
9259
|
+
server.registerTool(
|
|
9260
|
+
"shortest_path",
|
|
9261
|
+
{
|
|
9262
|
+
description: "Find the shortest graph path between two targets.",
|
|
9263
|
+
inputSchema: {
|
|
9264
|
+
from: z7.string().min(1).describe("Start node/page label or id"),
|
|
9265
|
+
to: z7.string().min(1).describe("End node/page label or id")
|
|
9266
|
+
}
|
|
9267
|
+
},
|
|
9268
|
+
async ({ from, to }) => {
|
|
9269
|
+
return asToolText(await pathGraphVault(rootDir, from, to));
|
|
9270
|
+
}
|
|
9271
|
+
);
|
|
9272
|
+
server.registerTool(
|
|
9273
|
+
"god_nodes",
|
|
9274
|
+
{
|
|
9275
|
+
description: "List the highest-connectivity graph nodes.",
|
|
9276
|
+
inputSchema: {
|
|
9277
|
+
limit: z7.number().int().min(1).max(25).optional().describe("Maximum nodes to return")
|
|
9278
|
+
}
|
|
9279
|
+
},
|
|
9280
|
+
async ({ limit }) => {
|
|
9281
|
+
return asToolText(await listGodNodes(rootDir, limit ?? 10));
|
|
9282
|
+
}
|
|
9283
|
+
);
|
|
8326
9284
|
server.registerTool(
|
|
8327
9285
|
"query_vault",
|
|
8328
9286
|
{
|
|
@@ -8900,6 +9858,34 @@ async function startGraphServer(rootDir, port) {
|
|
|
8900
9858
|
response.end(await fs14.readFile(paths.graphPath, "utf8"));
|
|
8901
9859
|
return;
|
|
8902
9860
|
}
|
|
9861
|
+
if (url.pathname === "/api/graph/query") {
|
|
9862
|
+
const question = url.searchParams.get("q") ?? "";
|
|
9863
|
+
const traversal = url.searchParams.get("traversal");
|
|
9864
|
+
const budget = Number.parseInt(url.searchParams.get("budget") ?? "12", 10);
|
|
9865
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
9866
|
+
response.end(
|
|
9867
|
+
JSON.stringify(
|
|
9868
|
+
await queryGraphVault(rootDir, question, {
|
|
9869
|
+
traversal: traversal === "dfs" ? "dfs" : "bfs",
|
|
9870
|
+
budget: Number.isFinite(budget) ? budget : 12
|
|
9871
|
+
})
|
|
9872
|
+
)
|
|
9873
|
+
);
|
|
9874
|
+
return;
|
|
9875
|
+
}
|
|
9876
|
+
if (url.pathname === "/api/graph/path") {
|
|
9877
|
+
const from = url.searchParams.get("from") ?? "";
|
|
9878
|
+
const to = url.searchParams.get("to") ?? "";
|
|
9879
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
9880
|
+
response.end(JSON.stringify(await pathGraphVault(rootDir, from, to)));
|
|
9881
|
+
return;
|
|
9882
|
+
}
|
|
9883
|
+
if (url.pathname === "/api/graph/explain") {
|
|
9884
|
+
const target2 = url.searchParams.get("target") ?? "";
|
|
9885
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
9886
|
+
response.end(JSON.stringify(await explainGraphVault(rootDir, target2)));
|
|
9887
|
+
return;
|
|
9888
|
+
}
|
|
8903
9889
|
if (url.pathname === "/api/search") {
|
|
8904
9890
|
if (!await fileExists(paths.searchDbPath)) {
|
|
8905
9891
|
response.writeHead(404, { "content-type": "application/json" });
|
|
@@ -9248,6 +10234,7 @@ export {
|
|
|
9248
10234
|
createWebSearchAdapter,
|
|
9249
10235
|
defaultVaultConfig,
|
|
9250
10236
|
defaultVaultSchema,
|
|
10237
|
+
explainGraphVault,
|
|
9251
10238
|
exploreVault,
|
|
9252
10239
|
exportGraphHtml,
|
|
9253
10240
|
getProviderForTask,
|
|
@@ -9263,13 +10250,16 @@ export {
|
|
|
9263
10250
|
lintVault,
|
|
9264
10251
|
listApprovals,
|
|
9265
10252
|
listCandidates,
|
|
10253
|
+
listGodNodes,
|
|
9266
10254
|
listManifests,
|
|
9267
10255
|
listPages,
|
|
9268
10256
|
listSchedules,
|
|
9269
10257
|
loadVaultConfig,
|
|
9270
10258
|
loadVaultSchema,
|
|
9271
10259
|
loadVaultSchemas,
|
|
10260
|
+
pathGraphVault,
|
|
9272
10261
|
promoteCandidate,
|
|
10262
|
+
queryGraphVault,
|
|
9273
10263
|
queryVault,
|
|
9274
10264
|
readApproval,
|
|
9275
10265
|
readExtractedText,
|