@swarmvaultai/engine 3.5.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,
@@ -91,6 +92,7 @@ import {
91
92
  readWatchStatusArtifact,
92
93
  rebuildRetrievalIndex,
93
94
  recordSession,
95
+ refreshGraphClusters,
94
96
  refreshVaultAfterOutputSave,
95
97
  rejectApproval,
96
98
  removeManifestBySourceId,
@@ -123,9 +125,10 @@ import {
123
125
  writeGuidedSourceSession,
124
126
  writeRetrievalManifest,
125
127
  writeWatchStatusArtifact
126
- } from "./chunk-HKU2T5JX.js";
128
+ } from "./chunk-5GEPTIZE.js";
127
129
  import {
128
130
  LocalWhisperProviderAdapter,
131
+ SWARMVAULT_OUT_ENV,
129
132
  appendJsonLine,
130
133
  assertProviderCapability,
131
134
  createProvider,
@@ -140,6 +143,7 @@ import {
140
143
  loadVaultConfig,
141
144
  normalizeWhitespace,
142
145
  readJsonFile,
146
+ resolveArtifactRootDir,
143
147
  resolvePaths,
144
148
  sha256,
145
149
  slugify,
@@ -147,7 +151,7 @@ import {
147
151
  truncate,
148
152
  uniqueBy,
149
153
  writeJsonFile
150
- } from "./chunk-7QHDATCQ.js";
154
+ } from "./chunk-7O2HJSWQ.js";
151
155
  import {
152
156
  estimatePageTokens,
153
157
  estimateTokens,
@@ -3920,27 +3924,78 @@ async function pushGraphNeo4j(rootDir, options = {}) {
3920
3924
  }
3921
3925
  }
3922
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
+
3923
3978
  // src/hooks.ts
3924
3979
  import fs6 from "fs/promises";
3925
- import path6 from "path";
3980
+ import path7 from "path";
3926
3981
  import process3 from "process";
3927
3982
  var hookStart = "# >>> swarmvault hook >>>";
3928
3983
  var hookEnd = "# <<< swarmvault hook <<<";
3929
3984
  async function findNearestGitRoot(startPath) {
3930
- let current = path6.resolve(startPath);
3985
+ let current = path7.resolve(startPath);
3931
3986
  try {
3932
3987
  const stat = await fs6.stat(current);
3933
3988
  if (!stat.isDirectory()) {
3934
- current = path6.dirname(current);
3989
+ current = path7.dirname(current);
3935
3990
  }
3936
3991
  } catch {
3937
- current = path6.dirname(current);
3992
+ current = path7.dirname(current);
3938
3993
  }
3939
3994
  while (true) {
3940
- if (await fileExists(path6.join(current, ".git"))) {
3995
+ if (await fileExists(path7.join(current, ".git"))) {
3941
3996
  return current;
3942
3997
  }
3943
- const parent = path6.dirname(current);
3998
+ const parent = path7.dirname(current);
3944
3999
  if (parent === current) {
3945
4000
  return null;
3946
4001
  }
@@ -3952,8 +4007,8 @@ function shellQuote(value) {
3952
4007
  }
3953
4008
  function resolveSwarmvaultExecutableCandidate() {
3954
4009
  const argvPath = process3.argv[1];
3955
- 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}`))) {
3956
- 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);
3957
4012
  }
3958
4013
  return "swarmvault";
3959
4014
  }
@@ -3972,7 +4027,7 @@ function managedHookBlock(vaultRoot) {
3972
4027
  ].join("\n");
3973
4028
  }
3974
4029
  function hookPath(repoRoot, hookName) {
3975
- return path6.join(repoRoot, ".git", "hooks", hookName);
4030
+ return path7.join(repoRoot, ".git", "hooks", hookName);
3976
4031
  }
3977
4032
  async function readHookStatus(filePath) {
3978
4033
  if (!await fileExists(filePath)) {
@@ -3996,7 +4051,7 @@ ${block}`.trimEnd();
3996
4051
  next = `#!/bin/sh
3997
4052
  ${block}`.trimEnd();
3998
4053
  }
3999
- await ensureDir(path6.dirname(filePath));
4054
+ await ensureDir(path7.dirname(filePath));
4000
4055
  await fs6.writeFile(filePath, `${next}
4001
4056
  `, { mode: 493, encoding: "utf8" });
4002
4057
  await fs6.chmod(filePath, 493);
@@ -4039,7 +4094,7 @@ async function installGitHooks(rootDir) {
4039
4094
  if (!repoRoot) {
4040
4095
  throw new Error("No git repository found above the current vault.");
4041
4096
  }
4042
- const block = managedHookBlock(path6.resolve(rootDir));
4097
+ const block = managedHookBlock(path7.resolve(rootDir));
4043
4098
  await upsertHookFile(hookPath(repoRoot, "post-commit"), block);
4044
4099
  await upsertHookFile(hookPath(repoRoot, "post-checkout"), block);
4045
4100
  return getGitHookStatus(rootDir);
@@ -4060,11 +4115,11 @@ async function uninstallGitHooks(rootDir) {
4060
4115
 
4061
4116
  // src/mcp.ts
4062
4117
  import fs7 from "fs/promises";
4063
- import path7 from "path";
4118
+ import path8 from "path";
4064
4119
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4065
4120
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4066
4121
  import { z } from "zod";
4067
- var SERVER_VERSION = "3.5.0";
4122
+ var SERVER_VERSION = "3.6.0";
4068
4123
  async function createMcpServer(rootDir) {
4069
4124
  const server = new McpServer({
4070
4125
  name: "swarmvault",
@@ -4202,6 +4257,18 @@ async function createMcpServer(rootDir) {
4202
4257
  return asToolText(await graphStatsVault(rootDir));
4203
4258
  })
4204
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
+ );
4205
4272
  server.registerTool(
4206
4273
  "get_node",
4207
4274
  {
@@ -4760,7 +4827,7 @@ async function createMcpServer(rootDir) {
4760
4827
  },
4761
4828
  async () => {
4762
4829
  const { paths } = await loadVaultConfig(rootDir);
4763
- 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();
4764
4831
  return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
4765
4832
  }
4766
4833
  );
@@ -4829,7 +4896,7 @@ async function createMcpServer(rootDir) {
4829
4896
  return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
4830
4897
  }
4831
4898
  const { paths } = await loadVaultConfig(rootDir);
4832
- const absolutePath = path7.resolve(paths.wikiDir, relativePath);
4899
+ const absolutePath = path8.resolve(paths.wikiDir, relativePath);
4833
4900
  return asTextResource(`swarmvault://pages/${encodedPath}`, await fs7.readFile(absolutePath, "utf8"));
4834
4901
  }
4835
4902
  );
@@ -4838,11 +4905,11 @@ async function createMcpServer(rootDir) {
4838
4905
  new ResourceTemplate("swarmvault://sessions/{path}", {
4839
4906
  list: async () => {
4840
4907
  const { paths } = await loadVaultConfig(rootDir);
4841
- 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();
4842
4909
  return {
4843
4910
  resources: files.map((relativePath) => ({
4844
4911
  uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
4845
- name: path7.basename(relativePath, ".md"),
4912
+ name: path8.basename(relativePath, ".md"),
4846
4913
  title: relativePath,
4847
4914
  description: "SwarmVault session artifact",
4848
4915
  mimeType: "text/markdown"
@@ -4859,7 +4926,7 @@ async function createMcpServer(rootDir) {
4859
4926
  const { paths } = await loadVaultConfig(rootDir);
4860
4927
  const encodedPath = typeof variables.path === "string" ? variables.path : "";
4861
4928
  const relativePath = decodeURIComponent(encodedPath);
4862
- const absolutePath = path7.resolve(paths.sessionsDir, relativePath);
4929
+ const absolutePath = path8.resolve(paths.sessionsDir, relativePath);
4863
4930
  if (!isPathWithin(paths.sessionsDir, absolutePath) || !await fileExists(absolutePath)) {
4864
4931
  return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
4865
4932
  }
@@ -4925,7 +4992,7 @@ function asTextResource(uri, text) {
4925
4992
  import { createWriteStream, constants as fsConstants } from "fs";
4926
4993
  import fs8 from "fs/promises";
4927
4994
  import os from "os";
4928
- import path8 from "path";
4995
+ import path9 from "path";
4929
4996
  import { Readable } from "stream";
4930
4997
  import { pipeline } from "stream/promises";
4931
4998
  var BINARY_CANDIDATES = ["whisper-cli", "whisper-cpp", "whisper"];
@@ -4953,10 +5020,10 @@ async function discoverLocalWhisperBinary(options = {}) {
4953
5020
  }
4954
5021
  const pathValue = env.PATH ?? "";
4955
5022
  const candidates = [];
4956
- for (const dir of pathValue.split(path8.delimiter)) {
5023
+ for (const dir of pathValue.split(path9.delimiter)) {
4957
5024
  if (!dir) continue;
4958
5025
  for (const name of BINARY_CANDIDATES) {
4959
- const full = path8.join(dir, name);
5026
+ const full = path9.join(dir, name);
4960
5027
  candidates.push(full);
4961
5028
  if (await isExecutable(full)) {
4962
5029
  return { binaryPath: full, candidates, source: "path" };
@@ -4967,14 +5034,14 @@ async function discoverLocalWhisperBinary(options = {}) {
4967
5034
  }
4968
5035
  function expectedModelPath(modelName, homeDir) {
4969
5036
  const home = homeDir ?? os.homedir();
4970
- return path8.join(home, ".swarmvault", "models", `ggml-${modelName}.bin`);
5037
+ return path9.join(home, ".swarmvault", "models", `ggml-${modelName}.bin`);
4971
5038
  }
4972
5039
  function modelDownloadUrl(modelName) {
4973
5040
  return `${HUGGINGFACE_BASE}/ggml-${modelName}.bin`;
4974
5041
  }
4975
5042
  async function downloadWhisperModel(options) {
4976
5043
  const destPath = expectedModelPath(options.modelName, options.homeDir);
4977
- await ensureDir(path8.dirname(destPath));
5044
+ await ensureDir(path9.dirname(destPath));
4978
5045
  const doFetch = options.fetchImpl ?? fetch;
4979
5046
  const url = modelDownloadUrl(options.modelName);
4980
5047
  const response = await doFetch(url);
@@ -5134,12 +5201,12 @@ async function withCapabilityFallback(provider, capability, run, fallback) {
5134
5201
 
5135
5202
  // src/schedule.ts
5136
5203
  import fs9 from "fs/promises";
5137
- import path9 from "path";
5204
+ import path10 from "path";
5138
5205
  function scheduleStatePath(schedulesDir, jobId) {
5139
- return path9.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
5206
+ return path10.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
5140
5207
  }
5141
5208
  function scheduleLockPath(schedulesDir, jobId) {
5142
- return path9.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
5209
+ return path10.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
5143
5210
  }
5144
5211
  function parseEveryDuration(value) {
5145
5212
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -5408,7 +5475,7 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
5408
5475
  // src/sources.ts
5409
5476
  import { spawn } from "child_process";
5410
5477
  import fs10 from "fs/promises";
5411
- import path10 from "path";
5478
+ import path11 from "path";
5412
5479
  import matter3 from "gray-matter";
5413
5480
  import { JSDOM } from "jsdom";
5414
5481
  var DEFAULT_CRAWL_MAX_PAGES = 12;
@@ -5454,24 +5521,24 @@ function emptyManagedSourceSyncCounts() {
5454
5521
  };
5455
5522
  }
5456
5523
  function withinRoot(rootPath, targetPath) {
5457
- const relative = path10.relative(rootPath, targetPath);
5458
- return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
5524
+ const relative = path11.relative(rootPath, targetPath);
5525
+ return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
5459
5526
  }
5460
5527
  async function findNearestGitRoot2(startPath) {
5461
- let current = path10.resolve(startPath);
5528
+ let current = path11.resolve(startPath);
5462
5529
  try {
5463
5530
  const stat = await fs10.stat(current);
5464
5531
  if (!stat.isDirectory()) {
5465
- current = path10.dirname(current);
5532
+ current = path11.dirname(current);
5466
5533
  }
5467
5534
  } catch {
5468
- current = path10.dirname(current);
5535
+ current = path11.dirname(current);
5469
5536
  }
5470
5537
  while (true) {
5471
- if (await fileExists(path10.join(current, ".git"))) {
5538
+ if (await fileExists(path11.join(current, ".git"))) {
5472
5539
  return current;
5473
5540
  }
5474
- const parent = path10.dirname(current);
5541
+ const parent = path11.dirname(current);
5475
5542
  if (parent === current) {
5476
5543
  return null;
5477
5544
  }
@@ -5545,7 +5612,7 @@ function isAllowedDocsCandidate(candidate, startUrl) {
5545
5612
  if (candidate.origin !== startUrl.origin) {
5546
5613
  return false;
5547
5614
  }
5548
- const extension = path10.extname(candidate.pathname).toLowerCase();
5615
+ const extension = path11.extname(candidate.pathname).toLowerCase();
5549
5616
  if (extension && extension !== ".html" && extension !== ".htm" && extension !== ".md") {
5550
5617
  return false;
5551
5618
  }
@@ -5634,12 +5701,12 @@ function matchesManagedSourceSpec(existing, input) {
5634
5701
  return false;
5635
5702
  }
5636
5703
  if (input.kind === "directory" || input.kind === "file") {
5637
- return path10.resolve(existing.path ?? "") === path10.resolve(input.path);
5704
+ return path11.resolve(existing.path ?? "") === path11.resolve(input.path);
5638
5705
  }
5639
5706
  return (existing.url ?? "") === input.url;
5640
5707
  }
5641
5708
  async function resolveManagedSourceInput(rootDir, input) {
5642
- const absoluteInput = path10.resolve(rootDir, input);
5709
+ const absoluteInput = path11.resolve(rootDir, input);
5643
5710
  if (!(input.startsWith("http://") || input.startsWith("https://"))) {
5644
5711
  const stat = await fs10.stat(absoluteInput).catch(() => null);
5645
5712
  if (!stat) {
@@ -5649,7 +5716,7 @@ async function resolveManagedSourceInput(rootDir, input) {
5649
5716
  return {
5650
5717
  kind: "file",
5651
5718
  path: absoluteInput,
5652
- title: path10.basename(absoluteInput, path10.extname(absoluteInput)) || absoluteInput
5719
+ title: path11.basename(absoluteInput, path11.extname(absoluteInput)) || absoluteInput
5653
5720
  };
5654
5721
  }
5655
5722
  if (!stat.isDirectory()) {
@@ -5661,7 +5728,7 @@ async function resolveManagedSourceInput(rootDir, input) {
5661
5728
  kind: "directory",
5662
5729
  path: absoluteInput,
5663
5730
  repoRoot,
5664
- title: path10.basename(absoluteInput) || absoluteInput
5731
+ title: path11.basename(absoluteInput) || absoluteInput
5665
5732
  };
5666
5733
  }
5667
5734
  const github = normalizeGitHubRepoRootUrl(input);
@@ -5684,16 +5751,16 @@ async function resolveManagedSourceInput(rootDir, input) {
5684
5751
  };
5685
5752
  }
5686
5753
  function directorySourceIdsFor(manifests, inputPath) {
5687
- 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));
5688
5755
  }
5689
5756
  function fileSourceIdsFor(manifests, inputPath) {
5690
- const absoluteInput = path10.resolve(inputPath);
5691
- 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));
5692
5759
  }
5693
5760
  async function syncDirectorySource(rootDir, inputPath, repoRoot) {
5694
5761
  const manifestsBefore = await listManifests(rootDir);
5695
5762
  const previousInScope = manifestsBefore.filter(
5696
- (manifest) => manifest.originalPath && withinRoot(path10.resolve(inputPath), path10.resolve(manifest.originalPath))
5763
+ (manifest) => manifest.originalPath && withinRoot(path11.resolve(inputPath), path11.resolve(manifest.originalPath))
5697
5764
  );
5698
5765
  const result = await ingestDirectory(rootDir, inputPath, { repoRoot });
5699
5766
  const removed = [];
@@ -5701,7 +5768,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
5701
5768
  if (!manifest.originalPath) {
5702
5769
  continue;
5703
5770
  }
5704
- if (await fileExists(path10.resolve(manifest.originalPath))) {
5771
+ if (await fileExists(path11.resolve(manifest.originalPath))) {
5705
5772
  continue;
5706
5773
  }
5707
5774
  const removedManifest = await removeManifestBySourceId(rootDir, manifest.sourceId);
@@ -5711,7 +5778,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
5711
5778
  }
5712
5779
  const manifestsAfter = await listManifests(rootDir);
5713
5780
  return {
5714
- title: path10.basename(inputPath) || inputPath,
5781
+ title: path11.basename(inputPath) || inputPath,
5715
5782
  sourceIds: directorySourceIdsFor(manifestsAfter, inputPath),
5716
5783
  counts: {
5717
5784
  scannedCount: result.scannedCount,
@@ -5727,7 +5794,7 @@ async function syncFileSource(rootDir, inputPath) {
5727
5794
  const result = await ingestInputDetailed(rootDir, inputPath);
5728
5795
  const manifestsAfter = await listManifests(rootDir);
5729
5796
  return {
5730
- title: path10.basename(inputPath, path10.extname(inputPath)) || inputPath,
5797
+ title: path11.basename(inputPath, path11.extname(inputPath)) || inputPath,
5731
5798
  sourceIds: fileSourceIdsFor(manifestsAfter, inputPath),
5732
5799
  counts: {
5733
5800
  scannedCount: result.scannedCount,
@@ -5761,7 +5828,7 @@ async function runGitCommand(cwd, args) {
5761
5828
  }
5762
5829
  async function syncGitHubRepoSource(rootDir, entry) {
5763
5830
  const workingDir = await managedSourceWorkingDir(rootDir, entry.id);
5764
- const checkoutDir = path10.join(workingDir, "checkout");
5831
+ const checkoutDir = path11.join(workingDir, "checkout");
5765
5832
  await fs10.rm(checkoutDir, { recursive: true, force: true });
5766
5833
  await ensureDir(workingDir);
5767
5834
  if (!entry.url) {
@@ -5892,7 +5959,7 @@ function scopedNodeIds(graph, sourceIds) {
5892
5959
  async function loadSourceAnalyses(rootDir, sourceIds) {
5893
5960
  const { paths } = await loadVaultConfig(rootDir);
5894
5961
  const analyses = await Promise.all(
5895
- 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`)))
5896
5963
  );
5897
5964
  return analyses.filter((analysis) => Boolean(analysis?.sourceId));
5898
5965
  }
@@ -6053,8 +6120,8 @@ async function writeSourceBriefForScope(rootDir, source) {
6053
6120
  confidence: 0.82
6054
6121
  }
6055
6122
  });
6056
- const absolutePath = path10.join(paths.wikiDir, output.page.path);
6057
- await ensureDir(path10.dirname(absolutePath));
6123
+ const absolutePath = path11.join(paths.wikiDir, output.page.path);
6124
+ await ensureDir(path11.dirname(absolutePath));
6058
6125
  await fs10.writeFile(absolutePath, output.content, "utf8");
6059
6126
  return absolutePath;
6060
6127
  }
@@ -6343,7 +6410,7 @@ function selectGuidedTargetPages(scope, sourcePages, questions) {
6343
6410
  return (matchedTargets.length ? matchedTargets : canonicalPages).slice(0, 6);
6344
6411
  }
6345
6412
  function insightRelativePathForTarget(page, scope) {
6346
- const basename = path10.basename(page.path);
6413
+ const basename = path11.basename(page.path);
6347
6414
  if (page.kind === "concept") {
6348
6415
  return `insights/concepts/${basename}`;
6349
6416
  }
@@ -6570,7 +6637,7 @@ async function stageSourceReviewForScope(rootDir, scope) {
6570
6637
  return {
6571
6638
  sourceId: scope.id,
6572
6639
  pageId: output.page.id,
6573
- reviewPath: path10.join(approval.approvalDir, "wiki", output.page.path),
6640
+ reviewPath: path11.join(approval.approvalDir, "wiki", output.page.path),
6574
6641
  staged: true,
6575
6642
  approvalId: approval.approvalId,
6576
6643
  approvalDir: approval.approvalDir
@@ -6637,7 +6704,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
6637
6704
  const evidenceState = contradictions.length > 0 ? "conflicting" : session.targetedPagePaths.some(
6638
6705
  (targetPath) => sourcePages.some((page) => page.path === targetPath && page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId)))
6639
6706
  ) ? "reinforcing" : session.targetedPagePaths.length ? "new" : "needs_judgment";
6640
- 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;
6641
6708
  const sessionMarkdown = [
6642
6709
  `# Guided Session: ${scope.title}`,
6643
6710
  "",
@@ -6720,8 +6787,8 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
6720
6787
  async function persistSourceSessionPage(rootDir, scope, session) {
6721
6788
  const { paths } = await loadVaultConfig(rootDir);
6722
6789
  const output = await buildSourceSessionSavedPage(rootDir, scope, session);
6723
- const absolutePath = path10.join(paths.wikiDir, output.page.path);
6724
- await ensureDir(path10.dirname(absolutePath));
6790
+ const absolutePath = path11.join(paths.wikiDir, output.page.path);
6791
+ await ensureDir(path11.dirname(absolutePath));
6725
6792
  await fs10.writeFile(absolutePath, output.content, "utf8");
6726
6793
  return { pageId: output.page.id, sessionPath: absolutePath };
6727
6794
  }
@@ -6750,7 +6817,7 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
6750
6817
  targetPages.map(async (targetPage) => {
6751
6818
  const evidenceState = classifyGuidedEvidenceState(scope, targetPage, contradictions);
6752
6819
  const relativePath = useCanonicalTargets && targetPage ? targetPage.path : targetPage ? insightRelativePathForTarget(targetPage, scope) : `insights/topics/${slugify(scope.title)}.md`;
6753
- const absolutePath = path10.join(paths.wikiDir, relativePath);
6820
+ const absolutePath = path11.join(paths.wikiDir, relativePath);
6754
6821
  const existingContent = await fileExists(absolutePath) ? await fs10.readFile(absolutePath, "utf8") : "";
6755
6822
  const parsed = existingContent ? matter3(existingContent) : { data: {}, content: "" };
6756
6823
  const existingData = parsed.data;
@@ -6922,8 +6989,8 @@ async function stageSourceGuideForScope(rootDir, scope, options = {}) {
6922
6989
  }
6923
6990
  );
6924
6991
  session.status = "staged";
6925
- session.reviewPath = path10.join(approval.approvalDir, "wiki", reviewOutput.page.path);
6926
- 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);
6927
6994
  session.approvalId = approval.approvalId;
6928
6995
  session.approvalDir = approval.approvalDir;
6929
6996
  const persisted = await persistSourceSessionPage(rootDir, scope, session);
@@ -7061,7 +7128,7 @@ async function addManagedSource(rootDir, input, options = {}) {
7061
7128
  const existing = sources.find((candidate) => matchesManagedSourceSpec(candidate, resolved));
7062
7129
  const now = (/* @__PURE__ */ new Date()).toISOString();
7063
7130
  const source = existing ?? {
7064
- 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),
7065
7132
  kind: resolved.kind,
7066
7133
  title: resolved.title,
7067
7134
  path: resolved.kind === "directory" || resolved.kind === "file" ? resolved.path : void 0,
@@ -7199,7 +7266,7 @@ import { randomUUID } from "crypto";
7199
7266
  import { EventEmitter } from "events";
7200
7267
  import fs11 from "fs/promises";
7201
7268
  import http from "http";
7202
- import path11 from "path";
7269
+ import path12 from "path";
7203
7270
  import { promisify as promisify2 } from "util";
7204
7271
  import matter4 from "gray-matter";
7205
7272
  import mime from "mime-types";
@@ -7364,7 +7431,7 @@ async function readViewerPage(rootDir, relativePath) {
7364
7431
  return null;
7365
7432
  }
7366
7433
  const { paths } = await loadVaultConfig(rootDir);
7367
- const absolutePath = path11.resolve(paths.wikiDir, relativePath);
7434
+ const absolutePath = path12.resolve(paths.wikiDir, relativePath);
7368
7435
  if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
7369
7436
  return null;
7370
7437
  }
@@ -7372,7 +7439,7 @@ async function readViewerPage(rootDir, relativePath) {
7372
7439
  const parsed = matter4(raw);
7373
7440
  return {
7374
7441
  path: relativePath,
7375
- 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)),
7376
7443
  frontmatter: parsed.data,
7377
7444
  content: parsed.content,
7378
7445
  assets: normalizeOutputAssets(parsed.data.output_assets)
@@ -7383,7 +7450,7 @@ async function readViewerAsset(rootDir, relativePath) {
7383
7450
  return null;
7384
7451
  }
7385
7452
  const { paths } = await loadVaultConfig(rootDir);
7386
- const absolutePath = path11.resolve(paths.wikiDir, relativePath);
7453
+ const absolutePath = path12.resolve(paths.wikiDir, relativePath);
7387
7454
  if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
7388
7455
  return null;
7389
7456
  }
@@ -7424,7 +7491,7 @@ async function writeInboxClip(rootDir, body) {
7424
7491
  const tags = Array.isArray(body.tags) ? body.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : [];
7425
7492
  const now = (/* @__PURE__ */ new Date()).toISOString();
7426
7493
  const fileName = `${now.replace(/[:.]/g, "-")}-${slugForClip(title)}.md`;
7427
- const inboxPath = path11.join(paths.inboxDir, fileName);
7494
+ const inboxPath = path12.join(paths.inboxDir, fileName);
7428
7495
  await fs11.mkdir(paths.inboxDir, { recursive: true });
7429
7496
  const lines = [
7430
7497
  "---",
@@ -7447,12 +7514,12 @@ async function writeInboxClip(rootDir, body) {
7447
7514
  return { mode: "inbox", inboxPath, result };
7448
7515
  }
7449
7516
  async function ensureViewerDist(viewerDistDir) {
7450
- const indexPath = path11.join(viewerDistDir, "index.html");
7517
+ const indexPath = path12.join(viewerDistDir, "index.html");
7451
7518
  if (await fileExists(indexPath)) {
7452
7519
  return;
7453
7520
  }
7454
- const viewerProjectDir = path11.dirname(viewerDistDir);
7455
- if (await fileExists(path11.join(viewerProjectDir, "package.json"))) {
7521
+ const viewerProjectDir = path12.dirname(viewerDistDir);
7522
+ if (await fileExists(path12.join(viewerProjectDir, "package.json"))) {
7456
7523
  await execFileAsync2("pnpm", ["build"], { cwd: viewerProjectDir });
7457
7524
  }
7458
7525
  }
@@ -7475,7 +7542,7 @@ async function startGraphServer(rootDir, port, options = {}) {
7475
7542
  response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
7476
7543
  return;
7477
7544
  }
7478
- const reportPath = path11.join(paths.wikiDir, "graph", "report.json");
7545
+ const reportPath = path12.join(paths.wikiDir, "graph", "report.json");
7479
7546
  const report = await readJsonFile(reportPath) ?? null;
7480
7547
  response.writeHead(200, { "content-type": "application/json" });
7481
7548
  response.end(JSON.stringify(buildViewerGraphArtifact(graph, { report, full: options.full ?? false })));
@@ -7539,7 +7606,7 @@ async function startGraphServer(rootDir, port, options = {}) {
7539
7606
  return;
7540
7607
  }
7541
7608
  if (url.pathname === "/api/graph-report") {
7542
- const reportPath = path11.join(paths.wikiDir, "graph", "report.json");
7609
+ const reportPath = path12.join(paths.wikiDir, "graph", "report.json");
7543
7610
  if (!await fileExists(reportPath)) {
7544
7611
  response.writeHead(404, { "content-type": "application/json" });
7545
7612
  response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
@@ -7783,7 +7850,7 @@ async function startGraphServer(rootDir, port, options = {}) {
7783
7850
  return;
7784
7851
  }
7785
7852
  if (url.pathname === "/api/workspace") {
7786
- const reportPath = path11.join(paths.wikiDir, "graph", "report.json");
7853
+ const reportPath = path12.join(paths.wikiDir, "graph", "report.json");
7787
7854
  const [graphRaw, reportRaw, approvalsRaw, candidatesRaw, memoryTasksRaw, watchStatusRaw, lintRaw, doctorRaw] = await Promise.all([
7788
7855
  readJsonFile(paths.graphPath).catch(() => null),
7789
7856
  readJsonFile(reportPath).catch(() => null),
@@ -7908,8 +7975,8 @@ async function startGraphServer(rootDir, port, options = {}) {
7908
7975
  return;
7909
7976
  }
7910
7977
  const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
7911
- const target = path11.join(paths.viewerDistDir, relativePath);
7912
- 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");
7913
7980
  const filePath = await fileExists(target) ? target : fallback;
7914
7981
  if (!await fileExists(filePath)) {
7915
7982
  response.writeHead(503, { "content-type": "text/plain" });
@@ -7956,7 +8023,7 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
7956
8023
  throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
7957
8024
  }
7958
8025
  await ensureViewerDist(paths.viewerDistDir);
7959
- const indexPath = path11.join(paths.viewerDistDir, "index.html");
8026
+ const indexPath = path12.join(paths.viewerDistDir, "index.html");
7960
8027
  if (!await fileExists(indexPath)) {
7961
8028
  throw new Error("Viewer build not found. Run `pnpm build` first.");
7962
8029
  }
@@ -7985,14 +8052,14 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
7985
8052
  const rawHtml = await fs11.readFile(indexPath, "utf8");
7986
8053
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
7987
8054
  const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
7988
- const scriptPath = scriptMatch?.[1] ? path11.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
7989
- 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;
7990
8057
  if (!scriptPath || !await fileExists(scriptPath)) {
7991
8058
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
7992
8059
  }
7993
8060
  const script = await fs11.readFile(scriptPath, "utf8");
7994
8061
  const style = stylePath && await fileExists(stylePath) ? await fs11.readFile(stylePath, "utf8") : "";
7995
- const report = await readJsonFile(path11.join(paths.wikiDir, "graph", "report.json"));
8062
+ const report = await readJsonFile(path12.join(paths.wikiDir, "graph", "report.json"));
7996
8063
  const embeddedData = JSON.stringify(
7997
8064
  { graph: buildViewerGraphArtifact(graph, { report, full: options.full ?? false }), pages: pages.filter(Boolean), report },
7998
8065
  null,
@@ -8015,9 +8082,9 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
8015
8082
  "</html>",
8016
8083
  ""
8017
8084
  ].filter(Boolean).join("\n");
8018
- await fs11.mkdir(path11.dirname(outputPath), { recursive: true });
8085
+ await fs11.mkdir(path12.dirname(outputPath), { recursive: true });
8019
8086
  await fs11.writeFile(outputPath, html, "utf8");
8020
- return path11.resolve(outputPath);
8087
+ return path12.resolve(outputPath);
8021
8088
  }
8022
8089
  export {
8023
8090
  ALL_MIGRATIONS,
@@ -8031,6 +8098,7 @@ export {
8031
8098
  LOCAL_WHISPER_MODEL_SIZES,
8032
8099
  LocalWhisperProviderAdapter,
8033
8100
  OPENAI_COMPATIBLE_CAPABILITY_MATRIX,
8101
+ SWARMVAULT_OUT_ENV,
8034
8102
  acceptApproval,
8035
8103
  addInput,
8036
8104
  addManagedSource,
@@ -8048,6 +8116,7 @@ export {
8048
8116
  buildGraphShareArtifact,
8049
8117
  buildMemoryGraphElements,
8050
8118
  buildRedactor,
8119
+ checkTrackedRepoChanges,
8051
8120
  compileVault,
8052
8121
  computeDecayScore,
8053
8122
  consolidateVault,
@@ -8079,6 +8148,7 @@ export {
8079
8148
  finishMemoryTask,
8080
8149
  getGitHookStatus,
8081
8150
  getGraphCommunityVault,
8151
+ getGraphStatus,
8082
8152
  getProviderForTask,
8083
8153
  getRetrievalStatus,
8084
8154
  getWatchStatus,
@@ -8133,6 +8203,7 @@ export {
8133
8203
  readMemoryTask,
8134
8204
  readPage,
8135
8205
  rebuildRetrievalIndex,
8206
+ refreshGraphClusters,
8136
8207
  registerLocalWhisperProvider,
8137
8208
  rejectApproval,
8138
8209
  reloadManagedSources,
@@ -8145,6 +8216,7 @@ export {
8145
8216
  renderGraphShareSvg,
8146
8217
  renderMemoryTaskMarkdown,
8147
8218
  resetDecay,
8219
+ resolveArtifactRootDir,
8148
8220
  resolveConsolidationConfig,
8149
8221
  resolveDecayConfig,
8150
8222
  resolveLargeRepoDefaults,