@swarmvaultai/engine 3.6.0 → 3.7.1
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/README.md +5 -4
- package/dist/chunk-JTRE7C7P.js +26062 -0
- package/dist/chunk-S2E65WRI.js +26062 -0
- package/dist/index.d.ts +75 -2
- package/dist/index.js +867 -131
- package/dist/memory-DNSQCDHC.js +32 -0
- package/dist/memory-G6I3DBW4.js +32 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -125,7 +125,7 @@ import {
|
|
|
125
125
|
writeGuidedSourceSession,
|
|
126
126
|
writeRetrievalManifest,
|
|
127
127
|
writeWatchStatusArtifact
|
|
128
|
-
} from "./chunk-
|
|
128
|
+
} from "./chunk-JTRE7C7P.js";
|
|
129
129
|
import {
|
|
130
130
|
LocalWhisperProviderAdapter,
|
|
131
131
|
SWARMVAULT_OUT_ENV,
|
|
@@ -563,6 +563,7 @@ import chokidar from "chokidar";
|
|
|
563
563
|
var MAX_BACKOFF_MS = 3e4;
|
|
564
564
|
var BACKOFF_THRESHOLD = 3;
|
|
565
565
|
var CRITICAL_THRESHOLD = 10;
|
|
566
|
+
var DEFAULT_MAX_GRAPH_SHRINK_RATIO = 0.25;
|
|
566
567
|
var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
|
|
567
568
|
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
568
569
|
".ts",
|
|
@@ -602,7 +603,12 @@ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
602
603
|
".psm1",
|
|
603
604
|
".ex",
|
|
604
605
|
".exs",
|
|
606
|
+
".svelte",
|
|
605
607
|
".jl",
|
|
608
|
+
".v",
|
|
609
|
+
".vh",
|
|
610
|
+
".sv",
|
|
611
|
+
".svh",
|
|
606
612
|
".r",
|
|
607
613
|
".R"
|
|
608
614
|
]);
|
|
@@ -638,6 +644,62 @@ function collectNonCodePaths(reasons) {
|
|
|
638
644
|
}
|
|
639
645
|
return result;
|
|
640
646
|
}
|
|
647
|
+
function shrinkDimension(before, after) {
|
|
648
|
+
const dropped = Math.max(0, before - after);
|
|
649
|
+
return {
|
|
650
|
+
before,
|
|
651
|
+
after,
|
|
652
|
+
dropped,
|
|
653
|
+
dropRatio: before > 0 ? dropped / before : 0
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function evaluateGraphShrinkGuard(previousGraph, nextGraph, options = {}) {
|
|
657
|
+
const threshold = options.threshold ?? DEFAULT_MAX_GRAPH_SHRINK_RATIO;
|
|
658
|
+
const nodes = shrinkDimension(previousGraph?.nodes.length ?? 0, nextGraph?.nodes.length ?? 0);
|
|
659
|
+
const edges = shrinkDimension(previousGraph?.edges.length ?? 0, nextGraph?.edges.length ?? 0);
|
|
660
|
+
const blocked = nodes.dropRatio > threshold || edges.dropRatio > threshold;
|
|
661
|
+
return {
|
|
662
|
+
blocked,
|
|
663
|
+
threshold,
|
|
664
|
+
nodes,
|
|
665
|
+
edges,
|
|
666
|
+
message: blocked ? `Graph update aborted: node count changed ${nodes.before} -> ${nodes.after} (${Math.round(
|
|
667
|
+
nodes.dropRatio * 100
|
|
668
|
+
)}% drop) and edge count changed ${edges.before} -> ${edges.after} (${Math.round(
|
|
669
|
+
edges.dropRatio * 100
|
|
670
|
+
)}% drop). Re-run with --force or SWARMVAULT_FORCE_UPDATE=1 if this shrink is expected.` : void 0
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function forceGraphUpdateEnabled(options) {
|
|
674
|
+
return options.force === true || process2.env.SWARMVAULT_FORCE_UPDATE === "1" || process2.env.SWARMVAULT_FORCE_UPDATE === "true";
|
|
675
|
+
}
|
|
676
|
+
function projectGraphAfterRemovals(graph, removedSourceIds) {
|
|
677
|
+
if (removedSourceIds.length === 0) {
|
|
678
|
+
return graph;
|
|
679
|
+
}
|
|
680
|
+
const removed = new Set(removedSourceIds);
|
|
681
|
+
const droppedNodeIds = new Set(
|
|
682
|
+
graph.nodes.filter((node) => node.sourceIds.length > 0 && node.sourceIds.every((sourceId) => removed.has(sourceId))).map((node) => node.id)
|
|
683
|
+
);
|
|
684
|
+
if (droppedNodeIds.size === 0) {
|
|
685
|
+
return graph;
|
|
686
|
+
}
|
|
687
|
+
const remainingNodes = graph.nodes.filter((node) => !droppedNodeIds.has(node.id));
|
|
688
|
+
const remainingEdges = graph.edges.filter((edge) => !droppedNodeIds.has(edge.source) && !droppedNodeIds.has(edge.target));
|
|
689
|
+
return { ...graph, nodes: remainingNodes, edges: remainingEdges };
|
|
690
|
+
}
|
|
691
|
+
async function predictRemovedSourceIdsFromRepoSync(rootDir, options) {
|
|
692
|
+
if (!options.repo) {
|
|
693
|
+
return [];
|
|
694
|
+
}
|
|
695
|
+
const overrideRoots = options.overrideRoots && options.overrideRoots.length > 0 ? options.overrideRoots : void 0;
|
|
696
|
+
const changes = await checkTrackedRepoChanges(rootDir, overrideRoots);
|
|
697
|
+
return [
|
|
698
|
+
...new Set(
|
|
699
|
+
changes.filter((change) => change.changeType === "removed").map((change) => change.sourceId).filter((sourceId) => Boolean(sourceId))
|
|
700
|
+
)
|
|
701
|
+
];
|
|
702
|
+
}
|
|
641
703
|
function hasIgnoredRepoSegment(baseDir, targetPath) {
|
|
642
704
|
const relativePath = path2.relative(baseDir, targetPath);
|
|
643
705
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
@@ -774,6 +836,7 @@ async function performWatchCycle(rootDir, paths, options, codeOnly = false) {
|
|
|
774
836
|
}
|
|
775
837
|
async function runWatchCycle(rootDir, options = {}) {
|
|
776
838
|
const { paths } = await initWorkspace(rootDir);
|
|
839
|
+
const previousGraph = await readJsonFile(paths.graphPath);
|
|
777
840
|
const startedAt = /* @__PURE__ */ new Date();
|
|
778
841
|
let success = true;
|
|
779
842
|
let error;
|
|
@@ -791,6 +854,16 @@ async function runWatchCycle(rootDir, options = {}) {
|
|
|
791
854
|
changedPages: []
|
|
792
855
|
};
|
|
793
856
|
try {
|
|
857
|
+
if (previousGraph && options.repo && !forceGraphUpdateEnabled(options)) {
|
|
858
|
+
const removedSourceIds = await predictRemovedSourceIdsFromRepoSync(rootDir, options);
|
|
859
|
+
if (removedSourceIds.length > 0) {
|
|
860
|
+
const projectedGraph = projectGraphAfterRemovals(previousGraph, removedSourceIds);
|
|
861
|
+
const guard = evaluateGraphShrinkGuard(previousGraph, projectedGraph, { threshold: options.maxGraphShrinkRatio });
|
|
862
|
+
if (guard.blocked) {
|
|
863
|
+
throw new Error(guard.message ?? "Graph update aborted because the graph shrank unexpectedly.");
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
794
867
|
result = await performWatchCycle(rootDir, paths, options, options.codeOnly ?? false);
|
|
795
868
|
return result;
|
|
796
869
|
} catch (caught) {
|
|
@@ -3710,9 +3783,350 @@ Community: ${communityLabel}`,
|
|
|
3710
3783
|
return { format: "canvas", outputPath: resolvedPath };
|
|
3711
3784
|
}
|
|
3712
3785
|
|
|
3713
|
-
// src/graph-
|
|
3786
|
+
// src/graph-merge.ts
|
|
3714
3787
|
import fs5 from "fs/promises";
|
|
3715
3788
|
import path5 from "path";
|
|
3789
|
+
function isRecord(value) {
|
|
3790
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3791
|
+
}
|
|
3792
|
+
function isSwarmVaultGraph(value) {
|
|
3793
|
+
return isRecord(value) && Array.isArray(value.nodes) && Array.isArray(value.edges) && Array.isArray(value.sources) && Array.isArray(value.pages);
|
|
3794
|
+
}
|
|
3795
|
+
function stringField(record, ...fields) {
|
|
3796
|
+
for (const field of fields) {
|
|
3797
|
+
const value = record[field];
|
|
3798
|
+
if (typeof value === "string" && value.trim()) {
|
|
3799
|
+
return value.trim();
|
|
3800
|
+
}
|
|
3801
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
3802
|
+
return String(value);
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
return void 0;
|
|
3806
|
+
}
|
|
3807
|
+
function arrayStringField(record, field) {
|
|
3808
|
+
const value = record[field];
|
|
3809
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
3810
|
+
}
|
|
3811
|
+
function numberField(record, field, fallback) {
|
|
3812
|
+
const value = record[field];
|
|
3813
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
3814
|
+
}
|
|
3815
|
+
function safePrefix(inputPath, index) {
|
|
3816
|
+
return slugify(path5.basename(inputPath, path5.extname(inputPath)) || `graph-${index + 1}`);
|
|
3817
|
+
}
|
|
3818
|
+
function prefixed(prefix, id) {
|
|
3819
|
+
return `${prefix}:${id}`;
|
|
3820
|
+
}
|
|
3821
|
+
function ensureUniquePrefix(base, used) {
|
|
3822
|
+
let candidate = base || "graph";
|
|
3823
|
+
let suffix = 2;
|
|
3824
|
+
while (used.has(candidate)) {
|
|
3825
|
+
candidate = `${base}-${suffix}`;
|
|
3826
|
+
suffix += 1;
|
|
3827
|
+
}
|
|
3828
|
+
used.add(candidate);
|
|
3829
|
+
return candidate;
|
|
3830
|
+
}
|
|
3831
|
+
function mapEvidenceClass(value) {
|
|
3832
|
+
const normalized = typeof value === "string" ? value.toLowerCase() : "";
|
|
3833
|
+
if (normalized === "extracted") return "extracted";
|
|
3834
|
+
if (normalized === "ambiguous") return "ambiguous";
|
|
3835
|
+
return "inferred";
|
|
3836
|
+
}
|
|
3837
|
+
function mapNodeType(value) {
|
|
3838
|
+
const normalized = typeof value === "string" ? value.toLowerCase() : "";
|
|
3839
|
+
if (["source", "file", "document", "paper", "image", "video"].includes(normalized)) return "source";
|
|
3840
|
+
if (["module", "code"].includes(normalized)) return "module";
|
|
3841
|
+
if (["function", "class", "symbol", "method", "component"].includes(normalized)) return "symbol";
|
|
3842
|
+
if (["entity", "person", "org", "organization"].includes(normalized)) return "entity";
|
|
3843
|
+
if (["rationale", "comment", "docstring", "why"].includes(normalized)) return "rationale";
|
|
3844
|
+
if (["decision", "adr"].includes(normalized)) return "decision";
|
|
3845
|
+
return "concept";
|
|
3846
|
+
}
|
|
3847
|
+
function remapSwarmVaultGraph(inputPath, graph, prefix) {
|
|
3848
|
+
const sourceMap = new Map(graph.sources.map((source) => [source.sourceId, prefixed(prefix, source.sourceId)]));
|
|
3849
|
+
const pageMap = new Map(graph.pages.map((page) => [page.id, prefixed(prefix, page.id)]));
|
|
3850
|
+
const nodeMap = new Map(graph.nodes.map((node) => [node.id, prefixed(prefix, node.id)]));
|
|
3851
|
+
const communityMap = new Map((graph.communities ?? []).map((community) => [community.id, prefixed(prefix, community.id)]));
|
|
3852
|
+
const sources = graph.sources.map((source) => ({
|
|
3853
|
+
...source,
|
|
3854
|
+
sourceId: sourceMap.get(source.sourceId) ?? prefixed(prefix, source.sourceId),
|
|
3855
|
+
title: `[${prefix}] ${source.title}`,
|
|
3856
|
+
sourceGroupId: source.sourceGroupId ? prefixed(prefix, source.sourceGroupId) : void 0,
|
|
3857
|
+
details: {
|
|
3858
|
+
...source.details ?? {},
|
|
3859
|
+
mergedInput: inputPath,
|
|
3860
|
+
mergedPrefix: prefix
|
|
3861
|
+
}
|
|
3862
|
+
}));
|
|
3863
|
+
const pages = graph.pages.map((page) => ({
|
|
3864
|
+
...page,
|
|
3865
|
+
id: pageMap.get(page.id) ?? prefixed(prefix, page.id),
|
|
3866
|
+
path: toPosix(path5.posix.join("merged", prefix, page.path)),
|
|
3867
|
+
sourceIds: page.sourceIds.map((sourceId) => sourceMap.get(sourceId) ?? prefixed(prefix, sourceId)),
|
|
3868
|
+
nodeIds: page.nodeIds.map((nodeId) => nodeMap.get(nodeId) ?? prefixed(prefix, nodeId)),
|
|
3869
|
+
relatedPageIds: page.relatedPageIds.map((pageId) => pageMap.get(pageId) ?? prefixed(prefix, pageId)),
|
|
3870
|
+
relatedNodeIds: page.relatedNodeIds.map((nodeId) => nodeMap.get(nodeId) ?? prefixed(prefix, nodeId)),
|
|
3871
|
+
relatedSourceIds: page.relatedSourceIds.map((sourceId) => sourceMap.get(sourceId) ?? prefixed(prefix, sourceId)),
|
|
3872
|
+
backlinks: page.backlinks.map((pageId) => pageMap.get(pageId) ?? prefixed(prefix, pageId)),
|
|
3873
|
+
supersededBy: page.supersededBy ? pageMap.get(page.supersededBy) ?? prefixed(prefix, page.supersededBy) : void 0
|
|
3874
|
+
}));
|
|
3875
|
+
const nodes = graph.nodes.map((node) => ({
|
|
3876
|
+
...node,
|
|
3877
|
+
id: nodeMap.get(node.id) ?? prefixed(prefix, node.id),
|
|
3878
|
+
pageId: node.pageId ? pageMap.get(node.pageId) ?? prefixed(prefix, node.pageId) : void 0,
|
|
3879
|
+
sourceIds: node.sourceIds.map((sourceId) => sourceMap.get(sourceId) ?? prefixed(prefix, sourceId)),
|
|
3880
|
+
moduleId: node.moduleId ? nodeMap.get(node.moduleId) ?? prefixed(prefix, node.moduleId) : void 0,
|
|
3881
|
+
communityId: node.communityId ? communityMap.get(node.communityId) ?? prefixed(prefix, node.communityId) : void 0
|
|
3882
|
+
}));
|
|
3883
|
+
const edges = graph.edges.map((edge) => ({
|
|
3884
|
+
...edge,
|
|
3885
|
+
id: prefixed(prefix, edge.id),
|
|
3886
|
+
source: nodeMap.get(edge.source) ?? prefixed(prefix, edge.source),
|
|
3887
|
+
target: nodeMap.get(edge.target) ?? prefixed(prefix, edge.target),
|
|
3888
|
+
provenance: edge.provenance.map((id) => nodeMap.get(id) ?? pageMap.get(id) ?? sourceMap.get(id) ?? prefixed(prefix, id))
|
|
3889
|
+
}));
|
|
3890
|
+
const hyperedges = graph.hyperedges.map((hyperedge) => ({
|
|
3891
|
+
...hyperedge,
|
|
3892
|
+
id: prefixed(prefix, hyperedge.id),
|
|
3893
|
+
nodeIds: hyperedge.nodeIds.map((nodeId) => nodeMap.get(nodeId) ?? prefixed(prefix, nodeId)),
|
|
3894
|
+
sourcePageIds: hyperedge.sourcePageIds.map((pageId) => pageMap.get(pageId) ?? prefixed(prefix, pageId))
|
|
3895
|
+
}));
|
|
3896
|
+
return {
|
|
3897
|
+
generatedAt: graph.generatedAt,
|
|
3898
|
+
nodes,
|
|
3899
|
+
edges,
|
|
3900
|
+
hyperedges,
|
|
3901
|
+
communities: (graph.communities ?? []).map((community) => ({
|
|
3902
|
+
...community,
|
|
3903
|
+
id: communityMap.get(community.id) ?? prefixed(prefix, community.id),
|
|
3904
|
+
label: `[${prefix}] ${community.label}`,
|
|
3905
|
+
nodeIds: community.nodeIds.map((nodeId) => nodeMap.get(nodeId) ?? prefixed(prefix, nodeId))
|
|
3906
|
+
})),
|
|
3907
|
+
sources,
|
|
3908
|
+
pages
|
|
3909
|
+
};
|
|
3910
|
+
}
|
|
3911
|
+
function nodeLinkArrays(raw) {
|
|
3912
|
+
const nodes = raw.nodes;
|
|
3913
|
+
const edges = Array.isArray(raw.links) ? raw.links : raw.edges;
|
|
3914
|
+
if (!Array.isArray(nodes) || !Array.isArray(edges)) {
|
|
3915
|
+
return null;
|
|
3916
|
+
}
|
|
3917
|
+
return {
|
|
3918
|
+
nodes,
|
|
3919
|
+
edges: edges.filter(isRecord)
|
|
3920
|
+
};
|
|
3921
|
+
}
|
|
3922
|
+
function nodeLinkNodeId(node, index) {
|
|
3923
|
+
if (typeof node === "string" || typeof node === "number") {
|
|
3924
|
+
return String(node);
|
|
3925
|
+
}
|
|
3926
|
+
return stringField(node, "id", "key", "name", "label") ?? `node-${index + 1}`;
|
|
3927
|
+
}
|
|
3928
|
+
function endpointId(value) {
|
|
3929
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
3930
|
+
return String(value);
|
|
3931
|
+
}
|
|
3932
|
+
if (isRecord(value)) {
|
|
3933
|
+
return stringField(value, "id", "key", "name", "label");
|
|
3934
|
+
}
|
|
3935
|
+
return void 0;
|
|
3936
|
+
}
|
|
3937
|
+
function remapNodeLinkGraph(inputPath, raw, prefix, now) {
|
|
3938
|
+
const arrays = nodeLinkArrays(raw);
|
|
3939
|
+
if (!arrays) {
|
|
3940
|
+
throw new Error(`${inputPath} is not a SwarmVault graph or node-link graph.`);
|
|
3941
|
+
}
|
|
3942
|
+
const syntheticSourceId = prefixed(prefix, "source");
|
|
3943
|
+
const source = {
|
|
3944
|
+
sourceId: syntheticSourceId,
|
|
3945
|
+
title: `${prefix} merged graph`,
|
|
3946
|
+
originType: "file",
|
|
3947
|
+
sourceKind: "data",
|
|
3948
|
+
sourceClass: "generated",
|
|
3949
|
+
originalPath: inputPath,
|
|
3950
|
+
storedPath: inputPath,
|
|
3951
|
+
mimeType: "application/json",
|
|
3952
|
+
contentHash: `sha256:${sha256(JSON.stringify(raw)).slice(0, 24)}`,
|
|
3953
|
+
semanticHash: `sha256:${sha256(`${inputPath}:${arrays.nodes.length}:${arrays.edges.length}`).slice(0, 24)}`,
|
|
3954
|
+
details: {
|
|
3955
|
+
mergedInput: inputPath,
|
|
3956
|
+
mergedFormat: "node-link"
|
|
3957
|
+
},
|
|
3958
|
+
createdAt: now,
|
|
3959
|
+
updatedAt: now
|
|
3960
|
+
};
|
|
3961
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
3962
|
+
const nodes = arrays.nodes.map((node, index) => {
|
|
3963
|
+
const originalId = nodeLinkNodeId(node, index);
|
|
3964
|
+
const mappedId = prefixed(prefix, originalId);
|
|
3965
|
+
idMap.set(originalId, mappedId);
|
|
3966
|
+
const record = isRecord(node) ? node : {};
|
|
3967
|
+
const label = stringField(record, "label", "name", "title", "path", "id") ?? originalId;
|
|
3968
|
+
const type = mapNodeType(record.type ?? record.file_type ?? record.kind ?? record.category);
|
|
3969
|
+
return {
|
|
3970
|
+
id: mappedId,
|
|
3971
|
+
type,
|
|
3972
|
+
label,
|
|
3973
|
+
sourceIds: [syntheticSourceId],
|
|
3974
|
+
projectIds: [],
|
|
3975
|
+
sourceClass: "generated",
|
|
3976
|
+
confidence: numberField(record, "confidence", numberField(record, "confidence_score", 1)),
|
|
3977
|
+
tags: arrayStringField(record, "tags")
|
|
3978
|
+
};
|
|
3979
|
+
});
|
|
3980
|
+
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
3981
|
+
const edges = arrays.edges.flatMap((edge, index) => {
|
|
3982
|
+
const source2 = endpointId(edge.source ?? edge.from);
|
|
3983
|
+
const target = endpointId(edge.target ?? edge.to);
|
|
3984
|
+
if (!source2 || !target) {
|
|
3985
|
+
return [];
|
|
3986
|
+
}
|
|
3987
|
+
const mappedSource = idMap.get(source2) ?? prefixed(prefix, source2);
|
|
3988
|
+
const mappedTarget = idMap.get(target) ?? prefixed(prefix, target);
|
|
3989
|
+
if (!nodeIds.has(mappedSource) || !nodeIds.has(mappedTarget)) {
|
|
3990
|
+
return [];
|
|
3991
|
+
}
|
|
3992
|
+
const evidenceClass = mapEvidenceClass(edge.evidenceClass ?? edge.evidence_class ?? edge.status ?? edge.confidence);
|
|
3993
|
+
return [
|
|
3994
|
+
{
|
|
3995
|
+
id: prefixed(prefix, stringField(edge, "id", "key") ?? `edge-${index + 1}`),
|
|
3996
|
+
source: mappedSource,
|
|
3997
|
+
target: mappedTarget,
|
|
3998
|
+
relation: stringField(edge, "relation", "type", "label") ?? "related_to",
|
|
3999
|
+
status: evidenceClass === "extracted" ? "extracted" : "inferred",
|
|
4000
|
+
evidenceClass,
|
|
4001
|
+
confidence: numberField(edge, "confidence", numberField(edge, "confidence_score", evidenceClass === "ambiguous" ? 0.5 : 0.75)),
|
|
4002
|
+
provenance: [syntheticSourceId]
|
|
4003
|
+
}
|
|
4004
|
+
];
|
|
4005
|
+
});
|
|
4006
|
+
const page = {
|
|
4007
|
+
id: prefixed(prefix, "page"),
|
|
4008
|
+
path: toPosix(path5.posix.join("merged", prefix, "index.md")),
|
|
4009
|
+
title: `${prefix} merged graph`,
|
|
4010
|
+
kind: "source",
|
|
4011
|
+
sourceClass: "generated",
|
|
4012
|
+
sourceIds: [syntheticSourceId],
|
|
4013
|
+
projectIds: [],
|
|
4014
|
+
nodeIds: nodes.map((node) => node.id),
|
|
4015
|
+
freshness: "fresh",
|
|
4016
|
+
status: "active",
|
|
4017
|
+
confidence: 1,
|
|
4018
|
+
backlinks: [],
|
|
4019
|
+
schemaHash: "merged-node-link",
|
|
4020
|
+
sourceHashes: { [syntheticSourceId]: source.contentHash },
|
|
4021
|
+
sourceSemanticHashes: { [syntheticSourceId]: source.semanticHash },
|
|
4022
|
+
relatedPageIds: [],
|
|
4023
|
+
relatedNodeIds: nodes.map((node) => node.id),
|
|
4024
|
+
relatedSourceIds: [syntheticSourceId],
|
|
4025
|
+
createdAt: now,
|
|
4026
|
+
updatedAt: now,
|
|
4027
|
+
compiledFrom: [inputPath],
|
|
4028
|
+
managedBy: "system"
|
|
4029
|
+
};
|
|
4030
|
+
return {
|
|
4031
|
+
generatedAt: now,
|
|
4032
|
+
nodes,
|
|
4033
|
+
edges,
|
|
4034
|
+
hyperedges: [],
|
|
4035
|
+
communities: [],
|
|
4036
|
+
sources: [source],
|
|
4037
|
+
pages: [page]
|
|
4038
|
+
};
|
|
4039
|
+
}
|
|
4040
|
+
function mergeGraphs(graphs, now) {
|
|
4041
|
+
return {
|
|
4042
|
+
generatedAt: now,
|
|
4043
|
+
nodes: uniqueBy(
|
|
4044
|
+
graphs.flatMap((graph) => graph.nodes),
|
|
4045
|
+
(node) => node.id
|
|
4046
|
+
),
|
|
4047
|
+
edges: uniqueBy(
|
|
4048
|
+
graphs.flatMap((graph) => graph.edges),
|
|
4049
|
+
(edge) => edge.id
|
|
4050
|
+
),
|
|
4051
|
+
hyperedges: uniqueBy(
|
|
4052
|
+
graphs.flatMap((graph) => graph.hyperedges),
|
|
4053
|
+
(hyperedge) => hyperedge.id
|
|
4054
|
+
),
|
|
4055
|
+
communities: uniqueBy(
|
|
4056
|
+
graphs.flatMap((graph) => graph.communities ?? []),
|
|
4057
|
+
(community) => community.id
|
|
4058
|
+
),
|
|
4059
|
+
sources: uniqueBy(
|
|
4060
|
+
graphs.flatMap((graph) => graph.sources),
|
|
4061
|
+
(source) => source.sourceId
|
|
4062
|
+
),
|
|
4063
|
+
pages: uniqueBy(
|
|
4064
|
+
graphs.flatMap((graph) => graph.pages),
|
|
4065
|
+
(page) => page.id
|
|
4066
|
+
)
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
4069
|
+
async function mergeGraphFiles(inputPaths, outputPath, options = {}) {
|
|
4070
|
+
if (inputPaths.length === 0) {
|
|
4071
|
+
throw new Error("At least one graph JSON path is required.");
|
|
4072
|
+
}
|
|
4073
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4074
|
+
const usedPrefixes = /* @__PURE__ */ new Set();
|
|
4075
|
+
const graphs = [];
|
|
4076
|
+
const inputGraphs = [];
|
|
4077
|
+
const warnings = [];
|
|
4078
|
+
for (const [index, inputPath] of inputPaths.entries()) {
|
|
4079
|
+
const resolvedInputPath = path5.resolve(inputPath);
|
|
4080
|
+
const raw = JSON.parse(await fs5.readFile(resolvedInputPath, "utf8"));
|
|
4081
|
+
const prefix = ensureUniquePrefix(
|
|
4082
|
+
inputPaths.length === 1 && options.label ? slugify(options.label) : safePrefix(resolvedInputPath, index),
|
|
4083
|
+
usedPrefixes
|
|
4084
|
+
);
|
|
4085
|
+
if (isSwarmVaultGraph(raw)) {
|
|
4086
|
+
const graph2 = remapSwarmVaultGraph(resolvedInputPath, raw, prefix);
|
|
4087
|
+
graphs.push(graph2);
|
|
4088
|
+
inputGraphs.push({
|
|
4089
|
+
path: resolvedInputPath,
|
|
4090
|
+
label: prefix,
|
|
4091
|
+
format: "swarmvault",
|
|
4092
|
+
nodeCount: raw.nodes.length,
|
|
4093
|
+
edgeCount: raw.edges.length
|
|
4094
|
+
});
|
|
4095
|
+
continue;
|
|
4096
|
+
}
|
|
4097
|
+
if (isRecord(raw) && nodeLinkArrays(raw)) {
|
|
4098
|
+
const graph2 = remapNodeLinkGraph(resolvedInputPath, raw, prefix, now);
|
|
4099
|
+
graphs.push(graph2);
|
|
4100
|
+
inputGraphs.push({
|
|
4101
|
+
path: resolvedInputPath,
|
|
4102
|
+
label: prefix,
|
|
4103
|
+
format: "node-link",
|
|
4104
|
+
nodeCount: graph2.nodes.length,
|
|
4105
|
+
edgeCount: graph2.edges.length
|
|
4106
|
+
});
|
|
4107
|
+
continue;
|
|
4108
|
+
}
|
|
4109
|
+
warnings.push(`${resolvedInputPath} was skipped because it is not a supported graph JSON shape.`);
|
|
4110
|
+
}
|
|
4111
|
+
if (graphs.length === 0) {
|
|
4112
|
+
throw new Error("No supported graph inputs were found.");
|
|
4113
|
+
}
|
|
4114
|
+
const graph = mergeGraphs(graphs, now);
|
|
4115
|
+
const resolvedOutputPath = path5.resolve(outputPath);
|
|
4116
|
+
await ensureDir(path5.dirname(resolvedOutputPath));
|
|
4117
|
+
await fs5.writeFile(resolvedOutputPath, `${JSON.stringify(graph, null, 2)}
|
|
4118
|
+
`, "utf8");
|
|
4119
|
+
return {
|
|
4120
|
+
outputPath: resolvedOutputPath,
|
|
4121
|
+
graph,
|
|
4122
|
+
inputGraphs,
|
|
4123
|
+
warnings
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
4126
|
+
|
|
4127
|
+
// src/graph-push.ts
|
|
4128
|
+
import fs6 from "fs/promises";
|
|
4129
|
+
import path6 from "path";
|
|
3716
4130
|
import neo4j from "neo4j-driver";
|
|
3717
4131
|
var DEFAULT_NEO4J_BATCH_SIZE = 500;
|
|
3718
4132
|
var DEFAULT_NEO4J_DATABASE = "neo4j";
|
|
@@ -3723,8 +4137,8 @@ function requireConfigValue(value, name) {
|
|
|
3723
4137
|
throw new Error(`Neo4j push requires ${name}. Configure \`graphSinks.neo4j.${name}\` or pass the matching CLI flag.`);
|
|
3724
4138
|
}
|
|
3725
4139
|
async function deriveVaultId(rootDir) {
|
|
3726
|
-
const realRoot = await
|
|
3727
|
-
const label = slugify(
|
|
4140
|
+
const realRoot = await fs6.realpath(rootDir).catch(() => path6.resolve(rootDir));
|
|
4141
|
+
const label = slugify(path6.basename(realRoot));
|
|
3728
4142
|
return `${label}-${sha256(realRoot).slice(0, 12)}`;
|
|
3729
4143
|
}
|
|
3730
4144
|
async function resolveNeo4jPushConfig(rootDir, options) {
|
|
@@ -3754,7 +4168,7 @@ function normalizeBatchSize(value) {
|
|
|
3754
4168
|
}
|
|
3755
4169
|
async function loadGraph2(rootDir) {
|
|
3756
4170
|
const { paths } = await loadVaultConfig(rootDir);
|
|
3757
|
-
const raw = JSON.parse(await
|
|
4171
|
+
const raw = JSON.parse(await fs6.readFile(paths.graphPath, "utf8"));
|
|
3758
4172
|
return raw;
|
|
3759
4173
|
}
|
|
3760
4174
|
function buildResult(input) {
|
|
@@ -3839,7 +4253,7 @@ async function writeSyncNode(session, input) {
|
|
|
3839
4253
|
].join("\n"),
|
|
3840
4254
|
{
|
|
3841
4255
|
vaultId: input.vaultId,
|
|
3842
|
-
rootDir:
|
|
4256
|
+
rootDir: path6.resolve(input.rootDir),
|
|
3843
4257
|
graphGeneratedAt: input.graph.generatedAt,
|
|
3844
4258
|
graphHash: graphHash(input.graph),
|
|
3845
4259
|
pushedAt: input.pushedAt,
|
|
@@ -3925,7 +4339,7 @@ async function pushGraphNeo4j(rootDir, options = {}) {
|
|
|
3925
4339
|
}
|
|
3926
4340
|
|
|
3927
4341
|
// src/graph-status.ts
|
|
3928
|
-
import
|
|
4342
|
+
import path7 from "path";
|
|
3929
4343
|
function recommendedCommand(input) {
|
|
3930
4344
|
if (!input.graphExists || !input.reportExists) {
|
|
3931
4345
|
return "swarmvault compile";
|
|
@@ -3941,8 +4355,8 @@ function recommendedCommand(input) {
|
|
|
3941
4355
|
async function getGraphStatus(rootDir, options = {}) {
|
|
3942
4356
|
const { paths } = await loadVaultConfig(rootDir);
|
|
3943
4357
|
const graphPath = paths.graphPath;
|
|
3944
|
-
const reportPath =
|
|
3945
|
-
const resolvedOverrideRoots = options.repoRoots?.map((repoRoot) =>
|
|
4358
|
+
const reportPath = path7.join(paths.wikiDir, "graph", "report.md");
|
|
4359
|
+
const resolvedOverrideRoots = options.repoRoots?.map((repoRoot) => path7.resolve(rootDir, repoRoot));
|
|
3946
4360
|
const [graphExists, reportExists, trackedRepoRoots, changes, pendingSemanticRefresh] = await Promise.all([
|
|
3947
4361
|
fileExists(graphPath),
|
|
3948
4362
|
fileExists(reportPath),
|
|
@@ -3975,27 +4389,268 @@ async function getGraphStatus(rootDir, options = {}) {
|
|
|
3975
4389
|
};
|
|
3976
4390
|
}
|
|
3977
4391
|
|
|
4392
|
+
// src/graph-tree.ts
|
|
4393
|
+
import path8 from "path";
|
|
4394
|
+
var DEFAULT_MAX_CHILDREN = 250;
|
|
4395
|
+
function compareTreeNodes(left, right) {
|
|
4396
|
+
const kindOrder = /* @__PURE__ */ new Map([
|
|
4397
|
+
["directory", 0],
|
|
4398
|
+
["source", 1],
|
|
4399
|
+
["module", 2],
|
|
4400
|
+
["symbol", 3],
|
|
4401
|
+
["rationale", 4],
|
|
4402
|
+
["node", 5],
|
|
4403
|
+
["more", 6],
|
|
4404
|
+
["root", 7]
|
|
4405
|
+
]);
|
|
4406
|
+
const leftKind = kindOrder.get(left.kind) ?? 99;
|
|
4407
|
+
const rightKind = kindOrder.get(right.kind) ?? 99;
|
|
4408
|
+
return leftKind - rightKind || left.label.localeCompare(right.label) || left.id.localeCompare(right.id);
|
|
4409
|
+
}
|
|
4410
|
+
function escapeHtml(value) {
|
|
4411
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
4412
|
+
}
|
|
4413
|
+
function normalizeSourcePath(rootDir, source) {
|
|
4414
|
+
const candidate = source.repoRelativePath ?? source.originalPath ?? source.storedPath ?? source.title;
|
|
4415
|
+
if (source.repoRelativePath) {
|
|
4416
|
+
return toPosix(source.repoRelativePath);
|
|
4417
|
+
}
|
|
4418
|
+
if (rootDir && path8.isAbsolute(candidate) && isPathWithin(rootDir, candidate)) {
|
|
4419
|
+
return toPosix(path8.relative(rootDir, candidate));
|
|
4420
|
+
}
|
|
4421
|
+
if (path8.isAbsolute(candidate)) {
|
|
4422
|
+
return toPosix(path8.basename(candidate));
|
|
4423
|
+
}
|
|
4424
|
+
return toPosix(candidate).replace(/^\/+/, "") || source.title || source.sourceId;
|
|
4425
|
+
}
|
|
4426
|
+
function makeDirectoryNode(parentId, segment) {
|
|
4427
|
+
return {
|
|
4428
|
+
id: `${parentId}/${segment}`,
|
|
4429
|
+
label: segment,
|
|
4430
|
+
kind: "directory",
|
|
4431
|
+
count: 0,
|
|
4432
|
+
children: []
|
|
4433
|
+
};
|
|
4434
|
+
}
|
|
4435
|
+
function ensureDirectory(parent, segment) {
|
|
4436
|
+
const existing = parent.children.find((child) => child.kind === "directory" && child.label === segment);
|
|
4437
|
+
if (existing) {
|
|
4438
|
+
existing.count += 1;
|
|
4439
|
+
return existing;
|
|
4440
|
+
}
|
|
4441
|
+
const created = makeDirectoryNode(parent.id, segment);
|
|
4442
|
+
created.count = 1;
|
|
4443
|
+
parent.children.push(created);
|
|
4444
|
+
return created;
|
|
4445
|
+
}
|
|
4446
|
+
function nodeChildrenForSource(sourceId, nodes) {
|
|
4447
|
+
const sourceNodes = nodes.filter((node) => node.sourceIds.includes(sourceId));
|
|
4448
|
+
const modules = sourceNodes.filter((node) => node.type === "module").sort((left, right) => left.label.localeCompare(right.label));
|
|
4449
|
+
const moduleIds = new Set(modules.map((node) => node.id));
|
|
4450
|
+
const symbols = sourceNodes.filter((node) => node.type === "symbol");
|
|
4451
|
+
const rationales = sourceNodes.filter((node) => node.type === "rationale");
|
|
4452
|
+
const children = [];
|
|
4453
|
+
for (const moduleNode of modules) {
|
|
4454
|
+
const moduleChildren = symbols.filter((symbol) => symbol.moduleId === moduleNode.id).sort((left, right) => left.label.localeCompare(right.label)).map((symbol) => graphNodeToTreeNode(symbol, "symbol"));
|
|
4455
|
+
children.push({
|
|
4456
|
+
id: `tree:${moduleNode.id}`,
|
|
4457
|
+
label: moduleNode.label,
|
|
4458
|
+
kind: "module",
|
|
4459
|
+
count: moduleChildren.length,
|
|
4460
|
+
children: moduleChildren,
|
|
4461
|
+
nodeId: moduleNode.id,
|
|
4462
|
+
sourceId,
|
|
4463
|
+
language: moduleNode.language
|
|
4464
|
+
});
|
|
4465
|
+
}
|
|
4466
|
+
for (const symbol of symbols.filter((node) => !node.moduleId || !moduleIds.has(node.moduleId))) {
|
|
4467
|
+
children.push(graphNodeToTreeNode(symbol, "symbol"));
|
|
4468
|
+
}
|
|
4469
|
+
for (const rationale of rationales) {
|
|
4470
|
+
children.push(graphNodeToTreeNode(rationale, "rationale"));
|
|
4471
|
+
}
|
|
4472
|
+
return children.sort(compareTreeNodes);
|
|
4473
|
+
}
|
|
4474
|
+
function graphNodeToTreeNode(node, kind) {
|
|
4475
|
+
return {
|
|
4476
|
+
id: `tree:${node.id}`,
|
|
4477
|
+
label: node.label,
|
|
4478
|
+
kind,
|
|
4479
|
+
count: 0,
|
|
4480
|
+
children: [],
|
|
4481
|
+
nodeId: node.id,
|
|
4482
|
+
language: node.language,
|
|
4483
|
+
symbolKind: node.symbolKind
|
|
4484
|
+
};
|
|
4485
|
+
}
|
|
4486
|
+
function sortAndCapTree(node, maxChildren) {
|
|
4487
|
+
const sortedChildren = node.children.map((child) => sortAndCapTree(child, maxChildren)).sort(compareTreeNodes);
|
|
4488
|
+
if (sortedChildren.length <= maxChildren) {
|
|
4489
|
+
return { ...node, children: sortedChildren };
|
|
4490
|
+
}
|
|
4491
|
+
const visible = sortedChildren.slice(0, maxChildren);
|
|
4492
|
+
const hidden = sortedChildren.length - visible.length;
|
|
4493
|
+
return {
|
|
4494
|
+
...node,
|
|
4495
|
+
hiddenChildren: hidden,
|
|
4496
|
+
children: [
|
|
4497
|
+
...visible,
|
|
4498
|
+
{
|
|
4499
|
+
id: `${node.id}:more`,
|
|
4500
|
+
label: `+${hidden} more`,
|
|
4501
|
+
kind: "more",
|
|
4502
|
+
count: hidden,
|
|
4503
|
+
children: []
|
|
4504
|
+
}
|
|
4505
|
+
]
|
|
4506
|
+
};
|
|
4507
|
+
}
|
|
4508
|
+
function buildGraphTree(graph, options = {}) {
|
|
4509
|
+
const root = {
|
|
4510
|
+
id: "tree:root",
|
|
4511
|
+
label: options.label ?? "SwarmVault Graph Tree",
|
|
4512
|
+
kind: "root",
|
|
4513
|
+
count: graph.sources.length,
|
|
4514
|
+
children: []
|
|
4515
|
+
};
|
|
4516
|
+
const nodes = [...graph.nodes];
|
|
4517
|
+
for (const source of [...graph.sources].sort(
|
|
4518
|
+
(left, right) => normalizeSourcePath(options.rootDir, left).localeCompare(normalizeSourcePath(options.rootDir, right))
|
|
4519
|
+
)) {
|
|
4520
|
+
const normalizedPath = normalizeSourcePath(options.rootDir, source);
|
|
4521
|
+
const segments = normalizedPath.split("/").filter(Boolean);
|
|
4522
|
+
const fileLabel = segments.pop() ?? source.title ?? source.sourceId;
|
|
4523
|
+
let parent = root;
|
|
4524
|
+
for (const segment of segments) {
|
|
4525
|
+
parent = ensureDirectory(parent, segment);
|
|
4526
|
+
}
|
|
4527
|
+
const children = nodeChildrenForSource(source.sourceId, nodes);
|
|
4528
|
+
parent.children.push({
|
|
4529
|
+
id: `tree:source:${source.sourceId}`,
|
|
4530
|
+
label: fileLabel,
|
|
4531
|
+
kind: "source",
|
|
4532
|
+
count: children.length,
|
|
4533
|
+
children,
|
|
4534
|
+
path: normalizedPath,
|
|
4535
|
+
sourceId: source.sourceId,
|
|
4536
|
+
language: source.language
|
|
4537
|
+
});
|
|
4538
|
+
}
|
|
4539
|
+
return sortAndCapTree(root, Math.max(1, options.maxChildren ?? DEFAULT_MAX_CHILDREN));
|
|
4540
|
+
}
|
|
4541
|
+
function renderNode(node) {
|
|
4542
|
+
const meta = [
|
|
4543
|
+
node.kind,
|
|
4544
|
+
node.language,
|
|
4545
|
+
node.symbolKind,
|
|
4546
|
+
node.path,
|
|
4547
|
+
node.nodeId,
|
|
4548
|
+
node.sourceId,
|
|
4549
|
+
node.count ? `${node.count} item${node.count === 1 ? "" : "s"}` : void 0
|
|
4550
|
+
].filter(Boolean);
|
|
4551
|
+
const content = [
|
|
4552
|
+
`<span class="label">${escapeHtml(node.label)}</span>`,
|
|
4553
|
+
meta.length ? `<span class="meta">${escapeHtml(meta.join(" \xB7 "))}</span>` : ""
|
|
4554
|
+
].join("");
|
|
4555
|
+
if (node.children.length === 0) {
|
|
4556
|
+
return `<li class="tree-node kind-${escapeHtml(node.kind)}">${content}</li>`;
|
|
4557
|
+
}
|
|
4558
|
+
return `<li class="tree-node kind-${escapeHtml(node.kind)}"><details open><summary>${content}</summary><ul>${node.children.map(renderNode).join("")}</ul></details></li>`;
|
|
4559
|
+
}
|
|
4560
|
+
function renderGraphTreeHtml(tree, graph) {
|
|
4561
|
+
return `<!doctype html>
|
|
4562
|
+
<html lang="en">
|
|
4563
|
+
<head>
|
|
4564
|
+
<meta charset="utf-8">
|
|
4565
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
4566
|
+
<title>${escapeHtml(tree.label)}</title>
|
|
4567
|
+
<style>
|
|
4568
|
+
:root { color-scheme: light dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
4569
|
+
body { margin: 0; background: #f7f7f5; color: #171717; }
|
|
4570
|
+
main { max-width: 1120px; margin: 0 auto; padding: 32px 20px 56px; }
|
|
4571
|
+
h1 { font-size: 28px; line-height: 1.2; margin: 0 0 8px; }
|
|
4572
|
+
.subtitle { color: #5d5d5d; margin: 0 0 20px; }
|
|
4573
|
+
.toolbar { display: flex; gap: 12px; align-items: center; margin: 0 0 18px; }
|
|
4574
|
+
input { flex: 1; min-width: 0; border: 1px solid #c9c9c9; border-radius: 6px; padding: 10px 12px; font: inherit; background: #fff; color: inherit; }
|
|
4575
|
+
.tree { background: #fff; border: 1px solid #deded9; border-radius: 8px; padding: 16px 18px; }
|
|
4576
|
+
ul { list-style: none; margin: 0; padding-left: 20px; }
|
|
4577
|
+
.tree > ul { padding-left: 0; }
|
|
4578
|
+
li { margin: 4px 0; }
|
|
4579
|
+
summary { cursor: pointer; }
|
|
4580
|
+
.label { font-weight: 600; }
|
|
4581
|
+
.meta { color: #666; font-size: 12px; margin-left: 8px; }
|
|
4582
|
+
.kind-directory > details > summary .label { color: #245b78; }
|
|
4583
|
+
.kind-source > details > summary .label, .kind-source > .label { color: #22543d; }
|
|
4584
|
+
.kind-module > details > summary .label { color: #6b3f12; }
|
|
4585
|
+
.kind-rationale > .label { color: #6d2f46; }
|
|
4586
|
+
.hidden { display: none !important; }
|
|
4587
|
+
@media (prefers-color-scheme: dark) {
|
|
4588
|
+
body { background: #161616; color: #efefef; }
|
|
4589
|
+
.subtitle, .meta { color: #ababab; }
|
|
4590
|
+
input, .tree { background: #202020; border-color: #3a3a3a; }
|
|
4591
|
+
}
|
|
4592
|
+
</style>
|
|
4593
|
+
</head>
|
|
4594
|
+
<body>
|
|
4595
|
+
<main>
|
|
4596
|
+
<h1>${escapeHtml(tree.label)}</h1>
|
|
4597
|
+
<p class="subtitle">${graph.sources.length} sources \xB7 ${graph.nodes.length} nodes \xB7 ${graph.edges.length} edges \xB7 generated ${escapeHtml(graph.generatedAt)}</p>
|
|
4598
|
+
<div class="toolbar"><input id="filter" type="search" placeholder="Filter files, modules, symbols, or ids" aria-label="Filter graph tree"></div>
|
|
4599
|
+
<section class="tree"><ul>${renderNode(tree)}</ul></section>
|
|
4600
|
+
</main>
|
|
4601
|
+
<script>
|
|
4602
|
+
const input = document.getElementById('filter');
|
|
4603
|
+
input.addEventListener('input', () => {
|
|
4604
|
+
const query = input.value.trim().toLowerCase();
|
|
4605
|
+
for (const node of document.querySelectorAll('.tree-node')) {
|
|
4606
|
+
const text = node.textContent.toLowerCase();
|
|
4607
|
+
node.classList.toggle('hidden', query.length > 0 && !text.includes(query));
|
|
4608
|
+
}
|
|
4609
|
+
});
|
|
4610
|
+
</script>
|
|
4611
|
+
</body>
|
|
4612
|
+
</html>
|
|
4613
|
+
`;
|
|
4614
|
+
}
|
|
4615
|
+
async function exportGraphTree(rootDir, outputPath, options = {}) {
|
|
4616
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
4617
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
4618
|
+
if (!graph) {
|
|
4619
|
+
throw new Error(`Graph artifact not found at ${paths.graphPath}. Run swarmvault compile first.`);
|
|
4620
|
+
}
|
|
4621
|
+
const tree = buildGraphTree(graph, { ...options, rootDir });
|
|
4622
|
+
const resolvedOutputPath = path8.resolve(rootDir, outputPath ?? path8.join(paths.wikiDir, "graph", "tree.html"));
|
|
4623
|
+
await ensureDir(path8.dirname(resolvedOutputPath));
|
|
4624
|
+
await import("fs/promises").then((fs13) => fs13.writeFile(resolvedOutputPath, renderGraphTreeHtml(tree, graph), "utf8"));
|
|
4625
|
+
return {
|
|
4626
|
+
outputPath: resolvedOutputPath,
|
|
4627
|
+
sourceCount: graph.sources.length,
|
|
4628
|
+
nodeCount: graph.nodes.length,
|
|
4629
|
+
tree
|
|
4630
|
+
};
|
|
4631
|
+
}
|
|
4632
|
+
|
|
3978
4633
|
// src/hooks.ts
|
|
3979
|
-
import
|
|
3980
|
-
import
|
|
4634
|
+
import fs7 from "fs/promises";
|
|
4635
|
+
import path9 from "path";
|
|
3981
4636
|
import process3 from "process";
|
|
3982
4637
|
var hookStart = "# >>> swarmvault hook >>>";
|
|
3983
4638
|
var hookEnd = "# <<< swarmvault hook <<<";
|
|
3984
4639
|
async function findNearestGitRoot(startPath) {
|
|
3985
|
-
let current =
|
|
4640
|
+
let current = path9.resolve(startPath);
|
|
3986
4641
|
try {
|
|
3987
|
-
const stat = await
|
|
4642
|
+
const stat = await fs7.stat(current);
|
|
3988
4643
|
if (!stat.isDirectory()) {
|
|
3989
|
-
current =
|
|
4644
|
+
current = path9.dirname(current);
|
|
3990
4645
|
}
|
|
3991
4646
|
} catch {
|
|
3992
|
-
current =
|
|
4647
|
+
current = path9.dirname(current);
|
|
3993
4648
|
}
|
|
3994
4649
|
while (true) {
|
|
3995
|
-
if (await fileExists(
|
|
4650
|
+
if (await fileExists(path9.join(current, ".git"))) {
|
|
3996
4651
|
return current;
|
|
3997
4652
|
}
|
|
3998
|
-
const parent =
|
|
4653
|
+
const parent = path9.dirname(current);
|
|
3999
4654
|
if (parent === current) {
|
|
4000
4655
|
return null;
|
|
4001
4656
|
}
|
|
@@ -4007,8 +4662,8 @@ function shellQuote(value) {
|
|
|
4007
4662
|
}
|
|
4008
4663
|
function resolveSwarmvaultExecutableCandidate() {
|
|
4009
4664
|
const argvPath = process3.argv[1];
|
|
4010
|
-
if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${
|
|
4011
|
-
return
|
|
4665
|
+
if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path9.sep}@swarmvaultai${path9.sep}cli${path9.sep}`) || argvPath.includes(`${path9.sep}packages${path9.sep}cli${path9.sep}`))) {
|
|
4666
|
+
return path9.resolve(argvPath);
|
|
4012
4667
|
}
|
|
4013
4668
|
return "swarmvault";
|
|
4014
4669
|
}
|
|
@@ -4027,17 +4682,17 @@ function managedHookBlock(vaultRoot) {
|
|
|
4027
4682
|
].join("\n");
|
|
4028
4683
|
}
|
|
4029
4684
|
function hookPath(repoRoot, hookName) {
|
|
4030
|
-
return
|
|
4685
|
+
return path9.join(repoRoot, ".git", "hooks", hookName);
|
|
4031
4686
|
}
|
|
4032
4687
|
async function readHookStatus(filePath) {
|
|
4033
4688
|
if (!await fileExists(filePath)) {
|
|
4034
4689
|
return "not_installed";
|
|
4035
4690
|
}
|
|
4036
|
-
const content = await
|
|
4691
|
+
const content = await fs7.readFile(filePath, "utf8");
|
|
4037
4692
|
return content.includes(hookStart) && content.includes(hookEnd) ? "installed" : "other_content";
|
|
4038
4693
|
}
|
|
4039
4694
|
async function upsertHookFile(filePath, block) {
|
|
4040
|
-
const existing = await fileExists(filePath) ? await
|
|
4695
|
+
const existing = await fileExists(filePath) ? await fs7.readFile(filePath, "utf8") : "";
|
|
4041
4696
|
let next;
|
|
4042
4697
|
const startIndex = existing.indexOf(hookStart);
|
|
4043
4698
|
const endIndex = existing.indexOf(hookEnd);
|
|
@@ -4051,16 +4706,16 @@ ${block}`.trimEnd();
|
|
|
4051
4706
|
next = `#!/bin/sh
|
|
4052
4707
|
${block}`.trimEnd();
|
|
4053
4708
|
}
|
|
4054
|
-
await ensureDir(
|
|
4055
|
-
await
|
|
4709
|
+
await ensureDir(path9.dirname(filePath));
|
|
4710
|
+
await fs7.writeFile(filePath, `${next}
|
|
4056
4711
|
`, { mode: 493, encoding: "utf8" });
|
|
4057
|
-
await
|
|
4712
|
+
await fs7.chmod(filePath, 493);
|
|
4058
4713
|
}
|
|
4059
4714
|
async function removeHookBlock(filePath) {
|
|
4060
4715
|
if (!await fileExists(filePath)) {
|
|
4061
4716
|
return;
|
|
4062
4717
|
}
|
|
4063
|
-
const existing = await
|
|
4718
|
+
const existing = await fs7.readFile(filePath, "utf8");
|
|
4064
4719
|
const startIndex = existing.indexOf(hookStart);
|
|
4065
4720
|
const endIndex = existing.indexOf(hookEnd);
|
|
4066
4721
|
if (startIndex === -1 || endIndex === -1) {
|
|
@@ -4068,10 +4723,10 @@ async function removeHookBlock(filePath) {
|
|
|
4068
4723
|
}
|
|
4069
4724
|
const next = `${existing.slice(0, startIndex)}${existing.slice(endIndex + hookEnd.length)}`.trim();
|
|
4070
4725
|
if (!next || next === "#!/bin/sh") {
|
|
4071
|
-
await
|
|
4726
|
+
await fs7.rm(filePath, { force: true });
|
|
4072
4727
|
return;
|
|
4073
4728
|
}
|
|
4074
|
-
await
|
|
4729
|
+
await fs7.writeFile(filePath, `${next}
|
|
4075
4730
|
`, "utf8");
|
|
4076
4731
|
}
|
|
4077
4732
|
async function getGitHookStatus(rootDir) {
|
|
@@ -4094,7 +4749,7 @@ async function installGitHooks(rootDir) {
|
|
|
4094
4749
|
if (!repoRoot) {
|
|
4095
4750
|
throw new Error("No git repository found above the current vault.");
|
|
4096
4751
|
}
|
|
4097
|
-
const block = managedHookBlock(
|
|
4752
|
+
const block = managedHookBlock(path9.resolve(rootDir));
|
|
4098
4753
|
await upsertHookFile(hookPath(repoRoot, "post-commit"), block);
|
|
4099
4754
|
await upsertHookFile(hookPath(repoRoot, "post-checkout"), block);
|
|
4100
4755
|
return getGitHookStatus(rootDir);
|
|
@@ -4114,12 +4769,12 @@ async function uninstallGitHooks(rootDir) {
|
|
|
4114
4769
|
}
|
|
4115
4770
|
|
|
4116
4771
|
// src/mcp.ts
|
|
4117
|
-
import
|
|
4118
|
-
import
|
|
4772
|
+
import fs8 from "fs/promises";
|
|
4773
|
+
import path10 from "path";
|
|
4119
4774
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4120
4775
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4121
4776
|
import { z } from "zod";
|
|
4122
|
-
var SERVER_VERSION = "3.
|
|
4777
|
+
var SERVER_VERSION = "3.7.1";
|
|
4123
4778
|
async function createMcpServer(rootDir) {
|
|
4124
4779
|
const server = new McpServer({
|
|
4125
4780
|
name: "swarmvault",
|
|
@@ -4827,7 +5482,7 @@ async function createMcpServer(rootDir) {
|
|
|
4827
5482
|
},
|
|
4828
5483
|
async () => {
|
|
4829
5484
|
const { paths } = await loadVaultConfig(rootDir);
|
|
4830
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
5485
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path10.relative(paths.sessionsDir, filePath))).sort();
|
|
4831
5486
|
return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
|
|
4832
5487
|
}
|
|
4833
5488
|
);
|
|
@@ -4896,8 +5551,8 @@ async function createMcpServer(rootDir) {
|
|
|
4896
5551
|
return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
|
|
4897
5552
|
}
|
|
4898
5553
|
const { paths } = await loadVaultConfig(rootDir);
|
|
4899
|
-
const absolutePath =
|
|
4900
|
-
return asTextResource(`swarmvault://pages/${encodedPath}`, await
|
|
5554
|
+
const absolutePath = path10.resolve(paths.wikiDir, relativePath);
|
|
5555
|
+
return asTextResource(`swarmvault://pages/${encodedPath}`, await fs8.readFile(absolutePath, "utf8"));
|
|
4901
5556
|
}
|
|
4902
5557
|
);
|
|
4903
5558
|
server.registerResource(
|
|
@@ -4905,11 +5560,11 @@ async function createMcpServer(rootDir) {
|
|
|
4905
5560
|
new ResourceTemplate("swarmvault://sessions/{path}", {
|
|
4906
5561
|
list: async () => {
|
|
4907
5562
|
const { paths } = await loadVaultConfig(rootDir);
|
|
4908
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
5563
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path10.relative(paths.sessionsDir, filePath))).sort();
|
|
4909
5564
|
return {
|
|
4910
5565
|
resources: files.map((relativePath) => ({
|
|
4911
5566
|
uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
|
|
4912
|
-
name:
|
|
5567
|
+
name: path10.basename(relativePath, ".md"),
|
|
4913
5568
|
title: relativePath,
|
|
4914
5569
|
description: "SwarmVault session artifact",
|
|
4915
5570
|
mimeType: "text/markdown"
|
|
@@ -4926,11 +5581,11 @@ async function createMcpServer(rootDir) {
|
|
|
4926
5581
|
const { paths } = await loadVaultConfig(rootDir);
|
|
4927
5582
|
const encodedPath = typeof variables.path === "string" ? variables.path : "";
|
|
4928
5583
|
const relativePath = decodeURIComponent(encodedPath);
|
|
4929
|
-
const absolutePath =
|
|
5584
|
+
const absolutePath = path10.resolve(paths.sessionsDir, relativePath);
|
|
4930
5585
|
if (!isPathWithin(paths.sessionsDir, absolutePath) || !await fileExists(absolutePath)) {
|
|
4931
5586
|
return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
|
|
4932
5587
|
}
|
|
4933
|
-
return asTextResource(`swarmvault://sessions/${encodedPath}`, await
|
|
5588
|
+
return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs8.readFile(absolutePath, "utf8"));
|
|
4934
5589
|
}
|
|
4935
5590
|
);
|
|
4936
5591
|
return server;
|
|
@@ -4990,9 +5645,9 @@ function asTextResource(uri, text) {
|
|
|
4990
5645
|
|
|
4991
5646
|
// src/providers/local-whisper-setup.ts
|
|
4992
5647
|
import { createWriteStream, constants as fsConstants } from "fs";
|
|
4993
|
-
import
|
|
5648
|
+
import fs9 from "fs/promises";
|
|
4994
5649
|
import os from "os";
|
|
4995
|
-
import
|
|
5650
|
+
import path11 from "path";
|
|
4996
5651
|
import { Readable } from "stream";
|
|
4997
5652
|
import { pipeline } from "stream/promises";
|
|
4998
5653
|
var BINARY_CANDIDATES = ["whisper-cli", "whisper-cpp", "whisper"];
|
|
@@ -5020,10 +5675,10 @@ async function discoverLocalWhisperBinary(options = {}) {
|
|
|
5020
5675
|
}
|
|
5021
5676
|
const pathValue = env.PATH ?? "";
|
|
5022
5677
|
const candidates = [];
|
|
5023
|
-
for (const dir of pathValue.split(
|
|
5678
|
+
for (const dir of pathValue.split(path11.delimiter)) {
|
|
5024
5679
|
if (!dir) continue;
|
|
5025
5680
|
for (const name of BINARY_CANDIDATES) {
|
|
5026
|
-
const full =
|
|
5681
|
+
const full = path11.join(dir, name);
|
|
5027
5682
|
candidates.push(full);
|
|
5028
5683
|
if (await isExecutable(full)) {
|
|
5029
5684
|
return { binaryPath: full, candidates, source: "path" };
|
|
@@ -5034,14 +5689,14 @@ async function discoverLocalWhisperBinary(options = {}) {
|
|
|
5034
5689
|
}
|
|
5035
5690
|
function expectedModelPath(modelName, homeDir) {
|
|
5036
5691
|
const home = homeDir ?? os.homedir();
|
|
5037
|
-
return
|
|
5692
|
+
return path11.join(home, ".swarmvault", "models", `ggml-${modelName}.bin`);
|
|
5038
5693
|
}
|
|
5039
5694
|
function modelDownloadUrl(modelName) {
|
|
5040
5695
|
return `${HUGGINGFACE_BASE}/ggml-${modelName}.bin`;
|
|
5041
5696
|
}
|
|
5042
5697
|
async function downloadWhisperModel(options) {
|
|
5043
5698
|
const destPath = expectedModelPath(options.modelName, options.homeDir);
|
|
5044
|
-
await ensureDir(
|
|
5699
|
+
await ensureDir(path11.dirname(destPath));
|
|
5045
5700
|
const doFetch = options.fetchImpl ?? fetch;
|
|
5046
5701
|
const url = modelDownloadUrl(options.modelName);
|
|
5047
5702
|
const response = await doFetch(url);
|
|
@@ -5062,8 +5717,8 @@ async function downloadWhisperModel(options) {
|
|
|
5062
5717
|
});
|
|
5063
5718
|
const tmpPath = `${destPath}.part`;
|
|
5064
5719
|
await pipeline(source, createWriteStream(tmpPath));
|
|
5065
|
-
await
|
|
5066
|
-
const stat = await
|
|
5720
|
+
await fs9.rename(tmpPath, destPath);
|
|
5721
|
+
const stat = await fs9.stat(destPath);
|
|
5067
5722
|
return { path: destPath, bytes: stat.size };
|
|
5068
5723
|
}
|
|
5069
5724
|
async function registerLocalWhisperProvider(options) {
|
|
@@ -5130,7 +5785,7 @@ async function summarizeLocalWhisperSetup(options) {
|
|
|
5130
5785
|
}
|
|
5131
5786
|
async function isExecutable(p) {
|
|
5132
5787
|
try {
|
|
5133
|
-
await
|
|
5788
|
+
await fs9.access(p, fsConstants.X_OK);
|
|
5134
5789
|
return true;
|
|
5135
5790
|
} catch {
|
|
5136
5791
|
return false;
|
|
@@ -5200,13 +5855,13 @@ async function withCapabilityFallback(provider, capability, run, fallback) {
|
|
|
5200
5855
|
}
|
|
5201
5856
|
|
|
5202
5857
|
// src/schedule.ts
|
|
5203
|
-
import
|
|
5204
|
-
import
|
|
5858
|
+
import fs10 from "fs/promises";
|
|
5859
|
+
import path12 from "path";
|
|
5205
5860
|
function scheduleStatePath(schedulesDir, jobId) {
|
|
5206
|
-
return
|
|
5861
|
+
return path12.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
5207
5862
|
}
|
|
5208
5863
|
function scheduleLockPath(schedulesDir, jobId) {
|
|
5209
|
-
return
|
|
5864
|
+
return path12.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
|
|
5210
5865
|
}
|
|
5211
5866
|
function parseEveryDuration(value) {
|
|
5212
5867
|
const match = value.trim().match(/^(\d+)(m|h|d)$/i);
|
|
@@ -5309,13 +5964,13 @@ async function acquireJobLease(rootDir, jobId) {
|
|
|
5309
5964
|
const { paths } = await loadVaultConfig(rootDir);
|
|
5310
5965
|
const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
|
|
5311
5966
|
await ensureDir(paths.schedulesDir);
|
|
5312
|
-
const handle = await
|
|
5967
|
+
const handle = await fs10.open(leasePath, "wx");
|
|
5313
5968
|
await handle.writeFile(`${process.pid}
|
|
5314
5969
|
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5315
5970
|
`);
|
|
5316
5971
|
await handle.close();
|
|
5317
5972
|
return async () => {
|
|
5318
|
-
await
|
|
5973
|
+
await fs10.rm(leasePath, { force: true });
|
|
5319
5974
|
};
|
|
5320
5975
|
}
|
|
5321
5976
|
async function listSchedules(rootDir) {
|
|
@@ -5474,8 +6129,8 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
|
|
|
5474
6129
|
|
|
5475
6130
|
// src/sources.ts
|
|
5476
6131
|
import { spawn } from "child_process";
|
|
5477
|
-
import
|
|
5478
|
-
import
|
|
6132
|
+
import fs11 from "fs/promises";
|
|
6133
|
+
import path13 from "path";
|
|
5479
6134
|
import matter3 from "gray-matter";
|
|
5480
6135
|
import { JSDOM } from "jsdom";
|
|
5481
6136
|
var DEFAULT_CRAWL_MAX_PAGES = 12;
|
|
@@ -5521,24 +6176,24 @@ function emptyManagedSourceSyncCounts() {
|
|
|
5521
6176
|
};
|
|
5522
6177
|
}
|
|
5523
6178
|
function withinRoot(rootPath, targetPath) {
|
|
5524
|
-
const relative =
|
|
5525
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
6179
|
+
const relative = path13.relative(rootPath, targetPath);
|
|
6180
|
+
return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
|
|
5526
6181
|
}
|
|
5527
6182
|
async function findNearestGitRoot2(startPath) {
|
|
5528
|
-
let current =
|
|
6183
|
+
let current = path13.resolve(startPath);
|
|
5529
6184
|
try {
|
|
5530
|
-
const stat = await
|
|
6185
|
+
const stat = await fs11.stat(current);
|
|
5531
6186
|
if (!stat.isDirectory()) {
|
|
5532
|
-
current =
|
|
6187
|
+
current = path13.dirname(current);
|
|
5533
6188
|
}
|
|
5534
6189
|
} catch {
|
|
5535
|
-
current =
|
|
6190
|
+
current = path13.dirname(current);
|
|
5536
6191
|
}
|
|
5537
6192
|
while (true) {
|
|
5538
|
-
if (await fileExists(
|
|
6193
|
+
if (await fileExists(path13.join(current, ".git"))) {
|
|
5539
6194
|
return current;
|
|
5540
6195
|
}
|
|
5541
|
-
const parent =
|
|
6196
|
+
const parent = path13.dirname(current);
|
|
5542
6197
|
if (parent === current) {
|
|
5543
6198
|
return null;
|
|
5544
6199
|
}
|
|
@@ -5612,7 +6267,7 @@ function isAllowedDocsCandidate(candidate, startUrl) {
|
|
|
5612
6267
|
if (candidate.origin !== startUrl.origin) {
|
|
5613
6268
|
return false;
|
|
5614
6269
|
}
|
|
5615
|
-
const extension =
|
|
6270
|
+
const extension = path13.extname(candidate.pathname).toLowerCase();
|
|
5616
6271
|
if (extension && extension !== ".html" && extension !== ".htm" && extension !== ".md") {
|
|
5617
6272
|
return false;
|
|
5618
6273
|
}
|
|
@@ -5701,14 +6356,40 @@ function matchesManagedSourceSpec(existing, input) {
|
|
|
5701
6356
|
return false;
|
|
5702
6357
|
}
|
|
5703
6358
|
if (input.kind === "directory" || input.kind === "file") {
|
|
5704
|
-
return
|
|
6359
|
+
return path13.resolve(existing.path ?? "") === path13.resolve(input.path);
|
|
6360
|
+
}
|
|
6361
|
+
if (input.kind === "github_repo") {
|
|
6362
|
+
return (existing.url ?? "") === input.url && (existing.branch ?? "") === (input.branch ?? "") && (existing.ref ?? "") === (input.ref ?? "");
|
|
5705
6363
|
}
|
|
5706
6364
|
return (existing.url ?? "") === input.url;
|
|
5707
6365
|
}
|
|
5708
|
-
|
|
5709
|
-
const
|
|
6366
|
+
function normalizeGitSelector(value, label) {
|
|
6367
|
+
const trimmed = value?.trim();
|
|
6368
|
+
if (!trimmed) {
|
|
6369
|
+
return void 0;
|
|
6370
|
+
}
|
|
6371
|
+
if (trimmed.startsWith("-")) {
|
|
6372
|
+
throw new Error(`Git ${label} must not start with "-".`);
|
|
6373
|
+
}
|
|
6374
|
+
if (/[\s\0]/.test(trimmed)) {
|
|
6375
|
+
throw new Error(`Git ${label} must not contain whitespace or NUL bytes.`);
|
|
6376
|
+
}
|
|
6377
|
+
return trimmed;
|
|
6378
|
+
}
|
|
6379
|
+
function normalizeCheckoutDir(rootDir, value) {
|
|
6380
|
+
const trimmed = value?.trim();
|
|
6381
|
+
if (!trimmed) {
|
|
6382
|
+
return void 0;
|
|
6383
|
+
}
|
|
6384
|
+
return path13.isAbsolute(trimmed) ? path13.resolve(trimmed) : path13.resolve(rootDir, trimmed);
|
|
6385
|
+
}
|
|
6386
|
+
async function resolveManagedSourceInput(rootDir, input, options = {}) {
|
|
6387
|
+
const absoluteInput = path13.resolve(rootDir, input);
|
|
5710
6388
|
if (!(input.startsWith("http://") || input.startsWith("https://"))) {
|
|
5711
|
-
|
|
6389
|
+
if (options.branch || options.ref || options.checkoutDir) {
|
|
6390
|
+
throw new Error("Git branch/ref/checkout options are only supported for public GitHub repo root URLs.");
|
|
6391
|
+
}
|
|
6392
|
+
const stat = await fs11.stat(absoluteInput).catch(() => null);
|
|
5712
6393
|
if (!stat) {
|
|
5713
6394
|
throw new Error(`Source not found: ${input}`);
|
|
5714
6395
|
}
|
|
@@ -5716,7 +6397,7 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
5716
6397
|
return {
|
|
5717
6398
|
kind: "file",
|
|
5718
6399
|
path: absoluteInput,
|
|
5719
|
-
title:
|
|
6400
|
+
title: path13.basename(absoluteInput, path13.extname(absoluteInput)) || absoluteInput
|
|
5720
6401
|
};
|
|
5721
6402
|
}
|
|
5722
6403
|
if (!stat.isDirectory()) {
|
|
@@ -5728,16 +6409,22 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
5728
6409
|
kind: "directory",
|
|
5729
6410
|
path: absoluteInput,
|
|
5730
6411
|
repoRoot,
|
|
5731
|
-
title:
|
|
6412
|
+
title: path13.basename(absoluteInput) || absoluteInput
|
|
5732
6413
|
};
|
|
5733
6414
|
}
|
|
5734
6415
|
const github = normalizeGitHubRepoRootUrl(input);
|
|
5735
6416
|
if (github) {
|
|
5736
6417
|
return {
|
|
5737
6418
|
kind: "github_repo",
|
|
5738
|
-
...github
|
|
6419
|
+
...github,
|
|
6420
|
+
branch: normalizeGitSelector(options.branch, "branch"),
|
|
6421
|
+
ref: normalizeGitSelector(options.ref, "ref"),
|
|
6422
|
+
checkoutDir: normalizeCheckoutDir(rootDir, options.checkoutDir)
|
|
5739
6423
|
};
|
|
5740
6424
|
}
|
|
6425
|
+
if (options.branch || options.ref || options.checkoutDir) {
|
|
6426
|
+
throw new Error("Git branch/ref/checkout options are only supported for public GitHub repo root URLs.");
|
|
6427
|
+
}
|
|
5741
6428
|
const parsed = new URL(input);
|
|
5742
6429
|
if (parsed.hostname.toLowerCase().includes("github.com")) {
|
|
5743
6430
|
throw new Error(
|
|
@@ -5751,16 +6438,16 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
5751
6438
|
};
|
|
5752
6439
|
}
|
|
5753
6440
|
function directorySourceIdsFor(manifests, inputPath) {
|
|
5754
|
-
return manifests.filter((manifest) => manifest.originalPath && withinRoot(
|
|
6441
|
+
return manifests.filter((manifest) => manifest.originalPath && withinRoot(path13.resolve(inputPath), path13.resolve(manifest.originalPath))).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
|
|
5755
6442
|
}
|
|
5756
6443
|
function fileSourceIdsFor(manifests, inputPath) {
|
|
5757
|
-
const absoluteInput =
|
|
5758
|
-
return manifests.filter((manifest) => manifest.originalPath &&
|
|
6444
|
+
const absoluteInput = path13.resolve(inputPath);
|
|
6445
|
+
return manifests.filter((manifest) => manifest.originalPath && path13.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
|
|
5759
6446
|
}
|
|
5760
6447
|
async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
5761
6448
|
const manifestsBefore = await listManifests(rootDir);
|
|
5762
6449
|
const previousInScope = manifestsBefore.filter(
|
|
5763
|
-
(manifest) => manifest.originalPath && withinRoot(
|
|
6450
|
+
(manifest) => manifest.originalPath && withinRoot(path13.resolve(inputPath), path13.resolve(manifest.originalPath))
|
|
5764
6451
|
);
|
|
5765
6452
|
const result = await ingestDirectory(rootDir, inputPath, { repoRoot });
|
|
5766
6453
|
const removed = [];
|
|
@@ -5768,7 +6455,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
|
5768
6455
|
if (!manifest.originalPath) {
|
|
5769
6456
|
continue;
|
|
5770
6457
|
}
|
|
5771
|
-
if (await fileExists(
|
|
6458
|
+
if (await fileExists(path13.resolve(manifest.originalPath))) {
|
|
5772
6459
|
continue;
|
|
5773
6460
|
}
|
|
5774
6461
|
const removedManifest = await removeManifestBySourceId(rootDir, manifest.sourceId);
|
|
@@ -5778,7 +6465,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
|
5778
6465
|
}
|
|
5779
6466
|
const manifestsAfter = await listManifests(rootDir);
|
|
5780
6467
|
return {
|
|
5781
|
-
title:
|
|
6468
|
+
title: path13.basename(inputPath) || inputPath,
|
|
5782
6469
|
sourceIds: directorySourceIdsFor(manifestsAfter, inputPath),
|
|
5783
6470
|
counts: {
|
|
5784
6471
|
scannedCount: result.scannedCount,
|
|
@@ -5794,7 +6481,7 @@ async function syncFileSource(rootDir, inputPath) {
|
|
|
5794
6481
|
const result = await ingestInputDetailed(rootDir, inputPath);
|
|
5795
6482
|
const manifestsAfter = await listManifests(rootDir);
|
|
5796
6483
|
return {
|
|
5797
|
-
title:
|
|
6484
|
+
title: path13.basename(inputPath, path13.extname(inputPath)) || inputPath,
|
|
5798
6485
|
sourceIds: fileSourceIdsFor(manifestsAfter, inputPath),
|
|
5799
6486
|
counts: {
|
|
5800
6487
|
scannedCount: result.scannedCount,
|
|
@@ -5828,8 +6515,11 @@ async function runGitCommand(cwd, args) {
|
|
|
5828
6515
|
}
|
|
5829
6516
|
async function syncGitHubRepoSource(rootDir, entry) {
|
|
5830
6517
|
const workingDir = await managedSourceWorkingDir(rootDir, entry.id);
|
|
5831
|
-
const
|
|
5832
|
-
|
|
6518
|
+
const externalCheckoutDir = entry.checkoutDir ? path13.resolve(entry.checkoutDir) : void 0;
|
|
6519
|
+
const checkoutDir = externalCheckoutDir ?? path13.join(workingDir, "checkout");
|
|
6520
|
+
if (!externalCheckoutDir) {
|
|
6521
|
+
await fs11.rm(checkoutDir, { recursive: true, force: true });
|
|
6522
|
+
}
|
|
5833
6523
|
await ensureDir(workingDir);
|
|
5834
6524
|
if (!entry.url) {
|
|
5835
6525
|
throw new Error(`Managed source ${entry.id} is missing its repository URL.`);
|
|
@@ -5838,7 +6528,35 @@ async function syncGitHubRepoSource(rootDir, entry) {
|
|
|
5838
6528
|
if (!github) {
|
|
5839
6529
|
throw new Error(`Managed source ${entry.id} has an invalid GitHub repo URL.`);
|
|
5840
6530
|
}
|
|
5841
|
-
|
|
6531
|
+
const branch = normalizeGitSelector(entry.branch, "branch");
|
|
6532
|
+
const ref = normalizeGitSelector(entry.ref, "ref");
|
|
6533
|
+
const cloneArgs = ["clone", "--depth", "1"];
|
|
6534
|
+
if (branch) {
|
|
6535
|
+
cloneArgs.push("--branch", branch);
|
|
6536
|
+
}
|
|
6537
|
+
cloneArgs.push(github.cloneUrl, checkoutDir);
|
|
6538
|
+
if (await fileExists(path13.join(checkoutDir, ".git"))) {
|
|
6539
|
+
await runGitCommand(checkoutDir, ["remote", "set-url", "origin", github.cloneUrl]);
|
|
6540
|
+
if (branch) {
|
|
6541
|
+
await runGitCommand(checkoutDir, ["fetch", "--depth", "1", "origin", branch]);
|
|
6542
|
+
} else {
|
|
6543
|
+
await runGitCommand(checkoutDir, ["fetch", "--depth", "1", "origin"]);
|
|
6544
|
+
}
|
|
6545
|
+
if (!ref) {
|
|
6546
|
+
await runGitCommand(checkoutDir, ["checkout", "--detach", "FETCH_HEAD"]);
|
|
6547
|
+
}
|
|
6548
|
+
} else {
|
|
6549
|
+
const existingEntries = await fs11.readdir(checkoutDir).catch(() => []);
|
|
6550
|
+
if (externalCheckoutDir && existingEntries.length > 0) {
|
|
6551
|
+
throw new Error(`Checkout directory exists but is not a Git repository: ${checkoutDir}`);
|
|
6552
|
+
}
|
|
6553
|
+
await ensureDir(path13.dirname(checkoutDir));
|
|
6554
|
+
await runGitCommand(workingDir, cloneArgs);
|
|
6555
|
+
}
|
|
6556
|
+
if (ref) {
|
|
6557
|
+
await runGitCommand(checkoutDir, ["fetch", "--depth", "1", "origin", ref]);
|
|
6558
|
+
await runGitCommand(checkoutDir, ["checkout", "--detach", "FETCH_HEAD"]);
|
|
6559
|
+
}
|
|
5842
6560
|
return await syncDirectorySource(rootDir, checkoutDir, checkoutDir);
|
|
5843
6561
|
}
|
|
5844
6562
|
async function syncCrawlSource(rootDir, entry, options) {
|
|
@@ -5959,7 +6677,7 @@ function scopedNodeIds(graph, sourceIds) {
|
|
|
5959
6677
|
async function loadSourceAnalyses(rootDir, sourceIds) {
|
|
5960
6678
|
const { paths } = await loadVaultConfig(rootDir);
|
|
5961
6679
|
const analyses = await Promise.all(
|
|
5962
|
-
sourceIds.map(async (sourceId) => await readJsonFile(
|
|
6680
|
+
sourceIds.map(async (sourceId) => await readJsonFile(path13.join(paths.analysesDir, `${sourceId}.json`)))
|
|
5963
6681
|
);
|
|
5964
6682
|
return analyses.filter((analysis) => Boolean(analysis?.sourceId));
|
|
5965
6683
|
}
|
|
@@ -6120,9 +6838,9 @@ async function writeSourceBriefForScope(rootDir, source) {
|
|
|
6120
6838
|
confidence: 0.82
|
|
6121
6839
|
}
|
|
6122
6840
|
});
|
|
6123
|
-
const absolutePath =
|
|
6124
|
-
await ensureDir(
|
|
6125
|
-
await
|
|
6841
|
+
const absolutePath = path13.join(paths.wikiDir, output.page.path);
|
|
6842
|
+
await ensureDir(path13.dirname(absolutePath));
|
|
6843
|
+
await fs11.writeFile(absolutePath, output.content, "utf8");
|
|
6126
6844
|
return absolutePath;
|
|
6127
6845
|
}
|
|
6128
6846
|
async function writeSourceBrief(rootDir, source) {
|
|
@@ -6410,7 +7128,7 @@ function selectGuidedTargetPages(scope, sourcePages, questions) {
|
|
|
6410
7128
|
return (matchedTargets.length ? matchedTargets : canonicalPages).slice(0, 6);
|
|
6411
7129
|
}
|
|
6412
7130
|
function insightRelativePathForTarget(page, scope) {
|
|
6413
|
-
const basename =
|
|
7131
|
+
const basename = path13.basename(page.path);
|
|
6414
7132
|
if (page.kind === "concept") {
|
|
6415
7133
|
return `insights/concepts/${basename}`;
|
|
6416
7134
|
}
|
|
@@ -6637,7 +7355,7 @@ async function stageSourceReviewForScope(rootDir, scope) {
|
|
|
6637
7355
|
return {
|
|
6638
7356
|
sourceId: scope.id,
|
|
6639
7357
|
pageId: output.page.id,
|
|
6640
|
-
reviewPath:
|
|
7358
|
+
reviewPath: path13.join(approval.approvalDir, "wiki", output.page.path),
|
|
6641
7359
|
staged: true,
|
|
6642
7360
|
approvalId: approval.approvalId,
|
|
6643
7361
|
approvalDir: approval.approvalDir
|
|
@@ -6704,7 +7422,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
|
|
|
6704
7422
|
const evidenceState = contradictions.length > 0 ? "conflicting" : session.targetedPagePaths.some(
|
|
6705
7423
|
(targetPath) => sourcePages.some((page) => page.path === targetPath && page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId)))
|
|
6706
7424
|
) ? "reinforcing" : session.targetedPagePaths.length ? "new" : "needs_judgment";
|
|
6707
|
-
const relativeBriefPath = session.briefPath &&
|
|
7425
|
+
const relativeBriefPath = session.briefPath && path13.isAbsolute(session.briefPath) ? path13.relative(paths.wikiDir, session.briefPath) : session.briefPath;
|
|
6708
7426
|
const sessionMarkdown = [
|
|
6709
7427
|
`# Guided Session: ${scope.title}`,
|
|
6710
7428
|
"",
|
|
@@ -6787,9 +7505,9 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
|
|
|
6787
7505
|
async function persistSourceSessionPage(rootDir, scope, session) {
|
|
6788
7506
|
const { paths } = await loadVaultConfig(rootDir);
|
|
6789
7507
|
const output = await buildSourceSessionSavedPage(rootDir, scope, session);
|
|
6790
|
-
const absolutePath =
|
|
6791
|
-
await ensureDir(
|
|
6792
|
-
await
|
|
7508
|
+
const absolutePath = path13.join(paths.wikiDir, output.page.path);
|
|
7509
|
+
await ensureDir(path13.dirname(absolutePath));
|
|
7510
|
+
await fs11.writeFile(absolutePath, output.content, "utf8");
|
|
6793
7511
|
return { pageId: output.page.id, sessionPath: absolutePath };
|
|
6794
7512
|
}
|
|
6795
7513
|
async function buildGuidedUpdatePages(rootDir, scope, session) {
|
|
@@ -6817,8 +7535,8 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
|
|
|
6817
7535
|
targetPages.map(async (targetPage) => {
|
|
6818
7536
|
const evidenceState = classifyGuidedEvidenceState(scope, targetPage, contradictions);
|
|
6819
7537
|
const relativePath = useCanonicalTargets && targetPage ? targetPage.path : targetPage ? insightRelativePathForTarget(targetPage, scope) : `insights/topics/${slugify(scope.title)}.md`;
|
|
6820
|
-
const absolutePath =
|
|
6821
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
7538
|
+
const absolutePath = path13.join(paths.wikiDir, relativePath);
|
|
7539
|
+
const existingContent = await fileExists(absolutePath) ? await fs11.readFile(absolutePath, "utf8") : "";
|
|
6822
7540
|
const parsed = existingContent ? matter3(existingContent) : { data: {}, content: "" };
|
|
6823
7541
|
const existingData = parsed.data;
|
|
6824
7542
|
const existingSourceIds = Array.isArray(existingData.source_ids) ? existingData.source_ids.filter((value) => typeof value === "string") : [];
|
|
@@ -6989,8 +7707,8 @@ async function stageSourceGuideForScope(rootDir, scope, options = {}) {
|
|
|
6989
7707
|
}
|
|
6990
7708
|
);
|
|
6991
7709
|
session.status = "staged";
|
|
6992
|
-
session.reviewPath =
|
|
6993
|
-
session.guidePath =
|
|
7710
|
+
session.reviewPath = path13.join(approval.approvalDir, "wiki", reviewOutput.page.path);
|
|
7711
|
+
session.guidePath = path13.join(approval.approvalDir, "wiki", guideOutput.page.path);
|
|
6994
7712
|
session.approvalId = approval.approvalId;
|
|
6995
7713
|
session.approvalDir = approval.approvalDir;
|
|
6996
7714
|
const persisted = await persistSourceSessionPage(rootDir, scope, session);
|
|
@@ -7124,16 +7842,28 @@ async function addManagedSource(rootDir, input, options = {}) {
|
|
|
7124
7842
|
const briefRequested = guideRequested ? true : options.brief ?? true;
|
|
7125
7843
|
const reviewRequested = guideRequested ? false : options.review ?? false;
|
|
7126
7844
|
const sources = await loadManagedSources(rootDir);
|
|
7127
|
-
const resolved = await resolveManagedSourceInput(rootDir, input);
|
|
7845
|
+
const resolved = await resolveManagedSourceInput(rootDir, input, options);
|
|
7128
7846
|
const existing = sources.find((candidate) => matchesManagedSourceSpec(candidate, resolved));
|
|
7129
7847
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7130
|
-
const source = existing
|
|
7131
|
-
|
|
7848
|
+
const source = existing ? {
|
|
7849
|
+
...existing,
|
|
7850
|
+
branch: resolved.kind === "github_repo" ? resolved.branch : existing.branch,
|
|
7851
|
+
ref: resolved.kind === "github_repo" ? resolved.ref : existing.ref,
|
|
7852
|
+
checkoutDir: resolved.kind === "github_repo" ? resolved.checkoutDir ?? existing.checkoutDir : existing.checkoutDir
|
|
7853
|
+
} : {
|
|
7854
|
+
id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path13.resolve(resolved.path), resolved.title) : stableManagedSourceId(
|
|
7855
|
+
resolved.kind,
|
|
7856
|
+
resolved.kind === "github_repo" ? `${resolved.url}#branch=${resolved.branch ?? ""}#ref=${resolved.ref ?? ""}` : resolved.url,
|
|
7857
|
+
resolved.title
|
|
7858
|
+
),
|
|
7132
7859
|
kind: resolved.kind,
|
|
7133
7860
|
title: resolved.title,
|
|
7134
7861
|
path: resolved.kind === "directory" || resolved.kind === "file" ? resolved.path : void 0,
|
|
7135
7862
|
repoRoot: resolved.kind === "directory" ? resolved.repoRoot : void 0,
|
|
7136
7863
|
url: resolved.kind === "directory" || resolved.kind === "file" ? void 0 : resolved.url,
|
|
7864
|
+
branch: resolved.kind === "github_repo" ? resolved.branch : void 0,
|
|
7865
|
+
ref: resolved.kind === "github_repo" ? resolved.ref : void 0,
|
|
7866
|
+
checkoutDir: resolved.kind === "github_repo" ? resolved.checkoutDir : void 0,
|
|
7137
7867
|
createdAt: now,
|
|
7138
7868
|
updatedAt: now,
|
|
7139
7869
|
status: "ready",
|
|
@@ -7256,7 +7986,7 @@ async function deleteManagedSource(rootDir, id) {
|
|
|
7256
7986
|
sources.filter((source) => source.id !== id)
|
|
7257
7987
|
);
|
|
7258
7988
|
const workingDir = await managedSourceWorkingDir(rootDir, id);
|
|
7259
|
-
await
|
|
7989
|
+
await fs11.rm(workingDir, { recursive: true, force: true });
|
|
7260
7990
|
return { removed: target };
|
|
7261
7991
|
}
|
|
7262
7992
|
|
|
@@ -7264,9 +7994,9 @@ async function deleteManagedSource(rootDir, id) {
|
|
|
7264
7994
|
import { execFile as execFile2 } from "child_process";
|
|
7265
7995
|
import { randomUUID } from "crypto";
|
|
7266
7996
|
import { EventEmitter } from "events";
|
|
7267
|
-
import
|
|
7997
|
+
import fs12 from "fs/promises";
|
|
7268
7998
|
import http from "http";
|
|
7269
|
-
import
|
|
7999
|
+
import path14 from "path";
|
|
7270
8000
|
import { promisify as promisify2 } from "util";
|
|
7271
8001
|
import matter4 from "gray-matter";
|
|
7272
8002
|
import mime from "mime-types";
|
|
@@ -7420,7 +8150,7 @@ function toViewerLintFindings(findings) {
|
|
|
7420
8150
|
var execFileAsync2 = promisify2(execFile2);
|
|
7421
8151
|
async function isReadableFile(absolutePath) {
|
|
7422
8152
|
try {
|
|
7423
|
-
const stats = await
|
|
8153
|
+
const stats = await fs12.stat(absolutePath);
|
|
7424
8154
|
return stats.isFile();
|
|
7425
8155
|
} catch {
|
|
7426
8156
|
return false;
|
|
@@ -7431,15 +8161,15 @@ async function readViewerPage(rootDir, relativePath) {
|
|
|
7431
8161
|
return null;
|
|
7432
8162
|
}
|
|
7433
8163
|
const { paths } = await loadVaultConfig(rootDir);
|
|
7434
|
-
const absolutePath =
|
|
8164
|
+
const absolutePath = path14.resolve(paths.wikiDir, relativePath);
|
|
7435
8165
|
if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
|
|
7436
8166
|
return null;
|
|
7437
8167
|
}
|
|
7438
|
-
const raw = await
|
|
8168
|
+
const raw = await fs12.readFile(absolutePath, "utf8");
|
|
7439
8169
|
const parsed = matter4(raw);
|
|
7440
8170
|
return {
|
|
7441
8171
|
path: relativePath,
|
|
7442
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
8172
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path14.basename(relativePath, path14.extname(relativePath)),
|
|
7443
8173
|
frontmatter: parsed.data,
|
|
7444
8174
|
content: parsed.content,
|
|
7445
8175
|
assets: normalizeOutputAssets(parsed.data.output_assets)
|
|
@@ -7450,12 +8180,12 @@ async function readViewerAsset(rootDir, relativePath) {
|
|
|
7450
8180
|
return null;
|
|
7451
8181
|
}
|
|
7452
8182
|
const { paths } = await loadVaultConfig(rootDir);
|
|
7453
|
-
const absolutePath =
|
|
8183
|
+
const absolutePath = path14.resolve(paths.wikiDir, relativePath);
|
|
7454
8184
|
if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
|
|
7455
8185
|
return null;
|
|
7456
8186
|
}
|
|
7457
8187
|
return {
|
|
7458
|
-
buffer: await
|
|
8188
|
+
buffer: await fs12.readFile(absolutePath),
|
|
7459
8189
|
mimeType: mime.lookup(absolutePath) || "application/octet-stream"
|
|
7460
8190
|
};
|
|
7461
8191
|
}
|
|
@@ -7491,8 +8221,8 @@ async function writeInboxClip(rootDir, body) {
|
|
|
7491
8221
|
const tags = Array.isArray(body.tags) ? body.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : [];
|
|
7492
8222
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7493
8223
|
const fileName = `${now.replace(/[:.]/g, "-")}-${slugForClip(title)}.md`;
|
|
7494
|
-
const inboxPath =
|
|
7495
|
-
await
|
|
8224
|
+
const inboxPath = path14.join(paths.inboxDir, fileName);
|
|
8225
|
+
await fs12.mkdir(paths.inboxDir, { recursive: true });
|
|
7496
8226
|
const lines = [
|
|
7497
8227
|
"---",
|
|
7498
8228
|
`title: ${JSON.stringify(title)}`,
|
|
@@ -7509,17 +8239,17 @@ async function writeInboxClip(rootDir, body) {
|
|
|
7509
8239
|
selectionHtml && !markdown ? ["", "## Original HTML", "", "```html", selectionHtml, "```"].join("\n") : void 0,
|
|
7510
8240
|
""
|
|
7511
8241
|
].filter((line) => line !== void 0);
|
|
7512
|
-
await
|
|
8242
|
+
await fs12.writeFile(inboxPath, lines.join("\n"), "utf8");
|
|
7513
8243
|
const result = await importInbox(rootDir, paths.inboxDir);
|
|
7514
8244
|
return { mode: "inbox", inboxPath, result };
|
|
7515
8245
|
}
|
|
7516
8246
|
async function ensureViewerDist(viewerDistDir) {
|
|
7517
|
-
const indexPath =
|
|
8247
|
+
const indexPath = path14.join(viewerDistDir, "index.html");
|
|
7518
8248
|
if (await fileExists(indexPath)) {
|
|
7519
8249
|
return;
|
|
7520
8250
|
}
|
|
7521
|
-
const viewerProjectDir =
|
|
7522
|
-
if (await fileExists(
|
|
8251
|
+
const viewerProjectDir = path14.dirname(viewerDistDir);
|
|
8252
|
+
if (await fileExists(path14.join(viewerProjectDir, "package.json"))) {
|
|
7523
8253
|
await execFileAsync2("pnpm", ["build"], { cwd: viewerProjectDir });
|
|
7524
8254
|
}
|
|
7525
8255
|
}
|
|
@@ -7542,7 +8272,7 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
7542
8272
|
response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
|
|
7543
8273
|
return;
|
|
7544
8274
|
}
|
|
7545
|
-
const reportPath =
|
|
8275
|
+
const reportPath = path14.join(paths.wikiDir, "graph", "report.json");
|
|
7546
8276
|
const report = await readJsonFile(reportPath) ?? null;
|
|
7547
8277
|
response.writeHead(200, { "content-type": "application/json" });
|
|
7548
8278
|
response.end(JSON.stringify(buildViewerGraphArtifact(graph, { report, full: options.full ?? false })));
|
|
@@ -7606,13 +8336,13 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
7606
8336
|
return;
|
|
7607
8337
|
}
|
|
7608
8338
|
if (url.pathname === "/api/graph-report") {
|
|
7609
|
-
const reportPath =
|
|
8339
|
+
const reportPath = path14.join(paths.wikiDir, "graph", "report.json");
|
|
7610
8340
|
if (!await fileExists(reportPath)) {
|
|
7611
8341
|
response.writeHead(404, { "content-type": "application/json" });
|
|
7612
8342
|
response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
|
|
7613
8343
|
return;
|
|
7614
8344
|
}
|
|
7615
|
-
const body = await
|
|
8345
|
+
const body = await fs12.readFile(reportPath, "utf8");
|
|
7616
8346
|
response.writeHead(200, { "content-type": "application/json" });
|
|
7617
8347
|
response.end(body);
|
|
7618
8348
|
return;
|
|
@@ -7850,7 +8580,7 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
7850
8580
|
return;
|
|
7851
8581
|
}
|
|
7852
8582
|
if (url.pathname === "/api/workspace") {
|
|
7853
|
-
const reportPath =
|
|
8583
|
+
const reportPath = path14.join(paths.wikiDir, "graph", "report.json");
|
|
7854
8584
|
const [graphRaw, reportRaw, approvalsRaw, candidatesRaw, memoryTasksRaw, watchStatusRaw, lintRaw, doctorRaw] = await Promise.all([
|
|
7855
8585
|
readJsonFile(paths.graphPath).catch(() => null),
|
|
7856
8586
|
readJsonFile(reportPath).catch(() => null),
|
|
@@ -7975,15 +8705,15 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
7975
8705
|
return;
|
|
7976
8706
|
}
|
|
7977
8707
|
const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
7978
|
-
const target =
|
|
7979
|
-
const fallback =
|
|
8708
|
+
const target = path14.join(paths.viewerDistDir, relativePath);
|
|
8709
|
+
const fallback = path14.join(paths.viewerDistDir, "index.html");
|
|
7980
8710
|
const filePath = await fileExists(target) ? target : fallback;
|
|
7981
8711
|
if (!await fileExists(filePath)) {
|
|
7982
8712
|
response.writeHead(503, { "content-type": "text/plain" });
|
|
7983
8713
|
response.end("Viewer build not found. Run `pnpm build` first.");
|
|
7984
8714
|
return;
|
|
7985
8715
|
}
|
|
7986
|
-
const staticBody = await
|
|
8716
|
+
const staticBody = await fs12.readFile(filePath);
|
|
7987
8717
|
response.writeHead(200, { "content-type": mime.lookup(filePath) || "text/plain" });
|
|
7988
8718
|
response.end(staticBody);
|
|
7989
8719
|
} catch (error) {
|
|
@@ -8023,7 +8753,7 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
|
|
|
8023
8753
|
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
8024
8754
|
}
|
|
8025
8755
|
await ensureViewerDist(paths.viewerDistDir);
|
|
8026
|
-
const indexPath =
|
|
8756
|
+
const indexPath = path14.join(paths.viewerDistDir, "index.html");
|
|
8027
8757
|
if (!await fileExists(indexPath)) {
|
|
8028
8758
|
throw new Error("Viewer build not found. Run `pnpm build` first.");
|
|
8029
8759
|
}
|
|
@@ -8049,17 +8779,17 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
|
|
|
8049
8779
|
} : null;
|
|
8050
8780
|
})
|
|
8051
8781
|
);
|
|
8052
|
-
const rawHtml = await
|
|
8782
|
+
const rawHtml = await fs12.readFile(indexPath, "utf8");
|
|
8053
8783
|
const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
|
|
8054
8784
|
const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
|
|
8055
|
-
const scriptPath = scriptMatch?.[1] ?
|
|
8056
|
-
const stylePath = styleMatch?.[1] ?
|
|
8785
|
+
const scriptPath = scriptMatch?.[1] ? path14.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
|
|
8786
|
+
const stylePath = styleMatch?.[1] ? path14.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
|
|
8057
8787
|
if (!scriptPath || !await fileExists(scriptPath)) {
|
|
8058
8788
|
throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
|
|
8059
8789
|
}
|
|
8060
|
-
const script = await
|
|
8061
|
-
const style = stylePath && await fileExists(stylePath) ? await
|
|
8062
|
-
const report = await readJsonFile(
|
|
8790
|
+
const script = await fs12.readFile(scriptPath, "utf8");
|
|
8791
|
+
const style = stylePath && await fileExists(stylePath) ? await fs12.readFile(stylePath, "utf8") : "";
|
|
8792
|
+
const report = await readJsonFile(path14.join(paths.wikiDir, "graph", "report.json"));
|
|
8063
8793
|
const embeddedData = JSON.stringify(
|
|
8064
8794
|
{ graph: buildViewerGraphArtifact(graph, { report, full: options.full ?? false }), pages: pages.filter(Boolean), report },
|
|
8065
8795
|
null,
|
|
@@ -8082,9 +8812,9 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
|
|
|
8082
8812
|
"</html>",
|
|
8083
8813
|
""
|
|
8084
8814
|
].filter(Boolean).join("\n");
|
|
8085
|
-
await
|
|
8086
|
-
await
|
|
8087
|
-
return
|
|
8815
|
+
await fs12.mkdir(path14.dirname(outputPath), { recursive: true });
|
|
8816
|
+
await fs12.writeFile(outputPath, html, "utf8");
|
|
8817
|
+
return path14.resolve(outputPath);
|
|
8088
8818
|
}
|
|
8089
8819
|
export {
|
|
8090
8820
|
ALL_MIGRATIONS,
|
|
@@ -8114,6 +8844,7 @@ export {
|
|
|
8114
8844
|
buildConfiguredRedactor,
|
|
8115
8845
|
buildContextPack,
|
|
8116
8846
|
buildGraphShareArtifact,
|
|
8847
|
+
buildGraphTree,
|
|
8117
8848
|
buildMemoryGraphElements,
|
|
8118
8849
|
buildRedactor,
|
|
8119
8850
|
checkTrackedRepoChanges,
|
|
@@ -8137,12 +8868,14 @@ export {
|
|
|
8137
8868
|
estimatePageTokens,
|
|
8138
8869
|
estimateTokens,
|
|
8139
8870
|
evaluateCandidateForPromotion,
|
|
8871
|
+
evaluateGraphShrinkGuard,
|
|
8140
8872
|
expectedModelPath,
|
|
8141
8873
|
explainGraphVault,
|
|
8142
8874
|
exploreVault,
|
|
8143
8875
|
exportGraphFormat,
|
|
8144
8876
|
exportGraphHtml,
|
|
8145
8877
|
exportGraphReportHtml,
|
|
8878
|
+
exportGraphTree,
|
|
8146
8879
|
exportObsidianCanvas,
|
|
8147
8880
|
exportObsidianVault,
|
|
8148
8881
|
finishMemoryTask,
|
|
@@ -8187,11 +8920,13 @@ export {
|
|
|
8187
8920
|
lookupPresetCapabilities,
|
|
8188
8921
|
markSuperseded,
|
|
8189
8922
|
memoryTaskHashes,
|
|
8923
|
+
mergeGraphFiles,
|
|
8190
8924
|
modelDownloadUrl,
|
|
8191
8925
|
pathGraphVault,
|
|
8192
8926
|
persistDecayFrontmatter,
|
|
8193
8927
|
planMigration,
|
|
8194
8928
|
previewCandidatePromotions,
|
|
8929
|
+
projectGraphAfterRemovals,
|
|
8195
8930
|
promoteCandidate,
|
|
8196
8931
|
pushGraphNeo4j,
|
|
8197
8932
|
queryGraphVault,
|
|
@@ -8214,6 +8949,7 @@ export {
|
|
|
8214
8949
|
renderGraphShareMarkdown,
|
|
8215
8950
|
renderGraphSharePreviewHtml,
|
|
8216
8951
|
renderGraphShareSvg,
|
|
8952
|
+
renderGraphTreeHtml,
|
|
8217
8953
|
renderMemoryTaskMarkdown,
|
|
8218
8954
|
resetDecay,
|
|
8219
8955
|
resolveArtifactRootDir,
|