ma-agents 3.15.1 → 3.15.3
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 +56 -10
- package/lib/knowledge-atlas/src/render/document.ts +26 -6
- package/lib/knowledge-atlas/src/render/graph.ts +37 -2
- package/lib/knowledge-atlas/src/scan/scanner.ts +14 -2
- package/package.json +1 -1
|
@@ -2106,7 +2106,9 @@ function classifyFile(fileId) {
|
|
|
2106
2106
|
if (/^\d+-\d+-[a-z0-9][a-z0-9-]*\.md$/.test(basename4)) return "story";
|
|
2107
2107
|
if (/product-brief/.test(basename4)) return "product-brief";
|
|
2108
2108
|
if (lowerFileId.includes("/briefs/")) return "brief";
|
|
2109
|
-
if (/prd/.test(basename4) || /product-requirements/.test(basename4))
|
|
2109
|
+
if ((/prd/.test(basename4) || /product-requirements/.test(basename4)) && !/(validation|report|checklist|review|cohesion|assessment)/.test(basename4)) {
|
|
2110
|
+
return "prd";
|
|
2111
|
+
}
|
|
2110
2112
|
if (/architecture/.test(basename4)) return "architecture";
|
|
2111
2113
|
if (/epic/.test(basename4)) return "epics";
|
|
2112
2114
|
if (basename4.endsWith(".md") || basename4.endsWith(".yaml") || basename4.endsWith(".yml")) {
|
|
@@ -12154,14 +12156,23 @@ function renderDocumentPage(fileRec, manifest, projectRoot, navFlags, resolveAbs
|
|
|
12154
12156
|
const statusBadgesHtml = renderStatusBadges(fileRec.statuses);
|
|
12155
12157
|
const existingIds = /* @__PURE__ */ new Set();
|
|
12156
12158
|
for (const m of renderedHtml.matchAll(/\sid="([^"]+)"/g)) existingIds.add(m[1]);
|
|
12157
|
-
const
|
|
12159
|
+
const escRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12160
|
+
const inlinePlacements = [];
|
|
12161
|
+
const fallbackAnchors = [];
|
|
12158
12162
|
for (const node of fileNodes) {
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12163
|
+
const a = node.anchor;
|
|
12164
|
+
if (!a || existingIds.has(a)) continue;
|
|
12165
|
+
existingIds.add(a);
|
|
12166
|
+
const m = new RegExp('<a\\b[^>]*href="[^"]*#' + escRe(a) + '"', "i").exec(renderedHtml);
|
|
12167
|
+
if (m) inlinePlacements.push({ idx: m.index, anchor: a });
|
|
12168
|
+
else fallbackAnchors.push(a);
|
|
12169
|
+
}
|
|
12170
|
+
inlinePlacements.sort((x, y) => y.idx - x.idx);
|
|
12171
|
+
for (const p of inlinePlacements) {
|
|
12172
|
+
const span = `<span class="entity-anchor" id="${esc4(p.anchor)}"></span>`;
|
|
12173
|
+
renderedHtml = renderedHtml.slice(0, p.idx) + span + renderedHtml.slice(p.idx);
|
|
12174
|
+
}
|
|
12175
|
+
const anchorTargetsHtml = fallbackAnchors.map((a) => `<span class="entity-anchor" id="${esc4(a)}"></span>`).join("\n");
|
|
12165
12176
|
const backLinkHtml = '<a class="back-link" href="../index.html">← Back to Home</a>';
|
|
12166
12177
|
const pageBody = [
|
|
12167
12178
|
'<div class="content-wrap">',
|
|
@@ -13061,6 +13072,7 @@ function buildAppScript() {
|
|
|
13061
13072
|
// mouse (finding #8: tooltip was shown with display:block but no left/top,
|
|
13062
13073
|
// so it rendered at the bottom of <main>).
|
|
13063
13074
|
var lastHoverNode = null;
|
|
13075
|
+
var selectedLink = null; // edge highlighted on click: drawn bold + its two endpoints ringed
|
|
13064
13076
|
|
|
13065
13077
|
function getFilteredData() {
|
|
13066
13078
|
var nodes = allNodes;
|
|
@@ -13183,6 +13195,18 @@ function buildAppScript() {
|
|
|
13183
13195
|
ctx.fillStyle = n.color || '#888';
|
|
13184
13196
|
traceNodeShapePath(ctx, n.shape, n.x, n.y, size);
|
|
13185
13197
|
ctx.fill();
|
|
13198
|
+
// Ring the two endpoints of the selected edge so both ends are easy to
|
|
13199
|
+
// find, however far apart they sit in the layout.
|
|
13200
|
+
if (selectedLink) {
|
|
13201
|
+
var s = selectedLink.source, t = selectedLink.target;
|
|
13202
|
+
if (n === s || n === t) {
|
|
13203
|
+
ctx.beginPath();
|
|
13204
|
+
ctx.arc(n.x, n.y, size + 3.5, 0, 2 * Math.PI);
|
|
13205
|
+
ctx.strokeStyle = '#111827';
|
|
13206
|
+
ctx.lineWidth = 1.8;
|
|
13207
|
+
ctx.stroke();
|
|
13208
|
+
}
|
|
13209
|
+
}
|
|
13186
13210
|
})
|
|
13187
13211
|
// Matching hit-test paint: without this, force-graph's click/hover
|
|
13188
13212
|
// detection falls back to a generic area that no longer matches the
|
|
@@ -13196,12 +13220,30 @@ function buildAppScript() {
|
|
|
13196
13220
|
})
|
|
13197
13221
|
.linkSource('source')
|
|
13198
13222
|
.linkTarget('target')
|
|
13199
|
-
|
|
13223
|
+
// Selected edge is drawn dark + thick so you can trace it end to end.
|
|
13224
|
+
.linkColor(function(e) { return e === selectedLink ? '#111827' : (e.color || '#aaa'); })
|
|
13225
|
+
.linkWidth(function(e) { return e === selectedLink ? 3.5 : (e.dashed ? 0.5 : 1); })
|
|
13200
13226
|
// Story 29.8 - dashed stroke for weak references edges; solid otherwise.
|
|
13201
13227
|
.linkLineDash(function(e) { return e.dashed ? [4, 3] : null; })
|
|
13202
|
-
.linkDirectionalArrowLength(4)
|
|
13228
|
+
.linkDirectionalArrowLength(function(e) { return e === selectedLink ? 7 : 4; })
|
|
13203
13229
|
.linkDirectionalArrowRelPos(1)
|
|
13204
13230
|
.linkCurvature(0.1)
|
|
13231
|
+
// Click an edge to highlight it (bold + ringed endpoints); click it again
|
|
13232
|
+
// or click empty space to clear. Re-applying the accessors forces an
|
|
13233
|
+
// immediate repaint of the new selection state.
|
|
13234
|
+
.onLinkClick(function(e) {
|
|
13235
|
+
selectedLink = (selectedLink === e) ? null : e;
|
|
13236
|
+
graph.linkWidth(graph.linkWidth()).linkColor(graph.linkColor());
|
|
13237
|
+
})
|
|
13238
|
+
.onLinkHover(function(e) {
|
|
13239
|
+
if (container) container.style.cursor = e ? 'pointer' : 'default';
|
|
13240
|
+
if (!tooltip) return;
|
|
13241
|
+
if (!e) { tooltip.style.display = 'none'; return; }
|
|
13242
|
+
var s = (e.source && e.source.label) || e.source;
|
|
13243
|
+
var t = (e.target && e.target.label) || e.target;
|
|
13244
|
+
tooltip.style.display = 'block';
|
|
13245
|
+
tooltip.textContent = s + ' \u2192 ' + t + (e.kind ? ' [' + e.kind + ']' : '');
|
|
13246
|
+
})
|
|
13205
13247
|
.onNodeClick(function(n) {
|
|
13206
13248
|
// Navigate to the reading view via the server-resolved doc page (the
|
|
13207
13249
|
// 'page' field, e.g. "docs/<slug>.html"), NOT the raw fileId \u2014 the
|
|
@@ -13242,6 +13284,10 @@ function buildAppScript() {
|
|
|
13242
13284
|
.onBackgroundClick(function() {
|
|
13243
13285
|
if (tooltip) tooltip.style.display = 'none';
|
|
13244
13286
|
lastHoverNode = null;
|
|
13287
|
+
if (selectedLink) {
|
|
13288
|
+
selectedLink = null;
|
|
13289
|
+
graph.linkWidth(graph.linkWidth()).linkColor(graph.linkColor());
|
|
13290
|
+
}
|
|
13245
13291
|
});
|
|
13246
13292
|
}
|
|
13247
13293
|
|
|
@@ -299,16 +299,36 @@ export function renderDocumentPage(
|
|
|
299
299
|
// to a heading slug (`story-11-oauth2-integration`). We emit an invisible
|
|
300
300
|
// anchor target for every file node whose anchor is not already a heading id,
|
|
301
301
|
// so every cross-link `…#anchor` resolves to a real location on the page.
|
|
302
|
+
// Place each entity's anchor at the FIRST place that entity is mentioned in
|
|
303
|
+
// the body (its first linkified occurrence: an <a … href="…#anchor">), so a
|
|
304
|
+
// cross-link or graph-node click lands where the entity is actually discussed.
|
|
305
|
+
// Previously every anchor was emitted as one clump at the top of the article,
|
|
306
|
+
// so every #anchor (e.g. all 9 epics) resolved to the same spot. Anchors with
|
|
307
|
+
// no linked mention fall back to the top clump.
|
|
302
308
|
const existingIds = new Set<string>();
|
|
303
309
|
for (const m of renderedHtml.matchAll(/\sid="([^"]+)"/g)) existingIds.add(m[1]!);
|
|
304
|
-
const
|
|
310
|
+
const escRe = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
311
|
+
const inlinePlacements: { idx: number; anchor: string }[] = [];
|
|
312
|
+
const fallbackAnchors: string[] = [];
|
|
305
313
|
for (const node of fileNodes) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
314
|
+
const a = node.anchor;
|
|
315
|
+
if (!a || existingIds.has(a)) continue;
|
|
316
|
+
existingIds.add(a);
|
|
317
|
+
// First <a> tag whose href ends with this exact anchor fragment.
|
|
318
|
+
const m = new RegExp('<a\\b[^>]*href="[^"]*#' + escRe(a) + '"', 'i').exec(renderedHtml);
|
|
319
|
+
if (m) inlinePlacements.push({ idx: m.index, anchor: a });
|
|
320
|
+
else fallbackAnchors.push(a);
|
|
321
|
+
}
|
|
322
|
+
// Insert the invisible anchor spans just before their target links. Apply from
|
|
323
|
+
// the last position to the first so earlier indices stay valid.
|
|
324
|
+
inlinePlacements.sort((x, y) => y.idx - x.idx);
|
|
325
|
+
for (const p of inlinePlacements) {
|
|
326
|
+
const span = `<span class="entity-anchor" id="${esc(p.anchor)}"></span>`;
|
|
327
|
+
renderedHtml = renderedHtml.slice(0, p.idx) + span + renderedHtml.slice(p.idx);
|
|
310
328
|
}
|
|
311
|
-
const anchorTargetsHtml =
|
|
329
|
+
const anchorTargetsHtml = fallbackAnchors
|
|
330
|
+
.map(a => `<span class="entity-anchor" id="${esc(a)}"></span>`)
|
|
331
|
+
.join('\n');
|
|
312
332
|
|
|
313
333
|
// Back link
|
|
314
334
|
const backLinkHtml = '<a class="back-link" href="../index.html">← Back to Home</a>';
|
|
@@ -648,6 +648,7 @@ function buildAppScript(): string {
|
|
|
648
648
|
// mouse (finding #8: tooltip was shown with display:block but no left/top,
|
|
649
649
|
// so it rendered at the bottom of <main>).
|
|
650
650
|
var lastHoverNode = null;
|
|
651
|
+
var selectedLink = null; // edge highlighted on click: drawn bold + its two endpoints ringed
|
|
651
652
|
|
|
652
653
|
function getFilteredData() {
|
|
653
654
|
var nodes = allNodes;
|
|
@@ -770,6 +771,18 @@ function buildAppScript(): string {
|
|
|
770
771
|
ctx.fillStyle = n.color || '#888';
|
|
771
772
|
traceNodeShapePath(ctx, n.shape, n.x, n.y, size);
|
|
772
773
|
ctx.fill();
|
|
774
|
+
// Ring the two endpoints of the selected edge so both ends are easy to
|
|
775
|
+
// find, however far apart they sit in the layout.
|
|
776
|
+
if (selectedLink) {
|
|
777
|
+
var s = selectedLink.source, t = selectedLink.target;
|
|
778
|
+
if (n === s || n === t) {
|
|
779
|
+
ctx.beginPath();
|
|
780
|
+
ctx.arc(n.x, n.y, size + 3.5, 0, 2 * Math.PI);
|
|
781
|
+
ctx.strokeStyle = '#111827';
|
|
782
|
+
ctx.lineWidth = 1.8;
|
|
783
|
+
ctx.stroke();
|
|
784
|
+
}
|
|
785
|
+
}
|
|
773
786
|
})
|
|
774
787
|
// Matching hit-test paint: without this, force-graph's click/hover
|
|
775
788
|
// detection falls back to a generic area that no longer matches the
|
|
@@ -783,12 +796,30 @@ function buildAppScript(): string {
|
|
|
783
796
|
})
|
|
784
797
|
.linkSource('source')
|
|
785
798
|
.linkTarget('target')
|
|
786
|
-
|
|
799
|
+
// Selected edge is drawn dark + thick so you can trace it end to end.
|
|
800
|
+
.linkColor(function(e) { return e === selectedLink ? '#111827' : (e.color || '#aaa'); })
|
|
801
|
+
.linkWidth(function(e) { return e === selectedLink ? 3.5 : (e.dashed ? 0.5 : 1); })
|
|
787
802
|
// Story 29.8 - dashed stroke for weak references edges; solid otherwise.
|
|
788
803
|
.linkLineDash(function(e) { return e.dashed ? [4, 3] : null; })
|
|
789
|
-
.linkDirectionalArrowLength(4)
|
|
804
|
+
.linkDirectionalArrowLength(function(e) { return e === selectedLink ? 7 : 4; })
|
|
790
805
|
.linkDirectionalArrowRelPos(1)
|
|
791
806
|
.linkCurvature(0.1)
|
|
807
|
+
// Click an edge to highlight it (bold + ringed endpoints); click it again
|
|
808
|
+
// or click empty space to clear. Re-applying the accessors forces an
|
|
809
|
+
// immediate repaint of the new selection state.
|
|
810
|
+
.onLinkClick(function(e) {
|
|
811
|
+
selectedLink = (selectedLink === e) ? null : e;
|
|
812
|
+
graph.linkWidth(graph.linkWidth()).linkColor(graph.linkColor());
|
|
813
|
+
})
|
|
814
|
+
.onLinkHover(function(e) {
|
|
815
|
+
if (container) container.style.cursor = e ? 'pointer' : 'default';
|
|
816
|
+
if (!tooltip) return;
|
|
817
|
+
if (!e) { tooltip.style.display = 'none'; return; }
|
|
818
|
+
var s = (e.source && e.source.label) || e.source;
|
|
819
|
+
var t = (e.target && e.target.label) || e.target;
|
|
820
|
+
tooltip.style.display = 'block';
|
|
821
|
+
tooltip.textContent = s + ' → ' + t + (e.kind ? ' [' + e.kind + ']' : '');
|
|
822
|
+
})
|
|
792
823
|
.onNodeClick(function(n) {
|
|
793
824
|
// Navigate to the reading view via the server-resolved doc page (the
|
|
794
825
|
// 'page' field, e.g. "docs/<slug>.html"), NOT the raw fileId — the
|
|
@@ -829,6 +860,10 @@ function buildAppScript(): string {
|
|
|
829
860
|
.onBackgroundClick(function() {
|
|
830
861
|
if (tooltip) tooltip.style.display = 'none';
|
|
831
862
|
lastHoverNode = null;
|
|
863
|
+
if (selectedLink) {
|
|
864
|
+
selectedLink = null;
|
|
865
|
+
graph.linkWidth(graph.linkWidth()).linkColor(graph.linkColor());
|
|
866
|
+
}
|
|
832
867
|
});
|
|
833
868
|
}
|
|
834
869
|
|
|
@@ -126,8 +126,20 @@ export function classifyFile(fileId: string): DocType {
|
|
|
126
126
|
// e.g. briefs/epic-overview.md must be 'brief', not 'epics'.
|
|
127
127
|
if (lowerFileId.includes('/briefs/')) return 'brief';
|
|
128
128
|
|
|
129
|
-
// PRD: filename contains 'prd' or matches product-requirements
|
|
130
|
-
|
|
129
|
+
// PRD: filename contains 'prd' or matches product-requirements — but NOT a
|
|
130
|
+
// derivative document *about* the PRD (validation report, checklist, review,
|
|
131
|
+
// cohesion/assessment report). Those score or critique the requirements; the
|
|
132
|
+
// requirements themselves live in the PRD proper. Classifying e.g.
|
|
133
|
+
// `prd-validation-report.md` as 'prd' made it a rival definition-site, so an
|
|
134
|
+
// FR the PRD actually defines could anchor in the report instead — the real
|
|
135
|
+
// Prophesee bug where FR1/FR3/FR21 links pointed at prd-validation-report.html
|
|
136
|
+
// rather than the PRD. Let those fall through to 'report'.
|
|
137
|
+
if (
|
|
138
|
+
(/prd/.test(basename) || /product-requirements/.test(basename)) &&
|
|
139
|
+
!/(validation|report|checklist|review|cohesion|assessment)/.test(basename)
|
|
140
|
+
) {
|
|
141
|
+
return 'prd';
|
|
142
|
+
}
|
|
131
143
|
|
|
132
144
|
// Architecture document
|
|
133
145
|
if (/architecture/.test(basename)) return 'architecture';
|