@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.
- package/dist/commands/check.d.ts.sync-conflict-20260305-170128-6PCZ3ZU.map +1 -0
- package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.d.ts +26 -0
- package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.d.ts.map +1 -0
- package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.js +438 -0
- package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.js.map +1 -0
- package/dist/commands/dock.sync-conflict-20260309-191923-6PCZ3ZU.js +1006 -0
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.d.ts.sync-conflict-20260306-165917-6PCZ3ZU.map +1 -0
- package/dist/commands/show.js +6 -0
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/show.sync-conflict-20260305-140755-6PCZ3ZU.js +1735 -0
- package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.d.ts +11 -0
- package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.d.ts.map +1 -0
- package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.js +1735 -0
- package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.js.map +1 -0
- package/dist/config/loader.js +1 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/config/loader.js.sync-conflict-20260309-033512-6PCZ3ZU.map +1 -0
- package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.d.ts +8 -0
- package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.d.ts.map +1 -0
- package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.js +162 -0
- package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.js.map +1 -0
- package/dist/config/schema.d.ts.sync-conflict-20260309-154654-6PCZ3ZU.map +1 -0
- package/dist/config/schema.sync-conflict-20260309-135703-6PCZ3ZU.js +214 -0
- package/dist/detect/frameworks.js.sync-conflict-20260306-123756-6PCZ3ZU.map +1 -0
- package/dist/detect/monorepo-patterns.js.sync-conflict-20260309-155400-6PCZ3ZU.map +1 -0
- package/dist/hooks/index.d.ts.sync-conflict-20260306-220901-6PCZ3ZU.map +1 -0
- package/dist/output/formatters.js.sync-conflict-20260306-134702-6PCZ3ZU.map +1 -0
- package/dist/output/formatters.sync-conflict-20260306-180804-6PCZ3ZU.js +867 -0
- package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.d.ts +29 -0
- package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.d.ts.map +1 -0
- package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.js +867 -0
- package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.js.map +1 -0
- package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.d.ts +29 -0
- package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.d.ts.map +1 -0
- package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.js +867 -0
- package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.js.map +1 -0
- package/dist/output/index.sync-conflict-20260309-222859-6PCZ3ZU.js +5 -0
- package/dist/output/reporters.d.sync-conflict-20260309-193820-6PCZ3ZU.ts +38 -0
- package/dist/output/reporters.d.ts.sync-conflict-20260306-193811-6PCZ3ZU.map +1 -0
- package/dist/output/reporters.sync-conflict-20260309-030558-6PCZ3ZU.js +182 -0
- package/dist/output/reports.d.ts.sync-conflict-20260307-172149-6PCZ3ZU.map +1 -0
- package/dist/output/reports.js.sync-conflict-20260305-161643-6PCZ3ZU.map +1 -0
- package/dist/output/reports.sync-conflict-20260305-211951-6PCZ3ZU.js +393 -0
- package/dist/output/visuals.d.ts +53 -0
- package/dist/output/visuals.d.ts.map +1 -0
- package/dist/output/visuals.js +194 -0
- package/dist/output/visuals.js.map +1 -0
- package/dist/services/drift-analysis.d.sync-conflict-20260306-151016-6PCZ3ZU.ts +194 -0
- package/dist/services/drift-analysis.d.ts.sync-conflict-20260307-175904-6PCZ3ZU.map +1 -0
- package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.d.ts +194 -0
- package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.d.ts.map +1 -0
- package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.js +1022 -0
- package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.js.map +1 -0
- package/dist/services/skill-export.d.ts.sync-conflict-20260309-171021-6PCZ3ZU.map +1 -0
- package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.d.ts +109 -0
- package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.d.ts.map +1 -0
- package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.js +737 -0
- package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.js.map +1 -0
- package/package.json +14 -14
- 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
|