@swarmvaultai/engine 0.1.25 → 0.1.27

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,8 @@ 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
+ import { pathToFileURL } from "url";
666
667
  import { Readability } from "@mozilla/readability";
667
668
  import matter3 from "gray-matter";
668
669
  import ignore from "ignore";
@@ -3143,9 +3144,68 @@ async function appendWatchRun(rootDir, run) {
3143
3144
  await appendJsonLine(paths.jobsLogPath, run);
3144
3145
  }
3145
3146
 
3147
+ // src/source-classification.ts
3148
+ import path8 from "path";
3149
+ var ALL_SOURCE_CLASSES = ["first_party", "third_party", "resource", "generated"];
3150
+ var THIRD_PARTY_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "vendor", "Pods"]);
3151
+ var GENERATED_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", ".next", "coverage", "DerivedData", "target"]);
3152
+ function matchesAnyGlob(relativePath, patterns) {
3153
+ return patterns.some(
3154
+ (pattern) => path8.matchesGlob(relativePath, pattern) || path8.matchesGlob(path8.posix.basename(relativePath), pattern)
3155
+ );
3156
+ }
3157
+ function classifyRepoPath(relativePath, repoAnalysis) {
3158
+ const normalized = relativePath.replace(/\\/g, "/");
3159
+ const custom = repoAnalysis?.classifyGlobs;
3160
+ if (custom?.first_party?.length && matchesAnyGlob(normalized, custom.first_party)) {
3161
+ return "first_party";
3162
+ }
3163
+ for (const sourceClass of ["third_party", "resource", "generated"]) {
3164
+ const patterns = custom?.[sourceClass];
3165
+ if (patterns?.length && matchesAnyGlob(normalized, patterns)) {
3166
+ return sourceClass;
3167
+ }
3168
+ }
3169
+ const segments = normalized.split("/").filter(Boolean);
3170
+ if (segments.some((segment) => THIRD_PARTY_SEGMENTS.has(segment))) {
3171
+ return "third_party";
3172
+ }
3173
+ if (segments.some((segment) => GENERATED_SEGMENTS.has(segment))) {
3174
+ return "generated";
3175
+ }
3176
+ if (segments.some((segment) => segment.endsWith(".xcassets") || segment.endsWith(".imageset"))) {
3177
+ return "resource";
3178
+ }
3179
+ return "first_party";
3180
+ }
3181
+ function normalizeExtractClasses(repoAnalysis, extra = []) {
3182
+ const configured = repoAnalysis?.extractClasses?.length ? repoAnalysis.extractClasses : ["first_party"];
3183
+ return ALL_SOURCE_CLASSES.filter((sourceClass) => (/* @__PURE__ */ new Set([...configured, ...extra])).has(sourceClass));
3184
+ }
3185
+ function aggregateSourceClass(values) {
3186
+ const available = ALL_SOURCE_CLASSES.filter((sourceClass) => values.includes(sourceClass));
3187
+ if (!available.length) {
3188
+ return void 0;
3189
+ }
3190
+ if (available.includes("first_party")) {
3191
+ return "first_party";
3192
+ }
3193
+ if (available.includes("resource")) {
3194
+ return "resource";
3195
+ }
3196
+ if (available.includes("third_party")) {
3197
+ return "third_party";
3198
+ }
3199
+ return "generated";
3200
+ }
3201
+ function aggregateManifestSourceClass(manifests, sourceIds) {
3202
+ const byId = new Map(manifests.map((manifest) => [manifest.sourceId, manifest.sourceClass]));
3203
+ return aggregateSourceClass(sourceIds.map((sourceId) => byId.get(sourceId)));
3204
+ }
3205
+
3146
3206
  // src/watch-state.ts
3147
3207
  import fs8 from "fs/promises";
3148
- import path8 from "path";
3208
+ import path9 from "path";
3149
3209
  import matter2 from "gray-matter";
3150
3210
  function pendingEntryKey(entry) {
3151
3211
  return entry.path;
@@ -3159,7 +3219,7 @@ function normalizeRelativePath(rootDir, filePath) {
3159
3219
  if (!filePath) {
3160
3220
  return void 0;
3161
3221
  }
3162
- return toPosix(path8.relative(rootDir, path8.resolve(filePath)));
3222
+ return toPosix(path9.relative(rootDir, path9.resolve(filePath)));
3163
3223
  }
3164
3224
  async function readPendingSemanticRefresh(rootDir) {
3165
3225
  const { paths } = await initWorkspace(rootDir);
@@ -3253,7 +3313,7 @@ async function markPagesStaleForSources(rootDir, sourceIds) {
3253
3313
  if (page.freshness !== "stale" || !page.sourceIds.some((sourceId) => affectedSourceIds.has(sourceId))) {
3254
3314
  continue;
3255
3315
  }
3256
- const absolutePath = path8.join(paths.wikiDir, page.path);
3316
+ const absolutePath = path9.join(paths.wikiDir, page.path);
3257
3317
  if (!await fileExists(absolutePath)) {
3258
3318
  continue;
3259
3319
  }
@@ -3272,7 +3332,7 @@ async function markPagesStaleForSources(rootDir, sourceIds) {
3272
3332
  // src/ingest.ts
3273
3333
  var DEFAULT_MAX_ASSET_SIZE = 10 * 1024 * 1024;
3274
3334
  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"]);
3335
+ var HARD_REPO_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
3276
3336
  function uniqueStrings(values) {
3277
3337
  return [...new Set(values.filter(Boolean))];
3278
3338
  }
@@ -3283,6 +3343,9 @@ function inferKind(mimeType, filePath) {
3283
3343
  if (mimeType.includes("markdown")) {
3284
3344
  return "markdown";
3285
3345
  }
3346
+ if (mimeType.includes("html")) {
3347
+ return "html";
3348
+ }
3286
3349
  if (mimeType.startsWith("text/")) {
3287
3350
  return "text";
3288
3351
  }
@@ -3292,9 +3355,6 @@ function inferKind(mimeType, filePath) {
3292
3355
  if (mimeType.startsWith("image/")) {
3293
3356
  return "image";
3294
3357
  }
3295
- if (mimeType.includes("html")) {
3296
- return "html";
3297
- }
3298
3358
  return "binary";
3299
3359
  }
3300
3360
  function titleFromText(fallback, content) {
@@ -3308,36 +3368,47 @@ function normalizeIngestOptions(options) {
3308
3368
  return {
3309
3369
  includeAssets: options?.includeAssets ?? true,
3310
3370
  maxAssetSize: Math.max(0, Math.floor(options?.maxAssetSize ?? DEFAULT_MAX_ASSET_SIZE)),
3311
- repoRoot: options?.repoRoot ? path9.resolve(options.repoRoot) : void 0,
3371
+ repoRoot: options?.repoRoot ? path10.resolve(options.repoRoot) : void 0,
3312
3372
  include: (options?.include ?? []).map((pattern) => pattern.trim()).filter(Boolean),
3313
3373
  exclude: (options?.exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean),
3314
3374
  maxFiles: Math.max(1, Math.floor(options?.maxFiles ?? DEFAULT_MAX_DIRECTORY_FILES)),
3315
- gitignore: options?.gitignore ?? true
3375
+ gitignore: options?.gitignore ?? true,
3376
+ extractClasses: options?.extractClasses ?? ["first_party"]
3316
3377
  };
3317
3378
  }
3318
- function matchesAnyGlob(relativePath, patterns) {
3379
+ async function resolveRepoIngestOptions(rootDir, options) {
3380
+ const normalized = normalizeIngestOptions(options);
3381
+ const { config } = await loadVaultConfig(rootDir);
3382
+ const repoAnalysis = config.repoAnalysis;
3383
+ return {
3384
+ ...normalized,
3385
+ extractClasses: normalizeExtractClasses(repoAnalysis, normalized.extractClasses),
3386
+ repoAnalysis
3387
+ };
3388
+ }
3389
+ function matchesAnyGlob2(relativePath, patterns) {
3319
3390
  return patterns.some(
3320
- (pattern) => path9.matchesGlob(relativePath, pattern) || path9.matchesGlob(path9.posix.basename(relativePath), pattern)
3391
+ (pattern) => path10.matchesGlob(relativePath, pattern) || path10.matchesGlob(path10.posix.basename(relativePath), pattern)
3321
3392
  );
3322
3393
  }
3323
3394
  function supportedDirectoryKind(sourceKind) {
3324
3395
  return sourceKind !== "binary";
3325
3396
  }
3326
3397
  async function findNearestGitRoot2(startPath) {
3327
- let current = path9.resolve(startPath);
3398
+ let current = path10.resolve(startPath);
3328
3399
  try {
3329
3400
  const stat = await fs9.stat(current);
3330
3401
  if (!stat.isDirectory()) {
3331
- current = path9.dirname(current);
3402
+ current = path10.dirname(current);
3332
3403
  }
3333
3404
  } catch {
3334
- current = path9.dirname(current);
3405
+ current = path10.dirname(current);
3335
3406
  }
3336
3407
  while (true) {
3337
- if (await fileExists(path9.join(current, ".git"))) {
3408
+ if (await fileExists(path10.join(current, ".git"))) {
3338
3409
  return current;
3339
3410
  }
3340
- const parent = path9.dirname(current);
3411
+ const parent = path10.dirname(current);
3341
3412
  if (parent === current) {
3342
3413
  return null;
3343
3414
  }
@@ -3345,26 +3416,26 @@ async function findNearestGitRoot2(startPath) {
3345
3416
  }
3346
3417
  }
3347
3418
  function withinRoot(rootPath, targetPath) {
3348
- const relative = path9.relative(rootPath, targetPath);
3349
- return relative === "" || !relative.startsWith("..") && !path9.isAbsolute(relative);
3419
+ const relative = path10.relative(rootPath, targetPath);
3420
+ return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
3350
3421
  }
3351
3422
  function repoRootFromManifest(manifest) {
3352
3423
  if (manifest.originType !== "file" || !manifest.originalPath || !manifest.repoRelativePath) {
3353
3424
  return null;
3354
3425
  }
3355
- const repoDir = path9.posix.dirname(manifest.repoRelativePath);
3356
- const fileDir = path9.dirname(path9.resolve(manifest.originalPath));
3426
+ const repoDir = path10.posix.dirname(manifest.repoRelativePath);
3427
+ const fileDir = path10.dirname(path10.resolve(manifest.originalPath));
3357
3428
  if (repoDir === "." || !repoDir) {
3358
3429
  return fileDir;
3359
3430
  }
3360
3431
  const segments = repoDir.split("/").filter(Boolean);
3361
- return path9.resolve(fileDir, ...segments.map(() => ".."));
3432
+ return path10.resolve(fileDir, ...segments.map(() => ".."));
3362
3433
  }
3363
3434
  function repoRelativePathFor(absolutePath, repoRoot) {
3364
3435
  if (!repoRoot || !withinRoot(repoRoot, absolutePath)) {
3365
3436
  return void 0;
3366
3437
  }
3367
- const relative = toPosix(path9.relative(repoRoot, absolutePath));
3438
+ const relative = toPosix(path10.relative(repoRoot, absolutePath));
3368
3439
  return relative && !relative.startsWith("..") ? relative : void 0;
3369
3440
  }
3370
3441
  function normalizeOriginUrl(input) {
@@ -3661,7 +3732,7 @@ function buildCompositeHash(payloadBytes, attachments = []) {
3661
3732
  return sha256(`${sha256(payloadBytes)}|${attachmentSignature}`);
3662
3733
  }
3663
3734
  function sanitizeAssetRelativePath(value) {
3664
- const normalized = path9.posix.normalize(value.replace(/\\/g, "/"));
3735
+ const normalized = path10.posix.normalize(value.replace(/\\/g, "/"));
3665
3736
  const segments = normalized.split("/").filter(Boolean).map((segment) => {
3666
3737
  if (segment === ".") {
3667
3738
  return "";
@@ -3681,7 +3752,7 @@ function normalizeLocalReference(value) {
3681
3752
  return null;
3682
3753
  }
3683
3754
  const lowered = candidate.toLowerCase();
3684
- if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path9.isAbsolute(candidate)) {
3755
+ if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path10.isAbsolute(candidate)) {
3685
3756
  return null;
3686
3757
  }
3687
3758
  return candidate.replace(/\\/g, "/");
@@ -3748,7 +3819,7 @@ async function readManifestByHash(manifestsDir, contentHash) {
3748
3819
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
3749
3820
  continue;
3750
3821
  }
3751
- const manifest = await readJsonFile(path9.join(manifestsDir, entry.name));
3822
+ const manifest = await readJsonFile(path10.join(manifestsDir, entry.name));
3752
3823
  if (manifest?.contentHash === contentHash) {
3753
3824
  return manifest;
3754
3825
  }
@@ -3761,7 +3832,7 @@ async function readManifestByOrigin(manifestsDir, prepared) {
3761
3832
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
3762
3833
  continue;
3763
3834
  }
3764
- const manifest = await readJsonFile(path9.join(manifestsDir, entry.name));
3835
+ const manifest = await readJsonFile(path10.join(manifestsDir, entry.name));
3765
3836
  if (manifest && manifestMatchesOrigin(manifest, prepared)) {
3766
3837
  return manifest;
3767
3838
  }
@@ -3772,7 +3843,7 @@ async function loadGitignoreMatcher(repoRoot, enabled) {
3772
3843
  if (!enabled) {
3773
3844
  return null;
3774
3845
  }
3775
- const gitignorePath = path9.join(repoRoot, ".gitignore");
3846
+ const gitignorePath = path10.join(repoRoot, ".gitignore");
3776
3847
  if (!await fileExists(gitignorePath)) {
3777
3848
  return null;
3778
3849
  }
@@ -3782,12 +3853,15 @@ async function loadGitignoreMatcher(repoRoot, enabled) {
3782
3853
  }
3783
3854
  function builtInIgnoreReason(relativePath) {
3784
3855
  for (const segment of relativePath.split("/")) {
3785
- if (BUILT_IN_REPO_IGNORES.has(segment)) {
3856
+ if (HARD_REPO_IGNORES.has(segment)) {
3786
3857
  return `built_in_ignore:${segment}`;
3787
3858
  }
3788
3859
  }
3789
3860
  return null;
3790
3861
  }
3862
+ function sourceClassForRelativePath(relativePath, options) {
3863
+ return classifyRepoPath(relativePath, options.repoAnalysis);
3864
+ }
3791
3865
  async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
3792
3866
  const matcher = await loadGitignoreMatcher(repoRoot, options.gitignore);
3793
3867
  const skipped = [];
@@ -3801,20 +3875,20 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
3801
3875
  const entries = await fs9.readdir(currentDir, { withFileTypes: true });
3802
3876
  entries.sort((left, right) => left.name.localeCompare(right.name));
3803
3877
  for (const entry of entries) {
3804
- const absolutePath = path9.join(currentDir, entry.name);
3805
- const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path9.relative(inputDir, absolutePath));
3878
+ const absolutePath = path10.join(currentDir, entry.name);
3879
+ const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(inputDir, absolutePath));
3806
3880
  const relativePath = relativeToRepo || entry.name;
3807
3881
  const builtInReason = builtInIgnoreReason(relativePath);
3808
3882
  if (builtInReason) {
3809
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: builtInReason });
3883
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: builtInReason });
3810
3884
  continue;
3811
3885
  }
3812
3886
  if (matcher?.ignores(relativePath)) {
3813
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "gitignore" });
3887
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "gitignore" });
3814
3888
  continue;
3815
3889
  }
3816
- if (matchesAnyGlob(relativePath, options.exclude)) {
3817
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "exclude_glob" });
3890
+ if (matchesAnyGlob2(relativePath, options.exclude)) {
3891
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "exclude_glob" });
3818
3892
  continue;
3819
3893
  }
3820
3894
  if (entry.isDirectory()) {
@@ -3822,21 +3896,26 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
3822
3896
  continue;
3823
3897
  }
3824
3898
  if (!entry.isFile()) {
3825
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
3899
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
3826
3900
  continue;
3827
3901
  }
3828
- if (options.include.length > 0 && !matchesAnyGlob(relativePath, options.include)) {
3829
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "include_glob" });
3902
+ if (options.include.length > 0 && !matchesAnyGlob2(relativePath, options.include)) {
3903
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "include_glob" });
3830
3904
  continue;
3831
3905
  }
3832
3906
  const mimeType = guessMimeType(absolutePath);
3833
3907
  const sourceKind = inferKind(mimeType, absolutePath);
3908
+ const sourceClass = sourceClassForRelativePath(relativePath, options);
3834
3909
  if (!supportedDirectoryKind(sourceKind)) {
3835
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
3910
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
3911
+ continue;
3912
+ }
3913
+ if (!options.extractClasses.includes(sourceClass)) {
3914
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: `source_class:${sourceClass}` });
3836
3915
  continue;
3837
3916
  }
3838
3917
  if (files.length >= options.maxFiles) {
3839
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "max_files" });
3918
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "max_files" });
3840
3919
  continue;
3841
3920
  }
3842
3921
  files.push(absolutePath);
@@ -3858,12 +3937,12 @@ function resolveUrlMimeType(input, response) {
3858
3937
  function buildRemoteAssetRelativePath(assetUrl, mimeType) {
3859
3938
  const url = new URL(assetUrl);
3860
3939
  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);
3940
+ const extension = path10.posix.extname(normalized);
3941
+ const directory = path10.posix.dirname(normalized);
3942
+ const basename = extension ? path10.posix.basename(normalized, extension) : path10.posix.basename(normalized);
3864
3943
  const resolvedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
3865
3944
  const hashedName = `${basename || "asset"}-${sha256(assetUrl).slice(0, 8)}${resolvedExtension}`;
3866
- return directory === "." ? hashedName : path9.posix.join(directory, hashedName);
3945
+ return directory === "." ? hashedName : path10.posix.join(directory, hashedName);
3867
3946
  }
3868
3947
  async function readResponseBytesWithinLimit(response, maxBytes) {
3869
3948
  const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
@@ -3990,7 +4069,7 @@ async function persistPreparedInput(rootDir, prepared, paths) {
3990
4069
  const extractionHash = prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact);
3991
4070
  const existingByOrigin = await readManifestByOrigin(paths.manifestsDir, prepared);
3992
4071
  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) {
4072
+ 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
4073
  return { manifest: existingByOrigin, isNew: false, wasUpdated: false };
3995
4074
  }
3996
4075
  if (existingByHash) {
@@ -3999,18 +4078,18 @@ async function persistPreparedInput(rootDir, prepared, paths) {
3999
4078
  const previous = existingByOrigin ?? void 0;
4000
4079
  const sourceId = previous?.sourceId ?? `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
4001
4080
  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);
4081
+ const storedPath = path10.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
4082
+ const extractedTextPath = prepared.extractedText ? path10.join(paths.extractsDir, `${sourceId}.md`) : void 0;
4083
+ const extractedMetadataPath = prepared.extractionArtifact ? path10.join(paths.extractsDir, `${sourceId}.json`) : void 0;
4084
+ const attachmentsDir = path10.join(paths.rawAssetsDir, sourceId);
4006
4085
  if (previous?.storedPath) {
4007
- await fs9.rm(path9.resolve(rootDir, previous.storedPath), { force: true });
4086
+ await fs9.rm(path10.resolve(rootDir, previous.storedPath), { force: true });
4008
4087
  }
4009
4088
  if (previous?.extractedTextPath) {
4010
- await fs9.rm(path9.resolve(rootDir, previous.extractedTextPath), { force: true });
4089
+ await fs9.rm(path10.resolve(rootDir, previous.extractedTextPath), { force: true });
4011
4090
  }
4012
4091
  if (previous?.extractedMetadataPath) {
4013
- await fs9.rm(path9.resolve(rootDir, previous.extractedMetadataPath), { force: true });
4092
+ await fs9.rm(path10.resolve(rootDir, previous.extractedMetadataPath), { force: true });
4014
4093
  }
4015
4094
  await fs9.rm(attachmentsDir, { recursive: true, force: true });
4016
4095
  await fs9.writeFile(storedPath, prepared.payloadBytes);
@@ -4022,11 +4101,11 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4022
4101
  }
4023
4102
  const manifestAttachments = [];
4024
4103
  for (const attachment of attachments) {
4025
- const absoluteAttachmentPath = path9.join(attachmentsDir, attachment.relativePath);
4026
- await ensureDir(path9.dirname(absoluteAttachmentPath));
4104
+ const absoluteAttachmentPath = path10.join(attachmentsDir, attachment.relativePath);
4105
+ await ensureDir(path10.dirname(absoluteAttachmentPath));
4027
4106
  await fs9.writeFile(absoluteAttachmentPath, attachment.bytes);
4028
4107
  manifestAttachments.push({
4029
- path: toPosix(path9.relative(rootDir, absoluteAttachmentPath)),
4108
+ path: toPosix(path10.relative(rootDir, absoluteAttachmentPath)),
4030
4109
  mimeType: attachment.mimeType,
4031
4110
  originalPath: attachment.originalPath
4032
4111
  });
@@ -4037,13 +4116,14 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4037
4116
  originType: prepared.originType,
4038
4117
  sourceKind: prepared.sourceKind,
4039
4118
  sourceType: prepared.sourceType,
4119
+ sourceClass: prepared.sourceClass,
4040
4120
  language: prepared.language,
4041
4121
  originalPath: prepared.originalPath,
4042
4122
  repoRelativePath: prepared.repoRelativePath,
4043
4123
  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,
4124
+ storedPath: toPosix(path10.relative(rootDir, storedPath)),
4125
+ extractedTextPath: extractedTextPath ? toPosix(path10.relative(rootDir, extractedTextPath)) : void 0,
4126
+ extractedMetadataPath: extractedMetadataPath ? toPosix(path10.relative(rootDir, extractedMetadataPath)) : void 0,
4047
4127
  extractionHash,
4048
4128
  mimeType: prepared.mimeType,
4049
4129
  contentHash,
@@ -4051,7 +4131,7 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4051
4131
  updatedAt: now,
4052
4132
  attachments: manifestAttachments.length ? manifestAttachments : void 0
4053
4133
  };
4054
- await writeJsonFile(path9.join(paths.manifestsDir, `${sourceId}.json`), manifest);
4134
+ await writeJsonFile(path10.join(paths.manifestsDir, `${sourceId}.json`), manifest);
4055
4135
  await appendLogEntry(rootDir, "ingest", prepared.title, [
4056
4136
  `source_id=${sourceId}`,
4057
4137
  `kind=${prepared.sourceKind}`,
@@ -4069,16 +4149,16 @@ async function persistPreparedInput(rootDir, prepared, paths) {
4069
4149
  return { manifest, isNew: !previous, wasUpdated: Boolean(previous) };
4070
4150
  }
4071
4151
  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 });
4152
+ await fs9.rm(path10.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
4153
+ await fs9.rm(path10.resolve(rootDir, manifest.storedPath), { force: true });
4074
4154
  if (manifest.extractedTextPath) {
4075
- await fs9.rm(path9.resolve(rootDir, manifest.extractedTextPath), { force: true });
4155
+ await fs9.rm(path10.resolve(rootDir, manifest.extractedTextPath), { force: true });
4076
4156
  }
4077
4157
  if (manifest.extractedMetadataPath) {
4078
- await fs9.rm(path9.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
4158
+ await fs9.rm(path10.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
4079
4159
  }
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 });
4160
+ await fs9.rm(path10.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
4161
+ await fs9.rm(path10.join(paths.analysesDir, `${manifest.sourceId}.json`), { force: true });
4082
4162
  }
4083
4163
  function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
4084
4164
  const candidates = [
@@ -4087,14 +4167,14 @@ function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
4087
4167
  paths.stateDir,
4088
4168
  paths.agentDir,
4089
4169
  paths.inboxDir,
4090
- path9.join(rootDir, ".claude"),
4091
- path9.join(rootDir, ".cursor"),
4092
- path9.join(rootDir, ".obsidian")
4170
+ path10.join(rootDir, ".claude"),
4171
+ path10.join(rootDir, ".cursor"),
4172
+ path10.join(rootDir, ".obsidian")
4093
4173
  ];
4094
- return candidates.map((candidate) => path9.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, candidate));
4174
+ return candidates.map((candidate) => path10.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, candidate));
4095
4175
  }
4096
4176
  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;
4177
+ 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
4178
  }
4099
4179
  function shouldDeferWatchSemanticRefresh(sourceKind) {
4100
4180
  return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "image";
@@ -4110,19 +4190,19 @@ async function listTrackedRepoRoots(rootDir) {
4110
4190
  }
4111
4191
  async function syncTrackedRepos(rootDir, options, repoRoots) {
4112
4192
  const { paths } = await initWorkspace(rootDir);
4113
- const normalizedOptions = normalizeIngestOptions(options);
4193
+ const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
4114
4194
  const manifests = await listManifests(rootDir);
4115
4195
  const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
4116
- (item) => path9.resolve(item)
4196
+ (item) => path10.resolve(item)
4117
4197
  );
4118
4198
  const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
4119
4199
  const manifestsByRepoRoot = /* @__PURE__ */ new Map();
4120
4200
  for (const manifest of manifests) {
4121
4201
  const repoRoot = repoRootFromManifest(manifest);
4122
- if (!repoRoot || !uniqueRoots.includes(path9.resolve(repoRoot))) {
4202
+ if (!repoRoot || !uniqueRoots.includes(path10.resolve(repoRoot))) {
4123
4203
  continue;
4124
4204
  }
4125
- const key = path9.resolve(repoRoot);
4205
+ const key = path10.resolve(repoRoot);
4126
4206
  const bucket = manifestsByRepoRoot.get(key) ?? [];
4127
4207
  bucket.push(manifest);
4128
4208
  manifestsByRepoRoot.set(key, bucket);
@@ -4147,14 +4227,15 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4147
4227
  skipped.push(
4148
4228
  ...collected.skipped,
4149
4229
  ...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
4150
- path: toPosix(path9.relative(rootDir, absolutePath)),
4230
+ path: toPosix(path10.relative(rootDir, absolutePath)),
4151
4231
  reason: "workspace_generated"
4152
4232
  }))
4153
4233
  );
4154
4234
  scannedCount += files.length;
4155
- const currentPaths = new Set(files.map((absolutePath) => path9.resolve(absolutePath)));
4235
+ const currentPaths = new Set(files.map((absolutePath) => path10.resolve(absolutePath)));
4156
4236
  for (const absolutePath of files) {
4157
- const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
4237
+ const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(repoRoot, absolutePath));
4238
+ const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
4158
4239
  const result = await persistPreparedInput(rootDir, prepared, paths);
4159
4240
  if (result.isNew) {
4160
4241
  imported.push(result.manifest);
@@ -4163,7 +4244,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4163
4244
  }
4164
4245
  }
4165
4246
  for (const manifest of repoManifests) {
4166
- const originalPath = manifest.originalPath ? path9.resolve(manifest.originalPath) : null;
4247
+ const originalPath = manifest.originalPath ? path10.resolve(manifest.originalPath) : null;
4167
4248
  if (originalPath && !currentPaths.has(originalPath)) {
4168
4249
  await removeManifestArtifacts(rootDir, manifest, paths);
4169
4250
  removed.push(manifest);
@@ -4171,7 +4252,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4171
4252
  }
4172
4253
  }
4173
4254
  if (uniqueRoots.length > 0) {
4174
- await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(path9.relative(rootDir, repoRoot)) || ".").join(","), [
4255
+ await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(path10.relative(rootDir, repoRoot)) || ".").join(","), [
4175
4256
  `repo_roots=${uniqueRoots.length}`,
4176
4257
  `scanned=${scannedCount}`,
4177
4258
  `imported=${imported.length}`,
@@ -4191,19 +4272,19 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
4191
4272
  }
4192
4273
  async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4193
4274
  const { paths } = await initWorkspace(rootDir);
4194
- const normalizedOptions = normalizeIngestOptions(options);
4275
+ const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
4195
4276
  const manifests = await listManifests(rootDir);
4196
4277
  const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
4197
- (item) => path9.resolve(item)
4278
+ (item) => path10.resolve(item)
4198
4279
  );
4199
4280
  const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
4200
4281
  const manifestsByRepoRoot = /* @__PURE__ */ new Map();
4201
4282
  for (const manifest of manifests) {
4202
4283
  const repoRoot = repoRootFromManifest(manifest);
4203
- if (!repoRoot || !uniqueRoots.includes(path9.resolve(repoRoot))) {
4284
+ if (!repoRoot || !uniqueRoots.includes(path10.resolve(repoRoot))) {
4204
4285
  continue;
4205
4286
  }
4206
- const key = path9.resolve(repoRoot);
4287
+ const key = path10.resolve(repoRoot);
4207
4288
  const bucket = manifestsByRepoRoot.get(key) ?? [];
4208
4289
  bucket.push(manifest);
4209
4290
  manifestsByRepoRoot.set(key, bucket);
@@ -4218,7 +4299,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4218
4299
  for (const repoRoot of uniqueRoots) {
4219
4300
  const repoManifests = manifestsByRepoRoot.get(repoRoot) ?? [];
4220
4301
  const manifestsByOriginalPath = new Map(
4221
- repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [path9.resolve(manifest.originalPath), manifest])
4302
+ repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [path10.resolve(manifest.originalPath), manifest])
4222
4303
  );
4223
4304
  if (!await fileExists(repoRoot)) {
4224
4305
  for (const manifest of repoManifests) {
@@ -4226,7 +4307,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4226
4307
  pendingSemanticRefresh.push({
4227
4308
  id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? manifest.storedPath),
4228
4309
  repoRoot,
4229
- path: toPosix(path9.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
4310
+ path: toPosix(path10.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
4230
4311
  changeType: "removed",
4231
4312
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
4232
4313
  sourceId: manifest.sourceId,
@@ -4246,16 +4327,17 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4246
4327
  skipped.push(
4247
4328
  ...collected.skipped,
4248
4329
  ...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
4249
- path: toPosix(path9.relative(rootDir, absolutePath)),
4330
+ path: toPosix(path10.relative(rootDir, absolutePath)),
4250
4331
  reason: "workspace_generated"
4251
4332
  }))
4252
4333
  );
4253
4334
  scannedCount += files.length;
4254
- const currentPaths = new Set(files.map((absolutePath) => path9.resolve(absolutePath)));
4335
+ const currentPaths = new Set(files.map((absolutePath) => path10.resolve(absolutePath)));
4255
4336
  for (const absolutePath of files) {
4256
- const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
4337
+ const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(repoRoot, absolutePath));
4338
+ const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
4257
4339
  if (shouldDeferWatchSemanticRefresh(prepared.sourceKind)) {
4258
- const existing = manifestsByOriginalPath.get(path9.resolve(absolutePath));
4340
+ const existing = manifestsByOriginalPath.get(path10.resolve(absolutePath));
4259
4341
  const contentHash = buildCompositeHash(prepared.payloadBytes, prepared.attachments);
4260
4342
  const changed = !existing || !preparedMatchesManifest(existing, prepared, contentHash);
4261
4343
  if (changed) {
@@ -4263,10 +4345,10 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4263
4345
  id: pendingSemanticRefreshId(
4264
4346
  existing ? "modified" : "added",
4265
4347
  repoRoot,
4266
- prepared.repoRelativePath ?? toPosix(path9.relative(repoRoot, absolutePath))
4348
+ prepared.repoRelativePath ?? toPosix(path10.relative(repoRoot, absolutePath))
4267
4349
  ),
4268
4350
  repoRoot,
4269
- path: toPosix(path9.relative(rootDir, absolutePath)),
4351
+ path: toPosix(path10.relative(rootDir, absolutePath)),
4270
4352
  changeType: existing ? "modified" : "added",
4271
4353
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
4272
4354
  sourceId: existing?.sourceId,
@@ -4286,13 +4368,13 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4286
4368
  }
4287
4369
  }
4288
4370
  for (const manifest of repoManifests) {
4289
- const originalPath = manifest.originalPath ? path9.resolve(manifest.originalPath) : null;
4371
+ const originalPath = manifest.originalPath ? path10.resolve(manifest.originalPath) : null;
4290
4372
  if (originalPath && !currentPaths.has(originalPath)) {
4291
4373
  if (shouldDeferWatchSemanticRefresh(manifest.sourceKind)) {
4292
4374
  pendingSemanticRefresh.push({
4293
- id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path9.relative(repoRoot, originalPath))),
4375
+ id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path10.relative(repoRoot, originalPath))),
4294
4376
  repoRoot,
4295
- path: toPosix(path9.relative(rootDir, originalPath)),
4377
+ path: toPosix(path10.relative(rootDir, originalPath)),
4296
4378
  changeType: "removed",
4297
4379
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
4298
4380
  sourceId: manifest.sourceId,
@@ -4310,7 +4392,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4310
4392
  await appendLogEntry(
4311
4393
  rootDir,
4312
4394
  "sync_repo_watch",
4313
- uniqueRoots.map((repoRoot) => toPosix(path9.relative(rootDir, repoRoot)) || ".").join(","),
4395
+ uniqueRoots.map((repoRoot) => toPosix(path10.relative(rootDir, repoRoot)) || ".").join(","),
4314
4396
  [
4315
4397
  `repo_roots=${uniqueRoots.length}`,
4316
4398
  `scanned=${scannedCount}`,
@@ -4335,26 +4417,32 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
4335
4417
  staleSourceIds: [...staleSourceIds]
4336
4418
  };
4337
4419
  }
4338
- async function prepareFileInput(rootDir, absoluteInput, repoRoot) {
4420
+ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
4339
4421
  const payloadBytes = await fs9.readFile(absoluteInput);
4340
4422
  const mimeType = guessMimeType(absoluteInput);
4341
4423
  const sourceKind = inferKind(mimeType, absoluteInput);
4342
4424
  const language = inferCodeLanguage(absoluteInput, mimeType);
4343
- const storedExtension = path9.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
4425
+ const storedExtension = path10.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
4344
4426
  let title;
4345
4427
  let extractedText;
4346
4428
  let extractionArtifact;
4347
4429
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
4348
4430
  extractedText = payloadBytes.toString("utf8");
4349
- title = titleFromText(path9.basename(absoluteInput, path9.extname(absoluteInput)), extractedText);
4431
+ title = titleFromText(path10.basename(absoluteInput, path10.extname(absoluteInput)), extractedText);
4350
4432
  extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
4433
+ } else if (sourceKind === "html") {
4434
+ const html = payloadBytes.toString("utf8");
4435
+ const converted = await convertHtmlToMarkdown(html, pathToFileURL(absoluteInput).toString());
4436
+ title = converted.title;
4437
+ extractedText = converted.markdown;
4438
+ extractionArtifact = createHtmlReadabilityExtractionArtifact(sourceKind, mimeType);
4351
4439
  } else if (sourceKind === "pdf") {
4352
- title = path9.basename(absoluteInput, path9.extname(absoluteInput));
4440
+ title = path10.basename(absoluteInput, path10.extname(absoluteInput));
4353
4441
  const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
4354
4442
  extractedText = extracted.extractedText;
4355
4443
  extractionArtifact = extracted.artifact;
4356
4444
  } else if (sourceKind === "image") {
4357
- title = path9.basename(absoluteInput, path9.extname(absoluteInput));
4445
+ title = path10.basename(absoluteInput, path10.extname(absoluteInput));
4358
4446
  const extracted = await extractImageWithVision(rootDir, {
4359
4447
  title,
4360
4448
  mimeType,
@@ -4364,12 +4452,13 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot) {
4364
4452
  extractedText = extracted.extractedText;
4365
4453
  extractionArtifact = extracted.artifact;
4366
4454
  } else {
4367
- title = path9.basename(absoluteInput, path9.extname(absoluteInput));
4455
+ title = path10.basename(absoluteInput, path10.extname(absoluteInput));
4368
4456
  }
4369
4457
  return {
4370
4458
  title,
4371
4459
  originType: "file",
4372
4460
  sourceKind,
4461
+ sourceClass,
4373
4462
  language,
4374
4463
  originalPath: toPosix(absoluteInput),
4375
4464
  repoRelativePath: repoRelativePathFor(absoluteInput, repoRoot),
@@ -4439,7 +4528,7 @@ async function prepareUrlInput(rootDir, input, options) {
4439
4528
  sourceKind = "markdown";
4440
4529
  storedExtension = ".md";
4441
4530
  } else {
4442
- const extension = path9.extname(inputUrl.pathname);
4531
+ const extension = path10.extname(inputUrl.pathname);
4443
4532
  storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
4444
4533
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
4445
4534
  extractedText = payloadBytes.toString("utf8");
@@ -4512,7 +4601,7 @@ async function collectInboxAttachmentRefs(inputDir, files) {
4512
4601
  }
4513
4602
  const sourceRefs = [];
4514
4603
  for (const ref of refs) {
4515
- const resolved = path9.resolve(path9.dirname(absolutePath), ref);
4604
+ const resolved = path10.resolve(path10.dirname(absolutePath), ref);
4516
4605
  if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
4517
4606
  continue;
4518
4607
  }
@@ -4548,7 +4637,7 @@ function rewriteMarkdownReferences(content, replacements) {
4548
4637
  async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
4549
4638
  const originalBytes = await fs9.readFile(absolutePath);
4550
4639
  const originalText = originalBytes.toString("utf8");
4551
- const title = titleFromText(path9.basename(absolutePath, path9.extname(absolutePath)), originalText);
4640
+ const title = titleFromText(path10.basename(absolutePath, path10.extname(absolutePath)), originalText);
4552
4641
  const attachments = [];
4553
4642
  for (const attachmentRef of attachmentRefs) {
4554
4643
  const bytes = await fs9.readFile(attachmentRef.absolutePath);
@@ -4575,7 +4664,7 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
4575
4664
  sourceKind: "markdown",
4576
4665
  originalPath: toPosix(absolutePath),
4577
4666
  mimeType: "text/markdown",
4578
- storedExtension: path9.extname(absolutePath) || ".md",
4667
+ storedExtension: path10.extname(absolutePath) || ".md",
4579
4668
  payloadBytes: Buffer.from(rewrittenText, "utf8"),
4580
4669
  extractedText: rewrittenText,
4581
4670
  extractionArtifact,
@@ -4590,8 +4679,8 @@ function isSupportedInboxKind(sourceKind) {
4590
4679
  async function ingestInput(rootDir, input, options) {
4591
4680
  const { paths } = await initWorkspace(rootDir);
4592
4681
  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));
4682
+ const absoluteInput = path10.resolve(rootDir, input);
4683
+ const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ?? path10.dirname(absoluteInput));
4595
4684
  const prepared = isHttpUrl(input) ? await prepareUrlInput(rootDir, input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
4596
4685
  const result = await persistPreparedInput(rootDir, prepared, paths);
4597
4686
  return result.manifest;
@@ -4680,8 +4769,8 @@ async function addInput(rootDir, input, options = {}) {
4680
4769
  }
4681
4770
  async function ingestDirectory(rootDir, inputDir, options) {
4682
4771
  const { paths } = await initWorkspace(rootDir);
4683
- const normalizedOptions = normalizeIngestOptions(options);
4684
- const absoluteInputDir = path9.resolve(rootDir, inputDir);
4772
+ const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
4773
+ const absoluteInputDir = path10.resolve(rootDir, inputDir);
4685
4774
  const repoRoot = normalizedOptions.repoRoot ?? await findNearestGitRoot2(absoluteInputDir) ?? absoluteInputDir;
4686
4775
  if (!await fileExists(absoluteInputDir)) {
4687
4776
  throw new Error(`Directory not found: ${absoluteInputDir}`);
@@ -4690,18 +4779,19 @@ async function ingestDirectory(rootDir, inputDir, options) {
4690
4779
  const imported = [];
4691
4780
  const updated = [];
4692
4781
  for (const absolutePath of files) {
4693
- const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
4782
+ const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path10.relative(repoRoot, absolutePath));
4783
+ const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
4694
4784
  const result = await persistPreparedInput(rootDir, prepared, paths);
4695
4785
  if (result.isNew) {
4696
4786
  imported.push(result.manifest);
4697
4787
  } else if (result.wasUpdated) {
4698
4788
  updated.push(result.manifest);
4699
4789
  } else {
4700
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4790
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4701
4791
  }
4702
4792
  }
4703
- await appendLogEntry(rootDir, "ingest_directory", toPosix(path9.relative(rootDir, absoluteInputDir)) || ".", [
4704
- `repo_root=${toPosix(path9.relative(rootDir, repoRoot)) || "."}`,
4793
+ await appendLogEntry(rootDir, "ingest_directory", toPosix(path10.relative(rootDir, absoluteInputDir)) || ".", [
4794
+ `repo_root=${toPosix(path10.relative(rootDir, repoRoot)) || "."}`,
4705
4795
  `scanned=${files.length}`,
4706
4796
  `imported=${imported.length}`,
4707
4797
  `updated=${updated.length}`,
@@ -4718,7 +4808,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
4718
4808
  }
4719
4809
  async function importInbox(rootDir, inputDir) {
4720
4810
  const { paths } = await initWorkspace(rootDir);
4721
- const effectiveInputDir = path9.resolve(rootDir, inputDir ?? paths.inboxDir);
4811
+ const effectiveInputDir = path10.resolve(rootDir, inputDir ?? paths.inboxDir);
4722
4812
  if (!await fileExists(effectiveInputDir)) {
4723
4813
  throw new Error(`Inbox directory not found: ${effectiveInputDir}`);
4724
4814
  }
@@ -4729,31 +4819,31 @@ async function importInbox(rootDir, inputDir) {
4729
4819
  const skipped = [];
4730
4820
  let attachmentCount = 0;
4731
4821
  for (const absolutePath of files) {
4732
- const basename = path9.basename(absolutePath);
4822
+ const basename = path10.basename(absolutePath);
4733
4823
  if (basename.startsWith(".")) {
4734
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "hidden_file" });
4824
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "hidden_file" });
4735
4825
  continue;
4736
4826
  }
4737
4827
  if (claimedAttachments.has(absolutePath)) {
4738
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
4828
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
4739
4829
  continue;
4740
4830
  }
4741
4831
  const mimeType = guessMimeType(absolutePath);
4742
4832
  const sourceKind = inferKind(mimeType, absolutePath);
4743
4833
  if (!isSupportedInboxKind(sourceKind)) {
4744
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
4834
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
4745
4835
  continue;
4746
4836
  }
4747
4837
  const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
4748
4838
  const result = await persistPreparedInput(rootDir, prepared, paths);
4749
4839
  if (!result.isNew) {
4750
- skipped.push({ path: toPosix(path9.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4840
+ skipped.push({ path: toPosix(path10.relative(rootDir, absolutePath)), reason: "duplicate_content" });
4751
4841
  continue;
4752
4842
  }
4753
4843
  attachmentCount += result.manifest.attachments?.length ?? 0;
4754
4844
  imported.push(result.manifest);
4755
4845
  }
4756
- await appendLogEntry(rootDir, "inbox_import", toPosix(path9.relative(rootDir, effectiveInputDir)) || ".", [
4846
+ await appendLogEntry(rootDir, "inbox_import", toPosix(path10.relative(rootDir, effectiveInputDir)) || ".", [
4757
4847
  `scanned=${files.length}`,
4758
4848
  `imported=${imported.length}`,
4759
4849
  `attachments=${attachmentCount}`,
@@ -4774,7 +4864,7 @@ async function listManifests(rootDir) {
4774
4864
  }
4775
4865
  const entries = await fs9.readdir(paths.manifestsDir);
4776
4866
  const manifests = await Promise.all(
4777
- entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path9.join(paths.manifestsDir, entry)))
4867
+ entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path10.join(paths.manifestsDir, entry)))
4778
4868
  );
4779
4869
  return manifests.filter((manifest) => Boolean(manifest));
4780
4870
  }
@@ -4782,7 +4872,7 @@ async function readExtractedText(rootDir, manifest) {
4782
4872
  if (!manifest.extractedTextPath) {
4783
4873
  return void 0;
4784
4874
  }
4785
- const absolutePath = path9.resolve(rootDir, manifest.extractedTextPath);
4875
+ const absolutePath = path10.resolve(rootDir, manifest.extractedTextPath);
4786
4876
  if (!await fileExists(absolutePath)) {
4787
4877
  return void 0;
4788
4878
  }
@@ -4792,7 +4882,7 @@ async function readExtractionArtifact(rootDir, manifest) {
4792
4882
  if (!manifest.extractedMetadataPath) {
4793
4883
  return void 0;
4794
4884
  }
4795
- const absolutePath = path9.resolve(rootDir, manifest.extractedMetadataPath);
4885
+ const absolutePath = path10.resolve(rootDir, manifest.extractedMetadataPath);
4796
4886
  if (!await fileExists(absolutePath)) {
4797
4887
  return void 0;
4798
4888
  }
@@ -4800,15 +4890,15 @@ async function readExtractionArtifact(rootDir, manifest) {
4800
4890
  }
4801
4891
 
4802
4892
  // src/mcp.ts
4803
- import fs16 from "fs/promises";
4804
- import path19 from "path";
4893
+ import fs17 from "fs/promises";
4894
+ import path21 from "path";
4805
4895
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4806
4896
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4807
4897
  import { z as z8 } from "zod";
4808
4898
 
4809
4899
  // src/schema.ts
4810
4900
  import fs10 from "fs/promises";
4811
- import path10 from "path";
4901
+ import path11 from "path";
4812
4902
  function normalizeSchemaContent(content) {
4813
4903
  return content.trim() ? content.trim() : defaultVaultSchema().trim();
4814
4904
  }
@@ -4822,7 +4912,7 @@ async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
4822
4912
  };
4823
4913
  }
4824
4914
  function resolveProjectSchemaPath(rootDir, schemaPath) {
4825
- return path10.resolve(rootDir, schemaPath);
4915
+ return path11.resolve(rootDir, schemaPath);
4826
4916
  }
4827
4917
  function composeVaultSchema(root, projectSchemas = []) {
4828
4918
  if (!projectSchemas.length) {
@@ -4838,7 +4928,7 @@ function composeVaultSchema(root, projectSchemas = []) {
4838
4928
  (schema) => [
4839
4929
  `## Project Schema`,
4840
4930
  "",
4841
- `Path: ${toPosix(path10.relative(path10.dirname(root.path), schema.path) || schema.path)}`,
4931
+ `Path: ${toPosix(path11.relative(path11.dirname(root.path), schema.path) || schema.path)}`,
4842
4932
  "",
4843
4933
  schema.content
4844
4934
  ].join("\n")
@@ -4914,13 +5004,13 @@ function buildSchemaPrompt(schema, instruction) {
4914
5004
  }
4915
5005
 
4916
5006
  // src/vault.ts
4917
- import fs15 from "fs/promises";
4918
- import path18 from "path";
5007
+ import fs16 from "fs/promises";
5008
+ import path20 from "path";
4919
5009
  import matter9 from "gray-matter";
4920
5010
  import { z as z7 } from "zod";
4921
5011
 
4922
5012
  // src/analysis.ts
4923
- import path11 from "path";
5013
+ import path12 from "path";
4924
5014
  import { z as z2 } from "zod";
4925
5015
  var ANALYSIS_FORMAT_VERSION = 5;
4926
5016
  var sourceAnalysisSchema = z2.object({
@@ -5143,7 +5233,7 @@ function extractionWarningSummary(manifest, extraction) {
5143
5233
  return `Imported ${manifest.sourceKind} source. Text extraction is not yet available for this source.`;
5144
5234
  }
5145
5235
  async function analyzeSource(manifest, extractedText, provider, paths, schema) {
5146
- const cachePath = path11.join(paths.analysesDir, `${manifest.sourceId}.json`);
5236
+ const cachePath = path12.join(paths.analysesDir, `${manifest.sourceId}.json`);
5147
5237
  const cached = await readJsonFile(cachePath);
5148
5238
  if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
5149
5239
  return cached;
@@ -5286,6 +5376,7 @@ function graphHash(graph) {
5286
5376
  type: node.type,
5287
5377
  label: node.label,
5288
5378
  pageId: node.pageId ?? null,
5379
+ sourceClass: node.sourceClass ?? null,
5289
5380
  communityId: node.communityId ?? null,
5290
5381
  degree: node.degree ?? null,
5291
5382
  bridgeScore: node.bridgeScore ?? null,
@@ -5300,6 +5391,7 @@ function graphHash(graph) {
5300
5391
  relation: edge.relation,
5301
5392
  status: edge.status,
5302
5393
  evidenceClass: edge.evidenceClass,
5394
+ similarityBasis: edge.similarityBasis ?? null,
5303
5395
  confidence: edge.confidence,
5304
5396
  provenance: [...edge.provenance].sort()
5305
5397
  })).sort((left, right) => left.id.localeCompare(right.id)),
@@ -5309,6 +5401,7 @@ function graphHash(graph) {
5309
5401
  kind: page.kind,
5310
5402
  status: page.status,
5311
5403
  sourceType: page.sourceType ?? null,
5404
+ sourceClass: page.sourceClass ?? null,
5312
5405
  sourceIds: [...page.sourceIds].sort(),
5313
5406
  projectIds: [...page.projectIds].sort(),
5314
5407
  nodeIds: [...page.nodeIds].sort()
@@ -5386,7 +5479,7 @@ function conflictConfidence(claimA, claimB) {
5386
5479
 
5387
5480
  // src/deep-lint.ts
5388
5481
  import fs11 from "fs/promises";
5389
- import path14 from "path";
5482
+ import path15 from "path";
5390
5483
  import matter4 from "gray-matter";
5391
5484
  import { z as z5 } from "zod";
5392
5485
 
@@ -5407,7 +5500,7 @@ function normalizeFindingSeverity(value) {
5407
5500
 
5408
5501
  // src/orchestration.ts
5409
5502
  import { spawn } from "child_process";
5410
- import path12 from "path";
5503
+ import path13 from "path";
5411
5504
  import { z as z3 } from "zod";
5412
5505
  var orchestrationRoleResultSchema = z3.object({
5413
5506
  summary: z3.string().optional(),
@@ -5500,7 +5593,7 @@ async function runProviderRole(rootDir, role, roleConfig, input) {
5500
5593
  }
5501
5594
  async function runCommandRole(rootDir, role, executor, input) {
5502
5595
  const [command, ...args] = executor.command;
5503
- const cwd = executor.cwd ? path12.resolve(rootDir, executor.cwd) : rootDir;
5596
+ const cwd = executor.cwd ? path13.resolve(rootDir, executor.cwd) : rootDir;
5504
5597
  const child = spawn(command, args, {
5505
5598
  cwd,
5506
5599
  env: {
@@ -5594,8 +5687,8 @@ function summarizeRoleQuestions(results) {
5594
5687
  }
5595
5688
 
5596
5689
  // src/web-search/registry.ts
5597
- import path13 from "path";
5598
- import { pathToFileURL } from "url";
5690
+ import path14 from "path";
5691
+ import { pathToFileURL as pathToFileURL2 } from "url";
5599
5692
  import { z as z4 } from "zod";
5600
5693
 
5601
5694
  // src/web-search/http-json.ts
@@ -5692,8 +5785,8 @@ async function createWebSearchAdapter(id, config, rootDir) {
5692
5785
  if (!config.module) {
5693
5786
  throw new Error(`Web search provider ${id} is type "custom" but no module path was configured.`);
5694
5787
  }
5695
- const resolvedModule = path13.isAbsolute(config.module) ? config.module : path13.resolve(rootDir, config.module);
5696
- const loaded = await import(pathToFileURL(resolvedModule).href);
5788
+ const resolvedModule = path14.isAbsolute(config.module) ? config.module : path14.resolve(rootDir, config.module);
5789
+ const loaded = await import(pathToFileURL2(resolvedModule).href);
5697
5790
  const parsed = customWebSearchModuleSchema.parse(loaded);
5698
5791
  return parsed.createAdapter(id, config, rootDir);
5699
5792
  }
@@ -5752,7 +5845,7 @@ async function loadContextPages(rootDir, graph) {
5752
5845
  );
5753
5846
  return Promise.all(
5754
5847
  contextPages.slice(0, 18).map(async (page) => {
5755
- const absolutePath = path14.join(paths.wikiDir, page.path);
5848
+ const absolutePath = path15.join(paths.wikiDir, page.path);
5756
5849
  const raw = await fs11.readFile(absolutePath, "utf8").catch(() => "");
5757
5850
  const parsed = matter4(raw);
5758
5851
  return {
@@ -5801,7 +5894,7 @@ function heuristicDeepFindings(contextPages, structuralFindings, graph) {
5801
5894
  code: "missing_citation",
5802
5895
  message: finding.message,
5803
5896
  pagePath: finding.pagePath,
5804
- suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path14.basename(finding.pagePath, ".md")}?` : void 0
5897
+ suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path15.basename(finding.pagePath, ".md")}?` : void 0
5805
5898
  });
5806
5899
  }
5807
5900
  for (const page of contextPages.filter((item) => item.kind === "source").slice(0, 3)) {
@@ -5978,6 +6071,353 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
5978
6071
  );
5979
6072
  }
5980
6073
 
6074
+ // src/embeddings.ts
6075
+ import fs12 from "fs/promises";
6076
+ import path16 from "path";
6077
+ var MAX_EMBEDDING_BATCH = 32;
6078
+ var MAX_SIMILARITY_NODES = 240;
6079
+ function cosineSimilarity(left, right) {
6080
+ if (!left.length || left.length !== right.length) {
6081
+ return 0;
6082
+ }
6083
+ let dot = 0;
6084
+ let leftNorm = 0;
6085
+ let rightNorm = 0;
6086
+ for (let index = 0; index < left.length; index += 1) {
6087
+ dot += left[index] * right[index];
6088
+ leftNorm += left[index] * left[index];
6089
+ rightNorm += right[index] * right[index];
6090
+ }
6091
+ if (leftNorm <= 0 || rightNorm <= 0) {
6092
+ return 0;
6093
+ }
6094
+ return dot / Math.sqrt(leftNorm * rightNorm);
6095
+ }
6096
+ function appendIfMissing(parts, value) {
6097
+ const normalized = value?.trim();
6098
+ if (!normalized) {
6099
+ return;
6100
+ }
6101
+ if (!parts.includes(normalized)) {
6102
+ parts.push(normalized);
6103
+ }
6104
+ }
6105
+ async function loadPageContents(rootDir, graph) {
6106
+ const { paths } = await loadVaultConfig(rootDir);
6107
+ const contents = /* @__PURE__ */ new Map();
6108
+ await Promise.all(
6109
+ graph.pages.map(async (page) => {
6110
+ const absolutePath = path16.join(paths.wikiDir, page.path);
6111
+ const content = await fs12.readFile(absolutePath, "utf8").catch(() => "");
6112
+ contents.set(page.id, content);
6113
+ })
6114
+ );
6115
+ return contents;
6116
+ }
6117
+ function itemTextForNode(node, graph, pageContents) {
6118
+ const page = graph.pages.find((candidate) => candidate.id === node.pageId);
6119
+ const parts = [`node ${node.type}`, node.label];
6120
+ appendIfMissing(parts, node.sourceClass);
6121
+ appendIfMissing(parts, node.language);
6122
+ appendIfMissing(parts, page?.title);
6123
+ appendIfMissing(parts, page?.sourceType);
6124
+ appendIfMissing(parts, page?.sourceClass);
6125
+ if (page) {
6126
+ appendIfMissing(parts, pageContents.get(page.id)?.slice(0, 800));
6127
+ }
6128
+ return parts.join("\n");
6129
+ }
6130
+ function itemTextForPage(page, pageContents) {
6131
+ const parts = [`page ${page.kind}`, page.title, page.path];
6132
+ appendIfMissing(parts, page.sourceType);
6133
+ appendIfMissing(parts, page.sourceClass);
6134
+ appendIfMissing(parts, pageContents.get(page.id)?.slice(0, 1200));
6135
+ return parts.join("\n");
6136
+ }
6137
+ function itemTextForHyperedge(graph, hyperedgeId) {
6138
+ const hyperedge = graph.hyperedges.find((candidate) => candidate.id === hyperedgeId);
6139
+ if (!hyperedge) {
6140
+ return "";
6141
+ }
6142
+ const nodeLabels = hyperedge.nodeIds.map((nodeId) => graph.nodes.find((node) => node.id === nodeId)?.label).filter((value) => Boolean(value));
6143
+ return [hyperedge.label, hyperedge.relation, hyperedge.why, ...nodeLabels].join("\n");
6144
+ }
6145
+ async function buildEmbeddableItems(rootDir, graph) {
6146
+ const pageContents = await loadPageContents(rootDir, graph);
6147
+ return uniqueBy(
6148
+ [
6149
+ ...graph.nodes.map(
6150
+ (node) => ({
6151
+ id: node.id,
6152
+ kind: "node",
6153
+ label: node.label,
6154
+ text: itemTextForNode(node, graph, pageContents),
6155
+ match: {
6156
+ type: "node",
6157
+ id: node.id,
6158
+ label: node.label,
6159
+ score: 0
6160
+ }
6161
+ })
6162
+ ),
6163
+ ...graph.pages.map(
6164
+ (page) => ({
6165
+ id: page.id,
6166
+ kind: "page",
6167
+ label: page.title,
6168
+ text: itemTextForPage(page, pageContents),
6169
+ match: {
6170
+ type: "page",
6171
+ id: page.id,
6172
+ label: page.title,
6173
+ score: 0
6174
+ }
6175
+ })
6176
+ ),
6177
+ ...(graph.hyperedges ?? []).map(
6178
+ (hyperedge) => ({
6179
+ id: hyperedge.id,
6180
+ kind: "hyperedge",
6181
+ label: hyperedge.label,
6182
+ text: itemTextForHyperedge(graph, hyperedge.id),
6183
+ match: {
6184
+ type: "hyperedge",
6185
+ id: hyperedge.id,
6186
+ label: hyperedge.label,
6187
+ score: 0
6188
+ }
6189
+ })
6190
+ )
6191
+ ],
6192
+ (item) => `${item.kind}:${item.id}`
6193
+ ).filter((item) => item.text.trim().length > 0);
6194
+ }
6195
+ async function resolveEmbeddingProvider(rootDir) {
6196
+ const { config } = await loadVaultConfig(rootDir);
6197
+ const explicitProviderId = config.tasks.embeddingProvider;
6198
+ if (explicitProviderId) {
6199
+ const providerConfig = config.providers[explicitProviderId];
6200
+ if (!providerConfig) {
6201
+ throw new Error(`No provider configured with id "${explicitProviderId}" for task "embeddingProvider".`);
6202
+ }
6203
+ const provider2 = await createProvider(explicitProviderId, providerConfig, rootDir);
6204
+ if (!provider2.capabilities.has("embeddings") || typeof provider2.embedTexts !== "function") {
6205
+ throw new Error(`Provider ${provider2.id} does not support required capability "embeddings".`);
6206
+ }
6207
+ return provider2;
6208
+ }
6209
+ const queryProviderId = config.tasks.queryProvider;
6210
+ const queryProviderConfig = config.providers[queryProviderId];
6211
+ if (!queryProviderConfig) {
6212
+ return null;
6213
+ }
6214
+ const provider = await createProvider(queryProviderId, queryProviderConfig, rootDir);
6215
+ return provider.capabilities.has("embeddings") && typeof provider.embedTexts === "function" ? provider : null;
6216
+ }
6217
+ async function readEmbeddingCache(rootDir) {
6218
+ const { paths } = await loadVaultConfig(rootDir);
6219
+ const provider = await resolveEmbeddingProvider(rootDir);
6220
+ if (!provider) {
6221
+ return { artifact: null, provider: null };
6222
+ }
6223
+ const cache = await readJsonFile(paths.embeddingsPath);
6224
+ if (!cache || cache.providerId !== provider.id || cache.providerModel !== provider.model) {
6225
+ return { artifact: null, provider };
6226
+ }
6227
+ return { artifact: cache, provider };
6228
+ }
6229
+ async function writeEmbeddingCache(rootDir, provider, graphHash2, entries) {
6230
+ const { paths } = await loadVaultConfig(rootDir);
6231
+ await writeJsonFile(paths.embeddingsPath, {
6232
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6233
+ providerId: provider.id,
6234
+ providerModel: provider.model,
6235
+ graphHash: graphHash2,
6236
+ entries: entries.sort((left, right) => `${left.kind}:${left.itemId}`.localeCompare(`${right.kind}:${right.itemId}`))
6237
+ });
6238
+ }
6239
+ async function embedTexts(provider, texts) {
6240
+ const vectors = [];
6241
+ for (let index = 0; index < texts.length; index += MAX_EMBEDDING_BATCH) {
6242
+ const batch = texts.slice(index, index + MAX_EMBEDDING_BATCH);
6243
+ const nextVectors = await provider.embedTexts(batch);
6244
+ vectors.push(...nextVectors);
6245
+ }
6246
+ return vectors;
6247
+ }
6248
+ async function resolveVectorsForItems(rootDir, graphHash2, items) {
6249
+ const { artifact, provider } = await readEmbeddingCache(rootDir);
6250
+ if (!provider) {
6251
+ return { provider: null, vectors: /* @__PURE__ */ new Map() };
6252
+ }
6253
+ const cachedByKey = new Map(
6254
+ (artifact?.entries ?? []).map((entry) => [`${entry.kind}:${entry.itemId}:${entry.contentHash}`, entry.values])
6255
+ );
6256
+ const vectors = /* @__PURE__ */ new Map();
6257
+ const missing = [];
6258
+ for (const item of items) {
6259
+ const contentHash = sha256(item.text);
6260
+ const cached = cachedByKey.get(`${item.kind}:${item.id}:${contentHash}`);
6261
+ if (cached?.length) {
6262
+ vectors.set(`${item.kind}:${item.id}`, cached);
6263
+ } else {
6264
+ missing.push(item);
6265
+ }
6266
+ }
6267
+ if (missing.length) {
6268
+ const nextVectors = await embedTexts(
6269
+ provider,
6270
+ missing.map((item) => item.text)
6271
+ );
6272
+ for (let index = 0; index < missing.length; index += 1) {
6273
+ vectors.set(`${missing[index].kind}:${missing[index].id}`, nextVectors[index] ?? []);
6274
+ }
6275
+ }
6276
+ await writeEmbeddingCache(
6277
+ rootDir,
6278
+ provider,
6279
+ graphHash2,
6280
+ items.map((item) => ({
6281
+ itemId: item.id,
6282
+ kind: item.kind,
6283
+ label: item.label,
6284
+ contentHash: sha256(item.text),
6285
+ values: vectors.get(`${item.kind}:${item.id}`) ?? []
6286
+ }))
6287
+ );
6288
+ return { provider, vectors };
6289
+ }
6290
+ async function semanticGraphMatches(rootDir, graph, question, limit = 12) {
6291
+ const items = await buildEmbeddableItems(rootDir, graph);
6292
+ const { provider, vectors } = await resolveVectorsForItems(rootDir, graph.generatedAt, items);
6293
+ if (!provider) {
6294
+ return [];
6295
+ }
6296
+ const [queryVector] = await provider.embedTexts([question]);
6297
+ if (!Array.isArray(queryVector) || queryVector.length === 0) {
6298
+ return [];
6299
+ }
6300
+ return items.map((item) => ({
6301
+ ...item.match,
6302
+ score: Math.max(0, Number((cosineSimilarity(queryVector, vectors.get(`${item.kind}:${item.id}`) ?? []) * 100).toFixed(2)))
6303
+ })).filter((match) => match.score >= 18).sort((left, right) => right.score - left.score || left.label.localeCompare(right.label)).slice(0, limit);
6304
+ }
6305
+ function distinctScope(left, right) {
6306
+ const leftSources = new Set(left.sourceIds);
6307
+ const rightSources = new Set(right.sourceIds);
6308
+ return [...leftSources].some((sourceId) => !rightSources.has(sourceId)) || [...rightSources].some((sourceId) => !leftSources.has(sourceId));
6309
+ }
6310
+ function nodePairKey(left, right) {
6311
+ return [left, right].sort((a, b) => a.localeCompare(b)).join("|");
6312
+ }
6313
+ function similarityReasonsForNodes(left, right) {
6314
+ const reasons = /* @__PURE__ */ new Set();
6315
+ if (left.sourceClass && right.sourceClass && left.sourceClass === right.sourceClass) {
6316
+ reasons.add("shared_tag");
6317
+ }
6318
+ if (left.language && right.language && left.language === right.language) {
6319
+ reasons.add("shared_symbol");
6320
+ }
6321
+ return [...reasons].sort((a, b) => a.localeCompare(b));
6322
+ }
6323
+ async function embeddingSimilarityEdges(rootDir, graph) {
6324
+ const candidateNodes = graph.nodes.filter(
6325
+ (node) => (node.type === "source" || node.type === "module" || node.type === "rationale") && node.sourceClass !== "generated"
6326
+ );
6327
+ if (candidateNodes.length < 2 || candidateNodes.length > MAX_SIMILARITY_NODES) {
6328
+ return [];
6329
+ }
6330
+ const items = candidateNodes.map(
6331
+ (node) => ({
6332
+ id: node.id,
6333
+ kind: "node",
6334
+ label: node.label,
6335
+ text: [
6336
+ node.label,
6337
+ node.type,
6338
+ node.sourceClass ?? "",
6339
+ node.language ?? "",
6340
+ graph.pages.find((page) => page.id === node.pageId)?.title ?? ""
6341
+ ].filter(Boolean).join("\n"),
6342
+ match: { type: "node", id: node.id, label: node.label, score: 0 }
6343
+ })
6344
+ );
6345
+ const { provider, vectors } = await resolveVectorsForItems(rootDir, graph.generatedAt, items);
6346
+ if (!provider) {
6347
+ return [];
6348
+ }
6349
+ const directPairs = new Set(graph.edges.map((edge) => nodePairKey(edge.source, edge.target)));
6350
+ const edges = [];
6351
+ for (let leftIndex = 0; leftIndex < candidateNodes.length; leftIndex += 1) {
6352
+ const left = candidateNodes[leftIndex];
6353
+ const leftVector = vectors.get(`node:${left.id}`) ?? [];
6354
+ const candidates = candidateNodes.slice(leftIndex + 1).filter((right) => distinctScope(left, right) && !directPairs.has(nodePairKey(left.id, right.id))).map((right) => ({
6355
+ right,
6356
+ score: cosineSimilarity(leftVector, vectors.get(`node:${right.id}`) ?? [])
6357
+ })).filter((candidate) => candidate.score >= 0.82).sort((a, b) => b.score - a.score).slice(0, 3);
6358
+ for (const candidate of candidates) {
6359
+ const right = candidate.right;
6360
+ const reasons = similarityReasonsForNodes(left, right) ?? [];
6361
+ edges.push({
6362
+ id: `similar-embed:${sha256(`${left.id}|${right.id}|${provider.id}`).slice(0, 16)}`,
6363
+ source: left.id,
6364
+ target: right.id,
6365
+ relation: "semantically_similar_to",
6366
+ status: "inferred",
6367
+ evidenceClass: "inferred",
6368
+ confidence: Number(candidate.score.toFixed(3)),
6369
+ provenance: uniqueBy(
6370
+ [...left.sourceIds, ...right.sourceIds].sort((a, b) => a.localeCompare(b)),
6371
+ (value) => value
6372
+ ),
6373
+ similarityReasons: reasons.length ? reasons : ["shared_tag"],
6374
+ similarityBasis: "embeddings"
6375
+ });
6376
+ }
6377
+ }
6378
+ return uniqueBy(edges, (edge) => edge.id).sort((left, right) => right.confidence - left.confidence || left.id.localeCompare(right.id));
6379
+ }
6380
+ function sourceClassBreakdown(graph) {
6381
+ return {
6382
+ first_party: {
6383
+ sources: graph.sources.filter((source) => source.sourceClass === "first_party").length,
6384
+ pages: graph.pages.filter((page) => page.sourceClass === "first_party").length,
6385
+ nodes: graph.nodes.filter((node) => node.sourceClass === "first_party").length
6386
+ },
6387
+ third_party: {
6388
+ sources: graph.sources.filter((source) => source.sourceClass === "third_party").length,
6389
+ pages: graph.pages.filter((page) => page.sourceClass === "third_party").length,
6390
+ nodes: graph.nodes.filter((node) => node.sourceClass === "third_party").length
6391
+ },
6392
+ resource: {
6393
+ sources: graph.sources.filter((source) => source.sourceClass === "resource").length,
6394
+ pages: graph.pages.filter((page) => page.sourceClass === "resource").length,
6395
+ nodes: graph.nodes.filter((node) => node.sourceClass === "resource").length
6396
+ },
6397
+ generated: {
6398
+ sources: graph.sources.filter((source) => source.sourceClass === "generated").length,
6399
+ pages: graph.pages.filter((page) => page.sourceClass === "generated").length,
6400
+ nodes: graph.nodes.filter((node) => node.sourceClass === "generated").length
6401
+ }
6402
+ };
6403
+ }
6404
+ function filterGraphBySourceClass(graph, sourceClass) {
6405
+ const nodeIds = new Set(graph.nodes.filter((node) => node.sourceClass === sourceClass).map((node) => node.id));
6406
+ const pageIds = new Set(graph.pages.filter((page) => page.sourceClass === sourceClass).map((page) => page.id));
6407
+ return {
6408
+ ...graph,
6409
+ nodes: graph.nodes.filter((node) => nodeIds.has(node.id)),
6410
+ edges: graph.edges.filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target)),
6411
+ hyperedges: graph.hyperedges.filter((hyperedge) => hyperedge.nodeIds.every((nodeId) => nodeIds.has(nodeId))),
6412
+ communities: (graph.communities ?? []).map((community) => ({
6413
+ ...community,
6414
+ nodeIds: community.nodeIds.filter((nodeId) => nodeIds.has(nodeId))
6415
+ })).filter((community) => community.nodeIds.length > 0),
6416
+ sources: graph.sources.filter((source) => source.sourceClass === sourceClass),
6417
+ pages: graph.pages.filter((page) => pageIds.has(page.id))
6418
+ };
6419
+ }
6420
+
5981
6421
  // src/graph-enrichment.ts
5982
6422
  var STOPWORDS2 = /* @__PURE__ */ new Set([
5983
6423
  "about",
@@ -6204,7 +6644,8 @@ function buildSemanticSimilarityEdges(nodes, edges, manifests, analyses) {
6204
6644
  [...left.sourceIds, ...right.sourceIds].sort((a, b) => a.localeCompare(b)),
6205
6645
  (value) => value
6206
6646
  ),
6207
- similarityReasons: [...reasons.keys()].sort((a, b) => a.localeCompare(b))
6647
+ similarityReasons: [...reasons.keys()].sort((a, b) => a.localeCompare(b)),
6648
+ similarityBasis: "feature_overlap"
6208
6649
  }
6209
6650
  ];
6210
6651
  }).sort((left, right) => right.confidence - left.confidence || left.id.localeCompare(right.id));
@@ -6287,9 +6728,11 @@ function buildModuleFormHyperedges(graph) {
6287
6728
  ];
6288
6729
  });
6289
6730
  }
6290
- function enrichGraph(graph, manifests, analyses) {
6731
+ function enrichGraph(graph, manifests, analyses, extraSimilarityEdges = []) {
6291
6732
  const similarityEdges = buildSemanticSimilarityEdges(graph.nodes, graph.edges, manifests, analyses);
6292
- const enrichedEdges = [...graph.edges, ...similarityEdges].sort((left, right) => left.id.localeCompare(right.id));
6733
+ const enrichedEdges = uniqueBy([...graph.edges, ...similarityEdges, ...extraSimilarityEdges], (edge) => edge.id).sort(
6734
+ (left, right) => left.id.localeCompare(right.id)
6735
+ );
6293
6736
  const hyperedges = uniqueBy(
6294
6737
  [
6295
6738
  ...buildTopicHyperedges({ ...graph, edges: enrichedEdges, hyperedges: [] }),
@@ -6420,13 +6863,19 @@ function queryGraph(graph, question, searchResults, options) {
6420
6863
  const traversal = options?.traversal ?? "bfs";
6421
6864
  const budget = Math.max(3, Math.min(options?.budget ?? 12, 50));
6422
6865
  const matches = uniqueBy(
6423
- [...pageSearchMatches(graph, question, searchResults), ...nodeMatches(graph, question), ...hyperedgeMatches(graph, question)],
6866
+ [
6867
+ ...options?.semanticMatches ?? [],
6868
+ ...pageSearchMatches(graph, question, searchResults),
6869
+ ...nodeMatches(graph, question),
6870
+ ...hyperedgeMatches(graph, question)
6871
+ ],
6424
6872
  (match) => `${match.type}:${match.id}`
6425
6873
  ).sort((left, right) => right.score - left.score || left.label.localeCompare(right.label)).slice(0, 12);
6426
6874
  const pages = pageById(graph);
6427
6875
  const seeds = uniqueBy(
6428
6876
  [
6429
6877
  ...searchResults.flatMap((result) => pages.get(result.pageId)?.nodeIds ?? []),
6878
+ ...matches.filter((match) => match.type === "page").flatMap((match) => pages.get(match.id)?.nodeIds ?? []),
6430
6879
  ...matches.filter((match) => match.type === "node").map((match) => match.id),
6431
6880
  ...matches.filter((match) => match.type === "hyperedge").flatMap((match) => graph.hyperedges.find((hyperedge) => hyperedge.id === match.id)?.nodeIds ?? [])
6432
6881
  ],
@@ -6458,6 +6907,7 @@ function queryGraph(graph, question, searchResults, options) {
6458
6907
  const pageIds = uniqueBy(
6459
6908
  [
6460
6909
  ...searchResults.map((result) => result.pageId),
6910
+ ...matches.filter((match) => match.type === "page").map((match) => match.id),
6461
6911
  ...visitedNodeIds.flatMap((nodeId) => {
6462
6912
  const node = nodes.get(nodeId);
6463
6913
  return node?.pageId ? [node.pageId] : [];
@@ -6478,7 +6928,7 @@ function queryGraph(graph, question, searchResults, options) {
6478
6928
  traversal,
6479
6929
  seedNodeIds: seeds,
6480
6930
  seedPageIds: uniqueBy(
6481
- searchResults.map((result) => result.pageId),
6931
+ [...searchResults.map((result) => result.pageId), ...matches.filter((match) => match.type === "page").map((match) => match.id)],
6482
6932
  (item) => item
6483
6933
  ),
6484
6934
  visitedNodeIds,
@@ -6729,6 +7179,7 @@ function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutput
6729
7179
  kind: "source",
6730
7180
  title: analysis.title,
6731
7181
  ...manifest.sourceType ? { source_type: manifest.sourceType } : {},
7182
+ ...manifest.sourceClass ? { source_class: manifest.sourceClass } : {},
6732
7183
  tags: decoratedTags(analysis.code ? ["source", "code"] : ["source"], decorations),
6733
7184
  source_ids: [manifest.sourceId],
6734
7185
  project_ids: decorations?.projectIds ?? [],
@@ -6752,6 +7203,7 @@ function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutput
6752
7203
  `Source ID: \`${manifest.sourceId}\``,
6753
7204
  manifest.url ? `Source URL: ${manifest.url}` : `Source Path: \`${manifest.originalPath ?? manifest.storedPath}\``,
6754
7205
  ...manifest.sourceType ? [`Source Type: \`${manifest.sourceType}\``, ""] : [""],
7206
+ ...manifest.sourceClass ? [`Source Class: \`${manifest.sourceClass}\``, ""] : [],
6755
7207
  "",
6756
7208
  "## Summary",
6757
7209
  "",
@@ -6797,6 +7249,7 @@ function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutput
6797
7249
  title: analysis.title,
6798
7250
  kind: "source",
6799
7251
  sourceType: manifest.sourceType,
7252
+ sourceClass: manifest.sourceClass,
6800
7253
  sourceIds: [manifest.sourceId],
6801
7254
  projectIds: decorations?.projectIds ?? [],
6802
7255
  nodeIds,
@@ -6859,6 +7312,7 @@ function buildModulePage(input) {
6859
7312
  page_id: pageId,
6860
7313
  kind: "module",
6861
7314
  title,
7315
+ ...manifest.sourceClass ? { source_class: manifest.sourceClass } : {},
6862
7316
  tags: decoratedTags(["module", "code", code.language], { projectIds: input.projectIds, extraTags: input.extraTags }),
6863
7317
  source_ids: [manifest.sourceId],
6864
7318
  project_ids: input.projectIds ?? [],
@@ -6890,6 +7344,7 @@ function buildModulePage(input) {
6890
7344
  `Source ID: \`${manifest.sourceId}\``,
6891
7345
  `Source Path: \`${manifest.originalPath ?? manifest.storedPath}\``,
6892
7346
  ...manifest.repoRelativePath ? [`Repo Path: \`${manifest.repoRelativePath}\``] : [],
7347
+ ...manifest.sourceClass ? [`Source Class: \`${manifest.sourceClass}\``] : [],
6893
7348
  `Language: \`${code.language}\``,
6894
7349
  ...code.moduleName ? [`Module Name: \`${code.moduleName}\``] : [],
6895
7350
  ...code.namespace ? [`Namespace/Package: \`${code.namespace}\``] : [],
@@ -6940,6 +7395,7 @@ function buildModulePage(input) {
6940
7395
  path: relativePath,
6941
7396
  title,
6942
7397
  kind: "module",
7398
+ sourceClass: manifest.sourceClass,
6943
7399
  sourceIds: [manifest.sourceId],
6944
7400
  projectIds: input.projectIds ?? [],
6945
7401
  nodeIds,
@@ -6974,6 +7430,7 @@ function buildAggregatePage(kind, name, descriptions, sourceAnalyses, sourceHash
6974
7430
  page_id: pageId,
6975
7431
  kind,
6976
7432
  title: name,
7433
+ ...decorations?.sourceClass ? { source_class: decorations.sourceClass } : {},
6977
7434
  tags: decoratedTags(metadata.status === "candidate" ? [kind, "candidate"] : [kind], decorations),
6978
7435
  source_ids: sourceIds,
6979
7436
  project_ids: decorations?.projectIds ?? [],
@@ -7015,6 +7472,7 @@ function buildAggregatePage(kind, name, descriptions, sourceAnalyses, sourceHash
7015
7472
  path: relativePath,
7016
7473
  title: name,
7017
7474
  kind,
7475
+ sourceClass: decorations?.sourceClass,
7018
7476
  sourceIds,
7019
7477
  projectIds: decorations?.projectIds ?? [],
7020
7478
  nodeIds: [pageId],
@@ -7146,15 +7604,15 @@ function sourceTypeForNode(node, pagesById) {
7146
7604
  return pagesById.get(node.pageId)?.sourceType;
7147
7605
  }
7148
7606
  function supportingPathDetails(graph, edge) {
7149
- const path23 = shortestGraphPath(graph, edge.source, edge.target);
7607
+ const path25 = shortestGraphPath(graph, edge.source, edge.target);
7150
7608
  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));
7609
+ const pathEdges = path25.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
7152
7610
  return {
7153
- pathNodeIds: path23.nodeIds,
7154
- pathEdgeIds: path23.edgeIds,
7611
+ pathNodeIds: path25.nodeIds,
7612
+ pathEdgeIds: path25.edgeIds,
7155
7613
  pathRelations: pathEdges.map((item) => item.relation),
7156
7614
  pathEvidenceClasses: pathEdges.map((item) => item.evidenceClass),
7157
- pathSummary: path23.summary
7615
+ pathSummary: path25.summary
7158
7616
  };
7159
7617
  }
7160
7618
  function surpriseScore(edge, graph, pagesById, hyperedgesByNodeId) {
@@ -7223,7 +7681,7 @@ function topSurprisingConnections(graph, pagesById) {
7223
7681
  }).map((edge) => {
7224
7682
  const source = nodesById.get(edge.source);
7225
7683
  const target = nodesById.get(edge.target);
7226
- const path23 = supportingPathDetails(graph, edge);
7684
+ const path25 = supportingPathDetails(graph, edge);
7227
7685
  const scored = surpriseScore(edge, graph, pagesById, hyperedgesByNodeId);
7228
7686
  return {
7229
7687
  id: edge.id,
@@ -7234,11 +7692,11 @@ function topSurprisingConnections(graph, pagesById) {
7234
7692
  relation: edge.relation,
7235
7693
  evidenceClass: edge.evidenceClass,
7236
7694
  confidence: edge.confidence,
7237
- pathNodeIds: path23.pathNodeIds,
7238
- pathEdgeIds: path23.pathEdgeIds,
7239
- pathRelations: path23.pathRelations,
7240
- pathEvidenceClasses: path23.pathEvidenceClasses,
7241
- pathSummary: path23.pathSummary,
7695
+ pathNodeIds: path25.pathNodeIds,
7696
+ pathEdgeIds: path25.pathEdgeIds,
7697
+ pathRelations: path25.pathRelations,
7698
+ pathEvidenceClasses: path25.pathEvidenceClasses,
7699
+ pathSummary: path25.pathSummary,
7242
7700
  why: scored.why,
7243
7701
  explanation: scored.explanation,
7244
7702
  surpriseScore: scored.score
@@ -7263,10 +7721,12 @@ function suggestedGraphQuestions(graph) {
7263
7721
  ]).slice(0, 6);
7264
7722
  }
7265
7723
  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) => {
7724
+ const firstPartyGraph = filterGraphBySourceClass(input.graph, "first_party");
7725
+ const reportGraph = firstPartyGraph.nodes.length ? firstPartyGraph : input.graph;
7726
+ const pagesById = new Map(reportGraph.pages.map((page) => [page.id, page]));
7727
+ const godNodes = reportGraph.nodes.filter((node) => node.isGodNode).sort((left, right) => (right.degree ?? 0) - (left.degree ?? 0)).slice(0, 8);
7728
+ const bridgeNodes = reportGraph.nodes.filter((node) => (node.bridgeScore ?? 0) > 0).sort((left, right) => (right.bridgeScore ?? 0) - (left.bridgeScore ?? 0)).slice(0, 8);
7729
+ const thinCommunities = (reportGraph.communities ?? []).filter((community) => community.nodeIds.length <= 2).map((community) => {
7270
7730
  const page = input.communityPages.find((candidate) => candidate.id === `graph:${community.id}`);
7271
7731
  return {
7272
7732
  id: community.id,
@@ -7277,8 +7737,19 @@ function buildGraphReportArtifact(input) {
7277
7737
  title: page?.title
7278
7738
  };
7279
7739
  });
7280
- const surprisingConnections = topSurprisingConnections(input.graph, pagesById);
7281
- const groupPatterns = topGroupPatterns(input.graph);
7740
+ const surprisingConnections = topSurprisingConnections(reportGraph, pagesById);
7741
+ const groupPatterns = topGroupPatterns(reportGraph);
7742
+ const breakdown = sourceClassBreakdown(input.graph);
7743
+ const warnings = [];
7744
+ const nonFirstPartyNodes = input.graph.nodes.length - breakdown.first_party.nodes;
7745
+ if (input.graph.nodes.length >= 1200) {
7746
+ warnings.push(`Large graph detected (${input.graph.nodes.length} nodes). First-party defaults are applied to report highlights.`);
7747
+ }
7748
+ if (nonFirstPartyNodes > 0 && nonFirstPartyNodes / Math.max(1, input.graph.nodes.length) >= 0.25) {
7749
+ warnings.push(
7750
+ `Non-first-party material accounts for ${(nonFirstPartyNodes / Math.max(1, input.graph.nodes.length) * 100).toFixed(1)}% of graph nodes.`
7751
+ );
7752
+ }
7282
7753
  return {
7283
7754
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7284
7755
  graphHash: input.graphHash,
@@ -7288,6 +7759,14 @@ function buildGraphReportArtifact(input) {
7288
7759
  pages: input.graph.pages.length,
7289
7760
  communities: input.graph.communities?.length ?? 0
7290
7761
  },
7762
+ firstPartyOverview: {
7763
+ nodes: reportGraph.nodes.length,
7764
+ edges: reportGraph.edges.length,
7765
+ pages: reportGraph.pages.length,
7766
+ communities: reportGraph.communities?.length ?? 0
7767
+ },
7768
+ sourceClassBreakdown: breakdown,
7769
+ warnings,
7291
7770
  benchmark: input.benchmark ? {
7292
7771
  generatedAt: input.benchmark.generatedAt,
7293
7772
  stale: input.benchmarkStale ?? false,
@@ -7383,6 +7862,18 @@ function buildGraphReportPage(input) {
7383
7862
  `- Edges: ${input.report.overview.edges}`,
7384
7863
  `- Pages: ${input.report.overview.pages}`,
7385
7864
  `- Communities: ${input.report.overview.communities}`,
7865
+ `- Default Focus: First-party nodes/pages (${input.report.firstPartyOverview.nodes} nodes, ${input.report.firstPartyOverview.edges} edges, ${input.report.firstPartyOverview.pages} pages).`,
7866
+ "",
7867
+ "## Repo Quality Warnings",
7868
+ "",
7869
+ ...input.report.warnings.length ? input.report.warnings.map((warning) => `- ${warning}`) : ["- No large-repo warnings."],
7870
+ "",
7871
+ "## Source Class Breakdown",
7872
+ "",
7873
+ `- First-party: ${input.report.sourceClassBreakdown.first_party.sources} sources, ${input.report.sourceClassBreakdown.first_party.pages} pages, ${input.report.sourceClassBreakdown.first_party.nodes} nodes`,
7874
+ `- Third-party: ${input.report.sourceClassBreakdown.third_party.sources} sources, ${input.report.sourceClassBreakdown.third_party.pages} pages, ${input.report.sourceClassBreakdown.third_party.nodes} nodes`,
7875
+ `- Resources: ${input.report.sourceClassBreakdown.resource.sources} sources, ${input.report.sourceClassBreakdown.resource.pages} pages, ${input.report.sourceClassBreakdown.resource.nodes} nodes`,
7876
+ `- Generated: ${input.report.sourceClassBreakdown.generated.sources} sources, ${input.report.sourceClassBreakdown.generated.pages} pages, ${input.report.sourceClassBreakdown.generated.nodes} nodes`,
7386
7877
  "",
7387
7878
  "## Benchmark Summary",
7388
7879
  "",
@@ -8090,13 +8581,13 @@ function buildOutputAssetManifest(input) {
8090
8581
  }
8091
8582
 
8092
8583
  // src/outputs.ts
8093
- import fs13 from "fs/promises";
8094
- import path16 from "path";
8584
+ import fs14 from "fs/promises";
8585
+ import path18 from "path";
8095
8586
  import matter7 from "gray-matter";
8096
8587
 
8097
8588
  // src/pages.ts
8098
- import fs12 from "fs/promises";
8099
- import path15 from "path";
8589
+ import fs13 from "fs/promises";
8590
+ import path17 from "path";
8100
8591
  import matter6 from "gray-matter";
8101
8592
  function normalizeStringArray(value) {
8102
8593
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
@@ -8121,6 +8612,9 @@ function normalizePageManager(value, fallback = "system") {
8121
8612
  function normalizeSourceType(value) {
8122
8613
  return value === "arxiv" || value === "doi" || value === "tweet" || value === "article" || value === "url" ? value : void 0;
8123
8614
  }
8615
+ function normalizeSourceClass(value) {
8616
+ return value === "first_party" || value === "third_party" || value === "resource" || value === "generated" ? value : void 0;
8617
+ }
8124
8618
  function normalizeOutputFormat(value, fallback = "markdown") {
8125
8619
  return value === "report" || value === "slides" || value === "chart" || value === "image" ? value : fallback;
8126
8620
  }
@@ -8171,7 +8665,7 @@ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
8171
8665
  updatedAt: updatedFallback
8172
8666
  };
8173
8667
  }
8174
- const content = await fs12.readFile(absolutePath, "utf8");
8668
+ const content = await fs13.readFile(absolutePath, "utf8");
8175
8669
  const parsed = matter6(content);
8176
8670
  return {
8177
8671
  status: normalizePageStatus(parsed.data.status, defaults.status ?? "active"),
@@ -8210,7 +8704,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
8210
8704
  const now = (/* @__PURE__ */ new Date()).toISOString();
8211
8705
  const fallbackCreatedAt = defaults.createdAt ?? now;
8212
8706
  const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
8213
- const title = typeof parsed.data.title === "string" ? parsed.data.title : path15.basename(relativePath, ".md");
8707
+ const title = typeof parsed.data.title === "string" ? parsed.data.title : path17.basename(relativePath, ".md");
8214
8708
  const kind = inferPageKind(relativePath, parsed.data.kind);
8215
8709
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
8216
8710
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
@@ -8227,6 +8721,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
8227
8721
  title,
8228
8722
  kind,
8229
8723
  sourceType: normalizeSourceType(parsed.data.source_type),
8724
+ sourceClass: normalizeSourceClass(parsed.data.source_class),
8230
8725
  sourceIds,
8231
8726
  projectIds,
8232
8727
  nodeIds,
@@ -8250,18 +8745,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
8250
8745
  };
8251
8746
  }
8252
8747
  async function loadInsightPages(wikiDir) {
8253
- const insightsDir = path15.join(wikiDir, "insights");
8748
+ const insightsDir = path17.join(wikiDir, "insights");
8254
8749
  if (!await fileExists(insightsDir)) {
8255
8750
  return [];
8256
8751
  }
8257
- const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path15.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
8752
+ const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path17.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
8258
8753
  const insights = [];
8259
8754
  for (const absolutePath of files) {
8260
- const relativePath = toPosix(path15.relative(wikiDir, absolutePath));
8261
- const content = await fs12.readFile(absolutePath, "utf8");
8755
+ const relativePath = toPosix(path17.relative(wikiDir, absolutePath));
8756
+ const content = await fs13.readFile(absolutePath, "utf8");
8262
8757
  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");
8758
+ const stats = await fs13.stat(absolutePath);
8759
+ const title = typeof parsed.data.title === "string" ? parsed.data.title : path17.basename(absolutePath, ".md");
8265
8760
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
8266
8761
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
8267
8762
  const nodeIds = normalizeStringArray(parsed.data.node_ids);
@@ -8279,6 +8774,7 @@ async function loadInsightPages(wikiDir) {
8279
8774
  path: relativePath,
8280
8775
  title,
8281
8776
  kind: "insight",
8777
+ sourceClass: normalizeSourceClass(parsed.data.source_class),
8282
8778
  sourceIds,
8283
8779
  projectIds,
8284
8780
  nodeIds,
@@ -8323,27 +8819,27 @@ function relatedOutputsForPage(targetPage, outputPages) {
8323
8819
  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
8820
  }
8325
8821
  async function resolveUniqueOutputSlug(wikiDir, baseSlug) {
8326
- const outputsDir = path16.join(wikiDir, "outputs");
8822
+ const outputsDir = path18.join(wikiDir, "outputs");
8327
8823
  const root = baseSlug || "output";
8328
8824
  let candidate = root;
8329
8825
  let counter = 2;
8330
- while (await fileExists(path16.join(outputsDir, `${candidate}.md`))) {
8826
+ while (await fileExists(path18.join(outputsDir, `${candidate}.md`))) {
8331
8827
  candidate = `${root}-${counter}`;
8332
8828
  counter++;
8333
8829
  }
8334
8830
  return candidate;
8335
8831
  }
8336
8832
  async function loadSavedOutputPages(wikiDir) {
8337
- const outputsDir = path16.join(wikiDir, "outputs");
8338
- const entries = await fs13.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
8833
+ const outputsDir = path18.join(wikiDir, "outputs");
8834
+ const entries = await fs14.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
8339
8835
  const outputs = [];
8340
8836
  for (const entry of entries) {
8341
8837
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md") {
8342
8838
  continue;
8343
8839
  }
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");
8840
+ const relativePath = path18.posix.join("outputs", entry.name);
8841
+ const absolutePath = path18.join(outputsDir, entry.name);
8842
+ const content = await fs14.readFile(absolutePath, "utf8");
8347
8843
  const parsed = matter7(content);
8348
8844
  const slug = entry.name.replace(/\.md$/, "");
8349
8845
  const title = typeof parsed.data.title === "string" ? parsed.data.title : slug;
@@ -8356,7 +8852,7 @@ async function loadSavedOutputPages(wikiDir) {
8356
8852
  const relatedSourceIds = normalizeStringArray(parsed.data.related_source_ids);
8357
8853
  const backlinks = normalizeStringArray(parsed.data.backlinks);
8358
8854
  const compiledFrom = normalizeStringArray(parsed.data.compiled_from);
8359
- const stats = await fs13.stat(absolutePath);
8855
+ const stats = await fs14.stat(absolutePath);
8360
8856
  const createdAt = typeof parsed.data.created_at === "string" ? parsed.data.created_at : stats.birthtimeMs > 0 ? stats.birthtime.toISOString() : stats.mtime.toISOString();
8361
8857
  const updatedAt = typeof parsed.data.updated_at === "string" ? parsed.data.updated_at : stats.mtime.toISOString();
8362
8858
  outputs.push({
@@ -8394,8 +8890,8 @@ async function loadSavedOutputPages(wikiDir) {
8394
8890
  }
8395
8891
 
8396
8892
  // src/search.ts
8397
- import fs14 from "fs/promises";
8398
- import path17 from "path";
8893
+ import fs15 from "fs/promises";
8894
+ import path19 from "path";
8399
8895
  import matter8 from "gray-matter";
8400
8896
  function getDatabaseSync() {
8401
8897
  const builtin = process.getBuiltinModule?.("node:sqlite");
@@ -8417,8 +8913,11 @@ function normalizeStatus(value) {
8417
8913
  function normalizeSourceType2(value) {
8418
8914
  return value === "arxiv" || value === "doi" || value === "tweet" || value === "article" || value === "url" ? value : void 0;
8419
8915
  }
8916
+ function normalizeSourceClass2(value) {
8917
+ return value === "first_party" || value === "third_party" || value === "resource" || value === "generated" ? value : void 0;
8918
+ }
8420
8919
  async function rebuildSearchIndex(dbPath, pages, wikiDir) {
8421
- await ensureDir(path17.dirname(dbPath));
8920
+ await ensureDir(path19.dirname(dbPath));
8422
8921
  const DatabaseSync = getDatabaseSync();
8423
8922
  const db = new DatabaseSync(dbPath);
8424
8923
  db.exec("PRAGMA journal_mode = WAL;");
@@ -8433,6 +8932,7 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
8433
8932
  kind TEXT NOT NULL,
8434
8933
  status TEXT NOT NULL,
8435
8934
  source_type TEXT NOT NULL,
8935
+ source_class TEXT NOT NULL,
8436
8936
  project_ids TEXT NOT NULL,
8437
8937
  project_key TEXT NOT NULL
8438
8938
  );
@@ -8446,11 +8946,11 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
8446
8946
  DELETE FROM pages;
8447
8947
  `);
8448
8948
  const insertPage = db.prepare(
8449
- "INSERT INTO pages (id, path, title, body, kind, status, source_type, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
8949
+ "INSERT INTO pages (id, path, title, body, kind, status, source_type, source_class, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
8450
8950
  );
8451
8951
  for (const page of pages) {
8452
- const absolutePath = path17.join(wikiDir, page.path);
8453
- const content = await fs14.readFile(absolutePath, "utf8");
8952
+ const absolutePath = path19.join(wikiDir, page.path);
8953
+ const content = await fs15.readFile(absolutePath, "utf8");
8454
8954
  const parsed = matter8(content);
8455
8955
  insertPage.run(
8456
8956
  page.id,
@@ -8460,6 +8960,7 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
8460
8960
  page.kind,
8461
8961
  page.status,
8462
8962
  typeof parsed.data.source_type === "string" ? parsed.data.source_type : "",
8963
+ typeof parsed.data.source_class === "string" ? parsed.data.source_class : "",
8463
8964
  JSON.stringify(page.projectIds),
8464
8965
  page.projectIds.map((projectId) => `|${projectId}|`).join("")
8465
8966
  );
@@ -8497,6 +8998,10 @@ function searchPages(dbPath, query, limitOrOptions = 5) {
8497
8998
  clauses.push("pages.source_type = ?");
8498
8999
  params.push(options.sourceType);
8499
9000
  }
9001
+ if (options.sourceClass && options.sourceClass !== "all") {
9002
+ clauses.push("pages.source_class = ?");
9003
+ params.push(options.sourceClass);
9004
+ }
8500
9005
  const statement = db.prepare(`
8501
9006
  SELECT
8502
9007
  pages.id AS pageId,
@@ -8505,6 +9010,7 @@ function searchPages(dbPath, query, limitOrOptions = 5) {
8505
9010
  pages.kind AS kind,
8506
9011
  pages.status AS status,
8507
9012
  pages.source_type AS sourceType,
9013
+ pages.source_class AS sourceClass,
8508
9014
  pages.project_ids AS projectIds,
8509
9015
  snippet(page_search, 1, '[', ']', '...', 16) AS snippet,
8510
9016
  bm25(page_search) AS rank
@@ -8533,6 +9039,7 @@ function searchPages(dbPath, query, limitOrOptions = 5) {
8533
9039
  kind: normalizeKind(row.kind),
8534
9040
  status: normalizeStatus(row.status),
8535
9041
  sourceType: normalizeSourceType2(row.sourceType),
9042
+ sourceClass: normalizeSourceClass2(row.sourceClass),
8536
9043
  snippet: String(row.snippet ?? ""),
8537
9044
  rank: Number(row.rank ?? 0)
8538
9045
  }));
@@ -8560,7 +9067,7 @@ function outputFormatInstruction(format) {
8560
9067
  }
8561
9068
  }
8562
9069
  function outputAssetPath(slug, fileName) {
8563
- return toPosix(path18.join("outputs", "assets", slug, fileName));
9070
+ return toPosix(path20.join("outputs", "assets", slug, fileName));
8564
9071
  }
8565
9072
  function outputAssetId(slug, role) {
8566
9073
  return `output:${slug}:asset:${role}`;
@@ -8700,7 +9207,7 @@ async function resolveImageGenerationProvider(rootDir) {
8700
9207
  if (!providerConfig) {
8701
9208
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
8702
9209
  }
8703
- const { createProvider: createProvider2 } = await import("./registry-6KZMA3XM.js");
9210
+ const { createProvider: createProvider2 } = await import("./registry-YGVTLIZH.js");
8704
9211
  return createProvider2(preferredProviderId, providerConfig, rootDir);
8705
9212
  }
8706
9213
  async function generateOutputArtifacts(rootDir, input) {
@@ -8898,7 +9405,7 @@ async function generateOutputArtifacts(rootDir, input) {
8898
9405
  };
8899
9406
  }
8900
9407
  function normalizeProjectRoot(root) {
8901
- const normalized = toPosix(path18.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
9408
+ const normalized = toPosix(path20.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
8902
9409
  return normalized;
8903
9410
  }
8904
9411
  function projectEntries(config) {
@@ -8924,10 +9431,10 @@ function manifestPathForProject(rootDir, manifest) {
8924
9431
  if (!rawPath) {
8925
9432
  return toPosix(manifest.storedPath);
8926
9433
  }
8927
- if (!path18.isAbsolute(rawPath)) {
9434
+ if (!path20.isAbsolute(rawPath)) {
8928
9435
  return normalizeProjectRoot(rawPath);
8929
9436
  }
8930
- const relative = toPosix(path18.relative(rootDir, rawPath));
9437
+ const relative = toPosix(path20.relative(rootDir, rawPath));
8931
9438
  return relative.startsWith("..") ? toPosix(rawPath) : normalizeProjectRoot(relative);
8932
9439
  }
8933
9440
  function prefixMatches(value, prefix) {
@@ -9101,7 +9608,7 @@ function pageHashes(pages) {
9101
9608
  return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
9102
9609
  }
9103
9610
  async function buildManagedGraphPage(absolutePath, defaults, build) {
9104
- const existingContent = await fileExists(absolutePath) ? await fs15.readFile(absolutePath, "utf8") : null;
9611
+ const existingContent = await fileExists(absolutePath) ? await fs16.readFile(absolutePath, "utf8") : null;
9105
9612
  let existing = await loadExistingManagedPageState(absolutePath, {
9106
9613
  status: defaults.status ?? "active",
9107
9614
  managedBy: defaults.managedBy
@@ -9139,7 +9646,7 @@ async function buildManagedGraphPage(absolutePath, defaults, build) {
9139
9646
  return built;
9140
9647
  }
9141
9648
  async function buildManagedContent(absolutePath, defaults, build) {
9142
- const existingContent = await fileExists(absolutePath) ? await fs15.readFile(absolutePath, "utf8") : null;
9649
+ const existingContent = await fileExists(absolutePath) ? await fs16.readFile(absolutePath, "utf8") : null;
9143
9650
  let existing = await loadExistingManagedPageState(absolutePath, {
9144
9651
  status: defaults.status ?? "active",
9145
9652
  managedBy: defaults.managedBy
@@ -9258,7 +9765,11 @@ function deriveGraphMetrics(nodes, edges) {
9258
9765
  communities
9259
9766
  };
9260
9767
  }
9768
+ function resetGraphNodeMetrics(nodes) {
9769
+ return nodes.map(({ communityId: _communityId, degree: _degree, bridgeScore: _bridgeScore, isGodNode: _isGodNode, ...node }) => node);
9770
+ }
9261
9771
  function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9772
+ const manifestsById = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
9262
9773
  const sourceNodes = manifests.map((manifest) => ({
9263
9774
  id: `source:${manifest.sourceId}`,
9264
9775
  type: "source",
@@ -9268,6 +9779,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9268
9779
  confidence: 1,
9269
9780
  sourceIds: [manifest.sourceId],
9270
9781
  projectIds: scopedProjectIdsFromSources([manifest.sourceId], sourceProjects),
9782
+ sourceClass: manifest.sourceClass,
9271
9783
  language: manifest.language
9272
9784
  }));
9273
9785
  const conceptMap = /* @__PURE__ */ new Map();
@@ -9284,7 +9796,6 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9284
9796
  edgesById.add(edge.id);
9285
9797
  edges.push(edge);
9286
9798
  };
9287
- const manifestsById = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
9288
9799
  const analysesBySourceId = new Map(analyses.map((analysis) => [analysis.sourceId, analysis]));
9289
9800
  for (const analysis of analyses) {
9290
9801
  for (const concept of analysis.concepts) {
@@ -9298,7 +9809,8 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9298
9809
  freshness: "fresh",
9299
9810
  confidence: nodeConfidence(sourceIds.length),
9300
9811
  sourceIds,
9301
- projectIds: scopedProjectIdsFromSources(sourceIds, sourceProjects)
9812
+ projectIds: scopedProjectIdsFromSources(sourceIds, sourceProjects),
9813
+ sourceClass: aggregateManifestSourceClass(manifests, sourceIds)
9302
9814
  });
9303
9815
  pushEdge({
9304
9816
  id: `${analysis.sourceId}->${concept.id}`,
@@ -9322,7 +9834,8 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9322
9834
  freshness: "fresh",
9323
9835
  confidence: nodeConfidence(sourceIds.length),
9324
9836
  sourceIds,
9325
- projectIds: scopedProjectIdsFromSources(sourceIds, sourceProjects)
9837
+ projectIds: scopedProjectIdsFromSources(sourceIds, sourceProjects),
9838
+ sourceClass: aggregateManifestSourceClass(manifests, sourceIds)
9326
9839
  });
9327
9840
  pushEdge({
9328
9841
  id: `${analysis.sourceId}->${entity.id}`,
@@ -9350,6 +9863,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9350
9863
  confidence: 1,
9351
9864
  sourceIds: [analysis.sourceId],
9352
9865
  projectIds: scopedProjectIdsFromSources([analysis.sourceId], sourceProjects),
9866
+ sourceClass: manifest.sourceClass,
9353
9867
  language: analysis.code.language,
9354
9868
  moduleId
9355
9869
  });
@@ -9373,6 +9887,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9373
9887
  confidence: symbol.exported ? 0.88 : 0.74,
9374
9888
  sourceIds: [analysis.sourceId],
9375
9889
  projectIds: scopedProjectIdsFromSources([analysis.sourceId], sourceProjects),
9890
+ sourceClass: manifest.sourceClass,
9376
9891
  language: analysis.code.language,
9377
9892
  moduleId,
9378
9893
  symbolKind: symbol.kind
@@ -9413,6 +9928,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
9413
9928
  confidence: 1,
9414
9929
  sourceIds: [analysis.sourceId],
9415
9930
  projectIds: scopedProjectIdsFromSources([analysis.sourceId], sourceProjects),
9931
+ sourceClass: manifest.sourceClass,
9416
9932
  language: analysis.code.language,
9417
9933
  moduleId
9418
9934
  });
@@ -9608,7 +10124,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
9608
10124
  const benchmark = await readJsonFile(paths.benchmarkPath);
9609
10125
  const communityRecords = [];
9610
10126
  for (const community of graph.communities ?? []) {
9611
- const absolutePath = path18.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
10127
+ const absolutePath = path20.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
9612
10128
  communityRecords.push(
9613
10129
  await buildManagedGraphPage(
9614
10130
  absolutePath,
@@ -9636,7 +10152,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
9636
10152
  recentResearchSources: recentResearchSourcePages(graph, previousCompiledAt),
9637
10153
  graphHash: graphHash(graph)
9638
10154
  });
9639
- const reportAbsolutePath = path18.join(paths.wikiDir, "graph", "report.md");
10155
+ const reportAbsolutePath = path20.join(paths.wikiDir, "graph", "report.md");
9640
10156
  const reportRecord = await buildManagedGraphPage(
9641
10157
  reportAbsolutePath,
9642
10158
  {
@@ -9657,7 +10173,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
9657
10173
  };
9658
10174
  }
9659
10175
  async function writePage(wikiDir, relativePath, content, changedPages) {
9660
- const absolutePath = path18.resolve(wikiDir, relativePath);
10176
+ const absolutePath = path20.resolve(wikiDir, relativePath);
9661
10177
  const changed = await writeFileIfChanged(absolutePath, content);
9662
10178
  if (changed) {
9663
10179
  changedPages.push(relativePath);
@@ -9688,6 +10204,7 @@ function emptyGraphPage(input) {
9688
10204
  path: input.path,
9689
10205
  title: input.title,
9690
10206
  kind: input.kind,
10207
+ sourceClass: input.sourceClass,
9691
10208
  sourceIds: input.sourceIds,
9692
10209
  projectIds: input.projectIds ?? [],
9693
10210
  nodeIds: input.nodeIds,
@@ -9719,29 +10236,29 @@ async function requiredCompileArtifactsExist(paths) {
9719
10236
  paths.graphPath,
9720
10237
  paths.codeIndexPath,
9721
10238
  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")
10239
+ path20.join(paths.wikiDir, "index.md"),
10240
+ path20.join(paths.wikiDir, "sources", "index.md"),
10241
+ path20.join(paths.wikiDir, "code", "index.md"),
10242
+ path20.join(paths.wikiDir, "concepts", "index.md"),
10243
+ path20.join(paths.wikiDir, "entities", "index.md"),
10244
+ path20.join(paths.wikiDir, "outputs", "index.md"),
10245
+ path20.join(paths.wikiDir, "projects", "index.md"),
10246
+ path20.join(paths.wikiDir, "candidates", "index.md")
9730
10247
  ];
9731
10248
  const checks = await Promise.all(requiredPaths.map((filePath) => fileExists(filePath)));
9732
10249
  return checks.every(Boolean);
9733
10250
  }
9734
10251
  async function loadAvailableCachedAnalyses(paths, manifests) {
9735
10252
  const analyses = await Promise.all(
9736
- manifests.map(async (manifest) => readJsonFile(path18.join(paths.analysesDir, `${manifest.sourceId}.json`)))
10253
+ manifests.map(async (manifest) => readJsonFile(path20.join(paths.analysesDir, `${manifest.sourceId}.json`)))
9737
10254
  );
9738
10255
  return analyses.filter((analysis) => Boolean(analysis));
9739
10256
  }
9740
10257
  function approvalManifestPath(paths, approvalId) {
9741
- return path18.join(paths.approvalsDir, approvalId, "manifest.json");
10258
+ return path20.join(paths.approvalsDir, approvalId, "manifest.json");
9742
10259
  }
9743
10260
  function approvalGraphPath(paths, approvalId) {
9744
- return path18.join(paths.approvalsDir, approvalId, "state", "graph.json");
10261
+ return path20.join(paths.approvalsDir, approvalId, "state", "graph.json");
9745
10262
  }
9746
10263
  async function readApprovalManifest(paths, approvalId) {
9747
10264
  const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
@@ -9751,7 +10268,7 @@ async function readApprovalManifest(paths, approvalId) {
9751
10268
  return manifest;
9752
10269
  }
9753
10270
  async function writeApprovalManifest(paths, manifest) {
9754
- await fs15.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
10271
+ await fs16.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
9755
10272
  `, "utf8");
9756
10273
  }
9757
10274
  async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
@@ -9766,7 +10283,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
9766
10283
  continue;
9767
10284
  }
9768
10285
  const previousPage = previousPagesById.get(nextPage.id);
9769
- const currentExists = await fileExists(path18.join(paths.wikiDir, file.relativePath));
10286
+ const currentExists = await fileExists(path20.join(paths.wikiDir, file.relativePath));
9770
10287
  if (previousPage && previousPage.path !== nextPage.path) {
9771
10288
  entries.push({
9772
10289
  pageId: nextPage.id,
@@ -9799,7 +10316,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
9799
10316
  const previousPage = previousPagesByPath.get(deletedPath);
9800
10317
  entries.push({
9801
10318
  pageId: previousPage?.id ?? `page:${slugify(deletedPath)}`,
9802
- title: previousPage?.title ?? path18.basename(deletedPath, ".md"),
10319
+ title: previousPage?.title ?? path20.basename(deletedPath, ".md"),
9803
10320
  kind: previousPage?.kind ?? "index",
9804
10321
  changeType: "delete",
9805
10322
  status: "pending",
@@ -9811,16 +10328,16 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
9811
10328
  }
9812
10329
  async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGraph, graph) {
9813
10330
  const approvalId = `compile-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
9814
- const approvalDir = path18.join(paths.approvalsDir, approvalId);
10331
+ const approvalDir = path20.join(paths.approvalsDir, approvalId);
9815
10332
  await ensureDir(approvalDir);
9816
- await ensureDir(path18.join(approvalDir, "wiki"));
9817
- await ensureDir(path18.join(approvalDir, "state"));
10333
+ await ensureDir(path20.join(approvalDir, "wiki"));
10334
+ await ensureDir(path20.join(approvalDir, "state"));
9818
10335
  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");
10336
+ const targetPath = path20.join(approvalDir, "wiki", file.relativePath);
10337
+ await ensureDir(path20.dirname(targetPath));
10338
+ await fs16.writeFile(targetPath, file.content, "utf8");
9822
10339
  }
9823
- await fs15.writeFile(path18.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
10340
+ await fs16.writeFile(path20.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
9824
10341
  await writeApprovalManifest(paths, {
9825
10342
  approvalId,
9826
10343
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -9856,6 +10373,7 @@ async function syncVaultArtifacts(rootDir, input) {
9856
10373
  title: modulePageTitle(manifest),
9857
10374
  kind: "module",
9858
10375
  sourceIds: [manifest.sourceId],
10376
+ sourceClass: manifest.sourceClass,
9859
10377
  projectIds: sourceProjectIds,
9860
10378
  nodeIds: [analysis.code.moduleId, ...analysis.code.symbols.map((symbol) => symbol.id)],
9861
10379
  schemaHash: sourceSchemaHash,
@@ -9868,6 +10386,7 @@ async function syncVaultArtifacts(rootDir, input) {
9868
10386
  title: analysis.title,
9869
10387
  kind: "source",
9870
10388
  sourceIds: [manifest.sourceId],
10389
+ sourceClass: manifest.sourceClass,
9871
10390
  projectIds: sourceProjectIds,
9872
10391
  nodeIds: [
9873
10392
  `source:${manifest.sourceId}`,
@@ -9880,7 +10399,7 @@ async function syncVaultArtifacts(rootDir, input) {
9880
10399
  confidence: 1
9881
10400
  });
9882
10401
  const sourceRecord = await buildManagedGraphPage(
9883
- path18.join(paths.wikiDir, preview.path),
10402
+ path20.join(paths.wikiDir, preview.path),
9884
10403
  {
9885
10404
  managedBy: "system",
9886
10405
  confidence: 1,
@@ -9895,7 +10414,8 @@ async function syncVaultArtifacts(rootDir, input) {
9895
10414
  modulePreview ?? void 0,
9896
10415
  {
9897
10416
  projectIds: sourceProjectIds,
9898
- extraTags: sourceCategoryTags
10417
+ extraTags: sourceCategoryTags,
10418
+ sourceClass: manifest.sourceClass
9899
10419
  }
9900
10420
  )
9901
10421
  );
@@ -9925,7 +10445,7 @@ async function syncVaultArtifacts(rootDir, input) {
9925
10445
  );
9926
10446
  records.push(
9927
10447
  await buildManagedGraphPage(
9928
- path18.join(paths.wikiDir, modulePreview.path),
10448
+ path20.join(paths.wikiDir, modulePreview.path),
9929
10449
  {
9930
10450
  managedBy: "system",
9931
10451
  confidence: 1,
@@ -9957,9 +10477,10 @@ async function syncVaultArtifacts(rootDir, input) {
9957
10477
  const previousEntry = input.previousState?.candidateHistory?.[pageId];
9958
10478
  const promoted = previousEntry?.status === "active" || promoteCandidates && shouldPromoteCandidate(previousEntry, sourceIds);
9959
10479
  const relativePath = promoted ? activeAggregatePath(itemKind, slug) : candidatePagePathFor(itemKind, slug);
10480
+ const aggregateSourceClass2 = aggregateManifestSourceClass(input.manifests, sourceIds);
9960
10481
  const fallbackPaths = [
9961
- path18.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
9962
- path18.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
10482
+ path20.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
10483
+ path20.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
9963
10484
  ];
9964
10485
  const confidence = nodeConfidence(aggregate.sourceAnalyses.length);
9965
10486
  const preview = emptyGraphPage({
@@ -9968,6 +10489,7 @@ async function syncVaultArtifacts(rootDir, input) {
9968
10489
  title: aggregate.name,
9969
10490
  kind: itemKind,
9970
10491
  sourceIds,
10492
+ sourceClass: aggregateSourceClass2,
9971
10493
  projectIds,
9972
10494
  nodeIds: [pageId],
9973
10495
  schemaHash,
@@ -9976,7 +10498,7 @@ async function syncVaultArtifacts(rootDir, input) {
9976
10498
  status: promoted ? "active" : "candidate"
9977
10499
  });
9978
10500
  const pageRecord = await buildManagedGraphPage(
9979
- path18.join(paths.wikiDir, relativePath),
10501
+ path20.join(paths.wikiDir, relativePath),
9980
10502
  {
9981
10503
  status: promoted ? "active" : "candidate",
9982
10504
  managedBy: "system",
@@ -10000,7 +10522,8 @@ async function syncVaultArtifacts(rootDir, input) {
10000
10522
  aggregate.name,
10001
10523
  ...aggregate.descriptions,
10002
10524
  ...aggregate.sourceAnalyses.map((item) => item.summary)
10003
- ])
10525
+ ]),
10526
+ sourceClass: aggregateSourceClass2
10004
10527
  }
10005
10528
  )
10006
10529
  );
@@ -10016,7 +10539,20 @@ async function syncVaultArtifacts(rootDir, input) {
10016
10539
  }
10017
10540
  const compiledPages = records.map((record) => record.page);
10018
10541
  const basePages = [...compiledPages, ...input.outputPages, ...input.insightPages];
10019
- const baseGraph = buildGraph(input.manifests, input.analyses, basePages, input.sourceProjects, input.codeIndex);
10542
+ const structuralGraph = buildGraph(input.manifests, input.analyses, basePages, input.sourceProjects, input.codeIndex);
10543
+ const embeddingEdges = await embeddingSimilarityEdges(rootDir, structuralGraph).catch(() => []);
10544
+ const baseGraph = embeddingEdges.length > 0 ? (() => {
10545
+ const edges = uniqueBy([...structuralGraph.edges, ...embeddingEdges], (edge) => edge.id).sort(
10546
+ (left, right) => left.id.localeCompare(right.id)
10547
+ );
10548
+ const metrics = deriveGraphMetrics(resetGraphNodeMetrics(structuralGraph.nodes), edges);
10549
+ return {
10550
+ ...structuralGraph,
10551
+ nodes: metrics.nodes,
10552
+ edges,
10553
+ communities: metrics.communities
10554
+ };
10555
+ })() : structuralGraph;
10020
10556
  const graphOrientation = await buildGraphOrientationPages(baseGraph, paths, globalSchemaHash, input.previousState?.generatedAt);
10021
10557
  records.push(...graphOrientation.records);
10022
10558
  const allPages = [...basePages, ...graphOrientation.records.map((record) => record.page)];
@@ -10057,7 +10593,7 @@ async function syncVaultArtifacts(rootDir, input) {
10057
10593
  confidence: 1
10058
10594
  }),
10059
10595
  content: await buildManagedContent(
10060
- path18.join(paths.wikiDir, "projects", "index.md"),
10596
+ path20.join(paths.wikiDir, "projects", "index.md"),
10061
10597
  {
10062
10598
  managedBy: "system",
10063
10599
  compiledFrom: indexCompiledFrom(projectIndexRefs)
@@ -10081,7 +10617,7 @@ async function syncVaultArtifacts(rootDir, input) {
10081
10617
  records.push({
10082
10618
  page: projectIndexRef,
10083
10619
  content: await buildManagedContent(
10084
- path18.join(paths.wikiDir, projectIndexRef.path),
10620
+ path20.join(paths.wikiDir, projectIndexRef.path),
10085
10621
  {
10086
10622
  managedBy: "system",
10087
10623
  compiledFrom: indexCompiledFrom(Object.values(sections).flat())
@@ -10109,7 +10645,7 @@ async function syncVaultArtifacts(rootDir, input) {
10109
10645
  confidence: 1
10110
10646
  }),
10111
10647
  content: await buildManagedContent(
10112
- path18.join(paths.wikiDir, "index.md"),
10648
+ path20.join(paths.wikiDir, "index.md"),
10113
10649
  {
10114
10650
  managedBy: "system",
10115
10651
  compiledFrom: indexCompiledFrom(allPages)
@@ -10140,7 +10676,7 @@ async function syncVaultArtifacts(rootDir, input) {
10140
10676
  confidence: 1
10141
10677
  }),
10142
10678
  content: await buildManagedContent(
10143
- path18.join(paths.wikiDir, relativePath),
10679
+ path20.join(paths.wikiDir, relativePath),
10144
10680
  {
10145
10681
  managedBy: "system",
10146
10682
  compiledFrom: indexCompiledFrom(pages)
@@ -10151,12 +10687,12 @@ async function syncVaultArtifacts(rootDir, input) {
10151
10687
  }
10152
10688
  const nextPagePaths = new Set(records.map((record) => record.page.path));
10153
10689
  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));
10690
+ 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
10691
  const obsoletePaths = uniqueStrings3([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
10156
10692
  const changedFiles = [];
10157
10693
  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;
10694
+ const absolutePath = path20.join(paths.wikiDir, record.page.path);
10695
+ const current = await fileExists(absolutePath) ? await fs16.readFile(absolutePath, "utf8") : null;
10160
10696
  if (current !== record.content) {
10161
10697
  changedPages.push(record.page.path);
10162
10698
  changedFiles.push({ relativePath: record.page.path, content: record.content });
@@ -10181,10 +10717,10 @@ async function syncVaultArtifacts(rootDir, input) {
10181
10717
  await writePage(paths.wikiDir, record.page.path, record.content, writeChanges);
10182
10718
  }
10183
10719
  for (const relativePath of obsoletePaths) {
10184
- await fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true });
10720
+ await fs16.rm(path20.join(paths.wikiDir, relativePath), { force: true });
10185
10721
  }
10186
10722
  await writeJsonFile(paths.graphPath, graph);
10187
- await writeJsonFile(path18.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
10723
+ await writeJsonFile(path20.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
10188
10724
  await writeJsonFile(paths.codeIndexPath, input.codeIndex);
10189
10725
  await writeJsonFile(paths.compileStatePath, {
10190
10726
  generatedAt: graph.generatedAt,
@@ -10255,17 +10791,17 @@ async function refreshIndexesAndSearch(rootDir, pages) {
10255
10791
  })
10256
10792
  );
10257
10793
  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"))
10794
+ ensureDir(path20.join(paths.wikiDir, "sources")),
10795
+ ensureDir(path20.join(paths.wikiDir, "code")),
10796
+ ensureDir(path20.join(paths.wikiDir, "concepts")),
10797
+ ensureDir(path20.join(paths.wikiDir, "entities")),
10798
+ ensureDir(path20.join(paths.wikiDir, "outputs")),
10799
+ ensureDir(path20.join(paths.wikiDir, "graph")),
10800
+ ensureDir(path20.join(paths.wikiDir, "graph", "communities")),
10801
+ ensureDir(path20.join(paths.wikiDir, "projects")),
10802
+ ensureDir(path20.join(paths.wikiDir, "candidates"))
10267
10803
  ]);
10268
- const projectsIndexPath = path18.join(paths.wikiDir, "projects", "index.md");
10804
+ const projectsIndexPath = path20.join(paths.wikiDir, "projects", "index.md");
10269
10805
  await writeFileIfChanged(
10270
10806
  projectsIndexPath,
10271
10807
  await buildManagedContent(
@@ -10286,7 +10822,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
10286
10822
  outputs: pages.filter((page) => page.kind === "output" && page.projectIds.includes(project.id)),
10287
10823
  candidates: pages.filter((page) => page.status === "candidate" && page.projectIds.includes(project.id))
10288
10824
  };
10289
- const absolutePath = path18.join(paths.wikiDir, "projects", project.id, "index.md");
10825
+ const absolutePath = path20.join(paths.wikiDir, "projects", project.id, "index.md");
10290
10826
  await writeFileIfChanged(
10291
10827
  absolutePath,
10292
10828
  await buildManagedContent(
@@ -10304,7 +10840,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
10304
10840
  )
10305
10841
  );
10306
10842
  }
10307
- const rootIndexPath = path18.join(paths.wikiDir, "index.md");
10843
+ const rootIndexPath = path20.join(paths.wikiDir, "index.md");
10308
10844
  await writeFileIfChanged(
10309
10845
  rootIndexPath,
10310
10846
  await buildManagedContent(
@@ -10325,7 +10861,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
10325
10861
  ["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
10326
10862
  ["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
10327
10863
  ]) {
10328
- const absolutePath = path18.join(paths.wikiDir, relativePath);
10864
+ const absolutePath = path20.join(paths.wikiDir, relativePath);
10329
10865
  await writeFileIfChanged(
10330
10866
  absolutePath,
10331
10867
  await buildManagedContent(
@@ -10339,23 +10875,23 @@ async function refreshIndexesAndSearch(rootDir, pages) {
10339
10875
  );
10340
10876
  }
10341
10877
  for (const record of graphOrientation.records) {
10342
- await writeFileIfChanged(path18.join(paths.wikiDir, record.page.path), record.content);
10878
+ await writeFileIfChanged(path20.join(paths.wikiDir, record.page.path), record.content);
10343
10879
  }
10344
10880
  if (graphOrientation.report) {
10345
- await writeJsonFile(path18.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
10881
+ await writeJsonFile(path20.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
10346
10882
  }
10347
- const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path18.relative(paths.wikiDir, absolutePath)));
10883
+ const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path20.relative(paths.wikiDir, absolutePath)));
10348
10884
  const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
10349
10885
  "projects/index.md",
10350
10886
  ...configuredProjects.map((project) => `projects/${project.id}/index.md`)
10351
10887
  ]);
10352
10888
  await Promise.all(
10353
- existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true }))
10889
+ existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs16.rm(path20.join(paths.wikiDir, relativePath), { force: true }))
10354
10890
  );
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)));
10891
+ 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
10892
  const allowedGraphPages = /* @__PURE__ */ new Set(["graph/index.md", ...graphOrientation.records.map((record) => record.page.path)]);
10357
10893
  await Promise.all(
10358
- existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs15.rm(path18.join(paths.wikiDir, relativePath), { force: true }))
10894
+ existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs16.rm(path20.join(paths.wikiDir, relativePath), { force: true }))
10359
10895
  );
10360
10896
  await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
10361
10897
  }
@@ -10375,7 +10911,7 @@ async function prepareOutputPageSave(rootDir, input) {
10375
10911
  confidence: 0.74
10376
10912
  }
10377
10913
  });
10378
- const absolutePath = path18.join(paths.wikiDir, output.page.path);
10914
+ const absolutePath = path20.join(paths.wikiDir, output.page.path);
10379
10915
  return {
10380
10916
  page: output.page,
10381
10917
  savedPath: absolutePath,
@@ -10387,15 +10923,15 @@ async function prepareOutputPageSave(rootDir, input) {
10387
10923
  async function persistOutputPage(rootDir, input) {
10388
10924
  const { paths } = await loadVaultConfig(rootDir);
10389
10925
  const prepared = await prepareOutputPageSave(rootDir, input);
10390
- await ensureDir(path18.dirname(prepared.savedPath));
10391
- await fs15.writeFile(prepared.savedPath, prepared.content, "utf8");
10926
+ await ensureDir(path20.dirname(prepared.savedPath));
10927
+ await fs16.writeFile(prepared.savedPath, prepared.content, "utf8");
10392
10928
  for (const assetFile of prepared.assetFiles) {
10393
- const assetPath = path18.join(paths.wikiDir, assetFile.relativePath);
10394
- await ensureDir(path18.dirname(assetPath));
10929
+ const assetPath = path20.join(paths.wikiDir, assetFile.relativePath);
10930
+ await ensureDir(path20.dirname(assetPath));
10395
10931
  if (typeof assetFile.content === "string") {
10396
- await fs15.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
10932
+ await fs16.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
10397
10933
  } else {
10398
- await fs15.writeFile(assetPath, assetFile.content);
10934
+ await fs16.writeFile(assetPath, assetFile.content);
10399
10935
  }
10400
10936
  }
10401
10937
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -10416,7 +10952,7 @@ async function prepareExploreHubSave(rootDir, input) {
10416
10952
  confidence: 0.76
10417
10953
  }
10418
10954
  });
10419
- const absolutePath = path18.join(paths.wikiDir, hub.page.path);
10955
+ const absolutePath = path20.join(paths.wikiDir, hub.page.path);
10420
10956
  return {
10421
10957
  page: hub.page,
10422
10958
  savedPath: absolutePath,
@@ -10428,15 +10964,15 @@ async function prepareExploreHubSave(rootDir, input) {
10428
10964
  async function persistExploreHub(rootDir, input) {
10429
10965
  const { paths } = await loadVaultConfig(rootDir);
10430
10966
  const prepared = await prepareExploreHubSave(rootDir, input);
10431
- await ensureDir(path18.dirname(prepared.savedPath));
10432
- await fs15.writeFile(prepared.savedPath, prepared.content, "utf8");
10967
+ await ensureDir(path20.dirname(prepared.savedPath));
10968
+ await fs16.writeFile(prepared.savedPath, prepared.content, "utf8");
10433
10969
  for (const assetFile of prepared.assetFiles) {
10434
- const assetPath = path18.join(paths.wikiDir, assetFile.relativePath);
10435
- await ensureDir(path18.dirname(assetPath));
10970
+ const assetPath = path20.join(paths.wikiDir, assetFile.relativePath);
10971
+ await ensureDir(path20.dirname(assetPath));
10436
10972
  if (typeof assetFile.content === "string") {
10437
- await fs15.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
10973
+ await fs16.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
10438
10974
  } else {
10439
- await fs15.writeFile(assetPath, assetFile.content);
10975
+ await fs16.writeFile(assetPath, assetFile.content);
10440
10976
  }
10441
10977
  }
10442
10978
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -10453,17 +10989,17 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
10453
10989
  }))
10454
10990
  ]);
10455
10991
  const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
10456
- const approvalDir = path18.join(paths.approvalsDir, approvalId);
10992
+ const approvalDir = path20.join(paths.approvalsDir, approvalId);
10457
10993
  await ensureDir(approvalDir);
10458
- await ensureDir(path18.join(approvalDir, "wiki"));
10459
- await ensureDir(path18.join(approvalDir, "state"));
10994
+ await ensureDir(path20.join(approvalDir, "wiki"));
10995
+ await ensureDir(path20.join(approvalDir, "state"));
10460
10996
  for (const file of changedFiles) {
10461
- const targetPath = path18.join(approvalDir, "wiki", file.relativePath);
10462
- await ensureDir(path18.dirname(targetPath));
10997
+ const targetPath = path20.join(approvalDir, "wiki", file.relativePath);
10998
+ await ensureDir(path20.dirname(targetPath));
10463
10999
  if ("binary" in file && file.binary) {
10464
- await fs15.writeFile(targetPath, Buffer.from(file.content, "base64"));
11000
+ await fs16.writeFile(targetPath, Buffer.from(file.content, "base64"));
10465
11001
  } else {
10466
- await fs15.writeFile(targetPath, file.content, "utf8");
11002
+ await fs16.writeFile(targetPath, file.content, "utf8");
10467
11003
  }
10468
11004
  }
10469
11005
  const nextPages = sortGraphPages([
@@ -10478,7 +11014,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
10478
11014
  sources: previousGraph?.sources ?? [],
10479
11015
  pages: nextPages
10480
11016
  };
10481
- await fs15.writeFile(path18.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
11017
+ await fs16.writeFile(path20.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
10482
11018
  await writeApprovalManifest(paths, {
10483
11019
  approvalId,
10484
11020
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10507,9 +11043,9 @@ async function executeQuery(rootDir, question, format) {
10507
11043
  const searchResults = searchPages(paths.searchDbPath, question, 5);
10508
11044
  const excerpts = await Promise.all(
10509
11045
  searchResults.map(async (result) => {
10510
- const absolutePath = path18.join(paths.wikiDir, result.path);
11046
+ const absolutePath = path20.join(paths.wikiDir, result.path);
10511
11047
  try {
10512
- const content = await fs15.readFile(absolutePath, "utf8");
11048
+ const content = await fs16.readFile(absolutePath, "utf8");
10513
11049
  const parsed = matter9(content);
10514
11050
  return `# ${result.title}
10515
11051
  ${truncate(normalizeWhitespace(parsed.content), 1200)}`;
@@ -10691,7 +11227,7 @@ function sortGraphPages(pages) {
10691
11227
  async function listApprovals(rootDir) {
10692
11228
  const { paths } = await loadVaultConfig(rootDir);
10693
11229
  const manifests = await Promise.all(
10694
- (await fs15.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
11230
+ (await fs16.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
10695
11231
  try {
10696
11232
  return await readApprovalManifest(paths, entry.name);
10697
11233
  } catch {
@@ -10707,8 +11243,8 @@ async function readApproval(rootDir, approvalId) {
10707
11243
  const details = await Promise.all(
10708
11244
  manifest.entries.map(async (entry) => {
10709
11245
  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;
11246
+ const currentContent = currentPath ? await fs16.readFile(path20.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
11247
+ const stagedContent = entry.nextPath ? await fs16.readFile(path20.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
10712
11248
  return {
10713
11249
  ...entry,
10714
11250
  currentContent,
@@ -10736,26 +11272,26 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
10736
11272
  if (!entry.nextPath) {
10737
11273
  throw new Error(`Approval entry ${entry.pageId} is missing a staged path.`);
10738
11274
  }
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");
11275
+ const stagedAbsolutePath = path20.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
11276
+ const stagedContent = await fs16.readFile(stagedAbsolutePath, "utf8");
11277
+ const targetAbsolutePath = path20.join(paths.wikiDir, entry.nextPath);
11278
+ await ensureDir(path20.dirname(targetAbsolutePath));
11279
+ await fs16.writeFile(targetAbsolutePath, stagedContent, "utf8");
10744
11280
  if (entry.changeType === "promote" && entry.previousPath) {
10745
- await fs15.rm(path18.join(paths.wikiDir, entry.previousPath), { force: true });
11281
+ await fs16.rm(path20.join(paths.wikiDir, entry.previousPath), { force: true });
10746
11282
  }
10747
11283
  const nextPage = bundleGraph?.pages.find((page) => page.id === entry.pageId && page.path === entry.nextPath) ?? parseStoredPage(entry.nextPath, stagedContent);
10748
11284
  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 });
11285
+ const outputAssetDir = path20.join(paths.wikiDir, "outputs", "assets", path20.basename(nextPage.path, ".md"));
11286
+ await fs16.rm(outputAssetDir, { recursive: true, force: true });
10751
11287
  for (const asset of nextPage.outputAssets) {
10752
- const stagedAssetPath = path18.join(paths.approvalsDir, approvalId, "wiki", asset.path);
11288
+ const stagedAssetPath = path20.join(paths.approvalsDir, approvalId, "wiki", asset.path);
10753
11289
  if (!await fileExists(stagedAssetPath)) {
10754
11290
  continue;
10755
11291
  }
10756
- const targetAssetPath = path18.join(paths.wikiDir, asset.path);
10757
- await ensureDir(path18.dirname(targetAssetPath));
10758
- await fs15.copyFile(stagedAssetPath, targetAssetPath);
11292
+ const targetAssetPath = path20.join(paths.wikiDir, asset.path);
11293
+ await ensureDir(path20.dirname(targetAssetPath));
11294
+ await fs16.copyFile(stagedAssetPath, targetAssetPath);
10759
11295
  }
10760
11296
  }
10761
11297
  nextPages = nextPages.filter(
@@ -10766,10 +11302,10 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
10766
11302
  } else {
10767
11303
  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
11304
  if (entry.previousPath) {
10769
- await fs15.rm(path18.join(paths.wikiDir, entry.previousPath), { force: true });
11305
+ await fs16.rm(path20.join(paths.wikiDir, entry.previousPath), { force: true });
10770
11306
  }
10771
11307
  if (deletedPage?.kind === "output") {
10772
- await fs15.rm(path18.join(paths.wikiDir, "outputs", "assets", path18.basename(deletedPage.path, ".md")), {
11308
+ await fs16.rm(path20.join(paths.wikiDir, "outputs", "assets", path20.basename(deletedPage.path, ".md")), {
10773
11309
  recursive: true,
10774
11310
  force: true
10775
11311
  });
@@ -10860,7 +11396,7 @@ async function promoteCandidate(rootDir, target) {
10860
11396
  const { paths } = await loadVaultConfig(rootDir);
10861
11397
  const graph = await readJsonFile(paths.graphPath);
10862
11398
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
10863
- const raw = await fs15.readFile(path18.join(paths.wikiDir, candidate.path), "utf8");
11399
+ const raw = await fs16.readFile(path20.join(paths.wikiDir, candidate.path), "utf8");
10864
11400
  const parsed = matter9(raw);
10865
11401
  const nextUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
10866
11402
  const nextContent = matter9.stringify(parsed.content, {
@@ -10872,10 +11408,10 @@ async function promoteCandidate(rootDir, target) {
10872
11408
  )
10873
11409
  });
10874
11410
  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 });
11411
+ const nextAbsolutePath = path20.join(paths.wikiDir, nextPath);
11412
+ await ensureDir(path20.dirname(nextAbsolutePath));
11413
+ await fs16.writeFile(nextAbsolutePath, nextContent, "utf8");
11414
+ await fs16.rm(path20.join(paths.wikiDir, candidate.path), { force: true });
10879
11415
  const nextPage = parseStoredPage(nextPath, nextContent, { createdAt: candidate.createdAt, updatedAt: nextUpdatedAt });
10880
11416
  const nextPages = sortGraphPages(
10881
11417
  (graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path).concat(nextPage)
@@ -10920,7 +11456,7 @@ async function archiveCandidate(rootDir, target) {
10920
11456
  const { paths } = await loadVaultConfig(rootDir);
10921
11457
  const graph = await readJsonFile(paths.graphPath);
10922
11458
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
10923
- await fs15.rm(path18.join(paths.wikiDir, candidate.path), { force: true });
11459
+ await fs16.rm(path20.join(paths.wikiDir, candidate.path), { force: true });
10924
11460
  const nextPages = sortGraphPages((graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path));
10925
11461
  const nextGraph = {
10926
11462
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -10959,18 +11495,18 @@ async function archiveCandidate(rootDir, target) {
10959
11495
  }
10960
11496
  async function ensureObsidianWorkspace(rootDir) {
10961
11497
  const { config } = await loadVaultConfig(rootDir);
10962
- const obsidianDir = path18.join(rootDir, ".obsidian");
11498
+ const obsidianDir = path20.join(rootDir, ".obsidian");
10963
11499
  const projectIds = projectEntries(config).map((project) => project.id);
10964
11500
  await ensureDir(obsidianDir);
10965
11501
  await Promise.all([
10966
- writeJsonFile(path18.join(obsidianDir, "app.json"), {
11502
+ writeJsonFile(path20.join(obsidianDir, "app.json"), {
10967
11503
  alwaysUpdateLinks: true,
10968
11504
  newFileLocation: "folder",
10969
11505
  newFileFolderPath: "wiki/insights",
10970
11506
  useMarkdownLinks: false,
10971
11507
  attachmentFolderPath: "raw/assets"
10972
11508
  }),
10973
- writeJsonFile(path18.join(obsidianDir, "core-plugins.json"), [
11509
+ writeJsonFile(path20.join(obsidianDir, "core-plugins.json"), [
10974
11510
  "file-explorer",
10975
11511
  "global-search",
10976
11512
  "switcher",
@@ -10980,7 +11516,7 @@ async function ensureObsidianWorkspace(rootDir) {
10980
11516
  "tag-pane",
10981
11517
  "page-preview"
10982
11518
  ]),
10983
- writeJsonFile(path18.join(obsidianDir, "graph.json"), {
11519
+ writeJsonFile(path20.join(obsidianDir, "graph.json"), {
10984
11520
  "collapse-filter": false,
10985
11521
  search: "",
10986
11522
  showTags: true,
@@ -10992,7 +11528,7 @@ async function ensureObsidianWorkspace(rootDir) {
10992
11528
  })),
10993
11529
  localJumps: false
10994
11530
  }),
10995
- writeJsonFile(path18.join(obsidianDir, "workspace.json"), {
11531
+ writeJsonFile(path20.join(obsidianDir, "workspace.json"), {
10996
11532
  active: "root",
10997
11533
  lastOpenFiles: ["wiki/index.md", "wiki/projects/index.md", "wiki/candidates/index.md", "wiki/insights/index.md"],
10998
11534
  left: {
@@ -11007,7 +11543,7 @@ async function ensureObsidianWorkspace(rootDir) {
11007
11543
  async function initVault(rootDir, options = {}) {
11008
11544
  const { paths } = await initWorkspace(rootDir);
11009
11545
  await installConfiguredAgents(rootDir);
11010
- const insightsIndexPath = path18.join(paths.wikiDir, "insights", "index.md");
11546
+ const insightsIndexPath = path20.join(paths.wikiDir, "insights", "index.md");
11011
11547
  const now = (/* @__PURE__ */ new Date()).toISOString();
11012
11548
  await writeFileIfChanged(
11013
11549
  insightsIndexPath,
@@ -11043,7 +11579,7 @@ async function initVault(rootDir, options = {}) {
11043
11579
  )
11044
11580
  );
11045
11581
  await writeFileIfChanged(
11046
- path18.join(paths.wikiDir, "projects", "index.md"),
11582
+ path20.join(paths.wikiDir, "projects", "index.md"),
11047
11583
  matter9.stringify(["# Projects", "", "- Run `swarmvault compile` to build project rollups.", ""].join("\n"), {
11048
11584
  page_id: "projects:index",
11049
11585
  kind: "index",
@@ -11065,7 +11601,7 @@ async function initVault(rootDir, options = {}) {
11065
11601
  })
11066
11602
  );
11067
11603
  await writeFileIfChanged(
11068
- path18.join(paths.wikiDir, "candidates", "index.md"),
11604
+ path20.join(paths.wikiDir, "candidates", "index.md"),
11069
11605
  matter9.stringify(["# Candidates", "", "- Run `swarmvault compile` to stage candidate pages.", ""].join("\n"), {
11070
11606
  page_id: "candidates:index",
11071
11607
  kind: "index",
@@ -11201,7 +11737,7 @@ async function compileVault(rootDir, options = {}) {
11201
11737
  ),
11202
11738
  Promise.all(
11203
11739
  clean.map(async (manifest) => {
11204
- const cached = await readJsonFile(path18.join(paths.analysesDir, `${manifest.sourceId}.json`));
11740
+ const cached = await readJsonFile(path20.join(paths.analysesDir, `${manifest.sourceId}.json`));
11205
11741
  if (cached) {
11206
11742
  return cached;
11207
11743
  }
@@ -11225,22 +11761,22 @@ async function compileVault(rootDir, options = {}) {
11225
11761
  }
11226
11762
  const enriched = enrichResolvedCodeImports(manifest, analysis, codeIndex);
11227
11763
  if (analysisSignature(enriched) !== analysisSignature(analysis)) {
11228
- await writeJsonFile(path18.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
11764
+ await writeJsonFile(path20.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
11229
11765
  }
11230
11766
  return enriched;
11231
11767
  })
11232
11768
  );
11233
11769
  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"))
11770
+ ensureDir(path20.join(paths.wikiDir, "sources")),
11771
+ ensureDir(path20.join(paths.wikiDir, "code")),
11772
+ ensureDir(path20.join(paths.wikiDir, "concepts")),
11773
+ ensureDir(path20.join(paths.wikiDir, "entities")),
11774
+ ensureDir(path20.join(paths.wikiDir, "outputs")),
11775
+ ensureDir(path20.join(paths.wikiDir, "projects")),
11776
+ ensureDir(path20.join(paths.wikiDir, "insights")),
11777
+ ensureDir(path20.join(paths.wikiDir, "candidates")),
11778
+ ensureDir(path20.join(paths.wikiDir, "candidates", "concepts")),
11779
+ ensureDir(path20.join(paths.wikiDir, "candidates", "entities"))
11244
11780
  ]);
11245
11781
  const sync = await syncVaultArtifacts(rootDir, {
11246
11782
  schemas,
@@ -11387,7 +11923,7 @@ async function queryVault(rootDir, options) {
11387
11923
  assetFiles: staged.assetFiles
11388
11924
  }
11389
11925
  ]);
11390
- stagedPath = path18.join(approval.approvalDir, "wiki", staged.page.path);
11926
+ stagedPath = path20.join(approval.approvalDir, "wiki", staged.page.path);
11391
11927
  savedPageId = staged.page.id;
11392
11928
  approvalId = approval.approvalId;
11393
11929
  approvalDir = approval.approvalDir;
@@ -11643,9 +12179,9 @@ ${orchestrationNotes.join("\n")}
11643
12179
  approvalId = approval.approvalId;
11644
12180
  approvalDir = approval.approvalDir;
11645
12181
  stepResults.forEach((result, index) => {
11646
- result.stagedPath = path18.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
12182
+ result.stagedPath = path20.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
11647
12183
  });
11648
- stagedHubPath = path18.join(approval.approvalDir, "wiki", hubPage.path);
12184
+ stagedHubPath = path20.join(approval.approvalDir, "wiki", hubPage.path);
11649
12185
  } else {
11650
12186
  await refreshVaultAfterOutputSave(rootDir);
11651
12187
  }
@@ -11706,11 +12242,18 @@ async function ensureCompiledGraph(rootDir) {
11706
12242
  }
11707
12243
  return graph;
11708
12244
  }
11709
- async function queryGraphVault(rootDir, question, options = {}) {
12245
+ async function runResolvedGraphQuery(rootDir, graph, question, options = {}) {
11710
12246
  const { paths } = await loadVaultConfig(rootDir);
11711
- const graph = await ensureCompiledGraph(rootDir);
11712
12247
  const searchResults = searchPages(paths.searchDbPath, question, { limit: Math.max(5, options.budget ?? 10) });
11713
- return queryGraph(graph, question, searchResults, options);
12248
+ const semanticMatches = await semanticGraphMatches(rootDir, graph, question, Math.max(8, options.budget ?? 12)).catch(() => []);
12249
+ return queryGraph(graph, question, searchResults, {
12250
+ ...options,
12251
+ semanticMatches
12252
+ });
12253
+ }
12254
+ async function queryGraphVault(rootDir, question, options = {}) {
12255
+ const graph = await ensureCompiledGraph(rootDir);
12256
+ return runResolvedGraphQuery(rootDir, graph, question, options);
11714
12257
  }
11715
12258
  async function benchmarkVault(rootDir, options = {}) {
11716
12259
  const { config, paths } = await loadVaultConfig(rootDir);
@@ -11725,30 +12268,31 @@ async function benchmarkVault(rootDir, options = {}) {
11725
12268
  }
11726
12269
  }
11727
12270
  for (const page of graph.pages) {
11728
- const absolutePath = path18.join(paths.wikiDir, page.path);
12271
+ const absolutePath = path20.join(paths.wikiDir, page.path);
11729
12272
  if (!await fileExists(absolutePath)) {
11730
12273
  continue;
11731
12274
  }
11732
- const parsed = matter9(await fs15.readFile(absolutePath, "utf8"));
12275
+ const parsed = matter9(await fs16.readFile(absolutePath, "utf8"));
11733
12276
  pageContentsById.set(page.id, parsed.content);
11734
12277
  }
11735
12278
  const configuredQuestions = (config.benchmark?.questions ?? []).map((question) => normalizeWhitespace(question)).filter(Boolean);
11736
12279
  const maxQuestions = Math.max(1, options.maxQuestions ?? config.benchmark?.maxQuestions ?? 3);
11737
12280
  const questions = (options.questions ?? []).map((question) => normalizeWhitespace(question)).filter(Boolean);
11738
12281
  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
- });
12282
+ const perQuestion = await Promise.all(
12283
+ sampleQuestions.map(async (question) => {
12284
+ const result = await runResolvedGraphQuery(rootDir, graph, question, { budget: 12 });
12285
+ const metrics = benchmarkQueryTokens(graph, result, pageContentsById);
12286
+ return {
12287
+ question,
12288
+ queryTokens: metrics.queryTokens,
12289
+ reduction: metrics.reduction,
12290
+ visitedNodeIds: result.visitedNodeIds,
12291
+ visitedEdgeIds: result.visitedEdgeIds,
12292
+ pageIds: result.pageIds
12293
+ };
12294
+ })
12295
+ );
11752
12296
  const artifact = buildBenchmarkArtifact({
11753
12297
  graph,
11754
12298
  corpusWords,
@@ -11773,7 +12317,7 @@ async function listGraphHyperedges(rootDir, target, limit = 25) {
11773
12317
  }
11774
12318
  async function readGraphReport(rootDir) {
11775
12319
  const { paths } = await loadVaultConfig(rootDir);
11776
- return readJsonFile(path18.join(paths.wikiDir, "graph", "report.json"));
12320
+ return readJsonFile(path20.join(paths.wikiDir, "graph", "report.json"));
11777
12321
  }
11778
12322
  async function listGodNodes(rootDir, limit = 10) {
11779
12323
  const graph = await ensureCompiledGraph(rootDir);
@@ -11786,15 +12330,15 @@ async function listPages(rootDir) {
11786
12330
  }
11787
12331
  async function readPage(rootDir, relativePath) {
11788
12332
  const { paths } = await loadVaultConfig(rootDir);
11789
- const absolutePath = path18.resolve(paths.wikiDir, relativePath);
12333
+ const absolutePath = path20.resolve(paths.wikiDir, relativePath);
11790
12334
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
11791
12335
  return null;
11792
12336
  }
11793
- const raw = await fs15.readFile(absolutePath, "utf8");
12337
+ const raw = await fs16.readFile(absolutePath, "utf8");
11794
12338
  const parsed = matter9(raw);
11795
12339
  return {
11796
12340
  path: relativePath,
11797
- title: typeof parsed.data.title === "string" ? parsed.data.title : path18.basename(relativePath, path18.extname(relativePath)),
12341
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path20.basename(relativePath, path20.extname(relativePath)),
11798
12342
  frontmatter: parsed.data,
11799
12343
  content: parsed.content
11800
12344
  };
@@ -11830,7 +12374,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
11830
12374
  severity: "warning",
11831
12375
  code: "stale_page",
11832
12376
  message: `Page ${page.title} is stale because the vault schema changed.`,
11833
- pagePath: path18.join(paths.wikiDir, page.path),
12377
+ pagePath: path20.join(paths.wikiDir, page.path),
11834
12378
  relatedPageIds: [page.id]
11835
12379
  });
11836
12380
  }
@@ -11841,7 +12385,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
11841
12385
  severity: "warning",
11842
12386
  code: "stale_page",
11843
12387
  message: `Page ${page.title} is stale because source ${sourceId} changed.`,
11844
- pagePath: path18.join(paths.wikiDir, page.path),
12388
+ pagePath: path20.join(paths.wikiDir, page.path),
11845
12389
  relatedSourceIds: [sourceId],
11846
12390
  relatedPageIds: [page.id]
11847
12391
  });
@@ -11852,13 +12396,13 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
11852
12396
  severity: "info",
11853
12397
  code: "orphan_page",
11854
12398
  message: `Page ${page.title} has no backlinks.`,
11855
- pagePath: path18.join(paths.wikiDir, page.path),
12399
+ pagePath: path20.join(paths.wikiDir, page.path),
11856
12400
  relatedPageIds: [page.id]
11857
12401
  });
11858
12402
  }
11859
- const absolutePath = path18.join(paths.wikiDir, page.path);
12403
+ const absolutePath = path20.join(paths.wikiDir, page.path);
11860
12404
  if (await fileExists(absolutePath)) {
11861
- const content = await fs15.readFile(absolutePath, "utf8");
12405
+ const content = await fs16.readFile(absolutePath, "utf8");
11862
12406
  if (content.includes("## Claims")) {
11863
12407
  const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
11864
12408
  if (uncited.length) {
@@ -11938,7 +12482,7 @@ async function bootstrapDemo(rootDir, input) {
11938
12482
  }
11939
12483
 
11940
12484
  // src/mcp.ts
11941
- var SERVER_VERSION = "0.1.25";
12485
+ var SERVER_VERSION = "0.1.27";
11942
12486
  async function createMcpServer(rootDir) {
11943
12487
  const server = new McpServer({
11944
12488
  name: "swarmvault",
@@ -12209,7 +12753,7 @@ async function createMcpServer(rootDir) {
12209
12753
  },
12210
12754
  async () => {
12211
12755
  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();
12756
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path21.relative(paths.sessionsDir, filePath))).sort();
12213
12757
  return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
12214
12758
  }
12215
12759
  );
@@ -12242,8 +12786,8 @@ async function createMcpServer(rootDir) {
12242
12786
  return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
12243
12787
  }
12244
12788
  const { paths } = await loadVaultConfig(rootDir);
12245
- const absolutePath = path19.resolve(paths.wikiDir, relativePath);
12246
- return asTextResource(`swarmvault://pages/${encodedPath}`, await fs16.readFile(absolutePath, "utf8"));
12789
+ const absolutePath = path21.resolve(paths.wikiDir, relativePath);
12790
+ return asTextResource(`swarmvault://pages/${encodedPath}`, await fs17.readFile(absolutePath, "utf8"));
12247
12791
  }
12248
12792
  );
12249
12793
  server.registerResource(
@@ -12251,11 +12795,11 @@ async function createMcpServer(rootDir) {
12251
12795
  new ResourceTemplate("swarmvault://sessions/{path}", {
12252
12796
  list: async () => {
12253
12797
  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();
12798
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path21.relative(paths.sessionsDir, filePath))).sort();
12255
12799
  return {
12256
12800
  resources: files.map((relativePath) => ({
12257
12801
  uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
12258
- name: path19.basename(relativePath, ".md"),
12802
+ name: path21.basename(relativePath, ".md"),
12259
12803
  title: relativePath,
12260
12804
  description: "SwarmVault session artifact",
12261
12805
  mimeType: "text/markdown"
@@ -12272,11 +12816,11 @@ async function createMcpServer(rootDir) {
12272
12816
  const { paths } = await loadVaultConfig(rootDir);
12273
12817
  const encodedPath = typeof variables.path === "string" ? variables.path : "";
12274
12818
  const relativePath = decodeURIComponent(encodedPath);
12275
- const absolutePath = path19.resolve(paths.sessionsDir, relativePath);
12819
+ const absolutePath = path21.resolve(paths.sessionsDir, relativePath);
12276
12820
  if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
12277
12821
  return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
12278
12822
  }
12279
- return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs16.readFile(absolutePath, "utf8"));
12823
+ return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs17.readFile(absolutePath, "utf8"));
12280
12824
  }
12281
12825
  );
12282
12826
  return server;
@@ -12324,13 +12868,13 @@ function asTextResource(uri, text) {
12324
12868
  }
12325
12869
 
12326
12870
  // src/schedule.ts
12327
- import fs17 from "fs/promises";
12328
- import path20 from "path";
12871
+ import fs18 from "fs/promises";
12872
+ import path22 from "path";
12329
12873
  function scheduleStatePath(schedulesDir, jobId) {
12330
- return path20.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
12874
+ return path22.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
12331
12875
  }
12332
12876
  function scheduleLockPath(schedulesDir, jobId) {
12333
- return path20.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
12877
+ return path22.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
12334
12878
  }
12335
12879
  function parseEveryDuration(value) {
12336
12880
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -12433,13 +12977,13 @@ async function acquireJobLease(rootDir, jobId) {
12433
12977
  const { paths } = await loadVaultConfig(rootDir);
12434
12978
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
12435
12979
  await ensureDir(paths.schedulesDir);
12436
- const handle = await fs17.open(leasePath, "wx");
12980
+ const handle = await fs18.open(leasePath, "wx");
12437
12981
  await handle.writeFile(`${process.pid}
12438
12982
  ${(/* @__PURE__ */ new Date()).toISOString()}
12439
12983
  `);
12440
12984
  await handle.close();
12441
12985
  return async () => {
12442
- await fs17.rm(leasePath, { force: true });
12986
+ await fs18.rm(leasePath, { force: true });
12443
12987
  };
12444
12988
  }
12445
12989
  async function listSchedules(rootDir) {
@@ -12587,31 +13131,31 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
12587
13131
 
12588
13132
  // src/viewer.ts
12589
13133
  import { execFile } from "child_process";
12590
- import fs18 from "fs/promises";
13134
+ import fs19 from "fs/promises";
12591
13135
  import http from "http";
12592
- import path22 from "path";
13136
+ import path24 from "path";
12593
13137
  import { promisify } from "util";
12594
13138
  import matter10 from "gray-matter";
12595
13139
  import mime2 from "mime-types";
12596
13140
 
12597
13141
  // src/watch.ts
12598
- import path21 from "path";
13142
+ import path23 from "path";
12599
13143
  import process2 from "process";
12600
13144
  import chokidar from "chokidar";
12601
13145
  var MAX_BACKOFF_MS = 3e4;
12602
13146
  var BACKOFF_THRESHOLD = 3;
12603
13147
  var CRITICAL_THRESHOLD = 10;
12604
- var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", "coverage", ".venv", "vendor", "target"]);
13148
+ var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
12605
13149
  function withinRoot2(rootPath, targetPath) {
12606
- const relative = path21.relative(rootPath, targetPath);
12607
- return relative === "" || !relative.startsWith("..") && !path21.isAbsolute(relative);
13150
+ const relative = path23.relative(rootPath, targetPath);
13151
+ return relative === "" || !relative.startsWith("..") && !path23.isAbsolute(relative);
12608
13152
  }
12609
13153
  function hasIgnoredRepoSegment(baseDir, targetPath) {
12610
- const relativePath = path21.relative(baseDir, targetPath);
13154
+ const relativePath = path23.relative(baseDir, targetPath);
12611
13155
  if (!relativePath || relativePath.startsWith("..")) {
12612
13156
  return false;
12613
13157
  }
12614
- return relativePath.split(path21.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
13158
+ return relativePath.split(path23.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
12615
13159
  }
12616
13160
  function workspaceIgnoreRoots(rootDir, paths) {
12617
13161
  return [
@@ -12620,16 +13164,16 @@ function workspaceIgnoreRoots(rootDir, paths) {
12620
13164
  paths.stateDir,
12621
13165
  paths.agentDir,
12622
13166
  paths.inboxDir,
12623
- path21.join(rootDir, ".claude"),
12624
- path21.join(rootDir, ".cursor"),
12625
- path21.join(rootDir, ".obsidian")
12626
- ].map((candidate) => path21.resolve(candidate));
13167
+ path23.join(rootDir, ".claude"),
13168
+ path23.join(rootDir, ".cursor"),
13169
+ path23.join(rootDir, ".obsidian")
13170
+ ].map((candidate) => path23.resolve(candidate));
12627
13171
  }
12628
13172
  async function resolveWatchTargets(rootDir, paths, options) {
12629
- const targets = /* @__PURE__ */ new Set([path21.resolve(paths.inboxDir)]);
13173
+ const targets = /* @__PURE__ */ new Set([path23.resolve(paths.inboxDir)]);
12630
13174
  if (options.repo) {
12631
13175
  for (const repoRoot of await listTrackedRepoRoots(rootDir)) {
12632
- targets.add(path21.resolve(repoRoot));
13176
+ targets.add(path23.resolve(repoRoot));
12633
13177
  }
12634
13178
  }
12635
13179
  return [...targets].sort((left, right) => left.localeCompare(right));
@@ -12759,7 +13303,7 @@ async function watchVault(rootDir, options = {}) {
12759
13303
  const { paths } = await initWorkspace(rootDir);
12760
13304
  const baseDebounceMs = options.debounceMs ?? 900;
12761
13305
  const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
12762
- const inboxWatchRoot = path21.resolve(paths.inboxDir);
13306
+ const inboxWatchRoot = path23.resolve(paths.inboxDir);
12763
13307
  let watchTargets = await resolveWatchTargets(rootDir, paths, options);
12764
13308
  let timer;
12765
13309
  let running = false;
@@ -12773,7 +13317,7 @@ async function watchVault(rootDir, options = {}) {
12773
13317
  usePolling: true,
12774
13318
  interval: 100,
12775
13319
  ignored: (targetPath) => {
12776
- const absolutePath = path21.resolve(targetPath);
13320
+ const absolutePath = path23.resolve(targetPath);
12777
13321
  const primaryTarget = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
12778
13322
  if (!primaryTarget) {
12779
13323
  return false;
@@ -12945,8 +13489,8 @@ async function watchVault(rootDir, options = {}) {
12945
13489
  }
12946
13490
  };
12947
13491
  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) || ".";
13492
+ const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, path23.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
13493
+ return path23.relative(baseDir, targetPath) || ".";
12950
13494
  };
12951
13495
  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
13496
  await new Promise((resolve, reject) => {
@@ -12987,15 +13531,15 @@ async function getWatchStatus(rootDir) {
12987
13531
  var execFileAsync = promisify(execFile);
12988
13532
  async function readViewerPage(rootDir, relativePath) {
12989
13533
  const { paths } = await loadVaultConfig(rootDir);
12990
- const absolutePath = path22.resolve(paths.wikiDir, relativePath);
13534
+ const absolutePath = path24.resolve(paths.wikiDir, relativePath);
12991
13535
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
12992
13536
  return null;
12993
13537
  }
12994
- const raw = await fs18.readFile(absolutePath, "utf8");
13538
+ const raw = await fs19.readFile(absolutePath, "utf8");
12995
13539
  const parsed = matter10(raw);
12996
13540
  return {
12997
13541
  path: relativePath,
12998
- title: typeof parsed.data.title === "string" ? parsed.data.title : path22.basename(relativePath, path22.extname(relativePath)),
13542
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path24.basename(relativePath, path24.extname(relativePath)),
12999
13543
  frontmatter: parsed.data,
13000
13544
  content: parsed.content,
13001
13545
  assets: normalizeOutputAssets(parsed.data.output_assets)
@@ -13003,12 +13547,12 @@ async function readViewerPage(rootDir, relativePath) {
13003
13547
  }
13004
13548
  async function readViewerAsset(rootDir, relativePath) {
13005
13549
  const { paths } = await loadVaultConfig(rootDir);
13006
- const absolutePath = path22.resolve(paths.wikiDir, relativePath);
13550
+ const absolutePath = path24.resolve(paths.wikiDir, relativePath);
13007
13551
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
13008
13552
  return null;
13009
13553
  }
13010
13554
  return {
13011
- buffer: await fs18.readFile(absolutePath),
13555
+ buffer: await fs19.readFile(absolutePath),
13012
13556
  mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
13013
13557
  };
13014
13558
  }
@@ -13031,12 +13575,12 @@ async function readJsonBody(request) {
13031
13575
  return JSON.parse(raw);
13032
13576
  }
13033
13577
  async function ensureViewerDist(viewerDistDir) {
13034
- const indexPath = path22.join(viewerDistDir, "index.html");
13578
+ const indexPath = path24.join(viewerDistDir, "index.html");
13035
13579
  if (await fileExists(indexPath)) {
13036
13580
  return;
13037
13581
  }
13038
- const viewerProjectDir = path22.dirname(viewerDistDir);
13039
- if (await fileExists(path22.join(viewerProjectDir, "package.json"))) {
13582
+ const viewerProjectDir = path24.dirname(viewerDistDir);
13583
+ if (await fileExists(path24.join(viewerProjectDir, "package.json"))) {
13040
13584
  await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
13041
13585
  }
13042
13586
  }
@@ -13053,7 +13597,7 @@ async function startGraphServer(rootDir, port) {
13053
13597
  return;
13054
13598
  }
13055
13599
  response.writeHead(200, { "content-type": "application/json" });
13056
- response.end(await fs18.readFile(paths.graphPath, "utf8"));
13600
+ response.end(await fs19.readFile(paths.graphPath, "utf8"));
13057
13601
  return;
13058
13602
  }
13059
13603
  if (url.pathname === "/api/graph/query") {
@@ -13096,26 +13640,28 @@ async function startGraphServer(rootDir, port) {
13096
13640
  const status = url.searchParams.get("status") ?? "all";
13097
13641
  const project = url.searchParams.get("project") ?? "all";
13098
13642
  const sourceType = url.searchParams.get("sourceType") ?? "all";
13643
+ const sourceClass = url.searchParams.get("sourceClass") ?? "all";
13099
13644
  const results = searchPages(paths.searchDbPath, query, {
13100
13645
  limit: Number.isFinite(limit) ? limit : 10,
13101
13646
  kind,
13102
13647
  status,
13103
13648
  project,
13104
- sourceType
13649
+ sourceType,
13650
+ sourceClass
13105
13651
  });
13106
13652
  response.writeHead(200, { "content-type": "application/json" });
13107
13653
  response.end(JSON.stringify(results));
13108
13654
  return;
13109
13655
  }
13110
13656
  if (url.pathname === "/api/graph-report") {
13111
- const reportPath = path22.join(paths.wikiDir, "graph", "report.json");
13657
+ const reportPath = path24.join(paths.wikiDir, "graph", "report.json");
13112
13658
  if (!await fileExists(reportPath)) {
13113
13659
  response.writeHead(404, { "content-type": "application/json" });
13114
13660
  response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
13115
13661
  return;
13116
13662
  }
13117
13663
  response.writeHead(200, { "content-type": "application/json" });
13118
- response.end(await fs18.readFile(reportPath, "utf8"));
13664
+ response.end(await fs19.readFile(reportPath, "utf8"));
13119
13665
  return;
13120
13666
  }
13121
13667
  if (url.pathname === "/api/watch-status") {
@@ -13198,8 +13744,8 @@ async function startGraphServer(rootDir, port) {
13198
13744
  return;
13199
13745
  }
13200
13746
  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");
13747
+ const target = path24.join(paths.viewerDistDir, relativePath);
13748
+ const fallback = path24.join(paths.viewerDistDir, "index.html");
13203
13749
  const filePath = await fileExists(target) ? target : fallback;
13204
13750
  if (!await fileExists(filePath)) {
13205
13751
  response.writeHead(503, { "content-type": "text/plain" });
@@ -13207,7 +13753,7 @@ async function startGraphServer(rootDir, port) {
13207
13753
  return;
13208
13754
  }
13209
13755
  response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
13210
- response.end(await fs18.readFile(filePath));
13756
+ response.end(await fs19.readFile(filePath));
13211
13757
  });
13212
13758
  await new Promise((resolve) => {
13213
13759
  server.listen(effectivePort, resolve);
@@ -13234,7 +13780,7 @@ async function exportGraphHtml(rootDir, outputPath) {
13234
13780
  throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
13235
13781
  }
13236
13782
  await ensureViewerDist(paths.viewerDistDir);
13237
- const indexPath = path22.join(paths.viewerDistDir, "index.html");
13783
+ const indexPath = path24.join(paths.viewerDistDir, "index.html");
13238
13784
  if (!await fileExists(indexPath)) {
13239
13785
  throw new Error("Viewer build not found. Run `pnpm build` first.");
13240
13786
  }
@@ -13248,6 +13794,7 @@ async function exportGraphHtml(rootDir, outputPath) {
13248
13794
  kind: page.kind,
13249
13795
  status: page.status,
13250
13796
  sourceType: page.sourceType,
13797
+ sourceClass: page.sourceClass,
13251
13798
  projectIds: page.projectIds,
13252
13799
  content: loaded.content,
13253
13800
  assets: await Promise.all(
@@ -13259,17 +13806,17 @@ async function exportGraphHtml(rootDir, outputPath) {
13259
13806
  } : null;
13260
13807
  })
13261
13808
  );
13262
- const rawHtml = await fs18.readFile(indexPath, "utf8");
13809
+ const rawHtml = await fs19.readFile(indexPath, "utf8");
13263
13810
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
13264
13811
  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;
13812
+ const scriptPath = scriptMatch?.[1] ? path24.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
13813
+ const stylePath = styleMatch?.[1] ? path24.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
13267
13814
  if (!scriptPath || !await fileExists(scriptPath)) {
13268
13815
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
13269
13816
  }
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"));
13817
+ const script = await fs19.readFile(scriptPath, "utf8");
13818
+ const style = stylePath && await fileExists(stylePath) ? await fs19.readFile(stylePath, "utf8") : "";
13819
+ const report = await readJsonFile(path24.join(paths.wikiDir, "graph", "report.json"));
13273
13820
  const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean), report }, null, 2).replace(/</g, "\\u003c");
13274
13821
  const html = [
13275
13822
  "<!doctype html>",
@@ -13288,9 +13835,9 @@ async function exportGraphHtml(rootDir, outputPath) {
13288
13835
  "</html>",
13289
13836
  ""
13290
13837
  ].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);
13838
+ await fs19.mkdir(path24.dirname(outputPath), { recursive: true });
13839
+ await fs19.writeFile(outputPath, html, "utf8");
13840
+ return path24.resolve(outputPath);
13294
13841
  }
13295
13842
  export {
13296
13843
  acceptApproval,