@swarmvaultai/engine 0.1.19 → 0.1.21
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 +43 -4
- package/dist/chunk-QMW7OISM.js +1063 -0
- package/dist/index.d.ts +113 -3
- package/dist/index.js +2282 -577
- package/dist/registry-X5PMZTZY.js +12 -0
- package/dist/viewer/assets/index-DEETVhXx.js +330 -0
- package/dist/viewer/index.html +1 -1
- package/dist/viewer/lib.d.ts +35 -1
- package/dist/viewer/lib.js +15 -0
- package/package.json +1 -1
- package/dist/viewer/assets/index-DWUwoLny.js +0 -330
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-QMW7OISM.js";
|
|
25
25
|
|
|
26
26
|
// src/agents.ts
|
|
27
27
|
import fs from "fs/promises";
|
|
@@ -39,6 +39,7 @@ function buildManagedBlock(target) {
|
|
|
39
39
|
"- Treat `raw/` as immutable source input.",
|
|
40
40
|
"- Treat `wiki/` as generated markdown owned by the agent and compiler workflow.",
|
|
41
41
|
"- Read `wiki/graph/report.md` before broad file searching when it exists; otherwise start with `wiki/index.md`.",
|
|
42
|
+
"- For graph questions, prefer `swarmvault graph query`, `swarmvault graph path`, and `swarmvault graph explain` before broad grep/glob searching.",
|
|
42
43
|
"- Preserve frontmatter fields including `page_id`, `source_ids`, `node_ids`, `freshness`, and `source_hashes`.",
|
|
43
44
|
"- Save high-value answers back into `wiki/outputs/` instead of leaving them only in chat.",
|
|
44
45
|
"- Prefer `swarmvault ingest`, `swarmvault compile`, `swarmvault query`, and `swarmvault lint` for SwarmVault maintenance tasks.",
|
|
@@ -50,6 +51,33 @@ function buildManagedBlock(target) {
|
|
|
50
51
|
}
|
|
51
52
|
return body;
|
|
52
53
|
}
|
|
54
|
+
var claudeHookMatcher = "Glob|Grep";
|
|
55
|
+
var claudeHookCommand = "if [ -f wiki/graph/report.md ]; then echo 'swarmvault: Graph report exists. Read wiki/graph/report.md before broad raw-file searching.'; fi";
|
|
56
|
+
async function installClaudeHook(rootDir) {
|
|
57
|
+
const settingsPath = path.join(rootDir, ".claude", "settings.json");
|
|
58
|
+
await ensureDir(path.dirname(settingsPath));
|
|
59
|
+
let settings = {};
|
|
60
|
+
if (await fileExists(settingsPath)) {
|
|
61
|
+
try {
|
|
62
|
+
settings = JSON.parse(await fs.readFile(settingsPath, "utf8"));
|
|
63
|
+
} catch {
|
|
64
|
+
settings = {};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const hooks = settings.hooks ?? {};
|
|
68
|
+
const preToolUse = hooks.PreToolUse ?? [];
|
|
69
|
+
const exists = preToolUse.some((entry) => entry.matcher === claudeHookMatcher && JSON.stringify(entry).includes("swarmvault:"));
|
|
70
|
+
if (!exists) {
|
|
71
|
+
preToolUse.push({
|
|
72
|
+
matcher: claudeHookMatcher,
|
|
73
|
+
hooks: [{ type: "command", command: claudeHookCommand }]
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
settings.hooks = { ...hooks, PreToolUse: preToolUse };
|
|
77
|
+
await fs.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
78
|
+
`, "utf8");
|
|
79
|
+
return settingsPath;
|
|
80
|
+
}
|
|
53
81
|
function targetPathForAgent(rootDir, agent) {
|
|
54
82
|
switch (agent) {
|
|
55
83
|
case "codex":
|
|
@@ -87,7 +115,7 @@ async function upsertManagedBlock(filePath, block) {
|
|
|
87
115
|
${block}
|
|
88
116
|
`, "utf8");
|
|
89
117
|
}
|
|
90
|
-
async function installAgent(rootDir, agent) {
|
|
118
|
+
async function installAgent(rootDir, agent, options = {}) {
|
|
91
119
|
await initWorkspace(rootDir);
|
|
92
120
|
const target = targetPathForAgent(rootDir, agent);
|
|
93
121
|
switch (agent) {
|
|
@@ -99,6 +127,9 @@ async function installAgent(rootDir, agent) {
|
|
|
99
127
|
return target;
|
|
100
128
|
case "claude": {
|
|
101
129
|
await upsertManagedBlock(target, buildManagedBlock("claude"));
|
|
130
|
+
if (options.claudeHook) {
|
|
131
|
+
await installClaudeHook(rootDir);
|
|
132
|
+
}
|
|
102
133
|
return target;
|
|
103
134
|
}
|
|
104
135
|
case "gemini": {
|
|
@@ -125,12 +156,445 @@ async function installConfiguredAgents(rootDir) {
|
|
|
125
156
|
dedupedTargets.set(target, agent);
|
|
126
157
|
}
|
|
127
158
|
}
|
|
128
|
-
return Promise.all(
|
|
159
|
+
return Promise.all(
|
|
160
|
+
[...dedupedTargets.values()].map(
|
|
161
|
+
(agent) => installAgent(rootDir, agent, {
|
|
162
|
+
claudeHook: agent === "claude"
|
|
163
|
+
})
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/graph-export.ts
|
|
169
|
+
import fs2 from "fs/promises";
|
|
170
|
+
import path2 from "path";
|
|
171
|
+
var NODE_COLORS = {
|
|
172
|
+
source: "#f59e0b",
|
|
173
|
+
module: "#fb7185",
|
|
174
|
+
symbol: "#8b5cf6",
|
|
175
|
+
rationale: "#14b8a6",
|
|
176
|
+
concept: "#0ea5e9",
|
|
177
|
+
entity: "#22c55e"
|
|
178
|
+
};
|
|
179
|
+
function xmlEscape(value) {
|
|
180
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
181
|
+
}
|
|
182
|
+
function cypherEscape(value) {
|
|
183
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
184
|
+
}
|
|
185
|
+
function relationType(relation) {
|
|
186
|
+
const normalized = relation.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
187
|
+
return normalized || "RELATED_TO";
|
|
188
|
+
}
|
|
189
|
+
function graphPageById(graph) {
|
|
190
|
+
return new Map(graph.pages.map((page) => [page.id, page]));
|
|
191
|
+
}
|
|
192
|
+
function graphNodeById(graph) {
|
|
193
|
+
return new Map(graph.nodes.map((node) => [node.id, node]));
|
|
194
|
+
}
|
|
195
|
+
function sortedCommunities(graph) {
|
|
196
|
+
const known = (graph.communities ?? []).map((community) => ({
|
|
197
|
+
...community,
|
|
198
|
+
nodeIds: [...community.nodeIds].sort((left, right) => left.localeCompare(right))
|
|
199
|
+
}));
|
|
200
|
+
const knownIds = new Set(known.flatMap((community) => community.nodeIds));
|
|
201
|
+
const unassigned = graph.nodes.filter((node) => !knownIds.has(node.id)).sort((left, right) => left.label.localeCompare(right.label) || left.id.localeCompare(right.id)).map((node) => node.id);
|
|
202
|
+
if (unassigned.length) {
|
|
203
|
+
known.push({
|
|
204
|
+
id: "community:unassigned",
|
|
205
|
+
label: "Unassigned",
|
|
206
|
+
nodeIds: unassigned
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return known.sort((left, right) => left.label.localeCompare(right.label) || left.id.localeCompare(right.id));
|
|
210
|
+
}
|
|
211
|
+
function layoutGraph(graph) {
|
|
212
|
+
const communities = sortedCommunities(graph);
|
|
213
|
+
const width = 1600;
|
|
214
|
+
const height = Math.max(900, 420 * Math.max(1, Math.ceil(communities.length / 3)));
|
|
215
|
+
const columns = Math.max(1, Math.ceil(Math.sqrt(Math.max(1, communities.length))));
|
|
216
|
+
const nodesById = graphNodeById(graph);
|
|
217
|
+
const positioned = [];
|
|
218
|
+
communities.forEach((community, index) => {
|
|
219
|
+
const col = index % columns;
|
|
220
|
+
const row = Math.floor(index / columns);
|
|
221
|
+
const centerX = 240 + col * 460;
|
|
222
|
+
const centerY = 220 + row * 360;
|
|
223
|
+
const members = community.nodeIds.map((nodeId) => nodesById.get(nodeId)).filter((node) => Boolean(node)).sort((left, right) => left.label.localeCompare(right.label) || left.id.localeCompare(right.id));
|
|
224
|
+
const radius = Math.max(40, 36 * Math.sqrt(members.length));
|
|
225
|
+
members.forEach((node, memberIndex) => {
|
|
226
|
+
const angle = Math.PI * 2 * memberIndex / Math.max(1, members.length);
|
|
227
|
+
positioned.push({
|
|
228
|
+
node,
|
|
229
|
+
x: centerX + Math.cos(angle) * radius,
|
|
230
|
+
y: centerY + Math.sin(angle) * radius
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
return { width, height, nodes: positioned };
|
|
235
|
+
}
|
|
236
|
+
function nodeShape(positioned) {
|
|
237
|
+
const { node, x, y } = positioned;
|
|
238
|
+
const fill = NODE_COLORS[node.type] ?? "#94a3b8";
|
|
239
|
+
if (node.type === "module") {
|
|
240
|
+
return `<rect x="${(x - 32).toFixed(1)}" y="${(y - 18).toFixed(1)}" width="64" height="36" rx="10" fill="${fill}" stroke="#0f172a" stroke-width="2" />`;
|
|
241
|
+
}
|
|
242
|
+
if (node.type === "symbol") {
|
|
243
|
+
const points = [
|
|
244
|
+
`${x.toFixed(1)},${(y - 18).toFixed(1)}`,
|
|
245
|
+
`${(x + 18).toFixed(1)},${y.toFixed(1)}`,
|
|
246
|
+
`${x.toFixed(1)},${(y + 18).toFixed(1)}`,
|
|
247
|
+
`${(x - 18).toFixed(1)},${y.toFixed(1)}`
|
|
248
|
+
].join(" ");
|
|
249
|
+
return `<polygon points="${points}" fill="${fill}" stroke="#0f172a" stroke-width="2" />`;
|
|
250
|
+
}
|
|
251
|
+
if (node.type === "rationale") {
|
|
252
|
+
const points = [
|
|
253
|
+
`${(x - 18).toFixed(1)},${(y - 10).toFixed(1)}`,
|
|
254
|
+
`${x.toFixed(1)},${(y - 20).toFixed(1)}`,
|
|
255
|
+
`${(x + 18).toFixed(1)},${(y - 10).toFixed(1)}`,
|
|
256
|
+
`${(x + 18).toFixed(1)},${(y + 10).toFixed(1)}`,
|
|
257
|
+
`${x.toFixed(1)},${(y + 20).toFixed(1)}`,
|
|
258
|
+
`${(x - 18).toFixed(1)},${(y + 10).toFixed(1)}`
|
|
259
|
+
].join(" ");
|
|
260
|
+
return `<polygon points="${points}" fill="${fill}" stroke="#0f172a" stroke-width="2" />`;
|
|
261
|
+
}
|
|
262
|
+
return `<circle cx="${x.toFixed(1)}" cy="${y.toFixed(1)}" r="${node.isGodNode ? 24 : 18}" fill="${fill}" stroke="#0f172a" stroke-width="${node.isGodNode ? 3 : 2}" />`;
|
|
263
|
+
}
|
|
264
|
+
function nodeTitle(node, page) {
|
|
265
|
+
return [
|
|
266
|
+
node.label,
|
|
267
|
+
`id=${node.id}`,
|
|
268
|
+
`type=${node.type}`,
|
|
269
|
+
node.communityId ? `community=${node.communityId}` : "",
|
|
270
|
+
page ? `page=${page.path}` : "",
|
|
271
|
+
node.degree !== void 0 ? `degree=${node.degree}` : "",
|
|
272
|
+
node.bridgeScore !== void 0 ? `bridge=${node.bridgeScore}` : ""
|
|
273
|
+
].filter(Boolean).join("\n");
|
|
274
|
+
}
|
|
275
|
+
function renderSvg(graph) {
|
|
276
|
+
const layout = layoutGraph(graph);
|
|
277
|
+
const pageById2 = graphPageById(graph);
|
|
278
|
+
const positionedById = new Map(layout.nodes.map((item) => [item.node.id, item]));
|
|
279
|
+
const communityLabels = sortedCommunities(graph).map((community, index) => {
|
|
280
|
+
const col = index % Math.max(1, Math.ceil(Math.sqrt(Math.max(1, sortedCommunities(graph).length))));
|
|
281
|
+
const row = Math.floor(index / Math.max(1, Math.ceil(Math.sqrt(Math.max(1, sortedCommunities(graph).length)))));
|
|
282
|
+
return {
|
|
283
|
+
label: community.label,
|
|
284
|
+
x: 240 + col * 460,
|
|
285
|
+
y: 90 + row * 360
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
const lines = [
|
|
289
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
290
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${layout.width}" height="${layout.height}" viewBox="0 0 ${layout.width} ${layout.height}" role="img" aria-labelledby="title desc">`,
|
|
291
|
+
' <title id="title">SwarmVault Graph Export</title>',
|
|
292
|
+
` <desc id="desc">Nodes=${graph.nodes.length}, edges=${graph.edges.length}, communities=${graph.communities?.length ?? 0}</desc>`,
|
|
293
|
+
" <defs>",
|
|
294
|
+
' <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">',
|
|
295
|
+
' <path d="M 0 0 L 10 5 L 0 10 z" fill="#64748b" />',
|
|
296
|
+
" </marker>",
|
|
297
|
+
" </defs>",
|
|
298
|
+
' <rect width="100%" height="100%" fill="#020617" />'
|
|
299
|
+
];
|
|
300
|
+
for (const community of communityLabels) {
|
|
301
|
+
lines.push(
|
|
302
|
+
` <text x="${community.x.toFixed(1)}" y="${community.y.toFixed(1)}" fill="#cbd5e1" font-family="Avenir Next, Segoe UI, sans-serif" font-size="16" text-anchor="middle">${xmlEscape(community.label)}</text>`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
for (const edge of [...graph.edges].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
306
|
+
const source = positionedById.get(edge.source);
|
|
307
|
+
const target = positionedById.get(edge.target);
|
|
308
|
+
if (!source || !target) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
lines.push(
|
|
312
|
+
` <g data-edge-id="${xmlEscape(edge.id)}" data-relation="${xmlEscape(edge.relation)}" data-evidence-class="${xmlEscape(edge.evidenceClass)}">`,
|
|
313
|
+
` <title>${xmlEscape(
|
|
314
|
+
`${source.node.label} --${edge.relation}/${edge.evidenceClass}/${edge.confidence.toFixed(2)}--> ${target.node.label}`
|
|
315
|
+
)}</title>`,
|
|
316
|
+
` <line x1="${source.x.toFixed(1)}" y1="${source.y.toFixed(1)}" x2="${target.x.toFixed(1)}" y2="${target.y.toFixed(1)}" stroke="#64748b" stroke-opacity="0.55" stroke-width="${Math.max(
|
|
317
|
+
1.5,
|
|
318
|
+
Math.min(4, edge.confidence * 3)
|
|
319
|
+
).toFixed(1)}" marker-end="url(#arrow)" />`,
|
|
320
|
+
" </g>"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
for (const positioned of layout.nodes) {
|
|
324
|
+
const page = positioned.node.pageId ? pageById2.get(positioned.node.pageId) : void 0;
|
|
325
|
+
lines.push(
|
|
326
|
+
` <g data-node-id="${xmlEscape(positioned.node.id)}" data-node-type="${xmlEscape(positioned.node.type)}" data-community-id="${xmlEscape(positioned.node.communityId ?? "")}">`,
|
|
327
|
+
` <title>${xmlEscape(nodeTitle(positioned.node, page))}</title>`,
|
|
328
|
+
` ${nodeShape(positioned)}`,
|
|
329
|
+
` <text x="${positioned.x.toFixed(1)}" y="${(positioned.y + 34).toFixed(1)}" fill="#e2e8f0" font-family="Avenir Next, Segoe UI, sans-serif" font-size="11" text-anchor="middle">${xmlEscape(positioned.node.label)}</text>`,
|
|
330
|
+
" </g>"
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
lines.push("</svg>", "");
|
|
334
|
+
return lines.join("\n");
|
|
335
|
+
}
|
|
336
|
+
function graphMlData(value) {
|
|
337
|
+
if (Array.isArray(value)) {
|
|
338
|
+
return JSON.stringify(value);
|
|
339
|
+
}
|
|
340
|
+
if (value === void 0 || value === null) {
|
|
341
|
+
return "";
|
|
342
|
+
}
|
|
343
|
+
return String(value);
|
|
344
|
+
}
|
|
345
|
+
function renderGraphMl(graph) {
|
|
346
|
+
const pageById2 = graphPageById(graph);
|
|
347
|
+
const keys = [
|
|
348
|
+
{ id: "n_label", for: "node", name: "label", type: "string" },
|
|
349
|
+
{ id: "n_type", for: "node", name: "type", type: "string" },
|
|
350
|
+
{ id: "n_page", for: "node", name: "pageId", type: "string" },
|
|
351
|
+
{ id: "n_page_path", for: "node", name: "pagePath", type: "string" },
|
|
352
|
+
{ id: "n_language", for: "node", name: "language", type: "string" },
|
|
353
|
+
{ id: "n_symbol_kind", for: "node", name: "symbolKind", type: "string" },
|
|
354
|
+
{ id: "n_project_ids", for: "node", name: "projectIds", type: "string" },
|
|
355
|
+
{ id: "n_source_ids", for: "node", name: "sourceIds", type: "string" },
|
|
356
|
+
{ id: "n_community", for: "node", name: "communityId", type: "string" },
|
|
357
|
+
{ id: "n_degree", for: "node", name: "degree", type: "double" },
|
|
358
|
+
{ id: "n_bridge", for: "node", name: "bridgeScore", type: "double" },
|
|
359
|
+
{ id: "e_relation", for: "edge", name: "relation", type: "string" },
|
|
360
|
+
{ id: "e_status", for: "edge", name: "status", type: "string" },
|
|
361
|
+
{ id: "e_evidence", for: "edge", name: "evidenceClass", type: "string" },
|
|
362
|
+
{ id: "e_confidence", for: "edge", name: "confidence", type: "double" },
|
|
363
|
+
{ id: "e_provenance", for: "edge", name: "provenance", type: "string" }
|
|
364
|
+
];
|
|
365
|
+
const lines = [
|
|
366
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
367
|
+
'<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">'
|
|
368
|
+
];
|
|
369
|
+
for (const key of keys) {
|
|
370
|
+
lines.push(` <key id="${key.id}" for="${key.for}" attr.name="${key.name}" attr.type="${key.type}" />`);
|
|
371
|
+
}
|
|
372
|
+
lines.push(' <graph id="swarmvault" edgedefault="directed">');
|
|
373
|
+
for (const node of [...graph.nodes].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
374
|
+
const page = node.pageId ? pageById2.get(node.pageId) : void 0;
|
|
375
|
+
lines.push(` <node id="${xmlEscape(node.id)}">`);
|
|
376
|
+
const dataEntries = [
|
|
377
|
+
["n_label", node.label],
|
|
378
|
+
["n_type", node.type],
|
|
379
|
+
["n_page", node.pageId],
|
|
380
|
+
["n_page_path", page?.path],
|
|
381
|
+
["n_language", node.language],
|
|
382
|
+
["n_symbol_kind", node.symbolKind],
|
|
383
|
+
["n_project_ids", node.projectIds],
|
|
384
|
+
["n_source_ids", node.sourceIds],
|
|
385
|
+
["n_community", node.communityId],
|
|
386
|
+
["n_degree", node.degree],
|
|
387
|
+
["n_bridge", node.bridgeScore]
|
|
388
|
+
];
|
|
389
|
+
for (const [key, value] of dataEntries) {
|
|
390
|
+
if (value === void 0) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
lines.push(` <data key="${key}">${xmlEscape(graphMlData(value))}</data>`);
|
|
394
|
+
}
|
|
395
|
+
lines.push(" </node>");
|
|
396
|
+
}
|
|
397
|
+
for (const edge of [...graph.edges].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
398
|
+
lines.push(` <edge id="${xmlEscape(edge.id)}" source="${xmlEscape(edge.source)}" target="${xmlEscape(edge.target)}">`);
|
|
399
|
+
for (const [key, value] of [
|
|
400
|
+
["e_relation", edge.relation],
|
|
401
|
+
["e_status", edge.status],
|
|
402
|
+
["e_evidence", edge.evidenceClass],
|
|
403
|
+
["e_confidence", edge.confidence],
|
|
404
|
+
["e_provenance", edge.provenance]
|
|
405
|
+
]) {
|
|
406
|
+
lines.push(` <data key="${key}">${xmlEscape(graphMlData(value))}</data>`);
|
|
407
|
+
}
|
|
408
|
+
lines.push(" </edge>");
|
|
409
|
+
}
|
|
410
|
+
lines.push(" </graph>", "</graphml>", "");
|
|
411
|
+
return lines.join("\n");
|
|
412
|
+
}
|
|
413
|
+
function renderCypher(graph) {
|
|
414
|
+
const pageById2 = graphPageById(graph);
|
|
415
|
+
const lines = ["// Neo4j Cypher import generated by SwarmVault", ""];
|
|
416
|
+
for (const node of [...graph.nodes].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
417
|
+
const page = node.pageId ? pageById2.get(node.pageId) : void 0;
|
|
418
|
+
const props = [
|
|
419
|
+
`id: '${cypherEscape(node.id)}'`,
|
|
420
|
+
`label: '${cypherEscape(node.label)}'`,
|
|
421
|
+
`type: '${cypherEscape(node.type)}'`,
|
|
422
|
+
`sourceIds: '${cypherEscape(JSON.stringify(node.sourceIds))}'`,
|
|
423
|
+
`projectIds: '${cypherEscape(JSON.stringify(node.projectIds))}'`,
|
|
424
|
+
node.pageId ? `pageId: '${cypherEscape(node.pageId)}'` : "",
|
|
425
|
+
page?.path ? `pagePath: '${cypherEscape(page.path)}'` : "",
|
|
426
|
+
node.language ? `language: '${cypherEscape(node.language)}'` : "",
|
|
427
|
+
node.symbolKind ? `symbolKind: '${cypherEscape(node.symbolKind)}'` : "",
|
|
428
|
+
node.communityId ? `communityId: '${cypherEscape(node.communityId)}'` : "",
|
|
429
|
+
node.degree !== void 0 ? `degree: ${node.degree}` : "",
|
|
430
|
+
node.bridgeScore !== void 0 ? `bridgeScore: ${node.bridgeScore}` : "",
|
|
431
|
+
node.isGodNode !== void 0 ? `isGodNode: ${node.isGodNode}` : ""
|
|
432
|
+
].filter(Boolean).join(", ");
|
|
433
|
+
lines.push(`MERGE (n:SwarmNode {id: '${cypherEscape(node.id)}'}) SET n += { ${props} };`);
|
|
434
|
+
}
|
|
435
|
+
lines.push("");
|
|
436
|
+
for (const edge of [...graph.edges].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
437
|
+
lines.push(
|
|
438
|
+
`MATCH (a:SwarmNode {id: '${cypherEscape(edge.source)}'}), (b:SwarmNode {id: '${cypherEscape(edge.target)}'})`,
|
|
439
|
+
`MERGE (a)-[r:${relationType(edge.relation)} {id: '${cypherEscape(edge.id)}'}]->(b)`,
|
|
440
|
+
`SET r += { relation: '${cypherEscape(edge.relation)}', status: '${cypherEscape(edge.status)}', evidenceClass: '${cypherEscape(
|
|
441
|
+
edge.evidenceClass
|
|
442
|
+
)}', confidence: ${edge.confidence}, provenance: '${cypherEscape(JSON.stringify(edge.provenance))}' };`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
lines.push("");
|
|
446
|
+
return lines.join("\n");
|
|
447
|
+
}
|
|
448
|
+
async function loadGraph(rootDir) {
|
|
449
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
450
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
451
|
+
if (!graph) {
|
|
452
|
+
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
453
|
+
}
|
|
454
|
+
return graph;
|
|
455
|
+
}
|
|
456
|
+
async function writeGraphExport(outputPath, content) {
|
|
457
|
+
await ensureDir(path2.dirname(outputPath));
|
|
458
|
+
await fs2.writeFile(outputPath, content, "utf8");
|
|
459
|
+
return path2.resolve(outputPath);
|
|
460
|
+
}
|
|
461
|
+
async function exportGraphFormat(rootDir, format, outputPath) {
|
|
462
|
+
const graph = await loadGraph(rootDir);
|
|
463
|
+
const rendered = format === "svg" ? renderSvg(graph) : format === "graphml" ? renderGraphMl(graph) : renderCypher(graph);
|
|
464
|
+
const resolvedPath = await writeGraphExport(outputPath, rendered);
|
|
465
|
+
return { format, outputPath: resolvedPath };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/hooks.ts
|
|
469
|
+
import fs3 from "fs/promises";
|
|
470
|
+
import path3 from "path";
|
|
471
|
+
var hookStart = "# >>> swarmvault hook >>>";
|
|
472
|
+
var hookEnd = "# <<< swarmvault hook <<<";
|
|
473
|
+
async function findNearestGitRoot(startPath) {
|
|
474
|
+
let current = path3.resolve(startPath);
|
|
475
|
+
try {
|
|
476
|
+
const stat = await fs3.stat(current);
|
|
477
|
+
if (!stat.isDirectory()) {
|
|
478
|
+
current = path3.dirname(current);
|
|
479
|
+
}
|
|
480
|
+
} catch {
|
|
481
|
+
current = path3.dirname(current);
|
|
482
|
+
}
|
|
483
|
+
while (true) {
|
|
484
|
+
if (await fileExists(path3.join(current, ".git"))) {
|
|
485
|
+
return current;
|
|
486
|
+
}
|
|
487
|
+
const parent = path3.dirname(current);
|
|
488
|
+
if (parent === current) {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
current = parent;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function shellQuote(value) {
|
|
495
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
496
|
+
}
|
|
497
|
+
function managedHookBlock(vaultRoot) {
|
|
498
|
+
return [
|
|
499
|
+
hookStart,
|
|
500
|
+
`cd ${shellQuote(vaultRoot)} || exit 0`,
|
|
501
|
+
"if command -v swarmvault >/dev/null 2>&1; then",
|
|
502
|
+
" swarmvault watch --repo --once >/dev/null 2>&1 || printf '[swarmvault hook] refresh failed\\n' >&2",
|
|
503
|
+
"fi",
|
|
504
|
+
hookEnd,
|
|
505
|
+
""
|
|
506
|
+
].join("\n");
|
|
507
|
+
}
|
|
508
|
+
function hookPath(repoRoot, hookName) {
|
|
509
|
+
return path3.join(repoRoot, ".git", "hooks", hookName);
|
|
510
|
+
}
|
|
511
|
+
async function readHookStatus(filePath) {
|
|
512
|
+
if (!await fileExists(filePath)) {
|
|
513
|
+
return "not_installed";
|
|
514
|
+
}
|
|
515
|
+
const content = await fs3.readFile(filePath, "utf8");
|
|
516
|
+
return content.includes(hookStart) && content.includes(hookEnd) ? "installed" : "other_content";
|
|
517
|
+
}
|
|
518
|
+
async function upsertHookFile(filePath, block) {
|
|
519
|
+
const existing = await fileExists(filePath) ? await fs3.readFile(filePath, "utf8") : "";
|
|
520
|
+
let next;
|
|
521
|
+
const startIndex = existing.indexOf(hookStart);
|
|
522
|
+
const endIndex = existing.indexOf(hookEnd);
|
|
523
|
+
if (startIndex !== -1 && endIndex !== -1) {
|
|
524
|
+
next = `${existing.slice(0, startIndex)}${block}${existing.slice(endIndex + hookEnd.length)}`.trimEnd();
|
|
525
|
+
} else if (existing.trim().length > 0) {
|
|
526
|
+
next = `${existing.trimEnd()}
|
|
527
|
+
|
|
528
|
+
${block}`.trimEnd();
|
|
529
|
+
} else {
|
|
530
|
+
next = `#!/bin/sh
|
|
531
|
+
${block}`.trimEnd();
|
|
532
|
+
}
|
|
533
|
+
await ensureDir(path3.dirname(filePath));
|
|
534
|
+
await fs3.writeFile(filePath, `${next}
|
|
535
|
+
`, { mode: 493, encoding: "utf8" });
|
|
536
|
+
await fs3.chmod(filePath, 493);
|
|
537
|
+
}
|
|
538
|
+
async function removeHookBlock(filePath) {
|
|
539
|
+
if (!await fileExists(filePath)) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const existing = await fs3.readFile(filePath, "utf8");
|
|
543
|
+
const startIndex = existing.indexOf(hookStart);
|
|
544
|
+
const endIndex = existing.indexOf(hookEnd);
|
|
545
|
+
if (startIndex === -1 || endIndex === -1) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const next = `${existing.slice(0, startIndex)}${existing.slice(endIndex + hookEnd.length)}`.trim();
|
|
549
|
+
if (!next || next === "#!/bin/sh") {
|
|
550
|
+
await fs3.rm(filePath, { force: true });
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
await fs3.writeFile(filePath, `${next}
|
|
554
|
+
`, "utf8");
|
|
555
|
+
}
|
|
556
|
+
async function getGitHookStatus(rootDir) {
|
|
557
|
+
const repoRoot = await findNearestGitRoot(rootDir);
|
|
558
|
+
if (!repoRoot) {
|
|
559
|
+
return {
|
|
560
|
+
repoRoot: null,
|
|
561
|
+
postCommit: "not_installed",
|
|
562
|
+
postCheckout: "not_installed"
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
repoRoot,
|
|
567
|
+
postCommit: await readHookStatus(hookPath(repoRoot, "post-commit")),
|
|
568
|
+
postCheckout: await readHookStatus(hookPath(repoRoot, "post-checkout"))
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
async function installGitHooks(rootDir) {
|
|
572
|
+
const repoRoot = await findNearestGitRoot(rootDir);
|
|
573
|
+
if (!repoRoot) {
|
|
574
|
+
throw new Error("No git repository found above the current vault.");
|
|
575
|
+
}
|
|
576
|
+
const block = managedHookBlock(path3.resolve(rootDir));
|
|
577
|
+
await upsertHookFile(hookPath(repoRoot, "post-commit"), block);
|
|
578
|
+
await upsertHookFile(hookPath(repoRoot, "post-checkout"), block);
|
|
579
|
+
return getGitHookStatus(rootDir);
|
|
580
|
+
}
|
|
581
|
+
async function uninstallGitHooks(rootDir) {
|
|
582
|
+
const repoRoot = await findNearestGitRoot(rootDir);
|
|
583
|
+
if (!repoRoot) {
|
|
584
|
+
return {
|
|
585
|
+
repoRoot: null,
|
|
586
|
+
postCommit: "not_installed",
|
|
587
|
+
postCheckout: "not_installed"
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
await removeHookBlock(hookPath(repoRoot, "post-commit"));
|
|
591
|
+
await removeHookBlock(hookPath(repoRoot, "post-checkout"));
|
|
592
|
+
return getGitHookStatus(rootDir);
|
|
129
593
|
}
|
|
130
594
|
|
|
131
595
|
// src/ingest.ts
|
|
132
|
-
import
|
|
133
|
-
import
|
|
596
|
+
import fs8 from "fs/promises";
|
|
597
|
+
import path8 from "path";
|
|
134
598
|
import { Readability } from "@mozilla/readability";
|
|
135
599
|
import ignore from "ignore";
|
|
136
600
|
import { JSDOM } from "jsdom";
|
|
@@ -138,16 +602,16 @@ import mime from "mime-types";
|
|
|
138
602
|
import TurndownService from "turndown";
|
|
139
603
|
|
|
140
604
|
// src/code-analysis.ts
|
|
141
|
-
import
|
|
142
|
-
import
|
|
605
|
+
import fs5 from "fs/promises";
|
|
606
|
+
import path5 from "path";
|
|
143
607
|
import ts from "typescript";
|
|
144
608
|
|
|
145
609
|
// src/code-tree-sitter.ts
|
|
146
|
-
import
|
|
610
|
+
import fs4 from "fs/promises";
|
|
147
611
|
import { createRequire } from "module";
|
|
148
|
-
import
|
|
612
|
+
import path4 from "path";
|
|
149
613
|
var require2 = createRequire(import.meta.url);
|
|
150
|
-
var TREE_SITTER_PACKAGE_ROOT =
|
|
614
|
+
var TREE_SITTER_PACKAGE_ROOT = path4.dirname(path4.dirname(require2.resolve("@vscode/tree-sitter-wasm")));
|
|
151
615
|
var RATIONALE_MARKERS = ["NOTE:", "IMPORTANT:", "HACK:", "WHY:", "RATIONALE:"];
|
|
152
616
|
function stripKnownCommentPrefix(line) {
|
|
153
617
|
let next = line.trim();
|
|
@@ -169,7 +633,9 @@ var grammarFileByLanguage = {
|
|
|
169
633
|
csharp: "tree-sitter-c-sharp.wasm",
|
|
170
634
|
c: "tree-sitter-cpp.wasm",
|
|
171
635
|
cpp: "tree-sitter-cpp.wasm",
|
|
172
|
-
php: "tree-sitter-php.wasm"
|
|
636
|
+
php: "tree-sitter-php.wasm",
|
|
637
|
+
ruby: "tree-sitter-ruby.wasm",
|
|
638
|
+
powershell: "tree-sitter-powershell.wasm"
|
|
173
639
|
};
|
|
174
640
|
async function getTreeSitterModule() {
|
|
175
641
|
if (!treeSitterModulePromise) {
|
|
@@ -182,7 +648,7 @@ async function getTreeSitterModule() {
|
|
|
182
648
|
async function ensureTreeSitterInit(module) {
|
|
183
649
|
if (!treeSitterInitPromise) {
|
|
184
650
|
treeSitterInitPromise = module.Parser.init({
|
|
185
|
-
locateFile: () =>
|
|
651
|
+
locateFile: () => path4.join(TREE_SITTER_PACKAGE_ROOT, "wasm", "tree-sitter.wasm")
|
|
186
652
|
});
|
|
187
653
|
}
|
|
188
654
|
return treeSitterInitPromise;
|
|
@@ -195,7 +661,7 @@ async function loadLanguage(language) {
|
|
|
195
661
|
const loader = (async () => {
|
|
196
662
|
const module = await getTreeSitterModule();
|
|
197
663
|
await ensureTreeSitterInit(module);
|
|
198
|
-
const bytes = await
|
|
664
|
+
const bytes = await fs4.readFile(path4.join(TREE_SITTER_PACKAGE_ROOT, "wasm", grammarFileByLanguage[language]));
|
|
199
665
|
return module.Language.load(bytes);
|
|
200
666
|
})();
|
|
201
667
|
languageCache.set(language, loader);
|
|
@@ -212,16 +678,16 @@ function stripCodeExtension(filePath) {
|
|
|
212
678
|
return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
|
|
213
679
|
}
|
|
214
680
|
function manifestModuleName(manifest, language) {
|
|
215
|
-
const repoPath = manifest.repoRelativePath ??
|
|
681
|
+
const repoPath = manifest.repoRelativePath ?? path4.basename(manifest.originalPath ?? manifest.storedPath);
|
|
216
682
|
const normalized = toPosix(stripCodeExtension(repoPath)).replace(/^\.\/+/, "");
|
|
217
683
|
if (!normalized) {
|
|
218
684
|
return void 0;
|
|
219
685
|
}
|
|
220
686
|
if (language === "python") {
|
|
221
687
|
const dotted = normalized.replace(/\/__init__$/i, "").replace(/\//g, ".").replace(/^src\./, "");
|
|
222
|
-
return dotted ||
|
|
688
|
+
return dotted || path4.posix.basename(normalized);
|
|
223
689
|
}
|
|
224
|
-
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) ||
|
|
690
|
+
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) || path4.posix.basename(normalized) : normalized;
|
|
225
691
|
}
|
|
226
692
|
function singleLineSignature(value) {
|
|
227
693
|
return truncate(
|
|
@@ -397,11 +863,29 @@ function extractIdentifier(node) {
|
|
|
397
863
|
if (!node) {
|
|
398
864
|
return void 0;
|
|
399
865
|
}
|
|
400
|
-
if ([
|
|
866
|
+
if ([
|
|
867
|
+
"identifier",
|
|
868
|
+
"field_identifier",
|
|
869
|
+
"type_identifier",
|
|
870
|
+
"name",
|
|
871
|
+
"package_identifier",
|
|
872
|
+
"constant",
|
|
873
|
+
"simple_name",
|
|
874
|
+
"function_name"
|
|
875
|
+
].includes(node.type)) {
|
|
401
876
|
return node.text.trim();
|
|
402
877
|
}
|
|
403
878
|
const preferred = node.childForFieldName("name") ?? node.namedChildren.find(
|
|
404
|
-
(child) => child && [
|
|
879
|
+
(child) => child && [
|
|
880
|
+
"identifier",
|
|
881
|
+
"field_identifier",
|
|
882
|
+
"type_identifier",
|
|
883
|
+
"name",
|
|
884
|
+
"package_identifier",
|
|
885
|
+
"constant",
|
|
886
|
+
"simple_name",
|
|
887
|
+
"function_name"
|
|
888
|
+
].includes(child.type)
|
|
405
889
|
) ?? node.namedChildren.at(-1) ?? null;
|
|
406
890
|
return preferred ? extractIdentifier(preferred) : void 0;
|
|
407
891
|
}
|
|
@@ -574,6 +1058,49 @@ function parseCppInclude(text) {
|
|
|
574
1058
|
reExport: false
|
|
575
1059
|
};
|
|
576
1060
|
}
|
|
1061
|
+
function rubyStringContent(node) {
|
|
1062
|
+
if (!node) {
|
|
1063
|
+
return void 0;
|
|
1064
|
+
}
|
|
1065
|
+
const contentNode = node.descendantsOfType(["string_content", "simple_symbol", "bare_string"]).find((item) => item !== null) ?? null;
|
|
1066
|
+
return contentNode?.text.trim() || void 0;
|
|
1067
|
+
}
|
|
1068
|
+
function parsePowerShellImport(commandNode) {
|
|
1069
|
+
const commandName = commandNode.descendantsOfType(["command_name", "command_name_expr"]).find((item) => item !== null)?.text.trim();
|
|
1070
|
+
const genericTokens = commandNode.descendantsOfType("generic_token").filter((item) => item !== null).map((item) => item.text.trim());
|
|
1071
|
+
if (commandNode.namedChildren.some((child) => child?.type === "command_invokation_operator")) {
|
|
1072
|
+
const specifier = commandName?.trim();
|
|
1073
|
+
if (specifier) {
|
|
1074
|
+
return {
|
|
1075
|
+
specifier,
|
|
1076
|
+
importedSymbols: [],
|
|
1077
|
+
isExternal: false,
|
|
1078
|
+
reExport: false
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (!commandName) {
|
|
1083
|
+
return void 0;
|
|
1084
|
+
}
|
|
1085
|
+
const lowerName = commandName.toLowerCase();
|
|
1086
|
+
if (lowerName === "using" && genericTokens.length >= 2 && genericTokens[0]?.toLowerCase() === "module") {
|
|
1087
|
+
return {
|
|
1088
|
+
specifier: genericTokens[1],
|
|
1089
|
+
importedSymbols: [],
|
|
1090
|
+
isExternal: !genericTokens[1]?.startsWith("."),
|
|
1091
|
+
reExport: false
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
if (lowerName === "import-module" && genericTokens[0]) {
|
|
1095
|
+
return {
|
|
1096
|
+
specifier: genericTokens[0],
|
|
1097
|
+
importedSymbols: [],
|
|
1098
|
+
isExternal: !genericTokens[0].startsWith("."),
|
|
1099
|
+
reExport: false
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
return void 0;
|
|
1103
|
+
}
|
|
577
1104
|
function pythonCodeAnalysis(manifest, rootNode, diagnostics) {
|
|
578
1105
|
const imports = [];
|
|
579
1106
|
const draftSymbols = [];
|
|
@@ -976,6 +1503,161 @@ function phpCodeAnalysis(manifest, rootNode, diagnostics) {
|
|
|
976
1503
|
namespace: namespaceName
|
|
977
1504
|
});
|
|
978
1505
|
}
|
|
1506
|
+
function rubyCodeAnalysis(manifest, rootNode, diagnostics) {
|
|
1507
|
+
const imports = [];
|
|
1508
|
+
const draftSymbols = [];
|
|
1509
|
+
const exportLabels = [];
|
|
1510
|
+
let namespaceName;
|
|
1511
|
+
const visitStatements = (node, scopeName, namespaceParts = []) => {
|
|
1512
|
+
if (!node) {
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
for (const child of node.namedChildren) {
|
|
1516
|
+
if (!child) {
|
|
1517
|
+
continue;
|
|
1518
|
+
}
|
|
1519
|
+
if (child.type === "call") {
|
|
1520
|
+
const callee = extractIdentifier(child.namedChildren.at(0) ?? null);
|
|
1521
|
+
if (callee === "require" || callee === "require_relative") {
|
|
1522
|
+
const specifier = rubyStringContent(child.childForFieldName("arguments") ?? child.namedChildren.at(1) ?? null);
|
|
1523
|
+
if (specifier) {
|
|
1524
|
+
imports.push({
|
|
1525
|
+
specifier,
|
|
1526
|
+
importedSymbols: [],
|
|
1527
|
+
isExternal: callee === "require" && !specifier.startsWith("."),
|
|
1528
|
+
reExport: false
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
if (child.type === "module") {
|
|
1535
|
+
const moduleName = extractIdentifier(child.childForFieldName("name") ?? child.namedChildren.at(0) ?? null);
|
|
1536
|
+
if (!moduleName) {
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
const nextNamespace = [...namespaceParts, moduleName];
|
|
1540
|
+
namespaceName ??= nextNamespace.join("::");
|
|
1541
|
+
visitStatements(findNamedChild(child, "body_statement"), void 0, nextNamespace);
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
if (child.type === "class") {
|
|
1545
|
+
const className = extractIdentifier(child.childForFieldName("name") ?? child.namedChildren.at(0) ?? null);
|
|
1546
|
+
if (!className) {
|
|
1547
|
+
continue;
|
|
1548
|
+
}
|
|
1549
|
+
const body = findNamedChild(child, "body_statement");
|
|
1550
|
+
const mixins = body ? body.namedChildren.filter((item) => item !== null && item.type === "call").filter((item) => extractIdentifier(item.namedChildren.at(0) ?? null) === "include").flatMap(
|
|
1551
|
+
(item) => item.descendantsOfType(["constant", "identifier"]).filter((descendant) => descendant !== null).slice(1).map((descendant) => normalizeSymbolReference(descendant.text)).filter(Boolean)
|
|
1552
|
+
) : [];
|
|
1553
|
+
draftSymbols.push({
|
|
1554
|
+
name: scopeName ? `${scopeName}::${className}` : className,
|
|
1555
|
+
kind: "class",
|
|
1556
|
+
signature: singleLineSignature(child.text),
|
|
1557
|
+
exported: true,
|
|
1558
|
+
callNames: [],
|
|
1559
|
+
extendsNames: descendantTypeNames(child.childForFieldName("superclass")),
|
|
1560
|
+
implementsNames: uniqueBy(mixins, (item) => item),
|
|
1561
|
+
bodyText: body?.text
|
|
1562
|
+
});
|
|
1563
|
+
exportLabels.push(scopeName ? `${scopeName}::${className}` : className);
|
|
1564
|
+
visitStatements(body, scopeName ? `${scopeName}::${className}` : className, namespaceParts);
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
if (child.type === "method") {
|
|
1568
|
+
const methodName = extractIdentifier(child.childForFieldName("name") ?? child.namedChildren.at(0) ?? null);
|
|
1569
|
+
if (!methodName) {
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
const symbolName = scopeName ? `${scopeName}#${methodName}` : methodName;
|
|
1573
|
+
draftSymbols.push({
|
|
1574
|
+
name: symbolName,
|
|
1575
|
+
kind: "function",
|
|
1576
|
+
signature: singleLineSignature(child.text),
|
|
1577
|
+
exported: true,
|
|
1578
|
+
callNames: [],
|
|
1579
|
+
extendsNames: [],
|
|
1580
|
+
implementsNames: [],
|
|
1581
|
+
bodyText: nodeText(findNamedChild(child, "body_statement") ?? child.childForFieldName("body"))
|
|
1582
|
+
});
|
|
1583
|
+
exportLabels.push(symbolName);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
visitStatements(rootNode, void 0, []);
|
|
1588
|
+
return finalizeCodeAnalysis(manifest, "ruby", imports, draftSymbols, exportLabels, diagnostics, {
|
|
1589
|
+
namespace: namespaceName
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
function powershellCodeAnalysis(manifest, rootNode, diagnostics) {
|
|
1593
|
+
const imports = [];
|
|
1594
|
+
const draftSymbols = [];
|
|
1595
|
+
const exportLabels = [];
|
|
1596
|
+
for (const child of rootNode.descendantsOfType(["command", "class_statement", "function_statement"]).filter((item) => item !== null)) {
|
|
1597
|
+
if (child.type === "command") {
|
|
1598
|
+
const parsed = parsePowerShellImport(child);
|
|
1599
|
+
if (parsed) {
|
|
1600
|
+
imports.push(parsed);
|
|
1601
|
+
}
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
if (child.type === "class_statement") {
|
|
1605
|
+
const names = child.namedChildren.filter((item) => item !== null && item.type === "simple_name").map((item) => item.text.trim());
|
|
1606
|
+
const className = names[0];
|
|
1607
|
+
if (!className) {
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
draftSymbols.push({
|
|
1611
|
+
name: className,
|
|
1612
|
+
kind: "class",
|
|
1613
|
+
signature: singleLineSignature(child.text),
|
|
1614
|
+
exported: true,
|
|
1615
|
+
callNames: [],
|
|
1616
|
+
extendsNames: names.slice(1, 2),
|
|
1617
|
+
implementsNames: [],
|
|
1618
|
+
bodyText: nodeText(child.childForFieldName("body")) || child.text
|
|
1619
|
+
});
|
|
1620
|
+
exportLabels.push(className);
|
|
1621
|
+
for (const methodNode of child.descendantsOfType("class_method_definition").filter((item) => item !== null)) {
|
|
1622
|
+
const methodName = methodNode.descendantsOfType("simple_name").filter((item) => item !== null).map((item) => item.text.trim())[0];
|
|
1623
|
+
if (!methodName) {
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
const symbolName = `${className}.${methodName}`;
|
|
1627
|
+
draftSymbols.push({
|
|
1628
|
+
name: symbolName,
|
|
1629
|
+
kind: "function",
|
|
1630
|
+
signature: singleLineSignature(methodNode.text),
|
|
1631
|
+
exported: true,
|
|
1632
|
+
callNames: [],
|
|
1633
|
+
extendsNames: [],
|
|
1634
|
+
implementsNames: [],
|
|
1635
|
+
bodyText: nodeText(findNamedChild(methodNode, "script_block") ?? methodNode.childForFieldName("body")) || methodNode.text
|
|
1636
|
+
});
|
|
1637
|
+
exportLabels.push(symbolName);
|
|
1638
|
+
}
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
if (child.type === "function_statement") {
|
|
1642
|
+
const functionName = extractIdentifier(findNamedChild(child, "function_name") ?? child.childForFieldName("name"));
|
|
1643
|
+
if (!functionName) {
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
draftSymbols.push({
|
|
1647
|
+
name: functionName,
|
|
1648
|
+
kind: "function",
|
|
1649
|
+
signature: singleLineSignature(child.text),
|
|
1650
|
+
exported: true,
|
|
1651
|
+
callNames: [],
|
|
1652
|
+
extendsNames: [],
|
|
1653
|
+
implementsNames: [],
|
|
1654
|
+
bodyText: nodeText(findNamedChild(child, "script_block") ?? child.childForFieldName("body")) || child.text
|
|
1655
|
+
});
|
|
1656
|
+
exportLabels.push(functionName);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return finalizeCodeAnalysis(manifest, "powershell", imports, draftSymbols, exportLabels, diagnostics);
|
|
1660
|
+
}
|
|
979
1661
|
function cFamilyCodeAnalysis(manifest, language, rootNode, diagnostics) {
|
|
980
1662
|
const imports = [];
|
|
981
1663
|
const draftSymbols = [];
|
|
@@ -1094,6 +1776,10 @@ async function analyzeTreeSitterCode(manifest, content, language) {
|
|
|
1094
1776
|
return { code: csharpCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
|
|
1095
1777
|
case "php":
|
|
1096
1778
|
return { code: phpCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
|
|
1779
|
+
case "ruby":
|
|
1780
|
+
return { code: rubyCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
|
|
1781
|
+
case "powershell":
|
|
1782
|
+
return { code: powershellCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
|
|
1097
1783
|
case "c":
|
|
1098
1784
|
case "cpp":
|
|
1099
1785
|
return { code: cFamilyCodeAnalysis(manifest, language, tree.rootNode, diagnostics), rationales };
|
|
@@ -1355,16 +2041,16 @@ function stripCodeExtension2(filePath) {
|
|
|
1355
2041
|
return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
|
|
1356
2042
|
}
|
|
1357
2043
|
function manifestModuleName2(manifest, language) {
|
|
1358
|
-
const repoPath = manifest.repoRelativePath ??
|
|
2044
|
+
const repoPath = manifest.repoRelativePath ?? path5.basename(manifest.originalPath ?? manifest.storedPath);
|
|
1359
2045
|
const normalized = toPosix(stripCodeExtension2(repoPath)).replace(/^\.\/+/, "");
|
|
1360
2046
|
if (!normalized) {
|
|
1361
2047
|
return void 0;
|
|
1362
2048
|
}
|
|
1363
2049
|
if (language === "python") {
|
|
1364
2050
|
const dotted = normalized.replace(/\/__init__$/i, "").replace(/\//g, ".").replace(/^src\./, "");
|
|
1365
|
-
return dotted ||
|
|
2051
|
+
return dotted || path5.posix.basename(normalized);
|
|
1366
2052
|
}
|
|
1367
|
-
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) ||
|
|
2053
|
+
return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) || path5.posix.basename(normalized) : normalized;
|
|
1368
2054
|
}
|
|
1369
2055
|
function finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, exportLabels, diagnostics, metadata) {
|
|
1370
2056
|
const topLevelNames = new Set(draftSymbols.map((symbol) => symbol.name));
|
|
@@ -1657,7 +2343,7 @@ function analyzeTypeScriptLikeCode(manifest, content) {
|
|
|
1657
2343
|
};
|
|
1658
2344
|
}
|
|
1659
2345
|
function inferCodeLanguage(filePath, mimeType = "") {
|
|
1660
|
-
const extension =
|
|
2346
|
+
const extension = path5.extname(filePath).toLowerCase();
|
|
1661
2347
|
if (extension === ".ts" || extension === ".mts" || extension === ".cts") {
|
|
1662
2348
|
return "typescript";
|
|
1663
2349
|
}
|
|
@@ -1688,6 +2374,12 @@ function inferCodeLanguage(filePath, mimeType = "") {
|
|
|
1688
2374
|
if (extension === ".php") {
|
|
1689
2375
|
return "php";
|
|
1690
2376
|
}
|
|
2377
|
+
if (extension === ".rb") {
|
|
2378
|
+
return "ruby";
|
|
2379
|
+
}
|
|
2380
|
+
if (extension === ".ps1" || extension === ".psm1" || extension === ".psd1") {
|
|
2381
|
+
return "powershell";
|
|
2382
|
+
}
|
|
1691
2383
|
if (extension === ".c") {
|
|
1692
2384
|
return "c";
|
|
1693
2385
|
}
|
|
@@ -1700,12 +2392,12 @@ function modulePageTitle(manifest) {
|
|
|
1700
2392
|
return `${manifest.title} module`;
|
|
1701
2393
|
}
|
|
1702
2394
|
function importResolutionCandidates(basePath, specifier, extensions) {
|
|
1703
|
-
const resolved =
|
|
1704
|
-
if (
|
|
2395
|
+
const resolved = path5.posix.normalize(path5.posix.join(path5.posix.dirname(basePath), specifier));
|
|
2396
|
+
if (path5.posix.extname(resolved)) {
|
|
1705
2397
|
return [resolved];
|
|
1706
2398
|
}
|
|
1707
|
-
const direct = extensions.map((extension) =>
|
|
1708
|
-
const indexFiles = extensions.map((extension) =>
|
|
2399
|
+
const direct = extensions.map((extension) => path5.posix.normalize(`${resolved}${extension}`));
|
|
2400
|
+
const indexFiles = extensions.map((extension) => path5.posix.normalize(path5.posix.join(resolved, `index${extension}`)));
|
|
1709
2401
|
return uniqueBy([resolved, ...direct, ...indexFiles], (candidate) => candidate);
|
|
1710
2402
|
}
|
|
1711
2403
|
function normalizeAlias(value) {
|
|
@@ -1724,32 +2416,32 @@ function recordAlias(target, value) {
|
|
|
1724
2416
|
}
|
|
1725
2417
|
function manifestBasenameWithoutExtension(manifest) {
|
|
1726
2418
|
const target = manifest.repoRelativePath ?? manifest.originalPath ?? manifest.storedPath;
|
|
1727
|
-
return
|
|
2419
|
+
return path5.posix.basename(stripCodeExtension2(normalizeAlias(target)));
|
|
1728
2420
|
}
|
|
1729
2421
|
async function readNearestGoModulePath(startPath, cache) {
|
|
1730
|
-
let current =
|
|
2422
|
+
let current = path5.resolve(startPath);
|
|
1731
2423
|
try {
|
|
1732
|
-
const stat = await
|
|
2424
|
+
const stat = await fs5.stat(current);
|
|
1733
2425
|
if (!stat.isDirectory()) {
|
|
1734
|
-
current =
|
|
2426
|
+
current = path5.dirname(current);
|
|
1735
2427
|
}
|
|
1736
2428
|
} catch {
|
|
1737
|
-
current =
|
|
2429
|
+
current = path5.dirname(current);
|
|
1738
2430
|
}
|
|
1739
2431
|
while (true) {
|
|
1740
2432
|
if (cache.has(current)) {
|
|
1741
2433
|
const cached = cache.get(current);
|
|
1742
2434
|
return cached === null ? void 0 : cached;
|
|
1743
2435
|
}
|
|
1744
|
-
const goModPath =
|
|
1745
|
-
if (await
|
|
1746
|
-
const content = await
|
|
2436
|
+
const goModPath = path5.join(current, "go.mod");
|
|
2437
|
+
if (await fs5.access(goModPath).then(() => true).catch(() => false)) {
|
|
2438
|
+
const content = await fs5.readFile(goModPath, "utf8");
|
|
1747
2439
|
const match = content.match(/^\s*module\s+(\S+)/m);
|
|
1748
2440
|
const modulePath = match?.[1]?.trim() ?? null;
|
|
1749
2441
|
cache.set(current, modulePath);
|
|
1750
2442
|
return modulePath ?? void 0;
|
|
1751
2443
|
}
|
|
1752
|
-
const parent =
|
|
2444
|
+
const parent = path5.dirname(current);
|
|
1753
2445
|
if (parent === current) {
|
|
1754
2446
|
cache.set(current, null);
|
|
1755
2447
|
return void 0;
|
|
@@ -1784,6 +2476,10 @@ function candidateExtensionsFor(language) {
|
|
|
1784
2476
|
return [".cs"];
|
|
1785
2477
|
case "php":
|
|
1786
2478
|
return [".php"];
|
|
2479
|
+
case "ruby":
|
|
2480
|
+
return [".rb"];
|
|
2481
|
+
case "powershell":
|
|
2482
|
+
return [".ps1", ".psm1", ".psd1"];
|
|
1787
2483
|
case "c":
|
|
1788
2484
|
return [".c", ".h"];
|
|
1789
2485
|
case "cpp":
|
|
@@ -1826,10 +2522,10 @@ async function buildCodeIndex(rootDir, manifests, analyses) {
|
|
|
1826
2522
|
if (normalizedNamespace) {
|
|
1827
2523
|
recordAlias(aliases, normalizedNamespace);
|
|
1828
2524
|
}
|
|
1829
|
-
const originalPath = manifest.originalPath ?
|
|
2525
|
+
const originalPath = manifest.originalPath ? path5.resolve(manifest.originalPath) : path5.resolve(rootDir, manifest.storedPath);
|
|
1830
2526
|
const goModulePath = await readNearestGoModulePath(originalPath, goModuleCache);
|
|
1831
2527
|
if (goModulePath && repoRelativePath) {
|
|
1832
|
-
const dir =
|
|
2528
|
+
const dir = path5.posix.dirname(repoRelativePath);
|
|
1833
2529
|
const packageAlias = dir === "." ? goModulePath : `${goModulePath}/${dir}`;
|
|
1834
2530
|
recordAlias(aliases, packageAlias);
|
|
1835
2531
|
}
|
|
@@ -1846,6 +2542,10 @@ async function buildCodeIndex(rootDir, manifests, analyses) {
|
|
|
1846
2542
|
recordAlias(aliases, `${normalizedNamespace}\\${basename}`);
|
|
1847
2543
|
}
|
|
1848
2544
|
break;
|
|
2545
|
+
case "ruby":
|
|
2546
|
+
case "powershell":
|
|
2547
|
+
recordAlias(aliases, basename);
|
|
2548
|
+
break;
|
|
1849
2549
|
default:
|
|
1850
2550
|
break;
|
|
1851
2551
|
}
|
|
@@ -1901,10 +2601,10 @@ function resolvePythonRelativeAliases(repoRelativePath, specifier) {
|
|
|
1901
2601
|
const dotMatch = specifier.match(/^\.+/);
|
|
1902
2602
|
const depth = dotMatch ? dotMatch[0].length : 0;
|
|
1903
2603
|
const relativeModule = specifier.slice(depth).replace(/\./g, "/");
|
|
1904
|
-
const baseDir =
|
|
1905
|
-
const parentDir =
|
|
1906
|
-
const moduleBase = relativeModule ?
|
|
1907
|
-
return uniqueBy([`${moduleBase}.py`,
|
|
2604
|
+
const baseDir = path5.posix.dirname(repoRelativePath);
|
|
2605
|
+
const parentDir = path5.posix.normalize(path5.posix.join(baseDir, ...Array(Math.max(depth - 1, 0)).fill("..")));
|
|
2606
|
+
const moduleBase = relativeModule ? path5.posix.join(parentDir, relativeModule) : parentDir;
|
|
2607
|
+
return uniqueBy([`${moduleBase}.py`, path5.posix.join(moduleBase, "__init__.py")], (item) => item);
|
|
1908
2608
|
}
|
|
1909
2609
|
function resolveRustAliases(manifest, specifier) {
|
|
1910
2610
|
const repoRelativePath = manifest.repoRelativePath ? normalizeAlias(manifest.repoRelativePath) : "";
|
|
@@ -1945,13 +2645,20 @@ function findImportCandidates(manifest, codeImport, lookup) {
|
|
|
1945
2645
|
case "csharp":
|
|
1946
2646
|
return aliasMatches(lookup, codeImport.specifier);
|
|
1947
2647
|
case "php":
|
|
2648
|
+
case "ruby":
|
|
2649
|
+
case "powershell":
|
|
1948
2650
|
if (repoRelativePath && isLocalIncludeSpecifier(codeImport.specifier)) {
|
|
1949
2651
|
return repoPathMatches(
|
|
1950
2652
|
lookup,
|
|
1951
2653
|
...importResolutionCandidates(repoRelativePath, codeImport.specifier, candidateExtensionsFor(language))
|
|
1952
2654
|
);
|
|
1953
2655
|
}
|
|
1954
|
-
return aliasMatches(
|
|
2656
|
+
return aliasMatches(
|
|
2657
|
+
lookup,
|
|
2658
|
+
codeImport.specifier,
|
|
2659
|
+
codeImport.specifier.replace(/\\/g, "/"),
|
|
2660
|
+
stripCodeExtension2(codeImport.specifier.replace(/\\/g, "/"))
|
|
2661
|
+
);
|
|
1955
2662
|
case "rust":
|
|
1956
2663
|
return aliasMatches(lookup, codeImport.specifier, ...resolveRustAliases(manifest, codeImport.specifier));
|
|
1957
2664
|
case "c":
|
|
@@ -1977,6 +2684,8 @@ function importLooksLocal(manifest, codeImport, candidates) {
|
|
|
1977
2684
|
case "rust":
|
|
1978
2685
|
return /^(crate|self|super)::/.test(codeImport.specifier);
|
|
1979
2686
|
case "php":
|
|
2687
|
+
case "ruby":
|
|
2688
|
+
case "powershell":
|
|
1980
2689
|
case "c":
|
|
1981
2690
|
case "cpp":
|
|
1982
2691
|
return !codeImport.isExternal;
|
|
@@ -2036,18 +2745,18 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
|
2036
2745
|
}
|
|
2037
2746
|
|
|
2038
2747
|
// src/logs.ts
|
|
2039
|
-
import
|
|
2040
|
-
import
|
|
2748
|
+
import fs6 from "fs/promises";
|
|
2749
|
+
import path6 from "path";
|
|
2041
2750
|
import matter from "gray-matter";
|
|
2042
2751
|
async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
|
|
2043
2752
|
const { paths } = await initWorkspace(rootDir);
|
|
2044
2753
|
await ensureDir(paths.sessionsDir);
|
|
2045
2754
|
const timestamp = startedAt.replace(/[:.]/g, "-");
|
|
2046
2755
|
const baseName = `${timestamp}-${operation}-${slugify(title)}`;
|
|
2047
|
-
let candidate =
|
|
2756
|
+
let candidate = path6.join(paths.sessionsDir, `${baseName}.md`);
|
|
2048
2757
|
let counter = 2;
|
|
2049
2758
|
while (await fileExists(candidate)) {
|
|
2050
|
-
candidate =
|
|
2759
|
+
candidate = path6.join(paths.sessionsDir, `${baseName}-${counter}.md`);
|
|
2051
2760
|
counter++;
|
|
2052
2761
|
}
|
|
2053
2762
|
return candidate;
|
|
@@ -2055,11 +2764,11 @@ async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
|
|
|
2055
2764
|
async function appendLogEntry(rootDir, action, title, lines = []) {
|
|
2056
2765
|
const { paths } = await initWorkspace(rootDir);
|
|
2057
2766
|
await ensureDir(paths.wikiDir);
|
|
2058
|
-
const logPath =
|
|
2767
|
+
const logPath = path6.join(paths.wikiDir, "log.md");
|
|
2059
2768
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
2060
2769
|
const entry = [`## [${timestamp}] ${action} | ${title}`, ...lines.map((line) => `- ${line}`), ""].join("\n");
|
|
2061
|
-
const existing = await fileExists(logPath) ? await
|
|
2062
|
-
await
|
|
2770
|
+
const existing = await fileExists(logPath) ? await fs6.readFile(logPath, "utf8") : "# Log\n\n";
|
|
2771
|
+
await fs6.writeFile(logPath, `${existing}${entry}
|
|
2063
2772
|
`, "utf8");
|
|
2064
2773
|
}
|
|
2065
2774
|
async function recordSession(rootDir, input) {
|
|
@@ -2069,8 +2778,8 @@ async function recordSession(rootDir, input) {
|
|
|
2069
2778
|
const finishedAtIso = new Date(input.finishedAt ?? input.startedAt).toISOString();
|
|
2070
2779
|
const durationMs = Math.max(0, new Date(finishedAtIso).getTime() - new Date(startedAtIso).getTime());
|
|
2071
2780
|
const sessionPath = await resolveUniqueSessionPath(rootDir, input.operation, input.title, startedAtIso);
|
|
2072
|
-
const sessionId =
|
|
2073
|
-
const relativeSessionPath =
|
|
2781
|
+
const sessionId = path6.basename(sessionPath, ".md");
|
|
2782
|
+
const relativeSessionPath = path6.relative(rootDir, sessionPath).split(path6.sep).join(path6.posix.sep);
|
|
2074
2783
|
const frontmatter = Object.fromEntries(
|
|
2075
2784
|
Object.entries({
|
|
2076
2785
|
session_id: sessionId,
|
|
@@ -2118,7 +2827,7 @@ async function recordSession(rootDir, input) {
|
|
|
2118
2827
|
frontmatter
|
|
2119
2828
|
);
|
|
2120
2829
|
await writeFileIfChanged(sessionPath, content);
|
|
2121
|
-
const logPath =
|
|
2830
|
+
const logPath = path6.join(paths.wikiDir, "log.md");
|
|
2122
2831
|
const timestamp = startedAtIso.slice(0, 19).replace("T", " ");
|
|
2123
2832
|
const entry = [
|
|
2124
2833
|
`## [${timestamp}] ${input.operation} | ${input.title}`,
|
|
@@ -2126,8 +2835,8 @@ async function recordSession(rootDir, input) {
|
|
|
2126
2835
|
...(input.lines ?? []).map((line) => `- ${line}`),
|
|
2127
2836
|
""
|
|
2128
2837
|
].join("\n");
|
|
2129
|
-
const existing = await fileExists(logPath) ? await
|
|
2130
|
-
await
|
|
2838
|
+
const existing = await fileExists(logPath) ? await fs6.readFile(logPath, "utf8") : "# Log\n\n";
|
|
2839
|
+
await fs6.writeFile(logPath, `${existing}${entry}
|
|
2131
2840
|
`, "utf8");
|
|
2132
2841
|
return { sessionPath, sessionId };
|
|
2133
2842
|
}
|
|
@@ -2136,10 +2845,136 @@ async function appendWatchRun(rootDir, run) {
|
|
|
2136
2845
|
await appendJsonLine(paths.jobsLogPath, run);
|
|
2137
2846
|
}
|
|
2138
2847
|
|
|
2139
|
-
// src/
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2848
|
+
// src/watch-state.ts
|
|
2849
|
+
import fs7 from "fs/promises";
|
|
2850
|
+
import path7 from "path";
|
|
2851
|
+
import matter2 from "gray-matter";
|
|
2852
|
+
function pendingEntryKey(entry) {
|
|
2853
|
+
return entry.path;
|
|
2854
|
+
}
|
|
2855
|
+
function sortPending(entries) {
|
|
2856
|
+
return [...entries].sort(
|
|
2857
|
+
(left, right) => left.path.localeCompare(right.path) || left.detectedAt.localeCompare(right.detectedAt) || left.id.localeCompare(right.id)
|
|
2858
|
+
);
|
|
2859
|
+
}
|
|
2860
|
+
function normalizeRelativePath(rootDir, filePath) {
|
|
2861
|
+
if (!filePath) {
|
|
2862
|
+
return void 0;
|
|
2863
|
+
}
|
|
2864
|
+
return toPosix(path7.relative(rootDir, path7.resolve(filePath)));
|
|
2865
|
+
}
|
|
2866
|
+
async function readPendingSemanticRefresh(rootDir) {
|
|
2867
|
+
const { paths } = await initWorkspace(rootDir);
|
|
2868
|
+
const entries = await readJsonFile(paths.pendingSemanticRefreshPath);
|
|
2869
|
+
return Array.isArray(entries) ? sortPending(entries) : [];
|
|
2870
|
+
}
|
|
2871
|
+
async function writePendingSemanticRefresh(rootDir, entries) {
|
|
2872
|
+
const { paths } = await initWorkspace(rootDir);
|
|
2873
|
+
await ensureDir(paths.watchDir);
|
|
2874
|
+
const normalized = sortPending(entries);
|
|
2875
|
+
await writeJsonFile(paths.pendingSemanticRefreshPath, normalized);
|
|
2876
|
+
return normalized;
|
|
2877
|
+
}
|
|
2878
|
+
async function mergePendingSemanticRefresh(rootDir, entries) {
|
|
2879
|
+
const existing = await readPendingSemanticRefresh(rootDir);
|
|
2880
|
+
const merged = new Map(existing.map((entry) => [pendingEntryKey(entry), entry]));
|
|
2881
|
+
for (const entry of entries) {
|
|
2882
|
+
merged.set(pendingEntryKey(entry), entry);
|
|
2883
|
+
}
|
|
2884
|
+
return writePendingSemanticRefresh(rootDir, [...merged.values()]);
|
|
2885
|
+
}
|
|
2886
|
+
async function clearPendingSemanticRefreshEntries(rootDir, targets) {
|
|
2887
|
+
const existing = await readPendingSemanticRefresh(rootDir);
|
|
2888
|
+
const relativePath = targets.relativePath ?? normalizeRelativePath(rootDir, targets.originalPath);
|
|
2889
|
+
return writePendingSemanticRefresh(
|
|
2890
|
+
rootDir,
|
|
2891
|
+
existing.filter((entry) => {
|
|
2892
|
+
if (targets.sourceId && entry.sourceId === targets.sourceId) {
|
|
2893
|
+
return false;
|
|
2894
|
+
}
|
|
2895
|
+
if (relativePath && entry.path === relativePath) {
|
|
2896
|
+
return false;
|
|
2897
|
+
}
|
|
2898
|
+
return true;
|
|
2899
|
+
})
|
|
2900
|
+
);
|
|
2901
|
+
}
|
|
2902
|
+
async function readWatchStatusArtifact(rootDir) {
|
|
2903
|
+
const { paths } = await initWorkspace(rootDir);
|
|
2904
|
+
return readJsonFile(paths.watchStatusPath);
|
|
2905
|
+
}
|
|
2906
|
+
async function writeWatchStatusArtifact(rootDir, status) {
|
|
2907
|
+
const { paths } = await initWorkspace(rootDir);
|
|
2908
|
+
await ensureDir(paths.watchDir);
|
|
2909
|
+
await writeJsonFile(paths.watchStatusPath, status);
|
|
2910
|
+
}
|
|
2911
|
+
async function markPagesStaleForSources(rootDir, sourceIds) {
|
|
2912
|
+
const uniqueSourceIds = [...new Set(sourceIds.filter(Boolean))];
|
|
2913
|
+
if (!uniqueSourceIds.length) {
|
|
2914
|
+
return [];
|
|
2915
|
+
}
|
|
2916
|
+
const { paths } = await initWorkspace(rootDir);
|
|
2917
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
2918
|
+
if (!graph) {
|
|
2919
|
+
return [];
|
|
2920
|
+
}
|
|
2921
|
+
const affectedSourceIds = new Set(uniqueSourceIds);
|
|
2922
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2923
|
+
let graphChanged = false;
|
|
2924
|
+
const affectedPagePaths = [];
|
|
2925
|
+
const nextPages = graph.pages.map((page) => {
|
|
2926
|
+
if (page.freshness === "stale" || !page.sourceIds.some((sourceId) => affectedSourceIds.has(sourceId))) {
|
|
2927
|
+
return page;
|
|
2928
|
+
}
|
|
2929
|
+
graphChanged = true;
|
|
2930
|
+
affectedPagePaths.push(page.path);
|
|
2931
|
+
return {
|
|
2932
|
+
...page,
|
|
2933
|
+
freshness: "stale",
|
|
2934
|
+
updatedAt: now
|
|
2935
|
+
};
|
|
2936
|
+
});
|
|
2937
|
+
const nextNodes = graph.nodes.map((node) => {
|
|
2938
|
+
if (node.freshness === "stale" || !node.sourceIds.some((sourceId) => affectedSourceIds.has(sourceId))) {
|
|
2939
|
+
return node;
|
|
2940
|
+
}
|
|
2941
|
+
graphChanged = true;
|
|
2942
|
+
return {
|
|
2943
|
+
...node,
|
|
2944
|
+
freshness: "stale"
|
|
2945
|
+
};
|
|
2946
|
+
});
|
|
2947
|
+
if (graphChanged) {
|
|
2948
|
+
await writeJsonFile(paths.graphPath, {
|
|
2949
|
+
...graph,
|
|
2950
|
+
nodes: nextNodes,
|
|
2951
|
+
pages: nextPages
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
for (const page of nextPages) {
|
|
2955
|
+
if (page.freshness !== "stale" || !page.sourceIds.some((sourceId) => affectedSourceIds.has(sourceId))) {
|
|
2956
|
+
continue;
|
|
2957
|
+
}
|
|
2958
|
+
const absolutePath = path7.join(paths.wikiDir, page.path);
|
|
2959
|
+
if (!await fileExists(absolutePath)) {
|
|
2960
|
+
continue;
|
|
2961
|
+
}
|
|
2962
|
+
const raw = await fs7.readFile(absolutePath, "utf8");
|
|
2963
|
+
const parsed = matter2(raw);
|
|
2964
|
+
if (parsed.data.freshness === "stale") {
|
|
2965
|
+
continue;
|
|
2966
|
+
}
|
|
2967
|
+
parsed.data.freshness = "stale";
|
|
2968
|
+
parsed.data.updated_at = now;
|
|
2969
|
+
await writeFileIfChanged(absolutePath, matter2.stringify(parsed.content, parsed.data));
|
|
2970
|
+
}
|
|
2971
|
+
return affectedPagePaths;
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
// src/ingest.ts
|
|
2975
|
+
var DEFAULT_MAX_ASSET_SIZE = 10 * 1024 * 1024;
|
|
2976
|
+
var DEFAULT_MAX_DIRECTORY_FILES = 5e3;
|
|
2977
|
+
var BUILT_IN_REPO_IGNORES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", "coverage", ".venv", "vendor", "target"]);
|
|
2143
2978
|
function inferKind(mimeType, filePath) {
|
|
2144
2979
|
if (inferCodeLanguage(filePath, mimeType)) {
|
|
2145
2980
|
return "code";
|
|
@@ -2172,7 +3007,7 @@ function normalizeIngestOptions(options) {
|
|
|
2172
3007
|
return {
|
|
2173
3008
|
includeAssets: options?.includeAssets ?? true,
|
|
2174
3009
|
maxAssetSize: Math.max(0, Math.floor(options?.maxAssetSize ?? DEFAULT_MAX_ASSET_SIZE)),
|
|
2175
|
-
repoRoot: options?.repoRoot ?
|
|
3010
|
+
repoRoot: options?.repoRoot ? path8.resolve(options.repoRoot) : void 0,
|
|
2176
3011
|
include: (options?.include ?? []).map((pattern) => pattern.trim()).filter(Boolean),
|
|
2177
3012
|
exclude: (options?.exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean),
|
|
2178
3013
|
maxFiles: Math.max(1, Math.floor(options?.maxFiles ?? DEFAULT_MAX_DIRECTORY_FILES)),
|
|
@@ -2181,27 +3016,27 @@ function normalizeIngestOptions(options) {
|
|
|
2181
3016
|
}
|
|
2182
3017
|
function matchesAnyGlob(relativePath, patterns) {
|
|
2183
3018
|
return patterns.some(
|
|
2184
|
-
(pattern) =>
|
|
3019
|
+
(pattern) => path8.matchesGlob(relativePath, pattern) || path8.matchesGlob(path8.posix.basename(relativePath), pattern)
|
|
2185
3020
|
);
|
|
2186
3021
|
}
|
|
2187
3022
|
function supportedDirectoryKind(sourceKind) {
|
|
2188
3023
|
return sourceKind !== "binary";
|
|
2189
3024
|
}
|
|
2190
|
-
async function
|
|
2191
|
-
let current =
|
|
3025
|
+
async function findNearestGitRoot2(startPath) {
|
|
3026
|
+
let current = path8.resolve(startPath);
|
|
2192
3027
|
try {
|
|
2193
|
-
const stat = await
|
|
3028
|
+
const stat = await fs8.stat(current);
|
|
2194
3029
|
if (!stat.isDirectory()) {
|
|
2195
|
-
current =
|
|
3030
|
+
current = path8.dirname(current);
|
|
2196
3031
|
}
|
|
2197
3032
|
} catch {
|
|
2198
|
-
current =
|
|
3033
|
+
current = path8.dirname(current);
|
|
2199
3034
|
}
|
|
2200
3035
|
while (true) {
|
|
2201
|
-
if (await fileExists(
|
|
3036
|
+
if (await fileExists(path8.join(current, ".git"))) {
|
|
2202
3037
|
return current;
|
|
2203
3038
|
}
|
|
2204
|
-
const parent =
|
|
3039
|
+
const parent = path8.dirname(current);
|
|
2205
3040
|
if (parent === current) {
|
|
2206
3041
|
return null;
|
|
2207
3042
|
}
|
|
@@ -2209,14 +3044,26 @@ async function findNearestGitRoot(startPath) {
|
|
|
2209
3044
|
}
|
|
2210
3045
|
}
|
|
2211
3046
|
function withinRoot(rootPath, targetPath) {
|
|
2212
|
-
const relative =
|
|
2213
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
3047
|
+
const relative = path8.relative(rootPath, targetPath);
|
|
3048
|
+
return relative === "" || !relative.startsWith("..") && !path8.isAbsolute(relative);
|
|
3049
|
+
}
|
|
3050
|
+
function repoRootFromManifest(manifest) {
|
|
3051
|
+
if (manifest.originType !== "file" || !manifest.originalPath || !manifest.repoRelativePath) {
|
|
3052
|
+
return null;
|
|
3053
|
+
}
|
|
3054
|
+
const repoDir = path8.posix.dirname(manifest.repoRelativePath);
|
|
3055
|
+
const fileDir = path8.dirname(path8.resolve(manifest.originalPath));
|
|
3056
|
+
if (repoDir === "." || !repoDir) {
|
|
3057
|
+
return fileDir;
|
|
3058
|
+
}
|
|
3059
|
+
const segments = repoDir.split("/").filter(Boolean);
|
|
3060
|
+
return path8.resolve(fileDir, ...segments.map(() => ".."));
|
|
2214
3061
|
}
|
|
2215
3062
|
function repoRelativePathFor(absolutePath, repoRoot) {
|
|
2216
3063
|
if (!repoRoot || !withinRoot(repoRoot, absolutePath)) {
|
|
2217
3064
|
return void 0;
|
|
2218
3065
|
}
|
|
2219
|
-
const relative = toPosix(
|
|
3066
|
+
const relative = toPosix(path8.relative(repoRoot, absolutePath));
|
|
2220
3067
|
return relative && !relative.startsWith("..") ? relative : void 0;
|
|
2221
3068
|
}
|
|
2222
3069
|
function normalizeOriginUrl(input) {
|
|
@@ -2226,6 +3073,150 @@ function normalizeOriginUrl(input) {
|
|
|
2226
3073
|
return input;
|
|
2227
3074
|
}
|
|
2228
3075
|
}
|
|
3076
|
+
function isHttpUrl(input) {
|
|
3077
|
+
return /^https?:\/\//i.test(input);
|
|
3078
|
+
}
|
|
3079
|
+
function stripLeadingLabel(value, label) {
|
|
3080
|
+
return value.startsWith(label) ? value.slice(label.length).trim() : value.trim();
|
|
3081
|
+
}
|
|
3082
|
+
function arxivIdFromInput(input) {
|
|
3083
|
+
const trimmed = input.trim();
|
|
3084
|
+
if (/^\d{4}\.\d{4,5}(v\d+)?$/i.test(trimmed)) {
|
|
3085
|
+
return trimmed;
|
|
3086
|
+
}
|
|
3087
|
+
try {
|
|
3088
|
+
const url = new URL(trimmed);
|
|
3089
|
+
const match = url.pathname.match(/(\d{4}\.\d{4,5}(?:v\d+)?)/i);
|
|
3090
|
+
return match?.[1] ?? null;
|
|
3091
|
+
} catch {
|
|
3092
|
+
return null;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
function isTweetUrl(input) {
|
|
3096
|
+
try {
|
|
3097
|
+
const url = new URL(input);
|
|
3098
|
+
return url.hostname.includes("x.com") || url.hostname.includes("twitter.com");
|
|
3099
|
+
} catch {
|
|
3100
|
+
return false;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
function markdownFrontmatter(value) {
|
|
3104
|
+
const lines = ["---"];
|
|
3105
|
+
for (const [key, rawValue] of Object.entries(value)) {
|
|
3106
|
+
if (!rawValue) {
|
|
3107
|
+
continue;
|
|
3108
|
+
}
|
|
3109
|
+
lines.push(`${key}: "${rawValue.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`);
|
|
3110
|
+
}
|
|
3111
|
+
lines.push("---", "");
|
|
3112
|
+
return lines;
|
|
3113
|
+
}
|
|
3114
|
+
function prepareCapturedMarkdownInput(input) {
|
|
3115
|
+
return {
|
|
3116
|
+
title: input.title,
|
|
3117
|
+
originType: "url",
|
|
3118
|
+
sourceKind: "markdown",
|
|
3119
|
+
url: normalizeOriginUrl(input.url),
|
|
3120
|
+
mimeType: "text/markdown",
|
|
3121
|
+
storedExtension: ".md",
|
|
3122
|
+
payloadBytes: Buffer.from(input.markdown, "utf8"),
|
|
3123
|
+
extractedText: input.markdown,
|
|
3124
|
+
logDetails: input.logDetails
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
3127
|
+
async function fetchText(url) {
|
|
3128
|
+
const response = await fetch(url);
|
|
3129
|
+
if (!response.ok) {
|
|
3130
|
+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
3131
|
+
}
|
|
3132
|
+
return response.text();
|
|
3133
|
+
}
|
|
3134
|
+
function domTextFromHtml(html, baseUrl) {
|
|
3135
|
+
const dom = new JSDOM(`<body>${html}</body>`, { url: baseUrl });
|
|
3136
|
+
return normalizeWhitespace(dom.window.document.body.textContent ?? "");
|
|
3137
|
+
}
|
|
3138
|
+
async function captureArxivMarkdown(input, options) {
|
|
3139
|
+
const arxivId = arxivIdFromInput(input);
|
|
3140
|
+
if (!arxivId) {
|
|
3141
|
+
throw new Error(`Could not determine an arXiv id from ${input}`);
|
|
3142
|
+
}
|
|
3143
|
+
const normalizedUrl = `https://arxiv.org/abs/${arxivId}`;
|
|
3144
|
+
const html = await fetchText(normalizedUrl);
|
|
3145
|
+
const dom = new JSDOM(html, { url: normalizedUrl });
|
|
3146
|
+
const document = dom.window.document;
|
|
3147
|
+
const metaTitle = document.querySelector('meta[name="citation_title"]')?.getAttribute("content")?.trim();
|
|
3148
|
+
const headingTitle = document.querySelector("h1.title")?.textContent?.trim();
|
|
3149
|
+
const title = stripLeadingLabel(metaTitle ?? headingTitle ?? arxivId, "Title:");
|
|
3150
|
+
const authors = [...document.querySelectorAll('meta[name="citation_author"]')].map((node) => node.getAttribute("content")?.trim()).filter((value) => Boolean(value));
|
|
3151
|
+
const authorsText = authors.join(", ") || stripLeadingLabel(document.querySelector(".authors")?.textContent?.trim() ?? "", "Authors:");
|
|
3152
|
+
const abstract = stripLeadingLabel(document.querySelector("blockquote.abstract")?.textContent?.trim() ?? "", "Abstract:");
|
|
3153
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3154
|
+
const markdown = [
|
|
3155
|
+
...markdownFrontmatter({
|
|
3156
|
+
capture_type: "arxiv",
|
|
3157
|
+
source_url: normalizedUrl,
|
|
3158
|
+
arxiv_id: arxivId,
|
|
3159
|
+
author: options.author,
|
|
3160
|
+
contributor: options.contributor,
|
|
3161
|
+
captured_at: capturedAt
|
|
3162
|
+
}),
|
|
3163
|
+
`# ${title}`,
|
|
3164
|
+
"",
|
|
3165
|
+
`- arXiv: ${arxivId}`,
|
|
3166
|
+
...authorsText ? [`- Authors: ${authorsText}`] : [],
|
|
3167
|
+
...options.author ? [`- Added By: ${options.author}`] : [],
|
|
3168
|
+
...options.contributor ? [`- Contributor: ${options.contributor}`] : [],
|
|
3169
|
+
"",
|
|
3170
|
+
"## Abstract",
|
|
3171
|
+
"",
|
|
3172
|
+
abstract || "Abstract not available from the fetched arXiv page.",
|
|
3173
|
+
"",
|
|
3174
|
+
"## Source",
|
|
3175
|
+
"",
|
|
3176
|
+
`- URL: ${normalizedUrl}`,
|
|
3177
|
+
""
|
|
3178
|
+
].join("\n");
|
|
3179
|
+
return { title, normalizedUrl, markdown };
|
|
3180
|
+
}
|
|
3181
|
+
async function captureTweetMarkdown(input, options) {
|
|
3182
|
+
const normalizedUrl = normalizeOriginUrl(input);
|
|
3183
|
+
const canonicalUrl = normalizedUrl.replace("x.com", "twitter.com");
|
|
3184
|
+
const oembedUrl = `https://publish.twitter.com/oembed?url=${encodeURIComponent(canonicalUrl)}&omit_script=true`;
|
|
3185
|
+
const response = await fetch(oembedUrl);
|
|
3186
|
+
let postText = "";
|
|
3187
|
+
let postAuthor = "";
|
|
3188
|
+
if (response.ok) {
|
|
3189
|
+
const payload = await response.json();
|
|
3190
|
+
postText = payload.html ? domTextFromHtml(payload.html, canonicalUrl) : "";
|
|
3191
|
+
postAuthor = payload.author_name?.trim() ?? "";
|
|
3192
|
+
}
|
|
3193
|
+
const title = postAuthor ? `X Post by ${postAuthor}` : "X Post";
|
|
3194
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3195
|
+
const markdown = [
|
|
3196
|
+
...markdownFrontmatter({
|
|
3197
|
+
capture_type: "tweet",
|
|
3198
|
+
source_url: normalizedUrl,
|
|
3199
|
+
author: options.author,
|
|
3200
|
+
contributor: options.contributor,
|
|
3201
|
+
captured_at: capturedAt
|
|
3202
|
+
}),
|
|
3203
|
+
`# ${title}`,
|
|
3204
|
+
"",
|
|
3205
|
+
...postAuthor ? [`- Post Author: ${postAuthor}`] : [],
|
|
3206
|
+
...options.author ? [`- Added By: ${options.author}`] : [],
|
|
3207
|
+
...options.contributor ? [`- Contributor: ${options.contributor}`] : [],
|
|
3208
|
+
"",
|
|
3209
|
+
"## Content",
|
|
3210
|
+
"",
|
|
3211
|
+
postText || `Captured the post link at ${normalizedUrl}. Rich text was unavailable from the public oEmbed response.`,
|
|
3212
|
+
"",
|
|
3213
|
+
"## Source",
|
|
3214
|
+
"",
|
|
3215
|
+
`- URL: ${normalizedUrl}`,
|
|
3216
|
+
""
|
|
3217
|
+
].join("\n");
|
|
3218
|
+
return { title, normalizedUrl, markdown };
|
|
3219
|
+
}
|
|
2229
3220
|
function manifestMatchesOrigin(manifest, prepared) {
|
|
2230
3221
|
if (prepared.originType === "url") {
|
|
2231
3222
|
return Boolean(prepared.url && manifest.url && normalizeOriginUrl(manifest.url) === normalizeOriginUrl(prepared.url));
|
|
@@ -2240,7 +3231,7 @@ function buildCompositeHash(payloadBytes, attachments = []) {
|
|
|
2240
3231
|
return sha256(`${sha256(payloadBytes)}|${attachmentSignature}`);
|
|
2241
3232
|
}
|
|
2242
3233
|
function sanitizeAssetRelativePath(value) {
|
|
2243
|
-
const normalized =
|
|
3234
|
+
const normalized = path8.posix.normalize(value.replace(/\\/g, "/"));
|
|
2244
3235
|
const segments = normalized.split("/").filter(Boolean).map((segment) => {
|
|
2245
3236
|
if (segment === ".") {
|
|
2246
3237
|
return "";
|
|
@@ -2260,7 +3251,7 @@ function normalizeLocalReference(value) {
|
|
|
2260
3251
|
return null;
|
|
2261
3252
|
}
|
|
2262
3253
|
const lowered = candidate.toLowerCase();
|
|
2263
|
-
if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") ||
|
|
3254
|
+
if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path8.isAbsolute(candidate)) {
|
|
2264
3255
|
return null;
|
|
2265
3256
|
}
|
|
2266
3257
|
return candidate.replace(/\\/g, "/");
|
|
@@ -2322,12 +3313,12 @@ async function convertHtmlToMarkdown(html, url) {
|
|
|
2322
3313
|
};
|
|
2323
3314
|
}
|
|
2324
3315
|
async function readManifestByHash(manifestsDir, contentHash) {
|
|
2325
|
-
const entries = await
|
|
3316
|
+
const entries = await fs8.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
|
|
2326
3317
|
for (const entry of entries) {
|
|
2327
3318
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
2328
3319
|
continue;
|
|
2329
3320
|
}
|
|
2330
|
-
const manifest = await readJsonFile(
|
|
3321
|
+
const manifest = await readJsonFile(path8.join(manifestsDir, entry.name));
|
|
2331
3322
|
if (manifest?.contentHash === contentHash) {
|
|
2332
3323
|
return manifest;
|
|
2333
3324
|
}
|
|
@@ -2335,12 +3326,12 @@ async function readManifestByHash(manifestsDir, contentHash) {
|
|
|
2335
3326
|
return null;
|
|
2336
3327
|
}
|
|
2337
3328
|
async function readManifestByOrigin(manifestsDir, prepared) {
|
|
2338
|
-
const entries = await
|
|
3329
|
+
const entries = await fs8.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
|
|
2339
3330
|
for (const entry of entries) {
|
|
2340
3331
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
2341
3332
|
continue;
|
|
2342
3333
|
}
|
|
2343
|
-
const manifest = await readJsonFile(
|
|
3334
|
+
const manifest = await readJsonFile(path8.join(manifestsDir, entry.name));
|
|
2344
3335
|
if (manifest && manifestMatchesOrigin(manifest, prepared)) {
|
|
2345
3336
|
return manifest;
|
|
2346
3337
|
}
|
|
@@ -2351,12 +3342,12 @@ async function loadGitignoreMatcher(repoRoot, enabled) {
|
|
|
2351
3342
|
if (!enabled) {
|
|
2352
3343
|
return null;
|
|
2353
3344
|
}
|
|
2354
|
-
const gitignorePath =
|
|
3345
|
+
const gitignorePath = path8.join(repoRoot, ".gitignore");
|
|
2355
3346
|
if (!await fileExists(gitignorePath)) {
|
|
2356
3347
|
return null;
|
|
2357
3348
|
}
|
|
2358
3349
|
const matcher = ignore();
|
|
2359
|
-
matcher.add(await
|
|
3350
|
+
matcher.add(await fs8.readFile(gitignorePath, "utf8"));
|
|
2360
3351
|
return matcher;
|
|
2361
3352
|
}
|
|
2362
3353
|
function builtInIgnoreReason(relativePath) {
|
|
@@ -2377,23 +3368,23 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
|
|
|
2377
3368
|
if (!currentDir) {
|
|
2378
3369
|
continue;
|
|
2379
3370
|
}
|
|
2380
|
-
const entries = await
|
|
3371
|
+
const entries = await fs8.readdir(currentDir, { withFileTypes: true });
|
|
2381
3372
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
2382
3373
|
for (const entry of entries) {
|
|
2383
|
-
const absolutePath =
|
|
2384
|
-
const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(
|
|
3374
|
+
const absolutePath = path8.join(currentDir, entry.name);
|
|
3375
|
+
const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path8.relative(inputDir, absolutePath));
|
|
2385
3376
|
const relativePath = relativeToRepo || entry.name;
|
|
2386
3377
|
const builtInReason = builtInIgnoreReason(relativePath);
|
|
2387
3378
|
if (builtInReason) {
|
|
2388
|
-
skipped.push({ path: toPosix(
|
|
3379
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: builtInReason });
|
|
2389
3380
|
continue;
|
|
2390
3381
|
}
|
|
2391
3382
|
if (matcher?.ignores(relativePath)) {
|
|
2392
|
-
skipped.push({ path: toPosix(
|
|
3383
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "gitignore" });
|
|
2393
3384
|
continue;
|
|
2394
3385
|
}
|
|
2395
3386
|
if (matchesAnyGlob(relativePath, options.exclude)) {
|
|
2396
|
-
skipped.push({ path: toPosix(
|
|
3387
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "exclude_glob" });
|
|
2397
3388
|
continue;
|
|
2398
3389
|
}
|
|
2399
3390
|
if (entry.isDirectory()) {
|
|
@@ -2401,21 +3392,21 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
|
|
|
2401
3392
|
continue;
|
|
2402
3393
|
}
|
|
2403
3394
|
if (!entry.isFile()) {
|
|
2404
|
-
skipped.push({ path: toPosix(
|
|
3395
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
|
|
2405
3396
|
continue;
|
|
2406
3397
|
}
|
|
2407
3398
|
if (options.include.length > 0 && !matchesAnyGlob(relativePath, options.include)) {
|
|
2408
|
-
skipped.push({ path: toPosix(
|
|
3399
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "include_glob" });
|
|
2409
3400
|
continue;
|
|
2410
3401
|
}
|
|
2411
3402
|
const mimeType = guessMimeType(absolutePath);
|
|
2412
3403
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
2413
3404
|
if (!supportedDirectoryKind(sourceKind)) {
|
|
2414
|
-
skipped.push({ path: toPosix(
|
|
3405
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
2415
3406
|
continue;
|
|
2416
3407
|
}
|
|
2417
3408
|
if (files.length >= options.maxFiles) {
|
|
2418
|
-
skipped.push({ path: toPosix(
|
|
3409
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "max_files" });
|
|
2419
3410
|
continue;
|
|
2420
3411
|
}
|
|
2421
3412
|
files.push(absolutePath);
|
|
@@ -2437,12 +3428,12 @@ function resolveUrlMimeType(input, response) {
|
|
|
2437
3428
|
function buildRemoteAssetRelativePath(assetUrl, mimeType) {
|
|
2438
3429
|
const url = new URL(assetUrl);
|
|
2439
3430
|
const normalized = sanitizeAssetRelativePath(`${url.hostname}${url.pathname || "/asset"}`);
|
|
2440
|
-
const extension =
|
|
2441
|
-
const directory =
|
|
2442
|
-
const basename = extension ?
|
|
3431
|
+
const extension = path8.posix.extname(normalized);
|
|
3432
|
+
const directory = path8.posix.dirname(normalized);
|
|
3433
|
+
const basename = extension ? path8.posix.basename(normalized, extension) : path8.posix.basename(normalized);
|
|
2443
3434
|
const resolvedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
|
|
2444
3435
|
const hashedName = `${basename || "asset"}-${sha256(assetUrl).slice(0, 8)}${resolvedExtension}`;
|
|
2445
|
-
return directory === "." ? hashedName :
|
|
3436
|
+
return directory === "." ? hashedName : path8.posix.join(directory, hashedName);
|
|
2446
3437
|
}
|
|
2447
3438
|
async function readResponseBytesWithinLimit(response, maxBytes) {
|
|
2448
3439
|
const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
|
|
@@ -2577,27 +3568,27 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
2577
3568
|
const previous = existingByOrigin ?? void 0;
|
|
2578
3569
|
const sourceId = previous?.sourceId ?? `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
|
|
2579
3570
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2580
|
-
const storedPath =
|
|
2581
|
-
const extractedTextPath = prepared.extractedText ?
|
|
2582
|
-
const attachmentsDir =
|
|
3571
|
+
const storedPath = path8.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
|
|
3572
|
+
const extractedTextPath = prepared.extractedText ? path8.join(paths.extractsDir, `${sourceId}.md`) : void 0;
|
|
3573
|
+
const attachmentsDir = path8.join(paths.rawAssetsDir, sourceId);
|
|
2583
3574
|
if (previous?.storedPath) {
|
|
2584
|
-
await
|
|
3575
|
+
await fs8.rm(path8.resolve(rootDir, previous.storedPath), { force: true });
|
|
2585
3576
|
}
|
|
2586
3577
|
if (previous?.extractedTextPath) {
|
|
2587
|
-
await
|
|
3578
|
+
await fs8.rm(path8.resolve(rootDir, previous.extractedTextPath), { force: true });
|
|
2588
3579
|
}
|
|
2589
|
-
await
|
|
2590
|
-
await
|
|
3580
|
+
await fs8.rm(attachmentsDir, { recursive: true, force: true });
|
|
3581
|
+
await fs8.writeFile(storedPath, prepared.payloadBytes);
|
|
2591
3582
|
if (prepared.extractedText && extractedTextPath) {
|
|
2592
|
-
await
|
|
3583
|
+
await fs8.writeFile(extractedTextPath, prepared.extractedText, "utf8");
|
|
2593
3584
|
}
|
|
2594
3585
|
const manifestAttachments = [];
|
|
2595
3586
|
for (const attachment of attachments) {
|
|
2596
|
-
const absoluteAttachmentPath =
|
|
2597
|
-
await ensureDir(
|
|
2598
|
-
await
|
|
3587
|
+
const absoluteAttachmentPath = path8.join(attachmentsDir, attachment.relativePath);
|
|
3588
|
+
await ensureDir(path8.dirname(absoluteAttachmentPath));
|
|
3589
|
+
await fs8.writeFile(absoluteAttachmentPath, attachment.bytes);
|
|
2599
3590
|
manifestAttachments.push({
|
|
2600
|
-
path: toPosix(
|
|
3591
|
+
path: toPosix(path8.relative(rootDir, absoluteAttachmentPath)),
|
|
2601
3592
|
mimeType: attachment.mimeType,
|
|
2602
3593
|
originalPath: attachment.originalPath
|
|
2603
3594
|
});
|
|
@@ -2611,15 +3602,15 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
2611
3602
|
originalPath: prepared.originalPath,
|
|
2612
3603
|
repoRelativePath: prepared.repoRelativePath,
|
|
2613
3604
|
url: prepared.url,
|
|
2614
|
-
storedPath: toPosix(
|
|
2615
|
-
extractedTextPath: extractedTextPath ? toPosix(
|
|
3605
|
+
storedPath: toPosix(path8.relative(rootDir, storedPath)),
|
|
3606
|
+
extractedTextPath: extractedTextPath ? toPosix(path8.relative(rootDir, extractedTextPath)) : void 0,
|
|
2616
3607
|
mimeType: prepared.mimeType,
|
|
2617
3608
|
contentHash,
|
|
2618
3609
|
createdAt: previous?.createdAt ?? now,
|
|
2619
3610
|
updatedAt: now,
|
|
2620
3611
|
attachments: manifestAttachments.length ? manifestAttachments : void 0
|
|
2621
3612
|
};
|
|
2622
|
-
await writeJsonFile(
|
|
3613
|
+
await writeJsonFile(path8.join(paths.manifestsDir, `${sourceId}.json`), manifest);
|
|
2623
3614
|
await appendLogEntry(rootDir, "ingest", prepared.title, [
|
|
2624
3615
|
`source_id=${sourceId}`,
|
|
2625
3616
|
`kind=${prepared.sourceKind}`,
|
|
@@ -2627,21 +3618,292 @@ async function persistPreparedInput(rootDir, prepared, paths) {
|
|
|
2627
3618
|
`updated=${previous ? "true" : "false"}`,
|
|
2628
3619
|
...prepared.logDetails ?? []
|
|
2629
3620
|
]);
|
|
3621
|
+
if (manifest.originalPath || manifest.repoRelativePath || manifest.sourceId) {
|
|
3622
|
+
await clearPendingSemanticRefreshEntries(rootDir, {
|
|
3623
|
+
sourceId: manifest.sourceId,
|
|
3624
|
+
originalPath: manifest.originalPath,
|
|
3625
|
+
relativePath: manifest.repoRelativePath
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
2630
3628
|
return { manifest, isNew: !previous, wasUpdated: Boolean(previous) };
|
|
2631
3629
|
}
|
|
3630
|
+
async function removeManifestArtifacts(rootDir, manifest, paths) {
|
|
3631
|
+
await fs8.rm(path8.join(paths.manifestsDir, `${manifest.sourceId}.json`), { force: true });
|
|
3632
|
+
await fs8.rm(path8.resolve(rootDir, manifest.storedPath), { force: true });
|
|
3633
|
+
if (manifest.extractedTextPath) {
|
|
3634
|
+
await fs8.rm(path8.resolve(rootDir, manifest.extractedTextPath), { force: true });
|
|
3635
|
+
}
|
|
3636
|
+
await fs8.rm(path8.join(paths.rawAssetsDir, manifest.sourceId), { recursive: true, force: true });
|
|
3637
|
+
await fs8.rm(path8.join(paths.analysesDir, `${manifest.sourceId}.json`), { force: true });
|
|
3638
|
+
}
|
|
3639
|
+
function repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot) {
|
|
3640
|
+
const candidates = [
|
|
3641
|
+
paths.rawDir,
|
|
3642
|
+
paths.wikiDir,
|
|
3643
|
+
paths.stateDir,
|
|
3644
|
+
paths.agentDir,
|
|
3645
|
+
paths.inboxDir,
|
|
3646
|
+
path8.join(rootDir, ".claude"),
|
|
3647
|
+
path8.join(rootDir, ".cursor"),
|
|
3648
|
+
path8.join(rootDir, ".obsidian")
|
|
3649
|
+
];
|
|
3650
|
+
return candidates.map((candidate) => path8.resolve(candidate)).filter((candidate, index, items) => items.indexOf(candidate) === index).filter((candidate) => withinRoot(repoRoot, candidate));
|
|
3651
|
+
}
|
|
3652
|
+
function preparedMatchesManifest(manifest, prepared, contentHash) {
|
|
3653
|
+
return manifest.contentHash === contentHash && manifest.title === prepared.title && manifest.sourceKind === prepared.sourceKind && manifest.language === prepared.language && manifest.mimeType === prepared.mimeType && manifest.repoRelativePath === prepared.repoRelativePath;
|
|
3654
|
+
}
|
|
3655
|
+
function shouldDeferWatchSemanticRefresh(sourceKind) {
|
|
3656
|
+
return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "image";
|
|
3657
|
+
}
|
|
3658
|
+
function pendingSemanticRefreshId(changeType, repoRoot, relativePath) {
|
|
3659
|
+
return `pending:${changeType}:${sha256(`${toPosix(repoRoot)}:${relativePath}`).slice(0, 12)}`;
|
|
3660
|
+
}
|
|
3661
|
+
async function listTrackedRepoRoots(rootDir) {
|
|
3662
|
+
const manifests = await listManifests(rootDir);
|
|
3663
|
+
return [...new Set(manifests.map((manifest) => repoRootFromManifest(manifest)).filter((item) => Boolean(item)))].sort(
|
|
3664
|
+
(left, right) => left.localeCompare(right)
|
|
3665
|
+
);
|
|
3666
|
+
}
|
|
3667
|
+
async function syncTrackedRepos(rootDir, options, repoRoots) {
|
|
3668
|
+
const { paths } = await initWorkspace(rootDir);
|
|
3669
|
+
const normalizedOptions = normalizeIngestOptions(options);
|
|
3670
|
+
const manifests = await listManifests(rootDir);
|
|
3671
|
+
const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
|
|
3672
|
+
(item) => path8.resolve(item)
|
|
3673
|
+
);
|
|
3674
|
+
const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
|
|
3675
|
+
const manifestsByRepoRoot = /* @__PURE__ */ new Map();
|
|
3676
|
+
for (const manifest of manifests) {
|
|
3677
|
+
const repoRoot = repoRootFromManifest(manifest);
|
|
3678
|
+
if (!repoRoot || !uniqueRoots.includes(path8.resolve(repoRoot))) {
|
|
3679
|
+
continue;
|
|
3680
|
+
}
|
|
3681
|
+
const key = path8.resolve(repoRoot);
|
|
3682
|
+
const bucket = manifestsByRepoRoot.get(key) ?? [];
|
|
3683
|
+
bucket.push(manifest);
|
|
3684
|
+
manifestsByRepoRoot.set(key, bucket);
|
|
3685
|
+
}
|
|
3686
|
+
const imported = [];
|
|
3687
|
+
const updated = [];
|
|
3688
|
+
const removed = [];
|
|
3689
|
+
const skipped = [];
|
|
3690
|
+
let scannedCount = 0;
|
|
3691
|
+
for (const repoRoot of uniqueRoots) {
|
|
3692
|
+
const repoManifests = manifestsByRepoRoot.get(repoRoot) ?? [];
|
|
3693
|
+
if (!await fileExists(repoRoot)) {
|
|
3694
|
+
for (const manifest of repoManifests) {
|
|
3695
|
+
await removeManifestArtifacts(rootDir, manifest, paths);
|
|
3696
|
+
removed.push(manifest);
|
|
3697
|
+
}
|
|
3698
|
+
continue;
|
|
3699
|
+
}
|
|
3700
|
+
const ignoreRoots = repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot);
|
|
3701
|
+
const collected = await collectDirectoryFiles(rootDir, repoRoot, repoRoot, normalizedOptions);
|
|
3702
|
+
const files = collected.files.filter((absolutePath) => !ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath)));
|
|
3703
|
+
skipped.push(
|
|
3704
|
+
...collected.skipped,
|
|
3705
|
+
...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
|
|
3706
|
+
path: toPosix(path8.relative(rootDir, absolutePath)),
|
|
3707
|
+
reason: "workspace_generated"
|
|
3708
|
+
}))
|
|
3709
|
+
);
|
|
3710
|
+
scannedCount += files.length;
|
|
3711
|
+
const currentPaths = new Set(files.map((absolutePath) => path8.resolve(absolutePath)));
|
|
3712
|
+
for (const absolutePath of files) {
|
|
3713
|
+
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
|
|
3714
|
+
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
3715
|
+
if (result.isNew) {
|
|
3716
|
+
imported.push(result.manifest);
|
|
3717
|
+
} else if (result.wasUpdated) {
|
|
3718
|
+
updated.push(result.manifest);
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
for (const manifest of repoManifests) {
|
|
3722
|
+
const originalPath = manifest.originalPath ? path8.resolve(manifest.originalPath) : null;
|
|
3723
|
+
if (originalPath && !currentPaths.has(originalPath)) {
|
|
3724
|
+
await removeManifestArtifacts(rootDir, manifest, paths);
|
|
3725
|
+
removed.push(manifest);
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
if (uniqueRoots.length > 0) {
|
|
3730
|
+
await appendLogEntry(rootDir, "sync_repo", uniqueRoots.map((repoRoot) => toPosix(path8.relative(rootDir, repoRoot)) || ".").join(","), [
|
|
3731
|
+
`repo_roots=${uniqueRoots.length}`,
|
|
3732
|
+
`scanned=${scannedCount}`,
|
|
3733
|
+
`imported=${imported.length}`,
|
|
3734
|
+
`updated=${updated.length}`,
|
|
3735
|
+
`removed=${removed.length}`,
|
|
3736
|
+
`skipped=${skipped.length}`
|
|
3737
|
+
]);
|
|
3738
|
+
}
|
|
3739
|
+
return {
|
|
3740
|
+
repoRoots: uniqueRoots,
|
|
3741
|
+
scannedCount,
|
|
3742
|
+
imported,
|
|
3743
|
+
updated,
|
|
3744
|
+
removed,
|
|
3745
|
+
skipped
|
|
3746
|
+
};
|
|
3747
|
+
}
|
|
3748
|
+
async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
3749
|
+
const { paths } = await initWorkspace(rootDir);
|
|
3750
|
+
const normalizedOptions = normalizeIngestOptions(options);
|
|
3751
|
+
const manifests = await listManifests(rootDir);
|
|
3752
|
+
const trackedRoots = (repoRoots && repoRoots.length > 0 ? repoRoots : await listTrackedRepoRoots(rootDir)).map(
|
|
3753
|
+
(item) => path8.resolve(item)
|
|
3754
|
+
);
|
|
3755
|
+
const uniqueRoots = [...new Set(trackedRoots)].sort((left, right) => left.localeCompare(right));
|
|
3756
|
+
const manifestsByRepoRoot = /* @__PURE__ */ new Map();
|
|
3757
|
+
for (const manifest of manifests) {
|
|
3758
|
+
const repoRoot = repoRootFromManifest(manifest);
|
|
3759
|
+
if (!repoRoot || !uniqueRoots.includes(path8.resolve(repoRoot))) {
|
|
3760
|
+
continue;
|
|
3761
|
+
}
|
|
3762
|
+
const key = path8.resolve(repoRoot);
|
|
3763
|
+
const bucket = manifestsByRepoRoot.get(key) ?? [];
|
|
3764
|
+
bucket.push(manifest);
|
|
3765
|
+
manifestsByRepoRoot.set(key, bucket);
|
|
3766
|
+
}
|
|
3767
|
+
const imported = [];
|
|
3768
|
+
const updated = [];
|
|
3769
|
+
const removed = [];
|
|
3770
|
+
const skipped = [];
|
|
3771
|
+
const pendingSemanticRefresh = [];
|
|
3772
|
+
const staleSourceIds = /* @__PURE__ */ new Set();
|
|
3773
|
+
let scannedCount = 0;
|
|
3774
|
+
for (const repoRoot of uniqueRoots) {
|
|
3775
|
+
const repoManifests = manifestsByRepoRoot.get(repoRoot) ?? [];
|
|
3776
|
+
const manifestsByOriginalPath = new Map(
|
|
3777
|
+
repoManifests.filter((manifest) => manifest.originalPath).map((manifest) => [path8.resolve(manifest.originalPath), manifest])
|
|
3778
|
+
);
|
|
3779
|
+
if (!await fileExists(repoRoot)) {
|
|
3780
|
+
for (const manifest of repoManifests) {
|
|
3781
|
+
if (shouldDeferWatchSemanticRefresh(manifest.sourceKind)) {
|
|
3782
|
+
pendingSemanticRefresh.push({
|
|
3783
|
+
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? manifest.storedPath),
|
|
3784
|
+
repoRoot,
|
|
3785
|
+
path: toPosix(path8.relative(rootDir, manifest.originalPath ?? manifest.storedPath)),
|
|
3786
|
+
changeType: "removed",
|
|
3787
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3788
|
+
sourceId: manifest.sourceId,
|
|
3789
|
+
sourceKind: manifest.sourceKind
|
|
3790
|
+
});
|
|
3791
|
+
staleSourceIds.add(manifest.sourceId);
|
|
3792
|
+
} else {
|
|
3793
|
+
await removeManifestArtifacts(rootDir, manifest, paths);
|
|
3794
|
+
removed.push(manifest);
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
continue;
|
|
3798
|
+
}
|
|
3799
|
+
const ignoreRoots = repoSyncWorkspaceIgnorePaths(rootDir, paths, repoRoot);
|
|
3800
|
+
const collected = await collectDirectoryFiles(rootDir, repoRoot, repoRoot, normalizedOptions);
|
|
3801
|
+
const files = collected.files.filter((absolutePath) => !ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath)));
|
|
3802
|
+
skipped.push(
|
|
3803
|
+
...collected.skipped,
|
|
3804
|
+
...collected.files.filter((absolutePath) => ignoreRoots.some((ignoreRoot) => withinRoot(ignoreRoot, absolutePath))).map((absolutePath) => ({
|
|
3805
|
+
path: toPosix(path8.relative(rootDir, absolutePath)),
|
|
3806
|
+
reason: "workspace_generated"
|
|
3807
|
+
}))
|
|
3808
|
+
);
|
|
3809
|
+
scannedCount += files.length;
|
|
3810
|
+
const currentPaths = new Set(files.map((absolutePath) => path8.resolve(absolutePath)));
|
|
3811
|
+
for (const absolutePath of files) {
|
|
3812
|
+
const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
|
|
3813
|
+
if (shouldDeferWatchSemanticRefresh(prepared.sourceKind)) {
|
|
3814
|
+
const existing = manifestsByOriginalPath.get(path8.resolve(absolutePath));
|
|
3815
|
+
const contentHash = buildCompositeHash(prepared.payloadBytes, prepared.attachments);
|
|
3816
|
+
const changed = !existing || !preparedMatchesManifest(existing, prepared, contentHash);
|
|
3817
|
+
if (changed) {
|
|
3818
|
+
pendingSemanticRefresh.push({
|
|
3819
|
+
id: pendingSemanticRefreshId(
|
|
3820
|
+
existing ? "modified" : "added",
|
|
3821
|
+
repoRoot,
|
|
3822
|
+
prepared.repoRelativePath ?? toPosix(path8.relative(repoRoot, absolutePath))
|
|
3823
|
+
),
|
|
3824
|
+
repoRoot,
|
|
3825
|
+
path: toPosix(path8.relative(rootDir, absolutePath)),
|
|
3826
|
+
changeType: existing ? "modified" : "added",
|
|
3827
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3828
|
+
sourceId: existing?.sourceId,
|
|
3829
|
+
sourceKind: prepared.sourceKind
|
|
3830
|
+
});
|
|
3831
|
+
if (existing?.sourceId) {
|
|
3832
|
+
staleSourceIds.add(existing.sourceId);
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
continue;
|
|
3836
|
+
}
|
|
3837
|
+
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
3838
|
+
if (result.isNew) {
|
|
3839
|
+
imported.push(result.manifest);
|
|
3840
|
+
} else if (result.wasUpdated) {
|
|
3841
|
+
updated.push(result.manifest);
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
for (const manifest of repoManifests) {
|
|
3845
|
+
const originalPath = manifest.originalPath ? path8.resolve(manifest.originalPath) : null;
|
|
3846
|
+
if (originalPath && !currentPaths.has(originalPath)) {
|
|
3847
|
+
if (shouldDeferWatchSemanticRefresh(manifest.sourceKind)) {
|
|
3848
|
+
pendingSemanticRefresh.push({
|
|
3849
|
+
id: pendingSemanticRefreshId("removed", repoRoot, manifest.repoRelativePath ?? toPosix(path8.relative(repoRoot, originalPath))),
|
|
3850
|
+
repoRoot,
|
|
3851
|
+
path: toPosix(path8.relative(rootDir, originalPath)),
|
|
3852
|
+
changeType: "removed",
|
|
3853
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3854
|
+
sourceId: manifest.sourceId,
|
|
3855
|
+
sourceKind: manifest.sourceKind
|
|
3856
|
+
});
|
|
3857
|
+
staleSourceIds.add(manifest.sourceId);
|
|
3858
|
+
} else {
|
|
3859
|
+
await removeManifestArtifacts(rootDir, manifest, paths);
|
|
3860
|
+
removed.push(manifest);
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
if (uniqueRoots.length > 0) {
|
|
3866
|
+
await appendLogEntry(
|
|
3867
|
+
rootDir,
|
|
3868
|
+
"sync_repo_watch",
|
|
3869
|
+
uniqueRoots.map((repoRoot) => toPosix(path8.relative(rootDir, repoRoot)) || ".").join(","),
|
|
3870
|
+
[
|
|
3871
|
+
`repo_roots=${uniqueRoots.length}`,
|
|
3872
|
+
`scanned=${scannedCount}`,
|
|
3873
|
+
`imported=${imported.length}`,
|
|
3874
|
+
`updated=${updated.length}`,
|
|
3875
|
+
`removed=${removed.length}`,
|
|
3876
|
+
`pending_semantic_refresh=${pendingSemanticRefresh.length}`,
|
|
3877
|
+
`skipped=${skipped.length}`
|
|
3878
|
+
]
|
|
3879
|
+
);
|
|
3880
|
+
}
|
|
3881
|
+
return {
|
|
3882
|
+
repoRoots: uniqueRoots,
|
|
3883
|
+
scannedCount,
|
|
3884
|
+
imported,
|
|
3885
|
+
updated,
|
|
3886
|
+
removed,
|
|
3887
|
+
skipped,
|
|
3888
|
+
pendingSemanticRefresh: pendingSemanticRefresh.filter(
|
|
3889
|
+
(entry, index, items) => index === items.findIndex((candidate) => candidate.id === entry.id)
|
|
3890
|
+
),
|
|
3891
|
+
staleSourceIds: [...staleSourceIds]
|
|
3892
|
+
};
|
|
3893
|
+
}
|
|
2632
3894
|
async function prepareFileInput(_rootDir, absoluteInput, repoRoot) {
|
|
2633
|
-
const payloadBytes = await
|
|
3895
|
+
const payloadBytes = await fs8.readFile(absoluteInput);
|
|
2634
3896
|
const mimeType = guessMimeType(absoluteInput);
|
|
2635
3897
|
const sourceKind = inferKind(mimeType, absoluteInput);
|
|
2636
3898
|
const language = inferCodeLanguage(absoluteInput, mimeType);
|
|
2637
|
-
const storedExtension =
|
|
3899
|
+
const storedExtension = path8.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
|
|
2638
3900
|
let title;
|
|
2639
3901
|
let extractedText;
|
|
2640
3902
|
if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
|
|
2641
3903
|
extractedText = payloadBytes.toString("utf8");
|
|
2642
|
-
title = titleFromText(
|
|
3904
|
+
title = titleFromText(path8.basename(absoluteInput, path8.extname(absoluteInput)), extractedText);
|
|
2643
3905
|
} else {
|
|
2644
|
-
title =
|
|
3906
|
+
title = path8.basename(absoluteInput, path8.extname(absoluteInput));
|
|
2645
3907
|
}
|
|
2646
3908
|
return {
|
|
2647
3909
|
title,
|
|
@@ -2711,7 +3973,7 @@ async function prepareUrlInput(input, options) {
|
|
|
2711
3973
|
sourceKind = "markdown";
|
|
2712
3974
|
storedExtension = ".md";
|
|
2713
3975
|
} else {
|
|
2714
|
-
const extension =
|
|
3976
|
+
const extension = path8.extname(inputUrl.pathname);
|
|
2715
3977
|
storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
|
|
2716
3978
|
if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
|
|
2717
3979
|
extractedText = payloadBytes.toString("utf8");
|
|
@@ -2761,14 +4023,14 @@ async function collectInboxAttachmentRefs(inputDir, files) {
|
|
|
2761
4023
|
if (sourceKind !== "markdown") {
|
|
2762
4024
|
continue;
|
|
2763
4025
|
}
|
|
2764
|
-
const content = await
|
|
4026
|
+
const content = await fs8.readFile(absolutePath, "utf8");
|
|
2765
4027
|
const refs = extractMarkdownReferences(content);
|
|
2766
4028
|
if (!refs.length) {
|
|
2767
4029
|
continue;
|
|
2768
4030
|
}
|
|
2769
4031
|
const sourceRefs = [];
|
|
2770
4032
|
for (const ref of refs) {
|
|
2771
|
-
const resolved =
|
|
4033
|
+
const resolved = path8.resolve(path8.dirname(absolutePath), ref);
|
|
2772
4034
|
if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
|
|
2773
4035
|
continue;
|
|
2774
4036
|
}
|
|
@@ -2802,12 +4064,12 @@ function rewriteMarkdownReferences(content, replacements) {
|
|
|
2802
4064
|
});
|
|
2803
4065
|
}
|
|
2804
4066
|
async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
2805
|
-
const originalBytes = await
|
|
4067
|
+
const originalBytes = await fs8.readFile(absolutePath);
|
|
2806
4068
|
const originalText = originalBytes.toString("utf8");
|
|
2807
|
-
const title = titleFromText(
|
|
4069
|
+
const title = titleFromText(path8.basename(absolutePath, path8.extname(absolutePath)), originalText);
|
|
2808
4070
|
const attachments = [];
|
|
2809
4071
|
for (const attachmentRef of attachmentRefs) {
|
|
2810
|
-
const bytes = await
|
|
4072
|
+
const bytes = await fs8.readFile(attachmentRef.absolutePath);
|
|
2811
4073
|
attachments.push({
|
|
2812
4074
|
relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
|
|
2813
4075
|
mimeType: guessMimeType(attachmentRef.absolutePath),
|
|
@@ -2830,7 +4092,7 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
|
|
|
2830
4092
|
sourceKind: "markdown",
|
|
2831
4093
|
originalPath: toPosix(absolutePath),
|
|
2832
4094
|
mimeType: "text/markdown",
|
|
2833
|
-
storedExtension:
|
|
4095
|
+
storedExtension: path8.extname(absolutePath) || ".md",
|
|
2834
4096
|
payloadBytes: Buffer.from(rewrittenText, "utf8"),
|
|
2835
4097
|
extractedText: rewrittenText,
|
|
2836
4098
|
attachments,
|
|
@@ -2843,17 +4105,70 @@ function isSupportedInboxKind(sourceKind) {
|
|
|
2843
4105
|
async function ingestInput(rootDir, input, options) {
|
|
2844
4106
|
const { paths } = await initWorkspace(rootDir);
|
|
2845
4107
|
const normalizedOptions = normalizeIngestOptions(options);
|
|
2846
|
-
const absoluteInput =
|
|
2847
|
-
const repoRoot =
|
|
2848
|
-
const prepared =
|
|
4108
|
+
const absoluteInput = path8.resolve(rootDir, input);
|
|
4109
|
+
const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot2(absoluteInput).then((value) => value ?? path8.dirname(absoluteInput));
|
|
4110
|
+
const prepared = isHttpUrl(input) ? await prepareUrlInput(input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
|
|
2849
4111
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
2850
4112
|
return result.manifest;
|
|
2851
4113
|
}
|
|
4114
|
+
async function addInput(rootDir, input, options = {}) {
|
|
4115
|
+
const { paths } = await initWorkspace(rootDir);
|
|
4116
|
+
if (!isHttpUrl(input) && !arxivIdFromInput(input)) {
|
|
4117
|
+
throw new Error("`swarmvault add` only supports URLs and bare arXiv ids in the current release.");
|
|
4118
|
+
}
|
|
4119
|
+
let prepared = null;
|
|
4120
|
+
let captureType = "url";
|
|
4121
|
+
let normalizedUrl = input;
|
|
4122
|
+
let fallback = false;
|
|
4123
|
+
try {
|
|
4124
|
+
if (arxivIdFromInput(input)) {
|
|
4125
|
+
const captured = await captureArxivMarkdown(input, options);
|
|
4126
|
+
prepared = prepareCapturedMarkdownInput({
|
|
4127
|
+
title: captured.title,
|
|
4128
|
+
url: captured.normalizedUrl,
|
|
4129
|
+
markdown: captured.markdown,
|
|
4130
|
+
logDetails: ["capture_type=arxiv"]
|
|
4131
|
+
});
|
|
4132
|
+
captureType = "arxiv";
|
|
4133
|
+
normalizedUrl = captured.normalizedUrl;
|
|
4134
|
+
} else if (isTweetUrl(input)) {
|
|
4135
|
+
const captured = await captureTweetMarkdown(input, options);
|
|
4136
|
+
prepared = prepareCapturedMarkdownInput({
|
|
4137
|
+
title: captured.title,
|
|
4138
|
+
url: captured.normalizedUrl,
|
|
4139
|
+
markdown: captured.markdown,
|
|
4140
|
+
logDetails: ["capture_type=tweet"]
|
|
4141
|
+
});
|
|
4142
|
+
captureType = "tweet";
|
|
4143
|
+
normalizedUrl = captured.normalizedUrl;
|
|
4144
|
+
}
|
|
4145
|
+
} catch {
|
|
4146
|
+
fallback = true;
|
|
4147
|
+
}
|
|
4148
|
+
if (!prepared) {
|
|
4149
|
+
normalizedUrl = arxivIdFromInput(input) ? `https://arxiv.org/abs/${arxivIdFromInput(input)}` : normalizeOriginUrl(input);
|
|
4150
|
+
return {
|
|
4151
|
+
captureType: "url",
|
|
4152
|
+
manifest: await ingestInput(rootDir, normalizedUrl, options),
|
|
4153
|
+
normalizedUrl,
|
|
4154
|
+
title: normalizedUrl,
|
|
4155
|
+
fallback: true
|
|
4156
|
+
};
|
|
4157
|
+
}
|
|
4158
|
+
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
4159
|
+
return {
|
|
4160
|
+
captureType,
|
|
4161
|
+
manifest: result.manifest,
|
|
4162
|
+
normalizedUrl,
|
|
4163
|
+
title: prepared.title,
|
|
4164
|
+
fallback
|
|
4165
|
+
};
|
|
4166
|
+
}
|
|
2852
4167
|
async function ingestDirectory(rootDir, inputDir, options) {
|
|
2853
4168
|
const { paths } = await initWorkspace(rootDir);
|
|
2854
4169
|
const normalizedOptions = normalizeIngestOptions(options);
|
|
2855
|
-
const absoluteInputDir =
|
|
2856
|
-
const repoRoot = normalizedOptions.repoRoot ?? await
|
|
4170
|
+
const absoluteInputDir = path8.resolve(rootDir, inputDir);
|
|
4171
|
+
const repoRoot = normalizedOptions.repoRoot ?? await findNearestGitRoot2(absoluteInputDir) ?? absoluteInputDir;
|
|
2857
4172
|
if (!await fileExists(absoluteInputDir)) {
|
|
2858
4173
|
throw new Error(`Directory not found: ${absoluteInputDir}`);
|
|
2859
4174
|
}
|
|
@@ -2868,11 +4183,11 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
2868
4183
|
} else if (result.wasUpdated) {
|
|
2869
4184
|
updated.push(result.manifest);
|
|
2870
4185
|
} else {
|
|
2871
|
-
skipped.push({ path: toPosix(
|
|
4186
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "duplicate_content" });
|
|
2872
4187
|
}
|
|
2873
4188
|
}
|
|
2874
|
-
await appendLogEntry(rootDir, "ingest_directory", toPosix(
|
|
2875
|
-
`repo_root=${toPosix(
|
|
4189
|
+
await appendLogEntry(rootDir, "ingest_directory", toPosix(path8.relative(rootDir, absoluteInputDir)) || ".", [
|
|
4190
|
+
`repo_root=${toPosix(path8.relative(rootDir, repoRoot)) || "."}`,
|
|
2876
4191
|
`scanned=${files.length}`,
|
|
2877
4192
|
`imported=${imported.length}`,
|
|
2878
4193
|
`updated=${updated.length}`,
|
|
@@ -2889,7 +4204,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
2889
4204
|
}
|
|
2890
4205
|
async function importInbox(rootDir, inputDir) {
|
|
2891
4206
|
const { paths } = await initWorkspace(rootDir);
|
|
2892
|
-
const effectiveInputDir =
|
|
4207
|
+
const effectiveInputDir = path8.resolve(rootDir, inputDir ?? paths.inboxDir);
|
|
2893
4208
|
if (!await fileExists(effectiveInputDir)) {
|
|
2894
4209
|
throw new Error(`Inbox directory not found: ${effectiveInputDir}`);
|
|
2895
4210
|
}
|
|
@@ -2900,31 +4215,31 @@ async function importInbox(rootDir, inputDir) {
|
|
|
2900
4215
|
const skipped = [];
|
|
2901
4216
|
let attachmentCount = 0;
|
|
2902
4217
|
for (const absolutePath of files) {
|
|
2903
|
-
const basename =
|
|
4218
|
+
const basename = path8.basename(absolutePath);
|
|
2904
4219
|
if (basename.startsWith(".")) {
|
|
2905
|
-
skipped.push({ path: toPosix(
|
|
4220
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "hidden_file" });
|
|
2906
4221
|
continue;
|
|
2907
4222
|
}
|
|
2908
4223
|
if (claimedAttachments.has(absolutePath)) {
|
|
2909
|
-
skipped.push({ path: toPosix(
|
|
4224
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
|
|
2910
4225
|
continue;
|
|
2911
4226
|
}
|
|
2912
4227
|
const mimeType = guessMimeType(absolutePath);
|
|
2913
4228
|
const sourceKind = inferKind(mimeType, absolutePath);
|
|
2914
4229
|
if (!isSupportedInboxKind(sourceKind)) {
|
|
2915
|
-
skipped.push({ path: toPosix(
|
|
4230
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
2916
4231
|
continue;
|
|
2917
4232
|
}
|
|
2918
4233
|
const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
|
|
2919
4234
|
const result = await persistPreparedInput(rootDir, prepared, paths);
|
|
2920
4235
|
if (!result.isNew) {
|
|
2921
|
-
skipped.push({ path: toPosix(
|
|
4236
|
+
skipped.push({ path: toPosix(path8.relative(rootDir, absolutePath)), reason: "duplicate_content" });
|
|
2922
4237
|
continue;
|
|
2923
4238
|
}
|
|
2924
4239
|
attachmentCount += result.manifest.attachments?.length ?? 0;
|
|
2925
4240
|
imported.push(result.manifest);
|
|
2926
4241
|
}
|
|
2927
|
-
await appendLogEntry(rootDir, "inbox_import", toPosix(
|
|
4242
|
+
await appendLogEntry(rootDir, "inbox_import", toPosix(path8.relative(rootDir, effectiveInputDir)) || ".", [
|
|
2928
4243
|
`scanned=${files.length}`,
|
|
2929
4244
|
`imported=${imported.length}`,
|
|
2930
4245
|
`attachments=${attachmentCount}`,
|
|
@@ -2943,9 +4258,9 @@ async function listManifests(rootDir) {
|
|
|
2943
4258
|
if (!await fileExists(paths.manifestsDir)) {
|
|
2944
4259
|
return [];
|
|
2945
4260
|
}
|
|
2946
|
-
const entries = await
|
|
4261
|
+
const entries = await fs8.readdir(paths.manifestsDir);
|
|
2947
4262
|
const manifests = await Promise.all(
|
|
2948
|
-
entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(
|
|
4263
|
+
entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path8.join(paths.manifestsDir, entry)))
|
|
2949
4264
|
);
|
|
2950
4265
|
return manifests.filter((manifest) => Boolean(manifest));
|
|
2951
4266
|
}
|
|
@@ -2953,28 +4268,28 @@ async function readExtractedText(rootDir, manifest) {
|
|
|
2953
4268
|
if (!manifest.extractedTextPath) {
|
|
2954
4269
|
return void 0;
|
|
2955
4270
|
}
|
|
2956
|
-
const absolutePath =
|
|
4271
|
+
const absolutePath = path8.resolve(rootDir, manifest.extractedTextPath);
|
|
2957
4272
|
if (!await fileExists(absolutePath)) {
|
|
2958
4273
|
return void 0;
|
|
2959
4274
|
}
|
|
2960
|
-
return
|
|
4275
|
+
return fs8.readFile(absolutePath, "utf8");
|
|
2961
4276
|
}
|
|
2962
4277
|
|
|
2963
4278
|
// src/mcp.ts
|
|
2964
|
-
import
|
|
2965
|
-
import
|
|
4279
|
+
import fs15 from "fs/promises";
|
|
4280
|
+
import path18 from "path";
|
|
2966
4281
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2967
4282
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2968
4283
|
import { z as z7 } from "zod";
|
|
2969
4284
|
|
|
2970
4285
|
// src/schema.ts
|
|
2971
|
-
import
|
|
2972
|
-
import
|
|
4286
|
+
import fs9 from "fs/promises";
|
|
4287
|
+
import path9 from "path";
|
|
2973
4288
|
function normalizeSchemaContent(content) {
|
|
2974
4289
|
return content.trim() ? content.trim() : defaultVaultSchema().trim();
|
|
2975
4290
|
}
|
|
2976
4291
|
async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
|
|
2977
|
-
const content = await fileExists(schemaPath) ? await
|
|
4292
|
+
const content = await fileExists(schemaPath) ? await fs9.readFile(schemaPath, "utf8") : fallback;
|
|
2978
4293
|
const normalized = normalizeSchemaContent(content);
|
|
2979
4294
|
return {
|
|
2980
4295
|
path: schemaPath,
|
|
@@ -2983,7 +4298,7 @@ async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
|
|
|
2983
4298
|
};
|
|
2984
4299
|
}
|
|
2985
4300
|
function resolveProjectSchemaPath(rootDir, schemaPath) {
|
|
2986
|
-
return
|
|
4301
|
+
return path9.resolve(rootDir, schemaPath);
|
|
2987
4302
|
}
|
|
2988
4303
|
function composeVaultSchema(root, projectSchemas = []) {
|
|
2989
4304
|
if (!projectSchemas.length) {
|
|
@@ -2999,7 +4314,7 @@ function composeVaultSchema(root, projectSchemas = []) {
|
|
|
2999
4314
|
(schema) => [
|
|
3000
4315
|
`## Project Schema`,
|
|
3001
4316
|
"",
|
|
3002
|
-
`Path: ${toPosix(
|
|
4317
|
+
`Path: ${toPosix(path9.relative(path9.dirname(root.path), schema.path) || schema.path)}`,
|
|
3003
4318
|
"",
|
|
3004
4319
|
schema.content
|
|
3005
4320
|
].join("\n")
|
|
@@ -3075,13 +4390,13 @@ function buildSchemaPrompt(schema, instruction) {
|
|
|
3075
4390
|
}
|
|
3076
4391
|
|
|
3077
4392
|
// src/vault.ts
|
|
3078
|
-
import
|
|
3079
|
-
import
|
|
3080
|
-
import
|
|
4393
|
+
import fs14 from "fs/promises";
|
|
4394
|
+
import path17 from "path";
|
|
4395
|
+
import matter8 from "gray-matter";
|
|
3081
4396
|
import { z as z6 } from "zod";
|
|
3082
4397
|
|
|
3083
4398
|
// src/analysis.ts
|
|
3084
|
-
import
|
|
4399
|
+
import path10 from "path";
|
|
3085
4400
|
import { z } from "zod";
|
|
3086
4401
|
var ANALYSIS_FORMAT_VERSION = 4;
|
|
3087
4402
|
var sourceAnalysisSchema = z.object({
|
|
@@ -3260,7 +4575,7 @@ ${truncate(text, 18e3)}`
|
|
|
3260
4575
|
};
|
|
3261
4576
|
}
|
|
3262
4577
|
async function analyzeSource(manifest, extractedText, provider, paths, schema) {
|
|
3263
|
-
const cachePath =
|
|
4578
|
+
const cachePath = path10.join(paths.analysesDir, `${manifest.sourceId}.json`);
|
|
3264
4579
|
const cached = await readJsonFile(cachePath);
|
|
3265
4580
|
if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.schemaHash === schema.hash) {
|
|
3266
4581
|
return cached;
|
|
@@ -3300,30 +4615,112 @@ function analysisSignature(analysis) {
|
|
|
3300
4615
|
return sha256(JSON.stringify(analysis));
|
|
3301
4616
|
}
|
|
3302
4617
|
|
|
3303
|
-
// src/
|
|
3304
|
-
|
|
3305
|
-
|
|
4618
|
+
// src/benchmark.ts
|
|
4619
|
+
var CHARS_PER_TOKEN = 4;
|
|
4620
|
+
var DEFAULT_BENCHMARK_QUESTIONS = [
|
|
4621
|
+
"How does this vault connect the main concepts?",
|
|
4622
|
+
"Which pages bridge the biggest communities?",
|
|
4623
|
+
"What are the core abstractions in this vault?",
|
|
4624
|
+
"Where are the biggest knowledge gaps?",
|
|
4625
|
+
"What evidence should I read first?"
|
|
4626
|
+
];
|
|
4627
|
+
function nodeMap(graph) {
|
|
4628
|
+
return new Map(graph.nodes.map((node) => [node.id, node]));
|
|
3306
4629
|
}
|
|
3307
|
-
function
|
|
3308
|
-
|
|
3309
|
-
const relevant = claims.filter((c) => c.text.toLowerCase().includes(lower));
|
|
3310
|
-
if (!relevant.length) {
|
|
3311
|
-
return 0.5;
|
|
3312
|
-
}
|
|
3313
|
-
return relevant.reduce((sum, c) => sum + c.confidence, 0) / relevant.length;
|
|
4630
|
+
function pageMap(graph) {
|
|
4631
|
+
return new Map(graph.pages.map((page) => [page.id, page]));
|
|
3314
4632
|
}
|
|
3315
|
-
function
|
|
3316
|
-
return Math.
|
|
4633
|
+
function estimateTokens(text) {
|
|
4634
|
+
return Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN));
|
|
3317
4635
|
}
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
4636
|
+
function estimateCorpusWords(texts) {
|
|
4637
|
+
return texts.reduce((total, text) => total + normalizeWhitespace(text).split(/\s+/).filter(Boolean).length, 0);
|
|
4638
|
+
}
|
|
4639
|
+
function benchmarkQueryTokens(graph, queryResult, pageContentsById) {
|
|
4640
|
+
const nodesById = nodeMap(graph);
|
|
4641
|
+
const pagesById = pageMap(graph);
|
|
4642
|
+
const edgeIds = new Set(queryResult.visitedEdgeIds);
|
|
4643
|
+
const lines = [];
|
|
4644
|
+
for (const pageId of queryResult.pageIds) {
|
|
4645
|
+
const page = pagesById.get(pageId);
|
|
4646
|
+
if (!page) {
|
|
4647
|
+
continue;
|
|
4648
|
+
}
|
|
4649
|
+
const content = normalizeWhitespace(pageContentsById.get(pageId) ?? "").slice(0, 280);
|
|
4650
|
+
lines.push(`PAGE ${page.title} path=${page.path} kind=${page.kind}`);
|
|
4651
|
+
if (content) {
|
|
4652
|
+
lines.push(`PAGE_BODY ${content}`);
|
|
4653
|
+
}
|
|
4654
|
+
}
|
|
4655
|
+
for (const nodeId of queryResult.visitedNodeIds) {
|
|
4656
|
+
const node = nodesById.get(nodeId);
|
|
4657
|
+
if (!node) {
|
|
4658
|
+
continue;
|
|
4659
|
+
}
|
|
4660
|
+
lines.push(`NODE ${node.label} type=${node.type} community=${node.communityId ?? "unassigned"} page=${node.pageId ?? "none"}`);
|
|
4661
|
+
}
|
|
4662
|
+
for (const edge of graph.edges) {
|
|
4663
|
+
if (!edgeIds.has(edge.id)) {
|
|
4664
|
+
continue;
|
|
4665
|
+
}
|
|
4666
|
+
const source = nodesById.get(edge.source)?.label ?? edge.source;
|
|
4667
|
+
const target = nodesById.get(edge.target)?.label ?? edge.target;
|
|
4668
|
+
lines.push(`EDGE ${source} --${edge.relation}/${edge.evidenceClass}/${edge.confidence.toFixed(2)}--> ${target}`);
|
|
4669
|
+
}
|
|
4670
|
+
const queryTokens = estimateTokens(lines.join("\n"));
|
|
4671
|
+
return {
|
|
4672
|
+
question: queryResult.question,
|
|
4673
|
+
queryTokens,
|
|
4674
|
+
reduction: 0,
|
|
4675
|
+
visitedNodeIds: queryResult.visitedNodeIds,
|
|
4676
|
+
pageIds: queryResult.pageIds
|
|
4677
|
+
};
|
|
4678
|
+
}
|
|
4679
|
+
function buildBenchmarkArtifact(input) {
|
|
4680
|
+
const corpusTokens = Math.max(1, Math.round(input.corpusWords * (100 / 75)));
|
|
4681
|
+
const perQuestion = input.perQuestion.filter((entry) => entry.queryTokens > 0).map((entry) => ({
|
|
4682
|
+
...entry,
|
|
4683
|
+
reduction: Number(Math.max(0, 1 - entry.queryTokens / Math.max(1, corpusTokens)).toFixed(3))
|
|
4684
|
+
}));
|
|
4685
|
+
const avgQueryTokens = perQuestion.length ? Math.max(1, Math.round(perQuestion.reduce((total, entry) => total + entry.queryTokens, 0) / perQuestion.length)) : 0;
|
|
4686
|
+
const reductionRatio = avgQueryTokens ? Number(Math.max(0, 1 - avgQueryTokens / Math.max(1, corpusTokens)).toFixed(3)) : 0;
|
|
4687
|
+
return {
|
|
4688
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4689
|
+
corpusWords: input.corpusWords,
|
|
4690
|
+
corpusTokens,
|
|
4691
|
+
nodes: input.graph.nodes.length,
|
|
4692
|
+
edges: input.graph.edges.length,
|
|
4693
|
+
avgQueryTokens,
|
|
4694
|
+
reductionRatio,
|
|
4695
|
+
sampleQuestions: input.questions,
|
|
4696
|
+
perQuestion
|
|
4697
|
+
};
|
|
4698
|
+
}
|
|
4699
|
+
|
|
4700
|
+
// src/confidence.ts
|
|
4701
|
+
function nodeConfidence(sourceCount) {
|
|
4702
|
+
return Math.min(0.5 + sourceCount * 0.15, 0.95);
|
|
4703
|
+
}
|
|
4704
|
+
function edgeConfidence(claims, conceptName) {
|
|
4705
|
+
const lower = conceptName.toLowerCase();
|
|
4706
|
+
const relevant = claims.filter((c) => c.text.toLowerCase().includes(lower));
|
|
4707
|
+
if (!relevant.length) {
|
|
4708
|
+
return 0.5;
|
|
4709
|
+
}
|
|
4710
|
+
return relevant.reduce((sum, c) => sum + c.confidence, 0) / relevant.length;
|
|
4711
|
+
}
|
|
4712
|
+
function conflictConfidence(claimA, claimB) {
|
|
4713
|
+
return Math.min(claimA.confidence, claimB.confidence);
|
|
4714
|
+
}
|
|
4715
|
+
|
|
4716
|
+
// src/deep-lint.ts
|
|
4717
|
+
import fs10 from "fs/promises";
|
|
4718
|
+
import path13 from "path";
|
|
4719
|
+
import matter3 from "gray-matter";
|
|
4720
|
+
import { z as z4 } from "zod";
|
|
4721
|
+
|
|
4722
|
+
// src/findings.ts
|
|
4723
|
+
function normalizeFindingSeverity(value) {
|
|
3327
4724
|
if (typeof value !== "string") {
|
|
3328
4725
|
return "info";
|
|
3329
4726
|
}
|
|
@@ -3339,7 +4736,7 @@ function normalizeFindingSeverity(value) {
|
|
|
3339
4736
|
|
|
3340
4737
|
// src/orchestration.ts
|
|
3341
4738
|
import { spawn } from "child_process";
|
|
3342
|
-
import
|
|
4739
|
+
import path11 from "path";
|
|
3343
4740
|
import { z as z2 } from "zod";
|
|
3344
4741
|
var orchestrationRoleResultSchema = z2.object({
|
|
3345
4742
|
summary: z2.string().optional(),
|
|
@@ -3432,7 +4829,7 @@ async function runProviderRole(rootDir, role, roleConfig, input) {
|
|
|
3432
4829
|
}
|
|
3433
4830
|
async function runCommandRole(rootDir, role, executor, input) {
|
|
3434
4831
|
const [command, ...args] = executor.command;
|
|
3435
|
-
const cwd = executor.cwd ?
|
|
4832
|
+
const cwd = executor.cwd ? path11.resolve(rootDir, executor.cwd) : rootDir;
|
|
3436
4833
|
const child = spawn(command, args, {
|
|
3437
4834
|
cwd,
|
|
3438
4835
|
env: {
|
|
@@ -3526,7 +4923,7 @@ function summarizeRoleQuestions(results) {
|
|
|
3526
4923
|
}
|
|
3527
4924
|
|
|
3528
4925
|
// src/web-search/registry.ts
|
|
3529
|
-
import
|
|
4926
|
+
import path12 from "path";
|
|
3530
4927
|
import { pathToFileURL } from "url";
|
|
3531
4928
|
import { z as z3 } from "zod";
|
|
3532
4929
|
|
|
@@ -3624,7 +5021,7 @@ async function createWebSearchAdapter(id, config, rootDir) {
|
|
|
3624
5021
|
if (!config.module) {
|
|
3625
5022
|
throw new Error(`Web search provider ${id} is type "custom" but no module path was configured.`);
|
|
3626
5023
|
}
|
|
3627
|
-
const resolvedModule =
|
|
5024
|
+
const resolvedModule = path12.isAbsolute(config.module) ? config.module : path12.resolve(rootDir, config.module);
|
|
3628
5025
|
const loaded = await import(pathToFileURL(resolvedModule).href);
|
|
3629
5026
|
const parsed = customWebSearchModuleSchema.parse(loaded);
|
|
3630
5027
|
return parsed.createAdapter(id, config, rootDir);
|
|
@@ -3684,9 +5081,9 @@ async function loadContextPages(rootDir, graph) {
|
|
|
3684
5081
|
);
|
|
3685
5082
|
return Promise.all(
|
|
3686
5083
|
contextPages.slice(0, 18).map(async (page) => {
|
|
3687
|
-
const absolutePath =
|
|
3688
|
-
const raw = await
|
|
3689
|
-
const parsed =
|
|
5084
|
+
const absolutePath = path13.join(paths.wikiDir, page.path);
|
|
5085
|
+
const raw = await fs10.readFile(absolutePath, "utf8").catch(() => "");
|
|
5086
|
+
const parsed = matter3(raw);
|
|
3690
5087
|
return {
|
|
3691
5088
|
id: page.id,
|
|
3692
5089
|
title: page.title,
|
|
@@ -3733,7 +5130,7 @@ function heuristicDeepFindings(contextPages, structuralFindings, graph) {
|
|
|
3733
5130
|
code: "missing_citation",
|
|
3734
5131
|
message: finding.message,
|
|
3735
5132
|
pagePath: finding.pagePath,
|
|
3736
|
-
suggestedQuery: finding.pagePath ? `Which sources support the claims in ${
|
|
5133
|
+
suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path13.basename(finding.pagePath, ".md")}?` : void 0
|
|
3737
5134
|
});
|
|
3738
5135
|
}
|
|
3739
5136
|
for (const page of contextPages.filter((item) => item.kind === "source").slice(0, 3)) {
|
|
@@ -4214,7 +5611,7 @@ function topGodNodes(graph, limit = 10) {
|
|
|
4214
5611
|
}
|
|
4215
5612
|
|
|
4216
5613
|
// src/markdown.ts
|
|
4217
|
-
import
|
|
5614
|
+
import matter4 from "gray-matter";
|
|
4218
5615
|
function uniqueStrings(values) {
|
|
4219
5616
|
return uniqueBy(values.filter(Boolean), (value) => value);
|
|
4220
5617
|
}
|
|
@@ -4381,7 +5778,7 @@ function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutput
|
|
|
4381
5778
|
compiledFrom: metadata.compiledFrom,
|
|
4382
5779
|
managedBy: metadata.managedBy
|
|
4383
5780
|
},
|
|
4384
|
-
content:
|
|
5781
|
+
content: matter4.stringify(body, frontmatter)
|
|
4385
5782
|
};
|
|
4386
5783
|
}
|
|
4387
5784
|
function buildModulePage(input) {
|
|
@@ -4528,7 +5925,7 @@ function buildModulePage(input) {
|
|
|
4528
5925
|
compiledFrom: metadata.compiledFrom,
|
|
4529
5926
|
managedBy: metadata.managedBy
|
|
4530
5927
|
},
|
|
4531
|
-
content:
|
|
5928
|
+
content: matter4.stringify(body, frontmatter)
|
|
4532
5929
|
};
|
|
4533
5930
|
}
|
|
4534
5931
|
function buildAggregatePage(kind, name, descriptions, sourceAnalyses, sourceHashes, schemaHash, metadata, relativePath, relatedOutputs = [], decorations) {
|
|
@@ -4599,7 +5996,7 @@ function buildAggregatePage(kind, name, descriptions, sourceAnalyses, sourceHash
|
|
|
4599
5996
|
compiledFrom: metadata.compiledFrom,
|
|
4600
5997
|
managedBy: metadata.managedBy
|
|
4601
5998
|
},
|
|
4602
|
-
content:
|
|
5999
|
+
content: matter4.stringify(body, frontmatter)
|
|
4603
6000
|
};
|
|
4604
6001
|
}
|
|
4605
6002
|
function buildIndexPage(pages, schemaHash, metadata, projectPages = []) {
|
|
@@ -4675,7 +6072,7 @@ function buildIndexPage(pages, schemaHash, metadata, projectPages = []) {
|
|
|
4675
6072
|
}
|
|
4676
6073
|
function buildSectionIndex(kind, pages, schemaHash, metadata, projectIds = []) {
|
|
4677
6074
|
const title = kind.charAt(0).toUpperCase() + kind.slice(1);
|
|
4678
|
-
return
|
|
6075
|
+
return matter4.stringify(
|
|
4679
6076
|
[`# ${title}`, "", ...pages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`), ""].join("\n"),
|
|
4680
6077
|
{
|
|
4681
6078
|
page_id: `${kind}:index`,
|
|
@@ -4770,6 +6167,15 @@ function buildGraphReportPage(input) {
|
|
|
4770
6167
|
`- Pages: ${input.graph.pages.length}`,
|
|
4771
6168
|
`- Communities: ${input.graph.communities?.length ?? 0}`,
|
|
4772
6169
|
"",
|
|
6170
|
+
...input.benchmark ? [
|
|
6171
|
+
"## Benchmark",
|
|
6172
|
+
"",
|
|
6173
|
+
`- Corpus Tokens: ${input.benchmark.corpusTokens}`,
|
|
6174
|
+
`- Avg Query Tokens: ${input.benchmark.avgQueryTokens}`,
|
|
6175
|
+
`- Reduction Ratio: ${(input.benchmark.reductionRatio * 100).toFixed(1)}%`,
|
|
6176
|
+
`- Sample Questions: ${input.benchmark.sampleQuestions.length}`,
|
|
6177
|
+
""
|
|
6178
|
+
] : [],
|
|
4773
6179
|
"## God Nodes",
|
|
4774
6180
|
"",
|
|
4775
6181
|
...godNodes.length ? godNodes.map((node) => `- ${graphNodeLink(node, pagesById)} (${nodeSummary(node)})`) : ["- No high-connectivity nodes detected."],
|
|
@@ -4822,7 +6228,7 @@ function buildGraphReportPage(input) {
|
|
|
4822
6228
|
compiledFrom: input.metadata.compiledFrom,
|
|
4823
6229
|
managedBy: input.metadata.managedBy
|
|
4824
6230
|
},
|
|
4825
|
-
content:
|
|
6231
|
+
content: matter4.stringify(body, frontmatter)
|
|
4826
6232
|
};
|
|
4827
6233
|
}
|
|
4828
6234
|
function buildCommunitySummaryPage(input) {
|
|
@@ -4904,11 +6310,11 @@ function buildCommunitySummaryPage(input) {
|
|
|
4904
6310
|
compiledFrom: input.metadata.compiledFrom,
|
|
4905
6311
|
managedBy: input.metadata.managedBy
|
|
4906
6312
|
},
|
|
4907
|
-
content:
|
|
6313
|
+
content: matter4.stringify(body, frontmatter)
|
|
4908
6314
|
};
|
|
4909
6315
|
}
|
|
4910
6316
|
function buildProjectsIndex(projectPages, schemaHash, metadata) {
|
|
4911
|
-
return
|
|
6317
|
+
return matter4.stringify(
|
|
4912
6318
|
[
|
|
4913
6319
|
"# Projects",
|
|
4914
6320
|
"",
|
|
@@ -4938,7 +6344,7 @@ function buildProjectsIndex(projectPages, schemaHash, metadata) {
|
|
|
4938
6344
|
}
|
|
4939
6345
|
function buildProjectIndex(input) {
|
|
4940
6346
|
const title = `Project: ${input.projectId}`;
|
|
4941
|
-
return
|
|
6347
|
+
return matter4.stringify(
|
|
4942
6348
|
[
|
|
4943
6349
|
`# ${title}`,
|
|
4944
6350
|
"",
|
|
@@ -5051,7 +6457,7 @@ function buildOutputPage(input) {
|
|
|
5051
6457
|
outputFormat: input.outputFormat,
|
|
5052
6458
|
outputAssets
|
|
5053
6459
|
},
|
|
5054
|
-
content:
|
|
6460
|
+
content: matter4.stringify(
|
|
5055
6461
|
(input.outputFormat === "slides" ? [
|
|
5056
6462
|
input.answer,
|
|
5057
6463
|
"",
|
|
@@ -5177,7 +6583,7 @@ function buildExploreHubPage(input) {
|
|
|
5177
6583
|
outputFormat: input.outputFormat,
|
|
5178
6584
|
outputAssets
|
|
5179
6585
|
},
|
|
5180
|
-
content:
|
|
6586
|
+
content: matter4.stringify(
|
|
5181
6587
|
(input.outputFormat === "slides" ? [
|
|
5182
6588
|
`# ${title}`,
|
|
5183
6589
|
"",
|
|
@@ -5441,14 +6847,14 @@ function buildOutputAssetManifest(input) {
|
|
|
5441
6847
|
}
|
|
5442
6848
|
|
|
5443
6849
|
// src/outputs.ts
|
|
5444
|
-
import
|
|
5445
|
-
import
|
|
5446
|
-
import
|
|
6850
|
+
import fs12 from "fs/promises";
|
|
6851
|
+
import path15 from "path";
|
|
6852
|
+
import matter6 from "gray-matter";
|
|
5447
6853
|
|
|
5448
6854
|
// src/pages.ts
|
|
5449
|
-
import
|
|
5450
|
-
import
|
|
5451
|
-
import
|
|
6855
|
+
import fs11 from "fs/promises";
|
|
6856
|
+
import path14 from "path";
|
|
6857
|
+
import matter5 from "gray-matter";
|
|
5452
6858
|
function normalizeStringArray(value) {
|
|
5453
6859
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
5454
6860
|
}
|
|
@@ -5519,8 +6925,8 @@ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
|
|
|
5519
6925
|
updatedAt: updatedFallback
|
|
5520
6926
|
};
|
|
5521
6927
|
}
|
|
5522
|
-
const content = await
|
|
5523
|
-
const parsed =
|
|
6928
|
+
const content = await fs11.readFile(absolutePath, "utf8");
|
|
6929
|
+
const parsed = matter5(content);
|
|
5524
6930
|
return {
|
|
5525
6931
|
status: normalizePageStatus(parsed.data.status, defaults.status ?? "active"),
|
|
5526
6932
|
managedBy: normalizePageManager(parsed.data.managed_by, defaults.managedBy ?? "system"),
|
|
@@ -5554,11 +6960,11 @@ function inferPageKind(relativePath, explicitKind = void 0) {
|
|
|
5554
6960
|
return "index";
|
|
5555
6961
|
}
|
|
5556
6962
|
function parseStoredPage(relativePath, content, defaults = {}) {
|
|
5557
|
-
const parsed =
|
|
6963
|
+
const parsed = matter5(content);
|
|
5558
6964
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5559
6965
|
const fallbackCreatedAt = defaults.createdAt ?? now;
|
|
5560
6966
|
const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
|
|
5561
|
-
const title = typeof parsed.data.title === "string" ? parsed.data.title :
|
|
6967
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : path14.basename(relativePath, ".md");
|
|
5562
6968
|
const kind = inferPageKind(relativePath, parsed.data.kind);
|
|
5563
6969
|
const sourceIds = normalizeStringArray(parsed.data.source_ids);
|
|
5564
6970
|
const projectIds = normalizeProjectIds(parsed.data.project_ids);
|
|
@@ -5597,18 +7003,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
|
|
|
5597
7003
|
};
|
|
5598
7004
|
}
|
|
5599
7005
|
async function loadInsightPages(wikiDir) {
|
|
5600
|
-
const insightsDir =
|
|
7006
|
+
const insightsDir = path14.join(wikiDir, "insights");
|
|
5601
7007
|
if (!await fileExists(insightsDir)) {
|
|
5602
7008
|
return [];
|
|
5603
7009
|
}
|
|
5604
|
-
const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) =>
|
|
7010
|
+
const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path14.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
|
|
5605
7011
|
const insights = [];
|
|
5606
7012
|
for (const absolutePath of files) {
|
|
5607
|
-
const relativePath = toPosix(
|
|
5608
|
-
const content = await
|
|
5609
|
-
const parsed =
|
|
5610
|
-
const stats = await
|
|
5611
|
-
const title = typeof parsed.data.title === "string" ? parsed.data.title :
|
|
7013
|
+
const relativePath = toPosix(path14.relative(wikiDir, absolutePath));
|
|
7014
|
+
const content = await fs11.readFile(absolutePath, "utf8");
|
|
7015
|
+
const parsed = matter5(content);
|
|
7016
|
+
const stats = await fs11.stat(absolutePath);
|
|
7017
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : path14.basename(absolutePath, ".md");
|
|
5612
7018
|
const sourceIds = normalizeStringArray(parsed.data.source_ids);
|
|
5613
7019
|
const projectIds = normalizeProjectIds(parsed.data.project_ids);
|
|
5614
7020
|
const nodeIds = normalizeStringArray(parsed.data.node_ids);
|
|
@@ -5670,28 +7076,28 @@ function relatedOutputsForPage(targetPage, outputPages) {
|
|
|
5670
7076
|
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);
|
|
5671
7077
|
}
|
|
5672
7078
|
async function resolveUniqueOutputSlug(wikiDir, baseSlug) {
|
|
5673
|
-
const outputsDir =
|
|
7079
|
+
const outputsDir = path15.join(wikiDir, "outputs");
|
|
5674
7080
|
const root = baseSlug || "output";
|
|
5675
7081
|
let candidate = root;
|
|
5676
7082
|
let counter = 2;
|
|
5677
|
-
while (await fileExists(
|
|
7083
|
+
while (await fileExists(path15.join(outputsDir, `${candidate}.md`))) {
|
|
5678
7084
|
candidate = `${root}-${counter}`;
|
|
5679
7085
|
counter++;
|
|
5680
7086
|
}
|
|
5681
7087
|
return candidate;
|
|
5682
7088
|
}
|
|
5683
7089
|
async function loadSavedOutputPages(wikiDir) {
|
|
5684
|
-
const outputsDir =
|
|
5685
|
-
const entries = await
|
|
7090
|
+
const outputsDir = path15.join(wikiDir, "outputs");
|
|
7091
|
+
const entries = await fs12.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
|
|
5686
7092
|
const outputs = [];
|
|
5687
7093
|
for (const entry of entries) {
|
|
5688
7094
|
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md") {
|
|
5689
7095
|
continue;
|
|
5690
7096
|
}
|
|
5691
|
-
const relativePath =
|
|
5692
|
-
const absolutePath =
|
|
5693
|
-
const content = await
|
|
5694
|
-
const parsed =
|
|
7097
|
+
const relativePath = path15.posix.join("outputs", entry.name);
|
|
7098
|
+
const absolutePath = path15.join(outputsDir, entry.name);
|
|
7099
|
+
const content = await fs12.readFile(absolutePath, "utf8");
|
|
7100
|
+
const parsed = matter6(content);
|
|
5695
7101
|
const slug = entry.name.replace(/\.md$/, "");
|
|
5696
7102
|
const title = typeof parsed.data.title === "string" ? parsed.data.title : slug;
|
|
5697
7103
|
const pageId = typeof parsed.data.page_id === "string" ? parsed.data.page_id : `output:${slug}`;
|
|
@@ -5703,7 +7109,7 @@ async function loadSavedOutputPages(wikiDir) {
|
|
|
5703
7109
|
const relatedSourceIds = normalizeStringArray(parsed.data.related_source_ids);
|
|
5704
7110
|
const backlinks = normalizeStringArray(parsed.data.backlinks);
|
|
5705
7111
|
const compiledFrom = normalizeStringArray(parsed.data.compiled_from);
|
|
5706
|
-
const stats = await
|
|
7112
|
+
const stats = await fs12.stat(absolutePath);
|
|
5707
7113
|
const createdAt = typeof parsed.data.created_at === "string" ? parsed.data.created_at : stats.birthtimeMs > 0 ? stats.birthtime.toISOString() : stats.mtime.toISOString();
|
|
5708
7114
|
const updatedAt = typeof parsed.data.updated_at === "string" ? parsed.data.updated_at : stats.mtime.toISOString();
|
|
5709
7115
|
outputs.push({
|
|
@@ -5741,9 +7147,9 @@ async function loadSavedOutputPages(wikiDir) {
|
|
|
5741
7147
|
}
|
|
5742
7148
|
|
|
5743
7149
|
// src/search.ts
|
|
5744
|
-
import
|
|
5745
|
-
import
|
|
5746
|
-
import
|
|
7150
|
+
import fs13 from "fs/promises";
|
|
7151
|
+
import path16 from "path";
|
|
7152
|
+
import matter7 from "gray-matter";
|
|
5747
7153
|
function getDatabaseSync() {
|
|
5748
7154
|
const builtin = process.getBuiltinModule?.("node:sqlite");
|
|
5749
7155
|
if (!builtin?.DatabaseSync) {
|
|
@@ -5762,7 +7168,7 @@ function normalizeStatus(value) {
|
|
|
5762
7168
|
return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : void 0;
|
|
5763
7169
|
}
|
|
5764
7170
|
async function rebuildSearchIndex(dbPath, pages, wikiDir) {
|
|
5765
|
-
await ensureDir(
|
|
7171
|
+
await ensureDir(path16.dirname(dbPath));
|
|
5766
7172
|
const DatabaseSync = getDatabaseSync();
|
|
5767
7173
|
const db = new DatabaseSync(dbPath);
|
|
5768
7174
|
db.exec("PRAGMA journal_mode = WAL;");
|
|
@@ -5792,9 +7198,9 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
|
|
|
5792
7198
|
"INSERT INTO pages (id, path, title, body, kind, status, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
|
5793
7199
|
);
|
|
5794
7200
|
for (const page of pages) {
|
|
5795
|
-
const absolutePath =
|
|
5796
|
-
const content = await
|
|
5797
|
-
const parsed =
|
|
7201
|
+
const absolutePath = path16.join(wikiDir, page.path);
|
|
7202
|
+
const content = await fs13.readFile(absolutePath, "utf8");
|
|
7203
|
+
const parsed = matter7(content);
|
|
5798
7204
|
insertPage.run(
|
|
5799
7205
|
page.id,
|
|
5800
7206
|
page.path,
|
|
@@ -5896,7 +7302,7 @@ function outputFormatInstruction(format) {
|
|
|
5896
7302
|
}
|
|
5897
7303
|
}
|
|
5898
7304
|
function outputAssetPath(slug, fileName) {
|
|
5899
|
-
return toPosix(
|
|
7305
|
+
return toPosix(path17.join("outputs", "assets", slug, fileName));
|
|
5900
7306
|
}
|
|
5901
7307
|
function outputAssetId(slug, role) {
|
|
5902
7308
|
return `output:${slug}:asset:${role}`;
|
|
@@ -6036,7 +7442,7 @@ async function resolveImageGenerationProvider(rootDir) {
|
|
|
6036
7442
|
if (!providerConfig) {
|
|
6037
7443
|
throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
|
|
6038
7444
|
}
|
|
6039
|
-
const { createProvider: createProvider2 } = await import("./registry-
|
|
7445
|
+
const { createProvider: createProvider2 } = await import("./registry-X5PMZTZY.js");
|
|
6040
7446
|
return createProvider2(preferredProviderId, providerConfig, rootDir);
|
|
6041
7447
|
}
|
|
6042
7448
|
async function generateOutputArtifacts(rootDir, input) {
|
|
@@ -6234,7 +7640,7 @@ async function generateOutputArtifacts(rootDir, input) {
|
|
|
6234
7640
|
};
|
|
6235
7641
|
}
|
|
6236
7642
|
function normalizeProjectRoot(root) {
|
|
6237
|
-
const normalized = toPosix(
|
|
7643
|
+
const normalized = toPosix(path17.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
6238
7644
|
return normalized;
|
|
6239
7645
|
}
|
|
6240
7646
|
function projectEntries(config) {
|
|
@@ -6260,10 +7666,10 @@ function manifestPathForProject(rootDir, manifest) {
|
|
|
6260
7666
|
if (!rawPath) {
|
|
6261
7667
|
return toPosix(manifest.storedPath);
|
|
6262
7668
|
}
|
|
6263
|
-
if (!
|
|
7669
|
+
if (!path17.isAbsolute(rawPath)) {
|
|
6264
7670
|
return normalizeProjectRoot(rawPath);
|
|
6265
7671
|
}
|
|
6266
|
-
const relative = toPosix(
|
|
7672
|
+
const relative = toPosix(path17.relative(rootDir, rawPath));
|
|
6267
7673
|
return relative.startsWith("..") ? toPosix(rawPath) : normalizeProjectRoot(relative);
|
|
6268
7674
|
}
|
|
6269
7675
|
function prefixMatches(value, prefix) {
|
|
@@ -6291,9 +7697,9 @@ function scopedProjectIdsFromSources(sourceIds, sourceProjects) {
|
|
|
6291
7697
|
const projectIds = uniqueStrings2(sourceIds.map((sourceId) => sourceProjects[sourceId] ?? "").filter(Boolean));
|
|
6292
7698
|
return projectIds.length === 1 ? projectIds : [];
|
|
6293
7699
|
}
|
|
6294
|
-
function schemaProjectIdsFromPages(pageIds,
|
|
7700
|
+
function schemaProjectIdsFromPages(pageIds, pageMap2) {
|
|
6295
7701
|
return uniqueStrings2(
|
|
6296
|
-
pageIds.flatMap((pageId) =>
|
|
7702
|
+
pageIds.flatMap((pageId) => pageMap2.get(pageId)?.projectIds ?? []).filter(Boolean).sort((left, right) => left.localeCompare(right))
|
|
6297
7703
|
);
|
|
6298
7704
|
}
|
|
6299
7705
|
function categoryTagsForSchema(schema, texts) {
|
|
@@ -6317,12 +7723,12 @@ function previousProjectSchemaHash(previousState, projectId) {
|
|
|
6317
7723
|
}
|
|
6318
7724
|
return previousState?.effectiveSchemaHashes?.projects?.[projectId] ?? previousState?.projectSchemaHashes?.[projectId] ?? previousGlobalSchemaHash(previousState);
|
|
6319
7725
|
}
|
|
6320
|
-
function expectedSchemaHashForPage(page, schemas,
|
|
7726
|
+
function expectedSchemaHashForPage(page, schemas, pageMap2, sourceProjects) {
|
|
6321
7727
|
if (page.kind === "source" || page.kind === "module" || page.kind === "concept" || page.kind === "entity") {
|
|
6322
7728
|
return effectiveHashForProject(schemas, scopedProjectIdsFromSources(page.sourceIds, sourceProjects)[0] ?? null);
|
|
6323
7729
|
}
|
|
6324
7730
|
if (page.kind === "output") {
|
|
6325
|
-
const projectIds = schemaProjectIdsFromPages(page.relatedPageIds,
|
|
7731
|
+
const projectIds = schemaProjectIdsFromPages(page.relatedPageIds, pageMap2);
|
|
6326
7732
|
if (projectIds.length) {
|
|
6327
7733
|
return composeVaultSchema(
|
|
6328
7734
|
schemas.root,
|
|
@@ -6437,7 +7843,7 @@ function pageHashes(pages) {
|
|
|
6437
7843
|
return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
|
|
6438
7844
|
}
|
|
6439
7845
|
async function buildManagedGraphPage(absolutePath, defaults, build) {
|
|
6440
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
7846
|
+
const existingContent = await fileExists(absolutePath) ? await fs14.readFile(absolutePath, "utf8") : null;
|
|
6441
7847
|
let existing = await loadExistingManagedPageState(absolutePath, {
|
|
6442
7848
|
status: defaults.status ?? "active",
|
|
6443
7849
|
managedBy: defaults.managedBy
|
|
@@ -6475,7 +7881,7 @@ async function buildManagedGraphPage(absolutePath, defaults, build) {
|
|
|
6475
7881
|
return built;
|
|
6476
7882
|
}
|
|
6477
7883
|
async function buildManagedContent(absolutePath, defaults, build) {
|
|
6478
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
7884
|
+
const existingContent = await fileExists(absolutePath) ? await fs14.readFile(absolutePath, "utf8") : null;
|
|
6479
7885
|
let existing = await loadExistingManagedPageState(absolutePath, {
|
|
6480
7886
|
status: defaults.status ?? "active",
|
|
6481
7887
|
managedBy: defaults.managedBy
|
|
@@ -6916,9 +8322,10 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
|
|
|
6916
8322
|
};
|
|
6917
8323
|
}
|
|
6918
8324
|
async function buildGraphOrientationPages(graph, paths, schemaHash) {
|
|
8325
|
+
const benchmark = await readJsonFile(paths.benchmarkPath);
|
|
6919
8326
|
const communityRecords = [];
|
|
6920
8327
|
for (const community of graph.communities ?? []) {
|
|
6921
|
-
const absolutePath =
|
|
8328
|
+
const absolutePath = path17.join(paths.wikiDir, "graph", "communities", `${community.id.replace(/^community:/, "")}.md`);
|
|
6922
8329
|
communityRecords.push(
|
|
6923
8330
|
await buildManagedGraphPage(
|
|
6924
8331
|
absolutePath,
|
|
@@ -6938,7 +8345,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash) {
|
|
|
6938
8345
|
)
|
|
6939
8346
|
);
|
|
6940
8347
|
}
|
|
6941
|
-
const reportAbsolutePath =
|
|
8348
|
+
const reportAbsolutePath = path17.join(paths.wikiDir, "graph", "report.md");
|
|
6942
8349
|
const reportRecord = await buildManagedGraphPage(
|
|
6943
8350
|
reportAbsolutePath,
|
|
6944
8351
|
{
|
|
@@ -6950,13 +8357,14 @@ async function buildGraphOrientationPages(graph, paths, schemaHash) {
|
|
|
6950
8357
|
graph,
|
|
6951
8358
|
schemaHash,
|
|
6952
8359
|
metadata,
|
|
6953
|
-
communityPages: communityRecords.map((record) => record.page)
|
|
8360
|
+
communityPages: communityRecords.map((record) => record.page),
|
|
8361
|
+
benchmark
|
|
6954
8362
|
})
|
|
6955
8363
|
);
|
|
6956
8364
|
return [reportRecord, ...communityRecords];
|
|
6957
8365
|
}
|
|
6958
8366
|
async function writePage(wikiDir, relativePath, content, changedPages) {
|
|
6959
|
-
const absolutePath =
|
|
8367
|
+
const absolutePath = path17.resolve(wikiDir, relativePath);
|
|
6960
8368
|
const changed = await writeFileIfChanged(absolutePath, content);
|
|
6961
8369
|
if (changed) {
|
|
6962
8370
|
changedPages.push(relativePath);
|
|
@@ -7018,34 +8426,29 @@ async function requiredCompileArtifactsExist(paths) {
|
|
|
7018
8426
|
paths.graphPath,
|
|
7019
8427
|
paths.codeIndexPath,
|
|
7020
8428
|
paths.searchDbPath,
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
8429
|
+
path17.join(paths.wikiDir, "index.md"),
|
|
8430
|
+
path17.join(paths.wikiDir, "sources", "index.md"),
|
|
8431
|
+
path17.join(paths.wikiDir, "code", "index.md"),
|
|
8432
|
+
path17.join(paths.wikiDir, "concepts", "index.md"),
|
|
8433
|
+
path17.join(paths.wikiDir, "entities", "index.md"),
|
|
8434
|
+
path17.join(paths.wikiDir, "outputs", "index.md"),
|
|
8435
|
+
path17.join(paths.wikiDir, "projects", "index.md"),
|
|
8436
|
+
path17.join(paths.wikiDir, "candidates", "index.md")
|
|
7029
8437
|
];
|
|
7030
8438
|
const checks = await Promise.all(requiredPaths.map((filePath) => fileExists(filePath)));
|
|
7031
8439
|
return checks.every(Boolean);
|
|
7032
8440
|
}
|
|
7033
|
-
async function
|
|
7034
|
-
|
|
7035
|
-
manifests.map(async (manifest) => {
|
|
7036
|
-
const cached = await readJsonFile(path14.join(paths.analysesDir, `${manifest.sourceId}.json`));
|
|
7037
|
-
if (!cached) {
|
|
7038
|
-
throw new Error(`Missing cached analysis for ${manifest.sourceId}. Run \`swarmvault compile\` first.`);
|
|
7039
|
-
}
|
|
7040
|
-
return cached;
|
|
7041
|
-
})
|
|
8441
|
+
async function loadAvailableCachedAnalyses(paths, manifests) {
|
|
8442
|
+
const analyses = await Promise.all(
|
|
8443
|
+
manifests.map(async (manifest) => readJsonFile(path17.join(paths.analysesDir, `${manifest.sourceId}.json`)))
|
|
7042
8444
|
);
|
|
8445
|
+
return analyses.filter((analysis) => Boolean(analysis));
|
|
7043
8446
|
}
|
|
7044
8447
|
function approvalManifestPath(paths, approvalId) {
|
|
7045
|
-
return
|
|
8448
|
+
return path17.join(paths.approvalsDir, approvalId, "manifest.json");
|
|
7046
8449
|
}
|
|
7047
8450
|
function approvalGraphPath(paths, approvalId) {
|
|
7048
|
-
return
|
|
8451
|
+
return path17.join(paths.approvalsDir, approvalId, "state", "graph.json");
|
|
7049
8452
|
}
|
|
7050
8453
|
async function readApprovalManifest(paths, approvalId) {
|
|
7051
8454
|
const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
|
|
@@ -7055,7 +8458,7 @@ async function readApprovalManifest(paths, approvalId) {
|
|
|
7055
8458
|
return manifest;
|
|
7056
8459
|
}
|
|
7057
8460
|
async function writeApprovalManifest(paths, manifest) {
|
|
7058
|
-
await
|
|
8461
|
+
await fs14.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
|
|
7059
8462
|
`, "utf8");
|
|
7060
8463
|
}
|
|
7061
8464
|
async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
|
|
@@ -7070,7 +8473,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
7070
8473
|
continue;
|
|
7071
8474
|
}
|
|
7072
8475
|
const previousPage = previousPagesById.get(nextPage.id);
|
|
7073
|
-
const currentExists = await fileExists(
|
|
8476
|
+
const currentExists = await fileExists(path17.join(paths.wikiDir, file.relativePath));
|
|
7074
8477
|
if (previousPage && previousPage.path !== nextPage.path) {
|
|
7075
8478
|
entries.push({
|
|
7076
8479
|
pageId: nextPage.id,
|
|
@@ -7103,7 +8506,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
7103
8506
|
const previousPage = previousPagesByPath.get(deletedPath);
|
|
7104
8507
|
entries.push({
|
|
7105
8508
|
pageId: previousPage?.id ?? `page:${slugify(deletedPath)}`,
|
|
7106
|
-
title: previousPage?.title ??
|
|
8509
|
+
title: previousPage?.title ?? path17.basename(deletedPath, ".md"),
|
|
7107
8510
|
kind: previousPage?.kind ?? "index",
|
|
7108
8511
|
changeType: "delete",
|
|
7109
8512
|
status: "pending",
|
|
@@ -7115,16 +8518,16 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
7115
8518
|
}
|
|
7116
8519
|
async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGraph, graph) {
|
|
7117
8520
|
const approvalId = `compile-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
7118
|
-
const approvalDir =
|
|
8521
|
+
const approvalDir = path17.join(paths.approvalsDir, approvalId);
|
|
7119
8522
|
await ensureDir(approvalDir);
|
|
7120
|
-
await ensureDir(
|
|
7121
|
-
await ensureDir(
|
|
8523
|
+
await ensureDir(path17.join(approvalDir, "wiki"));
|
|
8524
|
+
await ensureDir(path17.join(approvalDir, "state"));
|
|
7122
8525
|
for (const file of changedFiles) {
|
|
7123
|
-
const targetPath =
|
|
7124
|
-
await ensureDir(
|
|
7125
|
-
await
|
|
8526
|
+
const targetPath = path17.join(approvalDir, "wiki", file.relativePath);
|
|
8527
|
+
await ensureDir(path17.dirname(targetPath));
|
|
8528
|
+
await fs14.writeFile(targetPath, file.content, "utf8");
|
|
7126
8529
|
}
|
|
7127
|
-
await
|
|
8530
|
+
await fs14.writeFile(path17.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
|
|
7128
8531
|
await writeApprovalManifest(paths, {
|
|
7129
8532
|
approvalId,
|
|
7130
8533
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7184,7 +8587,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
7184
8587
|
confidence: 1
|
|
7185
8588
|
});
|
|
7186
8589
|
const sourceRecord = await buildManagedGraphPage(
|
|
7187
|
-
|
|
8590
|
+
path17.join(paths.wikiDir, preview.path),
|
|
7188
8591
|
{
|
|
7189
8592
|
managedBy: "system",
|
|
7190
8593
|
confidence: 1,
|
|
@@ -7229,7 +8632,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
7229
8632
|
);
|
|
7230
8633
|
records.push(
|
|
7231
8634
|
await buildManagedGraphPage(
|
|
7232
|
-
|
|
8635
|
+
path17.join(paths.wikiDir, modulePreview.path),
|
|
7233
8636
|
{
|
|
7234
8637
|
managedBy: "system",
|
|
7235
8638
|
confidence: 1,
|
|
@@ -7262,8 +8665,8 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
7262
8665
|
const promoted = previousEntry?.status === "active" || promoteCandidates && shouldPromoteCandidate(previousEntry, sourceIds);
|
|
7263
8666
|
const relativePath = promoted ? activeAggregatePath(itemKind, slug) : candidatePagePathFor(itemKind, slug);
|
|
7264
8667
|
const fallbackPaths = [
|
|
7265
|
-
|
|
7266
|
-
|
|
8668
|
+
path17.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
|
|
8669
|
+
path17.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
|
|
7267
8670
|
];
|
|
7268
8671
|
const confidence = nodeConfidence(aggregate.sourceAnalyses.length);
|
|
7269
8672
|
const preview = emptyGraphPage({
|
|
@@ -7280,7 +8683,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
7280
8683
|
status: promoted ? "active" : "candidate"
|
|
7281
8684
|
});
|
|
7282
8685
|
const pageRecord = await buildManagedGraphPage(
|
|
7283
|
-
|
|
8686
|
+
path17.join(paths.wikiDir, relativePath),
|
|
7284
8687
|
{
|
|
7285
8688
|
status: promoted ? "active" : "candidate",
|
|
7286
8689
|
managedBy: "system",
|
|
@@ -7361,7 +8764,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
7361
8764
|
confidence: 1
|
|
7362
8765
|
}),
|
|
7363
8766
|
content: await buildManagedContent(
|
|
7364
|
-
|
|
8767
|
+
path17.join(paths.wikiDir, "projects", "index.md"),
|
|
7365
8768
|
{
|
|
7366
8769
|
managedBy: "system",
|
|
7367
8770
|
compiledFrom: indexCompiledFrom(projectIndexRefs)
|
|
@@ -7385,7 +8788,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
7385
8788
|
records.push({
|
|
7386
8789
|
page: projectIndexRef,
|
|
7387
8790
|
content: await buildManagedContent(
|
|
7388
|
-
|
|
8791
|
+
path17.join(paths.wikiDir, projectIndexRef.path),
|
|
7389
8792
|
{
|
|
7390
8793
|
managedBy: "system",
|
|
7391
8794
|
compiledFrom: indexCompiledFrom(Object.values(sections).flat())
|
|
@@ -7413,7 +8816,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
7413
8816
|
confidence: 1
|
|
7414
8817
|
}),
|
|
7415
8818
|
content: await buildManagedContent(
|
|
7416
|
-
|
|
8819
|
+
path17.join(paths.wikiDir, "index.md"),
|
|
7417
8820
|
{
|
|
7418
8821
|
managedBy: "system",
|
|
7419
8822
|
compiledFrom: indexCompiledFrom(allPages)
|
|
@@ -7444,7 +8847,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
7444
8847
|
confidence: 1
|
|
7445
8848
|
}),
|
|
7446
8849
|
content: await buildManagedContent(
|
|
7447
|
-
|
|
8850
|
+
path17.join(paths.wikiDir, relativePath),
|
|
7448
8851
|
{
|
|
7449
8852
|
managedBy: "system",
|
|
7450
8853
|
compiledFrom: indexCompiledFrom(pages)
|
|
@@ -7455,12 +8858,12 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
7455
8858
|
}
|
|
7456
8859
|
const nextPagePaths = new Set(records.map((record) => record.page.path));
|
|
7457
8860
|
const obsoleteGraphPaths = (previousGraph?.pages ?? []).filter((page) => page.kind !== "output" && page.kind !== "insight").map((page) => page.path).filter((relativePath) => !nextPagePaths.has(relativePath));
|
|
7458
|
-
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(
|
|
8861
|
+
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path17.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
|
|
7459
8862
|
const obsoletePaths = uniqueStrings2([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
|
|
7460
8863
|
const changedFiles = [];
|
|
7461
8864
|
for (const record of records) {
|
|
7462
|
-
const absolutePath =
|
|
7463
|
-
const current = await fileExists(absolutePath) ? await
|
|
8865
|
+
const absolutePath = path17.join(paths.wikiDir, record.page.path);
|
|
8866
|
+
const current = await fileExists(absolutePath) ? await fs14.readFile(absolutePath, "utf8") : null;
|
|
7464
8867
|
if (current !== record.content) {
|
|
7465
8868
|
changedPages.push(record.page.path);
|
|
7466
8869
|
changedFiles.push({ relativePath: record.page.path, content: record.content });
|
|
@@ -7485,7 +8888,7 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
7485
8888
|
await writePage(paths.wikiDir, record.page.path, record.content, writeChanges);
|
|
7486
8889
|
}
|
|
7487
8890
|
for (const relativePath of obsoletePaths) {
|
|
7488
|
-
await
|
|
8891
|
+
await fs14.rm(path17.join(paths.wikiDir, relativePath), { force: true });
|
|
7489
8892
|
}
|
|
7490
8893
|
await writeJsonFile(paths.graphPath, graph);
|
|
7491
8894
|
await writeJsonFile(paths.codeIndexPath, input.codeIndex);
|
|
@@ -7556,17 +8959,17 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
7556
8959
|
})
|
|
7557
8960
|
);
|
|
7558
8961
|
await Promise.all([
|
|
7559
|
-
ensureDir(
|
|
7560
|
-
ensureDir(
|
|
7561
|
-
ensureDir(
|
|
7562
|
-
ensureDir(
|
|
7563
|
-
ensureDir(
|
|
7564
|
-
ensureDir(
|
|
7565
|
-
ensureDir(
|
|
7566
|
-
ensureDir(
|
|
7567
|
-
ensureDir(
|
|
8962
|
+
ensureDir(path17.join(paths.wikiDir, "sources")),
|
|
8963
|
+
ensureDir(path17.join(paths.wikiDir, "code")),
|
|
8964
|
+
ensureDir(path17.join(paths.wikiDir, "concepts")),
|
|
8965
|
+
ensureDir(path17.join(paths.wikiDir, "entities")),
|
|
8966
|
+
ensureDir(path17.join(paths.wikiDir, "outputs")),
|
|
8967
|
+
ensureDir(path17.join(paths.wikiDir, "graph")),
|
|
8968
|
+
ensureDir(path17.join(paths.wikiDir, "graph", "communities")),
|
|
8969
|
+
ensureDir(path17.join(paths.wikiDir, "projects")),
|
|
8970
|
+
ensureDir(path17.join(paths.wikiDir, "candidates"))
|
|
7568
8971
|
]);
|
|
7569
|
-
const projectsIndexPath =
|
|
8972
|
+
const projectsIndexPath = path17.join(paths.wikiDir, "projects", "index.md");
|
|
7570
8973
|
await writeFileIfChanged(
|
|
7571
8974
|
projectsIndexPath,
|
|
7572
8975
|
await buildManagedContent(
|
|
@@ -7587,7 +8990,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
7587
8990
|
outputs: pages.filter((page) => page.kind === "output" && page.projectIds.includes(project.id)),
|
|
7588
8991
|
candidates: pages.filter((page) => page.status === "candidate" && page.projectIds.includes(project.id))
|
|
7589
8992
|
};
|
|
7590
|
-
const absolutePath =
|
|
8993
|
+
const absolutePath = path17.join(paths.wikiDir, "projects", project.id, "index.md");
|
|
7591
8994
|
await writeFileIfChanged(
|
|
7592
8995
|
absolutePath,
|
|
7593
8996
|
await buildManagedContent(
|
|
@@ -7605,7 +9008,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
7605
9008
|
)
|
|
7606
9009
|
);
|
|
7607
9010
|
}
|
|
7608
|
-
const rootIndexPath =
|
|
9011
|
+
const rootIndexPath = path17.join(paths.wikiDir, "index.md");
|
|
7609
9012
|
await writeFileIfChanged(
|
|
7610
9013
|
rootIndexPath,
|
|
7611
9014
|
await buildManagedContent(
|
|
@@ -7626,7 +9029,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
7626
9029
|
["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
|
|
7627
9030
|
["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
|
|
7628
9031
|
]) {
|
|
7629
|
-
const absolutePath =
|
|
9032
|
+
const absolutePath = path17.join(paths.wikiDir, relativePath);
|
|
7630
9033
|
await writeFileIfChanged(
|
|
7631
9034
|
absolutePath,
|
|
7632
9035
|
await buildManagedContent(
|
|
@@ -7640,20 +9043,20 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
7640
9043
|
);
|
|
7641
9044
|
}
|
|
7642
9045
|
for (const record of graphOrientationRecords) {
|
|
7643
|
-
await writeFileIfChanged(
|
|
9046
|
+
await writeFileIfChanged(path17.join(paths.wikiDir, record.page.path), record.content);
|
|
7644
9047
|
}
|
|
7645
|
-
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(
|
|
9048
|
+
const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path17.relative(paths.wikiDir, absolutePath)));
|
|
7646
9049
|
const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
|
|
7647
9050
|
"projects/index.md",
|
|
7648
9051
|
...configuredProjects.map((project) => `projects/${project.id}/index.md`)
|
|
7649
9052
|
]);
|
|
7650
9053
|
await Promise.all(
|
|
7651
|
-
existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) =>
|
|
9054
|
+
existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs14.rm(path17.join(paths.wikiDir, relativePath), { force: true }))
|
|
7652
9055
|
);
|
|
7653
|
-
const existingGraphPages = (await listFilesRecursive(
|
|
9056
|
+
const existingGraphPages = (await listFilesRecursive(path17.join(paths.wikiDir, "graph").replace(/\/$/, "")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path17.relative(paths.wikiDir, absolutePath)));
|
|
7654
9057
|
const allowedGraphPages = /* @__PURE__ */ new Set(["graph/index.md", ...graphOrientationRecords.map((record) => record.page.path)]);
|
|
7655
9058
|
await Promise.all(
|
|
7656
|
-
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) =>
|
|
9059
|
+
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs14.rm(path17.join(paths.wikiDir, relativePath), { force: true }))
|
|
7657
9060
|
);
|
|
7658
9061
|
await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
|
|
7659
9062
|
}
|
|
@@ -7673,7 +9076,7 @@ async function prepareOutputPageSave(rootDir, input) {
|
|
|
7673
9076
|
confidence: 0.74
|
|
7674
9077
|
}
|
|
7675
9078
|
});
|
|
7676
|
-
const absolutePath =
|
|
9079
|
+
const absolutePath = path17.join(paths.wikiDir, output.page.path);
|
|
7677
9080
|
return {
|
|
7678
9081
|
page: output.page,
|
|
7679
9082
|
savedPath: absolutePath,
|
|
@@ -7685,15 +9088,15 @@ async function prepareOutputPageSave(rootDir, input) {
|
|
|
7685
9088
|
async function persistOutputPage(rootDir, input) {
|
|
7686
9089
|
const { paths } = await loadVaultConfig(rootDir);
|
|
7687
9090
|
const prepared = await prepareOutputPageSave(rootDir, input);
|
|
7688
|
-
await ensureDir(
|
|
7689
|
-
await
|
|
9091
|
+
await ensureDir(path17.dirname(prepared.savedPath));
|
|
9092
|
+
await fs14.writeFile(prepared.savedPath, prepared.content, "utf8");
|
|
7690
9093
|
for (const assetFile of prepared.assetFiles) {
|
|
7691
|
-
const assetPath =
|
|
7692
|
-
await ensureDir(
|
|
9094
|
+
const assetPath = path17.join(paths.wikiDir, assetFile.relativePath);
|
|
9095
|
+
await ensureDir(path17.dirname(assetPath));
|
|
7693
9096
|
if (typeof assetFile.content === "string") {
|
|
7694
|
-
await
|
|
9097
|
+
await fs14.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
|
|
7695
9098
|
} else {
|
|
7696
|
-
await
|
|
9099
|
+
await fs14.writeFile(assetPath, assetFile.content);
|
|
7697
9100
|
}
|
|
7698
9101
|
}
|
|
7699
9102
|
return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
|
|
@@ -7714,7 +9117,7 @@ async function prepareExploreHubSave(rootDir, input) {
|
|
|
7714
9117
|
confidence: 0.76
|
|
7715
9118
|
}
|
|
7716
9119
|
});
|
|
7717
|
-
const absolutePath =
|
|
9120
|
+
const absolutePath = path17.join(paths.wikiDir, hub.page.path);
|
|
7718
9121
|
return {
|
|
7719
9122
|
page: hub.page,
|
|
7720
9123
|
savedPath: absolutePath,
|
|
@@ -7726,15 +9129,15 @@ async function prepareExploreHubSave(rootDir, input) {
|
|
|
7726
9129
|
async function persistExploreHub(rootDir, input) {
|
|
7727
9130
|
const { paths } = await loadVaultConfig(rootDir);
|
|
7728
9131
|
const prepared = await prepareExploreHubSave(rootDir, input);
|
|
7729
|
-
await ensureDir(
|
|
7730
|
-
await
|
|
9132
|
+
await ensureDir(path17.dirname(prepared.savedPath));
|
|
9133
|
+
await fs14.writeFile(prepared.savedPath, prepared.content, "utf8");
|
|
7731
9134
|
for (const assetFile of prepared.assetFiles) {
|
|
7732
|
-
const assetPath =
|
|
7733
|
-
await ensureDir(
|
|
9135
|
+
const assetPath = path17.join(paths.wikiDir, assetFile.relativePath);
|
|
9136
|
+
await ensureDir(path17.dirname(assetPath));
|
|
7734
9137
|
if (typeof assetFile.content === "string") {
|
|
7735
|
-
await
|
|
9138
|
+
await fs14.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
|
|
7736
9139
|
} else {
|
|
7737
|
-
await
|
|
9140
|
+
await fs14.writeFile(assetPath, assetFile.content);
|
|
7738
9141
|
}
|
|
7739
9142
|
}
|
|
7740
9143
|
return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
|
|
@@ -7751,17 +9154,17 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
7751
9154
|
}))
|
|
7752
9155
|
]);
|
|
7753
9156
|
const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
7754
|
-
const approvalDir =
|
|
9157
|
+
const approvalDir = path17.join(paths.approvalsDir, approvalId);
|
|
7755
9158
|
await ensureDir(approvalDir);
|
|
7756
|
-
await ensureDir(
|
|
7757
|
-
await ensureDir(
|
|
9159
|
+
await ensureDir(path17.join(approvalDir, "wiki"));
|
|
9160
|
+
await ensureDir(path17.join(approvalDir, "state"));
|
|
7758
9161
|
for (const file of changedFiles) {
|
|
7759
|
-
const targetPath =
|
|
7760
|
-
await ensureDir(
|
|
9162
|
+
const targetPath = path17.join(approvalDir, "wiki", file.relativePath);
|
|
9163
|
+
await ensureDir(path17.dirname(targetPath));
|
|
7761
9164
|
if ("binary" in file && file.binary) {
|
|
7762
|
-
await
|
|
9165
|
+
await fs14.writeFile(targetPath, Buffer.from(file.content, "base64"));
|
|
7763
9166
|
} else {
|
|
7764
|
-
await
|
|
9167
|
+
await fs14.writeFile(targetPath, file.content, "utf8");
|
|
7765
9168
|
}
|
|
7766
9169
|
}
|
|
7767
9170
|
const nextPages = sortGraphPages([
|
|
@@ -7775,7 +9178,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
7775
9178
|
sources: previousGraph?.sources ?? [],
|
|
7776
9179
|
pages: nextPages
|
|
7777
9180
|
};
|
|
7778
|
-
await
|
|
9181
|
+
await fs14.writeFile(path17.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
|
|
7779
9182
|
await writeApprovalManifest(paths, {
|
|
7780
9183
|
approvalId,
|
|
7781
9184
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7797,17 +9200,17 @@ async function executeQuery(rootDir, question, format) {
|
|
|
7797
9200
|
await compileVault(rootDir, {});
|
|
7798
9201
|
}
|
|
7799
9202
|
const graph = await readJsonFile(paths.graphPath);
|
|
7800
|
-
const
|
|
9203
|
+
const pageMap2 = new Map((graph?.pages ?? []).map((page) => [page.id, page]));
|
|
7801
9204
|
const sourceProjects = Object.fromEntries(
|
|
7802
9205
|
(graph?.pages ?? []).filter((page) => page.kind === "source" && page.sourceIds.length).map((page) => [page.sourceIds[0], page.projectIds[0] ?? null])
|
|
7803
9206
|
);
|
|
7804
9207
|
const searchResults = searchPages(paths.searchDbPath, question, 5);
|
|
7805
9208
|
const excerpts = await Promise.all(
|
|
7806
9209
|
searchResults.map(async (result) => {
|
|
7807
|
-
const absolutePath =
|
|
9210
|
+
const absolutePath = path17.join(paths.wikiDir, result.path);
|
|
7808
9211
|
try {
|
|
7809
|
-
const content = await
|
|
7810
|
-
const parsed =
|
|
9212
|
+
const content = await fs14.readFile(absolutePath, "utf8");
|
|
9213
|
+
const parsed = matter8(content);
|
|
7811
9214
|
return `# ${result.title}
|
|
7812
9215
|
${truncate(normalizeWhitespace(parsed.content), 1200)}`;
|
|
7813
9216
|
} catch {
|
|
@@ -7821,14 +9224,14 @@ ${result.snippet}`;
|
|
|
7821
9224
|
(item) => item
|
|
7822
9225
|
);
|
|
7823
9226
|
const relatedNodeIds = uniqueBy(
|
|
7824
|
-
relatedPageIds.flatMap((pageId) =>
|
|
9227
|
+
relatedPageIds.flatMap((pageId) => pageMap2.get(pageId)?.nodeIds ?? []),
|
|
7825
9228
|
(item) => item
|
|
7826
9229
|
);
|
|
7827
9230
|
const relatedSourceIds = uniqueBy(
|
|
7828
|
-
relatedPageIds.flatMap((pageId) =>
|
|
9231
|
+
relatedPageIds.flatMap((pageId) => pageMap2.get(pageId)?.sourceIds ?? []),
|
|
7829
9232
|
(item) => item
|
|
7830
9233
|
);
|
|
7831
|
-
const schemaProjectIds = schemaProjectIdsFromPages(relatedPageIds,
|
|
9234
|
+
const schemaProjectIds = schemaProjectIdsFromPages(relatedPageIds, pageMap2);
|
|
7832
9235
|
const querySchema = composeVaultSchema(
|
|
7833
9236
|
schemas.root,
|
|
7834
9237
|
schemaProjectIds.map((projectId) => schemas.projects[projectId]).filter((schema) => Boolean(schema?.hash))
|
|
@@ -7915,7 +9318,7 @@ async function refreshVaultAfterOutputSave(rootDir) {
|
|
|
7915
9318
|
const schemas = await loadVaultSchemas(rootDir);
|
|
7916
9319
|
const manifests = await listManifests(rootDir);
|
|
7917
9320
|
const sourceProjects = resolveSourceProjects(rootDir, manifests, config);
|
|
7918
|
-
const cachedAnalyses = manifests.length ? await
|
|
9321
|
+
const cachedAnalyses = manifests.length ? await loadAvailableCachedAnalyses(paths, manifests) : [];
|
|
7919
9322
|
const codeIndex = await buildCodeIndex(rootDir, manifests, cachedAnalyses);
|
|
7920
9323
|
const analyses = cachedAnalyses.map((analysis) => {
|
|
7921
9324
|
const manifest = manifests.find((item) => item.sourceId === analysis.sourceId);
|
|
@@ -7988,7 +9391,7 @@ function sortGraphPages(pages) {
|
|
|
7988
9391
|
async function listApprovals(rootDir) {
|
|
7989
9392
|
const { paths } = await loadVaultConfig(rootDir);
|
|
7990
9393
|
const manifests = await Promise.all(
|
|
7991
|
-
(await
|
|
9394
|
+
(await fs14.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
7992
9395
|
try {
|
|
7993
9396
|
return await readApprovalManifest(paths, entry.name);
|
|
7994
9397
|
} catch {
|
|
@@ -8004,8 +9407,8 @@ async function readApproval(rootDir, approvalId) {
|
|
|
8004
9407
|
const details = await Promise.all(
|
|
8005
9408
|
manifest.entries.map(async (entry) => {
|
|
8006
9409
|
const currentPath = entry.previousPath ?? entry.nextPath;
|
|
8007
|
-
const currentContent = currentPath ? await
|
|
8008
|
-
const stagedContent = entry.nextPath ? await
|
|
9410
|
+
const currentContent = currentPath ? await fs14.readFile(path17.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
|
|
9411
|
+
const stagedContent = entry.nextPath ? await fs14.readFile(path17.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
|
|
8009
9412
|
return {
|
|
8010
9413
|
...entry,
|
|
8011
9414
|
currentContent,
|
|
@@ -8033,26 +9436,26 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
|
|
|
8033
9436
|
if (!entry.nextPath) {
|
|
8034
9437
|
throw new Error(`Approval entry ${entry.pageId} is missing a staged path.`);
|
|
8035
9438
|
}
|
|
8036
|
-
const stagedAbsolutePath =
|
|
8037
|
-
const stagedContent = await
|
|
8038
|
-
const targetAbsolutePath =
|
|
8039
|
-
await ensureDir(
|
|
8040
|
-
await
|
|
9439
|
+
const stagedAbsolutePath = path17.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
|
|
9440
|
+
const stagedContent = await fs14.readFile(stagedAbsolutePath, "utf8");
|
|
9441
|
+
const targetAbsolutePath = path17.join(paths.wikiDir, entry.nextPath);
|
|
9442
|
+
await ensureDir(path17.dirname(targetAbsolutePath));
|
|
9443
|
+
await fs14.writeFile(targetAbsolutePath, stagedContent, "utf8");
|
|
8041
9444
|
if (entry.changeType === "promote" && entry.previousPath) {
|
|
8042
|
-
await
|
|
9445
|
+
await fs14.rm(path17.join(paths.wikiDir, entry.previousPath), { force: true });
|
|
8043
9446
|
}
|
|
8044
9447
|
const nextPage = bundleGraph?.pages.find((page) => page.id === entry.pageId && page.path === entry.nextPath) ?? parseStoredPage(entry.nextPath, stagedContent);
|
|
8045
9448
|
if (nextPage.kind === "output" && nextPage.outputAssets?.length) {
|
|
8046
|
-
const outputAssetDir =
|
|
8047
|
-
await
|
|
9449
|
+
const outputAssetDir = path17.join(paths.wikiDir, "outputs", "assets", path17.basename(nextPage.path, ".md"));
|
|
9450
|
+
await fs14.rm(outputAssetDir, { recursive: true, force: true });
|
|
8048
9451
|
for (const asset of nextPage.outputAssets) {
|
|
8049
|
-
const stagedAssetPath =
|
|
9452
|
+
const stagedAssetPath = path17.join(paths.approvalsDir, approvalId, "wiki", asset.path);
|
|
8050
9453
|
if (!await fileExists(stagedAssetPath)) {
|
|
8051
9454
|
continue;
|
|
8052
9455
|
}
|
|
8053
|
-
const targetAssetPath =
|
|
8054
|
-
await ensureDir(
|
|
8055
|
-
await
|
|
9456
|
+
const targetAssetPath = path17.join(paths.wikiDir, asset.path);
|
|
9457
|
+
await ensureDir(path17.dirname(targetAssetPath));
|
|
9458
|
+
await fs14.copyFile(stagedAssetPath, targetAssetPath);
|
|
8056
9459
|
}
|
|
8057
9460
|
}
|
|
8058
9461
|
nextPages = nextPages.filter(
|
|
@@ -8063,10 +9466,10 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
|
|
|
8063
9466
|
} else {
|
|
8064
9467
|
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;
|
|
8065
9468
|
if (entry.previousPath) {
|
|
8066
|
-
await
|
|
9469
|
+
await fs14.rm(path17.join(paths.wikiDir, entry.previousPath), { force: true });
|
|
8067
9470
|
}
|
|
8068
9471
|
if (deletedPage?.kind === "output") {
|
|
8069
|
-
await
|
|
9472
|
+
await fs14.rm(path17.join(paths.wikiDir, "outputs", "assets", path17.basename(deletedPage.path, ".md")), {
|
|
8070
9473
|
recursive: true,
|
|
8071
9474
|
force: true
|
|
8072
9475
|
});
|
|
@@ -8156,10 +9559,10 @@ async function promoteCandidate(rootDir, target) {
|
|
|
8156
9559
|
const { paths } = await loadVaultConfig(rootDir);
|
|
8157
9560
|
const graph = await readJsonFile(paths.graphPath);
|
|
8158
9561
|
const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
|
|
8159
|
-
const raw = await
|
|
8160
|
-
const parsed =
|
|
9562
|
+
const raw = await fs14.readFile(path17.join(paths.wikiDir, candidate.path), "utf8");
|
|
9563
|
+
const parsed = matter8(raw);
|
|
8161
9564
|
const nextUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8162
|
-
const nextContent =
|
|
9565
|
+
const nextContent = matter8.stringify(parsed.content, {
|
|
8163
9566
|
...parsed.data,
|
|
8164
9567
|
status: "active",
|
|
8165
9568
|
updated_at: nextUpdatedAt,
|
|
@@ -8168,10 +9571,10 @@ async function promoteCandidate(rootDir, target) {
|
|
|
8168
9571
|
)
|
|
8169
9572
|
});
|
|
8170
9573
|
const nextPath = candidateActivePath(candidate);
|
|
8171
|
-
const nextAbsolutePath =
|
|
8172
|
-
await ensureDir(
|
|
8173
|
-
await
|
|
8174
|
-
await
|
|
9574
|
+
const nextAbsolutePath = path17.join(paths.wikiDir, nextPath);
|
|
9575
|
+
await ensureDir(path17.dirname(nextAbsolutePath));
|
|
9576
|
+
await fs14.writeFile(nextAbsolutePath, nextContent, "utf8");
|
|
9577
|
+
await fs14.rm(path17.join(paths.wikiDir, candidate.path), { force: true });
|
|
8175
9578
|
const nextPage = parseStoredPage(nextPath, nextContent, { createdAt: candidate.createdAt, updatedAt: nextUpdatedAt });
|
|
8176
9579
|
const nextPages = sortGraphPages(
|
|
8177
9580
|
(graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path).concat(nextPage)
|
|
@@ -8215,7 +9618,7 @@ async function archiveCandidate(rootDir, target) {
|
|
|
8215
9618
|
const { paths } = await loadVaultConfig(rootDir);
|
|
8216
9619
|
const graph = await readJsonFile(paths.graphPath);
|
|
8217
9620
|
const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
|
|
8218
|
-
await
|
|
9621
|
+
await fs14.rm(path17.join(paths.wikiDir, candidate.path), { force: true });
|
|
8219
9622
|
const nextPages = sortGraphPages((graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path));
|
|
8220
9623
|
const nextGraph = {
|
|
8221
9624
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8253,18 +9656,18 @@ async function archiveCandidate(rootDir, target) {
|
|
|
8253
9656
|
}
|
|
8254
9657
|
async function ensureObsidianWorkspace(rootDir) {
|
|
8255
9658
|
const { config } = await loadVaultConfig(rootDir);
|
|
8256
|
-
const obsidianDir =
|
|
9659
|
+
const obsidianDir = path17.join(rootDir, ".obsidian");
|
|
8257
9660
|
const projectIds = projectEntries(config).map((project) => project.id);
|
|
8258
9661
|
await ensureDir(obsidianDir);
|
|
8259
9662
|
await Promise.all([
|
|
8260
|
-
writeJsonFile(
|
|
9663
|
+
writeJsonFile(path17.join(obsidianDir, "app.json"), {
|
|
8261
9664
|
alwaysUpdateLinks: true,
|
|
8262
9665
|
newFileLocation: "folder",
|
|
8263
9666
|
newFileFolderPath: "wiki/insights",
|
|
8264
9667
|
useMarkdownLinks: false,
|
|
8265
9668
|
attachmentFolderPath: "raw/assets"
|
|
8266
9669
|
}),
|
|
8267
|
-
writeJsonFile(
|
|
9670
|
+
writeJsonFile(path17.join(obsidianDir, "core-plugins.json"), [
|
|
8268
9671
|
"file-explorer",
|
|
8269
9672
|
"global-search",
|
|
8270
9673
|
"switcher",
|
|
@@ -8274,7 +9677,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
8274
9677
|
"tag-pane",
|
|
8275
9678
|
"page-preview"
|
|
8276
9679
|
]),
|
|
8277
|
-
writeJsonFile(
|
|
9680
|
+
writeJsonFile(path17.join(obsidianDir, "graph.json"), {
|
|
8278
9681
|
"collapse-filter": false,
|
|
8279
9682
|
search: "",
|
|
8280
9683
|
showTags: true,
|
|
@@ -8286,7 +9689,7 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
8286
9689
|
})),
|
|
8287
9690
|
localJumps: false
|
|
8288
9691
|
}),
|
|
8289
|
-
writeJsonFile(
|
|
9692
|
+
writeJsonFile(path17.join(obsidianDir, "workspace.json"), {
|
|
8290
9693
|
active: "root",
|
|
8291
9694
|
lastOpenFiles: ["wiki/index.md", "wiki/projects/index.md", "wiki/candidates/index.md", "wiki/insights/index.md"],
|
|
8292
9695
|
left: {
|
|
@@ -8301,11 +9704,11 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
8301
9704
|
async function initVault(rootDir, options = {}) {
|
|
8302
9705
|
const { paths } = await initWorkspace(rootDir);
|
|
8303
9706
|
await installConfiguredAgents(rootDir);
|
|
8304
|
-
const insightsIndexPath =
|
|
9707
|
+
const insightsIndexPath = path17.join(paths.wikiDir, "insights", "index.md");
|
|
8305
9708
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8306
9709
|
await writeFileIfChanged(
|
|
8307
9710
|
insightsIndexPath,
|
|
8308
|
-
|
|
9711
|
+
matter8.stringify(
|
|
8309
9712
|
[
|
|
8310
9713
|
"# Insights",
|
|
8311
9714
|
"",
|
|
@@ -8337,8 +9740,8 @@ async function initVault(rootDir, options = {}) {
|
|
|
8337
9740
|
)
|
|
8338
9741
|
);
|
|
8339
9742
|
await writeFileIfChanged(
|
|
8340
|
-
|
|
8341
|
-
|
|
9743
|
+
path17.join(paths.wikiDir, "projects", "index.md"),
|
|
9744
|
+
matter8.stringify(["# Projects", "", "- Run `swarmvault compile` to build project rollups.", ""].join("\n"), {
|
|
8342
9745
|
page_id: "projects:index",
|
|
8343
9746
|
kind: "index",
|
|
8344
9747
|
title: "Projects",
|
|
@@ -8359,8 +9762,8 @@ async function initVault(rootDir, options = {}) {
|
|
|
8359
9762
|
})
|
|
8360
9763
|
);
|
|
8361
9764
|
await writeFileIfChanged(
|
|
8362
|
-
|
|
8363
|
-
|
|
9765
|
+
path17.join(paths.wikiDir, "candidates", "index.md"),
|
|
9766
|
+
matter8.stringify(["# Candidates", "", "- Run `swarmvault compile` to stage candidate pages.", ""].join("\n"), {
|
|
8364
9767
|
page_id: "candidates:index",
|
|
8365
9768
|
kind: "index",
|
|
8366
9769
|
title: "Candidates",
|
|
@@ -8476,7 +9879,7 @@ async function compileVault(rootDir, options = {}) {
|
|
|
8476
9879
|
),
|
|
8477
9880
|
Promise.all(
|
|
8478
9881
|
clean.map(async (manifest) => {
|
|
8479
|
-
const cached = await readJsonFile(
|
|
9882
|
+
const cached = await readJsonFile(path17.join(paths.analysesDir, `${manifest.sourceId}.json`));
|
|
8480
9883
|
if (cached) {
|
|
8481
9884
|
return cached;
|
|
8482
9885
|
}
|
|
@@ -8500,22 +9903,22 @@ async function compileVault(rootDir, options = {}) {
|
|
|
8500
9903
|
}
|
|
8501
9904
|
const enriched = enrichResolvedCodeImports(manifest, analysis, codeIndex);
|
|
8502
9905
|
if (analysisSignature(enriched) !== analysisSignature(analysis)) {
|
|
8503
|
-
await writeJsonFile(
|
|
9906
|
+
await writeJsonFile(path17.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
|
|
8504
9907
|
}
|
|
8505
9908
|
return enriched;
|
|
8506
9909
|
})
|
|
8507
9910
|
);
|
|
8508
9911
|
await Promise.all([
|
|
8509
|
-
ensureDir(
|
|
8510
|
-
ensureDir(
|
|
8511
|
-
ensureDir(
|
|
8512
|
-
ensureDir(
|
|
8513
|
-
ensureDir(
|
|
8514
|
-
ensureDir(
|
|
8515
|
-
ensureDir(
|
|
8516
|
-
ensureDir(
|
|
8517
|
-
ensureDir(
|
|
8518
|
-
ensureDir(
|
|
9912
|
+
ensureDir(path17.join(paths.wikiDir, "sources")),
|
|
9913
|
+
ensureDir(path17.join(paths.wikiDir, "code")),
|
|
9914
|
+
ensureDir(path17.join(paths.wikiDir, "concepts")),
|
|
9915
|
+
ensureDir(path17.join(paths.wikiDir, "entities")),
|
|
9916
|
+
ensureDir(path17.join(paths.wikiDir, "outputs")),
|
|
9917
|
+
ensureDir(path17.join(paths.wikiDir, "projects")),
|
|
9918
|
+
ensureDir(path17.join(paths.wikiDir, "insights")),
|
|
9919
|
+
ensureDir(path17.join(paths.wikiDir, "candidates")),
|
|
9920
|
+
ensureDir(path17.join(paths.wikiDir, "candidates", "concepts")),
|
|
9921
|
+
ensureDir(path17.join(paths.wikiDir, "candidates", "entities"))
|
|
8519
9922
|
]);
|
|
8520
9923
|
const sync = await syncVaultArtifacts(rootDir, {
|
|
8521
9924
|
schemas,
|
|
@@ -8657,7 +10060,7 @@ async function queryVault(rootDir, options) {
|
|
|
8657
10060
|
assetFiles: staged.assetFiles
|
|
8658
10061
|
}
|
|
8659
10062
|
]);
|
|
8660
|
-
stagedPath =
|
|
10063
|
+
stagedPath = path17.join(approval.approvalDir, "wiki", staged.page.path);
|
|
8661
10064
|
savedPageId = staged.page.id;
|
|
8662
10065
|
approvalId = approval.approvalId;
|
|
8663
10066
|
approvalDir = approval.approvalDir;
|
|
@@ -8913,9 +10316,9 @@ ${orchestrationNotes.join("\n")}
|
|
|
8913
10316
|
approvalId = approval.approvalId;
|
|
8914
10317
|
approvalDir = approval.approvalDir;
|
|
8915
10318
|
stepResults.forEach((result, index) => {
|
|
8916
|
-
result.stagedPath =
|
|
10319
|
+
result.stagedPath = path17.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
|
|
8917
10320
|
});
|
|
8918
|
-
stagedHubPath =
|
|
10321
|
+
stagedHubPath = path17.join(approval.approvalDir, "wiki", hubPage.path);
|
|
8919
10322
|
} else {
|
|
8920
10323
|
await refreshVaultAfterOutputSave(rootDir);
|
|
8921
10324
|
}
|
|
@@ -8982,6 +10385,50 @@ async function queryGraphVault(rootDir, question, options = {}) {
|
|
|
8982
10385
|
const searchResults = searchPages(paths.searchDbPath, question, { limit: Math.max(5, options.budget ?? 10) });
|
|
8983
10386
|
return queryGraph(graph, question, searchResults, options);
|
|
8984
10387
|
}
|
|
10388
|
+
async function benchmarkVault(rootDir, options = {}) {
|
|
10389
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
10390
|
+
const graph = await ensureCompiledGraph(rootDir);
|
|
10391
|
+
const manifests = await listManifests(rootDir);
|
|
10392
|
+
const pageContentsById = /* @__PURE__ */ new Map();
|
|
10393
|
+
let corpusWords = 0;
|
|
10394
|
+
for (const manifest of manifests) {
|
|
10395
|
+
const extractedText = await readExtractedText(rootDir, manifest);
|
|
10396
|
+
if (extractedText) {
|
|
10397
|
+
corpusWords += estimateCorpusWords([extractedText]);
|
|
10398
|
+
}
|
|
10399
|
+
}
|
|
10400
|
+
for (const page of graph.pages) {
|
|
10401
|
+
const absolutePath = path17.join(paths.wikiDir, page.path);
|
|
10402
|
+
if (!await fileExists(absolutePath)) {
|
|
10403
|
+
continue;
|
|
10404
|
+
}
|
|
10405
|
+
const parsed = matter8(await fs14.readFile(absolutePath, "utf8"));
|
|
10406
|
+
pageContentsById.set(page.id, parsed.content);
|
|
10407
|
+
}
|
|
10408
|
+
const questions = (options.questions ?? []).map((question) => normalizeWhitespace(question)).filter(Boolean);
|
|
10409
|
+
const sampleQuestions = questions.length ? questions : [...DEFAULT_BENCHMARK_QUESTIONS];
|
|
10410
|
+
const perQuestion = sampleQuestions.map((question) => {
|
|
10411
|
+
const searchResults = searchPages(paths.searchDbPath, question, { limit: 12 });
|
|
10412
|
+
const result = queryGraph(graph, question, searchResults, { budget: 12 });
|
|
10413
|
+
const metrics = benchmarkQueryTokens(graph, result, pageContentsById);
|
|
10414
|
+
return {
|
|
10415
|
+
question,
|
|
10416
|
+
queryTokens: metrics.queryTokens,
|
|
10417
|
+
reduction: metrics.reduction,
|
|
10418
|
+
visitedNodeIds: result.visitedNodeIds,
|
|
10419
|
+
pageIds: result.pageIds
|
|
10420
|
+
};
|
|
10421
|
+
});
|
|
10422
|
+
const artifact = buildBenchmarkArtifact({
|
|
10423
|
+
graph,
|
|
10424
|
+
corpusWords,
|
|
10425
|
+
questions: sampleQuestions,
|
|
10426
|
+
perQuestion
|
|
10427
|
+
});
|
|
10428
|
+
await writeJsonFile(paths.benchmarkPath, artifact);
|
|
10429
|
+
await refreshIndexesAndSearch(rootDir, graph.pages);
|
|
10430
|
+
return artifact;
|
|
10431
|
+
}
|
|
8985
10432
|
async function pathGraphVault(rootDir, from, to) {
|
|
8986
10433
|
const graph = await ensureCompiledGraph(rootDir);
|
|
8987
10434
|
return shortestGraphPath(graph, from, to);
|
|
@@ -9001,15 +10448,15 @@ async function listPages(rootDir) {
|
|
|
9001
10448
|
}
|
|
9002
10449
|
async function readPage(rootDir, relativePath) {
|
|
9003
10450
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9004
|
-
const absolutePath =
|
|
10451
|
+
const absolutePath = path17.resolve(paths.wikiDir, relativePath);
|
|
9005
10452
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
9006
10453
|
return null;
|
|
9007
10454
|
}
|
|
9008
|
-
const raw = await
|
|
9009
|
-
const parsed =
|
|
10455
|
+
const raw = await fs14.readFile(absolutePath, "utf8");
|
|
10456
|
+
const parsed = matter8(raw);
|
|
9010
10457
|
return {
|
|
9011
10458
|
path: relativePath,
|
|
9012
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
10459
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path17.basename(relativePath, path17.extname(relativePath)),
|
|
9013
10460
|
frontmatter: parsed.data,
|
|
9014
10461
|
content: parsed.content
|
|
9015
10462
|
};
|
|
@@ -9033,19 +10480,19 @@ async function getWorkspaceInfo(rootDir) {
|
|
|
9033
10480
|
}
|
|
9034
10481
|
function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sourceProjects) {
|
|
9035
10482
|
const manifestMap = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
|
|
9036
|
-
const
|
|
10483
|
+
const pageMap2 = new Map(graph.pages.map((page) => [page.id, page]));
|
|
9037
10484
|
return Promise.all(
|
|
9038
10485
|
graph.pages.map(async (page) => {
|
|
9039
10486
|
const findings = [];
|
|
9040
10487
|
if (page.kind === "insight") {
|
|
9041
10488
|
return findings;
|
|
9042
10489
|
}
|
|
9043
|
-
if (page.schemaHash !== expectedSchemaHashForPage(page, schemas,
|
|
10490
|
+
if (page.schemaHash !== expectedSchemaHashForPage(page, schemas, pageMap2, sourceProjects)) {
|
|
9044
10491
|
findings.push({
|
|
9045
10492
|
severity: "warning",
|
|
9046
10493
|
code: "stale_page",
|
|
9047
10494
|
message: `Page ${page.title} is stale because the vault schema changed.`,
|
|
9048
|
-
pagePath:
|
|
10495
|
+
pagePath: path17.join(paths.wikiDir, page.path),
|
|
9049
10496
|
relatedPageIds: [page.id]
|
|
9050
10497
|
});
|
|
9051
10498
|
}
|
|
@@ -9056,7 +10503,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
9056
10503
|
severity: "warning",
|
|
9057
10504
|
code: "stale_page",
|
|
9058
10505
|
message: `Page ${page.title} is stale because source ${sourceId} changed.`,
|
|
9059
|
-
pagePath:
|
|
10506
|
+
pagePath: path17.join(paths.wikiDir, page.path),
|
|
9060
10507
|
relatedSourceIds: [sourceId],
|
|
9061
10508
|
relatedPageIds: [page.id]
|
|
9062
10509
|
});
|
|
@@ -9067,13 +10514,13 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
|
|
|
9067
10514
|
severity: "info",
|
|
9068
10515
|
code: "orphan_page",
|
|
9069
10516
|
message: `Page ${page.title} has no backlinks.`,
|
|
9070
|
-
pagePath:
|
|
10517
|
+
pagePath: path17.join(paths.wikiDir, page.path),
|
|
9071
10518
|
relatedPageIds: [page.id]
|
|
9072
10519
|
});
|
|
9073
10520
|
}
|
|
9074
|
-
const absolutePath =
|
|
10521
|
+
const absolutePath = path17.join(paths.wikiDir, page.path);
|
|
9075
10522
|
if (await fileExists(absolutePath)) {
|
|
9076
|
-
const content = await
|
|
10523
|
+
const content = await fs14.readFile(absolutePath, "utf8");
|
|
9077
10524
|
if (content.includes("## Claims")) {
|
|
9078
10525
|
const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
|
|
9079
10526
|
if (uncited.length) {
|
|
@@ -9153,7 +10600,7 @@ async function bootstrapDemo(rootDir, input) {
|
|
|
9153
10600
|
}
|
|
9154
10601
|
|
|
9155
10602
|
// src/mcp.ts
|
|
9156
|
-
var SERVER_VERSION = "0.1.
|
|
10603
|
+
var SERVER_VERSION = "0.1.21";
|
|
9157
10604
|
async function createMcpServer(rootDir) {
|
|
9158
10605
|
const server = new McpServer({
|
|
9159
10606
|
name: "swarmvault",
|
|
@@ -9402,7 +10849,7 @@ async function createMcpServer(rootDir) {
|
|
|
9402
10849
|
},
|
|
9403
10850
|
async () => {
|
|
9404
10851
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9405
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
10852
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path18.relative(paths.sessionsDir, filePath))).sort();
|
|
9406
10853
|
return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
|
|
9407
10854
|
}
|
|
9408
10855
|
);
|
|
@@ -9435,8 +10882,8 @@ async function createMcpServer(rootDir) {
|
|
|
9435
10882
|
return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
|
|
9436
10883
|
}
|
|
9437
10884
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9438
|
-
const absolutePath =
|
|
9439
|
-
return asTextResource(`swarmvault://pages/${encodedPath}`, await
|
|
10885
|
+
const absolutePath = path18.resolve(paths.wikiDir, relativePath);
|
|
10886
|
+
return asTextResource(`swarmvault://pages/${encodedPath}`, await fs15.readFile(absolutePath, "utf8"));
|
|
9440
10887
|
}
|
|
9441
10888
|
);
|
|
9442
10889
|
server.registerResource(
|
|
@@ -9444,11 +10891,11 @@ async function createMcpServer(rootDir) {
|
|
|
9444
10891
|
new ResourceTemplate("swarmvault://sessions/{path}", {
|
|
9445
10892
|
list: async () => {
|
|
9446
10893
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9447
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
10894
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path18.relative(paths.sessionsDir, filePath))).sort();
|
|
9448
10895
|
return {
|
|
9449
10896
|
resources: files.map((relativePath) => ({
|
|
9450
10897
|
uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
|
|
9451
|
-
name:
|
|
10898
|
+
name: path18.basename(relativePath, ".md"),
|
|
9452
10899
|
title: relativePath,
|
|
9453
10900
|
description: "SwarmVault session artifact",
|
|
9454
10901
|
mimeType: "text/markdown"
|
|
@@ -9465,11 +10912,11 @@ async function createMcpServer(rootDir) {
|
|
|
9465
10912
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9466
10913
|
const encodedPath = typeof variables.path === "string" ? variables.path : "";
|
|
9467
10914
|
const relativePath = decodeURIComponent(encodedPath);
|
|
9468
|
-
const absolutePath =
|
|
10915
|
+
const absolutePath = path18.resolve(paths.sessionsDir, relativePath);
|
|
9469
10916
|
if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
|
|
9470
10917
|
return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
|
|
9471
10918
|
}
|
|
9472
|
-
return asTextResource(`swarmvault://sessions/${encodedPath}`, await
|
|
10919
|
+
return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs15.readFile(absolutePath, "utf8"));
|
|
9473
10920
|
}
|
|
9474
10921
|
);
|
|
9475
10922
|
return server;
|
|
@@ -9517,13 +10964,13 @@ function asTextResource(uri, text) {
|
|
|
9517
10964
|
}
|
|
9518
10965
|
|
|
9519
10966
|
// src/schedule.ts
|
|
9520
|
-
import
|
|
9521
|
-
import
|
|
10967
|
+
import fs16 from "fs/promises";
|
|
10968
|
+
import path19 from "path";
|
|
9522
10969
|
function scheduleStatePath(schedulesDir, jobId) {
|
|
9523
|
-
return
|
|
10970
|
+
return path19.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
9524
10971
|
}
|
|
9525
10972
|
function scheduleLockPath(schedulesDir, jobId) {
|
|
9526
|
-
return
|
|
10973
|
+
return path19.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
|
|
9527
10974
|
}
|
|
9528
10975
|
function parseEveryDuration(value) {
|
|
9529
10976
|
const match = value.trim().match(/^(\d+)(m|h|d)$/i);
|
|
@@ -9626,13 +11073,13 @@ async function acquireJobLease(rootDir, jobId) {
|
|
|
9626
11073
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9627
11074
|
const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
|
|
9628
11075
|
await ensureDir(paths.schedulesDir);
|
|
9629
|
-
const handle = await
|
|
11076
|
+
const handle = await fs16.open(leasePath, "wx");
|
|
9630
11077
|
await handle.writeFile(`${process.pid}
|
|
9631
11078
|
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
9632
11079
|
`);
|
|
9633
11080
|
await handle.close();
|
|
9634
11081
|
return async () => {
|
|
9635
|
-
await
|
|
11082
|
+
await fs16.rm(leasePath, { force: true });
|
|
9636
11083
|
};
|
|
9637
11084
|
}
|
|
9638
11085
|
async function listSchedules(rootDir) {
|
|
@@ -9780,24 +11227,415 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
|
|
|
9780
11227
|
|
|
9781
11228
|
// src/viewer.ts
|
|
9782
11229
|
import { execFile } from "child_process";
|
|
9783
|
-
import
|
|
11230
|
+
import fs17 from "fs/promises";
|
|
9784
11231
|
import http from "http";
|
|
9785
|
-
import
|
|
11232
|
+
import path21 from "path";
|
|
9786
11233
|
import { promisify } from "util";
|
|
9787
|
-
import
|
|
11234
|
+
import matter9 from "gray-matter";
|
|
9788
11235
|
import mime2 from "mime-types";
|
|
11236
|
+
|
|
11237
|
+
// src/watch.ts
|
|
11238
|
+
import path20 from "path";
|
|
11239
|
+
import process2 from "process";
|
|
11240
|
+
import chokidar from "chokidar";
|
|
11241
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
11242
|
+
var BACKOFF_THRESHOLD = 3;
|
|
11243
|
+
var CRITICAL_THRESHOLD = 10;
|
|
11244
|
+
var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", "coverage", ".venv", "vendor", "target"]);
|
|
11245
|
+
function withinRoot2(rootPath, targetPath) {
|
|
11246
|
+
const relative = path20.relative(rootPath, targetPath);
|
|
11247
|
+
return relative === "" || !relative.startsWith("..") && !path20.isAbsolute(relative);
|
|
11248
|
+
}
|
|
11249
|
+
function hasIgnoredRepoSegment(baseDir, targetPath) {
|
|
11250
|
+
const relativePath = path20.relative(baseDir, targetPath);
|
|
11251
|
+
if (!relativePath || relativePath.startsWith("..")) {
|
|
11252
|
+
return false;
|
|
11253
|
+
}
|
|
11254
|
+
return relativePath.split(path20.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
|
|
11255
|
+
}
|
|
11256
|
+
function workspaceIgnoreRoots(rootDir, paths) {
|
|
11257
|
+
return [
|
|
11258
|
+
paths.rawDir,
|
|
11259
|
+
paths.wikiDir,
|
|
11260
|
+
paths.stateDir,
|
|
11261
|
+
paths.agentDir,
|
|
11262
|
+
paths.inboxDir,
|
|
11263
|
+
path20.join(rootDir, ".claude"),
|
|
11264
|
+
path20.join(rootDir, ".cursor"),
|
|
11265
|
+
path20.join(rootDir, ".obsidian")
|
|
11266
|
+
].map((candidate) => path20.resolve(candidate));
|
|
11267
|
+
}
|
|
11268
|
+
async function resolveWatchTargets(rootDir, paths, options) {
|
|
11269
|
+
const targets = /* @__PURE__ */ new Set([path20.resolve(paths.inboxDir)]);
|
|
11270
|
+
if (options.repo) {
|
|
11271
|
+
for (const repoRoot of await listTrackedRepoRoots(rootDir)) {
|
|
11272
|
+
targets.add(path20.resolve(repoRoot));
|
|
11273
|
+
}
|
|
11274
|
+
}
|
|
11275
|
+
return [...targets].sort((left, right) => left.localeCompare(right));
|
|
11276
|
+
}
|
|
11277
|
+
async function performWatchCycle(rootDir, paths, options) {
|
|
11278
|
+
const imported = await importInbox(rootDir, paths.inboxDir);
|
|
11279
|
+
const repoSync = options.repo ? await syncTrackedReposForWatch(rootDir) : null;
|
|
11280
|
+
const compile = await compileVault(rootDir);
|
|
11281
|
+
const pendingSemanticRefresh = repoSync ? await mergePendingSemanticRefresh(rootDir, repoSync.pendingSemanticRefresh) : await readPendingSemanticRefresh(rootDir);
|
|
11282
|
+
const stalePagePaths = await markPagesStaleForSources(
|
|
11283
|
+
rootDir,
|
|
11284
|
+
pendingSemanticRefresh.map((entry) => entry.sourceId).filter((sourceId) => Boolean(sourceId))
|
|
11285
|
+
);
|
|
11286
|
+
const lintFindingCount = options.lint ? (await lintVault(rootDir)).length : void 0;
|
|
11287
|
+
return {
|
|
11288
|
+
watchedRepoRoots: repoSync?.repoRoots ?? [],
|
|
11289
|
+
importedCount: imported.imported.length,
|
|
11290
|
+
scannedCount: imported.scannedCount,
|
|
11291
|
+
attachmentCount: imported.attachmentCount,
|
|
11292
|
+
repoImportedCount: repoSync?.imported.length ?? 0,
|
|
11293
|
+
repoUpdatedCount: repoSync?.updated.length ?? 0,
|
|
11294
|
+
repoRemovedCount: repoSync?.removed.length ?? 0,
|
|
11295
|
+
repoScannedCount: repoSync?.scannedCount ?? 0,
|
|
11296
|
+
pendingSemanticRefreshCount: pendingSemanticRefresh.length,
|
|
11297
|
+
pendingSemanticRefreshPaths: pendingSemanticRefresh.map((entry) => entry.path),
|
|
11298
|
+
changedPages: [.../* @__PURE__ */ new Set([...compile.changedPages, ...stalePagePaths])],
|
|
11299
|
+
lintFindingCount
|
|
11300
|
+
};
|
|
11301
|
+
}
|
|
11302
|
+
async function runWatchCycle(rootDir, options = {}) {
|
|
11303
|
+
const { paths } = await initWorkspace(rootDir);
|
|
11304
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
11305
|
+
let success = true;
|
|
11306
|
+
let error;
|
|
11307
|
+
let result = {
|
|
11308
|
+
watchedRepoRoots: [],
|
|
11309
|
+
importedCount: 0,
|
|
11310
|
+
scannedCount: 0,
|
|
11311
|
+
attachmentCount: 0,
|
|
11312
|
+
repoImportedCount: 0,
|
|
11313
|
+
repoUpdatedCount: 0,
|
|
11314
|
+
repoRemovedCount: 0,
|
|
11315
|
+
repoScannedCount: 0,
|
|
11316
|
+
pendingSemanticRefreshCount: 0,
|
|
11317
|
+
pendingSemanticRefreshPaths: [],
|
|
11318
|
+
changedPages: []
|
|
11319
|
+
};
|
|
11320
|
+
try {
|
|
11321
|
+
result = await performWatchCycle(rootDir, paths, options);
|
|
11322
|
+
return result;
|
|
11323
|
+
} catch (caught) {
|
|
11324
|
+
success = false;
|
|
11325
|
+
error = caught instanceof Error ? caught.message : String(caught);
|
|
11326
|
+
throw caught;
|
|
11327
|
+
} finally {
|
|
11328
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
11329
|
+
await recordSession(rootDir, {
|
|
11330
|
+
operation: "watch",
|
|
11331
|
+
title: `Watch cycle for ${paths.inboxDir}${options.repo ? " and tracked repos" : ""}`,
|
|
11332
|
+
startedAt: startedAt.toISOString(),
|
|
11333
|
+
finishedAt: finishedAt.toISOString(),
|
|
11334
|
+
success,
|
|
11335
|
+
error,
|
|
11336
|
+
changedPages: result.changedPages,
|
|
11337
|
+
lintFindingCount: result.lintFindingCount,
|
|
11338
|
+
lines: [
|
|
11339
|
+
"reasons=once",
|
|
11340
|
+
`imported=${result.importedCount}`,
|
|
11341
|
+
`scanned=${result.scannedCount}`,
|
|
11342
|
+
`attachments=${result.attachmentCount}`,
|
|
11343
|
+
`repo_scanned=${result.repoScannedCount}`,
|
|
11344
|
+
`repo_imported=${result.repoImportedCount}`,
|
|
11345
|
+
`repo_updated=${result.repoUpdatedCount}`,
|
|
11346
|
+
`repo_removed=${result.repoRemovedCount}`,
|
|
11347
|
+
`pending_semantic_refresh=${result.pendingSemanticRefreshCount}`,
|
|
11348
|
+
`lint=${result.lintFindingCount ?? 0}`
|
|
11349
|
+
]
|
|
11350
|
+
});
|
|
11351
|
+
await appendWatchRun(rootDir, {
|
|
11352
|
+
startedAt: startedAt.toISOString(),
|
|
11353
|
+
finishedAt: finishedAt.toISOString(),
|
|
11354
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
11355
|
+
inputDir: paths.inboxDir,
|
|
11356
|
+
reasons: ["once"],
|
|
11357
|
+
importedCount: result.importedCount + result.repoImportedCount + result.repoUpdatedCount,
|
|
11358
|
+
scannedCount: result.scannedCount + result.repoScannedCount,
|
|
11359
|
+
attachmentCount: result.attachmentCount,
|
|
11360
|
+
changedPages: result.changedPages,
|
|
11361
|
+
repoImportedCount: result.repoImportedCount,
|
|
11362
|
+
repoUpdatedCount: result.repoUpdatedCount,
|
|
11363
|
+
repoRemovedCount: result.repoRemovedCount,
|
|
11364
|
+
repoScannedCount: result.repoScannedCount,
|
|
11365
|
+
pendingSemanticRefreshCount: result.pendingSemanticRefreshCount,
|
|
11366
|
+
pendingSemanticRefreshPaths: result.pendingSemanticRefreshPaths,
|
|
11367
|
+
lintFindingCount: result.lintFindingCount,
|
|
11368
|
+
success,
|
|
11369
|
+
error
|
|
11370
|
+
});
|
|
11371
|
+
await writeWatchStatusArtifact(rootDir, {
|
|
11372
|
+
generatedAt: finishedAt.toISOString(),
|
|
11373
|
+
watchedRepoRoots: result.watchedRepoRoots,
|
|
11374
|
+
lastRun: {
|
|
11375
|
+
startedAt: startedAt.toISOString(),
|
|
11376
|
+
finishedAt: finishedAt.toISOString(),
|
|
11377
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
11378
|
+
inputDir: paths.inboxDir,
|
|
11379
|
+
reasons: ["once"],
|
|
11380
|
+
importedCount: result.importedCount + result.repoImportedCount + result.repoUpdatedCount,
|
|
11381
|
+
scannedCount: result.scannedCount + result.repoScannedCount,
|
|
11382
|
+
attachmentCount: result.attachmentCount,
|
|
11383
|
+
changedPages: result.changedPages,
|
|
11384
|
+
repoImportedCount: result.repoImportedCount,
|
|
11385
|
+
repoUpdatedCount: result.repoUpdatedCount,
|
|
11386
|
+
repoRemovedCount: result.repoRemovedCount,
|
|
11387
|
+
repoScannedCount: result.repoScannedCount,
|
|
11388
|
+
pendingSemanticRefreshCount: result.pendingSemanticRefreshCount,
|
|
11389
|
+
pendingSemanticRefreshPaths: result.pendingSemanticRefreshPaths,
|
|
11390
|
+
lintFindingCount: result.lintFindingCount,
|
|
11391
|
+
success,
|
|
11392
|
+
error
|
|
11393
|
+
},
|
|
11394
|
+
pendingSemanticRefresh: await readPendingSemanticRefresh(rootDir)
|
|
11395
|
+
});
|
|
11396
|
+
}
|
|
11397
|
+
}
|
|
11398
|
+
async function watchVault(rootDir, options = {}) {
|
|
11399
|
+
const { paths } = await initWorkspace(rootDir);
|
|
11400
|
+
const baseDebounceMs = options.debounceMs ?? 900;
|
|
11401
|
+
const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
|
|
11402
|
+
const inboxWatchRoot = path20.resolve(paths.inboxDir);
|
|
11403
|
+
let watchTargets = await resolveWatchTargets(rootDir, paths, options);
|
|
11404
|
+
let timer;
|
|
11405
|
+
let running = false;
|
|
11406
|
+
let pending = false;
|
|
11407
|
+
let closed = false;
|
|
11408
|
+
let consecutiveFailures = 0;
|
|
11409
|
+
let currentDebounceMs = baseDebounceMs;
|
|
11410
|
+
const reasons = /* @__PURE__ */ new Set();
|
|
11411
|
+
const watcher = chokidar.watch(watchTargets, {
|
|
11412
|
+
ignoreInitial: true,
|
|
11413
|
+
usePolling: true,
|
|
11414
|
+
interval: 100,
|
|
11415
|
+
ignored: (targetPath) => {
|
|
11416
|
+
const absolutePath = path20.resolve(targetPath);
|
|
11417
|
+
const primaryTarget = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
|
|
11418
|
+
if (!primaryTarget) {
|
|
11419
|
+
return false;
|
|
11420
|
+
}
|
|
11421
|
+
if (primaryTarget !== inboxWatchRoot && ignoredRoots.some((ignoreRoot) => withinRoot2(ignoreRoot, absolutePath))) {
|
|
11422
|
+
return true;
|
|
11423
|
+
}
|
|
11424
|
+
return hasIgnoredRepoSegment(primaryTarget, absolutePath);
|
|
11425
|
+
},
|
|
11426
|
+
awaitWriteFinish: {
|
|
11427
|
+
stabilityThreshold: Math.max(250, Math.floor(baseDebounceMs / 2)),
|
|
11428
|
+
pollInterval: 100
|
|
11429
|
+
}
|
|
11430
|
+
});
|
|
11431
|
+
const syncWatchTargets = async () => {
|
|
11432
|
+
const nextTargets = await resolveWatchTargets(rootDir, paths, options);
|
|
11433
|
+
const nextSet = new Set(nextTargets);
|
|
11434
|
+
const currentSet = new Set(watchTargets);
|
|
11435
|
+
const toRemove = watchTargets.filter((target) => !nextSet.has(target));
|
|
11436
|
+
const toAdd = nextTargets.filter((target) => !currentSet.has(target));
|
|
11437
|
+
if (toRemove.length > 0) {
|
|
11438
|
+
await watcher.unwatch(toRemove);
|
|
11439
|
+
}
|
|
11440
|
+
if (toAdd.length > 0) {
|
|
11441
|
+
await watcher.add(toAdd);
|
|
11442
|
+
}
|
|
11443
|
+
watchTargets = nextTargets;
|
|
11444
|
+
};
|
|
11445
|
+
const schedule = (reason) => {
|
|
11446
|
+
if (closed) {
|
|
11447
|
+
return;
|
|
11448
|
+
}
|
|
11449
|
+
reasons.add(reason);
|
|
11450
|
+
pending = true;
|
|
11451
|
+
if (timer) {
|
|
11452
|
+
clearTimeout(timer);
|
|
11453
|
+
}
|
|
11454
|
+
timer = setTimeout(() => {
|
|
11455
|
+
void runCycle();
|
|
11456
|
+
}, currentDebounceMs);
|
|
11457
|
+
};
|
|
11458
|
+
const runCycle = async () => {
|
|
11459
|
+
if (running || closed || !pending) {
|
|
11460
|
+
return;
|
|
11461
|
+
}
|
|
11462
|
+
pending = false;
|
|
11463
|
+
running = true;
|
|
11464
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
11465
|
+
const runReasons = [...reasons];
|
|
11466
|
+
reasons.clear();
|
|
11467
|
+
let importedCount = 0;
|
|
11468
|
+
let scannedCount = 0;
|
|
11469
|
+
let attachmentCount = 0;
|
|
11470
|
+
let repoImportedCount = 0;
|
|
11471
|
+
let repoUpdatedCount = 0;
|
|
11472
|
+
let repoRemovedCount = 0;
|
|
11473
|
+
let repoScannedCount = 0;
|
|
11474
|
+
let watchedRepoRoots = [];
|
|
11475
|
+
let pendingSemanticRefreshCount = 0;
|
|
11476
|
+
let pendingSemanticRefreshPaths = [];
|
|
11477
|
+
let changedPages = [];
|
|
11478
|
+
let lintFindingCount;
|
|
11479
|
+
let success = true;
|
|
11480
|
+
let error;
|
|
11481
|
+
try {
|
|
11482
|
+
const result = await performWatchCycle(rootDir, paths, options);
|
|
11483
|
+
importedCount = result.importedCount;
|
|
11484
|
+
scannedCount = result.scannedCount;
|
|
11485
|
+
attachmentCount = result.attachmentCount;
|
|
11486
|
+
repoImportedCount = result.repoImportedCount;
|
|
11487
|
+
repoUpdatedCount = result.repoUpdatedCount;
|
|
11488
|
+
repoRemovedCount = result.repoRemovedCount;
|
|
11489
|
+
repoScannedCount = result.repoScannedCount;
|
|
11490
|
+
watchedRepoRoots = result.watchedRepoRoots;
|
|
11491
|
+
pendingSemanticRefreshCount = result.pendingSemanticRefreshCount;
|
|
11492
|
+
pendingSemanticRefreshPaths = result.pendingSemanticRefreshPaths;
|
|
11493
|
+
changedPages = result.changedPages;
|
|
11494
|
+
lintFindingCount = result.lintFindingCount;
|
|
11495
|
+
consecutiveFailures = 0;
|
|
11496
|
+
currentDebounceMs = baseDebounceMs;
|
|
11497
|
+
await syncWatchTargets();
|
|
11498
|
+
} catch (caught) {
|
|
11499
|
+
success = false;
|
|
11500
|
+
error = caught instanceof Error ? caught.message : String(caught);
|
|
11501
|
+
consecutiveFailures++;
|
|
11502
|
+
pending = true;
|
|
11503
|
+
if (consecutiveFailures >= CRITICAL_THRESHOLD) {
|
|
11504
|
+
process2.stderr.write(
|
|
11505
|
+
`[swarmvault watch] ${consecutiveFailures} consecutive failures. Check vault state. Continuing at max backoff.
|
|
11506
|
+
`
|
|
11507
|
+
);
|
|
11508
|
+
}
|
|
11509
|
+
if (consecutiveFailures >= BACKOFF_THRESHOLD) {
|
|
11510
|
+
const multiplier = 2 ** (consecutiveFailures - BACKOFF_THRESHOLD);
|
|
11511
|
+
currentDebounceMs = Math.min(baseDebounceMs * multiplier, MAX_BACKOFF_MS);
|
|
11512
|
+
}
|
|
11513
|
+
} finally {
|
|
11514
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
11515
|
+
await recordSession(rootDir, {
|
|
11516
|
+
operation: "watch",
|
|
11517
|
+
title: `Watch cycle for ${paths.inboxDir}${options.repo ? " and tracked repos" : ""}`,
|
|
11518
|
+
startedAt: startedAt.toISOString(),
|
|
11519
|
+
finishedAt: finishedAt.toISOString(),
|
|
11520
|
+
success,
|
|
11521
|
+
error,
|
|
11522
|
+
changedPages,
|
|
11523
|
+
lintFindingCount,
|
|
11524
|
+
lines: [
|
|
11525
|
+
`reasons=${runReasons.join(",") || "none"}`,
|
|
11526
|
+
`imported=${importedCount}`,
|
|
11527
|
+
`scanned=${scannedCount}`,
|
|
11528
|
+
`attachments=${attachmentCount}`,
|
|
11529
|
+
`repo_scanned=${repoScannedCount}`,
|
|
11530
|
+
`repo_imported=${repoImportedCount}`,
|
|
11531
|
+
`repo_updated=${repoUpdatedCount}`,
|
|
11532
|
+
`repo_removed=${repoRemovedCount}`,
|
|
11533
|
+
`lint=${lintFindingCount ?? 0}`
|
|
11534
|
+
]
|
|
11535
|
+
});
|
|
11536
|
+
await appendWatchRun(rootDir, {
|
|
11537
|
+
startedAt: startedAt.toISOString(),
|
|
11538
|
+
finishedAt: finishedAt.toISOString(),
|
|
11539
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
11540
|
+
inputDir: paths.inboxDir,
|
|
11541
|
+
reasons: runReasons,
|
|
11542
|
+
importedCount: importedCount + repoImportedCount + repoUpdatedCount,
|
|
11543
|
+
scannedCount: scannedCount + repoScannedCount,
|
|
11544
|
+
attachmentCount,
|
|
11545
|
+
changedPages,
|
|
11546
|
+
repoImportedCount,
|
|
11547
|
+
repoUpdatedCount,
|
|
11548
|
+
repoRemovedCount,
|
|
11549
|
+
repoScannedCount,
|
|
11550
|
+
pendingSemanticRefreshCount,
|
|
11551
|
+
pendingSemanticRefreshPaths,
|
|
11552
|
+
lintFindingCount,
|
|
11553
|
+
success,
|
|
11554
|
+
error
|
|
11555
|
+
});
|
|
11556
|
+
await writeWatchStatusArtifact(rootDir, {
|
|
11557
|
+
generatedAt: finishedAt.toISOString(),
|
|
11558
|
+
watchedRepoRoots,
|
|
11559
|
+
lastRun: {
|
|
11560
|
+
startedAt: startedAt.toISOString(),
|
|
11561
|
+
finishedAt: finishedAt.toISOString(),
|
|
11562
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
11563
|
+
inputDir: paths.inboxDir,
|
|
11564
|
+
reasons: runReasons,
|
|
11565
|
+
importedCount: importedCount + repoImportedCount + repoUpdatedCount,
|
|
11566
|
+
scannedCount: scannedCount + repoScannedCount,
|
|
11567
|
+
attachmentCount,
|
|
11568
|
+
changedPages,
|
|
11569
|
+
repoImportedCount,
|
|
11570
|
+
repoUpdatedCount,
|
|
11571
|
+
repoRemovedCount,
|
|
11572
|
+
repoScannedCount,
|
|
11573
|
+
pendingSemanticRefreshCount,
|
|
11574
|
+
pendingSemanticRefreshPaths,
|
|
11575
|
+
lintFindingCount,
|
|
11576
|
+
success,
|
|
11577
|
+
error
|
|
11578
|
+
},
|
|
11579
|
+
pendingSemanticRefresh: await readPendingSemanticRefresh(rootDir)
|
|
11580
|
+
});
|
|
11581
|
+
running = false;
|
|
11582
|
+
if (pending && !closed) {
|
|
11583
|
+
schedule("queued");
|
|
11584
|
+
}
|
|
11585
|
+
}
|
|
11586
|
+
};
|
|
11587
|
+
const reasonForPath = (targetPath) => {
|
|
11588
|
+
const baseDir = watchTargets.filter((watchTarget) => withinRoot2(watchTarget, path20.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
|
|
11589
|
+
return path20.relative(baseDir, targetPath) || ".";
|
|
11590
|
+
};
|
|
11591
|
+
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)}`));
|
|
11592
|
+
await new Promise((resolve, reject) => {
|
|
11593
|
+
const handleReady = () => {
|
|
11594
|
+
watcher.off("error", handleError);
|
|
11595
|
+
resolve();
|
|
11596
|
+
};
|
|
11597
|
+
const handleError = (caught) => {
|
|
11598
|
+
watcher.off("ready", handleReady);
|
|
11599
|
+
reject(caught);
|
|
11600
|
+
};
|
|
11601
|
+
watcher.once("ready", handleReady);
|
|
11602
|
+
watcher.once("error", handleError);
|
|
11603
|
+
});
|
|
11604
|
+
return {
|
|
11605
|
+
close: async () => {
|
|
11606
|
+
closed = true;
|
|
11607
|
+
if (timer) {
|
|
11608
|
+
clearTimeout(timer);
|
|
11609
|
+
}
|
|
11610
|
+
await watcher.close();
|
|
11611
|
+
}
|
|
11612
|
+
};
|
|
11613
|
+
}
|
|
11614
|
+
async function getWatchStatus(rootDir) {
|
|
11615
|
+
const persisted = await readWatchStatusArtifact(rootDir);
|
|
11616
|
+
const watchedRepoRoots = await listTrackedRepoRoots(rootDir);
|
|
11617
|
+
const pendingSemanticRefresh = await readPendingSemanticRefresh(rootDir);
|
|
11618
|
+
return {
|
|
11619
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11620
|
+
watchedRepoRoots,
|
|
11621
|
+
lastRun: persisted?.lastRun,
|
|
11622
|
+
pendingSemanticRefresh
|
|
11623
|
+
};
|
|
11624
|
+
}
|
|
11625
|
+
|
|
11626
|
+
// src/viewer.ts
|
|
9789
11627
|
var execFileAsync = promisify(execFile);
|
|
9790
11628
|
async function readViewerPage(rootDir, relativePath) {
|
|
9791
11629
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9792
|
-
const absolutePath =
|
|
11630
|
+
const absolutePath = path21.resolve(paths.wikiDir, relativePath);
|
|
9793
11631
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
9794
11632
|
return null;
|
|
9795
11633
|
}
|
|
9796
|
-
const raw = await
|
|
9797
|
-
const parsed =
|
|
11634
|
+
const raw = await fs17.readFile(absolutePath, "utf8");
|
|
11635
|
+
const parsed = matter9(raw);
|
|
9798
11636
|
return {
|
|
9799
11637
|
path: relativePath,
|
|
9800
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
11638
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path21.basename(relativePath, path21.extname(relativePath)),
|
|
9801
11639
|
frontmatter: parsed.data,
|
|
9802
11640
|
content: parsed.content,
|
|
9803
11641
|
assets: normalizeOutputAssets(parsed.data.output_assets)
|
|
@@ -9805,12 +11643,12 @@ async function readViewerPage(rootDir, relativePath) {
|
|
|
9805
11643
|
}
|
|
9806
11644
|
async function readViewerAsset(rootDir, relativePath) {
|
|
9807
11645
|
const { paths } = await loadVaultConfig(rootDir);
|
|
9808
|
-
const absolutePath =
|
|
11646
|
+
const absolutePath = path21.resolve(paths.wikiDir, relativePath);
|
|
9809
11647
|
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
9810
11648
|
return null;
|
|
9811
11649
|
}
|
|
9812
11650
|
return {
|
|
9813
|
-
buffer: await
|
|
11651
|
+
buffer: await fs17.readFile(absolutePath),
|
|
9814
11652
|
mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
|
|
9815
11653
|
};
|
|
9816
11654
|
}
|
|
@@ -9833,12 +11671,12 @@ async function readJsonBody(request) {
|
|
|
9833
11671
|
return JSON.parse(raw);
|
|
9834
11672
|
}
|
|
9835
11673
|
async function ensureViewerDist(viewerDistDir) {
|
|
9836
|
-
const indexPath =
|
|
11674
|
+
const indexPath = path21.join(viewerDistDir, "index.html");
|
|
9837
11675
|
if (await fileExists(indexPath)) {
|
|
9838
11676
|
return;
|
|
9839
11677
|
}
|
|
9840
|
-
const viewerProjectDir =
|
|
9841
|
-
if (await fileExists(
|
|
11678
|
+
const viewerProjectDir = path21.dirname(viewerDistDir);
|
|
11679
|
+
if (await fileExists(path21.join(viewerProjectDir, "package.json"))) {
|
|
9842
11680
|
await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
|
|
9843
11681
|
}
|
|
9844
11682
|
}
|
|
@@ -9855,7 +11693,7 @@ async function startGraphServer(rootDir, port) {
|
|
|
9855
11693
|
return;
|
|
9856
11694
|
}
|
|
9857
11695
|
response.writeHead(200, { "content-type": "application/json" });
|
|
9858
|
-
response.end(await
|
|
11696
|
+
response.end(await fs17.readFile(paths.graphPath, "utf8"));
|
|
9859
11697
|
return;
|
|
9860
11698
|
}
|
|
9861
11699
|
if (url.pathname === "/api/graph/query") {
|
|
@@ -9907,6 +11745,11 @@ async function startGraphServer(rootDir, port) {
|
|
|
9907
11745
|
response.end(JSON.stringify(results));
|
|
9908
11746
|
return;
|
|
9909
11747
|
}
|
|
11748
|
+
if (url.pathname === "/api/watch-status") {
|
|
11749
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
11750
|
+
response.end(JSON.stringify(await getWatchStatus(rootDir)));
|
|
11751
|
+
return;
|
|
11752
|
+
}
|
|
9910
11753
|
if (url.pathname === "/api/page") {
|
|
9911
11754
|
const relativePath2 = url.searchParams.get("path") ?? "";
|
|
9912
11755
|
const page = await readViewerPage(rootDir, relativePath2);
|
|
@@ -9982,8 +11825,8 @@ async function startGraphServer(rootDir, port) {
|
|
|
9982
11825
|
return;
|
|
9983
11826
|
}
|
|
9984
11827
|
const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
9985
|
-
const target =
|
|
9986
|
-
const fallback =
|
|
11828
|
+
const target = path21.join(paths.viewerDistDir, relativePath);
|
|
11829
|
+
const fallback = path21.join(paths.viewerDistDir, "index.html");
|
|
9987
11830
|
const filePath = await fileExists(target) ? target : fallback;
|
|
9988
11831
|
if (!await fileExists(filePath)) {
|
|
9989
11832
|
response.writeHead(503, { "content-type": "text/plain" });
|
|
@@ -9991,7 +11834,7 @@ async function startGraphServer(rootDir, port) {
|
|
|
9991
11834
|
return;
|
|
9992
11835
|
}
|
|
9993
11836
|
response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
|
|
9994
|
-
response.end(await
|
|
11837
|
+
response.end(await fs17.readFile(filePath));
|
|
9995
11838
|
});
|
|
9996
11839
|
await new Promise((resolve) => {
|
|
9997
11840
|
server.listen(effectivePort, resolve);
|
|
@@ -10018,7 +11861,7 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
10018
11861
|
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
10019
11862
|
}
|
|
10020
11863
|
await ensureViewerDist(paths.viewerDistDir);
|
|
10021
|
-
const indexPath =
|
|
11864
|
+
const indexPath = path21.join(paths.viewerDistDir, "index.html");
|
|
10022
11865
|
if (!await fileExists(indexPath)) {
|
|
10023
11866
|
throw new Error("Viewer build not found. Run `pnpm build` first.");
|
|
10024
11867
|
}
|
|
@@ -10042,16 +11885,16 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
10042
11885
|
} : null;
|
|
10043
11886
|
})
|
|
10044
11887
|
);
|
|
10045
|
-
const rawHtml = await
|
|
11888
|
+
const rawHtml = await fs17.readFile(indexPath, "utf8");
|
|
10046
11889
|
const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
|
|
10047
11890
|
const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
|
|
10048
|
-
const scriptPath = scriptMatch?.[1] ?
|
|
10049
|
-
const stylePath = styleMatch?.[1] ?
|
|
11891
|
+
const scriptPath = scriptMatch?.[1] ? path21.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
|
|
11892
|
+
const stylePath = styleMatch?.[1] ? path21.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
|
|
10050
11893
|
if (!scriptPath || !await fileExists(scriptPath)) {
|
|
10051
11894
|
throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
|
|
10052
11895
|
}
|
|
10053
|
-
const script = await
|
|
10054
|
-
const style = stylePath && await fileExists(stylePath) ? await
|
|
11896
|
+
const script = await fs17.readFile(scriptPath, "utf8");
|
|
11897
|
+
const style = stylePath && await fileExists(stylePath) ? await fs17.readFile(stylePath, "utf8") : "";
|
|
10055
11898
|
const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean) }, null, 2).replace(/</g, "\\u003c");
|
|
10056
11899
|
const html = [
|
|
10057
11900
|
"<!doctype html>",
|
|
@@ -10070,163 +11913,16 @@ async function exportGraphHtml(rootDir, outputPath) {
|
|
|
10070
11913
|
"</html>",
|
|
10071
11914
|
""
|
|
10072
11915
|
].filter(Boolean).join("\n");
|
|
10073
|
-
await
|
|
10074
|
-
await
|
|
10075
|
-
return
|
|
10076
|
-
}
|
|
10077
|
-
|
|
10078
|
-
// src/watch.ts
|
|
10079
|
-
import path18 from "path";
|
|
10080
|
-
import process2 from "process";
|
|
10081
|
-
import chokidar from "chokidar";
|
|
10082
|
-
var MAX_BACKOFF_MS = 3e4;
|
|
10083
|
-
var BACKOFF_THRESHOLD = 3;
|
|
10084
|
-
var CRITICAL_THRESHOLD = 10;
|
|
10085
|
-
async function watchVault(rootDir, options = {}) {
|
|
10086
|
-
const { paths } = await initWorkspace(rootDir);
|
|
10087
|
-
const baseDebounceMs = options.debounceMs ?? 900;
|
|
10088
|
-
let timer;
|
|
10089
|
-
let running = false;
|
|
10090
|
-
let pending = false;
|
|
10091
|
-
let closed = false;
|
|
10092
|
-
let consecutiveFailures = 0;
|
|
10093
|
-
let currentDebounceMs = baseDebounceMs;
|
|
10094
|
-
const reasons = /* @__PURE__ */ new Set();
|
|
10095
|
-
const watcher = chokidar.watch(paths.inboxDir, {
|
|
10096
|
-
ignoreInitial: true,
|
|
10097
|
-
usePolling: true,
|
|
10098
|
-
interval: 100,
|
|
10099
|
-
awaitWriteFinish: {
|
|
10100
|
-
stabilityThreshold: Math.max(250, Math.floor(baseDebounceMs / 2)),
|
|
10101
|
-
pollInterval: 100
|
|
10102
|
-
}
|
|
10103
|
-
});
|
|
10104
|
-
const schedule = (reason) => {
|
|
10105
|
-
if (closed) {
|
|
10106
|
-
return;
|
|
10107
|
-
}
|
|
10108
|
-
reasons.add(reason);
|
|
10109
|
-
pending = true;
|
|
10110
|
-
if (timer) {
|
|
10111
|
-
clearTimeout(timer);
|
|
10112
|
-
}
|
|
10113
|
-
timer = setTimeout(() => {
|
|
10114
|
-
void runCycle();
|
|
10115
|
-
}, currentDebounceMs);
|
|
10116
|
-
};
|
|
10117
|
-
const runCycle = async () => {
|
|
10118
|
-
if (running || closed || !pending) {
|
|
10119
|
-
return;
|
|
10120
|
-
}
|
|
10121
|
-
pending = false;
|
|
10122
|
-
running = true;
|
|
10123
|
-
const startedAt = /* @__PURE__ */ new Date();
|
|
10124
|
-
const runReasons = [...reasons];
|
|
10125
|
-
reasons.clear();
|
|
10126
|
-
let importedCount = 0;
|
|
10127
|
-
let scannedCount = 0;
|
|
10128
|
-
let attachmentCount = 0;
|
|
10129
|
-
let changedPages = [];
|
|
10130
|
-
let lintFindingCount;
|
|
10131
|
-
let success = true;
|
|
10132
|
-
let error;
|
|
10133
|
-
try {
|
|
10134
|
-
const imported = await importInbox(rootDir, paths.inboxDir);
|
|
10135
|
-
importedCount = imported.imported.length;
|
|
10136
|
-
scannedCount = imported.scannedCount;
|
|
10137
|
-
attachmentCount = imported.attachmentCount;
|
|
10138
|
-
const compile = await compileVault(rootDir);
|
|
10139
|
-
changedPages = compile.changedPages;
|
|
10140
|
-
if (options.lint) {
|
|
10141
|
-
const findings = await lintVault(rootDir);
|
|
10142
|
-
lintFindingCount = findings.length;
|
|
10143
|
-
}
|
|
10144
|
-
consecutiveFailures = 0;
|
|
10145
|
-
currentDebounceMs = baseDebounceMs;
|
|
10146
|
-
} catch (caught) {
|
|
10147
|
-
success = false;
|
|
10148
|
-
error = caught instanceof Error ? caught.message : String(caught);
|
|
10149
|
-
consecutiveFailures++;
|
|
10150
|
-
pending = true;
|
|
10151
|
-
if (consecutiveFailures >= CRITICAL_THRESHOLD) {
|
|
10152
|
-
process2.stderr.write(
|
|
10153
|
-
`[swarmvault watch] ${consecutiveFailures} consecutive failures. Check vault state. Continuing at max backoff.
|
|
10154
|
-
`
|
|
10155
|
-
);
|
|
10156
|
-
}
|
|
10157
|
-
if (consecutiveFailures >= BACKOFF_THRESHOLD) {
|
|
10158
|
-
const multiplier = 2 ** (consecutiveFailures - BACKOFF_THRESHOLD);
|
|
10159
|
-
currentDebounceMs = Math.min(baseDebounceMs * multiplier, MAX_BACKOFF_MS);
|
|
10160
|
-
}
|
|
10161
|
-
} finally {
|
|
10162
|
-
const finishedAt = /* @__PURE__ */ new Date();
|
|
10163
|
-
await recordSession(rootDir, {
|
|
10164
|
-
operation: "watch",
|
|
10165
|
-
title: `Watch cycle for ${paths.inboxDir}`,
|
|
10166
|
-
startedAt: startedAt.toISOString(),
|
|
10167
|
-
finishedAt: finishedAt.toISOString(),
|
|
10168
|
-
success,
|
|
10169
|
-
error,
|
|
10170
|
-
changedPages,
|
|
10171
|
-
lintFindingCount,
|
|
10172
|
-
lines: [
|
|
10173
|
-
`reasons=${runReasons.join(",") || "none"}`,
|
|
10174
|
-
`imported=${importedCount}`,
|
|
10175
|
-
`scanned=${scannedCount}`,
|
|
10176
|
-
`attachments=${attachmentCount}`,
|
|
10177
|
-
`lint=${lintFindingCount ?? 0}`
|
|
10178
|
-
]
|
|
10179
|
-
});
|
|
10180
|
-
await appendWatchRun(rootDir, {
|
|
10181
|
-
startedAt: startedAt.toISOString(),
|
|
10182
|
-
finishedAt: finishedAt.toISOString(),
|
|
10183
|
-
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
10184
|
-
inputDir: paths.inboxDir,
|
|
10185
|
-
reasons: runReasons,
|
|
10186
|
-
importedCount,
|
|
10187
|
-
scannedCount,
|
|
10188
|
-
attachmentCount,
|
|
10189
|
-
changedPages,
|
|
10190
|
-
lintFindingCount,
|
|
10191
|
-
success,
|
|
10192
|
-
error
|
|
10193
|
-
});
|
|
10194
|
-
running = false;
|
|
10195
|
-
if (pending && !closed) {
|
|
10196
|
-
schedule("queued");
|
|
10197
|
-
}
|
|
10198
|
-
}
|
|
10199
|
-
};
|
|
10200
|
-
watcher.on("add", (filePath) => schedule(`add:${toWatchReason(paths.inboxDir, filePath)}`)).on("change", (filePath) => schedule(`change:${toWatchReason(paths.inboxDir, filePath)}`)).on("unlink", (filePath) => schedule(`unlink:${toWatchReason(paths.inboxDir, filePath)}`)).on("addDir", (dirPath) => schedule(`addDir:${toWatchReason(paths.inboxDir, dirPath)}`)).on("unlinkDir", (dirPath) => schedule(`unlinkDir:${toWatchReason(paths.inboxDir, dirPath)}`)).on("error", (caught) => schedule(`error:${caught instanceof Error ? caught.message : String(caught)}`));
|
|
10201
|
-
await new Promise((resolve, reject) => {
|
|
10202
|
-
const handleReady = () => {
|
|
10203
|
-
watcher.off("error", handleError);
|
|
10204
|
-
resolve();
|
|
10205
|
-
};
|
|
10206
|
-
const handleError = (caught) => {
|
|
10207
|
-
watcher.off("ready", handleReady);
|
|
10208
|
-
reject(caught);
|
|
10209
|
-
};
|
|
10210
|
-
watcher.once("ready", handleReady);
|
|
10211
|
-
watcher.once("error", handleError);
|
|
10212
|
-
});
|
|
10213
|
-
return {
|
|
10214
|
-
close: async () => {
|
|
10215
|
-
closed = true;
|
|
10216
|
-
if (timer) {
|
|
10217
|
-
clearTimeout(timer);
|
|
10218
|
-
}
|
|
10219
|
-
await watcher.close();
|
|
10220
|
-
}
|
|
10221
|
-
};
|
|
10222
|
-
}
|
|
10223
|
-
function toWatchReason(baseDir, targetPath) {
|
|
10224
|
-
return path18.relative(baseDir, targetPath) || ".";
|
|
11916
|
+
await fs17.mkdir(path21.dirname(outputPath), { recursive: true });
|
|
11917
|
+
await fs17.writeFile(outputPath, html, "utf8");
|
|
11918
|
+
return path21.resolve(outputPath);
|
|
10225
11919
|
}
|
|
10226
11920
|
export {
|
|
10227
11921
|
acceptApproval,
|
|
11922
|
+
addInput,
|
|
10228
11923
|
archiveCandidate,
|
|
10229
11924
|
assertProviderCapability,
|
|
11925
|
+
benchmarkVault,
|
|
10230
11926
|
bootstrapDemo,
|
|
10231
11927
|
compileVault,
|
|
10232
11928
|
createMcpServer,
|
|
@@ -10236,8 +11932,11 @@ export {
|
|
|
10236
11932
|
defaultVaultSchema,
|
|
10237
11933
|
explainGraphVault,
|
|
10238
11934
|
exploreVault,
|
|
11935
|
+
exportGraphFormat,
|
|
10239
11936
|
exportGraphHtml,
|
|
11937
|
+
getGitHookStatus,
|
|
10240
11938
|
getProviderForTask,
|
|
11939
|
+
getWatchStatus,
|
|
10241
11940
|
getWebSearchAdapterForTask,
|
|
10242
11941
|
getWorkspaceInfo,
|
|
10243
11942
|
importInbox,
|
|
@@ -10247,6 +11946,7 @@ export {
|
|
|
10247
11946
|
initWorkspace,
|
|
10248
11947
|
installAgent,
|
|
10249
11948
|
installConfiguredAgents,
|
|
11949
|
+
installGitHooks,
|
|
10250
11950
|
lintVault,
|
|
10251
11951
|
listApprovals,
|
|
10252
11952
|
listCandidates,
|
|
@@ -10254,6 +11954,7 @@ export {
|
|
|
10254
11954
|
listManifests,
|
|
10255
11955
|
listPages,
|
|
10256
11956
|
listSchedules,
|
|
11957
|
+
listTrackedRepoRoots,
|
|
10257
11958
|
loadVaultConfig,
|
|
10258
11959
|
loadVaultSchema,
|
|
10259
11960
|
loadVaultSchemas,
|
|
@@ -10267,9 +11968,13 @@ export {
|
|
|
10267
11968
|
rejectApproval,
|
|
10268
11969
|
resolvePaths,
|
|
10269
11970
|
runSchedule,
|
|
11971
|
+
runWatchCycle,
|
|
10270
11972
|
searchVault,
|
|
10271
11973
|
serveSchedules,
|
|
10272
11974
|
startGraphServer,
|
|
10273
11975
|
startMcpServer,
|
|
11976
|
+
syncTrackedRepos,
|
|
11977
|
+
syncTrackedReposForWatch,
|
|
11978
|
+
uninstallGitHooks,
|
|
10274
11979
|
watchVault
|
|
10275
11980
|
};
|