@vibecheckai/cli 3.1.5 → 3.1.6
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 +1 -1
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -0
- package/bin/runners/lib/global-flags.js +213 -0
- package/bin/runners/lib/init-wizard.js +566 -273
- package/bin/runners/lib/interactive-menu.js +1496 -0
- package/bin/runners/runCheckpoint.js +503 -502
- package/bin/runners/runContext.js +15 -7
- package/bin/runners/runCtx.js +10 -5
- package/bin/runners/runDoctor.js +20 -32
- package/bin/runners/runFix.js +19 -11
- package/bin/runners/runInit.js +847 -55
- package/bin/runners/runPermissions.js +13 -7
- package/bin/runners/runProve.js +18 -12
- package/bin/runners/runReport.js +31 -18
- package/bin/runners/runScan.js +28 -17
- package/bin/runners/runShip.js +27 -21
- package/bin/runners/runStatus.js +17 -1
- package/bin/vibecheck.js +882 -201
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
package/bin/runners/runInit.js
CHANGED
|
@@ -182,6 +182,8 @@ const ICONS = {
|
|
|
182
182
|
update: '✏️',
|
|
183
183
|
skip: '⏭️',
|
|
184
184
|
detect: '🔍',
|
|
185
|
+
repair: '🔧',
|
|
186
|
+
tool: '🛠️',
|
|
185
187
|
|
|
186
188
|
// Objects
|
|
187
189
|
file: '📄',
|
|
@@ -295,6 +297,556 @@ function printSection(title, icon = '◆') {
|
|
|
295
297
|
printDivider();
|
|
296
298
|
}
|
|
297
299
|
|
|
300
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
301
|
+
// FRAMEWORK-SPECIFIC CONFIGURATION
|
|
302
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
303
|
+
|
|
304
|
+
function getFrameworkConfig(detection) {
|
|
305
|
+
if (!detection || !detection.frameworks) {
|
|
306
|
+
return {
|
|
307
|
+
framework: "node",
|
|
308
|
+
checks: ["routes", "env", "auth", "security"],
|
|
309
|
+
ignorePaths: [],
|
|
310
|
+
extra: {},
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Find the primary detected framework
|
|
315
|
+
const detectedFrameworks = Object.entries(detection.frameworks)
|
|
316
|
+
.filter(([key, fw]) => fw.detected)
|
|
317
|
+
.map(([key, fw]) => ({ key, ...fw }));
|
|
318
|
+
|
|
319
|
+
if (detectedFrameworks.length === 0) {
|
|
320
|
+
return {
|
|
321
|
+
framework: "node",
|
|
322
|
+
checks: ["routes", "env", "auth", "security"],
|
|
323
|
+
ignorePaths: [],
|
|
324
|
+
extra: {},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Use the first detected framework (usually the most specific)
|
|
329
|
+
const primary = detectedFrameworks[0];
|
|
330
|
+
const frameworkKey = primary.key;
|
|
331
|
+
|
|
332
|
+
// Framework-specific configurations
|
|
333
|
+
const frameworkConfigs = {
|
|
334
|
+
nextjs: {
|
|
335
|
+
framework: "nextjs",
|
|
336
|
+
checks: ["routes", "auth", "api", "security", "ssr"],
|
|
337
|
+
ignorePaths: [".next/", "out/", ".vercel/"],
|
|
338
|
+
extra: {},
|
|
339
|
+
},
|
|
340
|
+
remix: {
|
|
341
|
+
framework: "remix",
|
|
342
|
+
checks: ["routes", "auth", "api", "security", "loaders"],
|
|
343
|
+
ignorePaths: [".remix/", "build/"],
|
|
344
|
+
extra: {},
|
|
345
|
+
},
|
|
346
|
+
nuxt: {
|
|
347
|
+
framework: "nuxt",
|
|
348
|
+
checks: ["routes", "auth", "api", "security"],
|
|
349
|
+
ignorePaths: [".nuxt/", ".output/"],
|
|
350
|
+
extra: {},
|
|
351
|
+
},
|
|
352
|
+
sveltekit: {
|
|
353
|
+
framework: "sveltekit",
|
|
354
|
+
checks: ["routes", "auth", "api", "security"],
|
|
355
|
+
ignorePaths: [".svelte-kit/", "build/"],
|
|
356
|
+
extra: {},
|
|
357
|
+
},
|
|
358
|
+
react: {
|
|
359
|
+
framework: "react",
|
|
360
|
+
checks: ["components", "api", "security"],
|
|
361
|
+
ignorePaths: ["build/", "dist/"],
|
|
362
|
+
extra: {},
|
|
363
|
+
},
|
|
364
|
+
vue: {
|
|
365
|
+
framework: "vue",
|
|
366
|
+
checks: ["components", "api", "security"],
|
|
367
|
+
ignorePaths: ["dist/"],
|
|
368
|
+
extra: {},
|
|
369
|
+
},
|
|
370
|
+
fastify: {
|
|
371
|
+
framework: "fastify",
|
|
372
|
+
checks: ["routes", "auth", "api", "security"],
|
|
373
|
+
ignorePaths: [],
|
|
374
|
+
extra: {
|
|
375
|
+
fastify: {
|
|
376
|
+
plugins: detection.frameworks?.fastify?.plugins || [],
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
express: {
|
|
381
|
+
framework: "express",
|
|
382
|
+
checks: ["routes", "auth", "api", "security"],
|
|
383
|
+
ignorePaths: [],
|
|
384
|
+
extra: {},
|
|
385
|
+
},
|
|
386
|
+
nestjs: {
|
|
387
|
+
framework: "nestjs",
|
|
388
|
+
checks: ["routes", "auth", "api", "security", "modules"],
|
|
389
|
+
ignorePaths: ["dist/"],
|
|
390
|
+
extra: {},
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// Get framework-specific config or use detected checks
|
|
395
|
+
const config = frameworkConfigs[frameworkKey] || {
|
|
396
|
+
framework: frameworkKey,
|
|
397
|
+
checks: primary.checks || ["routes", "env", "auth", "security"],
|
|
398
|
+
ignorePaths: [],
|
|
399
|
+
extra: {},
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// Merge detected checks if available
|
|
403
|
+
if (primary.checks && primary.checks.length > 0) {
|
|
404
|
+
config.checks = [...new Set([...config.checks, ...primary.checks])];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return config;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
411
|
+
// INIT STATE TRACKING
|
|
412
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
413
|
+
|
|
414
|
+
function saveInitState(targetDir, state) {
|
|
415
|
+
try {
|
|
416
|
+
const statePath = path.join(targetDir, ".vibecheck", ".init-state.json");
|
|
417
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
418
|
+
} catch (e) {
|
|
419
|
+
// Non-critical - state tracking is optional
|
|
420
|
+
if (process.env.VIBECHECK_DEBUG) {
|
|
421
|
+
console.error(`Failed to save init state: ${e.message}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function loadInitState(targetDir) {
|
|
427
|
+
try {
|
|
428
|
+
const statePath = path.join(targetDir, ".vibecheck", ".init-state.json");
|
|
429
|
+
if (fs.existsSync(statePath)) {
|
|
430
|
+
return JSON.parse(fs.readFileSync(statePath, "utf-8"));
|
|
431
|
+
}
|
|
432
|
+
} catch (e) {
|
|
433
|
+
// Non-critical
|
|
434
|
+
}
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
439
|
+
// POST-INIT VALIDATION
|
|
440
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
441
|
+
|
|
442
|
+
async function verifyInitSetup(targetDir, opts) {
|
|
443
|
+
const issues = [];
|
|
444
|
+
const vibecheckDir = path.join(targetDir, ".vibecheck");
|
|
445
|
+
|
|
446
|
+
// Check directory structure
|
|
447
|
+
const requiredDirs = [
|
|
448
|
+
vibecheckDir,
|
|
449
|
+
path.join(vibecheckDir, "results"),
|
|
450
|
+
path.join(vibecheckDir, "contracts"),
|
|
451
|
+
];
|
|
452
|
+
|
|
453
|
+
for (const dir of requiredDirs) {
|
|
454
|
+
if (!fs.existsSync(dir)) {
|
|
455
|
+
issues.push(`Missing directory: ${path.relative(targetDir, dir)}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Check config.json
|
|
460
|
+
const configPath = path.join(vibecheckDir, "config.json");
|
|
461
|
+
if (!fs.existsSync(configPath)) {
|
|
462
|
+
issues.push("Missing: .vibecheck/config.json");
|
|
463
|
+
} else {
|
|
464
|
+
try {
|
|
465
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
466
|
+
if (!config.version || !config.checks) {
|
|
467
|
+
issues.push("Invalid config.json structure");
|
|
468
|
+
}
|
|
469
|
+
} catch (e) {
|
|
470
|
+
issues.push(`Invalid config.json: ${e.message}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Check truthpack
|
|
475
|
+
const truthpackPath = path.join(vibecheckDir, "truthpack.json");
|
|
476
|
+
if (!fs.existsSync(truthpackPath)) {
|
|
477
|
+
issues.push("Missing: .vibecheck/truthpack.json (run 'vibecheck ctx build')");
|
|
478
|
+
} else {
|
|
479
|
+
try {
|
|
480
|
+
const truthpack = JSON.parse(fs.readFileSync(truthpackPath, "utf-8"));
|
|
481
|
+
if (!truthpack.version || !truthpack.generatedAt) {
|
|
482
|
+
issues.push("Invalid truthpack.json structure");
|
|
483
|
+
}
|
|
484
|
+
} catch (e) {
|
|
485
|
+
issues.push(`Invalid truthpack.json: ${e.message}`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Check contracts
|
|
490
|
+
const contractsDir = path.join(vibecheckDir, "contracts");
|
|
491
|
+
const requiredContracts = ["routes.contract.json", "env.contract.json"];
|
|
492
|
+
for (const contract of requiredContracts) {
|
|
493
|
+
const contractPath = path.join(contractsDir, contract);
|
|
494
|
+
if (!fs.existsSync(contractPath)) {
|
|
495
|
+
issues.push(`Missing contract: ${contract}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (issues.length === 0 && !opts.json) {
|
|
500
|
+
console.log();
|
|
501
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} ${c.bold}Setup Verification:${c.reset} All checks passed`);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return {
|
|
505
|
+
success: issues.length === 0,
|
|
506
|
+
issues,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
511
|
+
// FRAMEWORK-SPECIFIC RECOMMENDATIONS
|
|
512
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
513
|
+
|
|
514
|
+
function printFrameworkRecommendations(detection, opts) {
|
|
515
|
+
const frameworks = Object.keys(detection.frameworks || {}).filter(k => detection.frameworks[k].detected);
|
|
516
|
+
if (frameworks.length === 0) return;
|
|
517
|
+
|
|
518
|
+
const primaryFramework = frameworks[0];
|
|
519
|
+
const framework = detection.frameworks[primaryFramework];
|
|
520
|
+
|
|
521
|
+
const recommendations = [];
|
|
522
|
+
|
|
523
|
+
// Next.js specific
|
|
524
|
+
if (primaryFramework === 'nextjs') {
|
|
525
|
+
recommendations.push({
|
|
526
|
+
icon: ICONS.framework,
|
|
527
|
+
title: "Next.js Route Verification",
|
|
528
|
+
command: "vibecheck scan --routes",
|
|
529
|
+
description: "Verify all Next.js routes are properly configured",
|
|
530
|
+
});
|
|
531
|
+
if (!opts.connect) {
|
|
532
|
+
recommendations.push({
|
|
533
|
+
icon: ICONS.ci,
|
|
534
|
+
title: "Add Vercel Integration",
|
|
535
|
+
command: "vibecheck init --connect",
|
|
536
|
+
description: "Enable GitHub Actions for automatic PR checks",
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Fastify specific
|
|
542
|
+
if (primaryFramework === 'fastify') {
|
|
543
|
+
recommendations.push({
|
|
544
|
+
icon: ICONS.framework,
|
|
545
|
+
title: "Fastify Route Extraction",
|
|
546
|
+
command: "vibecheck scan --routes",
|
|
547
|
+
description: "Extract and verify all Fastify routes",
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Express specific
|
|
552
|
+
if (primaryFramework === 'express') {
|
|
553
|
+
recommendations.push({
|
|
554
|
+
icon: ICONS.framework,
|
|
555
|
+
title: "Express Route Analysis",
|
|
556
|
+
command: "vibecheck scan --routes",
|
|
557
|
+
description: "Analyze Express route definitions",
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Auth providers
|
|
562
|
+
const authProviders = Object.keys(detection.auth || {}).filter(k => detection.auth[k].detected);
|
|
563
|
+
if (authProviders.length > 0) {
|
|
564
|
+
recommendations.push({
|
|
565
|
+
icon: ICONS.auth,
|
|
566
|
+
title: "Auth Coverage Check",
|
|
567
|
+
command: "vibecheck scan --auth",
|
|
568
|
+
description: "Verify authentication coverage across routes",
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Payment providers
|
|
573
|
+
const paymentProviders = Object.keys(detection.payments || {}).filter(k => detection.payments[k].detected);
|
|
574
|
+
if (paymentProviders.length > 0) {
|
|
575
|
+
recommendations.push({
|
|
576
|
+
icon: ICONS.payment,
|
|
577
|
+
title: "Payment Flow Verification",
|
|
578
|
+
command: "vibecheck scan --billing",
|
|
579
|
+
description: "Verify payment webhook handlers and idempotency",
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (recommendations.length > 0) {
|
|
584
|
+
console.log();
|
|
585
|
+
printSection('RECOMMENDATIONS', ICONS.sparkle);
|
|
586
|
+
console.log();
|
|
587
|
+
for (const rec of recommendations) {
|
|
588
|
+
console.log(` ${rec.icon} ${c.bold}${rec.title}${c.reset}`);
|
|
589
|
+
console.log(` ${c.dim}${rec.description}${c.reset}`);
|
|
590
|
+
console.log(` ${c.cyan}${rec.command}${c.reset}`);
|
|
591
|
+
console.log();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
597
|
+
// REPAIR MODE - Fix partial init state
|
|
598
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
599
|
+
|
|
600
|
+
async function runRepairMode(targetDir, projectName, opts) {
|
|
601
|
+
printSection('REPAIR MODE', ICONS.repair);
|
|
602
|
+
console.log();
|
|
603
|
+
console.log(` ${colors.info}${ICONS.info}${c.reset} Scanning for partial initialization state...`);
|
|
604
|
+
console.log();
|
|
605
|
+
|
|
606
|
+
// Load previous init state if available
|
|
607
|
+
const previousState = loadInitState(targetDir);
|
|
608
|
+
if (previousState && !opts.json) {
|
|
609
|
+
console.log(` ${c.dim}Previous init: ${previousState.timestamp} (${previousState.mode} mode)${c.reset}`);
|
|
610
|
+
if (previousState.errors && previousState.errors.length > 0) {
|
|
611
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} ${previousState.errors.length} error(s) from previous attempt`);
|
|
612
|
+
}
|
|
613
|
+
console.log();
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const vibecheckDir = path.join(targetDir, ".vibecheck");
|
|
617
|
+
const issues = [];
|
|
618
|
+
const fixes = [];
|
|
619
|
+
|
|
620
|
+
// Check for missing directories
|
|
621
|
+
const requiredDirs = [
|
|
622
|
+
vibecheckDir,
|
|
623
|
+
path.join(vibecheckDir, "results"),
|
|
624
|
+
path.join(vibecheckDir, "reports"),
|
|
625
|
+
path.join(vibecheckDir, "contracts"),
|
|
626
|
+
path.join(vibecheckDir, "missions"),
|
|
627
|
+
path.join(vibecheckDir, "checkpoints"),
|
|
628
|
+
path.join(vibecheckDir, "runs"),
|
|
629
|
+
];
|
|
630
|
+
|
|
631
|
+
for (const dir of requiredDirs) {
|
|
632
|
+
if (!fs.existsSync(dir)) {
|
|
633
|
+
issues.push(`Missing directory: ${path.relative(targetDir, dir)}`);
|
|
634
|
+
if (!opts.dryRun) {
|
|
635
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
636
|
+
fixes.push(`Created: ${path.relative(targetDir, dir)}`);
|
|
637
|
+
} else {
|
|
638
|
+
fixes.push(`Would create: ${path.relative(targetDir, dir)}`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Check for missing config.json
|
|
644
|
+
const configPath = path.join(vibecheckDir, "config.json");
|
|
645
|
+
if (!fs.existsSync(configPath)) {
|
|
646
|
+
issues.push("Missing: .vibecheck/config.json");
|
|
647
|
+
if (!opts.dryRun) {
|
|
648
|
+
try {
|
|
649
|
+
const { detectAll } = require("./lib/enterprise-detect");
|
|
650
|
+
const detection = detectAll(targetDir);
|
|
651
|
+
const frameworkConfig = getFrameworkConfig(detection);
|
|
652
|
+
const config = {
|
|
653
|
+
version: "2.0.0",
|
|
654
|
+
project: projectName,
|
|
655
|
+
createdAt: new Date().toISOString(),
|
|
656
|
+
framework: frameworkConfig.framework || "node",
|
|
657
|
+
checks: frameworkConfig.checks || ["routes", "env", "auth", "security"],
|
|
658
|
+
output: ".vibecheck",
|
|
659
|
+
policy: {
|
|
660
|
+
allowlist: { domains: [], packages: [] },
|
|
661
|
+
ignore: {
|
|
662
|
+
paths: [
|
|
663
|
+
"node_modules",
|
|
664
|
+
"__tests__",
|
|
665
|
+
"*.test.*",
|
|
666
|
+
"*.spec.*",
|
|
667
|
+
...(frameworkConfig.ignorePaths || []),
|
|
668
|
+
],
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
thresholds: {
|
|
672
|
+
minScore: 70,
|
|
673
|
+
maxBlockers: 0,
|
|
674
|
+
maxWarnings: 10,
|
|
675
|
+
},
|
|
676
|
+
...(frameworkConfig.extra || {}),
|
|
677
|
+
};
|
|
678
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
679
|
+
fixes.push("Created: .vibecheck/config.json");
|
|
680
|
+
} catch (e) {
|
|
681
|
+
issues.push(`Failed to create config.json: ${e.message}`);
|
|
682
|
+
}
|
|
683
|
+
} else {
|
|
684
|
+
fixes.push("Would create: .vibecheck/config.json");
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Check for missing truthpack
|
|
689
|
+
const truthpackPath = path.join(vibecheckDir, "truthpack.json");
|
|
690
|
+
if (!fs.existsSync(truthpackPath)) {
|
|
691
|
+
issues.push("Missing: .vibecheck/truthpack.json");
|
|
692
|
+
if (!opts.dryRun) {
|
|
693
|
+
startSpinner('Generating truthpack...', colors.accent);
|
|
694
|
+
try {
|
|
695
|
+
const { runCtx } = require("./runCtx");
|
|
696
|
+
await runCtx(["build", "--path", targetDir, "--quiet"]);
|
|
697
|
+
stopSpinner('Truthpack generated', true);
|
|
698
|
+
fixes.push("Generated: .vibecheck/truthpack.json");
|
|
699
|
+
} catch (e) {
|
|
700
|
+
stopSpinner('Truthpack generation failed', false);
|
|
701
|
+
issues.push(`Failed to generate truthpack: ${e.message}`);
|
|
702
|
+
}
|
|
703
|
+
} else {
|
|
704
|
+
fixes.push("Would generate: .vibecheck/truthpack.json");
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Check for missing contracts
|
|
709
|
+
const contractsDir = path.join(vibecheckDir, "contracts");
|
|
710
|
+
const routeContractPath = path.join(contractsDir, "routes.contract.json");
|
|
711
|
+
const envContractPath = path.join(contractsDir, "env.contract.json");
|
|
712
|
+
|
|
713
|
+
if (!fs.existsSync(routeContractPath)) {
|
|
714
|
+
issues.push("Missing: .vibecheck/contracts/routes.contract.json");
|
|
715
|
+
if (!opts.dryRun) {
|
|
716
|
+
const routeContract = {
|
|
717
|
+
version: "1.0.0",
|
|
718
|
+
generatedAt: new Date().toISOString(),
|
|
719
|
+
routes: [],
|
|
720
|
+
description: "Route contract - populated by scan",
|
|
721
|
+
};
|
|
722
|
+
fs.writeFileSync(routeContractPath, JSON.stringify(routeContract, null, 2));
|
|
723
|
+
fixes.push("Created: .vibecheck/contracts/routes.contract.json");
|
|
724
|
+
} else {
|
|
725
|
+
fixes.push("Would create: .vibecheck/contracts/routes.contract.json");
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (!fs.existsSync(envContractPath)) {
|
|
730
|
+
issues.push("Missing: .vibecheck/contracts/env.contract.json");
|
|
731
|
+
if (!opts.dryRun) {
|
|
732
|
+
const envContract = {
|
|
733
|
+
version: "1.0.0",
|
|
734
|
+
generatedAt: new Date().toISOString(),
|
|
735
|
+
required: [],
|
|
736
|
+
optional: [],
|
|
737
|
+
description: "Environment contract - populated by scan",
|
|
738
|
+
};
|
|
739
|
+
fs.writeFileSync(envContractPath, JSON.stringify(envContract, null, 2));
|
|
740
|
+
fixes.push("Created: .vibecheck/contracts/env.contract.json");
|
|
741
|
+
} else {
|
|
742
|
+
fixes.push("Would create: .vibecheck/contracts/env.contract.json");
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Use previous state to prioritize fixes
|
|
747
|
+
if (previousState && previousState.errors && previousState.errors.length > 0) {
|
|
748
|
+
const previousErrors = previousState.errors.map(e => e.step);
|
|
749
|
+
// Prioritize fixing errors from previous attempt
|
|
750
|
+
const prioritizedIssues = [];
|
|
751
|
+
const otherIssues = [];
|
|
752
|
+
|
|
753
|
+
for (const issue of issues) {
|
|
754
|
+
const isPreviousError = previousErrors.some(step => issue.toLowerCase().includes(step));
|
|
755
|
+
if (isPreviousError) {
|
|
756
|
+
prioritizedIssues.push(issue);
|
|
757
|
+
} else {
|
|
758
|
+
otherIssues.push(issue);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
issues.length = 0;
|
|
763
|
+
issues.push(...prioritizedIssues, ...otherIssues);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Summary
|
|
767
|
+
console.log();
|
|
768
|
+
if (issues.length === 0) {
|
|
769
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} No issues found - initialization is complete`);
|
|
770
|
+
// Clear previous state on successful repair
|
|
771
|
+
if (!opts.dryRun) {
|
|
772
|
+
const statePath = path.join(targetDir, ".vibecheck", ".init-state.json");
|
|
773
|
+
if (fs.existsSync(statePath)) {
|
|
774
|
+
fs.unlinkSync(statePath);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return 0;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} Found ${issues.length} issue(s)`);
|
|
781
|
+
if (previousState && previousState.errors && previousState.errors.length > 0) {
|
|
782
|
+
console.log(` ${c.dim}(${previousState.errors.length} from previous attempt)${c.reset}`);
|
|
783
|
+
}
|
|
784
|
+
console.log();
|
|
785
|
+
for (const issue of issues) {
|
|
786
|
+
console.log(` ${c.dim}•${c.reset} ${issue}`);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (fixes.length > 0) {
|
|
790
|
+
console.log();
|
|
791
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} Applied ${fixes.length} fix(es)`);
|
|
792
|
+
for (const fix of fixes) {
|
|
793
|
+
console.log(` ${c.dim}•${c.reset} ${fix}`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
console.log();
|
|
798
|
+
return issues.length > 0 ? (opts.dryRun ? 0 : 1) : 0;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
802
|
+
// ENHANCED DETECTION DISPLAY
|
|
803
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
804
|
+
|
|
805
|
+
function printEnhancedDetection(detection, verbose = false) {
|
|
806
|
+
const frameworks = Object.keys(detection.frameworks || {}).filter(k => detection.frameworks[k].detected);
|
|
807
|
+
const databases = Object.keys(detection.databases || {}).filter(k => detection.databases[k].detected);
|
|
808
|
+
const authProviders = Object.keys(detection.auth || {}).filter(k => detection.auth[k].detected);
|
|
809
|
+
const paymentProviders = Object.keys(detection.payments || {}).filter(k => detection.payments[k].detected);
|
|
810
|
+
|
|
811
|
+
if (frameworks.length > 0) {
|
|
812
|
+
console.log(` ${colors.info}${ICONS.info}${c.reset} ${c.bold}Detected Stack:${c.reset}`);
|
|
813
|
+
console.log(` ${ICONS.framework} Frameworks: ${frameworks.map(f => detection.frameworks[f].name).join(', ')}`);
|
|
814
|
+
|
|
815
|
+
if (databases.length > 0) {
|
|
816
|
+
console.log(` ${ICONS.database} Databases: ${databases.map(d => detection.databases[d].name).join(', ')}`);
|
|
817
|
+
}
|
|
818
|
+
if (authProviders.length > 0) {
|
|
819
|
+
console.log(` ${ICONS.auth} Auth: ${authProviders.map(a => detection.auth[a].name).join(', ')}`);
|
|
820
|
+
}
|
|
821
|
+
if (paymentProviders.length > 0) {
|
|
822
|
+
console.log(` ${ICONS.payment} Payments: ${paymentProviders.map(p => detection.payments[p].name).join(', ')}`);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (verbose) {
|
|
826
|
+
console.log();
|
|
827
|
+
console.log(` ${c.dim}Detection Details:${c.reset}`);
|
|
828
|
+
if (detection.packageManager) {
|
|
829
|
+
console.log(` Package Manager: ${detection.packageManager}`);
|
|
830
|
+
}
|
|
831
|
+
if (detection.nodeVersion) {
|
|
832
|
+
console.log(` Node Version: ${detection.nodeVersion}`);
|
|
833
|
+
}
|
|
834
|
+
const checks = detection.checks || [];
|
|
835
|
+
if (checks.length > 0) {
|
|
836
|
+
console.log(` Recommended Checks: ${checks.join(', ')}`);
|
|
837
|
+
}
|
|
838
|
+
const invariants = detection.invariants || [];
|
|
839
|
+
if (invariants.length > 0) {
|
|
840
|
+
console.log(` Invariants: ${invariants.join(', ')}`);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
console.log();
|
|
844
|
+
} else {
|
|
845
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} No framework detected - using default Node.js configuration`);
|
|
846
|
+
console.log();
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
298
850
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
299
851
|
// DETECTION DISPLAY
|
|
300
852
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -314,7 +866,14 @@ function printDetectionResults(detection) {
|
|
|
314
866
|
];
|
|
315
867
|
|
|
316
868
|
for (const cat of categories) {
|
|
317
|
-
const
|
|
869
|
+
const itemsObj = detection[cat.key] || {};
|
|
870
|
+
// Handle both object and array formats
|
|
871
|
+
const items = Array.isArray(itemsObj)
|
|
872
|
+
? itemsObj
|
|
873
|
+
: Object.entries(itemsObj)
|
|
874
|
+
.filter(([key, value]) => value && (value.detected || value === true))
|
|
875
|
+
.map(([key, value]) => typeof value === 'object' ? value.name || key : key);
|
|
876
|
+
|
|
318
877
|
if (items.length === 0) continue;
|
|
319
878
|
|
|
320
879
|
const itemList = items.slice(0, 4).join(', ');
|
|
@@ -343,6 +902,10 @@ function printSetupStep(step, status, detail = null) {
|
|
|
343
902
|
let icon, color;
|
|
344
903
|
|
|
345
904
|
switch (status) {
|
|
905
|
+
case 'dry-run':
|
|
906
|
+
icon = ICONS.info;
|
|
907
|
+
color = colors.warning;
|
|
908
|
+
break;
|
|
346
909
|
case 'creating':
|
|
347
910
|
icon = ICONS.create;
|
|
348
911
|
color = colors.accent;
|
|
@@ -550,6 +1113,11 @@ function printHelp() {
|
|
|
550
1113
|
${c.bold}Basic Options:${c.reset}
|
|
551
1114
|
${colors.accent}--path, -p <dir>${c.reset} Project path ${c.dim}(default: current directory)${c.reset}
|
|
552
1115
|
${colors.accent}--quick, -q${c.reset} Skip wizard, use defaults
|
|
1116
|
+
${colors.accent}--wizard, -i${c.reset} Interactive setup wizard
|
|
1117
|
+
${colors.accent}--dry-run${c.reset} Preview changes without applying
|
|
1118
|
+
${colors.accent}--repair${c.reset} Fix partial init state
|
|
1119
|
+
${colors.accent}--verify${c.reset} Verify setup after init
|
|
1120
|
+
${colors.accent}--verbose, -v${c.reset} Show detailed output
|
|
553
1121
|
${colors.accent}--git-hooks${c.reset} Install git pre-push hook
|
|
554
1122
|
${colors.accent}--json${c.reset} Output results as JSON
|
|
555
1123
|
${colors.accent}--help, -h${c.reset} Show this help
|
|
@@ -614,9 +1182,16 @@ function parseArgs(args) {
|
|
|
614
1182
|
help: false,
|
|
615
1183
|
quick: false,
|
|
616
1184
|
json: false,
|
|
1185
|
+
verbose: false,
|
|
617
1186
|
// Mode flags (--local is default, --connect requires STARTER)
|
|
618
1187
|
local: false,
|
|
619
1188
|
connect: false,
|
|
1189
|
+
// Enhancement flags
|
|
1190
|
+
wizard: false,
|
|
1191
|
+
interactive: false,
|
|
1192
|
+
dryRun: false,
|
|
1193
|
+
repair: false,
|
|
1194
|
+
verify: false,
|
|
620
1195
|
// Enterprise options
|
|
621
1196
|
enterprise: false,
|
|
622
1197
|
ci: false,
|
|
@@ -636,6 +1211,16 @@ function parseArgs(args) {
|
|
|
636
1211
|
if (a === "--help" || a === "-h") out.help = true;
|
|
637
1212
|
if (a === "--quick" || a === "-q") out.quick = true;
|
|
638
1213
|
if (a === "--json") out.json = true;
|
|
1214
|
+
if (a === "--verbose" || a === "-v") out.verbose = true;
|
|
1215
|
+
if (a === "--verify") out.verify = true;
|
|
1216
|
+
|
|
1217
|
+
// Enhancement flags
|
|
1218
|
+
if (a === "--wizard" || a === "--interactive" || a === "-i") {
|
|
1219
|
+
out.wizard = true;
|
|
1220
|
+
out.interactive = true;
|
|
1221
|
+
}
|
|
1222
|
+
if (a === "--dry-run" || a === "--dryrun") out.dryRun = true;
|
|
1223
|
+
if (a === "--repair") out.repair = true;
|
|
639
1224
|
|
|
640
1225
|
// Mode flags
|
|
641
1226
|
if (a === "--local" || a === "-l") out.local = true;
|
|
@@ -699,9 +1284,41 @@ async function runLocalSetup(targetDir, projectName, opts, filesCreated) {
|
|
|
699
1284
|
printSection('LOCAL SETUP', ICONS.setup);
|
|
700
1285
|
console.log();
|
|
701
1286
|
|
|
1287
|
+
const totalSteps = 7; // Approximate number of major steps
|
|
1288
|
+
let currentStep = 0;
|
|
1289
|
+
|
|
1290
|
+
function logStep(stepName) {
|
|
1291
|
+
currentStep++;
|
|
1292
|
+
if (opts.verbose && !opts.json) {
|
|
1293
|
+
console.log(` ${c.dim}[${currentStep}/${totalSteps}]${c.reset} ${stepName}`);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// Detect framework for framework-specific templates
|
|
1298
|
+
let detection = null;
|
|
1299
|
+
if (!opts.dryRun) {
|
|
1300
|
+
try {
|
|
1301
|
+
const { detectAll } = require("./lib/enterprise-detect");
|
|
1302
|
+
detection = detectAll(targetDir);
|
|
1303
|
+
if (!opts.json) {
|
|
1304
|
+
printEnhancedDetection(detection, opts.verbose);
|
|
1305
|
+
}
|
|
1306
|
+
} catch (e) {
|
|
1307
|
+
if (!opts.json) {
|
|
1308
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} Detection failed: ${e.message}`);
|
|
1309
|
+
if (opts.verbose) {
|
|
1310
|
+
console.log(` ${c.dim}Stack: ${e.stack}${c.reset}`);
|
|
1311
|
+
}
|
|
1312
|
+
console.log(` ${c.dim}Using default configuration${c.reset}`);
|
|
1313
|
+
console.log();
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
702
1318
|
const vibecheckDir = path.join(targetDir, ".vibecheck");
|
|
703
1319
|
|
|
704
1320
|
// 1. Create .vibecheck/ directory structure
|
|
1321
|
+
logStep('Creating directory structure');
|
|
705
1322
|
const dirs = [
|
|
706
1323
|
vibecheckDir,
|
|
707
1324
|
path.join(vibecheckDir, "results"),
|
|
@@ -714,50 +1331,82 @@ async function runLocalSetup(targetDir, projectName, opts, filesCreated) {
|
|
|
714
1331
|
|
|
715
1332
|
for (const dir of dirs) {
|
|
716
1333
|
if (!fs.existsSync(dir)) {
|
|
717
|
-
|
|
1334
|
+
if (opts.dryRun) {
|
|
1335
|
+
printSetupStep(path.relative(targetDir, dir), 'dry-run', 'would create');
|
|
1336
|
+
} else {
|
|
1337
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1338
|
+
}
|
|
718
1339
|
}
|
|
719
1340
|
}
|
|
720
|
-
|
|
721
|
-
|
|
1341
|
+
if (!opts.dryRun) {
|
|
1342
|
+
printSetupStep('.vibecheck/', 'success', 'directory structure created');
|
|
1343
|
+
filesCreated.push('.vibecheck/');
|
|
1344
|
+
}
|
|
722
1345
|
|
|
723
|
-
// 2. Create config.json
|
|
1346
|
+
// 2. Create config.json with framework-specific settings
|
|
1347
|
+
logStep('Creating configuration');
|
|
724
1348
|
const configPath = path.join(vibecheckDir, "config.json");
|
|
725
|
-
if (!fs.existsSync(configPath)) {
|
|
1349
|
+
if (!fs.existsSync(configPath) || opts.dryRun) {
|
|
1350
|
+
const frameworkConfig = getFrameworkConfig(detection);
|
|
726
1351
|
const config = {
|
|
727
1352
|
version: "2.0.0",
|
|
728
1353
|
project: projectName,
|
|
729
1354
|
createdAt: new Date().toISOString(),
|
|
730
|
-
|
|
1355
|
+
framework: frameworkConfig.framework || "node",
|
|
1356
|
+
checks: frameworkConfig.checks || ["routes", "env", "auth", "security"],
|
|
731
1357
|
output: ".vibecheck",
|
|
732
1358
|
policy: {
|
|
733
1359
|
allowlist: { domains: [], packages: [] },
|
|
734
|
-
ignore: {
|
|
1360
|
+
ignore: {
|
|
1361
|
+
paths: [
|
|
1362
|
+
"node_modules",
|
|
1363
|
+
"__tests__",
|
|
1364
|
+
"*.test.*",
|
|
1365
|
+
"*.spec.*",
|
|
1366
|
+
...(frameworkConfig.ignorePaths || [])
|
|
1367
|
+
]
|
|
1368
|
+
},
|
|
735
1369
|
},
|
|
736
1370
|
thresholds: {
|
|
737
1371
|
minScore: 70,
|
|
738
1372
|
maxBlockers: 0,
|
|
739
1373
|
maxWarnings: 10,
|
|
740
1374
|
},
|
|
1375
|
+
...(frameworkConfig.extra || {}),
|
|
741
1376
|
};
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
1377
|
+
|
|
1378
|
+
if (opts.dryRun) {
|
|
1379
|
+
printSetupStep('config.json', 'dry-run', `would create (framework: ${config.framework})`);
|
|
1380
|
+
console.log(` ${c.dim}Config preview:${c.reset}`);
|
|
1381
|
+
console.log(JSON.stringify(config, null, 2));
|
|
1382
|
+
} else {
|
|
1383
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
1384
|
+
printSetupStep('config.json', 'success', `configuration created (framework: ${config.framework})`);
|
|
1385
|
+
filesCreated.push('.vibecheck/config.json');
|
|
1386
|
+
}
|
|
745
1387
|
} else {
|
|
746
1388
|
printSetupStep('config.json', 'exists', 'already exists');
|
|
747
1389
|
}
|
|
748
1390
|
|
|
749
1391
|
// 3. Generate truthpack
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1392
|
+
logStep('Generating truthpack');
|
|
1393
|
+
if (!opts.dryRun) {
|
|
1394
|
+
startSpinner('Building truthpack...', colors.accent);
|
|
1395
|
+
try {
|
|
1396
|
+
const { runCtx } = require("./runCtx");
|
|
1397
|
+
await runCtx(["build", "--path", targetDir, "--quiet"]);
|
|
1398
|
+
stopSpinner('Truthpack generated', true);
|
|
1399
|
+
filesCreated.push('.vibecheck/truthpack.json');
|
|
1400
|
+
} catch (e) {
|
|
1401
|
+
stopSpinner('Truthpack generation failed', false);
|
|
1402
|
+
throw new Error(`Truthpack generation failed: ${e.message}. Run 'vibecheck ctx build' manually or 'vibecheck init --repair' to retry.`);
|
|
1403
|
+
}
|
|
1404
|
+
} else {
|
|
1405
|
+
printSetupStep('truthpack.json', 'dry-run', 'would generate via vibecheck ctx build');
|
|
758
1406
|
}
|
|
759
1407
|
|
|
760
1408
|
// 4. Generate contracts
|
|
1409
|
+
logStep('Creating contracts');
|
|
761
1410
|
const contractsDir = path.join(vibecheckDir, "contracts");
|
|
762
1411
|
|
|
763
1412
|
// Routes contract
|
|
@@ -769,9 +1418,13 @@ async function runLocalSetup(targetDir, projectName, opts, filesCreated) {
|
|
|
769
1418
|
routes: [],
|
|
770
1419
|
description: "Route contract - populated by scan",
|
|
771
1420
|
};
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1421
|
+
if (opts.dryRun) {
|
|
1422
|
+
printSetupStep('routes.contract.json', 'dry-run', 'would create');
|
|
1423
|
+
} else {
|
|
1424
|
+
fs.writeFileSync(routeContractPath, JSON.stringify(routeContract, null, 2));
|
|
1425
|
+
printSetupStep('routes.contract.json', 'success', 'route contract created');
|
|
1426
|
+
filesCreated.push('.vibecheck/contracts/routes.contract.json');
|
|
1427
|
+
}
|
|
775
1428
|
}
|
|
776
1429
|
|
|
777
1430
|
// Env contract
|
|
@@ -784,35 +1437,55 @@ async function runLocalSetup(targetDir, projectName, opts, filesCreated) {
|
|
|
784
1437
|
optional: [],
|
|
785
1438
|
description: "Environment contract - populated by scan",
|
|
786
1439
|
};
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1440
|
+
if (opts.dryRun) {
|
|
1441
|
+
printSetupStep('env.contract.json', 'dry-run', 'would create');
|
|
1442
|
+
} else {
|
|
1443
|
+
fs.writeFileSync(envContractPath, JSON.stringify(envContract, null, 2));
|
|
1444
|
+
printSetupStep('env.contract.json', 'success', 'env contract created');
|
|
1445
|
+
filesCreated.push('.vibecheck/contracts/env.contract.json');
|
|
1446
|
+
}
|
|
790
1447
|
}
|
|
791
1448
|
|
|
792
1449
|
// 5. Generate IDE rules (MDC for Cursor, rules for others)
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1450
|
+
logStep('Generating IDE rules');
|
|
1451
|
+
if (!opts.dryRun) {
|
|
1452
|
+
startSpinner('Generating IDE rules...', colors.accent);
|
|
1453
|
+
try {
|
|
1454
|
+
const { runContext } = require("./runContext");
|
|
1455
|
+
await runContext(["--path", targetDir, "--quiet"]);
|
|
1456
|
+
stopSpinner('IDE rules generated', true);
|
|
1457
|
+
filesCreated.push('.cursor/rules/', '.windsurf/', '.github/copilot-instructions.md');
|
|
1458
|
+
} catch (e) {
|
|
1459
|
+
stopSpinner('IDE rules generation failed', false);
|
|
1460
|
+
if (!opts.json) {
|
|
1461
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} ${e.message}`);
|
|
1462
|
+
console.log(` ${c.dim}Run 'vibecheck context' manually to generate IDE rules${c.reset}`);
|
|
1463
|
+
}
|
|
1464
|
+
// Don't throw - IDE rules are optional
|
|
1465
|
+
}
|
|
1466
|
+
} else {
|
|
1467
|
+
printSetupStep('IDE rules', 'dry-run', 'would generate via vibecheck context');
|
|
801
1468
|
}
|
|
802
1469
|
|
|
803
1470
|
// 6. Create .vibecheckrc at project root
|
|
1471
|
+
logStep('Creating root config');
|
|
804
1472
|
const rcPath = path.join(targetDir, ".vibecheckrc.json");
|
|
805
1473
|
if (!fs.existsSync(rcPath)) {
|
|
806
1474
|
const rc = {
|
|
807
1475
|
extends: ".vibecheck/config.json",
|
|
808
1476
|
tier: "free",
|
|
809
1477
|
};
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1478
|
+
if (opts.dryRun) {
|
|
1479
|
+
printSetupStep('.vibecheckrc.json', 'dry-run', 'would create');
|
|
1480
|
+
} else {
|
|
1481
|
+
fs.writeFileSync(rcPath, JSON.stringify(rc, null, 2));
|
|
1482
|
+
printSetupStep('.vibecheckrc.json', 'success', 'root config created');
|
|
1483
|
+
filesCreated.push('.vibecheckrc.json');
|
|
1484
|
+
}
|
|
813
1485
|
}
|
|
814
1486
|
|
|
815
1487
|
// 7. Update .gitignore
|
|
1488
|
+
logStep('Updating .gitignore');
|
|
816
1489
|
const gitignorePath = path.join(targetDir, ".gitignore");
|
|
817
1490
|
if (fs.existsSync(gitignorePath)) {
|
|
818
1491
|
let gitignore = fs.readFileSync(gitignorePath, "utf-8");
|
|
@@ -822,9 +1495,13 @@ async function runLocalSetup(targetDir, projectName, opts, filesCreated) {
|
|
|
822
1495
|
if (!gitignore.includes(".vibecheck/checkpoints/")) additions.push(".vibecheck/checkpoints/");
|
|
823
1496
|
|
|
824
1497
|
if (additions.length > 0) {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1498
|
+
if (opts.dryRun) {
|
|
1499
|
+
printSetupStep('.gitignore', 'dry-run', `would add: ${additions.join(', ')}`);
|
|
1500
|
+
} else {
|
|
1501
|
+
gitignore += `\n# vibecheck (generated outputs)\n${additions.join("\n")}\n`;
|
|
1502
|
+
fs.writeFileSync(gitignorePath, gitignore);
|
|
1503
|
+
printSetupStep('.gitignore', 'success', 'updated with vibecheck paths');
|
|
1504
|
+
}
|
|
828
1505
|
}
|
|
829
1506
|
}
|
|
830
1507
|
|
|
@@ -937,18 +1614,22 @@ async function runInit(args) {
|
|
|
937
1614
|
|
|
938
1615
|
const targetDir = path.resolve(opts.path);
|
|
939
1616
|
const projectName = path.basename(targetDir);
|
|
1617
|
+
let bannerPrinted = false;
|
|
940
1618
|
|
|
941
1619
|
// Enterprise mode - comprehensive setup
|
|
942
1620
|
const useEnterprise = opts.enterprise || opts.ci || opts.compliance ||
|
|
943
1621
|
opts.team || opts.mcp || opts.detect;
|
|
944
1622
|
|
|
945
1623
|
if (EnterpriseInit && useEnterprise && !opts.local && !opts.connect) {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1624
|
+
if (!opts.json) {
|
|
1625
|
+
printBanner();
|
|
1626
|
+
bannerPrinted = true;
|
|
1627
|
+
printEnterpriseHeader(opts);
|
|
1628
|
+
|
|
1629
|
+
console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
|
|
1630
|
+
console.log(` ${c.dim}Path:${c.reset} ${targetDir}`);
|
|
1631
|
+
console.log();
|
|
1632
|
+
}
|
|
952
1633
|
|
|
953
1634
|
const enterprise = new EnterpriseInit(targetDir, opts);
|
|
954
1635
|
return await enterprise.run();
|
|
@@ -956,10 +1637,12 @@ async function runInit(args) {
|
|
|
956
1637
|
|
|
957
1638
|
// Detect-only mode (quick detection report)
|
|
958
1639
|
if (opts.detect) {
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1640
|
+
if (!opts.json) {
|
|
1641
|
+
printBanner();
|
|
1642
|
+
bannerPrinted = true;
|
|
1643
|
+
console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
|
|
1644
|
+
console.log(` ${c.dim}Path:${c.reset} ${targetDir}`);
|
|
1645
|
+
}
|
|
963
1646
|
|
|
964
1647
|
startSpinner('Detecting project configuration...', colors.accent);
|
|
965
1648
|
|
|
@@ -967,9 +1650,11 @@ async function runInit(args) {
|
|
|
967
1650
|
const { detectAll } = require("./lib/enterprise-detect");
|
|
968
1651
|
const detection = detectAll(targetDir);
|
|
969
1652
|
stopSpinner('Detection complete', true);
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1653
|
+
if (!opts.json) {
|
|
1654
|
+
printDetectionResults(detection);
|
|
1655
|
+
console.log();
|
|
1656
|
+
console.log(` ${c.dim}Full JSON output:${c.reset}`);
|
|
1657
|
+
}
|
|
973
1658
|
console.log(JSON.stringify(detection, null, 2));
|
|
974
1659
|
} catch (e) {
|
|
975
1660
|
stopSpinner('Detection failed', false);
|
|
@@ -982,8 +1667,32 @@ async function runInit(args) {
|
|
|
982
1667
|
// NEW: --local and --connect modes
|
|
983
1668
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
984
1669
|
|
|
985
|
-
|
|
1670
|
+
// Interactive wizard mode
|
|
1671
|
+
if (opts.wizard && InitWizard) {
|
|
1672
|
+
if (!opts.json && !bannerPrinted) {
|
|
1673
|
+
printBanner();
|
|
1674
|
+
}
|
|
1675
|
+
const wizard = new InitWizard(targetDir, opts);
|
|
1676
|
+
return await wizard.run();
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// Repair mode - fix partial state
|
|
1680
|
+
if (opts.repair) {
|
|
1681
|
+
if (!opts.json && !bannerPrinted) {
|
|
1682
|
+
printBanner();
|
|
1683
|
+
bannerPrinted = true;
|
|
1684
|
+
console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
|
|
1685
|
+
console.log(` ${c.dim}Path:${c.reset} ${targetDir}`);
|
|
1686
|
+
console.log(` ${c.dim}Mode:${c.reset} ${colors.accent}Repair${c.reset}`);
|
|
1687
|
+
console.log();
|
|
1688
|
+
}
|
|
1689
|
+
return await runRepairMode(targetDir, projectName, opts);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
// Print banner only once (not already printed in enterprise/detect modes)
|
|
1693
|
+
if (!opts.json && !bannerPrinted) {
|
|
986
1694
|
printBanner();
|
|
1695
|
+
bannerPrinted = true;
|
|
987
1696
|
|
|
988
1697
|
const mode = opts.connect ? 'Local + Connect' : 'Local';
|
|
989
1698
|
const tierBadge = opts.connect ? `${c.cyan}[STARTER]${c.reset}` : `${c.green}[FREE]${c.reset}`;
|
|
@@ -991,6 +1700,9 @@ async function runInit(args) {
|
|
|
991
1700
|
console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
|
|
992
1701
|
console.log(` ${c.dim}Path:${c.reset} ${targetDir}`);
|
|
993
1702
|
console.log(` ${c.dim}Mode:${c.reset} ${colors.accent}${mode}${c.reset} ${tierBadge}`);
|
|
1703
|
+
if (opts.dryRun) {
|
|
1704
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} ${c.bold}DRY-RUN MODE${c.reset} - No files will be created`);
|
|
1705
|
+
}
|
|
994
1706
|
}
|
|
995
1707
|
|
|
996
1708
|
let filesCreated = [];
|
|
@@ -1001,6 +1713,7 @@ async function runInit(args) {
|
|
|
1001
1713
|
mode: opts.connect ? 'connect' : 'local',
|
|
1002
1714
|
filesCreated: [],
|
|
1003
1715
|
errors: [],
|
|
1716
|
+
dryRun: opts.dryRun,
|
|
1004
1717
|
};
|
|
1005
1718
|
|
|
1006
1719
|
// Always run local setup first
|
|
@@ -1008,9 +1721,16 @@ async function runInit(args) {
|
|
|
1008
1721
|
try {
|
|
1009
1722
|
filesCreated = await runLocalSetup(targetDir, projectName, opts, filesCreated);
|
|
1010
1723
|
} catch (e) {
|
|
1011
|
-
result.errors.push({ step: 'local', error: e.message });
|
|
1724
|
+
result.errors.push({ step: 'local', error: e.message, stack: e.stack });
|
|
1012
1725
|
if (!opts.json) {
|
|
1013
1726
|
console.log(` ${colors.error}${ICONS.cross}${c.reset} Local setup error: ${e.message}`);
|
|
1727
|
+
if (opts.verbose) {
|
|
1728
|
+
console.log(` ${c.dim}Stack: ${e.stack}${c.reset}`);
|
|
1729
|
+
}
|
|
1730
|
+
console.log(` ${colors.info}${ICONS.info}${c.reset} Run ${c.cyan}vibecheck init --repair${c.reset} to fix partial state`);
|
|
1731
|
+
}
|
|
1732
|
+
if (!opts.dryRun) {
|
|
1733
|
+
return 1; // Exit on error unless dry-run
|
|
1014
1734
|
}
|
|
1015
1735
|
}
|
|
1016
1736
|
}
|
|
@@ -1020,9 +1740,15 @@ async function runInit(args) {
|
|
|
1020
1740
|
try {
|
|
1021
1741
|
filesCreated = await runConnectSetup(targetDir, projectName, opts, filesCreated);
|
|
1022
1742
|
} catch (e) {
|
|
1023
|
-
result.errors.push({ step: 'connect', error: e.message });
|
|
1743
|
+
result.errors.push({ step: 'connect', error: e.message, stack: e.stack });
|
|
1024
1744
|
if (!opts.json) {
|
|
1025
1745
|
console.log(` ${colors.error}${ICONS.cross}${c.reset} Connect setup error: ${e.message}`);
|
|
1746
|
+
if (opts.verbose) {
|
|
1747
|
+
console.log(` ${c.dim}Stack: ${e.stack}${c.reset}`);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (!opts.dryRun) {
|
|
1751
|
+
return 1;
|
|
1026
1752
|
}
|
|
1027
1753
|
}
|
|
1028
1754
|
}
|
|
@@ -1063,6 +1789,31 @@ fi
|
|
|
1063
1789
|
const mode = opts.connect ? 'Local + Connect' : 'Local Setup';
|
|
1064
1790
|
printSuccessCard(projectName, mode, filesCreated.length);
|
|
1065
1791
|
|
|
1792
|
+
// Generate dependency graph
|
|
1793
|
+
if (!opts.dryRun) {
|
|
1794
|
+
if (!opts.json) {
|
|
1795
|
+
startSpinner('Generating dependency graph...', colors.accent);
|
|
1796
|
+
}
|
|
1797
|
+
try {
|
|
1798
|
+
const { buildDependencyGraph, generateHtmlVisualization } = require("./context/dependency-graph");
|
|
1799
|
+
const depGraph = buildDependencyGraph(targetDir);
|
|
1800
|
+
const htmlViz = generateHtmlVisualization(depGraph, { maxNodes: 200 });
|
|
1801
|
+
const graphPath = path.join(targetDir, ".vibecheck", "dependency-graph.html");
|
|
1802
|
+
fs.writeFileSync(graphPath, htmlViz);
|
|
1803
|
+
filesCreated.push('.vibecheck/dependency-graph.html');
|
|
1804
|
+
if (!opts.json) {
|
|
1805
|
+
stopSpinner('Dependency graph generated', true);
|
|
1806
|
+
}
|
|
1807
|
+
} catch (e) {
|
|
1808
|
+
if (!opts.json) {
|
|
1809
|
+
stopSpinner('Dependency graph skipped', false);
|
|
1810
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} ${e.message}`);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
} else {
|
|
1814
|
+
printSetupStep('dependency-graph.html', 'dry-run', 'would generate');
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1066
1817
|
// Init summary
|
|
1067
1818
|
printSection('INIT SUMMARY', ICONS.check);
|
|
1068
1819
|
console.log();
|
|
@@ -1070,11 +1821,52 @@ fi
|
|
|
1070
1821
|
console.log(` ${colors.success}${ICONS.check}${c.reset} Truthpack generated`);
|
|
1071
1822
|
console.log(` ${colors.success}${ICONS.check}${c.reset} Contracts initialized`);
|
|
1072
1823
|
console.log(` ${colors.success}${ICONS.check}${c.reset} IDE rules generated`);
|
|
1824
|
+
console.log(` ${colors.success}${ICONS.check}${c.reset} Dependency graph generated`);
|
|
1073
1825
|
if (opts.connect) {
|
|
1074
1826
|
console.log(` ${colors.success}${ICONS.check}${c.reset} GitHub Actions workflows installed`);
|
|
1075
1827
|
}
|
|
1076
1828
|
console.log();
|
|
1077
1829
|
|
|
1830
|
+
// Show dependency graph location
|
|
1831
|
+
const graphPath = path.join(targetDir, ".vibecheck", "dependency-graph.html");
|
|
1832
|
+
if (fs.existsSync(graphPath)) {
|
|
1833
|
+
console.log(` ${colors.info}${ICONS.info}${c.reset} ${c.bold}Dependency Graph:${c.reset}`);
|
|
1834
|
+
console.log(` ${c.cyan}file://${graphPath.replace(/\\/g, '/')}${c.reset}`);
|
|
1835
|
+
console.log();
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// Save init state for repair mode
|
|
1839
|
+
if (!opts.dryRun) {
|
|
1840
|
+
saveInitState(targetDir, {
|
|
1841
|
+
timestamp: new Date().toISOString(),
|
|
1842
|
+
mode: opts.connect ? 'connect' : 'local',
|
|
1843
|
+
filesCreated,
|
|
1844
|
+
errors: result.errors,
|
|
1845
|
+
detection: detection ? {
|
|
1846
|
+
frameworks: Object.keys(detection.frameworks || {}).filter(k => detection.frameworks[k].detected),
|
|
1847
|
+
checks: detection.checks || [],
|
|
1848
|
+
} : null,
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
// Post-init validation
|
|
1853
|
+
if (opts.verify && !opts.dryRun) {
|
|
1854
|
+
const validationResult = await verifyInitSetup(targetDir, opts);
|
|
1855
|
+
if (!validationResult.success && !opts.json) {
|
|
1856
|
+
console.log();
|
|
1857
|
+
console.log(` ${colors.warning}${ICONS.warning}${c.reset} ${c.bold}Validation Issues Found:${c.reset}`);
|
|
1858
|
+
for (const issue of validationResult.issues) {
|
|
1859
|
+
console.log(` ${c.dim}•${c.reset} ${issue}`);
|
|
1860
|
+
}
|
|
1861
|
+
console.log();
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
// Framework-specific recommendations
|
|
1866
|
+
if (detection && !opts.json && !opts.dryRun) {
|
|
1867
|
+
printFrameworkRecommendations(detection, opts);
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1078
1870
|
// Next steps
|
|
1079
1871
|
printNextSteps({ hasCI: opts.connect, mode: opts.connect ? 'connect' : 'local' });
|
|
1080
1872
|
|