@swarmvaultai/engine 0.1.24 → 0.1.26

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
@@ -21,7 +21,7 @@ import {
21
21
  uniqueBy,
22
22
  writeFileIfChanged,
23
23
  writeJsonFile
24
- } from "./chunk-6UPHDGEB.js";
24
+ } from "./chunk-LEUV6TWJ.js";
25
25
 
26
26
  // src/agents.ts
27
27
  import fs from "fs/promises";
@@ -662,7 +662,7 @@ async function uninstallGitHooks(rootDir) {
662
662
 
663
663
  // src/ingest.ts
664
664
  import fs9 from "fs/promises";
665
- import path9 from "path";
665
+ import path10 from "path";
666
666
  import { Readability } from "@mozilla/readability";
667
667
  import matter3 from "gray-matter";
668
668
  import ignore from "ignore";
@@ -3143,9 +3143,68 @@ async function appendWatchRun(rootDir, run) {
3143
3143
  await appendJsonLine(paths.jobsLogPath, run);
3144
3144
  }
3145
3145
 
3146
+ // src/source-classification.ts
3147
+ import path8 from "path";
3148
+ var ALL_SOURCE_CLASSES = ["first_party", "third_party", "resource", "generated"];
3149
+ var THIRD_PARTY_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "vendor", "Pods"]);
3150
+ var GENERATED_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", ".next", "coverage", "DerivedData", "target"]);
3151
+ function matchesAnyGlob(relativePath, patterns) {
3152
+ return patterns.some(
3153
+ (pattern) => path8.matchesGlob(relativePath, pattern) || path8.matchesGlob(path8.posix.basename(relativePath), pattern)
3154
+ );
3155
+ }
3156
+ function classifyRepoPath(relativePath, repoAnalysis) {
3157
+ const normalized = relativePath.replace(/\\/g, "/");
3158
+ const custom = repoAnalysis?.classifyGlobs;
3159
+ if (custom?.first_party?.length && matchesAnyGlob(normalized, custom.first_party)) {
3160
+ return "first_party";
3161
+ }
3162
+ for (const sourceClass of ["third_party", "resource", "generated"]) {
3163
+ const patterns = custom?.[sourceClass];
3164
+ if (patterns?.length && matchesAnyGlob(normalized, patterns)) {
3165
+ return sourceClass;
3166
+ }
3167
+ }
3168
+ const segments = normalized.split("/").filter(Boolean);
3169
+ if (segments.some((segment) => THIRD_PARTY_SEGMENTS.has(segment))) {
3170
+ return "third_party";
3171
+ }
3172
+ if (segments.some((segment) => GENERATED_SEGMENTS.has(segment))) {
3173
+ return "generated";
3174
+ }
3175
+ if (segments.some((segment) => segment.endsWith(".xcassets") || segment.endsWith(".imageset"))) {
3176
+ return "resource";
3177
+ }
3178
+ return "first_party";
3179
+ }
3180
+ function normalizeExtractClasses(repoAnalysis, extra = []) {
3181
+ const configured = repoAnalysis?.extractClasses?.length ? repoAnalysis.extractClasses : ["first_party"];
3182
+ return ALL_SOURCE_CLASSES.filter((sourceClass) => (/* @__PURE__ */ new Set([...configured, ...extra])).has(sourceClass));
3183
+ }
3184
+ function aggregateSourceClass(values) {
3185
+ const available = ALL_SOURCE_CLASSES.filter((sourceClass) => values.includes(sourceClass));
3186
+ if (!available.length) {
3187
+ return void 0;
3188
+ }
3189
+ if (available.includes("first_party")) {
3190
+ return "first_party";
3191
+ }
3192
+ if (available.includes("resource")) {
3193
+ return "resource";
3194
+ }
3195
+ if (available.includes("third_party")) {
3196
+ return "third_party";
3197
+ }
3198
+ return "generated";
3199
+ }
3200
+ function aggregateManifestSourceClass(manifests, sourceIds) {
3201
+ const byId = new Map(manifests.map((manifest) => [manifest.sourceId, manifest.sourceClass]));
3202
+ return aggregateSourceClass(sourceIds.map((sourceId) => byId.get(sourceId)));
3203
+ }
3204
+
3146
3205
  // src/watch-state.ts
3147
3206
  import fs8 from "fs/promises";
3148
- import path8 from "path";
3207
+ import path9 from "path";
3149
3208
  import matter2 from "gray-matter";
3150
3209
  function pendingEntryKey(entry) {
3151
3210
  return entry.path;
@@ -3159,7 +3218,7 @@ function normalizeRelativePath(rootDir, filePath) {
3159
3218
  if (!filePath) {
3160
3219
  return void 0;
3161
3220
  }
3162
- return toPosix(path8.relative(rootDir, path8.resolve(filePath)));
3221
+ return toPosix(path9.relative(rootDir, path9.resolve(filePath)));
3163
3222
  }
3164
3223
  async function readPendingSemanticRefresh(rootDir) {
3165
3224
  const { paths } = await initWorkspace(rootDir);
@@ -3253,7 +3312,7 @@ async function markPagesStaleForSources(rootDir, sourceIds) {
3253
3312
  if (page.freshness !== "stale" || !page.sourceIds.some((sourceId) => affectedSourceIds.has(sourceId))) {
3254
3313
  continue;
3255
3314
  }
3256
- const absolutePath = path8.join(paths.wikiDir, page.path);
3315
+ const absolutePath = path9.join(paths.wikiDir, page.path);
3257
3316
  if (!await fileExists(absolutePath)) {
3258
3317
  continue;
3259
3318
  }
@@ -3272,7 +3331,7 @@ async function markPagesStaleForSources(rootDir, sourceIds) {
3272
3331
  // src/ingest.ts
3273
3332
  var DEFAULT_MAX_ASSET_SIZE = 10 * 1024 * 1024;
3274
3333
  var DEFAULT_MAX_DIRECTORY_FILES = 5e3;
3275
- var BUILT_IN_REPO_IGNORES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", "coverage", ".venv", "vendor", "target"]);
3334
+ var HARD_REPO_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
3276
3335
  function uniqueStrings(values) {
3277
3336
  return [...new Set(values.filter(Boolean))];
3278
3337
  }
@@ -3308,36 +3367,47 @@ function normalizeIngestOptions(options) {
3308
3367
  return {
3309
3368
  includeAssets: options?.includeAssets ?? true,
3310
3369
  maxAssetSize: Math.max(0, Math.floor(options?.maxAssetSize ?? DEFAULT_MAX_ASSET_SIZE)),
3311
- repoRoot: options?.repoRoot ? path9.resolve(options.repoRoot) : void 0,
3370
+ repoRoot: options?.repoRoot ? path10.resolve(options.repoRoot) : void 0,
3312
3371
  include: (options?.include ?? []).map((pattern) => pattern.trim()).filter(Boolean),
3313
3372
  exclude: (options?.exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean),
3314
3373
  maxFiles: Math.max(1, Math.floor(options?.maxFiles ?? DEFAULT_MAX_DIRECTORY_FILES)),
3315
- gitignore: options?.gitignore ?? true
3374
+ gitignore: options?.gitignore ?? true,
3375
+ extractClasses: options?.extractClasses ?? ["first_party"]
3316
3376
  };
3317
3377
  }
3318
- function matchesAnyGlob(relativePath, patterns) {
3378
+ async function resolveRepoIngestOptions(rootDir, options) {
3379
+ const normalized = normalizeIngestOptions(options);
3380
+ const { config } = await loadVaultConfig(rootDir);
3381
+ const repoAnalysis = config.repoAnalysis;
3382
+ return {
3383
+ ...normalized,
3384
+ extractClasses: normalizeExtractClasses(repoAnalysis, normalized.extractClasses),
3385
+ repoAnalysis
3386
+ };
3387
+ }
3388
+ function matchesAnyGlob2(relativePath, patterns) {
3319
3389
  return patterns.some(
3320
- (pattern) => path9.matchesGlob(relativePath, pattern) || path9.matchesGlob(path9.posix.basename(relativePath), pattern)
3390
+ (pattern) => path10.matchesGlob(relativePath, pattern) || path10.matchesGlob(path10.posix.basename(relativePath), pattern)
3321
3391
  );
3322
3392
  }
3323
3393
  function supportedDirectoryKind(sourceKind) {
3324
3394
  return sourceKind !== "binary";
3325
3395
  }
3326
3396
  async function findNearestGitRoot2(startPath) {
3327
- let current = path9.resolve(startPath);
3397
+ let current = path10.resolve(startPath);
3328
3398
  try {
3329
3399
  const stat = await fs9.stat(current);
3330
3400
  if (!stat.isDirectory()) {
3331
- current = path9.dirname(current);
3401
+ current = path10.dirname(current);
3332
3402
  }
3333
3403
  } catch {
3334
- current = path9.dirname(current);
3404
+ current = path10.dirname(current);
3335
3405
  }
3336
3406
  while (true) {
3337
- if (await fileExists(path9.join(current, ".git"))) {
3407
+ if (await fileExists(path10.join(current, ".git"))) {
3338
3408
  return current;
3339
3409
  }
3340
- const parent = path9.dirname(current);
3410
+ const parent = path10.dirname(current);
3341
3411
  if (parent === current) {
3342
3412
  return null;
3343
3413
  }
@@ -3345,26 +3415,26 @@ async function findNearestGitRoot2(startPath) {
3345
3415
  }
3346
3416
  }
3347
3417
  function withinRoot(rootPath, targetPath) {
3348
- const relative = path9.relative(rootPath, targetPath);
3349
- return relative === "" || !relative.startsWith("..") && !path9.isAbsolute(relative);
3418
+ const relative = path10.relative(rootPath, targetPath);
3419
+ return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
3350
3420
  }
3351
3421
  function repoRootFromManifest(manifest) {
3352
3422
  if (manifest.originType !== "file" || !manifest.originalPath || !manifest.repoRelativePath) {
3353
3423
  return null;
3354
3424
  }
3355
- const repoDir = path9.posix.dirname(manifest.repoRelativePath);
3356
- const fileDir = path9.dirname(path9.resolve(manifest.originalPath));
3425
+ const repoDir = path10.posix.dirname(manifest.repoRelativePath);
3426
+ const fileDir = path10.dirname(path10.resolve(manifest.originalPath));
3357
3427
  if (repoDir === "." || !repoDir) {
3358
3428
  return fileDir;
3359
3429
  }
3360
3430
  const segments = repoDir.split("/").filter(Boolean);
3361
- return path9.resolve(fileDir, ...segments.map(() => ".."));
3431
+ return path10.resolve(fileDir, ...segments.map(() => ".."));
3362
3432
  }
3363
3433
  function repoRelativePathFor(absolutePath, repoRoot) {
3364
3434
  if (!repoRoot || !withinRoot(repoRoot, absolutePath)) {
3365
3435
  return void 0;
3366
3436
  }
3367
- const relative = toPosix(path9.relative(repoRoot, absolutePath));
3437
+ const relative = toPosix(path10.relative(repoRoot, absolutePath));
3368
3438
  return relative && !relative.startsWith("..") ? relative : void 0;
3369
3439
  }
3370
3440
  function normalizeOriginUrl(input) {
@@ -3661,7 +3731,7 @@ function buildCompositeHash(payloadBytes, attachments = []) {
3661
3731
  return sha256(`${sha256(payloadBytes)}|${attachmentSignature}`);
3662
3732
  }
3663
3733
  function sanitizeAssetRelativePath(value) {
3664
- const normalized = path9.posix.normalize(value.replace(/\\/g, "/"));
3734
+ const normalized = path10.posix.normalize(value.replace(/\\/g, "/"));
3665
3735
  const segments = normalized.split("/").filter(Boolean).map((segment) => {
3666
3736
  if (segment === ".") {
3667
3737
  return "";
@@ -3681,7 +3751,7 @@ function normalizeLocalReference(value) {
3681
3751
  return null;
3682
3752
  }
3683
3753
  const lowered = candidate.toLowerCase();
3684
- if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path9.isAbsolute(candidate)) {
3754
+ if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path10.isAbsolute(candidate)) {
3685
3755
  return null;
3686
3756
  }
3687
3757
  return candidate.replace(/\\/g, "/");
@@ -3748,7 +3818,7 @@ async function readManifestByHash(manifestsDir, contentHash) {
3748
3818
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
3749
3819
  continue;
3750
3820
  }
3751
- const manifest = await readJsonFile(path9.join(manifestsDir, entry.name));
3821
+ const manifest = await readJsonFile(path10.join(manifestsDir, entry.name));
3752
3822
  if (manifest?.contentHash === contentHash) {
3753
3823
  return manifest;
3754
3824
  }
@@ -3761,7 +3831,7 @@ async function readManifestByOrigin(manifestsDir, prepared) {
3761
3831
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
3762
3832
  continue;
3763
3833
  }
3764
- const manifest = await readJsonFile(path9.join(manifestsDir, entry.name));
3834
+ const manifest = await readJsonFile(path10.join(manifestsDir, entry.name));
3765
3835
  if (manifest && manifestMatchesOrigin(manifest, prepared)) {
3766
3836
  return manifest;
3767
3837
  }
@@ -3772,7 +3842,7 @@ async function loadGitignoreMatcher(repoRoot, enabled) {
3772
3842
  if (!enabled) {
3773
3843
  return null;
3774
3844
  }
3775
- const gitignorePath = path9.join(repoRoot, ".gitignore");
3845
+ const gitignorePath = path10.join(repoRoot, ".gitignore");
3776
3846
  if (!await fileExists(gitignorePath)) {
3777
3847
  return null;
3778
3848
  }
@@ -3782,12 +3852,15 @@ async function loadGitignoreMatcher(repoRoot, enabled) {
3782
3852
  }
3783
3853
  function builtInIgnoreReason(relativePath) {
3784
3854
  for (const segment of relativePath.split("/")) {
3785
- if (BUILT_IN_REPO_IGNORES.has(segment)) {
3855
+ if (HARD_REPO_IGNORES.has(segment)) {
3786
3856
  return `built_in_ignore:${segment}`;
3787
3857
  }
3788
3858
  }
3789
3859
  return null;
3790
3860
  }
3861
+ function sourceClassForRelativePath(relativePath, options) {
3862
+ return classifyRepoPath(relativePath, options.repoAnalysis);
3863
+ }
3791
3864
  async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
3792
3865
  const matcher = await loadGitignoreMatcher(repoRoot, options.gitignore);
3793
3866
  const skipped = [];
@@ -3801,20 +3874,20 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
3801
3874
  const entries = await fs9.readdir(currentDir, { withFileTypes: true });
3802
3875
  entries.sort((left, right) => left.name.localeCompare(right.name));
3803
3876
  for (const entry of entries) {
3804
- const absolutePath = path9.join(currentDir, entry.name);
3805
- const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path9.relative(inputDir, absolutePath));
3877
+ const absolutePath = path10.join(currentDir, entry.name);
3878
+ const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(inputDir, absolutePath));
3806
3879
  const relativePath = relativeToRepo || entry.name;
3807
3880
  const builtInReason = builtInIgnoreReason(relativePath);
3808
3881
  if (builtInReason) {
3809
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: builtInReason });
3882
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: builtInReason });
3810
3883
  continue;
3811
3884
  }
3812
3885
  if (matcher?.ignores(relativePath)) {
3813
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "gitignore" });
3886
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "gitignore" });
3814
3887
  continue;
3815
3888
  }
3816
- if (matchesAnyGlob(relativePath, options.exclude)) {
3817
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "exclude_glob" });
3889
+ if (matchesAnyGlob2(relativePath, options.exclude)) {
3890
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "exclude_glob" });
3818
3891
  continue;
3819
3892
  }
3820
3893
  if (entry.isDirectory()) {
@@ -3822,21 +3895,26 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
3822
3895
  continue;
3823
3896
  }
3824
3897
  if (!entry.isFile()) {
3825
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
3898
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
3826
3899
  continue;
3827
3900
  }
3828
- if (options.include.length > 0 && !matchesAnyGlob(relativePath, options.include)) {
3829
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "include_glob" });
3901
+ if (options.include.length > 0 && !matchesAnyGlob2(relativePath, options.include)) {
3902
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "include_glob" });
3830
3903
  continue;
3831
3904
  }
3832
3905
  const mimeType = guessMimeType(absolutePath);
3833
3906
  const sourceKind = inferKind(mimeType, absolutePath);
3907
+ const sourceClass = sourceClassForRelativePath(relativePath, options);
3834
3908
  if (!supportedDirectoryKind(sourceKind)) {
3835
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
3909
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
3910
+ continue;
3911
+ }
3912
+ if (!options.extractClasses.includes(sourceClass)) {
3913
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: `source_class:${sourceClass}` });
3836
3914
  continue;
3837
3915
  }
3838
3916
  if (files.length >= options.maxFiles) {
3839
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "max_files" });
3917
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "max_files" });
3840
3918
  continue;
3841
3919
  }
3842
3920
  files.push(absolutePath);
@@ -3858,12 +3936,12 @@ function resolveUrlMimeType(input, response) {
3858
3936
  function buildRemoteAssetRelativePath(assetUrl, mimeType) {
3859
3937
  const url = new URL(assetUrl);
3860
3938
  const normalized = sanitizeAssetRelativePath(`${url.hostname}${url.pathname || "/asset"}`);
3861
- const extension = path9.posix.extname(normalized);
3862
- const directory = path9.posix.dirname(normalized);
3863
- const basename = extension ? path9.posix.basename(normalized, extension) : path9.posix.basename(normalized);
3939
+ const extension = path10.posix.extname(normalized);
3940
+ const directory = path10.posix.dirname(normalized);
3941
+ const basename = extension ? path10.posix.basename(normalized, extension) : path10.posix.basename(normalized);
3864
3942
  const resolvedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
3865
3943
  const hashedName = `${basename || "asset"}-${sha256(assetUrl).slice(0, 8)}${resolvedExtension}`;
3866
- return directory === "." ? hashedName : path9.posix.join(directory, hashedName);
3944
+ return directory === "." ? hashedName : path10.posix.join(directory, hashedName);
3867
3945
  }
3868
3946
  async function readResponseBytesWithinLimit(response, maxBytes) {
3869
3947
  const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
@@ -3990,7 +4068,7 @@ async function persistPreparedInput(rootDir, prepared, paths) {
3990
4068
  const extractionHash = prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact);
3991
4069
  const existingByOrigin = await readManifestByOrigin(paths.manifestsDir, prepared);
3992
4070
  const existingByHash = existingByOrigin ? null : await readManifestByHash(paths.manifestsDir, contentHash);
3993
- if (existingByOrigin && existingByOrigin.contentHash === contentHash && existingByOrigin.extractionHash === extractionHash && existingByOrigin.title === prepared.title && existingByOrigin.sourceKind === prepared.sourceKind && existingByOrigin.sourceType === prepared.sourceType && existingByOrigin.language === prepared.language && existingByOrigin.mimeType === prepared.mimeType && existingByOrigin.repoRelativePath === prepared.repoRelativePath) {
4071
+ if (existingByOrigin && existingByOrigin.contentHash === contentHash && existingByOrigin.extractionHash === extractionHash && existingByOrigin.title === prepared.title && existingByOrigin.sourceKind === prepared.sourceKind && existingByOrigin.sourceType === prepared.sourceType && existingByOrigin.sourceClass === prepared.sourceClass && existingByOrigin.language === prepared.language && existingByOrigin.mimeType === prepared.mimeType && existingByOrigin.repoRelativePath === prepared.repoRelativePath) {
3994
4072
  return { manifest: existingByOrigin, isNew: false, wasUpdated: false };
3995
4073
  }
3996
4074
  if (existingByHash) {
@@ -3999,18 +4077,18 @@ async function persistPreparedInput(rootDir, prepared, paths) {
3999
4077
  const previous = existingByOrigin ?? void 0;
4000
4078
  const sourceId = previous?.sourceId ?? `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
4001
4079
  const now = (/* @__PURE__ */ new Date()).toISOString();
4002
- const storedPath = path9.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
4003
- const extractedTextPath = prepared.extractedText ? path9.join(paths.extractsDir, `${sourceId}.md`) : void 0;
4004
- const extractedMetadataPath = prepared.extractionArtifact ? path9.join(paths.extractsDir, `${sourceId}.json`) : void 0;
4005
- const attachmentsDir = path9.join(paths.rawAssetsDir, sourceId);
4080
+ const storedPath = path10.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
4081
+ const extractedTextPath = prepared.extractedText ? path10.join(paths.extractsDir, `${sourceId}.md`) : void 0;
4082
+ const extractedMetadataPath = prepared.extractionArtifact ? path10.join(paths.extractsDir, `${sourceId}.json`) : void 0;
4083
+ const attachmentsDir = path10.join(paths.rawAssetsDir, sourceId);
4006
4084
  if (previous?.storedPath) {
4007
- await fs9.rm(path9.resolve(rootDir, previous.storedPath), { force: true });
4085
+ await fs9.rm(path10.resolve(rootDir, previous.storedPath), { force: true });
4008
4086
  }
4009
4087
  if (previous?.extractedTextPath) {
4010
- await fs9.rm(path9.resolve(rootDir, previous.extractedTextPath), { force: true });
4088
+ await fs9.rm(path10.resolve(rootDir, previous.extractedTextPath), { force: true });
4011
4089
  }
4012
4090
  if (previous?.extractedMetadataPath) {
4013
- await fs9.rm(path9.resolve(rootDir, previous.extractedMetadataPath), { force: true });
4091
+ await fs9.rm(path10.resolve(rootDir, previous.extractedMetadataPath), { force: true });
4014
4092
  }
4015
4093
  await fs9.rm(attachmentsDir, { recursive: true, force: true });
4016
4094
  await fs9.writeFile(storedPath, prepared.payloadBytes);
@@ -4022,11 +4100,11 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4022
4100
  }
4023
4101
  const manifestAttachments = [];
4024
4102
  for (const attachment of attachments) {
4025
- const absoluteAttachmentPath = path9.join(attachmentsDir, attachment.relativePath);
4026
- await ensureDir(path9.dirname(absoluteAttachmentPath));
4103
+ const absoluteAttachmentPath = path10.join(attachmentsDir, attachment.relativePath);
4104
+ await ensureDir(path10.dirname(absoluteAttachmentPath));
4027
4105
  await fs9.writeFile(absoluteAttachmentPath, attachment.bytes);
4028
4106
  manifestAttachments.push({
4029
- path: toPosix(path9.relative(rootDir, absoluteAttachmentPath)),
4107
+ path: toPosix(path10.relative(rootDir, absoluteAttachmentPath)),
4030
4108
  mimeType: attachment.mimeType,
4031
4109
  originalPath: attachment.originalPath
4032
4110
  });
@@ -4037,13 +4115,14 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4037
4115
  originType: prepared.originType,
4038
4116
  sourceKind: prepared.sourceKind,
4039
4117
  sourceType: prepared.sourceType,
4118
+ sourceClass: prepared.sourceClass,
4040
4119
  language: prepared.language,
4041
4120
  originalPath: prepared.originalPath,
4042
4121
  repoRelativePath: prepared.repoRelativePath,
4043
4122
  url: prepared.url,
4044
- storedPath: toPosix(path9.relative(rootDir, storedPath)),
4045
- extractedTextPath: extractedTextPath ? toPosix(path9.relative(rootDir, extractedTextPath)) : void 0,
4046
- extractedMetadataPath: extractedMetadataPath ? toPosix(path9.relative(rootDir, extractedMetadataPath)) : void 0,
4123
+ storedPath: toPosix(path10.relative(rootDir, storedPath)),
4124
+ extractedTextPath: extractedTextPath ? toPosix(path10.relative(rootDir, extractedTextPath)) : void 0,
4125
+ extractedMetadataPath: extractedMetadataPath ? toPosix(path10.relative(rootDir, extractedMetadataPath)) : void 0,
4047
4126
  extractionHash,
4048
4127
  mimeType: prepared.mimeType,
4049
4128
  contentHash,
@@ -4051,7 +4130,7 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4051
4130
  updatedAt: now,
4052
4131
  attachments: manifestAttachments.length ? manifestAttachments : void 0
4053
4132
  };
4054
- await writeJsonFile(path9.join(paths.manifestsDir, `${sourceId}.json`), manifest);
4133
+ await writeJsonFile(path10.join(paths.manifestsDir, `${sourceId}.json`), manifest);
4055
4134
  await appendLogEntry(rootDir, "ingest", prepared.title, [
4056
4135
  `source_id=${sourceId}`,
4057
4136
  `kind=${prepared.sourceKind}`,
@@ -4069,16 +4148,16 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4069
4148
  return { manifest, isNew: !previous, wasUpdated: Boolean(previous) };
4070
4149
  }
4071
4150
  async function removeManifestArtifacts(rootDir, manifest, paths) {
4072
- await fs9.rm(path9.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
4073
- await fs9.rm(path9.resolve(rootDir, manifest.storedPath), { force: true });
4151
+ await fs9.rm(path10.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
4152
+ await fs9.rm(path10.resolve(rootDir, manifest.storedPath), { force: true });
4074
4153
  if (manifest.extractedTextPath) {
4075
- await fs9.rm(path9.resolve(rootDir, manifest.extractedTextPath), { force: true });
4154
+ await fs9.rm(path10.resolve(rootDir, manifest.extractedTextPath), { force: true });
4076
4155
  }
4077
4156
  if (manifest.extractedMetadataPath) {
4078
- await fs9.rm(path9.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
4157
+ await fs9.rm(path10.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
4079
4158
  }
4080
- await fs9.rm(path9.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
4081
- await fs9.rm(path9.join(paths.analysesDir, `${manifest.sourceId}.json`), { force: true });
4159
+ await fs9.rm(path10.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
4160
+ await fs9.rm(path10.join(paths.analysesDir, `${manifest.sourceId}.json`), { force: true });
4082
4161
  }
4083
4162
  function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
4084
4163
  const candidates = [
@@ -4087,14 +4166,14 @@ function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
4087
4166
  paths.stateDir,
4088
4167
  paths.agentDir,
4089
4168
  paths.inboxDir,
4090
- path9.join(rootDir, ".claude"),
4091
- path9.join(rootDir, ".cursor"),
4092
- path9.join(rootDir, ".obsidian")
4169
+ path10.join(rootDir, ".claude"),
4170
+ path10.join(rootDir, ".cursor"),
4171
+ path10.join(rootDir, ".obsidian")
4093
4172
  ];
4094
- return candidates.map((candidate) => path9.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, candidate));
4173
+ return candidates.map((candidate) => path10.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, candidate));
4095
4174
  }
4096
4175
  function preparedMatchesManifest(manifest, prepared, contentHash) {
4097
- return manifest.contentHash === contentHash && manifest.extractionHash === (prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact)) && manifest.title === prepared.title && manifest.sourceKind === prepared.sourceKind && manifest.sourceType === prepared.sourceType && manifest.language === prepared.language && manifest.mimeType === prepared.mimeType && manifest.repoRelativePath === prepared.repoRelativePath;
4176
+ return manifest.contentHash === contentHash && manifest.extractionHash === (prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact)) && manifest.title === prepared.title && manifest.sourceKind === prepared.sourceKind && manifest.sourceType === prepared.sourceType && manifest.sourceClass === prepared.sourceClass && manifest.language === prepared.language && manifest.mimeType === prepared.mimeType && manifest.repoRelativePath === prepared.repoRelativePath;
4098
4177
  }
4099
4178
  function shouldDeferWatchSemanticRefresh(sourceKind) {
4100
4179
  return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "image";
@@ -4110,19 +4189,19 @@ async function listTrackedRepoRoots(rootDir) {
4110
4189
  }
4111
4190
  async function syncTrackedRepos(rootDir, options, repoRoots) {
4112
4191
  const { paths } = await initWorkspace(rootDir);
4113
- const normalizedOptions = normalizeIngestOptions(options);
4192
+ const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
4114
4193
  const manifests = await listManifests(rootDir);
4115
4194
  const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
4116
- (item) => path9.resolve(item)
4195
+ (item) => path10.resolve(item)
4117
4196
  );
4118
4197
  const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
4119
4198
  const manifestsByRepoRoot = /* @__PURE__ */ new Map();
4120
4199
  for (const manifest of manifests) {
4121
4200
  const repoRoot = repoRootFromManifest(manifest);
4122
- if (!repoRoot || !uniqueRoots.includes(path9.resolve(repoRoot))) {
4201
+ if (!repoRoot || !uniqueRoots.includes(path10.resolve(repoRoot))) {
4123
4202
  continue;
4124
4203
  }
4125
- const key = path9.resolve(repoRoot);
4204
+ const key = path10.resolve(repoRoot);
4126
4205
  const bucket = manifestsByRepoRoot.get(key) ?? [];
4127
4206
  bucket.push(manifest);
4128
4207
  manifestsByRepoRoot.set(key, bucket);
@@ -4147,14 +4226,15 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4147
4226
  skipped.push(
4148
4227
  ...collected.skipped,
4149
4228
  ...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
4150
- path: toPosix(path9.relative(rootDir, absolutePath)),
4229
+ path: toPosix(path10.relative(rootDir, absolutePath)),
4151
4230
  reason: "workspace_generated"
4152
4231
  }))
4153
4232
  );
4154
4233
  scannedCount += files.length;
4155
- const currentPaths = new Set(files.map((absolutePath) => path9.resolve(absolutePath)));
4234
+ const currentPaths = new Set(files.map((absolutePath) => path10.resolve(absolutePath)));
4156
4235
  for (const absolutePath of files) {
4157
- const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
4236
+ const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(repoRoot, absolutePath));
4237
+ const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
4158
4238
  const result = await persistPreparedInput(rootDir, prepared, paths);
4159
4239
  if (result.isNew) {
4160
4240
  imported.push(result.manifest);
@@ -4163,7 +4243,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4163
4243
  }
4164
4244
  }
4165
4245
  for (const manifest of repoManifests) {
4166
- const originalPath = manifest.originalPath ? path9.resolve(manifest.originalPath) : null;
4246
+ const originalPath = manifest.originalPath ? path10.resolve(manifest.originalPath) : null;
4167
4247
  if (originalPath && !currentPaths.has(originalPath)) {
4168
4248
  await removeManifestArtifacts(rootDir, manifest, paths);
4169
4249
  removed.push(manifest);
@@ -4171,7 +4251,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4171
4251
  }
4172
4252
  }
4173
4253
  if (uniqueRoots.length > 0) {
4174
- await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(path9.relative(rootDir, repoRoot)) || ".").join(","), [
4254
+ await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(path10.relative(rootDir, repoRoot)) || ".").join(","), [
4175
4255
  `repo_roots=${uniqueRoots.length}`,
4176
4256
  `scanned=${scannedCount}`,
4177
4257
  `imported=${imported.length}`,
@@ -4191,19 +4271,19 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4191
4271
  }
4192
4272
  async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4193
4273
  const { paths } = await initWorkspace(rootDir);
4194
- const normalizedOptions = normalizeIngestOptions(options);
4274
+ const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
4195
4275
  const manifests = await listManifests(rootDir);
4196
4276
  const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
4197
- (item) => path9.resolve(item)
4277
+ (item) => path10.resolve(item)
4198
4278
  );
4199
4279
  const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
4200
4280
  const manifestsByRepoRoot = /* @__PURE__ */ new Map();
4201
4281
  for (const manifest of manifests) {
4202
4282
  const repoRoot = repoRootFromManifest(manifest);
4203
- if (!repoRoot || !uniqueRoots.includes(path9.resolve(repoRoot))) {
4283
+ if (!repoRoot || !uniqueRoots.includes(path10.resolve(repoRoot))) {
4204
4284
  continue;
4205
4285
  }
4206
- const key = path9.resolve(repoRoot);
4286
+ const key = path10.resolve(repoRoot);
4207
4287
  const bucket = manifestsByRepoRoot.get(key) ?? [];
4208
4288
  bucket.push(manifest);
4209
4289
  manifestsByRepoRoot.set(key, bucket);
@@ -4218,7 +4298,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4218
4298
  for (const repoRoot of uniqueRoots) {
4219
4299
  const repoManifests = manifestsByRepoRoot.get(repoRoot) ?? [];
4220
4300
  const manifestsByOriginalPath = new Map(
4221
- repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [path9.resolve(manifest.originalPath), manifest])
4301
+ repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [path10.resolve(manifest.originalPath), manifest])
4222
4302
  );
4223
4303
  if (!await fileExists(repoRoot)) {
4224
4304
  for (const manifest of repoManifests) {
@@ -4226,7 +4306,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4226
4306
  pendingSemanticRefresh.push({
4227
4307
  id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? manifest.storedPath),
4228
4308
  repoRoot,
4229
- path: toPosix(path9.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
4309
+ path: toPosix(path10.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
4230
4310
  changeType: "removed",
4231
4311
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
4232
4312
  sourceId: manifest.sourceId,
@@ -4246,16 +4326,17 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4246
4326
  skipped.push(
4247
4327
  ...collected.skipped,
4248
4328
  ...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
4249
- path: toPosix(path9.relative(rootDir, absolutePath)),
4329
+ path: toPosix(path10.relative(rootDir, absolutePath)),
4250
4330
  reason: "workspace_generated"
4251
4331
  }))
4252
4332
  );
4253
4333
  scannedCount += files.length;
4254
- const currentPaths = new Set(files.map((absolutePath) => path9.resolve(absolutePath)));
4334
+ const currentPaths = new Set(files.map((absolutePath) => path10.resolve(absolutePath)));
4255
4335
  for (const absolutePath of files) {
4256
- const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
4336
+ const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(repoRoot, absolutePath));
4337
+ const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
4257
4338
  if (shouldDeferWatchSemanticRefresh(prepared.sourceKind)) {
4258
- const existing = manifestsByOriginalPath.get(path9.resolve(absolutePath));
4339
+ const existing = manifestsByOriginalPath.get(path10.resolve(absolutePath));
4259
4340
  const contentHash = buildCompositeHash(prepared.payloadBytes, prepared.attachments);
4260
4341
  const changed = !existing || !preparedMatchesManifest(existing, prepared, contentHash);
4261
4342
  if (changed) {
@@ -4263,10 +4344,10 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4263
4344
  id: pendingSemanticRefreshId(
4264
4345
  existing ? "modified" : "added",
4265
4346
  repoRoot,
4266
- prepared.repoRelativePath ?? toPosix(path9.relative(repoRoot, absolutePath))
4347
+ prepared.repoRelativePath ?? toPosix(path10.relative(repoRoot, absolutePath))
4267
4348
  ),
4268
4349
  repoRoot,
4269
- path: toPosix(path9.relative(rootDir, absolutePath)),
4350
+ path: toPosix(path10.relative(rootDir, absolutePath)),
4270
4351
  changeType: existing ? "modified" : "added",
4271
4352
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
4272
4353
  sourceId: existing?.sourceId,
@@ -4286,13 +4367,13 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4286
4367
  }
4287
4368
  }
4288
4369
  for (const manifest of repoManifests) {
4289
- const originalPath = manifest.originalPath ? path9.resolve(manifest.originalPath) : null;
4370
+ const originalPath = manifest.originalPath ? path10.resolve(manifest.originalPath) : null;
4290
4371
  if (originalPath && !currentPaths.has(originalPath)) {
4291
4372
  if (shouldDeferWatchSemanticRefresh(manifest.sourceKind)) {
4292
4373
  pendingSemanticRefresh.push({
4293
- id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path9.relative(repoRoot, originalPath))),
4374
+ id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path10.relative(repoRoot, originalPath))),
4294
4375
  repoRoot,
4295
- path: toPosix(path9.relative(rootDir, originalPath)),
4376
+ path: toPosix(path10.relative(rootDir, originalPath)),
4296
4377
  changeType: "removed",
4297
4378
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
4298
4379
  sourceId: manifest.sourceId,
@@ -4310,7 +4391,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4310
4391
  await appendLogEntry(
4311
4392
  rootDir,
4312
4393
  "sync_repo_watch",
4313
- uniqueRoots.map((repoRoot) => toPosix(path9.relative(rootDir, repoRoot)) || ".").join(","),
4394
+ uniqueRoots.map((repoRoot) => toPosix(path10.relative(rootDir, repoRoot)) || ".").join(","),
4314
4395
  [
4315
4396
  `repo_roots=${uniqueRoots.length}`,
4316
4397
  `scanned=${scannedCount}`,
@@ -4335,26 +4416,26 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4335
4416
  staleSourceIds: [...staleSourceIds]
4336
4417
  };
4337
4418
  }
4338
- async function prepareFileInput(rootDir, absoluteInput, repoRoot) {
4419
+ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
4339
4420
  const payloadBytes = await fs9.readFile(absoluteInput);
4340
4421
  const mimeType = guessMimeType(absoluteInput);
4341
4422
  const sourceKind = inferKind(mimeType, absoluteInput);
4342
4423
  const language = inferCodeLanguage(absoluteInput, mimeType);
4343
- const storedExtension = path9.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
4424
+ const storedExtension = path10.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
4344
4425
  let title;
4345
4426
  let extractedText;
4346
4427
  let extractionArtifact;
4347
4428
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
4348
4429
  extractedText = payloadBytes.toString("utf8");
4349
- title = titleFromText(path9.basename(absoluteInput, path9.extname(absoluteInput)), extractedText);
4430
+ title = titleFromText(path10.basename(absoluteInput, path10.extname(absoluteInput)), extractedText);
4350
4431
  extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
4351
4432
  } else if (sourceKind === "pdf") {
4352
- title = path9.basename(absoluteInput, path9.extname(absoluteInput));
4433
+ title = path10.basename(absoluteInput, path10.extname(absoluteInput));
4353
4434
  const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
4354
4435
  extractedText = extracted.extractedText;
4355
4436
  extractionArtifact = extracted.artifact;
4356
4437
  } else if (sourceKind === "image") {
4357
- title = path9.basename(absoluteInput, path9.extname(absoluteInput));
4438
+ title = path10.basename(absoluteInput, path10.extname(absoluteInput));
4358
4439
  const extracted = await extractImageWithVision(rootDir, {
4359
4440
  title,
4360
4441
  mimeType,
@@ -4364,12 +4445,13 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot) {
4364
4445
  extractedText = extracted.extractedText;
4365
4446
  extractionArtifact = extracted.artifact;
4366
4447
  } else {
4367
- title = path9.basename(absoluteInput, path9.extname(absoluteInput));
4448
+ title = path10.basename(absoluteInput, path10.extname(absoluteInput));
4368
4449
  }
4369
4450
  return {
4370
4451
  title,
4371
4452
  originType: "file",
4372
4453
  sourceKind,
4454
+ sourceClass,
4373
4455
  language,
4374
4456
  originalPath: toPosix(absoluteInput),
4375
4457
  repoRelativePath: repoRelativePathFor(absoluteInput, repoRoot),
@@ -4439,7 +4521,7 @@ async function prepareUrlInput(rootDir, input, options) {
4439
4521
  sourceKind = "markdown";
4440
4522
  storedExtension = ".md";
4441
4523
  } else {
4442
- const extension = path9.extname(inputUrl.pathname);
4524
+ const extension = path10.extname(inputUrl.pathname);
4443
4525
  storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
4444
4526
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
4445
4527
  extractedText = payloadBytes.toString("utf8");
@@ -4512,7 +4594,7 @@ async function collectInboxAttachmentRefs(inputDir, files) {
4512
4594
  }
4513
4595
  const sourceRefs = [];
4514
4596
  for (const ref of refs) {
4515
- const resolved = path9.resolve(path9.dirname(absolutePath), ref);
4597
+ const resolved = path10.resolve(path10.dirname(absolutePath), ref);
4516
4598
  if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
4517
4599
  continue;
4518
4600
  }
@@ -4548,7 +4630,7 @@ function rewriteMarkdownReferences(content, replacements) {
4548
4630
  async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
4549
4631
  const originalBytes = await fs9.readFile(absolutePath);
4550
4632
  const originalText = originalBytes.toString("utf8");
4551
- const title = titleFromText(path9.basename(absolutePath, path9.extname(absolutePath)), originalText);
4633
+ const title = titleFromText(path10.basename(absolutePath, path10.extname(absolutePath)), originalText);
4552
4634
  const attachments = [];
4553
4635
  for (const attachmentRef of attachmentRefs) {
4554
4636
  const bytes = await fs9.readFile(attachmentRef.absolutePath);
@@ -4575,7 +4657,7 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
4575
4657
  sourceKind: "markdown",
4576
4658
  originalPath: toPosix(absolutePath),
4577
4659
  mimeType: "text/markdown",
4578
- storedExtension: path9.extname(absolutePath) || ".md",
4660
+ storedExtension: path10.extname(absolutePath) || ".md",
4579
4661
  payloadBytes: Buffer.from(rewrittenText, "utf8"),
4580
4662
  extractedText: rewrittenText,
4581
4663
  extractionArtifact,
@@ -4590,8 +4672,8 @@ function isSupportedInboxKind(sourceKind) {
4590
4672
  async function ingestInput(rootDir, input, options) {
4591
4673
  const { paths } = await initWorkspace(rootDir);
4592
4674
  const normalizedOptions = normalizeIngestOptions(options);
4593
- const absoluteInput = path9.resolve(rootDir, input);
4594
- const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ?? path9.dirname(absoluteInput));
4675
+ const absoluteInput = path10.resolve(rootDir, input);
4676
+ const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ?? path10.dirname(absoluteInput));
4595
4677
  const prepared = isHttpUrl(input) ? await prepareUrlInput(rootDir, input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
4596
4678
  const result = await persistPreparedInput(rootDir, prepared, paths);
4597
4679
  return result.manifest;
@@ -4680,8 +4762,8 @@ async function addInput(rootDir, input, options = {}) {
4680
4762
  }
4681
4763
  async function ingestDirectory(rootDir, inputDir, options) {
4682
4764
  const { paths } = await initWorkspace(rootDir);
4683
- const normalizedOptions = normalizeIngestOptions(options);
4684
- const absoluteInputDir = path9.resolve(rootDir, inputDir);
4765
+ const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
4766
+ const absoluteInputDir = path10.resolve(rootDir, inputDir);
4685
4767
  const repoRoot = normalizedOptions.repoRoot ?? await findNearestGitRoot2(absoluteInputDir) ?? absoluteInputDir;
4686
4768
  if (!await fileExists(absoluteInputDir)) {
4687
4769
  throw new Error(`Directory not found: ${absoluteInputDir}`);
@@ -4690,18 +4772,19 @@ async function ingestDirectory(rootDir, inputDir, options) {
4690
4772
  const imported = [];
4691
4773
  const updated = [];
4692
4774
  for (const absolutePath of files) {
4693
- const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
4775
+ const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(repoRoot, absolutePath));
4776
+ const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
4694
4777
  const result = await persistPreparedInput(rootDir, prepared, paths);
4695
4778
  if (result.isNew) {
4696
4779
  imported.push(result.manifest);
4697
4780
  } else if (result.wasUpdated) {
4698
4781
  updated.push(result.manifest);
4699
4782
  } else {
4700
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4783
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4701
4784
  }
4702
4785
  }
4703
- await appendLogEntry(rootDir, "ingest_directory", toPosix(path9.relative(rootDir, absoluteInputDir)) || ".", [
4704
- `repo_root=${toPosix(path9.relative(rootDir, repoRoot)) || "."}`,
4786
+ await appendLogEntry(rootDir, "ingest_directory", toPosix(path10.relative(rootDir, absoluteInputDir)) || ".", [
4787
+ `repo_root=${toPosix(path10.relative(rootDir, repoRoot)) || "."}`,
4705
4788
  `scanned=${files.length}`,
4706
4789
  `imported=${imported.length}`,
4707
4790
  `updated=${updated.length}`,
@@ -4718,7 +4801,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
4718
4801
  }
4719
4802
  async function importInbox(rootDir, inputDir) {
4720
4803
  const { paths } = await initWorkspace(rootDir);
4721
- const effectiveInputDir = path9.resolve(rootDir, inputDir ?? paths.inboxDir);
4804
+ const effectiveInputDir = path10.resolve(rootDir, inputDir ?? paths.inboxDir);
4722
4805
  if (!await fileExists(effectiveInputDir)) {
4723
4806
  throw new Error(`Inbox directory not found: ${effectiveInputDir}`);
4724
4807
  }
@@ -4729,31 +4812,31 @@ async function importInbox(rootDir, inputDir) {
4729
4812
  const skipped = [];
4730
4813
  let attachmentCount = 0;
4731
4814
  for (const absolutePath of files) {
4732
- const basename = path9.basename(absolutePath);
4815
+ const basename = path10.basename(absolutePath);
4733
4816
  if (basename.startsWith(".")) {
4734
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "hidden_file" });
4817
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "hidden_file" });
4735
4818
  continue;
4736
4819
  }
4737
4820
  if (claimedAttachments.has(absolutePath)) {
4738
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
4821
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
4739
4822
  continue;
4740
4823
  }
4741
4824
  const mimeType = guessMimeType(absolutePath);
4742
4825
  const sourceKind = inferKind(mimeType, absolutePath);
4743
4826
  if (!isSupportedInboxKind(sourceKind)) {
4744
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
4827
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
4745
4828
  continue;
4746
4829
  }
4747
4830
  const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
4748
4831
  const result = await persistPreparedInput(rootDir, prepared, paths);
4749
4832
  if (!result.isNew) {
4750
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4833
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4751
4834
  continue;
4752
4835
  }
4753
4836
  attachmentCount += result.manifest.attachments?.length ?? 0;
4754
4837
  imported.push(result.manifest);
4755
4838
  }
4756
- await appendLogEntry(rootDir, "inbox_import", toPosix(path9.relative(rootDir, effectiveInputDir)) || ".", [
4839
+ await appendLogEntry(rootDir, "inbox_import", toPosix(path10.relative(rootDir, effectiveInputDir)) || ".", [
4757
4840
  `scanned=${files.length}`,
4758
4841
  `imported=${imported.length}`,
4759
4842
  `attachments=${attachmentCount}`,
@@ -4774,7 +4857,7 @@ async function listManifests(rootDir) {
4774
4857
  }
4775
4858
  const entries = await fs9.readdir(paths.manifestsDir);
4776
4859
  const manifests = await Promise.all(
4777
- entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path9.join(paths.manifestsDir, entry)))
4860
+ entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path10.join(paths.manifestsDir, entry)))
4778
4861
  );
4779
4862
  return manifests.filter((manifest) => Boolean(manifest));
4780
4863
  }
@@ -4782,7 +4865,7 @@ async function readExtractedText(rootDir, manifest) {
4782
4865
  if (!manifest.extractedTextPath) {
4783
4866
  return void 0;
4784
4867
  }
4785
- const absolutePath = path9.resolve(rootDir, manifest.extractedTextPath);
4868
+ const absolutePath = path10.resolve(rootDir, manifest.extractedTextPath);
4786
4869
  if (!await fileExists(absolutePath)) {
4787
4870
  return void 0;
4788
4871
  }
@@ -4792,7 +4875,7 @@ async function readExtractionArtifact(rootDir, manifest) {
4792
4875
  if (!manifest.extractedMetadataPath) {
4793
4876
  return void 0;
4794
4877
  }
4795
- const absolutePath = path9.resolve(rootDir, manifest.extractedMetadataPath);
4878
+ const absolutePath = path10.resolve(rootDir, manifest.extractedMetadataPath);
4796
4879
  if (!await fileExists(absolutePath)) {
4797
4880
  return void 0;
4798
4881
  }
@@ -4800,15 +4883,15 @@ async function readExtractionArtifact(rootDir, manifest) {
4800
4883
  }
4801
4884
 
4802
4885
  // src/mcp.ts
4803
- import fs16 from "fs/promises";
4804
- import path19 from "path";
4886
+ import fs17 from "fs/promises";
4887
+ import path21 from "path";
4805
4888
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4806
4889
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4807
4890
  import { z as z8 } from "zod";
4808
4891
 
4809
4892
  // src/schema.ts
4810
4893
  import fs10 from "fs/promises";
4811
- import path10 from "path";
4894
+ import path11 from "path";
4812
4895
  function normalizeSchemaContent(content) {
4813
4896
  return content.trim() ? content.trim() : defaultVaultSchema().trim();
4814
4897
  }
@@ -4822,7 +4905,7 @@ async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
4822
4905
  };
4823
4906
  }
4824
4907
  function resolveProjectSchemaPath(rootDir, schemaPath) {
4825
- return path10.resolve(rootDir, schemaPath);
4908
+ return path11.resolve(rootDir, schemaPath);
4826
4909
  }
4827
4910
  function composeVaultSchema(root, projectSchemas = []) {
4828
4911
  if (!projectSchemas.length) {
@@ -4838,7 +4921,7 @@ function composeVaultSchema(root, projectSchemas = []) {
4838
4921
  (schema) => [
4839
4922
  `## Project Schema`,
4840
4923
  "",
4841
- `Path: ${toPosix(path10.relative(path10.dirname(root.path), schema.path) || schema.path)}`,
4924
+ `Path: ${toPosix(path11.relative(path11.dirname(root.path), schema.path) || schema.path)}`,
4842
4925
  "",
4843
4926
  schema.content
4844
4927
  ].join("\n")
@@ -4914,13 +4997,13 @@ function buildSchemaPrompt(schema, instruction) {
4914
4997
  }
4915
4998
 
4916
4999
  // src/vault.ts
4917
- import fs15 from "fs/promises";
4918
- import path18 from "path";
5000
+ import fs16 from "fs/promises";
5001
+ import path20 from "path";
4919
5002
  import matter9 from "gray-matter";
4920
5003
  import { z as z7 } from "zod";
4921
5004
 
4922
5005
  // src/analysis.ts
4923
- import path11 from "path";
5006
+ import path12 from "path";
4924
5007
  import { z as z2 } from "zod";
4925
5008
  var ANALYSIS_FORMAT_VERSION = 5;
4926
5009
  var sourceAnalysisSchema = z2.object({
@@ -5143,7 +5226,7 @@ function extractionWarningSummary(manifest, extraction) {
5143
5226
  return `Imported ${manifest.sourceKind} source. Text extraction is not yet available for this source.`;
5144
5227
  }
5145
5228
  async function analyzeSource(manifest, extractedText, provider, paths, schema) {
5146
- const cachePath = path11.join(paths.analysesDir, `${manifest.sourceId}.json`);
5229
+ const cachePath = path12.join(paths.analysesDir, `${manifest.sourceId}.json`);
5147
5230
  const cached = await readJsonFile(cachePath);
5148
5231
  if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
5149
5232
  return cached;
@@ -5286,6 +5369,7 @@ function graphHash(graph) {
5286
5369
  type: node.type,
5287
5370
  label: node.label,
5288
5371
  pageId: node.pageId ?? null,
5372
+ sourceClass: node.sourceClass ?? null,
5289
5373
  communityId: node.communityId ?? null,
5290
5374
  degree: node.degree ?? null,
5291
5375
  bridgeScore: node.bridgeScore ?? null,
@@ -5300,6 +5384,7 @@ function graphHash(graph) {
5300
5384
  relation: edge.relation,
5301
5385
  status: edge.status,
5302
5386
  evidenceClass: edge.evidenceClass,
5387
+ similarityBasis: edge.similarityBasis ?? null,
5303
5388
  confidence: edge.confidence,
5304
5389
  provenance: [...edge.provenance].sort()
5305
5390
  })).sort((left, right) => left.id.localeCompare(right.id)),
@@ -5309,6 +5394,7 @@ function graphHash(graph) {
5309
5394
  kind: page.kind,
5310
5395
  status: page.status,
5311
5396
  sourceType: page.sourceType ?? null,
5397
+ sourceClass: page.sourceClass ?? null,
5312
5398
  sourceIds: [...page.sourceIds].sort(),
5313
5399
  projectIds: [...page.projectIds].sort(),
5314
5400
  nodeIds: [...page.nodeIds].sort()
@@ -5386,7 +5472,7 @@ function conflictConfidence(claimA, claimB) {
5386
5472
 
5387
5473
  // src/deep-lint.ts
5388
5474
  import fs11 from "fs/promises";
5389
- import path14 from "path";
5475
+ import path15 from "path";
5390
5476
  import matter4 from "gray-matter";
5391
5477
  import { z as z5 } from "zod";
5392
5478
 
@@ -5407,7 +5493,7 @@ function normalizeFindingSeverity(value) {
5407
5493
 
5408
5494
  // src/orchestration.ts
5409
5495
  import { spawn } from "child_process";
5410
- import path12 from "path";
5496
+ import path13 from "path";
5411
5497
  import { z as z3 } from "zod";
5412
5498
  var orchestrationRoleResultSchema = z3.object({
5413
5499
  summary: z3.string().optional(),
@@ -5500,7 +5586,7 @@ async function runProviderRole(rootDir, role, roleConfig, input) {
5500
5586
  }
5501
5587
  async function runCommandRole(rootDir, role, executor, input) {
5502
5588
  const [command, ...args] = executor.command;
5503
- const cwd = executor.cwd ? path12.resolve(rootDir, executor.cwd) : rootDir;
5589
+ const cwd = executor.cwd ? path13.resolve(rootDir, executor.cwd) : rootDir;
5504
5590
  const child = spawn(command, args, {
5505
5591
  cwd,
5506
5592
  env: {
@@ -5594,7 +5680,7 @@ function summarizeRoleQuestions(results) {
5594
5680
  }
5595
5681
 
5596
5682
  // src/web-search/registry.ts
5597
- import path13 from "path";
5683
+ import path14 from "path";
5598
5684
  import { pathToFileURL } from "url";
5599
5685
  import { z as z4 } from "zod";
5600
5686
 
@@ -5692,7 +5778,7 @@ async function createWebSearchAdapter(id, config, rootDir) {
5692
5778
  if (!config.module) {
5693
5779
  throw new Error(`Web search provider ${id} is type "custom" but no module path was configured.`);
5694
5780
  }
5695
- const resolvedModule = path13.isAbsolute(config.module) ? config.module : path13.resolve(rootDir, config.module);
5781
+ const resolvedModule = path14.isAbsolute(config.module) ? config.module : path14.resolve(rootDir, config.module);
5696
5782
  const loaded = await import(pathToFileURL(resolvedModule).href);
5697
5783
  const parsed = customWebSearchModuleSchema.parse(loaded);
5698
5784
  return parsed.createAdapter(id, config, rootDir);
@@ -5752,7 +5838,7 @@ async function loadContextPages(rootDir, graph) {
5752
5838
  );
5753
5839
  return Promise.all(
5754
5840
  contextPages.slice(0, 18).map(async (page) => {
5755
- const absolutePath = path14.join(paths.wikiDir, page.path);
5841
+ const absolutePath = path15.join(paths.wikiDir, page.path);
5756
5842
  const raw = await fs11.readFile(absolutePath, "utf8").catch(() => "");
5757
5843
  const parsed = matter4(raw);
5758
5844
  return {
@@ -5801,7 +5887,7 @@ function heuristicDeepFindings(contextPages, structuralFindings, graph) {
5801
5887
  code: "missing_citation",
5802
5888
  message: finding.message,
5803
5889
  pagePath: finding.pagePath,
5804
- suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path14.basename(finding.pagePath, ".md")}?` : void 0
5890
+ suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path15.basename(finding.pagePath, ".md")}?` : void 0
5805
5891
  });
5806
5892
  }
5807
5893
  for (const page of contextPages.filter((item) => item.kind === "source").slice(0, 3)) {
@@ -5978,6 +6064,353 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
5978
6064
  );
5979
6065
  }
5980
6066
 
6067
+ // src/embeddings.ts
6068
+ import fs12 from "fs/promises";
6069
+ import path16 from "path";
6070
+ var MAX_EMBEDDING_BATCH = 32;
6071
+ var MAX_SIMILARITY_NODES = 240;
6072
+ function cosineSimilarity(left, right) {
6073
+ if (!left.length || left.length !== right.length) {
6074
+ return 0;
6075
+ }
6076
+ let dot = 0;
6077
+ let leftNorm = 0;
6078
+ let rightNorm = 0;
6079
+ for (let index = 0; index < left.length; index += 1) {
6080
+ dot += left[index] * right[index];
6081
+ leftNorm += left[index] * left[index];
6082
+ rightNorm += right[index] * right[index];
6083
+ }
6084
+ if (leftNorm <= 0 || rightNorm <= 0) {
6085
+ return 0;
6086
+ }
6087
+ return dot / Math.sqrt(leftNorm * rightNorm);
6088
+ }
6089
+ function appendIfMissing(parts, value) {
6090
+ const normalized = value?.trim();
6091
+ if (!normalized) {
6092
+ return;
6093
+ }
6094
+ if (!parts.includes(normalized)) {
6095
+ parts.push(normalized);
6096
+ }
6097
+ }
6098
+ async function loadPageContents(rootDir, graph) {
6099
+ const { paths } = await loadVaultConfig(rootDir);
6100
+ const contents = /* @__PURE__ */ new Map();
6101
+ await Promise.all(
6102
+ graph.pages.map(async (page) => {
6103
+ const absolutePath = path16.join(paths.wikiDir, page.path);
6104
+ const content = await fs12.readFile(absolutePath, "utf8").catch(() => "");
6105
+ contents.set(page.id, content);
6106
+ })
6107
+ );
6108
+ return contents;
6109
+ }
6110
+ function itemTextForNode(node, graph, pageContents) {
6111
+ const page = graph.pages.find((candidate) => candidate.id === node.pageId);
6112
+ const parts = [`node ${node.type}`, node.label];
6113
+ appendIfMissing(parts, node.sourceClass);
6114
+ appendIfMissing(parts, node.language);
6115
+ appendIfMissing(parts, page?.title);
6116
+ appendIfMissing(parts, page?.sourceType);
6117
+ appendIfMissing(parts, page?.sourceClass);
6118
+ if (page) {
6119
+ appendIfMissing(parts, pageContents.get(page.id)?.slice(0, 800));
6120
+ }
6121
+ return parts.join("\n");
6122
+ }
6123
+ function itemTextForPage(page, pageContents) {
6124
+ const parts = [`page ${page.kind}`, page.title, page.path];
6125
+ appendIfMissing(parts, page.sourceType);
6126
+ appendIfMissing(parts, page.sourceClass);
6127
+ appendIfMissing(parts, pageContents.get(page.id)?.slice(0, 1200));
6128
+ return parts.join("\n");
6129
+ }
6130
+ function itemTextForHyperedge(graph, hyperedgeId) {
6131
+ const hyperedge = graph.hyperedges.find((candidate) => candidate.id === hyperedgeId);
6132
+ if (!hyperedge) {
6133
+ return "";
6134
+ }
6135
+ const nodeLabels = hyperedge.nodeIds.map((nodeId) => graph.nodes.find((node) => node.id === nodeId)?.label).filter((value) => Boolean(value));
6136
+ return [hyperedge.label, hyperedge.relation, hyperedge.why, ...nodeLabels].join("\n");
6137
+ }
6138
+ async function buildEmbeddableItems(rootDir, graph) {
6139
+ const pageContents = await loadPageContents(rootDir, graph);
6140
+ return uniqueBy(
6141
+ [
6142
+ ...graph.nodes.map(
6143
+ (node) => ({
6144
+ id: node.id,
6145
+ kind: "node",
6146
+ label: node.label,
6147
+ text: itemTextForNode(node, graph, pageContents),
6148
+ match: {
6149
+ type: "node",
6150
+ id: node.id,
6151
+ label: node.label,
6152
+ score: 0
6153
+ }
6154
+ })
6155
+ ),
6156
+ ...graph.pages.map(
6157
+ (page) => ({
6158
+ id: page.id,
6159
+ kind: "page",
6160
+ label: page.title,
6161
+ text: itemTextForPage(page, pageContents),
6162
+ match: {
6163
+ type: "page",
6164
+ id: page.id,
6165
+ label: page.title,
6166
+ score: 0
6167
+ }
6168
+ })
6169
+ ),
6170
+ ...(graph.hyperedges ?? []).map(
6171
+ (hyperedge) => ({
6172
+ id: hyperedge.id,
6173
+ kind: "hyperedge",
6174
+ label: hyperedge.label,
6175
+ text: itemTextForHyperedge(graph, hyperedge.id),
6176
+ match: {
6177
+ type: "hyperedge",
6178
+ id: hyperedge.id,
6179
+ label: hyperedge.label,
6180
+ score: 0
6181
+ }
6182
+ })
6183
+ )
6184
+ ],
6185
+ (item) => `${item.kind}:${item.id}`
6186
+ ).filter((item) => item.text.trim().length > 0);
6187
+ }
6188
+ async function resolveEmbeddingProvider(rootDir) {
6189
+ const { config } = await loadVaultConfig(rootDir);
6190
+ const explicitProviderId = config.tasks.embeddingProvider;
6191
+ if (explicitProviderId) {
6192
+ const providerConfig = config.providers[explicitProviderId];
6193
+ if (!providerConfig) {
6194
+ throw new Error(`No provider configured with id "${explicitProviderId}" for task "embeddingProvider".`);
6195
+ }
6196
+ const provider2 = await createProvider(explicitProviderId, providerConfig, rootDir);
6197
+ if (!provider2.capabilities.has("embeddings") || typeof provider2.embedTexts !== "function") {
6198
+ throw new Error(`Provider ${provider2.id} does not support required capability "embeddings".`);
6199
+ }
6200
+ return provider2;
6201
+ }
6202
+ const queryProviderId = config.tasks.queryProvider;
6203
+ const queryProviderConfig = config.providers[queryProviderId];
6204
+ if (!queryProviderConfig) {
6205
+ return null;
6206
+ }
6207
+ const provider = await createProvider(queryProviderId, queryProviderConfig, rootDir);
6208
+ return provider.capabilities.has("embeddings") && typeof provider.embedTexts === "function" ? provider : null;
6209
+ }
6210
+ async function readEmbeddingCache(rootDir) {
6211
+ const { paths } = await loadVaultConfig(rootDir);
6212
+ const provider = await resolveEmbeddingProvider(rootDir);
6213
+ if (!provider) {
6214
+ return { artifact: null, provider: null };
6215
+ }
6216
+ const cache = await readJsonFile(paths.embeddingsPath);
6217
+ if (!cache || cache.providerId !== provider.id || cache.providerModel !== provider.model) {
6218
+ return { artifact: null, provider };
6219
+ }
6220
+ return { artifact: cache, provider };
6221
+ }
6222
+ async function writeEmbeddingCache(rootDir, provider, graphHash2, entries) {
6223
+ const { paths } = await loadVaultConfig(rootDir);
6224
+ await writeJsonFile(paths.embeddingsPath, {
6225
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6226
+ providerId: provider.id,
6227
+ providerModel: provider.model,
6228
+ graphHash: graphHash2,
6229
+ entries: entries.sort((left, right) => `${left.kind}:${left.itemId}`.localeCompare(`${right.kind}:${right.itemId}`))
6230
+ });
6231
+ }
6232
+ async function embedTexts(provider, texts) {
6233
+ const vectors = [];
6234
+ for (let index = 0; index < texts.length; index += MAX_EMBEDDING_BATCH) {
6235
+ const batch = texts.slice(index, index + MAX_EMBEDDING_BATCH);
6236
+ const nextVectors = await provider.embedTexts(batch);
6237
+ vectors.push(...nextVectors);
6238
+ }
6239
+ return vectors;
6240
+ }
6241
+ async function resolveVectorsForItems(rootDir, graphHash2, items) {
6242
+ const { artifact, provider } = await readEmbeddingCache(rootDir);
6243
+ if (!provider) {
6244
+ return { provider: null, vectors: /* @__PURE__ */ new Map() };
6245
+ }
6246
+ const cachedByKey = new Map(
6247
+ (artifact?.entries ?? []).map((entry) => [`${entry.kind}:${entry.itemId}:${entry.contentHash}`, entry.values])
6248
+ );
6249
+ const vectors = /* @__PURE__ */ new Map();
6250
+ const missing = [];
6251
+ for (const item of items) {
6252
+ const contentHash = sha256(item.text);
6253
+ const cached = cachedByKey.get(`${item.kind}:${item.id}:${contentHash}`);
6254
+ if (cached?.length) {
6255
+ vectors.set(`${item.kind}:${item.id}`, cached);
6256
+ } else {
6257
+ missing.push(item);
6258
+ }
6259
+ }
6260
+ if (missing.length) {
6261
+ const nextVectors = await embedTexts(
6262
+ provider,
6263
+ missing.map((item) => item.text)
6264
+ );
6265
+ for (let index = 0; index < missing.length; index += 1) {
6266
+ vectors.set(`${missing[index].kind}:${missing[index].id}`, nextVectors[index] ?? []);
6267
+ }
6268
+ }
6269
+ await writeEmbeddingCache(
6270
+ rootDir,
6271
+ provider,
6272
+ graphHash2,
6273
+ items.map((item) => ({
6274
+ itemId: item.id,
6275
+ kind: item.kind,
6276
+ label: item.label,
6277
+ contentHash: sha256(item.text),
6278
+ values: vectors.get(`${item.kind}:${item.id}`) ?? []
6279
+ }))
6280
+ );
6281
+ return { provider, vectors };
6282
+ }
6283
+ async function semanticGraphMatches(rootDir, graph, question, limit = 12) {
6284
+ const items = await buildEmbeddableItems(rootDir, graph);
6285
+ const { provider, vectors } = await resolveVectorsForItems(rootDir, graph.generatedAt, items);
6286
+ if (!provider) {
6287
+ return [];
6288
+ }
6289
+ const [queryVector] = await provider.embedTexts([question]);
6290
+ if (!Array.isArray(queryVector) || queryVector.length === 0) {
6291
+ return [];
6292
+ }
6293
+ return items.map((item) => ({
6294
+ ...item.match,
6295
+ score: Math.max(0, Number((cosineSimilarity(queryVector, vectors.get(`${item.kind}:${item.id}`) ?? []) * 100).toFixed(2)))
6296
+ })).filter((match) => match.score >= 18).sort((left, right) => right.score - left.score || left.label.localeCompare(right.label)).slice(0, limit);
6297
+ }
6298
+ function distinctScope(left, right) {
6299
+ const leftSources = new Set(left.sourceIds);
6300
+ const rightSources = new Set(right.sourceIds);
6301
+ return [...leftSources].some((sourceId) => !rightSources.has(sourceId)) || [...rightSources].some((sourceId) => !leftSources.has(sourceId));
6302
+ }
6303
+ function nodePairKey(left, right) {
6304
+ return [left, right].sort((a, b) => a.localeCompare(b)).join("|");
6305
+ }
6306
+ function similarityReasonsForNodes(left, right) {
6307
+ const reasons = /* @__PURE__ */ new Set();
6308
+ if (left.sourceClass && right.sourceClass && left.sourceClass === right.sourceClass) {
6309
+ reasons.add("shared_tag");
6310
+ }
6311
+ if (left.language && right.language && left.language === right.language) {
6312
+ reasons.add("shared_symbol");
6313
+ }
6314
+ return [...reasons].sort((a, b) => a.localeCompare(b));
6315
+ }
6316
+ async function embeddingSimilarityEdges(rootDir, graph) {
6317
+ const candidateNodes = graph.nodes.filter(
6318
+ (node) => (node.type === "source" || node.type === "module" || node.type === "rationale") && node.sourceClass !== "generated"
6319
+ );
6320
+ if (candidateNodes.length < 2 || candidateNodes.length > MAX_SIMILARITY_NODES) {
6321
+ return [];
6322
+ }
6323
+ const items = candidateNodes.map(
6324
+ (node) => ({
6325
+ id: node.id,
6326
+ kind: "node",
6327
+ label: node.label,
6328
+ text: [
6329
+ node.label,
6330
+ node.type,
6331
+ node.sourceClass ?? "",
6332
+ node.language ?? "",
6333
+ graph.pages.find((page) => page.id === node.pageId)?.title ?? ""
6334
+ ].filter(Boolean).join("\n"),
6335
+ match: { type: "node", id: node.id, label: node.label, score: 0 }
6336
+ })
6337
+ );
6338
+ const { provider, vectors } = await resolveVectorsForItems(rootDir, graph.generatedAt, items);
6339
+ if (!provider) {
6340
+ return [];
6341
+ }
6342
+ const directPairs = new Set(graph.edges.map((edge) => nodePairKey(edge.source, edge.target)));
6343
+ const edges = [];
6344
+ for (let leftIndex = 0; leftIndex < candidateNodes.length; leftIndex += 1) {
6345
+ const left = candidateNodes[leftIndex];
6346
+ const leftVector = vectors.get(`node:${left.id}`) ?? [];
6347
+ const candidates = candidateNodes.slice(leftIndex + 1).filter((right) => distinctScope(left, right) && !directPairs.has(nodePairKey(left.id, right.id))).map((right) => ({
6348
+ right,
6349
+ score: cosineSimilarity(leftVector, vectors.get(`node:${right.id}`) ?? [])
6350
+ })).filter((candidate) => candidate.score >= 0.82).sort((a, b) => b.score - a.score).slice(0, 3);
6351
+ for (const candidate of candidates) {
6352
+ const right = candidate.right;
6353
+ const reasons = similarityReasonsForNodes(left, right) ?? [];
6354
+ edges.push({
6355
+ id: `similar-embed:${sha256(`${left.id}|${right.id}|${provider.id}`).slice(0, 16)}`,
6356
+ source: left.id,
6357
+ target: right.id,
6358
+ relation: "semantically_similar_to",
6359
+ status: "inferred",
6360
+ evidenceClass: "inferred",
6361
+ confidence: Number(candidate.score.toFixed(3)),
6362
+ provenance: uniqueBy(
6363
+ [...left.sourceIds, ...right.sourceIds].sort((a, b) => a.localeCompare(b)),
6364
+ (value) => value
6365
+ ),
6366
+ similarityReasons: reasons.length ? reasons : ["shared_tag"],
6367
+ similarityBasis: "embeddings"
6368
+ });
6369
+ }
6370
+ }
6371
+ return uniqueBy(edges, (edge) => edge.id).sort((left, right) => right.confidence - left.confidence || left.id.localeCompare(right.id));
6372
+ }
6373
+ function sourceClassBreakdown(graph) {
6374
+ return {
6375
+ first_party: {
6376
+ sources: graph.sources.filter((source) => source.sourceClass === "first_party").length,
6377
+ pages: graph.pages.filter((page) => page.sourceClass === "first_party").length,
6378
+ nodes: graph.nodes.filter((node) => node.sourceClass === "first_party").length
6379
+ },
6380
+ third_party: {
6381
+ sources: graph.sources.filter((source) => source.sourceClass === "third_party").length,
6382
+ pages: graph.pages.filter((page) => page.sourceClass === "third_party").length,
6383
+ nodes: graph.nodes.filter((node) => node.sourceClass === "third_party").length
6384
+ },
6385
+ resource: {
6386
+ sources: graph.sources.filter((source) => source.sourceClass === "resource").length,
6387
+ pages: graph.pages.filter((page) => page.sourceClass === "resource").length,
6388
+ nodes: graph.nodes.filter((node) => node.sourceClass === "resource").length
6389
+ },
6390
+ generated: {
6391
+ sources: graph.sources.filter((source) => source.sourceClass === "generated").length,
6392
+ pages: graph.pages.filter((page) => page.sourceClass === "generated").length,
6393
+ nodes: graph.nodes.filter((node) => node.sourceClass === "generated").length
6394
+ }
6395
+ };
6396
+ }
6397
+ function filterGraphBySourceClass(graph, sourceClass) {
6398
+ const nodeIds = new Set(graph.nodes.filter((node) => node.sourceClass === sourceClass).map((node) => node.id));
6399
+ const pageIds = new Set(graph.pages.filter((page) => page.sourceClass === sourceClass).map((page) => page.id));
6400
+ return {
6401
+ ...graph,
6402
+ nodes: graph.nodes.filter((node) => nodeIds.has(node.id)),
6403
+ edges: graph.edges.filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target)),
6404
+ hyperedges: graph.hyperedges.filter((hyperedge) => hyperedge.nodeIds.every((nodeId) => nodeIds.has(nodeId))),
6405
+ communities: (graph.communities ?? []).map((community) => ({
6406
+ ...community,
6407
+ nodeIds: community.nodeIds.filter((nodeId) => nodeIds.has(nodeId))
6408
+ })).filter((community) => community.nodeIds.length > 0),
6409
+ sources: graph.sources.filter((source) => source.sourceClass === sourceClass),
6410
+ pages: graph.pages.filter((page) => pageIds.has(page.id))
6411
+ };
6412
+ }
6413
+
5981
6414
  // src/graph-enrichment.ts
5982
6415
  var STOPWORDS2 = /* @__PURE__ */ new Set([
5983
6416
  "about",
@@ -6204,7 +6637,8 @@ function buildSemanticSimilarityEdges(nodes, edges, manifests, analyses) {
6204
6637
  [...left.sourceIds, ...right.sourceIds].sort((a, b) => a.localeCompare(b)),
6205
6638
  (value) => value
6206
6639
  ),
6207
- similarityReasons: [...reasons.keys()].sort((a, b) => a.localeCompare(b))
6640
+ similarityReasons: [...reasons.keys()].sort((a, b) => a.localeCompare(b)),
6641
+ similarityBasis: "feature_overlap"
6208
6642
  }
6209
6643
  ];
6210
6644
  }).sort((left, right) => right.confidence - left.confidence || left.id.localeCompare(right.id));
@@ -6287,9 +6721,11 @@ function buildModuleFormHyperedges(graph) {
6287
6721
  ];
6288
6722
  });
6289
6723
  }
6290
- function enrichGraph(graph, manifests, analyses) {
6724
+ function enrichGraph(graph, manifests, analyses, extraSimilarityEdges = []) {
6291
6725
  const similarityEdges = buildSemanticSimilarityEdges(graph.nodes, graph.edges, manifests, analyses);
6292
- const enrichedEdges = [...graph.edges, ...similarityEdges].sort((left, right) => left.id.localeCompare(right.id));
6726
+ const enrichedEdges = uniqueBy([...graph.edges, ...similarityEdges, ...extraSimilarityEdges], (edge) => edge.id).sort(
6727
+ (left, right) => left.id.localeCompare(right.id)
6728
+ );
6293
6729
  const hyperedges = uniqueBy(
6294
6730
  [
6295
6731
  ...buildTopicHyperedges({ ...graph, edges: enrichedEdges, hyperedges: [] }),
@@ -6420,13 +6856,19 @@ function queryGraph(graph, question, searchResults, options) {
6420
6856
  const traversal = options?.traversal ?? "bfs";
6421
6857
  const budget = Math.max(3, Math.min(options?.budget ?? 12, 50));
6422
6858
  const matches = uniqueBy(
6423
- [...pageSearchMatches(graph, question, searchResults), ...nodeMatches(graph, question), ...hyperedgeMatches(graph, question)],
6859
+ [
6860
+ ...options?.semanticMatches ?? [],
6861
+ ...pageSearchMatches(graph, question, searchResults),
6862
+ ...nodeMatches(graph, question),
6863
+ ...hyperedgeMatches(graph, question)
6864
+ ],
6424
6865
  (match) => `${match.type}:${match.id}`
6425
6866
  ).sort((left, right) => right.score - left.score || left.label.localeCompare(right.label)).slice(0, 12);
6426
6867
  const pages = pageById(graph);
6427
6868
  const seeds = uniqueBy(
6428
6869
  [
6429
6870
  ...searchResults.flatMap((result) => pages.get(result.pageId)?.nodeIds ?? []),
6871
+ ...matches.filter((match) => match.type === "page").flatMap((match) => pages.get(match.id)?.nodeIds ?? []),
6430
6872
  ...matches.filter((match) => match.type === "node").map((match) => match.id),
6431
6873
  ...matches.filter((match) => match.type === "hyperedge").flatMap((match) => graph.hyperedges.find((hyperedge) => hyperedge.id === match.id)?.nodeIds ?? [])
6432
6874
  ],
@@ -6458,6 +6900,7 @@ function queryGraph(graph, question, searchResults, options) {
6458
6900
  const pageIds = uniqueBy(
6459
6901
  [
6460
6902
  ...searchResults.map((result) => result.pageId),
6903
+ ...matches.filter((match) => match.type === "page").map((match) => match.id),
6461
6904
  ...visitedNodeIds.flatMap((nodeId) => {
6462
6905
  const node = nodes.get(nodeId);
6463
6906
  return node?.pageId ? [node.pageId] : [];
@@ -6478,7 +6921,7 @@ function queryGraph(graph, question, searchResults, options) {
6478
6921
  traversal,
6479
6922
  seedNodeIds: seeds,
6480
6923
  seedPageIds: uniqueBy(
6481
- searchResults.map((result) => result.pageId),
6924
+ [...searchResults.map((result) => result.pageId), ...matches.filter((match) => match.type === "page").map((match) => match.id)],
6482
6925
  (item) => item
6483
6926
  ),
6484
6927
  visitedNodeIds,
@@ -6729,6 +7172,7 @@ function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutput
6729
7172
  kind: "source",
6730
7173
  title: analysis.title,
6731
7174
  ...manifest.sourceType ? { source_type: manifest.sourceType } : {},
7175
+ ...manifest.sourceClass ? { source_class: manifest.sourceClass } : {},
6732
7176
  tags: decoratedTags(analysis.code ? ["source", "code"] : ["source"], decorations),
6733
7177
  source_ids: [manifest.sourceId],
6734
7178
  project_ids: decorations?.projectIds ?? [],
@@ -6752,6 +7196,7 @@ function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutput
6752
7196
  `Source ID: \`${manifest.sourceId}\``,
6753
7197
  manifest.url ? `Source URL: ${manifest.url}` : `Source Path: \`${manifest.originalPath ?? manifest.storedPath}\``,
6754
7198
  ...manifest.sourceType ? [`Source Type: \`${manifest.sourceType}\``, ""] : [""],
7199
+ ...manifest.sourceClass ? [`Source Class: \`${manifest.sourceClass}\``, ""] : [],
6755
7200
  "",
6756
7201
  "## Summary",
6757
7202
  "",
@@ -6797,6 +7242,7 @@ function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutput
6797
7242
  title: analysis.title,
6798
7243
  kind: "source",
6799
7244
  sourceType: manifest.sourceType,
7245
+ sourceClass: manifest.sourceClass,
6800
7246
  sourceIds: [manifest.sourceId],
6801
7247
  projectIds: decorations?.projectIds ?? [],
6802
7248
  nodeIds,
@@ -6859,6 +7305,7 @@ function buildModulePage(input) {
6859
7305
  page_id: pageId,
6860
7306
  kind: "module",
6861
7307
  title,
7308
+ ...manifest.sourceClass ? { source_class: manifest.sourceClass } : {},
6862
7309
  tags: decoratedTags(["module", "code", code.language], { projectIds: input.projectIds, extraTags: input.extraTags }),
6863
7310
  source_ids: [manifest.sourceId],
6864
7311
  project_ids: input.projectIds ?? [],
@@ -6890,6 +7337,7 @@ function buildModulePage(input) {
6890
7337
  `Source ID: \`${manifest.sourceId}\``,
6891
7338
  `Source Path: \`${manifest.originalPath ?? manifest.storedPath}\``,
6892
7339
  ...manifest.repoRelativePath ? [`Repo Path: \`${manifest.repoRelativePath}\``] : [],
7340
+ ...manifest.sourceClass ? [`Source Class: \`${manifest.sourceClass}\``] : [],
6893
7341
  `Language: \`${code.language}\``,
6894
7342
  ...code.moduleName ? [`Module Name: \`${code.moduleName}\``] : [],
6895
7343
  ...code.namespace ? [`Namespace/Package: \`${code.namespace}\``] : [],
@@ -6940,6 +7388,7 @@ function buildModulePage(input) {
6940
7388
  path: relativePath,
6941
7389
  title,
6942
7390
  kind: "module",
7391
+ sourceClass: manifest.sourceClass,
6943
7392
  sourceIds: [manifest.sourceId],
6944
7393
  projectIds: input.projectIds ?? [],
6945
7394
  nodeIds,
@@ -6974,6 +7423,7 @@ function buildAggregatePage(kind, name, descriptions, sourceAnalyses, sourceHash
6974
7423
  page_id: pageId,
6975
7424
  kind,
6976
7425
  title: name,
7426
+ ...decorations?.sourceClass ? { source_class: decorations.sourceClass } : {},
6977
7427
  tags: decoratedTags(metadata.status === "candidate" ? [kind, "candidate"] : [kind], decorations),
6978
7428
  source_ids: sourceIds,
6979
7429
  project_ids: decorations?.projectIds ?? [],
@@ -7015,6 +7465,7 @@ function buildAggregatePage(kind, name, descriptions, sourceAnalyses, sourceHash
7015
7465
  path: relativePath,
7016
7466
  title: name,
7017
7467
  kind,
7468
+ sourceClass: decorations?.sourceClass,
7018
7469
  sourceIds,
7019
7470
  projectIds: decorations?.projectIds ?? [],
7020
7471
  nodeIds: [pageId],
@@ -7146,15 +7597,15 @@ function sourceTypeForNode(node, pagesById) {
7146
7597
  return pagesById.get(node.pageId)?.sourceType;
7147
7598
  }
7148
7599
  function supportingPathDetails(graph, edge) {
7149
- const path23 = shortestGraphPath(graph, edge.source, edge.target);
7600
+ const path25 = shortestGraphPath(graph, edge.source, edge.target);
7150
7601
  const edgesById = new Map(graph.edges.map((item) => [item.id, item]));
7151
- const pathEdges = path23.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
7602
+ const pathEdges = path25.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
7152
7603
  return {
7153
- pathNodeIds: path23.nodeIds,
7154
- pathEdgeIds: path23.edgeIds,
7604
+ pathNodeIds: path25.nodeIds,
7605
+ pathEdgeIds: path25.edgeIds,
7155
7606
  pathRelations: pathEdges.map((item) => item.relation),
7156
7607
  pathEvidenceClasses: pathEdges.map((item) => item.evidenceClass),
7157
- pathSummary: path23.summary
7608
+ pathSummary: path25.summary
7158
7609
  };
7159
7610
  }
7160
7611
  function surpriseScore(edge, graph, pagesById, hyperedgesByNodeId) {
@@ -7223,7 +7674,7 @@ function topSurprisingConnections(graph, pagesById) {
7223
7674
  }).map((edge) => {
7224
7675
  const source = nodesById.get(edge.source);
7225
7676
  const target = nodesById.get(edge.target);
7226
- const path23 = supportingPathDetails(graph, edge);
7677
+ const path25 = supportingPathDetails(graph, edge);
7227
7678
  const scored = surpriseScore(edge, graph, pagesById, hyperedgesByNodeId);
7228
7679
  return {
7229
7680
  id: edge.id,
@@ -7234,11 +7685,11 @@ function topSurprisingConnections(graph, pagesById) {
7234
7685
  relation: edge.relation,
7235
7686
  evidenceClass: edge.evidenceClass,
7236
7687
  confidence: edge.confidence,
7237
- pathNodeIds: path23.pathNodeIds,
7238
- pathEdgeIds: path23.pathEdgeIds,
7239
- pathRelations: path23.pathRelations,
7240
- pathEvidenceClasses: path23.pathEvidenceClasses,
7241
- pathSummary: path23.pathSummary,
7688
+ pathNodeIds: path25.pathNodeIds,
7689
+ pathEdgeIds: path25.pathEdgeIds,
7690
+ pathRelations: path25.pathRelations,
7691
+ pathEvidenceClasses: path25.pathEvidenceClasses,
7692
+ pathSummary: path25.pathSummary,
7242
7693
  why: scored.why,
7243
7694
  explanation: scored.explanation,
7244
7695
  surpriseScore: scored.score
@@ -7263,10 +7714,12 @@ function suggestedGraphQuestions(graph) {
7263
7714
  ]).slice(0, 6);
7264
7715
  }
7265
7716
  function buildGraphReportArtifact(input) {
7266
- const pagesById = new Map(input.graph.pages.map((page) => [page.id, page]));
7267
- const godNodes = input.graph.nodes.filter((node) => node.isGodNode).sort((left, right) => (right.degree ?? 0) - (left.degree ?? 0)).slice(0, 8);
7268
- const bridgeNodes = input.graph.nodes.filter((node) => (node.bridgeScore ?? 0) > 0).sort((left, right) => (right.bridgeScore ?? 0) - (left.bridgeScore ?? 0)).slice(0, 8);
7269
- const thinCommunities = (input.graph.communities ?? []).filter((community) => community.nodeIds.length <= 2).map((community) => {
7717
+ const firstPartyGraph = filterGraphBySourceClass(input.graph, "first_party");
7718
+ const reportGraph = firstPartyGraph.nodes.length ? firstPartyGraph : input.graph;
7719
+ const pagesById = new Map(reportGraph.pages.map((page) => [page.id, page]));
7720
+ const godNodes = reportGraph.nodes.filter((node) => node.isGodNode).sort((left, right) => (right.degree ?? 0) - (left.degree ?? 0)).slice(0, 8);
7721
+ const bridgeNodes = reportGraph.nodes.filter((node) => (node.bridgeScore ?? 0) > 0).sort((left, right) => (right.bridgeScore ?? 0) - (left.bridgeScore ?? 0)).slice(0, 8);
7722
+ const thinCommunities = (reportGraph.communities ?? []).filter((community) => community.nodeIds.length <= 2).map((community) => {
7270
7723
  const page = input.communityPages.find((candidate) => candidate.id === `graph:${community.id}`);
7271
7724
  return {
7272
7725
  id: community.id,
@@ -7277,8 +7730,19 @@ function buildGraphReportArtifact(input) {
7277
7730
  title: page?.title
7278
7731
  };
7279
7732
  });
7280
- const surprisingConnections = topSurprisingConnections(input.graph, pagesById);
7281
- const groupPatterns = topGroupPatterns(input.graph);
7733
+ const surprisingConnections = topSurprisingConnections(reportGraph, pagesById);
7734
+ const groupPatterns = topGroupPatterns(reportGraph);
7735
+ const breakdown = sourceClassBreakdown(input.graph);
7736
+ const warnings = [];
7737
+ const nonFirstPartyNodes = input.graph.nodes.length - breakdown.first_party.nodes;
7738
+ if (input.graph.nodes.length >= 1200) {
7739
+ warnings.push(`Large graph detected (${input.graph.nodes.length} nodes). First-party defaults are applied to report highlights.`);
7740
+ }
7741
+ if (nonFirstPartyNodes > 0 && nonFirstPartyNodes / Math.max(1, input.graph.nodes.length) >= 0.25) {
7742
+ warnings.push(
7743
+ `Non-first-party material accounts for ${(nonFirstPartyNodes / Math.max(1, input.graph.nodes.length) * 100).toFixed(1)}% of graph nodes.`
7744
+ );
7745
+ }
7282
7746
  return {
7283
7747
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7284
7748
  graphHash: input.graphHash,
@@ -7288,6 +7752,14 @@ function buildGraphReportArtifact(input) {
7288
7752
  pages: input.graph.pages.length,
7289
7753
  communities: input.graph.communities?.length ?? 0
7290
7754
  },
7755
+ firstPartyOverview: {
7756
+ nodes: reportGraph.nodes.length,
7757
+ edges: reportGraph.edges.length,
7758
+ pages: reportGraph.pages.length,
7759
+ communities: reportGraph.communities?.length ?? 0
7760
+ },
7761
+ sourceClassBreakdown: breakdown,
7762
+ warnings,
7291
7763
  benchmark: input.benchmark ? {
7292
7764
  generatedAt: input.benchmark.generatedAt,
7293
7765
  stale: input.benchmarkStale ?? false,
@@ -7383,6 +7855,18 @@ function buildGraphReportPage(input) {
7383
7855
  `- Edges: ${input.report.overview.edges}`,
7384
7856
  `- Pages: ${input.report.overview.pages}`,
7385
7857
  `- Communities: ${input.report.overview.communities}`,
7858
+ `- Default Focus: First-party nodes/pages (${input.report.firstPartyOverview.nodes} nodes, ${input.report.firstPartyOverview.edges} edges, ${input.report.firstPartyOverview.pages} pages).`,
7859
+ "",
7860
+ "## Repo Quality Warnings",
7861
+ "",
7862
+ ...input.report.warnings.length ? input.report.warnings.map((warning) => `- ${warning}`) : ["- No large-repo warnings."],
7863
+ "",
7864
+ "## Source Class Breakdown",
7865
+ "",
7866
+ `- First-party: ${input.report.sourceClassBreakdown.first_party.sources} sources, ${input.report.sourceClassBreakdown.first_party.pages} pages, ${input.report.sourceClassBreakdown.first_party.nodes} nodes`,
7867
+ `- Third-party: ${input.report.sourceClassBreakdown.third_party.sources} sources, ${input.report.sourceClassBreakdown.third_party.pages} pages, ${input.report.sourceClassBreakdown.third_party.nodes} nodes`,
7868
+ `- Resources: ${input.report.sourceClassBreakdown.resource.sources} sources, ${input.report.sourceClassBreakdown.resource.pages} pages, ${input.report.sourceClassBreakdown.resource.nodes} nodes`,
7869
+ `- Generated: ${input.report.sourceClassBreakdown.generated.sources} sources, ${input.report.sourceClassBreakdown.generated.pages} pages, ${input.report.sourceClassBreakdown.generated.nodes} nodes`,
7386
7870
  "",
7387
7871
  "## Benchmark Summary",
7388
7872
  "",
@@ -8090,13 +8574,13 @@ function buildOutputAssetManifest(input) {
8090
8574
  }
8091
8575
 
8092
8576
  // src/outputs.ts
8093
- import fs13 from "fs/promises";
8094
- import path16 from "path";
8577
+ import fs14 from "fs/promises";
8578
+ import path18 from "path";
8095
8579
  import matter7 from "gray-matter";
8096
8580
 
8097
8581
  // src/pages.ts
8098
- import fs12 from "fs/promises";
8099
- import path15 from "path";
8582
+ import fs13 from "fs/promises";
8583
+ import path17 from "path";
8100
8584
  import matter6 from "gray-matter";
8101
8585
  function normalizeStringArray(value) {
8102
8586
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
@@ -8121,6 +8605,9 @@ function normalizePageManager(value, fallback = "system") {
8121
8605
  function normalizeSourceType(value) {
8122
8606
  return value === "arxiv" || value === "doi" || value === "tweet" || value === "article" || value === "url" ? value : void 0;
8123
8607
  }
8608
+ function normalizeSourceClass(value) {
8609
+ return value === "first_party" || value === "third_party" || value === "resource" || value === "generated" ? value : void 0;
8610
+ }
8124
8611
  function normalizeOutputFormat(value, fallback = "markdown") {
8125
8612
  return value === "report" || value === "slides" || value === "chart" || value === "image" ? value : fallback;
8126
8613
  }
@@ -8171,7 +8658,7 @@ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
8171
8658
  updatedAt: updatedFallback
8172
8659
  };
8173
8660
  }
8174
- const content = await fs12.readFile(absolutePath, "utf8");
8661
+ const content = await fs13.readFile(absolutePath, "utf8");
8175
8662
  const parsed = matter6(content);
8176
8663
  return {
8177
8664
  status: normalizePageStatus(parsed.data.status, defaults.status ?? "active"),
@@ -8210,7 +8697,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
8210
8697
  const now = (/* @__PURE__ */ new Date()).toISOString();
8211
8698
  const fallbackCreatedAt = defaults.createdAt ?? now;
8212
8699
  const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
8213
- const title = typeof parsed.data.title === "string" ? parsed.data.title : path15.basename(relativePath, ".md");
8700
+ const title = typeof parsed.data.title === "string" ? parsed.data.title : path17.basename(relativePath, ".md");
8214
8701
  const kind = inferPageKind(relativePath, parsed.data.kind);
8215
8702
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
8216
8703
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
@@ -8227,6 +8714,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
8227
8714
  title,
8228
8715
  kind,
8229
8716
  sourceType: normalizeSourceType(parsed.data.source_type),
8717
+ sourceClass: normalizeSourceClass(parsed.data.source_class),
8230
8718
  sourceIds,
8231
8719
  projectIds,
8232
8720
  nodeIds,
@@ -8250,18 +8738,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
8250
8738
  };
8251
8739
  }
8252
8740
  async function loadInsightPages(wikiDir) {
8253
- const insightsDir = path15.join(wikiDir, "insights");
8741
+ const insightsDir = path17.join(wikiDir, "insights");
8254
8742
  if (!await fileExists(insightsDir)) {
8255
8743
  return [];
8256
8744
  }
8257
- const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path15.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
8745
+ const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path17.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
8258
8746
  const insights = [];
8259
8747
  for (const absolutePath of files) {
8260
- const relativePath = toPosix(path15.relative(wikiDir, absolutePath));
8261
- const content = await fs12.readFile(absolutePath, "utf8");
8748
+ const relativePath = toPosix(path17.relative(wikiDir, absolutePath));
8749
+ const content = await fs13.readFile(absolutePath, "utf8");
8262
8750
  const parsed = matter6(content);
8263
- const stats = await fs12.stat(absolutePath);
8264
- const title = typeof parsed.data.title === "string" ? parsed.data.title : path15.basename(absolutePath, ".md");
8751
+ const stats = await fs13.stat(absolutePath);
8752
+ const title = typeof parsed.data.title === "string" ? parsed.data.title : path17.basename(absolutePath, ".md");
8265
8753
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
8266
8754
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
8267
8755
  const nodeIds = normalizeStringArray(parsed.data.node_ids);
@@ -8279,6 +8767,7 @@ async function loadInsightPages(wikiDir) {
8279
8767
  path: relativePath,
8280
8768
  title,
8281
8769
  kind: "insight",
8770
+ sourceClass: normalizeSourceClass(parsed.data.source_class),
8282
8771
  sourceIds,
8283
8772
  projectIds,
8284
8773
  nodeIds,
@@ -8323,27 +8812,27 @@ function relatedOutputsForPage(targetPage, outputPages) {
8323
8812
  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);
8324
8813
  }
8325
8814
  async function resolveUniqueOutputSlug(wikiDir, baseSlug) {
8326
- const outputsDir = path16.join(wikiDir, "outputs");
8815
+ const outputsDir = path18.join(wikiDir, "outputs");
8327
8816
  const root = baseSlug || "output";
8328
8817
  let candidate = root;
8329
8818
  let counter = 2;
8330
- while (await fileExists(path16.join(outputsDir, `${candidate}.md`))) {
8819
+ while (await fileExists(path18.join(outputsDir, `${candidate}.md`))) {
8331
8820
  candidate = `${root}-${counter}`;
8332
8821
  counter++;
8333
8822
  }
8334
8823
  return candidate;
8335
8824
  }
8336
8825
  async function loadSavedOutputPages(wikiDir) {
8337
- const outputsDir = path16.join(wikiDir, "outputs");
8338
- const entries = await fs13.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
8826
+ const outputsDir = path18.join(wikiDir, "outputs");
8827
+ const entries = await fs14.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
8339
8828
  const outputs = [];
8340
8829
  for (const entry of entries) {
8341
8830
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md") {
8342
8831
  continue;
8343
8832
  }
8344
- const relativePath = path16.posix.join("outputs", entry.name);
8345
- const absolutePath = path16.join(outputsDir, entry.name);
8346
- const content = await fs13.readFile(absolutePath, "utf8");
8833
+ const relativePath = path18.posix.join("outputs", entry.name);
8834
+ const absolutePath = path18.join(outputsDir, entry.name);
8835
+ const content = await fs14.readFile(absolutePath, "utf8");
8347
8836
  const parsed = matter7(content);
8348
8837
  const slug = entry.name.replace(/\.md$/, "");
8349
8838
  const title = typeof parsed.data.title === "string" ? parsed.data.title : slug;
@@ -8356,7 +8845,7 @@ async function loadSavedOutputPages(wikiDir) {
8356
8845
  const relatedSourceIds = normalizeStringArray(parsed.data.related_source_ids);
8357
8846
  const backlinks = normalizeStringArray(parsed.data.backlinks);
8358
8847
  const compiledFrom = normalizeStringArray(parsed.data.compiled_from);
8359
- const stats = await fs13.stat(absolutePath);
8848
+ const stats = await fs14.stat(absolutePath);
8360
8849
  const createdAt = typeof parsed.data.created_at === "string" ? parsed.data.created_at : stats.birthtimeMs > 0 ? stats.birthtime.toISOString() : stats.mtime.toISOString();
8361
8850
  const updatedAt = typeof parsed.data.updated_at === "string" ? parsed.data.updated_at : stats.mtime.toISOString();
8362
8851
  outputs.push({
@@ -8394,8 +8883,8 @@ async function loadSavedOutputPages(wikiDir) {
8394
8883
  }
8395
8884
 
8396
8885
  // src/search.ts
8397
- import fs14 from "fs/promises";
8398
- import path17 from "path";
8886
+ import fs15 from "fs/promises";
8887
+ import path19 from "path";
8399
8888
  import matter8 from "gray-matter";
8400
8889
  function getDatabaseSync() {
8401
8890
  const builtin = process.getBuiltinModule?.("node:sqlite");
@@ -8417,8 +8906,11 @@ function normalizeStatus(value) {
8417
8906
  function normalizeSourceType2(value) {
8418
8907
  return value === "arxiv" || value === "doi" || value === "tweet" || value === "article" || value === "url" ? value : void 0;
8419
8908
  }
8909
+ function normalizeSourceClass2(value) {
8910
+ return value === "first_party" || value === "third_party" || value === "resource" || value === "generated" ? value : void 0;
8911
+ }
8420
8912
  async function rebuildSearchIndex(dbPath, pages, wikiDir) {
8421
- await ensureDir(path17.dirname(dbPath));
8913
+ await ensureDir(path19.dirname(dbPath));
8422
8914
  const DatabaseSync = getDatabaseSync();
8423
8915
  const db = new DatabaseSync(dbPath);
8424
8916
  db.exec("PRAGMA journal_mode = WAL;");
@@ -8433,6 +8925,7 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
8433
8925
  kind TEXT NOT NULL,
8434
8926
  status TEXT NOT NULL,
8435
8927
  source_type TEXT NOT NULL,
8928
+ source_class TEXT NOT NULL,
8436
8929
  project_ids TEXT NOT NULL,
8437
8930
  project_key TEXT NOT NULL
8438
8931
  );
@@ -8446,11 +8939,11 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
8446
8939
  DELETE FROM pages;
8447
8940
  `);
8448
8941
  const insertPage = db.prepare(
8449
- "INSERT INTO pages (id, path, title, body, kind, status, source_type, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
8942
+ "INSERT INTO pages (id, path, title, body, kind, status, source_type, source_class, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
8450
8943
  );
8451
8944
  for (const page of pages) {
8452
- const absolutePath = path17.join(wikiDir, page.path);
8453
- const content = await fs14.readFile(absolutePath, "utf8");
8945
+ const absolutePath = path19.join(wikiDir, page.path);
8946
+ const content = await fs15.readFile(absolutePath, "utf8");
8454
8947
  const parsed = matter8(content);
8455
8948
  insertPage.run(
8456
8949
  page.id,
@@ -8460,6 +8953,7 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
8460
8953
  page.kind,
8461
8954
  page.status,
8462
8955
  typeof parsed.data.source_type === "string" ? parsed.data.source_type : "",
8956
+ typeof parsed.data.source_class === "string" ? parsed.data.source_class : "",
8463
8957
  JSON.stringify(page.projectIds),
8464
8958
  page.projectIds.map((projectId) => `|${projectId}|`).join("")
8465
8959
  );
@@ -8497,6 +8991,10 @@ function searchPages(dbPath, query, limitOrOptions = 5) {
8497
8991
  clauses.push("pages.source_type = ?");
8498
8992
  params.push(options.sourceType);
8499
8993
  }
8994
+ if (options.sourceClass && options.sourceClass !== "all") {
8995
+ clauses.push("pages.source_class = ?");
8996
+ params.push(options.sourceClass);
8997
+ }
8500
8998
  const statement = db.prepare(`
8501
8999
  SELECT
8502
9000
  pages.id AS pageId,
@@ -8505,6 +9003,7 @@ function searchPages(dbPath, query, limitOrOptions = 5) {
8505
9003
  pages.kind AS kind,
8506
9004
  pages.status AS status,
8507
9005
  pages.source_type AS sourceType,
9006
+ pages.source_class AS sourceClass,
8508
9007
  pages.project_ids AS projectIds,
8509
9008
  snippet(page_search, 1, '[', ']', '...', 16) AS snippet,
8510
9009
  bm25(page_search) AS rank
@@ -8533,6 +9032,7 @@ function searchPages(dbPath, query, limitOrOptions = 5) {
8533
9032
  kind: normalizeKind(row.kind),
8534
9033
  status: normalizeStatus(row.status),
8535
9034
  sourceType: normalizeSourceType2(row.sourceType),
9035
+ sourceClass: normalizeSourceClass2(row.sourceClass),
8536
9036
  snippet: String(row.snippet ?? ""),
8537
9037
  rank: Number(row.rank ?? 0)
8538
9038
  }));
@@ -8560,7 +9060,7 @@ function outputFormatInstruction(format) {
8560
9060
  }
8561
9061
  }
8562
9062
  function outputAssetPath(slug, fileName) {
8563
- return toPosix(path18.join("outputs", "assets", slug, fileName));
9063
+ return toPosix(path20.join("outputs", "assets", slug, fileName));
8564
9064
  }
8565
9065
  function outputAssetId(slug, role) {
8566
9066
  return `output:${slug}:asset:${role}`;
@@ -8700,7 +9200,7 @@ async function resolveImageGenerationProvider(rootDir) {
8700
9200
  if (!providerConfig) {
8701
9201
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
8702
9202
  }
8703
- const { createProvider: createProvider2 } = await import("./registry-6KZMA3XM.js");
9203
+ const { createProvider: createProvider2 } = await import("./registry-YGVTLIZH.js");
8704
9204
  return createProvider2(preferredProviderId, providerConfig, rootDir);
8705
9205
  }
8706
9206
  async function generateOutputArtifacts(rootDir, input) {
@@ -8898,7 +9398,7 @@ async function generateOutputArtifacts(rootDir, input) {
8898
9398
  };
8899
9399
  }
8900
9400
  function normalizeProjectRoot(root) {
8901
- const normalized = toPosix(path18.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
9401
+ const normalized = toPosix(path20.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
8902
9402
  return normalized;
8903
9403
  }
8904
9404
  function projectEntries(config) {
@@ -8924,10 +9424,10 @@ function manifestPathForProject(rootDir, manifest) {
8924
9424
  if (!rawPath) {
8925
9425
  return toPosix(manifest.storedPath);
8926
9426
  }
8927
- if (!path18.isAbsolute(rawPath)) {
9427
+ if (!path20.isAbsolute(rawPath)) {
8928
9428
  return normalizeProjectRoot(rawPath);
8929
9429
  }
8930
- const relative = toPosix(path18.relative(rootDir, rawPath));
9430
+ const relative = toPosix(path20.relative(rootDir, rawPath));
8931
9431
  return relative.startsWith("..") ? toPosix(rawPath) : normalizeProjectRoot(relative);
8932
9432
  }
8933
9433
  function prefixMatches(value, prefix) {
@@ -9101,7 +9601,7 @@ function pageHashes(pages) {
9101
9601
  return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
9102
9602
  }
9103
9603
  async function buildManagedGraphPage(absolutePath, defaults, build) {
9104
- const existingContent = await fileExists(absolutePath) ? await fs15.readFile(absolutePath, "utf8") : null;
9604
+ const existingContent = await fileExists(absolutePath) ? await fs16.readFile(absolutePath, "utf8") : null;
9105
9605
  let existing = await loadExistingManagedPageState(absolutePath, {
9106
9606
  status: defaults.status ?? "active",
9107
9607
  managedBy: defaults.managedBy
@@ -9139,7 +9639,7 @@ async function buildManagedGraphPage(absolutePath, defaults, build) {
9139
9639
  return built;
9140
9640
  }
9141
9641
  async function buildManagedContent(absolutePath, defaults, build) {
9142
- const existingContent = await fileExists(absolutePath) ? await fs15.readFile(absolutePath, "utf8") : null;
9642
+ const existingContent = await fileExists(absolutePath) ? await fs16.readFile(absolutePath, "utf8") : null;
9143
9643
  let existing = await loadExistingManagedPageState(absolutePath, {
9144
9644
  status: defaults.status ?? "active",
9145
9645
  managedBy: defaults.managedBy
@@ -9258,7 +9758,11 @@ function deriveGraphMetrics(nodes, edges) {
9258
9758
  communities
9259
9759
  };
9260
9760
  }
9761
+ function resetGraphNodeMetrics(nodes) {
9762
+ return nodes.map(({ communityId: _communityId, degree: _degree, bridgeScore: _bridgeScore, isGodNode: _isGodNode, ...node }) => node);
9763
+ }
9261
9764
  function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9765
+ const manifestsById = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
9262
9766
  const sourceNodes = manifests.map((manifest) => ({
9263
9767
  id: `source:${manifest.sourceId}`,
9264
9768
  type: "source",
@@ -9268,6 +9772,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9268
9772
  confidence: 1,
9269
9773
  sourceIds: [manifest.sourceId],
9270
9774
  projectIds: scopedProjectIdsFromSources([manifest.sourceId], sourceProjects),
9775
+ sourceClass: manifest.sourceClass,
9271
9776
  language: manifest.language
9272
9777
  }));
9273
9778
  const conceptMap = /* @__PURE__ */ new Map();
@@ -9284,7 +9789,6 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9284
9789
  edgesById.add(edge.id);
9285
9790
  edges.push(edge);
9286
9791
  };
9287
- const manifestsById = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
9288
9792
  const analysesBySourceId = new Map(analyses.map((analysis) => [analysis.sourceId, analysis]));
9289
9793
  for (const analysis of analyses) {
9290
9794
  for (const concept of analysis.concepts) {
@@ -9298,7 +9802,8 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9298
9802
  freshness: "fresh",
9299
9803
  confidence: nodeConfidence(sourceIds.length),
9300
9804
  sourceIds,
9301
- projectIds: scopedProjectIdsFromSources(sourceIds, sourceProjects)
9805
+ projectIds: scopedProjectIdsFromSources(sourceIds, sourceProjects),
9806
+ sourceClass: aggregateManifestSourceClass(manifests, sourceIds)
9302
9807
  });
9303
9808
  pushEdge({
9304
9809
  id: `${analysis.sourceId}->${concept.id}`,
@@ -9322,7 +9827,8 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9322
9827
  freshness: "fresh",
9323
9828
  confidence: nodeConfidence(sourceIds.length),
9324
9829
  sourceIds,
9325
- projectIds: scopedProjectIdsFromSources(sourceIds, sourceProjects)
9830
+ projectIds: scopedProjectIdsFromSources(sourceIds, sourceProjects),
9831
+ sourceClass: aggregateManifestSourceClass(manifests, sourceIds)
9326
9832
  });
9327
9833
  pushEdge({
9328
9834
  id: `${analysis.sourceId}->${entity.id}`,
@@ -9350,6 +9856,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9350
9856
  confidence: 1,
9351
9857
  sourceIds: [analysis.sourceId],
9352
9858
  projectIds: scopedProjectIdsFromSources([analysis.sourceId], sourceProjects),
9859
+ sourceClass: manifest.sourceClass,
9353
9860
  language: analysis.code.language,
9354
9861
  moduleId
9355
9862
  });
@@ -9373,6 +9880,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9373
9880
  confidence: symbol.exported ? 0.88 : 0.74,
9374
9881
  sourceIds: [analysis.sourceId],
9375
9882
  projectIds: scopedProjectIdsFromSources([analysis.sourceId], sourceProjects),
9883
+ sourceClass: manifest.sourceClass,
9376
9884
  language: analysis.code.language,
9377
9885
  moduleId,
9378
9886
  symbolKind: symbol.kind
@@ -9413,6 +9921,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9413
9921
  confidence: 1,
9414
9922
  sourceIds: [analysis.sourceId],
9415
9923
  projectIds: scopedProjectIdsFromSources([analysis.sourceId], sourceProjects),
9924
+ sourceClass: manifest.sourceClass,
9416
9925
  language: analysis.code.language,
9417
9926
  moduleId
9418
9927
  });
@@ -9608,7 +10117,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
9608
10117
  const benchmark = await readJsonFile(paths.benchmarkPath);
9609
10118
  const communityRecords = [];
9610
10119
  for (const community of graph.communities ?? []) {
9611
- const absolutePath = path18.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
10120
+ const absolutePath = path20.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
9612
10121
  communityRecords.push(
9613
10122
  await buildManagedGraphPage(
9614
10123
  absolutePath,
@@ -9636,7 +10145,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
9636
10145
  recentResearchSources: recentResearchSourcePages(graph, previousCompiledAt),
9637
10146
  graphHash: graphHash(graph)
9638
10147
  });
9639
- const reportAbsolutePath = path18.join(paths.wikiDir, "graph", "report.md");
10148
+ const reportAbsolutePath = path20.join(paths.wikiDir, "graph", "report.md");
9640
10149
  const reportRecord = await buildManagedGraphPage(
9641
10150
  reportAbsolutePath,
9642
10151
  {
@@ -9657,7 +10166,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
9657
10166
  };
9658
10167
  }
9659
10168
  async function writePage(wikiDir, relativePath, content, changedPages) {
9660
- const absolutePath = path18.resolve(wikiDir, relativePath);
10169
+ const absolutePath = path20.resolve(wikiDir, relativePath);
9661
10170
  const changed = await writeFileIfChanged(absolutePath, content);
9662
10171
  if (changed) {
9663
10172
  changedPages.push(relativePath);
@@ -9688,6 +10197,7 @@ function emptyGraphPage(input) {
9688
10197
  path: input.path,
9689
10198
  title: input.title,
9690
10199
  kind: input.kind,
10200
+ sourceClass: input.sourceClass,
9691
10201
  sourceIds: input.sourceIds,
9692
10202
  projectIds: input.projectIds ?? [],
9693
10203
  nodeIds: input.nodeIds,
@@ -9719,29 +10229,29 @@ async function requiredCompileArtifactsExist(paths) {
9719
10229
  paths.graphPath,
9720
10230
  paths.codeIndexPath,
9721
10231
  paths.searchDbPath,
9722
- path18.join(paths.wikiDir, "index.md"),
9723
- path18.join(paths.wikiDir, "sources", "index.md"),
9724
- path18.join(paths.wikiDir, "code", "index.md"),
9725
- path18.join(paths.wikiDir, "concepts", "index.md"),
9726
- path18.join(paths.wikiDir, "entities", "index.md"),
9727
- path18.join(paths.wikiDir, "outputs", "index.md"),
9728
- path18.join(paths.wikiDir, "projects", "index.md"),
9729
- path18.join(paths.wikiDir, "candidates", "index.md")
10232
+ path20.join(paths.wikiDir, "index.md"),
10233
+ path20.join(paths.wikiDir, "sources", "index.md"),
10234
+ path20.join(paths.wikiDir, "code", "index.md"),
10235
+ path20.join(paths.wikiDir, "concepts", "index.md"),
10236
+ path20.join(paths.wikiDir, "entities", "index.md"),
10237
+ path20.join(paths.wikiDir, "outputs", "index.md"),
10238
+ path20.join(paths.wikiDir, "projects", "index.md"),
10239
+ path20.join(paths.wikiDir, "candidates", "index.md")
9730
10240
  ];
9731
10241
  const checks = await Promise.all(requiredPaths.map((filePath) => fileExists(filePath)));
9732
10242
  return checks.every(Boolean);
9733
10243
  }
9734
10244
  async function loadAvailableCachedAnalyses(paths, manifests) {
9735
10245
  const analyses = await Promise.all(
9736
- manifests.map(async (manifest) => readJsonFile(path18.join(paths.analysesDir, `${manifest.sourceId}.json`)))
10246
+ manifests.map(async (manifest) => readJsonFile(path20.join(paths.analysesDir, `${manifest.sourceId}.json`)))
9737
10247
  );
9738
10248
  return analyses.filter((analysis) => Boolean(analysis));
9739
10249
  }
9740
10250
  function approvalManifestPath(paths, approvalId) {
9741
- return path18.join(paths.approvalsDir, approvalId, "manifest.json");
10251
+ return path20.join(paths.approvalsDir, approvalId, "manifest.json");
9742
10252
  }
9743
10253
  function approvalGraphPath(paths, approvalId) {
9744
- return path18.join(paths.approvalsDir, approvalId, "state", "graph.json");
10254
+ return path20.join(paths.approvalsDir, approvalId, "state", "graph.json");
9745
10255
  }
9746
10256
  async function readApprovalManifest(paths, approvalId) {
9747
10257
  const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
@@ -9751,7 +10261,7 @@ async function readApprovalManifest(paths, approvalId) {
9751
10261
  return manifest;
9752
10262
  }
9753
10263
  async function writeApprovalManifest(paths, manifest) {
9754
- await fs15.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
10264
+ await fs16.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
9755
10265
  `, "utf8");
9756
10266
  }
9757
10267
  async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
@@ -9766,7 +10276,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
9766
10276
  continue;
9767
10277
  }
9768
10278
  const previousPage = previousPagesById.get(nextPage.id);
9769
- const currentExists = await fileExists(path18.join(paths.wikiDir, file.relativePath));
10279
+ const currentExists = await fileExists(path20.join(paths.wikiDir, file.relativePath));
9770
10280
  if (previousPage && previousPage.path !== nextPage.path) {
9771
10281
  entries.push({
9772
10282
  pageId: nextPage.id,
@@ -9799,7 +10309,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
9799
10309
  const previousPage = previousPagesByPath.get(deletedPath);
9800
10310
  entries.push({
9801
10311
  pageId: previousPage?.id ?? `page:${slugify(deletedPath)}`,
9802
- title: previousPage?.title ?? path18.basename(deletedPath, ".md"),
10312
+ title: previousPage?.title ?? path20.basename(deletedPath, ".md"),
9803
10313
  kind: previousPage?.kind ?? "index",
9804
10314
  changeType: "delete",
9805
10315
  status: "pending",
@@ -9811,16 +10321,16 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
9811
10321
  }
9812
10322
  async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGraph, graph) {
9813
10323
  const approvalId = `compile-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
9814
- const approvalDir = path18.join(paths.approvalsDir, approvalId);
10324
+ const approvalDir = path20.join(paths.approvalsDir, approvalId);
9815
10325
  await ensureDir(approvalDir);
9816
- await ensureDir(path18.join(approvalDir, "wiki"));
9817
- await ensureDir(path18.join(approvalDir, "state"));
10326
+ await ensureDir(path20.join(approvalDir, "wiki"));
10327
+ await ensureDir(path20.join(approvalDir, "state"));
9818
10328
  for (const file of changedFiles) {
9819
- const targetPath = path18.join(approvalDir, "wiki", file.relativePath);
9820
- await ensureDir(path18.dirname(targetPath));
9821
- await fs15.writeFile(targetPath, file.content, "utf8");
10329
+ const targetPath = path20.join(approvalDir, "wiki", file.relativePath);
10330
+ await ensureDir(path20.dirname(targetPath));
10331
+ await fs16.writeFile(targetPath, file.content, "utf8");
9822
10332
  }
9823
- await fs15.writeFile(path18.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
10333
+ await fs16.writeFile(path20.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
9824
10334
  await writeApprovalManifest(paths, {
9825
10335
  approvalId,
9826
10336
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -9856,6 +10366,7 @@ async function syncVaultArtifacts(rootDir, input) {
9856
10366
  title: modulePageTitle(manifest),
9857
10367
  kind: "module",
9858
10368
  sourceIds: [manifest.sourceId],
10369
+ sourceClass: manifest.sourceClass,
9859
10370
  projectIds: sourceProjectIds,
9860
10371
  nodeIds: [analysis.code.moduleId, ...analysis.code.symbols.map((symbol) => symbol.id)],
9861
10372
  schemaHash: sourceSchemaHash,
@@ -9868,6 +10379,7 @@ async function syncVaultArtifacts(rootDir, input) {
9868
10379
  title: analysis.title,
9869
10380
  kind: "source",
9870
10381
  sourceIds: [manifest.sourceId],
10382
+ sourceClass: manifest.sourceClass,
9871
10383
  projectIds: sourceProjectIds,
9872
10384
  nodeIds: [
9873
10385
  `source:${manifest.sourceId}`,
@@ -9880,7 +10392,7 @@ async function syncVaultArtifacts(rootDir, input) {
9880
10392
  confidence: 1
9881
10393
  });
9882
10394
  const sourceRecord = await buildManagedGraphPage(
9883
- path18.join(paths.wikiDir, preview.path),
10395
+ path20.join(paths.wikiDir, preview.path),
9884
10396
  {
9885
10397
  managedBy: "system",
9886
10398
  confidence: 1,
@@ -9895,7 +10407,8 @@ async function syncVaultArtifacts(rootDir, input) {
9895
10407
  modulePreview ?? void 0,
9896
10408
  {
9897
10409
  projectIds: sourceProjectIds,
9898
- extraTags: sourceCategoryTags
10410
+ extraTags: sourceCategoryTags,
10411
+ sourceClass: manifest.sourceClass
9899
10412
  }
9900
10413
  )
9901
10414
  );
@@ -9925,7 +10438,7 @@ async function syncVaultArtifacts(rootDir, input) {
9925
10438
  );
9926
10439
  records.push(
9927
10440
  await buildManagedGraphPage(
9928
- path18.join(paths.wikiDir, modulePreview.path),
10441
+ path20.join(paths.wikiDir, modulePreview.path),
9929
10442
  {
9930
10443
  managedBy: "system",
9931
10444
  confidence: 1,
@@ -9957,9 +10470,10 @@ async function syncVaultArtifacts(rootDir, input) {
9957
10470
  const previousEntry = input.previousState?.candidateHistory?.[pageId];
9958
10471
  const promoted = previousEntry?.status === "active" || promoteCandidates && shouldPromoteCandidate(previousEntry, sourceIds);
9959
10472
  const relativePath = promoted ? activeAggregatePath(itemKind, slug) : candidatePagePathFor(itemKind, slug);
10473
+ const aggregateSourceClass2 = aggregateManifestSourceClass(input.manifests, sourceIds);
9960
10474
  const fallbackPaths = [
9961
- path18.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
9962
- path18.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
10475
+ path20.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
10476
+ path20.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
9963
10477
  ];
9964
10478
  const confidence = nodeConfidence(aggregate.sourceAnalyses.length);
9965
10479
  const preview = emptyGraphPage({
@@ -9968,6 +10482,7 @@ async function syncVaultArtifacts(rootDir, input) {
9968
10482
  title: aggregate.name,
9969
10483
  kind: itemKind,
9970
10484
  sourceIds,
10485
+ sourceClass: aggregateSourceClass2,
9971
10486
  projectIds,
9972
10487
  nodeIds: [pageId],
9973
10488
  schemaHash,
@@ -9976,7 +10491,7 @@ async function syncVaultArtifacts(rootDir, input) {
9976
10491
  status: promoted ? "active" : "candidate"
9977
10492
  });
9978
10493
  const pageRecord = await buildManagedGraphPage(
9979
- path18.join(paths.wikiDir, relativePath),
10494
+ path20.join(paths.wikiDir, relativePath),
9980
10495
  {
9981
10496
  status: promoted ? "active" : "candidate",
9982
10497
  managedBy: "system",
@@ -10000,7 +10515,8 @@ async function syncVaultArtifacts(rootDir, input) {
10000
10515
  aggregate.name,
10001
10516
  ...aggregate.descriptions,
10002
10517
  ...aggregate.sourceAnalyses.map((item) => item.summary)
10003
- ])
10518
+ ]),
10519
+ sourceClass: aggregateSourceClass2
10004
10520
  }
10005
10521
  )
10006
10522
  );
@@ -10016,7 +10532,20 @@ async function syncVaultArtifacts(rootDir, input) {
10016
10532
  }
10017
10533
  const compiledPages = records.map((record) => record.page);
10018
10534
  const basePages = [...compiledPages, ...input.outputPages, ...input.insightPages];
10019
- const baseGraph = buildGraph(input.manifests, input.analyses, basePages, input.sourceProjects, input.codeIndex);
10535
+ const structuralGraph = buildGraph(input.manifests, input.analyses, basePages, input.sourceProjects, input.codeIndex);
10536
+ const embeddingEdges = await embeddingSimilarityEdges(rootDir, structuralGraph).catch(() => []);
10537
+ const baseGraph = embeddingEdges.length > 0 ? (() => {
10538
+ const edges = uniqueBy([...structuralGraph.edges, ...embeddingEdges], (edge) => edge.id).sort(
10539
+ (left, right) => left.id.localeCompare(right.id)
10540
+ );
10541
+ const metrics = deriveGraphMetrics(resetGraphNodeMetrics(structuralGraph.nodes), edges);
10542
+ return {
10543
+ ...structuralGraph,
10544
+ nodes: metrics.nodes,
10545
+ edges,
10546
+ communities: metrics.communities
10547
+ };
10548
+ })() : structuralGraph;
10020
10549
  const graphOrientation = await buildGraphOrientationPages(baseGraph, paths, globalSchemaHash, input.previousState?.generatedAt);
10021
10550
  records.push(...graphOrientation.records);
10022
10551
  const allPages = [...basePages, ...graphOrientation.records.map((record) => record.page)];
@@ -10057,7 +10586,7 @@ async function syncVaultArtifacts(rootDir, input) {
10057
10586
  confidence: 1
10058
10587
  }),
10059
10588
  content: await buildManagedContent(
10060
- path18.join(paths.wikiDir, "projects", "index.md"),
10589
+ path20.join(paths.wikiDir, "projects", "index.md"),
10061
10590
  {
10062
10591
  managedBy: "system",
10063
10592
  compiledFrom: indexCompiledFrom(projectIndexRefs)
@@ -10081,7 +10610,7 @@ async function syncVaultArtifacts(rootDir, input) {
10081
10610
  records.push({
10082
10611
  page: projectIndexRef,
10083
10612
  content: await buildManagedContent(
10084
- path18.join(paths.wikiDir, projectIndexRef.path),
10613
+ path20.join(paths.wikiDir, projectIndexRef.path),
10085
10614
  {
10086
10615
  managedBy: "system",
10087
10616
  compiledFrom: indexCompiledFrom(Object.values(sections).flat())
@@ -10109,7 +10638,7 @@ async function syncVaultArtifacts(rootDir, input) {
10109
10638
  confidence: 1
10110
10639
  }),
10111
10640
  content: await buildManagedContent(
10112
- path18.join(paths.wikiDir, "index.md"),
10641
+ path20.join(paths.wikiDir, "index.md"),
10113
10642
  {
10114
10643
  managedBy: "system",
10115
10644
  compiledFrom: indexCompiledFrom(allPages)
@@ -10140,7 +10669,7 @@ async function syncVaultArtifacts(rootDir, input) {
10140
10669
  confidence: 1
10141
10670
  }),
10142
10671
  content: await buildManagedContent(
10143
- path18.join(paths.wikiDir, relativePath),
10672
+ path20.join(paths.wikiDir, relativePath),
10144
10673
  {
10145
10674
  managedBy: "system",
10146
10675
  compiledFrom: indexCompiledFrom(pages)
@@ -10151,12 +10680,12 @@ async function syncVaultArtifacts(rootDir, input) {
10151
10680
  }
10152
10681
  const nextPagePaths = new Set(records.map((record) => record.page.path));
10153
10682
  const obsoleteGraphPaths = (previousGraph?.pages ?? []).filter((page) => page.kind !== "output" && page.kind !== "insight").map((page) => page.path).filter((relativePath) => !nextPagePaths.has(relativePath));
10154
- const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path18.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
10683
+ const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path20.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
10155
10684
  const obsoletePaths = uniqueStrings3([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
10156
10685
  const changedFiles = [];
10157
10686
  for (const record of records) {
10158
- const absolutePath = path18.join(paths.wikiDir, record.page.path);
10159
- const current = await fileExists(absolutePath) ? await fs15.readFile(absolutePath, "utf8") : null;
10687
+ const absolutePath = path20.join(paths.wikiDir, record.page.path);
10688
+ const current = await fileExists(absolutePath) ? await fs16.readFile(absolutePath, "utf8") : null;
10160
10689
  if (current !== record.content) {
10161
10690
  changedPages.push(record.page.path);
10162
10691
  changedFiles.push({ relativePath: record.page.path, content: record.content });
@@ -10181,10 +10710,10 @@ async function syncVaultArtifacts(rootDir, input) {
10181
10710
  await writePage(paths.wikiDir, record.page.path, record.content, writeChanges);
10182
10711
  }
10183
10712
  for (const relativePath of obsoletePaths) {
10184
- await fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true });
10713
+ await fs16.rm(path20.join(paths.wikiDir, relativePath), { force: true });
10185
10714
  }
10186
10715
  await writeJsonFile(paths.graphPath, graph);
10187
- await writeJsonFile(path18.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
10716
+ await writeJsonFile(path20.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
10188
10717
  await writeJsonFile(paths.codeIndexPath, input.codeIndex);
10189
10718
  await writeJsonFile(paths.compileStatePath, {
10190
10719
  generatedAt: graph.generatedAt,
@@ -10255,17 +10784,17 @@ async function refreshIndexesAndSearch(rootDir, pages) {
10255
10784
  })
10256
10785
  );
10257
10786
  await Promise.all([
10258
- ensureDir(path18.join(paths.wikiDir, "sources")),
10259
- ensureDir(path18.join(paths.wikiDir, "code")),
10260
- ensureDir(path18.join(paths.wikiDir, "concepts")),
10261
- ensureDir(path18.join(paths.wikiDir, "entities")),
10262
- ensureDir(path18.join(paths.wikiDir, "outputs")),
10263
- ensureDir(path18.join(paths.wikiDir, "graph")),
10264
- ensureDir(path18.join(paths.wikiDir, "graph", "communities")),
10265
- ensureDir(path18.join(paths.wikiDir, "projects")),
10266
- ensureDir(path18.join(paths.wikiDir, "candidates"))
10787
+ ensureDir(path20.join(paths.wikiDir, "sources")),
10788
+ ensureDir(path20.join(paths.wikiDir, "code")),
10789
+ ensureDir(path20.join(paths.wikiDir, "concepts")),
10790
+ ensureDir(path20.join(paths.wikiDir, "entities")),
10791
+ ensureDir(path20.join(paths.wikiDir, "outputs")),
10792
+ ensureDir(path20.join(paths.wikiDir, "graph")),
10793
+ ensureDir(path20.join(paths.wikiDir, "graph", "communities")),
10794
+ ensureDir(path20.join(paths.wikiDir, "projects")),
10795
+ ensureDir(path20.join(paths.wikiDir, "candidates"))
10267
10796
  ]);
10268
- const projectsIndexPath = path18.join(paths.wikiDir, "projects", "index.md");
10797
+ const projectsIndexPath = path20.join(paths.wikiDir, "projects", "index.md");
10269
10798
  await writeFileIfChanged(
10270
10799
  projectsIndexPath,
10271
10800
  await buildManagedContent(
@@ -10286,7 +10815,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
10286
10815
  outputs: pages.filter((page) => page.kind === "output" && page.projectIds.includes(project.id)),
10287
10816
  candidates: pages.filter((page) => page.status === "candidate" && page.projectIds.includes(project.id))
10288
10817
  };
10289
- const absolutePath = path18.join(paths.wikiDir, "projects", project.id, "index.md");
10818
+ const absolutePath = path20.join(paths.wikiDir, "projects", project.id, "index.md");
10290
10819
  await writeFileIfChanged(
10291
10820
  absolutePath,
10292
10821
  await buildManagedContent(
@@ -10304,7 +10833,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
10304
10833
  )
10305
10834
  );
10306
10835
  }
10307
- const rootIndexPath = path18.join(paths.wikiDir, "index.md");
10836
+ const rootIndexPath = path20.join(paths.wikiDir, "index.md");
10308
10837
  await writeFileIfChanged(
10309
10838
  rootIndexPath,
10310
10839
  await buildManagedContent(
@@ -10325,7 +10854,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
10325
10854
  ["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
10326
10855
  ["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
10327
10856
  ]) {
10328
- const absolutePath = path18.join(paths.wikiDir, relativePath);
10857
+ const absolutePath = path20.join(paths.wikiDir, relativePath);
10329
10858
  await writeFileIfChanged(
10330
10859
  absolutePath,
10331
10860
  await buildManagedContent(
@@ -10339,23 +10868,23 @@ async function refreshIndexesAndSearch(rootDir, pages) {
10339
10868
  );
10340
10869
  }
10341
10870
  for (const record of graphOrientation.records) {
10342
- await writeFileIfChanged(path18.join(paths.wikiDir, record.page.path), record.content);
10871
+ await writeFileIfChanged(path20.join(paths.wikiDir, record.page.path), record.content);
10343
10872
  }
10344
10873
  if (graphOrientation.report) {
10345
- await writeJsonFile(path18.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
10874
+ await writeJsonFile(path20.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
10346
10875
  }
10347
- const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path18.relative(paths.wikiDir, absolutePath)));
10876
+ const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path20.relative(paths.wikiDir, absolutePath)));
10348
10877
  const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
10349
10878
  "projects/index.md",
10350
10879
  ...configuredProjects.map((project) => `projects/${project.id}/index.md`)
10351
10880
  ]);
10352
10881
  await Promise.all(
10353
- existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true }))
10882
+ existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs16.rm(path20.join(paths.wikiDir, relativePath), { force: true }))
10354
10883
  );
10355
- const existingGraphPages = (await listFilesRecursive(path18.join(paths.wikiDir, "graph").replace(/\/$/, "")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path18.relative(paths.wikiDir, absolutePath)));
10884
+ const existingGraphPages = (await listFilesRecursive(path20.join(paths.wikiDir, "graph").replace(/\/$/, "")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path20.relative(paths.wikiDir, absolutePath)));
10356
10885
  const allowedGraphPages = /* @__PURE__ */ new Set(["graph/index.md", ...graphOrientation.records.map((record) => record.page.path)]);
10357
10886
  await Promise.all(
10358
- existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true }))
10887
+ existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs16.rm(path20.join(paths.wikiDir, relativePath), { force: true }))
10359
10888
  );
10360
10889
  await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
10361
10890
  }
@@ -10375,7 +10904,7 @@ async function prepareOutputPageSave(rootDir, input) {
10375
10904
  confidence: 0.74
10376
10905
  }
10377
10906
  });
10378
- const absolutePath = path18.join(paths.wikiDir, output.page.path);
10907
+ const absolutePath = path20.join(paths.wikiDir, output.page.path);
10379
10908
  return {
10380
10909
  page: output.page,
10381
10910
  savedPath: absolutePath,
@@ -10387,15 +10916,15 @@ async function prepareOutputPageSave(rootDir, input) {
10387
10916
  async function persistOutputPage(rootDir, input) {
10388
10917
  const { paths } = await loadVaultConfig(rootDir);
10389
10918
  const prepared = await prepareOutputPageSave(rootDir, input);
10390
- await ensureDir(path18.dirname(prepared.savedPath));
10391
- await fs15.writeFile(prepared.savedPath, prepared.content, "utf8");
10919
+ await ensureDir(path20.dirname(prepared.savedPath));
10920
+ await fs16.writeFile(prepared.savedPath, prepared.content, "utf8");
10392
10921
  for (const assetFile of prepared.assetFiles) {
10393
- const assetPath = path18.join(paths.wikiDir, assetFile.relativePath);
10394
- await ensureDir(path18.dirname(assetPath));
10922
+ const assetPath = path20.join(paths.wikiDir, assetFile.relativePath);
10923
+ await ensureDir(path20.dirname(assetPath));
10395
10924
  if (typeof assetFile.content === "string") {
10396
- await fs15.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
10925
+ await fs16.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
10397
10926
  } else {
10398
- await fs15.writeFile(assetPath, assetFile.content);
10927
+ await fs16.writeFile(assetPath, assetFile.content);
10399
10928
  }
10400
10929
  }
10401
10930
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -10416,7 +10945,7 @@ async function prepareExploreHubSave(rootDir, input) {
10416
10945
  confidence: 0.76
10417
10946
  }
10418
10947
  });
10419
- const absolutePath = path18.join(paths.wikiDir, hub.page.path);
10948
+ const absolutePath = path20.join(paths.wikiDir, hub.page.path);
10420
10949
  return {
10421
10950
  page: hub.page,
10422
10951
  savedPath: absolutePath,
@@ -10428,15 +10957,15 @@ async function prepareExploreHubSave(rootDir, input) {
10428
10957
  async function persistExploreHub(rootDir, input) {
10429
10958
  const { paths } = await loadVaultConfig(rootDir);
10430
10959
  const prepared = await prepareExploreHubSave(rootDir, input);
10431
- await ensureDir(path18.dirname(prepared.savedPath));
10432
- await fs15.writeFile(prepared.savedPath, prepared.content, "utf8");
10960
+ await ensureDir(path20.dirname(prepared.savedPath));
10961
+ await fs16.writeFile(prepared.savedPath, prepared.content, "utf8");
10433
10962
  for (const assetFile of prepared.assetFiles) {
10434
- const assetPath = path18.join(paths.wikiDir, assetFile.relativePath);
10435
- await ensureDir(path18.dirname(assetPath));
10963
+ const assetPath = path20.join(paths.wikiDir, assetFile.relativePath);
10964
+ await ensureDir(path20.dirname(assetPath));
10436
10965
  if (typeof assetFile.content === "string") {
10437
- await fs15.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
10966
+ await fs16.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
10438
10967
  } else {
10439
- await fs15.writeFile(assetPath, assetFile.content);
10968
+ await fs16.writeFile(assetPath, assetFile.content);
10440
10969
  }
10441
10970
  }
10442
10971
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -10453,17 +10982,17 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
10453
10982
  }))
10454
10983
  ]);
10455
10984
  const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
10456
- const approvalDir = path18.join(paths.approvalsDir, approvalId);
10985
+ const approvalDir = path20.join(paths.approvalsDir, approvalId);
10457
10986
  await ensureDir(approvalDir);
10458
- await ensureDir(path18.join(approvalDir, "wiki"));
10459
- await ensureDir(path18.join(approvalDir, "state"));
10987
+ await ensureDir(path20.join(approvalDir, "wiki"));
10988
+ await ensureDir(path20.join(approvalDir, "state"));
10460
10989
  for (const file of changedFiles) {
10461
- const targetPath = path18.join(approvalDir, "wiki", file.relativePath);
10462
- await ensureDir(path18.dirname(targetPath));
10990
+ const targetPath = path20.join(approvalDir, "wiki", file.relativePath);
10991
+ await ensureDir(path20.dirname(targetPath));
10463
10992
  if ("binary" in file && file.binary) {
10464
- await fs15.writeFile(targetPath, Buffer.from(file.content, "base64"));
10993
+ await fs16.writeFile(targetPath, Buffer.from(file.content, "base64"));
10465
10994
  } else {
10466
- await fs15.writeFile(targetPath, file.content, "utf8");
10995
+ await fs16.writeFile(targetPath, file.content, "utf8");
10467
10996
  }
10468
10997
  }
10469
10998
  const nextPages = sortGraphPages([
@@ -10478,7 +11007,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
10478
11007
  sources: previousGraph?.sources ?? [],
10479
11008
  pages: nextPages
10480
11009
  };
10481
- await fs15.writeFile(path18.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
11010
+ await fs16.writeFile(path20.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
10482
11011
  await writeApprovalManifest(paths, {
10483
11012
  approvalId,
10484
11013
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10507,9 +11036,9 @@ async function executeQuery(rootDir, question, format) {
10507
11036
  const searchResults = searchPages(paths.searchDbPath, question, 5);
10508
11037
  const excerpts = await Promise.all(
10509
11038
  searchResults.map(async (result) => {
10510
- const absolutePath = path18.join(paths.wikiDir, result.path);
11039
+ const absolutePath = path20.join(paths.wikiDir, result.path);
10511
11040
  try {
10512
- const content = await fs15.readFile(absolutePath, "utf8");
11041
+ const content = await fs16.readFile(absolutePath, "utf8");
10513
11042
  const parsed = matter9(content);
10514
11043
  return `# ${result.title}
10515
11044
  ${truncate(normalizeWhitespace(parsed.content), 1200)}`;
@@ -10691,7 +11220,7 @@ function sortGraphPages(pages) {
10691
11220
  async function listApprovals(rootDir) {
10692
11221
  const { paths } = await loadVaultConfig(rootDir);
10693
11222
  const manifests = await Promise.all(
10694
- (await fs15.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
11223
+ (await fs16.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
10695
11224
  try {
10696
11225
  return await readApprovalManifest(paths, entry.name);
10697
11226
  } catch {
@@ -10707,8 +11236,8 @@ async function readApproval(rootDir, approvalId) {
10707
11236
  const details = await Promise.all(
10708
11237
  manifest.entries.map(async (entry) => {
10709
11238
  const currentPath = entry.previousPath ?? entry.nextPath;
10710
- const currentContent = currentPath ? await fs15.readFile(path18.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
10711
- const stagedContent = entry.nextPath ? await fs15.readFile(path18.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
11239
+ const currentContent = currentPath ? await fs16.readFile(path20.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
11240
+ const stagedContent = entry.nextPath ? await fs16.readFile(path20.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
10712
11241
  return {
10713
11242
  ...entry,
10714
11243
  currentContent,
@@ -10736,26 +11265,26 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
10736
11265
  if (!entry.nextPath) {
10737
11266
  throw new Error(`Approval entry ${entry.pageId} is missing a staged path.`);
10738
11267
  }
10739
- const stagedAbsolutePath = path18.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
10740
- const stagedContent = await fs15.readFile(stagedAbsolutePath, "utf8");
10741
- const targetAbsolutePath = path18.join(paths.wikiDir, entry.nextPath);
10742
- await ensureDir(path18.dirname(targetAbsolutePath));
10743
- await fs15.writeFile(targetAbsolutePath, stagedContent, "utf8");
11268
+ const stagedAbsolutePath = path20.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
11269
+ const stagedContent = await fs16.readFile(stagedAbsolutePath, "utf8");
11270
+ const targetAbsolutePath = path20.join(paths.wikiDir, entry.nextPath);
11271
+ await ensureDir(path20.dirname(targetAbsolutePath));
11272
+ await fs16.writeFile(targetAbsolutePath, stagedContent, "utf8");
10744
11273
  if (entry.changeType === "promote" && entry.previousPath) {
10745
- await fs15.rm(path18.join(paths.wikiDir, entry.previousPath), { force: true });
11274
+ await fs16.rm(path20.join(paths.wikiDir, entry.previousPath), { force: true });
10746
11275
  }
10747
11276
  const nextPage = bundleGraph?.pages.find((page) => page.id === entry.pageId && page.path === entry.nextPath) ?? parseStoredPage(entry.nextPath, stagedContent);
10748
11277
  if (nextPage.kind === "output" && nextPage.outputAssets?.length) {
10749
- const outputAssetDir = path18.join(paths.wikiDir, "outputs", "assets", path18.basename(nextPage.path, ".md"));
10750
- await fs15.rm(outputAssetDir, { recursive: true, force: true });
11278
+ const outputAssetDir = path20.join(paths.wikiDir, "outputs", "assets", path20.basename(nextPage.path, ".md"));
11279
+ await fs16.rm(outputAssetDir, { recursive: true, force: true });
10751
11280
  for (const asset of nextPage.outputAssets) {
10752
- const stagedAssetPath = path18.join(paths.approvalsDir, approvalId, "wiki", asset.path);
11281
+ const stagedAssetPath = path20.join(paths.approvalsDir, approvalId, "wiki", asset.path);
10753
11282
  if (!await fileExists(stagedAssetPath)) {
10754
11283
  continue;
10755
11284
  }
10756
- const targetAssetPath = path18.join(paths.wikiDir, asset.path);
10757
- await ensureDir(path18.dirname(targetAssetPath));
10758
- await fs15.copyFile(stagedAssetPath, targetAssetPath);
11285
+ const targetAssetPath = path20.join(paths.wikiDir, asset.path);
11286
+ await ensureDir(path20.dirname(targetAssetPath));
11287
+ await fs16.copyFile(stagedAssetPath, targetAssetPath);
10759
11288
  }
10760
11289
  }
10761
11290
  nextPages = nextPages.filter(
@@ -10766,10 +11295,10 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
10766
11295
  } else {
10767
11296
  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;
10768
11297
  if (entry.previousPath) {
10769
- await fs15.rm(path18.join(paths.wikiDir, entry.previousPath), { force: true });
11298
+ await fs16.rm(path20.join(paths.wikiDir, entry.previousPath), { force: true });
10770
11299
  }
10771
11300
  if (deletedPage?.kind === "output") {
10772
- await fs15.rm(path18.join(paths.wikiDir, "outputs", "assets", path18.basename(deletedPage.path, ".md")), {
11301
+ await fs16.rm(path20.join(paths.wikiDir, "outputs", "assets", path20.basename(deletedPage.path, ".md")), {
10773
11302
  recursive: true,
10774
11303
  force: true
10775
11304
  });
@@ -10860,7 +11389,7 @@ async function promoteCandidate(rootDir, target) {
10860
11389
  const { paths } = await loadVaultConfig(rootDir);
10861
11390
  const graph = await readJsonFile(paths.graphPath);
10862
11391
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
10863
- const raw = await fs15.readFile(path18.join(paths.wikiDir, candidate.path), "utf8");
11392
+ const raw = await fs16.readFile(path20.join(paths.wikiDir, candidate.path), "utf8");
10864
11393
  const parsed = matter9(raw);
10865
11394
  const nextUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
10866
11395
  const nextContent = matter9.stringify(parsed.content, {
@@ -10872,10 +11401,10 @@ async function promoteCandidate(rootDir, target) {
10872
11401
  )
10873
11402
  });
10874
11403
  const nextPath = candidateActivePath(candidate);
10875
- const nextAbsolutePath = path18.join(paths.wikiDir, nextPath);
10876
- await ensureDir(path18.dirname(nextAbsolutePath));
10877
- await fs15.writeFile(nextAbsolutePath, nextContent, "utf8");
10878
- await fs15.rm(path18.join(paths.wikiDir, candidate.path), { force: true });
11404
+ const nextAbsolutePath = path20.join(paths.wikiDir, nextPath);
11405
+ await ensureDir(path20.dirname(nextAbsolutePath));
11406
+ await fs16.writeFile(nextAbsolutePath, nextContent, "utf8");
11407
+ await fs16.rm(path20.join(paths.wikiDir, candidate.path), { force: true });
10879
11408
  const nextPage = parseStoredPage(nextPath, nextContent, { createdAt: candidate.createdAt, updatedAt: nextUpdatedAt });
10880
11409
  const nextPages = sortGraphPages(
10881
11410
  (graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path).concat(nextPage)
@@ -10920,7 +11449,7 @@ async function archiveCandidate(rootDir, target) {
10920
11449
  const { paths } = await loadVaultConfig(rootDir);
10921
11450
  const graph = await readJsonFile(paths.graphPath);
10922
11451
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
10923
- await fs15.rm(path18.join(paths.wikiDir, candidate.path), { force: true });
11452
+ await fs16.rm(path20.join(paths.wikiDir, candidate.path), { force: true });
10924
11453
  const nextPages = sortGraphPages((graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path));
10925
11454
  const nextGraph = {
10926
11455
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10959,18 +11488,18 @@ async function archiveCandidate(rootDir, target) {
10959
11488
  }
10960
11489
  async function ensureObsidianWorkspace(rootDir) {
10961
11490
  const { config } = await loadVaultConfig(rootDir);
10962
- const obsidianDir = path18.join(rootDir, ".obsidian");
11491
+ const obsidianDir = path20.join(rootDir, ".obsidian");
10963
11492
  const projectIds = projectEntries(config).map((project) => project.id);
10964
11493
  await ensureDir(obsidianDir);
10965
11494
  await Promise.all([
10966
- writeJsonFile(path18.join(obsidianDir, "app.json"), {
11495
+ writeJsonFile(path20.join(obsidianDir, "app.json"), {
10967
11496
  alwaysUpdateLinks: true,
10968
11497
  newFileLocation: "folder",
10969
11498
  newFileFolderPath: "wiki/insights",
10970
11499
  useMarkdownLinks: false,
10971
11500
  attachmentFolderPath: "raw/assets"
10972
11501
  }),
10973
- writeJsonFile(path18.join(obsidianDir, "core-plugins.json"), [
11502
+ writeJsonFile(path20.join(obsidianDir, "core-plugins.json"), [
10974
11503
  "file-explorer",
10975
11504
  "global-search",
10976
11505
  "switcher",
@@ -10980,7 +11509,7 @@ async function ensureObsidianWorkspace(rootDir) {
10980
11509
  "tag-pane",
10981
11510
  "page-preview"
10982
11511
  ]),
10983
- writeJsonFile(path18.join(obsidianDir, "graph.json"), {
11512
+ writeJsonFile(path20.join(obsidianDir, "graph.json"), {
10984
11513
  "collapse-filter": false,
10985
11514
  search: "",
10986
11515
  showTags: true,
@@ -10992,7 +11521,7 @@ async function ensureObsidianWorkspace(rootDir) {
10992
11521
  })),
10993
11522
  localJumps: false
10994
11523
  }),
10995
- writeJsonFile(path18.join(obsidianDir, "workspace.json"), {
11524
+ writeJsonFile(path20.join(obsidianDir, "workspace.json"), {
10996
11525
  active: "root",
10997
11526
  lastOpenFiles: ["wiki/index.md", "wiki/projects/index.md", "wiki/candidates/index.md", "wiki/insights/index.md"],
10998
11527
  left: {
@@ -11007,7 +11536,7 @@ async function ensureObsidianWorkspace(rootDir) {
11007
11536
  async function initVault(rootDir, options = {}) {
11008
11537
  const { paths } = await initWorkspace(rootDir);
11009
11538
  await installConfiguredAgents(rootDir);
11010
- const insightsIndexPath = path18.join(paths.wikiDir, "insights", "index.md");
11539
+ const insightsIndexPath = path20.join(paths.wikiDir, "insights", "index.md");
11011
11540
  const now = (/* @__PURE__ */ new Date()).toISOString();
11012
11541
  await writeFileIfChanged(
11013
11542
  insightsIndexPath,
@@ -11043,7 +11572,7 @@ async function initVault(rootDir, options = {}) {
11043
11572
  )
11044
11573
  );
11045
11574
  await writeFileIfChanged(
11046
- path18.join(paths.wikiDir, "projects", "index.md"),
11575
+ path20.join(paths.wikiDir, "projects", "index.md"),
11047
11576
  matter9.stringify(["# Projects", "", "- Run `swarmvault compile` to build project rollups.", ""].join("\n"), {
11048
11577
  page_id: "projects:index",
11049
11578
  kind: "index",
@@ -11065,7 +11594,7 @@ async function initVault(rootDir, options = {}) {
11065
11594
  })
11066
11595
  );
11067
11596
  await writeFileIfChanged(
11068
- path18.join(paths.wikiDir, "candidates", "index.md"),
11597
+ path20.join(paths.wikiDir, "candidates", "index.md"),
11069
11598
  matter9.stringify(["# Candidates", "", "- Run `swarmvault compile` to stage candidate pages.", ""].join("\n"), {
11070
11599
  page_id: "candidates:index",
11071
11600
  kind: "index",
@@ -11201,7 +11730,7 @@ async function compileVault(rootDir, options = {}) {
11201
11730
  ),
11202
11731
  Promise.all(
11203
11732
  clean.map(async (manifest) => {
11204
- const cached = await readJsonFile(path18.join(paths.analysesDir, `${manifest.sourceId}.json`));
11733
+ const cached = await readJsonFile(path20.join(paths.analysesDir, `${manifest.sourceId}.json`));
11205
11734
  if (cached) {
11206
11735
  return cached;
11207
11736
  }
@@ -11225,22 +11754,22 @@ async function compileVault(rootDir, options = {}) {
11225
11754
  }
11226
11755
  const enriched = enrichResolvedCodeImports(manifest, analysis, codeIndex);
11227
11756
  if (analysisSignature(enriched) !== analysisSignature(analysis)) {
11228
- await writeJsonFile(path18.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
11757
+ await writeJsonFile(path20.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
11229
11758
  }
11230
11759
  return enriched;
11231
11760
  })
11232
11761
  );
11233
11762
  await Promise.all([
11234
- ensureDir(path18.join(paths.wikiDir, "sources")),
11235
- ensureDir(path18.join(paths.wikiDir, "code")),
11236
- ensureDir(path18.join(paths.wikiDir, "concepts")),
11237
- ensureDir(path18.join(paths.wikiDir, "entities")),
11238
- ensureDir(path18.join(paths.wikiDir, "outputs")),
11239
- ensureDir(path18.join(paths.wikiDir, "projects")),
11240
- ensureDir(path18.join(paths.wikiDir, "insights")),
11241
- ensureDir(path18.join(paths.wikiDir, "candidates")),
11242
- ensureDir(path18.join(paths.wikiDir, "candidates", "concepts")),
11243
- ensureDir(path18.join(paths.wikiDir, "candidates", "entities"))
11763
+ ensureDir(path20.join(paths.wikiDir, "sources")),
11764
+ ensureDir(path20.join(paths.wikiDir, "code")),
11765
+ ensureDir(path20.join(paths.wikiDir, "concepts")),
11766
+ ensureDir(path20.join(paths.wikiDir, "entities")),
11767
+ ensureDir(path20.join(paths.wikiDir, "outputs")),
11768
+ ensureDir(path20.join(paths.wikiDir, "projects")),
11769
+ ensureDir(path20.join(paths.wikiDir, "insights")),
11770
+ ensureDir(path20.join(paths.wikiDir, "candidates")),
11771
+ ensureDir(path20.join(paths.wikiDir, "candidates", "concepts")),
11772
+ ensureDir(path20.join(paths.wikiDir, "candidates", "entities"))
11244
11773
  ]);
11245
11774
  const sync = await syncVaultArtifacts(rootDir, {
11246
11775
  schemas,
@@ -11387,7 +11916,7 @@ async function queryVault(rootDir, options) {
11387
11916
  assetFiles: staged.assetFiles
11388
11917
  }
11389
11918
  ]);
11390
- stagedPath = path18.join(approval.approvalDir, "wiki", staged.page.path);
11919
+ stagedPath = path20.join(approval.approvalDir, "wiki", staged.page.path);
11391
11920
  savedPageId = staged.page.id;
11392
11921
  approvalId = approval.approvalId;
11393
11922
  approvalDir = approval.approvalDir;
@@ -11643,9 +12172,9 @@ ${orchestrationNotes.join("\n")}
11643
12172
  approvalId = approval.approvalId;
11644
12173
  approvalDir = approval.approvalDir;
11645
12174
  stepResults.forEach((result, index) => {
11646
- result.stagedPath = path18.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
12175
+ result.stagedPath = path20.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
11647
12176
  });
11648
- stagedHubPath = path18.join(approval.approvalDir, "wiki", hubPage.path);
12177
+ stagedHubPath = path20.join(approval.approvalDir, "wiki", hubPage.path);
11649
12178
  } else {
11650
12179
  await refreshVaultAfterOutputSave(rootDir);
11651
12180
  }
@@ -11706,11 +12235,18 @@ async function ensureCompiledGraph(rootDir) {
11706
12235
  }
11707
12236
  return graph;
11708
12237
  }
11709
- async function queryGraphVault(rootDir, question, options = {}) {
12238
+ async function runResolvedGraphQuery(rootDir, graph, question, options = {}) {
11710
12239
  const { paths } = await loadVaultConfig(rootDir);
11711
- const graph = await ensureCompiledGraph(rootDir);
11712
12240
  const searchResults = searchPages(paths.searchDbPath, question, { limit: Math.max(5, options.budget ?? 10) });
11713
- return queryGraph(graph, question, searchResults, options);
12241
+ const semanticMatches = await semanticGraphMatches(rootDir, graph, question, Math.max(8, options.budget ?? 12)).catch(() => []);
12242
+ return queryGraph(graph, question, searchResults, {
12243
+ ...options,
12244
+ semanticMatches
12245
+ });
12246
+ }
12247
+ async function queryGraphVault(rootDir, question, options = {}) {
12248
+ const graph = await ensureCompiledGraph(rootDir);
12249
+ return runResolvedGraphQuery(rootDir, graph, question, options);
11714
12250
  }
11715
12251
  async function benchmarkVault(rootDir, options = {}) {
11716
12252
  const { config, paths } = await loadVaultConfig(rootDir);
@@ -11725,30 +12261,31 @@ async function benchmarkVault(rootDir, options = {}) {
11725
12261
  }
11726
12262
  }
11727
12263
  for (const page of graph.pages) {
11728
- const absolutePath = path18.join(paths.wikiDir, page.path);
12264
+ const absolutePath = path20.join(paths.wikiDir, page.path);
11729
12265
  if (!await fileExists(absolutePath)) {
11730
12266
  continue;
11731
12267
  }
11732
- const parsed = matter9(await fs15.readFile(absolutePath, "utf8"));
12268
+ const parsed = matter9(await fs16.readFile(absolutePath, "utf8"));
11733
12269
  pageContentsById.set(page.id, parsed.content);
11734
12270
  }
11735
12271
  const configuredQuestions = (config.benchmark?.questions ?? []).map((question) => normalizeWhitespace(question)).filter(Boolean);
11736
12272
  const maxQuestions = Math.max(1, options.maxQuestions ?? config.benchmark?.maxQuestions ?? 3);
11737
12273
  const questions = (options.questions ?? []).map((question) => normalizeWhitespace(question)).filter(Boolean);
11738
12274
  const sampleQuestions = (questions.length ? questions : configuredQuestions.length ? configuredQuestions : defaultBenchmarkQuestionsForGraph(graph, maxQuestions)).slice(0, maxQuestions);
11739
- const perQuestion = sampleQuestions.map((question) => {
11740
- const searchResults = searchPages(paths.searchDbPath, question, { limit: 12 });
11741
- const result = queryGraph(graph, question, searchResults, { budget: 12 });
11742
- const metrics = benchmarkQueryTokens(graph, result, pageContentsById);
11743
- return {
11744
- question,
11745
- queryTokens: metrics.queryTokens,
11746
- reduction: metrics.reduction,
11747
- visitedNodeIds: result.visitedNodeIds,
11748
- visitedEdgeIds: result.visitedEdgeIds,
11749
- pageIds: result.pageIds
11750
- };
11751
- });
12275
+ const perQuestion = await Promise.all(
12276
+ sampleQuestions.map(async (question) => {
12277
+ const result = await runResolvedGraphQuery(rootDir, graph, question, { budget: 12 });
12278
+ const metrics = benchmarkQueryTokens(graph, result, pageContentsById);
12279
+ return {
12280
+ question,
12281
+ queryTokens: metrics.queryTokens,
12282
+ reduction: metrics.reduction,
12283
+ visitedNodeIds: result.visitedNodeIds,
12284
+ visitedEdgeIds: result.visitedEdgeIds,
12285
+ pageIds: result.pageIds
12286
+ };
12287
+ })
12288
+ );
11752
12289
  const artifact = buildBenchmarkArtifact({
11753
12290
  graph,
11754
12291
  corpusWords,
@@ -11773,7 +12310,7 @@ async function listGraphHyperedges(rootDir, target, limit = 25) {
11773
12310
  }
11774
12311
  async function readGraphReport(rootDir) {
11775
12312
  const { paths } = await loadVaultConfig(rootDir);
11776
- return readJsonFile(path18.join(paths.wikiDir, "graph", "report.json"));
12313
+ return readJsonFile(path20.join(paths.wikiDir, "graph", "report.json"));
11777
12314
  }
11778
12315
  async function listGodNodes(rootDir, limit = 10) {
11779
12316
  const graph = await ensureCompiledGraph(rootDir);
@@ -11786,15 +12323,15 @@ async function listPages(rootDir) {
11786
12323
  }
11787
12324
  async function readPage(rootDir, relativePath) {
11788
12325
  const { paths } = await loadVaultConfig(rootDir);
11789
- const absolutePath = path18.resolve(paths.wikiDir, relativePath);
12326
+ const absolutePath = path20.resolve(paths.wikiDir, relativePath);
11790
12327
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
11791
12328
  return null;
11792
12329
  }
11793
- const raw = await fs15.readFile(absolutePath, "utf8");
12330
+ const raw = await fs16.readFile(absolutePath, "utf8");
11794
12331
  const parsed = matter9(raw);
11795
12332
  return {
11796
12333
  path: relativePath,
11797
- title: typeof parsed.data.title === "string" ? parsed.data.title : path18.basename(relativePath, path18.extname(relativePath)),
12334
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path20.basename(relativePath, path20.extname(relativePath)),
11798
12335
  frontmatter: parsed.data,
11799
12336
  content: parsed.content
11800
12337
  };
@@ -11830,7 +12367,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
11830
12367
  severity: "warning",
11831
12368
  code: "stale_page",
11832
12369
  message: `Page ${page.title} is stale because the vault schema changed.`,
11833
- pagePath: path18.join(paths.wikiDir, page.path),
12370
+ pagePath: path20.join(paths.wikiDir, page.path),
11834
12371
  relatedPageIds: [page.id]
11835
12372
  });
11836
12373
  }
@@ -11841,7 +12378,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
11841
12378
  severity: "warning",
11842
12379
  code: "stale_page",
11843
12380
  message: `Page ${page.title} is stale because source ${sourceId} changed.`,
11844
- pagePath: path18.join(paths.wikiDir, page.path),
12381
+ pagePath: path20.join(paths.wikiDir, page.path),
11845
12382
  relatedSourceIds: [sourceId],
11846
12383
  relatedPageIds: [page.id]
11847
12384
  });
@@ -11852,13 +12389,13 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
11852
12389
  severity: "info",
11853
12390
  code: "orphan_page",
11854
12391
  message: `Page ${page.title} has no backlinks.`,
11855
- pagePath: path18.join(paths.wikiDir, page.path),
12392
+ pagePath: path20.join(paths.wikiDir, page.path),
11856
12393
  relatedPageIds: [page.id]
11857
12394
  });
11858
12395
  }
11859
- const absolutePath = path18.join(paths.wikiDir, page.path);
12396
+ const absolutePath = path20.join(paths.wikiDir, page.path);
11860
12397
  if (await fileExists(absolutePath)) {
11861
- const content = await fs15.readFile(absolutePath, "utf8");
12398
+ const content = await fs16.readFile(absolutePath, "utf8");
11862
12399
  if (content.includes("## Claims")) {
11863
12400
  const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
11864
12401
  if (uncited.length) {
@@ -11938,7 +12475,7 @@ async function bootstrapDemo(rootDir, input) {
11938
12475
  }
11939
12476
 
11940
12477
  // src/mcp.ts
11941
- var SERVER_VERSION = "0.1.24";
12478
+ var SERVER_VERSION = "0.1.26";
11942
12479
  async function createMcpServer(rootDir) {
11943
12480
  const server = new McpServer({
11944
12481
  name: "swarmvault",
@@ -12209,7 +12746,7 @@ async function createMcpServer(rootDir) {
12209
12746
  },
12210
12747
  async () => {
12211
12748
  const { paths } = await loadVaultConfig(rootDir);
12212
- const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path19.relative(paths.sessionsDir, filePath))).sort();
12749
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path21.relative(paths.sessionsDir, filePath))).sort();
12213
12750
  return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
12214
12751
  }
12215
12752
  );
@@ -12242,8 +12779,8 @@ async function createMcpServer(rootDir) {
12242
12779
  return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
12243
12780
  }
12244
12781
  const { paths } = await loadVaultConfig(rootDir);
12245
- const absolutePath = path19.resolve(paths.wikiDir, relativePath);
12246
- return asTextResource(`swarmvault://pages/${encodedPath}`, await fs16.readFile(absolutePath, "utf8"));
12782
+ const absolutePath = path21.resolve(paths.wikiDir, relativePath);
12783
+ return asTextResource(`swarmvault://pages/${encodedPath}`, await fs17.readFile(absolutePath, "utf8"));
12247
12784
  }
12248
12785
  );
12249
12786
  server.registerResource(
@@ -12251,11 +12788,11 @@ async function createMcpServer(rootDir) {
12251
12788
  new ResourceTemplate("swarmvault://sessions/{path}", {
12252
12789
  list: async () => {
12253
12790
  const { paths } = await loadVaultConfig(rootDir);
12254
- const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path19.relative(paths.sessionsDir, filePath))).sort();
12791
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path21.relative(paths.sessionsDir, filePath))).sort();
12255
12792
  return {
12256
12793
  resources: files.map((relativePath) => ({
12257
12794
  uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
12258
- name: path19.basename(relativePath, ".md"),
12795
+ name: path21.basename(relativePath, ".md"),
12259
12796
  title: relativePath,
12260
12797
  description: "SwarmVault session artifact",
12261
12798
  mimeType: "text/markdown"
@@ -12272,11 +12809,11 @@ async function createMcpServer(rootDir) {
12272
12809
  const { paths } = await loadVaultConfig(rootDir);
12273
12810
  const encodedPath = typeof variables.path === "string" ? variables.path : "";
12274
12811
  const relativePath = decodeURIComponent(encodedPath);
12275
- const absolutePath = path19.resolve(paths.sessionsDir, relativePath);
12812
+ const absolutePath = path21.resolve(paths.sessionsDir, relativePath);
12276
12813
  if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
12277
12814
  return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
12278
12815
  }
12279
- return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs16.readFile(absolutePath, "utf8"));
12816
+ return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs17.readFile(absolutePath, "utf8"));
12280
12817
  }
12281
12818
  );
12282
12819
  return server;
@@ -12324,13 +12861,13 @@ function asTextResource(uri, text) {
12324
12861
  }
12325
12862
 
12326
12863
  // src/schedule.ts
12327
- import fs17 from "fs/promises";
12328
- import path20 from "path";
12864
+ import fs18 from "fs/promises";
12865
+ import path22 from "path";
12329
12866
  function scheduleStatePath(schedulesDir, jobId) {
12330
- return path20.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
12867
+ return path22.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
12331
12868
  }
12332
12869
  function scheduleLockPath(schedulesDir, jobId) {
12333
- return path20.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
12870
+ return path22.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
12334
12871
  }
12335
12872
  function parseEveryDuration(value) {
12336
12873
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -12433,13 +12970,13 @@ async function acquireJobLease(rootDir, jobId) {
12433
12970
  const { paths } = await loadVaultConfig(rootDir);
12434
12971
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
12435
12972
  await ensureDir(paths.schedulesDir);
12436
- const handle = await fs17.open(leasePath, "wx");
12973
+ const handle = await fs18.open(leasePath, "wx");
12437
12974
  await handle.writeFile(`${process.pid}
12438
12975
  ${(/* @__PURE__ */ new Date()).toISOString()}
12439
12976
  `);
12440
12977
  await handle.close();
12441
12978
  return async () => {
12442
- await fs17.rm(leasePath, { force: true });
12979
+ await fs18.rm(leasePath, { force: true });
12443
12980
  };
12444
12981
  }
12445
12982
  async function listSchedules(rootDir) {
@@ -12587,31 +13124,31 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
12587
13124
 
12588
13125
  // src/viewer.ts
12589
13126
  import { execFile } from "child_process";
12590
- import fs18 from "fs/promises";
13127
+ import fs19 from "fs/promises";
12591
13128
  import http from "http";
12592
- import path22 from "path";
13129
+ import path24 from "path";
12593
13130
  import { promisify } from "util";
12594
13131
  import matter10 from "gray-matter";
12595
13132
  import mime2 from "mime-types";
12596
13133
 
12597
13134
  // src/watch.ts
12598
- import path21 from "path";
13135
+ import path23 from "path";
12599
13136
  import process2 from "process";
12600
13137
  import chokidar from "chokidar";
12601
13138
  var MAX_BACKOFF_MS = 3e4;
12602
13139
  var BACKOFF_THRESHOLD = 3;
12603
13140
  var CRITICAL_THRESHOLD = 10;
12604
- var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", "coverage", ".venv", "vendor", "target"]);
13141
+ var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
12605
13142
  function withinRoot2(rootPath, targetPath) {
12606
- const relative = path21.relative(rootPath, targetPath);
12607
- return relative === "" || !relative.startsWith("..") && !path21.isAbsolute(relative);
13143
+ const relative = path23.relative(rootPath, targetPath);
13144
+ return relative === "" || !relative.startsWith("..") && !path23.isAbsolute(relative);
12608
13145
  }
12609
13146
  function hasIgnoredRepoSegment(baseDir, targetPath) {
12610
- const relativePath = path21.relative(baseDir, targetPath);
13147
+ const relativePath = path23.relative(baseDir, targetPath);
12611
13148
  if (!relativePath || relativePath.startsWith("..")) {
12612
13149
  return false;
12613
13150
  }
12614
- return relativePath.split(path21.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
13151
+ return relativePath.split(path23.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
12615
13152
  }
12616
13153
  function workspaceIgnoreRoots(rootDir, paths) {
12617
13154
  return [
@@ -12620,16 +13157,16 @@ function workspaceIgnoreRoots(rootDir, paths) {
12620
13157
  paths.stateDir,
12621
13158
  paths.agentDir,
12622
13159
  paths.inboxDir,
12623
- path21.join(rootDir, ".claude"),
12624
- path21.join(rootDir, ".cursor"),
12625
- path21.join(rootDir, ".obsidian")
12626
- ].map((candidate) => path21.resolve(candidate));
13160
+ path23.join(rootDir, ".claude"),
13161
+ path23.join(rootDir, ".cursor"),
13162
+ path23.join(rootDir, ".obsidian")
13163
+ ].map((candidate) => path23.resolve(candidate));
12627
13164
  }
12628
13165
  async function resolveWatchTargets(rootDir, paths, options) {
12629
- const targets = /* @__PURE__ */ new Set([path21.resolve(paths.inboxDir)]);
13166
+ const targets = /* @__PURE__ */ new Set([path23.resolve(paths.inboxDir)]);
12630
13167
  if (options.repo) {
12631
13168
  for (const repoRoot of await listTrackedRepoRoots(rootDir)) {
12632
- targets.add(path21.resolve(repoRoot));
13169
+ targets.add(path23.resolve(repoRoot));
12633
13170
  }
12634
13171
  }
12635
13172
  return [...targets].sort((left, right) => left.localeCompare(right));
@@ -12759,7 +13296,7 @@ async function watchVault(rootDir, options = {}) {
12759
13296
  const { paths } = await initWorkspace(rootDir);
12760
13297
  const baseDebounceMs = options.debounceMs ?? 900;
12761
13298
  const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
12762
- const inboxWatchRoot = path21.resolve(paths.inboxDir);
13299
+ const inboxWatchRoot = path23.resolve(paths.inboxDir);
12763
13300
  let watchTargets = await resolveWatchTargets(rootDir, paths, options);
12764
13301
  let timer;
12765
13302
  let running = false;
@@ -12773,7 +13310,7 @@ async function watchVault(rootDir, options = {}) {
12773
13310
  usePolling: true,
12774
13311
  interval: 100,
12775
13312
  ignored: (targetPath) => {
12776
- const absolutePath = path21.resolve(targetPath);
13313
+ const absolutePath = path23.resolve(targetPath);
12777
13314
  const primaryTarget = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
12778
13315
  if (!primaryTarget) {
12779
13316
  return false;
@@ -12945,8 +13482,8 @@ async function watchVault(rootDir, options = {}) {
12945
13482
  }
12946
13483
  };
12947
13484
  const reasonForPath = (targetPath) => {
12948
- const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, path21.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
12949
- return path21.relative(baseDir, targetPath) || ".";
13485
+ const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, path23.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
13486
+ return path23.relative(baseDir, targetPath) || ".";
12950
13487
  };
12951
13488
  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)}`));
12952
13489
  await new Promise((resolve, reject) => {
@@ -12987,15 +13524,15 @@ async function getWatchStatus(rootDir) {
12987
13524
  var execFileAsync = promisify(execFile);
12988
13525
  async function readViewerPage(rootDir, relativePath) {
12989
13526
  const { paths } = await loadVaultConfig(rootDir);
12990
- const absolutePath = path22.resolve(paths.wikiDir, relativePath);
13527
+ const absolutePath = path24.resolve(paths.wikiDir, relativePath);
12991
13528
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
12992
13529
  return null;
12993
13530
  }
12994
- const raw = await fs18.readFile(absolutePath, "utf8");
13531
+ const raw = await fs19.readFile(absolutePath, "utf8");
12995
13532
  const parsed = matter10(raw);
12996
13533
  return {
12997
13534
  path: relativePath,
12998
- title: typeof parsed.data.title === "string" ? parsed.data.title : path22.basename(relativePath, path22.extname(relativePath)),
13535
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path24.basename(relativePath, path24.extname(relativePath)),
12999
13536
  frontmatter: parsed.data,
13000
13537
  content: parsed.content,
13001
13538
  assets: normalizeOutputAssets(parsed.data.output_assets)
@@ -13003,12 +13540,12 @@ async function readViewerPage(rootDir, relativePath) {
13003
13540
  }
13004
13541
  async function readViewerAsset(rootDir, relativePath) {
13005
13542
  const { paths } = await loadVaultConfig(rootDir);
13006
- const absolutePath = path22.resolve(paths.wikiDir, relativePath);
13543
+ const absolutePath = path24.resolve(paths.wikiDir, relativePath);
13007
13544
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
13008
13545
  return null;
13009
13546
  }
13010
13547
  return {
13011
- buffer: await fs18.readFile(absolutePath),
13548
+ buffer: await fs19.readFile(absolutePath),
13012
13549
  mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
13013
13550
  };
13014
13551
  }
@@ -13031,12 +13568,12 @@ async function readJsonBody(request) {
13031
13568
  return JSON.parse(raw);
13032
13569
  }
13033
13570
  async function ensureViewerDist(viewerDistDir) {
13034
- const indexPath = path22.join(viewerDistDir, "index.html");
13571
+ const indexPath = path24.join(viewerDistDir, "index.html");
13035
13572
  if (await fileExists(indexPath)) {
13036
13573
  return;
13037
13574
  }
13038
- const viewerProjectDir = path22.dirname(viewerDistDir);
13039
- if (await fileExists(path22.join(viewerProjectDir, "package.json"))) {
13575
+ const viewerProjectDir = path24.dirname(viewerDistDir);
13576
+ if (await fileExists(path24.join(viewerProjectDir, "package.json"))) {
13040
13577
  await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
13041
13578
  }
13042
13579
  }
@@ -13053,7 +13590,7 @@ async function startGraphServer(rootDir, port) {
13053
13590
  return;
13054
13591
  }
13055
13592
  response.writeHead(200, { "content-type": "application/json" });
13056
- response.end(await fs18.readFile(paths.graphPath, "utf8"));
13593
+ response.end(await fs19.readFile(paths.graphPath, "utf8"));
13057
13594
  return;
13058
13595
  }
13059
13596
  if (url.pathname === "/api/graph/query") {
@@ -13096,26 +13633,28 @@ async function startGraphServer(rootDir, port) {
13096
13633
  const status = url.searchParams.get("status") ?? "all";
13097
13634
  const project = url.searchParams.get("project") ?? "all";
13098
13635
  const sourceType = url.searchParams.get("sourceType") ?? "all";
13636
+ const sourceClass = url.searchParams.get("sourceClass") ?? "all";
13099
13637
  const results = searchPages(paths.searchDbPath, query, {
13100
13638
  limit: Number.isFinite(limit) ? limit : 10,
13101
13639
  kind,
13102
13640
  status,
13103
13641
  project,
13104
- sourceType
13642
+ sourceType,
13643
+ sourceClass
13105
13644
  });
13106
13645
  response.writeHead(200, { "content-type": "application/json" });
13107
13646
  response.end(JSON.stringify(results));
13108
13647
  return;
13109
13648
  }
13110
13649
  if (url.pathname === "/api/graph-report") {
13111
- const reportPath = path22.join(paths.wikiDir, "graph", "report.json");
13650
+ const reportPath = path24.join(paths.wikiDir, "graph", "report.json");
13112
13651
  if (!await fileExists(reportPath)) {
13113
13652
  response.writeHead(404, { "content-type": "application/json" });
13114
13653
  response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
13115
13654
  return;
13116
13655
  }
13117
13656
  response.writeHead(200, { "content-type": "application/json" });
13118
- response.end(await fs18.readFile(reportPath, "utf8"));
13657
+ response.end(await fs19.readFile(reportPath, "utf8"));
13119
13658
  return;
13120
13659
  }
13121
13660
  if (url.pathname === "/api/watch-status") {
@@ -13198,8 +13737,8 @@ async function startGraphServer(rootDir, port) {
13198
13737
  return;
13199
13738
  }
13200
13739
  const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
13201
- const target = path22.join(paths.viewerDistDir, relativePath);
13202
- const fallback = path22.join(paths.viewerDistDir, "index.html");
13740
+ const target = path24.join(paths.viewerDistDir, relativePath);
13741
+ const fallback = path24.join(paths.viewerDistDir, "index.html");
13203
13742
  const filePath = await fileExists(target) ? target : fallback;
13204
13743
  if (!await fileExists(filePath)) {
13205
13744
  response.writeHead(503, { "content-type": "text/plain" });
@@ -13207,7 +13746,7 @@ async function startGraphServer(rootDir, port) {
13207
13746
  return;
13208
13747
  }
13209
13748
  response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
13210
- response.end(await fs18.readFile(filePath));
13749
+ response.end(await fs19.readFile(filePath));
13211
13750
  });
13212
13751
  await new Promise((resolve) => {
13213
13752
  server.listen(effectivePort, resolve);
@@ -13234,7 +13773,7 @@ async function exportGraphHtml(rootDir, outputPath) {
13234
13773
  throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
13235
13774
  }
13236
13775
  await ensureViewerDist(paths.viewerDistDir);
13237
- const indexPath = path22.join(paths.viewerDistDir, "index.html");
13776
+ const indexPath = path24.join(paths.viewerDistDir, "index.html");
13238
13777
  if (!await fileExists(indexPath)) {
13239
13778
  throw new Error("Viewer build not found. Run `pnpm build` first.");
13240
13779
  }
@@ -13248,6 +13787,7 @@ async function exportGraphHtml(rootDir, outputPath) {
13248
13787
  kind: page.kind,
13249
13788
  status: page.status,
13250
13789
  sourceType: page.sourceType,
13790
+ sourceClass: page.sourceClass,
13251
13791
  projectIds: page.projectIds,
13252
13792
  content: loaded.content,
13253
13793
  assets: await Promise.all(
@@ -13259,17 +13799,17 @@ async function exportGraphHtml(rootDir, outputPath) {
13259
13799
  } : null;
13260
13800
  })
13261
13801
  );
13262
- const rawHtml = await fs18.readFile(indexPath, "utf8");
13802
+ const rawHtml = await fs19.readFile(indexPath, "utf8");
13263
13803
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
13264
13804
  const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
13265
- const scriptPath = scriptMatch?.[1] ? path22.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
13266
- const stylePath = styleMatch?.[1] ? path22.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
13805
+ const scriptPath = scriptMatch?.[1] ? path24.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
13806
+ const stylePath = styleMatch?.[1] ? path24.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
13267
13807
  if (!scriptPath || !await fileExists(scriptPath)) {
13268
13808
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
13269
13809
  }
13270
- const script = await fs18.readFile(scriptPath, "utf8");
13271
- const style = stylePath && await fileExists(stylePath) ? await fs18.readFile(stylePath, "utf8") : "";
13272
- const report = await readJsonFile(path22.join(paths.wikiDir, "graph", "report.json"));
13810
+ const script = await fs19.readFile(scriptPath, "utf8");
13811
+ const style = stylePath && await fileExists(stylePath) ? await fs19.readFile(stylePath, "utf8") : "";
13812
+ const report = await readJsonFile(path24.join(paths.wikiDir, "graph", "report.json"));
13273
13813
  const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean), report }, null, 2).replace(/</g, "\\u003c");
13274
13814
  const html = [
13275
13815
  "<!doctype html>",
@@ -13288,9 +13828,9 @@ async function exportGraphHtml(rootDir, outputPath) {
13288
13828
  "</html>",
13289
13829
  ""
13290
13830
  ].filter(Boolean).join("\n");
13291
- await fs18.mkdir(path22.dirname(outputPath), { recursive: true });
13292
- await fs18.writeFile(outputPath, html, "utf8");
13293
- return path22.resolve(outputPath);
13831
+ await fs19.mkdir(path24.dirname(outputPath), { recursive: true });
13832
+ await fs19.writeFile(outputPath, html, "utf8");
13833
+ return path24.resolve(outputPath);
13294
13834
  }
13295
13835
  export {
13296
13836
  acceptApproval,