@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/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  uniqueBy,
22
22
  writeFileIfChanged,
23
23
  writeJsonFile
24
- } from "./chunk-IHMJCCXR.js";
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
- var NODE_COLORS = {
752
- source: "#f59e0b",
753
- module: "#fb7185",
754
- symbol: "#8b5cf6",
755
- rationale: "#14b8a6",
756
- concept: "#0ea5e9",
757
- entity: "#22c55e"
758
- };
759
- function xmlEscape(value) {
760
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
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 exportHyperedgeNodeId(hyperedge) {
806
- return `hyperedge:${hyperedge.id}`;
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
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
- lines.push(
1089
- `MERGE (h:SwarmNode {id: ${cypherStringLiteral(hyperedgeNodeId)}}) SET h += { id: ${cypherStringLiteral(hyperedgeNodeId)}, label: ${cypherStringLiteral(
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 += { relation: ${cypherStringLiteral("group_member")}, status: ${cypherStringLiteral("inferred")}, evidenceClass: ${cypherStringLiteral(
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 += { relation: ${cypherStringLiteral(edge.relation)}, status: ${cypherStringLiteral(edge.status)}, evidenceClass: ${cypherStringLiteral(
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/hooks.ts
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 = path3.resolve(startPath);
1592
+ let current = path4.resolve(startPath);
1154
1593
  try {
1155
- const stat = await fs3.stat(current);
1594
+ const stat = await fs4.stat(current);
1156
1595
  if (!stat.isDirectory()) {
1157
- current = path3.dirname(current);
1596
+ current = path4.dirname(current);
1158
1597
  }
1159
1598
  } catch {
1160
- current = path3.dirname(current);
1599
+ current = path4.dirname(current);
1161
1600
  }
1162
1601
  while (true) {
1163
- if (await fileExists(path3.join(current, ".git"))) {
1602
+ if (await fileExists(path4.join(current, ".git"))) {
1164
1603
  return current;
1165
1604
  }
1166
- const parent = path3.dirname(current);
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(`${path3.sep}@swarmvaultai${path3.sep}cli${path3.sep}`) || argvPath.includes(`${path3.sep}packages${path3.sep}cli${path3.sep}`))) {
1179
- return path3.resolve(argvPath);
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 path3.join(repoRoot, ".git", "hooks", hookName);
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 fs3.readFile(filePath, "utf8");
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 fs3.readFile(filePath, "utf8") : "";
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(path3.dirname(filePath));
1223
- await fs3.writeFile(filePath, `${next}
1661
+ await ensureDir(path4.dirname(filePath));
1662
+ await fs4.writeFile(filePath, `${next}
1224
1663
  `, { mode: 493, encoding: "utf8" });
1225
- await fs3.chmod(filePath, 493);
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 fs3.readFile(filePath, "utf8");
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 fs3.rm(filePath, { force: true });
1678
+ await fs4.rm(filePath, { force: true });
1240
1679
  return;
1241
1680
  }
1242
- await fs3.writeFile(filePath, `${next}
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(path3.resolve(rootDir));
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 fs9 from "fs/promises";
1286
- import path10 from "path";
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 fs5 from "fs/promises";
1297
- import path5 from "path";
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 fs4 from "fs/promises";
1740
+ import fs5 from "fs/promises";
1302
1741
  import { createRequire } from "module";
1303
- import path4 from "path";
1742
+ import path5 from "path";
1304
1743
  var require2 = createRequire(import.meta.url);
1305
- var TREE_SITTER_PACKAGE_ROOT = path4.dirname(path4.dirname(require2.resolve("@vscode/tree-sitter-wasm")));
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: () => path4.join(TREE_SITTER_PACKAGE_ROOT, "wasm", "tree-sitter.wasm")
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 fs4.readFile(path4.join(TREE_SITTER_PACKAGE_ROOT, "wasm", grammarFileByLanguage[language]));
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 ?? path4.basename(manifest.originalPath ?? manifest.storedPath);
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 || path4.posix.basename(normalized);
1818
+ return dotted || path5.posix.basename(normalized);
1380
1819
  }
1381
- return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) || path4.posix.basename(normalized) : normalized;
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(sourceId, name, seen) {
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:${sourceId}:${count === 1 ? base : `${base}-${count}`}`;
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 ?? path5.basename(manifest.originalPath ?? manifest.storedPath);
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 || path5.posix.basename(normalized);
3232
+ return dotted || path6.posix.basename(normalized);
2794
3233
  }
2795
- return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) || path5.posix.basename(normalized) : normalized;
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(manifest.sourceId, symbol.name, seenSymbolIds),
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 = path5.extname(filePath).toLowerCase();
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 = path5.posix.normalize(path5.posix.join(path5.posix.dirname(basePath), specifier));
3138
- if (path5.posix.extname(resolved)) {
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) => path5.posix.normalize(`${resolved}${extension}`));
3142
- const indexFiles = extensions.map((extension) => path5.posix.normalize(path5.posix.join(resolved, `index${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 path5.posix.basename(stripCodeExtension2(normalizeAlias(target)));
3601
+ return path6.posix.basename(stripCodeExtension2(normalizeAlias(target)));
3162
3602
  }
3163
3603
  async function readNearestGoModulePath(startPath, cache) {
3164
- let current = path5.resolve(startPath);
3604
+ let current = path6.resolve(startPath);
3165
3605
  try {
3166
- const stat = await fs5.stat(current);
3606
+ const stat = await fs6.stat(current);
3167
3607
  if (!stat.isDirectory()) {
3168
- current = path5.dirname(current);
3608
+ current = path6.dirname(current);
3169
3609
  }
3170
3610
  } catch {
3171
- current = path5.dirname(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 = path5.join(current, "go.mod");
3179
- if (await fs5.access(goModPath).then(() => true).catch(() => false)) {
3180
- const content = await fs5.readFile(goModPath, "utf8");
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 = path5.dirname(current);
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 ? path5.resolve(manifest.originalPath) : path5.resolve(rootDir, manifest.storedPath);
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 = path5.posix.dirname(repoRelativePath);
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 = path5.posix.dirname(repoRelativePath);
3347
- const parentDir = path5.posix.normalize(path5.posix.join(baseDir, ...Array(Math.max(depth - 1, 0)).fill("..")));
3348
- const moduleBase = relativeModule ? path5.posix.join(parentDir, relativeModule) : parentDir;
3349
- return uniqueBy([`${moduleBase}.py`, path5.posix.join(moduleBase, "__init__.py")], (item) => item);
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 fs6 from "fs/promises";
3931
+ import fs7 from "fs/promises";
3492
3932
  import os from "os";
3493
- import path6 from "path";
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 fs6.mkdtemp(path6.join(os.tmpdir(), "swarmvault-image-extract-"));
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 = path6.join(tempDir, `source.${extension}`);
3578
- await fs6.writeFile(tempPath, input.bytes);
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 fs6.rm(tempDir, { recursive: true, force: true });
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 fs7 from "fs/promises";
3794
- import path7 from "path";
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 = path7.join(paths.sessionsDir, `${baseName}.md`);
4241
+ let candidate = path8.join(paths.sessionsDir, `${baseName}.md`);
3802
4242
  let counter = 2;
3803
4243
  while (await fileExists(candidate)) {
3804
- candidate = path7.join(paths.sessionsDir, `${baseName}-${counter}.md`);
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 = path7.join(paths.wikiDir, "log.md");
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 fs7.readFile(logPath, "utf8") : "# Log\n\n";
3816
- await fs7.writeFile(logPath, `${existing}${entry}
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 = path7.basename(sessionPath, ".md");
3827
- const relativeSessionPath = path7.relative(rootDir, sessionPath).split(path7.sep).join(path7.posix.sep);
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 = path7.join(paths.wikiDir, "log.md");
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 fs7.readFile(logPath, "utf8") : "# Log\n\n";
3884
- await fs7.writeFile(logPath, `${existing}${entry}
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 path8 from "path";
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) => path8.matchesGlob(relativePath, pattern) || path8.matchesGlob(path8.posix.basename(relativePath), 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 fs8 from "fs/promises";
3954
- import path9 from "path";
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(path9.relative(rootDir, path9.resolve(filePath)));
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 = path9.join(paths.wikiDir, page.path);
4502
+ const absolutePath = path10.join(paths.wikiDir, page.path);
4063
4503
  if (!await fileExists(absolutePath)) {
4064
4504
  continue;
4065
4505
  }
4066
- const raw = await fs8.readFile(absolutePath, "utf8");
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 ? path10.resolve(options.repoRoot) : void 0,
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) => path10.matchesGlob(relativePath, pattern) || path10.matchesGlob(path10.posix.basename(relativePath), 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 = path10.resolve(startPath);
4587
+ let current = path11.resolve(startPath);
4148
4588
  try {
4149
- const stat = await fs9.stat(current);
4589
+ const stat = await fs10.stat(current);
4150
4590
  if (!stat.isDirectory()) {
4151
- current = path10.dirname(current);
4591
+ current = path11.dirname(current);
4152
4592
  }
4153
4593
  } catch {
4154
- current = path10.dirname(current);
4594
+ current = path11.dirname(current);
4155
4595
  }
4156
4596
  while (true) {
4157
- if (await fileExists(path10.join(current, ".git"))) {
4597
+ if (await fileExists(path11.join(current, ".git"))) {
4158
4598
  return current;
4159
4599
  }
4160
- const parent = path10.dirname(current);
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 = path10.relative(rootPath, targetPath);
4169
- return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
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 = path10.posix.dirname(manifest.repoRelativePath);
4176
- const fileDir = path10.dirname(path10.resolve(manifest.originalPath));
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 path10.resolve(fileDir, ...segments.map(() => ".."));
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(path10.relative(repoRoot, absolutePath));
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 = path10.posix.normalize(value.replace(/\\/g, "/"));
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("#") || path10.isAbsolute(candidate)) {
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 fs9.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
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(path10.join(manifestsDir, entry.name));
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 fs9.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
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(path10.join(manifestsDir, entry.name));
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 = path10.join(repoRoot, ".gitignore");
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 fs9.readFile(gitignorePath, "utf8"));
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 fs9.readdir(currentDir, { withFileTypes: true });
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 = path10.join(currentDir, entry.name);
4644
- const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(inputDir, absolutePath));
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(path10.relative(rootDir, absolutePath)), reason: builtInReason });
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(path10.relative(rootDir, absolutePath)), reason: "gitignore" });
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(path10.relative(rootDir, absolutePath)), reason: "exclude_glob" });
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(path10.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
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(path10.relative(rootDir, absolutePath)), reason: "include_glob" });
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(path10.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
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(path10.relative(rootDir, absolutePath)), reason: `source_class:${sourceClass}` });
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(path10.relative(rootDir, absolutePath)), reason: "max_files" });
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 = path10.posix.extname(normalized);
4706
- const directory = path10.posix.dirname(normalized);
4707
- const basename = extension ? path10.posix.basename(normalized, extension) : path10.posix.basename(normalized);
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 : path10.posix.join(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 = path10.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
4863
- const extractedTextPath = prepared.extractedText ? path10.join(paths.extractsDir, `${sourceId}.md`) : void 0;
4864
- const extractedMetadataPath = prepared.extractionArtifact ? path10.join(paths.extractsDir, `${sourceId}.json`) : void 0;
4865
- const attachmentsDir = path10.join(paths.rawAssetsDir, sourceId);
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 fs9.rm(path10.resolve(rootDir, previous.storedPath), { force: true });
5343
+ await fs10.rm(path11.resolve(rootDir, previous.storedPath), { force: true });
4868
5344
  }
4869
5345
  if (previous?.extractedTextPath) {
4870
- await fs9.rm(path10.resolve(rootDir, previous.extractedTextPath), { force: true });
5346
+ await fs10.rm(path11.resolve(rootDir, previous.extractedTextPath), { force: true });
4871
5347
  }
4872
5348
  if (previous?.extractedMetadataPath) {
4873
- await fs9.rm(path10.resolve(rootDir, previous.extractedMetadataPath), { force: true });
5349
+ await fs10.rm(path11.resolve(rootDir, previous.extractedMetadataPath), { force: true });
4874
5350
  }
4875
- await fs9.rm(attachmentsDir, { recursive: true, force: true });
4876
- await fs9.writeFile(storedPath, prepared.payloadBytes);
5351
+ await fs10.rm(attachmentsDir, { recursive: true, force: true });
5352
+ await fs10.writeFile(storedPath, prepared.payloadBytes);
4877
5353
  if (prepared.extractedText && extractedTextPath) {
4878
- await fs9.writeFile(extractedTextPath, prepared.extractedText, "utf8");
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 = path10.join(attachmentsDir, attachment.relativePath);
4886
- await ensureDir(path10.dirname(absoluteAttachmentPath));
4887
- await fs9.writeFile(absoluteAttachmentPath, attachment.bytes);
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(path10.relative(rootDir, absoluteAttachmentPath)),
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(path10.relative(rootDir, storedPath)),
4906
- extractedTextPath: extractedTextPath ? toPosix(path10.relative(rootDir, extractedTextPath)) : void 0,
4907
- extractedMetadataPath: extractedMetadataPath ? toPosix(path10.relative(rootDir, extractedMetadataPath)) : void 0,
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(path10.join(paths.manifestsDir, `${sourceId}.json`), manifest);
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 fs9.rm(path10.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
4934
- await fs9.rm(path10.resolve(rootDir, manifest.storedPath), { force: true });
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 fs9.rm(path10.resolve(rootDir, manifest.extractedTextPath), { force: true });
5412
+ await fs10.rm(path11.resolve(rootDir, manifest.extractedTextPath), { force: true });
4937
5413
  }
4938
5414
  if (manifest.extractedMetadataPath) {
4939
- await fs9.rm(path10.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
5415
+ await fs10.rm(path11.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
4940
5416
  }
4941
- await fs9.rm(path10.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
4942
- await fs9.rm(path10.join(paths.analysesDir, `${manifest.sourceId}.json`), { force: true });
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
- path10.join(rootDir, ".claude"),
4952
- path10.join(rootDir, ".cursor"),
4953
- path10.join(rootDir, ".obsidian")
5427
+ path11.join(rootDir, ".claude"),
5428
+ path11.join(rootDir, ".cursor"),
5429
+ path11.join(rootDir, ".obsidian")
4954
5430
  ];
4955
- return candidates.map((candidate) => path10.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, 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) => path10.resolve(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(path10.resolve(repoRoot))) {
5459
+ if (!repoRoot || !uniqueRoots.includes(path11.resolve(repoRoot))) {
4984
5460
  continue;
4985
5461
  }
4986
- const key = path10.resolve(repoRoot);
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(path10.relative(rootDir, absolutePath)),
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) => path10.resolve(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(path10.relative(repoRoot, absolutePath));
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 ? path10.resolve(manifest.originalPath) : null;
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(path10.relative(rootDir, repoRoot)) || ".").join(","), [
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) => path10.resolve(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(path10.resolve(repoRoot))) {
5541
+ if (!repoRoot || !uniqueRoots.includes(path11.resolve(repoRoot))) {
5066
5542
  continue;
5067
5543
  }
5068
- const key = path10.resolve(repoRoot);
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) => [path10.resolve(manifest.originalPath), 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(path10.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
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(path10.relative(rootDir, absolutePath)),
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) => path10.resolve(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(path10.relative(repoRoot, absolutePath));
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(path10.resolve(absolutePath));
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(path10.relative(repoRoot, absolutePath))
5605
+ prepared.repoRelativePath ?? toPosix(path11.relative(repoRoot, absolutePath))
5130
5606
  ),
5131
5607
  repoRoot,
5132
- path: toPosix(path10.relative(rootDir, absolutePath)),
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 ? path10.resolve(manifest.originalPath) : null;
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(path10.relative(repoRoot, originalPath))),
5632
+ id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path11.relative(repoRoot, originalPath))),
5157
5633
  repoRoot,
5158
- path: toPosix(path10.relative(rootDir, originalPath)),
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(path10.relative(rootDir, repoRoot)) || ".").join(","),
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 fs9.readFile(absoluteInput);
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 = path10.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
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(path10.basename(absoluteInput, path10.extname(absoluteInput)), extractedText);
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 = path10.basename(absoluteInput, path10.extname(absoluteInput));
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 = path10.basename(absoluteInput, path10.extname(absoluteInput));
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 = path10.basename(absoluteInput, path10.extname(absoluteInput));
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 = path10.basename(absoluteInput, path10.extname(absoluteInput));
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 = path10.extname(inputUrl.pathname);
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 fs9.readFile(absolutePath, "utf8");
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 = path10.resolve(path10.dirname(absolutePath), ref);
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 fs9.readFile(absolutePath);
5907
+ const originalBytes = await fs10.readFile(absolutePath);
5431
5908
  const originalText = originalBytes.toString("utf8");
5432
- const title = titleFromText(path10.basename(absolutePath, path10.extname(absolutePath)), originalText);
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 fs9.readFile(attachmentRef.absolutePath);
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: path10.extname(absolutePath) || ".md",
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 fs9.readFile(absolutePath);
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 fs9.readFile(attachmentRef.absolutePath);
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 = path10.basename(absolutePath, path10.extname(absolutePath));
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: path10.extname(absolutePath) || ".html",
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 = path10.resolve(rootDir, input);
5517
- const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ?? path10.dirname(absoluteInput));
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 = path10.resolve(rootDir, inputDir);
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(path10.relative(repoRoot, absolutePath));
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(path10.relative(rootDir, absolutePath)), reason: "duplicate_content" });
6101
+ skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "duplicate_content" });
5625
6102
  }
5626
6103
  }
5627
- await appendLogEntry(rootDir, "ingest_directory", toPosix(path10.relative(rootDir, absoluteInputDir)) || ".", [
5628
- `repo_root=${toPosix(path10.relative(rootDir, repoRoot)) || "."}`,
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 = path10.resolve(rootDir, inputDir ?? paths.inboxDir);
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 = path10.basename(absolutePath);
6133
+ const basename = path11.basename(absolutePath);
5657
6134
  if (basename.startsWith(".")) {
5658
- skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "hidden_file" });
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(path10.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
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(path10.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
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(path10.relative(rootDir, absolutePath)), reason: "duplicate_content" });
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(path10.relative(rootDir, effectiveInputDir)) || ".", [
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 fs9.readdir(paths.manifestsDir);
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(path10.join(paths.manifestsDir, entry)))
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 = path10.resolve(rootDir, manifest.extractedTextPath);
6186
+ const absolutePath = path11.resolve(rootDir, manifest.extractedTextPath);
5710
6187
  if (!await fileExists(absolutePath)) {
5711
6188
  return void 0;
5712
6189
  }
5713
- return fs9.readFile(absolutePath, "utf8");
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 = path10.resolve(rootDir, manifest.extractedMetadataPath);
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 fs17 from "fs/promises";
5728
- import path21 from "path";
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 fs10 from "fs/promises";
5735
- import path11 from "path";
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 fs10.readFile(schemaPath, "utf8") : fallback;
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 path11.resolve(rootDir, schemaPath);
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(path11.relative(path11.dirname(root.path), schema.path) || schema.path)}`,
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 fs16 from "fs/promises";
5842
- import path20 from "path";
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 path12 from "path";
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 = path12.join(paths.analysesDir, `${manifest.sourceId}.json`);
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 fs11 from "fs/promises";
6316
- import path15 from "path";
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 path13 from "path";
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 ? path13.resolve(rootDir, executor.cwd) : rootDir;
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 path14 from "path";
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 = path14.isAbsolute(config.module) ? config.module : path14.resolve(rootDir, config.module);
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 = path15.join(paths.wikiDir, page.path);
6683
- const raw = await fs11.readFile(absolutePath, "utf8").catch(() => "");
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 ${path15.basename(finding.pagePath, ".md")}?` : void 0
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 fs12 from "fs/promises";
6910
- import path16 from "path";
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 = path16.join(paths.wikiDir, page.path);
6945
- const content = await fs12.readFile(absolutePath, "utf8").catch(() => {
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 path25 = shortestGraphPath(graph, edge.source, edge.target);
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 = path25.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
8769
+ const pathEdges = path26.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
8450
8770
  return {
8451
- pathNodeIds: path25.nodeIds,
8452
- pathEdgeIds: path25.edgeIds,
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: path25.summary
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 path25 = supportingPathDetails(graph, edge);
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: path25.pathNodeIds,
8536
- pathEdgeIds: path25.pathEdgeIds,
8537
- pathRelations: path25.pathRelations,
8538
- pathEvidenceClasses: path25.pathEvidenceClasses,
8539
- pathSummary: path25.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 fs14 from "fs/promises";
9425
- import path18 from "path";
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 fs13 from "fs/promises";
9430
- import path17 from "path";
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 fs13.readFile(absolutePath, "utf8");
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 : path17.basename(relativePath, ".md");
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 = path17.join(wikiDir, "insights");
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) => path17.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
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(path17.relative(wikiDir, absolutePath));
9596
- const content = await fs13.readFile(absolutePath, "utf8");
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 fs13.stat(absolutePath);
9599
- const title = typeof parsed.data.title === "string" ? parsed.data.title : path17.basename(absolutePath, ".md");
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 = path18.join(wikiDir, "outputs");
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(path18.join(outputsDir, `${candidate}.md`))) {
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 = path18.join(wikiDir, "outputs");
9674
- const entries = await fs14.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
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 = path18.posix.join("outputs", entry.name);
9681
- const absolutePath = path18.join(outputsDir, entry.name);
9682
- const content = await fs14.readFile(absolutePath, "utf8");
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 fs14.stat(absolutePath);
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 fs15 from "fs/promises";
9734
- import path19 from "path";
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(path19.dirname(dbPath));
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 = path19.join(wikiDir, page.path);
9793
- const content = await fs15.readFile(absolutePath, "utf8");
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(path20.join("outputs", "assets", slug, fileName));
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-5SYH3Y3U.js");
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(path20.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
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 (!path20.isAbsolute(rawPath)) {
10594
+ if (!path21.isAbsolute(rawPath)) {
10275
10595
  return normalizeProjectRoot(rawPath);
10276
10596
  }
10277
- const relative = toPosix(path20.relative(rootDir, rawPath));
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 fs16.readFile(absolutePath, "utf8") : null;
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 fs16.readFile(absolutePath, "utf8") : null;
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 ?? path20.basename(manifest.originalPath ?? manifest.storedPath));
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}:${path20.posix.dirname(manifestRepoPath(manifest))}`;
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 = path20.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
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 = path20.join(paths.wikiDir, "graph", "report.md");
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 = path20.resolve(wikiDir, relativePath);
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
- path20.join(paths.wikiDir, "index.md"),
11162
- path20.join(paths.wikiDir, "sources", "index.md"),
11163
- path20.join(paths.wikiDir, "code", "index.md"),
11164
- path20.join(paths.wikiDir, "concepts", "index.md"),
11165
- path20.join(paths.wikiDir, "entities", "index.md"),
11166
- path20.join(paths.wikiDir, "outputs", "index.md"),
11167
- path20.join(paths.wikiDir, "projects", "index.md"),
11168
- path20.join(paths.wikiDir, "candidates", "index.md")
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(path20.join(paths.analysesDir, `${manifest.sourceId}.json`)))
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 path20.join(paths.approvalsDir, approvalId, "manifest.json");
11500
+ return path21.join(paths.approvalsDir, approvalId, "manifest.json");
11181
11501
  }
11182
11502
  function approvalGraphPath(paths, approvalId) {
11183
- return path20.join(paths.approvalsDir, approvalId, "state", "graph.json");
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 fs16.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
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(path20.join(paths.wikiDir, file.relativePath));
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 ?? path20.basename(deletedPath, ".md"),
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 = path20.join(paths.approvalsDir, approvalId);
11573
+ const approvalDir = path21.join(paths.approvalsDir, approvalId);
11254
11574
  await ensureDir(approvalDir);
11255
- await ensureDir(path20.join(approvalDir, "wiki"));
11256
- await ensureDir(path20.join(approvalDir, "state"));
11575
+ await ensureDir(path21.join(approvalDir, "wiki"));
11576
+ await ensureDir(path21.join(approvalDir, "state"));
11257
11577
  for (const file of changedFiles) {
11258
- const targetPath = path20.join(approvalDir, "wiki", file.relativePath);
11259
- await ensureDir(path20.dirname(targetPath));
11260
- await fs16.writeFile(targetPath, file.content, "utf8");
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 fs16.writeFile(path20.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
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
- path20.join(paths.wikiDir, preview.path),
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
- path20.join(paths.wikiDir, modulePreview.path),
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
- path20.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
11405
- path20.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
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
- path20.join(paths.wikiDir, relativePath),
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
- path20.join(paths.wikiDir, "projects", "index.md"),
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
- path20.join(paths.wikiDir, projectIndexRef.path),
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
- path20.join(paths.wikiDir, "index.md"),
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
- path20.join(paths.wikiDir, relativePath),
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(path20.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
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 = path20.join(paths.wikiDir, record.page.path);
11617
- const current = await fileExists(absolutePath) ? await fs16.readFile(absolutePath, "utf8") : null;
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 fs16.rm(path20.join(paths.wikiDir, relativePath), { force: true });
11962
+ await fs17.rm(path21.join(paths.wikiDir, relativePath), { force: true });
11643
11963
  }
11644
11964
  await writeJsonFile(paths.graphPath, graph);
11645
- await writeJsonFile(path20.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
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(path20.join(paths.wikiDir, "sources")),
11717
- ensureDir(path20.join(paths.wikiDir, "code")),
11718
- ensureDir(path20.join(paths.wikiDir, "concepts")),
11719
- ensureDir(path20.join(paths.wikiDir, "entities")),
11720
- ensureDir(path20.join(paths.wikiDir, "outputs")),
11721
- ensureDir(path20.join(paths.wikiDir, "graph")),
11722
- ensureDir(path20.join(paths.wikiDir, "graph", "communities")),
11723
- ensureDir(path20.join(paths.wikiDir, "projects")),
11724
- ensureDir(path20.join(paths.wikiDir, "candidates"))
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 = path20.join(paths.wikiDir, "projects", "index.md");
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 = path20.join(paths.wikiDir, "projects", project.id, "index.md");
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 = path20.join(paths.wikiDir, "index.md");
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 = path20.join(paths.wikiDir, relativePath);
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(path20.join(paths.wikiDir, record.page.path), record.content);
12120
+ await writeFileIfChanged(path21.join(paths.wikiDir, record.page.path), record.content);
11801
12121
  }
11802
12122
  if (graphOrientation.report) {
11803
- await writeJsonFile(path20.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
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(path20.relative(paths.wikiDir, absolutePath)));
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) => fs16.rm(path20.join(paths.wikiDir, relativePath), { force: true }))
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(path20.join(paths.wikiDir, "graph").replace(/\/$/, "")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path20.relative(paths.wikiDir, absolutePath)));
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) => fs16.rm(path20.join(paths.wikiDir, relativePath), { force: true }))
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 = path20.join(paths.wikiDir, output.page.path);
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(path20.dirname(prepared.savedPath));
11849
- await fs16.writeFile(prepared.savedPath, prepared.content, "utf8");
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 = path20.join(paths.wikiDir, assetFile.relativePath);
11852
- await ensureDir(path20.dirname(assetPath));
12171
+ const assetPath = path21.join(paths.wikiDir, assetFile.relativePath);
12172
+ await ensureDir(path21.dirname(assetPath));
11853
12173
  if (typeof assetFile.content === "string") {
11854
- await fs16.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
12174
+ await fs17.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
11855
12175
  } else {
11856
- await fs16.writeFile(assetPath, assetFile.content);
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 = path20.join(paths.wikiDir, hub.page.path);
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(path20.dirname(prepared.savedPath));
11890
- await fs16.writeFile(prepared.savedPath, prepared.content, "utf8");
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 = path20.join(paths.wikiDir, assetFile.relativePath);
11893
- await ensureDir(path20.dirname(assetPath));
12212
+ const assetPath = path21.join(paths.wikiDir, assetFile.relativePath);
12213
+ await ensureDir(path21.dirname(assetPath));
11894
12214
  if (typeof assetFile.content === "string") {
11895
- await fs16.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
12215
+ await fs17.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
11896
12216
  } else {
11897
- await fs16.writeFile(assetPath, assetFile.content);
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 = path20.join(paths.approvalsDir, approvalId);
12234
+ const approvalDir = path21.join(paths.approvalsDir, approvalId);
11915
12235
  await ensureDir(approvalDir);
11916
- await ensureDir(path20.join(approvalDir, "wiki"));
11917
- await ensureDir(path20.join(approvalDir, "state"));
12236
+ await ensureDir(path21.join(approvalDir, "wiki"));
12237
+ await ensureDir(path21.join(approvalDir, "state"));
11918
12238
  for (const file of changedFiles) {
11919
- const targetPath = path20.join(approvalDir, "wiki", file.relativePath);
11920
- await ensureDir(path20.dirname(targetPath));
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 fs16.writeFile(targetPath, Buffer.from(file.content, "base64"));
12242
+ await fs17.writeFile(targetPath, Buffer.from(file.content, "base64"));
11923
12243
  } else {
11924
- await fs16.writeFile(targetPath, file.content, "utf8");
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 fs16.writeFile(path20.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
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 = path20.join(paths.wikiDir, result.path);
12288
+ const absolutePath = path21.join(paths.wikiDir, result.path);
11969
12289
  try {
11970
- const content = await fs16.readFile(absolutePath, "utf8");
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 fs16.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
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 fs16.readFile(path20.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
12169
- const stagedContent = entry.nextPath ? await fs16.readFile(path20.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
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 = path20.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
12198
- const stagedContent = await fs16.readFile(stagedAbsolutePath, "utf8");
12199
- const targetAbsolutePath = path20.join(paths.wikiDir, entry.nextPath);
12200
- await ensureDir(path20.dirname(targetAbsolutePath));
12201
- await fs16.writeFile(targetAbsolutePath, stagedContent, "utf8");
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 fs16.rm(path20.join(paths.wikiDir, entry.previousPath), { force: true });
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 = path20.join(paths.wikiDir, "outputs", "assets", path20.basename(nextPage.path, ".md"));
12208
- await fs16.rm(outputAssetDir, { recursive: true, force: true });
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 = path20.join(paths.approvalsDir, approvalId, "wiki", asset.path);
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 = path20.join(paths.wikiDir, asset.path);
12215
- await ensureDir(path20.dirname(targetAssetPath));
12216
- await fs16.copyFile(stagedAssetPath, targetAssetPath);
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 fs16.rm(path20.join(paths.wikiDir, entry.previousPath), { force: true });
12547
+ await fs17.rm(path21.join(paths.wikiDir, entry.previousPath), { force: true });
12228
12548
  }
12229
12549
  if (deletedPage?.kind === "output") {
12230
- await fs16.rm(path20.join(paths.wikiDir, "outputs", "assets", path20.basename(deletedPage.path, ".md")), {
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 fs16.readFile(path20.join(paths.wikiDir, candidate.path), "utf8");
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 = path20.join(paths.wikiDir, nextPath);
12334
- await ensureDir(path20.dirname(nextAbsolutePath));
12335
- await fs16.writeFile(nextAbsolutePath, nextContent, "utf8");
12336
- await fs16.rm(path20.join(paths.wikiDir, candidate.path), { force: true });
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 fs16.rm(path20.join(paths.wikiDir, candidate.path), { force: true });
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 = path20.join(rootDir, ".obsidian");
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(path20.join(obsidianDir, "app.json"), {
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(path20.join(obsidianDir, "core-plugins.json"), [
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(path20.join(obsidianDir, "graph.json"), {
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(path20.join(obsidianDir, "workspace.json"), {
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 = path20.join(paths.wikiDir, "insights", "index.md");
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
- path20.join(paths.wikiDir, "projects", "index.md"),
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
- path20.join(paths.wikiDir, "candidates", "index.md"),
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(path20.join(paths.analysesDir, `${manifest.sourceId}.json`));
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(path20.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
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(path20.join(paths.wikiDir, "sources")),
12693
- ensureDir(path20.join(paths.wikiDir, "code")),
12694
- ensureDir(path20.join(paths.wikiDir, "concepts")),
12695
- ensureDir(path20.join(paths.wikiDir, "entities")),
12696
- ensureDir(path20.join(paths.wikiDir, "outputs")),
12697
- ensureDir(path20.join(paths.wikiDir, "projects")),
12698
- ensureDir(path20.join(paths.wikiDir, "insights")),
12699
- ensureDir(path20.join(paths.wikiDir, "candidates")),
12700
- ensureDir(path20.join(paths.wikiDir, "candidates", "concepts")),
12701
- ensureDir(path20.join(paths.wikiDir, "candidates", "entities"))
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 = path20.join(approval.approvalDir, "wiki", staged.page.path);
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 = path20.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
13424
+ result.stagedPath = path21.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
13105
13425
  });
13106
- stagedHubPath = path20.join(approval.approvalDir, "wiki", hubPage.path);
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 = path20.join(paths.wikiDir, page.path);
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 fs16.readFile(absolutePath, "utf8"));
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(path20.join(paths.wikiDir, "graph", "report.json"));
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 = path20.resolve(paths.wikiDir, relativePath);
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 fs16.readFile(absolutePath, "utf8");
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 : path20.basename(relativePath, path20.extname(relativePath)),
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: path20.join(paths.wikiDir, page.path),
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: path20.join(paths.wikiDir, page.path),
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: path20.join(paths.wikiDir, page.path),
13641
+ pagePath: path21.join(paths.wikiDir, page.path),
13322
13642
  relatedPageIds: [page.id]
13323
13643
  });
13324
13644
  }
13325
- const absolutePath = path20.join(paths.wikiDir, page.path);
13645
+ const absolutePath = path21.join(paths.wikiDir, page.path);
13326
13646
  if (await fileExists(absolutePath)) {
13327
- const content = await fs16.readFile(absolutePath, "utf8");
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.29";
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(path21.relative(paths.sessionsDir, filePath))).sort();
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 = path21.resolve(paths.wikiDir, relativePath);
13712
- return asTextResource(`swarmvault://pages/${encodedPath}`, await fs17.readFile(absolutePath, "utf8"));
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(path21.relative(paths.sessionsDir, filePath))).sort();
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: path21.basename(relativePath, ".md"),
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 = path21.resolve(paths.sessionsDir, relativePath);
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 fs17.readFile(absolutePath, "utf8"));
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 fs18 from "fs/promises";
13794
- import path22 from "path";
14113
+ import fs19 from "fs/promises";
14114
+ import path23 from "path";
13795
14115
  function scheduleStatePath(schedulesDir, jobId) {
13796
- return path22.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
14116
+ return path23.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
13797
14117
  }
13798
14118
  function scheduleLockPath(schedulesDir, jobId) {
13799
- return path22.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
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 fs18.open(leasePath, "wx");
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 fs18.rm(leasePath, { force: true });
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 fs19 from "fs/promises";
14376
+ import fs20 from "fs/promises";
14057
14377
  import http from "http";
14058
- import path24 from "path";
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 path23 from "path";
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 = path23.relative(rootPath, targetPath);
14073
- return relative === "" || !relative.startsWith("..") && !path23.isAbsolute(relative);
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 = path23.relative(baseDir, targetPath);
14396
+ const relativePath = path24.relative(baseDir, targetPath);
14077
14397
  if (!relativePath || relativePath.startsWith("..")) {
14078
14398
  return false;
14079
14399
  }
14080
- return relativePath.split(path23.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
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
- path23.join(rootDir, ".claude"),
14090
- path23.join(rootDir, ".cursor"),
14091
- path23.join(rootDir, ".obsidian")
14092
- ].map((candidate) => path23.resolve(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([path23.resolve(paths.inboxDir)]);
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(path23.resolve(repoRoot));
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 = path23.resolve(paths.inboxDir);
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 = path23.resolve(targetPath);
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, path23.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
14427
- return path23.relative(baseDir, targetPath) || ".";
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 = path24.resolve(paths.wikiDir, relativePath);
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 fs19.readFile(absolutePath, "utf8");
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 : path24.basename(relativePath, path24.extname(relativePath)),
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 = path24.resolve(paths.wikiDir, relativePath);
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 fs19.readFile(absolutePath),
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 = path24.join(viewerDistDir, "index.html");
14832
+ const indexPath = path25.join(viewerDistDir, "index.html");
14513
14833
  if (await fileExists(indexPath)) {
14514
14834
  return;
14515
14835
  }
14516
- const viewerProjectDir = path24.dirname(viewerDistDir);
14517
- if (await fileExists(path24.join(viewerProjectDir, "package.json"))) {
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 fs19.readFile(paths.graphPath, "utf8"));
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 = path24.join(paths.wikiDir, "graph", "report.json");
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 fs19.readFile(reportPath, "utf8"));
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 = path24.join(paths.viewerDistDir, relativePath);
14682
- const fallback = path24.join(paths.viewerDistDir, "index.html");
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 fs19.readFile(filePath));
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 = path24.join(paths.viewerDistDir, "index.html");
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 fs19.readFile(indexPath, "utf8");
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] ? path24.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
14747
- const stylePath = styleMatch?.[1] ? path24.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
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 fs19.readFile(scriptPath, "utf8");
14752
- const style = stylePath && await fileExists(stylePath) ? await fs19.readFile(stylePath, "utf8") : "";
14753
- const report = await readJsonFile(path24.join(paths.wikiDir, "graph", "report.json"));
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 fs19.mkdir(path24.dirname(outputPath), { recursive: true });
14773
- await fs19.writeFile(outputPath, html, "utf8");
14774
- return path24.resolve(outputPath);
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,