ma-agents 3.15.5 → 3.16.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/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +1 -1
- package/lib/knowledge-atlas/dist/atlas.cjs +135 -54
- package/lib/knowledge-atlas/src/render/document.ts +100 -10
- package/lib/knowledge-atlas/src/render/site.ts +20 -2
- package/lib/knowledge-atlas/src/render/vendor.ts +43 -0
- package/lib/knowledge-atlas/vendor/drawio-viewer.min.js +7563 -0
- package/lib/knowledge-atlas/vendor/mermaid.min.js +2314 -0
- package/package.json +70 -70
|
@@ -2005,11 +2005,11 @@ var require_lunr = __commonJS({
|
|
|
2005
2005
|
});
|
|
2006
2006
|
|
|
2007
2007
|
// lib/knowledge-atlas/src/cli.ts
|
|
2008
|
-
var
|
|
2008
|
+
var path13 = __toESM(require("path"));
|
|
2009
2009
|
|
|
2010
2010
|
// lib/knowledge-atlas/src/generate.ts
|
|
2011
|
-
var
|
|
2012
|
-
var
|
|
2011
|
+
var fs12 = __toESM(require("fs"));
|
|
2012
|
+
var path12 = __toESM(require("path"));
|
|
2013
2013
|
|
|
2014
2014
|
// lib/knowledge-atlas/src/scan/scanner.ts
|
|
2015
2015
|
var fs2 = __toESM(require("fs"));
|
|
@@ -5636,8 +5636,8 @@ function mergeRecords(unchanged, reprocessed, removed) {
|
|
|
5636
5636
|
}
|
|
5637
5637
|
|
|
5638
5638
|
// lib/knowledge-atlas/src/render/site.ts
|
|
5639
|
-
var
|
|
5640
|
-
var
|
|
5639
|
+
var fs8 = __toESM(require("fs"));
|
|
5640
|
+
var path8 = __toESM(require("path"));
|
|
5641
5641
|
|
|
5642
5642
|
// lib/knowledge-atlas/src/render/design-system.ts
|
|
5643
5643
|
var DESIGN_SYSTEM_CSS = `/* Knowledge Atlas \u2014 design system (Story 29.3) */
|
|
@@ -12098,8 +12098,7 @@ function processMermaidFences(html) {
|
|
|
12098
12098
|
hasMermaid = true;
|
|
12099
12099
|
return [
|
|
12100
12100
|
'<figure class="mermaid-block" aria-label="Mermaid diagram">',
|
|
12101
|
-
|
|
12102
|
-
`<div class="mermaid"><pre class="mermaid-fallback">${codeContent.trim()}</pre></div>`,
|
|
12101
|
+
`<pre class="mermaid">${codeContent.trim()}</pre>`,
|
|
12103
12102
|
"</figure>"
|
|
12104
12103
|
].join("\n");
|
|
12105
12104
|
});
|
|
@@ -12119,6 +12118,9 @@ function getFileNodes(fileId, nodes) {
|
|
|
12119
12118
|
function renderDocumentPage(fileRec, manifest, projectRoot, navFlags, resolveAbsPath) {
|
|
12120
12119
|
const absPath = resolveAbsPath?.(fileRec.path) ?? path6.join(projectRoot, fileRec.path);
|
|
12121
12120
|
const title = fileTitle(fileRec.path);
|
|
12121
|
+
if (/\.drawio(\.xml)?$/i.test(fileRec.path)) {
|
|
12122
|
+
return renderDrawioPage(fileRec, absPath, title, manifest, navFlags);
|
|
12123
|
+
}
|
|
12122
12124
|
let rawContent = "";
|
|
12123
12125
|
try {
|
|
12124
12126
|
rawContent = fs6.readFileSync(absPath, "utf-8");
|
|
@@ -12139,7 +12141,7 @@ function renderDocumentPage(fileRec, manifest, projectRoot, navFlags, resolveAbs
|
|
|
12139
12141
|
const tocEntries = extractToc(markdownContent);
|
|
12140
12142
|
let renderedHtml = md.render(markdownContent);
|
|
12141
12143
|
renderedHtml = addHeadingAnchors(renderedHtml);
|
|
12142
|
-
const { html: mermaidHtml } = processMermaidFences(renderedHtml);
|
|
12144
|
+
const { html: mermaidHtml, hasMermaid } = processMermaidFences(renderedHtml);
|
|
12143
12145
|
renderedHtml = mermaidHtml;
|
|
12144
12146
|
renderedHtml = rewriteAuthoredLinks(renderedHtml, manifest, fileRec.path);
|
|
12145
12147
|
renderedHtml = linkifyHtml(renderedHtml, manifest, fileRec.path);
|
|
@@ -12188,16 +12190,83 @@ function renderDocumentPage(fileRec, manifest, projectRoot, navFlags, resolveAbs
|
|
|
12188
12190
|
"</article>",
|
|
12189
12191
|
"</div>"
|
|
12190
12192
|
].filter(Boolean).join("\n");
|
|
12193
|
+
const scripts = [];
|
|
12194
|
+
if (hasMermaid) {
|
|
12195
|
+
scripts.push('<script src="../assets/mermaid.min.js"></script>');
|
|
12196
|
+
scripts.push(
|
|
12197
|
+
'<script>try{mermaid.initialize({startOnLoad:true,securityLevel:"strict",theme:"neutral"});}catch(e){}</script>'
|
|
12198
|
+
);
|
|
12199
|
+
}
|
|
12191
12200
|
const html = pageShell({
|
|
12192
12201
|
title,
|
|
12193
12202
|
body: pageBody,
|
|
12194
12203
|
manifest,
|
|
12195
12204
|
pageType: "document",
|
|
12196
12205
|
baseHref: "../",
|
|
12197
|
-
navFlags
|
|
12206
|
+
navFlags,
|
|
12207
|
+
scripts
|
|
12198
12208
|
});
|
|
12199
12209
|
const articleText = `${title} ${renderedHtml}`.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
12200
|
-
return { html, articleText };
|
|
12210
|
+
return { html, articleText, usedMermaid: hasMermaid, usedDrawio: false };
|
|
12211
|
+
}
|
|
12212
|
+
function renderDrawioPage(fileRec, absPath, title, manifest, navFlags) {
|
|
12213
|
+
let xml = "";
|
|
12214
|
+
try {
|
|
12215
|
+
xml = fs6.readFileSync(absPath, "utf-8").replace(/\r\n/g, "\n");
|
|
12216
|
+
} catch {
|
|
12217
|
+
xml = "";
|
|
12218
|
+
}
|
|
12219
|
+
const config2 = { highlight: "#3572b0", nav: true, resize: true, "toolbar-nohide": true, toolbar: "zoom layers", xml };
|
|
12220
|
+
const dataAttr = esc4(JSON.stringify(config2));
|
|
12221
|
+
const body = [
|
|
12222
|
+
'<div class="content-wrap">',
|
|
12223
|
+
'<a class="back-link" href="../index.html">← Back to Home</a>',
|
|
12224
|
+
'<article class="doc-body">',
|
|
12225
|
+
`<h1>${esc4(title)}</h1>`,
|
|
12226
|
+
'<figure class="drawio-block" aria-label="draw.io diagram">',
|
|
12227
|
+
xml ? `<div class="mxgraph" style="max-width:100%;border:1px solid var(--border,#ddd);border-radius:6px" data-mxgraph="${dataAttr}"></div>` : "<p><em>(draw.io file could not be read.)</em></p>",
|
|
12228
|
+
'<noscript><p class="mermaid-note">This is a draw.io diagram; enable JavaScript to render it.</p></noscript>',
|
|
12229
|
+
"</figure>",
|
|
12230
|
+
"</article>",
|
|
12231
|
+
"</div>"
|
|
12232
|
+
].filter(Boolean).join("\n");
|
|
12233
|
+
const scripts = [
|
|
12234
|
+
'<script>window.DRAWIO_BASE_URL=".";window.PROXY_URL=".";window.STYLE_PATH=".";window.RESOURCE_BASE=".";window.SHAPES_PATH=".";window.STENCIL_PATH=".";window.IMAGE_PATH=".";</script>',
|
|
12235
|
+
'<script src="../assets/drawio-viewer.min.js"></script>'
|
|
12236
|
+
];
|
|
12237
|
+
const html = pageShell({
|
|
12238
|
+
title,
|
|
12239
|
+
body,
|
|
12240
|
+
manifest,
|
|
12241
|
+
pageType: "document",
|
|
12242
|
+
baseHref: "../",
|
|
12243
|
+
navFlags,
|
|
12244
|
+
scripts
|
|
12245
|
+
});
|
|
12246
|
+
return { html, articleText: title, usedMermaid: false, usedDrawio: true };
|
|
12247
|
+
}
|
|
12248
|
+
|
|
12249
|
+
// lib/knowledge-atlas/src/render/vendor.ts
|
|
12250
|
+
var fs7 = __toESM(require("fs"));
|
|
12251
|
+
var path7 = __toESM(require("path"));
|
|
12252
|
+
function vendorDir() {
|
|
12253
|
+
return path7.join(__dirname, "..", "vendor");
|
|
12254
|
+
}
|
|
12255
|
+
function readVendorAsset(name) {
|
|
12256
|
+
const p = path7.join(vendorDir(), name);
|
|
12257
|
+
try {
|
|
12258
|
+
return fs7.readFileSync(p, "utf-8");
|
|
12259
|
+
} catch {
|
|
12260
|
+
return null;
|
|
12261
|
+
}
|
|
12262
|
+
}
|
|
12263
|
+
function writeVendorAsset(outDir, name) {
|
|
12264
|
+
const content = readVendorAsset(name);
|
|
12265
|
+
if (content == null) return null;
|
|
12266
|
+
const assetsDir = path7.join(outDir, "assets");
|
|
12267
|
+
fs7.mkdirSync(assetsDir, { recursive: true });
|
|
12268
|
+
fs7.writeFileSync(path7.join(assetsDir, name), content.replace(/\r\n/g, "\n"), { encoding: "utf-8" });
|
|
12269
|
+
return `assets/${name}`;
|
|
12201
12270
|
}
|
|
12202
12271
|
|
|
12203
12272
|
// lib/knowledge-atlas/src/render/search.ts
|
|
@@ -12410,8 +12479,8 @@ function esc6(s) {
|
|
|
12410
12479
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
12411
12480
|
}
|
|
12412
12481
|
function writeHtml(filePath, content) {
|
|
12413
|
-
|
|
12414
|
-
|
|
12482
|
+
fs8.mkdirSync(path8.dirname(filePath), { recursive: true });
|
|
12483
|
+
fs8.writeFileSync(filePath, content.replace(/\r\n/g, "\n"), { encoding: "utf-8" });
|
|
12415
12484
|
}
|
|
12416
12485
|
function renderHomePage(manifest, navFlags) {
|
|
12417
12486
|
const { projectTitle, generatedAt, counts, nodes, edges } = manifest;
|
|
@@ -12548,35 +12617,47 @@ function renderHomePage(manifest, navFlags) {
|
|
|
12548
12617
|
function renderSite(manifest, outDir, resolveAbsPath) {
|
|
12549
12618
|
const written = [];
|
|
12550
12619
|
const docBodies = /* @__PURE__ */ new Map();
|
|
12551
|
-
|
|
12552
|
-
|
|
12620
|
+
fs8.mkdirSync(outDir, { recursive: true });
|
|
12621
|
+
fs8.mkdirSync(path8.join(outDir, "docs"), { recursive: true });
|
|
12553
12622
|
const fileIds = manifest.files.map((f) => f.path);
|
|
12554
12623
|
const slugMap = buildSlugMap(fileIds);
|
|
12555
12624
|
const navFlags = {
|
|
12556
|
-
hasGraphPage:
|
|
12557
|
-
hasDiagramsPage:
|
|
12558
|
-
hasOrphansPage:
|
|
12625
|
+
hasGraphPage: fs8.existsSync(path8.join(outDir, "graph.html")),
|
|
12626
|
+
hasDiagramsPage: fs8.existsSync(path8.join(outDir, "diagrams.html")),
|
|
12627
|
+
hasOrphansPage: fs8.existsSync(path8.join(outDir, "orphans.html"))
|
|
12559
12628
|
};
|
|
12629
|
+
let anyMermaid = false;
|
|
12630
|
+
let anyDrawio = false;
|
|
12560
12631
|
for (const file of manifest.files) {
|
|
12561
12632
|
const slug = slugMap.get(file.path) ?? filePathToSlug(file.path);
|
|
12562
|
-
const { html, articleText } = renderDocumentPage(file, manifest, manifest.projectRoot, navFlags, resolveAbsPath);
|
|
12633
|
+
const { html, articleText, usedMermaid, usedDrawio } = renderDocumentPage(file, manifest, manifest.projectRoot, navFlags, resolveAbsPath);
|
|
12563
12634
|
const relPath = `docs/${slug}.html`;
|
|
12564
|
-
writeHtml(
|
|
12635
|
+
writeHtml(path8.join(outDir, relPath), html);
|
|
12565
12636
|
written.push(relPath);
|
|
12637
|
+
if (usedMermaid) anyMermaid = true;
|
|
12638
|
+
if (usedDrawio) anyDrawio = true;
|
|
12566
12639
|
docBodies.set(file.path, articleText);
|
|
12567
12640
|
}
|
|
12641
|
+
if (anyMermaid) {
|
|
12642
|
+
const rel = writeVendorAsset(outDir, "mermaid.min.js");
|
|
12643
|
+
if (rel) written.push(rel);
|
|
12644
|
+
}
|
|
12645
|
+
if (anyDrawio) {
|
|
12646
|
+
const rel = writeVendorAsset(outDir, "drawio-viewer.min.js");
|
|
12647
|
+
if (rel) written.push(rel);
|
|
12648
|
+
}
|
|
12568
12649
|
const searchHtml = renderSearchPageHtml(manifest, docBodies, navFlags);
|
|
12569
|
-
writeHtml(
|
|
12650
|
+
writeHtml(path8.join(outDir, "search.html"), searchHtml);
|
|
12570
12651
|
written.push("search.html");
|
|
12571
12652
|
const indexHtml = renderHomePage(manifest, navFlags);
|
|
12572
|
-
writeHtml(
|
|
12653
|
+
writeHtml(path8.join(outDir, "index.html"), indexHtml);
|
|
12573
12654
|
written.push("index.html");
|
|
12574
12655
|
return written.sort();
|
|
12575
12656
|
}
|
|
12576
12657
|
|
|
12577
12658
|
// lib/knowledge-atlas/src/render/graph.ts
|
|
12578
|
-
var
|
|
12579
|
-
var
|
|
12659
|
+
var fs9 = __toESM(require("fs"));
|
|
12660
|
+
var path9 = __toESM(require("path"));
|
|
12580
12661
|
|
|
12581
12662
|
// lib/knowledge-atlas/src/render/force-graph-umd.ts
|
|
12582
12663
|
var FORCE_GRAPH_UMD = `// Version 1.51.4 force-graph - https://github.com/vasturiano/force-graph
|
|
@@ -13039,9 +13120,9 @@ function renderGraphPage(manifest, outDir) {
|
|
|
13039
13120
|
""
|
|
13040
13121
|
// trailing newline
|
|
13041
13122
|
].join("\n");
|
|
13042
|
-
const outPath =
|
|
13043
|
-
|
|
13044
|
-
|
|
13123
|
+
const outPath = path9.join(outDir, "graph.html");
|
|
13124
|
+
fs9.mkdirSync(outDir, { recursive: true });
|
|
13125
|
+
fs9.writeFileSync(outPath, html.replace(/\r\n/g, "\n"), { encoding: "utf-8" });
|
|
13045
13126
|
return ["graph.html"];
|
|
13046
13127
|
}
|
|
13047
13128
|
function buildAppScript() {
|
|
@@ -13351,8 +13432,8 @@ function buildAppScript() {
|
|
|
13351
13432
|
}
|
|
13352
13433
|
|
|
13353
13434
|
// lib/knowledge-atlas/src/render/diagrams.ts
|
|
13354
|
-
var
|
|
13355
|
-
var
|
|
13435
|
+
var fs10 = __toESM(require("fs"));
|
|
13436
|
+
var path10 = __toESM(require("path"));
|
|
13356
13437
|
function esc8(s) {
|
|
13357
13438
|
return (s ?? "").toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13358
13439
|
}
|
|
@@ -13919,15 +14000,15 @@ function renderDiagrams(manifest, outDir) {
|
|
|
13919
14000
|
""
|
|
13920
14001
|
// trailing newline
|
|
13921
14002
|
].join("\n");
|
|
13922
|
-
const outPath =
|
|
13923
|
-
|
|
13924
|
-
|
|
14003
|
+
const outPath = path10.join(outDir, "diagrams.html");
|
|
14004
|
+
fs10.mkdirSync(outDir, { recursive: true });
|
|
14005
|
+
fs10.writeFileSync(outPath, html.replace(/\r\n/g, "\n"), { encoding: "utf-8" });
|
|
13925
14006
|
return ["diagrams.html"];
|
|
13926
14007
|
}
|
|
13927
14008
|
|
|
13928
14009
|
// lib/knowledge-atlas/src/render/orphan.ts
|
|
13929
|
-
var
|
|
13930
|
-
var
|
|
14010
|
+
var fs11 = __toESM(require("fs"));
|
|
14011
|
+
var path11 = __toESM(require("path"));
|
|
13931
14012
|
function esc9(s) {
|
|
13932
14013
|
return (s ?? "").toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13933
14014
|
}
|
|
@@ -14289,16 +14370,16 @@ function renderOrphanPage(manifest, outDir) {
|
|
|
14289
14370
|
""
|
|
14290
14371
|
// trailing newline
|
|
14291
14372
|
].join("\n");
|
|
14292
|
-
const outPath =
|
|
14293
|
-
|
|
14294
|
-
|
|
14373
|
+
const outPath = path11.join(outDir, "orphans.html");
|
|
14374
|
+
fs11.mkdirSync(outDir, { recursive: true });
|
|
14375
|
+
fs11.writeFileSync(outPath, html.replace(/\r\n/g, "\n"), { encoding: "utf-8" });
|
|
14295
14376
|
return ["orphans.html"];
|
|
14296
14377
|
}
|
|
14297
14378
|
|
|
14298
14379
|
// lib/knowledge-atlas/src/generate.ts
|
|
14299
14380
|
var MANIFEST_VERSION = 3;
|
|
14300
14381
|
function normPath(p) {
|
|
14301
|
-
return
|
|
14382
|
+
return path12.resolve(p).replace(/[\\/]+$/, "");
|
|
14302
14383
|
}
|
|
14303
14384
|
function pathsEqual(a, b) {
|
|
14304
14385
|
return normPath(a) === normPath(b);
|
|
@@ -14306,7 +14387,7 @@ function pathsEqual(a, b) {
|
|
|
14306
14387
|
function isUnder(parent, child) {
|
|
14307
14388
|
const p = normPath(parent);
|
|
14308
14389
|
const c = normPath(child);
|
|
14309
|
-
return c.startsWith(p +
|
|
14390
|
+
return c.startsWith(p + path12.sep);
|
|
14310
14391
|
}
|
|
14311
14392
|
var MAX_TIME_VALUE_MS = 864e13;
|
|
14312
14393
|
function resolveTimestamp(now) {
|
|
@@ -14347,10 +14428,10 @@ function isoSecond(d) {
|
|
|
14347
14428
|
return d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
14348
14429
|
}
|
|
14349
14430
|
function deriveProjectTitle(projectRoot) {
|
|
14350
|
-
const pkgPath =
|
|
14431
|
+
const pkgPath = path12.join(projectRoot, "package.json");
|
|
14351
14432
|
try {
|
|
14352
|
-
if (
|
|
14353
|
-
const raw =
|
|
14433
|
+
if (fs12.existsSync(pkgPath)) {
|
|
14434
|
+
const raw = fs12.readFileSync(pkgPath, "utf-8");
|
|
14354
14435
|
const pkg = JSON.parse(raw);
|
|
14355
14436
|
if (typeof pkg["name"] === "string" && pkg["name"].trim()) {
|
|
14356
14437
|
return pkg["name"].trim();
|
|
@@ -14358,13 +14439,13 @@ function deriveProjectTitle(projectRoot) {
|
|
|
14358
14439
|
}
|
|
14359
14440
|
} catch {
|
|
14360
14441
|
}
|
|
14361
|
-
const base =
|
|
14442
|
+
const base = path12.basename(projectRoot).trim();
|
|
14362
14443
|
return base.length > 0 ? base : "Unknown Project";
|
|
14363
14444
|
}
|
|
14364
14445
|
function extractFileRecord(absPath, fileId, docType, contentHash2) {
|
|
14365
14446
|
let content = "";
|
|
14366
14447
|
try {
|
|
14367
|
-
content =
|
|
14448
|
+
content = fs12.readFileSync(absPath, "utf-8");
|
|
14368
14449
|
} catch {
|
|
14369
14450
|
return { path: fileId, contentHash: contentHash2, type: docType, entities: [], statuses: [], edges: [] };
|
|
14370
14451
|
}
|
|
@@ -14420,19 +14501,19 @@ function generate(opts) {
|
|
|
14420
14501
|
if (!projectRoot || typeof projectRoot !== "string") {
|
|
14421
14502
|
throw new Error("generate: projectRoot is required and must be a non-empty string");
|
|
14422
14503
|
}
|
|
14423
|
-
if (!
|
|
14504
|
+
if (!path12.isAbsolute(projectRoot)) {
|
|
14424
14505
|
throw new Error(`generate: projectRoot must be an absolute path (got: ${projectRoot})`);
|
|
14425
14506
|
}
|
|
14426
|
-
const rootStat =
|
|
14507
|
+
const rootStat = fs12.statSync(projectRoot, { throwIfNoEntry: false });
|
|
14427
14508
|
if (!rootStat || !rootStat.isDirectory()) {
|
|
14428
14509
|
throw new Error(`generate: projectRoot does not exist or is not a directory: ${projectRoot}`);
|
|
14429
14510
|
}
|
|
14430
14511
|
const { sources, outputFolder, outputOverlap } = resolveKnowledgeSources(projectRoot);
|
|
14431
|
-
const bmadOutputDir =
|
|
14432
|
-
const defaultOutDir =
|
|
14512
|
+
const bmadOutputDir = path12.join(projectRoot, outputFolder);
|
|
14513
|
+
const defaultOutDir = path12.join(bmadOutputDir, "knowledge-site");
|
|
14433
14514
|
const outDir = outDirOverride ?? defaultOutDir;
|
|
14434
|
-
const rel =
|
|
14435
|
-
if (rel.startsWith("..") ||
|
|
14515
|
+
const rel = path12.relative(projectRoot, outDir);
|
|
14516
|
+
if (rel.startsWith("..") || path12.isAbsolute(rel)) {
|
|
14436
14517
|
throw new Error(`generate: outDir escapes projectRoot: ${outDir}`);
|
|
14437
14518
|
}
|
|
14438
14519
|
const generatedAt = resolveTimestamp(opts.now);
|
|
@@ -14443,14 +14524,14 @@ function generate(opts) {
|
|
|
14443
14524
|
const isDefaultOut = pathsEqual(outDir, defaultOutDir);
|
|
14444
14525
|
const sourceAtOrUnderOut = sources.some((s) => pathsEqual(s.absPath, outDir) || isUnder(outDir, s.absPath));
|
|
14445
14526
|
if (isDefaultOut && !outputOverlap && !sourceAtOrUnderOut) {
|
|
14446
|
-
|
|
14527
|
+
fs12.rmSync(outDir, { recursive: true, force: true });
|
|
14447
14528
|
} else if (isDefaultOut && (outputOverlap || sourceAtOrUnderOut)) {
|
|
14448
14529
|
process.stderr.write(
|
|
14449
14530
|
`[atlas] WARN: refusing to purge ${outDir} \u2014 a knowledge source resolves at or under it (data-loss guard)
|
|
14450
14531
|
`
|
|
14451
14532
|
);
|
|
14452
14533
|
}
|
|
14453
|
-
|
|
14534
|
+
fs12.mkdirSync(outDir, { recursive: true });
|
|
14454
14535
|
const safeRender = (label, fn) => {
|
|
14455
14536
|
try {
|
|
14456
14537
|
return fn();
|
|
@@ -14467,7 +14548,7 @@ function generate(opts) {
|
|
|
14467
14548
|
const extraFileCount = graphFiles.length + diagramFiles.length + orphanFiles.length;
|
|
14468
14549
|
const written = renderSite(manifest, outDir, resolveAbsPath);
|
|
14469
14550
|
writeManifest(outDir, manifest);
|
|
14470
|
-
const indexPath =
|
|
14551
|
+
const indexPath = path12.join(outDir, "index.html");
|
|
14471
14552
|
return {
|
|
14472
14553
|
outDir,
|
|
14473
14554
|
indexPath,
|
|
@@ -14488,7 +14569,7 @@ function main() {
|
|
|
14488
14569
|
usage();
|
|
14489
14570
|
process.exit(args.length === 0 ? 1 : 0);
|
|
14490
14571
|
}
|
|
14491
|
-
const projectRoot =
|
|
14572
|
+
const projectRoot = path13.resolve(args[0]);
|
|
14492
14573
|
let outDir;
|
|
14493
14574
|
const outIdx = args.indexOf("--out");
|
|
14494
14575
|
if (outIdx !== -1) {
|
|
@@ -14496,7 +14577,7 @@ function main() {
|
|
|
14496
14577
|
process.stderr.write("[atlas] ERROR: --out requires a directory argument\n");
|
|
14497
14578
|
process.exit(1);
|
|
14498
14579
|
}
|
|
14499
|
-
outDir =
|
|
14580
|
+
outDir = path13.resolve(args[outIdx + 1]);
|
|
14500
14581
|
}
|
|
14501
14582
|
try {
|
|
14502
14583
|
const result = generate({ projectRoot, outDir });
|
|
@@ -151,12 +151,16 @@ function processMermaidFences(html: string): { html: string; hasMermaid: boolean
|
|
|
151
151
|
const fenceRe = /<pre><code class="language-mermaid">([\s\S]*?)<\/code><\/pre>/gi;
|
|
152
152
|
const result = html.replace(fenceRe, (_match, codeContent: string) => {
|
|
153
153
|
hasMermaid = true;
|
|
154
|
-
//
|
|
155
|
-
// (
|
|
154
|
+
// `<pre class="mermaid">` is mermaid's default render target: the vendored
|
|
155
|
+
// runtime (assets/mermaid.min.js, injected by the shell when hasMermaid) reads
|
|
156
|
+
// the element's textContent — the HTML entities markdown-it emitted decode
|
|
157
|
+
// back to the literal diagram source — and replaces it with rendered SVG.
|
|
158
|
+
// With no JS the source stays legible (the plain-text degradation fallback).
|
|
159
|
+
// linkify skips <pre>, and sanitize preserves `<pre class="…">`, so the
|
|
160
|
+
// diagram source reaches the browser uncorrupted (no injected <a> tags).
|
|
156
161
|
return [
|
|
157
162
|
'<figure class="mermaid-block" aria-label="Mermaid diagram">',
|
|
158
|
-
|
|
159
|
-
`<div class="mermaid"><pre class="mermaid-fallback">${codeContent.trim()}</pre></div>`,
|
|
163
|
+
`<pre class="mermaid">${codeContent.trim()}</pre>`,
|
|
160
164
|
'</figure>',
|
|
161
165
|
].join('\n');
|
|
162
166
|
});
|
|
@@ -218,6 +222,10 @@ export interface RenderedDocument {
|
|
|
218
222
|
html: string;
|
|
219
223
|
/** Plain-text of the rendered article body only (for the search index). */
|
|
220
224
|
articleText: string;
|
|
225
|
+
/** True if this page contains a mermaid diagram (needs the vendored runtime). */
|
|
226
|
+
usedMermaid: boolean;
|
|
227
|
+
/** True if this page is a rendered .drawio diagram (needs the vendored viewer). */
|
|
228
|
+
usedDrawio: boolean;
|
|
221
229
|
}
|
|
222
230
|
|
|
223
231
|
export function renderDocumentPage(
|
|
@@ -230,6 +238,12 @@ export function renderDocumentPage(
|
|
|
230
238
|
const absPath = resolveAbsPath?.(fileRec.path) ?? path.join(projectRoot, fileRec.path);
|
|
231
239
|
const title = fileTitle(fileRec.path);
|
|
232
240
|
|
|
241
|
+
// draw.io files are mxGraph XML, not markdown — render them via the vendored
|
|
242
|
+
// GraphViewer instead of the markdown pipeline (which would strip the XML).
|
|
243
|
+
if (/\.drawio(\.xml)?$/i.test(fileRec.path)) {
|
|
244
|
+
return renderDrawioPage(fileRec, absPath, title, manifest, navFlags);
|
|
245
|
+
}
|
|
246
|
+
|
|
233
247
|
// Read markdown content
|
|
234
248
|
let rawContent = '';
|
|
235
249
|
try {
|
|
@@ -258,8 +272,8 @@ export function renderDocumentPage(
|
|
|
258
272
|
// Add heading anchors for in-page navigation
|
|
259
273
|
renderedHtml = addHeadingAnchors(renderedHtml);
|
|
260
274
|
|
|
261
|
-
// Process mermaid fences
|
|
262
|
-
const { html: mermaidHtml } = processMermaidFences(renderedHtml);
|
|
275
|
+
// Process mermaid fences → live `<pre class="mermaid">` render targets.
|
|
276
|
+
const { html: mermaidHtml, hasMermaid } = processMermaidFences(renderedHtml);
|
|
263
277
|
renderedHtml = mermaidHtml;
|
|
264
278
|
|
|
265
279
|
// Rewrite AUTHORED relative markdown cross-links (e.g. `[x](roadmap.md)`)
|
|
@@ -354,9 +368,19 @@ export function renderDocumentPage(
|
|
|
354
368
|
'</div>',
|
|
355
369
|
].filter(Boolean).join('\n');
|
|
356
370
|
|
|
357
|
-
// Reading
|
|
358
|
-
//
|
|
359
|
-
//
|
|
371
|
+
// Reading content is legible with zero JS (progressive-enhancement baseline,
|
|
372
|
+
// AC 7): all links are static HTML and a mermaid figure degrades to its
|
|
373
|
+
// legible source. When the page HAS a mermaid diagram we additionally inject
|
|
374
|
+
// the vendored runtime so it renders to SVG — this is the only script on a
|
|
375
|
+
// doc page, and only on pages that need it. Doc pages live under docs/, so
|
|
376
|
+
// nav/search/asset links need a "../" baseHref.
|
|
377
|
+
const scripts: string[] = [];
|
|
378
|
+
if (hasMermaid) {
|
|
379
|
+
scripts.push('<script src="../assets/mermaid.min.js"></script>');
|
|
380
|
+
scripts.push(
|
|
381
|
+
'<script>try{mermaid.initialize({startOnLoad:true,securityLevel:"strict",theme:"neutral"});}catch(e){}</script>',
|
|
382
|
+
);
|
|
383
|
+
}
|
|
360
384
|
const html = pageShell({
|
|
361
385
|
title,
|
|
362
386
|
body: pageBody,
|
|
@@ -364,6 +388,7 @@ export function renderDocumentPage(
|
|
|
364
388
|
pageType: 'document',
|
|
365
389
|
baseHref: '../',
|
|
366
390
|
navFlags,
|
|
391
|
+
scripts,
|
|
367
392
|
});
|
|
368
393
|
|
|
369
394
|
// Article-only plain text for the search index (SF-1): strip tags from the
|
|
@@ -374,5 +399,70 @@ export function renderDocumentPage(
|
|
|
374
399
|
.replace(/\s+/g, ' ')
|
|
375
400
|
.trim();
|
|
376
401
|
|
|
377
|
-
return { html, articleText };
|
|
402
|
+
return { html, articleText, usedMermaid: hasMermaid, usedDrawio: false };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Render a draw.io (`.drawio`) file as a live diagram. draw.io files are
|
|
407
|
+
* mxGraph XML, not markdown — running them through the markdown/sanitize
|
|
408
|
+
* pipeline strips every mxGraph tag and yields a blank page. Instead we embed
|
|
409
|
+
* the raw XML in a draw.io GraphViewer element (`<div class="mxgraph">`) and
|
|
410
|
+
* load the vendored, URL-neutralized viewer, which renders it to SVG entirely
|
|
411
|
+
* offline. With no JS the page shows a short note + a link to the source file.
|
|
412
|
+
*/
|
|
413
|
+
function renderDrawioPage(
|
|
414
|
+
fileRec: FileRecord,
|
|
415
|
+
absPath: string,
|
|
416
|
+
title: string,
|
|
417
|
+
manifest: KnowledgeManifest,
|
|
418
|
+
navFlags?: NavFlags,
|
|
419
|
+
): RenderedDocument {
|
|
420
|
+
let xml = '';
|
|
421
|
+
try {
|
|
422
|
+
xml = fs.readFileSync(absPath, 'utf-8').replace(/\r\n/g, '\n');
|
|
423
|
+
} catch {
|
|
424
|
+
xml = '';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// GraphViewer reads a JSON config from data-mxgraph; `xml` carries the source.
|
|
428
|
+
// esc() HTML-escapes the JSON so the attribute is well-formed; the viewer
|
|
429
|
+
// HTML-decodes then JSON-parses it back to the original XML.
|
|
430
|
+
const config = { highlight: '#3572b0', nav: true, resize: true, 'toolbar-nohide': true, toolbar: 'zoom layers', xml };
|
|
431
|
+
const dataAttr = esc(JSON.stringify(config));
|
|
432
|
+
|
|
433
|
+
const body = [
|
|
434
|
+
'<div class="content-wrap">',
|
|
435
|
+
'<a class="back-link" href="../index.html">← Back to Home</a>',
|
|
436
|
+
'<article class="doc-body">',
|
|
437
|
+
`<h1>${esc(title)}</h1>`,
|
|
438
|
+
'<figure class="drawio-block" aria-label="draw.io diagram">',
|
|
439
|
+
xml
|
|
440
|
+
? `<div class="mxgraph" style="max-width:100%;border:1px solid var(--border,#ddd);border-radius:6px" data-mxgraph="${dataAttr}"></div>`
|
|
441
|
+
: '<p><em>(draw.io file could not be read.)</em></p>',
|
|
442
|
+
'<noscript><p class="mermaid-note">This is a draw.io diagram; enable JavaScript to render it.</p></noscript>',
|
|
443
|
+
'</figure>',
|
|
444
|
+
'</article>',
|
|
445
|
+
'</div>',
|
|
446
|
+
].filter(Boolean).join('\n');
|
|
447
|
+
|
|
448
|
+
// Predefine the viewer's config globals to inert local values so it never
|
|
449
|
+
// reaches for a network endpoint (the vendored file's diagrams.net/font/icon
|
|
450
|
+
// URLs are already neutralized; this belt-and-suspenders covers the `||`
|
|
451
|
+
// fallbacks, which need a TRUTHY value — "" would fall through to the URL).
|
|
452
|
+
const scripts = [
|
|
453
|
+
'<script>window.DRAWIO_BASE_URL=".";window.PROXY_URL=".";window.STYLE_PATH=".";window.RESOURCE_BASE=".";window.SHAPES_PATH=".";window.STENCIL_PATH=".";window.IMAGE_PATH=".";</script>',
|
|
454
|
+
'<script src="../assets/drawio-viewer.min.js"></script>',
|
|
455
|
+
];
|
|
456
|
+
|
|
457
|
+
const html = pageShell({
|
|
458
|
+
title,
|
|
459
|
+
body,
|
|
460
|
+
manifest,
|
|
461
|
+
pageType: 'document',
|
|
462
|
+
baseHref: '../',
|
|
463
|
+
navFlags,
|
|
464
|
+
scripts,
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
return { html, articleText: title, usedMermaid: false, usedDrawio: true };
|
|
378
468
|
}
|
|
@@ -28,6 +28,7 @@ import * as path from 'path';
|
|
|
28
28
|
import { pageShell } from './layout.js';
|
|
29
29
|
import type { NavFlags } from './layout.js';
|
|
30
30
|
import { renderDocumentPage } from './document.js';
|
|
31
|
+
import { writeVendorAsset } from './vendor.js';
|
|
31
32
|
import { renderSearchPageHtml } from './search.js';
|
|
32
33
|
import { filePathToSlug, buildSlugMap } from './paths.js';
|
|
33
34
|
import type { KnowledgeManifest, FileRecord } from '../types.js';
|
|
@@ -289,19 +290,36 @@ export function renderSite(
|
|
|
289
290
|
hasOrphansPage: fs.existsSync(path.join(outDir, 'orphans.html')),
|
|
290
291
|
};
|
|
291
292
|
|
|
292
|
-
// Step 1: Render document pages (reading views).
|
|
293
|
+
// Step 1: Render document pages (reading views). Legible with zero JS; pages
|
|
294
|
+
// that contain a mermaid diagram additionally load the vendored runtime.
|
|
295
|
+
let anyMermaid = false;
|
|
296
|
+
let anyDrawio = false;
|
|
293
297
|
for (const file of manifest.files) {
|
|
294
298
|
const slug = slugMap.get(file.path) ?? filePathToSlug(file.path);
|
|
295
|
-
const { html, articleText } = renderDocumentPage(file, manifest, manifest.projectRoot, navFlags, resolveAbsPath);
|
|
299
|
+
const { html, articleText, usedMermaid, usedDrawio } = renderDocumentPage(file, manifest, manifest.projectRoot, navFlags, resolveAbsPath);
|
|
296
300
|
const relPath = `docs/${slug}.html`;
|
|
297
301
|
writeHtml(path.join(outDir, relPath), html);
|
|
298
302
|
written.push(relPath);
|
|
303
|
+
if (usedMermaid) anyMermaid = true;
|
|
304
|
+
if (usedDrawio) anyDrawio = true;
|
|
299
305
|
|
|
300
306
|
// Index ONLY the article text (no nav/footer/TOC/status chrome), so search
|
|
301
307
|
// hits reflect document content rather than shell boilerplate.
|
|
302
308
|
docBodies.set(file.path, articleText);
|
|
303
309
|
}
|
|
304
310
|
|
|
311
|
+
// Step 1b: Copy the vendored browser runtimes into assets/ — but only the ones
|
|
312
|
+
// a page actually references (mermaid runtime / draw.io viewer), so a site with
|
|
313
|
+
// no such diagrams carries no extra weight.
|
|
314
|
+
if (anyMermaid) {
|
|
315
|
+
const rel = writeVendorAsset(outDir, 'mermaid.min.js');
|
|
316
|
+
if (rel) written.push(rel);
|
|
317
|
+
}
|
|
318
|
+
if (anyDrawio) {
|
|
319
|
+
const rel = writeVendorAsset(outDir, 'drawio-viewer.min.js');
|
|
320
|
+
if (rel) written.push(rel);
|
|
321
|
+
}
|
|
322
|
+
|
|
305
323
|
// Step 2: Render search.html (offline lunr search; PE fallback).
|
|
306
324
|
const searchHtml = renderSearchPageHtml(manifest, docBodies, navFlags);
|
|
307
325
|
writeHtml(path.join(outDir, 'search.html'), searchHtml);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/knowledge-atlas/src/render/vendor.ts
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* Locate and read vendored browser runtimes that are too large to embed as
|
|
5
|
+
* inline strings in the bundle (mermaid, the drawio viewer). They ship as real
|
|
6
|
+
* files under `lib/knowledge-atlas/vendor/` and are copied verbatim into a
|
|
7
|
+
* generated site's `assets/` directory, referenced by a local relative
|
|
8
|
+
* `<script src="…">`. Nothing here is fetched over the network — the files are
|
|
9
|
+
* part of the committed, offline ma-agents distribution.
|
|
10
|
+
*
|
|
11
|
+
* The engine runs as the bundled `lib/knowledge-atlas/dist/atlas.cjs`, so the
|
|
12
|
+
* vendor directory is a sibling of `dist/` (`../vendor`).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
|
|
18
|
+
/** Absolute path to the vendored-runtime directory, relative to the bundle. */
|
|
19
|
+
export function vendorDir(): string {
|
|
20
|
+
return path.join(__dirname, '..', 'vendor');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Read a vendored asset (e.g. "mermaid.min.js"); returns null if absent. */
|
|
24
|
+
export function readVendorAsset(name: string): string | null {
|
|
25
|
+
const p = path.join(vendorDir(), name);
|
|
26
|
+
try {
|
|
27
|
+
return fs.readFileSync(p, 'utf-8');
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Copy a vendored asset into `outDir/assets/<name>`. Returns the relative path
|
|
34
|
+
* written (e.g. "assets/mermaid.min.js"), or null if the asset is unavailable. */
|
|
35
|
+
export function writeVendorAsset(outDir: string, name: string): string | null {
|
|
36
|
+
const content = readVendorAsset(name);
|
|
37
|
+
if (content == null) return null;
|
|
38
|
+
const assetsDir = path.join(outDir, 'assets');
|
|
39
|
+
fs.mkdirSync(assetsDir, { recursive: true });
|
|
40
|
+
// LF-normalize for determinism (NFR62), consistent with writeHtml.
|
|
41
|
+
fs.writeFileSync(path.join(assetsDir, name), content.replace(/\r\n/g, '\n'), { encoding: 'utf-8' });
|
|
42
|
+
return `assets/${name}`;
|
|
43
|
+
}
|