@swarmvaultai/engine 0.7.30 → 0.7.31
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/chunk-NECZ4MUE.js +1416 -0
- package/dist/index.d.ts +86 -1
- package/dist/index.js +1889 -1434
- package/dist/registry-4C55ZCPL.js +12 -0
- package/dist/viewer/assets/{index-DxKn2KOc.js → index-CwkhOTfH.js} +37 -36
- package/dist/viewer/assets/{index-BHjjw4rU.css → index-DRAglPyY.css} +1 -1
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/lib.d.ts +27 -1
- package/dist/viewer/lib.js +141 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
uniqueBy,
|
|
23
23
|
writeFileIfChanged,
|
|
24
24
|
writeJsonFile
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-NECZ4MUE.js";
|
|
26
26
|
import {
|
|
27
27
|
estimatePageTokens,
|
|
28
28
|
estimateTokens,
|
|
@@ -492,6 +492,100 @@ async function autoCommitWikiChanges(rootDir, operation, detail, options) {
|
|
|
492
492
|
return message;
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
+
// src/candidate-promotion.ts
|
|
496
|
+
var DEFAULT_PROMOTION_CONFIG = {
|
|
497
|
+
enabled: false,
|
|
498
|
+
minSources: 3,
|
|
499
|
+
minConfidence: 0.8,
|
|
500
|
+
minAgreement: 0.7,
|
|
501
|
+
minDegree: 2,
|
|
502
|
+
minAgeHours: 24,
|
|
503
|
+
maxPerRun: 25,
|
|
504
|
+
dryRun: false
|
|
505
|
+
};
|
|
506
|
+
function jaccard(left, right) {
|
|
507
|
+
if (left.length === 0 && right.length === 0) return 1;
|
|
508
|
+
const leftSet = new Set(left);
|
|
509
|
+
const rightSet = new Set(right);
|
|
510
|
+
const union = /* @__PURE__ */ new Set([...leftSet, ...rightSet]);
|
|
511
|
+
if (union.size === 0) return 1;
|
|
512
|
+
let intersection = 0;
|
|
513
|
+
for (const item of leftSet) {
|
|
514
|
+
if (rightSet.has(item)) intersection++;
|
|
515
|
+
}
|
|
516
|
+
return intersection / union.size;
|
|
517
|
+
}
|
|
518
|
+
function hoursSince(iso, now) {
|
|
519
|
+
const then = new Date(iso).getTime();
|
|
520
|
+
if (!Number.isFinite(then)) return 0;
|
|
521
|
+
return Math.max(0, (now - then) / (1e3 * 60 * 60));
|
|
522
|
+
}
|
|
523
|
+
function maxDegreeFor(graph, nodeIds) {
|
|
524
|
+
let best = 0;
|
|
525
|
+
const byId = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
526
|
+
for (const nodeId of nodeIds) {
|
|
527
|
+
const node = byId.get(nodeId);
|
|
528
|
+
if (node && (node.degree ?? 0) > best) best = node.degree ?? 0;
|
|
529
|
+
}
|
|
530
|
+
return best;
|
|
531
|
+
}
|
|
532
|
+
function describeGate(result) {
|
|
533
|
+
const verb = result.passed ? ">=" : "<";
|
|
534
|
+
return `${result.gate} ${result.value.toFixed(2)} ${verb} ${result.threshold.toFixed(2)}`;
|
|
535
|
+
}
|
|
536
|
+
function evaluateCandidateForPromotion(page, graph, history, config, now = Date.now()) {
|
|
537
|
+
const historical = history?.[page.id];
|
|
538
|
+
const historicalSources = historical?.sourceIds ?? [];
|
|
539
|
+
const agreement = historicalSources.length ? jaccard(historicalSources, page.sourceIds) : 0;
|
|
540
|
+
const degree = maxDegreeFor(graph, page.nodeIds);
|
|
541
|
+
const ageHours = hoursSince(page.createdAt, now);
|
|
542
|
+
const gates = [
|
|
543
|
+
{ gate: "sources", value: page.sourceIds.length, threshold: config.minSources, passed: page.sourceIds.length >= config.minSources },
|
|
544
|
+
{ gate: "confidence", value: page.confidence, threshold: config.minConfidence, passed: page.confidence >= config.minConfidence },
|
|
545
|
+
{ gate: "agreement", value: agreement, threshold: config.minAgreement, passed: agreement >= config.minAgreement },
|
|
546
|
+
{ gate: "degree", value: degree, threshold: config.minDegree, passed: degree >= config.minDegree },
|
|
547
|
+
{ gate: "age", value: ageHours, threshold: config.minAgeHours, passed: ageHours >= config.minAgeHours }
|
|
548
|
+
];
|
|
549
|
+
const passedCount = gates.filter((gate) => gate.passed).length;
|
|
550
|
+
const promote = gates.every((gate) => gate.passed);
|
|
551
|
+
const score = passedCount / gates.length;
|
|
552
|
+
return {
|
|
553
|
+
pageId: page.id,
|
|
554
|
+
title: page.title,
|
|
555
|
+
kind: page.kind,
|
|
556
|
+
promote,
|
|
557
|
+
score,
|
|
558
|
+
gates,
|
|
559
|
+
reasons: gates.map(describeGate)
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function sortDecisionsForPromotion(decisions) {
|
|
563
|
+
return [...decisions].sort((left, right) => {
|
|
564
|
+
if (left.promote !== right.promote) return left.promote ? -1 : 1;
|
|
565
|
+
if (right.score !== left.score) return right.score - left.score;
|
|
566
|
+
return left.pageId.localeCompare(right.pageId);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
function renderPromotionSessionMarkdown(decisions, promotedPageIds, options) {
|
|
570
|
+
const lines = [];
|
|
571
|
+
lines.push(`# Auto-Promotion Run`);
|
|
572
|
+
lines.push("");
|
|
573
|
+
lines.push(`- started: ${options.startedAt}`);
|
|
574
|
+
lines.push(`- finished: ${options.finishedAt}`);
|
|
575
|
+
lines.push(`- mode: ${options.dryRun ? "dry-run" : "applied"}`);
|
|
576
|
+
lines.push(`- promoted: ${promotedPageIds.length}`);
|
|
577
|
+
lines.push(`- evaluated: ${decisions.length}`);
|
|
578
|
+
lines.push("");
|
|
579
|
+
lines.push(`| page | decision | score | reasons |`);
|
|
580
|
+
lines.push(`| --- | --- | --- | --- |`);
|
|
581
|
+
for (const decision of sortDecisionsForPromotion(decisions)) {
|
|
582
|
+
const decided = decision.promote ? promotedPageIds.includes(decision.pageId) ? "promoted" : "promote (dry-run)" : "skipped";
|
|
583
|
+
lines.push(`| ${decision.pageId} | ${decided} | ${decision.score.toFixed(2)} | ${decision.reasons.join("; ")} |`);
|
|
584
|
+
}
|
|
585
|
+
lines.push("");
|
|
586
|
+
return lines.join("\n");
|
|
587
|
+
}
|
|
588
|
+
|
|
495
589
|
// src/graph-export.ts
|
|
496
590
|
import { readFileSync } from "fs";
|
|
497
591
|
import fs2 from "fs/promises";
|
|
@@ -7803,7 +7897,7 @@ function tsconfigPathAliasesForFile(repoRelativePath, config) {
|
|
|
7803
7897
|
);
|
|
7804
7898
|
const patternBase = pattern.replace("*", "");
|
|
7805
7899
|
for (const candidate of [stripped, indexStripped]) {
|
|
7806
|
-
if (candidate
|
|
7900
|
+
if (candidate?.startsWith(targetPrefix)) {
|
|
7807
7901
|
aliases.push(patternBase + candidate.slice(targetPrefix.length));
|
|
7808
7902
|
}
|
|
7809
7903
|
}
|
|
@@ -7816,11 +7910,11 @@ function tsconfigPathAliasesForFile(repoRelativePath, config) {
|
|
|
7816
7910
|
}
|
|
7817
7911
|
}
|
|
7818
7912
|
if (config.baseUrl !== ".") {
|
|
7819
|
-
const basePrefix = normalizeAlias(config.baseUrl)
|
|
7913
|
+
const basePrefix = `${normalizeAlias(config.baseUrl)}/`;
|
|
7820
7914
|
if (stripped.startsWith(basePrefix)) {
|
|
7821
7915
|
aliases.push(stripped.slice(basePrefix.length));
|
|
7822
7916
|
}
|
|
7823
|
-
if (indexStripped
|
|
7917
|
+
if (indexStripped?.startsWith(basePrefix)) {
|
|
7824
7918
|
aliases.push(indexStripped.slice(basePrefix.length));
|
|
7825
7919
|
}
|
|
7826
7920
|
}
|
|
@@ -10995,6 +11089,37 @@ var MARKDOWN_SEMANTIC_FRONTMATTER_KEYS = [
|
|
|
10995
11089
|
function uniqueStrings(values) {
|
|
10996
11090
|
return [...new Set(values.filter(Boolean))];
|
|
10997
11091
|
}
|
|
11092
|
+
function ingestRunStatePath(stateDir, runId) {
|
|
11093
|
+
return path12.join(stateDir, "ingest-runs", `${runId}.json`);
|
|
11094
|
+
}
|
|
11095
|
+
function buildIngestRunId() {
|
|
11096
|
+
return `ingest-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
|
|
11097
|
+
}
|
|
11098
|
+
async function loadIngestRunState(stateDir, runId) {
|
|
11099
|
+
if (!runId) return null;
|
|
11100
|
+
const absolute = ingestRunStatePath(stateDir, runId);
|
|
11101
|
+
if (!await fileExists(absolute)) {
|
|
11102
|
+
throw new Error(`Ingest run state not found: ${runId}`);
|
|
11103
|
+
}
|
|
11104
|
+
const loaded = await readJsonFile(absolute);
|
|
11105
|
+
if (!loaded) {
|
|
11106
|
+
throw new Error(`Ingest run state is empty or unreadable: ${runId}`);
|
|
11107
|
+
}
|
|
11108
|
+
return loaded;
|
|
11109
|
+
}
|
|
11110
|
+
async function saveIngestRunState(stateDir, state) {
|
|
11111
|
+
const absolute = ingestRunStatePath(stateDir, state.runId);
|
|
11112
|
+
await ensureDir(path12.dirname(absolute));
|
|
11113
|
+
await writeJsonFile(absolute, state);
|
|
11114
|
+
return absolute;
|
|
11115
|
+
}
|
|
11116
|
+
async function clearIngestRunState(stateDir, runId) {
|
|
11117
|
+
const absolute = ingestRunStatePath(stateDir, runId);
|
|
11118
|
+
try {
|
|
11119
|
+
await fs11.rm(absolute, { force: true });
|
|
11120
|
+
} catch {
|
|
11121
|
+
}
|
|
11122
|
+
}
|
|
10998
11123
|
function inferKind(mimeType, filePath, detectionOptions = {}) {
|
|
10999
11124
|
if (inferCodeLanguage(filePath, mimeType, detectionOptions)) {
|
|
11000
11125
|
return "code";
|
|
@@ -11447,7 +11572,8 @@ function normalizeIngestOptions(options) {
|
|
|
11447
11572
|
exclude: (options?.exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean),
|
|
11448
11573
|
maxFiles: Math.max(1, Math.floor(options?.maxFiles ?? DEFAULT_MAX_DIRECTORY_FILES)),
|
|
11449
11574
|
gitignore: options?.gitignore ?? true,
|
|
11450
|
-
extractClasses: options?.extractClasses ?? ["first_party"]
|
|
11575
|
+
extractClasses: options?.extractClasses ?? ["first_party"],
|
|
11576
|
+
resume: options?.resume
|
|
11451
11577
|
};
|
|
11452
11578
|
}
|
|
11453
11579
|
async function resolveRepoIngestOptions(rootDir, options) {
|
|
@@ -13489,37 +13615,73 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
13489
13615
|
skipped: result.skipped
|
|
13490
13616
|
};
|
|
13491
13617
|
}
|
|
13492
|
-
const
|
|
13618
|
+
const collected = await collectDirectoryFiles(rootDir, absoluteInputDir, repoRoot, normalizedOptions);
|
|
13619
|
+
const skipped = collected.skipped;
|
|
13620
|
+
let files = collected.files;
|
|
13621
|
+
const resumeState = await loadIngestRunState(paths.stateDir, normalizedOptions.resume);
|
|
13622
|
+
if (resumeState) {
|
|
13623
|
+
const failedSet = new Set(resumeState.failed.map((entry) => entry.absolutePath));
|
|
13624
|
+
files = files.filter((absolutePath) => failedSet.has(absolutePath));
|
|
13625
|
+
}
|
|
13626
|
+
const runId = resumeState?.runId ?? buildIngestRunId();
|
|
13493
13627
|
const imported = [];
|
|
13494
13628
|
const updated = [];
|
|
13629
|
+
const failed = [];
|
|
13630
|
+
const failedRecords = [];
|
|
13495
13631
|
const progress = createProgressReporter("ingest", files.length);
|
|
13496
13632
|
for (const absolutePath of files) {
|
|
13633
|
+
const relativeForLog = toPosix(path12.relative(rootDir, absolutePath));
|
|
13497
13634
|
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path12.relative(repoRoot, absolutePath));
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13635
|
+
let preparedInputs;
|
|
13636
|
+
try {
|
|
13637
|
+
preparedInputs = await prepareFileInputs(
|
|
13638
|
+
rootDir,
|
|
13639
|
+
absolutePath,
|
|
13640
|
+
repoRoot,
|
|
13641
|
+
sourceClassForRelativePath(relativePath, normalizedOptions)
|
|
13642
|
+
);
|
|
13643
|
+
} catch (error) {
|
|
13644
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13645
|
+
failed.push({ path: relativeForLog, error: message, stage: "prepare" });
|
|
13646
|
+
failedRecords.push({ absolutePath, path: relativeForLog, error: message, stage: "prepare" });
|
|
13647
|
+
progress.tick();
|
|
13648
|
+
continue;
|
|
13510
13649
|
}
|
|
13511
|
-
|
|
13512
|
-
|
|
13650
|
+
try {
|
|
13651
|
+
const result = await persistPreparedInputs(rootDir, absolutePath, preparedInputs, paths);
|
|
13652
|
+
if (result.created.length) imported.push(...result.created);
|
|
13653
|
+
if (result.updated.length) updated.push(...result.updated);
|
|
13654
|
+
if (!result.created.length && !result.updated.length && !result.removed.length) {
|
|
13655
|
+
skipped.push({ path: relativeForLog, reason: "duplicate_content" });
|
|
13656
|
+
}
|
|
13657
|
+
} catch (error) {
|
|
13658
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13659
|
+
failed.push({ path: relativeForLog, error: message, stage: "persist" });
|
|
13660
|
+
failedRecords.push({ absolutePath, path: relativeForLog, error: message, stage: "persist" });
|
|
13513
13661
|
}
|
|
13514
13662
|
progress.tick();
|
|
13515
13663
|
}
|
|
13516
|
-
progress.finish(`imported=${imported.length}, updated=${updated.length}, skipped=${skipped.length}`);
|
|
13664
|
+
progress.finish(`imported=${imported.length}, updated=${updated.length}, skipped=${skipped.length}, failed=${failed.length}`);
|
|
13665
|
+
let statePath;
|
|
13666
|
+
if (failed.length) {
|
|
13667
|
+
statePath = await saveIngestRunState(paths.stateDir, {
|
|
13668
|
+
runId,
|
|
13669
|
+
inputDir: absoluteInputDir,
|
|
13670
|
+
repoRoot,
|
|
13671
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13672
|
+
failed: failedRecords
|
|
13673
|
+
});
|
|
13674
|
+
} else if (resumeState) {
|
|
13675
|
+
await clearIngestRunState(paths.stateDir, resumeState.runId);
|
|
13676
|
+
}
|
|
13517
13677
|
await appendLogEntry(rootDir, "ingest_directory", toPosix(path12.relative(rootDir, absoluteInputDir)) || ".", [
|
|
13518
13678
|
`repo_root=${toPosix(path12.relative(rootDir, repoRoot)) || "."}`,
|
|
13679
|
+
`run_id=${runId}`,
|
|
13519
13680
|
`scanned=${files.length}`,
|
|
13520
13681
|
`imported=${imported.length}`,
|
|
13521
13682
|
`updated=${updated.length}`,
|
|
13522
|
-
`skipped=${skipped.length}
|
|
13683
|
+
`skipped=${skipped.length}`,
|
|
13684
|
+
`failed=${failed.length}`
|
|
13523
13685
|
]);
|
|
13524
13686
|
return {
|
|
13525
13687
|
inputDir: absoluteInputDir,
|
|
@@ -13527,7 +13689,10 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
13527
13689
|
scannedCount: files.length,
|
|
13528
13690
|
imported,
|
|
13529
13691
|
updated,
|
|
13530
|
-
skipped
|
|
13692
|
+
skipped,
|
|
13693
|
+
failed,
|
|
13694
|
+
runId,
|
|
13695
|
+
statePath
|
|
13531
13696
|
};
|
|
13532
13697
|
}
|
|
13533
13698
|
async function importInbox(rootDir, inputDir) {
|
|
@@ -13643,7 +13808,7 @@ async function readExtractionArtifact(rootDir, manifest) {
|
|
|
13643
13808
|
|
|
13644
13809
|
// src/mcp.ts
|
|
13645
13810
|
import fs20 from "fs/promises";
|
|
13646
|
-
import
|
|
13811
|
+
import path25 from "path";
|
|
13647
13812
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13648
13813
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13649
13814
|
import { z as z8 } from "zod";
|
|
@@ -18056,7 +18221,7 @@ async function resolveImageGenerationProvider(rootDir) {
|
|
|
18056
18221
|
if (!providerConfig) {
|
|
18057
18222
|
throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
|
|
18058
18223
|
}
|
|
18059
|
-
const { createProvider: createProvider2 } = await import("./registry-
|
|
18224
|
+
const { createProvider: createProvider2 } = await import("./registry-4C55ZCPL.js");
|
|
18060
18225
|
return createProvider2(preferredProviderId, providerConfig, rootDir);
|
|
18061
18226
|
}
|
|
18062
18227
|
async function generateOutputArtifacts(rootDir, input) {
|
|
@@ -20877,10 +21042,9 @@ function updateCandidateHistory(compileState, page, deleted = false) {
|
|
|
20877
21042
|
function sortGraphPages(pages) {
|
|
20878
21043
|
return [...pages].sort((left, right) => left.path.localeCompare(right.path) || left.title.localeCompare(right.title));
|
|
20879
21044
|
}
|
|
20880
|
-
function
|
|
21045
|
+
function diffLines(current, staged) {
|
|
20881
21046
|
const currentLines = current.split("\n");
|
|
20882
21047
|
const stagedLines = staged.split("\n");
|
|
20883
|
-
const output = [`--- a/${label}`, `+++ b/${label}`];
|
|
20884
21048
|
const n = currentLines.length;
|
|
20885
21049
|
const m = stagedLines.length;
|
|
20886
21050
|
const dp = Array.from({ length: n + 1 }, () => Array(m + 1).fill(0));
|
|
@@ -20889,23 +21053,107 @@ function computeUnifiedDiff(current, staged, label) {
|
|
|
20889
21053
|
dp[i2][j2] = currentLines[i2] === stagedLines[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
20890
21054
|
}
|
|
20891
21055
|
}
|
|
21056
|
+
const lines = [];
|
|
20892
21057
|
let i = 0;
|
|
20893
21058
|
let j = 0;
|
|
20894
21059
|
while (i < n || j < m) {
|
|
20895
21060
|
if (i < n && j < m && currentLines[i] === stagedLines[j]) {
|
|
20896
|
-
|
|
21061
|
+
lines.push({ type: "context", value: currentLines[i] });
|
|
20897
21062
|
i++;
|
|
20898
21063
|
j++;
|
|
20899
21064
|
} else if (j < m && (i >= n || dp[i][j + 1] >= dp[i + 1][j])) {
|
|
20900
|
-
|
|
21065
|
+
lines.push({ type: "add", value: stagedLines[j] });
|
|
20901
21066
|
j++;
|
|
20902
21067
|
} else {
|
|
20903
|
-
|
|
21068
|
+
lines.push({ type: "remove", value: currentLines[i] });
|
|
20904
21069
|
i++;
|
|
20905
21070
|
}
|
|
20906
21071
|
}
|
|
21072
|
+
return lines;
|
|
21073
|
+
}
|
|
21074
|
+
function computeUnifiedDiff(current, staged, label) {
|
|
21075
|
+
const output = [`--- a/${label}`, `+++ b/${label}`];
|
|
21076
|
+
for (const line of diffLines(current, staged)) {
|
|
21077
|
+
const prefix = line.type === "add" ? "+" : line.type === "remove" ? "-" : " ";
|
|
21078
|
+
output.push(`${prefix}${line.value}`);
|
|
21079
|
+
}
|
|
20907
21080
|
return output.join("\n");
|
|
20908
21081
|
}
|
|
21082
|
+
var PROTECTED_FRONTMATTER_FIELDS = /* @__PURE__ */ new Set([
|
|
21083
|
+
"page_id",
|
|
21084
|
+
"source_ids",
|
|
21085
|
+
"node_ids",
|
|
21086
|
+
"freshness",
|
|
21087
|
+
"source_hashes",
|
|
21088
|
+
"source_semantic_hashes",
|
|
21089
|
+
"schema_hash"
|
|
21090
|
+
]);
|
|
21091
|
+
function stableSerialize(value) {
|
|
21092
|
+
if (value === void 0) return "undefined";
|
|
21093
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
21094
|
+
if (Array.isArray(value)) return `[${value.map(stableSerialize).join(",")}]`;
|
|
21095
|
+
const keys = Object.keys(value).sort();
|
|
21096
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${stableSerialize(value[key])}`).join(",")}}`;
|
|
21097
|
+
}
|
|
21098
|
+
function compareFrontmatter(currentData, stagedData) {
|
|
21099
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(currentData), ...Object.keys(stagedData)]);
|
|
21100
|
+
const changes = [];
|
|
21101
|
+
for (const key of keys) {
|
|
21102
|
+
const before = currentData[key];
|
|
21103
|
+
const after = stagedData[key];
|
|
21104
|
+
if (stableSerialize(before) === stableSerialize(after)) continue;
|
|
21105
|
+
changes.push({
|
|
21106
|
+
key,
|
|
21107
|
+
before,
|
|
21108
|
+
after,
|
|
21109
|
+
protected: PROTECTED_FRONTMATTER_FIELDS.has(key)
|
|
21110
|
+
});
|
|
21111
|
+
}
|
|
21112
|
+
return changes.sort((left, right) => left.key.localeCompare(right.key));
|
|
21113
|
+
}
|
|
21114
|
+
function computeStructuredDiff(current, staged, isBinaryAsset) {
|
|
21115
|
+
if (isBinaryAsset) {
|
|
21116
|
+
return {
|
|
21117
|
+
hunks: [],
|
|
21118
|
+
addedLines: 0,
|
|
21119
|
+
removedLines: 0,
|
|
21120
|
+
frontmatterChanges: []
|
|
21121
|
+
};
|
|
21122
|
+
}
|
|
21123
|
+
if (!current && !staged) return void 0;
|
|
21124
|
+
let currentData = {};
|
|
21125
|
+
let stagedData = {};
|
|
21126
|
+
let currentBody = current ?? "";
|
|
21127
|
+
let stagedBody = staged ?? "";
|
|
21128
|
+
if (current) {
|
|
21129
|
+
const parsed = matter10(current);
|
|
21130
|
+
currentData = parsed.data ?? {};
|
|
21131
|
+
currentBody = parsed.content;
|
|
21132
|
+
}
|
|
21133
|
+
if (staged) {
|
|
21134
|
+
const parsed = matter10(staged);
|
|
21135
|
+
stagedData = parsed.data ?? {};
|
|
21136
|
+
stagedBody = parsed.content;
|
|
21137
|
+
}
|
|
21138
|
+
const lines = diffLines(currentBody, stagedBody);
|
|
21139
|
+
const addedLines = lines.filter((line) => line.type === "add").length;
|
|
21140
|
+
const removedLines = lines.filter((line) => line.type === "remove").length;
|
|
21141
|
+
const hunks = lines.length ? [
|
|
21142
|
+
{
|
|
21143
|
+
oldStart: 1,
|
|
21144
|
+
oldLines: currentBody.split("\n").length,
|
|
21145
|
+
newStart: 1,
|
|
21146
|
+
newLines: stagedBody.split("\n").length,
|
|
21147
|
+
lines
|
|
21148
|
+
}
|
|
21149
|
+
] : [];
|
|
21150
|
+
return {
|
|
21151
|
+
hunks,
|
|
21152
|
+
addedLines,
|
|
21153
|
+
removedLines,
|
|
21154
|
+
frontmatterChanges: compareFrontmatter(currentData, stagedData)
|
|
21155
|
+
};
|
|
21156
|
+
}
|
|
20909
21157
|
function computeChangeSummary(current, staged, changeType) {
|
|
20910
21158
|
if (changeType === "create") return "New page";
|
|
20911
21159
|
if (changeType === "delete") return "Removed page";
|
|
@@ -20956,7 +21204,16 @@ async function readApproval(rootDir, approvalId, options) {
|
|
|
20956
21204
|
stagedContent
|
|
20957
21205
|
};
|
|
20958
21206
|
detail.changeSummary = computeChangeSummary(detail.currentContent, detail.stagedContent, detail.changeType);
|
|
20959
|
-
|
|
21207
|
+
const isBinaryAsset = detail.kind === "output";
|
|
21208
|
+
const structured = computeStructuredDiff(detail.currentContent, detail.stagedContent, isBinaryAsset);
|
|
21209
|
+
if (structured) {
|
|
21210
|
+
detail.structuredDiff = structured;
|
|
21211
|
+
const protectedChanges = structured.frontmatterChanges.filter((change) => change.protected);
|
|
21212
|
+
if (protectedChanges.length) {
|
|
21213
|
+
detail.warnings = ["protected_frontmatter_changed"];
|
|
21214
|
+
}
|
|
21215
|
+
}
|
|
21216
|
+
if (options?.diff && detail.currentContent && detail.stagedContent && !isBinaryAsset) {
|
|
20960
21217
|
detail.diff = computeUnifiedDiff(detail.currentContent, detail.stagedContent, detail.nextPath ?? detail.pageId);
|
|
20961
21218
|
}
|
|
20962
21219
|
return detail;
|
|
@@ -21167,6 +21424,86 @@ async function promoteCandidate(rootDir, target) {
|
|
|
21167
21424
|
updatedAt: nextPage.updatedAt
|
|
21168
21425
|
};
|
|
21169
21426
|
}
|
|
21427
|
+
function resolvePromotionConfig(config) {
|
|
21428
|
+
const overrides = config.candidate?.autoPromote ?? {};
|
|
21429
|
+
return { ...DEFAULT_PROMOTION_CONFIG, ...overrides };
|
|
21430
|
+
}
|
|
21431
|
+
function promotionSessionTitle(promotionConfig) {
|
|
21432
|
+
return promotionConfig.dryRun ? "auto-promote-dry-run" : "auto-promote";
|
|
21433
|
+
}
|
|
21434
|
+
async function runAutoPromotion(rootDir, options = {}) {
|
|
21435
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
21436
|
+
const { config, paths } = await loadVaultConfig(rootDir);
|
|
21437
|
+
const base = resolvePromotionConfig(config);
|
|
21438
|
+
const promotionConfig = { ...base, dryRun: options.dryRun ?? base.dryRun };
|
|
21439
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
21440
|
+
const compileState = await readJsonFile(paths.compileStatePath) ?? emptyCompileState();
|
|
21441
|
+
const candidates = (graph?.pages ?? []).filter(
|
|
21442
|
+
(page) => page.status === "candidate" && (page.kind === "concept" || page.kind === "entity")
|
|
21443
|
+
);
|
|
21444
|
+
const now = Date.now();
|
|
21445
|
+
const decisions = candidates.map(
|
|
21446
|
+
(page) => evaluateCandidateForPromotion(page, graph, compileState.candidateHistory, promotionConfig, now)
|
|
21447
|
+
);
|
|
21448
|
+
const sorted = sortDecisionsForPromotion(decisions);
|
|
21449
|
+
const acceptedIds = sorted.filter((decision) => decision.promote).slice(0, promotionConfig.maxPerRun).map((d) => d.pageId);
|
|
21450
|
+
const skippedIds = sorted.filter((decision) => !acceptedIds.includes(decision.pageId)).map((d) => d.pageId);
|
|
21451
|
+
const promotedPageIds = [];
|
|
21452
|
+
if (!promotionConfig.dryRun) {
|
|
21453
|
+
for (const pageId of acceptedIds) {
|
|
21454
|
+
try {
|
|
21455
|
+
await promoteCandidate(rootDir, pageId);
|
|
21456
|
+
promotedPageIds.push(pageId);
|
|
21457
|
+
} catch {
|
|
21458
|
+
}
|
|
21459
|
+
}
|
|
21460
|
+
}
|
|
21461
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
21462
|
+
const sessionBody = renderPromotionSessionMarkdown(decisions, promotedPageIds, {
|
|
21463
|
+
dryRun: promotionConfig.dryRun,
|
|
21464
|
+
startedAt,
|
|
21465
|
+
finishedAt
|
|
21466
|
+
});
|
|
21467
|
+
const { sessionPath } = await recordSession(rootDir, {
|
|
21468
|
+
operation: "candidate",
|
|
21469
|
+
title: promotionSessionTitle(promotionConfig),
|
|
21470
|
+
startedAt,
|
|
21471
|
+
finishedAt,
|
|
21472
|
+
success: true,
|
|
21473
|
+
relatedPageIds: decisions.map((decision) => decision.pageId),
|
|
21474
|
+
changedPages: promotedPageIds,
|
|
21475
|
+
lines: [
|
|
21476
|
+
`mode=${promotionConfig.dryRun ? "dry-run" : "applied"}`,
|
|
21477
|
+
`evaluated=${decisions.length}`,
|
|
21478
|
+
`promoted=${promotedPageIds.length}`,
|
|
21479
|
+
...sessionBody.split("\n")
|
|
21480
|
+
]
|
|
21481
|
+
});
|
|
21482
|
+
return {
|
|
21483
|
+
startedAt,
|
|
21484
|
+
finishedAt,
|
|
21485
|
+
dryRun: promotionConfig.dryRun,
|
|
21486
|
+
promotedPageIds,
|
|
21487
|
+
skippedPageIds: skippedIds,
|
|
21488
|
+
decisions: sorted,
|
|
21489
|
+
sessionPath
|
|
21490
|
+
};
|
|
21491
|
+
}
|
|
21492
|
+
async function previewCandidatePromotions(rootDir) {
|
|
21493
|
+
const { config, paths } = await loadVaultConfig(rootDir);
|
|
21494
|
+
const promotionConfig = resolvePromotionConfig(config);
|
|
21495
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
21496
|
+
const compileState = await readJsonFile(paths.compileStatePath) ?? emptyCompileState();
|
|
21497
|
+
const candidates = (graph?.pages ?? []).filter(
|
|
21498
|
+
(page) => page.status === "candidate" && (page.kind === "concept" || page.kind === "entity")
|
|
21499
|
+
);
|
|
21500
|
+
const now = Date.now();
|
|
21501
|
+
return sortDecisionsForPromotion(
|
|
21502
|
+
candidates.map(
|
|
21503
|
+
(page) => evaluateCandidateForPromotion(page, graph, compileState.candidateHistory, promotionConfig, now)
|
|
21504
|
+
)
|
|
21505
|
+
);
|
|
21506
|
+
}
|
|
21170
21507
|
async function archiveCandidate(rootDir, target) {
|
|
21171
21508
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
21172
21509
|
const { paths } = await loadVaultConfig(rootDir);
|
|
@@ -21713,6 +22050,19 @@ async function compileVault(rootDir, options = {}) {
|
|
|
21713
22050
|
pagesDropped: budgetResult.dropped.length
|
|
21714
22051
|
};
|
|
21715
22052
|
}
|
|
22053
|
+
let autoPromotionSummary;
|
|
22054
|
+
const promotionConfig = resolvePromotionConfig(config);
|
|
22055
|
+
const promotedFromAuto = [];
|
|
22056
|
+
if (promotionConfig.enabled && !options.approve) {
|
|
22057
|
+
const autoRun = await runAutoPromotion(rootDir, { dryRun: promotionConfig.dryRun });
|
|
22058
|
+
autoPromotionSummary = {
|
|
22059
|
+
evaluated: autoRun.decisions.length,
|
|
22060
|
+
promoted: autoRun.promotedPageIds.length,
|
|
22061
|
+
dryRun: autoRun.dryRun,
|
|
22062
|
+
sessionPath: autoRun.sessionPath
|
|
22063
|
+
};
|
|
22064
|
+
promotedFromAuto.push(...autoRun.promotedPageIds);
|
|
22065
|
+
}
|
|
21716
22066
|
return {
|
|
21717
22067
|
graphPath: paths.graphPath,
|
|
21718
22068
|
pageCount: sync.allPages.length,
|
|
@@ -21723,8 +22073,9 @@ async function compileVault(rootDir, options = {}) {
|
|
|
21723
22073
|
approvalDir: sync.approvalDir,
|
|
21724
22074
|
postPassApprovalId,
|
|
21725
22075
|
postPassApprovalDir,
|
|
21726
|
-
promotedPageIds: sync.promotedPageIds,
|
|
22076
|
+
promotedPageIds: [...sync.promotedPageIds, ...promotedFromAuto],
|
|
21727
22077
|
candidatePageCount: sync.candidatePageCount,
|
|
22078
|
+
autoPromotion: autoPromotionSummary,
|
|
21728
22079
|
tokenStats
|
|
21729
22080
|
};
|
|
21730
22081
|
}
|
|
@@ -22475,128 +22826,619 @@ async function bootstrapDemo(rootDir, input) {
|
|
|
22475
22826
|
};
|
|
22476
22827
|
}
|
|
22477
22828
|
|
|
22478
|
-
// src/
|
|
22479
|
-
|
|
22480
|
-
|
|
22481
|
-
|
|
22482
|
-
|
|
22483
|
-
|
|
22484
|
-
|
|
22485
|
-
|
|
22486
|
-
|
|
22487
|
-
|
|
22488
|
-
|
|
22489
|
-
|
|
22490
|
-
|
|
22491
|
-
|
|
22492
|
-
|
|
22493
|
-
|
|
22494
|
-
|
|
22495
|
-
|
|
22496
|
-
|
|
22497
|
-
|
|
22498
|
-
|
|
22499
|
-
|
|
22500
|
-
|
|
22501
|
-
|
|
22502
|
-
|
|
22503
|
-
|
|
22504
|
-
|
|
22505
|
-
|
|
22506
|
-
|
|
22507
|
-
|
|
22508
|
-
|
|
22509
|
-
|
|
22510
|
-
|
|
22511
|
-
|
|
22512
|
-
|
|
22513
|
-
|
|
22514
|
-
|
|
22515
|
-
|
|
22516
|
-
|
|
22517
|
-
|
|
22518
|
-
|
|
22519
|
-
|
|
22520
|
-
|
|
22521
|
-
|
|
22522
|
-
|
|
22523
|
-
|
|
22524
|
-
|
|
22525
|
-
|
|
22526
|
-
|
|
22527
|
-
|
|
22528
|
-
|
|
22529
|
-
|
|
22530
|
-
|
|
22531
|
-
|
|
22532
|
-
|
|
22533
|
-
|
|
22534
|
-
|
|
22535
|
-
|
|
22536
|
-
|
|
22537
|
-
|
|
22538
|
-
|
|
22539
|
-
|
|
22540
|
-
|
|
22541
|
-
|
|
22542
|
-
|
|
22543
|
-
|
|
22544
|
-
|
|
22545
|
-
|
|
22546
|
-
|
|
22547
|
-
|
|
22548
|
-
|
|
22549
|
-
|
|
22550
|
-
|
|
22551
|
-
|
|
22552
|
-
|
|
22553
|
-
|
|
22554
|
-
|
|
22555
|
-
|
|
22556
|
-
|
|
22557
|
-
|
|
22558
|
-
|
|
22559
|
-
|
|
22560
|
-
|
|
22561
|
-
|
|
22562
|
-
|
|
22563
|
-
|
|
22564
|
-
|
|
22565
|
-
);
|
|
22566
|
-
|
|
22567
|
-
|
|
22568
|
-
|
|
22569
|
-
|
|
22570
|
-
|
|
22571
|
-
|
|
22572
|
-
|
|
22573
|
-
|
|
22574
|
-
|
|
22575
|
-
|
|
22576
|
-
|
|
22577
|
-
);
|
|
22578
|
-
|
|
22579
|
-
|
|
22580
|
-
|
|
22581
|
-
|
|
22582
|
-
|
|
22583
|
-
|
|
22584
|
-
|
|
22585
|
-
|
|
22586
|
-
|
|
22587
|
-
|
|
22588
|
-
|
|
22589
|
-
|
|
22590
|
-
);
|
|
22591
|
-
|
|
22592
|
-
|
|
22593
|
-
|
|
22594
|
-
|
|
22595
|
-
|
|
22596
|
-
|
|
22597
|
-
|
|
22598
|
-
|
|
22599
|
-
|
|
22829
|
+
// src/watch.ts
|
|
22830
|
+
import path24 from "path";
|
|
22831
|
+
import process3 from "process";
|
|
22832
|
+
import chokidar from "chokidar";
|
|
22833
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
22834
|
+
var BACKOFF_THRESHOLD = 3;
|
|
22835
|
+
var CRITICAL_THRESHOLD = 10;
|
|
22836
|
+
var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
|
|
22837
|
+
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
22838
|
+
".ts",
|
|
22839
|
+
".tsx",
|
|
22840
|
+
".mts",
|
|
22841
|
+
".cts",
|
|
22842
|
+
".js",
|
|
22843
|
+
".jsx",
|
|
22844
|
+
".mjs",
|
|
22845
|
+
".cjs",
|
|
22846
|
+
".py",
|
|
22847
|
+
".go",
|
|
22848
|
+
".rs",
|
|
22849
|
+
".java",
|
|
22850
|
+
".kt",
|
|
22851
|
+
".kts",
|
|
22852
|
+
".scala",
|
|
22853
|
+
".sc",
|
|
22854
|
+
".dart",
|
|
22855
|
+
".lua",
|
|
22856
|
+
".zig",
|
|
22857
|
+
".cs",
|
|
22858
|
+
".php",
|
|
22859
|
+
".rb",
|
|
22860
|
+
".swift",
|
|
22861
|
+
".c",
|
|
22862
|
+
".h",
|
|
22863
|
+
".cpp",
|
|
22864
|
+
".cc",
|
|
22865
|
+
".cxx",
|
|
22866
|
+
".hpp",
|
|
22867
|
+
".hxx",
|
|
22868
|
+
".sh",
|
|
22869
|
+
".bash",
|
|
22870
|
+
".zsh",
|
|
22871
|
+
".ps1",
|
|
22872
|
+
".psm1",
|
|
22873
|
+
".ex",
|
|
22874
|
+
".exs",
|
|
22875
|
+
".jl",
|
|
22876
|
+
".r",
|
|
22877
|
+
".R"
|
|
22878
|
+
]);
|
|
22879
|
+
var FILE_CHANGE_RE = /^(?:add|change|unlink):(.+)$/;
|
|
22880
|
+
function isCodeOnlyChange(reasons) {
|
|
22881
|
+
for (const reason of reasons) {
|
|
22882
|
+
const match = reason.match(FILE_CHANGE_RE);
|
|
22883
|
+
if (!match) return false;
|
|
22884
|
+
const ext = path24.extname(match[1]).toLowerCase();
|
|
22885
|
+
if (!ext || !CODE_EXTENSIONS.has(ext)) return false;
|
|
22886
|
+
}
|
|
22887
|
+
return reasons.size > 0;
|
|
22888
|
+
}
|
|
22889
|
+
function hasNonCodeChanges(reasons) {
|
|
22890
|
+
for (const reason of reasons) {
|
|
22891
|
+
const match = reason.match(FILE_CHANGE_RE);
|
|
22892
|
+
if (!match) return true;
|
|
22893
|
+
const ext = path24.extname(match[1]).toLowerCase();
|
|
22894
|
+
if (!ext || !CODE_EXTENSIONS.has(ext)) return true;
|
|
22895
|
+
}
|
|
22896
|
+
return false;
|
|
22897
|
+
}
|
|
22898
|
+
function collectNonCodePaths(reasons) {
|
|
22899
|
+
const result = [];
|
|
22900
|
+
for (const reason of reasons) {
|
|
22901
|
+
const match = reason.match(FILE_CHANGE_RE);
|
|
22902
|
+
if (!match) {
|
|
22903
|
+
result.push(reason);
|
|
22904
|
+
continue;
|
|
22905
|
+
}
|
|
22906
|
+
const ext = path24.extname(match[1]).toLowerCase();
|
|
22907
|
+
if (!ext || !CODE_EXTENSIONS.has(ext)) result.push(match[1]);
|
|
22908
|
+
}
|
|
22909
|
+
return result;
|
|
22910
|
+
}
|
|
22911
|
+
function hasIgnoredRepoSegment(baseDir, targetPath) {
|
|
22912
|
+
const relativePath = path24.relative(baseDir, targetPath);
|
|
22913
|
+
if (!relativePath || relativePath.startsWith("..")) {
|
|
22914
|
+
return false;
|
|
22915
|
+
}
|
|
22916
|
+
return relativePath.split(path24.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
|
|
22917
|
+
}
|
|
22918
|
+
function workspaceIgnoreRoots(rootDir, paths) {
|
|
22919
|
+
return [
|
|
22920
|
+
paths.rawDir,
|
|
22921
|
+
paths.wikiDir,
|
|
22922
|
+
paths.stateDir,
|
|
22923
|
+
paths.agentDir,
|
|
22924
|
+
paths.inboxDir,
|
|
22925
|
+
path24.join(rootDir, ".claude"),
|
|
22926
|
+
path24.join(rootDir, ".cursor"),
|
|
22927
|
+
path24.join(rootDir, ".obsidian")
|
|
22928
|
+
].map((candidate) => path24.resolve(candidate));
|
|
22929
|
+
}
|
|
22930
|
+
async function resolveWatchTargets(rootDir, paths, options) {
|
|
22931
|
+
const targets = /* @__PURE__ */ new Set([path24.resolve(paths.inboxDir)]);
|
|
22932
|
+
if (options.repo) {
|
|
22933
|
+
for (const repoRoot of await listTrackedRepoRoots(rootDir)) {
|
|
22934
|
+
targets.add(path24.resolve(repoRoot));
|
|
22935
|
+
}
|
|
22936
|
+
}
|
|
22937
|
+
return [...targets].sort((left, right) => left.localeCompare(right));
|
|
22938
|
+
}
|
|
22939
|
+
async function performWatchCycle(rootDir, paths, options, codeOnly = false) {
|
|
22940
|
+
const imported = await importInbox(rootDir, paths.inboxDir);
|
|
22941
|
+
const repoSync = options.repo ? await syncTrackedReposForWatch(rootDir) : null;
|
|
22942
|
+
const compile = await compileVault(rootDir, { codeOnly });
|
|
22943
|
+
const pendingSemanticRefresh = repoSync ? await mergePendingSemanticRefresh(rootDir, repoSync.pendingSemanticRefresh) : await readPendingSemanticRefresh(rootDir);
|
|
22944
|
+
const stalePagePaths = await markPagesStaleForSources(
|
|
22945
|
+
rootDir,
|
|
22946
|
+
pendingSemanticRefresh.map((entry) => entry.sourceId).filter((sourceId) => Boolean(sourceId))
|
|
22947
|
+
);
|
|
22948
|
+
const lintFindingCount = options.lint ? (await lintVault(rootDir)).length : void 0;
|
|
22949
|
+
return {
|
|
22950
|
+
watchedRepoRoots: repoSync?.repoRoots ?? [],
|
|
22951
|
+
importedCount: imported.imported.length,
|
|
22952
|
+
scannedCount: imported.scannedCount,
|
|
22953
|
+
attachmentCount: imported.attachmentCount,
|
|
22954
|
+
repoImportedCount: repoSync?.imported.length ?? 0,
|
|
22955
|
+
repoUpdatedCount: repoSync?.updated.length ?? 0,
|
|
22956
|
+
repoRemovedCount: repoSync?.removed.length ?? 0,
|
|
22957
|
+
repoScannedCount: repoSync?.scannedCount ?? 0,
|
|
22958
|
+
pendingSemanticRefreshCount: pendingSemanticRefresh.length,
|
|
22959
|
+
pendingSemanticRefreshPaths: pendingSemanticRefresh.map((entry) => entry.path),
|
|
22960
|
+
changedPages: [.../* @__PURE__ */ new Set([...compile.changedPages, ...stalePagePaths])],
|
|
22961
|
+
lintFindingCount
|
|
22962
|
+
};
|
|
22963
|
+
}
|
|
22964
|
+
async function runWatchCycle(rootDir, options = {}) {
|
|
22965
|
+
const { paths } = await initWorkspace(rootDir);
|
|
22966
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
22967
|
+
let success = true;
|
|
22968
|
+
let error;
|
|
22969
|
+
let result = {
|
|
22970
|
+
watchedRepoRoots: [],
|
|
22971
|
+
importedCount: 0,
|
|
22972
|
+
scannedCount: 0,
|
|
22973
|
+
attachmentCount: 0,
|
|
22974
|
+
repoImportedCount: 0,
|
|
22975
|
+
repoUpdatedCount: 0,
|
|
22976
|
+
repoRemovedCount: 0,
|
|
22977
|
+
repoScannedCount: 0,
|
|
22978
|
+
pendingSemanticRefreshCount: 0,
|
|
22979
|
+
pendingSemanticRefreshPaths: [],
|
|
22980
|
+
changedPages: []
|
|
22981
|
+
};
|
|
22982
|
+
try {
|
|
22983
|
+
result = await performWatchCycle(rootDir, paths, options, options.codeOnly ?? false);
|
|
22984
|
+
return result;
|
|
22985
|
+
} catch (caught) {
|
|
22986
|
+
success = false;
|
|
22987
|
+
error = caught instanceof Error ? caught.message : String(caught);
|
|
22988
|
+
throw caught;
|
|
22989
|
+
} finally {
|
|
22990
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
22991
|
+
await recordSession(rootDir, {
|
|
22992
|
+
operation: "watch",
|
|
22993
|
+
title: `Watch cycle for ${paths.inboxDir}${options.repo ? " and tracked repos" : ""}`,
|
|
22994
|
+
startedAt: startedAt.toISOString(),
|
|
22995
|
+
finishedAt: finishedAt.toISOString(),
|
|
22996
|
+
success,
|
|
22997
|
+
error,
|
|
22998
|
+
changedPages: result.changedPages,
|
|
22999
|
+
lintFindingCount: result.lintFindingCount,
|
|
23000
|
+
lines: [
|
|
23001
|
+
"reasons=once",
|
|
23002
|
+
`imported=${result.importedCount}`,
|
|
23003
|
+
`scanned=${result.scannedCount}`,
|
|
23004
|
+
`attachments=${result.attachmentCount}`,
|
|
23005
|
+
`repo_scanned=${result.repoScannedCount}`,
|
|
23006
|
+
`repo_imported=${result.repoImportedCount}`,
|
|
23007
|
+
`repo_updated=${result.repoUpdatedCount}`,
|
|
23008
|
+
`repo_removed=${result.repoRemovedCount}`,
|
|
23009
|
+
`pending_semantic_refresh=${result.pendingSemanticRefreshCount}`,
|
|
23010
|
+
`lint=${result.lintFindingCount ?? 0}`
|
|
23011
|
+
]
|
|
23012
|
+
});
|
|
23013
|
+
await appendWatchRun(rootDir, {
|
|
23014
|
+
startedAt: startedAt.toISOString(),
|
|
23015
|
+
finishedAt: finishedAt.toISOString(),
|
|
23016
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
23017
|
+
inputDir: paths.inboxDir,
|
|
23018
|
+
reasons: ["once"],
|
|
23019
|
+
importedCount: result.importedCount + result.repoImportedCount + result.repoUpdatedCount,
|
|
23020
|
+
scannedCount: result.scannedCount + result.repoScannedCount,
|
|
23021
|
+
attachmentCount: result.attachmentCount,
|
|
23022
|
+
changedPages: result.changedPages,
|
|
23023
|
+
repoImportedCount: result.repoImportedCount,
|
|
23024
|
+
repoUpdatedCount: result.repoUpdatedCount,
|
|
23025
|
+
repoRemovedCount: result.repoRemovedCount,
|
|
23026
|
+
repoScannedCount: result.repoScannedCount,
|
|
23027
|
+
pendingSemanticRefreshCount: result.pendingSemanticRefreshCount,
|
|
23028
|
+
pendingSemanticRefreshPaths: result.pendingSemanticRefreshPaths,
|
|
23029
|
+
lintFindingCount: result.lintFindingCount,
|
|
23030
|
+
success,
|
|
23031
|
+
error
|
|
23032
|
+
});
|
|
23033
|
+
await writeWatchStatusArtifact(rootDir, {
|
|
23034
|
+
generatedAt: finishedAt.toISOString(),
|
|
23035
|
+
watchedRepoRoots: result.watchedRepoRoots,
|
|
23036
|
+
lastRun: {
|
|
23037
|
+
startedAt: startedAt.toISOString(),
|
|
23038
|
+
finishedAt: finishedAt.toISOString(),
|
|
23039
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
23040
|
+
inputDir: paths.inboxDir,
|
|
23041
|
+
reasons: ["once"],
|
|
23042
|
+
importedCount: result.importedCount + result.repoImportedCount + result.repoUpdatedCount,
|
|
23043
|
+
scannedCount: result.scannedCount + result.repoScannedCount,
|
|
23044
|
+
attachmentCount: result.attachmentCount,
|
|
23045
|
+
changedPages: result.changedPages,
|
|
23046
|
+
repoImportedCount: result.repoImportedCount,
|
|
23047
|
+
repoUpdatedCount: result.repoUpdatedCount,
|
|
23048
|
+
repoRemovedCount: result.repoRemovedCount,
|
|
23049
|
+
repoScannedCount: result.repoScannedCount,
|
|
23050
|
+
pendingSemanticRefreshCount: result.pendingSemanticRefreshCount,
|
|
23051
|
+
pendingSemanticRefreshPaths: result.pendingSemanticRefreshPaths,
|
|
23052
|
+
lintFindingCount: result.lintFindingCount,
|
|
23053
|
+
success,
|
|
23054
|
+
error
|
|
23055
|
+
},
|
|
23056
|
+
pendingSemanticRefresh: await readPendingSemanticRefresh(rootDir)
|
|
23057
|
+
});
|
|
23058
|
+
}
|
|
23059
|
+
}
|
|
23060
|
+
async function watchVault(rootDir, options = {}) {
|
|
23061
|
+
const { paths } = await initWorkspace(rootDir);
|
|
23062
|
+
const baseDebounceMs = options.debounceMs ?? 900;
|
|
23063
|
+
const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
|
|
23064
|
+
const inboxWatchRoot = path24.resolve(paths.inboxDir);
|
|
23065
|
+
let watchTargets = await resolveWatchTargets(rootDir, paths, options);
|
|
23066
|
+
let timer;
|
|
23067
|
+
let running = false;
|
|
23068
|
+
let pending = false;
|
|
23069
|
+
let closed = false;
|
|
23070
|
+
let consecutiveFailures = 0;
|
|
23071
|
+
let currentDebounceMs = baseDebounceMs;
|
|
23072
|
+
const reasons = /* @__PURE__ */ new Set();
|
|
23073
|
+
let activeCycle = null;
|
|
23074
|
+
const watcher = chokidar.watch(watchTargets, {
|
|
23075
|
+
ignoreInitial: true,
|
|
23076
|
+
usePolling: true,
|
|
23077
|
+
interval: 100,
|
|
23078
|
+
ignored: (targetPath) => {
|
|
23079
|
+
const absolutePath = path24.resolve(targetPath);
|
|
23080
|
+
const primaryTarget = watchTargets.filter((watchTarget) => isPathWithin(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
|
|
23081
|
+
if (!primaryTarget) {
|
|
23082
|
+
return false;
|
|
23083
|
+
}
|
|
23084
|
+
if (primaryTarget !== inboxWatchRoot && ignoredRoots.some((ignoreRoot) => isPathWithin(ignoreRoot, absolutePath))) {
|
|
23085
|
+
return true;
|
|
23086
|
+
}
|
|
23087
|
+
return hasIgnoredRepoSegment(primaryTarget, absolutePath);
|
|
23088
|
+
},
|
|
23089
|
+
awaitWriteFinish: {
|
|
23090
|
+
stabilityThreshold: Math.max(250, Math.floor(baseDebounceMs / 2)),
|
|
23091
|
+
pollInterval: 100
|
|
23092
|
+
}
|
|
23093
|
+
});
|
|
23094
|
+
const syncWatchTargets = async () => {
|
|
23095
|
+
const nextTargets = await resolveWatchTargets(rootDir, paths, options);
|
|
23096
|
+
const nextSet = new Set(nextTargets);
|
|
23097
|
+
const currentSet = new Set(watchTargets);
|
|
23098
|
+
const toRemove = watchTargets.filter((target) => !nextSet.has(target));
|
|
23099
|
+
const toAdd = nextTargets.filter((target) => !currentSet.has(target));
|
|
23100
|
+
if (toRemove.length > 0) {
|
|
23101
|
+
await watcher.unwatch(toRemove);
|
|
23102
|
+
}
|
|
23103
|
+
if (toAdd.length > 0) {
|
|
23104
|
+
await watcher.add(toAdd);
|
|
23105
|
+
}
|
|
23106
|
+
watchTargets = nextTargets;
|
|
23107
|
+
};
|
|
23108
|
+
const schedule = (reason) => {
|
|
23109
|
+
if (closed) {
|
|
23110
|
+
return;
|
|
23111
|
+
}
|
|
23112
|
+
reasons.add(reason);
|
|
23113
|
+
pending = true;
|
|
23114
|
+
if (timer) {
|
|
23115
|
+
clearTimeout(timer);
|
|
23116
|
+
}
|
|
23117
|
+
timer = setTimeout(() => {
|
|
23118
|
+
const cycle = runCycle();
|
|
23119
|
+
activeCycle = cycle.finally(() => {
|
|
23120
|
+
if (activeCycle === cycle) {
|
|
23121
|
+
activeCycle = null;
|
|
23122
|
+
}
|
|
23123
|
+
});
|
|
23124
|
+
}, currentDebounceMs);
|
|
23125
|
+
};
|
|
23126
|
+
const runCycle = async () => {
|
|
23127
|
+
if (running || closed || !pending) {
|
|
23128
|
+
return;
|
|
23129
|
+
}
|
|
23130
|
+
pending = false;
|
|
23131
|
+
running = true;
|
|
23132
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
23133
|
+
const detectedCodeOnly = isCodeOnlyChange(reasons);
|
|
23134
|
+
const hasDeferredNonCode = !detectedCodeOnly && hasNonCodeChanges(reasons);
|
|
23135
|
+
const codeOnlyChange = options.codeOnly || detectedCodeOnly || hasDeferredNonCode;
|
|
23136
|
+
const runReasons = [...reasons];
|
|
23137
|
+
reasons.clear();
|
|
23138
|
+
if (hasDeferredNonCode) {
|
|
23139
|
+
const nonCodePaths = collectNonCodePaths(new Set(runReasons));
|
|
23140
|
+
process3.stderr.write(
|
|
23141
|
+
`[swarmvault watch] Non-code changes detected (${nonCodePaths.length} file(s)) \u2014 run \`swarmvault compile\` to include LLM re-analysis.
|
|
23142
|
+
`
|
|
23143
|
+
);
|
|
23144
|
+
} else if (codeOnlyChange) {
|
|
23145
|
+
process3.stderr.write("[swarmvault watch] Code-only changes detected \u2014 skipping LLM re-analysis.\n");
|
|
23146
|
+
}
|
|
23147
|
+
let importedCount = 0;
|
|
23148
|
+
let scannedCount = 0;
|
|
23149
|
+
let attachmentCount = 0;
|
|
23150
|
+
let repoImportedCount = 0;
|
|
23151
|
+
let repoUpdatedCount = 0;
|
|
23152
|
+
let repoRemovedCount = 0;
|
|
23153
|
+
let repoScannedCount = 0;
|
|
23154
|
+
let watchedRepoRoots = [];
|
|
23155
|
+
let pendingSemanticRefreshCount = 0;
|
|
23156
|
+
let pendingSemanticRefreshPaths = [];
|
|
23157
|
+
let changedPages = [];
|
|
23158
|
+
let lintFindingCount;
|
|
23159
|
+
let success = true;
|
|
23160
|
+
let error;
|
|
23161
|
+
try {
|
|
23162
|
+
const result = await performWatchCycle(rootDir, paths, options, codeOnlyChange);
|
|
23163
|
+
importedCount = result.importedCount;
|
|
23164
|
+
scannedCount = result.scannedCount;
|
|
23165
|
+
attachmentCount = result.attachmentCount;
|
|
23166
|
+
repoImportedCount = result.repoImportedCount;
|
|
23167
|
+
repoUpdatedCount = result.repoUpdatedCount;
|
|
23168
|
+
repoRemovedCount = result.repoRemovedCount;
|
|
23169
|
+
repoScannedCount = result.repoScannedCount;
|
|
23170
|
+
watchedRepoRoots = result.watchedRepoRoots;
|
|
23171
|
+
pendingSemanticRefreshCount = result.pendingSemanticRefreshCount;
|
|
23172
|
+
pendingSemanticRefreshPaths = result.pendingSemanticRefreshPaths;
|
|
23173
|
+
changedPages = result.changedPages;
|
|
23174
|
+
lintFindingCount = result.lintFindingCount;
|
|
23175
|
+
consecutiveFailures = 0;
|
|
23176
|
+
currentDebounceMs = baseDebounceMs;
|
|
23177
|
+
await syncWatchTargets();
|
|
23178
|
+
} catch (caught) {
|
|
23179
|
+
success = false;
|
|
23180
|
+
error = caught instanceof Error ? caught.message : String(caught);
|
|
23181
|
+
consecutiveFailures++;
|
|
23182
|
+
pending = true;
|
|
23183
|
+
if (consecutiveFailures >= CRITICAL_THRESHOLD) {
|
|
23184
|
+
process3.stderr.write(
|
|
23185
|
+
`[swarmvault watch] ${consecutiveFailures} consecutive failures. Check vault state. Continuing at max backoff.
|
|
23186
|
+
`
|
|
23187
|
+
);
|
|
23188
|
+
}
|
|
23189
|
+
if (consecutiveFailures >= BACKOFF_THRESHOLD) {
|
|
23190
|
+
const multiplier = 2 ** (consecutiveFailures - BACKOFF_THRESHOLD);
|
|
23191
|
+
currentDebounceMs = Math.min(baseDebounceMs * multiplier, MAX_BACKOFF_MS);
|
|
23192
|
+
}
|
|
23193
|
+
} finally {
|
|
23194
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
23195
|
+
try {
|
|
23196
|
+
await recordSession(rootDir, {
|
|
23197
|
+
operation: "watch",
|
|
23198
|
+
title: `Watch cycle for ${paths.inboxDir}${options.repo ? " and tracked repos" : ""}`,
|
|
23199
|
+
startedAt: startedAt.toISOString(),
|
|
23200
|
+
finishedAt: finishedAt.toISOString(),
|
|
23201
|
+
success,
|
|
23202
|
+
error,
|
|
23203
|
+
changedPages,
|
|
23204
|
+
lintFindingCount,
|
|
23205
|
+
lines: [
|
|
23206
|
+
`reasons=${runReasons.join(",") || "none"}`,
|
|
23207
|
+
`code_only=${codeOnlyChange}`,
|
|
23208
|
+
`imported=${importedCount}`,
|
|
23209
|
+
`scanned=${scannedCount}`,
|
|
23210
|
+
`attachments=${attachmentCount}`,
|
|
23211
|
+
`repo_scanned=${repoScannedCount}`,
|
|
23212
|
+
`repo_imported=${repoImportedCount}`,
|
|
23213
|
+
`repo_updated=${repoUpdatedCount}`,
|
|
23214
|
+
`repo_removed=${repoRemovedCount}`,
|
|
23215
|
+
`lint=${lintFindingCount ?? 0}`
|
|
23216
|
+
]
|
|
23217
|
+
});
|
|
23218
|
+
} catch {
|
|
23219
|
+
process3.stderr.write("[swarmvault watch] Failed to record session log.\n");
|
|
23220
|
+
}
|
|
23221
|
+
try {
|
|
23222
|
+
await appendWatchRun(rootDir, {
|
|
23223
|
+
startedAt: startedAt.toISOString(),
|
|
23224
|
+
finishedAt: finishedAt.toISOString(),
|
|
23225
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
23226
|
+
inputDir: paths.inboxDir,
|
|
23227
|
+
reasons: runReasons,
|
|
23228
|
+
importedCount: importedCount + repoImportedCount + repoUpdatedCount,
|
|
23229
|
+
scannedCount: scannedCount + repoScannedCount,
|
|
23230
|
+
attachmentCount,
|
|
23231
|
+
changedPages,
|
|
23232
|
+
repoImportedCount,
|
|
23233
|
+
repoUpdatedCount,
|
|
23234
|
+
repoRemovedCount,
|
|
23235
|
+
repoScannedCount,
|
|
23236
|
+
pendingSemanticRefreshCount,
|
|
23237
|
+
pendingSemanticRefreshPaths,
|
|
23238
|
+
lintFindingCount,
|
|
23239
|
+
success,
|
|
23240
|
+
error
|
|
23241
|
+
});
|
|
23242
|
+
} catch {
|
|
23243
|
+
process3.stderr.write("[swarmvault watch] Failed to append watch run.\n");
|
|
23244
|
+
}
|
|
23245
|
+
try {
|
|
23246
|
+
await writeWatchStatusArtifact(rootDir, {
|
|
23247
|
+
generatedAt: finishedAt.toISOString(),
|
|
23248
|
+
watchedRepoRoots,
|
|
23249
|
+
lastRun: {
|
|
23250
|
+
startedAt: startedAt.toISOString(),
|
|
23251
|
+
finishedAt: finishedAt.toISOString(),
|
|
23252
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
23253
|
+
inputDir: paths.inboxDir,
|
|
23254
|
+
reasons: runReasons,
|
|
23255
|
+
importedCount: importedCount + repoImportedCount + repoUpdatedCount,
|
|
23256
|
+
scannedCount: scannedCount + repoScannedCount,
|
|
23257
|
+
attachmentCount,
|
|
23258
|
+
changedPages,
|
|
23259
|
+
repoImportedCount,
|
|
23260
|
+
repoUpdatedCount,
|
|
23261
|
+
repoRemovedCount,
|
|
23262
|
+
repoScannedCount,
|
|
23263
|
+
pendingSemanticRefreshCount,
|
|
23264
|
+
pendingSemanticRefreshPaths,
|
|
23265
|
+
lintFindingCount,
|
|
23266
|
+
success,
|
|
23267
|
+
error
|
|
23268
|
+
},
|
|
23269
|
+
pendingSemanticRefresh: await readPendingSemanticRefresh(rootDir)
|
|
23270
|
+
});
|
|
23271
|
+
} catch {
|
|
23272
|
+
process3.stderr.write("[swarmvault watch] Failed to write watch status artifact.\n");
|
|
23273
|
+
}
|
|
23274
|
+
running = false;
|
|
23275
|
+
if (pending && !closed) {
|
|
23276
|
+
schedule("queued");
|
|
23277
|
+
}
|
|
23278
|
+
}
|
|
23279
|
+
};
|
|
23280
|
+
const reasonForPath = (targetPath) => {
|
|
23281
|
+
const baseDir = watchTargets.filter((watchTarget) => isPathWithin(watchTarget, path24.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
|
|
23282
|
+
return path24.relative(baseDir, targetPath) || ".";
|
|
23283
|
+
};
|
|
23284
|
+
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)}`));
|
|
23285
|
+
await new Promise((resolve, reject) => {
|
|
23286
|
+
const handleReady = () => {
|
|
23287
|
+
watcher.off("error", handleError);
|
|
23288
|
+
resolve();
|
|
23289
|
+
};
|
|
23290
|
+
const handleError = (caught) => {
|
|
23291
|
+
watcher.off("ready", handleReady);
|
|
23292
|
+
reject(caught);
|
|
23293
|
+
};
|
|
23294
|
+
watcher.once("ready", handleReady);
|
|
23295
|
+
watcher.once("error", handleError);
|
|
23296
|
+
});
|
|
23297
|
+
return {
|
|
23298
|
+
close: async () => {
|
|
23299
|
+
closed = true;
|
|
23300
|
+
if (timer) {
|
|
23301
|
+
clearTimeout(timer);
|
|
23302
|
+
}
|
|
23303
|
+
await watcher.close();
|
|
23304
|
+
await activeCycle;
|
|
23305
|
+
}
|
|
23306
|
+
};
|
|
23307
|
+
}
|
|
23308
|
+
async function getWatchStatus(rootDir) {
|
|
23309
|
+
const persisted = await readWatchStatusArtifact(rootDir);
|
|
23310
|
+
const watchedRepoRoots = await listTrackedRepoRoots(rootDir);
|
|
23311
|
+
const pendingSemanticRefresh = await readPendingSemanticRefresh(rootDir);
|
|
23312
|
+
return {
|
|
23313
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23314
|
+
watchedRepoRoots,
|
|
23315
|
+
lastRun: persisted?.lastRun,
|
|
23316
|
+
pendingSemanticRefresh
|
|
23317
|
+
};
|
|
23318
|
+
}
|
|
23319
|
+
|
|
23320
|
+
// src/mcp.ts
|
|
23321
|
+
var SERVER_VERSION = "0.7.31";
|
|
23322
|
+
async function createMcpServer(rootDir) {
|
|
23323
|
+
const server = new McpServer({
|
|
23324
|
+
name: "swarmvault",
|
|
23325
|
+
version: SERVER_VERSION,
|
|
23326
|
+
websiteUrl: "https://www.swarmvault.ai"
|
|
23327
|
+
});
|
|
23328
|
+
server.registerTool(
|
|
23329
|
+
"workspace_info",
|
|
23330
|
+
{
|
|
23331
|
+
description: "Return the current SwarmVault workspace paths and high-level counts."
|
|
23332
|
+
},
|
|
23333
|
+
safeHandler(async () => {
|
|
23334
|
+
const info = await getWorkspaceInfo(rootDir);
|
|
23335
|
+
return asToolText(info);
|
|
23336
|
+
})
|
|
23337
|
+
);
|
|
23338
|
+
server.registerTool(
|
|
23339
|
+
"search_pages",
|
|
23340
|
+
{
|
|
23341
|
+
description: "Search compiled wiki pages using the local full-text index.",
|
|
23342
|
+
inputSchema: {
|
|
23343
|
+
query: z8.string().min(1).describe("Search query"),
|
|
23344
|
+
limit: z8.number().int().min(1).max(25).optional().describe("Maximum number of results")
|
|
23345
|
+
}
|
|
23346
|
+
},
|
|
23347
|
+
safeHandler(async ({ query, limit }) => {
|
|
23348
|
+
const results = await searchVault(rootDir, query, limit ?? 5);
|
|
23349
|
+
return asToolText(results);
|
|
23350
|
+
})
|
|
23351
|
+
);
|
|
23352
|
+
server.registerTool(
|
|
23353
|
+
"read_page",
|
|
23354
|
+
{
|
|
23355
|
+
description: "Read a generated wiki page by its path relative to wiki/.",
|
|
23356
|
+
inputSchema: {
|
|
23357
|
+
path: z8.string().min(1).describe("Path relative to wiki/, for example sources/example.md")
|
|
23358
|
+
}
|
|
23359
|
+
},
|
|
23360
|
+
safeHandler(async ({ path: relativePath }) => {
|
|
23361
|
+
const page = await readPage(rootDir, relativePath);
|
|
23362
|
+
if (!page) {
|
|
23363
|
+
return asToolError(`Page not found: ${relativePath}`);
|
|
23364
|
+
}
|
|
23365
|
+
return asToolText(page);
|
|
23366
|
+
})
|
|
23367
|
+
);
|
|
23368
|
+
server.registerTool(
|
|
23369
|
+
"list_sources",
|
|
23370
|
+
{
|
|
23371
|
+
description: "List source manifests in the current workspace.",
|
|
23372
|
+
inputSchema: {
|
|
23373
|
+
limit: z8.number().int().min(1).max(100).optional().describe("Maximum number of manifests to return")
|
|
23374
|
+
}
|
|
23375
|
+
},
|
|
23376
|
+
safeHandler(async ({ limit }) => {
|
|
23377
|
+
const manifests = await listManifests(rootDir);
|
|
23378
|
+
return asToolText(limit ? manifests.slice(0, limit) : manifests);
|
|
23379
|
+
})
|
|
23380
|
+
);
|
|
23381
|
+
server.registerTool(
|
|
23382
|
+
"query_graph",
|
|
23383
|
+
{
|
|
23384
|
+
description: "Traverse the local graph from search seeds without calling a model provider.",
|
|
23385
|
+
inputSchema: {
|
|
23386
|
+
question: z8.string().min(1).describe("Question or graph search seed"),
|
|
23387
|
+
traversal: z8.enum(["bfs", "dfs"]).optional().describe("Traversal strategy"),
|
|
23388
|
+
budget: z8.number().int().min(3).max(50).optional().describe("Maximum nodes to summarize")
|
|
23389
|
+
}
|
|
23390
|
+
},
|
|
23391
|
+
safeHandler(async ({ question, traversal, budget }) => {
|
|
23392
|
+
const result = await queryGraphVault(rootDir, question, {
|
|
23393
|
+
traversal,
|
|
23394
|
+
budget
|
|
23395
|
+
});
|
|
23396
|
+
return asToolText(result);
|
|
23397
|
+
})
|
|
23398
|
+
);
|
|
23399
|
+
server.registerTool(
|
|
23400
|
+
"graph_report",
|
|
23401
|
+
{
|
|
23402
|
+
description: "Return the machine-readable graph report and trust artifact."
|
|
23403
|
+
},
|
|
23404
|
+
safeHandler(async () => {
|
|
23405
|
+
return asToolText(await readGraphReport(rootDir) ?? { error: "Graph report not found. Run `swarmvault compile` first." });
|
|
23406
|
+
})
|
|
23407
|
+
);
|
|
23408
|
+
server.registerTool(
|
|
23409
|
+
"get_node",
|
|
23410
|
+
{
|
|
23411
|
+
description: "Explain a graph node, its page, community, neighbors, and group patterns.",
|
|
23412
|
+
inputSchema: {
|
|
23413
|
+
target: z8.string().min(1).describe("Node or page label/id")
|
|
23414
|
+
}
|
|
23415
|
+
},
|
|
23416
|
+
safeHandler(async ({ target }) => {
|
|
23417
|
+
return asToolText(await explainGraphVault(rootDir, target));
|
|
23418
|
+
})
|
|
23419
|
+
);
|
|
23420
|
+
server.registerTool(
|
|
23421
|
+
"get_hyperedges",
|
|
23422
|
+
{
|
|
23423
|
+
description: "List graph hyperedges, optionally filtered to a node or page target.",
|
|
23424
|
+
inputSchema: {
|
|
23425
|
+
target: z8.string().optional().describe("Optional node/page label or id to filter by"),
|
|
23426
|
+
limit: z8.number().int().min(1).max(50).optional().describe("Maximum hyperedges to return")
|
|
23427
|
+
}
|
|
23428
|
+
},
|
|
23429
|
+
safeHandler(async ({ target, limit }) => {
|
|
23430
|
+
return asToolText(await listGraphHyperedges(rootDir, target, limit ?? 25));
|
|
23431
|
+
})
|
|
23432
|
+
);
|
|
23433
|
+
server.registerTool(
|
|
23434
|
+
"get_neighbors",
|
|
23435
|
+
{
|
|
23436
|
+
description: "Return the neighbors of a graph node or page target.",
|
|
23437
|
+
inputSchema: {
|
|
23438
|
+
target: z8.string().min(1).describe("Node or page label/id")
|
|
23439
|
+
}
|
|
23440
|
+
},
|
|
23441
|
+
safeHandler(async ({ target }) => {
|
|
22600
23442
|
const explanation = await explainGraphVault(rootDir, target);
|
|
22601
23443
|
return asToolText(explanation.neighbors);
|
|
22602
23444
|
})
|
|
@@ -22695,6 +23537,106 @@ async function createMcpServer(rootDir) {
|
|
|
22695
23537
|
return asToolText(findings);
|
|
22696
23538
|
})
|
|
22697
23539
|
);
|
|
23540
|
+
server.registerTool(
|
|
23541
|
+
"list_approvals",
|
|
23542
|
+
{
|
|
23543
|
+
description: "List staged approval bundles awaiting review."
|
|
23544
|
+
},
|
|
23545
|
+
safeHandler(async () => {
|
|
23546
|
+
const approvals = await listApprovals(rootDir);
|
|
23547
|
+
return asToolText(approvals);
|
|
23548
|
+
})
|
|
23549
|
+
);
|
|
23550
|
+
server.registerTool(
|
|
23551
|
+
"read_approval",
|
|
23552
|
+
{
|
|
23553
|
+
description: "Read the details and structured diffs for an approval bundle.",
|
|
23554
|
+
inputSchema: {
|
|
23555
|
+
approvalId: z8.string().min(1).describe("Approval bundle id"),
|
|
23556
|
+
diff: z8.boolean().optional().describe("Include the textual unified diff alongside the structured diff")
|
|
23557
|
+
}
|
|
23558
|
+
},
|
|
23559
|
+
safeHandler(async ({ approvalId, diff }) => {
|
|
23560
|
+
const result = await readApproval(rootDir, approvalId, { diff: diff ?? true });
|
|
23561
|
+
return asToolText(result);
|
|
23562
|
+
})
|
|
23563
|
+
);
|
|
23564
|
+
server.registerTool(
|
|
23565
|
+
"promote_candidate",
|
|
23566
|
+
{
|
|
23567
|
+
description: "Promote a staged candidate into its active concept or entity page.",
|
|
23568
|
+
inputSchema: {
|
|
23569
|
+
target: z8.string().min(1).describe("Candidate page id or wiki/candidates path")
|
|
23570
|
+
}
|
|
23571
|
+
},
|
|
23572
|
+
safeHandler(async ({ target }) => {
|
|
23573
|
+
const result = await promoteCandidate(rootDir, target);
|
|
23574
|
+
return asToolText(result);
|
|
23575
|
+
})
|
|
23576
|
+
);
|
|
23577
|
+
server.registerTool(
|
|
23578
|
+
"archive_candidate",
|
|
23579
|
+
{
|
|
23580
|
+
description: "Archive a staged candidate without promoting it.",
|
|
23581
|
+
inputSchema: {
|
|
23582
|
+
target: z8.string().min(1).describe("Candidate page id or wiki/candidates path")
|
|
23583
|
+
}
|
|
23584
|
+
},
|
|
23585
|
+
safeHandler(async ({ target }) => {
|
|
23586
|
+
const result = await archiveCandidate(rootDir, target);
|
|
23587
|
+
return asToolText(result);
|
|
23588
|
+
})
|
|
23589
|
+
);
|
|
23590
|
+
server.registerTool(
|
|
23591
|
+
"preview_candidate_scores",
|
|
23592
|
+
{
|
|
23593
|
+
description: "Score staged candidates against the configured auto-promotion rules without promoting."
|
|
23594
|
+
},
|
|
23595
|
+
safeHandler(async () => {
|
|
23596
|
+
const decisions = await previewCandidatePromotions(rootDir);
|
|
23597
|
+
return asToolText(decisions);
|
|
23598
|
+
})
|
|
23599
|
+
);
|
|
23600
|
+
server.registerTool(
|
|
23601
|
+
"auto_promote_candidates",
|
|
23602
|
+
{
|
|
23603
|
+
description: "Apply configured auto-promotion rules to staged candidates. Requires candidate.autoPromote.enabled in config.",
|
|
23604
|
+
inputSchema: {
|
|
23605
|
+
dryRun: z8.boolean().optional().describe("Score candidates without moving files")
|
|
23606
|
+
}
|
|
23607
|
+
},
|
|
23608
|
+
safeHandler(async ({ dryRun }) => {
|
|
23609
|
+
const result = await runAutoPromotion(rootDir, { dryRun: dryRun ?? false });
|
|
23610
|
+
return asToolText(result);
|
|
23611
|
+
})
|
|
23612
|
+
);
|
|
23613
|
+
server.registerTool(
|
|
23614
|
+
"review_decision",
|
|
23615
|
+
{
|
|
23616
|
+
description: "Accept or reject approval bundle entries from a staged compile.",
|
|
23617
|
+
inputSchema: {
|
|
23618
|
+
approvalId: z8.string().min(1).describe("Approval bundle id as reported by list_approvals or read_approval"),
|
|
23619
|
+
decision: z8.enum(["accept", "reject"]).describe("Action to apply to the selected entries"),
|
|
23620
|
+
targets: z8.array(z8.string()).optional().describe("Specific entry page ids to act on (defaults to all pending)"),
|
|
23621
|
+
notes: z8.string().optional().describe("Free-form reviewer notes, surfaced in the session log")
|
|
23622
|
+
}
|
|
23623
|
+
},
|
|
23624
|
+
safeHandler(async ({ approvalId, decision, targets, notes }) => {
|
|
23625
|
+
const apply = decision === "accept" ? acceptApproval : rejectApproval;
|
|
23626
|
+
const result = await apply(rootDir, approvalId, targets ?? []);
|
|
23627
|
+
return asToolText({ ...result, notes });
|
|
23628
|
+
})
|
|
23629
|
+
);
|
|
23630
|
+
server.registerTool(
|
|
23631
|
+
"watch_status",
|
|
23632
|
+
{
|
|
23633
|
+
description: "Return the current watch-mode status: watched repos, last run summary, and pending semantic refreshes."
|
|
23634
|
+
},
|
|
23635
|
+
safeHandler(async () => {
|
|
23636
|
+
const status = await getWatchStatus(rootDir);
|
|
23637
|
+
return asToolText(status);
|
|
23638
|
+
})
|
|
23639
|
+
);
|
|
22698
23640
|
server.registerResource(
|
|
22699
23641
|
"swarmvault-config",
|
|
22700
23642
|
"swarmvault://config",
|
|
@@ -22761,7 +23703,7 @@ async function createMcpServer(rootDir) {
|
|
|
22761
23703
|
},
|
|
22762
23704
|
async () => {
|
|
22763
23705
|
const { paths } = await loadVaultConfig(rootDir);
|
|
22764
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
23706
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path25.relative(paths.sessionsDir, filePath))).sort();
|
|
22765
23707
|
return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
|
|
22766
23708
|
}
|
|
22767
23709
|
);
|
|
@@ -22794,7 +23736,7 @@ async function createMcpServer(rootDir) {
|
|
|
22794
23736
|
return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
|
|
22795
23737
|
}
|
|
22796
23738
|
const { paths } = await loadVaultConfig(rootDir);
|
|
22797
|
-
const absolutePath =
|
|
23739
|
+
const absolutePath = path25.resolve(paths.wikiDir, relativePath);
|
|
22798
23740
|
return asTextResource(`swarmvault://pages/${encodedPath}`, await fs20.readFile(absolutePath, "utf8"));
|
|
22799
23741
|
}
|
|
22800
23742
|
);
|
|
@@ -22803,11 +23745,11 @@ async function createMcpServer(rootDir) {
|
|
|
22803
23745
|
new ResourceTemplate("swarmvault://sessions/{path}", {
|
|
22804
23746
|
list: async () => {
|
|
22805
23747
|
const { paths } = await loadVaultConfig(rootDir);
|
|
22806
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
23748
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path25.relative(paths.sessionsDir, filePath))).sort();
|
|
22807
23749
|
return {
|
|
22808
23750
|
resources: files.map((relativePath) => ({
|
|
22809
23751
|
uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
|
|
22810
|
-
name:
|
|
23752
|
+
name: path25.basename(relativePath, ".md"),
|
|
22811
23753
|
title: relativePath,
|
|
22812
23754
|
description: "SwarmVault session artifact",
|
|
22813
23755
|
mimeType: "text/markdown"
|
|
@@ -22824,7 +23766,7 @@ async function createMcpServer(rootDir) {
|
|
|
22824
23766
|
const { paths } = await loadVaultConfig(rootDir);
|
|
22825
23767
|
const encodedPath = typeof variables.path === "string" ? variables.path : "";
|
|
22826
23768
|
const relativePath = decodeURIComponent(encodedPath);
|
|
22827
|
-
const absolutePath =
|
|
23769
|
+
const absolutePath = path25.resolve(paths.sessionsDir, relativePath);
|
|
22828
23770
|
if (!isPathWithin(paths.sessionsDir, absolutePath) || !await fileExists(absolutePath)) {
|
|
22829
23771
|
return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
|
|
22830
23772
|
}
|
|
@@ -22888,12 +23830,12 @@ function asTextResource(uri, text) {
|
|
|
22888
23830
|
|
|
22889
23831
|
// src/schedule.ts
|
|
22890
23832
|
import fs21 from "fs/promises";
|
|
22891
|
-
import
|
|
23833
|
+
import path26 from "path";
|
|
22892
23834
|
function scheduleStatePath(schedulesDir, jobId) {
|
|
22893
|
-
return
|
|
23835
|
+
return path26.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
22894
23836
|
}
|
|
22895
23837
|
function scheduleLockPath(schedulesDir, jobId) {
|
|
22896
|
-
return
|
|
23838
|
+
return path26.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
|
|
22897
23839
|
}
|
|
22898
23840
|
function parseEveryDuration(value) {
|
|
22899
23841
|
const match = value.trim().match(/^(\d+)(m|h|d)$/i);
|
|
@@ -23160,7 +24102,7 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
|
|
|
23160
24102
|
// src/sources.ts
|
|
23161
24103
|
import { spawn as spawn2 } from "child_process";
|
|
23162
24104
|
import fs22 from "fs/promises";
|
|
23163
|
-
import
|
|
24105
|
+
import path27 from "path";
|
|
23164
24106
|
import matter11 from "gray-matter";
|
|
23165
24107
|
import { JSDOM as JSDOM3 } from "jsdom";
|
|
23166
24108
|
var DEFAULT_CRAWL_MAX_PAGES = 12;
|
|
@@ -23206,24 +24148,24 @@ function emptyManagedSourceSyncCounts() {
|
|
|
23206
24148
|
};
|
|
23207
24149
|
}
|
|
23208
24150
|
function withinRoot2(rootPath, targetPath) {
|
|
23209
|
-
const relative =
|
|
23210
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
24151
|
+
const relative = path27.relative(rootPath, targetPath);
|
|
24152
|
+
return relative === "" || !relative.startsWith("..") && !path27.isAbsolute(relative);
|
|
23211
24153
|
}
|
|
23212
24154
|
async function findNearestGitRoot3(startPath) {
|
|
23213
|
-
let current =
|
|
24155
|
+
let current = path27.resolve(startPath);
|
|
23214
24156
|
try {
|
|
23215
24157
|
const stat = await fs22.stat(current);
|
|
23216
24158
|
if (!stat.isDirectory()) {
|
|
23217
|
-
current =
|
|
24159
|
+
current = path27.dirname(current);
|
|
23218
24160
|
}
|
|
23219
24161
|
} catch {
|
|
23220
|
-
current =
|
|
24162
|
+
current = path27.dirname(current);
|
|
23221
24163
|
}
|
|
23222
24164
|
while (true) {
|
|
23223
|
-
if (await fileExists(
|
|
24165
|
+
if (await fileExists(path27.join(current, ".git"))) {
|
|
23224
24166
|
return current;
|
|
23225
24167
|
}
|
|
23226
|
-
const parent =
|
|
24168
|
+
const parent = path27.dirname(current);
|
|
23227
24169
|
if (parent === current) {
|
|
23228
24170
|
return null;
|
|
23229
24171
|
}
|
|
@@ -23297,7 +24239,7 @@ function isAllowedDocsCandidate(candidate, startUrl) {
|
|
|
23297
24239
|
if (candidate.origin !== startUrl.origin) {
|
|
23298
24240
|
return false;
|
|
23299
24241
|
}
|
|
23300
|
-
const extension =
|
|
24242
|
+
const extension = path27.extname(candidate.pathname).toLowerCase();
|
|
23301
24243
|
if (extension && extension !== ".html" && extension !== ".htm" && extension !== ".md") {
|
|
23302
24244
|
return false;
|
|
23303
24245
|
}
|
|
@@ -23386,12 +24328,12 @@ function matchesManagedSourceSpec(existing, input) {
|
|
|
23386
24328
|
return false;
|
|
23387
24329
|
}
|
|
23388
24330
|
if (input.kind === "directory" || input.kind === "file") {
|
|
23389
|
-
return
|
|
24331
|
+
return path27.resolve(existing.path ?? "") === path27.resolve(input.path);
|
|
23390
24332
|
}
|
|
23391
24333
|
return (existing.url ?? "") === input.url;
|
|
23392
24334
|
}
|
|
23393
24335
|
async function resolveManagedSourceInput(rootDir, input) {
|
|
23394
|
-
const absoluteInput =
|
|
24336
|
+
const absoluteInput = path27.resolve(rootDir, input);
|
|
23395
24337
|
if (!(input.startsWith("http://") || input.startsWith("https://"))) {
|
|
23396
24338
|
const stat = await fs22.stat(absoluteInput).catch(() => null);
|
|
23397
24339
|
if (!stat) {
|
|
@@ -23401,7 +24343,7 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
23401
24343
|
return {
|
|
23402
24344
|
kind: "file",
|
|
23403
24345
|
path: absoluteInput,
|
|
23404
|
-
title:
|
|
24346
|
+
title: path27.basename(absoluteInput, path27.extname(absoluteInput)) || absoluteInput
|
|
23405
24347
|
};
|
|
23406
24348
|
}
|
|
23407
24349
|
if (!stat.isDirectory()) {
|
|
@@ -23413,7 +24355,7 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
23413
24355
|
kind: "directory",
|
|
23414
24356
|
path: absoluteInput,
|
|
23415
24357
|
repoRoot,
|
|
23416
|
-
title:
|
|
24358
|
+
title: path27.basename(absoluteInput) || absoluteInput
|
|
23417
24359
|
};
|
|
23418
24360
|
}
|
|
23419
24361
|
const github = normalizeGitHubRepoRootUrl(input);
|
|
@@ -23436,16 +24378,16 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
23436
24378
|
};
|
|
23437
24379
|
}
|
|
23438
24380
|
function directorySourceIdsFor(manifests, inputPath) {
|
|
23439
|
-
return manifests.filter((manifest) => manifest.originalPath && withinRoot2(
|
|
24381
|
+
return manifests.filter((manifest) => manifest.originalPath && withinRoot2(path27.resolve(inputPath), path27.resolve(manifest.originalPath))).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
|
|
23440
24382
|
}
|
|
23441
24383
|
function fileSourceIdsFor(manifests, inputPath) {
|
|
23442
|
-
const absoluteInput =
|
|
23443
|
-
return manifests.filter((manifest) => manifest.originalPath &&
|
|
24384
|
+
const absoluteInput = path27.resolve(inputPath);
|
|
24385
|
+
return manifests.filter((manifest) => manifest.originalPath && path27.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
|
|
23444
24386
|
}
|
|
23445
24387
|
async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
23446
24388
|
const manifestsBefore = await listManifests(rootDir);
|
|
23447
24389
|
const previousInScope = manifestsBefore.filter(
|
|
23448
|
-
(manifest) => manifest.originalPath && withinRoot2(
|
|
24390
|
+
(manifest) => manifest.originalPath && withinRoot2(path27.resolve(inputPath), path27.resolve(manifest.originalPath))
|
|
23449
24391
|
);
|
|
23450
24392
|
const result = await ingestDirectory(rootDir, inputPath, { repoRoot });
|
|
23451
24393
|
const removed = [];
|
|
@@ -23453,7 +24395,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
|
23453
24395
|
if (!manifest.originalPath) {
|
|
23454
24396
|
continue;
|
|
23455
24397
|
}
|
|
23456
|
-
if (await fileExists(
|
|
24398
|
+
if (await fileExists(path27.resolve(manifest.originalPath))) {
|
|
23457
24399
|
continue;
|
|
23458
24400
|
}
|
|
23459
24401
|
const removedManifest = await removeManifestBySourceId(rootDir, manifest.sourceId);
|
|
@@ -23463,7 +24405,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
|
23463
24405
|
}
|
|
23464
24406
|
const manifestsAfter = await listManifests(rootDir);
|
|
23465
24407
|
return {
|
|
23466
|
-
title:
|
|
24408
|
+
title: path27.basename(inputPath) || inputPath,
|
|
23467
24409
|
sourceIds: directorySourceIdsFor(manifestsAfter, inputPath),
|
|
23468
24410
|
counts: {
|
|
23469
24411
|
scannedCount: result.scannedCount,
|
|
@@ -23479,7 +24421,7 @@ async function syncFileSource(rootDir, inputPath) {
|
|
|
23479
24421
|
const result = await ingestInputDetailed(rootDir, inputPath);
|
|
23480
24422
|
const manifestsAfter = await listManifests(rootDir);
|
|
23481
24423
|
return {
|
|
23482
|
-
title:
|
|
24424
|
+
title: path27.basename(inputPath, path27.extname(inputPath)) || inputPath,
|
|
23483
24425
|
sourceIds: fileSourceIdsFor(manifestsAfter, inputPath),
|
|
23484
24426
|
counts: {
|
|
23485
24427
|
scannedCount: result.scannedCount,
|
|
@@ -23513,7 +24455,7 @@ async function runGitCommand(cwd, args) {
|
|
|
23513
24455
|
}
|
|
23514
24456
|
async function syncGitHubRepoSource(rootDir, entry) {
|
|
23515
24457
|
const workingDir = await managedSourceWorkingDir(rootDir, entry.id);
|
|
23516
|
-
const checkoutDir =
|
|
24458
|
+
const checkoutDir = path27.join(workingDir, "checkout");
|
|
23517
24459
|
await fs22.rm(checkoutDir, { recursive: true, force: true });
|
|
23518
24460
|
await ensureDir(workingDir);
|
|
23519
24461
|
if (!entry.url) {
|
|
@@ -23644,7 +24586,7 @@ function scopedNodeIds(graph, sourceIds) {
|
|
|
23644
24586
|
async function loadSourceAnalyses(rootDir, sourceIds) {
|
|
23645
24587
|
const { paths } = await loadVaultConfig(rootDir);
|
|
23646
24588
|
const analyses = await Promise.all(
|
|
23647
|
-
sourceIds.map(async (sourceId) => await readJsonFile(
|
|
24589
|
+
sourceIds.map(async (sourceId) => await readJsonFile(path27.join(paths.analysesDir, `${sourceId}.json`)))
|
|
23648
24590
|
);
|
|
23649
24591
|
return analyses.filter((analysis) => Boolean(analysis?.sourceId));
|
|
23650
24592
|
}
|
|
@@ -23805,8 +24747,8 @@ async function writeSourceBriefForScope(rootDir, source) {
|
|
|
23805
24747
|
confidence: 0.82
|
|
23806
24748
|
}
|
|
23807
24749
|
});
|
|
23808
|
-
const absolutePath =
|
|
23809
|
-
await ensureDir(
|
|
24750
|
+
const absolutePath = path27.join(paths.wikiDir, output.page.path);
|
|
24751
|
+
await ensureDir(path27.dirname(absolutePath));
|
|
23810
24752
|
await fs22.writeFile(absolutePath, output.content, "utf8");
|
|
23811
24753
|
return absolutePath;
|
|
23812
24754
|
}
|
|
@@ -24095,7 +25037,7 @@ function selectGuidedTargetPages(scope, sourcePages, questions) {
|
|
|
24095
25037
|
return (matchedTargets.length ? matchedTargets : canonicalPages).slice(0, 6);
|
|
24096
25038
|
}
|
|
24097
25039
|
function insightRelativePathForTarget(page, scope) {
|
|
24098
|
-
const basename =
|
|
25040
|
+
const basename = path27.basename(page.path);
|
|
24099
25041
|
if (page.kind === "concept") {
|
|
24100
25042
|
return `insights/concepts/${basename}`;
|
|
24101
25043
|
}
|
|
@@ -24251,191 +25193,36 @@ Entities: ${analysis.entities.map((entity) => entity.name).join(", ") || "none"}
|
|
|
24251
25193
|
"",
|
|
24252
25194
|
"Analyses:",
|
|
24253
25195
|
analysisContext || "No analysis context available.",
|
|
24254
|
-
"",
|
|
24255
|
-
"Deterministic fallback draft:",
|
|
24256
|
-
fallback
|
|
24257
|
-
].join("\n")
|
|
24258
|
-
});
|
|
24259
|
-
return response.text?.trim() ? response.text.trim() : fallback;
|
|
24260
|
-
} catch {
|
|
24261
|
-
return fallback;
|
|
24262
|
-
}
|
|
24263
|
-
}
|
|
24264
|
-
async function buildSourceGuideStagedPage(rootDir, scope) {
|
|
24265
|
-
const { config, paths } = await loadVaultConfig(rootDir);
|
|
24266
|
-
const markdown = await generateSourceGuideMarkdown(rootDir, scope);
|
|
24267
|
-
if (!markdown) {
|
|
24268
|
-
throw new Error(`Could not generate a source guide for ${scope.id}.`);
|
|
24269
|
-
}
|
|
24270
|
-
const graph = await readJsonFile(paths.graphPath);
|
|
24271
|
-
const schemas = await loadVaultSchemas(rootDir);
|
|
24272
|
-
const scopeManifests = manifestsForScope(graph, scope);
|
|
24273
|
-
const relatedPages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
|
|
24274
|
-
const contradictions = findContradictionsForScope(scope, await readGraphReport(rootDir));
|
|
24275
|
-
const selectedTargets = selectGuidedTargetPages(scope, relatedPages, defaultGuidedSessionQuestions());
|
|
24276
|
-
const relatedPageIds = relatedPages.slice(0, 18).map((page) => page.id);
|
|
24277
|
-
const relatedNodeIds = graph ? scopedNodeIds(graph, scope.sourceIds).slice(0, 28) : [];
|
|
24278
|
-
const projectIds = uniqueStrings4(relatedPages.flatMap((page) => page.projectIds));
|
|
24279
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24280
|
-
const output = buildOutputPage({
|
|
24281
|
-
title: `Source Guide: ${scope.title}`,
|
|
24282
|
-
question: `Guide ${scope.title}`,
|
|
24283
|
-
answer: markdown,
|
|
24284
|
-
citations: scope.sourceIds,
|
|
24285
|
-
schemaHash: sourceOutputSchemaHash(schemas, projectIds),
|
|
24286
|
-
outputFormat: "report",
|
|
24287
|
-
relatedPageIds,
|
|
24288
|
-
relatedNodeIds,
|
|
24289
|
-
relatedSourceIds: scope.sourceIds,
|
|
24290
|
-
projectIds,
|
|
24291
|
-
extraTags: ["source-guide", "guided-ingest"],
|
|
24292
|
-
origin: "source_guide",
|
|
24293
|
-
slug: `source-guides/${scope.id}`,
|
|
24294
|
-
metadata: {
|
|
24295
|
-
status: "draft",
|
|
24296
|
-
createdAt: now,
|
|
24297
|
-
updatedAt: now,
|
|
24298
|
-
compiledFrom: scope.sourceIds,
|
|
24299
|
-
managedBy: "system",
|
|
24300
|
-
confidence: 0.8
|
|
24301
|
-
},
|
|
24302
|
-
frontmatter: {
|
|
24303
|
-
profile_presets: config.profile.presets,
|
|
24304
|
-
source_type: scopeSourceType(scope, scopeManifests),
|
|
24305
|
-
occurred_at: scopeOccurredAt(scopeManifests),
|
|
24306
|
-
participants: scopeParticipants(scopeManifests),
|
|
24307
|
-
container_title: scopeContainerTitle(scopeManifests),
|
|
24308
|
-
conversation_id: scopeConversationId(scopeManifests),
|
|
24309
|
-
question_state: "answered",
|
|
24310
|
-
canonical_targets: selectedTargets.map((page) => page.path),
|
|
24311
|
-
evidence_state: contradictions.length ? "conflicting" : selectedTargets.some((page) => page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId))) ? "reinforcing" : selectedTargets.length ? "new" : "needs_judgment"
|
|
24312
|
-
}
|
|
24313
|
-
});
|
|
24314
|
-
return { page: output.page, content: output.content };
|
|
24315
|
-
}
|
|
24316
|
-
async function stageSourceReviewForScope(rootDir, scope) {
|
|
24317
|
-
const output = await buildSourceReviewStagedPage(rootDir, scope);
|
|
24318
|
-
const approval = await stageGeneratedOutputPages(rootDir, [{ page: output.page, content: output.content, label: "source-review" }], {
|
|
24319
|
-
bundleType: "source-review",
|
|
24320
|
-
title: `Source Review: ${scope.title}`
|
|
24321
|
-
});
|
|
24322
|
-
return {
|
|
24323
|
-
sourceId: scope.id,
|
|
24324
|
-
pageId: output.page.id,
|
|
24325
|
-
reviewPath: path26.join(approval.approvalDir, "wiki", output.page.path),
|
|
24326
|
-
staged: true,
|
|
24327
|
-
approvalId: approval.approvalId,
|
|
24328
|
-
approvalDir: approval.approvalDir
|
|
24329
|
-
};
|
|
24330
|
-
}
|
|
24331
|
-
function nextGuidedSourceSessionId(scope) {
|
|
24332
|
-
return `source-session-${slugify(scope.id)}-${sha256(`${scope.id}:${(/* @__PURE__ */ new Date()).toISOString()}`).slice(0, 8)}`;
|
|
24333
|
-
}
|
|
24334
|
-
function shouldReuseGuidedSourceSession(session) {
|
|
24335
|
-
return Boolean(session && session.status === "awaiting_input");
|
|
24336
|
-
}
|
|
24337
|
-
function questionAnswer(questions, id, fallback) {
|
|
24338
|
-
return normalizeGuidedAnswerValue(questions.find((question) => question.id === id)?.answer) ?? fallback;
|
|
24339
|
-
}
|
|
24340
|
-
async function prepareGuidedSourceSession(rootDir, scope, answers) {
|
|
24341
|
-
const existing = await findLatestGuidedSourceSessionByScope(rootDir, scope.id);
|
|
24342
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24343
|
-
const session = shouldReuseGuidedSourceSession(existing) ? {
|
|
24344
|
-
...existing,
|
|
24345
|
-
scopeTitle: scope.title,
|
|
24346
|
-
sourceIds: scope.sourceIds,
|
|
24347
|
-
kind: scope.kind,
|
|
24348
|
-
questions: mergeGuidedSessionQuestions(existing.questions, answers),
|
|
24349
|
-
updatedAt: now
|
|
24350
|
-
} : {
|
|
24351
|
-
sessionId: nextGuidedSourceSessionId(scope),
|
|
24352
|
-
scopeId: scope.id,
|
|
24353
|
-
scopeTitle: scope.title,
|
|
24354
|
-
sourceIds: scope.sourceIds,
|
|
24355
|
-
kind: scope.kind,
|
|
24356
|
-
status: "awaiting_input",
|
|
24357
|
-
createdAt: now,
|
|
24358
|
-
updatedAt: now,
|
|
24359
|
-
questions: mergeGuidedSessionQuestions(defaultGuidedSessionQuestions(), answers),
|
|
24360
|
-
briefPath: scope.briefPath,
|
|
24361
|
-
targetedPagePaths: [],
|
|
24362
|
-
stagedUpdatePaths: []
|
|
24363
|
-
};
|
|
24364
|
-
const statePath = await guidedSourceSessionStatePath(rootDir, session.sessionId);
|
|
24365
|
-
return { session, statePath };
|
|
24366
|
-
}
|
|
24367
|
-
async function buildSourceSessionSavedPage(rootDir, scope, session) {
|
|
24368
|
-
const { config, paths } = await loadVaultConfig(rootDir);
|
|
24369
|
-
let graph = await readJsonFile(paths.graphPath);
|
|
24370
|
-
if (!graph) {
|
|
24371
|
-
await compileVault(rootDir, {});
|
|
24372
|
-
graph = await readJsonFile(paths.graphPath);
|
|
24373
|
-
}
|
|
24374
|
-
const schemas = await loadVaultSchemas(rootDir);
|
|
24375
|
-
const scopeManifests = manifestsForScope(graph, scope);
|
|
24376
|
-
const sourcePages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
|
|
24377
|
-
const analyses = await loadSourceAnalyses(rootDir, scope.sourceIds);
|
|
24378
|
-
const report = await readGraphReport(rootDir);
|
|
24379
|
-
const contradictions = findContradictionsForScope(scope, report);
|
|
24380
|
-
const relatedPageIds = uniqueStrings4([
|
|
24381
|
-
...sourcePages.slice(0, 18).map((page) => page.id),
|
|
24382
|
-
...session.targetedPagePaths.map((relativePath) => {
|
|
24383
|
-
const page = graph?.pages.find((candidate) => candidate.path === relativePath);
|
|
24384
|
-
return page?.id ?? "";
|
|
24385
|
-
})
|
|
24386
|
-
]);
|
|
24387
|
-
const relatedNodeIds = graph ? scopedNodeIds(graph, scope.sourceIds).slice(0, 28) : [];
|
|
24388
|
-
const projectIds = uniqueStrings4(sourcePages.flatMap((page) => page.projectIds));
|
|
24389
|
-
const evidenceState = contradictions.length > 0 ? "conflicting" : session.targetedPagePaths.some(
|
|
24390
|
-
(targetPath) => sourcePages.some((page) => page.path === targetPath && page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId)))
|
|
24391
|
-
) ? "reinforcing" : session.targetedPagePaths.length ? "new" : "needs_judgment";
|
|
24392
|
-
const relativeBriefPath = session.briefPath && path26.isAbsolute(session.briefPath) ? path26.relative(paths.wikiDir, session.briefPath) : session.briefPath;
|
|
24393
|
-
const sessionMarkdown = [
|
|
24394
|
-
`# Guided Session: ${scope.title}`,
|
|
24395
|
-
"",
|
|
24396
|
-
`Status: \`${session.status}\``,
|
|
24397
|
-
`Session ID: \`${session.sessionId}\``,
|
|
24398
|
-
...session.approvalId ? [`Approval Bundle: \`${session.approvalId}\``] : [],
|
|
24399
|
-
...relativeBriefPath ? [`Brief: \`${relativeBriefPath}\``] : [],
|
|
24400
|
-
"",
|
|
24401
|
-
"## What This Source Is",
|
|
24402
|
-
"",
|
|
24403
|
-
...analyses.length ? analyses.slice(0, 6).map((analysis) => `- ${analysis.title}: ${analysis.summary}`) : ["- Awaiting compile context."],
|
|
24404
|
-
"",
|
|
24405
|
-
"## Guided Questions",
|
|
24406
|
-
"",
|
|
24407
|
-
...session.questions.flatMap((question) => [`### ${question.prompt}`, "", question.answer ?? "_Awaiting input._", ""]),
|
|
24408
|
-
"## Proposed Wiki Targets",
|
|
24409
|
-
"",
|
|
24410
|
-
...session.targetedPagePaths.length ? session.targetedPagePaths.map((targetPath) => `- [[${targetPath.replace(/\.md$/, "")}]]`) : ["- No canonical update targets selected yet."],
|
|
24411
|
-
"",
|
|
24412
|
-
"## Conflicts And Judgment Calls",
|
|
24413
|
-
"",
|
|
24414
|
-
...contradictions.length ? contradictions.map((contradiction) => `- ${contradiction.claimA} / ${contradiction.claimB}`) : ["- No contradictions are currently flagged for this source scope."],
|
|
24415
|
-
"",
|
|
24416
|
-
"## Follow-up Questions",
|
|
24417
|
-
"",
|
|
24418
|
-
...(() => {
|
|
24419
|
-
const followups = questionAnswer(session.questions, "followups", "");
|
|
24420
|
-
if (followups) {
|
|
24421
|
-
return followups.split(/\n+/).map((line) => line.trim()).filter(Boolean).map((line) => `- ${line.replace(/^-+\s*/, "")}`);
|
|
24422
|
-
}
|
|
24423
|
-
const analysisQuestions = uniqueStrings4(analyses.flatMap((analysis) => analysis.questions)).slice(0, 6);
|
|
24424
|
-
return analysisQuestions.length ? analysisQuestions.map((question) => `- ${question}`) : ["- No follow-up questions recorded yet."];
|
|
24425
|
-
})(),
|
|
24426
|
-
"",
|
|
24427
|
-
"## Related Artifacts",
|
|
24428
|
-
"",
|
|
24429
|
-
`- [[outputs/source-briefs/${scope.id}|Source Brief]]`,
|
|
24430
|
-
`- [[outputs/source-reviews/${scope.id}|Source Review]]`,
|
|
24431
|
-
`- [[outputs/source-guides/${scope.id}|Source Guide]]`,
|
|
24432
|
-
""
|
|
24433
|
-
].join("\n");
|
|
25196
|
+
"",
|
|
25197
|
+
"Deterministic fallback draft:",
|
|
25198
|
+
fallback
|
|
25199
|
+
].join("\n")
|
|
25200
|
+
});
|
|
25201
|
+
return response.text?.trim() ? response.text.trim() : fallback;
|
|
25202
|
+
} catch {
|
|
25203
|
+
return fallback;
|
|
25204
|
+
}
|
|
25205
|
+
}
|
|
25206
|
+
async function buildSourceGuideStagedPage(rootDir, scope) {
|
|
25207
|
+
const { config, paths } = await loadVaultConfig(rootDir);
|
|
25208
|
+
const markdown = await generateSourceGuideMarkdown(rootDir, scope);
|
|
25209
|
+
if (!markdown) {
|
|
25210
|
+
throw new Error(`Could not generate a source guide for ${scope.id}.`);
|
|
25211
|
+
}
|
|
25212
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
25213
|
+
const schemas = await loadVaultSchemas(rootDir);
|
|
25214
|
+
const scopeManifests = manifestsForScope(graph, scope);
|
|
25215
|
+
const relatedPages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
|
|
25216
|
+
const contradictions = findContradictionsForScope(scope, await readGraphReport(rootDir));
|
|
25217
|
+
const selectedTargets = selectGuidedTargetPages(scope, relatedPages, defaultGuidedSessionQuestions());
|
|
25218
|
+
const relatedPageIds = relatedPages.slice(0, 18).map((page) => page.id);
|
|
25219
|
+
const relatedNodeIds = graph ? scopedNodeIds(graph, scope.sourceIds).slice(0, 28) : [];
|
|
25220
|
+
const projectIds = uniqueStrings4(relatedPages.flatMap((page) => page.projectIds));
|
|
24434
25221
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24435
25222
|
const output = buildOutputPage({
|
|
24436
|
-
title: `
|
|
24437
|
-
question: `
|
|
24438
|
-
answer:
|
|
25223
|
+
title: `Source Guide: ${scope.title}`,
|
|
25224
|
+
question: `Guide ${scope.title}`,
|
|
25225
|
+
answer: markdown,
|
|
24439
25226
|
citations: scope.sourceIds,
|
|
24440
25227
|
schemaHash: sourceOutputSchemaHash(schemas, projectIds),
|
|
24441
25228
|
outputFormat: "report",
|
|
@@ -24443,1123 +25230,787 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
|
|
|
24443
25230
|
relatedNodeIds,
|
|
24444
25231
|
relatedSourceIds: scope.sourceIds,
|
|
24445
25232
|
projectIds,
|
|
24446
|
-
extraTags: ["source-
|
|
24447
|
-
origin: "
|
|
24448
|
-
slug: `source-
|
|
25233
|
+
extraTags: ["source-guide", "guided-ingest"],
|
|
25234
|
+
origin: "source_guide",
|
|
25235
|
+
slug: `source-guides/${scope.id}`,
|
|
24449
25236
|
metadata: {
|
|
24450
|
-
status: "
|
|
25237
|
+
status: "draft",
|
|
24451
25238
|
createdAt: now,
|
|
24452
25239
|
updatedAt: now,
|
|
24453
25240
|
compiledFrom: scope.sourceIds,
|
|
24454
25241
|
managedBy: "system",
|
|
24455
|
-
confidence: 0.
|
|
24456
|
-
},
|
|
24457
|
-
frontmatter: {
|
|
24458
|
-
profile_presets: config.profile.presets,
|
|
24459
|
-
source_type: scopeSourceType(scope, scopeManifests),
|
|
24460
|
-
occurred_at: scopeOccurredAt(scopeManifests),
|
|
24461
|
-
participants: scopeParticipants(scopeManifests),
|
|
24462
|
-
container_title: scopeContainerTitle(scopeManifests),
|
|
24463
|
-
conversation_id: scopeConversationId(scopeManifests),
|
|
24464
|
-
|
|
24465
|
-
|
|
24466
|
-
|
|
24467
|
-
evidence_state: evidenceState
|
|
24468
|
-
}
|
|
24469
|
-
});
|
|
24470
|
-
return { page: output.page, content: output.content };
|
|
24471
|
-
}
|
|
24472
|
-
async function persistSourceSessionPage(rootDir, scope, session) {
|
|
24473
|
-
const { paths } = await loadVaultConfig(rootDir);
|
|
24474
|
-
const output = await buildSourceSessionSavedPage(rootDir, scope, session);
|
|
24475
|
-
const absolutePath = path26.join(paths.wikiDir, output.page.path);
|
|
24476
|
-
await ensureDir(path26.dirname(absolutePath));
|
|
24477
|
-
await fs22.writeFile(absolutePath, output.content, "utf8");
|
|
24478
|
-
return { pageId: output.page.id, sessionPath: absolutePath };
|
|
24479
|
-
}
|
|
24480
|
-
async function buildGuidedUpdatePages(rootDir, scope, session) {
|
|
24481
|
-
const { config, paths } = await loadVaultConfig(rootDir);
|
|
24482
|
-
let graph = await readJsonFile(paths.graphPath);
|
|
24483
|
-
if (!graph) {
|
|
24484
|
-
await compileVault(rootDir, {});
|
|
24485
|
-
graph = await readJsonFile(paths.graphPath);
|
|
24486
|
-
}
|
|
24487
|
-
if (!graph) {
|
|
24488
|
-
return [];
|
|
24489
|
-
}
|
|
24490
|
-
const sourcePages = scopedSourcePages(graph, scope.sourceIds);
|
|
24491
|
-
const scopeManifests = manifestsForScope(graph, scope);
|
|
24492
|
-
const analyses = await loadSourceAnalyses(rootDir, scope.sourceIds);
|
|
24493
|
-
const report = await readGraphReport(rootDir);
|
|
24494
|
-
const contradictions = findContradictionsForScope(scope, report);
|
|
24495
|
-
const selectedTargets = selectGuidedTargetPages(scope, sourcePages, session.questions);
|
|
24496
|
-
const useCanonicalTargets = config.profile.guidedSessionMode === "canonical_review" && selectedTargets.length > 0;
|
|
24497
|
-
const targetPages = useCanonicalTargets ? selectedTargets : [selectedTargets[0] ?? null];
|
|
24498
|
-
session.targetedPagePaths = uniqueStrings4(
|
|
24499
|
-
useCanonicalTargets ? selectedTargets.map((page) => page.path) : selectedTargets.length ? selectedTargets.map((page) => page.path) : session.targetedPagePaths
|
|
24500
|
-
);
|
|
24501
|
-
return await Promise.all(
|
|
24502
|
-
targetPages.map(async (targetPage) => {
|
|
24503
|
-
const evidenceState = classifyGuidedEvidenceState(scope, targetPage, contradictions);
|
|
24504
|
-
const relativePath = useCanonicalTargets && targetPage ? targetPage.path : targetPage ? insightRelativePathForTarget(targetPage, scope) : `insights/topics/${slugify(scope.title)}.md`;
|
|
24505
|
-
const absolutePath = path26.join(paths.wikiDir, relativePath);
|
|
24506
|
-
const existingContent = await fileExists(absolutePath) ? await fs22.readFile(absolutePath, "utf8") : "";
|
|
24507
|
-
const parsed = existingContent ? matter11(existingContent) : { data: {}, content: "" };
|
|
24508
|
-
const existingData = parsed.data;
|
|
24509
|
-
const existingSourceIds = Array.isArray(existingData.source_ids) ? existingData.source_ids.filter((value) => typeof value === "string") : [];
|
|
24510
|
-
const existingProjectIds = Array.isArray(existingData.project_ids) ? existingData.project_ids.filter((value) => typeof value === "string") : [];
|
|
24511
|
-
const existingNodeIds = Array.isArray(existingData.node_ids) ? existingData.node_ids.filter((value) => typeof value === "string") : [];
|
|
24512
|
-
const existingBacklinks = Array.isArray(existingData.backlinks) ? existingData.backlinks.filter((value) => typeof value === "string") : [];
|
|
24513
|
-
const createdAt = typeof existingData.created_at === "string" && existingData.created_at.trim() ? existingData.created_at : (/* @__PURE__ */ new Date()).toISOString();
|
|
24514
|
-
const title = typeof existingData.title === "string" && existingData.title.trim() || (useCanonicalTargets && targetPage ? targetPage.title : targetPage ? insightTitleForTarget(targetPage, scope) : `${scope.title} Notes`);
|
|
24515
|
-
const baseBody = parsed.content.trim() ? parsed.content.trim() : [
|
|
24516
|
-
`# ${title}`,
|
|
24517
|
-
"",
|
|
24518
|
-
useCanonicalTargets ? "Canonical page maintained by SwarmVault. Guided sessions stage replaceable update blocks here for approval." : "Human-curated insight page. Guided sessions stage replaceable update blocks here.",
|
|
24519
|
-
""
|
|
24520
|
-
].join("\n");
|
|
24521
|
-
const importance = questionAnswer(
|
|
24522
|
-
session.questions,
|
|
24523
|
-
"importance",
|
|
24524
|
-
"Capture the most important new ideas from this source before treating them as canonical."
|
|
24525
|
-
);
|
|
24526
|
-
const exclude = questionAnswer(
|
|
24527
|
-
session.questions,
|
|
24528
|
-
"exclude",
|
|
24529
|
-
"Keep uncertain or incidental details provisional until they matter to the research thread."
|
|
24530
|
-
);
|
|
24531
|
-
const conflictNotes = questionAnswer(
|
|
24532
|
-
session.questions,
|
|
24533
|
-
"conflicts",
|
|
24534
|
-
contradictions.length ? "Review the conflicting evidence before accepting any canonical summary changes." : "No explicit conflicts were called out."
|
|
24535
|
-
);
|
|
24536
|
-
const followups = questionAnswer(session.questions, "followups", "Track follow-up questions on the source session page.");
|
|
24537
|
-
const updateBlock = [
|
|
24538
|
-
`## Guided Session Update: ${scope.title}`,
|
|
24539
|
-
"",
|
|
24540
|
-
`Evidence State: \`${evidenceState}\``,
|
|
24541
|
-
`Session: [[outputs/source-sessions/${scope.id}|Guided Session]]`,
|
|
24542
|
-
`Source Guide: [[outputs/source-guides/${scope.id}|Source Guide]]`,
|
|
24543
|
-
"",
|
|
24544
|
-
"### What Matters Now",
|
|
24545
|
-
"",
|
|
24546
|
-
importance,
|
|
24547
|
-
"",
|
|
24548
|
-
"### Proposed Integration",
|
|
24549
|
-
"",
|
|
24550
|
-
targetPage ? `- Fold the strongest source-backed takeaways into [[${targetPage.path.replace(/\.md$/, "")}|${targetPage.title}]].` : `- Start a durable topic note for ${scope.title}.`,
|
|
24551
|
-
...analyses.slice(0, 5).map((analysis) => `- ${truncate(normalizeWhitespace(analysis.summary), 180)}`),
|
|
24552
|
-
"",
|
|
24553
|
-
"### Keep Provisional Or Out",
|
|
24554
|
-
"",
|
|
24555
|
-
exclude,
|
|
24556
|
-
"",
|
|
24557
|
-
"### Reinforcing Or Conflicting Notes",
|
|
24558
|
-
"",
|
|
24559
|
-
conflictNotes,
|
|
24560
|
-
...contradictions.length ? ["", ...contradictions.slice(0, 4).map((contradiction) => `- ${contradiction.claimA} / ${contradiction.claimB}`)] : [],
|
|
24561
|
-
"",
|
|
24562
|
-
"### Follow-up Questions",
|
|
24563
|
-
"",
|
|
24564
|
-
followups,
|
|
24565
|
-
""
|
|
24566
|
-
].join("\n");
|
|
24567
|
-
const nextBody = replaceMarkedSection(baseBody, scope.id, updateBlock);
|
|
24568
|
-
const content = matter11.stringify(
|
|
24569
|
-
`${nextBody.trimEnd()}
|
|
24570
|
-
`,
|
|
24571
|
-
JSON.parse(
|
|
24572
|
-
JSON.stringify({
|
|
24573
|
-
...existingData,
|
|
24574
|
-
page_id: typeof existingData.page_id === "string" && existingData.page_id.trim() || (useCanonicalTargets && targetPage ? targetPage.id : `insight:${slugify(relativePath.replace(/\.md$/, ""))}`),
|
|
24575
|
-
kind: useCanonicalTargets && targetPage ? targetPage.kind : "insight",
|
|
24576
|
-
title,
|
|
24577
|
-
tags: uniqueStrings4([
|
|
24578
|
-
...Array.isArray(existingData.tags) ? existingData.tags.filter((value) => typeof value === "string") : [],
|
|
24579
|
-
...useCanonicalTargets ? ["guided-session", `guided/${targetPage?.kind ?? "page"}`] : insightTagsForTarget(targetPage)
|
|
24580
|
-
]),
|
|
24581
|
-
source_ids: uniqueStrings4([...existingSourceIds, ...scope.sourceIds]),
|
|
24582
|
-
project_ids: uniqueStrings4([...existingProjectIds, ...targetPage?.projectIds ?? []]),
|
|
24583
|
-
node_ids: uniqueStrings4([...existingNodeIds, ...targetPage?.nodeIds ?? []]),
|
|
24584
|
-
freshness: "fresh",
|
|
24585
|
-
status: existingData.status === "archived" ? "archived" : "active",
|
|
24586
|
-
confidence: 0.83,
|
|
24587
|
-
created_at: createdAt,
|
|
24588
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24589
|
-
compiled_from: uniqueStrings4([
|
|
24590
|
-
...Array.isArray(existingData.compiled_from) ? existingData.compiled_from.filter((value) => typeof value === "string") : [],
|
|
24591
|
-
...scope.sourceIds
|
|
24592
|
-
]),
|
|
24593
|
-
managed_by: typeof existingData.managed_by === "string" && (existingData.managed_by === "human" || existingData.managed_by === "system") ? existingData.managed_by : useCanonicalTargets ? "system" : "human",
|
|
24594
|
-
backlinks: uniqueStrings4([
|
|
24595
|
-
...existingBacklinks,
|
|
24596
|
-
...targetPage ? [targetPage.id] : [],
|
|
24597
|
-
`output:source-sessions/${scope.id}`,
|
|
24598
|
-
`output:source-guides/${scope.id}`
|
|
24599
|
-
]),
|
|
24600
|
-
schema_hash: typeof existingData.schema_hash === "string" ? existingData.schema_hash : "",
|
|
24601
|
-
source_hashes: existingData.source_hashes && typeof existingData.source_hashes === "object" ? existingData.source_hashes : {},
|
|
24602
|
-
source_semantic_hashes: existingData.source_semantic_hashes && typeof existingData.source_semantic_hashes === "object" ? existingData.source_semantic_hashes : {},
|
|
24603
|
-
profile_presets: config.profile.presets,
|
|
24604
|
-
source_type: scopeSourceType(scope, scopeManifests),
|
|
24605
|
-
occurred_at: scopeOccurredAt(scopeManifests),
|
|
24606
|
-
participants: scopeParticipants(scopeManifests),
|
|
24607
|
-
container_title: scopeContainerTitle(scopeManifests),
|
|
24608
|
-
conversation_id: scopeConversationId(scopeManifests),
|
|
24609
|
-
session_status: session.status,
|
|
24610
|
-
question_state: questionStateForSession(session),
|
|
24611
|
-
canonical_targets: useCanonicalTargets ? selectedTargets.map((page2) => page2.path) : [],
|
|
24612
|
-
evidence_state: evidenceState
|
|
24613
|
-
})
|
|
24614
|
-
)
|
|
24615
|
-
);
|
|
24616
|
-
const page = parseStoredPage(relativePath, content, {
|
|
24617
|
-
createdAt,
|
|
24618
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
24619
|
-
});
|
|
24620
|
-
if (!useCanonicalTargets && !selectedTargets.length) {
|
|
24621
|
-
session.targetedPagePaths = uniqueStrings4([...session.targetedPagePaths, relativePath]);
|
|
24622
|
-
}
|
|
24623
|
-
return { page, content, label: "guided-update" };
|
|
24624
|
-
})
|
|
24625
|
-
);
|
|
24626
|
-
}
|
|
24627
|
-
async function stageSourceGuideForScope(rootDir, scope, options = {}) {
|
|
24628
|
-
const { session, statePath } = await prepareGuidedSourceSession(rootDir, scope, options.answers);
|
|
24629
|
-
const briefPath = scope.briefPath ?? session.briefPath ?? await writeSourceBriefForScope(rootDir, scope) ?? void 0;
|
|
24630
|
-
session.briefPath = briefPath;
|
|
24631
|
-
if (briefPath) {
|
|
24632
|
-
await refreshVaultAfterOutputSave(rootDir);
|
|
24633
|
-
}
|
|
24634
|
-
if (answeredGuidedSessionQuestions(session.questions).length === 0) {
|
|
24635
|
-
session.status = "awaiting_input";
|
|
24636
|
-
const persisted2 = await persistSourceSessionPage(rootDir, scope, session);
|
|
24637
|
-
session.sessionPath = persisted2.sessionPath;
|
|
24638
|
-
await writeGuidedSourceSession(rootDir, session);
|
|
24639
|
-
await refreshVaultAfterOutputSave(rootDir);
|
|
24640
|
-
return {
|
|
24641
|
-
sourceId: scope.id,
|
|
24642
|
-
sessionId: session.sessionId,
|
|
24643
|
-
sessionPath: persisted2.sessionPath,
|
|
24644
|
-
sessionStatePath: statePath,
|
|
24645
|
-
status: session.status,
|
|
24646
|
-
questions: session.questions,
|
|
24647
|
-
awaitingInput: true,
|
|
24648
|
-
targetedPagePaths: session.targetedPagePaths,
|
|
24649
|
-
stagedUpdatePaths: session.stagedUpdatePaths,
|
|
24650
|
-
briefPath,
|
|
24651
|
-
staged: false
|
|
24652
|
-
};
|
|
24653
|
-
}
|
|
24654
|
-
session.status = "ready_to_stage";
|
|
24655
|
-
await writeGuidedSourceSession(rootDir, session);
|
|
24656
|
-
const reviewOutput = await buildSourceReviewStagedPage(rootDir, scope);
|
|
24657
|
-
const guideOutput = await buildSourceGuideStagedPage(rootDir, {
|
|
24658
|
-
...scope,
|
|
24659
|
-
briefPath
|
|
24660
|
-
});
|
|
24661
|
-
const guidedUpdates = await buildGuidedUpdatePages(rootDir, scope, session);
|
|
24662
|
-
session.stagedUpdatePaths = guidedUpdates.map((item) => item.page.path);
|
|
24663
|
-
const approval = await stageGeneratedOutputPages(
|
|
24664
|
-
rootDir,
|
|
24665
|
-
[
|
|
24666
|
-
{ page: reviewOutput.page, content: reviewOutput.content, label: "source-review" },
|
|
24667
|
-
{ page: guideOutput.page, content: guideOutput.content, label: "source-guide" },
|
|
24668
|
-
...guidedUpdates
|
|
24669
|
-
],
|
|
24670
|
-
{
|
|
24671
|
-
bundleType: "guided-session",
|
|
24672
|
-
title: `Guided Session: ${scope.title}`,
|
|
24673
|
-
sourceSessionId: session.sessionId
|
|
25242
|
+
confidence: 0.8
|
|
25243
|
+
},
|
|
25244
|
+
frontmatter: {
|
|
25245
|
+
profile_presets: config.profile.presets,
|
|
25246
|
+
source_type: scopeSourceType(scope, scopeManifests),
|
|
25247
|
+
occurred_at: scopeOccurredAt(scopeManifests),
|
|
25248
|
+
participants: scopeParticipants(scopeManifests),
|
|
25249
|
+
container_title: scopeContainerTitle(scopeManifests),
|
|
25250
|
+
conversation_id: scopeConversationId(scopeManifests),
|
|
25251
|
+
question_state: "answered",
|
|
25252
|
+
canonical_targets: selectedTargets.map((page) => page.path),
|
|
25253
|
+
evidence_state: contradictions.length ? "conflicting" : selectedTargets.some((page) => page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId))) ? "reinforcing" : selectedTargets.length ? "new" : "needs_judgment"
|
|
24674
25254
|
}
|
|
24675
|
-
);
|
|
24676
|
-
|
|
24677
|
-
|
|
24678
|
-
|
|
24679
|
-
|
|
24680
|
-
|
|
24681
|
-
|
|
24682
|
-
|
|
24683
|
-
|
|
24684
|
-
await refreshVaultAfterOutputSave(rootDir);
|
|
25255
|
+
});
|
|
25256
|
+
return { page: output.page, content: output.content };
|
|
25257
|
+
}
|
|
25258
|
+
async function stageSourceReviewForScope(rootDir, scope) {
|
|
25259
|
+
const output = await buildSourceReviewStagedPage(rootDir, scope);
|
|
25260
|
+
const approval = await stageGeneratedOutputPages(rootDir, [{ page: output.page, content: output.content, label: "source-review" }], {
|
|
25261
|
+
bundleType: "source-review",
|
|
25262
|
+
title: `Source Review: ${scope.title}`
|
|
25263
|
+
});
|
|
24685
25264
|
return {
|
|
24686
25265
|
sourceId: scope.id,
|
|
24687
|
-
pageId:
|
|
24688
|
-
|
|
24689
|
-
reviewPageId: reviewOutput.page.id,
|
|
24690
|
-
reviewPath: session.reviewPath,
|
|
24691
|
-
sessionId: session.sessionId,
|
|
24692
|
-
sessionPath: persisted.sessionPath,
|
|
24693
|
-
sessionStatePath: statePath,
|
|
24694
|
-
status: session.status,
|
|
24695
|
-
questions: session.questions,
|
|
24696
|
-
targetedPagePaths: session.targetedPagePaths,
|
|
24697
|
-
stagedUpdatePaths: session.stagedUpdatePaths,
|
|
24698
|
-
briefPath,
|
|
25266
|
+
pageId: output.page.id,
|
|
25267
|
+
reviewPath: path27.join(approval.approvalDir, "wiki", output.page.path),
|
|
24699
25268
|
staged: true,
|
|
24700
25269
|
approvalId: approval.approvalId,
|
|
24701
25270
|
approvalDir: approval.approvalDir
|
|
24702
25271
|
};
|
|
24703
25272
|
}
|
|
24704
|
-
function
|
|
24705
|
-
return {
|
|
24706
|
-
id: source.id,
|
|
24707
|
-
title: source.title,
|
|
24708
|
-
sourceIds: source.sourceIds,
|
|
24709
|
-
kind: source.kind,
|
|
24710
|
-
briefPath: source.briefPath
|
|
24711
|
-
};
|
|
24712
|
-
}
|
|
24713
|
-
function scopeFromManifest(manifest, manifests) {
|
|
24714
|
-
const groupId = manifest.sourceGroupId ?? manifest.sourceId;
|
|
24715
|
-
return {
|
|
24716
|
-
id: groupId,
|
|
24717
|
-
title: manifest.sourceGroupTitle ?? manifest.title,
|
|
24718
|
-
sourceIds: manifest.sourceGroupId ? manifests.filter((candidate) => candidate.sourceGroupId === manifest.sourceGroupId).map((candidate) => candidate.sourceId) : [manifest.sourceId],
|
|
24719
|
-
kind: manifest.sourceKind
|
|
24720
|
-
};
|
|
24721
|
-
}
|
|
24722
|
-
async function resolveSourceScope(rootDir, id) {
|
|
24723
|
-
const managedSources = await loadManagedSources(rootDir);
|
|
24724
|
-
const managedSource = managedSources.find((source) => source.id === id);
|
|
24725
|
-
if (managedSource) {
|
|
24726
|
-
return scopeFromManagedSource(managedSource);
|
|
24727
|
-
}
|
|
24728
|
-
const latestSession = await findLatestGuidedSourceSessionByScope(rootDir, id);
|
|
24729
|
-
if (latestSession) {
|
|
24730
|
-
return {
|
|
24731
|
-
id: latestSession.scopeId,
|
|
24732
|
-
title: latestSession.scopeTitle,
|
|
24733
|
-
sourceIds: latestSession.sourceIds
|
|
24734
|
-
};
|
|
24735
|
-
}
|
|
24736
|
-
const manifests = await listManifests(rootDir);
|
|
24737
|
-
const manifest = manifests.find((candidate) => candidate.sourceId === id) ?? manifests.find((candidate) => candidate.sourceGroupId === id);
|
|
24738
|
-
if (!manifest) {
|
|
24739
|
-
return null;
|
|
24740
|
-
}
|
|
24741
|
-
return scopeFromManifest(manifest, manifests);
|
|
24742
|
-
}
|
|
24743
|
-
async function reviewSourceScope(rootDir, scope) {
|
|
24744
|
-
return await stageSourceReviewForScope(rootDir, scope);
|
|
24745
|
-
}
|
|
24746
|
-
async function guideSourceScope(rootDir, scope, options = {}) {
|
|
24747
|
-
return await stageSourceGuideForScope(rootDir, scope, options);
|
|
24748
|
-
}
|
|
24749
|
-
async function reviewManagedSource(rootDir, id) {
|
|
24750
|
-
const scope = await resolveSourceScope(rootDir, id);
|
|
24751
|
-
if (!scope) {
|
|
24752
|
-
throw new Error(`Managed source or source id not found: ${id}`);
|
|
24753
|
-
}
|
|
24754
|
-
if (!await loadVaultConfig(rootDir).then(({ paths }) => fileExists(paths.graphPath))) {
|
|
24755
|
-
await compileVault(rootDir, {});
|
|
24756
|
-
}
|
|
24757
|
-
return await stageSourceReviewForScope(rootDir, scope);
|
|
24758
|
-
}
|
|
24759
|
-
async function guideManagedSource(rootDir, id, options = {}) {
|
|
24760
|
-
const scope = await resolveSourceScope(rootDir, id);
|
|
24761
|
-
if (!scope) {
|
|
24762
|
-
throw new Error(`Managed source or source id not found: ${id}`);
|
|
24763
|
-
}
|
|
24764
|
-
if (!await loadVaultConfig(rootDir).then(({ paths }) => fileExists(paths.graphPath))) {
|
|
24765
|
-
await compileVault(rootDir, {});
|
|
24766
|
-
}
|
|
24767
|
-
return await stageSourceGuideForScope(rootDir, scope, options);
|
|
24768
|
-
}
|
|
24769
|
-
async function resumeSourceSession(rootDir, id, options = {}) {
|
|
24770
|
-
const existingSession = await readGuidedSourceSession(rootDir, id);
|
|
24771
|
-
if (existingSession) {
|
|
24772
|
-
return await stageSourceGuideForScope(
|
|
24773
|
-
rootDir,
|
|
24774
|
-
{
|
|
24775
|
-
id: existingSession.scopeId,
|
|
24776
|
-
title: existingSession.scopeTitle,
|
|
24777
|
-
sourceIds: existingSession.sourceIds,
|
|
24778
|
-
kind: existingSession.kind,
|
|
24779
|
-
briefPath: existingSession.briefPath
|
|
24780
|
-
},
|
|
24781
|
-
options
|
|
24782
|
-
);
|
|
24783
|
-
}
|
|
24784
|
-
const scope = await resolveSourceScope(rootDir, id);
|
|
24785
|
-
if (!scope) {
|
|
24786
|
-
throw new Error(`Managed source, source scope, or guided session not found: ${id}`);
|
|
24787
|
-
}
|
|
24788
|
-
return await stageSourceGuideForScope(rootDir, scope, options);
|
|
24789
|
-
}
|
|
24790
|
-
function shouldCompile(changedSources, graphExists, compileRequested) {
|
|
24791
|
-
return compileRequested && (!graphExists || changedSources.length > 0);
|
|
25273
|
+
function nextGuidedSourceSessionId(scope) {
|
|
25274
|
+
return `source-session-${slugify(scope.id)}-${sha256(`${scope.id}:${(/* @__PURE__ */ new Date()).toISOString()}`).slice(0, 8)}`;
|
|
24792
25275
|
}
|
|
24793
|
-
|
|
24794
|
-
|
|
24795
|
-
return true;
|
|
24796
|
-
}
|
|
24797
|
-
if (!source.briefPath) {
|
|
24798
|
-
return true;
|
|
24799
|
-
}
|
|
24800
|
-
return !await fileExists(source.briefPath);
|
|
25276
|
+
function shouldReuseGuidedSourceSession(session) {
|
|
25277
|
+
return Boolean(session && session.status === "awaiting_input");
|
|
24801
25278
|
}
|
|
24802
|
-
|
|
24803
|
-
|
|
24804
|
-
return await loadManagedSources(rootDir);
|
|
25279
|
+
function questionAnswer(questions, id, fallback) {
|
|
25280
|
+
return normalizeGuidedAnswerValue(questions.find((question) => question.id === id)?.answer) ?? fallback;
|
|
24805
25281
|
}
|
|
24806
|
-
async function
|
|
24807
|
-
const
|
|
24808
|
-
const guideRequested = options.guide ?? false;
|
|
24809
|
-
const briefRequested = guideRequested ? true : options.brief ?? true;
|
|
24810
|
-
const reviewRequested = guideRequested ? false : options.review ?? false;
|
|
24811
|
-
const sources = await loadManagedSources(rootDir);
|
|
24812
|
-
const resolved = await resolveManagedSourceInput(rootDir, input);
|
|
24813
|
-
const existing = sources.find((candidate) => matchesManagedSourceSpec(candidate, resolved));
|
|
25282
|
+
async function prepareGuidedSourceSession(rootDir, scope, answers) {
|
|
25283
|
+
const existing = await findLatestGuidedSourceSessionByScope(rootDir, scope.id);
|
|
24814
25284
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24815
|
-
const
|
|
24816
|
-
|
|
24817
|
-
|
|
24818
|
-
|
|
24819
|
-
|
|
24820
|
-
|
|
24821
|
-
|
|
25285
|
+
const session = shouldReuseGuidedSourceSession(existing) ? {
|
|
25286
|
+
...existing,
|
|
25287
|
+
scopeTitle: scope.title,
|
|
25288
|
+
sourceIds: scope.sourceIds,
|
|
25289
|
+
kind: scope.kind,
|
|
25290
|
+
questions: mergeGuidedSessionQuestions(existing.questions, answers),
|
|
25291
|
+
updatedAt: now
|
|
25292
|
+
} : {
|
|
25293
|
+
sessionId: nextGuidedSourceSessionId(scope),
|
|
25294
|
+
scopeId: scope.id,
|
|
25295
|
+
scopeTitle: scope.title,
|
|
25296
|
+
sourceIds: scope.sourceIds,
|
|
25297
|
+
kind: scope.kind,
|
|
25298
|
+
status: "awaiting_input",
|
|
24822
25299
|
createdAt: now,
|
|
24823
25300
|
updatedAt: now,
|
|
24824
|
-
|
|
24825
|
-
|
|
25301
|
+
questions: mergeGuidedSessionQuestions(defaultGuidedSessionQuestions(), answers),
|
|
25302
|
+
briefPath: scope.briefPath,
|
|
25303
|
+
targetedPagePaths: [],
|
|
25304
|
+
stagedUpdatePaths: []
|
|
24826
25305
|
};
|
|
24827
|
-
const
|
|
24828
|
-
|
|
24829
|
-
|
|
24830
|
-
|
|
24831
|
-
const
|
|
24832
|
-
let
|
|
24833
|
-
if (
|
|
24834
|
-
|
|
24835
|
-
|
|
24836
|
-
let briefGenerated = false;
|
|
24837
|
-
let briefPath;
|
|
24838
|
-
if (compileRequested && briefRequested && synced.status === "ready" && await shouldRefreshBriefForManagedSource(synced, {
|
|
24839
|
-
compilePerformed: Boolean(compile),
|
|
24840
|
-
changed: synced.changed
|
|
24841
|
-
})) {
|
|
24842
|
-
const briefs = await generateBriefsForSources(rootDir, [synced]);
|
|
24843
|
-
briefPath = briefs.get(synced.id);
|
|
24844
|
-
briefGenerated = Boolean(briefPath);
|
|
25306
|
+
const statePath = await guidedSourceSessionStatePath(rootDir, session.sessionId);
|
|
25307
|
+
return { session, statePath };
|
|
25308
|
+
}
|
|
25309
|
+
async function buildSourceSessionSavedPage(rootDir, scope, session) {
|
|
25310
|
+
const { config, paths } = await loadVaultConfig(rootDir);
|
|
25311
|
+
let graph = await readJsonFile(paths.graphPath);
|
|
25312
|
+
if (!graph) {
|
|
25313
|
+
await compileVault(rootDir, {});
|
|
25314
|
+
graph = await readJsonFile(paths.graphPath);
|
|
24845
25315
|
}
|
|
24846
|
-
const
|
|
24847
|
-
|
|
24848
|
-
|
|
24849
|
-
|
|
24850
|
-
|
|
24851
|
-
const
|
|
24852
|
-
|
|
24853
|
-
|
|
24854
|
-
|
|
24855
|
-
|
|
24856
|
-
|
|
24857
|
-
|
|
24858
|
-
|
|
25316
|
+
const schemas = await loadVaultSchemas(rootDir);
|
|
25317
|
+
const scopeManifests = manifestsForScope(graph, scope);
|
|
25318
|
+
const sourcePages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
|
|
25319
|
+
const analyses = await loadSourceAnalyses(rootDir, scope.sourceIds);
|
|
25320
|
+
const report = await readGraphReport(rootDir);
|
|
25321
|
+
const contradictions = findContradictionsForScope(scope, report);
|
|
25322
|
+
const relatedPageIds = uniqueStrings4([
|
|
25323
|
+
...sourcePages.slice(0, 18).map((page) => page.id),
|
|
25324
|
+
...session.targetedPagePaths.map((relativePath) => {
|
|
25325
|
+
const page = graph?.pages.find((candidate) => candidate.path === relativePath);
|
|
25326
|
+
return page?.id ?? "";
|
|
25327
|
+
})
|
|
25328
|
+
]);
|
|
25329
|
+
const relatedNodeIds = graph ? scopedNodeIds(graph, scope.sourceIds).slice(0, 28) : [];
|
|
25330
|
+
const projectIds = uniqueStrings4(sourcePages.flatMap((page) => page.projectIds));
|
|
25331
|
+
const evidenceState = contradictions.length > 0 ? "conflicting" : session.targetedPagePaths.some(
|
|
25332
|
+
(targetPath) => sourcePages.some((page) => page.path === targetPath && page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId)))
|
|
25333
|
+
) ? "reinforcing" : session.targetedPagePaths.length ? "new" : "needs_judgment";
|
|
25334
|
+
const relativeBriefPath = session.briefPath && path27.isAbsolute(session.briefPath) ? path27.relative(paths.wikiDir, session.briefPath) : session.briefPath;
|
|
25335
|
+
const sessionMarkdown = [
|
|
25336
|
+
`# Guided Session: ${scope.title}`,
|
|
25337
|
+
"",
|
|
25338
|
+
`Status: \`${session.status}\``,
|
|
25339
|
+
`Session ID: \`${session.sessionId}\``,
|
|
25340
|
+
...session.approvalId ? [`Approval Bundle: \`${session.approvalId}\``] : [],
|
|
25341
|
+
...relativeBriefPath ? [`Brief: \`${relativeBriefPath}\``] : [],
|
|
25342
|
+
"",
|
|
25343
|
+
"## What This Source Is",
|
|
25344
|
+
"",
|
|
25345
|
+
...analyses.length ? analyses.slice(0, 6).map((analysis) => `- ${analysis.title}: ${analysis.summary}`) : ["- Awaiting compile context."],
|
|
25346
|
+
"",
|
|
25347
|
+
"## Guided Questions",
|
|
25348
|
+
"",
|
|
25349
|
+
...session.questions.flatMap((question) => [`### ${question.prompt}`, "", question.answer ?? "_Awaiting input._", ""]),
|
|
25350
|
+
"## Proposed Wiki Targets",
|
|
25351
|
+
"",
|
|
25352
|
+
...session.targetedPagePaths.length ? session.targetedPagePaths.map((targetPath) => `- [[${targetPath.replace(/\.md$/, "")}]]`) : ["- No canonical update targets selected yet."],
|
|
25353
|
+
"",
|
|
25354
|
+
"## Conflicts And Judgment Calls",
|
|
25355
|
+
"",
|
|
25356
|
+
...contradictions.length ? contradictions.map((contradiction) => `- ${contradiction.claimA} / ${contradiction.claimB}`) : ["- No contradictions are currently flagged for this source scope."],
|
|
25357
|
+
"",
|
|
25358
|
+
"## Follow-up Questions",
|
|
25359
|
+
"",
|
|
25360
|
+
...(() => {
|
|
25361
|
+
const followups = questionAnswer(session.questions, "followups", "");
|
|
25362
|
+
if (followups) {
|
|
25363
|
+
return followups.split(/\n+/).map((line) => line.trim()).filter(Boolean).map((line) => `- ${line.replace(/^-+\s*/, "")}`);
|
|
25364
|
+
}
|
|
25365
|
+
const analysisQuestions = uniqueStrings4(analyses.flatMap((analysis) => analysis.questions)).slice(0, 6);
|
|
25366
|
+
return analysisQuestions.length ? analysisQuestions.map((question) => `- ${question}`) : ["- No follow-up questions recorded yet."];
|
|
25367
|
+
})(),
|
|
25368
|
+
"",
|
|
25369
|
+
"## Related Artifacts",
|
|
25370
|
+
"",
|
|
25371
|
+
`- [[outputs/source-briefs/${scope.id}|Source Brief]]`,
|
|
25372
|
+
`- [[outputs/source-reviews/${scope.id}|Source Review]]`,
|
|
25373
|
+
`- [[outputs/source-guides/${scope.id}|Source Guide]]`,
|
|
25374
|
+
""
|
|
25375
|
+
].join("\n");
|
|
25376
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25377
|
+
const output = buildOutputPage({
|
|
25378
|
+
title: `Guided Session: ${scope.title}`,
|
|
25379
|
+
question: `Guided Session ${scope.title}`,
|
|
25380
|
+
answer: sessionMarkdown,
|
|
25381
|
+
citations: scope.sourceIds,
|
|
25382
|
+
schemaHash: sourceOutputSchemaHash(schemas, projectIds),
|
|
25383
|
+
outputFormat: "report",
|
|
25384
|
+
relatedPageIds,
|
|
25385
|
+
relatedNodeIds,
|
|
25386
|
+
relatedSourceIds: scope.sourceIds,
|
|
25387
|
+
projectIds,
|
|
25388
|
+
extraTags: ["source-session", "guided-session"],
|
|
25389
|
+
origin: "source_session",
|
|
25390
|
+
slug: `source-sessions/${scope.id}`,
|
|
25391
|
+
metadata: {
|
|
25392
|
+
status: "active",
|
|
25393
|
+
createdAt: now,
|
|
25394
|
+
updatedAt: now,
|
|
25395
|
+
compiledFrom: scope.sourceIds,
|
|
25396
|
+
managedBy: "system",
|
|
25397
|
+
confidence: 0.81
|
|
24859
25398
|
},
|
|
24860
|
-
|
|
24861
|
-
|
|
24862
|
-
|
|
24863
|
-
|
|
24864
|
-
|
|
24865
|
-
|
|
24866
|
-
|
|
24867
|
-
|
|
24868
|
-
|
|
24869
|
-
|
|
24870
|
-
|
|
24871
|
-
const compileRequested = options.compile ?? true;
|
|
24872
|
-
const guideRequested = options.guide ?? false;
|
|
24873
|
-
const briefRequested = guideRequested ? true : options.brief ?? true;
|
|
24874
|
-
const reviewRequested = guideRequested ? false : options.review ?? false;
|
|
24875
|
-
const sources = await loadManagedSources(rootDir);
|
|
24876
|
-
const selected = options.all || !options.id ? sources : sources.filter((source) => source.id === options.id);
|
|
24877
|
-
if (!selected.length) {
|
|
24878
|
-
throw new Error(options.id ? `Managed source not found: ${options.id}` : "No managed sources registered.");
|
|
24879
|
-
}
|
|
24880
|
-
const syncedSources = [];
|
|
24881
|
-
const changedSources = [];
|
|
24882
|
-
for (const source of selected) {
|
|
24883
|
-
const synced = await syncManagedSource(rootDir, source, options);
|
|
24884
|
-
syncedSources.push(synced);
|
|
24885
|
-
if (synced.changed) {
|
|
24886
|
-
changedSources.push(synced);
|
|
24887
|
-
}
|
|
24888
|
-
}
|
|
24889
|
-
const graphExists = await loadVaultConfig(rootDir).then(({ paths }) => fileExists(paths.graphPath));
|
|
24890
|
-
let compile;
|
|
24891
|
-
if (shouldCompile(changedSources, graphExists, compileRequested)) {
|
|
24892
|
-
compile = await compileVault(rootDir, {});
|
|
24893
|
-
}
|
|
24894
|
-
const briefPaths = compileRequested && briefRequested ? await generateBriefsForSources(
|
|
24895
|
-
rootDir,
|
|
24896
|
-
syncedSources.filter((source) => source.status === "ready")
|
|
24897
|
-
) : /* @__PURE__ */ new Map();
|
|
24898
|
-
const nextSources = sources.map((source) => {
|
|
24899
|
-
const synced = syncedSources.find((candidate) => candidate.id === source.id);
|
|
24900
|
-
if (!synced) {
|
|
24901
|
-
return source;
|
|
25399
|
+
frontmatter: {
|
|
25400
|
+
profile_presets: config.profile.presets,
|
|
25401
|
+
source_type: scopeSourceType(scope, scopeManifests),
|
|
25402
|
+
occurred_at: scopeOccurredAt(scopeManifests),
|
|
25403
|
+
participants: scopeParticipants(scopeManifests),
|
|
25404
|
+
container_title: scopeContainerTitle(scopeManifests),
|
|
25405
|
+
conversation_id: scopeConversationId(scopeManifests),
|
|
25406
|
+
session_status: session.status,
|
|
25407
|
+
question_state: questionStateForSession(session),
|
|
25408
|
+
canonical_targets: session.targetedPagePaths,
|
|
25409
|
+
evidence_state: evidenceState
|
|
24902
25410
|
}
|
|
24903
|
-
return {
|
|
24904
|
-
...synced,
|
|
24905
|
-
briefPath: briefPaths.get(synced.id) ?? synced.briefPath,
|
|
24906
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
24907
|
-
};
|
|
24908
25411
|
});
|
|
24909
|
-
|
|
24910
|
-
const reviews = reviewRequested ? await Promise.all(
|
|
24911
|
-
nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)).filter((source) => source.status === "ready").map(async (source) => await stageSourceReviewForScope(rootDir, scopeFromManagedSource(source)))
|
|
24912
|
-
) : [];
|
|
24913
|
-
const guides = guideRequested ? await Promise.all(
|
|
24914
|
-
nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)).filter((source) => source.status === "ready").map(
|
|
24915
|
-
async (source) => await stageSourceGuideForScope(
|
|
24916
|
-
rootDir,
|
|
24917
|
-
{
|
|
24918
|
-
...scopeFromManagedSource(source),
|
|
24919
|
-
briefPath: source.briefPath
|
|
24920
|
-
},
|
|
24921
|
-
{ answers: options.guideAnswers }
|
|
24922
|
-
)
|
|
24923
|
-
)
|
|
24924
|
-
) : [];
|
|
24925
|
-
return {
|
|
24926
|
-
sources: nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)),
|
|
24927
|
-
compile,
|
|
24928
|
-
briefPaths: [...briefPaths.values()],
|
|
24929
|
-
reviews,
|
|
24930
|
-
guides
|
|
24931
|
-
};
|
|
25412
|
+
return { page: output.page, content: output.content };
|
|
24932
25413
|
}
|
|
24933
|
-
async function
|
|
24934
|
-
const
|
|
24935
|
-
const
|
|
24936
|
-
|
|
24937
|
-
|
|
25414
|
+
async function persistSourceSessionPage(rootDir, scope, session) {
|
|
25415
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
25416
|
+
const output = await buildSourceSessionSavedPage(rootDir, scope, session);
|
|
25417
|
+
const absolutePath = path27.join(paths.wikiDir, output.page.path);
|
|
25418
|
+
await ensureDir(path27.dirname(absolutePath));
|
|
25419
|
+
await fs22.writeFile(absolutePath, output.content, "utf8");
|
|
25420
|
+
return { pageId: output.page.id, sessionPath: absolutePath };
|
|
25421
|
+
}
|
|
25422
|
+
async function buildGuidedUpdatePages(rootDir, scope, session) {
|
|
25423
|
+
const { config, paths } = await loadVaultConfig(rootDir);
|
|
25424
|
+
let graph = await readJsonFile(paths.graphPath);
|
|
25425
|
+
if (!graph) {
|
|
25426
|
+
await compileVault(rootDir, {});
|
|
25427
|
+
graph = await readJsonFile(paths.graphPath);
|
|
24938
25428
|
}
|
|
24939
|
-
|
|
24940
|
-
|
|
24941
|
-
|
|
25429
|
+
if (!graph) {
|
|
25430
|
+
return [];
|
|
25431
|
+
}
|
|
25432
|
+
const sourcePages = scopedSourcePages(graph, scope.sourceIds);
|
|
25433
|
+
const scopeManifests = manifestsForScope(graph, scope);
|
|
25434
|
+
const analyses = await loadSourceAnalyses(rootDir, scope.sourceIds);
|
|
25435
|
+
const report = await readGraphReport(rootDir);
|
|
25436
|
+
const contradictions = findContradictionsForScope(scope, report);
|
|
25437
|
+
const selectedTargets = selectGuidedTargetPages(scope, sourcePages, session.questions);
|
|
25438
|
+
const useCanonicalTargets = config.profile.guidedSessionMode === "canonical_review" && selectedTargets.length > 0;
|
|
25439
|
+
const targetPages = useCanonicalTargets ? selectedTargets : [selectedTargets[0] ?? null];
|
|
25440
|
+
session.targetedPagePaths = uniqueStrings4(
|
|
25441
|
+
useCanonicalTargets ? selectedTargets.map((page) => page.path) : selectedTargets.length ? selectedTargets.map((page) => page.path) : session.targetedPagePaths
|
|
25442
|
+
);
|
|
25443
|
+
return await Promise.all(
|
|
25444
|
+
targetPages.map(async (targetPage) => {
|
|
25445
|
+
const evidenceState = classifyGuidedEvidenceState(scope, targetPage, contradictions);
|
|
25446
|
+
const relativePath = useCanonicalTargets && targetPage ? targetPage.path : targetPage ? insightRelativePathForTarget(targetPage, scope) : `insights/topics/${slugify(scope.title)}.md`;
|
|
25447
|
+
const absolutePath = path27.join(paths.wikiDir, relativePath);
|
|
25448
|
+
const existingContent = await fileExists(absolutePath) ? await fs22.readFile(absolutePath, "utf8") : "";
|
|
25449
|
+
const parsed = existingContent ? matter11(existingContent) : { data: {}, content: "" };
|
|
25450
|
+
const existingData = parsed.data;
|
|
25451
|
+
const existingSourceIds = Array.isArray(existingData.source_ids) ? existingData.source_ids.filter((value) => typeof value === "string") : [];
|
|
25452
|
+
const existingProjectIds = Array.isArray(existingData.project_ids) ? existingData.project_ids.filter((value) => typeof value === "string") : [];
|
|
25453
|
+
const existingNodeIds = Array.isArray(existingData.node_ids) ? existingData.node_ids.filter((value) => typeof value === "string") : [];
|
|
25454
|
+
const existingBacklinks = Array.isArray(existingData.backlinks) ? existingData.backlinks.filter((value) => typeof value === "string") : [];
|
|
25455
|
+
const createdAt = typeof existingData.created_at === "string" && existingData.created_at.trim() ? existingData.created_at : (/* @__PURE__ */ new Date()).toISOString();
|
|
25456
|
+
const title = typeof existingData.title === "string" && existingData.title.trim() || (useCanonicalTargets && targetPage ? targetPage.title : targetPage ? insightTitleForTarget(targetPage, scope) : `${scope.title} Notes`);
|
|
25457
|
+
const baseBody = parsed.content.trim() ? parsed.content.trim() : [
|
|
25458
|
+
`# ${title}`,
|
|
25459
|
+
"",
|
|
25460
|
+
useCanonicalTargets ? "Canonical page maintained by SwarmVault. Guided sessions stage replaceable update blocks here for approval." : "Human-curated insight page. Guided sessions stage replaceable update blocks here.",
|
|
25461
|
+
""
|
|
25462
|
+
].join("\n");
|
|
25463
|
+
const importance = questionAnswer(
|
|
25464
|
+
session.questions,
|
|
25465
|
+
"importance",
|
|
25466
|
+
"Capture the most important new ideas from this source before treating them as canonical."
|
|
25467
|
+
);
|
|
25468
|
+
const exclude = questionAnswer(
|
|
25469
|
+
session.questions,
|
|
25470
|
+
"exclude",
|
|
25471
|
+
"Keep uncertain or incidental details provisional until they matter to the research thread."
|
|
25472
|
+
);
|
|
25473
|
+
const conflictNotes = questionAnswer(
|
|
25474
|
+
session.questions,
|
|
25475
|
+
"conflicts",
|
|
25476
|
+
contradictions.length ? "Review the conflicting evidence before accepting any canonical summary changes." : "No explicit conflicts were called out."
|
|
25477
|
+
);
|
|
25478
|
+
const followups = questionAnswer(session.questions, "followups", "Track follow-up questions on the source session page.");
|
|
25479
|
+
const updateBlock = [
|
|
25480
|
+
`## Guided Session Update: ${scope.title}`,
|
|
25481
|
+
"",
|
|
25482
|
+
`Evidence State: \`${evidenceState}\``,
|
|
25483
|
+
`Session: [[outputs/source-sessions/${scope.id}|Guided Session]]`,
|
|
25484
|
+
`Source Guide: [[outputs/source-guides/${scope.id}|Source Guide]]`,
|
|
25485
|
+
"",
|
|
25486
|
+
"### What Matters Now",
|
|
25487
|
+
"",
|
|
25488
|
+
importance,
|
|
25489
|
+
"",
|
|
25490
|
+
"### Proposed Integration",
|
|
25491
|
+
"",
|
|
25492
|
+
targetPage ? `- Fold the strongest source-backed takeaways into [[${targetPage.path.replace(/\.md$/, "")}|${targetPage.title}]].` : `- Start a durable topic note for ${scope.title}.`,
|
|
25493
|
+
...analyses.slice(0, 5).map((analysis) => `- ${truncate(normalizeWhitespace(analysis.summary), 180)}`),
|
|
25494
|
+
"",
|
|
25495
|
+
"### Keep Provisional Or Out",
|
|
25496
|
+
"",
|
|
25497
|
+
exclude,
|
|
25498
|
+
"",
|
|
25499
|
+
"### Reinforcing Or Conflicting Notes",
|
|
25500
|
+
"",
|
|
25501
|
+
conflictNotes,
|
|
25502
|
+
...contradictions.length ? ["", ...contradictions.slice(0, 4).map((contradiction) => `- ${contradiction.claimA} / ${contradiction.claimB}`)] : [],
|
|
25503
|
+
"",
|
|
25504
|
+
"### Follow-up Questions",
|
|
25505
|
+
"",
|
|
25506
|
+
followups,
|
|
25507
|
+
""
|
|
25508
|
+
].join("\n");
|
|
25509
|
+
const nextBody = replaceMarkedSection(baseBody, scope.id, updateBlock);
|
|
25510
|
+
const content = matter11.stringify(
|
|
25511
|
+
`${nextBody.trimEnd()}
|
|
25512
|
+
`,
|
|
25513
|
+
JSON.parse(
|
|
25514
|
+
JSON.stringify({
|
|
25515
|
+
...existingData,
|
|
25516
|
+
page_id: typeof existingData.page_id === "string" && existingData.page_id.trim() || (useCanonicalTargets && targetPage ? targetPage.id : `insight:${slugify(relativePath.replace(/\.md$/, ""))}`),
|
|
25517
|
+
kind: useCanonicalTargets && targetPage ? targetPage.kind : "insight",
|
|
25518
|
+
title,
|
|
25519
|
+
tags: uniqueStrings4([
|
|
25520
|
+
...Array.isArray(existingData.tags) ? existingData.tags.filter((value) => typeof value === "string") : [],
|
|
25521
|
+
...useCanonicalTargets ? ["guided-session", `guided/${targetPage?.kind ?? "page"}`] : insightTagsForTarget(targetPage)
|
|
25522
|
+
]),
|
|
25523
|
+
source_ids: uniqueStrings4([...existingSourceIds, ...scope.sourceIds]),
|
|
25524
|
+
project_ids: uniqueStrings4([...existingProjectIds, ...targetPage?.projectIds ?? []]),
|
|
25525
|
+
node_ids: uniqueStrings4([...existingNodeIds, ...targetPage?.nodeIds ?? []]),
|
|
25526
|
+
freshness: "fresh",
|
|
25527
|
+
status: existingData.status === "archived" ? "archived" : "active",
|
|
25528
|
+
confidence: 0.83,
|
|
25529
|
+
created_at: createdAt,
|
|
25530
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25531
|
+
compiled_from: uniqueStrings4([
|
|
25532
|
+
...Array.isArray(existingData.compiled_from) ? existingData.compiled_from.filter((value) => typeof value === "string") : [],
|
|
25533
|
+
...scope.sourceIds
|
|
25534
|
+
]),
|
|
25535
|
+
managed_by: typeof existingData.managed_by === "string" && (existingData.managed_by === "human" || existingData.managed_by === "system") ? existingData.managed_by : useCanonicalTargets ? "system" : "human",
|
|
25536
|
+
backlinks: uniqueStrings4([
|
|
25537
|
+
...existingBacklinks,
|
|
25538
|
+
...targetPage ? [targetPage.id] : [],
|
|
25539
|
+
`output:source-sessions/${scope.id}`,
|
|
25540
|
+
`output:source-guides/${scope.id}`
|
|
25541
|
+
]),
|
|
25542
|
+
schema_hash: typeof existingData.schema_hash === "string" ? existingData.schema_hash : "",
|
|
25543
|
+
source_hashes: existingData.source_hashes && typeof existingData.source_hashes === "object" ? existingData.source_hashes : {},
|
|
25544
|
+
source_semantic_hashes: existingData.source_semantic_hashes && typeof existingData.source_semantic_hashes === "object" ? existingData.source_semantic_hashes : {},
|
|
25545
|
+
profile_presets: config.profile.presets,
|
|
25546
|
+
source_type: scopeSourceType(scope, scopeManifests),
|
|
25547
|
+
occurred_at: scopeOccurredAt(scopeManifests),
|
|
25548
|
+
participants: scopeParticipants(scopeManifests),
|
|
25549
|
+
container_title: scopeContainerTitle(scopeManifests),
|
|
25550
|
+
conversation_id: scopeConversationId(scopeManifests),
|
|
25551
|
+
session_status: session.status,
|
|
25552
|
+
question_state: questionStateForSession(session),
|
|
25553
|
+
canonical_targets: useCanonicalTargets ? selectedTargets.map((page2) => page2.path) : [],
|
|
25554
|
+
evidence_state: evidenceState
|
|
25555
|
+
})
|
|
25556
|
+
)
|
|
25557
|
+
);
|
|
25558
|
+
const page = parseStoredPage(relativePath, content, {
|
|
25559
|
+
createdAt,
|
|
25560
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25561
|
+
});
|
|
25562
|
+
if (!useCanonicalTargets && !selectedTargets.length) {
|
|
25563
|
+
session.targetedPagePaths = uniqueStrings4([...session.targetedPagePaths, relativePath]);
|
|
25564
|
+
}
|
|
25565
|
+
return { page, content, label: "guided-update" };
|
|
25566
|
+
})
|
|
24942
25567
|
);
|
|
24943
|
-
const workingDir = await managedSourceWorkingDir(rootDir, id);
|
|
24944
|
-
await fs22.rm(workingDir, { recursive: true, force: true });
|
|
24945
|
-
return { removed: target };
|
|
24946
|
-
}
|
|
24947
|
-
|
|
24948
|
-
// src/viewer.ts
|
|
24949
|
-
import { execFile as execFile2 } from "child_process";
|
|
24950
|
-
import fs23 from "fs/promises";
|
|
24951
|
-
import http from "http";
|
|
24952
|
-
import path28 from "path";
|
|
24953
|
-
import { promisify as promisify2 } from "util";
|
|
24954
|
-
import matter12 from "gray-matter";
|
|
24955
|
-
import mime2 from "mime-types";
|
|
24956
|
-
|
|
24957
|
-
// src/graph-presentation.ts
|
|
24958
|
-
var OVERVIEW_THRESHOLD = 5e3;
|
|
24959
|
-
var OVERVIEW_NODE_BUDGET = 1500;
|
|
24960
|
-
function nodePriority(node, pinnedNodeIds) {
|
|
24961
|
-
return [pinnedNodeIds.has(node.id) ? 0 : 1, -(node.degree ?? 0), -(node.bridgeScore ?? 0), node.label, node.id];
|
|
24962
|
-
}
|
|
24963
|
-
function compareTuples(left, right) {
|
|
24964
|
-
const length = Math.max(left.length, right.length);
|
|
24965
|
-
for (let index = 0; index < length; index += 1) {
|
|
24966
|
-
const leftValue = left[index];
|
|
24967
|
-
const rightValue = right[index];
|
|
24968
|
-
if (leftValue === rightValue) {
|
|
24969
|
-
continue;
|
|
24970
|
-
}
|
|
24971
|
-
if (typeof leftValue === "number" && typeof rightValue === "number") {
|
|
24972
|
-
return leftValue - rightValue;
|
|
24973
|
-
}
|
|
24974
|
-
return String(leftValue ?? "").localeCompare(String(rightValue ?? ""));
|
|
24975
|
-
}
|
|
24976
|
-
return 0;
|
|
24977
25568
|
}
|
|
24978
|
-
function
|
|
24979
|
-
|
|
24980
|
-
|
|
24981
|
-
|
|
24982
|
-
if (
|
|
24983
|
-
|
|
24984
|
-
}
|
|
24985
|
-
return /* @__PURE__ */ new Set([
|
|
24986
|
-
...report.godNodes.map((node) => node.nodeId),
|
|
24987
|
-
...report.bridgeNodes.map((node) => node.nodeId),
|
|
24988
|
-
...report.surprisingConnections.flatMap((connection) => [connection.sourceNodeId, connection.targetNodeId])
|
|
24989
|
-
]);
|
|
24990
|
-
}
|
|
24991
|
-
function sampleGraphNodes(graph, report, nodeBudget = OVERVIEW_NODE_BUDGET) {
|
|
24992
|
-
const pinned = pinnedNodeIdsForReport(report);
|
|
24993
|
-
const nodeById2 = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
24994
|
-
const selected = new Set([...pinned].filter((nodeId) => nodeById2.has(nodeId)));
|
|
24995
|
-
const sortedCommunities2 = [...graph.communities ?? []].sort((left, right) => {
|
|
24996
|
-
const leftNodes = left.nodeIds.map((nodeId) => nodeById2.get(nodeId)).filter((node) => Boolean(node));
|
|
24997
|
-
const rightNodes = right.nodeIds.map((nodeId) => nodeById2.get(nodeId)).filter((node) => Boolean(node));
|
|
24998
|
-
const leftFirstParty = leftNodes.filter((node) => node.sourceClass === "first_party").length;
|
|
24999
|
-
const rightFirstParty = rightNodes.filter((node) => node.sourceClass === "first_party").length;
|
|
25000
|
-
return compareTuples(
|
|
25001
|
-
[-leftFirstParty, -leftNodes.length, left.label, left.id],
|
|
25002
|
-
[-rightFirstParty, -rightNodes.length, right.label, right.id]
|
|
25003
|
-
);
|
|
25004
|
-
});
|
|
25005
|
-
for (const community of sortedCommunities2) {
|
|
25006
|
-
const communityNodes = community.nodeIds.map((nodeId) => nodeById2.get(nodeId)).filter((node) => Boolean(node)).sort((left, right) => compareTuples(nodePriority(left, pinned), nodePriority(right, pinned)));
|
|
25007
|
-
for (const node of communityNodes) {
|
|
25008
|
-
if (selected.size >= nodeBudget && !pinned.has(node.id)) {
|
|
25009
|
-
break;
|
|
25010
|
-
}
|
|
25011
|
-
selected.add(node.id);
|
|
25012
|
-
}
|
|
25013
|
-
if (selected.size >= nodeBudget) {
|
|
25014
|
-
break;
|
|
25015
|
-
}
|
|
25016
|
-
}
|
|
25017
|
-
if (selected.size < nodeBudget) {
|
|
25018
|
-
for (const node of [...graph.nodes].sort((left, right) => compareTuples(nodePriority(left, pinned), nodePriority(right, pinned)))) {
|
|
25019
|
-
if (selected.size >= nodeBudget && !pinned.has(node.id)) {
|
|
25020
|
-
break;
|
|
25021
|
-
}
|
|
25022
|
-
selected.add(node.id);
|
|
25023
|
-
}
|
|
25569
|
+
async function stageSourceGuideForScope(rootDir, scope, options = {}) {
|
|
25570
|
+
const { session, statePath } = await prepareGuidedSourceSession(rootDir, scope, options.answers);
|
|
25571
|
+
const briefPath = scope.briefPath ?? session.briefPath ?? await writeSourceBriefForScope(rootDir, scope) ?? void 0;
|
|
25572
|
+
session.briefPath = briefPath;
|
|
25573
|
+
if (briefPath) {
|
|
25574
|
+
await refreshVaultAfterOutputSave(rootDir);
|
|
25024
25575
|
}
|
|
25025
|
-
|
|
25026
|
-
|
|
25027
|
-
|
|
25028
|
-
|
|
25029
|
-
|
|
25030
|
-
|
|
25031
|
-
if (options.full || graph.nodes.length <= threshold) {
|
|
25576
|
+
if (answeredGuidedSessionQuestions(session.questions).length === 0) {
|
|
25577
|
+
session.status = "awaiting_input";
|
|
25578
|
+
const persisted2 = await persistSourceSessionPage(rootDir, scope, session);
|
|
25579
|
+
session.sessionPath = persisted2.sessionPath;
|
|
25580
|
+
await writeGuidedSourceSession(rootDir, session);
|
|
25581
|
+
await refreshVaultAfterOutputSave(rootDir);
|
|
25032
25582
|
return {
|
|
25033
|
-
|
|
25034
|
-
|
|
25035
|
-
|
|
25036
|
-
|
|
25037
|
-
|
|
25038
|
-
|
|
25039
|
-
|
|
25040
|
-
|
|
25041
|
-
|
|
25042
|
-
|
|
25043
|
-
|
|
25044
|
-
}
|
|
25583
|
+
sourceId: scope.id,
|
|
25584
|
+
sessionId: session.sessionId,
|
|
25585
|
+
sessionPath: persisted2.sessionPath,
|
|
25586
|
+
sessionStatePath: statePath,
|
|
25587
|
+
status: session.status,
|
|
25588
|
+
questions: session.questions,
|
|
25589
|
+
awaitingInput: true,
|
|
25590
|
+
targetedPagePaths: session.targetedPagePaths,
|
|
25591
|
+
stagedUpdatePaths: session.stagedUpdatePaths,
|
|
25592
|
+
briefPath,
|
|
25593
|
+
staged: false
|
|
25045
25594
|
};
|
|
25046
25595
|
}
|
|
25047
|
-
|
|
25048
|
-
|
|
25049
|
-
const
|
|
25050
|
-
const
|
|
25051
|
-
|
|
25052
|
-
|
|
25053
|
-
|
|
25054
|
-
|
|
25055
|
-
|
|
25056
|
-
|
|
25057
|
-
|
|
25058
|
-
|
|
25059
|
-
|
|
25060
|
-
|
|
25061
|
-
|
|
25062
|
-
|
|
25063
|
-
|
|
25064
|
-
|
|
25065
|
-
|
|
25066
|
-
|
|
25067
|
-
|
|
25068
|
-
|
|
25069
|
-
|
|
25070
|
-
|
|
25071
|
-
|
|
25072
|
-
|
|
25073
|
-
|
|
25074
|
-
|
|
25075
|
-
|
|
25076
|
-
|
|
25077
|
-
|
|
25078
|
-
|
|
25079
|
-
|
|
25080
|
-
|
|
25081
|
-
|
|
25082
|
-
|
|
25083
|
-
|
|
25084
|
-
|
|
25085
|
-
|
|
25086
|
-
|
|
25087
|
-
|
|
25088
|
-
|
|
25089
|
-
|
|
25090
|
-
|
|
25091
|
-
|
|
25092
|
-
|
|
25093
|
-
|
|
25094
|
-
|
|
25095
|
-
|
|
25096
|
-
".kt",
|
|
25097
|
-
".kts",
|
|
25098
|
-
".scala",
|
|
25099
|
-
".sc",
|
|
25100
|
-
".dart",
|
|
25101
|
-
".lua",
|
|
25102
|
-
".zig",
|
|
25103
|
-
".cs",
|
|
25104
|
-
".php",
|
|
25105
|
-
".rb",
|
|
25106
|
-
".swift",
|
|
25107
|
-
".c",
|
|
25108
|
-
".h",
|
|
25109
|
-
".cpp",
|
|
25110
|
-
".cc",
|
|
25111
|
-
".cxx",
|
|
25112
|
-
".hpp",
|
|
25113
|
-
".hxx",
|
|
25114
|
-
".sh",
|
|
25115
|
-
".bash",
|
|
25116
|
-
".zsh",
|
|
25117
|
-
".ps1",
|
|
25118
|
-
".psm1",
|
|
25119
|
-
".ex",
|
|
25120
|
-
".exs",
|
|
25121
|
-
".jl",
|
|
25122
|
-
".r",
|
|
25123
|
-
".R"
|
|
25124
|
-
]);
|
|
25125
|
-
var FILE_CHANGE_RE = /^(?:add|change|unlink):(.+)$/;
|
|
25126
|
-
function isCodeOnlyChange(reasons) {
|
|
25127
|
-
for (const reason of reasons) {
|
|
25128
|
-
const match = reason.match(FILE_CHANGE_RE);
|
|
25129
|
-
if (!match) return false;
|
|
25130
|
-
const ext = path27.extname(match[1]).toLowerCase();
|
|
25131
|
-
if (!ext || !CODE_EXTENSIONS.has(ext)) return false;
|
|
25132
|
-
}
|
|
25133
|
-
return reasons.size > 0;
|
|
25596
|
+
session.status = "ready_to_stage";
|
|
25597
|
+
await writeGuidedSourceSession(rootDir, session);
|
|
25598
|
+
const reviewOutput = await buildSourceReviewStagedPage(rootDir, scope);
|
|
25599
|
+
const guideOutput = await buildSourceGuideStagedPage(rootDir, {
|
|
25600
|
+
...scope,
|
|
25601
|
+
briefPath
|
|
25602
|
+
});
|
|
25603
|
+
const guidedUpdates = await buildGuidedUpdatePages(rootDir, scope, session);
|
|
25604
|
+
session.stagedUpdatePaths = guidedUpdates.map((item) => item.page.path);
|
|
25605
|
+
const approval = await stageGeneratedOutputPages(
|
|
25606
|
+
rootDir,
|
|
25607
|
+
[
|
|
25608
|
+
{ page: reviewOutput.page, content: reviewOutput.content, label: "source-review" },
|
|
25609
|
+
{ page: guideOutput.page, content: guideOutput.content, label: "source-guide" },
|
|
25610
|
+
...guidedUpdates
|
|
25611
|
+
],
|
|
25612
|
+
{
|
|
25613
|
+
bundleType: "guided-session",
|
|
25614
|
+
title: `Guided Session: ${scope.title}`,
|
|
25615
|
+
sourceSessionId: session.sessionId
|
|
25616
|
+
}
|
|
25617
|
+
);
|
|
25618
|
+
session.status = "staged";
|
|
25619
|
+
session.reviewPath = path27.join(approval.approvalDir, "wiki", reviewOutput.page.path);
|
|
25620
|
+
session.guidePath = path27.join(approval.approvalDir, "wiki", guideOutput.page.path);
|
|
25621
|
+
session.approvalId = approval.approvalId;
|
|
25622
|
+
session.approvalDir = approval.approvalDir;
|
|
25623
|
+
const persisted = await persistSourceSessionPage(rootDir, scope, session);
|
|
25624
|
+
session.sessionPath = persisted.sessionPath;
|
|
25625
|
+
await writeGuidedSourceSession(rootDir, session);
|
|
25626
|
+
await refreshVaultAfterOutputSave(rootDir);
|
|
25627
|
+
return {
|
|
25628
|
+
sourceId: scope.id,
|
|
25629
|
+
pageId: guideOutput.page.id,
|
|
25630
|
+
guidePath: session.guidePath,
|
|
25631
|
+
reviewPageId: reviewOutput.page.id,
|
|
25632
|
+
reviewPath: session.reviewPath,
|
|
25633
|
+
sessionId: session.sessionId,
|
|
25634
|
+
sessionPath: persisted.sessionPath,
|
|
25635
|
+
sessionStatePath: statePath,
|
|
25636
|
+
status: session.status,
|
|
25637
|
+
questions: session.questions,
|
|
25638
|
+
targetedPagePaths: session.targetedPagePaths,
|
|
25639
|
+
stagedUpdatePaths: session.stagedUpdatePaths,
|
|
25640
|
+
briefPath,
|
|
25641
|
+
staged: true,
|
|
25642
|
+
approvalId: approval.approvalId,
|
|
25643
|
+
approvalDir: approval.approvalDir
|
|
25644
|
+
};
|
|
25134
25645
|
}
|
|
25135
|
-
function
|
|
25136
|
-
|
|
25137
|
-
|
|
25138
|
-
|
|
25139
|
-
|
|
25140
|
-
|
|
25141
|
-
|
|
25142
|
-
|
|
25646
|
+
function scopeFromManagedSource(source) {
|
|
25647
|
+
return {
|
|
25648
|
+
id: source.id,
|
|
25649
|
+
title: source.title,
|
|
25650
|
+
sourceIds: source.sourceIds,
|
|
25651
|
+
kind: source.kind,
|
|
25652
|
+
briefPath: source.briefPath
|
|
25653
|
+
};
|
|
25143
25654
|
}
|
|
25144
|
-
function
|
|
25145
|
-
const
|
|
25146
|
-
|
|
25147
|
-
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
25152
|
-
const ext = path27.extname(match[1]).toLowerCase();
|
|
25153
|
-
if (!ext || !CODE_EXTENSIONS.has(ext)) result.push(match[1]);
|
|
25154
|
-
}
|
|
25155
|
-
return result;
|
|
25655
|
+
function scopeFromManifest(manifest, manifests) {
|
|
25656
|
+
const groupId = manifest.sourceGroupId ?? manifest.sourceId;
|
|
25657
|
+
return {
|
|
25658
|
+
id: groupId,
|
|
25659
|
+
title: manifest.sourceGroupTitle ?? manifest.title,
|
|
25660
|
+
sourceIds: manifest.sourceGroupId ? manifests.filter((candidate) => candidate.sourceGroupId === manifest.sourceGroupId).map((candidate) => candidate.sourceId) : [manifest.sourceId],
|
|
25661
|
+
kind: manifest.sourceKind
|
|
25662
|
+
};
|
|
25156
25663
|
}
|
|
25157
|
-
function
|
|
25158
|
-
const
|
|
25159
|
-
|
|
25160
|
-
|
|
25664
|
+
async function resolveSourceScope(rootDir, id) {
|
|
25665
|
+
const managedSources = await loadManagedSources(rootDir);
|
|
25666
|
+
const managedSource = managedSources.find((source) => source.id === id);
|
|
25667
|
+
if (managedSource) {
|
|
25668
|
+
return scopeFromManagedSource(managedSource);
|
|
25669
|
+
}
|
|
25670
|
+
const latestSession = await findLatestGuidedSourceSessionByScope(rootDir, id);
|
|
25671
|
+
if (latestSession) {
|
|
25672
|
+
return {
|
|
25673
|
+
id: latestSession.scopeId,
|
|
25674
|
+
title: latestSession.scopeTitle,
|
|
25675
|
+
sourceIds: latestSession.sourceIds
|
|
25676
|
+
};
|
|
25677
|
+
}
|
|
25678
|
+
const manifests = await listManifests(rootDir);
|
|
25679
|
+
const manifest = manifests.find((candidate) => candidate.sourceId === id) ?? manifests.find((candidate) => candidate.sourceGroupId === id);
|
|
25680
|
+
if (!manifest) {
|
|
25681
|
+
return null;
|
|
25161
25682
|
}
|
|
25162
|
-
return
|
|
25683
|
+
return scopeFromManifest(manifest, manifests);
|
|
25163
25684
|
}
|
|
25164
|
-
function
|
|
25165
|
-
return
|
|
25166
|
-
paths.rawDir,
|
|
25167
|
-
paths.wikiDir,
|
|
25168
|
-
paths.stateDir,
|
|
25169
|
-
paths.agentDir,
|
|
25170
|
-
paths.inboxDir,
|
|
25171
|
-
path27.join(rootDir, ".claude"),
|
|
25172
|
-
path27.join(rootDir, ".cursor"),
|
|
25173
|
-
path27.join(rootDir, ".obsidian")
|
|
25174
|
-
].map((candidate) => path27.resolve(candidate));
|
|
25685
|
+
async function reviewSourceScope(rootDir, scope) {
|
|
25686
|
+
return await stageSourceReviewForScope(rootDir, scope);
|
|
25175
25687
|
}
|
|
25176
|
-
async function
|
|
25177
|
-
|
|
25178
|
-
|
|
25179
|
-
|
|
25180
|
-
|
|
25181
|
-
|
|
25688
|
+
async function guideSourceScope(rootDir, scope, options = {}) {
|
|
25689
|
+
return await stageSourceGuideForScope(rootDir, scope, options);
|
|
25690
|
+
}
|
|
25691
|
+
async function reviewManagedSource(rootDir, id) {
|
|
25692
|
+
const scope = await resolveSourceScope(rootDir, id);
|
|
25693
|
+
if (!scope) {
|
|
25694
|
+
throw new Error(`Managed source or source id not found: ${id}`);
|
|
25182
25695
|
}
|
|
25183
|
-
|
|
25696
|
+
if (!await loadVaultConfig(rootDir).then(({ paths }) => fileExists(paths.graphPath))) {
|
|
25697
|
+
await compileVault(rootDir, {});
|
|
25698
|
+
}
|
|
25699
|
+
return await stageSourceReviewForScope(rootDir, scope);
|
|
25184
25700
|
}
|
|
25185
|
-
async function
|
|
25186
|
-
const
|
|
25187
|
-
|
|
25188
|
-
|
|
25189
|
-
|
|
25190
|
-
|
|
25191
|
-
rootDir,
|
|
25192
|
-
|
|
25193
|
-
);
|
|
25194
|
-
const lintFindingCount = options.lint ? (await lintVault(rootDir)).length : void 0;
|
|
25195
|
-
return {
|
|
25196
|
-
watchedRepoRoots: repoSync?.repoRoots ?? [],
|
|
25197
|
-
importedCount: imported.imported.length,
|
|
25198
|
-
scannedCount: imported.scannedCount,
|
|
25199
|
-
attachmentCount: imported.attachmentCount,
|
|
25200
|
-
repoImportedCount: repoSync?.imported.length ?? 0,
|
|
25201
|
-
repoUpdatedCount: repoSync?.updated.length ?? 0,
|
|
25202
|
-
repoRemovedCount: repoSync?.removed.length ?? 0,
|
|
25203
|
-
repoScannedCount: repoSync?.scannedCount ?? 0,
|
|
25204
|
-
pendingSemanticRefreshCount: pendingSemanticRefresh.length,
|
|
25205
|
-
pendingSemanticRefreshPaths: pendingSemanticRefresh.map((entry) => entry.path),
|
|
25206
|
-
changedPages: [.../* @__PURE__ */ new Set([...compile.changedPages, ...stalePagePaths])],
|
|
25207
|
-
lintFindingCount
|
|
25208
|
-
};
|
|
25701
|
+
async function guideManagedSource(rootDir, id, options = {}) {
|
|
25702
|
+
const scope = await resolveSourceScope(rootDir, id);
|
|
25703
|
+
if (!scope) {
|
|
25704
|
+
throw new Error(`Managed source or source id not found: ${id}`);
|
|
25705
|
+
}
|
|
25706
|
+
if (!await loadVaultConfig(rootDir).then(({ paths }) => fileExists(paths.graphPath))) {
|
|
25707
|
+
await compileVault(rootDir, {});
|
|
25708
|
+
}
|
|
25709
|
+
return await stageSourceGuideForScope(rootDir, scope, options);
|
|
25209
25710
|
}
|
|
25210
|
-
async function
|
|
25211
|
-
const
|
|
25212
|
-
|
|
25213
|
-
|
|
25214
|
-
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
|
|
25220
|
-
|
|
25221
|
-
repoUpdatedCount: 0,
|
|
25222
|
-
repoRemovedCount: 0,
|
|
25223
|
-
repoScannedCount: 0,
|
|
25224
|
-
pendingSemanticRefreshCount: 0,
|
|
25225
|
-
pendingSemanticRefreshPaths: [],
|
|
25226
|
-
changedPages: []
|
|
25227
|
-
};
|
|
25228
|
-
try {
|
|
25229
|
-
result = await performWatchCycle(rootDir, paths, options, options.codeOnly ?? false);
|
|
25230
|
-
return result;
|
|
25231
|
-
} catch (caught) {
|
|
25232
|
-
success = false;
|
|
25233
|
-
error = caught instanceof Error ? caught.message : String(caught);
|
|
25234
|
-
throw caught;
|
|
25235
|
-
} finally {
|
|
25236
|
-
const finishedAt = /* @__PURE__ */ new Date();
|
|
25237
|
-
await recordSession(rootDir, {
|
|
25238
|
-
operation: "watch",
|
|
25239
|
-
title: `Watch cycle for ${paths.inboxDir}${options.repo ? " and tracked repos" : ""}`,
|
|
25240
|
-
startedAt: startedAt.toISOString(),
|
|
25241
|
-
finishedAt: finishedAt.toISOString(),
|
|
25242
|
-
success,
|
|
25243
|
-
error,
|
|
25244
|
-
changedPages: result.changedPages,
|
|
25245
|
-
lintFindingCount: result.lintFindingCount,
|
|
25246
|
-
lines: [
|
|
25247
|
-
"reasons=once",
|
|
25248
|
-
`imported=${result.importedCount}`,
|
|
25249
|
-
`scanned=${result.scannedCount}`,
|
|
25250
|
-
`attachments=${result.attachmentCount}`,
|
|
25251
|
-
`repo_scanned=${result.repoScannedCount}`,
|
|
25252
|
-
`repo_imported=${result.repoImportedCount}`,
|
|
25253
|
-
`repo_updated=${result.repoUpdatedCount}`,
|
|
25254
|
-
`repo_removed=${result.repoRemovedCount}`,
|
|
25255
|
-
`pending_semantic_refresh=${result.pendingSemanticRefreshCount}`,
|
|
25256
|
-
`lint=${result.lintFindingCount ?? 0}`
|
|
25257
|
-
]
|
|
25258
|
-
});
|
|
25259
|
-
await appendWatchRun(rootDir, {
|
|
25260
|
-
startedAt: startedAt.toISOString(),
|
|
25261
|
-
finishedAt: finishedAt.toISOString(),
|
|
25262
|
-
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
25263
|
-
inputDir: paths.inboxDir,
|
|
25264
|
-
reasons: ["once"],
|
|
25265
|
-
importedCount: result.importedCount + result.repoImportedCount + result.repoUpdatedCount,
|
|
25266
|
-
scannedCount: result.scannedCount + result.repoScannedCount,
|
|
25267
|
-
attachmentCount: result.attachmentCount,
|
|
25268
|
-
changedPages: result.changedPages,
|
|
25269
|
-
repoImportedCount: result.repoImportedCount,
|
|
25270
|
-
repoUpdatedCount: result.repoUpdatedCount,
|
|
25271
|
-
repoRemovedCount: result.repoRemovedCount,
|
|
25272
|
-
repoScannedCount: result.repoScannedCount,
|
|
25273
|
-
pendingSemanticRefreshCount: result.pendingSemanticRefreshCount,
|
|
25274
|
-
pendingSemanticRefreshPaths: result.pendingSemanticRefreshPaths,
|
|
25275
|
-
lintFindingCount: result.lintFindingCount,
|
|
25276
|
-
success,
|
|
25277
|
-
error
|
|
25278
|
-
});
|
|
25279
|
-
await writeWatchStatusArtifact(rootDir, {
|
|
25280
|
-
generatedAt: finishedAt.toISOString(),
|
|
25281
|
-
watchedRepoRoots: result.watchedRepoRoots,
|
|
25282
|
-
lastRun: {
|
|
25283
|
-
startedAt: startedAt.toISOString(),
|
|
25284
|
-
finishedAt: finishedAt.toISOString(),
|
|
25285
|
-
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
25286
|
-
inputDir: paths.inboxDir,
|
|
25287
|
-
reasons: ["once"],
|
|
25288
|
-
importedCount: result.importedCount + result.repoImportedCount + result.repoUpdatedCount,
|
|
25289
|
-
scannedCount: result.scannedCount + result.repoScannedCount,
|
|
25290
|
-
attachmentCount: result.attachmentCount,
|
|
25291
|
-
changedPages: result.changedPages,
|
|
25292
|
-
repoImportedCount: result.repoImportedCount,
|
|
25293
|
-
repoUpdatedCount: result.repoUpdatedCount,
|
|
25294
|
-
repoRemovedCount: result.repoRemovedCount,
|
|
25295
|
-
repoScannedCount: result.repoScannedCount,
|
|
25296
|
-
pendingSemanticRefreshCount: result.pendingSemanticRefreshCount,
|
|
25297
|
-
pendingSemanticRefreshPaths: result.pendingSemanticRefreshPaths,
|
|
25298
|
-
lintFindingCount: result.lintFindingCount,
|
|
25299
|
-
success,
|
|
25300
|
-
error
|
|
25711
|
+
async function resumeSourceSession(rootDir, id, options = {}) {
|
|
25712
|
+
const existingSession = await readGuidedSourceSession(rootDir, id);
|
|
25713
|
+
if (existingSession) {
|
|
25714
|
+
return await stageSourceGuideForScope(
|
|
25715
|
+
rootDir,
|
|
25716
|
+
{
|
|
25717
|
+
id: existingSession.scopeId,
|
|
25718
|
+
title: existingSession.scopeTitle,
|
|
25719
|
+
sourceIds: existingSession.sourceIds,
|
|
25720
|
+
kind: existingSession.kind,
|
|
25721
|
+
briefPath: existingSession.briefPath
|
|
25301
25722
|
},
|
|
25302
|
-
|
|
25303
|
-
|
|
25723
|
+
options
|
|
25724
|
+
);
|
|
25725
|
+
}
|
|
25726
|
+
const scope = await resolveSourceScope(rootDir, id);
|
|
25727
|
+
if (!scope) {
|
|
25728
|
+
throw new Error(`Managed source, source scope, or guided session not found: ${id}`);
|
|
25729
|
+
}
|
|
25730
|
+
return await stageSourceGuideForScope(rootDir, scope, options);
|
|
25731
|
+
}
|
|
25732
|
+
function shouldCompile(changedSources, graphExists, compileRequested) {
|
|
25733
|
+
return compileRequested && (!graphExists || changedSources.length > 0);
|
|
25734
|
+
}
|
|
25735
|
+
async function shouldRefreshBriefForManagedSource(source, options) {
|
|
25736
|
+
if (options.compilePerformed || options.changed) {
|
|
25737
|
+
return true;
|
|
25738
|
+
}
|
|
25739
|
+
if (!source.briefPath) {
|
|
25740
|
+
return true;
|
|
25304
25741
|
}
|
|
25742
|
+
return !await fileExists(source.briefPath);
|
|
25305
25743
|
}
|
|
25306
|
-
async function
|
|
25307
|
-
|
|
25308
|
-
|
|
25309
|
-
|
|
25310
|
-
|
|
25311
|
-
|
|
25312
|
-
|
|
25313
|
-
|
|
25314
|
-
|
|
25315
|
-
|
|
25316
|
-
|
|
25317
|
-
|
|
25318
|
-
const
|
|
25319
|
-
|
|
25320
|
-
|
|
25321
|
-
|
|
25322
|
-
|
|
25323
|
-
|
|
25324
|
-
|
|
25325
|
-
|
|
25326
|
-
|
|
25327
|
-
|
|
25328
|
-
|
|
25329
|
-
|
|
25330
|
-
if (primaryTarget !== inboxWatchRoot && ignoredRoots.some((ignoreRoot) => isPathWithin(ignoreRoot, absolutePath))) {
|
|
25331
|
-
return true;
|
|
25332
|
-
}
|
|
25333
|
-
return hasIgnoredRepoSegment(primaryTarget, absolutePath);
|
|
25334
|
-
},
|
|
25335
|
-
awaitWriteFinish: {
|
|
25336
|
-
stabilityThreshold: Math.max(250, Math.floor(baseDebounceMs / 2)),
|
|
25337
|
-
pollInterval: 100
|
|
25338
|
-
}
|
|
25339
|
-
});
|
|
25340
|
-
const syncWatchTargets = async () => {
|
|
25341
|
-
const nextTargets = await resolveWatchTargets(rootDir, paths, options);
|
|
25342
|
-
const nextSet = new Set(nextTargets);
|
|
25343
|
-
const currentSet = new Set(watchTargets);
|
|
25344
|
-
const toRemove = watchTargets.filter((target) => !nextSet.has(target));
|
|
25345
|
-
const toAdd = nextTargets.filter((target) => !currentSet.has(target));
|
|
25346
|
-
if (toRemove.length > 0) {
|
|
25347
|
-
await watcher.unwatch(toRemove);
|
|
25348
|
-
}
|
|
25349
|
-
if (toAdd.length > 0) {
|
|
25350
|
-
await watcher.add(toAdd);
|
|
25351
|
-
}
|
|
25352
|
-
watchTargets = nextTargets;
|
|
25353
|
-
};
|
|
25354
|
-
const schedule = (reason) => {
|
|
25355
|
-
if (closed) {
|
|
25356
|
-
return;
|
|
25357
|
-
}
|
|
25358
|
-
reasons.add(reason);
|
|
25359
|
-
pending = true;
|
|
25360
|
-
if (timer) {
|
|
25361
|
-
clearTimeout(timer);
|
|
25362
|
-
}
|
|
25363
|
-
timer = setTimeout(() => {
|
|
25364
|
-
const cycle = runCycle();
|
|
25365
|
-
activeCycle = cycle.finally(() => {
|
|
25366
|
-
if (activeCycle === cycle) {
|
|
25367
|
-
activeCycle = null;
|
|
25368
|
-
}
|
|
25369
|
-
});
|
|
25370
|
-
}, currentDebounceMs);
|
|
25744
|
+
async function listManagedSourceRecords(rootDir) {
|
|
25745
|
+
await ensureManagedSourcesArtifact(rootDir);
|
|
25746
|
+
return await loadManagedSources(rootDir);
|
|
25747
|
+
}
|
|
25748
|
+
async function addManagedSource(rootDir, input, options = {}) {
|
|
25749
|
+
const compileRequested = options.compile ?? true;
|
|
25750
|
+
const guideRequested = options.guide ?? false;
|
|
25751
|
+
const briefRequested = guideRequested ? true : options.brief ?? true;
|
|
25752
|
+
const reviewRequested = guideRequested ? false : options.review ?? false;
|
|
25753
|
+
const sources = await loadManagedSources(rootDir);
|
|
25754
|
+
const resolved = await resolveManagedSourceInput(rootDir, input);
|
|
25755
|
+
const existing = sources.find((candidate) => matchesManagedSourceSpec(candidate, resolved));
|
|
25756
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25757
|
+
const source = existing ?? {
|
|
25758
|
+
id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path27.resolve(resolved.path), resolved.title) : stableManagedSourceId(resolved.kind, resolved.url, resolved.title),
|
|
25759
|
+
kind: resolved.kind,
|
|
25760
|
+
title: resolved.title,
|
|
25761
|
+
path: resolved.kind === "directory" || resolved.kind === "file" ? resolved.path : void 0,
|
|
25762
|
+
repoRoot: resolved.kind === "directory" ? resolved.repoRoot : void 0,
|
|
25763
|
+
url: resolved.kind === "directory" || resolved.kind === "file" ? void 0 : resolved.url,
|
|
25764
|
+
createdAt: now,
|
|
25765
|
+
updatedAt: now,
|
|
25766
|
+
status: "ready",
|
|
25767
|
+
sourceIds: []
|
|
25371
25768
|
};
|
|
25372
|
-
const
|
|
25373
|
-
|
|
25374
|
-
|
|
25375
|
-
|
|
25376
|
-
|
|
25377
|
-
|
|
25378
|
-
|
|
25379
|
-
|
|
25380
|
-
|
|
25381
|
-
|
|
25382
|
-
|
|
25383
|
-
|
|
25384
|
-
|
|
25385
|
-
|
|
25386
|
-
|
|
25387
|
-
|
|
25388
|
-
|
|
25389
|
-
|
|
25390
|
-
|
|
25391
|
-
|
|
25392
|
-
|
|
25393
|
-
|
|
25394
|
-
|
|
25395
|
-
let attachmentCount = 0;
|
|
25396
|
-
let repoImportedCount = 0;
|
|
25397
|
-
let repoUpdatedCount = 0;
|
|
25398
|
-
let repoRemovedCount = 0;
|
|
25399
|
-
let repoScannedCount = 0;
|
|
25400
|
-
let watchedRepoRoots = [];
|
|
25401
|
-
let pendingSemanticRefreshCount = 0;
|
|
25402
|
-
let pendingSemanticRefreshPaths = [];
|
|
25403
|
-
let changedPages = [];
|
|
25404
|
-
let lintFindingCount;
|
|
25405
|
-
let success = true;
|
|
25406
|
-
let error;
|
|
25407
|
-
try {
|
|
25408
|
-
const result = await performWatchCycle(rootDir, paths, options, codeOnlyChange);
|
|
25409
|
-
importedCount = result.importedCount;
|
|
25410
|
-
scannedCount = result.scannedCount;
|
|
25411
|
-
attachmentCount = result.attachmentCount;
|
|
25412
|
-
repoImportedCount = result.repoImportedCount;
|
|
25413
|
-
repoUpdatedCount = result.repoUpdatedCount;
|
|
25414
|
-
repoRemovedCount = result.repoRemovedCount;
|
|
25415
|
-
repoScannedCount = result.repoScannedCount;
|
|
25416
|
-
watchedRepoRoots = result.watchedRepoRoots;
|
|
25417
|
-
pendingSemanticRefreshCount = result.pendingSemanticRefreshCount;
|
|
25418
|
-
pendingSemanticRefreshPaths = result.pendingSemanticRefreshPaths;
|
|
25419
|
-
changedPages = result.changedPages;
|
|
25420
|
-
lintFindingCount = result.lintFindingCount;
|
|
25421
|
-
consecutiveFailures = 0;
|
|
25422
|
-
currentDebounceMs = baseDebounceMs;
|
|
25423
|
-
await syncWatchTargets();
|
|
25424
|
-
} catch (caught) {
|
|
25425
|
-
success = false;
|
|
25426
|
-
error = caught instanceof Error ? caught.message : String(caught);
|
|
25427
|
-
consecutiveFailures++;
|
|
25428
|
-
pending = true;
|
|
25429
|
-
if (consecutiveFailures >= CRITICAL_THRESHOLD) {
|
|
25430
|
-
process3.stderr.write(
|
|
25431
|
-
`[swarmvault watch] ${consecutiveFailures} consecutive failures. Check vault state. Continuing at max backoff.
|
|
25432
|
-
`
|
|
25433
|
-
);
|
|
25434
|
-
}
|
|
25435
|
-
if (consecutiveFailures >= BACKOFF_THRESHOLD) {
|
|
25436
|
-
const multiplier = 2 ** (consecutiveFailures - BACKOFF_THRESHOLD);
|
|
25437
|
-
currentDebounceMs = Math.min(baseDebounceMs * multiplier, MAX_BACKOFF_MS);
|
|
25438
|
-
}
|
|
25439
|
-
} finally {
|
|
25440
|
-
const finishedAt = /* @__PURE__ */ new Date();
|
|
25441
|
-
try {
|
|
25442
|
-
await recordSession(rootDir, {
|
|
25443
|
-
operation: "watch",
|
|
25444
|
-
title: `Watch cycle for ${paths.inboxDir}${options.repo ? " and tracked repos" : ""}`,
|
|
25445
|
-
startedAt: startedAt.toISOString(),
|
|
25446
|
-
finishedAt: finishedAt.toISOString(),
|
|
25447
|
-
success,
|
|
25448
|
-
error,
|
|
25449
|
-
changedPages,
|
|
25450
|
-
lintFindingCount,
|
|
25451
|
-
lines: [
|
|
25452
|
-
`reasons=${runReasons.join(",") || "none"}`,
|
|
25453
|
-
`code_only=${codeOnlyChange}`,
|
|
25454
|
-
`imported=${importedCount}`,
|
|
25455
|
-
`scanned=${scannedCount}`,
|
|
25456
|
-
`attachments=${attachmentCount}`,
|
|
25457
|
-
`repo_scanned=${repoScannedCount}`,
|
|
25458
|
-
`repo_imported=${repoImportedCount}`,
|
|
25459
|
-
`repo_updated=${repoUpdatedCount}`,
|
|
25460
|
-
`repo_removed=${repoRemovedCount}`,
|
|
25461
|
-
`lint=${lintFindingCount ?? 0}`
|
|
25462
|
-
]
|
|
25463
|
-
});
|
|
25464
|
-
} catch {
|
|
25465
|
-
process3.stderr.write("[swarmvault watch] Failed to record session log.\n");
|
|
25466
|
-
}
|
|
25467
|
-
try {
|
|
25468
|
-
await appendWatchRun(rootDir, {
|
|
25469
|
-
startedAt: startedAt.toISOString(),
|
|
25470
|
-
finishedAt: finishedAt.toISOString(),
|
|
25471
|
-
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
25472
|
-
inputDir: paths.inboxDir,
|
|
25473
|
-
reasons: runReasons,
|
|
25474
|
-
importedCount: importedCount + repoImportedCount + repoUpdatedCount,
|
|
25475
|
-
scannedCount: scannedCount + repoScannedCount,
|
|
25476
|
-
attachmentCount,
|
|
25477
|
-
changedPages,
|
|
25478
|
-
repoImportedCount,
|
|
25479
|
-
repoUpdatedCount,
|
|
25480
|
-
repoRemovedCount,
|
|
25481
|
-
repoScannedCount,
|
|
25482
|
-
pendingSemanticRefreshCount,
|
|
25483
|
-
pendingSemanticRefreshPaths,
|
|
25484
|
-
lintFindingCount,
|
|
25485
|
-
success,
|
|
25486
|
-
error
|
|
25487
|
-
});
|
|
25488
|
-
} catch {
|
|
25489
|
-
process3.stderr.write("[swarmvault watch] Failed to append watch run.\n");
|
|
25490
|
-
}
|
|
25491
|
-
try {
|
|
25492
|
-
await writeWatchStatusArtifact(rootDir, {
|
|
25493
|
-
generatedAt: finishedAt.toISOString(),
|
|
25494
|
-
watchedRepoRoots,
|
|
25495
|
-
lastRun: {
|
|
25496
|
-
startedAt: startedAt.toISOString(),
|
|
25497
|
-
finishedAt: finishedAt.toISOString(),
|
|
25498
|
-
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
25499
|
-
inputDir: paths.inboxDir,
|
|
25500
|
-
reasons: runReasons,
|
|
25501
|
-
importedCount: importedCount + repoImportedCount + repoUpdatedCount,
|
|
25502
|
-
scannedCount: scannedCount + repoScannedCount,
|
|
25503
|
-
attachmentCount,
|
|
25504
|
-
changedPages,
|
|
25505
|
-
repoImportedCount,
|
|
25506
|
-
repoUpdatedCount,
|
|
25507
|
-
repoRemovedCount,
|
|
25508
|
-
repoScannedCount,
|
|
25509
|
-
pendingSemanticRefreshCount,
|
|
25510
|
-
pendingSemanticRefreshPaths,
|
|
25511
|
-
lintFindingCount,
|
|
25512
|
-
success,
|
|
25513
|
-
error
|
|
25514
|
-
},
|
|
25515
|
-
pendingSemanticRefresh: await readPendingSemanticRefresh(rootDir)
|
|
25516
|
-
});
|
|
25517
|
-
} catch {
|
|
25518
|
-
process3.stderr.write("[swarmvault watch] Failed to write watch status artifact.\n");
|
|
25519
|
-
}
|
|
25520
|
-
running = false;
|
|
25521
|
-
if (pending && !closed) {
|
|
25522
|
-
schedule("queued");
|
|
25523
|
-
}
|
|
25524
|
-
}
|
|
25769
|
+
const synced = await syncManagedSource(rootDir, source, options);
|
|
25770
|
+
if (synced.lastSyncStatus === "error") {
|
|
25771
|
+
throw new Error(synced.lastError ?? `Failed to add managed source ${synced.id}.`);
|
|
25772
|
+
}
|
|
25773
|
+
const graphExists = await loadVaultConfig(rootDir).then(({ paths }) => fileExists(paths.graphPath));
|
|
25774
|
+
let compile;
|
|
25775
|
+
if (shouldCompile(synced.changed ? [synced] : [], graphExists, compileRequested)) {
|
|
25776
|
+
compile = await compileVault(rootDir, {});
|
|
25777
|
+
}
|
|
25778
|
+
let briefGenerated = false;
|
|
25779
|
+
let briefPath;
|
|
25780
|
+
if (compileRequested && briefRequested && synced.status === "ready" && await shouldRefreshBriefForManagedSource(synced, {
|
|
25781
|
+
compilePerformed: Boolean(compile),
|
|
25782
|
+
changed: synced.changed
|
|
25783
|
+
})) {
|
|
25784
|
+
const briefs = await generateBriefsForSources(rootDir, [synced]);
|
|
25785
|
+
briefPath = briefs.get(synced.id);
|
|
25786
|
+
briefGenerated = Boolean(briefPath);
|
|
25787
|
+
}
|
|
25788
|
+
const nextSource = {
|
|
25789
|
+
...synced,
|
|
25790
|
+
briefPath: briefPath ?? synced.briefPath,
|
|
25791
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25525
25792
|
};
|
|
25526
|
-
const
|
|
25527
|
-
|
|
25528
|
-
|
|
25793
|
+
const nextSources = existing ? sources.map((candidate) => candidate.id === nextSource.id ? nextSource : candidate) : [...sources, nextSource];
|
|
25794
|
+
await saveManagedSources(rootDir, nextSources);
|
|
25795
|
+
const review = reviewRequested && nextSource.status === "ready" ? await stageSourceReviewForScope(rootDir, scopeFromManagedSource(nextSource)) : void 0;
|
|
25796
|
+
const guide = guideRequested && nextSource.status === "ready" ? await stageSourceGuideForScope(
|
|
25797
|
+
rootDir,
|
|
25798
|
+
{
|
|
25799
|
+
...scopeFromManagedSource(nextSource),
|
|
25800
|
+
briefPath: nextSource.briefPath
|
|
25801
|
+
},
|
|
25802
|
+
{ answers: options.guideAnswers }
|
|
25803
|
+
) : void 0;
|
|
25804
|
+
return {
|
|
25805
|
+
source: nextSource,
|
|
25806
|
+
compile,
|
|
25807
|
+
briefGenerated,
|
|
25808
|
+
review,
|
|
25809
|
+
guide
|
|
25529
25810
|
};
|
|
25530
|
-
|
|
25531
|
-
|
|
25532
|
-
|
|
25533
|
-
|
|
25534
|
-
|
|
25535
|
-
|
|
25536
|
-
|
|
25537
|
-
|
|
25538
|
-
|
|
25811
|
+
}
|
|
25812
|
+
async function reloadManagedSources(rootDir, options = {}) {
|
|
25813
|
+
const compileRequested = options.compile ?? true;
|
|
25814
|
+
const guideRequested = options.guide ?? false;
|
|
25815
|
+
const briefRequested = guideRequested ? true : options.brief ?? true;
|
|
25816
|
+
const reviewRequested = guideRequested ? false : options.review ?? false;
|
|
25817
|
+
const sources = await loadManagedSources(rootDir);
|
|
25818
|
+
const selected = options.all || !options.id ? sources : sources.filter((source) => source.id === options.id);
|
|
25819
|
+
if (!selected.length) {
|
|
25820
|
+
throw new Error(options.id ? `Managed source not found: ${options.id}` : "No managed sources registered.");
|
|
25821
|
+
}
|
|
25822
|
+
const syncedSources = [];
|
|
25823
|
+
const changedSources = [];
|
|
25824
|
+
for (const source of selected) {
|
|
25825
|
+
const synced = await syncManagedSource(rootDir, source, options);
|
|
25826
|
+
syncedSources.push(synced);
|
|
25827
|
+
if (synced.changed) {
|
|
25828
|
+
changedSources.push(synced);
|
|
25829
|
+
}
|
|
25830
|
+
}
|
|
25831
|
+
const graphExists = await loadVaultConfig(rootDir).then(({ paths }) => fileExists(paths.graphPath));
|
|
25832
|
+
let compile;
|
|
25833
|
+
if (shouldCompile(changedSources, graphExists, compileRequested)) {
|
|
25834
|
+
compile = await compileVault(rootDir, {});
|
|
25835
|
+
}
|
|
25836
|
+
const briefPaths = compileRequested && briefRequested ? await generateBriefsForSources(
|
|
25837
|
+
rootDir,
|
|
25838
|
+
syncedSources.filter((source) => source.status === "ready")
|
|
25839
|
+
) : /* @__PURE__ */ new Map();
|
|
25840
|
+
const nextSources = sources.map((source) => {
|
|
25841
|
+
const synced = syncedSources.find((candidate) => candidate.id === source.id);
|
|
25842
|
+
if (!synced) {
|
|
25843
|
+
return source;
|
|
25844
|
+
}
|
|
25845
|
+
return {
|
|
25846
|
+
...synced,
|
|
25847
|
+
briefPath: briefPaths.get(synced.id) ?? synced.briefPath,
|
|
25848
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25539
25849
|
};
|
|
25540
|
-
watcher.once("ready", handleReady);
|
|
25541
|
-
watcher.once("error", handleError);
|
|
25542
25850
|
});
|
|
25851
|
+
await saveManagedSources(rootDir, nextSources);
|
|
25852
|
+
const reviews = reviewRequested ? await Promise.all(
|
|
25853
|
+
nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)).filter((source) => source.status === "ready").map(async (source) => await stageSourceReviewForScope(rootDir, scopeFromManagedSource(source)))
|
|
25854
|
+
) : [];
|
|
25855
|
+
const guides = guideRequested ? await Promise.all(
|
|
25856
|
+
nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)).filter((source) => source.status === "ready").map(
|
|
25857
|
+
async (source) => await stageSourceGuideForScope(
|
|
25858
|
+
rootDir,
|
|
25859
|
+
{
|
|
25860
|
+
...scopeFromManagedSource(source),
|
|
25861
|
+
briefPath: source.briefPath
|
|
25862
|
+
},
|
|
25863
|
+
{ answers: options.guideAnswers }
|
|
25864
|
+
)
|
|
25865
|
+
)
|
|
25866
|
+
) : [];
|
|
25543
25867
|
return {
|
|
25544
|
-
|
|
25545
|
-
|
|
25546
|
-
|
|
25547
|
-
|
|
25868
|
+
sources: nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)),
|
|
25869
|
+
compile,
|
|
25870
|
+
briefPaths: [...briefPaths.values()],
|
|
25871
|
+
reviews,
|
|
25872
|
+
guides
|
|
25873
|
+
};
|
|
25874
|
+
}
|
|
25875
|
+
async function deleteManagedSource(rootDir, id) {
|
|
25876
|
+
const sources = await loadManagedSources(rootDir);
|
|
25877
|
+
const target = sources.find((source) => source.id === id);
|
|
25878
|
+
if (!target) {
|
|
25879
|
+
throw new Error(`Managed source not found: ${id}`);
|
|
25880
|
+
}
|
|
25881
|
+
await saveManagedSources(
|
|
25882
|
+
rootDir,
|
|
25883
|
+
sources.filter((source) => source.id !== id)
|
|
25884
|
+
);
|
|
25885
|
+
const workingDir = await managedSourceWorkingDir(rootDir, id);
|
|
25886
|
+
await fs22.rm(workingDir, { recursive: true, force: true });
|
|
25887
|
+
return { removed: target };
|
|
25888
|
+
}
|
|
25889
|
+
|
|
25890
|
+
// src/viewer.ts
|
|
25891
|
+
import { execFile as execFile2 } from "child_process";
|
|
25892
|
+
import fs23 from "fs/promises";
|
|
25893
|
+
import http from "http";
|
|
25894
|
+
import path28 from "path";
|
|
25895
|
+
import { promisify as promisify2 } from "util";
|
|
25896
|
+
import matter12 from "gray-matter";
|
|
25897
|
+
import mime2 from "mime-types";
|
|
25898
|
+
|
|
25899
|
+
// src/graph-presentation.ts
|
|
25900
|
+
var OVERVIEW_THRESHOLD = 5e3;
|
|
25901
|
+
var OVERVIEW_NODE_BUDGET = 1500;
|
|
25902
|
+
function nodePriority(node, pinnedNodeIds) {
|
|
25903
|
+
return [pinnedNodeIds.has(node.id) ? 0 : 1, -(node.degree ?? 0), -(node.bridgeScore ?? 0), node.label, node.id];
|
|
25904
|
+
}
|
|
25905
|
+
function compareTuples(left, right) {
|
|
25906
|
+
const length = Math.max(left.length, right.length);
|
|
25907
|
+
for (let index = 0; index < length; index += 1) {
|
|
25908
|
+
const leftValue = left[index];
|
|
25909
|
+
const rightValue = right[index];
|
|
25910
|
+
if (leftValue === rightValue) {
|
|
25911
|
+
continue;
|
|
25912
|
+
}
|
|
25913
|
+
if (typeof leftValue === "number" && typeof rightValue === "number") {
|
|
25914
|
+
return leftValue - rightValue;
|
|
25915
|
+
}
|
|
25916
|
+
return String(leftValue ?? "").localeCompare(String(rightValue ?? ""));
|
|
25917
|
+
}
|
|
25918
|
+
return 0;
|
|
25919
|
+
}
|
|
25920
|
+
function survivingHyperedges(hyperedges, sampledNodeIds) {
|
|
25921
|
+
return hyperedges.filter((hyperedge) => hyperedge.nodeIds.filter((nodeId) => sampledNodeIds.has(nodeId)).length >= 2);
|
|
25922
|
+
}
|
|
25923
|
+
function pinnedNodeIdsForReport(report) {
|
|
25924
|
+
if (!report) {
|
|
25925
|
+
return /* @__PURE__ */ new Set();
|
|
25926
|
+
}
|
|
25927
|
+
return /* @__PURE__ */ new Set([
|
|
25928
|
+
...report.godNodes.map((node) => node.nodeId),
|
|
25929
|
+
...report.bridgeNodes.map((node) => node.nodeId),
|
|
25930
|
+
...report.surprisingConnections.flatMap((connection) => [connection.sourceNodeId, connection.targetNodeId])
|
|
25931
|
+
]);
|
|
25932
|
+
}
|
|
25933
|
+
function sampleGraphNodes(graph, report, nodeBudget = OVERVIEW_NODE_BUDGET) {
|
|
25934
|
+
const pinned = pinnedNodeIdsForReport(report);
|
|
25935
|
+
const nodeById2 = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
25936
|
+
const selected = new Set([...pinned].filter((nodeId) => nodeById2.has(nodeId)));
|
|
25937
|
+
const sortedCommunities2 = [...graph.communities ?? []].sort((left, right) => {
|
|
25938
|
+
const leftNodes = left.nodeIds.map((nodeId) => nodeById2.get(nodeId)).filter((node) => Boolean(node));
|
|
25939
|
+
const rightNodes = right.nodeIds.map((nodeId) => nodeById2.get(nodeId)).filter((node) => Boolean(node));
|
|
25940
|
+
const leftFirstParty = leftNodes.filter((node) => node.sourceClass === "first_party").length;
|
|
25941
|
+
const rightFirstParty = rightNodes.filter((node) => node.sourceClass === "first_party").length;
|
|
25942
|
+
return compareTuples(
|
|
25943
|
+
[-leftFirstParty, -leftNodes.length, left.label, left.id],
|
|
25944
|
+
[-rightFirstParty, -rightNodes.length, right.label, right.id]
|
|
25945
|
+
);
|
|
25946
|
+
});
|
|
25947
|
+
for (const community of sortedCommunities2) {
|
|
25948
|
+
const communityNodes = community.nodeIds.map((nodeId) => nodeById2.get(nodeId)).filter((node) => Boolean(node)).sort((left, right) => compareTuples(nodePriority(left, pinned), nodePriority(right, pinned)));
|
|
25949
|
+
for (const node of communityNodes) {
|
|
25950
|
+
if (selected.size >= nodeBudget && !pinned.has(node.id)) {
|
|
25951
|
+
break;
|
|
25548
25952
|
}
|
|
25549
|
-
|
|
25550
|
-
await activeCycle;
|
|
25953
|
+
selected.add(node.id);
|
|
25551
25954
|
}
|
|
25552
|
-
|
|
25955
|
+
if (selected.size >= nodeBudget) {
|
|
25956
|
+
break;
|
|
25957
|
+
}
|
|
25958
|
+
}
|
|
25959
|
+
if (selected.size < nodeBudget) {
|
|
25960
|
+
for (const node of [...graph.nodes].sort((left, right) => compareTuples(nodePriority(left, pinned), nodePriority(right, pinned)))) {
|
|
25961
|
+
if (selected.size >= nodeBudget && !pinned.has(node.id)) {
|
|
25962
|
+
break;
|
|
25963
|
+
}
|
|
25964
|
+
selected.add(node.id);
|
|
25965
|
+
}
|
|
25966
|
+
}
|
|
25967
|
+
return selected;
|
|
25553
25968
|
}
|
|
25554
|
-
|
|
25555
|
-
const
|
|
25556
|
-
const
|
|
25557
|
-
const
|
|
25969
|
+
function buildViewerGraphArtifact(graph, options = {}) {
|
|
25970
|
+
const threshold = options.threshold ?? OVERVIEW_THRESHOLD;
|
|
25971
|
+
const nodeBudget = options.nodeBudget ?? OVERVIEW_NODE_BUDGET;
|
|
25972
|
+
const totalCommunities = graph.communities?.length ?? 0;
|
|
25973
|
+
if (options.full || graph.nodes.length <= threshold) {
|
|
25974
|
+
return {
|
|
25975
|
+
...graph,
|
|
25976
|
+
presentation: {
|
|
25977
|
+
mode: "full",
|
|
25978
|
+
threshold,
|
|
25979
|
+
nodeBudget,
|
|
25980
|
+
totalNodes: graph.nodes.length,
|
|
25981
|
+
displayedNodes: graph.nodes.length,
|
|
25982
|
+
totalEdges: graph.edges.length,
|
|
25983
|
+
displayedEdges: graph.edges.length,
|
|
25984
|
+
totalCommunities,
|
|
25985
|
+
displayedCommunities: totalCommunities
|
|
25986
|
+
}
|
|
25987
|
+
};
|
|
25988
|
+
}
|
|
25989
|
+
const sampledNodeIds = sampleGraphNodes(graph, options.report, nodeBudget);
|
|
25990
|
+
const nodes = graph.nodes.filter((node) => sampledNodeIds.has(node.id));
|
|
25991
|
+
const edges = graph.edges.filter((edge) => sampledNodeIds.has(edge.source) && sampledNodeIds.has(edge.target));
|
|
25992
|
+
const hyperedges = survivingHyperedges(graph.hyperedges ?? [], sampledNodeIds);
|
|
25993
|
+
const communities = (graph.communities ?? []).map((community) => ({
|
|
25994
|
+
...community,
|
|
25995
|
+
nodeIds: community.nodeIds.filter((nodeId) => sampledNodeIds.has(nodeId))
|
|
25996
|
+
})).filter((community) => community.nodeIds.length > 0);
|
|
25558
25997
|
return {
|
|
25559
|
-
|
|
25560
|
-
|
|
25561
|
-
|
|
25562
|
-
|
|
25998
|
+
...graph,
|
|
25999
|
+
nodes,
|
|
26000
|
+
edges,
|
|
26001
|
+
hyperedges,
|
|
26002
|
+
communities,
|
|
26003
|
+
presentation: {
|
|
26004
|
+
mode: "overview",
|
|
26005
|
+
threshold,
|
|
26006
|
+
nodeBudget,
|
|
26007
|
+
totalNodes: graph.nodes.length,
|
|
26008
|
+
displayedNodes: nodes.length,
|
|
26009
|
+
totalEdges: graph.edges.length,
|
|
26010
|
+
displayedEdges: edges.length,
|
|
26011
|
+
totalCommunities,
|
|
26012
|
+
displayedCommunities: communities.length
|
|
26013
|
+
}
|
|
25563
26014
|
};
|
|
25564
26015
|
}
|
|
25565
26016
|
|
|
@@ -25771,7 +26222,7 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
25771
26222
|
response.end(JSON.stringify({ error: "Missing approval id." }));
|
|
25772
26223
|
return;
|
|
25773
26224
|
}
|
|
25774
|
-
const approval = await readApproval(rootDir, approvalId);
|
|
26225
|
+
const approval = await readApproval(rootDir, approvalId, { diff: true });
|
|
25775
26226
|
response.writeHead(200, { "content-type": "application/json" });
|
|
25776
26227
|
response.end(JSON.stringify(approval));
|
|
25777
26228
|
return;
|
|
@@ -25962,6 +26413,7 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
|
|
|
25962
26413
|
return path28.resolve(outputPath);
|
|
25963
26414
|
}
|
|
25964
26415
|
export {
|
|
26416
|
+
DEFAULT_PROMOTION_CONFIG,
|
|
25965
26417
|
acceptApproval,
|
|
25966
26418
|
addInput,
|
|
25967
26419
|
addManagedSource,
|
|
@@ -25981,6 +26433,7 @@ export {
|
|
|
25981
26433
|
deleteManagedSource,
|
|
25982
26434
|
estimatePageTokens,
|
|
25983
26435
|
estimateTokens,
|
|
26436
|
+
evaluateCandidateForPromotion,
|
|
25984
26437
|
explainGraphVault,
|
|
25985
26438
|
exploreVault,
|
|
25986
26439
|
exportGraphFormat,
|
|
@@ -26019,6 +26472,7 @@ export {
|
|
|
26019
26472
|
loadVaultSchema,
|
|
26020
26473
|
loadVaultSchemas,
|
|
26021
26474
|
pathGraphVault,
|
|
26475
|
+
previewCandidatePromotions,
|
|
26022
26476
|
promoteCandidate,
|
|
26023
26477
|
pushGraphNeo4j,
|
|
26024
26478
|
queryGraphVault,
|
|
@@ -26033,6 +26487,7 @@ export {
|
|
|
26033
26487
|
resumeSourceSession,
|
|
26034
26488
|
reviewManagedSource,
|
|
26035
26489
|
reviewSourceScope,
|
|
26490
|
+
runAutoPromotion,
|
|
26036
26491
|
runSchedule,
|
|
26037
26492
|
runWatchCycle,
|
|
26038
26493
|
searchVault,
|