@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.
Files changed (58) hide show
  1. package/README.md +111 -112
  2. package/dist/agent-generator.d.ts +95 -0
  3. package/dist/agent-generator.d.ts.map +1 -0
  4. package/dist/agent-generator.js +1295 -0
  5. package/dist/agent-generator.js.map +1 -0
  6. package/dist/cli.js +76 -2
  7. package/dist/cli.js.map +1 -1
  8. package/dist/html-reporter.d.ts +8 -2
  9. package/dist/html-reporter.d.ts.map +1 -1
  10. package/dist/html-reporter.js +470 -5
  11. package/dist/html-reporter.js.map +1 -1
  12. package/dist/index.d.ts +26 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +25 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/refactor-engine.d.ts +18 -0
  17. package/dist/refactor-engine.d.ts.map +1 -0
  18. package/dist/refactor-engine.js +86 -0
  19. package/dist/refactor-engine.js.map +1 -0
  20. package/dist/refactor-reporter.d.ts +20 -0
  21. package/dist/refactor-reporter.d.ts.map +1 -0
  22. package/dist/refactor-reporter.js +389 -0
  23. package/dist/refactor-reporter.js.map +1 -0
  24. package/dist/rules/barrel-optimizer.d.ts +13 -0
  25. package/dist/rules/barrel-optimizer.d.ts.map +1 -0
  26. package/dist/rules/barrel-optimizer.js +77 -0
  27. package/dist/rules/barrel-optimizer.js.map +1 -0
  28. package/dist/rules/dead-code-detector.d.ts +21 -0
  29. package/dist/rules/dead-code-detector.d.ts.map +1 -0
  30. package/dist/rules/dead-code-detector.js +117 -0
  31. package/dist/rules/dead-code-detector.js.map +1 -0
  32. package/dist/rules/hub-splitter.d.ts +13 -0
  33. package/dist/rules/hub-splitter.d.ts.map +1 -0
  34. package/dist/rules/hub-splitter.js +110 -0
  35. package/dist/rules/hub-splitter.js.map +1 -0
  36. package/dist/rules/import-organizer.d.ts +13 -0
  37. package/dist/rules/import-organizer.d.ts.map +1 -0
  38. package/dist/rules/import-organizer.js +85 -0
  39. package/dist/rules/import-organizer.js.map +1 -0
  40. package/dist/rules/module-grouper.d.ts +13 -0
  41. package/dist/rules/module-grouper.d.ts.map +1 -0
  42. package/dist/rules/module-grouper.js +110 -0
  43. package/dist/rules/module-grouper.js.map +1 -0
  44. package/dist/types.d.ts +51 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/src/agent-generator.ts +1401 -0
  48. package/src/cli.ts +83 -2
  49. package/src/html-reporter.ts +496 -6
  50. package/src/index.ts +39 -1
  51. package/src/refactor-engine.ts +117 -0
  52. package/src/refactor-reporter.ts +408 -0
  53. package/src/rules/barrel-optimizer.ts +97 -0
  54. package/src/rules/dead-code-detector.ts +132 -0
  55. package/src/rules/hub-splitter.ts +123 -0
  56. package/src/rules/import-organizer.ts +98 -0
  57. package/src/rules/module-grouper.ts +124 -0
  58. package/src/types.ts +52 -0
@@ -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 = Math.max(400, nodes.length * 25);
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(80))
541
- .force('charge', d3.forceManyBody().strength(-200))
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