@swarmvaultai/engine 0.7.22 → 0.7.24

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.d.ts CHANGED
@@ -57,7 +57,7 @@ type PageStatus = "draft" | "candidate" | "active" | "archived";
57
57
  type PageManager = "system" | "human";
58
58
  type ApprovalEntryStatus = "pending" | "accepted" | "rejected";
59
59
  type ApprovalChangeType = "create" | "update" | "delete" | "promote";
60
- type ApprovalBundleType = "compile" | "generated_output" | "source_review" | "guided_source" | "guided_session";
60
+ type ApprovalBundleType = "compile" | "generated-output" | "source-review" | "guided-source" | "guided-session";
61
61
  type ApprovalEntryLabel = "source-brief" | "source-review" | "source-guide" | "guided-update";
62
62
  type GuidedSourceSessionStatus = "awaiting_input" | "ready_to_stage" | "staged" | "accepted" | "rejected";
63
63
  type VaultProfilePreset = "reader" | "timeline" | "diligence" | "thesis";
@@ -767,6 +767,7 @@ interface WatchOptions {
767
767
  lint?: boolean;
768
768
  debounceMs?: number;
769
769
  repo?: boolean;
770
+ codeOnly?: boolean;
770
771
  }
771
772
  interface PendingSemanticRefreshEntry {
772
773
  id: string;
package/dist/index.js CHANGED
@@ -2263,7 +2263,7 @@ function managedHookBlock(vaultRoot) {
2263
2263
  `swarmvault_bin=${shellQuote(resolvedExecutable)}`,
2264
2264
  '[ ! -x "$swarmvault_bin" ] && swarmvault_bin=$(command -v swarmvault 2>/dev/null || true)',
2265
2265
  'if [ -n "$swarmvault_bin" ] && [ -x "$swarmvault_bin" ]; then',
2266
- ` "$swarmvault_bin" watch --repo --once >/dev/null 2>&1 || printf '[swarmvault hook] refresh failed\\n' >&2`,
2266
+ ` "$swarmvault_bin" watch --repo --once --code-only >/dev/null 2>&1 || printf '[swarmvault hook] refresh failed\\n' >&2`,
2267
2267
  "fi",
2268
2268
  hookEnd,
2269
2269
  ""
@@ -12748,6 +12748,8 @@ function buildSchemaPrompt(schema, instruction) {
12748
12748
  // src/vault.ts
12749
12749
  import fs19 from "fs/promises";
12750
12750
  import path23 from "path";
12751
+ import Graph from "graphology";
12752
+ import louvain from "graphology-communities-louvain";
12751
12753
  import matter9 from "gray-matter";
12752
12754
  import { z as z7 } from "zod";
12753
12755
 
@@ -17483,8 +17485,8 @@ async function buildDashboardRecords(config, paths, graph, schemaHash, report) {
17483
17485
  ).slice(0, 20);
17484
17486
  const sourceSessions = await listGuidedSourceSessions(paths.rootDir);
17485
17487
  const stagedGuideBundles = (await Promise.all(
17486
- (await fs19.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => await readJsonFile(approvalManifestPath(paths, entry.name)))
17487
- )).filter((manifest) => Boolean(manifest)).filter((manifest) => manifest.bundleType === "guided_source" || manifest.bundleType === "guided_session").sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, 12);
17488
+ (await fs19.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => await readApprovalManifest(paths, entry.name).catch(() => null))
17489
+ )).filter((manifest) => Boolean(manifest)).filter((manifest) => manifest.bundleType === "guided-source" || manifest.bundleType === "guided-session").sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, 12);
17488
17490
  const readerFocusPages = uniqueBy([...guidePages, ...briefPages, ...conceptPages, ...entityPages], (page) => page.id).slice(0, 8);
17489
17491
  const diligenceSessions = sourceSessions.filter((session) => session.status === "staged" || session.status === "awaiting_input").slice(0, 8);
17490
17492
  const dashboards = [
@@ -18026,26 +18028,32 @@ function deriveGraphMetrics(nodes, edges) {
18026
18028
  }
18027
18029
  const communityMap = /* @__PURE__ */ new Map();
18028
18030
  const communities = [];
18029
- const visited = /* @__PURE__ */ new Set();
18031
+ const nonSourceIdSet = new Set(nonSourceNodes.map((node) => node.id));
18032
+ const louvainGraph = new Graph({ type: "undirected" });
18030
18033
  for (const node of nonSourceNodes) {
18031
- if (visited.has(node.id)) {
18032
- continue;
18033
- }
18034
- const queue = [node.id];
18035
- const memberIds = [];
18036
- visited.add(node.id);
18037
- while (queue.length) {
18038
- const current = queue.shift();
18039
- memberIds.push(current);
18040
- for (const neighbor of adjacency.get(current) ?? []) {
18041
- if (!visited.has(neighbor) && nodes.find((candidate) => candidate.id === neighbor)?.type !== "source") {
18042
- visited.add(neighbor);
18043
- queue.push(neighbor);
18044
- }
18034
+ louvainGraph.addNode(node.id);
18035
+ }
18036
+ for (const node of nonSourceNodes) {
18037
+ for (const neighbor of adjacency.get(node.id) ?? []) {
18038
+ if (nonSourceIdSet.has(neighbor) && !louvainGraph.hasEdge(node.id, neighbor)) {
18039
+ louvainGraph.addEdge(node.id, neighbor);
18045
18040
  }
18046
18041
  }
18047
- const labelSeed = nodes.find((candidate) => candidate.id === memberIds[0])?.label ?? `cluster-${communities.length + 1}`;
18048
- const communityId = buildCommunityId(labelSeed, communities.length);
18042
+ }
18043
+ const louvainMapping = louvainGraph.size > 0 ? louvain(louvainGraph, { resolution: 1 }) : {};
18044
+ const groupByCommunity = /* @__PURE__ */ new Map();
18045
+ let nextIsolated = -1;
18046
+ for (const node of nonSourceNodes) {
18047
+ const communityNumber = louvainMapping[node.id] ?? nextIsolated--;
18048
+ if (!groupByCommunity.has(communityNumber)) {
18049
+ groupByCommunity.set(communityNumber, []);
18050
+ }
18051
+ groupByCommunity.get(communityNumber).push(node.id);
18052
+ }
18053
+ let communityIndex = 0;
18054
+ for (const memberIds of groupByCommunity.values()) {
18055
+ const labelSeed = nodes.find((candidate) => candidate.id === memberIds[0])?.label ?? `cluster-${communityIndex + 1}`;
18056
+ const communityId = buildCommunityId(labelSeed, communityIndex);
18049
18057
  communities.push({
18050
18058
  id: communityId,
18051
18059
  label: labelSeed,
@@ -18054,6 +18062,7 @@ function deriveGraphMetrics(nodes, edges) {
18054
18062
  for (const memberId of memberIds) {
18055
18063
  communityMap.set(memberId, communityId);
18056
18064
  }
18065
+ communityIndex++;
18057
18066
  }
18058
18067
  const degreeMap = /* @__PURE__ */ new Map();
18059
18068
  for (const node of nodes) {
@@ -18704,11 +18713,22 @@ function approvalManifestPath(paths, approvalId) {
18704
18713
  function approvalGraphPath(paths, approvalId) {
18705
18714
  return path23.join(paths.approvalsDir, approvalId, "state", "graph.json");
18706
18715
  }
18716
+ function normalizeApprovalBundleType(raw) {
18717
+ if (!raw) return void 0;
18718
+ const legacy = {
18719
+ generated_output: "generated-output",
18720
+ source_review: "source-review",
18721
+ guided_source: "guided-source",
18722
+ guided_session: "guided-session"
18723
+ };
18724
+ return legacy[raw] ?? raw;
18725
+ }
18707
18726
  async function readApprovalManifest(paths, approvalId) {
18708
18727
  const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
18709
18728
  if (!manifest) {
18710
18729
  throw new Error(`Approval bundle not found: ${approvalId}`);
18711
18730
  }
18731
+ manifest.bundleType = normalizeApprovalBundleType(manifest.bundleType);
18712
18732
  return manifest;
18713
18733
  }
18714
18734
  async function writeApprovalManifest(paths, manifest) {
@@ -19551,7 +19571,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages, options = {}) {
19551
19571
  await writeApprovalManifest(paths, {
19552
19572
  approvalId,
19553
19573
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
19554
- bundleType: options.bundleType ?? "generated_output",
19574
+ bundleType: options.bundleType ?? "generated-output",
19555
19575
  title: options.title,
19556
19576
  sourceSessionId: options.sourceSessionId,
19557
19577
  entries: await buildApprovalEntries(
@@ -21243,7 +21263,7 @@ async function bootstrapDemo(rootDir, input) {
21243
21263
  }
21244
21264
 
21245
21265
  // src/mcp.ts
21246
- var SERVER_VERSION = "0.7.22";
21266
+ var SERVER_VERSION = "0.7.24";
21247
21267
  async function createMcpServer(rootDir) {
21248
21268
  const server = new McpServer({
21249
21269
  name: "swarmvault",
@@ -23069,7 +23089,7 @@ async function buildSourceGuideStagedPage(rootDir, scope) {
23069
23089
  async function stageSourceReviewForScope(rootDir, scope) {
23070
23090
  const output = await buildSourceReviewStagedPage(rootDir, scope);
23071
23091
  const approval = await stageGeneratedOutputPages(rootDir, [{ page: output.page, content: output.content, label: "source-review" }], {
23072
- bundleType: "source_review",
23092
+ bundleType: "source-review",
23073
23093
  title: `Source Review: ${scope.title}`
23074
23094
  });
23075
23095
  return {
@@ -23421,7 +23441,7 @@ async function stageSourceGuideForScope(rootDir, scope, options = {}) {
23421
23441
  ...guidedUpdates
23422
23442
  ],
23423
23443
  {
23424
- bundleType: "guided_session",
23444
+ bundleType: "guided-session",
23425
23445
  title: `Guided Session: ${scope.title}`,
23426
23446
  sourceSessionId: session.sessionId
23427
23447
  }
@@ -23875,16 +23895,38 @@ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
23875
23895
  ".r",
23876
23896
  ".R"
23877
23897
  ]);
23898
+ var FILE_CHANGE_RE = /^(?:add|change|unlink):(.+)$/;
23878
23899
  function isCodeOnlyChange(reasons) {
23879
23900
  for (const reason of reasons) {
23880
- const match = reason.match(/^(?:add|change|unlink):(.+)$/);
23881
- if (!match) continue;
23882
- const filePath = match[1];
23883
- const ext = path27.extname(filePath).toLowerCase();
23901
+ const match = reason.match(FILE_CHANGE_RE);
23902
+ if (!match) return false;
23903
+ const ext = path27.extname(match[1]).toLowerCase();
23884
23904
  if (!ext || !CODE_EXTENSIONS.has(ext)) return false;
23885
23905
  }
23886
23906
  return reasons.size > 0;
23887
23907
  }
23908
+ function hasNonCodeChanges(reasons) {
23909
+ for (const reason of reasons) {
23910
+ const match = reason.match(FILE_CHANGE_RE);
23911
+ if (!match) return true;
23912
+ const ext = path27.extname(match[1]).toLowerCase();
23913
+ if (!ext || !CODE_EXTENSIONS.has(ext)) return true;
23914
+ }
23915
+ return false;
23916
+ }
23917
+ function collectNonCodePaths(reasons) {
23918
+ const result = [];
23919
+ for (const reason of reasons) {
23920
+ const match = reason.match(FILE_CHANGE_RE);
23921
+ if (!match) {
23922
+ result.push(reason);
23923
+ continue;
23924
+ }
23925
+ const ext = path27.extname(match[1]).toLowerCase();
23926
+ if (!ext || !CODE_EXTENSIONS.has(ext)) result.push(match[1]);
23927
+ }
23928
+ return result;
23929
+ }
23888
23930
  function hasIgnoredRepoSegment(baseDir, targetPath) {
23889
23931
  const relativePath = path27.relative(baseDir, targetPath);
23890
23932
  if (!relativePath || relativePath.startsWith("..")) {
@@ -23957,7 +23999,7 @@ async function runWatchCycle(rootDir, options = {}) {
23957
23999
  changedPages: []
23958
24000
  };
23959
24001
  try {
23960
- result = await performWatchCycle(rootDir, paths, options);
24002
+ result = await performWatchCycle(rootDir, paths, options, options.codeOnly ?? false);
23961
24003
  return result;
23962
24004
  } catch (caught) {
23963
24005
  success = false;
@@ -24107,10 +24149,18 @@ async function watchVault(rootDir, options = {}) {
24107
24149
  pending = false;
24108
24150
  running = true;
24109
24151
  const startedAt = /* @__PURE__ */ new Date();
24110
- const codeOnlyChange = isCodeOnlyChange(reasons);
24152
+ const detectedCodeOnly = isCodeOnlyChange(reasons);
24153
+ const hasDeferredNonCode = !detectedCodeOnly && hasNonCodeChanges(reasons);
24154
+ const codeOnlyChange = options.codeOnly || detectedCodeOnly || hasDeferredNonCode;
24111
24155
  const runReasons = [...reasons];
24112
24156
  reasons.clear();
24113
- if (codeOnlyChange) {
24157
+ if (hasDeferredNonCode) {
24158
+ const nonCodePaths = collectNonCodePaths(new Set(runReasons));
24159
+ process3.stderr.write(
24160
+ `[swarmvault watch] Non-code changes detected (${nonCodePaths.length} file(s)) \u2014 run \`swarmvault compile\` to include LLM re-analysis.
24161
+ `
24162
+ );
24163
+ } else if (codeOnlyChange) {
24114
24164
  process3.stderr.write("[swarmvault watch] Code-only changes detected \u2014 skipping LLM re-analysis.\n");
24115
24165
  }
24116
24166
  let importedCount = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/engine",
3
- "version": "0.7.22",
3
+ "version": "0.7.24",
4
4
  "description": "Core engine for SwarmVault: ingest, compile, query, lint, and provider abstractions.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -50,6 +50,8 @@
50
50
  "csv-parse": "^6.2.1",
51
51
  "fast-xml-parser": "^5.5.11",
52
52
  "fflate": "^0.8.2",
53
+ "graphology": "^0.26.0",
54
+ "graphology-communities-louvain": "^2.0.2",
53
55
  "gray-matter": "^4.0.3",
54
56
  "ignore": "^7.0.5",
55
57
  "ini": "^6.0.0",
@@ -81,6 +83,7 @@
81
83
  "@types/mime-types": "^3.0.1",
82
84
  "@types/node": "^24.6.0",
83
85
  "@types/turndown": "^5.0.5",
86
+ "graphology-types": "^0.24.8",
84
87
  "tsup": "^8.5.0",
85
88
  "vitest": "^3.2.4"
86
89
  },