@girardelli/architect 1.3.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/README.md +111 -112
- package/dist/agent-generator.d.ts +106 -0
- package/dist/agent-generator.d.ts.map +1 -0
- package/dist/agent-generator.js +1398 -0
- package/dist/agent-generator.js.map +1 -0
- package/dist/cli.js +132 -15
- package/dist/cli.js.map +1 -1
- package/dist/html-reporter.d.ts +8 -2
- package/dist/html-reporter.d.ts.map +1 -1
- package/dist/html-reporter.js +773 -50
- package/dist/html-reporter.js.map +1 -1
- package/dist/index.d.ts +26 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/index.js.map +1 -1
- package/dist/refactor-engine.d.ts +18 -0
- package/dist/refactor-engine.d.ts.map +1 -0
- package/dist/refactor-engine.js +86 -0
- package/dist/refactor-engine.js.map +1 -0
- package/dist/refactor-reporter.d.ts +20 -0
- package/dist/refactor-reporter.d.ts.map +1 -0
- package/dist/refactor-reporter.js +389 -0
- package/dist/refactor-reporter.js.map +1 -0
- package/dist/rules/barrel-optimizer.d.ts +13 -0
- package/dist/rules/barrel-optimizer.d.ts.map +1 -0
- package/dist/rules/barrel-optimizer.js +77 -0
- package/dist/rules/barrel-optimizer.js.map +1 -0
- package/dist/rules/dead-code-detector.d.ts +21 -0
- package/dist/rules/dead-code-detector.d.ts.map +1 -0
- package/dist/rules/dead-code-detector.js +117 -0
- package/dist/rules/dead-code-detector.js.map +1 -0
- package/dist/rules/hub-splitter.d.ts +13 -0
- package/dist/rules/hub-splitter.d.ts.map +1 -0
- package/dist/rules/hub-splitter.js +110 -0
- package/dist/rules/hub-splitter.js.map +1 -0
- package/dist/rules/import-organizer.d.ts +13 -0
- package/dist/rules/import-organizer.d.ts.map +1 -0
- package/dist/rules/import-organizer.js +85 -0
- package/dist/rules/import-organizer.js.map +1 -0
- package/dist/rules/module-grouper.d.ts +13 -0
- package/dist/rules/module-grouper.d.ts.map +1 -0
- package/dist/rules/module-grouper.js +110 -0
- package/dist/rules/module-grouper.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agent-generator.ts +1526 -0
- package/src/cli.ts +150 -15
- package/src/html-reporter.ts +799 -51
- package/src/index.ts +39 -1
- package/src/refactor-engine.ts +117 -0
- package/src/refactor-reporter.ts +408 -0
- package/src/rules/barrel-optimizer.ts +97 -0
- package/src/rules/dead-code-detector.ts +132 -0
- package/src/rules/hub-splitter.ts +123 -0
- package/src/rules/import-organizer.ts +98 -0
- package/src/rules/module-grouper.ts +124 -0
- package/src/types.ts +52 -0
package/src/html-reporter.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { AnalysisReport, AntiPattern } from './types.js';
|
|
1
|
+
import { AnalysisReport, AntiPattern, RefactoringPlan, RefactorStep } from './types.js';
|
|
2
|
+
import { AgentSuggestion } from './agent-generator.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Generates premium visual HTML reports from AnalysisReport.
|
|
5
6
|
* Features: D3.js force graph, bubble charts, radar chart, animated counters.
|
|
6
7
|
*/
|
|
7
8
|
export class HtmlReportGenerator {
|
|
8
|
-
generateHtml(report: AnalysisReport): string {
|
|
9
|
+
generateHtml(report: AnalysisReport, plan?: RefactoringPlan, agentSuggestion?: AgentSuggestion): string {
|
|
9
10
|
const grouped = this.groupAntiPatterns(report.antiPatterns);
|
|
10
11
|
const sugGrouped = this.groupSuggestions(report.suggestions);
|
|
11
12
|
|
|
@@ -20,15 +21,62 @@ ${this.getStyles()}
|
|
|
20
21
|
</head>
|
|
21
22
|
<body>
|
|
22
23
|
${this.renderHeader(report)}
|
|
23
|
-
<div class="
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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>
|
|
32
80
|
</div>
|
|
33
81
|
${this.renderFooter()}
|
|
34
82
|
${this.getScripts(report)}
|
|
@@ -223,13 +271,21 @@ ${this.getScripts(report)}
|
|
|
223
271
|
private renderDependencyGraph(report: AnalysisReport): string {
|
|
224
272
|
if (report.dependencyGraph.edges.length === 0) return '';
|
|
225
273
|
|
|
226
|
-
// 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
|
|
227
278
|
const connectionCount: Record<string, number> = {};
|
|
228
279
|
for (const edge of report.dependencyGraph.edges) {
|
|
229
|
-
|
|
230
|
-
|
|
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
|
+
}
|
|
231
286
|
}
|
|
232
287
|
|
|
288
|
+
// Build layer map from report layers
|
|
233
289
|
const layerMap: Record<string, string> = {};
|
|
234
290
|
for (const layer of report.layers) {
|
|
235
291
|
for (const file of layer.files) {
|
|
@@ -237,34 +293,55 @@ ${this.getScripts(report)}
|
|
|
237
293
|
}
|
|
238
294
|
}
|
|
239
295
|
|
|
240
|
-
|
|
296
|
+
// Create nodes only from real files
|
|
297
|
+
const allNodes = [...realFiles].map(n => ({
|
|
241
298
|
id: n,
|
|
242
299
|
name: n.split('/').pop() || n,
|
|
243
300
|
connections: connectionCount[n] || 0,
|
|
244
301
|
layer: layerMap[n] || 'Other',
|
|
245
302
|
}));
|
|
246
303
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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))];
|
|
251
319
|
|
|
252
320
|
return `
|
|
253
321
|
<h2 class="section-title">🔗 Dependency Graph</h2>
|
|
254
322
|
<div class="card graph-card">
|
|
255
|
-
<div class="graph-
|
|
256
|
-
<
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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>` : ''}
|
|
262
339
|
</div>
|
|
263
|
-
<div id="dep-graph" style="width:100%; min-height:
|
|
264
|
-
<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>
|
|
265
342
|
</div>
|
|
266
|
-
<script type="application/json" id="graph-nodes">${JSON.stringify(
|
|
267
|
-
<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>`;
|
|
268
345
|
}
|
|
269
346
|
|
|
270
347
|
/**
|
|
@@ -388,11 +465,162 @@ ${this.getScripts(report)}
|
|
|
388
465
|
private renderFooter(): string {
|
|
389
466
|
return `
|
|
390
467
|
<div class="footer">
|
|
391
|
-
<p>Generated by <a href="https://github.com/camilooscargbaptista/architect">🏗️ Architect</a> — AI-powered architecture analysis</p>
|
|
468
|
+
<p>Generated by <a href="https://github.com/camilooscargbaptista/architect">🏗️ Architect v2.0</a> — AI-powered architecture analysis + refactoring engine</p>
|
|
392
469
|
<p>By <strong>Camilo Girardelli</strong> · <a href="https://www.girardellitecnologia.com">Girardelli Tecnologia</a></p>
|
|
393
470
|
</div>`;
|
|
394
471
|
}
|
|
395
472
|
|
|
473
|
+
// ── Refactoring Plan Section ──
|
|
474
|
+
|
|
475
|
+
private opColor(type: string): string {
|
|
476
|
+
switch (type) {
|
|
477
|
+
case 'CREATE': return '#22c55e';
|
|
478
|
+
case 'MOVE': return '#3b82f6';
|
|
479
|
+
case 'MODIFY': return '#f59e0b';
|
|
480
|
+
case 'DELETE': return '#ef4444';
|
|
481
|
+
default: return '#64748b';
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private opIcon(type: string): string {
|
|
486
|
+
switch (type) {
|
|
487
|
+
case 'CREATE': return '➕';
|
|
488
|
+
case 'MOVE': return '📦';
|
|
489
|
+
case 'MODIFY': return '✏️';
|
|
490
|
+
case 'DELETE': return '🗑️';
|
|
491
|
+
default: return '📄';
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private renderRefactoringPlan(plan: RefactoringPlan): string {
|
|
496
|
+
if (plan.steps.length === 0) {
|
|
497
|
+
return `
|
|
498
|
+
<h2 class="section-title">✅ Refactoring Plan</h2>
|
|
499
|
+
<div class="card success-card">
|
|
500
|
+
<p>No refactoring needed! Your architecture is already in great shape.</p>
|
|
501
|
+
</div>`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const improvement = plan.estimatedScoreAfter.overall - plan.currentScore.overall;
|
|
505
|
+
|
|
506
|
+
const metrics = Object.keys(plan.currentScore.breakdown) as Array<keyof typeof plan.currentScore.breakdown>;
|
|
507
|
+
const bars = metrics.map(metric => {
|
|
508
|
+
const before = plan.currentScore.breakdown[metric];
|
|
509
|
+
const after = plan.estimatedScoreAfter.breakdown[metric] ?? before;
|
|
510
|
+
const diff = after - before;
|
|
511
|
+
return `
|
|
512
|
+
<div class="comparison-row">
|
|
513
|
+
<div class="refactor-metric-name">${metric}</div>
|
|
514
|
+
<div class="refactor-metric-bars">
|
|
515
|
+
<div class="rbar-before" style="width: ${before}%; background: ${this.scoreColor(before)}40"><span>${before}</span></div>
|
|
516
|
+
<div class="rbar-after" style="width: ${after}%; background: ${this.scoreColor(after)}"><span>${after}</span></div>
|
|
517
|
+
</div>
|
|
518
|
+
<div class="refactor-metric-diff" style="color: ${diff > 0 ? '#22c55e' : '#64748b'}">
|
|
519
|
+
${diff > 0 ? `+${diff}` : diff === 0 ? '—' : String(diff)}
|
|
520
|
+
</div>
|
|
521
|
+
</div>`;
|
|
522
|
+
}).join('');
|
|
523
|
+
|
|
524
|
+
const stepsHtml = plan.steps.map(step => this.renderRefactorStep(step)).join('');
|
|
525
|
+
|
|
526
|
+
const criticalCount = plan.steps.filter(s => s.priority === 'CRITICAL').length;
|
|
527
|
+
const highCount = plan.steps.filter(s => s.priority === 'HIGH').length;
|
|
528
|
+
const mediumCount = plan.steps.filter(s => s.priority === 'MEDIUM').length;
|
|
529
|
+
const lowCount = plan.steps.filter(s => s.priority === 'LOW').length;
|
|
530
|
+
|
|
531
|
+
return `
|
|
532
|
+
<h2 class="section-title">🔧 Refactoring Plan</h2>
|
|
533
|
+
|
|
534
|
+
<div class="card refactor-score">
|
|
535
|
+
<div class="refactor-score-pair">
|
|
536
|
+
<div class="rscore-box">
|
|
537
|
+
<div class="rscore-num" style="color: ${this.scoreColor(plan.currentScore.overall)}">${plan.currentScore.overall}</div>
|
|
538
|
+
<div class="rscore-label">Current</div>
|
|
539
|
+
</div>
|
|
540
|
+
<div class="rscore-arrow">
|
|
541
|
+
<svg width="60" height="30" viewBox="0 0 60 30">
|
|
542
|
+
<path d="M5 15 L45 15 M40 8 L48 15 L40 22" stroke="#818cf8" stroke-width="2.5" fill="none"/>
|
|
543
|
+
</svg>
|
|
544
|
+
</div>
|
|
545
|
+
<div class="rscore-box">
|
|
546
|
+
<div class="rscore-num" style="color: ${this.scoreColor(plan.estimatedScoreAfter.overall)}">${plan.estimatedScoreAfter.overall}</div>
|
|
547
|
+
<div class="rscore-label">Estimated</div>
|
|
548
|
+
</div>
|
|
549
|
+
<div class="rscore-improvement" style="color: #22c55e">+${improvement} pts</div>
|
|
550
|
+
</div>
|
|
551
|
+
<div class="refactor-bars-section">
|
|
552
|
+
<div class="refactor-legend">
|
|
553
|
+
<span class="rlegend-tag rbefore">Before</span>
|
|
554
|
+
<span class="rlegend-tag rafter">After</span>
|
|
555
|
+
</div>
|
|
556
|
+
${bars}
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
|
|
560
|
+
<div class="refactor-stats-row">
|
|
561
|
+
<div class="rstat">${plan.steps.length} steps</div>
|
|
562
|
+
<div class="rstat">${plan.totalOperations} operations</div>
|
|
563
|
+
<div class="rstat">Tier 1: ${plan.tier1Steps}</div>
|
|
564
|
+
<div class="rstat">Tier 2: ${plan.tier2Steps}</div>
|
|
565
|
+
</div>
|
|
566
|
+
|
|
567
|
+
<div class="priority-bar">
|
|
568
|
+
${criticalCount ? `<div class="prio-seg prio-critical" style="flex: ${criticalCount}">🔴 ${criticalCount}</div>` : ''}
|
|
569
|
+
${highCount ? `<div class="prio-seg prio-high" style="flex: ${highCount}">🟠 ${highCount}</div>` : ''}
|
|
570
|
+
${mediumCount ? `<div class="prio-seg prio-medium" style="flex: ${mediumCount}">🔵 ${mediumCount}</div>` : ''}
|
|
571
|
+
${lowCount ? `<div class="prio-seg prio-low" style="flex: ${lowCount}">🟢 ${lowCount}</div>` : ''}
|
|
572
|
+
</div>
|
|
573
|
+
|
|
574
|
+
<div class="refactor-roadmap">
|
|
575
|
+
${stepsHtml}
|
|
576
|
+
</div>`;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private renderRefactorStep(step: RefactorStep): string {
|
|
580
|
+
const operationsHtml = step.operations.map(op => `
|
|
581
|
+
<div class="rop">
|
|
582
|
+
<span class="rop-icon">${this.opIcon(op.type)}</span>
|
|
583
|
+
<span class="rop-badge" style="background: ${this.opColor(op.type)}20; color: ${this.opColor(op.type)}; border: 1px solid ${this.opColor(op.type)}40">${op.type}</span>
|
|
584
|
+
<code class="rop-path">${this.escapeHtml(op.path)}</code>
|
|
585
|
+
${op.newPath ? `<span class="rop-arrow">→</span> <code class="rop-path">${this.escapeHtml(op.newPath)}</code>` : ''}
|
|
586
|
+
<div class="rop-desc">${this.escapeHtml(op.description)}</div>
|
|
587
|
+
</div>
|
|
588
|
+
`).join('');
|
|
589
|
+
|
|
590
|
+
const impactHtml = step.scoreImpact.map(i =>
|
|
591
|
+
`<span class="rimpact-tag">${i.metric}: ${i.before}→${i.after} <strong>+${i.after - i.before}</strong></span>`
|
|
592
|
+
).join('');
|
|
593
|
+
|
|
594
|
+
return `
|
|
595
|
+
<div class="rstep-card">
|
|
596
|
+
<div class="rstep-header">
|
|
597
|
+
<div class="rstep-number">${step.id}</div>
|
|
598
|
+
<div class="rstep-info">
|
|
599
|
+
<div class="rstep-title-row">
|
|
600
|
+
<h3>${this.escapeHtml(step.title)}</h3>
|
|
601
|
+
<span class="severity-badge severity-${step.priority}">${step.priority}</span>
|
|
602
|
+
<span class="tier-badge">Tier ${step.tier}</span>
|
|
603
|
+
</div>
|
|
604
|
+
<p class="rstep-desc">${this.escapeHtml(step.description)}</p>
|
|
605
|
+
<details class="rstep-details">
|
|
606
|
+
<summary>📖 Why?</summary>
|
|
607
|
+
<p class="rstep-rationale">${this.escapeHtml(step.rationale)}</p>
|
|
608
|
+
</details>
|
|
609
|
+
</div>
|
|
610
|
+
</div>
|
|
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>
|
|
617
|
+
<div class="rstep-impact">
|
|
618
|
+
<h4>📈 Score Impact</h4>
|
|
619
|
+
<div class="rimpact-tags">${impactHtml}</div>
|
|
620
|
+
</div>
|
|
621
|
+
</div>`;
|
|
622
|
+
}
|
|
623
|
+
|
|
396
624
|
/**
|
|
397
625
|
* All JavaScript for D3.js visualizations, animated counters, and radar chart
|
|
398
626
|
*/
|
|
@@ -414,6 +642,23 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
414
642
|
}, { threshold: 0.5 });
|
|
415
643
|
|
|
416
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
|
+
});
|
|
417
662
|
});
|
|
418
663
|
|
|
419
664
|
function animateCounter(el, target) {
|
|
@@ -516,7 +761,7 @@ function animateCounter(el, target) {
|
|
|
516
761
|
|
|
517
762
|
const container = document.getElementById('dep-graph');
|
|
518
763
|
const width = container.clientWidth || 800;
|
|
519
|
-
const height =
|
|
764
|
+
const height = 500;
|
|
520
765
|
container.style.height = height + 'px';
|
|
521
766
|
|
|
522
767
|
const layerColors = {
|
|
@@ -528,26 +773,43 @@ function animateCounter(el, target) {
|
|
|
528
773
|
.attr('width', width).attr('height', height)
|
|
529
774
|
.attr('viewBox', [0, 0, width, height]);
|
|
530
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
|
+
|
|
531
790
|
// Arrow marker
|
|
532
|
-
|
|
791
|
+
g.append('defs').append('marker')
|
|
533
792
|
.attr('id', 'arrowhead').attr('viewBox', '-0 -5 10 10')
|
|
534
793
|
.attr('refX', 20).attr('refY', 0).attr('orient', 'auto')
|
|
535
794
|
.attr('markerWidth', 6).attr('markerHeight', 6)
|
|
536
795
|
.append('path').attr('d', 'M 0,-5 L 10,0 L 0,5')
|
|
537
796
|
.attr('fill', '#475569');
|
|
538
797
|
|
|
798
|
+
// Tuned simulation for better spread
|
|
539
799
|
const simulation = d3.forceSimulation(nodes)
|
|
540
800
|
.force('link', d3.forceLink(links).id(d => d.id).distance(80))
|
|
541
|
-
.force('charge', d3.forceManyBody().strength(-
|
|
801
|
+
.force('charge', d3.forceManyBody().strength(-250))
|
|
542
802
|
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
543
|
-
.force('
|
|
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)));
|
|
544
806
|
|
|
545
|
-
const link =
|
|
807
|
+
const link = g.append('g')
|
|
546
808
|
.selectAll('line').data(links).join('line')
|
|
547
|
-
.attr('stroke', '#334155').attr('stroke-width', 1
|
|
548
|
-
.attr('stroke-opacity', 0.
|
|
809
|
+
.attr('stroke', '#334155').attr('stroke-width', 1)
|
|
810
|
+
.attr('stroke-opacity', 0.4).attr('marker-end', 'url(#arrowhead)');
|
|
549
811
|
|
|
550
|
-
const node =
|
|
812
|
+
const node = g.append('g')
|
|
551
813
|
.selectAll('g').data(nodes).join('g')
|
|
552
814
|
.call(d3.drag()
|
|
553
815
|
.on('start', (e, d) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
|
|
@@ -555,21 +817,21 @@ function animateCounter(el, target) {
|
|
|
555
817
|
.on('end', (e, d) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; })
|
|
556
818
|
);
|
|
557
819
|
|
|
558
|
-
// Node circles —
|
|
820
|
+
// Node circles — color by layer
|
|
559
821
|
node.append('circle')
|
|
560
|
-
.attr('r', d => Math.max(d.connections *
|
|
822
|
+
.attr('r', d => Math.max(d.connections * 2.5 + 5, 6))
|
|
561
823
|
.attr('fill', d => layerColors[d.layer] || '#64748b')
|
|
562
|
-
.attr('stroke', '#0f172a').attr('stroke-width',
|
|
563
|
-
.attr('opacity', 0.
|
|
824
|
+
.attr('stroke', '#0f172a').attr('stroke-width', 1.5)
|
|
825
|
+
.attr('opacity', 0.9);
|
|
564
826
|
|
|
565
|
-
// Node labels
|
|
566
|
-
node.append('text')
|
|
827
|
+
// Node labels — only show for nodes with enough connections
|
|
828
|
+
node.filter(d => d.connections >= 2).append('text')
|
|
567
829
|
.text(d => d.name.replace(/\\.[^.]+$/, ''))
|
|
568
|
-
.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))
|
|
569
831
|
.attr('text-anchor', 'middle')
|
|
570
|
-
.attr('fill', '#
|
|
832
|
+
.attr('fill', '#e2e8f0').attr('font-size', '9px').attr('font-weight', '500');
|
|
571
833
|
|
|
572
|
-
// Tooltip
|
|
834
|
+
// Tooltip
|
|
573
835
|
node.append('title')
|
|
574
836
|
.text(d => d.id + '\\nConnections: ' + d.connections + '\\nLayer: ' + d.layer);
|
|
575
837
|
|
|
@@ -579,6 +841,31 @@ function animateCounter(el, target) {
|
|
|
579
841
|
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
|
|
580
842
|
node.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');
|
|
581
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
|
+
};
|
|
582
869
|
})();
|
|
583
870
|
|
|
584
871
|
// ── Bubble Chart ──
|
|
@@ -648,6 +935,271 @@ function animateCounter(el, target) {
|
|
|
648
935
|
<\/script>`;
|
|
649
936
|
}
|
|
650
937
|
|
|
938
|
+
|
|
939
|
+
private renderAgentSuggestions(s: AgentSuggestion): string {
|
|
940
|
+
const roleIcon = (name: string): string => {
|
|
941
|
+
if (name.includes('ORCHESTRATOR')) return '\u{1F3AD}';
|
|
942
|
+
if (name.includes('BACKEND') || name.includes('FRONTEND') || name.includes('DATABASE') || name.includes('FLUTTER')) return '\u{1F4BB}';
|
|
943
|
+
if (name.includes('SECURITY')) return '\u{1F6E1}\uFE0F';
|
|
944
|
+
if (name.includes('QA')) return '\u{1F9EA}';
|
|
945
|
+
if (name.includes('TECH-DEBT')) return '\u{1F4CA}';
|
|
946
|
+
return '\u{1F916}';
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
const roleLabel = (name: string): string => {
|
|
950
|
+
if (name.includes('ORCHESTRATOR')) return 'coordination';
|
|
951
|
+
if (name.includes('SECURITY')) return 'protection';
|
|
952
|
+
if (name.includes('QA')) return 'quality';
|
|
953
|
+
if (name.includes('TECH-DEBT')) return 'governance';
|
|
954
|
+
return 'development';
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
const roleColor = (name: string): string => {
|
|
958
|
+
if (name.includes('ORCHESTRATOR')) return '#c084fc';
|
|
959
|
+
if (name.includes('SECURITY')) return '#f87171';
|
|
960
|
+
if (name.includes('QA')) return '#34d399';
|
|
961
|
+
if (name.includes('TECH-DEBT')) return '#fbbf24';
|
|
962
|
+
return '#60a5fa';
|
|
963
|
+
};
|
|
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
|
+
|
|
984
|
+
const agentCards = s.suggestedAgents.map(a =>
|
|
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>
|
|
989
|
+
<div class="agent-toggle-info">
|
|
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>` : ''}
|
|
993
|
+
</div>
|
|
994
|
+
${statusBadge(a.status)}
|
|
995
|
+
<div class="agent-toggle-check">\u2713</div>
|
|
996
|
+
</div>
|
|
997
|
+
</label>`
|
|
998
|
+
).join('\n');
|
|
999
|
+
|
|
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)}
|
|
1010
|
+
<div class="agent-toggle-check">\u2713</div>
|
|
1011
|
+
</div>
|
|
1012
|
+
</label>`;
|
|
1013
|
+
|
|
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');
|
|
1017
|
+
|
|
1018
|
+
const skillCards = s.suggestedSkills.map(sk =>
|
|
1019
|
+
`<label class="agent-toggle-card" data-category="skills">
|
|
1020
|
+
<input type="checkbox" class="agent-check" checked data-type="skills" data-item="${sk.source}">
|
|
1021
|
+
<div class="agent-toggle-inner" style="border-color:${statusBorder(sk.status)}">
|
|
1022
|
+
<span class="agent-toggle-icon">\u{1F9E0}</span>
|
|
1023
|
+
<div class="agent-toggle-info">
|
|
1024
|
+
<span class="agent-toggle-name">${sk.name}</span>
|
|
1025
|
+
<span class="agent-toggle-role" style="color:#34d399">${sk.description}</span>
|
|
1026
|
+
</div>
|
|
1027
|
+
${statusBadge(sk.status)}
|
|
1028
|
+
<div class="agent-toggle-check">\u2713</div>
|
|
1029
|
+
</div>
|
|
1030
|
+
</label>`
|
|
1031
|
+
).join('\n');
|
|
1032
|
+
|
|
1033
|
+
const auditSection = s.audit.filter(f => f.type !== 'OK').length > 0 ? `
|
|
1034
|
+
<div class="agent-audit-section">
|
|
1035
|
+
<h3 class="agent-section-subtitle">\u{1F50D} Audit Findings</h3>
|
|
1036
|
+
<div class="agent-audit-grid">
|
|
1037
|
+
${s.audit.filter(f => f.type !== 'OK').map(f => {
|
|
1038
|
+
const icon = f.type === 'MISSING' ? '\u274C' : f.type === 'IMPROVEMENT' ? '\u{1F4A1}' : '\u26A0\uFE0F';
|
|
1039
|
+
const cls = f.type === 'MISSING' ? 'audit-missing' : 'audit-improvement';
|
|
1040
|
+
return `<div class="agent-audit-item ${cls}">
|
|
1041
|
+
<span class="audit-icon">${icon}</span>
|
|
1042
|
+
<div class="audit-content">
|
|
1043
|
+
<span class="audit-desc">${f.description}</span>
|
|
1044
|
+
${f.suggestion ? `<span class="audit-suggestion">\u2192 ${f.suggestion}</span>` : ''}
|
|
1045
|
+
</div>
|
|
1046
|
+
</div>`;
|
|
1047
|
+
}).join('\n')}
|
|
1048
|
+
</div>
|
|
1049
|
+
</div>` : '';
|
|
1050
|
+
|
|
1051
|
+
const stackPills = [
|
|
1052
|
+
`\u{1F527} ${s.stack.primary}`,
|
|
1053
|
+
`\u{1F4E6} ${s.stack.frameworks.length > 0 ? s.stack.frameworks.join(', ') : 'No framework'}`,
|
|
1054
|
+
s.hasExistingAgents ? '\u{1F4C1} Existing .agent/' : '\u{1F4C1} New .agent/',
|
|
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'] : []),
|
|
1059
|
+
];
|
|
1060
|
+
|
|
1061
|
+
const totalItems = s.suggestedAgents.length + s.suggestedRules.length + s.suggestedGuards.length + s.suggestedWorkflows.length + s.suggestedSkills.length;
|
|
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
|
+
|
|
1069
|
+
return `
|
|
1070
|
+
<h2 class="section-title">\u{1F916} Agent System</h2>
|
|
1071
|
+
|
|
1072
|
+
<div class="card agent-system-card">
|
|
1073
|
+
<div class="agent-stack-banner">
|
|
1074
|
+
${stackPills.map(p => `<div class="stack-pill">${p}</div>`).join('\n ')}
|
|
1075
|
+
</div>
|
|
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
|
+
|
|
1083
|
+
<div class="agent-controls">
|
|
1084
|
+
<button class="agent-ctrl-btn" onclick="toggleAll(true)">\u2705 Select All</button>
|
|
1085
|
+
<button class="agent-ctrl-btn" onclick="toggleAll(false)">\u2B1C Select None</button>
|
|
1086
|
+
<span class="agent-count-label"><span id="agentSelectedCount">${totalItems}</span> selected</span>
|
|
1087
|
+
</div>
|
|
1088
|
+
|
|
1089
|
+
<h3 class="agent-section-subtitle">\u{1F916} Agents</h3>
|
|
1090
|
+
<div class="agent-toggle-grid">
|
|
1091
|
+
${agentCards}
|
|
1092
|
+
</div>
|
|
1093
|
+
|
|
1094
|
+
<div class="agent-extras-grid">
|
|
1095
|
+
<div>
|
|
1096
|
+
<h3 class="agent-section-subtitle">\u{1F4CF} Rules</h3>
|
|
1097
|
+
<div class="agent-toggle-list">${ruleCards}</div>
|
|
1098
|
+
</div>
|
|
1099
|
+
<div>
|
|
1100
|
+
<h3 class="agent-section-subtitle">\u{1F6E1}\uFE0F Guards</h3>
|
|
1101
|
+
<div class="agent-toggle-list">${guardCards}</div>
|
|
1102
|
+
</div>
|
|
1103
|
+
<div>
|
|
1104
|
+
<h3 class="agent-section-subtitle">\u26A1 Workflows</h3>
|
|
1105
|
+
<div class="agent-toggle-list">${workflowCards}</div>
|
|
1106
|
+
</div>
|
|
1107
|
+
</div>
|
|
1108
|
+
|
|
1109
|
+
<h3 class="agent-section-subtitle">\u{1F9E0} Skills <span style="font-size:0.7rem;color:#94a3b8;font-weight:400">from skills.sh</span></h3>
|
|
1110
|
+
<div class="agent-toggle-grid">
|
|
1111
|
+
${skillCards}
|
|
1112
|
+
</div>
|
|
1113
|
+
|
|
1114
|
+
${auditSection}
|
|
1115
|
+
|
|
1116
|
+
<div class="agent-command-box">
|
|
1117
|
+
<div class="agent-command-header">
|
|
1118
|
+
<span>\u{1F4A1} Command to generate selected items:</span>
|
|
1119
|
+
<button class="agent-copy-btn" onclick="copyAgentCommand()">
|
|
1120
|
+
<span id="copyIcon">\u{1F4CB}</span> Copy
|
|
1121
|
+
</button>
|
|
1122
|
+
</div>
|
|
1123
|
+
<code id="agentCommandOutput" class="agent-command-code">${s.command}</code>
|
|
1124
|
+
</div>
|
|
1125
|
+
</div>
|
|
1126
|
+
|
|
1127
|
+
<style>
|
|
1128
|
+
.agent-system-card { padding: 1.5rem; }
|
|
1129
|
+
.agent-stack-banner { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
|
|
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; }
|
|
1135
|
+
.agent-controls { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem; }
|
|
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; }
|
|
1137
|
+
.agent-ctrl-btn:hover { background: #334155; }
|
|
1138
|
+
.agent-count-label { color: #94a3b8; font-size: 0.85rem; margin-left: auto; }
|
|
1139
|
+
#agentSelectedCount { color: #c084fc; font-weight: 700; }
|
|
1140
|
+
.agent-section-subtitle { color: #e2e8f0; font-size: 1.05rem; font-weight: 700; margin: 1.25rem 0 0.75rem; }
|
|
1141
|
+
.agent-toggle-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }
|
|
1142
|
+
.agent-toggle-card { cursor: pointer; transition: all 0.3s; }
|
|
1143
|
+
.agent-toggle-card input { display: none; }
|
|
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; }
|
|
1145
|
+
.agent-toggle-card input:checked + .agent-toggle-inner { background: #1e1b4b; }
|
|
1146
|
+
.agent-toggle-icon { font-size: 1.3rem; flex-shrink: 0; }
|
|
1147
|
+
.agent-toggle-info { flex: 1; min-width: 0; }
|
|
1148
|
+
.agent-toggle-name { display: block; color: #e2e8f0; font-weight: 600; font-size: 0.85rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1149
|
+
.agent-toggle-role { display: block; font-size: 0.7rem; margin-top: 0.15rem; }
|
|
1150
|
+
.agent-toggle-check { color: #334155; font-size: 1rem; flex-shrink: 0; transition: color 0.3s; }
|
|
1151
|
+
.agent-toggle-card input:checked + .agent-toggle-inner .agent-toggle-check { color: #818cf8; }
|
|
1152
|
+
.agent-toggle-card.mini .agent-toggle-inner { padding: 0.5rem 0.75rem; border-radius: 8px; }
|
|
1153
|
+
.agent-extras-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-top: 0.5rem; }
|
|
1154
|
+
@media (max-width: 768px) { .agent-extras-grid { grid-template-columns: 1fr; } }
|
|
1155
|
+
.agent-toggle-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
1156
|
+
.agent-audit-section { margin-top: 1.5rem; }
|
|
1157
|
+
.agent-audit-grid { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
1158
|
+
.agent-audit-item { display: flex; gap: 0.75rem; align-items: flex-start; background: #1e293b; padding: 0.75rem 1rem; border-radius: 8px; }
|
|
1159
|
+
.agent-audit-item.audit-missing { border-left: 3px solid #ef4444; }
|
|
1160
|
+
.agent-audit-item.audit-improvement { border-left: 3px solid #fbbf24; }
|
|
1161
|
+
.audit-icon { font-size: 1rem; flex-shrink: 0; margin-top: 2px; }
|
|
1162
|
+
.audit-content { display: flex; flex-direction: column; gap: 0.25rem; }
|
|
1163
|
+
.audit-desc { color: #e2e8f0; font-size: 0.85rem; }
|
|
1164
|
+
.audit-suggestion { color: #94a3b8; font-size: 0.8rem; font-style: italic; }
|
|
1165
|
+
.agent-command-box { margin-top: 1.5rem; background: #0f172a; border-radius: 12px; border: 1px solid #334155; overflow: hidden; }
|
|
1166
|
+
.agent-command-header { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 1rem; background: #1e293b; font-size: 0.8rem; color: #94a3b8; }
|
|
1167
|
+
.agent-copy-btn { background: #c084fc; color: #0f172a; border: none; border-radius: 6px; padding: 0.4rem 0.8rem; cursor: pointer; font-size: 0.75rem; font-weight: 600; transition: all 0.2s; }
|
|
1168
|
+
.agent-copy-btn:hover { background: #a855f7; transform: scale(1.05); }
|
|
1169
|
+
.agent-command-code { display: block; padding: 1rem; color: #c084fc; font-size: 0.85rem; word-break: break-all; font-family: 'Fira Code', monospace; }
|
|
1170
|
+
</style>
|
|
1171
|
+
|
|
1172
|
+
<script>
|
|
1173
|
+
(function() {
|
|
1174
|
+
var basePath = ${JSON.stringify(s.command.replace('architect agents ', ''))};
|
|
1175
|
+
var totalItems = ${totalItems};
|
|
1176
|
+
function updateCommand() {
|
|
1177
|
+
var checks = document.querySelectorAll('.agent-check');
|
|
1178
|
+
var selected = { agents: [], rules: [], guards: [], workflows: [], skills: [] };
|
|
1179
|
+
var count = 0;
|
|
1180
|
+
checks.forEach(function(cb) { if (cb.checked) { selected[cb.dataset.type].push(cb.dataset.item); count++; } });
|
|
1181
|
+
document.getElementById('agentSelectedCount').textContent = count;
|
|
1182
|
+
var cmd;
|
|
1183
|
+
if (count === totalItems) { cmd = 'architect agents ' + basePath; }
|
|
1184
|
+
else if (count === 0) { cmd = '# No items selected'; }
|
|
1185
|
+
else {
|
|
1186
|
+
var parts = ['architect agents ' + basePath];
|
|
1187
|
+
if (selected.agents.length > 0) parts.push('--agents ' + selected.agents.join(','));
|
|
1188
|
+
if (selected.rules.length > 0) parts.push('--rules ' + selected.rules.join(','));
|
|
1189
|
+
if (selected.guards.length > 0) parts.push('--guards ' + selected.guards.join(','));
|
|
1190
|
+
if (selected.workflows.length > 0) parts.push('--workflows ' + selected.workflows.join(','));
|
|
1191
|
+
if (selected.skills.length > 0) parts.push('&& ' + selected.skills.map(function(sk){ return 'npx skills add ' + sk; }).join(' && '));
|
|
1192
|
+
cmd = parts.join(' ');
|
|
1193
|
+
}
|
|
1194
|
+
document.getElementById('agentCommandOutput').textContent = cmd;
|
|
1195
|
+
}
|
|
1196
|
+
document.querySelectorAll('.agent-check').forEach(function(cb) { cb.addEventListener('change', updateCommand); });
|
|
1197
|
+
window.toggleAll = function(state) { document.querySelectorAll('.agent-check').forEach(function(cb) { cb.checked = state; }); updateCommand(); };
|
|
1198
|
+
window.copyAgentCommand = function() { var cmd = document.getElementById('agentCommandOutput').textContent; navigator.clipboard.writeText(cmd).then(function() { var btn = document.getElementById('copyIcon'); btn.textContent = '\u2705'; setTimeout(function() { btn.textContent = '\ud83d\udccb'; }, 2000); }); };
|
|
1199
|
+
})();
|
|
1200
|
+
<\/script>`;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
651
1203
|
private getStyles(): string {
|
|
652
1204
|
return `<style>
|
|
653
1205
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
|
|
@@ -662,7 +1214,50 @@ function animateCounter(el, target) {
|
|
|
662
1214
|
min-height: 100vh;
|
|
663
1215
|
}
|
|
664
1216
|
|
|
665
|
-
|
|
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; }
|
|
666
1261
|
|
|
667
1262
|
/* ── Header ── */
|
|
668
1263
|
.header {
|
|
@@ -741,6 +1336,44 @@ function animateCounter(el, target) {
|
|
|
741
1336
|
display: flex; align-items: center; gap: 0.5rem;
|
|
742
1337
|
}
|
|
743
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
|
+
|
|
744
1377
|
/* ── Cards ── */
|
|
745
1378
|
.card {
|
|
746
1379
|
background: #1e293b; border-radius: 16px; border: 1px solid #334155;
|
|
@@ -750,17 +1383,42 @@ function animateCounter(el, target) {
|
|
|
750
1383
|
|
|
751
1384
|
/* ── Graph ── */
|
|
752
1385
|
.graph-card { padding: 1rem; }
|
|
1386
|
+
.graph-controls { margin-bottom: 0.75rem; }
|
|
753
1387
|
.graph-legend {
|
|
754
1388
|
display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.5rem;
|
|
755
1389
|
justify-content: center;
|
|
756
1390
|
}
|
|
757
1391
|
.legend-item { display: flex; align-items: center; gap: 4px; font-size: 0.75rem; color: #94a3b8; }
|
|
758
|
-
.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
|
+
}
|
|
759
1416
|
.graph-hint {
|
|
760
1417
|
text-align: center; font-size: 0.75rem; color: #475569; margin-top: 0.5rem;
|
|
761
1418
|
font-style: italic;
|
|
762
1419
|
}
|
|
763
|
-
#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; }
|
|
764
1422
|
|
|
765
1423
|
/* ── Layers Grid ── */
|
|
766
1424
|
.layers-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; }
|
|
@@ -810,12 +1468,102 @@ function animateCounter(el, target) {
|
|
|
810
1468
|
.footer a { color: #818cf8; text-decoration: none; }
|
|
811
1469
|
.footer a:hover { text-decoration: underline; }
|
|
812
1470
|
|
|
1471
|
+
/* ── Refactoring Plan ── */
|
|
1472
|
+
.refactor-score { padding: 2rem; }
|
|
1473
|
+
.refactor-score-pair {
|
|
1474
|
+
display: flex; align-items: center; justify-content: center; gap: 1.5rem;
|
|
1475
|
+
margin-bottom: 2rem; flex-wrap: wrap;
|
|
1476
|
+
}
|
|
1477
|
+
.rscore-box { text-align: center; }
|
|
1478
|
+
.rscore-num { font-size: 3rem; font-weight: 900; line-height: 1; }
|
|
1479
|
+
.rscore-label { font-size: 0.8rem; color: #94a3b8; text-transform: uppercase; letter-spacing: 1px; }
|
|
1480
|
+
.rscore-improvement { font-size: 1.3rem; font-weight: 700; }
|
|
1481
|
+
|
|
1482
|
+
.refactor-legend { display: flex; gap: 1rem; margin-bottom: 0.5rem; }
|
|
1483
|
+
.rlegend-tag { font-size: 0.75rem; padding: 0.2rem 0.6rem; border-radius: 6px; }
|
|
1484
|
+
.rlegend-tag.rbefore { background: rgba(255,255,255,0.05); color: #94a3b8; }
|
|
1485
|
+
.rlegend-tag.rafter { background: rgba(129,140,248,0.2); color: #818cf8; }
|
|
1486
|
+
|
|
1487
|
+
.refactor-metric-name { width: 100px; font-size: 0.8rem; text-transform: uppercase; color: #94a3b8; font-weight: 600; }
|
|
1488
|
+
.refactor-metric-bars { flex: 1; position: relative; height: 30px; }
|
|
1489
|
+
.rbar-before, .rbar-after {
|
|
1490
|
+
position: absolute; left: 0; height: 14px; border-radius: 4px;
|
|
1491
|
+
display: flex; align-items: center; padding-left: 6px;
|
|
1492
|
+
font-size: 0.7rem; font-weight: 600;
|
|
1493
|
+
}
|
|
1494
|
+
.rbar-before { top: 0; }
|
|
1495
|
+
.rbar-after { top: 15px; }
|
|
1496
|
+
.refactor-metric-diff { width: 50px; text-align: right; font-weight: 700; font-size: 0.85rem; }
|
|
1497
|
+
|
|
1498
|
+
.refactor-stats-row {
|
|
1499
|
+
display: flex; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap;
|
|
1500
|
+
}
|
|
1501
|
+
.rstat {
|
|
1502
|
+
background: #1e293b; border: 1px solid #334155; border-radius: 99px;
|
|
1503
|
+
padding: 0.4rem 1rem; font-size: 0.85rem; color: #94a3b8; font-weight: 500;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
.priority-bar {
|
|
1507
|
+
display: flex; border-radius: 12px; overflow: hidden; height: 32px; margin-bottom: 2rem;
|
|
1508
|
+
}
|
|
1509
|
+
.prio-seg {
|
|
1510
|
+
display: flex; align-items: center; justify-content: center;
|
|
1511
|
+
font-size: 0.75rem; font-weight: 600;
|
|
1512
|
+
}
|
|
1513
|
+
.prio-critical { background: #ef444430; color: #ef4444; }
|
|
1514
|
+
.prio-high { background: #f59e0b30; color: #f59e0b; }
|
|
1515
|
+
.prio-medium { background: #3b82f630; color: #60a5fa; }
|
|
1516
|
+
.prio-low { background: #22c55e30; color: #22c55e; }
|
|
1517
|
+
|
|
1518
|
+
.refactor-roadmap { display: flex; flex-direction: column; gap: 1rem; }
|
|
1519
|
+
.rstep-card {
|
|
1520
|
+
background: #1e293b; border-radius: 16px; border: 1px solid #334155;
|
|
1521
|
+
padding: 1.5rem; transition: border-color 0.2s;
|
|
1522
|
+
}
|
|
1523
|
+
.rstep-card:hover { border-color: #818cf8; }
|
|
1524
|
+
.rstep-header { display: flex; gap: 1rem; margin-bottom: 1rem; }
|
|
1525
|
+
.rstep-number {
|
|
1526
|
+
width: 40px; height: 40px; border-radius: 50%;
|
|
1527
|
+
background: linear-gradient(135deg, #818cf8, #c084fc);
|
|
1528
|
+
display: flex; align-items: center; justify-content: center;
|
|
1529
|
+
font-weight: 800; font-size: 1rem; color: white; flex-shrink: 0;
|
|
1530
|
+
}
|
|
1531
|
+
.rstep-info { flex: 1; }
|
|
1532
|
+
.rstep-title-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
|
|
1533
|
+
.rstep-title-row h3 { font-size: 1.1rem; font-weight: 700; }
|
|
1534
|
+
.rstep-desc { color: #94a3b8; font-size: 0.9rem; margin-top: 0.3rem; }
|
|
1535
|
+
.tier-badge {
|
|
1536
|
+
background: #818cf815; color: #818cf8; border: 1px solid #818cf830;
|
|
1537
|
+
padding: 0.15rem 0.5rem; border-radius: 99px; font-size: 0.65rem; font-weight: 600;
|
|
1538
|
+
}
|
|
1539
|
+
.rstep-details { margin-top: 0.5rem; }
|
|
1540
|
+
.rstep-details summary { cursor: pointer; color: #818cf8; font-size: 0.85rem; font-weight: 500; }
|
|
1541
|
+
.rstep-rationale { color: #64748b; font-size: 0.85rem; margin-top: 0.3rem; font-style: italic; }
|
|
1542
|
+
|
|
1543
|
+
.rstep-ops { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #334155; }
|
|
1544
|
+
.rstep-ops h4 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.5rem; }
|
|
1545
|
+
.rop { display: flex; align-items: flex-start; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
|
|
1546
|
+
.rop-icon { font-size: 0.9rem; }
|
|
1547
|
+
.rop-badge { padding: 0.1rem 0.4rem; border-radius: 6px; font-size: 0.65rem; font-weight: 700; }
|
|
1548
|
+
.rop-path { background: #0f172a; padding: 1px 6px; border-radius: 4px; font-size: 0.8rem; color: #c084fc; }
|
|
1549
|
+
.rop-arrow { color: #818cf8; font-weight: 700; }
|
|
1550
|
+
.rop-desc { width: 100%; color: #64748b; font-size: 0.8rem; padding-left: 1.8rem; }
|
|
1551
|
+
|
|
1552
|
+
.rstep-impact { margin-top: 0.5rem; }
|
|
1553
|
+
.rstep-impact h4 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.3rem; }
|
|
1554
|
+
.rimpact-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
|
1555
|
+
.rimpact-tag {
|
|
1556
|
+
background: #22c55e10; color: #22c55e; border: 1px solid #22c55e30;
|
|
1557
|
+
padding: 0.2rem 0.6rem; border-radius: 8px; font-size: 0.75rem; font-weight: 500;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
813
1560
|
/* ── Responsive ── */
|
|
814
1561
|
@media (max-width: 768px) {
|
|
815
1562
|
.score-hero { flex-direction: column; gap: 1.5rem; }
|
|
816
1563
|
.score-breakdown { grid-template-columns: 1fr; }
|
|
817
1564
|
.header h1 { font-size: 1.8rem; }
|
|
818
1565
|
.container { padding: 1rem; }
|
|
1566
|
+
.refactor-score-pair { flex-direction: column; }
|
|
819
1567
|
}
|
|
820
1568
|
|
|
821
1569
|
/* ── Print ── */
|