@swarmvaultai/engine 0.6.5 → 0.6.6

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
@@ -9,6 +9,7 @@ import {
9
9
  firstSentences,
10
10
  getProviderForTask,
11
11
  initWorkspace,
12
+ isPathWithin,
12
13
  listFilesRecursive,
13
14
  loadVaultConfig,
14
15
  normalizeWhitespace,
@@ -21,7 +22,7 @@ import {
21
22
  uniqueBy,
22
23
  writeFileIfChanged,
23
24
  writeJsonFile
24
- } from "./chunk-OK5752AP.js";
25
+ } from "./chunk-HRRPWXRZ.js";
25
26
 
26
27
  // src/agents.ts
27
28
  import crypto from "crypto";
@@ -3869,8 +3870,31 @@ function interpreterFromShebang(content) {
3869
3870
  }
3870
3871
  return basename(parts[0] ?? "");
3871
3872
  }
3872
- function isShellInterpreter(value) {
3873
- return value === "sh" || value === "bash" || value === "zsh";
3873
+ function languageFromInterpreter(interpreter) {
3874
+ switch (interpreter) {
3875
+ case "sh":
3876
+ case "bash":
3877
+ case "zsh":
3878
+ case "dash":
3879
+ case "ksh":
3880
+ case "ash":
3881
+ return "bash";
3882
+ case "node":
3883
+ case "nodejs":
3884
+ return "javascript";
3885
+ case "python":
3886
+ case "python2":
3887
+ case "python3":
3888
+ return "python";
3889
+ case "ruby":
3890
+ return "ruby";
3891
+ case "php":
3892
+ return "php";
3893
+ case "lua":
3894
+ return "lua";
3895
+ default:
3896
+ return void 0;
3897
+ }
3874
3898
  }
3875
3899
  function formatDiagnosticCategory(category) {
3876
3900
  switch (category) {
@@ -4472,8 +4496,11 @@ function inferCodeLanguage(filePath, mimeType = "", options = {}) {
4472
4496
  if ([".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"].includes(extension)) {
4473
4497
  return "cpp";
4474
4498
  }
4475
- if (!extension && options.executable && isShellInterpreter(interpreterFromShebang(options.content))) {
4476
- return "bash";
4499
+ if (!extension && options.executable) {
4500
+ const fromShebang = languageFromInterpreter(interpreterFromShebang(options.content));
4501
+ if (fromShebang) {
4502
+ return fromShebang;
4503
+ }
4477
4504
  }
4478
4505
  return void 0;
4479
4506
  }
@@ -6583,7 +6610,7 @@ function inferKind(mimeType, filePath, detectionOptions = {}) {
6583
6610
  if (mimeType === "text/csv" || mimeType === "text/tab-separated-values" || filePath.toLowerCase().endsWith(".csv") || filePath.toLowerCase().endsWith(".tsv")) {
6584
6611
  return "csv";
6585
6612
  }
6586
- if (mimeType.startsWith("text/")) {
6613
+ if (mimeType.startsWith("text/") || isStructuredTextMime(mimeType) || isKnownTextPath(filePath)) {
6587
6614
  return "text";
6588
6615
  }
6589
6616
  if (mimeType === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || filePath.toLowerCase().endsWith(".xlsx")) {
@@ -6597,6 +6624,26 @@ function inferKind(mimeType, filePath, detectionOptions = {}) {
6597
6624
  }
6598
6625
  return "binary";
6599
6626
  }
6627
+ function isStructuredTextMime(mimeType) {
6628
+ switch (mimeType) {
6629
+ case "application/json":
6630
+ case "application/json5":
6631
+ case "application/ld+json":
6632
+ case "application/manifest+json":
6633
+ case "application/xml":
6634
+ case "application/toml":
6635
+ case "application/yaml":
6636
+ case "application/x-yaml":
6637
+ case "application/javascript":
6638
+ case "application/ecmascript":
6639
+ case "application/typescript":
6640
+ case "application/x-sh":
6641
+ case "application/x-shellscript":
6642
+ return true;
6643
+ default:
6644
+ return false;
6645
+ }
6646
+ }
6600
6647
  async function localCodeDetectionOptions(absolutePath, payloadBytes) {
6601
6648
  if (path12.extname(absolutePath)) {
6602
6649
  return {};
@@ -6645,7 +6692,129 @@ function guessMimeType(target) {
6645
6692
  if (isRstFilePath(target)) {
6646
6693
  return "text/x-rst";
6647
6694
  }
6648
- return mime.lookup(target) || "application/octet-stream";
6695
+ const extension = path12.extname(target).toLowerCase();
6696
+ if (extension === ".ts" || extension === ".tsx" || extension === ".mts" || extension === ".cts") {
6697
+ return "text/typescript";
6698
+ }
6699
+ const looked = mime.lookup(target);
6700
+ if (looked) {
6701
+ return looked;
6702
+ }
6703
+ if (isKnownTextPath(target)) {
6704
+ return "text/plain";
6705
+ }
6706
+ return "application/octet-stream";
6707
+ }
6708
+ var KNOWN_TEXT_DOTFILE_NAMES = /* @__PURE__ */ new Set([
6709
+ ".gitignore",
6710
+ ".gitattributes",
6711
+ ".gitkeep",
6712
+ ".gitmodules",
6713
+ ".editorconfig",
6714
+ ".npmrc",
6715
+ ".yarnrc",
6716
+ ".prettierignore",
6717
+ ".prettierrc",
6718
+ ".dockerignore",
6719
+ ".eslintignore",
6720
+ ".eslintrc",
6721
+ ".nvmrc",
6722
+ ".node-version",
6723
+ ".python-version",
6724
+ ".ruby-version",
6725
+ ".tool-versions"
6726
+ ]);
6727
+ var KNOWN_TEXT_BASENAMES = /* @__PURE__ */ new Set([
6728
+ "readme",
6729
+ "license",
6730
+ "licence",
6731
+ "copying",
6732
+ "unlicense",
6733
+ "notice",
6734
+ "authors",
6735
+ "contributors",
6736
+ "patents",
6737
+ "maintainers",
6738
+ "owners",
6739
+ "codeowners",
6740
+ "changelog",
6741
+ "changes",
6742
+ "history",
6743
+ "news",
6744
+ "todo",
6745
+ "install",
6746
+ "dockerfile",
6747
+ "containerfile",
6748
+ "makefile",
6749
+ "gnumakefile",
6750
+ "rakefile",
6751
+ "gemfile",
6752
+ "procfile",
6753
+ "jenkinsfile",
6754
+ "vagrantfile",
6755
+ "brewfile",
6756
+ "go.mod",
6757
+ "go.sum",
6758
+ "go.work",
6759
+ "go.work.sum",
6760
+ "cargo.lock",
6761
+ "pipfile",
6762
+ "pipfile.lock",
6763
+ "poetry.lock",
6764
+ "uv.lock",
6765
+ "py.typed",
6766
+ "package-lock.json",
6767
+ "yarn.lock",
6768
+ "pnpm-lock.yaml",
6769
+ "composer.lock",
6770
+ "requirements.txt"
6771
+ ]);
6772
+ var KNOWN_TEXT_BASENAME_PREFIXES = ["license", "licence", "copying", "unlicense", "readme", "changelog", "dockerfile", "containerfile"];
6773
+ var KNOWN_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
6774
+ ".toml",
6775
+ ".lock",
6776
+ ".tmpl",
6777
+ ".template",
6778
+ ".mustache",
6779
+ ".hbs",
6780
+ ".handlebars",
6781
+ ".ejs",
6782
+ ".njk",
6783
+ ".liquid",
6784
+ ".vim",
6785
+ ".typed",
6786
+ ".env",
6787
+ ".properties",
6788
+ ".ini",
6789
+ ".cfg",
6790
+ ".conf",
6791
+ ".config",
6792
+ ".bazel",
6793
+ ".bzl",
6794
+ ".bat",
6795
+ ".cmd"
6796
+ ]);
6797
+ function isKnownTextPath(target) {
6798
+ const basename = path12.basename(target).toLowerCase();
6799
+ if (KNOWN_TEXT_DOTFILE_NAMES.has(basename)) {
6800
+ return true;
6801
+ }
6802
+ if (basename === ".env" || basename.startsWith(".env.")) {
6803
+ return true;
6804
+ }
6805
+ if (KNOWN_TEXT_BASENAMES.has(basename)) {
6806
+ return true;
6807
+ }
6808
+ for (const prefix of KNOWN_TEXT_BASENAME_PREFIXES) {
6809
+ if (basename === prefix || basename.startsWith(`${prefix}-`) || basename.startsWith(`${prefix}.`)) {
6810
+ return true;
6811
+ }
6812
+ }
6813
+ const extension = path12.extname(target).toLowerCase();
6814
+ if (extension && KNOWN_TEXT_EXTENSIONS.has(extension)) {
6815
+ return true;
6816
+ }
6817
+ return false;
6649
6818
  }
6650
6819
  function sourceGroupIdFor(prepared) {
6651
6820
  const originKey = prepared.originType === "url" ? prepared.url ?? prepared.title : prepared.originalPath ?? prepared.title;
@@ -8564,7 +8733,7 @@ async function collectInboxAttachmentRefs(inputDir, files) {
8564
8733
  const sourceRefs = [];
8565
8734
  for (const ref of refs) {
8566
8735
  const resolved = path12.resolve(path12.dirname(absolutePath), ref);
8567
- if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
8736
+ if (!isPathWithin(inputDir, resolved) || !await fileExists(resolved)) {
8568
8737
  continue;
8569
8738
  }
8570
8739
  sourceRefs.push({
@@ -13592,7 +13761,7 @@ async function resolveImageGenerationProvider(rootDir) {
13592
13761
  if (!providerConfig) {
13593
13762
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
13594
13763
  }
13595
- const { createProvider: createProvider2 } = await import("./registry-TYROWPR5.js");
13764
+ const { createProvider: createProvider2 } = await import("./registry-NBLIJHZT.js");
13596
13765
  return createProvider2(preferredProviderId, providerConfig, rootDir);
13597
13766
  }
13598
13767
  async function generateOutputArtifacts(rootDir, input) {
@@ -17631,9 +17800,16 @@ async function listPages(rootDir) {
17631
17800
  return graph?.pages ?? [];
17632
17801
  }
17633
17802
  async function readPage(rootDir, relativePath) {
17803
+ if (!relativePath) {
17804
+ return null;
17805
+ }
17634
17806
  const { paths } = await loadVaultConfig(rootDir);
17635
17807
  const absolutePath = path23.resolve(paths.wikiDir, relativePath);
17636
- if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
17808
+ if (!isPathWithin(paths.wikiDir, absolutePath)) {
17809
+ return null;
17810
+ }
17811
+ const stats = await fs19.stat(absolutePath).catch(() => null);
17812
+ if (!stats?.isFile()) {
17637
17813
  return null;
17638
17814
  }
17639
17815
  const raw = await fs19.readFile(absolutePath, "utf8");
@@ -17662,6 +17838,28 @@ async function getWorkspaceInfo(rootDir) {
17662
17838
  pageCount: pages.length
17663
17839
  };
17664
17840
  }
17841
+ function extractClaimSectionLines(content) {
17842
+ const lines = content.split("\n");
17843
+ let inClaims = false;
17844
+ let found = false;
17845
+ const claimLines = [];
17846
+ for (const line of lines) {
17847
+ const trimmed = line.trimEnd();
17848
+ if (trimmed === "## Claims") {
17849
+ inClaims = true;
17850
+ found = true;
17851
+ continue;
17852
+ }
17853
+ if (inClaims) {
17854
+ if (/^#{1,2}\s/.test(trimmed)) {
17855
+ inClaims = false;
17856
+ continue;
17857
+ }
17858
+ claimLines.push(line);
17859
+ }
17860
+ }
17861
+ return found ? claimLines : null;
17862
+ }
17665
17863
  function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sourceProjects) {
17666
17864
  const manifestMap = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
17667
17865
  const pageMap2 = new Map(graph.pages.map((page) => [page.id, page]));
@@ -17707,8 +17905,9 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
17707
17905
  const absolutePath = path23.join(paths.wikiDir, page.path);
17708
17906
  if (await fileExists(absolutePath)) {
17709
17907
  const content = await fs19.readFile(absolutePath, "utf8");
17710
- if (content.includes("## Claims")) {
17711
- const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
17908
+ const claimLines = extractClaimSectionLines(content);
17909
+ if (claimLines !== null) {
17910
+ const uncited = claimLines.filter((line) => line.startsWith("- ") && !line.includes("[source:"));
17712
17911
  if (uncited.length) {
17713
17912
  findings.push({
17714
17913
  severity: "warning",
@@ -17823,7 +18022,7 @@ async function bootstrapDemo(rootDir, input) {
17823
18022
  }
17824
18023
 
17825
18024
  // src/mcp.ts
17826
- var SERVER_VERSION = "0.6.5";
18025
+ var SERVER_VERSION = "0.6.6";
17827
18026
  async function createMcpServer(rootDir) {
17828
18027
  const server = new McpServer({
17829
18028
  name: "swarmvault",
@@ -18158,7 +18357,7 @@ async function createMcpServer(rootDir) {
18158
18357
  const encodedPath = typeof variables.path === "string" ? variables.path : "";
18159
18358
  const relativePath = decodeURIComponent(encodedPath);
18160
18359
  const absolutePath = path24.resolve(paths.sessionsDir, relativePath);
18161
- if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
18360
+ if (!isPathWithin(paths.sessionsDir, absolutePath) || !await fileExists(absolutePath)) {
18162
18361
  return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
18163
18362
  }
18164
18363
  return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs20.readFile(absolutePath, "utf8"));
@@ -18497,6 +18696,15 @@ var DOCS_HINT_SEGMENTS = /* @__PURE__ */ new Set([
18497
18696
  function uniqueStrings4(values) {
18498
18697
  return uniqueBy(values.filter(Boolean), (value) => value);
18499
18698
  }
18699
+ function sourceOutputSchemaHash(schemas, projectIds) {
18700
+ if (!projectIds.length) {
18701
+ return schemas.effective.global.hash;
18702
+ }
18703
+ return composeVaultSchema(
18704
+ schemas.root,
18705
+ uniqueStrings4([...projectIds].sort((left, right) => left.localeCompare(right))).map((projectId) => schemas.projects[projectId]).filter((schema) => Boolean(schema?.hash))
18706
+ ).hash;
18707
+ }
18500
18708
  function normalizeManagedStatus(value) {
18501
18709
  return value === "missing" || value === "error" ? value : "ready";
18502
18710
  }
@@ -19080,6 +19288,7 @@ async function writeSourceBriefForScope(rootDir, source) {
19080
19288
  return null;
19081
19289
  }
19082
19290
  const graph = await readJsonFile(paths.graphPath);
19291
+ const schemas = await loadVaultSchemas(rootDir);
19083
19292
  const relatedPages = graph ? scopedSourcePages(graph, source.sourceIds) : [];
19084
19293
  const relatedPageIds = relatedPages.slice(0, 12).map((page) => page.id);
19085
19294
  const relatedNodeIds = graph ? scopedNodeIds(graph, source.sourceIds).slice(0, 20) : [];
@@ -19090,7 +19299,7 @@ async function writeSourceBriefForScope(rootDir, source) {
19090
19299
  question: `Brief ${source.title}`,
19091
19300
  answer: markdown,
19092
19301
  citations: source.sourceIds,
19093
- schemaHash: graph?.generatedAt ?? "",
19302
+ schemaHash: sourceOutputSchemaHash(schemas, projectIds),
19094
19303
  outputFormat: "report",
19095
19304
  relatedPageIds,
19096
19305
  relatedNodeIds,
@@ -19325,6 +19534,7 @@ async function buildSourceReviewStagedPage(rootDir, scope) {
19325
19534
  throw new Error(`Could not generate a source review for ${scope.id}.`);
19326
19535
  }
19327
19536
  const graph = await readJsonFile(paths.graphPath);
19537
+ const schemas = await loadVaultSchemas(rootDir);
19328
19538
  const scopeManifests = manifestsForScope(graph, scope);
19329
19539
  const relatedPages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
19330
19540
  const relatedPageIds = relatedPages.slice(0, 16).map((page) => page.id);
@@ -19336,7 +19546,7 @@ async function buildSourceReviewStagedPage(rootDir, scope) {
19336
19546
  question: `Review ${scope.title}`,
19337
19547
  answer: markdown,
19338
19548
  citations: scope.sourceIds,
19339
- schemaHash: graph?.generatedAt ?? "",
19549
+ schemaHash: sourceOutputSchemaHash(schemas, projectIds),
19340
19550
  outputFormat: "report",
19341
19551
  relatedPageIds,
19342
19552
  relatedNodeIds,
@@ -19570,6 +19780,7 @@ async function buildSourceGuideStagedPage(rootDir, scope) {
19570
19780
  throw new Error(`Could not generate a source guide for ${scope.id}.`);
19571
19781
  }
19572
19782
  const graph = await readJsonFile(paths.graphPath);
19783
+ const schemas = await loadVaultSchemas(rootDir);
19573
19784
  const scopeManifests = manifestsForScope(graph, scope);
19574
19785
  const relatedPages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
19575
19786
  const contradictions = findContradictionsForScope(scope, await readGraphReport(rootDir));
@@ -19583,7 +19794,7 @@ async function buildSourceGuideStagedPage(rootDir, scope) {
19583
19794
  question: `Guide ${scope.title}`,
19584
19795
  answer: markdown,
19585
19796
  citations: scope.sourceIds,
19586
- schemaHash: graph?.generatedAt ?? "",
19797
+ schemaHash: sourceOutputSchemaHash(schemas, projectIds),
19587
19798
  outputFormat: "report",
19588
19799
  relatedPageIds,
19589
19800
  relatedNodeIds,
@@ -19672,6 +19883,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
19672
19883
  await compileVault(rootDir, {});
19673
19884
  graph = await readJsonFile(paths.graphPath);
19674
19885
  }
19886
+ const schemas = await loadVaultSchemas(rootDir);
19675
19887
  const scopeManifests = manifestsForScope(graph, scope);
19676
19888
  const sourcePages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
19677
19889
  const analyses = await loadSourceAnalyses(rootDir, scope.sourceIds);
@@ -19737,7 +19949,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
19737
19949
  question: `Guided Session ${scope.title}`,
19738
19950
  answer: sessionMarkdown,
19739
19951
  citations: scope.sourceIds,
19740
- schemaHash: graph?.generatedAt ?? "",
19952
+ schemaHash: sourceOutputSchemaHash(schemas, projectIds),
19741
19953
  outputFormat: "report",
19742
19954
  relatedPageIds,
19743
19955
  relatedNodeIds,
@@ -20782,10 +20994,21 @@ async function getWatchStatus(rootDir) {
20782
20994
 
20783
20995
  // src/viewer.ts
20784
20996
  var execFileAsync = promisify(execFile);
20997
+ async function isReadableFile(absolutePath) {
20998
+ try {
20999
+ const stats = await fs23.stat(absolutePath);
21000
+ return stats.isFile();
21001
+ } catch {
21002
+ return false;
21003
+ }
21004
+ }
20785
21005
  async function readViewerPage(rootDir, relativePath) {
21006
+ if (!relativePath) {
21007
+ return null;
21008
+ }
20786
21009
  const { paths } = await loadVaultConfig(rootDir);
20787
21010
  const absolutePath = path28.resolve(paths.wikiDir, relativePath);
20788
- if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
21011
+ if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
20789
21012
  return null;
20790
21013
  }
20791
21014
  const raw = await fs23.readFile(absolutePath, "utf8");
@@ -20799,9 +21022,12 @@ async function readViewerPage(rootDir, relativePath) {
20799
21022
  };
20800
21023
  }
20801
21024
  async function readViewerAsset(rootDir, relativePath) {
21025
+ if (!relativePath) {
21026
+ return null;
21027
+ }
20802
21028
  const { paths } = await loadVaultConfig(rootDir);
20803
21029
  const absolutePath = path28.resolve(paths.wikiDir, relativePath);
20804
- if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
21030
+ if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
20805
21031
  return null;
20806
21032
  }
20807
21033
  return {
@@ -20843,178 +21069,204 @@ async function startGraphServer(rootDir, port, options = {}) {
20843
21069
  await ensureViewerDist(paths.viewerDistDir);
20844
21070
  const server = http.createServer(async (request, response) => {
20845
21071
  const url = new URL(request.url ?? "/", `http://${request.headers.host ?? `localhost:${effectivePort}`}`);
20846
- if (url.pathname === "/api/graph") {
20847
- if (!await fileExists(paths.graphPath)) {
20848
- response.writeHead(404, { "content-type": "application/json" });
20849
- response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
21072
+ try {
21073
+ if (url.pathname === "/api/graph") {
21074
+ if (!await fileExists(paths.graphPath)) {
21075
+ response.writeHead(404, { "content-type": "application/json" });
21076
+ response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
21077
+ return;
21078
+ }
21079
+ const graph = await readJsonFile(paths.graphPath);
21080
+ if (!graph) {
21081
+ response.writeHead(404, { "content-type": "application/json" });
21082
+ response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
21083
+ return;
21084
+ }
21085
+ const reportPath = path28.join(paths.wikiDir, "graph", "report.json");
21086
+ const report = await readJsonFile(reportPath) ?? null;
21087
+ response.writeHead(200, { "content-type": "application/json" });
21088
+ response.end(JSON.stringify(buildViewerGraphArtifact(graph, { report, full: options.full ?? false })));
20850
21089
  return;
20851
21090
  }
20852
- const graph = await readJsonFile(paths.graphPath);
20853
- if (!graph) {
20854
- response.writeHead(404, { "content-type": "application/json" });
20855
- response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
21091
+ if (url.pathname === "/api/graph/query") {
21092
+ const question = url.searchParams.get("q") ?? "";
21093
+ const traversal = url.searchParams.get("traversal");
21094
+ const budget = Number.parseInt(url.searchParams.get("budget") ?? "12", 10);
21095
+ const result = await queryGraphVault(rootDir, question, {
21096
+ traversal: traversal === "dfs" ? "dfs" : "bfs",
21097
+ budget: Number.isFinite(budget) ? budget : 12
21098
+ });
21099
+ response.writeHead(200, { "content-type": "application/json" });
21100
+ response.end(JSON.stringify(result));
20856
21101
  return;
20857
21102
  }
20858
- const reportPath = path28.join(paths.wikiDir, "graph", "report.json");
20859
- const report = await readJsonFile(reportPath) ?? null;
20860
- response.writeHead(200, { "content-type": "application/json" });
20861
- response.end(JSON.stringify(buildViewerGraphArtifact(graph, { report, full: options.full ?? false })));
20862
- return;
20863
- }
20864
- if (url.pathname === "/api/graph/query") {
20865
- const question = url.searchParams.get("q") ?? "";
20866
- const traversal = url.searchParams.get("traversal");
20867
- const budget = Number.parseInt(url.searchParams.get("budget") ?? "12", 10);
20868
- response.writeHead(200, { "content-type": "application/json" });
20869
- response.end(
20870
- JSON.stringify(
20871
- await queryGraphVault(rootDir, question, {
20872
- traversal: traversal === "dfs" ? "dfs" : "bfs",
20873
- budget: Number.isFinite(budget) ? budget : 12
20874
- })
20875
- )
20876
- );
20877
- return;
20878
- }
20879
- if (url.pathname === "/api/graph/path") {
20880
- const from = url.searchParams.get("from") ?? "";
20881
- const to = url.searchParams.get("to") ?? "";
20882
- response.writeHead(200, { "content-type": "application/json" });
20883
- response.end(JSON.stringify(await pathGraphVault(rootDir, from, to)));
20884
- return;
20885
- }
20886
- if (url.pathname === "/api/graph/explain") {
20887
- const target2 = url.searchParams.get("target") ?? "";
20888
- response.writeHead(200, { "content-type": "application/json" });
20889
- response.end(JSON.stringify(await explainGraphVault(rootDir, target2)));
20890
- return;
20891
- }
20892
- if (url.pathname === "/api/search") {
20893
- if (!await fileExists(paths.searchDbPath)) {
20894
- response.writeHead(404, { "content-type": "application/json" });
20895
- response.end(JSON.stringify({ error: "Search index not found. Run `swarmvault compile` first." }));
21103
+ if (url.pathname === "/api/graph/path") {
21104
+ const from = url.searchParams.get("from") ?? "";
21105
+ const to = url.searchParams.get("to") ?? "";
21106
+ const result = await pathGraphVault(rootDir, from, to);
21107
+ response.writeHead(200, { "content-type": "application/json" });
21108
+ response.end(JSON.stringify(result));
20896
21109
  return;
20897
21110
  }
20898
- const query = url.searchParams.get("q") ?? "";
20899
- const limit = Number.parseInt(url.searchParams.get("limit") ?? "10", 10);
20900
- const kind = url.searchParams.get("kind") ?? "all";
20901
- const status = url.searchParams.get("status") ?? "all";
20902
- const project = url.searchParams.get("project") ?? "all";
20903
- const sourceType = url.searchParams.get("sourceType") ?? "all";
20904
- const sourceClass = url.searchParams.get("sourceClass") ?? "all";
20905
- const results = searchPages(paths.searchDbPath, query, {
20906
- limit: Number.isFinite(limit) ? limit : 10,
20907
- kind,
20908
- status,
20909
- project,
20910
- sourceType,
20911
- sourceClass
20912
- });
20913
- response.writeHead(200, { "content-type": "application/json" });
20914
- response.end(JSON.stringify(results));
20915
- return;
20916
- }
20917
- if (url.pathname === "/api/graph-report") {
20918
- const reportPath = path28.join(paths.wikiDir, "graph", "report.json");
20919
- if (!await fileExists(reportPath)) {
20920
- response.writeHead(404, { "content-type": "application/json" });
20921
- response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
21111
+ if (url.pathname === "/api/graph/explain") {
21112
+ const target2 = url.searchParams.get("target") ?? "";
21113
+ if (!target2) {
21114
+ response.writeHead(400, { "content-type": "application/json" });
21115
+ response.end(JSON.stringify({ error: "Missing explain target." }));
21116
+ return;
21117
+ }
21118
+ try {
21119
+ const result = await explainGraphVault(rootDir, target2);
21120
+ response.writeHead(200, { "content-type": "application/json" });
21121
+ response.end(JSON.stringify(result));
21122
+ } catch (error) {
21123
+ response.writeHead(404, { "content-type": "application/json" });
21124
+ response.end(JSON.stringify({ error: error instanceof Error ? error.message : `Could not resolve graph target: ${target2}` }));
21125
+ }
20922
21126
  return;
20923
21127
  }
20924
- response.writeHead(200, { "content-type": "application/json" });
20925
- response.end(await fs23.readFile(reportPath, "utf8"));
20926
- return;
20927
- }
20928
- if (url.pathname === "/api/watch-status") {
20929
- response.writeHead(200, { "content-type": "application/json" });
20930
- response.end(JSON.stringify(await getWatchStatus(rootDir)));
20931
- return;
20932
- }
20933
- if (url.pathname === "/api/page") {
20934
- const relativePath2 = url.searchParams.get("path") ?? "";
20935
- const page = await readViewerPage(rootDir, relativePath2);
20936
- if (!page) {
20937
- response.writeHead(404, { "content-type": "application/json" });
20938
- response.end(JSON.stringify({ error: `Page not found: ${relativePath2}` }));
21128
+ if (url.pathname === "/api/search") {
21129
+ if (!await fileExists(paths.searchDbPath)) {
21130
+ response.writeHead(404, { "content-type": "application/json" });
21131
+ response.end(JSON.stringify({ error: "Search index not found. Run `swarmvault compile` first." }));
21132
+ return;
21133
+ }
21134
+ const query = url.searchParams.get("q") ?? "";
21135
+ const limit = Number.parseInt(url.searchParams.get("limit") ?? "10", 10);
21136
+ const kind = url.searchParams.get("kind") ?? "all";
21137
+ const status = url.searchParams.get("status") ?? "all";
21138
+ const project = url.searchParams.get("project") ?? "all";
21139
+ const sourceType = url.searchParams.get("sourceType") ?? "all";
21140
+ const sourceClass = url.searchParams.get("sourceClass") ?? "all";
21141
+ const results = searchPages(paths.searchDbPath, query, {
21142
+ limit: Number.isFinite(limit) ? limit : 10,
21143
+ kind,
21144
+ status,
21145
+ project,
21146
+ sourceType,
21147
+ sourceClass
21148
+ });
21149
+ response.writeHead(200, { "content-type": "application/json" });
21150
+ response.end(JSON.stringify(results));
20939
21151
  return;
20940
21152
  }
20941
- response.writeHead(200, { "content-type": "application/json" });
20942
- response.end(JSON.stringify(page));
20943
- return;
20944
- }
20945
- if (url.pathname === "/api/asset") {
20946
- const relativePath2 = url.searchParams.get("path") ?? "";
20947
- const asset = await readViewerAsset(rootDir, relativePath2);
20948
- if (!asset) {
20949
- response.writeHead(404, { "content-type": "application/json" });
20950
- response.end(JSON.stringify({ error: `Asset not found: ${relativePath2}` }));
21153
+ if (url.pathname === "/api/graph-report") {
21154
+ const reportPath = path28.join(paths.wikiDir, "graph", "report.json");
21155
+ if (!await fileExists(reportPath)) {
21156
+ response.writeHead(404, { "content-type": "application/json" });
21157
+ response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
21158
+ return;
21159
+ }
21160
+ const body = await fs23.readFile(reportPath, "utf8");
21161
+ response.writeHead(200, { "content-type": "application/json" });
21162
+ response.end(body);
20951
21163
  return;
20952
21164
  }
20953
- response.writeHead(200, { "content-type": asset.mimeType });
20954
- response.end(asset.buffer);
20955
- return;
20956
- }
20957
- if (url.pathname === "/api/reviews" && request.method === "GET") {
20958
- response.writeHead(200, { "content-type": "application/json" });
20959
- response.end(JSON.stringify(await listApprovals(rootDir)));
20960
- return;
20961
- }
20962
- if (url.pathname === "/api/review" && request.method === "GET") {
20963
- const approvalId = url.searchParams.get("id") ?? "";
20964
- if (!approvalId) {
20965
- response.writeHead(400, { "content-type": "application/json" });
20966
- response.end(JSON.stringify({ error: "Missing approval id." }));
21165
+ if (url.pathname === "/api/watch-status") {
21166
+ const watchStatus = await getWatchStatus(rootDir);
21167
+ response.writeHead(200, { "content-type": "application/json" });
21168
+ response.end(JSON.stringify(watchStatus));
20967
21169
  return;
20968
21170
  }
20969
- response.writeHead(200, { "content-type": "application/json" });
20970
- response.end(JSON.stringify(await readApproval(rootDir, approvalId)));
20971
- return;
20972
- }
20973
- if (url.pathname === "/api/review" && request.method === "POST") {
20974
- const body = await readJsonBody(request);
20975
- const approvalId = typeof body.approvalId === "string" ? body.approvalId : "";
20976
- const targets = Array.isArray(body.targets) ? body.targets.filter((item) => typeof item === "string") : [];
20977
- const action = url.searchParams.get("action") ?? "";
20978
- if (!approvalId || action !== "accept" && action !== "reject") {
20979
- response.writeHead(400, { "content-type": "application/json" });
20980
- response.end(JSON.stringify({ error: "Missing approval id or invalid review action." }));
21171
+ if (url.pathname === "/api/page") {
21172
+ const relativePath2 = url.searchParams.get("path") ?? "";
21173
+ const page = await readViewerPage(rootDir, relativePath2);
21174
+ if (!page) {
21175
+ response.writeHead(404, { "content-type": "application/json" });
21176
+ response.end(JSON.stringify({ error: `Page not found: ${relativePath2}` }));
21177
+ return;
21178
+ }
21179
+ response.writeHead(200, { "content-type": "application/json" });
21180
+ response.end(JSON.stringify(page));
20981
21181
  return;
20982
21182
  }
20983
- const result = action === "accept" ? await acceptApproval(rootDir, approvalId, targets) : await rejectApproval(rootDir, approvalId, targets);
20984
- response.writeHead(200, { "content-type": "application/json" });
20985
- response.end(JSON.stringify(result));
20986
- return;
20987
- }
20988
- if (url.pathname === "/api/candidates" && request.method === "GET") {
20989
- response.writeHead(200, { "content-type": "application/json" });
20990
- response.end(JSON.stringify(await listCandidates(rootDir)));
20991
- return;
20992
- }
20993
- if (url.pathname === "/api/candidate" && request.method === "POST") {
20994
- const body = await readJsonBody(request);
20995
- const target2 = typeof body.target === "string" ? body.target : "";
20996
- const action = url.searchParams.get("action") ?? "";
20997
- if (!target2 || action !== "promote" && action !== "archive") {
20998
- response.writeHead(400, { "content-type": "application/json" });
20999
- response.end(JSON.stringify({ error: "Missing candidate target or invalid candidate action." }));
21183
+ if (url.pathname === "/api/asset") {
21184
+ const relativePath2 = url.searchParams.get("path") ?? "";
21185
+ const asset = await readViewerAsset(rootDir, relativePath2);
21186
+ if (!asset) {
21187
+ response.writeHead(404, { "content-type": "application/json" });
21188
+ response.end(JSON.stringify({ error: `Asset not found: ${relativePath2}` }));
21189
+ return;
21190
+ }
21191
+ response.writeHead(200, { "content-type": asset.mimeType });
21192
+ response.end(asset.buffer);
21000
21193
  return;
21001
21194
  }
21002
- const result = action === "promote" ? await promoteCandidate(rootDir, target2) : await archiveCandidate(rootDir, target2);
21003
- response.writeHead(200, { "content-type": "application/json" });
21004
- response.end(JSON.stringify(result));
21005
- return;
21006
- }
21007
- const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
21008
- const target = path28.join(paths.viewerDistDir, relativePath);
21009
- const fallback = path28.join(paths.viewerDistDir, "index.html");
21010
- const filePath = await fileExists(target) ? target : fallback;
21011
- if (!await fileExists(filePath)) {
21012
- response.writeHead(503, { "content-type": "text/plain" });
21013
- response.end("Viewer build not found. Run `pnpm build` first.");
21014
- return;
21195
+ if (url.pathname === "/api/reviews" && request.method === "GET") {
21196
+ const approvals = await listApprovals(rootDir);
21197
+ response.writeHead(200, { "content-type": "application/json" });
21198
+ response.end(JSON.stringify(approvals));
21199
+ return;
21200
+ }
21201
+ if (url.pathname === "/api/review" && request.method === "GET") {
21202
+ const approvalId = url.searchParams.get("id") ?? "";
21203
+ if (!approvalId) {
21204
+ response.writeHead(400, { "content-type": "application/json" });
21205
+ response.end(JSON.stringify({ error: "Missing approval id." }));
21206
+ return;
21207
+ }
21208
+ const approval = await readApproval(rootDir, approvalId);
21209
+ response.writeHead(200, { "content-type": "application/json" });
21210
+ response.end(JSON.stringify(approval));
21211
+ return;
21212
+ }
21213
+ if (url.pathname === "/api/review" && request.method === "POST") {
21214
+ const body = await readJsonBody(request);
21215
+ const approvalId = typeof body.approvalId === "string" ? body.approvalId : "";
21216
+ const targets = Array.isArray(body.targets) ? body.targets.filter((item) => typeof item === "string") : [];
21217
+ const action = url.searchParams.get("action") ?? "";
21218
+ if (!approvalId || action !== "accept" && action !== "reject") {
21219
+ response.writeHead(400, { "content-type": "application/json" });
21220
+ response.end(JSON.stringify({ error: "Missing approval id or invalid review action." }));
21221
+ return;
21222
+ }
21223
+ const result = action === "accept" ? await acceptApproval(rootDir, approvalId, targets) : await rejectApproval(rootDir, approvalId, targets);
21224
+ response.writeHead(200, { "content-type": "application/json" });
21225
+ response.end(JSON.stringify(result));
21226
+ return;
21227
+ }
21228
+ if (url.pathname === "/api/candidates" && request.method === "GET") {
21229
+ const candidates = await listCandidates(rootDir);
21230
+ response.writeHead(200, { "content-type": "application/json" });
21231
+ response.end(JSON.stringify(candidates));
21232
+ return;
21233
+ }
21234
+ if (url.pathname === "/api/candidate" && request.method === "POST") {
21235
+ const body = await readJsonBody(request);
21236
+ const target2 = typeof body.target === "string" ? body.target : "";
21237
+ const action = url.searchParams.get("action") ?? "";
21238
+ if (!target2 || action !== "promote" && action !== "archive") {
21239
+ response.writeHead(400, { "content-type": "application/json" });
21240
+ response.end(JSON.stringify({ error: "Missing candidate target or invalid candidate action." }));
21241
+ return;
21242
+ }
21243
+ const result = action === "promote" ? await promoteCandidate(rootDir, target2) : await archiveCandidate(rootDir, target2);
21244
+ response.writeHead(200, { "content-type": "application/json" });
21245
+ response.end(JSON.stringify(result));
21246
+ return;
21247
+ }
21248
+ const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
21249
+ const target = path28.join(paths.viewerDistDir, relativePath);
21250
+ const fallback = path28.join(paths.viewerDistDir, "index.html");
21251
+ const filePath = await fileExists(target) ? target : fallback;
21252
+ if (!await fileExists(filePath)) {
21253
+ response.writeHead(503, { "content-type": "text/plain" });
21254
+ response.end("Viewer build not found. Run `pnpm build` first.");
21255
+ return;
21256
+ }
21257
+ const staticBody = await fs23.readFile(filePath);
21258
+ response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
21259
+ response.end(staticBody);
21260
+ } catch (error) {
21261
+ const message = error instanceof Error ? error.message : String(error);
21262
+ console.error(`[viewer] ${request.method ?? "GET"} ${url.pathname} failed: ${message}`);
21263
+ if (!response.headersSent) {
21264
+ response.writeHead(500, { "content-type": "application/json" });
21265
+ response.end(JSON.stringify({ error: message }));
21266
+ } else {
21267
+ response.end();
21268
+ }
21015
21269
  }
21016
- response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
21017
- response.end(await fs23.readFile(filePath));
21018
21270
  });
21019
21271
  await new Promise((resolve) => {
21020
21272
  server.listen(effectivePort, resolve);