@swarmvaultai/engine 0.1.29 → 0.1.30
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 +13 -0
- package/dist/chunk-IAEYFTUS.js +1159 -0
- package/dist/index.d.ts +60 -1
- package/dist/index.js +994 -673
- 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) : "";
|
|
@@ -3488,9 +3928,9 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
|
3488
3928
|
}
|
|
3489
3929
|
|
|
3490
3930
|
// src/extraction.ts
|
|
3491
|
-
import
|
|
3931
|
+
import fs7 from "fs/promises";
|
|
3492
3932
|
import os from "os";
|
|
3493
|
-
import
|
|
3933
|
+
import path7 from "path";
|
|
3494
3934
|
import { strFromU8, unzipSync } from "fflate";
|
|
3495
3935
|
import { JSDOM } from "jsdom";
|
|
3496
3936
|
import { z } from "zod";
|
|
@@ -3572,14 +4012,14 @@ async function materializeAttachmentPath(input) {
|
|
|
3572
4012
|
if (!input.bytes) {
|
|
3573
4013
|
throw new Error("Image extraction requires a file path or bytes.");
|
|
3574
4014
|
}
|
|
3575
|
-
const tempDir = await
|
|
4015
|
+
const tempDir = await fs7.mkdtemp(path7.join(os.tmpdir(), "swarmvault-image-extract-"));
|
|
3576
4016
|
const extension = input.mimeType.split("/")[1]?.split("+")[0] ?? "bin";
|
|
3577
|
-
const tempPath =
|
|
3578
|
-
await
|
|
4017
|
+
const tempPath = path7.join(tempDir, `source.${extension}`);
|
|
4018
|
+
await fs7.writeFile(tempPath, input.bytes);
|
|
3579
4019
|
return {
|
|
3580
4020
|
filePath: tempPath,
|
|
3581
4021
|
cleanup: async () => {
|
|
3582
|
-
await
|
|
4022
|
+
await fs7.rm(tempDir, { recursive: true, force: true });
|
|
3583
4023
|
}
|
|
3584
4024
|
};
|
|
3585
4025
|
}
|
|
@@ -3790,18 +4230,18 @@ async function extractDocxText(input) {
|
|
|
3790
4230
|
}
|
|
3791
4231
|
|
|
3792
4232
|
// src/logs.ts
|
|
3793
|
-
import
|
|
3794
|
-
import
|
|
4233
|
+
import fs8 from "fs/promises";
|
|
4234
|
+
import path8 from "path";
|
|
3795
4235
|
import matter from "gray-matter";
|
|
3796
4236
|
async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
|
|
3797
4237
|
const { paths } = await initWorkspace(rootDir);
|
|
3798
4238
|
await ensureDir(paths.sessionsDir);
|
|
3799
4239
|
const timestamp = startedAt.replace(/[:.]/g, "-");
|
|
3800
4240
|
const baseName = `${timestamp}-${operation}-${slugify(title)}`;
|
|
3801
|
-
let candidate =
|
|
4241
|
+
let candidate = path8.join(paths.sessionsDir, `${baseName}.md`);
|
|
3802
4242
|
let counter = 2;
|
|
3803
4243
|
while (await fileExists(candidate)) {
|
|
3804
|
-
candidate =
|
|
4244
|
+
candidate = path8.join(paths.sessionsDir, `${baseName}-${counter}.md`);
|
|
3805
4245
|
counter++;
|
|
3806
4246
|
}
|
|
3807
4247
|
return candidate;
|
|
@@ -3809,11 +4249,11 @@ async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
|
|
|
3809
4249
|
async function appendLogEntry(rootDir, action, title, lines = []) {
|
|
3810
4250
|
const { paths } = await initWorkspace(rootDir);
|
|
3811
4251
|
await ensureDir(paths.wikiDir);
|
|
3812
|
-
const logPath =
|
|
4252
|
+
const logPath = path8.join(paths.wikiDir, "log.md");
|
|
3813
4253
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
3814
4254
|
const entry = [`## [${timestamp}] ${action} | ${title}`, ...lines.map((line) => `- ${line}`), ""].join("\n");
|
|
3815
|
-
const existing = await fileExists(logPath) ? await
|
|
3816
|
-
await
|
|
4255
|
+
const existing = await fileExists(logPath) ? await fs8.readFile(logPath, "utf8") : "# Log\n\n";
|
|
4256
|
+
await fs8.writeFile(logPath, `${existing}${entry}
|
|
3817
4257
|
`, "utf8");
|
|
3818
4258
|
}
|
|
3819
4259
|
async function recordSession(rootDir, input) {
|
|
@@ -3823,8 +4263,8 @@ async function recordSession(rootDir, input) {
|
|
|
3823
4263
|
const finishedAtIso = new Date(input.finishedAt ?? input.startedAt).toISOString();
|
|
3824
4264
|
const durationMs = Math.max(0, new Date(finishedAtIso).getTime() - new Date(startedAtIso).getTime());
|
|
3825
4265
|
const sessionPath = await resolveUniqueSessionPath(rootDir, input.operation, input.title, startedAtIso);
|
|
3826
|
-
const sessionId =
|
|
3827
|
-
const relativeSessionPath =
|
|
4266
|
+
const sessionId = path8.basename(sessionPath, ".md");
|
|
4267
|
+
const relativeSessionPath = path8.relative(rootDir, sessionPath).split(path8.sep).join(path8.posix.sep);
|
|
3828
4268
|
const frontmatter = Object.fromEntries(
|
|
3829
4269
|
Object.entries({
|
|
3830
4270
|
session_id: sessionId,
|
|
@@ -3872,7 +4312,7 @@ async function recordSession(rootDir, input) {
|
|
|
3872
4312
|
frontmatter
|
|
3873
4313
|
);
|
|
3874
4314
|
await writeFileIfChanged(sessionPath, content);
|
|
3875
|
-
const logPath =
|
|
4315
|
+
const logPath = path8.join(paths.wikiDir, "log.md");
|
|
3876
4316
|
const timestamp = startedAtIso.slice(0, 19).replace("T", " ");
|
|
3877
4317
|
const entry = [
|
|
3878
4318
|
`## [${timestamp}] ${input.operation} | ${input.title}`,
|
|
@@ -3880,8 +4320,8 @@ async function recordSession(rootDir, input) {
|
|
|
3880
4320
|
...(input.lines ?? []).map((line) => `- ${line}`),
|
|
3881
4321
|
""
|
|
3882
4322
|
].join("\n");
|
|
3883
|
-
const existing = await fileExists(logPath) ? await
|
|
3884
|
-
await
|
|
4323
|
+
const existing = await fileExists(logPath) ? await fs8.readFile(logPath, "utf8") : "# Log\n\n";
|
|
4324
|
+
await fs8.writeFile(logPath, `${existing}${entry}
|
|
3885
4325
|
`, "utf8");
|
|
3886
4326
|
return { sessionPath, sessionId };
|
|
3887
4327
|
}
|
|
@@ -3891,13 +4331,13 @@ async function appendWatchRun(rootDir, run) {
|
|
|
3891
4331
|
}
|
|
3892
4332
|
|
|
3893
4333
|
// src/source-classification.ts
|
|
3894
|
-
import
|
|
4334
|
+
import path9 from "path";
|
|
3895
4335
|
var ALL_SOURCE_CLASSES = ["first_party", "third_party", "resource", "generated"];
|
|
3896
4336
|
var THIRD_PARTY_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "vendor", "Pods"]);
|
|
3897
4337
|
var GENERATED_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", ".next", "coverage", "DerivedData", "target"]);
|
|
3898
4338
|
function matchesAnyGlob(relativePath, patterns) {
|
|
3899
4339
|
return patterns.some(
|
|
3900
|
-
(pattern) =>
|
|
4340
|
+
(pattern) => path9.matchesGlob(relativePath, pattern) || path9.matchesGlob(path9.posix.basename(relativePath), pattern)
|
|
3901
4341
|
);
|
|
3902
4342
|
}
|
|
3903
4343
|
function classifyRepoPath(relativePath, repoAnalysis) {
|
|
@@ -3950,8 +4390,8 @@ function aggregateManifestSourceClass(manifests, sourceIds) {
|
|
|
3950
4390
|
}
|
|
3951
4391
|
|
|
3952
4392
|
// src/watch-state.ts
|
|
3953
|
-
import
|
|
3954
|
-
import
|
|
4393
|
+
import fs9 from "fs/promises";
|
|
4394
|
+
import path10 from "path";
|
|
3955
4395
|
import matter2 from "gray-matter";
|
|
3956
4396
|
function pendingEntryKey(entry) {
|
|
3957
4397
|
return entry.path;
|
|
@@ -3965,7 +4405,7 @@ function normalizeRelativePath(rootDir, filePath) {
|
|
|
3965
4405
|
if (!filePath) {
|
|
3966
4406
|
return void 0;
|
|
3967
4407
|
}
|
|
3968
|
-
return toPosix(
|
|
4408
|
+
return toPosix(path10.relative(rootDir, path10.resolve(filePath)));
|
|
3969
4409
|
}
|
|
3970
4410
|
async function readPendingSemanticRefresh(rootDir) {
|
|
3971
4411
|
const { paths } = await initWorkspace(rootDir);
|
|
@@ -4059,11 +4499,11 @@ async function markPagesStaleForSources(rootDir, sourceIds) {
|
|
|
4059
4499
|
if (page.freshness !== "stale" || !page.sourceIds.some((sourceId) => affectedSourceIds.has(sourceId))) {
|
|
4060
4500
|
continue;
|
|
4061
4501
|
}
|
|
4062
|
-
const absolutePath =
|
|
4502
|
+
const absolutePath = path10.join(paths.wikiDir, page.path);
|
|
4063
4503
|
if (!await fileExists(absolutePath)) {
|
|
4064
4504
|
continue;
|
|
4065
4505
|
}
|
|
4066
|
-
const raw = await
|
|
4506
|
+
const raw = await fs9.readFile(absolutePath, "utf8");
|
|
4067
4507
|
const parsed = matter2(raw);
|
|
4068
4508
|
if (parsed.data.freshness === "stale") {
|
|
4069
4509
|
continue;
|
|
@@ -4117,7 +4557,7 @@ function normalizeIngestOptions(options) {
|
|
|
4117
4557
|
return {
|
|
4118
4558
|
includeAssets: options?.includeAssets ?? true,
|
|
4119
4559
|
maxAssetSize: Math.max(0, Math.floor(options?.maxAssetSize ?? DEFAULT_MAX_ASSET_SIZE)),
|
|
4120
|
-
repoRoot: options?.repoRoot ?
|
|
4560
|
+
repoRoot: options?.repoRoot ? path11.resolve(options.repoRoot) : void 0,
|
|
4121
4561
|
include: (options?.include ?? []).map((pattern) => pattern.trim()).filter(Boolean),
|
|
4122
4562
|
exclude: (options?.exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean),
|
|
4123
4563
|
maxFiles: Math.max(1, Math.floor(options?.maxFiles ?? DEFAULT_MAX_DIRECTORY_FILES)),
|
|
@@ -4137,27 +4577,27 @@ async function resolveRepoIngestOptions(rootDir, options) {
|
|
|
4137
4577
|
}
|
|
4138
4578
|
function matchesAnyGlob2(relativePath, patterns) {
|
|
4139
4579
|
return patterns.some(
|
|
4140
|
-
(pattern) =>
|
|
4580
|
+
(pattern) => path11.matchesGlob(relativePath, pattern) || path11.matchesGlob(path11.posix.basename(relativePath), pattern)
|
|
4141
4581
|
);
|
|
4142
4582
|
}
|
|
4143
4583
|
function supportedDirectoryKind(sourceKind) {
|
|
4144
4584
|
return sourceKind !== "binary";
|
|
4145
4585
|
}
|
|
4146
4586
|
async function findNearestGitRoot2(startPath) {
|
|
4147
|
-
let current =
|
|
4587
|
+
let current = path11.resolve(startPath);
|
|
4148
4588
|
try {
|
|
4149
|
-
const stat = await
|
|
4589
|
+
const stat = await fs10.stat(current);
|
|
4150
4590
|
if (!stat.isDirectory()) {
|
|
4151
|
-
current =
|
|
4591
|
+
current = path11.dirname(current);
|
|
4152
4592
|
}
|
|
4153
4593
|
} catch {
|
|
4154
|
-
current =
|
|
4594
|
+
current = path11.dirname(current);
|
|
4155
4595
|
}
|
|
4156
4596
|
while (true) {
|
|
4157
|
-
if (await fileExists(
|
|
4597
|
+
if (await fileExists(path11.join(current, ".git"))) {
|
|
4158
4598
|
return current;
|
|
4159
4599
|
}
|
|
4160
|
-
const parent =
|
|
4600
|
+
const parent = path11.dirname(current);
|
|
4161
4601
|
if (parent === current) {
|
|
4162
4602
|
return null;
|
|
4163
4603
|
}
|
|
@@ -4165,26 +4605,26 @@ async function findNearestGitRoot2(startPath) {
|
|
|
4165
4605
|
}
|
|
4166
4606
|
}
|
|
4167
4607
|
function withinRoot(rootPath, targetPath) {
|
|
4168
|
-
const relative =
|
|
4169
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
4608
|
+
const relative = path11.relative(rootPath, targetPath);
|
|
4609
|
+
return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
|
|
4170
4610
|
}
|
|
4171
4611
|
function repoRootFromManifest(manifest) {
|
|
4172
4612
|
if (manifest.originType !== "file" || !manifest.originalPath || !manifest.repoRelativePath) {
|
|
4173
4613
|
return null;
|
|
4174
4614
|
}
|
|
4175
|
-
const repoDir =
|
|
4176
|
-
const fileDir =
|
|
4615
|
+
const repoDir = path11.posix.dirname(manifest.repoRelativePath);
|
|
4616
|
+
const fileDir = path11.dirname(path11.resolve(manifest.originalPath));
|
|
4177
4617
|
if (repoDir === "." || !repoDir) {
|
|
4178
4618
|
return fileDir;
|
|
4179
4619
|
}
|
|
4180
4620
|
const segments = repoDir.split("/").filter(Boolean);
|
|
4181
|
-
return
|
|
4621
|
+
return path11.resolve(fileDir, ...segments.map(() => ".."));
|
|
4182
4622
|
}
|
|
4183
4623
|
function repoRelativePathFor(absolutePath, repoRoot) {
|
|
4184
4624
|
if (!repoRoot || !withinRoot(repoRoot, absolutePath)) {
|
|
4185
4625
|
return void 0;
|
|
4186
4626
|
}
|
|
4187
|
-
const relative = toPosix(
|
|
4627
|
+
const relative = toPosix(path11.relative(repoRoot, absolutePath));
|
|
4188
4628
|
return relative && !relative.startsWith("..") ? relative : void 0;
|
|
4189
4629
|
}
|
|
4190
4630
|
function normalizeOriginUrl(input) {
|
|
@@ -4260,7 +4700,41 @@ function prepareCapturedMarkdownInput(input) {
|
|
|
4260
4700
|
logDetails: input.logDetails
|
|
4261
4701
|
};
|
|
4262
4702
|
}
|
|
4703
|
+
function isPrivateIp(ip) {
|
|
4704
|
+
if (ip === "::1" || ip.startsWith("fc") || ip.startsWith("fd")) return true;
|
|
4705
|
+
const parts = ip.split(".").map(Number);
|
|
4706
|
+
if (parts.length !== 4 || parts.some((p) => Number.isNaN(p))) return false;
|
|
4707
|
+
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;
|
|
4708
|
+
}
|
|
4709
|
+
function allowPrivateUrlsForProcess() {
|
|
4710
|
+
return process.env.SWARMVAULT_ALLOW_PRIVATE_URLS === "1";
|
|
4711
|
+
}
|
|
4712
|
+
function isReservedTestHostname(hostname) {
|
|
4713
|
+
const lower = hostname.toLowerCase();
|
|
4714
|
+
return lower.endsWith(".test") || lower.endsWith(".example") || lower.endsWith(".invalid");
|
|
4715
|
+
}
|
|
4716
|
+
async function validateUrlSafety(url) {
|
|
4717
|
+
const parsed = new URL(url);
|
|
4718
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
4719
|
+
throw new Error(`Blocked protocol: ${parsed.protocol}`);
|
|
4720
|
+
}
|
|
4721
|
+
if (allowPrivateUrlsForProcess() || isReservedTestHostname(parsed.hostname)) {
|
|
4722
|
+
return;
|
|
4723
|
+
}
|
|
4724
|
+
let address;
|
|
4725
|
+
try {
|
|
4726
|
+
const { lookup } = await import("dns/promises");
|
|
4727
|
+
const result = await lookup(parsed.hostname);
|
|
4728
|
+
address = result.address;
|
|
4729
|
+
} catch {
|
|
4730
|
+
return;
|
|
4731
|
+
}
|
|
4732
|
+
if (isPrivateIp(address)) {
|
|
4733
|
+
throw new Error(`Blocked private/reserved IP ${address} (resolved from ${parsed.hostname})`);
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4263
4736
|
async function fetchText(url) {
|
|
4737
|
+
await validateUrlSafety(url);
|
|
4264
4738
|
const response = await fetch(url);
|
|
4265
4739
|
if (!response.ok) {
|
|
4266
4740
|
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
@@ -4268,6 +4742,7 @@ async function fetchText(url) {
|
|
|
4268
4742
|
return response.text();
|
|
4269
4743
|
}
|
|
4270
4744
|
async function fetchResolvedText(url) {
|
|
4745
|
+
await validateUrlSafety(url);
|
|
4271
4746
|
const response = await fetch(url);
|
|
4272
4747
|
if (!response.ok) {
|
|
4273
4748
|
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
@@ -4481,7 +4956,7 @@ function buildCompositeHash(payloadBytes, attachments = []) {
|
|
|
4481
4956
|
return sha256(`${sha256(payloadBytes)}|${attachmentSignature}`);
|
|
4482
4957
|
}
|
|
4483
4958
|
function sanitizeAssetRelativePath(value) {
|
|
4484
|
-
const normalized =
|
|
4959
|
+
const normalized = path11.posix.normalize(value.replace(/\\/g, "/"));
|
|
4485
4960
|
const segments = normalized.split("/").filter(Boolean).map((segment) => {
|
|
4486
4961
|
if (segment === ".") {
|
|
4487
4962
|
return "";
|
|
@@ -4501,7 +4976,7 @@ function normalizeLocalReference(value) {
|
|
|
4501
4976
|
return null;
|
|
4502
4977
|
}
|
|
4503
4978
|
const lowered = candidate.toLowerCase();
|
|
4504
|
-
if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") ||
|
|
4979
|
+
if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path11.isAbsolute(candidate)) {
|
|
4505
4980
|
return null;
|
|
4506
4981
|
}
|
|
4507
4982
|
return candidate.replace(/\\/g, "/");
|
|
@@ -4579,12 +5054,12 @@ async function convertHtmlToMarkdown(html, url) {
|
|
|
4579
5054
|
};
|
|
4580
5055
|
}
|
|
4581
5056
|
async function readManifestByHash(manifestsDir, contentHash) {
|
|
4582
|
-
const entries = await
|
|
5057
|
+
const entries = await fs10.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
|
|
4583
5058
|
for (const entry of entries) {
|
|
4584
5059
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
4585
5060
|
continue;
|
|
4586
5061
|
}
|
|
4587
|
-
const manifest = await readJsonFile(
|
|
5062
|
+
const manifest = await readJsonFile(path11.join(manifestsDir, entry.name));
|
|
4588
5063
|
if (manifest?.contentHash === contentHash) {
|
|
4589
5064
|
return manifest;
|
|
4590
5065
|
}
|
|
@@ -4592,12 +5067,12 @@ async function readManifestByHash(manifestsDir, contentHash) {
|
|
|
4592
5067
|
return null;
|
|
4593
5068
|
}
|
|
4594
5069
|
async function readManifestByOrigin(manifestsDir, prepared) {
|
|
4595
|
-
const entries = await
|
|
5070
|
+
const entries = await fs10.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
|
|
4596
5071
|
for (const entry of entries) {
|
|
4597
5072
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
4598
5073
|
continue;
|
|
4599
5074
|
}
|
|
4600
|
-
const manifest = await readJsonFile(
|
|
5075
|
+
const manifest = await readJsonFile(path11.join(manifestsDir, entry.name));
|
|
4601
5076
|
if (manifest && manifestMatchesOrigin(manifest, prepared)) {
|
|
4602
5077
|
return manifest;
|
|
4603
5078
|
}
|
|
@@ -4608,12 +5083,12 @@ async function loadGitignoreMatcher(repoRoot, enabled) {
|
|
|
4608
5083
|
if (!enabled) {
|
|
4609
5084
|
return null;
|
|
4610
5085
|
}
|
|
4611
|
-
const gitignorePath =
|
|
5086
|
+
const gitignorePath = path11.join(repoRoot, ".gitignore");
|
|
4612
5087
|
if (!await fileExists(gitignorePath)) {
|
|
4613
5088
|
return null;
|
|
4614
5089
|
}
|
|
4615
5090
|
const matcher = ignore();
|
|
4616
|
-
matcher.add(await
|
|
5091
|
+
matcher.add(await fs10.readFile(gitignorePath, "utf8"));
|
|
4617
5092
|
return matcher;
|
|
4618
5093
|
}
|
|
4619
5094
|
function builtInIgnoreReason(relativePath) {
|
|
@@ -4637,23 +5112,23 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
|
|
|
4637
5112
|
if (!currentDir) {
|
|
4638
5113
|
continue;
|
|
4639
5114
|
}
|
|
4640
|
-
const entries = await
|
|
5115
|
+
const entries = await fs10.readdir(currentDir, { withFileTypes: true });
|
|
4641
5116
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
4642
5117
|
for (const entry of entries) {
|
|
4643
|
-
const absolutePath =
|
|
4644
|
-
const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
5118
|
+
const absolutePath = path11.join(currentDir, entry.name);
|
|
5119
|
+
const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(inputDir, absolutePath));
|
|
4645
5120
|
const relativePath = relativeToRepo || entry.name;
|
|
4646
5121
|
const builtInReason = builtInIgnoreReason(relativePath);
|
|
4647
5122
|
if (builtInReason) {
|
|
4648
|
-
skipped.push({ path: toPosix(
|
|
5123
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: builtInReason });
|
|
4649
5124
|
continue;
|
|
4650
5125
|
}
|
|
4651
5126
|
if (matcher?.ignores(relativePath)) {
|
|
4652
|
-
skipped.push({ path: toPosix(
|
|
5127
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "gitignore" });
|
|
4653
5128
|
continue;
|
|
4654
5129
|
}
|
|
4655
5130
|
if (matchesAnyGlob2(relativePath, options.exclude)) {
|
|
4656
|
-
skipped.push({ path: toPosix(
|
|
5131
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "exclude_glob" });
|
|
4657
5132
|
continue;
|
|
4658
5133
|
}
|
|
4659
5134
|
if (entry.isDirectory()) {
|
|
@@ -4661,26 +5136,26 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
|
|
|
4661
5136
|
continue;
|
|
4662
5137
|
}
|
|
4663
5138
|
if (!entry.isFile()) {
|
|
4664
|
-
skipped.push({ path: toPosix(
|
|
5139
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
|
|
4665
5140
|
continue;
|
|
4666
5141
|
}
|
|
4667
5142
|
if (options.include.length > 0 && !matchesAnyGlob2(relativePath, options.include)) {
|
|
4668
|
-
skipped.push({ path: toPosix(
|
|
5143
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "include_glob" });
|
|
4669
5144
|
continue;
|
|
4670
5145
|
}
|
|
4671
5146
|
const mimeType = guessMimeType(absolutePath);
|
|
4672
5147
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
4673
5148
|
const sourceClass = sourceClassForRelativePath(relativePath, options);
|
|
4674
5149
|
if (!supportedDirectoryKind(sourceKind)) {
|
|
4675
|
-
skipped.push({ path: toPosix(
|
|
5150
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
4676
5151
|
continue;
|
|
4677
5152
|
}
|
|
4678
5153
|
if (!options.extractClasses.includes(sourceClass)) {
|
|
4679
|
-
skipped.push({ path: toPosix(
|
|
5154
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: `source_class:${sourceClass}` });
|
|
4680
5155
|
continue;
|
|
4681
5156
|
}
|
|
4682
5157
|
if (files.length >= options.maxFiles) {
|
|
4683
|
-
skipped.push({ path: toPosix(
|
|
5158
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "max_files" });
|
|
4684
5159
|
continue;
|
|
4685
5160
|
}
|
|
4686
5161
|
files.push(absolutePath);
|
|
@@ -4702,12 +5177,12 @@ function resolveUrlMimeType(input, response) {
|
|
|
4702
5177
|
function buildRemoteAssetRelativePath(assetUrl, mimeType) {
|
|
4703
5178
|
const url = new URL(assetUrl);
|
|
4704
5179
|
const normalized = sanitizeAssetRelativePath(`${url.hostname}${url.pathname || "/asset"}`);
|
|
4705
|
-
const extension =
|
|
4706
|
-
const directory =
|
|
4707
|
-
const basename = extension ?
|
|
5180
|
+
const extension = path11.posix.extname(normalized);
|
|
5181
|
+
const directory = path11.posix.dirname(normalized);
|
|
5182
|
+
const basename = extension ? path11.posix.basename(normalized, extension) : path11.posix.basename(normalized);
|
|
4708
5183
|
const resolvedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
|
|
4709
5184
|
const hashedName = `${basename || "asset"}-${sha256(assetUrl).slice(0, 8)}${resolvedExtension}`;
|
|
4710
|
-
return directory === "." ? hashedName :
|
|
5185
|
+
return directory === "." ? hashedName : path11.posix.join(directory, hashedName);
|
|
4711
5186
|
}
|
|
4712
5187
|
async function readResponseBytesWithinLimit(response, maxBytes) {
|
|
4713
5188
|
const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
|
|
@@ -4739,6 +5214,7 @@ async function readResponseBytesWithinLimit(response, maxBytes) {
|
|
|
4739
5214
|
return Buffer.concat(chunks);
|
|
4740
5215
|
}
|
|
4741
5216
|
async function fetchRemoteImageAttachment(assetUrl, maxAssetSize) {
|
|
5217
|
+
await validateUrlSafety(assetUrl);
|
|
4742
5218
|
const response = await fetch(assetUrl);
|
|
4743
5219
|
if (!response.ok) {
|
|
4744
5220
|
throw new Error(`failed with ${response.status} ${response.statusText}`);
|
|
@@ -4859,34 +5335,34 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4859
5335
|
const previous = existingByOrigin ?? void 0;
|
|
4860
5336
|
const sourceId = previous?.sourceId ?? `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
|
|
4861
5337
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4862
|
-
const storedPath =
|
|
4863
|
-
const extractedTextPath = prepared.extractedText ?
|
|
4864
|
-
const extractedMetadataPath = prepared.extractionArtifact ?
|
|
4865
|
-
const attachmentsDir =
|
|
5338
|
+
const storedPath = path11.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
|
|
5339
|
+
const extractedTextPath = prepared.extractedText ? path11.join(paths.extractsDir, `${sourceId}.md`) : void 0;
|
|
5340
|
+
const extractedMetadataPath = prepared.extractionArtifact ? path11.join(paths.extractsDir, `${sourceId}.json`) : void 0;
|
|
5341
|
+
const attachmentsDir = path11.join(paths.rawAssetsDir, sourceId);
|
|
4866
5342
|
if (previous?.storedPath) {
|
|
4867
|
-
await
|
|
5343
|
+
await fs10.rm(path11.resolve(rootDir, previous.storedPath), { force: true });
|
|
4868
5344
|
}
|
|
4869
5345
|
if (previous?.extractedTextPath) {
|
|
4870
|
-
await
|
|
5346
|
+
await fs10.rm(path11.resolve(rootDir, previous.extractedTextPath), { force: true });
|
|
4871
5347
|
}
|
|
4872
5348
|
if (previous?.extractedMetadataPath) {
|
|
4873
|
-
await
|
|
5349
|
+
await fs10.rm(path11.resolve(rootDir, previous.extractedMetadataPath), { force: true });
|
|
4874
5350
|
}
|
|
4875
|
-
await
|
|
4876
|
-
await
|
|
5351
|
+
await fs10.rm(attachmentsDir, { recursive: true, force: true });
|
|
5352
|
+
await fs10.writeFile(storedPath, prepared.payloadBytes);
|
|
4877
5353
|
if (prepared.extractedText && extractedTextPath) {
|
|
4878
|
-
await
|
|
5354
|
+
await fs10.writeFile(extractedTextPath, prepared.extractedText, "utf8");
|
|
4879
5355
|
}
|
|
4880
5356
|
if (prepared.extractionArtifact && extractedMetadataPath) {
|
|
4881
5357
|
await writeJsonFile(extractedMetadataPath, prepared.extractionArtifact);
|
|
4882
5358
|
}
|
|
4883
5359
|
const manifestAttachments = [];
|
|
4884
5360
|
for (const attachment of attachments) {
|
|
4885
|
-
const absoluteAttachmentPath =
|
|
4886
|
-
await ensureDir(
|
|
4887
|
-
await
|
|
5361
|
+
const absoluteAttachmentPath = path11.join(attachmentsDir, attachment.relativePath);
|
|
5362
|
+
await ensureDir(path11.dirname(absoluteAttachmentPath));
|
|
5363
|
+
await fs10.writeFile(absoluteAttachmentPath, attachment.bytes);
|
|
4888
5364
|
manifestAttachments.push({
|
|
4889
|
-
path: toPosix(
|
|
5365
|
+
path: toPosix(path11.relative(rootDir, absoluteAttachmentPath)),
|
|
4890
5366
|
mimeType: attachment.mimeType,
|
|
4891
5367
|
originalPath: attachment.originalPath
|
|
4892
5368
|
});
|
|
@@ -4902,9 +5378,9 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4902
5378
|
originalPath: prepared.originalPath,
|
|
4903
5379
|
repoRelativePath: prepared.repoRelativePath,
|
|
4904
5380
|
url: prepared.url,
|
|
4905
|
-
storedPath: toPosix(
|
|
4906
|
-
extractedTextPath: extractedTextPath ? toPosix(
|
|
4907
|
-
extractedMetadataPath: extractedMetadataPath ? toPosix(
|
|
5381
|
+
storedPath: toPosix(path11.relative(rootDir, storedPath)),
|
|
5382
|
+
extractedTextPath: extractedTextPath ? toPosix(path11.relative(rootDir, extractedTextPath)) : void 0,
|
|
5383
|
+
extractedMetadataPath: extractedMetadataPath ? toPosix(path11.relative(rootDir, extractedMetadataPath)) : void 0,
|
|
4908
5384
|
extractionHash,
|
|
4909
5385
|
mimeType: prepared.mimeType,
|
|
4910
5386
|
contentHash,
|
|
@@ -4912,7 +5388,7 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4912
5388
|
updatedAt: now,
|
|
4913
5389
|
attachments: manifestAttachments.length ? manifestAttachments : void 0
|
|
4914
5390
|
};
|
|
4915
|
-
await writeJsonFile(
|
|
5391
|
+
await writeJsonFile(path11.join(paths.manifestsDir, `${sourceId}.json`), manifest);
|
|
4916
5392
|
await appendLogEntry(rootDir, "ingest", prepared.title, [
|
|
4917
5393
|
`source_id=${sourceId}`,
|
|
4918
5394
|
`kind=${prepared.sourceKind}`,
|
|
@@ -4930,16 +5406,16 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4930
5406
|
return { manifest, isNew: !previous, wasUpdated: Boolean(previous) };
|
|
4931
5407
|
}
|
|
4932
5408
|
async function removeManifestArtifacts(rootDir, manifest, paths) {
|
|
4933
|
-
await
|
|
4934
|
-
await
|
|
5409
|
+
await fs10.rm(path11.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
|
|
5410
|
+
await fs10.rm(path11.resolve(rootDir, manifest.storedPath), { force: true });
|
|
4935
5411
|
if (manifest.extractedTextPath) {
|
|
4936
|
-
await
|
|
5412
|
+
await fs10.rm(path11.resolve(rootDir, manifest.extractedTextPath), { force: true });
|
|
4937
5413
|
}
|
|
4938
5414
|
if (manifest.extractedMetadataPath) {
|
|
4939
|
-
await
|
|
5415
|
+
await fs10.rm(path11.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
|
|
4940
5416
|
}
|
|
4941
|
-
await
|
|
4942
|
-
await
|
|
5417
|
+
await fs10.rm(path11.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
|
|
5418
|
+
await fs10.rm(path11.join(paths.analysesDir, `${manifest.sourceId}.json`), { force: true });
|
|
4943
5419
|
}
|
|
4944
5420
|
function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
|
|
4945
5421
|
const candidates = [
|
|
@@ -4948,11 +5424,11 @@ function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
|
|
|
4948
5424
|
paths.stateDir,
|
|
4949
5425
|
paths.agentDir,
|
|
4950
5426
|
paths.inboxDir,
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
5427
|
+
path11.join(rootDir, ".claude"),
|
|
5428
|
+
path11.join(rootDir, ".cursor"),
|
|
5429
|
+
path11.join(rootDir, ".obsidian")
|
|
4954
5430
|
];
|
|
4955
|
-
return candidates.map((candidate) =>
|
|
5431
|
+
return candidates.map((candidate) => path11.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, candidate));
|
|
4956
5432
|
}
|
|
4957
5433
|
function preparedMatchesManifest(manifest, prepared, contentHash) {
|
|
4958
5434
|
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 +5450,16 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
4974
5450
|
const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
|
|
4975
5451
|
const manifests = await listManifests(rootDir);
|
|
4976
5452
|
const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
|
|
4977
|
-
(item) =>
|
|
5453
|
+
(item) => path11.resolve(item)
|
|
4978
5454
|
);
|
|
4979
5455
|
const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
|
|
4980
5456
|
const manifestsByRepoRoot = /* @__PURE__ */ new Map();
|
|
4981
5457
|
for (const manifest of manifests) {
|
|
4982
5458
|
const repoRoot = repoRootFromManifest(manifest);
|
|
4983
|
-
if (!repoRoot || !uniqueRoots.includes(
|
|
5459
|
+
if (!repoRoot || !uniqueRoots.includes(path11.resolve(repoRoot))) {
|
|
4984
5460
|
continue;
|
|
4985
5461
|
}
|
|
4986
|
-
const key =
|
|
5462
|
+
const key = path11.resolve(repoRoot);
|
|
4987
5463
|
const bucket = manifestsByRepoRoot.get(key) ?? [];
|
|
4988
5464
|
bucket.push(manifest);
|
|
4989
5465
|
manifestsByRepoRoot.set(key, bucket);
|
|
@@ -5008,14 +5484,14 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
5008
5484
|
skipped.push(
|
|
5009
5485
|
...collected.skipped,
|
|
5010
5486
|
...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
|
|
5011
|
-
path: toPosix(
|
|
5487
|
+
path: toPosix(path11.relative(rootDir, absolutePath)),
|
|
5012
5488
|
reason: "workspace_generated"
|
|
5013
5489
|
}))
|
|
5014
5490
|
);
|
|
5015
5491
|
scannedCount += files.length;
|
|
5016
|
-
const currentPaths = new Set(files.map((absolutePath) =>
|
|
5492
|
+
const currentPaths = new Set(files.map((absolutePath) => path11.resolve(absolutePath)));
|
|
5017
5493
|
for (const absolutePath of files) {
|
|
5018
|
-
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
5494
|
+
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
|
|
5019
5495
|
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
|
|
5020
5496
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
5021
5497
|
if (result.isNew) {
|
|
@@ -5025,7 +5501,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
5025
5501
|
}
|
|
5026
5502
|
}
|
|
5027
5503
|
for (const manifest of repoManifests) {
|
|
5028
|
-
const originalPath = manifest.originalPath ?
|
|
5504
|
+
const originalPath = manifest.originalPath ? path11.resolve(manifest.originalPath) : null;
|
|
5029
5505
|
if (originalPath && !currentPaths.has(originalPath)) {
|
|
5030
5506
|
await removeManifestArtifacts(rootDir, manifest, paths);
|
|
5031
5507
|
removed.push(manifest);
|
|
@@ -5033,7 +5509,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
5033
5509
|
}
|
|
5034
5510
|
}
|
|
5035
5511
|
if (uniqueRoots.length > 0) {
|
|
5036
|
-
await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(
|
|
5512
|
+
await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(path11.relative(rootDir, repoRoot)) || ".").join(","), [
|
|
5037
5513
|
`repo_roots=${uniqueRoots.length}`,
|
|
5038
5514
|
`scanned=${scannedCount}`,
|
|
5039
5515
|
`imported=${imported.length}`,
|
|
@@ -5056,16 +5532,16 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5056
5532
|
const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
|
|
5057
5533
|
const manifests = await listManifests(rootDir);
|
|
5058
5534
|
const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
|
|
5059
|
-
(item) =>
|
|
5535
|
+
(item) => path11.resolve(item)
|
|
5060
5536
|
);
|
|
5061
5537
|
const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
|
|
5062
5538
|
const manifestsByRepoRoot = /* @__PURE__ */ new Map();
|
|
5063
5539
|
for (const manifest of manifests) {
|
|
5064
5540
|
const repoRoot = repoRootFromManifest(manifest);
|
|
5065
|
-
if (!repoRoot || !uniqueRoots.includes(
|
|
5541
|
+
if (!repoRoot || !uniqueRoots.includes(path11.resolve(repoRoot))) {
|
|
5066
5542
|
continue;
|
|
5067
5543
|
}
|
|
5068
|
-
const key =
|
|
5544
|
+
const key = path11.resolve(repoRoot);
|
|
5069
5545
|
const bucket = manifestsByRepoRoot.get(key) ?? [];
|
|
5070
5546
|
bucket.push(manifest);
|
|
5071
5547
|
manifestsByRepoRoot.set(key, bucket);
|
|
@@ -5080,7 +5556,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5080
5556
|
for (const repoRoot of uniqueRoots) {
|
|
5081
5557
|
const repoManifests = manifestsByRepoRoot.get(repoRoot) ?? [];
|
|
5082
5558
|
const manifestsByOriginalPath = new Map(
|
|
5083
|
-
repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [
|
|
5559
|
+
repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [path11.resolve(manifest.originalPath), manifest])
|
|
5084
5560
|
);
|
|
5085
5561
|
if (!await fileExists(repoRoot)) {
|
|
5086
5562
|
for (const manifest of repoManifests) {
|
|
@@ -5088,7 +5564,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5088
5564
|
pendingSemanticRefresh.push({
|
|
5089
5565
|
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? manifest.storedPath),
|
|
5090
5566
|
repoRoot,
|
|
5091
|
-
path: toPosix(
|
|
5567
|
+
path: toPosix(path11.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
|
|
5092
5568
|
changeType: "removed",
|
|
5093
5569
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5094
5570
|
sourceId: manifest.sourceId,
|
|
@@ -5108,17 +5584,17 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5108
5584
|
skipped.push(
|
|
5109
5585
|
...collected.skipped,
|
|
5110
5586
|
...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
|
|
5111
|
-
path: toPosix(
|
|
5587
|
+
path: toPosix(path11.relative(rootDir, absolutePath)),
|
|
5112
5588
|
reason: "workspace_generated"
|
|
5113
5589
|
}))
|
|
5114
5590
|
);
|
|
5115
5591
|
scannedCount += files.length;
|
|
5116
|
-
const currentPaths = new Set(files.map((absolutePath) =>
|
|
5592
|
+
const currentPaths = new Set(files.map((absolutePath) => path11.resolve(absolutePath)));
|
|
5117
5593
|
for (const absolutePath of files) {
|
|
5118
|
-
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
5594
|
+
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
|
|
5119
5595
|
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
|
|
5120
5596
|
if (shouldDeferWatchSemanticRefresh(prepared.sourceKind)) {
|
|
5121
|
-
const existing = manifestsByOriginalPath.get(
|
|
5597
|
+
const existing = manifestsByOriginalPath.get(path11.resolve(absolutePath));
|
|
5122
5598
|
const contentHash = buildCompositeHash(prepared.payloadBytes, prepared.attachments);
|
|
5123
5599
|
const changed = !existing || !preparedMatchesManifest(existing, prepared, contentHash);
|
|
5124
5600
|
if (changed) {
|
|
@@ -5126,10 +5602,10 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5126
5602
|
id: pendingSemanticRefreshId(
|
|
5127
5603
|
existing ? "modified" : "added",
|
|
5128
5604
|
repoRoot,
|
|
5129
|
-
prepared.repoRelativePath ?? toPosix(
|
|
5605
|
+
prepared.repoRelativePath ?? toPosix(path11.relative(repoRoot, absolutePath))
|
|
5130
5606
|
),
|
|
5131
5607
|
repoRoot,
|
|
5132
|
-
path: toPosix(
|
|
5608
|
+
path: toPosix(path11.relative(rootDir, absolutePath)),
|
|
5133
5609
|
changeType: existing ? "modified" : "added",
|
|
5134
5610
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5135
5611
|
sourceId: existing?.sourceId,
|
|
@@ -5149,13 +5625,13 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5149
5625
|
}
|
|
5150
5626
|
}
|
|
5151
5627
|
for (const manifest of repoManifests) {
|
|
5152
|
-
const originalPath = manifest.originalPath ?
|
|
5628
|
+
const originalPath = manifest.originalPath ? path11.resolve(manifest.originalPath) : null;
|
|
5153
5629
|
if (originalPath && !currentPaths.has(originalPath)) {
|
|
5154
5630
|
if (shouldDeferWatchSemanticRefresh(manifest.sourceKind)) {
|
|
5155
5631
|
pendingSemanticRefresh.push({
|
|
5156
|
-
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(
|
|
5632
|
+
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path11.relative(repoRoot, originalPath))),
|
|
5157
5633
|
repoRoot,
|
|
5158
|
-
path: toPosix(
|
|
5634
|
+
path: toPosix(path11.relative(rootDir, originalPath)),
|
|
5159
5635
|
changeType: "removed",
|
|
5160
5636
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5161
5637
|
sourceId: manifest.sourceId,
|
|
@@ -5173,7 +5649,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5173
5649
|
await appendLogEntry(
|
|
5174
5650
|
rootDir,
|
|
5175
5651
|
"sync_repo_watch",
|
|
5176
|
-
uniqueRoots.map((repoRoot) => toPosix(
|
|
5652
|
+
uniqueRoots.map((repoRoot) => toPosix(path11.relative(rootDir, repoRoot)) || ".").join(","),
|
|
5177
5653
|
[
|
|
5178
5654
|
`repo_roots=${uniqueRoots.length}`,
|
|
5179
5655
|
`scanned=${scannedCount}`,
|
|
@@ -5199,17 +5675,17 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
5199
5675
|
};
|
|
5200
5676
|
}
|
|
5201
5677
|
async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
5202
|
-
const payloadBytes = await
|
|
5678
|
+
const payloadBytes = await fs10.readFile(absoluteInput);
|
|
5203
5679
|
const mimeType = guessMimeType(absoluteInput);
|
|
5204
5680
|
const sourceKind = inferKind(mimeType, absoluteInput);
|
|
5205
5681
|
const language = inferCodeLanguage(absoluteInput, mimeType);
|
|
5206
|
-
const storedExtension =
|
|
5682
|
+
const storedExtension = path11.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
|
|
5207
5683
|
let title;
|
|
5208
5684
|
let extractedText;
|
|
5209
5685
|
let extractionArtifact;
|
|
5210
5686
|
if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
|
|
5211
5687
|
extractedText = payloadBytes.toString("utf8");
|
|
5212
|
-
title = titleFromText(
|
|
5688
|
+
title = titleFromText(path11.basename(absoluteInput, path11.extname(absoluteInput)), extractedText);
|
|
5213
5689
|
extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
|
|
5214
5690
|
} else if (sourceKind === "html") {
|
|
5215
5691
|
const html = payloadBytes.toString("utf8");
|
|
@@ -5218,18 +5694,18 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
|
5218
5694
|
extractedText = converted.markdown;
|
|
5219
5695
|
extractionArtifact = createHtmlReadabilityExtractionArtifact(sourceKind, mimeType);
|
|
5220
5696
|
} else if (sourceKind === "pdf") {
|
|
5221
|
-
title =
|
|
5697
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
5222
5698
|
const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
|
|
5223
5699
|
extractedText = extracted.extractedText;
|
|
5224
5700
|
extractionArtifact = extracted.artifact;
|
|
5225
5701
|
} else if (sourceKind === "docx") {
|
|
5226
|
-
title =
|
|
5702
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
5227
5703
|
const extracted = await extractDocxText({ mimeType, bytes: payloadBytes });
|
|
5228
5704
|
title = extracted.artifact.metadata?.title?.trim() || title;
|
|
5229
5705
|
extractedText = extracted.extractedText;
|
|
5230
5706
|
extractionArtifact = extracted.artifact;
|
|
5231
5707
|
} else if (sourceKind === "image") {
|
|
5232
|
-
title =
|
|
5708
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
5233
5709
|
const extracted = await extractImageWithVision(rootDir, {
|
|
5234
5710
|
title,
|
|
5235
5711
|
mimeType,
|
|
@@ -5239,7 +5715,7 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
|
5239
5715
|
extractedText = extracted.extractedText;
|
|
5240
5716
|
extractionArtifact = extracted.artifact;
|
|
5241
5717
|
} else {
|
|
5242
|
-
title =
|
|
5718
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
5243
5719
|
}
|
|
5244
5720
|
return {
|
|
5245
5721
|
title,
|
|
@@ -5258,6 +5734,7 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
|
5258
5734
|
};
|
|
5259
5735
|
}
|
|
5260
5736
|
async function prepareUrlInput(rootDir, input, options) {
|
|
5737
|
+
await validateUrlSafety(input);
|
|
5261
5738
|
const response = await fetch(input);
|
|
5262
5739
|
if (!response.ok) {
|
|
5263
5740
|
throw new Error(`Failed to fetch ${input}: ${response.status} ${response.statusText}`);
|
|
@@ -5315,7 +5792,7 @@ async function prepareUrlInput(rootDir, input, options) {
|
|
|
5315
5792
|
sourceKind = "markdown";
|
|
5316
5793
|
storedExtension = ".md";
|
|
5317
5794
|
} else {
|
|
5318
|
-
const extension =
|
|
5795
|
+
const extension = path11.extname(inputUrl.pathname);
|
|
5319
5796
|
storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
|
|
5320
5797
|
if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
|
|
5321
5798
|
extractedText = payloadBytes.toString("utf8");
|
|
@@ -5386,14 +5863,14 @@ async function collectInboxAttachmentRefs(inputDir, files) {
|
|
|
5386
5863
|
if (sourceKind !== "markdown" && sourceKind !== "html") {
|
|
5387
5864
|
continue;
|
|
5388
5865
|
}
|
|
5389
|
-
const content = await
|
|
5866
|
+
const content = await fs10.readFile(absolutePath, "utf8");
|
|
5390
5867
|
const refs = sourceKind === "html" ? extractHtmlLocalReferences(content, pathToFileURL(absolutePath).toString()) : extractMarkdownReferences(content);
|
|
5391
5868
|
if (!refs.length) {
|
|
5392
5869
|
continue;
|
|
5393
5870
|
}
|
|
5394
5871
|
const sourceRefs = [];
|
|
5395
5872
|
for (const ref of refs) {
|
|
5396
|
-
const resolved =
|
|
5873
|
+
const resolved = path11.resolve(path11.dirname(absolutePath), ref);
|
|
5397
5874
|
if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
|
|
5398
5875
|
continue;
|
|
5399
5876
|
}
|
|
@@ -5427,12 +5904,12 @@ function rewriteMarkdownReferences(content, replacements) {
|
|
|
5427
5904
|
});
|
|
5428
5905
|
}
|
|
5429
5906
|
async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
5430
|
-
const originalBytes = await
|
|
5907
|
+
const originalBytes = await fs10.readFile(absolutePath);
|
|
5431
5908
|
const originalText = originalBytes.toString("utf8");
|
|
5432
|
-
const title = titleFromText(
|
|
5909
|
+
const title = titleFromText(path11.basename(absolutePath, path11.extname(absolutePath)), originalText);
|
|
5433
5910
|
const attachments = [];
|
|
5434
5911
|
for (const attachmentRef of attachmentRefs) {
|
|
5435
|
-
const bytes = await
|
|
5912
|
+
const bytes = await fs10.readFile(attachmentRef.absolutePath);
|
|
5436
5913
|
attachments.push({
|
|
5437
5914
|
relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
|
|
5438
5915
|
mimeType: guessMimeType(attachmentRef.absolutePath),
|
|
@@ -5456,7 +5933,7 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
|
5456
5933
|
sourceKind: "markdown",
|
|
5457
5934
|
originalPath: toPosix(absolutePath),
|
|
5458
5935
|
mimeType: "text/markdown",
|
|
5459
|
-
storedExtension:
|
|
5936
|
+
storedExtension: path11.extname(absolutePath) || ".md",
|
|
5460
5937
|
payloadBytes: Buffer.from(rewrittenText, "utf8"),
|
|
5461
5938
|
extractedText: rewrittenText,
|
|
5462
5939
|
extractionArtifact,
|
|
@@ -5466,12 +5943,12 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
|
5466
5943
|
};
|
|
5467
5944
|
}
|
|
5468
5945
|
async function prepareInboxHtmlInput(absolutePath, attachmentRefs) {
|
|
5469
|
-
const originalBytes = await
|
|
5946
|
+
const originalBytes = await fs10.readFile(absolutePath);
|
|
5470
5947
|
const originalHtml = originalBytes.toString("utf8");
|
|
5471
5948
|
const initialConversion = await convertHtmlToMarkdown(originalHtml, pathToFileURL(absolutePath).toString());
|
|
5472
5949
|
const attachments = [];
|
|
5473
5950
|
for (const attachmentRef of attachmentRefs) {
|
|
5474
|
-
const bytes = await
|
|
5951
|
+
const bytes = await fs10.readFile(attachmentRef.absolutePath);
|
|
5475
5952
|
attachments.push({
|
|
5476
5953
|
relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
|
|
5477
5954
|
mimeType: guessMimeType(attachmentRef.absolutePath),
|
|
@@ -5480,7 +5957,7 @@ async function prepareInboxHtmlInput(absolutePath, attachmentRefs) {
|
|
|
5480
5957
|
});
|
|
5481
5958
|
}
|
|
5482
5959
|
const contentHash = buildCompositeHash(originalBytes, attachments);
|
|
5483
|
-
const fallbackTitle =
|
|
5960
|
+
const fallbackTitle = path11.basename(absolutePath, path11.extname(absolutePath));
|
|
5484
5961
|
const title = initialConversion.title || fallbackTitle;
|
|
5485
5962
|
const sourceId = `${slugify(title)}-${contentHash.slice(0, 8)}`;
|
|
5486
5963
|
const replacements = new Map(
|
|
@@ -5498,7 +5975,7 @@ async function prepareInboxHtmlInput(absolutePath, attachmentRefs) {
|
|
|
5498
5975
|
sourceKind: "html",
|
|
5499
5976
|
originalPath: toPosix(absolutePath),
|
|
5500
5977
|
mimeType: "text/html",
|
|
5501
|
-
storedExtension:
|
|
5978
|
+
storedExtension: path11.extname(absolutePath) || ".html",
|
|
5502
5979
|
payloadBytes: Buffer.from(rewrittenHtml, "utf8"),
|
|
5503
5980
|
extractedText: converted.markdown,
|
|
5504
5981
|
extractionArtifact,
|
|
@@ -5513,8 +5990,8 @@ function isSupportedInboxKind(sourceKind) {
|
|
|
5513
5990
|
async function ingestInput(rootDir, input, options) {
|
|
5514
5991
|
const { paths } = await initWorkspace(rootDir);
|
|
5515
5992
|
const normalizedOptions = normalizeIngestOptions(options);
|
|
5516
|
-
const absoluteInput =
|
|
5517
|
-
const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ??
|
|
5993
|
+
const absoluteInput = path11.resolve(rootDir, input);
|
|
5994
|
+
const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ?? path11.dirname(absoluteInput));
|
|
5518
5995
|
const prepared = isHttpUrl(input) ? await prepareUrlInput(rootDir, input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
|
|
5519
5996
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
5520
5997
|
return result.manifest;
|
|
@@ -5604,7 +6081,7 @@ async function addInput(rootDir, input, options = {}) {
|
|
|
5604
6081
|
async function ingestDirectory(rootDir, inputDir, options) {
|
|
5605
6082
|
const { paths } = await initWorkspace(rootDir);
|
|
5606
6083
|
const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
|
|
5607
|
-
const absoluteInputDir =
|
|
6084
|
+
const absoluteInputDir = path11.resolve(rootDir, inputDir);
|
|
5608
6085
|
const repoRoot = normalizedOptions.repoRoot ?? await findNearestGitRoot2(absoluteInputDir) ?? absoluteInputDir;
|
|
5609
6086
|
if (!await fileExists(absoluteInputDir)) {
|
|
5610
6087
|
throw new Error(`Directory not found: ${absoluteInputDir}`);
|
|
@@ -5613,7 +6090,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
5613
6090
|
const imported = [];
|
|
5614
6091
|
const updated = [];
|
|
5615
6092
|
for (const absolutePath of files) {
|
|
5616
|
-
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
6093
|
+
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
|
|
5617
6094
|
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
|
|
5618
6095
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
5619
6096
|
if (result.isNew) {
|
|
@@ -5621,11 +6098,11 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
5621
6098
|
} else if (result.wasUpdated) {
|
|
5622
6099
|
updated.push(result.manifest);
|
|
5623
6100
|
} else {
|
|
5624
|
-
skipped.push({ path: toPosix(
|
|
6101
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "duplicate_content" });
|
|
5625
6102
|
}
|
|
5626
6103
|
}
|
|
5627
|
-
await appendLogEntry(rootDir, "ingest_directory", toPosix(
|
|
5628
|
-
`repo_root=${toPosix(
|
|
6104
|
+
await appendLogEntry(rootDir, "ingest_directory", toPosix(path11.relative(rootDir, absoluteInputDir)) || ".", [
|
|
6105
|
+
`repo_root=${toPosix(path11.relative(rootDir, repoRoot)) || "."}`,
|
|
5629
6106
|
`scanned=${files.length}`,
|
|
5630
6107
|
`imported=${imported.length}`,
|
|
5631
6108
|
`updated=${updated.length}`,
|
|
@@ -5642,7 +6119,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
5642
6119
|
}
|
|
5643
6120
|
async function importInbox(rootDir, inputDir) {
|
|
5644
6121
|
const { paths } = await initWorkspace(rootDir);
|
|
5645
|
-
const effectiveInputDir =
|
|
6122
|
+
const effectiveInputDir = path11.resolve(rootDir, inputDir ?? paths.inboxDir);
|
|
5646
6123
|
if (!await fileExists(effectiveInputDir)) {
|
|
5647
6124
|
throw new Error(`Inbox directory not found: ${effectiveInputDir}`);
|
|
5648
6125
|
}
|
|
@@ -5653,31 +6130,31 @@ async function importInbox(rootDir, inputDir) {
|
|
|
5653
6130
|
const skipped = [];
|
|
5654
6131
|
let attachmentCount = 0;
|
|
5655
6132
|
for (const absolutePath of files) {
|
|
5656
|
-
const basename =
|
|
6133
|
+
const basename = path11.basename(absolutePath);
|
|
5657
6134
|
if (basename.startsWith(".")) {
|
|
5658
|
-
skipped.push({ path: toPosix(
|
|
6135
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "hidden_file" });
|
|
5659
6136
|
continue;
|
|
5660
6137
|
}
|
|
5661
6138
|
if (claimedAttachments.has(absolutePath)) {
|
|
5662
|
-
skipped.push({ path: toPosix(
|
|
6139
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
|
|
5663
6140
|
continue;
|
|
5664
6141
|
}
|
|
5665
6142
|
const mimeType = guessMimeType(absolutePath);
|
|
5666
6143
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
5667
6144
|
if (!isSupportedInboxKind(sourceKind)) {
|
|
5668
|
-
skipped.push({ path: toPosix(
|
|
6145
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
5669
6146
|
continue;
|
|
5670
6147
|
}
|
|
5671
6148
|
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
6149
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
5673
6150
|
if (!result.isNew) {
|
|
5674
|
-
skipped.push({ path: toPosix(
|
|
6151
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "duplicate_content" });
|
|
5675
6152
|
continue;
|
|
5676
6153
|
}
|
|
5677
6154
|
attachmentCount += result.manifest.attachments?.length ?? 0;
|
|
5678
6155
|
imported.push(result.manifest);
|
|
5679
6156
|
}
|
|
5680
|
-
await appendLogEntry(rootDir, "inbox_import", toPosix(
|
|
6157
|
+
await appendLogEntry(rootDir, "inbox_import", toPosix(path11.relative(rootDir, effectiveInputDir)) || ".", [
|
|
5681
6158
|
`scanned=${files.length}`,
|
|
5682
6159
|
`imported=${imported.length}`,
|
|
5683
6160
|
`attachments=${attachmentCount}`,
|
|
@@ -5696,9 +6173,9 @@ async function listManifests(rootDir) {
|
|
|
5696
6173
|
if (!await fileExists(paths.manifestsDir)) {
|
|
5697
6174
|
return [];
|
|
5698
6175
|
}
|
|
5699
|
-
const entries = await
|
|
6176
|
+
const entries = await fs10.readdir(paths.manifestsDir);
|
|
5700
6177
|
const manifests = await Promise.all(
|
|
5701
|
-
entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(
|
|
6178
|
+
entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path11.join(paths.manifestsDir, entry)))
|
|
5702
6179
|
);
|
|
5703
6180
|
return manifests.filter((manifest) => Boolean(manifest));
|
|
5704
6181
|
}
|
|
@@ -5706,17 +6183,17 @@ async function readExtractedText(rootDir, manifest) {
|
|
|
5706
6183
|
if (!manifest.extractedTextPath) {
|
|
5707
6184
|
return void 0;
|
|
5708
6185
|
}
|
|
5709
|
-
const absolutePath =
|
|
6186
|
+
const absolutePath = path11.resolve(rootDir, manifest.extractedTextPath);
|
|
5710
6187
|
if (!await fileExists(absolutePath)) {
|
|
5711
6188
|
return void 0;
|
|
5712
6189
|
}
|
|
5713
|
-
return
|
|
6190
|
+
return fs10.readFile(absolutePath, "utf8");
|
|
5714
6191
|
}
|
|
5715
6192
|
async function readExtractionArtifact(rootDir, manifest) {
|
|
5716
6193
|
if (!manifest.extractedMetadataPath) {
|
|
5717
6194
|
return void 0;
|
|
5718
6195
|
}
|
|
5719
|
-
const absolutePath =
|
|
6196
|
+
const absolutePath = path11.resolve(rootDir, manifest.extractedMetadataPath);
|
|
5720
6197
|
if (!await fileExists(absolutePath)) {
|
|
5721
6198
|
return void 0;
|
|
5722
6199
|
}
|
|
@@ -5724,20 +6201,20 @@ async function readExtractionArtifact(rootDir, manifest) {
|
|
|
5724
6201
|
}
|
|
5725
6202
|
|
|
5726
6203
|
// src/mcp.ts
|
|
5727
|
-
import
|
|
5728
|
-
import
|
|
6204
|
+
import fs18 from "fs/promises";
|
|
6205
|
+
import path22 from "path";
|
|
5729
6206
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5730
6207
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5731
6208
|
import { z as z8 } from "zod";
|
|
5732
6209
|
|
|
5733
6210
|
// src/schema.ts
|
|
5734
|
-
import
|
|
5735
|
-
import
|
|
6211
|
+
import fs11 from "fs/promises";
|
|
6212
|
+
import path12 from "path";
|
|
5736
6213
|
function normalizeSchemaContent(content) {
|
|
5737
6214
|
return content.trim() ? content.trim() : defaultVaultSchema().trim();
|
|
5738
6215
|
}
|
|
5739
6216
|
async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
|
|
5740
|
-
const content = await fileExists(schemaPath) ? await
|
|
6217
|
+
const content = await fileExists(schemaPath) ? await fs11.readFile(schemaPath, "utf8") : fallback;
|
|
5741
6218
|
const normalized = normalizeSchemaContent(content);
|
|
5742
6219
|
return {
|
|
5743
6220
|
path: schemaPath,
|
|
@@ -5746,7 +6223,7 @@ async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
|
|
|
5746
6223
|
};
|
|
5747
6224
|
}
|
|
5748
6225
|
function resolveProjectSchemaPath(rootDir, schemaPath) {
|
|
5749
|
-
return
|
|
6226
|
+
return path12.resolve(rootDir, schemaPath);
|
|
5750
6227
|
}
|
|
5751
6228
|
function composeVaultSchema(root, projectSchemas = []) {
|
|
5752
6229
|
if (!projectSchemas.length) {
|
|
@@ -5762,7 +6239,7 @@ function composeVaultSchema(root, projectSchemas = []) {
|
|
|
5762
6239
|
(schema) => [
|
|
5763
6240
|
`## Project Schema`,
|
|
5764
6241
|
"",
|
|
5765
|
-
`Path: ${toPosix(
|
|
6242
|
+
`Path: ${toPosix(path12.relative(path12.dirname(root.path), schema.path) || schema.path)}`,
|
|
5766
6243
|
"",
|
|
5767
6244
|
schema.content
|
|
5768
6245
|
].join("\n")
|
|
@@ -5838,13 +6315,13 @@ function buildSchemaPrompt(schema, instruction) {
|
|
|
5838
6315
|
}
|
|
5839
6316
|
|
|
5840
6317
|
// src/vault.ts
|
|
5841
|
-
import
|
|
5842
|
-
import
|
|
6318
|
+
import fs17 from "fs/promises";
|
|
6319
|
+
import path21 from "path";
|
|
5843
6320
|
import matter9 from "gray-matter";
|
|
5844
6321
|
import { z as z7 } from "zod";
|
|
5845
6322
|
|
|
5846
6323
|
// src/analysis.ts
|
|
5847
|
-
import
|
|
6324
|
+
import path13 from "path";
|
|
5848
6325
|
import { z as z2 } from "zod";
|
|
5849
6326
|
var ANALYSIS_FORMAT_VERSION = 5;
|
|
5850
6327
|
var sourceAnalysisSchema = z2.object({
|
|
@@ -6067,7 +6544,7 @@ function extractionWarningSummary(manifest, extraction) {
|
|
|
6067
6544
|
return `Imported ${manifest.sourceKind} source. Text extraction is not yet available for this source.`;
|
|
6068
6545
|
}
|
|
6069
6546
|
async function analyzeSource(manifest, extractedText, provider, paths, schema) {
|
|
6070
|
-
const cachePath =
|
|
6547
|
+
const cachePath = path13.join(paths.analysesDir, `${manifest.sourceId}.json`);
|
|
6071
6548
|
const cached = await readJsonFile(cachePath);
|
|
6072
6549
|
if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
|
|
6073
6550
|
return cached;
|
|
@@ -6138,163 +6615,6 @@ function analysisSignature(analysis) {
|
|
|
6138
6615
|
return sha256(JSON.stringify(analysis));
|
|
6139
6616
|
}
|
|
6140
6617
|
|
|
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
6618
|
// src/confidence.ts
|
|
6299
6619
|
function nodeConfidence(sourceCount) {
|
|
6300
6620
|
return Math.min(0.5 + sourceCount * 0.15, 0.95);
|
|
@@ -6312,8 +6632,8 @@ function conflictConfidence(claimA, claimB) {
|
|
|
6312
6632
|
}
|
|
6313
6633
|
|
|
6314
6634
|
// src/deep-lint.ts
|
|
6315
|
-
import
|
|
6316
|
-
import
|
|
6635
|
+
import fs12 from "fs/promises";
|
|
6636
|
+
import path16 from "path";
|
|
6317
6637
|
import matter4 from "gray-matter";
|
|
6318
6638
|
import { z as z5 } from "zod";
|
|
6319
6639
|
|
|
@@ -6334,7 +6654,7 @@ function normalizeFindingSeverity(value) {
|
|
|
6334
6654
|
|
|
6335
6655
|
// src/orchestration.ts
|
|
6336
6656
|
import { spawn } from "child_process";
|
|
6337
|
-
import
|
|
6657
|
+
import path14 from "path";
|
|
6338
6658
|
import { z as z3 } from "zod";
|
|
6339
6659
|
var orchestrationRoleResultSchema = z3.object({
|
|
6340
6660
|
summary: z3.string().optional(),
|
|
@@ -6427,7 +6747,7 @@ async function runProviderRole(rootDir, role, roleConfig, input) {
|
|
|
6427
6747
|
}
|
|
6428
6748
|
async function runCommandRole(rootDir, role, executor, input) {
|
|
6429
6749
|
const [command, ...args] = executor.command;
|
|
6430
|
-
const cwd = executor.cwd ?
|
|
6750
|
+
const cwd = executor.cwd ? path14.resolve(rootDir, executor.cwd) : rootDir;
|
|
6431
6751
|
const child = spawn(command, args, {
|
|
6432
6752
|
cwd,
|
|
6433
6753
|
env: {
|
|
@@ -6521,7 +6841,7 @@ function summarizeRoleQuestions(results) {
|
|
|
6521
6841
|
}
|
|
6522
6842
|
|
|
6523
6843
|
// src/web-search/registry.ts
|
|
6524
|
-
import
|
|
6844
|
+
import path15 from "path";
|
|
6525
6845
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
6526
6846
|
import { z as z4 } from "zod";
|
|
6527
6847
|
|
|
@@ -6619,7 +6939,7 @@ async function createWebSearchAdapter(id, config, rootDir) {
|
|
|
6619
6939
|
if (!config.module) {
|
|
6620
6940
|
throw new Error(`Web search provider ${id} is type "custom" but no module path was configured.`);
|
|
6621
6941
|
}
|
|
6622
|
-
const resolvedModule =
|
|
6942
|
+
const resolvedModule = path15.isAbsolute(config.module) ? config.module : path15.resolve(rootDir, config.module);
|
|
6623
6943
|
const loaded = await import(pathToFileURL2(resolvedModule).href);
|
|
6624
6944
|
const parsed = customWebSearchModuleSchema.parse(loaded);
|
|
6625
6945
|
return parsed.createAdapter(id, config, rootDir);
|
|
@@ -6679,8 +6999,8 @@ async function loadContextPages(rootDir, graph) {
|
|
|
6679
6999
|
);
|
|
6680
7000
|
return Promise.all(
|
|
6681
7001
|
contextPages.slice(0, 18).map(async (page) => {
|
|
6682
|
-
const absolutePath =
|
|
6683
|
-
const raw = await
|
|
7002
|
+
const absolutePath = path16.join(paths.wikiDir, page.path);
|
|
7003
|
+
const raw = await fs12.readFile(absolutePath, "utf8").catch(() => "");
|
|
6684
7004
|
const parsed = matter4(raw);
|
|
6685
7005
|
return {
|
|
6686
7006
|
id: page.id,
|
|
@@ -6728,7 +7048,7 @@ function heuristicDeepFindings(contextPages, structuralFindings, graph) {
|
|
|
6728
7048
|
code: "missing_citation",
|
|
6729
7049
|
message: finding.message,
|
|
6730
7050
|
pagePath: finding.pagePath,
|
|
6731
|
-
suggestedQuery: finding.pagePath ? `Which sources support the claims in ${
|
|
7051
|
+
suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path16.basename(finding.pagePath, ".md")}?` : void 0
|
|
6732
7052
|
});
|
|
6733
7053
|
}
|
|
6734
7054
|
for (const page of contextPages.filter((item) => item.kind === "source").slice(0, 3)) {
|
|
@@ -6906,8 +7226,8 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
|
|
|
6906
7226
|
}
|
|
6907
7227
|
|
|
6908
7228
|
// src/embeddings.ts
|
|
6909
|
-
import
|
|
6910
|
-
import
|
|
7229
|
+
import fs13 from "fs/promises";
|
|
7230
|
+
import path17 from "path";
|
|
6911
7231
|
var MAX_EMBEDDING_BATCH = 32;
|
|
6912
7232
|
var MAX_SIMILARITY_NODES = 240;
|
|
6913
7233
|
function cosineSimilarity(left, right) {
|
|
@@ -6941,8 +7261,8 @@ async function loadPageContents(rootDir, graph) {
|
|
|
6941
7261
|
const contents = /* @__PURE__ */ new Map();
|
|
6942
7262
|
await Promise.all(
|
|
6943
7263
|
graph.pages.map(async (page) => {
|
|
6944
|
-
const absolutePath =
|
|
6945
|
-
const content = await
|
|
7264
|
+
const absolutePath = path17.join(paths.wikiDir, page.path);
|
|
7265
|
+
const content = await fs13.readFile(absolutePath, "utf8").catch(() => {
|
|
6946
7266
|
process.stderr.write(`[swarmvault] Warning: could not read page ${page.path} for embedding
|
|
6947
7267
|
`);
|
|
6948
7268
|
return "";
|
|
@@ -8444,15 +8764,15 @@ function sourceTypeForNode(node, pagesById) {
|
|
|
8444
8764
|
return pagesById.get(node.pageId)?.sourceType;
|
|
8445
8765
|
}
|
|
8446
8766
|
function supportingPathDetails(graph, edge) {
|
|
8447
|
-
const
|
|
8767
|
+
const path26 = shortestGraphPath(graph, edge.source, edge.target);
|
|
8448
8768
|
const edgesById = new Map(graph.edges.map((item) => [item.id, item]));
|
|
8449
|
-
const pathEdges =
|
|
8769
|
+
const pathEdges = path26.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
|
|
8450
8770
|
return {
|
|
8451
|
-
pathNodeIds:
|
|
8452
|
-
pathEdgeIds:
|
|
8771
|
+
pathNodeIds: path26.nodeIds,
|
|
8772
|
+
pathEdgeIds: path26.edgeIds,
|
|
8453
8773
|
pathRelations: pathEdges.map((item) => item.relation),
|
|
8454
8774
|
pathEvidenceClasses: pathEdges.map((item) => item.evidenceClass),
|
|
8455
|
-
pathSummary:
|
|
8775
|
+
pathSummary: path26.summary
|
|
8456
8776
|
};
|
|
8457
8777
|
}
|
|
8458
8778
|
function surpriseScore(edge, graph, pagesById, hyperedgesByNodeId) {
|
|
@@ -8521,7 +8841,7 @@ function topSurprisingConnections(graph, pagesById) {
|
|
|
8521
8841
|
}).map((edge) => {
|
|
8522
8842
|
const source = nodesById.get(edge.source);
|
|
8523
8843
|
const target = nodesById.get(edge.target);
|
|
8524
|
-
const
|
|
8844
|
+
const path26 = supportingPathDetails(graph, edge);
|
|
8525
8845
|
const scored = surpriseScore(edge, graph, pagesById, hyperedgesByNodeId);
|
|
8526
8846
|
return {
|
|
8527
8847
|
id: edge.id,
|
|
@@ -8532,11 +8852,11 @@ function topSurprisingConnections(graph, pagesById) {
|
|
|
8532
8852
|
relation: edge.relation,
|
|
8533
8853
|
evidenceClass: edge.evidenceClass,
|
|
8534
8854
|
confidence: edge.confidence,
|
|
8535
|
-
pathNodeIds:
|
|
8536
|
-
pathEdgeIds:
|
|
8537
|
-
pathRelations:
|
|
8538
|
-
pathEvidenceClasses:
|
|
8539
|
-
pathSummary:
|
|
8855
|
+
pathNodeIds: path26.pathNodeIds,
|
|
8856
|
+
pathEdgeIds: path26.pathEdgeIds,
|
|
8857
|
+
pathRelations: path26.pathRelations,
|
|
8858
|
+
pathEvidenceClasses: path26.pathEvidenceClasses,
|
|
8859
|
+
pathSummary: path26.pathSummary,
|
|
8540
8860
|
why: scored.why,
|
|
8541
8861
|
explanation: scored.explanation,
|
|
8542
8862
|
surpriseScore: scored.score
|
|
@@ -9421,13 +9741,13 @@ function buildOutputAssetManifest(input) {
|
|
|
9421
9741
|
}
|
|
9422
9742
|
|
|
9423
9743
|
// src/outputs.ts
|
|
9424
|
-
import
|
|
9425
|
-
import
|
|
9744
|
+
import fs15 from "fs/promises";
|
|
9745
|
+
import path19 from "path";
|
|
9426
9746
|
import matter7 from "gray-matter";
|
|
9427
9747
|
|
|
9428
9748
|
// src/pages.ts
|
|
9429
|
-
import
|
|
9430
|
-
import
|
|
9749
|
+
import fs14 from "fs/promises";
|
|
9750
|
+
import path18 from "path";
|
|
9431
9751
|
import matter6 from "gray-matter";
|
|
9432
9752
|
function normalizeStringArray(value) {
|
|
9433
9753
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
@@ -9505,7 +9825,7 @@ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
|
|
|
9505
9825
|
updatedAt: updatedFallback
|
|
9506
9826
|
};
|
|
9507
9827
|
}
|
|
9508
|
-
const content = await
|
|
9828
|
+
const content = await fs14.readFile(absolutePath, "utf8");
|
|
9509
9829
|
const parsed = matter6(content);
|
|
9510
9830
|
return {
|
|
9511
9831
|
status: normalizePageStatus(parsed.data.status, defaults.status ?? "active"),
|
|
@@ -9544,7 +9864,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
|
|
|
9544
9864
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9545
9865
|
const fallbackCreatedAt = defaults.createdAt ?? now;
|
|
9546
9866
|
const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
|
|
9547
|
-
const title = typeof parsed.data.title === "string" ? parsed.data.title :
|
|
9867
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : path18.basename(relativePath, ".md");
|
|
9548
9868
|
const kind = inferPageKind(relativePath, parsed.data.kind);
|
|
9549
9869
|
const sourceIds = normalizeStringArray(parsed.data.source_ids);
|
|
9550
9870
|
const projectIds = normalizeProjectIds(parsed.data.project_ids);
|
|
@@ -9585,18 +9905,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
|
|
|
9585
9905
|
};
|
|
9586
9906
|
}
|
|
9587
9907
|
async function loadInsightPages(wikiDir) {
|
|
9588
|
-
const insightsDir =
|
|
9908
|
+
const insightsDir = path18.join(wikiDir, "insights");
|
|
9589
9909
|
if (!await fileExists(insightsDir)) {
|
|
9590
9910
|
return [];
|
|
9591
9911
|
}
|
|
9592
|
-
const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) =>
|
|
9912
|
+
const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path18.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
|
|
9593
9913
|
const insights = [];
|
|
9594
9914
|
for (const absolutePath of files) {
|
|
9595
|
-
const relativePath = toPosix(
|
|
9596
|
-
const content = await
|
|
9915
|
+
const relativePath = toPosix(path18.relative(wikiDir, absolutePath));
|
|
9916
|
+
const content = await fs14.readFile(absolutePath, "utf8");
|
|
9597
9917
|
const parsed = matter6(content);
|
|
9598
|
-
const stats = await
|
|
9599
|
-
const title = typeof parsed.data.title === "string" ? parsed.data.title :
|
|
9918
|
+
const stats = await fs14.stat(absolutePath);
|
|
9919
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : path18.basename(absolutePath, ".md");
|
|
9600
9920
|
const sourceIds = normalizeStringArray(parsed.data.source_ids);
|
|
9601
9921
|
const projectIds = normalizeProjectIds(parsed.data.project_ids);
|
|
9602
9922
|
const nodeIds = normalizeStringArray(parsed.data.node_ids);
|
|
@@ -9659,27 +9979,27 @@ function relatedOutputsForPage(targetPage, outputPages) {
|
|
|
9659
9979
|
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
9980
|
}
|
|
9661
9981
|
async function resolveUniqueOutputSlug(wikiDir, baseSlug) {
|
|
9662
|
-
const outputsDir =
|
|
9982
|
+
const outputsDir = path19.join(wikiDir, "outputs");
|
|
9663
9983
|
const root = baseSlug || "output";
|
|
9664
9984
|
let candidate = root;
|
|
9665
9985
|
let counter = 2;
|
|
9666
|
-
while (await fileExists(
|
|
9986
|
+
while (await fileExists(path19.join(outputsDir, `${candidate}.md`))) {
|
|
9667
9987
|
candidate = `${root}-${counter}`;
|
|
9668
9988
|
counter++;
|
|
9669
9989
|
}
|
|
9670
9990
|
return candidate;
|
|
9671
9991
|
}
|
|
9672
9992
|
async function loadSavedOutputPages(wikiDir) {
|
|
9673
|
-
const outputsDir =
|
|
9674
|
-
const entries = await
|
|
9993
|
+
const outputsDir = path19.join(wikiDir, "outputs");
|
|
9994
|
+
const entries = await fs15.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
|
|
9675
9995
|
const outputs = [];
|
|
9676
9996
|
for (const entry of entries) {
|
|
9677
9997
|
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md") {
|
|
9678
9998
|
continue;
|
|
9679
9999
|
}
|
|
9680
|
-
const relativePath =
|
|
9681
|
-
const absolutePath =
|
|
9682
|
-
const content = await
|
|
10000
|
+
const relativePath = path19.posix.join("outputs", entry.name);
|
|
10001
|
+
const absolutePath = path19.join(outputsDir, entry.name);
|
|
10002
|
+
const content = await fs15.readFile(absolutePath, "utf8");
|
|
9683
10003
|
const parsed = matter7(content);
|
|
9684
10004
|
const slug = entry.name.replace(/\.md$/, "");
|
|
9685
10005
|
const title = typeof parsed.data.title === "string" ? parsed.data.title : slug;
|
|
@@ -9692,7 +10012,7 @@ async function loadSavedOutputPages(wikiDir) {
|
|
|
9692
10012
|
const relatedSourceIds = normalizeStringArray(parsed.data.related_source_ids);
|
|
9693
10013
|
const backlinks = normalizeStringArray(parsed.data.backlinks);
|
|
9694
10014
|
const compiledFrom = normalizeStringArray(parsed.data.compiled_from);
|
|
9695
|
-
const stats = await
|
|
10015
|
+
const stats = await fs15.stat(absolutePath);
|
|
9696
10016
|
const createdAt = typeof parsed.data.created_at === "string" ? parsed.data.created_at : stats.birthtimeMs > 0 ? stats.birthtime.toISOString() : stats.mtime.toISOString();
|
|
9697
10017
|
const updatedAt = typeof parsed.data.updated_at === "string" ? parsed.data.updated_at : stats.mtime.toISOString();
|
|
9698
10018
|
outputs.push({
|
|
@@ -9730,8 +10050,8 @@ async function loadSavedOutputPages(wikiDir) {
|
|
|
9730
10050
|
}
|
|
9731
10051
|
|
|
9732
10052
|
// src/search.ts
|
|
9733
|
-
import
|
|
9734
|
-
import
|
|
10053
|
+
import fs16 from "fs/promises";
|
|
10054
|
+
import path20 from "path";
|
|
9735
10055
|
import matter8 from "gray-matter";
|
|
9736
10056
|
function getDatabaseSync() {
|
|
9737
10057
|
const builtin = process.getBuiltinModule?.("node:sqlite");
|
|
@@ -9757,7 +10077,7 @@ function normalizeSourceClass2(value) {
|
|
|
9757
10077
|
return value === "first_party" || value === "third_party" || value === "resource" || value === "generated" ? value : void 0;
|
|
9758
10078
|
}
|
|
9759
10079
|
async function rebuildSearchIndex(dbPath, pages, wikiDir) {
|
|
9760
|
-
await ensureDir(
|
|
10080
|
+
await ensureDir(path20.dirname(dbPath));
|
|
9761
10081
|
const DatabaseSync = getDatabaseSync();
|
|
9762
10082
|
const db = new DatabaseSync(dbPath);
|
|
9763
10083
|
db.exec("PRAGMA journal_mode = WAL;");
|
|
@@ -9789,8 +10109,8 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
|
|
|
9789
10109
|
"INSERT INTO pages (id, path, title, body, kind, status, source_type, source_class, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
9790
10110
|
);
|
|
9791
10111
|
for (const page of pages) {
|
|
9792
|
-
const absolutePath =
|
|
9793
|
-
const content = await
|
|
10112
|
+
const absolutePath = path20.join(wikiDir, page.path);
|
|
10113
|
+
const content = await fs16.readFile(absolutePath, "utf8");
|
|
9794
10114
|
const parsed = matter8(content);
|
|
9795
10115
|
insertPage.run(
|
|
9796
10116
|
page.id,
|
|
@@ -9907,7 +10227,7 @@ function outputFormatInstruction(format) {
|
|
|
9907
10227
|
}
|
|
9908
10228
|
}
|
|
9909
10229
|
function outputAssetPath(slug, fileName) {
|
|
9910
|
-
return toPosix(
|
|
10230
|
+
return toPosix(path21.join("outputs", "assets", slug, fileName));
|
|
9911
10231
|
}
|
|
9912
10232
|
function outputAssetId(slug, role) {
|
|
9913
10233
|
return `output:${slug}:asset:${role}`;
|
|
@@ -10047,7 +10367,7 @@ async function resolveImageGenerationProvider(rootDir) {
|
|
|
10047
10367
|
if (!providerConfig) {
|
|
10048
10368
|
throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
|
|
10049
10369
|
}
|
|
10050
|
-
const { createProvider: createProvider2 } = await import("./registry-
|
|
10370
|
+
const { createProvider: createProvider2 } = await import("./registry-G7NSRYCO.js");
|
|
10051
10371
|
return createProvider2(preferredProviderId, providerConfig, rootDir);
|
|
10052
10372
|
}
|
|
10053
10373
|
async function generateOutputArtifacts(rootDir, input) {
|
|
@@ -10245,7 +10565,7 @@ async function generateOutputArtifacts(rootDir, input) {
|
|
|
10245
10565
|
};
|
|
10246
10566
|
}
|
|
10247
10567
|
function normalizeProjectRoot(root) {
|
|
10248
|
-
const normalized = toPosix(
|
|
10568
|
+
const normalized = toPosix(path21.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
10249
10569
|
return normalized;
|
|
10250
10570
|
}
|
|
10251
10571
|
function projectEntries(config) {
|
|
@@ -10271,10 +10591,10 @@ function manifestPathForProject(rootDir, manifest) {
|
|
|
10271
10591
|
if (!rawPath) {
|
|
10272
10592
|
return toPosix(manifest.storedPath);
|
|
10273
10593
|
}
|
|
10274
|
-
if (!
|
|
10594
|
+
if (!path21.isAbsolute(rawPath)) {
|
|
10275
10595
|
return normalizeProjectRoot(rawPath);
|
|
10276
10596
|
}
|
|
10277
|
-
const relative = toPosix(
|
|
10597
|
+
const relative = toPosix(path21.relative(rootDir, rawPath));
|
|
10278
10598
|
return relative.startsWith("..") ? toPosix(rawPath) : normalizeProjectRoot(relative);
|
|
10279
10599
|
}
|
|
10280
10600
|
function prefixMatches(value, prefix) {
|
|
@@ -10448,7 +10768,7 @@ function pageHashes(pages) {
|
|
|
10448
10768
|
return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
|
|
10449
10769
|
}
|
|
10450
10770
|
async function buildManagedGraphPage(absolutePath, defaults, build) {
|
|
10451
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
10771
|
+
const existingContent = await fileExists(absolutePath) ? await fs17.readFile(absolutePath, "utf8") : null;
|
|
10452
10772
|
let existing = await loadExistingManagedPageState(absolutePath, {
|
|
10453
10773
|
status: defaults.status ?? "active",
|
|
10454
10774
|
managedBy: defaults.managedBy
|
|
@@ -10486,7 +10806,7 @@ async function buildManagedGraphPage(absolutePath, defaults, build) {
|
|
|
10486
10806
|
return built;
|
|
10487
10807
|
}
|
|
10488
10808
|
async function buildManagedContent(absolutePath, defaults, build) {
|
|
10489
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
10809
|
+
const existingContent = await fileExists(absolutePath) ? await fs17.readFile(absolutePath, "utf8") : null;
|
|
10490
10810
|
let existing = await loadExistingManagedPageState(absolutePath, {
|
|
10491
10811
|
status: defaults.status ?? "active",
|
|
10492
10812
|
managedBy: defaults.managedBy
|
|
@@ -10609,7 +10929,7 @@ function resetGraphNodeMetrics(nodes) {
|
|
|
10609
10929
|
return nodes.map(({ communityId: _communityId, degree: _degree, bridgeScore: _bridgeScore, isGodNode: _isGodNode, ...node }) => node);
|
|
10610
10930
|
}
|
|
10611
10931
|
function manifestRepoPath(manifest) {
|
|
10612
|
-
return toPosix(manifest.repoRelativePath ??
|
|
10932
|
+
return toPosix(manifest.repoRelativePath ?? path21.basename(manifest.originalPath ?? manifest.storedPath));
|
|
10613
10933
|
}
|
|
10614
10934
|
function goPackageScopeKey(manifest, analysis) {
|
|
10615
10935
|
if (analysis.code?.language !== "go") {
|
|
@@ -10619,7 +10939,7 @@ function goPackageScopeKey(manifest, analysis) {
|
|
|
10619
10939
|
if (!packageName) {
|
|
10620
10940
|
return null;
|
|
10621
10941
|
}
|
|
10622
|
-
return `${packageName}:${
|
|
10942
|
+
return `${packageName}:${path21.posix.dirname(manifestRepoPath(manifest))}`;
|
|
10623
10943
|
}
|
|
10624
10944
|
function buildGoPackageSymbolLookups(analyses, manifestsById) {
|
|
10625
10945
|
const lookups = /* @__PURE__ */ new Map();
|
|
@@ -11046,7 +11366,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
|
|
|
11046
11366
|
const benchmark = await readJsonFile(paths.benchmarkPath);
|
|
11047
11367
|
const communityRecords = [];
|
|
11048
11368
|
for (const community of graph.communities ?? []) {
|
|
11049
|
-
const absolutePath =
|
|
11369
|
+
const absolutePath = path21.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
|
|
11050
11370
|
communityRecords.push(
|
|
11051
11371
|
await buildManagedGraphPage(
|
|
11052
11372
|
absolutePath,
|
|
@@ -11074,7 +11394,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
|
|
|
11074
11394
|
recentResearchSources: recentResearchSourcePages(graph, previousCompiledAt),
|
|
11075
11395
|
graphHash: graphHash(graph)
|
|
11076
11396
|
});
|
|
11077
|
-
const reportAbsolutePath =
|
|
11397
|
+
const reportAbsolutePath = path21.join(paths.wikiDir, "graph", "report.md");
|
|
11078
11398
|
const reportRecord = await buildManagedGraphPage(
|
|
11079
11399
|
reportAbsolutePath,
|
|
11080
11400
|
{
|
|
@@ -11095,7 +11415,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
|
|
|
11095
11415
|
};
|
|
11096
11416
|
}
|
|
11097
11417
|
async function writePage(wikiDir, relativePath, content, changedPages) {
|
|
11098
|
-
const absolutePath =
|
|
11418
|
+
const absolutePath = path21.resolve(wikiDir, relativePath);
|
|
11099
11419
|
const changed = await writeFileIfChanged(absolutePath, content);
|
|
11100
11420
|
if (changed) {
|
|
11101
11421
|
changedPages.push(relativePath);
|
|
@@ -11158,29 +11478,29 @@ async function requiredCompileArtifactsExist(paths) {
|
|
|
11158
11478
|
paths.graphPath,
|
|
11159
11479
|
paths.codeIndexPath,
|
|
11160
11480
|
paths.searchDbPath,
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11481
|
+
path21.join(paths.wikiDir, "index.md"),
|
|
11482
|
+
path21.join(paths.wikiDir, "sources", "index.md"),
|
|
11483
|
+
path21.join(paths.wikiDir, "code", "index.md"),
|
|
11484
|
+
path21.join(paths.wikiDir, "concepts", "index.md"),
|
|
11485
|
+
path21.join(paths.wikiDir, "entities", "index.md"),
|
|
11486
|
+
path21.join(paths.wikiDir, "outputs", "index.md"),
|
|
11487
|
+
path21.join(paths.wikiDir, "projects", "index.md"),
|
|
11488
|
+
path21.join(paths.wikiDir, "candidates", "index.md")
|
|
11169
11489
|
];
|
|
11170
11490
|
const checks = await Promise.all(requiredPaths.map((filePath) => fileExists(filePath)));
|
|
11171
11491
|
return checks.every(Boolean);
|
|
11172
11492
|
}
|
|
11173
11493
|
async function loadAvailableCachedAnalyses(paths, manifests) {
|
|
11174
11494
|
const analyses = await Promise.all(
|
|
11175
|
-
manifests.map(async (manifest) => readJsonFile(
|
|
11495
|
+
manifests.map(async (manifest) => readJsonFile(path21.join(paths.analysesDir, `${manifest.sourceId}.json`)))
|
|
11176
11496
|
);
|
|
11177
11497
|
return analyses.filter((analysis) => Boolean(analysis));
|
|
11178
11498
|
}
|
|
11179
11499
|
function approvalManifestPath(paths, approvalId) {
|
|
11180
|
-
return
|
|
11500
|
+
return path21.join(paths.approvalsDir, approvalId, "manifest.json");
|
|
11181
11501
|
}
|
|
11182
11502
|
function approvalGraphPath(paths, approvalId) {
|
|
11183
|
-
return
|
|
11503
|
+
return path21.join(paths.approvalsDir, approvalId, "state", "graph.json");
|
|
11184
11504
|
}
|
|
11185
11505
|
async function readApprovalManifest(paths, approvalId) {
|
|
11186
11506
|
const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
|
|
@@ -11190,7 +11510,7 @@ async function readApprovalManifest(paths, approvalId) {
|
|
|
11190
11510
|
return manifest;
|
|
11191
11511
|
}
|
|
11192
11512
|
async function writeApprovalManifest(paths, manifest) {
|
|
11193
|
-
await
|
|
11513
|
+
await fs17.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
|
|
11194
11514
|
`, "utf8");
|
|
11195
11515
|
}
|
|
11196
11516
|
async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
|
|
@@ -11205,7 +11525,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
11205
11525
|
continue;
|
|
11206
11526
|
}
|
|
11207
11527
|
const previousPage = previousPagesById.get(nextPage.id);
|
|
11208
|
-
const currentExists = await fileExists(
|
|
11528
|
+
const currentExists = await fileExists(path21.join(paths.wikiDir, file.relativePath));
|
|
11209
11529
|
if (previousPage && previousPage.path !== nextPage.path) {
|
|
11210
11530
|
entries.push({
|
|
11211
11531
|
pageId: nextPage.id,
|
|
@@ -11238,7 +11558,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
11238
11558
|
const previousPage = previousPagesByPath.get(deletedPath);
|
|
11239
11559
|
entries.push({
|
|
11240
11560
|
pageId: previousPage?.id ?? `page:${slugify(deletedPath)}`,
|
|
11241
|
-
title: previousPage?.title ??
|
|
11561
|
+
title: previousPage?.title ?? path21.basename(deletedPath, ".md"),
|
|
11242
11562
|
kind: previousPage?.kind ?? "index",
|
|
11243
11563
|
changeType: "delete",
|
|
11244
11564
|
status: "pending",
|
|
@@ -11250,16 +11570,16 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
11250
11570
|
}
|
|
11251
11571
|
async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGraph, graph) {
|
|
11252
11572
|
const approvalId = `compile-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
11253
|
-
const approvalDir =
|
|
11573
|
+
const approvalDir = path21.join(paths.approvalsDir, approvalId);
|
|
11254
11574
|
await ensureDir(approvalDir);
|
|
11255
|
-
await ensureDir(
|
|
11256
|
-
await ensureDir(
|
|
11575
|
+
await ensureDir(path21.join(approvalDir, "wiki"));
|
|
11576
|
+
await ensureDir(path21.join(approvalDir, "state"));
|
|
11257
11577
|
for (const file of changedFiles) {
|
|
11258
|
-
const targetPath =
|
|
11259
|
-
await ensureDir(
|
|
11260
|
-
await
|
|
11578
|
+
const targetPath = path21.join(approvalDir, "wiki", file.relativePath);
|
|
11579
|
+
await ensureDir(path21.dirname(targetPath));
|
|
11580
|
+
await fs17.writeFile(targetPath, file.content, "utf8");
|
|
11261
11581
|
}
|
|
11262
|
-
await
|
|
11582
|
+
await fs17.writeFile(path21.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
|
|
11263
11583
|
await writeApprovalManifest(paths, {
|
|
11264
11584
|
approvalId,
|
|
11265
11585
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11321,7 +11641,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11321
11641
|
confidence: 1
|
|
11322
11642
|
});
|
|
11323
11643
|
const sourceRecord = await buildManagedGraphPage(
|
|
11324
|
-
|
|
11644
|
+
path21.join(paths.wikiDir, preview.path),
|
|
11325
11645
|
{
|
|
11326
11646
|
managedBy: "system",
|
|
11327
11647
|
confidence: 1,
|
|
@@ -11367,7 +11687,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11367
11687
|
);
|
|
11368
11688
|
records.push(
|
|
11369
11689
|
await buildManagedGraphPage(
|
|
11370
|
-
|
|
11690
|
+
path21.join(paths.wikiDir, modulePreview.path),
|
|
11371
11691
|
{
|
|
11372
11692
|
managedBy: "system",
|
|
11373
11693
|
confidence: 1,
|
|
@@ -11401,8 +11721,8 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11401
11721
|
const relativePath = promoted ? activeAggregatePath(itemKind, slug) : candidatePagePathFor(itemKind, slug);
|
|
11402
11722
|
const aggregateSourceClass2 = aggregateManifestSourceClass(input.manifests, sourceIds);
|
|
11403
11723
|
const fallbackPaths = [
|
|
11404
|
-
|
|
11405
|
-
|
|
11724
|
+
path21.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
|
|
11725
|
+
path21.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
|
|
11406
11726
|
];
|
|
11407
11727
|
const confidence = nodeConfidence(aggregate.sourceAnalyses.length);
|
|
11408
11728
|
const preview = emptyGraphPage({
|
|
@@ -11420,7 +11740,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11420
11740
|
status: promoted ? "active" : "candidate"
|
|
11421
11741
|
});
|
|
11422
11742
|
const pageRecord = await buildManagedGraphPage(
|
|
11423
|
-
|
|
11743
|
+
path21.join(paths.wikiDir, relativePath),
|
|
11424
11744
|
{
|
|
11425
11745
|
status: promoted ? "active" : "candidate",
|
|
11426
11746
|
managedBy: "system",
|
|
@@ -11515,7 +11835,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11515
11835
|
confidence: 1
|
|
11516
11836
|
}),
|
|
11517
11837
|
content: await buildManagedContent(
|
|
11518
|
-
|
|
11838
|
+
path21.join(paths.wikiDir, "projects", "index.md"),
|
|
11519
11839
|
{
|
|
11520
11840
|
managedBy: "system",
|
|
11521
11841
|
compiledFrom: indexCompiledFrom(projectIndexRefs)
|
|
@@ -11539,7 +11859,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11539
11859
|
records.push({
|
|
11540
11860
|
page: projectIndexRef,
|
|
11541
11861
|
content: await buildManagedContent(
|
|
11542
|
-
|
|
11862
|
+
path21.join(paths.wikiDir, projectIndexRef.path),
|
|
11543
11863
|
{
|
|
11544
11864
|
managedBy: "system",
|
|
11545
11865
|
compiledFrom: indexCompiledFrom(Object.values(sections).flat())
|
|
@@ -11567,7 +11887,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11567
11887
|
confidence: 1
|
|
11568
11888
|
}),
|
|
11569
11889
|
content: await buildManagedContent(
|
|
11570
|
-
|
|
11890
|
+
path21.join(paths.wikiDir, "index.md"),
|
|
11571
11891
|
{
|
|
11572
11892
|
managedBy: "system",
|
|
11573
11893
|
compiledFrom: indexCompiledFrom(allPages)
|
|
@@ -11598,7 +11918,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11598
11918
|
confidence: 1
|
|
11599
11919
|
}),
|
|
11600
11920
|
content: await buildManagedContent(
|
|
11601
|
-
|
|
11921
|
+
path21.join(paths.wikiDir, relativePath),
|
|
11602
11922
|
{
|
|
11603
11923
|
managedBy: "system",
|
|
11604
11924
|
compiledFrom: indexCompiledFrom(pages)
|
|
@@ -11609,12 +11929,12 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11609
11929
|
}
|
|
11610
11930
|
const nextPagePaths = new Set(records.map((record) => record.page.path));
|
|
11611
11931
|
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(
|
|
11932
|
+
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
11933
|
const obsoletePaths = uniqueStrings3([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
|
|
11614
11934
|
const changedFiles = [];
|
|
11615
11935
|
for (const record of records) {
|
|
11616
|
-
const absolutePath =
|
|
11617
|
-
const current = await fileExists(absolutePath) ? await
|
|
11936
|
+
const absolutePath = path21.join(paths.wikiDir, record.page.path);
|
|
11937
|
+
const current = await fileExists(absolutePath) ? await fs17.readFile(absolutePath, "utf8") : null;
|
|
11618
11938
|
if (current !== record.content) {
|
|
11619
11939
|
changedPages.push(record.page.path);
|
|
11620
11940
|
changedFiles.push({ relativePath: record.page.path, content: record.content });
|
|
@@ -11639,10 +11959,10 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11639
11959
|
await writePage(paths.wikiDir, record.page.path, record.content, writeChanges);
|
|
11640
11960
|
}
|
|
11641
11961
|
for (const relativePath of obsoletePaths) {
|
|
11642
|
-
await
|
|
11962
|
+
await fs17.rm(path21.join(paths.wikiDir, relativePath), { force: true });
|
|
11643
11963
|
}
|
|
11644
11964
|
await writeJsonFile(paths.graphPath, graph);
|
|
11645
|
-
await writeJsonFile(
|
|
11965
|
+
await writeJsonFile(path21.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
|
|
11646
11966
|
await writeJsonFile(paths.codeIndexPath, input.codeIndex);
|
|
11647
11967
|
await writeJsonFile(paths.compileStatePath, {
|
|
11648
11968
|
generatedAt: graph.generatedAt,
|
|
@@ -11713,17 +12033,17 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11713
12033
|
})
|
|
11714
12034
|
);
|
|
11715
12035
|
await Promise.all([
|
|
11716
|
-
ensureDir(
|
|
11717
|
-
ensureDir(
|
|
11718
|
-
ensureDir(
|
|
11719
|
-
ensureDir(
|
|
11720
|
-
ensureDir(
|
|
11721
|
-
ensureDir(
|
|
11722
|
-
ensureDir(
|
|
11723
|
-
ensureDir(
|
|
11724
|
-
ensureDir(
|
|
12036
|
+
ensureDir(path21.join(paths.wikiDir, "sources")),
|
|
12037
|
+
ensureDir(path21.join(paths.wikiDir, "code")),
|
|
12038
|
+
ensureDir(path21.join(paths.wikiDir, "concepts")),
|
|
12039
|
+
ensureDir(path21.join(paths.wikiDir, "entities")),
|
|
12040
|
+
ensureDir(path21.join(paths.wikiDir, "outputs")),
|
|
12041
|
+
ensureDir(path21.join(paths.wikiDir, "graph")),
|
|
12042
|
+
ensureDir(path21.join(paths.wikiDir, "graph", "communities")),
|
|
12043
|
+
ensureDir(path21.join(paths.wikiDir, "projects")),
|
|
12044
|
+
ensureDir(path21.join(paths.wikiDir, "candidates"))
|
|
11725
12045
|
]);
|
|
11726
|
-
const projectsIndexPath =
|
|
12046
|
+
const projectsIndexPath = path21.join(paths.wikiDir, "projects", "index.md");
|
|
11727
12047
|
await writeFileIfChanged(
|
|
11728
12048
|
projectsIndexPath,
|
|
11729
12049
|
await buildManagedContent(
|
|
@@ -11744,7 +12064,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11744
12064
|
outputs: pages.filter((page) => page.kind === "output" && page.projectIds.includes(project.id)),
|
|
11745
12065
|
candidates: pages.filter((page) => page.status === "candidate" && page.projectIds.includes(project.id))
|
|
11746
12066
|
};
|
|
11747
|
-
const absolutePath =
|
|
12067
|
+
const absolutePath = path21.join(paths.wikiDir, "projects", project.id, "index.md");
|
|
11748
12068
|
await writeFileIfChanged(
|
|
11749
12069
|
absolutePath,
|
|
11750
12070
|
await buildManagedContent(
|
|
@@ -11762,7 +12082,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11762
12082
|
)
|
|
11763
12083
|
);
|
|
11764
12084
|
}
|
|
11765
|
-
const rootIndexPath =
|
|
12085
|
+
const rootIndexPath = path21.join(paths.wikiDir, "index.md");
|
|
11766
12086
|
await writeFileIfChanged(
|
|
11767
12087
|
rootIndexPath,
|
|
11768
12088
|
await buildManagedContent(
|
|
@@ -11783,7 +12103,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11783
12103
|
["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
|
|
11784
12104
|
["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
|
|
11785
12105
|
]) {
|
|
11786
|
-
const absolutePath =
|
|
12106
|
+
const absolutePath = path21.join(paths.wikiDir, relativePath);
|
|
11787
12107
|
await writeFileIfChanged(
|
|
11788
12108
|
absolutePath,
|
|
11789
12109
|
await buildManagedContent(
|
|
@@ -11797,23 +12117,23 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11797
12117
|
);
|
|
11798
12118
|
}
|
|
11799
12119
|
for (const record of graphOrientation.records) {
|
|
11800
|
-
await writeFileIfChanged(
|
|
12120
|
+
await writeFileIfChanged(path21.join(paths.wikiDir, record.page.path), record.content);
|
|
11801
12121
|
}
|
|
11802
12122
|
if (graphOrientation.report) {
|
|
11803
|
-
await writeJsonFile(
|
|
12123
|
+
await writeJsonFile(path21.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
|
|
11804
12124
|
}
|
|
11805
|
-
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(
|
|
12125
|
+
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path21.relative(paths.wikiDir, absolutePath)));
|
|
11806
12126
|
const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
|
|
11807
12127
|
"projects/index.md",
|
|
11808
12128
|
...configuredProjects.map((project) => `projects/${project.id}/index.md`)
|
|
11809
12129
|
]);
|
|
11810
12130
|
await Promise.all(
|
|
11811
|
-
existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) =>
|
|
12131
|
+
existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs17.rm(path21.join(paths.wikiDir, relativePath), { force: true }))
|
|
11812
12132
|
);
|
|
11813
|
-
const existingGraphPages = (await listFilesRecursive(
|
|
12133
|
+
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
12134
|
const allowedGraphPages = /* @__PURE__ */ new Set(["graph/index.md", ...graphOrientation.records.map((record) => record.page.path)]);
|
|
11815
12135
|
await Promise.all(
|
|
11816
|
-
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) =>
|
|
12136
|
+
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs17.rm(path21.join(paths.wikiDir, relativePath), { force: true }))
|
|
11817
12137
|
);
|
|
11818
12138
|
await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
|
|
11819
12139
|
}
|
|
@@ -11833,7 +12153,7 @@ async function prepareOutputPageSave(rootDir, input) {
|
|
|
11833
12153
|
confidence: 0.74
|
|
11834
12154
|
}
|
|
11835
12155
|
});
|
|
11836
|
-
const absolutePath =
|
|
12156
|
+
const absolutePath = path21.join(paths.wikiDir, output.page.path);
|
|
11837
12157
|
return {
|
|
11838
12158
|
page: output.page,
|
|
11839
12159
|
savedPath: absolutePath,
|
|
@@ -11845,15 +12165,15 @@ async function prepareOutputPageSave(rootDir, input) {
|
|
|
11845
12165
|
async function persistOutputPage(rootDir, input) {
|
|
11846
12166
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11847
12167
|
const prepared = await prepareOutputPageSave(rootDir, input);
|
|
11848
|
-
await ensureDir(
|
|
11849
|
-
await
|
|
12168
|
+
await ensureDir(path21.dirname(prepared.savedPath));
|
|
12169
|
+
await fs17.writeFile(prepared.savedPath, prepared.content, "utf8");
|
|
11850
12170
|
for (const assetFile of prepared.assetFiles) {
|
|
11851
|
-
const assetPath =
|
|
11852
|
-
await ensureDir(
|
|
12171
|
+
const assetPath = path21.join(paths.wikiDir, assetFile.relativePath);
|
|
12172
|
+
await ensureDir(path21.dirname(assetPath));
|
|
11853
12173
|
if (typeof assetFile.content === "string") {
|
|
11854
|
-
await
|
|
12174
|
+
await fs17.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
|
|
11855
12175
|
} else {
|
|
11856
|
-
await
|
|
12176
|
+
await fs17.writeFile(assetPath, assetFile.content);
|
|
11857
12177
|
}
|
|
11858
12178
|
}
|
|
11859
12179
|
return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
|
|
@@ -11874,7 +12194,7 @@ async function prepareExploreHubSave(rootDir, input) {
|
|
|
11874
12194
|
confidence: 0.76
|
|
11875
12195
|
}
|
|
11876
12196
|
});
|
|
11877
|
-
const absolutePath =
|
|
12197
|
+
const absolutePath = path21.join(paths.wikiDir, hub.page.path);
|
|
11878
12198
|
return {
|
|
11879
12199
|
page: hub.page,
|
|
11880
12200
|
savedPath: absolutePath,
|
|
@@ -11886,15 +12206,15 @@ async function prepareExploreHubSave(rootDir, input) {
|
|
|
11886
12206
|
async function persistExploreHub(rootDir, input) {
|
|
11887
12207
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11888
12208
|
const prepared = await prepareExploreHubSave(rootDir, input);
|
|
11889
|
-
await ensureDir(
|
|
11890
|
-
await
|
|
12209
|
+
await ensureDir(path21.dirname(prepared.savedPath));
|
|
12210
|
+
await fs17.writeFile(prepared.savedPath, prepared.content, "utf8");
|
|
11891
12211
|
for (const assetFile of prepared.assetFiles) {
|
|
11892
|
-
const assetPath =
|
|
11893
|
-
await ensureDir(
|
|
12212
|
+
const assetPath = path21.join(paths.wikiDir, assetFile.relativePath);
|
|
12213
|
+
await ensureDir(path21.dirname(assetPath));
|
|
11894
12214
|
if (typeof assetFile.content === "string") {
|
|
11895
|
-
await
|
|
12215
|
+
await fs17.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
|
|
11896
12216
|
} else {
|
|
11897
|
-
await
|
|
12217
|
+
await fs17.writeFile(assetPath, assetFile.content);
|
|
11898
12218
|
}
|
|
11899
12219
|
}
|
|
11900
12220
|
return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
|
|
@@ -11911,17 +12231,17 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
11911
12231
|
}))
|
|
11912
12232
|
]);
|
|
11913
12233
|
const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
11914
|
-
const approvalDir =
|
|
12234
|
+
const approvalDir = path21.join(paths.approvalsDir, approvalId);
|
|
11915
12235
|
await ensureDir(approvalDir);
|
|
11916
|
-
await ensureDir(
|
|
11917
|
-
await ensureDir(
|
|
12236
|
+
await ensureDir(path21.join(approvalDir, "wiki"));
|
|
12237
|
+
await ensureDir(path21.join(approvalDir, "state"));
|
|
11918
12238
|
for (const file of changedFiles) {
|
|
11919
|
-
const targetPath =
|
|
11920
|
-
await ensureDir(
|
|
12239
|
+
const targetPath = path21.join(approvalDir, "wiki", file.relativePath);
|
|
12240
|
+
await ensureDir(path21.dirname(targetPath));
|
|
11921
12241
|
if ("binary" in file && file.binary) {
|
|
11922
|
-
await
|
|
12242
|
+
await fs17.writeFile(targetPath, Buffer.from(file.content, "base64"));
|
|
11923
12243
|
} else {
|
|
11924
|
-
await
|
|
12244
|
+
await fs17.writeFile(targetPath, file.content, "utf8");
|
|
11925
12245
|
}
|
|
11926
12246
|
}
|
|
11927
12247
|
const nextPages = sortGraphPages([
|
|
@@ -11936,7 +12256,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
11936
12256
|
sources: previousGraph?.sources ?? [],
|
|
11937
12257
|
pages: nextPages
|
|
11938
12258
|
};
|
|
11939
|
-
await
|
|
12259
|
+
await fs17.writeFile(path21.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
|
|
11940
12260
|
await writeApprovalManifest(paths, {
|
|
11941
12261
|
approvalId,
|
|
11942
12262
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11965,9 +12285,9 @@ async function executeQuery(rootDir, question, format) {
|
|
|
11965
12285
|
const searchResults = searchPages(paths.searchDbPath, question, 5);
|
|
11966
12286
|
const excerpts = await Promise.all(
|
|
11967
12287
|
searchResults.map(async (result) => {
|
|
11968
|
-
const absolutePath =
|
|
12288
|
+
const absolutePath = path21.join(paths.wikiDir, result.path);
|
|
11969
12289
|
try {
|
|
11970
|
-
const content = await
|
|
12290
|
+
const content = await fs17.readFile(absolutePath, "utf8");
|
|
11971
12291
|
const parsed = matter9(content);
|
|
11972
12292
|
return `# ${result.title}
|
|
11973
12293
|
${truncate(normalizeWhitespace(parsed.content), 1200)}`;
|
|
@@ -12149,7 +12469,7 @@ function sortGraphPages(pages) {
|
|
|
12149
12469
|
async function listApprovals(rootDir) {
|
|
12150
12470
|
const { paths } = await loadVaultConfig(rootDir);
|
|
12151
12471
|
const manifests = await Promise.all(
|
|
12152
|
-
(await
|
|
12472
|
+
(await fs17.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
12153
12473
|
try {
|
|
12154
12474
|
return await readApprovalManifest(paths, entry.name);
|
|
12155
12475
|
} catch {
|
|
@@ -12165,8 +12485,8 @@ async function readApproval(rootDir, approvalId) {
|
|
|
12165
12485
|
const details = await Promise.all(
|
|
12166
12486
|
manifest.entries.map(async (entry) => {
|
|
12167
12487
|
const currentPath = entry.previousPath ?? entry.nextPath;
|
|
12168
|
-
const currentContent = currentPath ? await
|
|
12169
|
-
const stagedContent = entry.nextPath ? await
|
|
12488
|
+
const currentContent = currentPath ? await fs17.readFile(path21.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
|
|
12489
|
+
const stagedContent = entry.nextPath ? await fs17.readFile(path21.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
|
|
12170
12490
|
return {
|
|
12171
12491
|
...entry,
|
|
12172
12492
|
currentContent,
|
|
@@ -12194,26 +12514,26 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
|
|
|
12194
12514
|
if (!entry.nextPath) {
|
|
12195
12515
|
throw new Error(`Approval entry ${entry.pageId} is missing a staged path.`);
|
|
12196
12516
|
}
|
|
12197
|
-
const stagedAbsolutePath =
|
|
12198
|
-
const stagedContent = await
|
|
12199
|
-
const targetAbsolutePath =
|
|
12200
|
-
await ensureDir(
|
|
12201
|
-
await
|
|
12517
|
+
const stagedAbsolutePath = path21.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
|
|
12518
|
+
const stagedContent = await fs17.readFile(stagedAbsolutePath, "utf8");
|
|
12519
|
+
const targetAbsolutePath = path21.join(paths.wikiDir, entry.nextPath);
|
|
12520
|
+
await ensureDir(path21.dirname(targetAbsolutePath));
|
|
12521
|
+
await fs17.writeFile(targetAbsolutePath, stagedContent, "utf8");
|
|
12202
12522
|
if (entry.changeType === "promote" && entry.previousPath) {
|
|
12203
|
-
await
|
|
12523
|
+
await fs17.rm(path21.join(paths.wikiDir, entry.previousPath), { force: true });
|
|
12204
12524
|
}
|
|
12205
12525
|
const nextPage = bundleGraph?.pages.find((page) => page.id === entry.pageId && page.path === entry.nextPath) ?? parseStoredPage(entry.nextPath, stagedContent);
|
|
12206
12526
|
if (nextPage.kind === "output" && nextPage.outputAssets?.length) {
|
|
12207
|
-
const outputAssetDir =
|
|
12208
|
-
await
|
|
12527
|
+
const outputAssetDir = path21.join(paths.wikiDir, "outputs", "assets", path21.basename(nextPage.path, ".md"));
|
|
12528
|
+
await fs17.rm(outputAssetDir, { recursive: true, force: true });
|
|
12209
12529
|
for (const asset of nextPage.outputAssets) {
|
|
12210
|
-
const stagedAssetPath =
|
|
12530
|
+
const stagedAssetPath = path21.join(paths.approvalsDir, approvalId, "wiki", asset.path);
|
|
12211
12531
|
if (!await fileExists(stagedAssetPath)) {
|
|
12212
12532
|
continue;
|
|
12213
12533
|
}
|
|
12214
|
-
const targetAssetPath =
|
|
12215
|
-
await ensureDir(
|
|
12216
|
-
await
|
|
12534
|
+
const targetAssetPath = path21.join(paths.wikiDir, asset.path);
|
|
12535
|
+
await ensureDir(path21.dirname(targetAssetPath));
|
|
12536
|
+
await fs17.copyFile(stagedAssetPath, targetAssetPath);
|
|
12217
12537
|
}
|
|
12218
12538
|
}
|
|
12219
12539
|
nextPages = nextPages.filter(
|
|
@@ -12224,10 +12544,10 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
|
|
|
12224
12544
|
} else {
|
|
12225
12545
|
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
12546
|
if (entry.previousPath) {
|
|
12227
|
-
await
|
|
12547
|
+
await fs17.rm(path21.join(paths.wikiDir, entry.previousPath), { force: true });
|
|
12228
12548
|
}
|
|
12229
12549
|
if (deletedPage?.kind === "output") {
|
|
12230
|
-
await
|
|
12550
|
+
await fs17.rm(path21.join(paths.wikiDir, "outputs", "assets", path21.basename(deletedPage.path, ".md")), {
|
|
12231
12551
|
recursive: true,
|
|
12232
12552
|
force: true
|
|
12233
12553
|
});
|
|
@@ -12318,7 +12638,7 @@ async function promoteCandidate(rootDir, target) {
|
|
|
12318
12638
|
const { paths } = await loadVaultConfig(rootDir);
|
|
12319
12639
|
const graph = await readJsonFile(paths.graphPath);
|
|
12320
12640
|
const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
|
|
12321
|
-
const raw = await
|
|
12641
|
+
const raw = await fs17.readFile(path21.join(paths.wikiDir, candidate.path), "utf8");
|
|
12322
12642
|
const parsed = matter9(raw);
|
|
12323
12643
|
const nextUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12324
12644
|
const nextContent = matter9.stringify(parsed.content, {
|
|
@@ -12330,10 +12650,10 @@ async function promoteCandidate(rootDir, target) {
|
|
|
12330
12650
|
)
|
|
12331
12651
|
});
|
|
12332
12652
|
const nextPath = candidateActivePath(candidate);
|
|
12333
|
-
const nextAbsolutePath =
|
|
12334
|
-
await ensureDir(
|
|
12335
|
-
await
|
|
12336
|
-
await
|
|
12653
|
+
const nextAbsolutePath = path21.join(paths.wikiDir, nextPath);
|
|
12654
|
+
await ensureDir(path21.dirname(nextAbsolutePath));
|
|
12655
|
+
await fs17.writeFile(nextAbsolutePath, nextContent, "utf8");
|
|
12656
|
+
await fs17.rm(path21.join(paths.wikiDir, candidate.path), { force: true });
|
|
12337
12657
|
const nextPage = parseStoredPage(nextPath, nextContent, { createdAt: candidate.createdAt, updatedAt: nextUpdatedAt });
|
|
12338
12658
|
const nextPages = sortGraphPages(
|
|
12339
12659
|
(graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path).concat(nextPage)
|
|
@@ -12378,7 +12698,7 @@ async function archiveCandidate(rootDir, target) {
|
|
|
12378
12698
|
const { paths } = await loadVaultConfig(rootDir);
|
|
12379
12699
|
const graph = await readJsonFile(paths.graphPath);
|
|
12380
12700
|
const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
|
|
12381
|
-
await
|
|
12701
|
+
await fs17.rm(path21.join(paths.wikiDir, candidate.path), { force: true });
|
|
12382
12702
|
const nextPages = sortGraphPages((graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path));
|
|
12383
12703
|
const nextGraph = {
|
|
12384
12704
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12417,18 +12737,18 @@ async function archiveCandidate(rootDir, target) {
|
|
|
12417
12737
|
}
|
|
12418
12738
|
async function ensureObsidianWorkspace(rootDir) {
|
|
12419
12739
|
const { config } = await loadVaultConfig(rootDir);
|
|
12420
|
-
const obsidianDir =
|
|
12740
|
+
const obsidianDir = path21.join(rootDir, ".obsidian");
|
|
12421
12741
|
const projectIds = projectEntries(config).map((project) => project.id);
|
|
12422
12742
|
await ensureDir(obsidianDir);
|
|
12423
12743
|
await Promise.all([
|
|
12424
|
-
writeJsonFile(
|
|
12744
|
+
writeJsonFile(path21.join(obsidianDir, "app.json"), {
|
|
12425
12745
|
alwaysUpdateLinks: true,
|
|
12426
12746
|
newFileLocation: "folder",
|
|
12427
12747
|
newFileFolderPath: "wiki/insights",
|
|
12428
12748
|
useMarkdownLinks: false,
|
|
12429
12749
|
attachmentFolderPath: "raw/assets"
|
|
12430
12750
|
}),
|
|
12431
|
-
writeJsonFile(
|
|
12751
|
+
writeJsonFile(path21.join(obsidianDir, "core-plugins.json"), [
|
|
12432
12752
|
"file-explorer",
|
|
12433
12753
|
"global-search",
|
|
12434
12754
|
"switcher",
|
|
@@ -12438,7 +12758,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
12438
12758
|
"tag-pane",
|
|
12439
12759
|
"page-preview"
|
|
12440
12760
|
]),
|
|
12441
|
-
writeJsonFile(
|
|
12761
|
+
writeJsonFile(path21.join(obsidianDir, "graph.json"), {
|
|
12442
12762
|
"collapse-filter": false,
|
|
12443
12763
|
search: "",
|
|
12444
12764
|
showTags: true,
|
|
@@ -12450,7 +12770,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
12450
12770
|
})),
|
|
12451
12771
|
localJumps: false
|
|
12452
12772
|
}),
|
|
12453
|
-
writeJsonFile(
|
|
12773
|
+
writeJsonFile(path21.join(obsidianDir, "workspace.json"), {
|
|
12454
12774
|
active: "root",
|
|
12455
12775
|
lastOpenFiles: ["wiki/index.md", "wiki/projects/index.md", "wiki/candidates/index.md", "wiki/insights/index.md"],
|
|
12456
12776
|
left: {
|
|
@@ -12465,7 +12785,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
12465
12785
|
async function initVault(rootDir, options = {}) {
|
|
12466
12786
|
const { paths } = await initWorkspace(rootDir);
|
|
12467
12787
|
await installConfiguredAgents(rootDir);
|
|
12468
|
-
const insightsIndexPath =
|
|
12788
|
+
const insightsIndexPath = path21.join(paths.wikiDir, "insights", "index.md");
|
|
12469
12789
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12470
12790
|
await writeFileIfChanged(
|
|
12471
12791
|
insightsIndexPath,
|
|
@@ -12501,7 +12821,7 @@ async function initVault(rootDir, options = {}) {
|
|
|
12501
12821
|
)
|
|
12502
12822
|
);
|
|
12503
12823
|
await writeFileIfChanged(
|
|
12504
|
-
|
|
12824
|
+
path21.join(paths.wikiDir, "projects", "index.md"),
|
|
12505
12825
|
matter9.stringify(["# Projects", "", "- Run `swarmvault compile` to build project rollups.", ""].join("\n"), {
|
|
12506
12826
|
page_id: "projects:index",
|
|
12507
12827
|
kind: "index",
|
|
@@ -12523,7 +12843,7 @@ async function initVault(rootDir, options = {}) {
|
|
|
12523
12843
|
})
|
|
12524
12844
|
);
|
|
12525
12845
|
await writeFileIfChanged(
|
|
12526
|
-
|
|
12846
|
+
path21.join(paths.wikiDir, "candidates", "index.md"),
|
|
12527
12847
|
matter9.stringify(["# Candidates", "", "- Run `swarmvault compile` to stage candidate pages.", ""].join("\n"), {
|
|
12528
12848
|
page_id: "candidates:index",
|
|
12529
12849
|
kind: "index",
|
|
@@ -12659,7 +12979,7 @@ async function compileVault(rootDir, options = {}) {
|
|
|
12659
12979
|
),
|
|
12660
12980
|
Promise.all(
|
|
12661
12981
|
clean.map(async (manifest) => {
|
|
12662
|
-
const cached = await readJsonFile(
|
|
12982
|
+
const cached = await readJsonFile(path21.join(paths.analysesDir, `${manifest.sourceId}.json`));
|
|
12663
12983
|
if (cached) {
|
|
12664
12984
|
return cached;
|
|
12665
12985
|
}
|
|
@@ -12683,22 +13003,22 @@ async function compileVault(rootDir, options = {}) {
|
|
|
12683
13003
|
}
|
|
12684
13004
|
const enriched = enrichResolvedCodeImports(manifest, analysis, codeIndex);
|
|
12685
13005
|
if (analysisSignature(enriched) !== analysisSignature(analysis)) {
|
|
12686
|
-
await writeJsonFile(
|
|
13006
|
+
await writeJsonFile(path21.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
|
|
12687
13007
|
}
|
|
12688
13008
|
return enriched;
|
|
12689
13009
|
})
|
|
12690
13010
|
);
|
|
12691
13011
|
await Promise.all([
|
|
12692
|
-
ensureDir(
|
|
12693
|
-
ensureDir(
|
|
12694
|
-
ensureDir(
|
|
12695
|
-
ensureDir(
|
|
12696
|
-
ensureDir(
|
|
12697
|
-
ensureDir(
|
|
12698
|
-
ensureDir(
|
|
12699
|
-
ensureDir(
|
|
12700
|
-
ensureDir(
|
|
12701
|
-
ensureDir(
|
|
13012
|
+
ensureDir(path21.join(paths.wikiDir, "sources")),
|
|
13013
|
+
ensureDir(path21.join(paths.wikiDir, "code")),
|
|
13014
|
+
ensureDir(path21.join(paths.wikiDir, "concepts")),
|
|
13015
|
+
ensureDir(path21.join(paths.wikiDir, "entities")),
|
|
13016
|
+
ensureDir(path21.join(paths.wikiDir, "outputs")),
|
|
13017
|
+
ensureDir(path21.join(paths.wikiDir, "projects")),
|
|
13018
|
+
ensureDir(path21.join(paths.wikiDir, "insights")),
|
|
13019
|
+
ensureDir(path21.join(paths.wikiDir, "candidates")),
|
|
13020
|
+
ensureDir(path21.join(paths.wikiDir, "candidates", "concepts")),
|
|
13021
|
+
ensureDir(path21.join(paths.wikiDir, "candidates", "entities"))
|
|
12702
13022
|
]);
|
|
12703
13023
|
const sync = await syncVaultArtifacts(rootDir, {
|
|
12704
13024
|
schemas,
|
|
@@ -12845,7 +13165,7 @@ async function queryVault(rootDir, options) {
|
|
|
12845
13165
|
assetFiles: staged.assetFiles
|
|
12846
13166
|
}
|
|
12847
13167
|
]);
|
|
12848
|
-
stagedPath =
|
|
13168
|
+
stagedPath = path21.join(approval.approvalDir, "wiki", staged.page.path);
|
|
12849
13169
|
savedPageId = staged.page.id;
|
|
12850
13170
|
approvalId = approval.approvalId;
|
|
12851
13171
|
approvalDir = approval.approvalDir;
|
|
@@ -13101,9 +13421,9 @@ ${orchestrationNotes.join("\n")}
|
|
|
13101
13421
|
approvalId = approval.approvalId;
|
|
13102
13422
|
approvalDir = approval.approvalDir;
|
|
13103
13423
|
stepResults.forEach((result, index) => {
|
|
13104
|
-
result.stagedPath =
|
|
13424
|
+
result.stagedPath = path21.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
|
|
13105
13425
|
});
|
|
13106
|
-
stagedHubPath =
|
|
13426
|
+
stagedHubPath = path21.join(approval.approvalDir, "wiki", hubPage.path);
|
|
13107
13427
|
} else {
|
|
13108
13428
|
await refreshVaultAfterOutputSave(rootDir);
|
|
13109
13429
|
}
|
|
@@ -13190,11 +13510,11 @@ async function benchmarkVault(rootDir, options = {}) {
|
|
|
13190
13510
|
}
|
|
13191
13511
|
}
|
|
13192
13512
|
for (const page of graph.pages) {
|
|
13193
|
-
const absolutePath =
|
|
13513
|
+
const absolutePath = path21.join(paths.wikiDir, page.path);
|
|
13194
13514
|
if (!await fileExists(absolutePath)) {
|
|
13195
13515
|
continue;
|
|
13196
13516
|
}
|
|
13197
|
-
const parsed = matter9(await
|
|
13517
|
+
const parsed = matter9(await fs17.readFile(absolutePath, "utf8"));
|
|
13198
13518
|
pageContentsById.set(page.id, parsed.content);
|
|
13199
13519
|
}
|
|
13200
13520
|
const configuredQuestions = (config.benchmark?.questions ?? []).map((question) => normalizeWhitespace(question)).filter(Boolean);
|
|
@@ -13239,7 +13559,7 @@ async function listGraphHyperedges(rootDir, target, limit = 25) {
|
|
|
13239
13559
|
}
|
|
13240
13560
|
async function readGraphReport(rootDir) {
|
|
13241
13561
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13242
|
-
return readJsonFile(
|
|
13562
|
+
return readJsonFile(path21.join(paths.wikiDir, "graph", "report.json"));
|
|
13243
13563
|
}
|
|
13244
13564
|
async function listGodNodes(rootDir, limit = 10) {
|
|
13245
13565
|
const graph = await ensureCompiledGraph(rootDir);
|
|
@@ -13252,15 +13572,15 @@ async function listPages(rootDir) {
|
|
|
13252
13572
|
}
|
|
13253
13573
|
async function readPage(rootDir, relativePath) {
|
|
13254
13574
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13255
|
-
const absolutePath =
|
|
13575
|
+
const absolutePath = path21.resolve(paths.wikiDir, relativePath);
|
|
13256
13576
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
13257
13577
|
return null;
|
|
13258
13578
|
}
|
|
13259
|
-
const raw = await
|
|
13579
|
+
const raw = await fs17.readFile(absolutePath, "utf8");
|
|
13260
13580
|
const parsed = matter9(raw);
|
|
13261
13581
|
return {
|
|
13262
13582
|
path: relativePath,
|
|
13263
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
13583
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path21.basename(relativePath, path21.extname(relativePath)),
|
|
13264
13584
|
frontmatter: parsed.data,
|
|
13265
13585
|
content: parsed.content
|
|
13266
13586
|
};
|
|
@@ -13296,7 +13616,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
13296
13616
|
severity: "warning",
|
|
13297
13617
|
code: "stale_page",
|
|
13298
13618
|
message: `Page ${page.title} is stale because the vault schema changed.`,
|
|
13299
|
-
pagePath:
|
|
13619
|
+
pagePath: path21.join(paths.wikiDir, page.path),
|
|
13300
13620
|
relatedPageIds: [page.id]
|
|
13301
13621
|
});
|
|
13302
13622
|
}
|
|
@@ -13307,7 +13627,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
13307
13627
|
severity: "warning",
|
|
13308
13628
|
code: "stale_page",
|
|
13309
13629
|
message: `Page ${page.title} is stale because source ${sourceId} changed.`,
|
|
13310
|
-
pagePath:
|
|
13630
|
+
pagePath: path21.join(paths.wikiDir, page.path),
|
|
13311
13631
|
relatedSourceIds: [sourceId],
|
|
13312
13632
|
relatedPageIds: [page.id]
|
|
13313
13633
|
});
|
|
@@ -13318,13 +13638,13 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
13318
13638
|
severity: "info",
|
|
13319
13639
|
code: "orphan_page",
|
|
13320
13640
|
message: `Page ${page.title} has no backlinks.`,
|
|
13321
|
-
pagePath:
|
|
13641
|
+
pagePath: path21.join(paths.wikiDir, page.path),
|
|
13322
13642
|
relatedPageIds: [page.id]
|
|
13323
13643
|
});
|
|
13324
13644
|
}
|
|
13325
|
-
const absolutePath =
|
|
13645
|
+
const absolutePath = path21.join(paths.wikiDir, page.path);
|
|
13326
13646
|
if (await fileExists(absolutePath)) {
|
|
13327
|
-
const content = await
|
|
13647
|
+
const content = await fs17.readFile(absolutePath, "utf8");
|
|
13328
13648
|
if (content.includes("## Claims")) {
|
|
13329
13649
|
const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
|
|
13330
13650
|
if (uncited.length) {
|
|
@@ -13404,7 +13724,7 @@ async function bootstrapDemo(rootDir, input) {
|
|
|
13404
13724
|
}
|
|
13405
13725
|
|
|
13406
13726
|
// src/mcp.ts
|
|
13407
|
-
var SERVER_VERSION = "0.1.
|
|
13727
|
+
var SERVER_VERSION = "0.1.30";
|
|
13408
13728
|
async function createMcpServer(rootDir) {
|
|
13409
13729
|
const server = new McpServer({
|
|
13410
13730
|
name: "swarmvault",
|
|
@@ -13675,7 +13995,7 @@ async function createMcpServer(rootDir) {
|
|
|
13675
13995
|
},
|
|
13676
13996
|
async () => {
|
|
13677
13997
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13678
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
13998
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path22.relative(paths.sessionsDir, filePath))).sort();
|
|
13679
13999
|
return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
|
|
13680
14000
|
}
|
|
13681
14001
|
);
|
|
@@ -13708,8 +14028,8 @@ async function createMcpServer(rootDir) {
|
|
|
13708
14028
|
return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
|
|
13709
14029
|
}
|
|
13710
14030
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13711
|
-
const absolutePath =
|
|
13712
|
-
return asTextResource(`swarmvault://pages/${encodedPath}`, await
|
|
14031
|
+
const absolutePath = path22.resolve(paths.wikiDir, relativePath);
|
|
14032
|
+
return asTextResource(`swarmvault://pages/${encodedPath}`, await fs18.readFile(absolutePath, "utf8"));
|
|
13713
14033
|
}
|
|
13714
14034
|
);
|
|
13715
14035
|
server.registerResource(
|
|
@@ -13717,11 +14037,11 @@ async function createMcpServer(rootDir) {
|
|
|
13717
14037
|
new ResourceTemplate("swarmvault://sessions/{path}", {
|
|
13718
14038
|
list: async () => {
|
|
13719
14039
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13720
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
14040
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path22.relative(paths.sessionsDir, filePath))).sort();
|
|
13721
14041
|
return {
|
|
13722
14042
|
resources: files.map((relativePath) => ({
|
|
13723
14043
|
uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
|
|
13724
|
-
name:
|
|
14044
|
+
name: path22.basename(relativePath, ".md"),
|
|
13725
14045
|
title: relativePath,
|
|
13726
14046
|
description: "SwarmVault session artifact",
|
|
13727
14047
|
mimeType: "text/markdown"
|
|
@@ -13738,11 +14058,11 @@ async function createMcpServer(rootDir) {
|
|
|
13738
14058
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13739
14059
|
const encodedPath = typeof variables.path === "string" ? variables.path : "";
|
|
13740
14060
|
const relativePath = decodeURIComponent(encodedPath);
|
|
13741
|
-
const absolutePath =
|
|
14061
|
+
const absolutePath = path22.resolve(paths.sessionsDir, relativePath);
|
|
13742
14062
|
if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
|
|
13743
14063
|
return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
|
|
13744
14064
|
}
|
|
13745
|
-
return asTextResource(`swarmvault://sessions/${encodedPath}`, await
|
|
14065
|
+
return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs18.readFile(absolutePath, "utf8"));
|
|
13746
14066
|
}
|
|
13747
14067
|
);
|
|
13748
14068
|
return server;
|
|
@@ -13790,13 +14110,13 @@ function asTextResource(uri, text) {
|
|
|
13790
14110
|
}
|
|
13791
14111
|
|
|
13792
14112
|
// src/schedule.ts
|
|
13793
|
-
import
|
|
13794
|
-
import
|
|
14113
|
+
import fs19 from "fs/promises";
|
|
14114
|
+
import path23 from "path";
|
|
13795
14115
|
function scheduleStatePath(schedulesDir, jobId) {
|
|
13796
|
-
return
|
|
14116
|
+
return path23.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
13797
14117
|
}
|
|
13798
14118
|
function scheduleLockPath(schedulesDir, jobId) {
|
|
13799
|
-
return
|
|
14119
|
+
return path23.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
|
|
13800
14120
|
}
|
|
13801
14121
|
function parseEveryDuration(value) {
|
|
13802
14122
|
const match = value.trim().match(/^(\d+)(m|h|d)$/i);
|
|
@@ -13899,13 +14219,13 @@ async function acquireJobLease(rootDir, jobId) {
|
|
|
13899
14219
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13900
14220
|
const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
|
|
13901
14221
|
await ensureDir(paths.schedulesDir);
|
|
13902
|
-
const handle = await
|
|
14222
|
+
const handle = await fs19.open(leasePath, "wx");
|
|
13903
14223
|
await handle.writeFile(`${process.pid}
|
|
13904
14224
|
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
13905
14225
|
`);
|
|
13906
14226
|
await handle.close();
|
|
13907
14227
|
return async () => {
|
|
13908
|
-
await
|
|
14228
|
+
await fs19.rm(leasePath, { force: true });
|
|
13909
14229
|
};
|
|
13910
14230
|
}
|
|
13911
14231
|
async function listSchedules(rootDir) {
|
|
@@ -14053,15 +14373,15 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
|
|
|
14053
14373
|
|
|
14054
14374
|
// src/viewer.ts
|
|
14055
14375
|
import { execFile } from "child_process";
|
|
14056
|
-
import
|
|
14376
|
+
import fs20 from "fs/promises";
|
|
14057
14377
|
import http from "http";
|
|
14058
|
-
import
|
|
14378
|
+
import path25 from "path";
|
|
14059
14379
|
import { promisify } from "util";
|
|
14060
14380
|
import matter10 from "gray-matter";
|
|
14061
14381
|
import mime2 from "mime-types";
|
|
14062
14382
|
|
|
14063
14383
|
// src/watch.ts
|
|
14064
|
-
import
|
|
14384
|
+
import path24 from "path";
|
|
14065
14385
|
import process3 from "process";
|
|
14066
14386
|
import chokidar from "chokidar";
|
|
14067
14387
|
var MAX_BACKOFF_MS = 3e4;
|
|
@@ -14069,15 +14389,15 @@ var BACKOFF_THRESHOLD = 3;
|
|
|
14069
14389
|
var CRITICAL_THRESHOLD = 10;
|
|
14070
14390
|
var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
|
|
14071
14391
|
function withinRoot2(rootPath, targetPath) {
|
|
14072
|
-
const relative =
|
|
14073
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
14392
|
+
const relative = path24.relative(rootPath, targetPath);
|
|
14393
|
+
return relative === "" || !relative.startsWith("..") && !path24.isAbsolute(relative);
|
|
14074
14394
|
}
|
|
14075
14395
|
function hasIgnoredRepoSegment(baseDir, targetPath) {
|
|
14076
|
-
const relativePath =
|
|
14396
|
+
const relativePath = path24.relative(baseDir, targetPath);
|
|
14077
14397
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
14078
14398
|
return false;
|
|
14079
14399
|
}
|
|
14080
|
-
return relativePath.split(
|
|
14400
|
+
return relativePath.split(path24.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
|
|
14081
14401
|
}
|
|
14082
14402
|
function workspaceIgnoreRoots(rootDir, paths) {
|
|
14083
14403
|
return [
|
|
@@ -14086,16 +14406,16 @@ function workspaceIgnoreRoots(rootDir, paths) {
|
|
|
14086
14406
|
paths.stateDir,
|
|
14087
14407
|
paths.agentDir,
|
|
14088
14408
|
paths.inboxDir,
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
14092
|
-
].map((candidate) =>
|
|
14409
|
+
path24.join(rootDir, ".claude"),
|
|
14410
|
+
path24.join(rootDir, ".cursor"),
|
|
14411
|
+
path24.join(rootDir, ".obsidian")
|
|
14412
|
+
].map((candidate) => path24.resolve(candidate));
|
|
14093
14413
|
}
|
|
14094
14414
|
async function resolveWatchTargets(rootDir, paths, options) {
|
|
14095
|
-
const targets = /* @__PURE__ */ new Set([
|
|
14415
|
+
const targets = /* @__PURE__ */ new Set([path24.resolve(paths.inboxDir)]);
|
|
14096
14416
|
if (options.repo) {
|
|
14097
14417
|
for (const repoRoot of await listTrackedRepoRoots(rootDir)) {
|
|
14098
|
-
targets.add(
|
|
14418
|
+
targets.add(path24.resolve(repoRoot));
|
|
14099
14419
|
}
|
|
14100
14420
|
}
|
|
14101
14421
|
return [...targets].sort((left, right) => left.localeCompare(right));
|
|
@@ -14225,7 +14545,7 @@ async function watchVault(rootDir, options = {}) {
|
|
|
14225
14545
|
const { paths } = await initWorkspace(rootDir);
|
|
14226
14546
|
const baseDebounceMs = options.debounceMs ?? 900;
|
|
14227
14547
|
const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
|
|
14228
|
-
const inboxWatchRoot =
|
|
14548
|
+
const inboxWatchRoot = path24.resolve(paths.inboxDir);
|
|
14229
14549
|
let watchTargets = await resolveWatchTargets(rootDir, paths, options);
|
|
14230
14550
|
let timer;
|
|
14231
14551
|
let running = false;
|
|
@@ -14239,7 +14559,7 @@ async function watchVault(rootDir, options = {}) {
|
|
|
14239
14559
|
usePolling: true,
|
|
14240
14560
|
interval: 100,
|
|
14241
14561
|
ignored: (targetPath) => {
|
|
14242
|
-
const absolutePath =
|
|
14562
|
+
const absolutePath = path24.resolve(targetPath);
|
|
14243
14563
|
const primaryTarget = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
|
|
14244
14564
|
if (!primaryTarget) {
|
|
14245
14565
|
return false;
|
|
@@ -14423,8 +14743,8 @@ async function watchVault(rootDir, options = {}) {
|
|
|
14423
14743
|
}
|
|
14424
14744
|
};
|
|
14425
14745
|
const reasonForPath = (targetPath) => {
|
|
14426
|
-
const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget,
|
|
14427
|
-
return
|
|
14746
|
+
const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, path24.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
|
|
14747
|
+
return path24.relative(baseDir, targetPath) || ".";
|
|
14428
14748
|
};
|
|
14429
14749
|
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
14750
|
await new Promise((resolve, reject) => {
|
|
@@ -14465,15 +14785,15 @@ async function getWatchStatus(rootDir) {
|
|
|
14465
14785
|
var execFileAsync = promisify(execFile);
|
|
14466
14786
|
async function readViewerPage(rootDir, relativePath) {
|
|
14467
14787
|
const { paths } = await loadVaultConfig(rootDir);
|
|
14468
|
-
const absolutePath =
|
|
14788
|
+
const absolutePath = path25.resolve(paths.wikiDir, relativePath);
|
|
14469
14789
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
14470
14790
|
return null;
|
|
14471
14791
|
}
|
|
14472
|
-
const raw = await
|
|
14792
|
+
const raw = await fs20.readFile(absolutePath, "utf8");
|
|
14473
14793
|
const parsed = matter10(raw);
|
|
14474
14794
|
return {
|
|
14475
14795
|
path: relativePath,
|
|
14476
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
14796
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path25.basename(relativePath, path25.extname(relativePath)),
|
|
14477
14797
|
frontmatter: parsed.data,
|
|
14478
14798
|
content: parsed.content,
|
|
14479
14799
|
assets: normalizeOutputAssets(parsed.data.output_assets)
|
|
@@ -14481,12 +14801,12 @@ async function readViewerPage(rootDir, relativePath) {
|
|
|
14481
14801
|
}
|
|
14482
14802
|
async function readViewerAsset(rootDir, relativePath) {
|
|
14483
14803
|
const { paths } = await loadVaultConfig(rootDir);
|
|
14484
|
-
const absolutePath =
|
|
14804
|
+
const absolutePath = path25.resolve(paths.wikiDir, relativePath);
|
|
14485
14805
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
14486
14806
|
return null;
|
|
14487
14807
|
}
|
|
14488
14808
|
return {
|
|
14489
|
-
buffer: await
|
|
14809
|
+
buffer: await fs20.readFile(absolutePath),
|
|
14490
14810
|
mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
|
|
14491
14811
|
};
|
|
14492
14812
|
}
|
|
@@ -14509,12 +14829,12 @@ async function readJsonBody(request) {
|
|
|
14509
14829
|
return JSON.parse(raw);
|
|
14510
14830
|
}
|
|
14511
14831
|
async function ensureViewerDist(viewerDistDir) {
|
|
14512
|
-
const indexPath =
|
|
14832
|
+
const indexPath = path25.join(viewerDistDir, "index.html");
|
|
14513
14833
|
if (await fileExists(indexPath)) {
|
|
14514
14834
|
return;
|
|
14515
14835
|
}
|
|
14516
|
-
const viewerProjectDir =
|
|
14517
|
-
if (await fileExists(
|
|
14836
|
+
const viewerProjectDir = path25.dirname(viewerDistDir);
|
|
14837
|
+
if (await fileExists(path25.join(viewerProjectDir, "package.json"))) {
|
|
14518
14838
|
await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
|
|
14519
14839
|
}
|
|
14520
14840
|
}
|
|
@@ -14531,7 +14851,7 @@ async function startGraphServer(rootDir, port) {
|
|
|
14531
14851
|
return;
|
|
14532
14852
|
}
|
|
14533
14853
|
response.writeHead(200, { "content-type": "application/json" });
|
|
14534
|
-
response.end(await
|
|
14854
|
+
response.end(await fs20.readFile(paths.graphPath, "utf8"));
|
|
14535
14855
|
return;
|
|
14536
14856
|
}
|
|
14537
14857
|
if (url.pathname === "/api/graph/query") {
|
|
@@ -14588,14 +14908,14 @@ async function startGraphServer(rootDir, port) {
|
|
|
14588
14908
|
return;
|
|
14589
14909
|
}
|
|
14590
14910
|
if (url.pathname === "/api/graph-report") {
|
|
14591
|
-
const reportPath =
|
|
14911
|
+
const reportPath = path25.join(paths.wikiDir, "graph", "report.json");
|
|
14592
14912
|
if (!await fileExists(reportPath)) {
|
|
14593
14913
|
response.writeHead(404, { "content-type": "application/json" });
|
|
14594
14914
|
response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
|
|
14595
14915
|
return;
|
|
14596
14916
|
}
|
|
14597
14917
|
response.writeHead(200, { "content-type": "application/json" });
|
|
14598
|
-
response.end(await
|
|
14918
|
+
response.end(await fs20.readFile(reportPath, "utf8"));
|
|
14599
14919
|
return;
|
|
14600
14920
|
}
|
|
14601
14921
|
if (url.pathname === "/api/watch-status") {
|
|
@@ -14678,8 +14998,8 @@ async function startGraphServer(rootDir, port) {
|
|
|
14678
14998
|
return;
|
|
14679
14999
|
}
|
|
14680
15000
|
const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
14681
|
-
const target =
|
|
14682
|
-
const fallback =
|
|
15001
|
+
const target = path25.join(paths.viewerDistDir, relativePath);
|
|
15002
|
+
const fallback = path25.join(paths.viewerDistDir, "index.html");
|
|
14683
15003
|
const filePath = await fileExists(target) ? target : fallback;
|
|
14684
15004
|
if (!await fileExists(filePath)) {
|
|
14685
15005
|
response.writeHead(503, { "content-type": "text/plain" });
|
|
@@ -14687,7 +15007,7 @@ async function startGraphServer(rootDir, port) {
|
|
|
14687
15007
|
return;
|
|
14688
15008
|
}
|
|
14689
15009
|
response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
|
|
14690
|
-
response.end(await
|
|
15010
|
+
response.end(await fs20.readFile(filePath));
|
|
14691
15011
|
});
|
|
14692
15012
|
await new Promise((resolve) => {
|
|
14693
15013
|
server.listen(effectivePort, resolve);
|
|
@@ -14714,7 +15034,7 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
14714
15034
|
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
14715
15035
|
}
|
|
14716
15036
|
await ensureViewerDist(paths.viewerDistDir);
|
|
14717
|
-
const indexPath =
|
|
15037
|
+
const indexPath = path25.join(paths.viewerDistDir, "index.html");
|
|
14718
15038
|
if (!await fileExists(indexPath)) {
|
|
14719
15039
|
throw new Error("Viewer build not found. Run `pnpm build` first.");
|
|
14720
15040
|
}
|
|
@@ -14740,17 +15060,17 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
14740
15060
|
} : null;
|
|
14741
15061
|
})
|
|
14742
15062
|
);
|
|
14743
|
-
const rawHtml = await
|
|
15063
|
+
const rawHtml = await fs20.readFile(indexPath, "utf8");
|
|
14744
15064
|
const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
|
|
14745
15065
|
const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
|
|
14746
|
-
const scriptPath = scriptMatch?.[1] ?
|
|
14747
|
-
const stylePath = styleMatch?.[1] ?
|
|
15066
|
+
const scriptPath = scriptMatch?.[1] ? path25.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
|
|
15067
|
+
const stylePath = styleMatch?.[1] ? path25.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
|
|
14748
15068
|
if (!scriptPath || !await fileExists(scriptPath)) {
|
|
14749
15069
|
throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
|
|
14750
15070
|
}
|
|
14751
|
-
const script = await
|
|
14752
|
-
const style = stylePath && await fileExists(stylePath) ? await
|
|
14753
|
-
const report = await readJsonFile(
|
|
15071
|
+
const script = await fs20.readFile(scriptPath, "utf8");
|
|
15072
|
+
const style = stylePath && await fileExists(stylePath) ? await fs20.readFile(stylePath, "utf8") : "";
|
|
15073
|
+
const report = await readJsonFile(path25.join(paths.wikiDir, "graph", "report.json"));
|
|
14754
15074
|
const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean), report }, null, 2).replace(/</g, "\\u003c");
|
|
14755
15075
|
const html = [
|
|
14756
15076
|
"<!doctype html>",
|
|
@@ -14769,9 +15089,9 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
14769
15089
|
"</html>",
|
|
14770
15090
|
""
|
|
14771
15091
|
].filter(Boolean).join("\n");
|
|
14772
|
-
await
|
|
14773
|
-
await
|
|
14774
|
-
return
|
|
15092
|
+
await fs20.mkdir(path25.dirname(outputPath), { recursive: true });
|
|
15093
|
+
await fs20.writeFile(outputPath, html, "utf8");
|
|
15094
|
+
return path25.resolve(outputPath);
|
|
14775
15095
|
}
|
|
14776
15096
|
export {
|
|
14777
15097
|
acceptApproval,
|
|
@@ -14817,6 +15137,7 @@ export {
|
|
|
14817
15137
|
loadVaultSchemas,
|
|
14818
15138
|
pathGraphVault,
|
|
14819
15139
|
promoteCandidate,
|
|
15140
|
+
pushGraphNeo4j,
|
|
14820
15141
|
queryGraphVault,
|
|
14821
15142
|
queryVault,
|
|
14822
15143
|
readApproval,
|