@swarmvaultai/engine 0.1.28 → 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-EXD4RWT3.js";
24
+ } from "./chunk-IAEYFTUS.js";
25
25
 
26
26
  // src/agents.ts
27
27
  import crypto from "crypto";
@@ -32,8 +32,8 @@ var managedStart = "<!-- swarmvault:managed:start -->";
32
32
  var managedEnd = "<!-- swarmvault:managed:end -->";
33
33
  var legacyManagedStart = "<!-- vault:managed:start -->";
34
34
  var legacyManagedEnd = "<!-- vault:managed:end -->";
35
- var claudeHookMatcher = "Glob|Grep";
36
- var claudeHookCommand = "if [ -f wiki/graph/report.md ]; then echo 'swarmvault: Graph report exists. Read wiki/graph/report.md before broad raw-file searching.'; fi";
35
+ var claudeSearchMatcher = "Glob|Grep";
36
+ var claudeSessionMatchers = ["startup", "resume", "clear", "compact"];
37
37
  var geminiSessionMatcher = "startup";
38
38
  var geminiSearchMatcher = "glob|grep|search|find";
39
39
  var copilotHookVersion = 1;
@@ -89,6 +89,8 @@ function primaryTargetPathForAgent(rootDir, agent) {
89
89
  }
90
90
  function hookScriptPathForAgent(rootDir, agent) {
91
91
  switch (agent) {
92
+ case "claude":
93
+ return path.join(rootDir, ".claude", "hooks", "swarmvault-graph-first.js");
92
94
  case "opencode":
93
95
  return path.join(rootDir, ".opencode", "plugins", "swarmvault-graph-first.js");
94
96
  case "gemini":
@@ -177,25 +179,106 @@ async function readJsonWithWarnings(filePath, fallback, label) {
177
179
  }
178
180
  async function installClaudeHook(rootDir) {
179
181
  const settingsPath = path.join(rootDir, ".claude", "settings.json");
182
+ const scriptPath = path.join(rootDir, ".claude", "hooks", "swarmvault-graph-first.js");
183
+ await writeOwnedFile(scriptPath, buildClaudeHookScript(), true);
180
184
  await ensureDir(path.dirname(settingsPath));
181
185
  const { data: settings, warnings } = await readJsonWithWarnings(settingsPath, {}, ".claude/settings.json");
182
186
  if (warnings.length > 0 && await fileExists(settingsPath)) {
183
187
  return { path: settingsPath, warnings };
184
188
  }
185
189
  const hooks = settings.hooks ?? {};
190
+ const sessionStart = hooks.SessionStart ?? [];
186
191
  const preToolUse = hooks.PreToolUse ?? [];
187
- const exists = preToolUse.some((entry) => entry.matcher === claudeHookMatcher && JSON.stringify(entry).includes("swarmvault:"));
188
- if (!exists) {
192
+ const sessionCommand = 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/swarmvault-graph-first.js" session-start';
193
+ const preToolUseCommand = 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/swarmvault-graph-first.js" pre-tool-use';
194
+ for (const matcher of claudeSessionMatchers) {
195
+ if (!sessionStart.some((entry) => entry.matcher === matcher && JSON.stringify(entry).includes("swarmvault-graph-first.js"))) {
196
+ sessionStart.push({
197
+ matcher,
198
+ hooks: [{ type: "command", command: sessionCommand }]
199
+ });
200
+ }
201
+ }
202
+ if (!preToolUse.some((entry) => entry.matcher === claudeSearchMatcher && JSON.stringify(entry).includes("swarmvault-graph-first.js"))) {
189
203
  preToolUse.push({
190
- matcher: claudeHookMatcher,
191
- hooks: [{ type: "command", command: claudeHookCommand }]
204
+ matcher: claudeSearchMatcher,
205
+ hooks: [{ type: "command", command: preToolUseCommand }]
192
206
  });
193
207
  }
194
- settings.hooks = { ...hooks, PreToolUse: preToolUse };
208
+ settings.hooks = { ...hooks, SessionStart: sessionStart, PreToolUse: preToolUse };
195
209
  await fs.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}
196
210
  `, "utf8");
197
211
  return { path: settingsPath, warnings: [] };
198
212
  }
213
+ function buildClaudeHookScript() {
214
+ return `#!/usr/bin/env node
215
+ import crypto from "node:crypto";
216
+ import fs from "node:fs/promises";
217
+ import os from "node:os";
218
+ import path from "node:path";
219
+
220
+ ${markerStateSnippet("claude").trim()}
221
+
222
+ async function readInput() {
223
+ let body = "";
224
+ for await (const chunk of process.stdin) {
225
+ body += chunk;
226
+ }
227
+ if (!body.trim()) {
228
+ return {};
229
+ }
230
+ try {
231
+ return JSON.parse(body);
232
+ } catch {
233
+ return {};
234
+ }
235
+ }
236
+
237
+ function emit(value) {
238
+ process.stdout.write(\`\${JSON.stringify(value)}\\n\`);
239
+ }
240
+
241
+ const mode = process.argv[2] ?? "";
242
+ const input = await readInput();
243
+ const cwd = resolveInputCwd(input);
244
+ const reportNote = "SwarmVault graph report exists at wiki/graph/report.md. Read it before broad grep/glob searching.";
245
+
246
+ if (!(await hasReport(cwd))) {
247
+ emit({});
248
+ process.exit(0);
249
+ }
250
+
251
+ if (mode === "session-start") {
252
+ await resetSession(cwd);
253
+ emit({
254
+ hookSpecificOutput: {
255
+ hookEventName: "SessionStart",
256
+ additionalContext: reportNote
257
+ }
258
+ });
259
+ process.exit(0);
260
+ }
261
+
262
+ const toolName = resolveToolName(input);
263
+ if (collectCandidatePaths(input).some((value) => isReportPath(value, cwd))) {
264
+ await markReportRead(cwd);
265
+ emit({});
266
+ process.exit(0);
267
+ }
268
+
269
+ if (isBroadSearchTool(toolName) && !(await hasSeenReport(cwd))) {
270
+ emit({
271
+ hookSpecificOutput: {
272
+ hookEventName: "PreToolUse",
273
+ additionalContext: reportNote
274
+ }
275
+ });
276
+ process.exit(0);
277
+ }
278
+
279
+ emit({});
280
+ `;
281
+ }
199
282
  function markerStateSnippet(agentKey) {
200
283
  return `
201
284
  function markerState(cwd) {
@@ -237,7 +320,9 @@ function collectCandidatePaths(node, acc = []) {
237
320
  for (const [key, value] of Object.entries(node)) {
238
321
  if (["path", "filePath", "file_path", "paths", "target", "targets"].includes(key)) {
239
322
  collectCandidatePaths(value, acc);
323
+ continue;
240
324
  }
325
+ collectCandidatePaths(value, acc);
241
326
  }
242
327
  return acc;
243
328
  }
@@ -663,32 +748,151 @@ async function installConfiguredAgents(rootDir) {
663
748
  // src/graph-export.ts
664
749
  import fs2 from "fs/promises";
665
750
  import path2 from "path";
666
- var NODE_COLORS = {
667
- source: "#f59e0b",
668
- module: "#fb7185",
669
- symbol: "#8b5cf6",
670
- rationale: "#14b8a6",
671
- concept: "#0ea5e9",
672
- entity: "#22c55e"
673
- };
674
- function xmlEscape(value) {
675
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
676
- }
677
- function cypherEscape(value) {
678
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
751
+
752
+ // src/graph-interchange.ts
753
+ function exportHyperedgeNodeId(hyperedge) {
754
+ return `hyperedge:${hyperedge.id}`;
679
755
  }
680
756
  function relationType(relation) {
681
757
  const normalized = relation.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
682
758
  return normalized || "RELATED_TO";
683
759
  }
760
+ function cypherStringLiteral(value) {
761
+ let escaped = "";
762
+ for (const char of value) {
763
+ switch (char) {
764
+ case "\\":
765
+ escaped += "\\\\";
766
+ break;
767
+ case "'":
768
+ escaped += "\\'";
769
+ break;
770
+ case "\n":
771
+ escaped += "\\n";
772
+ break;
773
+ case "\r":
774
+ escaped += "\\r";
775
+ break;
776
+ case " ":
777
+ escaped += "\\t";
778
+ break;
779
+ case "\b":
780
+ escaped += "\\b";
781
+ break;
782
+ case "\f":
783
+ escaped += "\\f";
784
+ break;
785
+ default: {
786
+ const code = char.codePointAt(0) ?? 0;
787
+ escaped += code < 32 || code === 8232 || code === 8233 ? `\\u${code.toString(16).padStart(4, "0")}` : char;
788
+ }
789
+ }
790
+ }
791
+ return `'${escaped}'`;
792
+ }
684
793
  function graphPageById(graph) {
685
794
  return new Map(graph.pages.map((page) => [page.id, page]));
686
795
  }
687
796
  function graphNodeById(graph) {
688
797
  return new Map(graph.nodes.map((node) => [node.id, node]));
689
798
  }
690
- function exportHyperedgeNodeId(hyperedge) {
691
- 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;");
692
896
  }
693
897
  function sortedCommunities(graph) {
694
898
  const known = (graph.communities ?? []).map((community) => ({
@@ -950,35 +1154,14 @@ function renderCypher(graph) {
950
1154
  const lines = ["// Neo4j Cypher import generated by SwarmVault", ""];
951
1155
  for (const node of [...graph.nodes].sort((left, right) => left.id.localeCompare(right.id))) {
952
1156
  const page = node.pageId ? pageById2.get(node.pageId) : void 0;
953
- const props = [
954
- `id: '${cypherEscape(node.id)}'`,
955
- `label: '${cypherEscape(node.label)}'`,
956
- `type: '${cypherEscape(node.type)}'`,
957
- `sourceIds: '${cypherEscape(JSON.stringify(node.sourceIds))}'`,
958
- `projectIds: '${cypherEscape(JSON.stringify(node.projectIds))}'`,
959
- node.pageId ? `pageId: '${cypherEscape(node.pageId)}'` : "",
960
- page?.path ? `pagePath: '${cypherEscape(page.path)}'` : "",
961
- node.language ? `language: '${cypherEscape(node.language)}'` : "",
962
- node.symbolKind ? `symbolKind: '${cypherEscape(node.symbolKind)}'` : "",
963
- node.communityId ? `communityId: '${cypherEscape(node.communityId)}'` : "",
964
- node.degree !== void 0 ? `degree: ${node.degree}` : "",
965
- node.bridgeScore !== void 0 ? `bridgeScore: ${node.bridgeScore}` : "",
966
- node.isGodNode !== void 0 ? `isGodNode: ${node.isGodNode}` : ""
967
- ].filter(Boolean).join(", ");
968
- lines.push(`MERGE (n:SwarmNode {id: '${cypherEscape(node.id)}'}) SET n += { ${props} };`);
1157
+ const props = Object.entries(normalizeSwarmNodeProps(node, page)).map(([key, value]) => `${key}: ${typeof value === "string" ? cypherStringLiteral(value) : value}`).filter(Boolean).join(", ");
1158
+ lines.push(`MERGE (n:SwarmNode {id: ${cypherStringLiteral(node.id)}}) SET n += { ${props} };`);
969
1159
  }
970
1160
  lines.push("");
971
1161
  for (const hyperedge of [...graph.hyperedges ?? []].sort((left, right) => left.id.localeCompare(right.id))) {
972
1162
  const hyperedgeNodeId = exportHyperedgeNodeId(hyperedge);
973
- lines.push(
974
- `MERGE (h:SwarmNode {id: '${cypherEscape(hyperedgeNodeId)}'}) SET h += { id: '${cypherEscape(hyperedgeNodeId)}', label: '${cypherEscape(
975
- hyperedge.label
976
- )}', type: 'hyperedge', relation: '${cypherEscape(hyperedge.relation)}', evidenceClass: '${cypherEscape(
977
- hyperedge.evidenceClass
978
- )}', confidence: ${hyperedge.confidence}, sourcePageIds: '${cypherEscape(JSON.stringify(hyperedge.sourcePageIds))}', why: '${cypherEscape(
979
- hyperedge.why
980
- )}' };`
981
- );
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} };`);
982
1165
  }
983
1166
  if ((graph.hyperedges ?? []).length) {
984
1167
  lines.push("");
@@ -986,23 +1169,21 @@ function renderCypher(graph) {
986
1169
  for (const hyperedge of [...graph.hyperedges ?? []].sort((left, right) => left.id.localeCompare(right.id))) {
987
1170
  const hyperedgeNodeId = exportHyperedgeNodeId(hyperedge);
988
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(", ");
989
1173
  lines.push(
990
- `MATCH (h:SwarmNode {id: '${cypherEscape(hyperedgeNodeId)}'}), (n:SwarmNode {id: '${cypherEscape(nodeId)}'})`,
991
- `MERGE (h)-[r:GROUP_MEMBER {id: '${cypherEscape(`member:${hyperedge.id}:${nodeId}`)}'}]->(n)`,
992
- `SET r += { relation: 'group_member', status: 'inferred', evidenceClass: '${cypherEscape(
993
- hyperedge.evidenceClass
994
- )}', confidence: ${hyperedge.confidence}, provenance: '${cypherEscape(JSON.stringify(hyperedge.sourcePageIds))}' };`
1174
+ `MATCH (h:SwarmNode {id: ${cypherStringLiteral(hyperedgeNodeId)}}), (n:SwarmNode {id: ${cypherStringLiteral(nodeId)}})`,
1175
+ `MERGE (h)-[r:GROUP_MEMBER {id: ${cypherStringLiteral(`member:${hyperedge.id}:${nodeId}`)}}]->(n)`,
1176
+ `SET r += { ${props} };`
995
1177
  );
996
1178
  }
997
1179
  }
998
1180
  lines.push("");
999
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(", ");
1000
1183
  lines.push(
1001
- `MATCH (a:SwarmNode {id: '${cypherEscape(edge.source)}'}), (b:SwarmNode {id: '${cypherEscape(edge.target)}'})`,
1002
- `MERGE (a)-[r:${relationType(edge.relation)} {id: '${cypherEscape(edge.id)}'}]->(b)`,
1003
- `SET r += { relation: '${cypherEscape(edge.relation)}', status: '${cypherEscape(edge.status)}', evidenceClass: '${cypherEscape(
1004
- edge.evidenceClass
1005
- )}', confidence: ${edge.confidence}, provenance: '${cypherEscape(JSON.stringify(edge.provenance))}'${edge.similarityReasons?.length ? `, similarityReasons: '${cypherEscape(JSON.stringify(edge.similarityReasons))}'` : ""} };`
1184
+ `MATCH (a:SwarmNode {id: ${cypherStringLiteral(edge.source)}}), (b:SwarmNode {id: ${cypherStringLiteral(edge.target)}})`,
1185
+ `MERGE (a)-[r:${relationType(edge.relation)} {id: ${cypherStringLiteral(edge.id)}}]->(b)`,
1186
+ `SET r += { ${props} };`
1006
1187
  );
1007
1188
  }
1008
1189
  lines.push("");
@@ -1028,104 +1209,477 @@ async function exportGraphFormat(rootDir, format, outputPath) {
1028
1209
  return { format, outputPath: resolvedPath };
1029
1210
  }
1030
1211
 
1031
- // src/hooks.ts
1212
+ // src/graph-push.ts
1032
1213
  import fs3 from "fs/promises";
1033
1214
  import path3 from "path";
1034
- import process2 from "process";
1035
- var hookStart = "# >>> swarmvault hook >>>";
1036
- var hookEnd = "# <<< swarmvault hook <<<";
1037
- async function findNearestGitRoot(startPath) {
1038
- let current = path3.resolve(startPath);
1039
- try {
1040
- const stat = await fs3.stat(current);
1041
- if (!stat.isDirectory()) {
1042
- current = path3.dirname(current);
1043
- }
1044
- } catch {
1045
- current = path3.dirname(current);
1046
- }
1047
- while (true) {
1048
- if (await fileExists(path3.join(current, ".git"))) {
1049
- return current;
1050
- }
1051
- const parent = path3.dirname(current);
1052
- if (parent === current) {
1053
- return null;
1054
- }
1055
- current = parent;
1056
- }
1057
- }
1058
- function shellQuote(value) {
1059
- return `'${value.replace(/'/g, `'"'"'`)}'`;
1060
- }
1061
- function resolveSwarmvaultExecutableCandidate() {
1062
- const argvPath = process2.argv[1];
1063
- 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}`))) {
1064
- return path3.resolve(argvPath);
1065
- }
1066
- return "swarmvault";
1067
- }
1068
- function managedHookBlock(vaultRoot) {
1069
- const resolvedExecutable = resolveSwarmvaultExecutableCandidate();
1070
- return [
1071
- hookStart,
1072
- `cd ${shellQuote(vaultRoot)} || exit 0`,
1073
- `swarmvault_bin=${shellQuote(resolvedExecutable)}`,
1074
- '[ ! -x "$swarmvault_bin" ] && swarmvault_bin=$(command -v swarmvault 2>/dev/null || true)',
1075
- 'if [ -n "$swarmvault_bin" ] && [ -x "$swarmvault_bin" ]; then',
1076
- ` "$swarmvault_bin" watch --repo --once >/dev/null 2>&1 || printf '[swarmvault hook] refresh failed\\n' >&2`,
1077
- "fi",
1078
- hookEnd,
1079
- ""
1080
- ].join("\n");
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]));
1081
1229
  }
1082
- function hookPath(repoRoot, hookName) {
1083
- return path3.join(repoRoot, ".git", "hooks", hookName);
1230
+ function pageMap(graph) {
1231
+ return new Map(graph.pages.map((page) => [page.id, page]));
1084
1232
  }
1085
- async function readHookStatus(filePath) {
1086
- if (!await fileExists(filePath)) {
1087
- return "not_installed";
1088
- }
1089
- const content = await fs3.readFile(filePath, "utf8");
1090
- return content.includes(hookStart) && content.includes(hookEnd) ? "installed" : "other_content";
1233
+ function estimateTokens(text) {
1234
+ return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));
1091
1235
  }
1092
- async function upsertHookFile(filePath, block) {
1093
- const existing = await fileExists(filePath) ? await fs3.readFile(filePath, "utf8") : "";
1094
- let next;
1095
- const startIndex = existing.indexOf(hookStart);
1096
- const endIndex = existing.indexOf(hookEnd);
1097
- if (startIndex !== -1 && endIndex !== -1) {
1098
- next = `${existing.slice(0, startIndex)}${block}${existing.slice(endIndex + hookEnd.length)}`.trimEnd();
1099
- } else if (existing.trim().length > 0) {
1100
- next = `${existing.trimEnd()}
1101
-
1102
- ${block}`.trimEnd();
1103
- } else {
1104
- next = `#!/bin/sh
1105
- ${block}`.trimEnd();
1106
- }
1107
- await ensureDir(path3.dirname(filePath));
1108
- await fs3.writeFile(filePath, `${next}
1109
- `, { mode: 493, encoding: "utf8" });
1110
- await fs3.chmod(filePath, 493);
1236
+ function estimateCorpusWords(texts) {
1237
+ return texts.reduce((total, text) => total + normalizeWhitespace(text).split(/\s+/).filter(Boolean).length, 0);
1111
1238
  }
1112
- async function removeHookBlock(filePath) {
1113
- if (!await fileExists(filePath)) {
1114
- return;
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
+ }
1115
1254
  }
1116
- const existing = await fs3.readFile(filePath, "utf8");
1117
- const startIndex = existing.indexOf(hookStart);
1118
- const endIndex = existing.indexOf(hookEnd);
1119
- if (startIndex === -1 || endIndex === -1) {
1120
- return;
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"}`);
1121
1261
  }
1122
- const next = `${existing.slice(0, startIndex)}${existing.slice(endIndex + hookEnd.length)}`.trim();
1123
- if (!next || next === "#!/bin/sh") {
1124
- await fs3.rm(filePath, { force: true });
1125
- return;
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}`);
1126
1269
  }
1127
- await fs3.writeFile(filePath, `${next}
1128
- `, "utf8");
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";
1588
+ import process2 from "process";
1589
+ var hookStart = "# >>> swarmvault hook >>>";
1590
+ var hookEnd = "# <<< swarmvault hook <<<";
1591
+ async function findNearestGitRoot(startPath) {
1592
+ let current = path4.resolve(startPath);
1593
+ try {
1594
+ const stat = await fs4.stat(current);
1595
+ if (!stat.isDirectory()) {
1596
+ current = path4.dirname(current);
1597
+ }
1598
+ } catch {
1599
+ current = path4.dirname(current);
1600
+ }
1601
+ while (true) {
1602
+ if (await fileExists(path4.join(current, ".git"))) {
1603
+ return current;
1604
+ }
1605
+ const parent = path4.dirname(current);
1606
+ if (parent === current) {
1607
+ return null;
1608
+ }
1609
+ current = parent;
1610
+ }
1611
+ }
1612
+ function shellQuote(value) {
1613
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
1614
+ }
1615
+ function resolveSwarmvaultExecutableCandidate() {
1616
+ const argvPath = process2.argv[1];
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);
1619
+ }
1620
+ return "swarmvault";
1621
+ }
1622
+ function managedHookBlock(vaultRoot) {
1623
+ const resolvedExecutable = resolveSwarmvaultExecutableCandidate();
1624
+ return [
1625
+ hookStart,
1626
+ `cd ${shellQuote(vaultRoot)} || exit 0`,
1627
+ `swarmvault_bin=${shellQuote(resolvedExecutable)}`,
1628
+ '[ ! -x "$swarmvault_bin" ] && swarmvault_bin=$(command -v swarmvault 2>/dev/null || true)',
1629
+ 'if [ -n "$swarmvault_bin" ] && [ -x "$swarmvault_bin" ]; then',
1630
+ ` "$swarmvault_bin" watch --repo --once >/dev/null 2>&1 || printf '[swarmvault hook] refresh failed\\n' >&2`,
1631
+ "fi",
1632
+ hookEnd,
1633
+ ""
1634
+ ].join("\n");
1635
+ }
1636
+ function hookPath(repoRoot, hookName) {
1637
+ return path4.join(repoRoot, ".git", "hooks", hookName);
1638
+ }
1639
+ async function readHookStatus(filePath) {
1640
+ if (!await fileExists(filePath)) {
1641
+ return "not_installed";
1642
+ }
1643
+ const content = await fs4.readFile(filePath, "utf8");
1644
+ return content.includes(hookStart) && content.includes(hookEnd) ? "installed" : "other_content";
1645
+ }
1646
+ async function upsertHookFile(filePath, block) {
1647
+ const existing = await fileExists(filePath) ? await fs4.readFile(filePath, "utf8") : "";
1648
+ let next;
1649
+ const startIndex = existing.indexOf(hookStart);
1650
+ const endIndex = existing.indexOf(hookEnd);
1651
+ if (startIndex !== -1 && endIndex !== -1) {
1652
+ next = `${existing.slice(0, startIndex)}${block}${existing.slice(endIndex + hookEnd.length)}`.trimEnd();
1653
+ } else if (existing.trim().length > 0) {
1654
+ next = `${existing.trimEnd()}
1655
+
1656
+ ${block}`.trimEnd();
1657
+ } else {
1658
+ next = `#!/bin/sh
1659
+ ${block}`.trimEnd();
1660
+ }
1661
+ await ensureDir(path4.dirname(filePath));
1662
+ await fs4.writeFile(filePath, `${next}
1663
+ `, { mode: 493, encoding: "utf8" });
1664
+ await fs4.chmod(filePath, 493);
1665
+ }
1666
+ async function removeHookBlock(filePath) {
1667
+ if (!await fileExists(filePath)) {
1668
+ return;
1669
+ }
1670
+ const existing = await fs4.readFile(filePath, "utf8");
1671
+ const startIndex = existing.indexOf(hookStart);
1672
+ const endIndex = existing.indexOf(hookEnd);
1673
+ if (startIndex === -1 || endIndex === -1) {
1674
+ return;
1675
+ }
1676
+ const next = `${existing.slice(0, startIndex)}${existing.slice(endIndex + hookEnd.length)}`.trim();
1677
+ if (!next || next === "#!/bin/sh") {
1678
+ await fs4.rm(filePath, { force: true });
1679
+ return;
1680
+ }
1681
+ await fs4.writeFile(filePath, `${next}
1682
+ `, "utf8");
1129
1683
  }
1130
1684
  async function getGitHookStatus(rootDir) {
1131
1685
  const repoRoot = await findNearestGitRoot(rootDir);
@@ -1147,7 +1701,7 @@ async function installGitHooks(rootDir) {
1147
1701
  if (!repoRoot) {
1148
1702
  throw new Error("No git repository found above the current vault.");
1149
1703
  }
1150
- const block = managedHookBlock(path3.resolve(rootDir));
1704
+ const block = managedHookBlock(path4.resolve(rootDir));
1151
1705
  await upsertHookFile(hookPath(repoRoot, "post-commit"), block);
1152
1706
  await upsertHookFile(hookPath(repoRoot, "post-checkout"), block);
1153
1707
  return getGitHookStatus(rootDir);
@@ -1167,27 +1721,27 @@ async function uninstallGitHooks(rootDir) {
1167
1721
  }
1168
1722
 
1169
1723
  // src/ingest.ts
1170
- import fs9 from "fs/promises";
1171
- import path10 from "path";
1724
+ import fs10 from "fs/promises";
1725
+ import path11 from "path";
1172
1726
  import { pathToFileURL } from "url";
1173
1727
  import { Readability } from "@mozilla/readability";
1174
1728
  import matter3 from "gray-matter";
1175
1729
  import ignore from "ignore";
1176
- import { JSDOM } from "jsdom";
1730
+ import { JSDOM as JSDOM2 } from "jsdom";
1177
1731
  import mime from "mime-types";
1178
1732
  import TurndownService from "turndown";
1179
1733
 
1180
1734
  // src/code-analysis.ts
1181
- import fs5 from "fs/promises";
1182
- import path5 from "path";
1735
+ import fs6 from "fs/promises";
1736
+ import path6 from "path";
1183
1737
  import ts from "typescript";
1184
1738
 
1185
1739
  // src/code-tree-sitter.ts
1186
- import fs4 from "fs/promises";
1740
+ import fs5 from "fs/promises";
1187
1741
  import { createRequire } from "module";
1188
- import path4 from "path";
1742
+ import path5 from "path";
1189
1743
  var require2 = createRequire(import.meta.url);
1190
- 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")));
1191
1745
  var RATIONALE_MARKERS = ["NOTE:", "IMPORTANT:", "HACK:", "WHY:", "RATIONALE:"];
1192
1746
  function stripKnownCommentPrefix(line) {
1193
1747
  let next = line.trim();
@@ -1224,7 +1778,7 @@ async function getTreeSitterModule() {
1224
1778
  async function ensureTreeSitterInit(module) {
1225
1779
  if (!treeSitterInitPromise) {
1226
1780
  treeSitterInitPromise = module.Parser.init({
1227
- locateFile: () => path4.join(TREE_SITTER_PACKAGE_ROOT, "wasm", "tree-sitter.wasm")
1781
+ locateFile: () => path5.join(TREE_SITTER_PACKAGE_ROOT, "wasm", "tree-sitter.wasm")
1228
1782
  });
1229
1783
  }
1230
1784
  return treeSitterInitPromise;
@@ -1237,7 +1791,7 @@ async function loadLanguage(language) {
1237
1791
  const loader = (async () => {
1238
1792
  const module = await getTreeSitterModule();
1239
1793
  await ensureTreeSitterInit(module);
1240
- 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]));
1241
1795
  return module.Language.load(bytes);
1242
1796
  })();
1243
1797
  languageCache.set(language, loader);
@@ -1254,16 +1808,16 @@ function stripCodeExtension(filePath) {
1254
1808
  return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
1255
1809
  }
1256
1810
  function manifestModuleName(manifest, language) {
1257
- const repoPath = manifest.repoRelativePath ?? path4.basename(manifest.originalPath ?? manifest.storedPath);
1811
+ const repoPath = manifest.repoRelativePath ?? path5.basename(manifest.originalPath ?? manifest.storedPath);
1258
1812
  const normalized = toPosix(stripCodeExtension(repoPath)).replace(/^\.\/+/, "");
1259
1813
  if (!normalized) {
1260
1814
  return void 0;
1261
1815
  }
1262
1816
  if (language === "python") {
1263
1817
  const dotted = normalized.replace(/\/__init__$/i, "").replace(/\//g, ".").replace(/^src\./, "");
1264
- return dotted || path4.posix.basename(normalized);
1818
+ return dotted || path5.posix.basename(normalized);
1265
1819
  }
1266
- 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;
1267
1821
  }
1268
1822
  function singleLineSignature(value) {
1269
1823
  return truncate(
@@ -1298,6 +1852,55 @@ function collectCallNamesFromText(text, availableNames, selfName) {
1298
1852
  }
1299
1853
  return uniqueBy(names, (name) => name);
1300
1854
  }
1855
+ function goReceiverBinding(node) {
1856
+ if (!node) {
1857
+ return {};
1858
+ }
1859
+ const names = uniqueBy(
1860
+ node.descendantsOfType(["identifier", "type_identifier"]).filter((item) => item !== null).map((item) => normalizeSymbolReference(item.text)).filter(Boolean),
1861
+ (item) => item
1862
+ );
1863
+ if (names.length === 0) {
1864
+ return {};
1865
+ }
1866
+ if (names.length === 1) {
1867
+ return { typeName: names[0] };
1868
+ }
1869
+ return {
1870
+ variableName: names[0],
1871
+ typeName: names.at(-1)
1872
+ };
1873
+ }
1874
+ function goCalledSymbolName(node, receiver) {
1875
+ if (!node) {
1876
+ return void 0;
1877
+ }
1878
+ if (node.type === "selector_expression") {
1879
+ const targetName = normalizeSymbolReference(
1880
+ extractIdentifier(node.childForFieldName("operand") ?? node.childForFieldName("object") ?? node.namedChildren.at(0) ?? null) ?? ""
1881
+ );
1882
+ const fieldName = normalizeSymbolReference(
1883
+ extractIdentifier(node.childForFieldName("field") ?? findNamedChild(node, "field_identifier") ?? node.namedChildren.at(-1) ?? null) ?? ""
1884
+ );
1885
+ if (!fieldName) {
1886
+ return void 0;
1887
+ }
1888
+ if (receiver.variableName && receiver.typeName && targetName === receiver.variableName) {
1889
+ return `${receiver.typeName}.${fieldName}`;
1890
+ }
1891
+ return fieldName;
1892
+ }
1893
+ return normalizeSymbolReference(extractIdentifier(node) ?? "");
1894
+ }
1895
+ function goCallNamesFromBody(bodyNode, receiver) {
1896
+ if (!bodyNode) {
1897
+ return [];
1898
+ }
1899
+ return uniqueBy(
1900
+ bodyNode.descendantsOfType("call_expression").filter((item) => item !== null).map((callNode) => goCalledSymbolName(callNode.childForFieldName("function") ?? callNode.namedChildren.at(0) ?? null, receiver)).filter((name) => Boolean(name)),
1901
+ (name) => name
1902
+ );
1903
+ }
1301
1904
  function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportLabels, diagnostics, metadata) {
1302
1905
  const topLevelNames = new Set(draftSymbols.map((symbol) => symbol.name));
1303
1906
  for (const symbol of draftSymbols) {
@@ -1784,7 +2387,9 @@ function goCodeAnalysis(manifest, rootNode, diagnostics) {
1784
2387
  if (!name) {
1785
2388
  continue;
1786
2389
  }
1787
- const receiverType = child.type === "method_declaration" ? normalizeSymbolReference(nodeText(child.childForFieldName("receiver")).replace(/[()]/g, " ").split(/\s+/).at(-1) ?? "") : "";
2390
+ const receiver = child.type === "method_declaration" ? goReceiverBinding(child.childForFieldName("receiver")) : {};
2391
+ const receiverType = receiver.typeName ?? "";
2392
+ const bodyNode = child.childForFieldName("body");
1788
2393
  const symbolName = receiverType ? `${receiverType}.${name}` : name;
1789
2394
  const exported = exportedByCapitalization(name);
1790
2395
  draftSymbols.push({
@@ -1792,10 +2397,10 @@ function goCodeAnalysis(manifest, rootNode, diagnostics) {
1792
2397
  kind: "function",
1793
2398
  signature: singleLineSignature(child.text),
1794
2399
  exported,
1795
- callNames: [],
2400
+ callNames: goCallNamesFromBody(bodyNode, receiver),
1796
2401
  extendsNames: [],
1797
2402
  implementsNames: [],
1798
- bodyText: nodeText(child.childForFieldName("body"))
2403
+ bodyText: nodeText(bodyNode)
1799
2404
  });
1800
2405
  if (exported) {
1801
2406
  exportLabels.push(symbolName);
@@ -2473,11 +3078,11 @@ function isNodeExported(node) {
2473
3078
  )
2474
3079
  );
2475
3080
  }
2476
- function makeSymbolId2(sourceId, name, seen) {
3081
+ function makeSymbolId2(scope, name, seen) {
2477
3082
  const base = slugify(name);
2478
3083
  const count = (seen.get(base) ?? 0) + 1;
2479
3084
  seen.set(base, count);
2480
- return `symbol:${sourceId}:${count === 1 ? base : `${base}-${count}`}`;
3085
+ return `symbol:${scope}:${count === 1 ? base : `${base}-${count}`}`;
2481
3086
  }
2482
3087
  function summarizeModule(manifest, code) {
2483
3088
  const localImports = code.imports.filter((item) => !item.isExternal && !item.reExport).length;
@@ -2617,16 +3222,16 @@ function stripCodeExtension2(filePath) {
2617
3222
  return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
2618
3223
  }
2619
3224
  function manifestModuleName2(manifest, language) {
2620
- const repoPath = manifest.repoRelativePath ?? path5.basename(manifest.originalPath ?? manifest.storedPath);
3225
+ const repoPath = manifest.repoRelativePath ?? path6.basename(manifest.originalPath ?? manifest.storedPath);
2621
3226
  const normalized = toPosix(stripCodeExtension2(repoPath)).replace(/^\.\/+/, "");
2622
3227
  if (!normalized) {
2623
3228
  return void 0;
2624
3229
  }
2625
3230
  if (language === "python") {
2626
3231
  const dotted = normalized.replace(/\/__init__$/i, "").replace(/\//g, ".").replace(/^src\./, "");
2627
- return dotted || path5.posix.basename(normalized);
3232
+ return dotted || path6.posix.basename(normalized);
2628
3233
  }
2629
- 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;
2630
3235
  }
2631
3236
  function finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, exportLabels, diagnostics, metadata) {
2632
3237
  const topLevelNames = new Set(draftSymbols.map((symbol) => symbol.name));
@@ -2636,8 +3241,9 @@ function finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, export
2636
3241
  }
2637
3242
  }
2638
3243
  const seenSymbolIds = /* @__PURE__ */ new Map();
3244
+ const symbolScope = metadata?.namespace ? `ns:${slugify(metadata.namespace)}` : manifest.sourceId;
2639
3245
  const symbols = draftSymbols.map((symbol) => ({
2640
- id: makeSymbolId2(manifest.sourceId, symbol.name, seenSymbolIds),
3246
+ id: makeSymbolId2(symbolScope, symbol.name, seenSymbolIds),
2641
3247
  name: symbol.name,
2642
3248
  kind: symbol.kind,
2643
3249
  signature: symbol.signature,
@@ -2919,7 +3525,7 @@ function analyzeTypeScriptLikeCode(manifest, content) {
2919
3525
  };
2920
3526
  }
2921
3527
  function inferCodeLanguage(filePath, mimeType = "") {
2922
- const extension = path5.extname(filePath).toLowerCase();
3528
+ const extension = path6.extname(filePath).toLowerCase();
2923
3529
  if (extension === ".ts" || extension === ".mts" || extension === ".cts") {
2924
3530
  return "typescript";
2925
3531
  }
@@ -2968,12 +3574,12 @@ function modulePageTitle(manifest) {
2968
3574
  return `${manifest.title} module`;
2969
3575
  }
2970
3576
  function importResolutionCandidates(basePath, specifier, extensions) {
2971
- const resolved = path5.posix.normalize(path5.posix.join(path5.posix.dirname(basePath), specifier));
2972
- 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)) {
2973
3579
  return [resolved];
2974
3580
  }
2975
- const direct = extensions.map((extension) => path5.posix.normalize(`${resolved}${extension}`));
2976
- 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}`)));
2977
3583
  return uniqueBy([resolved, ...direct, ...indexFiles], (candidate) => candidate);
2978
3584
  }
2979
3585
  function normalizeAlias(value) {
@@ -2992,32 +3598,32 @@ function recordAlias(target, value) {
2992
3598
  }
2993
3599
  function manifestBasenameWithoutExtension(manifest) {
2994
3600
  const target = manifest.repoRelativePath ?? manifest.originalPath ?? manifest.storedPath;
2995
- return path5.posix.basename(stripCodeExtension2(normalizeAlias(target)));
3601
+ return path6.posix.basename(stripCodeExtension2(normalizeAlias(target)));
2996
3602
  }
2997
3603
  async function readNearestGoModulePath(startPath, cache) {
2998
- let current = path5.resolve(startPath);
3604
+ let current = path6.resolve(startPath);
2999
3605
  try {
3000
- const stat = await fs5.stat(current);
3606
+ const stat = await fs6.stat(current);
3001
3607
  if (!stat.isDirectory()) {
3002
- current = path5.dirname(current);
3608
+ current = path6.dirname(current);
3003
3609
  }
3004
3610
  } catch {
3005
- current = path5.dirname(current);
3611
+ current = path6.dirname(current);
3006
3612
  }
3007
3613
  while (true) {
3008
3614
  if (cache.has(current)) {
3009
3615
  const cached = cache.get(current);
3010
3616
  return cached === null ? void 0 : cached;
3011
3617
  }
3012
- const goModPath = path5.join(current, "go.mod");
3013
- if (await fs5.access(goModPath).then(() => true).catch(() => false)) {
3014
- 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");
3015
3621
  const match = content.match(/^\s*module\s+(\S+)/m);
3016
3622
  const modulePath = match?.[1]?.trim() ?? null;
3017
3623
  cache.set(current, modulePath);
3018
3624
  return modulePath ?? void 0;
3019
3625
  }
3020
- const parent = path5.dirname(current);
3626
+ const parent = path6.dirname(current);
3021
3627
  if (parent === current) {
3022
3628
  cache.set(current, null);
3023
3629
  return void 0;
@@ -3098,10 +3704,10 @@ async function buildCodeIndex(rootDir, manifests, analyses) {
3098
3704
  if (normalizedNamespace) {
3099
3705
  recordAlias(aliases, normalizedNamespace);
3100
3706
  }
3101
- 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);
3102
3708
  const goModulePath = await readNearestGoModulePath(originalPath, goModuleCache);
3103
3709
  if (goModulePath && repoRelativePath) {
3104
- const dir = path5.posix.dirname(repoRelativePath);
3710
+ const dir = path6.posix.dirname(repoRelativePath);
3105
3711
  const packageAlias = dir === "." ? goModulePath : `${goModulePath}/${dir}`;
3106
3712
  recordAlias(aliases, packageAlias);
3107
3713
  }
@@ -3177,10 +3783,10 @@ function resolvePythonRelativeAliases(repoRelativePath, specifier) {
3177
3783
  const dotMatch = specifier.match(/^\.+/);
3178
3784
  const depth = dotMatch ? dotMatch[0].length : 0;
3179
3785
  const relativeModule = specifier.slice(depth).replace(/\./g, "/");
3180
- const baseDir = path5.posix.dirname(repoRelativePath);
3181
- const parentDir = path5.posix.normalize(path5.posix.join(baseDir, ...Array(Math.max(depth - 1, 0)).fill("..")));
3182
- const moduleBase = relativeModule ? path5.posix.join(parentDir, relativeModule) : parentDir;
3183
- 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);
3184
3790
  }
3185
3791
  function resolveRustAliases(manifest, specifier) {
3186
3792
  const repoRelativePath = manifest.repoRelativePath ? normalizeAlias(manifest.repoRelativePath) : "";
@@ -3322,9 +3928,11 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
3322
3928
  }
3323
3929
 
3324
3930
  // src/extraction.ts
3325
- import fs6 from "fs/promises";
3931
+ import fs7 from "fs/promises";
3326
3932
  import os from "os";
3327
- import path6 from "path";
3933
+ import path7 from "path";
3934
+ import { strFromU8, unzipSync } from "fflate";
3935
+ import { JSDOM } from "jsdom";
3328
3936
  import { z } from "zod";
3329
3937
  var imageVisionExtractionSchema = z.object({
3330
3938
  title: z.string().min(1).nullable().optional(),
@@ -3404,14 +4012,14 @@ async function materializeAttachmentPath(input) {
3404
4012
  if (!input.bytes) {
3405
4013
  throw new Error("Image extraction requires a file path or bytes.");
3406
4014
  }
3407
- 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-"));
3408
4016
  const extension = input.mimeType.split("/")[1]?.split("+")[0] ?? "bin";
3409
- const tempPath = path6.join(tempDir, `source.${extension}`);
3410
- await fs6.writeFile(tempPath, input.bytes);
4017
+ const tempPath = path7.join(tempDir, `source.${extension}`);
4018
+ await fs7.writeFile(tempPath, input.bytes);
3411
4019
  return {
3412
4020
  filePath: tempPath,
3413
4021
  cleanup: async () => {
3414
- await fs6.rm(tempDir, { recursive: true, force: true });
4022
+ await fs7.rm(tempDir, { recursive: true, force: true });
3415
4023
  }
3416
4024
  };
3417
4025
  }
@@ -3501,6 +4109,49 @@ function normalizePdfMetadata(raw) {
3501
4109
  }
3502
4110
  return Object.keys(metadata).length ? metadata : void 0;
3503
4111
  }
4112
+ function normalizeDocumentText(raw) {
4113
+ return raw.replace(/\r\n/g, "\n").split(/\n{2,}/).map((section) => normalizeWhitespace(section)).filter(Boolean).join("\n\n").trim();
4114
+ }
4115
+ function parseDocxCoreMetadata(bytes) {
4116
+ try {
4117
+ const archive = unzipSync(new Uint8Array(bytes));
4118
+ const coreXml = archive["docProps/core.xml"];
4119
+ if (!coreXml) {
4120
+ return void 0;
4121
+ }
4122
+ const dom = new JSDOM(strFromU8(coreXml), { contentType: "text/xml" });
4123
+ const document = dom.window.document;
4124
+ const valuesByLocalName = /* @__PURE__ */ new Map();
4125
+ for (const node of Array.from(document.getElementsByTagName("*"))) {
4126
+ const localName = node.localName?.trim().toLowerCase();
4127
+ const text = normalizeWhitespace(node.textContent ?? "");
4128
+ if (!localName || !text || valuesByLocalName.has(localName)) {
4129
+ continue;
4130
+ }
4131
+ valuesByLocalName.set(localName, text);
4132
+ }
4133
+ const metadata = {};
4134
+ const mappings = [
4135
+ ["title", "title"],
4136
+ ["author", "creator"],
4137
+ ["subject", "subject"],
4138
+ ["description", "description"],
4139
+ ["keywords", "keywords"],
4140
+ ["last_modified_by", "lastmodifiedby"],
4141
+ ["created", "created"],
4142
+ ["modified", "modified"]
4143
+ ];
4144
+ for (const [targetKey, sourceKey] of mappings) {
4145
+ const value = valuesByLocalName.get(sourceKey);
4146
+ if (value) {
4147
+ metadata[targetKey] = value;
4148
+ }
4149
+ }
4150
+ return Object.keys(metadata).length ? metadata : void 0;
4151
+ } catch {
4152
+ return void 0;
4153
+ }
4154
+ }
3504
4155
  async function extractPdfText(input) {
3505
4156
  try {
3506
4157
  const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
@@ -3548,20 +4199,49 @@ async function extractPdfText(input) {
3548
4199
  };
3549
4200
  }
3550
4201
  }
4202
+ async function extractDocxText(input) {
4203
+ try {
4204
+ const mammoth = await import("mammoth");
4205
+ const result = await mammoth.extractRawText({
4206
+ buffer: input.bytes
4207
+ });
4208
+ const extractedText = normalizeDocumentText(result.value);
4209
+ const warnings = result.messages.map((message) => normalizeWhitespace(message.message)).filter(Boolean).map((message) => truncate(message, 240));
4210
+ const artifact = {
4211
+ ...extractionMetadata("docx", input.mimeType, "docx_text"),
4212
+ metadata: parseDocxCoreMetadata(input.bytes),
4213
+ warnings: warnings.length ? warnings : void 0
4214
+ };
4215
+ if (!extractedText) {
4216
+ artifact.warnings = [...artifact.warnings ?? [], "DOCX text extraction completed but produced no extractable text."];
4217
+ }
4218
+ return {
4219
+ extractedText: extractedText || void 0,
4220
+ artifact
4221
+ };
4222
+ } catch (error) {
4223
+ return {
4224
+ artifact: {
4225
+ ...extractionMetadata("docx", input.mimeType, "docx_text"),
4226
+ warnings: [`DOCX text extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
4227
+ }
4228
+ };
4229
+ }
4230
+ }
3551
4231
 
3552
4232
  // src/logs.ts
3553
- import fs7 from "fs/promises";
3554
- import path7 from "path";
4233
+ import fs8 from "fs/promises";
4234
+ import path8 from "path";
3555
4235
  import matter from "gray-matter";
3556
4236
  async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
3557
4237
  const { paths } = await initWorkspace(rootDir);
3558
4238
  await ensureDir(paths.sessionsDir);
3559
4239
  const timestamp = startedAt.replace(/[:.]/g, "-");
3560
4240
  const baseName = `${timestamp}-${operation}-${slugify(title)}`;
3561
- let candidate = path7.join(paths.sessionsDir, `${baseName}.md`);
4241
+ let candidate = path8.join(paths.sessionsDir, `${baseName}.md`);
3562
4242
  let counter = 2;
3563
4243
  while (await fileExists(candidate)) {
3564
- candidate = path7.join(paths.sessionsDir, `${baseName}-${counter}.md`);
4244
+ candidate = path8.join(paths.sessionsDir, `${baseName}-${counter}.md`);
3565
4245
  counter++;
3566
4246
  }
3567
4247
  return candidate;
@@ -3569,11 +4249,11 @@ async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
3569
4249
  async function appendLogEntry(rootDir, action, title, lines = []) {
3570
4250
  const { paths } = await initWorkspace(rootDir);
3571
4251
  await ensureDir(paths.wikiDir);
3572
- const logPath = path7.join(paths.wikiDir, "log.md");
4252
+ const logPath = path8.join(paths.wikiDir, "log.md");
3573
4253
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
3574
4254
  const entry = [`## [${timestamp}] ${action} | ${title}`, ...lines.map((line) => `- ${line}`), ""].join("\n");
3575
- const existing = await fileExists(logPath) ? await fs7.readFile(logPath, "utf8") : "# Log\n\n";
3576
- 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}
3577
4257
  `, "utf8");
3578
4258
  }
3579
4259
  async function recordSession(rootDir, input) {
@@ -3583,8 +4263,8 @@ async function recordSession(rootDir, input) {
3583
4263
  const finishedAtIso = new Date(input.finishedAt ?? input.startedAt).toISOString();
3584
4264
  const durationMs = Math.max(0, new Date(finishedAtIso).getTime() - new Date(startedAtIso).getTime());
3585
4265
  const sessionPath = await resolveUniqueSessionPath(rootDir, input.operation, input.title, startedAtIso);
3586
- const sessionId = path7.basename(sessionPath, ".md");
3587
- 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);
3588
4268
  const frontmatter = Object.fromEntries(
3589
4269
  Object.entries({
3590
4270
  session_id: sessionId,
@@ -3632,7 +4312,7 @@ async function recordSession(rootDir, input) {
3632
4312
  frontmatter
3633
4313
  );
3634
4314
  await writeFileIfChanged(sessionPath, content);
3635
- const logPath = path7.join(paths.wikiDir, "log.md");
4315
+ const logPath = path8.join(paths.wikiDir, "log.md");
3636
4316
  const timestamp = startedAtIso.slice(0, 19).replace("T", " ");
3637
4317
  const entry = [
3638
4318
  `## [${timestamp}] ${input.operation} | ${input.title}`,
@@ -3640,8 +4320,8 @@ async function recordSession(rootDir, input) {
3640
4320
  ...(input.lines ?? []).map((line) => `- ${line}`),
3641
4321
  ""
3642
4322
  ].join("\n");
3643
- const existing = await fileExists(logPath) ? await fs7.readFile(logPath, "utf8") : "# Log\n\n";
3644
- 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}
3645
4325
  `, "utf8");
3646
4326
  return { sessionPath, sessionId };
3647
4327
  }
@@ -3651,13 +4331,13 @@ async function appendWatchRun(rootDir, run) {
3651
4331
  }
3652
4332
 
3653
4333
  // src/source-classification.ts
3654
- import path8 from "path";
4334
+ import path9 from "path";
3655
4335
  var ALL_SOURCE_CLASSES = ["first_party", "third_party", "resource", "generated"];
3656
4336
  var THIRD_PARTY_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "vendor", "Pods"]);
3657
4337
  var GENERATED_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", ".next", "coverage", "DerivedData", "target"]);
3658
4338
  function matchesAnyGlob(relativePath, patterns) {
3659
4339
  return patterns.some(
3660
- (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)
3661
4341
  );
3662
4342
  }
3663
4343
  function classifyRepoPath(relativePath, repoAnalysis) {
@@ -3710,8 +4390,8 @@ function aggregateManifestSourceClass(manifests, sourceIds) {
3710
4390
  }
3711
4391
 
3712
4392
  // src/watch-state.ts
3713
- import fs8 from "fs/promises";
3714
- import path9 from "path";
4393
+ import fs9 from "fs/promises";
4394
+ import path10 from "path";
3715
4395
  import matter2 from "gray-matter";
3716
4396
  function pendingEntryKey(entry) {
3717
4397
  return entry.path;
@@ -3725,7 +4405,7 @@ function normalizeRelativePath(rootDir, filePath) {
3725
4405
  if (!filePath) {
3726
4406
  return void 0;
3727
4407
  }
3728
- return toPosix(path9.relative(rootDir, path9.resolve(filePath)));
4408
+ return toPosix(path10.relative(rootDir, path10.resolve(filePath)));
3729
4409
  }
3730
4410
  async function readPendingSemanticRefresh(rootDir) {
3731
4411
  const { paths } = await initWorkspace(rootDir);
@@ -3819,11 +4499,11 @@ async function markPagesStaleForSources(rootDir, sourceIds) {
3819
4499
  if (page.freshness !== "stale" || !page.sourceIds.some((sourceId) => affectedSourceIds.has(sourceId))) {
3820
4500
  continue;
3821
4501
  }
3822
- const absolutePath = path9.join(paths.wikiDir, page.path);
4502
+ const absolutePath = path10.join(paths.wikiDir, page.path);
3823
4503
  if (!await fileExists(absolutePath)) {
3824
4504
  continue;
3825
4505
  }
3826
- const raw = await fs8.readFile(absolutePath, "utf8");
4506
+ const raw = await fs9.readFile(absolutePath, "utf8");
3827
4507
  const parsed = matter2(raw);
3828
4508
  if (parsed.data.freshness === "stale") {
3829
4509
  continue;
@@ -3858,6 +4538,9 @@ function inferKind(mimeType, filePath) {
3858
4538
  if (mimeType === "application/pdf" || filePath.toLowerCase().endsWith(".pdf")) {
3859
4539
  return "pdf";
3860
4540
  }
4541
+ if (mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || filePath.toLowerCase().endsWith(".docx")) {
4542
+ return "docx";
4543
+ }
3861
4544
  if (mimeType.startsWith("image/")) {
3862
4545
  return "image";
3863
4546
  }
@@ -3874,7 +4557,7 @@ function normalizeIngestOptions(options) {
3874
4557
  return {
3875
4558
  includeAssets: options?.includeAssets ?? true,
3876
4559
  maxAssetSize: Math.max(0, Math.floor(options?.maxAssetSize ?? DEFAULT_MAX_ASSET_SIZE)),
3877
- repoRoot: options?.repoRoot ? path10.resolve(options.repoRoot) : void 0,
4560
+ repoRoot: options?.repoRoot ? path11.resolve(options.repoRoot) : void 0,
3878
4561
  include: (options?.include ?? []).map((pattern) => pattern.trim()).filter(Boolean),
3879
4562
  exclude: (options?.exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean),
3880
4563
  maxFiles: Math.max(1, Math.floor(options?.maxFiles ?? DEFAULT_MAX_DIRECTORY_FILES)),
@@ -3894,27 +4577,27 @@ async function resolveRepoIngestOptions(rootDir, options) {
3894
4577
  }
3895
4578
  function matchesAnyGlob2(relativePath, patterns) {
3896
4579
  return patterns.some(
3897
- (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)
3898
4581
  );
3899
4582
  }
3900
4583
  function supportedDirectoryKind(sourceKind) {
3901
4584
  return sourceKind !== "binary";
3902
4585
  }
3903
4586
  async function findNearestGitRoot2(startPath) {
3904
- let current = path10.resolve(startPath);
4587
+ let current = path11.resolve(startPath);
3905
4588
  try {
3906
- const stat = await fs9.stat(current);
4589
+ const stat = await fs10.stat(current);
3907
4590
  if (!stat.isDirectory()) {
3908
- current = path10.dirname(current);
4591
+ current = path11.dirname(current);
3909
4592
  }
3910
4593
  } catch {
3911
- current = path10.dirname(current);
4594
+ current = path11.dirname(current);
3912
4595
  }
3913
4596
  while (true) {
3914
- if (await fileExists(path10.join(current, ".git"))) {
4597
+ if (await fileExists(path11.join(current, ".git"))) {
3915
4598
  return current;
3916
4599
  }
3917
- const parent = path10.dirname(current);
4600
+ const parent = path11.dirname(current);
3918
4601
  if (parent === current) {
3919
4602
  return null;
3920
4603
  }
@@ -3922,26 +4605,26 @@ async function findNearestGitRoot2(startPath) {
3922
4605
  }
3923
4606
  }
3924
4607
  function withinRoot(rootPath, targetPath) {
3925
- const relative = path10.relative(rootPath, targetPath);
3926
- return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
4608
+ const relative = path11.relative(rootPath, targetPath);
4609
+ return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
3927
4610
  }
3928
4611
  function repoRootFromManifest(manifest) {
3929
4612
  if (manifest.originType !== "file" || !manifest.originalPath || !manifest.repoRelativePath) {
3930
4613
  return null;
3931
4614
  }
3932
- const repoDir = path10.posix.dirname(manifest.repoRelativePath);
3933
- 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));
3934
4617
  if (repoDir === "." || !repoDir) {
3935
4618
  return fileDir;
3936
4619
  }
3937
4620
  const segments = repoDir.split("/").filter(Boolean);
3938
- return path10.resolve(fileDir, ...segments.map(() => ".."));
4621
+ return path11.resolve(fileDir, ...segments.map(() => ".."));
3939
4622
  }
3940
4623
  function repoRelativePathFor(absolutePath, repoRoot) {
3941
4624
  if (!repoRoot || !withinRoot(repoRoot, absolutePath)) {
3942
4625
  return void 0;
3943
4626
  }
3944
- const relative = toPosix(path10.relative(repoRoot, absolutePath));
4627
+ const relative = toPosix(path11.relative(repoRoot, absolutePath));
3945
4628
  return relative && !relative.startsWith("..") ? relative : void 0;
3946
4629
  }
3947
4630
  function normalizeOriginUrl(input) {
@@ -4017,7 +4700,41 @@ function prepareCapturedMarkdownInput(input) {
4017
4700
  logDetails: input.logDetails
4018
4701
  };
4019
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
+ }
4020
4736
  async function fetchText(url) {
4737
+ await validateUrlSafety(url);
4021
4738
  const response = await fetch(url);
4022
4739
  if (!response.ok) {
4023
4740
  throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
@@ -4025,6 +4742,7 @@ async function fetchText(url) {
4025
4742
  return response.text();
4026
4743
  }
4027
4744
  async function fetchResolvedText(url) {
4745
+ await validateUrlSafety(url);
4028
4746
  const response = await fetch(url);
4029
4747
  if (!response.ok) {
4030
4748
  throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
@@ -4036,7 +4754,7 @@ async function fetchResolvedText(url) {
4036
4754
  };
4037
4755
  }
4038
4756
  function domTextFromHtml(html, baseUrl) {
4039
- const dom = new JSDOM(`<body>${html}</body>`, { url: baseUrl });
4757
+ const dom = new JSDOM2(`<body>${html}</body>`, { url: baseUrl });
4040
4758
  return normalizeWhitespace(dom.window.document.body.textContent ?? "");
4041
4759
  }
4042
4760
  async function captureArxivMarkdown(input, options) {
@@ -4046,7 +4764,7 @@ async function captureArxivMarkdown(input, options) {
4046
4764
  }
4047
4765
  const normalizedUrl = `https://arxiv.org/abs/${arxivId}`;
4048
4766
  const html = await fetchText(normalizedUrl);
4049
- const dom = new JSDOM(html, { url: normalizedUrl });
4767
+ const dom = new JSDOM2(html, { url: normalizedUrl });
4050
4768
  const document = dom.window.document;
4051
4769
  const metaTitle = document.querySelector('meta[name="citation_title"]')?.getAttribute("content")?.trim();
4052
4770
  const headingTitle = document.querySelector("h1.title")?.textContent?.trim();
@@ -4155,7 +4873,7 @@ async function captureArticleMarkdown(rootDir, input, options, extra = { sourceT
4155
4873
  if (!resolved.contentType.includes("html")) {
4156
4874
  throw new Error(`Unsupported article content type: ${resolved.contentType}`);
4157
4875
  }
4158
- const dom = new JSDOM(resolved.text, { url: resolved.finalUrl });
4876
+ const dom = new JSDOM2(resolved.text, { url: resolved.finalUrl });
4159
4877
  const document = dom.window.document;
4160
4878
  const canonicalHref = document.querySelector('link[rel="canonical"]')?.getAttribute("href")?.trim();
4161
4879
  const canonicalUrl = canonicalHref ? normalizeOriginUrl(new URL(canonicalHref, resolved.finalUrl).toString()) : resolved.finalUrl;
@@ -4238,7 +4956,7 @@ function buildCompositeHash(payloadBytes, attachments = []) {
4238
4956
  return sha256(`${sha256(payloadBytes)}|${attachmentSignature}`);
4239
4957
  }
4240
4958
  function sanitizeAssetRelativePath(value) {
4241
- const normalized = path10.posix.normalize(value.replace(/\\/g, "/"));
4959
+ const normalized = path11.posix.normalize(value.replace(/\\/g, "/"));
4242
4960
  const segments = normalized.split("/").filter(Boolean).map((segment) => {
4243
4961
  if (segment === ".") {
4244
4962
  return "";
@@ -4258,7 +4976,7 @@ function normalizeLocalReference(value) {
4258
4976
  return null;
4259
4977
  }
4260
4978
  const lowered = candidate.toLowerCase();
4261
- 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)) {
4262
4980
  return null;
4263
4981
  }
4264
4982
  return candidate.replace(/\\/g, "/");
@@ -4274,6 +4992,22 @@ function extractMarkdownReferences(content) {
4274
4992
  }
4275
4993
  return references;
4276
4994
  }
4995
+ function extractHtmlLocalReferences(html, baseUrl) {
4996
+ const dom = new JSDOM2(html, { url: baseUrl });
4997
+ const document = dom.window.document;
4998
+ const references = [];
4999
+ for (const image of [...document.querySelectorAll("img[src]")]) {
5000
+ const src = image.getAttribute("src");
5001
+ if (!src) {
5002
+ continue;
5003
+ }
5004
+ const normalized = normalizeLocalReference(src);
5005
+ if (normalized) {
5006
+ references.push(normalized);
5007
+ }
5008
+ }
5009
+ return references;
5010
+ }
4277
5011
  function normalizeRemoteReference(value, baseUrl) {
4278
5012
  const trimmed = value.trim().replace(/^<|>$/g, "");
4279
5013
  const [withoutTitle] = trimmed.split(/\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/, 1);
@@ -4309,7 +5043,7 @@ function extractMarkdownImageReferences(content, baseUrl) {
4309
5043
  return references;
4310
5044
  }
4311
5045
  async function convertHtmlToMarkdown(html, url) {
4312
- const dom = new JSDOM(html, { url });
5046
+ const dom = new JSDOM2(html, { url });
4313
5047
  const article = new Readability(dom.window.document).parse();
4314
5048
  const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
4315
5049
  const body = article?.content ?? dom.window.document.body.innerHTML;
@@ -4320,12 +5054,12 @@ async function convertHtmlToMarkdown(html, url) {
4320
5054
  };
4321
5055
  }
4322
5056
  async function readManifestByHash(manifestsDir, contentHash) {
4323
- const entries = await fs9.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
5057
+ const entries = await fs10.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
4324
5058
  for (const entry of entries) {
4325
5059
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
4326
5060
  continue;
4327
5061
  }
4328
- const manifest = await readJsonFile(path10.join(manifestsDir, entry.name));
5062
+ const manifest = await readJsonFile(path11.join(manifestsDir, entry.name));
4329
5063
  if (manifest?.contentHash === contentHash) {
4330
5064
  return manifest;
4331
5065
  }
@@ -4333,12 +5067,12 @@ async function readManifestByHash(manifestsDir, contentHash) {
4333
5067
  return null;
4334
5068
  }
4335
5069
  async function readManifestByOrigin(manifestsDir, prepared) {
4336
- const entries = await fs9.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
5070
+ const entries = await fs10.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
4337
5071
  for (const entry of entries) {
4338
5072
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
4339
5073
  continue;
4340
5074
  }
4341
- const manifest = await readJsonFile(path10.join(manifestsDir, entry.name));
5075
+ const manifest = await readJsonFile(path11.join(manifestsDir, entry.name));
4342
5076
  if (manifest && manifestMatchesOrigin(manifest, prepared)) {
4343
5077
  return manifest;
4344
5078
  }
@@ -4349,12 +5083,12 @@ async function loadGitignoreMatcher(repoRoot, enabled) {
4349
5083
  if (!enabled) {
4350
5084
  return null;
4351
5085
  }
4352
- const gitignorePath = path10.join(repoRoot, ".gitignore");
5086
+ const gitignorePath = path11.join(repoRoot, ".gitignore");
4353
5087
  if (!await fileExists(gitignorePath)) {
4354
5088
  return null;
4355
5089
  }
4356
5090
  const matcher = ignore();
4357
- matcher.add(await fs9.readFile(gitignorePath, "utf8"));
5091
+ matcher.add(await fs10.readFile(gitignorePath, "utf8"));
4358
5092
  return matcher;
4359
5093
  }
4360
5094
  function builtInIgnoreReason(relativePath) {
@@ -4378,23 +5112,23 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
4378
5112
  if (!currentDir) {
4379
5113
  continue;
4380
5114
  }
4381
- const entries = await fs9.readdir(currentDir, { withFileTypes: true });
5115
+ const entries = await fs10.readdir(currentDir, { withFileTypes: true });
4382
5116
  entries.sort((left, right) => left.name.localeCompare(right.name));
4383
5117
  for (const entry of entries) {
4384
- const absolutePath = path10.join(currentDir, entry.name);
4385
- 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));
4386
5120
  const relativePath = relativeToRepo || entry.name;
4387
5121
  const builtInReason = builtInIgnoreReason(relativePath);
4388
5122
  if (builtInReason) {
4389
- skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: builtInReason });
5123
+ skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: builtInReason });
4390
5124
  continue;
4391
5125
  }
4392
5126
  if (matcher?.ignores(relativePath)) {
4393
- skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "gitignore" });
5127
+ skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "gitignore" });
4394
5128
  continue;
4395
5129
  }
4396
5130
  if (matchesAnyGlob2(relativePath, options.exclude)) {
4397
- skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "exclude_glob" });
5131
+ skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "exclude_glob" });
4398
5132
  continue;
4399
5133
  }
4400
5134
  if (entry.isDirectory()) {
@@ -4402,26 +5136,26 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
4402
5136
  continue;
4403
5137
  }
4404
5138
  if (!entry.isFile()) {
4405
- skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
5139
+ skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
4406
5140
  continue;
4407
5141
  }
4408
5142
  if (options.include.length > 0 && !matchesAnyGlob2(relativePath, options.include)) {
4409
- skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "include_glob" });
5143
+ skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "include_glob" });
4410
5144
  continue;
4411
5145
  }
4412
5146
  const mimeType = guessMimeType(absolutePath);
4413
5147
  const sourceKind = inferKind(mimeType, absolutePath);
4414
5148
  const sourceClass = sourceClassForRelativePath(relativePath, options);
4415
5149
  if (!supportedDirectoryKind(sourceKind)) {
4416
- 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}` });
4417
5151
  continue;
4418
5152
  }
4419
5153
  if (!options.extractClasses.includes(sourceClass)) {
4420
- 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}` });
4421
5155
  continue;
4422
5156
  }
4423
5157
  if (files.length >= options.maxFiles) {
4424
- skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "max_files" });
5158
+ skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "max_files" });
4425
5159
  continue;
4426
5160
  }
4427
5161
  files.push(absolutePath);
@@ -4443,12 +5177,12 @@ function resolveUrlMimeType(input, response) {
4443
5177
  function buildRemoteAssetRelativePath(assetUrl, mimeType) {
4444
5178
  const url = new URL(assetUrl);
4445
5179
  const normalized = sanitizeAssetRelativePath(`${url.hostname}${url.pathname || "/asset"}`);
4446
- const extension = path10.posix.extname(normalized);
4447
- const directory = path10.posix.dirname(normalized);
4448
- 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);
4449
5183
  const resolvedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
4450
5184
  const hashedName = `${basename || "asset"}-${sha256(assetUrl).slice(0, 8)}${resolvedExtension}`;
4451
- return directory === "." ? hashedName : path10.posix.join(directory, hashedName);
5185
+ return directory === "." ? hashedName : path11.posix.join(directory, hashedName);
4452
5186
  }
4453
5187
  async function readResponseBytesWithinLimit(response, maxBytes) {
4454
5188
  const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
@@ -4480,6 +5214,7 @@ async function readResponseBytesWithinLimit(response, maxBytes) {
4480
5214
  return Buffer.concat(chunks);
4481
5215
  }
4482
5216
  async function fetchRemoteImageAttachment(assetUrl, maxAssetSize) {
5217
+ await validateUrlSafety(assetUrl);
4483
5218
  const response = await fetch(assetUrl);
4484
5219
  if (!response.ok) {
4485
5220
  throw new Error(`failed with ${response.status} ${response.statusText}`);
@@ -4512,7 +5247,7 @@ async function collectRemoteImageAttachments(assetUrls, options) {
4512
5247
  return { attachments, skippedCount };
4513
5248
  }
4514
5249
  function extractHtmlImageReferences(html, baseUrl) {
4515
- const dom = new JSDOM(html, { url: baseUrl });
5250
+ const dom = new JSDOM2(html, { url: baseUrl });
4516
5251
  const document = dom.window.document;
4517
5252
  const references = [];
4518
5253
  for (const image of [...document.querySelectorAll("img[src]")]) {
@@ -4528,7 +5263,7 @@ function extractHtmlImageReferences(html, baseUrl) {
4528
5263
  return references;
4529
5264
  }
4530
5265
  function rewriteHtmlImageReferences(html, baseUrl, replacements) {
4531
- const dom = new JSDOM(html, { url: baseUrl });
5266
+ const dom = new JSDOM2(html, { url: baseUrl });
4532
5267
  const document = dom.window.document;
4533
5268
  for (const image of [...document.querySelectorAll("img[src]")]) {
4534
5269
  const src = image.getAttribute("src");
@@ -4543,6 +5278,22 @@ function rewriteHtmlImageReferences(html, baseUrl, replacements) {
4543
5278
  }
4544
5279
  return dom.serialize();
4545
5280
  }
5281
+ function rewriteHtmlLocalReferences(html, baseUrl, replacements) {
5282
+ const dom = new JSDOM2(html, { url: baseUrl });
5283
+ const document = dom.window.document;
5284
+ for (const image of [...document.querySelectorAll("img[src]")]) {
5285
+ const src = image.getAttribute("src");
5286
+ if (!src) {
5287
+ continue;
5288
+ }
5289
+ const normalized = normalizeLocalReference(src);
5290
+ const replacement = normalized ? replacements.get(normalized) : void 0;
5291
+ if (replacement) {
5292
+ image.setAttribute("src", replacement);
5293
+ }
5294
+ }
5295
+ return dom.serialize();
5296
+ }
4546
5297
  function rewriteMarkdownImageReferences(content, baseUrl, replacements) {
4547
5298
  return content.replace(/(!\[[^\]]*]\()([^)]+)(\))/g, (fullMatch, prefix, target, suffix) => {
4548
5299
  const normalized = normalizeRemoteReference(target, baseUrl);
@@ -4584,34 +5335,34 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4584
5335
  const previous = existingByOrigin ?? void 0;
4585
5336
  const sourceId = previous?.sourceId ?? `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
4586
5337
  const now = (/* @__PURE__ */ new Date()).toISOString();
4587
- const storedPath = path10.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
4588
- const extractedTextPath = prepared.extractedText ? path10.join(paths.extractsDir, `${sourceId}.md`) : void 0;
4589
- const extractedMetadataPath = prepared.extractionArtifact ? path10.join(paths.extractsDir, `${sourceId}.json`) : void 0;
4590
- 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);
4591
5342
  if (previous?.storedPath) {
4592
- await fs9.rm(path10.resolve(rootDir, previous.storedPath), { force: true });
5343
+ await fs10.rm(path11.resolve(rootDir, previous.storedPath), { force: true });
4593
5344
  }
4594
5345
  if (previous?.extractedTextPath) {
4595
- await fs9.rm(path10.resolve(rootDir, previous.extractedTextPath), { force: true });
5346
+ await fs10.rm(path11.resolve(rootDir, previous.extractedTextPath), { force: true });
4596
5347
  }
4597
5348
  if (previous?.extractedMetadataPath) {
4598
- await fs9.rm(path10.resolve(rootDir, previous.extractedMetadataPath), { force: true });
5349
+ await fs10.rm(path11.resolve(rootDir, previous.extractedMetadataPath), { force: true });
4599
5350
  }
4600
- await fs9.rm(attachmentsDir, { recursive: true, force: true });
4601
- await fs9.writeFile(storedPath, prepared.payloadBytes);
5351
+ await fs10.rm(attachmentsDir, { recursive: true, force: true });
5352
+ await fs10.writeFile(storedPath, prepared.payloadBytes);
4602
5353
  if (prepared.extractedText && extractedTextPath) {
4603
- await fs9.writeFile(extractedTextPath, prepared.extractedText, "utf8");
5354
+ await fs10.writeFile(extractedTextPath, prepared.extractedText, "utf8");
4604
5355
  }
4605
5356
  if (prepared.extractionArtifact && extractedMetadataPath) {
4606
5357
  await writeJsonFile(extractedMetadataPath, prepared.extractionArtifact);
4607
5358
  }
4608
5359
  const manifestAttachments = [];
4609
5360
  for (const attachment of attachments) {
4610
- const absoluteAttachmentPath = path10.join(attachmentsDir, attachment.relativePath);
4611
- await ensureDir(path10.dirname(absoluteAttachmentPath));
4612
- 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);
4613
5364
  manifestAttachments.push({
4614
- path: toPosix(path10.relative(rootDir, absoluteAttachmentPath)),
5365
+ path: toPosix(path11.relative(rootDir, absoluteAttachmentPath)),
4615
5366
  mimeType: attachment.mimeType,
4616
5367
  originalPath: attachment.originalPath
4617
5368
  });
@@ -4627,9 +5378,9 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4627
5378
  originalPath: prepared.originalPath,
4628
5379
  repoRelativePath: prepared.repoRelativePath,
4629
5380
  url: prepared.url,
4630
- storedPath: toPosix(path10.relative(rootDir, storedPath)),
4631
- extractedTextPath: extractedTextPath ? toPosix(path10.relative(rootDir, extractedTextPath)) : void 0,
4632
- 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,
4633
5384
  extractionHash,
4634
5385
  mimeType: prepared.mimeType,
4635
5386
  contentHash,
@@ -4637,7 +5388,7 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4637
5388
  updatedAt: now,
4638
5389
  attachments: manifestAttachments.length ? manifestAttachments : void 0
4639
5390
  };
4640
- await writeJsonFile(path10.join(paths.manifestsDir, `${sourceId}.json`), manifest);
5391
+ await writeJsonFile(path11.join(paths.manifestsDir, `${sourceId}.json`), manifest);
4641
5392
  await appendLogEntry(rootDir, "ingest", prepared.title, [
4642
5393
  `source_id=${sourceId}`,
4643
5394
  `kind=${prepared.sourceKind}`,
@@ -4655,16 +5406,16 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4655
5406
  return { manifest, isNew: !previous, wasUpdated: Boolean(previous) };
4656
5407
  }
4657
5408
  async function removeManifestArtifacts(rootDir, manifest, paths) {
4658
- await fs9.rm(path10.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
4659
- 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 });
4660
5411
  if (manifest.extractedTextPath) {
4661
- await fs9.rm(path10.resolve(rootDir, manifest.extractedTextPath), { force: true });
5412
+ await fs10.rm(path11.resolve(rootDir, manifest.extractedTextPath), { force: true });
4662
5413
  }
4663
5414
  if (manifest.extractedMetadataPath) {
4664
- await fs9.rm(path10.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
5415
+ await fs10.rm(path11.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
4665
5416
  }
4666
- await fs9.rm(path10.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
4667
- 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 });
4668
5419
  }
4669
5420
  function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
4670
5421
  const candidates = [
@@ -4673,17 +5424,17 @@ function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
4673
5424
  paths.stateDir,
4674
5425
  paths.agentDir,
4675
5426
  paths.inboxDir,
4676
- path10.join(rootDir, ".claude"),
4677
- path10.join(rootDir, ".cursor"),
4678
- path10.join(rootDir, ".obsidian")
5427
+ path11.join(rootDir, ".claude"),
5428
+ path11.join(rootDir, ".cursor"),
5429
+ path11.join(rootDir, ".obsidian")
4679
5430
  ];
4680
- 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));
4681
5432
  }
4682
5433
  function preparedMatchesManifest(manifest, prepared, contentHash) {
4683
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;
4684
5435
  }
4685
5436
  function shouldDeferWatchSemanticRefresh(sourceKind) {
4686
- return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "image";
5437
+ return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "docx" || sourceKind === "image";
4687
5438
  }
4688
5439
  function pendingSemanticRefreshId(changeType, repoRoot, relativePath) {
4689
5440
  return `pending:${changeType}:${sha256(`${toPosix(repoRoot)}:${relativePath}`).slice(0, 12)}`;
@@ -4699,16 +5450,16 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4699
5450
  const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
4700
5451
  const manifests = await listManifests(rootDir);
4701
5452
  const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
4702
- (item) => path10.resolve(item)
5453
+ (item) => path11.resolve(item)
4703
5454
  );
4704
5455
  const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
4705
5456
  const manifestsByRepoRoot = /* @__PURE__ */ new Map();
4706
5457
  for (const manifest of manifests) {
4707
5458
  const repoRoot = repoRootFromManifest(manifest);
4708
- if (!repoRoot || !uniqueRoots.includes(path10.resolve(repoRoot))) {
5459
+ if (!repoRoot || !uniqueRoots.includes(path11.resolve(repoRoot))) {
4709
5460
  continue;
4710
5461
  }
4711
- const key = path10.resolve(repoRoot);
5462
+ const key = path11.resolve(repoRoot);
4712
5463
  const bucket = manifestsByRepoRoot.get(key) ?? [];
4713
5464
  bucket.push(manifest);
4714
5465
  manifestsByRepoRoot.set(key, bucket);
@@ -4733,14 +5484,14 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4733
5484
  skipped.push(
4734
5485
  ...collected.skipped,
4735
5486
  ...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
4736
- path: toPosix(path10.relative(rootDir, absolutePath)),
5487
+ path: toPosix(path11.relative(rootDir, absolutePath)),
4737
5488
  reason: "workspace_generated"
4738
5489
  }))
4739
5490
  );
4740
5491
  scannedCount += files.length;
4741
- const currentPaths = new Set(files.map((absolutePath) => path10.resolve(absolutePath)));
5492
+ const currentPaths = new Set(files.map((absolutePath) => path11.resolve(absolutePath)));
4742
5493
  for (const absolutePath of files) {
4743
- const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(repoRoot, absolutePath));
5494
+ const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
4744
5495
  const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
4745
5496
  const result = await persistPreparedInput(rootDir, prepared, paths);
4746
5497
  if (result.isNew) {
@@ -4750,7 +5501,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4750
5501
  }
4751
5502
  }
4752
5503
  for (const manifest of repoManifests) {
4753
- const originalPath = manifest.originalPath ? path10.resolve(manifest.originalPath) : null;
5504
+ const originalPath = manifest.originalPath ? path11.resolve(manifest.originalPath) : null;
4754
5505
  if (originalPath && !currentPaths.has(originalPath)) {
4755
5506
  await removeManifestArtifacts(rootDir, manifest, paths);
4756
5507
  removed.push(manifest);
@@ -4758,7 +5509,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4758
5509
  }
4759
5510
  }
4760
5511
  if (uniqueRoots.length > 0) {
4761
- 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(","), [
4762
5513
  `repo_roots=${uniqueRoots.length}`,
4763
5514
  `scanned=${scannedCount}`,
4764
5515
  `imported=${imported.length}`,
@@ -4781,16 +5532,16 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4781
5532
  const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
4782
5533
  const manifests = await listManifests(rootDir);
4783
5534
  const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
4784
- (item) => path10.resolve(item)
5535
+ (item) => path11.resolve(item)
4785
5536
  );
4786
5537
  const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
4787
5538
  const manifestsByRepoRoot = /* @__PURE__ */ new Map();
4788
5539
  for (const manifest of manifests) {
4789
5540
  const repoRoot = repoRootFromManifest(manifest);
4790
- if (!repoRoot || !uniqueRoots.includes(path10.resolve(repoRoot))) {
5541
+ if (!repoRoot || !uniqueRoots.includes(path11.resolve(repoRoot))) {
4791
5542
  continue;
4792
5543
  }
4793
- const key = path10.resolve(repoRoot);
5544
+ const key = path11.resolve(repoRoot);
4794
5545
  const bucket = manifestsByRepoRoot.get(key) ?? [];
4795
5546
  bucket.push(manifest);
4796
5547
  manifestsByRepoRoot.set(key, bucket);
@@ -4805,7 +5556,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4805
5556
  for (const repoRoot of uniqueRoots) {
4806
5557
  const repoManifests = manifestsByRepoRoot.get(repoRoot) ?? [];
4807
5558
  const manifestsByOriginalPath = new Map(
4808
- 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])
4809
5560
  );
4810
5561
  if (!await fileExists(repoRoot)) {
4811
5562
  for (const manifest of repoManifests) {
@@ -4813,7 +5564,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4813
5564
  pendingSemanticRefresh.push({
4814
5565
  id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? manifest.storedPath),
4815
5566
  repoRoot,
4816
- path: toPosix(path10.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
5567
+ path: toPosix(path11.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
4817
5568
  changeType: "removed",
4818
5569
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
4819
5570
  sourceId: manifest.sourceId,
@@ -4833,17 +5584,17 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4833
5584
  skipped.push(
4834
5585
  ...collected.skipped,
4835
5586
  ...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
4836
- path: toPosix(path10.relative(rootDir, absolutePath)),
5587
+ path: toPosix(path11.relative(rootDir, absolutePath)),
4837
5588
  reason: "workspace_generated"
4838
5589
  }))
4839
5590
  );
4840
5591
  scannedCount += files.length;
4841
- const currentPaths = new Set(files.map((absolutePath) => path10.resolve(absolutePath)));
5592
+ const currentPaths = new Set(files.map((absolutePath) => path11.resolve(absolutePath)));
4842
5593
  for (const absolutePath of files) {
4843
- const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(repoRoot, absolutePath));
5594
+ const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
4844
5595
  const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
4845
5596
  if (shouldDeferWatchSemanticRefresh(prepared.sourceKind)) {
4846
- const existing = manifestsByOriginalPath.get(path10.resolve(absolutePath));
5597
+ const existing = manifestsByOriginalPath.get(path11.resolve(absolutePath));
4847
5598
  const contentHash = buildCompositeHash(prepared.payloadBytes, prepared.attachments);
4848
5599
  const changed = !existing || !preparedMatchesManifest(existing, prepared, contentHash);
4849
5600
  if (changed) {
@@ -4851,10 +5602,10 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4851
5602
  id: pendingSemanticRefreshId(
4852
5603
  existing ? "modified" : "added",
4853
5604
  repoRoot,
4854
- prepared.repoRelativePath ?? toPosix(path10.relative(repoRoot, absolutePath))
5605
+ prepared.repoRelativePath ?? toPosix(path11.relative(repoRoot, absolutePath))
4855
5606
  ),
4856
5607
  repoRoot,
4857
- path: toPosix(path10.relative(rootDir, absolutePath)),
5608
+ path: toPosix(path11.relative(rootDir, absolutePath)),
4858
5609
  changeType: existing ? "modified" : "added",
4859
5610
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
4860
5611
  sourceId: existing?.sourceId,
@@ -4874,13 +5625,13 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4874
5625
  }
4875
5626
  }
4876
5627
  for (const manifest of repoManifests) {
4877
- const originalPath = manifest.originalPath ? path10.resolve(manifest.originalPath) : null;
5628
+ const originalPath = manifest.originalPath ? path11.resolve(manifest.originalPath) : null;
4878
5629
  if (originalPath && !currentPaths.has(originalPath)) {
4879
5630
  if (shouldDeferWatchSemanticRefresh(manifest.sourceKind)) {
4880
5631
  pendingSemanticRefresh.push({
4881
- id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path10.relative(repoRoot, originalPath))),
5632
+ id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path11.relative(repoRoot, originalPath))),
4882
5633
  repoRoot,
4883
- path: toPosix(path10.relative(rootDir, originalPath)),
5634
+ path: toPosix(path11.relative(rootDir, originalPath)),
4884
5635
  changeType: "removed",
4885
5636
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
4886
5637
  sourceId: manifest.sourceId,
@@ -4898,7 +5649,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4898
5649
  await appendLogEntry(
4899
5650
  rootDir,
4900
5651
  "sync_repo_watch",
4901
- uniqueRoots.map((repoRoot) => toPosix(path10.relative(rootDir, repoRoot)) || ".").join(","),
5652
+ uniqueRoots.map((repoRoot) => toPosix(path11.relative(rootDir, repoRoot)) || ".").join(","),
4902
5653
  [
4903
5654
  `repo_roots=${uniqueRoots.length}`,
4904
5655
  `scanned=${scannedCount}`,
@@ -4924,17 +5675,17 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4924
5675
  };
4925
5676
  }
4926
5677
  async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
4927
- const payloadBytes = await fs9.readFile(absoluteInput);
5678
+ const payloadBytes = await fs10.readFile(absoluteInput);
4928
5679
  const mimeType = guessMimeType(absoluteInput);
4929
5680
  const sourceKind = inferKind(mimeType, absoluteInput);
4930
5681
  const language = inferCodeLanguage(absoluteInput, mimeType);
4931
- const storedExtension = path10.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
5682
+ const storedExtension = path11.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
4932
5683
  let title;
4933
5684
  let extractedText;
4934
5685
  let extractionArtifact;
4935
5686
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
4936
5687
  extractedText = payloadBytes.toString("utf8");
4937
- title = titleFromText(path10.basename(absoluteInput, path10.extname(absoluteInput)), extractedText);
5688
+ title = titleFromText(path11.basename(absoluteInput, path11.extname(absoluteInput)), extractedText);
4938
5689
  extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
4939
5690
  } else if (sourceKind === "html") {
4940
5691
  const html = payloadBytes.toString("utf8");
@@ -4943,12 +5694,18 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
4943
5694
  extractedText = converted.markdown;
4944
5695
  extractionArtifact = createHtmlReadabilityExtractionArtifact(sourceKind, mimeType);
4945
5696
  } else if (sourceKind === "pdf") {
4946
- title = path10.basename(absoluteInput, path10.extname(absoluteInput));
5697
+ title = path11.basename(absoluteInput, path11.extname(absoluteInput));
4947
5698
  const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
4948
5699
  extractedText = extracted.extractedText;
4949
5700
  extractionArtifact = extracted.artifact;
5701
+ } else if (sourceKind === "docx") {
5702
+ title = path11.basename(absoluteInput, path11.extname(absoluteInput));
5703
+ const extracted = await extractDocxText({ mimeType, bytes: payloadBytes });
5704
+ title = extracted.artifact.metadata?.title?.trim() || title;
5705
+ extractedText = extracted.extractedText;
5706
+ extractionArtifact = extracted.artifact;
4950
5707
  } else if (sourceKind === "image") {
4951
- title = path10.basename(absoluteInput, path10.extname(absoluteInput));
5708
+ title = path11.basename(absoluteInput, path11.extname(absoluteInput));
4952
5709
  const extracted = await extractImageWithVision(rootDir, {
4953
5710
  title,
4954
5711
  mimeType,
@@ -4958,7 +5715,7 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
4958
5715
  extractedText = extracted.extractedText;
4959
5716
  extractionArtifact = extracted.artifact;
4960
5717
  } else {
4961
- title = path10.basename(absoluteInput, path10.extname(absoluteInput));
5718
+ title = path11.basename(absoluteInput, path11.extname(absoluteInput));
4962
5719
  }
4963
5720
  return {
4964
5721
  title,
@@ -4977,6 +5734,7 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
4977
5734
  };
4978
5735
  }
4979
5736
  async function prepareUrlInput(rootDir, input, options) {
5737
+ await validateUrlSafety(input);
4980
5738
  const response = await fetch(input);
4981
5739
  if (!response.ok) {
4982
5740
  throw new Error(`Failed to fetch ${input}: ${response.status} ${response.statusText}`);
@@ -5034,7 +5792,7 @@ async function prepareUrlInput(rootDir, input, options) {
5034
5792
  sourceKind = "markdown";
5035
5793
  storedExtension = ".md";
5036
5794
  } else {
5037
- const extension = path10.extname(inputUrl.pathname);
5795
+ const extension = path11.extname(inputUrl.pathname);
5038
5796
  storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
5039
5797
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
5040
5798
  extractedText = payloadBytes.toString("utf8");
@@ -5064,6 +5822,11 @@ async function prepareUrlInput(rootDir, input, options) {
5064
5822
  const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
5065
5823
  extractedText = extracted.extractedText;
5066
5824
  extractionArtifact = extracted.artifact;
5825
+ } else if (sourceKind === "docx") {
5826
+ const extracted = await extractDocxText({ mimeType, bytes: payloadBytes });
5827
+ title = extracted.artifact.metadata?.title?.trim() || title;
5828
+ extractedText = extracted.extractedText;
5829
+ extractionArtifact = extracted.artifact;
5067
5830
  } else if (sourceKind === "image") {
5068
5831
  const extracted = await extractImageWithVision(rootDir, {
5069
5832
  title,
@@ -5097,17 +5860,17 @@ async function collectInboxAttachmentRefs(inputDir, files) {
5097
5860
  for (const absolutePath of files) {
5098
5861
  const mimeType = guessMimeType(absolutePath);
5099
5862
  const sourceKind = inferKind(mimeType, absolutePath);
5100
- if (sourceKind !== "markdown") {
5863
+ if (sourceKind !== "markdown" && sourceKind !== "html") {
5101
5864
  continue;
5102
5865
  }
5103
- const content = await fs9.readFile(absolutePath, "utf8");
5104
- const refs = extractMarkdownReferences(content);
5866
+ const content = await fs10.readFile(absolutePath, "utf8");
5867
+ const refs = sourceKind === "html" ? extractHtmlLocalReferences(content, pathToFileURL(absolutePath).toString()) : extractMarkdownReferences(content);
5105
5868
  if (!refs.length) {
5106
5869
  continue;
5107
5870
  }
5108
5871
  const sourceRefs = [];
5109
5872
  for (const ref of refs) {
5110
- const resolved = path10.resolve(path10.dirname(absolutePath), ref);
5873
+ const resolved = path11.resolve(path11.dirname(absolutePath), ref);
5111
5874
  if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
5112
5875
  continue;
5113
5876
  }
@@ -5140,13 +5903,52 @@ function rewriteMarkdownReferences(content, replacements) {
5140
5903
  return `${prefix}${replacement}${suffix}`;
5141
5904
  });
5142
5905
  }
5143
- async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
5144
- const originalBytes = await fs9.readFile(absolutePath);
5145
- const originalText = originalBytes.toString("utf8");
5146
- const title = titleFromText(path10.basename(absolutePath, path10.extname(absolutePath)), originalText);
5906
+ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
5907
+ const originalBytes = await fs10.readFile(absolutePath);
5908
+ const originalText = originalBytes.toString("utf8");
5909
+ const title = titleFromText(path11.basename(absolutePath, path11.extname(absolutePath)), originalText);
5910
+ const attachments = [];
5911
+ for (const attachmentRef of attachmentRefs) {
5912
+ const bytes = await fs10.readFile(attachmentRef.absolutePath);
5913
+ attachments.push({
5914
+ relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
5915
+ mimeType: guessMimeType(attachmentRef.absolutePath),
5916
+ originalPath: toPosix(attachmentRef.absolutePath),
5917
+ bytes
5918
+ });
5919
+ }
5920
+ const contentHash = buildCompositeHash(originalBytes, attachments);
5921
+ const sourceId = `${slugify(title)}-${contentHash.slice(0, 8)}`;
5922
+ const replacements = new Map(
5923
+ attachmentRefs.map((attachmentRef) => [
5924
+ attachmentRef.relativeRef.replace(/\\/g, "/"),
5925
+ `../assets/${sourceId}/${sanitizeAssetRelativePath(attachmentRef.relativeRef)}`
5926
+ ])
5927
+ );
5928
+ const rewrittenText = rewriteMarkdownReferences(originalText, replacements);
5929
+ const extractionArtifact = createPlainTextExtractionArtifact("markdown", "text/markdown");
5930
+ return {
5931
+ title,
5932
+ originType: "file",
5933
+ sourceKind: "markdown",
5934
+ originalPath: toPosix(absolutePath),
5935
+ mimeType: "text/markdown",
5936
+ storedExtension: path11.extname(absolutePath) || ".md",
5937
+ payloadBytes: Buffer.from(rewrittenText, "utf8"),
5938
+ extractedText: rewrittenText,
5939
+ extractionArtifact,
5940
+ extractionHash: buildExtractionHash(rewrittenText, extractionArtifact),
5941
+ attachments,
5942
+ contentHash
5943
+ };
5944
+ }
5945
+ async function prepareInboxHtmlInput(absolutePath, attachmentRefs) {
5946
+ const originalBytes = await fs10.readFile(absolutePath);
5947
+ const originalHtml = originalBytes.toString("utf8");
5948
+ const initialConversion = await convertHtmlToMarkdown(originalHtml, pathToFileURL(absolutePath).toString());
5147
5949
  const attachments = [];
5148
5950
  for (const attachmentRef of attachmentRefs) {
5149
- const bytes = await fs9.readFile(attachmentRef.absolutePath);
5951
+ const bytes = await fs10.readFile(attachmentRef.absolutePath);
5150
5952
  attachments.push({
5151
5953
  relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
5152
5954
  mimeType: guessMimeType(attachmentRef.absolutePath),
@@ -5155,6 +5957,8 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
5155
5957
  });
5156
5958
  }
5157
5959
  const contentHash = buildCompositeHash(originalBytes, attachments);
5960
+ const fallbackTitle = path11.basename(absolutePath, path11.extname(absolutePath));
5961
+ const title = initialConversion.title || fallbackTitle;
5158
5962
  const sourceId = `${slugify(title)}-${contentHash.slice(0, 8)}`;
5159
5963
  const replacements = new Map(
5160
5964
  attachmentRefs.map((attachmentRef) => [
@@ -5162,31 +5966,32 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
5162
5966
  `../assets/${sourceId}/${sanitizeAssetRelativePath(attachmentRef.relativeRef)}`
5163
5967
  ])
5164
5968
  );
5165
- const rewrittenText = rewriteMarkdownReferences(originalText, replacements);
5166
- const extractionArtifact = createPlainTextExtractionArtifact("markdown", "text/markdown");
5969
+ const rewrittenHtml = rewriteHtmlLocalReferences(originalHtml, pathToFileURL(absolutePath).toString(), replacements);
5970
+ const converted = rewrittenHtml === originalHtml ? initialConversion : await convertHtmlToMarkdown(rewrittenHtml, pathToFileURL(absolutePath).toString());
5971
+ const extractionArtifact = createHtmlReadabilityExtractionArtifact("html", "text/html");
5167
5972
  return {
5168
- title,
5973
+ title: converted.title || title,
5169
5974
  originType: "file",
5170
- sourceKind: "markdown",
5975
+ sourceKind: "html",
5171
5976
  originalPath: toPosix(absolutePath),
5172
- mimeType: "text/markdown",
5173
- storedExtension: path10.extname(absolutePath) || ".md",
5174
- payloadBytes: Buffer.from(rewrittenText, "utf8"),
5175
- extractedText: rewrittenText,
5977
+ mimeType: "text/html",
5978
+ storedExtension: path11.extname(absolutePath) || ".html",
5979
+ payloadBytes: Buffer.from(rewrittenHtml, "utf8"),
5980
+ extractedText: converted.markdown,
5176
5981
  extractionArtifact,
5177
- extractionHash: buildExtractionHash(rewrittenText, extractionArtifact),
5982
+ extractionHash: buildExtractionHash(converted.markdown, extractionArtifact),
5178
5983
  attachments,
5179
5984
  contentHash
5180
5985
  };
5181
5986
  }
5182
5987
  function isSupportedInboxKind(sourceKind) {
5183
- return ["markdown", "text", "html", "pdf", "image"].includes(sourceKind);
5988
+ return ["markdown", "text", "html", "pdf", "docx", "image"].includes(sourceKind);
5184
5989
  }
5185
5990
  async function ingestInput(rootDir, input, options) {
5186
5991
  const { paths } = await initWorkspace(rootDir);
5187
5992
  const normalizedOptions = normalizeIngestOptions(options);
5188
- const absoluteInput = path10.resolve(rootDir, input);
5189
- 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));
5190
5995
  const prepared = isHttpUrl(input) ? await prepareUrlInput(rootDir, input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
5191
5996
  const result = await persistPreparedInput(rootDir, prepared, paths);
5192
5997
  return result.manifest;
@@ -5276,7 +6081,7 @@ async function addInput(rootDir, input, options = {}) {
5276
6081
  async function ingestDirectory(rootDir, inputDir, options) {
5277
6082
  const { paths } = await initWorkspace(rootDir);
5278
6083
  const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
5279
- const absoluteInputDir = path10.resolve(rootDir, inputDir);
6084
+ const absoluteInputDir = path11.resolve(rootDir, inputDir);
5280
6085
  const repoRoot = normalizedOptions.repoRoot ?? await findNearestGitRoot2(absoluteInputDir) ?? absoluteInputDir;
5281
6086
  if (!await fileExists(absoluteInputDir)) {
5282
6087
  throw new Error(`Directory not found: ${absoluteInputDir}`);
@@ -5285,7 +6090,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
5285
6090
  const imported = [];
5286
6091
  const updated = [];
5287
6092
  for (const absolutePath of files) {
5288
- const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(repoRoot, absolutePath));
6093
+ const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
5289
6094
  const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
5290
6095
  const result = await persistPreparedInput(rootDir, prepared, paths);
5291
6096
  if (result.isNew) {
@@ -5293,11 +6098,11 @@ async function ingestDirectory(rootDir, inputDir, options) {
5293
6098
  } else if (result.wasUpdated) {
5294
6099
  updated.push(result.manifest);
5295
6100
  } else {
5296
- skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "duplicate_content" });
6101
+ skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "duplicate_content" });
5297
6102
  }
5298
6103
  }
5299
- await appendLogEntry(rootDir, "ingest_directory", toPosix(path10.relative(rootDir, absoluteInputDir)) || ".", [
5300
- `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)) || "."}`,
5301
6106
  `scanned=${files.length}`,
5302
6107
  `imported=${imported.length}`,
5303
6108
  `updated=${updated.length}`,
@@ -5314,7 +6119,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
5314
6119
  }
5315
6120
  async function importInbox(rootDir, inputDir) {
5316
6121
  const { paths } = await initWorkspace(rootDir);
5317
- const effectiveInputDir = path10.resolve(rootDir, inputDir ?? paths.inboxDir);
6122
+ const effectiveInputDir = path11.resolve(rootDir, inputDir ?? paths.inboxDir);
5318
6123
  if (!await fileExists(effectiveInputDir)) {
5319
6124
  throw new Error(`Inbox directory not found: ${effectiveInputDir}`);
5320
6125
  }
@@ -5325,31 +6130,31 @@ async function importInbox(rootDir, inputDir) {
5325
6130
  const skipped = [];
5326
6131
  let attachmentCount = 0;
5327
6132
  for (const absolutePath of files) {
5328
- const basename = path10.basename(absolutePath);
6133
+ const basename = path11.basename(absolutePath);
5329
6134
  if (basename.startsWith(".")) {
5330
- skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "hidden_file" });
6135
+ skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "hidden_file" });
5331
6136
  continue;
5332
6137
  }
5333
6138
  if (claimedAttachments.has(absolutePath)) {
5334
- skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
6139
+ skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
5335
6140
  continue;
5336
6141
  }
5337
6142
  const mimeType = guessMimeType(absolutePath);
5338
6143
  const sourceKind = inferKind(mimeType, absolutePath);
5339
6144
  if (!isSupportedInboxKind(sourceKind)) {
5340
- 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}` });
5341
6146
  continue;
5342
6147
  }
5343
- const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
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);
5344
6149
  const result = await persistPreparedInput(rootDir, prepared, paths);
5345
6150
  if (!result.isNew) {
5346
- skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "duplicate_content" });
6151
+ skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "duplicate_content" });
5347
6152
  continue;
5348
6153
  }
5349
6154
  attachmentCount += result.manifest.attachments?.length ?? 0;
5350
6155
  imported.push(result.manifest);
5351
6156
  }
5352
- await appendLogEntry(rootDir, "inbox_import", toPosix(path10.relative(rootDir, effectiveInputDir)) || ".", [
6157
+ await appendLogEntry(rootDir, "inbox_import", toPosix(path11.relative(rootDir, effectiveInputDir)) || ".", [
5353
6158
  `scanned=${files.length}`,
5354
6159
  `imported=${imported.length}`,
5355
6160
  `attachments=${attachmentCount}`,
@@ -5368,9 +6173,9 @@ async function listManifests(rootDir) {
5368
6173
  if (!await fileExists(paths.manifestsDir)) {
5369
6174
  return [];
5370
6175
  }
5371
- const entries = await fs9.readdir(paths.manifestsDir);
6176
+ const entries = await fs10.readdir(paths.manifestsDir);
5372
6177
  const manifests = await Promise.all(
5373
- 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)))
5374
6179
  );
5375
6180
  return manifests.filter((manifest) => Boolean(manifest));
5376
6181
  }
@@ -5378,17 +6183,17 @@ async function readExtractedText(rootDir, manifest) {
5378
6183
  if (!manifest.extractedTextPath) {
5379
6184
  return void 0;
5380
6185
  }
5381
- const absolutePath = path10.resolve(rootDir, manifest.extractedTextPath);
6186
+ const absolutePath = path11.resolve(rootDir, manifest.extractedTextPath);
5382
6187
  if (!await fileExists(absolutePath)) {
5383
6188
  return void 0;
5384
6189
  }
5385
- return fs9.readFile(absolutePath, "utf8");
6190
+ return fs10.readFile(absolutePath, "utf8");
5386
6191
  }
5387
6192
  async function readExtractionArtifact(rootDir, manifest) {
5388
6193
  if (!manifest.extractedMetadataPath) {
5389
6194
  return void 0;
5390
6195
  }
5391
- const absolutePath = path10.resolve(rootDir, manifest.extractedMetadataPath);
6196
+ const absolutePath = path11.resolve(rootDir, manifest.extractedMetadataPath);
5392
6197
  if (!await fileExists(absolutePath)) {
5393
6198
  return void 0;
5394
6199
  }
@@ -5396,20 +6201,20 @@ async function readExtractionArtifact(rootDir, manifest) {
5396
6201
  }
5397
6202
 
5398
6203
  // src/mcp.ts
5399
- import fs17 from "fs/promises";
5400
- import path21 from "path";
6204
+ import fs18 from "fs/promises";
6205
+ import path22 from "path";
5401
6206
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
5402
6207
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5403
6208
  import { z as z8 } from "zod";
5404
6209
 
5405
6210
  // src/schema.ts
5406
- import fs10 from "fs/promises";
5407
- import path11 from "path";
6211
+ import fs11 from "fs/promises";
6212
+ import path12 from "path";
5408
6213
  function normalizeSchemaContent(content) {
5409
6214
  return content.trim() ? content.trim() : defaultVaultSchema().trim();
5410
6215
  }
5411
6216
  async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
5412
- const content = await fileExists(schemaPath) ? await fs10.readFile(schemaPath, "utf8") : fallback;
6217
+ const content = await fileExists(schemaPath) ? await fs11.readFile(schemaPath, "utf8") : fallback;
5413
6218
  const normalized = normalizeSchemaContent(content);
5414
6219
  return {
5415
6220
  path: schemaPath,
@@ -5418,7 +6223,7 @@ async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
5418
6223
  };
5419
6224
  }
5420
6225
  function resolveProjectSchemaPath(rootDir, schemaPath) {
5421
- return path11.resolve(rootDir, schemaPath);
6226
+ return path12.resolve(rootDir, schemaPath);
5422
6227
  }
5423
6228
  function composeVaultSchema(root, projectSchemas = []) {
5424
6229
  if (!projectSchemas.length) {
@@ -5434,7 +6239,7 @@ function composeVaultSchema(root, projectSchemas = []) {
5434
6239
  (schema) => [
5435
6240
  `## Project Schema`,
5436
6241
  "",
5437
- `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)}`,
5438
6243
  "",
5439
6244
  schema.content
5440
6245
  ].join("\n")
@@ -5510,13 +6315,13 @@ function buildSchemaPrompt(schema, instruction) {
5510
6315
  }
5511
6316
 
5512
6317
  // src/vault.ts
5513
- import fs16 from "fs/promises";
5514
- import path20 from "path";
6318
+ import fs17 from "fs/promises";
6319
+ import path21 from "path";
5515
6320
  import matter9 from "gray-matter";
5516
6321
  import { z as z7 } from "zod";
5517
6322
 
5518
6323
  // src/analysis.ts
5519
- import path12 from "path";
6324
+ import path13 from "path";
5520
6325
  import { z as z2 } from "zod";
5521
6326
  var ANALYSIS_FORMAT_VERSION = 5;
5522
6327
  var sourceAnalysisSchema = z2.object({
@@ -5696,276 +6501,119 @@ ${truncate(text, 18e3)}`
5696
6501
  producedAt: (/* @__PURE__ */ new Date()).toISOString()
5697
6502
  };
5698
6503
  }
5699
- function analysisFromVisionExtraction(manifest, extraction, schemaHash) {
5700
- if (!extraction.vision) {
5701
- return null;
5702
- }
5703
- return {
5704
- analysisVersion: ANALYSIS_FORMAT_VERSION,
5705
- sourceId: manifest.sourceId,
5706
- sourceHash: manifest.contentHash,
5707
- extractionHash: manifest.extractionHash,
5708
- schemaHash,
5709
- title: extraction.vision.title?.trim() || manifest.title,
5710
- summary: extraction.vision.summary,
5711
- concepts: extraction.vision.concepts.map((term) => ({
5712
- id: `concept:${slugify(term.name)}`,
5713
- name: term.name,
5714
- description: term.description
5715
- })),
5716
- entities: extraction.vision.entities.map((term) => ({
5717
- id: `entity:${slugify(term.name)}`,
5718
- name: term.name,
5719
- description: term.description
5720
- })),
5721
- claims: extraction.vision.claims.map((claim, index) => ({
5722
- id: `claim:${manifest.sourceId}:${index + 1}`,
5723
- text: claim.text,
5724
- confidence: claim.confidence,
5725
- status: "extracted",
5726
- polarity: claim.polarity,
5727
- citation: manifest.sourceId
5728
- })),
5729
- questions: extraction.vision.questions,
5730
- rationales: [],
5731
- producedAt: (/* @__PURE__ */ new Date()).toISOString()
5732
- };
5733
- }
5734
- function extractionWarningSummary(manifest, extraction) {
5735
- const warning = extraction?.warnings?.find(Boolean);
5736
- if (warning) {
5737
- return `Imported ${manifest.sourceKind} source. ${warning}`;
5738
- }
5739
- return `Imported ${manifest.sourceKind} source. Text extraction is not yet available for this source.`;
5740
- }
5741
- async function analyzeSource(manifest, extractedText, provider, paths, schema) {
5742
- const cachePath = path12.join(paths.analysesDir, `${manifest.sourceId}.json`);
5743
- const cached = await readJsonFile(cachePath);
5744
- if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
5745
- return cached;
5746
- }
5747
- const extraction = await readExtractionArtifact(paths.rootDir, manifest);
5748
- const content = normalizeWhitespace(extractedText ?? "");
5749
- let analysis;
5750
- if (manifest.sourceKind === "code" && content) {
5751
- analysis = await analyzeCodeSource(manifest, extractedText ?? "", schema.hash);
5752
- } else if (manifest.sourceKind === "image") {
5753
- const visionAnalysis = extraction ? analysisFromVisionExtraction(manifest, extraction, schema.hash) : null;
5754
- if (visionAnalysis) {
5755
- analysis = visionAnalysis;
5756
- } else if (!content) {
5757
- analysis = {
5758
- analysisVersion: ANALYSIS_FORMAT_VERSION,
5759
- sourceId: manifest.sourceId,
5760
- sourceHash: manifest.contentHash,
5761
- extractionHash: manifest.extractionHash,
5762
- schemaHash: schema.hash,
5763
- title: manifest.title,
5764
- summary: extractionWarningSummary(manifest, extraction),
5765
- concepts: [],
5766
- entities: [],
5767
- claims: [],
5768
- questions: [],
5769
- rationales: [],
5770
- producedAt: (/* @__PURE__ */ new Date()).toISOString()
5771
- };
5772
- } else if (provider.type === "heuristic") {
5773
- analysis = heuristicAnalysis(manifest, content, schema.hash);
5774
- } else {
5775
- try {
5776
- analysis = await providerAnalysis(manifest, content, provider, schema);
5777
- } catch {
5778
- analysis = heuristicAnalysis(manifest, content, schema.hash);
5779
- }
5780
- }
5781
- } else if (!content) {
5782
- analysis = {
5783
- analysisVersion: ANALYSIS_FORMAT_VERSION,
5784
- sourceId: manifest.sourceId,
5785
- sourceHash: manifest.contentHash,
5786
- extractionHash: manifest.extractionHash,
5787
- schemaHash: schema.hash,
5788
- title: manifest.title,
5789
- summary: extractionWarningSummary(manifest, extraction),
5790
- concepts: [],
5791
- entities: [],
5792
- claims: [],
5793
- questions: [],
5794
- rationales: [],
5795
- producedAt: (/* @__PURE__ */ new Date()).toISOString()
5796
- };
5797
- } else if (provider.type === "heuristic") {
5798
- analysis = heuristicAnalysis(manifest, content, schema.hash);
5799
- } else {
5800
- try {
5801
- analysis = await providerAnalysis(manifest, content, provider, schema);
5802
- } catch {
5803
- analysis = heuristicAnalysis(manifest, content, schema.hash);
5804
- }
5805
- }
5806
- await writeJsonFile(cachePath, analysis);
5807
- return analysis;
5808
- }
5809
- function analysisSignature(analysis) {
5810
- return sha256(JSON.stringify(analysis));
5811
- }
5812
-
5813
- // src/benchmark.ts
5814
- var CHARS_PER_TOKEN = 4;
5815
- var DEFAULT_BENCHMARK_QUESTIONS = [
5816
- "How does this vault connect the main concepts?",
5817
- "Which pages bridge the biggest communities?",
5818
- "What are the core abstractions in this vault?",
5819
- "Where are the biggest knowledge gaps?",
5820
- "What evidence should I read first?"
5821
- ];
5822
- var RESEARCH_BENCHMARK_QUESTION = "Which research sources should I read first, and why?";
5823
- function nodeMap(graph) {
5824
- return new Map(graph.nodes.map((node) => [node.id, node]));
5825
- }
5826
- function pageMap(graph) {
5827
- return new Map(graph.pages.map((page) => [page.id, page]));
5828
- }
5829
- function estimateTokens(text) {
5830
- return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));
5831
- }
5832
- function estimateCorpusWords(texts) {
5833
- return texts.reduce((total, text) => total + normalizeWhitespace(text).split(/\s+/).filter(Boolean).length, 0);
5834
- }
5835
- function benchmarkQueryTokens(graph, queryResult, pageContentsById) {
5836
- const nodesById = nodeMap(graph);
5837
- const pagesById = pageMap(graph);
5838
- const edgeIds = new Set(queryResult.visitedEdgeIds);
5839
- const lines = [];
5840
- for (const pageId of queryResult.pageIds) {
5841
- const page = pagesById.get(pageId);
5842
- if (!page) {
5843
- continue;
5844
- }
5845
- const content = normalizeWhitespace(pageContentsById.get(pageId) ?? "").slice(0, 280);
5846
- lines.push(`PAGE ${page.title} path=${page.path} kind=${page.kind}`);
5847
- if (content) {
5848
- lines.push(`PAGE_BODY ${content}`);
5849
- }
5850
- }
5851
- for (const nodeId of queryResult.visitedNodeIds) {
5852
- const node = nodesById.get(nodeId);
5853
- if (!node) {
5854
- continue;
5855
- }
5856
- lines.push(`NODE ${node.label} type=${node.type} community=${node.communityId ?? "unassigned"} page=${node.pageId ?? "none"}`);
5857
- }
5858
- for (const edge of graph.edges) {
5859
- if (!edgeIds.has(edge.id)) {
5860
- continue;
5861
- }
5862
- const source = nodesById.get(edge.source)?.label ?? edge.source;
5863
- const target = nodesById.get(edge.target)?.label ?? edge.target;
5864
- lines.push(`EDGE ${source} --${edge.relation}/${edge.evidenceClass}/${edge.confidence.toFixed(2)}--> ${target}`);
5865
- }
5866
- const queryTokens = estimateTokens(lines.join("\n"));
5867
- return {
5868
- question: queryResult.question,
5869
- queryTokens,
5870
- reduction: 0,
5871
- visitedNodeIds: queryResult.visitedNodeIds,
5872
- visitedEdgeIds: queryResult.visitedEdgeIds,
5873
- pageIds: queryResult.pageIds
5874
- };
5875
- }
5876
- function graphHash(graph) {
5877
- const hashedPages = graph.pages.filter((page) => page.kind !== "graph_report" && page.kind !== "community_summary");
5878
- const normalized = JSON.stringify(
5879
- {
5880
- nodes: [...graph.nodes].map((node) => ({
5881
- id: node.id,
5882
- type: node.type,
5883
- label: node.label,
5884
- pageId: node.pageId ?? null,
5885
- sourceClass: node.sourceClass ?? null,
5886
- communityId: node.communityId ?? null,
5887
- degree: node.degree ?? null,
5888
- bridgeScore: node.bridgeScore ?? null,
5889
- isGodNode: node.isGodNode ?? false,
5890
- sourceIds: [...node.sourceIds].sort(),
5891
- projectIds: [...node.projectIds].sort()
5892
- })).sort((left, right) => left.id.localeCompare(right.id)),
5893
- edges: [...graph.edges].map((edge) => ({
5894
- id: edge.id,
5895
- source: edge.source,
5896
- target: edge.target,
5897
- relation: edge.relation,
5898
- status: edge.status,
5899
- evidenceClass: edge.evidenceClass,
5900
- similarityBasis: edge.similarityBasis ?? null,
5901
- confidence: edge.confidence,
5902
- provenance: [...edge.provenance].sort()
5903
- })).sort((left, right) => left.id.localeCompare(right.id)),
5904
- pages: [...hashedPages].map((page) => ({
5905
- id: page.id,
5906
- path: page.path,
5907
- kind: page.kind,
5908
- status: page.status,
5909
- sourceType: page.sourceType ?? null,
5910
- sourceClass: page.sourceClass ?? null,
5911
- sourceIds: [...page.sourceIds].sort(),
5912
- projectIds: [...page.projectIds].sort(),
5913
- nodeIds: [...page.nodeIds].sort()
5914
- })).sort((left, right) => left.id.localeCompare(right.id)),
5915
- communities: [...graph.communities ?? []].map((community) => ({
5916
- id: community.id,
5917
- label: community.label,
5918
- nodeIds: [...community.nodeIds].sort()
5919
- })).sort((left, right) => left.id.localeCompare(right.id))
5920
- },
5921
- null,
5922
- 0
5923
- );
5924
- return sha256(normalized);
5925
- }
5926
- function hasResearchSources(pages) {
5927
- return pages.some((page) => page.kind === "source" && Boolean(page.sourceType) && page.sourceType !== "url");
5928
- }
5929
- function defaultBenchmarkQuestionsForGraph(graph, maxQuestions = 3) {
5930
- const normalizedLimit = Math.max(1, Math.min(maxQuestions, DEFAULT_BENCHMARK_QUESTIONS.length));
5931
- const questions = [...DEFAULT_BENCHMARK_QUESTIONS];
5932
- if (hasResearchSources(graph.pages)) {
5933
- questions.unshift(RESEARCH_BENCHMARK_QUESTION);
6504
+ function analysisFromVisionExtraction(manifest, extraction, schemaHash) {
6505
+ if (!extraction.vision) {
6506
+ return null;
5934
6507
  }
5935
- return uniqueBy(questions, (item) => item).slice(0, normalizedLimit);
5936
- }
5937
- function buildBenchmarkArtifact(input) {
5938
- const corpusTokens = Math.max(1, Math.round(input.corpusWords * (100 / 75)));
5939
- const perQuestion = input.perQuestion.filter((entry) => entry.queryTokens > 0).map((entry) => ({
5940
- ...entry,
5941
- reduction: Number(Math.max(0, 1 - entry.queryTokens / Math.max(1, corpusTokens)).toFixed(3))
5942
- }));
5943
- const avgQueryTokens = perQuestion.length ? Math.max(1, Math.round(perQuestion.reduce((total, entry) => total + entry.queryTokens, 0) / perQuestion.length)) : 0;
5944
- const reductionRatio = avgQueryTokens ? Number(Math.max(0, 1 - avgQueryTokens / Math.max(1, corpusTokens)).toFixed(3)) : 0;
5945
- const uniqueVisitedNodes = new Set(perQuestion.flatMap((entry) => entry.visitedNodeIds)).size;
5946
- const summary = {
5947
- questionCount: input.questions.length,
5948
- uniqueVisitedNodes,
5949
- finalContextTokens: avgQueryTokens,
5950
- naiveCorpusTokens: corpusTokens,
5951
- avgReduction: reductionRatio,
5952
- reductionRatio
5953
- };
5954
6508
  return {
5955
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
5956
- graphHash: graphHash(input.graph),
5957
- corpusWords: input.corpusWords,
5958
- corpusTokens,
5959
- nodes: input.graph.nodes.length,
5960
- edges: input.graph.edges.length,
5961
- avgQueryTokens,
5962
- reductionRatio,
5963
- sampleQuestions: input.questions,
5964
- perQuestion,
5965
- questionResults: perQuestion,
5966
- summary
6509
+ analysisVersion: ANALYSIS_FORMAT_VERSION,
6510
+ sourceId: manifest.sourceId,
6511
+ sourceHash: manifest.contentHash,
6512
+ extractionHash: manifest.extractionHash,
6513
+ schemaHash,
6514
+ title: extraction.vision.title?.trim() || manifest.title,
6515
+ summary: extraction.vision.summary,
6516
+ concepts: extraction.vision.concepts.map((term) => ({
6517
+ id: `concept:${slugify(term.name)}`,
6518
+ name: term.name,
6519
+ description: term.description
6520
+ })),
6521
+ entities: extraction.vision.entities.map((term) => ({
6522
+ id: `entity:${slugify(term.name)}`,
6523
+ name: term.name,
6524
+ description: term.description
6525
+ })),
6526
+ claims: extraction.vision.claims.map((claim, index) => ({
6527
+ id: `claim:${manifest.sourceId}:${index + 1}`,
6528
+ text: claim.text,
6529
+ confidence: claim.confidence,
6530
+ status: "extracted",
6531
+ polarity: claim.polarity,
6532
+ citation: manifest.sourceId
6533
+ })),
6534
+ questions: extraction.vision.questions,
6535
+ rationales: [],
6536
+ producedAt: (/* @__PURE__ */ new Date()).toISOString()
5967
6537
  };
5968
6538
  }
6539
+ function extractionWarningSummary(manifest, extraction) {
6540
+ const warning = extraction?.warnings?.find(Boolean);
6541
+ if (warning) {
6542
+ return `Imported ${manifest.sourceKind} source. ${warning}`;
6543
+ }
6544
+ return `Imported ${manifest.sourceKind} source. Text extraction is not yet available for this source.`;
6545
+ }
6546
+ async function analyzeSource(manifest, extractedText, provider, paths, schema) {
6547
+ const cachePath = path13.join(paths.analysesDir, `${manifest.sourceId}.json`);
6548
+ const cached = await readJsonFile(cachePath);
6549
+ if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
6550
+ return cached;
6551
+ }
6552
+ const extraction = await readExtractionArtifact(paths.rootDir, manifest);
6553
+ const content = normalizeWhitespace(extractedText ?? "");
6554
+ let analysis;
6555
+ if (manifest.sourceKind === "code" && content) {
6556
+ analysis = await analyzeCodeSource(manifest, extractedText ?? "", schema.hash);
6557
+ } else if (manifest.sourceKind === "image") {
6558
+ const visionAnalysis = extraction ? analysisFromVisionExtraction(manifest, extraction, schema.hash) : null;
6559
+ if (visionAnalysis) {
6560
+ analysis = visionAnalysis;
6561
+ } else if (!content) {
6562
+ analysis = {
6563
+ analysisVersion: ANALYSIS_FORMAT_VERSION,
6564
+ sourceId: manifest.sourceId,
6565
+ sourceHash: manifest.contentHash,
6566
+ extractionHash: manifest.extractionHash,
6567
+ schemaHash: schema.hash,
6568
+ title: manifest.title,
6569
+ summary: extractionWarningSummary(manifest, extraction),
6570
+ concepts: [],
6571
+ entities: [],
6572
+ claims: [],
6573
+ questions: [],
6574
+ rationales: [],
6575
+ producedAt: (/* @__PURE__ */ new Date()).toISOString()
6576
+ };
6577
+ } else if (provider.type === "heuristic") {
6578
+ analysis = heuristicAnalysis(manifest, content, schema.hash);
6579
+ } else {
6580
+ try {
6581
+ analysis = await providerAnalysis(manifest, content, provider, schema);
6582
+ } catch {
6583
+ analysis = heuristicAnalysis(manifest, content, schema.hash);
6584
+ }
6585
+ }
6586
+ } else if (!content) {
6587
+ analysis = {
6588
+ analysisVersion: ANALYSIS_FORMAT_VERSION,
6589
+ sourceId: manifest.sourceId,
6590
+ sourceHash: manifest.contentHash,
6591
+ extractionHash: manifest.extractionHash,
6592
+ schemaHash: schema.hash,
6593
+ title: manifest.title,
6594
+ summary: extractionWarningSummary(manifest, extraction),
6595
+ concepts: [],
6596
+ entities: [],
6597
+ claims: [],
6598
+ questions: [],
6599
+ rationales: [],
6600
+ producedAt: (/* @__PURE__ */ new Date()).toISOString()
6601
+ };
6602
+ } else if (provider.type === "heuristic") {
6603
+ analysis = heuristicAnalysis(manifest, content, schema.hash);
6604
+ } else {
6605
+ try {
6606
+ analysis = await providerAnalysis(manifest, content, provider, schema);
6607
+ } catch {
6608
+ analysis = heuristicAnalysis(manifest, content, schema.hash);
6609
+ }
6610
+ }
6611
+ await writeJsonFile(cachePath, analysis);
6612
+ return analysis;
6613
+ }
6614
+ function analysisSignature(analysis) {
6615
+ return sha256(JSON.stringify(analysis));
6616
+ }
5969
6617
 
5970
6618
  // src/confidence.ts
5971
6619
  function nodeConfidence(sourceCount) {
@@ -5984,8 +6632,8 @@ function conflictConfidence(claimA, claimB) {
5984
6632
  }
5985
6633
 
5986
6634
  // src/deep-lint.ts
5987
- import fs11 from "fs/promises";
5988
- import path15 from "path";
6635
+ import fs12 from "fs/promises";
6636
+ import path16 from "path";
5989
6637
  import matter4 from "gray-matter";
5990
6638
  import { z as z5 } from "zod";
5991
6639
 
@@ -6006,7 +6654,7 @@ function normalizeFindingSeverity(value) {
6006
6654
 
6007
6655
  // src/orchestration.ts
6008
6656
  import { spawn } from "child_process";
6009
- import path13 from "path";
6657
+ import path14 from "path";
6010
6658
  import { z as z3 } from "zod";
6011
6659
  var orchestrationRoleResultSchema = z3.object({
6012
6660
  summary: z3.string().optional(),
@@ -6099,7 +6747,7 @@ async function runProviderRole(rootDir, role, roleConfig, input) {
6099
6747
  }
6100
6748
  async function runCommandRole(rootDir, role, executor, input) {
6101
6749
  const [command, ...args] = executor.command;
6102
- const cwd = executor.cwd ? path13.resolve(rootDir, executor.cwd) : rootDir;
6750
+ const cwd = executor.cwd ? path14.resolve(rootDir, executor.cwd) : rootDir;
6103
6751
  const child = spawn(command, args, {
6104
6752
  cwd,
6105
6753
  env: {
@@ -6193,7 +6841,7 @@ function summarizeRoleQuestions(results) {
6193
6841
  }
6194
6842
 
6195
6843
  // src/web-search/registry.ts
6196
- import path14 from "path";
6844
+ import path15 from "path";
6197
6845
  import { pathToFileURL as pathToFileURL2 } from "url";
6198
6846
  import { z as z4 } from "zod";
6199
6847
 
@@ -6291,7 +6939,7 @@ async function createWebSearchAdapter(id, config, rootDir) {
6291
6939
  if (!config.module) {
6292
6940
  throw new Error(`Web search provider ${id} is type "custom" but no module path was configured.`);
6293
6941
  }
6294
- 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);
6295
6943
  const loaded = await import(pathToFileURL2(resolvedModule).href);
6296
6944
  const parsed = customWebSearchModuleSchema.parse(loaded);
6297
6945
  return parsed.createAdapter(id, config, rootDir);
@@ -6351,8 +6999,8 @@ async function loadContextPages(rootDir, graph) {
6351
6999
  );
6352
7000
  return Promise.all(
6353
7001
  contextPages.slice(0, 18).map(async (page) => {
6354
- const absolutePath = path15.join(paths.wikiDir, page.path);
6355
- 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(() => "");
6356
7004
  const parsed = matter4(raw);
6357
7005
  return {
6358
7006
  id: page.id,
@@ -6400,7 +7048,7 @@ function heuristicDeepFindings(contextPages, structuralFindings, graph) {
6400
7048
  code: "missing_citation",
6401
7049
  message: finding.message,
6402
7050
  pagePath: finding.pagePath,
6403
- 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
6404
7052
  });
6405
7053
  }
6406
7054
  for (const page of contextPages.filter((item) => item.kind === "source").slice(0, 3)) {
@@ -6578,8 +7226,8 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
6578
7226
  }
6579
7227
 
6580
7228
  // src/embeddings.ts
6581
- import fs12 from "fs/promises";
6582
- import path16 from "path";
7229
+ import fs13 from "fs/promises";
7230
+ import path17 from "path";
6583
7231
  var MAX_EMBEDDING_BATCH = 32;
6584
7232
  var MAX_SIMILARITY_NODES = 240;
6585
7233
  function cosineSimilarity(left, right) {
@@ -6613,9 +7261,15 @@ async function loadPageContents(rootDir, graph) {
6613
7261
  const contents = /* @__PURE__ */ new Map();
6614
7262
  await Promise.all(
6615
7263
  graph.pages.map(async (page) => {
6616
- const absolutePath = path16.join(paths.wikiDir, page.path);
6617
- const content = await fs12.readFile(absolutePath, "utf8").catch(() => "");
6618
- contents.set(page.id, content);
7264
+ const absolutePath = path17.join(paths.wikiDir, page.path);
7265
+ const content = await fs13.readFile(absolutePath, "utf8").catch(() => {
7266
+ process.stderr.write(`[swarmvault] Warning: could not read page ${page.path} for embedding
7267
+ `);
7268
+ return "";
7269
+ });
7270
+ if (content) {
7271
+ contents.set(page.id, content);
7272
+ }
6619
7273
  })
6620
7274
  );
6621
7275
  return contents;
@@ -8110,15 +8764,15 @@ function sourceTypeForNode(node, pagesById) {
8110
8764
  return pagesById.get(node.pageId)?.sourceType;
8111
8765
  }
8112
8766
  function supportingPathDetails(graph, edge) {
8113
- const path25 = shortestGraphPath(graph, edge.source, edge.target);
8767
+ const path26 = shortestGraphPath(graph, edge.source, edge.target);
8114
8768
  const edgesById = new Map(graph.edges.map((item) => [item.id, item]));
8115
- 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));
8116
8770
  return {
8117
- pathNodeIds: path25.nodeIds,
8118
- pathEdgeIds: path25.edgeIds,
8771
+ pathNodeIds: path26.nodeIds,
8772
+ pathEdgeIds: path26.edgeIds,
8119
8773
  pathRelations: pathEdges.map((item) => item.relation),
8120
8774
  pathEvidenceClasses: pathEdges.map((item) => item.evidenceClass),
8121
- pathSummary: path25.summary
8775
+ pathSummary: path26.summary
8122
8776
  };
8123
8777
  }
8124
8778
  function surpriseScore(edge, graph, pagesById, hyperedgesByNodeId) {
@@ -8187,7 +8841,7 @@ function topSurprisingConnections(graph, pagesById) {
8187
8841
  }).map((edge) => {
8188
8842
  const source = nodesById.get(edge.source);
8189
8843
  const target = nodesById.get(edge.target);
8190
- const path25 = supportingPathDetails(graph, edge);
8844
+ const path26 = supportingPathDetails(graph, edge);
8191
8845
  const scored = surpriseScore(edge, graph, pagesById, hyperedgesByNodeId);
8192
8846
  return {
8193
8847
  id: edge.id,
@@ -8198,11 +8852,11 @@ function topSurprisingConnections(graph, pagesById) {
8198
8852
  relation: edge.relation,
8199
8853
  evidenceClass: edge.evidenceClass,
8200
8854
  confidence: edge.confidence,
8201
- pathNodeIds: path25.pathNodeIds,
8202
- pathEdgeIds: path25.pathEdgeIds,
8203
- pathRelations: path25.pathRelations,
8204
- pathEvidenceClasses: path25.pathEvidenceClasses,
8205
- pathSummary: path25.pathSummary,
8855
+ pathNodeIds: path26.pathNodeIds,
8856
+ pathEdgeIds: path26.pathEdgeIds,
8857
+ pathRelations: path26.pathRelations,
8858
+ pathEvidenceClasses: path26.pathEvidenceClasses,
8859
+ pathSummary: path26.pathSummary,
8206
8860
  why: scored.why,
8207
8861
  explanation: scored.explanation,
8208
8862
  surpriseScore: scored.score
@@ -9087,13 +9741,13 @@ function buildOutputAssetManifest(input) {
9087
9741
  }
9088
9742
 
9089
9743
  // src/outputs.ts
9090
- import fs14 from "fs/promises";
9091
- import path18 from "path";
9744
+ import fs15 from "fs/promises";
9745
+ import path19 from "path";
9092
9746
  import matter7 from "gray-matter";
9093
9747
 
9094
9748
  // src/pages.ts
9095
- import fs13 from "fs/promises";
9096
- import path17 from "path";
9749
+ import fs14 from "fs/promises";
9750
+ import path18 from "path";
9097
9751
  import matter6 from "gray-matter";
9098
9752
  function normalizeStringArray(value) {
9099
9753
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
@@ -9171,7 +9825,7 @@ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
9171
9825
  updatedAt: updatedFallback
9172
9826
  };
9173
9827
  }
9174
- const content = await fs13.readFile(absolutePath, "utf8");
9828
+ const content = await fs14.readFile(absolutePath, "utf8");
9175
9829
  const parsed = matter6(content);
9176
9830
  return {
9177
9831
  status: normalizePageStatus(parsed.data.status, defaults.status ?? "active"),
@@ -9210,7 +9864,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
9210
9864
  const now = (/* @__PURE__ */ new Date()).toISOString();
9211
9865
  const fallbackCreatedAt = defaults.createdAt ?? now;
9212
9866
  const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
9213
- 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");
9214
9868
  const kind = inferPageKind(relativePath, parsed.data.kind);
9215
9869
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
9216
9870
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
@@ -9251,18 +9905,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
9251
9905
  };
9252
9906
  }
9253
9907
  async function loadInsightPages(wikiDir) {
9254
- const insightsDir = path17.join(wikiDir, "insights");
9908
+ const insightsDir = path18.join(wikiDir, "insights");
9255
9909
  if (!await fileExists(insightsDir)) {
9256
9910
  return [];
9257
9911
  }
9258
- 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));
9259
9913
  const insights = [];
9260
9914
  for (const absolutePath of files) {
9261
- const relativePath = toPosix(path17.relative(wikiDir, absolutePath));
9262
- const content = await fs13.readFile(absolutePath, "utf8");
9915
+ const relativePath = toPosix(path18.relative(wikiDir, absolutePath));
9916
+ const content = await fs14.readFile(absolutePath, "utf8");
9263
9917
  const parsed = matter6(content);
9264
- const stats = await fs13.stat(absolutePath);
9265
- 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");
9266
9920
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
9267
9921
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
9268
9922
  const nodeIds = normalizeStringArray(parsed.data.node_ids);
@@ -9325,27 +9979,27 @@ function relatedOutputsForPage(targetPage, outputPages) {
9325
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);
9326
9980
  }
9327
9981
  async function resolveUniqueOutputSlug(wikiDir, baseSlug) {
9328
- const outputsDir = path18.join(wikiDir, "outputs");
9982
+ const outputsDir = path19.join(wikiDir, "outputs");
9329
9983
  const root = baseSlug || "output";
9330
9984
  let candidate = root;
9331
9985
  let counter = 2;
9332
- while (await fileExists(path18.join(outputsDir, `${candidate}.md`))) {
9986
+ while (await fileExists(path19.join(outputsDir, `${candidate}.md`))) {
9333
9987
  candidate = `${root}-${counter}`;
9334
9988
  counter++;
9335
9989
  }
9336
9990
  return candidate;
9337
9991
  }
9338
9992
  async function loadSavedOutputPages(wikiDir) {
9339
- const outputsDir = path18.join(wikiDir, "outputs");
9340
- 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(() => []);
9341
9995
  const outputs = [];
9342
9996
  for (const entry of entries) {
9343
9997
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md") {
9344
9998
  continue;
9345
9999
  }
9346
- const relativePath = path18.posix.join("outputs", entry.name);
9347
- const absolutePath = path18.join(outputsDir, entry.name);
9348
- 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");
9349
10003
  const parsed = matter7(content);
9350
10004
  const slug = entry.name.replace(/\.md$/, "");
9351
10005
  const title = typeof parsed.data.title === "string" ? parsed.data.title : slug;
@@ -9358,7 +10012,7 @@ async function loadSavedOutputPages(wikiDir) {
9358
10012
  const relatedSourceIds = normalizeStringArray(parsed.data.related_source_ids);
9359
10013
  const backlinks = normalizeStringArray(parsed.data.backlinks);
9360
10014
  const compiledFrom = normalizeStringArray(parsed.data.compiled_from);
9361
- const stats = await fs14.stat(absolutePath);
10015
+ const stats = await fs15.stat(absolutePath);
9362
10016
  const createdAt = typeof parsed.data.created_at === "string" ? parsed.data.created_at : stats.birthtimeMs > 0 ? stats.birthtime.toISOString() : stats.mtime.toISOString();
9363
10017
  const updatedAt = typeof parsed.data.updated_at === "string" ? parsed.data.updated_at : stats.mtime.toISOString();
9364
10018
  outputs.push({
@@ -9396,8 +10050,8 @@ async function loadSavedOutputPages(wikiDir) {
9396
10050
  }
9397
10051
 
9398
10052
  // src/search.ts
9399
- import fs15 from "fs/promises";
9400
- import path19 from "path";
10053
+ import fs16 from "fs/promises";
10054
+ import path20 from "path";
9401
10055
  import matter8 from "gray-matter";
9402
10056
  function getDatabaseSync() {
9403
10057
  const builtin = process.getBuiltinModule?.("node:sqlite");
@@ -9423,7 +10077,7 @@ function normalizeSourceClass2(value) {
9423
10077
  return value === "first_party" || value === "third_party" || value === "resource" || value === "generated" ? value : void 0;
9424
10078
  }
9425
10079
  async function rebuildSearchIndex(dbPath, pages, wikiDir) {
9426
- await ensureDir(path19.dirname(dbPath));
10080
+ await ensureDir(path20.dirname(dbPath));
9427
10081
  const DatabaseSync = getDatabaseSync();
9428
10082
  const db = new DatabaseSync(dbPath);
9429
10083
  db.exec("PRAGMA journal_mode = WAL;");
@@ -9455,8 +10109,8 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
9455
10109
  "INSERT INTO pages (id, path, title, body, kind, status, source_type, source_class, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
9456
10110
  );
9457
10111
  for (const page of pages) {
9458
- const absolutePath = path19.join(wikiDir, page.path);
9459
- const content = await fs15.readFile(absolutePath, "utf8");
10112
+ const absolutePath = path20.join(wikiDir, page.path);
10113
+ const content = await fs16.readFile(absolutePath, "utf8");
9460
10114
  const parsed = matter8(content);
9461
10115
  insertPage.run(
9462
10116
  page.id,
@@ -9573,7 +10227,7 @@ function outputFormatInstruction(format) {
9573
10227
  }
9574
10228
  }
9575
10229
  function outputAssetPath(slug, fileName) {
9576
- return toPosix(path20.join("outputs", "assets", slug, fileName));
10230
+ return toPosix(path21.join("outputs", "assets", slug, fileName));
9577
10231
  }
9578
10232
  function outputAssetId(slug, role) {
9579
10233
  return `output:${slug}:asset:${role}`;
@@ -9713,7 +10367,7 @@ async function resolveImageGenerationProvider(rootDir) {
9713
10367
  if (!providerConfig) {
9714
10368
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
9715
10369
  }
9716
- const { createProvider: createProvider2 } = await import("./registry-KLO5YIHP.js");
10370
+ const { createProvider: createProvider2 } = await import("./registry-G7NSRYCO.js");
9717
10371
  return createProvider2(preferredProviderId, providerConfig, rootDir);
9718
10372
  }
9719
10373
  async function generateOutputArtifacts(rootDir, input) {
@@ -9911,7 +10565,7 @@ async function generateOutputArtifacts(rootDir, input) {
9911
10565
  };
9912
10566
  }
9913
10567
  function normalizeProjectRoot(root) {
9914
- const normalized = toPosix(path20.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
10568
+ const normalized = toPosix(path21.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
9915
10569
  return normalized;
9916
10570
  }
9917
10571
  function projectEntries(config) {
@@ -9937,10 +10591,10 @@ function manifestPathForProject(rootDir, manifest) {
9937
10591
  if (!rawPath) {
9938
10592
  return toPosix(manifest.storedPath);
9939
10593
  }
9940
- if (!path20.isAbsolute(rawPath)) {
10594
+ if (!path21.isAbsolute(rawPath)) {
9941
10595
  return normalizeProjectRoot(rawPath);
9942
10596
  }
9943
- const relative = toPosix(path20.relative(rootDir, rawPath));
10597
+ const relative = toPosix(path21.relative(rootDir, rawPath));
9944
10598
  return relative.startsWith("..") ? toPosix(rawPath) : normalizeProjectRoot(relative);
9945
10599
  }
9946
10600
  function prefixMatches(value, prefix) {
@@ -10114,7 +10768,7 @@ function pageHashes(pages) {
10114
10768
  return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
10115
10769
  }
10116
10770
  async function buildManagedGraphPage(absolutePath, defaults, build) {
10117
- const existingContent = await fileExists(absolutePath) ? await fs16.readFile(absolutePath, "utf8") : null;
10771
+ const existingContent = await fileExists(absolutePath) ? await fs17.readFile(absolutePath, "utf8") : null;
10118
10772
  let existing = await loadExistingManagedPageState(absolutePath, {
10119
10773
  status: defaults.status ?? "active",
10120
10774
  managedBy: defaults.managedBy
@@ -10152,7 +10806,7 @@ async function buildManagedGraphPage(absolutePath, defaults, build) {
10152
10806
  return built;
10153
10807
  }
10154
10808
  async function buildManagedContent(absolutePath, defaults, build) {
10155
- const existingContent = await fileExists(absolutePath) ? await fs16.readFile(absolutePath, "utf8") : null;
10809
+ const existingContent = await fileExists(absolutePath) ? await fs17.readFile(absolutePath, "utf8") : null;
10156
10810
  let existing = await loadExistingManagedPageState(absolutePath, {
10157
10811
  status: defaults.status ?? "active",
10158
10812
  managedBy: defaults.managedBy
@@ -10274,8 +10928,64 @@ function deriveGraphMetrics(nodes, edges) {
10274
10928
  function resetGraphNodeMetrics(nodes) {
10275
10929
  return nodes.map(({ communityId: _communityId, degree: _degree, bridgeScore: _bridgeScore, isGodNode: _isGodNode, ...node }) => node);
10276
10930
  }
10931
+ function manifestRepoPath(manifest) {
10932
+ return toPosix(manifest.repoRelativePath ?? path21.basename(manifest.originalPath ?? manifest.storedPath));
10933
+ }
10934
+ function goPackageScopeKey(manifest, analysis) {
10935
+ if (analysis.code?.language !== "go") {
10936
+ return null;
10937
+ }
10938
+ const packageName = analysis.code.namespace?.trim();
10939
+ if (!packageName) {
10940
+ return null;
10941
+ }
10942
+ return `${packageName}:${path21.posix.dirname(manifestRepoPath(manifest))}`;
10943
+ }
10944
+ function buildGoPackageSymbolLookups(analyses, manifestsById) {
10945
+ const lookups = /* @__PURE__ */ new Map();
10946
+ for (const analysis of analyses) {
10947
+ if (analysis.code?.language !== "go") {
10948
+ continue;
10949
+ }
10950
+ const manifest = manifestsById.get(analysis.sourceId);
10951
+ if (!manifest) {
10952
+ continue;
10953
+ }
10954
+ const scopeKey = goPackageScopeKey(manifest, analysis);
10955
+ if (!scopeKey) {
10956
+ continue;
10957
+ }
10958
+ const current = lookups.get(scopeKey) ?? {
10959
+ byName: /* @__PURE__ */ new Map(),
10960
+ methodIdsByShortName: /* @__PURE__ */ new Map()
10961
+ };
10962
+ for (const symbol of analysis.code.symbols) {
10963
+ current.byName.set(symbol.name, symbol.id);
10964
+ const separator = symbol.name.lastIndexOf(".");
10965
+ if (separator > 0) {
10966
+ const shortName = symbol.name.slice(separator + 1);
10967
+ const matches = current.methodIdsByShortName.get(shortName) ?? /* @__PURE__ */ new Set();
10968
+ matches.add(symbol.id);
10969
+ current.methodIdsByShortName.set(shortName, matches);
10970
+ }
10971
+ }
10972
+ lookups.set(scopeKey, current);
10973
+ }
10974
+ return new Map(
10975
+ [...lookups.entries()].map(([scopeKey, value]) => [
10976
+ scopeKey,
10977
+ {
10978
+ byName: value.byName,
10979
+ uniqueMethodIdsByShortName: new Map(
10980
+ [...value.methodIdsByShortName.entries()].filter(([, ids]) => ids.size === 1).map(([shortName, ids]) => [shortName, [...ids][0]])
10981
+ )
10982
+ }
10983
+ ])
10984
+ );
10985
+ }
10277
10986
  function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
10278
10987
  const manifestsById = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
10988
+ const goPackageSymbolLookups = buildGoPackageSymbolLookups(analyses, manifestsById);
10279
10989
  const sourceNodes = manifests.map((manifest) => ({
10280
10990
  id: `source:${manifest.sourceId}`,
10281
10991
  type: "source",
@@ -10422,6 +11132,10 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
10422
11132
  }
10423
11133
  }
10424
11134
  const symbolIdsByName = new Map(analysis.code.symbols.map((symbol) => [symbol.name, symbol.id]));
11135
+ const goPackageLookup = analysis.code.language === "go" ? goPackageSymbolLookups.get(goPackageScopeKey(manifest, analysis) ?? "") : void 0;
11136
+ const localSymbolIdsByName = goPackageLookup?.byName ?? symbolIdsByName;
11137
+ const localGoMethodIdsByShortName = goPackageLookup?.uniqueMethodIdsByShortName ?? /* @__PURE__ */ new Map();
11138
+ const resolveLocalSymbolId = (targetName) => localSymbolIdsByName.get(targetName) ?? (analysis.code?.language === "go" ? localGoMethodIdsByShortName.get(targetName) : void 0);
10425
11139
  for (const rationale of analysis.rationales) {
10426
11140
  const targetSymbolId = rationale.symbolName ? symbolIdsByName.get(rationale.symbolName) : void 0;
10427
11141
  const targetId = targetSymbolId ?? moduleId;
@@ -10474,9 +11188,31 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
10474
11188
  }
10475
11189
  }
10476
11190
  }
11191
+ if (analysis.code.language === "go") {
11192
+ for (const symbol of analysis.code.symbols) {
11193
+ const separator = symbol.name.lastIndexOf(".");
11194
+ if (separator <= 0) {
11195
+ continue;
11196
+ }
11197
+ const receiverTypeId = localSymbolIdsByName.get(symbol.name.slice(0, separator));
11198
+ if (!receiverTypeId || receiverTypeId === symbol.id) {
11199
+ continue;
11200
+ }
11201
+ pushEdge({
11202
+ id: `${receiverTypeId}->${symbol.id}:defines:receiver`,
11203
+ source: receiverTypeId,
11204
+ target: symbol.id,
11205
+ relation: "defines",
11206
+ status: "extracted",
11207
+ evidenceClass: "extracted",
11208
+ confidence: 1,
11209
+ provenance: [analysis.sourceId]
11210
+ });
11211
+ }
11212
+ }
10477
11213
  for (const symbol of analysis.code.symbols) {
10478
11214
  for (const targetName of symbol.calls) {
10479
- const targetId = symbolIdsByName.get(targetName);
11215
+ const targetId = resolveLocalSymbolId(targetName);
10480
11216
  if (!targetId || targetId === symbol.id) {
10481
11217
  continue;
10482
11218
  }
@@ -10492,7 +11228,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
10492
11228
  });
10493
11229
  }
10494
11230
  for (const targetName of symbol.extends) {
10495
- const targetId = symbolIdsByName.get(targetName) ?? importedSymbolIdsByName.get(targetName);
11231
+ const targetId = resolveLocalSymbolId(targetName) ?? importedSymbolIdsByName.get(targetName);
10496
11232
  if (!targetId) {
10497
11233
  continue;
10498
11234
  }
@@ -10508,7 +11244,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
10508
11244
  });
10509
11245
  }
10510
11246
  for (const targetName of symbol.implements) {
10511
- const targetId = symbolIdsByName.get(targetName) ?? importedSymbolIdsByName.get(targetName);
11247
+ const targetId = resolveLocalSymbolId(targetName) ?? importedSymbolIdsByName.get(targetName);
10512
11248
  if (!targetId) {
10513
11249
  continue;
10514
11250
  }
@@ -10630,7 +11366,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
10630
11366
  const benchmark = await readJsonFile(paths.benchmarkPath);
10631
11367
  const communityRecords = [];
10632
11368
  for (const community of graph.communities ?? []) {
10633
- 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`);
10634
11370
  communityRecords.push(
10635
11371
  await buildManagedGraphPage(
10636
11372
  absolutePath,
@@ -10658,7 +11394,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
10658
11394
  recentResearchSources: recentResearchSourcePages(graph, previousCompiledAt),
10659
11395
  graphHash: graphHash(graph)
10660
11396
  });
10661
- const reportAbsolutePath = path20.join(paths.wikiDir, "graph", "report.md");
11397
+ const reportAbsolutePath = path21.join(paths.wikiDir, "graph", "report.md");
10662
11398
  const reportRecord = await buildManagedGraphPage(
10663
11399
  reportAbsolutePath,
10664
11400
  {
@@ -10679,7 +11415,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
10679
11415
  };
10680
11416
  }
10681
11417
  async function writePage(wikiDir, relativePath, content, changedPages) {
10682
- const absolutePath = path20.resolve(wikiDir, relativePath);
11418
+ const absolutePath = path21.resolve(wikiDir, relativePath);
10683
11419
  const changed = await writeFileIfChanged(absolutePath, content);
10684
11420
  if (changed) {
10685
11421
  changedPages.push(relativePath);
@@ -10742,29 +11478,29 @@ async function requiredCompileArtifactsExist(paths) {
10742
11478
  paths.graphPath,
10743
11479
  paths.codeIndexPath,
10744
11480
  paths.searchDbPath,
10745
- path20.join(paths.wikiDir, "index.md"),
10746
- path20.join(paths.wikiDir, "sources", "index.md"),
10747
- path20.join(paths.wikiDir, "code", "index.md"),
10748
- path20.join(paths.wikiDir, "concepts", "index.md"),
10749
- path20.join(paths.wikiDir, "entities", "index.md"),
10750
- path20.join(paths.wikiDir, "outputs", "index.md"),
10751
- path20.join(paths.wikiDir, "projects", "index.md"),
10752
- 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")
10753
11489
  ];
10754
11490
  const checks = await Promise.all(requiredPaths.map((filePath) => fileExists(filePath)));
10755
11491
  return checks.every(Boolean);
10756
11492
  }
10757
11493
  async function loadAvailableCachedAnalyses(paths, manifests) {
10758
11494
  const analyses = await Promise.all(
10759
- 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`)))
10760
11496
  );
10761
11497
  return analyses.filter((analysis) => Boolean(analysis));
10762
11498
  }
10763
11499
  function approvalManifestPath(paths, approvalId) {
10764
- return path20.join(paths.approvalsDir, approvalId, "manifest.json");
11500
+ return path21.join(paths.approvalsDir, approvalId, "manifest.json");
10765
11501
  }
10766
11502
  function approvalGraphPath(paths, approvalId) {
10767
- return path20.join(paths.approvalsDir, approvalId, "state", "graph.json");
11503
+ return path21.join(paths.approvalsDir, approvalId, "state", "graph.json");
10768
11504
  }
10769
11505
  async function readApprovalManifest(paths, approvalId) {
10770
11506
  const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
@@ -10774,7 +11510,7 @@ async function readApprovalManifest(paths, approvalId) {
10774
11510
  return manifest;
10775
11511
  }
10776
11512
  async function writeApprovalManifest(paths, manifest) {
10777
- 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)}
10778
11514
  `, "utf8");
10779
11515
  }
10780
11516
  async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
@@ -10789,7 +11525,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
10789
11525
  continue;
10790
11526
  }
10791
11527
  const previousPage = previousPagesById.get(nextPage.id);
10792
- const currentExists = await fileExists(path20.join(paths.wikiDir, file.relativePath));
11528
+ const currentExists = await fileExists(path21.join(paths.wikiDir, file.relativePath));
10793
11529
  if (previousPage && previousPage.path !== nextPage.path) {
10794
11530
  entries.push({
10795
11531
  pageId: nextPage.id,
@@ -10822,7 +11558,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
10822
11558
  const previousPage = previousPagesByPath.get(deletedPath);
10823
11559
  entries.push({
10824
11560
  pageId: previousPage?.id ?? `page:${slugify(deletedPath)}`,
10825
- title: previousPage?.title ?? path20.basename(deletedPath, ".md"),
11561
+ title: previousPage?.title ?? path21.basename(deletedPath, ".md"),
10826
11562
  kind: previousPage?.kind ?? "index",
10827
11563
  changeType: "delete",
10828
11564
  status: "pending",
@@ -10834,16 +11570,16 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
10834
11570
  }
10835
11571
  async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGraph, graph) {
10836
11572
  const approvalId = `compile-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
10837
- const approvalDir = path20.join(paths.approvalsDir, approvalId);
11573
+ const approvalDir = path21.join(paths.approvalsDir, approvalId);
10838
11574
  await ensureDir(approvalDir);
10839
- await ensureDir(path20.join(approvalDir, "wiki"));
10840
- await ensureDir(path20.join(approvalDir, "state"));
11575
+ await ensureDir(path21.join(approvalDir, "wiki"));
11576
+ await ensureDir(path21.join(approvalDir, "state"));
10841
11577
  for (const file of changedFiles) {
10842
- const targetPath = path20.join(approvalDir, "wiki", file.relativePath);
10843
- await ensureDir(path20.dirname(targetPath));
10844
- 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");
10845
11581
  }
10846
- 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");
10847
11583
  await writeApprovalManifest(paths, {
10848
11584
  approvalId,
10849
11585
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10905,7 +11641,7 @@ async function syncVaultArtifacts(rootDir, input) {
10905
11641
  confidence: 1
10906
11642
  });
10907
11643
  const sourceRecord = await buildManagedGraphPage(
10908
- path20.join(paths.wikiDir, preview.path),
11644
+ path21.join(paths.wikiDir, preview.path),
10909
11645
  {
10910
11646
  managedBy: "system",
10911
11647
  confidence: 1,
@@ -10951,7 +11687,7 @@ async function syncVaultArtifacts(rootDir, input) {
10951
11687
  );
10952
11688
  records.push(
10953
11689
  await buildManagedGraphPage(
10954
- path20.join(paths.wikiDir, modulePreview.path),
11690
+ path21.join(paths.wikiDir, modulePreview.path),
10955
11691
  {
10956
11692
  managedBy: "system",
10957
11693
  confidence: 1,
@@ -10985,8 +11721,8 @@ async function syncVaultArtifacts(rootDir, input) {
10985
11721
  const relativePath = promoted ? activeAggregatePath(itemKind, slug) : candidatePagePathFor(itemKind, slug);
10986
11722
  const aggregateSourceClass2 = aggregateManifestSourceClass(input.manifests, sourceIds);
10987
11723
  const fallbackPaths = [
10988
- path20.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
10989
- path20.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
11724
+ path21.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
11725
+ path21.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
10990
11726
  ];
10991
11727
  const confidence = nodeConfidence(aggregate.sourceAnalyses.length);
10992
11728
  const preview = emptyGraphPage({
@@ -11004,7 +11740,7 @@ async function syncVaultArtifacts(rootDir, input) {
11004
11740
  status: promoted ? "active" : "candidate"
11005
11741
  });
11006
11742
  const pageRecord = await buildManagedGraphPage(
11007
- path20.join(paths.wikiDir, relativePath),
11743
+ path21.join(paths.wikiDir, relativePath),
11008
11744
  {
11009
11745
  status: promoted ? "active" : "candidate",
11010
11746
  managedBy: "system",
@@ -11099,7 +11835,7 @@ async function syncVaultArtifacts(rootDir, input) {
11099
11835
  confidence: 1
11100
11836
  }),
11101
11837
  content: await buildManagedContent(
11102
- path20.join(paths.wikiDir, "projects", "index.md"),
11838
+ path21.join(paths.wikiDir, "projects", "index.md"),
11103
11839
  {
11104
11840
  managedBy: "system",
11105
11841
  compiledFrom: indexCompiledFrom(projectIndexRefs)
@@ -11123,7 +11859,7 @@ async function syncVaultArtifacts(rootDir, input) {
11123
11859
  records.push({
11124
11860
  page: projectIndexRef,
11125
11861
  content: await buildManagedContent(
11126
- path20.join(paths.wikiDir, projectIndexRef.path),
11862
+ path21.join(paths.wikiDir, projectIndexRef.path),
11127
11863
  {
11128
11864
  managedBy: "system",
11129
11865
  compiledFrom: indexCompiledFrom(Object.values(sections).flat())
@@ -11151,7 +11887,7 @@ async function syncVaultArtifacts(rootDir, input) {
11151
11887
  confidence: 1
11152
11888
  }),
11153
11889
  content: await buildManagedContent(
11154
- path20.join(paths.wikiDir, "index.md"),
11890
+ path21.join(paths.wikiDir, "index.md"),
11155
11891
  {
11156
11892
  managedBy: "system",
11157
11893
  compiledFrom: indexCompiledFrom(allPages)
@@ -11182,7 +11918,7 @@ async function syncVaultArtifacts(rootDir, input) {
11182
11918
  confidence: 1
11183
11919
  }),
11184
11920
  content: await buildManagedContent(
11185
- path20.join(paths.wikiDir, relativePath),
11921
+ path21.join(paths.wikiDir, relativePath),
11186
11922
  {
11187
11923
  managedBy: "system",
11188
11924
  compiledFrom: indexCompiledFrom(pages)
@@ -11193,12 +11929,12 @@ async function syncVaultArtifacts(rootDir, input) {
11193
11929
  }
11194
11930
  const nextPagePaths = new Set(records.map((record) => record.page.path));
11195
11931
  const obsoleteGraphPaths = (previousGraph?.pages ?? []).filter((page) => page.kind !== "output" && page.kind !== "insight").map((page) => page.path).filter((relativePath) => !nextPagePaths.has(relativePath));
11196
- 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));
11197
11933
  const obsoletePaths = uniqueStrings3([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
11198
11934
  const changedFiles = [];
11199
11935
  for (const record of records) {
11200
- const absolutePath = path20.join(paths.wikiDir, record.page.path);
11201
- 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;
11202
11938
  if (current !== record.content) {
11203
11939
  changedPages.push(record.page.path);
11204
11940
  changedFiles.push({ relativePath: record.page.path, content: record.content });
@@ -11223,10 +11959,10 @@ async function syncVaultArtifacts(rootDir, input) {
11223
11959
  await writePage(paths.wikiDir, record.page.path, record.content, writeChanges);
11224
11960
  }
11225
11961
  for (const relativePath of obsoletePaths) {
11226
- await fs16.rm(path20.join(paths.wikiDir, relativePath), { force: true });
11962
+ await fs17.rm(path21.join(paths.wikiDir, relativePath), { force: true });
11227
11963
  }
11228
11964
  await writeJsonFile(paths.graphPath, graph);
11229
- await writeJsonFile(path20.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
11965
+ await writeJsonFile(path21.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
11230
11966
  await writeJsonFile(paths.codeIndexPath, input.codeIndex);
11231
11967
  await writeJsonFile(paths.compileStatePath, {
11232
11968
  generatedAt: graph.generatedAt,
@@ -11297,17 +12033,17 @@ async function refreshIndexesAndSearch(rootDir, pages) {
11297
12033
  })
11298
12034
  );
11299
12035
  await Promise.all([
11300
- ensureDir(path20.join(paths.wikiDir, "sources")),
11301
- ensureDir(path20.join(paths.wikiDir, "code")),
11302
- ensureDir(path20.join(paths.wikiDir, "concepts")),
11303
- ensureDir(path20.join(paths.wikiDir, "entities")),
11304
- ensureDir(path20.join(paths.wikiDir, "outputs")),
11305
- ensureDir(path20.join(paths.wikiDir, "graph")),
11306
- ensureDir(path20.join(paths.wikiDir, "graph", "communities")),
11307
- ensureDir(path20.join(paths.wikiDir, "projects")),
11308
- 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"))
11309
12045
  ]);
11310
- const projectsIndexPath = path20.join(paths.wikiDir, "projects", "index.md");
12046
+ const projectsIndexPath = path21.join(paths.wikiDir, "projects", "index.md");
11311
12047
  await writeFileIfChanged(
11312
12048
  projectsIndexPath,
11313
12049
  await buildManagedContent(
@@ -11328,7 +12064,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
11328
12064
  outputs: pages.filter((page) => page.kind === "output" && page.projectIds.includes(project.id)),
11329
12065
  candidates: pages.filter((page) => page.status === "candidate" && page.projectIds.includes(project.id))
11330
12066
  };
11331
- const absolutePath = path20.join(paths.wikiDir, "projects", project.id, "index.md");
12067
+ const absolutePath = path21.join(paths.wikiDir, "projects", project.id, "index.md");
11332
12068
  await writeFileIfChanged(
11333
12069
  absolutePath,
11334
12070
  await buildManagedContent(
@@ -11346,7 +12082,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
11346
12082
  )
11347
12083
  );
11348
12084
  }
11349
- const rootIndexPath = path20.join(paths.wikiDir, "index.md");
12085
+ const rootIndexPath = path21.join(paths.wikiDir, "index.md");
11350
12086
  await writeFileIfChanged(
11351
12087
  rootIndexPath,
11352
12088
  await buildManagedContent(
@@ -11367,7 +12103,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
11367
12103
  ["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
11368
12104
  ["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
11369
12105
  ]) {
11370
- const absolutePath = path20.join(paths.wikiDir, relativePath);
12106
+ const absolutePath = path21.join(paths.wikiDir, relativePath);
11371
12107
  await writeFileIfChanged(
11372
12108
  absolutePath,
11373
12109
  await buildManagedContent(
@@ -11381,23 +12117,23 @@ async function refreshIndexesAndSearch(rootDir, pages) {
11381
12117
  );
11382
12118
  }
11383
12119
  for (const record of graphOrientation.records) {
11384
- await writeFileIfChanged(path20.join(paths.wikiDir, record.page.path), record.content);
12120
+ await writeFileIfChanged(path21.join(paths.wikiDir, record.page.path), record.content);
11385
12121
  }
11386
12122
  if (graphOrientation.report) {
11387
- await writeJsonFile(path20.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
12123
+ await writeJsonFile(path21.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
11388
12124
  }
11389
- 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)));
11390
12126
  const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
11391
12127
  "projects/index.md",
11392
12128
  ...configuredProjects.map((project) => `projects/${project.id}/index.md`)
11393
12129
  ]);
11394
12130
  await Promise.all(
11395
- 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 }))
11396
12132
  );
11397
- 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)));
11398
12134
  const allowedGraphPages = /* @__PURE__ */ new Set(["graph/index.md", ...graphOrientation.records.map((record) => record.page.path)]);
11399
12135
  await Promise.all(
11400
- 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 }))
11401
12137
  );
11402
12138
  await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
11403
12139
  }
@@ -11417,7 +12153,7 @@ async function prepareOutputPageSave(rootDir, input) {
11417
12153
  confidence: 0.74
11418
12154
  }
11419
12155
  });
11420
- const absolutePath = path20.join(paths.wikiDir, output.page.path);
12156
+ const absolutePath = path21.join(paths.wikiDir, output.page.path);
11421
12157
  return {
11422
12158
  page: output.page,
11423
12159
  savedPath: absolutePath,
@@ -11429,15 +12165,15 @@ async function prepareOutputPageSave(rootDir, input) {
11429
12165
  async function persistOutputPage(rootDir, input) {
11430
12166
  const { paths } = await loadVaultConfig(rootDir);
11431
12167
  const prepared = await prepareOutputPageSave(rootDir, input);
11432
- await ensureDir(path20.dirname(prepared.savedPath));
11433
- await fs16.writeFile(prepared.savedPath, prepared.content, "utf8");
12168
+ await ensureDir(path21.dirname(prepared.savedPath));
12169
+ await fs17.writeFile(prepared.savedPath, prepared.content, "utf8");
11434
12170
  for (const assetFile of prepared.assetFiles) {
11435
- const assetPath = path20.join(paths.wikiDir, assetFile.relativePath);
11436
- await ensureDir(path20.dirname(assetPath));
12171
+ const assetPath = path21.join(paths.wikiDir, assetFile.relativePath);
12172
+ await ensureDir(path21.dirname(assetPath));
11437
12173
  if (typeof assetFile.content === "string") {
11438
- await fs16.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
12174
+ await fs17.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
11439
12175
  } else {
11440
- await fs16.writeFile(assetPath, assetFile.content);
12176
+ await fs17.writeFile(assetPath, assetFile.content);
11441
12177
  }
11442
12178
  }
11443
12179
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -11458,7 +12194,7 @@ async function prepareExploreHubSave(rootDir, input) {
11458
12194
  confidence: 0.76
11459
12195
  }
11460
12196
  });
11461
- const absolutePath = path20.join(paths.wikiDir, hub.page.path);
12197
+ const absolutePath = path21.join(paths.wikiDir, hub.page.path);
11462
12198
  return {
11463
12199
  page: hub.page,
11464
12200
  savedPath: absolutePath,
@@ -11470,15 +12206,15 @@ async function prepareExploreHubSave(rootDir, input) {
11470
12206
  async function persistExploreHub(rootDir, input) {
11471
12207
  const { paths } = await loadVaultConfig(rootDir);
11472
12208
  const prepared = await prepareExploreHubSave(rootDir, input);
11473
- await ensureDir(path20.dirname(prepared.savedPath));
11474
- await fs16.writeFile(prepared.savedPath, prepared.content, "utf8");
12209
+ await ensureDir(path21.dirname(prepared.savedPath));
12210
+ await fs17.writeFile(prepared.savedPath, prepared.content, "utf8");
11475
12211
  for (const assetFile of prepared.assetFiles) {
11476
- const assetPath = path20.join(paths.wikiDir, assetFile.relativePath);
11477
- await ensureDir(path20.dirname(assetPath));
12212
+ const assetPath = path21.join(paths.wikiDir, assetFile.relativePath);
12213
+ await ensureDir(path21.dirname(assetPath));
11478
12214
  if (typeof assetFile.content === "string") {
11479
- await fs16.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
12215
+ await fs17.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
11480
12216
  } else {
11481
- await fs16.writeFile(assetPath, assetFile.content);
12217
+ await fs17.writeFile(assetPath, assetFile.content);
11482
12218
  }
11483
12219
  }
11484
12220
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -11495,17 +12231,17 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
11495
12231
  }))
11496
12232
  ]);
11497
12233
  const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
11498
- const approvalDir = path20.join(paths.approvalsDir, approvalId);
12234
+ const approvalDir = path21.join(paths.approvalsDir, approvalId);
11499
12235
  await ensureDir(approvalDir);
11500
- await ensureDir(path20.join(approvalDir, "wiki"));
11501
- await ensureDir(path20.join(approvalDir, "state"));
12236
+ await ensureDir(path21.join(approvalDir, "wiki"));
12237
+ await ensureDir(path21.join(approvalDir, "state"));
11502
12238
  for (const file of changedFiles) {
11503
- const targetPath = path20.join(approvalDir, "wiki", file.relativePath);
11504
- await ensureDir(path20.dirname(targetPath));
12239
+ const targetPath = path21.join(approvalDir, "wiki", file.relativePath);
12240
+ await ensureDir(path21.dirname(targetPath));
11505
12241
  if ("binary" in file && file.binary) {
11506
- await fs16.writeFile(targetPath, Buffer.from(file.content, "base64"));
12242
+ await fs17.writeFile(targetPath, Buffer.from(file.content, "base64"));
11507
12243
  } else {
11508
- await fs16.writeFile(targetPath, file.content, "utf8");
12244
+ await fs17.writeFile(targetPath, file.content, "utf8");
11509
12245
  }
11510
12246
  }
11511
12247
  const nextPages = sortGraphPages([
@@ -11520,7 +12256,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
11520
12256
  sources: previousGraph?.sources ?? [],
11521
12257
  pages: nextPages
11522
12258
  };
11523
- 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");
11524
12260
  await writeApprovalManifest(paths, {
11525
12261
  approvalId,
11526
12262
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11549,9 +12285,9 @@ async function executeQuery(rootDir, question, format) {
11549
12285
  const searchResults = searchPages(paths.searchDbPath, question, 5);
11550
12286
  const excerpts = await Promise.all(
11551
12287
  searchResults.map(async (result) => {
11552
- const absolutePath = path20.join(paths.wikiDir, result.path);
12288
+ const absolutePath = path21.join(paths.wikiDir, result.path);
11553
12289
  try {
11554
- const content = await fs16.readFile(absolutePath, "utf8");
12290
+ const content = await fs17.readFile(absolutePath, "utf8");
11555
12291
  const parsed = matter9(content);
11556
12292
  return `# ${result.title}
11557
12293
  ${truncate(normalizeWhitespace(parsed.content), 1200)}`;
@@ -11733,7 +12469,7 @@ function sortGraphPages(pages) {
11733
12469
  async function listApprovals(rootDir) {
11734
12470
  const { paths } = await loadVaultConfig(rootDir);
11735
12471
  const manifests = await Promise.all(
11736
- (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) => {
11737
12473
  try {
11738
12474
  return await readApprovalManifest(paths, entry.name);
11739
12475
  } catch {
@@ -11749,8 +12485,8 @@ async function readApproval(rootDir, approvalId) {
11749
12485
  const details = await Promise.all(
11750
12486
  manifest.entries.map(async (entry) => {
11751
12487
  const currentPath = entry.previousPath ?? entry.nextPath;
11752
- const currentContent = currentPath ? await fs16.readFile(path20.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
11753
- 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;
11754
12490
  return {
11755
12491
  ...entry,
11756
12492
  currentContent,
@@ -11778,26 +12514,26 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
11778
12514
  if (!entry.nextPath) {
11779
12515
  throw new Error(`Approval entry ${entry.pageId} is missing a staged path.`);
11780
12516
  }
11781
- const stagedAbsolutePath = path20.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
11782
- const stagedContent = await fs16.readFile(stagedAbsolutePath, "utf8");
11783
- const targetAbsolutePath = path20.join(paths.wikiDir, entry.nextPath);
11784
- await ensureDir(path20.dirname(targetAbsolutePath));
11785
- 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");
11786
12522
  if (entry.changeType === "promote" && entry.previousPath) {
11787
- await fs16.rm(path20.join(paths.wikiDir, entry.previousPath), { force: true });
12523
+ await fs17.rm(path21.join(paths.wikiDir, entry.previousPath), { force: true });
11788
12524
  }
11789
12525
  const nextPage = bundleGraph?.pages.find((page) => page.id === entry.pageId && page.path === entry.nextPath) ?? parseStoredPage(entry.nextPath, stagedContent);
11790
12526
  if (nextPage.kind === "output" && nextPage.outputAssets?.length) {
11791
- const outputAssetDir = path20.join(paths.wikiDir, "outputs", "assets", path20.basename(nextPage.path, ".md"));
11792
- 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 });
11793
12529
  for (const asset of nextPage.outputAssets) {
11794
- const stagedAssetPath = path20.join(paths.approvalsDir, approvalId, "wiki", asset.path);
12530
+ const stagedAssetPath = path21.join(paths.approvalsDir, approvalId, "wiki", asset.path);
11795
12531
  if (!await fileExists(stagedAssetPath)) {
11796
12532
  continue;
11797
12533
  }
11798
- const targetAssetPath = path20.join(paths.wikiDir, asset.path);
11799
- await ensureDir(path20.dirname(targetAssetPath));
11800
- 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);
11801
12537
  }
11802
12538
  }
11803
12539
  nextPages = nextPages.filter(
@@ -11808,10 +12544,10 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
11808
12544
  } else {
11809
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;
11810
12546
  if (entry.previousPath) {
11811
- await fs16.rm(path20.join(paths.wikiDir, entry.previousPath), { force: true });
12547
+ await fs17.rm(path21.join(paths.wikiDir, entry.previousPath), { force: true });
11812
12548
  }
11813
12549
  if (deletedPage?.kind === "output") {
11814
- 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")), {
11815
12551
  recursive: true,
11816
12552
  force: true
11817
12553
  });
@@ -11902,7 +12638,7 @@ async function promoteCandidate(rootDir, target) {
11902
12638
  const { paths } = await loadVaultConfig(rootDir);
11903
12639
  const graph = await readJsonFile(paths.graphPath);
11904
12640
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
11905
- 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");
11906
12642
  const parsed = matter9(raw);
11907
12643
  const nextUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
11908
12644
  const nextContent = matter9.stringify(parsed.content, {
@@ -11914,10 +12650,10 @@ async function promoteCandidate(rootDir, target) {
11914
12650
  )
11915
12651
  });
11916
12652
  const nextPath = candidateActivePath(candidate);
11917
- const nextAbsolutePath = path20.join(paths.wikiDir, nextPath);
11918
- await ensureDir(path20.dirname(nextAbsolutePath));
11919
- await fs16.writeFile(nextAbsolutePath, nextContent, "utf8");
11920
- 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 });
11921
12657
  const nextPage = parseStoredPage(nextPath, nextContent, { createdAt: candidate.createdAt, updatedAt: nextUpdatedAt });
11922
12658
  const nextPages = sortGraphPages(
11923
12659
  (graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path).concat(nextPage)
@@ -11962,7 +12698,7 @@ async function archiveCandidate(rootDir, target) {
11962
12698
  const { paths } = await loadVaultConfig(rootDir);
11963
12699
  const graph = await readJsonFile(paths.graphPath);
11964
12700
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
11965
- await fs16.rm(path20.join(paths.wikiDir, candidate.path), { force: true });
12701
+ await fs17.rm(path21.join(paths.wikiDir, candidate.path), { force: true });
11966
12702
  const nextPages = sortGraphPages((graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path));
11967
12703
  const nextGraph = {
11968
12704
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -12001,18 +12737,18 @@ async function archiveCandidate(rootDir, target) {
12001
12737
  }
12002
12738
  async function ensureObsidianWorkspace(rootDir) {
12003
12739
  const { config } = await loadVaultConfig(rootDir);
12004
- const obsidianDir = path20.join(rootDir, ".obsidian");
12740
+ const obsidianDir = path21.join(rootDir, ".obsidian");
12005
12741
  const projectIds = projectEntries(config).map((project) => project.id);
12006
12742
  await ensureDir(obsidianDir);
12007
12743
  await Promise.all([
12008
- writeJsonFile(path20.join(obsidianDir, "app.json"), {
12744
+ writeJsonFile(path21.join(obsidianDir, "app.json"), {
12009
12745
  alwaysUpdateLinks: true,
12010
12746
  newFileLocation: "folder",
12011
12747
  newFileFolderPath: "wiki/insights",
12012
12748
  useMarkdownLinks: false,
12013
12749
  attachmentFolderPath: "raw/assets"
12014
12750
  }),
12015
- writeJsonFile(path20.join(obsidianDir, "core-plugins.json"), [
12751
+ writeJsonFile(path21.join(obsidianDir, "core-plugins.json"), [
12016
12752
  "file-explorer",
12017
12753
  "global-search",
12018
12754
  "switcher",
@@ -12022,7 +12758,7 @@ async function ensureObsidianWorkspace(rootDir) {
12022
12758
  "tag-pane",
12023
12759
  "page-preview"
12024
12760
  ]),
12025
- writeJsonFile(path20.join(obsidianDir, "graph.json"), {
12761
+ writeJsonFile(path21.join(obsidianDir, "graph.json"), {
12026
12762
  "collapse-filter": false,
12027
12763
  search: "",
12028
12764
  showTags: true,
@@ -12034,7 +12770,7 @@ async function ensureObsidianWorkspace(rootDir) {
12034
12770
  })),
12035
12771
  localJumps: false
12036
12772
  }),
12037
- writeJsonFile(path20.join(obsidianDir, "workspace.json"), {
12773
+ writeJsonFile(path21.join(obsidianDir, "workspace.json"), {
12038
12774
  active: "root",
12039
12775
  lastOpenFiles: ["wiki/index.md", "wiki/projects/index.md", "wiki/candidates/index.md", "wiki/insights/index.md"],
12040
12776
  left: {
@@ -12049,7 +12785,7 @@ async function ensureObsidianWorkspace(rootDir) {
12049
12785
  async function initVault(rootDir, options = {}) {
12050
12786
  const { paths } = await initWorkspace(rootDir);
12051
12787
  await installConfiguredAgents(rootDir);
12052
- const insightsIndexPath = path20.join(paths.wikiDir, "insights", "index.md");
12788
+ const insightsIndexPath = path21.join(paths.wikiDir, "insights", "index.md");
12053
12789
  const now = (/* @__PURE__ */ new Date()).toISOString();
12054
12790
  await writeFileIfChanged(
12055
12791
  insightsIndexPath,
@@ -12085,7 +12821,7 @@ async function initVault(rootDir, options = {}) {
12085
12821
  )
12086
12822
  );
12087
12823
  await writeFileIfChanged(
12088
- path20.join(paths.wikiDir, "projects", "index.md"),
12824
+ path21.join(paths.wikiDir, "projects", "index.md"),
12089
12825
  matter9.stringify(["# Projects", "", "- Run `swarmvault compile` to build project rollups.", ""].join("\n"), {
12090
12826
  page_id: "projects:index",
12091
12827
  kind: "index",
@@ -12107,7 +12843,7 @@ async function initVault(rootDir, options = {}) {
12107
12843
  })
12108
12844
  );
12109
12845
  await writeFileIfChanged(
12110
- path20.join(paths.wikiDir, "candidates", "index.md"),
12846
+ path21.join(paths.wikiDir, "candidates", "index.md"),
12111
12847
  matter9.stringify(["# Candidates", "", "- Run `swarmvault compile` to stage candidate pages.", ""].join("\n"), {
12112
12848
  page_id: "candidates:index",
12113
12849
  kind: "index",
@@ -12243,7 +12979,7 @@ async function compileVault(rootDir, options = {}) {
12243
12979
  ),
12244
12980
  Promise.all(
12245
12981
  clean.map(async (manifest) => {
12246
- const cached = await readJsonFile(path20.join(paths.analysesDir, `${manifest.sourceId}.json`));
12982
+ const cached = await readJsonFile(path21.join(paths.analysesDir, `${manifest.sourceId}.json`));
12247
12983
  if (cached) {
12248
12984
  return cached;
12249
12985
  }
@@ -12267,22 +13003,22 @@ async function compileVault(rootDir, options = {}) {
12267
13003
  }
12268
13004
  const enriched = enrichResolvedCodeImports(manifest, analysis, codeIndex);
12269
13005
  if (analysisSignature(enriched) !== analysisSignature(analysis)) {
12270
- await writeJsonFile(path20.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
13006
+ await writeJsonFile(path21.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
12271
13007
  }
12272
13008
  return enriched;
12273
13009
  })
12274
13010
  );
12275
13011
  await Promise.all([
12276
- ensureDir(path20.join(paths.wikiDir, "sources")),
12277
- ensureDir(path20.join(paths.wikiDir, "code")),
12278
- ensureDir(path20.join(paths.wikiDir, "concepts")),
12279
- ensureDir(path20.join(paths.wikiDir, "entities")),
12280
- ensureDir(path20.join(paths.wikiDir, "outputs")),
12281
- ensureDir(path20.join(paths.wikiDir, "projects")),
12282
- ensureDir(path20.join(paths.wikiDir, "insights")),
12283
- ensureDir(path20.join(paths.wikiDir, "candidates")),
12284
- ensureDir(path20.join(paths.wikiDir, "candidates", "concepts")),
12285
- 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"))
12286
13022
  ]);
12287
13023
  const sync = await syncVaultArtifacts(rootDir, {
12288
13024
  schemas,
@@ -12429,7 +13165,7 @@ async function queryVault(rootDir, options) {
12429
13165
  assetFiles: staged.assetFiles
12430
13166
  }
12431
13167
  ]);
12432
- stagedPath = path20.join(approval.approvalDir, "wiki", staged.page.path);
13168
+ stagedPath = path21.join(approval.approvalDir, "wiki", staged.page.path);
12433
13169
  savedPageId = staged.page.id;
12434
13170
  approvalId = approval.approvalId;
12435
13171
  approvalDir = approval.approvalDir;
@@ -12685,9 +13421,9 @@ ${orchestrationNotes.join("\n")}
12685
13421
  approvalId = approval.approvalId;
12686
13422
  approvalDir = approval.approvalDir;
12687
13423
  stepResults.forEach((result, index) => {
12688
- result.stagedPath = path20.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
13424
+ result.stagedPath = path21.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
12689
13425
  });
12690
- stagedHubPath = path20.join(approval.approvalDir, "wiki", hubPage.path);
13426
+ stagedHubPath = path21.join(approval.approvalDir, "wiki", hubPage.path);
12691
13427
  } else {
12692
13428
  await refreshVaultAfterOutputSave(rootDir);
12693
13429
  }
@@ -12774,11 +13510,11 @@ async function benchmarkVault(rootDir, options = {}) {
12774
13510
  }
12775
13511
  }
12776
13512
  for (const page of graph.pages) {
12777
- const absolutePath = path20.join(paths.wikiDir, page.path);
13513
+ const absolutePath = path21.join(paths.wikiDir, page.path);
12778
13514
  if (!await fileExists(absolutePath)) {
12779
13515
  continue;
12780
13516
  }
12781
- const parsed = matter9(await fs16.readFile(absolutePath, "utf8"));
13517
+ const parsed = matter9(await fs17.readFile(absolutePath, "utf8"));
12782
13518
  pageContentsById.set(page.id, parsed.content);
12783
13519
  }
12784
13520
  const configuredQuestions = (config.benchmark?.questions ?? []).map((question) => normalizeWhitespace(question)).filter(Boolean);
@@ -12823,7 +13559,7 @@ async function listGraphHyperedges(rootDir, target, limit = 25) {
12823
13559
  }
12824
13560
  async function readGraphReport(rootDir) {
12825
13561
  const { paths } = await loadVaultConfig(rootDir);
12826
- return readJsonFile(path20.join(paths.wikiDir, "graph", "report.json"));
13562
+ return readJsonFile(path21.join(paths.wikiDir, "graph", "report.json"));
12827
13563
  }
12828
13564
  async function listGodNodes(rootDir, limit = 10) {
12829
13565
  const graph = await ensureCompiledGraph(rootDir);
@@ -12836,15 +13572,15 @@ async function listPages(rootDir) {
12836
13572
  }
12837
13573
  async function readPage(rootDir, relativePath) {
12838
13574
  const { paths } = await loadVaultConfig(rootDir);
12839
- const absolutePath = path20.resolve(paths.wikiDir, relativePath);
13575
+ const absolutePath = path21.resolve(paths.wikiDir, relativePath);
12840
13576
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
12841
13577
  return null;
12842
13578
  }
12843
- const raw = await fs16.readFile(absolutePath, "utf8");
13579
+ const raw = await fs17.readFile(absolutePath, "utf8");
12844
13580
  const parsed = matter9(raw);
12845
13581
  return {
12846
13582
  path: relativePath,
12847
- 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)),
12848
13584
  frontmatter: parsed.data,
12849
13585
  content: parsed.content
12850
13586
  };
@@ -12880,7 +13616,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
12880
13616
  severity: "warning",
12881
13617
  code: "stale_page",
12882
13618
  message: `Page ${page.title} is stale because the vault schema changed.`,
12883
- pagePath: path20.join(paths.wikiDir, page.path),
13619
+ pagePath: path21.join(paths.wikiDir, page.path),
12884
13620
  relatedPageIds: [page.id]
12885
13621
  });
12886
13622
  }
@@ -12891,7 +13627,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
12891
13627
  severity: "warning",
12892
13628
  code: "stale_page",
12893
13629
  message: `Page ${page.title} is stale because source ${sourceId} changed.`,
12894
- pagePath: path20.join(paths.wikiDir, page.path),
13630
+ pagePath: path21.join(paths.wikiDir, page.path),
12895
13631
  relatedSourceIds: [sourceId],
12896
13632
  relatedPageIds: [page.id]
12897
13633
  });
@@ -12902,13 +13638,13 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
12902
13638
  severity: "info",
12903
13639
  code: "orphan_page",
12904
13640
  message: `Page ${page.title} has no backlinks.`,
12905
- pagePath: path20.join(paths.wikiDir, page.path),
13641
+ pagePath: path21.join(paths.wikiDir, page.path),
12906
13642
  relatedPageIds: [page.id]
12907
13643
  });
12908
13644
  }
12909
- const absolutePath = path20.join(paths.wikiDir, page.path);
13645
+ const absolutePath = path21.join(paths.wikiDir, page.path);
12910
13646
  if (await fileExists(absolutePath)) {
12911
- const content = await fs16.readFile(absolutePath, "utf8");
13647
+ const content = await fs17.readFile(absolutePath, "utf8");
12912
13648
  if (content.includes("## Claims")) {
12913
13649
  const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
12914
13650
  if (uncited.length) {
@@ -12988,7 +13724,7 @@ async function bootstrapDemo(rootDir, input) {
12988
13724
  }
12989
13725
 
12990
13726
  // src/mcp.ts
12991
- var SERVER_VERSION = "0.1.28";
13727
+ var SERVER_VERSION = "0.1.30";
12992
13728
  async function createMcpServer(rootDir) {
12993
13729
  const server = new McpServer({
12994
13730
  name: "swarmvault",
@@ -13259,7 +13995,7 @@ async function createMcpServer(rootDir) {
13259
13995
  },
13260
13996
  async () => {
13261
13997
  const { paths } = await loadVaultConfig(rootDir);
13262
- 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();
13263
13999
  return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
13264
14000
  }
13265
14001
  );
@@ -13292,8 +14028,8 @@ async function createMcpServer(rootDir) {
13292
14028
  return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
13293
14029
  }
13294
14030
  const { paths } = await loadVaultConfig(rootDir);
13295
- const absolutePath = path21.resolve(paths.wikiDir, relativePath);
13296
- 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"));
13297
14033
  }
13298
14034
  );
13299
14035
  server.registerResource(
@@ -13301,11 +14037,11 @@ async function createMcpServer(rootDir) {
13301
14037
  new ResourceTemplate("swarmvault://sessions/{path}", {
13302
14038
  list: async () => {
13303
14039
  const { paths } = await loadVaultConfig(rootDir);
13304
- 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();
13305
14041
  return {
13306
14042
  resources: files.map((relativePath) => ({
13307
14043
  uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
13308
- name: path21.basename(relativePath, ".md"),
14044
+ name: path22.basename(relativePath, ".md"),
13309
14045
  title: relativePath,
13310
14046
  description: "SwarmVault session artifact",
13311
14047
  mimeType: "text/markdown"
@@ -13322,11 +14058,11 @@ async function createMcpServer(rootDir) {
13322
14058
  const { paths } = await loadVaultConfig(rootDir);
13323
14059
  const encodedPath = typeof variables.path === "string" ? variables.path : "";
13324
14060
  const relativePath = decodeURIComponent(encodedPath);
13325
- const absolutePath = path21.resolve(paths.sessionsDir, relativePath);
14061
+ const absolutePath = path22.resolve(paths.sessionsDir, relativePath);
13326
14062
  if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
13327
14063
  return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
13328
14064
  }
13329
- return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs17.readFile(absolutePath, "utf8"));
14065
+ return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs18.readFile(absolutePath, "utf8"));
13330
14066
  }
13331
14067
  );
13332
14068
  return server;
@@ -13374,13 +14110,13 @@ function asTextResource(uri, text) {
13374
14110
  }
13375
14111
 
13376
14112
  // src/schedule.ts
13377
- import fs18 from "fs/promises";
13378
- import path22 from "path";
14113
+ import fs19 from "fs/promises";
14114
+ import path23 from "path";
13379
14115
  function scheduleStatePath(schedulesDir, jobId) {
13380
- return path22.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
14116
+ return path23.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
13381
14117
  }
13382
14118
  function scheduleLockPath(schedulesDir, jobId) {
13383
- return path22.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
14119
+ return path23.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
13384
14120
  }
13385
14121
  function parseEveryDuration(value) {
13386
14122
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -13483,13 +14219,13 @@ async function acquireJobLease(rootDir, jobId) {
13483
14219
  const { paths } = await loadVaultConfig(rootDir);
13484
14220
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
13485
14221
  await ensureDir(paths.schedulesDir);
13486
- const handle = await fs18.open(leasePath, "wx");
14222
+ const handle = await fs19.open(leasePath, "wx");
13487
14223
  await handle.writeFile(`${process.pid}
13488
14224
  ${(/* @__PURE__ */ new Date()).toISOString()}
13489
14225
  `);
13490
14226
  await handle.close();
13491
14227
  return async () => {
13492
- await fs18.rm(leasePath, { force: true });
14228
+ await fs19.rm(leasePath, { force: true });
13493
14229
  };
13494
14230
  }
13495
14231
  async function listSchedules(rootDir) {
@@ -13637,15 +14373,15 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
13637
14373
 
13638
14374
  // src/viewer.ts
13639
14375
  import { execFile } from "child_process";
13640
- import fs19 from "fs/promises";
14376
+ import fs20 from "fs/promises";
13641
14377
  import http from "http";
13642
- import path24 from "path";
14378
+ import path25 from "path";
13643
14379
  import { promisify } from "util";
13644
14380
  import matter10 from "gray-matter";
13645
14381
  import mime2 from "mime-types";
13646
14382
 
13647
14383
  // src/watch.ts
13648
- import path23 from "path";
14384
+ import path24 from "path";
13649
14385
  import process3 from "process";
13650
14386
  import chokidar from "chokidar";
13651
14387
  var MAX_BACKOFF_MS = 3e4;
@@ -13653,15 +14389,15 @@ var BACKOFF_THRESHOLD = 3;
13653
14389
  var CRITICAL_THRESHOLD = 10;
13654
14390
  var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
13655
14391
  function withinRoot2(rootPath, targetPath) {
13656
- const relative = path23.relative(rootPath, targetPath);
13657
- return relative === "" || !relative.startsWith("..") && !path23.isAbsolute(relative);
14392
+ const relative = path24.relative(rootPath, targetPath);
14393
+ return relative === "" || !relative.startsWith("..") && !path24.isAbsolute(relative);
13658
14394
  }
13659
14395
  function hasIgnoredRepoSegment(baseDir, targetPath) {
13660
- const relativePath = path23.relative(baseDir, targetPath);
14396
+ const relativePath = path24.relative(baseDir, targetPath);
13661
14397
  if (!relativePath || relativePath.startsWith("..")) {
13662
14398
  return false;
13663
14399
  }
13664
- 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));
13665
14401
  }
13666
14402
  function workspaceIgnoreRoots(rootDir, paths) {
13667
14403
  return [
@@ -13670,16 +14406,16 @@ function workspaceIgnoreRoots(rootDir, paths) {
13670
14406
  paths.stateDir,
13671
14407
  paths.agentDir,
13672
14408
  paths.inboxDir,
13673
- path23.join(rootDir, ".claude"),
13674
- path23.join(rootDir, ".cursor"),
13675
- path23.join(rootDir, ".obsidian")
13676
- ].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));
13677
14413
  }
13678
14414
  async function resolveWatchTargets(rootDir, paths, options) {
13679
- const targets = /* @__PURE__ */ new Set([path23.resolve(paths.inboxDir)]);
14415
+ const targets = /* @__PURE__ */ new Set([path24.resolve(paths.inboxDir)]);
13680
14416
  if (options.repo) {
13681
14417
  for (const repoRoot of await listTrackedRepoRoots(rootDir)) {
13682
- targets.add(path23.resolve(repoRoot));
14418
+ targets.add(path24.resolve(repoRoot));
13683
14419
  }
13684
14420
  }
13685
14421
  return [...targets].sort((left, right) => left.localeCompare(right));
@@ -13809,7 +14545,7 @@ async function watchVault(rootDir, options = {}) {
13809
14545
  const { paths } = await initWorkspace(rootDir);
13810
14546
  const baseDebounceMs = options.debounceMs ?? 900;
13811
14547
  const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
13812
- const inboxWatchRoot = path23.resolve(paths.inboxDir);
14548
+ const inboxWatchRoot = path24.resolve(paths.inboxDir);
13813
14549
  let watchTargets = await resolveWatchTargets(rootDir, paths, options);
13814
14550
  let timer;
13815
14551
  let running = false;
@@ -13823,7 +14559,7 @@ async function watchVault(rootDir, options = {}) {
13823
14559
  usePolling: true,
13824
14560
  interval: 100,
13825
14561
  ignored: (targetPath) => {
13826
- const absolutePath = path23.resolve(targetPath);
14562
+ const absolutePath = path24.resolve(targetPath);
13827
14563
  const primaryTarget = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
13828
14564
  if (!primaryTarget) {
13829
14565
  return false;
@@ -13922,51 +14658,33 @@ async function watchVault(rootDir, options = {}) {
13922
14658
  }
13923
14659
  } finally {
13924
14660
  const finishedAt = /* @__PURE__ */ new Date();
13925
- await recordSession(rootDir, {
13926
- operation: "watch",
13927
- title: `Watch cycle for ${paths.inboxDir}${options.repo ? " and tracked repos" : ""}`,
13928
- startedAt: startedAt.toISOString(),
13929
- finishedAt: finishedAt.toISOString(),
13930
- success,
13931
- error,
13932
- changedPages,
13933
- lintFindingCount,
13934
- lines: [
13935
- `reasons=${runReasons.join(",") || "none"}`,
13936
- `imported=${importedCount}`,
13937
- `scanned=${scannedCount}`,
13938
- `attachments=${attachmentCount}`,
13939
- `repo_scanned=${repoScannedCount}`,
13940
- `repo_imported=${repoImportedCount}`,
13941
- `repo_updated=${repoUpdatedCount}`,
13942
- `repo_removed=${repoRemovedCount}`,
13943
- `lint=${lintFindingCount ?? 0}`
13944
- ]
13945
- });
13946
- await appendWatchRun(rootDir, {
13947
- startedAt: startedAt.toISOString(),
13948
- finishedAt: finishedAt.toISOString(),
13949
- durationMs: finishedAt.getTime() - startedAt.getTime(),
13950
- inputDir: paths.inboxDir,
13951
- reasons: runReasons,
13952
- importedCount: importedCount + repoImportedCount + repoUpdatedCount,
13953
- scannedCount: scannedCount + repoScannedCount,
13954
- attachmentCount,
13955
- changedPages,
13956
- repoImportedCount,
13957
- repoUpdatedCount,
13958
- repoRemovedCount,
13959
- repoScannedCount,
13960
- pendingSemanticRefreshCount,
13961
- pendingSemanticRefreshPaths,
13962
- lintFindingCount,
13963
- success,
13964
- error
13965
- });
13966
- await writeWatchStatusArtifact(rootDir, {
13967
- generatedAt: finishedAt.toISOString(),
13968
- watchedRepoRoots,
13969
- lastRun: {
14661
+ try {
14662
+ await recordSession(rootDir, {
14663
+ operation: "watch",
14664
+ title: `Watch cycle for ${paths.inboxDir}${options.repo ? " and tracked repos" : ""}`,
14665
+ startedAt: startedAt.toISOString(),
14666
+ finishedAt: finishedAt.toISOString(),
14667
+ success,
14668
+ error,
14669
+ changedPages,
14670
+ lintFindingCount,
14671
+ lines: [
14672
+ `reasons=${runReasons.join(",") || "none"}`,
14673
+ `imported=${importedCount}`,
14674
+ `scanned=${scannedCount}`,
14675
+ `attachments=${attachmentCount}`,
14676
+ `repo_scanned=${repoScannedCount}`,
14677
+ `repo_imported=${repoImportedCount}`,
14678
+ `repo_updated=${repoUpdatedCount}`,
14679
+ `repo_removed=${repoRemovedCount}`,
14680
+ `lint=${lintFindingCount ?? 0}`
14681
+ ]
14682
+ });
14683
+ } catch {
14684
+ process3.stderr.write("[swarmvault watch] Failed to record session log.\n");
14685
+ }
14686
+ try {
14687
+ await appendWatchRun(rootDir, {
13970
14688
  startedAt: startedAt.toISOString(),
13971
14689
  finishedAt: finishedAt.toISOString(),
13972
14690
  durationMs: finishedAt.getTime() - startedAt.getTime(),
@@ -13985,9 +14703,39 @@ async function watchVault(rootDir, options = {}) {
13985
14703
  lintFindingCount,
13986
14704
  success,
13987
14705
  error
13988
- },
13989
- pendingSemanticRefresh: await readPendingSemanticRefresh(rootDir)
13990
- });
14706
+ });
14707
+ } catch {
14708
+ process3.stderr.write("[swarmvault watch] Failed to append watch run.\n");
14709
+ }
14710
+ try {
14711
+ await writeWatchStatusArtifact(rootDir, {
14712
+ generatedAt: finishedAt.toISOString(),
14713
+ watchedRepoRoots,
14714
+ lastRun: {
14715
+ startedAt: startedAt.toISOString(),
14716
+ finishedAt: finishedAt.toISOString(),
14717
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
14718
+ inputDir: paths.inboxDir,
14719
+ reasons: runReasons,
14720
+ importedCount: importedCount + repoImportedCount + repoUpdatedCount,
14721
+ scannedCount: scannedCount + repoScannedCount,
14722
+ attachmentCount,
14723
+ changedPages,
14724
+ repoImportedCount,
14725
+ repoUpdatedCount,
14726
+ repoRemovedCount,
14727
+ repoScannedCount,
14728
+ pendingSemanticRefreshCount,
14729
+ pendingSemanticRefreshPaths,
14730
+ lintFindingCount,
14731
+ success,
14732
+ error
14733
+ },
14734
+ pendingSemanticRefresh: await readPendingSemanticRefresh(rootDir)
14735
+ });
14736
+ } catch {
14737
+ process3.stderr.write("[swarmvault watch] Failed to write watch status artifact.\n");
14738
+ }
13991
14739
  running = false;
13992
14740
  if (pending && !closed) {
13993
14741
  schedule("queued");
@@ -13995,8 +14743,8 @@ async function watchVault(rootDir, options = {}) {
13995
14743
  }
13996
14744
  };
13997
14745
  const reasonForPath = (targetPath) => {
13998
- const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, path23.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
13999
- 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) || ".";
14000
14748
  };
14001
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)}`));
14002
14750
  await new Promise((resolve, reject) => {
@@ -14037,15 +14785,15 @@ async function getWatchStatus(rootDir) {
14037
14785
  var execFileAsync = promisify(execFile);
14038
14786
  async function readViewerPage(rootDir, relativePath) {
14039
14787
  const { paths } = await loadVaultConfig(rootDir);
14040
- const absolutePath = path24.resolve(paths.wikiDir, relativePath);
14788
+ const absolutePath = path25.resolve(paths.wikiDir, relativePath);
14041
14789
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
14042
14790
  return null;
14043
14791
  }
14044
- const raw = await fs19.readFile(absolutePath, "utf8");
14792
+ const raw = await fs20.readFile(absolutePath, "utf8");
14045
14793
  const parsed = matter10(raw);
14046
14794
  return {
14047
14795
  path: relativePath,
14048
- 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)),
14049
14797
  frontmatter: parsed.data,
14050
14798
  content: parsed.content,
14051
14799
  assets: normalizeOutputAssets(parsed.data.output_assets)
@@ -14053,12 +14801,12 @@ async function readViewerPage(rootDir, relativePath) {
14053
14801
  }
14054
14802
  async function readViewerAsset(rootDir, relativePath) {
14055
14803
  const { paths } = await loadVaultConfig(rootDir);
14056
- const absolutePath = path24.resolve(paths.wikiDir, relativePath);
14804
+ const absolutePath = path25.resolve(paths.wikiDir, relativePath);
14057
14805
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
14058
14806
  return null;
14059
14807
  }
14060
14808
  return {
14061
- buffer: await fs19.readFile(absolutePath),
14809
+ buffer: await fs20.readFile(absolutePath),
14062
14810
  mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
14063
14811
  };
14064
14812
  }
@@ -14081,12 +14829,12 @@ async function readJsonBody(request) {
14081
14829
  return JSON.parse(raw);
14082
14830
  }
14083
14831
  async function ensureViewerDist(viewerDistDir) {
14084
- const indexPath = path24.join(viewerDistDir, "index.html");
14832
+ const indexPath = path25.join(viewerDistDir, "index.html");
14085
14833
  if (await fileExists(indexPath)) {
14086
14834
  return;
14087
14835
  }
14088
- const viewerProjectDir = path24.dirname(viewerDistDir);
14089
- if (await fileExists(path24.join(viewerProjectDir, "package.json"))) {
14836
+ const viewerProjectDir = path25.dirname(viewerDistDir);
14837
+ if (await fileExists(path25.join(viewerProjectDir, "package.json"))) {
14090
14838
  await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
14091
14839
  }
14092
14840
  }
@@ -14103,7 +14851,7 @@ async function startGraphServer(rootDir, port) {
14103
14851
  return;
14104
14852
  }
14105
14853
  response.writeHead(200, { "content-type": "application/json" });
14106
- response.end(await fs19.readFile(paths.graphPath, "utf8"));
14854
+ response.end(await fs20.readFile(paths.graphPath, "utf8"));
14107
14855
  return;
14108
14856
  }
14109
14857
  if (url.pathname === "/api/graph/query") {
@@ -14160,14 +14908,14 @@ async function startGraphServer(rootDir, port) {
14160
14908
  return;
14161
14909
  }
14162
14910
  if (url.pathname === "/api/graph-report") {
14163
- const reportPath = path24.join(paths.wikiDir, "graph", "report.json");
14911
+ const reportPath = path25.join(paths.wikiDir, "graph", "report.json");
14164
14912
  if (!await fileExists(reportPath)) {
14165
14913
  response.writeHead(404, { "content-type": "application/json" });
14166
14914
  response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
14167
14915
  return;
14168
14916
  }
14169
14917
  response.writeHead(200, { "content-type": "application/json" });
14170
- response.end(await fs19.readFile(reportPath, "utf8"));
14918
+ response.end(await fs20.readFile(reportPath, "utf8"));
14171
14919
  return;
14172
14920
  }
14173
14921
  if (url.pathname === "/api/watch-status") {
@@ -14250,8 +14998,8 @@ async function startGraphServer(rootDir, port) {
14250
14998
  return;
14251
14999
  }
14252
15000
  const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
14253
- const target = path24.join(paths.viewerDistDir, relativePath);
14254
- 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");
14255
15003
  const filePath = await fileExists(target) ? target : fallback;
14256
15004
  if (!await fileExists(filePath)) {
14257
15005
  response.writeHead(503, { "content-type": "text/plain" });
@@ -14259,7 +15007,7 @@ async function startGraphServer(rootDir, port) {
14259
15007
  return;
14260
15008
  }
14261
15009
  response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
14262
- response.end(await fs19.readFile(filePath));
15010
+ response.end(await fs20.readFile(filePath));
14263
15011
  });
14264
15012
  await new Promise((resolve) => {
14265
15013
  server.listen(effectivePort, resolve);
@@ -14286,7 +15034,7 @@ async function exportGraphHtml(rootDir, outputPath) {
14286
15034
  throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
14287
15035
  }
14288
15036
  await ensureViewerDist(paths.viewerDistDir);
14289
- const indexPath = path24.join(paths.viewerDistDir, "index.html");
15037
+ const indexPath = path25.join(paths.viewerDistDir, "index.html");
14290
15038
  if (!await fileExists(indexPath)) {
14291
15039
  throw new Error("Viewer build not found. Run `pnpm build` first.");
14292
15040
  }
@@ -14312,17 +15060,17 @@ async function exportGraphHtml(rootDir, outputPath) {
14312
15060
  } : null;
14313
15061
  })
14314
15062
  );
14315
- const rawHtml = await fs19.readFile(indexPath, "utf8");
15063
+ const rawHtml = await fs20.readFile(indexPath, "utf8");
14316
15064
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
14317
15065
  const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
14318
- const scriptPath = scriptMatch?.[1] ? path24.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
14319
- 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;
14320
15068
  if (!scriptPath || !await fileExists(scriptPath)) {
14321
15069
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
14322
15070
  }
14323
- const script = await fs19.readFile(scriptPath, "utf8");
14324
- const style = stylePath && await fileExists(stylePath) ? await fs19.readFile(stylePath, "utf8") : "";
14325
- 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"));
14326
15074
  const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean), report }, null, 2).replace(/</g, "\\u003c");
14327
15075
  const html = [
14328
15076
  "<!doctype html>",
@@ -14341,9 +15089,9 @@ async function exportGraphHtml(rootDir, outputPath) {
14341
15089
  "</html>",
14342
15090
  ""
14343
15091
  ].filter(Boolean).join("\n");
14344
- await fs19.mkdir(path24.dirname(outputPath), { recursive: true });
14345
- await fs19.writeFile(outputPath, html, "utf8");
14346
- 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);
14347
15095
  }
14348
15096
  export {
14349
15097
  acceptApproval,
@@ -14389,6 +15137,7 @@ export {
14389
15137
  loadVaultSchemas,
14390
15138
  pathGraphVault,
14391
15139
  promoteCandidate,
15140
+ pushGraphNeo4j,
14392
15141
  queryGraphVault,
14393
15142
  queryVault,
14394
15143
  readApproval,