@swarmvaultai/engine 3.10.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-Z552HHPV.js +26846 -0
- package/dist/index.d.ts +75 -1
- package/dist/index.js +793 -260
- package/dist/memory-SAQPBIB4.js +32 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -128,7 +128,7 @@ import {
|
|
|
128
128
|
writeGuidedSourceSession,
|
|
129
129
|
writeRetrievalManifest,
|
|
130
130
|
writeWatchStatusArtifact
|
|
131
|
-
} from "./chunk-
|
|
131
|
+
} from "./chunk-Z552HHPV.js";
|
|
132
132
|
import {
|
|
133
133
|
LocalWhisperProviderAdapter,
|
|
134
134
|
SWARMVAULT_OUT_ENV,
|
|
@@ -148,6 +148,7 @@ import {
|
|
|
148
148
|
readJsonFile,
|
|
149
149
|
resolveArtifactRootDir,
|
|
150
150
|
resolvePaths,
|
|
151
|
+
safeFrontmatter,
|
|
151
152
|
sha256,
|
|
152
153
|
slugify,
|
|
153
154
|
toPosix,
|
|
@@ -161,6 +162,323 @@ import {
|
|
|
161
162
|
trimToTokenBudget
|
|
162
163
|
} from "./chunk-NAIERP4C.js";
|
|
163
164
|
|
|
165
|
+
// src/ai-export.ts
|
|
166
|
+
import fs from "fs/promises";
|
|
167
|
+
import path from "path";
|
|
168
|
+
import matter from "gray-matter";
|
|
169
|
+
var DEFAULT_MAX_FULL_CHARS = 5e6;
|
|
170
|
+
var MAX_INDEX_EXCERPT_CHARS = 220;
|
|
171
|
+
function relativeOutputPath(outputDir, filePath) {
|
|
172
|
+
return toPosix(path.relative(outputDir, filePath));
|
|
173
|
+
}
|
|
174
|
+
async function writeTrackedText(files, outputDir, kind, filePath, content) {
|
|
175
|
+
await ensureDir(path.dirname(filePath));
|
|
176
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
177
|
+
files.push({
|
|
178
|
+
kind,
|
|
179
|
+
path: relativeOutputPath(outputDir, filePath),
|
|
180
|
+
bytes: Buffer.byteLength(content),
|
|
181
|
+
sha256: sha256(content)
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
async function writeTrackedJson(files, outputDir, kind, filePath, value) {
|
|
185
|
+
const content = `${JSON.stringify(value, null, 2)}
|
|
186
|
+
`;
|
|
187
|
+
await writeTrackedText(files, outputDir, kind, filePath, content);
|
|
188
|
+
}
|
|
189
|
+
function graphPathForNode(nodeId) {
|
|
190
|
+
return `swarmvault:node:${encodeURIComponent(nodeId)}`;
|
|
191
|
+
}
|
|
192
|
+
function graphPathForPage(pageId) {
|
|
193
|
+
return `swarmvault:page:${encodeURIComponent(pageId)}`;
|
|
194
|
+
}
|
|
195
|
+
function renderPageLine(page) {
|
|
196
|
+
const excerpt = truncate(normalizeWhitespace(page.body.replace(/^---[\s\S]*?---/, "")), MAX_INDEX_EXCERPT_CHARS);
|
|
197
|
+
const bits = [
|
|
198
|
+
`- ${page.graphPage.title} (${page.graphPage.kind}, ${page.graphPage.freshness})`,
|
|
199
|
+
` - path: wiki/${page.graphPage.path}`,
|
|
200
|
+
` - page_id: ${page.graphPage.id}`,
|
|
201
|
+
page.graphPage.sourceIds.length ? ` - sources: ${page.graphPage.sourceIds.slice(0, 6).join(", ")}` : void 0,
|
|
202
|
+
excerpt ? ` - excerpt: ${excerpt}` : void 0
|
|
203
|
+
].filter((line) => Boolean(line));
|
|
204
|
+
return bits.join("\n");
|
|
205
|
+
}
|
|
206
|
+
function renderLlmsIndex(input) {
|
|
207
|
+
const topPages = [...input.pages].sort(
|
|
208
|
+
(left, right) => right.graphPage.confidence - left.graphPage.confidence || right.graphPage.relatedPageIds.length - left.graphPage.relatedPageIds.length || left.graphPage.path.localeCompare(right.graphPage.path)
|
|
209
|
+
).slice(0, 80);
|
|
210
|
+
const communities = (input.graph.communities ?? []).slice(0, 20).map((community) => `- ${community.label} (${community.nodeIds.length} nodes)`).join("\n");
|
|
211
|
+
return [
|
|
212
|
+
"# SwarmVault AI Index",
|
|
213
|
+
"",
|
|
214
|
+
`Generated: ${input.generatedAt}`,
|
|
215
|
+
`Vault: ${input.vaultName}`,
|
|
216
|
+
"",
|
|
217
|
+
"## Use This Export",
|
|
218
|
+
"",
|
|
219
|
+
"- Start with `ai-readme.md` for navigation guidance.",
|
|
220
|
+
"- Use `llms-full.txt` when an agent needs a bounded plain-text dump of the compiled wiki.",
|
|
221
|
+
"- Use `graph.jsonld` when an agent or crawler needs a structured page/node/relation graph.",
|
|
222
|
+
"- Use `pages/` for per-page `.txt` and `.json` siblings when `--page-siblings` is enabled.",
|
|
223
|
+
"",
|
|
224
|
+
"## Counts",
|
|
225
|
+
"",
|
|
226
|
+
`- Sources: ${input.graph.sources.length}`,
|
|
227
|
+
`- Pages: ${input.graph.pages.length}`,
|
|
228
|
+
`- Nodes: ${input.graph.nodes.length}`,
|
|
229
|
+
`- Edges: ${input.graph.edges.length}`,
|
|
230
|
+
`- Hyperedges: ${input.graph.hyperedges.length}`,
|
|
231
|
+
"",
|
|
232
|
+
"## Commands",
|
|
233
|
+
"",
|
|
234
|
+
'- `swarmvault query "question"` asks the compiled wiki.',
|
|
235
|
+
'- `swarmvault chat "question"` keeps a persisted multi-turn session.',
|
|
236
|
+
'- `swarmvault context build "goal" --target <path>` creates a token-bounded handoff.',
|
|
237
|
+
"- `swarmvault graph serve` opens the workbench and graph viewer.",
|
|
238
|
+
"- `swarmvault doctor` checks graph, retrieval, review, watch, and task state.",
|
|
239
|
+
"",
|
|
240
|
+
communities ? "## Communities" : void 0,
|
|
241
|
+
communities || void 0,
|
|
242
|
+
"",
|
|
243
|
+
"## High-Signal Pages",
|
|
244
|
+
"",
|
|
245
|
+
topPages.map(renderPageLine).join("\n\n")
|
|
246
|
+
].filter((line) => line !== void 0).join("\n").trimEnd().concat("\n");
|
|
247
|
+
}
|
|
248
|
+
function renderAiReadme(input) {
|
|
249
|
+
return [
|
|
250
|
+
"# AI Handoff Pack",
|
|
251
|
+
"",
|
|
252
|
+
`Generated: ${input.generatedAt}`,
|
|
253
|
+
`Vault: ${input.vaultName}`,
|
|
254
|
+
"",
|
|
255
|
+
"This folder is a portable, static export of the compiled SwarmVault wiki for agents, crawlers, and documentation systems.",
|
|
256
|
+
"",
|
|
257
|
+
"## Files",
|
|
258
|
+
"",
|
|
259
|
+
"- `llms.txt` - concise index with stats, command hints, communities, and high-signal pages.",
|
|
260
|
+
`- \`llms-full.txt\` - plain-text wiki dump${input.truncatedFullText ? " truncated at the requested character cap" : ""}.`,
|
|
261
|
+
"- `graph.jsonld` - structured graph with pages, nodes, sources, and relation edges.",
|
|
262
|
+
"- `manifest.json` - file list, hashes, counts, and export settings.",
|
|
263
|
+
input.pageSiblings ? "- `pages/` - per-page `.txt` and `.json` siblings for direct retrieval." : void 0,
|
|
264
|
+
"",
|
|
265
|
+
"## Suggested Agent Flow",
|
|
266
|
+
"",
|
|
267
|
+
"1. Read `llms.txt` to understand the vault shape.",
|
|
268
|
+
"2. Search `llms-full.txt` or `pages/` for the specific topic.",
|
|
269
|
+
"3. Use `graph.jsonld` to follow page, node, and relation IDs when provenance matters.",
|
|
270
|
+
"4. Ask the live vault with `swarmvault query` or continue a session with `swarmvault chat` when shell access is available."
|
|
271
|
+
].filter((line) => line !== void 0).join("\n").trimEnd().concat("\n");
|
|
272
|
+
}
|
|
273
|
+
function renderFullText(input) {
|
|
274
|
+
const chunks = [
|
|
275
|
+
"# SwarmVault Full Wiki Export",
|
|
276
|
+
"",
|
|
277
|
+
`Generated: ${input.generatedAt}`,
|
|
278
|
+
`Vault: ${input.vaultName}`,
|
|
279
|
+
"",
|
|
280
|
+
"The sections below are compiled wiki pages separated by stable path markers.",
|
|
281
|
+
""
|
|
282
|
+
];
|
|
283
|
+
let usedChars = chunks.join("\n").length;
|
|
284
|
+
let truncated = false;
|
|
285
|
+
for (const page of input.pages) {
|
|
286
|
+
const section = [
|
|
287
|
+
"",
|
|
288
|
+
`--- PAGE: wiki/${page.graphPage.path} ---`,
|
|
289
|
+
`Title: ${page.graphPage.title}`,
|
|
290
|
+
`Page ID: ${page.graphPage.id}`,
|
|
291
|
+
`Kind: ${page.graphPage.kind}`,
|
|
292
|
+
page.graphPage.sourceIds.length ? `Source IDs: ${page.graphPage.sourceIds.join(", ")}` : void 0,
|
|
293
|
+
"",
|
|
294
|
+
page.content.trim(),
|
|
295
|
+
""
|
|
296
|
+
].filter((line) => line !== void 0).join("\n");
|
|
297
|
+
if (usedChars + section.length > input.maxChars) {
|
|
298
|
+
const remaining = Math.max(0, input.maxChars - usedChars);
|
|
299
|
+
if (remaining > 0) {
|
|
300
|
+
chunks.push(section.slice(0, remaining));
|
|
301
|
+
}
|
|
302
|
+
truncated = true;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
chunks.push(section);
|
|
306
|
+
usedChars += section.length;
|
|
307
|
+
}
|
|
308
|
+
if (truncated) {
|
|
309
|
+
chunks.push("", "[truncated: increase --max-full-chars to include more pages]");
|
|
310
|
+
}
|
|
311
|
+
return { content: chunks.join("\n").trimEnd().concat("\n"), truncated };
|
|
312
|
+
}
|
|
313
|
+
function buildJsonLd(input) {
|
|
314
|
+
const graphItems = [
|
|
315
|
+
{
|
|
316
|
+
"@id": "swarmvault:vault",
|
|
317
|
+
"@type": "Dataset",
|
|
318
|
+
name: input.vaultName,
|
|
319
|
+
dateModified: input.graph.generatedAt,
|
|
320
|
+
datePublished: input.generatedAt,
|
|
321
|
+
additionalType: "SwarmVaultKnowledgeGraph"
|
|
322
|
+
},
|
|
323
|
+
...input.graph.sources.map((source) => ({
|
|
324
|
+
"@id": `swarmvault:source:${encodeURIComponent(source.sourceId)}`,
|
|
325
|
+
"@type": "CreativeWork",
|
|
326
|
+
name: source.title,
|
|
327
|
+
encodingFormat: source.mimeType,
|
|
328
|
+
dateCreated: source.createdAt,
|
|
329
|
+
dateModified: source.updatedAt,
|
|
330
|
+
url: source.url,
|
|
331
|
+
fileFormat: source.sourceKind,
|
|
332
|
+
identifier: source.sourceId
|
|
333
|
+
})),
|
|
334
|
+
...input.graph.pages.map((page) => ({
|
|
335
|
+
"@id": graphPathForPage(page.id),
|
|
336
|
+
"@type": "CreativeWork",
|
|
337
|
+
name: page.title,
|
|
338
|
+
identifier: page.id,
|
|
339
|
+
url: `wiki/${page.path}`,
|
|
340
|
+
genre: page.kind,
|
|
341
|
+
dateCreated: page.createdAt,
|
|
342
|
+
dateModified: page.updatedAt,
|
|
343
|
+
about: page.nodeIds.map((nodeId) => ({ "@id": graphPathForNode(nodeId) })),
|
|
344
|
+
citation: page.sourceIds.map((sourceId) => ({ "@id": `swarmvault:source:${encodeURIComponent(sourceId)}` })),
|
|
345
|
+
isPartOf: { "@id": "swarmvault:vault" }
|
|
346
|
+
})),
|
|
347
|
+
...input.graph.nodes.map((node) => ({
|
|
348
|
+
"@id": graphPathForNode(node.id),
|
|
349
|
+
"@type": "Thing",
|
|
350
|
+
name: node.label,
|
|
351
|
+
identifier: node.id,
|
|
352
|
+
additionalType: node.type,
|
|
353
|
+
isPartOf: node.pageId ? { "@id": graphPathForPage(node.pageId) } : { "@id": "swarmvault:vault" },
|
|
354
|
+
sameAs: node.sourceIds.map((sourceId) => `swarmvault:source:${encodeURIComponent(sourceId)}`)
|
|
355
|
+
})),
|
|
356
|
+
...input.graph.edges.map((edge) => ({
|
|
357
|
+
"@id": `swarmvault:edge:${encodeURIComponent(edge.id)}`,
|
|
358
|
+
"@type": "swarmvault:Relation",
|
|
359
|
+
name: edge.relation,
|
|
360
|
+
identifier: edge.id,
|
|
361
|
+
"swarmvault:source": { "@id": graphPathForNode(edge.source) },
|
|
362
|
+
"swarmvault:target": { "@id": graphPathForNode(edge.target) },
|
|
363
|
+
"swarmvault:evidenceClass": edge.evidenceClass,
|
|
364
|
+
"swarmvault:confidence": edge.confidence,
|
|
365
|
+
"swarmvault:provenance": edge.provenance
|
|
366
|
+
}))
|
|
367
|
+
];
|
|
368
|
+
return {
|
|
369
|
+
"@context": {
|
|
370
|
+
"@vocab": "https://schema.org/",
|
|
371
|
+
swarmvault: "https://www.swarmvault.ai/ns#"
|
|
372
|
+
},
|
|
373
|
+
"@graph": graphItems
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
async function loadPages(rootDir, wikiDir, graph) {
|
|
377
|
+
const pages = [];
|
|
378
|
+
const sortedPages = [...graph.pages].sort((left, right) => left.path.localeCompare(right.path));
|
|
379
|
+
for (const graphPage of sortedPages) {
|
|
380
|
+
const absolutePath = path.join(wikiDir, graphPage.path);
|
|
381
|
+
if (!await fileExists(absolutePath)) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const content = await fs.readFile(absolutePath, "utf8");
|
|
385
|
+
const parsed = matter(content);
|
|
386
|
+
pages.push({
|
|
387
|
+
graphPage,
|
|
388
|
+
absolutePath: path.resolve(rootDir, absolutePath),
|
|
389
|
+
content,
|
|
390
|
+
body: parsed.content,
|
|
391
|
+
data: parsed.data
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
return pages;
|
|
395
|
+
}
|
|
396
|
+
async function exportAiPack(rootDir, options = {}) {
|
|
397
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
398
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
399
|
+
if (!graph) {
|
|
400
|
+
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
401
|
+
}
|
|
402
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
403
|
+
const vaultName = path.basename(paths.rootDir);
|
|
404
|
+
const outputDir = path.resolve(rootDir, options.outDir ?? path.join("wiki", "exports", "ai"));
|
|
405
|
+
const maxFullChars = Math.max(1e3, options.maxFullChars ?? DEFAULT_MAX_FULL_CHARS);
|
|
406
|
+
const pageSiblings = options.pageSiblings ?? true;
|
|
407
|
+
await ensureDir(outputDir);
|
|
408
|
+
const pages = await loadPages(rootDir, paths.wikiDir, graph);
|
|
409
|
+
const files = [];
|
|
410
|
+
const fullText = renderFullText({ generatedAt, vaultName, pages, maxChars: maxFullChars });
|
|
411
|
+
await writeTrackedText(
|
|
412
|
+
files,
|
|
413
|
+
outputDir,
|
|
414
|
+
"index",
|
|
415
|
+
path.join(outputDir, "llms.txt"),
|
|
416
|
+
renderLlmsIndex({ generatedAt, vaultName, graph, pages })
|
|
417
|
+
);
|
|
418
|
+
await writeTrackedText(files, outputDir, "full-text", path.join(outputDir, "llms-full.txt"), fullText.content);
|
|
419
|
+
await writeTrackedJson(
|
|
420
|
+
files,
|
|
421
|
+
outputDir,
|
|
422
|
+
"graph-jsonld",
|
|
423
|
+
path.join(outputDir, "graph.jsonld"),
|
|
424
|
+
buildJsonLd({ generatedAt, vaultName, graph })
|
|
425
|
+
);
|
|
426
|
+
await writeTrackedText(
|
|
427
|
+
files,
|
|
428
|
+
outputDir,
|
|
429
|
+
"readme",
|
|
430
|
+
path.join(outputDir, "ai-readme.md"),
|
|
431
|
+
renderAiReadme({ generatedAt, vaultName, truncatedFullText: fullText.truncated, pageSiblings })
|
|
432
|
+
);
|
|
433
|
+
if (pageSiblings) {
|
|
434
|
+
for (const page of pages) {
|
|
435
|
+
const siblingBase = path.join(outputDir, "pages", page.graphPage.path.replace(/\.md$/u, ""));
|
|
436
|
+
await writeTrackedText(files, outputDir, "page-text", `${siblingBase}.txt`, page.body.trim().concat("\n"));
|
|
437
|
+
await writeTrackedJson(files, outputDir, "page-json", `${siblingBase}.json`, {
|
|
438
|
+
title: page.graphPage.title,
|
|
439
|
+
path: page.graphPage.path,
|
|
440
|
+
pageId: page.graphPage.id,
|
|
441
|
+
kind: page.graphPage.kind,
|
|
442
|
+
freshness: page.graphPage.freshness,
|
|
443
|
+
confidence: page.graphPage.confidence,
|
|
444
|
+
sourceIds: page.graphPage.sourceIds,
|
|
445
|
+
nodeIds: page.graphPage.nodeIds,
|
|
446
|
+
relatedPageIds: page.graphPage.relatedPageIds,
|
|
447
|
+
relatedNodeIds: page.graphPage.relatedNodeIds,
|
|
448
|
+
frontmatter: page.data,
|
|
449
|
+
body: page.body.trim(),
|
|
450
|
+
contentSha256: sha256(page.content)
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const result = {
|
|
455
|
+
outputDir,
|
|
456
|
+
generatedAt,
|
|
457
|
+
pageCount: graph.pages.length,
|
|
458
|
+
sourceCount: graph.sources.length,
|
|
459
|
+
nodeCount: graph.nodes.length,
|
|
460
|
+
edgeCount: graph.edges.length,
|
|
461
|
+
truncatedFullText: fullText.truncated,
|
|
462
|
+
files
|
|
463
|
+
};
|
|
464
|
+
const manifestPath = path.join(outputDir, "manifest.json");
|
|
465
|
+
await writeJsonFile(manifestPath, {
|
|
466
|
+
...result,
|
|
467
|
+
files: [...files].sort((left, right) => left.path.localeCompare(right.path))
|
|
468
|
+
});
|
|
469
|
+
const manifestContent = await fs.readFile(manifestPath, "utf8");
|
|
470
|
+
files.push({
|
|
471
|
+
kind: "manifest",
|
|
472
|
+
path: relativeOutputPath(outputDir, manifestPath),
|
|
473
|
+
bytes: Buffer.byteLength(manifestContent),
|
|
474
|
+
sha256: sha256(manifestContent)
|
|
475
|
+
});
|
|
476
|
+
return {
|
|
477
|
+
...result,
|
|
478
|
+
files: [...files].sort((left, right) => left.path.localeCompare(right.path))
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
164
482
|
// src/auto-commit.ts
|
|
165
483
|
import { execFile } from "child_process";
|
|
166
484
|
import { promisify } from "util";
|
|
@@ -198,25 +516,235 @@ async function autoCommitWikiChanges(rootDir, operation, detail, options) {
|
|
|
198
516
|
return message;
|
|
199
517
|
}
|
|
200
518
|
|
|
519
|
+
// src/chat.ts
|
|
520
|
+
import fs2 from "fs/promises";
|
|
521
|
+
import path2 from "path";
|
|
522
|
+
import matter2 from "gray-matter";
|
|
523
|
+
var DEFAULT_HISTORY_TURNS = 6;
|
|
524
|
+
function timestampIdPrefix() {
|
|
525
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/u, "Z").replace("T", "-");
|
|
526
|
+
}
|
|
527
|
+
function chatDirs(paths) {
|
|
528
|
+
return {
|
|
529
|
+
stateDir: path2.join(paths.stateDir, "chat-sessions"),
|
|
530
|
+
wikiDir: path2.join(paths.wikiDir, "outputs", "chat-sessions")
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function sessionStatePath(stateDir, id) {
|
|
534
|
+
return path2.join(stateDir, `${id}.json`);
|
|
535
|
+
}
|
|
536
|
+
function sessionMarkdownPath(wikiDir, id) {
|
|
537
|
+
return path2.join(wikiDir, `${id}.md`);
|
|
538
|
+
}
|
|
539
|
+
function summarizeSession(session) {
|
|
540
|
+
return {
|
|
541
|
+
id: session.id,
|
|
542
|
+
title: session.title,
|
|
543
|
+
createdAt: session.createdAt,
|
|
544
|
+
updatedAt: session.updatedAt,
|
|
545
|
+
turnCount: session.turns.length,
|
|
546
|
+
markdownPath: session.markdownPath
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function renderSessionMarkdown(session) {
|
|
550
|
+
const body = [
|
|
551
|
+
`# ${session.title}`,
|
|
552
|
+
"",
|
|
553
|
+
`Session ID: \`${session.id}\``,
|
|
554
|
+
`Updated: ${session.updatedAt}`,
|
|
555
|
+
"",
|
|
556
|
+
...session.turns.flatMap((turn, index) => [
|
|
557
|
+
`## Turn ${index + 1} - ${turn.createdAt}`,
|
|
558
|
+
"",
|
|
559
|
+
"### Question",
|
|
560
|
+
"",
|
|
561
|
+
turn.question,
|
|
562
|
+
"",
|
|
563
|
+
"### Answer",
|
|
564
|
+
"",
|
|
565
|
+
turn.answer,
|
|
566
|
+
"",
|
|
567
|
+
turn.citations.length ? "### Citations" : void 0,
|
|
568
|
+
turn.citations.length ? "" : void 0,
|
|
569
|
+
...turn.citations.map((citation) => `- ${citation}`),
|
|
570
|
+
turn.savedPath ? "" : void 0,
|
|
571
|
+
turn.savedPath ? `Saved output: \`${turn.savedPath}\`` : void 0,
|
|
572
|
+
""
|
|
573
|
+
])
|
|
574
|
+
].filter((line) => line !== void 0).join("\n");
|
|
575
|
+
return matter2.stringify(
|
|
576
|
+
body,
|
|
577
|
+
safeFrontmatter({
|
|
578
|
+
session_id: session.id,
|
|
579
|
+
title: session.title,
|
|
580
|
+
created_at: session.createdAt,
|
|
581
|
+
updated_at: session.updatedAt,
|
|
582
|
+
turn_count: session.turns.length,
|
|
583
|
+
page_id: `chat:${session.id}`,
|
|
584
|
+
freshness: "fresh",
|
|
585
|
+
node_ids: [],
|
|
586
|
+
source_ids: [],
|
|
587
|
+
source_hashes: {}
|
|
588
|
+
})
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
async function persistSession(session, stateDir, wikiDir) {
|
|
592
|
+
await ensureDir(stateDir);
|
|
593
|
+
await ensureDir(wikiDir);
|
|
594
|
+
const statePath = sessionStatePath(stateDir, session.id);
|
|
595
|
+
const markdownPath = sessionMarkdownPath(wikiDir, session.id);
|
|
596
|
+
const persisted = { ...session, markdownPath };
|
|
597
|
+
await writeJsonFile(statePath, persisted);
|
|
598
|
+
await fs2.writeFile(markdownPath, renderSessionMarkdown(persisted), "utf8");
|
|
599
|
+
return { statePath, markdownPath };
|
|
600
|
+
}
|
|
601
|
+
async function resolveSessionId(stateDir, idOrPrefix) {
|
|
602
|
+
const direct = sessionStatePath(stateDir, idOrPrefix);
|
|
603
|
+
if (await fileExists(direct)) {
|
|
604
|
+
return idOrPrefix;
|
|
605
|
+
}
|
|
606
|
+
const entries = await fs2.readdir(stateDir).catch(() => []);
|
|
607
|
+
const matches = entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).filter((id) => id.startsWith(idOrPrefix));
|
|
608
|
+
if (matches.length === 1) {
|
|
609
|
+
return matches[0];
|
|
610
|
+
}
|
|
611
|
+
if (matches.length > 1) {
|
|
612
|
+
throw new Error(`Chat session prefix "${idOrPrefix}" is ambiguous: ${matches.slice(0, 8).join(", ")}`);
|
|
613
|
+
}
|
|
614
|
+
throw new Error(`Chat session not found: ${idOrPrefix}`);
|
|
615
|
+
}
|
|
616
|
+
async function loadSession(stateDir, idOrPrefix) {
|
|
617
|
+
const id = await resolveSessionId(stateDir, idOrPrefix);
|
|
618
|
+
const session = await readJsonFile(sessionStatePath(stateDir, id));
|
|
619
|
+
if (!session) {
|
|
620
|
+
throw new Error(`Chat session not found: ${id}`);
|
|
621
|
+
}
|
|
622
|
+
return session;
|
|
623
|
+
}
|
|
624
|
+
function createSession(rootDir, wikiDir, options, now) {
|
|
625
|
+
const title = truncate(options.title?.trim() || normalizeWhitespace(options.question), 80);
|
|
626
|
+
const id = `${timestampIdPrefix()}-${slugify(title)}`;
|
|
627
|
+
return {
|
|
628
|
+
id,
|
|
629
|
+
title,
|
|
630
|
+
createdAt: now,
|
|
631
|
+
updatedAt: now,
|
|
632
|
+
rootDir,
|
|
633
|
+
markdownPath: sessionMarkdownPath(wikiDir, id),
|
|
634
|
+
turns: []
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function buildPrompt(session, question, maxHistoryTurns) {
|
|
638
|
+
const recentTurns = session.turns.slice(-maxHistoryTurns);
|
|
639
|
+
if (!recentTurns.length) {
|
|
640
|
+
return question;
|
|
641
|
+
}
|
|
642
|
+
const history = recentTurns.map(
|
|
643
|
+
(turn, index) => [
|
|
644
|
+
`Turn ${index + 1}`,
|
|
645
|
+
`User: ${turn.question}`,
|
|
646
|
+
`Assistant: ${truncate(normalizeWhitespace(turn.answer), 1200)}`,
|
|
647
|
+
turn.citations.length ? `Citations: ${turn.citations.join(", ")}` : void 0
|
|
648
|
+
].filter((line) => line !== void 0).join("\n")
|
|
649
|
+
).join("\n\n");
|
|
650
|
+
return [
|
|
651
|
+
"Continue this SwarmVault chat session using the compiled wiki as the source of truth.",
|
|
652
|
+
"Use prior turns only for conversational continuity. Prefer current vault evidence over prior wording.",
|
|
653
|
+
"",
|
|
654
|
+
"Prior turns:",
|
|
655
|
+
history,
|
|
656
|
+
"",
|
|
657
|
+
"Current question:",
|
|
658
|
+
question
|
|
659
|
+
].join("\n");
|
|
660
|
+
}
|
|
661
|
+
async function listChatSessions(rootDir) {
|
|
662
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
663
|
+
const { stateDir } = chatDirs(paths);
|
|
664
|
+
const entries = await fs2.readdir(stateDir).catch(() => []);
|
|
665
|
+
const sessions = await Promise.all(
|
|
666
|
+
entries.filter((entry) => entry.endsWith(".json")).map(async (entry) => readJsonFile(path2.join(stateDir, entry)))
|
|
667
|
+
);
|
|
668
|
+
return sessions.filter((session) => Boolean(session)).map(summarizeSession).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
669
|
+
}
|
|
670
|
+
async function readChatSession(rootDir, idOrPrefix) {
|
|
671
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
672
|
+
const { stateDir } = chatDirs(paths);
|
|
673
|
+
return loadSession(stateDir, idOrPrefix);
|
|
674
|
+
}
|
|
675
|
+
async function deleteChatSession(rootDir, idOrPrefix) {
|
|
676
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
677
|
+
const { stateDir, wikiDir } = chatDirs(paths);
|
|
678
|
+
const session = await loadSession(stateDir, idOrPrefix);
|
|
679
|
+
await fs2.rm(sessionStatePath(stateDir, session.id), { force: true });
|
|
680
|
+
await fs2.rm(sessionMarkdownPath(wikiDir, session.id), { force: true });
|
|
681
|
+
return summarizeSession(session);
|
|
682
|
+
}
|
|
683
|
+
async function askChatSession(rootDir, options) {
|
|
684
|
+
const question = normalizeWhitespace(options.question);
|
|
685
|
+
if (!question) {
|
|
686
|
+
throw new Error("Chat question is required.");
|
|
687
|
+
}
|
|
688
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
689
|
+
const { stateDir, wikiDir } = chatDirs(paths);
|
|
690
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
691
|
+
const resumed = Boolean(options.sessionId);
|
|
692
|
+
const session = options.sessionId ? await loadSession(stateDir, options.sessionId) : createSession(paths.rootDir, wikiDir, options, now);
|
|
693
|
+
const prompt = buildPrompt(session, question, Math.max(0, options.maxHistoryTurns ?? DEFAULT_HISTORY_TURNS));
|
|
694
|
+
const query = await queryVault(paths.rootDir, {
|
|
695
|
+
question: prompt,
|
|
696
|
+
save: options.saveOutput ?? false,
|
|
697
|
+
format: options.format,
|
|
698
|
+
gapFill: options.gapFill
|
|
699
|
+
});
|
|
700
|
+
const turn = {
|
|
701
|
+
id: `${session.turns.length + 1}`,
|
|
702
|
+
createdAt: now,
|
|
703
|
+
question,
|
|
704
|
+
answer: query.answer,
|
|
705
|
+
citations: query.citations,
|
|
706
|
+
relatedPageIds: query.relatedPageIds,
|
|
707
|
+
relatedNodeIds: query.relatedNodeIds,
|
|
708
|
+
relatedSourceIds: query.relatedSourceIds,
|
|
709
|
+
outputFormat: query.outputFormat,
|
|
710
|
+
savedPath: query.savedPath
|
|
711
|
+
};
|
|
712
|
+
const updatedSession = {
|
|
713
|
+
...session,
|
|
714
|
+
updatedAt: now,
|
|
715
|
+
markdownPath: sessionMarkdownPath(wikiDir, session.id),
|
|
716
|
+
turns: [...session.turns, turn]
|
|
717
|
+
};
|
|
718
|
+
const persisted = await persistSession(updatedSession, stateDir, wikiDir);
|
|
719
|
+
return {
|
|
720
|
+
session: { ...updatedSession, markdownPath: persisted.markdownPath },
|
|
721
|
+
turn,
|
|
722
|
+
answer: query.answer,
|
|
723
|
+
markdownPath: persisted.markdownPath,
|
|
724
|
+
statePath: persisted.statePath,
|
|
725
|
+
resumed
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
201
729
|
// src/doctor.ts
|
|
202
|
-
import
|
|
203
|
-
import
|
|
730
|
+
import fs5 from "fs/promises";
|
|
731
|
+
import path5 from "path";
|
|
204
732
|
|
|
205
733
|
// src/migrate.ts
|
|
206
|
-
import
|
|
207
|
-
import
|
|
208
|
-
import
|
|
734
|
+
import fs3 from "fs/promises";
|
|
735
|
+
import path3 from "path";
|
|
736
|
+
import matter3 from "gray-matter";
|
|
209
737
|
var VAULT_VERSION_FILENAME = "vault-version.json";
|
|
210
738
|
async function walkMarkdownFiles(dir) {
|
|
211
739
|
const results = [];
|
|
212
740
|
let entries;
|
|
213
741
|
try {
|
|
214
|
-
entries = await
|
|
742
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
215
743
|
} catch {
|
|
216
744
|
return results;
|
|
217
745
|
}
|
|
218
746
|
for (const entry of entries) {
|
|
219
|
-
const full =
|
|
747
|
+
const full = path3.join(dir, entry.name);
|
|
220
748
|
if (entry.isDirectory()) {
|
|
221
749
|
results.push(...await walkMarkdownFiles(full));
|
|
222
750
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -227,8 +755,8 @@ async function walkMarkdownFiles(dir) {
|
|
|
227
755
|
}
|
|
228
756
|
async function readFrontmatterFile(filePath) {
|
|
229
757
|
try {
|
|
230
|
-
const raw = await
|
|
231
|
-
const parsed =
|
|
758
|
+
const raw = await fs3.readFile(filePath, "utf8");
|
|
759
|
+
const parsed = matter3(raw);
|
|
232
760
|
const data = parsed.data ?? {};
|
|
233
761
|
return { data, content: parsed.content };
|
|
234
762
|
} catch {
|
|
@@ -236,10 +764,10 @@ async function readFrontmatterFile(filePath) {
|
|
|
236
764
|
}
|
|
237
765
|
}
|
|
238
766
|
async function writeFrontmatterFile(filePath, data, content) {
|
|
239
|
-
await
|
|
767
|
+
await fs3.writeFile(filePath, matter3.stringify(content, data), "utf8");
|
|
240
768
|
}
|
|
241
769
|
function relFromRoot(rootDir, filePath) {
|
|
242
|
-
return
|
|
770
|
+
return path3.relative(rootDir, filePath) || filePath;
|
|
243
771
|
}
|
|
244
772
|
var MIGRATION_ADD_DECAY_FIELDS = {
|
|
245
773
|
id: "0.9-to-0.10-add-decay-fields",
|
|
@@ -280,7 +808,7 @@ var MIGRATION_ADD_TIER_DEFAULT = {
|
|
|
280
808
|
description: 'Tag insight pages with tier: "working" when the field is absent.',
|
|
281
809
|
async apply(ctx, options) {
|
|
282
810
|
const changed = [];
|
|
283
|
-
const insightsDir =
|
|
811
|
+
const insightsDir = path3.join(ctx.paths.wikiDir, "insights");
|
|
284
812
|
const files = await walkMarkdownFiles(insightsDir);
|
|
285
813
|
for (const filePath of files) {
|
|
286
814
|
const parsed = await readFrontmatterFile(filePath);
|
|
@@ -337,11 +865,11 @@ var MIGRATION_REBUILD_SEARCH_INDEX = {
|
|
|
337
865
|
description: "Mark state/search.sqlite as stale so the next compile regenerates it.",
|
|
338
866
|
async apply(ctx, options) {
|
|
339
867
|
const changed = [];
|
|
340
|
-
const searchPath =
|
|
868
|
+
const searchPath = path3.join(ctx.paths.stateDir, "search.sqlite");
|
|
341
869
|
try {
|
|
342
|
-
await
|
|
870
|
+
await fs3.access(searchPath);
|
|
343
871
|
if (!options.dryRun) {
|
|
344
|
-
await
|
|
872
|
+
await fs3.rm(searchPath, { force: true });
|
|
345
873
|
}
|
|
346
874
|
changed.push(relFromRoot(ctx.rootDir, searchPath));
|
|
347
875
|
} catch {
|
|
@@ -358,8 +886,8 @@ var MIGRATION_ADD_MEMORY_LEDGER = {
|
|
|
358
886
|
if (options.dryRun) {
|
|
359
887
|
return {
|
|
360
888
|
changed: [
|
|
361
|
-
relFromRoot(ctx.rootDir,
|
|
362
|
-
relFromRoot(ctx.rootDir,
|
|
889
|
+
relFromRoot(ctx.rootDir, path3.join(ctx.paths.stateDir, "memory", "tasks")),
|
|
890
|
+
relFromRoot(ctx.rootDir, path3.join(ctx.paths.wikiDir, "memory", "index.md"))
|
|
363
891
|
]
|
|
364
892
|
};
|
|
365
893
|
}
|
|
@@ -373,9 +901,9 @@ var MIGRATION_3_0_RETRIEVAL_AND_TASKS = {
|
|
|
373
901
|
description: "Move search config into retrieval, create state/retrieval, remove the legacy search index, and add task aliases to memory frontmatter.",
|
|
374
902
|
async apply(ctx, options) {
|
|
375
903
|
const changed = [];
|
|
376
|
-
const configPath =
|
|
904
|
+
const configPath = path3.join(ctx.rootDir, "swarmvault.config.json");
|
|
377
905
|
try {
|
|
378
|
-
const raw = await
|
|
906
|
+
const raw = await fs3.readFile(configPath, "utf8");
|
|
379
907
|
const config = JSON.parse(raw);
|
|
380
908
|
let mutated = false;
|
|
381
909
|
if (config.search) {
|
|
@@ -401,28 +929,28 @@ var MIGRATION_3_0_RETRIEVAL_AND_TASKS = {
|
|
|
401
929
|
}
|
|
402
930
|
if (mutated) {
|
|
403
931
|
if (!options.dryRun) {
|
|
404
|
-
await
|
|
932
|
+
await fs3.writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
405
933
|
`, "utf8");
|
|
406
934
|
}
|
|
407
935
|
changed.push(relFromRoot(ctx.rootDir, configPath));
|
|
408
936
|
}
|
|
409
937
|
} catch {
|
|
410
938
|
}
|
|
411
|
-
const retrievalDir =
|
|
939
|
+
const retrievalDir = path3.join(ctx.paths.stateDir, "retrieval");
|
|
412
940
|
if (!options.dryRun) {
|
|
413
941
|
await ensureDir(retrievalDir);
|
|
414
942
|
}
|
|
415
943
|
changed.push(relFromRoot(ctx.rootDir, retrievalDir));
|
|
416
|
-
const legacySearchPath =
|
|
944
|
+
const legacySearchPath = path3.join(ctx.paths.stateDir, "search.sqlite");
|
|
417
945
|
try {
|
|
418
|
-
await
|
|
946
|
+
await fs3.access(legacySearchPath);
|
|
419
947
|
if (!options.dryRun) {
|
|
420
|
-
await
|
|
948
|
+
await fs3.rm(legacySearchPath, { force: true });
|
|
421
949
|
}
|
|
422
950
|
changed.push(relFromRoot(ctx.rootDir, legacySearchPath));
|
|
423
951
|
} catch {
|
|
424
952
|
}
|
|
425
|
-
const memoryTaskDir =
|
|
953
|
+
const memoryTaskDir = path3.join(ctx.paths.wikiDir, "memory", "tasks");
|
|
426
954
|
const files = await walkMarkdownFiles(memoryTaskDir);
|
|
427
955
|
for (const filePath of files) {
|
|
428
956
|
const parsed = await readFrontmatterFile(filePath);
|
|
@@ -471,9 +999,9 @@ function compareSemver(a, b) {
|
|
|
471
999
|
return 0;
|
|
472
1000
|
}
|
|
473
1001
|
async function readVaultVersionRecord(stateDir) {
|
|
474
|
-
const filePath =
|
|
1002
|
+
const filePath = path3.join(stateDir, VAULT_VERSION_FILENAME);
|
|
475
1003
|
try {
|
|
476
|
-
const raw = await
|
|
1004
|
+
const raw = await fs3.readFile(filePath, "utf8");
|
|
477
1005
|
const parsed = JSON.parse(raw);
|
|
478
1006
|
if (typeof parsed.version === "string") return parsed;
|
|
479
1007
|
return null;
|
|
@@ -482,9 +1010,9 @@ async function readVaultVersionRecord(stateDir) {
|
|
|
482
1010
|
}
|
|
483
1011
|
}
|
|
484
1012
|
async function readGraphVersion(stateDir) {
|
|
485
|
-
const filePath =
|
|
1013
|
+
const filePath = path3.join(stateDir, "graph.json");
|
|
486
1014
|
try {
|
|
487
|
-
const raw = await
|
|
1015
|
+
const raw = await fs3.readFile(filePath, "utf8");
|
|
488
1016
|
const parsed = JSON.parse(raw);
|
|
489
1017
|
const version = parsed?.generatedBy?.version;
|
|
490
1018
|
return typeof version === "string" ? version : null;
|
|
@@ -502,7 +1030,7 @@ async function detectVaultVersion(rootDir) {
|
|
|
502
1030
|
}
|
|
503
1031
|
async function currentPackageVersion() {
|
|
504
1032
|
try {
|
|
505
|
-
const raw = await
|
|
1033
|
+
const raw = await fs3.readFile(new URL("../package.json", import.meta.url), "utf8");
|
|
506
1034
|
const parsed = JSON.parse(raw);
|
|
507
1035
|
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version : "0.0.0";
|
|
508
1036
|
} catch {
|
|
@@ -519,8 +1047,8 @@ async function planMigration(rootDir, targetVersion) {
|
|
|
519
1047
|
return { fromVersion, toVersion, steps };
|
|
520
1048
|
}
|
|
521
1049
|
async function writeVaultVersionRecord(stateDir, record) {
|
|
522
|
-
await
|
|
523
|
-
await
|
|
1050
|
+
await fs3.mkdir(stateDir, { recursive: true });
|
|
1051
|
+
await fs3.writeFile(path3.join(stateDir, VAULT_VERSION_FILENAME), `${JSON.stringify(record, null, 2)}
|
|
524
1052
|
`, "utf8");
|
|
525
1053
|
}
|
|
526
1054
|
async function runMigration(rootDir, options = {}) {
|
|
@@ -559,8 +1087,8 @@ async function runMigration(rootDir, options = {}) {
|
|
|
559
1087
|
}
|
|
560
1088
|
|
|
561
1089
|
// src/watch.ts
|
|
562
|
-
import
|
|
563
|
-
import
|
|
1090
|
+
import fs4 from "fs/promises";
|
|
1091
|
+
import path4 from "path";
|
|
564
1092
|
import process2 from "process";
|
|
565
1093
|
import chokidar from "chokidar";
|
|
566
1094
|
var MAX_BACKOFF_MS = 3e4;
|
|
@@ -620,7 +1148,7 @@ function isCodeOnlyChange(reasons) {
|
|
|
620
1148
|
for (const reason of reasons) {
|
|
621
1149
|
const match = reason.match(FILE_CHANGE_RE);
|
|
622
1150
|
if (!match) return false;
|
|
623
|
-
const ext =
|
|
1151
|
+
const ext = path4.extname(match[1]).toLowerCase();
|
|
624
1152
|
if (!ext || !CODE_EXTENSIONS.has(ext)) return false;
|
|
625
1153
|
}
|
|
626
1154
|
return reasons.size > 0;
|
|
@@ -629,7 +1157,7 @@ function hasNonCodeChanges(reasons) {
|
|
|
629
1157
|
for (const reason of reasons) {
|
|
630
1158
|
const match = reason.match(FILE_CHANGE_RE);
|
|
631
1159
|
if (!match) return true;
|
|
632
|
-
const ext =
|
|
1160
|
+
const ext = path4.extname(match[1]).toLowerCase();
|
|
633
1161
|
if (!ext || !CODE_EXTENSIONS.has(ext)) return true;
|
|
634
1162
|
}
|
|
635
1163
|
return false;
|
|
@@ -642,7 +1170,7 @@ function collectNonCodePaths(reasons) {
|
|
|
642
1170
|
result.push(reason);
|
|
643
1171
|
continue;
|
|
644
1172
|
}
|
|
645
|
-
const ext =
|
|
1173
|
+
const ext = path4.extname(match[1]).toLowerCase();
|
|
646
1174
|
if (!ext || !CODE_EXTENSIONS.has(ext)) result.push(match[1]);
|
|
647
1175
|
}
|
|
648
1176
|
return result;
|
|
@@ -704,11 +1232,11 @@ async function predictRemovedSourceIdsFromRepoSync(rootDir, options) {
|
|
|
704
1232
|
];
|
|
705
1233
|
}
|
|
706
1234
|
function hasIgnoredRepoSegment(baseDir, targetPath) {
|
|
707
|
-
const relativePath =
|
|
1235
|
+
const relativePath = path4.relative(baseDir, targetPath);
|
|
708
1236
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
709
1237
|
return false;
|
|
710
1238
|
}
|
|
711
|
-
return relativePath.split(
|
|
1239
|
+
return relativePath.split(path4.sep).some((segment) => REPO_WATCH_IGNORES.has(segment));
|
|
712
1240
|
}
|
|
713
1241
|
function workspaceIgnoreRoots(rootDir, paths) {
|
|
714
1242
|
return [
|
|
@@ -717,22 +1245,22 @@ function workspaceIgnoreRoots(rootDir, paths) {
|
|
|
717
1245
|
paths.stateDir,
|
|
718
1246
|
paths.agentDir,
|
|
719
1247
|
paths.inboxDir,
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
].map((candidate) =>
|
|
1248
|
+
path4.join(rootDir, ".claude"),
|
|
1249
|
+
path4.join(rootDir, ".cursor"),
|
|
1250
|
+
path4.join(rootDir, ".obsidian")
|
|
1251
|
+
].map((candidate) => path4.resolve(candidate));
|
|
724
1252
|
}
|
|
725
1253
|
async function resolveWatchTargets(rootDir, paths, options) {
|
|
726
|
-
const targets = /* @__PURE__ */ new Set([
|
|
1254
|
+
const targets = /* @__PURE__ */ new Set([path4.resolve(paths.inboxDir)]);
|
|
727
1255
|
if (options.repo) {
|
|
728
1256
|
for (const repoRoot of await resolveWatchedRepoRoots(rootDir, { overrideRoots: options.overrideRoots })) {
|
|
729
|
-
targets.add(
|
|
1257
|
+
targets.add(path4.resolve(repoRoot));
|
|
730
1258
|
}
|
|
731
1259
|
}
|
|
732
1260
|
return [...targets].sort((left, right) => left.localeCompare(right));
|
|
733
1261
|
}
|
|
734
1262
|
function resolveRootRelative(rootDir, candidate) {
|
|
735
|
-
return
|
|
1263
|
+
return path4.isAbsolute(candidate) ? path4.resolve(candidate) : path4.resolve(rootDir, candidate);
|
|
736
1264
|
}
|
|
737
1265
|
async function resolveWatchedRepoRoots(rootDir, options = {}) {
|
|
738
1266
|
const override = options.overrideRoots?.filter(Boolean) ?? [];
|
|
@@ -746,17 +1274,17 @@ async function resolveWatchedRepoRoots(rootDir, options = {}) {
|
|
|
746
1274
|
const excluded = new Set(
|
|
747
1275
|
(watchConfig.excludeRepoRoots ?? []).filter(Boolean).map((candidate) => resolveRootRelative(rootDir, candidate))
|
|
748
1276
|
);
|
|
749
|
-
return dedupeSorted(baseRoots.filter((candidate) => !excluded.has(
|
|
1277
|
+
return dedupeSorted(baseRoots.filter((candidate) => !excluded.has(path4.resolve(candidate))));
|
|
750
1278
|
}
|
|
751
1279
|
async function listWatchedRoots(rootDir, options = {}) {
|
|
752
1280
|
return resolveWatchedRepoRoots(rootDir, options);
|
|
753
1281
|
}
|
|
754
1282
|
function dedupeSorted(values) {
|
|
755
|
-
return [...new Set(values.map((value) =>
|
|
1283
|
+
return [...new Set(values.map((value) => path4.resolve(value)))].sort((left, right) => left.localeCompare(right));
|
|
756
1284
|
}
|
|
757
1285
|
async function readConfigJson(rootDir) {
|
|
758
|
-
const configPath =
|
|
759
|
-
const raw = await
|
|
1286
|
+
const configPath = path4.join(rootDir, "swarmvault.config.json");
|
|
1287
|
+
const raw = await fs4.readFile(configPath, "utf8");
|
|
760
1288
|
const parsed = JSON.parse(raw);
|
|
761
1289
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
762
1290
|
throw new Error("swarmvault.config.json must contain a JSON object.");
|
|
@@ -764,7 +1292,7 @@ async function readConfigJson(rootDir) {
|
|
|
764
1292
|
return { path: configPath, content: parsed };
|
|
765
1293
|
}
|
|
766
1294
|
async function writeConfigJson(configPath, content) {
|
|
767
|
-
await
|
|
1295
|
+
await fs4.writeFile(configPath, `${JSON.stringify(content, null, 2)}
|
|
768
1296
|
`, "utf8");
|
|
769
1297
|
}
|
|
770
1298
|
function getWatchBlock(config) {
|
|
@@ -948,7 +1476,7 @@ async function watchVault(rootDir, options = {}) {
|
|
|
948
1476
|
const { paths } = await initWorkspace(rootDir);
|
|
949
1477
|
const baseDebounceMs = options.debounceMs ?? 900;
|
|
950
1478
|
const ignoredRoots = workspaceIgnoreRoots(rootDir, paths);
|
|
951
|
-
const inboxWatchRoot =
|
|
1479
|
+
const inboxWatchRoot = path4.resolve(paths.inboxDir);
|
|
952
1480
|
let watchTargets = await resolveWatchTargets(rootDir, paths, options);
|
|
953
1481
|
let timer;
|
|
954
1482
|
let running = false;
|
|
@@ -963,7 +1491,7 @@ async function watchVault(rootDir, options = {}) {
|
|
|
963
1491
|
usePolling: true,
|
|
964
1492
|
interval: 100,
|
|
965
1493
|
ignored: (targetPath) => {
|
|
966
|
-
const absolutePath =
|
|
1494
|
+
const absolutePath = path4.resolve(targetPath);
|
|
967
1495
|
const primaryTarget = watchTargets.filter((watchTarget) => isPathWithin(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
|
|
968
1496
|
if (!primaryTarget) {
|
|
969
1497
|
return false;
|
|
@@ -1165,8 +1693,8 @@ async function watchVault(rootDir, options = {}) {
|
|
|
1165
1693
|
}
|
|
1166
1694
|
};
|
|
1167
1695
|
const reasonForPath = (targetPath) => {
|
|
1168
|
-
const baseDir = watchTargets.filter((watchTarget) => isPathWithin(watchTarget,
|
|
1169
|
-
return
|
|
1696
|
+
const baseDir = watchTargets.filter((watchTarget) => isPathWithin(watchTarget, path4.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
|
|
1697
|
+
return path4.relative(baseDir, targetPath) || ".";
|
|
1170
1698
|
};
|
|
1171
1699
|
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)}`));
|
|
1172
1700
|
await new Promise((resolve, reject) => {
|
|
@@ -1239,7 +1767,7 @@ function buildRecommendations(checks) {
|
|
|
1239
1767
|
}
|
|
1240
1768
|
async function currentPackageVersion2() {
|
|
1241
1769
|
try {
|
|
1242
|
-
const raw = await
|
|
1770
|
+
const raw = await fs5.readFile(new URL("../package.json", import.meta.url), "utf8");
|
|
1243
1771
|
const parsed = JSON.parse(raw);
|
|
1244
1772
|
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version : "0.0.0";
|
|
1245
1773
|
} catch {
|
|
@@ -1401,7 +1929,7 @@ async function doctorVault(rootDir, options = {}) {
|
|
|
1401
1929
|
ok: status === "ok",
|
|
1402
1930
|
status,
|
|
1403
1931
|
generatedAt,
|
|
1404
|
-
rootDir:
|
|
1932
|
+
rootDir: path5.resolve(rootDir),
|
|
1405
1933
|
version,
|
|
1406
1934
|
counts: {
|
|
1407
1935
|
sources: manifests.length,
|
|
@@ -1422,10 +1950,10 @@ async function doctorVault(rootDir, options = {}) {
|
|
|
1422
1950
|
|
|
1423
1951
|
// src/graph-export.ts
|
|
1424
1952
|
import { readFileSync } from "fs";
|
|
1425
|
-
import
|
|
1953
|
+
import fs6 from "fs/promises";
|
|
1426
1954
|
import { createRequire } from "module";
|
|
1427
|
-
import
|
|
1428
|
-
import
|
|
1955
|
+
import path6 from "path";
|
|
1956
|
+
import matter4 from "gray-matter";
|
|
1429
1957
|
|
|
1430
1958
|
// src/graph-interchange.ts
|
|
1431
1959
|
function exportHyperedgeNodeId(hyperedge) {
|
|
@@ -1902,8 +2430,8 @@ var _visNetworkJs;
|
|
|
1902
2430
|
function loadVisNetworkJs() {
|
|
1903
2431
|
if (!_visNetworkJs) {
|
|
1904
2432
|
const require2 = createRequire(import.meta.url);
|
|
1905
|
-
const pkgDir =
|
|
1906
|
-
_visNetworkJs = readFileSync(
|
|
2433
|
+
const pkgDir = path6.dirname(require2.resolve("vis-network/package.json"));
|
|
2434
|
+
_visNetworkJs = readFileSync(path6.join(pkgDir, "standalone/umd/vis-network.min.js"), "utf8");
|
|
1907
2435
|
}
|
|
1908
2436
|
return _visNetworkJs;
|
|
1909
2437
|
}
|
|
@@ -3238,9 +3766,9 @@ async function loadGraph(rootDir) {
|
|
|
3238
3766
|
return graph;
|
|
3239
3767
|
}
|
|
3240
3768
|
async function writeGraphExport(outputPath, content) {
|
|
3241
|
-
await ensureDir(
|
|
3242
|
-
await
|
|
3243
|
-
return
|
|
3769
|
+
await ensureDir(path6.dirname(outputPath));
|
|
3770
|
+
await fs6.writeFile(outputPath, content, "utf8");
|
|
3771
|
+
return path6.resolve(outputPath);
|
|
3244
3772
|
}
|
|
3245
3773
|
async function exportGraphFormat(rootDir, format, outputPath) {
|
|
3246
3774
|
const graph = await loadGraph(rootDir);
|
|
@@ -3251,7 +3779,7 @@ async function exportGraphFormat(rootDir, format, outputPath) {
|
|
|
3251
3779
|
async function exportGraphReportHtml(rootDir, outputPath) {
|
|
3252
3780
|
const { paths } = await loadVaultConfig(rootDir);
|
|
3253
3781
|
const graph = await loadGraph(rootDir);
|
|
3254
|
-
const report = await readJsonFile(
|
|
3782
|
+
const report = await readJsonFile(path6.join(paths.wikiDir, "graph", "report.json"));
|
|
3255
3783
|
const html = renderGraphReportHtml(graph, report);
|
|
3256
3784
|
const resolvedPath = await writeGraphExport(outputPath, html);
|
|
3257
3785
|
return { format: "report", outputPath: resolvedPath };
|
|
@@ -3283,7 +3811,7 @@ function typePluralDir(nodeType) {
|
|
|
3283
3811
|
function obsidianNodeSlug(node, pageById) {
|
|
3284
3812
|
if (node.pageId) {
|
|
3285
3813
|
const page = pageById.get(node.pageId);
|
|
3286
|
-
if (page) return
|
|
3814
|
+
if (page) return path6.basename(page.path, ".md");
|
|
3287
3815
|
}
|
|
3288
3816
|
return slugify(node.label);
|
|
3289
3817
|
}
|
|
@@ -3313,14 +3841,14 @@ async function listFilesRecursive2(dir, base = "") {
|
|
|
3313
3841
|
const results = [];
|
|
3314
3842
|
let entries;
|
|
3315
3843
|
try {
|
|
3316
|
-
entries = await
|
|
3844
|
+
entries = await fs6.readdir(dir, { withFileTypes: true });
|
|
3317
3845
|
} catch {
|
|
3318
3846
|
return results;
|
|
3319
3847
|
}
|
|
3320
3848
|
for (const entry of entries) {
|
|
3321
3849
|
const rel = base ? `${base}/${entry.name}` : entry.name;
|
|
3322
3850
|
if (entry.isDirectory()) {
|
|
3323
|
-
results.push(...await listFilesRecursive2(
|
|
3851
|
+
results.push(...await listFilesRecursive2(path6.join(dir, entry.name), rel));
|
|
3324
3852
|
} else {
|
|
3325
3853
|
results.push(rel);
|
|
3326
3854
|
}
|
|
@@ -3366,7 +3894,7 @@ function typedLinkFrontmatter(nodeIds, adjacency, nodesById, wikilinkTarget) {
|
|
|
3366
3894
|
async function exportObsidianVault(rootDir, outputDir) {
|
|
3367
3895
|
const graph = await loadGraph(rootDir);
|
|
3368
3896
|
const { paths } = await loadVaultConfig(rootDir);
|
|
3369
|
-
const resolvedOutputDir =
|
|
3897
|
+
const resolvedOutputDir = path6.resolve(outputDir);
|
|
3370
3898
|
await ensureDir(resolvedOutputDir);
|
|
3371
3899
|
const nodesById = graphNodeById(graph);
|
|
3372
3900
|
const pageById = graphPageById(graph);
|
|
@@ -3406,18 +3934,18 @@ async function exportObsidianVault(rootDir, outputDir) {
|
|
|
3406
3934
|
const pageByPath = new Map(graph.pages.map((p) => [p.path, p]));
|
|
3407
3935
|
for (const relPath of wikiFiles) {
|
|
3408
3936
|
if (!relPath.endsWith(".md")) continue;
|
|
3409
|
-
const srcFile =
|
|
3410
|
-
const destFile =
|
|
3411
|
-
await ensureDir(
|
|
3937
|
+
const srcFile = path6.join(paths.wikiDir, relPath);
|
|
3938
|
+
const destFile = path6.join(resolvedOutputDir, relPath);
|
|
3939
|
+
await ensureDir(path6.dirname(destFile));
|
|
3412
3940
|
let rawContent;
|
|
3413
3941
|
try {
|
|
3414
|
-
rawContent = await
|
|
3942
|
+
rawContent = await fs6.readFile(srcFile, "utf8");
|
|
3415
3943
|
} catch {
|
|
3416
3944
|
continue;
|
|
3417
3945
|
}
|
|
3418
3946
|
const matchingPage = pageByPath.get(relPath);
|
|
3419
3947
|
const pageNodes = matchingPage ? nodesByPageId.get(matchingPage.id) ?? [] : [];
|
|
3420
|
-
const parsed =
|
|
3948
|
+
const parsed = matter4(rawContent);
|
|
3421
3949
|
const data = parsed.data;
|
|
3422
3950
|
if (pageNodes.length > 0) {
|
|
3423
3951
|
const primaryNode = pageNodes[0];
|
|
@@ -3444,7 +3972,7 @@ async function exportObsidianVault(rootDir, outputDir) {
|
|
|
3444
3972
|
data[relation] = links;
|
|
3445
3973
|
}
|
|
3446
3974
|
}
|
|
3447
|
-
let outputContent =
|
|
3975
|
+
let outputContent = matter4.stringify(parsed.content, data);
|
|
3448
3976
|
if (pageNodes.length > 0) {
|
|
3449
3977
|
const connLines = connectionsSection(
|
|
3450
3978
|
pageNodes.map((n) => n.id),
|
|
@@ -3461,14 +3989,14 @@ ${connLines.join("\n")}
|
|
|
3461
3989
|
`;
|
|
3462
3990
|
}
|
|
3463
3991
|
}
|
|
3464
|
-
await
|
|
3992
|
+
await fs6.writeFile(destFile, outputContent, "utf8");
|
|
3465
3993
|
fileCount++;
|
|
3466
3994
|
}
|
|
3467
3995
|
for (const node of orphanNodes) {
|
|
3468
3996
|
const relPath = orphanFilePath.get(node.id);
|
|
3469
|
-
const destFile =
|
|
3470
|
-
await ensureDir(
|
|
3471
|
-
const slug =
|
|
3997
|
+
const destFile = path6.join(resolvedOutputDir, relPath);
|
|
3998
|
+
await ensureDir(path6.dirname(destFile));
|
|
3999
|
+
const slug = path6.basename(relPath, ".md");
|
|
3472
4000
|
const aliases = node.label !== slug ? [node.label] : [];
|
|
3473
4001
|
const frontmatter = {
|
|
3474
4002
|
id: node.id,
|
|
@@ -3494,8 +4022,8 @@ ${connLines.join("\n")}
|
|
|
3494
4022
|
if (connLines.length > 0) {
|
|
3495
4023
|
lines.push("## Connections", "", ...connLines, "");
|
|
3496
4024
|
}
|
|
3497
|
-
const content =
|
|
3498
|
-
await
|
|
4025
|
+
const content = matter4.stringify(lines.join("\n"), frontmatter);
|
|
4026
|
+
await fs6.writeFile(destFile, content, "utf8");
|
|
3499
4027
|
fileCount++;
|
|
3500
4028
|
}
|
|
3501
4029
|
const usedCommunityFileNames = /* @__PURE__ */ new Set();
|
|
@@ -3523,8 +4051,8 @@ ${connLines.join("\n")}
|
|
|
3523
4051
|
});
|
|
3524
4052
|
});
|
|
3525
4053
|
const communitySlug = deduplicateFileName(safeFileName(community.label), usedCommunityFileNames);
|
|
3526
|
-
const destFile =
|
|
3527
|
-
await ensureDir(
|
|
4054
|
+
const destFile = path6.join(resolvedOutputDir, "graph", "communities", `${communitySlug}.md`);
|
|
4055
|
+
await ensureDir(path6.dirname(destFile));
|
|
3528
4056
|
const lines = [`# ${community.label}`, "", "## Members", ""];
|
|
3529
4057
|
for (const member of memberNodes) {
|
|
3530
4058
|
const target = wikilinkTarget.get(member.id);
|
|
@@ -3552,18 +4080,18 @@ ${connLines.join("\n")}
|
|
|
3552
4080
|
node_count: memberNodes.length,
|
|
3553
4081
|
cohesion: Number(cohesion.toFixed(4))
|
|
3554
4082
|
};
|
|
3555
|
-
const content =
|
|
3556
|
-
await
|
|
4083
|
+
const content = matter4.stringify(lines.join("\n"), frontmatter);
|
|
4084
|
+
await fs6.writeFile(destFile, content, "utf8");
|
|
3557
4085
|
fileCount++;
|
|
3558
4086
|
}
|
|
3559
|
-
const outputsAssetsDir =
|
|
4087
|
+
const outputsAssetsDir = path6.join(paths.wikiDir, "outputs", "assets");
|
|
3560
4088
|
try {
|
|
3561
4089
|
const assetFiles = await listFilesRecursive2(outputsAssetsDir);
|
|
3562
4090
|
for (const relAsset of assetFiles) {
|
|
3563
|
-
const src =
|
|
3564
|
-
const dest =
|
|
3565
|
-
await ensureDir(
|
|
3566
|
-
await
|
|
4091
|
+
const src = path6.join(outputsAssetsDir, relAsset);
|
|
4092
|
+
const dest = path6.join(resolvedOutputDir, "outputs", "assets", relAsset);
|
|
4093
|
+
await ensureDir(path6.dirname(dest));
|
|
4094
|
+
await fs6.copyFile(src, dest);
|
|
3567
4095
|
fileCount++;
|
|
3568
4096
|
}
|
|
3569
4097
|
} catch {
|
|
@@ -3571,15 +4099,15 @@ ${connLines.join("\n")}
|
|
|
3571
4099
|
try {
|
|
3572
4100
|
const rawAssetFiles = await listFilesRecursive2(paths.rawAssetsDir);
|
|
3573
4101
|
for (const relAsset of rawAssetFiles) {
|
|
3574
|
-
const src =
|
|
3575
|
-
const dest =
|
|
3576
|
-
await ensureDir(
|
|
3577
|
-
await
|
|
4102
|
+
const src = path6.join(paths.rawAssetsDir, relAsset);
|
|
4103
|
+
const dest = path6.join(resolvedOutputDir, "raw", "assets", relAsset);
|
|
4104
|
+
await ensureDir(path6.dirname(dest));
|
|
4105
|
+
await fs6.copyFile(src, dest);
|
|
3578
4106
|
fileCount++;
|
|
3579
4107
|
}
|
|
3580
4108
|
} catch {
|
|
3581
4109
|
}
|
|
3582
|
-
const obsidianDir =
|
|
4110
|
+
const obsidianDir = path6.join(resolvedOutputDir, ".obsidian");
|
|
3583
4111
|
await ensureDir(obsidianDir);
|
|
3584
4112
|
const projectIds = Object.keys(
|
|
3585
4113
|
graph.pages.reduce(
|
|
@@ -3603,8 +4131,8 @@ ${connLines.join("\n")}
|
|
|
3603
4131
|
color: hexToObsidianColor(["#0ea5e9", "#22c55e", "#f59e0b", "#8b5cf6", "#fb7185", "#14b8a6"][index % 6])
|
|
3604
4132
|
}));
|
|
3605
4133
|
const colorGroups = [...nodeTypeGroups, ...projectColorGroups];
|
|
3606
|
-
await
|
|
3607
|
-
|
|
4134
|
+
await fs6.writeFile(
|
|
4135
|
+
path6.join(obsidianDir, "app.json"),
|
|
3608
4136
|
JSON.stringify(
|
|
3609
4137
|
{ newFileLocation: "folder", newFileFolderPath: "outputs", attachmentFolderPath: "raw/assets", useMarkdownLinks: false },
|
|
3610
4138
|
null,
|
|
@@ -3612,13 +4140,13 @@ ${connLines.join("\n")}
|
|
|
3612
4140
|
),
|
|
3613
4141
|
"utf8"
|
|
3614
4142
|
);
|
|
3615
|
-
await
|
|
3616
|
-
|
|
4143
|
+
await fs6.writeFile(
|
|
4144
|
+
path6.join(obsidianDir, "core-plugins.json"),
|
|
3617
4145
|
JSON.stringify(["file-explorer", "global-search", "graph", "backlink", "tag-pane", "page-preview", "outline"], null, 2),
|
|
3618
4146
|
"utf8"
|
|
3619
4147
|
);
|
|
3620
|
-
await
|
|
3621
|
-
|
|
4148
|
+
await fs6.writeFile(
|
|
4149
|
+
path6.join(obsidianDir, "graph.json"),
|
|
3622
4150
|
JSON.stringify(
|
|
3623
4151
|
{ colorGroups, "collapse-filter": false, search: "", showTags: true, showAttachments: false, showOrphans: true },
|
|
3624
4152
|
null,
|
|
@@ -3626,9 +4154,9 @@ ${connLines.join("\n")}
|
|
|
3626
4154
|
),
|
|
3627
4155
|
"utf8"
|
|
3628
4156
|
);
|
|
3629
|
-
await
|
|
4157
|
+
await fs6.writeFile(path6.join(obsidianDir, "types.json"), JSON.stringify({ types: OBSIDIAN_PROPERTY_TYPES }, null, 2), "utf8");
|
|
3630
4158
|
fileCount += 4;
|
|
3631
|
-
const dashboardDir =
|
|
4159
|
+
const dashboardDir = path6.join(resolvedOutputDir, "graph", "dashboards");
|
|
3632
4160
|
await ensureDir(dashboardDir);
|
|
3633
4161
|
const dvPages = [
|
|
3634
4162
|
{
|
|
@@ -3665,7 +4193,7 @@ ${connLines.join("\n")}
|
|
|
3665
4193
|
${dv.query}
|
|
3666
4194
|
\`\`\`
|
|
3667
4195
|
`;
|
|
3668
|
-
await
|
|
4196
|
+
await fs6.writeFile(path6.join(dashboardDir, `${dv.name}.md`), matter4.stringify(dvBody, dvFrontmatter), "utf8");
|
|
3669
4197
|
fileCount++;
|
|
3670
4198
|
}
|
|
3671
4199
|
return { format: "obsidian", outputPath: resolvedOutputDir, fileCount };
|
|
@@ -3787,8 +4315,8 @@ Community: ${communityLabel}`,
|
|
|
3787
4315
|
}
|
|
3788
4316
|
|
|
3789
4317
|
// src/graph-merge.ts
|
|
3790
|
-
import
|
|
3791
|
-
import
|
|
4318
|
+
import fs7 from "fs/promises";
|
|
4319
|
+
import path7 from "path";
|
|
3792
4320
|
function isRecord(value) {
|
|
3793
4321
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3794
4322
|
}
|
|
@@ -3816,7 +4344,7 @@ function numberField(record, field, fallback) {
|
|
|
3816
4344
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
3817
4345
|
}
|
|
3818
4346
|
function safePrefix(inputPath, index) {
|
|
3819
|
-
return slugify(
|
|
4347
|
+
return slugify(path7.basename(inputPath, path7.extname(inputPath)) || `graph-${index + 1}`);
|
|
3820
4348
|
}
|
|
3821
4349
|
function prefixed(prefix, id) {
|
|
3822
4350
|
return `${prefix}:${id}`;
|
|
@@ -3866,7 +4394,7 @@ function remapSwarmVaultGraph(inputPath, graph, prefix) {
|
|
|
3866
4394
|
const pages = graph.pages.map((page) => ({
|
|
3867
4395
|
...page,
|
|
3868
4396
|
id: pageMap.get(page.id) ?? prefixed(prefix, page.id),
|
|
3869
|
-
path: toPosix(
|
|
4397
|
+
path: toPosix(path7.posix.join("merged", prefix, page.path)),
|
|
3870
4398
|
sourceIds: page.sourceIds.map((sourceId) => sourceMap.get(sourceId) ?? prefixed(prefix, sourceId)),
|
|
3871
4399
|
nodeIds: page.nodeIds.map((nodeId) => nodeMap.get(nodeId) ?? prefixed(prefix, nodeId)),
|
|
3872
4400
|
relatedPageIds: page.relatedPageIds.map((pageId) => pageMap.get(pageId) ?? prefixed(prefix, pageId)),
|
|
@@ -4008,7 +4536,7 @@ function remapNodeLinkGraph(inputPath, raw, prefix, now) {
|
|
|
4008
4536
|
});
|
|
4009
4537
|
const page = {
|
|
4010
4538
|
id: prefixed(prefix, "page"),
|
|
4011
|
-
path: toPosix(
|
|
4539
|
+
path: toPosix(path7.posix.join("merged", prefix, "index.md")),
|
|
4012
4540
|
title: `${prefix} merged graph`,
|
|
4013
4541
|
kind: "source",
|
|
4014
4542
|
sourceClass: "generated",
|
|
@@ -4079,8 +4607,8 @@ async function mergeGraphFiles(inputPaths, outputPath, options = {}) {
|
|
|
4079
4607
|
const inputGraphs = [];
|
|
4080
4608
|
const warnings = [];
|
|
4081
4609
|
for (const [index, inputPath] of inputPaths.entries()) {
|
|
4082
|
-
const resolvedInputPath =
|
|
4083
|
-
const raw = JSON.parse(await
|
|
4610
|
+
const resolvedInputPath = path7.resolve(inputPath);
|
|
4611
|
+
const raw = JSON.parse(await fs7.readFile(resolvedInputPath, "utf8"));
|
|
4084
4612
|
const prefix = ensureUniquePrefix(
|
|
4085
4613
|
inputPaths.length === 1 && options.label ? slugify(options.label) : safePrefix(resolvedInputPath, index),
|
|
4086
4614
|
usedPrefixes
|
|
@@ -4115,9 +4643,9 @@ async function mergeGraphFiles(inputPaths, outputPath, options = {}) {
|
|
|
4115
4643
|
throw new Error("No supported graph inputs were found.");
|
|
4116
4644
|
}
|
|
4117
4645
|
const graph = mergeGraphs(graphs, now);
|
|
4118
|
-
const resolvedOutputPath =
|
|
4119
|
-
await ensureDir(
|
|
4120
|
-
await
|
|
4646
|
+
const resolvedOutputPath = path7.resolve(outputPath);
|
|
4647
|
+
await ensureDir(path7.dirname(resolvedOutputPath));
|
|
4648
|
+
await fs7.writeFile(resolvedOutputPath, `${JSON.stringify(graph, null, 2)}
|
|
4121
4649
|
`, "utf8");
|
|
4122
4650
|
return {
|
|
4123
4651
|
outputPath: resolvedOutputPath,
|
|
@@ -4128,8 +4656,8 @@ async function mergeGraphFiles(inputPaths, outputPath, options = {}) {
|
|
|
4128
4656
|
}
|
|
4129
4657
|
|
|
4130
4658
|
// src/graph-push.ts
|
|
4131
|
-
import
|
|
4132
|
-
import
|
|
4659
|
+
import fs8 from "fs/promises";
|
|
4660
|
+
import path8 from "path";
|
|
4133
4661
|
import neo4j from "neo4j-driver";
|
|
4134
4662
|
var DEFAULT_NEO4J_BATCH_SIZE = 500;
|
|
4135
4663
|
var DEFAULT_NEO4J_DATABASE = "neo4j";
|
|
@@ -4140,8 +4668,8 @@ function requireConfigValue(value, name) {
|
|
|
4140
4668
|
throw new Error(`Neo4j push requires ${name}. Configure \`graphSinks.neo4j.${name}\` or pass the matching CLI flag.`);
|
|
4141
4669
|
}
|
|
4142
4670
|
async function deriveVaultId(rootDir) {
|
|
4143
|
-
const realRoot = await
|
|
4144
|
-
const label = slugify(
|
|
4671
|
+
const realRoot = await fs8.realpath(rootDir).catch(() => path8.resolve(rootDir));
|
|
4672
|
+
const label = slugify(path8.basename(realRoot));
|
|
4145
4673
|
return `${label}-${sha256(realRoot).slice(0, 12)}`;
|
|
4146
4674
|
}
|
|
4147
4675
|
async function resolveNeo4jPushConfig(rootDir, options) {
|
|
@@ -4171,7 +4699,7 @@ function normalizeBatchSize(value) {
|
|
|
4171
4699
|
}
|
|
4172
4700
|
async function loadGraph2(rootDir) {
|
|
4173
4701
|
const { paths } = await loadVaultConfig(rootDir);
|
|
4174
|
-
const raw = JSON.parse(await
|
|
4702
|
+
const raw = JSON.parse(await fs8.readFile(paths.graphPath, "utf8"));
|
|
4175
4703
|
return raw;
|
|
4176
4704
|
}
|
|
4177
4705
|
function buildResult(input) {
|
|
@@ -4256,7 +4784,7 @@ async function writeSyncNode(session, input) {
|
|
|
4256
4784
|
].join("\n"),
|
|
4257
4785
|
{
|
|
4258
4786
|
vaultId: input.vaultId,
|
|
4259
|
-
rootDir:
|
|
4787
|
+
rootDir: path8.resolve(input.rootDir),
|
|
4260
4788
|
graphGeneratedAt: input.graph.generatedAt,
|
|
4261
4789
|
graphHash: graphHash(input.graph),
|
|
4262
4790
|
pushedAt: input.pushedAt,
|
|
@@ -4342,7 +4870,7 @@ async function pushGraphNeo4j(rootDir, options = {}) {
|
|
|
4342
4870
|
}
|
|
4343
4871
|
|
|
4344
4872
|
// src/graph-status.ts
|
|
4345
|
-
import
|
|
4873
|
+
import path9 from "path";
|
|
4346
4874
|
function recommendedCommand(input) {
|
|
4347
4875
|
if (!input.graphExists || !input.reportExists) {
|
|
4348
4876
|
return "swarmvault compile";
|
|
@@ -4358,8 +4886,8 @@ function recommendedCommand(input) {
|
|
|
4358
4886
|
async function getGraphStatus(rootDir, options = {}) {
|
|
4359
4887
|
const { paths } = await loadVaultConfig(rootDir);
|
|
4360
4888
|
const graphPath = paths.graphPath;
|
|
4361
|
-
const reportPath =
|
|
4362
|
-
const resolvedOverrideRoots = options.repoRoots?.map((repoRoot) =>
|
|
4889
|
+
const reportPath = path9.join(paths.wikiDir, "graph", "report.md");
|
|
4890
|
+
const resolvedOverrideRoots = options.repoRoots?.map((repoRoot) => path9.resolve(rootDir, repoRoot));
|
|
4363
4891
|
const [graphExists, reportExists, trackedRepoRoots, changes, pendingSemanticRefresh] = await Promise.all([
|
|
4364
4892
|
fileExists(graphPath),
|
|
4365
4893
|
fileExists(reportPath),
|
|
@@ -4393,7 +4921,7 @@ async function getGraphStatus(rootDir, options = {}) {
|
|
|
4393
4921
|
}
|
|
4394
4922
|
|
|
4395
4923
|
// src/graph-tree.ts
|
|
4396
|
-
import
|
|
4924
|
+
import path10 from "path";
|
|
4397
4925
|
var DEFAULT_MAX_CHILDREN = 250;
|
|
4398
4926
|
function compareTreeNodes(left, right) {
|
|
4399
4927
|
const kindOrder = /* @__PURE__ */ new Map([
|
|
@@ -4435,11 +4963,11 @@ function normalizeSourcePath(rootDir, source) {
|
|
|
4435
4963
|
if (source.repoRelativePath) {
|
|
4436
4964
|
return toPosix(source.repoRelativePath);
|
|
4437
4965
|
}
|
|
4438
|
-
if (rootDir &&
|
|
4439
|
-
return toPosix(
|
|
4966
|
+
if (rootDir && path10.isAbsolute(candidate) && isPathWithin(rootDir, candidate)) {
|
|
4967
|
+
return toPosix(path10.relative(rootDir, candidate));
|
|
4440
4968
|
}
|
|
4441
|
-
if (
|
|
4442
|
-
return toPosix(
|
|
4969
|
+
if (path10.isAbsolute(candidate)) {
|
|
4970
|
+
return toPosix(path10.basename(candidate));
|
|
4443
4971
|
}
|
|
4444
4972
|
return toPosix(candidate).replace(/^\/+/, "") || source.title || source.sourceId;
|
|
4445
4973
|
}
|
|
@@ -4758,9 +5286,9 @@ async function exportGraphTree(rootDir, outputPath, options = {}) {
|
|
|
4758
5286
|
throw new Error(`Graph artifact not found at ${paths.graphPath}. Run swarmvault compile first.`);
|
|
4759
5287
|
}
|
|
4760
5288
|
const tree = buildGraphTree(graph, { ...options, rootDir });
|
|
4761
|
-
const resolvedOutputPath =
|
|
4762
|
-
await ensureDir(
|
|
4763
|
-
await import("fs/promises").then((
|
|
5289
|
+
const resolvedOutputPath = path10.resolve(rootDir, outputPath ?? path10.join(paths.wikiDir, "graph", "tree.html"));
|
|
5290
|
+
await ensureDir(path10.dirname(resolvedOutputPath));
|
|
5291
|
+
await import("fs/promises").then((fs15) => fs15.writeFile(resolvedOutputPath, renderGraphTreeHtml(tree, graph), "utf8"));
|
|
4764
5292
|
return {
|
|
4765
5293
|
outputPath: resolvedOutputPath,
|
|
4766
5294
|
sourceCount: graph.sources.length,
|
|
@@ -4770,26 +5298,26 @@ async function exportGraphTree(rootDir, outputPath, options = {}) {
|
|
|
4770
5298
|
}
|
|
4771
5299
|
|
|
4772
5300
|
// src/hooks.ts
|
|
4773
|
-
import
|
|
4774
|
-
import
|
|
5301
|
+
import fs9 from "fs/promises";
|
|
5302
|
+
import path11 from "path";
|
|
4775
5303
|
import process3 from "process";
|
|
4776
5304
|
var hookStart = "# >>> swarmvault hook >>>";
|
|
4777
5305
|
var hookEnd = "# <<< swarmvault hook <<<";
|
|
4778
5306
|
async function findNearestGitRoot(startPath) {
|
|
4779
|
-
let current =
|
|
5307
|
+
let current = path11.resolve(startPath);
|
|
4780
5308
|
try {
|
|
4781
|
-
const stat = await
|
|
5309
|
+
const stat = await fs9.stat(current);
|
|
4782
5310
|
if (!stat.isDirectory()) {
|
|
4783
|
-
current =
|
|
5311
|
+
current = path11.dirname(current);
|
|
4784
5312
|
}
|
|
4785
5313
|
} catch {
|
|
4786
|
-
current =
|
|
5314
|
+
current = path11.dirname(current);
|
|
4787
5315
|
}
|
|
4788
5316
|
while (true) {
|
|
4789
|
-
if (await fileExists(
|
|
5317
|
+
if (await fileExists(path11.join(current, ".git"))) {
|
|
4790
5318
|
return current;
|
|
4791
5319
|
}
|
|
4792
|
-
const parent =
|
|
5320
|
+
const parent = path11.dirname(current);
|
|
4793
5321
|
if (parent === current) {
|
|
4794
5322
|
return null;
|
|
4795
5323
|
}
|
|
@@ -4801,8 +5329,8 @@ function shellQuote(value) {
|
|
|
4801
5329
|
}
|
|
4802
5330
|
function resolveSwarmvaultExecutableCandidate() {
|
|
4803
5331
|
const argvPath = process3.argv[1];
|
|
4804
|
-
if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${
|
|
4805
|
-
return
|
|
5332
|
+
if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path11.sep}@swarmvaultai${path11.sep}cli${path11.sep}`) || argvPath.includes(`${path11.sep}packages${path11.sep}cli${path11.sep}`))) {
|
|
5333
|
+
return path11.resolve(argvPath);
|
|
4806
5334
|
}
|
|
4807
5335
|
return "swarmvault";
|
|
4808
5336
|
}
|
|
@@ -4821,17 +5349,17 @@ function managedHookBlock(vaultRoot) {
|
|
|
4821
5349
|
].join("\n");
|
|
4822
5350
|
}
|
|
4823
5351
|
function hookPath(repoRoot, hookName) {
|
|
4824
|
-
return
|
|
5352
|
+
return path11.join(repoRoot, ".git", "hooks", hookName);
|
|
4825
5353
|
}
|
|
4826
5354
|
async function readHookStatus(filePath) {
|
|
4827
5355
|
if (!await fileExists(filePath)) {
|
|
4828
5356
|
return "not_installed";
|
|
4829
5357
|
}
|
|
4830
|
-
const content = await
|
|
5358
|
+
const content = await fs9.readFile(filePath, "utf8");
|
|
4831
5359
|
return content.includes(hookStart) && content.includes(hookEnd) ? "installed" : "other_content";
|
|
4832
5360
|
}
|
|
4833
5361
|
async function upsertHookFile(filePath, block) {
|
|
4834
|
-
const existing = await fileExists(filePath) ? await
|
|
5362
|
+
const existing = await fileExists(filePath) ? await fs9.readFile(filePath, "utf8") : "";
|
|
4835
5363
|
let next;
|
|
4836
5364
|
const startIndex = existing.indexOf(hookStart);
|
|
4837
5365
|
const endIndex = existing.indexOf(hookEnd);
|
|
@@ -4845,16 +5373,16 @@ ${block}`.trimEnd();
|
|
|
4845
5373
|
next = `#!/bin/sh
|
|
4846
5374
|
${block}`.trimEnd();
|
|
4847
5375
|
}
|
|
4848
|
-
await ensureDir(
|
|
4849
|
-
await
|
|
5376
|
+
await ensureDir(path11.dirname(filePath));
|
|
5377
|
+
await fs9.writeFile(filePath, `${next}
|
|
4850
5378
|
`, { mode: 493, encoding: "utf8" });
|
|
4851
|
-
await
|
|
5379
|
+
await fs9.chmod(filePath, 493);
|
|
4852
5380
|
}
|
|
4853
5381
|
async function removeHookBlock(filePath) {
|
|
4854
5382
|
if (!await fileExists(filePath)) {
|
|
4855
5383
|
return;
|
|
4856
5384
|
}
|
|
4857
|
-
const existing = await
|
|
5385
|
+
const existing = await fs9.readFile(filePath, "utf8");
|
|
4858
5386
|
const startIndex = existing.indexOf(hookStart);
|
|
4859
5387
|
const endIndex = existing.indexOf(hookEnd);
|
|
4860
5388
|
if (startIndex === -1 || endIndex === -1) {
|
|
@@ -4862,10 +5390,10 @@ async function removeHookBlock(filePath) {
|
|
|
4862
5390
|
}
|
|
4863
5391
|
const next = `${existing.slice(0, startIndex)}${existing.slice(endIndex + hookEnd.length)}`.trim();
|
|
4864
5392
|
if (!next || next === "#!/bin/sh") {
|
|
4865
|
-
await
|
|
5393
|
+
await fs9.rm(filePath, { force: true });
|
|
4866
5394
|
return;
|
|
4867
5395
|
}
|
|
4868
|
-
await
|
|
5396
|
+
await fs9.writeFile(filePath, `${next}
|
|
4869
5397
|
`, "utf8");
|
|
4870
5398
|
}
|
|
4871
5399
|
async function getGitHookStatus(rootDir) {
|
|
@@ -4888,7 +5416,7 @@ async function installGitHooks(rootDir) {
|
|
|
4888
5416
|
if (!repoRoot) {
|
|
4889
5417
|
throw new Error("No git repository found above the current vault.");
|
|
4890
5418
|
}
|
|
4891
|
-
const block = managedHookBlock(
|
|
5419
|
+
const block = managedHookBlock(path11.resolve(rootDir));
|
|
4892
5420
|
await upsertHookFile(hookPath(repoRoot, "post-commit"), block);
|
|
4893
5421
|
await upsertHookFile(hookPath(repoRoot, "post-checkout"), block);
|
|
4894
5422
|
return getGitHookStatus(rootDir);
|
|
@@ -4908,12 +5436,12 @@ async function uninstallGitHooks(rootDir) {
|
|
|
4908
5436
|
}
|
|
4909
5437
|
|
|
4910
5438
|
// src/mcp.ts
|
|
4911
|
-
import
|
|
4912
|
-
import
|
|
5439
|
+
import fs10 from "fs/promises";
|
|
5440
|
+
import path12 from "path";
|
|
4913
5441
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4914
5442
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4915
5443
|
import { z } from "zod";
|
|
4916
|
-
var SERVER_VERSION = "3.
|
|
5444
|
+
var SERVER_VERSION = "3.12.0";
|
|
4917
5445
|
var codeLanguageSchema = z.enum([
|
|
4918
5446
|
"javascript",
|
|
4919
5447
|
"jsx",
|
|
@@ -5670,7 +6198,7 @@ async function createMcpServer(rootDir) {
|
|
|
5670
6198
|
},
|
|
5671
6199
|
async () => {
|
|
5672
6200
|
const { paths } = await loadVaultConfig(rootDir);
|
|
5673
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
6201
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path12.relative(paths.sessionsDir, filePath))).sort();
|
|
5674
6202
|
return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
|
|
5675
6203
|
}
|
|
5676
6204
|
);
|
|
@@ -5739,8 +6267,8 @@ async function createMcpServer(rootDir) {
|
|
|
5739
6267
|
return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
|
|
5740
6268
|
}
|
|
5741
6269
|
const { paths } = await loadVaultConfig(rootDir);
|
|
5742
|
-
const absolutePath =
|
|
5743
|
-
return asTextResource(`swarmvault://pages/${encodedPath}`, await
|
|
6270
|
+
const absolutePath = path12.resolve(paths.wikiDir, relativePath);
|
|
6271
|
+
return asTextResource(`swarmvault://pages/${encodedPath}`, await fs10.readFile(absolutePath, "utf8"));
|
|
5744
6272
|
}
|
|
5745
6273
|
);
|
|
5746
6274
|
server.registerResource(
|
|
@@ -5748,11 +6276,11 @@ async function createMcpServer(rootDir) {
|
|
|
5748
6276
|
new ResourceTemplate("swarmvault://sessions/{path}", {
|
|
5749
6277
|
list: async () => {
|
|
5750
6278
|
const { paths } = await loadVaultConfig(rootDir);
|
|
5751
|
-
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(
|
|
6279
|
+
const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path12.relative(paths.sessionsDir, filePath))).sort();
|
|
5752
6280
|
return {
|
|
5753
6281
|
resources: files.map((relativePath) => ({
|
|
5754
6282
|
uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
|
|
5755
|
-
name:
|
|
6283
|
+
name: path12.basename(relativePath, ".md"),
|
|
5756
6284
|
title: relativePath,
|
|
5757
6285
|
description: "SwarmVault session artifact",
|
|
5758
6286
|
mimeType: "text/markdown"
|
|
@@ -5769,11 +6297,11 @@ async function createMcpServer(rootDir) {
|
|
|
5769
6297
|
const { paths } = await loadVaultConfig(rootDir);
|
|
5770
6298
|
const encodedPath = typeof variables.path === "string" ? variables.path : "";
|
|
5771
6299
|
const relativePath = decodeURIComponent(encodedPath);
|
|
5772
|
-
const absolutePath =
|
|
6300
|
+
const absolutePath = path12.resolve(paths.sessionsDir, relativePath);
|
|
5773
6301
|
if (!isPathWithin(paths.sessionsDir, absolutePath) || !await fileExists(absolutePath)) {
|
|
5774
6302
|
return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
|
|
5775
6303
|
}
|
|
5776
|
-
return asTextResource(`swarmvault://sessions/${encodedPath}`, await
|
|
6304
|
+
return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs10.readFile(absolutePath, "utf8"));
|
|
5777
6305
|
}
|
|
5778
6306
|
);
|
|
5779
6307
|
return server;
|
|
@@ -5833,9 +6361,9 @@ function asTextResource(uri, text) {
|
|
|
5833
6361
|
|
|
5834
6362
|
// src/providers/local-whisper-setup.ts
|
|
5835
6363
|
import { createWriteStream, constants as fsConstants } from "fs";
|
|
5836
|
-
import
|
|
6364
|
+
import fs11 from "fs/promises";
|
|
5837
6365
|
import os from "os";
|
|
5838
|
-
import
|
|
6366
|
+
import path13 from "path";
|
|
5839
6367
|
import { Readable } from "stream";
|
|
5840
6368
|
import { pipeline } from "stream/promises";
|
|
5841
6369
|
var BINARY_CANDIDATES = ["whisper-cli", "whisper-cpp", "whisper"];
|
|
@@ -5863,10 +6391,10 @@ async function discoverLocalWhisperBinary(options = {}) {
|
|
|
5863
6391
|
}
|
|
5864
6392
|
const pathValue = env.PATH ?? "";
|
|
5865
6393
|
const candidates = [];
|
|
5866
|
-
for (const dir of pathValue.split(
|
|
6394
|
+
for (const dir of pathValue.split(path13.delimiter)) {
|
|
5867
6395
|
if (!dir) continue;
|
|
5868
6396
|
for (const name of BINARY_CANDIDATES) {
|
|
5869
|
-
const full =
|
|
6397
|
+
const full = path13.join(dir, name);
|
|
5870
6398
|
candidates.push(full);
|
|
5871
6399
|
if (await isExecutable(full)) {
|
|
5872
6400
|
return { binaryPath: full, candidates, source: "path" };
|
|
@@ -5877,14 +6405,14 @@ async function discoverLocalWhisperBinary(options = {}) {
|
|
|
5877
6405
|
}
|
|
5878
6406
|
function expectedModelPath(modelName, homeDir) {
|
|
5879
6407
|
const home = homeDir ?? os.homedir();
|
|
5880
|
-
return
|
|
6408
|
+
return path13.join(home, ".swarmvault", "models", `ggml-${modelName}.bin`);
|
|
5881
6409
|
}
|
|
5882
6410
|
function modelDownloadUrl(modelName) {
|
|
5883
6411
|
return `${HUGGINGFACE_BASE}/ggml-${modelName}.bin`;
|
|
5884
6412
|
}
|
|
5885
6413
|
async function downloadWhisperModel(options) {
|
|
5886
6414
|
const destPath = expectedModelPath(options.modelName, options.homeDir);
|
|
5887
|
-
await ensureDir(
|
|
6415
|
+
await ensureDir(path13.dirname(destPath));
|
|
5888
6416
|
const doFetch = options.fetchImpl ?? fetch;
|
|
5889
6417
|
const url = modelDownloadUrl(options.modelName);
|
|
5890
6418
|
const response = await doFetch(url);
|
|
@@ -5905,8 +6433,8 @@ async function downloadWhisperModel(options) {
|
|
|
5905
6433
|
});
|
|
5906
6434
|
const tmpPath = `${destPath}.part`;
|
|
5907
6435
|
await pipeline(source, createWriteStream(tmpPath));
|
|
5908
|
-
await
|
|
5909
|
-
const stat = await
|
|
6436
|
+
await fs11.rename(tmpPath, destPath);
|
|
6437
|
+
const stat = await fs11.stat(destPath);
|
|
5910
6438
|
return { path: destPath, bytes: stat.size };
|
|
5911
6439
|
}
|
|
5912
6440
|
async function registerLocalWhisperProvider(options) {
|
|
@@ -5973,7 +6501,7 @@ async function summarizeLocalWhisperSetup(options) {
|
|
|
5973
6501
|
}
|
|
5974
6502
|
async function isExecutable(p) {
|
|
5975
6503
|
try {
|
|
5976
|
-
await
|
|
6504
|
+
await fs11.access(p, fsConstants.X_OK);
|
|
5977
6505
|
return true;
|
|
5978
6506
|
} catch {
|
|
5979
6507
|
return false;
|
|
@@ -6043,13 +6571,13 @@ async function withCapabilityFallback(provider, capability, run, fallback) {
|
|
|
6043
6571
|
}
|
|
6044
6572
|
|
|
6045
6573
|
// src/schedule.ts
|
|
6046
|
-
import
|
|
6047
|
-
import
|
|
6574
|
+
import fs12 from "fs/promises";
|
|
6575
|
+
import path14 from "path";
|
|
6048
6576
|
function scheduleStatePath(schedulesDir, jobId) {
|
|
6049
|
-
return
|
|
6577
|
+
return path14.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
6050
6578
|
}
|
|
6051
6579
|
function scheduleLockPath(schedulesDir, jobId) {
|
|
6052
|
-
return
|
|
6580
|
+
return path14.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
|
|
6053
6581
|
}
|
|
6054
6582
|
function parseEveryDuration(value) {
|
|
6055
6583
|
const match = value.trim().match(/^(\d+)(m|h|d)$/i);
|
|
@@ -6152,13 +6680,13 @@ async function acquireJobLease(rootDir, jobId) {
|
|
|
6152
6680
|
const { paths } = await loadVaultConfig(rootDir);
|
|
6153
6681
|
const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
|
|
6154
6682
|
await ensureDir(paths.schedulesDir);
|
|
6155
|
-
const handle = await
|
|
6683
|
+
const handle = await fs12.open(leasePath, "wx");
|
|
6156
6684
|
await handle.writeFile(`${process.pid}
|
|
6157
6685
|
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
6158
6686
|
`);
|
|
6159
6687
|
await handle.close();
|
|
6160
6688
|
return async () => {
|
|
6161
|
-
await
|
|
6689
|
+
await fs12.rm(leasePath, { force: true });
|
|
6162
6690
|
};
|
|
6163
6691
|
}
|
|
6164
6692
|
async function listSchedules(rootDir) {
|
|
@@ -6317,9 +6845,9 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
|
|
|
6317
6845
|
|
|
6318
6846
|
// src/sources.ts
|
|
6319
6847
|
import { spawn } from "child_process";
|
|
6320
|
-
import
|
|
6321
|
-
import
|
|
6322
|
-
import
|
|
6848
|
+
import fs13 from "fs/promises";
|
|
6849
|
+
import path15 from "path";
|
|
6850
|
+
import matter5 from "gray-matter";
|
|
6323
6851
|
import { JSDOM } from "jsdom";
|
|
6324
6852
|
var DEFAULT_CRAWL_MAX_PAGES = 12;
|
|
6325
6853
|
var DEFAULT_CRAWL_MAX_DEPTH = 2;
|
|
@@ -6364,24 +6892,24 @@ function emptyManagedSourceSyncCounts() {
|
|
|
6364
6892
|
};
|
|
6365
6893
|
}
|
|
6366
6894
|
function withinRoot(rootPath, targetPath) {
|
|
6367
|
-
const relative =
|
|
6368
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
6895
|
+
const relative = path15.relative(rootPath, targetPath);
|
|
6896
|
+
return relative === "" || !relative.startsWith("..") && !path15.isAbsolute(relative);
|
|
6369
6897
|
}
|
|
6370
6898
|
async function findNearestGitRoot2(startPath) {
|
|
6371
|
-
let current =
|
|
6899
|
+
let current = path15.resolve(startPath);
|
|
6372
6900
|
try {
|
|
6373
|
-
const stat = await
|
|
6901
|
+
const stat = await fs13.stat(current);
|
|
6374
6902
|
if (!stat.isDirectory()) {
|
|
6375
|
-
current =
|
|
6903
|
+
current = path15.dirname(current);
|
|
6376
6904
|
}
|
|
6377
6905
|
} catch {
|
|
6378
|
-
current =
|
|
6906
|
+
current = path15.dirname(current);
|
|
6379
6907
|
}
|
|
6380
6908
|
while (true) {
|
|
6381
|
-
if (await fileExists(
|
|
6909
|
+
if (await fileExists(path15.join(current, ".git"))) {
|
|
6382
6910
|
return current;
|
|
6383
6911
|
}
|
|
6384
|
-
const parent =
|
|
6912
|
+
const parent = path15.dirname(current);
|
|
6385
6913
|
if (parent === current) {
|
|
6386
6914
|
return null;
|
|
6387
6915
|
}
|
|
@@ -6455,7 +6983,7 @@ function isAllowedDocsCandidate(candidate, startUrl) {
|
|
|
6455
6983
|
if (candidate.origin !== startUrl.origin) {
|
|
6456
6984
|
return false;
|
|
6457
6985
|
}
|
|
6458
|
-
const extension =
|
|
6986
|
+
const extension = path15.extname(candidate.pathname).toLowerCase();
|
|
6459
6987
|
if (extension && extension !== ".html" && extension !== ".htm" && extension !== ".md") {
|
|
6460
6988
|
return false;
|
|
6461
6989
|
}
|
|
@@ -6544,7 +7072,7 @@ function matchesManagedSourceSpec(existing, input) {
|
|
|
6544
7072
|
return false;
|
|
6545
7073
|
}
|
|
6546
7074
|
if (input.kind === "directory" || input.kind === "file") {
|
|
6547
|
-
return
|
|
7075
|
+
return path15.resolve(existing.path ?? "") === path15.resolve(input.path);
|
|
6548
7076
|
}
|
|
6549
7077
|
if (input.kind === "github_repo") {
|
|
6550
7078
|
return (existing.url ?? "") === input.url && (existing.branch ?? "") === (input.branch ?? "") && (existing.ref ?? "") === (input.ref ?? "");
|
|
@@ -6569,15 +7097,15 @@ function normalizeCheckoutDir(rootDir, value) {
|
|
|
6569
7097
|
if (!trimmed) {
|
|
6570
7098
|
return void 0;
|
|
6571
7099
|
}
|
|
6572
|
-
return
|
|
7100
|
+
return path15.isAbsolute(trimmed) ? path15.resolve(trimmed) : path15.resolve(rootDir, trimmed);
|
|
6573
7101
|
}
|
|
6574
7102
|
async function resolveManagedSourceInput(rootDir, input, options = {}) {
|
|
6575
|
-
const absoluteInput =
|
|
7103
|
+
const absoluteInput = path15.resolve(rootDir, input);
|
|
6576
7104
|
if (!(input.startsWith("http://") || input.startsWith("https://"))) {
|
|
6577
7105
|
if (options.branch || options.ref || options.checkoutDir) {
|
|
6578
7106
|
throw new Error("Git branch/ref/checkout options are only supported for public GitHub repo root URLs.");
|
|
6579
7107
|
}
|
|
6580
|
-
const stat = await
|
|
7108
|
+
const stat = await fs13.stat(absoluteInput).catch(() => null);
|
|
6581
7109
|
if (!stat) {
|
|
6582
7110
|
throw new Error(`Source not found: ${input}`);
|
|
6583
7111
|
}
|
|
@@ -6585,7 +7113,7 @@ async function resolveManagedSourceInput(rootDir, input, options = {}) {
|
|
|
6585
7113
|
return {
|
|
6586
7114
|
kind: "file",
|
|
6587
7115
|
path: absoluteInput,
|
|
6588
|
-
title:
|
|
7116
|
+
title: path15.basename(absoluteInput, path15.extname(absoluteInput)) || absoluteInput
|
|
6589
7117
|
};
|
|
6590
7118
|
}
|
|
6591
7119
|
if (!stat.isDirectory()) {
|
|
@@ -6597,7 +7125,7 @@ async function resolveManagedSourceInput(rootDir, input, options = {}) {
|
|
|
6597
7125
|
kind: "directory",
|
|
6598
7126
|
path: absoluteInput,
|
|
6599
7127
|
repoRoot,
|
|
6600
|
-
title:
|
|
7128
|
+
title: path15.basename(absoluteInput) || absoluteInput
|
|
6601
7129
|
};
|
|
6602
7130
|
}
|
|
6603
7131
|
const github = normalizeGitHubRepoRootUrl(input);
|
|
@@ -6626,16 +7154,16 @@ async function resolveManagedSourceInput(rootDir, input, options = {}) {
|
|
|
6626
7154
|
};
|
|
6627
7155
|
}
|
|
6628
7156
|
function directorySourceIdsFor(manifests, inputPath) {
|
|
6629
|
-
return manifests.filter((manifest) => manifest.originalPath && withinRoot(
|
|
7157
|
+
return manifests.filter((manifest) => manifest.originalPath && withinRoot(path15.resolve(inputPath), path15.resolve(manifest.originalPath))).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
|
|
6630
7158
|
}
|
|
6631
7159
|
function fileSourceIdsFor(manifests, inputPath) {
|
|
6632
|
-
const absoluteInput =
|
|
6633
|
-
return manifests.filter((manifest) => manifest.originalPath &&
|
|
7160
|
+
const absoluteInput = path15.resolve(inputPath);
|
|
7161
|
+
return manifests.filter((manifest) => manifest.originalPath && path15.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
|
|
6634
7162
|
}
|
|
6635
7163
|
async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
6636
7164
|
const manifestsBefore = await listManifests(rootDir);
|
|
6637
7165
|
const previousInScope = manifestsBefore.filter(
|
|
6638
|
-
(manifest) => manifest.originalPath && withinRoot(
|
|
7166
|
+
(manifest) => manifest.originalPath && withinRoot(path15.resolve(inputPath), path15.resolve(manifest.originalPath))
|
|
6639
7167
|
);
|
|
6640
7168
|
const result = await ingestDirectory(rootDir, inputPath, { repoRoot });
|
|
6641
7169
|
const removed = [];
|
|
@@ -6643,7 +7171,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
|
6643
7171
|
if (!manifest.originalPath) {
|
|
6644
7172
|
continue;
|
|
6645
7173
|
}
|
|
6646
|
-
if (await fileExists(
|
|
7174
|
+
if (await fileExists(path15.resolve(manifest.originalPath))) {
|
|
6647
7175
|
continue;
|
|
6648
7176
|
}
|
|
6649
7177
|
const removedManifest = await removeManifestBySourceId(rootDir, manifest.sourceId);
|
|
@@ -6653,7 +7181,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
|
6653
7181
|
}
|
|
6654
7182
|
const manifestsAfter = await listManifests(rootDir);
|
|
6655
7183
|
return {
|
|
6656
|
-
title:
|
|
7184
|
+
title: path15.basename(inputPath) || inputPath,
|
|
6657
7185
|
sourceIds: directorySourceIdsFor(manifestsAfter, inputPath),
|
|
6658
7186
|
counts: {
|
|
6659
7187
|
scannedCount: result.scannedCount,
|
|
@@ -6669,7 +7197,7 @@ async function syncFileSource(rootDir, inputPath) {
|
|
|
6669
7197
|
const result = await ingestInputDetailed(rootDir, inputPath);
|
|
6670
7198
|
const manifestsAfter = await listManifests(rootDir);
|
|
6671
7199
|
return {
|
|
6672
|
-
title:
|
|
7200
|
+
title: path15.basename(inputPath, path15.extname(inputPath)) || inputPath,
|
|
6673
7201
|
sourceIds: fileSourceIdsFor(manifestsAfter, inputPath),
|
|
6674
7202
|
counts: {
|
|
6675
7203
|
scannedCount: result.scannedCount,
|
|
@@ -6703,10 +7231,10 @@ async function runGitCommand(cwd, args) {
|
|
|
6703
7231
|
}
|
|
6704
7232
|
async function syncGitHubRepoSource(rootDir, entry) {
|
|
6705
7233
|
const workingDir = await managedSourceWorkingDir(rootDir, entry.id);
|
|
6706
|
-
const externalCheckoutDir = entry.checkoutDir ?
|
|
6707
|
-
const checkoutDir = externalCheckoutDir ??
|
|
7234
|
+
const externalCheckoutDir = entry.checkoutDir ? path15.resolve(entry.checkoutDir) : void 0;
|
|
7235
|
+
const checkoutDir = externalCheckoutDir ?? path15.join(workingDir, "checkout");
|
|
6708
7236
|
if (!externalCheckoutDir) {
|
|
6709
|
-
await
|
|
7237
|
+
await fs13.rm(checkoutDir, { recursive: true, force: true });
|
|
6710
7238
|
}
|
|
6711
7239
|
await ensureDir(workingDir);
|
|
6712
7240
|
if (!entry.url) {
|
|
@@ -6723,7 +7251,7 @@ async function syncGitHubRepoSource(rootDir, entry) {
|
|
|
6723
7251
|
cloneArgs.push("--branch", branch);
|
|
6724
7252
|
}
|
|
6725
7253
|
cloneArgs.push(github.cloneUrl, checkoutDir);
|
|
6726
|
-
if (await fileExists(
|
|
7254
|
+
if (await fileExists(path15.join(checkoutDir, ".git"))) {
|
|
6727
7255
|
await runGitCommand(checkoutDir, ["remote", "set-url", "origin", github.cloneUrl]);
|
|
6728
7256
|
if (branch) {
|
|
6729
7257
|
await runGitCommand(checkoutDir, ["fetch", "--depth", "1", "origin", branch]);
|
|
@@ -6734,11 +7262,11 @@ async function syncGitHubRepoSource(rootDir, entry) {
|
|
|
6734
7262
|
await runGitCommand(checkoutDir, ["checkout", "--detach", "FETCH_HEAD"]);
|
|
6735
7263
|
}
|
|
6736
7264
|
} else {
|
|
6737
|
-
const existingEntries = await
|
|
7265
|
+
const existingEntries = await fs13.readdir(checkoutDir).catch(() => []);
|
|
6738
7266
|
if (externalCheckoutDir && existingEntries.length > 0) {
|
|
6739
7267
|
throw new Error(`Checkout directory exists but is not a Git repository: ${checkoutDir}`);
|
|
6740
7268
|
}
|
|
6741
|
-
await ensureDir(
|
|
7269
|
+
await ensureDir(path15.dirname(checkoutDir));
|
|
6742
7270
|
await runGitCommand(workingDir, cloneArgs);
|
|
6743
7271
|
}
|
|
6744
7272
|
if (ref) {
|
|
@@ -6865,7 +7393,7 @@ function scopedNodeIds(graph, sourceIds) {
|
|
|
6865
7393
|
async function loadSourceAnalyses(rootDir, sourceIds) {
|
|
6866
7394
|
const { paths } = await loadVaultConfig(rootDir);
|
|
6867
7395
|
const analyses = await Promise.all(
|
|
6868
|
-
sourceIds.map(async (sourceId) => await readJsonFile(
|
|
7396
|
+
sourceIds.map(async (sourceId) => await readJsonFile(path15.join(paths.analysesDir, `${sourceId}.json`)))
|
|
6869
7397
|
);
|
|
6870
7398
|
return analyses.filter((analysis) => Boolean(analysis?.sourceId));
|
|
6871
7399
|
}
|
|
@@ -7026,9 +7554,9 @@ async function writeSourceBriefForScope(rootDir, source) {
|
|
|
7026
7554
|
confidence: 0.82
|
|
7027
7555
|
}
|
|
7028
7556
|
});
|
|
7029
|
-
const absolutePath =
|
|
7030
|
-
await ensureDir(
|
|
7031
|
-
await
|
|
7557
|
+
const absolutePath = path15.join(paths.wikiDir, output.page.path);
|
|
7558
|
+
await ensureDir(path15.dirname(absolutePath));
|
|
7559
|
+
await fs13.writeFile(absolutePath, output.content, "utf8");
|
|
7032
7560
|
return absolutePath;
|
|
7033
7561
|
}
|
|
7034
7562
|
async function writeSourceBrief(rootDir, source) {
|
|
@@ -7316,7 +7844,7 @@ function selectGuidedTargetPages(scope, sourcePages, questions) {
|
|
|
7316
7844
|
return (matchedTargets.length ? matchedTargets : canonicalPages).slice(0, 6);
|
|
7317
7845
|
}
|
|
7318
7846
|
function insightRelativePathForTarget(page, scope) {
|
|
7319
|
-
const basename =
|
|
7847
|
+
const basename = path15.basename(page.path);
|
|
7320
7848
|
if (page.kind === "concept") {
|
|
7321
7849
|
return `insights/concepts/${basename}`;
|
|
7322
7850
|
}
|
|
@@ -7543,7 +8071,7 @@ async function stageSourceReviewForScope(rootDir, scope) {
|
|
|
7543
8071
|
return {
|
|
7544
8072
|
sourceId: scope.id,
|
|
7545
8073
|
pageId: output.page.id,
|
|
7546
|
-
reviewPath:
|
|
8074
|
+
reviewPath: path15.join(approval.approvalDir, "wiki", output.page.path),
|
|
7547
8075
|
staged: true,
|
|
7548
8076
|
approvalId: approval.approvalId,
|
|
7549
8077
|
approvalDir: approval.approvalDir
|
|
@@ -7610,7 +8138,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
|
|
|
7610
8138
|
const evidenceState = contradictions.length > 0 ? "conflicting" : session.targetedPagePaths.some(
|
|
7611
8139
|
(targetPath) => sourcePages.some((page) => page.path === targetPath && page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId)))
|
|
7612
8140
|
) ? "reinforcing" : session.targetedPagePaths.length ? "new" : "needs_judgment";
|
|
7613
|
-
const relativeBriefPath = session.briefPath &&
|
|
8141
|
+
const relativeBriefPath = session.briefPath && path15.isAbsolute(session.briefPath) ? path15.relative(paths.wikiDir, session.briefPath) : session.briefPath;
|
|
7614
8142
|
const sessionMarkdown = [
|
|
7615
8143
|
`# Guided Session: ${scope.title}`,
|
|
7616
8144
|
"",
|
|
@@ -7693,9 +8221,9 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
|
|
|
7693
8221
|
async function persistSourceSessionPage(rootDir, scope, session) {
|
|
7694
8222
|
const { paths } = await loadVaultConfig(rootDir);
|
|
7695
8223
|
const output = await buildSourceSessionSavedPage(rootDir, scope, session);
|
|
7696
|
-
const absolutePath =
|
|
7697
|
-
await ensureDir(
|
|
7698
|
-
await
|
|
8224
|
+
const absolutePath = path15.join(paths.wikiDir, output.page.path);
|
|
8225
|
+
await ensureDir(path15.dirname(absolutePath));
|
|
8226
|
+
await fs13.writeFile(absolutePath, output.content, "utf8");
|
|
7699
8227
|
return { pageId: output.page.id, sessionPath: absolutePath };
|
|
7700
8228
|
}
|
|
7701
8229
|
async function buildGuidedUpdatePages(rootDir, scope, session) {
|
|
@@ -7723,9 +8251,9 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
|
|
|
7723
8251
|
targetPages.map(async (targetPage) => {
|
|
7724
8252
|
const evidenceState = classifyGuidedEvidenceState(scope, targetPage, contradictions);
|
|
7725
8253
|
const relativePath = useCanonicalTargets && targetPage ? targetPage.path : targetPage ? insightRelativePathForTarget(targetPage, scope) : `insights/topics/${slugify(scope.title)}.md`;
|
|
7726
|
-
const absolutePath =
|
|
7727
|
-
const existingContent = await fileExists(absolutePath) ? await
|
|
7728
|
-
const parsed = existingContent ?
|
|
8254
|
+
const absolutePath = path15.join(paths.wikiDir, relativePath);
|
|
8255
|
+
const existingContent = await fileExists(absolutePath) ? await fs13.readFile(absolutePath, "utf8") : "";
|
|
8256
|
+
const parsed = existingContent ? matter5(existingContent) : { data: {}, content: "" };
|
|
7729
8257
|
const existingData = parsed.data;
|
|
7730
8258
|
const existingSourceIds = Array.isArray(existingData.source_ids) ? existingData.source_ids.filter((value) => typeof value === "string") : [];
|
|
7731
8259
|
const existingProjectIds = Array.isArray(existingData.project_ids) ? existingData.project_ids.filter((value) => typeof value === "string") : [];
|
|
@@ -7786,7 +8314,7 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
|
|
|
7786
8314
|
""
|
|
7787
8315
|
].join("\n");
|
|
7788
8316
|
const nextBody = replaceMarkedSection(baseBody, scope.id, updateBlock);
|
|
7789
|
-
const content =
|
|
8317
|
+
const content = matter5.stringify(
|
|
7790
8318
|
`${nextBody.trimEnd()}
|
|
7791
8319
|
`,
|
|
7792
8320
|
JSON.parse(
|
|
@@ -7895,8 +8423,8 @@ async function stageSourceGuideForScope(rootDir, scope, options = {}) {
|
|
|
7895
8423
|
}
|
|
7896
8424
|
);
|
|
7897
8425
|
session.status = "staged";
|
|
7898
|
-
session.reviewPath =
|
|
7899
|
-
session.guidePath =
|
|
8426
|
+
session.reviewPath = path15.join(approval.approvalDir, "wiki", reviewOutput.page.path);
|
|
8427
|
+
session.guidePath = path15.join(approval.approvalDir, "wiki", guideOutput.page.path);
|
|
7900
8428
|
session.approvalId = approval.approvalId;
|
|
7901
8429
|
session.approvalDir = approval.approvalDir;
|
|
7902
8430
|
const persisted = await persistSourceSessionPage(rootDir, scope, session);
|
|
@@ -8039,7 +8567,7 @@ async function addManagedSource(rootDir, input, options = {}) {
|
|
|
8039
8567
|
ref: resolved.kind === "github_repo" ? resolved.ref : existing.ref,
|
|
8040
8568
|
checkoutDir: resolved.kind === "github_repo" ? resolved.checkoutDir ?? existing.checkoutDir : existing.checkoutDir
|
|
8041
8569
|
} : {
|
|
8042
|
-
id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind,
|
|
8570
|
+
id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path15.resolve(resolved.path), resolved.title) : stableManagedSourceId(
|
|
8043
8571
|
resolved.kind,
|
|
8044
8572
|
resolved.kind === "github_repo" ? `${resolved.url}#branch=${resolved.branch ?? ""}#ref=${resolved.ref ?? ""}` : resolved.url,
|
|
8045
8573
|
resolved.title
|
|
@@ -8174,7 +8702,7 @@ async function deleteManagedSource(rootDir, id) {
|
|
|
8174
8702
|
sources.filter((source) => source.id !== id)
|
|
8175
8703
|
);
|
|
8176
8704
|
const workingDir = await managedSourceWorkingDir(rootDir, id);
|
|
8177
|
-
await
|
|
8705
|
+
await fs13.rm(workingDir, { recursive: true, force: true });
|
|
8178
8706
|
return { removed: target };
|
|
8179
8707
|
}
|
|
8180
8708
|
|
|
@@ -8182,11 +8710,11 @@ async function deleteManagedSource(rootDir, id) {
|
|
|
8182
8710
|
import { execFile as execFile2 } from "child_process";
|
|
8183
8711
|
import { randomUUID } from "crypto";
|
|
8184
8712
|
import { EventEmitter } from "events";
|
|
8185
|
-
import
|
|
8713
|
+
import fs14 from "fs/promises";
|
|
8186
8714
|
import http from "http";
|
|
8187
|
-
import
|
|
8715
|
+
import path16 from "path";
|
|
8188
8716
|
import { promisify as promisify2 } from "util";
|
|
8189
|
-
import
|
|
8717
|
+
import matter6 from "gray-matter";
|
|
8190
8718
|
import mime from "mime-types";
|
|
8191
8719
|
|
|
8192
8720
|
// src/graph-presentation.ts
|
|
@@ -8338,7 +8866,7 @@ function toViewerLintFindings(findings) {
|
|
|
8338
8866
|
var execFileAsync2 = promisify2(execFile2);
|
|
8339
8867
|
async function isReadableFile(absolutePath) {
|
|
8340
8868
|
try {
|
|
8341
|
-
const stats = await
|
|
8869
|
+
const stats = await fs14.stat(absolutePath);
|
|
8342
8870
|
return stats.isFile();
|
|
8343
8871
|
} catch {
|
|
8344
8872
|
return false;
|
|
@@ -8349,15 +8877,15 @@ async function readViewerPage(rootDir, relativePath) {
|
|
|
8349
8877
|
return null;
|
|
8350
8878
|
}
|
|
8351
8879
|
const { paths } = await loadVaultConfig(rootDir);
|
|
8352
|
-
const absolutePath =
|
|
8880
|
+
const absolutePath = path16.resolve(paths.wikiDir, relativePath);
|
|
8353
8881
|
if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
|
|
8354
8882
|
return null;
|
|
8355
8883
|
}
|
|
8356
|
-
const raw = await
|
|
8357
|
-
const parsed =
|
|
8884
|
+
const raw = await fs14.readFile(absolutePath, "utf8");
|
|
8885
|
+
const parsed = matter6(raw);
|
|
8358
8886
|
return {
|
|
8359
8887
|
path: relativePath,
|
|
8360
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title :
|
|
8888
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path16.basename(relativePath, path16.extname(relativePath)),
|
|
8361
8889
|
frontmatter: parsed.data,
|
|
8362
8890
|
content: parsed.content,
|
|
8363
8891
|
assets: normalizeOutputAssets(parsed.data.output_assets)
|
|
@@ -8368,12 +8896,12 @@ async function readViewerAsset(rootDir, relativePath) {
|
|
|
8368
8896
|
return null;
|
|
8369
8897
|
}
|
|
8370
8898
|
const { paths } = await loadVaultConfig(rootDir);
|
|
8371
|
-
const absolutePath =
|
|
8899
|
+
const absolutePath = path16.resolve(paths.wikiDir, relativePath);
|
|
8372
8900
|
if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
|
|
8373
8901
|
return null;
|
|
8374
8902
|
}
|
|
8375
8903
|
return {
|
|
8376
|
-
buffer: await
|
|
8904
|
+
buffer: await fs14.readFile(absolutePath),
|
|
8377
8905
|
mimeType: mime.lookup(absolutePath) || "application/octet-stream"
|
|
8378
8906
|
};
|
|
8379
8907
|
}
|
|
@@ -8409,8 +8937,8 @@ async function writeInboxClip(rootDir, body) {
|
|
|
8409
8937
|
const tags = Array.isArray(body.tags) ? body.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0) : [];
|
|
8410
8938
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8411
8939
|
const fileName = `${now.replace(/[:.]/g, "-")}-${slugForClip(title)}.md`;
|
|
8412
|
-
const inboxPath =
|
|
8413
|
-
await
|
|
8940
|
+
const inboxPath = path16.join(paths.inboxDir, fileName);
|
|
8941
|
+
await fs14.mkdir(paths.inboxDir, { recursive: true });
|
|
8414
8942
|
const lines = [
|
|
8415
8943
|
"---",
|
|
8416
8944
|
`title: ${JSON.stringify(title)}`,
|
|
@@ -8427,17 +8955,17 @@ async function writeInboxClip(rootDir, body) {
|
|
|
8427
8955
|
selectionHtml && !markdown ? ["", "## Original HTML", "", "```html", selectionHtml, "```"].join("\n") : void 0,
|
|
8428
8956
|
""
|
|
8429
8957
|
].filter((line) => line !== void 0);
|
|
8430
|
-
await
|
|
8958
|
+
await fs14.writeFile(inboxPath, lines.join("\n"), "utf8");
|
|
8431
8959
|
const result = await importInbox(rootDir, paths.inboxDir);
|
|
8432
8960
|
return { mode: "inbox", inboxPath, result };
|
|
8433
8961
|
}
|
|
8434
8962
|
async function ensureViewerDist(viewerDistDir) {
|
|
8435
|
-
const indexPath =
|
|
8963
|
+
const indexPath = path16.join(viewerDistDir, "index.html");
|
|
8436
8964
|
if (await fileExists(indexPath)) {
|
|
8437
8965
|
return;
|
|
8438
8966
|
}
|
|
8439
|
-
const viewerProjectDir =
|
|
8440
|
-
if (await fileExists(
|
|
8967
|
+
const viewerProjectDir = path16.dirname(viewerDistDir);
|
|
8968
|
+
if (await fileExists(path16.join(viewerProjectDir, "package.json"))) {
|
|
8441
8969
|
await execFileAsync2("pnpm", ["build"], { cwd: viewerProjectDir });
|
|
8442
8970
|
}
|
|
8443
8971
|
}
|
|
@@ -8460,7 +8988,7 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
8460
8988
|
response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
|
|
8461
8989
|
return;
|
|
8462
8990
|
}
|
|
8463
|
-
const reportPath =
|
|
8991
|
+
const reportPath = path16.join(paths.wikiDir, "graph", "report.json");
|
|
8464
8992
|
const report = await readJsonFile(reportPath) ?? null;
|
|
8465
8993
|
response.writeHead(200, { "content-type": "application/json" });
|
|
8466
8994
|
response.end(JSON.stringify(buildViewerGraphArtifact(graph, { report, full: options.full ?? false })));
|
|
@@ -8524,13 +9052,13 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
8524
9052
|
return;
|
|
8525
9053
|
}
|
|
8526
9054
|
if (url.pathname === "/api/graph-report") {
|
|
8527
|
-
const reportPath =
|
|
9055
|
+
const reportPath = path16.join(paths.wikiDir, "graph", "report.json");
|
|
8528
9056
|
if (!await fileExists(reportPath)) {
|
|
8529
9057
|
response.writeHead(404, { "content-type": "application/json" });
|
|
8530
9058
|
response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
|
|
8531
9059
|
return;
|
|
8532
9060
|
}
|
|
8533
|
-
const body = await
|
|
9061
|
+
const body = await fs14.readFile(reportPath, "utf8");
|
|
8534
9062
|
response.writeHead(200, { "content-type": "application/json" });
|
|
8535
9063
|
response.end(body);
|
|
8536
9064
|
return;
|
|
@@ -8768,7 +9296,7 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
8768
9296
|
return;
|
|
8769
9297
|
}
|
|
8770
9298
|
if (url.pathname === "/api/workspace") {
|
|
8771
|
-
const reportPath =
|
|
9299
|
+
const reportPath = path16.join(paths.wikiDir, "graph", "report.json");
|
|
8772
9300
|
const [graphRaw, reportRaw, approvalsRaw, candidatesRaw, memoryTasksRaw, watchStatusRaw, lintRaw, doctorRaw] = await Promise.all([
|
|
8773
9301
|
readJsonFile(paths.graphPath).catch(() => null),
|
|
8774
9302
|
readJsonFile(reportPath).catch(() => null),
|
|
@@ -8893,15 +9421,15 @@ async function startGraphServer(rootDir, port, options = {}) {
|
|
|
8893
9421
|
return;
|
|
8894
9422
|
}
|
|
8895
9423
|
const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
8896
|
-
const target =
|
|
8897
|
-
const fallback =
|
|
9424
|
+
const target = path16.join(paths.viewerDistDir, relativePath);
|
|
9425
|
+
const fallback = path16.join(paths.viewerDistDir, "index.html");
|
|
8898
9426
|
const filePath = await fileExists(target) ? target : fallback;
|
|
8899
9427
|
if (!await fileExists(filePath)) {
|
|
8900
9428
|
response.writeHead(503, { "content-type": "text/plain" });
|
|
8901
9429
|
response.end("Viewer build not found. Run `pnpm build` first.");
|
|
8902
9430
|
return;
|
|
8903
9431
|
}
|
|
8904
|
-
const staticBody = await
|
|
9432
|
+
const staticBody = await fs14.readFile(filePath);
|
|
8905
9433
|
response.writeHead(200, { "content-type": mime.lookup(filePath) || "text/plain" });
|
|
8906
9434
|
response.end(staticBody);
|
|
8907
9435
|
} catch (error) {
|
|
@@ -8941,7 +9469,7 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
|
|
|
8941
9469
|
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
8942
9470
|
}
|
|
8943
9471
|
await ensureViewerDist(paths.viewerDistDir);
|
|
8944
|
-
const indexPath =
|
|
9472
|
+
const indexPath = path16.join(paths.viewerDistDir, "index.html");
|
|
8945
9473
|
if (!await fileExists(indexPath)) {
|
|
8946
9474
|
throw new Error("Viewer build not found. Run `pnpm build` first.");
|
|
8947
9475
|
}
|
|
@@ -8967,17 +9495,17 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
|
|
|
8967
9495
|
} : null;
|
|
8968
9496
|
})
|
|
8969
9497
|
);
|
|
8970
|
-
const rawHtml = await
|
|
9498
|
+
const rawHtml = await fs14.readFile(indexPath, "utf8");
|
|
8971
9499
|
const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
|
|
8972
9500
|
const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
|
|
8973
|
-
const scriptPath = scriptMatch?.[1] ?
|
|
8974
|
-
const stylePath = styleMatch?.[1] ?
|
|
9501
|
+
const scriptPath = scriptMatch?.[1] ? path16.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
|
|
9502
|
+
const stylePath = styleMatch?.[1] ? path16.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
|
|
8975
9503
|
if (!scriptPath || !await fileExists(scriptPath)) {
|
|
8976
9504
|
throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
|
|
8977
9505
|
}
|
|
8978
|
-
const script = await
|
|
8979
|
-
const style = stylePath && await fileExists(stylePath) ? await
|
|
8980
|
-
const report = await readJsonFile(
|
|
9506
|
+
const script = await fs14.readFile(scriptPath, "utf8");
|
|
9507
|
+
const style = stylePath && await fileExists(stylePath) ? await fs14.readFile(stylePath, "utf8") : "";
|
|
9508
|
+
const report = await readJsonFile(path16.join(paths.wikiDir, "graph", "report.json"));
|
|
8981
9509
|
const embeddedData = JSON.stringify(
|
|
8982
9510
|
{ graph: buildViewerGraphArtifact(graph, { report, full: options.full ?? false }), pages: pages.filter(Boolean), report },
|
|
8983
9511
|
null,
|
|
@@ -9000,9 +9528,9 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
|
|
|
9000
9528
|
"</html>",
|
|
9001
9529
|
""
|
|
9002
9530
|
].filter(Boolean).join("\n");
|
|
9003
|
-
await
|
|
9004
|
-
await
|
|
9005
|
-
return
|
|
9531
|
+
await fs14.mkdir(path16.dirname(outputPath), { recursive: true });
|
|
9532
|
+
await fs14.writeFile(outputPath, html, "utf8");
|
|
9533
|
+
return path16.resolve(outputPath);
|
|
9006
9534
|
}
|
|
9007
9535
|
export {
|
|
9008
9536
|
ALL_MIGRATIONS,
|
|
@@ -9023,6 +9551,7 @@ export {
|
|
|
9023
9551
|
addWatchedRoot,
|
|
9024
9552
|
applyDecayToPages,
|
|
9025
9553
|
archiveCandidate,
|
|
9554
|
+
askChatSession,
|
|
9026
9555
|
assertProviderCapability,
|
|
9027
9556
|
autoCommitWikiChanges,
|
|
9028
9557
|
benchmarkVault,
|
|
@@ -9045,6 +9574,7 @@ export {
|
|
|
9045
9574
|
createWebSearchAdapter,
|
|
9046
9575
|
defaultVaultConfig,
|
|
9047
9576
|
defaultVaultSchema,
|
|
9577
|
+
deleteChatSession,
|
|
9048
9578
|
deleteContextPack,
|
|
9049
9579
|
deleteManagedSource,
|
|
9050
9580
|
detectVaultVersion,
|
|
@@ -9060,6 +9590,7 @@ export {
|
|
|
9060
9590
|
expectedModelPath,
|
|
9061
9591
|
explainGraphVault,
|
|
9062
9592
|
exploreVault,
|
|
9593
|
+
exportAiPack,
|
|
9063
9594
|
exportGraphFormat,
|
|
9064
9595
|
exportGraphHtml,
|
|
9065
9596
|
exportGraphReportHtml,
|
|
@@ -9092,6 +9623,7 @@ export {
|
|
|
9092
9623
|
lintVault,
|
|
9093
9624
|
listApprovals,
|
|
9094
9625
|
listCandidates,
|
|
9626
|
+
listChatSessions,
|
|
9095
9627
|
listContextPacks,
|
|
9096
9628
|
listGodNodes,
|
|
9097
9629
|
listGraphHyperedges,
|
|
@@ -9121,6 +9653,7 @@ export {
|
|
|
9121
9653
|
queryGraphVault,
|
|
9122
9654
|
queryVault,
|
|
9123
9655
|
readApproval,
|
|
9656
|
+
readChatSession,
|
|
9124
9657
|
readContextPack,
|
|
9125
9658
|
readExtractedText,
|
|
9126
9659
|
readGraphReport,
|