@swarmvaultai/engine 0.1.28 → 0.1.30
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 +15 -1
- package/dist/chunk-IAEYFTUS.js +1159 -0
- package/dist/chunk-IHMJCCXR.js +1146 -0
- package/dist/index.d.ts +62 -3
- package/dist/index.js +1682 -933
- package/dist/registry-5SYH3Y3U.js +12 -0
- package/dist/registry-G7NSRYCO.js +12 -0
- package/dist/viewer/assets/index-Csm8eB3P.js +331 -0
- package/dist/viewer/assets/index-DUJ6MWHL.css +1 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/lib.js +178 -0
- package/package.json +7 -4
- package/dist/viewer/assets/index-C7PCTMog.js +0 -330
- package/dist/viewer/assets/index-DiMCbjBi.css +0 -1
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
uniqueBy,
|
|
22
22
|
writeFileIfChanged,
|
|
23
23
|
writeJsonFile
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-IAEYFTUS.js";
|
|
25
25
|
|
|
26
26
|
// src/agents.ts
|
|
27
27
|
import crypto from "crypto";
|
|
@@ -32,8 +32,8 @@ var managedStart = "<!-- swarmvault:managed:start -->";
|
|
|
32
32
|
var managedEnd = "<!-- swarmvault:managed:end -->";
|
|
33
33
|
var legacyManagedStart = "<!-- vault:managed:start -->";
|
|
34
34
|
var legacyManagedEnd = "<!-- vault:managed:end -->";
|
|
35
|
-
var
|
|
36
|
-
var
|
|
35
|
+
var claudeSearchMatcher = "Glob|Grep";
|
|
36
|
+
var claudeSessionMatchers = ["startup", "resume", "clear", "compact"];
|
|
37
37
|
var geminiSessionMatcher = "startup";
|
|
38
38
|
var geminiSearchMatcher = "glob|grep|search|find";
|
|
39
39
|
var copilotHookVersion = 1;
|
|
@@ -89,6 +89,8 @@ function primaryTargetPathForAgent(rootDir, agent) {
|
|
|
89
89
|
}
|
|
90
90
|
function hookScriptPathForAgent(rootDir, agent) {
|
|
91
91
|
switch (agent) {
|
|
92
|
+
case "claude":
|
|
93
|
+
return path.join(rootDir, ".claude", "hooks", "swarmvault-graph-first.js");
|
|
92
94
|
case "opencode":
|
|
93
95
|
return path.join(rootDir, ".opencode", "plugins", "swarmvault-graph-first.js");
|
|
94
96
|
case "gemini":
|
|
@@ -177,25 +179,106 @@ async function readJsonWithWarnings(filePath, fallback, label) {
|
|
|
177
179
|
}
|
|
178
180
|
async function installClaudeHook(rootDir) {
|
|
179
181
|
const settingsPath = path.join(rootDir, ".claude", "settings.json");
|
|
182
|
+
const scriptPath = path.join(rootDir, ".claude", "hooks", "swarmvault-graph-first.js");
|
|
183
|
+
await writeOwnedFile(scriptPath, buildClaudeHookScript(), true);
|
|
180
184
|
await ensureDir(path.dirname(settingsPath));
|
|
181
185
|
const { data: settings, warnings } = await readJsonWithWarnings(settingsPath, {}, ".claude/settings.json");
|
|
182
186
|
if (warnings.length > 0 && await fileExists(settingsPath)) {
|
|
183
187
|
return { path: settingsPath, warnings };
|
|
184
188
|
}
|
|
185
189
|
const hooks = settings.hooks ?? {};
|
|
190
|
+
const sessionStart = hooks.SessionStart ?? [];
|
|
186
191
|
const preToolUse = hooks.PreToolUse ?? [];
|
|
187
|
-
const
|
|
188
|
-
|
|
192
|
+
const sessionCommand = 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/swarmvault-graph-first.js" session-start';
|
|
193
|
+
const preToolUseCommand = 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/swarmvault-graph-first.js" pre-tool-use';
|
|
194
|
+
for (const matcher of claudeSessionMatchers) {
|
|
195
|
+
if (!sessionStart.some((entry) => entry.matcher === matcher && JSON.stringify(entry).includes("swarmvault-graph-first.js"))) {
|
|
196
|
+
sessionStart.push({
|
|
197
|
+
matcher,
|
|
198
|
+
hooks: [{ type: "command", command: sessionCommand }]
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!preToolUse.some((entry) => entry.matcher === claudeSearchMatcher && JSON.stringify(entry).includes("swarmvault-graph-first.js"))) {
|
|
189
203
|
preToolUse.push({
|
|
190
|
-
matcher:
|
|
191
|
-
hooks: [{ type: "command", command:
|
|
204
|
+
matcher: claudeSearchMatcher,
|
|
205
|
+
hooks: [{ type: "command", command: preToolUseCommand }]
|
|
192
206
|
});
|
|
193
207
|
}
|
|
194
|
-
settings.hooks = { ...hooks, PreToolUse: preToolUse };
|
|
208
|
+
settings.hooks = { ...hooks, SessionStart: sessionStart, PreToolUse: preToolUse };
|
|
195
209
|
await fs.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
196
210
|
`, "utf8");
|
|
197
211
|
return { path: settingsPath, warnings: [] };
|
|
198
212
|
}
|
|
213
|
+
function buildClaudeHookScript() {
|
|
214
|
+
return `#!/usr/bin/env node
|
|
215
|
+
import crypto from "node:crypto";
|
|
216
|
+
import fs from "node:fs/promises";
|
|
217
|
+
import os from "node:os";
|
|
218
|
+
import path from "node:path";
|
|
219
|
+
|
|
220
|
+
${markerStateSnippet("claude").trim()}
|
|
221
|
+
|
|
222
|
+
async function readInput() {
|
|
223
|
+
let body = "";
|
|
224
|
+
for await (const chunk of process.stdin) {
|
|
225
|
+
body += chunk;
|
|
226
|
+
}
|
|
227
|
+
if (!body.trim()) {
|
|
228
|
+
return {};
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
return JSON.parse(body);
|
|
232
|
+
} catch {
|
|
233
|
+
return {};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function emit(value) {
|
|
238
|
+
process.stdout.write(\`\${JSON.stringify(value)}\\n\`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const mode = process.argv[2] ?? "";
|
|
242
|
+
const input = await readInput();
|
|
243
|
+
const cwd = resolveInputCwd(input);
|
|
244
|
+
const reportNote = "SwarmVault graph report exists at wiki/graph/report.md. Read it before broad grep/glob searching.";
|
|
245
|
+
|
|
246
|
+
if (!(await hasReport(cwd))) {
|
|
247
|
+
emit({});
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (mode === "session-start") {
|
|
252
|
+
await resetSession(cwd);
|
|
253
|
+
emit({
|
|
254
|
+
hookSpecificOutput: {
|
|
255
|
+
hookEventName: "SessionStart",
|
|
256
|
+
additionalContext: reportNote
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
process.exit(0);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const toolName = resolveToolName(input);
|
|
263
|
+
if (collectCandidatePaths(input).some((value) => isReportPath(value, cwd))) {
|
|
264
|
+
await markReportRead(cwd);
|
|
265
|
+
emit({});
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (isBroadSearchTool(toolName) && !(await hasSeenReport(cwd))) {
|
|
270
|
+
emit({
|
|
271
|
+
hookSpecificOutput: {
|
|
272
|
+
hookEventName: "PreToolUse",
|
|
273
|
+
additionalContext: reportNote
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
process.exit(0);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
emit({});
|
|
280
|
+
`;
|
|
281
|
+
}
|
|
199
282
|
function markerStateSnippet(agentKey) {
|
|
200
283
|
return `
|
|
201
284
|
function markerState(cwd) {
|
|
@@ -237,7 +320,9 @@ function collectCandidatePaths(node, acc = []) {
|
|
|
237
320
|
for (const [key, value] of Object.entries(node)) {
|
|
238
321
|
if (["path", "filePath", "file_path", "paths", "target", "targets"].includes(key)) {
|
|
239
322
|
collectCandidatePaths(value, acc);
|
|
323
|
+
continue;
|
|
240
324
|
}
|
|
325
|
+
collectCandidatePaths(value, acc);
|
|
241
326
|
}
|
|
242
327
|
return acc;
|
|
243
328
|
}
|
|
@@ -663,32 +748,151 @@ async function installConfiguredAgents(rootDir) {
|
|
|
663
748
|
// src/graph-export.ts
|
|
664
749
|
import fs2 from "fs/promises";
|
|
665
750
|
import path2 from "path";
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
rationale: "#14b8a6",
|
|
671
|
-
concept: "#0ea5e9",
|
|
672
|
-
entity: "#22c55e"
|
|
673
|
-
};
|
|
674
|
-
function xmlEscape(value) {
|
|
675
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
676
|
-
}
|
|
677
|
-
function cypherEscape(value) {
|
|
678
|
-
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
751
|
+
|
|
752
|
+
// src/graph-interchange.ts
|
|
753
|
+
function exportHyperedgeNodeId(hyperedge) {
|
|
754
|
+
return `hyperedge:${hyperedge.id}`;
|
|
679
755
|
}
|
|
680
756
|
function relationType(relation) {
|
|
681
757
|
const normalized = relation.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
682
758
|
return normalized || "RELATED_TO";
|
|
683
759
|
}
|
|
760
|
+
function cypherStringLiteral(value) {
|
|
761
|
+
let escaped = "";
|
|
762
|
+
for (const char of value) {
|
|
763
|
+
switch (char) {
|
|
764
|
+
case "\\":
|
|
765
|
+
escaped += "\\\\";
|
|
766
|
+
break;
|
|
767
|
+
case "'":
|
|
768
|
+
escaped += "\\'";
|
|
769
|
+
break;
|
|
770
|
+
case "\n":
|
|
771
|
+
escaped += "\\n";
|
|
772
|
+
break;
|
|
773
|
+
case "\r":
|
|
774
|
+
escaped += "\\r";
|
|
775
|
+
break;
|
|
776
|
+
case " ":
|
|
777
|
+
escaped += "\\t";
|
|
778
|
+
break;
|
|
779
|
+
case "\b":
|
|
780
|
+
escaped += "\\b";
|
|
781
|
+
break;
|
|
782
|
+
case "\f":
|
|
783
|
+
escaped += "\\f";
|
|
784
|
+
break;
|
|
785
|
+
default: {
|
|
786
|
+
const code = char.codePointAt(0) ?? 0;
|
|
787
|
+
escaped += code < 32 || code === 8232 || code === 8233 ? `\\u${code.toString(16).padStart(4, "0")}` : char;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return `'${escaped}'`;
|
|
792
|
+
}
|
|
684
793
|
function graphPageById(graph) {
|
|
685
794
|
return new Map(graph.pages.map((page) => [page.id, page]));
|
|
686
795
|
}
|
|
687
796
|
function graphNodeById(graph) {
|
|
688
797
|
return new Map(graph.nodes.map((node) => [node.id, node]));
|
|
689
798
|
}
|
|
690
|
-
function
|
|
691
|
-
return
|
|
799
|
+
function normalizeSwarmNodeProps(node, page) {
|
|
800
|
+
return {
|
|
801
|
+
id: node.id,
|
|
802
|
+
label: node.label,
|
|
803
|
+
type: node.type,
|
|
804
|
+
sourceIds: JSON.stringify(node.sourceIds),
|
|
805
|
+
projectIds: JSON.stringify(node.projectIds),
|
|
806
|
+
...node.pageId ? { pageId: node.pageId } : {},
|
|
807
|
+
...page?.path ? { pagePath: page.path } : {},
|
|
808
|
+
...node.sourceClass ? { sourceClass: node.sourceClass } : {},
|
|
809
|
+
...node.language ? { language: node.language } : {},
|
|
810
|
+
...node.moduleId ? { moduleId: node.moduleId } : {},
|
|
811
|
+
...node.symbolKind ? { symbolKind: node.symbolKind } : {},
|
|
812
|
+
...node.communityId ? { communityId: node.communityId } : {},
|
|
813
|
+
...node.freshness ? { freshness: node.freshness } : {},
|
|
814
|
+
...node.confidence !== void 0 ? { confidence: node.confidence } : {},
|
|
815
|
+
...node.degree !== void 0 ? { degree: node.degree } : {},
|
|
816
|
+
...node.bridgeScore !== void 0 ? { bridgeScore: node.bridgeScore } : {},
|
|
817
|
+
...node.isGodNode !== void 0 ? { isGodNode: node.isGodNode } : {}
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function normalizeHyperedgeNodeProps(hyperedge) {
|
|
821
|
+
return {
|
|
822
|
+
id: exportHyperedgeNodeId(hyperedge),
|
|
823
|
+
label: hyperedge.label,
|
|
824
|
+
type: "hyperedge",
|
|
825
|
+
relation: hyperedge.relation,
|
|
826
|
+
evidenceClass: hyperedge.evidenceClass,
|
|
827
|
+
confidence: hyperedge.confidence,
|
|
828
|
+
sourcePageIds: JSON.stringify(hyperedge.sourcePageIds),
|
|
829
|
+
why: hyperedge.why
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function normalizeEdgeProps(edge) {
|
|
833
|
+
return {
|
|
834
|
+
id: edge.id,
|
|
835
|
+
relation: edge.relation,
|
|
836
|
+
status: edge.status,
|
|
837
|
+
evidenceClass: edge.evidenceClass,
|
|
838
|
+
confidence: edge.confidence,
|
|
839
|
+
provenance: JSON.stringify(edge.provenance),
|
|
840
|
+
...edge.similarityReasons?.length ? { similarityReasons: JSON.stringify(edge.similarityReasons) } : {},
|
|
841
|
+
...edge.similarityBasis ? { similarityBasis: edge.similarityBasis } : {}
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
function normalizeGroupMemberProps(hyperedge, nodeId) {
|
|
845
|
+
return {
|
|
846
|
+
id: `member:${hyperedge.id}:${nodeId}`,
|
|
847
|
+
relation: "group_member",
|
|
848
|
+
status: "inferred",
|
|
849
|
+
evidenceClass: hyperedge.evidenceClass,
|
|
850
|
+
confidence: hyperedge.confidence,
|
|
851
|
+
provenance: JSON.stringify(hyperedge.sourcePageIds)
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
function filterGraphBySourceClasses(graph, includeClasses) {
|
|
855
|
+
const allowed = new Set(includeClasses);
|
|
856
|
+
const nodeIds = new Set(graph.nodes.filter((node) => node.sourceClass && allowed.has(node.sourceClass)).map((node) => node.id));
|
|
857
|
+
const pageIds = new Set(graph.pages.filter((page) => page.sourceClass && allowed.has(page.sourceClass)).map((page) => page.id));
|
|
858
|
+
return {
|
|
859
|
+
...graph,
|
|
860
|
+
nodes: graph.nodes.filter((node) => nodeIds.has(node.id)),
|
|
861
|
+
edges: graph.edges.filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target)),
|
|
862
|
+
hyperedges: graph.hyperedges.map((hyperedge) => ({
|
|
863
|
+
...hyperedge,
|
|
864
|
+
nodeIds: hyperedge.nodeIds.filter((nodeId) => nodeIds.has(nodeId))
|
|
865
|
+
})).filter((hyperedge) => hyperedge.nodeIds.length >= 2),
|
|
866
|
+
communities: (graph.communities ?? []).map((community) => ({
|
|
867
|
+
...community,
|
|
868
|
+
nodeIds: community.nodeIds.filter((nodeId) => nodeIds.has(nodeId))
|
|
869
|
+
})).filter((community) => community.nodeIds.length > 0),
|
|
870
|
+
sources: graph.sources.filter((source) => source.sourceClass && allowed.has(source.sourceClass)),
|
|
871
|
+
pages: graph.pages.filter((page) => pageIds.has(page.id))
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function graphCounts(graph) {
|
|
875
|
+
return {
|
|
876
|
+
sources: graph.sources.length,
|
|
877
|
+
pages: graph.pages.length,
|
|
878
|
+
nodes: graph.nodes.length,
|
|
879
|
+
relationships: graph.edges.length,
|
|
880
|
+
hyperedges: graph.hyperedges.length,
|
|
881
|
+
groupMembers: graph.hyperedges.reduce((total, hyperedge) => total + hyperedge.nodeIds.length, 0)
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/graph-export.ts
|
|
886
|
+
var NODE_COLORS = {
|
|
887
|
+
source: "#f59e0b",
|
|
888
|
+
module: "#fb7185",
|
|
889
|
+
symbol: "#8b5cf6",
|
|
890
|
+
rationale: "#14b8a6",
|
|
891
|
+
concept: "#0ea5e9",
|
|
892
|
+
entity: "#22c55e"
|
|
893
|
+
};
|
|
894
|
+
function xmlEscape(value) {
|
|
895
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
692
896
|
}
|
|
693
897
|
function sortedCommunities(graph) {
|
|
694
898
|
const known = (graph.communities ?? []).map((community) => ({
|
|
@@ -950,35 +1154,14 @@ function renderCypher(graph) {
|
|
|
950
1154
|
const lines = ["// Neo4j Cypher import generated by SwarmVault", ""];
|
|
951
1155
|
for (const node of [...graph.nodes].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
952
1156
|
const page = node.pageId ? pageById2.get(node.pageId) : void 0;
|
|
953
|
-
const props = [
|
|
954
|
-
|
|
955
|
-
`label: '${cypherEscape(node.label)}'`,
|
|
956
|
-
`type: '${cypherEscape(node.type)}'`,
|
|
957
|
-
`sourceIds: '${cypherEscape(JSON.stringify(node.sourceIds))}'`,
|
|
958
|
-
`projectIds: '${cypherEscape(JSON.stringify(node.projectIds))}'`,
|
|
959
|
-
node.pageId ? `pageId: '${cypherEscape(node.pageId)}'` : "",
|
|
960
|
-
page?.path ? `pagePath: '${cypherEscape(page.path)}'` : "",
|
|
961
|
-
node.language ? `language: '${cypherEscape(node.language)}'` : "",
|
|
962
|
-
node.symbolKind ? `symbolKind: '${cypherEscape(node.symbolKind)}'` : "",
|
|
963
|
-
node.communityId ? `communityId: '${cypherEscape(node.communityId)}'` : "",
|
|
964
|
-
node.degree !== void 0 ? `degree: ${node.degree}` : "",
|
|
965
|
-
node.bridgeScore !== void 0 ? `bridgeScore: ${node.bridgeScore}` : "",
|
|
966
|
-
node.isGodNode !== void 0 ? `isGodNode: ${node.isGodNode}` : ""
|
|
967
|
-
].filter(Boolean).join(", ");
|
|
968
|
-
lines.push(`MERGE (n:SwarmNode {id: '${cypherEscape(node.id)}'}) SET n += { ${props} };`);
|
|
1157
|
+
const props = Object.entries(normalizeSwarmNodeProps(node, page)).map(([key, value]) => `${key}: ${typeof value === "string" ? cypherStringLiteral(value) : value}`).filter(Boolean).join(", ");
|
|
1158
|
+
lines.push(`MERGE (n:SwarmNode {id: ${cypherStringLiteral(node.id)}}) SET n += { ${props} };`);
|
|
969
1159
|
}
|
|
970
1160
|
lines.push("");
|
|
971
1161
|
for (const hyperedge of [...graph.hyperedges ?? []].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
972
1162
|
const hyperedgeNodeId = exportHyperedgeNodeId(hyperedge);
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
hyperedge.label
|
|
976
|
-
)}', type: 'hyperedge', relation: '${cypherEscape(hyperedge.relation)}', evidenceClass: '${cypherEscape(
|
|
977
|
-
hyperedge.evidenceClass
|
|
978
|
-
)}', confidence: ${hyperedge.confidence}, sourcePageIds: '${cypherEscape(JSON.stringify(hyperedge.sourcePageIds))}', why: '${cypherEscape(
|
|
979
|
-
hyperedge.why
|
|
980
|
-
)}' };`
|
|
981
|
-
);
|
|
1163
|
+
const props = Object.entries(normalizeHyperedgeNodeProps(hyperedge)).map(([key, value]) => `${key}: ${typeof value === "string" ? cypherStringLiteral(value) : value}`).join(", ");
|
|
1164
|
+
lines.push(`MERGE (h:SwarmNode {id: ${cypherStringLiteral(hyperedgeNodeId)}}) SET h += { ${props} };`);
|
|
982
1165
|
}
|
|
983
1166
|
if ((graph.hyperedges ?? []).length) {
|
|
984
1167
|
lines.push("");
|
|
@@ -986,23 +1169,21 @@ function renderCypher(graph) {
|
|
|
986
1169
|
for (const hyperedge of [...graph.hyperedges ?? []].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
987
1170
|
const hyperedgeNodeId = exportHyperedgeNodeId(hyperedge);
|
|
988
1171
|
for (const nodeId of hyperedge.nodeIds) {
|
|
1172
|
+
const props = Object.entries(normalizeGroupMemberProps(hyperedge, nodeId)).map(([key, value]) => `${key}: ${typeof value === "string" ? cypherStringLiteral(value) : value}`).join(", ");
|
|
989
1173
|
lines.push(
|
|
990
|
-
`MATCH (h:SwarmNode {id:
|
|
991
|
-
`MERGE (h)-[r:GROUP_MEMBER {id:
|
|
992
|
-
`SET r += {
|
|
993
|
-
hyperedge.evidenceClass
|
|
994
|
-
)}', confidence: ${hyperedge.confidence}, provenance: '${cypherEscape(JSON.stringify(hyperedge.sourcePageIds))}' };`
|
|
1174
|
+
`MATCH (h:SwarmNode {id: ${cypherStringLiteral(hyperedgeNodeId)}}), (n:SwarmNode {id: ${cypherStringLiteral(nodeId)}})`,
|
|
1175
|
+
`MERGE (h)-[r:GROUP_MEMBER {id: ${cypherStringLiteral(`member:${hyperedge.id}:${nodeId}`)}}]->(n)`,
|
|
1176
|
+
`SET r += { ${props} };`
|
|
995
1177
|
);
|
|
996
1178
|
}
|
|
997
1179
|
}
|
|
998
1180
|
lines.push("");
|
|
999
1181
|
for (const edge of [...graph.edges].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
1182
|
+
const props = Object.entries(normalizeEdgeProps(edge)).map(([key, value]) => `${key}: ${typeof value === "string" ? cypherStringLiteral(value) : value}`).join(", ");
|
|
1000
1183
|
lines.push(
|
|
1001
|
-
`MATCH (a:SwarmNode {id:
|
|
1002
|
-
`MERGE (a)-[r:${relationType(edge.relation)} {id:
|
|
1003
|
-
`SET r += {
|
|
1004
|
-
edge.evidenceClass
|
|
1005
|
-
)}', confidence: ${edge.confidence}, provenance: '${cypherEscape(JSON.stringify(edge.provenance))}'${edge.similarityReasons?.length ? `, similarityReasons: '${cypherEscape(JSON.stringify(edge.similarityReasons))}'` : ""} };`
|
|
1184
|
+
`MATCH (a:SwarmNode {id: ${cypherStringLiteral(edge.source)}}), (b:SwarmNode {id: ${cypherStringLiteral(edge.target)}})`,
|
|
1185
|
+
`MERGE (a)-[r:${relationType(edge.relation)} {id: ${cypherStringLiteral(edge.id)}}]->(b)`,
|
|
1186
|
+
`SET r += { ${props} };`
|
|
1006
1187
|
);
|
|
1007
1188
|
}
|
|
1008
1189
|
lines.push("");
|
|
@@ -1028,104 +1209,477 @@ async function exportGraphFormat(rootDir, format, outputPath) {
|
|
|
1028
1209
|
return { format, outputPath: resolvedPath };
|
|
1029
1210
|
}
|
|
1030
1211
|
|
|
1031
|
-
// src/
|
|
1212
|
+
// src/graph-push.ts
|
|
1032
1213
|
import fs3 from "fs/promises";
|
|
1033
1214
|
import path3 from "path";
|
|
1034
|
-
import
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
if (await fileExists(path3.join(current, ".git"))) {
|
|
1049
|
-
return current;
|
|
1050
|
-
}
|
|
1051
|
-
const parent = path3.dirname(current);
|
|
1052
|
-
if (parent === current) {
|
|
1053
|
-
return null;
|
|
1054
|
-
}
|
|
1055
|
-
current = parent;
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
function shellQuote(value) {
|
|
1059
|
-
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
1060
|
-
}
|
|
1061
|
-
function resolveSwarmvaultExecutableCandidate() {
|
|
1062
|
-
const argvPath = process2.argv[1];
|
|
1063
|
-
if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path3.sep}@swarmvaultai${path3.sep}cli${path3.sep}`) || argvPath.includes(`${path3.sep}packages${path3.sep}cli${path3.sep}`))) {
|
|
1064
|
-
return path3.resolve(argvPath);
|
|
1065
|
-
}
|
|
1066
|
-
return "swarmvault";
|
|
1067
|
-
}
|
|
1068
|
-
function managedHookBlock(vaultRoot) {
|
|
1069
|
-
const resolvedExecutable = resolveSwarmvaultExecutableCandidate();
|
|
1070
|
-
return [
|
|
1071
|
-
hookStart,
|
|
1072
|
-
`cd ${shellQuote(vaultRoot)} || exit 0`,
|
|
1073
|
-
`swarmvault_bin=${shellQuote(resolvedExecutable)}`,
|
|
1074
|
-
'[ ! -x "$swarmvault_bin" ] && swarmvault_bin=$(command -v swarmvault 2>/dev/null || true)',
|
|
1075
|
-
'if [ -n "$swarmvault_bin" ] && [ -x "$swarmvault_bin" ]; then',
|
|
1076
|
-
` "$swarmvault_bin" watch --repo --once >/dev/null 2>&1 || printf '[swarmvault hook] refresh failed\\n' >&2`,
|
|
1077
|
-
"fi",
|
|
1078
|
-
hookEnd,
|
|
1079
|
-
""
|
|
1080
|
-
].join("\n");
|
|
1215
|
+
import neo4j from "neo4j-driver";
|
|
1216
|
+
|
|
1217
|
+
// src/benchmark.ts
|
|
1218
|
+
var CHARS_PER_TOKEN = 4;
|
|
1219
|
+
var DEFAULT_BENCHMARK_QUESTIONS = [
|
|
1220
|
+
"How does this vault connect the main concepts?",
|
|
1221
|
+
"Which pages bridge the biggest communities?",
|
|
1222
|
+
"What are the core abstractions in this vault?",
|
|
1223
|
+
"Where are the biggest knowledge gaps?",
|
|
1224
|
+
"What evidence should I read first?"
|
|
1225
|
+
];
|
|
1226
|
+
var RESEARCH_BENCHMARK_QUESTION = "Which research sources should I read first, and why?";
|
|
1227
|
+
function nodeMap(graph) {
|
|
1228
|
+
return new Map(graph.nodes.map((node) => [node.id, node]));
|
|
1081
1229
|
}
|
|
1082
|
-
function
|
|
1083
|
-
return
|
|
1230
|
+
function pageMap(graph) {
|
|
1231
|
+
return new Map(graph.pages.map((page) => [page.id, page]));
|
|
1084
1232
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
return "not_installed";
|
|
1088
|
-
}
|
|
1089
|
-
const content = await fs3.readFile(filePath, "utf8");
|
|
1090
|
-
return content.includes(hookStart) && content.includes(hookEnd) ? "installed" : "other_content";
|
|
1233
|
+
function estimateTokens(text) {
|
|
1234
|
+
return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));
|
|
1091
1235
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
let next;
|
|
1095
|
-
const startIndex = existing.indexOf(hookStart);
|
|
1096
|
-
const endIndex = existing.indexOf(hookEnd);
|
|
1097
|
-
if (startIndex !== -1 && endIndex !== -1) {
|
|
1098
|
-
next = `${existing.slice(0, startIndex)}${block}${existing.slice(endIndex + hookEnd.length)}`.trimEnd();
|
|
1099
|
-
} else if (existing.trim().length > 0) {
|
|
1100
|
-
next = `${existing.trimEnd()}
|
|
1101
|
-
|
|
1102
|
-
${block}`.trimEnd();
|
|
1103
|
-
} else {
|
|
1104
|
-
next = `#!/bin/sh
|
|
1105
|
-
${block}`.trimEnd();
|
|
1106
|
-
}
|
|
1107
|
-
await ensureDir(path3.dirname(filePath));
|
|
1108
|
-
await fs3.writeFile(filePath, `${next}
|
|
1109
|
-
`, { mode: 493, encoding: "utf8" });
|
|
1110
|
-
await fs3.chmod(filePath, 493);
|
|
1236
|
+
function estimateCorpusWords(texts) {
|
|
1237
|
+
return texts.reduce((total, text) => total + normalizeWhitespace(text).split(/\s+/).filter(Boolean).length, 0);
|
|
1111
1238
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1239
|
+
function benchmarkQueryTokens(graph, queryResult, pageContentsById) {
|
|
1240
|
+
const nodesById = nodeMap(graph);
|
|
1241
|
+
const pagesById = pageMap(graph);
|
|
1242
|
+
const edgeIds = new Set(queryResult.visitedEdgeIds);
|
|
1243
|
+
const lines = [];
|
|
1244
|
+
for (const pageId of queryResult.pageIds) {
|
|
1245
|
+
const page = pagesById.get(pageId);
|
|
1246
|
+
if (!page) {
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
const content = normalizeWhitespace(pageContentsById.get(pageId) ?? "").slice(0, 280);
|
|
1250
|
+
lines.push(`PAGE ${page.title} path=${page.path} kind=${page.kind}`);
|
|
1251
|
+
if (content) {
|
|
1252
|
+
lines.push(`PAGE_BODY ${content}`);
|
|
1253
|
+
}
|
|
1115
1254
|
}
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1255
|
+
for (const nodeId of queryResult.visitedNodeIds) {
|
|
1256
|
+
const node = nodesById.get(nodeId);
|
|
1257
|
+
if (!node) {
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
lines.push(`NODE ${node.label} type=${node.type} community=${node.communityId ?? "unassigned"} page=${node.pageId ?? "none"}`);
|
|
1121
1261
|
}
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1262
|
+
for (const edge of graph.edges) {
|
|
1263
|
+
if (!edgeIds.has(edge.id)) {
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
const source = nodesById.get(edge.source)?.label ?? edge.source;
|
|
1267
|
+
const target = nodesById.get(edge.target)?.label ?? edge.target;
|
|
1268
|
+
lines.push(`EDGE ${source} --${edge.relation}/${edge.evidenceClass}/${edge.confidence.toFixed(2)}--> ${target}`);
|
|
1126
1269
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1270
|
+
const queryTokens = estimateTokens(lines.join("\n"));
|
|
1271
|
+
return {
|
|
1272
|
+
question: queryResult.question,
|
|
1273
|
+
queryTokens,
|
|
1274
|
+
reduction: 0,
|
|
1275
|
+
visitedNodeIds: queryResult.visitedNodeIds,
|
|
1276
|
+
visitedEdgeIds: queryResult.visitedEdgeIds,
|
|
1277
|
+
pageIds: queryResult.pageIds
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
function graphHash(graph) {
|
|
1281
|
+
const hashedPages = graph.pages.filter((page) => page.kind !== "graph_report" && page.kind !== "community_summary");
|
|
1282
|
+
const normalized = JSON.stringify(
|
|
1283
|
+
{
|
|
1284
|
+
nodes: [...graph.nodes].map((node) => ({
|
|
1285
|
+
id: node.id,
|
|
1286
|
+
type: node.type,
|
|
1287
|
+
label: node.label,
|
|
1288
|
+
pageId: node.pageId ?? null,
|
|
1289
|
+
sourceClass: node.sourceClass ?? null,
|
|
1290
|
+
communityId: node.communityId ?? null,
|
|
1291
|
+
degree: node.degree ?? null,
|
|
1292
|
+
bridgeScore: node.bridgeScore ?? null,
|
|
1293
|
+
isGodNode: node.isGodNode ?? false,
|
|
1294
|
+
sourceIds: [...node.sourceIds].sort(),
|
|
1295
|
+
projectIds: [...node.projectIds].sort()
|
|
1296
|
+
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
1297
|
+
edges: [...graph.edges].map((edge) => ({
|
|
1298
|
+
id: edge.id,
|
|
1299
|
+
source: edge.source,
|
|
1300
|
+
target: edge.target,
|
|
1301
|
+
relation: edge.relation,
|
|
1302
|
+
status: edge.status,
|
|
1303
|
+
evidenceClass: edge.evidenceClass,
|
|
1304
|
+
similarityBasis: edge.similarityBasis ?? null,
|
|
1305
|
+
confidence: edge.confidence,
|
|
1306
|
+
provenance: [...edge.provenance].sort()
|
|
1307
|
+
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
1308
|
+
pages: [...hashedPages].map((page) => ({
|
|
1309
|
+
id: page.id,
|
|
1310
|
+
path: page.path,
|
|
1311
|
+
kind: page.kind,
|
|
1312
|
+
status: page.status,
|
|
1313
|
+
sourceType: page.sourceType ?? null,
|
|
1314
|
+
sourceClass: page.sourceClass ?? null,
|
|
1315
|
+
sourceIds: [...page.sourceIds].sort(),
|
|
1316
|
+
projectIds: [...page.projectIds].sort(),
|
|
1317
|
+
nodeIds: [...page.nodeIds].sort()
|
|
1318
|
+
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
1319
|
+
communities: [...graph.communities ?? []].map((community) => ({
|
|
1320
|
+
id: community.id,
|
|
1321
|
+
label: community.label,
|
|
1322
|
+
nodeIds: [...community.nodeIds].sort()
|
|
1323
|
+
})).sort((left, right) => left.id.localeCompare(right.id))
|
|
1324
|
+
},
|
|
1325
|
+
null,
|
|
1326
|
+
0
|
|
1327
|
+
);
|
|
1328
|
+
return sha256(normalized);
|
|
1329
|
+
}
|
|
1330
|
+
function hasResearchSources(pages) {
|
|
1331
|
+
return pages.some((page) => page.kind === "source" && Boolean(page.sourceType) && page.sourceType !== "url");
|
|
1332
|
+
}
|
|
1333
|
+
function defaultBenchmarkQuestionsForGraph(graph, maxQuestions = 3) {
|
|
1334
|
+
const normalizedLimit = Math.max(1, Math.min(maxQuestions, DEFAULT_BENCHMARK_QUESTIONS.length));
|
|
1335
|
+
const questions = [...DEFAULT_BENCHMARK_QUESTIONS];
|
|
1336
|
+
if (hasResearchSources(graph.pages)) {
|
|
1337
|
+
questions.unshift(RESEARCH_BENCHMARK_QUESTION);
|
|
1338
|
+
}
|
|
1339
|
+
return uniqueBy(questions, (item) => item).slice(0, normalizedLimit);
|
|
1340
|
+
}
|
|
1341
|
+
function buildBenchmarkArtifact(input) {
|
|
1342
|
+
const corpusTokens = Math.max(1, Math.round(input.corpusWords * (100 / 75)));
|
|
1343
|
+
const perQuestion = input.perQuestion.filter((entry) => entry.queryTokens > 0).map((entry) => ({
|
|
1344
|
+
...entry,
|
|
1345
|
+
reduction: Number(Math.max(0, 1 - entry.queryTokens / Math.max(1, corpusTokens)).toFixed(3))
|
|
1346
|
+
}));
|
|
1347
|
+
const avgQueryTokens = perQuestion.length ? Math.max(1, Math.round(perQuestion.reduce((total, entry) => total + entry.queryTokens, 0) / perQuestion.length)) : 0;
|
|
1348
|
+
const reductionRatio = avgQueryTokens ? Number(Math.max(0, 1 - avgQueryTokens / Math.max(1, corpusTokens)).toFixed(3)) : 0;
|
|
1349
|
+
const uniqueVisitedNodes = new Set(perQuestion.flatMap((entry) => entry.visitedNodeIds)).size;
|
|
1350
|
+
const summary = {
|
|
1351
|
+
questionCount: input.questions.length,
|
|
1352
|
+
uniqueVisitedNodes,
|
|
1353
|
+
finalContextTokens: avgQueryTokens,
|
|
1354
|
+
naiveCorpusTokens: corpusTokens,
|
|
1355
|
+
avgReduction: reductionRatio,
|
|
1356
|
+
reductionRatio
|
|
1357
|
+
};
|
|
1358
|
+
return {
|
|
1359
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1360
|
+
graphHash: graphHash(input.graph),
|
|
1361
|
+
corpusWords: input.corpusWords,
|
|
1362
|
+
corpusTokens,
|
|
1363
|
+
nodes: input.graph.nodes.length,
|
|
1364
|
+
edges: input.graph.edges.length,
|
|
1365
|
+
avgQueryTokens,
|
|
1366
|
+
reductionRatio,
|
|
1367
|
+
sampleQuestions: input.questions,
|
|
1368
|
+
perQuestion,
|
|
1369
|
+
questionResults: perQuestion,
|
|
1370
|
+
summary
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// src/graph-push.ts
|
|
1375
|
+
var DEFAULT_NEO4J_BATCH_SIZE = 500;
|
|
1376
|
+
var DEFAULT_NEO4J_DATABASE = "neo4j";
|
|
1377
|
+
function requireConfigValue(value, name) {
|
|
1378
|
+
if (typeof value === "string" && value.trim()) {
|
|
1379
|
+
return value.trim();
|
|
1380
|
+
}
|
|
1381
|
+
throw new Error(`Neo4j push requires ${name}. Configure \`graphSinks.neo4j.${name}\` or pass the matching CLI flag.`);
|
|
1382
|
+
}
|
|
1383
|
+
async function deriveVaultId(rootDir) {
|
|
1384
|
+
const realRoot = await fs3.realpath(rootDir).catch(() => path3.resolve(rootDir));
|
|
1385
|
+
const label = slugify(path3.basename(realRoot));
|
|
1386
|
+
return `${label}-${sha256(realRoot).slice(0, 12)}`;
|
|
1387
|
+
}
|
|
1388
|
+
async function resolveNeo4jPushConfig(rootDir, options) {
|
|
1389
|
+
const { config } = await loadVaultConfig(rootDir);
|
|
1390
|
+
const sink = config.graphSinks?.neo4j;
|
|
1391
|
+
const includeClasses = normalizeIncludedClasses(options.includeClasses ?? sink?.includeClasses ?? ["first_party"]);
|
|
1392
|
+
return {
|
|
1393
|
+
uri: requireConfigValue(options.uri ?? sink?.uri, "uri"),
|
|
1394
|
+
username: requireConfigValue(options.username ?? sink?.username, "username"),
|
|
1395
|
+
passwordEnv: requireConfigValue(options.passwordEnv ?? sink?.passwordEnv, "passwordEnv"),
|
|
1396
|
+
database: options.database?.trim() || sink?.database?.trim() || DEFAULT_NEO4J_DATABASE,
|
|
1397
|
+
vaultId: options.vaultId?.trim() || sink?.vaultId?.trim() || await deriveVaultId(rootDir),
|
|
1398
|
+
includeClasses,
|
|
1399
|
+
batchSize: normalizeBatchSize(options.batchSize ?? sink?.batchSize)
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
function normalizeIncludedClasses(values) {
|
|
1403
|
+
const allowed = ["first_party", "third_party", "resource", "generated"];
|
|
1404
|
+
const unique = [...new Set(values)].filter((value) => allowed.includes(value));
|
|
1405
|
+
return unique.length ? unique : ["first_party"];
|
|
1406
|
+
}
|
|
1407
|
+
function normalizeBatchSize(value) {
|
|
1408
|
+
if (!Number.isFinite(value) || !value || value <= 0) {
|
|
1409
|
+
return DEFAULT_NEO4J_BATCH_SIZE;
|
|
1410
|
+
}
|
|
1411
|
+
return Math.max(1, Math.floor(value));
|
|
1412
|
+
}
|
|
1413
|
+
async function loadGraph2(rootDir) {
|
|
1414
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
1415
|
+
const raw = JSON.parse(await fs3.readFile(paths.graphPath, "utf8"));
|
|
1416
|
+
return raw;
|
|
1417
|
+
}
|
|
1418
|
+
function buildResult(input) {
|
|
1419
|
+
const counts = graphCounts(input.filteredGraph);
|
|
1420
|
+
const fullCounts = graphCounts(input.fullGraph);
|
|
1421
|
+
return {
|
|
1422
|
+
sink: "neo4j",
|
|
1423
|
+
uri: input.resolved.uri,
|
|
1424
|
+
database: input.resolved.database,
|
|
1425
|
+
vaultId: input.resolved.vaultId,
|
|
1426
|
+
dryRun: input.dryRun,
|
|
1427
|
+
graphHash: graphHash(input.fullGraph),
|
|
1428
|
+
includedSourceClasses: input.resolved.includeClasses,
|
|
1429
|
+
counts,
|
|
1430
|
+
skipped: {
|
|
1431
|
+
sources: Math.max(0, fullCounts.sources - counts.sources),
|
|
1432
|
+
pages: Math.max(0, fullCounts.pages - counts.pages),
|
|
1433
|
+
nodes: Math.max(0, fullCounts.nodes - counts.nodes),
|
|
1434
|
+
relationships: Math.max(0, fullCounts.relationships - counts.relationships),
|
|
1435
|
+
hyperedges: Math.max(0, fullCounts.hyperedges - counts.hyperedges),
|
|
1436
|
+
groupMembers: Math.max(0, fullCounts.groupMembers - counts.groupMembers)
|
|
1437
|
+
},
|
|
1438
|
+
warnings: input.warnings ?? []
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
function createDriver(uri, username, password) {
|
|
1442
|
+
return neo4j.driver(uri, neo4j.auth.basic(username, password));
|
|
1443
|
+
}
|
|
1444
|
+
async function ensureNeo4jConstraints(session) {
|
|
1445
|
+
await session.run("CREATE CONSTRAINT swarmvault_node_identity IF NOT EXISTS FOR (n:SwarmNode) REQUIRE (n.vaultId, n.id) IS UNIQUE");
|
|
1446
|
+
await session.run("CREATE CONSTRAINT swarmvault_sync_identity IF NOT EXISTS FOR (s:SwarmVaultSync) REQUIRE s.vaultId IS UNIQUE");
|
|
1447
|
+
}
|
|
1448
|
+
function chunkRows(rows, batchSize) {
|
|
1449
|
+
const chunks = [];
|
|
1450
|
+
for (let index = 0; index < rows.length; index += batchSize) {
|
|
1451
|
+
chunks.push(rows.slice(index, index + batchSize));
|
|
1452
|
+
}
|
|
1453
|
+
return chunks;
|
|
1454
|
+
}
|
|
1455
|
+
async function writeNodeRows(session, vaultId, rows, batchSize) {
|
|
1456
|
+
for (const chunk of chunkRows(rows, batchSize)) {
|
|
1457
|
+
await session.executeWrite(
|
|
1458
|
+
(tx) => tx.run(["UNWIND $rows AS row", "MERGE (n:SwarmNode { vaultId: $vaultId, id: row.id })", "SET n += row.props"].join("\n"), {
|
|
1459
|
+
vaultId,
|
|
1460
|
+
rows: chunk
|
|
1461
|
+
})
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
async function writeEdgeRows(session, vaultId, rows, batchSize, relation) {
|
|
1466
|
+
const neoRelation = relationType(relation);
|
|
1467
|
+
const query = [
|
|
1468
|
+
"UNWIND $rows AS row",
|
|
1469
|
+
"MATCH (a:SwarmNode { vaultId: $vaultId, id: row.source })",
|
|
1470
|
+
"MATCH (b:SwarmNode { vaultId: $vaultId, id: row.target })",
|
|
1471
|
+
`MERGE (a)-[r:${neoRelation} { vaultId: $vaultId, id: row.id }]->(b)`,
|
|
1472
|
+
"SET r += row.props"
|
|
1473
|
+
].join("\n");
|
|
1474
|
+
for (const chunk of chunkRows(rows, batchSize)) {
|
|
1475
|
+
await session.executeWrite((tx) => tx.run(query, { vaultId, rows: chunk }));
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
async function writeSyncNode(session, input) {
|
|
1479
|
+
await session.executeWrite(
|
|
1480
|
+
(tx) => tx.run(
|
|
1481
|
+
[
|
|
1482
|
+
"MERGE (s:SwarmVaultSync { vaultId: $vaultId })",
|
|
1483
|
+
"SET s += {",
|
|
1484
|
+
" vaultId: $vaultId,",
|
|
1485
|
+
" rootDir: $rootDir,",
|
|
1486
|
+
" graphGeneratedAt: $graphGeneratedAt,",
|
|
1487
|
+
" graphHash: $graphHash,",
|
|
1488
|
+
" pushedAt: $pushedAt,",
|
|
1489
|
+
" includedSourceClasses: $includedSourceClasses,",
|
|
1490
|
+
" sources: $sources,",
|
|
1491
|
+
" pages: $pages,",
|
|
1492
|
+
" nodes: $nodes,",
|
|
1493
|
+
" relationships: $relationships,",
|
|
1494
|
+
" hyperedges: $hyperedges,",
|
|
1495
|
+
" groupMembers: $groupMembers",
|
|
1496
|
+
"}"
|
|
1497
|
+
].join("\n"),
|
|
1498
|
+
{
|
|
1499
|
+
vaultId: input.vaultId,
|
|
1500
|
+
rootDir: path3.resolve(input.rootDir),
|
|
1501
|
+
graphGeneratedAt: input.graph.generatedAt,
|
|
1502
|
+
graphHash: graphHash(input.graph),
|
|
1503
|
+
pushedAt: input.pushedAt,
|
|
1504
|
+
includedSourceClasses: input.includedSourceClasses,
|
|
1505
|
+
...input.counts
|
|
1506
|
+
}
|
|
1507
|
+
)
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
async function pushGraphNeo4j(rootDir, options = {}) {
|
|
1511
|
+
const graph = await loadGraph2(rootDir);
|
|
1512
|
+
const resolved = await resolveNeo4jPushConfig(rootDir, options);
|
|
1513
|
+
const filteredGraph = filterGraphBySourceClasses(graph, resolved.includeClasses);
|
|
1514
|
+
const warnings = filteredGraph.nodes.length || filteredGraph.hyperedges.length || filteredGraph.edges.length ? [] : [`No graph records matched the included source classes: ${resolved.includeClasses.join(", ")}`];
|
|
1515
|
+
const result = buildResult({
|
|
1516
|
+
resolved,
|
|
1517
|
+
filteredGraph,
|
|
1518
|
+
fullGraph: graph,
|
|
1519
|
+
dryRun: options.dryRun ?? false,
|
|
1520
|
+
warnings
|
|
1521
|
+
});
|
|
1522
|
+
if (options.dryRun) {
|
|
1523
|
+
return result;
|
|
1524
|
+
}
|
|
1525
|
+
const password = process.env[resolved.passwordEnv];
|
|
1526
|
+
if (!password) {
|
|
1527
|
+
throw new Error(`Environment variable ${resolved.passwordEnv} is required for Neo4j push.`);
|
|
1528
|
+
}
|
|
1529
|
+
const driver = (options.driverFactory ?? createDriver)(resolved.uri, resolved.username, password);
|
|
1530
|
+
const session = driver.session({ database: resolved.database });
|
|
1531
|
+
try {
|
|
1532
|
+
await ensureNeo4jConstraints(session);
|
|
1533
|
+
const pageById2 = graphPageById(filteredGraph);
|
|
1534
|
+
const nodeRows = [
|
|
1535
|
+
...filteredGraph.nodes.sort((left, right) => left.id.localeCompare(right.id)).map((node) => ({
|
|
1536
|
+
id: node.id,
|
|
1537
|
+
props: normalizeSwarmNodeProps(node, node.pageId ? pageById2.get(node.pageId) : void 0)
|
|
1538
|
+
})),
|
|
1539
|
+
...filteredGraph.hyperedges.sort((left, right) => left.id.localeCompare(right.id)).map((hyperedge) => ({
|
|
1540
|
+
id: normalizeHyperedgeNodeProps(hyperedge).id,
|
|
1541
|
+
props: normalizeHyperedgeNodeProps(hyperedge)
|
|
1542
|
+
}))
|
|
1543
|
+
];
|
|
1544
|
+
await writeNodeRows(session, resolved.vaultId, nodeRows, resolved.batchSize);
|
|
1545
|
+
const edgeGroups = /* @__PURE__ */ new Map();
|
|
1546
|
+
for (const edge of [...filteredGraph.edges].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
1547
|
+
const rows = edgeGroups.get(edge.relation) ?? [];
|
|
1548
|
+
rows.push({
|
|
1549
|
+
id: edge.id,
|
|
1550
|
+
source: edge.source,
|
|
1551
|
+
target: edge.target,
|
|
1552
|
+
props: normalizeEdgeProps(edge)
|
|
1553
|
+
});
|
|
1554
|
+
edgeGroups.set(edge.relation, rows);
|
|
1555
|
+
}
|
|
1556
|
+
for (const [relation, rows] of [...edgeGroups.entries()].sort((left, right) => left[0].localeCompare(right[0]))) {
|
|
1557
|
+
await writeEdgeRows(session, resolved.vaultId, rows, resolved.batchSize, relation);
|
|
1558
|
+
}
|
|
1559
|
+
const memberRows = filteredGraph.hyperedges.flatMap(
|
|
1560
|
+
(hyperedge) => hyperedge.nodeIds.map((nodeId) => ({
|
|
1561
|
+
id: `member:${hyperedge.id}:${nodeId}`,
|
|
1562
|
+
source: normalizeHyperedgeNodeProps(hyperedge).id,
|
|
1563
|
+
target: nodeId,
|
|
1564
|
+
props: normalizeGroupMemberProps(hyperedge, nodeId)
|
|
1565
|
+
}))
|
|
1566
|
+
);
|
|
1567
|
+
if (memberRows.length) {
|
|
1568
|
+
await writeEdgeRows(session, resolved.vaultId, memberRows, resolved.batchSize, "group_member");
|
|
1569
|
+
}
|
|
1570
|
+
await writeSyncNode(session, {
|
|
1571
|
+
vaultId: resolved.vaultId,
|
|
1572
|
+
rootDir,
|
|
1573
|
+
graph,
|
|
1574
|
+
pushedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1575
|
+
includedSourceClasses: resolved.includeClasses,
|
|
1576
|
+
counts: result.counts
|
|
1577
|
+
});
|
|
1578
|
+
return result;
|
|
1579
|
+
} finally {
|
|
1580
|
+
await session.close();
|
|
1581
|
+
await driver.close();
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// src/hooks.ts
|
|
1586
|
+
import fs4 from "fs/promises";
|
|
1587
|
+
import path4 from "path";
|
|
1588
|
+
import process2 from "process";
|
|
1589
|
+
var hookStart = "# >>> swarmvault hook >>>";
|
|
1590
|
+
var hookEnd = "# <<< swarmvault hook <<<";
|
|
1591
|
+
async function findNearestGitRoot(startPath) {
|
|
1592
|
+
let current = path4.resolve(startPath);
|
|
1593
|
+
try {
|
|
1594
|
+
const stat = await fs4.stat(current);
|
|
1595
|
+
if (!stat.isDirectory()) {
|
|
1596
|
+
current = path4.dirname(current);
|
|
1597
|
+
}
|
|
1598
|
+
} catch {
|
|
1599
|
+
current = path4.dirname(current);
|
|
1600
|
+
}
|
|
1601
|
+
while (true) {
|
|
1602
|
+
if (await fileExists(path4.join(current, ".git"))) {
|
|
1603
|
+
return current;
|
|
1604
|
+
}
|
|
1605
|
+
const parent = path4.dirname(current);
|
|
1606
|
+
if (parent === current) {
|
|
1607
|
+
return null;
|
|
1608
|
+
}
|
|
1609
|
+
current = parent;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
function shellQuote(value) {
|
|
1613
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
1614
|
+
}
|
|
1615
|
+
function resolveSwarmvaultExecutableCandidate() {
|
|
1616
|
+
const argvPath = process2.argv[1];
|
|
1617
|
+
if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path4.sep}@swarmvaultai${path4.sep}cli${path4.sep}`) || argvPath.includes(`${path4.sep}packages${path4.sep}cli${path4.sep}`))) {
|
|
1618
|
+
return path4.resolve(argvPath);
|
|
1619
|
+
}
|
|
1620
|
+
return "swarmvault";
|
|
1621
|
+
}
|
|
1622
|
+
function managedHookBlock(vaultRoot) {
|
|
1623
|
+
const resolvedExecutable = resolveSwarmvaultExecutableCandidate();
|
|
1624
|
+
return [
|
|
1625
|
+
hookStart,
|
|
1626
|
+
`cd ${shellQuote(vaultRoot)} || exit 0`,
|
|
1627
|
+
`swarmvault_bin=${shellQuote(resolvedExecutable)}`,
|
|
1628
|
+
'[ ! -x "$swarmvault_bin" ] && swarmvault_bin=$(command -v swarmvault 2>/dev/null || true)',
|
|
1629
|
+
'if [ -n "$swarmvault_bin" ] && [ -x "$swarmvault_bin" ]; then',
|
|
1630
|
+
` "$swarmvault_bin" watch --repo --once >/dev/null 2>&1 || printf '[swarmvault hook] refresh failed\\n' >&2`,
|
|
1631
|
+
"fi",
|
|
1632
|
+
hookEnd,
|
|
1633
|
+
""
|
|
1634
|
+
].join("\n");
|
|
1635
|
+
}
|
|
1636
|
+
function hookPath(repoRoot, hookName) {
|
|
1637
|
+
return path4.join(repoRoot, ".git", "hooks", hookName);
|
|
1638
|
+
}
|
|
1639
|
+
async function readHookStatus(filePath) {
|
|
1640
|
+
if (!await fileExists(filePath)) {
|
|
1641
|
+
return "not_installed";
|
|
1642
|
+
}
|
|
1643
|
+
const content = await fs4.readFile(filePath, "utf8");
|
|
1644
|
+
return content.includes(hookStart) && content.includes(hookEnd) ? "installed" : "other_content";
|
|
1645
|
+
}
|
|
1646
|
+
async function upsertHookFile(filePath, block) {
|
|
1647
|
+
const existing = await fileExists(filePath) ? await fs4.readFile(filePath, "utf8") : "";
|
|
1648
|
+
let next;
|
|
1649
|
+
const startIndex = existing.indexOf(hookStart);
|
|
1650
|
+
const endIndex = existing.indexOf(hookEnd);
|
|
1651
|
+
if (startIndex !== -1 && endIndex !== -1) {
|
|
1652
|
+
next = `${existing.slice(0, startIndex)}${block}${existing.slice(endIndex + hookEnd.length)}`.trimEnd();
|
|
1653
|
+
} else if (existing.trim().length > 0) {
|
|
1654
|
+
next = `${existing.trimEnd()}
|
|
1655
|
+
|
|
1656
|
+
${block}`.trimEnd();
|
|
1657
|
+
} else {
|
|
1658
|
+
next = `#!/bin/sh
|
|
1659
|
+
${block}`.trimEnd();
|
|
1660
|
+
}
|
|
1661
|
+
await ensureDir(path4.dirname(filePath));
|
|
1662
|
+
await fs4.writeFile(filePath, `${next}
|
|
1663
|
+
`, { mode: 493, encoding: "utf8" });
|
|
1664
|
+
await fs4.chmod(filePath, 493);
|
|
1665
|
+
}
|
|
1666
|
+
async function removeHookBlock(filePath) {
|
|
1667
|
+
if (!await fileExists(filePath)) {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
const existing = await fs4.readFile(filePath, "utf8");
|
|
1671
|
+
const startIndex = existing.indexOf(hookStart);
|
|
1672
|
+
const endIndex = existing.indexOf(hookEnd);
|
|
1673
|
+
if (startIndex === -1 || endIndex === -1) {
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
const next = `${existing.slice(0, startIndex)}${existing.slice(endIndex + hookEnd.length)}`.trim();
|
|
1677
|
+
if (!next || next === "#!/bin/sh") {
|
|
1678
|
+
await fs4.rm(filePath, { force: true });
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
await fs4.writeFile(filePath, `${next}
|
|
1682
|
+
`, "utf8");
|
|
1129
1683
|
}
|
|
1130
1684
|
async function getGitHookStatus(rootDir) {
|
|
1131
1685
|
const repoRoot = await findNearestGitRoot(rootDir);
|
|
@@ -1147,7 +1701,7 @@ async function installGitHooks(rootDir) {
|
|
|
1147
1701
|
if (!repoRoot) {
|
|
1148
1702
|
throw new Error("No git repository found above the current vault.");
|
|
1149
1703
|
}
|
|
1150
|
-
const block = managedHookBlock(
|
|
1704
|
+
const block = managedHookBlock(path4.resolve(rootDir));
|
|
1151
1705
|
await upsertHookFile(hookPath(repoRoot, "post-commit"), block);
|
|
1152
1706
|
await upsertHookFile(hookPath(repoRoot, "post-checkout"), block);
|
|
1153
1707
|
return getGitHookStatus(rootDir);
|
|
@@ -1167,27 +1721,27 @@ async function uninstallGitHooks(rootDir) {
|
|
|
1167
1721
|
}
|
|
1168
1722
|
|
|
1169
1723
|
// src/ingest.ts
|
|
1170
|
-
import
|
|
1171
|
-
import
|
|
1724
|
+
import fs10 from "fs/promises";
|
|
1725
|
+
import path11 from "path";
|
|
1172
1726
|
import { pathToFileURL } from "url";
|
|
1173
1727
|
import { Readability } from "@mozilla/readability";
|
|
1174
1728
|
import matter3 from "gray-matter";
|
|
1175
1729
|
import ignore from "ignore";
|
|
1176
|
-
import { JSDOM } from "jsdom";
|
|
1730
|
+
import { JSDOM as JSDOM2 } from "jsdom";
|
|
1177
1731
|
import mime from "mime-types";
|
|
1178
1732
|
import TurndownService from "turndown";
|
|
1179
1733
|
|
|
1180
1734
|
// src/code-analysis.ts
|
|
1181
|
-
import
|
|
1182
|
-
import
|
|
1735
|
+
import fs6 from "fs/promises";
|
|
1736
|
+
import path6 from "path";
|
|
1183
1737
|
import ts from "typescript";
|
|
1184
1738
|
|
|
1185
1739
|
// src/code-tree-sitter.ts
|
|
1186
|
-
import
|
|
1740
|
+
import fs5 from "fs/promises";
|
|
1187
1741
|
import { createRequire } from "module";
|
|
1188
|
-
import
|
|
1742
|
+
import path5 from "path";
|
|
1189
1743
|
var require2 = createRequire(import.meta.url);
|
|
1190
|
-
var TREE_SITTER_PACKAGE_ROOT =
|
|
1744
|
+
var TREE_SITTER_PACKAGE_ROOT = path5.dirname(path5.dirname(require2.resolve("@vscode/tree-sitter-wasm")));
|
|
1191
1745
|
var RATIONALE_MARKERS = ["NOTE:", "IMPORTANT:", "HACK:", "WHY:", "RATIONALE:"];
|
|
1192
1746
|
function stripKnownCommentPrefix(line) {
|
|
1193
1747
|
let next = line.trim();
|
|
@@ -1224,7 +1778,7 @@ async function getTreeSitterModule() {
|
|
|
1224
1778
|
async function ensureTreeSitterInit(module) {
|
|
1225
1779
|
if (!treeSitterInitPromise) {
|
|
1226
1780
|
treeSitterInitPromise = module.Parser.init({
|
|
1227
|
-
locateFile: () =>
|
|
1781
|
+
locateFile: () => path5.join(TREE_SITTER_PACKAGE_ROOT, "wasm", "tree-sitter.wasm")
|
|
1228
1782
|
});
|
|
1229
1783
|
}
|
|
1230
1784
|
return treeSitterInitPromise;
|
|
@@ -1237,7 +1791,7 @@ async function loadLanguage(language) {
|
|
|
1237
1791
|
const loader = (async () => {
|
|
1238
1792
|
const module = await getTreeSitterModule();
|
|
1239
1793
|
await ensureTreeSitterInit(module);
|
|
1240
|
-
const bytes = await
|
|
1794
|
+
const bytes = await fs5.readFile(path5.join(TREE_SITTER_PACKAGE_ROOT, "wasm", grammarFileByLanguage[language]));
|
|
1241
1795
|
return module.Language.load(bytes);
|
|
1242
1796
|
})();
|
|
1243
1797
|
languageCache.set(language, loader);
|
|
@@ -1254,16 +1808,16 @@ function stripCodeExtension(filePath) {
|
|
|
1254
1808
|
return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
|
|
1255
1809
|
}
|
|
1256
1810
|
function manifestModuleName(manifest, language) {
|
|
1257
|
-
const repoPath = manifest.repoRelativePath ??
|
|
1811
|
+
const repoPath = manifest.repoRelativePath ?? path5.basename(manifest.originalPath ?? manifest.storedPath);
|
|
1258
1812
|
const normalized = toPosix(stripCodeExtension(repoPath)).replace(/^\.\/+/, "");
|
|
1259
1813
|
if (!normalized) {
|
|
1260
1814
|
return void 0;
|
|
1261
1815
|
}
|
|
1262
1816
|
if (language === "python") {
|
|
1263
1817
|
const dotted = normalized.replace(/\/__init__$/i, "").replace(/\//g, ".").replace(/^src\./, "");
|
|
1264
|
-
return dotted ||
|
|
1818
|
+
return dotted || path5.posix.basename(normalized);
|
|
1265
1819
|
}
|
|
1266
|
-
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) ||
|
|
1820
|
+
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) || path5.posix.basename(normalized) : normalized;
|
|
1267
1821
|
}
|
|
1268
1822
|
function singleLineSignature(value) {
|
|
1269
1823
|
return truncate(
|
|
@@ -1298,6 +1852,55 @@ function collectCallNamesFromText(text, availableNames, selfName) {
|
|
|
1298
1852
|
}
|
|
1299
1853
|
return uniqueBy(names, (name) => name);
|
|
1300
1854
|
}
|
|
1855
|
+
function goReceiverBinding(node) {
|
|
1856
|
+
if (!node) {
|
|
1857
|
+
return {};
|
|
1858
|
+
}
|
|
1859
|
+
const names = uniqueBy(
|
|
1860
|
+
node.descendantsOfType(["identifier", "type_identifier"]).filter((item) => item !== null).map((item) => normalizeSymbolReference(item.text)).filter(Boolean),
|
|
1861
|
+
(item) => item
|
|
1862
|
+
);
|
|
1863
|
+
if (names.length === 0) {
|
|
1864
|
+
return {};
|
|
1865
|
+
}
|
|
1866
|
+
if (names.length === 1) {
|
|
1867
|
+
return { typeName: names[0] };
|
|
1868
|
+
}
|
|
1869
|
+
return {
|
|
1870
|
+
variableName: names[0],
|
|
1871
|
+
typeName: names.at(-1)
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
function goCalledSymbolName(node, receiver) {
|
|
1875
|
+
if (!node) {
|
|
1876
|
+
return void 0;
|
|
1877
|
+
}
|
|
1878
|
+
if (node.type === "selector_expression") {
|
|
1879
|
+
const targetName = normalizeSymbolReference(
|
|
1880
|
+
extractIdentifier(node.childForFieldName("operand") ?? node.childForFieldName("object") ?? node.namedChildren.at(0) ?? null) ?? ""
|
|
1881
|
+
);
|
|
1882
|
+
const fieldName = normalizeSymbolReference(
|
|
1883
|
+
extractIdentifier(node.childForFieldName("field") ?? findNamedChild(node, "field_identifier") ?? node.namedChildren.at(-1) ?? null) ?? ""
|
|
1884
|
+
);
|
|
1885
|
+
if (!fieldName) {
|
|
1886
|
+
return void 0;
|
|
1887
|
+
}
|
|
1888
|
+
if (receiver.variableName && receiver.typeName && targetName === receiver.variableName) {
|
|
1889
|
+
return `${receiver.typeName}.${fieldName}`;
|
|
1890
|
+
}
|
|
1891
|
+
return fieldName;
|
|
1892
|
+
}
|
|
1893
|
+
return normalizeSymbolReference(extractIdentifier(node) ?? "");
|
|
1894
|
+
}
|
|
1895
|
+
function goCallNamesFromBody(bodyNode, receiver) {
|
|
1896
|
+
if (!bodyNode) {
|
|
1897
|
+
return [];
|
|
1898
|
+
}
|
|
1899
|
+
return uniqueBy(
|
|
1900
|
+
bodyNode.descendantsOfType("call_expression").filter((item) => item !== null).map((callNode) => goCalledSymbolName(callNode.childForFieldName("function") ?? callNode.namedChildren.at(0) ?? null, receiver)).filter((name) => Boolean(name)),
|
|
1901
|
+
(name) => name
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1301
1904
|
function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportLabels, diagnostics, metadata) {
|
|
1302
1905
|
const topLevelNames = new Set(draftSymbols.map((symbol) => symbol.name));
|
|
1303
1906
|
for (const symbol of draftSymbols) {
|
|
@@ -1784,7 +2387,9 @@ function goCodeAnalysis(manifest, rootNode, diagnostics) {
|
|
|
1784
2387
|
if (!name) {
|
|
1785
2388
|
continue;
|
|
1786
2389
|
}
|
|
1787
|
-
const
|
|
2390
|
+
const receiver = child.type === "method_declaration" ? goReceiverBinding(child.childForFieldName("receiver")) : {};
|
|
2391
|
+
const receiverType = receiver.typeName ?? "";
|
|
2392
|
+
const bodyNode = child.childForFieldName("body");
|
|
1788
2393
|
const symbolName = receiverType ? `${receiverType}.${name}` : name;
|
|
1789
2394
|
const exported = exportedByCapitalization(name);
|
|
1790
2395
|
draftSymbols.push({
|
|
@@ -1792,10 +2397,10 @@ function goCodeAnalysis(manifest, rootNode, diagnostics) {
|
|
|
1792
2397
|
kind: "function",
|
|
1793
2398
|
signature: singleLineSignature(child.text),
|
|
1794
2399
|
exported,
|
|
1795
|
-
callNames:
|
|
2400
|
+
callNames: goCallNamesFromBody(bodyNode, receiver),
|
|
1796
2401
|
extendsNames: [],
|
|
1797
2402
|
implementsNames: [],
|
|
1798
|
-
bodyText: nodeText(
|
|
2403
|
+
bodyText: nodeText(bodyNode)
|
|
1799
2404
|
});
|
|
1800
2405
|
if (exported) {
|
|
1801
2406
|
exportLabels.push(symbolName);
|
|
@@ -2473,11 +3078,11 @@ function isNodeExported(node) {
|
|
|
2473
3078
|
)
|
|
2474
3079
|
);
|
|
2475
3080
|
}
|
|
2476
|
-
function makeSymbolId2(
|
|
3081
|
+
function makeSymbolId2(scope, name, seen) {
|
|
2477
3082
|
const base = slugify(name);
|
|
2478
3083
|
const count = (seen.get(base) ?? 0) + 1;
|
|
2479
3084
|
seen.set(base, count);
|
|
2480
|
-
return `symbol:${
|
|
3085
|
+
return `symbol:${scope}:${count === 1 ? base : `${base}-${count}`}`;
|
|
2481
3086
|
}
|
|
2482
3087
|
function summarizeModule(manifest, code) {
|
|
2483
3088
|
const localImports = code.imports.filter((item) => !item.isExternal && !item.reExport).length;
|
|
@@ -2617,16 +3222,16 @@ function stripCodeExtension2(filePath) {
|
|
|
2617
3222
|
return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
|
|
2618
3223
|
}
|
|
2619
3224
|
function manifestModuleName2(manifest, language) {
|
|
2620
|
-
const repoPath = manifest.repoRelativePath ??
|
|
3225
|
+
const repoPath = manifest.repoRelativePath ?? path6.basename(manifest.originalPath ?? manifest.storedPath);
|
|
2621
3226
|
const normalized = toPosix(stripCodeExtension2(repoPath)).replace(/^\.\/+/, "");
|
|
2622
3227
|
if (!normalized) {
|
|
2623
3228
|
return void 0;
|
|
2624
3229
|
}
|
|
2625
3230
|
if (language === "python") {
|
|
2626
3231
|
const dotted = normalized.replace(/\/__init__$/i, "").replace(/\//g, ".").replace(/^src\./, "");
|
|
2627
|
-
return dotted ||
|
|
3232
|
+
return dotted || path6.posix.basename(normalized);
|
|
2628
3233
|
}
|
|
2629
|
-
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) ||
|
|
3234
|
+
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) || path6.posix.basename(normalized) : normalized;
|
|
2630
3235
|
}
|
|
2631
3236
|
function finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, exportLabels, diagnostics, metadata) {
|
|
2632
3237
|
const topLevelNames = new Set(draftSymbols.map((symbol) => symbol.name));
|
|
@@ -2636,8 +3241,9 @@ function finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, export
|
|
|
2636
3241
|
}
|
|
2637
3242
|
}
|
|
2638
3243
|
const seenSymbolIds = /* @__PURE__ */ new Map();
|
|
3244
|
+
const symbolScope = metadata?.namespace ? `ns:${slugify(metadata.namespace)}` : manifest.sourceId;
|
|
2639
3245
|
const symbols = draftSymbols.map((symbol) => ({
|
|
2640
|
-
id: makeSymbolId2(
|
|
3246
|
+
id: makeSymbolId2(symbolScope, symbol.name, seenSymbolIds),
|
|
2641
3247
|
name: symbol.name,
|
|
2642
3248
|
kind: symbol.kind,
|
|
2643
3249
|
signature: symbol.signature,
|
|
@@ -2919,7 +3525,7 @@ function analyzeTypeScriptLikeCode(manifest, content) {
|
|
|
2919
3525
|
};
|
|
2920
3526
|
}
|
|
2921
3527
|
function inferCodeLanguage(filePath, mimeType = "") {
|
|
2922
|
-
const extension =
|
|
3528
|
+
const extension = path6.extname(filePath).toLowerCase();
|
|
2923
3529
|
if (extension === ".ts" || extension === ".mts" || extension === ".cts") {
|
|
2924
3530
|
return "typescript";
|
|
2925
3531
|
}
|
|
@@ -2968,12 +3574,12 @@ function modulePageTitle(manifest) {
|
|
|
2968
3574
|
return `${manifest.title} module`;
|
|
2969
3575
|
}
|
|
2970
3576
|
function importResolutionCandidates(basePath, specifier, extensions) {
|
|
2971
|
-
const resolved =
|
|
2972
|
-
if (
|
|
3577
|
+
const resolved = path6.posix.normalize(path6.posix.join(path6.posix.dirname(basePath), specifier));
|
|
3578
|
+
if (path6.posix.extname(resolved)) {
|
|
2973
3579
|
return [resolved];
|
|
2974
3580
|
}
|
|
2975
|
-
const direct = extensions.map((extension) =>
|
|
2976
|
-
const indexFiles = extensions.map((extension) =>
|
|
3581
|
+
const direct = extensions.map((extension) => path6.posix.normalize(`${resolved}${extension}`));
|
|
3582
|
+
const indexFiles = extensions.map((extension) => path6.posix.normalize(path6.posix.join(resolved, `index${extension}`)));
|
|
2977
3583
|
return uniqueBy([resolved, ...direct, ...indexFiles], (candidate) => candidate);
|
|
2978
3584
|
}
|
|
2979
3585
|
function normalizeAlias(value) {
|
|
@@ -2992,32 +3598,32 @@ function recordAlias(target, value) {
|
|
|
2992
3598
|
}
|
|
2993
3599
|
function manifestBasenameWithoutExtension(manifest) {
|
|
2994
3600
|
const target = manifest.repoRelativePath ?? manifest.originalPath ?? manifest.storedPath;
|
|
2995
|
-
return
|
|
3601
|
+
return path6.posix.basename(stripCodeExtension2(normalizeAlias(target)));
|
|
2996
3602
|
}
|
|
2997
3603
|
async function readNearestGoModulePath(startPath, cache) {
|
|
2998
|
-
let current =
|
|
3604
|
+
let current = path6.resolve(startPath);
|
|
2999
3605
|
try {
|
|
3000
|
-
const stat = await
|
|
3606
|
+
const stat = await fs6.stat(current);
|
|
3001
3607
|
if (!stat.isDirectory()) {
|
|
3002
|
-
current =
|
|
3608
|
+
current = path6.dirname(current);
|
|
3003
3609
|
}
|
|
3004
3610
|
} catch {
|
|
3005
|
-
current =
|
|
3611
|
+
current = path6.dirname(current);
|
|
3006
3612
|
}
|
|
3007
3613
|
while (true) {
|
|
3008
3614
|
if (cache.has(current)) {
|
|
3009
3615
|
const cached = cache.get(current);
|
|
3010
3616
|
return cached === null ? void 0 : cached;
|
|
3011
3617
|
}
|
|
3012
|
-
const goModPath =
|
|
3013
|
-
if (await
|
|
3014
|
-
const content = await
|
|
3618
|
+
const goModPath = path6.join(current, "go.mod");
|
|
3619
|
+
if (await fs6.access(goModPath).then(() => true).catch(() => false)) {
|
|
3620
|
+
const content = await fs6.readFile(goModPath, "utf8");
|
|
3015
3621
|
const match = content.match(/^\s*module\s+(\S+)/m);
|
|
3016
3622
|
const modulePath = match?.[1]?.trim() ?? null;
|
|
3017
3623
|
cache.set(current, modulePath);
|
|
3018
3624
|
return modulePath ?? void 0;
|
|
3019
3625
|
}
|
|
3020
|
-
const parent =
|
|
3626
|
+
const parent = path6.dirname(current);
|
|
3021
3627
|
if (parent === current) {
|
|
3022
3628
|
cache.set(current, null);
|
|
3023
3629
|
return void 0;
|
|
@@ -3098,10 +3704,10 @@ async function buildCodeIndex(rootDir, manifests, analyses) {
|
|
|
3098
3704
|
if (normalizedNamespace) {
|
|
3099
3705
|
recordAlias(aliases, normalizedNamespace);
|
|
3100
3706
|
}
|
|
3101
|
-
const originalPath = manifest.originalPath ?
|
|
3707
|
+
const originalPath = manifest.originalPath ? path6.resolve(manifest.originalPath) : path6.resolve(rootDir, manifest.storedPath);
|
|
3102
3708
|
const goModulePath = await readNearestGoModulePath(originalPath, goModuleCache);
|
|
3103
3709
|
if (goModulePath && repoRelativePath) {
|
|
3104
|
-
const dir =
|
|
3710
|
+
const dir = path6.posix.dirname(repoRelativePath);
|
|
3105
3711
|
const packageAlias = dir === "." ? goModulePath : `${goModulePath}/${dir}`;
|
|
3106
3712
|
recordAlias(aliases, packageAlias);
|
|
3107
3713
|
}
|
|
@@ -3177,10 +3783,10 @@ function resolvePythonRelativeAliases(repoRelativePath, specifier) {
|
|
|
3177
3783
|
const dotMatch = specifier.match(/^\.+/);
|
|
3178
3784
|
const depth = dotMatch ? dotMatch[0].length : 0;
|
|
3179
3785
|
const relativeModule = specifier.slice(depth).replace(/\./g, "/");
|
|
3180
|
-
const baseDir =
|
|
3181
|
-
const parentDir =
|
|
3182
|
-
const moduleBase = relativeModule ?
|
|
3183
|
-
return uniqueBy([`${moduleBase}.py`,
|
|
3786
|
+
const baseDir = path6.posix.dirname(repoRelativePath);
|
|
3787
|
+
const parentDir = path6.posix.normalize(path6.posix.join(baseDir, ...Array(Math.max(depth - 1, 0)).fill("..")));
|
|
3788
|
+
const moduleBase = relativeModule ? path6.posix.join(parentDir, relativeModule) : parentDir;
|
|
3789
|
+
return uniqueBy([`${moduleBase}.py`, path6.posix.join(moduleBase, "__init__.py")], (item) => item);
|
|
3184
3790
|
}
|
|
3185
3791
|
function resolveRustAliases(manifest, specifier) {
|
|
3186
3792
|
const repoRelativePath = manifest.repoRelativePath ? normalizeAlias(manifest.repoRelativePath) : "";
|
|
@@ -3322,9 +3928,11 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
|
3322
3928
|
}
|
|
3323
3929
|
|
|
3324
3930
|
// src/extraction.ts
|
|
3325
|
-
import
|
|
3931
|
+
import fs7 from "fs/promises";
|
|
3326
3932
|
import os from "os";
|
|
3327
|
-
import
|
|
3933
|
+
import path7 from "path";
|
|
3934
|
+
import { strFromU8, unzipSync } from "fflate";
|
|
3935
|
+
import { JSDOM } from "jsdom";
|
|
3328
3936
|
import { z } from "zod";
|
|
3329
3937
|
var imageVisionExtractionSchema = z.object({
|
|
3330
3938
|
title: z.string().min(1).nullable().optional(),
|
|
@@ -3404,14 +4012,14 @@ async function materializeAttachmentPath(input) {
|
|
|
3404
4012
|
if (!input.bytes) {
|
|
3405
4013
|
throw new Error("Image extraction requires a file path or bytes.");
|
|
3406
4014
|
}
|
|
3407
|
-
const tempDir = await
|
|
4015
|
+
const tempDir = await fs7.mkdtemp(path7.join(os.tmpdir(), "swarmvault-image-extract-"));
|
|
3408
4016
|
const extension = input.mimeType.split("/")[1]?.split("+")[0] ?? "bin";
|
|
3409
|
-
const tempPath =
|
|
3410
|
-
await
|
|
4017
|
+
const tempPath = path7.join(tempDir, `source.${extension}`);
|
|
4018
|
+
await fs7.writeFile(tempPath, input.bytes);
|
|
3411
4019
|
return {
|
|
3412
4020
|
filePath: tempPath,
|
|
3413
4021
|
cleanup: async () => {
|
|
3414
|
-
await
|
|
4022
|
+
await fs7.rm(tempDir, { recursive: true, force: true });
|
|
3415
4023
|
}
|
|
3416
4024
|
};
|
|
3417
4025
|
}
|
|
@@ -3501,6 +4109,49 @@ function normalizePdfMetadata(raw) {
|
|
|
3501
4109
|
}
|
|
3502
4110
|
return Object.keys(metadata).length ? metadata : void 0;
|
|
3503
4111
|
}
|
|
4112
|
+
function normalizeDocumentText(raw) {
|
|
4113
|
+
return raw.replace(/\r\n/g, "\n").split(/\n{2,}/).map((section) => normalizeWhitespace(section)).filter(Boolean).join("\n\n").trim();
|
|
4114
|
+
}
|
|
4115
|
+
function parseDocxCoreMetadata(bytes) {
|
|
4116
|
+
try {
|
|
4117
|
+
const archive = unzipSync(new Uint8Array(bytes));
|
|
4118
|
+
const coreXml = archive["docProps/core.xml"];
|
|
4119
|
+
if (!coreXml) {
|
|
4120
|
+
return void 0;
|
|
4121
|
+
}
|
|
4122
|
+
const dom = new JSDOM(strFromU8(coreXml), { contentType: "text/xml" });
|
|
4123
|
+
const document = dom.window.document;
|
|
4124
|
+
const valuesByLocalName = /* @__PURE__ */ new Map();
|
|
4125
|
+
for (const node of Array.from(document.getElementsByTagName("*"))) {
|
|
4126
|
+
const localName = node.localName?.trim().toLowerCase();
|
|
4127
|
+
const text = normalizeWhitespace(node.textContent ?? "");
|
|
4128
|
+
if (!localName || !text || valuesByLocalName.has(localName)) {
|
|
4129
|
+
continue;
|
|
4130
|
+
}
|
|
4131
|
+
valuesByLocalName.set(localName, text);
|
|
4132
|
+
}
|
|
4133
|
+
const metadata = {};
|
|
4134
|
+
const mappings = [
|
|
4135
|
+
["title", "title"],
|
|
4136
|
+
["author", "creator"],
|
|
4137
|
+
["subject", "subject"],
|
|
4138
|
+
["description", "description"],
|
|
4139
|
+
["keywords", "keywords"],
|
|
4140
|
+
["last_modified_by", "lastmodifiedby"],
|
|
4141
|
+
["created", "created"],
|
|
4142
|
+
["modified", "modified"]
|
|
4143
|
+
];
|
|
4144
|
+
for (const [targetKey, sourceKey] of mappings) {
|
|
4145
|
+
const value = valuesByLocalName.get(sourceKey);
|
|
4146
|
+
if (value) {
|
|
4147
|
+
metadata[targetKey] = value;
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
return Object.keys(metadata).length ? metadata : void 0;
|
|
4151
|
+
} catch {
|
|
4152
|
+
return void 0;
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
3504
4155
|
async function extractPdfText(input) {
|
|
3505
4156
|
try {
|
|
3506
4157
|
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
@@ -3548,20 +4199,49 @@ async function extractPdfText(input) {
|
|
|
3548
4199
|
};
|
|
3549
4200
|
}
|
|
3550
4201
|
}
|
|
4202
|
+
async function extractDocxText(input) {
|
|
4203
|
+
try {
|
|
4204
|
+
const mammoth = await import("mammoth");
|
|
4205
|
+
const result = await mammoth.extractRawText({
|
|
4206
|
+
buffer: input.bytes
|
|
4207
|
+
});
|
|
4208
|
+
const extractedText = normalizeDocumentText(result.value);
|
|
4209
|
+
const warnings = result.messages.map((message) => normalizeWhitespace(message.message)).filter(Boolean).map((message) => truncate(message, 240));
|
|
4210
|
+
const artifact = {
|
|
4211
|
+
...extractionMetadata("docx", input.mimeType, "docx_text"),
|
|
4212
|
+
metadata: parseDocxCoreMetadata(input.bytes),
|
|
4213
|
+
warnings: warnings.length ? warnings : void 0
|
|
4214
|
+
};
|
|
4215
|
+
if (!extractedText) {
|
|
4216
|
+
artifact.warnings = [...artifact.warnings ?? [], "DOCX text extraction completed but produced no extractable text."];
|
|
4217
|
+
}
|
|
4218
|
+
return {
|
|
4219
|
+
extractedText: extractedText || void 0,
|
|
4220
|
+
artifact
|
|
4221
|
+
};
|
|
4222
|
+
} catch (error) {
|
|
4223
|
+
return {
|
|
4224
|
+
artifact: {
|
|
4225
|
+
...extractionMetadata("docx", input.mimeType, "docx_text"),
|
|
4226
|
+
warnings: [`DOCX text extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
|
|
4227
|
+
}
|
|
4228
|
+
};
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
3551
4231
|
|
|
3552
4232
|
// src/logs.ts
|
|
3553
|
-
import
|
|
3554
|
-
import
|
|
4233
|
+
import fs8 from "fs/promises";
|
|
4234
|
+
import path8 from "path";
|
|
3555
4235
|
import matter from "gray-matter";
|
|
3556
4236
|
async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
|
|
3557
4237
|
const { paths } = await initWorkspace(rootDir);
|
|
3558
4238
|
await ensureDir(paths.sessionsDir);
|
|
3559
4239
|
const timestamp = startedAt.replace(/[:.]/g, "-");
|
|
3560
4240
|
const baseName = `${timestamp}-${operation}-${slugify(title)}`;
|
|
3561
|
-
let candidate =
|
|
4241
|
+
let candidate = path8.join(paths.sessionsDir, `${baseName}.md`);
|
|
3562
4242
|
let counter = 2;
|
|
3563
4243
|
while (await fileExists(candidate)) {
|
|
3564
|
-
candidate =
|
|
4244
|
+
candidate = path8.join(paths.sessionsDir, `${baseName}-${counter}.md`);
|
|
3565
4245
|
counter++;
|
|
3566
4246
|
}
|
|
3567
4247
|
return candidate;
|
|
@@ -3569,11 +4249,11 @@ async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
|
|
|
3569
4249
|
async function appendLogEntry(rootDir, action, title, lines = []) {
|
|
3570
4250
|
const { paths } = await initWorkspace(rootDir);
|
|
3571
4251
|
await ensureDir(paths.wikiDir);
|
|
3572
|
-
const logPath =
|
|
4252
|
+
const logPath = path8.join(paths.wikiDir, "log.md");
|
|
3573
4253
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
3574
4254
|
const entry = [`## [${timestamp}] ${action} | ${title}`, ...lines.map((line) => `- ${line}`), ""].join("\n");
|
|
3575
|
-
const existing = await fileExists(logPath) ? await
|
|
3576
|
-
await
|
|
4255
|
+
const existing = await fileExists(logPath) ? await fs8.readFile(logPath, "utf8") : "# Log\n\n";
|
|
4256
|
+
await fs8.writeFile(logPath, `${existing}${entry}
|
|
3577
4257
|
`, "utf8");
|
|
3578
4258
|
}
|
|
3579
4259
|
async function recordSession(rootDir, input) {
|
|
@@ -3583,8 +4263,8 @@ async function recordSession(rootDir, input) {
|
|
|
3583
4263
|
const finishedAtIso = new Date(input.finishedAt ?? input.startedAt).toISOString();
|
|
3584
4264
|
const durationMs = Math.max(0, new Date(finishedAtIso).getTime() - new Date(startedAtIso).getTime());
|
|
3585
4265
|
const sessionPath = await resolveUniqueSessionPath(rootDir, input.operation, input.title, startedAtIso);
|
|
3586
|
-
const sessionId =
|
|
3587
|
-
const relativeSessionPath =
|
|
4266
|
+
const sessionId = path8.basename(sessionPath, ".md");
|
|
4267
|
+
const relativeSessionPath = path8.relative(rootDir, sessionPath).split(path8.sep).join(path8.posix.sep);
|
|
3588
4268
|
const frontmatter = Object.fromEntries(
|
|
3589
4269
|
Object.entries({
|
|
3590
4270
|
session_id: sessionId,
|
|
@@ -3632,7 +4312,7 @@ async function recordSession(rootDir, input) {
|
|
|
3632
4312
|
frontmatter
|
|
3633
4313
|
);
|
|
3634
4314
|
await writeFileIfChanged(sessionPath, content);
|
|
3635
|
-
const logPath =
|
|
4315
|
+
const logPath = path8.join(paths.wikiDir, "log.md");
|
|
3636
4316
|
const timestamp = startedAtIso.slice(0, 19).replace("T", " ");
|
|
3637
4317
|
const entry = [
|
|
3638
4318
|
`## [${timestamp}] ${input.operation} | ${input.title}`,
|
|
@@ -3640,8 +4320,8 @@ async function recordSession(rootDir, input) {
|
|
|
3640
4320
|
...(input.lines ?? []).map((line) => `- ${line}`),
|
|
3641
4321
|
""
|
|
3642
4322
|
].join("\n");
|
|
3643
|
-
const existing = await fileExists(logPath) ? await
|
|
3644
|
-
await
|
|
4323
|
+
const existing = await fileExists(logPath) ? await fs8.readFile(logPath, "utf8") : "# Log\n\n";
|
|
4324
|
+
await fs8.writeFile(logPath, `${existing}${entry}
|
|
3645
4325
|
`, "utf8");
|
|
3646
4326
|
return { sessionPath, sessionId };
|
|
3647
4327
|
}
|
|
@@ -3651,13 +4331,13 @@ async function appendWatchRun(rootDir, run) {
|
|
|
3651
4331
|
}
|
|
3652
4332
|
|
|
3653
4333
|
// src/source-classification.ts
|
|
3654
|
-
import
|
|
4334
|
+
import path9 from "path";
|
|
3655
4335
|
var ALL_SOURCE_CLASSES = ["first_party", "third_party", "resource", "generated"];
|
|
3656
4336
|
var THIRD_PARTY_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "vendor", "Pods"]);
|
|
3657
4337
|
var GENERATED_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", ".next", "coverage", "DerivedData", "target"]);
|
|
3658
4338
|
function matchesAnyGlob(relativePath, patterns) {
|
|
3659
4339
|
return patterns.some(
|
|
3660
|
-
(pattern) =>
|
|
4340
|
+
(pattern) => path9.matchesGlob(relativePath, pattern) || path9.matchesGlob(path9.posix.basename(relativePath), pattern)
|
|
3661
4341
|
);
|
|
3662
4342
|
}
|
|
3663
4343
|
function classifyRepoPath(relativePath, repoAnalysis) {
|
|
@@ -3710,8 +4390,8 @@ function aggregateManifestSourceClass(manifests, sourceIds) {
|
|
|
3710
4390
|
}
|
|
3711
4391
|
|
|
3712
4392
|
// src/watch-state.ts
|
|
3713
|
-
import
|
|
3714
|
-
import
|
|
4393
|
+
import fs9 from "fs/promises";
|
|
4394
|
+
import path10 from "path";
|
|
3715
4395
|
import matter2 from "gray-matter";
|
|
3716
4396
|
function pendingEntryKey(entry) {
|
|
3717
4397
|
return entry.path;
|
|
@@ -3725,7 +4405,7 @@ function normalizeRelativePath(rootDir, filePath) {
|
|
|
3725
4405
|
if (!filePath) {
|
|
3726
4406
|
return void 0;
|
|
3727
4407
|
}
|
|
3728
|
-
return toPosix(
|
|
4408
|
+
return toPosix(path10.relative(rootDir, path10.resolve(filePath)));
|
|
3729
4409
|
}
|
|
3730
4410
|
async function readPendingSemanticRefresh(rootDir) {
|
|
3731
4411
|
const { paths } = await initWorkspace(rootDir);
|
|
@@ -3819,11 +4499,11 @@ async function markPagesStaleForSources(rootDir, sourceIds) {
|
|
|
3819
4499
|
if (page.freshness !== "stale" || !page.sourceIds.some((sourceId) => affectedSourceIds.has(sourceId))) {
|
|
3820
4500
|
continue;
|
|
3821
4501
|
}
|
|
3822
|
-
const absolutePath =
|
|
4502
|
+
const absolutePath = path10.join(paths.wikiDir, page.path);
|
|
3823
4503
|
if (!await fileExists(absolutePath)) {
|
|
3824
4504
|
continue;
|
|
3825
4505
|
}
|
|
3826
|
-
const raw = await
|
|
4506
|
+
const raw = await fs9.readFile(absolutePath, "utf8");
|
|
3827
4507
|
const parsed = matter2(raw);
|
|
3828
4508
|
if (parsed.data.freshness === "stale") {
|
|
3829
4509
|
continue;
|
|
@@ -3858,6 +4538,9 @@ function inferKind(mimeType, filePath) {
|
|
|
3858
4538
|
if (mimeType === "application/pdf" || filePath.toLowerCase().endsWith(".pdf")) {
|
|
3859
4539
|
return "pdf";
|
|
3860
4540
|
}
|
|
4541
|
+
if (mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || filePath.toLowerCase().endsWith(".docx")) {
|
|
4542
|
+
return "docx";
|
|
4543
|
+
}
|
|
3861
4544
|
if (mimeType.startsWith("image/")) {
|
|
3862
4545
|
return "image";
|
|
3863
4546
|
}
|
|
@@ -3874,7 +4557,7 @@ function normalizeIngestOptions(options) {
|
|
|
3874
4557
|
return {
|
|
3875
4558
|
includeAssets: options?.includeAssets ?? true,
|
|
3876
4559
|
maxAssetSize: Math.max(0, Math.floor(options?.maxAssetSize ?? DEFAULT_MAX_ASSET_SIZE)),
|
|
3877
|
-
repoRoot: options?.repoRoot ?
|
|
4560
|
+
repoRoot: options?.repoRoot ? path11.resolve(options.repoRoot) : void 0,
|
|
3878
4561
|
include: (options?.include ?? []).map((pattern) => pattern.trim()).filter(Boolean),
|
|
3879
4562
|
exclude: (options?.exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean),
|
|
3880
4563
|
maxFiles: Math.max(1, Math.floor(options?.maxFiles ?? DEFAULT_MAX_DIRECTORY_FILES)),
|
|
@@ -3894,27 +4577,27 @@ async function resolveRepoIngestOptions(rootDir, options) {
|
|
|
3894
4577
|
}
|
|
3895
4578
|
function matchesAnyGlob2(relativePath, patterns) {
|
|
3896
4579
|
return patterns.some(
|
|
3897
|
-
(pattern) =>
|
|
4580
|
+
(pattern) => path11.matchesGlob(relativePath, pattern) || path11.matchesGlob(path11.posix.basename(relativePath), pattern)
|
|
3898
4581
|
);
|
|
3899
4582
|
}
|
|
3900
4583
|
function supportedDirectoryKind(sourceKind) {
|
|
3901
4584
|
return sourceKind !== "binary";
|
|
3902
4585
|
}
|
|
3903
4586
|
async function findNearestGitRoot2(startPath) {
|
|
3904
|
-
let current =
|
|
4587
|
+
let current = path11.resolve(startPath);
|
|
3905
4588
|
try {
|
|
3906
|
-
const stat = await
|
|
4589
|
+
const stat = await fs10.stat(current);
|
|
3907
4590
|
if (!stat.isDirectory()) {
|
|
3908
|
-
current =
|
|
4591
|
+
current = path11.dirname(current);
|
|
3909
4592
|
}
|
|
3910
4593
|
} catch {
|
|
3911
|
-
current =
|
|
4594
|
+
current = path11.dirname(current);
|
|
3912
4595
|
}
|
|
3913
4596
|
while (true) {
|
|
3914
|
-
if (await fileExists(
|
|
4597
|
+
if (await fileExists(path11.join(current, ".git"))) {
|
|
3915
4598
|
return current;
|
|
3916
4599
|
}
|
|
3917
|
-
const parent =
|
|
4600
|
+
const parent = path11.dirname(current);
|
|
3918
4601
|
if (parent === current) {
|
|
3919
4602
|
return null;
|
|
3920
4603
|
}
|
|
@@ -3922,26 +4605,26 @@ async function findNearestGitRoot2(startPath) {
|
|
|
3922
4605
|
}
|
|
3923
4606
|
}
|
|
3924
4607
|
function withinRoot(rootPath, targetPath) {
|
|
3925
|
-
const relative =
|
|
3926
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
4608
|
+
const relative = path11.relative(rootPath, targetPath);
|
|
4609
|
+
return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
|
|
3927
4610
|
}
|
|
3928
4611
|
function repoRootFromManifest(manifest) {
|
|
3929
4612
|
if (manifest.originType !== "file" || !manifest.originalPath || !manifest.repoRelativePath) {
|
|
3930
4613
|
return null;
|
|
3931
4614
|
}
|
|
3932
|
-
const repoDir =
|
|
3933
|
-
const fileDir =
|
|
4615
|
+
const repoDir = path11.posix.dirname(manifest.repoRelativePath);
|
|
4616
|
+
const fileDir = path11.dirname(path11.resolve(manifest.originalPath));
|
|
3934
4617
|
if (repoDir === "." || !repoDir) {
|
|
3935
4618
|
return fileDir;
|
|
3936
4619
|
}
|
|
3937
4620
|
const segments = repoDir.split("/").filter(Boolean);
|
|
3938
|
-
return
|
|
4621
|
+
return path11.resolve(fileDir, ...segments.map(() => ".."));
|
|
3939
4622
|
}
|
|
3940
4623
|
function repoRelativePathFor(absolutePath, repoRoot) {
|
|
3941
4624
|
if (!repoRoot || !withinRoot(repoRoot, absolutePath)) {
|
|
3942
4625
|
return void 0;
|
|
3943
4626
|
}
|
|
3944
|
-
const relative = toPosix(
|
|
4627
|
+
const relative = toPosix(path11.relative(repoRoot, absolutePath));
|
|
3945
4628
|
return relative && !relative.startsWith("..") ? relative : void 0;
|
|
3946
4629
|
}
|
|
3947
4630
|
function normalizeOriginUrl(input) {
|
|
@@ -4017,7 +4700,41 @@ function prepareCapturedMarkdownInput(input) {
|
|
|
4017
4700
|
logDetails: input.logDetails
|
|
4018
4701
|
};
|
|
4019
4702
|
}
|
|
4703
|
+
function isPrivateIp(ip) {
|
|
4704
|
+
if (ip === "::1" || ip.startsWith("fc") || ip.startsWith("fd")) return true;
|
|
4705
|
+
const parts = ip.split(".").map(Number);
|
|
4706
|
+
if (parts.length !== 4 || parts.some((p) => Number.isNaN(p))) return false;
|
|
4707
|
+
return parts[0] === 0 || parts[0] === 127 || parts[0] === 10 || parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31 || parts[0] === 192 && parts[1] === 168 || parts[0] === 169 && parts[1] === 254;
|
|
4708
|
+
}
|
|
4709
|
+
function allowPrivateUrlsForProcess() {
|
|
4710
|
+
return process.env.SWARMVAULT_ALLOW_PRIVATE_URLS === "1";
|
|
4711
|
+
}
|
|
4712
|
+
function isReservedTestHostname(hostname) {
|
|
4713
|
+
const lower = hostname.toLowerCase();
|
|
4714
|
+
return lower.endsWith(".test") || lower.endsWith(".example") || lower.endsWith(".invalid");
|
|
4715
|
+
}
|
|
4716
|
+
async function validateUrlSafety(url) {
|
|
4717
|
+
const parsed = new URL(url);
|
|
4718
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
4719
|
+
throw new Error(`Blocked protocol: ${parsed.protocol}`);
|
|
4720
|
+
}
|
|
4721
|
+
if (allowPrivateUrlsForProcess() || isReservedTestHostname(parsed.hostname)) {
|
|
4722
|
+
return;
|
|
4723
|
+
}
|
|
4724
|
+
let address;
|
|
4725
|
+
try {
|
|
4726
|
+
const { lookup } = await import("dns/promises");
|
|
4727
|
+
const result = await lookup(parsed.hostname);
|
|
4728
|
+
address = result.address;
|
|
4729
|
+
} catch {
|
|
4730
|
+
return;
|
|
4731
|
+
}
|
|
4732
|
+
if (isPrivateIp(address)) {
|
|
4733
|
+
throw new Error(`Blocked private/reserved IP ${address} (resolved from ${parsed.hostname})`);
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4020
4736
|
async function fetchText(url) {
|
|
4737
|
+
await validateUrlSafety(url);
|
|
4021
4738
|
const response = await fetch(url);
|
|
4022
4739
|
if (!response.ok) {
|
|
4023
4740
|
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
@@ -4025,6 +4742,7 @@ async function fetchText(url) {
|
|
|
4025
4742
|
return response.text();
|
|
4026
4743
|
}
|
|
4027
4744
|
async function fetchResolvedText(url) {
|
|
4745
|
+
await validateUrlSafety(url);
|
|
4028
4746
|
const response = await fetch(url);
|
|
4029
4747
|
if (!response.ok) {
|
|
4030
4748
|
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
@@ -4036,7 +4754,7 @@ async function fetchResolvedText(url) {
|
|
|
4036
4754
|
};
|
|
4037
4755
|
}
|
|
4038
4756
|
function domTextFromHtml(html, baseUrl) {
|
|
4039
|
-
const dom = new
|
|
4757
|
+
const dom = new JSDOM2(`<body>${html}</body>`, { url: baseUrl });
|
|
4040
4758
|
return normalizeWhitespace(dom.window.document.body.textContent ?? "");
|
|
4041
4759
|
}
|
|
4042
4760
|
async function captureArxivMarkdown(input, options) {
|
|
@@ -4046,7 +4764,7 @@ async function captureArxivMarkdown(input, options) {
|
|
|
4046
4764
|
}
|
|
4047
4765
|
const normalizedUrl = `https://arxiv.org/abs/${arxivId}`;
|
|
4048
4766
|
const html = await fetchText(normalizedUrl);
|
|
4049
|
-
const dom = new
|
|
4767
|
+
const dom = new JSDOM2(html, { url: normalizedUrl });
|
|
4050
4768
|
const document = dom.window.document;
|
|
4051
4769
|
const metaTitle = document.querySelector('meta[name="citation_title"]')?.getAttribute("content")?.trim();
|
|
4052
4770
|
const headingTitle = document.querySelector("h1.title")?.textContent?.trim();
|
|
@@ -4155,7 +4873,7 @@ async function captureArticleMarkdown(rootDir, input, options, extra = { sourceT
|
|
|
4155
4873
|
if (!resolved.contentType.includes("html")) {
|
|
4156
4874
|
throw new Error(`Unsupported article content type: ${resolved.contentType}`);
|
|
4157
4875
|
}
|
|
4158
|
-
const dom = new
|
|
4876
|
+
const dom = new JSDOM2(resolved.text, { url: resolved.finalUrl });
|
|
4159
4877
|
const document = dom.window.document;
|
|
4160
4878
|
const canonicalHref = document.querySelector('link[rel="canonical"]')?.getAttribute("href")?.trim();
|
|
4161
4879
|
const canonicalUrl = canonicalHref ? normalizeOriginUrl(new URL(canonicalHref, resolved.finalUrl).toString()) : resolved.finalUrl;
|
|
@@ -4238,7 +4956,7 @@ function buildCompositeHash(payloadBytes, attachments = []) {
|
|
|
4238
4956
|
return sha256(`${sha256(payloadBytes)}|${attachmentSignature}`);
|
|
4239
4957
|
}
|
|
4240
4958
|
function sanitizeAssetRelativePath(value) {
|
|
4241
|
-
const normalized =
|
|
4959
|
+
const normalized = path11.posix.normalize(value.replace(/\\/g, "/"));
|
|
4242
4960
|
const segments = normalized.split("/").filter(Boolean).map((segment) => {
|
|
4243
4961
|
if (segment === ".") {
|
|
4244
4962
|
return "";
|
|
@@ -4258,7 +4976,7 @@ function normalizeLocalReference(value) {
|
|
|
4258
4976
|
return null;
|
|
4259
4977
|
}
|
|
4260
4978
|
const lowered = candidate.toLowerCase();
|
|
4261
|
-
if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") ||
|
|
4979
|
+
if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path11.isAbsolute(candidate)) {
|
|
4262
4980
|
return null;
|
|
4263
4981
|
}
|
|
4264
4982
|
return candidate.replace(/\\/g, "/");
|
|
@@ -4274,6 +4992,22 @@ function extractMarkdownReferences(content) {
|
|
|
4274
4992
|
}
|
|
4275
4993
|
return references;
|
|
4276
4994
|
}
|
|
4995
|
+
function extractHtmlLocalReferences(html, baseUrl) {
|
|
4996
|
+
const dom = new JSDOM2(html, { url: baseUrl });
|
|
4997
|
+
const document = dom.window.document;
|
|
4998
|
+
const references = [];
|
|
4999
|
+
for (const image of [...document.querySelectorAll("img[src]")]) {
|
|
5000
|
+
const src = image.getAttribute("src");
|
|
5001
|
+
if (!src) {
|
|
5002
|
+
continue;
|
|
5003
|
+
}
|
|
5004
|
+
const normalized = normalizeLocalReference(src);
|
|
5005
|
+
if (normalized) {
|
|
5006
|
+
references.push(normalized);
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
return references;
|
|
5010
|
+
}
|
|
4277
5011
|
function normalizeRemoteReference(value, baseUrl) {
|
|
4278
5012
|
const trimmed = value.trim().replace(/^<|>$/g, "");
|
|
4279
5013
|
const [withoutTitle] = trimmed.split(/\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/, 1);
|
|
@@ -4309,7 +5043,7 @@ function extractMarkdownImageReferences(content, baseUrl) {
|
|
|
4309
5043
|
return references;
|
|
4310
5044
|
}
|
|
4311
5045
|
async function convertHtmlToMarkdown(html, url) {
|
|
4312
|
-
const dom = new
|
|
5046
|
+
const dom = new JSDOM2(html, { url });
|
|
4313
5047
|
const article = new Readability(dom.window.document).parse();
|
|
4314
5048
|
const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
|
|
4315
5049
|
const body = article?.content ?? dom.window.document.body.innerHTML;
|
|
@@ -4320,12 +5054,12 @@ async function convertHtmlToMarkdown(html, url) {
|
|
|
4320
5054
|
};
|
|
4321
5055
|
}
|
|
4322
5056
|
async function readManifestByHash(manifestsDir, contentHash) {
|
|
4323
|
-
const entries = await
|
|
5057
|
+
const entries = await fs10.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
|
|
4324
5058
|
for (const entry of entries) {
|
|
4325
5059
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
4326
5060
|
continue;
|
|
4327
5061
|
}
|
|
4328
|
-
const manifest = await readJsonFile(
|
|
5062
|
+
const manifest = await readJsonFile(path11.join(manifestsDir, entry.name));
|
|
4329
5063
|
if (manifest?.contentHash === contentHash) {
|
|
4330
5064
|
return manifest;
|
|
4331
5065
|
}
|
|
@@ -4333,12 +5067,12 @@ async function readManifestByHash(manifestsDir, contentHash) {
|
|
|
4333
5067
|
return null;
|
|
4334
5068
|
}
|
|
4335
5069
|
async function readManifestByOrigin(manifestsDir, prepared) {
|
|
4336
|
-
const entries = await
|
|
5070
|
+
const entries = await fs10.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
|
|
4337
5071
|
for (const entry of entries) {
|
|
4338
5072
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
4339
5073
|
continue;
|
|
4340
5074
|
}
|
|
4341
|
-
const manifest = await readJsonFile(
|
|
5075
|
+
const manifest = await readJsonFile(path11.join(manifestsDir, entry.name));
|
|
4342
5076
|
if (manifest && manifestMatchesOrigin(manifest, prepared)) {
|
|
4343
5077
|
return manifest;
|
|
4344
5078
|
}
|
|
@@ -4349,12 +5083,12 @@ async function loadGitignoreMatcher(repoRoot, enabled) {
|
|
|
4349
5083
|
if (!enabled) {
|
|
4350
5084
|
return null;
|
|
4351
5085
|
}
|
|
4352
|
-
const gitignorePath =
|
|
5086
|
+
const gitignorePath = path11.join(repoRoot, ".gitignore");
|
|
4353
5087
|
if (!await fileExists(gitignorePath)) {
|
|
4354
5088
|
return null;
|
|
4355
5089
|
}
|
|
4356
5090
|
const matcher = ignore();
|
|
4357
|
-
matcher.add(await
|
|
5091
|
+
matcher.add(await fs10.readFile(gitignorePath, "utf8"));
|
|
4358
5092
|
return matcher;
|
|
4359
5093
|
}
|
|
4360
5094
|
function builtInIgnoreReason(relativePath) {
|
|
@@ -4378,23 +5112,23 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
|
|
|
4378
5112
|
if (!currentDir) {
|
|
4379
5113
|
continue;
|
|
4380
5114
|
}
|
|
4381
|
-
const entries = await
|
|
5115
|
+
const entries = await fs10.readdir(currentDir, { withFileTypes: true });
|
|
4382
5116
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
4383
5117
|
for (const entry of entries) {
|
|
4384
|
-
const absolutePath =
|
|
4385
|
-
const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
5118
|
+
const absolutePath = path11.join(currentDir, entry.name);
|
|
5119
|
+
const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(inputDir, absolutePath));
|
|
4386
5120
|
const relativePath = relativeToRepo || entry.name;
|
|
4387
5121
|
const builtInReason = builtInIgnoreReason(relativePath);
|
|
4388
5122
|
if (builtInReason) {
|
|
4389
|
-
skipped.push({ path: toPosix(
|
|
5123
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: builtInReason });
|
|
4390
5124
|
continue;
|
|
4391
5125
|
}
|
|
4392
5126
|
if (matcher?.ignores(relativePath)) {
|
|
4393
|
-
skipped.push({ path: toPosix(
|
|
5127
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "gitignore" });
|
|
4394
5128
|
continue;
|
|
4395
5129
|
}
|
|
4396
5130
|
if (matchesAnyGlob2(relativePath, options.exclude)) {
|
|
4397
|
-
skipped.push({ path: toPosix(
|
|
5131
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "exclude_glob" });
|
|
4398
5132
|
continue;
|
|
4399
5133
|
}
|
|
4400
5134
|
if (entry.isDirectory()) {
|
|
@@ -4402,26 +5136,26 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
|
|
|
4402
5136
|
continue;
|
|
4403
5137
|
}
|
|
4404
5138
|
if (!entry.isFile()) {
|
|
4405
|
-
skipped.push({ path: toPosix(
|
|
5139
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
|
|
4406
5140
|
continue;
|
|
4407
5141
|
}
|
|
4408
5142
|
if (options.include.length > 0 && !matchesAnyGlob2(relativePath, options.include)) {
|
|
4409
|
-
skipped.push({ path: toPosix(
|
|
5143
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "include_glob" });
|
|
4410
5144
|
continue;
|
|
4411
5145
|
}
|
|
4412
5146
|
const mimeType = guessMimeType(absolutePath);
|
|
4413
5147
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
4414
5148
|
const sourceClass = sourceClassForRelativePath(relativePath, options);
|
|
4415
5149
|
if (!supportedDirectoryKind(sourceKind)) {
|
|
4416
|
-
skipped.push({ path: toPosix(
|
|
5150
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
4417
5151
|
continue;
|
|
4418
5152
|
}
|
|
4419
5153
|
if (!options.extractClasses.includes(sourceClass)) {
|
|
4420
|
-
skipped.push({ path: toPosix(
|
|
5154
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: `source_class:${sourceClass}` });
|
|
4421
5155
|
continue;
|
|
4422
5156
|
}
|
|
4423
5157
|
if (files.length >= options.maxFiles) {
|
|
4424
|
-
skipped.push({ path: toPosix(
|
|
5158
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "max_files" });
|
|
4425
5159
|
continue;
|
|
4426
5160
|
}
|
|
4427
5161
|
files.push(absolutePath);
|
|
@@ -4443,12 +5177,12 @@ function resolveUrlMimeType(input, response) {
|
|
|
4443
5177
|
function buildRemoteAssetRelativePath(assetUrl, mimeType) {
|
|
4444
5178
|
const url = new URL(assetUrl);
|
|
4445
5179
|
const normalized = sanitizeAssetRelativePath(`${url.hostname}${url.pathname || "/asset"}`);
|
|
4446
|
-
const extension =
|
|
4447
|
-
const directory =
|
|
4448
|
-
const basename = extension ?
|
|
5180
|
+
const extension = path11.posix.extname(normalized);
|
|
5181
|
+
const directory = path11.posix.dirname(normalized);
|
|
5182
|
+
const basename = extension ? path11.posix.basename(normalized, extension) : path11.posix.basename(normalized);
|
|
4449
5183
|
const resolvedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
|
|
4450
5184
|
const hashedName = `${basename || "asset"}-${sha256(assetUrl).slice(0, 8)}${resolvedExtension}`;
|
|
4451
|
-
return directory === "." ? hashedName :
|
|
5185
|
+
return directory === "." ? hashedName : path11.posix.join(directory, hashedName);
|
|
4452
5186
|
}
|
|
4453
5187
|
async function readResponseBytesWithinLimit(response, maxBytes) {
|
|
4454
5188
|
const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
|
|
@@ -4480,6 +5214,7 @@ async function readResponseBytesWithinLimit(response, maxBytes) {
|
|
|
4480
5214
|
return Buffer.concat(chunks);
|
|
4481
5215
|
}
|
|
4482
5216
|
async function fetchRemoteImageAttachment(assetUrl, maxAssetSize) {
|
|
5217
|
+
await validateUrlSafety(assetUrl);
|
|
4483
5218
|
const response = await fetch(assetUrl);
|
|
4484
5219
|
if (!response.ok) {
|
|
4485
5220
|
throw new Error(`failed with ${response.status} ${response.statusText}`);
|
|
@@ -4512,7 +5247,7 @@ async function collectRemoteImageAttachments(assetUrls, options) {
|
|
|
4512
5247
|
return { attachments, skippedCount };
|
|
4513
5248
|
}
|
|
4514
5249
|
function extractHtmlImageReferences(html, baseUrl) {
|
|
4515
|
-
const dom = new
|
|
5250
|
+
const dom = new JSDOM2(html, { url: baseUrl });
|
|
4516
5251
|
const document = dom.window.document;
|
|
4517
5252
|
const references = [];
|
|
4518
5253
|
for (const image of [...document.querySelectorAll("img[src]")]) {
|
|
@@ -4528,7 +5263,7 @@ function extractHtmlImageReferences(html, baseUrl) {
|
|
|
4528
5263
|
return references;
|
|
4529
5264
|
}
|
|
4530
5265
|
function rewriteHtmlImageReferences(html, baseUrl, replacements) {
|
|
4531
|
-
const dom = new
|
|
5266
|
+
const dom = new JSDOM2(html, { url: baseUrl });
|
|
4532
5267
|
const document = dom.window.document;
|
|
4533
5268
|
for (const image of [...document.querySelectorAll("img[src]")]) {
|
|
4534
5269
|
const src = image.getAttribute("src");
|
|
@@ -4543,6 +5278,22 @@ function rewriteHtmlImageReferences(html, baseUrl, replacements) {
|
|
|
4543
5278
|
}
|
|
4544
5279
|
return dom.serialize();
|
|
4545
5280
|
}
|
|
5281
|
+
function rewriteHtmlLocalReferences(html, baseUrl, replacements) {
|
|
5282
|
+
const dom = new JSDOM2(html, { url: baseUrl });
|
|
5283
|
+
const document = dom.window.document;
|
|
5284
|
+
for (const image of [...document.querySelectorAll("img[src]")]) {
|
|
5285
|
+
const src = image.getAttribute("src");
|
|
5286
|
+
if (!src) {
|
|
5287
|
+
continue;
|
|
5288
|
+
}
|
|
5289
|
+
const normalized = normalizeLocalReference(src);
|
|
5290
|
+
const replacement = normalized ? replacements.get(normalized) : void 0;
|
|
5291
|
+
if (replacement) {
|
|
5292
|
+
image.setAttribute("src", replacement);
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5295
|
+
return dom.serialize();
|
|
5296
|
+
}
|
|
4546
5297
|
function rewriteMarkdownImageReferences(content, baseUrl, replacements) {
|
|
4547
5298
|
return content.replace(/(!\[[^\]]*]\()([^)]+)(\))/g, (fullMatch, prefix, target, suffix) => {
|
|
4548
5299
|
const normalized = normalizeRemoteReference(target, baseUrl);
|
|
@@ -4584,34 +5335,34 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4584
5335
|
const previous = existingByOrigin ?? void 0;
|
|
4585
5336
|
const sourceId = previous?.sourceId ?? `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
|
|
4586
5337
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4587
|
-
const storedPath =
|
|
4588
|
-
const extractedTextPath = prepared.extractedText ?
|
|
4589
|
-
const extractedMetadataPath = prepared.extractionArtifact ?
|
|
4590
|
-
const attachmentsDir =
|
|
5338
|
+
const storedPath = path11.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
|
|
5339
|
+
const extractedTextPath = prepared.extractedText ? path11.join(paths.extractsDir, `${sourceId}.md`) : void 0;
|
|
5340
|
+
const extractedMetadataPath = prepared.extractionArtifact ? path11.join(paths.extractsDir, `${sourceId}.json`) : void 0;
|
|
5341
|
+
const attachmentsDir = path11.join(paths.rawAssetsDir, sourceId);
|
|
4591
5342
|
if (previous?.storedPath) {
|
|
4592
|
-
await
|
|
5343
|
+
await fs10.rm(path11.resolve(rootDir, previous.storedPath), { force: true });
|
|
4593
5344
|
}
|
|
4594
5345
|
if (previous?.extractedTextPath) {
|
|
4595
|
-
await
|
|
5346
|
+
await fs10.rm(path11.resolve(rootDir, previous.extractedTextPath), { force: true });
|
|
4596
5347
|
}
|
|
4597
5348
|
if (previous?.extractedMetadataPath) {
|
|
4598
|
-
await
|
|
5349
|
+
await fs10.rm(path11.resolve(rootDir, previous.extractedMetadataPath), { force: true });
|
|
4599
5350
|
}
|
|
4600
|
-
await
|
|
4601
|
-
await
|
|
5351
|
+
await fs10.rm(attachmentsDir, { recursive: true, force: true });
|
|
5352
|
+
await fs10.writeFile(storedPath, prepared.payloadBytes);
|
|
4602
5353
|
if (prepared.extractedText && extractedTextPath) {
|
|
4603
|
-
await
|
|
5354
|
+
await fs10.writeFile(extractedTextPath, prepared.extractedText, "utf8");
|
|
4604
5355
|
}
|
|
4605
5356
|
if (prepared.extractionArtifact && extractedMetadataPath) {
|
|
4606
5357
|
await writeJsonFile(extractedMetadataPath, prepared.extractionArtifact);
|
|
4607
5358
|
}
|
|
4608
5359
|
const manifestAttachments = [];
|
|
4609
5360
|
for (const attachment of attachments) {
|
|
4610
|
-
const absoluteAttachmentPath =
|
|
4611
|
-
await ensureDir(
|
|
4612
|
-
await
|
|
5361
|
+
const absoluteAttachmentPath = path11.join(attachmentsDir, attachment.relativePath);
|
|
5362
|
+
await ensureDir(path11.dirname(absoluteAttachmentPath));
|
|
5363
|
+
await fs10.writeFile(absoluteAttachmentPath, attachment.bytes);
|
|
4613
5364
|
manifestAttachments.push({
|
|
4614
|
-
path: toPosix(
|
|
5365
|
+
path: toPosix(path11.relative(rootDir, absoluteAttachmentPath)),
|
|
4615
5366
|
mimeType: attachment.mimeType,
|
|
4616
5367
|
originalPath: attachment.originalPath
|
|
4617
5368
|
});
|
|
@@ -4627,9 +5378,9 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4627
5378
|
originalPath: prepared.originalPath,
|
|
4628
5379
|
repoRelativePath: prepared.repoRelativePath,
|
|
4629
5380
|
url: prepared.url,
|
|
4630
|
-
storedPath: toPosix(
|
|
4631
|
-
extractedTextPath: extractedTextPath ? toPosix(
|
|
4632
|
-
extractedMetadataPath: extractedMetadataPath ? toPosix(
|
|
5381
|
+
storedPath: toPosix(path11.relative(rootDir, storedPath)),
|
|
5382
|
+
extractedTextPath: extractedTextPath ? toPosix(path11.relative(rootDir, extractedTextPath)) : void 0,
|
|
5383
|
+
extractedMetadataPath: extractedMetadataPath ? toPosix(path11.relative(rootDir, extractedMetadataPath)) : void 0,
|
|
4633
5384
|
extractionHash,
|
|
4634
5385
|
mimeType: prepared.mimeType,
|
|
4635
5386
|
contentHash,
|
|
@@ -4637,7 +5388,7 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4637
5388
|
updatedAt: now,
|
|
4638
5389
|
attachments: manifestAttachments.length ? manifestAttachments : void 0
|
|
4639
5390
|
};
|
|
4640
|
-
await writeJsonFile(
|
|
5391
|
+
await writeJsonFile(path11.join(paths.manifestsDir, `${sourceId}.json`), manifest);
|
|
4641
5392
|
await appendLogEntry(rootDir, "ingest", prepared.title, [
|
|
4642
5393
|
`source_id=${sourceId}`,
|
|
4643
5394
|
`kind=${prepared.sourceKind}`,
|
|
@@ -4655,16 +5406,16 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
4655
5406
|
return { manifest, isNew: !previous, wasUpdated: Boolean(previous) };
|
|
4656
5407
|
}
|
|
4657
5408
|
async function removeManifestArtifacts(rootDir, manifest, paths) {
|
|
4658
|
-
await
|
|
4659
|
-
await
|
|
5409
|
+
await fs10.rm(path11.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
|
|
5410
|
+
await fs10.rm(path11.resolve(rootDir, manifest.storedPath), { force: true });
|
|
4660
5411
|
if (manifest.extractedTextPath) {
|
|
4661
|
-
await
|
|
5412
|
+
await fs10.rm(path11.resolve(rootDir, manifest.extractedTextPath), { force: true });
|
|
4662
5413
|
}
|
|
4663
5414
|
if (manifest.extractedMetadataPath) {
|
|
4664
|
-
await
|
|
5415
|
+
await fs10.rm(path11.resolve(rootDir, manifest.extractedMetadataPath), { force: true });
|
|
4665
5416
|
}
|
|
4666
|
-
await
|
|
4667
|
-
await
|
|
5417
|
+
await fs10.rm(path11.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
|
|
5418
|
+
await fs10.rm(path11.join(paths.analysesDir, `${manifest.sourceId}.json`), { force: true });
|
|
4668
5419
|
}
|
|
4669
5420
|
function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
|
|
4670
5421
|
const candidates = [
|
|
@@ -4673,17 +5424,17 @@ function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
|
|
|
4673
5424
|
paths.stateDir,
|
|
4674
5425
|
paths.agentDir,
|
|
4675
5426
|
paths.inboxDir,
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
5427
|
+
path11.join(rootDir, ".claude"),
|
|
5428
|
+
path11.join(rootDir, ".cursor"),
|
|
5429
|
+
path11.join(rootDir, ".obsidian")
|
|
4679
5430
|
];
|
|
4680
|
-
return candidates.map((candidate) =>
|
|
5431
|
+
return candidates.map((candidate) => path11.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, candidate));
|
|
4681
5432
|
}
|
|
4682
5433
|
function preparedMatchesManifest(manifest, prepared, contentHash) {
|
|
4683
5434
|
return manifest.contentHash === contentHash && manifest.extractionHash === (prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact)) && manifest.title === prepared.title && manifest.sourceKind === prepared.sourceKind && manifest.sourceType === prepared.sourceType && manifest.sourceClass === prepared.sourceClass && manifest.language === prepared.language && manifest.mimeType === prepared.mimeType && manifest.repoRelativePath === prepared.repoRelativePath;
|
|
4684
5435
|
}
|
|
4685
5436
|
function shouldDeferWatchSemanticRefresh(sourceKind) {
|
|
4686
|
-
return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "image";
|
|
5437
|
+
return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "docx" || sourceKind === "image";
|
|
4687
5438
|
}
|
|
4688
5439
|
function pendingSemanticRefreshId(changeType, repoRoot, relativePath) {
|
|
4689
5440
|
return `pending:${changeType}:${sha256(`${toPosix(repoRoot)}:${relativePath}`).slice(0, 12)}`;
|
|
@@ -4699,16 +5450,16 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
4699
5450
|
const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
|
|
4700
5451
|
const manifests = await listManifests(rootDir);
|
|
4701
5452
|
const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
|
|
4702
|
-
(item) =>
|
|
5453
|
+
(item) => path11.resolve(item)
|
|
4703
5454
|
);
|
|
4704
5455
|
const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
|
|
4705
5456
|
const manifestsByRepoRoot = /* @__PURE__ */ new Map();
|
|
4706
5457
|
for (const manifest of manifests) {
|
|
4707
5458
|
const repoRoot = repoRootFromManifest(manifest);
|
|
4708
|
-
if (!repoRoot || !uniqueRoots.includes(
|
|
5459
|
+
if (!repoRoot || !uniqueRoots.includes(path11.resolve(repoRoot))) {
|
|
4709
5460
|
continue;
|
|
4710
5461
|
}
|
|
4711
|
-
const key =
|
|
5462
|
+
const key = path11.resolve(repoRoot);
|
|
4712
5463
|
const bucket = manifestsByRepoRoot.get(key) ?? [];
|
|
4713
5464
|
bucket.push(manifest);
|
|
4714
5465
|
manifestsByRepoRoot.set(key, bucket);
|
|
@@ -4733,14 +5484,14 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
4733
5484
|
skipped.push(
|
|
4734
5485
|
...collected.skipped,
|
|
4735
5486
|
...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
|
|
4736
|
-
path: toPosix(
|
|
5487
|
+
path: toPosix(path11.relative(rootDir, absolutePath)),
|
|
4737
5488
|
reason: "workspace_generated"
|
|
4738
5489
|
}))
|
|
4739
5490
|
);
|
|
4740
5491
|
scannedCount += files.length;
|
|
4741
|
-
const currentPaths = new Set(files.map((absolutePath) =>
|
|
5492
|
+
const currentPaths = new Set(files.map((absolutePath) => path11.resolve(absolutePath)));
|
|
4742
5493
|
for (const absolutePath of files) {
|
|
4743
|
-
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
5494
|
+
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
|
|
4744
5495
|
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
|
|
4745
5496
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
4746
5497
|
if (result.isNew) {
|
|
@@ -4750,7 +5501,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
4750
5501
|
}
|
|
4751
5502
|
}
|
|
4752
5503
|
for (const manifest of repoManifests) {
|
|
4753
|
-
const originalPath = manifest.originalPath ?
|
|
5504
|
+
const originalPath = manifest.originalPath ? path11.resolve(manifest.originalPath) : null;
|
|
4754
5505
|
if (originalPath && !currentPaths.has(originalPath)) {
|
|
4755
5506
|
await removeManifestArtifacts(rootDir, manifest, paths);
|
|
4756
5507
|
removed.push(manifest);
|
|
@@ -4758,7 +5509,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
|
4758
5509
|
}
|
|
4759
5510
|
}
|
|
4760
5511
|
if (uniqueRoots.length > 0) {
|
|
4761
|
-
await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(
|
|
5512
|
+
await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(path11.relative(rootDir, repoRoot)) || ".").join(","), [
|
|
4762
5513
|
`repo_roots=${uniqueRoots.length}`,
|
|
4763
5514
|
`scanned=${scannedCount}`,
|
|
4764
5515
|
`imported=${imported.length}`,
|
|
@@ -4781,16 +5532,16 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
4781
5532
|
const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
|
|
4782
5533
|
const manifests = await listManifests(rootDir);
|
|
4783
5534
|
const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
|
|
4784
|
-
(item) =>
|
|
5535
|
+
(item) => path11.resolve(item)
|
|
4785
5536
|
);
|
|
4786
5537
|
const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
|
|
4787
5538
|
const manifestsByRepoRoot = /* @__PURE__ */ new Map();
|
|
4788
5539
|
for (const manifest of manifests) {
|
|
4789
5540
|
const repoRoot = repoRootFromManifest(manifest);
|
|
4790
|
-
if (!repoRoot || !uniqueRoots.includes(
|
|
5541
|
+
if (!repoRoot || !uniqueRoots.includes(path11.resolve(repoRoot))) {
|
|
4791
5542
|
continue;
|
|
4792
5543
|
}
|
|
4793
|
-
const key =
|
|
5544
|
+
const key = path11.resolve(repoRoot);
|
|
4794
5545
|
const bucket = manifestsByRepoRoot.get(key) ?? [];
|
|
4795
5546
|
bucket.push(manifest);
|
|
4796
5547
|
manifestsByRepoRoot.set(key, bucket);
|
|
@@ -4805,7 +5556,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
4805
5556
|
for (const repoRoot of uniqueRoots) {
|
|
4806
5557
|
const repoManifests = manifestsByRepoRoot.get(repoRoot) ?? [];
|
|
4807
5558
|
const manifestsByOriginalPath = new Map(
|
|
4808
|
-
repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [
|
|
5559
|
+
repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [path11.resolve(manifest.originalPath), manifest])
|
|
4809
5560
|
);
|
|
4810
5561
|
if (!await fileExists(repoRoot)) {
|
|
4811
5562
|
for (const manifest of repoManifests) {
|
|
@@ -4813,7 +5564,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
4813
5564
|
pendingSemanticRefresh.push({
|
|
4814
5565
|
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? manifest.storedPath),
|
|
4815
5566
|
repoRoot,
|
|
4816
|
-
path: toPosix(
|
|
5567
|
+
path: toPosix(path11.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
|
|
4817
5568
|
changeType: "removed",
|
|
4818
5569
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4819
5570
|
sourceId: manifest.sourceId,
|
|
@@ -4833,17 +5584,17 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
4833
5584
|
skipped.push(
|
|
4834
5585
|
...collected.skipped,
|
|
4835
5586
|
...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
|
|
4836
|
-
path: toPosix(
|
|
5587
|
+
path: toPosix(path11.relative(rootDir, absolutePath)),
|
|
4837
5588
|
reason: "workspace_generated"
|
|
4838
5589
|
}))
|
|
4839
5590
|
);
|
|
4840
5591
|
scannedCount += files.length;
|
|
4841
|
-
const currentPaths = new Set(files.map((absolutePath) =>
|
|
5592
|
+
const currentPaths = new Set(files.map((absolutePath) => path11.resolve(absolutePath)));
|
|
4842
5593
|
for (const absolutePath of files) {
|
|
4843
|
-
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
5594
|
+
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
|
|
4844
5595
|
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
|
|
4845
5596
|
if (shouldDeferWatchSemanticRefresh(prepared.sourceKind)) {
|
|
4846
|
-
const existing = manifestsByOriginalPath.get(
|
|
5597
|
+
const existing = manifestsByOriginalPath.get(path11.resolve(absolutePath));
|
|
4847
5598
|
const contentHash = buildCompositeHash(prepared.payloadBytes, prepared.attachments);
|
|
4848
5599
|
const changed = !existing || !preparedMatchesManifest(existing, prepared, contentHash);
|
|
4849
5600
|
if (changed) {
|
|
@@ -4851,10 +5602,10 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
4851
5602
|
id: pendingSemanticRefreshId(
|
|
4852
5603
|
existing ? "modified" : "added",
|
|
4853
5604
|
repoRoot,
|
|
4854
|
-
prepared.repoRelativePath ?? toPosix(
|
|
5605
|
+
prepared.repoRelativePath ?? toPosix(path11.relative(repoRoot, absolutePath))
|
|
4855
5606
|
),
|
|
4856
5607
|
repoRoot,
|
|
4857
|
-
path: toPosix(
|
|
5608
|
+
path: toPosix(path11.relative(rootDir, absolutePath)),
|
|
4858
5609
|
changeType: existing ? "modified" : "added",
|
|
4859
5610
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4860
5611
|
sourceId: existing?.sourceId,
|
|
@@ -4874,13 +5625,13 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
4874
5625
|
}
|
|
4875
5626
|
}
|
|
4876
5627
|
for (const manifest of repoManifests) {
|
|
4877
|
-
const originalPath = manifest.originalPath ?
|
|
5628
|
+
const originalPath = manifest.originalPath ? path11.resolve(manifest.originalPath) : null;
|
|
4878
5629
|
if (originalPath && !currentPaths.has(originalPath)) {
|
|
4879
5630
|
if (shouldDeferWatchSemanticRefresh(manifest.sourceKind)) {
|
|
4880
5631
|
pendingSemanticRefresh.push({
|
|
4881
|
-
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(
|
|
5632
|
+
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path11.relative(repoRoot, originalPath))),
|
|
4882
5633
|
repoRoot,
|
|
4883
|
-
path: toPosix(
|
|
5634
|
+
path: toPosix(path11.relative(rootDir, originalPath)),
|
|
4884
5635
|
changeType: "removed",
|
|
4885
5636
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4886
5637
|
sourceId: manifest.sourceId,
|
|
@@ -4898,7 +5649,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
4898
5649
|
await appendLogEntry(
|
|
4899
5650
|
rootDir,
|
|
4900
5651
|
"sync_repo_watch",
|
|
4901
|
-
uniqueRoots.map((repoRoot) => toPosix(
|
|
5652
|
+
uniqueRoots.map((repoRoot) => toPosix(path11.relative(rootDir, repoRoot)) || ".").join(","),
|
|
4902
5653
|
[
|
|
4903
5654
|
`repo_roots=${uniqueRoots.length}`,
|
|
4904
5655
|
`scanned=${scannedCount}`,
|
|
@@ -4924,17 +5675,17 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
4924
5675
|
};
|
|
4925
5676
|
}
|
|
4926
5677
|
async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
4927
|
-
const payloadBytes = await
|
|
5678
|
+
const payloadBytes = await fs10.readFile(absoluteInput);
|
|
4928
5679
|
const mimeType = guessMimeType(absoluteInput);
|
|
4929
5680
|
const sourceKind = inferKind(mimeType, absoluteInput);
|
|
4930
5681
|
const language = inferCodeLanguage(absoluteInput, mimeType);
|
|
4931
|
-
const storedExtension =
|
|
5682
|
+
const storedExtension = path11.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
|
|
4932
5683
|
let title;
|
|
4933
5684
|
let extractedText;
|
|
4934
5685
|
let extractionArtifact;
|
|
4935
5686
|
if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
|
|
4936
5687
|
extractedText = payloadBytes.toString("utf8");
|
|
4937
|
-
title = titleFromText(
|
|
5688
|
+
title = titleFromText(path11.basename(absoluteInput, path11.extname(absoluteInput)), extractedText);
|
|
4938
5689
|
extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
|
|
4939
5690
|
} else if (sourceKind === "html") {
|
|
4940
5691
|
const html = payloadBytes.toString("utf8");
|
|
@@ -4943,12 +5694,18 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
|
4943
5694
|
extractedText = converted.markdown;
|
|
4944
5695
|
extractionArtifact = createHtmlReadabilityExtractionArtifact(sourceKind, mimeType);
|
|
4945
5696
|
} else if (sourceKind === "pdf") {
|
|
4946
|
-
title =
|
|
5697
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
4947
5698
|
const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
|
|
4948
5699
|
extractedText = extracted.extractedText;
|
|
4949
5700
|
extractionArtifact = extracted.artifact;
|
|
5701
|
+
} else if (sourceKind === "docx") {
|
|
5702
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
5703
|
+
const extracted = await extractDocxText({ mimeType, bytes: payloadBytes });
|
|
5704
|
+
title = extracted.artifact.metadata?.title?.trim() || title;
|
|
5705
|
+
extractedText = extracted.extractedText;
|
|
5706
|
+
extractionArtifact = extracted.artifact;
|
|
4950
5707
|
} else if (sourceKind === "image") {
|
|
4951
|
-
title =
|
|
5708
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
4952
5709
|
const extracted = await extractImageWithVision(rootDir, {
|
|
4953
5710
|
title,
|
|
4954
5711
|
mimeType,
|
|
@@ -4958,7 +5715,7 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
|
4958
5715
|
extractedText = extracted.extractedText;
|
|
4959
5716
|
extractionArtifact = extracted.artifact;
|
|
4960
5717
|
} else {
|
|
4961
|
-
title =
|
|
5718
|
+
title = path11.basename(absoluteInput, path11.extname(absoluteInput));
|
|
4962
5719
|
}
|
|
4963
5720
|
return {
|
|
4964
5721
|
title,
|
|
@@ -4977,6 +5734,7 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
|
4977
5734
|
};
|
|
4978
5735
|
}
|
|
4979
5736
|
async function prepareUrlInput(rootDir, input, options) {
|
|
5737
|
+
await validateUrlSafety(input);
|
|
4980
5738
|
const response = await fetch(input);
|
|
4981
5739
|
if (!response.ok) {
|
|
4982
5740
|
throw new Error(`Failed to fetch ${input}: ${response.status} ${response.statusText}`);
|
|
@@ -5034,7 +5792,7 @@ async function prepareUrlInput(rootDir, input, options) {
|
|
|
5034
5792
|
sourceKind = "markdown";
|
|
5035
5793
|
storedExtension = ".md";
|
|
5036
5794
|
} else {
|
|
5037
|
-
const extension =
|
|
5795
|
+
const extension = path11.extname(inputUrl.pathname);
|
|
5038
5796
|
storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
|
|
5039
5797
|
if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
|
|
5040
5798
|
extractedText = payloadBytes.toString("utf8");
|
|
@@ -5064,6 +5822,11 @@ async function prepareUrlInput(rootDir, input, options) {
|
|
|
5064
5822
|
const extracted = await extractPdfText({ mimeType, bytes: payloadBytes });
|
|
5065
5823
|
extractedText = extracted.extractedText;
|
|
5066
5824
|
extractionArtifact = extracted.artifact;
|
|
5825
|
+
} else if (sourceKind === "docx") {
|
|
5826
|
+
const extracted = await extractDocxText({ mimeType, bytes: payloadBytes });
|
|
5827
|
+
title = extracted.artifact.metadata?.title?.trim() || title;
|
|
5828
|
+
extractedText = extracted.extractedText;
|
|
5829
|
+
extractionArtifact = extracted.artifact;
|
|
5067
5830
|
} else if (sourceKind === "image") {
|
|
5068
5831
|
const extracted = await extractImageWithVision(rootDir, {
|
|
5069
5832
|
title,
|
|
@@ -5097,17 +5860,17 @@ async function collectInboxAttachmentRefs(inputDir, files) {
|
|
|
5097
5860
|
for (const absolutePath of files) {
|
|
5098
5861
|
const mimeType = guessMimeType(absolutePath);
|
|
5099
5862
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
5100
|
-
if (sourceKind !== "markdown") {
|
|
5863
|
+
if (sourceKind !== "markdown" && sourceKind !== "html") {
|
|
5101
5864
|
continue;
|
|
5102
5865
|
}
|
|
5103
|
-
const content = await
|
|
5104
|
-
const refs = extractMarkdownReferences(content);
|
|
5866
|
+
const content = await fs10.readFile(absolutePath, "utf8");
|
|
5867
|
+
const refs = sourceKind === "html" ? extractHtmlLocalReferences(content, pathToFileURL(absolutePath).toString()) : extractMarkdownReferences(content);
|
|
5105
5868
|
if (!refs.length) {
|
|
5106
5869
|
continue;
|
|
5107
5870
|
}
|
|
5108
5871
|
const sourceRefs = [];
|
|
5109
5872
|
for (const ref of refs) {
|
|
5110
|
-
const resolved =
|
|
5873
|
+
const resolved = path11.resolve(path11.dirname(absolutePath), ref);
|
|
5111
5874
|
if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
|
|
5112
5875
|
continue;
|
|
5113
5876
|
}
|
|
@@ -5140,13 +5903,52 @@ function rewriteMarkdownReferences(content, replacements) {
|
|
|
5140
5903
|
return `${prefix}${replacement}${suffix}`;
|
|
5141
5904
|
});
|
|
5142
5905
|
}
|
|
5143
|
-
async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
5144
|
-
const originalBytes = await
|
|
5145
|
-
const originalText = originalBytes.toString("utf8");
|
|
5146
|
-
const title = titleFromText(
|
|
5906
|
+
async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
5907
|
+
const originalBytes = await fs10.readFile(absolutePath);
|
|
5908
|
+
const originalText = originalBytes.toString("utf8");
|
|
5909
|
+
const title = titleFromText(path11.basename(absolutePath, path11.extname(absolutePath)), originalText);
|
|
5910
|
+
const attachments = [];
|
|
5911
|
+
for (const attachmentRef of attachmentRefs) {
|
|
5912
|
+
const bytes = await fs10.readFile(attachmentRef.absolutePath);
|
|
5913
|
+
attachments.push({
|
|
5914
|
+
relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
|
|
5915
|
+
mimeType: guessMimeType(attachmentRef.absolutePath),
|
|
5916
|
+
originalPath: toPosix(attachmentRef.absolutePath),
|
|
5917
|
+
bytes
|
|
5918
|
+
});
|
|
5919
|
+
}
|
|
5920
|
+
const contentHash = buildCompositeHash(originalBytes, attachments);
|
|
5921
|
+
const sourceId = `${slugify(title)}-${contentHash.slice(0, 8)}`;
|
|
5922
|
+
const replacements = new Map(
|
|
5923
|
+
attachmentRefs.map((attachmentRef) => [
|
|
5924
|
+
attachmentRef.relativeRef.replace(/\\/g, "/"),
|
|
5925
|
+
`../assets/${sourceId}/${sanitizeAssetRelativePath(attachmentRef.relativeRef)}`
|
|
5926
|
+
])
|
|
5927
|
+
);
|
|
5928
|
+
const rewrittenText = rewriteMarkdownReferences(originalText, replacements);
|
|
5929
|
+
const extractionArtifact = createPlainTextExtractionArtifact("markdown", "text/markdown");
|
|
5930
|
+
return {
|
|
5931
|
+
title,
|
|
5932
|
+
originType: "file",
|
|
5933
|
+
sourceKind: "markdown",
|
|
5934
|
+
originalPath: toPosix(absolutePath),
|
|
5935
|
+
mimeType: "text/markdown",
|
|
5936
|
+
storedExtension: path11.extname(absolutePath) || ".md",
|
|
5937
|
+
payloadBytes: Buffer.from(rewrittenText, "utf8"),
|
|
5938
|
+
extractedText: rewrittenText,
|
|
5939
|
+
extractionArtifact,
|
|
5940
|
+
extractionHash: buildExtractionHash(rewrittenText, extractionArtifact),
|
|
5941
|
+
attachments,
|
|
5942
|
+
contentHash
|
|
5943
|
+
};
|
|
5944
|
+
}
|
|
5945
|
+
async function prepareInboxHtmlInput(absolutePath, attachmentRefs) {
|
|
5946
|
+
const originalBytes = await fs10.readFile(absolutePath);
|
|
5947
|
+
const originalHtml = originalBytes.toString("utf8");
|
|
5948
|
+
const initialConversion = await convertHtmlToMarkdown(originalHtml, pathToFileURL(absolutePath).toString());
|
|
5147
5949
|
const attachments = [];
|
|
5148
5950
|
for (const attachmentRef of attachmentRefs) {
|
|
5149
|
-
const bytes = await
|
|
5951
|
+
const bytes = await fs10.readFile(attachmentRef.absolutePath);
|
|
5150
5952
|
attachments.push({
|
|
5151
5953
|
relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
|
|
5152
5954
|
mimeType: guessMimeType(attachmentRef.absolutePath),
|
|
@@ -5155,6 +5957,8 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
|
5155
5957
|
});
|
|
5156
5958
|
}
|
|
5157
5959
|
const contentHash = buildCompositeHash(originalBytes, attachments);
|
|
5960
|
+
const fallbackTitle = path11.basename(absolutePath, path11.extname(absolutePath));
|
|
5961
|
+
const title = initialConversion.title || fallbackTitle;
|
|
5158
5962
|
const sourceId = `${slugify(title)}-${contentHash.slice(0, 8)}`;
|
|
5159
5963
|
const replacements = new Map(
|
|
5160
5964
|
attachmentRefs.map((attachmentRef) => [
|
|
@@ -5162,31 +5966,32 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
|
5162
5966
|
`../assets/${sourceId}/${sanitizeAssetRelativePath(attachmentRef.relativeRef)}`
|
|
5163
5967
|
])
|
|
5164
5968
|
);
|
|
5165
|
-
const
|
|
5166
|
-
const
|
|
5969
|
+
const rewrittenHtml = rewriteHtmlLocalReferences(originalHtml, pathToFileURL(absolutePath).toString(), replacements);
|
|
5970
|
+
const converted = rewrittenHtml === originalHtml ? initialConversion : await convertHtmlToMarkdown(rewrittenHtml, pathToFileURL(absolutePath).toString());
|
|
5971
|
+
const extractionArtifact = createHtmlReadabilityExtractionArtifact("html", "text/html");
|
|
5167
5972
|
return {
|
|
5168
|
-
title,
|
|
5973
|
+
title: converted.title || title,
|
|
5169
5974
|
originType: "file",
|
|
5170
|
-
sourceKind: "
|
|
5975
|
+
sourceKind: "html",
|
|
5171
5976
|
originalPath: toPosix(absolutePath),
|
|
5172
|
-
mimeType: "text/
|
|
5173
|
-
storedExtension:
|
|
5174
|
-
payloadBytes: Buffer.from(
|
|
5175
|
-
extractedText:
|
|
5977
|
+
mimeType: "text/html",
|
|
5978
|
+
storedExtension: path11.extname(absolutePath) || ".html",
|
|
5979
|
+
payloadBytes: Buffer.from(rewrittenHtml, "utf8"),
|
|
5980
|
+
extractedText: converted.markdown,
|
|
5176
5981
|
extractionArtifact,
|
|
5177
|
-
extractionHash: buildExtractionHash(
|
|
5982
|
+
extractionHash: buildExtractionHash(converted.markdown, extractionArtifact),
|
|
5178
5983
|
attachments,
|
|
5179
5984
|
contentHash
|
|
5180
5985
|
};
|
|
5181
5986
|
}
|
|
5182
5987
|
function isSupportedInboxKind(sourceKind) {
|
|
5183
|
-
return ["markdown", "text", "html", "pdf", "image"].includes(sourceKind);
|
|
5988
|
+
return ["markdown", "text", "html", "pdf", "docx", "image"].includes(sourceKind);
|
|
5184
5989
|
}
|
|
5185
5990
|
async function ingestInput(rootDir, input, options) {
|
|
5186
5991
|
const { paths } = await initWorkspace(rootDir);
|
|
5187
5992
|
const normalizedOptions = normalizeIngestOptions(options);
|
|
5188
|
-
const absoluteInput =
|
|
5189
|
-
const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ??
|
|
5993
|
+
const absoluteInput = path11.resolve(rootDir, input);
|
|
5994
|
+
const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ?? path11.dirname(absoluteInput));
|
|
5190
5995
|
const prepared = isHttpUrl(input) ? await prepareUrlInput(rootDir, input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
|
|
5191
5996
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
5192
5997
|
return result.manifest;
|
|
@@ -5276,7 +6081,7 @@ async function addInput(rootDir, input, options = {}) {
|
|
|
5276
6081
|
async function ingestDirectory(rootDir, inputDir, options) {
|
|
5277
6082
|
const { paths } = await initWorkspace(rootDir);
|
|
5278
6083
|
const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
|
|
5279
|
-
const absoluteInputDir =
|
|
6084
|
+
const absoluteInputDir = path11.resolve(rootDir, inputDir);
|
|
5280
6085
|
const repoRoot = normalizedOptions.repoRoot ?? await findNearestGitRoot2(absoluteInputDir) ?? absoluteInputDir;
|
|
5281
6086
|
if (!await fileExists(absoluteInputDir)) {
|
|
5282
6087
|
throw new Error(`Directory not found: ${absoluteInputDir}`);
|
|
@@ -5285,7 +6090,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
5285
6090
|
const imported = [];
|
|
5286
6091
|
const updated = [];
|
|
5287
6092
|
for (const absolutePath of files) {
|
|
5288
|
-
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
6093
|
+
const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
|
|
5289
6094
|
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
|
|
5290
6095
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
5291
6096
|
if (result.isNew) {
|
|
@@ -5293,11 +6098,11 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
5293
6098
|
} else if (result.wasUpdated) {
|
|
5294
6099
|
updated.push(result.manifest);
|
|
5295
6100
|
} else {
|
|
5296
|
-
skipped.push({ path: toPosix(
|
|
6101
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "duplicate_content" });
|
|
5297
6102
|
}
|
|
5298
6103
|
}
|
|
5299
|
-
await appendLogEntry(rootDir, "ingest_directory", toPosix(
|
|
5300
|
-
`repo_root=${toPosix(
|
|
6104
|
+
await appendLogEntry(rootDir, "ingest_directory", toPosix(path11.relative(rootDir, absoluteInputDir)) || ".", [
|
|
6105
|
+
`repo_root=${toPosix(path11.relative(rootDir, repoRoot)) || "."}`,
|
|
5301
6106
|
`scanned=${files.length}`,
|
|
5302
6107
|
`imported=${imported.length}`,
|
|
5303
6108
|
`updated=${updated.length}`,
|
|
@@ -5314,7 +6119,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
5314
6119
|
}
|
|
5315
6120
|
async function importInbox(rootDir, inputDir) {
|
|
5316
6121
|
const { paths } = await initWorkspace(rootDir);
|
|
5317
|
-
const effectiveInputDir =
|
|
6122
|
+
const effectiveInputDir = path11.resolve(rootDir, inputDir ?? paths.inboxDir);
|
|
5318
6123
|
if (!await fileExists(effectiveInputDir)) {
|
|
5319
6124
|
throw new Error(`Inbox directory not found: ${effectiveInputDir}`);
|
|
5320
6125
|
}
|
|
@@ -5325,31 +6130,31 @@ async function importInbox(rootDir, inputDir) {
|
|
|
5325
6130
|
const skipped = [];
|
|
5326
6131
|
let attachmentCount = 0;
|
|
5327
6132
|
for (const absolutePath of files) {
|
|
5328
|
-
const basename =
|
|
6133
|
+
const basename = path11.basename(absolutePath);
|
|
5329
6134
|
if (basename.startsWith(".")) {
|
|
5330
|
-
skipped.push({ path: toPosix(
|
|
6135
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "hidden_file" });
|
|
5331
6136
|
continue;
|
|
5332
6137
|
}
|
|
5333
6138
|
if (claimedAttachments.has(absolutePath)) {
|
|
5334
|
-
skipped.push({ path: toPosix(
|
|
6139
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
|
|
5335
6140
|
continue;
|
|
5336
6141
|
}
|
|
5337
6142
|
const mimeType = guessMimeType(absolutePath);
|
|
5338
6143
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
5339
6144
|
if (!isSupportedInboxKind(sourceKind)) {
|
|
5340
|
-
skipped.push({ path: toPosix(
|
|
6145
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
5341
6146
|
continue;
|
|
5342
6147
|
}
|
|
5343
|
-
const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
|
|
6148
|
+
const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : sourceKind === "html" && refsBySource.has(absolutePath) ? await prepareInboxHtmlInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
|
|
5344
6149
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
5345
6150
|
if (!result.isNew) {
|
|
5346
|
-
skipped.push({ path: toPosix(
|
|
6151
|
+
skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "duplicate_content" });
|
|
5347
6152
|
continue;
|
|
5348
6153
|
}
|
|
5349
6154
|
attachmentCount += result.manifest.attachments?.length ?? 0;
|
|
5350
6155
|
imported.push(result.manifest);
|
|
5351
6156
|
}
|
|
5352
|
-
await appendLogEntry(rootDir, "inbox_import", toPosix(
|
|
6157
|
+
await appendLogEntry(rootDir, "inbox_import", toPosix(path11.relative(rootDir, effectiveInputDir)) || ".", [
|
|
5353
6158
|
`scanned=${files.length}`,
|
|
5354
6159
|
`imported=${imported.length}`,
|
|
5355
6160
|
`attachments=${attachmentCount}`,
|
|
@@ -5368,9 +6173,9 @@ async function listManifests(rootDir) {
|
|
|
5368
6173
|
if (!await fileExists(paths.manifestsDir)) {
|
|
5369
6174
|
return [];
|
|
5370
6175
|
}
|
|
5371
|
-
const entries = await
|
|
6176
|
+
const entries = await fs10.readdir(paths.manifestsDir);
|
|
5372
6177
|
const manifests = await Promise.all(
|
|
5373
|
-
entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(
|
|
6178
|
+
entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path11.join(paths.manifestsDir, entry)))
|
|
5374
6179
|
);
|
|
5375
6180
|
return manifests.filter((manifest) => Boolean(manifest));
|
|
5376
6181
|
}
|
|
@@ -5378,17 +6183,17 @@ async function readExtractedText(rootDir, manifest) {
|
|
|
5378
6183
|
if (!manifest.extractedTextPath) {
|
|
5379
6184
|
return void 0;
|
|
5380
6185
|
}
|
|
5381
|
-
const absolutePath =
|
|
6186
|
+
const absolutePath = path11.resolve(rootDir, manifest.extractedTextPath);
|
|
5382
6187
|
if (!await fileExists(absolutePath)) {
|
|
5383
6188
|
return void 0;
|
|
5384
6189
|
}
|
|
5385
|
-
return
|
|
6190
|
+
return fs10.readFile(absolutePath, "utf8");
|
|
5386
6191
|
}
|
|
5387
6192
|
async function readExtractionArtifact(rootDir, manifest) {
|
|
5388
6193
|
if (!manifest.extractedMetadataPath) {
|
|
5389
6194
|
return void 0;
|
|
5390
6195
|
}
|
|
5391
|
-
const absolutePath =
|
|
6196
|
+
const absolutePath = path11.resolve(rootDir, manifest.extractedMetadataPath);
|
|
5392
6197
|
if (!await fileExists(absolutePath)) {
|
|
5393
6198
|
return void 0;
|
|
5394
6199
|
}
|
|
@@ -5396,20 +6201,20 @@ async function readExtractionArtifact(rootDir, manifest) {
|
|
|
5396
6201
|
}
|
|
5397
6202
|
|
|
5398
6203
|
// src/mcp.ts
|
|
5399
|
-
import
|
|
5400
|
-
import
|
|
6204
|
+
import fs18 from "fs/promises";
|
|
6205
|
+
import path22 from "path";
|
|
5401
6206
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5402
6207
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5403
6208
|
import { z as z8 } from "zod";
|
|
5404
6209
|
|
|
5405
6210
|
// src/schema.ts
|
|
5406
|
-
import
|
|
5407
|
-
import
|
|
6211
|
+
import fs11 from "fs/promises";
|
|
6212
|
+
import path12 from "path";
|
|
5408
6213
|
function normalizeSchemaContent(content) {
|
|
5409
6214
|
return content.trim() ? content.trim() : defaultVaultSchema().trim();
|
|
5410
6215
|
}
|
|
5411
6216
|
async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
|
|
5412
|
-
const content = await fileExists(schemaPath) ? await
|
|
6217
|
+
const content = await fileExists(schemaPath) ? await fs11.readFile(schemaPath, "utf8") : fallback;
|
|
5413
6218
|
const normalized = normalizeSchemaContent(content);
|
|
5414
6219
|
return {
|
|
5415
6220
|
path: schemaPath,
|
|
@@ -5418,7 +6223,7 @@ async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
|
|
|
5418
6223
|
};
|
|
5419
6224
|
}
|
|
5420
6225
|
function resolveProjectSchemaPath(rootDir, schemaPath) {
|
|
5421
|
-
return
|
|
6226
|
+
return path12.resolve(rootDir, schemaPath);
|
|
5422
6227
|
}
|
|
5423
6228
|
function composeVaultSchema(root, projectSchemas = []) {
|
|
5424
6229
|
if (!projectSchemas.length) {
|
|
@@ -5434,7 +6239,7 @@ function composeVaultSchema(root, projectSchemas = []) {
|
|
|
5434
6239
|
(schema) => [
|
|
5435
6240
|
`## Project Schema`,
|
|
5436
6241
|
"",
|
|
5437
|
-
`Path: ${toPosix(
|
|
6242
|
+
`Path: ${toPosix(path12.relative(path12.dirname(root.path), schema.path) || schema.path)}`,
|
|
5438
6243
|
"",
|
|
5439
6244
|
schema.content
|
|
5440
6245
|
].join("\n")
|
|
@@ -5510,13 +6315,13 @@ function buildSchemaPrompt(schema, instruction) {
|
|
|
5510
6315
|
}
|
|
5511
6316
|
|
|
5512
6317
|
// src/vault.ts
|
|
5513
|
-
import
|
|
5514
|
-
import
|
|
6318
|
+
import fs17 from "fs/promises";
|
|
6319
|
+
import path21 from "path";
|
|
5515
6320
|
import matter9 from "gray-matter";
|
|
5516
6321
|
import { z as z7 } from "zod";
|
|
5517
6322
|
|
|
5518
6323
|
// src/analysis.ts
|
|
5519
|
-
import
|
|
6324
|
+
import path13 from "path";
|
|
5520
6325
|
import { z as z2 } from "zod";
|
|
5521
6326
|
var ANALYSIS_FORMAT_VERSION = 5;
|
|
5522
6327
|
var sourceAnalysisSchema = z2.object({
|
|
@@ -5696,276 +6501,119 @@ ${truncate(text, 18e3)}`
|
|
|
5696
6501
|
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5697
6502
|
};
|
|
5698
6503
|
}
|
|
5699
|
-
function analysisFromVisionExtraction(manifest, extraction, schemaHash) {
|
|
5700
|
-
if (!extraction.vision) {
|
|
5701
|
-
return null;
|
|
5702
|
-
}
|
|
5703
|
-
return {
|
|
5704
|
-
analysisVersion: ANALYSIS_FORMAT_VERSION,
|
|
5705
|
-
sourceId: manifest.sourceId,
|
|
5706
|
-
sourceHash: manifest.contentHash,
|
|
5707
|
-
extractionHash: manifest.extractionHash,
|
|
5708
|
-
schemaHash,
|
|
5709
|
-
title: extraction.vision.title?.trim() || manifest.title,
|
|
5710
|
-
summary: extraction.vision.summary,
|
|
5711
|
-
concepts: extraction.vision.concepts.map((term) => ({
|
|
5712
|
-
id: `concept:${slugify(term.name)}`,
|
|
5713
|
-
name: term.name,
|
|
5714
|
-
description: term.description
|
|
5715
|
-
})),
|
|
5716
|
-
entities: extraction.vision.entities.map((term) => ({
|
|
5717
|
-
id: `entity:${slugify(term.name)}`,
|
|
5718
|
-
name: term.name,
|
|
5719
|
-
description: term.description
|
|
5720
|
-
})),
|
|
5721
|
-
claims: extraction.vision.claims.map((claim, index) => ({
|
|
5722
|
-
id: `claim:${manifest.sourceId}:${index + 1}`,
|
|
5723
|
-
text: claim.text,
|
|
5724
|
-
confidence: claim.confidence,
|
|
5725
|
-
status: "extracted",
|
|
5726
|
-
polarity: claim.polarity,
|
|
5727
|
-
citation: manifest.sourceId
|
|
5728
|
-
})),
|
|
5729
|
-
questions: extraction.vision.questions,
|
|
5730
|
-
rationales: [],
|
|
5731
|
-
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5732
|
-
};
|
|
5733
|
-
}
|
|
5734
|
-
function extractionWarningSummary(manifest, extraction) {
|
|
5735
|
-
const warning = extraction?.warnings?.find(Boolean);
|
|
5736
|
-
if (warning) {
|
|
5737
|
-
return `Imported ${manifest.sourceKind} source. ${warning}`;
|
|
5738
|
-
}
|
|
5739
|
-
return `Imported ${manifest.sourceKind} source. Text extraction is not yet available for this source.`;
|
|
5740
|
-
}
|
|
5741
|
-
async function analyzeSource(manifest, extractedText, provider, paths, schema) {
|
|
5742
|
-
const cachePath = path12.join(paths.analysesDir, `${manifest.sourceId}.json`);
|
|
5743
|
-
const cached = await readJsonFile(cachePath);
|
|
5744
|
-
if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
|
|
5745
|
-
return cached;
|
|
5746
|
-
}
|
|
5747
|
-
const extraction = await readExtractionArtifact(paths.rootDir, manifest);
|
|
5748
|
-
const content = normalizeWhitespace(extractedText ?? "");
|
|
5749
|
-
let analysis;
|
|
5750
|
-
if (manifest.sourceKind === "code" && content) {
|
|
5751
|
-
analysis = await analyzeCodeSource(manifest, extractedText ?? "", schema.hash);
|
|
5752
|
-
} else if (manifest.sourceKind === "image") {
|
|
5753
|
-
const visionAnalysis = extraction ? analysisFromVisionExtraction(manifest, extraction, schema.hash) : null;
|
|
5754
|
-
if (visionAnalysis) {
|
|
5755
|
-
analysis = visionAnalysis;
|
|
5756
|
-
} else if (!content) {
|
|
5757
|
-
analysis = {
|
|
5758
|
-
analysisVersion: ANALYSIS_FORMAT_VERSION,
|
|
5759
|
-
sourceId: manifest.sourceId,
|
|
5760
|
-
sourceHash: manifest.contentHash,
|
|
5761
|
-
extractionHash: manifest.extractionHash,
|
|
5762
|
-
schemaHash: schema.hash,
|
|
5763
|
-
title: manifest.title,
|
|
5764
|
-
summary: extractionWarningSummary(manifest, extraction),
|
|
5765
|
-
concepts: [],
|
|
5766
|
-
entities: [],
|
|
5767
|
-
claims: [],
|
|
5768
|
-
questions: [],
|
|
5769
|
-
rationales: [],
|
|
5770
|
-
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5771
|
-
};
|
|
5772
|
-
} else if (provider.type === "heuristic") {
|
|
5773
|
-
analysis = heuristicAnalysis(manifest, content, schema.hash);
|
|
5774
|
-
} else {
|
|
5775
|
-
try {
|
|
5776
|
-
analysis = await providerAnalysis(manifest, content, provider, schema);
|
|
5777
|
-
} catch {
|
|
5778
|
-
analysis = heuristicAnalysis(manifest, content, schema.hash);
|
|
5779
|
-
}
|
|
5780
|
-
}
|
|
5781
|
-
} else if (!content) {
|
|
5782
|
-
analysis = {
|
|
5783
|
-
analysisVersion: ANALYSIS_FORMAT_VERSION,
|
|
5784
|
-
sourceId: manifest.sourceId,
|
|
5785
|
-
sourceHash: manifest.contentHash,
|
|
5786
|
-
extractionHash: manifest.extractionHash,
|
|
5787
|
-
schemaHash: schema.hash,
|
|
5788
|
-
title: manifest.title,
|
|
5789
|
-
summary: extractionWarningSummary(manifest, extraction),
|
|
5790
|
-
concepts: [],
|
|
5791
|
-
entities: [],
|
|
5792
|
-
claims: [],
|
|
5793
|
-
questions: [],
|
|
5794
|
-
rationales: [],
|
|
5795
|
-
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5796
|
-
};
|
|
5797
|
-
} else if (provider.type === "heuristic") {
|
|
5798
|
-
analysis = heuristicAnalysis(manifest, content, schema.hash);
|
|
5799
|
-
} else {
|
|
5800
|
-
try {
|
|
5801
|
-
analysis = await providerAnalysis(manifest, content, provider, schema);
|
|
5802
|
-
} catch {
|
|
5803
|
-
analysis = heuristicAnalysis(manifest, content, schema.hash);
|
|
5804
|
-
}
|
|
5805
|
-
}
|
|
5806
|
-
await writeJsonFile(cachePath, analysis);
|
|
5807
|
-
return analysis;
|
|
5808
|
-
}
|
|
5809
|
-
function analysisSignature(analysis) {
|
|
5810
|
-
return sha256(JSON.stringify(analysis));
|
|
5811
|
-
}
|
|
5812
|
-
|
|
5813
|
-
// src/benchmark.ts
|
|
5814
|
-
var CHARS_PER_TOKEN = 4;
|
|
5815
|
-
var DEFAULT_BENCHMARK_QUESTIONS = [
|
|
5816
|
-
"How does this vault connect the main concepts?",
|
|
5817
|
-
"Which pages bridge the biggest communities?",
|
|
5818
|
-
"What are the core abstractions in this vault?",
|
|
5819
|
-
"Where are the biggest knowledge gaps?",
|
|
5820
|
-
"What evidence should I read first?"
|
|
5821
|
-
];
|
|
5822
|
-
var RESEARCH_BENCHMARK_QUESTION = "Which research sources should I read first, and why?";
|
|
5823
|
-
function nodeMap(graph) {
|
|
5824
|
-
return new Map(graph.nodes.map((node) => [node.id, node]));
|
|
5825
|
-
}
|
|
5826
|
-
function pageMap(graph) {
|
|
5827
|
-
return new Map(graph.pages.map((page) => [page.id, page]));
|
|
5828
|
-
}
|
|
5829
|
-
function estimateTokens(text) {
|
|
5830
|
-
return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));
|
|
5831
|
-
}
|
|
5832
|
-
function estimateCorpusWords(texts) {
|
|
5833
|
-
return texts.reduce((total, text) => total + normalizeWhitespace(text).split(/\s+/).filter(Boolean).length, 0);
|
|
5834
|
-
}
|
|
5835
|
-
function benchmarkQueryTokens(graph, queryResult, pageContentsById) {
|
|
5836
|
-
const nodesById = nodeMap(graph);
|
|
5837
|
-
const pagesById = pageMap(graph);
|
|
5838
|
-
const edgeIds = new Set(queryResult.visitedEdgeIds);
|
|
5839
|
-
const lines = [];
|
|
5840
|
-
for (const pageId of queryResult.pageIds) {
|
|
5841
|
-
const page = pagesById.get(pageId);
|
|
5842
|
-
if (!page) {
|
|
5843
|
-
continue;
|
|
5844
|
-
}
|
|
5845
|
-
const content = normalizeWhitespace(pageContentsById.get(pageId) ?? "").slice(0, 280);
|
|
5846
|
-
lines.push(`PAGE ${page.title} path=${page.path} kind=${page.kind}`);
|
|
5847
|
-
if (content) {
|
|
5848
|
-
lines.push(`PAGE_BODY ${content}`);
|
|
5849
|
-
}
|
|
5850
|
-
}
|
|
5851
|
-
for (const nodeId of queryResult.visitedNodeIds) {
|
|
5852
|
-
const node = nodesById.get(nodeId);
|
|
5853
|
-
if (!node) {
|
|
5854
|
-
continue;
|
|
5855
|
-
}
|
|
5856
|
-
lines.push(`NODE ${node.label} type=${node.type} community=${node.communityId ?? "unassigned"} page=${node.pageId ?? "none"}`);
|
|
5857
|
-
}
|
|
5858
|
-
for (const edge of graph.edges) {
|
|
5859
|
-
if (!edgeIds.has(edge.id)) {
|
|
5860
|
-
continue;
|
|
5861
|
-
}
|
|
5862
|
-
const source = nodesById.get(edge.source)?.label ?? edge.source;
|
|
5863
|
-
const target = nodesById.get(edge.target)?.label ?? edge.target;
|
|
5864
|
-
lines.push(`EDGE ${source} --${edge.relation}/${edge.evidenceClass}/${edge.confidence.toFixed(2)}--> ${target}`);
|
|
5865
|
-
}
|
|
5866
|
-
const queryTokens = estimateTokens(lines.join("\n"));
|
|
5867
|
-
return {
|
|
5868
|
-
question: queryResult.question,
|
|
5869
|
-
queryTokens,
|
|
5870
|
-
reduction: 0,
|
|
5871
|
-
visitedNodeIds: queryResult.visitedNodeIds,
|
|
5872
|
-
visitedEdgeIds: queryResult.visitedEdgeIds,
|
|
5873
|
-
pageIds: queryResult.pageIds
|
|
5874
|
-
};
|
|
5875
|
-
}
|
|
5876
|
-
function graphHash(graph) {
|
|
5877
|
-
const hashedPages = graph.pages.filter((page) => page.kind !== "graph_report" && page.kind !== "community_summary");
|
|
5878
|
-
const normalized = JSON.stringify(
|
|
5879
|
-
{
|
|
5880
|
-
nodes: [...graph.nodes].map((node) => ({
|
|
5881
|
-
id: node.id,
|
|
5882
|
-
type: node.type,
|
|
5883
|
-
label: node.label,
|
|
5884
|
-
pageId: node.pageId ?? null,
|
|
5885
|
-
sourceClass: node.sourceClass ?? null,
|
|
5886
|
-
communityId: node.communityId ?? null,
|
|
5887
|
-
degree: node.degree ?? null,
|
|
5888
|
-
bridgeScore: node.bridgeScore ?? null,
|
|
5889
|
-
isGodNode: node.isGodNode ?? false,
|
|
5890
|
-
sourceIds: [...node.sourceIds].sort(),
|
|
5891
|
-
projectIds: [...node.projectIds].sort()
|
|
5892
|
-
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
5893
|
-
edges: [...graph.edges].map((edge) => ({
|
|
5894
|
-
id: edge.id,
|
|
5895
|
-
source: edge.source,
|
|
5896
|
-
target: edge.target,
|
|
5897
|
-
relation: edge.relation,
|
|
5898
|
-
status: edge.status,
|
|
5899
|
-
evidenceClass: edge.evidenceClass,
|
|
5900
|
-
similarityBasis: edge.similarityBasis ?? null,
|
|
5901
|
-
confidence: edge.confidence,
|
|
5902
|
-
provenance: [...edge.provenance].sort()
|
|
5903
|
-
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
5904
|
-
pages: [...hashedPages].map((page) => ({
|
|
5905
|
-
id: page.id,
|
|
5906
|
-
path: page.path,
|
|
5907
|
-
kind: page.kind,
|
|
5908
|
-
status: page.status,
|
|
5909
|
-
sourceType: page.sourceType ?? null,
|
|
5910
|
-
sourceClass: page.sourceClass ?? null,
|
|
5911
|
-
sourceIds: [...page.sourceIds].sort(),
|
|
5912
|
-
projectIds: [...page.projectIds].sort(),
|
|
5913
|
-
nodeIds: [...page.nodeIds].sort()
|
|
5914
|
-
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
5915
|
-
communities: [...graph.communities ?? []].map((community) => ({
|
|
5916
|
-
id: community.id,
|
|
5917
|
-
label: community.label,
|
|
5918
|
-
nodeIds: [...community.nodeIds].sort()
|
|
5919
|
-
})).sort((left, right) => left.id.localeCompare(right.id))
|
|
5920
|
-
},
|
|
5921
|
-
null,
|
|
5922
|
-
0
|
|
5923
|
-
);
|
|
5924
|
-
return sha256(normalized);
|
|
5925
|
-
}
|
|
5926
|
-
function hasResearchSources(pages) {
|
|
5927
|
-
return pages.some((page) => page.kind === "source" && Boolean(page.sourceType) && page.sourceType !== "url");
|
|
5928
|
-
}
|
|
5929
|
-
function defaultBenchmarkQuestionsForGraph(graph, maxQuestions = 3) {
|
|
5930
|
-
const normalizedLimit = Math.max(1, Math.min(maxQuestions, DEFAULT_BENCHMARK_QUESTIONS.length));
|
|
5931
|
-
const questions = [...DEFAULT_BENCHMARK_QUESTIONS];
|
|
5932
|
-
if (hasResearchSources(graph.pages)) {
|
|
5933
|
-
questions.unshift(RESEARCH_BENCHMARK_QUESTION);
|
|
6504
|
+
function analysisFromVisionExtraction(manifest, extraction, schemaHash) {
|
|
6505
|
+
if (!extraction.vision) {
|
|
6506
|
+
return null;
|
|
5934
6507
|
}
|
|
5935
|
-
return uniqueBy(questions, (item) => item).slice(0, normalizedLimit);
|
|
5936
|
-
}
|
|
5937
|
-
function buildBenchmarkArtifact(input) {
|
|
5938
|
-
const corpusTokens = Math.max(1, Math.round(input.corpusWords * (100 / 75)));
|
|
5939
|
-
const perQuestion = input.perQuestion.filter((entry) => entry.queryTokens > 0).map((entry) => ({
|
|
5940
|
-
...entry,
|
|
5941
|
-
reduction: Number(Math.max(0, 1 - entry.queryTokens / Math.max(1, corpusTokens)).toFixed(3))
|
|
5942
|
-
}));
|
|
5943
|
-
const avgQueryTokens = perQuestion.length ? Math.max(1, Math.round(perQuestion.reduce((total, entry) => total + entry.queryTokens, 0) / perQuestion.length)) : 0;
|
|
5944
|
-
const reductionRatio = avgQueryTokens ? Number(Math.max(0, 1 - avgQueryTokens / Math.max(1, corpusTokens)).toFixed(3)) : 0;
|
|
5945
|
-
const uniqueVisitedNodes = new Set(perQuestion.flatMap((entry) => entry.visitedNodeIds)).size;
|
|
5946
|
-
const summary = {
|
|
5947
|
-
questionCount: input.questions.length,
|
|
5948
|
-
uniqueVisitedNodes,
|
|
5949
|
-
finalContextTokens: avgQueryTokens,
|
|
5950
|
-
naiveCorpusTokens: corpusTokens,
|
|
5951
|
-
avgReduction: reductionRatio,
|
|
5952
|
-
reductionRatio
|
|
5953
|
-
};
|
|
5954
6508
|
return {
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
6509
|
+
analysisVersion: ANALYSIS_FORMAT_VERSION,
|
|
6510
|
+
sourceId: manifest.sourceId,
|
|
6511
|
+
sourceHash: manifest.contentHash,
|
|
6512
|
+
extractionHash: manifest.extractionHash,
|
|
6513
|
+
schemaHash,
|
|
6514
|
+
title: extraction.vision.title?.trim() || manifest.title,
|
|
6515
|
+
summary: extraction.vision.summary,
|
|
6516
|
+
concepts: extraction.vision.concepts.map((term) => ({
|
|
6517
|
+
id: `concept:${slugify(term.name)}`,
|
|
6518
|
+
name: term.name,
|
|
6519
|
+
description: term.description
|
|
6520
|
+
})),
|
|
6521
|
+
entities: extraction.vision.entities.map((term) => ({
|
|
6522
|
+
id: `entity:${slugify(term.name)}`,
|
|
6523
|
+
name: term.name,
|
|
6524
|
+
description: term.description
|
|
6525
|
+
})),
|
|
6526
|
+
claims: extraction.vision.claims.map((claim, index) => ({
|
|
6527
|
+
id: `claim:${manifest.sourceId}:${index + 1}`,
|
|
6528
|
+
text: claim.text,
|
|
6529
|
+
confidence: claim.confidence,
|
|
6530
|
+
status: "extracted",
|
|
6531
|
+
polarity: claim.polarity,
|
|
6532
|
+
citation: manifest.sourceId
|
|
6533
|
+
})),
|
|
6534
|
+
questions: extraction.vision.questions,
|
|
6535
|
+
rationales: [],
|
|
6536
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5967
6537
|
};
|
|
5968
6538
|
}
|
|
6539
|
+
function extractionWarningSummary(manifest, extraction) {
|
|
6540
|
+
const warning = extraction?.warnings?.find(Boolean);
|
|
6541
|
+
if (warning) {
|
|
6542
|
+
return `Imported ${manifest.sourceKind} source. ${warning}`;
|
|
6543
|
+
}
|
|
6544
|
+
return `Imported ${manifest.sourceKind} source. Text extraction is not yet available for this source.`;
|
|
6545
|
+
}
|
|
6546
|
+
async function analyzeSource(manifest, extractedText, provider, paths, schema) {
|
|
6547
|
+
const cachePath = path13.join(paths.analysesDir, `${manifest.sourceId}.json`);
|
|
6548
|
+
const cached = await readJsonFile(cachePath);
|
|
6549
|
+
if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.extractionHash === manifest.extractionHash && cached.schemaHash === schema.hash) {
|
|
6550
|
+
return cached;
|
|
6551
|
+
}
|
|
6552
|
+
const extraction = await readExtractionArtifact(paths.rootDir, manifest);
|
|
6553
|
+
const content = normalizeWhitespace(extractedText ?? "");
|
|
6554
|
+
let analysis;
|
|
6555
|
+
if (manifest.sourceKind === "code" && content) {
|
|
6556
|
+
analysis = await analyzeCodeSource(manifest, extractedText ?? "", schema.hash);
|
|
6557
|
+
} else if (manifest.sourceKind === "image") {
|
|
6558
|
+
const visionAnalysis = extraction ? analysisFromVisionExtraction(manifest, extraction, schema.hash) : null;
|
|
6559
|
+
if (visionAnalysis) {
|
|
6560
|
+
analysis = visionAnalysis;
|
|
6561
|
+
} else if (!content) {
|
|
6562
|
+
analysis = {
|
|
6563
|
+
analysisVersion: ANALYSIS_FORMAT_VERSION,
|
|
6564
|
+
sourceId: manifest.sourceId,
|
|
6565
|
+
sourceHash: manifest.contentHash,
|
|
6566
|
+
extractionHash: manifest.extractionHash,
|
|
6567
|
+
schemaHash: schema.hash,
|
|
6568
|
+
title: manifest.title,
|
|
6569
|
+
summary: extractionWarningSummary(manifest, extraction),
|
|
6570
|
+
concepts: [],
|
|
6571
|
+
entities: [],
|
|
6572
|
+
claims: [],
|
|
6573
|
+
questions: [],
|
|
6574
|
+
rationales: [],
|
|
6575
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6576
|
+
};
|
|
6577
|
+
} else if (provider.type === "heuristic") {
|
|
6578
|
+
analysis = heuristicAnalysis(manifest, content, schema.hash);
|
|
6579
|
+
} else {
|
|
6580
|
+
try {
|
|
6581
|
+
analysis = await providerAnalysis(manifest, content, provider, schema);
|
|
6582
|
+
} catch {
|
|
6583
|
+
analysis = heuristicAnalysis(manifest, content, schema.hash);
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6586
|
+
} else if (!content) {
|
|
6587
|
+
analysis = {
|
|
6588
|
+
analysisVersion: ANALYSIS_FORMAT_VERSION,
|
|
6589
|
+
sourceId: manifest.sourceId,
|
|
6590
|
+
sourceHash: manifest.contentHash,
|
|
6591
|
+
extractionHash: manifest.extractionHash,
|
|
6592
|
+
schemaHash: schema.hash,
|
|
6593
|
+
title: manifest.title,
|
|
6594
|
+
summary: extractionWarningSummary(manifest, extraction),
|
|
6595
|
+
concepts: [],
|
|
6596
|
+
entities: [],
|
|
6597
|
+
claims: [],
|
|
6598
|
+
questions: [],
|
|
6599
|
+
rationales: [],
|
|
6600
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6601
|
+
};
|
|
6602
|
+
} else if (provider.type === "heuristic") {
|
|
6603
|
+
analysis = heuristicAnalysis(manifest, content, schema.hash);
|
|
6604
|
+
} else {
|
|
6605
|
+
try {
|
|
6606
|
+
analysis = await providerAnalysis(manifest, content, provider, schema);
|
|
6607
|
+
} catch {
|
|
6608
|
+
analysis = heuristicAnalysis(manifest, content, schema.hash);
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
await writeJsonFile(cachePath, analysis);
|
|
6612
|
+
return analysis;
|
|
6613
|
+
}
|
|
6614
|
+
function analysisSignature(analysis) {
|
|
6615
|
+
return sha256(JSON.stringify(analysis));
|
|
6616
|
+
}
|
|
5969
6617
|
|
|
5970
6618
|
// src/confidence.ts
|
|
5971
6619
|
function nodeConfidence(sourceCount) {
|
|
@@ -5984,8 +6632,8 @@ function conflictConfidence(claimA, claimB) {
|
|
|
5984
6632
|
}
|
|
5985
6633
|
|
|
5986
6634
|
// src/deep-lint.ts
|
|
5987
|
-
import
|
|
5988
|
-
import
|
|
6635
|
+
import fs12 from "fs/promises";
|
|
6636
|
+
import path16 from "path";
|
|
5989
6637
|
import matter4 from "gray-matter";
|
|
5990
6638
|
import { z as z5 } from "zod";
|
|
5991
6639
|
|
|
@@ -6006,7 +6654,7 @@ function normalizeFindingSeverity(value) {
|
|
|
6006
6654
|
|
|
6007
6655
|
// src/orchestration.ts
|
|
6008
6656
|
import { spawn } from "child_process";
|
|
6009
|
-
import
|
|
6657
|
+
import path14 from "path";
|
|
6010
6658
|
import { z as z3 } from "zod";
|
|
6011
6659
|
var orchestrationRoleResultSchema = z3.object({
|
|
6012
6660
|
summary: z3.string().optional(),
|
|
@@ -6099,7 +6747,7 @@ async function runProviderRole(rootDir, role, roleConfig, input) {
|
|
|
6099
6747
|
}
|
|
6100
6748
|
async function runCommandRole(rootDir, role, executor, input) {
|
|
6101
6749
|
const [command, ...args] = executor.command;
|
|
6102
|
-
const cwd = executor.cwd ?
|
|
6750
|
+
const cwd = executor.cwd ? path14.resolve(rootDir, executor.cwd) : rootDir;
|
|
6103
6751
|
const child = spawn(command, args, {
|
|
6104
6752
|
cwd,
|
|
6105
6753
|
env: {
|
|
@@ -6193,7 +6841,7 @@ function summarizeRoleQuestions(results) {
|
|
|
6193
6841
|
}
|
|
6194
6842
|
|
|
6195
6843
|
// src/web-search/registry.ts
|
|
6196
|
-
import
|
|
6844
|
+
import path15 from "path";
|
|
6197
6845
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
6198
6846
|
import { z as z4 } from "zod";
|
|
6199
6847
|
|
|
@@ -6291,7 +6939,7 @@ async function createWebSearchAdapter(id, config, rootDir) {
|
|
|
6291
6939
|
if (!config.module) {
|
|
6292
6940
|
throw new Error(`Web search provider ${id} is type "custom" but no module path was configured.`);
|
|
6293
6941
|
}
|
|
6294
|
-
const resolvedModule =
|
|
6942
|
+
const resolvedModule = path15.isAbsolute(config.module) ? config.module : path15.resolve(rootDir, config.module);
|
|
6295
6943
|
const loaded = await import(pathToFileURL2(resolvedModule).href);
|
|
6296
6944
|
const parsed = customWebSearchModuleSchema.parse(loaded);
|
|
6297
6945
|
return parsed.createAdapter(id, config, rootDir);
|
|
@@ -6351,8 +6999,8 @@ async function loadContextPages(rootDir, graph) {
|
|
|
6351
6999
|
);
|
|
6352
7000
|
return Promise.all(
|
|
6353
7001
|
contextPages.slice(0, 18).map(async (page) => {
|
|
6354
|
-
const absolutePath =
|
|
6355
|
-
const raw = await
|
|
7002
|
+
const absolutePath = path16.join(paths.wikiDir, page.path);
|
|
7003
|
+
const raw = await fs12.readFile(absolutePath, "utf8").catch(() => "");
|
|
6356
7004
|
const parsed = matter4(raw);
|
|
6357
7005
|
return {
|
|
6358
7006
|
id: page.id,
|
|
@@ -6400,7 +7048,7 @@ function heuristicDeepFindings(contextPages, structuralFindings, graph) {
|
|
|
6400
7048
|
code: "missing_citation",
|
|
6401
7049
|
message: finding.message,
|
|
6402
7050
|
pagePath: finding.pagePath,
|
|
6403
|
-
suggestedQuery: finding.pagePath ? `Which sources support the claims in ${
|
|
7051
|
+
suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path16.basename(finding.pagePath, ".md")}?` : void 0
|
|
6404
7052
|
});
|
|
6405
7053
|
}
|
|
6406
7054
|
for (const page of contextPages.filter((item) => item.kind === "source").slice(0, 3)) {
|
|
@@ -6578,8 +7226,8 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
|
|
|
6578
7226
|
}
|
|
6579
7227
|
|
|
6580
7228
|
// src/embeddings.ts
|
|
6581
|
-
import
|
|
6582
|
-
import
|
|
7229
|
+
import fs13 from "fs/promises";
|
|
7230
|
+
import path17 from "path";
|
|
6583
7231
|
var MAX_EMBEDDING_BATCH = 32;
|
|
6584
7232
|
var MAX_SIMILARITY_NODES = 240;
|
|
6585
7233
|
function cosineSimilarity(left, right) {
|
|
@@ -6613,9 +7261,15 @@ async function loadPageContents(rootDir, graph) {
|
|
|
6613
7261
|
const contents = /* @__PURE__ */ new Map();
|
|
6614
7262
|
await Promise.all(
|
|
6615
7263
|
graph.pages.map(async (page) => {
|
|
6616
|
-
const absolutePath =
|
|
6617
|
-
const content = await
|
|
6618
|
-
|
|
7264
|
+
const absolutePath = path17.join(paths.wikiDir, page.path);
|
|
7265
|
+
const content = await fs13.readFile(absolutePath, "utf8").catch(() => {
|
|
7266
|
+
process.stderr.write(`[swarmvault] Warning: could not read page ${page.path} for embedding
|
|
7267
|
+
`);
|
|
7268
|
+
return "";
|
|
7269
|
+
});
|
|
7270
|
+
if (content) {
|
|
7271
|
+
contents.set(page.id, content);
|
|
7272
|
+
}
|
|
6619
7273
|
})
|
|
6620
7274
|
);
|
|
6621
7275
|
return contents;
|
|
@@ -8110,15 +8764,15 @@ function sourceTypeForNode(node, pagesById) {
|
|
|
8110
8764
|
return pagesById.get(node.pageId)?.sourceType;
|
|
8111
8765
|
}
|
|
8112
8766
|
function supportingPathDetails(graph, edge) {
|
|
8113
|
-
const
|
|
8767
|
+
const path26 = shortestGraphPath(graph, edge.source, edge.target);
|
|
8114
8768
|
const edgesById = new Map(graph.edges.map((item) => [item.id, item]));
|
|
8115
|
-
const pathEdges =
|
|
8769
|
+
const pathEdges = path26.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
|
|
8116
8770
|
return {
|
|
8117
|
-
pathNodeIds:
|
|
8118
|
-
pathEdgeIds:
|
|
8771
|
+
pathNodeIds: path26.nodeIds,
|
|
8772
|
+
pathEdgeIds: path26.edgeIds,
|
|
8119
8773
|
pathRelations: pathEdges.map((item) => item.relation),
|
|
8120
8774
|
pathEvidenceClasses: pathEdges.map((item) => item.evidenceClass),
|
|
8121
|
-
pathSummary:
|
|
8775
|
+
pathSummary: path26.summary
|
|
8122
8776
|
};
|
|
8123
8777
|
}
|
|
8124
8778
|
function surpriseScore(edge, graph, pagesById, hyperedgesByNodeId) {
|
|
@@ -8187,7 +8841,7 @@ function topSurprisingConnections(graph, pagesById) {
|
|
|
8187
8841
|
}).map((edge) => {
|
|
8188
8842
|
const source = nodesById.get(edge.source);
|
|
8189
8843
|
const target = nodesById.get(edge.target);
|
|
8190
|
-
const
|
|
8844
|
+
const path26 = supportingPathDetails(graph, edge);
|
|
8191
8845
|
const scored = surpriseScore(edge, graph, pagesById, hyperedgesByNodeId);
|
|
8192
8846
|
return {
|
|
8193
8847
|
id: edge.id,
|
|
@@ -8198,11 +8852,11 @@ function topSurprisingConnections(graph, pagesById) {
|
|
|
8198
8852
|
relation: edge.relation,
|
|
8199
8853
|
evidenceClass: edge.evidenceClass,
|
|
8200
8854
|
confidence: edge.confidence,
|
|
8201
|
-
pathNodeIds:
|
|
8202
|
-
pathEdgeIds:
|
|
8203
|
-
pathRelations:
|
|
8204
|
-
pathEvidenceClasses:
|
|
8205
|
-
pathSummary:
|
|
8855
|
+
pathNodeIds: path26.pathNodeIds,
|
|
8856
|
+
pathEdgeIds: path26.pathEdgeIds,
|
|
8857
|
+
pathRelations: path26.pathRelations,
|
|
8858
|
+
pathEvidenceClasses: path26.pathEvidenceClasses,
|
|
8859
|
+
pathSummary: path26.pathSummary,
|
|
8206
8860
|
why: scored.why,
|
|
8207
8861
|
explanation: scored.explanation,
|
|
8208
8862
|
surpriseScore: scored.score
|
|
@@ -9087,13 +9741,13 @@ function buildOutputAssetManifest(input) {
|
|
|
9087
9741
|
}
|
|
9088
9742
|
|
|
9089
9743
|
// src/outputs.ts
|
|
9090
|
-
import
|
|
9091
|
-
import
|
|
9744
|
+
import fs15 from "fs/promises";
|
|
9745
|
+
import path19 from "path";
|
|
9092
9746
|
import matter7 from "gray-matter";
|
|
9093
9747
|
|
|
9094
9748
|
// src/pages.ts
|
|
9095
|
-
import
|
|
9096
|
-
import
|
|
9749
|
+
import fs14 from "fs/promises";
|
|
9750
|
+
import path18 from "path";
|
|
9097
9751
|
import matter6 from "gray-matter";
|
|
9098
9752
|
function normalizeStringArray(value) {
|
|
9099
9753
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
@@ -9171,7 +9825,7 @@ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
|
|
|
9171
9825
|
updatedAt: updatedFallback
|
|
9172
9826
|
};
|
|
9173
9827
|
}
|
|
9174
|
-
const content = await
|
|
9828
|
+
const content = await fs14.readFile(absolutePath, "utf8");
|
|
9175
9829
|
const parsed = matter6(content);
|
|
9176
9830
|
return {
|
|
9177
9831
|
status: normalizePageStatus(parsed.data.status, defaults.status ?? "active"),
|
|
@@ -9210,7 +9864,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
|
|
|
9210
9864
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9211
9865
|
const fallbackCreatedAt = defaults.createdAt ?? now;
|
|
9212
9866
|
const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
|
|
9213
|
-
const title = typeof parsed.data.title === "string" ? parsed.data.title :
|
|
9867
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : path18.basename(relativePath, ".md");
|
|
9214
9868
|
const kind = inferPageKind(relativePath, parsed.data.kind);
|
|
9215
9869
|
const sourceIds = normalizeStringArray(parsed.data.source_ids);
|
|
9216
9870
|
const projectIds = normalizeProjectIds(parsed.data.project_ids);
|
|
@@ -9251,18 +9905,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
|
|
|
9251
9905
|
};
|
|
9252
9906
|
}
|
|
9253
9907
|
async function loadInsightPages(wikiDir) {
|
|
9254
|
-
const insightsDir =
|
|
9908
|
+
const insightsDir = path18.join(wikiDir, "insights");
|
|
9255
9909
|
if (!await fileExists(insightsDir)) {
|
|
9256
9910
|
return [];
|
|
9257
9911
|
}
|
|
9258
|
-
const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) =>
|
|
9912
|
+
const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path18.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
|
|
9259
9913
|
const insights = [];
|
|
9260
9914
|
for (const absolutePath of files) {
|
|
9261
|
-
const relativePath = toPosix(
|
|
9262
|
-
const content = await
|
|
9915
|
+
const relativePath = toPosix(path18.relative(wikiDir, absolutePath));
|
|
9916
|
+
const content = await fs14.readFile(absolutePath, "utf8");
|
|
9263
9917
|
const parsed = matter6(content);
|
|
9264
|
-
const stats = await
|
|
9265
|
-
const title = typeof parsed.data.title === "string" ? parsed.data.title :
|
|
9918
|
+
const stats = await fs14.stat(absolutePath);
|
|
9919
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : path18.basename(absolutePath, ".md");
|
|
9266
9920
|
const sourceIds = normalizeStringArray(parsed.data.source_ids);
|
|
9267
9921
|
const projectIds = normalizeProjectIds(parsed.data.project_ids);
|
|
9268
9922
|
const nodeIds = normalizeStringArray(parsed.data.node_ids);
|
|
@@ -9325,27 +9979,27 @@ function relatedOutputsForPage(targetPage, outputPages) {
|
|
|
9325
9979
|
return outputPages.map((page) => ({ page, rank: relationRank(page, targetPage) })).filter((item) => item.rank > 0).sort((left, right) => right.rank - left.rank || left.page.title.localeCompare(right.page.title)).map((item) => item.page);
|
|
9326
9980
|
}
|
|
9327
9981
|
async function resolveUniqueOutputSlug(wikiDir, baseSlug) {
|
|
9328
|
-
const outputsDir =
|
|
9982
|
+
const outputsDir = path19.join(wikiDir, "outputs");
|
|
9329
9983
|
const root = baseSlug || "output";
|
|
9330
9984
|
let candidate = root;
|
|
9331
9985
|
let counter = 2;
|
|
9332
|
-
while (await fileExists(
|
|
9986
|
+
while (await fileExists(path19.join(outputsDir, `${candidate}.md`))) {
|
|
9333
9987
|
candidate = `${root}-${counter}`;
|
|
9334
9988
|
counter++;
|
|
9335
9989
|
}
|
|
9336
9990
|
return candidate;
|
|
9337
9991
|
}
|
|
9338
9992
|
async function loadSavedOutputPages(wikiDir) {
|
|
9339
|
-
const outputsDir =
|
|
9340
|
-
const entries = await
|
|
9993
|
+
const outputsDir = path19.join(wikiDir, "outputs");
|
|
9994
|
+
const entries = await fs15.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
|
|
9341
9995
|
const outputs = [];
|
|
9342
9996
|
for (const entry of entries) {
|
|
9343
9997
|
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md") {
|
|
9344
9998
|
continue;
|
|
9345
9999
|
}
|
|
9346
|
-
const relativePath =
|
|
9347
|
-
const absolutePath =
|
|
9348
|
-
const content = await
|
|
10000
|
+
const relativePath = path19.posix.join("outputs", entry.name);
|
|
10001
|
+
const absolutePath = path19.join(outputsDir, entry.name);
|
|
10002
|
+
const content = await fs15.readFile(absolutePath, "utf8");
|
|
9349
10003
|
const parsed = matter7(content);
|
|
9350
10004
|
const slug = entry.name.replace(/\.md$/, "");
|
|
9351
10005
|
const title = typeof parsed.data.title === "string" ? parsed.data.title : slug;
|
|
@@ -9358,7 +10012,7 @@ async function loadSavedOutputPages(wikiDir) {
|
|
|
9358
10012
|
const relatedSourceIds = normalizeStringArray(parsed.data.related_source_ids);
|
|
9359
10013
|
const backlinks = normalizeStringArray(parsed.data.backlinks);
|
|
9360
10014
|
const compiledFrom = normalizeStringArray(parsed.data.compiled_from);
|
|
9361
|
-
const stats = await
|
|
10015
|
+
const stats = await fs15.stat(absolutePath);
|
|
9362
10016
|
const createdAt = typeof parsed.data.created_at === "string" ? parsed.data.created_at : stats.birthtimeMs > 0 ? stats.birthtime.toISOString() : stats.mtime.toISOString();
|
|
9363
10017
|
const updatedAt = typeof parsed.data.updated_at === "string" ? parsed.data.updated_at : stats.mtime.toISOString();
|
|
9364
10018
|
outputs.push({
|
|
@@ -9396,8 +10050,8 @@ async function loadSavedOutputPages(wikiDir) {
|
|
|
9396
10050
|
}
|
|
9397
10051
|
|
|
9398
10052
|
// src/search.ts
|
|
9399
|
-
import
|
|
9400
|
-
import
|
|
10053
|
+
import fs16 from "fs/promises";
|
|
10054
|
+
import path20 from "path";
|
|
9401
10055
|
import matter8 from "gray-matter";
|
|
9402
10056
|
function getDatabaseSync() {
|
|
9403
10057
|
const builtin = process.getBuiltinModule?.("node:sqlite");
|
|
@@ -9423,7 +10077,7 @@ function normalizeSourceClass2(value) {
|
|
|
9423
10077
|
return value === "first_party" || value === "third_party" || value === "resource" || value === "generated" ? value : void 0;
|
|
9424
10078
|
}
|
|
9425
10079
|
async function rebuildSearchIndex(dbPath, pages, wikiDir) {
|
|
9426
|
-
await ensureDir(
|
|
10080
|
+
await ensureDir(path20.dirname(dbPath));
|
|
9427
10081
|
const DatabaseSync = getDatabaseSync();
|
|
9428
10082
|
const db = new DatabaseSync(dbPath);
|
|
9429
10083
|
db.exec("PRAGMA journal_mode = WAL;");
|
|
@@ -9455,8 +10109,8 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
|
|
|
9455
10109
|
"INSERT INTO pages (id, path, title, body, kind, status, source_type, source_class, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
9456
10110
|
);
|
|
9457
10111
|
for (const page of pages) {
|
|
9458
|
-
const absolutePath =
|
|
9459
|
-
const content = await
|
|
10112
|
+
const absolutePath = path20.join(wikiDir, page.path);
|
|
10113
|
+
const content = await fs16.readFile(absolutePath, "utf8");
|
|
9460
10114
|
const parsed = matter8(content);
|
|
9461
10115
|
insertPage.run(
|
|
9462
10116
|
page.id,
|
|
@@ -9573,7 +10227,7 @@ function outputFormatInstruction(format) {
|
|
|
9573
10227
|
}
|
|
9574
10228
|
}
|
|
9575
10229
|
function outputAssetPath(slug, fileName) {
|
|
9576
|
-
return toPosix(
|
|
10230
|
+
return toPosix(path21.join("outputs", "assets", slug, fileName));
|
|
9577
10231
|
}
|
|
9578
10232
|
function outputAssetId(slug, role) {
|
|
9579
10233
|
return `output:${slug}:asset:${role}`;
|
|
@@ -9713,7 +10367,7 @@ async function resolveImageGenerationProvider(rootDir) {
|
|
|
9713
10367
|
if (!providerConfig) {
|
|
9714
10368
|
throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
|
|
9715
10369
|
}
|
|
9716
|
-
const { createProvider: createProvider2 } = await import("./registry-
|
|
10370
|
+
const { createProvider: createProvider2 } = await import("./registry-G7NSRYCO.js");
|
|
9717
10371
|
return createProvider2(preferredProviderId, providerConfig, rootDir);
|
|
9718
10372
|
}
|
|
9719
10373
|
async function generateOutputArtifacts(rootDir, input) {
|
|
@@ -9911,7 +10565,7 @@ async function generateOutputArtifacts(rootDir, input) {
|
|
|
9911
10565
|
};
|
|
9912
10566
|
}
|
|
9913
10567
|
function normalizeProjectRoot(root) {
|
|
9914
|
-
const normalized = toPosix(
|
|
10568
|
+
const normalized = toPosix(path21.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
9915
10569
|
return normalized;
|
|
9916
10570
|
}
|
|
9917
10571
|
function projectEntries(config) {
|
|
@@ -9937,10 +10591,10 @@ function manifestPathForProject(rootDir, manifest) {
|
|
|
9937
10591
|
if (!rawPath) {
|
|
9938
10592
|
return toPosix(manifest.storedPath);
|
|
9939
10593
|
}
|
|
9940
|
-
if (!
|
|
10594
|
+
if (!path21.isAbsolute(rawPath)) {
|
|
9941
10595
|
return normalizeProjectRoot(rawPath);
|
|
9942
10596
|
}
|
|
9943
|
-
const relative = toPosix(
|
|
10597
|
+
const relative = toPosix(path21.relative(rootDir, rawPath));
|
|
9944
10598
|
return relative.startsWith("..") ? toPosix(rawPath) : normalizeProjectRoot(relative);
|
|
9945
10599
|
}
|
|
9946
10600
|
function prefixMatches(value, prefix) {
|
|
@@ -10114,7 +10768,7 @@ function pageHashes(pages) {
|
|
|
10114
10768
|
return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
|
|
10115
10769
|
}
|
|
10116
10770
|
async function buildManagedGraphPage(absolutePath, defaults, build) {
|
|
10117
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
10771
|
+
const existingContent = await fileExists(absolutePath) ? await fs17.readFile(absolutePath, "utf8") : null;
|
|
10118
10772
|
let existing = await loadExistingManagedPageState(absolutePath, {
|
|
10119
10773
|
status: defaults.status ?? "active",
|
|
10120
10774
|
managedBy: defaults.managedBy
|
|
@@ -10152,7 +10806,7 @@ async function buildManagedGraphPage(absolutePath, defaults, build) {
|
|
|
10152
10806
|
return built;
|
|
10153
10807
|
}
|
|
10154
10808
|
async function buildManagedContent(absolutePath, defaults, build) {
|
|
10155
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
10809
|
+
const existingContent = await fileExists(absolutePath) ? await fs17.readFile(absolutePath, "utf8") : null;
|
|
10156
10810
|
let existing = await loadExistingManagedPageState(absolutePath, {
|
|
10157
10811
|
status: defaults.status ?? "active",
|
|
10158
10812
|
managedBy: defaults.managedBy
|
|
@@ -10274,8 +10928,64 @@ function deriveGraphMetrics(nodes, edges) {
|
|
|
10274
10928
|
function resetGraphNodeMetrics(nodes) {
|
|
10275
10929
|
return nodes.map(({ communityId: _communityId, degree: _degree, bridgeScore: _bridgeScore, isGodNode: _isGodNode, ...node }) => node);
|
|
10276
10930
|
}
|
|
10931
|
+
function manifestRepoPath(manifest) {
|
|
10932
|
+
return toPosix(manifest.repoRelativePath ?? path21.basename(manifest.originalPath ?? manifest.storedPath));
|
|
10933
|
+
}
|
|
10934
|
+
function goPackageScopeKey(manifest, analysis) {
|
|
10935
|
+
if (analysis.code?.language !== "go") {
|
|
10936
|
+
return null;
|
|
10937
|
+
}
|
|
10938
|
+
const packageName = analysis.code.namespace?.trim();
|
|
10939
|
+
if (!packageName) {
|
|
10940
|
+
return null;
|
|
10941
|
+
}
|
|
10942
|
+
return `${packageName}:${path21.posix.dirname(manifestRepoPath(manifest))}`;
|
|
10943
|
+
}
|
|
10944
|
+
function buildGoPackageSymbolLookups(analyses, manifestsById) {
|
|
10945
|
+
const lookups = /* @__PURE__ */ new Map();
|
|
10946
|
+
for (const analysis of analyses) {
|
|
10947
|
+
if (analysis.code?.language !== "go") {
|
|
10948
|
+
continue;
|
|
10949
|
+
}
|
|
10950
|
+
const manifest = manifestsById.get(analysis.sourceId);
|
|
10951
|
+
if (!manifest) {
|
|
10952
|
+
continue;
|
|
10953
|
+
}
|
|
10954
|
+
const scopeKey = goPackageScopeKey(manifest, analysis);
|
|
10955
|
+
if (!scopeKey) {
|
|
10956
|
+
continue;
|
|
10957
|
+
}
|
|
10958
|
+
const current = lookups.get(scopeKey) ?? {
|
|
10959
|
+
byName: /* @__PURE__ */ new Map(),
|
|
10960
|
+
methodIdsByShortName: /* @__PURE__ */ new Map()
|
|
10961
|
+
};
|
|
10962
|
+
for (const symbol of analysis.code.symbols) {
|
|
10963
|
+
current.byName.set(symbol.name, symbol.id);
|
|
10964
|
+
const separator = symbol.name.lastIndexOf(".");
|
|
10965
|
+
if (separator > 0) {
|
|
10966
|
+
const shortName = symbol.name.slice(separator + 1);
|
|
10967
|
+
const matches = current.methodIdsByShortName.get(shortName) ?? /* @__PURE__ */ new Set();
|
|
10968
|
+
matches.add(symbol.id);
|
|
10969
|
+
current.methodIdsByShortName.set(shortName, matches);
|
|
10970
|
+
}
|
|
10971
|
+
}
|
|
10972
|
+
lookups.set(scopeKey, current);
|
|
10973
|
+
}
|
|
10974
|
+
return new Map(
|
|
10975
|
+
[...lookups.entries()].map(([scopeKey, value]) => [
|
|
10976
|
+
scopeKey,
|
|
10977
|
+
{
|
|
10978
|
+
byName: value.byName,
|
|
10979
|
+
uniqueMethodIdsByShortName: new Map(
|
|
10980
|
+
[...value.methodIdsByShortName.entries()].filter(([, ids]) => ids.size === 1).map(([shortName, ids]) => [shortName, [...ids][0]])
|
|
10981
|
+
)
|
|
10982
|
+
}
|
|
10983
|
+
])
|
|
10984
|
+
);
|
|
10985
|
+
}
|
|
10277
10986
|
function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
10278
10987
|
const manifestsById = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
|
|
10988
|
+
const goPackageSymbolLookups = buildGoPackageSymbolLookups(analyses, manifestsById);
|
|
10279
10989
|
const sourceNodes = manifests.map((manifest) => ({
|
|
10280
10990
|
id: `source:${manifest.sourceId}`,
|
|
10281
10991
|
type: "source",
|
|
@@ -10422,6 +11132,10 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
10422
11132
|
}
|
|
10423
11133
|
}
|
|
10424
11134
|
const symbolIdsByName = new Map(analysis.code.symbols.map((symbol) => [symbol.name, symbol.id]));
|
|
11135
|
+
const goPackageLookup = analysis.code.language === "go" ? goPackageSymbolLookups.get(goPackageScopeKey(manifest, analysis) ?? "") : void 0;
|
|
11136
|
+
const localSymbolIdsByName = goPackageLookup?.byName ?? symbolIdsByName;
|
|
11137
|
+
const localGoMethodIdsByShortName = goPackageLookup?.uniqueMethodIdsByShortName ?? /* @__PURE__ */ new Map();
|
|
11138
|
+
const resolveLocalSymbolId = (targetName) => localSymbolIdsByName.get(targetName) ?? (analysis.code?.language === "go" ? localGoMethodIdsByShortName.get(targetName) : void 0);
|
|
10425
11139
|
for (const rationale of analysis.rationales) {
|
|
10426
11140
|
const targetSymbolId = rationale.symbolName ? symbolIdsByName.get(rationale.symbolName) : void 0;
|
|
10427
11141
|
const targetId = targetSymbolId ?? moduleId;
|
|
@@ -10474,9 +11188,31 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
10474
11188
|
}
|
|
10475
11189
|
}
|
|
10476
11190
|
}
|
|
11191
|
+
if (analysis.code.language === "go") {
|
|
11192
|
+
for (const symbol of analysis.code.symbols) {
|
|
11193
|
+
const separator = symbol.name.lastIndexOf(".");
|
|
11194
|
+
if (separator <= 0) {
|
|
11195
|
+
continue;
|
|
11196
|
+
}
|
|
11197
|
+
const receiverTypeId = localSymbolIdsByName.get(symbol.name.slice(0, separator));
|
|
11198
|
+
if (!receiverTypeId || receiverTypeId === symbol.id) {
|
|
11199
|
+
continue;
|
|
11200
|
+
}
|
|
11201
|
+
pushEdge({
|
|
11202
|
+
id: `${receiverTypeId}->${symbol.id}:defines:receiver`,
|
|
11203
|
+
source: receiverTypeId,
|
|
11204
|
+
target: symbol.id,
|
|
11205
|
+
relation: "defines",
|
|
11206
|
+
status: "extracted",
|
|
11207
|
+
evidenceClass: "extracted",
|
|
11208
|
+
confidence: 1,
|
|
11209
|
+
provenance: [analysis.sourceId]
|
|
11210
|
+
});
|
|
11211
|
+
}
|
|
11212
|
+
}
|
|
10477
11213
|
for (const symbol of analysis.code.symbols) {
|
|
10478
11214
|
for (const targetName of symbol.calls) {
|
|
10479
|
-
const targetId =
|
|
11215
|
+
const targetId = resolveLocalSymbolId(targetName);
|
|
10480
11216
|
if (!targetId || targetId === symbol.id) {
|
|
10481
11217
|
continue;
|
|
10482
11218
|
}
|
|
@@ -10492,7 +11228,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
10492
11228
|
});
|
|
10493
11229
|
}
|
|
10494
11230
|
for (const targetName of symbol.extends) {
|
|
10495
|
-
const targetId =
|
|
11231
|
+
const targetId = resolveLocalSymbolId(targetName) ?? importedSymbolIdsByName.get(targetName);
|
|
10496
11232
|
if (!targetId) {
|
|
10497
11233
|
continue;
|
|
10498
11234
|
}
|
|
@@ -10508,7 +11244,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
10508
11244
|
});
|
|
10509
11245
|
}
|
|
10510
11246
|
for (const targetName of symbol.implements) {
|
|
10511
|
-
const targetId =
|
|
11247
|
+
const targetId = resolveLocalSymbolId(targetName) ?? importedSymbolIdsByName.get(targetName);
|
|
10512
11248
|
if (!targetId) {
|
|
10513
11249
|
continue;
|
|
10514
11250
|
}
|
|
@@ -10630,7 +11366,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
|
|
|
10630
11366
|
const benchmark = await readJsonFile(paths.benchmarkPath);
|
|
10631
11367
|
const communityRecords = [];
|
|
10632
11368
|
for (const community of graph.communities ?? []) {
|
|
10633
|
-
const absolutePath =
|
|
11369
|
+
const absolutePath = path21.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
|
|
10634
11370
|
communityRecords.push(
|
|
10635
11371
|
await buildManagedGraphPage(
|
|
10636
11372
|
absolutePath,
|
|
@@ -10658,7 +11394,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
|
|
|
10658
11394
|
recentResearchSources: recentResearchSourcePages(graph, previousCompiledAt),
|
|
10659
11395
|
graphHash: graphHash(graph)
|
|
10660
11396
|
});
|
|
10661
|
-
const reportAbsolutePath =
|
|
11397
|
+
const reportAbsolutePath = path21.join(paths.wikiDir, "graph", "report.md");
|
|
10662
11398
|
const reportRecord = await buildManagedGraphPage(
|
|
10663
11399
|
reportAbsolutePath,
|
|
10664
11400
|
{
|
|
@@ -10679,7 +11415,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
|
|
|
10679
11415
|
};
|
|
10680
11416
|
}
|
|
10681
11417
|
async function writePage(wikiDir, relativePath, content, changedPages) {
|
|
10682
|
-
const absolutePath =
|
|
11418
|
+
const absolutePath = path21.resolve(wikiDir, relativePath);
|
|
10683
11419
|
const changed = await writeFileIfChanged(absolutePath, content);
|
|
10684
11420
|
if (changed) {
|
|
10685
11421
|
changedPages.push(relativePath);
|
|
@@ -10742,29 +11478,29 @@ async function requiredCompileArtifactsExist(paths) {
|
|
|
10742
11478
|
paths.graphPath,
|
|
10743
11479
|
paths.codeIndexPath,
|
|
10744
11480
|
paths.searchDbPath,
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
11481
|
+
path21.join(paths.wikiDir, "index.md"),
|
|
11482
|
+
path21.join(paths.wikiDir, "sources", "index.md"),
|
|
11483
|
+
path21.join(paths.wikiDir, "code", "index.md"),
|
|
11484
|
+
path21.join(paths.wikiDir, "concepts", "index.md"),
|
|
11485
|
+
path21.join(paths.wikiDir, "entities", "index.md"),
|
|
11486
|
+
path21.join(paths.wikiDir, "outputs", "index.md"),
|
|
11487
|
+
path21.join(paths.wikiDir, "projects", "index.md"),
|
|
11488
|
+
path21.join(paths.wikiDir, "candidates", "index.md")
|
|
10753
11489
|
];
|
|
10754
11490
|
const checks = await Promise.all(requiredPaths.map((filePath) => fileExists(filePath)));
|
|
10755
11491
|
return checks.every(Boolean);
|
|
10756
11492
|
}
|
|
10757
11493
|
async function loadAvailableCachedAnalyses(paths, manifests) {
|
|
10758
11494
|
const analyses = await Promise.all(
|
|
10759
|
-
manifests.map(async (manifest) => readJsonFile(
|
|
11495
|
+
manifests.map(async (manifest) => readJsonFile(path21.join(paths.analysesDir, `${manifest.sourceId}.json`)))
|
|
10760
11496
|
);
|
|
10761
11497
|
return analyses.filter((analysis) => Boolean(analysis));
|
|
10762
11498
|
}
|
|
10763
11499
|
function approvalManifestPath(paths, approvalId) {
|
|
10764
|
-
return
|
|
11500
|
+
return path21.join(paths.approvalsDir, approvalId, "manifest.json");
|
|
10765
11501
|
}
|
|
10766
11502
|
function approvalGraphPath(paths, approvalId) {
|
|
10767
|
-
return
|
|
11503
|
+
return path21.join(paths.approvalsDir, approvalId, "state", "graph.json");
|
|
10768
11504
|
}
|
|
10769
11505
|
async function readApprovalManifest(paths, approvalId) {
|
|
10770
11506
|
const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
|
|
@@ -10774,7 +11510,7 @@ async function readApprovalManifest(paths, approvalId) {
|
|
|
10774
11510
|
return manifest;
|
|
10775
11511
|
}
|
|
10776
11512
|
async function writeApprovalManifest(paths, manifest) {
|
|
10777
|
-
await
|
|
11513
|
+
await fs17.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
|
|
10778
11514
|
`, "utf8");
|
|
10779
11515
|
}
|
|
10780
11516
|
async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
|
|
@@ -10789,7 +11525,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
10789
11525
|
continue;
|
|
10790
11526
|
}
|
|
10791
11527
|
const previousPage = previousPagesById.get(nextPage.id);
|
|
10792
|
-
const currentExists = await fileExists(
|
|
11528
|
+
const currentExists = await fileExists(path21.join(paths.wikiDir, file.relativePath));
|
|
10793
11529
|
if (previousPage && previousPage.path !== nextPage.path) {
|
|
10794
11530
|
entries.push({
|
|
10795
11531
|
pageId: nextPage.id,
|
|
@@ -10822,7 +11558,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
10822
11558
|
const previousPage = previousPagesByPath.get(deletedPath);
|
|
10823
11559
|
entries.push({
|
|
10824
11560
|
pageId: previousPage?.id ?? `page:${slugify(deletedPath)}`,
|
|
10825
|
-
title: previousPage?.title ??
|
|
11561
|
+
title: previousPage?.title ?? path21.basename(deletedPath, ".md"),
|
|
10826
11562
|
kind: previousPage?.kind ?? "index",
|
|
10827
11563
|
changeType: "delete",
|
|
10828
11564
|
status: "pending",
|
|
@@ -10834,16 +11570,16 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
10834
11570
|
}
|
|
10835
11571
|
async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGraph, graph) {
|
|
10836
11572
|
const approvalId = `compile-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
10837
|
-
const approvalDir =
|
|
11573
|
+
const approvalDir = path21.join(paths.approvalsDir, approvalId);
|
|
10838
11574
|
await ensureDir(approvalDir);
|
|
10839
|
-
await ensureDir(
|
|
10840
|
-
await ensureDir(
|
|
11575
|
+
await ensureDir(path21.join(approvalDir, "wiki"));
|
|
11576
|
+
await ensureDir(path21.join(approvalDir, "state"));
|
|
10841
11577
|
for (const file of changedFiles) {
|
|
10842
|
-
const targetPath =
|
|
10843
|
-
await ensureDir(
|
|
10844
|
-
await
|
|
11578
|
+
const targetPath = path21.join(approvalDir, "wiki", file.relativePath);
|
|
11579
|
+
await ensureDir(path21.dirname(targetPath));
|
|
11580
|
+
await fs17.writeFile(targetPath, file.content, "utf8");
|
|
10845
11581
|
}
|
|
10846
|
-
await
|
|
11582
|
+
await fs17.writeFile(path21.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
|
|
10847
11583
|
await writeApprovalManifest(paths, {
|
|
10848
11584
|
approvalId,
|
|
10849
11585
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -10905,7 +11641,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
10905
11641
|
confidence: 1
|
|
10906
11642
|
});
|
|
10907
11643
|
const sourceRecord = await buildManagedGraphPage(
|
|
10908
|
-
|
|
11644
|
+
path21.join(paths.wikiDir, preview.path),
|
|
10909
11645
|
{
|
|
10910
11646
|
managedBy: "system",
|
|
10911
11647
|
confidence: 1,
|
|
@@ -10951,7 +11687,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
10951
11687
|
);
|
|
10952
11688
|
records.push(
|
|
10953
11689
|
await buildManagedGraphPage(
|
|
10954
|
-
|
|
11690
|
+
path21.join(paths.wikiDir, modulePreview.path),
|
|
10955
11691
|
{
|
|
10956
11692
|
managedBy: "system",
|
|
10957
11693
|
confidence: 1,
|
|
@@ -10985,8 +11721,8 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
10985
11721
|
const relativePath = promoted ? activeAggregatePath(itemKind, slug) : candidatePagePathFor(itemKind, slug);
|
|
10986
11722
|
const aggregateSourceClass2 = aggregateManifestSourceClass(input.manifests, sourceIds);
|
|
10987
11723
|
const fallbackPaths = [
|
|
10988
|
-
|
|
10989
|
-
|
|
11724
|
+
path21.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
|
|
11725
|
+
path21.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
|
|
10990
11726
|
];
|
|
10991
11727
|
const confidence = nodeConfidence(aggregate.sourceAnalyses.length);
|
|
10992
11728
|
const preview = emptyGraphPage({
|
|
@@ -11004,7 +11740,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11004
11740
|
status: promoted ? "active" : "candidate"
|
|
11005
11741
|
});
|
|
11006
11742
|
const pageRecord = await buildManagedGraphPage(
|
|
11007
|
-
|
|
11743
|
+
path21.join(paths.wikiDir, relativePath),
|
|
11008
11744
|
{
|
|
11009
11745
|
status: promoted ? "active" : "candidate",
|
|
11010
11746
|
managedBy: "system",
|
|
@@ -11099,7 +11835,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11099
11835
|
confidence: 1
|
|
11100
11836
|
}),
|
|
11101
11837
|
content: await buildManagedContent(
|
|
11102
|
-
|
|
11838
|
+
path21.join(paths.wikiDir, "projects", "index.md"),
|
|
11103
11839
|
{
|
|
11104
11840
|
managedBy: "system",
|
|
11105
11841
|
compiledFrom: indexCompiledFrom(projectIndexRefs)
|
|
@@ -11123,7 +11859,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11123
11859
|
records.push({
|
|
11124
11860
|
page: projectIndexRef,
|
|
11125
11861
|
content: await buildManagedContent(
|
|
11126
|
-
|
|
11862
|
+
path21.join(paths.wikiDir, projectIndexRef.path),
|
|
11127
11863
|
{
|
|
11128
11864
|
managedBy: "system",
|
|
11129
11865
|
compiledFrom: indexCompiledFrom(Object.values(sections).flat())
|
|
@@ -11151,7 +11887,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11151
11887
|
confidence: 1
|
|
11152
11888
|
}),
|
|
11153
11889
|
content: await buildManagedContent(
|
|
11154
|
-
|
|
11890
|
+
path21.join(paths.wikiDir, "index.md"),
|
|
11155
11891
|
{
|
|
11156
11892
|
managedBy: "system",
|
|
11157
11893
|
compiledFrom: indexCompiledFrom(allPages)
|
|
@@ -11182,7 +11918,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11182
11918
|
confidence: 1
|
|
11183
11919
|
}),
|
|
11184
11920
|
content: await buildManagedContent(
|
|
11185
|
-
|
|
11921
|
+
path21.join(paths.wikiDir, relativePath),
|
|
11186
11922
|
{
|
|
11187
11923
|
managedBy: "system",
|
|
11188
11924
|
compiledFrom: indexCompiledFrom(pages)
|
|
@@ -11193,12 +11929,12 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11193
11929
|
}
|
|
11194
11930
|
const nextPagePaths = new Set(records.map((record) => record.page.path));
|
|
11195
11931
|
const obsoleteGraphPaths = (previousGraph?.pages ?? []).filter((page) => page.kind !== "output" && page.kind !== "insight").map((page) => page.path).filter((relativePath) => !nextPagePaths.has(relativePath));
|
|
11196
|
-
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(
|
|
11932
|
+
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path21.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
|
|
11197
11933
|
const obsoletePaths = uniqueStrings3([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
|
|
11198
11934
|
const changedFiles = [];
|
|
11199
11935
|
for (const record of records) {
|
|
11200
|
-
const absolutePath =
|
|
11201
|
-
const current = await fileExists(absolutePath) ? await
|
|
11936
|
+
const absolutePath = path21.join(paths.wikiDir, record.page.path);
|
|
11937
|
+
const current = await fileExists(absolutePath) ? await fs17.readFile(absolutePath, "utf8") : null;
|
|
11202
11938
|
if (current !== record.content) {
|
|
11203
11939
|
changedPages.push(record.page.path);
|
|
11204
11940
|
changedFiles.push({ relativePath: record.page.path, content: record.content });
|
|
@@ -11223,10 +11959,10 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
11223
11959
|
await writePage(paths.wikiDir, record.page.path, record.content, writeChanges);
|
|
11224
11960
|
}
|
|
11225
11961
|
for (const relativePath of obsoletePaths) {
|
|
11226
|
-
await
|
|
11962
|
+
await fs17.rm(path21.join(paths.wikiDir, relativePath), { force: true });
|
|
11227
11963
|
}
|
|
11228
11964
|
await writeJsonFile(paths.graphPath, graph);
|
|
11229
|
-
await writeJsonFile(
|
|
11965
|
+
await writeJsonFile(path21.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
|
|
11230
11966
|
await writeJsonFile(paths.codeIndexPath, input.codeIndex);
|
|
11231
11967
|
await writeJsonFile(paths.compileStatePath, {
|
|
11232
11968
|
generatedAt: graph.generatedAt,
|
|
@@ -11297,17 +12033,17 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11297
12033
|
})
|
|
11298
12034
|
);
|
|
11299
12035
|
await Promise.all([
|
|
11300
|
-
ensureDir(
|
|
11301
|
-
ensureDir(
|
|
11302
|
-
ensureDir(
|
|
11303
|
-
ensureDir(
|
|
11304
|
-
ensureDir(
|
|
11305
|
-
ensureDir(
|
|
11306
|
-
ensureDir(
|
|
11307
|
-
ensureDir(
|
|
11308
|
-
ensureDir(
|
|
12036
|
+
ensureDir(path21.join(paths.wikiDir, "sources")),
|
|
12037
|
+
ensureDir(path21.join(paths.wikiDir, "code")),
|
|
12038
|
+
ensureDir(path21.join(paths.wikiDir, "concepts")),
|
|
12039
|
+
ensureDir(path21.join(paths.wikiDir, "entities")),
|
|
12040
|
+
ensureDir(path21.join(paths.wikiDir, "outputs")),
|
|
12041
|
+
ensureDir(path21.join(paths.wikiDir, "graph")),
|
|
12042
|
+
ensureDir(path21.join(paths.wikiDir, "graph", "communities")),
|
|
12043
|
+
ensureDir(path21.join(paths.wikiDir, "projects")),
|
|
12044
|
+
ensureDir(path21.join(paths.wikiDir, "candidates"))
|
|
11309
12045
|
]);
|
|
11310
|
-
const projectsIndexPath =
|
|
12046
|
+
const projectsIndexPath = path21.join(paths.wikiDir, "projects", "index.md");
|
|
11311
12047
|
await writeFileIfChanged(
|
|
11312
12048
|
projectsIndexPath,
|
|
11313
12049
|
await buildManagedContent(
|
|
@@ -11328,7 +12064,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11328
12064
|
outputs: pages.filter((page) => page.kind === "output" && page.projectIds.includes(project.id)),
|
|
11329
12065
|
candidates: pages.filter((page) => page.status === "candidate" && page.projectIds.includes(project.id))
|
|
11330
12066
|
};
|
|
11331
|
-
const absolutePath =
|
|
12067
|
+
const absolutePath = path21.join(paths.wikiDir, "projects", project.id, "index.md");
|
|
11332
12068
|
await writeFileIfChanged(
|
|
11333
12069
|
absolutePath,
|
|
11334
12070
|
await buildManagedContent(
|
|
@@ -11346,7 +12082,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11346
12082
|
)
|
|
11347
12083
|
);
|
|
11348
12084
|
}
|
|
11349
|
-
const rootIndexPath =
|
|
12085
|
+
const rootIndexPath = path21.join(paths.wikiDir, "index.md");
|
|
11350
12086
|
await writeFileIfChanged(
|
|
11351
12087
|
rootIndexPath,
|
|
11352
12088
|
await buildManagedContent(
|
|
@@ -11367,7 +12103,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11367
12103
|
["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
|
|
11368
12104
|
["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
|
|
11369
12105
|
]) {
|
|
11370
|
-
const absolutePath =
|
|
12106
|
+
const absolutePath = path21.join(paths.wikiDir, relativePath);
|
|
11371
12107
|
await writeFileIfChanged(
|
|
11372
12108
|
absolutePath,
|
|
11373
12109
|
await buildManagedContent(
|
|
@@ -11381,23 +12117,23 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
11381
12117
|
);
|
|
11382
12118
|
}
|
|
11383
12119
|
for (const record of graphOrientation.records) {
|
|
11384
|
-
await writeFileIfChanged(
|
|
12120
|
+
await writeFileIfChanged(path21.join(paths.wikiDir, record.page.path), record.content);
|
|
11385
12121
|
}
|
|
11386
12122
|
if (graphOrientation.report) {
|
|
11387
|
-
await writeJsonFile(
|
|
12123
|
+
await writeJsonFile(path21.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
|
|
11388
12124
|
}
|
|
11389
|
-
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(
|
|
12125
|
+
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path21.relative(paths.wikiDir, absolutePath)));
|
|
11390
12126
|
const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
|
|
11391
12127
|
"projects/index.md",
|
|
11392
12128
|
...configuredProjects.map((project) => `projects/${project.id}/index.md`)
|
|
11393
12129
|
]);
|
|
11394
12130
|
await Promise.all(
|
|
11395
|
-
existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) =>
|
|
12131
|
+
existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs17.rm(path21.join(paths.wikiDir, relativePath), { force: true }))
|
|
11396
12132
|
);
|
|
11397
|
-
const existingGraphPages = (await listFilesRecursive(
|
|
12133
|
+
const existingGraphPages = (await listFilesRecursive(path21.join(paths.wikiDir, "graph").replace(/\/$/, "")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path21.relative(paths.wikiDir, absolutePath)));
|
|
11398
12134
|
const allowedGraphPages = /* @__PURE__ */ new Set(["graph/index.md", ...graphOrientation.records.map((record) => record.page.path)]);
|
|
11399
12135
|
await Promise.all(
|
|
11400
|
-
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) =>
|
|
12136
|
+
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs17.rm(path21.join(paths.wikiDir, relativePath), { force: true }))
|
|
11401
12137
|
);
|
|
11402
12138
|
await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
|
|
11403
12139
|
}
|
|
@@ -11417,7 +12153,7 @@ async function prepareOutputPageSave(rootDir, input) {
|
|
|
11417
12153
|
confidence: 0.74
|
|
11418
12154
|
}
|
|
11419
12155
|
});
|
|
11420
|
-
const absolutePath =
|
|
12156
|
+
const absolutePath = path21.join(paths.wikiDir, output.page.path);
|
|
11421
12157
|
return {
|
|
11422
12158
|
page: output.page,
|
|
11423
12159
|
savedPath: absolutePath,
|
|
@@ -11429,15 +12165,15 @@ async function prepareOutputPageSave(rootDir, input) {
|
|
|
11429
12165
|
async function persistOutputPage(rootDir, input) {
|
|
11430
12166
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11431
12167
|
const prepared = await prepareOutputPageSave(rootDir, input);
|
|
11432
|
-
await ensureDir(
|
|
11433
|
-
await
|
|
12168
|
+
await ensureDir(path21.dirname(prepared.savedPath));
|
|
12169
|
+
await fs17.writeFile(prepared.savedPath, prepared.content, "utf8");
|
|
11434
12170
|
for (const assetFile of prepared.assetFiles) {
|
|
11435
|
-
const assetPath =
|
|
11436
|
-
await ensureDir(
|
|
12171
|
+
const assetPath = path21.join(paths.wikiDir, assetFile.relativePath);
|
|
12172
|
+
await ensureDir(path21.dirname(assetPath));
|
|
11437
12173
|
if (typeof assetFile.content === "string") {
|
|
11438
|
-
await
|
|
12174
|
+
await fs17.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
|
|
11439
12175
|
} else {
|
|
11440
|
-
await
|
|
12176
|
+
await fs17.writeFile(assetPath, assetFile.content);
|
|
11441
12177
|
}
|
|
11442
12178
|
}
|
|
11443
12179
|
return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
|
|
@@ -11458,7 +12194,7 @@ async function prepareExploreHubSave(rootDir, input) {
|
|
|
11458
12194
|
confidence: 0.76
|
|
11459
12195
|
}
|
|
11460
12196
|
});
|
|
11461
|
-
const absolutePath =
|
|
12197
|
+
const absolutePath = path21.join(paths.wikiDir, hub.page.path);
|
|
11462
12198
|
return {
|
|
11463
12199
|
page: hub.page,
|
|
11464
12200
|
savedPath: absolutePath,
|
|
@@ -11470,15 +12206,15 @@ async function prepareExploreHubSave(rootDir, input) {
|
|
|
11470
12206
|
async function persistExploreHub(rootDir, input) {
|
|
11471
12207
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11472
12208
|
const prepared = await prepareExploreHubSave(rootDir, input);
|
|
11473
|
-
await ensureDir(
|
|
11474
|
-
await
|
|
12209
|
+
await ensureDir(path21.dirname(prepared.savedPath));
|
|
12210
|
+
await fs17.writeFile(prepared.savedPath, prepared.content, "utf8");
|
|
11475
12211
|
for (const assetFile of prepared.assetFiles) {
|
|
11476
|
-
const assetPath =
|
|
11477
|
-
await ensureDir(
|
|
12212
|
+
const assetPath = path21.join(paths.wikiDir, assetFile.relativePath);
|
|
12213
|
+
await ensureDir(path21.dirname(assetPath));
|
|
11478
12214
|
if (typeof assetFile.content === "string") {
|
|
11479
|
-
await
|
|
12215
|
+
await fs17.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
|
|
11480
12216
|
} else {
|
|
11481
|
-
await
|
|
12217
|
+
await fs17.writeFile(assetPath, assetFile.content);
|
|
11482
12218
|
}
|
|
11483
12219
|
}
|
|
11484
12220
|
return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
|
|
@@ -11495,17 +12231,17 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
11495
12231
|
}))
|
|
11496
12232
|
]);
|
|
11497
12233
|
const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
11498
|
-
const approvalDir =
|
|
12234
|
+
const approvalDir = path21.join(paths.approvalsDir, approvalId);
|
|
11499
12235
|
await ensureDir(approvalDir);
|
|
11500
|
-
await ensureDir(
|
|
11501
|
-
await ensureDir(
|
|
12236
|
+
await ensureDir(path21.join(approvalDir, "wiki"));
|
|
12237
|
+
await ensureDir(path21.join(approvalDir, "state"));
|
|
11502
12238
|
for (const file of changedFiles) {
|
|
11503
|
-
const targetPath =
|
|
11504
|
-
await ensureDir(
|
|
12239
|
+
const targetPath = path21.join(approvalDir, "wiki", file.relativePath);
|
|
12240
|
+
await ensureDir(path21.dirname(targetPath));
|
|
11505
12241
|
if ("binary" in file && file.binary) {
|
|
11506
|
-
await
|
|
12242
|
+
await fs17.writeFile(targetPath, Buffer.from(file.content, "base64"));
|
|
11507
12243
|
} else {
|
|
11508
|
-
await
|
|
12244
|
+
await fs17.writeFile(targetPath, file.content, "utf8");
|
|
11509
12245
|
}
|
|
11510
12246
|
}
|
|
11511
12247
|
const nextPages = sortGraphPages([
|
|
@@ -11520,7 +12256,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
11520
12256
|
sources: previousGraph?.sources ?? [],
|
|
11521
12257
|
pages: nextPages
|
|
11522
12258
|
};
|
|
11523
|
-
await
|
|
12259
|
+
await fs17.writeFile(path21.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
|
|
11524
12260
|
await writeApprovalManifest(paths, {
|
|
11525
12261
|
approvalId,
|
|
11526
12262
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11549,9 +12285,9 @@ async function executeQuery(rootDir, question, format) {
|
|
|
11549
12285
|
const searchResults = searchPages(paths.searchDbPath, question, 5);
|
|
11550
12286
|
const excerpts = await Promise.all(
|
|
11551
12287
|
searchResults.map(async (result) => {
|
|
11552
|
-
const absolutePath =
|
|
12288
|
+
const absolutePath = path21.join(paths.wikiDir, result.path);
|
|
11553
12289
|
try {
|
|
11554
|
-
const content = await
|
|
12290
|
+
const content = await fs17.readFile(absolutePath, "utf8");
|
|
11555
12291
|
const parsed = matter9(content);
|
|
11556
12292
|
return `# ${result.title}
|
|
11557
12293
|
${truncate(normalizeWhitespace(parsed.content), 1200)}`;
|
|
@@ -11733,7 +12469,7 @@ function sortGraphPages(pages) {
|
|
|
11733
12469
|
async function listApprovals(rootDir) {
|
|
11734
12470
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11735
12471
|
const manifests = await Promise.all(
|
|
11736
|
-
(await
|
|
12472
|
+
(await fs17.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
11737
12473
|
try {
|
|
11738
12474
|
return await readApprovalManifest(paths, entry.name);
|
|
11739
12475
|
} catch {
|
|
@@ -11749,8 +12485,8 @@ async function readApproval(rootDir, approvalId) {
|
|
|
11749
12485
|
const details = await Promise.all(
|
|
11750
12486
|
manifest.entries.map(async (entry) => {
|
|
11751
12487
|
const currentPath = entry.previousPath ?? entry.nextPath;
|
|
11752
|
-
const currentContent = currentPath ? await
|
|
11753
|
-
const stagedContent = entry.nextPath ? await
|
|
12488
|
+
const currentContent = currentPath ? await fs17.readFile(path21.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
|
|
12489
|
+
const stagedContent = entry.nextPath ? await fs17.readFile(path21.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
|
|
11754
12490
|
return {
|
|
11755
12491
|
...entry,
|
|
11756
12492
|
currentContent,
|
|
@@ -11778,26 +12514,26 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
|
|
|
11778
12514
|
if (!entry.nextPath) {
|
|
11779
12515
|
throw new Error(`Approval entry ${entry.pageId} is missing a staged path.`);
|
|
11780
12516
|
}
|
|
11781
|
-
const stagedAbsolutePath =
|
|
11782
|
-
const stagedContent = await
|
|
11783
|
-
const targetAbsolutePath =
|
|
11784
|
-
await ensureDir(
|
|
11785
|
-
await
|
|
12517
|
+
const stagedAbsolutePath = path21.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
|
|
12518
|
+
const stagedContent = await fs17.readFile(stagedAbsolutePath, "utf8");
|
|
12519
|
+
const targetAbsolutePath = path21.join(paths.wikiDir, entry.nextPath);
|
|
12520
|
+
await ensureDir(path21.dirname(targetAbsolutePath));
|
|
12521
|
+
await fs17.writeFile(targetAbsolutePath, stagedContent, "utf8");
|
|
11786
12522
|
if (entry.changeType === "promote" && entry.previousPath) {
|
|
11787
|
-
await
|
|
12523
|
+
await fs17.rm(path21.join(paths.wikiDir, entry.previousPath), { force: true });
|
|
11788
12524
|
}
|
|
11789
12525
|
const nextPage = bundleGraph?.pages.find((page) => page.id === entry.pageId && page.path === entry.nextPath) ?? parseStoredPage(entry.nextPath, stagedContent);
|
|
11790
12526
|
if (nextPage.kind === "output" && nextPage.outputAssets?.length) {
|
|
11791
|
-
const outputAssetDir =
|
|
11792
|
-
await
|
|
12527
|
+
const outputAssetDir = path21.join(paths.wikiDir, "outputs", "assets", path21.basename(nextPage.path, ".md"));
|
|
12528
|
+
await fs17.rm(outputAssetDir, { recursive: true, force: true });
|
|
11793
12529
|
for (const asset of nextPage.outputAssets) {
|
|
11794
|
-
const stagedAssetPath =
|
|
12530
|
+
const stagedAssetPath = path21.join(paths.approvalsDir, approvalId, "wiki", asset.path);
|
|
11795
12531
|
if (!await fileExists(stagedAssetPath)) {
|
|
11796
12532
|
continue;
|
|
11797
12533
|
}
|
|
11798
|
-
const targetAssetPath =
|
|
11799
|
-
await ensureDir(
|
|
11800
|
-
await
|
|
12534
|
+
const targetAssetPath = path21.join(paths.wikiDir, asset.path);
|
|
12535
|
+
await ensureDir(path21.dirname(targetAssetPath));
|
|
12536
|
+
await fs17.copyFile(stagedAssetPath, targetAssetPath);
|
|
11801
12537
|
}
|
|
11802
12538
|
}
|
|
11803
12539
|
nextPages = nextPages.filter(
|
|
@@ -11808,10 +12544,10 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
|
|
|
11808
12544
|
} else {
|
|
11809
12545
|
const deletedPage = nextPages.find((page) => page.id === entry.pageId || page.path === entry.previousPath) ?? bundleGraph?.pages.find((page) => page.id === entry.pageId || page.path === entry.previousPath) ?? null;
|
|
11810
12546
|
if (entry.previousPath) {
|
|
11811
|
-
await
|
|
12547
|
+
await fs17.rm(path21.join(paths.wikiDir, entry.previousPath), { force: true });
|
|
11812
12548
|
}
|
|
11813
12549
|
if (deletedPage?.kind === "output") {
|
|
11814
|
-
await
|
|
12550
|
+
await fs17.rm(path21.join(paths.wikiDir, "outputs", "assets", path21.basename(deletedPage.path, ".md")), {
|
|
11815
12551
|
recursive: true,
|
|
11816
12552
|
force: true
|
|
11817
12553
|
});
|
|
@@ -11902,7 +12638,7 @@ async function promoteCandidate(rootDir, target) {
|
|
|
11902
12638
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11903
12639
|
const graph = await readJsonFile(paths.graphPath);
|
|
11904
12640
|
const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
|
|
11905
|
-
const raw = await
|
|
12641
|
+
const raw = await fs17.readFile(path21.join(paths.wikiDir, candidate.path), "utf8");
|
|
11906
12642
|
const parsed = matter9(raw);
|
|
11907
12643
|
const nextUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11908
12644
|
const nextContent = matter9.stringify(parsed.content, {
|
|
@@ -11914,10 +12650,10 @@ async function promoteCandidate(rootDir, target) {
|
|
|
11914
12650
|
)
|
|
11915
12651
|
});
|
|
11916
12652
|
const nextPath = candidateActivePath(candidate);
|
|
11917
|
-
const nextAbsolutePath =
|
|
11918
|
-
await ensureDir(
|
|
11919
|
-
await
|
|
11920
|
-
await
|
|
12653
|
+
const nextAbsolutePath = path21.join(paths.wikiDir, nextPath);
|
|
12654
|
+
await ensureDir(path21.dirname(nextAbsolutePath));
|
|
12655
|
+
await fs17.writeFile(nextAbsolutePath, nextContent, "utf8");
|
|
12656
|
+
await fs17.rm(path21.join(paths.wikiDir, candidate.path), { force: true });
|
|
11921
12657
|
const nextPage = parseStoredPage(nextPath, nextContent, { createdAt: candidate.createdAt, updatedAt: nextUpdatedAt });
|
|
11922
12658
|
const nextPages = sortGraphPages(
|
|
11923
12659
|
(graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path).concat(nextPage)
|
|
@@ -11962,7 +12698,7 @@ async function archiveCandidate(rootDir, target) {
|
|
|
11962
12698
|
const { paths } = await loadVaultConfig(rootDir);
|
|
11963
12699
|
const graph = await readJsonFile(paths.graphPath);
|
|
11964
12700
|
const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
|
|
11965
|
-
await
|
|
12701
|
+
await fs17.rm(path21.join(paths.wikiDir, candidate.path), { force: true });
|
|
11966
12702
|
const nextPages = sortGraphPages((graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path));
|
|
11967
12703
|
const nextGraph = {
|
|
11968
12704
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12001,18 +12737,18 @@ async function archiveCandidate(rootDir, target) {
|
|
|
12001
12737
|
}
|
|
12002
12738
|
async function ensureObsidianWorkspace(rootDir) {
|
|
12003
12739
|
const { config } = await loadVaultConfig(rootDir);
|
|
12004
|
-
const obsidianDir =
|
|
12740
|
+
const obsidianDir = path21.join(rootDir, ".obsidian");
|
|
12005
12741
|
const projectIds = projectEntries(config).map((project) => project.id);
|
|
12006
12742
|
await ensureDir(obsidianDir);
|
|
12007
12743
|
await Promise.all([
|
|
12008
|
-
writeJsonFile(
|
|
12744
|
+
writeJsonFile(path21.join(obsidianDir, "app.json"), {
|
|
12009
12745
|
alwaysUpdateLinks: true,
|
|
12010
12746
|
newFileLocation: "folder",
|
|
12011
12747
|
newFileFolderPath: "wiki/insights",
|
|
12012
12748
|
useMarkdownLinks: false,
|
|
12013
12749
|
attachmentFolderPath: "raw/assets"
|
|
12014
12750
|
}),
|
|
12015
|
-
writeJsonFile(
|
|
12751
|
+
writeJsonFile(path21.join(obsidianDir, "core-plugins.json"), [
|
|
12016
12752
|
"file-explorer",
|
|
12017
12753
|
"global-search",
|
|
12018
12754
|
"switcher",
|
|
@@ -12022,7 +12758,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
12022
12758
|
"tag-pane",
|
|
12023
12759
|
"page-preview"
|
|
12024
12760
|
]),
|
|
12025
|
-
writeJsonFile(
|
|
12761
|
+
writeJsonFile(path21.join(obsidianDir, "graph.json"), {
|
|
12026
12762
|
"collapse-filter": false,
|
|
12027
12763
|
search: "",
|
|
12028
12764
|
showTags: true,
|
|
@@ -12034,7 +12770,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
12034
12770
|
})),
|
|
12035
12771
|
localJumps: false
|
|
12036
12772
|
}),
|
|
12037
|
-
writeJsonFile(
|
|
12773
|
+
writeJsonFile(path21.join(obsidianDir, "workspace.json"), {
|
|
12038
12774
|
active: "root",
|
|
12039
12775
|
lastOpenFiles: ["wiki/index.md", "wiki/projects/index.md", "wiki/candidates/index.md", "wiki/insights/index.md"],
|
|
12040
12776
|
left: {
|
|
@@ -12049,7 +12785,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
12049
12785
|
async function initVault(rootDir, options = {}) {
|
|
12050
12786
|
const { paths } = await initWorkspace(rootDir);
|
|
12051
12787
|
await installConfiguredAgents(rootDir);
|
|
12052
|
-
const insightsIndexPath =
|
|
12788
|
+
const insightsIndexPath = path21.join(paths.wikiDir, "insights", "index.md");
|
|
12053
12789
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12054
12790
|
await writeFileIfChanged(
|
|
12055
12791
|
insightsIndexPath,
|
|
@@ -12085,7 +12821,7 @@ async function initVault(rootDir, options = {}) {
|
|
|
12085
12821
|
)
|
|
12086
12822
|
);
|
|
12087
12823
|
await writeFileIfChanged(
|
|
12088
|
-
|
|
12824
|
+
path21.join(paths.wikiDir, "projects", "index.md"),
|
|
12089
12825
|
matter9.stringify(["# Projects", "", "- Run `swarmvault compile` to build project rollups.", ""].join("\n"), {
|
|
12090
12826
|
page_id: "projects:index",
|
|
12091
12827
|
kind: "index",
|
|
@@ -12107,7 +12843,7 @@ async function initVault(rootDir, options = {}) {
|
|
|
12107
12843
|
})
|
|
12108
12844
|
);
|
|
12109
12845
|
await writeFileIfChanged(
|
|
12110
|
-
|
|
12846
|
+
path21.join(paths.wikiDir, "candidates", "index.md"),
|
|
12111
12847
|
matter9.stringify(["# Candidates", "", "- Run `swarmvault compile` to stage candidate pages.", ""].join("\n"), {
|
|
12112
12848
|
page_id: "candidates:index",
|
|
12113
12849
|
kind: "index",
|
|
@@ -12243,7 +12979,7 @@ async function compileVault(rootDir, options = {}) {
|
|
|
12243
12979
|
),
|
|
12244
12980
|
Promise.all(
|
|
12245
12981
|
clean.map(async (manifest) => {
|
|
12246
|
-
const cached = await readJsonFile(
|
|
12982
|
+
const cached = await readJsonFile(path21.join(paths.analysesDir, `${manifest.sourceId}.json`));
|
|
12247
12983
|
if (cached) {
|
|
12248
12984
|
return cached;
|
|
12249
12985
|
}
|
|
@@ -12267,22 +13003,22 @@ async function compileVault(rootDir, options = {}) {
|
|
|
12267
13003
|
}
|
|
12268
13004
|
const enriched = enrichResolvedCodeImports(manifest, analysis, codeIndex);
|
|
12269
13005
|
if (analysisSignature(enriched) !== analysisSignature(analysis)) {
|
|
12270
|
-
await writeJsonFile(
|
|
13006
|
+
await writeJsonFile(path21.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
|
|
12271
13007
|
}
|
|
12272
13008
|
return enriched;
|
|
12273
13009
|
})
|
|
12274
13010
|
);
|
|
12275
13011
|
await Promise.all([
|
|
12276
|
-
ensureDir(
|
|
12277
|
-
ensureDir(
|
|
12278
|
-
ensureDir(
|
|
12279
|
-
ensureDir(
|
|
12280
|
-
ensureDir(
|
|
12281
|
-
ensureDir(
|
|
12282
|
-
ensureDir(
|
|
12283
|
-
ensureDir(
|
|
12284
|
-
ensureDir(
|
|
12285
|
-
ensureDir(
|
|
13012
|
+
ensureDir(path21.join(paths.wikiDir, "sources")),
|
|
13013
|
+
ensureDir(path21.join(paths.wikiDir, "code")),
|
|
13014
|
+
ensureDir(path21.join(paths.wikiDir, "concepts")),
|
|
13015
|
+
ensureDir(path21.join(paths.wikiDir, "entities")),
|
|
13016
|
+
ensureDir(path21.join(paths.wikiDir, "outputs")),
|
|
13017
|
+
ensureDir(path21.join(paths.wikiDir, "projects")),
|
|
13018
|
+
ensureDir(path21.join(paths.wikiDir, "insights")),
|
|
13019
|
+
ensureDir(path21.join(paths.wikiDir, "candidates")),
|
|
13020
|
+
ensureDir(path21.join(paths.wikiDir, "candidates", "concepts")),
|
|
13021
|
+
ensureDir(path21.join(paths.wikiDir, "candidates", "entities"))
|
|
12286
13022
|
]);
|
|
12287
13023
|
const sync = await syncVaultArtifacts(rootDir, {
|
|
12288
13024
|
schemas,
|
|
@@ -12429,7 +13165,7 @@ async function queryVault(rootDir, options) {
|
|
|
12429
13165
|
assetFiles: staged.assetFiles
|
|
12430
13166
|
}
|
|
12431
13167
|
]);
|
|
12432
|
-
stagedPath =
|
|
13168
|
+
stagedPath = path21.join(approval.approvalDir, "wiki", staged.page.path);
|
|
12433
13169
|
savedPageId = staged.page.id;
|
|
12434
13170
|
approvalId = approval.approvalId;
|
|
12435
13171
|
approvalDir = approval.approvalDir;
|
|
@@ -12685,9 +13421,9 @@ ${orchestrationNotes.join("\n")}
|
|
|
12685
13421
|
approvalId = approval.approvalId;
|
|
12686
13422
|
approvalDir = approval.approvalDir;
|
|
12687
13423
|
stepResults.forEach((result, index) => {
|
|
12688
|
-
result.stagedPath =
|
|
13424
|
+
result.stagedPath = path21.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
|
|
12689
13425
|
});
|
|
12690
|
-
stagedHubPath =
|
|
13426
|
+
stagedHubPath = path21.join(approval.approvalDir, "wiki", hubPage.path);
|
|
12691
13427
|
} else {
|
|
12692
13428
|
await refreshVaultAfterOutputSave(rootDir);
|
|
12693
13429
|
}
|
|
@@ -12774,11 +13510,11 @@ async function benchmarkVault(rootDir, options = {}) {
|
|
|
12774
13510
|
}
|
|
12775
13511
|
}
|
|
12776
13512
|
for (const page of graph.pages) {
|
|
12777
|
-
const absolutePath =
|
|
13513
|
+
const absolutePath = path21.join(paths.wikiDir, page.path);
|
|
12778
13514
|
if (!await fileExists(absolutePath)) {
|
|
12779
13515
|
continue;
|
|
12780
13516
|
}
|
|
12781
|
-
const parsed = matter9(await
|
|
13517
|
+
const parsed = matter9(await fs17.readFile(absolutePath, "utf8"));
|
|
12782
13518
|
pageContentsById.set(page.id, parsed.content);
|
|
12783
13519
|
}
|
|
12784
13520
|
const configuredQuestions = (config.benchmark?.questions ?? []).map((question) => normalizeWhitespace(question)).filter(Boolean);
|
|
@@ -12823,7 +13559,7 @@ async function listGraphHyperedges(rootDir, target, limit = 25) {
|
|
|
12823
13559
|
}
|
|
12824
13560
|
async function readGraphReport(rootDir) {
|
|
12825
13561
|
const { paths } = await loadVaultConfig(rootDir);
|
|
12826
|
-
return readJsonFile(
|
|
13562
|
+
return readJsonFile(path21.join(paths.wikiDir, "graph", "report.json"));
|
|
12827
13563
|
}
|
|
12828
13564
|
async function listGodNodes(rootDir, limit = 10) {
|
|
12829
13565
|
const graph = await ensureCompiledGraph(rootDir);
|
|
@@ -12836,15 +13572,15 @@ async function listPages(rootDir) {
|
|
|
12836
13572
|
}
|
|
12837
13573
|
async function readPage(rootDir, relativePath) {
|
|
12838
13574
|
const { paths } = await loadVaultConfig(rootDir);
|
|
12839
|
-
const absolutePath =
|
|
13575
|
+
const absolutePath = path21.resolve(paths.wikiDir, relativePath);
|
|
12840
13576
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
12841
13577
|
return null;
|
|
12842
13578
|
}
|
|
12843
|
-
const raw = await
|
|
13579
|
+
const raw = await fs17.readFile(absolutePath, "utf8");
|
|
12844
13580
|
const parsed = matter9(raw);
|
|
12845
13581
|
return {
|
|
12846
13582
|
path: relativePath,
|
|
12847
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
13583
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path21.basename(relativePath, path21.extname(relativePath)),
|
|
12848
13584
|
frontmatter: parsed.data,
|
|
12849
13585
|
content: parsed.content
|
|
12850
13586
|
};
|
|
@@ -12880,7 +13616,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
12880
13616
|
severity: "warning",
|
|
12881
13617
|
code: "stale_page",
|
|
12882
13618
|
message: `Page ${page.title} is stale because the vault schema changed.`,
|
|
12883
|
-
pagePath:
|
|
13619
|
+
pagePath: path21.join(paths.wikiDir, page.path),
|
|
12884
13620
|
relatedPageIds: [page.id]
|
|
12885
13621
|
});
|
|
12886
13622
|
}
|
|
@@ -12891,7 +13627,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
12891
13627
|
severity: "warning",
|
|
12892
13628
|
code: "stale_page",
|
|
12893
13629
|
message: `Page ${page.title} is stale because source ${sourceId} changed.`,
|
|
12894
|
-
pagePath:
|
|
13630
|
+
pagePath: path21.join(paths.wikiDir, page.path),
|
|
12895
13631
|
relatedSourceIds: [sourceId],
|
|
12896
13632
|
relatedPageIds: [page.id]
|
|
12897
13633
|
});
|
|
@@ -12902,13 +13638,13 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
12902
13638
|
severity: "info",
|
|
12903
13639
|
code: "orphan_page",
|
|
12904
13640
|
message: `Page ${page.title} has no backlinks.`,
|
|
12905
|
-
pagePath:
|
|
13641
|
+
pagePath: path21.join(paths.wikiDir, page.path),
|
|
12906
13642
|
relatedPageIds: [page.id]
|
|
12907
13643
|
});
|
|
12908
13644
|
}
|
|
12909
|
-
const absolutePath =
|
|
13645
|
+
const absolutePath = path21.join(paths.wikiDir, page.path);
|
|
12910
13646
|
if (await fileExists(absolutePath)) {
|
|
12911
|
-
const content = await
|
|
13647
|
+
const content = await fs17.readFile(absolutePath, "utf8");
|
|
12912
13648
|
if (content.includes("## Claims")) {
|
|
12913
13649
|
const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
|
|
12914
13650
|
if (uncited.length) {
|
|
@@ -12988,7 +13724,7 @@ async function bootstrapDemo(rootDir, input) {
|
|
|
12988
13724
|
}
|
|
12989
13725
|
|
|
12990
13726
|
// src/mcp.ts
|
|
12991
|
-
var SERVER_VERSION = "0.1.
|
|
13727
|
+
var SERVER_VERSION = "0.1.30";
|
|
12992
13728
|
async function createMcpServer(rootDir) {
|
|
12993
13729
|
const server = new McpServer({
|
|
12994
13730
|
name: "swarmvault",
|
|
@@ -13259,7 +13995,7 @@ async function createMcpServer(rootDir) {
|
|
|
13259
13995
|
},
|
|
13260
13996
|
async () => {
|
|
13261
13997
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13262
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
13998
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path22.relative(paths.sessionsDir, filePath))).sort();
|
|
13263
13999
|
return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
|
|
13264
14000
|
}
|
|
13265
14001
|
);
|
|
@@ -13292,8 +14028,8 @@ async function createMcpServer(rootDir) {
|
|
|
13292
14028
|
return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
|
|
13293
14029
|
}
|
|
13294
14030
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13295
|
-
const absolutePath =
|
|
13296
|
-
return asTextResource(`swarmvault://pages/${encodedPath}`, await
|
|
14031
|
+
const absolutePath = path22.resolve(paths.wikiDir, relativePath);
|
|
14032
|
+
return asTextResource(`swarmvault://pages/${encodedPath}`, await fs18.readFile(absolutePath, "utf8"));
|
|
13297
14033
|
}
|
|
13298
14034
|
);
|
|
13299
14035
|
server.registerResource(
|
|
@@ -13301,11 +14037,11 @@ async function createMcpServer(rootDir) {
|
|
|
13301
14037
|
new ResourceTemplate("swarmvault://sessions/{path}", {
|
|
13302
14038
|
list: async () => {
|
|
13303
14039
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13304
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
14040
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path22.relative(paths.sessionsDir, filePath))).sort();
|
|
13305
14041
|
return {
|
|
13306
14042
|
resources: files.map((relativePath) => ({
|
|
13307
14043
|
uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
|
|
13308
|
-
name:
|
|
14044
|
+
name: path22.basename(relativePath, ".md"),
|
|
13309
14045
|
title: relativePath,
|
|
13310
14046
|
description: "SwarmVault session artifact",
|
|
13311
14047
|
mimeType: "text/markdown"
|
|
@@ -13322,11 +14058,11 @@ async function createMcpServer(rootDir) {
|
|
|
13322
14058
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13323
14059
|
const encodedPath = typeof variables.path === "string" ? variables.path : "";
|
|
13324
14060
|
const relativePath = decodeURIComponent(encodedPath);
|
|
13325
|
-
const absolutePath =
|
|
14061
|
+
const absolutePath = path22.resolve(paths.sessionsDir, relativePath);
|
|
13326
14062
|
if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
|
|
13327
14063
|
return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
|
|
13328
14064
|
}
|
|
13329
|
-
return asTextResource(`swarmvault://sessions/${encodedPath}`, await
|
|
14065
|
+
return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs18.readFile(absolutePath, "utf8"));
|
|
13330
14066
|
}
|
|
13331
14067
|
);
|
|
13332
14068
|
return server;
|
|
@@ -13374,13 +14110,13 @@ function asTextResource(uri, text) {
|
|
|
13374
14110
|
}
|
|
13375
14111
|
|
|
13376
14112
|
// src/schedule.ts
|
|
13377
|
-
import
|
|
13378
|
-
import
|
|
14113
|
+
import fs19 from "fs/promises";
|
|
14114
|
+
import path23 from "path";
|
|
13379
14115
|
function scheduleStatePath(schedulesDir, jobId) {
|
|
13380
|
-
return
|
|
14116
|
+
return path23.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
13381
14117
|
}
|
|
13382
14118
|
function scheduleLockPath(schedulesDir, jobId) {
|
|
13383
|
-
return
|
|
14119
|
+
return path23.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
|
|
13384
14120
|
}
|
|
13385
14121
|
function parseEveryDuration(value) {
|
|
13386
14122
|
const match = value.trim().match(/^(\d+)(m|h|d)$/i);
|
|
@@ -13483,13 +14219,13 @@ async function acquireJobLease(rootDir, jobId) {
|
|
|
13483
14219
|
const { paths } = await loadVaultConfig(rootDir);
|
|
13484
14220
|
const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
|
|
13485
14221
|
await ensureDir(paths.schedulesDir);
|
|
13486
|
-
const handle = await
|
|
14222
|
+
const handle = await fs19.open(leasePath, "wx");
|
|
13487
14223
|
await handle.writeFile(`${process.pid}
|
|
13488
14224
|
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
13489
14225
|
`);
|
|
13490
14226
|
await handle.close();
|
|
13491
14227
|
return async () => {
|
|
13492
|
-
await
|
|
14228
|
+
await fs19.rm(leasePath, { force: true });
|
|
13493
14229
|
};
|
|
13494
14230
|
}
|
|
13495
14231
|
async function listSchedules(rootDir) {
|
|
@@ -13637,15 +14373,15 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
|
|
|
13637
14373
|
|
|
13638
14374
|
// src/viewer.ts
|
|
13639
14375
|
import { execFile } from "child_process";
|
|
13640
|
-
import
|
|
14376
|
+
import fs20 from "fs/promises";
|
|
13641
14377
|
import http from "http";
|
|
13642
|
-
import
|
|
14378
|
+
import path25 from "path";
|
|
13643
14379
|
import { promisify } from "util";
|
|
13644
14380
|
import matter10 from "gray-matter";
|
|
13645
14381
|
import mime2 from "mime-types";
|
|
13646
14382
|
|
|
13647
14383
|
// src/watch.ts
|
|
13648
|
-
import
|
|
14384
|
+
import path24 from "path";
|
|
13649
14385
|
import process3 from "process";
|
|
13650
14386
|
import chokidar from "chokidar";
|
|
13651
14387
|
var MAX_BACKOFF_MS = 3e4;
|
|
@@ -13653,15 +14389,15 @@ var BACKOFF_THRESHOLD = 3;
|
|
|
13653
14389
|
var CRITICAL_THRESHOLD = 10;
|
|
13654
14390
|
var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
|
|
13655
14391
|
function withinRoot2(rootPath, targetPath) {
|
|
13656
|
-
const relative =
|
|
13657
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
14392
|
+
const relative = path24.relative(rootPath, targetPath);
|
|
14393
|
+
return relative === "" || !relative.startsWith("..") && !path24.isAbsolute(relative);
|
|
13658
14394
|
}
|
|
13659
14395
|
function hasIgnoredRepoSegment(baseDir, targetPath) {
|
|
13660
|
-
const relativePath =
|
|
14396
|
+
const relativePath = path24.relative(baseDir, targetPath);
|
|
13661
14397
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
13662
14398
|
return false;
|
|
13663
14399
|
}
|
|
13664
|
-
return relativePath.split(
|
|
14400
|
+
return relativePath.split(path24.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
|
|
13665
14401
|
}
|
|
13666
14402
|
function workspaceIgnoreRoots(rootDir, paths) {
|
|
13667
14403
|
return [
|
|
@@ -13670,16 +14406,16 @@ function workspaceIgnoreRoots(rootDir, paths) {
|
|
|
13670
14406
|
paths.stateDir,
|
|
13671
14407
|
paths.agentDir,
|
|
13672
14408
|
paths.inboxDir,
|
|
13673
|
-
|
|
13674
|
-
|
|
13675
|
-
|
|
13676
|
-
].map((candidate) =>
|
|
14409
|
+
path24.join(rootDir, ".claude"),
|
|
14410
|
+
path24.join(rootDir, ".cursor"),
|
|
14411
|
+
path24.join(rootDir, ".obsidian")
|
|
14412
|
+
].map((candidate) => path24.resolve(candidate));
|
|
13677
14413
|
}
|
|
13678
14414
|
async function resolveWatchTargets(rootDir, paths, options) {
|
|
13679
|
-
const targets = /* @__PURE__ */ new Set([
|
|
14415
|
+
const targets = /* @__PURE__ */ new Set([path24.resolve(paths.inboxDir)]);
|
|
13680
14416
|
if (options.repo) {
|
|
13681
14417
|
for (const repoRoot of await listTrackedRepoRoots(rootDir)) {
|
|
13682
|
-
targets.add(
|
|
14418
|
+
targets.add(path24.resolve(repoRoot));
|
|
13683
14419
|
}
|
|
13684
14420
|
}
|
|
13685
14421
|
return [...targets].sort((left, right) => left.localeCompare(right));
|
|
@@ -13809,7 +14545,7 @@ async function watchVault(rootDir, options = {}) {
|
|
|
13809
14545
|
const { paths } = await initWorkspace(rootDir);
|
|
13810
14546
|
const baseDebounceMs = options.debounceMs ?? 900;
|
|
13811
14547
|
const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
|
|
13812
|
-
const inboxWatchRoot =
|
|
14548
|
+
const inboxWatchRoot = path24.resolve(paths.inboxDir);
|
|
13813
14549
|
let watchTargets = await resolveWatchTargets(rootDir, paths, options);
|
|
13814
14550
|
let timer;
|
|
13815
14551
|
let running = false;
|
|
@@ -13823,7 +14559,7 @@ async function watchVault(rootDir, options = {}) {
|
|
|
13823
14559
|
usePolling: true,
|
|
13824
14560
|
interval: 100,
|
|
13825
14561
|
ignored: (targetPath) => {
|
|
13826
|
-
const absolutePath =
|
|
14562
|
+
const absolutePath = path24.resolve(targetPath);
|
|
13827
14563
|
const primaryTarget = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
|
|
13828
14564
|
if (!primaryTarget) {
|
|
13829
14565
|
return false;
|
|
@@ -13922,51 +14658,33 @@ async function watchVault(rootDir, options = {}) {
|
|
|
13922
14658
|
}
|
|
13923
14659
|
} finally {
|
|
13924
14660
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
13925
|
-
|
|
13926
|
-
|
|
13927
|
-
|
|
13928
|
-
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
|
|
13944
|
-
|
|
13945
|
-
|
|
13946
|
-
|
|
13947
|
-
|
|
13948
|
-
|
|
13949
|
-
|
|
13950
|
-
|
|
13951
|
-
|
|
13952
|
-
importedCount: importedCount + repoImportedCount + repoUpdatedCount,
|
|
13953
|
-
scannedCount: scannedCount + repoScannedCount,
|
|
13954
|
-
attachmentCount,
|
|
13955
|
-
changedPages,
|
|
13956
|
-
repoImportedCount,
|
|
13957
|
-
repoUpdatedCount,
|
|
13958
|
-
repoRemovedCount,
|
|
13959
|
-
repoScannedCount,
|
|
13960
|
-
pendingSemanticRefreshCount,
|
|
13961
|
-
pendingSemanticRefreshPaths,
|
|
13962
|
-
lintFindingCount,
|
|
13963
|
-
success,
|
|
13964
|
-
error
|
|
13965
|
-
});
|
|
13966
|
-
await writeWatchStatusArtifact(rootDir, {
|
|
13967
|
-
generatedAt: finishedAt.toISOString(),
|
|
13968
|
-
watchedRepoRoots,
|
|
13969
|
-
lastRun: {
|
|
14661
|
+
try {
|
|
14662
|
+
await recordSession(rootDir, {
|
|
14663
|
+
operation: "watch",
|
|
14664
|
+
title: `Watch cycle for ${paths.inboxDir}${options.repo ? " and tracked repos" : ""}`,
|
|
14665
|
+
startedAt: startedAt.toISOString(),
|
|
14666
|
+
finishedAt: finishedAt.toISOString(),
|
|
14667
|
+
success,
|
|
14668
|
+
error,
|
|
14669
|
+
changedPages,
|
|
14670
|
+
lintFindingCount,
|
|
14671
|
+
lines: [
|
|
14672
|
+
`reasons=${runReasons.join(",") || "none"}`,
|
|
14673
|
+
`imported=${importedCount}`,
|
|
14674
|
+
`scanned=${scannedCount}`,
|
|
14675
|
+
`attachments=${attachmentCount}`,
|
|
14676
|
+
`repo_scanned=${repoScannedCount}`,
|
|
14677
|
+
`repo_imported=${repoImportedCount}`,
|
|
14678
|
+
`repo_updated=${repoUpdatedCount}`,
|
|
14679
|
+
`repo_removed=${repoRemovedCount}`,
|
|
14680
|
+
`lint=${lintFindingCount ?? 0}`
|
|
14681
|
+
]
|
|
14682
|
+
});
|
|
14683
|
+
} catch {
|
|
14684
|
+
process3.stderr.write("[swarmvault watch] Failed to record session log.\n");
|
|
14685
|
+
}
|
|
14686
|
+
try {
|
|
14687
|
+
await appendWatchRun(rootDir, {
|
|
13970
14688
|
startedAt: startedAt.toISOString(),
|
|
13971
14689
|
finishedAt: finishedAt.toISOString(),
|
|
13972
14690
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
@@ -13985,9 +14703,39 @@ async function watchVault(rootDir, options = {}) {
|
|
|
13985
14703
|
lintFindingCount,
|
|
13986
14704
|
success,
|
|
13987
14705
|
error
|
|
13988
|
-
}
|
|
13989
|
-
|
|
13990
|
-
|
|
14706
|
+
});
|
|
14707
|
+
} catch {
|
|
14708
|
+
process3.stderr.write("[swarmvault watch] Failed to append watch run.\n");
|
|
14709
|
+
}
|
|
14710
|
+
try {
|
|
14711
|
+
await writeWatchStatusArtifact(rootDir, {
|
|
14712
|
+
generatedAt: finishedAt.toISOString(),
|
|
14713
|
+
watchedRepoRoots,
|
|
14714
|
+
lastRun: {
|
|
14715
|
+
startedAt: startedAt.toISOString(),
|
|
14716
|
+
finishedAt: finishedAt.toISOString(),
|
|
14717
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
14718
|
+
inputDir: paths.inboxDir,
|
|
14719
|
+
reasons: runReasons,
|
|
14720
|
+
importedCount: importedCount + repoImportedCount + repoUpdatedCount,
|
|
14721
|
+
scannedCount: scannedCount + repoScannedCount,
|
|
14722
|
+
attachmentCount,
|
|
14723
|
+
changedPages,
|
|
14724
|
+
repoImportedCount,
|
|
14725
|
+
repoUpdatedCount,
|
|
14726
|
+
repoRemovedCount,
|
|
14727
|
+
repoScannedCount,
|
|
14728
|
+
pendingSemanticRefreshCount,
|
|
14729
|
+
pendingSemanticRefreshPaths,
|
|
14730
|
+
lintFindingCount,
|
|
14731
|
+
success,
|
|
14732
|
+
error
|
|
14733
|
+
},
|
|
14734
|
+
pendingSemanticRefresh: await readPendingSemanticRefresh(rootDir)
|
|
14735
|
+
});
|
|
14736
|
+
} catch {
|
|
14737
|
+
process3.stderr.write("[swarmvault watch] Failed to write watch status artifact.\n");
|
|
14738
|
+
}
|
|
13991
14739
|
running = false;
|
|
13992
14740
|
if (pending && !closed) {
|
|
13993
14741
|
schedule("queued");
|
|
@@ -13995,8 +14743,8 @@ async function watchVault(rootDir, options = {}) {
|
|
|
13995
14743
|
}
|
|
13996
14744
|
};
|
|
13997
14745
|
const reasonForPath = (targetPath) => {
|
|
13998
|
-
const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget,
|
|
13999
|
-
return
|
|
14746
|
+
const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, path24.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
|
|
14747
|
+
return path24.relative(baseDir, targetPath) || ".";
|
|
14000
14748
|
};
|
|
14001
14749
|
watcher.on("add", (filePath) => schedule(`add:${reasonForPath(filePath)}`)).on("change", (filePath) => schedule(`change:${reasonForPath(filePath)}`)).on("unlink", (filePath) => schedule(`unlink:${reasonForPath(filePath)}`)).on("addDir", (dirPath) => schedule(`addDir:${reasonForPath(dirPath)}`)).on("unlinkDir", (dirPath) => schedule(`unlinkDir:${reasonForPath(dirPath)}`)).on("error", (caught) => schedule(`error:${caught instanceof Error ? caught.message : String(caught)}`));
|
|
14002
14750
|
await new Promise((resolve, reject) => {
|
|
@@ -14037,15 +14785,15 @@ async function getWatchStatus(rootDir) {
|
|
|
14037
14785
|
var execFileAsync = promisify(execFile);
|
|
14038
14786
|
async function readViewerPage(rootDir, relativePath) {
|
|
14039
14787
|
const { paths } = await loadVaultConfig(rootDir);
|
|
14040
|
-
const absolutePath =
|
|
14788
|
+
const absolutePath = path25.resolve(paths.wikiDir, relativePath);
|
|
14041
14789
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
14042
14790
|
return null;
|
|
14043
14791
|
}
|
|
14044
|
-
const raw = await
|
|
14792
|
+
const raw = await fs20.readFile(absolutePath, "utf8");
|
|
14045
14793
|
const parsed = matter10(raw);
|
|
14046
14794
|
return {
|
|
14047
14795
|
path: relativePath,
|
|
14048
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
14796
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path25.basename(relativePath, path25.extname(relativePath)),
|
|
14049
14797
|
frontmatter: parsed.data,
|
|
14050
14798
|
content: parsed.content,
|
|
14051
14799
|
assets: normalizeOutputAssets(parsed.data.output_assets)
|
|
@@ -14053,12 +14801,12 @@ async function readViewerPage(rootDir, relativePath) {
|
|
|
14053
14801
|
}
|
|
14054
14802
|
async function readViewerAsset(rootDir, relativePath) {
|
|
14055
14803
|
const { paths } = await loadVaultConfig(rootDir);
|
|
14056
|
-
const absolutePath =
|
|
14804
|
+
const absolutePath = path25.resolve(paths.wikiDir, relativePath);
|
|
14057
14805
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
14058
14806
|
return null;
|
|
14059
14807
|
}
|
|
14060
14808
|
return {
|
|
14061
|
-
buffer: await
|
|
14809
|
+
buffer: await fs20.readFile(absolutePath),
|
|
14062
14810
|
mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
|
|
14063
14811
|
};
|
|
14064
14812
|
}
|
|
@@ -14081,12 +14829,12 @@ async function readJsonBody(request) {
|
|
|
14081
14829
|
return JSON.parse(raw);
|
|
14082
14830
|
}
|
|
14083
14831
|
async function ensureViewerDist(viewerDistDir) {
|
|
14084
|
-
const indexPath =
|
|
14832
|
+
const indexPath = path25.join(viewerDistDir, "index.html");
|
|
14085
14833
|
if (await fileExists(indexPath)) {
|
|
14086
14834
|
return;
|
|
14087
14835
|
}
|
|
14088
|
-
const viewerProjectDir =
|
|
14089
|
-
if (await fileExists(
|
|
14836
|
+
const viewerProjectDir = path25.dirname(viewerDistDir);
|
|
14837
|
+
if (await fileExists(path25.join(viewerProjectDir, "package.json"))) {
|
|
14090
14838
|
await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
|
|
14091
14839
|
}
|
|
14092
14840
|
}
|
|
@@ -14103,7 +14851,7 @@ async function startGraphServer(rootDir, port) {
|
|
|
14103
14851
|
return;
|
|
14104
14852
|
}
|
|
14105
14853
|
response.writeHead(200, { "content-type": "application/json" });
|
|
14106
|
-
response.end(await
|
|
14854
|
+
response.end(await fs20.readFile(paths.graphPath, "utf8"));
|
|
14107
14855
|
return;
|
|
14108
14856
|
}
|
|
14109
14857
|
if (url.pathname === "/api/graph/query") {
|
|
@@ -14160,14 +14908,14 @@ async function startGraphServer(rootDir, port) {
|
|
|
14160
14908
|
return;
|
|
14161
14909
|
}
|
|
14162
14910
|
if (url.pathname === "/api/graph-report") {
|
|
14163
|
-
const reportPath =
|
|
14911
|
+
const reportPath = path25.join(paths.wikiDir, "graph", "report.json");
|
|
14164
14912
|
if (!await fileExists(reportPath)) {
|
|
14165
14913
|
response.writeHead(404, { "content-type": "application/json" });
|
|
14166
14914
|
response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
|
|
14167
14915
|
return;
|
|
14168
14916
|
}
|
|
14169
14917
|
response.writeHead(200, { "content-type": "application/json" });
|
|
14170
|
-
response.end(await
|
|
14918
|
+
response.end(await fs20.readFile(reportPath, "utf8"));
|
|
14171
14919
|
return;
|
|
14172
14920
|
}
|
|
14173
14921
|
if (url.pathname === "/api/watch-status") {
|
|
@@ -14250,8 +14998,8 @@ async function startGraphServer(rootDir, port) {
|
|
|
14250
14998
|
return;
|
|
14251
14999
|
}
|
|
14252
15000
|
const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
14253
|
-
const target =
|
|
14254
|
-
const fallback =
|
|
15001
|
+
const target = path25.join(paths.viewerDistDir, relativePath);
|
|
15002
|
+
const fallback = path25.join(paths.viewerDistDir, "index.html");
|
|
14255
15003
|
const filePath = await fileExists(target) ? target : fallback;
|
|
14256
15004
|
if (!await fileExists(filePath)) {
|
|
14257
15005
|
response.writeHead(503, { "content-type": "text/plain" });
|
|
@@ -14259,7 +15007,7 @@ async function startGraphServer(rootDir, port) {
|
|
|
14259
15007
|
return;
|
|
14260
15008
|
}
|
|
14261
15009
|
response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
|
|
14262
|
-
response.end(await
|
|
15010
|
+
response.end(await fs20.readFile(filePath));
|
|
14263
15011
|
});
|
|
14264
15012
|
await new Promise((resolve) => {
|
|
14265
15013
|
server.listen(effectivePort, resolve);
|
|
@@ -14286,7 +15034,7 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
14286
15034
|
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
14287
15035
|
}
|
|
14288
15036
|
await ensureViewerDist(paths.viewerDistDir);
|
|
14289
|
-
const indexPath =
|
|
15037
|
+
const indexPath = path25.join(paths.viewerDistDir, "index.html");
|
|
14290
15038
|
if (!await fileExists(indexPath)) {
|
|
14291
15039
|
throw new Error("Viewer build not found. Run `pnpm build` first.");
|
|
14292
15040
|
}
|
|
@@ -14312,17 +15060,17 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
14312
15060
|
} : null;
|
|
14313
15061
|
})
|
|
14314
15062
|
);
|
|
14315
|
-
const rawHtml = await
|
|
15063
|
+
const rawHtml = await fs20.readFile(indexPath, "utf8");
|
|
14316
15064
|
const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
|
|
14317
15065
|
const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
|
|
14318
|
-
const scriptPath = scriptMatch?.[1] ?
|
|
14319
|
-
const stylePath = styleMatch?.[1] ?
|
|
15066
|
+
const scriptPath = scriptMatch?.[1] ? path25.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
|
|
15067
|
+
const stylePath = styleMatch?.[1] ? path25.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
|
|
14320
15068
|
if (!scriptPath || !await fileExists(scriptPath)) {
|
|
14321
15069
|
throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
|
|
14322
15070
|
}
|
|
14323
|
-
const script = await
|
|
14324
|
-
const style = stylePath && await fileExists(stylePath) ? await
|
|
14325
|
-
const report = await readJsonFile(
|
|
15071
|
+
const script = await fs20.readFile(scriptPath, "utf8");
|
|
15072
|
+
const style = stylePath && await fileExists(stylePath) ? await fs20.readFile(stylePath, "utf8") : "";
|
|
15073
|
+
const report = await readJsonFile(path25.join(paths.wikiDir, "graph", "report.json"));
|
|
14326
15074
|
const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean), report }, null, 2).replace(/</g, "\\u003c");
|
|
14327
15075
|
const html = [
|
|
14328
15076
|
"<!doctype html>",
|
|
@@ -14341,9 +15089,9 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
14341
15089
|
"</html>",
|
|
14342
15090
|
""
|
|
14343
15091
|
].filter(Boolean).join("\n");
|
|
14344
|
-
await
|
|
14345
|
-
await
|
|
14346
|
-
return
|
|
15092
|
+
await fs20.mkdir(path25.dirname(outputPath), { recursive: true });
|
|
15093
|
+
await fs20.writeFile(outputPath, html, "utf8");
|
|
15094
|
+
return path25.resolve(outputPath);
|
|
14347
15095
|
}
|
|
14348
15096
|
export {
|
|
14349
15097
|
acceptApproval,
|
|
@@ -14389,6 +15137,7 @@ export {
|
|
|
14389
15137
|
loadVaultSchemas,
|
|
14390
15138
|
pathGraphVault,
|
|
14391
15139
|
promoteCandidate,
|
|
15140
|
+
pushGraphNeo4j,
|
|
14392
15141
|
queryGraphVault,
|
|
14393
15142
|
queryVault,
|
|
14394
15143
|
readApproval,
|