@vibecheckai/cli 3.1.8 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/registry.js +106 -116
- package/bin/runners/context/generators/mcp.js +18 -0
- package/bin/runners/context/index.js +72 -4
- package/bin/runners/context/proof-context.js +293 -1
- package/bin/runners/context/security-scanner.js +311 -73
- package/bin/runners/lib/analyzers.js +607 -20
- package/bin/runners/lib/detectors-v2.js +172 -15
- package/bin/runners/lib/entitlements-v2.js +48 -1
- package/bin/runners/lib/evidence-pack.js +678 -0
- package/bin/runners/lib/html-proof-report.js +913 -0
- package/bin/runners/lib/missions/plan.js +231 -41
- package/bin/runners/lib/missions/templates.js +125 -0
- package/bin/runners/lib/scan-output.js +492 -253
- package/bin/runners/lib/ship-output.js +901 -641
- package/bin/runners/runCheckpoint.js +44 -3
- package/bin/runners/runContext.d.ts +4 -0
- package/bin/runners/runDoctor.js +10 -2
- package/bin/runners/runFix.js +51 -341
- package/bin/runners/runInit.js +11 -0
- package/bin/runners/runPolish.d.ts +4 -0
- package/bin/runners/runPolish.js +608 -29
- package/bin/runners/runProve.js +210 -25
- package/bin/runners/runReality.js +846 -101
- package/bin/runners/runScan.js +238 -4
- package/bin/runners/runShip.js +19 -3
- package/bin/runners/runWatch.js +14 -1
- package/bin/vibecheck.js +32 -2
- package/mcp-server/consolidated-tools.js +408 -42
- package/mcp-server/index.js +152 -15
- package/mcp-server/proof-tools.js +571 -0
- package/mcp-server/tier-auth.js +22 -19
- package/mcp-server/tools-v3.js +744 -0
- package/mcp-server/truth-firewall-tools.js +190 -4
- package/package.json +3 -1
- package/bin/runners/runInstall.js +0 -281
- package/bin/runners/runLabs.js +0 -341
package/bin/runners/runPolish.js
CHANGED
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
* - Configuration: Environment variables, feature flags, logging
|
|
17
17
|
* - Documentation: README, API docs, CHANGELOG, contributing guide
|
|
18
18
|
* - Infrastructure: Docker, CI/CD, monitoring, backups
|
|
19
|
+
* - Observability: OpenTelemetry, structured logging, metrics, tracing
|
|
20
|
+
* - Resilience: Circuit breakers, retry logic, timeouts, graceful shutdown
|
|
21
|
+
* - Internationalization: i18n setup, RTL support, locale detection
|
|
22
|
+
* - Privacy: GDPR compliance, cookie consent, data export, account deletion
|
|
19
23
|
*/
|
|
20
24
|
|
|
21
25
|
const fs = require("fs");
|
|
@@ -148,6 +152,185 @@ async function findAllFiles(dir, pattern, maxDepth = 5, currentDepth = 0) {
|
|
|
148
152
|
return results;
|
|
149
153
|
}
|
|
150
154
|
|
|
155
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
156
|
+
// LIBRARY DETECTION - Reduces false positives by detecting library alternatives
|
|
157
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Common libraries that provide specific functionality
|
|
161
|
+
* Used to skip checks when a library already handles it
|
|
162
|
+
*/
|
|
163
|
+
const LIBRARY_ALTERNATIVES = {
|
|
164
|
+
// Error handling libraries
|
|
165
|
+
errorBoundary: [
|
|
166
|
+
'react-error-boundary',
|
|
167
|
+
'@sentry/react',
|
|
168
|
+
'bugsnag-react',
|
|
169
|
+
],
|
|
170
|
+
|
|
171
|
+
// Toast/notification libraries
|
|
172
|
+
toast: [
|
|
173
|
+
'react-hot-toast',
|
|
174
|
+
'react-toastify',
|
|
175
|
+
'sonner',
|
|
176
|
+
'@radix-ui/react-toast',
|
|
177
|
+
'notistack',
|
|
178
|
+
'react-notifications',
|
|
179
|
+
],
|
|
180
|
+
|
|
181
|
+
// Loading/spinner libraries
|
|
182
|
+
spinner: [
|
|
183
|
+
'react-spinners',
|
|
184
|
+
'react-loader-spinner',
|
|
185
|
+
'react-loading',
|
|
186
|
+
'@chakra-ui/react', // Has Spinner
|
|
187
|
+
'@mantine/core', // Has Loader
|
|
188
|
+
],
|
|
189
|
+
|
|
190
|
+
// Skeleton libraries
|
|
191
|
+
skeleton: [
|
|
192
|
+
'react-loading-skeleton',
|
|
193
|
+
'react-content-loader',
|
|
194
|
+
'@chakra-ui/react', // Has Skeleton
|
|
195
|
+
'@mantine/core', // Has Skeleton
|
|
196
|
+
'@radix-ui/themes',
|
|
197
|
+
],
|
|
198
|
+
|
|
199
|
+
// Form validation libraries
|
|
200
|
+
formValidation: [
|
|
201
|
+
'react-hook-form',
|
|
202
|
+
'formik',
|
|
203
|
+
'@tanstack/react-form',
|
|
204
|
+
'react-final-form',
|
|
205
|
+
'zod', // Often paired with RHF
|
|
206
|
+
'yup',
|
|
207
|
+
],
|
|
208
|
+
|
|
209
|
+
// UI component libraries (provide many components)
|
|
210
|
+
uiLibrary: [
|
|
211
|
+
'@chakra-ui/react',
|
|
212
|
+
'@mantine/core',
|
|
213
|
+
'@radix-ui/themes',
|
|
214
|
+
'@mui/material',
|
|
215
|
+
'antd',
|
|
216
|
+
'@nextui-org/react',
|
|
217
|
+
'shadcn',
|
|
218
|
+
'@headlessui/react',
|
|
219
|
+
],
|
|
220
|
+
|
|
221
|
+
// State management (affects how data flows)
|
|
222
|
+
stateManagement: [
|
|
223
|
+
'zustand',
|
|
224
|
+
'@reduxjs/toolkit',
|
|
225
|
+
'jotai',
|
|
226
|
+
'recoil',
|
|
227
|
+
'mobx',
|
|
228
|
+
'@tanstack/react-query',
|
|
229
|
+
],
|
|
230
|
+
|
|
231
|
+
// Internationalization
|
|
232
|
+
i18n: [
|
|
233
|
+
'next-intl',
|
|
234
|
+
'react-i18next',
|
|
235
|
+
'next-translate',
|
|
236
|
+
'@formatjs/intl',
|
|
237
|
+
'lingui',
|
|
238
|
+
],
|
|
239
|
+
|
|
240
|
+
// Analytics/monitoring
|
|
241
|
+
analytics: [
|
|
242
|
+
'@sentry/nextjs',
|
|
243
|
+
'@sentry/react',
|
|
244
|
+
'@vercel/analytics',
|
|
245
|
+
'posthog-js',
|
|
246
|
+
'mixpanel-browser',
|
|
247
|
+
'@segment/analytics-next',
|
|
248
|
+
],
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Check if package.json contains any of the specified libraries
|
|
253
|
+
* @param {string} packageJsonContent - Raw package.json content
|
|
254
|
+
* @param {string[]} libraries - List of library names to check
|
|
255
|
+
* @returns {string|null} - Name of found library or null
|
|
256
|
+
*/
|
|
257
|
+
function hasLibrary(packageJsonContent, libraries) {
|
|
258
|
+
if (!packageJsonContent) return null;
|
|
259
|
+
|
|
260
|
+
for (const lib of libraries) {
|
|
261
|
+
// Check both dependencies and devDependencies
|
|
262
|
+
// Use word boundary to avoid partial matches
|
|
263
|
+
const pattern = new RegExp(`"${lib.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*:`, 'i');
|
|
264
|
+
if (pattern.test(packageJsonContent)) {
|
|
265
|
+
return lib;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Detect project type for context-aware checking
|
|
274
|
+
* @param {string} projectPath - Path to project root
|
|
275
|
+
* @param {string} packageJsonContent - Raw package.json content
|
|
276
|
+
* @returns {Object} Project type info
|
|
277
|
+
*/
|
|
278
|
+
async function detectProjectType(projectPath, packageJsonContent) {
|
|
279
|
+
const hasApp = await pathExists(path.join(projectPath, 'app'));
|
|
280
|
+
const hasPages = await pathExists(path.join(projectPath, 'pages'));
|
|
281
|
+
const hasSrc = await pathExists(path.join(projectPath, 'src'));
|
|
282
|
+
|
|
283
|
+
const isNextJs = packageJsonContent && /["']next["']/.test(packageJsonContent);
|
|
284
|
+
const isRemix = packageJsonContent && /["']@remix-run\//.test(packageJsonContent);
|
|
285
|
+
const isVite = packageJsonContent && /["']vite["']/.test(packageJsonContent);
|
|
286
|
+
const isAstro = packageJsonContent && /["']astro["']/.test(packageJsonContent);
|
|
287
|
+
|
|
288
|
+
// Check for specific project patterns
|
|
289
|
+
const isLibrary = packageJsonContent && /"main"|"module"|"exports"/.test(packageJsonContent);
|
|
290
|
+
const isCli = packageJsonContent && /"bin"/.test(packageJsonContent);
|
|
291
|
+
const isApi = !hasApp && !hasPages && packageJsonContent && /["']express["']|["']fastify["']|["']hono["']/.test(packageJsonContent);
|
|
292
|
+
|
|
293
|
+
// Check if using a comprehensive UI library
|
|
294
|
+
const uiLib = hasLibrary(packageJsonContent, LIBRARY_ALTERNATIVES.uiLibrary);
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
isNextJs,
|
|
298
|
+
isRemix,
|
|
299
|
+
isVite,
|
|
300
|
+
isAstro,
|
|
301
|
+
isLibrary,
|
|
302
|
+
isCli,
|
|
303
|
+
isApi,
|
|
304
|
+
hasAppRouter: hasApp && isNextJs,
|
|
305
|
+
hasPagesRouter: hasPages && isNextJs,
|
|
306
|
+
hasSrc,
|
|
307
|
+
uiLibrary: uiLib,
|
|
308
|
+
// Skip frontend polish checks for non-frontend projects
|
|
309
|
+
skipFrontend: isLibrary || isCli || isApi,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Calculate issue severity based on project context
|
|
315
|
+
* Some issues are less important for certain project types
|
|
316
|
+
*/
|
|
317
|
+
function adjustSeverity(baseSeverity, projectType, issueId) {
|
|
318
|
+
// For libraries/CLIs, frontend issues are less critical
|
|
319
|
+
if (projectType.skipFrontend && issueId.startsWith('missing-')) {
|
|
320
|
+
return 'low';
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// If using a comprehensive UI library, component issues are less critical
|
|
324
|
+
if (projectType.uiLibrary) {
|
|
325
|
+
const componentIssues = ['missing-spinner', 'missing-skeleton', 'missing-toast', 'missing-empty-states'];
|
|
326
|
+
if (componentIssues.includes(issueId)) {
|
|
327
|
+
return 'low';
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return baseSeverity;
|
|
332
|
+
}
|
|
333
|
+
|
|
151
334
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
152
335
|
// POLISH CHECKERS
|
|
153
336
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -155,6 +338,7 @@ async function findAllFiles(dir, pattern, maxDepth = 5, currentDepth = 0) {
|
|
|
155
338
|
const checkers = {
|
|
156
339
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
157
340
|
// FRONTEND CHECKER - Comprehensive UX/UI Polish Analysis
|
|
341
|
+
// Enhanced with library detection and false positive reduction
|
|
158
342
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
159
343
|
async frontend(projectPath) {
|
|
160
344
|
const issues = [];
|
|
@@ -172,20 +356,37 @@ const checkers = {
|
|
|
172
356
|
const searchPath = hasSrc ? srcPath : (hasApp ? appPath : pagesPath);
|
|
173
357
|
const packageJson = await readFileSafe(path.join(projectPath, 'package.json'));
|
|
174
358
|
|
|
359
|
+
// Detect project type and installed libraries for context-aware checking
|
|
360
|
+
const projectType = await detectProjectType(projectPath, packageJson);
|
|
361
|
+
|
|
362
|
+
// Skip frontend checks for non-frontend projects
|
|
363
|
+
if (projectType.skipFrontend) {
|
|
364
|
+
return issues;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Check for library alternatives to reduce false positives
|
|
368
|
+
const errorBoundaryLib = hasLibrary(packageJson, LIBRARY_ALTERNATIVES.errorBoundary);
|
|
369
|
+
const toastLib = hasLibrary(packageJson, LIBRARY_ALTERNATIVES.toast);
|
|
370
|
+
const spinnerLib = hasLibrary(packageJson, LIBRARY_ALTERNATIVES.spinner);
|
|
371
|
+
const skeletonLib = hasLibrary(packageJson, LIBRARY_ALTERNATIVES.skeleton);
|
|
372
|
+
const formLib = hasLibrary(packageJson, LIBRARY_ALTERNATIVES.formValidation);
|
|
373
|
+
|
|
175
374
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
176
375
|
// ERROR HANDLING & BOUNDARIES
|
|
177
376
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
178
377
|
|
|
179
378
|
const hasErrorBoundary = await findFile(searchPath, /ErrorBoundary|error\.(tsx?|jsx?)$/i);
|
|
180
|
-
if (!hasErrorBoundary) {
|
|
379
|
+
if (!hasErrorBoundary && !errorBoundaryLib && !projectType.uiLibrary) {
|
|
380
|
+
const severity = adjustSeverity('critical', projectType, 'missing-error-boundary');
|
|
181
381
|
issues.push({
|
|
182
382
|
id: 'missing-error-boundary',
|
|
183
383
|
category: 'Frontend',
|
|
184
|
-
severity
|
|
384
|
+
severity,
|
|
185
385
|
title: 'Missing Error Boundary',
|
|
186
386
|
description: 'No error boundary component found. React errors will crash the entire app.',
|
|
187
387
|
suggestion: 'Add ErrorBoundary component to catch and handle React errors gracefully.',
|
|
188
388
|
autoFixable: true,
|
|
389
|
+
confidence: 0.9, // High confidence - error boundaries are critical
|
|
189
390
|
aiPrompt: `Create a reusable ErrorBoundary component for React/Next.js that:
|
|
190
391
|
1. Catches JavaScript errors anywhere in the child component tree
|
|
191
392
|
2. Logs errors to console and optionally to an error tracking service
|
|
@@ -200,18 +401,24 @@ const checkers = {
|
|
|
200
401
|
|
|
201
402
|
Place it in src/components/ErrorBoundary.tsx`,
|
|
202
403
|
});
|
|
404
|
+
} else if (errorBoundaryLib) {
|
|
405
|
+
// Library provides this - no issue needed
|
|
203
406
|
}
|
|
204
407
|
|
|
205
408
|
const has404 = await findFile(searchPath, /not-found|NotFound|404/i);
|
|
206
|
-
|
|
409
|
+
// For Next.js app router, check the specific convention
|
|
410
|
+
const hasNextAppRouter404 = hasApp && await pathExists(path.join(appPath, 'not-found.tsx'));
|
|
411
|
+
if (!has404 && !hasNextAppRouter404) {
|
|
412
|
+
const severity = adjustSeverity('medium', projectType, 'missing-404');
|
|
207
413
|
issues.push({
|
|
208
414
|
id: 'missing-404',
|
|
209
415
|
category: 'Frontend',
|
|
210
|
-
severity
|
|
416
|
+
severity,
|
|
211
417
|
title: 'Missing 404 Page',
|
|
212
418
|
description: 'No custom 404/NotFound page found. Users will see default browser error.',
|
|
213
419
|
suggestion: 'Add a custom 404 page with navigation options.',
|
|
214
420
|
autoFixable: true,
|
|
421
|
+
confidence: 0.85, // High confidence but not critical
|
|
215
422
|
aiPrompt: `Create a beautiful, user-friendly 404 Not Found page that:
|
|
216
423
|
1. Has a visually appealing design with subtle animations
|
|
217
424
|
2. Shows a clear "Page Not Found" message
|
|
@@ -234,16 +441,21 @@ For Pages Router: Create pages/404.tsx`,
|
|
|
234
441
|
// LOADING STATES & SKELETONS
|
|
235
442
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
236
443
|
|
|
237
|
-
const hasLoadingSpinner = await findFile(searchPath, /Spinner|LoadingSpinner
|
|
238
|
-
if
|
|
444
|
+
const hasLoadingSpinner = await findFile(searchPath, /Spinner|LoadingSpinner|Loading\.(tsx?|jsx?)$/i);
|
|
445
|
+
// Skip if a spinner library or UI library is installed
|
|
446
|
+
if (!hasLoadingSpinner && !spinnerLib && !projectType.uiLibrary) {
|
|
447
|
+
const severity = adjustSeverity('high', projectType, 'missing-spinner');
|
|
239
448
|
issues.push({
|
|
240
449
|
id: 'missing-spinner',
|
|
241
450
|
category: 'Frontend',
|
|
242
|
-
severity
|
|
451
|
+
severity,
|
|
243
452
|
title: 'Missing Loading Spinner',
|
|
244
453
|
description: 'No spinner component found. Users need visual feedback during loading.',
|
|
245
|
-
suggestion:
|
|
454
|
+
suggestion: spinnerLib
|
|
455
|
+
? `Consider using the ${spinnerLib} library you have installed.`
|
|
456
|
+
: 'Add a reusable Spinner component with multiple sizes and variants.',
|
|
246
457
|
autoFixable: true,
|
|
458
|
+
confidence: 0.7, // Medium confidence - could be using inline styles or library
|
|
247
459
|
aiPrompt: `Create a modern, reusable Spinner/Loading component that:
|
|
248
460
|
1. Has multiple size variants: sm, md, lg, xl
|
|
249
461
|
2. Supports custom colors via props or CSS variables
|
|
@@ -259,16 +471,21 @@ Create: src/components/ui/Spinner.tsx and src/components/ui/Spinner.css`,
|
|
|
259
471
|
});
|
|
260
472
|
}
|
|
261
473
|
|
|
262
|
-
const hasSkeleton = await findFile(searchPath, /Skeleton|Shimmer|
|
|
263
|
-
if
|
|
474
|
+
const hasSkeleton = await findFile(searchPath, /Skeleton|Shimmer|ContentLoader/i);
|
|
475
|
+
// Skip if a skeleton library or UI library is installed
|
|
476
|
+
if (!hasSkeleton && !skeletonLib && !projectType.uiLibrary) {
|
|
477
|
+
const severity = adjustSeverity('high', projectType, 'missing-skeleton');
|
|
264
478
|
issues.push({
|
|
265
479
|
id: 'missing-skeleton',
|
|
266
480
|
category: 'Frontend',
|
|
267
|
-
severity
|
|
481
|
+
severity,
|
|
268
482
|
title: 'Missing Skeleton Loaders',
|
|
269
483
|
description: 'No skeleton/shimmer components found. Skeleton loaders provide better perceived performance than spinners.',
|
|
270
|
-
suggestion:
|
|
484
|
+
suggestion: skeletonLib
|
|
485
|
+
? `Consider using the ${skeletonLib} library you have installed.`
|
|
486
|
+
: 'Add skeleton components for content placeholders.',
|
|
271
487
|
autoFixable: true,
|
|
488
|
+
confidence: 0.65, // Medium-low confidence - many apps don't need skeletons
|
|
272
489
|
aiPrompt: `Create a comprehensive Skeleton loading system with:
|
|
273
490
|
|
|
274
491
|
1. Base Skeleton component with:
|
|
@@ -358,16 +575,21 @@ Create: src/components/ui/EmptyState.tsx with all variants`,
|
|
|
358
575
|
});
|
|
359
576
|
}
|
|
360
577
|
|
|
361
|
-
const hasToast = await findFile(searchPath, /Toast|Notification|Snackbar/i);
|
|
362
|
-
if
|
|
578
|
+
const hasToast = await findFile(searchPath, /Toast|Notification|Snackbar|Toaster/i);
|
|
579
|
+
// Skip if a toast library or UI library is installed
|
|
580
|
+
if (!hasToast && !toastLib && !projectType.uiLibrary) {
|
|
581
|
+
const severity = adjustSeverity('high', projectType, 'missing-toast');
|
|
363
582
|
issues.push({
|
|
364
583
|
id: 'missing-toast',
|
|
365
584
|
category: 'Frontend',
|
|
366
|
-
severity
|
|
585
|
+
severity,
|
|
367
586
|
title: 'Missing Toast/Notification System',
|
|
368
587
|
description: 'No toast or notification component found for user feedback.',
|
|
369
|
-
suggestion:
|
|
588
|
+
suggestion: toastLib
|
|
589
|
+
? `Consider using the ${toastLib} library you have installed.`
|
|
590
|
+
: 'Add a toast/notification system for user feedback on actions.',
|
|
370
591
|
autoFixable: true,
|
|
592
|
+
confidence: 0.75, // Medium-high confidence
|
|
371
593
|
aiPrompt: `Create a complete Toast notification system with:
|
|
372
594
|
|
|
373
595
|
1. Toast component with:
|
|
@@ -401,16 +623,20 @@ Create: src/components/ui/Toast.tsx and src/hooks/useToast.ts`,
|
|
|
401
623
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
402
624
|
|
|
403
625
|
const hasFormValidation = await findFile(searchPath, /FormError|FieldError|ValidationMessage/i);
|
|
404
|
-
|
|
405
|
-
if (!hasFormValidation && !
|
|
626
|
+
// Skip if a form library is installed
|
|
627
|
+
if (!hasFormValidation && !formLib && !projectType.uiLibrary) {
|
|
628
|
+
const severity = adjustSeverity('high', projectType, 'missing-form-validation-ui');
|
|
406
629
|
issues.push({
|
|
407
630
|
id: 'missing-form-validation-ui',
|
|
408
631
|
category: 'Frontend',
|
|
409
|
-
severity
|
|
632
|
+
severity,
|
|
410
633
|
title: 'Missing Form Validation UI',
|
|
411
634
|
description: 'No form validation components found. Users need clear feedback on form errors.',
|
|
412
|
-
suggestion:
|
|
635
|
+
suggestion: formLib
|
|
636
|
+
? `The ${formLib} library you have installed provides form validation. Make sure to display errors.`
|
|
637
|
+
: 'Add form validation components with clear error states.',
|
|
413
638
|
autoFixable: true,
|
|
639
|
+
confidence: 0.65, // Medium confidence - many libraries provide this
|
|
414
640
|
aiPrompt: `Create a comprehensive form validation UI system with:
|
|
415
641
|
|
|
416
642
|
1. FormField wrapper component with:
|
|
@@ -2099,6 +2325,350 @@ export const UploadProgress = ({ progress, fileName }) => (
|
|
|
2099
2325
|
|
|
2100
2326
|
return issues;
|
|
2101
2327
|
},
|
|
2328
|
+
|
|
2329
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2330
|
+
// OBSERVABILITY CHECKER - Logging, Metrics, Tracing
|
|
2331
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2332
|
+
async observability(projectPath) {
|
|
2333
|
+
const issues = [];
|
|
2334
|
+
const packageJson = await readFileSafe(path.join(projectPath, 'package.json'));
|
|
2335
|
+
const srcPath = path.join(projectPath, 'src');
|
|
2336
|
+
const hasSrc = await pathExists(srcPath);
|
|
2337
|
+
const searchPath = hasSrc ? srcPath : projectPath;
|
|
2338
|
+
|
|
2339
|
+
// Check for OpenTelemetry
|
|
2340
|
+
const hasOpenTelemetry = packageJson && /@opentelemetry|otel/i.test(packageJson);
|
|
2341
|
+
if (!hasOpenTelemetry) {
|
|
2342
|
+
issues.push({
|
|
2343
|
+
id: 'missing-opentelemetry',
|
|
2344
|
+
category: 'Observability',
|
|
2345
|
+
severity: 'medium',
|
|
2346
|
+
title: 'Missing OpenTelemetry',
|
|
2347
|
+
description: 'No OpenTelemetry setup found. Distributed tracing helps debug production issues.',
|
|
2348
|
+
suggestion: 'Add @opentelemetry/sdk-node and configure tracing for your application.',
|
|
2349
|
+
autoFixable: false,
|
|
2350
|
+
aiPrompt: 'Set up OpenTelemetry for distributed tracing in my Node.js/Next.js application. Include automatic instrumentation for HTTP requests, database queries, and external API calls. Configure exporters for Jaeger or similar backend.',
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
// Check for structured logging
|
|
2355
|
+
const hasStructuredLogging = packageJson && /pino|winston|bunyan|@elastic\/ecs-pino-format/i.test(packageJson);
|
|
2356
|
+
if (!hasStructuredLogging) {
|
|
2357
|
+
issues.push({
|
|
2358
|
+
id: 'missing-structured-logging',
|
|
2359
|
+
category: 'Observability',
|
|
2360
|
+
severity: 'high',
|
|
2361
|
+
title: 'Missing Structured Logging',
|
|
2362
|
+
description: 'No structured logging library found. console.log is hard to parse in production.',
|
|
2363
|
+
suggestion: 'Add pino or winston for structured JSON logging.',
|
|
2364
|
+
autoFixable: false,
|
|
2365
|
+
aiPrompt: 'Set up structured logging using pino in my application. Configure it to output JSON logs in production with request ID correlation, log levels, and proper error serialization. Include a development mode with pretty-printing.',
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// Check for metrics
|
|
2370
|
+
const hasMetrics = packageJson && /prom-client|@opentelemetry\/sdk-metrics|datadog-metrics/i.test(packageJson);
|
|
2371
|
+
if (!hasMetrics) {
|
|
2372
|
+
issues.push({
|
|
2373
|
+
id: 'missing-metrics',
|
|
2374
|
+
category: 'Observability',
|
|
2375
|
+
severity: 'medium',
|
|
2376
|
+
title: 'Missing Metrics Collection',
|
|
2377
|
+
description: 'No metrics library found. You won\'t have visibility into application performance.',
|
|
2378
|
+
suggestion: 'Add prom-client or OpenTelemetry metrics for application monitoring.',
|
|
2379
|
+
autoFixable: false,
|
|
2380
|
+
aiPrompt: 'Set up Prometheus metrics collection in my application using prom-client. Include default metrics (CPU, memory, event loop lag) and custom business metrics for key operations.',
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
// Check for correlation IDs
|
|
2385
|
+
const hasCorrelationId = await findFile(searchPath, /correlation|request.*id|trace.*id|x-request-id/i);
|
|
2386
|
+
if (!hasCorrelationId && hasSrc) {
|
|
2387
|
+
issues.push({
|
|
2388
|
+
id: 'missing-correlation-ids',
|
|
2389
|
+
category: 'Observability',
|
|
2390
|
+
severity: 'medium',
|
|
2391
|
+
title: 'Missing Request Correlation IDs',
|
|
2392
|
+
description: 'No correlation ID handling found. Debugging distributed requests is difficult.',
|
|
2393
|
+
suggestion: 'Add correlation ID middleware to trace requests across services.',
|
|
2394
|
+
autoFixable: true,
|
|
2395
|
+
aiPrompt: 'Add request correlation ID middleware to my Express/Next.js application. Generate unique IDs for each request, propagate them in headers, and include them in all log output.',
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
return issues;
|
|
2400
|
+
},
|
|
2401
|
+
|
|
2402
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2403
|
+
// RESILIENCE CHECKER - Circuit Breakers, Retry, Graceful Degradation
|
|
2404
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2405
|
+
async resilience(projectPath) {
|
|
2406
|
+
const issues = [];
|
|
2407
|
+
const packageJson = await readFileSafe(path.join(projectPath, 'package.json'));
|
|
2408
|
+
const srcPath = path.join(projectPath, 'src');
|
|
2409
|
+
const hasSrc = await pathExists(srcPath);
|
|
2410
|
+
const searchPath = hasSrc ? srcPath : projectPath;
|
|
2411
|
+
|
|
2412
|
+
// Check for circuit breaker
|
|
2413
|
+
const hasCircuitBreaker = packageJson && /opossum|cockatiel|resilience4j|circuit.*breaker/i.test(packageJson);
|
|
2414
|
+
if (!hasCircuitBreaker) {
|
|
2415
|
+
issues.push({
|
|
2416
|
+
id: 'missing-circuit-breaker',
|
|
2417
|
+
category: 'Resilience',
|
|
2418
|
+
severity: 'medium',
|
|
2419
|
+
title: 'Missing Circuit Breaker',
|
|
2420
|
+
description: 'No circuit breaker library found. External service failures can cascade.',
|
|
2421
|
+
suggestion: 'Add opossum or cockatiel for circuit breaker patterns on external calls.',
|
|
2422
|
+
autoFixable: false,
|
|
2423
|
+
aiPrompt: 'Implement circuit breaker pattern using opossum for external API calls in my application. Configure appropriate thresholds, fallback responses, and health checks for third-party services.',
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
// Check for retry logic
|
|
2428
|
+
const hasRetry = packageJson && /axios-retry|got|ky|p-retry|async-retry/i.test(packageJson);
|
|
2429
|
+
const hasRetryInCode = await findFile(searchPath, /retry|exponential.*backoff|maxRetries/i);
|
|
2430
|
+
if (!hasRetry && !hasRetryInCode) {
|
|
2431
|
+
issues.push({
|
|
2432
|
+
id: 'missing-retry-logic',
|
|
2433
|
+
category: 'Resilience',
|
|
2434
|
+
severity: 'high',
|
|
2435
|
+
title: 'Missing Retry Logic',
|
|
2436
|
+
description: 'No retry mechanism found. Transient failures will cause immediate errors.',
|
|
2437
|
+
suggestion: 'Add retry logic with exponential backoff for external API calls.',
|
|
2438
|
+
autoFixable: false,
|
|
2439
|
+
aiPrompt: 'Add retry logic with exponential backoff to external API calls in my application. Use axios-retry or p-retry with configurable max retries, backoff multiplier, and jitter.',
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
// Check for timeout configuration
|
|
2444
|
+
const hasTimeoutConfig = await findFile(searchPath, /timeout|AbortController|AbortSignal/i);
|
|
2445
|
+
if (!hasTimeoutConfig && hasSrc) {
|
|
2446
|
+
issues.push({
|
|
2447
|
+
id: 'missing-timeouts',
|
|
2448
|
+
category: 'Resilience',
|
|
2449
|
+
severity: 'high',
|
|
2450
|
+
title: 'Missing Request Timeouts',
|
|
2451
|
+
description: 'No timeout configuration found. Slow external services can hang your app.',
|
|
2452
|
+
suggestion: 'Add timeouts to all external HTTP calls and database queries.',
|
|
2453
|
+
autoFixable: false,
|
|
2454
|
+
aiPrompt: 'Add proper timeout handling to all external API calls and database queries in my application. Use AbortController for fetch requests and configure connection/query timeouts for database operations.',
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
// Check for graceful shutdown
|
|
2459
|
+
const hasGracefulShutdown = await findFile(searchPath, /graceful.*shutdown|SIGTERM|SIGINT|beforeExit/i);
|
|
2460
|
+
if (!hasGracefulShutdown) {
|
|
2461
|
+
issues.push({
|
|
2462
|
+
id: 'missing-graceful-shutdown',
|
|
2463
|
+
category: 'Resilience',
|
|
2464
|
+
severity: 'high',
|
|
2465
|
+
title: 'Missing Graceful Shutdown',
|
|
2466
|
+
description: 'No graceful shutdown handling found. In-flight requests may be dropped.',
|
|
2467
|
+
suggestion: 'Add SIGTERM/SIGINT handlers to drain connections before shutdown.',
|
|
2468
|
+
autoFixable: true,
|
|
2469
|
+
aiPrompt: 'Implement graceful shutdown in my Node.js application. Handle SIGTERM and SIGINT signals, stop accepting new connections, drain existing connections, close database pools, and exit cleanly.',
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
// Check for bulkhead pattern
|
|
2474
|
+
const hasBulkhead = packageJson && /bottleneck|p-limit|p-queue|semaphore/i.test(packageJson);
|
|
2475
|
+
if (!hasBulkhead) {
|
|
2476
|
+
issues.push({
|
|
2477
|
+
id: 'missing-bulkhead',
|
|
2478
|
+
category: 'Resilience',
|
|
2479
|
+
severity: 'low',
|
|
2480
|
+
title: 'Missing Bulkhead/Rate Limiting for Internal Operations',
|
|
2481
|
+
description: 'No internal concurrency limiting found. Resource exhaustion possible.',
|
|
2482
|
+
suggestion: 'Add bottleneck or p-limit to limit concurrent operations.',
|
|
2483
|
+
autoFixable: false,
|
|
2484
|
+
aiPrompt: 'Add concurrency limiting using bottleneck or p-limit for resource-intensive operations (file uploads, heavy computations, external API calls) to prevent resource exhaustion.',
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
return issues;
|
|
2489
|
+
},
|
|
2490
|
+
|
|
2491
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2492
|
+
// INTERNATIONALIZATION CHECKER - i18n Setup
|
|
2493
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2494
|
+
async internationalization(projectPath) {
|
|
2495
|
+
const issues = [];
|
|
2496
|
+
const packageJson = await readFileSafe(path.join(projectPath, 'package.json'));
|
|
2497
|
+
const srcPath = path.join(projectPath, 'src');
|
|
2498
|
+
const hasSrc = await pathExists(srcPath);
|
|
2499
|
+
const searchPath = hasSrc ? srcPath : projectPath;
|
|
2500
|
+
|
|
2501
|
+
// Check for i18n library
|
|
2502
|
+
const hasI18n = packageJson && /next-intl|react-i18next|i18next|lingui|formatjs|react-intl/i.test(packageJson);
|
|
2503
|
+
if (!hasI18n) {
|
|
2504
|
+
issues.push({
|
|
2505
|
+
id: 'missing-i18n',
|
|
2506
|
+
category: 'Internationalization',
|
|
2507
|
+
severity: 'low',
|
|
2508
|
+
title: 'Missing Internationalization (i18n)',
|
|
2509
|
+
description: 'No i18n library found. Adding translations later is costly.',
|
|
2510
|
+
suggestion: 'Add next-intl or react-i18next for internationalization support.',
|
|
2511
|
+
autoFixable: false,
|
|
2512
|
+
aiPrompt: 'Set up internationalization in my Next.js/React application using next-intl or react-i18next. Include locale detection, language switching, and a translation extraction workflow.',
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
// Check for locale detection
|
|
2517
|
+
const hasLocaleDetection = await findFile(searchPath, /locale|navigator\.language|accept-language|getLocale/i);
|
|
2518
|
+
if (hasI18n && !hasLocaleDetection) {
|
|
2519
|
+
issues.push({
|
|
2520
|
+
id: 'missing-locale-detection',
|
|
2521
|
+
category: 'Internationalization',
|
|
2522
|
+
severity: 'medium',
|
|
2523
|
+
title: 'Missing Locale Detection',
|
|
2524
|
+
description: 'i18n setup found but no automatic locale detection.',
|
|
2525
|
+
suggestion: 'Add automatic locale detection from browser settings or Accept-Language header.',
|
|
2526
|
+
autoFixable: false,
|
|
2527
|
+
aiPrompt: 'Add automatic locale detection to my internationalized application. Detect from Accept-Language header on server, navigator.language on client, and allow user override with persistence.',
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
// Check for RTL support
|
|
2532
|
+
const hasRTL = await findFile(searchPath, /rtl|dir=.*rtl|direction.*rtl|ltr.*rtl/i);
|
|
2533
|
+
if (hasI18n && !hasRTL) {
|
|
2534
|
+
issues.push({
|
|
2535
|
+
id: 'missing-rtl-support',
|
|
2536
|
+
category: 'Internationalization',
|
|
2537
|
+
severity: 'low',
|
|
2538
|
+
title: 'Missing RTL Support',
|
|
2539
|
+
description: 'No RTL (right-to-left) support found. Arabic/Hebrew users affected.',
|
|
2540
|
+
suggestion: 'Add RTL layout support using CSS logical properties or rtlcss.',
|
|
2541
|
+
autoFixable: false,
|
|
2542
|
+
aiPrompt: 'Add RTL (right-to-left) support to my application for Arabic and Hebrew locales. Use CSS logical properties (start/end instead of left/right), directional icons, and proper text alignment.',
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// Check for translation files
|
|
2547
|
+
const hasTranslationFiles = await pathExists(path.join(projectPath, 'locales')) ||
|
|
2548
|
+
await pathExists(path.join(projectPath, 'translations')) ||
|
|
2549
|
+
await pathExists(path.join(projectPath, 'messages')) ||
|
|
2550
|
+
await pathExists(path.join(srcPath, 'locales'));
|
|
2551
|
+
if (hasI18n && !hasTranslationFiles) {
|
|
2552
|
+
issues.push({
|
|
2553
|
+
id: 'missing-translation-files',
|
|
2554
|
+
category: 'Internationalization',
|
|
2555
|
+
severity: 'medium',
|
|
2556
|
+
title: 'Missing Translation Files',
|
|
2557
|
+
description: 'i18n library found but no translation files directory.',
|
|
2558
|
+
suggestion: 'Create locales/ directory with JSON translation files for each language.',
|
|
2559
|
+
autoFixable: true,
|
|
2560
|
+
aiPrompt: 'Create a translation file structure for my i18n setup. Include English as the base language with organized namespaces, and add a script to extract translation keys from code.',
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
return issues;
|
|
2565
|
+
},
|
|
2566
|
+
|
|
2567
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2568
|
+
// PRIVACY CHECKER - GDPR, Cookie Consent, Data Handling
|
|
2569
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2570
|
+
async privacy(projectPath) {
|
|
2571
|
+
const issues = [];
|
|
2572
|
+
const packageJson = await readFileSafe(path.join(projectPath, 'package.json'));
|
|
2573
|
+
const srcPath = path.join(projectPath, 'src');
|
|
2574
|
+
const hasSrc = await pathExists(srcPath);
|
|
2575
|
+
const searchPath = hasSrc ? srcPath : projectPath;
|
|
2576
|
+
|
|
2577
|
+
// Check for cookie consent
|
|
2578
|
+
const hasCookieConsent = packageJson && /cookie-consent|cookieconsent|react-cookie-consent|gdpr|onetrust|cookiebot/i.test(packageJson);
|
|
2579
|
+
const hasCookieConsentInCode = await findFile(searchPath, /cookie.*consent|gdpr.*consent|consent.*banner/i);
|
|
2580
|
+
if (!hasCookieConsent && !hasCookieConsentInCode) {
|
|
2581
|
+
issues.push({
|
|
2582
|
+
id: 'missing-cookie-consent',
|
|
2583
|
+
category: 'Privacy',
|
|
2584
|
+
severity: 'high',
|
|
2585
|
+
title: 'Missing Cookie Consent',
|
|
2586
|
+
description: 'No cookie consent mechanism found. GDPR/CCPA compliance required.',
|
|
2587
|
+
suggestion: 'Add a cookie consent banner for EU/California visitors.',
|
|
2588
|
+
autoFixable: false,
|
|
2589
|
+
aiPrompt: 'Implement a GDPR-compliant cookie consent banner for my application. Include categories (necessary, analytics, marketing), remember user choice, and only load tracking scripts after consent.',
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
// Check for privacy policy
|
|
2594
|
+
const hasPrivacyPolicy = await pathExists(path.join(projectPath, 'public', 'privacy.html')) ||
|
|
2595
|
+
await pathExists(path.join(projectPath, 'public', 'privacy-policy.html')) ||
|
|
2596
|
+
await findFile(searchPath, /privacy.*policy|PrivacyPolicy/i);
|
|
2597
|
+
if (!hasPrivacyPolicy) {
|
|
2598
|
+
issues.push({
|
|
2599
|
+
id: 'missing-privacy-policy',
|
|
2600
|
+
category: 'Privacy',
|
|
2601
|
+
severity: 'high',
|
|
2602
|
+
title: 'Missing Privacy Policy',
|
|
2603
|
+
description: 'No privacy policy page found. Required by law in many jurisdictions.',
|
|
2604
|
+
suggestion: 'Add a privacy policy page explaining data collection and usage.',
|
|
2605
|
+
autoFixable: false,
|
|
2606
|
+
aiPrompt: 'Create a privacy policy page for my application that covers: what data is collected, how it is used, third-party services, cookies, user rights (GDPR), and contact information.',
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
// Check for data export/deletion capability
|
|
2611
|
+
const hasDataExport = await findFile(searchPath, /export.*data|download.*data|gdpr.*export|data.*portability/i);
|
|
2612
|
+
if (!hasDataExport) {
|
|
2613
|
+
issues.push({
|
|
2614
|
+
id: 'missing-data-export',
|
|
2615
|
+
category: 'Privacy',
|
|
2616
|
+
severity: 'medium',
|
|
2617
|
+
title: 'Missing Data Export Feature',
|
|
2618
|
+
description: 'No data export capability found. GDPR requires data portability.',
|
|
2619
|
+
suggestion: 'Add ability for users to export their data in a standard format.',
|
|
2620
|
+
autoFixable: false,
|
|
2621
|
+
aiPrompt: 'Implement a data export feature for GDPR compliance. Allow users to download all their personal data in JSON or CSV format, including profile, activity history, and any stored preferences.',
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
// Check for account deletion
|
|
2626
|
+
const hasAccountDeletion = await findFile(searchPath, /delete.*account|account.*deletion|remove.*account|gdpr.*delete/i);
|
|
2627
|
+
if (!hasAccountDeletion) {
|
|
2628
|
+
issues.push({
|
|
2629
|
+
id: 'missing-account-deletion',
|
|
2630
|
+
category: 'Privacy',
|
|
2631
|
+
severity: 'high',
|
|
2632
|
+
title: 'Missing Account Deletion',
|
|
2633
|
+
description: 'No account deletion feature found. GDPR requires right to erasure.',
|
|
2634
|
+
suggestion: 'Add ability for users to delete their account and all associated data.',
|
|
2635
|
+
autoFixable: false,
|
|
2636
|
+
aiPrompt: 'Implement account deletion functionality with GDPR right to erasure. Include confirmation flow, grace period option, cascade deletion of related data, and anonymization of non-deletable records.',
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
// Check for data retention policy
|
|
2641
|
+
const hasDataRetention = await findFile(searchPath, /retention|data.*cleanup|purge.*old|ttl.*expire/i);
|
|
2642
|
+
if (!hasDataRetention) {
|
|
2643
|
+
issues.push({
|
|
2644
|
+
id: 'missing-data-retention',
|
|
2645
|
+
category: 'Privacy',
|
|
2646
|
+
severity: 'medium',
|
|
2647
|
+
title: 'Missing Data Retention Policy',
|
|
2648
|
+
description: 'No data retention/cleanup logic found. Old data should be purged.',
|
|
2649
|
+
suggestion: 'Implement data retention policies to automatically purge old data.',
|
|
2650
|
+
autoFixable: false,
|
|
2651
|
+
aiPrompt: 'Implement a data retention policy for my application. Add scheduled jobs to purge old sessions, logs, and inactive accounts. Document retention periods for each data type.',
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
// Check for PII encryption
|
|
2656
|
+
const hasEncryption = await findFile(searchPath, /encrypt|crypto.*cipher|aes|bcrypt|argon2/i);
|
|
2657
|
+
if (!hasEncryption) {
|
|
2658
|
+
issues.push({
|
|
2659
|
+
id: 'missing-pii-encryption',
|
|
2660
|
+
category: 'Privacy',
|
|
2661
|
+
severity: 'high',
|
|
2662
|
+
title: 'Missing Data Encryption',
|
|
2663
|
+
description: 'No encryption utilities found. Sensitive data should be encrypted.',
|
|
2664
|
+
suggestion: 'Add encryption for PII at rest and ensure passwords use bcrypt/argon2.',
|
|
2665
|
+
autoFixable: false,
|
|
2666
|
+
aiPrompt: 'Set up encryption for sensitive data in my application. Use bcrypt or argon2 for passwords, field-level encryption for PII, and ensure encryption keys are properly managed.',
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
return issues;
|
|
2671
|
+
},
|
|
2102
2672
|
};
|
|
2103
2673
|
|
|
2104
2674
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -2217,15 +2787,19 @@ ${c.bold}OPTIONS${c.reset}
|
|
|
2217
2787
|
--json Output as JSON (includes AI prompts)
|
|
2218
2788
|
|
|
2219
2789
|
${c.bold}CATEGORIES${c.reset}
|
|
2220
|
-
frontend
|
|
2221
|
-
backend
|
|
2222
|
-
security
|
|
2223
|
-
performance
|
|
2224
|
-
accessibility
|
|
2225
|
-
seo
|
|
2226
|
-
configuration
|
|
2227
|
-
documentation
|
|
2228
|
-
infrastructure
|
|
2790
|
+
frontend Error boundaries, skeletons, ARIA, haptics, animations, caching
|
|
2791
|
+
backend Health endpoints, validation, rate limiting, error handling
|
|
2792
|
+
security .env files, security headers, CORS, secrets management
|
|
2793
|
+
performance Image optimization, caching, bundle analysis
|
|
2794
|
+
accessibility A11y testing, skip links, focus management
|
|
2795
|
+
seo Meta tags, sitemap, robots.txt, Open Graph
|
|
2796
|
+
configuration TypeScript, ESLint, Prettier, EditorConfig
|
|
2797
|
+
documentation README, CHANGELOG, CONTRIBUTING, LICENSE
|
|
2798
|
+
infrastructure Docker, CI/CD, deployment config, env validation
|
|
2799
|
+
observability OpenTelemetry, structured logging, metrics, tracing
|
|
2800
|
+
resilience Circuit breakers, retry logic, timeouts, graceful shutdown
|
|
2801
|
+
internationalization i18n setup, RTL support, locale detection, translations
|
|
2802
|
+
privacy GDPR, cookie consent, data export, account deletion
|
|
2229
2803
|
|
|
2230
2804
|
${c.bold}EXAMPLES${c.reset}
|
|
2231
2805
|
vibecheck polish # Analyze current project
|
|
@@ -2271,6 +2845,11 @@ function getCategoryIcon(category) {
|
|
|
2271
2845
|
Configuration: icons.gear,
|
|
2272
2846
|
Documentation: icons.book,
|
|
2273
2847
|
Infrastructure: icons.rocket,
|
|
2848
|
+
// New world-class categories
|
|
2849
|
+
Observability: '📊',
|
|
2850
|
+
Resilience: '🛡️',
|
|
2851
|
+
Internationalization: '🌍',
|
|
2852
|
+
Privacy: '🔐',
|
|
2274
2853
|
};
|
|
2275
2854
|
return categoryIcons[category] || icons.star;
|
|
2276
2855
|
}
|