@swarmvaultai/engine 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  uniqueBy,
22
22
  writeFileIfChanged,
23
23
  writeJsonFile
24
- } from "./chunk-CWLDFLH2.js";
24
+ } from "./chunk-B3FC4J3P.js";
25
25
 
26
26
  // src/agents.ts
27
27
  import crypto from "crypto";
@@ -12885,7 +12885,7 @@ async function resolveImageGenerationProvider(rootDir) {
12885
12885
  if (!providerConfig) {
12886
12886
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
12887
12887
  }
12888
- const { createProvider: createProvider2 } = await import("./registry-2REAPKPO.js");
12888
+ const { createProvider: createProvider2 } = await import("./registry-KVJAO5DF.js");
12889
12889
  return createProvider2(preferredProviderId, providerConfig, rootDir);
12890
12890
  }
12891
12891
  async function generateOutputArtifacts(rootDir, input) {
@@ -13263,6 +13263,8 @@ function approvalSummary(manifest) {
13263
13263
  return {
13264
13264
  approvalId: manifest.approvalId,
13265
13265
  createdAt: manifest.createdAt,
13266
+ bundleType: manifest.bundleType,
13267
+ title: manifest.title,
13266
13268
  entryCount: manifest.entries.length,
13267
13269
  pendingCount: manifest.entries.filter((entry) => entry.status === "pending").length,
13268
13270
  acceptedCount: manifest.entries.filter((entry) => entry.status === "accepted").length,
@@ -13374,6 +13376,9 @@ async function buildDashboardRecords(paths, graph, schemaHash, report) {
13374
13376
  const sourcePages = graph.pages.filter((page) => page.kind === "source");
13375
13377
  const reviewPages = graph.pages.filter((page) => page.kind === "output" && page.path.startsWith("outputs/source-reviews/"));
13376
13378
  const briefPages = graph.pages.filter((page) => page.kind === "output" && page.path.startsWith("outputs/source-briefs/"));
13379
+ const guidePages = graph.pages.filter((page) => page.kind === "output" && page.path.startsWith("outputs/source-guides/"));
13380
+ const conceptPages = graph.pages.filter((page) => page.kind === "concept" && page.status !== "candidate").slice(0, 16);
13381
+ const entityPages = graph.pages.filter((page) => page.kind === "entity" && page.status !== "candidate").slice(0, 16);
13377
13382
  const manifests = graph.sources;
13378
13383
  const manifestBySourceId = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
13379
13384
  const timelineManifests = manifests.filter((manifest) => manifestDetailValue(manifest, "occurred_at")).sort((left, right) => (manifestDetailValue(right, "occurred_at") ?? "").localeCompare(manifestDetailValue(left, "occurred_at") ?? "")).slice(0, 25);
@@ -13382,6 +13387,9 @@ async function buildDashboardRecords(paths, graph, schemaHash, report) {
13382
13387
  const openQuestions = uniqueStrings3(
13383
13388
  analyses.flatMap((analysis) => analysis.questions.map((question) => `${analysis.title}: ${question}`))
13384
13389
  ).slice(0, 20);
13390
+ const stagedGuideBundles = (await Promise.all(
13391
+ (await fs18.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => await readJsonFile(approvalManifestPath(paths, entry.name)))
13392
+ )).filter((manifest) => Boolean(manifest)).filter((manifest) => manifest.bundleType === "guided_source").sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, 12);
13385
13393
  const dashboards = [
13386
13394
  {
13387
13395
  relativePath: "dashboards/index.md",
@@ -13391,7 +13399,10 @@ async function buildDashboardRecords(paths, graph, schemaHash, report) {
13391
13399
  "# Dashboards",
13392
13400
  "",
13393
13401
  "- [[dashboards/recent-sources|Recent Sources]]",
13402
+ "- [[dashboards/reading-log|Reading Log]]",
13394
13403
  "- [[dashboards/timeline|Timeline]]",
13404
+ "- [[dashboards/source-guides|Source Guides]]",
13405
+ "- [[dashboards/research-map|Research Map]]",
13395
13406
  "- [[dashboards/contradictions|Contradictions]]",
13396
13407
  "- [[dashboards/open-questions|Open Questions]]",
13397
13408
  "",
@@ -13464,6 +13475,49 @@ async function buildDashboardRecords(paths, graph, schemaHash, report) {
13464
13475
  }
13465
13476
  )
13466
13477
  },
13478
+ {
13479
+ relativePath: "dashboards/reading-log.md",
13480
+ title: "Reading Log",
13481
+ content: (metadata) => matter9.stringify(
13482
+ [
13483
+ "# Reading Log",
13484
+ "",
13485
+ ...timelineManifests.length ? timelineManifests.map((manifest) => {
13486
+ const occurredAt = manifestDetailValue(manifest, "occurred_at") ?? manifest.updatedAt;
13487
+ const participants = manifestDetailValue(manifest, "participants");
13488
+ return `- ${occurredAt}: ${manifest.title}${participants ? ` (${participants})` : ""}`;
13489
+ }) : recentSourcePages.map((page) => `- ${page.updatedAt}: [[${page.path.replace(/\.md$/, "")}|${page.title}]]`),
13490
+ "",
13491
+ "```dataview",
13492
+ "TABLE occurred_at, source_type, participants, container_title",
13493
+ 'FROM "sources"',
13494
+ "SORT occurred_at desc",
13495
+ "LIMIT 25",
13496
+ "```",
13497
+ ""
13498
+ ].join("\n"),
13499
+ {
13500
+ page_id: "dashboards:reading-log",
13501
+ kind: "index",
13502
+ title: "Reading Log",
13503
+ tags: ["index", "dashboard", "reading-log"],
13504
+ source_ids: timelineManifests.map((manifest) => manifest.sourceId),
13505
+ project_ids: [],
13506
+ node_ids: [],
13507
+ freshness: "fresh",
13508
+ status: metadata.status,
13509
+ confidence: 1,
13510
+ created_at: metadata.createdAt,
13511
+ updated_at: metadata.updatedAt,
13512
+ compiled_from: timelineManifests.map((manifest) => manifest.sourceId),
13513
+ managed_by: metadata.managedBy,
13514
+ backlinks: [],
13515
+ schema_hash: schemaHash,
13516
+ source_hashes: {},
13517
+ source_semantic_hashes: {}
13518
+ }
13519
+ )
13520
+ },
13467
13521
  {
13468
13522
  relativePath: "dashboards/timeline.md",
13469
13523
  title: "Timeline",
@@ -13507,6 +13561,112 @@ async function buildDashboardRecords(paths, graph, schemaHash, report) {
13507
13561
  }
13508
13562
  )
13509
13563
  },
13564
+ {
13565
+ relativePath: "dashboards/source-guides.md",
13566
+ title: "Source Guides",
13567
+ content: (metadata) => matter9.stringify(
13568
+ [
13569
+ "# Source Guides",
13570
+ "",
13571
+ ...guidePages.length ? guidePages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No accepted source guides yet."],
13572
+ "",
13573
+ "## Pending Guided Bundles",
13574
+ "",
13575
+ ...stagedGuideBundles.length ? stagedGuideBundles.map(
13576
+ (bundle) => `- ${bundle.createdAt}: \`${bundle.approvalId}\`${bundle.title ? ` ${bundle.title}` : ""} (${bundle.entries.length} staged entr${bundle.entries.length === 1 ? "y" : "ies"})`
13577
+ ) : ["- No staged guided bundles right now."],
13578
+ "",
13579
+ "```dataview",
13580
+ 'LIST FROM "outputs/source-guides"',
13581
+ "SORT file.mtime desc",
13582
+ "```",
13583
+ ""
13584
+ ].join("\n"),
13585
+ {
13586
+ page_id: "dashboards:source-guides",
13587
+ kind: "index",
13588
+ title: "Source Guides",
13589
+ tags: ["index", "dashboard", "source-guides"],
13590
+ source_ids: uniqueStrings3([
13591
+ ...guidePages.flatMap((page) => page.sourceIds),
13592
+ ...stagedGuideBundles.flatMap((bundle) => bundle.entries.flatMap((entry) => entry.sourceIds))
13593
+ ]),
13594
+ project_ids: [],
13595
+ node_ids: [],
13596
+ freshness: "fresh",
13597
+ status: metadata.status,
13598
+ confidence: 1,
13599
+ created_at: metadata.createdAt,
13600
+ updated_at: metadata.updatedAt,
13601
+ compiled_from: uniqueStrings3([
13602
+ ...guidePages.flatMap((page) => page.sourceIds),
13603
+ ...stagedGuideBundles.flatMap((bundle) => bundle.entries.flatMap((entry) => entry.sourceIds))
13604
+ ]),
13605
+ managed_by: metadata.managedBy,
13606
+ backlinks: [],
13607
+ schema_hash: schemaHash,
13608
+ source_hashes: {},
13609
+ source_semantic_hashes: {}
13610
+ }
13611
+ )
13612
+ },
13613
+ {
13614
+ relativePath: "dashboards/research-map.md",
13615
+ title: "Research Map",
13616
+ content: (metadata) => matter9.stringify(
13617
+ [
13618
+ "# Research Map",
13619
+ "",
13620
+ "## Canonical Concept Pages",
13621
+ "",
13622
+ ...conceptPages.length ? conceptPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No concept pages yet."],
13623
+ "",
13624
+ "## Canonical Entity Pages",
13625
+ "",
13626
+ ...entityPages.length ? entityPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No entity pages yet."],
13627
+ "",
13628
+ "## Recently Guided Sources",
13629
+ "",
13630
+ ...guidePages.length ? guidePages.slice(0, 8).map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No accepted source guides yet."],
13631
+ ...report?.suggestedQuestions?.length ? ["", "## Suggested Questions", "", ...report.suggestedQuestions.slice(0, 8).map((question) => `- ${question}`)] : [],
13632
+ "",
13633
+ "```dataview",
13634
+ 'TABLE file.folder, file.mtime FROM "concepts" OR "entities"',
13635
+ "SORT file.mtime desc",
13636
+ "LIMIT 30",
13637
+ "```",
13638
+ ""
13639
+ ].join("\n"),
13640
+ {
13641
+ page_id: "dashboards:research-map",
13642
+ kind: "index",
13643
+ title: "Research Map",
13644
+ tags: ["index", "dashboard", "research-map"],
13645
+ source_ids: uniqueStrings3([
13646
+ ...conceptPages.flatMap((page) => page.sourceIds),
13647
+ ...entityPages.flatMap((page) => page.sourceIds),
13648
+ ...guidePages.flatMap((page) => page.sourceIds)
13649
+ ]),
13650
+ project_ids: [],
13651
+ node_ids: [],
13652
+ freshness: "fresh",
13653
+ status: metadata.status,
13654
+ confidence: 1,
13655
+ created_at: metadata.createdAt,
13656
+ updated_at: metadata.updatedAt,
13657
+ compiled_from: uniqueStrings3([
13658
+ ...conceptPages.flatMap((page) => page.sourceIds),
13659
+ ...entityPages.flatMap((page) => page.sourceIds),
13660
+ ...guidePages.flatMap((page) => page.sourceIds)
13661
+ ]),
13662
+ managed_by: metadata.managedBy,
13663
+ backlinks: [],
13664
+ schema_hash: schemaHash,
13665
+ source_hashes: {},
13666
+ source_semantic_hashes: {}
13667
+ }
13668
+ )
13669
+ },
13510
13670
  {
13511
13671
  relativePath: "dashboards/contradictions.md",
13512
13672
  title: "Contradictions",
@@ -13520,14 +13680,14 @@ async function buildDashboardRecords(paths, graph, schemaHash, report) {
13520
13680
  return `- ${left} / ${right}: ${contradiction.claimA} <> ${contradiction.claimB}`;
13521
13681
  }) : ["- No contradictions are currently flagged."],
13522
13682
  "",
13523
- ...reviewPages.length || briefPages.length ? [
13683
+ ...reviewPages.length || briefPages.length || guidePages.length ? [
13524
13684
  "## Related Reviews",
13525
13685
  "",
13526
- ...[...reviewPages, ...briefPages].slice(0, 12).map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`),
13686
+ ...[...guidePages, ...reviewPages, ...briefPages].slice(0, 12).map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`),
13527
13687
  ""
13528
13688
  ] : [],
13529
13689
  "```dataview",
13530
- 'LIST FROM "outputs/source-reviews"',
13690
+ 'LIST FROM "outputs/source-reviews" OR "outputs/source-guides"',
13531
13691
  "SORT file.mtime desc",
13532
13692
  "```",
13533
13693
  ""
@@ -13564,7 +13724,7 @@ async function buildDashboardRecords(paths, graph, schemaHash, report) {
13564
13724
  ...openQuestions.length ? openQuestions.map((question) => `- ${question}`) : ["- No open questions are currently extracted."],
13565
13725
  "",
13566
13726
  "```dataview",
13567
- 'LIST FROM "outputs/source-briefs" OR "outputs/source-reviews"',
13727
+ 'LIST FROM "outputs/source-briefs" OR "outputs/source-reviews" OR "outputs/source-guides"',
13568
13728
  "SORT file.mtime desc",
13569
13729
  "```",
13570
13730
  ""
@@ -14338,7 +14498,7 @@ async function writeApprovalManifest(paths, manifest) {
14338
14498
  await fs18.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
14339
14499
  `, "utf8");
14340
14500
  }
14341
- async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
14501
+ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph, labelsByPath = /* @__PURE__ */ new Map()) {
14342
14502
  const previousPagesById = new Map((previousGraph?.pages ?? []).map((page) => [page.id, page]));
14343
14503
  const previousPagesByPath = new Map((previousGraph?.pages ?? []).map((page) => [page.path, page]));
14344
14504
  const nextPagesByPath = new Map(graph.pages.map((page) => [page.path, page]));
@@ -14360,7 +14520,8 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
14360
14520
  status: "pending",
14361
14521
  sourceIds: nextPage.sourceIds,
14362
14522
  nextPath: nextPage.path,
14363
- previousPath: previousPage.path
14523
+ previousPath: previousPage.path,
14524
+ label: labelsByPath.get(nextPage.path) ?? labelsByPath.get(previousPage.path)
14364
14525
  });
14365
14526
  handledDeletedPaths.add(previousPage.path);
14366
14527
  continue;
@@ -14373,7 +14534,8 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
14373
14534
  status: "pending",
14374
14535
  sourceIds: nextPage.sourceIds,
14375
14536
  nextPath: nextPage.path,
14376
- previousPath: previousPage?.path
14537
+ previousPath: previousPage?.path,
14538
+ label: labelsByPath.get(nextPage.path) ?? (previousPage?.path ? labelsByPath.get(previousPage.path) : void 0)
14377
14539
  });
14378
14540
  }
14379
14541
  for (const deletedPath of deletedPaths.sort((left, right) => left.localeCompare(right))) {
@@ -14388,7 +14550,8 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
14388
14550
  changeType: "delete",
14389
14551
  status: "pending",
14390
14552
  sourceIds: previousPage?.sourceIds ?? [],
14391
- previousPath: deletedPath
14553
+ previousPath: deletedPath,
14554
+ label: labelsByPath.get(deletedPath)
14392
14555
  });
14393
14556
  }
14394
14557
  return uniqueBy(entries, (entry) => `${entry.pageId}:${entry.changeType}:${entry.nextPath ?? ""}:${entry.previousPath ?? ""}`);
@@ -14408,6 +14571,8 @@ async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGr
14408
14571
  await writeApprovalManifest(paths, {
14409
14572
  approvalId,
14410
14573
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
14574
+ bundleType: "compile",
14575
+ title: "Compile Approval",
14411
14576
  entries: await buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph)
14412
14577
  });
14413
14578
  return { approvalId, approvalDir };
@@ -15123,7 +15288,7 @@ async function persistExploreHub(rootDir, input) {
15123
15288
  }
15124
15289
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
15125
15290
  }
15126
- async function stageOutputApprovalBundle(rootDir, stagedPages) {
15291
+ async function stageOutputApprovalBundle(rootDir, stagedPages, options = {}) {
15127
15292
  const { paths } = await loadVaultConfig(rootDir);
15128
15293
  const previousGraph = await readJsonFile(paths.graphPath);
15129
15294
  const changedFiles = stagedPages.flatMap((item) => [
@@ -15134,6 +15299,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
15134
15299
  binary: typeof assetFile.content !== "string"
15135
15300
  }))
15136
15301
  ]);
15302
+ const labelsByPath = new Map(stagedPages.filter((item) => item.label).map((item) => [item.page.path, item.label]));
15137
15303
  const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
15138
15304
  const approvalDir = path22.join(paths.approvalsDir, approvalId);
15139
15305
  await ensureDir(approvalDir);
@@ -15164,18 +15330,21 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
15164
15330
  await writeApprovalManifest(paths, {
15165
15331
  approvalId,
15166
15332
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
15333
+ bundleType: options.bundleType ?? "generated_output",
15334
+ title: options.title,
15167
15335
  entries: await buildApprovalEntries(
15168
15336
  paths,
15169
15337
  stagedPages.map((item) => ({ relativePath: item.page.path, content: item.content })),
15170
15338
  [],
15171
15339
  previousGraph ?? null,
15172
- graph
15340
+ graph,
15341
+ labelsByPath
15173
15342
  )
15174
15343
  });
15175
15344
  return { approvalId, approvalDir };
15176
15345
  }
15177
- async function stageGeneratedOutputPages(rootDir, stagedPages) {
15178
- return await stageOutputApprovalBundle(rootDir, stagedPages);
15346
+ async function stageGeneratedOutputPages(rootDir, stagedPages, options = {}) {
15347
+ return await stageOutputApprovalBundle(rootDir, stagedPages, options);
15179
15348
  }
15180
15349
  async function executeQuery(rootDir, question, format) {
15181
15350
  const { paths } = await loadVaultConfig(rootDir);
@@ -15748,14 +15917,24 @@ async function ensureObsidianWorkspace(rootDir) {
15748
15917
  ]);
15749
15918
  }
15750
15919
  async function initVault(rootDir, options = {}) {
15751
- const { paths } = await initWorkspace(rootDir);
15920
+ const profile = options.profile ?? "default";
15921
+ const { paths } = await initWorkspace(rootDir, { profile });
15752
15922
  await installConfiguredAgents(rootDir);
15753
15923
  const insightsIndexPath = path22.join(paths.wikiDir, "insights", "index.md");
15754
15924
  const now = (/* @__PURE__ */ new Date()).toISOString();
15755
15925
  await writeFileIfChanged(
15756
15926
  insightsIndexPath,
15757
15927
  matter9.stringify(
15758
- [
15928
+ (profile === "personal-research" ? [
15929
+ "# Insights",
15930
+ "",
15931
+ "Human-authored research notes live here.",
15932
+ "",
15933
+ "- Use this folder for thesis notes, reading reflections, synthesis drafts, and decisions you want to keep explicitly human-authored.",
15934
+ "- Guided ingest can propose updates elsewhere, but SwarmVault does not rewrite files inside `wiki/insights/` after initialization.",
15935
+ "- Treat these pages as the human judgment layer for your vault.",
15936
+ ""
15937
+ ] : [
15759
15938
  "# Insights",
15760
15939
  "",
15761
15940
  "Human-authored notes live here.",
@@ -15763,7 +15942,7 @@ async function initVault(rootDir, options = {}) {
15763
15942
  "- SwarmVault can read these pages during compile and query.",
15764
15943
  "- SwarmVault does not rewrite files inside `wiki/insights/` after initialization.",
15765
15944
  ""
15766
- ].join("\n"),
15945
+ ]).join("\n"),
15767
15946
  {
15768
15947
  page_id: "insights:index",
15769
15948
  kind: "index",
@@ -15835,6 +16014,42 @@ async function initVault(rootDir, options = {}) {
15835
16014
  if (options.obsidian) {
15836
16015
  await ensureObsidianWorkspace(rootDir);
15837
16016
  }
16017
+ if (profile === "personal-research") {
16018
+ await writeFileIfChanged(
16019
+ path22.join(paths.wikiDir, "insights", "research-playbook.md"),
16020
+ matter9.stringify(
16021
+ [
16022
+ "# Personal Research Playbook",
16023
+ "",
16024
+ "- Add one source at a time with `swarmvault ingest <input> --guide` or `swarmvault source add <input> --guide`.",
16025
+ "- Review `wiki/outputs/source-briefs/`, `wiki/outputs/source-reviews/`, and `wiki/outputs/source-guides/` before accepting staged updates.",
16026
+ "- Keep unresolved questions visible in `wiki/dashboards/open-questions.md`.",
16027
+ "- Use `swarmvault review list` and `swarmvault review show --diff` to decide what becomes canonical.",
16028
+ ""
16029
+ ].join("\n"),
16030
+ {
16031
+ page_id: "insights:research-playbook",
16032
+ kind: "insight",
16033
+ title: "Personal Research Playbook",
16034
+ tags: ["insight", "research", "playbook"],
16035
+ source_ids: [],
16036
+ project_ids: [],
16037
+ node_ids: [],
16038
+ freshness: "fresh",
16039
+ status: "active",
16040
+ confidence: 1,
16041
+ created_at: now,
16042
+ updated_at: now,
16043
+ compiled_from: [],
16044
+ managed_by: "human",
16045
+ backlinks: [],
16046
+ schema_hash: "",
16047
+ source_hashes: {},
16048
+ source_semantic_hashes: {}
16049
+ }
16050
+ )
16051
+ );
16052
+ }
15838
16053
  }
15839
16054
  async function runConfiguredBenchmark(rootDir, config) {
15840
16055
  if (config.benchmark?.enabled === false) {
@@ -16748,7 +16963,7 @@ async function bootstrapDemo(rootDir, input) {
16748
16963
  }
16749
16964
 
16750
16965
  // src/mcp.ts
16751
- var SERVER_VERSION = "0.4.0";
16966
+ var SERVER_VERSION = "0.5.0";
16752
16967
  async function createMcpServer(rootDir) {
16753
16968
  const server = new McpServer({
16754
16969
  name: "swarmvault",
@@ -17927,7 +18142,7 @@ function renderDeterministicSourceBrief(input) {
17927
18142
  ""
17928
18143
  ].join("\n");
17929
18144
  }
17930
- async function generateSourceBriefMarkdown(rootDir, source) {
18145
+ async function generateSourceBriefMarkdownForScope(rootDir, source) {
17931
18146
  const { paths } = await loadVaultConfig(rootDir);
17932
18147
  const graph = await readJsonFile(paths.graphPath);
17933
18148
  if (!graph) {
@@ -17964,7 +18179,7 @@ Entities: ${analysis.entities.map((entity) => entity.name).join(", ") || "none"}
17964
18179
  ),
17965
18180
  prompt: [
17966
18181
  `Source title: ${source.title}`,
17967
- `Source kind: ${source.kind}`,
18182
+ `Source kind: ${source.kind ?? "source"}`,
17968
18183
  `Tracked source ids: ${source.sourceIds.join(", ") || "none"}`,
17969
18184
  "",
17970
18185
  "Pages:",
@@ -17982,12 +18197,12 @@ Entities: ${analysis.entities.map((entity) => entity.name).join(", ") || "none"}
17982
18197
  return fallback;
17983
18198
  }
17984
18199
  }
17985
- async function writeSourceBrief(rootDir, source) {
18200
+ async function writeSourceBriefForScope(rootDir, source) {
17986
18201
  if (!source.sourceIds.length) {
17987
18202
  return null;
17988
18203
  }
17989
18204
  const { paths } = await loadVaultConfig(rootDir);
17990
- const markdown = await generateSourceBriefMarkdown(rootDir, source);
18205
+ const markdown = await generateSourceBriefMarkdownForScope(rootDir, source);
17991
18206
  if (!markdown) {
17992
18207
  return null;
17993
18208
  }
@@ -18025,6 +18240,9 @@ async function writeSourceBrief(rootDir, source) {
18025
18240
  await fs21.writeFile(absolutePath, output.content, "utf8");
18026
18241
  return absolutePath;
18027
18242
  }
18243
+ async function writeSourceBrief(rootDir, source) {
18244
+ return await writeSourceBriefForScope(rootDir, scopeFromManagedSource(source));
18245
+ }
18028
18246
  async function generateBriefsForSources(rootDir, sources) {
18029
18247
  const briefPaths = /* @__PURE__ */ new Map();
18030
18248
  for (const source of sources) {
@@ -18137,7 +18355,7 @@ Entities: ${analysis.entities.map((entity) => entity.name).join(", ") || "none"}
18137
18355
  return fallback;
18138
18356
  }
18139
18357
  }
18140
- async function stageSourceReviewForScope(rootDir, scope) {
18358
+ async function buildSourceReviewStagedPage(rootDir, scope) {
18141
18359
  const { paths } = await loadVaultConfig(rootDir);
18142
18360
  const markdown = await generateSourceReviewMarkdown(rootDir, scope);
18143
18361
  if (!markdown) {
@@ -18172,7 +18390,176 @@ async function stageSourceReviewForScope(rootDir, scope) {
18172
18390
  confidence: 0.79
18173
18391
  }
18174
18392
  });
18175
- const approval = await stageGeneratedOutputPages(rootDir, [{ page: output.page, content: output.content }]);
18393
+ return { page: output.page, content: output.content };
18394
+ }
18395
+ function classifySourceGuidePageBuckets(sourcePages, scopeSourceIds) {
18396
+ const scopeSet = new Set(scopeSourceIds);
18397
+ const canonicalPages = sourcePages.filter((page) => page.kind === "source" || page.kind === "concept" || page.kind === "entity").slice(0, 12);
18398
+ const newPages = canonicalPages.filter((page) => page.sourceIds.every((sourceId) => scopeSet.has(sourceId))).slice(0, 6);
18399
+ const reinforcingPages = canonicalPages.filter((page) => page.sourceIds.some((sourceId) => !scopeSet.has(sourceId))).slice(0, 6);
18400
+ return { canonicalPages, newPages, reinforcingPages };
18401
+ }
18402
+ function renderDeterministicSourceGuide(input) {
18403
+ const { canonicalPages, newPages, reinforcingPages } = classifySourceGuidePageBuckets(input.sourcePages, input.scope.sourceIds);
18404
+ const modulePages = input.sourcePages.filter((page) => page.kind === "module").slice(0, 6);
18405
+ const takeaways = uniqueStrings4(
18406
+ input.analyses.flatMap((analysis) => [
18407
+ analysis.summary,
18408
+ ...analysis.concepts.map((concept) => concept.description),
18409
+ ...analysis.entities.map((entity) => entity.description)
18410
+ ]).filter(Boolean).map((value) => normalizeWhitespace(value))
18411
+ ).slice(0, 7).map((value) => truncate(value, 180));
18412
+ const questions = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.questions)).slice(0, 6);
18413
+ const contradictions = input.report?.contradictions.filter(
18414
+ (contradiction) => input.scope.sourceIds.includes(contradiction.sourceIdA) || input.scope.sourceIds.includes(contradiction.sourceIdB)
18415
+ ) ?? [];
18416
+ return [
18417
+ `# Source Guide: ${input.scope.title}`,
18418
+ "",
18419
+ "## What This Source Is",
18420
+ "",
18421
+ takeaways.length ? takeaways[0] : `${input.scope.title} has been compiled into the vault and is ready for guided review.`,
18422
+ "",
18423
+ "## Key Takeaways",
18424
+ "",
18425
+ ...takeaways.length ? takeaways.map((takeaway) => `- ${takeaway}`) : ["- No takeaways are available until the source is compiled."],
18426
+ "",
18427
+ "## Proposed Canonical Pages To Update",
18428
+ "",
18429
+ ...canonicalPages.length ? canonicalPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No likely canonical pages were identified yet."],
18430
+ "",
18431
+ "## New, Reinforcing, And Conflicting Claims",
18432
+ "",
18433
+ ...newPages.length ? ["New or source-local pages:", ...newPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`), ""] : [],
18434
+ ...reinforcingPages.length ? ["Reinforcing existing pages:", ...reinforcingPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`), ""] : [],
18435
+ ...contradictions.length ? ["Conflicts to judge:", ...contradictions.map((contradiction) => `- ${contradiction.claimA} / ${contradiction.claimB}`), ""] : ["Conflicts to judge:", "- No contradictions are currently flagged for this source scope.", ""],
18436
+ "## What Should Probably Stay Out For Now",
18437
+ "",
18438
+ ...modulePages.length ? ["- Avoid promoting narrow implementation details unless they matter to your thesis or recurring questions."] : ["- Avoid promoting incidental details that are not yet supported by multiple sources or clear research goals."],
18439
+ ...contradictions.length ? ["- Keep contested claims provisional until you review the conflicting evidence side by side."] : [],
18440
+ "",
18441
+ "## Needs Human Judgment",
18442
+ "",
18443
+ ...questions.length ? questions.map((question) => `- ${question}`) : ["- Decide which proposed canonical pages deserve durable summary updates."],
18444
+ "",
18445
+ "## Suggested Follow-up Questions",
18446
+ "",
18447
+ ...questions.length ? questions.map((question) => `- ${question}`) : ["- What changed in your understanding after reading this source?"],
18448
+ ""
18449
+ ].join("\n");
18450
+ }
18451
+ async function generateSourceGuideMarkdown(rootDir, scope) {
18452
+ const { paths } = await loadVaultConfig(rootDir);
18453
+ let graph = await readJsonFile(paths.graphPath);
18454
+ if (!graph) {
18455
+ await compileVault(rootDir, {});
18456
+ graph = await readJsonFile(paths.graphPath);
18457
+ }
18458
+ if (!graph) {
18459
+ return null;
18460
+ }
18461
+ const sourcePages = scopedSourcePages(graph, scope.sourceIds);
18462
+ const analyses = await loadSourceAnalyses(rootDir, scope.sourceIds);
18463
+ const report = await readGraphReport(rootDir);
18464
+ const fallback = renderDeterministicSourceGuide({
18465
+ scope,
18466
+ sourcePages,
18467
+ analyses,
18468
+ report
18469
+ });
18470
+ const provider = await getProviderForTask(rootDir, "queryProvider");
18471
+ if (provider.type === "heuristic") {
18472
+ return fallback;
18473
+ }
18474
+ try {
18475
+ const schemas = await loadVaultSchemas(rootDir);
18476
+ const { canonicalPages, newPages, reinforcingPages } = classifySourceGuidePageBuckets(sourcePages, scope.sourceIds);
18477
+ const pageContext = sourcePages.slice(0, 12).map((page) => `- ${page.title} (${page.kind}) -> ${page.path}`).join("\n");
18478
+ const analysisContext = analyses.slice(0, 8).map(
18479
+ (analysis) => `# ${analysis.title}
18480
+ Summary: ${analysis.summary}
18481
+ Questions: ${analysis.questions.join(" | ") || "none"}
18482
+ Concepts: ${analysis.concepts.map((concept) => concept.name).join(", ") || "none"}
18483
+ Entities: ${analysis.entities.map((entity) => entity.name).join(", ") || "none"}`
18484
+ ).join("\n\n---\n\n");
18485
+ const response = await provider.generateText({
18486
+ system: buildSchemaPrompt(
18487
+ schemas.effective.global,
18488
+ "Write a concise markdown source guide with sections: What This Source Is, Key Takeaways, Proposed Canonical Pages To Update, New Reinforcing And Conflicting Claims, What Should Probably Stay Out For Now, Needs Human Judgment, Suggested Follow-up Questions. Focus on helping a human integrate one source into an evolving research wiki."
18489
+ ),
18490
+ prompt: [
18491
+ `Source scope: ${scope.title}`,
18492
+ `Scope id: ${scope.id}`,
18493
+ `Tracked source ids: ${scope.sourceIds.join(", ") || "none"}`,
18494
+ `Current brief path: ${scope.briefPath ?? "none"}`,
18495
+ "",
18496
+ "Likely canonical pages:",
18497
+ canonicalPages.length ? canonicalPages.map((page) => `- ${page.title} -> ${page.path}`).join("\n") : "- none",
18498
+ "",
18499
+ "Likely source-local pages:",
18500
+ newPages.length ? newPages.map((page) => `- ${page.title} -> ${page.path}`).join("\n") : "- none",
18501
+ "",
18502
+ "Likely reinforcing pages:",
18503
+ reinforcingPages.length ? reinforcingPages.map((page) => `- ${page.title} -> ${page.path}`).join("\n") : "- none",
18504
+ "",
18505
+ "Pages:",
18506
+ pageContext || "- none",
18507
+ "",
18508
+ "Analyses:",
18509
+ analysisContext || "No analysis context available.",
18510
+ "",
18511
+ "Deterministic fallback draft:",
18512
+ fallback
18513
+ ].join("\n")
18514
+ });
18515
+ return response.text?.trim() ? response.text.trim() : fallback;
18516
+ } catch {
18517
+ return fallback;
18518
+ }
18519
+ }
18520
+ async function buildSourceGuideStagedPage(rootDir, scope) {
18521
+ const { paths } = await loadVaultConfig(rootDir);
18522
+ const markdown = await generateSourceGuideMarkdown(rootDir, scope);
18523
+ if (!markdown) {
18524
+ throw new Error(`Could not generate a source guide for ${scope.id}.`);
18525
+ }
18526
+ const graph = await readJsonFile(paths.graphPath);
18527
+ const relatedPages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
18528
+ const relatedPageIds = relatedPages.slice(0, 18).map((page) => page.id);
18529
+ const relatedNodeIds = graph ? scopedNodeIds(graph, scope.sourceIds).slice(0, 28) : [];
18530
+ const projectIds = uniqueStrings4(relatedPages.flatMap((page) => page.projectIds));
18531
+ const now = (/* @__PURE__ */ new Date()).toISOString();
18532
+ const output = buildOutputPage({
18533
+ title: `Source Guide: ${scope.title}`,
18534
+ question: `Guide ${scope.title}`,
18535
+ answer: markdown,
18536
+ citations: scope.sourceIds,
18537
+ schemaHash: graph?.generatedAt ?? "",
18538
+ outputFormat: "report",
18539
+ relatedPageIds,
18540
+ relatedNodeIds,
18541
+ relatedSourceIds: scope.sourceIds,
18542
+ projectIds,
18543
+ extraTags: ["source-guide", "guided-ingest"],
18544
+ origin: "query",
18545
+ slug: `source-guides/${scope.id}`,
18546
+ metadata: {
18547
+ status: "draft",
18548
+ createdAt: now,
18549
+ updatedAt: now,
18550
+ compiledFrom: scope.sourceIds,
18551
+ managedBy: "system",
18552
+ confidence: 0.8
18553
+ }
18554
+ });
18555
+ return { page: output.page, content: output.content };
18556
+ }
18557
+ async function stageSourceReviewForScope(rootDir, scope) {
18558
+ const output = await buildSourceReviewStagedPage(rootDir, scope);
18559
+ const approval = await stageGeneratedOutputPages(rootDir, [{ page: output.page, content: output.content, label: "source-review" }], {
18560
+ bundleType: "source_review",
18561
+ title: `Source Review: ${scope.title}`
18562
+ });
18176
18563
  return {
18177
18564
  sourceId: scope.id,
18178
18565
  pageId: output.page.id,
@@ -18182,16 +18569,55 @@ async function stageSourceReviewForScope(rootDir, scope) {
18182
18569
  approvalDir: approval.approvalDir
18183
18570
  };
18184
18571
  }
18572
+ async function stageSourceGuideForScope(rootDir, scope) {
18573
+ const briefPath = scope.briefPath ?? await writeSourceBriefForScope(rootDir, scope) ?? void 0;
18574
+ if (briefPath) {
18575
+ await refreshVaultAfterOutputSave(rootDir);
18576
+ }
18577
+ const reviewOutput = await buildSourceReviewStagedPage(rootDir, scope);
18578
+ const guideOutput = await buildSourceGuideStagedPage(rootDir, {
18579
+ ...scope,
18580
+ briefPath
18581
+ });
18582
+ const approval = await stageGeneratedOutputPages(
18583
+ rootDir,
18584
+ [
18585
+ { page: reviewOutput.page, content: reviewOutput.content, label: "source-review" },
18586
+ { page: guideOutput.page, content: guideOutput.content, label: "source-guide" }
18587
+ ],
18588
+ {
18589
+ bundleType: "guided_source",
18590
+ title: `Guided Source: ${scope.title}`
18591
+ }
18592
+ );
18593
+ await refreshVaultAfterOutputSave(rootDir);
18594
+ return {
18595
+ sourceId: scope.id,
18596
+ pageId: guideOutput.page.id,
18597
+ guidePath: path25.join(approval.approvalDir, "wiki", guideOutput.page.path),
18598
+ reviewPageId: reviewOutput.page.id,
18599
+ reviewPath: path25.join(approval.approvalDir, "wiki", reviewOutput.page.path),
18600
+ briefPath,
18601
+ staged: true,
18602
+ approvalId: approval.approvalId,
18603
+ approvalDir: approval.approvalDir
18604
+ };
18605
+ }
18185
18606
  function scopeFromManagedSource(source) {
18186
18607
  return {
18187
18608
  id: source.id,
18188
18609
  title: source.title,
18189
- sourceIds: source.sourceIds
18610
+ sourceIds: source.sourceIds,
18611
+ kind: source.kind,
18612
+ briefPath: source.briefPath
18190
18613
  };
18191
18614
  }
18192
18615
  async function reviewSourceScope(rootDir, scope) {
18193
18616
  return await stageSourceReviewForScope(rootDir, scope);
18194
18617
  }
18618
+ async function guideSourceScope(rootDir, scope) {
18619
+ return await stageSourceGuideForScope(rootDir, scope);
18620
+ }
18195
18621
  async function reviewManagedSource(rootDir, id) {
18196
18622
  const managedSources = await loadManagedSources(rootDir);
18197
18623
  const managedSource = managedSources.find((source) => source.id === id);
@@ -18201,14 +18627,37 @@ async function reviewManagedSource(rootDir, id) {
18201
18627
  }
18202
18628
  return await stageSourceReviewForScope(rootDir, scopeFromManagedSource(managedSource));
18203
18629
  }
18204
- const manifest = (await listManifests(rootDir)).find((candidate) => candidate.sourceId === id);
18630
+ const manifests = await listManifests(rootDir);
18631
+ const manifest = manifests.find((candidate) => candidate.sourceId === id);
18205
18632
  if (!manifest) {
18206
18633
  throw new Error(`Managed source or source id not found: ${id}`);
18207
18634
  }
18208
18635
  return await stageSourceReviewForScope(rootDir, {
18209
- id: manifest.sourceId,
18210
- title: manifest.title,
18211
- sourceIds: [manifest.sourceId]
18636
+ id: manifest.sourceGroupId ?? manifest.sourceId,
18637
+ title: manifest.sourceGroupTitle ?? manifest.title,
18638
+ sourceIds: manifest.sourceGroupId ? manifests.filter((candidate) => candidate.sourceGroupId === manifest.sourceGroupId).map((candidate) => candidate.sourceId) : [manifest.sourceId],
18639
+ kind: manifest.sourceKind
18640
+ });
18641
+ }
18642
+ async function guideManagedSource(rootDir, id) {
18643
+ const managedSources = await loadManagedSources(rootDir);
18644
+ const managedSource = managedSources.find((source) => source.id === id);
18645
+ if (managedSource) {
18646
+ if (!await loadVaultConfig(rootDir).then(({ paths }) => fileExists(paths.graphPath))) {
18647
+ await compileVault(rootDir, {});
18648
+ }
18649
+ return await stageSourceGuideForScope(rootDir, scopeFromManagedSource(managedSource));
18650
+ }
18651
+ const manifests = await listManifests(rootDir);
18652
+ const manifest = manifests.find((candidate) => candidate.sourceId === id);
18653
+ if (!manifest) {
18654
+ throw new Error(`Managed source or source id not found: ${id}`);
18655
+ }
18656
+ return await stageSourceGuideForScope(rootDir, {
18657
+ id: manifest.sourceGroupId ?? manifest.sourceId,
18658
+ title: manifest.sourceGroupTitle ?? manifest.title,
18659
+ sourceIds: manifest.sourceGroupId ? manifests.filter((candidate) => candidate.sourceGroupId === manifest.sourceGroupId).map((candidate) => candidate.sourceId) : [manifest.sourceId],
18660
+ kind: manifest.sourceKind
18212
18661
  });
18213
18662
  }
18214
18663
  function shouldCompile(changedSources, graphExists, compileRequested) {
@@ -18220,8 +18669,9 @@ async function listManagedSourceRecords(rootDir) {
18220
18669
  }
18221
18670
  async function addManagedSource(rootDir, input, options = {}) {
18222
18671
  const compileRequested = options.compile ?? true;
18223
- const briefRequested = options.brief ?? true;
18224
- const reviewRequested = options.review ?? false;
18672
+ const guideRequested = options.guide ?? false;
18673
+ const briefRequested = guideRequested ? true : options.brief ?? true;
18674
+ const reviewRequested = guideRequested ? false : options.review ?? false;
18225
18675
  const sources = await loadManagedSources(rootDir);
18226
18676
  const resolved = await resolveManagedSourceInput(rootDir, input);
18227
18677
  const existing = sources.find((candidate) => matchesManagedSourceSpec(candidate, resolved));
@@ -18262,17 +18712,23 @@ async function addManagedSource(rootDir, input, options = {}) {
18262
18712
  const nextSources = existing ? sources.map((candidate) => candidate.id === nextSource.id ? nextSource : candidate) : [...sources, nextSource];
18263
18713
  await saveManagedSources(rootDir, nextSources);
18264
18714
  const review = reviewRequested && nextSource.status === "ready" ? await stageSourceReviewForScope(rootDir, scopeFromManagedSource(nextSource)) : void 0;
18715
+ const guide = guideRequested && nextSource.status === "ready" ? await stageSourceGuideForScope(rootDir, {
18716
+ ...scopeFromManagedSource(nextSource),
18717
+ briefPath: nextSource.briefPath
18718
+ }) : void 0;
18265
18719
  return {
18266
18720
  source: nextSource,
18267
18721
  compile,
18268
18722
  briefGenerated,
18269
- review
18723
+ review,
18724
+ guide
18270
18725
  };
18271
18726
  }
18272
18727
  async function reloadManagedSources(rootDir, options = {}) {
18273
18728
  const compileRequested = options.compile ?? true;
18274
- const briefRequested = options.brief ?? true;
18275
- const reviewRequested = options.review ?? false;
18729
+ const guideRequested = options.guide ?? false;
18730
+ const briefRequested = guideRequested ? true : options.brief ?? true;
18731
+ const reviewRequested = guideRequested ? false : options.review ?? false;
18276
18732
  const sources = await loadManagedSources(rootDir);
18277
18733
  const selected = options.all || !options.id ? sources : sources.filter((source) => source.id === options.id);
18278
18734
  if (!selected.length) {
@@ -18311,11 +18767,20 @@ async function reloadManagedSources(rootDir, options = {}) {
18311
18767
  const reviews = reviewRequested ? await Promise.all(
18312
18768
  nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)).filter((source) => source.status === "ready").map(async (source) => await stageSourceReviewForScope(rootDir, scopeFromManagedSource(source)))
18313
18769
  ) : [];
18770
+ const guides = guideRequested ? await Promise.all(
18771
+ nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)).filter((source) => source.status === "ready").map(
18772
+ async (source) => await stageSourceGuideForScope(rootDir, {
18773
+ ...scopeFromManagedSource(source),
18774
+ briefPath: source.briefPath
18775
+ })
18776
+ )
18777
+ ) : [];
18314
18778
  return {
18315
18779
  sources: nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)),
18316
18780
  compile,
18317
18781
  briefPaths: [...briefPaths.values()],
18318
- reviews
18782
+ reviews,
18783
+ guides
18319
18784
  };
18320
18785
  }
18321
18786
  async function deleteManagedSource(rootDir, id) {
@@ -19216,6 +19681,8 @@ export {
19216
19681
  getWatchStatus,
19217
19682
  getWebSearchAdapterForTask,
19218
19683
  getWorkspaceInfo,
19684
+ guideManagedSource,
19685
+ guideSourceScope,
19219
19686
  importInbox,
19220
19687
  ingestDirectory,
19221
19688
  ingestInput,