@buoy-design/cli 0.3.32 → 0.3.34

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 (61) hide show
  1. package/dist/commands/check.d.ts.sync-conflict-20260305-170128-6PCZ3ZU.map +1 -0
  2. package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.d.ts +26 -0
  3. package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.d.ts.map +1 -0
  4. package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.js +438 -0
  5. package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.js.map +1 -0
  6. package/dist/commands/dock.sync-conflict-20260309-191923-6PCZ3ZU.js +1006 -0
  7. package/dist/commands/show.d.ts.map +1 -1
  8. package/dist/commands/show.d.ts.sync-conflict-20260306-165917-6PCZ3ZU.map +1 -0
  9. package/dist/commands/show.js +6 -0
  10. package/dist/commands/show.js.map +1 -1
  11. package/dist/commands/show.sync-conflict-20260305-140755-6PCZ3ZU.js +1735 -0
  12. package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.d.ts +11 -0
  13. package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.d.ts.map +1 -0
  14. package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.js +1735 -0
  15. package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.js.map +1 -0
  16. package/dist/config/loader.js +1 -1
  17. package/dist/config/loader.js.map +1 -1
  18. package/dist/config/loader.js.sync-conflict-20260309-033512-6PCZ3ZU.map +1 -0
  19. package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.d.ts +8 -0
  20. package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.d.ts.map +1 -0
  21. package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.js +162 -0
  22. package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.js.map +1 -0
  23. package/dist/config/schema.d.ts.sync-conflict-20260309-154654-6PCZ3ZU.map +1 -0
  24. package/dist/config/schema.sync-conflict-20260309-135703-6PCZ3ZU.js +214 -0
  25. package/dist/detect/frameworks.js.sync-conflict-20260306-123756-6PCZ3ZU.map +1 -0
  26. package/dist/detect/monorepo-patterns.js.sync-conflict-20260309-155400-6PCZ3ZU.map +1 -0
  27. package/dist/hooks/index.d.ts.sync-conflict-20260306-220901-6PCZ3ZU.map +1 -0
  28. package/dist/output/formatters.js.sync-conflict-20260306-134702-6PCZ3ZU.map +1 -0
  29. package/dist/output/formatters.sync-conflict-20260306-180804-6PCZ3ZU.js +867 -0
  30. package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.d.ts +29 -0
  31. package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.d.ts.map +1 -0
  32. package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.js +867 -0
  33. package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.js.map +1 -0
  34. package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.d.ts +29 -0
  35. package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.d.ts.map +1 -0
  36. package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.js +867 -0
  37. package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.js.map +1 -0
  38. package/dist/output/index.sync-conflict-20260309-222859-6PCZ3ZU.js +5 -0
  39. package/dist/output/reporters.d.sync-conflict-20260309-193820-6PCZ3ZU.ts +38 -0
  40. package/dist/output/reporters.d.ts.sync-conflict-20260306-193811-6PCZ3ZU.map +1 -0
  41. package/dist/output/reporters.sync-conflict-20260309-030558-6PCZ3ZU.js +182 -0
  42. package/dist/output/reports.d.ts.sync-conflict-20260307-172149-6PCZ3ZU.map +1 -0
  43. package/dist/output/reports.js.sync-conflict-20260305-161643-6PCZ3ZU.map +1 -0
  44. package/dist/output/reports.sync-conflict-20260305-211951-6PCZ3ZU.js +393 -0
  45. package/dist/output/visuals.d.ts +53 -0
  46. package/dist/output/visuals.d.ts.map +1 -0
  47. package/dist/output/visuals.js +194 -0
  48. package/dist/output/visuals.js.map +1 -0
  49. package/dist/services/drift-analysis.d.sync-conflict-20260306-151016-6PCZ3ZU.ts +194 -0
  50. package/dist/services/drift-analysis.d.ts.sync-conflict-20260307-175904-6PCZ3ZU.map +1 -0
  51. package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.d.ts +194 -0
  52. package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.d.ts.map +1 -0
  53. package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.js +1022 -0
  54. package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.js.map +1 -0
  55. package/dist/services/skill-export.d.ts.sync-conflict-20260309-171021-6PCZ3ZU.map +1 -0
  56. package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.d.ts +109 -0
  57. package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.d.ts.map +1 -0
  58. package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.js +737 -0
  59. package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.js.map +1 -0
  60. package/package.json +14 -14
  61. package/LICENSE +0 -21
@@ -0,0 +1,393 @@
1
+ // apps/cli/src/output/reports.ts
2
+ // Visual 1-page reports for design system health
3
+ import chalk from 'chalk';
4
+ import { highlight } from './visuals.js';
5
+ // ============================================================================
6
+ // Helper Functions
7
+ // ============================================================================
8
+ function section(title, width = 78) {
9
+ const line = '─'.repeat(width - title.length - 4);
10
+ return `┌─ ${title} ${line}┐`;
11
+ }
12
+ function sectionEnd(width = 78) {
13
+ return `└${'─'.repeat(width - 2)}┘`;
14
+ }
15
+ function progressBar(percent, width = 20) {
16
+ const filled = Math.round((percent / 100) * width);
17
+ const empty = width - filled;
18
+ return highlight.success('\u2588'.repeat(filled)) + highlight.dim('\u2591'.repeat(empty));
19
+ }
20
+ function healthDots(percent) {
21
+ const filled = Math.round(percent / 20);
22
+ const empty = 5 - filled;
23
+ return highlight.success('\u25CF'.repeat(filled)) + highlight.dim('\u25CB'.repeat(empty));
24
+ }
25
+ function trendArrow(trend) {
26
+ if (typeof trend === 'number') {
27
+ if (trend > 0)
28
+ return chalk.green(`↑ ${trend}%`);
29
+ if (trend < 0)
30
+ return chalk.red(`↓ ${Math.abs(trend)}%`);
31
+ return chalk.dim('→ 0%');
32
+ }
33
+ switch (trend) {
34
+ case 'improving': return chalk.green('↓ improving');
35
+ case 'declining': return chalk.red('↑ declining');
36
+ default: return chalk.dim('→ stable');
37
+ }
38
+ }
39
+ function formatMoney(amount) {
40
+ return '$' + amount.toLocaleString('en-US');
41
+ }
42
+ function formatDate(date) {
43
+ const now = new Date();
44
+ const diffMs = now.getTime() - date.getTime();
45
+ const diffMins = Math.floor(diffMs / 60000);
46
+ if (diffMins < 60)
47
+ return `${diffMins}m ago`;
48
+ const diffHours = Math.floor(diffMins / 60);
49
+ if (diffHours < 24)
50
+ return `${diffHours}hr ago`;
51
+ return date.toLocaleDateString();
52
+ }
53
+ // ============================================================================
54
+ // Developer Daily Brief
55
+ // ============================================================================
56
+ export function formatDeveloperBrief(data) {
57
+ const lines = [];
58
+ const width = 80;
59
+ // Header
60
+ lines.push('');
61
+ lines.push(chalk.bold.cyan('╭' + '─'.repeat(width - 2) + '╮'));
62
+ lines.push(chalk.bold.cyan('│') + ' ' + chalk.bold('BUOY DEVELOPER BRIEF') + ' '.repeat(width - 46) + chalk.dim(`${data.branch.target} ← ${data.branch.source}`) + ' ' + chalk.bold.cyan('│'));
63
+ lines.push(chalk.bold.cyan('│') + ' ' + chalk.dim('Your changes vs. design system') + ' '.repeat(width - 57) + chalk.dim(`Generated: just now`) + ' ' + chalk.bold.cyan('│'));
64
+ lines.push(chalk.bold.cyan('╰' + '─'.repeat(width - 2) + '╯'));
65
+ lines.push('');
66
+ // Your Changes Section
67
+ lines.push(section('YOUR CHANGES', width));
68
+ lines.push('│' + ' '.repeat(width - 2) + '│');
69
+ lines.push('│ ' + `Files touched: ${chalk.bold(data.filesChanged.length)}`.padEnd(width - 4) + '│');
70
+ lines.push('│ ' + `Components modified: ${chalk.bold(data.componentsModified.length)}`.padEnd(width - 4) + '│');
71
+ // Show component status indicators
72
+ if (data.componentsModified.length > 0) {
73
+ lines.push('│' + ' '.repeat(width - 2) + '│');
74
+ const componentLine = data.componentsModified.slice(0, 3).map(comp => {
75
+ const hasDrift = data.drifts.some(d => d.source.entityName === comp);
76
+ if (hasDrift) {
77
+ return ` ${chalk.yellow('⚠')} ${comp}`;
78
+ }
79
+ return ` ${chalk.green('✓')} ${comp} ${chalk.dim('Clean')}`;
80
+ }).join(' ');
81
+ lines.push('│ ' + componentLine.padEnd(width - 4 + 20) + '│'); // Extra padding for ANSI
82
+ }
83
+ lines.push('│' + ' '.repeat(width - 2) + '│');
84
+ lines.push(sectionEnd(width));
85
+ lines.push('');
86
+ // Action Needed Section
87
+ if (data.drifts.length > 0) {
88
+ lines.push(section('ACTION NEEDED', width));
89
+ lines.push('│' + ' '.repeat(width - 2) + '│');
90
+ for (const drift of data.drifts.slice(0, 3)) {
91
+ const icon = drift.severity === 'critical' ? chalk.red('!') : chalk.yellow('⚠');
92
+ lines.push(`│ ${icon} ${chalk.bold(drift.source.location || drift.source.entityName)}`.padEnd(width + 8) + '│');
93
+ lines.push(`│ └─ ${drift.message}`.padEnd(width - 2) + '│');
94
+ // Show fix suggestion
95
+ if (drift.type === 'hardcoded-value') {
96
+ lines.push(`│ └─ ${chalk.dim('Use:')} ${chalk.cyan('var(--color-primary)')} ${chalk.dim('or theme token')}`.padEnd(width + 15) + '│');
97
+ }
98
+ lines.push('│' + ' '.repeat(width - 2) + '│');
99
+ }
100
+ // Quick actions
101
+ lines.push('│ ' + chalk.bold.cyan('Quick actions:') + ' '.repeat(width - 18) + '│');
102
+ lines.push('│ ' + ` ${chalk.cyan('buoy drift fix --interactive')} ${chalk.dim('Fix with guided prompts')}`.padEnd(width + 10) + '│');
103
+ lines.push('│ ' + ` ${chalk.cyan('buoy ignore <file>:<line>')} ${chalk.dim('Mark as intentional')}`.padEnd(width + 10) + '│');
104
+ lines.push('│' + ' '.repeat(width - 2) + '│');
105
+ lines.push(sectionEnd(width));
106
+ lines.push('');
107
+ }
108
+ // Components You're Using
109
+ const healthEntries = Object.entries(data.componentHealth).slice(0, 3);
110
+ if (healthEntries.length > 0) {
111
+ lines.push(section('COMPONENTS YOU\'RE USING', width));
112
+ lines.push('│' + ' '.repeat(width - 2) + '│');
113
+ const healthLine = healthEntries.map(([name, stats]) => {
114
+ return ` ${chalk.bold(name)} ${healthDots(stats.health)} ${stats.health}%`;
115
+ }).join(' ');
116
+ lines.push('│' + healthLine.padEnd(width + 25) + '│');
117
+ const usageLine = healthEntries.map(([_name, stats]) => {
118
+ return ` └─ ${stats.usageCount} uses, ${stats.driftCount} drift`;
119
+ }).join(' ');
120
+ lines.push('│' + chalk.dim(usageLine).padEnd(width + 10) + '│');
121
+ lines.push('│' + ' '.repeat(width - 2) + '│');
122
+ lines.push(sectionEnd(width));
123
+ lines.push('');
124
+ }
125
+ // Quick Stats
126
+ lines.push(section('QUICK STATS', width));
127
+ const statsLine = `│ Your drift rate: ${chalk.bold(data.personalStats.driftRate + '%')} │ Team avg: ${chalk.bold(data.personalStats.teamAverage + '%')} │ Trend: ${trendArrow(data.personalStats.trend)}`;
128
+ lines.push(statsLine.padEnd(width + 25) + '│');
129
+ lines.push(sectionEnd(width));
130
+ return lines.join('\n');
131
+ }
132
+ // ============================================================================
133
+ // Team Dashboard
134
+ // ============================================================================
135
+ export function formatTeamDashboard(data) {
136
+ const lines = [];
137
+ const width = 80;
138
+ // Header
139
+ lines.push('');
140
+ lines.push(chalk.bold.blue('╭' + '─'.repeat(width - 2) + '╮'));
141
+ lines.push(chalk.bold.blue('│') + ' ' + chalk.bold('DESIGN SYSTEM HEALTH REPORT') + ' '.repeat(width - 54) + chalk.dim(data.period.label) + ' ' + chalk.bold.blue('│'));
142
+ lines.push(chalk.bold.blue('│') + ' ' + chalk.dim(`@acme/design-system v${data.version}`) + ' '.repeat(width - 50) + chalk.dim('vs. last week') + ' ' + chalk.bold.blue('│'));
143
+ lines.push(chalk.bold.blue('╰' + '─'.repeat(width - 2) + '╯'));
144
+ lines.push('');
145
+ // System Health Section
146
+ lines.push(section('SYSTEM HEALTH', width));
147
+ lines.push('│' + ' '.repeat(width - 2) + '│');
148
+ // Health metrics
149
+ const tokenBar = progressBar(data.health.tokenCoverage.current, 14);
150
+ const componentBar = progressBar(data.health.componentAdoption.current, 14);
151
+ const patternBar = progressBar(data.health.patternConsistency.current, 14);
152
+ lines.push(`│ ${chalk.bold('TOKEN COVERAGE')} ${chalk.bold('COMPONENT ADOPTION')} ${chalk.bold('PATTERN CONSISTENCY')} │`);
153
+ lines.push(`│ ${tokenBar} ${data.health.tokenCoverage.current}% ${componentBar} ${data.health.componentAdoption.current}% ${patternBar} ${data.health.patternConsistency.current}% │`);
154
+ lines.push(`│ ${trendArrow(data.health.tokenCoverage.change)} ${trendArrow(data.health.componentAdoption.change)} ${trendArrow(data.health.patternConsistency.change)} │`);
155
+ lines.push('│' + ' '.repeat(width - 2) + '│');
156
+ lines.push(`│ Components: ${data.totals.components} Instances: ${data.totals.instances.toLocaleString()} Active repos: ${data.totals.repos} │`);
157
+ lines.push(`│ Tokens: ${data.totals.tokens} Drift signals: ${data.totals.driftSignals} Contributors: ${data.totals.contributors} │`);
158
+ lines.push('│' + ' '.repeat(width - 2) + '│');
159
+ lines.push(sectionEnd(width));
160
+ lines.push('');
161
+ // Drift Distribution
162
+ const byTypeEntries = Object.entries(data.driftDistribution.byType).slice(0, 5);
163
+ const byTeamEntries = Object.entries(data.driftDistribution.byTeam).slice(0, 5);
164
+ if (byTypeEntries.length > 0 || byTeamEntries.length > 0) {
165
+ lines.push(section('DRIFT DISTRIBUTION', width));
166
+ lines.push('│' + ' '.repeat(width - 2) + '│');
167
+ lines.push(`│ ${chalk.bold('By Type')} ${chalk.bold('By Team')}`.padEnd(width + 5) + '│');
168
+ lines.push(`│ ${'─'.repeat(25)} ${'─'.repeat(25)}`.padEnd(width - 2) + '│');
169
+ const maxRows = Math.max(byTypeEntries.length, byTeamEntries.length);
170
+ for (let i = 0; i < maxRows; i++) {
171
+ const typeEntry = byTypeEntries[i];
172
+ const teamEntry = byTeamEntries[i];
173
+ const typePart = typeEntry
174
+ ? `${typeEntry[0].padEnd(18)} ${chalk.cyan('█'.repeat(Math.min(8, Math.ceil(typeEntry[1] / 50))))} ${typeEntry[1]}`
175
+ : '';
176
+ const teamPart = teamEntry
177
+ ? `${teamEntry[0].padEnd(15)} ${chalk.yellow('█'.repeat(Math.min(8, Math.ceil(teamEntry[1] / 50))))} ${teamEntry[1]}`
178
+ : '';
179
+ lines.push(`│ ${typePart.padEnd(38)} ${teamPart}`.padEnd(width + 15) + '│');
180
+ }
181
+ lines.push('│' + ' '.repeat(width - 2) + '│');
182
+ lines.push(sectionEnd(width));
183
+ lines.push('');
184
+ }
185
+ // Component Health
186
+ const healthy = data.componentHealth.filter(c => c.category === 'healthy').slice(0, 4);
187
+ const attention = data.componentHealth.filter(c => c.category === 'attention').slice(0, 4);
188
+ const critical = data.componentHealth.filter(c => c.category === 'critical').slice(0, 2);
189
+ if (data.componentHealth.length > 0) {
190
+ lines.push(section('COMPONENT HEALTH', width));
191
+ lines.push('│' + ' '.repeat(width - 2) + '│');
192
+ lines.push(`│ ${chalk.green.bold('✓ HEALTHY (>90%)')} ${chalk.yellow.bold('⚠ ATTENTION (<70%)')} ${chalk.red.bold('✗ CRITICAL (<50%)')} │`);
193
+ lines.push(`│ ${'───────────────'} ${'─────────────────'} ${'────────────────'} │`);
194
+ const maxRows = Math.max(healthy.length, attention.length, critical.length);
195
+ for (let i = 0; i < maxRows; i++) {
196
+ const h = healthy[i];
197
+ const a = attention[i];
198
+ const c = critical[i];
199
+ const hPart = h ? `${h.name.padEnd(12)} ${h.health}%` : '';
200
+ const aPart = a ? `${a.name.padEnd(12)} ${a.health}%` : '';
201
+ const cPart = c ? `${c.name.padEnd(12)} ${c.health}%` : '';
202
+ lines.push(`│ ${chalk.green(hPart.padEnd(18))} ${chalk.yellow(aPart.padEnd(18))} ${chalk.red(cPart.padEnd(18))} │`);
203
+ }
204
+ lines.push('│' + ' '.repeat(width - 2) + '│');
205
+ lines.push(sectionEnd(width));
206
+ lines.push('');
207
+ }
208
+ // Emerging Patterns
209
+ if (data.emergingPatterns.length > 0) {
210
+ lines.push(section('EMERGING PATTERNS', width));
211
+ lines.push('│' + ' '.repeat(width - 2) + '│');
212
+ for (const pattern of data.emergingPatterns.slice(0, 3)) {
213
+ lines.push(`│ ${chalk.cyan('🆕')} ${chalk.bold(pattern.name)} ${pattern.instances} instances across ${pattern.repos} repos Status: ${chalk.yellow(pattern.status)}`.padEnd(width + 10) + '│');
214
+ lines.push(`│ First seen: ${pattern.firstSeen.toLocaleDateString()} Authors: ${pattern.authors.join(', ')}`.padEnd(width - 2) + '│');
215
+ lines.push('│' + ' '.repeat(width - 2) + '│');
216
+ }
217
+ lines.push(sectionEnd(width));
218
+ lines.push('');
219
+ }
220
+ // Actions
221
+ if (data.actions.length > 0) {
222
+ lines.push(section('ACTIONS', width));
223
+ data.actions.forEach((action, i) => {
224
+ lines.push(`│ ${i + 1}. ${action}`.padEnd(width - 2) + '│');
225
+ });
226
+ lines.push(sectionEnd(width));
227
+ }
228
+ return lines.join('\n');
229
+ }
230
+ // ============================================================================
231
+ // Executive Summary
232
+ // ============================================================================
233
+ export function formatExecutiveSummary(data) {
234
+ const lines = [];
235
+ const width = 80;
236
+ // Header
237
+ lines.push('');
238
+ lines.push(chalk.bold.magenta('╭' + '─'.repeat(width - 2) + '╮'));
239
+ lines.push(chalk.bold.magenta('│') + ' ' + chalk.bold('DESIGN SYSTEM EXECUTIVE SUMMARY') + ' '.repeat(width - 52) + chalk.dim(data.period.label) + ' ' + chalk.bold.magenta('│'));
240
+ lines.push(chalk.bold.magenta('│') + ' ' + chalk.dim('Design-Development Alignment Report') + ' '.repeat(width - 41) + chalk.bold.magenta('│'));
241
+ lines.push(chalk.bold.magenta('╰' + '─'.repeat(width - 2) + '╯'));
242
+ lines.push('');
243
+ // The Headline
244
+ lines.push(section('THE HEADLINE', width));
245
+ lines.push('│' + ' '.repeat(width - 2) + '│');
246
+ const alignmentBar = progressBar(data.alignment.current, 30);
247
+ lines.push(`│ ${alignmentBar} ${chalk.bold.green(data.alignment.current + '% ALIGNED')}`.padEnd(width + 15) + '│');
248
+ lines.push('│' + ' '.repeat(width - 2) + '│');
249
+ lines.push(`│ "${data.alignment.current}% of shipped code matches design intent. Up from ${data.alignment.previous}% last period."`.padEnd(width - 2) + '│');
250
+ lines.push('│' + ' '.repeat(width - 2) + '│');
251
+ lines.push('│' + ' '.repeat(width - 2) + '│');
252
+ lines.push(sectionEnd(width));
253
+ lines.push('');
254
+ // Business Impact
255
+ lines.push(section('BUSINESS IMPACT', width));
256
+ lines.push('│' + ' '.repeat(width - 2) + '│');
257
+ lines.push(`│ ${chalk.bold('DESIGN REVIEW TIME')} ${chalk.bold('DEV REWORK REDUCTION')} ${chalk.bold('BRAND CONSISTENCY')} │`);
258
+ lines.push(`│ ${chalk.green('↓ ' + data.impact.designReviewTime.reduction + '%')} ${chalk.green('↓ ' + data.impact.devRework.reduction + '%')} ${chalk.green('↑ ' + data.impact.brandConsistency.increase + '%')} │`);
259
+ lines.push(`│ ${data.impact.designReviewTime.from} → ${data.impact.designReviewTime.to} ${data.impact.designReviewTime.unit} ${data.impact.devRework.from} → ${data.impact.devRework.to} ${data.impact.devRework.unit} NPS: ${data.impact.brandConsistency.nps.from} → ${data.impact.brandConsistency.nps.to} │`);
260
+ lines.push('│' + ' '.repeat(width - 2) + '│');
261
+ lines.push(`│ Estimated savings: ${chalk.bold.green(formatMoney(data.impact.estimatedSavings))} in reduced rework and review cycles`.padEnd(width - 2) + '│');
262
+ lines.push('│' + ' '.repeat(width - 2) + '│');
263
+ lines.push(sectionEnd(width));
264
+ lines.push('');
265
+ // Adoption Trajectory
266
+ lines.push(section('ADOPTION TRAJECTORY', width));
267
+ lines.push('│' + ' '.repeat(width - 2) + '│');
268
+ lines.push(`│ At current velocity: ${chalk.bold(data.trajectory.projected + '%')} alignment achievable by ${chalk.bold(data.trajectory.targetDate)}`.padEnd(width - 2) + '│');
269
+ lines.push('│' + ' '.repeat(width - 2) + '│');
270
+ lines.push(sectionEnd(width));
271
+ lines.push('');
272
+ // Risk Areas
273
+ if (data.risks.length > 0) {
274
+ lines.push(section('RISK AREAS', width));
275
+ lines.push('│' + ' '.repeat(width - 2) + '│');
276
+ for (const risk of data.risks) {
277
+ lines.push(`│ ${chalk.yellow('⚠')} ${chalk.bold(risk.title)}`.padEnd(width + 5) + '│');
278
+ lines.push(`│ ${risk.description}`.padEnd(width - 2) + '│');
279
+ lines.push(`│ ${chalk.dim('Recommendation:')} ${risk.recommendation}`.padEnd(width - 2) + '│');
280
+ lines.push('│' + ' '.repeat(width - 2) + '│');
281
+ }
282
+ lines.push(sectionEnd(width));
283
+ lines.push('');
284
+ }
285
+ // Key Decisions Needed
286
+ if (data.decisions.length > 0) {
287
+ lines.push(section('KEY DECISIONS NEEDED', width));
288
+ lines.push('│' + ' '.repeat(width - 2) + '│');
289
+ data.decisions.forEach((decision, i) => {
290
+ lines.push(`│ ${i + 1}. ${decision}`.padEnd(width - 2) + '│');
291
+ });
292
+ lines.push('│' + ' '.repeat(width - 2) + '│');
293
+ lines.push(sectionEnd(width));
294
+ lines.push('');
295
+ }
296
+ // Team Performance
297
+ if (data.teamPerformance.leading.length > 0 || data.teamPerformance.needingSupport.length > 0) {
298
+ const leadingPart = data.teamPerformance.leading.slice(0, 3).map(t => `${chalk.green('🏆')} ${t.name}: ${t.alignment}%`).join(' ');
299
+ const supportPart = data.teamPerformance.needingSupport.slice(0, 3).map(t => `${chalk.yellow('🔧')} ${t.name}: ${t.alignment}% (${t.reason})`).join(' ');
300
+ lines.push(`┌─ TEAMS LEADING ──────────────────┬─ TEAMS NEEDING SUPPORT ──────────────────┐`);
301
+ lines.push(`│ ${leadingPart}`.padEnd(36) + `│ ${supportPart}`.padEnd(45) + '│');
302
+ lines.push(`└──────────────────────────────────┴──────────────────────────────────────────┘`);
303
+ }
304
+ return lines.join('\n');
305
+ }
306
+ // ============================================================================
307
+ // Reality Mirror (Designer Report)
308
+ // ============================================================================
309
+ export function formatRealityMirror(data) {
310
+ const lines = [];
311
+ const width = 80;
312
+ // Header
313
+ lines.push('');
314
+ lines.push(chalk.bold.yellow('╭' + '─'.repeat(width - 2) + '╮'));
315
+ lines.push(chalk.bold.yellow('│') + ' ' + chalk.bold('REALITY MIRROR') + ' '.repeat(width - 42) + chalk.dim(data.designFile) + ' ' + chalk.bold.yellow('│'));
316
+ lines.push(chalk.bold.yellow('│') + ' ' + chalk.dim('What you designed vs. what shipped') + ' '.repeat(width - 56) + chalk.dim(`Last sync: ${formatDate(data.lastSync)}`) + ' ' + chalk.bold.yellow('│'));
317
+ lines.push(chalk.bold.yellow('╰' + '─'.repeat(width - 2) + '╯'));
318
+ lines.push('');
319
+ // Fidelity Score
320
+ lines.push(section('FIDELITY SCORE', width));
321
+ lines.push('│' + ' '.repeat(width - 2) + '│');
322
+ const fidelityBar = progressBar(data.fidelityScore, 20);
323
+ const fidelityColor = data.fidelityScore >= 90 ? chalk.green : data.fidelityScore >= 70 ? chalk.yellow : chalk.red;
324
+ lines.push(`│ ${chalk.bold('YOUR DESIGN')} ${chalk.bold('SHIPPED CODE')} ${chalk.bold('MATCH')} │`);
325
+ lines.push(`│ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │`);
326
+ lines.push(`│ │ ${chalk.green('▓▓▓▓▓▓▓▓▓')} │ → │ ${fidelityColor('▓▓▓▓▓' + '░'.repeat(4))} │ = │ ${fidelityColor.bold(data.fidelityScore + '%')} │ │`);
327
+ lines.push(`│ └─────────────┘ └─────────────┘ │ ${fidelityBar.slice(0, 10)}│ │`);
328
+ lines.push(`│ └───────────┘ │`);
329
+ lines.push('│' + ' '.repeat(width - 2) + '│');
330
+ if (data.fidelityScore === 100) {
331
+ lines.push(`│ "${chalk.green('100% of your design intent made it to production unchanged. Perfect match!')}"`.padEnd(width - 2) + '│');
332
+ }
333
+ else {
334
+ lines.push(`│ "${data.fidelityScore}% of your design intent made it to production unchanged."`.padEnd(width - 2) + '│');
335
+ }
336
+ lines.push('│' + ' '.repeat(width - 2) + '│');
337
+ lines.push(sectionEnd(width));
338
+ lines.push('');
339
+ // What Changed
340
+ if (data.changes.length > 0) {
341
+ lines.push(section('WHAT CHANGED', width));
342
+ lines.push('│' + ' '.repeat(width - 2) + '│');
343
+ for (const change of data.changes) {
344
+ lines.push(`│ ${chalk.bold(change.category)}`.padEnd(width + 5) + '│');
345
+ lines.push(`│ ${'─'.repeat(width - 4)}`.padEnd(width - 2) + '│');
346
+ lines.push(`│ Your value → Shipped as Why it matters`.padEnd(width - 2) + '│');
347
+ const designedColor = change.category === 'COLORS' ? chalk.bgHex(change.designed.replace('#', '')).black(' ') : '';
348
+ const shippedColor = change.category === 'COLORS' ? chalk.bgHex(change.shipped.replace('#', '')).black(' ') : '';
349
+ lines.push(`│ ${designedColor} ${change.designed} ${shippedColor} ${change.shipped} ${change.impact}`.padEnd(width + 10) + '│');
350
+ lines.push('│' + ' '.repeat(width - 2) + '│');
351
+ }
352
+ lines.push(sectionEnd(width));
353
+ lines.push('');
354
+ }
355
+ // Pattern Drift
356
+ if (data.patternDrift.length > 0) {
357
+ lines.push(section('PATTERN DRIFT', width));
358
+ lines.push('│' + ' '.repeat(width - 2) + '│');
359
+ lines.push(`│ ${chalk.bold('Component')} ${chalk.bold('Your Intent')} ${chalk.bold('Shipped')}`.padEnd(width - 2) + '│');
360
+ lines.push(`│ ${'─'.repeat(width - 4)}`.padEnd(width - 2) + '│');
361
+ for (const pattern of data.patternDrift) {
362
+ lines.push(`│ ${chalk.bold(pattern.component)}`.padEnd(width + 5) + '│');
363
+ lines.push(`│ ${pattern.designedAs}`.padEnd(width - 2) + '│');
364
+ lines.push(`│ ${chalk.dim('→')} ${pattern.shippedAs}`.padEnd(width - 2) + '│');
365
+ lines.push('│' + ' '.repeat(width - 2) + '│');
366
+ lines.push(`│ ${chalk.dim('User Impact:')} ${pattern.userImpact}`.padEnd(width - 2) + '│');
367
+ if (pattern.devNote) {
368
+ lines.push(`│ ${chalk.dim('Dev Note:')} "${pattern.devNote.message}" - ${pattern.devNote.author}, ${pattern.devNote.date.toLocaleDateString()}`.padEnd(width - 2) + '│');
369
+ }
370
+ lines.push('│' + ' '.repeat(width - 2) + '│');
371
+ }
372
+ lines.push(sectionEnd(width));
373
+ lines.push('');
374
+ }
375
+ // Start a Conversation
376
+ if (data.conversations.length > 0) {
377
+ lines.push(section('START A CONVERSATION', width));
378
+ lines.push('│' + ' '.repeat(width - 2) + '│');
379
+ lines.push(`│ These changes might be intentional improvements. Want to discuss?`.padEnd(width - 2) + '│');
380
+ lines.push('│' + ' '.repeat(width - 2) + '│');
381
+ lines.push(`│ ┌${'─'.repeat(width - 6)}┐ │`);
382
+ for (const conv of data.conversations) {
383
+ const icon = conv.action === 'discuss' ? '💬' : conv.action === 'accept' ? '✓' : '↩';
384
+ const target = conv.target ? ` → ${conv.target}` : '';
385
+ lines.push(`│ │ ${icon} "${conv.label}"${target}`.padEnd(width - 2) + '│ │');
386
+ }
387
+ lines.push(`│ └${'─'.repeat(width - 6)}┘ │`);
388
+ lines.push('│' + ' '.repeat(width - 2) + '│');
389
+ lines.push(sectionEnd(width));
390
+ }
391
+ return lines.join('\n');
392
+ }
393
+ //# sourceMappingURL=reports.js.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Visual Feedback System
3
+ *
4
+ * Centralized visual primitives for CLI output, inspired by react-doctor.
5
+ * Provides: highlighter, score bar, score gauge, buoy mascot, summary box,
6
+ * severity icons, and separator.
7
+ *
8
+ * All functions are pure (return strings, no side effects).
9
+ */
10
+ import { type ChalkInstance } from 'chalk';
11
+ export declare const highlight: {
12
+ readonly error: ChalkInstance;
13
+ readonly warn: ChalkInstance;
14
+ readonly info: ChalkInstance;
15
+ readonly success: ChalkInstance;
16
+ readonly dim: ChalkInstance;
17
+ readonly brand: ChalkInstance;
18
+ };
19
+ export interface ScoreThreshold {
20
+ color: ChalkInstance;
21
+ label: string;
22
+ state: 'good' | 'attention' | 'critical';
23
+ }
24
+ export declare function getScoreThreshold(score: number): ScoreThreshold;
25
+ export declare function colorizeByScore(text: string, score: number): string;
26
+ export declare function scoreBar(score: number): string;
27
+ export declare function scoreGauge(score: number): string;
28
+ export declare const severityIcon: {
29
+ readonly critical: string;
30
+ readonly warning: string;
31
+ readonly info: string;
32
+ };
33
+ export declare const driftSeverityIcon: {
34
+ readonly critical: string;
35
+ readonly warning: string;
36
+ readonly info: string;
37
+ };
38
+ export declare function buoyMascot(score: number): string;
39
+ export interface SummaryBoxData {
40
+ score: number;
41
+ components: number;
42
+ tokens: number;
43
+ drifts: {
44
+ critical: number;
45
+ warning: number;
46
+ info: number;
47
+ total: number;
48
+ };
49
+ elapsed?: string;
50
+ }
51
+ export declare function summaryBox(data: SummaryBoxData): string;
52
+ export declare function separator(width?: number): string;
53
+ //# sourceMappingURL=visuals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visuals.d.ts","sourceRoot":"","sources":["../../src/output/visuals.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAc,EAAE,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAMlD,eAAO,MAAM,SAAS;;;;;;;CAOZ,CAAC;AASX,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;CAC1C;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAQ/D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnE;AAQD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM9C;AAMD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAMD,eAAO,MAAM,YAAY;;;;CAIf,CAAC;AAEX,eAAO,MAAM,iBAAiB;;;;CAIpB,CAAC;AAuCX,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWhD;AAQD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA2BD,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CA2DvD;AAMD,wBAAgB,SAAS,CAAC,KAAK,GAAE,MAAW,GAAG,MAAM,CAEpD"}
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Visual Feedback System
3
+ *
4
+ * Centralized visual primitives for CLI output, inspired by react-doctor.
5
+ * Provides: highlighter, score bar, score gauge, buoy mascot, summary box,
6
+ * severity icons, and separator.
7
+ *
8
+ * All functions are pure (return strings, no side effects).
9
+ */
10
+ import chalk from 'chalk';
11
+ // ============================================================================
12
+ // Highlighter (Semantic Color Palette)
13
+ // ============================================================================
14
+ export const highlight = {
15
+ error: chalk.red,
16
+ warn: chalk.yellow,
17
+ info: chalk.cyan,
18
+ success: chalk.green,
19
+ dim: chalk.dim,
20
+ brand: chalk.cyan.bold,
21
+ };
22
+ // ============================================================================
23
+ // Score Thresholds
24
+ // ============================================================================
25
+ const SCORE_GOOD_THRESHOLD = 75;
26
+ const SCORE_ATTENTION_THRESHOLD = 50;
27
+ export function getScoreThreshold(score) {
28
+ if (score >= SCORE_GOOD_THRESHOLD) {
29
+ return { color: highlight.success, label: 'Good', state: 'good' };
30
+ }
31
+ if (score >= SCORE_ATTENTION_THRESHOLD) {
32
+ return { color: highlight.warn, label: 'Needs attention', state: 'attention' };
33
+ }
34
+ return { color: highlight.error, label: 'Critical', state: 'critical' };
35
+ }
36
+ export function colorizeByScore(text, score) {
37
+ const { color } = getScoreThreshold(score);
38
+ return color(text);
39
+ }
40
+ // ============================================================================
41
+ // Score Bar
42
+ // ============================================================================
43
+ const SCORE_BAR_WIDTH = 50;
44
+ export function scoreBar(score) {
45
+ const clamped = Math.max(0, Math.min(100, Math.round(score)));
46
+ const filled = Math.round((clamped / 100) * SCORE_BAR_WIDTH);
47
+ const empty = SCORE_BAR_WIDTH - filled;
48
+ const { color } = getScoreThreshold(clamped);
49
+ return color('\u2588'.repeat(filled)) + highlight.dim('\u2591'.repeat(empty));
50
+ }
51
+ // ============================================================================
52
+ // Score Gauge
53
+ // ============================================================================
54
+ export function scoreGauge(score) {
55
+ const clamped = Math.max(0, Math.min(100, Math.round(score)));
56
+ const { color, label } = getScoreThreshold(clamped);
57
+ return `${color(String(clamped))} / 100 ${color(label)}`;
58
+ }
59
+ // ============================================================================
60
+ // Severity Icons
61
+ // ============================================================================
62
+ export const severityIcon = {
63
+ critical: highlight.error('\u2717'),
64
+ warning: highlight.warn('\u26A0'),
65
+ info: highlight.success('\u2714'),
66
+ };
67
+ export const driftSeverityIcon = {
68
+ critical: highlight.error('\u2717'),
69
+ warning: highlight.warn('\u26A0'),
70
+ info: chalk.blue('i'),
71
+ };
72
+ // ============================================================================
73
+ // Buoy ASCII Mascot
74
+ // ============================================================================
75
+ const BUOY_GOOD = [
76
+ ' . ',
77
+ ' /_\\ ',
78
+ ' / \u25E0 \\ ',
79
+ ' |\u2500\u2500\u2500\u2500\u2500| ',
80
+ ' | BUOY| ',
81
+ ' |\u2500\u2500\u2500\u2500\u2500| ',
82
+ '~~~\\___/~~~~~~~',
83
+ '~~~~~~~~~~~~~~~',
84
+ ];
85
+ const BUOY_ATTENTION = [
86
+ ' . ',
87
+ ' /_\\ ',
88
+ ' / \u2022 \\ ',
89
+ ' |\u2500\u2500\u2500\u2500\u2500| ',
90
+ ' ~~~| BUOY| ',
91
+ '~~/~~|\u2500\u2500\u2500\u2500\u2500|~~',
92
+ '~~~~\\___/~~~~~~',
93
+ '~~~~~~~~~~~~~~~',
94
+ ];
95
+ const BUOY_CRITICAL = [
96
+ '~~~~~~~~~~~~~~~',
97
+ '~~~~~.~~~~~~~~~',
98
+ '~~~~/\u2500\\~~~~~~~',
99
+ '~~~/ x \\~~~~~~',
100
+ '~~|\u2500\u2500\u2500\u2500\u2500|~~~~~',
101
+ '~~| BUOY|~~~~~',
102
+ '~~~~~~~~~~~~~~~',
103
+ '~~~~~~~~~~~~~~~',
104
+ ];
105
+ export function buoyMascot(score) {
106
+ const { color, state } = getScoreThreshold(score);
107
+ const mascots = {
108
+ good: BUOY_GOOD,
109
+ attention: BUOY_ATTENTION,
110
+ critical: BUOY_CRITICAL,
111
+ };
112
+ const lines = mascots[state] ?? BUOY_GOOD;
113
+ return lines.map(line => color(line)).join('\n');
114
+ }
115
+ // ============================================================================
116
+ // Framed Summary Box
117
+ // ============================================================================
118
+ const FRAME_WIDTH = 60;
119
+ function stripAnsi(str) {
120
+ // eslint-disable-next-line no-control-regex
121
+ return str.replace(/\u001b\[[0-9;]*m/g, '');
122
+ }
123
+ function centerPad(text, width) {
124
+ const visibleLength = stripAnsi(text).length;
125
+ const padding = Math.max(0, width - visibleLength);
126
+ const leftPad = Math.floor(padding / 2);
127
+ const rightPad = padding - leftPad;
128
+ return ' '.repeat(leftPad) + text + ' '.repeat(rightPad);
129
+ }
130
+ function frameLine(content, width = FRAME_WIDTH) {
131
+ const inner = width - 2; // account for border chars
132
+ const visibleLen = stripAnsi(content).length;
133
+ const rightPad = Math.max(0, inner - visibleLen);
134
+ return highlight.dim('\u2502') + content + ' '.repeat(rightPad) + highlight.dim('\u2502');
135
+ }
136
+ function frameEmpty(width = FRAME_WIDTH) {
137
+ const inner = width - 2;
138
+ return highlight.dim('\u2502') + ' '.repeat(inner) + highlight.dim('\u2502');
139
+ }
140
+ export function summaryBox(data) {
141
+ const inner = FRAME_WIDTH - 2;
142
+ const lines = [];
143
+ // Top border
144
+ lines.push(highlight.dim('\u250C' + '\u2500'.repeat(inner) + '\u2510'));
145
+ // Mascot
146
+ const mascotLines = buoyMascot(data.score).split('\n');
147
+ for (const ml of mascotLines) {
148
+ lines.push(frameLine(centerPad(ml, inner), FRAME_WIDTH));
149
+ }
150
+ // Blank
151
+ lines.push(frameEmpty());
152
+ // Branding
153
+ lines.push(frameLine(centerPad(highlight.brand('BUOY'), inner), FRAME_WIDTH));
154
+ lines.push(frameLine(centerPad(highlight.dim('Design Drift Detection'), inner), FRAME_WIDTH));
155
+ // Blank
156
+ lines.push(frameEmpty());
157
+ // Score gauge
158
+ lines.push(frameLine(centerPad(scoreGauge(data.score), inner), FRAME_WIDTH));
159
+ // Blank
160
+ lines.push(frameEmpty());
161
+ // Score bar
162
+ lines.push(frameLine(centerPad(scoreBar(data.score), inner), FRAME_WIDTH));
163
+ // Blank
164
+ lines.push(frameEmpty());
165
+ // Summary stats
166
+ const driftParts = [];
167
+ if (data.drifts.critical > 0) {
168
+ driftParts.push(`${driftSeverityIcon.critical} ${data.drifts.critical} errors`);
169
+ }
170
+ if (data.drifts.warning > 0) {
171
+ driftParts.push(`${driftSeverityIcon.warning} ${data.drifts.warning} warnings`);
172
+ }
173
+ let statsLine;
174
+ if (data.drifts.total > 0) {
175
+ const driftStr = driftParts.join(' ');
176
+ const suffix = data.elapsed ? ` in ${data.elapsed}` : '';
177
+ statsLine = `${driftStr} across ${data.components} components${suffix}`;
178
+ }
179
+ else {
180
+ const suffix = data.elapsed ? ` in ${data.elapsed}` : '';
181
+ statsLine = `${data.components} components ${data.tokens} tokens${suffix}`;
182
+ }
183
+ lines.push(frameLine(centerPad(highlight.dim(statsLine), inner), FRAME_WIDTH));
184
+ // Bottom border
185
+ lines.push(highlight.dim('\u2514' + '\u2500'.repeat(inner) + '\u2518'));
186
+ return lines.join('\n');
187
+ }
188
+ // ============================================================================
189
+ // Separator
190
+ // ============================================================================
191
+ export function separator(width = 40) {
192
+ return highlight.dim('\u2500'.repeat(width));
193
+ }
194
+ //# sourceMappingURL=visuals.js.map