@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.
Files changed (36) hide show
  1. package/bin/registry.js +106 -116
  2. package/bin/runners/context/generators/mcp.js +18 -0
  3. package/bin/runners/context/index.js +72 -4
  4. package/bin/runners/context/proof-context.js +293 -1
  5. package/bin/runners/context/security-scanner.js +311 -73
  6. package/bin/runners/lib/analyzers.js +607 -20
  7. package/bin/runners/lib/detectors-v2.js +172 -15
  8. package/bin/runners/lib/entitlements-v2.js +48 -1
  9. package/bin/runners/lib/evidence-pack.js +678 -0
  10. package/bin/runners/lib/html-proof-report.js +913 -0
  11. package/bin/runners/lib/missions/plan.js +231 -41
  12. package/bin/runners/lib/missions/templates.js +125 -0
  13. package/bin/runners/lib/scan-output.js +492 -253
  14. package/bin/runners/lib/ship-output.js +901 -641
  15. package/bin/runners/runCheckpoint.js +44 -3
  16. package/bin/runners/runContext.d.ts +4 -0
  17. package/bin/runners/runDoctor.js +10 -2
  18. package/bin/runners/runFix.js +51 -341
  19. package/bin/runners/runInit.js +11 -0
  20. package/bin/runners/runPolish.d.ts +4 -0
  21. package/bin/runners/runPolish.js +608 -29
  22. package/bin/runners/runProve.js +210 -25
  23. package/bin/runners/runReality.js +846 -101
  24. package/bin/runners/runScan.js +238 -4
  25. package/bin/runners/runShip.js +19 -3
  26. package/bin/runners/runWatch.js +14 -1
  27. package/bin/vibecheck.js +32 -2
  28. package/mcp-server/consolidated-tools.js +408 -42
  29. package/mcp-server/index.js +152 -15
  30. package/mcp-server/proof-tools.js +571 -0
  31. package/mcp-server/tier-auth.js +22 -19
  32. package/mcp-server/tools-v3.js +744 -0
  33. package/mcp-server/truth-firewall-tools.js +190 -4
  34. package/package.json +3 -1
  35. package/bin/runners/runInstall.js +0 -281
  36. package/bin/runners/runLabs.js +0 -341
@@ -398,7 +398,7 @@ export async function handleTruthFirewallTool(toolName, args, projectPath = proc
398
398
  return await addAssumption(projectPath, args);
399
399
 
400
400
  case "vibecheck.validate_plan":
401
- return await validatePlanTool(projectPath, args);
401
+ return await getPlanValidationResult(projectPath, args);
402
402
 
403
403
  case "vibecheck.check_drift":
404
404
  return await checkDriftTool(projectPath, args);
@@ -421,8 +421,147 @@ const state = {
421
421
  assumptions: [],
422
422
  verifiedClaims: new Map(),
423
423
  maxAssumptions: 2,
424
+ lastValidationByProject: new Map(),
424
425
  };
425
426
 
427
+ const MAX_EVIDENCE_SNIPPET = 200;
428
+
429
+ /**
430
+ * Policy configuration - aligned with proof-context.js TRUTH_CONTRACT
431
+ */
432
+ const POLICY_CONFIG = {
433
+ strict: {
434
+ minConfidence: 0.8,
435
+ allowUnknown: false,
436
+ requireValidation: true,
437
+ blockOnDrift: true,
438
+ validationTTL: 5 * 60 * 1000, // 5 minutes
439
+ },
440
+ balanced: {
441
+ minConfidence: 0.6,
442
+ allowUnknown: false,
443
+ requireValidation: true,
444
+ blockOnDrift: false,
445
+ validationTTL: 10 * 60 * 1000, // 10 minutes
446
+ },
447
+ permissive: {
448
+ minConfidence: 0.4,
449
+ allowUnknown: true,
450
+ requireValidation: false,
451
+ blockOnDrift: false,
452
+ validationTTL: 30 * 60 * 1000, // 30 minutes
453
+ },
454
+ };
455
+
456
+ /**
457
+ * Get policy configuration
458
+ */
459
+ export function getPolicyConfig(policy = 'strict') {
460
+ return POLICY_CONFIG[policy] || POLICY_CONFIG.strict;
461
+ }
462
+
463
+ function confidenceToScore(confidence) {
464
+ if (typeof confidence === "number") return confidence;
465
+ switch (confidence) {
466
+ case "high":
467
+ return 0.9;
468
+ case "medium":
469
+ return 0.7;
470
+ case "low":
471
+ return 0.5;
472
+ default:
473
+ return 0.6;
474
+ }
475
+ }
476
+
477
+ async function readSnippet(projectPath, file, line) {
478
+ if (!file) return "";
479
+ try {
480
+ const content = await fs.readFile(path.join(projectPath, file), "utf8");
481
+ const lines = content.split("\n");
482
+ const idx = Math.max(0, Math.min(lines.length - 1, line - 1));
483
+ return (lines[idx] || "").slice(0, MAX_EVIDENCE_SNIPPET);
484
+ } catch {
485
+ return "";
486
+ }
487
+ }
488
+
489
+ async function normalizeEvidence(projectPath, evidence, fallback, confidence) {
490
+ const raw = Array.isArray(evidence) ? evidence : evidence ? [evidence] : [];
491
+ const normalized = [];
492
+
493
+ for (const item of raw) {
494
+ const file = item?.file || fallback?.file || "";
495
+ const line = Number(item?.line || item?.lines || fallback?.line || 1);
496
+ const snippet =
497
+ item?.snippet ||
498
+ item?.evidence ||
499
+ (await readSnippet(projectPath, file, line));
500
+
501
+ normalized.push({
502
+ file,
503
+ line,
504
+ snippet,
505
+ confidence: item?.confidence ?? confidenceToScore(confidence),
506
+ });
507
+ }
508
+
509
+ if (normalized.length === 0 && fallback?.file) {
510
+ normalized.push({
511
+ file: fallback.file,
512
+ line: fallback.line || 1,
513
+ snippet: await readSnippet(projectPath, fallback.file, fallback.line || 1),
514
+ confidence: confidenceToScore(confidence),
515
+ });
516
+ }
517
+
518
+ return normalized;
519
+ }
520
+
521
+ /**
522
+ * Check if there's a recent claim validation for the project.
523
+ * The TTL depends on the policy mode.
524
+ */
525
+ export function hasRecentClaimValidation(projectPath, policy = 'strict') {
526
+ const last = state.lastValidationByProject.get(projectPath);
527
+ if (typeof last !== "number") return false;
528
+
529
+ const config = getPolicyConfig(policy);
530
+ const maxAgeMs = config.validationTTL;
531
+ return Date.now() - last <= maxAgeMs;
532
+ }
533
+
534
+ /**
535
+ * Validate a claim result against policy thresholds.
536
+ * Returns an enforcement decision.
537
+ */
538
+ export function enforceClaimResult(result, policy = 'strict') {
539
+ const config = getPolicyConfig(policy);
540
+ const confidence = confidenceToScore(result.confidence || result.result === 'true' ? 0.9 : 0.3);
541
+
542
+ // Unknown results
543
+ if (result.result === 'unknown') {
544
+ if (!config.allowUnknown) {
545
+ return {
546
+ allowed: false,
547
+ reason: `Unknown claims are not allowed in ${policy} mode`,
548
+ suggestion: 'Use search_evidence to find proof or get_truthpack to refresh context',
549
+ };
550
+ }
551
+ }
552
+
553
+ // Low confidence
554
+ if (confidence < config.minConfidence) {
555
+ return {
556
+ allowed: false,
557
+ reason: `Confidence ${(confidence * 100).toFixed(0)}% below ${policy} threshold ${(config.minConfidence * 100).toFixed(0)}%`,
558
+ suggestion: 'Find additional evidence or use permissive policy',
559
+ };
560
+ }
561
+
562
+ return { allowed: true };
563
+ }
564
+
426
565
  async function getTruthPack(projectPath, args) {
427
566
  const scope = args.scope || 'all';
428
567
  const refresh = args.refresh || false;
@@ -439,6 +578,7 @@ async function getTruthPack(projectPath, args) {
439
578
  commitHash: getCommitHash(projectPath),
440
579
  sections: {},
441
580
  confidence: 0,
581
+ _attribution: CONTEXT_ATTRIBUTION,
442
582
  };
443
583
 
444
584
  if (scope === 'all' || scope === 'routes') {
@@ -510,22 +650,45 @@ async function validateClaim(projectPath, args) {
510
650
  result.nextSteps = [`Verification error: ${error.message}`];
511
651
  }
512
652
 
513
- // If unknown, add helpful next steps
653
+ // ENFORCED: Unknown claims must return explicit "unknown" error
654
+ // that blocks dependent actions in strict/balanced modes
514
655
  if (result.result === 'unknown') {
515
656
  result.nextSteps.push(
516
657
  'call vibecheck.search_evidence to find related code',
517
658
  'call vibecheck.get_truthpack to get full context',
518
659
  );
519
660
  result.warning = '⚠️ UNKNOWN claims BLOCK dependent actions. Verify before proceeding.';
661
+ result.enforcement = {
662
+ allowed: false,
663
+ reason: 'Claim result is unknown - cannot proceed without evidence',
664
+ blockedActions: ['fix', 'autopilot_apply', 'propose_patch'],
665
+ };
666
+ } else if (result.result === 'true') {
667
+ result.enforcement = {
668
+ allowed: true,
669
+ confidence: confidenceToScore(result.confidence),
670
+ };
671
+ } else {
672
+ // result is 'false'
673
+ result.enforcement = {
674
+ allowed: false,
675
+ reason: 'Claim is disproven - do not proceed with dependent actions',
676
+ };
520
677
  }
521
678
 
522
679
  // Cache result
523
- state.verifiedClaims.set(claimId, { result, timestamp: Date.now() });
680
+ state.verifiedClaims.set(claimId, { result, timestamp: Date.now(), projectPath });
681
+ state.lastValidationByProject.set(projectPath, Date.now());
524
682
 
525
683
  return {
526
684
  claimId,
527
685
  ...result,
686
+ evidence: await normalizeEvidence(projectPath, result.evidence, {
687
+ file: subject?.path || subject?.name,
688
+ line: 1,
689
+ }, result.confidence),
528
690
  timestamp: new Date().toISOString(),
691
+ _attribution: CONTEXT_ATTRIBUTION,
529
692
  };
530
693
  }
531
694
 
@@ -565,6 +728,7 @@ async function compileContext(projectPath, args) {
565
728
  invariants,
566
729
  tokenCount,
567
730
  warnings: generateContextWarnings(domains, policy, relevantRoutes.length),
731
+ _attribution: CONTEXT_ATTRIBUTION,
568
732
  };
569
733
  }
570
734
 
@@ -589,6 +753,7 @@ async function searchEvidence(projectPath, args) {
589
753
  line: i + 1,
590
754
  snippet: snippet.slice(0, 300),
591
755
  hash: crypto.createHash('sha256').update(lines[i]).digest('hex').slice(0, 16),
756
+ confidence: 0.6,
592
757
  });
593
758
 
594
759
  if (results.length >= limit) break;
@@ -604,6 +769,7 @@ async function searchEvidence(projectPath, args) {
604
769
  query,
605
770
  count: results.length,
606
771
  results,
772
+ _attribution: CONTEXT_ATTRIBUTION,
607
773
  };
608
774
  }
609
775
 
@@ -780,7 +946,7 @@ async function addAssumption(projectPath, args) {
780
946
  // PLAN VALIDATION & DRIFT DETECTION (Spec 10.3)
781
947
  // =============================================================================
782
948
 
783
- async function validatePlanTool(projectPath, args) {
949
+ async function getPlanValidationResult(projectPath, args) {
784
950
  const { plan, strict = false } = args;
785
951
 
786
952
  // Load contracts
@@ -1224,6 +1390,11 @@ export function getProjectFingerprint(projectPath) {
1224
1390
  };
1225
1391
  }
1226
1392
 
1393
+ /**
1394
+ * Context attribution message shown when AI uses vibecheck data
1395
+ */
1396
+ const CONTEXT_ATTRIBUTION = "🧠 Context enhanced by vibecheck";
1397
+
1227
1398
  /**
1228
1399
  * Wrap MCP response with standard metadata including fingerprint (Spec 10.2)
1229
1400
  */
@@ -1233,9 +1404,17 @@ export function wrapMcpResponse(data, projectPath) {
1233
1404
  version: '2.0.0',
1234
1405
  projectFingerprint: getProjectFingerprint(projectPath),
1235
1406
  data,
1407
+ _attribution: CONTEXT_ATTRIBUTION,
1236
1408
  };
1237
1409
  }
1238
1410
 
1411
+ /**
1412
+ * Get the context attribution message
1413
+ */
1414
+ export function getContextAttribution() {
1415
+ return CONTEXT_ATTRIBUTION;
1416
+ }
1417
+
1239
1418
  async function extractRoutes(projectPath) {
1240
1419
  const routes = [];
1241
1420
  const files = await findSourceFiles(projectPath);
@@ -1497,4 +1676,11 @@ async function findSourceFiles(projectPath) {
1497
1676
  export default {
1498
1677
  TRUTH_FIREWALL_TOOLS,
1499
1678
  handleTruthFirewallTool,
1679
+ hasRecentClaimValidation,
1680
+ enforceClaimResult,
1681
+ getPolicyConfig,
1682
+ getProjectFingerprint,
1683
+ wrapMcpResponse,
1684
+ getContextAttribution,
1685
+ CONTEXT_ATTRIBUTION,
1500
1686
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecheckai/cli",
3
- "version": "3.1.8",
3
+ "version": "3.2.0",
4
4
  "description": "Vibecheck CLI - Ship with confidence. One verdict: SHIP | WARN | BLOCK.",
5
5
  "main": "bin/vibecheck.js",
6
6
  "bin": {
@@ -28,6 +28,8 @@
28
28
  "@babel/parser": "^7.23.0",
29
29
  "@babel/traverse": "^7.23.0",
30
30
  "@babel/types": "^7.23.0",
31
+ "@vibecheck/core": "workspace:*",
32
+ "@vibecheck/security": "workspace:*",
31
33
  "chalk": "^5.3.0",
32
34
  "commander": "^12.0.0",
33
35
  "debug": "^4.3.4",
@@ -1,281 +0,0 @@
1
- /**
2
- * vibecheck install - Zero-friction Onboarding
3
- *
4
- * ═══════════════════════════════════════════════════════════════════════════════
5
- * ENTERPRISE EDITION - World-Class Terminal Experience
6
- * ═══════════════════════════════════════════════════════════════════════════════
7
- */
8
-
9
- const fs = require("fs");
10
- const path = require("path");
11
- const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
12
- const { buildTruthpack, writeTruthpack } = require("./lib/truth");
13
- const { detectPackageManager, detectNext, detectFastify, detectFastifyEntry } = require("./lib/detect");
14
- const { readPkg, writePkg, upsertScripts } = require("./lib/pkgjson");
15
- const { writeEnvTemplateFromTruthpack } = require("./lib/env-template");
16
-
17
- // ═══════════════════════════════════════════════════════════════════════════════
18
- // ADVANCED TERMINAL - ANSI CODES & UTILITIES
19
- // ═══════════════════════════════════════════════════════════════════════════════
20
-
21
- const c = {
22
- reset: '\x1b[0m',
23
- bold: '\x1b[1m',
24
- dim: '\x1b[2m',
25
- italic: '\x1b[3m',
26
- red: '\x1b[31m',
27
- green: '\x1b[32m',
28
- yellow: '\x1b[33m',
29
- blue: '\x1b[34m',
30
- magenta: '\x1b[35m',
31
- cyan: '\x1b[36m',
32
- white: '\x1b[37m',
33
- gray: '\x1b[90m',
34
- clearLine: '\x1b[2K',
35
- hideCursor: '\x1b[?25l',
36
- showCursor: '\x1b[?25h',
37
- };
38
-
39
- const rgb = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
40
-
41
- const colors = {
42
- gradient1: rgb(255, 200, 100),
43
- gradient2: rgb(255, 180, 80),
44
- gradient3: rgb(255, 160, 60),
45
- shipGreen: rgb(0, 255, 150),
46
- warnAmber: rgb(255, 200, 0),
47
- blockRed: rgb(255, 80, 80),
48
- accent: rgb(255, 200, 100),
49
- muted: rgb(120, 120, 140),
50
- next: rgb(100, 200, 255),
51
- fastify: rgb(255, 150, 200),
52
- };
53
-
54
- // ═══════════════════════════════════════════════════════════════════════════════
55
- // PREMIUM BANNER
56
- // ═══════════════════════════════════════════════════════════════════════════════
57
-
58
- const INSTALL_BANNER = `
59
- ${rgb(255, 200, 100)} ██╗███╗ ██╗███████╗████████╗ █████╗ ██╗ ██╗ ${c.reset}
60
- ${rgb(255, 180, 80)} ██║████╗ ██║██╔════╝╚══██╔══╝██╔══██╗██║ ██║ ${c.reset}
61
- ${rgb(255, 160, 60)} ██║██╔██╗ ██║███████╗ ██║ ███████║██║ ██║ ${c.reset}
62
- ${rgb(255, 140, 40)} ██║██║╚██╗██║╚════██║ ██║ ██╔══██║██║ ██║ ${c.reset}
63
- ${rgb(255, 120, 20)} ██║██║ ╚████║███████║ ██║ ██║ ██║███████╗███████╗${c.reset}
64
- ${rgb(255, 100, 0)} ╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝${c.reset}
65
- `;
66
-
67
- const BANNER_FULL = `
68
- ${rgb(255, 200, 100)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${c.reset}
69
- ${rgb(255, 190, 90)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${c.reset}
70
- ${rgb(255, 180, 80)} ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ${c.reset}
71
- ${rgb(255, 160, 60)} ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ${c.reset}
72
- ${rgb(255, 140, 40)} ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗${c.reset}
73
- ${rgb(255, 120, 20)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${c.reset}
74
-
75
- ${c.dim} ┌─────────────────────────────────────────────────────────────────────┐${c.reset}
76
- ${c.dim} │${c.reset} ${rgb(255, 200, 100)}📥${c.reset} ${c.bold}INSTALL${c.reset} ${c.dim}•${c.reset} ${rgb(200, 200, 200)}Zero-Friction${c.reset} ${c.dim}•${c.reset} ${rgb(150, 150, 150)}Auto-Detect${c.reset} ${c.dim}•${c.reset} ${rgb(100, 100, 100)}Onboarding${c.reset} ${c.dim}│${c.reset}
77
- ${c.dim} └─────────────────────────────────────────────────────────────────────┘${c.reset}
78
- `;
79
-
80
- const ICONS = {
81
- install: '📥',
82
- package: '📦',
83
- file: '📄',
84
- check: '✓',
85
- cross: '✗',
86
- warning: '⚠',
87
- arrow: '→',
88
- bullet: '•',
89
- sparkle: '✨',
90
- folder: '📁',
91
- next: '▲',
92
- fastify: '⚡',
93
- npm: '📦',
94
- gear: '⚙️',
95
- };
96
-
97
- const SPINNER_DOTS = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
98
- let spinnerIndex = 0;
99
- let spinnerInterval = null;
100
- let spinnerStartTime = null;
101
-
102
- function formatDuration(ms) {
103
- if (ms < 1000) return `${ms}ms`;
104
- return `${(ms / 1000).toFixed(1)}s`;
105
- }
106
-
107
- function startSpinner(message) {
108
- spinnerStartTime = Date.now();
109
- process.stdout.write(c.hideCursor);
110
- spinnerInterval = setInterval(() => {
111
- const elapsed = formatDuration(Date.now() - spinnerStartTime);
112
- process.stdout.write(`\r${c.clearLine} ${colors.accent}${SPINNER_DOTS[spinnerIndex]}${c.reset} ${message} ${c.dim}${elapsed}${c.reset}`);
113
- spinnerIndex = (spinnerIndex + 1) % SPINNER_DOTS.length;
114
- }, 80);
115
- }
116
-
117
- function stopSpinner(message, success = true) {
118
- if (spinnerInterval) {
119
- clearInterval(spinnerInterval);
120
- spinnerInterval = null;
121
- }
122
- const elapsed = spinnerStartTime ? formatDuration(Date.now() - spinnerStartTime) : '';
123
- const icon = success ? `${colors.shipGreen}${ICONS.check}${c.reset}` : `${colors.blockRed}${ICONS.cross}${c.reset}`;
124
- process.stdout.write(`\r${c.clearLine} ${icon} ${message} ${c.dim}${elapsed}${c.reset}\n`);
125
- process.stdout.write(c.showCursor);
126
- spinnerStartTime = null;
127
- }
128
-
129
- function printDivider(char = '─', width = 69, color = c.dim) {
130
- console.log(`${color} ${char.repeat(width)}${c.reset}`);
131
- }
132
-
133
- function printSection(title, icon = '◆') {
134
- console.log();
135
- console.log(` ${colors.accent}${icon}${c.reset} ${c.bold}${title}${c.reset}`);
136
- printDivider();
137
- }
138
-
139
- function printHelp(opts = {}) {
140
- if (shouldShowBanner(opts)) {
141
- console.log(BANNER_FULL);
142
- }
143
- console.log(`
144
- ${c.bold}Usage:${c.reset} vibecheck install [options]
145
-
146
- ${c.bold}Zero-Friction Onboarding${c.reset} — Auto-detect and configure your project.
147
-
148
- ${c.bold}Options:${c.reset}
149
- ${colors.accent}--path, -p <dir>${c.reset} Project path ${c.dim}(default: current directory)${c.reset}
150
- ${colors.accent}--help, -h${c.reset} Show this help
151
-
152
- ${c.bold}What It Does:${c.reset}
153
- ${colors.shipGreen}1.${c.reset} Detects package manager ${c.dim}(npm, yarn, pnpm)${c.reset}
154
- ${colors.shipGreen}2.${c.reset} Detects frameworks ${c.dim}(Next.js, Fastify)${c.reset}
155
- ${colors.shipGreen}3.${c.reset} Builds initial truthpack
156
- ${colors.shipGreen}4.${c.reset} Creates .vibecheck/config.json
157
- ${colors.shipGreen}5.${c.reset} Generates env template from truthpack
158
- ${colors.shipGreen}6.${c.reset} Adds vibecheck scripts to package.json
159
-
160
- ${c.bold}Created Files:${c.reset}
161
- ${ICONS.file} ${colors.accent}.vibecheck/config.json${c.reset} ${c.dim}Local configuration${c.reset}
162
- ${ICONS.file} ${colors.accent}.vibecheck/truthpack.json${c.reset} ${c.dim}Ground truth for AI agents${c.reset}
163
- ${ICONS.file} ${colors.accent}.env.template${c.reset} ${c.dim}Env vars from truthpack${c.reset}
164
-
165
- ${c.bold}Examples:${c.reset}
166
- ${c.dim}# Install in current directory${c.reset}
167
- vibecheck install
168
-
169
- ${c.dim}# Install in specific directory${c.reset}
170
- vibecheck install --path ./my-app
171
- `);
172
- }
173
-
174
- function ensureDir(p) {
175
- fs.mkdirSync(p, { recursive: true });
176
- }
177
-
178
- async function runInstall(argsOrContext = {}) {
179
- // Handle array args from CLI
180
- let opts = { noBanner: false, json: false, quiet: false, ci: false };
181
- if (Array.isArray(argsOrContext)) {
182
- const { flags } = parseGlobalFlags(argsOrContext);
183
- opts = { ...opts, ...flags };
184
- if (opts.help) {
185
- printHelp(opts);
186
- return 0;
187
- }
188
- argsOrContext = { repoRoot: opts.path || process.cwd() };
189
- }
190
-
191
- const { repoRoot } = argsOrContext;
192
- const root = repoRoot || process.cwd();
193
- const projectName = path.basename(root);
194
-
195
- // Print banner
196
- if (shouldShowBanner(opts)) {
197
- console.log(BANNER_FULL);
198
- }
199
- console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
200
- console.log(` ${c.dim}Path:${c.reset} ${root}`);
201
- console.log();
202
-
203
- const { path: pkgPath, json: pkg } = readPkg(root);
204
-
205
- // Detect environment
206
- startSpinner('Detecting environment...');
207
- const pm = detectPackageManager(root);
208
- const next = detectNext(root, pkg);
209
- const fastify = detectFastify(root, pkg);
210
- const fastifyEntry = fastify.enabled ? await detectFastifyEntry(root) : null;
211
- stopSpinner('Environment detected', true);
212
-
213
- // Build truthpack
214
- startSpinner('Building truthpack...');
215
- const truthpack = await buildTruthpack({ repoRoot: root, fastifyEntry: fastifyEntry || undefined });
216
- writeTruthpack(root, truthpack);
217
- stopSpinner('Truthpack built', true);
218
-
219
- // Write config
220
- startSpinner('Writing configuration...');
221
- const cfgDir = path.join(root, ".vibecheck");
222
- ensureDir(cfgDir);
223
-
224
- const cfg = {
225
- version: 1,
226
- detected: {
227
- packageManager: pm,
228
- next: next.enabled,
229
- fastify: fastify.enabled
230
- },
231
- fastifyEntry: fastifyEntry || null
232
- };
233
-
234
- fs.writeFileSync(path.join(cfgDir, "config.json"), JSON.stringify(cfg, null, 2), "utf8");
235
- stopSpinner('Configuration written', true);
236
-
237
- // Generate env template
238
- startSpinner('Generating env template...');
239
- const envRes = writeEnvTemplateFromTruthpack(root, truthpack);
240
- stopSpinner(envRes.wrote ? `Env template generated (+${envRes.added.length} vars)` : 'Env template up to date', true);
241
-
242
- // Add scripts
243
- startSpinner('Updating package.json scripts...');
244
- const scriptsToAdd = {
245
- "vibecheck:ctx": "vibecheck ctx",
246
- "vibecheck:ship": "vibecheck ship",
247
- "vibecheck:fix": "vibecheck fix --apply",
248
- "vibecheck:pr": "vibecheck pr"
249
- };
250
- const changedScripts = upsertScripts(pkg, scriptsToAdd);
251
- if (changedScripts.length) writePkg(pkgPath, pkg);
252
- stopSpinner(changedScripts.length ? `Added ${changedScripts.length} scripts` : 'Scripts up to date', true);
253
-
254
- // Summary
255
- printSection('DETECTED', ICONS.gear);
256
- console.log();
257
- console.log(` ${ICONS.npm} ${c.bold}Package Manager:${c.reset} ${colors.accent}${pm}${c.reset}`);
258
- console.log(` ${colors.next}${ICONS.next}${c.reset} ${c.bold}Next.js:${c.reset} ${next.enabled ? `${colors.shipGreen}yes${c.reset}` : `${c.dim}no${c.reset}`}`);
259
- console.log(` ${colors.fastify}${ICONS.fastify}${c.reset} ${c.bold}Fastify:${c.reset} ${fastify.enabled ? `${colors.shipGreen}yes${c.reset}` : `${c.dim}no${c.reset}`}`);
260
- if (fastifyEntry) {
261
- console.log(` ${c.dim} Entry:${c.reset} ${colors.accent}${fastifyEntry}${c.reset}`);
262
- }
263
-
264
- printSection('CREATED FILES', ICONS.folder);
265
- console.log();
266
- console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} ${ICONS.file} ${colors.accent}.vibecheck/config.json${c.reset}`);
267
- console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} ${ICONS.file} ${colors.accent}.vibecheck/truthpack.json${c.reset}`);
268
- if (envRes.wrote) {
269
- console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} ${ICONS.file} ${colors.accent}${envRes.outRel}${c.reset} ${c.dim}(+${envRes.added.length} vars)${c.reset}`);
270
- }
271
- if (changedScripts.length) {
272
- console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} ${ICONS.file} ${colors.accent}package.json${c.reset} ${c.dim}(${changedScripts.join(', ')})${c.reset}`);
273
- }
274
-
275
- // Installation complete
276
- console.log();
277
- console.log(` ${colors.shipGreen}${ICONS.sparkle}${c.reset} Installation complete!`);
278
- console.log();
279
- }
280
-
281
- module.exports = { runInstall };