@swarmvaultai/engine 0.1.29 → 0.1.31
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 -1
- package/dist/chunk-IAEYFTUS.js +1159 -0
- package/dist/index.d.ts +75 -2
- package/dist/index.js +1209 -699
- package/dist/registry-G7NSRYCO.js +12 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
uniqueBy,
|
|
22
22
|
writeFileIfChanged,
|
|
23
23
|
writeJsonFile
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-IAEYFTUS.js";
|
|
25
25
|
|
|
26
26
|
// src/agents.ts
|
|
27
27
|
import crypto from "crypto";
|
|
@@ -748,16 +748,14 @@ async function installConfiguredAgents(rootDir) {
|
|
|
748
748
|
// src/graph-export.ts
|
|
749
749
|
import fs2 from "fs/promises";
|
|
750
750
|
import path2 from "path";
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
function xmlEscape(value) {
|
|
760
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
751
|
+
|
|
752
|
+
// src/graph-interchange.ts
|
|
753
|
+
function exportHyperedgeNodeId(hyperedge) {
|
|
754
|
+
return `hyperedge:${hyperedge.id}`;
|
|
755
|
+
}
|
|
756
|
+
function relationType(relation) {
|
|
757
|
+
const normalized = relation.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
758
|
+
return normalized || "RELATED_TO";
|
|
761
759
|
}
|
|
762
760
|
function cypherStringLiteral(value) {
|
|
763
761
|
let escaped = "";
|
|
@@ -792,18 +790,109 @@ function cypherStringLiteral(value) {
|
|
|
792
790
|
}
|
|
793
791
|
return `'${escaped}'`;
|
|
794
792
|
}
|
|
795
|
-
function relationType(relation) {
|
|
796
|
-
const normalized = relation.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
797
|
-
return normalized || "RELATED_TO";
|
|
798
|
-
}
|
|
799
793
|
function graphPageById(graph) {
|
|
800
794
|
return new Map(graph.pages.map((page) => [page.id, page]));
|
|
801
795
|
}
|
|
802
796
|
function graphNodeById(graph) {
|
|
803
797
|
return new Map(graph.nodes.map((node) => [node.id, node]));
|
|
804
798
|
}
|
|
805
|
-
function
|
|
806
|
-
return
|
|
799
|
+
function normalizeSwarmNodeProps(node, page) {
|
|
800
|
+
return {
|
|
801
|
+
id: node.id,
|
|
802
|
+
label: node.label,
|
|
803
|
+
type: node.type,
|
|
804
|
+
sourceIds: JSON.stringify(node.sourceIds),
|
|
805
|
+
projectIds: JSON.stringify(node.projectIds),
|
|
806
|
+
...node.pageId ? { pageId: node.pageId } : {},
|
|
807
|
+
...page?.path ? { pagePath: page.path } : {},
|
|
808
|
+
...node.sourceClass ? { sourceClass: node.sourceClass } : {},
|
|
809
|
+
...node.language ? { language: node.language } : {},
|
|
810
|
+
...node.moduleId ? { moduleId: node.moduleId } : {},
|
|
811
|
+
...node.symbolKind ? { symbolKind: node.symbolKind } : {},
|
|
812
|
+
...node.communityId ? { communityId: node.communityId } : {},
|
|
813
|
+
...node.freshness ? { freshness: node.freshness } : {},
|
|
814
|
+
...node.confidence !== void 0 ? { confidence: node.confidence } : {},
|
|
815
|
+
...node.degree !== void 0 ? { degree: node.degree } : {},
|
|
816
|
+
...node.bridgeScore !== void 0 ? { bridgeScore: node.bridgeScore } : {},
|
|
817
|
+
...node.isGodNode !== void 0 ? { isGodNode: node.isGodNode } : {}
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function normalizeHyperedgeNodeProps(hyperedge) {
|
|
821
|
+
return {
|
|
822
|
+
id: exportHyperedgeNodeId(hyperedge),
|
|
823
|
+
label: hyperedge.label,
|
|
824
|
+
type: "hyperedge",
|
|
825
|
+
relation: hyperedge.relation,
|
|
826
|
+
evidenceClass: hyperedge.evidenceClass,
|
|
827
|
+
confidence: hyperedge.confidence,
|
|
828
|
+
sourcePageIds: JSON.stringify(hyperedge.sourcePageIds),
|
|
829
|
+
why: hyperedge.why
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function normalizeEdgeProps(edge) {
|
|
833
|
+
return {
|
|
834
|
+
id: edge.id,
|
|
835
|
+
relation: edge.relation,
|
|
836
|
+
status: edge.status,
|
|
837
|
+
evidenceClass: edge.evidenceClass,
|
|
838
|
+
confidence: edge.confidence,
|
|
839
|
+
provenance: JSON.stringify(edge.provenance),
|
|
840
|
+
...edge.similarityReasons?.length ? { similarityReasons: JSON.stringify(edge.similarityReasons) } : {},
|
|
841
|
+
...edge.similarityBasis ? { similarityBasis: edge.similarityBasis } : {}
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
function normalizeGroupMemberProps(hyperedge, nodeId) {
|
|
845
|
+
return {
|
|
846
|
+
id: `member:${hyperedge.id}:${nodeId}`,
|
|
847
|
+
relation: "group_member",
|
|
848
|
+
status: "inferred",
|
|
849
|
+
evidenceClass: hyperedge.evidenceClass,
|
|
850
|
+
confidence: hyperedge.confidence,
|
|
851
|
+
provenance: JSON.stringify(hyperedge.sourcePageIds)
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
function filterGraphBySourceClasses(graph, includeClasses) {
|
|
855
|
+
const allowed = new Set(includeClasses);
|
|
856
|
+
const nodeIds = new Set(graph.nodes.filter((node) => node.sourceClass && allowed.has(node.sourceClass)).map((node) => node.id));
|
|
857
|
+
const pageIds = new Set(graph.pages.filter((page) => page.sourceClass && allowed.has(page.sourceClass)).map((page) => page.id));
|
|
858
|
+
return {
|
|
859
|
+
...graph,
|
|
860
|
+
nodes: graph.nodes.filter((node) => nodeIds.has(node.id)),
|
|
861
|
+
edges: graph.edges.filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target)),
|
|
862
|
+
hyperedges: graph.hyperedges.map((hyperedge) => ({
|
|
863
|
+
...hyperedge,
|
|
864
|
+
nodeIds: hyperedge.nodeIds.filter((nodeId) => nodeIds.has(nodeId))
|
|
865
|
+
})).filter((hyperedge) => hyperedge.nodeIds.length >= 2),
|
|
866
|
+
communities: (graph.communities ?? []).map((community) => ({
|
|
867
|
+
...community,
|
|
868
|
+
nodeIds: community.nodeIds.filter((nodeId) => nodeIds.has(nodeId))
|
|
869
|
+
})).filter((community) => community.nodeIds.length > 0),
|
|
870
|
+
sources: graph.sources.filter((source) => source.sourceClass && allowed.has(source.sourceClass)),
|
|
871
|
+
pages: graph.pages.filter((page) => pageIds.has(page.id))
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function graphCounts(graph) {
|
|
875
|
+
return {
|
|
876
|
+
sources: graph.sources.length,
|
|
877
|
+
pages: graph.pages.length,
|
|
878
|
+
nodes: graph.nodes.length,
|
|
879
|
+
relationships: graph.edges.length,
|
|
880
|
+
hyperedges: graph.hyperedges.length,
|
|
881
|
+
groupMembers: graph.hyperedges.reduce((total, hyperedge) => total + hyperedge.nodeIds.length, 0)
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/graph-export.ts
|
|
886
|
+
var NODE_COLORS = {
|
|
887
|
+
source: "#f59e0b",
|
|
888
|
+
module: "#fb7185",
|
|
889
|
+
symbol: "#8b5cf6",
|
|
890
|
+
rationale: "#14b8a6",
|
|
891
|
+
concept: "#0ea5e9",
|
|
892
|
+
entity: "#22c55e"
|
|
893
|
+
};
|
|
894
|
+
function xmlEscape(value) {
|
|
895
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
807
896
|
}
|
|
808
897
|
function sortedCommunities(graph) {
|
|
809
898
|
const known = (graph.communities ?? []).map((community) => ({
|
|
@@ -1065,35 +1154,14 @@ function renderCypher(graph) {
|
|
|
1065
1154
|
const lines = ["// Neo4j Cypher import generated by SwarmVault", ""];
|
|
1066
1155
|
for (const node of [...graph.nodes].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
1067
1156
|
const page = node.pageId ? pageById2.get(node.pageId) : void 0;
|
|
1068
|
-
const props = [
|
|
1069
|
-
`id: ${cypherStringLiteral(node.id)}`,
|
|
1070
|
-
`label: ${cypherStringLiteral(node.label)}`,
|
|
1071
|
-
`type: ${cypherStringLiteral(node.type)}`,
|
|
1072
|
-
`sourceIds: ${cypherStringLiteral(JSON.stringify(node.sourceIds))}`,
|
|
1073
|
-
`projectIds: ${cypherStringLiteral(JSON.stringify(node.projectIds))}`,
|
|
1074
|
-
node.pageId ? `pageId: ${cypherStringLiteral(node.pageId)}` : "",
|
|
1075
|
-
page?.path ? `pagePath: ${cypherStringLiteral(page.path)}` : "",
|
|
1076
|
-
node.language ? `language: ${cypherStringLiteral(node.language)}` : "",
|
|
1077
|
-
node.symbolKind ? `symbolKind: ${cypherStringLiteral(node.symbolKind)}` : "",
|
|
1078
|
-
node.communityId ? `communityId: ${cypherStringLiteral(node.communityId)}` : "",
|
|
1079
|
-
node.degree !== void 0 ? `degree: ${node.degree}` : "",
|
|
1080
|
-
node.bridgeScore !== void 0 ? `bridgeScore: ${node.bridgeScore}` : "",
|
|
1081
|
-
node.isGodNode !== void 0 ? `isGodNode: ${node.isGodNode}` : ""
|
|
1082
|
-
].filter(Boolean).join(", ");
|
|
1157
|
+
const props = Object.entries(normalizeSwarmNodeProps(node, page)).map(([key, value]) => `${key}: ${typeof value === "string" ? cypherStringLiteral(value) : value}`).filter(Boolean).join(", ");
|
|
1083
1158
|
lines.push(`MERGE (n:SwarmNode {id: ${cypherStringLiteral(node.id)}}) SET n += { ${props} };`);
|
|
1084
1159
|
}
|
|
1085
1160
|
lines.push("");
|
|
1086
1161
|
for (const hyperedge of [...graph.hyperedges ?? []].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
1087
1162
|
const hyperedgeNodeId = exportHyperedgeNodeId(hyperedge);
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
hyperedge.label
|
|
1091
|
-
)}, type: ${cypherStringLiteral("hyperedge")}, relation: ${cypherStringLiteral(hyperedge.relation)}, evidenceClass: ${cypherStringLiteral(
|
|
1092
|
-
hyperedge.evidenceClass
|
|
1093
|
-
)}, confidence: ${hyperedge.confidence}, sourcePageIds: ${cypherStringLiteral(JSON.stringify(hyperedge.sourcePageIds))}, why: ${cypherStringLiteral(
|
|
1094
|
-
hyperedge.why
|
|
1095
|
-
)} };`
|
|
1096
|
-
);
|
|
1163
|
+
const props = Object.entries(normalizeHyperedgeNodeProps(hyperedge)).map(([key, value]) => `${key}: ${typeof value === "string" ? cypherStringLiteral(value) : value}`).join(", ");
|
|
1164
|
+
lines.push(`MERGE (h:SwarmNode {id: ${cypherStringLiteral(hyperedgeNodeId)}}) SET h += { ${props} };`);
|
|
1097
1165
|
}
|
|
1098
1166
|
if ((graph.hyperedges ?? []).length) {
|
|
1099
1167
|
lines.push("");
|
|
@@ -1101,23 +1169,21 @@ function renderCypher(graph) {
|
|
|
1101
1169
|
for (const hyperedge of [...graph.hyperedges ?? []].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
1102
1170
|
const hyperedgeNodeId = exportHyperedgeNodeId(hyperedge);
|
|
1103
1171
|
for (const nodeId of hyperedge.nodeIds) {
|
|
1172
|
+
const props = Object.entries(normalizeGroupMemberProps(hyperedge, nodeId)).map(([key, value]) => `${key}: ${typeof value === "string" ? cypherStringLiteral(value) : value}`).join(", ");
|
|
1104
1173
|
lines.push(
|
|
1105
1174
|
`MATCH (h:SwarmNode {id: ${cypherStringLiteral(hyperedgeNodeId)}}), (n:SwarmNode {id: ${cypherStringLiteral(nodeId)}})`,
|
|
1106
1175
|
`MERGE (h)-[r:GROUP_MEMBER {id: ${cypherStringLiteral(`member:${hyperedge.id}:${nodeId}`)}}]->(n)`,
|
|
1107
|
-
`SET r += {
|
|
1108
|
-
hyperedge.evidenceClass
|
|
1109
|
-
)}, confidence: ${hyperedge.confidence}, provenance: ${cypherStringLiteral(JSON.stringify(hyperedge.sourcePageIds))} };`
|
|
1176
|
+
`SET r += { ${props} };`
|
|
1110
1177
|
);
|
|
1111
1178
|
}
|
|
1112
1179
|
}
|
|
1113
1180
|
lines.push("");
|
|
1114
1181
|
for (const edge of [...graph.edges].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
1182
|
+
const props = Object.entries(normalizeEdgeProps(edge)).map(([key, value]) => `${key}: ${typeof value === "string" ? cypherStringLiteral(value) : value}`).join(", ");
|
|
1115
1183
|
lines.push(
|
|
1116
1184
|
`MATCH (a:SwarmNode {id: ${cypherStringLiteral(edge.source)}}), (b:SwarmNode {id: ${cypherStringLiteral(edge.target)}})`,
|
|
1117
1185
|
`MERGE (a)-[r:${relationType(edge.relation)} {id: ${cypherStringLiteral(edge.id)}}]->(b)`,
|
|
1118
|
-
`SET r += {
|
|
1119
|
-
edge.evidenceClass
|
|
1120
|
-
)}, confidence: ${edge.confidence}, provenance: ${cypherStringLiteral(JSON.stringify(edge.provenance))}${edge.similarityReasons?.length ? `, similarityReasons: ${cypherStringLiteral(JSON.stringify(edge.similarityReasons))}` : ""} };`
|
|
1186
|
+
`SET r += { ${props} };`
|
|
1121
1187
|
);
|
|
1122
1188
|
}
|
|
1123
1189
|
lines.push("");
|
|
@@ -1143,27 +1209,400 @@ async function exportGraphFormat(rootDir, format, outputPath) {
|
|
|
1143
1209
|
return { format, outputPath: resolvedPath };
|
|
1144
1210
|
}
|
|
1145
1211
|
|
|
1146
|
-
// src/
|
|
1212
|
+
// src/graph-push.ts
|
|
1147
1213
|
import fs3 from "fs/promises";
|
|
1148
1214
|
import path3 from "path";
|
|
1215
|
+
import neo4j from "neo4j-driver";
|
|
1216
|
+
|
|
1217
|
+
// src/benchmark.ts
|
|
1218
|
+
var CHARS_PER_TOKEN = 4;
|
|
1219
|
+
var DEFAULT_BENCHMARK_QUESTIONS = [
|
|
1220
|
+
"How does this vault connect the main concepts?",
|
|
1221
|
+
"Which pages bridge the biggest communities?",
|
|
1222
|
+
"What are the core abstractions in this vault?",
|
|
1223
|
+
"Where are the biggest knowledge gaps?",
|
|
1224
|
+
"What evidence should I read first?"
|
|
1225
|
+
];
|
|
1226
|
+
var RESEARCH_BENCHMARK_QUESTION = "Which research sources should I read first, and why?";
|
|
1227
|
+
function nodeMap(graph) {
|
|
1228
|
+
return new Map(graph.nodes.map((node) => [node.id, node]));
|
|
1229
|
+
}
|
|
1230
|
+
function pageMap(graph) {
|
|
1231
|
+
return new Map(graph.pages.map((page) => [page.id, page]));
|
|
1232
|
+
}
|
|
1233
|
+
function estimateTokens(text) {
|
|
1234
|
+
return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));
|
|
1235
|
+
}
|
|
1236
|
+
function estimateCorpusWords(texts) {
|
|
1237
|
+
return texts.reduce((total, text) => total + normalizeWhitespace(text).split(/\s+/).filter(Boolean).length, 0);
|
|
1238
|
+
}
|
|
1239
|
+
function benchmarkQueryTokens(graph, queryResult, pageContentsById) {
|
|
1240
|
+
const nodesById = nodeMap(graph);
|
|
1241
|
+
const pagesById = pageMap(graph);
|
|
1242
|
+
const edgeIds = new Set(queryResult.visitedEdgeIds);
|
|
1243
|
+
const lines = [];
|
|
1244
|
+
for (const pageId of queryResult.pageIds) {
|
|
1245
|
+
const page = pagesById.get(pageId);
|
|
1246
|
+
if (!page) {
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
const content = normalizeWhitespace(pageContentsById.get(pageId) ?? "").slice(0, 280);
|
|
1250
|
+
lines.push(`PAGE ${page.title} path=${page.path} kind=${page.kind}`);
|
|
1251
|
+
if (content) {
|
|
1252
|
+
lines.push(`PAGE_BODY ${content}`);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
for (const nodeId of queryResult.visitedNodeIds) {
|
|
1256
|
+
const node = nodesById.get(nodeId);
|
|
1257
|
+
if (!node) {
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
lines.push(`NODE ${node.label} type=${node.type} community=${node.communityId ?? "unassigned"} page=${node.pageId ?? "none"}`);
|
|
1261
|
+
}
|
|
1262
|
+
for (const edge of graph.edges) {
|
|
1263
|
+
if (!edgeIds.has(edge.id)) {
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
const source = nodesById.get(edge.source)?.label ?? edge.source;
|
|
1267
|
+
const target = nodesById.get(edge.target)?.label ?? edge.target;
|
|
1268
|
+
lines.push(`EDGE ${source} --${edge.relation}/${edge.evidenceClass}/${edge.confidence.toFixed(2)}--> ${target}`);
|
|
1269
|
+
}
|
|
1270
|
+
const queryTokens = estimateTokens(lines.join("\n"));
|
|
1271
|
+
return {
|
|
1272
|
+
question: queryResult.question,
|
|
1273
|
+
queryTokens,
|
|
1274
|
+
reduction: 0,
|
|
1275
|
+
visitedNodeIds: queryResult.visitedNodeIds,
|
|
1276
|
+
visitedEdgeIds: queryResult.visitedEdgeIds,
|
|
1277
|
+
pageIds: queryResult.pageIds
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
function graphHash(graph) {
|
|
1281
|
+
const hashedPages = graph.pages.filter((page) => page.kind !== "graph_report" && page.kind !== "community_summary");
|
|
1282
|
+
const normalized = JSON.stringify(
|
|
1283
|
+
{
|
|
1284
|
+
nodes: [...graph.nodes].map((node) => ({
|
|
1285
|
+
id: node.id,
|
|
1286
|
+
type: node.type,
|
|
1287
|
+
label: node.label,
|
|
1288
|
+
pageId: node.pageId ?? null,
|
|
1289
|
+
sourceClass: node.sourceClass ?? null,
|
|
1290
|
+
communityId: node.communityId ?? null,
|
|
1291
|
+
degree: node.degree ?? null,
|
|
1292
|
+
bridgeScore: node.bridgeScore ?? null,
|
|
1293
|
+
isGodNode: node.isGodNode ?? false,
|
|
1294
|
+
sourceIds: [...node.sourceIds].sort(),
|
|
1295
|
+
projectIds: [...node.projectIds].sort()
|
|
1296
|
+
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
1297
|
+
edges: [...graph.edges].map((edge) => ({
|
|
1298
|
+
id: edge.id,
|
|
1299
|
+
source: edge.source,
|
|
1300
|
+
target: edge.target,
|
|
1301
|
+
relation: edge.relation,
|
|
1302
|
+
status: edge.status,
|
|
1303
|
+
evidenceClass: edge.evidenceClass,
|
|
1304
|
+
similarityBasis: edge.similarityBasis ?? null,
|
|
1305
|
+
confidence: edge.confidence,
|
|
1306
|
+
provenance: [...edge.provenance].sort()
|
|
1307
|
+
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
1308
|
+
pages: [...hashedPages].map((page) => ({
|
|
1309
|
+
id: page.id,
|
|
1310
|
+
path: page.path,
|
|
1311
|
+
kind: page.kind,
|
|
1312
|
+
status: page.status,
|
|
1313
|
+
sourceType: page.sourceType ?? null,
|
|
1314
|
+
sourceClass: page.sourceClass ?? null,
|
|
1315
|
+
sourceIds: [...page.sourceIds].sort(),
|
|
1316
|
+
projectIds: [...page.projectIds].sort(),
|
|
1317
|
+
nodeIds: [...page.nodeIds].sort()
|
|
1318
|
+
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
1319
|
+
communities: [...graph.communities ?? []].map((community) => ({
|
|
1320
|
+
id: community.id,
|
|
1321
|
+
label: community.label,
|
|
1322
|
+
nodeIds: [...community.nodeIds].sort()
|
|
1323
|
+
})).sort((left, right) => left.id.localeCompare(right.id))
|
|
1324
|
+
},
|
|
1325
|
+
null,
|
|
1326
|
+
0
|
|
1327
|
+
);
|
|
1328
|
+
return sha256(normalized);
|
|
1329
|
+
}
|
|
1330
|
+
function hasResearchSources(pages) {
|
|
1331
|
+
return pages.some((page) => page.kind === "source" && Boolean(page.sourceType) && page.sourceType !== "url");
|
|
1332
|
+
}
|
|
1333
|
+
function defaultBenchmarkQuestionsForGraph(graph, maxQuestions = 3) {
|
|
1334
|
+
const normalizedLimit = Math.max(1, Math.min(maxQuestions, DEFAULT_BENCHMARK_QUESTIONS.length));
|
|
1335
|
+
const questions = [...DEFAULT_BENCHMARK_QUESTIONS];
|
|
1336
|
+
if (hasResearchSources(graph.pages)) {
|
|
1337
|
+
questions.unshift(RESEARCH_BENCHMARK_QUESTION);
|
|
1338
|
+
}
|
|
1339
|
+
return uniqueBy(questions, (item) => item).slice(0, normalizedLimit);
|
|
1340
|
+
}
|
|
1341
|
+
function buildBenchmarkArtifact(input) {
|
|
1342
|
+
const corpusTokens = Math.max(1, Math.round(input.corpusWords * (100 / 75)));
|
|
1343
|
+
const perQuestion = input.perQuestion.filter((entry) => entry.queryTokens > 0).map((entry) => ({
|
|
1344
|
+
...entry,
|
|
1345
|
+
reduction: Number(Math.max(0, 1 - entry.queryTokens / Math.max(1, corpusTokens)).toFixed(3))
|
|
1346
|
+
}));
|
|
1347
|
+
const avgQueryTokens = perQuestion.length ? Math.max(1, Math.round(perQuestion.reduce((total, entry) => total + entry.queryTokens, 0) / perQuestion.length)) : 0;
|
|
1348
|
+
const reductionRatio = avgQueryTokens ? Number(Math.max(0, 1 - avgQueryTokens / Math.max(1, corpusTokens)).toFixed(3)) : 0;
|
|
1349
|
+
const uniqueVisitedNodes = new Set(perQuestion.flatMap((entry) => entry.visitedNodeIds)).size;
|
|
1350
|
+
const summary = {
|
|
1351
|
+
questionCount: input.questions.length,
|
|
1352
|
+
uniqueVisitedNodes,
|
|
1353
|
+
finalContextTokens: avgQueryTokens,
|
|
1354
|
+
naiveCorpusTokens: corpusTokens,
|
|
1355
|
+
avgReduction: reductionRatio,
|
|
1356
|
+
reductionRatio
|
|
1357
|
+
};
|
|
1358
|
+
return {
|
|
1359
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1360
|
+
graphHash: graphHash(input.graph),
|
|
1361
|
+
corpusWords: input.corpusWords,
|
|
1362
|
+
corpusTokens,
|
|
1363
|
+
nodes: input.graph.nodes.length,
|
|
1364
|
+
edges: input.graph.edges.length,
|
|
1365
|
+
avgQueryTokens,
|
|
1366
|
+
reductionRatio,
|
|
1367
|
+
sampleQuestions: input.questions,
|
|
1368
|
+
perQuestion,
|
|
1369
|
+
questionResults: perQuestion,
|
|
1370
|
+
summary
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// src/graph-push.ts
|
|
1375
|
+
var DEFAULT_NEO4J_BATCH_SIZE = 500;
|
|
1376
|
+
var DEFAULT_NEO4J_DATABASE = "neo4j";
|
|
1377
|
+
function requireConfigValue(value, name) {
|
|
1378
|
+
if (typeof value === "string" && value.trim()) {
|
|
1379
|
+
return value.trim();
|
|
1380
|
+
}
|
|
1381
|
+
throw new Error(`Neo4j push requires ${name}. Configure \`graphSinks.neo4j.${name}\` or pass the matching CLI flag.`);
|
|
1382
|
+
}
|
|
1383
|
+
async function deriveVaultId(rootDir) {
|
|
1384
|
+
const realRoot = await fs3.realpath(rootDir).catch(() => path3.resolve(rootDir));
|
|
1385
|
+
const label = slugify(path3.basename(realRoot));
|
|
1386
|
+
return `${label}-${sha256(realRoot).slice(0, 12)}`;
|
|
1387
|
+
}
|
|
1388
|
+
async function resolveNeo4jPushConfig(rootDir, options) {
|
|
1389
|
+
const { config } = await loadVaultConfig(rootDir);
|
|
1390
|
+
const sink = config.graphSinks?.neo4j;
|
|
1391
|
+
const includeClasses = normalizeIncludedClasses(options.includeClasses ?? sink?.includeClasses ?? ["first_party"]);
|
|
1392
|
+
return {
|
|
1393
|
+
uri: requireConfigValue(options.uri ?? sink?.uri, "uri"),
|
|
1394
|
+
username: requireConfigValue(options.username ?? sink?.username, "username"),
|
|
1395
|
+
passwordEnv: requireConfigValue(options.passwordEnv ?? sink?.passwordEnv, "passwordEnv"),
|
|
1396
|
+
database: options.database?.trim() || sink?.database?.trim() || DEFAULT_NEO4J_DATABASE,
|
|
1397
|
+
vaultId: options.vaultId?.trim() || sink?.vaultId?.trim() || await deriveVaultId(rootDir),
|
|
1398
|
+
includeClasses,
|
|
1399
|
+
batchSize: normalizeBatchSize(options.batchSize ?? sink?.batchSize)
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
function normalizeIncludedClasses(values) {
|
|
1403
|
+
const allowed = ["first_party", "third_party", "resource", "generated"];
|
|
1404
|
+
const unique = [...new Set(values)].filter((value) => allowed.includes(value));
|
|
1405
|
+
return unique.length ? unique : ["first_party"];
|
|
1406
|
+
}
|
|
1407
|
+
function normalizeBatchSize(value) {
|
|
1408
|
+
if (!Number.isFinite(value) || !value || value <= 0) {
|
|
1409
|
+
return DEFAULT_NEO4J_BATCH_SIZE;
|
|
1410
|
+
}
|
|
1411
|
+
return Math.max(1, Math.floor(value));
|
|
1412
|
+
}
|
|
1413
|
+
async function loadGraph2(rootDir) {
|
|
1414
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
1415
|
+
const raw = JSON.parse(await fs3.readFile(paths.graphPath, "utf8"));
|
|
1416
|
+
return raw;
|
|
1417
|
+
}
|
|
1418
|
+
function buildResult(input) {
|
|
1419
|
+
const counts = graphCounts(input.filteredGraph);
|
|
1420
|
+
const fullCounts = graphCounts(input.fullGraph);
|
|
1421
|
+
return {
|
|
1422
|
+
sink: "neo4j",
|
|
1423
|
+
uri: input.resolved.uri,
|
|
1424
|
+
database: input.resolved.database,
|
|
1425
|
+
vaultId: input.resolved.vaultId,
|
|
1426
|
+
dryRun: input.dryRun,
|
|
1427
|
+
graphHash: graphHash(input.fullGraph),
|
|
1428
|
+
includedSourceClasses: input.resolved.includeClasses,
|
|
1429
|
+
counts,
|
|
1430
|
+
skipped: {
|
|
1431
|
+
sources: Math.max(0, fullCounts.sources - counts.sources),
|
|
1432
|
+
pages: Math.max(0, fullCounts.pages - counts.pages),
|
|
1433
|
+
nodes: Math.max(0, fullCounts.nodes - counts.nodes),
|
|
1434
|
+
relationships: Math.max(0, fullCounts.relationships - counts.relationships),
|
|
1435
|
+
hyperedges: Math.max(0, fullCounts.hyperedges - counts.hyperedges),
|
|
1436
|
+
groupMembers: Math.max(0, fullCounts.groupMembers - counts.groupMembers)
|
|
1437
|
+
},
|
|
1438
|
+
warnings: input.warnings ?? []
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
function createDriver(uri, username, password) {
|
|
1442
|
+
return neo4j.driver(uri, neo4j.auth.basic(username, password));
|
|
1443
|
+
}
|
|
1444
|
+
async function ensureNeo4jConstraints(session) {
|
|
1445
|
+
await session.run("CREATE CONSTRAINT swarmvault_node_identity IF NOT EXISTS FOR (n:SwarmNode) REQUIRE (n.vaultId, n.id) IS UNIQUE");
|
|
1446
|
+
await session.run("CREATE CONSTRAINT swarmvault_sync_identity IF NOT EXISTS FOR (s:SwarmVaultSync) REQUIRE s.vaultId IS UNIQUE");
|
|
1447
|
+
}
|
|
1448
|
+
function chunkRows(rows, batchSize) {
|
|
1449
|
+
const chunks = [];
|
|
1450
|
+
for (let index = 0; index < rows.length; index += batchSize) {
|
|
1451
|
+
chunks.push(rows.slice(index, index + batchSize));
|
|
1452
|
+
}
|
|
1453
|
+
return chunks;
|
|
1454
|
+
}
|
|
1455
|
+
async function writeNodeRows(session, vaultId, rows, batchSize) {
|
|
1456
|
+
for (const chunk of chunkRows(rows, batchSize)) {
|
|
1457
|
+
await session.executeWrite(
|
|
1458
|
+
(tx) => tx.run(["UNWIND $rows AS row", "MERGE (n:SwarmNode { vaultId: $vaultId, id: row.id })", "SET n += row.props"].join("\n"), {
|
|
1459
|
+
vaultId,
|
|
1460
|
+
rows: chunk
|
|
1461
|
+
})
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
async function writeEdgeRows(session, vaultId, rows, batchSize, relation) {
|
|
1466
|
+
const neoRelation = relationType(relation);
|
|
1467
|
+
const query = [
|
|
1468
|
+
"UNWIND $rows AS row",
|
|
1469
|
+
"MATCH (a:SwarmNode { vaultId: $vaultId, id: row.source })",
|
|
1470
|
+
"MATCH (b:SwarmNode { vaultId: $vaultId, id: row.target })",
|
|
1471
|
+
`MERGE (a)-[r:${neoRelation} { vaultId: $vaultId, id: row.id }]->(b)`,
|
|
1472
|
+
"SET r += row.props"
|
|
1473
|
+
].join("\n");
|
|
1474
|
+
for (const chunk of chunkRows(rows, batchSize)) {
|
|
1475
|
+
await session.executeWrite((tx) => tx.run(query, { vaultId, rows: chunk }));
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
async function writeSyncNode(session, input) {
|
|
1479
|
+
await session.executeWrite(
|
|
1480
|
+
(tx) => tx.run(
|
|
1481
|
+
[
|
|
1482
|
+
"MERGE (s:SwarmVaultSync { vaultId: $vaultId })",
|
|
1483
|
+
"SET s += {",
|
|
1484
|
+
" vaultId: $vaultId,",
|
|
1485
|
+
" rootDir: $rootDir,",
|
|
1486
|
+
" graphGeneratedAt: $graphGeneratedAt,",
|
|
1487
|
+
" graphHash: $graphHash,",
|
|
1488
|
+
" pushedAt: $pushedAt,",
|
|
1489
|
+
" includedSourceClasses: $includedSourceClasses,",
|
|
1490
|
+
" sources: $sources,",
|
|
1491
|
+
" pages: $pages,",
|
|
1492
|
+
" nodes: $nodes,",
|
|
1493
|
+
" relationships: $relationships,",
|
|
1494
|
+
" hyperedges: $hyperedges,",
|
|
1495
|
+
" groupMembers: $groupMembers",
|
|
1496
|
+
"}"
|
|
1497
|
+
].join("\n"),
|
|
1498
|
+
{
|
|
1499
|
+
vaultId: input.vaultId,
|
|
1500
|
+
rootDir: path3.resolve(input.rootDir),
|
|
1501
|
+
graphGeneratedAt: input.graph.generatedAt,
|
|
1502
|
+
graphHash: graphHash(input.graph),
|
|
1503
|
+
pushedAt: input.pushedAt,
|
|
1504
|
+
includedSourceClasses: input.includedSourceClasses,
|
|
1505
|
+
...input.counts
|
|
1506
|
+
}
|
|
1507
|
+
)
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
async function pushGraphNeo4j(rootDir, options = {}) {
|
|
1511
|
+
const graph = await loadGraph2(rootDir);
|
|
1512
|
+
const resolved = await resolveNeo4jPushConfig(rootDir, options);
|
|
1513
|
+
const filteredGraph = filterGraphBySourceClasses(graph, resolved.includeClasses);
|
|
1514
|
+
const warnings = filteredGraph.nodes.length || filteredGraph.hyperedges.length || filteredGraph.edges.length ? [] : [`No graph records matched the included source classes: ${resolved.includeClasses.join(", ")}`];
|
|
1515
|
+
const result = buildResult({
|
|
1516
|
+
resolved,
|
|
1517
|
+
filteredGraph,
|
|
1518
|
+
fullGraph: graph,
|
|
1519
|
+
dryRun: options.dryRun ?? false,
|
|
1520
|
+
warnings
|
|
1521
|
+
});
|
|
1522
|
+
if (options.dryRun) {
|
|
1523
|
+
return result;
|
|
1524
|
+
}
|
|
1525
|
+
const password = process.env[resolved.passwordEnv];
|
|
1526
|
+
if (!password) {
|
|
1527
|
+
throw new Error(`Environment variable ${resolved.passwordEnv} is required for Neo4j push.`);
|
|
1528
|
+
}
|
|
1529
|
+
const driver = (options.driverFactory ?? createDriver)(resolved.uri, resolved.username, password);
|
|
1530
|
+
const session = driver.session({ database: resolved.database });
|
|
1531
|
+
try {
|
|
1532
|
+
await ensureNeo4jConstraints(session);
|
|
1533
|
+
const pageById2 = graphPageById(filteredGraph);
|
|
1534
|
+
const nodeRows = [
|
|
1535
|
+
...filteredGraph.nodes.sort((left, right) => left.id.localeCompare(right.id)).map((node) => ({
|
|
1536
|
+
id: node.id,
|
|
1537
|
+
props: normalizeSwarmNodeProps(node, node.pageId ? pageById2.get(node.pageId) : void 0)
|
|
1538
|
+
})),
|
|
1539
|
+
...filteredGraph.hyperedges.sort((left, right) => left.id.localeCompare(right.id)).map((hyperedge) => ({
|
|
1540
|
+
id: normalizeHyperedgeNodeProps(hyperedge).id,
|
|
1541
|
+
props: normalizeHyperedgeNodeProps(hyperedge)
|
|
1542
|
+
}))
|
|
1543
|
+
];
|
|
1544
|
+
await writeNodeRows(session, resolved.vaultId, nodeRows, resolved.batchSize);
|
|
1545
|
+
const edgeGroups = /* @__PURE__ */ new Map();
|
|
1546
|
+
for (const edge of [...filteredGraph.edges].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
1547
|
+
const rows = edgeGroups.get(edge.relation) ?? [];
|
|
1548
|
+
rows.push({
|
|
1549
|
+
id: edge.id,
|
|
1550
|
+
source: edge.source,
|
|
1551
|
+
target: edge.target,
|
|
1552
|
+
props: normalizeEdgeProps(edge)
|
|
1553
|
+
});
|
|
1554
|
+
edgeGroups.set(edge.relation, rows);
|
|
1555
|
+
}
|
|
1556
|
+
for (const [relation, rows] of [...edgeGroups.entries()].sort((left, right) => left[0].localeCompare(right[0]))) {
|
|
1557
|
+
await writeEdgeRows(session, resolved.vaultId, rows, resolved.batchSize, relation);
|
|
1558
|
+
}
|
|
1559
|
+
const memberRows = filteredGraph.hyperedges.flatMap(
|
|
1560
|
+
(hyperedge) => hyperedge.nodeIds.map((nodeId) => ({
|
|
1561
|
+
id: `member:${hyperedge.id}:${nodeId}`,
|
|
1562
|
+
source: normalizeHyperedgeNodeProps(hyperedge).id,
|
|
1563
|
+
target: nodeId,
|
|
1564
|
+
props: normalizeGroupMemberProps(hyperedge, nodeId)
|
|
1565
|
+
}))
|
|
1566
|
+
);
|
|
1567
|
+
if (memberRows.length) {
|
|
1568
|
+
await writeEdgeRows(session, resolved.vaultId, memberRows, resolved.batchSize, "group_member");
|
|
1569
|
+
}
|
|
1570
|
+
await writeSyncNode(session, {
|
|
1571
|
+
vaultId: resolved.vaultId,
|
|
1572
|
+
rootDir,
|
|
1573
|
+
graph,
|
|
1574
|
+
pushedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1575
|
+
includedSourceClasses: resolved.includeClasses,
|
|
1576
|
+
counts: result.counts
|
|
1577
|
+
});
|
|
1578
|
+
return result;
|
|
1579
|
+
} finally {
|
|
1580
|
+
await session.close();
|
|
1581
|
+
await driver.close();
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// src/hooks.ts
|
|
1586
|
+
import fs4 from "fs/promises";
|
|
1587
|
+
import path4 from "path";
|
|
1149
1588
|
import process2 from "process";
|
|
1150
1589
|
var hookStart = "# >>> swarmvault hook >>>";
|
|
1151
1590
|
var hookEnd = "# <<< swarmvault hook <<<";
|
|
1152
1591
|
async function findNearestGitRoot(startPath) {
|
|
1153
|
-
let current =
|
|
1592
|
+
let current = path4.resolve(startPath);
|
|
1154
1593
|
try {
|
|
1155
|
-
const stat = await
|
|
1594
|
+
const stat = await fs4.stat(current);
|
|
1156
1595
|
if (!stat.isDirectory()) {
|
|
1157
|
-
current =
|
|
1596
|
+
current = path4.dirname(current);
|
|
1158
1597
|
}
|
|
1159
1598
|
} catch {
|
|
1160
|
-
current =
|
|
1599
|
+
current = path4.dirname(current);
|
|
1161
1600
|
}
|
|
1162
1601
|
while (true) {
|
|
1163
|
-
if (await fileExists(
|
|
1602
|
+
if (await fileExists(path4.join(current, ".git"))) {
|
|
1164
1603
|
return current;
|
|
1165
1604
|
}
|
|
1166
|
-
const parent =
|
|
1605
|
+
const parent = path4.dirname(current);
|
|
1167
1606
|
if (parent === current) {
|
|
1168
1607
|
return null;
|
|
1169
1608
|
}
|
|
@@ -1175,8 +1614,8 @@ function shellQuote(value) {
|
|
|
1175
1614
|
}
|
|
1176
1615
|
function resolveSwarmvaultExecutableCandidate() {
|
|
1177
1616
|
const argvPath = process2.argv[1];
|
|
1178
|
-
if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${
|
|
1179
|
-
return
|
|
1617
|
+
if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path4.sep}@swarmvaultai${path4.sep}cli${path4.sep}`) || argvPath.includes(`${path4.sep}packages${path4.sep}cli${path4.sep}`))) {
|
|
1618
|
+
return path4.resolve(argvPath);
|
|
1180
1619
|
}
|
|
1181
1620
|
return "swarmvault";
|
|
1182
1621
|
}
|
|
@@ -1195,17 +1634,17 @@ function managedHookBlock(vaultRoot) {
|
|
|
1195
1634
|
].join("\n");
|
|
1196
1635
|
}
|
|
1197
1636
|
function hookPath(repoRoot, hookName) {
|
|
1198
|
-
return
|
|
1637
|
+
return path4.join(repoRoot, ".git", "hooks", hookName);
|
|
1199
1638
|
}
|
|
1200
1639
|
async function readHookStatus(filePath) {
|
|
1201
1640
|
if (!await fileExists(filePath)) {
|
|
1202
1641
|
return "not_installed";
|
|
1203
1642
|
}
|
|
1204
|
-
const content = await
|
|
1643
|
+
const content = await fs4.readFile(filePath, "utf8");
|
|
1205
1644
|
return content.includes(hookStart) && content.includes(hookEnd) ? "installed" : "other_content";
|
|
1206
1645
|
}
|
|
1207
1646
|
async function upsertHookFile(filePath, block) {
|
|
1208
|
-
const existing = await fileExists(filePath) ? await
|
|
1647
|
+
const existing = await fileExists(filePath) ? await fs4.readFile(filePath, "utf8") : "";
|
|
1209
1648
|
let next;
|
|
1210
1649
|
const startIndex = existing.indexOf(hookStart);
|
|
1211
1650
|
const endIndex = existing.indexOf(hookEnd);
|
|
@@ -1219,16 +1658,16 @@ ${block}`.trimEnd();
|
|
|
1219
1658
|
next = `#!/bin/sh
|
|
1220
1659
|
${block}`.trimEnd();
|
|
1221
1660
|
}
|
|
1222
|
-
await ensureDir(
|
|
1223
|
-
await
|
|
1661
|
+
await ensureDir(path4.dirname(filePath));
|
|
1662
|
+
await fs4.writeFile(filePath, `${next}
|
|
1224
1663
|
`, { mode: 493, encoding: "utf8" });
|
|
1225
|
-
await
|
|
1664
|
+
await fs4.chmod(filePath, 493);
|
|
1226
1665
|
}
|
|
1227
1666
|
async function removeHookBlock(filePath) {
|
|
1228
1667
|
if (!await fileExists(filePath)) {
|
|
1229
1668
|
return;
|
|
1230
1669
|
}
|
|
1231
|
-
const existing = await
|
|
1670
|
+
const existing = await fs4.readFile(filePath, "utf8");
|
|
1232
1671
|
const startIndex = existing.indexOf(hookStart);
|
|
1233
1672
|
const endIndex = existing.indexOf(hookEnd);
|
|
1234
1673
|
if (startIndex === -1 || endIndex === -1) {
|
|
@@ -1236,10 +1675,10 @@ async function removeHookBlock(filePath) {
|
|
|
1236
1675
|
}
|
|
1237
1676
|
const next = `${existing.slice(0, startIndex)}${existing.slice(endIndex + hookEnd.length)}`.trim();
|
|
1238
1677
|
if (!next || next === "#!/bin/sh") {
|
|
1239
|
-
await
|
|
1678
|
+
await fs4.rm(filePath, { force: true });
|
|
1240
1679
|
return;
|
|
1241
1680
|
}
|
|
1242
|
-
await
|
|
1681
|
+
await fs4.writeFile(filePath, `${next}
|
|
1243
1682
|
`, "utf8");
|
|
1244
1683
|
}
|
|
1245
1684
|
async function getGitHookStatus(rootDir) {
|
|
@@ -1262,7 +1701,7 @@ async function installGitHooks(rootDir) {
|
|
|
1262
1701
|
if (!repoRoot) {
|
|
1263
1702
|
throw new Error("No git repository found above the current vault.");
|
|
1264
1703
|
}
|
|
1265
|
-
const block = managedHookBlock(
|
|
1704
|
+
const block = managedHookBlock(path4.resolve(rootDir));
|
|
1266
1705
|
await upsertHookFile(hookPath(repoRoot, "post-commit"), block);
|
|
1267
1706
|
await upsertHookFile(hookPath(repoRoot, "post-checkout"), block);
|
|
1268
1707
|
return getGitHookStatus(rootDir);
|
|
@@ -1282,8 +1721,8 @@ async function uninstallGitHooks(rootDir) {
|
|
|
1282
1721
|
}
|
|
1283
1722
|
|
|
1284
1723
|
// src/ingest.ts
|
|
1285
|
-
import
|
|
1286
|
-
import
|
|
1724
|
+
import fs10 from "fs/promises";
|
|
1725
|
+
import path11 from "path";
|
|
1287
1726
|
import { pathToFileURL } from "url";
|
|
1288
1727
|
import { Readability } from "@mozilla/readability";
|
|
1289
1728
|
import matter3 from "gray-matter";
|
|
@@ -1293,16 +1732,16 @@ import mime from "mime-types";
|
|
|
1293
1732
|
import TurndownService from "turndown";
|
|
1294
1733
|
|
|
1295
1734
|
// src/code-analysis.ts
|
|
1296
|
-
import
|
|
1297
|
-
import
|
|
1735
|
+
import fs6 from "fs/promises";
|
|
1736
|
+
import path6 from "path";
|
|
1298
1737
|
import ts from "typescript";
|
|
1299
1738
|
|
|
1300
1739
|
// src/code-tree-sitter.ts
|
|
1301
|
-
import
|
|
1740
|
+
import fs5 from "fs/promises";
|
|
1302
1741
|
import { createRequire } from "module";
|
|
1303
|
-
import
|
|
1742
|
+
import path5 from "path";
|
|
1304
1743
|
var require2 = createRequire(import.meta.url);
|
|
1305
|
-
var TREE_SITTER_PACKAGE_ROOT =
|
|
1744
|
+
var TREE_SITTER_PACKAGE_ROOT = path5.dirname(path5.dirname(require2.resolve("@vscode/tree-sitter-wasm")));
|
|
1306
1745
|
var RATIONALE_MARKERS = ["NOTE:", "IMPORTANT:", "HACK:", "WHY:", "RATIONALE:"];
|
|
1307
1746
|
function stripKnownCommentPrefix(line) {
|
|
1308
1747
|
let next = line.trim();
|
|
@@ -1339,7 +1778,7 @@ async function getTreeSitterModule() {
|
|
|
1339
1778
|
async function ensureTreeSitterInit(module) {
|
|
1340
1779
|
if (!treeSitterInitPromise) {
|
|
1341
1780
|
treeSitterInitPromise = module.Parser.init({
|
|
1342
|
-
locateFile: () =>
|
|
1781
|
+
locateFile: () => path5.join(TREE_SITTER_PACKAGE_ROOT, "wasm", "tree-sitter.wasm")
|
|
1343
1782
|
});
|
|
1344
1783
|
}
|
|
1345
1784
|
return treeSitterInitPromise;
|
|
@@ -1352,7 +1791,7 @@ async function loadLanguage(language) {
|
|
|
1352
1791
|
const loader = (async () => {
|
|
1353
1792
|
const module = await getTreeSitterModule();
|
|
1354
1793
|
await ensureTreeSitterInit(module);
|
|
1355
|
-
const bytes = await
|
|
1794
|
+
const bytes = await fs5.readFile(path5.join(TREE_SITTER_PACKAGE_ROOT, "wasm", grammarFileByLanguage[language]));
|
|
1356
1795
|
return module.Language.load(bytes);
|
|
1357
1796
|
})();
|
|
1358
1797
|
languageCache.set(language, loader);
|
|
@@ -1369,16 +1808,16 @@ function stripCodeExtension(filePath) {
|
|
|
1369
1808
|
return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
|
|
1370
1809
|
}
|
|
1371
1810
|
function manifestModuleName(manifest, language) {
|
|
1372
|
-
const repoPath = manifest.repoRelativePath ??
|
|
1811
|
+
const repoPath = manifest.repoRelativePath ?? path5.basename(manifest.originalPath ?? manifest.storedPath);
|
|
1373
1812
|
const normalized = toPosix(stripCodeExtension(repoPath)).replace(/^\.\/+/, "");
|
|
1374
1813
|
if (!normalized) {
|
|
1375
1814
|
return void 0;
|
|
1376
1815
|
}
|
|
1377
1816
|
if (language === "python") {
|
|
1378
1817
|
const dotted = normalized.replace(/\/__init__$/i, "").replace(/\//g, ".").replace(/^src\./, "");
|
|
1379
|
-
return dotted ||
|
|
1818
|
+
return dotted || path5.posix.basename(normalized);
|
|
1380
1819
|
}
|
|
1381
|
-
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) ||
|
|
1820
|
+
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) || path5.posix.basename(normalized) : normalized;
|
|
1382
1821
|
}
|
|
1383
1822
|
function singleLineSignature(value) {
|
|
1384
1823
|
return truncate(
|
|
@@ -2639,11 +3078,11 @@ function isNodeExported(node) {
|
|
|
2639
3078
|
)
|
|
2640
3079
|
);
|
|
2641
3080
|
}
|
|
2642
|
-
function makeSymbolId2(
|
|
3081
|
+
function makeSymbolId2(scope, name, seen) {
|
|
2643
3082
|
const base = slugify(name);
|
|
2644
3083
|
const count = (seen.get(base) ?? 0) + 1;
|
|
2645
3084
|
seen.set(base, count);
|
|
2646
|
-
return `symbol:${
|
|
3085
|
+
return `symbol:${scope}:${count === 1 ? base : `${base}-${count}`}`;
|
|
2647
3086
|
}
|
|
2648
3087
|
function summarizeModule(manifest, code) {
|
|
2649
3088
|
const localImports = code.imports.filter((item) => !item.isExternal && !item.reExport).length;
|
|
@@ -2783,16 +3222,16 @@ function stripCodeExtension2(filePath) {
|
|
|
2783
3222
|
return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
|
|
2784
3223
|
}
|
|
2785
3224
|
function manifestModuleName2(manifest, language) {
|
|
2786
|
-
const repoPath = manifest.repoRelativePath ??
|
|
3225
|
+
const repoPath = manifest.repoRelativePath ?? path6.basename(manifest.originalPath ?? manifest.storedPath);
|
|
2787
3226
|
const normalized = toPosix(stripCodeExtension2(repoPath)).replace(/^\.\/+/, "");
|
|
2788
3227
|
if (!normalized) {
|
|
2789
3228
|
return void 0;
|
|
2790
3229
|
}
|
|
2791
3230
|
if (language === "python") {
|
|
2792
3231
|
const dotted = normalized.replace(/\/__init__$/i, "").replace(/\//g, ".").replace(/^src\./, "");
|
|
2793
|
-
return dotted ||
|
|
3232
|
+
return dotted || path6.posix.basename(normalized);
|
|
2794
3233
|
}
|
|
2795
|
-
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) ||
|
|
3234
|
+
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) || path6.posix.basename(normalized) : normalized;
|
|
2796
3235
|
}
|
|
2797
3236
|
function finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, exportLabels, diagnostics, metadata) {
|
|
2798
3237
|
const topLevelNames = new Set(draftSymbols.map((symbol) => symbol.name));
|
|
@@ -2802,8 +3241,9 @@ function finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, export
|
|
|
2802
3241
|
}
|
|
2803
3242
|
}
|
|
2804
3243
|
const seenSymbolIds = /* @__PURE__ */ new Map();
|
|
3244
|
+
const symbolScope = metadata?.namespace ? `ns:${slugify(metadata.namespace)}` : manifest.sourceId;
|
|
2805
3245
|
const symbols = draftSymbols.map((symbol) => ({
|
|
2806
|
-
id: makeSymbolId2(
|
|
3246
|
+
id: makeSymbolId2(symbolScope, symbol.name, seenSymbolIds),
|
|
2807
3247
|
name: symbol.name,
|
|
2808
3248
|
kind: symbol.kind,
|
|
2809
3249
|
signature: symbol.signature,
|
|
@@ -3085,7 +3525,7 @@ function analyzeTypeScriptLikeCode(manifest, content) {
|
|
|
3085
3525
|
};
|
|
3086
3526
|
}
|
|
3087
3527
|
function inferCodeLanguage(filePath, mimeType = "") {
|
|
3088
|
-
const extension =
|
|
3528
|
+
const extension = path6.extname(filePath).toLowerCase();
|
|
3089
3529
|
if (extension === ".ts" || extension === ".mts" || extension === ".cts") {
|
|
3090
3530
|
return "typescript";
|
|
3091
3531
|
}
|
|
@@ -3134,12 +3574,12 @@ function modulePageTitle(manifest) {
|
|
|
3134
3574
|
return `${manifest.title} module`;
|
|
3135
3575
|
}
|
|
3136
3576
|
function importResolutionCandidates(basePath, specifier, extensions) {
|
|
3137
|
-
const resolved =
|
|
3138
|
-
if (
|
|
3577
|
+
const resolved = path6.posix.normalize(path6.posix.join(path6.posix.dirname(basePath), specifier));
|
|
3578
|
+
if (path6.posix.extname(resolved)) {
|
|
3139
3579
|
return [resolved];
|
|
3140
3580
|
}
|
|
3141
|
-
const direct = extensions.map((extension) =>
|
|
3142
|
-
const indexFiles = extensions.map((extension) =>
|
|
3581
|
+
const direct = extensions.map((extension) => path6.posix.normalize(`${resolved}${extension}`));
|
|
3582
|
+
const indexFiles = extensions.map((extension) => path6.posix.normalize(path6.posix.join(resolved, `index${extension}`)));
|
|
3143
3583
|
return uniqueBy([resolved, ...direct, ...indexFiles], (candidate) => candidate);
|
|
3144
3584
|
}
|
|
3145
3585
|
function normalizeAlias(value) {
|
|
@@ -3158,32 +3598,32 @@ function recordAlias(target, value) {
|
|
|
3158
3598
|
}
|
|
3159
3599
|
function manifestBasenameWithoutExtension(manifest) {
|
|
3160
3600
|
const target = manifest.repoRelativePath ?? manifest.originalPath ?? manifest.storedPath;
|
|
3161
|
-
return
|
|
3601
|
+
return path6.posix.basename(stripCodeExtension2(normalizeAlias(target)));
|
|
3162
3602
|
}
|
|
3163
3603
|
async function readNearestGoModulePath(startPath, cache) {
|
|
3164
|
-
let current =
|
|
3604
|
+
let current = path6.resolve(startPath);
|
|
3165
3605
|
try {
|
|
3166
|
-
const stat = await
|
|
3606
|
+
const stat = await fs6.stat(current);
|
|
3167
3607
|
if (!stat.isDirectory()) {
|
|
3168
|
-
current =
|
|
3608
|
+
current = path6.dirname(current);
|
|
3169
3609
|
}
|
|
3170
3610
|
} catch {
|
|
3171
|
-
current =
|
|
3611
|
+
current = path6.dirname(current);
|
|
3172
3612
|
}
|
|
3173
3613
|
while (true) {
|
|
3174
3614
|
if (cache.has(current)) {
|
|
3175
3615
|
const cached = cache.get(current);
|
|
3176
3616
|
return cached === null ? void 0 : cached;
|
|
3177
3617
|
}
|
|
3178
|
-
const goModPath =
|
|
3179
|
-
if (await
|
|
3180
|
-
const content = await
|
|
3618
|
+
const goModPath = path6.join(current, "go.mod");
|
|
3619
|
+
if (await fs6.access(goModPath).then(() => true).catch(() => false)) {
|
|
3620
|
+
const content = await fs6.readFile(goModPath, "utf8");
|
|
3181
3621
|
const match = content.match(/^\s*module\s+(\S+)/m);
|
|
3182
3622
|
const modulePath = match?.[1]?.trim() ?? null;
|
|
3183
3623
|
cache.set(current, modulePath);
|
|
3184
3624
|
return modulePath ?? void 0;
|
|
3185
3625
|
}
|
|
3186
|
-
const parent =
|
|
3626
|
+
const parent = path6.dirname(current);
|
|
3187
3627
|
if (parent === current) {
|
|
3188
3628
|
cache.set(current, null);
|
|
3189
3629
|
return void 0;
|
|
@@ -3264,10 +3704,10 @@ async function buildCodeIndex(rootDir, manifests, analyses) {
|
|
|
3264
3704
|
if (normalizedNamespace) {
|
|
3265
3705
|
recordAlias(aliases, normalizedNamespace);
|
|
3266
3706
|
}
|
|
3267
|
-
const originalPath = manifest.originalPath ?
|
|
3707
|
+
const originalPath = manifest.originalPath ? path6.resolve(manifest.originalPath) : path6.resolve(rootDir, manifest.storedPath);
|
|
3268
3708
|
const goModulePath = await readNearestGoModulePath(originalPath, goModuleCache);
|
|
3269
3709
|
if (goModulePath && repoRelativePath) {
|
|
3270
|
-
const dir =
|
|
3710
|
+
const dir = path6.posix.dirname(repoRelativePath);
|
|
3271
3711
|
const packageAlias = dir === "." ? goModulePath : `${goModulePath}/${dir}`;
|
|
3272
3712
|
recordAlias(aliases, packageAlias);
|
|
3273
3713
|
}
|
|
@@ -3343,10 +3783,10 @@ function resolvePythonRelativeAliases(repoRelativePath, specifier) {
|
|
|
3343
3783
|
const dotMatch = specifier.match(/^\.+/);
|
|
3344
3784
|
const depth = dotMatch ? dotMatch[0].length : 0;
|
|
3345
3785
|
const relativeModule = specifier.slice(depth).replace(/\./g, "/");
|
|
3346
|
-
const baseDir =
|
|
3347
|
-
const parentDir =
|
|
3348
|
-
const moduleBase = relativeModule ?
|
|
3349
|
-
return uniqueBy([`${moduleBase}.py`,
|
|
3786
|
+
const baseDir = path6.posix.dirname(repoRelativePath);
|
|
3787
|
+
const parentDir = path6.posix.normalize(path6.posix.join(baseDir, ...Array(Math.max(depth - 1, 0)).fill("..")));
|
|
3788
|
+
const moduleBase = relativeModule ? path6.posix.join(parentDir, relativeModule) : parentDir;
|
|
3789
|
+
return uniqueBy([`${moduleBase}.py`, path6.posix.join(moduleBase, "__init__.py")], (item) => item);
|
|
3350
3790
|
}
|
|
3351
3791
|
function resolveRustAliases(manifest, specifier) {
|
|
3352
3792
|
const repoRelativePath = manifest.repoRelativePath ? normalizeAlias(manifest.repoRelativePath) : "";
|
|
@@ -3470,7 +3910,7 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
|
3470
3910
|
const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType) ?? "typescript";
|
|
3471
3911
|
const { code, rationales } = language === "javascript" || language === "jsx" || language === "typescript" || language === "tsx" ? analyzeTypeScriptLikeCode(manifest, extractedText) : await analyzeTreeSitterCode(manifest, extractedText, language);
|
|
3472
3912
|
return {
|
|
3473
|
-
analysisVersion:
|
|
3913
|
+
analysisVersion: 6,
|
|
3474
3914
|
sourceId: manifest.sourceId,
|
|
3475
3915
|
sourceHash: manifest.contentHash,
|
|
3476
3916
|
extractionHash: manifest.extractionHash,
|
|
@@ -3481,6 +3921,7 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
|
3481
3921
|
entities: [],
|
|
3482
3922
|
claims: codeClaims(manifest, code),
|
|
3483
3923
|
questions: codeQuestions(manifest, code),
|
|
3924
|
+
tags: [],
|
|
3484
3925
|
rationales,
|
|
3485
3926
|
code,
|
|
3486
3927
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3488,9 +3929,9 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
|
3488
3929
|
}
|
|
3489
3930
|
|
|
3490
3931
|
// src/extraction.ts
|
|
3491
|
-
import
|
|
3932
|
+
import fs7 from "fs/promises";
|
|
3492
3933
|
import os from "os";
|
|
3493
|
-
import
|
|
3934
|
+
import path7 from "path";
|
|
3494
3935
|
import { strFromU8, unzipSync } from "fflate";
|
|
3495
3936
|
import { JSDOM } from "jsdom";
|
|
3496
3937
|
import { z } from "zod";
|
|
@@ -3572,14 +4013,14 @@ async function materializeAttachmentPath(input) {
|
|
|
3572
4013
|
if (!input.bytes) {
|
|
3573
4014
|
throw new Error("Image extraction requires a file path or bytes.");
|
|
3574
4015
|
}
|
|
3575
|
-
const tempDir = await
|
|
4016
|
+
const tempDir = await fs7.mkdtemp(path7.join(os.tmpdir(), "swarmvault-image-extract-"));
|
|
3576
4017
|
const extension = input.mimeType.split("/")[1]?.split("+")[0] ?? "bin";
|
|
3577
|
-
const tempPath =
|
|
3578
|
-
await
|
|
4018
|
+
const tempPath = path7.join(tempDir, `source.${extension}`);
|
|
4019
|
+
await fs7.writeFile(tempPath, input.bytes);
|
|
3579
4020
|
return {
|
|
3580
4021
|
filePath: tempPath,
|
|
3581
4022
|
cleanup: async () => {
|
|
3582
|
-
await
|
|
4023
|
+
await fs7.rm(tempDir, { recursive: true, force: true });
|
|
3583
4024
|
}
|
|
3584
4025
|
};
|
|
3585
4026
|
}
|
|
@@ -3790,18 +4231,18 @@ async function extractDocxText(input) {
|
|
|
3790
4231
|
}
|
|
3791
4232
|
|
|
3792
4233
|
// src/logs.ts
|
|
3793
|
-
import
|
|
3794
|
-
import
|
|
4234
|
+
import fs8 from "fs/promises";
|
|
4235
|
+
import path8 from "path";
|
|
3795
4236
|
import matter from "gray-matter";
|
|
3796
4237
|
async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
|
|
3797
4238
|
const { paths } = await initWorkspace(rootDir);
|
|
3798
4239
|
await ensureDir(paths.sessionsDir);
|
|
3799
4240
|
const timestamp = startedAt.replace(/[:.]/g, "-");
|
|
3800
4241
|
const baseName = `${timestamp}-${operation}-${slugify(title)}`;
|
|
3801
|
-
let candidate =
|
|
4242
|
+
let candidate = path8.join(paths.sessionsDir, `${baseName}.md`);
|
|
3802
4243
|
let counter = 2;
|
|
3803
4244
|
while (await fileExists(candidate)) {
|
|
3804
|
-
candidate =
|
|
4245
|
+
candidate = path8.join(paths.sessionsDir, `${baseName}-${counter}.md`);
|
|
3805
4246
|
counter++;
|
|
3806
4247
|
}
|
|
3807
4248
|
return candidate;
|
|
@@ -3809,11 +4250,11 @@ async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
|
|
|
3809
4250
|
async function appendLogEntry(rootDir, action, title, lines = []) {
|
|
3810
4251
|
const { paths } = await initWorkspace(rootDir);
|
|
3811
4252
|
await ensureDir(paths.wikiDir);
|
|
3812
|
-
const logPath =
|
|
4253
|
+
const logPath = path8.join(paths.wikiDir, "log.md");
|
|
3813
4254
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
3814
4255
|
const entry = [`## [${timestamp}] ${action} | ${title}`, ...lines.map((line) => `- ${line}`), ""].join("\n");
|
|
3815
|
-
const existing = await fileExists(logPath) ? await
|
|
3816
|
-
await
|
|
4256
|
+
const existing = await fileExists(logPath) ? await fs8.readFile(logPath, "utf8") : "# Log\n\n";
|
|
4257
|
+
await fs8.writeFile(logPath, `${existing}${entry}
|
|
3817
4258
|
`, "utf8");
|
|
3818
4259
|
}
|
|
3819
4260
|
async function recordSession(rootDir, input) {
|
|
@@ -3823,8 +4264,8 @@ async function recordSession(rootDir, input) {
|
|
|
3823
4264
|
const finishedAtIso = new Date(input.finishedAt ?? input.startedAt).toISOString();
|
|
3824
4265
|
const durationMs = Math.max(0, new Date(finishedAtIso).getTime() - new Date(startedAtIso).getTime());
|
|
3825
4266
|
const sessionPath = await resolveUniqueSessionPath(rootDir, input.operation, input.title, startedAtIso);
|
|
3826
|
-
const sessionId =
|
|
3827
|
-
const relativeSessionPath =
|
|
4267
|
+
const sessionId = path8.basename(sessionPath, ".md");
|
|
4268
|
+
const relativeSessionPath = path8.relative(rootDir, sessionPath).split(path8.sep).join(path8.posix.sep);
|
|
3828
4269
|
const frontmatter = Object.fromEntries(
|
|
3829
4270
|
Object.entries({
|
|
3830
4271
|
session_id: sessionId,
|
|
@@ -3872,7 +4313,7 @@ async function recordSession(rootDir, input) {
|
|
|
3872
4313
|
frontmatter
|
|
3873
4314
|
);
|
|
3874
4315
|
await writeFileIfChanged(sessionPath, content);
|
|
3875
|
-
const logPath =
|
|
4316
|
+
const logPath = path8.join(paths.wikiDir, "log.md");
|
|
3876
4317
|
const timestamp = startedAtIso.slice(0, 19).replace("T", " ");
|
|
3877
4318
|
const entry = [
|
|
3878
4319
|
`## [${timestamp}] ${input.operation} | ${input.title}`,
|
|
@@ -3880,8 +4321,8 @@ async function recordSession(rootDir, input) {
|
|
|
3880
4321
|
...(input.lines ?? []).map((line) => `- ${line}`),
|
|
3881
4322
|
""
|
|
3882
4323
|
].join("\n");
|
|
3883
|
-
const existing = await fileExists(logPath) ? await
|
|
3884
|
-
await
|
|
4324
|
+
const existing = await fileExists(logPath) ? await fs8.readFile(logPath, "utf8") : "# Log\n\n";
|
|
4325
|
+
await fs8.writeFile(logPath, `${existing}${entry}
|
|
3885
4326
|
`, "utf8");
|
|
3886
4327
|
return { sessionPath, sessionId };
|
|
3887
4328
|
}
|
|
@@ -3891,13 +4332,13 @@ async function appendWatchRun(rootDir, run) {
|
|
|
3891
4332
|
}
|
|
3892
4333
|
|
|
3893
4334
|
// src/source-classification.ts
|
|
3894
|
-
import
|
|
4335
|
+
import path9 from "path";
|
|
3895
4336
|
var ALL_SOURCE_CLASSES = ["first_party", "third_party", "resource", "generated"];
|
|
3896
4337
|
var THIRD_PARTY_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "vendor", "Pods"]);
|
|
3897
4338
|
var GENERATED_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", ".next", "coverage", "DerivedData", "target"]);
|
|
3898
4339
|
function matchesAnyGlob(relativePath, patterns) {
|
|
3899
4340
|
return patterns.some(
|
|
3900
|
-
(pattern) =>
|
|
4341
|
+
(pattern) => path9.matchesGlob(relativePath, pattern) || path9.matchesGlob(path9.posix.basename(relativePath), pattern)
|
|
3901
4342
|
);
|
|
3902
4343
|
}
|
|
3903
4344
|
function classifyRepoPath(relativePath, repoAnalysis) {
|
|
@@ -3950,8 +4391,8 @@ function aggregateManifestSourceClass(manifests, sourceIds) {
|
|
|
3950
4391
|
}
|
|
3951
4392
|
|
|
3952
4393
|
// src/watch-state.ts
|
|
3953
|
-
import
|
|
3954
|
-
import
|
|
4394
|
+
import fs9 from "fs/promises";
|
|
4395
|
+
import path10 from "path";
|
|
3955
4396
|
import matter2 from "gray-matter";
|
|
3956
4397
|
function pendingEntryKey(entry) {
|
|
3957
4398
|
return entry.path;
|
|
@@ -3965,7 +4406,7 @@ function normalizeRelativePath(rootDir, filePath) {
|
|
|
3965
4406
|
if (!filePath) {
|
|
3966
4407
|
return void 0;
|
|
3967
4408
|
}
|
|
3968
|
-
return toPosix(
|
|
4409
|
+
return toPosix(path10.relative(rootDir, path10.resolve(filePath)));
|
|
3969
4410
|
}
|
|
3970
4411
|
async function readPendingSemanticRefresh(rootDir) {
|
|
3971
4412
|
const { paths } = await initWorkspace(rootDir);
|
|
@@ -4059,11 +4500,11 @@ async function markPagesStaleForSources(rootDir, sourceIds) {
|
|
|
4059
4500
|
if (page.freshness !== "stale" || !page.sourceIds.some((sourceId) => affectedSourceIds.has(sourceId))) {
|
|
4060
4501
|
continue;
|
|
4061
4502
|
}
|
|
4062
|
-
const absolutePath =
|
|
4503
|
+
const absolutePath = path10.join(paths.wikiDir, page.path);
|
|
4063
4504
|
if (!await fileExists(absolutePath)) {
|
|
4064
4505
|
continue;
|
|
4065
4506
|
}
|
|
4066
|
-
const raw = await
|
|
4507
|
+
const raw = await fs9.readFile(absolutePath, "utf8");
|
|
4067
4508
|
const parsed = matter2(raw);
|
|
4068
4509
|
if (parsed.data.freshness === "stale") {
|
|
4069
4510
|
continue;
|
|
@@ -4117,7 +4558,7 @@ function normalizeIngestOptions(options) {
|
|
|
4117
4558
|
return {
|
|
4118
4559
|
includeAssets: options?.includeAssets ?? true,
|
|
4119
4560
|
maxAssetSize: Math.max(0, Math.floor(options?.maxAssetSize ?? DEFAULT_MAX_ASSET_SIZE)),
|
|
4120
|
-
repoRoot: options?.repoRoot ?
|
|
4561
|
+
repoRoot: options?.repoRoot ? path11.resolve(options.repoRoot) : void 0,
|
|
4121
4562
|
include: (options?.include ?? []).map((pattern) => pattern.trim()).filter(Boolean),
|
|
4122
4563
|
exclude: (options?.exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean),
|
|
4123
4564
|
maxFiles: Math.max(1, Math.floor(options?.maxFiles ?? DEFAULT_MAX_DIRECTORY_FILES)),
|
|
@@ -4137,27 +4578,27 @@ async function resolveRepoIngestOptions(rootDir, options) {
|
|
|
4137
4578
|
}
|
|
4138
4579
|
function matchesAnyGlob2(relativePath, patterns) {
|
|
4139
4580
|
return patterns.some(
|
|
4140
|
-
(pattern) =>
|
|
4581
|
+
(pattern) => path11.matchesGlob(relativePath, pattern) || path11.matchesGlob(path11.posix.basename(relativePath), pattern)
|
|
4141
4582
|
);
|
|
4142
4583
|
}
|
|
4143
4584
|
function supportedDirectoryKind(sourceKind) {
|
|
4144
4585
|
return sourceKind !== "binary";
|
|
4145
4586
|
}
|
|
4146
4587
|
async function findNearestGitRoot2(startPath) {
|
|
4147
|
-
let current =
|
|
4588
|
+
let current = path11.resolve(startPath);
|
|
4148
4589
|
try {
|
|
4149
|
-
const stat = await
|
|
4590
|
+
const stat = await fs10.stat(current);
|
|
4150
4591
|
if (!stat.isDirectory()) {
|
|
4151
|
-
current =
|
|
4592
|
+
current = path11.dirname(current);
|
|
4152
4593
|
}
|
|
4153
4594
|
} catch {
|
|
4154
|
-
current =
|
|
4595
|
+
current = path11.dirname(current);
|
|
4155
4596
|
}
|
|
4156
4597
|
while (true) {
|
|
4157
|
-
if (await fileExists(
|
|
4598
|
+
if (await fileExists(path11.join(current, ".git"))) {
|
|
4158
4599
|
return current;
|
|
4159
4600
|
}
|
|
4160
|
-
const parent =
|
|
4601
|
+
const parent = path11.dirname(current);
|
|
4161
4602
|
if (parent === current) {
|
|
4162
4603
|
return null;
|
|
4163
4604
|
}
|
|
@@ -4165,26 +4606,26 @@ async function findNearestGitRoot2(startPath) {
|
|
|
4165
4606
|
}
|
|
4166
4607
|
}
|
|
4167
4608
|
function withinRoot(rootPath, targetPath) {
|
|
4168
|
-
const relative =
|
|
4169
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
4609
|
+
const relative = path11.relative(rootPath, targetPath);
|
|
4610
|
+
return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
|
|
4170
4611
|
}
|
|
4171
4612
|
function repoRootFromManifest(manifest) {
|
|
4172
4613
|
if (manifest.originType !== "file" || !manifest.originalPath || !manifest.repoRelativePath) {
|
|
4173
4614
|
return null;
|
|
4174
4615
|
}
|
|
4175
|
-
const repoDir =
|
|
4176
|
-
const fileDir =
|
|
4616
|
+
const repoDir = path11.posix.dirname(manifest.repoRelativePath);
|
|
4617
|
+
const fileDir = path11.dirname(path11.resolve(manifest.originalPath));
|
|
4177
4618
|
if (repoDir === "." || !repoDir) {
|
|
4178
4619
|
return fileDir;
|
|
4179
4620
|
}
|
|
4180
4621
|
const segments = repoDir.split("/").filter(Boolean);
|
|
4181
|
-
return
|
|
4622
|
+
return path11.resolve(fileDir, ...segments.map(() => ".."));
|
|
4182
4623
|
}
|
|
4183
4624
|
function repoRelativePathFor(absolutePath, repoRoot) {
|
|
4184
4625
|
if (!repoRoot || !withinRoot(repoRoot, absolutePath)) {
|
|
4185
4626
|
return void 0;
|
|
4186
4627
|
}
|
|
4187
|
-
const relative = toPosix(
|
|
4628
|
+
const relative = toPosix(path11.relative(repoRoot, absolutePath));
|
|
4188
4629
|
return relative && !relative.startsWith("..") ? relative : void 0;
|
|
4189
4630
|
}
|
|
4190
4631
|
function normalizeOriginUrl(input) {
|
|
@@ -4260,7 +4701,41 @@ function prepareCapturedMarkdownInput(input) {
|
|
|
4260
4701
|
logDetails: input.logDetails
|
|
4261
4702
|
};
|
|
4262
4703
|
}
|
|
4704
|
+
function isPrivateIp(ip) {
|
|
4705
|
+
if (ip === "::1" || ip.startsWith("fc") || ip.startsWith("fd")) return true;
|
|
4706
|
+
const parts = ip.split(".").map(Number);
|
|
4707
|
+
if (parts.length !== 4 || parts.some((p) => Number.isNaN(p))) return false;
|
|
4708
|
+
return parts[0] === 0 || parts[0] === 127 || parts[0] === 10 || parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31 || parts[0] === 192 && parts[1] === 168 || parts[0] === 169 && parts[1] === 254;
|
|
4709
|
+
}
|
|
4710
|
+
function allowPrivateUrlsForProcess() {
|
|
4711
|
+
return process.env.SWARMVAULT_ALLOW_PRIVATE_URLS === "1";
|
|
4712
|
+
}
|
|
4713
|
+
function isReservedTestHostname(hostname) {
|
|
4714
|
+
const lower = hostname.toLowerCase();
|
|
4715
|
+
return lower.endsWith(".test") || lower.endsWith(".example") || lower.endsWith(".invalid");
|
|
4716
|
+
}
|
|
4717
|
+
async function validateUrlSafety(url) {
|
|
4718
|
+
const parsed = new URL(url);
|
|
4719
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
4720
|
+
throw new Error(`Blocked protocol: ${parsed.protocol}`);
|
|
4721
|
+
}
|
|
4722
|
+
if (allowPrivateUrlsForProcess() || isReservedTestHostname(parsed.hostname)) {
|
|
4723
|
+
return;
|
|
4724
|
+
}
|
|
4725
|
+
let address;
|
|
4726
|
+
try {
|
|
4727
|
+
const { lookup } = await import("dns/promises");
|
|
4728
|
+
const result = await lookup(parsed.hostname);
|
|
4729
|
+
address = result.address;
|
|
4730
|
+
} catch {
|
|
4731
|
+
return;
|
|
4732
|
+
}
|
|
4733
|
+
if (isPrivateIp(address)) {
|
|
4734
|
+
throw new Error(`Blocked private/reserved IP ${address} (resolved from ${parsed.hostname})`);
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4263
4737
|
async function fetchText(url) {
|
|
4738
|
+
await validateUrlSafety(url);
|
|
4264
4739
|
const response = await fetch(url);
|
|
4265
4740
|
if (!response.ok) {
|
|
4266
4741
|
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
@@ -4268,6 +4743,7 @@ async function fetchText(url) {
|
|
|
4268
4743
|
return response.text();
|
|
4269
4744
|
}
|
|
4270
4745
|
async function fetchResolvedText(url) {
|
|
4746
|
+
await validateUrlSafety(url);
|
|
4271
4747
|
const response = await fetch(url);
|
|
4272
4748
|
if (!response.ok) {
|
|
4273
4749
|
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
@@ -4481,7 +4957,7 @@ function buildCompositeHash(payloadBytes, attachments = []) {
|
|
|
4481
4957
|
return sha256(`${sha256(payloadBytes)}|${attachmentSignature}`);
|
|
4482
4958
|
}
|
|
4483
4959
|
function sanitizeAssetRelativePath(value) {
|
|
4484
|
-
const normalized =
|
|
4960
|
+
const normalized = path11.posix.normalize(value.replace(/\\/g, "/"));
|
|
4485
4961
|
const segments = normalized.split("/").filter(Boolean).map((segment) => {
|
|
4486
4962
|
if (segment === ".") {
|
|
4487
4963
|
return "";
|
|
@@ -4501,7 +4977,7 @@ function normalizeLocalReference(value) {
|
|
|
4501
4977
|
return null;
|
|
4502
4978
|
}
|
|
4503
4979
|
const lowered = candidate.toLowerCase();
|
|
4504
|
-
if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") ||
|
|
4980
|
+
if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path11.isAbsolute(candidate)) {
|
|
4505
4981
|
return null;
|
|
4506
4982
|
}
|
|
4507
4983
|
return candidate.replace(/\\/g, "/");
|
|
@@ -4579,12 +5055,12 @@ async function convertHtmlToMarkdown(html, url) {
|
|
|
4579
5055
|
};
|
|
4580
5056
|
}
|
|
4581
5057
|
async function readManifestByHash(manifestsDir, contentHash) {
|
|
4582
|
-
const entries = await
|
|
5058
|
+
const entries = await fs10.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
|
|
4583
5059
|
for (const entry of entries) {
|
|
4584
5060
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
4585
5061
|
continue;
|
|
4586
5062
|
}
|
|
4587
|
-
const manifest = await readJsonFile(
|
|
5063
|
+
const manifest = await readJsonFile(path11.join(manifestsDir, entry.name));
|
|
4588
5064
|
if (manifest?.contentHash === contentHash) {
|
|
4589
5065
|
return manifest;
|
|
4590
5066
|
}
|
|
@@ -4592,12 +5068,12 @@ async function readManifestByHash(manifestsDir, contentHash) {
|
|
|
4592
5068
|
return null;
|
|
4593
5069
|
}
|
|
4594
5070
|
async function readManifestByOrigin(manifestsDir, prepared) {
|
|
4595
|
-
const entries = await
|
|
5071
|
+
const entries = await fs10.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
|
|
4596
5072
|
for (const entry of entries) {
|
|
4597
5073
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
4598
5074
|
continue;
|
|
4599
5075
|
}
|
|
4600
|
-
const manifest = await readJsonFile(
|
|
5076
|
+
const manifest = await readJsonFile(path11.join(manifestsDir, entry.name));
|
|
4601
5077
|
if (manifest && manifestMatchesOrigin(manifest, prepared)) {
|
|
4602
5078
|
return manifest;
|
|
4603
5079
|
}
|
|
@@ -4608,12 +5084,12 @@ async function loadGitignoreMatcher(repoRoot, enabled) {
|
|
|
4608
5084
|
if (!enabled) {
|
|
4609
5085
|
return null;
|
|
4610
5086
|
}
|
|
4611
|
-
const gitignorePath =
|
|
5087
|
+
const gitignorePath = path11.join(repoRoot, ".gitignore");
|
|
4612
5088
|
if (!await fileExists(gitignorePath)) {
|
|
4613
5089
|
return null;
|
|
4614
5090
|
}
|
|
4615
5091
|
const matcher = ignore();
|
|
4616
|
-
matcher.add(await
|
|
5092
|
+
matcher.add(await fs10.readFile(gitignorePath, "utf8"));
|
|
4617
5093
|
return matcher;
|
|
4618
5094
|
}
|
|
4619
5095
|
function builtInIgnoreReason(relativePath) {
|
|
@@ -4637,23 +5113,23 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
|
|
|
4637
5113
|
if (!currentDir) {
|
|
4638
5114
|
continue;
|
|
4639
5115
|
}
|
|
4640
|
-
const entries = await
|
|
5116
|
+
const entries = await fs10.readdir(currentDir, { withFileTypes: true });
|
|
4641
5117
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
4642
5118
|
for (const entry of entries) {
|
|
4643
|
-
const absolutePath =
|
|
4644
|
-
const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
5119
|
+
const absolutePath = path11.join(currentDir, entry.name);
|
|
5120
|
+
const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(inputDir, absolutePath));
|
|
4645
5121
|
const relativePath = relativeToRepo || entry.name;
|
|
4646
5122
|
const builtInReason = builtInIgnoreReason(relativePath);
|
|
4647
5123
|
if (builtInReason) {
|
|
4648
|
-
skipped.push({ path: toPosix(
|
|
5124
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: builtInReason });
|
|
4649
5125
|
continue;
|
|
4650
5126
|
}
|
|
4651
5127
|
if (matcher?.ignores(relativePath)) {
|
|
4652
|
-
skipped.push({ path: toPosix(
|
|
5128
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "gitignore" });
|
|
4653
5129
|
continue;
|
|
4654
5130
|
}
|
|
4655
5131
|
if (matchesAnyGlob2(relativePath, options.exclude)) {
|
|
4656
|
-
skipped.push({ path: toPosix(
|
|
5132
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "exclude_glob" });
|
|
4657
5133
|
continue;
|
|
4658
5134
|
}
|
|
4659
5135
|
if (entry.isDirectory()) {
|
|
@@ -4661,26 +5137,26 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
|
|
|
4661
5137
|
continue;
|
|
4662
5138
|
}
|
|
4663
5139
|
if (!entry.isFile()) {
|
|
4664
|
-
skipped.push({ path: toPosix(
|
|
5140
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
|
|
4665
5141
|
continue;
|
|
4666
5142
|
}
|
|
4667
5143
|
if (options.include.length > 0 && !matchesAnyGlob2(relativePath, options.include)) {
|
|
4668
|
-
skipped.push({ path: toPosix(
|
|
5144
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "include_glob" });
|
|
4669
5145
|
continue;
|
|
4670
5146
|
}
|
|
4671
5147
|
const mimeType = guessMimeType(absolutePath);
|
|
4672
5148
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
4673
5149
|
const sourceClass = sourceClassForRelativePath(relativePath, options);
|
|
4674
5150
|
if (!supportedDirectoryKind(sourceKind)) {
|
|
4675
|
-
skipped.push({ path: toPosix(
|
|
5151
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
4676
5152
|
continue;
|
|
4677
5153
|
}
|
|
4678
5154
|
if (!options.extractClasses.includes(sourceClass)) {
|
|
4679
|
-
skipped.push({ path: toPosix(
|
|
5155
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: `source_class:${sourceClass}` });
|
|
4680
5156
|
continue;
|
|
4681
5157
|
}
|
|
4682
5158
|
if (files.length >= options.maxFiles) {
|
|
4683
|
-
skipped.push({ path: toPosix(
|
|
5159
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "max_files" });
|
|
4684
5160
|
continue;
|
|
4685
5161
|
}
|
|
4686
5162
|
files.push(absolutePath);
|
|
@@ -4702,12 +5178,12 @@ function resolveUrlMimeType(input, response) {
|
|
|
4702
5178
|
function buildRemoteAssetRelativePath(assetUrl, mimeType) {
|
|
4703
5179
|
const url = new URL(assetUrl);
|
|
4704
5180
|
const normalized = sanitizeAssetRelativePath(`${url.hostname}${url.pathname || "/asset"}`);
|
|
4705
|
-
const extension =
|
|
4706
|
-
const directory =
|
|
4707
|
-
const basename = extension ?
|
|
5181
|
+
const extension = path11.posix.extname(normalized);
|
|
5182
|
+
const directory = path11.posix.dirname(normalized);
|
|
5183
|
+
const basename = extension ? path11.posix.basename(normalized, extension) : path11.posix.basename(normalized);
|
|
4708
5184
|
const resolvedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
|
|
4709
5185
|
const hashedName = `${basename || "asset"}-${sha256(assetUrl).slice(0, 8)}${resolvedExtension}`;
|
|
4710
|
-
return directory === "." ? hashedName :
|
|
5186
|
+
return directory === "." ? hashedName : path11.posix.join(directory, hashedName);
|
|
4711
5187
|
}
|
|
4712
5188
|
async function readResponseBytesWithinLimit(response, maxBytes) {
|
|
4713
5189
|
const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
|
|
@@ -4739,6 +5215,7 @@ async function readResponseBytesWithinLimit(response, maxBytes) {
|
|
|
4739
5215
|
return Buffer.concat(chunks);
|
|
4740
5216
|
}
|
|
4741
5217
|
async function fetchRemoteImageAttachment(assetUrl, maxAssetSize) {
|
|
5218
|
+
await validateUrlSafety(assetUrl);
|
|
4742
5219
|
const response = await fetch(assetUrl);
|
|
4743
5220
|
if (!response.ok) {
|
|
4744
5221
|
throw new Error(`failed with ${response.status} ${response.statusText}`);
|
|
@@ -4859,34 +5336,34 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4859
5336
|
const previous = existingByOrigin ?? void 0;
|
|
4860
5337
|
const sourceId = previous?.sourceId ?? `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
|
|
4861
5338
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4862
|
-
const storedPath =
|
|
4863
|
-
const extractedTextPath = prepared.extractedText ?
|
|
4864
|
-
const extractedMetadataPath = prepared.extractionArtifact ?
|
|
4865
|
-
const attachmentsDir =
|
|
5339
|
+
const storedPath = path11.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
|
|
5340
|
+
const extractedTextPath = prepared.extractedText ? path11.join(paths.extractsDir, `${sourceId}.md`) : void 0;
|
|
5341
|
+
const extractedMetadataPath = prepared.extractionArtifact ? path11.join(paths.extractsDir, `${sourceId}.json`) : void 0;
|
|
5342
|
+
const attachmentsDir = path11.join(paths.rawAssetsDir, sourceId);
|
|
4866
5343
|
if (previous?.storedPath) {
|
|
4867
|
-
await
|
|
5344
|
+
await fs10.rm(path11.resolve(rootDir, previous.storedPath), { force: true });
|
|
4868
5345
|
}
|
|
4869
5346
|
if (previous?.extractedTextPath) {
|
|
4870
|
-
await
|
|
5347
|
+
await fs10.rm(path11.resolve(rootDir, previous.extractedTextPath), { force: true });
|
|
4871
5348
|
}
|
|
4872
5349
|
if (previous?.extractedMetadataPath) {
|
|
4873
|
-
await
|
|
5350
|
+
await fs10.rm(path11.resolve(rootDir, previous.extractedMetadataPath), { force: true });
|
|
4874
5351
|
}
|
|
4875
|
-
await
|
|
4876
|
-
await
|
|
5352
|
+
await fs10.rm(attachmentsDir, { recursive: true, force: true });
|
|
5353
|
+
await fs10.writeFile(storedPath, prepared.payloadBytes);
|
|
4877
5354
|
if (prepared.extractedText && extractedTextPath) {
|
|
4878
|
-
await
|
|
5355
|
+
await fs10.writeFile(extractedTextPath, prepared.extractedText, "utf8");
|
|
4879
5356
|
}
|
|
4880
5357
|
if (prepared.extractionArtifact && extractedMetadataPath) {
|
|
4881
5358
|
await writeJsonFile(extractedMetadataPath, prepared.extractionArtifact);
|
|
4882
5359
|
}
|
|
4883
5360
|
const manifestAttachments = [];
|
|
4884
5361
|
for (const attachment of attachments) {
|
|
4885
|
-
const absoluteAttachmentPath =
|
|
4886
|
-
await ensureDir(
|
|
4887
|
-
await
|
|
5362
|
+
const absoluteAttachmentPath = path11.join(attachmentsDir, attachment.relativePath);
|
|
5363
|
+
await ensureDir(path11.dirname(absoluteAttachmentPath));
|
|
5364
|
+
await fs10.writeFile(absoluteAttachmentPath, attachment.bytes);
|
|
4888
5365
|
manifestAttachments.push({
|
|
4889
|
-
path: toPosix(
|
|
5366
|
+
path: toPosix(path11.relative(rootDir, absoluteAttachmentPath)),
|
|
4890
5367
|
mimeType: attachment.mimeType,
|
|
4891
5368
|
originalPath: attachment.originalPath
|
|
4892
5369
|
});
|
|
@@ -4902,9 +5379,9 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4902
5379
|
originalPath: prepared.originalPath,
|
|
4903
5380
|
repoRelativePath: prepared.repoRelativePath,
|
|
4904
5381
|
url: prepared.url,
|
|
4905
|
-
storedPath: toPosix(
|
|
4906
|
-
extractedTextPath: extractedTextPath ? toPosix(
|
|
4907
|
-
extractedMetadataPath: extractedMetadataPath ? toPosix(
|
|
5382
|
+
storedPath: toPosix(path11.relative(rootDir, storedPath)),
|
|
5383
|
+
extractedTextPath: extractedTextPath ? toPosix(path11.relative(rootDir, extractedTextPath)) : void 0,
|
|
5384
|
+
extractedMetadataPath: extractedMetadataPath ? toPosix(path11.relative(rootDir, extractedMetadataPath)) : void 0,
|
|
4908
5385
|
extractionHash,
|
|
4909
5386
|
mimeType: prepared.mimeType,
|
|
4910
5387
|
contentHash,
|
|
@@ -4912,7 +5389,7 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4912
5389
|
updatedAt: now,
|
|
4913
5390
|
attachments: manifestAttachments.length ? manifestAttachments : void 0
|
|
4914
5391
|
};
|
|
4915
|
-
await writeJsonFile(
|
|
5392
|
+
await writeJsonFile(path11.join(paths.manifestsDir, `${sourceId}.json`), manifest);
|
|
4916
5393
|
await appendLogEntry(rootDir, "ingest", prepared.title, [
|
|
4917
5394
|
`source_id=${sourceId}`,
|
|
4918
5395
|
`kind=${prepared.sourceKind}`,
|
|
@@ -4930,16 +5407,16 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4930
5407
|
return { manifest, isNew: !previous, wasUpdated: Boolean(previous) };
|
|
4931
5408
|
}
|
|
4932
5409
|
async function removeManifestArtifacts(rootDir, manifest, paths) {
|
|
4933
|
-
await
|
|
4934
|
-
await
|
|
5410
|
+
await fs10.rm(path11.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
|
|
5411
|
+
await fs10.rm(path11.resolve(rootDir, manifest.storedPath), { force: true });
|
|
4935
5412
|
if (manifest.extractedTextPath) {
|
|
4936
|
-
await
|
|
5413
|
+
await fs10.rm(path11.resolve(rootDir, manifest.extractedTextPath), { force: true });
|
|
4937
5414
|
}
|
|
4938
5415
|
if (manifest.extractedMetadataPath) {
|
|
4939
|
-
await
|
|
5416
|
+
await fs10.rm(path11.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
|
|
4940
5417
|
}
|
|
4941
|
-
await
|
|
4942
|
-
await
|
|
5418
|
+
await fs10.rm(path11.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
|
|
5419
|
+
await fs10.rm(path11.join(paths.analysesDir, `${manifest.sourceId}.json`), { force: true });
|
|
4943
5420
|
}
|
|
4944
5421
|
function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
|
|
4945
5422
|
const candidates = [
|
|
@@ -4948,11 +5425,11 @@ function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
|
|
|
4948
5425
|
paths.stateDir,
|
|
4949
5426
|
paths.agentDir,
|
|
4950
5427
|
paths.inboxDir,
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
5428
|
+
path11.join(rootDir, ".claude"),
|
|
5429
|
+
path11.join(rootDir, ".cursor"),
|
|
5430
|
+
path11.join(rootDir, ".obsidian")
|
|
4954
5431
|
];
|
|
4955
|
-
return candidates.map((candidate) =>
|
|
5432
|
+
return candidates.map((candidate) => path11.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, candidate));
|
|
4956
5433
|
}
|
|
4957
5434
|
function preparedMatchesManifest(manifest, prepared, contentHash) {
|
|
4958
5435
|
return manifest.contentHash === contentHash && manifest.extractionHash === (prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact)) && manifest.title === prepared.title && manifest.sourceKind === prepared.sourceKind && manifest.sourceType === prepared.sourceType && manifest.sourceClass === prepared.sourceClass && manifest.language === prepared.language && manifest.mimeType === prepared.mimeType && manifest.repoRelativePath === prepared.repoRelativePath;
|
|
@@ -4974,16 +5451,16 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
4974
5451
|
const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
|
|
4975
5452
|
const manifests = await listManifests(rootDir);
|
|
4976
5453
|
const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
|
|
4977
|
-
(item) =>
|
|
5454
|
+
(item) => path11.resolve(item)
|
|
4978
5455
|
);
|
|
4979
5456
|
const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
|
|
4980
5457
|
const manifestsByRepoRoot = /* @__PURE__ */ new Map();
|
|
4981
5458
|
for (const manifest of manifests) {
|
|
4982
5459
|
const repoRoot = repoRootFromManifest(manifest);
|
|
4983
|
-
if (!repoRoot || !uniqueRoots.includes(
|
|
5460
|
+
if (!repoRoot || !uniqueRoots.includes(path11.resolve(repoRoot))) {
|
|
4984
5461
|
continue;
|
|
4985
5462
|
}
|
|
4986
|
-
const key =
|
|
5463
|
+
const key = path11.resolve(repoRoot);
|
|
4987
5464
|
const bucket = manifestsByRepoRoot.get(key) ?? [];
|
|
4988
5465
|
bucket.push(manifest);
|
|
4989
5466
|
manifestsByRepoRoot.set(key, bucket);
|
|
@@ -5008,14 +5485,14 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
5008
5485
|
skipped.push(
|
|
5009
5486
|
...collected.skipped,
|
|
5010
5487
|
...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
|
|
5011
|
-
path: toPosix(
|
|
5488
|
+
path: toPosix(path11.relative(rootDir, absolutePath)),
|
|
5012
5489
|
reason: "workspace_generated"
|
|
5013
5490
|
}))
|
|
5014
5491
|
);
|
|
5015
5492
|
scannedCount += files.length;
|
|
5016
|
-
const currentPaths = new Set(files.map((absolutePath) =>
|
|
5493
|
+
const currentPaths = new Set(files.map((absolutePath) => path11.resolve(absolutePath)));
|
|
5017
5494
|
for (const absolutePath of files) {
|
|
5018
|
-
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
5495
|
+
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
|
|
5019
5496
|
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
|
|
5020
5497
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
5021
5498
|
if (result.isNew) {
|
|
@@ -5025,7 +5502,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
5025
5502
|
}
|
|
5026
5503
|
}
|
|
5027
5504
|
for (const manifest of repoManifests) {
|
|
5028
|
-
const originalPath = manifest.originalPath ?
|
|
5505
|
+
const originalPath = manifest.originalPath ? path11.resolve(manifest.originalPath) : null;
|
|
5029
5506
|
if (originalPath && !currentPaths.has(originalPath)) {
|
|
5030
5507
|
await removeManifestArtifacts(rootDir, manifest, paths);
|
|
5031
5508
|
removed.push(manifest);
|
|
@@ -5033,7 +5510,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
5033
5510
|
}
|
|
5034
5511
|
}
|
|
5035
5512
|
if (uniqueRoots.length > 0) {
|
|
5036
|
-
await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(
|
|
5513
|
+
await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(path11.relative(rootDir, repoRoot)) || ".").join(","), [
|
|
5037
5514
|
`repo_roots=${uniqueRoots.length}`,
|
|
5038
5515
|
`scanned=${scannedCount}`,
|
|
5039
5516
|
`imported=${imported.length}`,
|
|
@@ -5056,16 +5533,16 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5056
5533
|
const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
|
|
5057
5534
|
const manifests = await listManifests(rootDir);
|
|
5058
5535
|
const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
|
|
5059
|
-
(item) =>
|
|
5536
|
+
(item) => path11.resolve(item)
|
|
5060
5537
|
);
|
|
5061
5538
|
const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
|
|
5062
5539
|
const manifestsByRepoRoot = /* @__PURE__ */ new Map();
|
|
5063
5540
|
for (const manifest of manifests) {
|
|
5064
5541
|
const repoRoot = repoRootFromManifest(manifest);
|
|
5065
|
-
if (!repoRoot || !uniqueRoots.includes(
|
|
5542
|
+
if (!repoRoot || !uniqueRoots.includes(path11.resolve(repoRoot))) {
|
|
5066
5543
|
continue;
|
|
5067
5544
|
}
|
|
5068
|
-
const key =
|
|
5545
|
+
const key = path11.resolve(repoRoot);
|
|
5069
5546
|
const bucket = manifestsByRepoRoot.get(key) ?? [];
|
|
5070
5547
|
bucket.push(manifest);
|
|
5071
5548
|
manifestsByRepoRoot.set(key, bucket);
|
|
@@ -5080,7 +5557,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5080
5557
|
for (const repoRoot of uniqueRoots) {
|
|
5081
5558
|
const repoManifests = manifestsByRepoRoot.get(repoRoot) ?? [];
|
|
5082
5559
|
const manifestsByOriginalPath = new Map(
|
|
5083
|
-
repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [
|
|
5560
|
+
repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [path11.resolve(manifest.originalPath), manifest])
|
|
5084
5561
|
);
|
|
5085
5562
|
if (!await fileExists(repoRoot)) {
|
|
5086
5563
|
for (const manifest of repoManifests) {
|
|
@@ -5088,7 +5565,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5088
5565
|
pendingSemanticRefresh.push({
|
|
5089
5566
|
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? manifest.storedPath),
|
|
5090
5567
|
repoRoot,
|
|
5091
|
-
path: toPosix(
|
|
5568
|
+
path: toPosix(path11.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
|
|
5092
5569
|
changeType: "removed",
|
|
5093
5570
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5094
5571
|
sourceId: manifest.sourceId,
|
|
@@ -5108,17 +5585,17 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5108
5585
|
skipped.push(
|
|
5109
5586
|
...collected.skipped,
|
|
5110
5587
|
...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
|
|
5111
|
-
path: toPosix(
|
|
5588
|
+
path: toPosix(path11.relative(rootDir, absolutePath)),
|
|
5112
5589
|
reason: "workspace_generated"
|
|
5113
5590
|
}))
|
|
5114
5591
|
);
|
|
5115
5592
|
scannedCount += files.length;
|
|
5116
|
-
const currentPaths = new Set(files.map((absolutePath) =>
|
|
5593
|
+
const currentPaths = new Set(files.map((absolutePath) => path11.resolve(absolutePath)));
|
|
5117
5594
|
for (const absolutePath of files) {
|
|
5118
|
-
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
5595
|
+
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
|
|
5119
5596
|
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
|
|
5120
5597
|
if (shouldDeferWatchSemanticRefresh(prepared.sourceKind)) {
|
|
5121
|
-
const existing = manifestsByOriginalPath.get(
|
|
5598
|
+
const existing = manifestsByOriginalPath.get(path11.resolve(absolutePath));
|
|
5122
5599
|
const contentHash = buildCompositeHash(prepared.payloadBytes, prepared.attachments);
|
|
5123
5600
|
const changed = !existing || !preparedMatchesManifest(existing, prepared, contentHash);
|
|
5124
5601
|
if (changed) {
|
|
@@ -5126,10 +5603,10 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5126
5603
|
id: pendingSemanticRefreshId(
|
|
5127
5604
|
existing ? "modified" : "added",
|
|
5128
5605
|
repoRoot,
|
|
5129
|
-
prepared.repoRelativePath ?? toPosix(
|
|
5606
|
+
prepared.repoRelativePath ?? toPosix(path11.relative(repoRoot, absolutePath))
|
|
5130
5607
|
),
|
|
5131
5608
|
repoRoot,
|
|
5132
|
-
path: toPosix(
|
|
5609
|
+
path: toPosix(path11.relative(rootDir, absolutePath)),
|
|
5133
5610
|
changeType: existing ? "modified" : "added",
|
|
5134
5611
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5135
5612
|
sourceId: existing?.sourceId,
|
|
@@ -5149,13 +5626,13 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5149
5626
|
}
|
|
5150
5627
|
}
|
|
5151
5628
|
for (const manifest of repoManifests) {
|
|
5152
|
-
const originalPath = manifest.originalPath ?
|
|
5629
|
+
const originalPath = manifest.originalPath ? path11.resolve(manifest.originalPath) : null;
|
|
5153
5630
|
if (originalPath && !currentPaths.has(originalPath)) {
|
|
5154
5631
|
if (shouldDeferWatchSemanticRefresh(manifest.sourceKind)) {
|
|
5155
5632
|
pendingSemanticRefresh.push({
|
|
5156
|
-
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(
|
|
5633
|
+
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path11.relative(repoRoot, originalPath))),
|
|
5157
5634
|
repoRoot,
|
|
5158
|
-
path: toPosix(
|
|
5635
|
+
path: toPosix(path11.relative(rootDir, originalPath)),
|
|
5159
5636
|
changeType: "removed",
|
|
5160
5637
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5161
5638
|
sourceId: manifest.sourceId,
|
|
@@ -5173,7 +5650,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5173
5650
|
await appendLogEntry(
|
|
5174
5651
|
rootDir,
|
|
5175
5652
|
"sync_repo_watch",
|
|
5176
|
-
uniqueRoots.map((repoRoot) => toPosix(
|
|
5653
|
+
uniqueRoots.map((repoRoot) => toPosix(path11.relative(rootDir, repoRoot)) || ".").join(","),
|
|
5177
5654
|
[
|
|
5178
5655
|
`repo_roots=${uniqueRoots.length}`,
|
|
5179
5656
|
`scanned=${scannedCount}`,
|
|
@@ -5199,17 +5676,17 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5199
5676
|
};
|
|
5200
5677
|
}
|
|
5201
5678
|
async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
5202
|
-
const payloadBytes = await
|
|
5679
|
+
const payloadBytes = await fs10.readFile(absoluteInput);
|
|
5203
5680
|
const mimeType = guessMimeType(absoluteInput);
|
|
5204
5681
|
const sourceKind = inferKind(mimeType, absoluteInput);
|
|
5205
5682
|
const language = inferCodeLanguage(absoluteInput, mimeType);
|
|
5206
|
-
const storedExtension =
|
|
5683
|
+
const storedExtension = path11.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
|
|
5207
5684
|
let title;
|
|
5208
5685
|
let extractedText;
|
|
5209
5686
|
let extractionArtifact;
|
|
5210
5687
|
if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
|
|
5211
5688
|
extractedText = payloadBytes.toString("utf8");
|
|
5212
|
-
title = titleFromText(
|
|
5689
|
+
title = titleFromText(path11.basename(absoluteInput, path11.extname(absoluteInput)), extractedText);
|
|
5213
5690
|
extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
|
|
5214
5691
|
} else if (sourceKind === "html") {
|
|
5215
5692
|
const html = payloadBytes.toString("utf8");
|
|
@@ -5218,18 +5695,18 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
|
5218
5695
|
extractedText = converted.markdown;
|
|
5219
5696
|
extractionArtifact = createHtmlReadabilityExtractionArtifact(sourceKind, mimeType);
|
|
5220
5697
|
} else if (sourceKind === "pdf") {
|
|
5221
|
-
title =
|
|
5698
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
5222
5699
|
const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
|
|
5223
5700
|
extractedText = extracted.extractedText;
|
|
5224
5701
|
extractionArtifact = extracted.artifact;
|
|
5225
5702
|
} else if (sourceKind === "docx") {
|
|
5226
|
-
title =
|
|
5703
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
5227
5704
|
const extracted = await extractDocxText({ mimeType, bytes: payloadBytes });
|
|
5228
5705
|
title = extracted.artifact.metadata?.title?.trim() || title;
|
|
5229
5706
|
extractedText = extracted.extractedText;
|
|
5230
5707
|
extractionArtifact = extracted.artifact;
|
|
5231
5708
|
} else if (sourceKind === "image") {
|
|
5232
|
-
title =
|
|
5709
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
5233
5710
|
const extracted = await extractImageWithVision(rootDir, {
|
|
5234
5711
|
title,
|
|
5235
5712
|
mimeType,
|
|
@@ -5239,7 +5716,7 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
|
5239
5716
|
extractedText = extracted.extractedText;
|
|
5240
5717
|
extractionArtifact = extracted.artifact;
|
|
5241
5718
|
} else {
|
|
5242
|
-
title =
|
|
5719
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
5243
5720
|
}
|
|
5244
5721
|
return {
|
|
5245
5722
|
title,
|
|
@@ -5258,6 +5735,7 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
|
5258
5735
|
};
|
|
5259
5736
|
}
|
|
5260
5737
|
async function prepareUrlInput(rootDir, input, options) {
|
|
5738
|
+
await validateUrlSafety(input);
|
|
5261
5739
|
const response = await fetch(input);
|
|
5262
5740
|
if (!response.ok) {
|
|
5263
5741
|
throw new Error(`Failed to fetch ${input}: ${response.status} ${response.statusText}`);
|
|
@@ -5315,7 +5793,7 @@ async function prepareUrlInput(rootDir, input, options) {
|
|
|
5315
5793
|
sourceKind = "markdown";
|
|
5316
5794
|
storedExtension = ".md";
|
|
5317
5795
|
} else {
|
|
5318
|
-
const extension =
|
|
5796
|
+
const extension = path11.extname(inputUrl.pathname);
|
|
5319
5797
|
storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
|
|
5320
5798
|
if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
|
|
5321
5799
|
extractedText = payloadBytes.toString("utf8");
|
|
@@ -5386,14 +5864,14 @@ async function collectInboxAttachmentRefs(inputDir, files) {
|
|
|
5386
5864
|
if (sourceKind !== "markdown" && sourceKind !== "html") {
|
|
5387
5865
|
continue;
|
|
5388
5866
|
}
|
|
5389
|
-
const content = await
|
|
5867
|
+
const content = await fs10.readFile(absolutePath, "utf8");
|
|
5390
5868
|
const refs = sourceKind === "html" ? extractHtmlLocalReferences(content, pathToFileURL(absolutePath).toString()) : extractMarkdownReferences(content);
|
|
5391
5869
|
if (!refs.length) {
|
|
5392
5870
|
continue;
|
|
5393
5871
|
}
|
|
5394
5872
|
const sourceRefs = [];
|
|
5395
5873
|
for (const ref of refs) {
|
|
5396
|
-
const resolved =
|
|
5874
|
+
const resolved = path11.resolve(path11.dirname(absolutePath), ref);
|
|
5397
5875
|
if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
|
|
5398
5876
|
continue;
|
|
5399
5877
|
}
|
|
@@ -5427,12 +5905,12 @@ function rewriteMarkdownReferences(content, replacements) {
|
|
|
5427
5905
|
});
|
|
5428
5906
|
}
|
|
5429
5907
|
async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
5430
|
-
const originalBytes = await
|
|
5908
|
+
const originalBytes = await fs10.readFile(absolutePath);
|
|
5431
5909
|
const originalText = originalBytes.toString("utf8");
|
|
5432
|
-
const title = titleFromText(
|
|
5910
|
+
const title = titleFromText(path11.basename(absolutePath, path11.extname(absolutePath)), originalText);
|
|
5433
5911
|
const attachments = [];
|
|
5434
5912
|
for (const attachmentRef of attachmentRefs) {
|
|
5435
|
-
const bytes = await
|
|
5913
|
+
const bytes = await fs10.readFile(attachmentRef.absolutePath);
|
|
5436
5914
|
attachments.push({
|
|
5437
5915
|
relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
|
|
5438
5916
|
mimeType: guessMimeType(attachmentRef.absolutePath),
|
|
@@ -5456,7 +5934,7 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
|
5456
5934
|
sourceKind: "markdown",
|
|
5457
5935
|
originalPath: toPosix(absolutePath),
|
|
5458
5936
|
mimeType: "text/markdown",
|
|
5459
|
-
storedExtension:
|
|
5937
|
+
storedExtension: path11.extname(absolutePath) || ".md",
|
|
5460
5938
|
payloadBytes: Buffer.from(rewrittenText, "utf8"),
|
|
5461
5939
|
extractedText: rewrittenText,
|
|
5462
5940
|
extractionArtifact,
|
|
@@ -5466,12 +5944,12 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
|
5466
5944
|
};
|
|
5467
5945
|
}
|
|
5468
5946
|
async function prepareInboxHtmlInput(absolutePath, attachmentRefs) {
|
|
5469
|
-
const originalBytes = await
|
|
5947
|
+
const originalBytes = await fs10.readFile(absolutePath);
|
|
5470
5948
|
const originalHtml = originalBytes.toString("utf8");
|
|
5471
5949
|
const initialConversion = await convertHtmlToMarkdown(originalHtml, pathToFileURL(absolutePath).toString());
|
|
5472
5950
|
const attachments = [];
|
|
5473
5951
|
for (const attachmentRef of attachmentRefs) {
|
|
5474
|
-
const bytes = await
|
|
5952
|
+
const bytes = await fs10.readFile(attachmentRef.absolutePath);
|
|
5475
5953
|
attachments.push({
|
|
5476
5954
|
relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
|
|
5477
5955
|
mimeType: guessMimeType(attachmentRef.absolutePath),
|
|
@@ -5480,7 +5958,7 @@ async function prepareInboxHtmlInput(absolutePath, attachmentRefs) {
|
|
|
5480
5958
|
});
|
|
5481
5959
|
}
|
|
5482
5960
|
const contentHash = buildCompositeHash(originalBytes, attachments);
|
|
5483
|
-
const fallbackTitle =
|
|
5961
|
+
const fallbackTitle = path11.basename(absolutePath, path11.extname(absolutePath));
|
|
5484
5962
|
const title = initialConversion.title || fallbackTitle;
|
|
5485
5963
|
const sourceId = `${slugify(title)}-${contentHash.slice(0, 8)}`;
|
|
5486
5964
|
const replacements = new Map(
|
|
@@ -5498,7 +5976,7 @@ async function prepareInboxHtmlInput(absolutePath, attachmentRefs) {
|
|
|
5498
5976
|
sourceKind: "html",
|
|
5499
5977
|
originalPath: toPosix(absolutePath),
|
|
5500
5978
|
mimeType: "text/html",
|
|
5501
|
-
storedExtension:
|
|
5979
|
+
storedExtension: path11.extname(absolutePath) || ".html",
|
|
5502
5980
|
payloadBytes: Buffer.from(rewrittenHtml, "utf8"),
|
|
5503
5981
|
extractedText: converted.markdown,
|
|
5504
5982
|
extractionArtifact,
|
|
@@ -5513,8 +5991,8 @@ function isSupportedInboxKind(sourceKind) {
|
|
|
5513
5991
|
async function ingestInput(rootDir, input, options) {
|
|
5514
5992
|
const { paths } = await initWorkspace(rootDir);
|
|
5515
5993
|
const normalizedOptions = normalizeIngestOptions(options);
|
|
5516
|
-
const absoluteInput =
|
|
5517
|
-
const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ??
|
|
5994
|
+
const absoluteInput = path11.resolve(rootDir, input);
|
|
5995
|
+
const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ?? path11.dirname(absoluteInput));
|
|
5518
5996
|
const prepared = isHttpUrl(input) ? await prepareUrlInput(rootDir, input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
|
|
5519
5997
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
5520
5998
|
return result.manifest;
|
|
@@ -5604,7 +6082,7 @@ async function addInput(rootDir, input, options = {}) {
|
|
|
5604
6082
|
async function ingestDirectory(rootDir, inputDir, options) {
|
|
5605
6083
|
const { paths } = await initWorkspace(rootDir);
|
|
5606
6084
|
const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
|
|
5607
|
-
const absoluteInputDir =
|
|
6085
|
+
const absoluteInputDir = path11.resolve(rootDir, inputDir);
|
|
5608
6086
|
const repoRoot = normalizedOptions.repoRoot ?? await findNearestGitRoot2(absoluteInputDir) ?? absoluteInputDir;
|
|
5609
6087
|
if (!await fileExists(absoluteInputDir)) {
|
|
5610
6088
|
throw new Error(`Directory not found: ${absoluteInputDir}`);
|
|
@@ -5613,7 +6091,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
5613
6091
|
const imported = [];
|
|
5614
6092
|
const updated = [];
|
|
5615
6093
|
for (const absolutePath of files) {
|
|
5616
|
-
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
6094
|
+
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
|
|
5617
6095
|
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
|
|
5618
6096
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
5619
6097
|
if (result.isNew) {
|
|
@@ -5621,11 +6099,11 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
5621
6099
|
} else if (result.wasUpdated) {
|
|
5622
6100
|
updated.push(result.manifest);
|
|
5623
6101
|
} else {
|
|
5624
|
-
skipped.push({ path: toPosix(
|
|
6102
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "duplicate_content" });
|
|
5625
6103
|
}
|
|
5626
6104
|
}
|
|
5627
|
-
await appendLogEntry(rootDir, "ingest_directory", toPosix(
|
|
5628
|
-
`repo_root=${toPosix(
|
|
6105
|
+
await appendLogEntry(rootDir, "ingest_directory", toPosix(path11.relative(rootDir, absoluteInputDir)) || ".", [
|
|
6106
|
+
`repo_root=${toPosix(path11.relative(rootDir, repoRoot)) || "."}`,
|
|
5629
6107
|
`scanned=${files.length}`,
|
|
5630
6108
|
`imported=${imported.length}`,
|
|
5631
6109
|
`updated=${updated.length}`,
|
|
@@ -5642,7 +6120,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
5642
6120
|
}
|
|
5643
6121
|
async function importInbox(rootDir, inputDir) {
|
|
5644
6122
|
const { paths } = await initWorkspace(rootDir);
|
|
5645
|
-
const effectiveInputDir =
|
|
6123
|
+
const effectiveInputDir = path11.resolve(rootDir, inputDir ?? paths.inboxDir);
|
|
5646
6124
|
if (!await fileExists(effectiveInputDir)) {
|
|
5647
6125
|
throw new Error(`Inbox directory not found: ${effectiveInputDir}`);
|
|
5648
6126
|
}
|
|
@@ -5653,31 +6131,31 @@ async function importInbox(rootDir, inputDir) {
|
|
|
5653
6131
|
const skipped = [];
|
|
5654
6132
|
let attachmentCount = 0;
|
|
5655
6133
|
for (const absolutePath of files) {
|
|
5656
|
-
const basename =
|
|
6134
|
+
const basename = path11.basename(absolutePath);
|
|
5657
6135
|
if (basename.startsWith(".")) {
|
|
5658
|
-
skipped.push({ path: toPosix(
|
|
6136
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "hidden_file" });
|
|
5659
6137
|
continue;
|
|
5660
6138
|
}
|
|
5661
6139
|
if (claimedAttachments.has(absolutePath)) {
|
|
5662
|
-
skipped.push({ path: toPosix(
|
|
6140
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
|
|
5663
6141
|
continue;
|
|
5664
6142
|
}
|
|
5665
6143
|
const mimeType = guessMimeType(absolutePath);
|
|
5666
6144
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
5667
6145
|
if (!isSupportedInboxKind(sourceKind)) {
|
|
5668
|
-
skipped.push({ path: toPosix(
|
|
6146
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
5669
6147
|
continue;
|
|
5670
6148
|
}
|
|
5671
6149
|
const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : sourceKind === "html" && refsBySource.has(absolutePath) ? await prepareInboxHtmlInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
|
|
5672
6150
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
5673
6151
|
if (!result.isNew) {
|
|
5674
|
-
skipped.push({ path: toPosix(
|
|
6152
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "duplicate_content" });
|
|
5675
6153
|
continue;
|
|
5676
6154
|
}
|
|
5677
6155
|
attachmentCount += result.manifest.attachments?.length ?? 0;
|
|
5678
6156
|
imported.push(result.manifest);
|
|
5679
6157
|
}
|
|
5680
|
-
await appendLogEntry(rootDir, "inbox_import", toPosix(
|
|
6158
|
+
await appendLogEntry(rootDir, "inbox_import", toPosix(path11.relative(rootDir, effectiveInputDir)) || ".", [
|
|
5681
6159
|
`scanned=${files.length}`,
|
|
5682
6160
|
`imported=${imported.length}`,
|
|
5683
6161
|
`attachments=${attachmentCount}`,
|
|
@@ -5696,9 +6174,9 @@ async function listManifests(rootDir) {
|
|
|
5696
6174
|
if (!await fileExists(paths.manifestsDir)) {
|
|
5697
6175
|
return [];
|
|
5698
6176
|
}
|
|
5699
|
-
const entries = await
|
|
6177
|
+
const entries = await fs10.readdir(paths.manifestsDir);
|
|
5700
6178
|
const manifests = await Promise.all(
|
|
5701
|
-
entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(
|
|
6179
|
+
entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path11.join(paths.manifestsDir, entry)))
|
|
5702
6180
|
);
|
|
5703
6181
|
return manifests.filter((manifest) => Boolean(manifest));
|
|
5704
6182
|
}
|
|
@@ -5706,17 +6184,17 @@ async function readExtractedText(rootDir, manifest) {
|
|
|
5706
6184
|
if (!manifest.extractedTextPath) {
|
|
5707
6185
|
return void 0;
|
|
5708
6186
|
}
|
|
5709
|
-
const absolutePath =
|
|
6187
|
+
const absolutePath = path11.resolve(rootDir, manifest.extractedTextPath);
|
|
5710
6188
|
if (!await fileExists(absolutePath)) {
|
|
5711
6189
|
return void 0;
|
|
5712
6190
|
}
|
|
5713
|
-
return
|
|
6191
|
+
return fs10.readFile(absolutePath, "utf8");
|
|
5714
6192
|
}
|
|
5715
6193
|
async function readExtractionArtifact(rootDir, manifest) {
|
|
5716
6194
|
if (!manifest.extractedMetadataPath) {
|
|
5717
6195
|
return void 0;
|
|
5718
6196
|
}
|
|
5719
|
-
const absolutePath =
|
|
6197
|
+
const absolutePath = path11.resolve(rootDir, manifest.extractedMetadataPath);
|
|
5720
6198
|
if (!await fileExists(absolutePath)) {
|
|
5721
6199
|
return void 0;
|
|
5722
6200
|
}
|
|
@@ -5724,20 +6202,20 @@ async function readExtractionArtifact(rootDir, manifest) {
|
|
|
5724
6202
|
}
|
|
5725
6203
|
|
|
5726
6204
|
// src/mcp.ts
|
|
5727
|
-
import
|
|
5728
|
-
import
|
|
6205
|
+
import fs18 from "fs/promises";
|
|
6206
|
+
import path22 from "path";
|
|
5729
6207
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5730
6208
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5731
6209
|
import { z as z8 } from "zod";
|
|
5732
6210
|
|
|
5733
6211
|
// src/schema.ts
|
|
5734
|
-
import
|
|
5735
|
-
import
|
|
6212
|
+
import fs11 from "fs/promises";
|
|
6213
|
+
import path12 from "path";
|
|
5736
6214
|
function normalizeSchemaContent(content) {
|
|
5737
6215
|
return content.trim() ? content.trim() : defaultVaultSchema().trim();
|
|
5738
6216
|
}
|
|
5739
6217
|
async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
|
|
5740
|
-
const content = await fileExists(schemaPath) ? await
|
|
6218
|
+
const content = await fileExists(schemaPath) ? await fs11.readFile(schemaPath, "utf8") : fallback;
|
|
5741
6219
|
const normalized = normalizeSchemaContent(content);
|
|
5742
6220
|
return {
|
|
5743
6221
|
path: schemaPath,
|
|
@@ -5746,7 +6224,7 @@ async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
|
|
|
5746
6224
|
};
|
|
5747
6225
|
}
|
|
5748
6226
|
function resolveProjectSchemaPath(rootDir, schemaPath) {
|
|
5749
|
-
return
|
|
6227
|
+
return path12.resolve(rootDir, schemaPath);
|
|
5750
6228
|
}
|
|
5751
6229
|
function composeVaultSchema(root, projectSchemas = []) {
|
|
5752
6230
|
if (!projectSchemas.length) {
|
|
@@ -5762,7 +6240,7 @@ function composeVaultSchema(root, projectSchemas = []) {
|
|
|
5762
6240
|
(schema) => [
|
|
5763
6241
|
`## Project Schema`,
|
|
5764
6242
|
"",
|
|
5765
|
-
`Path: ${toPosix(
|
|
6243
|
+
`Path: ${toPosix(path12.relative(path12.dirname(root.path), schema.path) || schema.path)}`,
|
|
5766
6244
|
"",
|
|
5767
6245
|
schema.content
|
|
5768
6246
|
].join("\n")
|
|
@@ -5838,15 +6316,15 @@ function buildSchemaPrompt(schema, instruction) {
|
|
|
5838
6316
|
}
|
|
5839
6317
|
|
|
5840
6318
|
// src/vault.ts
|
|
5841
|
-
import
|
|
5842
|
-
import
|
|
6319
|
+
import fs17 from "fs/promises";
|
|
6320
|
+
import path21 from "path";
|
|
5843
6321
|
import matter9 from "gray-matter";
|
|
5844
6322
|
import { z as z7 } from "zod";
|
|
5845
6323
|
|
|
5846
6324
|
// src/analysis.ts
|
|
5847
|
-
import
|
|
6325
|
+
import path13 from "path";
|
|
5848
6326
|
import { z as z2 } from "zod";
|
|
5849
|
-
var ANALYSIS_FORMAT_VERSION =
|
|
6327
|
+
var ANALYSIS_FORMAT_VERSION = 6;
|
|
5850
6328
|
var sourceAnalysisSchema = z2.object({
|
|
5851
6329
|
title: z2.string().min(1),
|
|
5852
6330
|
summary: z2.string().min(1),
|
|
@@ -5861,7 +6339,8 @@ var sourceAnalysisSchema = z2.object({
|
|
|
5861
6339
|
citation: z2.string().min(1)
|
|
5862
6340
|
})
|
|
5863
6341
|
).max(8).default([]),
|
|
5864
|
-
questions: z2.array(z2.string()).max(6).default([])
|
|
6342
|
+
questions: z2.array(z2.string()).max(6).default([]),
|
|
6343
|
+
tags: z2.array(z2.string()).max(5).default([])
|
|
5865
6344
|
});
|
|
5866
6345
|
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
5867
6346
|
"about",
|
|
@@ -5965,6 +6444,7 @@ function heuristicAnalysis(manifest, text, schemaHash) {
|
|
|
5965
6444
|
citation: manifest.sourceId
|
|
5966
6445
|
})),
|
|
5967
6446
|
questions: concepts.slice(0, 3).map((term) => `How does ${term.name} relate to ${manifest.title}?`),
|
|
6447
|
+
tags: [],
|
|
5968
6448
|
rationales: [],
|
|
5969
6449
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5970
6450
|
};
|
|
@@ -5977,6 +6457,8 @@ async function providerAnalysis(manifest, text, provider, schema) {
|
|
|
5977
6457
|
"",
|
|
5978
6458
|
"Follow the vault schema when choosing titles, categories, relationships, and summaries.",
|
|
5979
6459
|
"",
|
|
6460
|
+
"Return up to 5 broad domain tags that categorize this source. Tags should be lowercase kebab-case (e.g., cryptography, distributed-systems, machine-learning). These are broader categories, not specific concepts or entity names.",
|
|
6461
|
+
"",
|
|
5980
6462
|
`Vault schema path: ${schema.path}`,
|
|
5981
6463
|
"",
|
|
5982
6464
|
"Vault schema instructions:",
|
|
@@ -6020,6 +6502,7 @@ ${truncate(text, 18e3)}`
|
|
|
6020
6502
|
citation: claim.citation
|
|
6021
6503
|
})),
|
|
6022
6504
|
questions: parsed.questions,
|
|
6505
|
+
tags: parsed.tags,
|
|
6023
6506
|
rationales: [],
|
|
6024
6507
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6025
6508
|
};
|
|
@@ -6055,6 +6538,7 @@ function analysisFromVisionExtraction(manifest, extraction, schemaHash) {
|
|
|
6055
6538
|
citation: manifest.sourceId
|
|
6056
6539
|
})),
|
|
6057
6540
|
questions: extraction.vision.questions,
|
|
6541
|
+
tags: [],
|
|
6058
6542
|
rationales: [],
|
|
6059
6543
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6060
6544
|
};
|
|
@@ -6067,7 +6551,7 @@ function extractionWarningSummary(manifest, extraction) {
|
|
|
6067
6551
|
return `Imported ${manifest.sourceKind} source. Text extraction is not yet available for this source.`;
|
|
6068
6552
|
}
|
|
6069
6553
|
async function analyzeSource(manifest, extractedText, provider, paths, schema) {
|
|
6070
|
-
const cachePath =
|
|
6554
|
+
const cachePath = path13.join(paths.analysesDir, `${manifest.sourceId}.json`);
|
|
6071
6555
|
const cached = await readJsonFile(cachePath);
|
|
6072
6556
|
if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
|
|
6073
6557
|
return cached;
|
|
@@ -6094,6 +6578,7 @@ async function analyzeSource(manifest, extractedText, provider, paths, schema) {
|
|
|
6094
6578
|
entities: [],
|
|
6095
6579
|
claims: [],
|
|
6096
6580
|
questions: [],
|
|
6581
|
+
tags: [],
|
|
6097
6582
|
rationales: [],
|
|
6098
6583
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6099
6584
|
};
|
|
@@ -6119,6 +6604,7 @@ async function analyzeSource(manifest, extractedText, provider, paths, schema) {
|
|
|
6119
6604
|
entities: [],
|
|
6120
6605
|
claims: [],
|
|
6121
6606
|
questions: [],
|
|
6607
|
+
tags: [],
|
|
6122
6608
|
rationales: [],
|
|
6123
6609
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6124
6610
|
};
|
|
@@ -6138,163 +6624,6 @@ function analysisSignature(analysis) {
|
|
|
6138
6624
|
return sha256(JSON.stringify(analysis));
|
|
6139
6625
|
}
|
|
6140
6626
|
|
|
6141
|
-
// src/benchmark.ts
|
|
6142
|
-
var CHARS_PER_TOKEN = 4;
|
|
6143
|
-
var DEFAULT_BENCHMARK_QUESTIONS = [
|
|
6144
|
-
"How does this vault connect the main concepts?",
|
|
6145
|
-
"Which pages bridge the biggest communities?",
|
|
6146
|
-
"What are the core abstractions in this vault?",
|
|
6147
|
-
"Where are the biggest knowledge gaps?",
|
|
6148
|
-
"What evidence should I read first?"
|
|
6149
|
-
];
|
|
6150
|
-
var RESEARCH_BENCHMARK_QUESTION = "Which research sources should I read first, and why?";
|
|
6151
|
-
function nodeMap(graph) {
|
|
6152
|
-
return new Map(graph.nodes.map((node) => [node.id, node]));
|
|
6153
|
-
}
|
|
6154
|
-
function pageMap(graph) {
|
|
6155
|
-
return new Map(graph.pages.map((page) => [page.id, page]));
|
|
6156
|
-
}
|
|
6157
|
-
function estimateTokens(text) {
|
|
6158
|
-
return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));
|
|
6159
|
-
}
|
|
6160
|
-
function estimateCorpusWords(texts) {
|
|
6161
|
-
return texts.reduce((total, text) => total + normalizeWhitespace(text).split(/\s+/).filter(Boolean).length, 0);
|
|
6162
|
-
}
|
|
6163
|
-
function benchmarkQueryTokens(graph, queryResult, pageContentsById) {
|
|
6164
|
-
const nodesById = nodeMap(graph);
|
|
6165
|
-
const pagesById = pageMap(graph);
|
|
6166
|
-
const edgeIds = new Set(queryResult.visitedEdgeIds);
|
|
6167
|
-
const lines = [];
|
|
6168
|
-
for (const pageId of queryResult.pageIds) {
|
|
6169
|
-
const page = pagesById.get(pageId);
|
|
6170
|
-
if (!page) {
|
|
6171
|
-
continue;
|
|
6172
|
-
}
|
|
6173
|
-
const content = normalizeWhitespace(pageContentsById.get(pageId) ?? "").slice(0, 280);
|
|
6174
|
-
lines.push(`PAGE ${page.title} path=${page.path} kind=${page.kind}`);
|
|
6175
|
-
if (content) {
|
|
6176
|
-
lines.push(`PAGE_BODY ${content}`);
|
|
6177
|
-
}
|
|
6178
|
-
}
|
|
6179
|
-
for (const nodeId of queryResult.visitedNodeIds) {
|
|
6180
|
-
const node = nodesById.get(nodeId);
|
|
6181
|
-
if (!node) {
|
|
6182
|
-
continue;
|
|
6183
|
-
}
|
|
6184
|
-
lines.push(`NODE ${node.label} type=${node.type} community=${node.communityId ?? "unassigned"} page=${node.pageId ?? "none"}`);
|
|
6185
|
-
}
|
|
6186
|
-
for (const edge of graph.edges) {
|
|
6187
|
-
if (!edgeIds.has(edge.id)) {
|
|
6188
|
-
continue;
|
|
6189
|
-
}
|
|
6190
|
-
const source = nodesById.get(edge.source)?.label ?? edge.source;
|
|
6191
|
-
const target = nodesById.get(edge.target)?.label ?? edge.target;
|
|
6192
|
-
lines.push(`EDGE ${source} --${edge.relation}/${edge.evidenceClass}/${edge.confidence.toFixed(2)}--> ${target}`);
|
|
6193
|
-
}
|
|
6194
|
-
const queryTokens = estimateTokens(lines.join("\n"));
|
|
6195
|
-
return {
|
|
6196
|
-
question: queryResult.question,
|
|
6197
|
-
queryTokens,
|
|
6198
|
-
reduction: 0,
|
|
6199
|
-
visitedNodeIds: queryResult.visitedNodeIds,
|
|
6200
|
-
visitedEdgeIds: queryResult.visitedEdgeIds,
|
|
6201
|
-
pageIds: queryResult.pageIds
|
|
6202
|
-
};
|
|
6203
|
-
}
|
|
6204
|
-
function graphHash(graph) {
|
|
6205
|
-
const hashedPages = graph.pages.filter((page) => page.kind !== "graph_report" && page.kind !== "community_summary");
|
|
6206
|
-
const normalized = JSON.stringify(
|
|
6207
|
-
{
|
|
6208
|
-
nodes: [...graph.nodes].map((node) => ({
|
|
6209
|
-
id: node.id,
|
|
6210
|
-
type: node.type,
|
|
6211
|
-
label: node.label,
|
|
6212
|
-
pageId: node.pageId ?? null,
|
|
6213
|
-
sourceClass: node.sourceClass ?? null,
|
|
6214
|
-
communityId: node.communityId ?? null,
|
|
6215
|
-
degree: node.degree ?? null,
|
|
6216
|
-
bridgeScore: node.bridgeScore ?? null,
|
|
6217
|
-
isGodNode: node.isGodNode ?? false,
|
|
6218
|
-
sourceIds: [...node.sourceIds].sort(),
|
|
6219
|
-
projectIds: [...node.projectIds].sort()
|
|
6220
|
-
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
6221
|
-
edges: [...graph.edges].map((edge) => ({
|
|
6222
|
-
id: edge.id,
|
|
6223
|
-
source: edge.source,
|
|
6224
|
-
target: edge.target,
|
|
6225
|
-
relation: edge.relation,
|
|
6226
|
-
status: edge.status,
|
|
6227
|
-
evidenceClass: edge.evidenceClass,
|
|
6228
|
-
similarityBasis: edge.similarityBasis ?? null,
|
|
6229
|
-
confidence: edge.confidence,
|
|
6230
|
-
provenance: [...edge.provenance].sort()
|
|
6231
|
-
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
6232
|
-
pages: [...hashedPages].map((page) => ({
|
|
6233
|
-
id: page.id,
|
|
6234
|
-
path: page.path,
|
|
6235
|
-
kind: page.kind,
|
|
6236
|
-
status: page.status,
|
|
6237
|
-
sourceType: page.sourceType ?? null,
|
|
6238
|
-
sourceClass: page.sourceClass ?? null,
|
|
6239
|
-
sourceIds: [...page.sourceIds].sort(),
|
|
6240
|
-
projectIds: [...page.projectIds].sort(),
|
|
6241
|
-
nodeIds: [...page.nodeIds].sort()
|
|
6242
|
-
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
6243
|
-
communities: [...graph.communities ?? []].map((community) => ({
|
|
6244
|
-
id: community.id,
|
|
6245
|
-
label: community.label,
|
|
6246
|
-
nodeIds: [...community.nodeIds].sort()
|
|
6247
|
-
})).sort((left, right) => left.id.localeCompare(right.id))
|
|
6248
|
-
},
|
|
6249
|
-
null,
|
|
6250
|
-
0
|
|
6251
|
-
);
|
|
6252
|
-
return sha256(normalized);
|
|
6253
|
-
}
|
|
6254
|
-
function hasResearchSources(pages) {
|
|
6255
|
-
return pages.some((page) => page.kind === "source" && Boolean(page.sourceType) && page.sourceType !== "url");
|
|
6256
|
-
}
|
|
6257
|
-
function defaultBenchmarkQuestionsForGraph(graph, maxQuestions = 3) {
|
|
6258
|
-
const normalizedLimit = Math.max(1, Math.min(maxQuestions, DEFAULT_BENCHMARK_QUESTIONS.length));
|
|
6259
|
-
const questions = [...DEFAULT_BENCHMARK_QUESTIONS];
|
|
6260
|
-
if (hasResearchSources(graph.pages)) {
|
|
6261
|
-
questions.unshift(RESEARCH_BENCHMARK_QUESTION);
|
|
6262
|
-
}
|
|
6263
|
-
return uniqueBy(questions, (item) => item).slice(0, normalizedLimit);
|
|
6264
|
-
}
|
|
6265
|
-
function buildBenchmarkArtifact(input) {
|
|
6266
|
-
const corpusTokens = Math.max(1, Math.round(input.corpusWords * (100 / 75)));
|
|
6267
|
-
const perQuestion = input.perQuestion.filter((entry) => entry.queryTokens > 0).map((entry) => ({
|
|
6268
|
-
...entry,
|
|
6269
|
-
reduction: Number(Math.max(0, 1 - entry.queryTokens / Math.max(1, corpusTokens)).toFixed(3))
|
|
6270
|
-
}));
|
|
6271
|
-
const avgQueryTokens = perQuestion.length ? Math.max(1, Math.round(perQuestion.reduce((total, entry) => total + entry.queryTokens, 0) / perQuestion.length)) : 0;
|
|
6272
|
-
const reductionRatio = avgQueryTokens ? Number(Math.max(0, 1 - avgQueryTokens / Math.max(1, corpusTokens)).toFixed(3)) : 0;
|
|
6273
|
-
const uniqueVisitedNodes = new Set(perQuestion.flatMap((entry) => entry.visitedNodeIds)).size;
|
|
6274
|
-
const summary = {
|
|
6275
|
-
questionCount: input.questions.length,
|
|
6276
|
-
uniqueVisitedNodes,
|
|
6277
|
-
finalContextTokens: avgQueryTokens,
|
|
6278
|
-
naiveCorpusTokens: corpusTokens,
|
|
6279
|
-
avgReduction: reductionRatio,
|
|
6280
|
-
reductionRatio
|
|
6281
|
-
};
|
|
6282
|
-
return {
|
|
6283
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6284
|
-
graphHash: graphHash(input.graph),
|
|
6285
|
-
corpusWords: input.corpusWords,
|
|
6286
|
-
corpusTokens,
|
|
6287
|
-
nodes: input.graph.nodes.length,
|
|
6288
|
-
edges: input.graph.edges.length,
|
|
6289
|
-
avgQueryTokens,
|
|
6290
|
-
reductionRatio,
|
|
6291
|
-
sampleQuestions: input.questions,
|
|
6292
|
-
perQuestion,
|
|
6293
|
-
questionResults: perQuestion,
|
|
6294
|
-
summary
|
|
6295
|
-
};
|
|
6296
|
-
}
|
|
6297
|
-
|
|
6298
6627
|
// src/confidence.ts
|
|
6299
6628
|
function nodeConfidence(sourceCount) {
|
|
6300
6629
|
return Math.min(0.5 + sourceCount * 0.15, 0.95);
|
|
@@ -6312,8 +6641,8 @@ function conflictConfidence(claimA, claimB) {
|
|
|
6312
6641
|
}
|
|
6313
6642
|
|
|
6314
6643
|
// src/deep-lint.ts
|
|
6315
|
-
import
|
|
6316
|
-
import
|
|
6644
|
+
import fs12 from "fs/promises";
|
|
6645
|
+
import path16 from "path";
|
|
6317
6646
|
import matter4 from "gray-matter";
|
|
6318
6647
|
import { z as z5 } from "zod";
|
|
6319
6648
|
|
|
@@ -6334,7 +6663,7 @@ function normalizeFindingSeverity(value) {
|
|
|
6334
6663
|
|
|
6335
6664
|
// src/orchestration.ts
|
|
6336
6665
|
import { spawn } from "child_process";
|
|
6337
|
-
import
|
|
6666
|
+
import path14 from "path";
|
|
6338
6667
|
import { z as z3 } from "zod";
|
|
6339
6668
|
var orchestrationRoleResultSchema = z3.object({
|
|
6340
6669
|
summary: z3.string().optional(),
|
|
@@ -6427,7 +6756,7 @@ async function runProviderRole(rootDir, role, roleConfig, input) {
|
|
|
6427
6756
|
}
|
|
6428
6757
|
async function runCommandRole(rootDir, role, executor, input) {
|
|
6429
6758
|
const [command, ...args] = executor.command;
|
|
6430
|
-
const cwd = executor.cwd ?
|
|
6759
|
+
const cwd = executor.cwd ? path14.resolve(rootDir, executor.cwd) : rootDir;
|
|
6431
6760
|
const child = spawn(command, args, {
|
|
6432
6761
|
cwd,
|
|
6433
6762
|
env: {
|
|
@@ -6521,7 +6850,7 @@ function summarizeRoleQuestions(results) {
|
|
|
6521
6850
|
}
|
|
6522
6851
|
|
|
6523
6852
|
// src/web-search/registry.ts
|
|
6524
|
-
import
|
|
6853
|
+
import path15 from "path";
|
|
6525
6854
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
6526
6855
|
import { z as z4 } from "zod";
|
|
6527
6856
|
|
|
@@ -6619,7 +6948,7 @@ async function createWebSearchAdapter(id, config, rootDir) {
|
|
|
6619
6948
|
if (!config.module) {
|
|
6620
6949
|
throw new Error(`Web search provider ${id} is type "custom" but no module path was configured.`);
|
|
6621
6950
|
}
|
|
6622
|
-
const resolvedModule =
|
|
6951
|
+
const resolvedModule = path15.isAbsolute(config.module) ? config.module : path15.resolve(rootDir, config.module);
|
|
6623
6952
|
const loaded = await import(pathToFileURL2(resolvedModule).href);
|
|
6624
6953
|
const parsed = customWebSearchModuleSchema.parse(loaded);
|
|
6625
6954
|
return parsed.createAdapter(id, config, rootDir);
|
|
@@ -6647,7 +6976,14 @@ var deepLintResponseSchema = z5.object({
|
|
|
6647
6976
|
findings: z5.array(
|
|
6648
6977
|
z5.object({
|
|
6649
6978
|
severity: z5.string().optional().default("info"),
|
|
6650
|
-
code: z5.enum([
|
|
6979
|
+
code: z5.enum([
|
|
6980
|
+
"coverage_gap",
|
|
6981
|
+
"contradiction_candidate",
|
|
6982
|
+
"contradiction",
|
|
6983
|
+
"missing_citation",
|
|
6984
|
+
"candidate_page",
|
|
6985
|
+
"follow_up_question"
|
|
6986
|
+
]),
|
|
6651
6987
|
message: z5.string().min(1),
|
|
6652
6988
|
relatedSourceIds: z5.array(z5.string()).default([]),
|
|
6653
6989
|
relatedPageIds: z5.array(z5.string()).default([]),
|
|
@@ -6679,8 +7015,8 @@ async function loadContextPages(rootDir, graph) {
|
|
|
6679
7015
|
);
|
|
6680
7016
|
return Promise.all(
|
|
6681
7017
|
contextPages.slice(0, 18).map(async (page) => {
|
|
6682
|
-
const absolutePath =
|
|
6683
|
-
const raw = await
|
|
7018
|
+
const absolutePath = path16.join(paths.wikiDir, page.path);
|
|
7019
|
+
const raw = await fs12.readFile(absolutePath, "utf8").catch(() => "");
|
|
6684
7020
|
const parsed = matter4(raw);
|
|
6685
7021
|
return {
|
|
6686
7022
|
id: page.id,
|
|
@@ -6728,7 +7064,7 @@ function heuristicDeepFindings(contextPages, structuralFindings, graph) {
|
|
|
6728
7064
|
code: "missing_citation",
|
|
6729
7065
|
message: finding.message,
|
|
6730
7066
|
pagePath: finding.pagePath,
|
|
6731
|
-
suggestedQuery: finding.pagePath ? `Which sources support the claims in ${
|
|
7067
|
+
suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path16.basename(finding.pagePath, ".md")}?` : void 0
|
|
6732
7068
|
});
|
|
6733
7069
|
}
|
|
6734
7070
|
for (const page of contextPages.filter((item) => item.kind === "source").slice(0, 3)) {
|
|
@@ -6780,6 +7116,7 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
|
|
|
6780
7116
|
system: "You are an auditor for a local-first LLM knowledge vault. Return advisory findings only. Do not propose direct file edits.",
|
|
6781
7117
|
prompt: [
|
|
6782
7118
|
"Review this SwarmVault state and return high-signal advisory findings.",
|
|
7119
|
+
"Look for claims that contradict each other across different sources. When you find a genuine contradiction, use code 'contradiction' and include both source IDs in relatedSourceIds.",
|
|
6783
7120
|
"",
|
|
6784
7121
|
"Schema:",
|
|
6785
7122
|
schema.content,
|
|
@@ -6906,8 +7243,8 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
|
|
|
6906
7243
|
}
|
|
6907
7244
|
|
|
6908
7245
|
// src/embeddings.ts
|
|
6909
|
-
import
|
|
6910
|
-
import
|
|
7246
|
+
import fs13 from "fs/promises";
|
|
7247
|
+
import path17 from "path";
|
|
6911
7248
|
var MAX_EMBEDDING_BATCH = 32;
|
|
6912
7249
|
var MAX_SIMILARITY_NODES = 240;
|
|
6913
7250
|
function cosineSimilarity(left, right) {
|
|
@@ -6941,8 +7278,8 @@ async function loadPageContents(rootDir, graph) {
|
|
|
6941
7278
|
const contents = /* @__PURE__ */ new Map();
|
|
6942
7279
|
await Promise.all(
|
|
6943
7280
|
graph.pages.map(async (page) => {
|
|
6944
|
-
const absolutePath =
|
|
6945
|
-
const content = await
|
|
7281
|
+
const absolutePath = path17.join(paths.wikiDir, page.path);
|
|
7282
|
+
const content = await fs13.readFile(absolutePath, "utf8").catch(() => {
|
|
6946
7283
|
process.stderr.write(`[swarmvault] Warning: could not read page ${page.path} for embedding
|
|
6947
7284
|
`);
|
|
6948
7285
|
return "";
|
|
@@ -8444,15 +8781,15 @@ function sourceTypeForNode(node, pagesById) {
|
|
|
8444
8781
|
return pagesById.get(node.pageId)?.sourceType;
|
|
8445
8782
|
}
|
|
8446
8783
|
function supportingPathDetails(graph, edge) {
|
|
8447
|
-
const
|
|
8784
|
+
const path26 = shortestGraphPath(graph, edge.source, edge.target);
|
|
8448
8785
|
const edgesById = new Map(graph.edges.map((item) => [item.id, item]));
|
|
8449
|
-
const pathEdges =
|
|
8786
|
+
const pathEdges = path26.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
|
|
8450
8787
|
return {
|
|
8451
|
-
pathNodeIds:
|
|
8452
|
-
pathEdgeIds:
|
|
8788
|
+
pathNodeIds: path26.nodeIds,
|
|
8789
|
+
pathEdgeIds: path26.edgeIds,
|
|
8453
8790
|
pathRelations: pathEdges.map((item) => item.relation),
|
|
8454
8791
|
pathEvidenceClasses: pathEdges.map((item) => item.evidenceClass),
|
|
8455
|
-
pathSummary:
|
|
8792
|
+
pathSummary: path26.summary
|
|
8456
8793
|
};
|
|
8457
8794
|
}
|
|
8458
8795
|
function surpriseScore(edge, graph, pagesById, hyperedgesByNodeId) {
|
|
@@ -8521,7 +8858,7 @@ function topSurprisingConnections(graph, pagesById) {
|
|
|
8521
8858
|
}).map((edge) => {
|
|
8522
8859
|
const source = nodesById.get(edge.source);
|
|
8523
8860
|
const target = nodesById.get(edge.target);
|
|
8524
|
-
const
|
|
8861
|
+
const path26 = supportingPathDetails(graph, edge);
|
|
8525
8862
|
const scored = surpriseScore(edge, graph, pagesById, hyperedgesByNodeId);
|
|
8526
8863
|
return {
|
|
8527
8864
|
id: edge.id,
|
|
@@ -8532,11 +8869,11 @@ function topSurprisingConnections(graph, pagesById) {
|
|
|
8532
8869
|
relation: edge.relation,
|
|
8533
8870
|
evidenceClass: edge.evidenceClass,
|
|
8534
8871
|
confidence: edge.confidence,
|
|
8535
|
-
pathNodeIds:
|
|
8536
|
-
pathEdgeIds:
|
|
8537
|
-
pathRelations:
|
|
8538
|
-
pathEvidenceClasses:
|
|
8539
|
-
pathSummary:
|
|
8872
|
+
pathNodeIds: path26.pathNodeIds,
|
|
8873
|
+
pathEdgeIds: path26.pathEdgeIds,
|
|
8874
|
+
pathRelations: path26.pathRelations,
|
|
8875
|
+
pathEvidenceClasses: path26.pathEvidenceClasses,
|
|
8876
|
+
pathSummary: path26.pathSummary,
|
|
8540
8877
|
why: scored.why,
|
|
8541
8878
|
explanation: scored.explanation,
|
|
8542
8879
|
surpriseScore: scored.score
|
|
@@ -8642,6 +8979,13 @@ function buildGraphReportArtifact(input) {
|
|
|
8642
8979
|
title: page.title,
|
|
8643
8980
|
sourceType: page.sourceType,
|
|
8644
8981
|
updatedAt: page.updatedAt
|
|
8982
|
+
})),
|
|
8983
|
+
contradictions: (input.contradictions ?? []).map((c) => ({
|
|
8984
|
+
sourceIdA: c.sourceIdA,
|
|
8985
|
+
sourceIdB: c.sourceIdB,
|
|
8986
|
+
claimA: c.claimA.text,
|
|
8987
|
+
claimB: c.claimB.text,
|
|
8988
|
+
confidenceDelta: Math.abs(c.claimA.confidence - c.claimB.confidence)
|
|
8645
8989
|
}))
|
|
8646
8990
|
};
|
|
8647
8991
|
}
|
|
@@ -8761,6 +9105,12 @@ function buildGraphReportPage(input) {
|
|
|
8761
9105
|
return `- ${sourceLabel} ${connection.relation} ${targetLabel} (${connection.evidenceClass}, ${connection.confidence.toFixed(2)}). Why: ${connection.why}. ${connection.explanation} Path: ${connection.pathSummary}.`;
|
|
8762
9106
|
}) : ["- No cross-community links detected."],
|
|
8763
9107
|
"",
|
|
9108
|
+
"## Contradictions",
|
|
9109
|
+
"",
|
|
9110
|
+
...input.report.contradictions.length ? input.report.contradictions.map(
|
|
9111
|
+
(c) => `- **${c.claimA}** vs **${c.claimB}** (sources: \`${c.sourceIdA}\`, \`${c.sourceIdB}\`, confidence delta: ${c.confidenceDelta.toFixed(2)})`
|
|
9112
|
+
) : ["- No contradictions detected."],
|
|
9113
|
+
"",
|
|
8764
9114
|
"## Group Patterns",
|
|
8765
9115
|
"",
|
|
8766
9116
|
...input.report.groupPatterns.length ? input.report.groupPatterns.map((hyperedge) => {
|
|
@@ -9421,13 +9771,13 @@ function buildOutputAssetManifest(input) {
|
|
|
9421
9771
|
}
|
|
9422
9772
|
|
|
9423
9773
|
// src/outputs.ts
|
|
9424
|
-
import
|
|
9425
|
-
import
|
|
9774
|
+
import fs15 from "fs/promises";
|
|
9775
|
+
import path19 from "path";
|
|
9426
9776
|
import matter7 from "gray-matter";
|
|
9427
9777
|
|
|
9428
9778
|
// src/pages.ts
|
|
9429
|
-
import
|
|
9430
|
-
import
|
|
9779
|
+
import fs14 from "fs/promises";
|
|
9780
|
+
import path18 from "path";
|
|
9431
9781
|
import matter6 from "gray-matter";
|
|
9432
9782
|
function normalizeStringArray(value) {
|
|
9433
9783
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
@@ -9505,7 +9855,7 @@ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
|
|
|
9505
9855
|
updatedAt: updatedFallback
|
|
9506
9856
|
};
|
|
9507
9857
|
}
|
|
9508
|
-
const content = await
|
|
9858
|
+
const content = await fs14.readFile(absolutePath, "utf8");
|
|
9509
9859
|
const parsed = matter6(content);
|
|
9510
9860
|
return {
|
|
9511
9861
|
status: normalizePageStatus(parsed.data.status, defaults.status ?? "active"),
|
|
@@ -9544,7 +9894,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
|
|
|
9544
9894
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9545
9895
|
const fallbackCreatedAt = defaults.createdAt ?? now;
|
|
9546
9896
|
const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
|
|
9547
|
-
const title = typeof parsed.data.title === "string" ? parsed.data.title :
|
|
9897
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : path18.basename(relativePath, ".md");
|
|
9548
9898
|
const kind = inferPageKind(relativePath, parsed.data.kind);
|
|
9549
9899
|
const sourceIds = normalizeStringArray(parsed.data.source_ids);
|
|
9550
9900
|
const projectIds = normalizeProjectIds(parsed.data.project_ids);
|
|
@@ -9585,18 +9935,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
|
|
|
9585
9935
|
};
|
|
9586
9936
|
}
|
|
9587
9937
|
async function loadInsightPages(wikiDir) {
|
|
9588
|
-
const insightsDir =
|
|
9938
|
+
const insightsDir = path18.join(wikiDir, "insights");
|
|
9589
9939
|
if (!await fileExists(insightsDir)) {
|
|
9590
9940
|
return [];
|
|
9591
9941
|
}
|
|
9592
|
-
const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) =>
|
|
9942
|
+
const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path18.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
|
|
9593
9943
|
const insights = [];
|
|
9594
9944
|
for (const absolutePath of files) {
|
|
9595
|
-
const relativePath = toPosix(
|
|
9596
|
-
const content = await
|
|
9945
|
+
const relativePath = toPosix(path18.relative(wikiDir, absolutePath));
|
|
9946
|
+
const content = await fs14.readFile(absolutePath, "utf8");
|
|
9597
9947
|
const parsed = matter6(content);
|
|
9598
|
-
const stats = await
|
|
9599
|
-
const title = typeof parsed.data.title === "string" ? parsed.data.title :
|
|
9948
|
+
const stats = await fs14.stat(absolutePath);
|
|
9949
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : path18.basename(absolutePath, ".md");
|
|
9600
9950
|
const sourceIds = normalizeStringArray(parsed.data.source_ids);
|
|
9601
9951
|
const projectIds = normalizeProjectIds(parsed.data.project_ids);
|
|
9602
9952
|
const nodeIds = normalizeStringArray(parsed.data.node_ids);
|
|
@@ -9659,27 +10009,27 @@ function relatedOutputsForPage(targetPage, outputPages) {
|
|
|
9659
10009
|
return outputPages.map((page) => ({ page, rank: relationRank(page, targetPage) })).filter((item) => item.rank > 0).sort((left, right) => right.rank - left.rank || left.page.title.localeCompare(right.page.title)).map((item) => item.page);
|
|
9660
10010
|
}
|
|
9661
10011
|
async function resolveUniqueOutputSlug(wikiDir, baseSlug) {
|
|
9662
|
-
const outputsDir =
|
|
10012
|
+
const outputsDir = path19.join(wikiDir, "outputs");
|
|
9663
10013
|
const root = baseSlug || "output";
|
|
9664
10014
|
let candidate = root;
|
|
9665
10015
|
let counter = 2;
|
|
9666
|
-
while (await fileExists(
|
|
10016
|
+
while (await fileExists(path19.join(outputsDir, `${candidate}.md`))) {
|
|
9667
10017
|
candidate = `${root}-${counter}`;
|
|
9668
10018
|
counter++;
|
|
9669
10019
|
}
|
|
9670
10020
|
return candidate;
|
|
9671
10021
|
}
|
|
9672
10022
|
async function loadSavedOutputPages(wikiDir) {
|
|
9673
|
-
const outputsDir =
|
|
9674
|
-
const entries = await
|
|
10023
|
+
const outputsDir = path19.join(wikiDir, "outputs");
|
|
10024
|
+
const entries = await fs15.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
|
|
9675
10025
|
const outputs = [];
|
|
9676
10026
|
for (const entry of entries) {
|
|
9677
10027
|
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md") {
|
|
9678
10028
|
continue;
|
|
9679
10029
|
}
|
|
9680
|
-
const relativePath =
|
|
9681
|
-
const absolutePath =
|
|
9682
|
-
const content = await
|
|
10030
|
+
const relativePath = path19.posix.join("outputs", entry.name);
|
|
10031
|
+
const absolutePath = path19.join(outputsDir, entry.name);
|
|
10032
|
+
const content = await fs15.readFile(absolutePath, "utf8");
|
|
9683
10033
|
const parsed = matter7(content);
|
|
9684
10034
|
const slug = entry.name.replace(/\.md$/, "");
|
|
9685
10035
|
const title = typeof parsed.data.title === "string" ? parsed.data.title : slug;
|
|
@@ -9692,7 +10042,7 @@ async function loadSavedOutputPages(wikiDir) {
|
|
|
9692
10042
|
const relatedSourceIds = normalizeStringArray(parsed.data.related_source_ids);
|
|
9693
10043
|
const backlinks = normalizeStringArray(parsed.data.backlinks);
|
|
9694
10044
|
const compiledFrom = normalizeStringArray(parsed.data.compiled_from);
|
|
9695
|
-
const stats = await
|
|
10045
|
+
const stats = await fs15.stat(absolutePath);
|
|
9696
10046
|
const createdAt = typeof parsed.data.created_at === "string" ? parsed.data.created_at : stats.birthtimeMs > 0 ? stats.birthtime.toISOString() : stats.mtime.toISOString();
|
|
9697
10047
|
const updatedAt = typeof parsed.data.updated_at === "string" ? parsed.data.updated_at : stats.mtime.toISOString();
|
|
9698
10048
|
outputs.push({
|
|
@@ -9730,8 +10080,8 @@ async function loadSavedOutputPages(wikiDir) {
|
|
|
9730
10080
|
}
|
|
9731
10081
|
|
|
9732
10082
|
// src/search.ts
|
|
9733
|
-
import
|
|
9734
|
-
import
|
|
10083
|
+
import fs16 from "fs/promises";
|
|
10084
|
+
import path20 from "path";
|
|
9735
10085
|
import matter8 from "gray-matter";
|
|
9736
10086
|
function getDatabaseSync() {
|
|
9737
10087
|
const builtin = process.getBuiltinModule?.("node:sqlite");
|
|
@@ -9757,7 +10107,7 @@ function normalizeSourceClass2(value) {
|
|
|
9757
10107
|
return value === "first_party" || value === "third_party" || value === "resource" || value === "generated" ? value : void 0;
|
|
9758
10108
|
}
|
|
9759
10109
|
async function rebuildSearchIndex(dbPath, pages, wikiDir) {
|
|
9760
|
-
await ensureDir(
|
|
10110
|
+
await ensureDir(path20.dirname(dbPath));
|
|
9761
10111
|
const DatabaseSync = getDatabaseSync();
|
|
9762
10112
|
const db = new DatabaseSync(dbPath);
|
|
9763
10113
|
db.exec("PRAGMA journal_mode = WAL;");
|
|
@@ -9789,8 +10139,8 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
|
|
|
9789
10139
|
"INSERT INTO pages (id, path, title, body, kind, status, source_type, source_class, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
9790
10140
|
);
|
|
9791
10141
|
for (const page of pages) {
|
|
9792
|
-
const absolutePath =
|
|
9793
|
-
const content = await
|
|
10142
|
+
const absolutePath = path20.join(wikiDir, page.path);
|
|
10143
|
+
const content = await fs16.readFile(absolutePath, "utf8");
|
|
9794
10144
|
const parsed = matter8(content);
|
|
9795
10145
|
insertPage.run(
|
|
9796
10146
|
page.id,
|
|
@@ -9907,7 +10257,7 @@ function outputFormatInstruction(format) {
|
|
|
9907
10257
|
}
|
|
9908
10258
|
}
|
|
9909
10259
|
function outputAssetPath(slug, fileName) {
|
|
9910
|
-
return toPosix(
|
|
10260
|
+
return toPosix(path21.join("outputs", "assets", slug, fileName));
|
|
9911
10261
|
}
|
|
9912
10262
|
function outputAssetId(slug, role) {
|
|
9913
10263
|
return `output:${slug}:asset:${role}`;
|
|
@@ -10047,7 +10397,7 @@ async function resolveImageGenerationProvider(rootDir) {
|
|
|
10047
10397
|
if (!providerConfig) {
|
|
10048
10398
|
throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
|
|
10049
10399
|
}
|
|
10050
|
-
const { createProvider: createProvider2 } = await import("./registry-
|
|
10400
|
+
const { createProvider: createProvider2 } = await import("./registry-G7NSRYCO.js");
|
|
10051
10401
|
return createProvider2(preferredProviderId, providerConfig, rootDir);
|
|
10052
10402
|
}
|
|
10053
10403
|
async function generateOutputArtifacts(rootDir, input) {
|
|
@@ -10245,7 +10595,7 @@ async function generateOutputArtifacts(rootDir, input) {
|
|
|
10245
10595
|
};
|
|
10246
10596
|
}
|
|
10247
10597
|
function normalizeProjectRoot(root) {
|
|
10248
|
-
const normalized = toPosix(
|
|
10598
|
+
const normalized = toPosix(path21.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
10249
10599
|
return normalized;
|
|
10250
10600
|
}
|
|
10251
10601
|
function projectEntries(config) {
|
|
@@ -10271,10 +10621,10 @@ function manifestPathForProject(rootDir, manifest) {
|
|
|
10271
10621
|
if (!rawPath) {
|
|
10272
10622
|
return toPosix(manifest.storedPath);
|
|
10273
10623
|
}
|
|
10274
|
-
if (!
|
|
10624
|
+
if (!path21.isAbsolute(rawPath)) {
|
|
10275
10625
|
return normalizeProjectRoot(rawPath);
|
|
10276
10626
|
}
|
|
10277
|
-
const relative = toPosix(
|
|
10627
|
+
const relative = toPosix(path21.relative(rootDir, rawPath));
|
|
10278
10628
|
return relative.startsWith("..") ? toPosix(rawPath) : normalizeProjectRoot(relative);
|
|
10279
10629
|
}
|
|
10280
10630
|
function prefixMatches(value, prefix) {
|
|
@@ -10448,7 +10798,7 @@ function pageHashes(pages) {
|
|
|
10448
10798
|
return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
|
|
10449
10799
|
}
|
|
10450
10800
|
async function buildManagedGraphPage(absolutePath, defaults, build) {
|
|
10451
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
10801
|
+
const existingContent = await fileExists(absolutePath) ? await fs17.readFile(absolutePath, "utf8") : null;
|
|
10452
10802
|
let existing = await loadExistingManagedPageState(absolutePath, {
|
|
10453
10803
|
status: defaults.status ?? "active",
|
|
10454
10804
|
managedBy: defaults.managedBy
|
|
@@ -10486,7 +10836,7 @@ async function buildManagedGraphPage(absolutePath, defaults, build) {
|
|
|
10486
10836
|
return built;
|
|
10487
10837
|
}
|
|
10488
10838
|
async function buildManagedContent(absolutePath, defaults, build) {
|
|
10489
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
10839
|
+
const existingContent = await fileExists(absolutePath) ? await fs17.readFile(absolutePath, "utf8") : null;
|
|
10490
10840
|
let existing = await loadExistingManagedPageState(absolutePath, {
|
|
10491
10841
|
status: defaults.status ?? "active",
|
|
10492
10842
|
managedBy: defaults.managedBy
|
|
@@ -10609,7 +10959,7 @@ function resetGraphNodeMetrics(nodes) {
|
|
|
10609
10959
|
return nodes.map(({ communityId: _communityId, degree: _degree, bridgeScore: _bridgeScore, isGodNode: _isGodNode, ...node }) => node);
|
|
10610
10960
|
}
|
|
10611
10961
|
function manifestRepoPath(manifest) {
|
|
10612
|
-
return toPosix(manifest.repoRelativePath ??
|
|
10962
|
+
return toPosix(manifest.repoRelativePath ?? path21.basename(manifest.originalPath ?? manifest.storedPath));
|
|
10613
10963
|
}
|
|
10614
10964
|
function goPackageScopeKey(manifest, analysis) {
|
|
10615
10965
|
if (analysis.code?.language !== "go") {
|
|
@@ -10619,7 +10969,7 @@ function goPackageScopeKey(manifest, analysis) {
|
|
|
10619
10969
|
if (!packageName) {
|
|
10620
10970
|
return null;
|
|
10621
10971
|
}
|
|
10622
|
-
return `${packageName}:${
|
|
10972
|
+
return `${packageName}:${path21.posix.dirname(manifestRepoPath(manifest))}`;
|
|
10623
10973
|
}
|
|
10624
10974
|
function buildGoPackageSymbolLookups(analyses, manifestsById) {
|
|
10625
10975
|
const lookups = /* @__PURE__ */ new Map();
|
|
@@ -10663,21 +11013,64 @@ function buildGoPackageSymbolLookups(analyses, manifestsById) {
|
|
|
10663
11013
|
])
|
|
10664
11014
|
);
|
|
10665
11015
|
}
|
|
11016
|
+
function claimTokens(text) {
|
|
11017
|
+
return new Set(
|
|
11018
|
+
text.toLowerCase().match(/[a-z][a-z0-9-]{2,}/g)?.filter((t) => !(/* @__PURE__ */ new Set(["the", "and", "for", "that", "this", "with", "are", "was", "from", "has", "not", "all", "but"])).has(t)) ?? []
|
|
11019
|
+
);
|
|
11020
|
+
}
|
|
11021
|
+
function claimJaccardSimilarity(a, b) {
|
|
11022
|
+
if (a.size === 0 && b.size === 0) return 0;
|
|
11023
|
+
let intersection = 0;
|
|
11024
|
+
for (const token of a) {
|
|
11025
|
+
if (b.has(token)) intersection++;
|
|
11026
|
+
}
|
|
11027
|
+
return intersection / (a.size + b.size - intersection);
|
|
11028
|
+
}
|
|
11029
|
+
function detectContradictions(analyses) {
|
|
11030
|
+
const contradictions = [];
|
|
11031
|
+
const claimsWithTokens = analyses.flatMap(
|
|
11032
|
+
(analysis) => analysis.claims.filter((c) => c.polarity === "positive" || c.polarity === "negative").map((c) => ({ sourceId: analysis.sourceId, claim: c, tokens: claimTokens(c.text) }))
|
|
11033
|
+
);
|
|
11034
|
+
for (let i = 0; i < claimsWithTokens.length; i++) {
|
|
11035
|
+
for (let j = i + 1; j < claimsWithTokens.length; j++) {
|
|
11036
|
+
const a = claimsWithTokens[i];
|
|
11037
|
+
const b = claimsWithTokens[j];
|
|
11038
|
+
if (a.sourceId === b.sourceId) continue;
|
|
11039
|
+
if (a.claim.polarity === b.claim.polarity) continue;
|
|
11040
|
+
const similarity = claimJaccardSimilarity(a.tokens, b.tokens);
|
|
11041
|
+
if (similarity >= 0.3) {
|
|
11042
|
+
contradictions.push({
|
|
11043
|
+
sourceIdA: a.sourceId,
|
|
11044
|
+
sourceIdB: b.sourceId,
|
|
11045
|
+
claimA: { text: a.claim.text, confidence: a.claim.confidence },
|
|
11046
|
+
claimB: { text: b.claim.text, confidence: b.claim.confidence },
|
|
11047
|
+
similarity
|
|
11048
|
+
});
|
|
11049
|
+
}
|
|
11050
|
+
}
|
|
11051
|
+
}
|
|
11052
|
+
return contradictions;
|
|
11053
|
+
}
|
|
10666
11054
|
function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
10667
11055
|
const manifestsById = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
|
|
10668
11056
|
const goPackageSymbolLookups = buildGoPackageSymbolLookups(analyses, manifestsById);
|
|
10669
|
-
const
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
11057
|
+
const analysesBySourceId = new Map(analyses.map((analysis) => [analysis.sourceId, analysis]));
|
|
11058
|
+
const sourceNodes = manifests.map((manifest) => {
|
|
11059
|
+
const analysis = analysesBySourceId.get(manifest.sourceId);
|
|
11060
|
+
return {
|
|
11061
|
+
id: `source:${manifest.sourceId}`,
|
|
11062
|
+
type: "source",
|
|
11063
|
+
label: manifest.title,
|
|
11064
|
+
pageId: `source:${manifest.sourceId}`,
|
|
11065
|
+
freshness: "fresh",
|
|
11066
|
+
confidence: 1,
|
|
11067
|
+
sourceIds: [manifest.sourceId],
|
|
11068
|
+
projectIds: scopedProjectIdsFromSources([manifest.sourceId], sourceProjects),
|
|
11069
|
+
sourceClass: manifest.sourceClass,
|
|
11070
|
+
language: manifest.language,
|
|
11071
|
+
tags: analysis?.tags ?? []
|
|
11072
|
+
};
|
|
11073
|
+
});
|
|
10681
11074
|
const conceptMap = /* @__PURE__ */ new Map();
|
|
10682
11075
|
const entityMap = /* @__PURE__ */ new Map();
|
|
10683
11076
|
const moduleMap = /* @__PURE__ */ new Map();
|
|
@@ -10692,7 +11085,6 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
10692
11085
|
edgesById.add(edge.id);
|
|
10693
11086
|
edges.push(edge);
|
|
10694
11087
|
};
|
|
10695
|
-
const analysesBySourceId = new Map(analyses.map((analysis) => [analysis.sourceId, analysis]));
|
|
10696
11088
|
for (const analysis of analyses) {
|
|
10697
11089
|
for (const concept of analysis.concepts) {
|
|
10698
11090
|
const existing = conceptMap.get(concept.id);
|
|
@@ -11042,11 +11434,11 @@ function recentResearchSourcePages(graph, previousCompiledAt) {
|
|
|
11042
11434
|
sourceType: page.sourceType
|
|
11043
11435
|
}));
|
|
11044
11436
|
}
|
|
11045
|
-
async function buildGraphOrientationPages(graph, paths, schemaHash, previousCompiledAt) {
|
|
11437
|
+
async function buildGraphOrientationPages(graph, paths, schemaHash, previousCompiledAt, contradictions = []) {
|
|
11046
11438
|
const benchmark = await readJsonFile(paths.benchmarkPath);
|
|
11047
11439
|
const communityRecords = [];
|
|
11048
11440
|
for (const community of graph.communities ?? []) {
|
|
11049
|
-
const absolutePath =
|
|
11441
|
+
const absolutePath = path21.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
|
|
11050
11442
|
communityRecords.push(
|
|
11051
11443
|
await buildManagedGraphPage(
|
|
11052
11444
|
absolutePath,
|
|
@@ -11072,9 +11464,10 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
|
|
|
11072
11464
|
benchmark,
|
|
11073
11465
|
benchmarkStale: benchmark ? benchmark.graphHash !== graphHash(graph) : false,
|
|
11074
11466
|
recentResearchSources: recentResearchSourcePages(graph, previousCompiledAt),
|
|
11075
|
-
graphHash: graphHash(graph)
|
|
11467
|
+
graphHash: graphHash(graph),
|
|
11468
|
+
contradictions
|
|
11076
11469
|
});
|
|
11077
|
-
const reportAbsolutePath =
|
|
11470
|
+
const reportAbsolutePath = path21.join(paths.wikiDir, "graph", "report.md");
|
|
11078
11471
|
const reportRecord = await buildManagedGraphPage(
|
|
11079
11472
|
reportAbsolutePath,
|
|
11080
11473
|
{
|
|
@@ -11095,7 +11488,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
|
|
|
11095
11488
|
};
|
|
11096
11489
|
}
|
|
11097
11490
|
async function writePage(wikiDir, relativePath, content, changedPages) {
|
|
11098
|
-
const absolutePath =
|
|
11491
|
+
const absolutePath = path21.resolve(wikiDir, relativePath);
|
|
11099
11492
|
const changed = await writeFileIfChanged(absolutePath, content);
|
|
11100
11493
|
if (changed) {
|
|
11101
11494
|
changedPages.push(relativePath);
|
|
@@ -11158,29 +11551,29 @@ async function requiredCompileArtifactsExist(paths) {
|
|
|
11158
11551
|
paths.graphPath,
|
|
11159
11552
|
paths.codeIndexPath,
|
|
11160
11553
|
paths.searchDbPath,
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11554
|
+
path21.join(paths.wikiDir, "index.md"),
|
|
11555
|
+
path21.join(paths.wikiDir, "sources", "index.md"),
|
|
11556
|
+
path21.join(paths.wikiDir, "code", "index.md"),
|
|
11557
|
+
path21.join(paths.wikiDir, "concepts", "index.md"),
|
|
11558
|
+
path21.join(paths.wikiDir, "entities", "index.md"),
|
|
11559
|
+
path21.join(paths.wikiDir, "outputs", "index.md"),
|
|
11560
|
+
path21.join(paths.wikiDir, "projects", "index.md"),
|
|
11561
|
+
path21.join(paths.wikiDir, "candidates", "index.md")
|
|
11169
11562
|
];
|
|
11170
11563
|
const checks = await Promise.all(requiredPaths.map((filePath) => fileExists(filePath)));
|
|
11171
11564
|
return checks.every(Boolean);
|
|
11172
11565
|
}
|
|
11173
11566
|
async function loadAvailableCachedAnalyses(paths, manifests) {
|
|
11174
11567
|
const analyses = await Promise.all(
|
|
11175
|
-
manifests.map(async (manifest) => readJsonFile(
|
|
11568
|
+
manifests.map(async (manifest) => readJsonFile(path21.join(paths.analysesDir, `${manifest.sourceId}.json`)))
|
|
11176
11569
|
);
|
|
11177
11570
|
return analyses.filter((analysis) => Boolean(analysis));
|
|
11178
11571
|
}
|
|
11179
11572
|
function approvalManifestPath(paths, approvalId) {
|
|
11180
|
-
return
|
|
11573
|
+
return path21.join(paths.approvalsDir, approvalId, "manifest.json");
|
|
11181
11574
|
}
|
|
11182
11575
|
function approvalGraphPath(paths, approvalId) {
|
|
11183
|
-
return
|
|
11576
|
+
return path21.join(paths.approvalsDir, approvalId, "state", "graph.json");
|
|
11184
11577
|
}
|
|
11185
11578
|
async function readApprovalManifest(paths, approvalId) {
|
|
11186
11579
|
const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
|
|
@@ -11190,7 +11583,7 @@ async function readApprovalManifest(paths, approvalId) {
|
|
|
11190
11583
|
return manifest;
|
|
11191
11584
|
}
|
|
11192
11585
|
async function writeApprovalManifest(paths, manifest) {
|
|
11193
|
-
await
|
|
11586
|
+
await fs17.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
|
|
11194
11587
|
`, "utf8");
|
|
11195
11588
|
}
|
|
11196
11589
|
async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
|
|
@@ -11205,7 +11598,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
11205
11598
|
continue;
|
|
11206
11599
|
}
|
|
11207
11600
|
const previousPage = previousPagesById.get(nextPage.id);
|
|
11208
|
-
const currentExists = await fileExists(
|
|
11601
|
+
const currentExists = await fileExists(path21.join(paths.wikiDir, file.relativePath));
|
|
11209
11602
|
if (previousPage && previousPage.path !== nextPage.path) {
|
|
11210
11603
|
entries.push({
|
|
11211
11604
|
pageId: nextPage.id,
|
|
@@ -11238,7 +11631,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
11238
11631
|
const previousPage = previousPagesByPath.get(deletedPath);
|
|
11239
11632
|
entries.push({
|
|
11240
11633
|
pageId: previousPage?.id ?? `page:${slugify(deletedPath)}`,
|
|
11241
|
-
title: previousPage?.title ??
|
|
11634
|
+
title: previousPage?.title ?? path21.basename(deletedPath, ".md"),
|
|
11242
11635
|
kind: previousPage?.kind ?? "index",
|
|
11243
11636
|
changeType: "delete",
|
|
11244
11637
|
status: "pending",
|
|
@@ -11250,16 +11643,16 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
11250
11643
|
}
|
|
11251
11644
|
async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGraph, graph) {
|
|
11252
11645
|
const approvalId = `compile-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
11253
|
-
const approvalDir =
|
|
11646
|
+
const approvalDir = path21.join(paths.approvalsDir, approvalId);
|
|
11254
11647
|
await ensureDir(approvalDir);
|
|
11255
|
-
await ensureDir(
|
|
11256
|
-
await ensureDir(
|
|
11648
|
+
await ensureDir(path21.join(approvalDir, "wiki"));
|
|
11649
|
+
await ensureDir(path21.join(approvalDir, "state"));
|
|
11257
11650
|
for (const file of changedFiles) {
|
|
11258
|
-
const targetPath =
|
|
11259
|
-
await ensureDir(
|
|
11260
|
-
await
|
|
11651
|
+
const targetPath = path21.join(approvalDir, "wiki", file.relativePath);
|
|
11652
|
+
await ensureDir(path21.dirname(targetPath));
|
|
11653
|
+
await fs17.writeFile(targetPath, file.content, "utf8");
|
|
11261
11654
|
}
|
|
11262
|
-
await
|
|
11655
|
+
await fs17.writeFile(path21.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
|
|
11263
11656
|
await writeApprovalManifest(paths, {
|
|
11264
11657
|
approvalId,
|
|
11265
11658
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11321,7 +11714,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11321
11714
|
confidence: 1
|
|
11322
11715
|
});
|
|
11323
11716
|
const sourceRecord = await buildManagedGraphPage(
|
|
11324
|
-
|
|
11717
|
+
path21.join(paths.wikiDir, preview.path),
|
|
11325
11718
|
{
|
|
11326
11719
|
managedBy: "system",
|
|
11327
11720
|
confidence: 1,
|
|
@@ -11336,7 +11729,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11336
11729
|
modulePreview ?? void 0,
|
|
11337
11730
|
{
|
|
11338
11731
|
projectIds: sourceProjectIds,
|
|
11339
|
-
extraTags: sourceCategoryTags,
|
|
11732
|
+
extraTags: [...sourceCategoryTags, ...analysis.tags ?? []],
|
|
11340
11733
|
sourceClass: manifest.sourceClass
|
|
11341
11734
|
}
|
|
11342
11735
|
)
|
|
@@ -11367,7 +11760,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11367
11760
|
);
|
|
11368
11761
|
records.push(
|
|
11369
11762
|
await buildManagedGraphPage(
|
|
11370
|
-
|
|
11763
|
+
path21.join(paths.wikiDir, modulePreview.path),
|
|
11371
11764
|
{
|
|
11372
11765
|
managedBy: "system",
|
|
11373
11766
|
confidence: 1,
|
|
@@ -11382,7 +11775,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11382
11775
|
localModules,
|
|
11383
11776
|
relatedOutputs: relatedOutputsForPage(modulePreview, input.outputPages),
|
|
11384
11777
|
projectIds: sourceProjectIds,
|
|
11385
|
-
extraTags: sourceCategoryTags
|
|
11778
|
+
extraTags: [...sourceCategoryTags, ...analysis.tags ?? []]
|
|
11386
11779
|
})
|
|
11387
11780
|
)
|
|
11388
11781
|
);
|
|
@@ -11401,8 +11794,8 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11401
11794
|
const relativePath = promoted ? activeAggregatePath(itemKind, slug) : candidatePagePathFor(itemKind, slug);
|
|
11402
11795
|
const aggregateSourceClass2 = aggregateManifestSourceClass(input.manifests, sourceIds);
|
|
11403
11796
|
const fallbackPaths = [
|
|
11404
|
-
|
|
11405
|
-
|
|
11797
|
+
path21.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
|
|
11798
|
+
path21.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
|
|
11406
11799
|
];
|
|
11407
11800
|
const confidence = nodeConfidence(aggregate.sourceAnalyses.length);
|
|
11408
11801
|
const preview = emptyGraphPage({
|
|
@@ -11420,7 +11813,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11420
11813
|
status: promoted ? "active" : "candidate"
|
|
11421
11814
|
});
|
|
11422
11815
|
const pageRecord = await buildManagedGraphPage(
|
|
11423
|
-
|
|
11816
|
+
path21.join(paths.wikiDir, relativePath),
|
|
11424
11817
|
{
|
|
11425
11818
|
status: promoted ? "active" : "candidate",
|
|
11426
11819
|
managedBy: "system",
|
|
@@ -11462,6 +11855,22 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11462
11855
|
const compiledPages = records.map((record) => record.page);
|
|
11463
11856
|
const basePages = [...compiledPages, ...input.outputPages, ...input.insightPages];
|
|
11464
11857
|
const structuralGraph = buildGraph(input.manifests, input.analyses, basePages, input.sourceProjects, input.codeIndex);
|
|
11858
|
+
const contradictions = detectContradictions(input.analyses);
|
|
11859
|
+
for (const contradiction of contradictions) {
|
|
11860
|
+
const edgeId = `contradiction:${contradiction.sourceIdA}->${contradiction.sourceIdB}`;
|
|
11861
|
+
if (!structuralGraph.edges.some((e) => e.id === edgeId)) {
|
|
11862
|
+
structuralGraph.edges.push({
|
|
11863
|
+
id: edgeId,
|
|
11864
|
+
source: `source:${contradiction.sourceIdA}`,
|
|
11865
|
+
target: `source:${contradiction.sourceIdB}`,
|
|
11866
|
+
relation: "contradicts",
|
|
11867
|
+
status: "conflicted",
|
|
11868
|
+
evidenceClass: "ambiguous",
|
|
11869
|
+
confidence: Math.abs(contradiction.claimA.confidence - contradiction.claimB.confidence),
|
|
11870
|
+
provenance: [contradiction.sourceIdA, contradiction.sourceIdB]
|
|
11871
|
+
});
|
|
11872
|
+
}
|
|
11873
|
+
}
|
|
11465
11874
|
const embeddingEdges = await embeddingSimilarityEdges(rootDir, structuralGraph).catch(() => []);
|
|
11466
11875
|
const baseGraph = embeddingEdges.length > 0 ? (() => {
|
|
11467
11876
|
const edges = uniqueBy([...structuralGraph.edges, ...embeddingEdges], (edge) => edge.id).sort(
|
|
@@ -11475,7 +11884,13 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11475
11884
|
communities: metrics.communities
|
|
11476
11885
|
};
|
|
11477
11886
|
})() : structuralGraph;
|
|
11478
|
-
const graphOrientation = await buildGraphOrientationPages(
|
|
11887
|
+
const graphOrientation = await buildGraphOrientationPages(
|
|
11888
|
+
baseGraph,
|
|
11889
|
+
paths,
|
|
11890
|
+
globalSchemaHash,
|
|
11891
|
+
input.previousState?.generatedAt,
|
|
11892
|
+
contradictions
|
|
11893
|
+
);
|
|
11479
11894
|
records.push(...graphOrientation.records);
|
|
11480
11895
|
const allPages = [...basePages, ...graphOrientation.records.map((record) => record.page)];
|
|
11481
11896
|
const graph = {
|
|
@@ -11515,7 +11930,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11515
11930
|
confidence: 1
|
|
11516
11931
|
}),
|
|
11517
11932
|
content: await buildManagedContent(
|
|
11518
|
-
|
|
11933
|
+
path21.join(paths.wikiDir, "projects", "index.md"),
|
|
11519
11934
|
{
|
|
11520
11935
|
managedBy: "system",
|
|
11521
11936
|
compiledFrom: indexCompiledFrom(projectIndexRefs)
|
|
@@ -11539,7 +11954,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11539
11954
|
records.push({
|
|
11540
11955
|
page: projectIndexRef,
|
|
11541
11956
|
content: await buildManagedContent(
|
|
11542
|
-
|
|
11957
|
+
path21.join(paths.wikiDir, projectIndexRef.path),
|
|
11543
11958
|
{
|
|
11544
11959
|
managedBy: "system",
|
|
11545
11960
|
compiledFrom: indexCompiledFrom(Object.values(sections).flat())
|
|
@@ -11567,7 +11982,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11567
11982
|
confidence: 1
|
|
11568
11983
|
}),
|
|
11569
11984
|
content: await buildManagedContent(
|
|
11570
|
-
|
|
11985
|
+
path21.join(paths.wikiDir, "index.md"),
|
|
11571
11986
|
{
|
|
11572
11987
|
managedBy: "system",
|
|
11573
11988
|
compiledFrom: indexCompiledFrom(allPages)
|
|
@@ -11598,7 +12013,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11598
12013
|
confidence: 1
|
|
11599
12014
|
}),
|
|
11600
12015
|
content: await buildManagedContent(
|
|
11601
|
-
|
|
12016
|
+
path21.join(paths.wikiDir, relativePath),
|
|
11602
12017
|
{
|
|
11603
12018
|
managedBy: "system",
|
|
11604
12019
|
compiledFrom: indexCompiledFrom(pages)
|
|
@@ -11609,12 +12024,12 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11609
12024
|
}
|
|
11610
12025
|
const nextPagePaths = new Set(records.map((record) => record.page.path));
|
|
11611
12026
|
const obsoleteGraphPaths = (previousGraph?.pages ?? []).filter((page) => page.kind !== "output" && page.kind !== "insight").map((page) => page.path).filter((relativePath) => !nextPagePaths.has(relativePath));
|
|
11612
|
-
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(
|
|
12027
|
+
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path21.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
|
|
11613
12028
|
const obsoletePaths = uniqueStrings3([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
|
|
11614
12029
|
const changedFiles = [];
|
|
11615
12030
|
for (const record of records) {
|
|
11616
|
-
const absolutePath =
|
|
11617
|
-
const current = await fileExists(absolutePath) ? await
|
|
12031
|
+
const absolutePath = path21.join(paths.wikiDir, record.page.path);
|
|
12032
|
+
const current = await fileExists(absolutePath) ? await fs17.readFile(absolutePath, "utf8") : null;
|
|
11618
12033
|
if (current !== record.content) {
|
|
11619
12034
|
changedPages.push(record.page.path);
|
|
11620
12035
|
changedFiles.push({ relativePath: record.page.path, content: record.content });
|
|
@@ -11639,10 +12054,10 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11639
12054
|
await writePage(paths.wikiDir, record.page.path, record.content, writeChanges);
|
|
11640
12055
|
}
|
|
11641
12056
|
for (const relativePath of obsoletePaths) {
|
|
11642
|
-
await
|
|
12057
|
+
await fs17.rm(path21.join(paths.wikiDir, relativePath), { force: true });
|
|
11643
12058
|
}
|
|
11644
12059
|
await writeJsonFile(paths.graphPath, graph);
|
|
11645
|
-
await writeJsonFile(
|
|
12060
|
+
await writeJsonFile(path21.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
|
|
11646
12061
|
await writeJsonFile(paths.codeIndexPath, input.codeIndex);
|
|
11647
12062
|
await writeJsonFile(paths.compileStatePath, {
|
|
11648
12063
|
generatedAt: graph.generatedAt,
|
|
@@ -11713,17 +12128,17 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11713
12128
|
})
|
|
11714
12129
|
);
|
|
11715
12130
|
await Promise.all([
|
|
11716
|
-
ensureDir(
|
|
11717
|
-
ensureDir(
|
|
11718
|
-
ensureDir(
|
|
11719
|
-
ensureDir(
|
|
11720
|
-
ensureDir(
|
|
11721
|
-
ensureDir(
|
|
11722
|
-
ensureDir(
|
|
11723
|
-
ensureDir(
|
|
11724
|
-
ensureDir(
|
|
12131
|
+
ensureDir(path21.join(paths.wikiDir, "sources")),
|
|
12132
|
+
ensureDir(path21.join(paths.wikiDir, "code")),
|
|
12133
|
+
ensureDir(path21.join(paths.wikiDir, "concepts")),
|
|
12134
|
+
ensureDir(path21.join(paths.wikiDir, "entities")),
|
|
12135
|
+
ensureDir(path21.join(paths.wikiDir, "outputs")),
|
|
12136
|
+
ensureDir(path21.join(paths.wikiDir, "graph")),
|
|
12137
|
+
ensureDir(path21.join(paths.wikiDir, "graph", "communities")),
|
|
12138
|
+
ensureDir(path21.join(paths.wikiDir, "projects")),
|
|
12139
|
+
ensureDir(path21.join(paths.wikiDir, "candidates"))
|
|
11725
12140
|
]);
|
|
11726
|
-
const projectsIndexPath =
|
|
12141
|
+
const projectsIndexPath = path21.join(paths.wikiDir, "projects", "index.md");
|
|
11727
12142
|
await writeFileIfChanged(
|
|
11728
12143
|
projectsIndexPath,
|
|
11729
12144
|
await buildManagedContent(
|
|
@@ -11744,7 +12159,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11744
12159
|
outputs: pages.filter((page) => page.kind === "output" && page.projectIds.includes(project.id)),
|
|
11745
12160
|
candidates: pages.filter((page) => page.status === "candidate" && page.projectIds.includes(project.id))
|
|
11746
12161
|
};
|
|
11747
|
-
const absolutePath =
|
|
12162
|
+
const absolutePath = path21.join(paths.wikiDir, "projects", project.id, "index.md");
|
|
11748
12163
|
await writeFileIfChanged(
|
|
11749
12164
|
absolutePath,
|
|
11750
12165
|
await buildManagedContent(
|
|
@@ -11762,7 +12177,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11762
12177
|
)
|
|
11763
12178
|
);
|
|
11764
12179
|
}
|
|
11765
|
-
const rootIndexPath =
|
|
12180
|
+
const rootIndexPath = path21.join(paths.wikiDir, "index.md");
|
|
11766
12181
|
await writeFileIfChanged(
|
|
11767
12182
|
rootIndexPath,
|
|
11768
12183
|
await buildManagedContent(
|
|
@@ -11783,7 +12198,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11783
12198
|
["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
|
|
11784
12199
|
["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
|
|
11785
12200
|
]) {
|
|
11786
|
-
const absolutePath =
|
|
12201
|
+
const absolutePath = path21.join(paths.wikiDir, relativePath);
|
|
11787
12202
|
await writeFileIfChanged(
|
|
11788
12203
|
absolutePath,
|
|
11789
12204
|
await buildManagedContent(
|
|
@@ -11797,23 +12212,23 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11797
12212
|
);
|
|
11798
12213
|
}
|
|
11799
12214
|
for (const record of graphOrientation.records) {
|
|
11800
|
-
await writeFileIfChanged(
|
|
12215
|
+
await writeFileIfChanged(path21.join(paths.wikiDir, record.page.path), record.content);
|
|
11801
12216
|
}
|
|
11802
12217
|
if (graphOrientation.report) {
|
|
11803
|
-
await writeJsonFile(
|
|
12218
|
+
await writeJsonFile(path21.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
|
|
11804
12219
|
}
|
|
11805
|
-
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(
|
|
12220
|
+
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path21.relative(paths.wikiDir, absolutePath)));
|
|
11806
12221
|
const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
|
|
11807
12222
|
"projects/index.md",
|
|
11808
12223
|
...configuredProjects.map((project) => `projects/${project.id}/index.md`)
|
|
11809
12224
|
]);
|
|
11810
12225
|
await Promise.all(
|
|
11811
|
-
existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) =>
|
|
12226
|
+
existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs17.rm(path21.join(paths.wikiDir, relativePath), { force: true }))
|
|
11812
12227
|
);
|
|
11813
|
-
const existingGraphPages = (await listFilesRecursive(
|
|
12228
|
+
const existingGraphPages = (await listFilesRecursive(path21.join(paths.wikiDir, "graph").replace(/\/$/, "")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path21.relative(paths.wikiDir, absolutePath)));
|
|
11814
12229
|
const allowedGraphPages = /* @__PURE__ */ new Set(["graph/index.md", ...graphOrientation.records.map((record) => record.page.path)]);
|
|
11815
12230
|
await Promise.all(
|
|
11816
|
-
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) =>
|
|
12231
|
+
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs17.rm(path21.join(paths.wikiDir, relativePath), { force: true }))
|
|
11817
12232
|
);
|
|
11818
12233
|
await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
|
|
11819
12234
|
}
|
|
@@ -11833,7 +12248,7 @@ async function prepareOutputPageSave(rootDir, input) {
|
|
|
11833
12248
|
confidence: 0.74
|
|
11834
12249
|
}
|
|
11835
12250
|
});
|
|
11836
|
-
const absolutePath =
|
|
12251
|
+
const absolutePath = path21.join(paths.wikiDir, output.page.path);
|
|
11837
12252
|
return {
|
|
11838
12253
|
page: output.page,
|
|
11839
12254
|
savedPath: absolutePath,
|
|
@@ -11845,15 +12260,15 @@ async function prepareOutputPageSave(rootDir, input) {
|
|
|
11845
12260
|
async function persistOutputPage(rootDir, input) {
|
|
11846
12261
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11847
12262
|
const prepared = await prepareOutputPageSave(rootDir, input);
|
|
11848
|
-
await ensureDir(
|
|
11849
|
-
await
|
|
12263
|
+
await ensureDir(path21.dirname(prepared.savedPath));
|
|
12264
|
+
await fs17.writeFile(prepared.savedPath, prepared.content, "utf8");
|
|
11850
12265
|
for (const assetFile of prepared.assetFiles) {
|
|
11851
|
-
const assetPath =
|
|
11852
|
-
await ensureDir(
|
|
12266
|
+
const assetPath = path21.join(paths.wikiDir, assetFile.relativePath);
|
|
12267
|
+
await ensureDir(path21.dirname(assetPath));
|
|
11853
12268
|
if (typeof assetFile.content === "string") {
|
|
11854
|
-
await
|
|
12269
|
+
await fs17.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
|
|
11855
12270
|
} else {
|
|
11856
|
-
await
|
|
12271
|
+
await fs17.writeFile(assetPath, assetFile.content);
|
|
11857
12272
|
}
|
|
11858
12273
|
}
|
|
11859
12274
|
return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
|
|
@@ -11874,7 +12289,7 @@ async function prepareExploreHubSave(rootDir, input) {
|
|
|
11874
12289
|
confidence: 0.76
|
|
11875
12290
|
}
|
|
11876
12291
|
});
|
|
11877
|
-
const absolutePath =
|
|
12292
|
+
const absolutePath = path21.join(paths.wikiDir, hub.page.path);
|
|
11878
12293
|
return {
|
|
11879
12294
|
page: hub.page,
|
|
11880
12295
|
savedPath: absolutePath,
|
|
@@ -11886,15 +12301,15 @@ async function prepareExploreHubSave(rootDir, input) {
|
|
|
11886
12301
|
async function persistExploreHub(rootDir, input) {
|
|
11887
12302
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11888
12303
|
const prepared = await prepareExploreHubSave(rootDir, input);
|
|
11889
|
-
await ensureDir(
|
|
11890
|
-
await
|
|
12304
|
+
await ensureDir(path21.dirname(prepared.savedPath));
|
|
12305
|
+
await fs17.writeFile(prepared.savedPath, prepared.content, "utf8");
|
|
11891
12306
|
for (const assetFile of prepared.assetFiles) {
|
|
11892
|
-
const assetPath =
|
|
11893
|
-
await ensureDir(
|
|
12307
|
+
const assetPath = path21.join(paths.wikiDir, assetFile.relativePath);
|
|
12308
|
+
await ensureDir(path21.dirname(assetPath));
|
|
11894
12309
|
if (typeof assetFile.content === "string") {
|
|
11895
|
-
await
|
|
12310
|
+
await fs17.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
|
|
11896
12311
|
} else {
|
|
11897
|
-
await
|
|
12312
|
+
await fs17.writeFile(assetPath, assetFile.content);
|
|
11898
12313
|
}
|
|
11899
12314
|
}
|
|
11900
12315
|
return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
|
|
@@ -11911,17 +12326,17 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
11911
12326
|
}))
|
|
11912
12327
|
]);
|
|
11913
12328
|
const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
11914
|
-
const approvalDir =
|
|
12329
|
+
const approvalDir = path21.join(paths.approvalsDir, approvalId);
|
|
11915
12330
|
await ensureDir(approvalDir);
|
|
11916
|
-
await ensureDir(
|
|
11917
|
-
await ensureDir(
|
|
12331
|
+
await ensureDir(path21.join(approvalDir, "wiki"));
|
|
12332
|
+
await ensureDir(path21.join(approvalDir, "state"));
|
|
11918
12333
|
for (const file of changedFiles) {
|
|
11919
|
-
const targetPath =
|
|
11920
|
-
await ensureDir(
|
|
12334
|
+
const targetPath = path21.join(approvalDir, "wiki", file.relativePath);
|
|
12335
|
+
await ensureDir(path21.dirname(targetPath));
|
|
11921
12336
|
if ("binary" in file && file.binary) {
|
|
11922
|
-
await
|
|
12337
|
+
await fs17.writeFile(targetPath, Buffer.from(file.content, "base64"));
|
|
11923
12338
|
} else {
|
|
11924
|
-
await
|
|
12339
|
+
await fs17.writeFile(targetPath, file.content, "utf8");
|
|
11925
12340
|
}
|
|
11926
12341
|
}
|
|
11927
12342
|
const nextPages = sortGraphPages([
|
|
@@ -11936,7 +12351,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
11936
12351
|
sources: previousGraph?.sources ?? [],
|
|
11937
12352
|
pages: nextPages
|
|
11938
12353
|
};
|
|
11939
|
-
await
|
|
12354
|
+
await fs17.writeFile(path21.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
|
|
11940
12355
|
await writeApprovalManifest(paths, {
|
|
11941
12356
|
approvalId,
|
|
11942
12357
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11965,9 +12380,9 @@ async function executeQuery(rootDir, question, format) {
|
|
|
11965
12380
|
const searchResults = searchPages(paths.searchDbPath, question, 5);
|
|
11966
12381
|
const excerpts = await Promise.all(
|
|
11967
12382
|
searchResults.map(async (result) => {
|
|
11968
|
-
const absolutePath =
|
|
12383
|
+
const absolutePath = path21.join(paths.wikiDir, result.path);
|
|
11969
12384
|
try {
|
|
11970
|
-
const content = await
|
|
12385
|
+
const content = await fs17.readFile(absolutePath, "utf8");
|
|
11971
12386
|
const parsed = matter9(content);
|
|
11972
12387
|
return `# ${result.title}
|
|
11973
12388
|
${truncate(normalizeWhitespace(parsed.content), 1200)}`;
|
|
@@ -12146,10 +12561,62 @@ function updateCandidateHistory(compileState, page, deleted = false) {
|
|
|
12146
12561
|
function sortGraphPages(pages) {
|
|
12147
12562
|
return [...pages].sort((left, right) => left.path.localeCompare(right.path) || left.title.localeCompare(right.title));
|
|
12148
12563
|
}
|
|
12564
|
+
function computeUnifiedDiff(current, staged, label) {
|
|
12565
|
+
const currentLines = current.split("\n");
|
|
12566
|
+
const stagedLines = staged.split("\n");
|
|
12567
|
+
const output = [`--- a/${label}`, `+++ b/${label}`];
|
|
12568
|
+
const n = currentLines.length;
|
|
12569
|
+
const m = stagedLines.length;
|
|
12570
|
+
const dp = Array.from({ length: n + 1 }, () => Array(m + 1).fill(0));
|
|
12571
|
+
for (let i2 = n - 1; i2 >= 0; i2--) {
|
|
12572
|
+
for (let j2 = m - 1; j2 >= 0; j2--) {
|
|
12573
|
+
dp[i2][j2] = currentLines[i2] === stagedLines[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
12574
|
+
}
|
|
12575
|
+
}
|
|
12576
|
+
let i = 0;
|
|
12577
|
+
let j = 0;
|
|
12578
|
+
while (i < n || j < m) {
|
|
12579
|
+
if (i < n && j < m && currentLines[i] === stagedLines[j]) {
|
|
12580
|
+
output.push(` ${currentLines[i]}`);
|
|
12581
|
+
i++;
|
|
12582
|
+
j++;
|
|
12583
|
+
} else if (j < m && (i >= n || dp[i][j + 1] >= dp[i + 1][j])) {
|
|
12584
|
+
output.push(`+${stagedLines[j]}`);
|
|
12585
|
+
j++;
|
|
12586
|
+
} else {
|
|
12587
|
+
output.push(`-${currentLines[i]}`);
|
|
12588
|
+
i++;
|
|
12589
|
+
}
|
|
12590
|
+
}
|
|
12591
|
+
return output.join("\n");
|
|
12592
|
+
}
|
|
12593
|
+
function computeChangeSummary(current, staged, changeType) {
|
|
12594
|
+
if (changeType === "create") return "New page";
|
|
12595
|
+
if (changeType === "delete") return "Removed page";
|
|
12596
|
+
if (changeType === "promote") return "Promoted from candidate";
|
|
12597
|
+
if (!current || !staged) return "Updated page";
|
|
12598
|
+
const currentParsed = matter9(current);
|
|
12599
|
+
const stagedParsed = matter9(staged);
|
|
12600
|
+
const changes = [];
|
|
12601
|
+
const currentTags = currentParsed.data.tags ?? [];
|
|
12602
|
+
const stagedTags = stagedParsed.data.tags ?? [];
|
|
12603
|
+
const addedTags = stagedTags.filter((t) => !currentTags.includes(t));
|
|
12604
|
+
const removedTags = currentTags.filter((t) => !stagedTags.includes(t));
|
|
12605
|
+
if (addedTags.length) changes.push(`added ${addedTags.length} tag(s)`);
|
|
12606
|
+
if (removedTags.length) changes.push(`removed ${removedTags.length} tag(s)`);
|
|
12607
|
+
if (currentParsed.data.title !== stagedParsed.data.title) changes.push("updated title");
|
|
12608
|
+
const currentLines = currentParsed.content.trim().split("\n").length;
|
|
12609
|
+
const stagedLines = stagedParsed.content.trim().split("\n").length;
|
|
12610
|
+
const lineDelta = stagedLines - currentLines;
|
|
12611
|
+
if (lineDelta > 0) changes.push(`added ${lineDelta} line(s)`);
|
|
12612
|
+
else if (lineDelta < 0) changes.push(`removed ${Math.abs(lineDelta)} line(s)`);
|
|
12613
|
+
else if (currentParsed.content !== stagedParsed.content) changes.push("modified content");
|
|
12614
|
+
return changes.length ? changes.join(", ") : "no visible changes";
|
|
12615
|
+
}
|
|
12149
12616
|
async function listApprovals(rootDir) {
|
|
12150
12617
|
const { paths } = await loadVaultConfig(rootDir);
|
|
12151
12618
|
const manifests = await Promise.all(
|
|
12152
|
-
(await
|
|
12619
|
+
(await fs17.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
12153
12620
|
try {
|
|
12154
12621
|
return await readApprovalManifest(paths, entry.name);
|
|
12155
12622
|
} catch {
|
|
@@ -12159,19 +12626,24 @@ async function listApprovals(rootDir) {
|
|
|
12159
12626
|
);
|
|
12160
12627
|
return manifests.filter((manifest) => Boolean(manifest)).map(approvalSummary).sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
12161
12628
|
}
|
|
12162
|
-
async function readApproval(rootDir, approvalId) {
|
|
12629
|
+
async function readApproval(rootDir, approvalId, options) {
|
|
12163
12630
|
const { paths } = await loadVaultConfig(rootDir);
|
|
12164
12631
|
const manifest = await readApprovalManifest(paths, approvalId);
|
|
12165
12632
|
const details = await Promise.all(
|
|
12166
12633
|
manifest.entries.map(async (entry) => {
|
|
12167
12634
|
const currentPath = entry.previousPath ?? entry.nextPath;
|
|
12168
|
-
const currentContent = currentPath ? await
|
|
12169
|
-
const stagedContent = entry.nextPath ? await
|
|
12170
|
-
|
|
12635
|
+
const currentContent = currentPath ? await fs17.readFile(path21.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
|
|
12636
|
+
const stagedContent = entry.nextPath ? await fs17.readFile(path21.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
|
|
12637
|
+
const detail = {
|
|
12171
12638
|
...entry,
|
|
12172
12639
|
currentContent,
|
|
12173
12640
|
stagedContent
|
|
12174
12641
|
};
|
|
12642
|
+
detail.changeSummary = computeChangeSummary(detail.currentContent, detail.stagedContent, detail.changeType);
|
|
12643
|
+
if (options?.diff && detail.currentContent && detail.stagedContent) {
|
|
12644
|
+
detail.diff = computeUnifiedDiff(detail.currentContent, detail.stagedContent, detail.nextPath ?? detail.pageId);
|
|
12645
|
+
}
|
|
12646
|
+
return detail;
|
|
12175
12647
|
})
|
|
12176
12648
|
);
|
|
12177
12649
|
return {
|
|
@@ -12194,26 +12666,26 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
|
|
|
12194
12666
|
if (!entry.nextPath) {
|
|
12195
12667
|
throw new Error(`Approval entry ${entry.pageId} is missing a staged path.`);
|
|
12196
12668
|
}
|
|
12197
|
-
const stagedAbsolutePath =
|
|
12198
|
-
const stagedContent = await
|
|
12199
|
-
const targetAbsolutePath =
|
|
12200
|
-
await ensureDir(
|
|
12201
|
-
await
|
|
12669
|
+
const stagedAbsolutePath = path21.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
|
|
12670
|
+
const stagedContent = await fs17.readFile(stagedAbsolutePath, "utf8");
|
|
12671
|
+
const targetAbsolutePath = path21.join(paths.wikiDir, entry.nextPath);
|
|
12672
|
+
await ensureDir(path21.dirname(targetAbsolutePath));
|
|
12673
|
+
await fs17.writeFile(targetAbsolutePath, stagedContent, "utf8");
|
|
12202
12674
|
if (entry.changeType === "promote" && entry.previousPath) {
|
|
12203
|
-
await
|
|
12675
|
+
await fs17.rm(path21.join(paths.wikiDir, entry.previousPath), { force: true });
|
|
12204
12676
|
}
|
|
12205
12677
|
const nextPage = bundleGraph?.pages.find((page) => page.id === entry.pageId && page.path === entry.nextPath) ?? parseStoredPage(entry.nextPath, stagedContent);
|
|
12206
12678
|
if (nextPage.kind === "output" && nextPage.outputAssets?.length) {
|
|
12207
|
-
const outputAssetDir =
|
|
12208
|
-
await
|
|
12679
|
+
const outputAssetDir = path21.join(paths.wikiDir, "outputs", "assets", path21.basename(nextPage.path, ".md"));
|
|
12680
|
+
await fs17.rm(outputAssetDir, { recursive: true, force: true });
|
|
12209
12681
|
for (const asset of nextPage.outputAssets) {
|
|
12210
|
-
const stagedAssetPath =
|
|
12682
|
+
const stagedAssetPath = path21.join(paths.approvalsDir, approvalId, "wiki", asset.path);
|
|
12211
12683
|
if (!await fileExists(stagedAssetPath)) {
|
|
12212
12684
|
continue;
|
|
12213
12685
|
}
|
|
12214
|
-
const targetAssetPath =
|
|
12215
|
-
await ensureDir(
|
|
12216
|
-
await
|
|
12686
|
+
const targetAssetPath = path21.join(paths.wikiDir, asset.path);
|
|
12687
|
+
await ensureDir(path21.dirname(targetAssetPath));
|
|
12688
|
+
await fs17.copyFile(stagedAssetPath, targetAssetPath);
|
|
12217
12689
|
}
|
|
12218
12690
|
}
|
|
12219
12691
|
nextPages = nextPages.filter(
|
|
@@ -12224,10 +12696,10 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
|
|
|
12224
12696
|
} else {
|
|
12225
12697
|
const deletedPage = nextPages.find((page) => page.id === entry.pageId || page.path === entry.previousPath) ?? bundleGraph?.pages.find((page) => page.id === entry.pageId || page.path === entry.previousPath) ?? null;
|
|
12226
12698
|
if (entry.previousPath) {
|
|
12227
|
-
await
|
|
12699
|
+
await fs17.rm(path21.join(paths.wikiDir, entry.previousPath), { force: true });
|
|
12228
12700
|
}
|
|
12229
12701
|
if (deletedPage?.kind === "output") {
|
|
12230
|
-
await
|
|
12702
|
+
await fs17.rm(path21.join(paths.wikiDir, "outputs", "assets", path21.basename(deletedPage.path, ".md")), {
|
|
12231
12703
|
recursive: true,
|
|
12232
12704
|
force: true
|
|
12233
12705
|
});
|
|
@@ -12318,7 +12790,7 @@ async function promoteCandidate(rootDir, target) {
|
|
|
12318
12790
|
const { paths } = await loadVaultConfig(rootDir);
|
|
12319
12791
|
const graph = await readJsonFile(paths.graphPath);
|
|
12320
12792
|
const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
|
|
12321
|
-
const raw = await
|
|
12793
|
+
const raw = await fs17.readFile(path21.join(paths.wikiDir, candidate.path), "utf8");
|
|
12322
12794
|
const parsed = matter9(raw);
|
|
12323
12795
|
const nextUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12324
12796
|
const nextContent = matter9.stringify(parsed.content, {
|
|
@@ -12330,10 +12802,10 @@ async function promoteCandidate(rootDir, target) {
|
|
|
12330
12802
|
)
|
|
12331
12803
|
});
|
|
12332
12804
|
const nextPath = candidateActivePath(candidate);
|
|
12333
|
-
const nextAbsolutePath =
|
|
12334
|
-
await ensureDir(
|
|
12335
|
-
await
|
|
12336
|
-
await
|
|
12805
|
+
const nextAbsolutePath = path21.join(paths.wikiDir, nextPath);
|
|
12806
|
+
await ensureDir(path21.dirname(nextAbsolutePath));
|
|
12807
|
+
await fs17.writeFile(nextAbsolutePath, nextContent, "utf8");
|
|
12808
|
+
await fs17.rm(path21.join(paths.wikiDir, candidate.path), { force: true });
|
|
12337
12809
|
const nextPage = parseStoredPage(nextPath, nextContent, { createdAt: candidate.createdAt, updatedAt: nextUpdatedAt });
|
|
12338
12810
|
const nextPages = sortGraphPages(
|
|
12339
12811
|
(graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path).concat(nextPage)
|
|
@@ -12378,7 +12850,7 @@ async function archiveCandidate(rootDir, target) {
|
|
|
12378
12850
|
const { paths } = await loadVaultConfig(rootDir);
|
|
12379
12851
|
const graph = await readJsonFile(paths.graphPath);
|
|
12380
12852
|
const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
|
|
12381
|
-
await
|
|
12853
|
+
await fs17.rm(path21.join(paths.wikiDir, candidate.path), { force: true });
|
|
12382
12854
|
const nextPages = sortGraphPages((graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path));
|
|
12383
12855
|
const nextGraph = {
|
|
12384
12856
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12417,18 +12889,18 @@ async function archiveCandidate(rootDir, target) {
|
|
|
12417
12889
|
}
|
|
12418
12890
|
async function ensureObsidianWorkspace(rootDir) {
|
|
12419
12891
|
const { config } = await loadVaultConfig(rootDir);
|
|
12420
|
-
const obsidianDir =
|
|
12892
|
+
const obsidianDir = path21.join(rootDir, ".obsidian");
|
|
12421
12893
|
const projectIds = projectEntries(config).map((project) => project.id);
|
|
12422
12894
|
await ensureDir(obsidianDir);
|
|
12423
12895
|
await Promise.all([
|
|
12424
|
-
writeJsonFile(
|
|
12896
|
+
writeJsonFile(path21.join(obsidianDir, "app.json"), {
|
|
12425
12897
|
alwaysUpdateLinks: true,
|
|
12426
12898
|
newFileLocation: "folder",
|
|
12427
12899
|
newFileFolderPath: "wiki/insights",
|
|
12428
12900
|
useMarkdownLinks: false,
|
|
12429
12901
|
attachmentFolderPath: "raw/assets"
|
|
12430
12902
|
}),
|
|
12431
|
-
writeJsonFile(
|
|
12903
|
+
writeJsonFile(path21.join(obsidianDir, "core-plugins.json"), [
|
|
12432
12904
|
"file-explorer",
|
|
12433
12905
|
"global-search",
|
|
12434
12906
|
"switcher",
|
|
@@ -12438,7 +12910,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
12438
12910
|
"tag-pane",
|
|
12439
12911
|
"page-preview"
|
|
12440
12912
|
]),
|
|
12441
|
-
writeJsonFile(
|
|
12913
|
+
writeJsonFile(path21.join(obsidianDir, "graph.json"), {
|
|
12442
12914
|
"collapse-filter": false,
|
|
12443
12915
|
search: "",
|
|
12444
12916
|
showTags: true,
|
|
@@ -12450,7 +12922,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
12450
12922
|
})),
|
|
12451
12923
|
localJumps: false
|
|
12452
12924
|
}),
|
|
12453
|
-
writeJsonFile(
|
|
12925
|
+
writeJsonFile(path21.join(obsidianDir, "workspace.json"), {
|
|
12454
12926
|
active: "root",
|
|
12455
12927
|
lastOpenFiles: ["wiki/index.md", "wiki/projects/index.md", "wiki/candidates/index.md", "wiki/insights/index.md"],
|
|
12456
12928
|
left: {
|
|
@@ -12465,7 +12937,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
12465
12937
|
async function initVault(rootDir, options = {}) {
|
|
12466
12938
|
const { paths } = await initWorkspace(rootDir);
|
|
12467
12939
|
await installConfiguredAgents(rootDir);
|
|
12468
|
-
const insightsIndexPath =
|
|
12940
|
+
const insightsIndexPath = path21.join(paths.wikiDir, "insights", "index.md");
|
|
12469
12941
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12470
12942
|
await writeFileIfChanged(
|
|
12471
12943
|
insightsIndexPath,
|
|
@@ -12501,7 +12973,7 @@ async function initVault(rootDir, options = {}) {
|
|
|
12501
12973
|
)
|
|
12502
12974
|
);
|
|
12503
12975
|
await writeFileIfChanged(
|
|
12504
|
-
|
|
12976
|
+
path21.join(paths.wikiDir, "projects", "index.md"),
|
|
12505
12977
|
matter9.stringify(["# Projects", "", "- Run `swarmvault compile` to build project rollups.", ""].join("\n"), {
|
|
12506
12978
|
page_id: "projects:index",
|
|
12507
12979
|
kind: "index",
|
|
@@ -12523,7 +12995,7 @@ async function initVault(rootDir, options = {}) {
|
|
|
12523
12995
|
})
|
|
12524
12996
|
);
|
|
12525
12997
|
await writeFileIfChanged(
|
|
12526
|
-
|
|
12998
|
+
path21.join(paths.wikiDir, "candidates", "index.md"),
|
|
12527
12999
|
matter9.stringify(["# Candidates", "", "- Run `swarmvault compile` to stage candidate pages.", ""].join("\n"), {
|
|
12528
13000
|
page_id: "candidates:index",
|
|
12529
13001
|
kind: "index",
|
|
@@ -12659,7 +13131,7 @@ async function compileVault(rootDir, options = {}) {
|
|
|
12659
13131
|
),
|
|
12660
13132
|
Promise.all(
|
|
12661
13133
|
clean.map(async (manifest) => {
|
|
12662
|
-
const cached = await readJsonFile(
|
|
13134
|
+
const cached = await readJsonFile(path21.join(paths.analysesDir, `${manifest.sourceId}.json`));
|
|
12663
13135
|
if (cached) {
|
|
12664
13136
|
return cached;
|
|
12665
13137
|
}
|
|
@@ -12683,22 +13155,22 @@ async function compileVault(rootDir, options = {}) {
|
|
|
12683
13155
|
}
|
|
12684
13156
|
const enriched = enrichResolvedCodeImports(manifest, analysis, codeIndex);
|
|
12685
13157
|
if (analysisSignature(enriched) !== analysisSignature(analysis)) {
|
|
12686
|
-
await writeJsonFile(
|
|
13158
|
+
await writeJsonFile(path21.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
|
|
12687
13159
|
}
|
|
12688
13160
|
return enriched;
|
|
12689
13161
|
})
|
|
12690
13162
|
);
|
|
12691
13163
|
await Promise.all([
|
|
12692
|
-
ensureDir(
|
|
12693
|
-
ensureDir(
|
|
12694
|
-
ensureDir(
|
|
12695
|
-
ensureDir(
|
|
12696
|
-
ensureDir(
|
|
12697
|
-
ensureDir(
|
|
12698
|
-
ensureDir(
|
|
12699
|
-
ensureDir(
|
|
12700
|
-
ensureDir(
|
|
12701
|
-
ensureDir(
|
|
13164
|
+
ensureDir(path21.join(paths.wikiDir, "sources")),
|
|
13165
|
+
ensureDir(path21.join(paths.wikiDir, "code")),
|
|
13166
|
+
ensureDir(path21.join(paths.wikiDir, "concepts")),
|
|
13167
|
+
ensureDir(path21.join(paths.wikiDir, "entities")),
|
|
13168
|
+
ensureDir(path21.join(paths.wikiDir, "outputs")),
|
|
13169
|
+
ensureDir(path21.join(paths.wikiDir, "projects")),
|
|
13170
|
+
ensureDir(path21.join(paths.wikiDir, "insights")),
|
|
13171
|
+
ensureDir(path21.join(paths.wikiDir, "candidates")),
|
|
13172
|
+
ensureDir(path21.join(paths.wikiDir, "candidates", "concepts")),
|
|
13173
|
+
ensureDir(path21.join(paths.wikiDir, "candidates", "entities"))
|
|
12702
13174
|
]);
|
|
12703
13175
|
const sync = await syncVaultArtifacts(rootDir, {
|
|
12704
13176
|
schemas,
|
|
@@ -12845,7 +13317,7 @@ async function queryVault(rootDir, options) {
|
|
|
12845
13317
|
assetFiles: staged.assetFiles
|
|
12846
13318
|
}
|
|
12847
13319
|
]);
|
|
12848
|
-
stagedPath =
|
|
13320
|
+
stagedPath = path21.join(approval.approvalDir, "wiki", staged.page.path);
|
|
12849
13321
|
savedPageId = staged.page.id;
|
|
12850
13322
|
approvalId = approval.approvalId;
|
|
12851
13323
|
approvalDir = approval.approvalDir;
|
|
@@ -13101,9 +13573,9 @@ ${orchestrationNotes.join("\n")}
|
|
|
13101
13573
|
approvalId = approval.approvalId;
|
|
13102
13574
|
approvalDir = approval.approvalDir;
|
|
13103
13575
|
stepResults.forEach((result, index) => {
|
|
13104
|
-
result.stagedPath =
|
|
13576
|
+
result.stagedPath = path21.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
|
|
13105
13577
|
});
|
|
13106
|
-
stagedHubPath =
|
|
13578
|
+
stagedHubPath = path21.join(approval.approvalDir, "wiki", hubPage.path);
|
|
13107
13579
|
} else {
|
|
13108
13580
|
await refreshVaultAfterOutputSave(rootDir);
|
|
13109
13581
|
}
|
|
@@ -13190,11 +13662,11 @@ async function benchmarkVault(rootDir, options = {}) {
|
|
|
13190
13662
|
}
|
|
13191
13663
|
}
|
|
13192
13664
|
for (const page of graph.pages) {
|
|
13193
|
-
const absolutePath =
|
|
13665
|
+
const absolutePath = path21.join(paths.wikiDir, page.path);
|
|
13194
13666
|
if (!await fileExists(absolutePath)) {
|
|
13195
13667
|
continue;
|
|
13196
13668
|
}
|
|
13197
|
-
const parsed = matter9(await
|
|
13669
|
+
const parsed = matter9(await fs17.readFile(absolutePath, "utf8"));
|
|
13198
13670
|
pageContentsById.set(page.id, parsed.content);
|
|
13199
13671
|
}
|
|
13200
13672
|
const configuredQuestions = (config.benchmark?.questions ?? []).map((question) => normalizeWhitespace(question)).filter(Boolean);
|
|
@@ -13239,7 +13711,7 @@ async function listGraphHyperedges(rootDir, target, limit = 25) {
|
|
|
13239
13711
|
}
|
|
13240
13712
|
async function readGraphReport(rootDir) {
|
|
13241
13713
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13242
|
-
return readJsonFile(
|
|
13714
|
+
return readJsonFile(path21.join(paths.wikiDir, "graph", "report.json"));
|
|
13243
13715
|
}
|
|
13244
13716
|
async function listGodNodes(rootDir, limit = 10) {
|
|
13245
13717
|
const graph = await ensureCompiledGraph(rootDir);
|
|
@@ -13252,15 +13724,15 @@ async function listPages(rootDir) {
|
|
|
13252
13724
|
}
|
|
13253
13725
|
async function readPage(rootDir, relativePath) {
|
|
13254
13726
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13255
|
-
const absolutePath =
|
|
13727
|
+
const absolutePath = path21.resolve(paths.wikiDir, relativePath);
|
|
13256
13728
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
13257
13729
|
return null;
|
|
13258
13730
|
}
|
|
13259
|
-
const raw = await
|
|
13731
|
+
const raw = await fs17.readFile(absolutePath, "utf8");
|
|
13260
13732
|
const parsed = matter9(raw);
|
|
13261
13733
|
return {
|
|
13262
13734
|
path: relativePath,
|
|
13263
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
13735
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path21.basename(relativePath, path21.extname(relativePath)),
|
|
13264
13736
|
frontmatter: parsed.data,
|
|
13265
13737
|
content: parsed.content
|
|
13266
13738
|
};
|
|
@@ -13296,7 +13768,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
13296
13768
|
severity: "warning",
|
|
13297
13769
|
code: "stale_page",
|
|
13298
13770
|
message: `Page ${page.title} is stale because the vault schema changed.`,
|
|
13299
|
-
pagePath:
|
|
13771
|
+
pagePath: path21.join(paths.wikiDir, page.path),
|
|
13300
13772
|
relatedPageIds: [page.id]
|
|
13301
13773
|
});
|
|
13302
13774
|
}
|
|
@@ -13307,7 +13779,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
13307
13779
|
severity: "warning",
|
|
13308
13780
|
code: "stale_page",
|
|
13309
13781
|
message: `Page ${page.title} is stale because source ${sourceId} changed.`,
|
|
13310
|
-
pagePath:
|
|
13782
|
+
pagePath: path21.join(paths.wikiDir, page.path),
|
|
13311
13783
|
relatedSourceIds: [sourceId],
|
|
13312
13784
|
relatedPageIds: [page.id]
|
|
13313
13785
|
});
|
|
@@ -13318,13 +13790,13 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
13318
13790
|
severity: "info",
|
|
13319
13791
|
code: "orphan_page",
|
|
13320
13792
|
message: `Page ${page.title} has no backlinks.`,
|
|
13321
|
-
pagePath:
|
|
13793
|
+
pagePath: path21.join(paths.wikiDir, page.path),
|
|
13322
13794
|
relatedPageIds: [page.id]
|
|
13323
13795
|
});
|
|
13324
13796
|
}
|
|
13325
|
-
const absolutePath =
|
|
13797
|
+
const absolutePath = path21.join(paths.wikiDir, page.path);
|
|
13326
13798
|
if (await fileExists(absolutePath)) {
|
|
13327
|
-
const content = await
|
|
13799
|
+
const content = await fs17.readFile(absolutePath, "utf8");
|
|
13328
13800
|
if (content.includes("## Claims")) {
|
|
13329
13801
|
const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
|
|
13330
13802
|
if (uncited.length) {
|
|
@@ -13367,11 +13839,43 @@ async function lintVault(rootDir, options = {}) {
|
|
|
13367
13839
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13368
13840
|
success: true,
|
|
13369
13841
|
lintFindingCount: findings2.length,
|
|
13370
|
-
lines: [
|
|
13842
|
+
lines: [
|
|
13843
|
+
`findings=${findings2.length}`,
|
|
13844
|
+
`deep=${Boolean(options.deep)}`,
|
|
13845
|
+
`web=${Boolean(options.web)}`,
|
|
13846
|
+
`conflicts=${Boolean(options.conflicts)}`
|
|
13847
|
+
]
|
|
13371
13848
|
});
|
|
13372
13849
|
return findings2;
|
|
13373
13850
|
}
|
|
13851
|
+
const contradictionFindings = options.conflicts ? graph.edges.filter((edge) => edge.relation === "contradicts").map((edge) => {
|
|
13852
|
+
const sourceIdA = edge.provenance[0] ?? edge.source.replace(/^source:/, "");
|
|
13853
|
+
const sourceIdB = edge.provenance[1] ?? edge.target.replace(/^source:/, "");
|
|
13854
|
+
return {
|
|
13855
|
+
severity: "warning",
|
|
13856
|
+
code: "contradiction",
|
|
13857
|
+
message: `Contradicting claims detected between source "${sourceIdA}" and source "${sourceIdB}".`,
|
|
13858
|
+
relatedSourceIds: [sourceIdA, sourceIdB]
|
|
13859
|
+
};
|
|
13860
|
+
}) : [];
|
|
13861
|
+
if (options.conflicts && !options.deep) {
|
|
13862
|
+
await recordSession(rootDir, {
|
|
13863
|
+
operation: "lint",
|
|
13864
|
+
title: `Linted ${graph.pages.length} page(s)`,
|
|
13865
|
+
startedAt,
|
|
13866
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13867
|
+
success: true,
|
|
13868
|
+
relatedPageIds: graph.pages.map((page) => page.id),
|
|
13869
|
+
relatedSourceIds: uniqueStrings3(graph.pages.flatMap((page) => page.sourceIds)),
|
|
13870
|
+
lintFindingCount: contradictionFindings.length,
|
|
13871
|
+
lines: [`findings=${contradictionFindings.length}`, `deep=false`, `web=false`, `conflicts=true`]
|
|
13872
|
+
});
|
|
13873
|
+
return contradictionFindings;
|
|
13874
|
+
}
|
|
13374
13875
|
const findings = await structuralLintFindings(rootDir, paths, graph, schemas, manifests, sourceProjects);
|
|
13876
|
+
if (options.conflicts) {
|
|
13877
|
+
findings.push(...contradictionFindings);
|
|
13878
|
+
}
|
|
13375
13879
|
if (options.deep) {
|
|
13376
13880
|
findings.push(...await runDeepLint(rootDir, findings, { web: options.web }));
|
|
13377
13881
|
}
|
|
@@ -13386,7 +13890,12 @@ async function lintVault(rootDir, options = {}) {
|
|
|
13386
13890
|
relatedPageIds: graph.pages.map((page) => page.id),
|
|
13387
13891
|
relatedSourceIds: uniqueStrings3(graph.pages.flatMap((page) => page.sourceIds)),
|
|
13388
13892
|
lintFindingCount: findings.length,
|
|
13389
|
-
lines: [
|
|
13893
|
+
lines: [
|
|
13894
|
+
`findings=${findings.length}`,
|
|
13895
|
+
`deep=${Boolean(options.deep)}`,
|
|
13896
|
+
`web=${Boolean(options.web)}`,
|
|
13897
|
+
`conflicts=${Boolean(options.conflicts)}`
|
|
13898
|
+
]
|
|
13390
13899
|
});
|
|
13391
13900
|
return findings;
|
|
13392
13901
|
}
|
|
@@ -13404,7 +13913,7 @@ async function bootstrapDemo(rootDir, input) {
|
|
|
13404
13913
|
}
|
|
13405
13914
|
|
|
13406
13915
|
// src/mcp.ts
|
|
13407
|
-
var SERVER_VERSION = "0.1.
|
|
13916
|
+
var SERVER_VERSION = "0.1.31";
|
|
13408
13917
|
async function createMcpServer(rootDir) {
|
|
13409
13918
|
const server = new McpServer({
|
|
13410
13919
|
name: "swarmvault",
|
|
@@ -13675,7 +14184,7 @@ async function createMcpServer(rootDir) {
|
|
|
13675
14184
|
},
|
|
13676
14185
|
async () => {
|
|
13677
14186
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13678
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
14187
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path22.relative(paths.sessionsDir, filePath))).sort();
|
|
13679
14188
|
return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
|
|
13680
14189
|
}
|
|
13681
14190
|
);
|
|
@@ -13708,8 +14217,8 @@ async function createMcpServer(rootDir) {
|
|
|
13708
14217
|
return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
|
|
13709
14218
|
}
|
|
13710
14219
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13711
|
-
const absolutePath =
|
|
13712
|
-
return asTextResource(`swarmvault://pages/${encodedPath}`, await
|
|
14220
|
+
const absolutePath = path22.resolve(paths.wikiDir, relativePath);
|
|
14221
|
+
return asTextResource(`swarmvault://pages/${encodedPath}`, await fs18.readFile(absolutePath, "utf8"));
|
|
13713
14222
|
}
|
|
13714
14223
|
);
|
|
13715
14224
|
server.registerResource(
|
|
@@ -13717,11 +14226,11 @@ async function createMcpServer(rootDir) {
|
|
|
13717
14226
|
new ResourceTemplate("swarmvault://sessions/{path}", {
|
|
13718
14227
|
list: async () => {
|
|
13719
14228
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13720
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
14229
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path22.relative(paths.sessionsDir, filePath))).sort();
|
|
13721
14230
|
return {
|
|
13722
14231
|
resources: files.map((relativePath) => ({
|
|
13723
14232
|
uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
|
|
13724
|
-
name:
|
|
14233
|
+
name: path22.basename(relativePath, ".md"),
|
|
13725
14234
|
title: relativePath,
|
|
13726
14235
|
description: "SwarmVault session artifact",
|
|
13727
14236
|
mimeType: "text/markdown"
|
|
@@ -13738,11 +14247,11 @@ async function createMcpServer(rootDir) {
|
|
|
13738
14247
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13739
14248
|
const encodedPath = typeof variables.path === "string" ? variables.path : "";
|
|
13740
14249
|
const relativePath = decodeURIComponent(encodedPath);
|
|
13741
|
-
const absolutePath =
|
|
14250
|
+
const absolutePath = path22.resolve(paths.sessionsDir, relativePath);
|
|
13742
14251
|
if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
|
|
13743
14252
|
return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
|
|
13744
14253
|
}
|
|
13745
|
-
return asTextResource(`swarmvault://sessions/${encodedPath}`, await
|
|
14254
|
+
return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs18.readFile(absolutePath, "utf8"));
|
|
13746
14255
|
}
|
|
13747
14256
|
);
|
|
13748
14257
|
return server;
|
|
@@ -13790,13 +14299,13 @@ function asTextResource(uri, text) {
|
|
|
13790
14299
|
}
|
|
13791
14300
|
|
|
13792
14301
|
// src/schedule.ts
|
|
13793
|
-
import
|
|
13794
|
-
import
|
|
14302
|
+
import fs19 from "fs/promises";
|
|
14303
|
+
import path23 from "path";
|
|
13795
14304
|
function scheduleStatePath(schedulesDir, jobId) {
|
|
13796
|
-
return
|
|
14305
|
+
return path23.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
13797
14306
|
}
|
|
13798
14307
|
function scheduleLockPath(schedulesDir, jobId) {
|
|
13799
|
-
return
|
|
14308
|
+
return path23.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
|
|
13800
14309
|
}
|
|
13801
14310
|
function parseEveryDuration(value) {
|
|
13802
14311
|
const match = value.trim().match(/^(\d+)(m|h|d)$/i);
|
|
@@ -13899,13 +14408,13 @@ async function acquireJobLease(rootDir, jobId) {
|
|
|
13899
14408
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13900
14409
|
const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
|
|
13901
14410
|
await ensureDir(paths.schedulesDir);
|
|
13902
|
-
const handle = await
|
|
14411
|
+
const handle = await fs19.open(leasePath, "wx");
|
|
13903
14412
|
await handle.writeFile(`${process.pid}
|
|
13904
14413
|
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
13905
14414
|
`);
|
|
13906
14415
|
await handle.close();
|
|
13907
14416
|
return async () => {
|
|
13908
|
-
await
|
|
14417
|
+
await fs19.rm(leasePath, { force: true });
|
|
13909
14418
|
};
|
|
13910
14419
|
}
|
|
13911
14420
|
async function listSchedules(rootDir) {
|
|
@@ -14053,15 +14562,15 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
|
|
|
14053
14562
|
|
|
14054
14563
|
// src/viewer.ts
|
|
14055
14564
|
import { execFile } from "child_process";
|
|
14056
|
-
import
|
|
14565
|
+
import fs20 from "fs/promises";
|
|
14057
14566
|
import http from "http";
|
|
14058
|
-
import
|
|
14567
|
+
import path25 from "path";
|
|
14059
14568
|
import { promisify } from "util";
|
|
14060
14569
|
import matter10 from "gray-matter";
|
|
14061
14570
|
import mime2 from "mime-types";
|
|
14062
14571
|
|
|
14063
14572
|
// src/watch.ts
|
|
14064
|
-
import
|
|
14573
|
+
import path24 from "path";
|
|
14065
14574
|
import process3 from "process";
|
|
14066
14575
|
import chokidar from "chokidar";
|
|
14067
14576
|
var MAX_BACKOFF_MS = 3e4;
|
|
@@ -14069,15 +14578,15 @@ var BACKOFF_THRESHOLD = 3;
|
|
|
14069
14578
|
var CRITICAL_THRESHOLD = 10;
|
|
14070
14579
|
var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
|
|
14071
14580
|
function withinRoot2(rootPath, targetPath) {
|
|
14072
|
-
const relative =
|
|
14073
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
14581
|
+
const relative = path24.relative(rootPath, targetPath);
|
|
14582
|
+
return relative === "" || !relative.startsWith("..") && !path24.isAbsolute(relative);
|
|
14074
14583
|
}
|
|
14075
14584
|
function hasIgnoredRepoSegment(baseDir, targetPath) {
|
|
14076
|
-
const relativePath =
|
|
14585
|
+
const relativePath = path24.relative(baseDir, targetPath);
|
|
14077
14586
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
14078
14587
|
return false;
|
|
14079
14588
|
}
|
|
14080
|
-
return relativePath.split(
|
|
14589
|
+
return relativePath.split(path24.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
|
|
14081
14590
|
}
|
|
14082
14591
|
function workspaceIgnoreRoots(rootDir, paths) {
|
|
14083
14592
|
return [
|
|
@@ -14086,16 +14595,16 @@ function workspaceIgnoreRoots(rootDir, paths) {
|
|
|
14086
14595
|
paths.stateDir,
|
|
14087
14596
|
paths.agentDir,
|
|
14088
14597
|
paths.inboxDir,
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
14092
|
-
].map((candidate) =>
|
|
14598
|
+
path24.join(rootDir, ".claude"),
|
|
14599
|
+
path24.join(rootDir, ".cursor"),
|
|
14600
|
+
path24.join(rootDir, ".obsidian")
|
|
14601
|
+
].map((candidate) => path24.resolve(candidate));
|
|
14093
14602
|
}
|
|
14094
14603
|
async function resolveWatchTargets(rootDir, paths, options) {
|
|
14095
|
-
const targets = /* @__PURE__ */ new Set([
|
|
14604
|
+
const targets = /* @__PURE__ */ new Set([path24.resolve(paths.inboxDir)]);
|
|
14096
14605
|
if (options.repo) {
|
|
14097
14606
|
for (const repoRoot of await listTrackedRepoRoots(rootDir)) {
|
|
14098
|
-
targets.add(
|
|
14607
|
+
targets.add(path24.resolve(repoRoot));
|
|
14099
14608
|
}
|
|
14100
14609
|
}
|
|
14101
14610
|
return [...targets].sort((left, right) => left.localeCompare(right));
|
|
@@ -14225,7 +14734,7 @@ async function watchVault(rootDir, options = {}) {
|
|
|
14225
14734
|
const { paths } = await initWorkspace(rootDir);
|
|
14226
14735
|
const baseDebounceMs = options.debounceMs ?? 900;
|
|
14227
14736
|
const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
|
|
14228
|
-
const inboxWatchRoot =
|
|
14737
|
+
const inboxWatchRoot = path24.resolve(paths.inboxDir);
|
|
14229
14738
|
let watchTargets = await resolveWatchTargets(rootDir, paths, options);
|
|
14230
14739
|
let timer;
|
|
14231
14740
|
let running = false;
|
|
@@ -14239,7 +14748,7 @@ async function watchVault(rootDir, options = {}) {
|
|
|
14239
14748
|
usePolling: true,
|
|
14240
14749
|
interval: 100,
|
|
14241
14750
|
ignored: (targetPath) => {
|
|
14242
|
-
const absolutePath =
|
|
14751
|
+
const absolutePath = path24.resolve(targetPath);
|
|
14243
14752
|
const primaryTarget = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
|
|
14244
14753
|
if (!primaryTarget) {
|
|
14245
14754
|
return false;
|
|
@@ -14423,8 +14932,8 @@ async function watchVault(rootDir, options = {}) {
|
|
|
14423
14932
|
}
|
|
14424
14933
|
};
|
|
14425
14934
|
const reasonForPath = (targetPath) => {
|
|
14426
|
-
const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget,
|
|
14427
|
-
return
|
|
14935
|
+
const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, path24.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
|
|
14936
|
+
return path24.relative(baseDir, targetPath) || ".";
|
|
14428
14937
|
};
|
|
14429
14938
|
watcher.on("add", (filePath) => schedule(`add:${reasonForPath(filePath)}`)).on("change", (filePath) => schedule(`change:${reasonForPath(filePath)}`)).on("unlink", (filePath) => schedule(`unlink:${reasonForPath(filePath)}`)).on("addDir", (dirPath) => schedule(`addDir:${reasonForPath(dirPath)}`)).on("unlinkDir", (dirPath) => schedule(`unlinkDir:${reasonForPath(dirPath)}`)).on("error", (caught) => schedule(`error:${caught instanceof Error ? caught.message : String(caught)}`));
|
|
14430
14939
|
await new Promise((resolve, reject) => {
|
|
@@ -14465,15 +14974,15 @@ async function getWatchStatus(rootDir) {
|
|
|
14465
14974
|
var execFileAsync = promisify(execFile);
|
|
14466
14975
|
async function readViewerPage(rootDir, relativePath) {
|
|
14467
14976
|
const { paths } = await loadVaultConfig(rootDir);
|
|
14468
|
-
const absolutePath =
|
|
14977
|
+
const absolutePath = path25.resolve(paths.wikiDir, relativePath);
|
|
14469
14978
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
14470
14979
|
return null;
|
|
14471
14980
|
}
|
|
14472
|
-
const raw = await
|
|
14981
|
+
const raw = await fs20.readFile(absolutePath, "utf8");
|
|
14473
14982
|
const parsed = matter10(raw);
|
|
14474
14983
|
return {
|
|
14475
14984
|
path: relativePath,
|
|
14476
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
14985
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path25.basename(relativePath, path25.extname(relativePath)),
|
|
14477
14986
|
frontmatter: parsed.data,
|
|
14478
14987
|
content: parsed.content,
|
|
14479
14988
|
assets: normalizeOutputAssets(parsed.data.output_assets)
|
|
@@ -14481,12 +14990,12 @@ async function readViewerPage(rootDir, relativePath) {
|
|
|
14481
14990
|
}
|
|
14482
14991
|
async function readViewerAsset(rootDir, relativePath) {
|
|
14483
14992
|
const { paths } = await loadVaultConfig(rootDir);
|
|
14484
|
-
const absolutePath =
|
|
14993
|
+
const absolutePath = path25.resolve(paths.wikiDir, relativePath);
|
|
14485
14994
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
14486
14995
|
return null;
|
|
14487
14996
|
}
|
|
14488
14997
|
return {
|
|
14489
|
-
buffer: await
|
|
14998
|
+
buffer: await fs20.readFile(absolutePath),
|
|
14490
14999
|
mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
|
|
14491
15000
|
};
|
|
14492
15001
|
}
|
|
@@ -14509,12 +15018,12 @@ async function readJsonBody(request) {
|
|
|
14509
15018
|
return JSON.parse(raw);
|
|
14510
15019
|
}
|
|
14511
15020
|
async function ensureViewerDist(viewerDistDir) {
|
|
14512
|
-
const indexPath =
|
|
15021
|
+
const indexPath = path25.join(viewerDistDir, "index.html");
|
|
14513
15022
|
if (await fileExists(indexPath)) {
|
|
14514
15023
|
return;
|
|
14515
15024
|
}
|
|
14516
|
-
const viewerProjectDir =
|
|
14517
|
-
if (await fileExists(
|
|
15025
|
+
const viewerProjectDir = path25.dirname(viewerDistDir);
|
|
15026
|
+
if (await fileExists(path25.join(viewerProjectDir, "package.json"))) {
|
|
14518
15027
|
await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
|
|
14519
15028
|
}
|
|
14520
15029
|
}
|
|
@@ -14531,7 +15040,7 @@ async function startGraphServer(rootDir, port) {
|
|
|
14531
15040
|
return;
|
|
14532
15041
|
}
|
|
14533
15042
|
response.writeHead(200, { "content-type": "application/json" });
|
|
14534
|
-
response.end(await
|
|
15043
|
+
response.end(await fs20.readFile(paths.graphPath, "utf8"));
|
|
14535
15044
|
return;
|
|
14536
15045
|
}
|
|
14537
15046
|
if (url.pathname === "/api/graph/query") {
|
|
@@ -14588,14 +15097,14 @@ async function startGraphServer(rootDir, port) {
|
|
|
14588
15097
|
return;
|
|
14589
15098
|
}
|
|
14590
15099
|
if (url.pathname === "/api/graph-report") {
|
|
14591
|
-
const reportPath =
|
|
15100
|
+
const reportPath = path25.join(paths.wikiDir, "graph", "report.json");
|
|
14592
15101
|
if (!await fileExists(reportPath)) {
|
|
14593
15102
|
response.writeHead(404, { "content-type": "application/json" });
|
|
14594
15103
|
response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
|
|
14595
15104
|
return;
|
|
14596
15105
|
}
|
|
14597
15106
|
response.writeHead(200, { "content-type": "application/json" });
|
|
14598
|
-
response.end(await
|
|
15107
|
+
response.end(await fs20.readFile(reportPath, "utf8"));
|
|
14599
15108
|
return;
|
|
14600
15109
|
}
|
|
14601
15110
|
if (url.pathname === "/api/watch-status") {
|
|
@@ -14678,8 +15187,8 @@ async function startGraphServer(rootDir, port) {
|
|
|
14678
15187
|
return;
|
|
14679
15188
|
}
|
|
14680
15189
|
const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
14681
|
-
const target =
|
|
14682
|
-
const fallback =
|
|
15190
|
+
const target = path25.join(paths.viewerDistDir, relativePath);
|
|
15191
|
+
const fallback = path25.join(paths.viewerDistDir, "index.html");
|
|
14683
15192
|
const filePath = await fileExists(target) ? target : fallback;
|
|
14684
15193
|
if (!await fileExists(filePath)) {
|
|
14685
15194
|
response.writeHead(503, { "content-type": "text/plain" });
|
|
@@ -14687,7 +15196,7 @@ async function startGraphServer(rootDir, port) {
|
|
|
14687
15196
|
return;
|
|
14688
15197
|
}
|
|
14689
15198
|
response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
|
|
14690
|
-
response.end(await
|
|
15199
|
+
response.end(await fs20.readFile(filePath));
|
|
14691
15200
|
});
|
|
14692
15201
|
await new Promise((resolve) => {
|
|
14693
15202
|
server.listen(effectivePort, resolve);
|
|
@@ -14714,7 +15223,7 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
14714
15223
|
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
14715
15224
|
}
|
|
14716
15225
|
await ensureViewerDist(paths.viewerDistDir);
|
|
14717
|
-
const indexPath =
|
|
15226
|
+
const indexPath = path25.join(paths.viewerDistDir, "index.html");
|
|
14718
15227
|
if (!await fileExists(indexPath)) {
|
|
14719
15228
|
throw new Error("Viewer build not found. Run `pnpm build` first.");
|
|
14720
15229
|
}
|
|
@@ -14740,17 +15249,17 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
14740
15249
|
} : null;
|
|
14741
15250
|
})
|
|
14742
15251
|
);
|
|
14743
|
-
const rawHtml = await
|
|
15252
|
+
const rawHtml = await fs20.readFile(indexPath, "utf8");
|
|
14744
15253
|
const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
|
|
14745
15254
|
const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
|
|
14746
|
-
const scriptPath = scriptMatch?.[1] ?
|
|
14747
|
-
const stylePath = styleMatch?.[1] ?
|
|
15255
|
+
const scriptPath = scriptMatch?.[1] ? path25.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
|
|
15256
|
+
const stylePath = styleMatch?.[1] ? path25.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
|
|
14748
15257
|
if (!scriptPath || !await fileExists(scriptPath)) {
|
|
14749
15258
|
throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
|
|
14750
15259
|
}
|
|
14751
|
-
const script = await
|
|
14752
|
-
const style = stylePath && await fileExists(stylePath) ? await
|
|
14753
|
-
const report = await readJsonFile(
|
|
15260
|
+
const script = await fs20.readFile(scriptPath, "utf8");
|
|
15261
|
+
const style = stylePath && await fileExists(stylePath) ? await fs20.readFile(stylePath, "utf8") : "";
|
|
15262
|
+
const report = await readJsonFile(path25.join(paths.wikiDir, "graph", "report.json"));
|
|
14754
15263
|
const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean), report }, null, 2).replace(/</g, "\\u003c");
|
|
14755
15264
|
const html = [
|
|
14756
15265
|
"<!doctype html>",
|
|
@@ -14769,9 +15278,9 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
14769
15278
|
"</html>",
|
|
14770
15279
|
""
|
|
14771
15280
|
].filter(Boolean).join("\n");
|
|
14772
|
-
await
|
|
14773
|
-
await
|
|
14774
|
-
return
|
|
15281
|
+
await fs20.mkdir(path25.dirname(outputPath), { recursive: true });
|
|
15282
|
+
await fs20.writeFile(outputPath, html, "utf8");
|
|
15283
|
+
return path25.resolve(outputPath);
|
|
14775
15284
|
}
|
|
14776
15285
|
export {
|
|
14777
15286
|
acceptApproval,
|
|
@@ -14817,6 +15326,7 @@ export {
|
|
|
14817
15326
|
loadVaultSchemas,
|
|
14818
15327
|
pathGraphVault,
|
|
14819
15328
|
promoteCandidate,
|
|
15329
|
+
pushGraphNeo4j,
|
|
14820
15330
|
queryGraphVault,
|
|
14821
15331
|
queryVault,
|
|
14822
15332
|
readApproval,
|