ma-agents 3.15.1 → 3.15.2

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.
@@ -37,7 +37,7 @@
37
37
  "name": "ma-skills",
38
38
  "source": "./",
39
39
  "description": "ma-agents extension module providing enterprise SDLC personas and operational workflow skills.",
40
- "version": "3.15.1",
40
+ "version": "3.15.2",
41
41
  "author": {
42
42
  "name": "Alon Mayaffit"
43
43
  },
@@ -12154,14 +12154,23 @@ function renderDocumentPage(fileRec, manifest, projectRoot, navFlags, resolveAbs
12154
12154
  const statusBadgesHtml = renderStatusBadges(fileRec.statuses);
12155
12155
  const existingIds = /* @__PURE__ */ new Set();
12156
12156
  for (const m of renderedHtml.matchAll(/\sid="([^"]+)"/g)) existingIds.add(m[1]);
12157
- const anchorTargets = [];
12157
+ const escRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12158
+ const inlinePlacements = [];
12159
+ const fallbackAnchors = [];
12158
12160
  for (const node of fileNodes) {
12159
- if (node.anchor && !existingIds.has(node.anchor)) {
12160
- anchorTargets.push(`<span class="entity-anchor" id="${esc4(node.anchor)}"></span>`);
12161
- existingIds.add(node.anchor);
12162
- }
12163
- }
12164
- const anchorTargetsHtml = anchorTargets.join("\n");
12161
+ const a = node.anchor;
12162
+ if (!a || existingIds.has(a)) continue;
12163
+ existingIds.add(a);
12164
+ const m = new RegExp('<a\\b[^>]*href="[^"]*#' + escRe(a) + '"', "i").exec(renderedHtml);
12165
+ if (m) inlinePlacements.push({ idx: m.index, anchor: a });
12166
+ else fallbackAnchors.push(a);
12167
+ }
12168
+ inlinePlacements.sort((x, y) => y.idx - x.idx);
12169
+ for (const p of inlinePlacements) {
12170
+ const span = `<span class="entity-anchor" id="${esc4(p.anchor)}"></span>`;
12171
+ renderedHtml = renderedHtml.slice(0, p.idx) + span + renderedHtml.slice(p.idx);
12172
+ }
12173
+ const anchorTargetsHtml = fallbackAnchors.map((a) => `<span class="entity-anchor" id="${esc4(a)}"></span>`).join("\n");
12165
12174
  const backLinkHtml = '<a class="back-link" href="../index.html">&larr; Back to Home</a>';
12166
12175
  const pageBody = [
12167
12176
  '<div class="content-wrap">',
@@ -13061,6 +13070,7 @@ function buildAppScript() {
13061
13070
  // mouse (finding #8: tooltip was shown with display:block but no left/top,
13062
13071
  // so it rendered at the bottom of <main>).
13063
13072
  var lastHoverNode = null;
13073
+ var selectedLink = null; // edge highlighted on click: drawn bold + its two endpoints ringed
13064
13074
 
13065
13075
  function getFilteredData() {
13066
13076
  var nodes = allNodes;
@@ -13183,6 +13193,18 @@ function buildAppScript() {
13183
13193
  ctx.fillStyle = n.color || '#888';
13184
13194
  traceNodeShapePath(ctx, n.shape, n.x, n.y, size);
13185
13195
  ctx.fill();
13196
+ // Ring the two endpoints of the selected edge so both ends are easy to
13197
+ // find, however far apart they sit in the layout.
13198
+ if (selectedLink) {
13199
+ var s = selectedLink.source, t = selectedLink.target;
13200
+ if (n === s || n === t) {
13201
+ ctx.beginPath();
13202
+ ctx.arc(n.x, n.y, size + 3.5, 0, 2 * Math.PI);
13203
+ ctx.strokeStyle = '#111827';
13204
+ ctx.lineWidth = 1.8;
13205
+ ctx.stroke();
13206
+ }
13207
+ }
13186
13208
  })
13187
13209
  // Matching hit-test paint: without this, force-graph's click/hover
13188
13210
  // detection falls back to a generic area that no longer matches the
@@ -13196,12 +13218,30 @@ function buildAppScript() {
13196
13218
  })
13197
13219
  .linkSource('source')
13198
13220
  .linkTarget('target')
13199
- .linkColor(function(e) { return e.color || '#aaa'; })
13221
+ // Selected edge is drawn dark + thick so you can trace it end to end.
13222
+ .linkColor(function(e) { return e === selectedLink ? '#111827' : (e.color || '#aaa'); })
13223
+ .linkWidth(function(e) { return e === selectedLink ? 3.5 : (e.dashed ? 0.5 : 1); })
13200
13224
  // Story 29.8 - dashed stroke for weak references edges; solid otherwise.
13201
13225
  .linkLineDash(function(e) { return e.dashed ? [4, 3] : null; })
13202
- .linkDirectionalArrowLength(4)
13226
+ .linkDirectionalArrowLength(function(e) { return e === selectedLink ? 7 : 4; })
13203
13227
  .linkDirectionalArrowRelPos(1)
13204
13228
  .linkCurvature(0.1)
13229
+ // Click an edge to highlight it (bold + ringed endpoints); click it again
13230
+ // or click empty space to clear. Re-applying the accessors forces an
13231
+ // immediate repaint of the new selection state.
13232
+ .onLinkClick(function(e) {
13233
+ selectedLink = (selectedLink === e) ? null : e;
13234
+ graph.linkWidth(graph.linkWidth()).linkColor(graph.linkColor());
13235
+ })
13236
+ .onLinkHover(function(e) {
13237
+ if (container) container.style.cursor = e ? 'pointer' : 'default';
13238
+ if (!tooltip) return;
13239
+ if (!e) { tooltip.style.display = 'none'; return; }
13240
+ var s = (e.source && e.source.label) || e.source;
13241
+ var t = (e.target && e.target.label) || e.target;
13242
+ tooltip.style.display = 'block';
13243
+ tooltip.textContent = s + ' \u2192 ' + t + (e.kind ? ' [' + e.kind + ']' : '');
13244
+ })
13205
13245
  .onNodeClick(function(n) {
13206
13246
  // Navigate to the reading view via the server-resolved doc page (the
13207
13247
  // 'page' field, e.g. "docs/<slug>.html"), NOT the raw fileId \u2014 the
@@ -13242,6 +13282,10 @@ function buildAppScript() {
13242
13282
  .onBackgroundClick(function() {
13243
13283
  if (tooltip) tooltip.style.display = 'none';
13244
13284
  lastHoverNode = null;
13285
+ if (selectedLink) {
13286
+ selectedLink = null;
13287
+ graph.linkWidth(graph.linkWidth()).linkColor(graph.linkColor());
13288
+ }
13245
13289
  });
13246
13290
  }
13247
13291
 
@@ -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 anchorTargets: string[] = [];
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
- if (node.anchor && !existingIds.has(node.anchor)) {
307
- anchorTargets.push(`<span class="entity-anchor" id="${esc(node.anchor)}"></span>`);
308
- existingIds.add(node.anchor);
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 = anchorTargets.join('\n');
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">&larr; 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
- .linkColor(function(e) { return e.color || '#aaa'; })
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ma-agents",
3
- "version": "3.15.1",
3
+ "version": "3.15.2",
4
4
  "description": "NPX tool to install skills for AI coding agents (Claude Code, Gemini, Copilot, Kilocode, Cline, Cursor, Roo Code)",
5
5
  "main": "index.js",
6
6
  "bin": {