@swarmvaultai/engine 0.3.0 → 0.4.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/index.d.ts +39 -4
- package/dist/index.js +1457 -132
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -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);
|
|
@@ -12587,6 +13360,267 @@ async function buildManagedContent(absolutePath, defaults, build) {
|
|
|
12587
13360
|
}
|
|
12588
13361
|
return content;
|
|
12589
13362
|
}
|
|
13363
|
+
function manifestDetailValue(manifest, key) {
|
|
13364
|
+
const value = manifest.details?.[key];
|
|
13365
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
13366
|
+
}
|
|
13367
|
+
async function loadAnalysesBySourceIds(paths, sourceIds) {
|
|
13368
|
+
const analyses = await Promise.all(
|
|
13369
|
+
sourceIds.map(async (sourceId) => await readJsonFile(path22.join(paths.analysesDir, `${sourceId}.json`)))
|
|
13370
|
+
);
|
|
13371
|
+
return analyses.filter((analysis) => Boolean(analysis?.sourceId));
|
|
13372
|
+
}
|
|
13373
|
+
async function buildDashboardRecords(paths, graph, schemaHash, report) {
|
|
13374
|
+
const sourcePages = graph.pages.filter((page) => page.kind === "source");
|
|
13375
|
+
const reviewPages = graph.pages.filter((page) => page.kind === "output" && page.path.startsWith("outputs/source-reviews/"));
|
|
13376
|
+
const briefPages = graph.pages.filter((page) => page.kind === "output" && page.path.startsWith("outputs/source-briefs/"));
|
|
13377
|
+
const manifests = graph.sources;
|
|
13378
|
+
const manifestBySourceId = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
|
|
13379
|
+
const timelineManifests = manifests.filter((manifest) => manifestDetailValue(manifest, "occurred_at")).sort((left, right) => (manifestDetailValue(right, "occurred_at") ?? "").localeCompare(manifestDetailValue(left, "occurred_at") ?? "")).slice(0, 25);
|
|
13380
|
+
const recentSourcePages = [...sourcePages].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, 20);
|
|
13381
|
+
const analyses = await loadAnalysesBySourceIds(paths, uniqueStrings3(sourcePages.flatMap((page) => page.sourceIds)));
|
|
13382
|
+
const openQuestions = uniqueStrings3(
|
|
13383
|
+
analyses.flatMap((analysis) => analysis.questions.map((question) => `${analysis.title}: ${question}`))
|
|
13384
|
+
).slice(0, 20);
|
|
13385
|
+
const dashboards = [
|
|
13386
|
+
{
|
|
13387
|
+
relativePath: "dashboards/index.md",
|
|
13388
|
+
title: "Dashboards",
|
|
13389
|
+
content: (metadata) => matter9.stringify(
|
|
13390
|
+
[
|
|
13391
|
+
"# Dashboards",
|
|
13392
|
+
"",
|
|
13393
|
+
"- [[dashboards/recent-sources|Recent Sources]]",
|
|
13394
|
+
"- [[dashboards/timeline|Timeline]]",
|
|
13395
|
+
"- [[dashboards/contradictions|Contradictions]]",
|
|
13396
|
+
"- [[dashboards/open-questions|Open Questions]]",
|
|
13397
|
+
"",
|
|
13398
|
+
"```dataview",
|
|
13399
|
+
"TABLE file.mtime AS updated",
|
|
13400
|
+
'FROM "dashboards"',
|
|
13401
|
+
'WHERE file.name != "index"',
|
|
13402
|
+
"SORT file.mtime desc",
|
|
13403
|
+
"```",
|
|
13404
|
+
""
|
|
13405
|
+
].join("\n"),
|
|
13406
|
+
{
|
|
13407
|
+
page_id: "dashboards:index",
|
|
13408
|
+
kind: "index",
|
|
13409
|
+
title: "Dashboards",
|
|
13410
|
+
tags: ["index", "dashboards"],
|
|
13411
|
+
source_ids: [],
|
|
13412
|
+
project_ids: [],
|
|
13413
|
+
node_ids: [],
|
|
13414
|
+
freshness: "fresh",
|
|
13415
|
+
status: metadata.status,
|
|
13416
|
+
confidence: 1,
|
|
13417
|
+
created_at: metadata.createdAt,
|
|
13418
|
+
updated_at: metadata.updatedAt,
|
|
13419
|
+
compiled_from: metadata.compiledFrom,
|
|
13420
|
+
managed_by: metadata.managedBy,
|
|
13421
|
+
backlinks: [],
|
|
13422
|
+
schema_hash: schemaHash,
|
|
13423
|
+
source_hashes: {},
|
|
13424
|
+
source_semantic_hashes: {}
|
|
13425
|
+
}
|
|
13426
|
+
)
|
|
13427
|
+
},
|
|
13428
|
+
{
|
|
13429
|
+
relativePath: "dashboards/recent-sources.md",
|
|
13430
|
+
title: "Recent Sources",
|
|
13431
|
+
content: (metadata) => matter9.stringify(
|
|
13432
|
+
[
|
|
13433
|
+
"# Recent Sources",
|
|
13434
|
+
"",
|
|
13435
|
+
...recentSourcePages.length ? recentSourcePages.map((page) => `- ${page.updatedAt}: [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No source pages yet."],
|
|
13436
|
+
"",
|
|
13437
|
+
"```dataview",
|
|
13438
|
+
"TABLE source_type, occurred_at, participants",
|
|
13439
|
+
'FROM "sources"',
|
|
13440
|
+
"SORT updated_at desc",
|
|
13441
|
+
"LIMIT 25",
|
|
13442
|
+
"```",
|
|
13443
|
+
""
|
|
13444
|
+
].join("\n"),
|
|
13445
|
+
{
|
|
13446
|
+
page_id: "dashboards:recent-sources",
|
|
13447
|
+
kind: "index",
|
|
13448
|
+
title: "Recent Sources",
|
|
13449
|
+
tags: ["index", "dashboard", "recent-sources"],
|
|
13450
|
+
source_ids: recentSourcePages.flatMap((page) => page.sourceIds),
|
|
13451
|
+
project_ids: [],
|
|
13452
|
+
node_ids: [],
|
|
13453
|
+
freshness: "fresh",
|
|
13454
|
+
status: metadata.status,
|
|
13455
|
+
confidence: 1,
|
|
13456
|
+
created_at: metadata.createdAt,
|
|
13457
|
+
updated_at: metadata.updatedAt,
|
|
13458
|
+
compiled_from: recentSourcePages.flatMap((page) => page.sourceIds),
|
|
13459
|
+
managed_by: metadata.managedBy,
|
|
13460
|
+
backlinks: [],
|
|
13461
|
+
schema_hash: schemaHash,
|
|
13462
|
+
source_hashes: {},
|
|
13463
|
+
source_semantic_hashes: {}
|
|
13464
|
+
}
|
|
13465
|
+
)
|
|
13466
|
+
},
|
|
13467
|
+
{
|
|
13468
|
+
relativePath: "dashboards/timeline.md",
|
|
13469
|
+
title: "Timeline",
|
|
13470
|
+
content: (metadata) => matter9.stringify(
|
|
13471
|
+
[
|
|
13472
|
+
"# Timeline",
|
|
13473
|
+
"",
|
|
13474
|
+
...timelineManifests.length ? timelineManifests.map((manifest) => {
|
|
13475
|
+
const occurredAt = manifestDetailValue(manifest, "occurred_at") ?? manifest.updatedAt;
|
|
13476
|
+
const sourcePage = sourcePages.find((page) => page.sourceIds.includes(manifest.sourceId));
|
|
13477
|
+
return `- ${occurredAt}: ${sourcePage ? `[[${sourcePage.path.replace(/\.md$/, "")}|${sourcePage.title}]]` : manifest.title}`;
|
|
13478
|
+
}) : ["- No timeline-aware sources yet."],
|
|
13479
|
+
"",
|
|
13480
|
+
"```dataview",
|
|
13481
|
+
"TABLE occurred_at, participants, container_title",
|
|
13482
|
+
'FROM "sources"',
|
|
13483
|
+
"WHERE occurred_at",
|
|
13484
|
+
"SORT occurred_at desc",
|
|
13485
|
+
"```",
|
|
13486
|
+
""
|
|
13487
|
+
].join("\n"),
|
|
13488
|
+
{
|
|
13489
|
+
page_id: "dashboards:timeline",
|
|
13490
|
+
kind: "index",
|
|
13491
|
+
title: "Timeline",
|
|
13492
|
+
tags: ["index", "dashboard", "timeline"],
|
|
13493
|
+
source_ids: timelineManifests.map((manifest) => manifest.sourceId),
|
|
13494
|
+
project_ids: [],
|
|
13495
|
+
node_ids: [],
|
|
13496
|
+
freshness: "fresh",
|
|
13497
|
+
status: metadata.status,
|
|
13498
|
+
confidence: 1,
|
|
13499
|
+
created_at: metadata.createdAt,
|
|
13500
|
+
updated_at: metadata.updatedAt,
|
|
13501
|
+
compiled_from: timelineManifests.map((manifest) => manifest.sourceId),
|
|
13502
|
+
managed_by: metadata.managedBy,
|
|
13503
|
+
backlinks: [],
|
|
13504
|
+
schema_hash: schemaHash,
|
|
13505
|
+
source_hashes: {},
|
|
13506
|
+
source_semantic_hashes: {}
|
|
13507
|
+
}
|
|
13508
|
+
)
|
|
13509
|
+
},
|
|
13510
|
+
{
|
|
13511
|
+
relativePath: "dashboards/contradictions.md",
|
|
13512
|
+
title: "Contradictions",
|
|
13513
|
+
content: (metadata) => matter9.stringify(
|
|
13514
|
+
[
|
|
13515
|
+
"# Contradictions",
|
|
13516
|
+
"",
|
|
13517
|
+
...report?.contradictions.length ? report.contradictions.map((contradiction) => {
|
|
13518
|
+
const left = manifestBySourceId.get(contradiction.sourceIdA)?.title ?? contradiction.sourceIdA;
|
|
13519
|
+
const right = manifestBySourceId.get(contradiction.sourceIdB)?.title ?? contradiction.sourceIdB;
|
|
13520
|
+
return `- ${left} / ${right}: ${contradiction.claimA} <> ${contradiction.claimB}`;
|
|
13521
|
+
}) : ["- No contradictions are currently flagged."],
|
|
13522
|
+
"",
|
|
13523
|
+
...reviewPages.length || briefPages.length ? [
|
|
13524
|
+
"## Related Reviews",
|
|
13525
|
+
"",
|
|
13526
|
+
...[...reviewPages, ...briefPages].slice(0, 12).map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`),
|
|
13527
|
+
""
|
|
13528
|
+
] : [],
|
|
13529
|
+
"```dataview",
|
|
13530
|
+
'LIST FROM "outputs/source-reviews"',
|
|
13531
|
+
"SORT file.mtime desc",
|
|
13532
|
+
"```",
|
|
13533
|
+
""
|
|
13534
|
+
].join("\n"),
|
|
13535
|
+
{
|
|
13536
|
+
page_id: "dashboards:contradictions",
|
|
13537
|
+
kind: "index",
|
|
13538
|
+
title: "Contradictions",
|
|
13539
|
+
tags: ["index", "dashboard", "contradictions"],
|
|
13540
|
+
source_ids: report?.contradictions.flatMap((item) => [item.sourceIdA, item.sourceIdB]) ?? [],
|
|
13541
|
+
project_ids: [],
|
|
13542
|
+
node_ids: [],
|
|
13543
|
+
freshness: "fresh",
|
|
13544
|
+
status: metadata.status,
|
|
13545
|
+
confidence: 1,
|
|
13546
|
+
created_at: metadata.createdAt,
|
|
13547
|
+
updated_at: metadata.updatedAt,
|
|
13548
|
+
compiled_from: report?.contradictions.flatMap((item) => [item.sourceIdA, item.sourceIdB]) ?? [],
|
|
13549
|
+
managed_by: metadata.managedBy,
|
|
13550
|
+
backlinks: [],
|
|
13551
|
+
schema_hash: schemaHash,
|
|
13552
|
+
source_hashes: {},
|
|
13553
|
+
source_semantic_hashes: {}
|
|
13554
|
+
}
|
|
13555
|
+
)
|
|
13556
|
+
},
|
|
13557
|
+
{
|
|
13558
|
+
relativePath: "dashboards/open-questions.md",
|
|
13559
|
+
title: "Open Questions",
|
|
13560
|
+
content: (metadata) => matter9.stringify(
|
|
13561
|
+
[
|
|
13562
|
+
"# Open Questions",
|
|
13563
|
+
"",
|
|
13564
|
+
...openQuestions.length ? openQuestions.map((question) => `- ${question}`) : ["- No open questions are currently extracted."],
|
|
13565
|
+
"",
|
|
13566
|
+
"```dataview",
|
|
13567
|
+
'LIST FROM "outputs/source-briefs" OR "outputs/source-reviews"',
|
|
13568
|
+
"SORT file.mtime desc",
|
|
13569
|
+
"```",
|
|
13570
|
+
""
|
|
13571
|
+
].join("\n"),
|
|
13572
|
+
{
|
|
13573
|
+
page_id: "dashboards:open-questions",
|
|
13574
|
+
kind: "index",
|
|
13575
|
+
title: "Open Questions",
|
|
13576
|
+
tags: ["index", "dashboard", "open-questions"],
|
|
13577
|
+
source_ids: analyses.map((analysis) => analysis.sourceId),
|
|
13578
|
+
project_ids: [],
|
|
13579
|
+
node_ids: [],
|
|
13580
|
+
freshness: "fresh",
|
|
13581
|
+
status: metadata.status,
|
|
13582
|
+
confidence: 1,
|
|
13583
|
+
created_at: metadata.createdAt,
|
|
13584
|
+
updated_at: metadata.updatedAt,
|
|
13585
|
+
compiled_from: analyses.map((analysis) => analysis.sourceId),
|
|
13586
|
+
managed_by: metadata.managedBy,
|
|
13587
|
+
backlinks: [],
|
|
13588
|
+
schema_hash: schemaHash,
|
|
13589
|
+
source_hashes: {},
|
|
13590
|
+
source_semantic_hashes: {}
|
|
13591
|
+
}
|
|
13592
|
+
)
|
|
13593
|
+
}
|
|
13594
|
+
];
|
|
13595
|
+
const records = [];
|
|
13596
|
+
for (const dashboard of dashboards) {
|
|
13597
|
+
const absolutePath = path22.join(paths.wikiDir, dashboard.relativePath);
|
|
13598
|
+
const compiledFrom = dashboard.relativePath === "dashboards/recent-sources.md" ? recentSourcePages.flatMap((page) => page.sourceIds) : [];
|
|
13599
|
+
const content = await buildManagedContent(
|
|
13600
|
+
absolutePath,
|
|
13601
|
+
{
|
|
13602
|
+
managedBy: "system",
|
|
13603
|
+
compiledFrom
|
|
13604
|
+
},
|
|
13605
|
+
dashboard.content
|
|
13606
|
+
);
|
|
13607
|
+
records.push({
|
|
13608
|
+
page: emptyGraphPage({
|
|
13609
|
+
id: `dashboard:${dashboard.relativePath.replace(/\.md$/, "")}`,
|
|
13610
|
+
path: dashboard.relativePath,
|
|
13611
|
+
title: dashboard.title,
|
|
13612
|
+
kind: "index",
|
|
13613
|
+
sourceIds: compiledFrom,
|
|
13614
|
+
nodeIds: [],
|
|
13615
|
+
schemaHash,
|
|
13616
|
+
sourceHashes: {},
|
|
13617
|
+
confidence: 1
|
|
13618
|
+
}),
|
|
13619
|
+
content
|
|
13620
|
+
});
|
|
13621
|
+
}
|
|
13622
|
+
return records;
|
|
13623
|
+
}
|
|
12590
13624
|
function indexCompiledFrom(pages) {
|
|
12591
13625
|
return uniqueStrings3(pages.flatMap((page) => page.sourceIds));
|
|
12592
13626
|
}
|
|
@@ -13612,8 +14646,19 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
13612
14646
|
input.previousState?.generatedAt,
|
|
13613
14647
|
contradictions
|
|
13614
14648
|
);
|
|
13615
|
-
|
|
13616
|
-
const
|
|
14649
|
+
const preliminaryPages = [...basePages, ...graphOrientation.records.map((record) => record.page)];
|
|
14650
|
+
const dashboardRecords = await buildDashboardRecords(
|
|
14651
|
+
paths,
|
|
14652
|
+
{
|
|
14653
|
+
...baseGraph,
|
|
14654
|
+
sources: input.manifests,
|
|
14655
|
+
pages: preliminaryPages
|
|
14656
|
+
},
|
|
14657
|
+
globalSchemaHash,
|
|
14658
|
+
graphOrientation.report
|
|
14659
|
+
);
|
|
14660
|
+
records.push(...graphOrientation.records, ...dashboardRecords);
|
|
14661
|
+
const allPages = uniqueBy([...preliminaryPages, ...dashboardRecords.map((record) => record.page)], (page) => page.id);
|
|
13617
14662
|
const graph = {
|
|
13618
14663
|
...baseGraph,
|
|
13619
14664
|
pages: allPages
|
|
@@ -13717,6 +14762,11 @@ async function syncVaultArtifacts(rootDir, input) {
|
|
|
13717
14762
|
["concepts/index.md", "concepts", activeConceptPages],
|
|
13718
14763
|
["entities/index.md", "entities", activeEntityPages],
|
|
13719
14764
|
["outputs/index.md", "outputs", allPages.filter((page) => page.kind === "output")],
|
|
14765
|
+
[
|
|
14766
|
+
"dashboards/index.md",
|
|
14767
|
+
"dashboards",
|
|
14768
|
+
allPages.filter((page) => page.kind === "index" && page.path.startsWith("dashboards/") && page.path !== "dashboards/index.md")
|
|
14769
|
+
],
|
|
13720
14770
|
["candidates/index.md", "candidates", candidatePages],
|
|
13721
14771
|
["graph/index.md", "graph", allPages.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
|
|
13722
14772
|
]) {
|
|
@@ -13817,17 +14867,40 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
13817
14867
|
const compileState = await readJsonFile(paths.compileStatePath);
|
|
13818
14868
|
const globalSchemaHash = schemas.effective.global.hash;
|
|
13819
14869
|
const currentGraph = await readJsonFile(paths.graphPath);
|
|
13820
|
-
const
|
|
14870
|
+
const orientationPages = uniqueBy(
|
|
14871
|
+
pages.filter((page) => page.kind !== "graph_report" && page.kind !== "community_summary"),
|
|
14872
|
+
(page) => page.id
|
|
14873
|
+
);
|
|
14874
|
+
const basePages = uniqueBy(
|
|
14875
|
+
pages.filter(
|
|
14876
|
+
(page) => page.kind !== "graph_report" && page.kind !== "community_summary" && !(page.kind === "index" && page.path.startsWith("dashboards/"))
|
|
14877
|
+
),
|
|
14878
|
+
(page) => page.id
|
|
14879
|
+
);
|
|
13821
14880
|
const graphOrientation = currentGraph ? await buildGraphOrientationPages(
|
|
13822
14881
|
{
|
|
13823
14882
|
...currentGraph,
|
|
13824
|
-
pages:
|
|
14883
|
+
pages: orientationPages
|
|
13825
14884
|
},
|
|
13826
14885
|
paths,
|
|
13827
14886
|
globalSchemaHash,
|
|
13828
14887
|
compileState?.generatedAt
|
|
13829
14888
|
) : { records: [], report: null };
|
|
13830
|
-
const
|
|
14889
|
+
const dashboardRecords = currentGraph ? await buildDashboardRecords(
|
|
14890
|
+
paths,
|
|
14891
|
+
{
|
|
14892
|
+
...currentGraph,
|
|
14893
|
+
pages: [...basePages, ...graphOrientation.records.map((record) => record.page)]
|
|
14894
|
+
},
|
|
14895
|
+
globalSchemaHash,
|
|
14896
|
+
graphOrientation.report
|
|
14897
|
+
) : [];
|
|
14898
|
+
const pagesWithGraph = sortGraphPages(
|
|
14899
|
+
uniqueBy(
|
|
14900
|
+
[...basePages, ...graphOrientation.records.map((record) => record.page), ...dashboardRecords.map((record) => record.page)],
|
|
14901
|
+
(page) => page.id
|
|
14902
|
+
)
|
|
14903
|
+
);
|
|
13831
14904
|
if (currentGraph) {
|
|
13832
14905
|
await writeJsonFile(paths.graphPath, {
|
|
13833
14906
|
...currentGraph,
|
|
@@ -13855,6 +14928,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
13855
14928
|
ensureDir(path22.join(paths.wikiDir, "concepts")),
|
|
13856
14929
|
ensureDir(path22.join(paths.wikiDir, "entities")),
|
|
13857
14930
|
ensureDir(path22.join(paths.wikiDir, "outputs")),
|
|
14931
|
+
ensureDir(path22.join(paths.wikiDir, "dashboards")),
|
|
13858
14932
|
ensureDir(path22.join(paths.wikiDir, "graph")),
|
|
13859
14933
|
ensureDir(path22.join(paths.wikiDir, "graph", "communities")),
|
|
13860
14934
|
ensureDir(path22.join(paths.wikiDir, "projects")),
|
|
@@ -13917,6 +14991,11 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
13917
14991
|
["concepts/index.md", "concepts", pagesWithGraph.filter((page) => page.kind === "concept" && page.status !== "candidate")],
|
|
13918
14992
|
["entities/index.md", "entities", pagesWithGraph.filter((page) => page.kind === "entity" && page.status !== "candidate")],
|
|
13919
14993
|
["outputs/index.md", "outputs", pagesWithGraph.filter((page) => page.kind === "output")],
|
|
14994
|
+
[
|
|
14995
|
+
"dashboards/index.md",
|
|
14996
|
+
"dashboards",
|
|
14997
|
+
pagesWithGraph.filter((page) => page.kind === "index" && page.path.startsWith("dashboards/") && page.path !== "dashboards/index.md")
|
|
14998
|
+
],
|
|
13920
14999
|
["candidates/index.md", "candidates", pagesWithGraph.filter((page) => page.status === "candidate")],
|
|
13921
15000
|
["graph/index.md", "graph", pagesWithGraph.filter((page) => page.kind === "graph_report" || page.kind === "community_summary")]
|
|
13922
15001
|
]) {
|
|
@@ -13936,6 +15015,9 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
13936
15015
|
for (const record of graphOrientation.records) {
|
|
13937
15016
|
await writeFileIfChanged(path22.join(paths.wikiDir, record.page.path), record.content);
|
|
13938
15017
|
}
|
|
15018
|
+
for (const record of dashboardRecords) {
|
|
15019
|
+
await writeFileIfChanged(path22.join(paths.wikiDir, record.page.path), record.content);
|
|
15020
|
+
}
|
|
13939
15021
|
if (graphOrientation.report) {
|
|
13940
15022
|
await writeJsonFile(path22.join(paths.wikiDir, "graph", "report.json"), graphOrientation.report);
|
|
13941
15023
|
}
|
|
@@ -13952,6 +15034,11 @@ async function refreshIndexesAndSearch(rootDir, pages) {
|
|
|
13952
15034
|
await Promise.all(
|
|
13953
15035
|
existingGraphPages.filter((relativePath) => !allowedGraphPages.has(relativePath)).map((relativePath) => fs18.rm(path22.join(paths.wikiDir, relativePath), { force: true }))
|
|
13954
15036
|
);
|
|
15037
|
+
const existingDashboardPages = (await listFilesRecursive(path22.join(paths.wikiDir, "dashboards")).catch(() => [])).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path22.relative(paths.wikiDir, absolutePath)));
|
|
15038
|
+
const allowedDashboardPages = /* @__PURE__ */ new Set(["dashboards/index.md", ...dashboardRecords.map((record) => record.page.path)]);
|
|
15039
|
+
await Promise.all(
|
|
15040
|
+
existingDashboardPages.filter((relativePath) => !allowedDashboardPages.has(relativePath)).map((relativePath) => fs18.rm(path22.join(paths.wikiDir, relativePath), { force: true }))
|
|
15041
|
+
);
|
|
13955
15042
|
await rebuildSearchIndex(paths.searchDbPath, pagesWithGraph, paths.wikiDir);
|
|
13956
15043
|
}
|
|
13957
15044
|
async function prepareOutputPageSave(rootDir, input) {
|
|
@@ -14087,6 +15174,9 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
14087
15174
|
});
|
|
14088
15175
|
return { approvalId, approvalDir };
|
|
14089
15176
|
}
|
|
15177
|
+
async function stageGeneratedOutputPages(rootDir, stagedPages) {
|
|
15178
|
+
return await stageOutputApprovalBundle(rootDir, stagedPages);
|
|
15179
|
+
}
|
|
14090
15180
|
async function executeQuery(rootDir, question, format) {
|
|
14091
15181
|
const { paths } = await loadVaultConfig(rootDir);
|
|
14092
15182
|
const schemas = await loadVaultSchemas(rootDir);
|
|
@@ -15428,7 +16518,17 @@ async function benchmarkVault(rootDir, options = {}) {
|
|
|
15428
16518
|
});
|
|
15429
16519
|
await writeJsonFile(paths.benchmarkPath, artifact);
|
|
15430
16520
|
await refreshIndexesAndSearch(rootDir, graph.pages);
|
|
15431
|
-
|
|
16521
|
+
const refreshedGraph = await readJsonFile(paths.graphPath) ?? graph;
|
|
16522
|
+
const refreshedHash = graphHash(refreshedGraph);
|
|
16523
|
+
if (artifact.graphHash === refreshedHash) {
|
|
16524
|
+
return artifact;
|
|
16525
|
+
}
|
|
16526
|
+
const refreshedArtifact = {
|
|
16527
|
+
...artifact,
|
|
16528
|
+
graphHash: refreshedHash
|
|
16529
|
+
};
|
|
16530
|
+
await writeJsonFile(paths.benchmarkPath, refreshedArtifact);
|
|
16531
|
+
return refreshedArtifact;
|
|
15432
16532
|
}
|
|
15433
16533
|
async function pathGraphVault(rootDir, from, to) {
|
|
15434
16534
|
const graph = await ensureCompiledGraph(rootDir);
|
|
@@ -15648,7 +16748,7 @@ async function bootstrapDemo(rootDir, input) {
|
|
|
15648
16748
|
}
|
|
15649
16749
|
|
|
15650
16750
|
// src/mcp.ts
|
|
15651
|
-
var SERVER_VERSION = "0.
|
|
16751
|
+
var SERVER_VERSION = "0.4.0";
|
|
15652
16752
|
async function createMcpServer(rootDir) {
|
|
15653
16753
|
const server = new McpServer({
|
|
15654
16754
|
name: "swarmvault",
|
|
@@ -16504,7 +17604,7 @@ function matchesManagedSourceSpec(existing, input) {
|
|
|
16504
17604
|
if (existing.kind !== input.kind) {
|
|
16505
17605
|
return false;
|
|
16506
17606
|
}
|
|
16507
|
-
if (input.kind === "directory") {
|
|
17607
|
+
if (input.kind === "directory" || input.kind === "file") {
|
|
16508
17608
|
return path25.resolve(existing.path ?? "") === path25.resolve(input.path);
|
|
16509
17609
|
}
|
|
16510
17610
|
return (existing.url ?? "") === input.url;
|
|
@@ -16516,10 +17616,15 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
16516
17616
|
if (!stat) {
|
|
16517
17617
|
throw new Error(`Source not found: ${input}`);
|
|
16518
17618
|
}
|
|
17619
|
+
if (stat.isFile()) {
|
|
17620
|
+
return {
|
|
17621
|
+
kind: "file",
|
|
17622
|
+
path: absoluteInput,
|
|
17623
|
+
title: path25.basename(absoluteInput, path25.extname(absoluteInput)) || absoluteInput
|
|
17624
|
+
};
|
|
17625
|
+
}
|
|
16519
17626
|
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
|
-
);
|
|
17627
|
+
throw new Error("`swarmvault source add` supports local files, directories, public GitHub repo root URLs, and docs hubs.");
|
|
16523
17628
|
}
|
|
16524
17629
|
const detectedRepoRoot = await findNearestGitRoot3(absoluteInput);
|
|
16525
17630
|
const repoRoot = detectedRepoRoot && !(withinRoot2(rootDir, absoluteInput) && !withinRoot2(rootDir, detectedRepoRoot)) ? detectedRepoRoot : absoluteInput;
|
|
@@ -16552,6 +17657,10 @@ async function resolveManagedSourceInput(rootDir, input) {
|
|
|
16552
17657
|
function directorySourceIdsFor(manifests, inputPath) {
|
|
16553
17658
|
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
17659
|
}
|
|
17660
|
+
function fileSourceIdsFor(manifests, inputPath) {
|
|
17661
|
+
const absoluteInput = path25.resolve(inputPath);
|
|
17662
|
+
return manifests.filter((manifest) => manifest.originalPath && path25.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
|
|
17663
|
+
}
|
|
16555
17664
|
async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
16556
17665
|
const manifestsBefore = await listManifests(rootDir);
|
|
16557
17666
|
const previousInScope = manifestsBefore.filter(
|
|
@@ -16585,6 +17694,22 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
|
|
|
16585
17694
|
changed: result.imported.length + result.updated.length + removed.length > 0
|
|
16586
17695
|
};
|
|
16587
17696
|
}
|
|
17697
|
+
async function syncFileSource(rootDir, inputPath) {
|
|
17698
|
+
const result = await ingestInputDetailed(rootDir, inputPath);
|
|
17699
|
+
const manifestsAfter = await listManifests(rootDir);
|
|
17700
|
+
return {
|
|
17701
|
+
title: path25.basename(inputPath, path25.extname(inputPath)) || inputPath,
|
|
17702
|
+
sourceIds: fileSourceIdsFor(manifestsAfter, inputPath),
|
|
17703
|
+
counts: {
|
|
17704
|
+
scannedCount: result.scannedCount,
|
|
17705
|
+
importedCount: result.created.length,
|
|
17706
|
+
updatedCount: result.updated.length,
|
|
17707
|
+
removedCount: result.removed.length,
|
|
17708
|
+
skippedCount: result.skipped.length
|
|
17709
|
+
},
|
|
17710
|
+
changed: result.created.length + result.updated.length + result.removed.length > 0
|
|
17711
|
+
};
|
|
17712
|
+
}
|
|
16588
17713
|
async function runGitCommand(cwd, args) {
|
|
16589
17714
|
await new Promise((resolve, reject) => {
|
|
16590
17715
|
const child = spawn2("git", args, {
|
|
@@ -16679,6 +17804,22 @@ async function syncManagedSource(rootDir, entry, options) {
|
|
|
16679
17804
|
};
|
|
16680
17805
|
}
|
|
16681
17806
|
sync = await syncDirectorySource(rootDir, entry.path, entry.repoRoot);
|
|
17807
|
+
} else if (entry.kind === "file") {
|
|
17808
|
+
if (!entry.path) {
|
|
17809
|
+
throw new Error(`Managed source ${entry.id} is missing its file path.`);
|
|
17810
|
+
}
|
|
17811
|
+
if (!await fileExists(entry.path)) {
|
|
17812
|
+
return {
|
|
17813
|
+
...entry,
|
|
17814
|
+
status: "missing",
|
|
17815
|
+
updatedAt: now,
|
|
17816
|
+
lastSyncAt: now,
|
|
17817
|
+
lastSyncStatus: "error",
|
|
17818
|
+
lastError: `File not found: ${entry.path}`,
|
|
17819
|
+
changed: false
|
|
17820
|
+
};
|
|
17821
|
+
}
|
|
17822
|
+
sync = await syncFileSource(rootDir, entry.path);
|
|
16682
17823
|
} else if (entry.kind === "github_repo") {
|
|
16683
17824
|
sync = await syncGitHubRepoSource(rootDir, entry);
|
|
16684
17825
|
} else {
|
|
@@ -16897,6 +18038,179 @@ async function generateBriefsForSources(rootDir, sources) {
|
|
|
16897
18038
|
}
|
|
16898
18039
|
return briefPaths;
|
|
16899
18040
|
}
|
|
18041
|
+
function renderDeterministicSourceReview(input) {
|
|
18042
|
+
const canonicalPages = input.sourcePages.filter((page) => page.kind === "source" || page.kind === "concept" || page.kind === "entity").slice(0, 10);
|
|
18043
|
+
const modulePages = input.sourcePages.filter((page) => page.kind === "module").slice(0, 8);
|
|
18044
|
+
const questions = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.questions)).slice(0, 8);
|
|
18045
|
+
const concepts = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.concepts.map((concept) => concept.name))).slice(0, 8);
|
|
18046
|
+
const entities = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.entities.map((entity) => entity.name))).slice(0, 8);
|
|
18047
|
+
const contradictions = input.report?.contradictions.filter(
|
|
18048
|
+
(contradiction) => input.scope.sourceIds.includes(contradiction.sourceIdA) || input.scope.sourceIds.includes(contradiction.sourceIdB)
|
|
18049
|
+
) ?? [];
|
|
18050
|
+
return [
|
|
18051
|
+
`# Source Review: ${input.scope.title}`,
|
|
18052
|
+
"",
|
|
18053
|
+
"## What This Source Contains",
|
|
18054
|
+
"",
|
|
18055
|
+
...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."],
|
|
18056
|
+
"",
|
|
18057
|
+
"## Likely Canonical Pages To Update",
|
|
18058
|
+
"",
|
|
18059
|
+
...canonicalPages.length ? canonicalPages.map((page) => `- [[${page.path.replace(/\.md$/, "")}|${page.title}]]`) : ["- No canonical source, concept, or entity pages are linked to this source yet."],
|
|
18060
|
+
"",
|
|
18061
|
+
"## Important Topics And Entities",
|
|
18062
|
+
"",
|
|
18063
|
+
...concepts.length ? [`Concepts: ${concepts.join(", ")}`] : ["Concepts: none detected."],
|
|
18064
|
+
...entities.length ? [`Entities: ${entities.join(", ")}`] : ["Entities: none detected."],
|
|
18065
|
+
...modulePages.length ? ["", ...modulePages.map((page) => `- Module: [[${page.path.replace(/\.md$/, "")}|${page.title}]]`)] : [],
|
|
18066
|
+
"",
|
|
18067
|
+
"## Contradictions To Inspect",
|
|
18068
|
+
"",
|
|
18069
|
+
...contradictions.length ? contradictions.map((contradiction) => `- ${contradiction.claimA} / ${contradiction.claimB}`) : ["- No contradictions are currently flagged for this source scope."],
|
|
18070
|
+
"",
|
|
18071
|
+
"## Open Questions",
|
|
18072
|
+
"",
|
|
18073
|
+
...questions.length ? questions.map((question) => `- ${question}`) : ["- No extracted open questions yet."],
|
|
18074
|
+
"",
|
|
18075
|
+
"## Suggested Next Steps",
|
|
18076
|
+
"",
|
|
18077
|
+
...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."],
|
|
18078
|
+
""
|
|
18079
|
+
].join("\n");
|
|
18080
|
+
}
|
|
18081
|
+
async function generateSourceReviewMarkdown(rootDir, scope) {
|
|
18082
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
18083
|
+
let graph = await readJsonFile(paths.graphPath);
|
|
18084
|
+
if (!graph) {
|
|
18085
|
+
await compileVault(rootDir, {});
|
|
18086
|
+
graph = await readJsonFile(paths.graphPath);
|
|
18087
|
+
}
|
|
18088
|
+
if (!graph) {
|
|
18089
|
+
return null;
|
|
18090
|
+
}
|
|
18091
|
+
const sourcePages = scopedSourcePages(graph, scope.sourceIds);
|
|
18092
|
+
const analyses = await loadSourceAnalyses(rootDir, scope.sourceIds);
|
|
18093
|
+
const report = await readGraphReport(rootDir);
|
|
18094
|
+
const fallback = renderDeterministicSourceReview({
|
|
18095
|
+
scope,
|
|
18096
|
+
sourcePages,
|
|
18097
|
+
graph,
|
|
18098
|
+
analyses,
|
|
18099
|
+
report
|
|
18100
|
+
});
|
|
18101
|
+
const provider = await getProviderForTask(rootDir, "queryProvider");
|
|
18102
|
+
if (provider.type === "heuristic") {
|
|
18103
|
+
return fallback;
|
|
18104
|
+
}
|
|
18105
|
+
try {
|
|
18106
|
+
const schemas = await loadVaultSchemas(rootDir);
|
|
18107
|
+
const pageContext = sourcePages.slice(0, 12).map((page) => `- ${page.title} (${page.kind}) -> ${page.path}`).join("\n");
|
|
18108
|
+
const analysisContext = analyses.slice(0, 8).map(
|
|
18109
|
+
(analysis) => `# ${analysis.title}
|
|
18110
|
+
Summary: ${analysis.summary}
|
|
18111
|
+
Questions: ${analysis.questions.join(" | ") || "none"}
|
|
18112
|
+
Concepts: ${analysis.concepts.map((concept) => concept.name).join(", ") || "none"}
|
|
18113
|
+
Entities: ${analysis.entities.map((entity) => entity.name).join(", ") || "none"}`
|
|
18114
|
+
).join("\n\n---\n\n");
|
|
18115
|
+
const response = await provider.generateText({
|
|
18116
|
+
system: buildSchemaPrompt(
|
|
18117
|
+
schemas.effective.global,
|
|
18118
|
+
"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."
|
|
18119
|
+
),
|
|
18120
|
+
prompt: [
|
|
18121
|
+
`Source scope: ${scope.title}`,
|
|
18122
|
+
`Scope id: ${scope.id}`,
|
|
18123
|
+
`Tracked source ids: ${scope.sourceIds.join(", ") || "none"}`,
|
|
18124
|
+
"",
|
|
18125
|
+
"Pages:",
|
|
18126
|
+
pageContext || "- none",
|
|
18127
|
+
"",
|
|
18128
|
+
"Analyses:",
|
|
18129
|
+
analysisContext || "No analysis context available.",
|
|
18130
|
+
"",
|
|
18131
|
+
"Deterministic fallback draft:",
|
|
18132
|
+
fallback
|
|
18133
|
+
].join("\n")
|
|
18134
|
+
});
|
|
18135
|
+
return response.text?.trim() ? response.text.trim() : fallback;
|
|
18136
|
+
} catch {
|
|
18137
|
+
return fallback;
|
|
18138
|
+
}
|
|
18139
|
+
}
|
|
18140
|
+
async function stageSourceReviewForScope(rootDir, scope) {
|
|
18141
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
18142
|
+
const markdown = await generateSourceReviewMarkdown(rootDir, scope);
|
|
18143
|
+
if (!markdown) {
|
|
18144
|
+
throw new Error(`Could not generate a source review for ${scope.id}.`);
|
|
18145
|
+
}
|
|
18146
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
18147
|
+
const relatedPages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
|
|
18148
|
+
const relatedPageIds = relatedPages.slice(0, 16).map((page) => page.id);
|
|
18149
|
+
const relatedNodeIds = graph ? scopedNodeIds(graph, scope.sourceIds).slice(0, 24) : [];
|
|
18150
|
+
const projectIds = uniqueStrings4(relatedPages.flatMap((page) => page.projectIds));
|
|
18151
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18152
|
+
const output = buildOutputPage({
|
|
18153
|
+
title: `Source Review: ${scope.title}`,
|
|
18154
|
+
question: `Review ${scope.title}`,
|
|
18155
|
+
answer: markdown,
|
|
18156
|
+
citations: scope.sourceIds,
|
|
18157
|
+
schemaHash: graph?.generatedAt ?? "",
|
|
18158
|
+
outputFormat: "report",
|
|
18159
|
+
relatedPageIds,
|
|
18160
|
+
relatedNodeIds,
|
|
18161
|
+
relatedSourceIds: scope.sourceIds,
|
|
18162
|
+
projectIds,
|
|
18163
|
+
extraTags: ["source-review"],
|
|
18164
|
+
origin: "query",
|
|
18165
|
+
slug: `source-reviews/${scope.id}`,
|
|
18166
|
+
metadata: {
|
|
18167
|
+
status: "draft",
|
|
18168
|
+
createdAt: now,
|
|
18169
|
+
updatedAt: now,
|
|
18170
|
+
compiledFrom: scope.sourceIds,
|
|
18171
|
+
managedBy: "system",
|
|
18172
|
+
confidence: 0.79
|
|
18173
|
+
}
|
|
18174
|
+
});
|
|
18175
|
+
const approval = await stageGeneratedOutputPages(rootDir, [{ page: output.page, content: output.content }]);
|
|
18176
|
+
return {
|
|
18177
|
+
sourceId: scope.id,
|
|
18178
|
+
pageId: output.page.id,
|
|
18179
|
+
reviewPath: path25.join(approval.approvalDir, "wiki", output.page.path),
|
|
18180
|
+
staged: true,
|
|
18181
|
+
approvalId: approval.approvalId,
|
|
18182
|
+
approvalDir: approval.approvalDir
|
|
18183
|
+
};
|
|
18184
|
+
}
|
|
18185
|
+
function scopeFromManagedSource(source) {
|
|
18186
|
+
return {
|
|
18187
|
+
id: source.id,
|
|
18188
|
+
title: source.title,
|
|
18189
|
+
sourceIds: source.sourceIds
|
|
18190
|
+
};
|
|
18191
|
+
}
|
|
18192
|
+
async function reviewSourceScope(rootDir, scope) {
|
|
18193
|
+
return await stageSourceReviewForScope(rootDir, scope);
|
|
18194
|
+
}
|
|
18195
|
+
async function reviewManagedSource(rootDir, id) {
|
|
18196
|
+
const managedSources = await loadManagedSources(rootDir);
|
|
18197
|
+
const managedSource = managedSources.find((source) => source.id === id);
|
|
18198
|
+
if (managedSource) {
|
|
18199
|
+
if (!await loadVaultConfig(rootDir).then(({ paths }) => fileExists(paths.graphPath))) {
|
|
18200
|
+
await compileVault(rootDir, {});
|
|
18201
|
+
}
|
|
18202
|
+
return await stageSourceReviewForScope(rootDir, scopeFromManagedSource(managedSource));
|
|
18203
|
+
}
|
|
18204
|
+
const manifest = (await listManifests(rootDir)).find((candidate) => candidate.sourceId === id);
|
|
18205
|
+
if (!manifest) {
|
|
18206
|
+
throw new Error(`Managed source or source id not found: ${id}`);
|
|
18207
|
+
}
|
|
18208
|
+
return await stageSourceReviewForScope(rootDir, {
|
|
18209
|
+
id: manifest.sourceId,
|
|
18210
|
+
title: manifest.title,
|
|
18211
|
+
sourceIds: [manifest.sourceId]
|
|
18212
|
+
});
|
|
18213
|
+
}
|
|
16900
18214
|
function shouldCompile(changedSources, graphExists, compileRequested) {
|
|
16901
18215
|
return compileRequested && (!graphExists || changedSources.length > 0);
|
|
16902
18216
|
}
|
|
@@ -16907,17 +18221,18 @@ async function listManagedSourceRecords(rootDir) {
|
|
|
16907
18221
|
async function addManagedSource(rootDir, input, options = {}) {
|
|
16908
18222
|
const compileRequested = options.compile ?? true;
|
|
16909
18223
|
const briefRequested = options.brief ?? true;
|
|
18224
|
+
const reviewRequested = options.review ?? false;
|
|
16910
18225
|
const sources = await loadManagedSources(rootDir);
|
|
16911
18226
|
const resolved = await resolveManagedSourceInput(rootDir, input);
|
|
16912
18227
|
const existing = sources.find((candidate) => matchesManagedSourceSpec(candidate, resolved));
|
|
16913
18228
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16914
18229
|
const source = existing ?? {
|
|
16915
|
-
id: resolved.kind === "directory" ? stableManagedSourceId(
|
|
18230
|
+
id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path25.resolve(resolved.path), resolved.title) : stableManagedSourceId(resolved.kind, resolved.url, resolved.title),
|
|
16916
18231
|
kind: resolved.kind,
|
|
16917
18232
|
title: resolved.title,
|
|
16918
|
-
path: resolved.kind === "directory" ? resolved.path : void 0,
|
|
18233
|
+
path: resolved.kind === "directory" || resolved.kind === "file" ? resolved.path : void 0,
|
|
16919
18234
|
repoRoot: resolved.kind === "directory" ? resolved.repoRoot : void 0,
|
|
16920
|
-
url: resolved.kind === "directory" ? void 0 : resolved.url,
|
|
18235
|
+
url: resolved.kind === "directory" || resolved.kind === "file" ? void 0 : resolved.url,
|
|
16921
18236
|
createdAt: now,
|
|
16922
18237
|
updatedAt: now,
|
|
16923
18238
|
status: "ready",
|
|
@@ -16946,15 +18261,18 @@ async function addManagedSource(rootDir, input, options = {}) {
|
|
|
16946
18261
|
};
|
|
16947
18262
|
const nextSources = existing ? sources.map((candidate) => candidate.id === nextSource.id ? nextSource : candidate) : [...sources, nextSource];
|
|
16948
18263
|
await saveManagedSources(rootDir, nextSources);
|
|
18264
|
+
const review = reviewRequested && nextSource.status === "ready" ? await stageSourceReviewForScope(rootDir, scopeFromManagedSource(nextSource)) : void 0;
|
|
16949
18265
|
return {
|
|
16950
18266
|
source: nextSource,
|
|
16951
18267
|
compile,
|
|
16952
|
-
briefGenerated
|
|
18268
|
+
briefGenerated,
|
|
18269
|
+
review
|
|
16953
18270
|
};
|
|
16954
18271
|
}
|
|
16955
18272
|
async function reloadManagedSources(rootDir, options = {}) {
|
|
16956
18273
|
const compileRequested = options.compile ?? true;
|
|
16957
18274
|
const briefRequested = options.brief ?? true;
|
|
18275
|
+
const reviewRequested = options.review ?? false;
|
|
16958
18276
|
const sources = await loadManagedSources(rootDir);
|
|
16959
18277
|
const selected = options.all || !options.id ? sources : sources.filter((source) => source.id === options.id);
|
|
16960
18278
|
if (!selected.length) {
|
|
@@ -16990,10 +18308,14 @@ async function reloadManagedSources(rootDir, options = {}) {
|
|
|
16990
18308
|
};
|
|
16991
18309
|
});
|
|
16992
18310
|
await saveManagedSources(rootDir, nextSources);
|
|
18311
|
+
const reviews = reviewRequested ? await Promise.all(
|
|
18312
|
+
nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)).filter((source) => source.status === "ready").map(async (source) => await stageSourceReviewForScope(rootDir, scopeFromManagedSource(source)))
|
|
18313
|
+
) : [];
|
|
16993
18314
|
return {
|
|
16994
18315
|
sources: nextSources.filter((source) => selected.some((candidate) => candidate.id === source.id)),
|
|
16995
18316
|
compile,
|
|
16996
|
-
briefPaths: [...briefPaths.values()]
|
|
18317
|
+
briefPaths: [...briefPaths.values()],
|
|
18318
|
+
reviews
|
|
16997
18319
|
};
|
|
16998
18320
|
}
|
|
16999
18321
|
async function deleteManagedSource(rootDir, id) {
|
|
@@ -17928,10 +19250,13 @@ export {
|
|
|
17928
19250
|
rejectApproval,
|
|
17929
19251
|
reloadManagedSources,
|
|
17930
19252
|
resolvePaths,
|
|
19253
|
+
reviewManagedSource,
|
|
19254
|
+
reviewSourceScope,
|
|
17931
19255
|
runSchedule,
|
|
17932
19256
|
runWatchCycle,
|
|
17933
19257
|
searchVault,
|
|
17934
19258
|
serveSchedules,
|
|
19259
|
+
stageGeneratedOutputPages,
|
|
17935
19260
|
startGraphServer,
|
|
17936
19261
|
startMcpServer,
|
|
17937
19262
|
syncTrackedRepos,
|