@buoy-design/cli 0.3.33 → 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/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +235 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/baseline.d.ts +47 -0
- package/dist/commands/baseline.d.ts.map +1 -0
- package/dist/commands/baseline.js +327 -0
- package/dist/commands/baseline.js.map +1 -0
- package/dist/commands/begin.d.ts +9 -0
- package/dist/commands/begin.d.ts.map +1 -0
- package/dist/commands/begin.js +827 -0
- package/dist/commands/begin.js.map +1 -0
- package/dist/commands/build.d.ts +8 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +26 -0
- package/dist/commands/build.js.map +1 -0
- 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/commands.d.ts +8 -0
- package/dist/commands/commands.d.ts.map +1 -0
- package/dist/commands/commands.js +148 -0
- package/dist/commands/commands.js.map +1 -0
- package/dist/commands/components-query.d.ts +13 -0
- package/dist/commands/components-query.d.ts.map +1 -0
- package/dist/commands/components-query.js +263 -0
- package/dist/commands/components-query.js.map +1 -0
- package/dist/commands/components.d.ts +8 -0
- package/dist/commands/components.d.ts.map +1 -0
- package/dist/commands/components.js +14 -0
- package/dist/commands/components.js.map +1 -0
- package/dist/commands/dock.sync-conflict-20260309-191923-6PCZ3ZU.js +1006 -0
- package/dist/commands/history.d.ts +3 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +352 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/plugins.d.ts +3 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +63 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/show.d.ts.sync-conflict-20260306-165917-6PCZ3ZU.map +1 -0
- 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,827 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buoy begin - Interactive wizard for new users.
|
|
3
|
+
*
|
|
4
|
+
* Explains what Buoy does, scans the project, and guides users through setup
|
|
5
|
+
* with clear, jargon-free language.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { glob } from 'glob';
|
|
12
|
+
import { loadConfig, getConfigPath } from '../config/loader.js';
|
|
13
|
+
import { buildAutoConfig } from '../config/auto-detect.js';
|
|
14
|
+
import { spinner, error as errorLog } from '../output/reporters.js';
|
|
15
|
+
import { ScanOrchestrator } from '../scan/orchestrator.js';
|
|
16
|
+
import { ProjectDetector } from '../detect/project-detector.js';
|
|
17
|
+
import { showMenu, } from '../wizard/menu.js';
|
|
18
|
+
import { reviewIssues } from '../wizard/issue-reviewer.js';
|
|
19
|
+
import { setupCI } from '../wizard/ci-generator.js';
|
|
20
|
+
import { setupAIGuardrails } from '../wizard/ai-guardrails-generator.js';
|
|
21
|
+
import { setupHooks as installHooks, generateStandaloneHook, detectHookSystem, } from '../hooks/index.js';
|
|
22
|
+
/**
|
|
23
|
+
* Detect if project has design tokens.
|
|
24
|
+
*/
|
|
25
|
+
async function detectTokens(cwd) {
|
|
26
|
+
const patterns = [
|
|
27
|
+
'**/design-tokens.json',
|
|
28
|
+
'**/tokens.json',
|
|
29
|
+
'**/.tokens.json',
|
|
30
|
+
'**/tokens.css',
|
|
31
|
+
'**/variables.css',
|
|
32
|
+
'**/design-tokens.css',
|
|
33
|
+
'**/_tokens.scss',
|
|
34
|
+
'**/_variables.scss',
|
|
35
|
+
'**/theme.json',
|
|
36
|
+
'**/style-dictionary/**/*.json',
|
|
37
|
+
];
|
|
38
|
+
const files = [];
|
|
39
|
+
for (const pattern of patterns) {
|
|
40
|
+
const matches = await glob(pattern, {
|
|
41
|
+
cwd,
|
|
42
|
+
nodir: true,
|
|
43
|
+
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**'],
|
|
44
|
+
});
|
|
45
|
+
files.push(...matches);
|
|
46
|
+
}
|
|
47
|
+
// Also check if tailwind.config exists (counts as having tokens)
|
|
48
|
+
const tailwindConfigs = await glob('tailwind.config.{js,ts,mjs}', { cwd });
|
|
49
|
+
if (tailwindConfigs.length > 0) {
|
|
50
|
+
files.push(...tailwindConfigs);
|
|
51
|
+
}
|
|
52
|
+
return { hasTokens: files.length > 0, files: [...new Set(files)] };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Detect if pre-commit hooks are set up.
|
|
56
|
+
*/
|
|
57
|
+
function detectHooks(cwd) {
|
|
58
|
+
const gitHookPath = join(cwd, '.git', 'hooks', 'pre-commit');
|
|
59
|
+
if (existsSync(gitHookPath)) {
|
|
60
|
+
try {
|
|
61
|
+
const content = readFileSync(gitHookPath, 'utf-8');
|
|
62
|
+
if (content.includes('buoy')) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Ignore read errors
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Check for husky
|
|
71
|
+
const huskyPath = join(cwd, '.husky', 'pre-commit');
|
|
72
|
+
if (existsSync(huskyPath)) {
|
|
73
|
+
try {
|
|
74
|
+
const content = readFileSync(huskyPath, 'utf-8');
|
|
75
|
+
if (content.includes('buoy')) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Ignore read errors
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Check for lint-staged config with buoy
|
|
84
|
+
const packageJsonPath = join(cwd, 'package.json');
|
|
85
|
+
if (existsSync(packageJsonPath)) {
|
|
86
|
+
try {
|
|
87
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
88
|
+
if (pkg['lint-staged'] && JSON.stringify(pkg['lint-staged']).includes('buoy')) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Ignore read errors
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Detect if baseline is set up.
|
|
100
|
+
*/
|
|
101
|
+
function detectBaseline(cwd) {
|
|
102
|
+
return existsSync(join(cwd, '.buoy', 'baseline.json')) ||
|
|
103
|
+
existsSync(join(cwd, 'buoy-baseline.json'));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Detect if AI guardrails are set up.
|
|
107
|
+
*/
|
|
108
|
+
function detectAISetup(cwd) {
|
|
109
|
+
// Check for skill file
|
|
110
|
+
if (existsSync(join(cwd, '.claude', 'skills', 'design-system', 'SKILL.md'))) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
// Check for design system section in CLAUDE.md
|
|
114
|
+
const claudeMdPath = join(cwd, 'CLAUDE.md');
|
|
115
|
+
if (existsSync(claudeMdPath)) {
|
|
116
|
+
try {
|
|
117
|
+
const content = readFileSync(claudeMdPath, 'utf-8');
|
|
118
|
+
if (content.includes('Design System') || content.includes('design-system') || content.includes('buoy')) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Ignore read errors
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Run all setup steps automatically without prompts.
|
|
130
|
+
* This is the "just make it work" mode for users who want quick setup.
|
|
131
|
+
*/
|
|
132
|
+
async function runQuickOnboard(cwd) {
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(chalk.cyan.bold('🛟 Buoy Quick Setup'));
|
|
135
|
+
console.log('');
|
|
136
|
+
// Step 1: Detect project state
|
|
137
|
+
const spin = spinner('Analyzing project...');
|
|
138
|
+
const tokenResult = await detectTokens(cwd);
|
|
139
|
+
const hasAISetup = detectAISetup(cwd);
|
|
140
|
+
const existingConfig = getConfigPath();
|
|
141
|
+
spin.stop();
|
|
142
|
+
console.log(` ${chalk.green('✓')} Project analyzed`);
|
|
143
|
+
// Step 2: Create tokens if needed
|
|
144
|
+
if (!tokenResult.hasTokens) {
|
|
145
|
+
console.log('');
|
|
146
|
+
console.log(chalk.cyan(' Creating design tokens...'));
|
|
147
|
+
const { spawn } = await import('child_process');
|
|
148
|
+
await new Promise((resolve) => {
|
|
149
|
+
const child = spawn('npx', ['ahoybuoy', 'anchor', '-y'], {
|
|
150
|
+
cwd,
|
|
151
|
+
stdio: 'inherit',
|
|
152
|
+
});
|
|
153
|
+
child.on('close', () => resolve());
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
console.log(` ${chalk.green('✓')} Design tokens found`);
|
|
158
|
+
}
|
|
159
|
+
// Step 3: Load or create config
|
|
160
|
+
let config;
|
|
161
|
+
if (existingConfig) {
|
|
162
|
+
const result = await loadConfig();
|
|
163
|
+
config = result.config;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const autoResult = await buildAutoConfig(cwd);
|
|
167
|
+
config = autoResult.config;
|
|
168
|
+
}
|
|
169
|
+
// Step 4: Run scan
|
|
170
|
+
const scanSpin = spinner('Scanning for drift...');
|
|
171
|
+
try {
|
|
172
|
+
const orchestrator = new ScanOrchestrator(config);
|
|
173
|
+
const { components } = await orchestrator.scanComponents({});
|
|
174
|
+
const detector = new ProjectDetector(cwd);
|
|
175
|
+
const projectInfo = await detector.detect();
|
|
176
|
+
const { SemanticDiffEngine } = await import('@buoy-design/core/analysis');
|
|
177
|
+
const engine = new SemanticDiffEngine();
|
|
178
|
+
const diffResult = engine.analyzeComponents(components, {
|
|
179
|
+
checkDeprecated: true,
|
|
180
|
+
checkNaming: true,
|
|
181
|
+
checkDocumentation: true,
|
|
182
|
+
});
|
|
183
|
+
scanSpin.stop();
|
|
184
|
+
console.log(` ${chalk.green('✓')} Scanned ${components.length} components`);
|
|
185
|
+
const critical = diffResult.drifts.filter(d => d.severity === 'critical').length;
|
|
186
|
+
const warning = diffResult.drifts.filter(d => d.severity === 'warning').length;
|
|
187
|
+
if (diffResult.drifts.length > 0) {
|
|
188
|
+
console.log(` ${chalk.yellow('!')} Found ${diffResult.drifts.length} drift issue${diffResult.drifts.length === 1 ? '' : 's'}`);
|
|
189
|
+
if (critical > 0)
|
|
190
|
+
console.log(` ${chalk.red('•')} ${critical} critical`);
|
|
191
|
+
if (warning > 0)
|
|
192
|
+
console.log(` ${chalk.yellow('•')} ${warning} warnings`);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
console.log(` ${chalk.green('✓')} No drift detected`);
|
|
196
|
+
}
|
|
197
|
+
// Log frameworks detected
|
|
198
|
+
if (projectInfo.frameworks.length > 0) {
|
|
199
|
+
console.log(` ${chalk.green('✓')} Detected: ${projectInfo.frameworks.map(f => f.name).join(', ')}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
scanSpin.stop();
|
|
204
|
+
console.log(` ${chalk.yellow('!')} Scan skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
205
|
+
}
|
|
206
|
+
// Step 5: Set up AI guardrails if needed
|
|
207
|
+
if (!hasAISetup) {
|
|
208
|
+
console.log('');
|
|
209
|
+
console.log(chalk.cyan(' Setting up AI guardrails...'));
|
|
210
|
+
const result = await setupAIGuardrails(cwd, config);
|
|
211
|
+
if (result.skillExported) {
|
|
212
|
+
console.log(` ${chalk.green('✓')} AI skill exported`);
|
|
213
|
+
}
|
|
214
|
+
if (result.contextGenerated) {
|
|
215
|
+
console.log(` ${chalk.green('✓')} CLAUDE.md updated`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.log(` ${chalk.green('✓')} AI guardrails already configured`);
|
|
220
|
+
}
|
|
221
|
+
// Summary
|
|
222
|
+
console.log('');
|
|
223
|
+
console.log(chalk.green.bold(' ━'.repeat(24)));
|
|
224
|
+
console.log('');
|
|
225
|
+
console.log(chalk.green.bold(' ✓ Setup complete!'));
|
|
226
|
+
console.log('');
|
|
227
|
+
console.log(chalk.green.bold(' ━'.repeat(24)));
|
|
228
|
+
console.log('');
|
|
229
|
+
console.log(chalk.dim(' Next steps:'));
|
|
230
|
+
console.log(` ${chalk.cyan('buoy show all')} Check for drift`);
|
|
231
|
+
console.log(` ${chalk.cyan('buoy drift check')} Pre-commit validation`);
|
|
232
|
+
console.log(` ${chalk.cyan('buoy begin')} Interactive setup`);
|
|
233
|
+
console.log('');
|
|
234
|
+
}
|
|
235
|
+
export function createBeginCommand() {
|
|
236
|
+
return new Command('begin')
|
|
237
|
+
.description('Interactive wizard to get started with Buoy')
|
|
238
|
+
.option('-y, --yes', 'Run all setup steps automatically without prompts')
|
|
239
|
+
.action(async (options) => {
|
|
240
|
+
const cwd = process.cwd();
|
|
241
|
+
// Quick onboard mode - run all steps automatically
|
|
242
|
+
if (options.yes) {
|
|
243
|
+
await runQuickOnboard(cwd);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Check if we're in an interactive terminal
|
|
247
|
+
if (!process.stdin.isTTY) {
|
|
248
|
+
// Run scan and output AI-friendly results so the AI can walk the user through it
|
|
249
|
+
await runAIGuidedWizard(cwd);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
// Welcome
|
|
253
|
+
console.log('');
|
|
254
|
+
console.log(chalk.cyan.bold('🛟 Welcome to Buoy'));
|
|
255
|
+
console.log('');
|
|
256
|
+
// Detect project state
|
|
257
|
+
const spin = spinner('Analyzing your project...');
|
|
258
|
+
const tokenResult = await detectTokens(cwd);
|
|
259
|
+
const hasAISetup = detectAISetup(cwd);
|
|
260
|
+
const hasHooks = detectHooks(cwd);
|
|
261
|
+
const hasBaseline = detectBaseline(cwd);
|
|
262
|
+
const hasCISetup = existsSync(join(cwd, '.github', 'workflows', 'buoy.yml')) ||
|
|
263
|
+
existsSync(join(cwd, '.gitlab-ci.yml'));
|
|
264
|
+
const hasConfig = !!getConfigPath();
|
|
265
|
+
// Initialize state
|
|
266
|
+
const state = {
|
|
267
|
+
hasTokens: tokenResult.hasTokens,
|
|
268
|
+
tokenFiles: tokenResult.files,
|
|
269
|
+
hasAISetup,
|
|
270
|
+
hasHooks,
|
|
271
|
+
hasCISetup,
|
|
272
|
+
hasConfig,
|
|
273
|
+
hasBaseline,
|
|
274
|
+
hasScanned: false,
|
|
275
|
+
components: [],
|
|
276
|
+
tokens: [],
|
|
277
|
+
drifts: [],
|
|
278
|
+
issuesReviewed: false,
|
|
279
|
+
};
|
|
280
|
+
spin.stop();
|
|
281
|
+
// Show what we found
|
|
282
|
+
showProjectStatus(state);
|
|
283
|
+
// Main menu loop
|
|
284
|
+
await menuLoop(cwd, state);
|
|
285
|
+
// Exit message
|
|
286
|
+
showExitMessage();
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Show project status based on detection.
|
|
291
|
+
*/
|
|
292
|
+
function showProjectStatus(state) {
|
|
293
|
+
console.log(chalk.dim(' What we found:'));
|
|
294
|
+
console.log('');
|
|
295
|
+
// Tokens
|
|
296
|
+
if (state.hasTokens) {
|
|
297
|
+
console.log(` ${chalk.green('✓')} Design tokens found`);
|
|
298
|
+
if (state.tokenFiles.length <= 3) {
|
|
299
|
+
state.tokenFiles.forEach(f => console.log(chalk.dim(` ${f}`)));
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
console.log(chalk.dim(` ${state.tokenFiles.length} token files`));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
console.log(` ${chalk.yellow('○')} No design tokens found`);
|
|
307
|
+
}
|
|
308
|
+
// AI Setup
|
|
309
|
+
if (state.hasAISetup) {
|
|
310
|
+
console.log(` ${chalk.green('✓')} AI guardrails configured`);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
console.log(` ${chalk.yellow('○')} AI guardrails not set up`);
|
|
314
|
+
}
|
|
315
|
+
// Pre-commit hooks
|
|
316
|
+
if (state.hasHooks) {
|
|
317
|
+
console.log(` ${chalk.green('✓')} Pre-commit hooks active`);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
console.log(` ${chalk.dim('○')} Pre-commit hooks not set up`);
|
|
321
|
+
}
|
|
322
|
+
// CI Setup
|
|
323
|
+
if (state.hasCISetup) {
|
|
324
|
+
console.log(` ${chalk.green('✓')} CI/CD integration active`);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
console.log(` ${chalk.dim('○')} CI/CD not configured`);
|
|
328
|
+
}
|
|
329
|
+
// Baseline
|
|
330
|
+
if (state.hasBaseline) {
|
|
331
|
+
console.log(` ${chalk.green('✓')} Baseline established`);
|
|
332
|
+
}
|
|
333
|
+
console.log('');
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Scan the project and show results in a user-friendly way.
|
|
337
|
+
*/
|
|
338
|
+
async function runScan(cwd) {
|
|
339
|
+
const spin = spinner('Looking at your project...');
|
|
340
|
+
try {
|
|
341
|
+
// Load or auto-detect config
|
|
342
|
+
const existingConfig = getConfigPath();
|
|
343
|
+
let config;
|
|
344
|
+
let autoResult;
|
|
345
|
+
if (existingConfig) {
|
|
346
|
+
const result = await loadConfig();
|
|
347
|
+
config = result.config;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
autoResult = await buildAutoConfig(cwd);
|
|
351
|
+
config = autoResult.config;
|
|
352
|
+
}
|
|
353
|
+
// Run scan
|
|
354
|
+
const orchestrator = new ScanOrchestrator(config);
|
|
355
|
+
const { components } = await orchestrator.scanComponents({
|
|
356
|
+
onProgress: (msg) => {
|
|
357
|
+
spin.text = msg;
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
// Detect frameworks
|
|
361
|
+
spin.text = 'Detecting frameworks...';
|
|
362
|
+
const detector = new ProjectDetector(cwd);
|
|
363
|
+
const projectInfo = await detector.detect();
|
|
364
|
+
// Run drift analysis
|
|
365
|
+
spin.text = 'Analyzing for drift...';
|
|
366
|
+
const { SemanticDiffEngine } = await import('@buoy-design/core/analysis');
|
|
367
|
+
const engine = new SemanticDiffEngine();
|
|
368
|
+
const diffResult = engine.analyzeComponents(components, {
|
|
369
|
+
checkDeprecated: true,
|
|
370
|
+
checkNaming: true,
|
|
371
|
+
checkDocumentation: true,
|
|
372
|
+
});
|
|
373
|
+
const drifts = [...diffResult.drifts];
|
|
374
|
+
// Check framework sprawl
|
|
375
|
+
const sprawlSignal = engine.checkFrameworkSprawl(projectInfo.frameworks.map(f => ({ name: f.name, version: f.version })));
|
|
376
|
+
if (sprawlSignal) {
|
|
377
|
+
drifts.push(sprawlSignal);
|
|
378
|
+
}
|
|
379
|
+
spin.stop();
|
|
380
|
+
return { components, drifts, config, projectInfo, autoResult };
|
|
381
|
+
}
|
|
382
|
+
catch (err) {
|
|
383
|
+
spin.stop();
|
|
384
|
+
throw err;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Show scan results in a friendly way with clear transparency.
|
|
389
|
+
*/
|
|
390
|
+
function showScanResults(components, drifts, projectInfo) {
|
|
391
|
+
console.log('');
|
|
392
|
+
console.log(chalk.bold(' Scan Results'));
|
|
393
|
+
console.log('');
|
|
394
|
+
// Framework detection
|
|
395
|
+
if (projectInfo.frameworks.length > 0) {
|
|
396
|
+
const frameworkNames = projectInfo.frameworks.map(f => f.name).join(', ');
|
|
397
|
+
console.log(` ${chalk.green('✓')} Detected: ${chalk.cyan(frameworkNames)}`);
|
|
398
|
+
}
|
|
399
|
+
// Components - be transparent about what was scanned
|
|
400
|
+
if (components.length > 0) {
|
|
401
|
+
console.log(` ${chalk.green('✓')} Scanned ${components.length} component${components.length === 1 ? '' : 's'}`);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
console.log(` ${chalk.yellow('○')} No components found to scan`);
|
|
405
|
+
console.log(chalk.dim(' Tip: Run buoy show all --verbose to see what paths are being searched'));
|
|
406
|
+
}
|
|
407
|
+
// Drift summary - clearer language about severity
|
|
408
|
+
const critical = drifts.filter(d => d.severity === 'critical').length;
|
|
409
|
+
const warning = drifts.filter(d => d.severity === 'warning').length;
|
|
410
|
+
const total = drifts.length;
|
|
411
|
+
console.log('');
|
|
412
|
+
if (components.length === 0) {
|
|
413
|
+
console.log(chalk.dim(' (No components to analyze for drift)'));
|
|
414
|
+
}
|
|
415
|
+
else if (total === 0) {
|
|
416
|
+
console.log(` ${chalk.green('✓')} No drift detected — your code follows consistent patterns!`);
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
console.log(` ${chalk.yellow('!')} Found ${total} inconsistenc${total === 1 ? 'y' : 'ies'}:`);
|
|
420
|
+
if (critical > 0) {
|
|
421
|
+
console.log(` ${chalk.red('•')} ${critical} ${chalk.red('should fix')} — hardcoded values that bypass design tokens`);
|
|
422
|
+
}
|
|
423
|
+
if (warning > 0) {
|
|
424
|
+
console.log(` ${chalk.yellow('•')} ${warning} ${chalk.yellow('could improve')} — naming or pattern suggestions`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
console.log('');
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Setup pre-commit hooks with celebration!
|
|
431
|
+
* This is the "lock in your gains" moment.
|
|
432
|
+
*/
|
|
433
|
+
async function setupHooks(cwd) {
|
|
434
|
+
console.log('');
|
|
435
|
+
console.log(chalk.cyan.bold(' 🔒 Locking in your design system...'));
|
|
436
|
+
console.log('');
|
|
437
|
+
const hookSystem = detectHookSystem(cwd);
|
|
438
|
+
let success = false;
|
|
439
|
+
if (hookSystem) {
|
|
440
|
+
console.log(chalk.dim(` Detected: ${hookSystem}`));
|
|
441
|
+
const result = installHooks(cwd);
|
|
442
|
+
success = result.success;
|
|
443
|
+
if (result.success) {
|
|
444
|
+
console.log(` ${chalk.green('✓')} ${result.message}`);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
console.log(` ${chalk.yellow('!')} ${result.message}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
// Generate standalone hook
|
|
452
|
+
const result = generateStandaloneHook(cwd);
|
|
453
|
+
success = result.success;
|
|
454
|
+
if (result.success) {
|
|
455
|
+
console.log(` ${chalk.green('✓')} ${result.message}`);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
console.log(` ${chalk.yellow('!')} ${result.message}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (success) {
|
|
462
|
+
// Celebration!
|
|
463
|
+
console.log('');
|
|
464
|
+
console.log(chalk.green.bold(' ━'.repeat(24)));
|
|
465
|
+
console.log('');
|
|
466
|
+
console.log(chalk.green.bold(' 🎉 Your design system is protected!'));
|
|
467
|
+
console.log('');
|
|
468
|
+
console.log(chalk.green.bold(' ━'.repeat(24)));
|
|
469
|
+
console.log('');
|
|
470
|
+
console.log(chalk.dim(' Every commit will now be checked for drift.'));
|
|
471
|
+
console.log(chalk.dim(' Use --no-verify to bypass if needed.'));
|
|
472
|
+
console.log('');
|
|
473
|
+
}
|
|
474
|
+
return { success };
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Main menu loop - smart recommendations based on project state.
|
|
478
|
+
*/
|
|
479
|
+
async function menuLoop(cwd, state) {
|
|
480
|
+
let config;
|
|
481
|
+
while (true) {
|
|
482
|
+
const action = await showMainMenu(state);
|
|
483
|
+
switch (action) {
|
|
484
|
+
case 'scan': {
|
|
485
|
+
// Run scan to discover components and tokens
|
|
486
|
+
console.log('');
|
|
487
|
+
console.log(chalk.cyan(' Scanning your codebase...'));
|
|
488
|
+
console.log('');
|
|
489
|
+
try {
|
|
490
|
+
const scanResult = await runScan(cwd);
|
|
491
|
+
state.hasScanned = true;
|
|
492
|
+
state.components = scanResult.components;
|
|
493
|
+
state.drifts = scanResult.drifts;
|
|
494
|
+
config = scanResult.config;
|
|
495
|
+
console.log('');
|
|
496
|
+
console.log(chalk.green(' ✓ Scan complete!'));
|
|
497
|
+
console.log('');
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
501
|
+
errorLog(`Scan failed: ${message}`);
|
|
502
|
+
}
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
case 'dock-agents': {
|
|
506
|
+
// Run dock agents to set up AI integration
|
|
507
|
+
console.log('');
|
|
508
|
+
console.log(chalk.cyan(' Setting up AI integration...'));
|
|
509
|
+
console.log('');
|
|
510
|
+
// Load config if needed
|
|
511
|
+
if (!config) {
|
|
512
|
+
const configPath = getConfigPath();
|
|
513
|
+
if (configPath) {
|
|
514
|
+
const result = await loadConfig();
|
|
515
|
+
config = result.config;
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
const autoResult = await buildAutoConfig(cwd);
|
|
519
|
+
config = autoResult.config;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const result = await setupAIGuardrails(cwd, config);
|
|
523
|
+
if (result.skillExported || result.contextGenerated) {
|
|
524
|
+
state.hasAISetup = true;
|
|
525
|
+
}
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
case 'check-drift': {
|
|
529
|
+
// Run scan and show drift
|
|
530
|
+
try {
|
|
531
|
+
const scanResult = await runScan(cwd);
|
|
532
|
+
state.hasScanned = true;
|
|
533
|
+
state.components = scanResult.components;
|
|
534
|
+
state.drifts = scanResult.drifts;
|
|
535
|
+
config = scanResult.config;
|
|
536
|
+
showScanResults(scanResult.components, scanResult.drifts, scanResult.projectInfo);
|
|
537
|
+
}
|
|
538
|
+
catch (err) {
|
|
539
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
540
|
+
errorLog(`Scan failed: ${message}`);
|
|
541
|
+
}
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
case 'review-issues': {
|
|
545
|
+
if (state.drifts.length === 0) {
|
|
546
|
+
console.log(chalk.green('\n No issues to review!\n'));
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
const result = await reviewIssues(state.drifts);
|
|
550
|
+
if (result.completed) {
|
|
551
|
+
state.issuesReviewed = true;
|
|
552
|
+
}
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
case 'setup-hooks': {
|
|
556
|
+
const result = await setupHooks(cwd);
|
|
557
|
+
if (result.success) {
|
|
558
|
+
state.hasHooks = true;
|
|
559
|
+
}
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
case 'setup-ci': {
|
|
563
|
+
await setupCI(cwd);
|
|
564
|
+
state.hasCISetup = true;
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
case 'learn-more': {
|
|
568
|
+
showLearnMore();
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
case 'exit':
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Show the main menu with smart recommendations based on project state.
|
|
578
|
+
*/
|
|
579
|
+
async function showMainMenu(state) {
|
|
580
|
+
const options = [];
|
|
581
|
+
// Smart recommendations based on state
|
|
582
|
+
if (!state.hasTokens) {
|
|
583
|
+
// No tokens - primary action is scan
|
|
584
|
+
options.push({
|
|
585
|
+
label: '🔍 Scan your codebase',
|
|
586
|
+
value: 'scan',
|
|
587
|
+
description: 'Find components and design values in your code',
|
|
588
|
+
});
|
|
589
|
+
options.push({
|
|
590
|
+
label: 'Check for drift anyway',
|
|
591
|
+
value: 'check-drift',
|
|
592
|
+
description: 'Find hardcoded values without a token file',
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
else if (!state.hasAISetup) {
|
|
596
|
+
// Has tokens but no AI setup - primary action is dock agents
|
|
597
|
+
options.push({
|
|
598
|
+
label: '🛟 Set up AI integration',
|
|
599
|
+
value: 'dock-agents',
|
|
600
|
+
description: 'Help AI tools follow your tokens and patterns',
|
|
601
|
+
});
|
|
602
|
+
options.push({
|
|
603
|
+
label: 'Check for drift',
|
|
604
|
+
value: 'check-drift',
|
|
605
|
+
description: 'Find code that diverges from your tokens',
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
// Has tokens + AI setup - primary action is drift check
|
|
610
|
+
options.push({
|
|
611
|
+
label: 'Check for drift',
|
|
612
|
+
value: 'check-drift',
|
|
613
|
+
description: 'Find code that diverges from your design system',
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
// Show issues if we have them
|
|
617
|
+
if (state.hasScanned && state.drifts.length > 0 && !state.issuesReviewed) {
|
|
618
|
+
options.push({
|
|
619
|
+
label: `Review ${state.drifts.length} issue${state.drifts.length === 1 ? '' : 's'}`,
|
|
620
|
+
value: 'review-issues',
|
|
621
|
+
description: 'See details and how to fix them',
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
// Hook setup - only show after baseline is set OR no drift OR issues reviewed
|
|
625
|
+
// This is the "lock in your gains" moment
|
|
626
|
+
const isCleanState = state.hasBaseline ||
|
|
627
|
+
(state.hasScanned && state.drifts.length === 0) ||
|
|
628
|
+
state.issuesReviewed;
|
|
629
|
+
if (!state.hasHooks && state.hasTokens && isCleanState) {
|
|
630
|
+
options.push({
|
|
631
|
+
label: '🔒 Lock it in with pre-commit hooks',
|
|
632
|
+
value: 'setup-hooks',
|
|
633
|
+
description: 'Catch new drift before it\'s committed',
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
// CI setup - show after hooks are set up, or if they explicitly want it
|
|
637
|
+
if (!state.hasCISetup && state.hasTokens && (state.hasHooks || isCleanState)) {
|
|
638
|
+
options.push({
|
|
639
|
+
label: '🚦 Add to CI/CD',
|
|
640
|
+
value: 'setup-ci',
|
|
641
|
+
description: 'Block PRs that introduce drift',
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
// Learn more always available
|
|
645
|
+
options.push({
|
|
646
|
+
label: 'Learn more',
|
|
647
|
+
value: 'learn-more',
|
|
648
|
+
description: 'What is design drift?',
|
|
649
|
+
});
|
|
650
|
+
options.push({
|
|
651
|
+
label: 'Exit',
|
|
652
|
+
value: 'exit',
|
|
653
|
+
});
|
|
654
|
+
return showMenu('What would you like to do?', options);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Show learn more section - explains drift in plain language.
|
|
658
|
+
*/
|
|
659
|
+
function showLearnMore() {
|
|
660
|
+
console.log('');
|
|
661
|
+
console.log(chalk.bold(' What is Design Drift?'));
|
|
662
|
+
console.log('');
|
|
663
|
+
console.log(chalk.dim(' When you have a design system, you want code to follow it.'));
|
|
664
|
+
console.log(chalk.dim(' But over time, inconsistencies creep in:'));
|
|
665
|
+
console.log('');
|
|
666
|
+
console.log(` ${chalk.red('•')} Someone writes ${chalk.yellow('color: #3b82f6')} instead of using your blue token`);
|
|
667
|
+
console.log(` ${chalk.red('•')} A developer uses ${chalk.yellow('padding: 17px')} instead of your 4px spacing scale`);
|
|
668
|
+
console.log(` ${chalk.red('•')} AI tools generate code that ignores your design system`);
|
|
669
|
+
console.log('');
|
|
670
|
+
console.log(chalk.dim(' This is "design drift" — and it adds up fast.'));
|
|
671
|
+
console.log('');
|
|
672
|
+
console.log(chalk.bold(' How Buoy Helps'));
|
|
673
|
+
console.log('');
|
|
674
|
+
console.log(` ${chalk.green('1.')} ${chalk.cyan('Scan')} your code to find drift`);
|
|
675
|
+
console.log(` ${chalk.green('2.')} ${chalk.cyan('Review')} issues and see suggested fixes`);
|
|
676
|
+
console.log(` ${chalk.green('3.')} ${chalk.cyan('Prevent')} future drift with CI checks and AI guardrails`);
|
|
677
|
+
console.log('');
|
|
678
|
+
console.log(chalk.dim(` Docs: ${chalk.cyan('https://buoy.design/docs')}`));
|
|
679
|
+
console.log('');
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Show exit message.
|
|
683
|
+
*/
|
|
684
|
+
function showExitMessage() {
|
|
685
|
+
console.log('');
|
|
686
|
+
console.log(chalk.green(' ✓ You\'re all set!'));
|
|
687
|
+
console.log('');
|
|
688
|
+
console.log(chalk.dim(' Quick commands:'));
|
|
689
|
+
console.log(` ${chalk.cyan('buoy show drift')} See all drift issues`);
|
|
690
|
+
console.log(` ${chalk.cyan('buoy show health')} Quick health overview`);
|
|
691
|
+
console.log(` ${chalk.cyan('buoy drift check')} Pre-commit validation`);
|
|
692
|
+
console.log('');
|
|
693
|
+
console.log(chalk.dim(` Run ${chalk.cyan('buoy begin')} anytime to return here.`));
|
|
694
|
+
console.log('');
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Run the wizard in AI-guided mode - scans project and outputs everything
|
|
698
|
+
* the AI needs to walk the user through setup conversationally.
|
|
699
|
+
*/
|
|
700
|
+
async function runAIGuidedWizard(cwd) {
|
|
701
|
+
console.log(`
|
|
702
|
+
🛟 Buoy - Design Drift Detection
|
|
703
|
+
|
|
704
|
+
Analyzing your project...
|
|
705
|
+
`);
|
|
706
|
+
try {
|
|
707
|
+
// Detect project state
|
|
708
|
+
const tokenResult = await detectTokens(cwd);
|
|
709
|
+
const hasAISetup = detectAISetup(cwd);
|
|
710
|
+
const hasCISetup = existsSync(join(cwd, '.github', 'workflows', 'buoy.yml')) ||
|
|
711
|
+
existsSync(join(cwd, '.gitlab-ci.yml'));
|
|
712
|
+
const existingConfig = getConfigPath();
|
|
713
|
+
// Load config
|
|
714
|
+
let config;
|
|
715
|
+
if (existingConfig) {
|
|
716
|
+
const result = await loadConfig();
|
|
717
|
+
config = result.config;
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
const autoResult = await buildAutoConfig(cwd);
|
|
721
|
+
config = autoResult.config;
|
|
722
|
+
}
|
|
723
|
+
// Run scan
|
|
724
|
+
const orchestrator = new ScanOrchestrator(config);
|
|
725
|
+
const { components } = await orchestrator.scanComponents({});
|
|
726
|
+
// Detect frameworks
|
|
727
|
+
const detector = new ProjectDetector(cwd);
|
|
728
|
+
const projectInfo = await detector.detect();
|
|
729
|
+
// Run drift analysis
|
|
730
|
+
const { SemanticDiffEngine } = await import('@buoy-design/core/analysis');
|
|
731
|
+
const engine = new SemanticDiffEngine();
|
|
732
|
+
const diffResult = engine.analyzeComponents(components, {
|
|
733
|
+
checkDeprecated: true,
|
|
734
|
+
checkNaming: true,
|
|
735
|
+
checkDocumentation: true,
|
|
736
|
+
});
|
|
737
|
+
const drifts = [...diffResult.drifts];
|
|
738
|
+
// Output project state
|
|
739
|
+
console.log('─'.repeat(50));
|
|
740
|
+
console.log('PROJECT STATE');
|
|
741
|
+
console.log('─'.repeat(50));
|
|
742
|
+
console.log('');
|
|
743
|
+
// Framework
|
|
744
|
+
if (projectInfo.frameworks.length > 0) {
|
|
745
|
+
console.log(`Framework: ${projectInfo.frameworks.map(f => f.name).join(', ')}`);
|
|
746
|
+
}
|
|
747
|
+
console.log(`Components: ${components.length} found`);
|
|
748
|
+
// Tokens
|
|
749
|
+
if (tokenResult.hasTokens) {
|
|
750
|
+
console.log(`Design tokens: ✓ Found (${tokenResult.files.slice(0, 2).join(', ')}${tokenResult.files.length > 2 ? '...' : ''})`);
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
console.log('Design tokens: ✗ Not found');
|
|
754
|
+
}
|
|
755
|
+
// AI Setup
|
|
756
|
+
console.log(`AI guardrails: ${hasAISetup ? '✓ Configured' : '✗ Not set up'}`);
|
|
757
|
+
// CI Setup
|
|
758
|
+
console.log(`CI/CD: ${hasCISetup ? '✓ Active' : '○ Not configured'}`);
|
|
759
|
+
// Drift
|
|
760
|
+
console.log('');
|
|
761
|
+
if (drifts.length > 0) {
|
|
762
|
+
const critical = drifts.filter(d => d.severity === 'critical').length;
|
|
763
|
+
const warning = drifts.filter(d => d.severity === 'warning').length;
|
|
764
|
+
console.log(`Drift: ${drifts.length} issue${drifts.length === 1 ? '' : 's'}`);
|
|
765
|
+
if (critical > 0)
|
|
766
|
+
console.log(` • ${critical} critical`);
|
|
767
|
+
if (warning > 0)
|
|
768
|
+
console.log(` • ${warning} warnings`);
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
console.log('Drift: None detected');
|
|
772
|
+
}
|
|
773
|
+
// Smart recommendations based on state
|
|
774
|
+
console.log('');
|
|
775
|
+
console.log('─'.repeat(50));
|
|
776
|
+
console.log('RECOMMENDED ACTION');
|
|
777
|
+
console.log('─'.repeat(50));
|
|
778
|
+
console.log('');
|
|
779
|
+
if (!tokenResult.hasTokens) {
|
|
780
|
+
// No tokens - recommend scanning to find values
|
|
781
|
+
console.log('This project has no design tokens.');
|
|
782
|
+
console.log('');
|
|
783
|
+
console.log('PRIMARY: Scan your codebase');
|
|
784
|
+
console.log(' → Run: buoy drift scan');
|
|
785
|
+
console.log(' This finds components and design values in your code.');
|
|
786
|
+
console.log('');
|
|
787
|
+
console.log('ALTERNATIVE: Check for drift without tokens');
|
|
788
|
+
console.log(' → Run: buoy show drift');
|
|
789
|
+
console.log(' Find hardcoded values that should be tokens.');
|
|
790
|
+
}
|
|
791
|
+
else if (!hasAISetup) {
|
|
792
|
+
// Has tokens but no AI setup - recommend dock agents
|
|
793
|
+
console.log('This project has tokens but AI tools aren\'t configured to use them.');
|
|
794
|
+
console.log('');
|
|
795
|
+
console.log('PRIMARY: Set up AI integration');
|
|
796
|
+
console.log(' → Run: buoy dock agents');
|
|
797
|
+
console.log(' This creates skill files and updates CLAUDE.md so AI follows your tokens.');
|
|
798
|
+
console.log('');
|
|
799
|
+
console.log('ALTERNATIVE: Check for drift');
|
|
800
|
+
console.log(' → Run: buoy show drift');
|
|
801
|
+
console.log(' See if code follows your design system.');
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
// Has tokens + AI - recommend drift check
|
|
805
|
+
console.log('This project is set up! AI tools know about your design system.');
|
|
806
|
+
console.log('');
|
|
807
|
+
console.log('PRIMARY: Check for drift');
|
|
808
|
+
console.log(' → Run: buoy show drift');
|
|
809
|
+
console.log(' Find code that diverges from your design system.');
|
|
810
|
+
console.log('');
|
|
811
|
+
console.log('OTHER OPTIONS:');
|
|
812
|
+
console.log(' • buoy dock hooks Interactive setup for Claude or git hooks');
|
|
813
|
+
console.log(' • buoy dock hooks --claude Claude hooks (design system in every session)');
|
|
814
|
+
console.log(' • buoy dock hooks --commit Git pre-commit hooks (catch drift before commit)');
|
|
815
|
+
console.log(' • buoy ahoy github GitHub PR bot (comments on drift)');
|
|
816
|
+
}
|
|
817
|
+
console.log('');
|
|
818
|
+
console.log('─'.repeat(50));
|
|
819
|
+
}
|
|
820
|
+
catch (err) {
|
|
821
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
822
|
+
console.log(`Analysis failed: ${message}`);
|
|
823
|
+
console.log('');
|
|
824
|
+
console.log('Try running: buoy show all');
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
//# sourceMappingURL=begin.js.map
|