@swarmvaultai/engine 0.1.21 → 0.1.22

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
@@ -593,8 +593,8 @@ async function uninstallGitHooks(rootDir) {
593
593
  }
594
594
 
595
595
  // src/ingest.ts
596
- import fs8 from "fs/promises";
597
- import path8 from "path";
596
+ import fs9 from "fs/promises";
597
+ import path9 from "path";
598
598
  import { Readability } from "@mozilla/readability";
599
599
  import ignore from "ignore";
600
600
  import { JSDOM } from "jsdom";
@@ -2728,9 +2728,10 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
2728
2728
  const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType) ?? "typescript";
2729
2729
  const { code, rationales } = language === "javascript" || language === "jsx" || language === "typescript" || language === "tsx" ? analyzeTypeScriptLikeCode(manifest, extractedText) : await analyzeTreeSitterCode(manifest, extractedText, language);
2730
2730
  return {
2731
- analysisVersion: 4,
2731
+ analysisVersion: 5,
2732
2732
  sourceId: manifest.sourceId,
2733
2733
  sourceHash: manifest.contentHash,
2734
+ extractionHash: manifest.extractionHash,
2734
2735
  schemaHash,
2735
2736
  title: manifest.title,
2736
2737
  summary: summarizeModule(manifest, code),
@@ -2744,19 +2745,247 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
2744
2745
  };
2745
2746
  }
2746
2747
 
2747
- // src/logs.ts
2748
+ // src/extraction.ts
2748
2749
  import fs6 from "fs/promises";
2750
+ import os from "os";
2749
2751
  import path6 from "path";
2752
+ import { z } from "zod";
2753
+ var imageVisionExtractionSchema = z.object({
2754
+ title: z.string().min(1).nullable().optional(),
2755
+ summary: z.string().min(1),
2756
+ text: z.string().default(""),
2757
+ concepts: z.array(
2758
+ z.object({
2759
+ name: z.string().min(1),
2760
+ description: z.string().default("")
2761
+ })
2762
+ ).max(12).default([]),
2763
+ entities: z.array(
2764
+ z.object({
2765
+ name: z.string().min(1),
2766
+ description: z.string().default("")
2767
+ })
2768
+ ).max(12).default([]),
2769
+ claims: z.array(
2770
+ z.object({
2771
+ text: z.string().min(1),
2772
+ confidence: z.number().min(0).max(1).default(0.65),
2773
+ polarity: z.enum(["positive", "negative", "neutral"]).default("neutral")
2774
+ })
2775
+ ).max(8).default([]),
2776
+ questions: z.array(z.string().min(1)).max(6).default([])
2777
+ });
2778
+ function extractionMetadata(sourceKind, mimeType, extractor) {
2779
+ return {
2780
+ extractor,
2781
+ sourceKind,
2782
+ mimeType,
2783
+ producedAt: (/* @__PURE__ */ new Date()).toISOString()
2784
+ };
2785
+ }
2786
+ function buildExtractionHash(extractedText, artifact) {
2787
+ if (!extractedText && !artifact) {
2788
+ return void 0;
2789
+ }
2790
+ const normalizedArtifact = artifact ? {
2791
+ ...artifact,
2792
+ producedAt: void 0
2793
+ } : null;
2794
+ return sha256(
2795
+ JSON.stringify({
2796
+ extractedText: extractedText ?? null,
2797
+ artifact: normalizedArtifact
2798
+ })
2799
+ );
2800
+ }
2801
+ function createPlainTextExtractionArtifact(sourceKind, mimeType) {
2802
+ return extractionMetadata(sourceKind, mimeType, "plain_text");
2803
+ }
2804
+ function createHtmlReadabilityExtractionArtifact(sourceKind, mimeType) {
2805
+ return extractionMetadata(sourceKind, mimeType, "html_readability");
2806
+ }
2807
+ function normalizeVisionMarkdown(payload) {
2808
+ const sections = [];
2809
+ if (payload.summary.trim()) {
2810
+ sections.push(payload.summary.trim());
2811
+ }
2812
+ if (payload.text.trim()) {
2813
+ sections.push(payload.text.trim());
2814
+ }
2815
+ if (payload.claims.length) {
2816
+ sections.push(payload.claims.map((claim) => `- ${claim.text}`).join("\n"));
2817
+ }
2818
+ return sections.join("\n\n").trim();
2819
+ }
2820
+ async function materializeAttachmentPath(input) {
2821
+ if (input.filePath) {
2822
+ return {
2823
+ filePath: input.filePath,
2824
+ cleanup: async () => {
2825
+ }
2826
+ };
2827
+ }
2828
+ if (!input.bytes) {
2829
+ throw new Error("Image extraction requires a file path or bytes.");
2830
+ }
2831
+ const tempDir = await fs6.mkdtemp(path6.join(os.tmpdir(), "swarmvault-image-extract-"));
2832
+ const extension = input.mimeType.split("/")[1]?.split("+")[0] ?? "bin";
2833
+ const tempPath = path6.join(tempDir, `source.${extension}`);
2834
+ await fs6.writeFile(tempPath, input.bytes);
2835
+ return {
2836
+ filePath: tempPath,
2837
+ cleanup: async () => {
2838
+ await fs6.rm(tempDir, { recursive: true, force: true });
2839
+ }
2840
+ };
2841
+ }
2842
+ async function extractImageWithVision(rootDir, input) {
2843
+ let provider;
2844
+ try {
2845
+ provider = await getProviderForTask(rootDir, "visionProvider");
2846
+ } catch (error) {
2847
+ return {
2848
+ artifact: {
2849
+ ...extractionMetadata("image", input.mimeType, "image_vision"),
2850
+ warnings: [`Vision extraction unavailable: ${error instanceof Error ? error.message : "provider not configured"}`]
2851
+ }
2852
+ };
2853
+ }
2854
+ if (provider.type === "heuristic" || !provider.capabilities.has("vision") || !provider.capabilities.has("structured")) {
2855
+ return {
2856
+ artifact: {
2857
+ ...extractionMetadata("image", input.mimeType, "image_vision"),
2858
+ warnings: [`Vision extraction unavailable for provider ${provider.id}. Configure a structured multimodal provider.`]
2859
+ }
2860
+ };
2861
+ }
2862
+ const attachment = await materializeAttachmentPath(input);
2863
+ try {
2864
+ const parsed = await provider.generateStructured(
2865
+ {
2866
+ system: [
2867
+ "You extract grounded notes from a single image for a local-first knowledge vault.",
2868
+ "Only describe content that is actually visible.",
2869
+ "If the image contains text, transcribe it accurately.",
2870
+ "If the image is a diagram or screenshot, summarize the key visible relationships and labels without speculation."
2871
+ ].join("\n"),
2872
+ prompt: [
2873
+ `Source title: ${input.title}`,
2874
+ "Return structured extraction for this image.",
2875
+ "Include a concise summary, OCR-style text, grounded concepts/entities, visible claims, and follow-up questions."
2876
+ ].join("\n"),
2877
+ attachments: [{ mimeType: input.mimeType, filePath: attachment.filePath }]
2878
+ },
2879
+ imageVisionExtractionSchema
2880
+ );
2881
+ const artifact = {
2882
+ ...extractionMetadata("image", input.mimeType, "image_vision"),
2883
+ providerId: provider.id,
2884
+ providerModel: provider.model,
2885
+ vision: {
2886
+ title: parsed.title ?? void 0,
2887
+ summary: parsed.summary,
2888
+ text: parsed.text,
2889
+ concepts: parsed.concepts,
2890
+ entities: parsed.entities,
2891
+ claims: parsed.claims,
2892
+ questions: parsed.questions
2893
+ }
2894
+ };
2895
+ return {
2896
+ title: parsed.title ?? void 0,
2897
+ extractedText: normalizeVisionMarkdown(parsed),
2898
+ artifact
2899
+ };
2900
+ } catch (error) {
2901
+ return {
2902
+ artifact: {
2903
+ ...extractionMetadata("image", input.mimeType, "image_vision"),
2904
+ providerId: provider.id,
2905
+ providerModel: provider.model,
2906
+ warnings: [`Vision extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
2907
+ }
2908
+ };
2909
+ } finally {
2910
+ await attachment.cleanup();
2911
+ }
2912
+ }
2913
+ function normalizePdfMetadata(raw) {
2914
+ if (!raw || typeof raw !== "object") {
2915
+ return void 0;
2916
+ }
2917
+ const metadata = {};
2918
+ for (const [key, value] of Object.entries(raw)) {
2919
+ if (typeof value === "string") {
2920
+ const cleaned = normalizeWhitespace(value);
2921
+ if (cleaned) {
2922
+ metadata[key] = cleaned;
2923
+ }
2924
+ }
2925
+ }
2926
+ return Object.keys(metadata).length ? metadata : void 0;
2927
+ }
2928
+ async function extractPdfText(input) {
2929
+ try {
2930
+ const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
2931
+ const task = pdfjs.getDocument({
2932
+ data: new Uint8Array(input.bytes),
2933
+ useWorkerFetch: false,
2934
+ isEvalSupported: false,
2935
+ disableFontFace: true,
2936
+ verbosity: 0
2937
+ });
2938
+ const document = await task.promise;
2939
+ const pageTexts = [];
2940
+ for (let pageNumber = 1; pageNumber <= document.numPages; pageNumber += 1) {
2941
+ const page = await document.getPage(pageNumber);
2942
+ const textContent = await page.getTextContent();
2943
+ const pageText = normalizeWhitespace(
2944
+ textContent.items.map((item) => typeof item === "object" && item && "str" in item && typeof item.str === "string" ? item.str : "").join(" ")
2945
+ );
2946
+ if (pageText) {
2947
+ pageTexts.push(pageText);
2948
+ }
2949
+ page.cleanup();
2950
+ }
2951
+ const metadataResult = await document.getMetadata().catch(() => null);
2952
+ await task.destroy();
2953
+ const extractedText = pageTexts.join("\n\n").trim();
2954
+ const artifact = {
2955
+ ...extractionMetadata("pdf", input.mimeType, "pdf_text"),
2956
+ pageCount: document.numPages,
2957
+ metadata: normalizePdfMetadata(metadataResult?.info)
2958
+ };
2959
+ if (!extractedText) {
2960
+ artifact.warnings = ["PDF text extraction completed but produced no extractable text."];
2961
+ }
2962
+ return {
2963
+ extractedText: extractedText || void 0,
2964
+ artifact
2965
+ };
2966
+ } catch (error) {
2967
+ return {
2968
+ artifact: {
2969
+ ...extractionMetadata("pdf", input.mimeType, "pdf_text"),
2970
+ warnings: [`PDF text extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
2971
+ }
2972
+ };
2973
+ }
2974
+ }
2975
+
2976
+ // src/logs.ts
2977
+ import fs7 from "fs/promises";
2978
+ import path7 from "path";
2750
2979
  import matter from "gray-matter";
2751
2980
  async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
2752
2981
  const { paths } = await initWorkspace(rootDir);
2753
2982
  await ensureDir(paths.sessionsDir);
2754
2983
  const timestamp = startedAt.replace(/[:.]/g, "-");
2755
2984
  const baseName = `${timestamp}-${operation}-${slugify(title)}`;
2756
- let candidate = path6.join(paths.sessionsDir, `${baseName}.md`);
2985
+ let candidate = path7.join(paths.sessionsDir, `${baseName}.md`);
2757
2986
  let counter = 2;
2758
2987
  while (await fileExists(candidate)) {
2759
- candidate = path6.join(paths.sessionsDir, `${baseName}-${counter}.md`);
2988
+ candidate = path7.join(paths.sessionsDir, `${baseName}-${counter}.md`);
2760
2989
  counter++;
2761
2990
  }
2762
2991
  return candidate;
@@ -2764,11 +2993,11 @@ async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
2764
2993
  async function appendLogEntry(rootDir, action, title, lines = []) {
2765
2994
  const { paths } = await initWorkspace(rootDir);
2766
2995
  await ensureDir(paths.wikiDir);
2767
- const logPath = path6.join(paths.wikiDir, "log.md");
2996
+ const logPath = path7.join(paths.wikiDir, "log.md");
2768
2997
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
2769
2998
  const entry = [`## [${timestamp}] ${action} | ${title}`, ...lines.map((line) => `- ${line}`), ""].join("\n");
2770
- const existing = await fileExists(logPath) ? await fs6.readFile(logPath, "utf8") : "# Log\n\n";
2771
- await fs6.writeFile(logPath, `${existing}${entry}
2999
+ const existing = await fileExists(logPath) ? await fs7.readFile(logPath, "utf8") : "# Log\n\n";
3000
+ await fs7.writeFile(logPath, `${existing}${entry}
2772
3001
  `, "utf8");
2773
3002
  }
2774
3003
  async function recordSession(rootDir, input) {
@@ -2778,8 +3007,8 @@ async function recordSession(rootDir, input) {
2778
3007
  const finishedAtIso = new Date(input.finishedAt ?? input.startedAt).toISOString();
2779
3008
  const durationMs = Math.max(0, new Date(finishedAtIso).getTime() - new Date(startedAtIso).getTime());
2780
3009
  const sessionPath = await resolveUniqueSessionPath(rootDir, input.operation, input.title, startedAtIso);
2781
- const sessionId = path6.basename(sessionPath, ".md");
2782
- const relativeSessionPath = path6.relative(rootDir, sessionPath).split(path6.sep).join(path6.posix.sep);
3010
+ const sessionId = path7.basename(sessionPath, ".md");
3011
+ const relativeSessionPath = path7.relative(rootDir, sessionPath).split(path7.sep).join(path7.posix.sep);
2783
3012
  const frontmatter = Object.fromEntries(
2784
3013
  Object.entries({
2785
3014
  session_id: sessionId,
@@ -2827,7 +3056,7 @@ async function recordSession(rootDir, input) {
2827
3056
  frontmatter
2828
3057
  );
2829
3058
  await writeFileIfChanged(sessionPath, content);
2830
- const logPath = path6.join(paths.wikiDir, "log.md");
3059
+ const logPath = path7.join(paths.wikiDir, "log.md");
2831
3060
  const timestamp = startedAtIso.slice(0, 19).replace("T", " ");
2832
3061
  const entry = [
2833
3062
  `## [${timestamp}] ${input.operation} | ${input.title}`,
@@ -2835,8 +3064,8 @@ async function recordSession(rootDir, input) {
2835
3064
  ...(input.lines ?? []).map((line) => `- ${line}`),
2836
3065
  ""
2837
3066
  ].join("\n");
2838
- const existing = await fileExists(logPath) ? await fs6.readFile(logPath, "utf8") : "# Log\n\n";
2839
- await fs6.writeFile(logPath, `${existing}${entry}
3067
+ const existing = await fileExists(logPath) ? await fs7.readFile(logPath, "utf8") : "# Log\n\n";
3068
+ await fs7.writeFile(logPath, `${existing}${entry}
2840
3069
  `, "utf8");
2841
3070
  return { sessionPath, sessionId };
2842
3071
  }
@@ -2846,8 +3075,8 @@ async function appendWatchRun(rootDir, run) {
2846
3075
  }
2847
3076
 
2848
3077
  // src/watch-state.ts
2849
- import fs7 from "fs/promises";
2850
- import path7 from "path";
3078
+ import fs8 from "fs/promises";
3079
+ import path8 from "path";
2851
3080
  import matter2 from "gray-matter";
2852
3081
  function pendingEntryKey(entry) {
2853
3082
  return entry.path;
@@ -2861,7 +3090,7 @@ function normalizeRelativePath(rootDir, filePath) {
2861
3090
  if (!filePath) {
2862
3091
  return void 0;
2863
3092
  }
2864
- return toPosix(path7.relative(rootDir, path7.resolve(filePath)));
3093
+ return toPosix(path8.relative(rootDir, path8.resolve(filePath)));
2865
3094
  }
2866
3095
  async function readPendingSemanticRefresh(rootDir) {
2867
3096
  const { paths } = await initWorkspace(rootDir);
@@ -2955,11 +3184,11 @@ async function markPagesStaleForSources(rootDir, sourceIds) {
2955
3184
  if (page.freshness !== "stale" || !page.sourceIds.some((sourceId) => affectedSourceIds.has(sourceId))) {
2956
3185
  continue;
2957
3186
  }
2958
- const absolutePath = path7.join(paths.wikiDir, page.path);
3187
+ const absolutePath = path8.join(paths.wikiDir, page.path);
2959
3188
  if (!await fileExists(absolutePath)) {
2960
3189
  continue;
2961
3190
  }
2962
- const raw = await fs7.readFile(absolutePath, "utf8");
3191
+ const raw = await fs8.readFile(absolutePath, "utf8");
2963
3192
  const parsed = matter2(raw);
2964
3193
  if (parsed.data.freshness === "stale") {
2965
3194
  continue;
@@ -3007,7 +3236,7 @@ function normalizeIngestOptions(options) {
3007
3236
  return {
3008
3237
  includeAssets: options?.includeAssets ?? true,
3009
3238
  maxAssetSize: Math.max(0, Math.floor(options?.maxAssetSize ?? DEFAULT_MAX_ASSET_SIZE)),
3010
- repoRoot: options?.repoRoot ? path8.resolve(options.repoRoot) : void 0,
3239
+ repoRoot: options?.repoRoot ? path9.resolve(options.repoRoot) : void 0,
3011
3240
  include: (options?.include ?? []).map((pattern) => pattern.trim()).filter(Boolean),
3012
3241
  exclude: (options?.exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean),
3013
3242
  maxFiles: Math.max(1, Math.floor(options?.maxFiles ?? DEFAULT_MAX_DIRECTORY_FILES)),
@@ -3016,27 +3245,27 @@ function normalizeIngestOptions(options) {
3016
3245
  }
3017
3246
  function matchesAnyGlob(relativePath, patterns) {
3018
3247
  return patterns.some(
3019
- (pattern) => path8.matchesGlob(relativePath, pattern) || path8.matchesGlob(path8.posix.basename(relativePath), pattern)
3248
+ (pattern) => path9.matchesGlob(relativePath, pattern) || path9.matchesGlob(path9.posix.basename(relativePath), pattern)
3020
3249
  );
3021
3250
  }
3022
3251
  function supportedDirectoryKind(sourceKind) {
3023
3252
  return sourceKind !== "binary";
3024
3253
  }
3025
3254
  async function findNearestGitRoot2(startPath) {
3026
- let current = path8.resolve(startPath);
3255
+ let current = path9.resolve(startPath);
3027
3256
  try {
3028
- const stat = await fs8.stat(current);
3257
+ const stat = await fs9.stat(current);
3029
3258
  if (!stat.isDirectory()) {
3030
- current = path8.dirname(current);
3259
+ current = path9.dirname(current);
3031
3260
  }
3032
3261
  } catch {
3033
- current = path8.dirname(current);
3262
+ current = path9.dirname(current);
3034
3263
  }
3035
3264
  while (true) {
3036
- if (await fileExists(path8.join(current, ".git"))) {
3265
+ if (await fileExists(path9.join(current, ".git"))) {
3037
3266
  return current;
3038
3267
  }
3039
- const parent = path8.dirname(current);
3268
+ const parent = path9.dirname(current);
3040
3269
  if (parent === current) {
3041
3270
  return null;
3042
3271
  }
@@ -3044,26 +3273,26 @@ async function findNearestGitRoot2(startPath) {
3044
3273
  }
3045
3274
  }
3046
3275
  function withinRoot(rootPath, targetPath) {
3047
- const relative = path8.relative(rootPath, targetPath);
3048
- return relative === "" || !relative.startsWith("..") && !path8.isAbsolute(relative);
3276
+ const relative = path9.relative(rootPath, targetPath);
3277
+ return relative === "" || !relative.startsWith("..") && !path9.isAbsolute(relative);
3049
3278
  }
3050
3279
  function repoRootFromManifest(manifest) {
3051
3280
  if (manifest.originType !== "file" || !manifest.originalPath || !manifest.repoRelativePath) {
3052
3281
  return null;
3053
3282
  }
3054
- const repoDir = path8.posix.dirname(manifest.repoRelativePath);
3055
- const fileDir = path8.dirname(path8.resolve(manifest.originalPath));
3283
+ const repoDir = path9.posix.dirname(manifest.repoRelativePath);
3284
+ const fileDir = path9.dirname(path9.resolve(manifest.originalPath));
3056
3285
  if (repoDir === "." || !repoDir) {
3057
3286
  return fileDir;
3058
3287
  }
3059
3288
  const segments = repoDir.split("/").filter(Boolean);
3060
- return path8.resolve(fileDir, ...segments.map(() => ".."));
3289
+ return path9.resolve(fileDir, ...segments.map(() => ".."));
3061
3290
  }
3062
3291
  function repoRelativePathFor(absolutePath, repoRoot) {
3063
3292
  if (!repoRoot || !withinRoot(repoRoot, absolutePath)) {
3064
3293
  return void 0;
3065
3294
  }
3066
- const relative = toPosix(path8.relative(repoRoot, absolutePath));
3295
+ const relative = toPosix(path9.relative(repoRoot, absolutePath));
3067
3296
  return relative && !relative.startsWith("..") ? relative : void 0;
3068
3297
  }
3069
3298
  function normalizeOriginUrl(input) {
@@ -3231,7 +3460,7 @@ function buildCompositeHash(payloadBytes, attachments = []) {
3231
3460
  return sha256(`${sha256(payloadBytes)}|${attachmentSignature}`);
3232
3461
  }
3233
3462
  function sanitizeAssetRelativePath(value) {
3234
- const normalized = path8.posix.normalize(value.replace(/\\/g, "/"));
3463
+ const normalized = path9.posix.normalize(value.replace(/\\/g, "/"));
3235
3464
  const segments = normalized.split("/").filter(Boolean).map((segment) => {
3236
3465
  if (segment === ".") {
3237
3466
  return "";
@@ -3251,7 +3480,7 @@ function normalizeLocalReference(value) {
3251
3480
  return null;
3252
3481
  }
3253
3482
  const lowered = candidate.toLowerCase();
3254
- if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path8.isAbsolute(candidate)) {
3483
+ if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path9.isAbsolute(candidate)) {
3255
3484
  return null;
3256
3485
  }
3257
3486
  return candidate.replace(/\\/g, "/");
@@ -3313,12 +3542,12 @@ async function convertHtmlToMarkdown(html, url) {
3313
3542
  };
3314
3543
  }
3315
3544
  async function readManifestByHash(manifestsDir, contentHash) {
3316
- const entries = await fs8.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
3545
+ const entries = await fs9.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
3317
3546
  for (const entry of entries) {
3318
3547
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
3319
3548
  continue;
3320
3549
  }
3321
- const manifest = await readJsonFile(path8.join(manifestsDir, entry.name));
3550
+ const manifest = await readJsonFile(path9.join(manifestsDir, entry.name));
3322
3551
  if (manifest?.contentHash === contentHash) {
3323
3552
  return manifest;
3324
3553
  }
@@ -3326,12 +3555,12 @@ async function readManifestByHash(manifestsDir, contentHash) {
3326
3555
  return null;
3327
3556
  }
3328
3557
  async function readManifestByOrigin(manifestsDir, prepared) {
3329
- const entries = await fs8.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
3558
+ const entries = await fs9.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
3330
3559
  for (const entry of entries) {
3331
3560
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
3332
3561
  continue;
3333
3562
  }
3334
- const manifest = await readJsonFile(path8.join(manifestsDir, entry.name));
3563
+ const manifest = await readJsonFile(path9.join(manifestsDir, entry.name));
3335
3564
  if (manifest && manifestMatchesOrigin(manifest, prepared)) {
3336
3565
  return manifest;
3337
3566
  }
@@ -3342,12 +3571,12 @@ async function loadGitignoreMatcher(repoRoot, enabled) {
3342
3571
  if (!enabled) {
3343
3572
  return null;
3344
3573
  }
3345
- const gitignorePath = path8.join(repoRoot, ".gitignore");
3574
+ const gitignorePath = path9.join(repoRoot, ".gitignore");
3346
3575
  if (!await fileExists(gitignorePath)) {
3347
3576
  return null;
3348
3577
  }
3349
3578
  const matcher = ignore();
3350
- matcher.add(await fs8.readFile(gitignorePath, "utf8"));
3579
+ matcher.add(await fs9.readFile(gitignorePath, "utf8"));
3351
3580
  return matcher;
3352
3581
  }
3353
3582
  function builtInIgnoreReason(relativePath) {
@@ -3368,23 +3597,23 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
3368
3597
  if (!currentDir) {
3369
3598
  continue;
3370
3599
  }
3371
- const entries = await fs8.readdir(currentDir, { withFileTypes: true });
3600
+ const entries = await fs9.readdir(currentDir, { withFileTypes: true });
3372
3601
  entries.sort((left, right) => left.name.localeCompare(right.name));
3373
3602
  for (const entry of entries) {
3374
- const absolutePath = path8.join(currentDir, entry.name);
3375
- const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path8.relative(inputDir, absolutePath));
3603
+ const absolutePath = path9.join(currentDir, entry.name);
3604
+ const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path9.relative(inputDir, absolutePath));
3376
3605
  const relativePath = relativeToRepo || entry.name;
3377
3606
  const builtInReason = builtInIgnoreReason(relativePath);
3378
3607
  if (builtInReason) {
3379
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: builtInReason });
3608
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: builtInReason });
3380
3609
  continue;
3381
3610
  }
3382
3611
  if (matcher?.ignores(relativePath)) {
3383
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "gitignore" });
3612
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "gitignore" });
3384
3613
  continue;
3385
3614
  }
3386
3615
  if (matchesAnyGlob(relativePath, options.exclude)) {
3387
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "exclude_glob" });
3616
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "exclude_glob" });
3388
3617
  continue;
3389
3618
  }
3390
3619
  if (entry.isDirectory()) {
@@ -3392,21 +3621,21 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
3392
3621
  continue;
3393
3622
  }
3394
3623
  if (!entry.isFile()) {
3395
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
3624
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
3396
3625
  continue;
3397
3626
  }
3398
3627
  if (options.include.length > 0 && !matchesAnyGlob(relativePath, options.include)) {
3399
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "include_glob" });
3628
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "include_glob" });
3400
3629
  continue;
3401
3630
  }
3402
3631
  const mimeType = guessMimeType(absolutePath);
3403
3632
  const sourceKind = inferKind(mimeType, absolutePath);
3404
3633
  if (!supportedDirectoryKind(sourceKind)) {
3405
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
3634
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
3406
3635
  continue;
3407
3636
  }
3408
3637
  if (files.length >= options.maxFiles) {
3409
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "max_files" });
3638
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "max_files" });
3410
3639
  continue;
3411
3640
  }
3412
3641
  files.push(absolutePath);
@@ -3428,12 +3657,12 @@ function resolveUrlMimeType(input, response) {
3428
3657
  function buildRemoteAssetRelativePath(assetUrl, mimeType) {
3429
3658
  const url = new URL(assetUrl);
3430
3659
  const normalized = sanitizeAssetRelativePath(`${url.hostname}${url.pathname || "/asset"}`);
3431
- const extension = path8.posix.extname(normalized);
3432
- const directory = path8.posix.dirname(normalized);
3433
- const basename = extension ? path8.posix.basename(normalized, extension) : path8.posix.basename(normalized);
3660
+ const extension = path9.posix.extname(normalized);
3661
+ const directory = path9.posix.dirname(normalized);
3662
+ const basename = extension ? path9.posix.basename(normalized, extension) : path9.posix.basename(normalized);
3434
3663
  const resolvedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
3435
3664
  const hashedName = `${basename || "asset"}-${sha256(assetUrl).slice(0, 8)}${resolvedExtension}`;
3436
- return directory === "." ? hashedName : path8.posix.join(directory, hashedName);
3665
+ return directory === "." ? hashedName : path9.posix.join(directory, hashedName);
3437
3666
  }
3438
3667
  async function readResponseBytesWithinLimit(response, maxBytes) {
3439
3668
  const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
@@ -3557,9 +3786,10 @@ async function persistPreparedInput(rootDir, prepared, paths) {
3557
3786
  await ensureDir(paths.extractsDir);
3558
3787
  const attachments = prepared.attachments ?? [];
3559
3788
  const contentHash = prepared.contentHash ?? buildCompositeHash(prepared.payloadBytes, attachments);
3789
+ const extractionHash = prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact);
3560
3790
  const existingByOrigin = await readManifestByOrigin(paths.manifestsDir, prepared);
3561
3791
  const existingByHash = existingByOrigin ? null : await readManifestByHash(paths.manifestsDir, contentHash);
3562
- if (existingByOrigin && existingByOrigin.contentHash === contentHash && existingByOrigin.title === prepared.title && existingByOrigin.sourceKind === prepared.sourceKind && existingByOrigin.language === prepared.language && existingByOrigin.mimeType === prepared.mimeType && existingByOrigin.repoRelativePath === prepared.repoRelativePath) {
3792
+ if (existingByOrigin && existingByOrigin.contentHash === contentHash && existingByOrigin.extractionHash === extractionHash && existingByOrigin.title === prepared.title && existingByOrigin.sourceKind === prepared.sourceKind && existingByOrigin.language === prepared.language && existingByOrigin.mimeType === prepared.mimeType && existingByOrigin.repoRelativePath === prepared.repoRelativePath) {
3563
3793
  return { manifest: existingByOrigin, isNew: false, wasUpdated: false };
3564
3794
  }
3565
3795
  if (existingByHash) {
@@ -3568,27 +3798,34 @@ async function persistPreparedInput(rootDir, prepared, paths) {
3568
3798
  const previous = existingByOrigin ?? void 0;
3569
3799
  const sourceId = previous?.sourceId ?? `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
3570
3800
  const now = (/* @__PURE__ */ new Date()).toISOString();
3571
- const storedPath = path8.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
3572
- const extractedTextPath = prepared.extractedText ? path8.join(paths.extractsDir, `${sourceId}.md`) : void 0;
3573
- const attachmentsDir = path8.join(paths.rawAssetsDir, sourceId);
3801
+ const storedPath = path9.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
3802
+ const extractedTextPath = prepared.extractedText ? path9.join(paths.extractsDir, `${sourceId}.md`) : void 0;
3803
+ const extractedMetadataPath = prepared.extractionArtifact ? path9.join(paths.extractsDir, `${sourceId}.json`) : void 0;
3804
+ const attachmentsDir = path9.join(paths.rawAssetsDir, sourceId);
3574
3805
  if (previous?.storedPath) {
3575
- await fs8.rm(path8.resolve(rootDir, previous.storedPath), { force: true });
3806
+ await fs9.rm(path9.resolve(rootDir, previous.storedPath), { force: true });
3576
3807
  }
3577
3808
  if (previous?.extractedTextPath) {
3578
- await fs8.rm(path8.resolve(rootDir, previous.extractedTextPath), { force: true });
3809
+ await fs9.rm(path9.resolve(rootDir, previous.extractedTextPath), { force: true });
3810
+ }
3811
+ if (previous?.extractedMetadataPath) {
3812
+ await fs9.rm(path9.resolve(rootDir, previous.extractedMetadataPath), { force: true });
3579
3813
  }
3580
- await fs8.rm(attachmentsDir, { recursive: true, force: true });
3581
- await fs8.writeFile(storedPath, prepared.payloadBytes);
3814
+ await fs9.rm(attachmentsDir, { recursive: true, force: true });
3815
+ await fs9.writeFile(storedPath, prepared.payloadBytes);
3582
3816
  if (prepared.extractedText && extractedTextPath) {
3583
- await fs8.writeFile(extractedTextPath, prepared.extractedText, "utf8");
3817
+ await fs9.writeFile(extractedTextPath, prepared.extractedText, "utf8");
3818
+ }
3819
+ if (prepared.extractionArtifact && extractedMetadataPath) {
3820
+ await writeJsonFile(extractedMetadataPath, prepared.extractionArtifact);
3584
3821
  }
3585
3822
  const manifestAttachments = [];
3586
3823
  for (const attachment of attachments) {
3587
- const absoluteAttachmentPath = path8.join(attachmentsDir, attachment.relativePath);
3588
- await ensureDir(path8.dirname(absoluteAttachmentPath));
3589
- await fs8.writeFile(absoluteAttachmentPath, attachment.bytes);
3824
+ const absoluteAttachmentPath = path9.join(attachmentsDir, attachment.relativePath);
3825
+ await ensureDir(path9.dirname(absoluteAttachmentPath));
3826
+ await fs9.writeFile(absoluteAttachmentPath, attachment.bytes);
3590
3827
  manifestAttachments.push({
3591
- path: toPosix(path8.relative(rootDir, absoluteAttachmentPath)),
3828
+ path: toPosix(path9.relative(rootDir, absoluteAttachmentPath)),
3592
3829
  mimeType: attachment.mimeType,
3593
3830
  originalPath: attachment.originalPath
3594
3831
  });
@@ -3602,15 +3839,17 @@ async function persistPreparedInput(rootDir, prepared, paths) {
3602
3839
  originalPath: prepared.originalPath,
3603
3840
  repoRelativePath: prepared.repoRelativePath,
3604
3841
  url: prepared.url,
3605
- storedPath: toPosix(path8.relative(rootDir, storedPath)),
3606
- extractedTextPath: extractedTextPath ? toPosix(path8.relative(rootDir, extractedTextPath)) : void 0,
3842
+ storedPath: toPosix(path9.relative(rootDir, storedPath)),
3843
+ extractedTextPath: extractedTextPath ? toPosix(path9.relative(rootDir, extractedTextPath)) : void 0,
3844
+ extractedMetadataPath: extractedMetadataPath ? toPosix(path9.relative(rootDir, extractedMetadataPath)) : void 0,
3845
+ extractionHash,
3607
3846
  mimeType: prepared.mimeType,
3608
3847
  contentHash,
3609
3848
  createdAt: previous?.createdAt ?? now,
3610
3849
  updatedAt: now,
3611
3850
  attachments: manifestAttachments.length ? manifestAttachments : void 0
3612
3851
  };
3613
- await writeJsonFile(path8.join(paths.manifestsDir, `${sourceId}.json`), manifest);
3852
+ await writeJsonFile(path9.join(paths.manifestsDir, `${sourceId}.json`), manifest);
3614
3853
  await appendLogEntry(rootDir, "ingest", prepared.title, [
3615
3854
  `source_id=${sourceId}`,
3616
3855
  `kind=${prepared.sourceKind}`,
@@ -3628,13 +3867,16 @@ async function persistPreparedInput(rootDir, prepared, paths) {
3628
3867
  return { manifest, isNew: !previous, wasUpdated: Boolean(previous) };
3629
3868
  }
3630
3869
  async function removeManifestArtifacts(rootDir, manifest, paths) {
3631
- await fs8.rm(path8.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
3632
- await fs8.rm(path8.resolve(rootDir, manifest.storedPath), { force: true });
3870
+ await fs9.rm(path9.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
3871
+ await fs9.rm(path9.resolve(rootDir, manifest.storedPath), { force: true });
3633
3872
  if (manifest.extractedTextPath) {
3634
- await fs8.rm(path8.resolve(rootDir, manifest.extractedTextPath), { force: true });
3873
+ await fs9.rm(path9.resolve(rootDir, manifest.extractedTextPath), { force: true });
3635
3874
  }
3636
- await fs8.rm(path8.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
3637
- await fs8.rm(path8.join(paths.analysesDir, `${manifest.sourceId}.json`), { force: true });
3875
+ if (manifest.extractedMetadataPath) {
3876
+ await fs9.rm(path9.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
3877
+ }
3878
+ await fs9.rm(path9.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
3879
+ await fs9.rm(path9.join(paths.analysesDir, `${manifest.sourceId}.json`), { force: true });
3638
3880
  }
3639
3881
  function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
3640
3882
  const candidates = [
@@ -3643,14 +3885,14 @@ function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
3643
3885
  paths.stateDir,
3644
3886
  paths.agentDir,
3645
3887
  paths.inboxDir,
3646
- path8.join(rootDir, ".claude"),
3647
- path8.join(rootDir, ".cursor"),
3648
- path8.join(rootDir, ".obsidian")
3888
+ path9.join(rootDir, ".claude"),
3889
+ path9.join(rootDir, ".cursor"),
3890
+ path9.join(rootDir, ".obsidian")
3649
3891
  ];
3650
- return candidates.map((candidate) => path8.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, candidate));
3892
+ return candidates.map((candidate) => path9.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, candidate));
3651
3893
  }
3652
3894
  function preparedMatchesManifest(manifest, prepared, contentHash) {
3653
- return manifest.contentHash === contentHash && manifest.title === prepared.title && manifest.sourceKind === prepared.sourceKind && manifest.language === prepared.language && manifest.mimeType === prepared.mimeType && manifest.repoRelativePath === prepared.repoRelativePath;
3895
+ return manifest.contentHash === contentHash && manifest.extractionHash === (prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact)) && manifest.title === prepared.title && manifest.sourceKind === prepared.sourceKind && manifest.language === prepared.language && manifest.mimeType === prepared.mimeType && manifest.repoRelativePath === prepared.repoRelativePath;
3654
3896
  }
3655
3897
  function shouldDeferWatchSemanticRefresh(sourceKind) {
3656
3898
  return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "image";
@@ -3669,16 +3911,16 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
3669
3911
  const normalizedOptions = normalizeIngestOptions(options);
3670
3912
  const manifests = await listManifests(rootDir);
3671
3913
  const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
3672
- (item) => path8.resolve(item)
3914
+ (item) => path9.resolve(item)
3673
3915
  );
3674
3916
  const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
3675
3917
  const manifestsByRepoRoot = /* @__PURE__ */ new Map();
3676
3918
  for (const manifest of manifests) {
3677
3919
  const repoRoot = repoRootFromManifest(manifest);
3678
- if (!repoRoot || !uniqueRoots.includes(path8.resolve(repoRoot))) {
3920
+ if (!repoRoot || !uniqueRoots.includes(path9.resolve(repoRoot))) {
3679
3921
  continue;
3680
3922
  }
3681
- const key = path8.resolve(repoRoot);
3923
+ const key = path9.resolve(repoRoot);
3682
3924
  const bucket = manifestsByRepoRoot.get(key) ?? [];
3683
3925
  bucket.push(manifest);
3684
3926
  manifestsByRepoRoot.set(key, bucket);
@@ -3703,12 +3945,12 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
3703
3945
  skipped.push(
3704
3946
  ...collected.skipped,
3705
3947
  ...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
3706
- path: toPosix(path8.relative(rootDir, absolutePath)),
3948
+ path: toPosix(path9.relative(rootDir, absolutePath)),
3707
3949
  reason: "workspace_generated"
3708
3950
  }))
3709
3951
  );
3710
3952
  scannedCount += files.length;
3711
- const currentPaths = new Set(files.map((absolutePath) => path8.resolve(absolutePath)));
3953
+ const currentPaths = new Set(files.map((absolutePath) => path9.resolve(absolutePath)));
3712
3954
  for (const absolutePath of files) {
3713
3955
  const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
3714
3956
  const result = await persistPreparedInput(rootDir, prepared, paths);
@@ -3719,7 +3961,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
3719
3961
  }
3720
3962
  }
3721
3963
  for (const manifest of repoManifests) {
3722
- const originalPath = manifest.originalPath ? path8.resolve(manifest.originalPath) : null;
3964
+ const originalPath = manifest.originalPath ? path9.resolve(manifest.originalPath) : null;
3723
3965
  if (originalPath && !currentPaths.has(originalPath)) {
3724
3966
  await removeManifestArtifacts(rootDir, manifest, paths);
3725
3967
  removed.push(manifest);
@@ -3727,7 +3969,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
3727
3969
  }
3728
3970
  }
3729
3971
  if (uniqueRoots.length > 0) {
3730
- await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(path8.relative(rootDir, repoRoot)) || ".").join(","), [
3972
+ await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(path9.relative(rootDir, repoRoot)) || ".").join(","), [
3731
3973
  `repo_roots=${uniqueRoots.length}`,
3732
3974
  `scanned=${scannedCount}`,
3733
3975
  `imported=${imported.length}`,
@@ -3750,16 +3992,16 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
3750
3992
  const normalizedOptions = normalizeIngestOptions(options);
3751
3993
  const manifests = await listManifests(rootDir);
3752
3994
  const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
3753
- (item) => path8.resolve(item)
3995
+ (item) => path9.resolve(item)
3754
3996
  );
3755
3997
  const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
3756
3998
  const manifestsByRepoRoot = /* @__PURE__ */ new Map();
3757
3999
  for (const manifest of manifests) {
3758
4000
  const repoRoot = repoRootFromManifest(manifest);
3759
- if (!repoRoot || !uniqueRoots.includes(path8.resolve(repoRoot))) {
4001
+ if (!repoRoot || !uniqueRoots.includes(path9.resolve(repoRoot))) {
3760
4002
  continue;
3761
4003
  }
3762
- const key = path8.resolve(repoRoot);
4004
+ const key = path9.resolve(repoRoot);
3763
4005
  const bucket = manifestsByRepoRoot.get(key) ?? [];
3764
4006
  bucket.push(manifest);
3765
4007
  manifestsByRepoRoot.set(key, bucket);
@@ -3774,7 +4016,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
3774
4016
  for (const repoRoot of uniqueRoots) {
3775
4017
  const repoManifests = manifestsByRepoRoot.get(repoRoot) ?? [];
3776
4018
  const manifestsByOriginalPath = new Map(
3777
- repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [path8.resolve(manifest.originalPath), manifest])
4019
+ repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [path9.resolve(manifest.originalPath), manifest])
3778
4020
  );
3779
4021
  if (!await fileExists(repoRoot)) {
3780
4022
  for (const manifest of repoManifests) {
@@ -3782,7 +4024,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
3782
4024
  pendingSemanticRefresh.push({
3783
4025
  id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? manifest.storedPath),
3784
4026
  repoRoot,
3785
- path: toPosix(path8.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
4027
+ path: toPosix(path9.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
3786
4028
  changeType: "removed",
3787
4029
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
3788
4030
  sourceId: manifest.sourceId,
@@ -3802,16 +4044,16 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
3802
4044
  skipped.push(
3803
4045
  ...collected.skipped,
3804
4046
  ...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
3805
- path: toPosix(path8.relative(rootDir, absolutePath)),
4047
+ path: toPosix(path9.relative(rootDir, absolutePath)),
3806
4048
  reason: "workspace_generated"
3807
4049
  }))
3808
4050
  );
3809
4051
  scannedCount += files.length;
3810
- const currentPaths = new Set(files.map((absolutePath) => path8.resolve(absolutePath)));
4052
+ const currentPaths = new Set(files.map((absolutePath) => path9.resolve(absolutePath)));
3811
4053
  for (const absolutePath of files) {
3812
4054
  const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
3813
4055
  if (shouldDeferWatchSemanticRefresh(prepared.sourceKind)) {
3814
- const existing = manifestsByOriginalPath.get(path8.resolve(absolutePath));
4056
+ const existing = manifestsByOriginalPath.get(path9.resolve(absolutePath));
3815
4057
  const contentHash = buildCompositeHash(prepared.payloadBytes, prepared.attachments);
3816
4058
  const changed = !existing || !preparedMatchesManifest(existing, prepared, contentHash);
3817
4059
  if (changed) {
@@ -3819,10 +4061,10 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
3819
4061
  id: pendingSemanticRefreshId(
3820
4062
  existing ? "modified" : "added",
3821
4063
  repoRoot,
3822
- prepared.repoRelativePath ?? toPosix(path8.relative(repoRoot, absolutePath))
4064
+ prepared.repoRelativePath ?? toPosix(path9.relative(repoRoot, absolutePath))
3823
4065
  ),
3824
4066
  repoRoot,
3825
- path: toPosix(path8.relative(rootDir, absolutePath)),
4067
+ path: toPosix(path9.relative(rootDir, absolutePath)),
3826
4068
  changeType: existing ? "modified" : "added",
3827
4069
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
3828
4070
  sourceId: existing?.sourceId,
@@ -3842,13 +4084,13 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
3842
4084
  }
3843
4085
  }
3844
4086
  for (const manifest of repoManifests) {
3845
- const originalPath = manifest.originalPath ? path8.resolve(manifest.originalPath) : null;
4087
+ const originalPath = manifest.originalPath ? path9.resolve(manifest.originalPath) : null;
3846
4088
  if (originalPath && !currentPaths.has(originalPath)) {
3847
4089
  if (shouldDeferWatchSemanticRefresh(manifest.sourceKind)) {
3848
4090
  pendingSemanticRefresh.push({
3849
- id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path8.relative(repoRoot, originalPath))),
4091
+ id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path9.relative(repoRoot, originalPath))),
3850
4092
  repoRoot,
3851
- path: toPosix(path8.relative(rootDir, originalPath)),
4093
+ path: toPosix(path9.relative(rootDir, originalPath)),
3852
4094
  changeType: "removed",
3853
4095
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
3854
4096
  sourceId: manifest.sourceId,
@@ -3866,7 +4108,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
3866
4108
  await appendLogEntry(
3867
4109
  rootDir,
3868
4110
  "sync_repo_watch",
3869
- uniqueRoots.map((repoRoot) => toPosix(path8.relative(rootDir, repoRoot)) || ".").join(","),
4111
+ uniqueRoots.map((repoRoot) => toPosix(path9.relative(rootDir, repoRoot)) || ".").join(","),
3870
4112
  [
3871
4113
  `repo_roots=${uniqueRoots.length}`,
3872
4114
  `scanned=${scannedCount}`,
@@ -3891,19 +4133,36 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
3891
4133
  staleSourceIds: [...staleSourceIds]
3892
4134
  };
3893
4135
  }
3894
- async function prepareFileInput(_rootDir, absoluteInput, repoRoot) {
3895
- const payloadBytes = await fs8.readFile(absoluteInput);
4136
+ async function prepareFileInput(rootDir, absoluteInput, repoRoot) {
4137
+ const payloadBytes = await fs9.readFile(absoluteInput);
3896
4138
  const mimeType = guessMimeType(absoluteInput);
3897
4139
  const sourceKind = inferKind(mimeType, absoluteInput);
3898
4140
  const language = inferCodeLanguage(absoluteInput, mimeType);
3899
- const storedExtension = path8.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
4141
+ const storedExtension = path9.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
3900
4142
  let title;
3901
4143
  let extractedText;
4144
+ let extractionArtifact;
3902
4145
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
3903
4146
  extractedText = payloadBytes.toString("utf8");
3904
- title = titleFromText(path8.basename(absoluteInput, path8.extname(absoluteInput)), extractedText);
4147
+ title = titleFromText(path9.basename(absoluteInput, path9.extname(absoluteInput)), extractedText);
4148
+ extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
4149
+ } else if (sourceKind === "pdf") {
4150
+ title = path9.basename(absoluteInput, path9.extname(absoluteInput));
4151
+ const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
4152
+ extractedText = extracted.extractedText;
4153
+ extractionArtifact = extracted.artifact;
4154
+ } else if (sourceKind === "image") {
4155
+ title = path9.basename(absoluteInput, path9.extname(absoluteInput));
4156
+ const extracted = await extractImageWithVision(rootDir, {
4157
+ title,
4158
+ mimeType,
4159
+ filePath: absoluteInput
4160
+ });
4161
+ title = extracted.title?.trim() || title;
4162
+ extractedText = extracted.extractedText;
4163
+ extractionArtifact = extracted.artifact;
3905
4164
  } else {
3906
- title = path8.basename(absoluteInput, path8.extname(absoluteInput));
4165
+ title = path9.basename(absoluteInput, path9.extname(absoluteInput));
3907
4166
  }
3908
4167
  return {
3909
4168
  title,
@@ -3915,10 +4174,12 @@ async function prepareFileInput(_rootDir, absoluteInput, repoRoot) {
3915
4174
  mimeType,
3916
4175
  storedExtension,
3917
4176
  payloadBytes,
3918
- extractedText
4177
+ extractedText,
4178
+ extractionArtifact,
4179
+ extractionHash: buildExtractionHash(extractedText, extractionArtifact)
3919
4180
  };
3920
4181
  }
3921
- async function prepareUrlInput(input, options) {
4182
+ async function prepareUrlInput(rootDir, input, options) {
3922
4183
  const response = await fetch(input);
3923
4184
  if (!response.ok) {
3924
4185
  throw new Error(`Failed to fetch ${input}: ${response.status} ${response.statusText}`);
@@ -3932,6 +4193,7 @@ async function prepareUrlInput(input, options) {
3932
4193
  let storedExtension = ".bin";
3933
4194
  let title = inputUrl.hostname + inputUrl.pathname;
3934
4195
  let extractedText;
4196
+ let extractionArtifact;
3935
4197
  let attachments;
3936
4198
  let contentHash;
3937
4199
  const logDetails = [];
@@ -3962,6 +4224,7 @@ async function prepareUrlInput(input, options) {
3962
4224
  }
3963
4225
  const converted = localizedHtml === html && !attachments?.length ? initialConversion : await convertHtmlToMarkdown(localizedHtml, input);
3964
4226
  extractedText = converted.markdown;
4227
+ extractionArtifact = createHtmlReadabilityExtractionArtifact("markdown", "text/markdown");
3965
4228
  if (localAssetReplacements?.size) {
3966
4229
  const absoluteLocalAssetReplacements = new Map(
3967
4230
  [...localAssetReplacements.values()].map((replacement) => [new URL(replacement, input).toString(), replacement])
@@ -3973,11 +4236,12 @@ async function prepareUrlInput(input, options) {
3973
4236
  sourceKind = "markdown";
3974
4237
  storedExtension = ".md";
3975
4238
  } else {
3976
- const extension = path8.extname(inputUrl.pathname);
4239
+ const extension = path9.extname(inputUrl.pathname);
3977
4240
  storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
3978
4241
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
3979
4242
  extractedText = payloadBytes.toString("utf8");
3980
4243
  title = titleFromText(title || inputUrl.hostname, extractedText);
4244
+ extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
3981
4245
  if (sourceKind === "markdown" && options.includeAssets) {
3982
4246
  const { attachments: remoteAttachments, skippedCount } = await collectRemoteImageAttachments(
3983
4247
  extractMarkdownImageReferences(extractedText, input),
@@ -3998,6 +4262,19 @@ async function prepareUrlInput(input, options) {
3998
4262
  logDetails.push(`remote_asset_skips=${skippedCount}`);
3999
4263
  }
4000
4264
  }
4265
+ } else if (sourceKind === "pdf") {
4266
+ const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
4267
+ extractedText = extracted.extractedText;
4268
+ extractionArtifact = extracted.artifact;
4269
+ } else if (sourceKind === "image") {
4270
+ const extracted = await extractImageWithVision(rootDir, {
4271
+ title,
4272
+ mimeType,
4273
+ bytes: payloadBytes
4274
+ });
4275
+ title = extracted.title?.trim() || title;
4276
+ extractedText = extracted.extractedText;
4277
+ extractionArtifact = extracted.artifact;
4001
4278
  }
4002
4279
  }
4003
4280
  return {
@@ -4010,6 +4287,8 @@ async function prepareUrlInput(input, options) {
4010
4287
  storedExtension,
4011
4288
  payloadBytes,
4012
4289
  extractedText,
4290
+ extractionArtifact,
4291
+ extractionHash: buildExtractionHash(extractedText, extractionArtifact),
4013
4292
  attachments,
4014
4293
  contentHash,
4015
4294
  logDetails
@@ -4023,14 +4302,14 @@ async function collectInboxAttachmentRefs(inputDir, files) {
4023
4302
  if (sourceKind !== "markdown") {
4024
4303
  continue;
4025
4304
  }
4026
- const content = await fs8.readFile(absolutePath, "utf8");
4305
+ const content = await fs9.readFile(absolutePath, "utf8");
4027
4306
  const refs = extractMarkdownReferences(content);
4028
4307
  if (!refs.length) {
4029
4308
  continue;
4030
4309
  }
4031
4310
  const sourceRefs = [];
4032
4311
  for (const ref of refs) {
4033
- const resolved = path8.resolve(path8.dirname(absolutePath), ref);
4312
+ const resolved = path9.resolve(path9.dirname(absolutePath), ref);
4034
4313
  if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
4035
4314
  continue;
4036
4315
  }
@@ -4064,12 +4343,12 @@ function rewriteMarkdownReferences(content, replacements) {
4064
4343
  });
4065
4344
  }
4066
4345
  async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
4067
- const originalBytes = await fs8.readFile(absolutePath);
4346
+ const originalBytes = await fs9.readFile(absolutePath);
4068
4347
  const originalText = originalBytes.toString("utf8");
4069
- const title = titleFromText(path8.basename(absolutePath, path8.extname(absolutePath)), originalText);
4348
+ const title = titleFromText(path9.basename(absolutePath, path9.extname(absolutePath)), originalText);
4070
4349
  const attachments = [];
4071
4350
  for (const attachmentRef of attachmentRefs) {
4072
- const bytes = await fs8.readFile(attachmentRef.absolutePath);
4351
+ const bytes = await fs9.readFile(attachmentRef.absolutePath);
4073
4352
  attachments.push({
4074
4353
  relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
4075
4354
  mimeType: guessMimeType(attachmentRef.absolutePath),
@@ -4086,15 +4365,18 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
4086
4365
  ])
4087
4366
  );
4088
4367
  const rewrittenText = rewriteMarkdownReferences(originalText, replacements);
4368
+ const extractionArtifact = createPlainTextExtractionArtifact("markdown", "text/markdown");
4089
4369
  return {
4090
4370
  title,
4091
4371
  originType: "file",
4092
4372
  sourceKind: "markdown",
4093
4373
  originalPath: toPosix(absolutePath),
4094
4374
  mimeType: "text/markdown",
4095
- storedExtension: path8.extname(absolutePath) || ".md",
4375
+ storedExtension: path9.extname(absolutePath) || ".md",
4096
4376
  payloadBytes: Buffer.from(rewrittenText, "utf8"),
4097
4377
  extractedText: rewrittenText,
4378
+ extractionArtifact,
4379
+ extractionHash: buildExtractionHash(rewrittenText, extractionArtifact),
4098
4380
  attachments,
4099
4381
  contentHash
4100
4382
  };
@@ -4105,9 +4387,9 @@ function isSupportedInboxKind(sourceKind) {
4105
4387
  async function ingestInput(rootDir, input, options) {
4106
4388
  const { paths } = await initWorkspace(rootDir);
4107
4389
  const normalizedOptions = normalizeIngestOptions(options);
4108
- const absoluteInput = path8.resolve(rootDir, input);
4109
- const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ?? path8.dirname(absoluteInput));
4110
- const prepared = isHttpUrl(input) ? await prepareUrlInput(input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
4390
+ const absoluteInput = path9.resolve(rootDir, input);
4391
+ const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ?? path9.dirname(absoluteInput));
4392
+ const prepared = isHttpUrl(input) ? await prepareUrlInput(rootDir, input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
4111
4393
  const result = await persistPreparedInput(rootDir, prepared, paths);
4112
4394
  return result.manifest;
4113
4395
  }
@@ -4167,7 +4449,7 @@ async function addInput(rootDir, input, options = {}) {
4167
4449
  async function ingestDirectory(rootDir, inputDir, options) {
4168
4450
  const { paths } = await initWorkspace(rootDir);
4169
4451
  const normalizedOptions = normalizeIngestOptions(options);
4170
- const absoluteInputDir = path8.resolve(rootDir, inputDir);
4452
+ const absoluteInputDir = path9.resolve(rootDir, inputDir);
4171
4453
  const repoRoot = normalizedOptions.repoRoot ?? await findNearestGitRoot2(absoluteInputDir) ?? absoluteInputDir;
4172
4454
  if (!await fileExists(absoluteInputDir)) {
4173
4455
  throw new Error(`Directory not found: ${absoluteInputDir}`);
@@ -4183,11 +4465,11 @@ async function ingestDirectory(rootDir, inputDir, options) {
4183
4465
  } else if (result.wasUpdated) {
4184
4466
  updated.push(result.manifest);
4185
4467
  } else {
4186
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4468
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4187
4469
  }
4188
4470
  }
4189
- await appendLogEntry(rootDir, "ingest_directory", toPosix(path8.relative(rootDir, absoluteInputDir)) || ".", [
4190
- `repo_root=${toPosix(path8.relative(rootDir, repoRoot)) || "."}`,
4471
+ await appendLogEntry(rootDir, "ingest_directory", toPosix(path9.relative(rootDir, absoluteInputDir)) || ".", [
4472
+ `repo_root=${toPosix(path9.relative(rootDir, repoRoot)) || "."}`,
4191
4473
  `scanned=${files.length}`,
4192
4474
  `imported=${imported.length}`,
4193
4475
  `updated=${updated.length}`,
@@ -4204,7 +4486,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
4204
4486
  }
4205
4487
  async function importInbox(rootDir, inputDir) {
4206
4488
  const { paths } = await initWorkspace(rootDir);
4207
- const effectiveInputDir = path8.resolve(rootDir, inputDir ?? paths.inboxDir);
4489
+ const effectiveInputDir = path9.resolve(rootDir, inputDir ?? paths.inboxDir);
4208
4490
  if (!await fileExists(effectiveInputDir)) {
4209
4491
  throw new Error(`Inbox directory not found: ${effectiveInputDir}`);
4210
4492
  }
@@ -4215,31 +4497,31 @@ async function importInbox(rootDir, inputDir) {
4215
4497
  const skipped = [];
4216
4498
  let attachmentCount = 0;
4217
4499
  for (const absolutePath of files) {
4218
- const basename = path8.basename(absolutePath);
4500
+ const basename = path9.basename(absolutePath);
4219
4501
  if (basename.startsWith(".")) {
4220
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "hidden_file" });
4502
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "hidden_file" });
4221
4503
  continue;
4222
4504
  }
4223
4505
  if (claimedAttachments.has(absolutePath)) {
4224
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
4506
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
4225
4507
  continue;
4226
4508
  }
4227
4509
  const mimeType = guessMimeType(absolutePath);
4228
4510
  const sourceKind = inferKind(mimeType, absolutePath);
4229
4511
  if (!isSupportedInboxKind(sourceKind)) {
4230
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
4512
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
4231
4513
  continue;
4232
4514
  }
4233
4515
  const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
4234
4516
  const result = await persistPreparedInput(rootDir, prepared, paths);
4235
4517
  if (!result.isNew) {
4236
- skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4518
+ skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4237
4519
  continue;
4238
4520
  }
4239
4521
  attachmentCount += result.manifest.attachments?.length ?? 0;
4240
4522
  imported.push(result.manifest);
4241
4523
  }
4242
- await appendLogEntry(rootDir, "inbox_import", toPosix(path8.relative(rootDir, effectiveInputDir)) || ".", [
4524
+ await appendLogEntry(rootDir, "inbox_import", toPosix(path9.relative(rootDir, effectiveInputDir)) || ".", [
4243
4525
  `scanned=${files.length}`,
4244
4526
  `imported=${imported.length}`,
4245
4527
  `attachments=${attachmentCount}`,
@@ -4258,9 +4540,9 @@ async function listManifests(rootDir) {
4258
4540
  if (!await fileExists(paths.manifestsDir)) {
4259
4541
  return [];
4260
4542
  }
4261
- const entries = await fs8.readdir(paths.manifestsDir);
4543
+ const entries = await fs9.readdir(paths.manifestsDir);
4262
4544
  const manifests = await Promise.all(
4263
- entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path8.join(paths.manifestsDir, entry)))
4545
+ entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path9.join(paths.manifestsDir, entry)))
4264
4546
  );
4265
4547
  return manifests.filter((manifest) => Boolean(manifest));
4266
4548
  }
@@ -4268,28 +4550,38 @@ async function readExtractedText(rootDir, manifest) {
4268
4550
  if (!manifest.extractedTextPath) {
4269
4551
  return void 0;
4270
4552
  }
4271
- const absolutePath = path8.resolve(rootDir, manifest.extractedTextPath);
4553
+ const absolutePath = path9.resolve(rootDir, manifest.extractedTextPath);
4554
+ if (!await fileExists(absolutePath)) {
4555
+ return void 0;
4556
+ }
4557
+ return fs9.readFile(absolutePath, "utf8");
4558
+ }
4559
+ async function readExtractionArtifact(rootDir, manifest) {
4560
+ if (!manifest.extractedMetadataPath) {
4561
+ return void 0;
4562
+ }
4563
+ const absolutePath = path9.resolve(rootDir, manifest.extractedMetadataPath);
4272
4564
  if (!await fileExists(absolutePath)) {
4273
4565
  return void 0;
4274
4566
  }
4275
- return fs8.readFile(absolutePath, "utf8");
4567
+ return await readJsonFile(absolutePath) ?? void 0;
4276
4568
  }
4277
4569
 
4278
4570
  // src/mcp.ts
4279
- import fs15 from "fs/promises";
4280
- import path18 from "path";
4571
+ import fs16 from "fs/promises";
4572
+ import path19 from "path";
4281
4573
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4282
4574
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4283
- import { z as z7 } from "zod";
4575
+ import { z as z8 } from "zod";
4284
4576
 
4285
4577
  // src/schema.ts
4286
- import fs9 from "fs/promises";
4287
- import path9 from "path";
4578
+ import fs10 from "fs/promises";
4579
+ import path10 from "path";
4288
4580
  function normalizeSchemaContent(content) {
4289
4581
  return content.trim() ? content.trim() : defaultVaultSchema().trim();
4290
4582
  }
4291
4583
  async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
4292
- const content = await fileExists(schemaPath) ? await fs9.readFile(schemaPath, "utf8") : fallback;
4584
+ const content = await fileExists(schemaPath) ? await fs10.readFile(schemaPath, "utf8") : fallback;
4293
4585
  const normalized = normalizeSchemaContent(content);
4294
4586
  return {
4295
4587
  path: schemaPath,
@@ -4298,7 +4590,7 @@ async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
4298
4590
  };
4299
4591
  }
4300
4592
  function resolveProjectSchemaPath(rootDir, schemaPath) {
4301
- return path9.resolve(rootDir, schemaPath);
4593
+ return path10.resolve(rootDir, schemaPath);
4302
4594
  }
4303
4595
  function composeVaultSchema(root, projectSchemas = []) {
4304
4596
  if (!projectSchemas.length) {
@@ -4314,7 +4606,7 @@ function composeVaultSchema(root, projectSchemas = []) {
4314
4606
  (schema) => [
4315
4607
  `## Project Schema`,
4316
4608
  "",
4317
- `Path: ${toPosix(path9.relative(path9.dirname(root.path), schema.path) || schema.path)}`,
4609
+ `Path: ${toPosix(path10.relative(path10.dirname(root.path), schema.path) || schema.path)}`,
4318
4610
  "",
4319
4611
  schema.content
4320
4612
  ].join("\n")
@@ -4390,30 +4682,30 @@ function buildSchemaPrompt(schema, instruction) {
4390
4682
  }
4391
4683
 
4392
4684
  // src/vault.ts
4393
- import fs14 from "fs/promises";
4394
- import path17 from "path";
4685
+ import fs15 from "fs/promises";
4686
+ import path18 from "path";
4395
4687
  import matter8 from "gray-matter";
4396
- import { z as z6 } from "zod";
4688
+ import { z as z7 } from "zod";
4397
4689
 
4398
4690
  // src/analysis.ts
4399
- import path10 from "path";
4400
- import { z } from "zod";
4401
- var ANALYSIS_FORMAT_VERSION = 4;
4402
- var sourceAnalysisSchema = z.object({
4403
- title: z.string().min(1),
4404
- summary: z.string().min(1),
4405
- concepts: z.array(z.object({ name: z.string().min(1), description: z.string().default("") })).max(12).default([]),
4406
- entities: z.array(z.object({ name: z.string().min(1), description: z.string().default("") })).max(12).default([]),
4407
- claims: z.array(
4408
- z.object({
4409
- text: z.string().min(1),
4410
- confidence: z.number().min(0).max(1).default(0.6),
4411
- status: z.enum(["extracted", "inferred", "conflicted", "stale"]).default("extracted"),
4412
- polarity: z.enum(["positive", "negative", "neutral"]).default("neutral"),
4413
- citation: z.string().min(1)
4691
+ import path11 from "path";
4692
+ import { z as z2 } from "zod";
4693
+ var ANALYSIS_FORMAT_VERSION = 5;
4694
+ var sourceAnalysisSchema = z2.object({
4695
+ title: z2.string().min(1),
4696
+ summary: z2.string().min(1),
4697
+ concepts: z2.array(z2.object({ name: z2.string().min(1), description: z2.string().default("") })).max(12).default([]),
4698
+ entities: z2.array(z2.object({ name: z2.string().min(1), description: z2.string().default("") })).max(12).default([]),
4699
+ claims: z2.array(
4700
+ z2.object({
4701
+ text: z2.string().min(1),
4702
+ confidence: z2.number().min(0).max(1).default(0.6),
4703
+ status: z2.enum(["extracted", "inferred", "conflicted", "stale"]).default("extracted"),
4704
+ polarity: z2.enum(["positive", "negative", "neutral"]).default("neutral"),
4705
+ citation: z2.string().min(1)
4414
4706
  })
4415
4707
  ).max(8).default([]),
4416
- questions: z.array(z.string()).max(6).default([])
4708
+ questions: z2.array(z2.string()).max(6).default([])
4417
4709
  });
4418
4710
  var STOPWORDS = /* @__PURE__ */ new Set([
4419
4711
  "about",
@@ -4502,6 +4794,7 @@ function heuristicAnalysis(manifest, text, schemaHash) {
4502
4794
  analysisVersion: ANALYSIS_FORMAT_VERSION,
4503
4795
  sourceId: manifest.sourceId,
4504
4796
  sourceHash: manifest.contentHash,
4797
+ extractionHash: manifest.extractionHash,
4505
4798
  schemaHash,
4506
4799
  title: deriveTitle(manifest, text),
4507
4800
  summary: firstSentences(normalized, 3) || truncate(normalized, 280) || `Imported ${manifest.sourceKind} source.`,
@@ -4548,6 +4841,7 @@ ${truncate(text, 18e3)}`
4548
4841
  analysisVersion: ANALYSIS_FORMAT_VERSION,
4549
4842
  sourceId: manifest.sourceId,
4550
4843
  sourceHash: manifest.contentHash,
4844
+ extractionHash: manifest.extractionHash,
4551
4845
  schemaHash: schema.hash,
4552
4846
  title: parsed.title,
4553
4847
  summary: parsed.summary,
@@ -4574,24 +4868,97 @@ ${truncate(text, 18e3)}`
4574
4868
  producedAt: (/* @__PURE__ */ new Date()).toISOString()
4575
4869
  };
4576
4870
  }
4871
+ function analysisFromVisionExtraction(manifest, extraction, schemaHash) {
4872
+ if (!extraction.vision) {
4873
+ return null;
4874
+ }
4875
+ return {
4876
+ analysisVersion: ANALYSIS_FORMAT_VERSION,
4877
+ sourceId: manifest.sourceId,
4878
+ sourceHash: manifest.contentHash,
4879
+ extractionHash: manifest.extractionHash,
4880
+ schemaHash,
4881
+ title: extraction.vision.title?.trim() || manifest.title,
4882
+ summary: extraction.vision.summary,
4883
+ concepts: extraction.vision.concepts.map((term) => ({
4884
+ id: `concept:${slugify(term.name)}`,
4885
+ name: term.name,
4886
+ description: term.description
4887
+ })),
4888
+ entities: extraction.vision.entities.map((term) => ({
4889
+ id: `entity:${slugify(term.name)}`,
4890
+ name: term.name,
4891
+ description: term.description
4892
+ })),
4893
+ claims: extraction.vision.claims.map((claim, index) => ({
4894
+ id: `claim:${manifest.sourceId}:${index + 1}`,
4895
+ text: claim.text,
4896
+ confidence: claim.confidence,
4897
+ status: "extracted",
4898
+ polarity: claim.polarity,
4899
+ citation: manifest.sourceId
4900
+ })),
4901
+ questions: extraction.vision.questions,
4902
+ rationales: [],
4903
+ producedAt: (/* @__PURE__ */ new Date()).toISOString()
4904
+ };
4905
+ }
4906
+ function extractionWarningSummary(manifest, extraction) {
4907
+ const warning = extraction?.warnings?.find(Boolean);
4908
+ if (warning) {
4909
+ return `Imported ${manifest.sourceKind} source. ${warning}`;
4910
+ }
4911
+ return `Imported ${manifest.sourceKind} source. Text extraction is not yet available for this source.`;
4912
+ }
4577
4913
  async function analyzeSource(manifest, extractedText, provider, paths, schema) {
4578
- const cachePath = path10.join(paths.analysesDir, `${manifest.sourceId}.json`);
4914
+ const cachePath = path11.join(paths.analysesDir, `${manifest.sourceId}.json`);
4579
4915
  const cached = await readJsonFile(cachePath);
4580
- if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.schemaHash === schema.hash) {
4916
+ if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
4581
4917
  return cached;
4582
4918
  }
4919
+ const extraction = await readExtractionArtifact(paths.rootDir, manifest);
4583
4920
  const content = normalizeWhitespace(extractedText ?? "");
4584
4921
  let analysis;
4585
4922
  if (manifest.sourceKind === "code" && content) {
4586
4923
  analysis = await analyzeCodeSource(manifest, extractedText ?? "", schema.hash);
4924
+ } else if (manifest.sourceKind === "image") {
4925
+ const visionAnalysis = extraction ? analysisFromVisionExtraction(manifest, extraction, schema.hash) : null;
4926
+ if (visionAnalysis) {
4927
+ analysis = visionAnalysis;
4928
+ } else if (!content) {
4929
+ analysis = {
4930
+ analysisVersion: ANALYSIS_FORMAT_VERSION,
4931
+ sourceId: manifest.sourceId,
4932
+ sourceHash: manifest.contentHash,
4933
+ extractionHash: manifest.extractionHash,
4934
+ schemaHash: schema.hash,
4935
+ title: manifest.title,
4936
+ summary: extractionWarningSummary(manifest, extraction),
4937
+ concepts: [],
4938
+ entities: [],
4939
+ claims: [],
4940
+ questions: [],
4941
+ rationales: [],
4942
+ producedAt: (/* @__PURE__ */ new Date()).toISOString()
4943
+ };
4944
+ } else if (provider.type === "heuristic") {
4945
+ analysis = heuristicAnalysis(manifest, content, schema.hash);
4946
+ } else {
4947
+ try {
4948
+ analysis = await providerAnalysis(manifest, content, provider, schema);
4949
+ } catch {
4950
+ analysis = heuristicAnalysis(manifest, content, schema.hash);
4951
+ }
4952
+ }
4587
4953
  } else if (!content) {
4588
4954
  analysis = {
4589
4955
  analysisVersion: ANALYSIS_FORMAT_VERSION,
4590
4956
  sourceId: manifest.sourceId,
4591
4957
  sourceHash: manifest.contentHash,
4958
+ extractionHash: manifest.extractionHash,
4592
4959
  schemaHash: schema.hash,
4593
4960
  title: manifest.title,
4594
- summary: `Imported ${manifest.sourceKind} source. Text extraction is not yet available for this source.`,
4961
+ summary: extractionWarningSummary(manifest, extraction),
4595
4962
  concepts: [],
4596
4963
  entities: [],
4597
4964
  claims: [],
@@ -4714,10 +5081,10 @@ function conflictConfidence(claimA, claimB) {
4714
5081
  }
4715
5082
 
4716
5083
  // src/deep-lint.ts
4717
- import fs10 from "fs/promises";
4718
- import path13 from "path";
5084
+ import fs11 from "fs/promises";
5085
+ import path14 from "path";
4719
5086
  import matter3 from "gray-matter";
4720
- import { z as z4 } from "zod";
5087
+ import { z as z5 } from "zod";
4721
5088
 
4722
5089
  // src/findings.ts
4723
5090
  function normalizeFindingSeverity(value) {
@@ -4736,25 +5103,25 @@ function normalizeFindingSeverity(value) {
4736
5103
 
4737
5104
  // src/orchestration.ts
4738
5105
  import { spawn } from "child_process";
4739
- import path11 from "path";
4740
- import { z as z2 } from "zod";
4741
- var orchestrationRoleResultSchema = z2.object({
4742
- summary: z2.string().optional(),
4743
- findings: z2.array(
4744
- z2.object({
4745
- severity: z2.string().optional().default("info"),
4746
- message: z2.string().min(1),
4747
- relatedPageIds: z2.array(z2.string()).optional(),
4748
- relatedSourceIds: z2.array(z2.string()).optional(),
4749
- suggestedQuery: z2.string().optional()
5106
+ import path12 from "path";
5107
+ import { z as z3 } from "zod";
5108
+ var orchestrationRoleResultSchema = z3.object({
5109
+ summary: z3.string().optional(),
5110
+ findings: z3.array(
5111
+ z3.object({
5112
+ severity: z3.string().optional().default("info"),
5113
+ message: z3.string().min(1),
5114
+ relatedPageIds: z3.array(z3.string()).optional(),
5115
+ relatedSourceIds: z3.array(z3.string()).optional(),
5116
+ suggestedQuery: z3.string().optional()
4750
5117
  })
4751
5118
  ).default([]),
4752
- questions: z2.array(z2.string().min(1)).default([]),
4753
- proposals: z2.array(
4754
- z2.object({
4755
- path: z2.string().min(1),
4756
- content: z2.string().min(1),
4757
- reason: z2.string().min(1)
5119
+ questions: z3.array(z3.string().min(1)).default([]),
5120
+ proposals: z3.array(
5121
+ z3.object({
5122
+ path: z3.string().min(1),
5123
+ content: z3.string().min(1),
5124
+ reason: z3.string().min(1)
4758
5125
  })
4759
5126
  ).default([])
4760
5127
  });
@@ -4829,7 +5196,7 @@ async function runProviderRole(rootDir, role, roleConfig, input) {
4829
5196
  }
4830
5197
  async function runCommandRole(rootDir, role, executor, input) {
4831
5198
  const [command, ...args] = executor.command;
4832
- const cwd = executor.cwd ? path11.resolve(rootDir, executor.cwd) : rootDir;
5199
+ const cwd = executor.cwd ? path12.resolve(rootDir, executor.cwd) : rootDir;
4833
5200
  const child = spawn(command, args, {
4834
5201
  cwd,
4835
5202
  env: {
@@ -4923,9 +5290,9 @@ function summarizeRoleQuestions(results) {
4923
5290
  }
4924
5291
 
4925
5292
  // src/web-search/registry.ts
4926
- import path12 from "path";
5293
+ import path13 from "path";
4927
5294
  import { pathToFileURL } from "url";
4928
- import { z as z3 } from "zod";
5295
+ import { z as z4 } from "zod";
4929
5296
 
4930
5297
  // src/web-search/http-json.ts
4931
5298
  function deepGet(value, pathValue) {
@@ -5007,10 +5374,10 @@ var HttpJsonWebSearchAdapter = class {
5007
5374
  };
5008
5375
 
5009
5376
  // src/web-search/registry.ts
5010
- var customWebSearchModuleSchema = z3.object({
5011
- createAdapter: z3.function({
5012
- input: [z3.string(), z3.custom(), z3.string()],
5013
- output: z3.promise(z3.custom())
5377
+ var customWebSearchModuleSchema = z4.object({
5378
+ createAdapter: z4.function({
5379
+ input: [z4.string(), z4.custom(), z4.string()],
5380
+ output: z4.promise(z4.custom())
5014
5381
  })
5015
5382
  });
5016
5383
  async function createWebSearchAdapter(id, config, rootDir) {
@@ -5021,7 +5388,7 @@ async function createWebSearchAdapter(id, config, rootDir) {
5021
5388
  if (!config.module) {
5022
5389
  throw new Error(`Web search provider ${id} is type "custom" but no module path was configured.`);
5023
5390
  }
5024
- const resolvedModule = path12.isAbsolute(config.module) ? config.module : path12.resolve(rootDir, config.module);
5391
+ const resolvedModule = path13.isAbsolute(config.module) ? config.module : path13.resolve(rootDir, config.module);
5025
5392
  const loaded = await import(pathToFileURL(resolvedModule).href);
5026
5393
  const parsed = customWebSearchModuleSchema.parse(loaded);
5027
5394
  return parsed.createAdapter(id, config, rootDir);
@@ -5045,15 +5412,15 @@ async function getWebSearchAdapterForTask(rootDir, task) {
5045
5412
  }
5046
5413
 
5047
5414
  // src/deep-lint.ts
5048
- var deepLintResponseSchema = z4.object({
5049
- findings: z4.array(
5050
- z4.object({
5051
- severity: z4.string().optional().default("info"),
5052
- code: z4.enum(["coverage_gap", "contradiction_candidate", "missing_citation", "candidate_page", "follow_up_question"]),
5053
- message: z4.string().min(1),
5054
- relatedSourceIds: z4.array(z4.string()).default([]),
5055
- relatedPageIds: z4.array(z4.string()).default([]),
5056
- suggestedQuery: z4.string().optional()
5415
+ var deepLintResponseSchema = z5.object({
5416
+ findings: z5.array(
5417
+ z5.object({
5418
+ severity: z5.string().optional().default("info"),
5419
+ code: z5.enum(["coverage_gap", "contradiction_candidate", "missing_citation", "candidate_page", "follow_up_question"]),
5420
+ message: z5.string().min(1),
5421
+ relatedSourceIds: z5.array(z5.string()).default([]),
5422
+ relatedPageIds: z5.array(z5.string()).default([]),
5423
+ suggestedQuery: z5.string().optional()
5057
5424
  })
5058
5425
  ).max(20)
5059
5426
  });
@@ -5081,8 +5448,8 @@ async function loadContextPages(rootDir, graph) {
5081
5448
  );
5082
5449
  return Promise.all(
5083
5450
  contextPages.slice(0, 18).map(async (page) => {
5084
- const absolutePath = path13.join(paths.wikiDir, page.path);
5085
- const raw = await fs10.readFile(absolutePath, "utf8").catch(() => "");
5451
+ const absolutePath = path14.join(paths.wikiDir, page.path);
5452
+ const raw = await fs11.readFile(absolutePath, "utf8").catch(() => "");
5086
5453
  const parsed = matter3(raw);
5087
5454
  return {
5088
5455
  id: page.id,
@@ -5130,7 +5497,7 @@ function heuristicDeepFindings(contextPages, structuralFindings, graph) {
5130
5497
  code: "missing_citation",
5131
5498
  message: finding.message,
5132
5499
  pagePath: finding.pagePath,
5133
- suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path13.basename(finding.pagePath, ".md")}?` : void 0
5500
+ suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path14.basename(finding.pagePath, ".md")}?` : void 0
5134
5501
  });
5135
5502
  }
5136
5503
  for (const page of contextPages.filter((item) => item.kind === "source").slice(0, 3)) {
@@ -6653,49 +7020,49 @@ function buildExploreHubPage(input) {
6653
7020
  }
6654
7021
 
6655
7022
  // src/output-artifacts.ts
6656
- import { z as z5 } from "zod";
7023
+ import { z as z6 } from "zod";
6657
7024
  function escapeXml(value) {
6658
7025
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
6659
7026
  }
6660
7027
  function clampNumber(value, min, max) {
6661
7028
  return Math.min(max, Math.max(min, value));
6662
7029
  }
6663
- var chartSpecSchema = z5.object({
6664
- kind: z5.enum(["bar", "line"]).default("bar"),
6665
- title: z5.string().min(1),
6666
- subtitle: z5.string().optional(),
6667
- xLabel: z5.string().optional(),
6668
- yLabel: z5.string().optional(),
6669
- seriesLabel: z5.string().optional(),
6670
- data: z5.array(
6671
- z5.object({
6672
- label: z5.string().min(1),
6673
- value: z5.number().finite()
7030
+ var chartSpecSchema = z6.object({
7031
+ kind: z6.enum(["bar", "line"]).default("bar"),
7032
+ title: z6.string().min(1),
7033
+ subtitle: z6.string().optional(),
7034
+ xLabel: z6.string().optional(),
7035
+ yLabel: z6.string().optional(),
7036
+ seriesLabel: z6.string().optional(),
7037
+ data: z6.array(
7038
+ z6.object({
7039
+ label: z6.string().min(1),
7040
+ value: z6.number().finite()
6674
7041
  })
6675
7042
  ).min(2).max(12),
6676
- notes: z5.array(z5.string().min(1)).max(5).optional()
7043
+ notes: z6.array(z6.string().min(1)).max(5).optional()
6677
7044
  });
6678
- var sceneSpecSchema = z5.object({
6679
- title: z5.string().min(1),
6680
- alt: z5.string().min(1),
6681
- background: z5.string().optional(),
6682
- width: z5.number().int().positive().max(2400).optional(),
6683
- height: z5.number().int().positive().max(2400).optional(),
6684
- elements: z5.array(
6685
- z5.object({
6686
- kind: z5.enum(["shape", "label"]),
6687
- shape: z5.enum(["rect", "circle", "line"]).optional(),
6688
- x: z5.number().finite(),
6689
- y: z5.number().finite(),
6690
- width: z5.number().finite().optional(),
6691
- height: z5.number().finite().optional(),
6692
- radius: z5.number().finite().optional(),
6693
- text: z5.string().optional(),
6694
- fontSize: z5.number().finite().optional(),
6695
- fill: z5.string().optional(),
6696
- stroke: z5.string().optional(),
6697
- strokeWidth: z5.number().finite().optional(),
6698
- opacity: z5.number().finite().optional()
7045
+ var sceneSpecSchema = z6.object({
7046
+ title: z6.string().min(1),
7047
+ alt: z6.string().min(1),
7048
+ background: z6.string().optional(),
7049
+ width: z6.number().int().positive().max(2400).optional(),
7050
+ height: z6.number().int().positive().max(2400).optional(),
7051
+ elements: z6.array(
7052
+ z6.object({
7053
+ kind: z6.enum(["shape", "label"]),
7054
+ shape: z6.enum(["rect", "circle", "line"]).optional(),
7055
+ x: z6.number().finite(),
7056
+ y: z6.number().finite(),
7057
+ width: z6.number().finite().optional(),
7058
+ height: z6.number().finite().optional(),
7059
+ radius: z6.number().finite().optional(),
7060
+ text: z6.string().optional(),
7061
+ fontSize: z6.number().finite().optional(),
7062
+ fill: z6.string().optional(),
7063
+ stroke: z6.string().optional(),
7064
+ strokeWidth: z6.number().finite().optional(),
7065
+ opacity: z6.number().finite().optional()
6699
7066
  })
6700
7067
  ).min(1).max(32)
6701
7068
  });
@@ -6847,13 +7214,13 @@ function buildOutputAssetManifest(input) {
6847
7214
  }
6848
7215
 
6849
7216
  // src/outputs.ts
6850
- import fs12 from "fs/promises";
6851
- import path15 from "path";
7217
+ import fs13 from "fs/promises";
7218
+ import path16 from "path";
6852
7219
  import matter6 from "gray-matter";
6853
7220
 
6854
7221
  // src/pages.ts
6855
- import fs11 from "fs/promises";
6856
- import path14 from "path";
7222
+ import fs12 from "fs/promises";
7223
+ import path15 from "path";
6857
7224
  import matter5 from "gray-matter";
6858
7225
  function normalizeStringArray(value) {
6859
7226
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
@@ -6925,7 +7292,7 @@ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
6925
7292
  updatedAt: updatedFallback
6926
7293
  };
6927
7294
  }
6928
- const content = await fs11.readFile(absolutePath, "utf8");
7295
+ const content = await fs12.readFile(absolutePath, "utf8");
6929
7296
  const parsed = matter5(content);
6930
7297
  return {
6931
7298
  status: normalizePageStatus(parsed.data.status, defaults.status ?? "active"),
@@ -6964,7 +7331,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
6964
7331
  const now = (/* @__PURE__ */ new Date()).toISOString();
6965
7332
  const fallbackCreatedAt = defaults.createdAt ?? now;
6966
7333
  const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
6967
- const title = typeof parsed.data.title === "string" ? parsed.data.title : path14.basename(relativePath, ".md");
7334
+ const title = typeof parsed.data.title === "string" ? parsed.data.title : path15.basename(relativePath, ".md");
6968
7335
  const kind = inferPageKind(relativePath, parsed.data.kind);
6969
7336
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
6970
7337
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
@@ -7003,18 +7370,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
7003
7370
  };
7004
7371
  }
7005
7372
  async function loadInsightPages(wikiDir) {
7006
- const insightsDir = path14.join(wikiDir, "insights");
7373
+ const insightsDir = path15.join(wikiDir, "insights");
7007
7374
  if (!await fileExists(insightsDir)) {
7008
7375
  return [];
7009
7376
  }
7010
- const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path14.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
7377
+ const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path15.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
7011
7378
  const insights = [];
7012
7379
  for (const absolutePath of files) {
7013
- const relativePath = toPosix(path14.relative(wikiDir, absolutePath));
7014
- const content = await fs11.readFile(absolutePath, "utf8");
7380
+ const relativePath = toPosix(path15.relative(wikiDir, absolutePath));
7381
+ const content = await fs12.readFile(absolutePath, "utf8");
7015
7382
  const parsed = matter5(content);
7016
- const stats = await fs11.stat(absolutePath);
7017
- const title = typeof parsed.data.title === "string" ? parsed.data.title : path14.basename(absolutePath, ".md");
7383
+ const stats = await fs12.stat(absolutePath);
7384
+ const title = typeof parsed.data.title === "string" ? parsed.data.title : path15.basename(absolutePath, ".md");
7018
7385
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
7019
7386
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
7020
7387
  const nodeIds = normalizeStringArray(parsed.data.node_ids);
@@ -7076,27 +7443,27 @@ function relatedOutputsForPage(targetPage, outputPages) {
7076
7443
  return outputPages.map((page) => ({ page, rank: relationRank(page, targetPage) })).filter((item) => item.rank > 0).sort((left, right) => right.rank - left.rank || left.page.title.localeCompare(right.page.title)).map((item) => item.page);
7077
7444
  }
7078
7445
  async function resolveUniqueOutputSlug(wikiDir, baseSlug) {
7079
- const outputsDir = path15.join(wikiDir, "outputs");
7446
+ const outputsDir = path16.join(wikiDir, "outputs");
7080
7447
  const root = baseSlug || "output";
7081
7448
  let candidate = root;
7082
7449
  let counter = 2;
7083
- while (await fileExists(path15.join(outputsDir, `${candidate}.md`))) {
7450
+ while (await fileExists(path16.join(outputsDir, `${candidate}.md`))) {
7084
7451
  candidate = `${root}-${counter}`;
7085
7452
  counter++;
7086
7453
  }
7087
7454
  return candidate;
7088
7455
  }
7089
7456
  async function loadSavedOutputPages(wikiDir) {
7090
- const outputsDir = path15.join(wikiDir, "outputs");
7091
- const entries = await fs12.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
7457
+ const outputsDir = path16.join(wikiDir, "outputs");
7458
+ const entries = await fs13.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
7092
7459
  const outputs = [];
7093
7460
  for (const entry of entries) {
7094
7461
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md") {
7095
7462
  continue;
7096
7463
  }
7097
- const relativePath = path15.posix.join("outputs", entry.name);
7098
- const absolutePath = path15.join(outputsDir, entry.name);
7099
- const content = await fs12.readFile(absolutePath, "utf8");
7464
+ const relativePath = path16.posix.join("outputs", entry.name);
7465
+ const absolutePath = path16.join(outputsDir, entry.name);
7466
+ const content = await fs13.readFile(absolutePath, "utf8");
7100
7467
  const parsed = matter6(content);
7101
7468
  const slug = entry.name.replace(/\.md$/, "");
7102
7469
  const title = typeof parsed.data.title === "string" ? parsed.data.title : slug;
@@ -7109,7 +7476,7 @@ async function loadSavedOutputPages(wikiDir) {
7109
7476
  const relatedSourceIds = normalizeStringArray(parsed.data.related_source_ids);
7110
7477
  const backlinks = normalizeStringArray(parsed.data.backlinks);
7111
7478
  const compiledFrom = normalizeStringArray(parsed.data.compiled_from);
7112
- const stats = await fs12.stat(absolutePath);
7479
+ const stats = await fs13.stat(absolutePath);
7113
7480
  const createdAt = typeof parsed.data.created_at === "string" ? parsed.data.created_at : stats.birthtimeMs > 0 ? stats.birthtime.toISOString() : stats.mtime.toISOString();
7114
7481
  const updatedAt = typeof parsed.data.updated_at === "string" ? parsed.data.updated_at : stats.mtime.toISOString();
7115
7482
  outputs.push({
@@ -7147,8 +7514,8 @@ async function loadSavedOutputPages(wikiDir) {
7147
7514
  }
7148
7515
 
7149
7516
  // src/search.ts
7150
- import fs13 from "fs/promises";
7151
- import path16 from "path";
7517
+ import fs14 from "fs/promises";
7518
+ import path17 from "path";
7152
7519
  import matter7 from "gray-matter";
7153
7520
  function getDatabaseSync() {
7154
7521
  const builtin = process.getBuiltinModule?.("node:sqlite");
@@ -7168,7 +7535,7 @@ function normalizeStatus(value) {
7168
7535
  return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : void 0;
7169
7536
  }
7170
7537
  async function rebuildSearchIndex(dbPath, pages, wikiDir) {
7171
- await ensureDir(path16.dirname(dbPath));
7538
+ await ensureDir(path17.dirname(dbPath));
7172
7539
  const DatabaseSync = getDatabaseSync();
7173
7540
  const db = new DatabaseSync(dbPath);
7174
7541
  db.exec("PRAGMA journal_mode = WAL;");
@@ -7198,8 +7565,8 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
7198
7565
  "INSERT INTO pages (id, path, title, body, kind, status, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
7199
7566
  );
7200
7567
  for (const page of pages) {
7201
- const absolutePath = path16.join(wikiDir, page.path);
7202
- const content = await fs13.readFile(absolutePath, "utf8");
7568
+ const absolutePath = path17.join(wikiDir, page.path);
7569
+ const content = await fs14.readFile(absolutePath, "utf8");
7203
7570
  const parsed = matter7(content);
7204
7571
  insertPage.run(
7205
7572
  page.id,
@@ -7302,7 +7669,7 @@ function outputFormatInstruction(format) {
7302
7669
  }
7303
7670
  }
7304
7671
  function outputAssetPath(slug, fileName) {
7305
- return toPosix(path17.join("outputs", "assets", slug, fileName));
7672
+ return toPosix(path18.join("outputs", "assets", slug, fileName));
7306
7673
  }
7307
7674
  function outputAssetId(slug, role) {
7308
7675
  return `output:${slug}:asset:${role}`;
@@ -7640,7 +8007,7 @@ async function generateOutputArtifacts(rootDir, input) {
7640
8007
  };
7641
8008
  }
7642
8009
  function normalizeProjectRoot(root) {
7643
- const normalized = toPosix(path17.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
8010
+ const normalized = toPosix(path18.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
7644
8011
  return normalized;
7645
8012
  }
7646
8013
  function projectEntries(config) {
@@ -7666,10 +8033,10 @@ function manifestPathForProject(rootDir, manifest) {
7666
8033
  if (!rawPath) {
7667
8034
  return toPosix(manifest.storedPath);
7668
8035
  }
7669
- if (!path17.isAbsolute(rawPath)) {
8036
+ if (!path18.isAbsolute(rawPath)) {
7670
8037
  return normalizeProjectRoot(rawPath);
7671
8038
  }
7672
- const relative = toPosix(path17.relative(rootDir, rawPath));
8039
+ const relative = toPosix(path18.relative(rootDir, rawPath));
7673
8040
  return relative.startsWith("..") ? toPosix(rawPath) : normalizeProjectRoot(relative);
7674
8041
  }
7675
8042
  function prefixMatches(value, prefix) {
@@ -7843,7 +8210,7 @@ function pageHashes(pages) {
7843
8210
  return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
7844
8211
  }
7845
8212
  async function buildManagedGraphPage(absolutePath, defaults, build) {
7846
- const existingContent = await fileExists(absolutePath) ? await fs14.readFile(absolutePath, "utf8") : null;
8213
+ const existingContent = await fileExists(absolutePath) ? await fs15.readFile(absolutePath, "utf8") : null;
7847
8214
  let existing = await loadExistingManagedPageState(absolutePath, {
7848
8215
  status: defaults.status ?? "active",
7849
8216
  managedBy: defaults.managedBy
@@ -7881,7 +8248,7 @@ async function buildManagedGraphPage(absolutePath, defaults, build) {
7881
8248
  return built;
7882
8249
  }
7883
8250
  async function buildManagedContent(absolutePath, defaults, build) {
7884
- const existingContent = await fileExists(absolutePath) ? await fs14.readFile(absolutePath, "utf8") : null;
8251
+ const existingContent = await fileExists(absolutePath) ? await fs15.readFile(absolutePath, "utf8") : null;
7885
8252
  let existing = await loadExistingManagedPageState(absolutePath, {
7886
8253
  status: defaults.status ?? "active",
7887
8254
  managedBy: defaults.managedBy
@@ -8325,7 +8692,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash) {
8325
8692
  const benchmark = await readJsonFile(paths.benchmarkPath);
8326
8693
  const communityRecords = [];
8327
8694
  for (const community of graph.communities ?? []) {
8328
- const absolutePath = path17.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
8695
+ const absolutePath = path18.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
8329
8696
  communityRecords.push(
8330
8697
  await buildManagedGraphPage(
8331
8698
  absolutePath,
@@ -8345,7 +8712,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash) {
8345
8712
  )
8346
8713
  );
8347
8714
  }
8348
- const reportAbsolutePath = path17.join(paths.wikiDir, "graph", "report.md");
8715
+ const reportAbsolutePath = path18.join(paths.wikiDir, "graph", "report.md");
8349
8716
  const reportRecord = await buildManagedGraphPage(
8350
8717
  reportAbsolutePath,
8351
8718
  {
@@ -8364,7 +8731,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash) {
8364
8731
  return [reportRecord, ...communityRecords];
8365
8732
  }
8366
8733
  async function writePage(wikiDir, relativePath, content, changedPages) {
8367
- const absolutePath = path17.resolve(wikiDir, relativePath);
8734
+ const absolutePath = path18.resolve(wikiDir, relativePath);
8368
8735
  const changed = await writeFileIfChanged(absolutePath, content);
8369
8736
  if (changed) {
8370
8737
  changedPages.push(relativePath);
@@ -8426,29 +8793,29 @@ async function requiredCompileArtifactsExist(paths) {
8426
8793
  paths.graphPath,
8427
8794
  paths.codeIndexPath,
8428
8795
  paths.searchDbPath,
8429
- path17.join(paths.wikiDir, "index.md"),
8430
- path17.join(paths.wikiDir, "sources", "index.md"),
8431
- path17.join(paths.wikiDir, "code", "index.md"),
8432
- path17.join(paths.wikiDir, "concepts", "index.md"),
8433
- path17.join(paths.wikiDir, "entities", "index.md"),
8434
- path17.join(paths.wikiDir, "outputs", "index.md"),
8435
- path17.join(paths.wikiDir, "projects", "index.md"),
8436
- path17.join(paths.wikiDir, "candidates", "index.md")
8796
+ path18.join(paths.wikiDir, "index.md"),
8797
+ path18.join(paths.wikiDir, "sources", "index.md"),
8798
+ path18.join(paths.wikiDir, "code", "index.md"),
8799
+ path18.join(paths.wikiDir, "concepts", "index.md"),
8800
+ path18.join(paths.wikiDir, "entities", "index.md"),
8801
+ path18.join(paths.wikiDir, "outputs", "index.md"),
8802
+ path18.join(paths.wikiDir, "projects", "index.md"),
8803
+ path18.join(paths.wikiDir, "candidates", "index.md")
8437
8804
  ];
8438
8805
  const checks = await Promise.all(requiredPaths.map((filePath) => fileExists(filePath)));
8439
8806
  return checks.every(Boolean);
8440
8807
  }
8441
8808
  async function loadAvailableCachedAnalyses(paths, manifests) {
8442
8809
  const analyses = await Promise.all(
8443
- manifests.map(async (manifest) => readJsonFile(path17.join(paths.analysesDir, `${manifest.sourceId}.json`)))
8810
+ manifests.map(async (manifest) => readJsonFile(path18.join(paths.analysesDir, `${manifest.sourceId}.json`)))
8444
8811
  );
8445
8812
  return analyses.filter((analysis) => Boolean(analysis));
8446
8813
  }
8447
8814
  function approvalManifestPath(paths, approvalId) {
8448
- return path17.join(paths.approvalsDir, approvalId, "manifest.json");
8815
+ return path18.join(paths.approvalsDir, approvalId, "manifest.json");
8449
8816
  }
8450
8817
  function approvalGraphPath(paths, approvalId) {
8451
- return path17.join(paths.approvalsDir, approvalId, "state", "graph.json");
8818
+ return path18.join(paths.approvalsDir, approvalId, "state", "graph.json");
8452
8819
  }
8453
8820
  async function readApprovalManifest(paths, approvalId) {
8454
8821
  const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
@@ -8458,7 +8825,7 @@ async function readApprovalManifest(paths, approvalId) {
8458
8825
  return manifest;
8459
8826
  }
8460
8827
  async function writeApprovalManifest(paths, manifest) {
8461
- await fs14.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
8828
+ await fs15.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
8462
8829
  `, "utf8");
8463
8830
  }
8464
8831
  async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
@@ -8473,7 +8840,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
8473
8840
  continue;
8474
8841
  }
8475
8842
  const previousPage = previousPagesById.get(nextPage.id);
8476
- const currentExists = await fileExists(path17.join(paths.wikiDir, file.relativePath));
8843
+ const currentExists = await fileExists(path18.join(paths.wikiDir, file.relativePath));
8477
8844
  if (previousPage && previousPage.path !== nextPage.path) {
8478
8845
  entries.push({
8479
8846
  pageId: nextPage.id,
@@ -8506,7 +8873,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
8506
8873
  const previousPage = previousPagesByPath.get(deletedPath);
8507
8874
  entries.push({
8508
8875
  pageId: previousPage?.id ?? `page:${slugify(deletedPath)}`,
8509
- title: previousPage?.title ?? path17.basename(deletedPath, ".md"),
8876
+ title: previousPage?.title ?? path18.basename(deletedPath, ".md"),
8510
8877
  kind: previousPage?.kind ?? "index",
8511
8878
  changeType: "delete",
8512
8879
  status: "pending",
@@ -8518,16 +8885,16 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
8518
8885
  }
8519
8886
  async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGraph, graph) {
8520
8887
  const approvalId = `compile-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
8521
- const approvalDir = path17.join(paths.approvalsDir, approvalId);
8888
+ const approvalDir = path18.join(paths.approvalsDir, approvalId);
8522
8889
  await ensureDir(approvalDir);
8523
- await ensureDir(path17.join(approvalDir, "wiki"));
8524
- await ensureDir(path17.join(approvalDir, "state"));
8890
+ await ensureDir(path18.join(approvalDir, "wiki"));
8891
+ await ensureDir(path18.join(approvalDir, "state"));
8525
8892
  for (const file of changedFiles) {
8526
- const targetPath = path17.join(approvalDir, "wiki", file.relativePath);
8527
- await ensureDir(path17.dirname(targetPath));
8528
- await fs14.writeFile(targetPath, file.content, "utf8");
8893
+ const targetPath = path18.join(approvalDir, "wiki", file.relativePath);
8894
+ await ensureDir(path18.dirname(targetPath));
8895
+ await fs15.writeFile(targetPath, file.content, "utf8");
8529
8896
  }
8530
- await fs14.writeFile(path17.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
8897
+ await fs15.writeFile(path18.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
8531
8898
  await writeApprovalManifest(paths, {
8532
8899
  approvalId,
8533
8900
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8587,7 +8954,7 @@ async function syncVaultArtifacts(rootDir, input) {
8587
8954
  confidence: 1
8588
8955
  });
8589
8956
  const sourceRecord = await buildManagedGraphPage(
8590
- path17.join(paths.wikiDir, preview.path),
8957
+ path18.join(paths.wikiDir, preview.path),
8591
8958
  {
8592
8959
  managedBy: "system",
8593
8960
  confidence: 1,
@@ -8632,7 +8999,7 @@ async function syncVaultArtifacts(rootDir, input) {
8632
8999
  );
8633
9000
  records.push(
8634
9001
  await buildManagedGraphPage(
8635
- path17.join(paths.wikiDir, modulePreview.path),
9002
+ path18.join(paths.wikiDir, modulePreview.path),
8636
9003
  {
8637
9004
  managedBy: "system",
8638
9005
  confidence: 1,
@@ -8665,8 +9032,8 @@ async function syncVaultArtifacts(rootDir, input) {
8665
9032
  const promoted = previousEntry?.status === "active" || promoteCandidates && shouldPromoteCandidate(previousEntry, sourceIds);
8666
9033
  const relativePath = promoted ? activeAggregatePath(itemKind, slug) : candidatePagePathFor(itemKind, slug);
8667
9034
  const fallbackPaths = [
8668
- path17.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
8669
- path17.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
9035
+ path18.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
9036
+ path18.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
8670
9037
  ];
8671
9038
  const confidence = nodeConfidence(aggregate.sourceAnalyses.length);
8672
9039
  const preview = emptyGraphPage({
@@ -8683,7 +9050,7 @@ async function syncVaultArtifacts(rootDir, input) {
8683
9050
  status: promoted ? "active" : "candidate"
8684
9051
  });
8685
9052
  const pageRecord = await buildManagedGraphPage(
8686
- path17.join(paths.wikiDir, relativePath),
9053
+ path18.join(paths.wikiDir, relativePath),
8687
9054
  {
8688
9055
  status: promoted ? "active" : "candidate",
8689
9056
  managedBy: "system",
@@ -8764,7 +9131,7 @@ async function syncVaultArtifacts(rootDir, input) {
8764
9131
  confidence: 1
8765
9132
  }),
8766
9133
  content: await buildManagedContent(
8767
- path17.join(paths.wikiDir, "projects", "index.md"),
9134
+ path18.join(paths.wikiDir, "projects", "index.md"),
8768
9135
  {
8769
9136
  managedBy: "system",
8770
9137
  compiledFrom: indexCompiledFrom(projectIndexRefs)
@@ -8788,7 +9155,7 @@ async function syncVaultArtifacts(rootDir, input) {
8788
9155
  records.push({
8789
9156
  page: projectIndexRef,
8790
9157
  content: await buildManagedContent(
8791
- path17.join(paths.wikiDir, projectIndexRef.path),
9158
+ path18.join(paths.wikiDir, projectIndexRef.path),
8792
9159
  {
8793
9160
  managedBy: "system",
8794
9161
  compiledFrom: indexCompiledFrom(Object.values(sections).flat())
@@ -8816,7 +9183,7 @@ async function syncVaultArtifacts(rootDir, input) {
8816
9183
  confidence: 1
8817
9184
  }),
8818
9185
  content: await buildManagedContent(
8819
- path17.join(paths.wikiDir, "index.md"),
9186
+ path18.join(paths.wikiDir, "index.md"),
8820
9187
  {
8821
9188
  managedBy: "system",
8822
9189
  compiledFrom: indexCompiledFrom(allPages)
@@ -8847,7 +9214,7 @@ async function syncVaultArtifacts(rootDir, input) {
8847
9214
  confidence: 1
8848
9215
  }),
8849
9216
  content: await buildManagedContent(
8850
- path17.join(paths.wikiDir, relativePath),
9217
+ path18.join(paths.wikiDir, relativePath),
8851
9218
  {
8852
9219
  managedBy: "system",
8853
9220
  compiledFrom: indexCompiledFrom(pages)
@@ -8858,12 +9225,12 @@ async function syncVaultArtifacts(rootDir, input) {
8858
9225
  }
8859
9226
  const nextPagePaths = new Set(records.map((record) => record.page.path));
8860
9227
  const obsoleteGraphPaths = (previousGraph?.pages ?? []).filter((page) => page.kind !== "output" && page.kind !== "insight").map((page) => page.path).filter((relativePath) => !nextPagePaths.has(relativePath));
8861
- const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path17.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
9228
+ const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path18.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
8862
9229
  const obsoletePaths = uniqueStrings2([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
8863
9230
  const changedFiles = [];
8864
9231
  for (const record of records) {
8865
- const absolutePath = path17.join(paths.wikiDir, record.page.path);
8866
- const current = await fileExists(absolutePath) ? await fs14.readFile(absolutePath, "utf8") : null;
9232
+ const absolutePath = path18.join(paths.wikiDir, record.page.path);
9233
+ const current = await fileExists(absolutePath) ? await fs15.readFile(absolutePath, "utf8") : null;
8867
9234
  if (current !== record.content) {
8868
9235
  changedPages.push(record.page.path);
8869
9236
  changedFiles.push({ relativePath: record.page.path, content: record.content });
@@ -8888,7 +9255,7 @@ async function syncVaultArtifacts(rootDir, input) {
8888
9255
  await writePage(paths.wikiDir, record.page.path, record.content, writeChanges);
8889
9256
  }
8890
9257
  for (const relativePath of obsoletePaths) {
8891
- await fs14.rm(path17.join(paths.wikiDir, relativePath), { force: true });
9258
+ await fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true });
8892
9259
  }
8893
9260
  await writeJsonFile(paths.graphPath, graph);
8894
9261
  await writeJsonFile(paths.codeIndexPath, input.codeIndex);
@@ -8959,17 +9326,17 @@ async function refreshIndexesAndSearch(rootDir, pages) {
8959
9326
  })
8960
9327
  );
8961
9328
  await Promise.all([
8962
- ensureDir(path17.join(paths.wikiDir, "sources")),
8963
- ensureDir(path17.join(paths.wikiDir, "code")),
8964
- ensureDir(path17.join(paths.wikiDir, "concepts")),
8965
- ensureDir(path17.join(paths.wikiDir, "entities")),
8966
- ensureDir(path17.join(paths.wikiDir, "outputs")),
8967
- ensureDir(path17.join(paths.wikiDir, "graph")),
8968
- ensureDir(path17.join(paths.wikiDir, "graph", "communities")),
8969
- ensureDir(path17.join(paths.wikiDir, "projects")),
8970
- ensureDir(path17.join(paths.wikiDir, "candidates"))
9329
+ ensureDir(path18.join(paths.wikiDir, "sources")),
9330
+ ensureDir(path18.join(paths.wikiDir, "code")),
9331
+ ensureDir(path18.join(paths.wikiDir, "concepts")),
9332
+ ensureDir(path18.join(paths.wikiDir, "entities")),
9333
+ ensureDir(path18.join(paths.wikiDir, "outputs")),
9334
+ ensureDir(path18.join(paths.wikiDir, "graph")),
9335
+ ensureDir(path18.join(paths.wikiDir, "graph", "communities")),
9336
+ ensureDir(path18.join(paths.wikiDir, "projects")),
9337
+ ensureDir(path18.join(paths.wikiDir, "candidates"))
8971
9338
  ]);
8972
- const projectsIndexPath = path17.join(paths.wikiDir, "projects", "index.md");
9339
+ const projectsIndexPath = path18.join(paths.wikiDir, "projects", "index.md");
8973
9340
  await writeFileIfChanged(
8974
9341
  projectsIndexPath,
8975
9342
  await buildManagedContent(
@@ -8990,7 +9357,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
8990
9357
  outputs: pages.filter((page) => page.kind === "output" && page.projectIds.includes(project.id)),
8991
9358
  candidates: pages.filter((page) => page.status === "candidate" && page.projectIds.includes(project.id))
8992
9359
  };
8993
- const absolutePath = path17.join(paths.wikiDir, "projects", project.id, "index.md");
9360
+ const absolutePath = path18.join(paths.wikiDir, "projects", project.id, "index.md");
8994
9361
  await writeFileIfChanged(
8995
9362
  absolutePath,
8996
9363
  await buildManagedContent(
@@ -9008,7 +9375,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
9008
9375
  )
9009
9376
  );
9010
9377
  }
9011
- const rootIndexPath = path17.join(paths.wikiDir, "index.md");
9378
+ const rootIndexPath = path18.join(paths.wikiDir, "index.md");
9012
9379
  await writeFileIfChanged(
9013
9380
  rootIndexPath,
9014
9381
  await buildManagedContent(
@@ -9029,7 +9396,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
9029
9396
  ["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
9030
9397
  ["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
9031
9398
  ]) {
9032
- const absolutePath = path17.join(paths.wikiDir, relativePath);
9399
+ const absolutePath = path18.join(paths.wikiDir, relativePath);
9033
9400
  await writeFileIfChanged(
9034
9401
  absolutePath,
9035
9402
  await buildManagedContent(
@@ -9043,20 +9410,20 @@ async function refreshIndexesAndSearch(rootDir, pages) {
9043
9410
  );
9044
9411
  }
9045
9412
  for (const record of graphOrientationRecords) {
9046
- await writeFileIfChanged(path17.join(paths.wikiDir, record.page.path), record.content);
9413
+ await writeFileIfChanged(path18.join(paths.wikiDir, record.page.path), record.content);
9047
9414
  }
9048
- const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path17.relative(paths.wikiDir, absolutePath)));
9415
+ const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path18.relative(paths.wikiDir, absolutePath)));
9049
9416
  const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
9050
9417
  "projects/index.md",
9051
9418
  ...configuredProjects.map((project) => `projects/${project.id}/index.md`)
9052
9419
  ]);
9053
9420
  await Promise.all(
9054
- existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs14.rm(path17.join(paths.wikiDir, relativePath), { force: true }))
9421
+ existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true }))
9055
9422
  );
9056
- const existingGraphPages = (await listFilesRecursive(path17.join(paths.wikiDir, "graph").replace(/\/$/, "")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path17.relative(paths.wikiDir, absolutePath)));
9423
+ const existingGraphPages = (await listFilesRecursive(path18.join(paths.wikiDir, "graph").replace(/\/$/, "")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path18.relative(paths.wikiDir, absolutePath)));
9057
9424
  const allowedGraphPages = /* @__PURE__ */ new Set(["graph/index.md", ...graphOrientationRecords.map((record) => record.page.path)]);
9058
9425
  await Promise.all(
9059
- existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs14.rm(path17.join(paths.wikiDir, relativePath), { force: true }))
9426
+ existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true }))
9060
9427
  );
9061
9428
  await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
9062
9429
  }
@@ -9076,7 +9443,7 @@ async function prepareOutputPageSave(rootDir, input) {
9076
9443
  confidence: 0.74
9077
9444
  }
9078
9445
  });
9079
- const absolutePath = path17.join(paths.wikiDir, output.page.path);
9446
+ const absolutePath = path18.join(paths.wikiDir, output.page.path);
9080
9447
  return {
9081
9448
  page: output.page,
9082
9449
  savedPath: absolutePath,
@@ -9088,15 +9455,15 @@ async function prepareOutputPageSave(rootDir, input) {
9088
9455
  async function persistOutputPage(rootDir, input) {
9089
9456
  const { paths } = await loadVaultConfig(rootDir);
9090
9457
  const prepared = await prepareOutputPageSave(rootDir, input);
9091
- await ensureDir(path17.dirname(prepared.savedPath));
9092
- await fs14.writeFile(prepared.savedPath, prepared.content, "utf8");
9458
+ await ensureDir(path18.dirname(prepared.savedPath));
9459
+ await fs15.writeFile(prepared.savedPath, prepared.content, "utf8");
9093
9460
  for (const assetFile of prepared.assetFiles) {
9094
- const assetPath = path17.join(paths.wikiDir, assetFile.relativePath);
9095
- await ensureDir(path17.dirname(assetPath));
9461
+ const assetPath = path18.join(paths.wikiDir, assetFile.relativePath);
9462
+ await ensureDir(path18.dirname(assetPath));
9096
9463
  if (typeof assetFile.content === "string") {
9097
- await fs14.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
9464
+ await fs15.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
9098
9465
  } else {
9099
- await fs14.writeFile(assetPath, assetFile.content);
9466
+ await fs15.writeFile(assetPath, assetFile.content);
9100
9467
  }
9101
9468
  }
9102
9469
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -9117,7 +9484,7 @@ async function prepareExploreHubSave(rootDir, input) {
9117
9484
  confidence: 0.76
9118
9485
  }
9119
9486
  });
9120
- const absolutePath = path17.join(paths.wikiDir, hub.page.path);
9487
+ const absolutePath = path18.join(paths.wikiDir, hub.page.path);
9121
9488
  return {
9122
9489
  page: hub.page,
9123
9490
  savedPath: absolutePath,
@@ -9129,15 +9496,15 @@ async function prepareExploreHubSave(rootDir, input) {
9129
9496
  async function persistExploreHub(rootDir, input) {
9130
9497
  const { paths } = await loadVaultConfig(rootDir);
9131
9498
  const prepared = await prepareExploreHubSave(rootDir, input);
9132
- await ensureDir(path17.dirname(prepared.savedPath));
9133
- await fs14.writeFile(prepared.savedPath, prepared.content, "utf8");
9499
+ await ensureDir(path18.dirname(prepared.savedPath));
9500
+ await fs15.writeFile(prepared.savedPath, prepared.content, "utf8");
9134
9501
  for (const assetFile of prepared.assetFiles) {
9135
- const assetPath = path17.join(paths.wikiDir, assetFile.relativePath);
9136
- await ensureDir(path17.dirname(assetPath));
9502
+ const assetPath = path18.join(paths.wikiDir, assetFile.relativePath);
9503
+ await ensureDir(path18.dirname(assetPath));
9137
9504
  if (typeof assetFile.content === "string") {
9138
- await fs14.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
9505
+ await fs15.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
9139
9506
  } else {
9140
- await fs14.writeFile(assetPath, assetFile.content);
9507
+ await fs15.writeFile(assetPath, assetFile.content);
9141
9508
  }
9142
9509
  }
9143
9510
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -9154,17 +9521,17 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
9154
9521
  }))
9155
9522
  ]);
9156
9523
  const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
9157
- const approvalDir = path17.join(paths.approvalsDir, approvalId);
9524
+ const approvalDir = path18.join(paths.approvalsDir, approvalId);
9158
9525
  await ensureDir(approvalDir);
9159
- await ensureDir(path17.join(approvalDir, "wiki"));
9160
- await ensureDir(path17.join(approvalDir, "state"));
9526
+ await ensureDir(path18.join(approvalDir, "wiki"));
9527
+ await ensureDir(path18.join(approvalDir, "state"));
9161
9528
  for (const file of changedFiles) {
9162
- const targetPath = path17.join(approvalDir, "wiki", file.relativePath);
9163
- await ensureDir(path17.dirname(targetPath));
9529
+ const targetPath = path18.join(approvalDir, "wiki", file.relativePath);
9530
+ await ensureDir(path18.dirname(targetPath));
9164
9531
  if ("binary" in file && file.binary) {
9165
- await fs14.writeFile(targetPath, Buffer.from(file.content, "base64"));
9532
+ await fs15.writeFile(targetPath, Buffer.from(file.content, "base64"));
9166
9533
  } else {
9167
- await fs14.writeFile(targetPath, file.content, "utf8");
9534
+ await fs15.writeFile(targetPath, file.content, "utf8");
9168
9535
  }
9169
9536
  }
9170
9537
  const nextPages = sortGraphPages([
@@ -9178,7 +9545,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
9178
9545
  sources: previousGraph?.sources ?? [],
9179
9546
  pages: nextPages
9180
9547
  };
9181
- await fs14.writeFile(path17.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
9548
+ await fs15.writeFile(path18.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
9182
9549
  await writeApprovalManifest(paths, {
9183
9550
  approvalId,
9184
9551
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -9207,9 +9574,9 @@ async function executeQuery(rootDir, question, format) {
9207
9574
  const searchResults = searchPages(paths.searchDbPath, question, 5);
9208
9575
  const excerpts = await Promise.all(
9209
9576
  searchResults.map(async (result) => {
9210
- const absolutePath = path17.join(paths.wikiDir, result.path);
9577
+ const absolutePath = path18.join(paths.wikiDir, result.path);
9211
9578
  try {
9212
- const content = await fs14.readFile(absolutePath, "utf8");
9579
+ const content = await fs15.readFile(absolutePath, "utf8");
9213
9580
  const parsed = matter8(content);
9214
9581
  return `# ${result.title}
9215
9582
  ${truncate(normalizeWhitespace(parsed.content), 1200)}`;
@@ -9307,8 +9674,8 @@ async function generateFollowUpQuestions(rootDir, question, answer) {
9307
9674
  Current answer:
9308
9675
  ${answer}`
9309
9676
  },
9310
- z6.object({
9311
- questions: z6.array(z6.string().min(1)).max(5)
9677
+ z7.object({
9678
+ questions: z7.array(z7.string().min(1)).max(5)
9312
9679
  })
9313
9680
  );
9314
9681
  return uniqueBy(response.questions, (item) => item).filter((item) => item !== question);
@@ -9391,7 +9758,7 @@ function sortGraphPages(pages) {
9391
9758
  async function listApprovals(rootDir) {
9392
9759
  const { paths } = await loadVaultConfig(rootDir);
9393
9760
  const manifests = await Promise.all(
9394
- (await fs14.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
9761
+ (await fs15.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
9395
9762
  try {
9396
9763
  return await readApprovalManifest(paths, entry.name);
9397
9764
  } catch {
@@ -9407,8 +9774,8 @@ async function readApproval(rootDir, approvalId) {
9407
9774
  const details = await Promise.all(
9408
9775
  manifest.entries.map(async (entry) => {
9409
9776
  const currentPath = entry.previousPath ?? entry.nextPath;
9410
- const currentContent = currentPath ? await fs14.readFile(path17.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
9411
- const stagedContent = entry.nextPath ? await fs14.readFile(path17.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
9777
+ const currentContent = currentPath ? await fs15.readFile(path18.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
9778
+ const stagedContent = entry.nextPath ? await fs15.readFile(path18.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
9412
9779
  return {
9413
9780
  ...entry,
9414
9781
  currentContent,
@@ -9436,26 +9803,26 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
9436
9803
  if (!entry.nextPath) {
9437
9804
  throw new Error(`Approval entry ${entry.pageId} is missing a staged path.`);
9438
9805
  }
9439
- const stagedAbsolutePath = path17.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
9440
- const stagedContent = await fs14.readFile(stagedAbsolutePath, "utf8");
9441
- const targetAbsolutePath = path17.join(paths.wikiDir, entry.nextPath);
9442
- await ensureDir(path17.dirname(targetAbsolutePath));
9443
- await fs14.writeFile(targetAbsolutePath, stagedContent, "utf8");
9806
+ const stagedAbsolutePath = path18.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
9807
+ const stagedContent = await fs15.readFile(stagedAbsolutePath, "utf8");
9808
+ const targetAbsolutePath = path18.join(paths.wikiDir, entry.nextPath);
9809
+ await ensureDir(path18.dirname(targetAbsolutePath));
9810
+ await fs15.writeFile(targetAbsolutePath, stagedContent, "utf8");
9444
9811
  if (entry.changeType === "promote" && entry.previousPath) {
9445
- await fs14.rm(path17.join(paths.wikiDir, entry.previousPath), { force: true });
9812
+ await fs15.rm(path18.join(paths.wikiDir, entry.previousPath), { force: true });
9446
9813
  }
9447
9814
  const nextPage = bundleGraph?.pages.find((page) => page.id === entry.pageId && page.path === entry.nextPath) ?? parseStoredPage(entry.nextPath, stagedContent);
9448
9815
  if (nextPage.kind === "output" && nextPage.outputAssets?.length) {
9449
- const outputAssetDir = path17.join(paths.wikiDir, "outputs", "assets", path17.basename(nextPage.path, ".md"));
9450
- await fs14.rm(outputAssetDir, { recursive: true, force: true });
9816
+ const outputAssetDir = path18.join(paths.wikiDir, "outputs", "assets", path18.basename(nextPage.path, ".md"));
9817
+ await fs15.rm(outputAssetDir, { recursive: true, force: true });
9451
9818
  for (const asset of nextPage.outputAssets) {
9452
- const stagedAssetPath = path17.join(paths.approvalsDir, approvalId, "wiki", asset.path);
9819
+ const stagedAssetPath = path18.join(paths.approvalsDir, approvalId, "wiki", asset.path);
9453
9820
  if (!await fileExists(stagedAssetPath)) {
9454
9821
  continue;
9455
9822
  }
9456
- const targetAssetPath = path17.join(paths.wikiDir, asset.path);
9457
- await ensureDir(path17.dirname(targetAssetPath));
9458
- await fs14.copyFile(stagedAssetPath, targetAssetPath);
9823
+ const targetAssetPath = path18.join(paths.wikiDir, asset.path);
9824
+ await ensureDir(path18.dirname(targetAssetPath));
9825
+ await fs15.copyFile(stagedAssetPath, targetAssetPath);
9459
9826
  }
9460
9827
  }
9461
9828
  nextPages = nextPages.filter(
@@ -9466,10 +9833,10 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
9466
9833
  } else {
9467
9834
  const deletedPage = nextPages.find((page) => page.id === entry.pageId || page.path === entry.previousPath) ?? bundleGraph?.pages.find((page) => page.id === entry.pageId || page.path === entry.previousPath) ?? null;
9468
9835
  if (entry.previousPath) {
9469
- await fs14.rm(path17.join(paths.wikiDir, entry.previousPath), { force: true });
9836
+ await fs15.rm(path18.join(paths.wikiDir, entry.previousPath), { force: true });
9470
9837
  }
9471
9838
  if (deletedPage?.kind === "output") {
9472
- await fs14.rm(path17.join(paths.wikiDir, "outputs", "assets", path17.basename(deletedPage.path, ".md")), {
9839
+ await fs15.rm(path18.join(paths.wikiDir, "outputs", "assets", path18.basename(deletedPage.path, ".md")), {
9473
9840
  recursive: true,
9474
9841
  force: true
9475
9842
  });
@@ -9559,7 +9926,7 @@ async function promoteCandidate(rootDir, target) {
9559
9926
  const { paths } = await loadVaultConfig(rootDir);
9560
9927
  const graph = await readJsonFile(paths.graphPath);
9561
9928
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
9562
- const raw = await fs14.readFile(path17.join(paths.wikiDir, candidate.path), "utf8");
9929
+ const raw = await fs15.readFile(path18.join(paths.wikiDir, candidate.path), "utf8");
9563
9930
  const parsed = matter8(raw);
9564
9931
  const nextUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
9565
9932
  const nextContent = matter8.stringify(parsed.content, {
@@ -9571,10 +9938,10 @@ async function promoteCandidate(rootDir, target) {
9571
9938
  )
9572
9939
  });
9573
9940
  const nextPath = candidateActivePath(candidate);
9574
- const nextAbsolutePath = path17.join(paths.wikiDir, nextPath);
9575
- await ensureDir(path17.dirname(nextAbsolutePath));
9576
- await fs14.writeFile(nextAbsolutePath, nextContent, "utf8");
9577
- await fs14.rm(path17.join(paths.wikiDir, candidate.path), { force: true });
9941
+ const nextAbsolutePath = path18.join(paths.wikiDir, nextPath);
9942
+ await ensureDir(path18.dirname(nextAbsolutePath));
9943
+ await fs15.writeFile(nextAbsolutePath, nextContent, "utf8");
9944
+ await fs15.rm(path18.join(paths.wikiDir, candidate.path), { force: true });
9578
9945
  const nextPage = parseStoredPage(nextPath, nextContent, { createdAt: candidate.createdAt, updatedAt: nextUpdatedAt });
9579
9946
  const nextPages = sortGraphPages(
9580
9947
  (graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path).concat(nextPage)
@@ -9618,7 +9985,7 @@ async function archiveCandidate(rootDir, target) {
9618
9985
  const { paths } = await loadVaultConfig(rootDir);
9619
9986
  const graph = await readJsonFile(paths.graphPath);
9620
9987
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
9621
- await fs14.rm(path17.join(paths.wikiDir, candidate.path), { force: true });
9988
+ await fs15.rm(path18.join(paths.wikiDir, candidate.path), { force: true });
9622
9989
  const nextPages = sortGraphPages((graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path));
9623
9990
  const nextGraph = {
9624
9991
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -9656,18 +10023,18 @@ async function archiveCandidate(rootDir, target) {
9656
10023
  }
9657
10024
  async function ensureObsidianWorkspace(rootDir) {
9658
10025
  const { config } = await loadVaultConfig(rootDir);
9659
- const obsidianDir = path17.join(rootDir, ".obsidian");
10026
+ const obsidianDir = path18.join(rootDir, ".obsidian");
9660
10027
  const projectIds = projectEntries(config).map((project) => project.id);
9661
10028
  await ensureDir(obsidianDir);
9662
10029
  await Promise.all([
9663
- writeJsonFile(path17.join(obsidianDir, "app.json"), {
10030
+ writeJsonFile(path18.join(obsidianDir, "app.json"), {
9664
10031
  alwaysUpdateLinks: true,
9665
10032
  newFileLocation: "folder",
9666
10033
  newFileFolderPath: "wiki/insights",
9667
10034
  useMarkdownLinks: false,
9668
10035
  attachmentFolderPath: "raw/assets"
9669
10036
  }),
9670
- writeJsonFile(path17.join(obsidianDir, "core-plugins.json"), [
10037
+ writeJsonFile(path18.join(obsidianDir, "core-plugins.json"), [
9671
10038
  "file-explorer",
9672
10039
  "global-search",
9673
10040
  "switcher",
@@ -9677,7 +10044,7 @@ async function ensureObsidianWorkspace(rootDir) {
9677
10044
  "tag-pane",
9678
10045
  "page-preview"
9679
10046
  ]),
9680
- writeJsonFile(path17.join(obsidianDir, "graph.json"), {
10047
+ writeJsonFile(path18.join(obsidianDir, "graph.json"), {
9681
10048
  "collapse-filter": false,
9682
10049
  search: "",
9683
10050
  showTags: true,
@@ -9689,7 +10056,7 @@ async function ensureObsidianWorkspace(rootDir) {
9689
10056
  })),
9690
10057
  localJumps: false
9691
10058
  }),
9692
- writeJsonFile(path17.join(obsidianDir, "workspace.json"), {
10059
+ writeJsonFile(path18.join(obsidianDir, "workspace.json"), {
9693
10060
  active: "root",
9694
10061
  lastOpenFiles: ["wiki/index.md", "wiki/projects/index.md", "wiki/candidates/index.md", "wiki/insights/index.md"],
9695
10062
  left: {
@@ -9704,7 +10071,7 @@ async function ensureObsidianWorkspace(rootDir) {
9704
10071
  async function initVault(rootDir, options = {}) {
9705
10072
  const { paths } = await initWorkspace(rootDir);
9706
10073
  await installConfiguredAgents(rootDir);
9707
- const insightsIndexPath = path17.join(paths.wikiDir, "insights", "index.md");
10074
+ const insightsIndexPath = path18.join(paths.wikiDir, "insights", "index.md");
9708
10075
  const now = (/* @__PURE__ */ new Date()).toISOString();
9709
10076
  await writeFileIfChanged(
9710
10077
  insightsIndexPath,
@@ -9740,7 +10107,7 @@ async function initVault(rootDir, options = {}) {
9740
10107
  )
9741
10108
  );
9742
10109
  await writeFileIfChanged(
9743
- path17.join(paths.wikiDir, "projects", "index.md"),
10110
+ path18.join(paths.wikiDir, "projects", "index.md"),
9744
10111
  matter8.stringify(["# Projects", "", "- Run `swarmvault compile` to build project rollups.", ""].join("\n"), {
9745
10112
  page_id: "projects:index",
9746
10113
  kind: "index",
@@ -9762,7 +10129,7 @@ async function initVault(rootDir, options = {}) {
9762
10129
  })
9763
10130
  );
9764
10131
  await writeFileIfChanged(
9765
- path17.join(paths.wikiDir, "candidates", "index.md"),
10132
+ path18.join(paths.wikiDir, "candidates", "index.md"),
9766
10133
  matter8.stringify(["# Candidates", "", "- Run `swarmvault compile` to stage candidate pages.", ""].join("\n"), {
9767
10134
  page_id: "candidates:index",
9768
10135
  kind: "index",
@@ -9879,7 +10246,7 @@ async function compileVault(rootDir, options = {}) {
9879
10246
  ),
9880
10247
  Promise.all(
9881
10248
  clean.map(async (manifest) => {
9882
- const cached = await readJsonFile(path17.join(paths.analysesDir, `${manifest.sourceId}.json`));
10249
+ const cached = await readJsonFile(path18.join(paths.analysesDir, `${manifest.sourceId}.json`));
9883
10250
  if (cached) {
9884
10251
  return cached;
9885
10252
  }
@@ -9903,22 +10270,22 @@ async function compileVault(rootDir, options = {}) {
9903
10270
  }
9904
10271
  const enriched = enrichResolvedCodeImports(manifest, analysis, codeIndex);
9905
10272
  if (analysisSignature(enriched) !== analysisSignature(analysis)) {
9906
- await writeJsonFile(path17.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
10273
+ await writeJsonFile(path18.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
9907
10274
  }
9908
10275
  return enriched;
9909
10276
  })
9910
10277
  );
9911
10278
  await Promise.all([
9912
- ensureDir(path17.join(paths.wikiDir, "sources")),
9913
- ensureDir(path17.join(paths.wikiDir, "code")),
9914
- ensureDir(path17.join(paths.wikiDir, "concepts")),
9915
- ensureDir(path17.join(paths.wikiDir, "entities")),
9916
- ensureDir(path17.join(paths.wikiDir, "outputs")),
9917
- ensureDir(path17.join(paths.wikiDir, "projects")),
9918
- ensureDir(path17.join(paths.wikiDir, "insights")),
9919
- ensureDir(path17.join(paths.wikiDir, "candidates")),
9920
- ensureDir(path17.join(paths.wikiDir, "candidates", "concepts")),
9921
- ensureDir(path17.join(paths.wikiDir, "candidates", "entities"))
10279
+ ensureDir(path18.join(paths.wikiDir, "sources")),
10280
+ ensureDir(path18.join(paths.wikiDir, "code")),
10281
+ ensureDir(path18.join(paths.wikiDir, "concepts")),
10282
+ ensureDir(path18.join(paths.wikiDir, "entities")),
10283
+ ensureDir(path18.join(paths.wikiDir, "outputs")),
10284
+ ensureDir(path18.join(paths.wikiDir, "projects")),
10285
+ ensureDir(path18.join(paths.wikiDir, "insights")),
10286
+ ensureDir(path18.join(paths.wikiDir, "candidates")),
10287
+ ensureDir(path18.join(paths.wikiDir, "candidates", "concepts")),
10288
+ ensureDir(path18.join(paths.wikiDir, "candidates", "entities"))
9922
10289
  ]);
9923
10290
  const sync = await syncVaultArtifacts(rootDir, {
9924
10291
  schemas,
@@ -10060,7 +10427,7 @@ async function queryVault(rootDir, options) {
10060
10427
  assetFiles: staged.assetFiles
10061
10428
  }
10062
10429
  ]);
10063
- stagedPath = path17.join(approval.approvalDir, "wiki", staged.page.path);
10430
+ stagedPath = path18.join(approval.approvalDir, "wiki", staged.page.path);
10064
10431
  savedPageId = staged.page.id;
10065
10432
  approvalId = approval.approvalId;
10066
10433
  approvalDir = approval.approvalDir;
@@ -10316,9 +10683,9 @@ ${orchestrationNotes.join("\n")}
10316
10683
  approvalId = approval.approvalId;
10317
10684
  approvalDir = approval.approvalDir;
10318
10685
  stepResults.forEach((result, index) => {
10319
- result.stagedPath = path17.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
10686
+ result.stagedPath = path18.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
10320
10687
  });
10321
- stagedHubPath = path17.join(approval.approvalDir, "wiki", hubPage.path);
10688
+ stagedHubPath = path18.join(approval.approvalDir, "wiki", hubPage.path);
10322
10689
  } else {
10323
10690
  await refreshVaultAfterOutputSave(rootDir);
10324
10691
  }
@@ -10398,11 +10765,11 @@ async function benchmarkVault(rootDir, options = {}) {
10398
10765
  }
10399
10766
  }
10400
10767
  for (const page of graph.pages) {
10401
- const absolutePath = path17.join(paths.wikiDir, page.path);
10768
+ const absolutePath = path18.join(paths.wikiDir, page.path);
10402
10769
  if (!await fileExists(absolutePath)) {
10403
10770
  continue;
10404
10771
  }
10405
- const parsed = matter8(await fs14.readFile(absolutePath, "utf8"));
10772
+ const parsed = matter8(await fs15.readFile(absolutePath, "utf8"));
10406
10773
  pageContentsById.set(page.id, parsed.content);
10407
10774
  }
10408
10775
  const questions = (options.questions ?? []).map((question) => normalizeWhitespace(question)).filter(Boolean);
@@ -10448,15 +10815,15 @@ async function listPages(rootDir) {
10448
10815
  }
10449
10816
  async function readPage(rootDir, relativePath) {
10450
10817
  const { paths } = await loadVaultConfig(rootDir);
10451
- const absolutePath = path17.resolve(paths.wikiDir, relativePath);
10818
+ const absolutePath = path18.resolve(paths.wikiDir, relativePath);
10452
10819
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
10453
10820
  return null;
10454
10821
  }
10455
- const raw = await fs14.readFile(absolutePath, "utf8");
10822
+ const raw = await fs15.readFile(absolutePath, "utf8");
10456
10823
  const parsed = matter8(raw);
10457
10824
  return {
10458
10825
  path: relativePath,
10459
- title: typeof parsed.data.title === "string" ? parsed.data.title : path17.basename(relativePath, path17.extname(relativePath)),
10826
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path18.basename(relativePath, path18.extname(relativePath)),
10460
10827
  frontmatter: parsed.data,
10461
10828
  content: parsed.content
10462
10829
  };
@@ -10492,7 +10859,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
10492
10859
  severity: "warning",
10493
10860
  code: "stale_page",
10494
10861
  message: `Page ${page.title} is stale because the vault schema changed.`,
10495
- pagePath: path17.join(paths.wikiDir, page.path),
10862
+ pagePath: path18.join(paths.wikiDir, page.path),
10496
10863
  relatedPageIds: [page.id]
10497
10864
  });
10498
10865
  }
@@ -10503,7 +10870,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
10503
10870
  severity: "warning",
10504
10871
  code: "stale_page",
10505
10872
  message: `Page ${page.title} is stale because source ${sourceId} changed.`,
10506
- pagePath: path17.join(paths.wikiDir, page.path),
10873
+ pagePath: path18.join(paths.wikiDir, page.path),
10507
10874
  relatedSourceIds: [sourceId],
10508
10875
  relatedPageIds: [page.id]
10509
10876
  });
@@ -10514,13 +10881,13 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
10514
10881
  severity: "info",
10515
10882
  code: "orphan_page",
10516
10883
  message: `Page ${page.title} has no backlinks.`,
10517
- pagePath: path17.join(paths.wikiDir, page.path),
10884
+ pagePath: path18.join(paths.wikiDir, page.path),
10518
10885
  relatedPageIds: [page.id]
10519
10886
  });
10520
10887
  }
10521
- const absolutePath = path17.join(paths.wikiDir, page.path);
10888
+ const absolutePath = path18.join(paths.wikiDir, page.path);
10522
10889
  if (await fileExists(absolutePath)) {
10523
- const content = await fs14.readFile(absolutePath, "utf8");
10890
+ const content = await fs15.readFile(absolutePath, "utf8");
10524
10891
  if (content.includes("## Claims")) {
10525
10892
  const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
10526
10893
  if (uncited.length) {
@@ -10600,7 +10967,7 @@ async function bootstrapDemo(rootDir, input) {
10600
10967
  }
10601
10968
 
10602
10969
  // src/mcp.ts
10603
- var SERVER_VERSION = "0.1.21";
10970
+ var SERVER_VERSION = "0.1.22";
10604
10971
  async function createMcpServer(rootDir) {
10605
10972
  const server = new McpServer({
10606
10973
  name: "swarmvault",
@@ -10622,8 +10989,8 @@ async function createMcpServer(rootDir) {
10622
10989
  {
10623
10990
  description: "Search compiled wiki pages using the local full-text index.",
10624
10991
  inputSchema: {
10625
- query: z7.string().min(1).describe("Search query"),
10626
- limit: z7.number().int().min(1).max(25).optional().describe("Maximum number of results")
10992
+ query: z8.string().min(1).describe("Search query"),
10993
+ limit: z8.number().int().min(1).max(25).optional().describe("Maximum number of results")
10627
10994
  }
10628
10995
  },
10629
10996
  async ({ query, limit }) => {
@@ -10636,7 +11003,7 @@ async function createMcpServer(rootDir) {
10636
11003
  {
10637
11004
  description: "Read a generated wiki page by its path relative to wiki/.",
10638
11005
  inputSchema: {
10639
- path: z7.string().min(1).describe("Path relative to wiki/, for example sources/example.md")
11006
+ path: z8.string().min(1).describe("Path relative to wiki/, for example sources/example.md")
10640
11007
  }
10641
11008
  },
10642
11009
  async ({ path: relativePath }) => {
@@ -10652,7 +11019,7 @@ async function createMcpServer(rootDir) {
10652
11019
  {
10653
11020
  description: "List source manifests in the current workspace.",
10654
11021
  inputSchema: {
10655
- limit: z7.number().int().min(1).max(100).optional().describe("Maximum number of manifests to return")
11022
+ limit: z8.number().int().min(1).max(100).optional().describe("Maximum number of manifests to return")
10656
11023
  }
10657
11024
  },
10658
11025
  async ({ limit }) => {
@@ -10665,9 +11032,9 @@ async function createMcpServer(rootDir) {
10665
11032
  {
10666
11033
  description: "Traverse the local graph from search seeds without calling a model provider.",
10667
11034
  inputSchema: {
10668
- question: z7.string().min(1).describe("Question or graph search seed"),
10669
- traversal: z7.enum(["bfs", "dfs"]).optional().describe("Traversal strategy"),
10670
- budget: z7.number().int().min(3).max(50).optional().describe("Maximum nodes to summarize")
11035
+ question: z8.string().min(1).describe("Question or graph search seed"),
11036
+ traversal: z8.enum(["bfs", "dfs"]).optional().describe("Traversal strategy"),
11037
+ budget: z8.number().int().min(3).max(50).optional().describe("Maximum nodes to summarize")
10671
11038
  }
10672
11039
  },
10673
11040
  async ({ question, traversal, budget }) => {
@@ -10683,7 +11050,7 @@ async function createMcpServer(rootDir) {
10683
11050
  {
10684
11051
  description: "Explain a graph node, its page, community, and neighbors.",
10685
11052
  inputSchema: {
10686
- target: z7.string().min(1).describe("Node or page label/id")
11053
+ target: z8.string().min(1).describe("Node or page label/id")
10687
11054
  }
10688
11055
  },
10689
11056
  async ({ target }) => {
@@ -10695,7 +11062,7 @@ async function createMcpServer(rootDir) {
10695
11062
  {
10696
11063
  description: "Return the neighbors of a graph node or page target.",
10697
11064
  inputSchema: {
10698
- target: z7.string().min(1).describe("Node or page label/id")
11065
+ target: z8.string().min(1).describe("Node or page label/id")
10699
11066
  }
10700
11067
  },
10701
11068
  async ({ target }) => {
@@ -10708,8 +11075,8 @@ async function createMcpServer(rootDir) {
10708
11075
  {
10709
11076
  description: "Find the shortest graph path between two targets.",
10710
11077
  inputSchema: {
10711
- from: z7.string().min(1).describe("Start node/page label or id"),
10712
- to: z7.string().min(1).describe("End node/page label or id")
11078
+ from: z8.string().min(1).describe("Start node/page label or id"),
11079
+ to: z8.string().min(1).describe("End node/page label or id")
10713
11080
  }
10714
11081
  },
10715
11082
  async ({ from, to }) => {
@@ -10721,7 +11088,7 @@ async function createMcpServer(rootDir) {
10721
11088
  {
10722
11089
  description: "List the highest-connectivity graph nodes.",
10723
11090
  inputSchema: {
10724
- limit: z7.number().int().min(1).max(25).optional().describe("Maximum nodes to return")
11091
+ limit: z8.number().int().min(1).max(25).optional().describe("Maximum nodes to return")
10725
11092
  }
10726
11093
  },
10727
11094
  async ({ limit }) => {
@@ -10733,9 +11100,9 @@ async function createMcpServer(rootDir) {
10733
11100
  {
10734
11101
  description: "Ask a question against the compiled vault and optionally save the answer.",
10735
11102
  inputSchema: {
10736
- question: z7.string().min(1).describe("Question to ask the vault"),
10737
- save: z7.boolean().optional().describe("Persist the answer to wiki/outputs"),
10738
- format: z7.enum(["markdown", "report", "slides", "chart", "image"]).optional().describe("Output format")
11103
+ question: z8.string().min(1).describe("Question to ask the vault"),
11104
+ save: z8.boolean().optional().describe("Persist the answer to wiki/outputs"),
11105
+ format: z8.enum(["markdown", "report", "slides", "chart", "image"]).optional().describe("Output format")
10739
11106
  }
10740
11107
  },
10741
11108
  async ({ question, save, format }) => {
@@ -10752,7 +11119,7 @@ async function createMcpServer(rootDir) {
10752
11119
  {
10753
11120
  description: "Ingest a local file path or URL into the SwarmVault workspace.",
10754
11121
  inputSchema: {
10755
- input: z7.string().min(1).describe("Local path or URL to ingest")
11122
+ input: z8.string().min(1).describe("Local path or URL to ingest")
10756
11123
  }
10757
11124
  },
10758
11125
  async ({ input }) => {
@@ -10765,7 +11132,7 @@ async function createMcpServer(rootDir) {
10765
11132
  {
10766
11133
  description: "Compile source manifests into wiki pages, graph data, and search index.",
10767
11134
  inputSchema: {
10768
- approve: z7.boolean().optional().describe("Stage a review bundle without applying active page changes")
11135
+ approve: z8.boolean().optional().describe("Stage a review bundle without applying active page changes")
10769
11136
  }
10770
11137
  },
10771
11138
  async ({ approve }) => {
@@ -10849,7 +11216,7 @@ async function createMcpServer(rootDir) {
10849
11216
  },
10850
11217
  async () => {
10851
11218
  const { paths } = await loadVaultConfig(rootDir);
10852
- const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path18.relative(paths.sessionsDir, filePath))).sort();
11219
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path19.relative(paths.sessionsDir, filePath))).sort();
10853
11220
  return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
10854
11221
  }
10855
11222
  );
@@ -10882,8 +11249,8 @@ async function createMcpServer(rootDir) {
10882
11249
  return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
10883
11250
  }
10884
11251
  const { paths } = await loadVaultConfig(rootDir);
10885
- const absolutePath = path18.resolve(paths.wikiDir, relativePath);
10886
- return asTextResource(`swarmvault://pages/${encodedPath}`, await fs15.readFile(absolutePath, "utf8"));
11252
+ const absolutePath = path19.resolve(paths.wikiDir, relativePath);
11253
+ return asTextResource(`swarmvault://pages/${encodedPath}`, await fs16.readFile(absolutePath, "utf8"));
10887
11254
  }
10888
11255
  );
10889
11256
  server.registerResource(
@@ -10891,11 +11258,11 @@ async function createMcpServer(rootDir) {
10891
11258
  new ResourceTemplate("swarmvault://sessions/{path}", {
10892
11259
  list: async () => {
10893
11260
  const { paths } = await loadVaultConfig(rootDir);
10894
- const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path18.relative(paths.sessionsDir, filePath))).sort();
11261
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path19.relative(paths.sessionsDir, filePath))).sort();
10895
11262
  return {
10896
11263
  resources: files.map((relativePath) => ({
10897
11264
  uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
10898
- name: path18.basename(relativePath, ".md"),
11265
+ name: path19.basename(relativePath, ".md"),
10899
11266
  title: relativePath,
10900
11267
  description: "SwarmVault session artifact",
10901
11268
  mimeType: "text/markdown"
@@ -10912,11 +11279,11 @@ async function createMcpServer(rootDir) {
10912
11279
  const { paths } = await loadVaultConfig(rootDir);
10913
11280
  const encodedPath = typeof variables.path === "string" ? variables.path : "";
10914
11281
  const relativePath = decodeURIComponent(encodedPath);
10915
- const absolutePath = path18.resolve(paths.sessionsDir, relativePath);
11282
+ const absolutePath = path19.resolve(paths.sessionsDir, relativePath);
10916
11283
  if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
10917
11284
  return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
10918
11285
  }
10919
- return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs15.readFile(absolutePath, "utf8"));
11286
+ return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs16.readFile(absolutePath, "utf8"));
10920
11287
  }
10921
11288
  );
10922
11289
  return server;
@@ -10964,13 +11331,13 @@ function asTextResource(uri, text) {
10964
11331
  }
10965
11332
 
10966
11333
  // src/schedule.ts
10967
- import fs16 from "fs/promises";
10968
- import path19 from "path";
11334
+ import fs17 from "fs/promises";
11335
+ import path20 from "path";
10969
11336
  function scheduleStatePath(schedulesDir, jobId) {
10970
- return path19.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
11337
+ return path20.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
10971
11338
  }
10972
11339
  function scheduleLockPath(schedulesDir, jobId) {
10973
- return path19.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
11340
+ return path20.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
10974
11341
  }
10975
11342
  function parseEveryDuration(value) {
10976
11343
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -11073,13 +11440,13 @@ async function acquireJobLease(rootDir, jobId) {
11073
11440
  const { paths } = await loadVaultConfig(rootDir);
11074
11441
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
11075
11442
  await ensureDir(paths.schedulesDir);
11076
- const handle = await fs16.open(leasePath, "wx");
11443
+ const handle = await fs17.open(leasePath, "wx");
11077
11444
  await handle.writeFile(`${process.pid}
11078
11445
  ${(/* @__PURE__ */ new Date()).toISOString()}
11079
11446
  `);
11080
11447
  await handle.close();
11081
11448
  return async () => {
11082
- await fs16.rm(leasePath, { force: true });
11449
+ await fs17.rm(leasePath, { force: true });
11083
11450
  };
11084
11451
  }
11085
11452
  async function listSchedules(rootDir) {
@@ -11227,15 +11594,15 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
11227
11594
 
11228
11595
  // src/viewer.ts
11229
11596
  import { execFile } from "child_process";
11230
- import fs17 from "fs/promises";
11597
+ import fs18 from "fs/promises";
11231
11598
  import http from "http";
11232
- import path21 from "path";
11599
+ import path22 from "path";
11233
11600
  import { promisify } from "util";
11234
11601
  import matter9 from "gray-matter";
11235
11602
  import mime2 from "mime-types";
11236
11603
 
11237
11604
  // src/watch.ts
11238
- import path20 from "path";
11605
+ import path21 from "path";
11239
11606
  import process2 from "process";
11240
11607
  import chokidar from "chokidar";
11241
11608
  var MAX_BACKOFF_MS = 3e4;
@@ -11243,15 +11610,15 @@ var BACKOFF_THRESHOLD = 3;
11243
11610
  var CRITICAL_THRESHOLD = 10;
11244
11611
  var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", "coverage", ".venv", "vendor", "target"]);
11245
11612
  function withinRoot2(rootPath, targetPath) {
11246
- const relative = path20.relative(rootPath, targetPath);
11247
- return relative === "" || !relative.startsWith("..") && !path20.isAbsolute(relative);
11613
+ const relative = path21.relative(rootPath, targetPath);
11614
+ return relative === "" || !relative.startsWith("..") && !path21.isAbsolute(relative);
11248
11615
  }
11249
11616
  function hasIgnoredRepoSegment(baseDir, targetPath) {
11250
- const relativePath = path20.relative(baseDir, targetPath);
11617
+ const relativePath = path21.relative(baseDir, targetPath);
11251
11618
  if (!relativePath || relativePath.startsWith("..")) {
11252
11619
  return false;
11253
11620
  }
11254
- return relativePath.split(path20.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
11621
+ return relativePath.split(path21.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
11255
11622
  }
11256
11623
  function workspaceIgnoreRoots(rootDir, paths) {
11257
11624
  return [
@@ -11260,16 +11627,16 @@ function workspaceIgnoreRoots(rootDir, paths) {
11260
11627
  paths.stateDir,
11261
11628
  paths.agentDir,
11262
11629
  paths.inboxDir,
11263
- path20.join(rootDir, ".claude"),
11264
- path20.join(rootDir, ".cursor"),
11265
- path20.join(rootDir, ".obsidian")
11266
- ].map((candidate) => path20.resolve(candidate));
11630
+ path21.join(rootDir, ".claude"),
11631
+ path21.join(rootDir, ".cursor"),
11632
+ path21.join(rootDir, ".obsidian")
11633
+ ].map((candidate) => path21.resolve(candidate));
11267
11634
  }
11268
11635
  async function resolveWatchTargets(rootDir, paths, options) {
11269
- const targets = /* @__PURE__ */ new Set([path20.resolve(paths.inboxDir)]);
11636
+ const targets = /* @__PURE__ */ new Set([path21.resolve(paths.inboxDir)]);
11270
11637
  if (options.repo) {
11271
11638
  for (const repoRoot of await listTrackedRepoRoots(rootDir)) {
11272
- targets.add(path20.resolve(repoRoot));
11639
+ targets.add(path21.resolve(repoRoot));
11273
11640
  }
11274
11641
  }
11275
11642
  return [...targets].sort((left, right) => left.localeCompare(right));
@@ -11399,7 +11766,7 @@ async function watchVault(rootDir, options = {}) {
11399
11766
  const { paths } = await initWorkspace(rootDir);
11400
11767
  const baseDebounceMs = options.debounceMs ?? 900;
11401
11768
  const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
11402
- const inboxWatchRoot = path20.resolve(paths.inboxDir);
11769
+ const inboxWatchRoot = path21.resolve(paths.inboxDir);
11403
11770
  let watchTargets = await resolveWatchTargets(rootDir, paths, options);
11404
11771
  let timer;
11405
11772
  let running = false;
@@ -11413,7 +11780,7 @@ async function watchVault(rootDir, options = {}) {
11413
11780
  usePolling: true,
11414
11781
  interval: 100,
11415
11782
  ignored: (targetPath) => {
11416
- const absolutePath = path20.resolve(targetPath);
11783
+ const absolutePath = path21.resolve(targetPath);
11417
11784
  const primaryTarget = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
11418
11785
  if (!primaryTarget) {
11419
11786
  return false;
@@ -11585,8 +11952,8 @@ async function watchVault(rootDir, options = {}) {
11585
11952
  }
11586
11953
  };
11587
11954
  const reasonForPath = (targetPath) => {
11588
- const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, path20.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
11589
- return path20.relative(baseDir, targetPath) || ".";
11955
+ const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, path21.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
11956
+ return path21.relative(baseDir, targetPath) || ".";
11590
11957
  };
11591
11958
  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)}`));
11592
11959
  await new Promise((resolve, reject) => {
@@ -11627,15 +11994,15 @@ async function getWatchStatus(rootDir) {
11627
11994
  var execFileAsync = promisify(execFile);
11628
11995
  async function readViewerPage(rootDir, relativePath) {
11629
11996
  const { paths } = await loadVaultConfig(rootDir);
11630
- const absolutePath = path21.resolve(paths.wikiDir, relativePath);
11997
+ const absolutePath = path22.resolve(paths.wikiDir, relativePath);
11631
11998
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
11632
11999
  return null;
11633
12000
  }
11634
- const raw = await fs17.readFile(absolutePath, "utf8");
12001
+ const raw = await fs18.readFile(absolutePath, "utf8");
11635
12002
  const parsed = matter9(raw);
11636
12003
  return {
11637
12004
  path: relativePath,
11638
- title: typeof parsed.data.title === "string" ? parsed.data.title : path21.basename(relativePath, path21.extname(relativePath)),
12005
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path22.basename(relativePath, path22.extname(relativePath)),
11639
12006
  frontmatter: parsed.data,
11640
12007
  content: parsed.content,
11641
12008
  assets: normalizeOutputAssets(parsed.data.output_assets)
@@ -11643,12 +12010,12 @@ async function readViewerPage(rootDir, relativePath) {
11643
12010
  }
11644
12011
  async function readViewerAsset(rootDir, relativePath) {
11645
12012
  const { paths } = await loadVaultConfig(rootDir);
11646
- const absolutePath = path21.resolve(paths.wikiDir, relativePath);
12013
+ const absolutePath = path22.resolve(paths.wikiDir, relativePath);
11647
12014
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
11648
12015
  return null;
11649
12016
  }
11650
12017
  return {
11651
- buffer: await fs17.readFile(absolutePath),
12018
+ buffer: await fs18.readFile(absolutePath),
11652
12019
  mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
11653
12020
  };
11654
12021
  }
@@ -11671,12 +12038,12 @@ async function readJsonBody(request) {
11671
12038
  return JSON.parse(raw);
11672
12039
  }
11673
12040
  async function ensureViewerDist(viewerDistDir) {
11674
- const indexPath = path21.join(viewerDistDir, "index.html");
12041
+ const indexPath = path22.join(viewerDistDir, "index.html");
11675
12042
  if (await fileExists(indexPath)) {
11676
12043
  return;
11677
12044
  }
11678
- const viewerProjectDir = path21.dirname(viewerDistDir);
11679
- if (await fileExists(path21.join(viewerProjectDir, "package.json"))) {
12045
+ const viewerProjectDir = path22.dirname(viewerDistDir);
12046
+ if (await fileExists(path22.join(viewerProjectDir, "package.json"))) {
11680
12047
  await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
11681
12048
  }
11682
12049
  }
@@ -11693,7 +12060,7 @@ async function startGraphServer(rootDir, port) {
11693
12060
  return;
11694
12061
  }
11695
12062
  response.writeHead(200, { "content-type": "application/json" });
11696
- response.end(await fs17.readFile(paths.graphPath, "utf8"));
12063
+ response.end(await fs18.readFile(paths.graphPath, "utf8"));
11697
12064
  return;
11698
12065
  }
11699
12066
  if (url.pathname === "/api/graph/query") {
@@ -11825,8 +12192,8 @@ async function startGraphServer(rootDir, port) {
11825
12192
  return;
11826
12193
  }
11827
12194
  const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
11828
- const target = path21.join(paths.viewerDistDir, relativePath);
11829
- const fallback = path21.join(paths.viewerDistDir, "index.html");
12195
+ const target = path22.join(paths.viewerDistDir, relativePath);
12196
+ const fallback = path22.join(paths.viewerDistDir, "index.html");
11830
12197
  const filePath = await fileExists(target) ? target : fallback;
11831
12198
  if (!await fileExists(filePath)) {
11832
12199
  response.writeHead(503, { "content-type": "text/plain" });
@@ -11834,7 +12201,7 @@ async function startGraphServer(rootDir, port) {
11834
12201
  return;
11835
12202
  }
11836
12203
  response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
11837
- response.end(await fs17.readFile(filePath));
12204
+ response.end(await fs18.readFile(filePath));
11838
12205
  });
11839
12206
  await new Promise((resolve) => {
11840
12207
  server.listen(effectivePort, resolve);
@@ -11861,7 +12228,7 @@ async function exportGraphHtml(rootDir, outputPath) {
11861
12228
  throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
11862
12229
  }
11863
12230
  await ensureViewerDist(paths.viewerDistDir);
11864
- const indexPath = path21.join(paths.viewerDistDir, "index.html");
12231
+ const indexPath = path22.join(paths.viewerDistDir, "index.html");
11865
12232
  if (!await fileExists(indexPath)) {
11866
12233
  throw new Error("Viewer build not found. Run `pnpm build` first.");
11867
12234
  }
@@ -11885,16 +12252,16 @@ async function exportGraphHtml(rootDir, outputPath) {
11885
12252
  } : null;
11886
12253
  })
11887
12254
  );
11888
- const rawHtml = await fs17.readFile(indexPath, "utf8");
12255
+ const rawHtml = await fs18.readFile(indexPath, "utf8");
11889
12256
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
11890
12257
  const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
11891
- const scriptPath = scriptMatch?.[1] ? path21.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
11892
- const stylePath = styleMatch?.[1] ? path21.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
12258
+ const scriptPath = scriptMatch?.[1] ? path22.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
12259
+ const stylePath = styleMatch?.[1] ? path22.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
11893
12260
  if (!scriptPath || !await fileExists(scriptPath)) {
11894
12261
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
11895
12262
  }
11896
- const script = await fs17.readFile(scriptPath, "utf8");
11897
- const style = stylePath && await fileExists(stylePath) ? await fs17.readFile(stylePath, "utf8") : "";
12263
+ const script = await fs18.readFile(scriptPath, "utf8");
12264
+ const style = stylePath && await fileExists(stylePath) ? await fs18.readFile(stylePath, "utf8") : "";
11898
12265
  const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean) }, null, 2).replace(/</g, "\\u003c");
11899
12266
  const html = [
11900
12267
  "<!doctype html>",
@@ -11913,9 +12280,9 @@ async function exportGraphHtml(rootDir, outputPath) {
11913
12280
  "</html>",
11914
12281
  ""
11915
12282
  ].filter(Boolean).join("\n");
11916
- await fs17.mkdir(path21.dirname(outputPath), { recursive: true });
11917
- await fs17.writeFile(outputPath, html, "utf8");
11918
- return path21.resolve(outputPath);
12283
+ await fs18.mkdir(path22.dirname(outputPath), { recursive: true });
12284
+ await fs18.writeFile(outputPath, html, "utf8");
12285
+ return path22.resolve(outputPath);
11919
12286
  }
11920
12287
  export {
11921
12288
  acceptApproval,