@swarmvaultai/engine 0.3.0 → 0.5.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-B3FC4J3P.js +1214 -0
- package/dist/index.d.ts +73 -6
- package/dist/index.js +1941 -149
- package/dist/registry-KVJAO5DF.js +12 -0
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
uniqueBy,
|
|
22
22
|
writeFileIfChanged,
|
|
23
23
|
writeJsonFile
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-B3FC4J3P.js";
|
|
25
25
|
|
|
26
26
|
// src/agents.ts
|
|
27
27
|
import crypto from "crypto";
|
|
@@ -4504,6 +4504,7 @@ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
|
|
|
4504
4504
|
import fs7 from "fs/promises";
|
|
4505
4505
|
import os from "os";
|
|
4506
4506
|
import path7 from "path";
|
|
4507
|
+
import { Readable } from "stream";
|
|
4507
4508
|
import { parse as parseCsvSync } from "csv-parse/sync";
|
|
4508
4509
|
import { strFromU8, unzipSync } from "fflate";
|
|
4509
4510
|
import { JSDOM } from "jsdom";
|
|
@@ -5171,6 +5172,508 @@ async function extractEpubChapters(input) {
|
|
|
5171
5172
|
};
|
|
5172
5173
|
}
|
|
5173
5174
|
}
|
|
5175
|
+
function timestampFromMs(value) {
|
|
5176
|
+
const totalMs = Math.max(0, Math.floor(value));
|
|
5177
|
+
const totalSeconds = Math.floor(totalMs / 1e3);
|
|
5178
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
5179
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
5180
|
+
const seconds = totalSeconds % 60;
|
|
5181
|
+
const milliseconds = totalMs % 1e3;
|
|
5182
|
+
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}.${String(
|
|
5183
|
+
milliseconds
|
|
5184
|
+
).padStart(3, "0")}`;
|
|
5185
|
+
}
|
|
5186
|
+
function normalizeDelimitedList(values) {
|
|
5187
|
+
const unique = [...new Set(values.map((value) => normalizeWhitespace(value)).filter(Boolean))];
|
|
5188
|
+
return unique.length ? unique.join(", ") : void 0;
|
|
5189
|
+
}
|
|
5190
|
+
function normalizeIsoDate(value) {
|
|
5191
|
+
if (value instanceof Date && Number.isFinite(value.getTime())) {
|
|
5192
|
+
return value.toISOString();
|
|
5193
|
+
}
|
|
5194
|
+
if (typeof value === "string" && value.trim()) {
|
|
5195
|
+
const parsed = new Date(value);
|
|
5196
|
+
if (Number.isFinite(parsed.getTime())) {
|
|
5197
|
+
return parsed.toISOString();
|
|
5198
|
+
}
|
|
5199
|
+
}
|
|
5200
|
+
return void 0;
|
|
5201
|
+
}
|
|
5202
|
+
function addressNames(value) {
|
|
5203
|
+
if (!value || typeof value !== "object" || !("value" in value) || !Array.isArray(value.value)) {
|
|
5204
|
+
return [];
|
|
5205
|
+
}
|
|
5206
|
+
return value.value.map((entry) => normalizeWhitespace(entry.name ?? entry.address ?? "")).filter(Boolean);
|
|
5207
|
+
}
|
|
5208
|
+
function addressList(value) {
|
|
5209
|
+
return normalizeDelimitedList(addressNames(value));
|
|
5210
|
+
}
|
|
5211
|
+
function emailConversationId(parsed) {
|
|
5212
|
+
const asArray = (value) => Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
5213
|
+
return normalizeWhitespace(parsed.messageId ?? "") || normalizeWhitespace(asArray(parsed.inReplyTo)[0] ?? "") || normalizeWhitespace(asArray(parsed.references)[0] ?? "") || void 0;
|
|
5214
|
+
}
|
|
5215
|
+
function emailBodyMarkdown(parsed) {
|
|
5216
|
+
const text = normalizeDocumentText(parsed.text ?? "");
|
|
5217
|
+
if (text) {
|
|
5218
|
+
return text;
|
|
5219
|
+
}
|
|
5220
|
+
if (typeof parsed.html === "string" && parsed.html.trim()) {
|
|
5221
|
+
return normalizeDocumentText(htmlToMarkdown(parsed.html));
|
|
5222
|
+
}
|
|
5223
|
+
return "";
|
|
5224
|
+
}
|
|
5225
|
+
function normalizeParsedEmail(parsed, fallbackTitle) {
|
|
5226
|
+
const title = normalizeWhitespace(parsed.subject ?? "") || fallbackTitle;
|
|
5227
|
+
const sender = addressList(parsed.from);
|
|
5228
|
+
const recipients = addressList(parsed.to);
|
|
5229
|
+
const cc = addressList(parsed.cc);
|
|
5230
|
+
const occurredAt = normalizeIsoDate(parsed.date);
|
|
5231
|
+
const participants = normalizeDelimitedList([...addressNames(parsed.from), ...addressNames(parsed.to), ...addressNames(parsed.cc)]);
|
|
5232
|
+
const conversationId = emailConversationId(parsed);
|
|
5233
|
+
const body = emailBodyMarkdown(parsed);
|
|
5234
|
+
const attachmentCount = Array.isArray(parsed.attachments) ? parsed.attachments.length : 0;
|
|
5235
|
+
return {
|
|
5236
|
+
title,
|
|
5237
|
+
conversationId,
|
|
5238
|
+
metadata: {
|
|
5239
|
+
...occurredAt ? { occurred_at: occurredAt } : {},
|
|
5240
|
+
...sender ? { sender } : {},
|
|
5241
|
+
...recipients ? { recipients } : {},
|
|
5242
|
+
...cc ? { cc } : {},
|
|
5243
|
+
...participants ? { participants } : {},
|
|
5244
|
+
...conversationId ? { conversation_id: conversationId } : {},
|
|
5245
|
+
...normalizeWhitespace(parsed.messageId ?? "") ? { message_id: normalizeWhitespace(parsed.messageId ?? "") } : {},
|
|
5246
|
+
...attachmentCount ? { attachment_count: String(attachmentCount) } : {}
|
|
5247
|
+
},
|
|
5248
|
+
markdown: [
|
|
5249
|
+
`# ${title}`,
|
|
5250
|
+
"",
|
|
5251
|
+
...occurredAt ? [`Date: ${occurredAt}`] : [],
|
|
5252
|
+
...sender ? [`From: ${sender}`] : [],
|
|
5253
|
+
...recipients ? [`To: ${recipients}`] : [],
|
|
5254
|
+
...cc ? [`CC: ${cc}`] : [],
|
|
5255
|
+
...conversationId ? [`Conversation ID: ${conversationId}`] : [],
|
|
5256
|
+
...attachmentCount ? [`Attachments: ${attachmentCount}`] : [],
|
|
5257
|
+
"",
|
|
5258
|
+
"## Message",
|
|
5259
|
+
"",
|
|
5260
|
+
body || "No readable body content was extracted from this email.",
|
|
5261
|
+
""
|
|
5262
|
+
].join("\n")
|
|
5263
|
+
};
|
|
5264
|
+
}
|
|
5265
|
+
function calendarAttendees(value) {
|
|
5266
|
+
if (!value) {
|
|
5267
|
+
return [];
|
|
5268
|
+
}
|
|
5269
|
+
const attendees = Array.isArray(value) ? value : [value];
|
|
5270
|
+
return attendees.map((entry) => {
|
|
5271
|
+
if (!entry || typeof entry !== "object") {
|
|
5272
|
+
return "";
|
|
5273
|
+
}
|
|
5274
|
+
const item = entry;
|
|
5275
|
+
const name = normalizeWhitespace(String(item.params?.CN ?? ""));
|
|
5276
|
+
const address = normalizeWhitespace(String(item.val ?? item.value ?? ""));
|
|
5277
|
+
return name || address;
|
|
5278
|
+
}).filter(Boolean);
|
|
5279
|
+
}
|
|
5280
|
+
function slackFormatSpeakerId(input, usersById) {
|
|
5281
|
+
return usersById.get(input) ?? input;
|
|
5282
|
+
}
|
|
5283
|
+
function slackNormalizeText(text, usersById) {
|
|
5284
|
+
return normalizeWhitespace(
|
|
5285
|
+
text.replace(/<@([A-Z0-9]+)>/g, (_, userId) => `@${slackFormatSpeakerId(userId, usersById)}`).replace(/<#[A-Z0-9]+\|([^>]+)>/g, "#$1").replace(/<(https?:\/\/[^>|]+)\|([^>]+)>/g, "$2 ($1)").replace(/<(https?:\/\/[^>]+)>/g, "$1")
|
|
5286
|
+
);
|
|
5287
|
+
}
|
|
5288
|
+
function slackMessageTimestamp(ts2, fallbackDate) {
|
|
5289
|
+
const numeric = Number(ts2);
|
|
5290
|
+
if (Number.isFinite(numeric) && numeric > 0) {
|
|
5291
|
+
return new Date(numeric * 1e3).toISOString();
|
|
5292
|
+
}
|
|
5293
|
+
return (/* @__PURE__ */ new Date(`${fallbackDate}T00:00:00.000Z`)).toISOString();
|
|
5294
|
+
}
|
|
5295
|
+
async function loadZipMessageBuffers(bytes) {
|
|
5296
|
+
const { MboxStream } = await import("node-mbox");
|
|
5297
|
+
const stream = MboxStream(Readable.from([bytes]));
|
|
5298
|
+
return await new Promise((resolve, reject) => {
|
|
5299
|
+
const messages = [];
|
|
5300
|
+
stream.on("data", (message) => {
|
|
5301
|
+
messages.push(Buffer.isBuffer(message) ? message : Buffer.from(message));
|
|
5302
|
+
});
|
|
5303
|
+
stream.on("error", reject);
|
|
5304
|
+
stream.on("finish", () => resolve(messages));
|
|
5305
|
+
stream.on("end", () => resolve(messages));
|
|
5306
|
+
});
|
|
5307
|
+
}
|
|
5308
|
+
function archiveEntriesAsText(archive) {
|
|
5309
|
+
return new Map(
|
|
5310
|
+
Object.entries(archive).filter(([, value]) => value).map(([entryPath, value]) => [entryPath, strFromU8(value)])
|
|
5311
|
+
);
|
|
5312
|
+
}
|
|
5313
|
+
function looksLikeSlackEntries(entries) {
|
|
5314
|
+
const all = [...entries];
|
|
5315
|
+
const hasChannelsIndex = all.some(
|
|
5316
|
+
(entry) => entry === "channels.json" || entry === "groups.json" || entry === "dms.json" || entry === "mpims.json"
|
|
5317
|
+
);
|
|
5318
|
+
const hasChannelDayFiles = all.some((entry) => /^[^/]+\/\d{4}-\d{2}-\d{2}\.json$/i.test(entry));
|
|
5319
|
+
return hasChannelsIndex && hasChannelDayFiles;
|
|
5320
|
+
}
|
|
5321
|
+
function slackEntriesFromChannelIndex(raw, usersById) {
|
|
5322
|
+
const entries = /* @__PURE__ */ new Map();
|
|
5323
|
+
if (!Array.isArray(raw)) {
|
|
5324
|
+
return entries;
|
|
5325
|
+
}
|
|
5326
|
+
for (const item of raw) {
|
|
5327
|
+
if (!item || typeof item !== "object") {
|
|
5328
|
+
continue;
|
|
5329
|
+
}
|
|
5330
|
+
const value = item;
|
|
5331
|
+
const id = normalizeWhitespace(value.id ?? "");
|
|
5332
|
+
const title = normalizeWhitespace(value.name ?? "");
|
|
5333
|
+
if (!title) {
|
|
5334
|
+
continue;
|
|
5335
|
+
}
|
|
5336
|
+
const members = (Array.isArray(value.members) ? value.members : value.user ? [value.user] : []).map((member) => slackFormatSpeakerId(member, usersById)).filter(Boolean);
|
|
5337
|
+
entries.set(title, { id, title, members });
|
|
5338
|
+
}
|
|
5339
|
+
return entries;
|
|
5340
|
+
}
|
|
5341
|
+
async function extractTranscriptText(input) {
|
|
5342
|
+
try {
|
|
5343
|
+
const { parseSync } = await import("subtitle");
|
|
5344
|
+
const rawText = decodeTextBytes(input.bytes);
|
|
5345
|
+
const cues = parseSync(rawText).filter((node) => node.type === "cue" && node.data).map((node) => ({
|
|
5346
|
+
start: Math.max(0, node.data?.start ?? 0),
|
|
5347
|
+
end: Math.max(0, node.data?.end ?? 0),
|
|
5348
|
+
text: normalizeWhitespace((node.data?.text ?? "").replace(/\s*\n+\s*/g, " "))
|
|
5349
|
+
})).filter((cue) => cue.text);
|
|
5350
|
+
const title = input.fileName ? path7.basename(input.fileName, path7.extname(input.fileName)) : void 0;
|
|
5351
|
+
const extractedText = [
|
|
5352
|
+
title ? `# ${title}` : null,
|
|
5353
|
+
`Format: ${input.fileName?.toLowerCase().endsWith(".vtt") ? "WebVTT" : "SRT"}`,
|
|
5354
|
+
`Segments: ${cues.length}`,
|
|
5355
|
+
...cues.length ? [`Start: ${timestampFromMs(cues[0].start)}`, `End: ${timestampFromMs(cues[cues.length - 1].end)}`] : [],
|
|
5356
|
+
"",
|
|
5357
|
+
"## Transcript",
|
|
5358
|
+
"",
|
|
5359
|
+
...cues.length ? cues.map((cue) => `- [${timestampFromMs(cue.start)} - ${timestampFromMs(cue.end)}] ${cue.text}`) : ["- No transcript segments were extracted."],
|
|
5360
|
+
""
|
|
5361
|
+
].filter((item) => Boolean(item)).join("\n");
|
|
5362
|
+
return {
|
|
5363
|
+
title,
|
|
5364
|
+
extractedText,
|
|
5365
|
+
artifact: {
|
|
5366
|
+
...extractionMetadata("transcript", input.mimeType, "transcript_text"),
|
|
5367
|
+
metadata: {
|
|
5368
|
+
format: input.fileName?.toLowerCase().endsWith(".vtt") ? "vtt" : "srt",
|
|
5369
|
+
segment_count: String(cues.length),
|
|
5370
|
+
...cues.length ? { started_at: timestampFromMs(cues[0].start), ended_at: timestampFromMs(cues[cues.length - 1].end) } : {}
|
|
5371
|
+
}
|
|
5372
|
+
}
|
|
5373
|
+
};
|
|
5374
|
+
} catch (error) {
|
|
5375
|
+
return {
|
|
5376
|
+
artifact: {
|
|
5377
|
+
...extractionMetadata("transcript", input.mimeType, "transcript_text"),
|
|
5378
|
+
warnings: [`Transcript extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
|
|
5379
|
+
}
|
|
5380
|
+
};
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
async function extractEmailText(input) {
|
|
5384
|
+
try {
|
|
5385
|
+
const { simpleParser } = await import("mailparser");
|
|
5386
|
+
const fallbackTitle = input.fileName ? path7.basename(input.fileName, path7.extname(input.fileName)) : "Email";
|
|
5387
|
+
const parsed = await simpleParser(input.bytes);
|
|
5388
|
+
const normalized = normalizeParsedEmail(parsed, fallbackTitle);
|
|
5389
|
+
return {
|
|
5390
|
+
title: normalized.title,
|
|
5391
|
+
extractedText: normalized.markdown,
|
|
5392
|
+
artifact: {
|
|
5393
|
+
...extractionMetadata("email", input.mimeType, "email_text"),
|
|
5394
|
+
metadata: normalized.metadata
|
|
5395
|
+
}
|
|
5396
|
+
};
|
|
5397
|
+
} catch (error) {
|
|
5398
|
+
return {
|
|
5399
|
+
artifact: {
|
|
5400
|
+
...extractionMetadata("email", input.mimeType, "email_text"),
|
|
5401
|
+
warnings: [`Email extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
|
|
5402
|
+
}
|
|
5403
|
+
};
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
async function extractMboxMessages(input) {
|
|
5407
|
+
try {
|
|
5408
|
+
const title = input.fileName ? path7.basename(input.fileName, path7.extname(input.fileName)) : "Mailbox";
|
|
5409
|
+
const { simpleParser } = await import("mailparser");
|
|
5410
|
+
const messages = await loadZipMessageBuffers(input.bytes);
|
|
5411
|
+
const extracted = [];
|
|
5412
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
5413
|
+
const parsed = await simpleParser(messages[index]);
|
|
5414
|
+
const normalized = normalizeParsedEmail(parsed, `Message ${index + 1}`);
|
|
5415
|
+
const conversationId = normalized.conversationId || `${index + 1}`;
|
|
5416
|
+
extracted.push({
|
|
5417
|
+
partKey: `${conversationId}-${index + 1}`,
|
|
5418
|
+
title: normalized.title,
|
|
5419
|
+
markdown: normalized.markdown,
|
|
5420
|
+
metadata: {
|
|
5421
|
+
...normalized.metadata,
|
|
5422
|
+
container_title: title,
|
|
5423
|
+
mailbox_title: title,
|
|
5424
|
+
part_index: String(index + 1),
|
|
5425
|
+
part_count: String(messages.length)
|
|
5426
|
+
}
|
|
5427
|
+
});
|
|
5428
|
+
}
|
|
5429
|
+
return {
|
|
5430
|
+
title,
|
|
5431
|
+
messages: extracted,
|
|
5432
|
+
warnings: extracted.length ? void 0 : ["Mailbox extraction completed but found no readable messages."]
|
|
5433
|
+
};
|
|
5434
|
+
} catch (error) {
|
|
5435
|
+
return {
|
|
5436
|
+
messages: [],
|
|
5437
|
+
warnings: [`Mailbox extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
|
|
5438
|
+
};
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
async function extractCalendarEvents(input) {
|
|
5442
|
+
try {
|
|
5443
|
+
const ical = await import("node-ical");
|
|
5444
|
+
const calendarTitle = input.fileName ? path7.basename(input.fileName, path7.extname(input.fileName)) : "Calendar";
|
|
5445
|
+
const parsed = ical.default.sync.parseICS(decodeTextBytes(input.bytes));
|
|
5446
|
+
const events = [];
|
|
5447
|
+
for (const item of Object.values(parsed)) {
|
|
5448
|
+
if (!item || typeof item !== "object" || item.type !== "VEVENT") {
|
|
5449
|
+
continue;
|
|
5450
|
+
}
|
|
5451
|
+
const event = item;
|
|
5452
|
+
const title = normalizeWhitespace(event.summary ?? "") || "Calendar Event";
|
|
5453
|
+
const occurredAt = normalizeIsoDate(event.start);
|
|
5454
|
+
const endsAt = normalizeIsoDate(event.end);
|
|
5455
|
+
const organizer = event.organizer ? normalizeWhitespace(String(event.organizer.params?.CN ?? event.organizer.val ?? "")) : void 0;
|
|
5456
|
+
const attendees = calendarAttendees(event.attendees);
|
|
5457
|
+
const participants = normalizeDelimitedList([organizer ?? "", ...attendees]);
|
|
5458
|
+
const location = normalizeWhitespace(event.location ?? "") || void 0;
|
|
5459
|
+
const description = normalizeDocumentText(event.description ?? "");
|
|
5460
|
+
const conversationId = normalizeWhitespace(event.uid ?? "") || `${title}-${occurredAt ?? events.length + 1}`;
|
|
5461
|
+
events.push({
|
|
5462
|
+
partKey: conversationId,
|
|
5463
|
+
title,
|
|
5464
|
+
metadata: {
|
|
5465
|
+
container_title: calendarTitle,
|
|
5466
|
+
...occurredAt ? { occurred_at: occurredAt } : {},
|
|
5467
|
+
...endsAt ? { ends_at: endsAt } : {},
|
|
5468
|
+
...organizer ? { organizer } : {},
|
|
5469
|
+
...location ? { location } : {},
|
|
5470
|
+
...participants ? { participants } : {},
|
|
5471
|
+
conversation_id: conversationId
|
|
5472
|
+
},
|
|
5473
|
+
markdown: [
|
|
5474
|
+
`# ${title}`,
|
|
5475
|
+
"",
|
|
5476
|
+
...occurredAt ? [`Start: ${occurredAt}`] : [],
|
|
5477
|
+
...endsAt ? [`End: ${endsAt}`] : [],
|
|
5478
|
+
...organizer ? [`Organizer: ${organizer}`] : [],
|
|
5479
|
+
...attendees.length ? [`Attendees: ${attendees.join(", ")}`] : [],
|
|
5480
|
+
...location ? [`Location: ${location}`] : [],
|
|
5481
|
+
...conversationId ? [`Event ID: ${conversationId}`] : [],
|
|
5482
|
+
"",
|
|
5483
|
+
"## Description",
|
|
5484
|
+
"",
|
|
5485
|
+
description || "No event description was provided.",
|
|
5486
|
+
""
|
|
5487
|
+
].join("\n")
|
|
5488
|
+
});
|
|
5489
|
+
}
|
|
5490
|
+
return {
|
|
5491
|
+
title: calendarTitle,
|
|
5492
|
+
events,
|
|
5493
|
+
warnings: events.length ? void 0 : ["Calendar extraction completed but found no VEVENT entries."]
|
|
5494
|
+
};
|
|
5495
|
+
} catch (error) {
|
|
5496
|
+
return {
|
|
5497
|
+
events: [],
|
|
5498
|
+
warnings: [`Calendar extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
|
|
5499
|
+
};
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
function parseSlackExportEntries(entries, exportTitle) {
|
|
5503
|
+
const usersById = /* @__PURE__ */ new Map();
|
|
5504
|
+
const rawUsers = entries.get("users.json");
|
|
5505
|
+
if (rawUsers) {
|
|
5506
|
+
const parsed = JSON.parse(rawUsers);
|
|
5507
|
+
for (const user of parsed) {
|
|
5508
|
+
const id = normalizeWhitespace(user.id ?? "");
|
|
5509
|
+
const name = normalizeWhitespace(user.profile?.display_name ?? user.real_name ?? user.profile?.real_name ?? user.name ?? "");
|
|
5510
|
+
if (id && name) {
|
|
5511
|
+
usersById.set(id, name);
|
|
5512
|
+
}
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
const channelIndex = /* @__PURE__ */ new Map();
|
|
5516
|
+
for (const indexPath of ["channels.json", "groups.json", "dms.json", "mpims.json"]) {
|
|
5517
|
+
const rawIndex = entries.get(indexPath);
|
|
5518
|
+
if (!rawIndex) {
|
|
5519
|
+
continue;
|
|
5520
|
+
}
|
|
5521
|
+
const parsed = JSON.parse(rawIndex);
|
|
5522
|
+
for (const [key, value] of slackEntriesFromChannelIndex(parsed, usersById)) {
|
|
5523
|
+
channelIndex.set(key, value);
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
const conversationPaths = [...entries.keys()].filter((entryPath) => /^[^/]+\/\d{4}-\d{2}-\d{2}\.json$/i.test(entryPath)).sort((left, right) => left.localeCompare(right));
|
|
5527
|
+
const conversations = [];
|
|
5528
|
+
for (const entryPath of conversationPaths) {
|
|
5529
|
+
const raw = entries.get(entryPath);
|
|
5530
|
+
if (!raw) {
|
|
5531
|
+
continue;
|
|
5532
|
+
}
|
|
5533
|
+
const messages = JSON.parse(raw);
|
|
5534
|
+
if (!Array.isArray(messages)) {
|
|
5535
|
+
continue;
|
|
5536
|
+
}
|
|
5537
|
+
const [channelName, dateFile] = entryPath.split("/");
|
|
5538
|
+
const date = dateFile?.replace(/\.json$/i, "") ?? "";
|
|
5539
|
+
const channel = channelIndex.get(channelName ?? "") ?? {
|
|
5540
|
+
id: channelName ?? "",
|
|
5541
|
+
title: channelName ?? "channel",
|
|
5542
|
+
members: []
|
|
5543
|
+
};
|
|
5544
|
+
const participants = new Set(channel.members);
|
|
5545
|
+
const lines = [];
|
|
5546
|
+
const threadIds = /* @__PURE__ */ new Set();
|
|
5547
|
+
const sortedMessages = [...messages].sort((left, right) => Number(left.ts ?? 0) - Number(right.ts ?? 0));
|
|
5548
|
+
let occurredAt;
|
|
5549
|
+
for (const message of sortedMessages) {
|
|
5550
|
+
const speaker = normalizeWhitespace(
|
|
5551
|
+
message.username ?? message.bot_profile?.name ?? (message.user ? slackFormatSpeakerId(message.user, usersById) : "")
|
|
5552
|
+
) || "unknown";
|
|
5553
|
+
participants.add(speaker);
|
|
5554
|
+
const messageTime = slackMessageTimestamp(message.ts, date);
|
|
5555
|
+
occurredAt ??= messageTime;
|
|
5556
|
+
const normalizedText = slackNormalizeText(
|
|
5557
|
+
[
|
|
5558
|
+
message.text ?? "",
|
|
5559
|
+
...Array.isArray(message.files) ? message.files.map((file) => normalizeWhitespace(file.title ?? file.name ?? "")).filter(Boolean).map((label) => `Attachment: ${label}`) : []
|
|
5560
|
+
].join("\n"),
|
|
5561
|
+
usersById
|
|
5562
|
+
);
|
|
5563
|
+
if (message.thread_ts && message.thread_ts !== message.ts) {
|
|
5564
|
+
threadIds.add(message.thread_ts);
|
|
5565
|
+
}
|
|
5566
|
+
lines.push(
|
|
5567
|
+
`- [${messageTime}] ${speaker}${message.thread_ts ? ` {thread:${message.thread_ts}}` : ""}${message.ts ? ` {id:${message.ts}}` : ""}: ${normalizedText || normalizeWhitespace(message.subtype ?? "") || "[no text]"}`
|
|
5568
|
+
);
|
|
5569
|
+
}
|
|
5570
|
+
const participantsList = normalizeDelimitedList([...participants]);
|
|
5571
|
+
const conversationId = `${channel.id || channel.title}:${date}`;
|
|
5572
|
+
conversations.push({
|
|
5573
|
+
partKey: `${channel.title}-${date}`,
|
|
5574
|
+
title: `#${channel.title} - ${date}`,
|
|
5575
|
+
metadata: {
|
|
5576
|
+
workspace_title: exportTitle,
|
|
5577
|
+
channel: channel.title,
|
|
5578
|
+
...channel.id ? { channel_id: channel.id } : {},
|
|
5579
|
+
...occurredAt ? { occurred_at: occurredAt } : {},
|
|
5580
|
+
...participantsList ? { participants: participantsList } : {},
|
|
5581
|
+
container_title: `${exportTitle} / #${channel.title}`,
|
|
5582
|
+
conversation_id: conversationId,
|
|
5583
|
+
date,
|
|
5584
|
+
message_count: String(sortedMessages.length),
|
|
5585
|
+
thread_count: String(threadIds.size)
|
|
5586
|
+
},
|
|
5587
|
+
markdown: [
|
|
5588
|
+
`# #${channel.title} - ${date}`,
|
|
5589
|
+
"",
|
|
5590
|
+
`Workspace: ${exportTitle}`,
|
|
5591
|
+
`Messages: ${sortedMessages.length}`,
|
|
5592
|
+
`Threads: ${threadIds.size}`,
|
|
5593
|
+
...participantsList ? [`Participants: ${participantsList}`] : [],
|
|
5594
|
+
"",
|
|
5595
|
+
"## Messages",
|
|
5596
|
+
"",
|
|
5597
|
+
...lines.length ? lines : ["- No messages were extracted."],
|
|
5598
|
+
""
|
|
5599
|
+
].join("\n")
|
|
5600
|
+
});
|
|
5601
|
+
}
|
|
5602
|
+
return {
|
|
5603
|
+
title: exportTitle,
|
|
5604
|
+
conversations,
|
|
5605
|
+
warnings: conversations.length ? void 0 : ["Slack export parsing completed but found no channel day files."]
|
|
5606
|
+
};
|
|
5607
|
+
}
|
|
5608
|
+
function isSlackExportArchive(bytes) {
|
|
5609
|
+
try {
|
|
5610
|
+
const archive = unzipSync(new Uint8Array(bytes));
|
|
5611
|
+
return looksLikeSlackEntries(Object.keys(archive));
|
|
5612
|
+
} catch {
|
|
5613
|
+
return false;
|
|
5614
|
+
}
|
|
5615
|
+
}
|
|
5616
|
+
async function isSlackExportDirectory(directoryPath) {
|
|
5617
|
+
const entries = await fs7.readdir(directoryPath).catch(() => []);
|
|
5618
|
+
if (!entries.length) {
|
|
5619
|
+
return false;
|
|
5620
|
+
}
|
|
5621
|
+
const fileSet = new Set(entries);
|
|
5622
|
+
const hasIndex = ["channels.json", "groups.json", "dms.json", "mpims.json"].some((name) => fileSet.has(name));
|
|
5623
|
+
if (!hasIndex) {
|
|
5624
|
+
return false;
|
|
5625
|
+
}
|
|
5626
|
+
for (const entry of entries) {
|
|
5627
|
+
const channelDir = path7.join(directoryPath, entry);
|
|
5628
|
+
const stat = await fs7.stat(channelDir).catch(() => null);
|
|
5629
|
+
if (!stat?.isDirectory()) {
|
|
5630
|
+
continue;
|
|
5631
|
+
}
|
|
5632
|
+
const channelEntries = await fs7.readdir(channelDir).catch(() => []);
|
|
5633
|
+
if (channelEntries.some((name) => /^\d{4}-\d{2}-\d{2}\.json$/i.test(name))) {
|
|
5634
|
+
return true;
|
|
5635
|
+
}
|
|
5636
|
+
}
|
|
5637
|
+
return false;
|
|
5638
|
+
}
|
|
5639
|
+
async function extractSlackExportArchive(input) {
|
|
5640
|
+
try {
|
|
5641
|
+
const archive = unzipSync(new Uint8Array(input.bytes));
|
|
5642
|
+
const title = input.fileName ? path7.basename(input.fileName, path7.extname(input.fileName)) : "Slack Export";
|
|
5643
|
+
return parseSlackExportEntries(archiveEntriesAsText(archive), title);
|
|
5644
|
+
} catch (error) {
|
|
5645
|
+
return {
|
|
5646
|
+
conversations: [],
|
|
5647
|
+
warnings: [`Slack export extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
|
|
5648
|
+
};
|
|
5649
|
+
}
|
|
5650
|
+
}
|
|
5651
|
+
async function extractSlackExportDirectory(directoryPath) {
|
|
5652
|
+
const title = path7.basename(directoryPath) || "Slack Export";
|
|
5653
|
+
try {
|
|
5654
|
+
const entries = /* @__PURE__ */ new Map();
|
|
5655
|
+
const queue = [directoryPath];
|
|
5656
|
+
while (queue.length > 0) {
|
|
5657
|
+
const current = queue.shift();
|
|
5658
|
+
const children = await fs7.readdir(current, { withFileTypes: true });
|
|
5659
|
+
for (const child of children) {
|
|
5660
|
+
const absoluteChild = path7.join(current, child.name);
|
|
5661
|
+
if (child.isDirectory()) {
|
|
5662
|
+
queue.push(absoluteChild);
|
|
5663
|
+
continue;
|
|
5664
|
+
}
|
|
5665
|
+
const relativeChild = path7.posix.relative(directoryPath, absoluteChild.split(path7.sep).join(path7.posix.sep));
|
|
5666
|
+
entries.set(relativeChild, await fs7.readFile(absoluteChild, "utf8"));
|
|
5667
|
+
}
|
|
5668
|
+
}
|
|
5669
|
+
return parseSlackExportEntries(entries, title);
|
|
5670
|
+
} catch (error) {
|
|
5671
|
+
return {
|
|
5672
|
+
conversations: [],
|
|
5673
|
+
warnings: [`Slack export extraction failed: ${error instanceof Error ? truncate(error.message, 240) : "unknown error"}`]
|
|
5674
|
+
};
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5174
5677
|
|
|
5175
5678
|
// src/logs.ts
|
|
5176
5679
|
import fs8 from "fs/promises";
|
|
@@ -5600,6 +6103,9 @@ function inferKind(mimeType, filePath) {
|
|
|
5600
6103
|
if (isRstFilePath(filePath)) {
|
|
5601
6104
|
return "text";
|
|
5602
6105
|
}
|
|
6106
|
+
if (isTranscriptFilePath(filePath) || mimeType === "application/x-subrip" || mimeType === "text/vtt") {
|
|
6107
|
+
return "transcript";
|
|
6108
|
+
}
|
|
5603
6109
|
if (mimeType.includes("markdown")) {
|
|
5604
6110
|
return "markdown";
|
|
5605
6111
|
}
|
|
@@ -5612,6 +6118,12 @@ function inferKind(mimeType, filePath) {
|
|
|
5612
6118
|
if (mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || filePath.toLowerCase().endsWith(".docx")) {
|
|
5613
6119
|
return "docx";
|
|
5614
6120
|
}
|
|
6121
|
+
if (isEmailFilePath(filePath) || mimeType === "message/rfc822" || mimeType === "application/mbox") {
|
|
6122
|
+
return "email";
|
|
6123
|
+
}
|
|
6124
|
+
if (isCalendarFilePath(filePath) || mimeType === "text/calendar") {
|
|
6125
|
+
return "calendar";
|
|
6126
|
+
}
|
|
5615
6127
|
if (mimeType === "application/epub+zip" || filePath.toLowerCase().endsWith(".epub")) {
|
|
5616
6128
|
return "epub";
|
|
5617
6129
|
}
|
|
@@ -5636,6 +6148,17 @@ function isRstFilePath(filePath) {
|
|
|
5636
6148
|
const extension = path12.extname(filePath).toLowerCase();
|
|
5637
6149
|
return extension === ".rst" || extension === ".rest";
|
|
5638
6150
|
}
|
|
6151
|
+
function isTranscriptFilePath(filePath) {
|
|
6152
|
+
const extension = path12.extname(filePath).toLowerCase();
|
|
6153
|
+
return extension === ".srt" || extension === ".vtt";
|
|
6154
|
+
}
|
|
6155
|
+
function isEmailFilePath(filePath) {
|
|
6156
|
+
const extension = path12.extname(filePath).toLowerCase();
|
|
6157
|
+
return extension === ".eml" || extension === ".mbox";
|
|
6158
|
+
}
|
|
6159
|
+
function isCalendarFilePath(filePath) {
|
|
6160
|
+
return path12.extname(filePath).toLowerCase() === ".ics";
|
|
6161
|
+
}
|
|
5639
6162
|
function titleFromText(fallback, content, filePath) {
|
|
5640
6163
|
if (filePath && isRstFilePath(filePath)) {
|
|
5641
6164
|
const rstTitle = titleFromRst(fallback, content);
|
|
@@ -5656,6 +6179,53 @@ function sourceGroupIdFor(prepared) {
|
|
|
5656
6179
|
const originKey = prepared.originType === "url" ? prepared.url ?? prepared.title : prepared.originalPath ?? prepared.title;
|
|
5657
6180
|
return `${slugify(prepared.title)}-${sha256(originKey).slice(0, 8)}`;
|
|
5658
6181
|
}
|
|
6182
|
+
function groupedPreparedInputsFor(input) {
|
|
6183
|
+
const groupId = sourceGroupIdFor({
|
|
6184
|
+
title: input.title,
|
|
6185
|
+
originType: input.originType,
|
|
6186
|
+
originalPath: input.originalPath,
|
|
6187
|
+
url: input.url
|
|
6188
|
+
});
|
|
6189
|
+
return input.parts.map(
|
|
6190
|
+
(part, index) => finalizePreparedInput({
|
|
6191
|
+
title: `${input.title} - ${part.title}`,
|
|
6192
|
+
originType: input.originType,
|
|
6193
|
+
sourceKind: input.sourceKind,
|
|
6194
|
+
sourceClass: input.sourceClass,
|
|
6195
|
+
originalPath: input.originalPath,
|
|
6196
|
+
repoRelativePath: input.repoRelativePath,
|
|
6197
|
+
url: input.url,
|
|
6198
|
+
mimeType: "text/markdown",
|
|
6199
|
+
storedExtension: input.storedExtension,
|
|
6200
|
+
payloadBytes: Buffer.from(part.markdown, "utf8"),
|
|
6201
|
+
extractedText: part.markdown,
|
|
6202
|
+
extractionArtifact: {
|
|
6203
|
+
extractor: `${input.sourceKind}_text`,
|
|
6204
|
+
sourceKind: input.sourceKind,
|
|
6205
|
+
mimeType: input.mimeType,
|
|
6206
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6207
|
+
metadata: {
|
|
6208
|
+
...part.metadata,
|
|
6209
|
+
part_index: String(index + 1),
|
|
6210
|
+
part_count: String(input.parts.length)
|
|
6211
|
+
},
|
|
6212
|
+
warnings: input.warnings
|
|
6213
|
+
},
|
|
6214
|
+
sourceGroupId: groupId,
|
|
6215
|
+
sourceGroupTitle: input.title,
|
|
6216
|
+
sourcePartKey: part.partKey,
|
|
6217
|
+
partIndex: index + 1,
|
|
6218
|
+
partCount: input.parts.length,
|
|
6219
|
+
partTitle: part.title,
|
|
6220
|
+
details: {
|
|
6221
|
+
...part.metadata,
|
|
6222
|
+
part_index: String(index + 1),
|
|
6223
|
+
part_count: String(input.parts.length)
|
|
6224
|
+
},
|
|
6225
|
+
logDetails: input.logDetails
|
|
6226
|
+
})
|
|
6227
|
+
);
|
|
6228
|
+
}
|
|
5659
6229
|
function rstAdornmentLine(line) {
|
|
5660
6230
|
const trimmed = line.trim();
|
|
5661
6231
|
if (trimmed.length < 3) {
|
|
@@ -5886,6 +6456,13 @@ async function findNearestGitRoot2(startPath) {
|
|
|
5886
6456
|
current = parent;
|
|
5887
6457
|
}
|
|
5888
6458
|
}
|
|
6459
|
+
async function detectScopedRepoRoot(rootDir, inputPath, fallbackRoot) {
|
|
6460
|
+
const detectedRepoRoot = await findNearestGitRoot2(inputPath);
|
|
6461
|
+
if (!detectedRepoRoot) {
|
|
6462
|
+
return fallbackRoot;
|
|
6463
|
+
}
|
|
6464
|
+
return withinRoot(rootDir, inputPath) && !withinRoot(rootDir, detectedRepoRoot) ? fallbackRoot : detectedRepoRoot;
|
|
6465
|
+
}
|
|
5889
6466
|
function withinRoot(rootPath, targetPath) {
|
|
5890
6467
|
const relative = path12.relative(rootPath, targetPath);
|
|
5891
6468
|
return relative === "" || !relative.startsWith("..") && !path12.isAbsolute(relative);
|
|
@@ -6440,7 +7017,13 @@ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
|
|
|
6440
7017
|
continue;
|
|
6441
7018
|
}
|
|
6442
7019
|
const mimeType = guessMimeType(absolutePath);
|
|
6443
|
-
|
|
7020
|
+
let sourceKind = inferKind(mimeType, absolutePath);
|
|
7021
|
+
if (sourceKind === "binary" && path12.extname(absolutePath).toLowerCase() === ".zip") {
|
|
7022
|
+
const bytes = await fs11.readFile(absolutePath);
|
|
7023
|
+
if (isSlackExportArchive(bytes)) {
|
|
7024
|
+
sourceKind = "chat_export";
|
|
7025
|
+
}
|
|
7026
|
+
}
|
|
6444
7027
|
const sourceClass = sourceClassForRelativePath(relativePath, options);
|
|
6445
7028
|
if (!supportedDirectoryKind(sourceKind)) {
|
|
6446
7029
|
skipped.push({ path: toPosix(path12.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
@@ -6775,7 +7358,7 @@ function preparedMatchesManifest(manifest, prepared, contentHash) {
|
|
|
6775
7358
|
return manifest.contentHash === contentHash && manifest.extractionHash === (prepared.extractionHash ?? buildExtractionHash(prepared.extractedText, prepared.extractionArtifact)) && manifest.semanticHash === (prepared.semanticHash ?? contentHash) && manifest.title === prepared.title && manifest.sourceKind === prepared.sourceKind && manifest.sourceType === prepared.sourceType && manifest.sourceClass === prepared.sourceClass && manifest.language === prepared.language && manifest.mimeType === prepared.mimeType && manifest.repoRelativePath === prepared.repoRelativePath && manifest.sourceGroupId === prepared.sourceGroupId && manifest.sourceGroupTitle === prepared.sourceGroupTitle && manifest.sourcePartKey === prepared.sourcePartKey && manifest.partIndex === prepared.partIndex && manifest.partCount === prepared.partCount && manifest.partTitle === prepared.partTitle && JSON.stringify(manifest.details ?? {}) === JSON.stringify(prepared.details ?? {});
|
|
6776
7359
|
}
|
|
6777
7360
|
function shouldDeferWatchSemanticRefresh(sourceKind) {
|
|
6778
|
-
return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "docx" || sourceKind === "epub" || sourceKind === "csv" || sourceKind === "xlsx" || sourceKind === "pptx" || sourceKind === "image";
|
|
7361
|
+
return sourceKind === "markdown" || sourceKind === "text" || sourceKind === "html" || sourceKind === "pdf" || sourceKind === "docx" || sourceKind === "epub" || sourceKind === "csv" || sourceKind === "xlsx" || sourceKind === "pptx" || sourceKind === "transcript" || sourceKind === "chat_export" || sourceKind === "email" || sourceKind === "calendar" || sourceKind === "image";
|
|
6779
7362
|
}
|
|
6780
7363
|
function pendingSemanticRefreshId(changeType, repoRoot, relativePath) {
|
|
6781
7364
|
return `pending:${changeType}:${sha256(`${toPosix(repoRoot)}:${relativePath}`).slice(0, 12)}`;
|
|
@@ -7043,6 +7626,23 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
|
|
|
7043
7626
|
}
|
|
7044
7627
|
async function prepareFileInputs(rootDir, absoluteInput, repoRoot, sourceClass) {
|
|
7045
7628
|
const payloadBytes = await fs11.readFile(absoluteInput);
|
|
7629
|
+
if (path12.extname(absoluteInput).toLowerCase() === ".zip" && isSlackExportArchive(payloadBytes)) {
|
|
7630
|
+
const slackExport = await extractSlackExportArchive({ mimeType: "application/zip", bytes: payloadBytes, fileName: absoluteInput });
|
|
7631
|
+
if (slackExport.conversations.length) {
|
|
7632
|
+
return groupedPreparedInputsFor({
|
|
7633
|
+
title: slackExport.title?.trim() || path12.basename(absoluteInput, path12.extname(absoluteInput)),
|
|
7634
|
+
originType: "file",
|
|
7635
|
+
sourceKind: "chat_export",
|
|
7636
|
+
sourceClass,
|
|
7637
|
+
originalPath: toPosix(absoluteInput),
|
|
7638
|
+
repoRelativePath: repoRelativePathFor(absoluteInput, repoRoot),
|
|
7639
|
+
mimeType: "application/zip",
|
|
7640
|
+
storedExtension: ".md",
|
|
7641
|
+
warnings: slackExport.warnings,
|
|
7642
|
+
parts: slackExport.conversations
|
|
7643
|
+
});
|
|
7644
|
+
}
|
|
7645
|
+
}
|
|
7046
7646
|
const mimeType = guessMimeType(absoluteInput);
|
|
7047
7647
|
const sourceKind = inferKind(mimeType, absoluteInput);
|
|
7048
7648
|
const language = inferCodeLanguage(absoluteInput, mimeType);
|
|
@@ -7072,6 +7672,68 @@ async function prepareFileInputs(rootDir, absoluteInput, repoRoot, sourceClass)
|
|
|
7072
7672
|
title = extracted.artifact.metadata?.title?.trim() || title;
|
|
7073
7673
|
extractedText = extracted.extractedText;
|
|
7074
7674
|
extractionArtifact = extracted.artifact;
|
|
7675
|
+
} else if (sourceKind === "transcript") {
|
|
7676
|
+
title = path12.basename(absoluteInput, path12.extname(absoluteInput));
|
|
7677
|
+
const extracted = await extractTranscriptText({ mimeType, bytes: payloadBytes, fileName: absoluteInput });
|
|
7678
|
+
title = extracted.title?.trim() || title;
|
|
7679
|
+
extractedText = extracted.extractedText;
|
|
7680
|
+
extractionArtifact = extracted.artifact;
|
|
7681
|
+
} else if (sourceKind === "email" && path12.extname(absoluteInput).toLowerCase() === ".eml") {
|
|
7682
|
+
title = path12.basename(absoluteInput, path12.extname(absoluteInput));
|
|
7683
|
+
const extracted = await extractEmailText({ mimeType, bytes: payloadBytes, fileName: absoluteInput });
|
|
7684
|
+
title = extracted.title?.trim() || title;
|
|
7685
|
+
extractedText = extracted.extractedText;
|
|
7686
|
+
extractionArtifact = extracted.artifact;
|
|
7687
|
+
} else if (sourceKind === "email" && path12.extname(absoluteInput).toLowerCase() === ".mbox") {
|
|
7688
|
+
title = path12.basename(absoluteInput, path12.extname(absoluteInput));
|
|
7689
|
+
const extracted = await extractMboxMessages({ mimeType, bytes: payloadBytes, fileName: absoluteInput });
|
|
7690
|
+
title = extracted.title?.trim() || title;
|
|
7691
|
+
if (extracted.messages.length) {
|
|
7692
|
+
return groupedPreparedInputsFor({
|
|
7693
|
+
title,
|
|
7694
|
+
originType: "file",
|
|
7695
|
+
sourceKind: "email",
|
|
7696
|
+
sourceClass,
|
|
7697
|
+
originalPath: toPosix(absoluteInput),
|
|
7698
|
+
repoRelativePath: repoRelativePathFor(absoluteInput, repoRoot),
|
|
7699
|
+
mimeType,
|
|
7700
|
+
storedExtension: ".md",
|
|
7701
|
+
warnings: extracted.warnings,
|
|
7702
|
+
parts: extracted.messages
|
|
7703
|
+
});
|
|
7704
|
+
}
|
|
7705
|
+
extractionArtifact = {
|
|
7706
|
+
extractor: "email_text",
|
|
7707
|
+
sourceKind: "email",
|
|
7708
|
+
mimeType,
|
|
7709
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7710
|
+
warnings: extracted.warnings ?? ["Mailbox extraction completed but produced no readable messages."]
|
|
7711
|
+
};
|
|
7712
|
+
} else if (sourceKind === "calendar") {
|
|
7713
|
+
title = path12.basename(absoluteInput, path12.extname(absoluteInput));
|
|
7714
|
+
const extracted = await extractCalendarEvents({ mimeType, bytes: payloadBytes, fileName: absoluteInput });
|
|
7715
|
+
title = extracted.title?.trim() || title;
|
|
7716
|
+
if (extracted.events.length) {
|
|
7717
|
+
return groupedPreparedInputsFor({
|
|
7718
|
+
title,
|
|
7719
|
+
originType: "file",
|
|
7720
|
+
sourceKind: "calendar",
|
|
7721
|
+
sourceClass,
|
|
7722
|
+
originalPath: toPosix(absoluteInput),
|
|
7723
|
+
repoRelativePath: repoRelativePathFor(absoluteInput, repoRoot),
|
|
7724
|
+
mimeType,
|
|
7725
|
+
storedExtension: ".md",
|
|
7726
|
+
warnings: extracted.warnings,
|
|
7727
|
+
parts: extracted.events
|
|
7728
|
+
});
|
|
7729
|
+
}
|
|
7730
|
+
extractionArtifact = {
|
|
7731
|
+
extractor: "calendar_text",
|
|
7732
|
+
sourceKind: "calendar",
|
|
7733
|
+
mimeType,
|
|
7734
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7735
|
+
warnings: extracted.warnings ?? ["Calendar extraction completed but found no events."]
|
|
7736
|
+
};
|
|
7075
7737
|
} else if (sourceKind === "csv") {
|
|
7076
7738
|
title = path12.basename(absoluteInput, path12.extname(absoluteInput));
|
|
7077
7739
|
const extracted = await extractCsvText({ mimeType, bytes: payloadBytes, fileName: absoluteInput });
|
|
@@ -7094,63 +7756,25 @@ async function prepareFileInputs(rootDir, absoluteInput, repoRoot, sourceClass)
|
|
|
7094
7756
|
title = path12.basename(absoluteInput, path12.extname(absoluteInput));
|
|
7095
7757
|
const extracted = await extractEpubChapters({ mimeType, bytes: payloadBytes, fileName: absoluteInput });
|
|
7096
7758
|
title = extracted.title?.trim() || title;
|
|
7097
|
-
const groupId = sourceGroupIdFor({
|
|
7098
|
-
title,
|
|
7099
|
-
originType: "file",
|
|
7100
|
-
originalPath: toPosix(absoluteInput)
|
|
7101
|
-
});
|
|
7102
7759
|
if (extracted.chapters.length) {
|
|
7103
|
-
return
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
sourceKind: "epub",
|
|
7118
|
-
mimeType,
|
|
7119
|
-
producedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7120
|
-
metadata: {
|
|
7121
|
-
...chapter.metadata,
|
|
7122
|
-
chapter_index: String(index + 1),
|
|
7123
|
-
chapter_count: String(extracted.chapters.length)
|
|
7124
|
-
},
|
|
7125
|
-
warnings: extracted.warnings
|
|
7126
|
-
},
|
|
7127
|
-
extractionHash: buildExtractionHash(chapter.markdown, {
|
|
7128
|
-
extractor: "epub_text",
|
|
7129
|
-
sourceKind: "epub",
|
|
7130
|
-
mimeType,
|
|
7131
|
-
producedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7132
|
-
metadata: {
|
|
7133
|
-
...chapter.metadata,
|
|
7134
|
-
chapter_index: String(index + 1),
|
|
7135
|
-
chapter_count: String(extracted.chapters.length)
|
|
7136
|
-
},
|
|
7137
|
-
warnings: extracted.warnings
|
|
7138
|
-
}),
|
|
7139
|
-
sourceGroupId: groupId,
|
|
7140
|
-
sourceGroupTitle: title,
|
|
7141
|
-
sourcePartKey: chapter.partKey,
|
|
7142
|
-
partIndex: index + 1,
|
|
7143
|
-
partCount: extracted.chapters.length,
|
|
7144
|
-
partTitle: chapter.title,
|
|
7145
|
-
details: {
|
|
7146
|
-
book_title: title,
|
|
7147
|
-
chapter_title: chapter.title,
|
|
7148
|
-
chapter_index: String(index + 1),
|
|
7149
|
-
chapter_count: String(extracted.chapters.length),
|
|
7760
|
+
return groupedPreparedInputsFor({
|
|
7761
|
+
title,
|
|
7762
|
+
originType: "file",
|
|
7763
|
+
sourceKind: "epub",
|
|
7764
|
+
sourceClass,
|
|
7765
|
+
originalPath: toPosix(absoluteInput),
|
|
7766
|
+
repoRelativePath: repoRelativePathFor(absoluteInput, repoRoot),
|
|
7767
|
+
mimeType,
|
|
7768
|
+
storedExtension: ".md",
|
|
7769
|
+
warnings: extracted.warnings,
|
|
7770
|
+
parts: extracted.chapters.map((chapter) => ({
|
|
7771
|
+
...chapter,
|
|
7772
|
+
metadata: {
|
|
7773
|
+
...chapter.metadata,
|
|
7150
7774
|
...extracted.author ? { author: extracted.author } : {}
|
|
7151
7775
|
}
|
|
7152
|
-
})
|
|
7153
|
-
);
|
|
7776
|
+
}))
|
|
7777
|
+
});
|
|
7154
7778
|
}
|
|
7155
7779
|
extractedText = void 0;
|
|
7156
7780
|
extractionArtifact = {
|
|
@@ -7208,6 +7832,25 @@ async function prepareUrlInputs(rootDir, input, options) {
|
|
|
7208
7832
|
const finalUrl = normalizeOriginUrl(response.url || input);
|
|
7209
7833
|
const inputUrl = new URL(finalUrl);
|
|
7210
7834
|
const originalPayloadBytes = Buffer.from(await response.arrayBuffer());
|
|
7835
|
+
if (path12.extname(inputUrl.pathname).toLowerCase() === ".zip" && isSlackExportArchive(originalPayloadBytes)) {
|
|
7836
|
+
const slackExport = await extractSlackExportArchive({
|
|
7837
|
+
mimeType: "application/zip",
|
|
7838
|
+
bytes: originalPayloadBytes,
|
|
7839
|
+
fileName: inputUrl.pathname
|
|
7840
|
+
});
|
|
7841
|
+
if (slackExport.conversations.length) {
|
|
7842
|
+
return groupedPreparedInputsFor({
|
|
7843
|
+
title: slackExport.title?.trim() || inputUrl.hostname,
|
|
7844
|
+
originType: "url",
|
|
7845
|
+
sourceKind: "chat_export",
|
|
7846
|
+
url: finalUrl,
|
|
7847
|
+
mimeType: "application/zip",
|
|
7848
|
+
storedExtension: ".md",
|
|
7849
|
+
warnings: slackExport.warnings,
|
|
7850
|
+
parts: slackExport.conversations
|
|
7851
|
+
});
|
|
7852
|
+
}
|
|
7853
|
+
}
|
|
7211
7854
|
let payloadBytes = originalPayloadBytes;
|
|
7212
7855
|
let mimeType = resolveUrlMimeType(input, response);
|
|
7213
7856
|
let sourceKind = inferKind(mimeType, inputUrl.pathname);
|
|
@@ -7294,6 +7937,60 @@ async function prepareUrlInputs(rootDir, input, options) {
|
|
|
7294
7937
|
title = extracted.artifact.metadata?.title?.trim() || title;
|
|
7295
7938
|
extractedText = extracted.extractedText;
|
|
7296
7939
|
extractionArtifact = extracted.artifact;
|
|
7940
|
+
} else if (sourceKind === "transcript") {
|
|
7941
|
+
const extracted = await extractTranscriptText({ mimeType, bytes: payloadBytes, fileName: inputUrl.pathname });
|
|
7942
|
+
title = extracted.title?.trim() || title;
|
|
7943
|
+
extractedText = extracted.extractedText;
|
|
7944
|
+
extractionArtifact = extracted.artifact;
|
|
7945
|
+
} else if (sourceKind === "email" && path12.extname(inputUrl.pathname).toLowerCase() === ".eml") {
|
|
7946
|
+
const extracted = await extractEmailText({ mimeType, bytes: payloadBytes, fileName: inputUrl.pathname });
|
|
7947
|
+
title = extracted.title?.trim() || title;
|
|
7948
|
+
extractedText = extracted.extractedText;
|
|
7949
|
+
extractionArtifact = extracted.artifact;
|
|
7950
|
+
} else if (sourceKind === "email" && path12.extname(inputUrl.pathname).toLowerCase() === ".mbox") {
|
|
7951
|
+
const extracted = await extractMboxMessages({ mimeType, bytes: payloadBytes, fileName: inputUrl.pathname });
|
|
7952
|
+
title = extracted.title?.trim() || title;
|
|
7953
|
+
if (extracted.messages.length) {
|
|
7954
|
+
return groupedPreparedInputsFor({
|
|
7955
|
+
title,
|
|
7956
|
+
originType: "url",
|
|
7957
|
+
sourceKind: "email",
|
|
7958
|
+
url: finalUrl,
|
|
7959
|
+
mimeType,
|
|
7960
|
+
storedExtension: ".md",
|
|
7961
|
+
warnings: extracted.warnings,
|
|
7962
|
+
parts: extracted.messages
|
|
7963
|
+
});
|
|
7964
|
+
}
|
|
7965
|
+
extractionArtifact = {
|
|
7966
|
+
extractor: "email_text",
|
|
7967
|
+
sourceKind: "email",
|
|
7968
|
+
mimeType,
|
|
7969
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7970
|
+
warnings: extracted.warnings ?? ["Mailbox extraction completed but produced no readable messages."]
|
|
7971
|
+
};
|
|
7972
|
+
} else if (sourceKind === "calendar") {
|
|
7973
|
+
const extracted = await extractCalendarEvents({ mimeType, bytes: payloadBytes, fileName: inputUrl.pathname });
|
|
7974
|
+
title = extracted.title?.trim() || title;
|
|
7975
|
+
if (extracted.events.length) {
|
|
7976
|
+
return groupedPreparedInputsFor({
|
|
7977
|
+
title,
|
|
7978
|
+
originType: "url",
|
|
7979
|
+
sourceKind: "calendar",
|
|
7980
|
+
url: finalUrl,
|
|
7981
|
+
mimeType,
|
|
7982
|
+
storedExtension: ".md",
|
|
7983
|
+
warnings: extracted.warnings,
|
|
7984
|
+
parts: extracted.events
|
|
7985
|
+
});
|
|
7986
|
+
}
|
|
7987
|
+
extractionArtifact = {
|
|
7988
|
+
extractor: "calendar_text",
|
|
7989
|
+
sourceKind: "calendar",
|
|
7990
|
+
mimeType,
|
|
7991
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7992
|
+
warnings: extracted.warnings ?? ["Calendar extraction completed but found no events."]
|
|
7993
|
+
};
|
|
7297
7994
|
} else if (sourceKind === "csv") {
|
|
7298
7995
|
const extracted = await extractCsvText({ mimeType, bytes: payloadBytes, fileName: inputUrl.pathname });
|
|
7299
7996
|
title = extracted.title?.trim() || title;
|
|
@@ -7312,62 +8009,24 @@ async function prepareUrlInputs(rootDir, input, options) {
|
|
|
7312
8009
|
} else if (sourceKind === "epub") {
|
|
7313
8010
|
const extracted = await extractEpubChapters({ mimeType, bytes: payloadBytes, fileName: inputUrl.pathname });
|
|
7314
8011
|
title = extracted.title?.trim() || title;
|
|
7315
|
-
const groupId = sourceGroupIdFor({
|
|
7316
|
-
title,
|
|
7317
|
-
originType: "url",
|
|
7318
|
-
url: finalUrl
|
|
7319
|
-
});
|
|
7320
8012
|
if (extracted.chapters.length) {
|
|
7321
|
-
return
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
sourceKind: "epub",
|
|
7334
|
-
mimeType,
|
|
7335
|
-
producedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7336
|
-
metadata: {
|
|
7337
|
-
...chapter.metadata,
|
|
7338
|
-
chapter_index: String(index + 1),
|
|
7339
|
-
chapter_count: String(extracted.chapters.length)
|
|
7340
|
-
},
|
|
7341
|
-
warnings: extracted.warnings
|
|
7342
|
-
},
|
|
7343
|
-
extractionHash: buildExtractionHash(chapter.markdown, {
|
|
7344
|
-
extractor: "epub_text",
|
|
7345
|
-
sourceKind: "epub",
|
|
7346
|
-
mimeType,
|
|
7347
|
-
producedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7348
|
-
metadata: {
|
|
7349
|
-
...chapter.metadata,
|
|
7350
|
-
chapter_index: String(index + 1),
|
|
7351
|
-
chapter_count: String(extracted.chapters.length)
|
|
7352
|
-
},
|
|
7353
|
-
warnings: extracted.warnings
|
|
7354
|
-
}),
|
|
7355
|
-
sourceGroupId: groupId,
|
|
7356
|
-
sourceGroupTitle: title,
|
|
7357
|
-
sourcePartKey: chapter.partKey,
|
|
7358
|
-
partIndex: index + 1,
|
|
7359
|
-
partCount: extracted.chapters.length,
|
|
7360
|
-
partTitle: chapter.title,
|
|
7361
|
-
details: {
|
|
7362
|
-
book_title: title,
|
|
7363
|
-
chapter_title: chapter.title,
|
|
7364
|
-
chapter_index: String(index + 1),
|
|
7365
|
-
chapter_count: String(extracted.chapters.length),
|
|
8013
|
+
return groupedPreparedInputsFor({
|
|
8014
|
+
title,
|
|
8015
|
+
originType: "url",
|
|
8016
|
+
sourceKind: "epub",
|
|
8017
|
+
url: finalUrl,
|
|
8018
|
+
mimeType,
|
|
8019
|
+
storedExtension: ".md",
|
|
8020
|
+
warnings: extracted.warnings,
|
|
8021
|
+
parts: extracted.chapters.map((chapter) => ({
|
|
8022
|
+
...chapter,
|
|
8023
|
+
metadata: {
|
|
8024
|
+
...chapter.metadata,
|
|
7366
8025
|
...extracted.author ? { author: extracted.author } : {}
|
|
7367
|
-
}
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
);
|
|
8026
|
+
}
|
|
8027
|
+
})),
|
|
8028
|
+
logDetails
|
|
8029
|
+
});
|
|
7371
8030
|
}
|
|
7372
8031
|
extractionArtifact = {
|
|
7373
8032
|
extractor: "epub_text",
|
|
@@ -7544,13 +8203,28 @@ async function prepareInboxHtmlInput(absolutePath, attachmentRefs) {
|
|
|
7544
8203
|
};
|
|
7545
8204
|
}
|
|
7546
8205
|
function isSupportedInboxKind(sourceKind) {
|
|
7547
|
-
return [
|
|
8206
|
+
return [
|
|
8207
|
+
"markdown",
|
|
8208
|
+
"text",
|
|
8209
|
+
"html",
|
|
8210
|
+
"pdf",
|
|
8211
|
+
"docx",
|
|
8212
|
+
"epub",
|
|
8213
|
+
"csv",
|
|
8214
|
+
"xlsx",
|
|
8215
|
+
"pptx",
|
|
8216
|
+
"transcript",
|
|
8217
|
+
"chat_export",
|
|
8218
|
+
"email",
|
|
8219
|
+
"calendar",
|
|
8220
|
+
"image"
|
|
8221
|
+
].includes(sourceKind);
|
|
7548
8222
|
}
|
|
7549
8223
|
async function ingestInputDetailed(rootDir, input, options) {
|
|
7550
8224
|
const { paths } = await initWorkspace(rootDir);
|
|
7551
8225
|
const normalizedOptions = normalizeIngestOptions(options);
|
|
7552
8226
|
const absoluteInput = path12.resolve(rootDir, input);
|
|
7553
|
-
const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await
|
|
8227
|
+
const repoRoot = isHttpUrl(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await detectScopedRepoRoot(rootDir, absoluteInput, path12.dirname(absoluteInput));
|
|
7554
8228
|
const prepared = isHttpUrl(input) ? await prepareUrlInputs(rootDir, input, normalizedOptions) : await prepareFileInputs(rootDir, absoluteInput, repoRoot);
|
|
7555
8229
|
return await persistPreparedInputs(rootDir, input, prepared, paths);
|
|
7556
8230
|
}
|
|
@@ -7648,10 +8322,39 @@ async function ingestDirectory(rootDir, inputDir, options) {
|
|
|
7648
8322
|
const { paths } = await initWorkspace(rootDir);
|
|
7649
8323
|
const normalizedOptions = await resolveRepoIngestOptions(rootDir, options);
|
|
7650
8324
|
const absoluteInputDir = path12.resolve(rootDir, inputDir);
|
|
7651
|
-
const repoRoot = normalizedOptions.repoRoot ?? await
|
|
8325
|
+
const repoRoot = normalizedOptions.repoRoot ?? await detectScopedRepoRoot(rootDir, absoluteInputDir, absoluteInputDir);
|
|
7652
8326
|
if (!await fileExists(absoluteInputDir)) {
|
|
7653
8327
|
throw new Error(`Directory not found: ${absoluteInputDir}`);
|
|
7654
8328
|
}
|
|
8329
|
+
if (await isSlackExportDirectory(absoluteInputDir)) {
|
|
8330
|
+
const extracted = await extractSlackExportDirectory(absoluteInputDir);
|
|
8331
|
+
const preparedInputs = groupedPreparedInputsFor({
|
|
8332
|
+
title: extracted.title?.trim() || path12.basename(absoluteInputDir),
|
|
8333
|
+
originType: "file",
|
|
8334
|
+
sourceKind: "chat_export",
|
|
8335
|
+
originalPath: toPosix(absoluteInputDir),
|
|
8336
|
+
mimeType: "application/json",
|
|
8337
|
+
storedExtension: ".md",
|
|
8338
|
+
warnings: extracted.warnings,
|
|
8339
|
+
parts: extracted.conversations
|
|
8340
|
+
});
|
|
8341
|
+
const result = await persistPreparedInputs(rootDir, absoluteInputDir, preparedInputs, paths);
|
|
8342
|
+
await appendLogEntry(rootDir, "ingest_directory", toPosix(path12.relative(rootDir, absoluteInputDir)) || ".", [
|
|
8343
|
+
`repo_root=${toPosix(path12.relative(rootDir, repoRoot)) || "."}`,
|
|
8344
|
+
`scanned=${preparedInputs.length}`,
|
|
8345
|
+
`imported=${result.created.length}`,
|
|
8346
|
+
`updated=${result.updated.length}`,
|
|
8347
|
+
`skipped=${result.skipped.length}`
|
|
8348
|
+
]);
|
|
8349
|
+
return {
|
|
8350
|
+
inputDir: absoluteInputDir,
|
|
8351
|
+
repoRoot,
|
|
8352
|
+
scannedCount: preparedInputs.length,
|
|
8353
|
+
imported: result.created,
|
|
8354
|
+
updated: result.updated,
|
|
8355
|
+
skipped: result.skipped
|
|
8356
|
+
};
|
|
8357
|
+
}
|
|
7655
8358
|
const { files, skipped } = await collectDirectoryFiles(rootDir, absoluteInputDir, repoRoot, normalizedOptions);
|
|
7656
8359
|
const imported = [];
|
|
7657
8360
|
const updated = [];
|
|
@@ -7716,7 +8419,13 @@ async function importInbox(rootDir, inputDir) {
|
|
|
7716
8419
|
continue;
|
|
7717
8420
|
}
|
|
7718
8421
|
const mimeType = guessMimeType(absolutePath);
|
|
7719
|
-
|
|
8422
|
+
let sourceKind = inferKind(mimeType, absolutePath);
|
|
8423
|
+
if (sourceKind === "binary" && path12.extname(absolutePath).toLowerCase() === ".zip") {
|
|
8424
|
+
const bytes = await fs11.readFile(absolutePath);
|
|
8425
|
+
if (isSlackExportArchive(bytes)) {
|
|
8426
|
+
sourceKind = "chat_export";
|
|
8427
|
+
}
|
|
8428
|
+
}
|
|
7720
8429
|
if (!isSupportedInboxKind(sourceKind)) {
|
|
7721
8430
|
skipped.push({ path: toPosix(path12.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
|
|
7722
8431
|
continue;
|
|
@@ -9945,6 +10654,19 @@ function relatedOutputsSection(relatedOutputs) {
|
|
|
9945
10654
|
}
|
|
9946
10655
|
return ["## Related Outputs", "", ...relatedOutputs.map((page) => `- ${pageLink(page)}`), ""];
|
|
9947
10656
|
}
|
|
10657
|
+
function detailValue(manifest, key) {
|
|
10658
|
+
const value = manifest.details?.[key];
|
|
10659
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
10660
|
+
return normalized || void 0;
|
|
10661
|
+
}
|
|
10662
|
+
function detailList(manifest, key) {
|
|
10663
|
+
const value = detailValue(manifest, key);
|
|
10664
|
+
if (!value) {
|
|
10665
|
+
return void 0;
|
|
10666
|
+
}
|
|
10667
|
+
const items = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
10668
|
+
return items.length ? items : void 0;
|
|
10669
|
+
}
|
|
9948
10670
|
function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutputs = [], modulePage, decorations) {
|
|
9949
10671
|
const relativePath = pagePathFor("source", manifest.sourceId);
|
|
9950
10672
|
const pageId = `source:${manifest.sourceId}`;
|
|
@@ -9968,6 +10690,10 @@ function buildSourcePage(manifest, analysis, schemaHash, metadata, relatedOutput
|
|
|
9968
10690
|
title: analysis.title,
|
|
9969
10691
|
...manifest.sourceType ? { source_type: manifest.sourceType } : {},
|
|
9970
10692
|
...manifest.sourceClass ? { source_class: manifest.sourceClass } : {},
|
|
10693
|
+
...detailValue(manifest, "occurred_at") ? { occurred_at: detailValue(manifest, "occurred_at") } : {},
|
|
10694
|
+
...detailList(manifest, "participants") ? { participants: detailList(manifest, "participants") } : {},
|
|
10695
|
+
...detailValue(manifest, "container_title") ? { container_title: detailValue(manifest, "container_title") } : {},
|
|
10696
|
+
...detailValue(manifest, "conversation_id") ? { conversation_id: detailValue(manifest, "conversation_id") } : {},
|
|
9971
10697
|
tags: decoratedTags(analysis.code ? ["source", "code"] : ["source"], decorations),
|
|
9972
10698
|
source_ids: [manifest.sourceId],
|
|
9973
10699
|
project_ids: decorations?.projectIds ?? [],
|
|
@@ -10300,6 +11026,9 @@ function buildIndexPage(pages, schemaHash, metadata, projectPages = []) {
|
|
|
10300
11026
|
const outputs = pages.filter((page) => page.kind === "output");
|
|
10301
11027
|
const insights = pages.filter((page) => page.kind === "insight");
|
|
10302
11028
|
const graphPages = pages.filter((page) => page.kind === "graph_report" || page.kind === "community_summary");
|
|
11029
|
+
const dashboards = pages.filter(
|
|
11030
|
+
(page) => page.kind === "index" && page.path.startsWith("dashboards/") && page.path !== "dashboards/index.md"
|
|
11031
|
+
);
|
|
10303
11032
|
return [
|
|
10304
11033
|
"---",
|
|
10305
11034
|
"page_id: index",
|
|
@@ -10345,6 +11074,10 @@ function buildIndexPage(pages, schemaHash, metadata, projectPages = []) {
|
|
|
10345
11074
|
"",
|
|
10346
11075
|
...outputs.length ? outputs.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No saved outputs yet."],
|
|
10347
11076
|
"",
|
|
11077
|
+
"## Dashboards",
|
|
11078
|
+
"",
|
|
11079
|
+
...dashboards.length ? dashboards.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No dashboards yet."],
|
|
11080
|
+
"",
|
|
10348
11081
|
"## Graph",
|
|
10349
11082
|
"",
|
|
10350
11083
|
...graphPages.length ? graphPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No graph reports yet."],
|
|
@@ -11821,15 +12554,37 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
|
|
|
11821
12554
|
const insertPage = db.prepare(
|
|
11822
12555
|
"INSERT INTO pages (id, path, title, body, kind, status, source_type, source_class, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
11823
12556
|
);
|
|
12557
|
+
const rootDir = path21.dirname(wikiDir);
|
|
11824
12558
|
for (const page of pages) {
|
|
11825
12559
|
const absolutePath = path21.join(wikiDir, page.path);
|
|
11826
12560
|
const content = await fs17.readFile(absolutePath, "utf8");
|
|
11827
12561
|
const parsed = matter8(content);
|
|
12562
|
+
let body = parsed.content;
|
|
12563
|
+
const primarySourceId = Array.isArray(parsed.data.source_ids) && typeof parsed.data.source_ids[0] === "string" ? parsed.data.source_ids[0] : page.sourceIds[0];
|
|
12564
|
+
if ((page.kind === "source" || page.kind === "module") && primarySourceId) {
|
|
12565
|
+
try {
|
|
12566
|
+
const manifest = JSON.parse(
|
|
12567
|
+
await fs17.readFile(path21.join(rootDir, "state", "manifests", `${primarySourceId}.json`), "utf8")
|
|
12568
|
+
);
|
|
12569
|
+
const excerptPath = manifest.extractedTextPath ?? manifest.storedPath;
|
|
12570
|
+
if (excerptPath) {
|
|
12571
|
+
const excerpt = await fs17.readFile(path21.join(rootDir, excerptPath), "utf8");
|
|
12572
|
+
if (excerpt.trim()) {
|
|
12573
|
+
body = `${body}
|
|
12574
|
+
|
|
12575
|
+
## Source Excerpt
|
|
12576
|
+
|
|
12577
|
+
${excerpt.trim()}`.trim();
|
|
12578
|
+
}
|
|
12579
|
+
}
|
|
12580
|
+
} catch {
|
|
12581
|
+
}
|
|
12582
|
+
}
|
|
11828
12583
|
insertPage.run(
|
|
11829
12584
|
page.id,
|
|
11830
12585
|
page.path,
|
|
11831
12586
|
page.title,
|
|
11832
|
-
|
|
12587
|
+
body,
|
|
11833
12588
|
page.kind,
|
|
11834
12589
|
page.status,
|
|
11835
12590
|
typeof parsed.data.source_type === "string" ? parsed.data.source_type : "",
|
|
@@ -11890,7 +12645,25 @@ function searchPages(dbPath, query, limitOrOptions = 5) {
|
|
|
11890
12645
|
FROM page_search
|
|
11891
12646
|
JOIN pages ON pages.rowid = page_search.rowid
|
|
11892
12647
|
WHERE ${clauses.join(" AND ")}
|
|
11893
|
-
ORDER BY
|
|
12648
|
+
ORDER BY
|
|
12649
|
+
CASE pages.status
|
|
12650
|
+
WHEN 'active' THEN 0
|
|
12651
|
+
WHEN 'draft' THEN 1
|
|
12652
|
+
WHEN 'candidate' THEN 2
|
|
12653
|
+
ELSE 3
|
|
12654
|
+
END,
|
|
12655
|
+
CASE pages.kind
|
|
12656
|
+
WHEN 'source' THEN 0
|
|
12657
|
+
WHEN 'module' THEN 1
|
|
12658
|
+
WHEN 'output' THEN 2
|
|
12659
|
+
WHEN 'insight' THEN 3
|
|
12660
|
+
WHEN 'graph_report' THEN 4
|
|
12661
|
+
WHEN 'community_summary' THEN 5
|
|
12662
|
+
WHEN 'concept' THEN 6
|
|
12663
|
+
WHEN 'entity' THEN 7
|
|
12664
|
+
ELSE 8
|
|
12665
|
+
END,
|
|
12666
|
+
rank
|
|
11894
12667
|
LIMIT ?
|
|
11895
12668
|
`);
|
|
11896
12669
|
params.push(options.limit ?? 5);
|
|
@@ -12112,7 +12885,7 @@ async function resolveImageGenerationProvider(rootDir) {
|
|
|
12112
12885
|
if (!providerConfig) {
|
|
12113
12886
|
throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
|
|
12114
12887
|
}
|
|
12115
|
-
const { createProvider: createProvider2 } = await import("./registry-
|
|
12888
|
+
const { createProvider: createProvider2 } = await import("./registry-KVJAO5DF.js");
|
|
12116
12889
|
return createProvider2(preferredProviderId, providerConfig, rootDir);
|
|
12117
12890
|
}
|
|
12118
12891
|
async function generateOutputArtifacts(rootDir, input) {
|
|
@@ -12490,6 +13263,8 @@ function approvalSummary(manifest) {
|
|
|
12490
13263
|
return {
|
|
12491
13264
|
approvalId: manifest.approvalId,
|
|
12492
13265
|
createdAt: manifest.createdAt,
|
|
13266
|
+
bundleType: manifest.bundleType,
|
|
13267
|
+
title: manifest.title,
|
|
12493
13268
|
entryCount: manifest.entries.length,
|
|
12494
13269
|
pendingCount: manifest.entries.filter((entry) => entry.status === "pending").length,
|
|
12495
13270
|
acceptedCount: manifest.entries.filter((entry) => entry.status === "accepted").length,
|
|
@@ -12587,6 +13362,425 @@ async function buildManagedContent(absolutePath, defaults, build) {
|
|
|
12587
13362
|
}
|
|
12588
13363
|
return content;
|
|
12589
13364
|
}
|
|
13365
|
+
function manifestDetailValue(manifest, key) {
|
|
13366
|
+
const value = manifest.details?.[key];
|
|
13367
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
13368
|
+
}
|
|
13369
|
+
async function loadAnalysesBySourceIds(paths, sourceIds) {
|
|
13370
|
+
const analyses = await Promise.all(
|
|
13371
|
+
sourceIds.map(async (sourceId) => await readJsonFile(path22.join(paths.analysesDir, `${sourceId}.json`)))
|
|
13372
|
+
);
|
|
13373
|
+
return analyses.filter((analysis) => Boolean(analysis?.sourceId));
|
|
13374
|
+
}
|
|
13375
|
+
async function buildDashboardRecords(paths, graph, schemaHash, report) {
|
|
13376
|
+
const sourcePages = graph.pages.filter((page) => page.kind === "source");
|
|
13377
|
+
const reviewPages = graph.pages.filter((page) => page.kind === "output" && page.path.startsWith("outputs/source-reviews/"));
|
|
13378
|
+
const briefPages = graph.pages.filter((page) => page.kind === "output" && page.path.startsWith("outputs/source-briefs/"));
|
|
13379
|
+
const guidePages = graph.pages.filter((page) => page.kind === "output" && page.path.startsWith("outputs/source-guides/"));
|
|
13380
|
+
const conceptPages = graph.pages.filter((page) => page.kind === "concept" && page.status !== "candidate").slice(0, 16);
|
|
13381
|
+
const entityPages = graph.pages.filter((page) => page.kind === "entity" && page.status !== "candidate").slice(0, 16);
|
|
13382
|
+
const manifests = graph.sources;
|
|
13383
|
+
const manifestBySourceId = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
|
|
13384
|
+
const timelineManifests = manifests.filter((manifest) => manifestDetailValue(manifest, "occurred_at")).sort((left, right) => (manifestDetailValue(right, "occurred_at") ?? "").localeCompare(manifestDetailValue(left, "occurred_at") ?? "")).slice(0, 25);
|
|
13385
|
+
const recentSourcePages = [...sourcePages].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, 20);
|
|
13386
|
+
const analyses = await loadAnalysesBySourceIds(paths, uniqueStrings3(sourcePages.flatMap((page) => page.sourceIds)));
|
|
13387
|
+
const openQuestions = uniqueStrings3(
|
|
13388
|
+
analyses.flatMap((analysis) => analysis.questions.map((question) => `${analysis.title}: ${question}`))
|
|
13389
|
+
).slice(0, 20);
|
|
13390
|
+
const stagedGuideBundles = (await Promise.all(
|
|
13391
|
+
(await fs18.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => await readJsonFile(approvalManifestPath(paths, entry.name)))
|
|
13392
|
+
)).filter((manifest) => Boolean(manifest)).filter((manifest) => manifest.bundleType === "guided_source").sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, 12);
|
|
13393
|
+
const dashboards = [
|
|
13394
|
+
{
|
|
13395
|
+
relativePath: "dashboards/index.md",
|
|
13396
|
+
title: "Dashboards",
|
|
13397
|
+
content: (metadata) => matter9.stringify(
|
|
13398
|
+
[
|
|
13399
|
+
"# Dashboards",
|
|
13400
|
+
"",
|
|
13401
|
+
"- [[dashboards/recent-sources|Recent Sources]]",
|
|
13402
|
+
"- [[dashboards/reading-log|Reading Log]]",
|
|
13403
|
+
"- [[dashboards/timeline|Timeline]]",
|
|
13404
|
+
"- [[dashboards/source-guides|Source Guides]]",
|
|
13405
|
+
"- [[dashboards/research-map|Research Map]]",
|
|
13406
|
+
"- [[dashboards/contradictions|Contradictions]]",
|
|
13407
|
+
"- [[dashboards/open-questions|Open Questions]]",
|
|
13408
|
+
"",
|
|
13409
|
+
"```dataview",
|
|
13410
|
+
"TABLE file.mtime AS updated",
|
|
13411
|
+
'FROM "dashboards"',
|
|
13412
|
+
'WHERE file.name != "index"',
|
|
13413
|
+
"SORT file.mtime desc",
|
|
13414
|
+
"```",
|
|
13415
|
+
""
|
|
13416
|
+
].join("\n"),
|
|
13417
|
+
{
|
|
13418
|
+
page_id: "dashboards:index",
|
|
13419
|
+
kind: "index",
|
|
13420
|
+
title: "Dashboards",
|
|
13421
|
+
tags: ["index", "dashboards"],
|
|
13422
|
+
source_ids: [],
|
|
13423
|
+
project_ids: [],
|
|
13424
|
+
node_ids: [],
|
|
13425
|
+
freshness: "fresh",
|
|
13426
|
+
status: metadata.status,
|
|
13427
|
+
confidence: 1,
|
|
13428
|
+
created_at: metadata.createdAt,
|
|
13429
|
+
updated_at: metadata.updatedAt,
|
|
13430
|
+
compiled_from: metadata.compiledFrom,
|
|
13431
|
+
managed_by: metadata.managedBy,
|
|
13432
|
+
backlinks: [],
|
|
13433
|
+
schema_hash: schemaHash,
|
|
13434
|
+
source_hashes: {},
|
|
13435
|
+
source_semantic_hashes: {}
|
|
13436
|
+
}
|
|
13437
|
+
)
|
|
13438
|
+
},
|
|
13439
|
+
{
|
|
13440
|
+
relativePath: "dashboards/recent-sources.md",
|
|
13441
|
+
title: "Recent Sources",
|
|
13442
|
+
content: (metadata) => matter9.stringify(
|
|
13443
|
+
[
|
|
13444
|
+
"# Recent Sources",
|
|
13445
|
+
"",
|
|
13446
|
+
...recentSourcePages.length ? recentSourcePages.map((page) => `- ${page.updatedAt}: [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No source pages yet."],
|
|
13447
|
+
"",
|
|
13448
|
+
"```dataview",
|
|
13449
|
+
"TABLE source_type, occurred_at, participants",
|
|
13450
|
+
'FROM "sources"',
|
|
13451
|
+
"SORT updated_at desc",
|
|
13452
|
+
"LIMIT 25",
|
|
13453
|
+
"```",
|
|
13454
|
+
""
|
|
13455
|
+
].join("\n"),
|
|
13456
|
+
{
|
|
13457
|
+
page_id: "dashboards:recent-sources",
|
|
13458
|
+
kind: "index",
|
|
13459
|
+
title: "Recent Sources",
|
|
13460
|
+
tags: ["index", "dashboard", "recent-sources"],
|
|
13461
|
+
source_ids: recentSourcePages.flatMap((page) => page.sourceIds),
|
|
13462
|
+
project_ids: [],
|
|
13463
|
+
node_ids: [],
|
|
13464
|
+
freshness: "fresh",
|
|
13465
|
+
status: metadata.status,
|
|
13466
|
+
confidence: 1,
|
|
13467
|
+
created_at: metadata.createdAt,
|
|
13468
|
+
updated_at: metadata.updatedAt,
|
|
13469
|
+
compiled_from: recentSourcePages.flatMap((page) => page.sourceIds),
|
|
13470
|
+
managed_by: metadata.managedBy,
|
|
13471
|
+
backlinks: [],
|
|
13472
|
+
schema_hash: schemaHash,
|
|
13473
|
+
source_hashes: {},
|
|
13474
|
+
source_semantic_hashes: {}
|
|
13475
|
+
}
|
|
13476
|
+
)
|
|
13477
|
+
},
|
|
13478
|
+
{
|
|
13479
|
+
relativePath: "dashboards/reading-log.md",
|
|
13480
|
+
title: "Reading Log",
|
|
13481
|
+
content: (metadata) => matter9.stringify(
|
|
13482
|
+
[
|
|
13483
|
+
"# Reading Log",
|
|
13484
|
+
"",
|
|
13485
|
+
...timelineManifests.length ? timelineManifests.map((manifest) => {
|
|
13486
|
+
const occurredAt = manifestDetailValue(manifest, "occurred_at") ?? manifest.updatedAt;
|
|
13487
|
+
const participants = manifestDetailValue(manifest, "participants");
|
|
13488
|
+
return `- ${occurredAt}: ${manifest.title}${participants ? ` (${participants})` : ""}`;
|
|
13489
|
+
}) : recentSourcePages.map((page) => `- ${page.updatedAt}: [[${page.path.replace(/\.md$/, "")}|${page.title}]]`),
|
|
13490
|
+
"",
|
|
13491
|
+
"```dataview",
|
|
13492
|
+
"TABLE occurred_at, source_type, participants, container_title",
|
|
13493
|
+
'FROM "sources"',
|
|
13494
|
+
"SORT occurred_at desc",
|
|
13495
|
+
"LIMIT 25",
|
|
13496
|
+
"```",
|
|
13497
|
+
""
|
|
13498
|
+
].join("\n"),
|
|
13499
|
+
{
|
|
13500
|
+
page_id: "dashboards:reading-log",
|
|
13501
|
+
kind: "index",
|
|
13502
|
+
title: "Reading Log",
|
|
13503
|
+
tags: ["index", "dashboard", "reading-log"],
|
|
13504
|
+
source_ids: timelineManifests.map((manifest) => manifest.sourceId),
|
|
13505
|
+
project_ids: [],
|
|
13506
|
+
node_ids: [],
|
|
13507
|
+
freshness: "fresh",
|
|
13508
|
+
status: metadata.status,
|
|
13509
|
+
confidence: 1,
|
|
13510
|
+
created_at: metadata.createdAt,
|
|
13511
|
+
updated_at: metadata.updatedAt,
|
|
13512
|
+
compiled_from: timelineManifests.map((manifest) => manifest.sourceId),
|
|
13513
|
+
managed_by: metadata.managedBy,
|
|
13514
|
+
backlinks: [],
|
|
13515
|
+
schema_hash: schemaHash,
|
|
13516
|
+
source_hashes: {},
|
|
13517
|
+
source_semantic_hashes: {}
|
|
13518
|
+
}
|
|
13519
|
+
)
|
|
13520
|
+
},
|
|
13521
|
+
{
|
|
13522
|
+
relativePath: "dashboards/timeline.md",
|
|
13523
|
+
title: "Timeline",
|
|
13524
|
+
content: (metadata) => matter9.stringify(
|
|
13525
|
+
[
|
|
13526
|
+
"# Timeline",
|
|
13527
|
+
"",
|
|
13528
|
+
...timelineManifests.length ? timelineManifests.map((manifest) => {
|
|
13529
|
+
const occurredAt = manifestDetailValue(manifest, "occurred_at") ?? manifest.updatedAt;
|
|
13530
|
+
const sourcePage = sourcePages.find((page) => page.sourceIds.includes(manifest.sourceId));
|
|
13531
|
+
return `- ${occurredAt}: ${sourcePage ? `[[${sourcePage.path.replace(/\.md$/, "")}|${sourcePage.title}]]` : manifest.title}`;
|
|
13532
|
+
}) : ["- No timeline-aware sources yet."],
|
|
13533
|
+
"",
|
|
13534
|
+
"```dataview",
|
|
13535
|
+
"TABLE occurred_at, participants, container_title",
|
|
13536
|
+
'FROM "sources"',
|
|
13537
|
+
"WHERE occurred_at",
|
|
13538
|
+
"SORT occurred_at desc",
|
|
13539
|
+
"```",
|
|
13540
|
+
""
|
|
13541
|
+
].join("\n"),
|
|
13542
|
+
{
|
|
13543
|
+
page_id: "dashboards:timeline",
|
|
13544
|
+
kind: "index",
|
|
13545
|
+
title: "Timeline",
|
|
13546
|
+
tags: ["index", "dashboard", "timeline"],
|
|
13547
|
+
source_ids: timelineManifests.map((manifest) => manifest.sourceId),
|
|
13548
|
+
project_ids: [],
|
|
13549
|
+
node_ids: [],
|
|
13550
|
+
freshness: "fresh",
|
|
13551
|
+
status: metadata.status,
|
|
13552
|
+
confidence: 1,
|
|
13553
|
+
created_at: metadata.createdAt,
|
|
13554
|
+
updated_at: metadata.updatedAt,
|
|
13555
|
+
compiled_from: timelineManifests.map((manifest) => manifest.sourceId),
|
|
13556
|
+
managed_by: metadata.managedBy,
|
|
13557
|
+
backlinks: [],
|
|
13558
|
+
schema_hash: schemaHash,
|
|
13559
|
+
source_hashes: {},
|
|
13560
|
+
source_semantic_hashes: {}
|
|
13561
|
+
}
|
|
13562
|
+
)
|
|
13563
|
+
},
|
|
13564
|
+
{
|
|
13565
|
+
relativePath: "dashboards/source-guides.md",
|
|
13566
|
+
title: "Source Guides",
|
|
13567
|
+
content: (metadata) => matter9.stringify(
|
|
13568
|
+
[
|
|
13569
|
+
"# Source Guides",
|
|
13570
|
+
"",
|
|
13571
|
+
...guidePages.length ? guidePages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No accepted source guides yet."],
|
|
13572
|
+
"",
|
|
13573
|
+
"## Pending Guided Bundles",
|
|
13574
|
+
"",
|
|
13575
|
+
...stagedGuideBundles.length ? stagedGuideBundles.map(
|
|
13576
|
+
(bundle) => `- ${bundle.createdAt}: \`${bundle.approvalId}\`${bundle.title ? ` ${bundle.title}` : ""} (${bundle.entries.length} staged entr${bundle.entries.length === 1 ? "y" : "ies"})`
|
|
13577
|
+
) : ["- No staged guided bundles right now."],
|
|
13578
|
+
"",
|
|
13579
|
+
"```dataview",
|
|
13580
|
+
'LIST FROM "outputs/source-guides"',
|
|
13581
|
+
"SORT file.mtime desc",
|
|
13582
|
+
"```",
|
|
13583
|
+
""
|
|
13584
|
+
].join("\n"),
|
|
13585
|
+
{
|
|
13586
|
+
page_id: "dashboards:source-guides",
|
|
13587
|
+
kind: "index",
|
|
13588
|
+
title: "Source Guides",
|
|
13589
|
+
tags: ["index", "dashboard", "source-guides"],
|
|
13590
|
+
source_ids: uniqueStrings3([
|
|
13591
|
+
...guidePages.flatMap((page) => page.sourceIds),
|
|
13592
|
+
...stagedGuideBundles.flatMap((bundle) => bundle.entries.flatMap((entry) => entry.sourceIds))
|
|
13593
|
+
]),
|
|
13594
|
+
project_ids: [],
|
|
13595
|
+
node_ids: [],
|
|
13596
|
+
freshness: "fresh",
|
|
13597
|
+
status: metadata.status,
|
|
13598
|
+
confidence: 1,
|
|
13599
|
+
created_at: metadata.createdAt,
|
|
13600
|
+
updated_at: metadata.updatedAt,
|
|
13601
|
+
compiled_from: uniqueStrings3([
|
|
13602
|
+
...guidePages.flatMap((page) => page.sourceIds),
|
|
13603
|
+
...stagedGuideBundles.flatMap((bundle) => bundle.entries.flatMap((entry) => entry.sourceIds))
|
|
13604
|
+
]),
|
|
13605
|
+
managed_by: metadata.managedBy,
|
|
13606
|
+
backlinks: [],
|
|
13607
|
+
schema_hash: schemaHash,
|
|
13608
|
+
source_hashes: {},
|
|
13609
|
+
source_semantic_hashes: {}
|
|
13610
|
+
}
|
|
13611
|
+
)
|
|
13612
|
+
},
|
|
13613
|
+
{
|
|
13614
|
+
relativePath: "dashboards/research-map.md",
|
|
13615
|
+
title: "Research Map",
|
|
13616
|
+
content: (metadata) => matter9.stringify(
|
|
13617
|
+
[
|
|
13618
|
+
"# Research Map",
|
|
13619
|
+
"",
|
|
13620
|
+
"## Canonical Concept Pages",
|
|
13621
|
+
"",
|
|
13622
|
+
...conceptPages.length ? conceptPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No concept pages yet."],
|
|
13623
|
+
"",
|
|
13624
|
+
"## Canonical Entity Pages",
|
|
13625
|
+
"",
|
|
13626
|
+
...entityPages.length ? entityPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No entity pages yet."],
|
|
13627
|
+
"",
|
|
13628
|
+
"## Recently Guided Sources",
|
|
13629
|
+
"",
|
|
13630
|
+
...guidePages.length ? guidePages.slice(0, 8).map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No accepted source guides yet."],
|
|
13631
|
+
...report?.suggestedQuestions?.length ? ["", "## Suggested Questions", "", ...report.suggestedQuestions.slice(0, 8).map((question) => `- ${question}`)] : [],
|
|
13632
|
+
"",
|
|
13633
|
+
"```dataview",
|
|
13634
|
+
'TABLE file.folder, file.mtime FROM "concepts" OR "entities"',
|
|
13635
|
+
"SORT file.mtime desc",
|
|
13636
|
+
"LIMIT 30",
|
|
13637
|
+
"```",
|
|
13638
|
+
""
|
|
13639
|
+
].join("\n"),
|
|
13640
|
+
{
|
|
13641
|
+
page_id: "dashboards:research-map",
|
|
13642
|
+
kind: "index",
|
|
13643
|
+
title: "Research Map",
|
|
13644
|
+
tags: ["index", "dashboard", "research-map"],
|
|
13645
|
+
source_ids: uniqueStrings3([
|
|
13646
|
+
...conceptPages.flatMap((page) => page.sourceIds),
|
|
13647
|
+
...entityPages.flatMap((page) => page.sourceIds),
|
|
13648
|
+
...guidePages.flatMap((page) => page.sourceIds)
|
|
13649
|
+
]),
|
|
13650
|
+
project_ids: [],
|
|
13651
|
+
node_ids: [],
|
|
13652
|
+
freshness: "fresh",
|
|
13653
|
+
status: metadata.status,
|
|
13654
|
+
confidence: 1,
|
|
13655
|
+
created_at: metadata.createdAt,
|
|
13656
|
+
updated_at: metadata.updatedAt,
|
|
13657
|
+
compiled_from: uniqueStrings3([
|
|
13658
|
+
...conceptPages.flatMap((page) => page.sourceIds),
|
|
13659
|
+
...entityPages.flatMap((page) => page.sourceIds),
|
|
13660
|
+
...guidePages.flatMap((page) => page.sourceIds)
|
|
13661
|
+
]),
|
|
13662
|
+
managed_by: metadata.managedBy,
|
|
13663
|
+
backlinks: [],
|
|
13664
|
+
schema_hash: schemaHash,
|
|
13665
|
+
source_hashes: {},
|
|
13666
|
+
source_semantic_hashes: {}
|
|
13667
|
+
}
|
|
13668
|
+
)
|
|
13669
|
+
},
|
|
13670
|
+
{
|
|
13671
|
+
relativePath: "dashboards/contradictions.md",
|
|
13672
|
+
title: "Contradictions",
|
|
13673
|
+
content: (metadata) => matter9.stringify(
|
|
13674
|
+
[
|
|
13675
|
+
"# Contradictions",
|
|
13676
|
+
"",
|
|
13677
|
+
...report?.contradictions.length ? report.contradictions.map((contradiction) => {
|
|
13678
|
+
const left = manifestBySourceId.get(contradiction.sourceIdA)?.title ?? contradiction.sourceIdA;
|
|
13679
|
+
const right = manifestBySourceId.get(contradiction.sourceIdB)?.title ?? contradiction.sourceIdB;
|
|
13680
|
+
return `- ${left} / ${right}: ${contradiction.claimA} <> ${contradiction.claimB}`;
|
|
13681
|
+
}) : ["- No contradictions are currently flagged."],
|
|
13682
|
+
"",
|
|
13683
|
+
...reviewPages.length || briefPages.length || guidePages.length ? [
|
|
13684
|
+
"## Related Reviews",
|
|
13685
|
+
"",
|
|
13686
|
+
...[...guidePages, ...reviewPages, ...briefPages].slice(0, 12).map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`),
|
|
13687
|
+
""
|
|
13688
|
+
] : [],
|
|
13689
|
+
"```dataview",
|
|
13690
|
+
'LIST FROM "outputs/source-reviews" OR "outputs/source-guides"',
|
|
13691
|
+
"SORT file.mtime desc",
|
|
13692
|
+
"```",
|
|
13693
|
+
""
|
|
13694
|
+
].join("\n"),
|
|
13695
|
+
{
|
|
13696
|
+
page_id: "dashboards:contradictions",
|
|
13697
|
+
kind: "index",
|
|
13698
|
+
title: "Contradictions",
|
|
13699
|
+
tags: ["index", "dashboard", "contradictions"],
|
|
13700
|
+
source_ids: report?.contradictions.flatMap((item) => [item.sourceIdA, item.sourceIdB]) ?? [],
|
|
13701
|
+
project_ids: [],
|
|
13702
|
+
node_ids: [],
|
|
13703
|
+
freshness: "fresh",
|
|
13704
|
+
status: metadata.status,
|
|
13705
|
+
confidence: 1,
|
|
13706
|
+
created_at: metadata.createdAt,
|
|
13707
|
+
updated_at: metadata.updatedAt,
|
|
13708
|
+
compiled_from: report?.contradictions.flatMap((item) => [item.sourceIdA, item.sourceIdB]) ?? [],
|
|
13709
|
+
managed_by: metadata.managedBy,
|
|
13710
|
+
backlinks: [],
|
|
13711
|
+
schema_hash: schemaHash,
|
|
13712
|
+
source_hashes: {},
|
|
13713
|
+
source_semantic_hashes: {}
|
|
13714
|
+
}
|
|
13715
|
+
)
|
|
13716
|
+
},
|
|
13717
|
+
{
|
|
13718
|
+
relativePath: "dashboards/open-questions.md",
|
|
13719
|
+
title: "Open Questions",
|
|
13720
|
+
content: (metadata) => matter9.stringify(
|
|
13721
|
+
[
|
|
13722
|
+
"# Open Questions",
|
|
13723
|
+
"",
|
|
13724
|
+
...openQuestions.length ? openQuestions.map((question) => `- ${question}`) : ["- No open questions are currently extracted."],
|
|
13725
|
+
"",
|
|
13726
|
+
"```dataview",
|
|
13727
|
+
'LIST FROM "outputs/source-briefs" OR "outputs/source-reviews" OR "outputs/source-guides"',
|
|
13728
|
+
"SORT file.mtime desc",
|
|
13729
|
+
"```",
|
|
13730
|
+
""
|
|
13731
|
+
].join("\n"),
|
|
13732
|
+
{
|
|
13733
|
+
page_id: "dashboards:open-questions",
|
|
13734
|
+
kind: "index",
|
|
13735
|
+
title: "Open Questions",
|
|
13736
|
+
tags: ["index", "dashboard", "open-questions"],
|
|
13737
|
+
source_ids: analyses.map((analysis) => analysis.sourceId),
|
|
13738
|
+
project_ids: [],
|
|
13739
|
+
node_ids: [],
|
|
13740
|
+
freshness: "fresh",
|
|
13741
|
+
status: metadata.status,
|
|
13742
|
+
confidence: 1,
|
|
13743
|
+
created_at: metadata.createdAt,
|
|
13744
|
+
updated_at: metadata.updatedAt,
|
|
13745
|
+
compiled_from: analyses.map((analysis) => analysis.sourceId),
|
|
13746
|
+
managed_by: metadata.managedBy,
|
|
13747
|
+
backlinks: [],
|
|
13748
|
+
schema_hash: schemaHash,
|
|
13749
|
+
source_hashes: {},
|
|
13750
|
+
source_semantic_hashes: {}
|
|
13751
|
+
}
|
|
13752
|
+
)
|
|
13753
|
+
}
|
|
13754
|
+
];
|
|
13755
|
+
const records = [];
|
|
13756
|
+
for (const dashboard of dashboards) {
|
|
13757
|
+
const absolutePath = path22.join(paths.wikiDir, dashboard.relativePath);
|
|
13758
|
+
const compiledFrom = dashboard.relativePath === "dashboards/recent-sources.md" ? recentSourcePages.flatMap((page) => page.sourceIds) : [];
|
|
13759
|
+
const content = await buildManagedContent(
|
|
13760
|
+
absolutePath,
|
|
13761
|
+
{
|
|
13762
|
+
managedBy: "system",
|
|
13763
|
+
compiledFrom
|
|
13764
|
+
},
|
|
13765
|
+
dashboard.content
|
|
13766
|
+
);
|
|
13767
|
+
records.push({
|
|
13768
|
+
page: emptyGraphPage({
|
|
13769
|
+
id: `dashboard:${dashboard.relativePath.replace(/\.md$/, "")}`,
|
|
13770
|
+
path: dashboard.relativePath,
|
|
13771
|
+
title: dashboard.title,
|
|
13772
|
+
kind: "index",
|
|
13773
|
+
sourceIds: compiledFrom,
|
|
13774
|
+
nodeIds: [],
|
|
13775
|
+
schemaHash,
|
|
13776
|
+
sourceHashes: {},
|
|
13777
|
+
confidence: 1
|
|
13778
|
+
}),
|
|
13779
|
+
content
|
|
13780
|
+
});
|
|
13781
|
+
}
|
|
13782
|
+
return records;
|
|
13783
|
+
}
|
|
12590
13784
|
function indexCompiledFrom(pages) {
|
|
12591
13785
|
return uniqueStrings3(pages.flatMap((page) => page.sourceIds));
|
|
12592
13786
|
}
|
|
@@ -13304,7 +14498,7 @@ async function writeApprovalManifest(paths, manifest) {
|
|
|
13304
14498
|
await fs18.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
|
|
13305
14499
|
`, "utf8");
|
|
13306
14500
|
}
|
|
13307
|
-
async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
|
|
14501
|
+
async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph, labelsByPath = /* @__PURE__ */ new Map()) {
|
|
13308
14502
|
const previousPagesById = new Map((previousGraph?.pages ?? []).map((page) => [page.id, page]));
|
|
13309
14503
|
const previousPagesByPath = new Map((previousGraph?.pages ?? []).map((page) => [page.path, page]));
|
|
13310
14504
|
const nextPagesByPath = new Map(graph.pages.map((page) => [page.path, page]));
|
|
@@ -13326,7 +14520,8 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
13326
14520
|
status: "pending",
|
|
13327
14521
|
sourceIds: nextPage.sourceIds,
|
|
13328
14522
|
nextPath: nextPage.path,
|
|
13329
|
-
previousPath: previousPage.path
|
|
14523
|
+
previousPath: previousPage.path,
|
|
14524
|
+
label: labelsByPath.get(nextPage.path) ?? labelsByPath.get(previousPage.path)
|
|
13330
14525
|
});
|
|
13331
14526
|
handledDeletedPaths.add(previousPage.path);
|
|
13332
14527
|
continue;
|
|
@@ -13339,7 +14534,8 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
13339
14534
|
status: "pending",
|
|
13340
14535
|
sourceIds: nextPage.sourceIds,
|
|
13341
14536
|
nextPath: nextPage.path,
|
|
13342
|
-
previousPath: previousPage?.path
|
|
14537
|
+
previousPath: previousPage?.path,
|
|
14538
|
+
label: labelsByPath.get(nextPage.path) ?? (previousPage?.path ? labelsByPath.get(previousPage.path) : void 0)
|
|
13343
14539
|
});
|
|
13344
14540
|
}
|
|
13345
14541
|
for (const deletedPath of deletedPaths.sort((left, right) => left.localeCompare(right))) {
|
|
@@ -13354,7 +14550,8 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
|
|
|
13354
14550
|
changeType: "delete",
|
|
13355
14551
|
status: "pending",
|
|
13356
14552
|
sourceIds: previousPage?.sourceIds ?? [],
|
|
13357
|
-
previousPath: deletedPath
|
|
14553
|
+
previousPath: deletedPath,
|
|
14554
|
+
label: labelsByPath.get(deletedPath)
|
|
13358
14555
|
});
|
|
13359
14556
|
}
|
|
13360
14557
|
return uniqueBy(entries, (entry) => `${entry.pageId}:${entry.changeType}:${entry.nextPath ?? ""}:${entry.previousPath ?? ""}`);
|
|
@@ -13374,6 +14571,8 @@ async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGr
|
|
|
13374
14571
|
await writeApprovalManifest(paths, {
|
|
13375
14572
|
approvalId,
|
|
13376
14573
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14574
|
+
bundleType: "compile",
|
|
14575
|
+
title: "Compile Approval",
|
|
13377
14576
|
entries: await buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph)
|
|
13378
14577
|
});
|
|
13379
14578
|
return { approvalId, approvalDir };
|
|
@@ -13612,8 +14811,19 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
13612
14811
|
input.previousState?.generatedAt,
|
|
13613
14812
|
contradictions
|
|
13614
14813
|
);
|
|
13615
|
-
|
|
13616
|
-
const
|
|
14814
|
+
const preliminaryPages = [...basePages, ...graphOrientation.records.map((record) => record.page)];
|
|
14815
|
+
const dashboardRecords = await buildDashboardRecords(
|
|
14816
|
+
paths,
|
|
14817
|
+
{
|
|
14818
|
+
...baseGraph,
|
|
14819
|
+
sources: input.manifests,
|
|
14820
|
+
pages: preliminaryPages
|
|
14821
|
+
},
|
|
14822
|
+
globalSchemaHash,
|
|
14823
|
+
graphOrientation.report
|
|
14824
|
+
);
|
|
14825
|
+
records.push(...graphOrientation.records, ...dashboardRecords);
|
|
14826
|
+
const allPages = uniqueBy([...preliminaryPages, ...dashboardRecords.map((record) => record.page)], (page) => page.id);
|
|
13617
14827
|
const graph = {
|
|
13618
14828
|
...baseGraph,
|
|
13619
14829
|
pages: allPages
|
|
@@ -13717,6 +14927,11 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
13717
14927
|
["concepts/index.md", "concepts", activeConceptPages],
|
|
13718
14928
|
["entities/index.md", "entities", activeEntityPages],
|
|
13719
14929
|
["outputs/index.md", "outputs", allPages.filter((page) => page.kind === "output")],
|
|
14930
|
+
[
|
|
14931
|
+
"dashboards/index.md",
|
|
14932
|
+
"dashboards",
|
|
14933
|
+
allPages.filter((page) => page.kind === "index" && page.path.startsWith("dashboards/") && page.path !== "dashboards/index.md")
|
|
14934
|
+
],
|
|
13720
14935
|
["candidates/index.md", "candidates", candidatePages],
|
|
13721
14936
|
["graph/index.md", "graph", allPages.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
|
|
13722
14937
|
]) {
|
|
@@ -13817,17 +15032,40 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
13817
15032
|
const compileState = await readJsonFile(paths.compileStatePath);
|
|
13818
15033
|
const globalSchemaHash = schemas.effective.global.hash;
|
|
13819
15034
|
const currentGraph = await readJsonFile(paths.graphPath);
|
|
13820
|
-
const
|
|
15035
|
+
const orientationPages = uniqueBy(
|
|
15036
|
+
pages.filter((page) => page.kind !== "graph_report" && page.kind !== "community_summary"),
|
|
15037
|
+
(page) => page.id
|
|
15038
|
+
);
|
|
15039
|
+
const basePages = uniqueBy(
|
|
15040
|
+
pages.filter(
|
|
15041
|
+
(page) => page.kind !== "graph_report" && page.kind !== "community_summary" && !(page.kind === "index" && page.path.startsWith("dashboards/"))
|
|
15042
|
+
),
|
|
15043
|
+
(page) => page.id
|
|
15044
|
+
);
|
|
13821
15045
|
const graphOrientation = currentGraph ? await buildGraphOrientationPages(
|
|
13822
15046
|
{
|
|
13823
15047
|
...currentGraph,
|
|
13824
|
-
pages:
|
|
15048
|
+
pages: orientationPages
|
|
13825
15049
|
},
|
|
13826
15050
|
paths,
|
|
13827
15051
|
globalSchemaHash,
|
|
13828
15052
|
compileState?.generatedAt
|
|
13829
15053
|
) : { records: [], report: null };
|
|
13830
|
-
const
|
|
15054
|
+
const dashboardRecords = currentGraph ? await buildDashboardRecords(
|
|
15055
|
+
paths,
|
|
15056
|
+
{
|
|
15057
|
+
...currentGraph,
|
|
15058
|
+
pages: [...basePages, ...graphOrientation.records.map((record) => record.page)]
|
|
15059
|
+
},
|
|
15060
|
+
globalSchemaHash,
|
|
15061
|
+
graphOrientation.report
|
|
15062
|
+
) : [];
|
|
15063
|
+
const pagesWithGraph = sortGraphPages(
|
|
15064
|
+
uniqueBy(
|
|
15065
|
+
[...basePages, ...graphOrientation.records.map((record) => record.page), ...dashboardRecords.map((record) => record.page)],
|
|
15066
|
+
(page) => page.id
|
|
15067
|
+
)
|
|
15068
|
+
);
|
|
13831
15069
|
if (currentGraph) {
|
|
13832
15070
|
await writeJsonFile(paths.graphPath, {
|
|
13833
15071
|
...currentGraph,
|
|
@@ -13855,6 +15093,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
13855
15093
|
ensureDir(path22.join(paths.wikiDir, "concepts")),
|
|
13856
15094
|
ensureDir(path22.join(paths.wikiDir, "entities")),
|
|
13857
15095
|
ensureDir(path22.join(paths.wikiDir, "outputs")),
|
|
15096
|
+
ensureDir(path22.join(paths.wikiDir, "dashboards")),
|
|
13858
15097
|
ensureDir(path22.join(paths.wikiDir, "graph")),
|
|
13859
15098
|
ensureDir(path22.join(paths.wikiDir, "graph", "communities")),
|
|
13860
15099
|
ensureDir(path22.join(paths.wikiDir, "projects")),
|
|
@@ -13917,6 +15156,11 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
13917
15156
|
["concepts/index.md", "concepts", pagesWithGraph.filter((page) => page.kind === "concept" && page.status !== "candidate")],
|
|
13918
15157
|
["entities/index.md", "entities", pagesWithGraph.filter((page) => page.kind === "entity" && page.status !== "candidate")],
|
|
13919
15158
|
["outputs/index.md", "outputs", pagesWithGraph.filter((page) => page.kind === "output")],
|
|
15159
|
+
[
|
|
15160
|
+
"dashboards/index.md",
|
|
15161
|
+
"dashboards",
|
|
15162
|
+
pagesWithGraph.filter((page) => page.kind === "index" && page.path.startsWith("dashboards/") && page.path !== "dashboards/index.md")
|
|
15163
|
+
],
|
|
13920
15164
|
["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
|
|
13921
15165
|
["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
|
|
13922
15166
|
]) {
|
|
@@ -13936,6 +15180,9 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
13936
15180
|
for (const record of graphOrientation.records) {
|
|
13937
15181
|
await writeFileIfChanged(path22.join(paths.wikiDir, record.page.path), record.content);
|
|
13938
15182
|
}
|
|
15183
|
+
for (const record of dashboardRecords) {
|
|
15184
|
+
await writeFileIfChanged(path22.join(paths.wikiDir, record.page.path), record.content);
|
|
15185
|
+
}
|
|
13939
15186
|
if (graphOrientation.report) {
|
|
13940
15187
|
await writeJsonFile(path22.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
|
|
13941
15188
|
}
|
|
@@ -13952,6 +15199,11 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
13952
15199
|
await Promise.all(
|
|
13953
15200
|
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs18.rm(path22.join(paths.wikiDir, relativePath), { force: true }))
|
|
13954
15201
|
);
|
|
15202
|
+
const existingDashboardPages = (await listFilesRecursive(path22.join(paths.wikiDir, "dashboards")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path22.relative(paths.wikiDir, absolutePath)));
|
|
15203
|
+
const allowedDashboardPages = /* @__PURE__ */ new Set(["dashboards/index.md", ...dashboardRecords.map((record) => record.page.path)]);
|
|
15204
|
+
await Promise.all(
|
|
15205
|
+
existingDashboardPages.filter((relativePath) => !allowedDashboardPages.has(relativePath)).map((relativePath) => fs18.rm(path22.join(paths.wikiDir, relativePath), { force: true }))
|
|
15206
|
+
);
|
|
13955
15207
|
await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
|
|
13956
15208
|
}
|
|
13957
15209
|
async function prepareOutputPageSave(rootDir, input) {
|
|
@@ -14036,7 +15288,7 @@ async function persistExploreHub(rootDir, input) {
|
|
|
14036
15288
|
}
|
|
14037
15289
|
return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
|
|
14038
15290
|
}
|
|
14039
|
-
async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
15291
|
+
async function stageOutputApprovalBundle(rootDir, stagedPages, options = {}) {
|
|
14040
15292
|
const { paths } = await loadVaultConfig(rootDir);
|
|
14041
15293
|
const previousGraph = await readJsonFile(paths.graphPath);
|
|
14042
15294
|
const changedFiles = stagedPages.flatMap((item) => [
|
|
@@ -14047,6 +15299,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
14047
15299
|
binary: typeof assetFile.content !== "string"
|
|
14048
15300
|
}))
|
|
14049
15301
|
]);
|
|
15302
|
+
const labelsByPath = new Map(stagedPages.filter((item) => item.label).map((item) => [item.page.path, item.label]));
|
|
14050
15303
|
const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
14051
15304
|
const approvalDir = path22.join(paths.approvalsDir, approvalId);
|
|
14052
15305
|
await ensureDir(approvalDir);
|
|
@@ -14077,16 +15330,22 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
14077
15330
|
await writeApprovalManifest(paths, {
|
|
14078
15331
|
approvalId,
|
|
14079
15332
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15333
|
+
bundleType: options.bundleType ?? "generated_output",
|
|
15334
|
+
title: options.title,
|
|
14080
15335
|
entries: await buildApprovalEntries(
|
|
14081
15336
|
paths,
|
|
14082
15337
|
stagedPages.map((item) => ({ relativePath: item.page.path, content: item.content })),
|
|
14083
15338
|
[],
|
|
14084
15339
|
previousGraph ?? null,
|
|
14085
|
-
graph
|
|
15340
|
+
graph,
|
|
15341
|
+
labelsByPath
|
|
14086
15342
|
)
|
|
14087
15343
|
});
|
|
14088
15344
|
return { approvalId, approvalDir };
|
|
14089
15345
|
}
|
|
15346
|
+
async function stageGeneratedOutputPages(rootDir, stagedPages, options = {}) {
|
|
15347
|
+
return await stageOutputApprovalBundle(rootDir, stagedPages, options);
|
|
15348
|
+
}
|
|
14090
15349
|
async function executeQuery(rootDir, question, format) {
|
|
14091
15350
|
const { paths } = await loadVaultConfig(rootDir);
|
|
14092
15351
|
const schemas = await loadVaultSchemas(rootDir);
|
|
@@ -14658,14 +15917,24 @@ async function ensureObsidianWorkspace(rootDir) {
|
|
|
14658
15917
|
]);
|
|
14659
15918
|
}
|
|
14660
15919
|
async function initVault(rootDir, options = {}) {
|
|
14661
|
-
const
|
|
15920
|
+
const profile = options.profile ?? "default";
|
|
15921
|
+
const { paths } = await initWorkspace(rootDir, { profile });
|
|
14662
15922
|
await installConfiguredAgents(rootDir);
|
|
14663
15923
|
const insightsIndexPath = path22.join(paths.wikiDir, "insights", "index.md");
|
|
14664
15924
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14665
15925
|
await writeFileIfChanged(
|
|
14666
15926
|
insightsIndexPath,
|
|
14667
15927
|
matter9.stringify(
|
|
14668
|
-
[
|
|
15928
|
+
(profile === "personal-research" ? [
|
|
15929
|
+
"# Insights",
|
|
15930
|
+
"",
|
|
15931
|
+
"Human-authored research notes live here.",
|
|
15932
|
+
"",
|
|
15933
|
+
"- Use this folder for thesis notes, reading reflections, synthesis drafts, and decisions you want to keep explicitly human-authored.",
|
|
15934
|
+
"- Guided ingest can propose updates elsewhere, but SwarmVault does not rewrite files inside `wiki/insights/` after initialization.",
|
|
15935
|
+
"- Treat these pages as the human judgment layer for your vault.",
|
|
15936
|
+
""
|
|
15937
|
+
] : [
|
|
14669
15938
|
"# Insights",
|
|
14670
15939
|
"",
|
|
14671
15940
|
"Human-authored notes live here.",
|
|
@@ -14673,7 +15942,7 @@ async function initVault(rootDir, options = {}) {
|
|
|
14673
15942
|
"- SwarmVault can read these pages during compile and query.",
|
|
14674
15943
|
"- SwarmVault does not rewrite files inside `wiki/insights/` after initialization.",
|
|
14675
15944
|
""
|
|
14676
|
-
].join("\n"),
|
|
15945
|
+
]).join("\n"),
|
|
14677
15946
|
{
|
|
14678
15947
|
page_id: "insights:index",
|
|
14679
15948
|
kind: "index",
|
|
@@ -14745,6 +16014,42 @@ async function initVault(rootDir, options = {}) {
|
|
|
14745
16014
|
if (options.obsidian) {
|
|
14746
16015
|
await ensureObsidianWorkspace(rootDir);
|
|
14747
16016
|
}
|
|
16017
|
+
if (profile === "personal-research") {
|
|
16018
|
+
await writeFileIfChanged(
|
|
16019
|
+
path22.join(paths.wikiDir, "insights", "research-playbook.md"),
|
|
16020
|
+
matter9.stringify(
|
|
16021
|
+
[
|
|
16022
|
+
"# Personal Research Playbook",
|
|
16023
|
+
"",
|
|
16024
|
+
"- Add one source at a time with `swarmvault ingest <input> --guide` or `swarmvault source add <input> --guide`.",
|
|
16025
|
+
"- Review `wiki/outputs/source-briefs/`, `wiki/outputs/source-reviews/`, and `wiki/outputs/source-guides/` before accepting staged updates.",
|
|
16026
|
+
"- Keep unresolved questions visible in `wiki/dashboards/open-questions.md`.",
|
|
16027
|
+
"- Use `swarmvault review list` and `swarmvault review show --diff` to decide what becomes canonical.",
|
|
16028
|
+
""
|
|
16029
|
+
].join("\n"),
|
|
16030
|
+
{
|
|
16031
|
+
page_id: "insights:research-playbook",
|
|
16032
|
+
kind: "insight",
|
|
16033
|
+
title: "Personal Research Playbook",
|
|
16034
|
+
tags: ["insight", "research", "playbook"],
|
|
16035
|
+
source_ids: [],
|
|
16036
|
+
project_ids: [],
|
|
16037
|
+
node_ids: [],
|
|
16038
|
+
freshness: "fresh",
|
|
16039
|
+
status: "active",
|
|
16040
|
+
confidence: 1,
|
|
16041
|
+
created_at: now,
|
|
16042
|
+
updated_at: now,
|
|
16043
|
+
compiled_from: [],
|
|
16044
|
+
managed_by: "human",
|
|
16045
|
+
backlinks: [],
|
|
16046
|
+
schema_hash: "",
|
|
16047
|
+
source_hashes: {},
|
|
16048
|
+
source_semantic_hashes: {}
|
|
16049
|
+
}
|
|
16050
|
+
)
|
|
16051
|
+
);
|
|
16052
|
+
}
|
|
14748
16053
|
}
|
|
14749
16054
|
async function runConfiguredBenchmark(rootDir, config) {
|
|
14750
16055
|
if (config.benchmark?.enabled === false) {
|
|
@@ -15428,7 +16733,17 @@ async function benchmarkVault(rootDir, options = {}) {
|
|
|
15428
16733
|
});
|
|
15429
16734
|
await writeJsonFile(paths.benchmarkPath, artifact);
|
|
15430
16735
|
await refreshIndexesAndSearch(rootDir, graph.pages);
|
|
15431
|
-
|
|
16736
|
+
const refreshedGraph = await readJsonFile(paths.graphPath) ?? graph;
|
|
16737
|
+
const refreshedHash = graphHash(refreshedGraph);
|
|
16738
|
+
if (artifact.graphHash === refreshedHash) {
|
|
16739
|
+
return artifact;
|
|
16740
|
+
}
|
|
16741
|
+
const refreshedArtifact = {
|
|
16742
|
+
...artifact,
|
|
16743
|
+
graphHash: refreshedHash
|
|
16744
|
+
};
|
|
16745
|
+
await writeJsonFile(paths.benchmarkPath, refreshedArtifact);
|
|
16746
|
+
return refreshedArtifact;
|
|
15432
16747
|
}
|
|
15433
16748
|
async function pathGraphVault(rootDir, from, to) {
|
|
15434
16749
|
const graph = await ensureCompiledGraph(rootDir);
|
|
@@ -15648,7 +16963,7 @@ async function bootstrapDemo(rootDir, input) {
|
|
|
15648
16963
|
}
|
|
15649
16964
|
|
|
15650
16965
|
// src/mcp.ts
|
|
15651
|
-
var SERVER_VERSION = "0.
|
|
16966
|
+
var SERVER_VERSION = "0.5.0";
|
|
15652
16967
|
async function createMcpServer(rootDir) {
|
|
15653
16968
|
const server = new McpServer({
|
|
15654
16969
|
name: "swarmvault",
|
|
@@ -16504,7 +17819,7 @@ function matchesManagedSourceSpec(existing, input) {
|
|
|
16504
17819
|
if (existing.kind !== input.kind) {
|
|
16505
17820
|
return false;
|
|
16506
17821
|
}
|
|
16507
|
-
if (input.kind === "directory") {
|
|
17822
|
+
if (input.kind === "directory" || input.kind === "file") {
|
|
16508
17823
|
return path25.resolve(existing.path ?? "") === path25.resolve(input.path);
|
|
16509
17824
|
}
|
|
16510
17825
|
return (existing.url ?? "") === input.url;
|
|
@@ -16516,10 +17831,15 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
16516
17831
|
if (!stat) {
|
|
16517
17832
|
throw new Error(`Source not found: ${input}`);
|
|
16518
17833
|
}
|
|
17834
|
+
if (stat.isFile()) {
|
|
17835
|
+
return {
|
|
17836
|
+
kind: "file",
|
|
17837
|
+
path: absoluteInput,
|
|
17838
|
+
title: path25.basename(absoluteInput, path25.extname(absoluteInput)) || absoluteInput
|
|
17839
|
+
};
|
|
17840
|
+
}
|
|
16519
17841
|
if (!stat.isDirectory()) {
|
|
16520
|
-
throw new Error(
|
|
16521
|
-
"`swarmvault source add` supports directories, public GitHub repo root URLs, and docs hubs. Use `swarmvault ingest` for single files."
|
|
16522
|
-
);
|
|
17842
|
+
throw new Error("`swarmvault source add` supports local files, directories, public GitHub repo root URLs, and docs hubs.");
|
|
16523
17843
|
}
|
|
16524
17844
|
const detectedRepoRoot = await findNearestGitRoot3(absoluteInput);
|
|
16525
17845
|
const repoRoot = detectedRepoRoot && !(withinRoot2(rootDir, absoluteInput) && !withinRoot2(rootDir, detectedRepoRoot)) ? detectedRepoRoot : absoluteInput;
|
|
@@ -16552,6 +17872,10 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
16552
17872
|
function directorySourceIdsFor(manifests, inputPath) {
|
|
16553
17873
|
return manifests.filter((manifest) => manifest.originalPath && withinRoot2(path25.resolve(inputPath), path25.resolve(manifest.originalPath))).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
|
|
16554
17874
|
}
|
|
17875
|
+
function fileSourceIdsFor(manifests, inputPath) {
|
|
17876
|
+
const absoluteInput = path25.resolve(inputPath);
|
|
17877
|
+
return manifests.filter((manifest) => manifest.originalPath && path25.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
|
|
17878
|
+
}
|
|
16555
17879
|
async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
16556
17880
|
const manifestsBefore = await listManifests(rootDir);
|
|
16557
17881
|
const previousInScope = manifestsBefore.filter(
|
|
@@ -16585,6 +17909,22 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
|
16585
17909
|
changed: result.imported.length + result.updated.length + removed.length > 0
|
|
16586
17910
|
};
|
|
16587
17911
|
}
|
|
17912
|
+
async function syncFileSource(rootDir, inputPath) {
|
|
17913
|
+
const result = await ingestInputDetailed(rootDir, inputPath);
|
|
17914
|
+
const manifestsAfter = await listManifests(rootDir);
|
|
17915
|
+
return {
|
|
17916
|
+
title: path25.basename(inputPath, path25.extname(inputPath)) || inputPath,
|
|
17917
|
+
sourceIds: fileSourceIdsFor(manifestsAfter, inputPath),
|
|
17918
|
+
counts: {
|
|
17919
|
+
scannedCount: result.scannedCount,
|
|
17920
|
+
importedCount: result.created.length,
|
|
17921
|
+
updatedCount: result.updated.length,
|
|
17922
|
+
removedCount: result.removed.length,
|
|
17923
|
+
skippedCount: result.skipped.length
|
|
17924
|
+
},
|
|
17925
|
+
changed: result.created.length + result.updated.length + result.removed.length > 0
|
|
17926
|
+
};
|
|
17927
|
+
}
|
|
16588
17928
|
async function runGitCommand(cwd, args) {
|
|
16589
17929
|
await new Promise((resolve, reject) => {
|
|
16590
17930
|
const child = spawn2("git", args, {
|
|
@@ -16679,6 +18019,22 @@ async function syncManagedSource(rootDir, entry, options) {
|
|
|
16679
18019
|
};
|
|
16680
18020
|
}
|
|
16681
18021
|
sync = await syncDirectorySource(rootDir, entry.path, entry.repoRoot);
|
|
18022
|
+
} else if (entry.kind === "file") {
|
|
18023
|
+
if (!entry.path) {
|
|
18024
|
+
throw new Error(`Managed source ${entry.id} is missing its file path.`);
|
|
18025
|
+
}
|
|
18026
|
+
if (!await fileExists(entry.path)) {
|
|
18027
|
+
return {
|
|
18028
|
+
...entry,
|
|
18029
|
+
status: "missing",
|
|
18030
|
+
updatedAt: now,
|
|
18031
|
+
lastSyncAt: now,
|
|
18032
|
+
lastSyncStatus: "error",
|
|
18033
|
+
lastError: `File not found: ${entry.path}`,
|
|
18034
|
+
changed: false
|
|
18035
|
+
};
|
|
18036
|
+
}
|
|
18037
|
+
sync = await syncFileSource(rootDir, entry.path);
|
|
16682
18038
|
} else if (entry.kind === "github_repo") {
|
|
16683
18039
|
sync = await syncGitHubRepoSource(rootDir, entry);
|
|
16684
18040
|
} else {
|
|
@@ -16786,7 +18142,7 @@ function renderDeterministicSourceBrief(input) {
|
|
|
16786
18142
|
""
|
|
16787
18143
|
].join("\n");
|
|
16788
18144
|
}
|
|
16789
|
-
async function
|
|
18145
|
+
async function generateSourceBriefMarkdownForScope(rootDir, source) {
|
|
16790
18146
|
const { paths } = await loadVaultConfig(rootDir);
|
|
16791
18147
|
const graph = await readJsonFile(paths.graphPath);
|
|
16792
18148
|
if (!graph) {
|
|
@@ -16823,7 +18179,7 @@ Entities: ${analysis.entities.map((entity) => entity.name).join(", ") || "none"}
|
|
|
16823
18179
|
),
|
|
16824
18180
|
prompt: [
|
|
16825
18181
|
`Source title: ${source.title}`,
|
|
16826
|
-
`Source kind: ${source.kind}`,
|
|
18182
|
+
`Source kind: ${source.kind ?? "source"}`,
|
|
16827
18183
|
`Tracked source ids: ${source.sourceIds.join(", ") || "none"}`,
|
|
16828
18184
|
"",
|
|
16829
18185
|
"Pages:",
|
|
@@ -16841,12 +18197,12 @@ Entities: ${analysis.entities.map((entity) => entity.name).join(", ") || "none"}
|
|
|
16841
18197
|
return fallback;
|
|
16842
18198
|
}
|
|
16843
18199
|
}
|
|
16844
|
-
async function
|
|
18200
|
+
async function writeSourceBriefForScope(rootDir, source) {
|
|
16845
18201
|
if (!source.sourceIds.length) {
|
|
16846
18202
|
return null;
|
|
16847
18203
|
}
|
|
16848
18204
|
const { paths } = await loadVaultConfig(rootDir);
|
|
16849
|
-
const markdown = await
|
|
18205
|
+
const markdown = await generateSourceBriefMarkdownForScope(rootDir, source);
|
|
16850
18206
|
if (!markdown) {
|
|
16851
18207
|
return null;
|
|
16852
18208
|
}
|
|
@@ -16884,6 +18240,9 @@ async function writeSourceBrief(rootDir, source) {
|
|
|
16884
18240
|
await fs21.writeFile(absolutePath, output.content, "utf8");
|
|
16885
18241
|
return absolutePath;
|
|
16886
18242
|
}
|
|
18243
|
+
async function writeSourceBrief(rootDir, source) {
|
|
18244
|
+
return await writeSourceBriefForScope(rootDir, scopeFromManagedSource(source));
|
|
18245
|
+
}
|
|
16887
18246
|
async function generateBriefsForSources(rootDir, sources) {
|
|
16888
18247
|
const briefPaths = /* @__PURE__ */ new Map();
|
|
16889
18248
|
for (const source of sources) {
|
|
@@ -16897,6 +18256,410 @@ async function generateBriefsForSources(rootDir, sources) {
|
|
|
16897
18256
|
}
|
|
16898
18257
|
return briefPaths;
|
|
16899
18258
|
}
|
|
18259
|
+
function renderDeterministicSourceReview(input) {
|
|
18260
|
+
const canonicalPages = input.sourcePages.filter((page) => page.kind === "source" || page.kind === "concept" || page.kind === "entity").slice(0, 10);
|
|
18261
|
+
const modulePages = input.sourcePages.filter((page) => page.kind === "module").slice(0, 8);
|
|
18262
|
+
const questions = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.questions)).slice(0, 8);
|
|
18263
|
+
const concepts = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.concepts.map((concept) => concept.name))).slice(0, 8);
|
|
18264
|
+
const entities = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.entities.map((entity) => entity.name))).slice(0, 8);
|
|
18265
|
+
const contradictions = input.report?.contradictions.filter(
|
|
18266
|
+
(contradiction) => input.scope.sourceIds.includes(contradiction.sourceIdA) || input.scope.sourceIds.includes(contradiction.sourceIdB)
|
|
18267
|
+
) ?? [];
|
|
18268
|
+
return [
|
|
18269
|
+
`# Source Review: ${input.scope.title}`,
|
|
18270
|
+
"",
|
|
18271
|
+
"## What This Source Contains",
|
|
18272
|
+
"",
|
|
18273
|
+
...input.analyses.length ? input.analyses.map((analysis) => `- ${analysis.title}: ${analysis.summary}`) : ["- This source has not been analyzed yet. Compile the vault before trusting downstream pages."],
|
|
18274
|
+
"",
|
|
18275
|
+
"## Likely Canonical Pages To Update",
|
|
18276
|
+
"",
|
|
18277
|
+
...canonicalPages.length ? canonicalPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No canonical source, concept, or entity pages are linked to this source yet."],
|
|
18278
|
+
"",
|
|
18279
|
+
"## Important Topics And Entities",
|
|
18280
|
+
"",
|
|
18281
|
+
...concepts.length ? [`Concepts: ${concepts.join(", ")}`] : ["Concepts: none detected."],
|
|
18282
|
+
...entities.length ? [`Entities: ${entities.join(", ")}`] : ["Entities: none detected."],
|
|
18283
|
+
...modulePages.length ? ["", ...modulePages.map((page) => `- Module: [[${page.path.replace(/\.md$/, "")}|${page.title}]]`)] : [],
|
|
18284
|
+
"",
|
|
18285
|
+
"## Contradictions To Inspect",
|
|
18286
|
+
"",
|
|
18287
|
+
...contradictions.length ? contradictions.map((contradiction) => `- ${contradiction.claimA} / ${contradiction.claimB}`) : ["- No contradictions are currently flagged for this source scope."],
|
|
18288
|
+
"",
|
|
18289
|
+
"## Open Questions",
|
|
18290
|
+
"",
|
|
18291
|
+
...questions.length ? questions.map((question) => `- ${question}`) : ["- No extracted open questions yet."],
|
|
18292
|
+
"",
|
|
18293
|
+
"## Suggested Next Steps",
|
|
18294
|
+
"",
|
|
18295
|
+
...canonicalPages.length ? canonicalPages.slice(0, 5).map((page) => `- Review [[${page.path.replace(/\.md$/, "")}|${page.title}]] for canonical updates.`) : ["- Review the source page and decide which canonical pages should exist."],
|
|
18296
|
+
""
|
|
18297
|
+
].join("\n");
|
|
18298
|
+
}
|
|
18299
|
+
async function generateSourceReviewMarkdown(rootDir, scope) {
|
|
18300
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
18301
|
+
let graph = await readJsonFile(paths.graphPath);
|
|
18302
|
+
if (!graph) {
|
|
18303
|
+
await compileVault(rootDir, {});
|
|
18304
|
+
graph = await readJsonFile(paths.graphPath);
|
|
18305
|
+
}
|
|
18306
|
+
if (!graph) {
|
|
18307
|
+
return null;
|
|
18308
|
+
}
|
|
18309
|
+
const sourcePages = scopedSourcePages(graph, scope.sourceIds);
|
|
18310
|
+
const analyses = await loadSourceAnalyses(rootDir, scope.sourceIds);
|
|
18311
|
+
const report = await readGraphReport(rootDir);
|
|
18312
|
+
const fallback = renderDeterministicSourceReview({
|
|
18313
|
+
scope,
|
|
18314
|
+
sourcePages,
|
|
18315
|
+
graph,
|
|
18316
|
+
analyses,
|
|
18317
|
+
report
|
|
18318
|
+
});
|
|
18319
|
+
const provider = await getProviderForTask(rootDir, "queryProvider");
|
|
18320
|
+
if (provider.type === "heuristic") {
|
|
18321
|
+
return fallback;
|
|
18322
|
+
}
|
|
18323
|
+
try {
|
|
18324
|
+
const schemas = await loadVaultSchemas(rootDir);
|
|
18325
|
+
const pageContext = sourcePages.slice(0, 12).map((page) => `- ${page.title} (${page.kind}) -> ${page.path}`).join("\n");
|
|
18326
|
+
const analysisContext = analyses.slice(0, 8).map(
|
|
18327
|
+
(analysis) => `# ${analysis.title}
|
|
18328
|
+
Summary: ${analysis.summary}
|
|
18329
|
+
Questions: ${analysis.questions.join(" | ") || "none"}
|
|
18330
|
+
Concepts: ${analysis.concepts.map((concept) => concept.name).join(", ") || "none"}
|
|
18331
|
+
Entities: ${analysis.entities.map((entity) => entity.name).join(", ") || "none"}`
|
|
18332
|
+
).join("\n\n---\n\n");
|
|
18333
|
+
const response = await provider.generateText({
|
|
18334
|
+
system: buildSchemaPrompt(
|
|
18335
|
+
schemas.effective.global,
|
|
18336
|
+
"Write a concise markdown source review with sections: What This Source Contains, Likely Canonical Pages To Update, Important Topics And Entities, Contradictions To Inspect, Open Questions, Suggested Next Steps. Focus on helping a human decide what to keep, update, or question in the wiki."
|
|
18337
|
+
),
|
|
18338
|
+
prompt: [
|
|
18339
|
+
`Source scope: ${scope.title}`,
|
|
18340
|
+
`Scope id: ${scope.id}`,
|
|
18341
|
+
`Tracked source ids: ${scope.sourceIds.join(", ") || "none"}`,
|
|
18342
|
+
"",
|
|
18343
|
+
"Pages:",
|
|
18344
|
+
pageContext || "- none",
|
|
18345
|
+
"",
|
|
18346
|
+
"Analyses:",
|
|
18347
|
+
analysisContext || "No analysis context available.",
|
|
18348
|
+
"",
|
|
18349
|
+
"Deterministic fallback draft:",
|
|
18350
|
+
fallback
|
|
18351
|
+
].join("\n")
|
|
18352
|
+
});
|
|
18353
|
+
return response.text?.trim() ? response.text.trim() : fallback;
|
|
18354
|
+
} catch {
|
|
18355
|
+
return fallback;
|
|
18356
|
+
}
|
|
18357
|
+
}
|
|
18358
|
+
async function buildSourceReviewStagedPage(rootDir, scope) {
|
|
18359
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
18360
|
+
const markdown = await generateSourceReviewMarkdown(rootDir, scope);
|
|
18361
|
+
if (!markdown) {
|
|
18362
|
+
throw new Error(`Could not generate a source review for ${scope.id}.`);
|
|
18363
|
+
}
|
|
18364
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
18365
|
+
const relatedPages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
|
|
18366
|
+
const relatedPageIds = relatedPages.slice(0, 16).map((page) => page.id);
|
|
18367
|
+
const relatedNodeIds = graph ? scopedNodeIds(graph, scope.sourceIds).slice(0, 24) : [];
|
|
18368
|
+
const projectIds = uniqueStrings4(relatedPages.flatMap((page) => page.projectIds));
|
|
18369
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18370
|
+
const output = buildOutputPage({
|
|
18371
|
+
title: `Source Review: ${scope.title}`,
|
|
18372
|
+
question: `Review ${scope.title}`,
|
|
18373
|
+
answer: markdown,
|
|
18374
|
+
citations: scope.sourceIds,
|
|
18375
|
+
schemaHash: graph?.generatedAt ?? "",
|
|
18376
|
+
outputFormat: "report",
|
|
18377
|
+
relatedPageIds,
|
|
18378
|
+
relatedNodeIds,
|
|
18379
|
+
relatedSourceIds: scope.sourceIds,
|
|
18380
|
+
projectIds,
|
|
18381
|
+
extraTags: ["source-review"],
|
|
18382
|
+
origin: "query",
|
|
18383
|
+
slug: `source-reviews/${scope.id}`,
|
|
18384
|
+
metadata: {
|
|
18385
|
+
status: "draft",
|
|
18386
|
+
createdAt: now,
|
|
18387
|
+
updatedAt: now,
|
|
18388
|
+
compiledFrom: scope.sourceIds,
|
|
18389
|
+
managedBy: "system",
|
|
18390
|
+
confidence: 0.79
|
|
18391
|
+
}
|
|
18392
|
+
});
|
|
18393
|
+
return { page: output.page, content: output.content };
|
|
18394
|
+
}
|
|
18395
|
+
function classifySourceGuidePageBuckets(sourcePages, scopeSourceIds) {
|
|
18396
|
+
const scopeSet = new Set(scopeSourceIds);
|
|
18397
|
+
const canonicalPages = sourcePages.filter((page) => page.kind === "source" || page.kind === "concept" || page.kind === "entity").slice(0, 12);
|
|
18398
|
+
const newPages = canonicalPages.filter((page) => page.sourceIds.every((sourceId) => scopeSet.has(sourceId))).slice(0, 6);
|
|
18399
|
+
const reinforcingPages = canonicalPages.filter((page) => page.sourceIds.some((sourceId) => !scopeSet.has(sourceId))).slice(0, 6);
|
|
18400
|
+
return { canonicalPages, newPages, reinforcingPages };
|
|
18401
|
+
}
|
|
18402
|
+
function renderDeterministicSourceGuide(input) {
|
|
18403
|
+
const { canonicalPages, newPages, reinforcingPages } = classifySourceGuidePageBuckets(input.sourcePages, input.scope.sourceIds);
|
|
18404
|
+
const modulePages = input.sourcePages.filter((page) => page.kind === "module").slice(0, 6);
|
|
18405
|
+
const takeaways = uniqueStrings4(
|
|
18406
|
+
input.analyses.flatMap((analysis) => [
|
|
18407
|
+
analysis.summary,
|
|
18408
|
+
...analysis.concepts.map((concept) => concept.description),
|
|
18409
|
+
...analysis.entities.map((entity) => entity.description)
|
|
18410
|
+
]).filter(Boolean).map((value) => normalizeWhitespace(value))
|
|
18411
|
+
).slice(0, 7).map((value) => truncate(value, 180));
|
|
18412
|
+
const questions = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.questions)).slice(0, 6);
|
|
18413
|
+
const contradictions = input.report?.contradictions.filter(
|
|
18414
|
+
(contradiction) => input.scope.sourceIds.includes(contradiction.sourceIdA) || input.scope.sourceIds.includes(contradiction.sourceIdB)
|
|
18415
|
+
) ?? [];
|
|
18416
|
+
return [
|
|
18417
|
+
`# Source Guide: ${input.scope.title}`,
|
|
18418
|
+
"",
|
|
18419
|
+
"## What This Source Is",
|
|
18420
|
+
"",
|
|
18421
|
+
takeaways.length ? takeaways[0] : `${input.scope.title} has been compiled into the vault and is ready for guided review.`,
|
|
18422
|
+
"",
|
|
18423
|
+
"## Key Takeaways",
|
|
18424
|
+
"",
|
|
18425
|
+
...takeaways.length ? takeaways.map((takeaway) => `- ${takeaway}`) : ["- No takeaways are available until the source is compiled."],
|
|
18426
|
+
"",
|
|
18427
|
+
"## Proposed Canonical Pages To Update",
|
|
18428
|
+
"",
|
|
18429
|
+
...canonicalPages.length ? canonicalPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No likely canonical pages were identified yet."],
|
|
18430
|
+
"",
|
|
18431
|
+
"## New, Reinforcing, And Conflicting Claims",
|
|
18432
|
+
"",
|
|
18433
|
+
...newPages.length ? ["New or source-local pages:", ...newPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`), ""] : [],
|
|
18434
|
+
...reinforcingPages.length ? ["Reinforcing existing pages:", ...reinforcingPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`), ""] : [],
|
|
18435
|
+
...contradictions.length ? ["Conflicts to judge:", ...contradictions.map((contradiction) => `- ${contradiction.claimA} / ${contradiction.claimB}`), ""] : ["Conflicts to judge:", "- No contradictions are currently flagged for this source scope.", ""],
|
|
18436
|
+
"## What Should Probably Stay Out For Now",
|
|
18437
|
+
"",
|
|
18438
|
+
...modulePages.length ? ["- Avoid promoting narrow implementation details unless they matter to your thesis or recurring questions."] : ["- Avoid promoting incidental details that are not yet supported by multiple sources or clear research goals."],
|
|
18439
|
+
...contradictions.length ? ["- Keep contested claims provisional until you review the conflicting evidence side by side."] : [],
|
|
18440
|
+
"",
|
|
18441
|
+
"## Needs Human Judgment",
|
|
18442
|
+
"",
|
|
18443
|
+
...questions.length ? questions.map((question) => `- ${question}`) : ["- Decide which proposed canonical pages deserve durable summary updates."],
|
|
18444
|
+
"",
|
|
18445
|
+
"## Suggested Follow-up Questions",
|
|
18446
|
+
"",
|
|
18447
|
+
...questions.length ? questions.map((question) => `- ${question}`) : ["- What changed in your understanding after reading this source?"],
|
|
18448
|
+
""
|
|
18449
|
+
].join("\n");
|
|
18450
|
+
}
|
|
18451
|
+
async function generateSourceGuideMarkdown(rootDir, scope) {
|
|
18452
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
18453
|
+
let graph = await readJsonFile(paths.graphPath);
|
|
18454
|
+
if (!graph) {
|
|
18455
|
+
await compileVault(rootDir, {});
|
|
18456
|
+
graph = await readJsonFile(paths.graphPath);
|
|
18457
|
+
}
|
|
18458
|
+
if (!graph) {
|
|
18459
|
+
return null;
|
|
18460
|
+
}
|
|
18461
|
+
const sourcePages = scopedSourcePages(graph, scope.sourceIds);
|
|
18462
|
+
const analyses = await loadSourceAnalyses(rootDir, scope.sourceIds);
|
|
18463
|
+
const report = await readGraphReport(rootDir);
|
|
18464
|
+
const fallback = renderDeterministicSourceGuide({
|
|
18465
|
+
scope,
|
|
18466
|
+
sourcePages,
|
|
18467
|
+
analyses,
|
|
18468
|
+
report
|
|
18469
|
+
});
|
|
18470
|
+
const provider = await getProviderForTask(rootDir, "queryProvider");
|
|
18471
|
+
if (provider.type === "heuristic") {
|
|
18472
|
+
return fallback;
|
|
18473
|
+
}
|
|
18474
|
+
try {
|
|
18475
|
+
const schemas = await loadVaultSchemas(rootDir);
|
|
18476
|
+
const { canonicalPages, newPages, reinforcingPages } = classifySourceGuidePageBuckets(sourcePages, scope.sourceIds);
|
|
18477
|
+
const pageContext = sourcePages.slice(0, 12).map((page) => `- ${page.title} (${page.kind}) -> ${page.path}`).join("\n");
|
|
18478
|
+
const analysisContext = analyses.slice(0, 8).map(
|
|
18479
|
+
(analysis) => `# ${analysis.title}
|
|
18480
|
+
Summary: ${analysis.summary}
|
|
18481
|
+
Questions: ${analysis.questions.join(" | ") || "none"}
|
|
18482
|
+
Concepts: ${analysis.concepts.map((concept) => concept.name).join(", ") || "none"}
|
|
18483
|
+
Entities: ${analysis.entities.map((entity) => entity.name).join(", ") || "none"}`
|
|
18484
|
+
).join("\n\n---\n\n");
|
|
18485
|
+
const response = await provider.generateText({
|
|
18486
|
+
system: buildSchemaPrompt(
|
|
18487
|
+
schemas.effective.global,
|
|
18488
|
+
"Write a concise markdown source guide with sections: What This Source Is, Key Takeaways, Proposed Canonical Pages To Update, New Reinforcing And Conflicting Claims, What Should Probably Stay Out For Now, Needs Human Judgment, Suggested Follow-up Questions. Focus on helping a human integrate one source into an evolving research wiki."
|
|
18489
|
+
),
|
|
18490
|
+
prompt: [
|
|
18491
|
+
`Source scope: ${scope.title}`,
|
|
18492
|
+
`Scope id: ${scope.id}`,
|
|
18493
|
+
`Tracked source ids: ${scope.sourceIds.join(", ") || "none"}`,
|
|
18494
|
+
`Current brief path: ${scope.briefPath ?? "none"}`,
|
|
18495
|
+
"",
|
|
18496
|
+
"Likely canonical pages:",
|
|
18497
|
+
canonicalPages.length ? canonicalPages.map((page) => `- ${page.title} -> ${page.path}`).join("\n") : "- none",
|
|
18498
|
+
"",
|
|
18499
|
+
"Likely source-local pages:",
|
|
18500
|
+
newPages.length ? newPages.map((page) => `- ${page.title} -> ${page.path}`).join("\n") : "- none",
|
|
18501
|
+
"",
|
|
18502
|
+
"Likely reinforcing pages:",
|
|
18503
|
+
reinforcingPages.length ? reinforcingPages.map((page) => `- ${page.title} -> ${page.path}`).join("\n") : "- none",
|
|
18504
|
+
"",
|
|
18505
|
+
"Pages:",
|
|
18506
|
+
pageContext || "- none",
|
|
18507
|
+
"",
|
|
18508
|
+
"Analyses:",
|
|
18509
|
+
analysisContext || "No analysis context available.",
|
|
18510
|
+
"",
|
|
18511
|
+
"Deterministic fallback draft:",
|
|
18512
|
+
fallback
|
|
18513
|
+
].join("\n")
|
|
18514
|
+
});
|
|
18515
|
+
return response.text?.trim() ? response.text.trim() : fallback;
|
|
18516
|
+
} catch {
|
|
18517
|
+
return fallback;
|
|
18518
|
+
}
|
|
18519
|
+
}
|
|
18520
|
+
async function buildSourceGuideStagedPage(rootDir, scope) {
|
|
18521
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
18522
|
+
const markdown = await generateSourceGuideMarkdown(rootDir, scope);
|
|
18523
|
+
if (!markdown) {
|
|
18524
|
+
throw new Error(`Could not generate a source guide for ${scope.id}.`);
|
|
18525
|
+
}
|
|
18526
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
18527
|
+
const relatedPages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
|
|
18528
|
+
const relatedPageIds = relatedPages.slice(0, 18).map((page) => page.id);
|
|
18529
|
+
const relatedNodeIds = graph ? scopedNodeIds(graph, scope.sourceIds).slice(0, 28) : [];
|
|
18530
|
+
const projectIds = uniqueStrings4(relatedPages.flatMap((page) => page.projectIds));
|
|
18531
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18532
|
+
const output = buildOutputPage({
|
|
18533
|
+
title: `Source Guide: ${scope.title}`,
|
|
18534
|
+
question: `Guide ${scope.title}`,
|
|
18535
|
+
answer: markdown,
|
|
18536
|
+
citations: scope.sourceIds,
|
|
18537
|
+
schemaHash: graph?.generatedAt ?? "",
|
|
18538
|
+
outputFormat: "report",
|
|
18539
|
+
relatedPageIds,
|
|
18540
|
+
relatedNodeIds,
|
|
18541
|
+
relatedSourceIds: scope.sourceIds,
|
|
18542
|
+
projectIds,
|
|
18543
|
+
extraTags: ["source-guide", "guided-ingest"],
|
|
18544
|
+
origin: "query",
|
|
18545
|
+
slug: `source-guides/${scope.id}`,
|
|
18546
|
+
metadata: {
|
|
18547
|
+
status: "draft",
|
|
18548
|
+
createdAt: now,
|
|
18549
|
+
updatedAt: now,
|
|
18550
|
+
compiledFrom: scope.sourceIds,
|
|
18551
|
+
managedBy: "system",
|
|
18552
|
+
confidence: 0.8
|
|
18553
|
+
}
|
|
18554
|
+
});
|
|
18555
|
+
return { page: output.page, content: output.content };
|
|
18556
|
+
}
|
|
18557
|
+
async function stageSourceReviewForScope(rootDir, scope) {
|
|
18558
|
+
const output = await buildSourceReviewStagedPage(rootDir, scope);
|
|
18559
|
+
const approval = await stageGeneratedOutputPages(rootDir, [{ page: output.page, content: output.content, label: "source-review" }], {
|
|
18560
|
+
bundleType: "source_review",
|
|
18561
|
+
title: `Source Review: ${scope.title}`
|
|
18562
|
+
});
|
|
18563
|
+
return {
|
|
18564
|
+
sourceId: scope.id,
|
|
18565
|
+
pageId: output.page.id,
|
|
18566
|
+
reviewPath: path25.join(approval.approvalDir, "wiki", output.page.path),
|
|
18567
|
+
staged: true,
|
|
18568
|
+
approvalId: approval.approvalId,
|
|
18569
|
+
approvalDir: approval.approvalDir
|
|
18570
|
+
};
|
|
18571
|
+
}
|
|
18572
|
+
async function stageSourceGuideForScope(rootDir, scope) {
|
|
18573
|
+
const briefPath = scope.briefPath ?? await writeSourceBriefForScope(rootDir, scope) ?? void 0;
|
|
18574
|
+
if (briefPath) {
|
|
18575
|
+
await refreshVaultAfterOutputSave(rootDir);
|
|
18576
|
+
}
|
|
18577
|
+
const reviewOutput = await buildSourceReviewStagedPage(rootDir, scope);
|
|
18578
|
+
const guideOutput = await buildSourceGuideStagedPage(rootDir, {
|
|
18579
|
+
...scope,
|
|
18580
|
+
briefPath
|
|
18581
|
+
});
|
|
18582
|
+
const approval = await stageGeneratedOutputPages(
|
|
18583
|
+
rootDir,
|
|
18584
|
+
[
|
|
18585
|
+
{ page: reviewOutput.page, content: reviewOutput.content, label: "source-review" },
|
|
18586
|
+
{ page: guideOutput.page, content: guideOutput.content, label: "source-guide" }
|
|
18587
|
+
],
|
|
18588
|
+
{
|
|
18589
|
+
bundleType: "guided_source",
|
|
18590
|
+
title: `Guided Source: ${scope.title}`
|
|
18591
|
+
}
|
|
18592
|
+
);
|
|
18593
|
+
await refreshVaultAfterOutputSave(rootDir);
|
|
18594
|
+
return {
|
|
18595
|
+
sourceId: scope.id,
|
|
18596
|
+
pageId: guideOutput.page.id,
|
|
18597
|
+
guidePath: path25.join(approval.approvalDir, "wiki", guideOutput.page.path),
|
|
18598
|
+
reviewPageId: reviewOutput.page.id,
|
|
18599
|
+
reviewPath: path25.join(approval.approvalDir, "wiki", reviewOutput.page.path),
|
|
18600
|
+
briefPath,
|
|
18601
|
+
staged: true,
|
|
18602
|
+
approvalId: approval.approvalId,
|
|
18603
|
+
approvalDir: approval.approvalDir
|
|
18604
|
+
};
|
|
18605
|
+
}
|
|
18606
|
+
function scopeFromManagedSource(source) {
|
|
18607
|
+
return {
|
|
18608
|
+
id: source.id,
|
|
18609
|
+
title: source.title,
|
|
18610
|
+
sourceIds: source.sourceIds,
|
|
18611
|
+
kind: source.kind,
|
|
18612
|
+
briefPath: source.briefPath
|
|
18613
|
+
};
|
|
18614
|
+
}
|
|
18615
|
+
async function reviewSourceScope(rootDir, scope) {
|
|
18616
|
+
return await stageSourceReviewForScope(rootDir, scope);
|
|
18617
|
+
}
|
|
18618
|
+
async function guideSourceScope(rootDir, scope) {
|
|
18619
|
+
return await stageSourceGuideForScope(rootDir, scope);
|
|
18620
|
+
}
|
|
18621
|
+
async function reviewManagedSource(rootDir, id) {
|
|
18622
|
+
const managedSources = await loadManagedSources(rootDir);
|
|
18623
|
+
const managedSource = managedSources.find((source) => source.id === id);
|
|
18624
|
+
if (managedSource) {
|
|
18625
|
+
if (!await loadVaultConfig(rootDir).then(({ paths }) => fileExists(paths.graphPath))) {
|
|
18626
|
+
await compileVault(rootDir, {});
|
|
18627
|
+
}
|
|
18628
|
+
return await stageSourceReviewForScope(rootDir, scopeFromManagedSource(managedSource));
|
|
18629
|
+
}
|
|
18630
|
+
const manifests = await listManifests(rootDir);
|
|
18631
|
+
const manifest = manifests.find((candidate) => candidate.sourceId === id);
|
|
18632
|
+
if (!manifest) {
|
|
18633
|
+
throw new Error(`Managed source or source id not found: ${id}`);
|
|
18634
|
+
}
|
|
18635
|
+
return await stageSourceReviewForScope(rootDir, {
|
|
18636
|
+
id: manifest.sourceGroupId ?? manifest.sourceId,
|
|
18637
|
+
title: manifest.sourceGroupTitle ?? manifest.title,
|
|
18638
|
+
sourceIds: manifest.sourceGroupId ? manifests.filter((candidate) => candidate.sourceGroupId === manifest.sourceGroupId).map((candidate) => candidate.sourceId) : [manifest.sourceId],
|
|
18639
|
+
kind: manifest.sourceKind
|
|
18640
|
+
});
|
|
18641
|
+
}
|
|
18642
|
+
async function guideManagedSource(rootDir, id) {
|
|
18643
|
+
const managedSources = await loadManagedSources(rootDir);
|
|
18644
|
+
const managedSource = managedSources.find((source) => source.id === id);
|
|
18645
|
+
if (managedSource) {
|
|
18646
|
+
if (!await loadVaultConfig(rootDir).then(({ paths }) => fileExists(paths.graphPath))) {
|
|
18647
|
+
await compileVault(rootDir, {});
|
|
18648
|
+
}
|
|
18649
|
+
return await stageSourceGuideForScope(rootDir, scopeFromManagedSource(managedSource));
|
|
18650
|
+
}
|
|
18651
|
+
const manifests = await listManifests(rootDir);
|
|
18652
|
+
const manifest = manifests.find((candidate) => candidate.sourceId === id);
|
|
18653
|
+
if (!manifest) {
|
|
18654
|
+
throw new Error(`Managed source or source id not found: ${id}`);
|
|
18655
|
+
}
|
|
18656
|
+
return await stageSourceGuideForScope(rootDir, {
|
|
18657
|
+
id: manifest.sourceGroupId ?? manifest.sourceId,
|
|
18658
|
+
title: manifest.sourceGroupTitle ?? manifest.title,
|
|
18659
|
+
sourceIds: manifest.sourceGroupId ? manifests.filter((candidate) => candidate.sourceGroupId === manifest.sourceGroupId).map((candidate) => candidate.sourceId) : [manifest.sourceId],
|
|
18660
|
+
kind: manifest.sourceKind
|
|
18661
|
+
});
|
|
18662
|
+
}
|
|
16900
18663
|
function shouldCompile(changedSources, graphExists, compileRequested) {
|
|
16901
18664
|
return compileRequested && (!graphExists || changedSources.length > 0);
|
|
16902
18665
|
}
|
|
@@ -16906,18 +18669,20 @@ async function listManagedSourceRecords(rootDir) {
|
|
|
16906
18669
|
}
|
|
16907
18670
|
async function addManagedSource(rootDir, input, options = {}) {
|
|
16908
18671
|
const compileRequested = options.compile ?? true;
|
|
16909
|
-
const
|
|
18672
|
+
const guideRequested = options.guide ?? false;
|
|
18673
|
+
const briefRequested = guideRequested ? true : options.brief ?? true;
|
|
18674
|
+
const reviewRequested = guideRequested ? false : options.review ?? false;
|
|
16910
18675
|
const sources = await loadManagedSources(rootDir);
|
|
16911
18676
|
const resolved = await resolveManagedSourceInput(rootDir, input);
|
|
16912
18677
|
const existing = sources.find((candidate) => matchesManagedSourceSpec(candidate, resolved));
|
|
16913
18678
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16914
18679
|
const source = existing ?? {
|
|
16915
|
-
id: resolved.kind === "directory" ? stableManagedSourceId(
|
|
18680
|
+
id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path25.resolve(resolved.path), resolved.title) : stableManagedSourceId(resolved.kind, resolved.url, resolved.title),
|
|
16916
18681
|
kind: resolved.kind,
|
|
16917
18682
|
title: resolved.title,
|
|
16918
|
-
path: resolved.kind === "directory" ? resolved.path : void 0,
|
|
18683
|
+
path: resolved.kind === "directory" || resolved.kind === "file" ? resolved.path : void 0,
|
|
16919
18684
|
repoRoot: resolved.kind === "directory" ? resolved.repoRoot : void 0,
|
|
16920
|
-
url: resolved.kind === "directory" ? void 0 : resolved.url,
|
|
18685
|
+
url: resolved.kind === "directory" || resolved.kind === "file" ? void 0 : resolved.url,
|
|
16921
18686
|
createdAt: now,
|
|
16922
18687
|
updatedAt: now,
|
|
16923
18688
|
status: "ready",
|
|
@@ -16946,15 +18711,24 @@ async function addManagedSource(rootDir, input, options = {}) {
|
|
|
16946
18711
|
};
|
|
16947
18712
|
const nextSources = existing ? sources.map((candidate) => candidate.id === nextSource.id ? nextSource : candidate) : [...sources, nextSource];
|
|
16948
18713
|
await saveManagedSources(rootDir, nextSources);
|
|
18714
|
+
const review = reviewRequested && nextSource.status === "ready" ? await stageSourceReviewForScope(rootDir, scopeFromManagedSource(nextSource)) : void 0;
|
|
18715
|
+
const guide = guideRequested && nextSource.status === "ready" ? await stageSourceGuideForScope(rootDir, {
|
|
18716
|
+
...scopeFromManagedSource(nextSource),
|
|
18717
|
+
briefPath: nextSource.briefPath
|
|
18718
|
+
}) : void 0;
|
|
16949
18719
|
return {
|
|
16950
18720
|
source: nextSource,
|
|
16951
18721
|
compile,
|
|
16952
|
-
briefGenerated
|
|
18722
|
+
briefGenerated,
|
|
18723
|
+
review,
|
|
18724
|
+
guide
|
|
16953
18725
|
};
|
|
16954
18726
|
}
|
|
16955
18727
|
async function reloadManagedSources(rootDir, options = {}) {
|
|
16956
18728
|
const compileRequested = options.compile ?? true;
|
|
16957
|
-
const
|
|
18729
|
+
const guideRequested = options.guide ?? false;
|
|
18730
|
+
const briefRequested = guideRequested ? true : options.brief ?? true;
|
|
18731
|
+
const reviewRequested = guideRequested ? false : options.review ?? false;
|
|
16958
18732
|
const sources = await loadManagedSources(rootDir);
|
|
16959
18733
|
const selected = options.all || !options.id ? sources : sources.filter((source) => source.id === options.id);
|
|
16960
18734
|
if (!selected.length) {
|
|
@@ -16990,10 +18764,23 @@ async function reloadManagedSources(rootDir, options = {}) {
|
|
|
16990
18764
|
};
|
|
16991
18765
|
});
|
|
16992
18766
|
await saveManagedSources(rootDir, nextSources);
|
|
18767
|
+
const reviews = reviewRequested ? await Promise.all(
|
|
18768
|
+
nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)).filter((source) => source.status === "ready").map(async (source) => await stageSourceReviewForScope(rootDir, scopeFromManagedSource(source)))
|
|
18769
|
+
) : [];
|
|
18770
|
+
const guides = guideRequested ? await Promise.all(
|
|
18771
|
+
nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)).filter((source) => source.status === "ready").map(
|
|
18772
|
+
async (source) => await stageSourceGuideForScope(rootDir, {
|
|
18773
|
+
...scopeFromManagedSource(source),
|
|
18774
|
+
briefPath: source.briefPath
|
|
18775
|
+
})
|
|
18776
|
+
)
|
|
18777
|
+
) : [];
|
|
16993
18778
|
return {
|
|
16994
18779
|
sources: nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)),
|
|
16995
18780
|
compile,
|
|
16996
|
-
briefPaths: [...briefPaths.values()]
|
|
18781
|
+
briefPaths: [...briefPaths.values()],
|
|
18782
|
+
reviews,
|
|
18783
|
+
guides
|
|
16997
18784
|
};
|
|
16998
18785
|
}
|
|
16999
18786
|
async function deleteManagedSource(rootDir, id) {
|
|
@@ -17894,6 +19681,8 @@ export {
|
|
|
17894
19681
|
getWatchStatus,
|
|
17895
19682
|
getWebSearchAdapterForTask,
|
|
17896
19683
|
getWorkspaceInfo,
|
|
19684
|
+
guideManagedSource,
|
|
19685
|
+
guideSourceScope,
|
|
17897
19686
|
importInbox,
|
|
17898
19687
|
ingestDirectory,
|
|
17899
19688
|
ingestInput,
|
|
@@ -17928,10 +19717,13 @@ export {
|
|
|
17928
19717
|
rejectApproval,
|
|
17929
19718
|
reloadManagedSources,
|
|
17930
19719
|
resolvePaths,
|
|
19720
|
+
reviewManagedSource,
|
|
19721
|
+
reviewSourceScope,
|
|
17931
19722
|
runSchedule,
|
|
17932
19723
|
runWatchCycle,
|
|
17933
19724
|
searchVault,
|
|
17934
19725
|
serveSchedules,
|
|
19726
|
+
stageGeneratedOutputPages,
|
|
17935
19727
|
startGraphServer,
|
|
17936
19728
|
startMcpServer,
|
|
17937
19729
|
syncTrackedRepos,
|