@swarmvaultai/engine 0.2.1 → 0.2.2

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
@@ -4481,9 +4481,10 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
4481
4481
  const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType) ?? "typescript";
4482
4482
  const { code, rationales } = language === "javascript" || language === "jsx" || language === "typescript" || language === "tsx" ? analyzeTypeScriptLikeCode(manifest, extractedText) : await analyzeTreeSitterCode(manifest, extractedText, language);
4483
4483
  return {
4484
- analysisVersion: 6,
4484
+ analysisVersion: 7,
4485
4485
  sourceId: manifest.sourceId,
4486
4486
  sourceHash: manifest.contentHash,
4487
+ semanticHash: manifest.semanticHash,
4487
4488
  extractionHash: manifest.extractionHash,
4488
4489
  schemaHash,
4489
4490
  title: manifest.title,
@@ -5208,6 +5209,17 @@ var HARD_REPO_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
5208
5209
  var PROGRESS_FILE_THRESHOLD = 150;
5209
5210
  var PROGRESS_UPDATE_INTERVAL = 100;
5210
5211
  var RST_HEADING_MARKERS = /* @__PURE__ */ new Set(["=", "-", "~", "^", '"', "#", "*", "+"]);
5212
+ var MARKDOWN_SEMANTIC_FRONTMATTER_KEYS = [
5213
+ "title",
5214
+ "summary",
5215
+ "description",
5216
+ "aliases",
5217
+ "tags",
5218
+ "authors",
5219
+ "published_at",
5220
+ "canonical_url",
5221
+ "source_type"
5222
+ ];
5211
5223
  function uniqueStrings(values) {
5212
5224
  return [...new Set(values.filter(Boolean))];
5213
5225
  }
@@ -5345,6 +5357,65 @@ function extractedTextForPlainSource(filePath, sourceKind, content) {
5345
5357
  }
5346
5358
  return content;
5347
5359
  }
5360
+ function normalizeSemanticMarkdownScalar(value) {
5361
+ if (typeof value !== "string") {
5362
+ return void 0;
5363
+ }
5364
+ const normalized = normalizeWhitespace(value.trim());
5365
+ return normalized || void 0;
5366
+ }
5367
+ function normalizeSemanticMarkdownList(value) {
5368
+ if (!Array.isArray(value)) {
5369
+ return void 0;
5370
+ }
5371
+ const items = uniqueStrings(
5372
+ value.flatMap((item) => typeof item === "string" ? [normalizeWhitespace(item.trim())] : []).filter(Boolean)
5373
+ );
5374
+ return items.length ? items : void 0;
5375
+ }
5376
+ function semanticMarkdownTitle(fallback, content, filePath) {
5377
+ const parsed = matter3(content);
5378
+ const frontmatterTitle = normalizeSemanticMarkdownScalar(parsed.data.title);
5379
+ if (frontmatterTitle) {
5380
+ return frontmatterTitle;
5381
+ }
5382
+ return titleFromText(fallback, parsed.content, filePath);
5383
+ }
5384
+ function semanticMarkdownContent(content) {
5385
+ const parsed = matter3(content);
5386
+ const body = parsed.content.replace(/\r\n?/g, "\n").trim();
5387
+ const semanticFrontmatter = Object.fromEntries(
5388
+ MARKDOWN_SEMANTIC_FRONTMATTER_KEYS.flatMap((key) => {
5389
+ const value = key === "aliases" || key === "tags" || key === "authors" ? normalizeSemanticMarkdownList(parsed.data[key]) : normalizeSemanticMarkdownScalar(parsed.data[key]);
5390
+ return value === void 0 ? [] : [[key, value]];
5391
+ })
5392
+ );
5393
+ const semanticLines = Object.entries(semanticFrontmatter).map(
5394
+ ([key, value]) => `${key}: ${Array.isArray(value) ? value.join(", ") : value}`
5395
+ );
5396
+ const extractedText = [...semanticLines, ...semanticLines.length && body ? [""] : [], body].filter(Boolean).join("\n").trim();
5397
+ return {
5398
+ extractedText,
5399
+ semanticHash: sha256(
5400
+ JSON.stringify({
5401
+ body,
5402
+ frontmatter: semanticFrontmatter
5403
+ })
5404
+ )
5405
+ };
5406
+ }
5407
+ function finalizePreparedInput(prepared) {
5408
+ if (prepared.sourceKind !== "markdown") {
5409
+ return prepared;
5410
+ }
5411
+ const semantic = semanticMarkdownContent(prepared.payloadBytes.toString("utf8"));
5412
+ return {
5413
+ ...prepared,
5414
+ extractedText: semantic.extractedText,
5415
+ extractionHash: buildExtractionHash(semantic.extractedText, prepared.extractionArtifact),
5416
+ semanticHash: semantic.semanticHash
5417
+ };
5418
+ }
5348
5419
  function shouldEmitProgress(totalItems) {
5349
5420
  return totalItems >= PROGRESS_FILE_THRESHOLD && Boolean(process.stderr?.isTTY);
5350
5421
  }
@@ -5511,7 +5582,7 @@ function markdownFrontmatter(value) {
5511
5582
  return matter3.stringify("", normalized).trimEnd().split("\n").concat([""]);
5512
5583
  }
5513
5584
  function prepareCapturedMarkdownInput(input) {
5514
- return {
5585
+ return finalizePreparedInput({
5515
5586
  title: input.title,
5516
5587
  originType: "url",
5517
5588
  sourceKind: "markdown",
@@ -5523,7 +5594,7 @@ function prepareCapturedMarkdownInput(input) {
5523
5594
  extractedText: input.markdown,
5524
5595
  attachments: input.attachments,
5525
5596
  logDetails: input.logDetails
5526
- };
5597
+ });
5527
5598
  }
5528
5599
  function isPrivateIp(ip) {
5529
5600
  if (ip === "::1" || ip.startsWith("fc") || ip.startsWith("fd")) return true;
@@ -5886,7 +5957,10 @@ async function readManifestByHash(manifestsDir, contentHash) {
5886
5957
  }
5887
5958
  const manifest = await readJsonFile(path12.join(manifestsDir, entry.name));
5888
5959
  if (manifest?.contentHash === contentHash) {
5889
- return manifest;
5960
+ return {
5961
+ ...manifest,
5962
+ semanticHash: manifest.semanticHash ?? manifest.contentHash
5963
+ };
5890
5964
  }
5891
5965
  }
5892
5966
  return null;
@@ -5899,7 +5973,10 @@ async function readManifestByOrigin(manifestsDir, prepared) {
5899
5973
  }
5900
5974
  const manifest = await readJsonFile(path12.join(manifestsDir, entry.name));
5901
5975
  if (manifest && manifestMatchesOrigin(manifest, prepared)) {
5902
- return manifest;
5976
+ return {
5977
+ ...manifest,
5978
+ semanticHash: manifest.semanticHash ?? manifest.contentHash
5979
+ };
5903
5980
  }
5904
5981
  }
5905
5982
  return null;
@@ -6148,10 +6225,11 @@ async function persistPreparedInput(rootDir, prepared, paths) {
6148
6225
  await ensureDir(paths.extractsDir);
6149
6226
  const attachments = prepared.attachments ?? [];
6150
6227
  const contentHash = prepared.contentHash ?? buildCompositeHash(prepared.payloadBytes, attachments);
6228
+ const semanticHash = prepared.semanticHash ?? contentHash;
6151
6229
  const extractionHash = prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact);
6152
6230
  const existingByOrigin = await readManifestByOrigin(paths.manifestsDir, prepared);
6153
6231
  const existingByHash = existingByOrigin ? null : await readManifestByHash(paths.manifestsDir, contentHash);
6154
- if (existingByOrigin && existingByOrigin.contentHash === contentHash && existingByOrigin.extractionHash === extractionHash && existingByOrigin.title === prepared.title && existingByOrigin.sourceKind === prepared.sourceKind && existingByOrigin.sourceType === prepared.sourceType && existingByOrigin.sourceClass === prepared.sourceClass && existingByOrigin.language === prepared.language && existingByOrigin.mimeType === prepared.mimeType && existingByOrigin.repoRelativePath === prepared.repoRelativePath) {
6232
+ if (existingByOrigin && existingByOrigin.contentHash === contentHash && existingByOrigin.semanticHash === semanticHash && existingByOrigin.extractionHash === extractionHash && existingByOrigin.title === prepared.title && existingByOrigin.sourceKind === prepared.sourceKind && existingByOrigin.sourceType === prepared.sourceType && existingByOrigin.sourceClass === prepared.sourceClass && existingByOrigin.language === prepared.language && existingByOrigin.mimeType === prepared.mimeType && existingByOrigin.repoRelativePath === prepared.repoRelativePath) {
6155
6233
  return { manifest: existingByOrigin, isNew: false, wasUpdated: false };
6156
6234
  }
6157
6235
  if (existingByHash) {
@@ -6209,6 +6287,7 @@ async function persistPreparedInput(rootDir, prepared, paths) {
6209
6287
  extractionHash,
6210
6288
  mimeType: prepared.mimeType,
6211
6289
  contentHash,
6290
+ semanticHash,
6212
6291
  createdAt: previous?.createdAt ?? now,
6213
6292
  updatedAt: now,
6214
6293
  attachments: manifestAttachments.length ? manifestAttachments : void 0
@@ -6523,14 +6602,15 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
6523
6602
  let extractedText;
6524
6603
  let extractionArtifact;
6525
6604
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
6526
- extractedText = extractedTextForPlainSource(absoluteInput, sourceKind, payloadBytes.toString("utf8"));
6527
- title = titleFromText(path12.basename(absoluteInput, path12.extname(absoluteInput)), extractedText, absoluteInput);
6605
+ const rawText = payloadBytes.toString("utf8");
6606
+ extractedText = sourceKind === "markdown" ? semanticMarkdownContent(rawText).extractedText : extractedTextForPlainSource(absoluteInput, sourceKind, rawText);
6607
+ title = sourceKind === "markdown" ? semanticMarkdownTitle(path12.basename(absoluteInput, path12.extname(absoluteInput)), rawText, absoluteInput) : titleFromText(path12.basename(absoluteInput, path12.extname(absoluteInput)), extractedText, absoluteInput);
6528
6608
  extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
6529
6609
  } else if (sourceKind === "html") {
6530
6610
  const html = payloadBytes.toString("utf8");
6531
6611
  const converted = await convertHtmlToMarkdown(html, pathToFileURL(absoluteInput).toString());
6532
6612
  title = converted.title;
6533
- extractedText = converted.markdown;
6613
+ extractedText = semanticMarkdownContent(converted.markdown).extractedText;
6534
6614
  extractionArtifact = createHtmlReadabilityExtractionArtifact(sourceKind, mimeType);
6535
6615
  } else if (sourceKind === "pdf") {
6536
6616
  title = path12.basename(absoluteInput, path12.extname(absoluteInput));
@@ -6556,7 +6636,7 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
6556
6636
  } else {
6557
6637
  title = path12.basename(absoluteInput, path12.extname(absoluteInput));
6558
6638
  }
6559
- return {
6639
+ return finalizePreparedInput({
6560
6640
  title,
6561
6641
  originType: "file",
6562
6642
  sourceKind,
@@ -6570,7 +6650,7 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
6570
6650
  extractedText,
6571
6651
  extractionArtifact,
6572
6652
  extractionHash: buildExtractionHash(extractedText, extractionArtifact)
6573
- };
6653
+ });
6574
6654
  }
6575
6655
  async function prepareUrlInput(rootDir, input, options) {
6576
6656
  await validateUrlSafety(input);
@@ -6634,8 +6714,9 @@ async function prepareUrlInput(rootDir, input, options) {
6634
6714
  const extension = path12.extname(inputUrl.pathname);
6635
6715
  storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
6636
6716
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
6637
- extractedText = extractedTextForPlainSource(inputUrl.pathname, sourceKind, payloadBytes.toString("utf8"));
6638
- title = titleFromText(title || inputUrl.hostname, extractedText, inputUrl.pathname);
6717
+ const rawText = payloadBytes.toString("utf8");
6718
+ extractedText = sourceKind === "markdown" ? semanticMarkdownContent(rawText).extractedText : extractedTextForPlainSource(inputUrl.pathname, sourceKind, rawText);
6719
+ title = sourceKind === "markdown" ? semanticMarkdownTitle(title || inputUrl.hostname, rawText, inputUrl.pathname) : titleFromText(title || inputUrl.hostname, extractedText, inputUrl.pathname);
6639
6720
  extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
6640
6721
  if (sourceKind === "markdown" && options.includeAssets) {
6641
6722
  const { attachments: remoteAttachments, skippedCount } = await collectRemoteImageAttachments(
@@ -6677,7 +6758,7 @@ async function prepareUrlInput(rootDir, input, options) {
6677
6758
  extractionArtifact = extracted.artifact;
6678
6759
  }
6679
6760
  }
6680
- return {
6761
+ return finalizePreparedInput({
6681
6762
  title,
6682
6763
  originType: "url",
6683
6764
  sourceKind,
@@ -6692,7 +6773,7 @@ async function prepareUrlInput(rootDir, input, options) {
6692
6773
  attachments,
6693
6774
  contentHash,
6694
6775
  logDetails
6695
- };
6776
+ });
6696
6777
  }
6697
6778
  async function collectInboxAttachmentRefs(inputDir, files) {
6698
6779
  const refsBySource = /* @__PURE__ */ new Map();
@@ -6766,7 +6847,7 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
6766
6847
  );
6767
6848
  const rewrittenText = rewriteMarkdownReferences(originalText, replacements);
6768
6849
  const extractionArtifact = createPlainTextExtractionArtifact("markdown", "text/markdown");
6769
- return {
6850
+ return finalizePreparedInput({
6770
6851
  title,
6771
6852
  originType: "file",
6772
6853
  sourceKind: "markdown",
@@ -6779,7 +6860,7 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
6779
6860
  extractionHash: buildExtractionHash(rewrittenText, extractionArtifact),
6780
6861
  attachments,
6781
6862
  contentHash
6782
- };
6863
+ });
6783
6864
  }
6784
6865
  async function prepareInboxHtmlInput(absolutePath, attachmentRefs) {
6785
6866
  const originalBytes = await fs11.readFile(absolutePath);
@@ -7021,7 +7102,10 @@ async function listManifests(rootDir) {
7021
7102
  const manifests = await Promise.all(
7022
7103
  entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path12.join(paths.manifestsDir, entry)))
7023
7104
  );
7024
- return manifests.filter((manifest) => Boolean(manifest));
7105
+ return manifests.filter((manifest) => Boolean(manifest)).map((manifest) => ({
7106
+ ...manifest,
7107
+ semanticHash: manifest.semanticHash ?? manifest.contentHash
7108
+ }));
7025
7109
  }
7026
7110
  async function removeManifestBySourceId(rootDir, sourceId) {
7027
7111
  const { paths } = await initWorkspace(rootDir);
@@ -7029,8 +7113,12 @@ async function removeManifestBySourceId(rootDir, sourceId) {
7029
7113
  if (!manifest) {
7030
7114
  return null;
7031
7115
  }
7032
- await removeManifestArtifacts(rootDir, manifest, paths);
7033
- return manifest;
7116
+ const normalizedManifest = {
7117
+ ...manifest,
7118
+ semanticHash: manifest.semanticHash ?? manifest.contentHash
7119
+ };
7120
+ await removeManifestArtifacts(rootDir, normalizedManifest, paths);
7121
+ return normalizedManifest;
7034
7122
  }
7035
7123
  async function readExtractedText(rootDir, manifest) {
7036
7124
  if (!manifest.extractedTextPath) {
@@ -7176,7 +7264,7 @@ import { z as z7 } from "zod";
7176
7264
  // src/analysis.ts
7177
7265
  import path14 from "path";
7178
7266
  import { z as z2 } from "zod";
7179
- var ANALYSIS_FORMAT_VERSION = 6;
7267
+ var ANALYSIS_FORMAT_VERSION = 7;
7180
7268
  var sourceAnalysisSchema = z2.object({
7181
7269
  title: z2.string().min(1),
7182
7270
  summary: z2.string().min(1),
@@ -7281,6 +7369,7 @@ function heuristicAnalysis(manifest, text, schemaHash) {
7281
7369
  analysisVersion: ANALYSIS_FORMAT_VERSION,
7282
7370
  sourceId: manifest.sourceId,
7283
7371
  sourceHash: manifest.contentHash,
7372
+ semanticHash: manifest.semanticHash,
7284
7373
  extractionHash: manifest.extractionHash,
7285
7374
  schemaHash,
7286
7375
  title: deriveTitle(manifest, text),
@@ -7331,6 +7420,7 @@ ${truncate(text, 18e3)}`
7331
7420
  analysisVersion: ANALYSIS_FORMAT_VERSION,
7332
7421
  sourceId: manifest.sourceId,
7333
7422
  sourceHash: manifest.contentHash,
7423
+ semanticHash: manifest.semanticHash,
7334
7424
  extractionHash: manifest.extractionHash,
7335
7425
  schemaHash: schema.hash,
7336
7426
  title: parsed.title,
@@ -7367,6 +7457,7 @@ function analysisFromVisionExtraction(manifest, extraction, schemaHash) {
7367
7457
  analysisVersion: ANALYSIS_FORMAT_VERSION,
7368
7458
  sourceId: manifest.sourceId,
7369
7459
  sourceHash: manifest.contentHash,
7460
+ semanticHash: manifest.semanticHash,
7370
7461
  extractionHash: manifest.extractionHash,
7371
7462
  schemaHash,
7372
7463
  title: extraction.vision.title?.trim() || manifest.title,
@@ -7405,7 +7496,7 @@ function extractionWarningSummary(manifest, extraction) {
7405
7496
  async function analyzeSource(manifest, extractedText, provider, paths, schema) {
7406
7497
  const cachePath = path14.join(paths.analysesDir, `${manifest.sourceId}.json`);
7407
7498
  const cached = await readJsonFile(cachePath);
7408
- if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
7499
+ if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && (cached.semanticHash ?? cached.sourceHash) === manifest.semanticHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
7409
7500
  return cached;
7410
7501
  }
7411
7502
  const extraction = await readExtractionArtifact(paths.rootDir, manifest);
@@ -7422,6 +7513,7 @@ async function analyzeSource(manifest, extractedText, provider, paths, schema) {
7422
7513
  analysisVersion: ANALYSIS_FORMAT_VERSION,
7423
7514
  sourceId: manifest.sourceId,
7424
7515
  sourceHash: manifest.contentHash,
7516
+ semanticHash: manifest.semanticHash,
7425
7517
  extractionHash: manifest.extractionHash,
7426
7518
  schemaHash: schema.hash,
7427
7519
  title: manifest.title,
@@ -7448,6 +7540,7 @@ async function analyzeSource(manifest, extractedText, provider, paths, schema) {
7448
7540
  analysisVersion: ANALYSIS_FORMAT_VERSION,
7449
7541
  sourceId: manifest.sourceId,
7450
7542
  sourceHash: manifest.contentHash,
7543
+ semanticHash: manifest.semanticHash,
7451
7544
  extractionHash: manifest.extractionHash,
7452
7545
  schemaHash: schema.hash,
7453
7546
  title: manifest.title,
@@ -8231,7 +8324,9 @@ async function resolveEmbeddingProvider(rootDir) {
8231
8324
  }
8232
8325
  const provider2 = await createProvider(explicitProviderId, providerConfig, rootDir);
8233
8326
  if (!provider2.capabilities.has("embeddings") || typeof provider2.embedTexts !== "function") {
8234
- throw new Error(`Provider ${provider2.id} does not support required capability "embeddings".`);
8327
+ throw new Error(
8328
+ `Provider ${provider2.id} does not support required capability "embeddings". Configure tasks.embeddingProvider to use an embedding-capable backend such as ollama or another openai-compatible embedding service.`
8329
+ );
8235
8330
  }
8236
8331
  return provider2;
8237
8332
  }
@@ -9127,6 +9222,18 @@ function uniqueStrings2(values) {
9127
9222
  function safeFrontmatter(value) {
9128
9223
  return JSON.parse(JSON.stringify(value));
9129
9224
  }
9225
+ function sourceHashesForManifest(manifest) {
9226
+ return {
9227
+ sourceHashes: { [manifest.sourceId]: manifest.contentHash },
9228
+ sourceSemanticHashes: { [manifest.sourceId]: manifest.semanticHash }
9229
+ };
9230
+ }
9231
+ function sourceHashFrontmatter(sourceHashes, sourceSemanticHashes) {
9232
+ return {
9233
+ source_hashes: sourceHashes,
9234
+ source_semantic_hashes: sourceSemanticHashes
9235
+ };
9236
+ }
9130
9237
  function decoratedTags(baseTags, decorations) {
9131
9238
  return uniqueStrings2([
9132
9239
  ...baseTags,
@@ -9190,6 +9297,7 @@ function relatedOutputsSection(relatedOutputs) {
9190
9297
  function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutputs = [], modulePage, decorations) {
9191
9298
  const relativePath = pagePathFor("source", manifest.sourceId);
9192
9299
  const pageId = `source:${manifest.sourceId}`;
9300
+ const { sourceHashes, sourceSemanticHashes } = sourceHashesForManifest(manifest);
9193
9301
  const moduleNodeIds = analysis.code ? [analysis.code.moduleId, ...analysis.code.symbols.map((symbol) => symbol.id)] : [];
9194
9302
  const nodeIds = [
9195
9303
  `source:${manifest.sourceId}`,
@@ -9222,9 +9330,7 @@ function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutput
9222
9330
  managed_by: metadata.managedBy,
9223
9331
  backlinks,
9224
9332
  schema_hash: schemaHash,
9225
- source_hashes: {
9226
- [manifest.sourceId]: manifest.contentHash
9227
- }
9333
+ ...sourceHashFrontmatter(sourceHashes, sourceSemanticHashes)
9228
9334
  };
9229
9335
  const body = [
9230
9336
  `# ${analysis.title}`,
@@ -9287,7 +9393,8 @@ function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutput
9287
9393
  confidence: metadata.confidence,
9288
9394
  backlinks,
9289
9395
  schemaHash,
9290
- sourceHashes: { [manifest.sourceId]: manifest.contentHash },
9396
+ sourceHashes,
9397
+ sourceSemanticHashes,
9291
9398
  relatedPageIds: [...modulePage ? [modulePage.id] : [], ...relatedOutputs.map((page) => page.id)],
9292
9399
  relatedNodeIds: moduleNodeIds,
9293
9400
  relatedSourceIds: [],
@@ -9312,6 +9419,7 @@ function buildModulePage(input) {
9312
9419
  const localModuleBacklinks = input.localModules.map((moduleRef) => moduleRef.page.id);
9313
9420
  const relatedOutputs = input.relatedOutputs ?? [];
9314
9421
  const backlinks = uniqueStrings2([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]);
9422
+ const { sourceHashes, sourceSemanticHashes } = sourceHashesForManifest(manifest);
9315
9423
  const importsSection = code.imports.length ? code.imports.map((item) => {
9316
9424
  const localModule = item.resolvedSourceId ? input.localModules.find((moduleRef) => moduleRef.sourceId === item.resolvedSourceId && moduleRef.reExport === item.reExport) : void 0;
9317
9425
  const importedBits = [
@@ -9355,9 +9463,7 @@ function buildModulePage(input) {
9355
9463
  managed_by: metadata.managedBy,
9356
9464
  backlinks,
9357
9465
  schema_hash: schemaHash,
9358
- source_hashes: {
9359
- [manifest.sourceId]: manifest.contentHash
9360
- },
9466
+ ...sourceHashFrontmatter(sourceHashes, sourceSemanticHashes),
9361
9467
  related_page_ids: uniqueStrings2([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]),
9362
9468
  related_node_ids: [],
9363
9469
  related_source_ids: uniqueStrings2([
@@ -9433,7 +9539,8 @@ function buildModulePage(input) {
9433
9539
  confidence: metadata.confidence,
9434
9540
  backlinks,
9435
9541
  schemaHash,
9436
- sourceHashes: { [manifest.sourceId]: manifest.contentHash },
9542
+ sourceHashes,
9543
+ sourceSemanticHashes,
9437
9544
  relatedPageIds: uniqueStrings2([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]),
9438
9545
  relatedNodeIds: [],
9439
9546
  relatedSourceIds: uniqueStrings2([
@@ -9449,7 +9556,7 @@ function buildModulePage(input) {
9449
9556
  content: matter5.stringify(body, frontmatter)
9450
9557
  };
9451
9558
  }
9452
- function buildAggregatePage(kind, name, descriptions, sourceAnalyses, sourceHashes, schemaHash, metadata, relativePath, relatedOutputs = [], decorations) {
9559
+ function buildAggregatePage(kind, name, descriptions, sourceAnalyses, sourceHashes, sourceSemanticHashes, schemaHash, metadata, relativePath, relatedOutputs = [], decorations) {
9453
9560
  const slug = slugify(name);
9454
9561
  const pageId = `${kind}:${slug}`;
9455
9562
  const sourceIds = sourceAnalyses.map((item) => item.sourceId);
@@ -9473,7 +9580,7 @@ function buildAggregatePage(kind, name, descriptions, sourceAnalyses, sourceHash
9473
9580
  managed_by: metadata.managedBy,
9474
9581
  backlinks: otherPages,
9475
9582
  schema_hash: schemaHash,
9476
- source_hashes: sourceHashes
9583
+ ...sourceHashFrontmatter(sourceHashes, sourceSemanticHashes)
9477
9584
  };
9478
9585
  const body = [
9479
9586
  `# ${name}`,
@@ -9511,6 +9618,7 @@ function buildAggregatePage(kind, name, descriptions, sourceAnalyses, sourceHash
9511
9618
  backlinks: otherPages,
9512
9619
  schemaHash,
9513
9620
  sourceHashes,
9621
+ sourceSemanticHashes,
9514
9622
  relatedPageIds: relatedOutputs.map((page) => page.id),
9515
9623
  relatedNodeIds: [],
9516
9624
  relatedSourceIds: [],
@@ -9551,6 +9659,7 @@ function buildIndexPage(pages, schemaHash, metadata, projectPages = []) {
9551
9659
  "backlinks: []",
9552
9660
  `schema_hash: ${schemaHash}`,
9553
9661
  "source_hashes: {}",
9662
+ "source_semantic_hashes: {}",
9554
9663
  "---",
9555
9664
  "",
9556
9665
  "# SwarmVault Index",
@@ -9614,7 +9723,8 @@ function buildSectionIndex(kind, pages, schemaHash, metadata, projectIds = []) {
9614
9723
  managed_by: metadata.managedBy,
9615
9724
  backlinks: [],
9616
9725
  schema_hash: schemaHash,
9617
- source_hashes: {}
9726
+ source_hashes: {},
9727
+ source_semantic_hashes: {}
9618
9728
  }
9619
9729
  );
9620
9730
  }
@@ -9910,6 +10020,7 @@ function buildGraphReportPage(input) {
9910
10020
  backlinks: [],
9911
10021
  schema_hash: input.schemaHash,
9912
10022
  source_hashes: {},
10023
+ source_semantic_hashes: {},
9913
10024
  related_page_ids: relatedPageIds,
9914
10025
  related_node_ids: relatedNodeIds,
9915
10026
  related_source_ids: relatedSourceIds
@@ -10025,6 +10136,7 @@ function buildGraphReportPage(input) {
10025
10136
  backlinks: [],
10026
10137
  schemaHash: input.schemaHash,
10027
10138
  sourceHashes: {},
10139
+ sourceSemanticHashes: {},
10028
10140
  relatedPageIds,
10029
10141
  relatedNodeIds,
10030
10142
  relatedSourceIds,
@@ -10068,6 +10180,7 @@ function buildCommunitySummaryPage(input) {
10068
10180
  backlinks: ["graph:report"],
10069
10181
  schema_hash: input.schemaHash,
10070
10182
  source_hashes: {},
10183
+ source_semantic_hashes: {},
10071
10184
  related_page_ids: uniqueStrings2(["graph:report", ...communityPageIds]),
10072
10185
  related_node_ids: input.community.nodeIds,
10073
10186
  related_source_ids: relatedSourceIds
@@ -10107,6 +10220,7 @@ function buildCommunitySummaryPage(input) {
10107
10220
  backlinks: ["graph:report"],
10108
10221
  schemaHash: input.schemaHash,
10109
10222
  sourceHashes: {},
10223
+ sourceSemanticHashes: {},
10110
10224
  relatedPageIds: uniqueStrings2(["graph:report", ...communityPageIds]),
10111
10225
  relatedNodeIds: input.community.nodeIds,
10112
10226
  relatedSourceIds,
@@ -10143,7 +10257,8 @@ function buildProjectsIndex(projectPages, schemaHash, metadata) {
10143
10257
  managed_by: metadata.managedBy,
10144
10258
  backlinks: [],
10145
10259
  schema_hash: schemaHash,
10146
- source_hashes: {}
10260
+ source_hashes: {},
10261
+ source_semantic_hashes: {}
10147
10262
  }
10148
10263
  );
10149
10264
  }
@@ -10195,7 +10310,8 @@ function buildProjectIndex(input) {
10195
10310
  managed_by: input.metadata.managedBy,
10196
10311
  backlinks: [],
10197
10312
  schema_hash: input.schemaHash,
10198
- source_hashes: {}
10313
+ source_hashes: {},
10314
+ source_semantic_hashes: {}
10199
10315
  }
10200
10316
  );
10201
10317
  }
@@ -10226,6 +10342,7 @@ function buildOutputPage(input) {
10226
10342
  backlinks,
10227
10343
  schema_hash: input.schemaHash,
10228
10344
  source_hashes: {},
10345
+ source_semantic_hashes: {},
10229
10346
  related_page_ids: relatedPageIds,
10230
10347
  related_node_ids: relatedNodeIds,
10231
10348
  related_source_ids: relatedSourceIds,
@@ -10250,6 +10367,7 @@ function buildOutputPage(input) {
10250
10367
  backlinks,
10251
10368
  schemaHash: input.schemaHash,
10252
10369
  sourceHashes: {},
10370
+ sourceSemanticHashes: {},
10253
10371
  relatedPageIds,
10254
10372
  relatedNodeIds,
10255
10373
  relatedSourceIds,
@@ -10352,6 +10470,7 @@ function buildExploreHubPage(input) {
10352
10470
  backlinks,
10353
10471
  schema_hash: input.schemaHash,
10354
10472
  source_hashes: {},
10473
+ source_semantic_hashes: {},
10355
10474
  related_page_ids: relatedPageIds,
10356
10475
  related_node_ids: relatedNodeIds,
10357
10476
  related_source_ids: relatedSourceIds,
@@ -10376,6 +10495,7 @@ function buildExploreHubPage(input) {
10376
10495
  backlinks,
10377
10496
  schemaHash: input.schemaHash,
10378
10497
  sourceHashes: {},
10498
+ sourceSemanticHashes: {},
10379
10499
  relatedPageIds,
10380
10500
  relatedNodeIds,
10381
10501
  relatedSourceIds,
@@ -10674,6 +10794,9 @@ function normalizeSourceHashes(value) {
10674
10794
  Object.entries(value).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string")
10675
10795
  );
10676
10796
  }
10797
+ function normalizeSourceSemanticHashes(value) {
10798
+ return normalizeSourceHashes(value);
10799
+ }
10677
10800
  function normalizePageStatus(value, fallback = "active") {
10678
10801
  return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : fallback;
10679
10802
  }
@@ -10802,6 +10925,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
10802
10925
  backlinks,
10803
10926
  schemaHash: typeof parsed.data.schema_hash === "string" ? parsed.data.schema_hash : "",
10804
10927
  sourceHashes: normalizeSourceHashes(parsed.data.source_hashes),
10928
+ sourceSemanticHashes: normalizeSourceSemanticHashes(parsed.data.source_semantic_hashes),
10805
10929
  relatedPageIds,
10806
10930
  relatedNodeIds,
10807
10931
  relatedSourceIds,
@@ -10855,6 +10979,7 @@ async function loadInsightPages(wikiDir) {
10855
10979
  backlinks,
10856
10980
  schemaHash: typeof parsed.data.schema_hash === "string" ? parsed.data.schema_hash : "",
10857
10981
  sourceHashes: normalizeSourceHashes(parsed.data.source_hashes),
10982
+ sourceSemanticHashes: normalizeSourceSemanticHashes(parsed.data.source_semantic_hashes),
10858
10983
  relatedPageIds,
10859
10984
  relatedNodeIds,
10860
10985
  relatedSourceIds,
@@ -10955,6 +11080,7 @@ async function loadSavedOutputPages(wikiDir) {
10955
11080
  backlinks,
10956
11081
  schemaHash: typeof parsed.data.schema_hash === "string" ? parsed.data.schema_hash : "",
10957
11082
  sourceHashes: normalizeSourceHashes(parsed.data.source_hashes),
11083
+ sourceSemanticHashes: normalizeSourceSemanticHashes(parsed.data.source_semantic_hashes),
10958
11084
  relatedPageIds,
10959
11085
  relatedNodeIds,
10960
11086
  relatedSourceIds,
@@ -12431,11 +12557,13 @@ function aggregateItems(analyses, kind) {
12431
12557
  name: item.name,
12432
12558
  descriptions: [],
12433
12559
  sourceAnalyses: [],
12434
- sourceHashes: {}
12560
+ sourceHashes: {},
12561
+ sourceSemanticHashes: {}
12435
12562
  };
12436
12563
  existing.descriptions.push(item.description);
12437
12564
  existing.sourceAnalyses.push(analysis);
12438
12565
  existing.sourceHashes[analysis.sourceId] = analysis.sourceHash;
12566
+ existing.sourceSemanticHashes[analysis.sourceId] = analysis.semanticHash;
12439
12567
  grouped.set(key, existing);
12440
12568
  }
12441
12569
  }
@@ -12457,6 +12585,7 @@ function emptyGraphPage(input) {
12457
12585
  backlinks: [],
12458
12586
  schemaHash: input.schemaHash,
12459
12587
  sourceHashes: input.sourceHashes,
12588
+ sourceSemanticHashes: input.sourceSemanticHashes ?? {},
12460
12589
  relatedPageIds: [],
12461
12590
  relatedNodeIds: [],
12462
12591
  relatedSourceIds: [],
@@ -12621,6 +12750,7 @@ async function syncVaultArtifacts(rootDir, input) {
12621
12750
  nodeIds: [analysis.code.moduleId, ...analysis.code.symbols.map((symbol) => symbol.id)],
12622
12751
  schemaHash: sourceSchemaHash,
12623
12752
  sourceHashes: { [manifest.sourceId]: manifest.contentHash },
12753
+ sourceSemanticHashes: { [manifest.sourceId]: manifest.semanticHash },
12624
12754
  confidence: 1
12625
12755
  }) : null;
12626
12756
  const preview = emptyGraphPage({
@@ -12639,6 +12769,7 @@ async function syncVaultArtifacts(rootDir, input) {
12639
12769
  ],
12640
12770
  schemaHash: sourceSchemaHash,
12641
12771
  sourceHashes: { [manifest.sourceId]: manifest.contentHash },
12772
+ sourceSemanticHashes: { [manifest.sourceId]: manifest.semanticHash },
12642
12773
  confidence: 1
12643
12774
  });
12644
12775
  const sourceRecord = await buildManagedGraphPage(
@@ -12755,6 +12886,7 @@ async function syncVaultArtifacts(rootDir, input) {
12755
12886
  aggregate.descriptions,
12756
12887
  aggregate.sourceAnalyses,
12757
12888
  aggregate.sourceHashes,
12889
+ aggregate.sourceSemanticHashes,
12758
12890
  schemaHash,
12759
12891
  metadata,
12760
12892
  relativePath,
@@ -13002,6 +13134,7 @@ async function syncVaultArtifacts(rootDir, input) {
13002
13134
  projectConfigHash: projectConfigHash(config),
13003
13135
  analyses: Object.fromEntries(input.analyses.map((analysis) => [analysis.sourceId, analysisSignature(analysis)])),
13004
13136
  sourceHashes: Object.fromEntries(input.manifests.map((manifest) => [manifest.sourceId, manifest.contentHash])),
13137
+ sourceSemanticHashes: Object.fromEntries(input.manifests.map((manifest) => [manifest.sourceId, manifest.semanticHash])),
13005
13138
  sourceProjects: input.sourceProjects,
13006
13139
  outputHashes: input.outputHashes,
13007
13140
  insightHashes: input.insightHashes,
@@ -13467,6 +13600,7 @@ function emptyCompileState() {
13467
13600
  projectConfigHash: "",
13468
13601
  analyses: {},
13469
13602
  sourceHashes: {},
13603
+ sourceSemanticHashes: {},
13470
13604
  sourceProjects: {},
13471
13605
  outputHashes: {},
13472
13606
  insightHashes: {},
@@ -13896,7 +14030,8 @@ async function initVault(rootDir, options = {}) {
13896
14030
  managed_by: "human",
13897
14031
  backlinks: [],
13898
14032
  schema_hash: "",
13899
- source_hashes: {}
14033
+ source_hashes: {},
14034
+ source_semantic_hashes: {}
13900
14035
  }
13901
14036
  )
13902
14037
  );
@@ -13919,7 +14054,8 @@ async function initVault(rootDir, options = {}) {
13919
14054
  managed_by: "system",
13920
14055
  backlinks: [],
13921
14056
  schema_hash: "",
13922
- source_hashes: {}
14057
+ source_hashes: {},
14058
+ source_semantic_hashes: {}
13923
14059
  })
13924
14060
  );
13925
14061
  await writeFileIfChanged(
@@ -13941,7 +14077,8 @@ async function initVault(rootDir, options = {}) {
13941
14077
  managed_by: "system",
13942
14078
  backlinks: [],
13943
14079
  schema_hash: "",
13944
- source_hashes: {}
14080
+ source_hashes: {},
14081
+ source_semantic_hashes: {}
13945
14082
  })
13946
14083
  );
13947
14084
  if (options.obsidian) {
@@ -13982,7 +14119,7 @@ async function compileVault(rootDir, options = {}) {
13982
14119
  );
13983
14120
  const nextProjectConfigHash = projectConfigHash(config);
13984
14121
  const projectConfigChanged = !previousState || previousState.projectConfigHash !== nextProjectConfigHash;
13985
- const previousSourceHashes = previousState?.sourceHashes ?? {};
14122
+ const previousSourceHashes = previousState?.sourceSemanticHashes ?? previousState?.sourceHashes ?? {};
13986
14123
  const previousAnalyses = previousState?.analyses ?? {};
13987
14124
  const previousSourceProjects = previousState?.sourceProjects ?? {};
13988
14125
  const previousOutputHashes = previousState?.outputHashes ?? {};
@@ -13997,7 +14134,7 @@ async function compileVault(rootDir, options = {}) {
13997
14134
  const dirty = [];
13998
14135
  const clean = [];
13999
14136
  for (const manifest of manifests) {
14000
- const hashChanged = previousSourceHashes[manifest.sourceId] !== manifest.contentHash;
14137
+ const hashChanged = previousSourceHashes[manifest.sourceId] !== manifest.semanticHash;
14001
14138
  const noAnalysis = !previousAnalyses[manifest.sourceId];
14002
14139
  const projectId = sourceProjects[manifest.sourceId] ?? null;
14003
14140
  const projectChanged = (previousSourceProjects[manifest.sourceId] ?? null) !== projectId;
@@ -14707,9 +14844,11 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
14707
14844
  relatedPageIds: [page.id]
14708
14845
  });
14709
14846
  }
14710
- for (const [sourceId, knownHash] of Object.entries(page.sourceHashes)) {
14847
+ const freshnessHashes = Object.keys(page.sourceSemanticHashes).length ? page.sourceSemanticHashes : page.sourceHashes;
14848
+ for (const [sourceId, knownHash] of Object.entries(freshnessHashes)) {
14711
14849
  const manifest = manifestMap.get(sourceId);
14712
- if (manifest && manifest.contentHash !== knownHash) {
14850
+ const manifestHash = manifest?.semanticHash ?? manifest?.contentHash;
14851
+ if (manifestHash && manifestHash !== knownHash) {
14713
14852
  findings.push({
14714
14853
  severity: "warning",
14715
14854
  code: "stale_page",
@@ -14848,7 +14987,7 @@ async function bootstrapDemo(rootDir, input) {
14848
14987
  }
14849
14988
 
14850
14989
  // src/mcp.ts
14851
- var SERVER_VERSION = "0.2.1";
14990
+ var SERVER_VERSION = "0.2.2";
14852
14991
  async function createMcpServer(rootDir) {
14853
14992
  const server = new McpServer({
14854
14993
  name: "swarmvault",
@@ -16221,6 +16360,124 @@ import { promisify } from "util";
16221
16360
  import matter10 from "gray-matter";
16222
16361
  import mime2 from "mime-types";
16223
16362
 
16363
+ // src/graph-presentation.ts
16364
+ var OVERVIEW_THRESHOLD = 5e3;
16365
+ var OVERVIEW_NODE_BUDGET = 1500;
16366
+ function nodePriority(node, pinnedNodeIds) {
16367
+ return [pinnedNodeIds.has(node.id) ? 0 : 1, -(node.degree ?? 0), -(node.bridgeScore ?? 0), node.label, node.id];
16368
+ }
16369
+ function compareTuples(left, right) {
16370
+ const length = Math.max(left.length, right.length);
16371
+ for (let index = 0; index < length; index += 1) {
16372
+ const leftValue = left[index];
16373
+ const rightValue = right[index];
16374
+ if (leftValue === rightValue) {
16375
+ continue;
16376
+ }
16377
+ if (typeof leftValue === "number" && typeof rightValue === "number") {
16378
+ return leftValue - rightValue;
16379
+ }
16380
+ return String(leftValue ?? "").localeCompare(String(rightValue ?? ""));
16381
+ }
16382
+ return 0;
16383
+ }
16384
+ function survivingHyperedges(hyperedges, sampledNodeIds) {
16385
+ return hyperedges.filter((hyperedge) => hyperedge.nodeIds.filter((nodeId) => sampledNodeIds.has(nodeId)).length >= 2);
16386
+ }
16387
+ function pinnedNodeIdsForReport(report) {
16388
+ if (!report) {
16389
+ return /* @__PURE__ */ new Set();
16390
+ }
16391
+ return /* @__PURE__ */ new Set([
16392
+ ...report.godNodes.map((node) => node.nodeId),
16393
+ ...report.bridgeNodes.map((node) => node.nodeId),
16394
+ ...report.surprisingConnections.flatMap((connection) => [connection.sourceNodeId, connection.targetNodeId])
16395
+ ]);
16396
+ }
16397
+ function sampleGraphNodes(graph, report, nodeBudget = OVERVIEW_NODE_BUDGET) {
16398
+ const pinned = pinnedNodeIdsForReport(report);
16399
+ const nodeById2 = new Map(graph.nodes.map((node) => [node.id, node]));
16400
+ const selected = new Set([...pinned].filter((nodeId) => nodeById2.has(nodeId)));
16401
+ const sortedCommunities2 = [...graph.communities ?? []].sort((left, right) => {
16402
+ const leftNodes = left.nodeIds.map((nodeId) => nodeById2.get(nodeId)).filter((node) => Boolean(node));
16403
+ const rightNodes = right.nodeIds.map((nodeId) => nodeById2.get(nodeId)).filter((node) => Boolean(node));
16404
+ const leftFirstParty = leftNodes.filter((node) => node.sourceClass === "first_party").length;
16405
+ const rightFirstParty = rightNodes.filter((node) => node.sourceClass === "first_party").length;
16406
+ return compareTuples(
16407
+ [-leftFirstParty, -leftNodes.length, left.label, left.id],
16408
+ [-rightFirstParty, -rightNodes.length, right.label, right.id]
16409
+ );
16410
+ });
16411
+ for (const community of sortedCommunities2) {
16412
+ const communityNodes = community.nodeIds.map((nodeId) => nodeById2.get(nodeId)).filter((node) => Boolean(node)).sort((left, right) => compareTuples(nodePriority(left, pinned), nodePriority(right, pinned)));
16413
+ for (const node of communityNodes) {
16414
+ if (selected.size >= nodeBudget && !pinned.has(node.id)) {
16415
+ break;
16416
+ }
16417
+ selected.add(node.id);
16418
+ }
16419
+ if (selected.size >= nodeBudget) {
16420
+ break;
16421
+ }
16422
+ }
16423
+ if (selected.size < nodeBudget) {
16424
+ for (const node of [...graph.nodes].sort((left, right) => compareTuples(nodePriority(left, pinned), nodePriority(right, pinned)))) {
16425
+ if (selected.size >= nodeBudget && !pinned.has(node.id)) {
16426
+ break;
16427
+ }
16428
+ selected.add(node.id);
16429
+ }
16430
+ }
16431
+ return selected;
16432
+ }
16433
+ function buildViewerGraphArtifact(graph, options = {}) {
16434
+ const threshold = options.threshold ?? OVERVIEW_THRESHOLD;
16435
+ const nodeBudget = options.nodeBudget ?? OVERVIEW_NODE_BUDGET;
16436
+ const totalCommunities = graph.communities?.length ?? 0;
16437
+ if (options.full || graph.nodes.length <= threshold) {
16438
+ return {
16439
+ ...graph,
16440
+ presentation: {
16441
+ mode: "full",
16442
+ threshold,
16443
+ nodeBudget,
16444
+ totalNodes: graph.nodes.length,
16445
+ displayedNodes: graph.nodes.length,
16446
+ totalEdges: graph.edges.length,
16447
+ displayedEdges: graph.edges.length,
16448
+ totalCommunities,
16449
+ displayedCommunities: totalCommunities
16450
+ }
16451
+ };
16452
+ }
16453
+ const sampledNodeIds = sampleGraphNodes(graph, options.report, nodeBudget);
16454
+ const nodes = graph.nodes.filter((node) => sampledNodeIds.has(node.id));
16455
+ const edges = graph.edges.filter((edge) => sampledNodeIds.has(edge.source) && sampledNodeIds.has(edge.target));
16456
+ const hyperedges = survivingHyperedges(graph.hyperedges ?? [], sampledNodeIds);
16457
+ const communities = (graph.communities ?? []).map((community) => ({
16458
+ ...community,
16459
+ nodeIds: community.nodeIds.filter((nodeId) => sampledNodeIds.has(nodeId))
16460
+ })).filter((community) => community.nodeIds.length > 0);
16461
+ return {
16462
+ ...graph,
16463
+ nodes,
16464
+ edges,
16465
+ hyperedges,
16466
+ communities,
16467
+ presentation: {
16468
+ mode: "overview",
16469
+ threshold,
16470
+ nodeBudget,
16471
+ totalNodes: graph.nodes.length,
16472
+ displayedNodes: nodes.length,
16473
+ totalEdges: graph.edges.length,
16474
+ displayedEdges: edges.length,
16475
+ totalCommunities,
16476
+ displayedCommunities: communities.length
16477
+ }
16478
+ };
16479
+ }
16480
+
16224
16481
  // src/watch.ts
16225
16482
  import path26 from "path";
16226
16483
  import process3 from "process";
@@ -16686,7 +16943,7 @@ async function ensureViewerDist(viewerDistDir) {
16686
16943
  await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
16687
16944
  }
16688
16945
  }
16689
- async function startGraphServer(rootDir, port) {
16946
+ async function startGraphServer(rootDir, port, options = {}) {
16690
16947
  const { config, paths } = await loadVaultConfig(rootDir);
16691
16948
  const effectivePort = port ?? config.viewer.port;
16692
16949
  await ensureViewerDist(paths.viewerDistDir);
@@ -16698,8 +16955,16 @@ async function startGraphServer(rootDir, port) {
16698
16955
  response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
16699
16956
  return;
16700
16957
  }
16958
+ const graph = await readJsonFile(paths.graphPath);
16959
+ if (!graph) {
16960
+ response.writeHead(404, { "content-type": "application/json" });
16961
+ response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
16962
+ return;
16963
+ }
16964
+ const reportPath = path27.join(paths.wikiDir, "graph", "report.json");
16965
+ const report = await readJsonFile(reportPath) ?? null;
16701
16966
  response.writeHead(200, { "content-type": "application/json" });
16702
- response.end(await fs22.readFile(paths.graphPath, "utf8"));
16967
+ response.end(JSON.stringify(buildViewerGraphArtifact(graph, { report, full: options.full ?? false })));
16703
16968
  return;
16704
16969
  }
16705
16970
  if (url.pathname === "/api/graph/query") {
@@ -16875,7 +17140,7 @@ async function startGraphServer(rootDir, port) {
16875
17140
  }
16876
17141
  };
16877
17142
  }
16878
- async function exportGraphHtml(rootDir, outputPath) {
17143
+ async function exportGraphHtml(rootDir, outputPath, options = {}) {
16879
17144
  const { paths } = await loadVaultConfig(rootDir);
16880
17145
  const graph = await readJsonFile(paths.graphPath);
16881
17146
  if (!graph) {
@@ -16919,7 +17184,11 @@ async function exportGraphHtml(rootDir, outputPath) {
16919
17184
  const script = await fs22.readFile(scriptPath, "utf8");
16920
17185
  const style = stylePath && await fileExists(stylePath) ? await fs22.readFile(stylePath, "utf8") : "";
16921
17186
  const report = await readJsonFile(path27.join(paths.wikiDir, "graph", "report.json"));
16922
- const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean), report }, null, 2).replace(/</g, "\\u003c");
17187
+ const embeddedData = JSON.stringify(
17188
+ { graph: buildViewerGraphArtifact(graph, { report, full: options.full ?? false }), pages: pages.filter(Boolean), report },
17189
+ null,
17190
+ 2
17191
+ ).replace(/</g, "\\u003c");
16923
17192
  const html = [
16924
17193
  "<!doctype html>",
16925
17194
  '<html lang="en">',