@swarmvaultai/engine 3.11.0 → 3.12.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
@@ -128,7 +128,7 @@ import {
128
128
  writeGuidedSourceSession,
129
129
  writeRetrievalManifest,
130
130
  writeWatchStatusArtifact
131
- } from "./chunk-2PN46RDI.js";
131
+ } from "./chunk-Z552HHPV.js";
132
132
  import {
133
133
  LocalWhisperProviderAdapter,
134
134
  SWARMVAULT_OUT_ENV,
@@ -148,6 +148,7 @@ import {
148
148
  readJsonFile,
149
149
  resolveArtifactRootDir,
150
150
  resolvePaths,
151
+ safeFrontmatter,
151
152
  sha256,
152
153
  slugify,
153
154
  toPosix,
@@ -161,6 +162,323 @@ import {
161
162
  trimToTokenBudget
162
163
  } from "./chunk-NAIERP4C.js";
163
164
 
165
+ // src/ai-export.ts
166
+ import fs from "fs/promises";
167
+ import path from "path";
168
+ import matter from "gray-matter";
169
+ var DEFAULT_MAX_FULL_CHARS = 5e6;
170
+ var MAX_INDEX_EXCERPT_CHARS = 220;
171
+ function relativeOutputPath(outputDir, filePath) {
172
+ return toPosix(path.relative(outputDir, filePath));
173
+ }
174
+ async function writeTrackedText(files, outputDir, kind, filePath, content) {
175
+ await ensureDir(path.dirname(filePath));
176
+ await fs.writeFile(filePath, content, "utf8");
177
+ files.push({
178
+ kind,
179
+ path: relativeOutputPath(outputDir, filePath),
180
+ bytes: Buffer.byteLength(content),
181
+ sha256: sha256(content)
182
+ });
183
+ }
184
+ async function writeTrackedJson(files, outputDir, kind, filePath, value) {
185
+ const content = `${JSON.stringify(value, null, 2)}
186
+ `;
187
+ await writeTrackedText(files, outputDir, kind, filePath, content);
188
+ }
189
+ function graphPathForNode(nodeId) {
190
+ return `swarmvault:node:${encodeURIComponent(nodeId)}`;
191
+ }
192
+ function graphPathForPage(pageId) {
193
+ return `swarmvault:page:${encodeURIComponent(pageId)}`;
194
+ }
195
+ function renderPageLine(page) {
196
+ const excerpt = truncate(normalizeWhitespace(page.body.replace(/^---[\s\S]*?---/, "")), MAX_INDEX_EXCERPT_CHARS);
197
+ const bits = [
198
+ `- ${page.graphPage.title} (${page.graphPage.kind}, ${page.graphPage.freshness})`,
199
+ ` - path: wiki/${page.graphPage.path}`,
200
+ ` - page_id: ${page.graphPage.id}`,
201
+ page.graphPage.sourceIds.length ? ` - sources: ${page.graphPage.sourceIds.slice(0, 6).join(", ")}` : void 0,
202
+ excerpt ? ` - excerpt: ${excerpt}` : void 0
203
+ ].filter((line) => Boolean(line));
204
+ return bits.join("\n");
205
+ }
206
+ function renderLlmsIndex(input) {
207
+ const topPages = [...input.pages].sort(
208
+ (left, right) => right.graphPage.confidence - left.graphPage.confidence || right.graphPage.relatedPageIds.length - left.graphPage.relatedPageIds.length || left.graphPage.path.localeCompare(right.graphPage.path)
209
+ ).slice(0, 80);
210
+ const communities = (input.graph.communities ?? []).slice(0, 20).map((community) => `- ${community.label} (${community.nodeIds.length} nodes)`).join("\n");
211
+ return [
212
+ "# SwarmVault AI Index",
213
+ "",
214
+ `Generated: ${input.generatedAt}`,
215
+ `Vault: ${input.vaultName}`,
216
+ "",
217
+ "## Use This Export",
218
+ "",
219
+ "- Start with `ai-readme.md` for navigation guidance.",
220
+ "- Use `llms-full.txt` when an agent needs a bounded plain-text dump of the compiled wiki.",
221
+ "- Use `graph.jsonld` when an agent or crawler needs a structured page/node/relation graph.",
222
+ "- Use `pages/` for per-page `.txt` and `.json` siblings when `--page-siblings` is enabled.",
223
+ "",
224
+ "## Counts",
225
+ "",
226
+ `- Sources: ${input.graph.sources.length}`,
227
+ `- Pages: ${input.graph.pages.length}`,
228
+ `- Nodes: ${input.graph.nodes.length}`,
229
+ `- Edges: ${input.graph.edges.length}`,
230
+ `- Hyperedges: ${input.graph.hyperedges.length}`,
231
+ "",
232
+ "## Commands",
233
+ "",
234
+ '- `swarmvault query "question"` asks the compiled wiki.',
235
+ '- `swarmvault chat "question"` keeps a persisted multi-turn session.',
236
+ '- `swarmvault context build "goal" --target <path>` creates a token-bounded handoff.',
237
+ "- `swarmvault graph serve` opens the workbench and graph viewer.",
238
+ "- `swarmvault doctor` checks graph, retrieval, review, watch, and task state.",
239
+ "",
240
+ communities ? "## Communities" : void 0,
241
+ communities || void 0,
242
+ "",
243
+ "## High-Signal Pages",
244
+ "",
245
+ topPages.map(renderPageLine).join("\n\n")
246
+ ].filter((line) => line !== void 0).join("\n").trimEnd().concat("\n");
247
+ }
248
+ function renderAiReadme(input) {
249
+ return [
250
+ "# AI Handoff Pack",
251
+ "",
252
+ `Generated: ${input.generatedAt}`,
253
+ `Vault: ${input.vaultName}`,
254
+ "",
255
+ "This folder is a portable, static export of the compiled SwarmVault wiki for agents, crawlers, and documentation systems.",
256
+ "",
257
+ "## Files",
258
+ "",
259
+ "- `llms.txt` - concise index with stats, command hints, communities, and high-signal pages.",
260
+ `- \`llms-full.txt\` - plain-text wiki dump${input.truncatedFullText ? " truncated at the requested character cap" : ""}.`,
261
+ "- `graph.jsonld` - structured graph with pages, nodes, sources, and relation edges.",
262
+ "- `manifest.json` - file list, hashes, counts, and export settings.",
263
+ input.pageSiblings ? "- `pages/` - per-page `.txt` and `.json` siblings for direct retrieval." : void 0,
264
+ "",
265
+ "## Suggested Agent Flow",
266
+ "",
267
+ "1. Read `llms.txt` to understand the vault shape.",
268
+ "2. Search `llms-full.txt` or `pages/` for the specific topic.",
269
+ "3. Use `graph.jsonld` to follow page, node, and relation IDs when provenance matters.",
270
+ "4. Ask the live vault with `swarmvault query` or continue a session with `swarmvault chat` when shell access is available."
271
+ ].filter((line) => line !== void 0).join("\n").trimEnd().concat("\n");
272
+ }
273
+ function renderFullText(input) {
274
+ const chunks = [
275
+ "# SwarmVault Full Wiki Export",
276
+ "",
277
+ `Generated: ${input.generatedAt}`,
278
+ `Vault: ${input.vaultName}`,
279
+ "",
280
+ "The sections below are compiled wiki pages separated by stable path markers.",
281
+ ""
282
+ ];
283
+ let usedChars = chunks.join("\n").length;
284
+ let truncated = false;
285
+ for (const page of input.pages) {
286
+ const section = [
287
+ "",
288
+ `--- PAGE: wiki/${page.graphPage.path} ---`,
289
+ `Title: ${page.graphPage.title}`,
290
+ `Page ID: ${page.graphPage.id}`,
291
+ `Kind: ${page.graphPage.kind}`,
292
+ page.graphPage.sourceIds.length ? `Source IDs: ${page.graphPage.sourceIds.join(", ")}` : void 0,
293
+ "",
294
+ page.content.trim(),
295
+ ""
296
+ ].filter((line) => line !== void 0).join("\n");
297
+ if (usedChars + section.length > input.maxChars) {
298
+ const remaining = Math.max(0, input.maxChars - usedChars);
299
+ if (remaining > 0) {
300
+ chunks.push(section.slice(0, remaining));
301
+ }
302
+ truncated = true;
303
+ break;
304
+ }
305
+ chunks.push(section);
306
+ usedChars += section.length;
307
+ }
308
+ if (truncated) {
309
+ chunks.push("", "[truncated: increase --max-full-chars to include more pages]");
310
+ }
311
+ return { content: chunks.join("\n").trimEnd().concat("\n"), truncated };
312
+ }
313
+ function buildJsonLd(input) {
314
+ const graphItems = [
315
+ {
316
+ "@id": "swarmvault:vault",
317
+ "@type": "Dataset",
318
+ name: input.vaultName,
319
+ dateModified: input.graph.generatedAt,
320
+ datePublished: input.generatedAt,
321
+ additionalType: "SwarmVaultKnowledgeGraph"
322
+ },
323
+ ...input.graph.sources.map((source) => ({
324
+ "@id": `swarmvault:source:${encodeURIComponent(source.sourceId)}`,
325
+ "@type": "CreativeWork",
326
+ name: source.title,
327
+ encodingFormat: source.mimeType,
328
+ dateCreated: source.createdAt,
329
+ dateModified: source.updatedAt,
330
+ url: source.url,
331
+ fileFormat: source.sourceKind,
332
+ identifier: source.sourceId
333
+ })),
334
+ ...input.graph.pages.map((page) => ({
335
+ "@id": graphPathForPage(page.id),
336
+ "@type": "CreativeWork",
337
+ name: page.title,
338
+ identifier: page.id,
339
+ url: `wiki/${page.path}`,
340
+ genre: page.kind,
341
+ dateCreated: page.createdAt,
342
+ dateModified: page.updatedAt,
343
+ about: page.nodeIds.map((nodeId) => ({ "@id": graphPathForNode(nodeId) })),
344
+ citation: page.sourceIds.map((sourceId) => ({ "@id": `swarmvault:source:${encodeURIComponent(sourceId)}` })),
345
+ isPartOf: { "@id": "swarmvault:vault" }
346
+ })),
347
+ ...input.graph.nodes.map((node) => ({
348
+ "@id": graphPathForNode(node.id),
349
+ "@type": "Thing",
350
+ name: node.label,
351
+ identifier: node.id,
352
+ additionalType: node.type,
353
+ isPartOf: node.pageId ? { "@id": graphPathForPage(node.pageId) } : { "@id": "swarmvault:vault" },
354
+ sameAs: node.sourceIds.map((sourceId) => `swarmvault:source:${encodeURIComponent(sourceId)}`)
355
+ })),
356
+ ...input.graph.edges.map((edge) => ({
357
+ "@id": `swarmvault:edge:${encodeURIComponent(edge.id)}`,
358
+ "@type": "swarmvault:Relation",
359
+ name: edge.relation,
360
+ identifier: edge.id,
361
+ "swarmvault:source": { "@id": graphPathForNode(edge.source) },
362
+ "swarmvault:target": { "@id": graphPathForNode(edge.target) },
363
+ "swarmvault:evidenceClass": edge.evidenceClass,
364
+ "swarmvault:confidence": edge.confidence,
365
+ "swarmvault:provenance": edge.provenance
366
+ }))
367
+ ];
368
+ return {
369
+ "@context": {
370
+ "@vocab": "https://schema.org/",
371
+ swarmvault: "https://www.swarmvault.ai/ns#"
372
+ },
373
+ "@graph": graphItems
374
+ };
375
+ }
376
+ async function loadPages(rootDir, wikiDir, graph) {
377
+ const pages = [];
378
+ const sortedPages = [...graph.pages].sort((left, right) => left.path.localeCompare(right.path));
379
+ for (const graphPage of sortedPages) {
380
+ const absolutePath = path.join(wikiDir, graphPage.path);
381
+ if (!await fileExists(absolutePath)) {
382
+ continue;
383
+ }
384
+ const content = await fs.readFile(absolutePath, "utf8");
385
+ const parsed = matter(content);
386
+ pages.push({
387
+ graphPage,
388
+ absolutePath: path.resolve(rootDir, absolutePath),
389
+ content,
390
+ body: parsed.content,
391
+ data: parsed.data
392
+ });
393
+ }
394
+ return pages;
395
+ }
396
+ async function exportAiPack(rootDir, options = {}) {
397
+ const { paths } = await loadVaultConfig(rootDir);
398
+ const graph = await readJsonFile(paths.graphPath);
399
+ if (!graph) {
400
+ throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
401
+ }
402
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
403
+ const vaultName = path.basename(paths.rootDir);
404
+ const outputDir = path.resolve(rootDir, options.outDir ?? path.join("wiki", "exports", "ai"));
405
+ const maxFullChars = Math.max(1e3, options.maxFullChars ?? DEFAULT_MAX_FULL_CHARS);
406
+ const pageSiblings = options.pageSiblings ?? true;
407
+ await ensureDir(outputDir);
408
+ const pages = await loadPages(rootDir, paths.wikiDir, graph);
409
+ const files = [];
410
+ const fullText = renderFullText({ generatedAt, vaultName, pages, maxChars: maxFullChars });
411
+ await writeTrackedText(
412
+ files,
413
+ outputDir,
414
+ "index",
415
+ path.join(outputDir, "llms.txt"),
416
+ renderLlmsIndex({ generatedAt, vaultName, graph, pages })
417
+ );
418
+ await writeTrackedText(files, outputDir, "full-text", path.join(outputDir, "llms-full.txt"), fullText.content);
419
+ await writeTrackedJson(
420
+ files,
421
+ outputDir,
422
+ "graph-jsonld",
423
+ path.join(outputDir, "graph.jsonld"),
424
+ buildJsonLd({ generatedAt, vaultName, graph })
425
+ );
426
+ await writeTrackedText(
427
+ files,
428
+ outputDir,
429
+ "readme",
430
+ path.join(outputDir, "ai-readme.md"),
431
+ renderAiReadme({ generatedAt, vaultName, truncatedFullText: fullText.truncated, pageSiblings })
432
+ );
433
+ if (pageSiblings) {
434
+ for (const page of pages) {
435
+ const siblingBase = path.join(outputDir, "pages", page.graphPage.path.replace(/\.md$/u, ""));
436
+ await writeTrackedText(files, outputDir, "page-text", `${siblingBase}.txt`, page.body.trim().concat("\n"));
437
+ await writeTrackedJson(files, outputDir, "page-json", `${siblingBase}.json`, {
438
+ title: page.graphPage.title,
439
+ path: page.graphPage.path,
440
+ pageId: page.graphPage.id,
441
+ kind: page.graphPage.kind,
442
+ freshness: page.graphPage.freshness,
443
+ confidence: page.graphPage.confidence,
444
+ sourceIds: page.graphPage.sourceIds,
445
+ nodeIds: page.graphPage.nodeIds,
446
+ relatedPageIds: page.graphPage.relatedPageIds,
447
+ relatedNodeIds: page.graphPage.relatedNodeIds,
448
+ frontmatter: page.data,
449
+ body: page.body.trim(),
450
+ contentSha256: sha256(page.content)
451
+ });
452
+ }
453
+ }
454
+ const result = {
455
+ outputDir,
456
+ generatedAt,
457
+ pageCount: graph.pages.length,
458
+ sourceCount: graph.sources.length,
459
+ nodeCount: graph.nodes.length,
460
+ edgeCount: graph.edges.length,
461
+ truncatedFullText: fullText.truncated,
462
+ files
463
+ };
464
+ const manifestPath = path.join(outputDir, "manifest.json");
465
+ await writeJsonFile(manifestPath, {
466
+ ...result,
467
+ files: [...files].sort((left, right) => left.path.localeCompare(right.path))
468
+ });
469
+ const manifestContent = await fs.readFile(manifestPath, "utf8");
470
+ files.push({
471
+ kind: "manifest",
472
+ path: relativeOutputPath(outputDir, manifestPath),
473
+ bytes: Buffer.byteLength(manifestContent),
474
+ sha256: sha256(manifestContent)
475
+ });
476
+ return {
477
+ ...result,
478
+ files: [...files].sort((left, right) => left.path.localeCompare(right.path))
479
+ };
480
+ }
481
+
164
482
  // src/auto-commit.ts
165
483
  import { execFile } from "child_process";
166
484
  import { promisify } from "util";
@@ -198,25 +516,235 @@ async function autoCommitWikiChanges(rootDir, operation, detail, options) {
198
516
  return message;
199
517
  }
200
518
 
519
+ // src/chat.ts
520
+ import fs2 from "fs/promises";
521
+ import path2 from "path";
522
+ import matter2 from "gray-matter";
523
+ var DEFAULT_HISTORY_TURNS = 6;
524
+ function timestampIdPrefix() {
525
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/u, "Z").replace("T", "-");
526
+ }
527
+ function chatDirs(paths) {
528
+ return {
529
+ stateDir: path2.join(paths.stateDir, "chat-sessions"),
530
+ wikiDir: path2.join(paths.wikiDir, "outputs", "chat-sessions")
531
+ };
532
+ }
533
+ function sessionStatePath(stateDir, id) {
534
+ return path2.join(stateDir, `${id}.json`);
535
+ }
536
+ function sessionMarkdownPath(wikiDir, id) {
537
+ return path2.join(wikiDir, `${id}.md`);
538
+ }
539
+ function summarizeSession(session) {
540
+ return {
541
+ id: session.id,
542
+ title: session.title,
543
+ createdAt: session.createdAt,
544
+ updatedAt: session.updatedAt,
545
+ turnCount: session.turns.length,
546
+ markdownPath: session.markdownPath
547
+ };
548
+ }
549
+ function renderSessionMarkdown(session) {
550
+ const body = [
551
+ `# ${session.title}`,
552
+ "",
553
+ `Session ID: \`${session.id}\``,
554
+ `Updated: ${session.updatedAt}`,
555
+ "",
556
+ ...session.turns.flatMap((turn, index) => [
557
+ `## Turn ${index + 1} - ${turn.createdAt}`,
558
+ "",
559
+ "### Question",
560
+ "",
561
+ turn.question,
562
+ "",
563
+ "### Answer",
564
+ "",
565
+ turn.answer,
566
+ "",
567
+ turn.citations.length ? "### Citations" : void 0,
568
+ turn.citations.length ? "" : void 0,
569
+ ...turn.citations.map((citation) => `- ${citation}`),
570
+ turn.savedPath ? "" : void 0,
571
+ turn.savedPath ? `Saved output: \`${turn.savedPath}\`` : void 0,
572
+ ""
573
+ ])
574
+ ].filter((line) => line !== void 0).join("\n");
575
+ return matter2.stringify(
576
+ body,
577
+ safeFrontmatter({
578
+ session_id: session.id,
579
+ title: session.title,
580
+ created_at: session.createdAt,
581
+ updated_at: session.updatedAt,
582
+ turn_count: session.turns.length,
583
+ page_id: `chat:${session.id}`,
584
+ freshness: "fresh",
585
+ node_ids: [],
586
+ source_ids: [],
587
+ source_hashes: {}
588
+ })
589
+ );
590
+ }
591
+ async function persistSession(session, stateDir, wikiDir) {
592
+ await ensureDir(stateDir);
593
+ await ensureDir(wikiDir);
594
+ const statePath = sessionStatePath(stateDir, session.id);
595
+ const markdownPath = sessionMarkdownPath(wikiDir, session.id);
596
+ const persisted = { ...session, markdownPath };
597
+ await writeJsonFile(statePath, persisted);
598
+ await fs2.writeFile(markdownPath, renderSessionMarkdown(persisted), "utf8");
599
+ return { statePath, markdownPath };
600
+ }
601
+ async function resolveSessionId(stateDir, idOrPrefix) {
602
+ const direct = sessionStatePath(stateDir, idOrPrefix);
603
+ if (await fileExists(direct)) {
604
+ return idOrPrefix;
605
+ }
606
+ const entries = await fs2.readdir(stateDir).catch(() => []);
607
+ const matches = entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).filter((id) => id.startsWith(idOrPrefix));
608
+ if (matches.length === 1) {
609
+ return matches[0];
610
+ }
611
+ if (matches.length > 1) {
612
+ throw new Error(`Chat session prefix "${idOrPrefix}" is ambiguous: ${matches.slice(0, 8).join(", ")}`);
613
+ }
614
+ throw new Error(`Chat session not found: ${idOrPrefix}`);
615
+ }
616
+ async function loadSession(stateDir, idOrPrefix) {
617
+ const id = await resolveSessionId(stateDir, idOrPrefix);
618
+ const session = await readJsonFile(sessionStatePath(stateDir, id));
619
+ if (!session) {
620
+ throw new Error(`Chat session not found: ${id}`);
621
+ }
622
+ return session;
623
+ }
624
+ function createSession(rootDir, wikiDir, options, now) {
625
+ const title = truncate(options.title?.trim() || normalizeWhitespace(options.question), 80);
626
+ const id = `${timestampIdPrefix()}-${slugify(title)}`;
627
+ return {
628
+ id,
629
+ title,
630
+ createdAt: now,
631
+ updatedAt: now,
632
+ rootDir,
633
+ markdownPath: sessionMarkdownPath(wikiDir, id),
634
+ turns: []
635
+ };
636
+ }
637
+ function buildPrompt(session, question, maxHistoryTurns) {
638
+ const recentTurns = session.turns.slice(-maxHistoryTurns);
639
+ if (!recentTurns.length) {
640
+ return question;
641
+ }
642
+ const history = recentTurns.map(
643
+ (turn, index) => [
644
+ `Turn ${index + 1}`,
645
+ `User: ${turn.question}`,
646
+ `Assistant: ${truncate(normalizeWhitespace(turn.answer), 1200)}`,
647
+ turn.citations.length ? `Citations: ${turn.citations.join(", ")}` : void 0
648
+ ].filter((line) => line !== void 0).join("\n")
649
+ ).join("\n\n");
650
+ return [
651
+ "Continue this SwarmVault chat session using the compiled wiki as the source of truth.",
652
+ "Use prior turns only for conversational continuity. Prefer current vault evidence over prior wording.",
653
+ "",
654
+ "Prior turns:",
655
+ history,
656
+ "",
657
+ "Current question:",
658
+ question
659
+ ].join("\n");
660
+ }
661
+ async function listChatSessions(rootDir) {
662
+ const { paths } = await loadVaultConfig(rootDir);
663
+ const { stateDir } = chatDirs(paths);
664
+ const entries = await fs2.readdir(stateDir).catch(() => []);
665
+ const sessions = await Promise.all(
666
+ entries.filter((entry) => entry.endsWith(".json")).map(async (entry) => readJsonFile(path2.join(stateDir, entry)))
667
+ );
668
+ return sessions.filter((session) => Boolean(session)).map(summarizeSession).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
669
+ }
670
+ async function readChatSession(rootDir, idOrPrefix) {
671
+ const { paths } = await loadVaultConfig(rootDir);
672
+ const { stateDir } = chatDirs(paths);
673
+ return loadSession(stateDir, idOrPrefix);
674
+ }
675
+ async function deleteChatSession(rootDir, idOrPrefix) {
676
+ const { paths } = await loadVaultConfig(rootDir);
677
+ const { stateDir, wikiDir } = chatDirs(paths);
678
+ const session = await loadSession(stateDir, idOrPrefix);
679
+ await fs2.rm(sessionStatePath(stateDir, session.id), { force: true });
680
+ await fs2.rm(sessionMarkdownPath(wikiDir, session.id), { force: true });
681
+ return summarizeSession(session);
682
+ }
683
+ async function askChatSession(rootDir, options) {
684
+ const question = normalizeWhitespace(options.question);
685
+ if (!question) {
686
+ throw new Error("Chat question is required.");
687
+ }
688
+ const { paths } = await loadVaultConfig(rootDir);
689
+ const { stateDir, wikiDir } = chatDirs(paths);
690
+ const now = (/* @__PURE__ */ new Date()).toISOString();
691
+ const resumed = Boolean(options.sessionId);
692
+ const session = options.sessionId ? await loadSession(stateDir, options.sessionId) : createSession(paths.rootDir, wikiDir, options, now);
693
+ const prompt = buildPrompt(session, question, Math.max(0, options.maxHistoryTurns ?? DEFAULT_HISTORY_TURNS));
694
+ const query = await queryVault(paths.rootDir, {
695
+ question: prompt,
696
+ save: options.saveOutput ?? false,
697
+ format: options.format,
698
+ gapFill: options.gapFill
699
+ });
700
+ const turn = {
701
+ id: `${session.turns.length + 1}`,
702
+ createdAt: now,
703
+ question,
704
+ answer: query.answer,
705
+ citations: query.citations,
706
+ relatedPageIds: query.relatedPageIds,
707
+ relatedNodeIds: query.relatedNodeIds,
708
+ relatedSourceIds: query.relatedSourceIds,
709
+ outputFormat: query.outputFormat,
710
+ savedPath: query.savedPath
711
+ };
712
+ const updatedSession = {
713
+ ...session,
714
+ updatedAt: now,
715
+ markdownPath: sessionMarkdownPath(wikiDir, session.id),
716
+ turns: [...session.turns, turn]
717
+ };
718
+ const persisted = await persistSession(updatedSession, stateDir, wikiDir);
719
+ return {
720
+ session: { ...updatedSession, markdownPath: persisted.markdownPath },
721
+ turn,
722
+ answer: query.answer,
723
+ markdownPath: persisted.markdownPath,
724
+ statePath: persisted.statePath,
725
+ resumed
726
+ };
727
+ }
728
+
201
729
  // src/doctor.ts
202
- import fs3 from "fs/promises";
203
- import path3 from "path";
730
+ import fs5 from "fs/promises";
731
+ import path5 from "path";
204
732
 
205
733
  // src/migrate.ts
206
- import fs from "fs/promises";
207
- import path from "path";
208
- import matter from "gray-matter";
734
+ import fs3 from "fs/promises";
735
+ import path3 from "path";
736
+ import matter3 from "gray-matter";
209
737
  var VAULT_VERSION_FILENAME = "vault-version.json";
210
738
  async function walkMarkdownFiles(dir) {
211
739
  const results = [];
212
740
  let entries;
213
741
  try {
214
- entries = await fs.readdir(dir, { withFileTypes: true });
742
+ entries = await fs3.readdir(dir, { withFileTypes: true });
215
743
  } catch {
216
744
  return results;
217
745
  }
218
746
  for (const entry of entries) {
219
- const full = path.join(dir, entry.name);
747
+ const full = path3.join(dir, entry.name);
220
748
  if (entry.isDirectory()) {
221
749
  results.push(...await walkMarkdownFiles(full));
222
750
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -227,8 +755,8 @@ async function walkMarkdownFiles(dir) {
227
755
  }
228
756
  async function readFrontmatterFile(filePath) {
229
757
  try {
230
- const raw = await fs.readFile(filePath, "utf8");
231
- const parsed = matter(raw);
758
+ const raw = await fs3.readFile(filePath, "utf8");
759
+ const parsed = matter3(raw);
232
760
  const data = parsed.data ?? {};
233
761
  return { data, content: parsed.content };
234
762
  } catch {
@@ -236,10 +764,10 @@ async function readFrontmatterFile(filePath) {
236
764
  }
237
765
  }
238
766
  async function writeFrontmatterFile(filePath, data, content) {
239
- await fs.writeFile(filePath, matter.stringify(content, data), "utf8");
767
+ await fs3.writeFile(filePath, matter3.stringify(content, data), "utf8");
240
768
  }
241
769
  function relFromRoot(rootDir, filePath) {
242
- return path.relative(rootDir, filePath) || filePath;
770
+ return path3.relative(rootDir, filePath) || filePath;
243
771
  }
244
772
  var MIGRATION_ADD_DECAY_FIELDS = {
245
773
  id: "0.9-to-0.10-add-decay-fields",
@@ -280,7 +808,7 @@ var MIGRATION_ADD_TIER_DEFAULT = {
280
808
  description: 'Tag insight pages with tier: "working" when the field is absent.',
281
809
  async apply(ctx, options) {
282
810
  const changed = [];
283
- const insightsDir = path.join(ctx.paths.wikiDir, "insights");
811
+ const insightsDir = path3.join(ctx.paths.wikiDir, "insights");
284
812
  const files = await walkMarkdownFiles(insightsDir);
285
813
  for (const filePath of files) {
286
814
  const parsed = await readFrontmatterFile(filePath);
@@ -337,11 +865,11 @@ var MIGRATION_REBUILD_SEARCH_INDEX = {
337
865
  description: "Mark state/search.sqlite as stale so the next compile regenerates it.",
338
866
  async apply(ctx, options) {
339
867
  const changed = [];
340
- const searchPath = path.join(ctx.paths.stateDir, "search.sqlite");
868
+ const searchPath = path3.join(ctx.paths.stateDir, "search.sqlite");
341
869
  try {
342
- await fs.access(searchPath);
870
+ await fs3.access(searchPath);
343
871
  if (!options.dryRun) {
344
- await fs.rm(searchPath, { force: true });
872
+ await fs3.rm(searchPath, { force: true });
345
873
  }
346
874
  changed.push(relFromRoot(ctx.rootDir, searchPath));
347
875
  } catch {
@@ -358,8 +886,8 @@ var MIGRATION_ADD_MEMORY_LEDGER = {
358
886
  if (options.dryRun) {
359
887
  return {
360
888
  changed: [
361
- relFromRoot(ctx.rootDir, path.join(ctx.paths.stateDir, "memory", "tasks")),
362
- relFromRoot(ctx.rootDir, path.join(ctx.paths.wikiDir, "memory", "index.md"))
889
+ relFromRoot(ctx.rootDir, path3.join(ctx.paths.stateDir, "memory", "tasks")),
890
+ relFromRoot(ctx.rootDir, path3.join(ctx.paths.wikiDir, "memory", "index.md"))
363
891
  ]
364
892
  };
365
893
  }
@@ -373,9 +901,9 @@ var MIGRATION_3_0_RETRIEVAL_AND_TASKS = {
373
901
  description: "Move search config into retrieval, create state/retrieval, remove the legacy search index, and add task aliases to memory frontmatter.",
374
902
  async apply(ctx, options) {
375
903
  const changed = [];
376
- const configPath = path.join(ctx.rootDir, "swarmvault.config.json");
904
+ const configPath = path3.join(ctx.rootDir, "swarmvault.config.json");
377
905
  try {
378
- const raw = await fs.readFile(configPath, "utf8");
906
+ const raw = await fs3.readFile(configPath, "utf8");
379
907
  const config = JSON.parse(raw);
380
908
  let mutated = false;
381
909
  if (config.search) {
@@ -401,28 +929,28 @@ var MIGRATION_3_0_RETRIEVAL_AND_TASKS = {
401
929
  }
402
930
  if (mutated) {
403
931
  if (!options.dryRun) {
404
- await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}
932
+ await fs3.writeFile(configPath, `${JSON.stringify(config, null, 2)}
405
933
  `, "utf8");
406
934
  }
407
935
  changed.push(relFromRoot(ctx.rootDir, configPath));
408
936
  }
409
937
  } catch {
410
938
  }
411
- const retrievalDir = path.join(ctx.paths.stateDir, "retrieval");
939
+ const retrievalDir = path3.join(ctx.paths.stateDir, "retrieval");
412
940
  if (!options.dryRun) {
413
941
  await ensureDir(retrievalDir);
414
942
  }
415
943
  changed.push(relFromRoot(ctx.rootDir, retrievalDir));
416
- const legacySearchPath = path.join(ctx.paths.stateDir, "search.sqlite");
944
+ const legacySearchPath = path3.join(ctx.paths.stateDir, "search.sqlite");
417
945
  try {
418
- await fs.access(legacySearchPath);
946
+ await fs3.access(legacySearchPath);
419
947
  if (!options.dryRun) {
420
- await fs.rm(legacySearchPath, { force: true });
948
+ await fs3.rm(legacySearchPath, { force: true });
421
949
  }
422
950
  changed.push(relFromRoot(ctx.rootDir, legacySearchPath));
423
951
  } catch {
424
952
  }
425
- const memoryTaskDir = path.join(ctx.paths.wikiDir, "memory", "tasks");
953
+ const memoryTaskDir = path3.join(ctx.paths.wikiDir, "memory", "tasks");
426
954
  const files = await walkMarkdownFiles(memoryTaskDir);
427
955
  for (const filePath of files) {
428
956
  const parsed = await readFrontmatterFile(filePath);
@@ -471,9 +999,9 @@ function compareSemver(a, b) {
471
999
  return 0;
472
1000
  }
473
1001
  async function readVaultVersionRecord(stateDir) {
474
- const filePath = path.join(stateDir, VAULT_VERSION_FILENAME);
1002
+ const filePath = path3.join(stateDir, VAULT_VERSION_FILENAME);
475
1003
  try {
476
- const raw = await fs.readFile(filePath, "utf8");
1004
+ const raw = await fs3.readFile(filePath, "utf8");
477
1005
  const parsed = JSON.parse(raw);
478
1006
  if (typeof parsed.version === "string") return parsed;
479
1007
  return null;
@@ -482,9 +1010,9 @@ async function readVaultVersionRecord(stateDir) {
482
1010
  }
483
1011
  }
484
1012
  async function readGraphVersion(stateDir) {
485
- const filePath = path.join(stateDir, "graph.json");
1013
+ const filePath = path3.join(stateDir, "graph.json");
486
1014
  try {
487
- const raw = await fs.readFile(filePath, "utf8");
1015
+ const raw = await fs3.readFile(filePath, "utf8");
488
1016
  const parsed = JSON.parse(raw);
489
1017
  const version = parsed?.generatedBy?.version;
490
1018
  return typeof version === "string" ? version : null;
@@ -502,7 +1030,7 @@ async function detectVaultVersion(rootDir) {
502
1030
  }
503
1031
  async function currentPackageVersion() {
504
1032
  try {
505
- const raw = await fs.readFile(new URL("../package.json", import.meta.url), "utf8");
1033
+ const raw = await fs3.readFile(new URL("../package.json", import.meta.url), "utf8");
506
1034
  const parsed = JSON.parse(raw);
507
1035
  return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version : "0.0.0";
508
1036
  } catch {
@@ -519,8 +1047,8 @@ async function planMigration(rootDir, targetVersion) {
519
1047
  return { fromVersion, toVersion, steps };
520
1048
  }
521
1049
  async function writeVaultVersionRecord(stateDir, record) {
522
- await fs.mkdir(stateDir, { recursive: true });
523
- await fs.writeFile(path.join(stateDir, VAULT_VERSION_FILENAME), `${JSON.stringify(record, null, 2)}
1050
+ await fs3.mkdir(stateDir, { recursive: true });
1051
+ await fs3.writeFile(path3.join(stateDir, VAULT_VERSION_FILENAME), `${JSON.stringify(record, null, 2)}
524
1052
  `, "utf8");
525
1053
  }
526
1054
  async function runMigration(rootDir, options = {}) {
@@ -559,8 +1087,8 @@ async function runMigration(rootDir, options = {}) {
559
1087
  }
560
1088
 
561
1089
  // src/watch.ts
562
- import fs2 from "fs/promises";
563
- import path2 from "path";
1090
+ import fs4 from "fs/promises";
1091
+ import path4 from "path";
564
1092
  import process2 from "process";
565
1093
  import chokidar from "chokidar";
566
1094
  var MAX_BACKOFF_MS = 3e4;
@@ -620,7 +1148,7 @@ function isCodeOnlyChange(reasons) {
620
1148
  for (const reason of reasons) {
621
1149
  const match = reason.match(FILE_CHANGE_RE);
622
1150
  if (!match) return false;
623
- const ext = path2.extname(match[1]).toLowerCase();
1151
+ const ext = path4.extname(match[1]).toLowerCase();
624
1152
  if (!ext || !CODE_EXTENSIONS.has(ext)) return false;
625
1153
  }
626
1154
  return reasons.size > 0;
@@ -629,7 +1157,7 @@ function hasNonCodeChanges(reasons) {
629
1157
  for (const reason of reasons) {
630
1158
  const match = reason.match(FILE_CHANGE_RE);
631
1159
  if (!match) return true;
632
- const ext = path2.extname(match[1]).toLowerCase();
1160
+ const ext = path4.extname(match[1]).toLowerCase();
633
1161
  if (!ext || !CODE_EXTENSIONS.has(ext)) return true;
634
1162
  }
635
1163
  return false;
@@ -642,7 +1170,7 @@ function collectNonCodePaths(reasons) {
642
1170
  result.push(reason);
643
1171
  continue;
644
1172
  }
645
- const ext = path2.extname(match[1]).toLowerCase();
1173
+ const ext = path4.extname(match[1]).toLowerCase();
646
1174
  if (!ext || !CODE_EXTENSIONS.has(ext)) result.push(match[1]);
647
1175
  }
648
1176
  return result;
@@ -704,11 +1232,11 @@ async function predictRemovedSourceIdsFromRepoSync(rootDir, options) {
704
1232
  ];
705
1233
  }
706
1234
  function hasIgnoredRepoSegment(baseDir, targetPath) {
707
- const relativePath = path2.relative(baseDir, targetPath);
1235
+ const relativePath = path4.relative(baseDir, targetPath);
708
1236
  if (!relativePath || relativePath.startsWith("..")) {
709
1237
  return false;
710
1238
  }
711
- return relativePath.split(path2.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
1239
+ return relativePath.split(path4.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
712
1240
  }
713
1241
  function workspaceIgnoreRoots(rootDir, paths) {
714
1242
  return [
@@ -717,22 +1245,22 @@ function workspaceIgnoreRoots(rootDir, paths) {
717
1245
  paths.stateDir,
718
1246
  paths.agentDir,
719
1247
  paths.inboxDir,
720
- path2.join(rootDir, ".claude"),
721
- path2.join(rootDir, ".cursor"),
722
- path2.join(rootDir, ".obsidian")
723
- ].map((candidate) => path2.resolve(candidate));
1248
+ path4.join(rootDir, ".claude"),
1249
+ path4.join(rootDir, ".cursor"),
1250
+ path4.join(rootDir, ".obsidian")
1251
+ ].map((candidate) => path4.resolve(candidate));
724
1252
  }
725
1253
  async function resolveWatchTargets(rootDir, paths, options) {
726
- const targets = /* @__PURE__ */ new Set([path2.resolve(paths.inboxDir)]);
1254
+ const targets = /* @__PURE__ */ new Set([path4.resolve(paths.inboxDir)]);
727
1255
  if (options.repo) {
728
1256
  for (const repoRoot of await resolveWatchedRepoRoots(rootDir, { overrideRoots: options.overrideRoots })) {
729
- targets.add(path2.resolve(repoRoot));
1257
+ targets.add(path4.resolve(repoRoot));
730
1258
  }
731
1259
  }
732
1260
  return [...targets].sort((left, right) => left.localeCompare(right));
733
1261
  }
734
1262
  function resolveRootRelative(rootDir, candidate) {
735
- return path2.isAbsolute(candidate) ? path2.resolve(candidate) : path2.resolve(rootDir, candidate);
1263
+ return path4.isAbsolute(candidate) ? path4.resolve(candidate) : path4.resolve(rootDir, candidate);
736
1264
  }
737
1265
  async function resolveWatchedRepoRoots(rootDir, options = {}) {
738
1266
  const override = options.overrideRoots?.filter(Boolean) ?? [];
@@ -746,17 +1274,17 @@ async function resolveWatchedRepoRoots(rootDir, options = {}) {
746
1274
  const excluded = new Set(
747
1275
  (watchConfig.excludeRepoRoots ?? []).filter(Boolean).map((candidate) => resolveRootRelative(rootDir, candidate))
748
1276
  );
749
- return dedupeSorted(baseRoots.filter((candidate) => !excluded.has(path2.resolve(candidate))));
1277
+ return dedupeSorted(baseRoots.filter((candidate) => !excluded.has(path4.resolve(candidate))));
750
1278
  }
751
1279
  async function listWatchedRoots(rootDir, options = {}) {
752
1280
  return resolveWatchedRepoRoots(rootDir, options);
753
1281
  }
754
1282
  function dedupeSorted(values) {
755
- return [...new Set(values.map((value) => path2.resolve(value)))].sort((left, right) => left.localeCompare(right));
1283
+ return [...new Set(values.map((value) => path4.resolve(value)))].sort((left, right) => left.localeCompare(right));
756
1284
  }
757
1285
  async function readConfigJson(rootDir) {
758
- const configPath = path2.join(rootDir, "swarmvault.config.json");
759
- const raw = await fs2.readFile(configPath, "utf8");
1286
+ const configPath = path4.join(rootDir, "swarmvault.config.json");
1287
+ const raw = await fs4.readFile(configPath, "utf8");
760
1288
  const parsed = JSON.parse(raw);
761
1289
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
762
1290
  throw new Error("swarmvault.config.json must contain a JSON object.");
@@ -764,7 +1292,7 @@ async function readConfigJson(rootDir) {
764
1292
  return { path: configPath, content: parsed };
765
1293
  }
766
1294
  async function writeConfigJson(configPath, content) {
767
- await fs2.writeFile(configPath, `${JSON.stringify(content, null, 2)}
1295
+ await fs4.writeFile(configPath, `${JSON.stringify(content, null, 2)}
768
1296
  `, "utf8");
769
1297
  }
770
1298
  function getWatchBlock(config) {
@@ -948,7 +1476,7 @@ async function watchVault(rootDir, options = {}) {
948
1476
  const { paths } = await initWorkspace(rootDir);
949
1477
  const baseDebounceMs = options.debounceMs ?? 900;
950
1478
  const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
951
- const inboxWatchRoot = path2.resolve(paths.inboxDir);
1479
+ const inboxWatchRoot = path4.resolve(paths.inboxDir);
952
1480
  let watchTargets = await resolveWatchTargets(rootDir, paths, options);
953
1481
  let timer;
954
1482
  let running = false;
@@ -963,7 +1491,7 @@ async function watchVault(rootDir, options = {}) {
963
1491
  usePolling: true,
964
1492
  interval: 100,
965
1493
  ignored: (targetPath) => {
966
- const absolutePath = path2.resolve(targetPath);
1494
+ const absolutePath = path4.resolve(targetPath);
967
1495
  const primaryTarget = watchTargets.filter((watchTarget) => isPathWithin(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
968
1496
  if (!primaryTarget) {
969
1497
  return false;
@@ -1165,8 +1693,8 @@ async function watchVault(rootDir, options = {}) {
1165
1693
  }
1166
1694
  };
1167
1695
  const reasonForPath = (targetPath) => {
1168
- const baseDir = watchTargets.filter((watchTarget) => isPathWithin(watchTarget, path2.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
1169
- return path2.relative(baseDir, targetPath) || ".";
1696
+ const baseDir = watchTargets.filter((watchTarget) => isPathWithin(watchTarget, path4.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
1697
+ return path4.relative(baseDir, targetPath) || ".";
1170
1698
  };
1171
1699
  watcher.on("add", (filePath) => schedule(`add:${reasonForPath(filePath)}`)).on("change", (filePath) => schedule(`change:${reasonForPath(filePath)}`)).on("unlink", (filePath) => schedule(`unlink:${reasonForPath(filePath)}`)).on("addDir", (dirPath) => schedule(`addDir:${reasonForPath(dirPath)}`)).on("unlinkDir", (dirPath) => schedule(`unlinkDir:${reasonForPath(dirPath)}`)).on("error", (caught) => schedule(`error:${caught instanceof Error ? caught.message : String(caught)}`));
1172
1700
  await new Promise((resolve, reject) => {
@@ -1239,7 +1767,7 @@ function buildRecommendations(checks) {
1239
1767
  }
1240
1768
  async function currentPackageVersion2() {
1241
1769
  try {
1242
- const raw = await fs3.readFile(new URL("../package.json", import.meta.url), "utf8");
1770
+ const raw = await fs5.readFile(new URL("../package.json", import.meta.url), "utf8");
1243
1771
  const parsed = JSON.parse(raw);
1244
1772
  return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version : "0.0.0";
1245
1773
  } catch {
@@ -1401,7 +1929,7 @@ async function doctorVault(rootDir, options = {}) {
1401
1929
  ok: status === "ok",
1402
1930
  status,
1403
1931
  generatedAt,
1404
- rootDir: path3.resolve(rootDir),
1932
+ rootDir: path5.resolve(rootDir),
1405
1933
  version,
1406
1934
  counts: {
1407
1935
  sources: manifests.length,
@@ -1422,10 +1950,10 @@ async function doctorVault(rootDir, options = {}) {
1422
1950
 
1423
1951
  // src/graph-export.ts
1424
1952
  import { readFileSync } from "fs";
1425
- import fs4 from "fs/promises";
1953
+ import fs6 from "fs/promises";
1426
1954
  import { createRequire } from "module";
1427
- import path4 from "path";
1428
- import matter2 from "gray-matter";
1955
+ import path6 from "path";
1956
+ import matter4 from "gray-matter";
1429
1957
 
1430
1958
  // src/graph-interchange.ts
1431
1959
  function exportHyperedgeNodeId(hyperedge) {
@@ -1902,8 +2430,8 @@ var _visNetworkJs;
1902
2430
  function loadVisNetworkJs() {
1903
2431
  if (!_visNetworkJs) {
1904
2432
  const require2 = createRequire(import.meta.url);
1905
- const pkgDir = path4.dirname(require2.resolve("vis-network/package.json"));
1906
- _visNetworkJs = readFileSync(path4.join(pkgDir, "standalone/umd/vis-network.min.js"), "utf8");
2433
+ const pkgDir = path6.dirname(require2.resolve("vis-network/package.json"));
2434
+ _visNetworkJs = readFileSync(path6.join(pkgDir, "standalone/umd/vis-network.min.js"), "utf8");
1907
2435
  }
1908
2436
  return _visNetworkJs;
1909
2437
  }
@@ -3238,9 +3766,9 @@ async function loadGraph(rootDir) {
3238
3766
  return graph;
3239
3767
  }
3240
3768
  async function writeGraphExport(outputPath, content) {
3241
- await ensureDir(path4.dirname(outputPath));
3242
- await fs4.writeFile(outputPath, content, "utf8");
3243
- return path4.resolve(outputPath);
3769
+ await ensureDir(path6.dirname(outputPath));
3770
+ await fs6.writeFile(outputPath, content, "utf8");
3771
+ return path6.resolve(outputPath);
3244
3772
  }
3245
3773
  async function exportGraphFormat(rootDir, format, outputPath) {
3246
3774
  const graph = await loadGraph(rootDir);
@@ -3251,7 +3779,7 @@ async function exportGraphFormat(rootDir, format, outputPath) {
3251
3779
  async function exportGraphReportHtml(rootDir, outputPath) {
3252
3780
  const { paths } = await loadVaultConfig(rootDir);
3253
3781
  const graph = await loadGraph(rootDir);
3254
- const report = await readJsonFile(path4.join(paths.wikiDir, "graph", "report.json"));
3782
+ const report = await readJsonFile(path6.join(paths.wikiDir, "graph", "report.json"));
3255
3783
  const html = renderGraphReportHtml(graph, report);
3256
3784
  const resolvedPath = await writeGraphExport(outputPath, html);
3257
3785
  return { format: "report", outputPath: resolvedPath };
@@ -3283,7 +3811,7 @@ function typePluralDir(nodeType) {
3283
3811
  function obsidianNodeSlug(node, pageById) {
3284
3812
  if (node.pageId) {
3285
3813
  const page = pageById.get(node.pageId);
3286
- if (page) return path4.basename(page.path, ".md");
3814
+ if (page) return path6.basename(page.path, ".md");
3287
3815
  }
3288
3816
  return slugify(node.label);
3289
3817
  }
@@ -3313,14 +3841,14 @@ async function listFilesRecursive2(dir, base = "") {
3313
3841
  const results = [];
3314
3842
  let entries;
3315
3843
  try {
3316
- entries = await fs4.readdir(dir, { withFileTypes: true });
3844
+ entries = await fs6.readdir(dir, { withFileTypes: true });
3317
3845
  } catch {
3318
3846
  return results;
3319
3847
  }
3320
3848
  for (const entry of entries) {
3321
3849
  const rel = base ? `${base}/${entry.name}` : entry.name;
3322
3850
  if (entry.isDirectory()) {
3323
- results.push(...await listFilesRecursive2(path4.join(dir, entry.name), rel));
3851
+ results.push(...await listFilesRecursive2(path6.join(dir, entry.name), rel));
3324
3852
  } else {
3325
3853
  results.push(rel);
3326
3854
  }
@@ -3366,7 +3894,7 @@ function typedLinkFrontmatter(nodeIds, adjacency, nodesById, wikilinkTarget) {
3366
3894
  async function exportObsidianVault(rootDir, outputDir) {
3367
3895
  const graph = await loadGraph(rootDir);
3368
3896
  const { paths } = await loadVaultConfig(rootDir);
3369
- const resolvedOutputDir = path4.resolve(outputDir);
3897
+ const resolvedOutputDir = path6.resolve(outputDir);
3370
3898
  await ensureDir(resolvedOutputDir);
3371
3899
  const nodesById = graphNodeById(graph);
3372
3900
  const pageById = graphPageById(graph);
@@ -3406,18 +3934,18 @@ async function exportObsidianVault(rootDir, outputDir) {
3406
3934
  const pageByPath = new Map(graph.pages.map((p) => [p.path, p]));
3407
3935
  for (const relPath of wikiFiles) {
3408
3936
  if (!relPath.endsWith(".md")) continue;
3409
- const srcFile = path4.join(paths.wikiDir, relPath);
3410
- const destFile = path4.join(resolvedOutputDir, relPath);
3411
- await ensureDir(path4.dirname(destFile));
3937
+ const srcFile = path6.join(paths.wikiDir, relPath);
3938
+ const destFile = path6.join(resolvedOutputDir, relPath);
3939
+ await ensureDir(path6.dirname(destFile));
3412
3940
  let rawContent;
3413
3941
  try {
3414
- rawContent = await fs4.readFile(srcFile, "utf8");
3942
+ rawContent = await fs6.readFile(srcFile, "utf8");
3415
3943
  } catch {
3416
3944
  continue;
3417
3945
  }
3418
3946
  const matchingPage = pageByPath.get(relPath);
3419
3947
  const pageNodes = matchingPage ? nodesByPageId.get(matchingPage.id) ?? [] : [];
3420
- const parsed = matter2(rawContent);
3948
+ const parsed = matter4(rawContent);
3421
3949
  const data = parsed.data;
3422
3950
  if (pageNodes.length > 0) {
3423
3951
  const primaryNode = pageNodes[0];
@@ -3444,7 +3972,7 @@ async function exportObsidianVault(rootDir, outputDir) {
3444
3972
  data[relation] = links;
3445
3973
  }
3446
3974
  }
3447
- let outputContent = matter2.stringify(parsed.content, data);
3975
+ let outputContent = matter4.stringify(parsed.content, data);
3448
3976
  if (pageNodes.length > 0) {
3449
3977
  const connLines = connectionsSection(
3450
3978
  pageNodes.map((n) => n.id),
@@ -3461,14 +3989,14 @@ ${connLines.join("\n")}
3461
3989
  `;
3462
3990
  }
3463
3991
  }
3464
- await fs4.writeFile(destFile, outputContent, "utf8");
3992
+ await fs6.writeFile(destFile, outputContent, "utf8");
3465
3993
  fileCount++;
3466
3994
  }
3467
3995
  for (const node of orphanNodes) {
3468
3996
  const relPath = orphanFilePath.get(node.id);
3469
- const destFile = path4.join(resolvedOutputDir, relPath);
3470
- await ensureDir(path4.dirname(destFile));
3471
- const slug = path4.basename(relPath, ".md");
3997
+ const destFile = path6.join(resolvedOutputDir, relPath);
3998
+ await ensureDir(path6.dirname(destFile));
3999
+ const slug = path6.basename(relPath, ".md");
3472
4000
  const aliases = node.label !== slug ? [node.label] : [];
3473
4001
  const frontmatter = {
3474
4002
  id: node.id,
@@ -3494,8 +4022,8 @@ ${connLines.join("\n")}
3494
4022
  if (connLines.length > 0) {
3495
4023
  lines.push("## Connections", "", ...connLines, "");
3496
4024
  }
3497
- const content = matter2.stringify(lines.join("\n"), frontmatter);
3498
- await fs4.writeFile(destFile, content, "utf8");
4025
+ const content = matter4.stringify(lines.join("\n"), frontmatter);
4026
+ await fs6.writeFile(destFile, content, "utf8");
3499
4027
  fileCount++;
3500
4028
  }
3501
4029
  const usedCommunityFileNames = /* @__PURE__ */ new Set();
@@ -3523,8 +4051,8 @@ ${connLines.join("\n")}
3523
4051
  });
3524
4052
  });
3525
4053
  const communitySlug = deduplicateFileName(safeFileName(community.label), usedCommunityFileNames);
3526
- const destFile = path4.join(resolvedOutputDir, "graph", "communities", `${communitySlug}.md`);
3527
- await ensureDir(path4.dirname(destFile));
4054
+ const destFile = path6.join(resolvedOutputDir, "graph", "communities", `${communitySlug}.md`);
4055
+ await ensureDir(path6.dirname(destFile));
3528
4056
  const lines = [`# ${community.label}`, "", "## Members", ""];
3529
4057
  for (const member of memberNodes) {
3530
4058
  const target = wikilinkTarget.get(member.id);
@@ -3552,18 +4080,18 @@ ${connLines.join("\n")}
3552
4080
  node_count: memberNodes.length,
3553
4081
  cohesion: Number(cohesion.toFixed(4))
3554
4082
  };
3555
- const content = matter2.stringify(lines.join("\n"), frontmatter);
3556
- await fs4.writeFile(destFile, content, "utf8");
4083
+ const content = matter4.stringify(lines.join("\n"), frontmatter);
4084
+ await fs6.writeFile(destFile, content, "utf8");
3557
4085
  fileCount++;
3558
4086
  }
3559
- const outputsAssetsDir = path4.join(paths.wikiDir, "outputs", "assets");
4087
+ const outputsAssetsDir = path6.join(paths.wikiDir, "outputs", "assets");
3560
4088
  try {
3561
4089
  const assetFiles = await listFilesRecursive2(outputsAssetsDir);
3562
4090
  for (const relAsset of assetFiles) {
3563
- const src = path4.join(outputsAssetsDir, relAsset);
3564
- const dest = path4.join(resolvedOutputDir, "outputs", "assets", relAsset);
3565
- await ensureDir(path4.dirname(dest));
3566
- await fs4.copyFile(src, dest);
4091
+ const src = path6.join(outputsAssetsDir, relAsset);
4092
+ const dest = path6.join(resolvedOutputDir, "outputs", "assets", relAsset);
4093
+ await ensureDir(path6.dirname(dest));
4094
+ await fs6.copyFile(src, dest);
3567
4095
  fileCount++;
3568
4096
  }
3569
4097
  } catch {
@@ -3571,15 +4099,15 @@ ${connLines.join("\n")}
3571
4099
  try {
3572
4100
  const rawAssetFiles = await listFilesRecursive2(paths.rawAssetsDir);
3573
4101
  for (const relAsset of rawAssetFiles) {
3574
- const src = path4.join(paths.rawAssetsDir, relAsset);
3575
- const dest = path4.join(resolvedOutputDir, "raw", "assets", relAsset);
3576
- await ensureDir(path4.dirname(dest));
3577
- await fs4.copyFile(src, dest);
4102
+ const src = path6.join(paths.rawAssetsDir, relAsset);
4103
+ const dest = path6.join(resolvedOutputDir, "raw", "assets", relAsset);
4104
+ await ensureDir(path6.dirname(dest));
4105
+ await fs6.copyFile(src, dest);
3578
4106
  fileCount++;
3579
4107
  }
3580
4108
  } catch {
3581
4109
  }
3582
- const obsidianDir = path4.join(resolvedOutputDir, ".obsidian");
4110
+ const obsidianDir = path6.join(resolvedOutputDir, ".obsidian");
3583
4111
  await ensureDir(obsidianDir);
3584
4112
  const projectIds = Object.keys(
3585
4113
  graph.pages.reduce(
@@ -3603,8 +4131,8 @@ ${connLines.join("\n")}
3603
4131
  color: hexToObsidianColor(["#0ea5e9", "#22c55e", "#f59e0b", "#8b5cf6", "#fb7185", "#14b8a6"][index % 6])
3604
4132
  }));
3605
4133
  const colorGroups = [...nodeTypeGroups, ...projectColorGroups];
3606
- await fs4.writeFile(
3607
- path4.join(obsidianDir, "app.json"),
4134
+ await fs6.writeFile(
4135
+ path6.join(obsidianDir, "app.json"),
3608
4136
  JSON.stringify(
3609
4137
  { newFileLocation: "folder", newFileFolderPath: "outputs", attachmentFolderPath: "raw/assets", useMarkdownLinks: false },
3610
4138
  null,
@@ -3612,13 +4140,13 @@ ${connLines.join("\n")}
3612
4140
  ),
3613
4141
  "utf8"
3614
4142
  );
3615
- await fs4.writeFile(
3616
- path4.join(obsidianDir, "core-plugins.json"),
4143
+ await fs6.writeFile(
4144
+ path6.join(obsidianDir, "core-plugins.json"),
3617
4145
  JSON.stringify(["file-explorer", "global-search", "graph", "backlink", "tag-pane", "page-preview", "outline"], null, 2),
3618
4146
  "utf8"
3619
4147
  );
3620
- await fs4.writeFile(
3621
- path4.join(obsidianDir, "graph.json"),
4148
+ await fs6.writeFile(
4149
+ path6.join(obsidianDir, "graph.json"),
3622
4150
  JSON.stringify(
3623
4151
  { colorGroups, "collapse-filter": false, search: "", showTags: true, showAttachments: false, showOrphans: true },
3624
4152
  null,
@@ -3626,9 +4154,9 @@ ${connLines.join("\n")}
3626
4154
  ),
3627
4155
  "utf8"
3628
4156
  );
3629
- await fs4.writeFile(path4.join(obsidianDir, "types.json"), JSON.stringify({ types: OBSIDIAN_PROPERTY_TYPES }, null, 2), "utf8");
4157
+ await fs6.writeFile(path6.join(obsidianDir, "types.json"), JSON.stringify({ types: OBSIDIAN_PROPERTY_TYPES }, null, 2), "utf8");
3630
4158
  fileCount += 4;
3631
- const dashboardDir = path4.join(resolvedOutputDir, "graph", "dashboards");
4159
+ const dashboardDir = path6.join(resolvedOutputDir, "graph", "dashboards");
3632
4160
  await ensureDir(dashboardDir);
3633
4161
  const dvPages = [
3634
4162
  {
@@ -3665,7 +4193,7 @@ ${connLines.join("\n")}
3665
4193
  ${dv.query}
3666
4194
  \`\`\`
3667
4195
  `;
3668
- await fs4.writeFile(path4.join(dashboardDir, `${dv.name}.md`), matter2.stringify(dvBody, dvFrontmatter), "utf8");
4196
+ await fs6.writeFile(path6.join(dashboardDir, `${dv.name}.md`), matter4.stringify(dvBody, dvFrontmatter), "utf8");
3669
4197
  fileCount++;
3670
4198
  }
3671
4199
  return { format: "obsidian", outputPath: resolvedOutputDir, fileCount };
@@ -3787,8 +4315,8 @@ Community: ${communityLabel}`,
3787
4315
  }
3788
4316
 
3789
4317
  // src/graph-merge.ts
3790
- import fs5 from "fs/promises";
3791
- import path5 from "path";
4318
+ import fs7 from "fs/promises";
4319
+ import path7 from "path";
3792
4320
  function isRecord(value) {
3793
4321
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3794
4322
  }
@@ -3816,7 +4344,7 @@ function numberField(record, field, fallback) {
3816
4344
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
3817
4345
  }
3818
4346
  function safePrefix(inputPath, index) {
3819
- return slugify(path5.basename(inputPath, path5.extname(inputPath)) || `graph-${index + 1}`);
4347
+ return slugify(path7.basename(inputPath, path7.extname(inputPath)) || `graph-${index + 1}`);
3820
4348
  }
3821
4349
  function prefixed(prefix, id) {
3822
4350
  return `${prefix}:${id}`;
@@ -3866,7 +4394,7 @@ function remapSwarmVaultGraph(inputPath, graph, prefix) {
3866
4394
  const pages = graph.pages.map((page) => ({
3867
4395
  ...page,
3868
4396
  id: pageMap.get(page.id) ?? prefixed(prefix, page.id),
3869
- path: toPosix(path5.posix.join("merged", prefix, page.path)),
4397
+ path: toPosix(path7.posix.join("merged", prefix, page.path)),
3870
4398
  sourceIds: page.sourceIds.map((sourceId) => sourceMap.get(sourceId) ?? prefixed(prefix, sourceId)),
3871
4399
  nodeIds: page.nodeIds.map((nodeId) => nodeMap.get(nodeId) ?? prefixed(prefix, nodeId)),
3872
4400
  relatedPageIds: page.relatedPageIds.map((pageId) => pageMap.get(pageId) ?? prefixed(prefix, pageId)),
@@ -4008,7 +4536,7 @@ function remapNodeLinkGraph(inputPath, raw, prefix, now) {
4008
4536
  });
4009
4537
  const page = {
4010
4538
  id: prefixed(prefix, "page"),
4011
- path: toPosix(path5.posix.join("merged", prefix, "index.md")),
4539
+ path: toPosix(path7.posix.join("merged", prefix, "index.md")),
4012
4540
  title: `${prefix} merged graph`,
4013
4541
  kind: "source",
4014
4542
  sourceClass: "generated",
@@ -4079,8 +4607,8 @@ async function mergeGraphFiles(inputPaths, outputPath, options = {}) {
4079
4607
  const inputGraphs = [];
4080
4608
  const warnings = [];
4081
4609
  for (const [index, inputPath] of inputPaths.entries()) {
4082
- const resolvedInputPath = path5.resolve(inputPath);
4083
- const raw = JSON.parse(await fs5.readFile(resolvedInputPath, "utf8"));
4610
+ const resolvedInputPath = path7.resolve(inputPath);
4611
+ const raw = JSON.parse(await fs7.readFile(resolvedInputPath, "utf8"));
4084
4612
  const prefix = ensureUniquePrefix(
4085
4613
  inputPaths.length === 1 && options.label ? slugify(options.label) : safePrefix(resolvedInputPath, index),
4086
4614
  usedPrefixes
@@ -4115,9 +4643,9 @@ async function mergeGraphFiles(inputPaths, outputPath, options = {}) {
4115
4643
  throw new Error("No supported graph inputs were found.");
4116
4644
  }
4117
4645
  const graph = mergeGraphs(graphs, now);
4118
- const resolvedOutputPath = path5.resolve(outputPath);
4119
- await ensureDir(path5.dirname(resolvedOutputPath));
4120
- await fs5.writeFile(resolvedOutputPath, `${JSON.stringify(graph, null, 2)}
4646
+ const resolvedOutputPath = path7.resolve(outputPath);
4647
+ await ensureDir(path7.dirname(resolvedOutputPath));
4648
+ await fs7.writeFile(resolvedOutputPath, `${JSON.stringify(graph, null, 2)}
4121
4649
  `, "utf8");
4122
4650
  return {
4123
4651
  outputPath: resolvedOutputPath,
@@ -4128,8 +4656,8 @@ async function mergeGraphFiles(inputPaths, outputPath, options = {}) {
4128
4656
  }
4129
4657
 
4130
4658
  // src/graph-push.ts
4131
- import fs6 from "fs/promises";
4132
- import path6 from "path";
4659
+ import fs8 from "fs/promises";
4660
+ import path8 from "path";
4133
4661
  import neo4j from "neo4j-driver";
4134
4662
  var DEFAULT_NEO4J_BATCH_SIZE = 500;
4135
4663
  var DEFAULT_NEO4J_DATABASE = "neo4j";
@@ -4140,8 +4668,8 @@ function requireConfigValue(value, name) {
4140
4668
  throw new Error(`Neo4j push requires ${name}. Configure \`graphSinks.neo4j.${name}\` or pass the matching CLI flag.`);
4141
4669
  }
4142
4670
  async function deriveVaultId(rootDir) {
4143
- const realRoot = await fs6.realpath(rootDir).catch(() => path6.resolve(rootDir));
4144
- const label = slugify(path6.basename(realRoot));
4671
+ const realRoot = await fs8.realpath(rootDir).catch(() => path8.resolve(rootDir));
4672
+ const label = slugify(path8.basename(realRoot));
4145
4673
  return `${label}-${sha256(realRoot).slice(0, 12)}`;
4146
4674
  }
4147
4675
  async function resolveNeo4jPushConfig(rootDir, options) {
@@ -4171,7 +4699,7 @@ function normalizeBatchSize(value) {
4171
4699
  }
4172
4700
  async function loadGraph2(rootDir) {
4173
4701
  const { paths } = await loadVaultConfig(rootDir);
4174
- const raw = JSON.parse(await fs6.readFile(paths.graphPath, "utf8"));
4702
+ const raw = JSON.parse(await fs8.readFile(paths.graphPath, "utf8"));
4175
4703
  return raw;
4176
4704
  }
4177
4705
  function buildResult(input) {
@@ -4256,7 +4784,7 @@ async function writeSyncNode(session, input) {
4256
4784
  ].join("\n"),
4257
4785
  {
4258
4786
  vaultId: input.vaultId,
4259
- rootDir: path6.resolve(input.rootDir),
4787
+ rootDir: path8.resolve(input.rootDir),
4260
4788
  graphGeneratedAt: input.graph.generatedAt,
4261
4789
  graphHash: graphHash(input.graph),
4262
4790
  pushedAt: input.pushedAt,
@@ -4342,7 +4870,7 @@ async function pushGraphNeo4j(rootDir, options = {}) {
4342
4870
  }
4343
4871
 
4344
4872
  // src/graph-status.ts
4345
- import path7 from "path";
4873
+ import path9 from "path";
4346
4874
  function recommendedCommand(input) {
4347
4875
  if (!input.graphExists || !input.reportExists) {
4348
4876
  return "swarmvault compile";
@@ -4358,8 +4886,8 @@ function recommendedCommand(input) {
4358
4886
  async function getGraphStatus(rootDir, options = {}) {
4359
4887
  const { paths } = await loadVaultConfig(rootDir);
4360
4888
  const graphPath = paths.graphPath;
4361
- const reportPath = path7.join(paths.wikiDir, "graph", "report.md");
4362
- const resolvedOverrideRoots = options.repoRoots?.map((repoRoot) => path7.resolve(rootDir, repoRoot));
4889
+ const reportPath = path9.join(paths.wikiDir, "graph", "report.md");
4890
+ const resolvedOverrideRoots = options.repoRoots?.map((repoRoot) => path9.resolve(rootDir, repoRoot));
4363
4891
  const [graphExists, reportExists, trackedRepoRoots, changes, pendingSemanticRefresh] = await Promise.all([
4364
4892
  fileExists(graphPath),
4365
4893
  fileExists(reportPath),
@@ -4393,7 +4921,7 @@ async function getGraphStatus(rootDir, options = {}) {
4393
4921
  }
4394
4922
 
4395
4923
  // src/graph-tree.ts
4396
- import path8 from "path";
4924
+ import path10 from "path";
4397
4925
  var DEFAULT_MAX_CHILDREN = 250;
4398
4926
  function compareTreeNodes(left, right) {
4399
4927
  const kindOrder = /* @__PURE__ */ new Map([
@@ -4435,11 +4963,11 @@ function normalizeSourcePath(rootDir, source) {
4435
4963
  if (source.repoRelativePath) {
4436
4964
  return toPosix(source.repoRelativePath);
4437
4965
  }
4438
- if (rootDir && path8.isAbsolute(candidate) && isPathWithin(rootDir, candidate)) {
4439
- return toPosix(path8.relative(rootDir, candidate));
4966
+ if (rootDir && path10.isAbsolute(candidate) && isPathWithin(rootDir, candidate)) {
4967
+ return toPosix(path10.relative(rootDir, candidate));
4440
4968
  }
4441
- if (path8.isAbsolute(candidate)) {
4442
- return toPosix(path8.basename(candidate));
4969
+ if (path10.isAbsolute(candidate)) {
4970
+ return toPosix(path10.basename(candidate));
4443
4971
  }
4444
4972
  return toPosix(candidate).replace(/^\/+/, "") || source.title || source.sourceId;
4445
4973
  }
@@ -4758,9 +5286,9 @@ async function exportGraphTree(rootDir, outputPath, options = {}) {
4758
5286
  throw new Error(`Graph artifact not found at ${paths.graphPath}. Run swarmvault compile first.`);
4759
5287
  }
4760
5288
  const tree = buildGraphTree(graph, { ...options, rootDir });
4761
- const resolvedOutputPath = path8.resolve(rootDir, outputPath ?? path8.join(paths.wikiDir, "graph", "tree.html"));
4762
- await ensureDir(path8.dirname(resolvedOutputPath));
4763
- await import("fs/promises").then((fs13) => fs13.writeFile(resolvedOutputPath, renderGraphTreeHtml(tree, graph), "utf8"));
5289
+ const resolvedOutputPath = path10.resolve(rootDir, outputPath ?? path10.join(paths.wikiDir, "graph", "tree.html"));
5290
+ await ensureDir(path10.dirname(resolvedOutputPath));
5291
+ await import("fs/promises").then((fs15) => fs15.writeFile(resolvedOutputPath, renderGraphTreeHtml(tree, graph), "utf8"));
4764
5292
  return {
4765
5293
  outputPath: resolvedOutputPath,
4766
5294
  sourceCount: graph.sources.length,
@@ -4770,26 +5298,26 @@ async function exportGraphTree(rootDir, outputPath, options = {}) {
4770
5298
  }
4771
5299
 
4772
5300
  // src/hooks.ts
4773
- import fs7 from "fs/promises";
4774
- import path9 from "path";
5301
+ import fs9 from "fs/promises";
5302
+ import path11 from "path";
4775
5303
  import process3 from "process";
4776
5304
  var hookStart = "# >>> swarmvault hook >>>";
4777
5305
  var hookEnd = "# <<< swarmvault hook <<<";
4778
5306
  async function findNearestGitRoot(startPath) {
4779
- let current = path9.resolve(startPath);
5307
+ let current = path11.resolve(startPath);
4780
5308
  try {
4781
- const stat = await fs7.stat(current);
5309
+ const stat = await fs9.stat(current);
4782
5310
  if (!stat.isDirectory()) {
4783
- current = path9.dirname(current);
5311
+ current = path11.dirname(current);
4784
5312
  }
4785
5313
  } catch {
4786
- current = path9.dirname(current);
5314
+ current = path11.dirname(current);
4787
5315
  }
4788
5316
  while (true) {
4789
- if (await fileExists(path9.join(current, ".git"))) {
5317
+ if (await fileExists(path11.join(current, ".git"))) {
4790
5318
  return current;
4791
5319
  }
4792
- const parent = path9.dirname(current);
5320
+ const parent = path11.dirname(current);
4793
5321
  if (parent === current) {
4794
5322
  return null;
4795
5323
  }
@@ -4801,8 +5329,8 @@ function shellQuote(value) {
4801
5329
  }
4802
5330
  function resolveSwarmvaultExecutableCandidate() {
4803
5331
  const argvPath = process3.argv[1];
4804
- if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path9.sep}@swarmvaultai${path9.sep}cli${path9.sep}`) || argvPath.includes(`${path9.sep}packages${path9.sep}cli${path9.sep}`))) {
4805
- return path9.resolve(argvPath);
5332
+ if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path11.sep}@swarmvaultai${path11.sep}cli${path11.sep}`) || argvPath.includes(`${path11.sep}packages${path11.sep}cli${path11.sep}`))) {
5333
+ return path11.resolve(argvPath);
4806
5334
  }
4807
5335
  return "swarmvault";
4808
5336
  }
@@ -4821,17 +5349,17 @@ function managedHookBlock(vaultRoot) {
4821
5349
  ].join("\n");
4822
5350
  }
4823
5351
  function hookPath(repoRoot, hookName) {
4824
- return path9.join(repoRoot, ".git", "hooks", hookName);
5352
+ return path11.join(repoRoot, ".git", "hooks", hookName);
4825
5353
  }
4826
5354
  async function readHookStatus(filePath) {
4827
5355
  if (!await fileExists(filePath)) {
4828
5356
  return "not_installed";
4829
5357
  }
4830
- const content = await fs7.readFile(filePath, "utf8");
5358
+ const content = await fs9.readFile(filePath, "utf8");
4831
5359
  return content.includes(hookStart) && content.includes(hookEnd) ? "installed" : "other_content";
4832
5360
  }
4833
5361
  async function upsertHookFile(filePath, block) {
4834
- const existing = await fileExists(filePath) ? await fs7.readFile(filePath, "utf8") : "";
5362
+ const existing = await fileExists(filePath) ? await fs9.readFile(filePath, "utf8") : "";
4835
5363
  let next;
4836
5364
  const startIndex = existing.indexOf(hookStart);
4837
5365
  const endIndex = existing.indexOf(hookEnd);
@@ -4845,16 +5373,16 @@ ${block}`.trimEnd();
4845
5373
  next = `#!/bin/sh
4846
5374
  ${block}`.trimEnd();
4847
5375
  }
4848
- await ensureDir(path9.dirname(filePath));
4849
- await fs7.writeFile(filePath, `${next}
5376
+ await ensureDir(path11.dirname(filePath));
5377
+ await fs9.writeFile(filePath, `${next}
4850
5378
  `, { mode: 493, encoding: "utf8" });
4851
- await fs7.chmod(filePath, 493);
5379
+ await fs9.chmod(filePath, 493);
4852
5380
  }
4853
5381
  async function removeHookBlock(filePath) {
4854
5382
  if (!await fileExists(filePath)) {
4855
5383
  return;
4856
5384
  }
4857
- const existing = await fs7.readFile(filePath, "utf8");
5385
+ const existing = await fs9.readFile(filePath, "utf8");
4858
5386
  const startIndex = existing.indexOf(hookStart);
4859
5387
  const endIndex = existing.indexOf(hookEnd);
4860
5388
  if (startIndex === -1 || endIndex === -1) {
@@ -4862,10 +5390,10 @@ async function removeHookBlock(filePath) {
4862
5390
  }
4863
5391
  const next = `${existing.slice(0, startIndex)}${existing.slice(endIndex + hookEnd.length)}`.trim();
4864
5392
  if (!next || next === "#!/bin/sh") {
4865
- await fs7.rm(filePath, { force: true });
5393
+ await fs9.rm(filePath, { force: true });
4866
5394
  return;
4867
5395
  }
4868
- await fs7.writeFile(filePath, `${next}
5396
+ await fs9.writeFile(filePath, `${next}
4869
5397
  `, "utf8");
4870
5398
  }
4871
5399
  async function getGitHookStatus(rootDir) {
@@ -4888,7 +5416,7 @@ async function installGitHooks(rootDir) {
4888
5416
  if (!repoRoot) {
4889
5417
  throw new Error("No git repository found above the current vault.");
4890
5418
  }
4891
- const block = managedHookBlock(path9.resolve(rootDir));
5419
+ const block = managedHookBlock(path11.resolve(rootDir));
4892
5420
  await upsertHookFile(hookPath(repoRoot, "post-commit"), block);
4893
5421
  await upsertHookFile(hookPath(repoRoot, "post-checkout"), block);
4894
5422
  return getGitHookStatus(rootDir);
@@ -4908,12 +5436,12 @@ async function uninstallGitHooks(rootDir) {
4908
5436
  }
4909
5437
 
4910
5438
  // src/mcp.ts
4911
- import fs8 from "fs/promises";
4912
- import path10 from "path";
5439
+ import fs10 from "fs/promises";
5440
+ import path12 from "path";
4913
5441
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4914
5442
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4915
5443
  import { z } from "zod";
4916
- var SERVER_VERSION = "3.11.0";
5444
+ var SERVER_VERSION = "3.12.0";
4917
5445
  var codeLanguageSchema = z.enum([
4918
5446
  "javascript",
4919
5447
  "jsx",
@@ -5670,7 +6198,7 @@ async function createMcpServer(rootDir) {
5670
6198
  },
5671
6199
  async () => {
5672
6200
  const { paths } = await loadVaultConfig(rootDir);
5673
- const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path10.relative(paths.sessionsDir, filePath))).sort();
6201
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path12.relative(paths.sessionsDir, filePath))).sort();
5674
6202
  return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
5675
6203
  }
5676
6204
  );
@@ -5739,8 +6267,8 @@ async function createMcpServer(rootDir) {
5739
6267
  return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
5740
6268
  }
5741
6269
  const { paths } = await loadVaultConfig(rootDir);
5742
- const absolutePath = path10.resolve(paths.wikiDir, relativePath);
5743
- return asTextResource(`swarmvault://pages/${encodedPath}`, await fs8.readFile(absolutePath, "utf8"));
6270
+ const absolutePath = path12.resolve(paths.wikiDir, relativePath);
6271
+ return asTextResource(`swarmvault://pages/${encodedPath}`, await fs10.readFile(absolutePath, "utf8"));
5744
6272
  }
5745
6273
  );
5746
6274
  server.registerResource(
@@ -5748,11 +6276,11 @@ async function createMcpServer(rootDir) {
5748
6276
  new ResourceTemplate("swarmvault://sessions/{path}", {
5749
6277
  list: async () => {
5750
6278
  const { paths } = await loadVaultConfig(rootDir);
5751
- const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path10.relative(paths.sessionsDir, filePath))).sort();
6279
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path12.relative(paths.sessionsDir, filePath))).sort();
5752
6280
  return {
5753
6281
  resources: files.map((relativePath) => ({
5754
6282
  uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
5755
- name: path10.basename(relativePath, ".md"),
6283
+ name: path12.basename(relativePath, ".md"),
5756
6284
  title: relativePath,
5757
6285
  description: "SwarmVault session artifact",
5758
6286
  mimeType: "text/markdown"
@@ -5769,11 +6297,11 @@ async function createMcpServer(rootDir) {
5769
6297
  const { paths } = await loadVaultConfig(rootDir);
5770
6298
  const encodedPath = typeof variables.path === "string" ? variables.path : "";
5771
6299
  const relativePath = decodeURIComponent(encodedPath);
5772
- const absolutePath = path10.resolve(paths.sessionsDir, relativePath);
6300
+ const absolutePath = path12.resolve(paths.sessionsDir, relativePath);
5773
6301
  if (!isPathWithin(paths.sessionsDir, absolutePath) || !await fileExists(absolutePath)) {
5774
6302
  return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
5775
6303
  }
5776
- return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs8.readFile(absolutePath, "utf8"));
6304
+ return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs10.readFile(absolutePath, "utf8"));
5777
6305
  }
5778
6306
  );
5779
6307
  return server;
@@ -5833,9 +6361,9 @@ function asTextResource(uri, text) {
5833
6361
 
5834
6362
  // src/providers/local-whisper-setup.ts
5835
6363
  import { createWriteStream, constants as fsConstants } from "fs";
5836
- import fs9 from "fs/promises";
6364
+ import fs11 from "fs/promises";
5837
6365
  import os from "os";
5838
- import path11 from "path";
6366
+ import path13 from "path";
5839
6367
  import { Readable } from "stream";
5840
6368
  import { pipeline } from "stream/promises";
5841
6369
  var BINARY_CANDIDATES = ["whisper-cli", "whisper-cpp", "whisper"];
@@ -5863,10 +6391,10 @@ async function discoverLocalWhisperBinary(options = {}) {
5863
6391
  }
5864
6392
  const pathValue = env.PATH ?? "";
5865
6393
  const candidates = [];
5866
- for (const dir of pathValue.split(path11.delimiter)) {
6394
+ for (const dir of pathValue.split(path13.delimiter)) {
5867
6395
  if (!dir) continue;
5868
6396
  for (const name of BINARY_CANDIDATES) {
5869
- const full = path11.join(dir, name);
6397
+ const full = path13.join(dir, name);
5870
6398
  candidates.push(full);
5871
6399
  if (await isExecutable(full)) {
5872
6400
  return { binaryPath: full, candidates, source: "path" };
@@ -5877,14 +6405,14 @@ async function discoverLocalWhisperBinary(options = {}) {
5877
6405
  }
5878
6406
  function expectedModelPath(modelName, homeDir) {
5879
6407
  const home = homeDir ?? os.homedir();
5880
- return path11.join(home, ".swarmvault", "models", `ggml-${modelName}.bin`);
6408
+ return path13.join(home, ".swarmvault", "models", `ggml-${modelName}.bin`);
5881
6409
  }
5882
6410
  function modelDownloadUrl(modelName) {
5883
6411
  return `${HUGGINGFACE_BASE}/ggml-${modelName}.bin`;
5884
6412
  }
5885
6413
  async function downloadWhisperModel(options) {
5886
6414
  const destPath = expectedModelPath(options.modelName, options.homeDir);
5887
- await ensureDir(path11.dirname(destPath));
6415
+ await ensureDir(path13.dirname(destPath));
5888
6416
  const doFetch = options.fetchImpl ?? fetch;
5889
6417
  const url = modelDownloadUrl(options.modelName);
5890
6418
  const response = await doFetch(url);
@@ -5905,8 +6433,8 @@ async function downloadWhisperModel(options) {
5905
6433
  });
5906
6434
  const tmpPath = `${destPath}.part`;
5907
6435
  await pipeline(source, createWriteStream(tmpPath));
5908
- await fs9.rename(tmpPath, destPath);
5909
- const stat = await fs9.stat(destPath);
6436
+ await fs11.rename(tmpPath, destPath);
6437
+ const stat = await fs11.stat(destPath);
5910
6438
  return { path: destPath, bytes: stat.size };
5911
6439
  }
5912
6440
  async function registerLocalWhisperProvider(options) {
@@ -5973,7 +6501,7 @@ async function summarizeLocalWhisperSetup(options) {
5973
6501
  }
5974
6502
  async function isExecutable(p) {
5975
6503
  try {
5976
- await fs9.access(p, fsConstants.X_OK);
6504
+ await fs11.access(p, fsConstants.X_OK);
5977
6505
  return true;
5978
6506
  } catch {
5979
6507
  return false;
@@ -6043,13 +6571,13 @@ async function withCapabilityFallback(provider, capability, run, fallback) {
6043
6571
  }
6044
6572
 
6045
6573
  // src/schedule.ts
6046
- import fs10 from "fs/promises";
6047
- import path12 from "path";
6574
+ import fs12 from "fs/promises";
6575
+ import path14 from "path";
6048
6576
  function scheduleStatePath(schedulesDir, jobId) {
6049
- return path12.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
6577
+ return path14.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
6050
6578
  }
6051
6579
  function scheduleLockPath(schedulesDir, jobId) {
6052
- return path12.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
6580
+ return path14.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
6053
6581
  }
6054
6582
  function parseEveryDuration(value) {
6055
6583
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -6152,13 +6680,13 @@ async function acquireJobLease(rootDir, jobId) {
6152
6680
  const { paths } = await loadVaultConfig(rootDir);
6153
6681
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
6154
6682
  await ensureDir(paths.schedulesDir);
6155
- const handle = await fs10.open(leasePath, "wx");
6683
+ const handle = await fs12.open(leasePath, "wx");
6156
6684
  await handle.writeFile(`${process.pid}
6157
6685
  ${(/* @__PURE__ */ new Date()).toISOString()}
6158
6686
  `);
6159
6687
  await handle.close();
6160
6688
  return async () => {
6161
- await fs10.rm(leasePath, { force: true });
6689
+ await fs12.rm(leasePath, { force: true });
6162
6690
  };
6163
6691
  }
6164
6692
  async function listSchedules(rootDir) {
@@ -6317,9 +6845,9 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
6317
6845
 
6318
6846
  // src/sources.ts
6319
6847
  import { spawn } from "child_process";
6320
- import fs11 from "fs/promises";
6321
- import path13 from "path";
6322
- import matter3 from "gray-matter";
6848
+ import fs13 from "fs/promises";
6849
+ import path15 from "path";
6850
+ import matter5 from "gray-matter";
6323
6851
  import { JSDOM } from "jsdom";
6324
6852
  var DEFAULT_CRAWL_MAX_PAGES = 12;
6325
6853
  var DEFAULT_CRAWL_MAX_DEPTH = 2;
@@ -6364,24 +6892,24 @@ function emptyManagedSourceSyncCounts() {
6364
6892
  };
6365
6893
  }
6366
6894
  function withinRoot(rootPath, targetPath) {
6367
- const relative = path13.relative(rootPath, targetPath);
6368
- return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
6895
+ const relative = path15.relative(rootPath, targetPath);
6896
+ return relative === "" || !relative.startsWith("..") && !path15.isAbsolute(relative);
6369
6897
  }
6370
6898
  async function findNearestGitRoot2(startPath) {
6371
- let current = path13.resolve(startPath);
6899
+ let current = path15.resolve(startPath);
6372
6900
  try {
6373
- const stat = await fs11.stat(current);
6901
+ const stat = await fs13.stat(current);
6374
6902
  if (!stat.isDirectory()) {
6375
- current = path13.dirname(current);
6903
+ current = path15.dirname(current);
6376
6904
  }
6377
6905
  } catch {
6378
- current = path13.dirname(current);
6906
+ current = path15.dirname(current);
6379
6907
  }
6380
6908
  while (true) {
6381
- if (await fileExists(path13.join(current, ".git"))) {
6909
+ if (await fileExists(path15.join(current, ".git"))) {
6382
6910
  return current;
6383
6911
  }
6384
- const parent = path13.dirname(current);
6912
+ const parent = path15.dirname(current);
6385
6913
  if (parent === current) {
6386
6914
  return null;
6387
6915
  }
@@ -6455,7 +6983,7 @@ function isAllowedDocsCandidate(candidate, startUrl) {
6455
6983
  if (candidate.origin !== startUrl.origin) {
6456
6984
  return false;
6457
6985
  }
6458
- const extension = path13.extname(candidate.pathname).toLowerCase();
6986
+ const extension = path15.extname(candidate.pathname).toLowerCase();
6459
6987
  if (extension && extension !== ".html" && extension !== ".htm" && extension !== ".md") {
6460
6988
  return false;
6461
6989
  }
@@ -6544,7 +7072,7 @@ function matchesManagedSourceSpec(existing, input) {
6544
7072
  return false;
6545
7073
  }
6546
7074
  if (input.kind === "directory" || input.kind === "file") {
6547
- return path13.resolve(existing.path ?? "") === path13.resolve(input.path);
7075
+ return path15.resolve(existing.path ?? "") === path15.resolve(input.path);
6548
7076
  }
6549
7077
  if (input.kind === "github_repo") {
6550
7078
  return (existing.url ?? "") === input.url && (existing.branch ?? "") === (input.branch ?? "") && (existing.ref ?? "") === (input.ref ?? "");
@@ -6569,15 +7097,15 @@ function normalizeCheckoutDir(rootDir, value) {
6569
7097
  if (!trimmed) {
6570
7098
  return void 0;
6571
7099
  }
6572
- return path13.isAbsolute(trimmed) ? path13.resolve(trimmed) : path13.resolve(rootDir, trimmed);
7100
+ return path15.isAbsolute(trimmed) ? path15.resolve(trimmed) : path15.resolve(rootDir, trimmed);
6573
7101
  }
6574
7102
  async function resolveManagedSourceInput(rootDir, input, options = {}) {
6575
- const absoluteInput = path13.resolve(rootDir, input);
7103
+ const absoluteInput = path15.resolve(rootDir, input);
6576
7104
  if (!(input.startsWith("http://") || input.startsWith("https://"))) {
6577
7105
  if (options.branch || options.ref || options.checkoutDir) {
6578
7106
  throw new Error("Git branch/ref/checkout options are only supported for public GitHub repo root URLs.");
6579
7107
  }
6580
- const stat = await fs11.stat(absoluteInput).catch(() => null);
7108
+ const stat = await fs13.stat(absoluteInput).catch(() => null);
6581
7109
  if (!stat) {
6582
7110
  throw new Error(`Source not found: ${input}`);
6583
7111
  }
@@ -6585,7 +7113,7 @@ async function resolveManagedSourceInput(rootDir, input, options = {}) {
6585
7113
  return {
6586
7114
  kind: "file",
6587
7115
  path: absoluteInput,
6588
- title: path13.basename(absoluteInput, path13.extname(absoluteInput)) || absoluteInput
7116
+ title: path15.basename(absoluteInput, path15.extname(absoluteInput)) || absoluteInput
6589
7117
  };
6590
7118
  }
6591
7119
  if (!stat.isDirectory()) {
@@ -6597,7 +7125,7 @@ async function resolveManagedSourceInput(rootDir, input, options = {}) {
6597
7125
  kind: "directory",
6598
7126
  path: absoluteInput,
6599
7127
  repoRoot,
6600
- title: path13.basename(absoluteInput) || absoluteInput
7128
+ title: path15.basename(absoluteInput) || absoluteInput
6601
7129
  };
6602
7130
  }
6603
7131
  const github = normalizeGitHubRepoRootUrl(input);
@@ -6626,16 +7154,16 @@ async function resolveManagedSourceInput(rootDir, input, options = {}) {
6626
7154
  };
6627
7155
  }
6628
7156
  function directorySourceIdsFor(manifests, inputPath) {
6629
- return manifests.filter((manifest) => manifest.originalPath && withinRoot(path13.resolve(inputPath), path13.resolve(manifest.originalPath))).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
7157
+ return manifests.filter((manifest) => manifest.originalPath && withinRoot(path15.resolve(inputPath), path15.resolve(manifest.originalPath))).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
6630
7158
  }
6631
7159
  function fileSourceIdsFor(manifests, inputPath) {
6632
- const absoluteInput = path13.resolve(inputPath);
6633
- return manifests.filter((manifest) => manifest.originalPath && path13.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
7160
+ const absoluteInput = path15.resolve(inputPath);
7161
+ return manifests.filter((manifest) => manifest.originalPath && path15.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
6634
7162
  }
6635
7163
  async function syncDirectorySource(rootDir, inputPath, repoRoot) {
6636
7164
  const manifestsBefore = await listManifests(rootDir);
6637
7165
  const previousInScope = manifestsBefore.filter(
6638
- (manifest) => manifest.originalPath && withinRoot(path13.resolve(inputPath), path13.resolve(manifest.originalPath))
7166
+ (manifest) => manifest.originalPath && withinRoot(path15.resolve(inputPath), path15.resolve(manifest.originalPath))
6639
7167
  );
6640
7168
  const result = await ingestDirectory(rootDir, inputPath, { repoRoot });
6641
7169
  const removed = [];
@@ -6643,7 +7171,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
6643
7171
  if (!manifest.originalPath) {
6644
7172
  continue;
6645
7173
  }
6646
- if (await fileExists(path13.resolve(manifest.originalPath))) {
7174
+ if (await fileExists(path15.resolve(manifest.originalPath))) {
6647
7175
  continue;
6648
7176
  }
6649
7177
  const removedManifest = await removeManifestBySourceId(rootDir, manifest.sourceId);
@@ -6653,7 +7181,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
6653
7181
  }
6654
7182
  const manifestsAfter = await listManifests(rootDir);
6655
7183
  return {
6656
- title: path13.basename(inputPath) || inputPath,
7184
+ title: path15.basename(inputPath) || inputPath,
6657
7185
  sourceIds: directorySourceIdsFor(manifestsAfter, inputPath),
6658
7186
  counts: {
6659
7187
  scannedCount: result.scannedCount,
@@ -6669,7 +7197,7 @@ async function syncFileSource(rootDir, inputPath) {
6669
7197
  const result = await ingestInputDetailed(rootDir, inputPath);
6670
7198
  const manifestsAfter = await listManifests(rootDir);
6671
7199
  return {
6672
- title: path13.basename(inputPath, path13.extname(inputPath)) || inputPath,
7200
+ title: path15.basename(inputPath, path15.extname(inputPath)) || inputPath,
6673
7201
  sourceIds: fileSourceIdsFor(manifestsAfter, inputPath),
6674
7202
  counts: {
6675
7203
  scannedCount: result.scannedCount,
@@ -6703,10 +7231,10 @@ async function runGitCommand(cwd, args) {
6703
7231
  }
6704
7232
  async function syncGitHubRepoSource(rootDir, entry) {
6705
7233
  const workingDir = await managedSourceWorkingDir(rootDir, entry.id);
6706
- const externalCheckoutDir = entry.checkoutDir ? path13.resolve(entry.checkoutDir) : void 0;
6707
- const checkoutDir = externalCheckoutDir ?? path13.join(workingDir, "checkout");
7234
+ const externalCheckoutDir = entry.checkoutDir ? path15.resolve(entry.checkoutDir) : void 0;
7235
+ const checkoutDir = externalCheckoutDir ?? path15.join(workingDir, "checkout");
6708
7236
  if (!externalCheckoutDir) {
6709
- await fs11.rm(checkoutDir, { recursive: true, force: true });
7237
+ await fs13.rm(checkoutDir, { recursive: true, force: true });
6710
7238
  }
6711
7239
  await ensureDir(workingDir);
6712
7240
  if (!entry.url) {
@@ -6723,7 +7251,7 @@ async function syncGitHubRepoSource(rootDir, entry) {
6723
7251
  cloneArgs.push("--branch", branch);
6724
7252
  }
6725
7253
  cloneArgs.push(github.cloneUrl, checkoutDir);
6726
- if (await fileExists(path13.join(checkoutDir, ".git"))) {
7254
+ if (await fileExists(path15.join(checkoutDir, ".git"))) {
6727
7255
  await runGitCommand(checkoutDir, ["remote", "set-url", "origin", github.cloneUrl]);
6728
7256
  if (branch) {
6729
7257
  await runGitCommand(checkoutDir, ["fetch", "--depth", "1", "origin", branch]);
@@ -6734,11 +7262,11 @@ async function syncGitHubRepoSource(rootDir, entry) {
6734
7262
  await runGitCommand(checkoutDir, ["checkout", "--detach", "FETCH_HEAD"]);
6735
7263
  }
6736
7264
  } else {
6737
- const existingEntries = await fs11.readdir(checkoutDir).catch(() => []);
7265
+ const existingEntries = await fs13.readdir(checkoutDir).catch(() => []);
6738
7266
  if (externalCheckoutDir && existingEntries.length > 0) {
6739
7267
  throw new Error(`Checkout directory exists but is not a Git repository: ${checkoutDir}`);
6740
7268
  }
6741
- await ensureDir(path13.dirname(checkoutDir));
7269
+ await ensureDir(path15.dirname(checkoutDir));
6742
7270
  await runGitCommand(workingDir, cloneArgs);
6743
7271
  }
6744
7272
  if (ref) {
@@ -6865,7 +7393,7 @@ function scopedNodeIds(graph, sourceIds) {
6865
7393
  async function loadSourceAnalyses(rootDir, sourceIds) {
6866
7394
  const { paths } = await loadVaultConfig(rootDir);
6867
7395
  const analyses = await Promise.all(
6868
- sourceIds.map(async (sourceId) => await readJsonFile(path13.join(paths.analysesDir, `${sourceId}.json`)))
7396
+ sourceIds.map(async (sourceId) => await readJsonFile(path15.join(paths.analysesDir, `${sourceId}.json`)))
6869
7397
  );
6870
7398
  return analyses.filter((analysis) => Boolean(analysis?.sourceId));
6871
7399
  }
@@ -7026,9 +7554,9 @@ async function writeSourceBriefForScope(rootDir, source) {
7026
7554
  confidence: 0.82
7027
7555
  }
7028
7556
  });
7029
- const absolutePath = path13.join(paths.wikiDir, output.page.path);
7030
- await ensureDir(path13.dirname(absolutePath));
7031
- await fs11.writeFile(absolutePath, output.content, "utf8");
7557
+ const absolutePath = path15.join(paths.wikiDir, output.page.path);
7558
+ await ensureDir(path15.dirname(absolutePath));
7559
+ await fs13.writeFile(absolutePath, output.content, "utf8");
7032
7560
  return absolutePath;
7033
7561
  }
7034
7562
  async function writeSourceBrief(rootDir, source) {
@@ -7316,7 +7844,7 @@ function selectGuidedTargetPages(scope, sourcePages, questions) {
7316
7844
  return (matchedTargets.length ? matchedTargets : canonicalPages).slice(0, 6);
7317
7845
  }
7318
7846
  function insightRelativePathForTarget(page, scope) {
7319
- const basename = path13.basename(page.path);
7847
+ const basename = path15.basename(page.path);
7320
7848
  if (page.kind === "concept") {
7321
7849
  return `insights/concepts/${basename}`;
7322
7850
  }
@@ -7543,7 +8071,7 @@ async function stageSourceReviewForScope(rootDir, scope) {
7543
8071
  return {
7544
8072
  sourceId: scope.id,
7545
8073
  pageId: output.page.id,
7546
- reviewPath: path13.join(approval.approvalDir, "wiki", output.page.path),
8074
+ reviewPath: path15.join(approval.approvalDir, "wiki", output.page.path),
7547
8075
  staged: true,
7548
8076
  approvalId: approval.approvalId,
7549
8077
  approvalDir: approval.approvalDir
@@ -7610,7 +8138,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
7610
8138
  const evidenceState = contradictions.length > 0 ? "conflicting" : session.targetedPagePaths.some(
7611
8139
  (targetPath) => sourcePages.some((page) => page.path === targetPath && page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId)))
7612
8140
  ) ? "reinforcing" : session.targetedPagePaths.length ? "new" : "needs_judgment";
7613
- const relativeBriefPath = session.briefPath && path13.isAbsolute(session.briefPath) ? path13.relative(paths.wikiDir, session.briefPath) : session.briefPath;
8141
+ const relativeBriefPath = session.briefPath && path15.isAbsolute(session.briefPath) ? path15.relative(paths.wikiDir, session.briefPath) : session.briefPath;
7614
8142
  const sessionMarkdown = [
7615
8143
  `# Guided Session: ${scope.title}`,
7616
8144
  "",
@@ -7693,9 +8221,9 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
7693
8221
  async function persistSourceSessionPage(rootDir, scope, session) {
7694
8222
  const { paths } = await loadVaultConfig(rootDir);
7695
8223
  const output = await buildSourceSessionSavedPage(rootDir, scope, session);
7696
- const absolutePath = path13.join(paths.wikiDir, output.page.path);
7697
- await ensureDir(path13.dirname(absolutePath));
7698
- await fs11.writeFile(absolutePath, output.content, "utf8");
8224
+ const absolutePath = path15.join(paths.wikiDir, output.page.path);
8225
+ await ensureDir(path15.dirname(absolutePath));
8226
+ await fs13.writeFile(absolutePath, output.content, "utf8");
7699
8227
  return { pageId: output.page.id, sessionPath: absolutePath };
7700
8228
  }
7701
8229
  async function buildGuidedUpdatePages(rootDir, scope, session) {
@@ -7723,9 +8251,9 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
7723
8251
  targetPages.map(async (targetPage) => {
7724
8252
  const evidenceState = classifyGuidedEvidenceState(scope, targetPage, contradictions);
7725
8253
  const relativePath = useCanonicalTargets && targetPage ? targetPage.path : targetPage ? insightRelativePathForTarget(targetPage, scope) : `insights/topics/${slugify(scope.title)}.md`;
7726
- const absolutePath = path13.join(paths.wikiDir, relativePath);
7727
- const existingContent = await fileExists(absolutePath) ? await fs11.readFile(absolutePath, "utf8") : "";
7728
- const parsed = existingContent ? matter3(existingContent) : { data: {}, content: "" };
8254
+ const absolutePath = path15.join(paths.wikiDir, relativePath);
8255
+ const existingContent = await fileExists(absolutePath) ? await fs13.readFile(absolutePath, "utf8") : "";
8256
+ const parsed = existingContent ? matter5(existingContent) : { data: {}, content: "" };
7729
8257
  const existingData = parsed.data;
7730
8258
  const existingSourceIds = Array.isArray(existingData.source_ids) ? existingData.source_ids.filter((value) => typeof value === "string") : [];
7731
8259
  const existingProjectIds = Array.isArray(existingData.project_ids) ? existingData.project_ids.filter((value) => typeof value === "string") : [];
@@ -7786,7 +8314,7 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
7786
8314
  ""
7787
8315
  ].join("\n");
7788
8316
  const nextBody = replaceMarkedSection(baseBody, scope.id, updateBlock);
7789
- const content = matter3.stringify(
8317
+ const content = matter5.stringify(
7790
8318
  `${nextBody.trimEnd()}
7791
8319
  `,
7792
8320
  JSON.parse(
@@ -7895,8 +8423,8 @@ async function stageSourceGuideForScope(rootDir, scope, options = {}) {
7895
8423
  }
7896
8424
  );
7897
8425
  session.status = "staged";
7898
- session.reviewPath = path13.join(approval.approvalDir, "wiki", reviewOutput.page.path);
7899
- session.guidePath = path13.join(approval.approvalDir, "wiki", guideOutput.page.path);
8426
+ session.reviewPath = path15.join(approval.approvalDir, "wiki", reviewOutput.page.path);
8427
+ session.guidePath = path15.join(approval.approvalDir, "wiki", guideOutput.page.path);
7900
8428
  session.approvalId = approval.approvalId;
7901
8429
  session.approvalDir = approval.approvalDir;
7902
8430
  const persisted = await persistSourceSessionPage(rootDir, scope, session);
@@ -8039,7 +8567,7 @@ async function addManagedSource(rootDir, input, options = {}) {
8039
8567
  ref: resolved.kind === "github_repo" ? resolved.ref : existing.ref,
8040
8568
  checkoutDir: resolved.kind === "github_repo" ? resolved.checkoutDir ?? existing.checkoutDir : existing.checkoutDir
8041
8569
  } : {
8042
- id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path13.resolve(resolved.path), resolved.title) : stableManagedSourceId(
8570
+ id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path15.resolve(resolved.path), resolved.title) : stableManagedSourceId(
8043
8571
  resolved.kind,
8044
8572
  resolved.kind === "github_repo" ? `${resolved.url}#branch=${resolved.branch ?? ""}#ref=${resolved.ref ?? ""}` : resolved.url,
8045
8573
  resolved.title
@@ -8174,7 +8702,7 @@ async function deleteManagedSource(rootDir, id) {
8174
8702
  sources.filter((source) => source.id !== id)
8175
8703
  );
8176
8704
  const workingDir = await managedSourceWorkingDir(rootDir, id);
8177
- await fs11.rm(workingDir, { recursive: true, force: true });
8705
+ await fs13.rm(workingDir, { recursive: true, force: true });
8178
8706
  return { removed: target };
8179
8707
  }
8180
8708
 
@@ -8182,11 +8710,11 @@ async function deleteManagedSource(rootDir, id) {
8182
8710
  import { execFile as execFile2 } from "child_process";
8183
8711
  import { randomUUID } from "crypto";
8184
8712
  import { EventEmitter } from "events";
8185
- import fs12 from "fs/promises";
8713
+ import fs14 from "fs/promises";
8186
8714
  import http from "http";
8187
- import path14 from "path";
8715
+ import path16 from "path";
8188
8716
  import { promisify as promisify2 } from "util";
8189
- import matter4 from "gray-matter";
8717
+ import matter6 from "gray-matter";
8190
8718
  import mime from "mime-types";
8191
8719
 
8192
8720
  // src/graph-presentation.ts
@@ -8338,7 +8866,7 @@ function toViewerLintFindings(findings) {
8338
8866
  var execFileAsync2 = promisify2(execFile2);
8339
8867
  async function isReadableFile(absolutePath) {
8340
8868
  try {
8341
- const stats = await fs12.stat(absolutePath);
8869
+ const stats = await fs14.stat(absolutePath);
8342
8870
  return stats.isFile();
8343
8871
  } catch {
8344
8872
  return false;
@@ -8349,15 +8877,15 @@ async function readViewerPage(rootDir, relativePath) {
8349
8877
  return null;
8350
8878
  }
8351
8879
  const { paths } = await loadVaultConfig(rootDir);
8352
- const absolutePath = path14.resolve(paths.wikiDir, relativePath);
8880
+ const absolutePath = path16.resolve(paths.wikiDir, relativePath);
8353
8881
  if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
8354
8882
  return null;
8355
8883
  }
8356
- const raw = await fs12.readFile(absolutePath, "utf8");
8357
- const parsed = matter4(raw);
8884
+ const raw = await fs14.readFile(absolutePath, "utf8");
8885
+ const parsed = matter6(raw);
8358
8886
  return {
8359
8887
  path: relativePath,
8360
- title: typeof parsed.data.title === "string" ? parsed.data.title : path14.basename(relativePath, path14.extname(relativePath)),
8888
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path16.basename(relativePath, path16.extname(relativePath)),
8361
8889
  frontmatter: parsed.data,
8362
8890
  content: parsed.content,
8363
8891
  assets: normalizeOutputAssets(parsed.data.output_assets)
@@ -8368,12 +8896,12 @@ async function readViewerAsset(rootDir, relativePath) {
8368
8896
  return null;
8369
8897
  }
8370
8898
  const { paths } = await loadVaultConfig(rootDir);
8371
- const absolutePath = path14.resolve(paths.wikiDir, relativePath);
8899
+ const absolutePath = path16.resolve(paths.wikiDir, relativePath);
8372
8900
  if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
8373
8901
  return null;
8374
8902
  }
8375
8903
  return {
8376
- buffer: await fs12.readFile(absolutePath),
8904
+ buffer: await fs14.readFile(absolutePath),
8377
8905
  mimeType: mime.lookup(absolutePath) || "application/octet-stream"
8378
8906
  };
8379
8907
  }
@@ -8409,8 +8937,8 @@ async function writeInboxClip(rootDir, body) {
8409
8937
  const tags = Array.isArray(body.tags) ? body.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : [];
8410
8938
  const now = (/* @__PURE__ */ new Date()).toISOString();
8411
8939
  const fileName = `${now.replace(/[:.]/g, "-")}-${slugForClip(title)}.md`;
8412
- const inboxPath = path14.join(paths.inboxDir, fileName);
8413
- await fs12.mkdir(paths.inboxDir, { recursive: true });
8940
+ const inboxPath = path16.join(paths.inboxDir, fileName);
8941
+ await fs14.mkdir(paths.inboxDir, { recursive: true });
8414
8942
  const lines = [
8415
8943
  "---",
8416
8944
  `title: ${JSON.stringify(title)}`,
@@ -8427,17 +8955,17 @@ async function writeInboxClip(rootDir, body) {
8427
8955
  selectionHtml && !markdown ? ["", "## Original HTML", "", "```html", selectionHtml, "```"].join("\n") : void 0,
8428
8956
  ""
8429
8957
  ].filter((line) => line !== void 0);
8430
- await fs12.writeFile(inboxPath, lines.join("\n"), "utf8");
8958
+ await fs14.writeFile(inboxPath, lines.join("\n"), "utf8");
8431
8959
  const result = await importInbox(rootDir, paths.inboxDir);
8432
8960
  return { mode: "inbox", inboxPath, result };
8433
8961
  }
8434
8962
  async function ensureViewerDist(viewerDistDir) {
8435
- const indexPath = path14.join(viewerDistDir, "index.html");
8963
+ const indexPath = path16.join(viewerDistDir, "index.html");
8436
8964
  if (await fileExists(indexPath)) {
8437
8965
  return;
8438
8966
  }
8439
- const viewerProjectDir = path14.dirname(viewerDistDir);
8440
- if (await fileExists(path14.join(viewerProjectDir, "package.json"))) {
8967
+ const viewerProjectDir = path16.dirname(viewerDistDir);
8968
+ if (await fileExists(path16.join(viewerProjectDir, "package.json"))) {
8441
8969
  await execFileAsync2("pnpm", ["build"], { cwd: viewerProjectDir });
8442
8970
  }
8443
8971
  }
@@ -8460,7 +8988,7 @@ async function startGraphServer(rootDir, port, options = {}) {
8460
8988
  response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
8461
8989
  return;
8462
8990
  }
8463
- const reportPath = path14.join(paths.wikiDir, "graph", "report.json");
8991
+ const reportPath = path16.join(paths.wikiDir, "graph", "report.json");
8464
8992
  const report = await readJsonFile(reportPath) ?? null;
8465
8993
  response.writeHead(200, { "content-type": "application/json" });
8466
8994
  response.end(JSON.stringify(buildViewerGraphArtifact(graph, { report, full: options.full ?? false })));
@@ -8524,13 +9052,13 @@ async function startGraphServer(rootDir, port, options = {}) {
8524
9052
  return;
8525
9053
  }
8526
9054
  if (url.pathname === "/api/graph-report") {
8527
- const reportPath = path14.join(paths.wikiDir, "graph", "report.json");
9055
+ const reportPath = path16.join(paths.wikiDir, "graph", "report.json");
8528
9056
  if (!await fileExists(reportPath)) {
8529
9057
  response.writeHead(404, { "content-type": "application/json" });
8530
9058
  response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
8531
9059
  return;
8532
9060
  }
8533
- const body = await fs12.readFile(reportPath, "utf8");
9061
+ const body = await fs14.readFile(reportPath, "utf8");
8534
9062
  response.writeHead(200, { "content-type": "application/json" });
8535
9063
  response.end(body);
8536
9064
  return;
@@ -8768,7 +9296,7 @@ async function startGraphServer(rootDir, port, options = {}) {
8768
9296
  return;
8769
9297
  }
8770
9298
  if (url.pathname === "/api/workspace") {
8771
- const reportPath = path14.join(paths.wikiDir, "graph", "report.json");
9299
+ const reportPath = path16.join(paths.wikiDir, "graph", "report.json");
8772
9300
  const [graphRaw, reportRaw, approvalsRaw, candidatesRaw, memoryTasksRaw, watchStatusRaw, lintRaw, doctorRaw] = await Promise.all([
8773
9301
  readJsonFile(paths.graphPath).catch(() => null),
8774
9302
  readJsonFile(reportPath).catch(() => null),
@@ -8893,15 +9421,15 @@ async function startGraphServer(rootDir, port, options = {}) {
8893
9421
  return;
8894
9422
  }
8895
9423
  const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
8896
- const target = path14.join(paths.viewerDistDir, relativePath);
8897
- const fallback = path14.join(paths.viewerDistDir, "index.html");
9424
+ const target = path16.join(paths.viewerDistDir, relativePath);
9425
+ const fallback = path16.join(paths.viewerDistDir, "index.html");
8898
9426
  const filePath = await fileExists(target) ? target : fallback;
8899
9427
  if (!await fileExists(filePath)) {
8900
9428
  response.writeHead(503, { "content-type": "text/plain" });
8901
9429
  response.end("Viewer build not found. Run `pnpm build` first.");
8902
9430
  return;
8903
9431
  }
8904
- const staticBody = await fs12.readFile(filePath);
9432
+ const staticBody = await fs14.readFile(filePath);
8905
9433
  response.writeHead(200, { "content-type": mime.lookup(filePath) || "text/plain" });
8906
9434
  response.end(staticBody);
8907
9435
  } catch (error) {
@@ -8941,7 +9469,7 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
8941
9469
  throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
8942
9470
  }
8943
9471
  await ensureViewerDist(paths.viewerDistDir);
8944
- const indexPath = path14.join(paths.viewerDistDir, "index.html");
9472
+ const indexPath = path16.join(paths.viewerDistDir, "index.html");
8945
9473
  if (!await fileExists(indexPath)) {
8946
9474
  throw new Error("Viewer build not found. Run `pnpm build` first.");
8947
9475
  }
@@ -8967,17 +9495,17 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
8967
9495
  } : null;
8968
9496
  })
8969
9497
  );
8970
- const rawHtml = await fs12.readFile(indexPath, "utf8");
9498
+ const rawHtml = await fs14.readFile(indexPath, "utf8");
8971
9499
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
8972
9500
  const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
8973
- const scriptPath = scriptMatch?.[1] ? path14.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
8974
- const stylePath = styleMatch?.[1] ? path14.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
9501
+ const scriptPath = scriptMatch?.[1] ? path16.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
9502
+ const stylePath = styleMatch?.[1] ? path16.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
8975
9503
  if (!scriptPath || !await fileExists(scriptPath)) {
8976
9504
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
8977
9505
  }
8978
- const script = await fs12.readFile(scriptPath, "utf8");
8979
- const style = stylePath && await fileExists(stylePath) ? await fs12.readFile(stylePath, "utf8") : "";
8980
- const report = await readJsonFile(path14.join(paths.wikiDir, "graph", "report.json"));
9506
+ const script = await fs14.readFile(scriptPath, "utf8");
9507
+ const style = stylePath && await fileExists(stylePath) ? await fs14.readFile(stylePath, "utf8") : "";
9508
+ const report = await readJsonFile(path16.join(paths.wikiDir, "graph", "report.json"));
8981
9509
  const embeddedData = JSON.stringify(
8982
9510
  { graph: buildViewerGraphArtifact(graph, { report, full: options.full ?? false }), pages: pages.filter(Boolean), report },
8983
9511
  null,
@@ -9000,9 +9528,9 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
9000
9528
  "</html>",
9001
9529
  ""
9002
9530
  ].filter(Boolean).join("\n");
9003
- await fs12.mkdir(path14.dirname(outputPath), { recursive: true });
9004
- await fs12.writeFile(outputPath, html, "utf8");
9005
- return path14.resolve(outputPath);
9531
+ await fs14.mkdir(path16.dirname(outputPath), { recursive: true });
9532
+ await fs14.writeFile(outputPath, html, "utf8");
9533
+ return path16.resolve(outputPath);
9006
9534
  }
9007
9535
  export {
9008
9536
  ALL_MIGRATIONS,
@@ -9023,6 +9551,7 @@ export {
9023
9551
  addWatchedRoot,
9024
9552
  applyDecayToPages,
9025
9553
  archiveCandidate,
9554
+ askChatSession,
9026
9555
  assertProviderCapability,
9027
9556
  autoCommitWikiChanges,
9028
9557
  benchmarkVault,
@@ -9045,6 +9574,7 @@ export {
9045
9574
  createWebSearchAdapter,
9046
9575
  defaultVaultConfig,
9047
9576
  defaultVaultSchema,
9577
+ deleteChatSession,
9048
9578
  deleteContextPack,
9049
9579
  deleteManagedSource,
9050
9580
  detectVaultVersion,
@@ -9060,6 +9590,7 @@ export {
9060
9590
  expectedModelPath,
9061
9591
  explainGraphVault,
9062
9592
  exploreVault,
9593
+ exportAiPack,
9063
9594
  exportGraphFormat,
9064
9595
  exportGraphHtml,
9065
9596
  exportGraphReportHtml,
@@ -9092,6 +9623,7 @@ export {
9092
9623
  lintVault,
9093
9624
  listApprovals,
9094
9625
  listCandidates,
9626
+ listChatSessions,
9095
9627
  listContextPacks,
9096
9628
  listGodNodes,
9097
9629
  listGraphHyperedges,
@@ -9121,6 +9653,7 @@ export {
9121
9653
  queryGraphVault,
9122
9654
  queryVault,
9123
9655
  readApproval,
9656
+ readChatSession,
9124
9657
  readContextPack,
9125
9658
  readExtractedText,
9126
9659
  readGraphReport,