@swarmvaultai/engine 0.1.7 → 0.1.9
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 +4 -4
- package/dist/index.js +622 -599
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1059,7 +1059,7 @@ import fs10 from "fs/promises";
|
|
|
1059
1059
|
import path14 from "path";
|
|
1060
1060
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1061
1061
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1062
|
-
import { z as
|
|
1062
|
+
import { z as z8 } from "zod";
|
|
1063
1063
|
|
|
1064
1064
|
// src/schema.ts
|
|
1065
1065
|
import fs4 from "fs/promises";
|
|
@@ -1172,7 +1172,7 @@ function buildSchemaPrompt(schema, instruction) {
|
|
|
1172
1172
|
import fs9 from "fs/promises";
|
|
1173
1173
|
import path13 from "path";
|
|
1174
1174
|
import matter7 from "gray-matter";
|
|
1175
|
-
import { z as
|
|
1175
|
+
import { z as z7 } from "zod";
|
|
1176
1176
|
|
|
1177
1177
|
// src/analysis.ts
|
|
1178
1178
|
import path6 from "path";
|
|
@@ -1407,29 +1407,46 @@ function conflictConfidence(claimA, claimB) {
|
|
|
1407
1407
|
import fs5 from "fs/promises";
|
|
1408
1408
|
import path9 from "path";
|
|
1409
1409
|
import matter2 from "gray-matter";
|
|
1410
|
-
import { z as
|
|
1410
|
+
import { z as z5 } from "zod";
|
|
1411
|
+
|
|
1412
|
+
// src/findings.ts
|
|
1413
|
+
import { z as z2 } from "zod";
|
|
1414
|
+
function normalizeFindingSeverity(value) {
|
|
1415
|
+
if (typeof value !== "string") {
|
|
1416
|
+
return "info";
|
|
1417
|
+
}
|
|
1418
|
+
const normalized = value.trim().toLowerCase();
|
|
1419
|
+
if (normalized === "error" || normalized === "critical" || normalized === "fatal" || normalized === "high" || normalized === "severe") {
|
|
1420
|
+
return "error";
|
|
1421
|
+
}
|
|
1422
|
+
if (normalized === "warning" || normalized === "warn" || normalized === "medium" || normalized === "moderate" || normalized === "caution") {
|
|
1423
|
+
return "warning";
|
|
1424
|
+
}
|
|
1425
|
+
return "info";
|
|
1426
|
+
}
|
|
1427
|
+
var findingSeveritySchema = z2.any().transform((value) => normalizeFindingSeverity(value));
|
|
1411
1428
|
|
|
1412
1429
|
// src/orchestration.ts
|
|
1413
|
-
import path7 from "path";
|
|
1414
1430
|
import { spawn } from "child_process";
|
|
1415
|
-
import
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1431
|
+
import path7 from "path";
|
|
1432
|
+
import { z as z3 } from "zod";
|
|
1433
|
+
var orchestrationRoleResultSchema = z3.object({
|
|
1434
|
+
summary: z3.string().optional(),
|
|
1435
|
+
findings: z3.array(
|
|
1436
|
+
z3.object({
|
|
1437
|
+
severity: findingSeveritySchema,
|
|
1438
|
+
message: z3.string().min(1),
|
|
1439
|
+
relatedPageIds: z3.array(z3.string()).optional(),
|
|
1440
|
+
relatedSourceIds: z3.array(z3.string()).optional(),
|
|
1441
|
+
suggestedQuery: z3.string().optional()
|
|
1425
1442
|
})
|
|
1426
1443
|
).default([]),
|
|
1427
|
-
questions:
|
|
1428
|
-
proposals:
|
|
1429
|
-
|
|
1430
|
-
path:
|
|
1431
|
-
content:
|
|
1432
|
-
reason:
|
|
1444
|
+
questions: z3.array(z3.string().min(1)).default([]),
|
|
1445
|
+
proposals: z3.array(
|
|
1446
|
+
z3.object({
|
|
1447
|
+
path: z3.string().min(1),
|
|
1448
|
+
content: z3.string().min(1),
|
|
1449
|
+
reason: z3.string().min(1)
|
|
1433
1450
|
})
|
|
1434
1451
|
).default([])
|
|
1435
1452
|
});
|
|
@@ -1600,7 +1617,7 @@ function summarizeRoleQuestions(results) {
|
|
|
1600
1617
|
// src/web-search/registry.ts
|
|
1601
1618
|
import path8 from "path";
|
|
1602
1619
|
import { pathToFileURL } from "url";
|
|
1603
|
-
import { z as
|
|
1620
|
+
import { z as z4 } from "zod";
|
|
1604
1621
|
|
|
1605
1622
|
// src/web-search/http-json.ts
|
|
1606
1623
|
function deepGet(value, pathValue) {
|
|
@@ -1682,10 +1699,10 @@ var HttpJsonWebSearchAdapter = class {
|
|
|
1682
1699
|
};
|
|
1683
1700
|
|
|
1684
1701
|
// src/web-search/registry.ts
|
|
1685
|
-
var customWebSearchModuleSchema =
|
|
1686
|
-
createAdapter:
|
|
1687
|
-
input: [
|
|
1688
|
-
output:
|
|
1702
|
+
var customWebSearchModuleSchema = z4.object({
|
|
1703
|
+
createAdapter: z4.function({
|
|
1704
|
+
input: [z4.string(), z4.custom(), z4.string()],
|
|
1705
|
+
output: z4.promise(z4.custom())
|
|
1689
1706
|
})
|
|
1690
1707
|
});
|
|
1691
1708
|
async function createWebSearchAdapter(id, config, rootDir) {
|
|
@@ -1720,15 +1737,15 @@ async function getWebSearchAdapterForTask(rootDir, task) {
|
|
|
1720
1737
|
}
|
|
1721
1738
|
|
|
1722
1739
|
// src/deep-lint.ts
|
|
1723
|
-
var deepLintResponseSchema =
|
|
1724
|
-
findings:
|
|
1725
|
-
|
|
1726
|
-
severity:
|
|
1727
|
-
code:
|
|
1728
|
-
message:
|
|
1729
|
-
relatedSourceIds:
|
|
1730
|
-
relatedPageIds:
|
|
1731
|
-
suggestedQuery:
|
|
1740
|
+
var deepLintResponseSchema = z5.object({
|
|
1741
|
+
findings: z5.array(
|
|
1742
|
+
z5.object({
|
|
1743
|
+
severity: findingSeveritySchema,
|
|
1744
|
+
code: z5.enum(["coverage_gap", "contradiction_candidate", "missing_citation", "candidate_page", "follow_up_question"]),
|
|
1745
|
+
message: z5.string().min(1),
|
|
1746
|
+
relatedSourceIds: z5.array(z5.string()).default([]),
|
|
1747
|
+
relatedPageIds: z5.array(z5.string()).default([]),
|
|
1748
|
+
suggestedQuery: z5.string().optional()
|
|
1732
1749
|
})
|
|
1733
1750
|
).max(20)
|
|
1734
1751
|
});
|
|
@@ -1982,199 +1999,6 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
|
|
|
1982
1999
|
);
|
|
1983
2000
|
}
|
|
1984
2001
|
|
|
1985
|
-
// src/output-artifacts.ts
|
|
1986
|
-
import { z as z5 } from "zod";
|
|
1987
|
-
function escapeXml(value) {
|
|
1988
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1989
|
-
}
|
|
1990
|
-
function clampNumber(value, min, max) {
|
|
1991
|
-
return Math.min(max, Math.max(min, value));
|
|
1992
|
-
}
|
|
1993
|
-
var chartSpecSchema = z5.object({
|
|
1994
|
-
kind: z5.enum(["bar", "line"]).default("bar"),
|
|
1995
|
-
title: z5.string().min(1),
|
|
1996
|
-
subtitle: z5.string().optional(),
|
|
1997
|
-
xLabel: z5.string().optional(),
|
|
1998
|
-
yLabel: z5.string().optional(),
|
|
1999
|
-
seriesLabel: z5.string().optional(),
|
|
2000
|
-
data: z5.array(
|
|
2001
|
-
z5.object({
|
|
2002
|
-
label: z5.string().min(1),
|
|
2003
|
-
value: z5.number().finite()
|
|
2004
|
-
})
|
|
2005
|
-
).min(2).max(12),
|
|
2006
|
-
notes: z5.array(z5.string().min(1)).max(5).optional()
|
|
2007
|
-
});
|
|
2008
|
-
var sceneSpecSchema = z5.object({
|
|
2009
|
-
title: z5.string().min(1),
|
|
2010
|
-
alt: z5.string().min(1),
|
|
2011
|
-
background: z5.string().optional(),
|
|
2012
|
-
width: z5.number().int().positive().max(2400).optional(),
|
|
2013
|
-
height: z5.number().int().positive().max(2400).optional(),
|
|
2014
|
-
elements: z5.array(
|
|
2015
|
-
z5.object({
|
|
2016
|
-
kind: z5.enum(["shape", "label"]),
|
|
2017
|
-
shape: z5.enum(["rect", "circle", "line"]).optional(),
|
|
2018
|
-
x: z5.number().finite(),
|
|
2019
|
-
y: z5.number().finite(),
|
|
2020
|
-
width: z5.number().finite().optional(),
|
|
2021
|
-
height: z5.number().finite().optional(),
|
|
2022
|
-
radius: z5.number().finite().optional(),
|
|
2023
|
-
text: z5.string().optional(),
|
|
2024
|
-
fontSize: z5.number().finite().optional(),
|
|
2025
|
-
fill: z5.string().optional(),
|
|
2026
|
-
stroke: z5.string().optional(),
|
|
2027
|
-
strokeWidth: z5.number().finite().optional(),
|
|
2028
|
-
opacity: z5.number().finite().optional()
|
|
2029
|
-
})
|
|
2030
|
-
).min(1).max(32)
|
|
2031
|
-
});
|
|
2032
|
-
function renderChartSvg(spec) {
|
|
2033
|
-
const width = 1200;
|
|
2034
|
-
const height = 720;
|
|
2035
|
-
const margin = { top: 110, right: 80, bottom: 110, left: 110 };
|
|
2036
|
-
const chartWidth = width - margin.left - margin.right;
|
|
2037
|
-
const chartHeight = height - margin.top - margin.bottom;
|
|
2038
|
-
const values = spec.data.map((item) => item.value);
|
|
2039
|
-
const maxValue = Math.max(...values, 1);
|
|
2040
|
-
const minValue = Math.min(...values, 0);
|
|
2041
|
-
const domainMin = Math.min(0, minValue);
|
|
2042
|
-
const domainMax = maxValue <= domainMin ? domainMin + 1 : maxValue;
|
|
2043
|
-
const ticks = 5;
|
|
2044
|
-
const tickValues = Array.from({ length: ticks + 1 }, (_, index) => domainMin + (domainMax - domainMin) * index / ticks);
|
|
2045
|
-
const projectY = (value) => margin.top + chartHeight - (value - domainMin) / (domainMax - domainMin || 1) * chartHeight;
|
|
2046
|
-
const zeroY = projectY(0);
|
|
2047
|
-
const step = chartWidth / Math.max(1, spec.data.length);
|
|
2048
|
-
const barWidth = Math.min(84, step * 0.6);
|
|
2049
|
-
const points = spec.data.map((item, index) => {
|
|
2050
|
-
const centerX = margin.left + step * index + step / 2;
|
|
2051
|
-
const y = projectY(item.value);
|
|
2052
|
-
return { ...item, centerX, y };
|
|
2053
|
-
});
|
|
2054
|
-
const gridLines = tickValues.map((value) => {
|
|
2055
|
-
const y = projectY(value);
|
|
2056
|
-
return [
|
|
2057
|
-
`<line x1="${margin.left}" y1="${y}" x2="${width - margin.right}" y2="${y}" stroke="#dbe4ec" stroke-width="1" />`,
|
|
2058
|
-
`<text x="${margin.left - 16}" y="${y + 4}" text-anchor="end" font-size="14" fill="#475569">${escapeXml(value.toFixed(0))}</text>`
|
|
2059
|
-
].join("");
|
|
2060
|
-
}).join("");
|
|
2061
|
-
const bars = spec.kind === "bar" ? points.map((point) => {
|
|
2062
|
-
const top = Math.min(point.y, zeroY);
|
|
2063
|
-
const barHeight = Math.max(8, Math.abs(zeroY - point.y));
|
|
2064
|
-
return [
|
|
2065
|
-
`<rect x="${point.centerX - barWidth / 2}" y="${top}" width="${barWidth}" height="${barHeight}" rx="12" fill="#0ea5e9" opacity="0.92" />`,
|
|
2066
|
-
`<text x="${point.centerX}" y="${top - 10}" text-anchor="middle" font-size="13" fill="#0f172a">${escapeXml(
|
|
2067
|
-
point.value.toFixed(0)
|
|
2068
|
-
)}</text>`
|
|
2069
|
-
].join("");
|
|
2070
|
-
}).join("") : "";
|
|
2071
|
-
const linePath = spec.kind === "line" ? points.map((point, index) => `${index === 0 ? "M" : "L"} ${point.centerX} ${point.y}`).join(" ") : "";
|
|
2072
|
-
const lineMarks = spec.kind === "line" ? [
|
|
2073
|
-
`<path d="${linePath}" fill="none" stroke="#0ea5e9" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" />`,
|
|
2074
|
-
...points.map(
|
|
2075
|
-
(point) => `<circle cx="${point.centerX}" cy="${point.y}" r="8" fill="#f8fafc" stroke="#0ea5e9" stroke-width="4" />
|
|
2076
|
-
<text x="${point.centerX}" y="${point.y - 18}" text-anchor="middle" font-size="13" fill="#0f172a">${escapeXml(
|
|
2077
|
-
point.value.toFixed(0)
|
|
2078
|
-
)}</text>`
|
|
2079
|
-
)
|
|
2080
|
-
].join("") : "";
|
|
2081
|
-
const labels = points.map(
|
|
2082
|
-
(point) => `<text x="${point.centerX}" y="${height - margin.bottom + 28}" text-anchor="middle" font-size="14" fill="#334155">${escapeXml(
|
|
2083
|
-
point.label
|
|
2084
|
-
)}</text>`
|
|
2085
|
-
).join("");
|
|
2086
|
-
const notes = (spec.notes ?? []).map((note, index) => `<text x="${margin.left}" y="${height - 26 - index * 18}" font-size="13" fill="#475569">${escapeXml(note)}</text>`).join("");
|
|
2087
|
-
const svg = [
|
|
2088
|
-
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(spec.title)}">`,
|
|
2089
|
-
'<rect width="100%" height="100%" fill="#f8fafc" />',
|
|
2090
|
-
`<text x="${margin.left}" y="56" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(spec.title)}</text>`,
|
|
2091
|
-
spec.subtitle ? `<text x="${margin.left}" y="86" font-size="18" fill="#475569">${escapeXml(spec.subtitle)}</text>` : "",
|
|
2092
|
-
gridLines,
|
|
2093
|
-
`<line x1="${margin.left}" y1="${zeroY}" x2="${width - margin.right}" y2="${zeroY}" stroke="#0f172a" stroke-width="2" />`,
|
|
2094
|
-
`<line x1="${margin.left}" y1="${margin.top}" x2="${margin.left}" y2="${height - margin.bottom}" stroke="#0f172a" stroke-width="2" />`,
|
|
2095
|
-
bars,
|
|
2096
|
-
lineMarks,
|
|
2097
|
-
labels,
|
|
2098
|
-
spec.xLabel ? `<text x="${margin.left + chartWidth / 2}" y="${height - 46}" text-anchor="middle" font-size="15" fill="#475569">${escapeXml(spec.xLabel)}</text>` : "",
|
|
2099
|
-
spec.yLabel ? `<text x="34" y="${margin.top + chartHeight / 2}" text-anchor="middle" font-size="15" fill="#475569" transform="rotate(-90 34 ${margin.top + chartHeight / 2})">${escapeXml(spec.yLabel)}</text>` : "",
|
|
2100
|
-
spec.seriesLabel ? `<text x="${width - margin.right}" y="56" text-anchor="end" font-size="15" fill="#475569">${escapeXml(spec.seriesLabel)}</text>` : "",
|
|
2101
|
-
notes,
|
|
2102
|
-
"</svg>"
|
|
2103
|
-
].filter(Boolean).join("");
|
|
2104
|
-
return { svg, width, height };
|
|
2105
|
-
}
|
|
2106
|
-
function renderSceneSvg(spec) {
|
|
2107
|
-
const width = clampNumber(spec.width ?? 1200, 480, 1600);
|
|
2108
|
-
const height = clampNumber(spec.height ?? 720, 320, 1200);
|
|
2109
|
-
const elements = spec.elements.map((element) => {
|
|
2110
|
-
const opacity = element.opacity === void 0 ? 1 : clampNumber(element.opacity, 0, 1);
|
|
2111
|
-
if (element.kind === "label") {
|
|
2112
|
-
return `<text x="${element.x}" y="${element.y}" font-size="${clampNumber(element.fontSize ?? 28, 10, 72)}" fill="${escapeXml(
|
|
2113
|
-
element.fill ?? "#0f172a"
|
|
2114
|
-
)}" opacity="${opacity}" font-family="'Avenir Next', 'Segoe UI', sans-serif">${escapeXml(element.text ?? "")}</text>`;
|
|
2115
|
-
}
|
|
2116
|
-
switch (element.shape) {
|
|
2117
|
-
case "circle":
|
|
2118
|
-
return `<circle cx="${element.x}" cy="${element.y}" r="${Math.max(6, element.radius ?? 40)}" fill="${escapeXml(
|
|
2119
|
-
element.fill ?? "#dbeafe"
|
|
2120
|
-
)}" stroke="${escapeXml(element.stroke ?? "#0ea5e9")}" stroke-width="${Math.max(1, element.strokeWidth ?? 2)}" opacity="${opacity}" />`;
|
|
2121
|
-
case "line":
|
|
2122
|
-
return `<line x1="${element.x}" y1="${element.y}" x2="${element.x + (element.width ?? 120)}" y2="${element.y + (element.height ?? 0)}" stroke="${escapeXml(element.stroke ?? "#475569")}" stroke-width="${Math.max(1, element.strokeWidth ?? 3)}" opacity="${opacity}" />`;
|
|
2123
|
-
case "rect":
|
|
2124
|
-
default:
|
|
2125
|
-
return `<rect x="${element.x}" y="${element.y}" width="${Math.max(8, element.width ?? 160)}" height="${Math.max(
|
|
2126
|
-
8,
|
|
2127
|
-
element.height ?? 120
|
|
2128
|
-
)}" rx="22" fill="${escapeXml(element.fill ?? "#e2e8f0")}" stroke="${escapeXml(element.stroke ?? "#94a3b8")}" stroke-width="${Math.max(
|
|
2129
|
-
1,
|
|
2130
|
-
element.strokeWidth ?? 2
|
|
2131
|
-
)}" opacity="${opacity}" />`;
|
|
2132
|
-
}
|
|
2133
|
-
}).join("");
|
|
2134
|
-
const svg = [
|
|
2135
|
-
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(
|
|
2136
|
-
spec.alt
|
|
2137
|
-
)}">`,
|
|
2138
|
-
`<rect width="100%" height="100%" fill="${escapeXml(spec.background ?? "#f8fafc")}" />`,
|
|
2139
|
-
`<text x="48" y="64" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(spec.title)}</text>`,
|
|
2140
|
-
elements,
|
|
2141
|
-
`</svg>`
|
|
2142
|
-
].join("");
|
|
2143
|
-
return { svg, width, height };
|
|
2144
|
-
}
|
|
2145
|
-
function renderRasterPosterSvg(input) {
|
|
2146
|
-
const width = clampNumber(input.width ?? 1200, 480, 1600);
|
|
2147
|
-
const height = clampNumber(input.height ?? 720, 320, 1200);
|
|
2148
|
-
const inset = 42;
|
|
2149
|
-
const svg = [
|
|
2150
|
-
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(
|
|
2151
|
-
input.alt
|
|
2152
|
-
)}">`,
|
|
2153
|
-
'<rect width="100%" height="100%" fill="#f8fafc" />',
|
|
2154
|
-
`<text x="${inset}" y="56" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(input.title)}</text>`,
|
|
2155
|
-
`<image href="${escapeXml(input.rasterFileName)}" x="${inset}" y="92" width="${width - inset * 2}" height="${height - 148}" preserveAspectRatio="xMidYMid meet" />`,
|
|
2156
|
-
`</svg>`
|
|
2157
|
-
].join("");
|
|
2158
|
-
return { svg, width, height };
|
|
2159
|
-
}
|
|
2160
|
-
function buildOutputAssetManifest(input) {
|
|
2161
|
-
return `${JSON.stringify(
|
|
2162
|
-
{
|
|
2163
|
-
slug: input.slug,
|
|
2164
|
-
format: input.format,
|
|
2165
|
-
question: input.question,
|
|
2166
|
-
title: input.title,
|
|
2167
|
-
answer: input.answer,
|
|
2168
|
-
citations: input.citations,
|
|
2169
|
-
assets: input.assets,
|
|
2170
|
-
spec: input.spec
|
|
2171
|
-
},
|
|
2172
|
-
null,
|
|
2173
|
-
2
|
|
2174
|
-
)}
|
|
2175
|
-
`;
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
2002
|
// src/markdown.ts
|
|
2179
2003
|
import matter3 from "gray-matter";
|
|
2180
2004
|
function uniqueStrings(values) {
|
|
@@ -2804,10 +2628,7 @@ function buildOutputPage(input) {
|
|
|
2804
2628
|
] : input.outputFormat === "chart" || input.outputFormat === "image" ? [
|
|
2805
2629
|
`# ${input.title ?? input.question}`,
|
|
2806
2630
|
"",
|
|
2807
|
-
...primaryOutputAsset(outputAssets) ? [
|
|
2808
|
-
`?.path ?? "")})`,
|
|
2809
|
-
""
|
|
2810
|
-
] : [],
|
|
2631
|
+
...primaryOutputAsset(outputAssets) ? [`?.path ?? "")})`, ""] : [],
|
|
2811
2632
|
input.answer,
|
|
2812
2633
|
"",
|
|
2813
2634
|
...outputAssetSection(outputAssets),
|
|
@@ -2939,10 +2760,7 @@ function buildExploreHubPage(input) {
|
|
|
2939
2760
|
] : input.outputFormat === "chart" || input.outputFormat === "image" ? [
|
|
2940
2761
|
`# ${title}`,
|
|
2941
2762
|
"",
|
|
2942
|
-
...primaryOutputAsset(outputAssets) ? [
|
|
2943
|
-
`?.path ?? "")})`,
|
|
2944
|
-
""
|
|
2945
|
-
] : [],
|
|
2763
|
+
...primaryOutputAsset(outputAssets) ? [`?.path ?? "")})`, ""] : [],
|
|
2946
2764
|
"## Root Question",
|
|
2947
2765
|
"",
|
|
2948
2766
|
input.question,
|
|
@@ -2986,77 +2804,271 @@ function buildExploreHubPage(input) {
|
|
|
2986
2804
|
};
|
|
2987
2805
|
}
|
|
2988
2806
|
|
|
2989
|
-
// src/
|
|
2990
|
-
import
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
// src/pages.ts
|
|
2995
|
-
import fs6 from "fs/promises";
|
|
2996
|
-
import path10 from "path";
|
|
2997
|
-
import matter4 from "gray-matter";
|
|
2998
|
-
function normalizeStringArray(value) {
|
|
2999
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
3000
|
-
}
|
|
3001
|
-
function normalizeProjectIds(value) {
|
|
3002
|
-
return normalizeStringArray(value);
|
|
3003
|
-
}
|
|
3004
|
-
function normalizeSourceHashes(value) {
|
|
3005
|
-
if (!value || typeof value !== "object") {
|
|
3006
|
-
return {};
|
|
3007
|
-
}
|
|
3008
|
-
return Object.fromEntries(
|
|
3009
|
-
Object.entries(value).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string")
|
|
3010
|
-
);
|
|
3011
|
-
}
|
|
3012
|
-
function normalizePageStatus(value, fallback = "active") {
|
|
3013
|
-
return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : fallback;
|
|
3014
|
-
}
|
|
3015
|
-
function normalizePageManager(value, fallback = "system") {
|
|
3016
|
-
return value === "human" || value === "system" ? value : fallback;
|
|
3017
|
-
}
|
|
3018
|
-
function normalizeOutputFormat(value, fallback = "markdown") {
|
|
3019
|
-
return value === "report" || value === "slides" || value === "chart" || value === "image" ? value : fallback;
|
|
3020
|
-
}
|
|
3021
|
-
function normalizeOutputAssets(value) {
|
|
3022
|
-
if (!Array.isArray(value)) {
|
|
3023
|
-
return [];
|
|
3024
|
-
}
|
|
3025
|
-
return value.reduce((assets, item) => {
|
|
3026
|
-
if (!item || typeof item !== "object") {
|
|
3027
|
-
return assets;
|
|
3028
|
-
}
|
|
3029
|
-
const candidate = item;
|
|
3030
|
-
const id = typeof candidate.id === "string" ? candidate.id : "";
|
|
3031
|
-
const role = candidate.role;
|
|
3032
|
-
const assetPath = typeof candidate.path === "string" ? candidate.path : "";
|
|
3033
|
-
const mimeType = typeof candidate.mimeType === "string" ? candidate.mimeType : "";
|
|
3034
|
-
if (!id || !assetPath || !mimeType || role !== "primary" && role !== "preview" && role !== "manifest" && role !== "poster") {
|
|
3035
|
-
return assets;
|
|
3036
|
-
}
|
|
3037
|
-
assets.push({
|
|
3038
|
-
id,
|
|
3039
|
-
role,
|
|
3040
|
-
path: assetPath,
|
|
3041
|
-
mimeType,
|
|
3042
|
-
width: typeof candidate.width === "number" ? candidate.width : void 0,
|
|
3043
|
-
height: typeof candidate.height === "number" ? candidate.height : void 0,
|
|
3044
|
-
dataPath: typeof candidate.dataPath === "string" ? candidate.dataPath : void 0
|
|
3045
|
-
});
|
|
3046
|
-
return assets;
|
|
3047
|
-
}, []);
|
|
2807
|
+
// src/output-artifacts.ts
|
|
2808
|
+
import { z as z6 } from "zod";
|
|
2809
|
+
function escapeXml(value) {
|
|
2810
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3048
2811
|
}
|
|
3049
|
-
function
|
|
3050
|
-
|
|
3051
|
-
return fallback;
|
|
3052
|
-
}
|
|
3053
|
-
const parsed = Date.parse(value);
|
|
3054
|
-
return Number.isNaN(parsed) ? fallback : new Date(parsed).toISOString();
|
|
2812
|
+
function clampNumber(value, min, max) {
|
|
2813
|
+
return Math.min(max, Math.max(min, value));
|
|
3055
2814
|
}
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
2815
|
+
var chartSpecSchema = z6.object({
|
|
2816
|
+
kind: z6.enum(["bar", "line"]).default("bar"),
|
|
2817
|
+
title: z6.string().min(1),
|
|
2818
|
+
subtitle: z6.string().optional(),
|
|
2819
|
+
xLabel: z6.string().optional(),
|
|
2820
|
+
yLabel: z6.string().optional(),
|
|
2821
|
+
seriesLabel: z6.string().optional(),
|
|
2822
|
+
data: z6.array(
|
|
2823
|
+
z6.object({
|
|
2824
|
+
label: z6.string().min(1),
|
|
2825
|
+
value: z6.number().finite()
|
|
2826
|
+
})
|
|
2827
|
+
).min(2).max(12),
|
|
2828
|
+
notes: z6.array(z6.string().min(1)).max(5).optional()
|
|
2829
|
+
});
|
|
2830
|
+
var sceneSpecSchema = z6.object({
|
|
2831
|
+
title: z6.string().min(1),
|
|
2832
|
+
alt: z6.string().min(1),
|
|
2833
|
+
background: z6.string().optional(),
|
|
2834
|
+
width: z6.number().int().positive().max(2400).optional(),
|
|
2835
|
+
height: z6.number().int().positive().max(2400).optional(),
|
|
2836
|
+
elements: z6.array(
|
|
2837
|
+
z6.object({
|
|
2838
|
+
kind: z6.enum(["shape", "label"]),
|
|
2839
|
+
shape: z6.enum(["rect", "circle", "line"]).optional(),
|
|
2840
|
+
x: z6.number().finite(),
|
|
2841
|
+
y: z6.number().finite(),
|
|
2842
|
+
width: z6.number().finite().optional(),
|
|
2843
|
+
height: z6.number().finite().optional(),
|
|
2844
|
+
radius: z6.number().finite().optional(),
|
|
2845
|
+
text: z6.string().optional(),
|
|
2846
|
+
fontSize: z6.number().finite().optional(),
|
|
2847
|
+
fill: z6.string().optional(),
|
|
2848
|
+
stroke: z6.string().optional(),
|
|
2849
|
+
strokeWidth: z6.number().finite().optional(),
|
|
2850
|
+
opacity: z6.number().finite().optional()
|
|
2851
|
+
})
|
|
2852
|
+
).min(1).max(32)
|
|
2853
|
+
});
|
|
2854
|
+
function renderChartSvg(spec) {
|
|
2855
|
+
const width = 1200;
|
|
2856
|
+
const height = 720;
|
|
2857
|
+
const margin = { top: 110, right: 80, bottom: 110, left: 110 };
|
|
2858
|
+
const chartWidth = width - margin.left - margin.right;
|
|
2859
|
+
const chartHeight = height - margin.top - margin.bottom;
|
|
2860
|
+
const values = spec.data.map((item) => item.value);
|
|
2861
|
+
const maxValue = Math.max(...values, 1);
|
|
2862
|
+
const minValue = Math.min(...values, 0);
|
|
2863
|
+
const domainMin = Math.min(0, minValue);
|
|
2864
|
+
const domainMax = maxValue <= domainMin ? domainMin + 1 : maxValue;
|
|
2865
|
+
const ticks = 5;
|
|
2866
|
+
const tickValues = Array.from({ length: ticks + 1 }, (_, index) => domainMin + (domainMax - domainMin) * index / ticks);
|
|
2867
|
+
const projectY = (value) => margin.top + chartHeight - (value - domainMin) / (domainMax - domainMin || 1) * chartHeight;
|
|
2868
|
+
const zeroY = projectY(0);
|
|
2869
|
+
const step = chartWidth / Math.max(1, spec.data.length);
|
|
2870
|
+
const barWidth = Math.min(84, step * 0.6);
|
|
2871
|
+
const points = spec.data.map((item, index) => {
|
|
2872
|
+
const centerX = margin.left + step * index + step / 2;
|
|
2873
|
+
const y = projectY(item.value);
|
|
2874
|
+
return { ...item, centerX, y };
|
|
2875
|
+
});
|
|
2876
|
+
const gridLines = tickValues.map((value) => {
|
|
2877
|
+
const y = projectY(value);
|
|
2878
|
+
return [
|
|
2879
|
+
`<line x1="${margin.left}" y1="${y}" x2="${width - margin.right}" y2="${y}" stroke="#dbe4ec" stroke-width="1" />`,
|
|
2880
|
+
`<text x="${margin.left - 16}" y="${y + 4}" text-anchor="end" font-size="14" fill="#475569">${escapeXml(value.toFixed(0))}</text>`
|
|
2881
|
+
].join("");
|
|
2882
|
+
}).join("");
|
|
2883
|
+
const bars = spec.kind === "bar" ? points.map((point) => {
|
|
2884
|
+
const top = Math.min(point.y, zeroY);
|
|
2885
|
+
const barHeight = Math.max(8, Math.abs(zeroY - point.y));
|
|
2886
|
+
return [
|
|
2887
|
+
`<rect x="${point.centerX - barWidth / 2}" y="${top}" width="${barWidth}" height="${barHeight}" rx="12" fill="#0ea5e9" opacity="0.92" />`,
|
|
2888
|
+
`<text x="${point.centerX}" y="${top - 10}" text-anchor="middle" font-size="13" fill="#0f172a">${escapeXml(
|
|
2889
|
+
point.value.toFixed(0)
|
|
2890
|
+
)}</text>`
|
|
2891
|
+
].join("");
|
|
2892
|
+
}).join("") : "";
|
|
2893
|
+
const linePath = spec.kind === "line" ? points.map((point, index) => `${index === 0 ? "M" : "L"} ${point.centerX} ${point.y}`).join(" ") : "";
|
|
2894
|
+
const lineMarks = spec.kind === "line" ? [
|
|
2895
|
+
`<path d="${linePath}" fill="none" stroke="#0ea5e9" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" />`,
|
|
2896
|
+
...points.map(
|
|
2897
|
+
(point) => `<circle cx="${point.centerX}" cy="${point.y}" r="8" fill="#f8fafc" stroke="#0ea5e9" stroke-width="4" />
|
|
2898
|
+
<text x="${point.centerX}" y="${point.y - 18}" text-anchor="middle" font-size="13" fill="#0f172a">${escapeXml(
|
|
2899
|
+
point.value.toFixed(0)
|
|
2900
|
+
)}</text>`
|
|
2901
|
+
)
|
|
2902
|
+
].join("") : "";
|
|
2903
|
+
const labels = points.map(
|
|
2904
|
+
(point) => `<text x="${point.centerX}" y="${height - margin.bottom + 28}" text-anchor="middle" font-size="14" fill="#334155">${escapeXml(
|
|
2905
|
+
point.label
|
|
2906
|
+
)}</text>`
|
|
2907
|
+
).join("");
|
|
2908
|
+
const notes = (spec.notes ?? []).map(
|
|
2909
|
+
(note, index) => `<text x="${margin.left}" y="${height - 26 - index * 18}" font-size="13" fill="#475569">${escapeXml(note)}</text>`
|
|
2910
|
+
).join("");
|
|
2911
|
+
const svg = [
|
|
2912
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(spec.title)}">`,
|
|
2913
|
+
'<rect width="100%" height="100%" fill="#f8fafc" />',
|
|
2914
|
+
`<text x="${margin.left}" y="56" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(spec.title)}</text>`,
|
|
2915
|
+
spec.subtitle ? `<text x="${margin.left}" y="86" font-size="18" fill="#475569">${escapeXml(spec.subtitle)}</text>` : "",
|
|
2916
|
+
gridLines,
|
|
2917
|
+
`<line x1="${margin.left}" y1="${zeroY}" x2="${width - margin.right}" y2="${zeroY}" stroke="#0f172a" stroke-width="2" />`,
|
|
2918
|
+
`<line x1="${margin.left}" y1="${margin.top}" x2="${margin.left}" y2="${height - margin.bottom}" stroke="#0f172a" stroke-width="2" />`,
|
|
2919
|
+
bars,
|
|
2920
|
+
lineMarks,
|
|
2921
|
+
labels,
|
|
2922
|
+
spec.xLabel ? `<text x="${margin.left + chartWidth / 2}" y="${height - 46}" text-anchor="middle" font-size="15" fill="#475569">${escapeXml(spec.xLabel)}</text>` : "",
|
|
2923
|
+
spec.yLabel ? `<text x="34" y="${margin.top + chartHeight / 2}" text-anchor="middle" font-size="15" fill="#475569" transform="rotate(-90 34 ${margin.top + chartHeight / 2})">${escapeXml(spec.yLabel)}</text>` : "",
|
|
2924
|
+
spec.seriesLabel ? `<text x="${width - margin.right}" y="56" text-anchor="end" font-size="15" fill="#475569">${escapeXml(spec.seriesLabel)}</text>` : "",
|
|
2925
|
+
notes,
|
|
2926
|
+
"</svg>"
|
|
2927
|
+
].filter(Boolean).join("");
|
|
2928
|
+
return { svg, width, height };
|
|
2929
|
+
}
|
|
2930
|
+
function renderSceneSvg(spec) {
|
|
2931
|
+
const width = clampNumber(spec.width ?? 1200, 480, 1600);
|
|
2932
|
+
const height = clampNumber(spec.height ?? 720, 320, 1200);
|
|
2933
|
+
const elements = spec.elements.map((element) => {
|
|
2934
|
+
const opacity = element.opacity === void 0 ? 1 : clampNumber(element.opacity, 0, 1);
|
|
2935
|
+
if (element.kind === "label") {
|
|
2936
|
+
return `<text x="${element.x}" y="${element.y}" font-size="${clampNumber(element.fontSize ?? 28, 10, 72)}" fill="${escapeXml(
|
|
2937
|
+
element.fill ?? "#0f172a"
|
|
2938
|
+
)}" opacity="${opacity}" font-family="'Avenir Next', 'Segoe UI', sans-serif">${escapeXml(element.text ?? "")}</text>`;
|
|
2939
|
+
}
|
|
2940
|
+
switch (element.shape) {
|
|
2941
|
+
case "circle":
|
|
2942
|
+
return `<circle cx="${element.x}" cy="${element.y}" r="${Math.max(6, element.radius ?? 40)}" fill="${escapeXml(
|
|
2943
|
+
element.fill ?? "#dbeafe"
|
|
2944
|
+
)}" stroke="${escapeXml(element.stroke ?? "#0ea5e9")}" stroke-width="${Math.max(1, element.strokeWidth ?? 2)}" opacity="${opacity}" />`;
|
|
2945
|
+
case "line":
|
|
2946
|
+
return `<line x1="${element.x}" y1="${element.y}" x2="${element.x + (element.width ?? 120)}" y2="${element.y + (element.height ?? 0)}" stroke="${escapeXml(element.stroke ?? "#475569")}" stroke-width="${Math.max(1, element.strokeWidth ?? 3)}" opacity="${opacity}" />`;
|
|
2947
|
+
default:
|
|
2948
|
+
return `<rect x="${element.x}" y="${element.y}" width="${Math.max(8, element.width ?? 160)}" height="${Math.max(
|
|
2949
|
+
8,
|
|
2950
|
+
element.height ?? 120
|
|
2951
|
+
)}" rx="22" fill="${escapeXml(element.fill ?? "#e2e8f0")}" stroke="${escapeXml(element.stroke ?? "#94a3b8")}" stroke-width="${Math.max(
|
|
2952
|
+
1,
|
|
2953
|
+
element.strokeWidth ?? 2
|
|
2954
|
+
)}" opacity="${opacity}" />`;
|
|
2955
|
+
}
|
|
2956
|
+
}).join("");
|
|
2957
|
+
const svg = [
|
|
2958
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(
|
|
2959
|
+
spec.alt
|
|
2960
|
+
)}">`,
|
|
2961
|
+
`<rect width="100%" height="100%" fill="${escapeXml(spec.background ?? "#f8fafc")}" />`,
|
|
2962
|
+
`<text x="48" y="64" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(spec.title)}</text>`,
|
|
2963
|
+
elements,
|
|
2964
|
+
`</svg>`
|
|
2965
|
+
].join("");
|
|
2966
|
+
return { svg, width, height };
|
|
2967
|
+
}
|
|
2968
|
+
function renderRasterPosterSvg(input) {
|
|
2969
|
+
const width = clampNumber(input.width ?? 1200, 480, 1600);
|
|
2970
|
+
const height = clampNumber(input.height ?? 720, 320, 1200);
|
|
2971
|
+
const inset = 42;
|
|
2972
|
+
const svg = [
|
|
2973
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(
|
|
2974
|
+
input.alt
|
|
2975
|
+
)}">`,
|
|
2976
|
+
'<rect width="100%" height="100%" fill="#f8fafc" />',
|
|
2977
|
+
`<text x="${inset}" y="56" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(input.title)}</text>`,
|
|
2978
|
+
`<image href="${escapeXml(input.rasterFileName)}" x="${inset}" y="92" width="${width - inset * 2}" height="${height - 148}" preserveAspectRatio="xMidYMid meet" />`,
|
|
2979
|
+
`</svg>`
|
|
2980
|
+
].join("");
|
|
2981
|
+
return { svg, width, height };
|
|
2982
|
+
}
|
|
2983
|
+
function buildOutputAssetManifest(input) {
|
|
2984
|
+
return `${JSON.stringify(
|
|
2985
|
+
{
|
|
2986
|
+
slug: input.slug,
|
|
2987
|
+
format: input.format,
|
|
2988
|
+
question: input.question,
|
|
2989
|
+
title: input.title,
|
|
2990
|
+
answer: input.answer,
|
|
2991
|
+
citations: input.citations,
|
|
2992
|
+
assets: input.assets,
|
|
2993
|
+
spec: input.spec
|
|
2994
|
+
},
|
|
2995
|
+
null,
|
|
2996
|
+
2
|
|
2997
|
+
)}
|
|
2998
|
+
`;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
// src/outputs.ts
|
|
3002
|
+
import fs7 from "fs/promises";
|
|
3003
|
+
import path11 from "path";
|
|
3004
|
+
import matter5 from "gray-matter";
|
|
3005
|
+
|
|
3006
|
+
// src/pages.ts
|
|
3007
|
+
import fs6 from "fs/promises";
|
|
3008
|
+
import path10 from "path";
|
|
3009
|
+
import matter4 from "gray-matter";
|
|
3010
|
+
function normalizeStringArray(value) {
|
|
3011
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
3012
|
+
}
|
|
3013
|
+
function normalizeProjectIds(value) {
|
|
3014
|
+
return normalizeStringArray(value);
|
|
3015
|
+
}
|
|
3016
|
+
function normalizeSourceHashes(value) {
|
|
3017
|
+
if (!value || typeof value !== "object") {
|
|
3018
|
+
return {};
|
|
3019
|
+
}
|
|
3020
|
+
return Object.fromEntries(
|
|
3021
|
+
Object.entries(value).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string")
|
|
3022
|
+
);
|
|
3023
|
+
}
|
|
3024
|
+
function normalizePageStatus(value, fallback = "active") {
|
|
3025
|
+
return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : fallback;
|
|
3026
|
+
}
|
|
3027
|
+
function normalizePageManager(value, fallback = "system") {
|
|
3028
|
+
return value === "human" || value === "system" ? value : fallback;
|
|
3029
|
+
}
|
|
3030
|
+
function normalizeOutputFormat(value, fallback = "markdown") {
|
|
3031
|
+
return value === "report" || value === "slides" || value === "chart" || value === "image" ? value : fallback;
|
|
3032
|
+
}
|
|
3033
|
+
function normalizeOutputAssets(value) {
|
|
3034
|
+
if (!Array.isArray(value)) {
|
|
3035
|
+
return [];
|
|
3036
|
+
}
|
|
3037
|
+
return value.reduce((assets, item) => {
|
|
3038
|
+
if (!item || typeof item !== "object") {
|
|
3039
|
+
return assets;
|
|
3040
|
+
}
|
|
3041
|
+
const candidate = item;
|
|
3042
|
+
const id = typeof candidate.id === "string" ? candidate.id : "";
|
|
3043
|
+
const role = candidate.role;
|
|
3044
|
+
const assetPath = typeof candidate.path === "string" ? candidate.path : "";
|
|
3045
|
+
const mimeType = typeof candidate.mimeType === "string" ? candidate.mimeType : "";
|
|
3046
|
+
if (!id || !assetPath || !mimeType || role !== "primary" && role !== "preview" && role !== "manifest" && role !== "poster") {
|
|
3047
|
+
return assets;
|
|
3048
|
+
}
|
|
3049
|
+
assets.push({
|
|
3050
|
+
id,
|
|
3051
|
+
role,
|
|
3052
|
+
path: assetPath,
|
|
3053
|
+
mimeType,
|
|
3054
|
+
width: typeof candidate.width === "number" ? candidate.width : void 0,
|
|
3055
|
+
height: typeof candidate.height === "number" ? candidate.height : void 0,
|
|
3056
|
+
dataPath: typeof candidate.dataPath === "string" ? candidate.dataPath : void 0
|
|
3057
|
+
});
|
|
3058
|
+
return assets;
|
|
3059
|
+
}, []);
|
|
3060
|
+
}
|
|
3061
|
+
function normalizeTimestamp(value, fallback) {
|
|
3062
|
+
if (typeof value !== "string") {
|
|
3063
|
+
return fallback;
|
|
3064
|
+
}
|
|
3065
|
+
const parsed = Date.parse(value);
|
|
3066
|
+
return Number.isNaN(parsed) ? fallback : new Date(parsed).toISOString();
|
|
3067
|
+
}
|
|
3068
|
+
async function loadExistingManagedPageState(absolutePath, defaults = {}) {
|
|
3069
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3070
|
+
const createdFallback = defaults.createdAt ?? now;
|
|
3071
|
+
const updatedFallback = defaults.updatedAt ?? createdFallback;
|
|
3060
3072
|
if (!await fileExists(absolutePath)) {
|
|
3061
3073
|
return {
|
|
3062
3074
|
status: defaults.status ?? "active",
|
|
@@ -5091,7 +5103,13 @@ async function prepareOutputPageSave(rootDir, input) {
|
|
|
5091
5103
|
}
|
|
5092
5104
|
});
|
|
5093
5105
|
const absolutePath = path13.join(paths.wikiDir, output.page.path);
|
|
5094
|
-
return {
|
|
5106
|
+
return {
|
|
5107
|
+
page: output.page,
|
|
5108
|
+
savedPath: absolutePath,
|
|
5109
|
+
outputAssets: output.page.outputAssets ?? [],
|
|
5110
|
+
content: output.content,
|
|
5111
|
+
assetFiles: input.assetFiles ?? []
|
|
5112
|
+
};
|
|
5095
5113
|
}
|
|
5096
5114
|
async function persistOutputPage(rootDir, input) {
|
|
5097
5115
|
const { paths } = await loadVaultConfig(rootDir);
|
|
@@ -5126,7 +5144,13 @@ async function prepareExploreHubSave(rootDir, input) {
|
|
|
5126
5144
|
}
|
|
5127
5145
|
});
|
|
5128
5146
|
const absolutePath = path13.join(paths.wikiDir, hub.page.path);
|
|
5129
|
-
return {
|
|
5147
|
+
return {
|
|
5148
|
+
page: hub.page,
|
|
5149
|
+
savedPath: absolutePath,
|
|
5150
|
+
outputAssets: hub.page.outputAssets ?? [],
|
|
5151
|
+
content: hub.content,
|
|
5152
|
+
assetFiles: input.assetFiles ?? []
|
|
5153
|
+
};
|
|
5130
5154
|
}
|
|
5131
5155
|
async function persistExploreHub(rootDir, input) {
|
|
5132
5156
|
const { paths } = await loadVaultConfig(rootDir);
|
|
@@ -5169,9 +5193,10 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
|
|
|
5169
5193
|
await fs9.writeFile(targetPath, file.content, "utf8");
|
|
5170
5194
|
}
|
|
5171
5195
|
}
|
|
5172
|
-
const nextPages = sortGraphPages(
|
|
5173
|
-
|
|
5174
|
-
|
|
5196
|
+
const nextPages = sortGraphPages([
|
|
5197
|
+
...(previousGraph?.pages ?? []).filter((page) => !stagedPages.some((item) => item.page.id === page.id || item.page.path === page.path)),
|
|
5198
|
+
...stagedPages.map((item) => item.page)
|
|
5199
|
+
]);
|
|
5175
5200
|
const graph = {
|
|
5176
5201
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5177
5202
|
nodes: previousGraph?.nodes ?? [],
|
|
@@ -5308,8 +5333,8 @@ async function generateFollowUpQuestions(rootDir, question, answer) {
|
|
|
5308
5333
|
Current answer:
|
|
5309
5334
|
${answer}`
|
|
5310
5335
|
},
|
|
5311
|
-
|
|
5312
|
-
questions:
|
|
5336
|
+
z7.object({
|
|
5337
|
+
questions: z7.array(z7.string().min(1)).max(5)
|
|
5313
5338
|
})
|
|
5314
5339
|
);
|
|
5315
5340
|
return uniqueBy(response.questions, (item) => item).filter((item) => item !== question);
|
|
@@ -6144,9 +6169,7 @@ async function exploreVault(rootDir, options) {
|
|
|
6144
6169
|
`Citations: ${query.citations.join(", ") || "none"}`
|
|
6145
6170
|
].join("\n")
|
|
6146
6171
|
});
|
|
6147
|
-
const orchestrationNotes = roleResults.flatMap(
|
|
6148
|
-
(result) => result.findings.map((finding) => `- [${result.role}] ${finding.message}`)
|
|
6149
|
-
);
|
|
6172
|
+
const orchestrationNotes = roleResults.flatMap((result) => result.findings.map((finding) => `- [${result.role}] ${finding.message}`));
|
|
6150
6173
|
const enrichedAnswer = orchestrationNotes.length ? `${query.answer}
|
|
6151
6174
|
|
|
6152
6175
|
## Agent Review
|
|
@@ -6508,7 +6531,7 @@ async function bootstrapDemo(rootDir, input) {
|
|
|
6508
6531
|
}
|
|
6509
6532
|
|
|
6510
6533
|
// src/mcp.ts
|
|
6511
|
-
var SERVER_VERSION = "0.1.
|
|
6534
|
+
var SERVER_VERSION = "0.1.9";
|
|
6512
6535
|
async function createMcpServer(rootDir) {
|
|
6513
6536
|
const server = new McpServer({
|
|
6514
6537
|
name: "swarmvault",
|
|
@@ -6530,8 +6553,8 @@ async function createMcpServer(rootDir) {
|
|
|
6530
6553
|
{
|
|
6531
6554
|
description: "Search compiled wiki pages using the local full-text index.",
|
|
6532
6555
|
inputSchema: {
|
|
6533
|
-
query:
|
|
6534
|
-
limit:
|
|
6556
|
+
query: z8.string().min(1).describe("Search query"),
|
|
6557
|
+
limit: z8.number().int().min(1).max(25).optional().describe("Maximum number of results")
|
|
6535
6558
|
}
|
|
6536
6559
|
},
|
|
6537
6560
|
async ({ query, limit }) => {
|
|
@@ -6544,7 +6567,7 @@ async function createMcpServer(rootDir) {
|
|
|
6544
6567
|
{
|
|
6545
6568
|
description: "Read a generated wiki page by its path relative to wiki/.",
|
|
6546
6569
|
inputSchema: {
|
|
6547
|
-
path:
|
|
6570
|
+
path: z8.string().min(1).describe("Path relative to wiki/, for example sources/example.md")
|
|
6548
6571
|
}
|
|
6549
6572
|
},
|
|
6550
6573
|
async ({ path: relativePath }) => {
|
|
@@ -6560,7 +6583,7 @@ async function createMcpServer(rootDir) {
|
|
|
6560
6583
|
{
|
|
6561
6584
|
description: "List source manifests in the current workspace.",
|
|
6562
6585
|
inputSchema: {
|
|
6563
|
-
limit:
|
|
6586
|
+
limit: z8.number().int().min(1).max(100).optional().describe("Maximum number of manifests to return")
|
|
6564
6587
|
}
|
|
6565
6588
|
},
|
|
6566
6589
|
async ({ limit }) => {
|
|
@@ -6573,9 +6596,9 @@ async function createMcpServer(rootDir) {
|
|
|
6573
6596
|
{
|
|
6574
6597
|
description: "Ask a question against the compiled vault and optionally save the answer.",
|
|
6575
6598
|
inputSchema: {
|
|
6576
|
-
question:
|
|
6577
|
-
save:
|
|
6578
|
-
format:
|
|
6599
|
+
question: z8.string().min(1).describe("Question to ask the vault"),
|
|
6600
|
+
save: z8.boolean().optional().describe("Persist the answer to wiki/outputs"),
|
|
6601
|
+
format: z8.enum(["markdown", "report", "slides", "chart", "image"]).optional().describe("Output format")
|
|
6579
6602
|
}
|
|
6580
6603
|
},
|
|
6581
6604
|
async ({ question, save, format }) => {
|
|
@@ -6592,7 +6615,7 @@ async function createMcpServer(rootDir) {
|
|
|
6592
6615
|
{
|
|
6593
6616
|
description: "Ingest a local file path or URL into the SwarmVault workspace.",
|
|
6594
6617
|
inputSchema: {
|
|
6595
|
-
input:
|
|
6618
|
+
input: z8.string().min(1).describe("Local path or URL to ingest")
|
|
6596
6619
|
}
|
|
6597
6620
|
},
|
|
6598
6621
|
async ({ input }) => {
|
|
@@ -6605,7 +6628,7 @@ async function createMcpServer(rootDir) {
|
|
|
6605
6628
|
{
|
|
6606
6629
|
description: "Compile source manifests into wiki pages, graph data, and search index.",
|
|
6607
6630
|
inputSchema: {
|
|
6608
|
-
approve:
|
|
6631
|
+
approve: z8.boolean().optional().describe("Stage a review bundle without applying active page changes")
|
|
6609
6632
|
}
|
|
6610
6633
|
},
|
|
6611
6634
|
async ({ approve }) => {
|
|
@@ -6803,283 +6826,14 @@ function asTextResource(uri, text) {
|
|
|
6803
6826
|
};
|
|
6804
6827
|
}
|
|
6805
6828
|
|
|
6806
|
-
// src/
|
|
6807
|
-
import { execFile } from "child_process";
|
|
6829
|
+
// src/schedule.ts
|
|
6808
6830
|
import fs11 from "fs/promises";
|
|
6809
|
-
import http from "http";
|
|
6810
6831
|
import path15 from "path";
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
import mime2 from "mime-types";
|
|
6814
|
-
var execFileAsync = promisify(execFile);
|
|
6815
|
-
async function readViewerPage(rootDir, relativePath) {
|
|
6816
|
-
const { paths } = await loadVaultConfig(rootDir);
|
|
6817
|
-
const absolutePath = path15.resolve(paths.wikiDir, relativePath);
|
|
6818
|
-
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
6819
|
-
return null;
|
|
6820
|
-
}
|
|
6821
|
-
const raw = await fs11.readFile(absolutePath, "utf8");
|
|
6822
|
-
const parsed = matter8(raw);
|
|
6823
|
-
return {
|
|
6824
|
-
path: relativePath,
|
|
6825
|
-
title: typeof parsed.data.title === "string" ? parsed.data.title : path15.basename(relativePath, path15.extname(relativePath)),
|
|
6826
|
-
frontmatter: parsed.data,
|
|
6827
|
-
content: parsed.content,
|
|
6828
|
-
assets: normalizeOutputAssets(parsed.data.output_assets)
|
|
6829
|
-
};
|
|
6830
|
-
}
|
|
6831
|
-
async function readViewerAsset(rootDir, relativePath) {
|
|
6832
|
-
const { paths } = await loadVaultConfig(rootDir);
|
|
6833
|
-
const absolutePath = path15.resolve(paths.wikiDir, relativePath);
|
|
6834
|
-
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
6835
|
-
return null;
|
|
6836
|
-
}
|
|
6837
|
-
return {
|
|
6838
|
-
buffer: await fs11.readFile(absolutePath),
|
|
6839
|
-
mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
|
|
6840
|
-
};
|
|
6841
|
-
}
|
|
6842
|
-
async function assetDataUrl(rootDir, relativePath) {
|
|
6843
|
-
const asset = await readViewerAsset(rootDir, relativePath);
|
|
6844
|
-
if (!asset) {
|
|
6845
|
-
return void 0;
|
|
6846
|
-
}
|
|
6847
|
-
return `data:${asset.mimeType};base64,${asset.buffer.toString("base64")}`;
|
|
6848
|
-
}
|
|
6849
|
-
async function readJsonBody(request) {
|
|
6850
|
-
const chunks = [];
|
|
6851
|
-
for await (const chunk of request) {
|
|
6852
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
6853
|
-
}
|
|
6854
|
-
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
6855
|
-
if (!raw) {
|
|
6856
|
-
return {};
|
|
6857
|
-
}
|
|
6858
|
-
return JSON.parse(raw);
|
|
6859
|
-
}
|
|
6860
|
-
async function ensureViewerDist(viewerDistDir) {
|
|
6861
|
-
const indexPath = path15.join(viewerDistDir, "index.html");
|
|
6862
|
-
if (await fileExists(indexPath)) {
|
|
6863
|
-
return;
|
|
6864
|
-
}
|
|
6865
|
-
const viewerProjectDir = path15.dirname(viewerDistDir);
|
|
6866
|
-
if (await fileExists(path15.join(viewerProjectDir, "package.json"))) {
|
|
6867
|
-
await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
|
|
6868
|
-
}
|
|
6869
|
-
}
|
|
6870
|
-
async function startGraphServer(rootDir, port) {
|
|
6871
|
-
const { config, paths } = await loadVaultConfig(rootDir);
|
|
6872
|
-
const effectivePort = port ?? config.viewer.port;
|
|
6873
|
-
await ensureViewerDist(paths.viewerDistDir);
|
|
6874
|
-
const server = http.createServer(async (request, response) => {
|
|
6875
|
-
const url = new URL(request.url ?? "/", `http://${request.headers.host ?? `localhost:${effectivePort}`}`);
|
|
6876
|
-
if (url.pathname === "/api/graph") {
|
|
6877
|
-
if (!await fileExists(paths.graphPath)) {
|
|
6878
|
-
response.writeHead(404, { "content-type": "application/json" });
|
|
6879
|
-
response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
|
|
6880
|
-
return;
|
|
6881
|
-
}
|
|
6882
|
-
response.writeHead(200, { "content-type": "application/json" });
|
|
6883
|
-
response.end(await fs11.readFile(paths.graphPath, "utf8"));
|
|
6884
|
-
return;
|
|
6885
|
-
}
|
|
6886
|
-
if (url.pathname === "/api/search") {
|
|
6887
|
-
if (!await fileExists(paths.searchDbPath)) {
|
|
6888
|
-
response.writeHead(404, { "content-type": "application/json" });
|
|
6889
|
-
response.end(JSON.stringify({ error: "Search index not found. Run `swarmvault compile` first." }));
|
|
6890
|
-
return;
|
|
6891
|
-
}
|
|
6892
|
-
const query = url.searchParams.get("q") ?? "";
|
|
6893
|
-
const limit = Number.parseInt(url.searchParams.get("limit") ?? "10", 10);
|
|
6894
|
-
const kind = url.searchParams.get("kind") ?? "all";
|
|
6895
|
-
const status = url.searchParams.get("status") ?? "all";
|
|
6896
|
-
const project = url.searchParams.get("project") ?? "all";
|
|
6897
|
-
const results = searchPages(paths.searchDbPath, query, {
|
|
6898
|
-
limit: Number.isFinite(limit) ? limit : 10,
|
|
6899
|
-
kind,
|
|
6900
|
-
status,
|
|
6901
|
-
project
|
|
6902
|
-
});
|
|
6903
|
-
response.writeHead(200, { "content-type": "application/json" });
|
|
6904
|
-
response.end(JSON.stringify(results));
|
|
6905
|
-
return;
|
|
6906
|
-
}
|
|
6907
|
-
if (url.pathname === "/api/page") {
|
|
6908
|
-
const relativePath2 = url.searchParams.get("path") ?? "";
|
|
6909
|
-
const page = await readViewerPage(rootDir, relativePath2);
|
|
6910
|
-
if (!page) {
|
|
6911
|
-
response.writeHead(404, { "content-type": "application/json" });
|
|
6912
|
-
response.end(JSON.stringify({ error: `Page not found: ${relativePath2}` }));
|
|
6913
|
-
return;
|
|
6914
|
-
}
|
|
6915
|
-
response.writeHead(200, { "content-type": "application/json" });
|
|
6916
|
-
response.end(JSON.stringify(page));
|
|
6917
|
-
return;
|
|
6918
|
-
}
|
|
6919
|
-
if (url.pathname === "/api/asset") {
|
|
6920
|
-
const relativePath2 = url.searchParams.get("path") ?? "";
|
|
6921
|
-
const asset = await readViewerAsset(rootDir, relativePath2);
|
|
6922
|
-
if (!asset) {
|
|
6923
|
-
response.writeHead(404, { "content-type": "application/json" });
|
|
6924
|
-
response.end(JSON.stringify({ error: `Asset not found: ${relativePath2}` }));
|
|
6925
|
-
return;
|
|
6926
|
-
}
|
|
6927
|
-
response.writeHead(200, { "content-type": asset.mimeType });
|
|
6928
|
-
response.end(asset.buffer);
|
|
6929
|
-
return;
|
|
6930
|
-
}
|
|
6931
|
-
if (url.pathname === "/api/reviews" && request.method === "GET") {
|
|
6932
|
-
response.writeHead(200, { "content-type": "application/json" });
|
|
6933
|
-
response.end(JSON.stringify(await listApprovals(rootDir)));
|
|
6934
|
-
return;
|
|
6935
|
-
}
|
|
6936
|
-
if (url.pathname === "/api/review" && request.method === "GET") {
|
|
6937
|
-
const approvalId = url.searchParams.get("id") ?? "";
|
|
6938
|
-
if (!approvalId) {
|
|
6939
|
-
response.writeHead(400, { "content-type": "application/json" });
|
|
6940
|
-
response.end(JSON.stringify({ error: "Missing approval id." }));
|
|
6941
|
-
return;
|
|
6942
|
-
}
|
|
6943
|
-
response.writeHead(200, { "content-type": "application/json" });
|
|
6944
|
-
response.end(JSON.stringify(await readApproval(rootDir, approvalId)));
|
|
6945
|
-
return;
|
|
6946
|
-
}
|
|
6947
|
-
if (url.pathname === "/api/review" && request.method === "POST") {
|
|
6948
|
-
const body = await readJsonBody(request);
|
|
6949
|
-
const approvalId = typeof body.approvalId === "string" ? body.approvalId : "";
|
|
6950
|
-
const targets = Array.isArray(body.targets) ? body.targets.filter((item) => typeof item === "string") : [];
|
|
6951
|
-
const action = url.searchParams.get("action") ?? "";
|
|
6952
|
-
if (!approvalId || action !== "accept" && action !== "reject") {
|
|
6953
|
-
response.writeHead(400, { "content-type": "application/json" });
|
|
6954
|
-
response.end(JSON.stringify({ error: "Missing approval id or invalid review action." }));
|
|
6955
|
-
return;
|
|
6956
|
-
}
|
|
6957
|
-
const result = action === "accept" ? await acceptApproval(rootDir, approvalId, targets) : await rejectApproval(rootDir, approvalId, targets);
|
|
6958
|
-
response.writeHead(200, { "content-type": "application/json" });
|
|
6959
|
-
response.end(JSON.stringify(result));
|
|
6960
|
-
return;
|
|
6961
|
-
}
|
|
6962
|
-
if (url.pathname === "/api/candidates" && request.method === "GET") {
|
|
6963
|
-
response.writeHead(200, { "content-type": "application/json" });
|
|
6964
|
-
response.end(JSON.stringify(await listCandidates(rootDir)));
|
|
6965
|
-
return;
|
|
6966
|
-
}
|
|
6967
|
-
if (url.pathname === "/api/candidate" && request.method === "POST") {
|
|
6968
|
-
const body = await readJsonBody(request);
|
|
6969
|
-
const target2 = typeof body.target === "string" ? body.target : "";
|
|
6970
|
-
const action = url.searchParams.get("action") ?? "";
|
|
6971
|
-
if (!target2 || action !== "promote" && action !== "archive") {
|
|
6972
|
-
response.writeHead(400, { "content-type": "application/json" });
|
|
6973
|
-
response.end(JSON.stringify({ error: "Missing candidate target or invalid candidate action." }));
|
|
6974
|
-
return;
|
|
6975
|
-
}
|
|
6976
|
-
const result = action === "promote" ? await promoteCandidate(rootDir, target2) : await archiveCandidate(rootDir, target2);
|
|
6977
|
-
response.writeHead(200, { "content-type": "application/json" });
|
|
6978
|
-
response.end(JSON.stringify(result));
|
|
6979
|
-
return;
|
|
6980
|
-
}
|
|
6981
|
-
const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
6982
|
-
const target = path15.join(paths.viewerDistDir, relativePath);
|
|
6983
|
-
const fallback = path15.join(paths.viewerDistDir, "index.html");
|
|
6984
|
-
const filePath = await fileExists(target) ? target : fallback;
|
|
6985
|
-
if (!await fileExists(filePath)) {
|
|
6986
|
-
response.writeHead(503, { "content-type": "text/plain" });
|
|
6987
|
-
response.end("Viewer build not found. Run `pnpm build` first.");
|
|
6988
|
-
return;
|
|
6989
|
-
}
|
|
6990
|
-
response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
|
|
6991
|
-
response.end(await fs11.readFile(filePath));
|
|
6992
|
-
});
|
|
6993
|
-
await new Promise((resolve) => {
|
|
6994
|
-
server.listen(effectivePort, resolve);
|
|
6995
|
-
});
|
|
6996
|
-
return {
|
|
6997
|
-
port: effectivePort,
|
|
6998
|
-
close: async () => {
|
|
6999
|
-
await new Promise((resolve, reject) => {
|
|
7000
|
-
server.close((error) => {
|
|
7001
|
-
if (error) {
|
|
7002
|
-
reject(error);
|
|
7003
|
-
return;
|
|
7004
|
-
}
|
|
7005
|
-
resolve();
|
|
7006
|
-
});
|
|
7007
|
-
});
|
|
7008
|
-
}
|
|
7009
|
-
};
|
|
7010
|
-
}
|
|
7011
|
-
async function exportGraphHtml(rootDir, outputPath) {
|
|
7012
|
-
const { paths } = await loadVaultConfig(rootDir);
|
|
7013
|
-
const graph = await readJsonFile(paths.graphPath);
|
|
7014
|
-
if (!graph) {
|
|
7015
|
-
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
7016
|
-
}
|
|
7017
|
-
await ensureViewerDist(paths.viewerDistDir);
|
|
7018
|
-
const indexPath = path15.join(paths.viewerDistDir, "index.html");
|
|
7019
|
-
if (!await fileExists(indexPath)) {
|
|
7020
|
-
throw new Error("Viewer build not found. Run `pnpm build` first.");
|
|
7021
|
-
}
|
|
7022
|
-
const pages = await Promise.all(
|
|
7023
|
-
graph.pages.map(async (page) => {
|
|
7024
|
-
const loaded = await readViewerPage(rootDir, page.path);
|
|
7025
|
-
return loaded ? {
|
|
7026
|
-
pageId: page.id,
|
|
7027
|
-
path: loaded.path,
|
|
7028
|
-
title: loaded.title,
|
|
7029
|
-
kind: page.kind,
|
|
7030
|
-
status: page.status,
|
|
7031
|
-
projectIds: page.projectIds,
|
|
7032
|
-
content: loaded.content,
|
|
7033
|
-
assets: await Promise.all(
|
|
7034
|
-
loaded.assets.map(async (asset) => ({
|
|
7035
|
-
...asset,
|
|
7036
|
-
dataUrl: await assetDataUrl(rootDir, asset.path)
|
|
7037
|
-
}))
|
|
7038
|
-
)
|
|
7039
|
-
} : null;
|
|
7040
|
-
})
|
|
7041
|
-
);
|
|
7042
|
-
const rawHtml = await fs11.readFile(indexPath, "utf8");
|
|
7043
|
-
const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
|
|
7044
|
-
const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
|
|
7045
|
-
const scriptPath = scriptMatch?.[1] ? path15.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
|
|
7046
|
-
const stylePath = styleMatch?.[1] ? path15.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
|
|
7047
|
-
if (!scriptPath || !await fileExists(scriptPath)) {
|
|
7048
|
-
throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
|
|
7049
|
-
}
|
|
7050
|
-
const script = await fs11.readFile(scriptPath, "utf8");
|
|
7051
|
-
const style = stylePath && await fileExists(stylePath) ? await fs11.readFile(stylePath, "utf8") : "";
|
|
7052
|
-
const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean) }, null, 2).replace(/</g, "\\u003c");
|
|
7053
|
-
const html = [
|
|
7054
|
-
"<!doctype html>",
|
|
7055
|
-
'<html lang="en">',
|
|
7056
|
-
" <head>",
|
|
7057
|
-
' <meta charset="UTF-8" />',
|
|
7058
|
-
' <meta name="viewport" content="width=device-width, initial-scale=1.0" />',
|
|
7059
|
-
" <title>SwarmVault Graph Export</title>",
|
|
7060
|
-
style ? ` <style>${style}</style>` : "",
|
|
7061
|
-
" </head>",
|
|
7062
|
-
" <body>",
|
|
7063
|
-
' <div id="root"></div>',
|
|
7064
|
-
` <script>window.__SWARMVAULT_EMBEDDED_DATA__ = ${embeddedData};</script>`,
|
|
7065
|
-
` <script type="module">${script}</script>`,
|
|
7066
|
-
" </body>",
|
|
7067
|
-
"</html>",
|
|
7068
|
-
""
|
|
7069
|
-
].filter(Boolean).join("\n");
|
|
7070
|
-
await fs11.mkdir(path15.dirname(outputPath), { recursive: true });
|
|
7071
|
-
await fs11.writeFile(outputPath, html, "utf8");
|
|
7072
|
-
return path15.resolve(outputPath);
|
|
7073
|
-
}
|
|
7074
|
-
|
|
7075
|
-
// src/schedule.ts
|
|
7076
|
-
import fs12 from "fs/promises";
|
|
7077
|
-
import path16 from "path";
|
|
7078
|
-
function scheduleStatePath(schedulesDir, jobId) {
|
|
7079
|
-
return path16.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
6832
|
+
function scheduleStatePath(schedulesDir, jobId) {
|
|
6833
|
+
return path15.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
7080
6834
|
}
|
|
7081
6835
|
function scheduleLockPath(schedulesDir, jobId) {
|
|
7082
|
-
return
|
|
6836
|
+
return path15.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
|
|
7083
6837
|
}
|
|
7084
6838
|
function parseEveryDuration(value) {
|
|
7085
6839
|
const match = value.trim().match(/^(\d+)(m|h|d)$/i);
|
|
@@ -7182,13 +6936,13 @@ async function acquireJobLease(rootDir, jobId) {
|
|
|
7182
6936
|
const { paths } = await loadVaultConfig(rootDir);
|
|
7183
6937
|
const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
|
|
7184
6938
|
await ensureDir(paths.schedulesDir);
|
|
7185
|
-
const handle = await
|
|
6939
|
+
const handle = await fs11.open(leasePath, "wx");
|
|
7186
6940
|
await handle.writeFile(`${process.pid}
|
|
7187
6941
|
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
7188
6942
|
`);
|
|
7189
6943
|
await handle.close();
|
|
7190
6944
|
return async () => {
|
|
7191
|
-
await
|
|
6945
|
+
await fs11.rm(leasePath, { force: true });
|
|
7192
6946
|
};
|
|
7193
6947
|
}
|
|
7194
6948
|
async function listSchedules(rootDir) {
|
|
@@ -7334,6 +7088,275 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
|
|
|
7334
7088
|
};
|
|
7335
7089
|
}
|
|
7336
7090
|
|
|
7091
|
+
// src/viewer.ts
|
|
7092
|
+
import { execFile } from "child_process";
|
|
7093
|
+
import fs12 from "fs/promises";
|
|
7094
|
+
import http from "http";
|
|
7095
|
+
import path16 from "path";
|
|
7096
|
+
import { promisify } from "util";
|
|
7097
|
+
import matter8 from "gray-matter";
|
|
7098
|
+
import mime2 from "mime-types";
|
|
7099
|
+
var execFileAsync = promisify(execFile);
|
|
7100
|
+
async function readViewerPage(rootDir, relativePath) {
|
|
7101
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
7102
|
+
const absolutePath = path16.resolve(paths.wikiDir, relativePath);
|
|
7103
|
+
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
7104
|
+
return null;
|
|
7105
|
+
}
|
|
7106
|
+
const raw = await fs12.readFile(absolutePath, "utf8");
|
|
7107
|
+
const parsed = matter8(raw);
|
|
7108
|
+
return {
|
|
7109
|
+
path: relativePath,
|
|
7110
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : path16.basename(relativePath, path16.extname(relativePath)),
|
|
7111
|
+
frontmatter: parsed.data,
|
|
7112
|
+
content: parsed.content,
|
|
7113
|
+
assets: normalizeOutputAssets(parsed.data.output_assets)
|
|
7114
|
+
};
|
|
7115
|
+
}
|
|
7116
|
+
async function readViewerAsset(rootDir, relativePath) {
|
|
7117
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
7118
|
+
const absolutePath = path16.resolve(paths.wikiDir, relativePath);
|
|
7119
|
+
if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
|
|
7120
|
+
return null;
|
|
7121
|
+
}
|
|
7122
|
+
return {
|
|
7123
|
+
buffer: await fs12.readFile(absolutePath),
|
|
7124
|
+
mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
|
|
7125
|
+
};
|
|
7126
|
+
}
|
|
7127
|
+
async function assetDataUrl(rootDir, relativePath) {
|
|
7128
|
+
const asset = await readViewerAsset(rootDir, relativePath);
|
|
7129
|
+
if (!asset) {
|
|
7130
|
+
return void 0;
|
|
7131
|
+
}
|
|
7132
|
+
return `data:${asset.mimeType};base64,${asset.buffer.toString("base64")}`;
|
|
7133
|
+
}
|
|
7134
|
+
async function readJsonBody(request) {
|
|
7135
|
+
const chunks = [];
|
|
7136
|
+
for await (const chunk of request) {
|
|
7137
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
7138
|
+
}
|
|
7139
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
7140
|
+
if (!raw) {
|
|
7141
|
+
return {};
|
|
7142
|
+
}
|
|
7143
|
+
return JSON.parse(raw);
|
|
7144
|
+
}
|
|
7145
|
+
async function ensureViewerDist(viewerDistDir) {
|
|
7146
|
+
const indexPath = path16.join(viewerDistDir, "index.html");
|
|
7147
|
+
if (await fileExists(indexPath)) {
|
|
7148
|
+
return;
|
|
7149
|
+
}
|
|
7150
|
+
const viewerProjectDir = path16.dirname(viewerDistDir);
|
|
7151
|
+
if (await fileExists(path16.join(viewerProjectDir, "package.json"))) {
|
|
7152
|
+
await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
|
|
7153
|
+
}
|
|
7154
|
+
}
|
|
7155
|
+
async function startGraphServer(rootDir, port) {
|
|
7156
|
+
const { config, paths } = await loadVaultConfig(rootDir);
|
|
7157
|
+
const effectivePort = port ?? config.viewer.port;
|
|
7158
|
+
await ensureViewerDist(paths.viewerDistDir);
|
|
7159
|
+
const server = http.createServer(async (request, response) => {
|
|
7160
|
+
const url = new URL(request.url ?? "/", `http://${request.headers.host ?? `localhost:${effectivePort}`}`);
|
|
7161
|
+
if (url.pathname === "/api/graph") {
|
|
7162
|
+
if (!await fileExists(paths.graphPath)) {
|
|
7163
|
+
response.writeHead(404, { "content-type": "application/json" });
|
|
7164
|
+
response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
|
|
7165
|
+
return;
|
|
7166
|
+
}
|
|
7167
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
7168
|
+
response.end(await fs12.readFile(paths.graphPath, "utf8"));
|
|
7169
|
+
return;
|
|
7170
|
+
}
|
|
7171
|
+
if (url.pathname === "/api/search") {
|
|
7172
|
+
if (!await fileExists(paths.searchDbPath)) {
|
|
7173
|
+
response.writeHead(404, { "content-type": "application/json" });
|
|
7174
|
+
response.end(JSON.stringify({ error: "Search index not found. Run `swarmvault compile` first." }));
|
|
7175
|
+
return;
|
|
7176
|
+
}
|
|
7177
|
+
const query = url.searchParams.get("q") ?? "";
|
|
7178
|
+
const limit = Number.parseInt(url.searchParams.get("limit") ?? "10", 10);
|
|
7179
|
+
const kind = url.searchParams.get("kind") ?? "all";
|
|
7180
|
+
const status = url.searchParams.get("status") ?? "all";
|
|
7181
|
+
const project = url.searchParams.get("project") ?? "all";
|
|
7182
|
+
const results = searchPages(paths.searchDbPath, query, {
|
|
7183
|
+
limit: Number.isFinite(limit) ? limit : 10,
|
|
7184
|
+
kind,
|
|
7185
|
+
status,
|
|
7186
|
+
project
|
|
7187
|
+
});
|
|
7188
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
7189
|
+
response.end(JSON.stringify(results));
|
|
7190
|
+
return;
|
|
7191
|
+
}
|
|
7192
|
+
if (url.pathname === "/api/page") {
|
|
7193
|
+
const relativePath2 = url.searchParams.get("path") ?? "";
|
|
7194
|
+
const page = await readViewerPage(rootDir, relativePath2);
|
|
7195
|
+
if (!page) {
|
|
7196
|
+
response.writeHead(404, { "content-type": "application/json" });
|
|
7197
|
+
response.end(JSON.stringify({ error: `Page not found: ${relativePath2}` }));
|
|
7198
|
+
return;
|
|
7199
|
+
}
|
|
7200
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
7201
|
+
response.end(JSON.stringify(page));
|
|
7202
|
+
return;
|
|
7203
|
+
}
|
|
7204
|
+
if (url.pathname === "/api/asset") {
|
|
7205
|
+
const relativePath2 = url.searchParams.get("path") ?? "";
|
|
7206
|
+
const asset = await readViewerAsset(rootDir, relativePath2);
|
|
7207
|
+
if (!asset) {
|
|
7208
|
+
response.writeHead(404, { "content-type": "application/json" });
|
|
7209
|
+
response.end(JSON.stringify({ error: `Asset not found: ${relativePath2}` }));
|
|
7210
|
+
return;
|
|
7211
|
+
}
|
|
7212
|
+
response.writeHead(200, { "content-type": asset.mimeType });
|
|
7213
|
+
response.end(asset.buffer);
|
|
7214
|
+
return;
|
|
7215
|
+
}
|
|
7216
|
+
if (url.pathname === "/api/reviews" && request.method === "GET") {
|
|
7217
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
7218
|
+
response.end(JSON.stringify(await listApprovals(rootDir)));
|
|
7219
|
+
return;
|
|
7220
|
+
}
|
|
7221
|
+
if (url.pathname === "/api/review" && request.method === "GET") {
|
|
7222
|
+
const approvalId = url.searchParams.get("id") ?? "";
|
|
7223
|
+
if (!approvalId) {
|
|
7224
|
+
response.writeHead(400, { "content-type": "application/json" });
|
|
7225
|
+
response.end(JSON.stringify({ error: "Missing approval id." }));
|
|
7226
|
+
return;
|
|
7227
|
+
}
|
|
7228
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
7229
|
+
response.end(JSON.stringify(await readApproval(rootDir, approvalId)));
|
|
7230
|
+
return;
|
|
7231
|
+
}
|
|
7232
|
+
if (url.pathname === "/api/review" && request.method === "POST") {
|
|
7233
|
+
const body = await readJsonBody(request);
|
|
7234
|
+
const approvalId = typeof body.approvalId === "string" ? body.approvalId : "";
|
|
7235
|
+
const targets = Array.isArray(body.targets) ? body.targets.filter((item) => typeof item === "string") : [];
|
|
7236
|
+
const action = url.searchParams.get("action") ?? "";
|
|
7237
|
+
if (!approvalId || action !== "accept" && action !== "reject") {
|
|
7238
|
+
response.writeHead(400, { "content-type": "application/json" });
|
|
7239
|
+
response.end(JSON.stringify({ error: "Missing approval id or invalid review action." }));
|
|
7240
|
+
return;
|
|
7241
|
+
}
|
|
7242
|
+
const result = action === "accept" ? await acceptApproval(rootDir, approvalId, targets) : await rejectApproval(rootDir, approvalId, targets);
|
|
7243
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
7244
|
+
response.end(JSON.stringify(result));
|
|
7245
|
+
return;
|
|
7246
|
+
}
|
|
7247
|
+
if (url.pathname === "/api/candidates" && request.method === "GET") {
|
|
7248
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
7249
|
+
response.end(JSON.stringify(await listCandidates(rootDir)));
|
|
7250
|
+
return;
|
|
7251
|
+
}
|
|
7252
|
+
if (url.pathname === "/api/candidate" && request.method === "POST") {
|
|
7253
|
+
const body = await readJsonBody(request);
|
|
7254
|
+
const target2 = typeof body.target === "string" ? body.target : "";
|
|
7255
|
+
const action = url.searchParams.get("action") ?? "";
|
|
7256
|
+
if (!target2 || action !== "promote" && action !== "archive") {
|
|
7257
|
+
response.writeHead(400, { "content-type": "application/json" });
|
|
7258
|
+
response.end(JSON.stringify({ error: "Missing candidate target or invalid candidate action." }));
|
|
7259
|
+
return;
|
|
7260
|
+
}
|
|
7261
|
+
const result = action === "promote" ? await promoteCandidate(rootDir, target2) : await archiveCandidate(rootDir, target2);
|
|
7262
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
7263
|
+
response.end(JSON.stringify(result));
|
|
7264
|
+
return;
|
|
7265
|
+
}
|
|
7266
|
+
const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
7267
|
+
const target = path16.join(paths.viewerDistDir, relativePath);
|
|
7268
|
+
const fallback = path16.join(paths.viewerDistDir, "index.html");
|
|
7269
|
+
const filePath = await fileExists(target) ? target : fallback;
|
|
7270
|
+
if (!await fileExists(filePath)) {
|
|
7271
|
+
response.writeHead(503, { "content-type": "text/plain" });
|
|
7272
|
+
response.end("Viewer build not found. Run `pnpm build` first.");
|
|
7273
|
+
return;
|
|
7274
|
+
}
|
|
7275
|
+
response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
|
|
7276
|
+
response.end(await fs12.readFile(filePath));
|
|
7277
|
+
});
|
|
7278
|
+
await new Promise((resolve) => {
|
|
7279
|
+
server.listen(effectivePort, resolve);
|
|
7280
|
+
});
|
|
7281
|
+
return {
|
|
7282
|
+
port: effectivePort,
|
|
7283
|
+
close: async () => {
|
|
7284
|
+
await new Promise((resolve, reject) => {
|
|
7285
|
+
server.close((error) => {
|
|
7286
|
+
if (error) {
|
|
7287
|
+
reject(error);
|
|
7288
|
+
return;
|
|
7289
|
+
}
|
|
7290
|
+
resolve();
|
|
7291
|
+
});
|
|
7292
|
+
});
|
|
7293
|
+
}
|
|
7294
|
+
};
|
|
7295
|
+
}
|
|
7296
|
+
async function exportGraphHtml(rootDir, outputPath) {
|
|
7297
|
+
const { paths } = await loadVaultConfig(rootDir);
|
|
7298
|
+
const graph = await readJsonFile(paths.graphPath);
|
|
7299
|
+
if (!graph) {
|
|
7300
|
+
throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
|
|
7301
|
+
}
|
|
7302
|
+
await ensureViewerDist(paths.viewerDistDir);
|
|
7303
|
+
const indexPath = path16.join(paths.viewerDistDir, "index.html");
|
|
7304
|
+
if (!await fileExists(indexPath)) {
|
|
7305
|
+
throw new Error("Viewer build not found. Run `pnpm build` first.");
|
|
7306
|
+
}
|
|
7307
|
+
const pages = await Promise.all(
|
|
7308
|
+
graph.pages.map(async (page) => {
|
|
7309
|
+
const loaded = await readViewerPage(rootDir, page.path);
|
|
7310
|
+
return loaded ? {
|
|
7311
|
+
pageId: page.id,
|
|
7312
|
+
path: loaded.path,
|
|
7313
|
+
title: loaded.title,
|
|
7314
|
+
kind: page.kind,
|
|
7315
|
+
status: page.status,
|
|
7316
|
+
projectIds: page.projectIds,
|
|
7317
|
+
content: loaded.content,
|
|
7318
|
+
assets: await Promise.all(
|
|
7319
|
+
loaded.assets.map(async (asset) => ({
|
|
7320
|
+
...asset,
|
|
7321
|
+
dataUrl: await assetDataUrl(rootDir, asset.path)
|
|
7322
|
+
}))
|
|
7323
|
+
)
|
|
7324
|
+
} : null;
|
|
7325
|
+
})
|
|
7326
|
+
);
|
|
7327
|
+
const rawHtml = await fs12.readFile(indexPath, "utf8");
|
|
7328
|
+
const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
|
|
7329
|
+
const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
|
|
7330
|
+
const scriptPath = scriptMatch?.[1] ? path16.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
|
|
7331
|
+
const stylePath = styleMatch?.[1] ? path16.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
|
|
7332
|
+
if (!scriptPath || !await fileExists(scriptPath)) {
|
|
7333
|
+
throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
|
|
7334
|
+
}
|
|
7335
|
+
const script = await fs12.readFile(scriptPath, "utf8");
|
|
7336
|
+
const style = stylePath && await fileExists(stylePath) ? await fs12.readFile(stylePath, "utf8") : "";
|
|
7337
|
+
const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean) }, null, 2).replace(/</g, "\\u003c");
|
|
7338
|
+
const html = [
|
|
7339
|
+
"<!doctype html>",
|
|
7340
|
+
'<html lang="en">',
|
|
7341
|
+
" <head>",
|
|
7342
|
+
' <meta charset="UTF-8" />',
|
|
7343
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1.0" />',
|
|
7344
|
+
" <title>SwarmVault Graph Export</title>",
|
|
7345
|
+
style ? ` <style>${style}</style>` : "",
|
|
7346
|
+
" </head>",
|
|
7347
|
+
" <body>",
|
|
7348
|
+
' <div id="root"></div>',
|
|
7349
|
+
` <script>window.__SWARMVAULT_EMBEDDED_DATA__ = ${embeddedData};</script>`,
|
|
7350
|
+
` <script type="module">${script}</script>`,
|
|
7351
|
+
" </body>",
|
|
7352
|
+
"</html>",
|
|
7353
|
+
""
|
|
7354
|
+
].filter(Boolean).join("\n");
|
|
7355
|
+
await fs12.mkdir(path16.dirname(outputPath), { recursive: true });
|
|
7356
|
+
await fs12.writeFile(outputPath, html, "utf8");
|
|
7357
|
+
return path16.resolve(outputPath);
|
|
7358
|
+
}
|
|
7359
|
+
|
|
7337
7360
|
// src/watch.ts
|
|
7338
7361
|
import path17 from "path";
|
|
7339
7362
|
import process2 from "process";
|