ma-agents 3.15.4 → 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 +138 -55
- package/lib/knowledge-atlas/src/render/document.ts +109 -12
- 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);
|
|
@@ -12163,7 +12165,9 @@ function renderDocumentPage(fileRec, manifest, projectRoot, navFlags, resolveAbs
|
|
|
12163
12165
|
const a = node.anchor;
|
|
12164
12166
|
if (!a || existingIds.has(a)) continue;
|
|
12165
12167
|
existingIds.add(a);
|
|
12166
|
-
const
|
|
12168
|
+
const ra = escRe(a);
|
|
12169
|
+
let m = new RegExp('<a\\b[^>]*href="[^"]*#' + ra + '"[^>]*>[^<]*</a>\\s*:', "i").exec(renderedHtml);
|
|
12170
|
+
if (!m) m = new RegExp('<a\\b[^>]*href="[^"]*#' + ra + '"', "i").exec(renderedHtml);
|
|
12167
12171
|
if (m) inlinePlacements.push({ idx: m.index, anchor: a });
|
|
12168
12172
|
else fallbackAnchors.push(a);
|
|
12169
12173
|
}
|
|
@@ -12186,16 +12190,83 @@ function renderDocumentPage(fileRec, manifest, projectRoot, navFlags, resolveAbs
|
|
|
12186
12190
|
"</article>",
|
|
12187
12191
|
"</div>"
|
|
12188
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
|
+
}
|
|
12189
12200
|
const html = pageShell({
|
|
12190
12201
|
title,
|
|
12191
12202
|
body: pageBody,
|
|
12192
12203
|
manifest,
|
|
12193
12204
|
pageType: "document",
|
|
12194
12205
|
baseHref: "../",
|
|
12195
|
-
navFlags
|
|
12206
|
+
navFlags,
|
|
12207
|
+
scripts
|
|
12196
12208
|
});
|
|
12197
12209
|
const articleText = `${title} ${renderedHtml}`.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
12198
|
-
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}`;
|
|
12199
12270
|
}
|
|
12200
12271
|
|
|
12201
12272
|
// lib/knowledge-atlas/src/render/search.ts
|
|
@@ -12408,8 +12479,8 @@ function esc6(s) {
|
|
|
12408
12479
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
12409
12480
|
}
|
|
12410
12481
|
function writeHtml(filePath, content) {
|
|
12411
|
-
|
|
12412
|
-
|
|
12482
|
+
fs8.mkdirSync(path8.dirname(filePath), { recursive: true });
|
|
12483
|
+
fs8.writeFileSync(filePath, content.replace(/\r\n/g, "\n"), { encoding: "utf-8" });
|
|
12413
12484
|
}
|
|
12414
12485
|
function renderHomePage(manifest, navFlags) {
|
|
12415
12486
|
const { projectTitle, generatedAt, counts, nodes, edges } = manifest;
|
|
@@ -12546,35 +12617,47 @@ function renderHomePage(manifest, navFlags) {
|
|
|
12546
12617
|
function renderSite(manifest, outDir, resolveAbsPath) {
|
|
12547
12618
|
const written = [];
|
|
12548
12619
|
const docBodies = /* @__PURE__ */ new Map();
|
|
12549
|
-
|
|
12550
|
-
|
|
12620
|
+
fs8.mkdirSync(outDir, { recursive: true });
|
|
12621
|
+
fs8.mkdirSync(path8.join(outDir, "docs"), { recursive: true });
|
|
12551
12622
|
const fileIds = manifest.files.map((f) => f.path);
|
|
12552
12623
|
const slugMap = buildSlugMap(fileIds);
|
|
12553
12624
|
const navFlags = {
|
|
12554
|
-
hasGraphPage:
|
|
12555
|
-
hasDiagramsPage:
|
|
12556
|
-
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"))
|
|
12557
12628
|
};
|
|
12629
|
+
let anyMermaid = false;
|
|
12630
|
+
let anyDrawio = false;
|
|
12558
12631
|
for (const file of manifest.files) {
|
|
12559
12632
|
const slug = slugMap.get(file.path) ?? filePathToSlug(file.path);
|
|
12560
|
-
const { html, articleText } = renderDocumentPage(file, manifest, manifest.projectRoot, navFlags, resolveAbsPath);
|
|
12633
|
+
const { html, articleText, usedMermaid, usedDrawio } = renderDocumentPage(file, manifest, manifest.projectRoot, navFlags, resolveAbsPath);
|
|
12561
12634
|
const relPath = `docs/${slug}.html`;
|
|
12562
|
-
writeHtml(
|
|
12635
|
+
writeHtml(path8.join(outDir, relPath), html);
|
|
12563
12636
|
written.push(relPath);
|
|
12637
|
+
if (usedMermaid) anyMermaid = true;
|
|
12638
|
+
if (usedDrawio) anyDrawio = true;
|
|
12564
12639
|
docBodies.set(file.path, articleText);
|
|
12565
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
|
+
}
|
|
12566
12649
|
const searchHtml = renderSearchPageHtml(manifest, docBodies, navFlags);
|
|
12567
|
-
writeHtml(
|
|
12650
|
+
writeHtml(path8.join(outDir, "search.html"), searchHtml);
|
|
12568
12651
|
written.push("search.html");
|
|
12569
12652
|
const indexHtml = renderHomePage(manifest, navFlags);
|
|
12570
|
-
writeHtml(
|
|
12653
|
+
writeHtml(path8.join(outDir, "index.html"), indexHtml);
|
|
12571
12654
|
written.push("index.html");
|
|
12572
12655
|
return written.sort();
|
|
12573
12656
|
}
|
|
12574
12657
|
|
|
12575
12658
|
// lib/knowledge-atlas/src/render/graph.ts
|
|
12576
|
-
var
|
|
12577
|
-
var
|
|
12659
|
+
var fs9 = __toESM(require("fs"));
|
|
12660
|
+
var path9 = __toESM(require("path"));
|
|
12578
12661
|
|
|
12579
12662
|
// lib/knowledge-atlas/src/render/force-graph-umd.ts
|
|
12580
12663
|
var FORCE_GRAPH_UMD = `// Version 1.51.4 force-graph - https://github.com/vasturiano/force-graph
|
|
@@ -13037,9 +13120,9 @@ function renderGraphPage(manifest, outDir) {
|
|
|
13037
13120
|
""
|
|
13038
13121
|
// trailing newline
|
|
13039
13122
|
].join("\n");
|
|
13040
|
-
const outPath =
|
|
13041
|
-
|
|
13042
|
-
|
|
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" });
|
|
13043
13126
|
return ["graph.html"];
|
|
13044
13127
|
}
|
|
13045
13128
|
function buildAppScript() {
|
|
@@ -13349,8 +13432,8 @@ function buildAppScript() {
|
|
|
13349
13432
|
}
|
|
13350
13433
|
|
|
13351
13434
|
// lib/knowledge-atlas/src/render/diagrams.ts
|
|
13352
|
-
var
|
|
13353
|
-
var
|
|
13435
|
+
var fs10 = __toESM(require("fs"));
|
|
13436
|
+
var path10 = __toESM(require("path"));
|
|
13354
13437
|
function esc8(s) {
|
|
13355
13438
|
return (s ?? "").toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13356
13439
|
}
|
|
@@ -13917,15 +14000,15 @@ function renderDiagrams(manifest, outDir) {
|
|
|
13917
14000
|
""
|
|
13918
14001
|
// trailing newline
|
|
13919
14002
|
].join("\n");
|
|
13920
|
-
const outPath =
|
|
13921
|
-
|
|
13922
|
-
|
|
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" });
|
|
13923
14006
|
return ["diagrams.html"];
|
|
13924
14007
|
}
|
|
13925
14008
|
|
|
13926
14009
|
// lib/knowledge-atlas/src/render/orphan.ts
|
|
13927
|
-
var
|
|
13928
|
-
var
|
|
14010
|
+
var fs11 = __toESM(require("fs"));
|
|
14011
|
+
var path11 = __toESM(require("path"));
|
|
13929
14012
|
function esc9(s) {
|
|
13930
14013
|
return (s ?? "").toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13931
14014
|
}
|
|
@@ -14287,16 +14370,16 @@ function renderOrphanPage(manifest, outDir) {
|
|
|
14287
14370
|
""
|
|
14288
14371
|
// trailing newline
|
|
14289
14372
|
].join("\n");
|
|
14290
|
-
const outPath =
|
|
14291
|
-
|
|
14292
|
-
|
|
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" });
|
|
14293
14376
|
return ["orphans.html"];
|
|
14294
14377
|
}
|
|
14295
14378
|
|
|
14296
14379
|
// lib/knowledge-atlas/src/generate.ts
|
|
14297
14380
|
var MANIFEST_VERSION = 3;
|
|
14298
14381
|
function normPath(p) {
|
|
14299
|
-
return
|
|
14382
|
+
return path12.resolve(p).replace(/[\\/]+$/, "");
|
|
14300
14383
|
}
|
|
14301
14384
|
function pathsEqual(a, b) {
|
|
14302
14385
|
return normPath(a) === normPath(b);
|
|
@@ -14304,7 +14387,7 @@ function pathsEqual(a, b) {
|
|
|
14304
14387
|
function isUnder(parent, child) {
|
|
14305
14388
|
const p = normPath(parent);
|
|
14306
14389
|
const c = normPath(child);
|
|
14307
|
-
return c.startsWith(p +
|
|
14390
|
+
return c.startsWith(p + path12.sep);
|
|
14308
14391
|
}
|
|
14309
14392
|
var MAX_TIME_VALUE_MS = 864e13;
|
|
14310
14393
|
function resolveTimestamp(now) {
|
|
@@ -14345,10 +14428,10 @@ function isoSecond(d) {
|
|
|
14345
14428
|
return d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
14346
14429
|
}
|
|
14347
14430
|
function deriveProjectTitle(projectRoot) {
|
|
14348
|
-
const pkgPath =
|
|
14431
|
+
const pkgPath = path12.join(projectRoot, "package.json");
|
|
14349
14432
|
try {
|
|
14350
|
-
if (
|
|
14351
|
-
const raw =
|
|
14433
|
+
if (fs12.existsSync(pkgPath)) {
|
|
14434
|
+
const raw = fs12.readFileSync(pkgPath, "utf-8");
|
|
14352
14435
|
const pkg = JSON.parse(raw);
|
|
14353
14436
|
if (typeof pkg["name"] === "string" && pkg["name"].trim()) {
|
|
14354
14437
|
return pkg["name"].trim();
|
|
@@ -14356,13 +14439,13 @@ function deriveProjectTitle(projectRoot) {
|
|
|
14356
14439
|
}
|
|
14357
14440
|
} catch {
|
|
14358
14441
|
}
|
|
14359
|
-
const base =
|
|
14442
|
+
const base = path12.basename(projectRoot).trim();
|
|
14360
14443
|
return base.length > 0 ? base : "Unknown Project";
|
|
14361
14444
|
}
|
|
14362
14445
|
function extractFileRecord(absPath, fileId, docType, contentHash2) {
|
|
14363
14446
|
let content = "";
|
|
14364
14447
|
try {
|
|
14365
|
-
content =
|
|
14448
|
+
content = fs12.readFileSync(absPath, "utf-8");
|
|
14366
14449
|
} catch {
|
|
14367
14450
|
return { path: fileId, contentHash: contentHash2, type: docType, entities: [], statuses: [], edges: [] };
|
|
14368
14451
|
}
|
|
@@ -14418,19 +14501,19 @@ function generate(opts) {
|
|
|
14418
14501
|
if (!projectRoot || typeof projectRoot !== "string") {
|
|
14419
14502
|
throw new Error("generate: projectRoot is required and must be a non-empty string");
|
|
14420
14503
|
}
|
|
14421
|
-
if (!
|
|
14504
|
+
if (!path12.isAbsolute(projectRoot)) {
|
|
14422
14505
|
throw new Error(`generate: projectRoot must be an absolute path (got: ${projectRoot})`);
|
|
14423
14506
|
}
|
|
14424
|
-
const rootStat =
|
|
14507
|
+
const rootStat = fs12.statSync(projectRoot, { throwIfNoEntry: false });
|
|
14425
14508
|
if (!rootStat || !rootStat.isDirectory()) {
|
|
14426
14509
|
throw new Error(`generate: projectRoot does not exist or is not a directory: ${projectRoot}`);
|
|
14427
14510
|
}
|
|
14428
14511
|
const { sources, outputFolder, outputOverlap } = resolveKnowledgeSources(projectRoot);
|
|
14429
|
-
const bmadOutputDir =
|
|
14430
|
-
const defaultOutDir =
|
|
14512
|
+
const bmadOutputDir = path12.join(projectRoot, outputFolder);
|
|
14513
|
+
const defaultOutDir = path12.join(bmadOutputDir, "knowledge-site");
|
|
14431
14514
|
const outDir = outDirOverride ?? defaultOutDir;
|
|
14432
|
-
const rel =
|
|
14433
|
-
if (rel.startsWith("..") ||
|
|
14515
|
+
const rel = path12.relative(projectRoot, outDir);
|
|
14516
|
+
if (rel.startsWith("..") || path12.isAbsolute(rel)) {
|
|
14434
14517
|
throw new Error(`generate: outDir escapes projectRoot: ${outDir}`);
|
|
14435
14518
|
}
|
|
14436
14519
|
const generatedAt = resolveTimestamp(opts.now);
|
|
@@ -14441,14 +14524,14 @@ function generate(opts) {
|
|
|
14441
14524
|
const isDefaultOut = pathsEqual(outDir, defaultOutDir);
|
|
14442
14525
|
const sourceAtOrUnderOut = sources.some((s) => pathsEqual(s.absPath, outDir) || isUnder(outDir, s.absPath));
|
|
14443
14526
|
if (isDefaultOut && !outputOverlap && !sourceAtOrUnderOut) {
|
|
14444
|
-
|
|
14527
|
+
fs12.rmSync(outDir, { recursive: true, force: true });
|
|
14445
14528
|
} else if (isDefaultOut && (outputOverlap || sourceAtOrUnderOut)) {
|
|
14446
14529
|
process.stderr.write(
|
|
14447
14530
|
`[atlas] WARN: refusing to purge ${outDir} \u2014 a knowledge source resolves at or under it (data-loss guard)
|
|
14448
14531
|
`
|
|
14449
14532
|
);
|
|
14450
14533
|
}
|
|
14451
|
-
|
|
14534
|
+
fs12.mkdirSync(outDir, { recursive: true });
|
|
14452
14535
|
const safeRender = (label, fn) => {
|
|
14453
14536
|
try {
|
|
14454
14537
|
return fn();
|
|
@@ -14465,7 +14548,7 @@ function generate(opts) {
|
|
|
14465
14548
|
const extraFileCount = graphFiles.length + diagramFiles.length + orphanFiles.length;
|
|
14466
14549
|
const written = renderSite(manifest, outDir, resolveAbsPath);
|
|
14467
14550
|
writeManifest(outDir, manifest);
|
|
14468
|
-
const indexPath =
|
|
14551
|
+
const indexPath = path12.join(outDir, "index.html");
|
|
14469
14552
|
return {
|
|
14470
14553
|
outDir,
|
|
14471
14554
|
indexPath,
|
|
@@ -14486,7 +14569,7 @@ function main() {
|
|
|
14486
14569
|
usage();
|
|
14487
14570
|
process.exit(args.length === 0 ? 1 : 0);
|
|
14488
14571
|
}
|
|
14489
|
-
const projectRoot =
|
|
14572
|
+
const projectRoot = path13.resolve(args[0]);
|
|
14490
14573
|
let outDir;
|
|
14491
14574
|
const outIdx = args.indexOf("--out");
|
|
14492
14575
|
if (outIdx !== -1) {
|
|
@@ -14494,7 +14577,7 @@ function main() {
|
|
|
14494
14577
|
process.stderr.write("[atlas] ERROR: --out requires a directory argument\n");
|
|
14495
14578
|
process.exit(1);
|
|
14496
14579
|
}
|
|
14497
|
-
outDir =
|
|
14580
|
+
outDir = path13.resolve(args[outIdx + 1]);
|
|
14498
14581
|
}
|
|
14499
14582
|
try {
|
|
14500
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)`)
|
|
@@ -314,8 +328,15 @@ export function renderDocumentPage(
|
|
|
314
328
|
const a = node.anchor;
|
|
315
329
|
if (!a || existingIds.has(a)) continue;
|
|
316
330
|
existingIds.add(a);
|
|
317
|
-
|
|
318
|
-
|
|
331
|
+
const ra = escRe(a);
|
|
332
|
+
// Prefer the DEFINITION occurrence — a linkified mention immediately followed
|
|
333
|
+
// by a colon (the BMAD "**FRn:** description" / "## FRn: …" pattern) — over an
|
|
334
|
+
// earlier passing mention. Without this, an FR that a revision-history /
|
|
335
|
+
// changelog row lists near the top of the PRD ("…update FR21, FR91…") anchored
|
|
336
|
+
// there, so clicking the FR jumped to the changelog instead of its definition.
|
|
337
|
+
let m = new RegExp('<a\\b[^>]*href="[^"]*#' + ra + '"[^>]*>[^<]*</a>\\s*:', 'i').exec(renderedHtml);
|
|
338
|
+
// Otherwise fall back to the first linkified mention anywhere on the page.
|
|
339
|
+
if (!m) m = new RegExp('<a\\b[^>]*href="[^"]*#' + ra + '"', 'i').exec(renderedHtml);
|
|
319
340
|
if (m) inlinePlacements.push({ idx: m.index, anchor: a });
|
|
320
341
|
else fallbackAnchors.push(a);
|
|
321
342
|
}
|
|
@@ -347,9 +368,19 @@ export function renderDocumentPage(
|
|
|
347
368
|
'</div>',
|
|
348
369
|
].filter(Boolean).join('\n');
|
|
349
370
|
|
|
350
|
-
// Reading
|
|
351
|
-
//
|
|
352
|
-
//
|
|
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
|
+
}
|
|
353
384
|
const html = pageShell({
|
|
354
385
|
title,
|
|
355
386
|
body: pageBody,
|
|
@@ -357,6 +388,7 @@ export function renderDocumentPage(
|
|
|
357
388
|
pageType: 'document',
|
|
358
389
|
baseHref: '../',
|
|
359
390
|
navFlags,
|
|
391
|
+
scripts,
|
|
360
392
|
});
|
|
361
393
|
|
|
362
394
|
// Article-only plain text for the search index (SF-1): strip tags from the
|
|
@@ -367,5 +399,70 @@ export function renderDocumentPage(
|
|
|
367
399
|
.replace(/\s+/g, ' ')
|
|
368
400
|
.trim();
|
|
369
401
|
|
|
370
|
-
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 };
|
|
371
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);
|