@swarmvaultai/engine 0.7.31 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ PRIMARY_SCHEMA_FILENAME,
2
3
  appendJsonLine,
3
4
  assertProviderCapability,
4
5
  createProvider,
@@ -22,7 +23,7 @@ import {
22
23
  uniqueBy,
23
24
  writeFileIfChanged,
24
25
  writeJsonFile
25
- } from "./chunk-NECZ4MUE.js";
26
+ } from "./chunk-RSQRF4FV.js";
26
27
  import {
27
28
  estimatePageTokens,
28
29
  estimateTokens,
@@ -32,6 +33,7 @@ import {
32
33
  // src/agents.ts
33
34
  import crypto from "crypto";
34
35
  import fs from "fs/promises";
36
+ import os from "os";
35
37
  import path from "path";
36
38
  import { fileURLToPath } from "url";
37
39
  import YAML from "yaml";
@@ -83,23 +85,128 @@ var agentFileKinds = {
83
85
  copilot: ".github/copilot-instructions.md",
84
86
  trae: ".trae/rules/swarmvault.md",
85
87
  claw: ".claw/skills/swarmvault/SKILL.md",
86
- droid: ".factory/rules/swarmvault.md"
88
+ droid: ".factory/rules/swarmvault.md",
89
+ kiro: ".kiro/skills/swarmvault/SKILL.md",
90
+ kiroSteering: ".kiro/steering/swarmvault.md",
91
+ antigravityRules: ".agent/rules/swarmvault.md",
92
+ antigravityWorkflow: ".agent/workflows/swarmvault.md",
93
+ vscode: ".github/chatmodes/swarmvault.chatmode.md"
87
94
  };
95
+ var hermesUserSkillRelative = path.join(".hermes", "skills", "swarmvault", "SKILL.md");
96
+ function hermesUserSkillPath() {
97
+ return path.join(os.homedir(), hermesUserSkillRelative);
98
+ }
99
+ var SWARMVAULT_RULE_BULLETS = [
100
+ "- Read `swarmvault.schema.md` before compile or query style work. It is the canonical schema path.",
101
+ "- Treat `raw/` as immutable source input.",
102
+ "- Treat `wiki/` as generated markdown owned by the agent and compiler workflow.",
103
+ "- Read `wiki/graph/report.md` before broad file searching when it exists; otherwise start with `wiki/index.md`.",
104
+ "- For graph questions, prefer `swarmvault graph query`, `swarmvault graph path`, and `swarmvault graph explain` before broad grep/glob searching.",
105
+ "- Preserve frontmatter fields including `page_id`, `source_ids`, `node_ids`, `freshness`, and `source_hashes`.",
106
+ "- Save high-value answers back into `wiki/outputs/` instead of leaving them only in chat.",
107
+ "- Prefer `swarmvault ingest`, `swarmvault compile`, `swarmvault query`, and `swarmvault lint` for SwarmVault maintenance tasks."
108
+ ];
88
109
  function buildManagedBlock(target) {
89
110
  const heading = target === "aider" ? "# SwarmVault Conventions" : target === "copilot" ? "# SwarmVault Repository Instructions" : "# SwarmVault Rules";
111
+ return [managedStart, heading, "", ...SWARMVAULT_RULE_BULLETS, managedEnd, ""].join("\n");
112
+ }
113
+ function buildSkillFrontmatter() {
114
+ const frontmatter = YAML.stringify({
115
+ name: "swarmvault",
116
+ description: "SwarmVault graph-first workflow. Use to read the compiled wiki and query the knowledge graph before broad file search."
117
+ }).trimEnd();
118
+ return ["---", frontmatter, "---"].join("\n");
119
+ }
120
+ function buildSkillBody() {
121
+ return [
122
+ "# SwarmVault",
123
+ "",
124
+ "SwarmVault compiles curated sources in `raw/` into a queryable wiki in `wiki/` and a knowledge graph in `state/graph.json`.",
125
+ "",
126
+ "## Rules",
127
+ "",
128
+ ...SWARMVAULT_RULE_BULLETS,
129
+ "",
130
+ "## Entry points",
131
+ "",
132
+ "- `swarmvault ingest <path>` \u2014 register a new source",
133
+ "- `swarmvault compile` \u2014 refresh wiki pages and graph",
134
+ '- `swarmvault query "<question>"` \u2014 save-first multi-step query',
135
+ "- `swarmvault graph query|path|explain` \u2014 deterministic graph traversal",
136
+ "- `swarmvault lint` \u2014 wiki health and contradiction checks",
137
+ ""
138
+ ].join("\n");
139
+ }
140
+ function buildStandaloneSkillFile() {
141
+ return `${buildSkillFrontmatter()}
142
+
143
+ ${buildSkillBody()}`;
144
+ }
145
+ function buildKiroSteeringFile() {
146
+ const frontmatter = YAML.stringify({
147
+ inclusion: "always",
148
+ description: "Always-on SwarmVault rules."
149
+ }).trimEnd();
150
+ return ["---", frontmatter, "---", "", "# SwarmVault Rules", "", ...SWARMVAULT_RULE_BULLETS, ""].join("\n");
151
+ }
152
+ function buildAntigravityRulesFile() {
153
+ const frontmatter = YAML.stringify({
154
+ alwaysApply: true,
155
+ description: "SwarmVault graph-first repository rules."
156
+ }).trimEnd();
157
+ return [
158
+ "---",
159
+ frontmatter,
160
+ "---",
161
+ "",
162
+ "# SwarmVault Rules",
163
+ "",
164
+ ...SWARMVAULT_RULE_BULLETS,
165
+ "",
166
+ "> MCP navigation hint: SwarmVault exposes a local MCP server via `swarmvault mcp`. Wire it into your Antigravity MCP config to query the graph without shelling out."
167
+ ].join("\n");
168
+ }
169
+ function buildAntigravityWorkflowFile() {
170
+ const frontmatter = YAML.stringify({
171
+ command: "swarmvault",
172
+ description: "Compile, query, and lint the SwarmVault vault."
173
+ }).trimEnd();
90
174
  return [
91
- managedStart,
92
- heading,
175
+ "---",
176
+ frontmatter,
177
+ "---",
178
+ "",
179
+ "# /swarmvault",
180
+ "",
181
+ "Run SwarmVault against the current directory.",
182
+ "",
183
+ "## Steps",
184
+ "",
185
+ "1. If no vault exists, run `swarmvault init`.",
186
+ "2. For new sources, run `swarmvault ingest <path>`.",
187
+ "3. Run `swarmvault compile` to refresh the wiki and graph.",
188
+ "4. For follow-up questions, prefer `swarmvault query`, `swarmvault graph query`, `swarmvault graph path`, `swarmvault graph explain`.",
189
+ "5. Save high-value answers to `wiki/outputs/`.",
190
+ ""
191
+ ].join("\n");
192
+ }
193
+ function buildVscodeChatmodeFile() {
194
+ const frontmatter = YAML.stringify({
195
+ description: "SwarmVault graph-first workflow for VS Code Copilot Chat.",
196
+ tools: ["codebase", "terminal"]
197
+ }).trimEnd();
198
+ return [
199
+ "---",
200
+ frontmatter,
201
+ "---",
202
+ "",
203
+ "# SwarmVault mode",
204
+ "",
205
+ "You are working inside a SwarmVault vault. Follow these rules before other actions:",
206
+ "",
207
+ ...SWARMVAULT_RULE_BULLETS,
93
208
  "",
94
- "- Read `swarmvault.schema.md` before compile or query style work. It is the canonical schema path.",
95
- "- Treat `raw/` as immutable source input.",
96
- "- Treat `wiki/` as generated markdown owned by the agent and compiler workflow.",
97
- "- Read `wiki/graph/report.md` before broad file searching when it exists; otherwise start with `wiki/index.md`.",
98
- "- For graph questions, prefer `swarmvault graph query`, `swarmvault graph path`, and `swarmvault graph explain` before broad grep/glob searching.",
99
- "- Preserve frontmatter fields including `page_id`, `source_ids`, `node_ids`, `freshness`, and `source_hashes`.",
100
- "- Save high-value answers back into `wiki/outputs/` instead of leaving them only in chat.",
101
- "- Prefer `swarmvault ingest`, `swarmvault compile`, `swarmvault query`, and `swarmvault lint` for SwarmVault maintenance tasks.",
102
- managedEnd,
209
+ "Use the terminal tool to run `swarmvault` commands. Prefer graph queries over broad grep/glob.",
103
210
  ""
104
211
  ].join("\n");
105
212
  }
@@ -136,6 +243,14 @@ function primaryTargetPathForAgent(rootDir, agent) {
136
243
  return path.join(rootDir, agentFileKinds.claw);
137
244
  case "droid":
138
245
  return path.join(rootDir, agentFileKinds.droid);
246
+ case "kiro":
247
+ return path.join(rootDir, agentFileKinds.kiro);
248
+ case "hermes":
249
+ return hermesUserSkillPath();
250
+ case "antigravity":
251
+ return path.join(rootDir, agentFileKinds.antigravityRules);
252
+ case "vscode":
253
+ return path.join(rootDir, agentFileKinds.vscode);
139
254
  default:
140
255
  throw new Error(`Unsupported agent ${String(agent)}`);
141
256
  }
@@ -174,6 +289,15 @@ function targetsForAgent(rootDir, agent, options = {}) {
174
289
  if (agent === "aider") {
175
290
  targets.push(path.join(rootDir, ".aider.conf.yml"));
176
291
  }
292
+ if (agent === "kiro") {
293
+ targets.push(path.join(rootDir, agentFileKinds.kiroSteering));
294
+ }
295
+ if (agent === "hermes") {
296
+ targets.push(path.join(rootDir, agentFileKinds.agents));
297
+ }
298
+ if (agent === "antigravity") {
299
+ targets.push(path.join(rootDir, agentFileKinds.antigravityWorkflow));
300
+ }
177
301
  if (options.hook && supportsAgentHook(agent)) {
178
302
  const configPath = hookConfigPathForAgent(rootDir, agent);
179
303
  const scriptPath = hookScriptPathForAgent(rootDir, agent);
@@ -409,6 +533,21 @@ async function installAgent(rootDir, agent, options = {}) {
409
533
  case "droid":
410
534
  await writeOwnedFile(target, buildManagedBlock("droid"));
411
535
  break;
536
+ case "kiro":
537
+ await writeOwnedFile(target, buildStandaloneSkillFile());
538
+ await writeOwnedFile(path.join(rootDir, agentFileKinds.kiroSteering), buildKiroSteeringFile());
539
+ break;
540
+ case "hermes":
541
+ await upsertManagedBlock(path.join(rootDir, agentFileKinds.agents), buildManagedBlock("agents"));
542
+ await writeOwnedFile(hermesUserSkillPath(), buildStandaloneSkillFile());
543
+ break;
544
+ case "antigravity":
545
+ await writeOwnedFile(target, buildAntigravityRulesFile());
546
+ await writeOwnedFile(path.join(rootDir, agentFileKinds.antigravityWorkflow), buildAntigravityWorkflowFile());
547
+ break;
548
+ case "vscode":
549
+ await writeOwnedFile(target, buildVscodeChatmodeFile());
550
+ break;
412
551
  default:
413
552
  throw new Error(`Unsupported agent ${String(agent)}`);
414
553
  }
@@ -2624,7 +2763,10 @@ async function pushGraphNeo4j(rootDir, options = {}) {
2624
2763
 
2625
2764
  // src/graph-tools.ts
2626
2765
  function normalizeTarget(value) {
2627
- return normalizeWhitespace(value).toLowerCase();
2766
+ return normalizeWhitespace(value).normalize("NFKD").replace(/\p{Mn}+/gu, "").toLowerCase();
2767
+ }
2768
+ function computeNormLabel(label) {
2769
+ return normalizeTarget(label);
2628
2770
  }
2629
2771
  function nodeById(graph) {
2630
2772
  return new Map(graph.nodes.map((node) => [node.id, node]));
@@ -8567,7 +8709,7 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
8567
8709
 
8568
8710
  // src/extraction.ts
8569
8711
  import fs7 from "fs/promises";
8570
- import os from "os";
8712
+ import os2 from "os";
8571
8713
  import path7 from "path";
8572
8714
  import { Readable } from "stream";
8573
8715
  import { parse as parseCsvSync } from "csv-parse/sync";
@@ -8691,7 +8833,7 @@ async function materializeAttachmentPath(input) {
8691
8833
  if (!input.bytes) {
8692
8834
  throw new Error("Image extraction requires a file path or bytes.");
8693
8835
  }
8694
- const tempDir = await fs7.mkdtemp(path7.join(os.tmpdir(), "swarmvault-image-extract-"));
8836
+ const tempDir = await fs7.mkdtemp(path7.join(os2.tmpdir(), "swarmvault-image-extract-"));
8695
8837
  const extension = input.mimeType.split("/")[1]?.split("+")[0] ?? "bin";
8696
8838
  const tempPath = path7.join(tempDir, `source.${extension}`);
8697
8839
  await fs7.writeFile(tempPath, input.bytes);
@@ -8773,6 +8915,27 @@ async function extractImageWithVision(rootDir, input) {
8773
8915
  await attachment.cleanup();
8774
8916
  }
8775
8917
  }
8918
+ async function buildCorpusHint(rootDir, maxTerms = 6) {
8919
+ let graphPath;
8920
+ try {
8921
+ const { paths } = await loadVaultConfig(rootDir);
8922
+ graphPath = paths.graphPath;
8923
+ } catch {
8924
+ return void 0;
8925
+ }
8926
+ if (!await fileExists(graphPath)) {
8927
+ return void 0;
8928
+ }
8929
+ const graph = await readJsonFile(graphPath);
8930
+ if (!graph || !Array.isArray(graph.nodes) || graph.nodes.length === 0) {
8931
+ return void 0;
8932
+ }
8933
+ const top = graph.nodes.filter((node) => node.type !== "source" && Boolean(node.label)).sort((left, right) => (right.degree ?? 0) - (left.degree ?? 0)).slice(0, maxTerms).map((node) => node.label.trim()).filter((label) => label.length > 0);
8934
+ if (top.length === 0) {
8935
+ return void 0;
8936
+ }
8937
+ return `This audio is likely about ${top.join(", ")}.`;
8938
+ }
8776
8939
  async function extractAudioTranscription(rootDir, input) {
8777
8940
  let provider;
8778
8941
  try {
@@ -8793,11 +8956,13 @@ async function extractAudioTranscription(rootDir, input) {
8793
8956
  }
8794
8957
  };
8795
8958
  }
8959
+ const corpusHint = input.corpusHint ?? await buildCorpusHint(rootDir);
8796
8960
  try {
8797
8961
  const result = await provider.transcribeAudio({
8798
8962
  mimeType: input.mimeType,
8799
8963
  bytes: input.bytes,
8800
- fileName: input.fileName
8964
+ fileName: input.fileName,
8965
+ corpusHint
8801
8966
  });
8802
8967
  const metadata = {};
8803
8968
  if (result.duration !== void 0) {
@@ -8806,6 +8971,9 @@ async function extractAudioTranscription(rootDir, input) {
8806
8971
  if (result.language) {
8807
8972
  metadata.language = result.language;
8808
8973
  }
8974
+ if (corpusHint) {
8975
+ metadata.corpus_hint = corpusHint;
8976
+ }
8809
8977
  return {
8810
8978
  extractedText: result.text || void 0,
8811
8979
  artifact: {
@@ -18221,7 +18389,7 @@ async function resolveImageGenerationProvider(rootDir) {
18221
18389
  if (!providerConfig) {
18222
18390
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
18223
18391
  }
18224
- const { createProvider: createProvider2 } = await import("./registry-4C55ZCPL.js");
18392
+ const { createProvider: createProvider2 } = await import("./registry-JR5WY22P.js");
18225
18393
  return createProvider2(preferredProviderId, providerConfig, rootDir);
18226
18394
  }
18227
18395
  async function generateOutputArtifacts(rootDir, input) {
@@ -19255,6 +19423,13 @@ function autoResolution(nodeCount, edgeCount) {
19255
19423
  if (edgeCount / Math.max(1, nodeCount) < 2) return 0.8;
19256
19424
  return 1;
19257
19425
  }
19426
+ function pruneDanglingEdges(nodes, edges) {
19427
+ const nodeIds = new Set(nodes.map((node) => node.id));
19428
+ return edges.filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target));
19429
+ }
19430
+ function applyNormLabel(nodes) {
19431
+ return nodes.map((node) => node.normLabel ? node : { ...node, normLabel: computeNormLabel(node.label) });
19432
+ }
19258
19433
  function deriveGraphMetrics(nodes, edges, options) {
19259
19434
  const adjacency = /* @__PURE__ */ new Map();
19260
19435
  const connect = (left, right) => {
@@ -19672,20 +19847,33 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex, opti
19672
19847
  }
19673
19848
  for (const symbol of analysis.code.symbols) {
19674
19849
  for (const targetName of symbol.calls) {
19675
- const targetId = resolveLocalSymbolId(targetName);
19676
- if (!targetId || targetId === symbol.id) {
19850
+ const localTargetId = resolveLocalSymbolId(targetName);
19851
+ if (localTargetId && localTargetId !== symbol.id) {
19852
+ pushEdge({
19853
+ id: `${symbol.id}->${localTargetId}:calls`,
19854
+ source: symbol.id,
19855
+ target: localTargetId,
19856
+ relation: "calls",
19857
+ status: "extracted",
19858
+ evidenceClass: "extracted",
19859
+ confidence: 1,
19860
+ provenance: [analysis.sourceId]
19861
+ });
19677
19862
  continue;
19678
19863
  }
19679
- pushEdge({
19680
- id: `${symbol.id}->${targetId}:calls`,
19681
- source: symbol.id,
19682
- target: targetId,
19683
- relation: "calls",
19684
- status: "extracted",
19685
- evidenceClass: "extracted",
19686
- confidence: 1,
19687
- provenance: [analysis.sourceId]
19688
- });
19864
+ const crossFileTargetId = importedSymbolIdsByName.get(targetName);
19865
+ if (crossFileTargetId && crossFileTargetId !== symbol.id) {
19866
+ pushEdge({
19867
+ id: `${symbol.id}->${crossFileTargetId}:calls`,
19868
+ source: symbol.id,
19869
+ target: crossFileTargetId,
19870
+ relation: "calls",
19871
+ status: "inferred",
19872
+ evidenceClass: "inferred",
19873
+ confidence: 0.8,
19874
+ provenance: [analysis.sourceId]
19875
+ });
19876
+ }
19689
19877
  }
19690
19878
  for (const targetName of symbol.extends) {
19691
19879
  const targetId = resolveLocalSymbolId(targetName) ?? importedSymbolIdsByName.get(targetName);
@@ -19800,11 +19988,17 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex, opti
19800
19988
  analyses
19801
19989
  );
19802
19990
  const metrics = deriveGraphMetrics(graphNodes, enriched.edges, { resolution: options?.communityResolution });
19991
+ const finalNodes = applyNormLabel(metrics.nodes);
19992
+ const finalEdges = pruneDanglingEdges(finalNodes, enriched.edges);
19993
+ const finalHyperedges = (enriched.hyperedges ?? []).filter((hyperedge) => {
19994
+ const nodeIdSet = new Set(finalNodes.map((node) => node.id));
19995
+ return hyperedge.nodeIds.every((id) => nodeIdSet.has(id));
19996
+ });
19803
19997
  return {
19804
19998
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
19805
- nodes: metrics.nodes,
19806
- edges: enriched.edges,
19807
- hyperedges: enriched.hyperedges,
19999
+ nodes: finalNodes,
20000
+ edges: finalEdges,
20001
+ hyperedges: finalHyperedges,
19808
20002
  communities: metrics.communities,
19809
20003
  sources: manifests,
19810
20004
  pages
@@ -21344,18 +21538,52 @@ async function rejectApproval(rootDir, approvalId, targets = []) {
21344
21538
  }
21345
21539
  async function listCandidates(rootDir) {
21346
21540
  const pages = await listPages(rootDir);
21347
- return pages.filter(
21541
+ const candidates = pages.filter(
21348
21542
  (page) => page.status === "candidate" && (page.kind === "concept" || page.kind === "entity")
21349
- ).map((page) => ({
21350
- pageId: page.id,
21351
- title: page.title,
21352
- kind: page.kind,
21353
- path: page.path,
21354
- activePath: candidateActivePath(page),
21355
- sourceIds: page.sourceIds,
21356
- createdAt: page.createdAt,
21357
- updatedAt: page.updatedAt
21358
- })).sort((left, right) => left.title.localeCompare(right.title));
21543
+ );
21544
+ let scoreLookup = null;
21545
+ try {
21546
+ const { config, paths } = await loadVaultConfig(rootDir);
21547
+ const promotionConfig = resolvePromotionConfig(config);
21548
+ const graph = await readJsonFile(paths.graphPath);
21549
+ if (graph) {
21550
+ const compileState = await readJsonFile(paths.compileStatePath) ?? emptyCompileState();
21551
+ const now = Date.now();
21552
+ const decisions = candidates.map(
21553
+ (page) => evaluateCandidateForPromotion(page, graph, compileState.candidateHistory, promotionConfig, now)
21554
+ );
21555
+ scoreLookup = new Map(
21556
+ decisions.map((decision) => [
21557
+ decision.pageId,
21558
+ {
21559
+ score: decision.score,
21560
+ breakdown: Object.fromEntries(decision.gates.map((gate) => [gate.gate, gate.value]))
21561
+ }
21562
+ ])
21563
+ );
21564
+ }
21565
+ } catch {
21566
+ scoreLookup = null;
21567
+ }
21568
+ return candidates.map((page) => {
21569
+ const scored = scoreLookup?.get(page.id);
21570
+ return {
21571
+ pageId: page.id,
21572
+ title: page.title,
21573
+ kind: page.kind,
21574
+ path: page.path,
21575
+ activePath: candidateActivePath(page),
21576
+ sourceIds: page.sourceIds,
21577
+ createdAt: page.createdAt,
21578
+ updatedAt: page.updatedAt,
21579
+ ...scored ? { score: scored.score, scoreBreakdown: scored.breakdown } : {}
21580
+ };
21581
+ }).sort((left, right) => {
21582
+ const ls = left.score ?? -1;
21583
+ const rs = right.score ?? -1;
21584
+ if (ls !== rs) return rs - ls;
21585
+ return left.title.localeCompare(right.title);
21586
+ });
21359
21587
  }
21360
21588
  function resolveCandidateTarget(pages, target) {
21361
21589
  const candidate = pages.find((page) => page.status === "candidate" && (page.id === target || page.path === target));
@@ -21622,7 +21850,101 @@ async function ensureObsidianWorkspace(rootDir) {
21622
21850
  })
21623
21851
  ]);
21624
21852
  }
21853
+ async function initLiteVault(rootDir, options) {
21854
+ const rawDir = path23.join(rootDir, "raw");
21855
+ const wikiDir = path23.join(rootDir, "wiki");
21856
+ const schemaPath = path23.join(rootDir, PRIMARY_SCHEMA_FILENAME);
21857
+ const indexPath = path23.join(wikiDir, "index.md");
21858
+ const logPath = path23.join(wikiDir, "log.md");
21859
+ await Promise.all([ensureDir(rawDir), ensureDir(wikiDir)]);
21860
+ if (!await fileExists(schemaPath)) {
21861
+ await fs19.writeFile(schemaPath, defaultVaultSchema("default"), "utf8");
21862
+ }
21863
+ const now = (/* @__PURE__ */ new Date()).toISOString();
21864
+ if (!await fileExists(indexPath)) {
21865
+ await fs19.writeFile(
21866
+ indexPath,
21867
+ matter10.stringify(
21868
+ [
21869
+ "# Wiki Index",
21870
+ "",
21871
+ "This lite vault is agent-maintained. Drop sources into `raw/`, edit `swarmvault.schema.md` to teach the agent how the wiki should be organized, then ask your agent to read sources and update pages here.",
21872
+ "",
21873
+ "- Summaries, entity pages, and concept pages live under `wiki/`.",
21874
+ "- Append every ingest/query/lint operation to `wiki/log.md`.",
21875
+ "- Run `swarmvault init` (without `--lite`) when you want the full toolchain with graph, search, and approvals.",
21876
+ ""
21877
+ ].join("\n"),
21878
+ {
21879
+ page_id: "wiki:index",
21880
+ kind: "index",
21881
+ title: "Wiki Index",
21882
+ tags: ["index"],
21883
+ source_ids: [],
21884
+ project_ids: [],
21885
+ node_ids: [],
21886
+ freshness: "fresh",
21887
+ status: "active",
21888
+ confidence: 1,
21889
+ created_at: now,
21890
+ updated_at: now,
21891
+ compiled_from: [],
21892
+ managed_by: "agent",
21893
+ backlinks: [],
21894
+ schema_hash: "",
21895
+ source_hashes: {},
21896
+ source_semantic_hashes: {}
21897
+ }
21898
+ ),
21899
+ "utf8"
21900
+ );
21901
+ }
21902
+ if (!await fileExists(logPath)) {
21903
+ await fs19.writeFile(
21904
+ logPath,
21905
+ matter10.stringify(
21906
+ [
21907
+ "# Activity Log",
21908
+ "",
21909
+ "Append-only chronological record. One line per ingest/query/lint operation, newest at the bottom.",
21910
+ "",
21911
+ "Format: `## [YYYY-MM-DD] <verb> | <subject>`",
21912
+ ""
21913
+ ].join("\n"),
21914
+ {
21915
+ page_id: "wiki:log",
21916
+ kind: "index",
21917
+ title: "Activity Log",
21918
+ tags: ["log", "append-only"],
21919
+ source_ids: [],
21920
+ project_ids: [],
21921
+ node_ids: [],
21922
+ freshness: "fresh",
21923
+ status: "active",
21924
+ confidence: 1,
21925
+ created_at: now,
21926
+ updated_at: now,
21927
+ compiled_from: [],
21928
+ managed_by: "agent",
21929
+ backlinks: [],
21930
+ schema_hash: "",
21931
+ source_hashes: {},
21932
+ source_semantic_hashes: {}
21933
+ }
21934
+ ),
21935
+ "utf8"
21936
+ );
21937
+ }
21938
+ if (options.obsidian) {
21939
+ const obsidianDir = path23.join(rootDir, ".obsidian");
21940
+ await ensureDir(obsidianDir);
21941
+ }
21942
+ }
21625
21943
  async function initVault(rootDir, options = {}) {
21944
+ if (options.lite) {
21945
+ await initLiteVault(rootDir, options);
21946
+ return;
21947
+ }
21626
21948
  const requestedProfile = options.profile ?? "default";
21627
21949
  const { config, paths } = await initWorkspace(rootDir, { profile: requestedProfile });
21628
21950
  const profile = config.profile;
@@ -23318,7 +23640,7 @@ async function getWatchStatus(rootDir) {
23318
23640
  }
23319
23641
 
23320
23642
  // src/mcp.ts
23321
- var SERVER_VERSION = "0.7.31";
23643
+ var SERVER_VERSION = "0.9.0";
23322
23644
  async function createMcpServer(rootDir) {
23323
23645
  const server = new McpServer({
23324
23646
  name: "swarmvault",
@@ -25889,6 +26211,8 @@ async function deleteManagedSource(rootDir, id) {
25889
26211
 
25890
26212
  // src/viewer.ts
25891
26213
  import { execFile as execFile2 } from "child_process";
26214
+ import { randomUUID } from "crypto";
26215
+ import { EventEmitter } from "events";
25892
26216
  import fs23 from "fs/promises";
25893
26217
  import http from "http";
25894
26218
  import path28 from "path";
@@ -26015,6 +26339,33 @@ function buildViewerGraphArtifact(graph, options = {}) {
26015
26339
  }
26016
26340
 
26017
26341
  // src/viewer.ts
26342
+ var ViewerEventBus = class extends EventEmitter {
26343
+ publish(event) {
26344
+ const enriched = {
26345
+ id: event.id ?? randomUUID(),
26346
+ timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
26347
+ type: event.type,
26348
+ level: event.level,
26349
+ message: event.message,
26350
+ meta: event.meta
26351
+ };
26352
+ this.emit("event", enriched);
26353
+ return enriched;
26354
+ }
26355
+ };
26356
+ var viewerEventBus = new ViewerEventBus();
26357
+ viewerEventBus.setMaxListeners(64);
26358
+ function toViewerLintFindings(findings) {
26359
+ const detectedAt = (/* @__PURE__ */ new Date()).toISOString();
26360
+ return findings.map((finding, index) => ({
26361
+ id: `${finding.code}:${index}`,
26362
+ severity: finding.severity,
26363
+ category: finding.code,
26364
+ message: finding.message,
26365
+ pagePath: finding.pagePath,
26366
+ detectedAt
26367
+ }));
26368
+ }
26018
26369
  var execFileAsync2 = promisify2(execFile2);
26019
26370
  async function isReadableFile(absolutePath) {
26020
26371
  try {
@@ -26262,6 +26613,76 @@ async function startGraphServer(rootDir, port, options = {}) {
26262
26613
  response.end(JSON.stringify(result));
26263
26614
  return;
26264
26615
  }
26616
+ if (url.pathname === "/api/lint") {
26617
+ try {
26618
+ const findings = await lintVault(rootDir);
26619
+ response.writeHead(200, { "content-type": "application/json" });
26620
+ response.end(JSON.stringify(toViewerLintFindings(findings)));
26621
+ } catch (error) {
26622
+ response.writeHead(200, { "content-type": "application/json" });
26623
+ response.end(JSON.stringify([]));
26624
+ console.warn(`[viewer] /api/lint failed: ${error instanceof Error ? error.message : String(error)}`);
26625
+ }
26626
+ return;
26627
+ }
26628
+ if (url.pathname === "/api/workspace") {
26629
+ const reportPath = path28.join(paths.wikiDir, "graph", "report.json");
26630
+ const [graphRaw, reportRaw, approvalsRaw, candidatesRaw, watchStatusRaw, lintRaw] = await Promise.all([
26631
+ readJsonFile(paths.graphPath).catch(() => null),
26632
+ readJsonFile(reportPath).catch(() => null),
26633
+ listApprovals(rootDir).catch(() => []),
26634
+ listCandidates(rootDir).catch(() => []),
26635
+ getWatchStatus(rootDir).catch(() => ({ generatedAt: "", watchedRepoRoots: [], pendingSemanticRefresh: [] })),
26636
+ lintVault(rootDir).catch(() => [])
26637
+ ]);
26638
+ const viewerGraph = graphRaw ? buildViewerGraphArtifact(graphRaw, { report: reportRaw, full: options.full ?? false }) : null;
26639
+ response.writeHead(200, { "content-type": "application/json" });
26640
+ response.end(
26641
+ JSON.stringify({
26642
+ graph: viewerGraph,
26643
+ graphReport: reportRaw ?? null,
26644
+ approvals: approvalsRaw,
26645
+ candidates: candidatesRaw,
26646
+ watchStatus: watchStatusRaw,
26647
+ lintFindings: toViewerLintFindings(lintRaw)
26648
+ })
26649
+ );
26650
+ return;
26651
+ }
26652
+ if (url.pathname === "/api/events") {
26653
+ response.writeHead(200, {
26654
+ "content-type": "text/event-stream",
26655
+ "cache-control": "no-cache, no-transform",
26656
+ connection: "keep-alive",
26657
+ "x-accel-buffering": "no"
26658
+ });
26659
+ const send = (event) => {
26660
+ response.write(`id: ${event.id}
26661
+ `);
26662
+ response.write(`data: ${JSON.stringify(event)}
26663
+
26664
+ `);
26665
+ };
26666
+ send({
26667
+ id: randomUUID(),
26668
+ type: "connected",
26669
+ level: "info",
26670
+ message: "Activity stream connected.",
26671
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
26672
+ });
26673
+ const listener = (event) => send(event);
26674
+ viewerEventBus.on("event", listener);
26675
+ const heartbeat = setInterval(() => {
26676
+ response.write(`: keepalive ${(/* @__PURE__ */ new Date()).toISOString()}
26677
+
26678
+ `);
26679
+ }, 25e3);
26680
+ request.on("close", () => {
26681
+ clearInterval(heartbeat);
26682
+ viewerEventBus.off("event", listener);
26683
+ });
26684
+ return;
26685
+ }
26265
26686
  if (url.pathname === "/api/clip" && request.method === "POST") {
26266
26687
  const body = await readJsonBody(request);
26267
26688
  const clipUrl = typeof body.url === "string" ? body.url.trim() : "";
@@ -0,0 +1,12 @@
1
+ import {
2
+ assertProviderCapability,
3
+ createProvider,
4
+ getProviderForTask,
5
+ getResolvedPaths
6
+ } from "./chunk-BTWPJEP2.js";
7
+ export {
8
+ assertProviderCapability,
9
+ createProvider,
10
+ getProviderForTask,
11
+ getResolvedPaths
12
+ };