@swarmvaultai/engine 3.4.0 → 3.6.0

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
@@ -23,6 +23,7 @@ import {
23
23
  buildOutputPage,
24
24
  buildRedactor,
25
25
  buildSchemaPrompt,
26
+ checkTrackedRepoChanges,
26
27
  compileVault,
27
28
  composeVaultSchema,
28
29
  computeDecayScore,
@@ -38,11 +39,13 @@ import {
38
39
  exploreVault,
39
40
  findLatestGuidedSourceSessionByScope,
40
41
  finishMemoryTask,
42
+ getGraphCommunityVault,
41
43
  getRetrievalStatus,
42
44
  getWebSearchAdapterForTask,
43
45
  getWorkspaceInfo,
44
46
  graphDiff,
45
47
  graphHash,
48
+ graphStatsVault,
46
49
  guidedSourceSessionStatePath,
47
50
  importInbox,
48
51
  ingestDirectory,
@@ -89,6 +92,7 @@ import {
89
92
  readWatchStatusArtifact,
90
93
  rebuildRetrievalIndex,
91
94
  recordSession,
95
+ refreshGraphClusters,
92
96
  refreshVaultAfterOutputSave,
93
97
  rejectApproval,
94
98
  removeManifestBySourceId,
@@ -121,9 +125,10 @@ import {
121
125
  writeGuidedSourceSession,
122
126
  writeRetrievalManifest,
123
127
  writeWatchStatusArtifact
124
- } from "./chunk-USSP4GVB.js";
128
+ } from "./chunk-5GEPTIZE.js";
125
129
  import {
126
130
  LocalWhisperProviderAdapter,
131
+ SWARMVAULT_OUT_ENV,
127
132
  appendJsonLine,
128
133
  assertProviderCapability,
129
134
  createProvider,
@@ -138,6 +143,7 @@ import {
138
143
  loadVaultConfig,
139
144
  normalizeWhitespace,
140
145
  readJsonFile,
146
+ resolveArtifactRootDir,
141
147
  resolvePaths,
142
148
  sha256,
143
149
  slugify,
@@ -145,7 +151,7 @@ import {
145
151
  truncate,
146
152
  uniqueBy,
147
153
  writeJsonFile
148
- } from "./chunk-7QHDATCQ.js";
154
+ } from "./chunk-7O2HJSWQ.js";
149
155
  import {
150
156
  estimatePageTokens,
151
157
  estimateTokens,
@@ -3918,27 +3924,78 @@ async function pushGraphNeo4j(rootDir, options = {}) {
3918
3924
  }
3919
3925
  }
3920
3926
 
3927
+ // src/graph-status.ts
3928
+ import path6 from "path";
3929
+ function recommendedCommand(input) {
3930
+ if (!input.graphExists || !input.reportExists) {
3931
+ return "swarmvault compile";
3932
+ }
3933
+ if (input.semanticChangeCount > 0 || input.pendingSemanticRefreshCount > 0) {
3934
+ return "swarmvault compile";
3935
+ }
3936
+ if (input.codeChangeCount > 0) {
3937
+ return "swarmvault graph update";
3938
+ }
3939
+ return null;
3940
+ }
3941
+ async function getGraphStatus(rootDir, options = {}) {
3942
+ const { paths } = await loadVaultConfig(rootDir);
3943
+ const graphPath = paths.graphPath;
3944
+ const reportPath = path6.join(paths.wikiDir, "graph", "report.md");
3945
+ const resolvedOverrideRoots = options.repoRoots?.map((repoRoot) => path6.resolve(rootDir, repoRoot));
3946
+ const [graphExists, reportExists, trackedRepoRoots, changes, pendingSemanticRefresh] = await Promise.all([
3947
+ fileExists(graphPath),
3948
+ fileExists(reportPath),
3949
+ resolvedOverrideRoots ? Promise.resolve([...new Set(resolvedOverrideRoots)].sort((left, right) => left.localeCompare(right))) : listTrackedRepoRoots(rootDir),
3950
+ checkTrackedRepoChanges(rootDir, resolvedOverrideRoots),
3951
+ readJsonFile(paths.pendingSemanticRefreshPath).then((entries) => Array.isArray(entries) ? entries : [])
3952
+ ]);
3953
+ const codeChangeCount = changes.filter((change) => change.refreshType === "code").length;
3954
+ const semanticChangeCount = changes.filter((change) => change.refreshType === "semantic").length;
3955
+ const command = recommendedCommand({
3956
+ graphExists,
3957
+ reportExists,
3958
+ codeChangeCount,
3959
+ semanticChangeCount,
3960
+ pendingSemanticRefreshCount: pendingSemanticRefresh.length
3961
+ });
3962
+ return {
3963
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3964
+ graphExists,
3965
+ graphPath,
3966
+ reportExists,
3967
+ reportPath,
3968
+ trackedRepoRoots,
3969
+ codeChangeCount,
3970
+ semanticChangeCount,
3971
+ pendingSemanticRefresh,
3972
+ stale: Boolean(command),
3973
+ recommendedCommand: command,
3974
+ changes
3975
+ };
3976
+ }
3977
+
3921
3978
  // src/hooks.ts
3922
3979
  import fs6 from "fs/promises";
3923
- import path6 from "path";
3980
+ import path7 from "path";
3924
3981
  import process3 from "process";
3925
3982
  var hookStart = "# >>> swarmvault hook >>>";
3926
3983
  var hookEnd = "# <<< swarmvault hook <<<";
3927
3984
  async function findNearestGitRoot(startPath) {
3928
- let current = path6.resolve(startPath);
3985
+ let current = path7.resolve(startPath);
3929
3986
  try {
3930
3987
  const stat = await fs6.stat(current);
3931
3988
  if (!stat.isDirectory()) {
3932
- current = path6.dirname(current);
3989
+ current = path7.dirname(current);
3933
3990
  }
3934
3991
  } catch {
3935
- current = path6.dirname(current);
3992
+ current = path7.dirname(current);
3936
3993
  }
3937
3994
  while (true) {
3938
- if (await fileExists(path6.join(current, ".git"))) {
3995
+ if (await fileExists(path7.join(current, ".git"))) {
3939
3996
  return current;
3940
3997
  }
3941
- const parent = path6.dirname(current);
3998
+ const parent = path7.dirname(current);
3942
3999
  if (parent === current) {
3943
4000
  return null;
3944
4001
  }
@@ -3950,8 +4007,8 @@ function shellQuote(value) {
3950
4007
  }
3951
4008
  function resolveSwarmvaultExecutableCandidate() {
3952
4009
  const argvPath = process3.argv[1];
3953
- if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path6.sep}@swarmvaultai${path6.sep}cli${path6.sep}`) || argvPath.includes(`${path6.sep}packages${path6.sep}cli${path6.sep}`))) {
3954
- return path6.resolve(argvPath);
4010
+ if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path7.sep}@swarmvaultai${path7.sep}cli${path7.sep}`) || argvPath.includes(`${path7.sep}packages${path7.sep}cli${path7.sep}`))) {
4011
+ return path7.resolve(argvPath);
3955
4012
  }
3956
4013
  return "swarmvault";
3957
4014
  }
@@ -3970,7 +4027,7 @@ function managedHookBlock(vaultRoot) {
3970
4027
  ].join("\n");
3971
4028
  }
3972
4029
  function hookPath(repoRoot, hookName) {
3973
- return path6.join(repoRoot, ".git", "hooks", hookName);
4030
+ return path7.join(repoRoot, ".git", "hooks", hookName);
3974
4031
  }
3975
4032
  async function readHookStatus(filePath) {
3976
4033
  if (!await fileExists(filePath)) {
@@ -3994,7 +4051,7 @@ ${block}`.trimEnd();
3994
4051
  next = `#!/bin/sh
3995
4052
  ${block}`.trimEnd();
3996
4053
  }
3997
- await ensureDir(path6.dirname(filePath));
4054
+ await ensureDir(path7.dirname(filePath));
3998
4055
  await fs6.writeFile(filePath, `${next}
3999
4056
  `, { mode: 493, encoding: "utf8" });
4000
4057
  await fs6.chmod(filePath, 493);
@@ -4037,7 +4094,7 @@ async function installGitHooks(rootDir) {
4037
4094
  if (!repoRoot) {
4038
4095
  throw new Error("No git repository found above the current vault.");
4039
4096
  }
4040
- const block = managedHookBlock(path6.resolve(rootDir));
4097
+ const block = managedHookBlock(path7.resolve(rootDir));
4041
4098
  await upsertHookFile(hookPath(repoRoot, "post-commit"), block);
4042
4099
  await upsertHookFile(hookPath(repoRoot, "post-checkout"), block);
4043
4100
  return getGitHookStatus(rootDir);
@@ -4058,11 +4115,11 @@ async function uninstallGitHooks(rootDir) {
4058
4115
 
4059
4116
  // src/mcp.ts
4060
4117
  import fs7 from "fs/promises";
4061
- import path7 from "path";
4118
+ import path8 from "path";
4062
4119
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4063
4120
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4064
4121
  import { z } from "zod";
4065
- var SERVER_VERSION = "3.4.0";
4122
+ var SERVER_VERSION = "3.6.0";
4066
4123
  async function createMcpServer(rootDir) {
4067
4124
  const server = new McpServer({
4068
4125
  name: "swarmvault",
@@ -4191,6 +4248,27 @@ async function createMcpServer(rootDir) {
4191
4248
  return asToolText(await readGraphReport(rootDir) ?? { error: "Graph report not found. Run `swarmvault compile` first." });
4192
4249
  })
4193
4250
  );
4251
+ server.registerTool(
4252
+ "graph_stats",
4253
+ {
4254
+ description: "Return lightweight counts for graph nodes, evidence classes, source classes, communities, pages, and edges."
4255
+ },
4256
+ safeHandler(async () => {
4257
+ return asToolText(await graphStatsVault(rootDir));
4258
+ })
4259
+ );
4260
+ server.registerTool(
4261
+ "cluster_graph",
4262
+ {
4263
+ description: "Recompute graph communities, node degrees, god-node flags, and graph report artifacts from the existing compiled graph.",
4264
+ inputSchema: {
4265
+ resolution: z.number().positive().optional().describe("Optional Louvain community resolution override")
4266
+ }
4267
+ },
4268
+ safeHandler(async ({ resolution }) => {
4269
+ return asToolText(await refreshGraphClusters(rootDir, { resolution }));
4270
+ })
4271
+ );
4194
4272
  server.registerTool(
4195
4273
  "get_node",
4196
4274
  {
@@ -4203,6 +4281,19 @@ async function createMcpServer(rootDir) {
4203
4281
  return asToolText(await explainGraphVault(rootDir, target));
4204
4282
  })
4205
4283
  );
4284
+ server.registerTool(
4285
+ "get_community",
4286
+ {
4287
+ description: "Return members, pages, and top evidence edges for a graph community by id or label.",
4288
+ inputSchema: {
4289
+ target: z.string().min(1).describe("Community id or label"),
4290
+ limit: z.number().int().min(1).max(100).optional().describe("Maximum evidence edges to return")
4291
+ }
4292
+ },
4293
+ safeHandler(async ({ target, limit }) => {
4294
+ return asToolText(await getGraphCommunityVault(rootDir, target, limit ?? 25));
4295
+ })
4296
+ );
4206
4297
  server.registerTool(
4207
4298
  "get_hyperedges",
4208
4299
  {
@@ -4736,7 +4827,7 @@ async function createMcpServer(rootDir) {
4736
4827
  },
4737
4828
  async () => {
4738
4829
  const { paths } = await loadVaultConfig(rootDir);
4739
- const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path7.relative(paths.sessionsDir, filePath))).sort();
4830
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path8.relative(paths.sessionsDir, filePath))).sort();
4740
4831
  return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
4741
4832
  }
4742
4833
  );
@@ -4805,7 +4896,7 @@ async function createMcpServer(rootDir) {
4805
4896
  return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
4806
4897
  }
4807
4898
  const { paths } = await loadVaultConfig(rootDir);
4808
- const absolutePath = path7.resolve(paths.wikiDir, relativePath);
4899
+ const absolutePath = path8.resolve(paths.wikiDir, relativePath);
4809
4900
  return asTextResource(`swarmvault://pages/${encodedPath}`, await fs7.readFile(absolutePath, "utf8"));
4810
4901
  }
4811
4902
  );
@@ -4814,11 +4905,11 @@ async function createMcpServer(rootDir) {
4814
4905
  new ResourceTemplate("swarmvault://sessions/{path}", {
4815
4906
  list: async () => {
4816
4907
  const { paths } = await loadVaultConfig(rootDir);
4817
- const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path7.relative(paths.sessionsDir, filePath))).sort();
4908
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path8.relative(paths.sessionsDir, filePath))).sort();
4818
4909
  return {
4819
4910
  resources: files.map((relativePath) => ({
4820
4911
  uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
4821
- name: path7.basename(relativePath, ".md"),
4912
+ name: path8.basename(relativePath, ".md"),
4822
4913
  title: relativePath,
4823
4914
  description: "SwarmVault session artifact",
4824
4915
  mimeType: "text/markdown"
@@ -4835,7 +4926,7 @@ async function createMcpServer(rootDir) {
4835
4926
  const { paths } = await loadVaultConfig(rootDir);
4836
4927
  const encodedPath = typeof variables.path === "string" ? variables.path : "";
4837
4928
  const relativePath = decodeURIComponent(encodedPath);
4838
- const absolutePath = path7.resolve(paths.sessionsDir, relativePath);
4929
+ const absolutePath = path8.resolve(paths.sessionsDir, relativePath);
4839
4930
  if (!isPathWithin(paths.sessionsDir, absolutePath) || !await fileExists(absolutePath)) {
4840
4931
  return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
4841
4932
  }
@@ -4901,7 +4992,7 @@ function asTextResource(uri, text) {
4901
4992
  import { createWriteStream, constants as fsConstants } from "fs";
4902
4993
  import fs8 from "fs/promises";
4903
4994
  import os from "os";
4904
- import path8 from "path";
4995
+ import path9 from "path";
4905
4996
  import { Readable } from "stream";
4906
4997
  import { pipeline } from "stream/promises";
4907
4998
  var BINARY_CANDIDATES = ["whisper-cli", "whisper-cpp", "whisper"];
@@ -4929,10 +5020,10 @@ async function discoverLocalWhisperBinary(options = {}) {
4929
5020
  }
4930
5021
  const pathValue = env.PATH ?? "";
4931
5022
  const candidates = [];
4932
- for (const dir of pathValue.split(path8.delimiter)) {
5023
+ for (const dir of pathValue.split(path9.delimiter)) {
4933
5024
  if (!dir) continue;
4934
5025
  for (const name of BINARY_CANDIDATES) {
4935
- const full = path8.join(dir, name);
5026
+ const full = path9.join(dir, name);
4936
5027
  candidates.push(full);
4937
5028
  if (await isExecutable(full)) {
4938
5029
  return { binaryPath: full, candidates, source: "path" };
@@ -4943,14 +5034,14 @@ async function discoverLocalWhisperBinary(options = {}) {
4943
5034
  }
4944
5035
  function expectedModelPath(modelName, homeDir) {
4945
5036
  const home = homeDir ?? os.homedir();
4946
- return path8.join(home, ".swarmvault", "models", `ggml-${modelName}.bin`);
5037
+ return path9.join(home, ".swarmvault", "models", `ggml-${modelName}.bin`);
4947
5038
  }
4948
5039
  function modelDownloadUrl(modelName) {
4949
5040
  return `${HUGGINGFACE_BASE}/ggml-${modelName}.bin`;
4950
5041
  }
4951
5042
  async function downloadWhisperModel(options) {
4952
5043
  const destPath = expectedModelPath(options.modelName, options.homeDir);
4953
- await ensureDir(path8.dirname(destPath));
5044
+ await ensureDir(path9.dirname(destPath));
4954
5045
  const doFetch = options.fetchImpl ?? fetch;
4955
5046
  const url = modelDownloadUrl(options.modelName);
4956
5047
  const response = await doFetch(url);
@@ -5110,12 +5201,12 @@ async function withCapabilityFallback(provider, capability, run, fallback) {
5110
5201
 
5111
5202
  // src/schedule.ts
5112
5203
  import fs9 from "fs/promises";
5113
- import path9 from "path";
5204
+ import path10 from "path";
5114
5205
  function scheduleStatePath(schedulesDir, jobId) {
5115
- return path9.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
5206
+ return path10.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
5116
5207
  }
5117
5208
  function scheduleLockPath(schedulesDir, jobId) {
5118
- return path9.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
5209
+ return path10.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
5119
5210
  }
5120
5211
  function parseEveryDuration(value) {
5121
5212
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -5384,7 +5475,7 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
5384
5475
  // src/sources.ts
5385
5476
  import { spawn } from "child_process";
5386
5477
  import fs10 from "fs/promises";
5387
- import path10 from "path";
5478
+ import path11 from "path";
5388
5479
  import matter3 from "gray-matter";
5389
5480
  import { JSDOM } from "jsdom";
5390
5481
  var DEFAULT_CRAWL_MAX_PAGES = 12;
@@ -5430,24 +5521,24 @@ function emptyManagedSourceSyncCounts() {
5430
5521
  };
5431
5522
  }
5432
5523
  function withinRoot(rootPath, targetPath) {
5433
- const relative = path10.relative(rootPath, targetPath);
5434
- return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
5524
+ const relative = path11.relative(rootPath, targetPath);
5525
+ return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
5435
5526
  }
5436
5527
  async function findNearestGitRoot2(startPath) {
5437
- let current = path10.resolve(startPath);
5528
+ let current = path11.resolve(startPath);
5438
5529
  try {
5439
5530
  const stat = await fs10.stat(current);
5440
5531
  if (!stat.isDirectory()) {
5441
- current = path10.dirname(current);
5532
+ current = path11.dirname(current);
5442
5533
  }
5443
5534
  } catch {
5444
- current = path10.dirname(current);
5535
+ current = path11.dirname(current);
5445
5536
  }
5446
5537
  while (true) {
5447
- if (await fileExists(path10.join(current, ".git"))) {
5538
+ if (await fileExists(path11.join(current, ".git"))) {
5448
5539
  return current;
5449
5540
  }
5450
- const parent = path10.dirname(current);
5541
+ const parent = path11.dirname(current);
5451
5542
  if (parent === current) {
5452
5543
  return null;
5453
5544
  }
@@ -5521,7 +5612,7 @@ function isAllowedDocsCandidate(candidate, startUrl) {
5521
5612
  if (candidate.origin !== startUrl.origin) {
5522
5613
  return false;
5523
5614
  }
5524
- const extension = path10.extname(candidate.pathname).toLowerCase();
5615
+ const extension = path11.extname(candidate.pathname).toLowerCase();
5525
5616
  if (extension && extension !== ".html" && extension !== ".htm" && extension !== ".md") {
5526
5617
  return false;
5527
5618
  }
@@ -5610,12 +5701,12 @@ function matchesManagedSourceSpec(existing, input) {
5610
5701
  return false;
5611
5702
  }
5612
5703
  if (input.kind === "directory" || input.kind === "file") {
5613
- return path10.resolve(existing.path ?? "") === path10.resolve(input.path);
5704
+ return path11.resolve(existing.path ?? "") === path11.resolve(input.path);
5614
5705
  }
5615
5706
  return (existing.url ?? "") === input.url;
5616
5707
  }
5617
5708
  async function resolveManagedSourceInput(rootDir, input) {
5618
- const absoluteInput = path10.resolve(rootDir, input);
5709
+ const absoluteInput = path11.resolve(rootDir, input);
5619
5710
  if (!(input.startsWith("http://") || input.startsWith("https://"))) {
5620
5711
  const stat = await fs10.stat(absoluteInput).catch(() => null);
5621
5712
  if (!stat) {
@@ -5625,7 +5716,7 @@ async function resolveManagedSourceInput(rootDir, input) {
5625
5716
  return {
5626
5717
  kind: "file",
5627
5718
  path: absoluteInput,
5628
- title: path10.basename(absoluteInput, path10.extname(absoluteInput)) || absoluteInput
5719
+ title: path11.basename(absoluteInput, path11.extname(absoluteInput)) || absoluteInput
5629
5720
  };
5630
5721
  }
5631
5722
  if (!stat.isDirectory()) {
@@ -5637,7 +5728,7 @@ async function resolveManagedSourceInput(rootDir, input) {
5637
5728
  kind: "directory",
5638
5729
  path: absoluteInput,
5639
5730
  repoRoot,
5640
- title: path10.basename(absoluteInput) || absoluteInput
5731
+ title: path11.basename(absoluteInput) || absoluteInput
5641
5732
  };
5642
5733
  }
5643
5734
  const github = normalizeGitHubRepoRootUrl(input);
@@ -5660,16 +5751,16 @@ async function resolveManagedSourceInput(rootDir, input) {
5660
5751
  };
5661
5752
  }
5662
5753
  function directorySourceIdsFor(manifests, inputPath) {
5663
- return manifests.filter((manifest) => manifest.originalPath && withinRoot(path10.resolve(inputPath), path10.resolve(manifest.originalPath))).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
5754
+ return manifests.filter((manifest) => manifest.originalPath && withinRoot(path11.resolve(inputPath), path11.resolve(manifest.originalPath))).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
5664
5755
  }
5665
5756
  function fileSourceIdsFor(manifests, inputPath) {
5666
- const absoluteInput = path10.resolve(inputPath);
5667
- return manifests.filter((manifest) => manifest.originalPath && path10.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
5757
+ const absoluteInput = path11.resolve(inputPath);
5758
+ return manifests.filter((manifest) => manifest.originalPath && path11.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
5668
5759
  }
5669
5760
  async function syncDirectorySource(rootDir, inputPath, repoRoot) {
5670
5761
  const manifestsBefore = await listManifests(rootDir);
5671
5762
  const previousInScope = manifestsBefore.filter(
5672
- (manifest) => manifest.originalPath && withinRoot(path10.resolve(inputPath), path10.resolve(manifest.originalPath))
5763
+ (manifest) => manifest.originalPath && withinRoot(path11.resolve(inputPath), path11.resolve(manifest.originalPath))
5673
5764
  );
5674
5765
  const result = await ingestDirectory(rootDir, inputPath, { repoRoot });
5675
5766
  const removed = [];
@@ -5677,7 +5768,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
5677
5768
  if (!manifest.originalPath) {
5678
5769
  continue;
5679
5770
  }
5680
- if (await fileExists(path10.resolve(manifest.originalPath))) {
5771
+ if (await fileExists(path11.resolve(manifest.originalPath))) {
5681
5772
  continue;
5682
5773
  }
5683
5774
  const removedManifest = await removeManifestBySourceId(rootDir, manifest.sourceId);
@@ -5687,7 +5778,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
5687
5778
  }
5688
5779
  const manifestsAfter = await listManifests(rootDir);
5689
5780
  return {
5690
- title: path10.basename(inputPath) || inputPath,
5781
+ title: path11.basename(inputPath) || inputPath,
5691
5782
  sourceIds: directorySourceIdsFor(manifestsAfter, inputPath),
5692
5783
  counts: {
5693
5784
  scannedCount: result.scannedCount,
@@ -5703,7 +5794,7 @@ async function syncFileSource(rootDir, inputPath) {
5703
5794
  const result = await ingestInputDetailed(rootDir, inputPath);
5704
5795
  const manifestsAfter = await listManifests(rootDir);
5705
5796
  return {
5706
- title: path10.basename(inputPath, path10.extname(inputPath)) || inputPath,
5797
+ title: path11.basename(inputPath, path11.extname(inputPath)) || inputPath,
5707
5798
  sourceIds: fileSourceIdsFor(manifestsAfter, inputPath),
5708
5799
  counts: {
5709
5800
  scannedCount: result.scannedCount,
@@ -5737,7 +5828,7 @@ async function runGitCommand(cwd, args) {
5737
5828
  }
5738
5829
  async function syncGitHubRepoSource(rootDir, entry) {
5739
5830
  const workingDir = await managedSourceWorkingDir(rootDir, entry.id);
5740
- const checkoutDir = path10.join(workingDir, "checkout");
5831
+ const checkoutDir = path11.join(workingDir, "checkout");
5741
5832
  await fs10.rm(checkoutDir, { recursive: true, force: true });
5742
5833
  await ensureDir(workingDir);
5743
5834
  if (!entry.url) {
@@ -5868,7 +5959,7 @@ function scopedNodeIds(graph, sourceIds) {
5868
5959
  async function loadSourceAnalyses(rootDir, sourceIds) {
5869
5960
  const { paths } = await loadVaultConfig(rootDir);
5870
5961
  const analyses = await Promise.all(
5871
- sourceIds.map(async (sourceId) => await readJsonFile(path10.join(paths.analysesDir, `${sourceId}.json`)))
5962
+ sourceIds.map(async (sourceId) => await readJsonFile(path11.join(paths.analysesDir, `${sourceId}.json`)))
5872
5963
  );
5873
5964
  return analyses.filter((analysis) => Boolean(analysis?.sourceId));
5874
5965
  }
@@ -6029,8 +6120,8 @@ async function writeSourceBriefForScope(rootDir, source) {
6029
6120
  confidence: 0.82
6030
6121
  }
6031
6122
  });
6032
- const absolutePath = path10.join(paths.wikiDir, output.page.path);
6033
- await ensureDir(path10.dirname(absolutePath));
6123
+ const absolutePath = path11.join(paths.wikiDir, output.page.path);
6124
+ await ensureDir(path11.dirname(absolutePath));
6034
6125
  await fs10.writeFile(absolutePath, output.content, "utf8");
6035
6126
  return absolutePath;
6036
6127
  }
@@ -6319,7 +6410,7 @@ function selectGuidedTargetPages(scope, sourcePages, questions) {
6319
6410
  return (matchedTargets.length ? matchedTargets : canonicalPages).slice(0, 6);
6320
6411
  }
6321
6412
  function insightRelativePathForTarget(page, scope) {
6322
- const basename = path10.basename(page.path);
6413
+ const basename = path11.basename(page.path);
6323
6414
  if (page.kind === "concept") {
6324
6415
  return `insights/concepts/${basename}`;
6325
6416
  }
@@ -6546,7 +6637,7 @@ async function stageSourceReviewForScope(rootDir, scope) {
6546
6637
  return {
6547
6638
  sourceId: scope.id,
6548
6639
  pageId: output.page.id,
6549
- reviewPath: path10.join(approval.approvalDir, "wiki", output.page.path),
6640
+ reviewPath: path11.join(approval.approvalDir, "wiki", output.page.path),
6550
6641
  staged: true,
6551
6642
  approvalId: approval.approvalId,
6552
6643
  approvalDir: approval.approvalDir
@@ -6613,7 +6704,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
6613
6704
  const evidenceState = contradictions.length > 0 ? "conflicting" : session.targetedPagePaths.some(
6614
6705
  (targetPath) => sourcePages.some((page) => page.path === targetPath && page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId)))
6615
6706
  ) ? "reinforcing" : session.targetedPagePaths.length ? "new" : "needs_judgment";
6616
- const relativeBriefPath = session.briefPath && path10.isAbsolute(session.briefPath) ? path10.relative(paths.wikiDir, session.briefPath) : session.briefPath;
6707
+ const relativeBriefPath = session.briefPath && path11.isAbsolute(session.briefPath) ? path11.relative(paths.wikiDir, session.briefPath) : session.briefPath;
6617
6708
  const sessionMarkdown = [
6618
6709
  `# Guided Session: ${scope.title}`,
6619
6710
  "",
@@ -6696,8 +6787,8 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
6696
6787
  async function persistSourceSessionPage(rootDir, scope, session) {
6697
6788
  const { paths } = await loadVaultConfig(rootDir);
6698
6789
  const output = await buildSourceSessionSavedPage(rootDir, scope, session);
6699
- const absolutePath = path10.join(paths.wikiDir, output.page.path);
6700
- await ensureDir(path10.dirname(absolutePath));
6790
+ const absolutePath = path11.join(paths.wikiDir, output.page.path);
6791
+ await ensureDir(path11.dirname(absolutePath));
6701
6792
  await fs10.writeFile(absolutePath, output.content, "utf8");
6702
6793
  return { pageId: output.page.id, sessionPath: absolutePath };
6703
6794
  }
@@ -6726,7 +6817,7 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
6726
6817
  targetPages.map(async (targetPage) => {
6727
6818
  const evidenceState = classifyGuidedEvidenceState(scope, targetPage, contradictions);
6728
6819
  const relativePath = useCanonicalTargets && targetPage ? targetPage.path : targetPage ? insightRelativePathForTarget(targetPage, scope) : `insights/topics/${slugify(scope.title)}.md`;
6729
- const absolutePath = path10.join(paths.wikiDir, relativePath);
6820
+ const absolutePath = path11.join(paths.wikiDir, relativePath);
6730
6821
  const existingContent = await fileExists(absolutePath) ? await fs10.readFile(absolutePath, "utf8") : "";
6731
6822
  const parsed = existingContent ? matter3(existingContent) : { data: {}, content: "" };
6732
6823
  const existingData = parsed.data;
@@ -6898,8 +6989,8 @@ async function stageSourceGuideForScope(rootDir, scope, options = {}) {
6898
6989
  }
6899
6990
  );
6900
6991
  session.status = "staged";
6901
- session.reviewPath = path10.join(approval.approvalDir, "wiki", reviewOutput.page.path);
6902
- session.guidePath = path10.join(approval.approvalDir, "wiki", guideOutput.page.path);
6992
+ session.reviewPath = path11.join(approval.approvalDir, "wiki", reviewOutput.page.path);
6993
+ session.guidePath = path11.join(approval.approvalDir, "wiki", guideOutput.page.path);
6903
6994
  session.approvalId = approval.approvalId;
6904
6995
  session.approvalDir = approval.approvalDir;
6905
6996
  const persisted = await persistSourceSessionPage(rootDir, scope, session);
@@ -7037,7 +7128,7 @@ async function addManagedSource(rootDir, input, options = {}) {
7037
7128
  const existing = sources.find((candidate) => matchesManagedSourceSpec(candidate, resolved));
7038
7129
  const now = (/* @__PURE__ */ new Date()).toISOString();
7039
7130
  const source = existing ?? {
7040
- id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path10.resolve(resolved.path), resolved.title) : stableManagedSourceId(resolved.kind, resolved.url, resolved.title),
7131
+ id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path11.resolve(resolved.path), resolved.title) : stableManagedSourceId(resolved.kind, resolved.url, resolved.title),
7041
7132
  kind: resolved.kind,
7042
7133
  title: resolved.title,
7043
7134
  path: resolved.kind === "directory" || resolved.kind === "file" ? resolved.path : void 0,
@@ -7175,7 +7266,7 @@ import { randomUUID } from "crypto";
7175
7266
  import { EventEmitter } from "events";
7176
7267
  import fs11 from "fs/promises";
7177
7268
  import http from "http";
7178
- import path11 from "path";
7269
+ import path12 from "path";
7179
7270
  import { promisify as promisify2 } from "util";
7180
7271
  import matter4 from "gray-matter";
7181
7272
  import mime from "mime-types";
@@ -7340,7 +7431,7 @@ async function readViewerPage(rootDir, relativePath) {
7340
7431
  return null;
7341
7432
  }
7342
7433
  const { paths } = await loadVaultConfig(rootDir);
7343
- const absolutePath = path11.resolve(paths.wikiDir, relativePath);
7434
+ const absolutePath = path12.resolve(paths.wikiDir, relativePath);
7344
7435
  if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
7345
7436
  return null;
7346
7437
  }
@@ -7348,7 +7439,7 @@ async function readViewerPage(rootDir, relativePath) {
7348
7439
  const parsed = matter4(raw);
7349
7440
  return {
7350
7441
  path: relativePath,
7351
- title: typeof parsed.data.title === "string" ? parsed.data.title : path11.basename(relativePath, path11.extname(relativePath)),
7442
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path12.basename(relativePath, path12.extname(relativePath)),
7352
7443
  frontmatter: parsed.data,
7353
7444
  content: parsed.content,
7354
7445
  assets: normalizeOutputAssets(parsed.data.output_assets)
@@ -7359,7 +7450,7 @@ async function readViewerAsset(rootDir, relativePath) {
7359
7450
  return null;
7360
7451
  }
7361
7452
  const { paths } = await loadVaultConfig(rootDir);
7362
- const absolutePath = path11.resolve(paths.wikiDir, relativePath);
7453
+ const absolutePath = path12.resolve(paths.wikiDir, relativePath);
7363
7454
  if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
7364
7455
  return null;
7365
7456
  }
@@ -7400,7 +7491,7 @@ async function writeInboxClip(rootDir, body) {
7400
7491
  const tags = Array.isArray(body.tags) ? body.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : [];
7401
7492
  const now = (/* @__PURE__ */ new Date()).toISOString();
7402
7493
  const fileName = `${now.replace(/[:.]/g, "-")}-${slugForClip(title)}.md`;
7403
- const inboxPath = path11.join(paths.inboxDir, fileName);
7494
+ const inboxPath = path12.join(paths.inboxDir, fileName);
7404
7495
  await fs11.mkdir(paths.inboxDir, { recursive: true });
7405
7496
  const lines = [
7406
7497
  "---",
@@ -7423,12 +7514,12 @@ async function writeInboxClip(rootDir, body) {
7423
7514
  return { mode: "inbox", inboxPath, result };
7424
7515
  }
7425
7516
  async function ensureViewerDist(viewerDistDir) {
7426
- const indexPath = path11.join(viewerDistDir, "index.html");
7517
+ const indexPath = path12.join(viewerDistDir, "index.html");
7427
7518
  if (await fileExists(indexPath)) {
7428
7519
  return;
7429
7520
  }
7430
- const viewerProjectDir = path11.dirname(viewerDistDir);
7431
- if (await fileExists(path11.join(viewerProjectDir, "package.json"))) {
7521
+ const viewerProjectDir = path12.dirname(viewerDistDir);
7522
+ if (await fileExists(path12.join(viewerProjectDir, "package.json"))) {
7432
7523
  await execFileAsync2("pnpm", ["build"], { cwd: viewerProjectDir });
7433
7524
  }
7434
7525
  }
@@ -7451,7 +7542,7 @@ async function startGraphServer(rootDir, port, options = {}) {
7451
7542
  response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
7452
7543
  return;
7453
7544
  }
7454
- const reportPath = path11.join(paths.wikiDir, "graph", "report.json");
7545
+ const reportPath = path12.join(paths.wikiDir, "graph", "report.json");
7455
7546
  const report = await readJsonFile(reportPath) ?? null;
7456
7547
  response.writeHead(200, { "content-type": "application/json" });
7457
7548
  response.end(JSON.stringify(buildViewerGraphArtifact(graph, { report, full: options.full ?? false })));
@@ -7515,7 +7606,7 @@ async function startGraphServer(rootDir, port, options = {}) {
7515
7606
  return;
7516
7607
  }
7517
7608
  if (url.pathname === "/api/graph-report") {
7518
- const reportPath = path11.join(paths.wikiDir, "graph", "report.json");
7609
+ const reportPath = path12.join(paths.wikiDir, "graph", "report.json");
7519
7610
  if (!await fileExists(reportPath)) {
7520
7611
  response.writeHead(404, { "content-type": "application/json" });
7521
7612
  response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
@@ -7759,7 +7850,7 @@ async function startGraphServer(rootDir, port, options = {}) {
7759
7850
  return;
7760
7851
  }
7761
7852
  if (url.pathname === "/api/workspace") {
7762
- const reportPath = path11.join(paths.wikiDir, "graph", "report.json");
7853
+ const reportPath = path12.join(paths.wikiDir, "graph", "report.json");
7763
7854
  const [graphRaw, reportRaw, approvalsRaw, candidatesRaw, memoryTasksRaw, watchStatusRaw, lintRaw, doctorRaw] = await Promise.all([
7764
7855
  readJsonFile(paths.graphPath).catch(() => null),
7765
7856
  readJsonFile(reportPath).catch(() => null),
@@ -7884,8 +7975,8 @@ async function startGraphServer(rootDir, port, options = {}) {
7884
7975
  return;
7885
7976
  }
7886
7977
  const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
7887
- const target = path11.join(paths.viewerDistDir, relativePath);
7888
- const fallback = path11.join(paths.viewerDistDir, "index.html");
7978
+ const target = path12.join(paths.viewerDistDir, relativePath);
7979
+ const fallback = path12.join(paths.viewerDistDir, "index.html");
7889
7980
  const filePath = await fileExists(target) ? target : fallback;
7890
7981
  if (!await fileExists(filePath)) {
7891
7982
  response.writeHead(503, { "content-type": "text/plain" });
@@ -7932,7 +8023,7 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
7932
8023
  throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
7933
8024
  }
7934
8025
  await ensureViewerDist(paths.viewerDistDir);
7935
- const indexPath = path11.join(paths.viewerDistDir, "index.html");
8026
+ const indexPath = path12.join(paths.viewerDistDir, "index.html");
7936
8027
  if (!await fileExists(indexPath)) {
7937
8028
  throw new Error("Viewer build not found. Run `pnpm build` first.");
7938
8029
  }
@@ -7961,14 +8052,14 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
7961
8052
  const rawHtml = await fs11.readFile(indexPath, "utf8");
7962
8053
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
7963
8054
  const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
7964
- const scriptPath = scriptMatch?.[1] ? path11.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
7965
- const stylePath = styleMatch?.[1] ? path11.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
8055
+ const scriptPath = scriptMatch?.[1] ? path12.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
8056
+ const stylePath = styleMatch?.[1] ? path12.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
7966
8057
  if (!scriptPath || !await fileExists(scriptPath)) {
7967
8058
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
7968
8059
  }
7969
8060
  const script = await fs11.readFile(scriptPath, "utf8");
7970
8061
  const style = stylePath && await fileExists(stylePath) ? await fs11.readFile(stylePath, "utf8") : "";
7971
- const report = await readJsonFile(path11.join(paths.wikiDir, "graph", "report.json"));
8062
+ const report = await readJsonFile(path12.join(paths.wikiDir, "graph", "report.json"));
7972
8063
  const embeddedData = JSON.stringify(
7973
8064
  { graph: buildViewerGraphArtifact(graph, { report, full: options.full ?? false }), pages: pages.filter(Boolean), report },
7974
8065
  null,
@@ -7991,9 +8082,9 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
7991
8082
  "</html>",
7992
8083
  ""
7993
8084
  ].filter(Boolean).join("\n");
7994
- await fs11.mkdir(path11.dirname(outputPath), { recursive: true });
8085
+ await fs11.mkdir(path12.dirname(outputPath), { recursive: true });
7995
8086
  await fs11.writeFile(outputPath, html, "utf8");
7996
- return path11.resolve(outputPath);
8087
+ return path12.resolve(outputPath);
7997
8088
  }
7998
8089
  export {
7999
8090
  ALL_MIGRATIONS,
@@ -8007,6 +8098,7 @@ export {
8007
8098
  LOCAL_WHISPER_MODEL_SIZES,
8008
8099
  LocalWhisperProviderAdapter,
8009
8100
  OPENAI_COMPATIBLE_CAPABILITY_MATRIX,
8101
+ SWARMVAULT_OUT_ENV,
8010
8102
  acceptApproval,
8011
8103
  addInput,
8012
8104
  addManagedSource,
@@ -8024,6 +8116,7 @@ export {
8024
8116
  buildGraphShareArtifact,
8025
8117
  buildMemoryGraphElements,
8026
8118
  buildRedactor,
8119
+ checkTrackedRepoChanges,
8027
8120
  compileVault,
8028
8121
  computeDecayScore,
8029
8122
  consolidateVault,
@@ -8054,12 +8147,15 @@ export {
8054
8147
  exportObsidianVault,
8055
8148
  finishMemoryTask,
8056
8149
  getGitHookStatus,
8150
+ getGraphCommunityVault,
8151
+ getGraphStatus,
8057
8152
  getProviderForTask,
8058
8153
  getRetrievalStatus,
8059
8154
  getWatchStatus,
8060
8155
  getWebSearchAdapterForTask,
8061
8156
  getWorkspaceInfo,
8062
8157
  graphDiff,
8158
+ graphStatsVault,
8063
8159
  guideManagedSource,
8064
8160
  guideSourceScope,
8065
8161
  importInbox,
@@ -8107,6 +8203,7 @@ export {
8107
8203
  readMemoryTask,
8108
8204
  readPage,
8109
8205
  rebuildRetrievalIndex,
8206
+ refreshGraphClusters,
8110
8207
  registerLocalWhisperProvider,
8111
8208
  rejectApproval,
8112
8209
  reloadManagedSources,
@@ -8119,6 +8216,7 @@ export {
8119
8216
  renderGraphShareSvg,
8120
8217
  renderMemoryTaskMarkdown,
8121
8218
  resetDecay,
8219
+ resolveArtifactRootDir,
8122
8220
  resolveConsolidationConfig,
8123
8221
  resolveDecayConfig,
8124
8222
  resolveLargeRepoDefaults,