@swarmvaultai/engine 0.6.6 → 0.6.7

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.
Files changed (2) hide show
  1. package/dist/index.js +140 -150
  2. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -64,6 +64,13 @@ function buildManagedBlock(target) {
64
64
  ""
65
65
  ].join("\n");
66
66
  }
67
+ function buildCursorRule() {
68
+ const frontmatter = YAML.stringify({
69
+ description: "SwarmVault graph-first repository instructions.",
70
+ alwaysApply: true
71
+ }).trimEnd();
72
+ return ["---", frontmatter, "---", "", buildManagedBlock("cursor").trimEnd(), ""].join("\n");
73
+ }
67
74
  function supportsAgentHook(agent) {
68
75
  return agent === "claude" || agent === "opencode" || agent === "gemini" || agent === "copilot";
69
76
  }
@@ -690,8 +697,7 @@ async function installAgent(rootDir, agent, options = {}) {
690
697
  await upsertManagedBlock(target, buildManagedBlock("gemini"));
691
698
  break;
692
699
  case "cursor":
693
- await writeOwnedFile(target, `${buildManagedBlock("cursor")}
694
- `);
700
+ await writeOwnedFile(target, buildCursorRule());
695
701
  break;
696
702
  case "aider":
697
703
  await upsertManagedBlock(target, buildManagedBlock("aider"));
@@ -1343,10 +1349,13 @@ function buildBenchmarkArtifact(input) {
1343
1349
  const corpusTokens = Math.max(1, Math.round(input.corpusWords * (100 / 75)));
1344
1350
  const perQuestion = input.perQuestion.filter((entry) => entry.queryTokens > 0).map((entry) => ({
1345
1351
  ...entry,
1346
- reduction: Number(Math.max(0, 1 - entry.queryTokens / Math.max(1, corpusTokens)).toFixed(3))
1352
+ // Honest reduction: negative values mean graph context is larger than the
1353
+ // full corpus, which is the truth on very small vaults. Clamping to zero
1354
+ // hid that signal.
1355
+ reduction: Number((1 - entry.queryTokens / Math.max(1, corpusTokens)).toFixed(3))
1347
1356
  }));
1348
1357
  const avgQueryTokens = perQuestion.length ? Math.max(1, Math.round(perQuestion.reduce((total, entry) => total + entry.queryTokens, 0) / perQuestion.length)) : 0;
1349
- const reductionRatio = avgQueryTokens ? Number(Math.max(0, 1 - avgQueryTokens / Math.max(1, corpusTokens)).toFixed(3)) : 0;
1358
+ const reductionRatio = avgQueryTokens ? Number((1 - avgQueryTokens / Math.max(1, corpusTokens)).toFixed(3)) : 0;
1350
1359
  const uniqueVisitedNodes = new Set(perQuestion.flatMap((entry) => entry.visitedNodeIds)).size;
1351
1360
  const summary = {
1352
1361
  questionCount: input.questions.length,
@@ -1728,6 +1737,7 @@ import { pathToFileURL } from "url";
1728
1737
  import { Readability } from "@mozilla/readability";
1729
1738
  import matter3 from "gray-matter";
1730
1739
  import ignore from "ignore";
1740
+ import { isText } from "istextorbinary";
1731
1741
  import { JSDOM as JSDOM2 } from "jsdom";
1732
1742
  import mime from "mime-types";
1733
1743
  import TurndownService2 from "turndown";
@@ -2447,14 +2457,18 @@ function rubyStringContent(node) {
2447
2457
  const contentNode = node.descendantsOfType(["string_content", "simple_symbol", "bare_string"]).find((item) => item !== null) ?? null;
2448
2458
  return contentNode?.text.trim() || void 0;
2449
2459
  }
2460
+ function normalizePowerShellDotSourceSpecifier(raw) {
2461
+ const unquoted = raw.replace(/^['"]+|['"]+$/g, "").trim();
2462
+ return unquoted.replace(/^\$PSScriptRoot(?:[\\/]+|$)/i, "./");
2463
+ }
2450
2464
  function parsePowerShellImport(commandNode) {
2451
2465
  const commandName = commandNode.descendantsOfType(["command_name", "command_name_expr"]).find((item) => item !== null)?.text.trim();
2452
2466
  const genericTokens = commandNode.descendantsOfType("generic_token").filter((item) => item !== null).map((item) => item.text.trim());
2453
2467
  if (commandNode.namedChildren.some((child) => child?.type === "command_invokation_operator")) {
2454
- const specifier = commandName?.trim();
2455
- if (specifier) {
2468
+ const raw = commandName?.trim();
2469
+ if (raw) {
2456
2470
  return {
2457
- specifier,
2471
+ specifier: normalizePowerShellDotSourceSpecifier(raw),
2458
2472
  importedSymbols: [],
2459
2473
  isExternal: false,
2460
2474
  reExport: false
@@ -2935,6 +2949,21 @@ function rustCodeAnalysis(manifest, rootNode, diagnostics) {
2935
2949
  imports.push(parseRustUse(child.text));
2936
2950
  continue;
2937
2951
  }
2952
+ if (child.type === "mod_item") {
2953
+ const hasInlineBody = child.namedChildren.some((item) => item?.type === "declaration_list");
2954
+ if (!hasInlineBody) {
2955
+ const modName = extractIdentifier(child.childForFieldName("name") ?? findNamedChild(child, "identifier"));
2956
+ if (modName) {
2957
+ imports.push({
2958
+ specifier: `self::${modName}`,
2959
+ importedSymbols: [],
2960
+ isExternal: false,
2961
+ reExport: false
2962
+ });
2963
+ }
2964
+ }
2965
+ continue;
2966
+ }
2938
2967
  const name = child.type === "function_item" ? extractIdentifier(child.childForFieldName("name")) : extractIdentifier(child.childForFieldName("name"));
2939
2968
  if (child.type === "impl_item") {
2940
2969
  const traitName = normalizeSymbolReference(nodeText(child.childForFieldName("trait")));
@@ -3518,8 +3547,9 @@ function rubyCodeAnalysis(manifest, rootNode, diagnostics) {
3518
3547
  if (callee === "require" || callee === "require_relative") {
3519
3548
  const specifier = rubyStringContent(child.childForFieldName("arguments") ?? child.namedChildren.at(1) ?? null);
3520
3549
  if (specifier) {
3550
+ const normalizedSpecifier = callee === "require_relative" && !specifier.startsWith(".") && !specifier.startsWith("/") ? `./${specifier}` : specifier;
3521
3551
  imports.push({
3522
- specifier,
3552
+ specifier: normalizedSpecifier,
3523
3553
  importedSymbols: [],
3524
3554
  isExternal: callee === "require" && !specifier.startsWith("."),
3525
3555
  reExport: false
@@ -3766,7 +3796,7 @@ async function analyzeTreeSitterCode(manifest, content, language) {
3766
3796
  };
3767
3797
  }
3768
3798
  try {
3769
- const diagnostics = diagnosticsFromTree(tree.rootNode);
3799
+ const diagnostics = language === "lua" ? [] : diagnosticsFromTree(tree.rootNode);
3770
3800
  const rationales = extractTreeSitterRationales(manifest, language, tree.rootNode);
3771
3801
  switch (language) {
3772
3802
  case "bash":
@@ -4509,7 +4539,13 @@ function modulePageTitle(manifest) {
4509
4539
  }
4510
4540
  function importResolutionCandidates(basePath, specifier, extensions) {
4511
4541
  const resolved = path6.posix.normalize(path6.posix.join(path6.posix.dirname(basePath), specifier));
4512
- if (path6.posix.extname(resolved)) {
4542
+ const resolvedExt = path6.posix.extname(resolved);
4543
+ if (resolvedExt) {
4544
+ if (extensions.includes(resolvedExt)) {
4545
+ const resolvedBase = resolved.slice(0, -resolvedExt.length);
4546
+ const candidates = [resolved, ...extensions.map((extension) => `${resolvedBase}${extension}`)];
4547
+ return uniqueBy(candidates, (candidate) => candidate);
4548
+ }
4513
4549
  return [resolved];
4514
4550
  }
4515
4551
  const direct = extensions.map((extension) => path6.posix.normalize(`${resolved}${extension}`));
@@ -4604,7 +4640,9 @@ async function readNearestGoModulePath(startPath, cache) {
4604
4640
  }
4605
4641
  function rustModuleAlias(repoRelativePath) {
4606
4642
  const withoutExt = stripCodeExtension2(normalizeAlias(repoRelativePath));
4607
- const trimmed = withoutExt.replace(/^src\//, "").replace(/\/mod$/i, "");
4643
+ const srcIdx = withoutExt.lastIndexOf("/src/");
4644
+ const withinCrate = srcIdx >= 0 ? withoutExt.slice(srcIdx + "/src/".length) : withoutExt.replace(/^src\//, "");
4645
+ const trimmed = withinCrate.replace(/\/mod$/i, "");
4608
4646
  if (!trimmed || trimmed === "lib" || trimmed === "main") {
4609
4647
  return "crate";
4610
4648
  }
@@ -4819,7 +4857,7 @@ function resolveRustAliases(manifest, specifier) {
4819
4857
  if (!currentAlias) {
4820
4858
  return [];
4821
4859
  }
4822
- const currentParts = currentAlias.replace(/^crate::?/, "").split("::").filter(Boolean);
4860
+ const currentParts = currentAlias.replace(/^crate(?:::)?/, "").split("::").filter(Boolean);
4823
4861
  if (specifier.startsWith("self::")) {
4824
4862
  return [`crate${currentParts.length ? `::${currentParts.join("::")}` : ""}::${specifier.slice("self::".length)}`];
4825
4863
  }
@@ -6610,7 +6648,7 @@ function inferKind(mimeType, filePath, detectionOptions = {}) {
6610
6648
  if (mimeType === "text/csv" || mimeType === "text/tab-separated-values" || filePath.toLowerCase().endsWith(".csv") || filePath.toLowerCase().endsWith(".tsv")) {
6611
6649
  return "csv";
6612
6650
  }
6613
- if (mimeType.startsWith("text/") || isStructuredTextMime(mimeType) || isKnownTextPath(filePath)) {
6651
+ if (mimeType.startsWith("text/") || isStructuredTextMime(mimeType)) {
6614
6652
  return "text";
6615
6653
  }
6616
6654
  if (mimeType === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || filePath.toLowerCase().endsWith(".xlsx")) {
@@ -6696,125 +6734,30 @@ function guessMimeType(target) {
6696
6734
  if (extension === ".ts" || extension === ".tsx" || extension === ".mts" || extension === ".cts") {
6697
6735
  return "text/typescript";
6698
6736
  }
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;
6737
+ return mime.lookup(target) || "application/octet-stream";
6738
+ }
6739
+ function refineBinaryKindWithBytes(absolutePath, currentKind, bytes) {
6740
+ if (currentKind !== "binary") {
6741
+ return currentKind;
6807
6742
  }
6808
- for (const prefix of KNOWN_TEXT_BASENAME_PREFIXES) {
6809
- if (basename === prefix || basename.startsWith(`${prefix}-`) || basename.startsWith(`${prefix}.`)) {
6810
- return true;
6811
- }
6743
+ const sniffSlice = bytes.length > 4096 ? bytes.subarray(0, 4096) : bytes;
6744
+ return isText(absolutePath, sniffSlice) ? "text" : currentKind;
6745
+ }
6746
+ async function refineBinaryKindWithContentSniff(absolutePath, currentKind) {
6747
+ if (currentKind !== "binary") {
6748
+ return currentKind;
6812
6749
  }
6813
- const extension = path12.extname(target).toLowerCase();
6814
- if (extension && KNOWN_TEXT_EXTENSIONS.has(extension)) {
6815
- return true;
6750
+ let handle;
6751
+ try {
6752
+ handle = await fs11.open(absolutePath, "r");
6753
+ const chunk = Buffer.alloc(4096);
6754
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, 0);
6755
+ return refineBinaryKindWithBytes(absolutePath, currentKind, chunk.subarray(0, bytesRead));
6756
+ } catch {
6757
+ return currentKind;
6758
+ } finally {
6759
+ await handle?.close().catch(() => void 0);
6816
6760
  }
6817
- return false;
6818
6761
  }
6819
6762
  function sourceGroupIdFor(prepared) {
6820
6763
  const originKey = prepared.originType === "url" ? prepared.url ?? prepared.title : prepared.originalPath ?? prepared.title;
@@ -7666,6 +7609,7 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
7666
7609
  sourceKind = "chat_export";
7667
7610
  }
7668
7611
  }
7612
+ sourceKind = await refineBinaryKindWithContentSniff(absolutePath, sourceKind);
7669
7613
  const sourceClass = sourceClassForRelativePath(relativePath, options);
7670
7614
  if (!supportedDirectoryKind(sourceKind)) {
7671
7615
  skipped.push({ path: toPosix(path12.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
@@ -8287,7 +8231,7 @@ async function prepareFileInputs(rootDir, absoluteInput, repoRoot, sourceClass)
8287
8231
  }
8288
8232
  const mimeType = guessMimeType(absoluteInput);
8289
8233
  const detectionOptions = await localCodeDetectionOptions(absoluteInput, payloadBytes);
8290
- const sourceKind = inferKind(mimeType, absoluteInput, detectionOptions);
8234
+ const sourceKind = refineBinaryKindWithBytes(absoluteInput, inferKind(mimeType, absoluteInput, detectionOptions), payloadBytes);
8291
8235
  const language = inferCodeLanguage(absoluteInput, mimeType, detectionOptions);
8292
8236
  const storedExtension = path12.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
8293
8237
  let title;
@@ -9071,6 +9015,7 @@ async function importInbox(rootDir, inputDir) {
9071
9015
  sourceKind = "chat_export";
9072
9016
  }
9073
9017
  }
9018
+ sourceKind = await refineBinaryKindWithContentSniff(absolutePath, sourceKind);
9074
9019
  if (!isSupportedInboxKind(sourceKind)) {
9075
9020
  skipped.push({ path: toPosix(path12.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
9076
9021
  continue;
@@ -9268,6 +9213,7 @@ import { z as z7 } from "zod";
9268
9213
 
9269
9214
  // src/analysis.ts
9270
9215
  import path14 from "path";
9216
+ import nlp from "compromise";
9271
9217
  import { fromMarkdown } from "mdast-util-from-markdown";
9272
9218
  import { z as z2 } from "zod";
9273
9219
  var ANALYSIS_FORMAT_VERSION = 7;
@@ -9345,11 +9291,31 @@ function extractTopTerms(text, count) {
9345
9291
  return [...frequency.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).slice(0, count).map(([token]) => token);
9346
9292
  }
9347
9293
  function extractEntities(text, count) {
9348
- const matches = text.match(/\b[A-Z][A-Za-z0-9-]+(?:\s+[A-Z][A-Za-z0-9-]+){0,2}\b/g) ?? [];
9349
- return uniqueBy(
9350
- matches.map((value) => normalizeWhitespace(value)),
9351
- (value) => value.toLowerCase()
9352
- ).slice(0, count);
9294
+ const candidates = [];
9295
+ try {
9296
+ const doc = nlp(text);
9297
+ const segments = [
9298
+ doc.match("#ProperNoun+").out("array"),
9299
+ doc.people().out("array"),
9300
+ doc.places().out("array"),
9301
+ doc.organizations().out("array"),
9302
+ doc.topics().out("array")
9303
+ ];
9304
+ for (const segment of segments) {
9305
+ for (const term of segment) {
9306
+ const normalized = normalizeWhitespace(term);
9307
+ if (normalized) {
9308
+ candidates.push(normalized);
9309
+ }
9310
+ }
9311
+ }
9312
+ } catch {
9313
+ }
9314
+ if (candidates.length === 0) {
9315
+ const matches = text.match(/\b[A-Z][A-Za-z0-9-]+(?:\s+[A-Z][A-Za-z0-9-]+){0,2}\b/g) ?? [];
9316
+ candidates.push(...matches.map((value) => normalizeWhitespace(value)));
9317
+ }
9318
+ return uniqueBy(candidates, (value) => value.toLowerCase()).slice(0, count);
9353
9319
  }
9354
9320
  function detectPolarity(text) {
9355
9321
  if (/\b(no|not|never|cannot|can't|won't|without)\b/i.test(text)) {
@@ -11068,15 +11034,37 @@ function graphAdjacency(graph) {
11068
11034
  }
11069
11035
  return adjacency;
11070
11036
  }
11037
+ var NODE_TYPE_PRIORITY = {
11038
+ concept: 6,
11039
+ entity: 5,
11040
+ source: 4,
11041
+ module: 3,
11042
+ symbol: 2,
11043
+ rationale: 1
11044
+ };
11045
+ function nodeTypePriority(type) {
11046
+ return NODE_TYPE_PRIORITY[type] ?? 0;
11047
+ }
11048
+ function compareLabelCandidates(left, right) {
11049
+ const priorityDelta = nodeTypePriority(right.type) - nodeTypePriority(left.type);
11050
+ if (priorityDelta !== 0) {
11051
+ return priorityDelta;
11052
+ }
11053
+ const degreeDelta = (right.degree ?? 0) - (left.degree ?? 0);
11054
+ if (degreeDelta !== 0) {
11055
+ return degreeDelta;
11056
+ }
11057
+ return left.id.localeCompare(right.id);
11058
+ }
11071
11059
  function resolveNode(graph, target) {
11072
11060
  const normalized = normalizeTarget(target);
11073
11061
  const byId = nodeById(graph);
11074
11062
  if (byId.has(target)) {
11075
11063
  return byId.get(target);
11076
11064
  }
11077
- const exact = graph.nodes.find((node) => normalizeTarget(node.label) === normalized || normalizeTarget(node.id) === normalized);
11078
- if (exact) {
11079
- return exact;
11065
+ const labelMatches = graph.nodes.filter((node) => normalizeTarget(node.label) === normalized || normalizeTarget(node.id) === normalized);
11066
+ if (labelMatches.length) {
11067
+ return labelMatches.sort(compareLabelCandidates)[0];
11080
11068
  }
11081
11069
  const pages = graph.pages.map((page) => ({
11082
11070
  page,
@@ -11085,7 +11073,7 @@ function resolveNode(graph, target) {
11085
11073
  if (pages[0]) {
11086
11074
  return primaryNodeForPage(graph, pages[0].page);
11087
11075
  }
11088
- return graph.nodes.map((node) => ({ node, score: Math.max(scoreMatch(target, node.label), scoreMatch(target, node.id)) })).filter((item) => item.score > 0).sort((left, right) => right.score - left.score || left.node.label.localeCompare(right.node.label))[0]?.node;
11076
+ return graph.nodes.map((node) => ({ node, score: Math.max(scoreMatch(target, node.label), scoreMatch(target, node.id)) })).filter((item) => item.score > 0).sort((left, right) => right.score - left.score || compareLabelCandidates(left.node, right.node))[0]?.node;
11089
11077
  }
11090
11078
  function communityLabel(graph, communityId) {
11091
11079
  if (!communityId) {
@@ -17860,6 +17848,10 @@ function extractClaimSectionLines(content) {
17860
17848
  }
17861
17849
  return found ? claimLines : null;
17862
17850
  }
17851
+ function isClaimPlaceholderBullet(line) {
17852
+ const trimmed = line.trim();
17853
+ return /^-\s+No\s+claims\s+extracted\.?$/i.test(trimmed);
17854
+ }
17863
17855
  function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sourceProjects) {
17864
17856
  const manifestMap = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
17865
17857
  const pageMap2 = new Map(graph.pages.map((page) => [page.id, page]));
@@ -17907,7 +17899,9 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
17907
17899
  const content = await fs19.readFile(absolutePath, "utf8");
17908
17900
  const claimLines = extractClaimSectionLines(content);
17909
17901
  if (claimLines !== null) {
17910
- const uncited = claimLines.filter((line) => line.startsWith("- ") && !line.includes("[source:"));
17902
+ const uncited = claimLines.filter(
17903
+ (line) => line.startsWith("- ") && !line.includes("[source:") && !isClaimPlaceholderBullet(line)
17904
+ );
17911
17905
  if (uncited.length) {
17912
17906
  findings.push({
17913
17907
  severity: "warning",
@@ -18022,7 +18016,7 @@ async function bootstrapDemo(rootDir, input) {
18022
18016
  }
18023
18017
 
18024
18018
  // src/mcp.ts
18025
- var SERVER_VERSION = "0.6.6";
18019
+ var SERVER_VERSION = "0.6.7";
18026
18020
  async function createMcpServer(rootDir) {
18027
18021
  const server = new McpServer({
18028
18022
  name: "swarmvault",
@@ -21115,14 +21109,9 @@ async function startGraphServer(rootDir, port, options = {}) {
21115
21109
  response.end(JSON.stringify({ error: "Missing explain target." }));
21116
21110
  return;
21117
21111
  }
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
- }
21112
+ const result = await explainGraphVault(rootDir, target2);
21113
+ response.writeHead(200, { "content-type": "application/json" });
21114
+ response.end(JSON.stringify(result));
21126
21115
  return;
21127
21116
  }
21128
21117
  if (url.pathname === "/api/search") {
@@ -21261,7 +21250,8 @@ async function startGraphServer(rootDir, port, options = {}) {
21261
21250
  const message = error instanceof Error ? error.message : String(error);
21262
21251
  console.error(`[viewer] ${request.method ?? "GET"} ${url.pathname} failed: ${message}`);
21263
21252
  if (!response.headersSent) {
21264
- response.writeHead(500, { "content-type": "application/json" });
21253
+ const status = /not found|could not resolve|cannot resolve/i.test(message) ? 404 : 500;
21254
+ response.writeHead(status, { "content-type": "application/json" });
21265
21255
  response.end(JSON.stringify({ error: message }));
21266
21256
  } else {
21267
21257
  response.end();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/engine",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
4
4
  "description": "Core engine for SwarmVault: ingest, compile, query, lint, and provider abstractions.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -44,10 +44,12 @@
44
44
  "@mozilla/readability": "^0.6.0",
45
45
  "@vscode/tree-sitter-wasm": "^0.3.1",
46
46
  "chokidar": "^4.0.3",
47
+ "compromise": "^14.14.4",
47
48
  "csv-parse": "^6.2.1",
48
49
  "fflate": "^0.8.2",
49
50
  "gray-matter": "^4.0.3",
50
51
  "ignore": "^7.0.5",
52
+ "istextorbinary": "^9.5.0",
51
53
  "jsdom": "^27.0.0",
52
54
  "mailparser": "^3.9.8",
53
55
  "mammoth": "^1.12.0",