@swarmvaultai/engine 3.6.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/chunk-S2E65WRI.js +26062 -0
- package/dist/index.d.ts +74 -2
- package/dist/index.js +835 -131
- package/dist/memory-DNSQCDHC.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-S2E65WRI.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,35 @@ 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
|
+
}
|
|
641
676
|
function hasIgnoredRepoSegment(baseDir, targetPath) {
|
|
642
677
|
const relativePath = path2.relative(baseDir, targetPath);
|
|
643
678
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
@@ -774,6 +809,7 @@ async function performWatchCycle(rootDir, paths, options, codeOnly = false) {
|
|
|
774
809
|
}
|
|
775
810
|
async function runWatchCycle(rootDir, options = {}) {
|
|
776
811
|
const { paths } = await initWorkspace(rootDir);
|
|
812
|
+
const previousGraph = await readJsonFile(paths.graphPath);
|
|
777
813
|
const startedAt = /* @__PURE__ */ new Date();
|
|
778
814
|
let success = true;
|
|
779
815
|
let error;
|
|
@@ -792,6 +828,12 @@ async function runWatchCycle(rootDir, options = {}) {
|
|
|
792
828
|
};
|
|
793
829
|
try {
|
|
794
830
|
result = await performWatchCycle(rootDir, paths, options, options.codeOnly ?? false);
|
|
831
|
+
const nextGraph = await readJsonFile(paths.graphPath);
|
|
832
|
+
const guard = evaluateGraphShrinkGuard(previousGraph, nextGraph, { threshold: options.maxGraphShrinkRatio });
|
|
833
|
+
if (previousGraph && nextGraph && guard.blocked && !forceGraphUpdateEnabled(options)) {
|
|
834
|
+
await writeJsonFile(paths.graphPath, previousGraph);
|
|
835
|
+
throw new Error(guard.message ?? "Graph update aborted because the graph shrank unexpectedly.");
|
|
836
|
+
}
|
|
795
837
|
return result;
|
|
796
838
|
} catch (caught) {
|
|
797
839
|
success = false;
|
|
@@ -3710,9 +3752,350 @@ Community: ${communityLabel}`,
|
|
|
3710
3752
|
return { format: "canvas", outputPath: resolvedPath };
|
|
3711
3753
|
}
|
|
3712
3754
|
|
|
3713
|
-
// src/graph-
|
|
3755
|
+
// src/graph-merge.ts
|
|
3714
3756
|
import fs5 from "fs/promises";
|
|
3715
3757
|
import path5 from "path";
|
|
3758
|
+
function isRecord(value) {
|
|
3759
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3760
|
+
}
|
|
3761
|
+
function isSwarmVaultGraph(value) {
|
|
3762
|
+
return isRecord(value) && Array.isArray(value.nodes) && Array.isArray(value.edges) && Array.isArray(value.sources) && Array.isArray(value.pages);
|
|
3763
|
+
}
|
|
3764
|
+
function stringField(record, ...fields) {
|
|
3765
|
+
for (const field of fields) {
|
|
3766
|
+
const value = record[field];
|
|
3767
|
+
if (typeof value === "string" && value.trim()) {
|
|
3768
|
+
return value.trim();
|
|
3769
|
+
}
|
|
3770
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
3771
|
+
return String(value);
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
return void 0;
|
|
3775
|
+
}
|
|
3776
|
+
function arrayStringField(record, field) {
|
|
3777
|
+
const value = record[field];
|
|
3778
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
3779
|
+
}
|
|
3780
|
+
function numberField(record, field, fallback) {
|
|
3781
|
+
const value = record[field];
|
|
3782
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
3783
|
+
}
|
|
3784
|
+
function safePrefix(inputPath, index) {
|
|
3785
|
+
return slugify(path5.basename(inputPath, path5.extname(inputPath)) || `graph-${index + 1}`);
|
|
3786
|
+
}
|
|
3787
|
+
function prefixed(prefix, id) {
|
|
3788
|
+
return `${prefix}:${id}`;
|
|
3789
|
+
}
|
|
3790
|
+
function ensureUniquePrefix(base, used) {
|
|
3791
|
+
let candidate = base || "graph";
|
|
3792
|
+
let suffix = 2;
|
|
3793
|
+
while (used.has(candidate)) {
|
|
3794
|
+
candidate = `${base}-${suffix}`;
|
|
3795
|
+
suffix += 1;
|
|
3796
|
+
}
|
|
3797
|
+
used.add(candidate);
|
|
3798
|
+
return candidate;
|
|
3799
|
+
}
|
|
3800
|
+
function mapEvidenceClass(value) {
|
|
3801
|
+
const normalized = typeof value === "string" ? value.toLowerCase() : "";
|
|
3802
|
+
if (normalized === "extracted") return "extracted";
|
|
3803
|
+
if (normalized === "ambiguous") return "ambiguous";
|
|
3804
|
+
return "inferred";
|
|
3805
|
+
}
|
|
3806
|
+
function mapNodeType(value) {
|
|
3807
|
+
const normalized = typeof value === "string" ? value.toLowerCase() : "";
|
|
3808
|
+
if (["source", "file", "document", "paper", "image", "video"].includes(normalized)) return "source";
|
|
3809
|
+
if (["module", "code"].includes(normalized)) return "module";
|
|
3810
|
+
if (["function", "class", "symbol", "method", "component"].includes(normalized)) return "symbol";
|
|
3811
|
+
if (["entity", "person", "org", "organization"].includes(normalized)) return "entity";
|
|
3812
|
+
if (["rationale", "comment", "docstring", "why"].includes(normalized)) return "rationale";
|
|
3813
|
+
if (["decision", "adr"].includes(normalized)) return "decision";
|
|
3814
|
+
return "concept";
|
|
3815
|
+
}
|
|
3816
|
+
function remapSwarmVaultGraph(inputPath, graph, prefix) {
|
|
3817
|
+
const sourceMap = new Map(graph.sources.map((source) => [source.sourceId, prefixed(prefix, source.sourceId)]));
|
|
3818
|
+
const pageMap = new Map(graph.pages.map((page) => [page.id, prefixed(prefix, page.id)]));
|
|
3819
|
+
const nodeMap = new Map(graph.nodes.map((node) => [node.id, prefixed(prefix, node.id)]));
|
|
3820
|
+
const communityMap = new Map((graph.communities ?? []).map((community) => [community.id, prefixed(prefix, community.id)]));
|
|
3821
|
+
const sources = graph.sources.map((source) => ({
|
|
3822
|
+
...source,
|
|
3823
|
+
sourceId: sourceMap.get(source.sourceId) ?? prefixed(prefix, source.sourceId),
|
|
3824
|
+
title: `[${prefix}] ${source.title}`,
|
|
3825
|
+
sourceGroupId: source.sourceGroupId ? prefixed(prefix, source.sourceGroupId) : void 0,
|
|
3826
|
+
details: {
|
|
3827
|
+
...source.details ?? {},
|
|
3828
|
+
mergedInput: inputPath,
|
|
3829
|
+
mergedPrefix: prefix
|
|
3830
|
+
}
|
|
3831
|
+
}));
|
|
3832
|
+
const pages = graph.pages.map((page) => ({
|
|
3833
|
+
...page,
|
|
3834
|
+
id: pageMap.get(page.id) ?? prefixed(prefix, page.id),
|
|
3835
|
+
path: toPosix(path5.posix.join("merged", prefix, page.path)),
|
|
3836
|
+
sourceIds: page.sourceIds.map((sourceId) => sourceMap.get(sourceId) ?? prefixed(prefix, sourceId)),
|
|
3837
|
+
nodeIds: page.nodeIds.map((nodeId) => nodeMap.get(nodeId) ?? prefixed(prefix, nodeId)),
|
|
3838
|
+
relatedPageIds: page.relatedPageIds.map((pageId) => pageMap.get(pageId) ?? prefixed(prefix, pageId)),
|
|
3839
|
+
relatedNodeIds: page.relatedNodeIds.map((nodeId) => nodeMap.get(nodeId) ?? prefixed(prefix, nodeId)),
|
|
3840
|
+
relatedSourceIds: page.relatedSourceIds.map((sourceId) => sourceMap.get(sourceId) ?? prefixed(prefix, sourceId)),
|
|
3841
|
+
backlinks: page.backlinks.map((pageId) => pageMap.get(pageId) ?? prefixed(prefix, pageId)),
|
|
3842
|
+
supersededBy: page.supersededBy ? pageMap.get(page.supersededBy) ?? prefixed(prefix, page.supersededBy) : void 0
|
|
3843
|
+
}));
|
|
3844
|
+
const nodes = graph.nodes.map((node) => ({
|
|
3845
|
+
...node,
|
|
3846
|
+
id: nodeMap.get(node.id) ?? prefixed(prefix, node.id),
|
|
3847
|
+
pageId: node.pageId ? pageMap.get(node.pageId) ?? prefixed(prefix, node.pageId) : void 0,
|
|
3848
|
+
sourceIds: node.sourceIds.map((sourceId) => sourceMap.get(sourceId) ?? prefixed(prefix, sourceId)),
|
|
3849
|
+
moduleId: node.moduleId ? nodeMap.get(node.moduleId) ?? prefixed(prefix, node.moduleId) : void 0,
|
|
3850
|
+
communityId: node.communityId ? communityMap.get(node.communityId) ?? prefixed(prefix, node.communityId) : void 0
|
|
3851
|
+
}));
|
|
3852
|
+
const edges = graph.edges.map((edge) => ({
|
|
3853
|
+
...edge,
|
|
3854
|
+
id: prefixed(prefix, edge.id),
|
|
3855
|
+
source: nodeMap.get(edge.source) ?? prefixed(prefix, edge.source),
|
|
3856
|
+
target: nodeMap.get(edge.target) ?? prefixed(prefix, edge.target),
|
|
3857
|
+
provenance: edge.provenance.map((id) => nodeMap.get(id) ?? pageMap.get(id) ?? sourceMap.get(id) ?? prefixed(prefix, id))
|
|
3858
|
+
}));
|
|
3859
|
+
const hyperedges = graph.hyperedges.map((hyperedge) => ({
|
|
3860
|
+
...hyperedge,
|
|
3861
|
+
id: prefixed(prefix, hyperedge.id),
|
|
3862
|
+
nodeIds: hyperedge.nodeIds.map((nodeId) => nodeMap.get(nodeId) ?? prefixed(prefix, nodeId)),
|
|
3863
|
+
sourcePageIds: hyperedge.sourcePageIds.map((pageId) => pageMap.get(pageId) ?? prefixed(prefix, pageId))
|
|
3864
|
+
}));
|
|
3865
|
+
return {
|
|
3866
|
+
generatedAt: graph.generatedAt,
|
|
3867
|
+
nodes,
|
|
3868
|
+
edges,
|
|
3869
|
+
hyperedges,
|
|
3870
|
+
communities: (graph.communities ?? []).map((community) => ({
|
|
3871
|
+
...community,
|
|
3872
|
+
id: communityMap.get(community.id) ?? prefixed(prefix, community.id),
|
|
3873
|
+
label: `[${prefix}] ${community.label}`,
|
|
3874
|
+
nodeIds: community.nodeIds.map((nodeId) => nodeMap.get(nodeId) ?? prefixed(prefix, nodeId))
|
|
3875
|
+
})),
|
|
3876
|
+
sources,
|
|
3877
|
+
pages
|
|
3878
|
+
};
|
|
3879
|
+
}
|
|
3880
|
+
function nodeLinkArrays(raw) {
|
|
3881
|
+
const nodes = raw.nodes;
|
|
3882
|
+
const edges = Array.isArray(raw.links) ? raw.links : raw.edges;
|
|
3883
|
+
if (!Array.isArray(nodes) || !Array.isArray(edges)) {
|
|
3884
|
+
return null;
|
|
3885
|
+
}
|
|
3886
|
+
return {
|
|
3887
|
+
nodes,
|
|
3888
|
+
edges: edges.filter(isRecord)
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
function nodeLinkNodeId(node, index) {
|
|
3892
|
+
if (typeof node === "string" || typeof node === "number") {
|
|
3893
|
+
return String(node);
|
|
3894
|
+
}
|
|
3895
|
+
return stringField(node, "id", "key", "name", "label") ?? `node-${index + 1}`;
|
|
3896
|
+
}
|
|
3897
|
+
function endpointId(value) {
|
|
3898
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
3899
|
+
return String(value);
|
|
3900
|
+
}
|
|
3901
|
+
if (isRecord(value)) {
|
|
3902
|
+
return stringField(value, "id", "key", "name", "label");
|
|
3903
|
+
}
|
|
3904
|
+
return void 0;
|
|
3905
|
+
}
|
|
3906
|
+
function remapNodeLinkGraph(inputPath, raw, prefix, now) {
|
|
3907
|
+
const arrays = nodeLinkArrays(raw);
|
|
3908
|
+
if (!arrays) {
|
|
3909
|
+
throw new Error(`${inputPath} is not a SwarmVault graph or node-link graph.`);
|
|
3910
|
+
}
|
|
3911
|
+
const syntheticSourceId = prefixed(prefix, "source");
|
|
3912
|
+
const source = {
|
|
3913
|
+
sourceId: syntheticSourceId,
|
|
3914
|
+
title: `${prefix} merged graph`,
|
|
3915
|
+
originType: "file",
|
|
3916
|
+
sourceKind: "data",
|
|
3917
|
+
sourceClass: "generated",
|
|
3918
|
+
originalPath: inputPath,
|
|
3919
|
+
storedPath: inputPath,
|
|
3920
|
+
mimeType: "application/json",
|
|
3921
|
+
contentHash: `sha256:${sha256(JSON.stringify(raw)).slice(0, 24)}`,
|
|
3922
|
+
semanticHash: `sha256:${sha256(`${inputPath}:${arrays.nodes.length}:${arrays.edges.length}`).slice(0, 24)}`,
|
|
3923
|
+
details: {
|
|
3924
|
+
mergedInput: inputPath,
|
|
3925
|
+
mergedFormat: "node-link"
|
|
3926
|
+
},
|
|
3927
|
+
createdAt: now,
|
|
3928
|
+
updatedAt: now
|
|
3929
|
+
};
|
|
3930
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
3931
|
+
const nodes = arrays.nodes.map((node, index) => {
|
|
3932
|
+
const originalId = nodeLinkNodeId(node, index);
|
|
3933
|
+
const mappedId = prefixed(prefix, originalId);
|
|
3934
|
+
idMap.set(originalId, mappedId);
|
|
3935
|
+
const record = isRecord(node) ? node : {};
|
|
3936
|
+
const label = stringField(record, "label", "name", "title", "path", "id") ?? originalId;
|
|
3937
|
+
const type = mapNodeType(record.type ?? record.file_type ?? record.kind ?? record.category);
|
|
3938
|
+
return {
|
|
3939
|
+
id: mappedId,
|
|
3940
|
+
type,
|
|
3941
|
+
label,
|
|
3942
|
+
sourceIds: [syntheticSourceId],
|
|
3943
|
+
projectIds: [],
|
|
3944
|
+
sourceClass: "generated",
|
|
3945
|
+
confidence: numberField(record, "confidence", numberField(record, "confidence_score", 1)),
|
|
3946
|
+
tags: arrayStringField(record, "tags")
|
|
3947
|
+
};
|
|
3948
|
+
});
|
|
3949
|
+
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
3950
|
+
const edges = arrays.edges.flatMap((edge, index) => {
|
|
3951
|
+
const source2 = endpointId(edge.source ?? edge.from);
|
|
3952
|
+
const target = endpointId(edge.target ?? edge.to);
|
|
3953
|
+
if (!source2 || !target) {
|
|
3954
|
+
return [];
|
|
3955
|
+
}
|
|
3956
|
+
const mappedSource = idMap.get(source2) ?? prefixed(prefix, source2);
|
|
3957
|
+
const mappedTarget = idMap.get(target) ?? prefixed(prefix, target);
|
|
3958
|
+
if (!nodeIds.has(mappedSource) || !nodeIds.has(mappedTarget)) {
|
|
3959
|
+
return [];
|
|
3960
|
+
}
|
|
3961
|
+
const evidenceClass = mapEvidenceClass(edge.evidenceClass ?? edge.evidence_class ?? edge.status ?? edge.confidence);
|
|
3962
|
+
return [
|
|
3963
|
+
{
|
|
3964
|
+
id: prefixed(prefix, stringField(edge, "id", "key") ?? `edge-${index + 1}`),
|
|
3965
|
+
source: mappedSource,
|
|
3966
|
+
target: mappedTarget,
|
|
3967
|
+
relation: stringField(edge, "relation", "type", "label") ?? "related_to",
|
|
3968
|
+
status: evidenceClass === "extracted" ? "extracted" : "inferred",
|
|
3969
|
+
evidenceClass,
|
|
3970
|
+
confidence: numberField(edge, "confidence", numberField(edge, "confidence_score", evidenceClass === "ambiguous" ? 0.5 : 0.75)),
|
|
3971
|
+
provenance: [syntheticSourceId]
|
|
3972
|
+
}
|
|
3973
|
+
];
|
|
3974
|
+
});
|
|
3975
|
+
const page = {
|
|
3976
|
+
id: prefixed(prefix, "page"),
|
|
3977
|
+
path: toPosix(path5.posix.join("merged", prefix, "index.md")),
|
|
3978
|
+
title: `${prefix} merged graph`,
|
|
3979
|
+
kind: "source",
|
|
3980
|
+
sourceClass: "generated",
|
|
3981
|
+
sourceIds: [syntheticSourceId],
|
|
3982
|
+
projectIds: [],
|
|
3983
|
+
nodeIds: nodes.map((node) => node.id),
|
|
3984
|
+
freshness: "fresh",
|
|
3985
|
+
status: "active",
|
|
3986
|
+
confidence: 1,
|
|
3987
|
+
backlinks: [],
|
|
3988
|
+
schemaHash: "merged-node-link",
|
|
3989
|
+
sourceHashes: { [syntheticSourceId]: source.contentHash },
|
|
3990
|
+
sourceSemanticHashes: { [syntheticSourceId]: source.semanticHash },
|
|
3991
|
+
relatedPageIds: [],
|
|
3992
|
+
relatedNodeIds: nodes.map((node) => node.id),
|
|
3993
|
+
relatedSourceIds: [syntheticSourceId],
|
|
3994
|
+
createdAt: now,
|
|
3995
|
+
updatedAt: now,
|
|
3996
|
+
compiledFrom: [inputPath],
|
|
3997
|
+
managedBy: "system"
|
|
3998
|
+
};
|
|
3999
|
+
return {
|
|
4000
|
+
generatedAt: now,
|
|
4001
|
+
nodes,
|
|
4002
|
+
edges,
|
|
4003
|
+
hyperedges: [],
|
|
4004
|
+
communities: [],
|
|
4005
|
+
sources: [source],
|
|
4006
|
+
pages: [page]
|
|
4007
|
+
};
|
|
4008
|
+
}
|
|
4009
|
+
function mergeGraphs(graphs, now) {
|
|
4010
|
+
return {
|
|
4011
|
+
generatedAt: now,
|
|
4012
|
+
nodes: uniqueBy(
|
|
4013
|
+
graphs.flatMap((graph) => graph.nodes),
|
|
4014
|
+
(node) => node.id
|
|
4015
|
+
),
|
|
4016
|
+
edges: uniqueBy(
|
|
4017
|
+
graphs.flatMap((graph) => graph.edges),
|
|
4018
|
+
(edge) => edge.id
|
|
4019
|
+
),
|
|
4020
|
+
hyperedges: uniqueBy(
|
|
4021
|
+
graphs.flatMap((graph) => graph.hyperedges),
|
|
4022
|
+
(hyperedge) => hyperedge.id
|
|
4023
|
+
),
|
|
4024
|
+
communities: uniqueBy(
|
|
4025
|
+
graphs.flatMap((graph) => graph.communities ?? []),
|
|
4026
|
+
(community) => community.id
|
|
4027
|
+
),
|
|
4028
|
+
sources: uniqueBy(
|
|
4029
|
+
graphs.flatMap((graph) => graph.sources),
|
|
4030
|
+
(source) => source.sourceId
|
|
4031
|
+
),
|
|
4032
|
+
pages: uniqueBy(
|
|
4033
|
+
graphs.flatMap((graph) => graph.pages),
|
|
4034
|
+
(page) => page.id
|
|
4035
|
+
)
|
|
4036
|
+
};
|
|
4037
|
+
}
|
|
4038
|
+
async function mergeGraphFiles(inputPaths, outputPath, options = {}) {
|
|
4039
|
+
if (inputPaths.length === 0) {
|
|
4040
|
+
throw new Error("At least one graph JSON path is required.");
|
|
4041
|
+
}
|
|
4042
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4043
|
+
const usedPrefixes = /* @__PURE__ */ new Set();
|
|
4044
|
+
const graphs = [];
|
|
4045
|
+
const inputGraphs = [];
|
|
4046
|
+
const warnings = [];
|
|
4047
|
+
for (const [index, inputPath] of inputPaths.entries()) {
|
|
4048
|
+
const resolvedInputPath = path5.resolve(inputPath);
|
|
4049
|
+
const raw = JSON.parse(await fs5.readFile(resolvedInputPath, "utf8"));
|
|
4050
|
+
const prefix = ensureUniquePrefix(
|
|
4051
|
+
inputPaths.length === 1 && options.label ? slugify(options.label) : safePrefix(resolvedInputPath, index),
|
|
4052
|
+
usedPrefixes
|
|
4053
|
+
);
|
|
4054
|
+
if (isSwarmVaultGraph(raw)) {
|
|
4055
|
+
const graph2 = remapSwarmVaultGraph(resolvedInputPath, raw, prefix);
|
|
4056
|
+
graphs.push(graph2);
|
|
4057
|
+
inputGraphs.push({
|
|
4058
|
+
path: resolvedInputPath,
|
|
4059
|
+
label: prefix,
|
|
4060
|
+
format: "swarmvault",
|
|
4061
|
+
nodeCount: raw.nodes.length,
|
|
4062
|
+
edgeCount: raw.edges.length
|
|
4063
|
+
});
|
|
4064
|
+
continue;
|
|
4065
|
+
}
|
|
4066
|
+
if (isRecord(raw) && nodeLinkArrays(raw)) {
|
|
4067
|
+
const graph2 = remapNodeLinkGraph(resolvedInputPath, raw, prefix, now);
|
|
4068
|
+
graphs.push(graph2);
|
|
4069
|
+
inputGraphs.push({
|
|
4070
|
+
path: resolvedInputPath,
|
|
4071
|
+
label: prefix,
|
|
4072
|
+
format: "node-link",
|
|
4073
|
+
nodeCount: graph2.nodes.length,
|
|
4074
|
+
edgeCount: graph2.edges.length
|
|
4075
|
+
});
|
|
4076
|
+
continue;
|
|
4077
|
+
}
|
|
4078
|
+
warnings.push(`${resolvedInputPath} was skipped because it is not a supported graph JSON shape.`);
|
|
4079
|
+
}
|
|
4080
|
+
if (graphs.length === 0) {
|
|
4081
|
+
throw new Error("No supported graph inputs were found.");
|
|
4082
|
+
}
|
|
4083
|
+
const graph = mergeGraphs(graphs, now);
|
|
4084
|
+
const resolvedOutputPath = path5.resolve(outputPath);
|
|
4085
|
+
await ensureDir(path5.dirname(resolvedOutputPath));
|
|
4086
|
+
await fs5.writeFile(resolvedOutputPath, `${JSON.stringify(graph, null, 2)}
|
|
4087
|
+
`, "utf8");
|
|
4088
|
+
return {
|
|
4089
|
+
outputPath: resolvedOutputPath,
|
|
4090
|
+
graph,
|
|
4091
|
+
inputGraphs,
|
|
4092
|
+
warnings
|
|
4093
|
+
};
|
|
4094
|
+
}
|
|
4095
|
+
|
|
4096
|
+
// src/graph-push.ts
|
|
4097
|
+
import fs6 from "fs/promises";
|
|
4098
|
+
import path6 from "path";
|
|
3716
4099
|
import neo4j from "neo4j-driver";
|
|
3717
4100
|
var DEFAULT_NEO4J_BATCH_SIZE = 500;
|
|
3718
4101
|
var DEFAULT_NEO4J_DATABASE = "neo4j";
|
|
@@ -3723,8 +4106,8 @@ function requireConfigValue(value, name) {
|
|
|
3723
4106
|
throw new Error(`Neo4j push requires ${name}. Configure \`graphSinks.neo4j.${name}\` or pass the matching CLI flag.`);
|
|
3724
4107
|
}
|
|
3725
4108
|
async function deriveVaultId(rootDir) {
|
|
3726
|
-
const realRoot = await
|
|
3727
|
-
const label = slugify(
|
|
4109
|
+
const realRoot = await fs6.realpath(rootDir).catch(() => path6.resolve(rootDir));
|
|
4110
|
+
const label = slugify(path6.basename(realRoot));
|
|
3728
4111
|
return `${label}-${sha256(realRoot).slice(0, 12)}`;
|
|
3729
4112
|
}
|
|
3730
4113
|
async function resolveNeo4jPushConfig(rootDir, options) {
|
|
@@ -3754,7 +4137,7 @@ function normalizeBatchSize(value) {
|
|
|
3754
4137
|
}
|
|
3755
4138
|
async function loadGraph2(rootDir) {
|
|
3756
4139
|
const { paths } = await loadVaultConfig(rootDir);
|
|
3757
|
-
const raw = JSON.parse(await
|
|
4140
|
+
const raw = JSON.parse(await fs6.readFile(paths.graphPath, "utf8"));
|
|
3758
4141
|
return raw;
|
|
3759
4142
|
}
|
|
3760
4143
|
function buildResult(input) {
|
|
@@ -3839,7 +4222,7 @@ async function writeSyncNode(session, input) {
|
|
|
3839
4222
|
].join("\n"),
|
|
3840
4223
|
{
|
|
3841
4224
|
vaultId: input.vaultId,
|
|
3842
|
-
rootDir:
|
|
4225
|
+
rootDir: path6.resolve(input.rootDir),
|
|
3843
4226
|
graphGeneratedAt: input.graph.generatedAt,
|
|
3844
4227
|
graphHash: graphHash(input.graph),
|
|
3845
4228
|
pushedAt: input.pushedAt,
|
|
@@ -3925,7 +4308,7 @@ async function pushGraphNeo4j(rootDir, options = {}) {
|
|
|
3925
4308
|
}
|
|
3926
4309
|
|
|
3927
4310
|
// src/graph-status.ts
|
|
3928
|
-
import
|
|
4311
|
+
import path7 from "path";
|
|
3929
4312
|
function recommendedCommand(input) {
|
|
3930
4313
|
if (!input.graphExists || !input.reportExists) {
|
|
3931
4314
|
return "swarmvault compile";
|
|
@@ -3941,8 +4324,8 @@ function recommendedCommand(input) {
|
|
|
3941
4324
|
async function getGraphStatus(rootDir, options = {}) {
|
|
3942
4325
|
const { paths } = await loadVaultConfig(rootDir);
|
|
3943
4326
|
const graphPath = paths.graphPath;
|
|
3944
|
-
const reportPath =
|
|
3945
|
-
const resolvedOverrideRoots = options.repoRoots?.map((repoRoot) =>
|
|
4327
|
+
const reportPath = path7.join(paths.wikiDir, "graph", "report.md");
|
|
4328
|
+
const resolvedOverrideRoots = options.repoRoots?.map((repoRoot) => path7.resolve(rootDir, repoRoot));
|
|
3946
4329
|
const [graphExists, reportExists, trackedRepoRoots, changes, pendingSemanticRefresh] = await Promise.all([
|
|
3947
4330
|
fileExists(graphPath),
|
|
3948
4331
|
fileExists(reportPath),
|
|
@@ -3975,27 +4358,268 @@ async function getGraphStatus(rootDir, options = {}) {
|
|
|
3975
4358
|
};
|
|
3976
4359
|
}
|
|
3977
4360
|
|
|
4361
|
+
// src/graph-tree.ts
|
|
4362
|
+
import path8 from "path";
|
|
4363
|
+
var DEFAULT_MAX_CHILDREN = 250;
|
|
4364
|
+
function compareTreeNodes(left, right) {
|
|
4365
|
+
const kindOrder = /* @__PURE__ */ new Map([
|
|
4366
|
+
["directory", 0],
|
|
4367
|
+
["source", 1],
|
|
4368
|
+
["module", 2],
|
|
4369
|
+
["symbol", 3],
|
|
4370
|
+
["rationale", 4],
|
|
4371
|
+
["node", 5],
|
|
4372
|
+
["more", 6],
|
|
4373
|
+
["root", 7]
|
|
4374
|
+
]);
|
|
4375
|
+
const leftKind = kindOrder.get(left.kind) ?? 99;
|
|
4376
|
+
const rightKind = kindOrder.get(right.kind) ?? 99;
|
|
4377
|
+
return leftKind - rightKind || left.label.localeCompare(right.label) || left.id.localeCompare(right.id);
|
|
4378
|
+
}
|
|
4379
|
+
function escapeHtml(value) {
|
|
4380
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
4381
|
+
}
|
|
4382
|
+
function normalizeSourcePath(rootDir, source) {
|
|
4383
|
+
const candidate = source.repoRelativePath ?? source.originalPath ?? source.storedPath ?? source.title;
|
|
4384
|
+
if (source.repoRelativePath) {
|
|
4385
|
+
return toPosix(source.repoRelativePath);
|
|
4386
|
+
}
|
|
4387
|
+
if (rootDir && path8.isAbsolute(candidate) && isPathWithin(rootDir, candidate)) {
|
|
4388
|
+
return toPosix(path8.relative(rootDir, candidate));
|
|
4389
|
+
}
|
|
4390
|
+
if (path8.isAbsolute(candidate)) {
|
|
4391
|
+
return toPosix(path8.basename(candidate));
|
|
4392
|
+
}
|
|
4393
|
+
return toPosix(candidate).replace(/^\/+/, "") || source.title || source.sourceId;
|
|
4394
|
+
}
|
|
4395
|
+
function makeDirectoryNode(parentId, segment) {
|
|
4396
|
+
return {
|
|
4397
|
+
id: `${parentId}/${segment}`,
|
|
4398
|
+
label: segment,
|
|
4399
|
+
kind: "directory",
|
|
4400
|
+
count: 0,
|
|
4401
|
+
children: []
|
|
4402
|
+
};
|
|
4403
|
+
}
|
|
4404
|
+
function ensureDirectory(parent, segment) {
|
|
4405
|
+
const existing = parent.children.find((child) => child.kind === "directory" && child.label === segment);
|
|
4406
|
+
if (existing) {
|
|
4407
|
+
existing.count += 1;
|
|
4408
|
+
return existing;
|
|
4409
|
+
}
|
|
4410
|
+
const created = makeDirectoryNode(parent.id, segment);
|
|
4411
|
+
created.count = 1;
|
|
4412
|
+
parent.children.push(created);
|
|
4413
|
+
return created;
|
|
4414
|
+
}
|
|
4415
|
+
function nodeChildrenForSource(sourceId, nodes) {
|
|
4416
|
+
const sourceNodes = nodes.filter((node) => node.sourceIds.includes(sourceId));
|
|
4417
|
+
const modules = sourceNodes.filter((node) => node.type === "module").sort((left, right) => left.label.localeCompare(right.label));
|
|
4418
|
+
const moduleIds = new Set(modules.map((node) => node.id));
|
|
4419
|
+
const symbols = sourceNodes.filter((node) => node.type === "symbol");
|
|
4420
|
+
const rationales = sourceNodes.filter((node) => node.type === "rationale");
|
|
4421
|
+
const children = [];
|
|
4422
|
+
for (const moduleNode of modules) {
|
|
4423
|
+
const moduleChildren = symbols.filter((symbol) => symbol.moduleId === moduleNode.id).sort((left, right) => left.label.localeCompare(right.label)).map((symbol) => graphNodeToTreeNode(symbol, "symbol"));
|
|
4424
|
+
children.push({
|
|
4425
|
+
id: `tree:${moduleNode.id}`,
|
|
4426
|
+
label: moduleNode.label,
|
|
4427
|
+
kind: "module",
|
|
4428
|
+
count: moduleChildren.length,
|
|
4429
|
+
children: moduleChildren,
|
|
4430
|
+
nodeId: moduleNode.id,
|
|
4431
|
+
sourceId,
|
|
4432
|
+
language: moduleNode.language
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
for (const symbol of symbols.filter((node) => !node.moduleId || !moduleIds.has(node.moduleId))) {
|
|
4436
|
+
children.push(graphNodeToTreeNode(symbol, "symbol"));
|
|
4437
|
+
}
|
|
4438
|
+
for (const rationale of rationales) {
|
|
4439
|
+
children.push(graphNodeToTreeNode(rationale, "rationale"));
|
|
4440
|
+
}
|
|
4441
|
+
return children.sort(compareTreeNodes);
|
|
4442
|
+
}
|
|
4443
|
+
function graphNodeToTreeNode(node, kind) {
|
|
4444
|
+
return {
|
|
4445
|
+
id: `tree:${node.id}`,
|
|
4446
|
+
label: node.label,
|
|
4447
|
+
kind,
|
|
4448
|
+
count: 0,
|
|
4449
|
+
children: [],
|
|
4450
|
+
nodeId: node.id,
|
|
4451
|
+
language: node.language,
|
|
4452
|
+
symbolKind: node.symbolKind
|
|
4453
|
+
};
|
|
4454
|
+
}
|
|
4455
|
+
function sortAndCapTree(node, maxChildren) {
|
|
4456
|
+
const sortedChildren = node.children.map((child) => sortAndCapTree(child, maxChildren)).sort(compareTreeNodes);
|
|
4457
|
+
if (sortedChildren.length <= maxChildren) {
|
|
4458
|
+
return { ...node, children: sortedChildren };
|
|
4459
|
+
}
|
|
4460
|
+
const visible = sortedChildren.slice(0, maxChildren);
|
|
4461
|
+
const hidden = sortedChildren.length - visible.length;
|
|
4462
|
+
return {
|
|
4463
|
+
...node,
|
|
4464
|
+
hiddenChildren: hidden,
|
|
4465
|
+
children: [
|
|
4466
|
+
...visible,
|
|
4467
|
+
{
|
|
4468
|
+
id: `${node.id}:more`,
|
|
4469
|
+
label: `+${hidden} more`,
|
|
4470
|
+
kind: "more",
|
|
4471
|
+
count: hidden,
|
|
4472
|
+
children: []
|
|
4473
|
+
}
|
|
4474
|
+
]
|
|
4475
|
+
};
|
|
4476
|
+
}
|
|
4477
|
+
function buildGraphTree(graph, options = {}) {
|
|
4478
|
+
const root = {
|
|
4479
|
+
id: "tree:root",
|
|
4480
|
+
label: options.label ?? "SwarmVault Graph Tree",
|
|
4481
|
+
kind: "root",
|
|
4482
|
+
count: graph.sources.length,
|
|
4483
|
+
children: []
|
|
4484
|
+
};
|
|
4485
|
+
const nodes = [...graph.nodes];
|
|
4486
|
+
for (const source of [...graph.sources].sort(
|
|
4487
|
+
(left, right) => normalizeSourcePath(options.rootDir, left).localeCompare(normalizeSourcePath(options.rootDir, right))
|
|
4488
|
+
)) {
|
|
4489
|
+
const normalizedPath = normalizeSourcePath(options.rootDir, source);
|
|
4490
|
+
const segments = normalizedPath.split("/").filter(Boolean);
|
|
4491
|
+
const fileLabel = segments.pop() ?? source.title ?? source.sourceId;
|
|
4492
|
+
let parent = root;
|
|
4493
|
+
for (const segment of segments) {
|
|
4494
|
+
parent = ensureDirectory(parent, segment);
|
|
4495
|
+
}
|
|
4496
|
+
const children = nodeChildrenForSource(source.sourceId, nodes);
|
|
4497
|
+
parent.children.push({
|
|
4498
|
+
id: `tree:source:${source.sourceId}`,
|
|
4499
|
+
label: fileLabel,
|
|
4500
|
+
kind: "source",
|
|
4501
|
+
count: children.length,
|
|
4502
|
+
children,
|
|
4503
|
+
path: normalizedPath,
|
|
4504
|
+
sourceId: source.sourceId,
|
|
4505
|
+
language: source.language
|
|
4506
|
+
});
|
|
4507
|
+
}
|
|
4508
|
+
return sortAndCapTree(root, Math.max(1, options.maxChildren ?? DEFAULT_MAX_CHILDREN));
|
|
4509
|
+
}
|
|
4510
|
+
function renderNode(node) {
|
|
4511
|
+
const meta = [
|
|
4512
|
+
node.kind,
|
|
4513
|
+
node.language,
|
|
4514
|
+
node.symbolKind,
|
|
4515
|
+
node.path,
|
|
4516
|
+
node.nodeId,
|
|
4517
|
+
node.sourceId,
|
|
4518
|
+
node.count ? `${node.count} item${node.count === 1 ? "" : "s"}` : void 0
|
|
4519
|
+
].filter(Boolean);
|
|
4520
|
+
const content = [
|
|
4521
|
+
`<span class="label">${escapeHtml(node.label)}</span>`,
|
|
4522
|
+
meta.length ? `<span class="meta">${escapeHtml(meta.join(" \xB7 "))}</span>` : ""
|
|
4523
|
+
].join("");
|
|
4524
|
+
if (node.children.length === 0) {
|
|
4525
|
+
return `<li class="tree-node kind-${escapeHtml(node.kind)}">${content}</li>`;
|
|
4526
|
+
}
|
|
4527
|
+
return `<li class="tree-node kind-${escapeHtml(node.kind)}"><details open><summary>${content}</summary><ul>${node.children.map(renderNode).join("")}</ul></details></li>`;
|
|
4528
|
+
}
|
|
4529
|
+
function renderGraphTreeHtml(tree, graph) {
|
|
4530
|
+
return `<!doctype html>
|
|
4531
|
+
<html lang="en">
|
|
4532
|
+
<head>
|
|
4533
|
+
<meta charset="utf-8">
|
|
4534
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
4535
|
+
<title>${escapeHtml(tree.label)}</title>
|
|
4536
|
+
<style>
|
|
4537
|
+
:root { color-scheme: light dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
4538
|
+
body { margin: 0; background: #f7f7f5; color: #171717; }
|
|
4539
|
+
main { max-width: 1120px; margin: 0 auto; padding: 32px 20px 56px; }
|
|
4540
|
+
h1 { font-size: 28px; line-height: 1.2; margin: 0 0 8px; }
|
|
4541
|
+
.subtitle { color: #5d5d5d; margin: 0 0 20px; }
|
|
4542
|
+
.toolbar { display: flex; gap: 12px; align-items: center; margin: 0 0 18px; }
|
|
4543
|
+
input { flex: 1; min-width: 0; border: 1px solid #c9c9c9; border-radius: 6px; padding: 10px 12px; font: inherit; background: #fff; color: inherit; }
|
|
4544
|
+
.tree { background: #fff; border: 1px solid #deded9; border-radius: 8px; padding: 16px 18px; }
|
|
4545
|
+
ul { list-style: none; margin: 0; padding-left: 20px; }
|
|
4546
|
+
.tree > ul { padding-left: 0; }
|
|
4547
|
+
li { margin: 4px 0; }
|
|
4548
|
+
summary { cursor: pointer; }
|
|
4549
|
+
.label { font-weight: 600; }
|
|
4550
|
+
.meta { color: #666; font-size: 12px; margin-left: 8px; }
|
|
4551
|
+
.kind-directory > details > summary .label { color: #245b78; }
|
|
4552
|
+
.kind-source > details > summary .label, .kind-source > .label { color: #22543d; }
|
|
4553
|
+
.kind-module > details > summary .label { color: #6b3f12; }
|
|
4554
|
+
.kind-rationale > .label { color: #6d2f46; }
|
|
4555
|
+
.hidden { display: none !important; }
|
|
4556
|
+
@media (prefers-color-scheme: dark) {
|
|
4557
|
+
body { background: #161616; color: #efefef; }
|
|
4558
|
+
.subtitle, .meta { color: #ababab; }
|
|
4559
|
+
input, .tree { background: #202020; border-color: #3a3a3a; }
|
|
4560
|
+
}
|
|
4561
|
+
</style>
|
|
4562
|
+
</head>
|
|
4563
|
+
<body>
|
|
4564
|
+
<main>
|
|
4565
|
+
<h1>${escapeHtml(tree.label)}</h1>
|
|
4566
|
+
<p class="subtitle">${graph.sources.length} sources \xB7 ${graph.nodes.length} nodes \xB7 ${graph.edges.length} edges \xB7 generated ${escapeHtml(graph.generatedAt)}</p>
|
|
4567
|
+
<div class="toolbar"><input id="filter" type="search" placeholder="Filter files, modules, symbols, or ids" aria-label="Filter graph tree"></div>
|
|
4568
|
+
<section class="tree"><ul>${renderNode(tree)}</ul></section>
|
|
4569
|
+
</main>
|
|
4570
|
+
<script>
|
|
4571
|
+
const input = document.getElementById('filter');
|
|
4572
|
+
input.addEventListener('input', () => {
|
|
4573
|
+
const query = input.value.trim().toLowerCase();
|
|
4574
|
+
for (const node of document.querySelectorAll('.tree-node')) {
|
|
4575
|
+
const text = node.textContent.toLowerCase();
|
|
4576
|
+
node.classList.toggle('hidden', query.length > 0 && !text.includes(query));
|
|
4577
|
+
}
|
|
4578
|
+
});
|
|
4579
|
+
</script>
|
|
4580
|
+
</body>
|
|
4581
|
+
</html>
|
|
4582
|
+
`;
|
|
4583
|
+
}
|
|
4584
|
+
async function exportGraphTree(rootDir, outputPath, options = {}) {
|
|
4585
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
4586
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
4587
|
+
if (!graph) {
|
|
4588
|
+
throw new Error(`Graph artifact not found at ${paths.graphPath}. Run swarmvault compile first.`);
|
|
4589
|
+
}
|
|
4590
|
+
const tree = buildGraphTree(graph, { ...options, rootDir });
|
|
4591
|
+
const resolvedOutputPath = path8.resolve(rootDir, outputPath ?? path8.join(paths.wikiDir, "graph", "tree.html"));
|
|
4592
|
+
await ensureDir(path8.dirname(resolvedOutputPath));
|
|
4593
|
+
await import("fs/promises").then((fs13) => fs13.writeFile(resolvedOutputPath, renderGraphTreeHtml(tree, graph), "utf8"));
|
|
4594
|
+
return {
|
|
4595
|
+
outputPath: resolvedOutputPath,
|
|
4596
|
+
sourceCount: graph.sources.length,
|
|
4597
|
+
nodeCount: graph.nodes.length,
|
|
4598
|
+
tree
|
|
4599
|
+
};
|
|
4600
|
+
}
|
|
4601
|
+
|
|
3978
4602
|
// src/hooks.ts
|
|
3979
|
-
import
|
|
3980
|
-
import
|
|
4603
|
+
import fs7 from "fs/promises";
|
|
4604
|
+
import path9 from "path";
|
|
3981
4605
|
import process3 from "process";
|
|
3982
4606
|
var hookStart = "# >>> swarmvault hook >>>";
|
|
3983
4607
|
var hookEnd = "# <<< swarmvault hook <<<";
|
|
3984
4608
|
async function findNearestGitRoot(startPath) {
|
|
3985
|
-
let current =
|
|
4609
|
+
let current = path9.resolve(startPath);
|
|
3986
4610
|
try {
|
|
3987
|
-
const stat = await
|
|
4611
|
+
const stat = await fs7.stat(current);
|
|
3988
4612
|
if (!stat.isDirectory()) {
|
|
3989
|
-
current =
|
|
4613
|
+
current = path9.dirname(current);
|
|
3990
4614
|
}
|
|
3991
4615
|
} catch {
|
|
3992
|
-
current =
|
|
4616
|
+
current = path9.dirname(current);
|
|
3993
4617
|
}
|
|
3994
4618
|
while (true) {
|
|
3995
|
-
if (await fileExists(
|
|
4619
|
+
if (await fileExists(path9.join(current, ".git"))) {
|
|
3996
4620
|
return current;
|
|
3997
4621
|
}
|
|
3998
|
-
const parent =
|
|
4622
|
+
const parent = path9.dirname(current);
|
|
3999
4623
|
if (parent === current) {
|
|
4000
4624
|
return null;
|
|
4001
4625
|
}
|
|
@@ -4007,8 +4631,8 @@ function shellQuote(value) {
|
|
|
4007
4631
|
}
|
|
4008
4632
|
function resolveSwarmvaultExecutableCandidate() {
|
|
4009
4633
|
const argvPath = process3.argv[1];
|
|
4010
|
-
if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${
|
|
4011
|
-
return
|
|
4634
|
+
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}`))) {
|
|
4635
|
+
return path9.resolve(argvPath);
|
|
4012
4636
|
}
|
|
4013
4637
|
return "swarmvault";
|
|
4014
4638
|
}
|
|
@@ -4027,17 +4651,17 @@ function managedHookBlock(vaultRoot) {
|
|
|
4027
4651
|
].join("\n");
|
|
4028
4652
|
}
|
|
4029
4653
|
function hookPath(repoRoot, hookName) {
|
|
4030
|
-
return
|
|
4654
|
+
return path9.join(repoRoot, ".git", "hooks", hookName);
|
|
4031
4655
|
}
|
|
4032
4656
|
async function readHookStatus(filePath) {
|
|
4033
4657
|
if (!await fileExists(filePath)) {
|
|
4034
4658
|
return "not_installed";
|
|
4035
4659
|
}
|
|
4036
|
-
const content = await
|
|
4660
|
+
const content = await fs7.readFile(filePath, "utf8");
|
|
4037
4661
|
return content.includes(hookStart) && content.includes(hookEnd) ? "installed" : "other_content";
|
|
4038
4662
|
}
|
|
4039
4663
|
async function upsertHookFile(filePath, block) {
|
|
4040
|
-
const existing = await fileExists(filePath) ? await
|
|
4664
|
+
const existing = await fileExists(filePath) ? await fs7.readFile(filePath, "utf8") : "";
|
|
4041
4665
|
let next;
|
|
4042
4666
|
const startIndex = existing.indexOf(hookStart);
|
|
4043
4667
|
const endIndex = existing.indexOf(hookEnd);
|
|
@@ -4051,16 +4675,16 @@ ${block}`.trimEnd();
|
|
|
4051
4675
|
next = `#!/bin/sh
|
|
4052
4676
|
${block}`.trimEnd();
|
|
4053
4677
|
}
|
|
4054
|
-
await ensureDir(
|
|
4055
|
-
await
|
|
4678
|
+
await ensureDir(path9.dirname(filePath));
|
|
4679
|
+
await fs7.writeFile(filePath, `${next}
|
|
4056
4680
|
`, { mode: 493, encoding: "utf8" });
|
|
4057
|
-
await
|
|
4681
|
+
await fs7.chmod(filePath, 493);
|
|
4058
4682
|
}
|
|
4059
4683
|
async function removeHookBlock(filePath) {
|
|
4060
4684
|
if (!await fileExists(filePath)) {
|
|
4061
4685
|
return;
|
|
4062
4686
|
}
|
|
4063
|
-
const existing = await
|
|
4687
|
+
const existing = await fs7.readFile(filePath, "utf8");
|
|
4064
4688
|
const startIndex = existing.indexOf(hookStart);
|
|
4065
4689
|
const endIndex = existing.indexOf(hookEnd);
|
|
4066
4690
|
if (startIndex === -1 || endIndex === -1) {
|
|
@@ -4068,10 +4692,10 @@ async function removeHookBlock(filePath) {
|
|
|
4068
4692
|
}
|
|
4069
4693
|
const next = `${existing.slice(0, startIndex)}${existing.slice(endIndex + hookEnd.length)}`.trim();
|
|
4070
4694
|
if (!next || next === "#!/bin/sh") {
|
|
4071
|
-
await
|
|
4695
|
+
await fs7.rm(filePath, { force: true });
|
|
4072
4696
|
return;
|
|
4073
4697
|
}
|
|
4074
|
-
await
|
|
4698
|
+
await fs7.writeFile(filePath, `${next}
|
|
4075
4699
|
`, "utf8");
|
|
4076
4700
|
}
|
|
4077
4701
|
async function getGitHookStatus(rootDir) {
|
|
@@ -4094,7 +4718,7 @@ async function installGitHooks(rootDir) {
|
|
|
4094
4718
|
if (!repoRoot) {
|
|
4095
4719
|
throw new Error("No git repository found above the current vault.");
|
|
4096
4720
|
}
|
|
4097
|
-
const block = managedHookBlock(
|
|
4721
|
+
const block = managedHookBlock(path9.resolve(rootDir));
|
|
4098
4722
|
await upsertHookFile(hookPath(repoRoot, "post-commit"), block);
|
|
4099
4723
|
await upsertHookFile(hookPath(repoRoot, "post-checkout"), block);
|
|
4100
4724
|
return getGitHookStatus(rootDir);
|
|
@@ -4114,12 +4738,12 @@ async function uninstallGitHooks(rootDir) {
|
|
|
4114
4738
|
}
|
|
4115
4739
|
|
|
4116
4740
|
// src/mcp.ts
|
|
4117
|
-
import
|
|
4118
|
-
import
|
|
4741
|
+
import fs8 from "fs/promises";
|
|
4742
|
+
import path10 from "path";
|
|
4119
4743
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4120
4744
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4121
4745
|
import { z } from "zod";
|
|
4122
|
-
var SERVER_VERSION = "3.
|
|
4746
|
+
var SERVER_VERSION = "3.7.0";
|
|
4123
4747
|
async function createMcpServer(rootDir) {
|
|
4124
4748
|
const server = new McpServer({
|
|
4125
4749
|
name: "swarmvault",
|
|
@@ -4827,7 +5451,7 @@ async function createMcpServer(rootDir) {
|
|
|
4827
5451
|
},
|
|
4828
5452
|
async () => {
|
|
4829
5453
|
const { paths } = await loadVaultConfig(rootDir);
|
|
4830
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
5454
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path10.relative(paths.sessionsDir, filePath))).sort();
|
|
4831
5455
|
return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
|
|
4832
5456
|
}
|
|
4833
5457
|
);
|
|
@@ -4896,8 +5520,8 @@ async function createMcpServer(rootDir) {
|
|
|
4896
5520
|
return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
|
|
4897
5521
|
}
|
|
4898
5522
|
const { paths } = await loadVaultConfig(rootDir);
|
|
4899
|
-
const absolutePath =
|
|
4900
|
-
return asTextResource(`swarmvault://pages/${encodedPath}`, await
|
|
5523
|
+
const absolutePath = path10.resolve(paths.wikiDir, relativePath);
|
|
5524
|
+
return asTextResource(`swarmvault://pages/${encodedPath}`, await fs8.readFile(absolutePath, "utf8"));
|
|
4901
5525
|
}
|
|
4902
5526
|
);
|
|
4903
5527
|
server.registerResource(
|
|
@@ -4905,11 +5529,11 @@ async function createMcpServer(rootDir) {
|
|
|
4905
5529
|
new ResourceTemplate("swarmvault://sessions/{path}", {
|
|
4906
5530
|
list: async () => {
|
|
4907
5531
|
const { paths } = await loadVaultConfig(rootDir);
|
|
4908
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
5532
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path10.relative(paths.sessionsDir, filePath))).sort();
|
|
4909
5533
|
return {
|
|
4910
5534
|
resources: files.map((relativePath) => ({
|
|
4911
5535
|
uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
|
|
4912
|
-
name:
|
|
5536
|
+
name: path10.basename(relativePath, ".md"),
|
|
4913
5537
|
title: relativePath,
|
|
4914
5538
|
description: "SwarmVault session artifact",
|
|
4915
5539
|
mimeType: "text/markdown"
|
|
@@ -4926,11 +5550,11 @@ async function createMcpServer(rootDir) {
|
|
|
4926
5550
|
const { paths } = await loadVaultConfig(rootDir);
|
|
4927
5551
|
const encodedPath = typeof variables.path === "string" ? variables.path : "";
|
|
4928
5552
|
const relativePath = decodeURIComponent(encodedPath);
|
|
4929
|
-
const absolutePath =
|
|
5553
|
+
const absolutePath = path10.resolve(paths.sessionsDir, relativePath);
|
|
4930
5554
|
if (!isPathWithin(paths.sessionsDir, absolutePath) || !await fileExists(absolutePath)) {
|
|
4931
5555
|
return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
|
|
4932
5556
|
}
|
|
4933
|
-
return asTextResource(`swarmvault://sessions/${encodedPath}`, await
|
|
5557
|
+
return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs8.readFile(absolutePath, "utf8"));
|
|
4934
5558
|
}
|
|
4935
5559
|
);
|
|
4936
5560
|
return server;
|
|
@@ -4990,9 +5614,9 @@ function asTextResource(uri, text) {
|
|
|
4990
5614
|
|
|
4991
5615
|
// src/providers/local-whisper-setup.ts
|
|
4992
5616
|
import { createWriteStream, constants as fsConstants } from "fs";
|
|
4993
|
-
import
|
|
5617
|
+
import fs9 from "fs/promises";
|
|
4994
5618
|
import os from "os";
|
|
4995
|
-
import
|
|
5619
|
+
import path11 from "path";
|
|
4996
5620
|
import { Readable } from "stream";
|
|
4997
5621
|
import { pipeline } from "stream/promises";
|
|
4998
5622
|
var BINARY_CANDIDATES = ["whisper-cli", "whisper-cpp", "whisper"];
|
|
@@ -5020,10 +5644,10 @@ async function discoverLocalWhisperBinary(options = {}) {
|
|
|
5020
5644
|
}
|
|
5021
5645
|
const pathValue = env.PATH ?? "";
|
|
5022
5646
|
const candidates = [];
|
|
5023
|
-
for (const dir of pathValue.split(
|
|
5647
|
+
for (const dir of pathValue.split(path11.delimiter)) {
|
|
5024
5648
|
if (!dir) continue;
|
|
5025
5649
|
for (const name of BINARY_CANDIDATES) {
|
|
5026
|
-
const full =
|
|
5650
|
+
const full = path11.join(dir, name);
|
|
5027
5651
|
candidates.push(full);
|
|
5028
5652
|
if (await isExecutable(full)) {
|
|
5029
5653
|
return { binaryPath: full, candidates, source: "path" };
|
|
@@ -5034,14 +5658,14 @@ async function discoverLocalWhisperBinary(options = {}) {
|
|
|
5034
5658
|
}
|
|
5035
5659
|
function expectedModelPath(modelName, homeDir) {
|
|
5036
5660
|
const home = homeDir ?? os.homedir();
|
|
5037
|
-
return
|
|
5661
|
+
return path11.join(home, ".swarmvault", "models", `ggml-${modelName}.bin`);
|
|
5038
5662
|
}
|
|
5039
5663
|
function modelDownloadUrl(modelName) {
|
|
5040
5664
|
return `${HUGGINGFACE_BASE}/ggml-${modelName}.bin`;
|
|
5041
5665
|
}
|
|
5042
5666
|
async function downloadWhisperModel(options) {
|
|
5043
5667
|
const destPath = expectedModelPath(options.modelName, options.homeDir);
|
|
5044
|
-
await ensureDir(
|
|
5668
|
+
await ensureDir(path11.dirname(destPath));
|
|
5045
5669
|
const doFetch = options.fetchImpl ?? fetch;
|
|
5046
5670
|
const url = modelDownloadUrl(options.modelName);
|
|
5047
5671
|
const response = await doFetch(url);
|
|
@@ -5062,8 +5686,8 @@ async function downloadWhisperModel(options) {
|
|
|
5062
5686
|
});
|
|
5063
5687
|
const tmpPath = `${destPath}.part`;
|
|
5064
5688
|
await pipeline(source, createWriteStream(tmpPath));
|
|
5065
|
-
await
|
|
5066
|
-
const stat = await
|
|
5689
|
+
await fs9.rename(tmpPath, destPath);
|
|
5690
|
+
const stat = await fs9.stat(destPath);
|
|
5067
5691
|
return { path: destPath, bytes: stat.size };
|
|
5068
5692
|
}
|
|
5069
5693
|
async function registerLocalWhisperProvider(options) {
|
|
@@ -5130,7 +5754,7 @@ async function summarizeLocalWhisperSetup(options) {
|
|
|
5130
5754
|
}
|
|
5131
5755
|
async function isExecutable(p) {
|
|
5132
5756
|
try {
|
|
5133
|
-
await
|
|
5757
|
+
await fs9.access(p, fsConstants.X_OK);
|
|
5134
5758
|
return true;
|
|
5135
5759
|
} catch {
|
|
5136
5760
|
return false;
|
|
@@ -5200,13 +5824,13 @@ async function withCapabilityFallback(provider, capability, run, fallback) {
|
|
|
5200
5824
|
}
|
|
5201
5825
|
|
|
5202
5826
|
// src/schedule.ts
|
|
5203
|
-
import
|
|
5204
|
-
import
|
|
5827
|
+
import fs10 from "fs/promises";
|
|
5828
|
+
import path12 from "path";
|
|
5205
5829
|
function scheduleStatePath(schedulesDir, jobId) {
|
|
5206
|
-
return
|
|
5830
|
+
return path12.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
5207
5831
|
}
|
|
5208
5832
|
function scheduleLockPath(schedulesDir, jobId) {
|
|
5209
|
-
return
|
|
5833
|
+
return path12.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
|
|
5210
5834
|
}
|
|
5211
5835
|
function parseEveryDuration(value) {
|
|
5212
5836
|
const match = value.trim().match(/^(\d+)(m|h|d)$/i);
|
|
@@ -5309,13 +5933,13 @@ async function acquireJobLease(rootDir, jobId) {
|
|
|
5309
5933
|
const { paths } = await loadVaultConfig(rootDir);
|
|
5310
5934
|
const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
|
|
5311
5935
|
await ensureDir(paths.schedulesDir);
|
|
5312
|
-
const handle = await
|
|
5936
|
+
const handle = await fs10.open(leasePath, "wx");
|
|
5313
5937
|
await handle.writeFile(`${process.pid}
|
|
5314
5938
|
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5315
5939
|
`);
|
|
5316
5940
|
await handle.close();
|
|
5317
5941
|
return async () => {
|
|
5318
|
-
await
|
|
5942
|
+
await fs10.rm(leasePath, { force: true });
|
|
5319
5943
|
};
|
|
5320
5944
|
}
|
|
5321
5945
|
async function listSchedules(rootDir) {
|
|
@@ -5474,8 +6098,8 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
|
|
|
5474
6098
|
|
|
5475
6099
|
// src/sources.ts
|
|
5476
6100
|
import { spawn } from "child_process";
|
|
5477
|
-
import
|
|
5478
|
-
import
|
|
6101
|
+
import fs11 from "fs/promises";
|
|
6102
|
+
import path13 from "path";
|
|
5479
6103
|
import matter3 from "gray-matter";
|
|
5480
6104
|
import { JSDOM } from "jsdom";
|
|
5481
6105
|
var DEFAULT_CRAWL_MAX_PAGES = 12;
|
|
@@ -5521,24 +6145,24 @@ function emptyManagedSourceSyncCounts() {
|
|
|
5521
6145
|
};
|
|
5522
6146
|
}
|
|
5523
6147
|
function withinRoot(rootPath, targetPath) {
|
|
5524
|
-
const relative =
|
|
5525
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
6148
|
+
const relative = path13.relative(rootPath, targetPath);
|
|
6149
|
+
return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
|
|
5526
6150
|
}
|
|
5527
6151
|
async function findNearestGitRoot2(startPath) {
|
|
5528
|
-
let current =
|
|
6152
|
+
let current = path13.resolve(startPath);
|
|
5529
6153
|
try {
|
|
5530
|
-
const stat = await
|
|
6154
|
+
const stat = await fs11.stat(current);
|
|
5531
6155
|
if (!stat.isDirectory()) {
|
|
5532
|
-
current =
|
|
6156
|
+
current = path13.dirname(current);
|
|
5533
6157
|
}
|
|
5534
6158
|
} catch {
|
|
5535
|
-
current =
|
|
6159
|
+
current = path13.dirname(current);
|
|
5536
6160
|
}
|
|
5537
6161
|
while (true) {
|
|
5538
|
-
if (await fileExists(
|
|
6162
|
+
if (await fileExists(path13.join(current, ".git"))) {
|
|
5539
6163
|
return current;
|
|
5540
6164
|
}
|
|
5541
|
-
const parent =
|
|
6165
|
+
const parent = path13.dirname(current);
|
|
5542
6166
|
if (parent === current) {
|
|
5543
6167
|
return null;
|
|
5544
6168
|
}
|
|
@@ -5612,7 +6236,7 @@ function isAllowedDocsCandidate(candidate, startUrl) {
|
|
|
5612
6236
|
if (candidate.origin !== startUrl.origin) {
|
|
5613
6237
|
return false;
|
|
5614
6238
|
}
|
|
5615
|
-
const extension =
|
|
6239
|
+
const extension = path13.extname(candidate.pathname).toLowerCase();
|
|
5616
6240
|
if (extension && extension !== ".html" && extension !== ".htm" && extension !== ".md") {
|
|
5617
6241
|
return false;
|
|
5618
6242
|
}
|
|
@@ -5701,14 +6325,40 @@ function matchesManagedSourceSpec(existing, input) {
|
|
|
5701
6325
|
return false;
|
|
5702
6326
|
}
|
|
5703
6327
|
if (input.kind === "directory" || input.kind === "file") {
|
|
5704
|
-
return
|
|
6328
|
+
return path13.resolve(existing.path ?? "") === path13.resolve(input.path);
|
|
6329
|
+
}
|
|
6330
|
+
if (input.kind === "github_repo") {
|
|
6331
|
+
return (existing.url ?? "") === input.url && (existing.branch ?? "") === (input.branch ?? "") && (existing.ref ?? "") === (input.ref ?? "");
|
|
5705
6332
|
}
|
|
5706
6333
|
return (existing.url ?? "") === input.url;
|
|
5707
6334
|
}
|
|
5708
|
-
|
|
5709
|
-
const
|
|
6335
|
+
function normalizeGitSelector(value, label) {
|
|
6336
|
+
const trimmed = value?.trim();
|
|
6337
|
+
if (!trimmed) {
|
|
6338
|
+
return void 0;
|
|
6339
|
+
}
|
|
6340
|
+
if (trimmed.startsWith("-")) {
|
|
6341
|
+
throw new Error(`Git ${label} must not start with "-".`);
|
|
6342
|
+
}
|
|
6343
|
+
if (/[\s\0]/.test(trimmed)) {
|
|
6344
|
+
throw new Error(`Git ${label} must not contain whitespace or NUL bytes.`);
|
|
6345
|
+
}
|
|
6346
|
+
return trimmed;
|
|
6347
|
+
}
|
|
6348
|
+
function normalizeCheckoutDir(rootDir, value) {
|
|
6349
|
+
const trimmed = value?.trim();
|
|
6350
|
+
if (!trimmed) {
|
|
6351
|
+
return void 0;
|
|
6352
|
+
}
|
|
6353
|
+
return path13.isAbsolute(trimmed) ? path13.resolve(trimmed) : path13.resolve(rootDir, trimmed);
|
|
6354
|
+
}
|
|
6355
|
+
async function resolveManagedSourceInput(rootDir, input, options = {}) {
|
|
6356
|
+
const absoluteInput = path13.resolve(rootDir, input);
|
|
5710
6357
|
if (!(input.startsWith("http://") || input.startsWith("https://"))) {
|
|
5711
|
-
|
|
6358
|
+
if (options.branch || options.ref || options.checkoutDir) {
|
|
6359
|
+
throw new Error("Git branch/ref/checkout options are only supported for public GitHub repo root URLs.");
|
|
6360
|
+
}
|
|
6361
|
+
const stat = await fs11.stat(absoluteInput).catch(() => null);
|
|
5712
6362
|
if (!stat) {
|
|
5713
6363
|
throw new Error(`Source not found: ${input}`);
|
|
5714
6364
|
}
|
|
@@ -5716,7 +6366,7 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
5716
6366
|
return {
|
|
5717
6367
|
kind: "file",
|
|
5718
6368
|
path: absoluteInput,
|
|
5719
|
-
title:
|
|
6369
|
+
title: path13.basename(absoluteInput, path13.extname(absoluteInput)) || absoluteInput
|
|
5720
6370
|
};
|
|
5721
6371
|
}
|
|
5722
6372
|
if (!stat.isDirectory()) {
|
|
@@ -5728,16 +6378,22 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
5728
6378
|
kind: "directory",
|
|
5729
6379
|
path: absoluteInput,
|
|
5730
6380
|
repoRoot,
|
|
5731
|
-
title:
|
|
6381
|
+
title: path13.basename(absoluteInput) || absoluteInput
|
|
5732
6382
|
};
|
|
5733
6383
|
}
|
|
5734
6384
|
const github = normalizeGitHubRepoRootUrl(input);
|
|
5735
6385
|
if (github) {
|
|
5736
6386
|
return {
|
|
5737
6387
|
kind: "github_repo",
|
|
5738
|
-
...github
|
|
6388
|
+
...github,
|
|
6389
|
+
branch: normalizeGitSelector(options.branch, "branch"),
|
|
6390
|
+
ref: normalizeGitSelector(options.ref, "ref"),
|
|
6391
|
+
checkoutDir: normalizeCheckoutDir(rootDir, options.checkoutDir)
|
|
5739
6392
|
};
|
|
5740
6393
|
}
|
|
6394
|
+
if (options.branch || options.ref || options.checkoutDir) {
|
|
6395
|
+
throw new Error("Git branch/ref/checkout options are only supported for public GitHub repo root URLs.");
|
|
6396
|
+
}
|
|
5741
6397
|
const parsed = new URL(input);
|
|
5742
6398
|
if (parsed.hostname.toLowerCase().includes("github.com")) {
|
|
5743
6399
|
throw new Error(
|
|
@@ -5751,16 +6407,16 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
5751
6407
|
};
|
|
5752
6408
|
}
|
|
5753
6409
|
function directorySourceIdsFor(manifests, inputPath) {
|
|
5754
|
-
return manifests.filter((manifest) => manifest.originalPath && withinRoot(
|
|
6410
|
+
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
6411
|
}
|
|
5756
6412
|
function fileSourceIdsFor(manifests, inputPath) {
|
|
5757
|
-
const absoluteInput =
|
|
5758
|
-
return manifests.filter((manifest) => manifest.originalPath &&
|
|
6413
|
+
const absoluteInput = path13.resolve(inputPath);
|
|
6414
|
+
return manifests.filter((manifest) => manifest.originalPath && path13.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
|
|
5759
6415
|
}
|
|
5760
6416
|
async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
5761
6417
|
const manifestsBefore = await listManifests(rootDir);
|
|
5762
6418
|
const previousInScope = manifestsBefore.filter(
|
|
5763
|
-
(manifest) => manifest.originalPath && withinRoot(
|
|
6419
|
+
(manifest) => manifest.originalPath && withinRoot(path13.resolve(inputPath), path13.resolve(manifest.originalPath))
|
|
5764
6420
|
);
|
|
5765
6421
|
const result = await ingestDirectory(rootDir, inputPath, { repoRoot });
|
|
5766
6422
|
const removed = [];
|
|
@@ -5768,7 +6424,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
|
5768
6424
|
if (!manifest.originalPath) {
|
|
5769
6425
|
continue;
|
|
5770
6426
|
}
|
|
5771
|
-
if (await fileExists(
|
|
6427
|
+
if (await fileExists(path13.resolve(manifest.originalPath))) {
|
|
5772
6428
|
continue;
|
|
5773
6429
|
}
|
|
5774
6430
|
const removedManifest = await removeManifestBySourceId(rootDir, manifest.sourceId);
|
|
@@ -5778,7 +6434,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
|
5778
6434
|
}
|
|
5779
6435
|
const manifestsAfter = await listManifests(rootDir);
|
|
5780
6436
|
return {
|
|
5781
|
-
title:
|
|
6437
|
+
title: path13.basename(inputPath) || inputPath,
|
|
5782
6438
|
sourceIds: directorySourceIdsFor(manifestsAfter, inputPath),
|
|
5783
6439
|
counts: {
|
|
5784
6440
|
scannedCount: result.scannedCount,
|
|
@@ -5794,7 +6450,7 @@ async function syncFileSource(rootDir, inputPath) {
|
|
|
5794
6450
|
const result = await ingestInputDetailed(rootDir, inputPath);
|
|
5795
6451
|
const manifestsAfter = await listManifests(rootDir);
|
|
5796
6452
|
return {
|
|
5797
|
-
title:
|
|
6453
|
+
title: path13.basename(inputPath, path13.extname(inputPath)) || inputPath,
|
|
5798
6454
|
sourceIds: fileSourceIdsFor(manifestsAfter, inputPath),
|
|
5799
6455
|
counts: {
|
|
5800
6456
|
scannedCount: result.scannedCount,
|
|
@@ -5828,8 +6484,11 @@ async function runGitCommand(cwd, args) {
|
|
|
5828
6484
|
}
|
|
5829
6485
|
async function syncGitHubRepoSource(rootDir, entry) {
|
|
5830
6486
|
const workingDir = await managedSourceWorkingDir(rootDir, entry.id);
|
|
5831
|
-
const
|
|
5832
|
-
|
|
6487
|
+
const externalCheckoutDir = entry.checkoutDir ? path13.resolve(entry.checkoutDir) : void 0;
|
|
6488
|
+
const checkoutDir = externalCheckoutDir ?? path13.join(workingDir, "checkout");
|
|
6489
|
+
if (!externalCheckoutDir) {
|
|
6490
|
+
await fs11.rm(checkoutDir, { recursive: true, force: true });
|
|
6491
|
+
}
|
|
5833
6492
|
await ensureDir(workingDir);
|
|
5834
6493
|
if (!entry.url) {
|
|
5835
6494
|
throw new Error(`Managed source ${entry.id} is missing its repository URL.`);
|
|
@@ -5838,7 +6497,35 @@ async function syncGitHubRepoSource(rootDir, entry) {
|
|
|
5838
6497
|
if (!github) {
|
|
5839
6498
|
throw new Error(`Managed source ${entry.id} has an invalid GitHub repo URL.`);
|
|
5840
6499
|
}
|
|
5841
|
-
|
|
6500
|
+
const branch = normalizeGitSelector(entry.branch, "branch");
|
|
6501
|
+
const ref = normalizeGitSelector(entry.ref, "ref");
|
|
6502
|
+
const cloneArgs = ["clone", "--depth", "1"];
|
|
6503
|
+
if (branch) {
|
|
6504
|
+
cloneArgs.push("--branch", branch);
|
|
6505
|
+
}
|
|
6506
|
+
cloneArgs.push(github.cloneUrl, checkoutDir);
|
|
6507
|
+
if (await fileExists(path13.join(checkoutDir, ".git"))) {
|
|
6508
|
+
await runGitCommand(checkoutDir, ["remote", "set-url", "origin", github.cloneUrl]);
|
|
6509
|
+
if (branch) {
|
|
6510
|
+
await runGitCommand(checkoutDir, ["fetch", "--depth", "1", "origin", branch]);
|
|
6511
|
+
} else {
|
|
6512
|
+
await runGitCommand(checkoutDir, ["fetch", "--depth", "1", "origin"]);
|
|
6513
|
+
}
|
|
6514
|
+
if (!ref) {
|
|
6515
|
+
await runGitCommand(checkoutDir, ["checkout", "--detach", "FETCH_HEAD"]);
|
|
6516
|
+
}
|
|
6517
|
+
} else {
|
|
6518
|
+
const existingEntries = await fs11.readdir(checkoutDir).catch(() => []);
|
|
6519
|
+
if (externalCheckoutDir && existingEntries.length > 0) {
|
|
6520
|
+
throw new Error(`Checkout directory exists but is not a Git repository: ${checkoutDir}`);
|
|
6521
|
+
}
|
|
6522
|
+
await ensureDir(path13.dirname(checkoutDir));
|
|
6523
|
+
await runGitCommand(workingDir, cloneArgs);
|
|
6524
|
+
}
|
|
6525
|
+
if (ref) {
|
|
6526
|
+
await runGitCommand(checkoutDir, ["fetch", "--depth", "1", "origin", ref]);
|
|
6527
|
+
await runGitCommand(checkoutDir, ["checkout", "--detach", "FETCH_HEAD"]);
|
|
6528
|
+
}
|
|
5842
6529
|
return await syncDirectorySource(rootDir, checkoutDir, checkoutDir);
|
|
5843
6530
|
}
|
|
5844
6531
|
async function syncCrawlSource(rootDir, entry, options) {
|
|
@@ -5959,7 +6646,7 @@ function scopedNodeIds(graph, sourceIds) {
|
|
|
5959
6646
|
async function loadSourceAnalyses(rootDir, sourceIds) {
|
|
5960
6647
|
const { paths } = await loadVaultConfig(rootDir);
|
|
5961
6648
|
const analyses = await Promise.all(
|
|
5962
|
-
sourceIds.map(async (sourceId) => await readJsonFile(
|
|
6649
|
+
sourceIds.map(async (sourceId) => await readJsonFile(path13.join(paths.analysesDir, `${sourceId}.json`)))
|
|
5963
6650
|
);
|
|
5964
6651
|
return analyses.filter((analysis) => Boolean(analysis?.sourceId));
|
|
5965
6652
|
}
|
|
@@ -6120,9 +6807,9 @@ async function writeSourceBriefForScope(rootDir, source) {
|
|
|
6120
6807
|
confidence: 0.82
|
|
6121
6808
|
}
|
|
6122
6809
|
});
|
|
6123
|
-
const absolutePath =
|
|
6124
|
-
await ensureDir(
|
|
6125
|
-
await
|
|
6810
|
+
const absolutePath = path13.join(paths.wikiDir, output.page.path);
|
|
6811
|
+
await ensureDir(path13.dirname(absolutePath));
|
|
6812
|
+
await fs11.writeFile(absolutePath, output.content, "utf8");
|
|
6126
6813
|
return absolutePath;
|
|
6127
6814
|
}
|
|
6128
6815
|
async function writeSourceBrief(rootDir, source) {
|
|
@@ -6410,7 +7097,7 @@ function selectGuidedTargetPages(scope, sourcePages, questions) {
|
|
|
6410
7097
|
return (matchedTargets.length ? matchedTargets : canonicalPages).slice(0, 6);
|
|
6411
7098
|
}
|
|
6412
7099
|
function insightRelativePathForTarget(page, scope) {
|
|
6413
|
-
const basename =
|
|
7100
|
+
const basename = path13.basename(page.path);
|
|
6414
7101
|
if (page.kind === "concept") {
|
|
6415
7102
|
return `insights/concepts/${basename}`;
|
|
6416
7103
|
}
|
|
@@ -6637,7 +7324,7 @@ async function stageSourceReviewForScope(rootDir, scope) {
|
|
|
6637
7324
|
return {
|
|
6638
7325
|
sourceId: scope.id,
|
|
6639
7326
|
pageId: output.page.id,
|
|
6640
|
-
reviewPath:
|
|
7327
|
+
reviewPath: path13.join(approval.approvalDir, "wiki", output.page.path),
|
|
6641
7328
|
staged: true,
|
|
6642
7329
|
approvalId: approval.approvalId,
|
|
6643
7330
|
approvalDir: approval.approvalDir
|
|
@@ -6704,7 +7391,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
|
|
|
6704
7391
|
const evidenceState = contradictions.length > 0 ? "conflicting" : session.targetedPagePaths.some(
|
|
6705
7392
|
(targetPath) => sourcePages.some((page) => page.path === targetPath && page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId)))
|
|
6706
7393
|
) ? "reinforcing" : session.targetedPagePaths.length ? "new" : "needs_judgment";
|
|
6707
|
-
const relativeBriefPath = session.briefPath &&
|
|
7394
|
+
const relativeBriefPath = session.briefPath && path13.isAbsolute(session.briefPath) ? path13.relative(paths.wikiDir, session.briefPath) : session.briefPath;
|
|
6708
7395
|
const sessionMarkdown = [
|
|
6709
7396
|
`# Guided Session: ${scope.title}`,
|
|
6710
7397
|
"",
|
|
@@ -6787,9 +7474,9 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
|
|
|
6787
7474
|
async function persistSourceSessionPage(rootDir, scope, session) {
|
|
6788
7475
|
const { paths } = await loadVaultConfig(rootDir);
|
|
6789
7476
|
const output = await buildSourceSessionSavedPage(rootDir, scope, session);
|
|
6790
|
-
const absolutePath =
|
|
6791
|
-
await ensureDir(
|
|
6792
|
-
await
|
|
7477
|
+
const absolutePath = path13.join(paths.wikiDir, output.page.path);
|
|
7478
|
+
await ensureDir(path13.dirname(absolutePath));
|
|
7479
|
+
await fs11.writeFile(absolutePath, output.content, "utf8");
|
|
6793
7480
|
return { pageId: output.page.id, sessionPath: absolutePath };
|
|
6794
7481
|
}
|
|
6795
7482
|
async function buildGuidedUpdatePages(rootDir, scope, session) {
|
|
@@ -6817,8 +7504,8 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
|
|
|
6817
7504
|
targetPages.map(async (targetPage) => {
|
|
6818
7505
|
const evidenceState = classifyGuidedEvidenceState(scope, targetPage, contradictions);
|
|
6819
7506
|
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
|
|
7507
|
+
const absolutePath = path13.join(paths.wikiDir, relativePath);
|
|
7508
|
+
const existingContent = await fileExists(absolutePath) ? await fs11.readFile(absolutePath, "utf8") : "";
|
|
6822
7509
|
const parsed = existingContent ? matter3(existingContent) : { data: {}, content: "" };
|
|
6823
7510
|
const existingData = parsed.data;
|
|
6824
7511
|
const existingSourceIds = Array.isArray(existingData.source_ids) ? existingData.source_ids.filter((value) => typeof value === "string") : [];
|
|
@@ -6989,8 +7676,8 @@ async function stageSourceGuideForScope(rootDir, scope, options = {}) {
|
|
|
6989
7676
|
}
|
|
6990
7677
|
);
|
|
6991
7678
|
session.status = "staged";
|
|
6992
|
-
session.reviewPath =
|
|
6993
|
-
session.guidePath =
|
|
7679
|
+
session.reviewPath = path13.join(approval.approvalDir, "wiki", reviewOutput.page.path);
|
|
7680
|
+
session.guidePath = path13.join(approval.approvalDir, "wiki", guideOutput.page.path);
|
|
6994
7681
|
session.approvalId = approval.approvalId;
|
|
6995
7682
|
session.approvalDir = approval.approvalDir;
|
|
6996
7683
|
const persisted = await persistSourceSessionPage(rootDir, scope, session);
|
|
@@ -7124,16 +7811,28 @@ async function addManagedSource(rootDir, input, options = {}) {
|
|
|
7124
7811
|
const briefRequested = guideRequested ? true : options.brief ?? true;
|
|
7125
7812
|
const reviewRequested = guideRequested ? false : options.review ?? false;
|
|
7126
7813
|
const sources = await loadManagedSources(rootDir);
|
|
7127
|
-
const resolved = await resolveManagedSourceInput(rootDir, input);
|
|
7814
|
+
const resolved = await resolveManagedSourceInput(rootDir, input, options);
|
|
7128
7815
|
const existing = sources.find((candidate) => matchesManagedSourceSpec(candidate, resolved));
|
|
7129
7816
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7130
|
-
const source = existing
|
|
7131
|
-
|
|
7817
|
+
const source = existing ? {
|
|
7818
|
+
...existing,
|
|
7819
|
+
branch: resolved.kind === "github_repo" ? resolved.branch : existing.branch,
|
|
7820
|
+
ref: resolved.kind === "github_repo" ? resolved.ref : existing.ref,
|
|
7821
|
+
checkoutDir: resolved.kind === "github_repo" ? resolved.checkoutDir ?? existing.checkoutDir : existing.checkoutDir
|
|
7822
|
+
} : {
|
|
7823
|
+
id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path13.resolve(resolved.path), resolved.title) : stableManagedSourceId(
|
|
7824
|
+
resolved.kind,
|
|
7825
|
+
resolved.kind === "github_repo" ? `${resolved.url}#branch=${resolved.branch ?? ""}#ref=${resolved.ref ?? ""}` : resolved.url,
|
|
7826
|
+
resolved.title
|
|
7827
|
+
),
|
|
7132
7828
|
kind: resolved.kind,
|
|
7133
7829
|
title: resolved.title,
|
|
7134
7830
|
path: resolved.kind === "directory" || resolved.kind === "file" ? resolved.path : void 0,
|
|
7135
7831
|
repoRoot: resolved.kind === "directory" ? resolved.repoRoot : void 0,
|
|
7136
7832
|
url: resolved.kind === "directory" || resolved.kind === "file" ? void 0 : resolved.url,
|
|
7833
|
+
branch: resolved.kind === "github_repo" ? resolved.branch : void 0,
|
|
7834
|
+
ref: resolved.kind === "github_repo" ? resolved.ref : void 0,
|
|
7835
|
+
checkoutDir: resolved.kind === "github_repo" ? resolved.checkoutDir : void 0,
|
|
7137
7836
|
createdAt: now,
|
|
7138
7837
|
updatedAt: now,
|
|
7139
7838
|
status: "ready",
|
|
@@ -7256,7 +7955,7 @@ async function deleteManagedSource(rootDir, id) {
|
|
|
7256
7955
|
sources.filter((source) => source.id !== id)
|
|
7257
7956
|
);
|
|
7258
7957
|
const workingDir = await managedSourceWorkingDir(rootDir, id);
|
|
7259
|
-
await
|
|
7958
|
+
await fs11.rm(workingDir, { recursive: true, force: true });
|
|
7260
7959
|
return { removed: target };
|
|
7261
7960
|
}
|
|
7262
7961
|
|
|
@@ -7264,9 +7963,9 @@ async function deleteManagedSource(rootDir, id) {
|
|
|
7264
7963
|
import { execFile as execFile2 } from "child_process";
|
|
7265
7964
|
import { randomUUID } from "crypto";
|
|
7266
7965
|
import { EventEmitter } from "events";
|
|
7267
|
-
import
|
|
7966
|
+
import fs12 from "fs/promises";
|
|
7268
7967
|
import http from "http";
|
|
7269
|
-
import
|
|
7968
|
+
import path14 from "path";
|
|
7270
7969
|
import { promisify as promisify2 } from "util";
|
|
7271
7970
|
import matter4 from "gray-matter";
|
|
7272
7971
|
import mime from "mime-types";
|
|
@@ -7420,7 +8119,7 @@ function toViewerLintFindings(findings) {
|
|
|
7420
8119
|
var execFileAsync2 = promisify2(execFile2);
|
|
7421
8120
|
async function isReadableFile(absolutePath) {
|
|
7422
8121
|
try {
|
|
7423
|
-
const stats = await
|
|
8122
|
+
const stats = await fs12.stat(absolutePath);
|
|
7424
8123
|
return stats.isFile();
|
|
7425
8124
|
} catch {
|
|
7426
8125
|
return false;
|
|
@@ -7431,15 +8130,15 @@ async function readViewerPage(rootDir, relativePath) {
|
|
|
7431
8130
|
return null;
|
|
7432
8131
|
}
|
|
7433
8132
|
const { paths } = await loadVaultConfig(rootDir);
|
|
7434
|
-
const absolutePath =
|
|
8133
|
+
const absolutePath = path14.resolve(paths.wikiDir, relativePath);
|
|
7435
8134
|
if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
|
|
7436
8135
|
return null;
|
|
7437
8136
|
}
|
|
7438
|
-
const raw = await
|
|
8137
|
+
const raw = await fs12.readFile(absolutePath, "utf8");
|
|
7439
8138
|
const parsed = matter4(raw);
|
|
7440
8139
|
return {
|
|
7441
8140
|
path: relativePath,
|
|
7442
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
8141
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path14.basename(relativePath, path14.extname(relativePath)),
|
|
7443
8142
|
frontmatter: parsed.data,
|
|
7444
8143
|
content: parsed.content,
|
|
7445
8144
|
assets: normalizeOutputAssets(parsed.data.output_assets)
|
|
@@ -7450,12 +8149,12 @@ async function readViewerAsset(rootDir, relativePath) {
|
|
|
7450
8149
|
return null;
|
|
7451
8150
|
}
|
|
7452
8151
|
const { paths } = await loadVaultConfig(rootDir);
|
|
7453
|
-
const absolutePath =
|
|
8152
|
+
const absolutePath = path14.resolve(paths.wikiDir, relativePath);
|
|
7454
8153
|
if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
|
|
7455
8154
|
return null;
|
|
7456
8155
|
}
|
|
7457
8156
|
return {
|
|
7458
|
-
buffer: await
|
|
8157
|
+
buffer: await fs12.readFile(absolutePath),
|
|
7459
8158
|
mimeType: mime.lookup(absolutePath) || "application/octet-stream"
|
|
7460
8159
|
};
|
|
7461
8160
|
}
|
|
@@ -7491,8 +8190,8 @@ async function writeInboxClip(rootDir, body) {
|
|
|
7491
8190
|
const tags = Array.isArray(body.tags) ? body.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : [];
|
|
7492
8191
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7493
8192
|
const fileName = `${now.replace(/[:.]/g, "-")}-${slugForClip(title)}.md`;
|
|
7494
|
-
const inboxPath =
|
|
7495
|
-
await
|
|
8193
|
+
const inboxPath = path14.join(paths.inboxDir, fileName);
|
|
8194
|
+
await fs12.mkdir(paths.inboxDir, { recursive: true });
|
|
7496
8195
|
const lines = [
|
|
7497
8196
|
"---",
|
|
7498
8197
|
`title: ${JSON.stringify(title)}`,
|
|
@@ -7509,17 +8208,17 @@ async function writeInboxClip(rootDir, body) {
|
|
|
7509
8208
|
selectionHtml && !markdown ? ["", "## Original HTML", "", "```html", selectionHtml, "```"].join("\n") : void 0,
|
|
7510
8209
|
""
|
|
7511
8210
|
].filter((line) => line !== void 0);
|
|
7512
|
-
await
|
|
8211
|
+
await fs12.writeFile(inboxPath, lines.join("\n"), "utf8");
|
|
7513
8212
|
const result = await importInbox(rootDir, paths.inboxDir);
|
|
7514
8213
|
return { mode: "inbox", inboxPath, result };
|
|
7515
8214
|
}
|
|
7516
8215
|
async function ensureViewerDist(viewerDistDir) {
|
|
7517
|
-
const indexPath =
|
|
8216
|
+
const indexPath = path14.join(viewerDistDir, "index.html");
|
|
7518
8217
|
if (await fileExists(indexPath)) {
|
|
7519
8218
|
return;
|
|
7520
8219
|
}
|
|
7521
|
-
const viewerProjectDir =
|
|
7522
|
-
if (await fileExists(
|
|
8220
|
+
const viewerProjectDir = path14.dirname(viewerDistDir);
|
|
8221
|
+
if (await fileExists(path14.join(viewerProjectDir, "package.json"))) {
|
|
7523
8222
|
await execFileAsync2("pnpm", ["build"], { cwd: viewerProjectDir });
|
|
7524
8223
|
}
|
|
7525
8224
|
}
|
|
@@ -7542,7 +8241,7 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
7542
8241
|
response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
|
|
7543
8242
|
return;
|
|
7544
8243
|
}
|
|
7545
|
-
const reportPath =
|
|
8244
|
+
const reportPath = path14.join(paths.wikiDir, "graph", "report.json");
|
|
7546
8245
|
const report = await readJsonFile(reportPath) ?? null;
|
|
7547
8246
|
response.writeHead(200, { "content-type": "application/json" });
|
|
7548
8247
|
response.end(JSON.stringify(buildViewerGraphArtifact(graph, { report, full: options.full ?? false })));
|
|
@@ -7606,13 +8305,13 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
7606
8305
|
return;
|
|
7607
8306
|
}
|
|
7608
8307
|
if (url.pathname === "/api/graph-report") {
|
|
7609
|
-
const reportPath =
|
|
8308
|
+
const reportPath = path14.join(paths.wikiDir, "graph", "report.json");
|
|
7610
8309
|
if (!await fileExists(reportPath)) {
|
|
7611
8310
|
response.writeHead(404, { "content-type": "application/json" });
|
|
7612
8311
|
response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
|
|
7613
8312
|
return;
|
|
7614
8313
|
}
|
|
7615
|
-
const body = await
|
|
8314
|
+
const body = await fs12.readFile(reportPath, "utf8");
|
|
7616
8315
|
response.writeHead(200, { "content-type": "application/json" });
|
|
7617
8316
|
response.end(body);
|
|
7618
8317
|
return;
|
|
@@ -7850,7 +8549,7 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
7850
8549
|
return;
|
|
7851
8550
|
}
|
|
7852
8551
|
if (url.pathname === "/api/workspace") {
|
|
7853
|
-
const reportPath =
|
|
8552
|
+
const reportPath = path14.join(paths.wikiDir, "graph", "report.json");
|
|
7854
8553
|
const [graphRaw, reportRaw, approvalsRaw, candidatesRaw, memoryTasksRaw, watchStatusRaw, lintRaw, doctorRaw] = await Promise.all([
|
|
7855
8554
|
readJsonFile(paths.graphPath).catch(() => null),
|
|
7856
8555
|
readJsonFile(reportPath).catch(() => null),
|
|
@@ -7975,15 +8674,15 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
7975
8674
|
return;
|
|
7976
8675
|
}
|
|
7977
8676
|
const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
7978
|
-
const target =
|
|
7979
|
-
const fallback =
|
|
8677
|
+
const target = path14.join(paths.viewerDistDir, relativePath);
|
|
8678
|
+
const fallback = path14.join(paths.viewerDistDir, "index.html");
|
|
7980
8679
|
const filePath = await fileExists(target) ? target : fallback;
|
|
7981
8680
|
if (!await fileExists(filePath)) {
|
|
7982
8681
|
response.writeHead(503, { "content-type": "text/plain" });
|
|
7983
8682
|
response.end("Viewer build not found. Run `pnpm build` first.");
|
|
7984
8683
|
return;
|
|
7985
8684
|
}
|
|
7986
|
-
const staticBody = await
|
|
8685
|
+
const staticBody = await fs12.readFile(filePath);
|
|
7987
8686
|
response.writeHead(200, { "content-type": mime.lookup(filePath) || "text/plain" });
|
|
7988
8687
|
response.end(staticBody);
|
|
7989
8688
|
} catch (error) {
|
|
@@ -8023,7 +8722,7 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
|
|
|
8023
8722
|
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
8024
8723
|
}
|
|
8025
8724
|
await ensureViewerDist(paths.viewerDistDir);
|
|
8026
|
-
const indexPath =
|
|
8725
|
+
const indexPath = path14.join(paths.viewerDistDir, "index.html");
|
|
8027
8726
|
if (!await fileExists(indexPath)) {
|
|
8028
8727
|
throw new Error("Viewer build not found. Run `pnpm build` first.");
|
|
8029
8728
|
}
|
|
@@ -8049,17 +8748,17 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
|
|
|
8049
8748
|
} : null;
|
|
8050
8749
|
})
|
|
8051
8750
|
);
|
|
8052
|
-
const rawHtml = await
|
|
8751
|
+
const rawHtml = await fs12.readFile(indexPath, "utf8");
|
|
8053
8752
|
const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
|
|
8054
8753
|
const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
|
|
8055
|
-
const scriptPath = scriptMatch?.[1] ?
|
|
8056
|
-
const stylePath = styleMatch?.[1] ?
|
|
8754
|
+
const scriptPath = scriptMatch?.[1] ? path14.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
|
|
8755
|
+
const stylePath = styleMatch?.[1] ? path14.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
|
|
8057
8756
|
if (!scriptPath || !await fileExists(scriptPath)) {
|
|
8058
8757
|
throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
|
|
8059
8758
|
}
|
|
8060
|
-
const script = await
|
|
8061
|
-
const style = stylePath && await fileExists(stylePath) ? await
|
|
8062
|
-
const report = await readJsonFile(
|
|
8759
|
+
const script = await fs12.readFile(scriptPath, "utf8");
|
|
8760
|
+
const style = stylePath && await fileExists(stylePath) ? await fs12.readFile(stylePath, "utf8") : "";
|
|
8761
|
+
const report = await readJsonFile(path14.join(paths.wikiDir, "graph", "report.json"));
|
|
8063
8762
|
const embeddedData = JSON.stringify(
|
|
8064
8763
|
{ graph: buildViewerGraphArtifact(graph, { report, full: options.full ?? false }), pages: pages.filter(Boolean), report },
|
|
8065
8764
|
null,
|
|
@@ -8082,9 +8781,9 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
|
|
|
8082
8781
|
"</html>",
|
|
8083
8782
|
""
|
|
8084
8783
|
].filter(Boolean).join("\n");
|
|
8085
|
-
await
|
|
8086
|
-
await
|
|
8087
|
-
return
|
|
8784
|
+
await fs12.mkdir(path14.dirname(outputPath), { recursive: true });
|
|
8785
|
+
await fs12.writeFile(outputPath, html, "utf8");
|
|
8786
|
+
return path14.resolve(outputPath);
|
|
8088
8787
|
}
|
|
8089
8788
|
export {
|
|
8090
8789
|
ALL_MIGRATIONS,
|
|
@@ -8114,6 +8813,7 @@ export {
|
|
|
8114
8813
|
buildConfiguredRedactor,
|
|
8115
8814
|
buildContextPack,
|
|
8116
8815
|
buildGraphShareArtifact,
|
|
8816
|
+
buildGraphTree,
|
|
8117
8817
|
buildMemoryGraphElements,
|
|
8118
8818
|
buildRedactor,
|
|
8119
8819
|
checkTrackedRepoChanges,
|
|
@@ -8137,12 +8837,14 @@ export {
|
|
|
8137
8837
|
estimatePageTokens,
|
|
8138
8838
|
estimateTokens,
|
|
8139
8839
|
evaluateCandidateForPromotion,
|
|
8840
|
+
evaluateGraphShrinkGuard,
|
|
8140
8841
|
expectedModelPath,
|
|
8141
8842
|
explainGraphVault,
|
|
8142
8843
|
exploreVault,
|
|
8143
8844
|
exportGraphFormat,
|
|
8144
8845
|
exportGraphHtml,
|
|
8145
8846
|
exportGraphReportHtml,
|
|
8847
|
+
exportGraphTree,
|
|
8146
8848
|
exportObsidianCanvas,
|
|
8147
8849
|
exportObsidianVault,
|
|
8148
8850
|
finishMemoryTask,
|
|
@@ -8187,6 +8889,7 @@ export {
|
|
|
8187
8889
|
lookupPresetCapabilities,
|
|
8188
8890
|
markSuperseded,
|
|
8189
8891
|
memoryTaskHashes,
|
|
8892
|
+
mergeGraphFiles,
|
|
8190
8893
|
modelDownloadUrl,
|
|
8191
8894
|
pathGraphVault,
|
|
8192
8895
|
persistDecayFrontmatter,
|
|
@@ -8214,6 +8917,7 @@ export {
|
|
|
8214
8917
|
renderGraphShareMarkdown,
|
|
8215
8918
|
renderGraphSharePreviewHtml,
|
|
8216
8919
|
renderGraphShareSvg,
|
|
8920
|
+
renderGraphTreeHtml,
|
|
8217
8921
|
renderMemoryTaskMarkdown,
|
|
8218
8922
|
resetDecay,
|
|
8219
8923
|
resolveArtifactRootDir,
|