@girardelli/architect 2.1.0 → 2.2.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/dist/agent-generator.d.ts +15 -4
- package/dist/agent-generator.d.ts.map +1 -1
- package/dist/agent-generator.js +136 -33
- package/dist/agent-generator.js.map +1 -1
- package/dist/cli.js +61 -18
- package/dist/cli.js.map +1 -1
- package/dist/html-reporter.d.ts.map +1 -1
- package/dist/html-reporter.js +356 -98
- package/dist/html-reporter.js.map +1 -1
- package/package.json +1 -1
- package/src/agent-generator.ts +175 -50
- package/src/cli.ts +72 -18
- package/src/html-reporter.ts +363 -105
package/src/html-reporter.ts
CHANGED
|
@@ -21,17 +21,62 @@ ${this.getStyles()}
|
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
${this.renderHeader(report)}
|
|
24
|
-
<div class="
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
<div class="report-layout">
|
|
25
|
+
<nav class="sidebar" id="reportSidebar">
|
|
26
|
+
<div class="sidebar-title">Navigation</div>
|
|
27
|
+
<a href="#score" class="sidebar-link active" data-section="score">📊 Score</a>
|
|
28
|
+
<a href="#layers" class="sidebar-link" data-section="layers">📐 Layers & Graph</a>
|
|
29
|
+
<a href="#anti-patterns" class="sidebar-link" data-section="anti-patterns">⚠️ Anti-Patterns (${report.antiPatterns.length})</a>
|
|
30
|
+
<a href="#suggestions" class="sidebar-link" data-section="suggestions">💡 Suggestions (${report.suggestions.length})</a>
|
|
31
|
+
${plan ? `<a href="#refactoring" class="sidebar-link" data-section="refactoring">🔧 Refactoring (${plan.steps.length})</a>` : ''}
|
|
32
|
+
${agentSuggestion ? `<a href="#agents" class="sidebar-link" data-section="agents">🤖 Agents</a>` : ''}
|
|
33
|
+
</nav>
|
|
34
|
+
<button class="sidebar-toggle" onclick="document.getElementById('reportSidebar').classList.toggle('sidebar-open')">☰</button>
|
|
35
|
+
|
|
36
|
+
<div class="container">
|
|
37
|
+
<div id="score">
|
|
38
|
+
${this.renderScoreHero(report)}
|
|
39
|
+
${this.renderRadarChart(report)}
|
|
40
|
+
${this.renderStats(report)}
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<details class="section-accordion" id="layers" open>
|
|
44
|
+
<summary class="section-accordion-header">📐 Layer Analysis & Dependencies</summary>
|
|
45
|
+
<div class="section-accordion-body">
|
|
46
|
+
${this.renderLayers(report)}
|
|
47
|
+
${this.renderDependencyGraph(report)}
|
|
48
|
+
</div>
|
|
49
|
+
</details>
|
|
50
|
+
|
|
51
|
+
<details class="section-accordion" id="anti-patterns" open>
|
|
52
|
+
<summary class="section-accordion-header">⚠️ Anti-Patterns (${report.antiPatterns.length})</summary>
|
|
53
|
+
<div class="section-accordion-body">
|
|
54
|
+
${this.renderAntiPatternBubbles(report, grouped)}
|
|
55
|
+
${this.renderAntiPatterns(report, grouped)}
|
|
56
|
+
</div>
|
|
57
|
+
</details>
|
|
58
|
+
|
|
59
|
+
<details class="section-accordion" id="suggestions">
|
|
60
|
+
<summary class="section-accordion-header">💡 Suggestions (${report.suggestions.length})</summary>
|
|
61
|
+
<div class="section-accordion-body">
|
|
62
|
+
${this.renderSuggestions(sugGrouped)}
|
|
63
|
+
</div>
|
|
64
|
+
</details>
|
|
65
|
+
|
|
66
|
+
${plan ? `<details class="section-accordion" id="refactoring" open>
|
|
67
|
+
<summary class="section-accordion-header">🔧 Refactoring Plan (${plan.steps.length} steps, ${plan.totalOperations} operations)</summary>
|
|
68
|
+
<div class="section-accordion-body">
|
|
69
|
+
${this.renderRefactoringPlan(plan)}
|
|
70
|
+
</div>
|
|
71
|
+
</details>` : ''}
|
|
72
|
+
|
|
73
|
+
${agentSuggestion ? `<details class="section-accordion" id="agents" open>
|
|
74
|
+
<summary class="section-accordion-header">🤖 Agent System</summary>
|
|
75
|
+
<div class="section-accordion-body">
|
|
76
|
+
${this.renderAgentSuggestions(agentSuggestion)}
|
|
77
|
+
</div>
|
|
78
|
+
</details>` : ''}
|
|
79
|
+
</div>
|
|
35
80
|
</div>
|
|
36
81
|
${this.renderFooter()}
|
|
37
82
|
${this.getScripts(report)}
|
|
@@ -226,13 +271,21 @@ ${this.getScripts(report)}
|
|
|
226
271
|
private renderDependencyGraph(report: AnalysisReport): string {
|
|
227
272
|
if (report.dependencyGraph.edges.length === 0) return '';
|
|
228
273
|
|
|
229
|
-
// Build
|
|
274
|
+
// Build real file set — only files that appear as SOURCE in edges (these are real scanned files)
|
|
275
|
+
const realFiles = new Set(report.dependencyGraph.edges.map(e => e.from));
|
|
276
|
+
|
|
277
|
+
// Count connections only for real files
|
|
230
278
|
const connectionCount: Record<string, number> = {};
|
|
231
279
|
for (const edge of report.dependencyGraph.edges) {
|
|
232
|
-
|
|
233
|
-
|
|
280
|
+
if (realFiles.has(edge.from)) {
|
|
281
|
+
connectionCount[edge.from] = (connectionCount[edge.from] || 0) + 1;
|
|
282
|
+
}
|
|
283
|
+
if (realFiles.has(edge.to)) {
|
|
284
|
+
connectionCount[edge.to] = (connectionCount[edge.to] || 0) + 1;
|
|
285
|
+
}
|
|
234
286
|
}
|
|
235
287
|
|
|
288
|
+
// Build layer map from report layers
|
|
236
289
|
const layerMap: Record<string, string> = {};
|
|
237
290
|
for (const layer of report.layers) {
|
|
238
291
|
for (const file of layer.files) {
|
|
@@ -240,34 +293,55 @@ ${this.getScripts(report)}
|
|
|
240
293
|
}
|
|
241
294
|
}
|
|
242
295
|
|
|
243
|
-
|
|
296
|
+
// Create nodes only from real files
|
|
297
|
+
const allNodes = [...realFiles].map(n => ({
|
|
244
298
|
id: n,
|
|
245
299
|
name: n.split('/').pop() || n,
|
|
246
300
|
connections: connectionCount[n] || 0,
|
|
247
301
|
layer: layerMap[n] || 'Other',
|
|
248
302
|
}));
|
|
249
303
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
304
|
+
// Build links only between real files
|
|
305
|
+
const allLinks = report.dependencyGraph.edges
|
|
306
|
+
.filter(e => realFiles.has(e.from) && realFiles.has(e.to))
|
|
307
|
+
.map(e => ({ source: e.from, target: e.to }));
|
|
308
|
+
|
|
309
|
+
// Limit to top N most-connected nodes for large projects
|
|
310
|
+
const maxNodes = 60;
|
|
311
|
+
const sortedNodes = [...allNodes].sort((a, b) => b.connections - a.connections);
|
|
312
|
+
const limitedNodes = sortedNodes.slice(0, maxNodes);
|
|
313
|
+
const limitedNodeIds = new Set(limitedNodes.map(n => n.id));
|
|
314
|
+
const limitedLinks = allLinks.filter(l => limitedNodeIds.has(l.source) && limitedNodeIds.has(l.target));
|
|
315
|
+
const isLimited = allNodes.length > maxNodes;
|
|
316
|
+
|
|
317
|
+
// Collect unique layers from limited nodes
|
|
318
|
+
const uniqueLayers = [...new Set(limitedNodes.map(n => n.layer))];
|
|
254
319
|
|
|
255
320
|
return `
|
|
256
321
|
<h2 class="section-title">🔗 Dependency Graph</h2>
|
|
257
322
|
<div class="card graph-card">
|
|
258
|
-
<div class="graph-
|
|
259
|
-
<
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
323
|
+
<div class="graph-controls">
|
|
324
|
+
<div class="graph-legend">
|
|
325
|
+
<span class="legend-item"><span class="legend-dot" style="background: #ec4899"></span> API</span>
|
|
326
|
+
<span class="legend-item"><span class="legend-dot" style="background: #3b82f6"></span> Service</span>
|
|
327
|
+
<span class="legend-item"><span class="legend-dot" style="background: #10b981"></span> Data</span>
|
|
328
|
+
<span class="legend-item"><span class="legend-dot" style="background: #f59e0b"></span> UI</span>
|
|
329
|
+
<span class="legend-item"><span class="legend-dot" style="background: #8b5cf6"></span> Infra</span>
|
|
330
|
+
<span class="legend-item"><span class="legend-dot" style="background: #64748b"></span> Other</span>
|
|
331
|
+
</div>
|
|
332
|
+
<div class="graph-filters">
|
|
333
|
+
<input type="text" id="graphSearch" class="graph-search" placeholder="🔍 Search node..." oninput="filterGraphNodes(this.value)">
|
|
334
|
+
<div class="graph-layer-filters">
|
|
335
|
+
${uniqueLayers.map(l => `<label class="graph-filter-check"><input type="checkbox" checked data-layer="${l}" onchange="toggleGraphLayer('${l}', this.checked)"><span class="legend-dot" style="background: ${({'API': '#ec4899', 'Service': '#3b82f6', 'Data': '#10b981', 'UI': '#f59e0b', 'Infrastructure': '#8b5cf6'} as Record<string, string>)[l] || '#64748b'}"></span> ${l}</label>`).join('')}
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
${isLimited ? `<div class="graph-limit-notice">Showing top ${maxNodes} of ${allNodes.length} source files (most connected) · ${limitedLinks.length} links</div>` : ''}
|
|
265
339
|
</div>
|
|
266
|
-
<div id="dep-graph" style="width:100%; min-height:
|
|
267
|
-
<div class="graph-hint">🖱️ Drag nodes to
|
|
340
|
+
<div id="dep-graph" style="width:100%; min-height:500px;"></div>
|
|
341
|
+
<div class="graph-hint">🖱️ Drag nodes • Scroll to zoom • Double-click to reset • Node size = connections</div>
|
|
268
342
|
</div>
|
|
269
|
-
<script type="application/json" id="graph-nodes">${JSON.stringify(
|
|
270
|
-
<script type="application/json" id="graph-links">${JSON.stringify(
|
|
343
|
+
<script type="application/json" id="graph-nodes">${JSON.stringify(limitedNodes)}<\\/script>
|
|
344
|
+
<script type="application/json" id="graph-links">${JSON.stringify(limitedLinks)}<\\/script>`;
|
|
271
345
|
}
|
|
272
346
|
|
|
273
347
|
/**
|
|
@@ -534,10 +608,12 @@ ${this.getScripts(report)}
|
|
|
534
608
|
</details>
|
|
535
609
|
</div>
|
|
536
610
|
</div>
|
|
537
|
-
<
|
|
538
|
-
<
|
|
539
|
-
|
|
540
|
-
|
|
611
|
+
<details class="rstep-ops-accordion">
|
|
612
|
+
<summary class="rstep-ops-toggle">📋 Operations (${step.operations.length})</summary>
|
|
613
|
+
<div class="rstep-ops">
|
|
614
|
+
${operationsHtml}
|
|
615
|
+
</div>
|
|
616
|
+
</details>
|
|
541
617
|
<div class="rstep-impact">
|
|
542
618
|
<h4>📈 Score Impact</h4>
|
|
543
619
|
<div class="rimpact-tags">${impactHtml}</div>
|
|
@@ -566,6 +642,23 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
566
642
|
}, { threshold: 0.5 });
|
|
567
643
|
|
|
568
644
|
counters.forEach(c => observer.observe(c));
|
|
645
|
+
|
|
646
|
+
// ── Sidebar Active Section Tracking ──
|
|
647
|
+
const sectionIds = ['score', 'layers', 'anti-patterns', 'suggestions', 'refactoring', 'agents'];
|
|
648
|
+
const sectionObserver = new IntersectionObserver((entries) => {
|
|
649
|
+
entries.forEach(entry => {
|
|
650
|
+
if (entry.isIntersecting) {
|
|
651
|
+
document.querySelectorAll('.sidebar-link').forEach(l => l.classList.remove('active'));
|
|
652
|
+
const link = document.querySelector('.sidebar-link[data-section="' + entry.target.id + '"]');
|
|
653
|
+
if (link) link.classList.add('active');
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
}, { threshold: 0.15, rootMargin: '-80px 0px -60% 0px' });
|
|
657
|
+
|
|
658
|
+
sectionIds.forEach(id => {
|
|
659
|
+
const el = document.getElementById(id);
|
|
660
|
+
if (el) sectionObserver.observe(el);
|
|
661
|
+
});
|
|
569
662
|
});
|
|
570
663
|
|
|
571
664
|
function animateCounter(el, target) {
|
|
@@ -668,7 +761,7 @@ function animateCounter(el, target) {
|
|
|
668
761
|
|
|
669
762
|
const container = document.getElementById('dep-graph');
|
|
670
763
|
const width = container.clientWidth || 800;
|
|
671
|
-
const height =
|
|
764
|
+
const height = 500;
|
|
672
765
|
container.style.height = height + 'px';
|
|
673
766
|
|
|
674
767
|
const layerColors = {
|
|
@@ -680,28 +773,43 @@ function animateCounter(el, target) {
|
|
|
680
773
|
.attr('width', width).attr('height', height)
|
|
681
774
|
.attr('viewBox', [0, 0, width, height]);
|
|
682
775
|
|
|
776
|
+
// Zoom container
|
|
777
|
+
const g = svg.append('g');
|
|
778
|
+
|
|
779
|
+
// Zoom behavior
|
|
780
|
+
const zoom = d3.zoom()
|
|
781
|
+
.scaleExtent([0.2, 5])
|
|
782
|
+
.on('zoom', (event) => { g.attr('transform', event.transform); });
|
|
783
|
+
svg.call(zoom);
|
|
784
|
+
|
|
785
|
+
// Double-click to reset zoom
|
|
786
|
+
svg.on('dblclick.zoom', () => {
|
|
787
|
+
svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity);
|
|
788
|
+
});
|
|
789
|
+
|
|
683
790
|
// Arrow marker
|
|
684
|
-
|
|
791
|
+
g.append('defs').append('marker')
|
|
685
792
|
.attr('id', 'arrowhead').attr('viewBox', '-0 -5 10 10')
|
|
686
793
|
.attr('refX', 20).attr('refY', 0).attr('orient', 'auto')
|
|
687
794
|
.attr('markerWidth', 6).attr('markerHeight', 6)
|
|
688
795
|
.append('path').attr('d', 'M 0,-5 L 10,0 L 0,5')
|
|
689
796
|
.attr('fill', '#475569');
|
|
690
797
|
|
|
798
|
+
// Tuned simulation for better spread
|
|
691
799
|
const simulation = d3.forceSimulation(nodes)
|
|
692
|
-
.force('link', d3.forceLink(links).id(d => d.id).distance(
|
|
693
|
-
.force('charge', d3.forceManyBody().strength(-
|
|
800
|
+
.force('link', d3.forceLink(links).id(d => d.id).distance(80))
|
|
801
|
+
.force('charge', d3.forceManyBody().strength(-250))
|
|
694
802
|
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
695
|
-
.force('x', d3.forceX(width / 2).strength(0.
|
|
696
|
-
.force('y', d3.forceY(height / 2).strength(0.
|
|
697
|
-
.force('collision', d3.forceCollide().radius(d => Math.max(d.connections *
|
|
803
|
+
.force('x', d3.forceX(width / 2).strength(0.05))
|
|
804
|
+
.force('y', d3.forceY(height / 2).strength(0.05))
|
|
805
|
+
.force('collision', d3.forceCollide().radius(d => Math.max(d.connections * 2 + 16, 20)));
|
|
698
806
|
|
|
699
|
-
const link =
|
|
807
|
+
const link = g.append('g')
|
|
700
808
|
.selectAll('line').data(links).join('line')
|
|
701
|
-
.attr('stroke', '#334155').attr('stroke-width', 1
|
|
702
|
-
.attr('stroke-opacity', 0.
|
|
809
|
+
.attr('stroke', '#334155').attr('stroke-width', 1)
|
|
810
|
+
.attr('stroke-opacity', 0.4).attr('marker-end', 'url(#arrowhead)');
|
|
703
811
|
|
|
704
|
-
const node =
|
|
812
|
+
const node = g.append('g')
|
|
705
813
|
.selectAll('g').data(nodes).join('g')
|
|
706
814
|
.call(d3.drag()
|
|
707
815
|
.on('start', (e, d) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
|
|
@@ -709,37 +817,55 @@ function animateCounter(el, target) {
|
|
|
709
817
|
.on('end', (e, d) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; })
|
|
710
818
|
);
|
|
711
819
|
|
|
712
|
-
// Node circles —
|
|
820
|
+
// Node circles — color by layer
|
|
713
821
|
node.append('circle')
|
|
714
|
-
.attr('r', d => Math.max(d.connections *
|
|
822
|
+
.attr('r', d => Math.max(d.connections * 2.5 + 5, 6))
|
|
715
823
|
.attr('fill', d => layerColors[d.layer] || '#64748b')
|
|
716
|
-
.attr('stroke', '#0f172a').attr('stroke-width',
|
|
717
|
-
.attr('opacity', 0.
|
|
824
|
+
.attr('stroke', '#0f172a').attr('stroke-width', 1.5)
|
|
825
|
+
.attr('opacity', 0.9);
|
|
718
826
|
|
|
719
|
-
// Node labels
|
|
720
|
-
node.append('text')
|
|
827
|
+
// Node labels — only show for nodes with enough connections
|
|
828
|
+
node.filter(d => d.connections >= 2).append('text')
|
|
721
829
|
.text(d => d.name.replace(/\\.[^.]+$/, ''))
|
|
722
|
-
.attr('x', 0).attr('y', d => -(Math.max(d.connections *
|
|
830
|
+
.attr('x', 0).attr('y', d => -(Math.max(d.connections * 2.5 + 5, 6) + 4))
|
|
723
831
|
.attr('text-anchor', 'middle')
|
|
724
|
-
.attr('fill', '#
|
|
832
|
+
.attr('fill', '#e2e8f0').attr('font-size', '9px').attr('font-weight', '500');
|
|
725
833
|
|
|
726
|
-
// Tooltip
|
|
834
|
+
// Tooltip
|
|
727
835
|
node.append('title')
|
|
728
836
|
.text(d => d.id + '\\nConnections: ' + d.connections + '\\nLayer: ' + d.layer);
|
|
729
837
|
|
|
730
838
|
simulation.on('tick', () => {
|
|
731
|
-
// Clamp nodes to stay within SVG bounds
|
|
732
|
-
nodes.forEach(d => {
|
|
733
|
-
const r = Math.max(d.connections * 3 + 6, 8) + 10;
|
|
734
|
-
d.x = Math.max(r, Math.min(width - r, d.x));
|
|
735
|
-
d.y = Math.max(r, Math.min(height - r, d.y));
|
|
736
|
-
});
|
|
737
|
-
|
|
738
839
|
link
|
|
739
840
|
.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
|
|
740
841
|
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
|
|
741
842
|
node.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');
|
|
742
843
|
});
|
|
844
|
+
|
|
845
|
+
// Expose search and filter functions
|
|
846
|
+
window.filterGraphNodes = function(query) {
|
|
847
|
+
if (!query) {
|
|
848
|
+
node.attr('opacity', 1);
|
|
849
|
+
link.attr('opacity', 0.4);
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
query = query.toLowerCase();
|
|
853
|
+
node.attr('opacity', d => d.id.toLowerCase().includes(query) || d.name.toLowerCase().includes(query) ? 1 : 0.1);
|
|
854
|
+
link.attr('opacity', d => {
|
|
855
|
+
const srcMatch = d.source.id.toLowerCase().includes(query);
|
|
856
|
+
const tgtMatch = d.target.id.toLowerCase().includes(query);
|
|
857
|
+
return (srcMatch || tgtMatch) ? 0.6 : 0.05;
|
|
858
|
+
});
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
window.toggleGraphLayer = function(layer, visible) {
|
|
862
|
+
node.filter(d => d.layer === layer)
|
|
863
|
+
.transition().duration(300)
|
|
864
|
+
.attr('opacity', visible ? 1 : 0.05);
|
|
865
|
+
link.filter(d => d.source.layer === layer || d.target.layer === layer)
|
|
866
|
+
.transition().duration(300)
|
|
867
|
+
.attr('opacity', visible ? 0.4 : 0.02);
|
|
868
|
+
};
|
|
743
869
|
})();
|
|
744
870
|
|
|
745
871
|
// ── Bubble Chart ──
|
|
@@ -836,62 +962,69 @@ function animateCounter(el, target) {
|
|
|
836
962
|
return '#60a5fa';
|
|
837
963
|
};
|
|
838
964
|
|
|
965
|
+
// Status helpers
|
|
966
|
+
const statusBadge = (status: string): string => {
|
|
967
|
+
const map: Record<string, { icon: string; label: string; color: string }> = {
|
|
968
|
+
'KEEP': { icon: '✅', label: 'KEEP', color: '#22c55e' },
|
|
969
|
+
'MODIFY': { icon: '🔵', label: 'MODIFY', color: '#3b82f6' },
|
|
970
|
+
'CREATE': { icon: '🟡', label: 'NEW', color: '#f59e0b' },
|
|
971
|
+
'DELETE': { icon: '🔴', label: 'REMOVE', color: '#ef4444' },
|
|
972
|
+
};
|
|
973
|
+
const s = map[status] || map['CREATE'];
|
|
974
|
+
return `<span class="agent-status-badge" style="background:${s.color}20;color:${s.color};border:1px solid ${s.color}40">${s.icon} ${s.label}</span>`;
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
const statusBorder = (status: string): string => {
|
|
978
|
+
const map: Record<string, string> = {
|
|
979
|
+
'KEEP': '#22c55e', 'MODIFY': '#3b82f6', 'CREATE': '#f59e0b', 'DELETE': '#ef4444',
|
|
980
|
+
};
|
|
981
|
+
return map[status] || '#334155';
|
|
982
|
+
};
|
|
983
|
+
|
|
839
984
|
const agentCards = s.suggestedAgents.map(a =>
|
|
840
|
-
`<label class="agent-toggle-card" data-category="agents" data-name="${a}">
|
|
841
|
-
<input type="checkbox" class="agent-check" checked data-type="agents" data-item="${a}">
|
|
842
|
-
<div class="agent-toggle-inner">
|
|
843
|
-
<div class="agent-toggle-icon">${roleIcon(a)}</div>
|
|
985
|
+
`<label class="agent-toggle-card" data-category="agents" data-name="${a.name}">
|
|
986
|
+
<input type="checkbox" class="agent-check" ${a.status !== 'DELETE' ? 'checked' : ''} data-type="agents" data-item="${a.name}">
|
|
987
|
+
<div class="agent-toggle-inner" style="border-color:${statusBorder(a.status)}">
|
|
988
|
+
<div class="agent-toggle-icon">${roleIcon(a.name)}</div>
|
|
844
989
|
<div class="agent-toggle-info">
|
|
845
|
-
<span class="agent-toggle-name">${a}</span>
|
|
846
|
-
<span class="agent-toggle-role" style="color:${roleColor(a)}">${roleLabel(a)}</span>
|
|
990
|
+
<span class="agent-toggle-name">${a.name}</span>
|
|
991
|
+
<span class="agent-toggle-role" style="color:${roleColor(a.name)}">${roleLabel(a.name)}</span>
|
|
992
|
+
${a.description ? `<span class="agent-toggle-desc">${a.description}</span>` : ''}
|
|
847
993
|
</div>
|
|
994
|
+
${statusBadge(a.status)}
|
|
848
995
|
<div class="agent-toggle-check">\u2713</div>
|
|
849
996
|
</div>
|
|
850
997
|
</label>`
|
|
851
998
|
).join('\n');
|
|
852
999
|
|
|
853
|
-
const
|
|
854
|
-
`<label class="agent-toggle-card mini" data-category="
|
|
855
|
-
<input type="checkbox" class="agent-check" checked data-type="
|
|
856
|
-
<div class="agent-toggle-inner">
|
|
857
|
-
<span class="agent-toggle-icon"
|
|
858
|
-
<
|
|
1000
|
+
const miniCard = (item: { name: string; status: string; description?: string }, icon: string, type: string): string =>
|
|
1001
|
+
`<label class="agent-toggle-card mini" data-category="${type}">
|
|
1002
|
+
<input type="checkbox" class="agent-check" ${item.status !== 'DELETE' ? 'checked' : ''} data-type="${type}" data-item="${item.name}">
|
|
1003
|
+
<div class="agent-toggle-inner" style="border-color:${statusBorder(item.status)}">
|
|
1004
|
+
<span class="agent-toggle-icon">${icon}</span>
|
|
1005
|
+
<div class="agent-toggle-info">
|
|
1006
|
+
<span class="agent-toggle-name">${item.name}.md</span>
|
|
1007
|
+
${item.description ? `<span class="agent-toggle-desc">${item.description}</span>` : ''}
|
|
1008
|
+
</div>
|
|
1009
|
+
${statusBadge(item.status)}
|
|
859
1010
|
<div class="agent-toggle-check">\u2713</div>
|
|
860
1011
|
</div>
|
|
861
|
-
</label
|
|
862
|
-
).join('\n');
|
|
1012
|
+
</label>`;
|
|
863
1013
|
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
<div class="agent-toggle-inner">
|
|
868
|
-
<span class="agent-toggle-icon">\u{1F6E1}\uFE0F</span>
|
|
869
|
-
<span class="agent-toggle-name">${g}.md</span>
|
|
870
|
-
<div class="agent-toggle-check">\u2713</div>
|
|
871
|
-
</div>
|
|
872
|
-
</label>`
|
|
873
|
-
).join('\n');
|
|
874
|
-
|
|
875
|
-
const workflowCards = s.suggestedWorkflows.map(w =>
|
|
876
|
-
`<label class="agent-toggle-card mini" data-category="workflows">
|
|
877
|
-
<input type="checkbox" class="agent-check" checked data-type="workflows" data-item="${w}">
|
|
878
|
-
<div class="agent-toggle-inner">
|
|
879
|
-
<span class="agent-toggle-icon">\u26A1</span>
|
|
880
|
-
<span class="agent-toggle-name">${w}.md</span>
|
|
881
|
-
<div class="agent-toggle-check">\u2713</div>
|
|
882
|
-
</div>
|
|
883
|
-
</label>`
|
|
884
|
-
).join('\n');
|
|
1014
|
+
const ruleCards = s.suggestedRules.map(r => miniCard(r, '\u{1F4CF}', 'rules')).join('\n');
|
|
1015
|
+
const guardCards = s.suggestedGuards.map(g => miniCard(g, '\u{1F6E1}\uFE0F', 'guards')).join('\n');
|
|
1016
|
+
const workflowCards = s.suggestedWorkflows.map(w => miniCard(w, '\u26A1', 'workflows')).join('\n');
|
|
885
1017
|
|
|
886
1018
|
const skillCards = s.suggestedSkills.map(sk =>
|
|
887
1019
|
`<label class="agent-toggle-card" data-category="skills">
|
|
888
1020
|
<input type="checkbox" class="agent-check" checked data-type="skills" data-item="${sk.source}">
|
|
889
|
-
<div class="agent-toggle-inner">
|
|
1021
|
+
<div class="agent-toggle-inner" style="border-color:${statusBorder(sk.status)}">
|
|
890
1022
|
<span class="agent-toggle-icon">\u{1F9E0}</span>
|
|
891
1023
|
<div class="agent-toggle-info">
|
|
892
1024
|
<span class="agent-toggle-name">${sk.name}</span>
|
|
893
1025
|
<span class="agent-toggle-role" style="color:#34d399">${sk.description}</span>
|
|
894
1026
|
</div>
|
|
1027
|
+
${statusBadge(sk.status)}
|
|
895
1028
|
<div class="agent-toggle-check">\u2713</div>
|
|
896
1029
|
</div>
|
|
897
1030
|
</label>`
|
|
@@ -919,19 +1052,34 @@ function animateCounter(el, target) {
|
|
|
919
1052
|
`\u{1F527} ${s.stack.primary}`,
|
|
920
1053
|
`\u{1F4E6} ${s.stack.frameworks.length > 0 ? s.stack.frameworks.join(', ') : 'No framework'}`,
|
|
921
1054
|
s.hasExistingAgents ? '\u{1F4C1} Existing .agent/' : '\u{1F4C1} New .agent/',
|
|
922
|
-
|
|
1055
|
+
...(s.stack.hasBackend ? ['\u{1F519} Backend'] : []),
|
|
1056
|
+
...(s.stack.hasFrontend ? ['\u{1F5A5}\uFE0F Frontend'] : []),
|
|
1057
|
+
...(s.stack.hasMobile ? ['\u{1F4F1} Mobile'] : []),
|
|
1058
|
+
...(s.stack.hasDatabase ? ['\u{1F5C4}\uFE0F Database'] : []),
|
|
923
1059
|
];
|
|
924
1060
|
|
|
925
1061
|
const totalItems = s.suggestedAgents.length + s.suggestedRules.length + s.suggestedGuards.length + s.suggestedWorkflows.length + s.suggestedSkills.length;
|
|
926
1062
|
|
|
1063
|
+
// Status summary counts
|
|
1064
|
+
const allItems = [...s.suggestedAgents, ...s.suggestedRules, ...s.suggestedGuards, ...s.suggestedWorkflows];
|
|
1065
|
+
const keepCount = allItems.filter(i => i.status === 'KEEP').length;
|
|
1066
|
+
const modifyCount = allItems.filter(i => i.status === 'MODIFY').length;
|
|
1067
|
+
const createCount = allItems.filter(i => i.status === 'CREATE').length;
|
|
1068
|
+
|
|
927
1069
|
return `
|
|
928
|
-
<h2 class="section-title">\u{1F916} Agent System
|
|
1070
|
+
<h2 class="section-title">\u{1F916} Agent System</h2>
|
|
929
1071
|
|
|
930
1072
|
<div class="card agent-system-card">
|
|
931
1073
|
<div class="agent-stack-banner">
|
|
932
1074
|
${stackPills.map(p => `<div class="stack-pill">${p}</div>`).join('\n ')}
|
|
933
1075
|
</div>
|
|
934
1076
|
|
|
1077
|
+
<div class="agent-status-legend">
|
|
1078
|
+
<span class="status-legend-item"><span class="legend-dot" style="background:#22c55e"></span> KEEP (${keepCount})</span>
|
|
1079
|
+
<span class="status-legend-item"><span class="legend-dot" style="background:#3b82f6"></span> MODIFY (${modifyCount})</span>
|
|
1080
|
+
<span class="status-legend-item"><span class="legend-dot" style="background:#f59e0b"></span> NEW (${createCount})</span>
|
|
1081
|
+
</div>
|
|
1082
|
+
|
|
935
1083
|
<div class="agent-controls">
|
|
936
1084
|
<button class="agent-ctrl-btn" onclick="toggleAll(true)">\u2705 Select All</button>
|
|
937
1085
|
<button class="agent-ctrl-btn" onclick="toggleAll(false)">\u2B1C Select None</button>
|
|
@@ -979,18 +1127,22 @@ function animateCounter(el, target) {
|
|
|
979
1127
|
<style>
|
|
980
1128
|
.agent-system-card { padding: 1.5rem; }
|
|
981
1129
|
.agent-stack-banner { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
|
|
982
|
-
.stack-pill { background: #1e293b; border: 1px solid #334155; border-radius: 99px; padding: 0.4rem 1rem; font-size: 0.8rem; color: #94a3b8; white-space:
|
|
1130
|
+
.stack-pill { background: #1e293b; border: 1px solid #334155; border-radius: 99px; padding: 0.4rem 1rem; font-size: 0.8rem; color: #94a3b8; white-space: nowrap; }
|
|
1131
|
+
.agent-status-legend { display: flex; gap: 1.5rem; margin-bottom: 1rem; padding: 0.5rem 0; border-bottom: 1px solid #1e293b; }
|
|
1132
|
+
.status-legend-item { display: flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; color: #94a3b8; }
|
|
1133
|
+
.agent-status-badge { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.15rem 0.5rem; border-radius: 99px; font-size: 0.65rem; font-weight: 700; flex-shrink: 0; letter-spacing: 0.03em; }
|
|
1134
|
+
.agent-toggle-desc { display: block; font-size: 0.65rem; color: #64748b; margin-top: 0.15rem; line-height: 1.3; }
|
|
983
1135
|
.agent-controls { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem; }
|
|
984
1136
|
.agent-ctrl-btn { background: #1e293b; border: 1px solid #334155; color: #e2e8f0; padding: 0.4rem 1rem; border-radius: 8px; font-size: 0.8rem; cursor: pointer; transition: all 0.2s; }
|
|
985
1137
|
.agent-ctrl-btn:hover { background: #334155; }
|
|
986
1138
|
.agent-count-label { color: #94a3b8; font-size: 0.85rem; margin-left: auto; }
|
|
987
1139
|
#agentSelectedCount { color: #c084fc; font-weight: 700; }
|
|
988
1140
|
.agent-section-subtitle { color: #e2e8f0; font-size: 1.05rem; font-weight: 700; margin: 1.25rem 0 0.75rem; }
|
|
989
|
-
.agent-toggle-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(
|
|
1141
|
+
.agent-toggle-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }
|
|
990
1142
|
.agent-toggle-card { cursor: pointer; transition: all 0.3s; }
|
|
991
1143
|
.agent-toggle-card input { display: none; }
|
|
992
1144
|
.agent-toggle-inner { display: flex; align-items: center; gap: 0.75rem; background: #1e293b; border: 2px solid #334155; border-radius: 12px; padding: 0.75rem 1rem; transition: all 0.3s; }
|
|
993
|
-
.agent-toggle-card input:checked + .agent-toggle-inner {
|
|
1145
|
+
.agent-toggle-card input:checked + .agent-toggle-inner { background: #1e1b4b; }
|
|
994
1146
|
.agent-toggle-icon { font-size: 1.3rem; flex-shrink: 0; }
|
|
995
1147
|
.agent-toggle-info { flex: 1; min-width: 0; }
|
|
996
1148
|
.agent-toggle-name { display: block; color: #e2e8f0; font-weight: 600; font-size: 0.85rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
@@ -1062,7 +1214,50 @@ function animateCounter(el, target) {
|
|
|
1062
1214
|
min-height: 100vh;
|
|
1063
1215
|
}
|
|
1064
1216
|
|
|
1065
|
-
|
|
1217
|
+
html { scroll-behavior: smooth; }
|
|
1218
|
+
|
|
1219
|
+
/* ── Layout ── */
|
|
1220
|
+
.report-layout { display: flex; min-height: 100vh; }
|
|
1221
|
+
|
|
1222
|
+
.sidebar {
|
|
1223
|
+
position: sticky; top: 0; height: 100vh; width: 220px; min-width: 220px;
|
|
1224
|
+
background: linear-gradient(180deg, #0f172a 0%, #1e293b 100%);
|
|
1225
|
+
border-right: 1px solid #334155; padding: 1.5rem 0;
|
|
1226
|
+
display: flex; flex-direction: column; gap: 0.25rem;
|
|
1227
|
+
overflow-y: auto; z-index: 100;
|
|
1228
|
+
}
|
|
1229
|
+
.sidebar-title {
|
|
1230
|
+
font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
|
|
1231
|
+
letter-spacing: 0.15em; color: #475569; padding: 0 1.25rem; margin-bottom: 0.75rem;
|
|
1232
|
+
}
|
|
1233
|
+
.sidebar-link {
|
|
1234
|
+
display: flex; align-items: center; gap: 0.5rem; padding: 0.6rem 1.25rem;
|
|
1235
|
+
color: #94a3b8; text-decoration: none; font-size: 0.8rem; font-weight: 500;
|
|
1236
|
+
border-left: 3px solid transparent; transition: all 0.2s;
|
|
1237
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
1238
|
+
}
|
|
1239
|
+
.sidebar-link:hover { color: #e2e8f0; background: #1e293b; border-left-color: #475569; }
|
|
1240
|
+
.sidebar-link.active { color: #c084fc; background: #c084fc10; border-left-color: #c084fc; font-weight: 700; }
|
|
1241
|
+
|
|
1242
|
+
.sidebar-toggle {
|
|
1243
|
+
display: none; position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 200;
|
|
1244
|
+
width: 48px; height: 48px; border-radius: 50%; border: none;
|
|
1245
|
+
background: #c084fc; color: #0f172a; font-size: 1.2rem; cursor: pointer;
|
|
1246
|
+
box-shadow: 0 4px 16px rgba(192,132,252,0.4); transition: all 0.2s;
|
|
1247
|
+
}
|
|
1248
|
+
.sidebar-toggle:hover { transform: scale(1.1); }
|
|
1249
|
+
|
|
1250
|
+
@media (max-width: 1024px) {
|
|
1251
|
+
.sidebar {
|
|
1252
|
+
position: fixed; left: -240px; top: 0; width: 240px; min-width: 240px;
|
|
1253
|
+
transition: left 0.3s ease; box-shadow: none;
|
|
1254
|
+
}
|
|
1255
|
+
.sidebar.sidebar-open { left: 0; box-shadow: 4px 0 24px rgba(0,0,0,0.5); }
|
|
1256
|
+
.sidebar-toggle { display: flex; align-items: center; justify-content: center; }
|
|
1257
|
+
.report-layout { flex-direction: column; }
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 2rem; flex: 1; min-width: 0; }
|
|
1066
1261
|
|
|
1067
1262
|
/* ── Header ── */
|
|
1068
1263
|
.header {
|
|
@@ -1141,6 +1336,44 @@ function animateCounter(el, target) {
|
|
|
1141
1336
|
display: flex; align-items: center; gap: 0.5rem;
|
|
1142
1337
|
}
|
|
1143
1338
|
|
|
1339
|
+
/* ── Section Accordion ── */
|
|
1340
|
+
.section-accordion {
|
|
1341
|
+
margin: 1.5rem 0; border: 1px solid #334155; border-radius: 16px;
|
|
1342
|
+
background: transparent; overflow: hidden;
|
|
1343
|
+
}
|
|
1344
|
+
.section-accordion-header {
|
|
1345
|
+
cursor: pointer; list-style: none; display: flex; align-items: center; gap: 0.75rem;
|
|
1346
|
+
font-size: 1.3rem; font-weight: 700; color: #e2e8f0;
|
|
1347
|
+
padding: 1.25rem 1.5rem; background: linear-gradient(135deg, #1e293b, #0f172a);
|
|
1348
|
+
border-bottom: 1px solid transparent; transition: all 0.3s; user-select: none;
|
|
1349
|
+
}
|
|
1350
|
+
.section-accordion-header:hover { background: linear-gradient(135deg, #334155, #1e293b); }
|
|
1351
|
+
.section-accordion[open] > .section-accordion-header { border-bottom-color: #334155; }
|
|
1352
|
+
.section-accordion-header::after {
|
|
1353
|
+
content: '\\25B6'; margin-left: auto; font-size: 0.8rem; color: #818cf8;
|
|
1354
|
+
transition: transform 0.3s;
|
|
1355
|
+
}
|
|
1356
|
+
.section-accordion[open] > .section-accordion-header::after { transform: rotate(90deg); }
|
|
1357
|
+
.section-accordion-header::-webkit-details-marker { display: none; }
|
|
1358
|
+
.section-accordion-body { padding: 0.5rem 0; }
|
|
1359
|
+
|
|
1360
|
+
/* ── Operations Accordion (inside refactoring steps) ── */
|
|
1361
|
+
.rstep-ops-accordion {
|
|
1362
|
+
margin: 0.75rem 0; border: 1px solid #1e293b; border-radius: 10px; overflow: hidden;
|
|
1363
|
+
}
|
|
1364
|
+
.rstep-ops-toggle {
|
|
1365
|
+
cursor: pointer; list-style: none; display: flex; align-items: center; gap: 0.5rem;
|
|
1366
|
+
font-size: 0.9rem; font-weight: 600; color: #94a3b8;
|
|
1367
|
+
padding: 0.75rem 1rem; background: #0f172a; transition: all 0.2s;
|
|
1368
|
+
}
|
|
1369
|
+
.rstep-ops-toggle:hover { background: #1e293b; color: #e2e8f0; }
|
|
1370
|
+
.rstep-ops-toggle::after {
|
|
1371
|
+
content: '\\25B6'; margin-left: auto; font-size: 0.65rem; color: #818cf8;
|
|
1372
|
+
transition: transform 0.3s;
|
|
1373
|
+
}
|
|
1374
|
+
.rstep-ops-accordion[open] > .rstep-ops-toggle::after { transform: rotate(90deg); }
|
|
1375
|
+
.rstep-ops-toggle::-webkit-details-marker { display: none; }
|
|
1376
|
+
|
|
1144
1377
|
/* ── Cards ── */
|
|
1145
1378
|
.card {
|
|
1146
1379
|
background: #1e293b; border-radius: 16px; border: 1px solid #334155;
|
|
@@ -1150,17 +1383,42 @@ function animateCounter(el, target) {
|
|
|
1150
1383
|
|
|
1151
1384
|
/* ── Graph ── */
|
|
1152
1385
|
.graph-card { padding: 1rem; }
|
|
1386
|
+
.graph-controls { margin-bottom: 0.75rem; }
|
|
1153
1387
|
.graph-legend {
|
|
1154
1388
|
display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.5rem;
|
|
1155
1389
|
justify-content: center;
|
|
1156
1390
|
}
|
|
1157
1391
|
.legend-item { display: flex; align-items: center; gap: 4px; font-size: 0.75rem; color: #94a3b8; }
|
|
1158
|
-
.legend-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
|
|
1392
|
+
.legend-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
|
|
1393
|
+
.graph-filters {
|
|
1394
|
+
display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap;
|
|
1395
|
+
justify-content: center; margin-top: 0.5rem;
|
|
1396
|
+
}
|
|
1397
|
+
.graph-search {
|
|
1398
|
+
background: #0f172a; border: 1px solid #334155; border-radius: 8px;
|
|
1399
|
+
padding: 0.4rem 0.75rem; color: #e2e8f0; font-size: 0.8rem;
|
|
1400
|
+
outline: none; width: 180px; transition: border-color 0.2s;
|
|
1401
|
+
}
|
|
1402
|
+
.graph-search:focus { border-color: #818cf8; }
|
|
1403
|
+
.graph-layer-filters {
|
|
1404
|
+
display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;
|
|
1405
|
+
}
|
|
1406
|
+
.graph-filter-check {
|
|
1407
|
+
display: flex; align-items: center; gap: 4px;
|
|
1408
|
+
font-size: 0.75rem; color: #94a3b8; cursor: pointer;
|
|
1409
|
+
}
|
|
1410
|
+
.graph-filter-check input { width: 14px; height: 14px; accent-color: #818cf8; }
|
|
1411
|
+
.graph-limit-notice {
|
|
1412
|
+
text-align: center; font-size: 0.75rem; color: #f59e0b;
|
|
1413
|
+
background: #f59e0b15; padding: 0.3rem 0.75rem; border-radius: 6px;
|
|
1414
|
+
margin-top: 0.5rem;
|
|
1415
|
+
}
|
|
1159
1416
|
.graph-hint {
|
|
1160
1417
|
text-align: center; font-size: 0.75rem; color: #475569; margin-top: 0.5rem;
|
|
1161
1418
|
font-style: italic;
|
|
1162
1419
|
}
|
|
1163
|
-
#dep-graph svg { background: rgba(0,0,0,0.2); border-radius: 12px; }
|
|
1420
|
+
#dep-graph svg { background: rgba(0,0,0,0.2); border-radius: 12px; cursor: grab; }
|
|
1421
|
+
#dep-graph svg:active { cursor: grabbing; }
|
|
1164
1422
|
|
|
1165
1423
|
/* ── Layers Grid ── */
|
|
1166
1424
|
.layers-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; }
|