@girardelli/architect 1.3.0 โ 2.1.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 +95 -0
- package/dist/agent-generator.d.ts.map +1 -0
- package/dist/agent-generator.js +1295 -0
- package/dist/agent-generator.js.map +1 -0
- package/dist/cli.js +76 -2
- 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 +470 -5
- 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 +1401 -0
- package/src/cli.ts +83 -2
- package/src/html-reporter.ts +496 -6
- 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
|
|
|
@@ -29,6 +30,8 @@ ${this.renderHeader(report)}
|
|
|
29
30
|
${this.renderAntiPatternBubbles(report, grouped)}
|
|
30
31
|
${this.renderAntiPatterns(report, grouped)}
|
|
31
32
|
${this.renderSuggestions(sugGrouped)}
|
|
33
|
+
${plan ? this.renderRefactoringPlan(plan) : ''}
|
|
34
|
+
${agentSuggestion ? this.renderAgentSuggestions(agentSuggestion) : ''}
|
|
32
35
|
</div>
|
|
33
36
|
${this.renderFooter()}
|
|
34
37
|
${this.getScripts(report)}
|
|
@@ -388,11 +391,160 @@ ${this.getScripts(report)}
|
|
|
388
391
|
private renderFooter(): string {
|
|
389
392
|
return `
|
|
390
393
|
<div class="footer">
|
|
391
|
-
<p>Generated by <a href="https://github.com/camilooscargbaptista/architect">๐๏ธ Architect</a> โ AI-powered architecture analysis</p>
|
|
394
|
+
<p>Generated by <a href="https://github.com/camilooscargbaptista/architect">๐๏ธ Architect v2.0</a> โ AI-powered architecture analysis + refactoring engine</p>
|
|
392
395
|
<p>By <strong>Camilo Girardelli</strong> ยท <a href="https://www.girardellitecnologia.com">Girardelli Tecnologia</a></p>
|
|
393
396
|
</div>`;
|
|
394
397
|
}
|
|
395
398
|
|
|
399
|
+
// โโ Refactoring Plan Section โโ
|
|
400
|
+
|
|
401
|
+
private opColor(type: string): string {
|
|
402
|
+
switch (type) {
|
|
403
|
+
case 'CREATE': return '#22c55e';
|
|
404
|
+
case 'MOVE': return '#3b82f6';
|
|
405
|
+
case 'MODIFY': return '#f59e0b';
|
|
406
|
+
case 'DELETE': return '#ef4444';
|
|
407
|
+
default: return '#64748b';
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private opIcon(type: string): string {
|
|
412
|
+
switch (type) {
|
|
413
|
+
case 'CREATE': return 'โ';
|
|
414
|
+
case 'MOVE': return '๐ฆ';
|
|
415
|
+
case 'MODIFY': return 'โ๏ธ';
|
|
416
|
+
case 'DELETE': return '๐๏ธ';
|
|
417
|
+
default: return '๐';
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private renderRefactoringPlan(plan: RefactoringPlan): string {
|
|
422
|
+
if (plan.steps.length === 0) {
|
|
423
|
+
return `
|
|
424
|
+
<h2 class="section-title">โ
Refactoring Plan</h2>
|
|
425
|
+
<div class="card success-card">
|
|
426
|
+
<p>No refactoring needed! Your architecture is already in great shape.</p>
|
|
427
|
+
</div>`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const improvement = plan.estimatedScoreAfter.overall - plan.currentScore.overall;
|
|
431
|
+
|
|
432
|
+
const metrics = Object.keys(plan.currentScore.breakdown) as Array<keyof typeof plan.currentScore.breakdown>;
|
|
433
|
+
const bars = metrics.map(metric => {
|
|
434
|
+
const before = plan.currentScore.breakdown[metric];
|
|
435
|
+
const after = plan.estimatedScoreAfter.breakdown[metric] ?? before;
|
|
436
|
+
const diff = after - before;
|
|
437
|
+
return `
|
|
438
|
+
<div class="comparison-row">
|
|
439
|
+
<div class="refactor-metric-name">${metric}</div>
|
|
440
|
+
<div class="refactor-metric-bars">
|
|
441
|
+
<div class="rbar-before" style="width: ${before}%; background: ${this.scoreColor(before)}40"><span>${before}</span></div>
|
|
442
|
+
<div class="rbar-after" style="width: ${after}%; background: ${this.scoreColor(after)}"><span>${after}</span></div>
|
|
443
|
+
</div>
|
|
444
|
+
<div class="refactor-metric-diff" style="color: ${diff > 0 ? '#22c55e' : '#64748b'}">
|
|
445
|
+
${diff > 0 ? `+${diff}` : diff === 0 ? 'โ' : String(diff)}
|
|
446
|
+
</div>
|
|
447
|
+
</div>`;
|
|
448
|
+
}).join('');
|
|
449
|
+
|
|
450
|
+
const stepsHtml = plan.steps.map(step => this.renderRefactorStep(step)).join('');
|
|
451
|
+
|
|
452
|
+
const criticalCount = plan.steps.filter(s => s.priority === 'CRITICAL').length;
|
|
453
|
+
const highCount = plan.steps.filter(s => s.priority === 'HIGH').length;
|
|
454
|
+
const mediumCount = plan.steps.filter(s => s.priority === 'MEDIUM').length;
|
|
455
|
+
const lowCount = plan.steps.filter(s => s.priority === 'LOW').length;
|
|
456
|
+
|
|
457
|
+
return `
|
|
458
|
+
<h2 class="section-title">๐ง Refactoring Plan</h2>
|
|
459
|
+
|
|
460
|
+
<div class="card refactor-score">
|
|
461
|
+
<div class="refactor-score-pair">
|
|
462
|
+
<div class="rscore-box">
|
|
463
|
+
<div class="rscore-num" style="color: ${this.scoreColor(plan.currentScore.overall)}">${plan.currentScore.overall}</div>
|
|
464
|
+
<div class="rscore-label">Current</div>
|
|
465
|
+
</div>
|
|
466
|
+
<div class="rscore-arrow">
|
|
467
|
+
<svg width="60" height="30" viewBox="0 0 60 30">
|
|
468
|
+
<path d="M5 15 L45 15 M40 8 L48 15 L40 22" stroke="#818cf8" stroke-width="2.5" fill="none"/>
|
|
469
|
+
</svg>
|
|
470
|
+
</div>
|
|
471
|
+
<div class="rscore-box">
|
|
472
|
+
<div class="rscore-num" style="color: ${this.scoreColor(plan.estimatedScoreAfter.overall)}">${plan.estimatedScoreAfter.overall}</div>
|
|
473
|
+
<div class="rscore-label">Estimated</div>
|
|
474
|
+
</div>
|
|
475
|
+
<div class="rscore-improvement" style="color: #22c55e">+${improvement} pts</div>
|
|
476
|
+
</div>
|
|
477
|
+
<div class="refactor-bars-section">
|
|
478
|
+
<div class="refactor-legend">
|
|
479
|
+
<span class="rlegend-tag rbefore">Before</span>
|
|
480
|
+
<span class="rlegend-tag rafter">After</span>
|
|
481
|
+
</div>
|
|
482
|
+
${bars}
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
<div class="refactor-stats-row">
|
|
487
|
+
<div class="rstat">${plan.steps.length} steps</div>
|
|
488
|
+
<div class="rstat">${plan.totalOperations} operations</div>
|
|
489
|
+
<div class="rstat">Tier 1: ${plan.tier1Steps}</div>
|
|
490
|
+
<div class="rstat">Tier 2: ${plan.tier2Steps}</div>
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
<div class="priority-bar">
|
|
494
|
+
${criticalCount ? `<div class="prio-seg prio-critical" style="flex: ${criticalCount}">๐ด ${criticalCount}</div>` : ''}
|
|
495
|
+
${highCount ? `<div class="prio-seg prio-high" style="flex: ${highCount}">๐ ${highCount}</div>` : ''}
|
|
496
|
+
${mediumCount ? `<div class="prio-seg prio-medium" style="flex: ${mediumCount}">๐ต ${mediumCount}</div>` : ''}
|
|
497
|
+
${lowCount ? `<div class="prio-seg prio-low" style="flex: ${lowCount}">๐ข ${lowCount}</div>` : ''}
|
|
498
|
+
</div>
|
|
499
|
+
|
|
500
|
+
<div class="refactor-roadmap">
|
|
501
|
+
${stepsHtml}
|
|
502
|
+
</div>`;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
private renderRefactorStep(step: RefactorStep): string {
|
|
506
|
+
const operationsHtml = step.operations.map(op => `
|
|
507
|
+
<div class="rop">
|
|
508
|
+
<span class="rop-icon">${this.opIcon(op.type)}</span>
|
|
509
|
+
<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>
|
|
510
|
+
<code class="rop-path">${this.escapeHtml(op.path)}</code>
|
|
511
|
+
${op.newPath ? `<span class="rop-arrow">โ</span> <code class="rop-path">${this.escapeHtml(op.newPath)}</code>` : ''}
|
|
512
|
+
<div class="rop-desc">${this.escapeHtml(op.description)}</div>
|
|
513
|
+
</div>
|
|
514
|
+
`).join('');
|
|
515
|
+
|
|
516
|
+
const impactHtml = step.scoreImpact.map(i =>
|
|
517
|
+
`<span class="rimpact-tag">${i.metric}: ${i.before}โ${i.after} <strong>+${i.after - i.before}</strong></span>`
|
|
518
|
+
).join('');
|
|
519
|
+
|
|
520
|
+
return `
|
|
521
|
+
<div class="rstep-card">
|
|
522
|
+
<div class="rstep-header">
|
|
523
|
+
<div class="rstep-number">${step.id}</div>
|
|
524
|
+
<div class="rstep-info">
|
|
525
|
+
<div class="rstep-title-row">
|
|
526
|
+
<h3>${this.escapeHtml(step.title)}</h3>
|
|
527
|
+
<span class="severity-badge severity-${step.priority}">${step.priority}</span>
|
|
528
|
+
<span class="tier-badge">Tier ${step.tier}</span>
|
|
529
|
+
</div>
|
|
530
|
+
<p class="rstep-desc">${this.escapeHtml(step.description)}</p>
|
|
531
|
+
<details class="rstep-details">
|
|
532
|
+
<summary>๐ Why?</summary>
|
|
533
|
+
<p class="rstep-rationale">${this.escapeHtml(step.rationale)}</p>
|
|
534
|
+
</details>
|
|
535
|
+
</div>
|
|
536
|
+
</div>
|
|
537
|
+
<div class="rstep-ops">
|
|
538
|
+
<h4>๐ Operations (${step.operations.length})</h4>
|
|
539
|
+
${operationsHtml}
|
|
540
|
+
</div>
|
|
541
|
+
<div class="rstep-impact">
|
|
542
|
+
<h4>๐ Score Impact</h4>
|
|
543
|
+
<div class="rimpact-tags">${impactHtml}</div>
|
|
544
|
+
</div>
|
|
545
|
+
</div>`;
|
|
546
|
+
}
|
|
547
|
+
|
|
396
548
|
/**
|
|
397
549
|
* All JavaScript for D3.js visualizations, animated counters, and radar chart
|
|
398
550
|
*/
|
|
@@ -516,7 +668,7 @@ function animateCounter(el, target) {
|
|
|
516
668
|
|
|
517
669
|
const container = document.getElementById('dep-graph');
|
|
518
670
|
const width = container.clientWidth || 800;
|
|
519
|
-
const height =
|
|
671
|
+
const height = 450;
|
|
520
672
|
container.style.height = height + 'px';
|
|
521
673
|
|
|
522
674
|
const layerColors = {
|
|
@@ -537,9 +689,11 @@ function animateCounter(el, target) {
|
|
|
537
689
|
.attr('fill', '#475569');
|
|
538
690
|
|
|
539
691
|
const simulation = d3.forceSimulation(nodes)
|
|
540
|
-
.force('link', d3.forceLink(links).id(d => d.id).distance(
|
|
541
|
-
.force('charge', d3.forceManyBody().strength(-
|
|
692
|
+
.force('link', d3.forceLink(links).id(d => d.id).distance(60))
|
|
693
|
+
.force('charge', d3.forceManyBody().strength(-150))
|
|
542
694
|
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
695
|
+
.force('x', d3.forceX(width / 2).strength(0.1))
|
|
696
|
+
.force('y', d3.forceY(height / 2).strength(0.1))
|
|
543
697
|
.force('collision', d3.forceCollide().radius(d => Math.max(d.connections * 3 + 12, 15)));
|
|
544
698
|
|
|
545
699
|
const link = svg.append('g')
|
|
@@ -574,6 +728,13 @@ function animateCounter(el, target) {
|
|
|
574
728
|
.text(d => d.id + '\\nConnections: ' + d.connections + '\\nLayer: ' + d.layer);
|
|
575
729
|
|
|
576
730
|
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
|
+
|
|
577
738
|
link
|
|
578
739
|
.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
|
|
579
740
|
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
|
|
@@ -648,6 +809,245 @@ function animateCounter(el, target) {
|
|
|
648
809
|
<\/script>`;
|
|
649
810
|
}
|
|
650
811
|
|
|
812
|
+
|
|
813
|
+
private renderAgentSuggestions(s: AgentSuggestion): string {
|
|
814
|
+
const roleIcon = (name: string): string => {
|
|
815
|
+
if (name.includes('ORCHESTRATOR')) return '\u{1F3AD}';
|
|
816
|
+
if (name.includes('BACKEND') || name.includes('FRONTEND') || name.includes('DATABASE') || name.includes('FLUTTER')) return '\u{1F4BB}';
|
|
817
|
+
if (name.includes('SECURITY')) return '\u{1F6E1}\uFE0F';
|
|
818
|
+
if (name.includes('QA')) return '\u{1F9EA}';
|
|
819
|
+
if (name.includes('TECH-DEBT')) return '\u{1F4CA}';
|
|
820
|
+
return '\u{1F916}';
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
const roleLabel = (name: string): string => {
|
|
824
|
+
if (name.includes('ORCHESTRATOR')) return 'coordination';
|
|
825
|
+
if (name.includes('SECURITY')) return 'protection';
|
|
826
|
+
if (name.includes('QA')) return 'quality';
|
|
827
|
+
if (name.includes('TECH-DEBT')) return 'governance';
|
|
828
|
+
return 'development';
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
const roleColor = (name: string): string => {
|
|
832
|
+
if (name.includes('ORCHESTRATOR')) return '#c084fc';
|
|
833
|
+
if (name.includes('SECURITY')) return '#f87171';
|
|
834
|
+
if (name.includes('QA')) return '#34d399';
|
|
835
|
+
if (name.includes('TECH-DEBT')) return '#fbbf24';
|
|
836
|
+
return '#60a5fa';
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
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>
|
|
844
|
+
<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>
|
|
847
|
+
</div>
|
|
848
|
+
<div class="agent-toggle-check">\u2713</div>
|
|
849
|
+
</div>
|
|
850
|
+
</label>`
|
|
851
|
+
).join('\n');
|
|
852
|
+
|
|
853
|
+
const ruleCards = s.suggestedRules.map(r =>
|
|
854
|
+
`<label class="agent-toggle-card mini" data-category="rules">
|
|
855
|
+
<input type="checkbox" class="agent-check" checked data-type="rules" data-item="${r}">
|
|
856
|
+
<div class="agent-toggle-inner">
|
|
857
|
+
<span class="agent-toggle-icon">\u{1F4CF}</span>
|
|
858
|
+
<span class="agent-toggle-name">${r}.md</span>
|
|
859
|
+
<div class="agent-toggle-check">\u2713</div>
|
|
860
|
+
</div>
|
|
861
|
+
</label>`
|
|
862
|
+
).join('\n');
|
|
863
|
+
|
|
864
|
+
const guardCards = s.suggestedGuards.map(g =>
|
|
865
|
+
`<label class="agent-toggle-card mini" data-category="guards">
|
|
866
|
+
<input type="checkbox" class="agent-check" checked data-type="guards" data-item="${g}">
|
|
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');
|
|
885
|
+
|
|
886
|
+
const skillCards = s.suggestedSkills.map(sk =>
|
|
887
|
+
`<label class="agent-toggle-card" data-category="skills">
|
|
888
|
+
<input type="checkbox" class="agent-check" checked data-type="skills" data-item="${sk.source}">
|
|
889
|
+
<div class="agent-toggle-inner">
|
|
890
|
+
<span class="agent-toggle-icon">\u{1F9E0}</span>
|
|
891
|
+
<div class="agent-toggle-info">
|
|
892
|
+
<span class="agent-toggle-name">${sk.name}</span>
|
|
893
|
+
<span class="agent-toggle-role" style="color:#34d399">${sk.description}</span>
|
|
894
|
+
</div>
|
|
895
|
+
<div class="agent-toggle-check">\u2713</div>
|
|
896
|
+
</div>
|
|
897
|
+
</label>`
|
|
898
|
+
).join('\n');
|
|
899
|
+
|
|
900
|
+
const auditSection = s.audit.filter(f => f.type !== 'OK').length > 0 ? `
|
|
901
|
+
<div class="agent-audit-section">
|
|
902
|
+
<h3 class="agent-section-subtitle">\u{1F50D} Audit Findings</h3>
|
|
903
|
+
<div class="agent-audit-grid">
|
|
904
|
+
${s.audit.filter(f => f.type !== 'OK').map(f => {
|
|
905
|
+
const icon = f.type === 'MISSING' ? '\u274C' : f.type === 'IMPROVEMENT' ? '\u{1F4A1}' : '\u26A0\uFE0F';
|
|
906
|
+
const cls = f.type === 'MISSING' ? 'audit-missing' : 'audit-improvement';
|
|
907
|
+
return `<div class="agent-audit-item ${cls}">
|
|
908
|
+
<span class="audit-icon">${icon}</span>
|
|
909
|
+
<div class="audit-content">
|
|
910
|
+
<span class="audit-desc">${f.description}</span>
|
|
911
|
+
${f.suggestion ? `<span class="audit-suggestion">\u2192 ${f.suggestion}</span>` : ''}
|
|
912
|
+
</div>
|
|
913
|
+
</div>`;
|
|
914
|
+
}).join('\n')}
|
|
915
|
+
</div>
|
|
916
|
+
</div>` : '';
|
|
917
|
+
|
|
918
|
+
const stackPills = [
|
|
919
|
+
`\u{1F527} ${s.stack.primary}`,
|
|
920
|
+
`\u{1F4E6} ${s.stack.frameworks.length > 0 ? s.stack.frameworks.join(', ') : 'No framework'}`,
|
|
921
|
+
s.hasExistingAgents ? '\u{1F4C1} Existing .agent/' : '\u{1F4C1} New .agent/',
|
|
922
|
+
[s.stack.hasBackend ? '\u{1F519} Backend' : '', s.stack.hasFrontend ? '\u{1F5A5}\uFE0F Frontend' : '', s.stack.hasMobile ? '\u{1F4F1} Mobile' : '', s.stack.hasDatabase ? '\u{1F5C4}\uFE0F DB' : ''].filter(Boolean).join('\n ')
|
|
923
|
+
];
|
|
924
|
+
|
|
925
|
+
const totalItems = s.suggestedAgents.length + s.suggestedRules.length + s.suggestedGuards.length + s.suggestedWorkflows.length + s.suggestedSkills.length;
|
|
926
|
+
|
|
927
|
+
return `
|
|
928
|
+
<h2 class="section-title">\u{1F916} Agent System (Suggested)</h2>
|
|
929
|
+
|
|
930
|
+
<div class="card agent-system-card">
|
|
931
|
+
<div class="agent-stack-banner">
|
|
932
|
+
${stackPills.map(p => `<div class="stack-pill">${p}</div>`).join('\n ')}
|
|
933
|
+
</div>
|
|
934
|
+
|
|
935
|
+
<div class="agent-controls">
|
|
936
|
+
<button class="agent-ctrl-btn" onclick="toggleAll(true)">\u2705 Select All</button>
|
|
937
|
+
<button class="agent-ctrl-btn" onclick="toggleAll(false)">\u2B1C Select None</button>
|
|
938
|
+
<span class="agent-count-label"><span id="agentSelectedCount">${totalItems}</span> selected</span>
|
|
939
|
+
</div>
|
|
940
|
+
|
|
941
|
+
<h3 class="agent-section-subtitle">\u{1F916} Agents</h3>
|
|
942
|
+
<div class="agent-toggle-grid">
|
|
943
|
+
${agentCards}
|
|
944
|
+
</div>
|
|
945
|
+
|
|
946
|
+
<div class="agent-extras-grid">
|
|
947
|
+
<div>
|
|
948
|
+
<h3 class="agent-section-subtitle">\u{1F4CF} Rules</h3>
|
|
949
|
+
<div class="agent-toggle-list">${ruleCards}</div>
|
|
950
|
+
</div>
|
|
951
|
+
<div>
|
|
952
|
+
<h3 class="agent-section-subtitle">\u{1F6E1}\uFE0F Guards</h3>
|
|
953
|
+
<div class="agent-toggle-list">${guardCards}</div>
|
|
954
|
+
</div>
|
|
955
|
+
<div>
|
|
956
|
+
<h3 class="agent-section-subtitle">\u26A1 Workflows</h3>
|
|
957
|
+
<div class="agent-toggle-list">${workflowCards}</div>
|
|
958
|
+
</div>
|
|
959
|
+
</div>
|
|
960
|
+
|
|
961
|
+
<h3 class="agent-section-subtitle">\u{1F9E0} Skills <span style="font-size:0.7rem;color:#94a3b8;font-weight:400">from skills.sh</span></h3>
|
|
962
|
+
<div class="agent-toggle-grid">
|
|
963
|
+
${skillCards}
|
|
964
|
+
</div>
|
|
965
|
+
|
|
966
|
+
${auditSection}
|
|
967
|
+
|
|
968
|
+
<div class="agent-command-box">
|
|
969
|
+
<div class="agent-command-header">
|
|
970
|
+
<span>\u{1F4A1} Command to generate selected items:</span>
|
|
971
|
+
<button class="agent-copy-btn" onclick="copyAgentCommand()">
|
|
972
|
+
<span id="copyIcon">\u{1F4CB}</span> Copy
|
|
973
|
+
</button>
|
|
974
|
+
</div>
|
|
975
|
+
<code id="agentCommandOutput" class="agent-command-code">${s.command}</code>
|
|
976
|
+
</div>
|
|
977
|
+
</div>
|
|
978
|
+
|
|
979
|
+
<style>
|
|
980
|
+
.agent-system-card { padding: 1.5rem; }
|
|
981
|
+
.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: pre-line; }
|
|
983
|
+
.agent-controls { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem; }
|
|
984
|
+
.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
|
+
.agent-ctrl-btn:hover { background: #334155; }
|
|
986
|
+
.agent-count-label { color: #94a3b8; font-size: 0.85rem; margin-left: auto; }
|
|
987
|
+
#agentSelectedCount { color: #c084fc; font-weight: 700; }
|
|
988
|
+
.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(260px, 1fr)); gap: 0.75rem; }
|
|
990
|
+
.agent-toggle-card { cursor: pointer; transition: all 0.3s; }
|
|
991
|
+
.agent-toggle-card input { display: none; }
|
|
992
|
+
.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 { border-color: #818cf8; background: #1e1b4b; }
|
|
994
|
+
.agent-toggle-icon { font-size: 1.3rem; flex-shrink: 0; }
|
|
995
|
+
.agent-toggle-info { flex: 1; min-width: 0; }
|
|
996
|
+
.agent-toggle-name { display: block; color: #e2e8f0; font-weight: 600; font-size: 0.85rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
997
|
+
.agent-toggle-role { display: block; font-size: 0.7rem; margin-top: 0.15rem; }
|
|
998
|
+
.agent-toggle-check { color: #334155; font-size: 1rem; flex-shrink: 0; transition: color 0.3s; }
|
|
999
|
+
.agent-toggle-card input:checked + .agent-toggle-inner .agent-toggle-check { color: #818cf8; }
|
|
1000
|
+
.agent-toggle-card.mini .agent-toggle-inner { padding: 0.5rem 0.75rem; border-radius: 8px; }
|
|
1001
|
+
.agent-extras-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-top: 0.5rem; }
|
|
1002
|
+
@media (max-width: 768px) { .agent-extras-grid { grid-template-columns: 1fr; } }
|
|
1003
|
+
.agent-toggle-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
1004
|
+
.agent-audit-section { margin-top: 1.5rem; }
|
|
1005
|
+
.agent-audit-grid { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
1006
|
+
.agent-audit-item { display: flex; gap: 0.75rem; align-items: flex-start; background: #1e293b; padding: 0.75rem 1rem; border-radius: 8px; }
|
|
1007
|
+
.agent-audit-item.audit-missing { border-left: 3px solid #ef4444; }
|
|
1008
|
+
.agent-audit-item.audit-improvement { border-left: 3px solid #fbbf24; }
|
|
1009
|
+
.audit-icon { font-size: 1rem; flex-shrink: 0; margin-top: 2px; }
|
|
1010
|
+
.audit-content { display: flex; flex-direction: column; gap: 0.25rem; }
|
|
1011
|
+
.audit-desc { color: #e2e8f0; font-size: 0.85rem; }
|
|
1012
|
+
.audit-suggestion { color: #94a3b8; font-size: 0.8rem; font-style: italic; }
|
|
1013
|
+
.agent-command-box { margin-top: 1.5rem; background: #0f172a; border-radius: 12px; border: 1px solid #334155; overflow: hidden; }
|
|
1014
|
+
.agent-command-header { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem 1rem; background: #1e293b; font-size: 0.8rem; color: #94a3b8; }
|
|
1015
|
+
.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; }
|
|
1016
|
+
.agent-copy-btn:hover { background: #a855f7; transform: scale(1.05); }
|
|
1017
|
+
.agent-command-code { display: block; padding: 1rem; color: #c084fc; font-size: 0.85rem; word-break: break-all; font-family: 'Fira Code', monospace; }
|
|
1018
|
+
</style>
|
|
1019
|
+
|
|
1020
|
+
<script>
|
|
1021
|
+
(function() {
|
|
1022
|
+
var basePath = ${JSON.stringify(s.command.replace('architect agents ', ''))};
|
|
1023
|
+
var totalItems = ${totalItems};
|
|
1024
|
+
function updateCommand() {
|
|
1025
|
+
var checks = document.querySelectorAll('.agent-check');
|
|
1026
|
+
var selected = { agents: [], rules: [], guards: [], workflows: [], skills: [] };
|
|
1027
|
+
var count = 0;
|
|
1028
|
+
checks.forEach(function(cb) { if (cb.checked) { selected[cb.dataset.type].push(cb.dataset.item); count++; } });
|
|
1029
|
+
document.getElementById('agentSelectedCount').textContent = count;
|
|
1030
|
+
var cmd;
|
|
1031
|
+
if (count === totalItems) { cmd = 'architect agents ' + basePath; }
|
|
1032
|
+
else if (count === 0) { cmd = '# No items selected'; }
|
|
1033
|
+
else {
|
|
1034
|
+
var parts = ['architect agents ' + basePath];
|
|
1035
|
+
if (selected.agents.length > 0) parts.push('--agents ' + selected.agents.join(','));
|
|
1036
|
+
if (selected.rules.length > 0) parts.push('--rules ' + selected.rules.join(','));
|
|
1037
|
+
if (selected.guards.length > 0) parts.push('--guards ' + selected.guards.join(','));
|
|
1038
|
+
if (selected.workflows.length > 0) parts.push('--workflows ' + selected.workflows.join(','));
|
|
1039
|
+
if (selected.skills.length > 0) parts.push('&& ' + selected.skills.map(function(sk){ return 'npx skills add ' + sk; }).join(' && '));
|
|
1040
|
+
cmd = parts.join(' ');
|
|
1041
|
+
}
|
|
1042
|
+
document.getElementById('agentCommandOutput').textContent = cmd;
|
|
1043
|
+
}
|
|
1044
|
+
document.querySelectorAll('.agent-check').forEach(function(cb) { cb.addEventListener('change', updateCommand); });
|
|
1045
|
+
window.toggleAll = function(state) { document.querySelectorAll('.agent-check').forEach(function(cb) { cb.checked = state; }); updateCommand(); };
|
|
1046
|
+
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); }); };
|
|
1047
|
+
})();
|
|
1048
|
+
<\/script>`;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
651
1051
|
private getStyles(): string {
|
|
652
1052
|
return `<style>
|
|
653
1053
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
|
|
@@ -810,12 +1210,102 @@ function animateCounter(el, target) {
|
|
|
810
1210
|
.footer a { color: #818cf8; text-decoration: none; }
|
|
811
1211
|
.footer a:hover { text-decoration: underline; }
|
|
812
1212
|
|
|
1213
|
+
/* โโ Refactoring Plan โโ */
|
|
1214
|
+
.refactor-score { padding: 2rem; }
|
|
1215
|
+
.refactor-score-pair {
|
|
1216
|
+
display: flex; align-items: center; justify-content: center; gap: 1.5rem;
|
|
1217
|
+
margin-bottom: 2rem; flex-wrap: wrap;
|
|
1218
|
+
}
|
|
1219
|
+
.rscore-box { text-align: center; }
|
|
1220
|
+
.rscore-num { font-size: 3rem; font-weight: 900; line-height: 1; }
|
|
1221
|
+
.rscore-label { font-size: 0.8rem; color: #94a3b8; text-transform: uppercase; letter-spacing: 1px; }
|
|
1222
|
+
.rscore-improvement { font-size: 1.3rem; font-weight: 700; }
|
|
1223
|
+
|
|
1224
|
+
.refactor-legend { display: flex; gap: 1rem; margin-bottom: 0.5rem; }
|
|
1225
|
+
.rlegend-tag { font-size: 0.75rem; padding: 0.2rem 0.6rem; border-radius: 6px; }
|
|
1226
|
+
.rlegend-tag.rbefore { background: rgba(255,255,255,0.05); color: #94a3b8; }
|
|
1227
|
+
.rlegend-tag.rafter { background: rgba(129,140,248,0.2); color: #818cf8; }
|
|
1228
|
+
|
|
1229
|
+
.refactor-metric-name { width: 100px; font-size: 0.8rem; text-transform: uppercase; color: #94a3b8; font-weight: 600; }
|
|
1230
|
+
.refactor-metric-bars { flex: 1; position: relative; height: 30px; }
|
|
1231
|
+
.rbar-before, .rbar-after {
|
|
1232
|
+
position: absolute; left: 0; height: 14px; border-radius: 4px;
|
|
1233
|
+
display: flex; align-items: center; padding-left: 6px;
|
|
1234
|
+
font-size: 0.7rem; font-weight: 600;
|
|
1235
|
+
}
|
|
1236
|
+
.rbar-before { top: 0; }
|
|
1237
|
+
.rbar-after { top: 15px; }
|
|
1238
|
+
.refactor-metric-diff { width: 50px; text-align: right; font-weight: 700; font-size: 0.85rem; }
|
|
1239
|
+
|
|
1240
|
+
.refactor-stats-row {
|
|
1241
|
+
display: flex; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap;
|
|
1242
|
+
}
|
|
1243
|
+
.rstat {
|
|
1244
|
+
background: #1e293b; border: 1px solid #334155; border-radius: 99px;
|
|
1245
|
+
padding: 0.4rem 1rem; font-size: 0.85rem; color: #94a3b8; font-weight: 500;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.priority-bar {
|
|
1249
|
+
display: flex; border-radius: 12px; overflow: hidden; height: 32px; margin-bottom: 2rem;
|
|
1250
|
+
}
|
|
1251
|
+
.prio-seg {
|
|
1252
|
+
display: flex; align-items: center; justify-content: center;
|
|
1253
|
+
font-size: 0.75rem; font-weight: 600;
|
|
1254
|
+
}
|
|
1255
|
+
.prio-critical { background: #ef444430; color: #ef4444; }
|
|
1256
|
+
.prio-high { background: #f59e0b30; color: #f59e0b; }
|
|
1257
|
+
.prio-medium { background: #3b82f630; color: #60a5fa; }
|
|
1258
|
+
.prio-low { background: #22c55e30; color: #22c55e; }
|
|
1259
|
+
|
|
1260
|
+
.refactor-roadmap { display: flex; flex-direction: column; gap: 1rem; }
|
|
1261
|
+
.rstep-card {
|
|
1262
|
+
background: #1e293b; border-radius: 16px; border: 1px solid #334155;
|
|
1263
|
+
padding: 1.5rem; transition: border-color 0.2s;
|
|
1264
|
+
}
|
|
1265
|
+
.rstep-card:hover { border-color: #818cf8; }
|
|
1266
|
+
.rstep-header { display: flex; gap: 1rem; margin-bottom: 1rem; }
|
|
1267
|
+
.rstep-number {
|
|
1268
|
+
width: 40px; height: 40px; border-radius: 50%;
|
|
1269
|
+
background: linear-gradient(135deg, #818cf8, #c084fc);
|
|
1270
|
+
display: flex; align-items: center; justify-content: center;
|
|
1271
|
+
font-weight: 800; font-size: 1rem; color: white; flex-shrink: 0;
|
|
1272
|
+
}
|
|
1273
|
+
.rstep-info { flex: 1; }
|
|
1274
|
+
.rstep-title-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
|
|
1275
|
+
.rstep-title-row h3 { font-size: 1.1rem; font-weight: 700; }
|
|
1276
|
+
.rstep-desc { color: #94a3b8; font-size: 0.9rem; margin-top: 0.3rem; }
|
|
1277
|
+
.tier-badge {
|
|
1278
|
+
background: #818cf815; color: #818cf8; border: 1px solid #818cf830;
|
|
1279
|
+
padding: 0.15rem 0.5rem; border-radius: 99px; font-size: 0.65rem; font-weight: 600;
|
|
1280
|
+
}
|
|
1281
|
+
.rstep-details { margin-top: 0.5rem; }
|
|
1282
|
+
.rstep-details summary { cursor: pointer; color: #818cf8; font-size: 0.85rem; font-weight: 500; }
|
|
1283
|
+
.rstep-rationale { color: #64748b; font-size: 0.85rem; margin-top: 0.3rem; font-style: italic; }
|
|
1284
|
+
|
|
1285
|
+
.rstep-ops { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #334155; }
|
|
1286
|
+
.rstep-ops h4 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.5rem; }
|
|
1287
|
+
.rop { display: flex; align-items: flex-start; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
|
|
1288
|
+
.rop-icon { font-size: 0.9rem; }
|
|
1289
|
+
.rop-badge { padding: 0.1rem 0.4rem; border-radius: 6px; font-size: 0.65rem; font-weight: 700; }
|
|
1290
|
+
.rop-path { background: #0f172a; padding: 1px 6px; border-radius: 4px; font-size: 0.8rem; color: #c084fc; }
|
|
1291
|
+
.rop-arrow { color: #818cf8; font-weight: 700; }
|
|
1292
|
+
.rop-desc { width: 100%; color: #64748b; font-size: 0.8rem; padding-left: 1.8rem; }
|
|
1293
|
+
|
|
1294
|
+
.rstep-impact { margin-top: 0.5rem; }
|
|
1295
|
+
.rstep-impact h4 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.3rem; }
|
|
1296
|
+
.rimpact-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
|
1297
|
+
.rimpact-tag {
|
|
1298
|
+
background: #22c55e10; color: #22c55e; border: 1px solid #22c55e30;
|
|
1299
|
+
padding: 0.2rem 0.6rem; border-radius: 8px; font-size: 0.75rem; font-weight: 500;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
813
1302
|
/* โโ Responsive โโ */
|
|
814
1303
|
@media (max-width: 768px) {
|
|
815
1304
|
.score-hero { flex-direction: column; gap: 1.5rem; }
|
|
816
1305
|
.score-breakdown { grid-template-columns: 1fr; }
|
|
817
1306
|
.header h1 { font-size: 1.8rem; }
|
|
818
1307
|
.container { padding: 1rem; }
|
|
1308
|
+
.refactor-score-pair { flex-direction: column; }
|
|
819
1309
|
}
|
|
820
1310
|
|
|
821
1311
|
/* โโ Print โโ */
|
package/src/index.ts
CHANGED
|
@@ -5,12 +5,15 @@ import { ArchitectureScorer } from './scorer.js';
|
|
|
5
5
|
import { DiagramGenerator } from './diagram.js';
|
|
6
6
|
import { ReportGenerator } from './reporter.js';
|
|
7
7
|
import { HtmlReportGenerator } from './html-reporter.js';
|
|
8
|
+
import { RefactorEngine } from './refactor-engine.js';
|
|
9
|
+
import { AgentGenerator, AgentSuggestion } from './agent-generator.js';
|
|
8
10
|
import { ConfigLoader } from './config.js';
|
|
9
|
-
import { AnalysisReport } from './types.js';
|
|
11
|
+
import { AnalysisReport, RefactoringPlan } from './types.js';
|
|
10
12
|
import { relative } from 'path';
|
|
11
13
|
|
|
12
14
|
export interface ArchitectCommand {
|
|
13
15
|
analyze: (path: string) => Promise<AnalysisReport>;
|
|
16
|
+
refactor: (report: AnalysisReport, projectPath: string) => RefactoringPlan;
|
|
14
17
|
diagram: (path: string) => Promise<string>;
|
|
15
18
|
score: (path: string) => Promise<{ overall: number; breakdown: Record<string, number> }>;
|
|
16
19
|
antiPatterns: (path: string) => Promise<Array<{ name: string; severity: string; description: string }>>;
|
|
@@ -82,6 +85,40 @@ class Architect implements ArchitectCommand {
|
|
|
82
85
|
return this.relativizePaths(report, projectPath);
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Generate a refactoring plan from an analysis report.
|
|
90
|
+
* Uses Tier 1 (rule engine) and Tier 2 (AST) transforms.
|
|
91
|
+
*/
|
|
92
|
+
refactor(report: AnalysisReport, projectPath: string): RefactoringPlan {
|
|
93
|
+
const engine = new RefactorEngine();
|
|
94
|
+
return engine.analyze(report, projectPath);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generate or audit .agent/ directory for a project.
|
|
99
|
+
*/
|
|
100
|
+
agents(
|
|
101
|
+
report: AnalysisReport,
|
|
102
|
+
plan: RefactoringPlan,
|
|
103
|
+
projectPath: string,
|
|
104
|
+
outputDir?: string
|
|
105
|
+
): { generated: string[]; audit: Array<{ type: string; category: string; file: string; description: string; suggestion?: string }> } {
|
|
106
|
+
const generator = new AgentGenerator();
|
|
107
|
+
return generator.generate(report, plan, projectPath, outputDir);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Suggest agents without writing files โ dry-run for unified report.
|
|
112
|
+
*/
|
|
113
|
+
suggestAgents(
|
|
114
|
+
report: AnalysisReport,
|
|
115
|
+
plan: RefactoringPlan,
|
|
116
|
+
projectPath: string,
|
|
117
|
+
): AgentSuggestion {
|
|
118
|
+
const generator = new AgentGenerator();
|
|
119
|
+
return generator.suggest(report, plan, projectPath);
|
|
120
|
+
}
|
|
121
|
+
|
|
85
122
|
private relativizePaths(report: AnalysisReport, basePath: string): AnalysisReport {
|
|
86
123
|
const rel = (p: string): string => {
|
|
87
124
|
if (p.startsWith('/') || p.startsWith('\\')) {
|
|
@@ -268,6 +305,7 @@ export {
|
|
|
268
305
|
DiagramGenerator,
|
|
269
306
|
ReportGenerator,
|
|
270
307
|
HtmlReportGenerator,
|
|
308
|
+
AgentGenerator,
|
|
271
309
|
ConfigLoader,
|
|
272
310
|
};
|
|
273
311
|
|